Compare commits

...

29 Commits

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

## What it does

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

## Why is this bad?

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

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

View File

@@ -29,7 +29,7 @@ jobs:
- run: ./target/debug/ruff_dev generate-all
- run: git diff --quiet README.md || echo "::error file=README.md::This file is outdated. Run 'cargo dev generate-all'."
- run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. Run 'cargo dev generate-all'."
- run: git diff --exit-code -- README.md ruff.schema.json
- run: git diff --exit-code -- README.md ruff.schema.json docs
cargo-fmt:
name: "cargo fmt"

View File

@@ -5,6 +5,14 @@ repos:
hooks:
- id: validate-pyproject
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.16
hooks:
- id: mdformat
additional_dependencies:
- mdformat-black
- black==23.1.0 # Must be the latest version of Black
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.33.0
hooks:

View File

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

View File

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

View File

@@ -2,16 +2,16 @@
Welcome! We're happy to have you here. Thank you in advance for your contribution to Ruff.
* [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](#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
@@ -91,27 +91,27 @@ The vast majority of the code, including all lint rules, lives in the `ruff` cra
At time of writing, the repository includes the following crates:
* `crates/ruff`: library crate containing all lint rules and the core logic for running them.
* `crates/ruff_cli`: binary crate containing Ruff's command-line interface.
* `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g., `cargo dev generate-all`).
* `crates/ruff_macros`: library crate containing macros used by Ruff.
* `crates/ruff_python`: library crate implementing Python-specific functionality (e.g., lists of standard library modules by versionb).
* `crates/flake8_to_ruff`: binary crate for generating Ruff configuration from Flake8 configuration.
- `crates/ruff`: library crate containing all lint rules and the core logic for running them.
- `crates/ruff_cli`: binary crate containing Ruff's command-line interface.
- `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g., `cargo dev generate-all`).
- `crates/ruff_macros`: library crate containing macros used by Ruff.
- `crates/ruff_python`: library crate implementing Python-specific functionality (e.g., lists of standard library modules by versionb).
- `crates/flake8_to_ruff`: binary crate for generating Ruff configuration from Flake8 configuration.
### Example: Adding a new lint rule
At a high level, the steps involved in adding a new lint rule are as follows:
1. Determine a name for the new rule as per our [rule naming convention](#rule-naming-convention).
2. Create a file for your rule (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`).
3. In that file, define a violation struct. You can grep for `define_violation!` to see examples.
4. Map the violation struct to a rule code in `crates/ruff/src/registry.rs` (e.g., `E402`).
5. Define the logic for triggering the violation in `crates/ruff/src/checkers/ast.rs` (for AST-based
1. Create a file for your rule (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`).
1. In that file, define a violation struct. You can grep for `define_violation!` to see examples.
1. Map the violation struct to a rule code in `crates/ruff/src/registry.rs` (e.g., `E402`).
1. Define the logic for triggering the violation in `crates/ruff/src/checkers/ast.rs` (for AST-based
checks), `crates/ruff/src/checkers/tokens.rs` (for token-based checks), `crates/ruff/src/checkers/lines.rs`
(for text-based checks), or `crates/ruff/src/checkers/filesystem.rs` (for filesystem-based
checks).
6. Add a test fixture.
7. Update the generated files (documentation and generated code).
1. Add a test fixture.
1. Update the generated files (documentation and generated code).
To define the violation, start by creating a dedicated file for your rule under the appropriate
rule linter (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`). That file should
@@ -147,9 +147,9 @@ The rule name should make sense when read as "allow _rule-name_" or "allow _rule
This implies that rule names:
* should state the bad thing being checked for
- should state the bad thing being checked for
* should not contain instructions on what you what you should use instead
- should not contain instructions on what you what you should use instead
(these belong in the rule documentation and the `autofix_title` for rules that have autofix)
When re-implementing rules from other linters, this convention is given more importance than
@@ -191,13 +191,13 @@ To preview any changes to the documentation locally:
pip install -r docs/requirements.txt
```
2. Generate the MkDocs site with:
1. Generate the MkDocs site with:
```shell
python scripts/generate_mkdocs.py
```
3. Run the development server with:
1. Run the development server with:
```shell
mkdocs serve

17
Cargo.lock generated
View File

@@ -2141,6 +2141,7 @@ dependencies = [
"clap 4.1.6",
"insta",
"is-macro",
"itertools",
"once_cell",
"ruff_formatter",
"ruff_python",
@@ -2230,7 +2231,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=c4b67896662b16b5699a779c0e52aa0ca2587fec#c4b67896662b16b5699a779c0e52aa0ca2587fec"
source = "git+https://github.com/RustPython/RustPython.git?rev=aa8336ee94492b52458ed8e1517238e5c6c2914c#aa8336ee94492b52458ed8e1517238e5c6c2914c"
dependencies = [
"num-bigint",
"rustpython-compiler-core",
@@ -2239,7 +2240,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=c4b67896662b16b5699a779c0e52aa0ca2587fec#c4b67896662b16b5699a779c0e52aa0ca2587fec"
source = "git+https://github.com/RustPython/RustPython.git?rev=aa8336ee94492b52458ed8e1517238e5c6c2914c#aa8336ee94492b52458ed8e1517238e5c6c2914c"
dependencies = [
"ascii",
"bitflags",
@@ -2264,7 +2265,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=c4b67896662b16b5699a779c0e52aa0ca2587fec#c4b67896662b16b5699a779c0e52aa0ca2587fec"
source = "git+https://github.com/RustPython/RustPython.git?rev=aa8336ee94492b52458ed8e1517238e5c6c2914c#aa8336ee94492b52458ed8e1517238e5c6c2914c"
dependencies = [
"bincode",
"bitflags",
@@ -2281,7 +2282,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=c4b67896662b16b5699a779c0e52aa0ca2587fec#c4b67896662b16b5699a779c0e52aa0ca2587fec"
source = "git+https://github.com/RustPython/RustPython.git?rev=aa8336ee94492b52458ed8e1517238e5c6c2914c#aa8336ee94492b52458ed8e1517238e5c6c2914c"
dependencies = [
"ahash",
"anyhow",
@@ -2947,9 +2948,11 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unicode_names2"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029df4cc8238cefc911704ff8fa210853a0f3bce2694d8f51181dd41ee0f3301"
version = "0.6.0"
source = "git+https://github.com/youknowone/unicode_names2.git?tag=v0.6.0+character-alias#4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde"
dependencies = [
"phf",
]
[[package]]
name = "untrusted"

View File

@@ -14,8 +14,8 @@ libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e
once_cell = { version = "1.16.0" }
regex = { version = "1.6.0" }
rustc-hash = { version = "1.1.0" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "c4b67896662b16b5699a779c0e52aa0ca2587fec" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "c4b67896662b16b5699a779c0e52aa0ca2587fec" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "aa8336ee94492b52458ed8e1517238e5c6c2914c" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "aa8336ee94492b52458ed8e1517238e5c6c2914c" }
schemars = { version = "0.8.11" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }

120
README.md
View File

@@ -24,17 +24,17 @@ An extremely fast Python linter, written in Rust.
<i>Linting the CPython codebase from scratch.</i>
</p>
* ⚡️ 10-100x faster than existing linters
* 🐍 Installable via `pip`
* 🛠️ `pyproject.toml` support
* 🤝 Python 3.11 compatibility
* 📦 Built-in caching, to avoid re-analyzing unchanged files
* 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
* 📏 Over [500 built-in rules](https://beta.ruff.rs/docs/rules/) (and growing)
* ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
* 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
* ⌨️ First-party editor integrations for [VS Code](https://github.com/charliermarsh/ruff-vscode) and [more](https://github.com/charliermarsh/ruff-lsp)
* 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://beta.ruff.rs/docs/configuration/#pyprojecttoml-discovery)
- ⚡️ 10-100x faster than existing linters
- 🐍 Installable via `pip`
- 🛠️ `pyproject.toml` support
- 🤝 Python 3.11 compatibility
- 📦 Built-in caching, to avoid re-analyzing unchanged files
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
- 📏 Over [500 built-in rules](https://beta.ruff.rs/docs/rules/)
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
- ⌨️ First-party editor integrations for [VS Code](https://github.com/charliermarsh/ruff-vscode) and [more](https://github.com/charliermarsh/ruff-lsp)
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://beta.ruff.rs/docs/configuration/#pyprojecttoml-discovery)
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
functionality behind a single, common interface.
@@ -47,11 +47,11 @@ all while executing tens or hundreds of times faster than any individual tool.
Ruff is extremely actively developed and used in major open-source projects like:
* [pandas](https://github.com/pandas-dev/pandas)
* [FastAPI](https://github.com/tiangolo/fastapi)
* [Transformers (Hugging Face)](https://github.com/huggingface/transformers)
* [Apache Airflow](https://github.com/apache/airflow)
* [SciPy](https://github.com/scipy/scipy)
- [pandas](https://github.com/pandas-dev/pandas)
- [FastAPI](https://github.com/tiangolo/fastapi)
- [Transformers (Hugging Face)](https://github.com/huggingface/transformers)
- [Apache Airflow](https://github.com/apache/airflow)
- [SciPy](https://github.com/scipy/scipy)
...and many more.
@@ -98,13 +98,13 @@ developer of [Zulip](https://github.com/zulip/zulip):
For more, see the [documentation](https://beta.ruff.rs/docs/).
1. [Getting Started](#getting-started)
2. [Configuration](#configuration)
3. [Rules](#rules)
4. [Contributing](#contributing)
5. [Support](#support)
6. [Acknowledgements](#acknowledgements)
7. [Who's Using Ruff?](#whos-using-ruff)
8. [License](#license)
1. [Configuration](#configuration)
1. [Rules](#rules)
1. [Contributing](#contributing)
1. [Support](#support)
1. [Acknowledgements](#acknowledgements)
1. [Who's Using Ruff?](#whos-using-ruff)
1. [License](#license)
## Getting Started
@@ -186,7 +186,6 @@ exclude = [
"node_modules",
"venv",
]
per-file-ignores = {}
# Same as Black.
line-length = 88
@@ -225,6 +224,9 @@ and a [subset](https://beta.ruff.rs/docs/rules/#error-e) of the `E` category, om
stylistic rules made obsolete by the use of an autoformatter, like
[Black](https://github.com/psf/black).
If you're just getting started with Ruff, **the default rule set is a great place to start**: it
catches a wide variety of common errors (like unused imports) with zero configuration.
<!-- End section: Rules -->
For a complete enumeration of the supported rules, see [_Rules_](https://beta.ruff.rs/docs/rules/).
@@ -269,41 +271,41 @@ Ruff is released under the MIT license.
Ruff is used in a number of major open-source projects, including:
* [pandas](https://github.com/pandas-dev/pandas)
* [FastAPI](https://github.com/tiangolo/fastapi)
* [Transformers (Hugging Face)](https://github.com/huggingface/transformers)
* [Diffusers (Hugging Face)](https://github.com/huggingface/diffusers)
* [Apache Airflow](https://github.com/apache/airflow)
* [SciPy](https://github.com/scipy/scipy)
* [Zulip](https://github.com/zulip/zulip)
* [Bokeh](https://github.com/bokeh/bokeh)
* [Pydantic](https://github.com/pydantic/pydantic)
* [Dagster](https://github.com/dagster-io/dagster)
* [Dagger](https://github.com/dagger/dagger)
* [Sphinx](https://github.com/sphinx-doc/sphinx)
* [Hatch](https://github.com/pypa/hatch)
* [PDM](https://github.com/pdm-project/pdm)
* [Jupyter](https://github.com/jupyter-server/jupyter_server)
* [Great Expectations](https://github.com/great-expectations/great_expectations)
* [ONNX](https://github.com/onnx/onnx)
* [Polars](https://github.com/pola-rs/polars)
* [Ibis](https://github.com/ibis-project/ibis)
* [Synapse (Matrix)](https://github.com/matrix-org/synapse)
* [SnowCLI (Snowflake)](https://github.com/Snowflake-Labs/snowcli)
* [Dispatch (Netflix)](https://github.com/Netflix/dispatch)
* [Saleor](https://github.com/saleor/saleor)
* [Pynecone](https://github.com/pynecone-io/pynecone)
* [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
* [Home Assistant](https://github.com/home-assistant/core)
* [Pylint](https://github.com/PyCQA/pylint)
* [Cryptography (PyCA)](https://github.com/pyca/cryptography)
* [cibuildwheel (PyPA)](https://github.com/pypa/cibuildwheel)
* [build (PyPA)](https://github.com/pypa/build)
* [Babel](https://github.com/python-babel/babel)
* [featuretools](https://github.com/alteryx/featuretools)
* [meson-python](https://github.com/mesonbuild/meson-python)
* [ZenML](https://github.com/zenml-io/zenml)
* [delta-rs](https://github.com/delta-io/delta-rs)
- [pandas](https://github.com/pandas-dev/pandas)
- [FastAPI](https://github.com/tiangolo/fastapi)
- [Transformers (Hugging Face)](https://github.com/huggingface/transformers)
- [Diffusers (Hugging Face)](https://github.com/huggingface/diffusers)
- [Apache Airflow](https://github.com/apache/airflow)
- [SciPy](https://github.com/scipy/scipy)
- [Zulip](https://github.com/zulip/zulip)
- [Bokeh](https://github.com/bokeh/bokeh)
- [Pydantic](https://github.com/pydantic/pydantic)
- [Dagster](https://github.com/dagster-io/dagster)
- [Dagger](https://github.com/dagger/dagger)
- [Sphinx](https://github.com/sphinx-doc/sphinx)
- [Hatch](https://github.com/pypa/hatch)
- [PDM](https://github.com/pdm-project/pdm)
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
- [Great Expectations](https://github.com/great-expectations/great_expectations)
- [ONNX](https://github.com/onnx/onnx)
- [Polars](https://github.com/pola-rs/polars)
- [Ibis](https://github.com/ibis-project/ibis)
- [Synapse (Matrix)](https://github.com/matrix-org/synapse)
- [SnowCLI (Snowflake)](https://github.com/Snowflake-Labs/snowcli)
- [Dispatch (Netflix)](https://github.com/Netflix/dispatch)
- [Saleor](https://github.com/saleor/saleor)
- [Pynecone](https://github.com/pynecone-io/pynecone)
- [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
- [Home Assistant](https://github.com/home-assistant/core)
- [Pylint](https://github.com/PyCQA/pylint)
- [Cryptography (PyCA)](https://github.com/pyca/cryptography)
- [cibuildwheel (PyPA)](https://github.com/pypa/cibuildwheel)
- [build (PyPA)](https://github.com/pypa/build)
- [Babel](https://github.com/python-babel/babel)
- [featuretools](https://github.com/alteryx/featuretools)
- [meson-python](https://github.com/mesonbuild/meson-python)
- [ZenML](https://github.com/zenml-io/zenml)
- [delta-rs](https://github.com/delta-io/delta-rs)
## License

View File

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

View File

@@ -19,6 +19,8 @@ token = "s3cr3t"
secrete = "s3cr3t"
safe = password = "s3cr3t"
password = safe = "s3cr3t"
PASSWORD = "s3cr3t"
PassWord = "s3cr3t"
d["password"] = "s3cr3t"
d["pass"] = "s3cr3t"
@@ -68,6 +70,8 @@ passed_msg = "You have passed!"
compassion = "Please don't match!"
impassable = "You shall not pass!"
passwords = ""
PASSWORDS = ""
passphrases = ""
PassPhrases = ""
tokens = ""
secrets = ""

View File

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

View File

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

View File

@@ -74,3 +74,13 @@ elif b == b"two":
return 2
elif a == b"three":
return 3
# SIM116
if func_name == "create":
return "A"
elif func_name == "modify":
return "M"
elif func_name == "remove":
return "D"
elif func_name == "move":
return "MV"

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,9 @@ logging.error("Example log %s, %s", "foo", "bar", "baz", *args)
# do not handle calls with **kwargs
logging.error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
# do not handle keyword arguments
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d")
import warning
warning.warning("Hello %s %s", "World!")

View File

@@ -12,6 +12,9 @@ logging.error("Example log %s, %s", "foo", "bar", "baz", *args)
# do not handle calls with **kwargs
logging.error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
# do not handle keyword arguments
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d", {"objects": 1, "modifications": 1, "errors": 1})
import warning
warning.warning("Hello %s", "World!", "again")

View File

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

View File

@@ -35,3 +35,9 @@ MyType = TypedDict("MyType", {"in": int, "x-y": int})
# unpacking (OK)
c = {"c": float}
MyType = TypedDict("MyType", {"a": int, "b": str, **c})
# Empty dict literal
MyType = TypedDict("MyType", {})
# Empty dict call
MyType = TypedDict("MyType", dict())

View File

@@ -23,3 +23,9 @@ MyType = NamedTuple(
# invalid identifiers (OK)
MyType = NamedTuple("MyType", [("x-y", int), ("b", tuple[str, ...])])
# no fields
MyType = typing.NamedTuple("MyType")
# empty fields
MyType = typing.NamedTuple("MyType", [])

View File

@@ -0,0 +1,7 @@
isinstance(1, (int, float)) # UP038
issubclass("yes", (int, float, str)) # UP038
isinstance(1, int) # OK
issubclass("yes", int) # OK
isinstance(1, int | float) # OK
issubclass("yes", int | str) # OK

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
use std::path::Path;
use bitflags::bitflags;
use itertools::Itertools;
use log::error;
use once_cell::sync::Lazy;
@@ -575,33 +576,32 @@ pub fn has_non_none_keyword(keywords: &[Keyword], keyword: &str) -> bool {
})
}
bitflags! {
pub struct Exceptions: u32 {
const NAME_ERROR = 0b0000_0001;
const MODULE_NOT_FOUND_ERROR = 0b0000_0010;
}
}
/// Extract the names of all handled exceptions.
pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<CallPath> {
// TODO(charlie): Use `resolve_call_path` to avoid false positives for
// overridden builtins.
let mut handler_names = vec![];
pub fn extract_handled_exceptions(handlers: &[Excepthandler]) -> Vec<&Expr> {
let mut handled_exceptions = Vec::new();
for handler in handlers {
match &handler.node {
ExcepthandlerKind::ExceptHandler { type_, .. } => {
if let Some(type_) = type_ {
if let ExprKind::Tuple { elts, .. } = &type_.node {
for type_ in elts {
let call_path = collect_call_path(type_);
if !call_path.is_empty() {
handler_names.push(call_path);
}
handled_exceptions.push(type_);
}
} else {
let call_path = collect_call_path(type_);
if !call_path.is_empty() {
handler_names.push(call_path);
}
handled_exceptions.push(type_);
}
}
}
}
}
handler_names
handled_exceptions
}
/// Return the set of all bound argument names.
@@ -765,7 +765,7 @@ pub fn from_relative_import<'a>(module: &'a [String], name: &'a str) -> CallPath
call_path
}
/// A [`Visitor`] that collects all return statements in a function or method.
/// A [`Visitor`] that collects all `return` statements in a function or method.
#[derive(Default)]
pub struct ReturnStatementVisitor<'a> {
pub returns: Vec<Option<&'a Expr>>,
@@ -786,6 +786,48 @@ where
}
}
/// A [`Visitor`] that collects all `raise` statements in a function or method.
#[derive(Default)]
pub struct RaiseStatementVisitor<'a> {
pub raises: Vec<(Range, Option<&'a Expr>, Option<&'a Expr>)>,
}
impl<'a, 'b> Visitor<'b> for RaiseStatementVisitor<'b>
where
'b: 'a,
{
fn visit_stmt(&mut self, stmt: &'b Stmt) {
match &stmt.node {
StmtKind::Raise { exc, cause } => {
self.raises
.push((Range::from_located(stmt), exc.as_deref(), cause.as_deref()));
}
StmtKind::ClassDef { .. }
| StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::Try { .. }
| StmtKind::TryStar { .. } => {}
StmtKind::If { body, orelse, .. } => {
visitor::walk_body(self, body);
visitor::walk_body(self, orelse);
}
StmtKind::While { body, .. }
| StmtKind::With { body, .. }
| StmtKind::AsyncWith { body, .. }
| StmtKind::For { body, .. }
| StmtKind::AsyncFor { body, .. } => {
visitor::walk_body(self, body);
}
StmtKind::Match { cases, .. } => {
for case in cases {
visitor::walk_body(self, &case.body);
}
}
_ => {}
}
}
}
/// Convert a location within a file (relative to `base`) to an absolute
/// position.
pub fn to_absolute(relative: Location, base: Location) -> Location {

View File

@@ -19,7 +19,8 @@ use rustpython_parser::ast::{
use smallvec::smallvec;
use crate::ast::helpers::{
binding_range, collect_call_path, extract_handler_names, from_relative_import, to_module_path,
binding_range, collect_call_path, extract_handled_exceptions, from_relative_import,
to_module_path, Exceptions,
};
use crate::ast::operations::{extract_all_names, AllNamesFlags};
use crate::ast::relocate::relocate_expr;
@@ -109,7 +110,7 @@ pub struct Checker<'a> {
pub(crate) seen_import_boundary: bool,
futures_allowed: bool,
annotations_future_enabled: bool,
except_handlers: Vec<Vec<Vec<&'a str>>>,
handled_exceptions: Vec<Exceptions>,
// Check-specific state.
pub(crate) flake8_bugbear_seen: Vec<&'a Expr>,
}
@@ -178,7 +179,7 @@ impl<'a> Checker<'a> {
seen_import_boundary: false,
futures_allowed: true,
annotations_future_enabled: is_interface_definition,
except_handlers: vec![],
handled_exceptions: vec![],
// Check-specific state.
flake8_bugbear_seen: vec![],
}
@@ -233,6 +234,14 @@ impl<'a> Checker<'a> {
.map_or(false, |binding| binding.kind.is_builtin())
}
/// Resolves the call path, e.g. if you have a file
///
/// ```python
/// from sys import version_info as python_version
/// print(python_version)
/// ```
///
/// then `python_version` from the print statement will resolve to `sys.version_info`.
pub fn resolve_call_path<'b>(&'a self, value: &'b Expr) -> Option<CallPath<'a>>
where
'b: 'a,
@@ -962,6 +971,13 @@ where
pyupgrade::rules::rewrite_mock_import(self, stmt);
}
// If a module is imported within a `ModuleNotFoundError` body, treat that as a
// synthetic usage.
let is_handled = self
.handled_exceptions
.iter()
.any(|exceptions| exceptions.contains(Exceptions::MODULE_NOT_FOUND_ERROR));
for alias in names {
if alias.node.name == "__future__" {
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
@@ -1004,7 +1020,18 @@ where
Binding {
kind: BindingKind::SubmoduleImportation(name, full_name),
runtime_usage: None,
synthetic_usage: None,
synthetic_usage: if is_handled {
Some((
self.scopes[*(self
.scope_stack
.last()
.expect("No current scope found"))]
.id,
Range::from_located(alias),
))
} else {
None
},
typing_usage: None,
range: Range::from_located(alias),
source: Some(self.current_stmt().clone()),
@@ -1012,6 +1039,14 @@ where
},
);
} else {
// Treat explicit re-export as usage (e.g., `from .applications
// import FastAPI as FastAPI`).
let is_explicit_reexport = alias
.node
.asname
.as_ref()
.map_or(false, |asname| asname == &alias.node.name);
// Given `import foo`, `name` and `full_name` would both be `foo`.
// Given `import foo as bar`, `name` would be `bar` and `full_name` would
// be `foo`.
@@ -1022,14 +1057,7 @@ where
Binding {
kind: BindingKind::Importation(name, full_name),
runtime_usage: None,
// Treat explicit re-export as usage (e.g., `import applications
// as applications`).
synthetic_usage: if alias
.node
.asname
.as_ref()
.map_or(false, |asname| asname == &alias.node.name)
{
synthetic_usage: if is_handled || is_explicit_reexport {
Some((
self.scopes[*(self
.scope_stack
@@ -1285,6 +1313,13 @@ where
}
}
// If a module is imported within a `ModuleNotFoundError` body, treat that as a
// synthetic usage.
let is_handled = self
.handled_exceptions
.iter()
.any(|exceptions| exceptions.contains(Exceptions::MODULE_NOT_FOUND_ERROR));
for alias in names {
if let Some("__future__") = module.as_deref() {
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
@@ -1331,7 +1366,18 @@ where
Binding {
kind: BindingKind::StarImportation(*level, module.clone()),
runtime_usage: None,
synthetic_usage: None,
synthetic_usage: if is_handled {
Some((
self.scopes[*(self
.scope_stack
.last()
.expect("No current scope found"))]
.id,
Range::from_located(alias),
))
} else {
None
},
typing_usage: None,
range: Range::from_located(stmt),
source: Some(self.current_stmt().clone()),
@@ -1375,6 +1421,14 @@ where
self.check_builtin_shadowing(asname, stmt, false);
}
// Treat explicit re-export as usage (e.g., `from .applications
// import FastAPI as FastAPI`).
let is_explicit_reexport = alias
.node
.asname
.as_ref()
.map_or(false, |asname| asname == &alias.node.name);
// Given `from foo import bar`, `name` would be "bar" and `full_name` would
// be "foo.bar". Given `from foo import bar as baz`, `name` would be "baz"
// and `full_name` would be "foo.bar".
@@ -1384,33 +1438,25 @@ where
module.as_deref(),
&alias.node.name,
);
let range = Range::from_located(alias);
self.add_binding(
name,
Binding {
kind: BindingKind::FromImportation(name, full_name),
runtime_usage: None,
// Treat explicit re-export as usage (e.g., `from .applications
// import FastAPI as FastAPI`).
synthetic_usage: if alias
.node
.asname
.as_ref()
.map_or(false, |asname| asname == &alias.node.name)
{
synthetic_usage: if is_handled || is_explicit_reexport {
Some((
self.scopes[*(self
.scope_stack
.last()
.expect("No current scope found"))]
.id,
range,
Range::from_located(alias),
))
} else {
None
},
typing_usage: None,
range,
range: Range::from_located(alias),
source: Some(self.current_stmt().clone()),
context: self.execution_context(),
},
@@ -1636,7 +1682,14 @@ where
flake8_simplify::rules::needless_bool(self, stmt);
}
if self.settings.rules.enabled(&Rule::ManualDictLookup) {
flake8_simplify::rules::manual_dict_lookup(self, stmt, test, body, orelse);
flake8_simplify::rules::manual_dict_lookup(
self,
stmt,
test,
body,
orelse,
self.current_stmt_parent().map(std::convert::Into::into),
);
}
if self.settings.rules.enabled(&Rule::UseTernaryOperator) {
flake8_simplify::rules::use_ternary_operator(
@@ -2113,17 +2166,23 @@ where
orelse,
finalbody,
} => {
// TODO(charlie): The use of `smallvec` here leads to a lifetime issue.
let handler_names = extract_handler_names(handlers)
.into_iter()
.map(|call_path| call_path.to_vec())
.collect();
self.except_handlers.push(handler_names);
let mut handled_exceptions = Exceptions::empty();
for type_ in extract_handled_exceptions(handlers) {
if let Some(call_path) = self.resolve_call_path(type_) {
if call_path.as_slice() == ["", "NameError"] {
handled_exceptions |= Exceptions::NAME_ERROR;
} else if call_path.as_slice() == ["", "ModuleNotFoundError"] {
handled_exceptions |= Exceptions::MODULE_NOT_FOUND_ERROR;
}
}
}
self.handled_exceptions.push(handled_exceptions);
if self.settings.rules.enabled(&Rule::JumpStatementInFinally) {
flake8_bugbear::rules::jump_statement_in_finally(self, finalbody);
}
self.visit_body(body);
self.except_handlers.pop();
self.handled_exceptions.pop();
self.in_exception_handler = true;
for excepthandler in handlers {
@@ -2525,6 +2584,9 @@ where
if self.settings.rules.enabled(&Rule::OSErrorAlias) {
pyupgrade::rules::os_error_alias(self, &expr);
}
if self.settings.rules.enabled(&Rule::IsinstanceWithTuple) {
pyupgrade::rules::use_pep604_isinstance(self, expr, func, args);
}
// flake8-print
if self.settings.rules.enabled(&Rule::PrintFound)
@@ -2939,18 +3001,6 @@ where
flake8_pytest_style::rules::fail_call(self, func, args, keywords);
}
// ruff
if self
.settings
.rules
.enabled(&Rule::KeywordArgumentBeforeStarArgument)
{
self.diagnostics
.extend(ruff::rules::keyword_argument_before_star_argument(
args, keywords,
));
}
// flake8-simplify
if self
.settings
@@ -3384,6 +3434,16 @@ where
comparators,
);
}
if self.settings.rules.enabled(&Rule::BadVersionInfoComparison) {
flake8_pyi::rules::bad_version_info_comparison(
self,
expr,
left,
ops,
comparators,
);
}
}
}
ExprKind::Constant {
@@ -4480,13 +4540,12 @@ impl<'a> Checker<'a> {
}
// Avoid flagging if NameError is handled.
if let Some(handler_names) = self.except_handlers.last() {
if handler_names
.iter()
.any(|call_path| call_path.as_slice() == ["NameError"])
{
return;
}
if self
.handled_exceptions
.iter()
.any(|handler_names| handler_names.contains(Exceptions::NAME_ERROR))
{
return;
}
self.diagnostics.push(Diagnostic::new(

View File

@@ -8,7 +8,8 @@ use crate::registry::Diagnostic;
use crate::rules::pycodestyle::logical_lines::{iter_logical_lines, TokenFlags};
use crate::rules::pycodestyle::rules::{
extraneous_whitespace, indentation, missing_whitespace_after_keyword, space_around_operator,
whitespace_around_keywords, whitespace_before_comment,
whitespace_around_keywords, whitespace_around_named_parameter_equals,
whitespace_before_comment,
};
use crate::settings::Settings;
use crate::source_code::{Locator, Stylist};
@@ -132,6 +133,21 @@ pub fn check_logical_lines(
}
}
}
if line.flags.contains(TokenFlags::OPERATOR) {
for (location, kind) in
whitespace_around_named_parameter_equals(&line.tokens, &line.text)
{
if settings.rules.enabled(kind.rule()) {
diagnostics.push(Diagnostic {
kind,
location,
end_location: location,
fix: None,
parent: None,
});
}
}
}
for (index, kind) in indentation(
&line,

View File

@@ -8,8 +8,8 @@ use crate::rules::flake8_executable::rules::{
shebang_missing, shebang_newline, shebang_not_executable, shebang_python, shebang_whitespace,
};
use crate::rules::pycodestyle::rules::{
doc_line_too_long, line_too_long, mixed_spaces_and_tabs, no_newline_at_end_of_file,
trailing_whitespace,
doc_line_too_long, indentation_contains_tabs, line_too_long, mixed_spaces_and_tabs,
no_newline_at_end_of_file, trailing_whitespace,
};
use crate::rules::pygrep_hooks::rules::{blanket_noqa, blanket_type_ignore};
use crate::rules::pylint;
@@ -45,6 +45,7 @@ pub fn check_physical_lines(
let enforce_trailing_whitespace = settings.rules.enabled(&Rule::TrailingWhitespace);
let enforce_blank_line_contains_whitespace =
settings.rules.enabled(&Rule::BlankLineContainsWhitespace);
let enforce_indentation_contains_tabs = settings.rules.enabled(&Rule::IndentationContainsTabs);
let fix_unnecessary_coding_comment =
autofix.into() && settings.rules.should_fix(&Rule::UTF8EncodingDeclaration);
@@ -149,6 +150,12 @@ pub fn check_physical_lines(
diagnostics.push(diagnostic);
}
}
if enforce_indentation_contains_tabs {
if let Some(diagnostic) = indentation_contains_tabs(index, line) {
diagnostics.push(diagnostic);
}
}
}
if enforce_no_newline_at_end_of_file {

View File

@@ -37,6 +37,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E224") => Rule::TabAfterOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E251") => Rule::UnexpectedSpacesAroundKeywordParameterEquals,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E252") => Rule::MissingWhitespaceAroundParameterEquals,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E261") => Rule::TooFewSpacesBeforeInlineComment,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E262") => Rule::NoSpaceAfterInlineComment,
@@ -74,6 +78,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pycodestyle, "E999") => Rule::SyntaxError,
// pycodestyle warnings
(Pycodestyle, "W191") => Rule::IndentationContainsTabs,
(Pycodestyle, "W291") => Rule::TrailingWhitespace,
(Pycodestyle, "W292") => Rule::NoNewLineAtEndOfFile,
(Pycodestyle, "W293") => Rule::BlankLineContainsWhitespace,
@@ -339,6 +344,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pyupgrade, "035") => Rule::ImportReplacements,
(Pyupgrade, "036") => Rule::OutdatedVersionBlock,
(Pyupgrade, "037") => Rule::QuotedAnnotation,
(Pyupgrade, "038") => Rule::IsinstanceWithTuple,
// pydocstyle
(Pydocstyle, "100") => Rule::PublicModule,
@@ -487,6 +493,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
// flake8-pyi
(Flake8Pyi, "001") => Rule::PrefixTypeParams,
(Flake8Pyi, "006") => Rule::BadVersionInfoComparison,
(Flake8Pyi, "007") => Rule::UnrecognizedPlatformCheck,
(Flake8Pyi, "008") => Rule::UnrecognizedPlatformName,
(Flake8Pyi, "009") => Rule::PassStatementStubBody,
@@ -616,7 +623,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Ruff, "001") => Rule::AmbiguousUnicodeCharacterString,
(Ruff, "002") => Rule::AmbiguousUnicodeCharacterDocstring,
(Ruff, "003") => Rule::AmbiguousUnicodeCharacterComment,
(Ruff, "004") => Rule::KeywordArgumentBeforeStarArgument,
(Ruff, "005") => Rule::UnpackInsteadOfConcatenatingToCollectionLiteral,
(Ruff, "006") => Rule::AsyncioDanglingTask,
(Ruff, "100") => Rule::UnusedNOQA,

View File

@@ -201,8 +201,8 @@ pub fn check(contents: &str, options: JsValue) -> Result<JsValue, JsValue> {
&indexer,
&directives,
&settings,
flags::Autofix::Enabled,
flags::Noqa::Enabled,
flags::Autofix::Enabled,
);
let messages: Vec<ExpandedMessage> = diagnostics

View File

@@ -62,8 +62,8 @@ pub fn check_path(
indexer: &Indexer,
directives: &Directives,
settings: &Settings,
autofix: flags::Autofix,
noqa: flags::Noqa,
autofix: flags::Autofix,
) -> LinterResult<Vec<Diagnostic>> {
// Aggregate all diagnostics.
let mut diagnostics = vec![];
@@ -255,8 +255,8 @@ pub fn add_noqa_to_path(path: &Path, package: Option<&Path>, settings: &Settings
&indexer,
&directives,
settings,
flags::Autofix::Disabled,
flags::Noqa::Disabled,
flags::Autofix::Disabled,
);
// Log any parse errors.
@@ -287,6 +287,7 @@ pub fn lint_only(
path: &Path,
package: Option<&Path>,
settings: &Settings,
noqa: flags::Noqa,
autofix: flags::Autofix,
) -> LinterResult<Vec<Message>> {
// Tokenize once.
@@ -316,8 +317,8 @@ pub fn lint_only(
&indexer,
&directives,
settings,
noqa,
autofix,
flags::Noqa::Enabled,
);
// Convert from diagnostics to messages.
@@ -345,6 +346,7 @@ pub fn lint_fix<'a>(
contents: &'a str,
path: &Path,
package: Option<&Path>,
noqa: flags::Noqa,
settings: &Settings,
) -> Result<(LinterResult<Vec<Message>>, Cow<'a, str>, FixTable)> {
let mut transformed = Cow::Borrowed(contents);
@@ -387,8 +389,8 @@ pub fn lint_fix<'a>(
&indexer,
&directives,
settings,
noqa,
flags::Autofix::Enabled,
flags::Noqa::Enabled,
);
if iterations == 0 {

View File

@@ -59,6 +59,10 @@ ruff_macros::register_rules!(
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::TabAfterKeyword,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::UnexpectedSpacesAroundKeywordParameterEquals,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MissingWhitespaceAroundParameterEquals,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::TabBeforeKeyword,
rules::pycodestyle::rules::MultipleImportsOnOneLine,
rules::pycodestyle::rules::ModuleImportNotAtTopOfFile,
@@ -79,6 +83,7 @@ ruff_macros::register_rules!(
rules::pycodestyle::rules::IOError,
rules::pycodestyle::rules::SyntaxError,
// pycodestyle warnings
rules::pycodestyle::rules::IndentationContainsTabs,
rules::pycodestyle::rules::TrailingWhitespace,
rules::pycodestyle::rules::NoNewLineAtEndOfFile,
rules::pycodestyle::rules::BlankLineContainsWhitespace,
@@ -326,6 +331,7 @@ ruff_macros::register_rules!(
rules::pyupgrade::rules::ImportReplacements,
rules::pyupgrade::rules::OutdatedVersionBlock,
rules::pyupgrade::rules::QuotedAnnotation,
rules::pyupgrade::rules::IsinstanceWithTuple,
// pydocstyle
rules::pydocstyle::rules::PublicModule,
rules::pydocstyle::rules::PublicClass,
@@ -461,6 +467,7 @@ ruff_macros::register_rules!(
rules::flake8_errmsg::rules::DotFormatInException,
// flake8-pyi
rules::flake8_pyi::rules::PrefixTypeParams,
rules::flake8_pyi::rules::BadVersionInfoComparison,
rules::flake8_pyi::rules::UnrecognizedPlatformCheck,
rules::flake8_pyi::rules::UnrecognizedPlatformName,
rules::flake8_pyi::rules::PassStatementStubBody,
@@ -577,7 +584,6 @@ ruff_macros::register_rules!(
rules::ruff::rules::AmbiguousUnicodeCharacterString,
rules::ruff::rules::AmbiguousUnicodeCharacterDocstring,
rules::ruff::rules::AmbiguousUnicodeCharacterComment,
rules::ruff::rules::KeywordArgumentBeforeStarArgument,
rules::ruff::rules::UnpackInsteadOfConcatenatingToCollectionLiteral,
rules::ruff::rules::AsyncioDanglingTask,
rules::ruff::rules::UnusedNOQA,
@@ -803,6 +809,7 @@ impl Rule {
| Rule::ShebangPython
| Rule::ShebangWhitespace
| Rule::TrailingWhitespace
| Rule::IndentationContainsTabs
| Rule::BlankLineContainsWhitespace => &LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring
@@ -848,6 +855,8 @@ impl Rule {
| Rule::UnexpectedIndentationComment
| Rule::WhitespaceAfterOpenBracket
| Rule::WhitespaceBeforeCloseBracket
| Rule::UnexpectedSpacesAroundKeywordParameterEquals
| Rule::MissingWhitespaceAroundParameterEquals
| Rule::WhitespaceBeforePunctuation => &LintSource::LogicalLines,
_ => &LintSource::Ast,
}

View File

@@ -92,5 +92,7 @@ static REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
// TODO(charlie): Remove by 2023-04-01.
("TYP", "TCH"),
("TYP001", "TCH001"),
// TODO(charlie): Remove by 2023-06-01.
("RUF004", "B026"),
])
});

View File

@@ -398,9 +398,9 @@ define_violation!(
/// ```
///
/// ## References
/// * [PEP 484](https://www.python.org/dev/peps/pep-0484/#the-any-type)
/// * [`typing.Any`](https://docs.python.org/3/library/typing.html#typing.Any)
/// * [Mypy: The Any type](https://mypy.readthedocs.io/en/stable/kinds_of_types.html#the-any-type)
/// - [PEP 484](https://www.python.org/dev/peps/pep-0484/#the-any-type)
/// - [`typing.Any`](https://docs.python.org/3/library/typing.html#typing.Any)
/// - [Mypy: The Any type](https://mypy.readthedocs.io/en/stable/kinds_of_types.html#the-any-type)
pub struct AnyType {
pub name: String,
}

View File

@@ -37,8 +37,8 @@ pub struct Options {
/// Whether to suppress `ANN200`-level violations for functions that meet
/// either of the following criteria:
///
/// * Contain no `return` statement.
/// * Explicit `return` statement(s) all return `None` (explicitly or
/// - Contain no `return` statement.
/// - Explicit `return` statement(s) all return `None` (explicitly or
/// implicitly).
pub suppress_none_returning: Option<bool>,
#[option(

View File

@@ -4,8 +4,9 @@ use rustpython_parser::ast::{Constant, Expr, ExprKind};
use crate::checkers::ast::Checker;
static PASSWORD_CANDIDATE_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(^|_)(pas+wo?r?d|pass(phrase)?|pwd|token|secrete?)($|_)").unwrap());
static PASSWORD_CANDIDATE_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(^|_)(?i)(pas+wo?r?d|pass(phrase)?|pwd|token|secrete?)($|_)").unwrap()
});
pub fn string_literal(expr: &Expr) -> Option<&str> {
match &expr.node {
@@ -17,7 +18,6 @@ pub fn string_literal(expr: &Expr) -> Option<&str> {
}
}
// Maybe use regex for this?
pub fn matches_password_name(string: &str) -> bool {
PASSWORD_CANDIDATE_REGEX.is_match(string)
}

View File

@@ -33,8 +33,8 @@ 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)
/// - [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;
);
impl Violation for HardcodedSQLExpression {

View File

@@ -1,5 +1,5 @@
---
source: src/rules/flake8_bandit/mod.rs
source: crates/ruff/src/rules/flake8_bandit/mod.rs
expression: diagnostics
---
- kind:
@@ -105,46 +105,46 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 23
column: 16
end_location:
row: 23
column: 24
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 24
column: 12
end_location:
row: 24
column: 20
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 25
column: 14
end_location:
row: 25
column: 22
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 26
row: 22
column: 11
end_location:
row: 26
row: 22
column: 19
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 23
column: 11
end_location:
row: 23
column: 19
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 25
column: 16
end_location:
row: 25
column: 24
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 26
column: 12
end_location:
row: 26
column: 20
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
@@ -161,9 +161,31 @@ expression: diagnostics
string: s3cr3t
location:
row: 28
column: 13
column: 11
end_location:
row: 28
column: 19
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 29
column: 14
end_location:
row: 29
column: 22
fix: ~
parent: ~
- kind:
HardcodedPasswordString:
string: s3cr3t
location:
row: 30
column: 13
end_location:
row: 30
column: 21
fix: ~
parent: ~
@@ -171,10 +193,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 29
row: 31
column: 15
end_location:
row: 29
row: 31
column: 23
fix: ~
parent: ~
@@ -182,10 +204,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 30
row: 32
column: 23
end_location:
row: 30
row: 32
column: 31
fix: ~
parent: ~
@@ -193,10 +215,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 31
row: 33
column: 23
end_location:
row: 31
row: 33
column: 31
fix: ~
parent: ~
@@ -204,10 +226,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 35
row: 37
column: 15
end_location:
row: 35
row: 37
column: 23
fix: ~
parent: ~
@@ -215,10 +237,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 39
row: 41
column: 19
end_location:
row: 39
row: 41
column: 27
fix: ~
parent: ~
@@ -226,10 +248,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 40
row: 42
column: 16
end_location:
row: 40
row: 42
column: 24
fix: ~
parent: ~
@@ -237,10 +259,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 41
row: 43
column: 17
end_location:
row: 41
row: 43
column: 25
fix: ~
parent: ~
@@ -248,10 +270,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 42
row: 44
column: 14
end_location:
row: 42
row: 44
column: 22
fix: ~
parent: ~
@@ -259,10 +281,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 43
row: 45
column: 17
end_location:
row: 43
row: 45
column: 25
fix: ~
parent: ~
@@ -270,10 +292,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 44
row: 46
column: 16
end_location:
row: 44
row: 46
column: 24
fix: ~
parent: ~
@@ -281,10 +303,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 45
row: 47
column: 18
end_location:
row: 45
row: 47
column: 26
fix: ~
parent: ~
@@ -292,10 +314,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 47
row: 49
column: 12
end_location:
row: 47
row: 49
column: 20
fix: ~
parent: ~
@@ -303,10 +325,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 48
row: 50
column: 9
end_location:
row: 48
row: 50
column: 17
fix: ~
parent: ~
@@ -314,10 +336,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 49
row: 51
column: 10
end_location:
row: 49
row: 51
column: 18
fix: ~
parent: ~
@@ -325,10 +347,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 50
row: 52
column: 7
end_location:
row: 50
row: 52
column: 15
fix: ~
parent: ~
@@ -336,10 +358,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 51
row: 53
column: 10
end_location:
row: 51
row: 53
column: 18
fix: ~
parent: ~
@@ -347,10 +369,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 52
row: 54
column: 9
end_location:
row: 52
row: 54
column: 17
fix: ~
parent: ~
@@ -358,10 +380,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 53
row: 55
column: 11
end_location:
row: 53
row: 55
column: 19
fix: ~
parent: ~
@@ -369,10 +391,10 @@ expression: diagnostics
HardcodedPasswordString:
string: s3cr3t
location:
row: 54
row: 56
column: 20
end_location:
row: 54
row: 56
column: 28
fix: ~
parent: ~
@@ -380,10 +402,10 @@ expression: diagnostics
HardcodedPasswordString:
string: "1\n2"
location:
row: 56
row: 58
column: 12
end_location:
row: 56
row: 58
column: 18
fix: ~
parent: ~
@@ -391,10 +413,10 @@ expression: diagnostics
HardcodedPasswordString:
string: "3\t4"
location:
row: 59
row: 61
column: 12
end_location:
row: 59
row: 61
column: 18
fix: ~
parent: ~
@@ -402,10 +424,10 @@ expression: diagnostics
HardcodedPasswordString:
string: "5\r6"
location:
row: 62
row: 64
column: 12
end_location:
row: 62
row: 64
column: 18
fix: ~
parent: ~

View File

@@ -1,10 +1,10 @@
use rustpython_parser::ast::{ExprKind, Stmt};
use ruff_macros::{define_violation, derive_message_formats};
use ruff_python::str::is_lower;
use rustpython_parser::ast::{ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::helpers::RaiseStatementVisitor;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
use crate::violation::Violation;
@@ -16,62 +16,32 @@ impl Violation for RaiseWithoutFromInsideExcept {
#[derive_message_formats]
fn message(&self) -> String {
format!(
"Within an except clause, raise exceptions with `raise ... from err` or `raise ... \
"Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... \
from None` to distinguish them from errors in exception handling"
)
}
}
struct RaiseVisitor {
diagnostics: Vec<Diagnostic>,
}
/// B904
pub fn raise_without_from_inside_except(checker: &mut Checker, body: &[Stmt]) {
let raises = {
let mut visitor = RaiseStatementVisitor::default();
visitor::walk_body(&mut visitor, body);
visitor.raises
};
impl<'a> Visitor<'a> for RaiseVisitor {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
match &stmt.node {
StmtKind::Raise {
exc: Some(exc),
cause: None,
} => match &exc.node {
ExprKind::Name { id, .. } if is_lower(id) => {}
_ => {
self.diagnostics.push(Diagnostic::new(
RaiseWithoutFromInsideExcept,
Range::from_located(stmt),
));
}
},
StmtKind::ClassDef { .. }
| StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::Try { .. }
| StmtKind::TryStar { .. } => {}
StmtKind::If { body, orelse, .. } => {
visitor::walk_body(self, body);
visitor::walk_body(self, orelse);
}
StmtKind::While { body, .. }
| StmtKind::With { body, .. }
| StmtKind::AsyncWith { body, .. }
| StmtKind::For { body, .. }
| StmtKind::AsyncFor { body, .. } => {
visitor::walk_body(self, body);
}
StmtKind::Match { cases, .. } => {
for case in cases {
visitor::walk_body(self, &case.body);
for (range, exc, cause) in raises {
if cause.is_none() {
if let Some(exc) = exc {
match &exc.node {
ExprKind::Name { id, .. } if is_lower(id) => {}
_ => {
checker
.diagnostics
.push(Diagnostic::new(RaiseWithoutFromInsideExcept, range));
}
}
}
_ => {}
}
}
}
/// B904
pub fn raise_without_from_inside_except(checker: &mut Checker, body: &[Stmt]) {
let mut visitor = RaiseVisitor {
diagnostics: vec![],
};
visitor::walk_body(&mut visitor, body);
checker.diagnostics.extend(visitor.diagnostics);
}

View File

@@ -23,7 +23,7 @@ define_violation!(
///
/// ## Options
///
/// * `flake8-builtins.builtins-ignorelist`
/// - `flake8-builtins.builtins-ignorelist`
///
/// ## Example
/// ```python
@@ -45,7 +45,7 @@ define_violation!(
/// return result
/// ```
///
/// * [Why is it a bad idea to name a variable `id` in Python?_](https://stackoverflow.com/questions/77552/id-is-a-bad-variable-name-in-python)
/// - [_Why is it a bad idea to name a variable `id` in Python?_](https://stackoverflow.com/questions/77552/id-is-a-bad-variable-name-in-python)
pub struct BuiltinVariableShadowing {
pub name: String,
}
@@ -73,7 +73,7 @@ define_violation!(
///
/// ## Options
///
/// * `flake8-builtins.builtins-ignorelist`
/// - `flake8-builtins.builtins-ignorelist`
///
/// ## Example
/// ```python
@@ -128,7 +128,7 @@ define_violation!(
///
/// ## Options
///
/// * `flake8-builtins.builtins-ignorelist`
/// - `flake8-builtins.builtins-ignorelist`
///
/// ## Example
/// ```python

View File

@@ -32,19 +32,19 @@ define_violation!(
///
/// 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)`.
/// - 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,

View File

@@ -32,11 +32,11 @@ define_violation!(
///
/// 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
/// - 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
/// - 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
/// - 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,

View File

@@ -20,6 +20,7 @@ define_violation!(
/// ```python
/// from django.forms import ModelForm
///
///
/// class PostForm(ModelForm):
/// class Meta:
/// model = Post
@@ -30,6 +31,7 @@ define_violation!(
/// ```python
/// from django.forms import ModelForm
///
///
/// class PostForm(ModelForm):
/// class Meta:
/// model = Post

View File

@@ -26,19 +26,21 @@ define_violation!(
/// ```python
/// from django.db import models
///
///
/// class MyModel(models.Model):
/// field = models.CharField(max_length=255)
/// 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}"
/// class MyModel(models.Model):
/// field = models.CharField(max_length=255)
///
/// def __str__(self):
/// return f"{self.field}"
/// ```
pub struct ModelWithoutDunderStr;
);

View File

@@ -22,10 +22,11 @@ define_violation!(
/// 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
/// pass
/// ```
///
/// Use instead:
@@ -33,6 +34,7 @@ define_violation!(
/// 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):

View File

@@ -28,14 +28,16 @@ define_violation!(
/// ```python
/// from django.db import models
///
///
/// class MyModel(models.Model):
/// field = models.CharField(max_length=255, null=True)
/// 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="")
/// ```

View File

@@ -24,7 +24,7 @@ define_violation!(
/// ```
///
/// Python will produce a traceback like:
/// ```python
/// ```console
/// Traceback (most recent call last):
/// File "tmp.py", line 2, in <module>
/// raise RuntimeError("Some value is incorrect")
@@ -38,7 +38,7 @@ define_violation!(
/// ```
///
/// Which will produce a traceback like:
/// ```python
/// ```console
/// Traceback (most recent call last):
/// File "tmp.py", line 3, in <module>
/// raise RuntimeError(msg)
@@ -72,7 +72,7 @@ define_violation!(
/// ```
///
/// Python will produce a traceback like:
/// ```python
/// ```console
/// Traceback (most recent call last):
/// File "tmp.py", line 2, in <module>
/// raise RuntimeError(f"{sub!r} is incorrect")
@@ -87,8 +87,7 @@ define_violation!(
/// ```
///
/// Which will produce a traceback like:
/// ```python
/// Traceback (most recent call last):
/// ```console
/// File "tmp.py", line 3, in <module>
/// raise RuntimeError(msg)
/// RuntimeError: 'Some value' is incorrect
@@ -122,7 +121,7 @@ define_violation!(
/// ```
///
/// Python will produce a traceback like:
/// ```python
/// ```console
/// Traceback (most recent call last):
/// File "tmp.py", line 2, in <module>
/// raise RuntimeError("'{}' is incorrect".format(sub))
@@ -137,7 +136,7 @@ define_violation!(
/// ```
///
/// Which will produce a traceback like:
/// ```python
/// ```console
/// Traceback (most recent call last):
/// File "tmp.py", line 3, in <module>
/// raise RuntimeError(msg)

View File

@@ -56,7 +56,7 @@ define_violation!(
/// to `false`.
///
/// ## Options
/// * `flake8-implicit-str-concat.allow-multiline`
/// - `flake8-implicit-str-concat.allow-multiline`
///
/// ## Example
/// ```python
@@ -73,7 +73,7 @@ define_violation!(
/// ```
///
/// ## References
/// * [PEP 8](https://peps.python.org/pep-0008/#maximum-line-length)
/// - [PEP 8](https://peps.python.org/pep-0008/#maximum-line-length)
pub struct MultiLineImplicitStringConcatenation;
);
impl Violation for MultiLineImplicitStringConcatenation {

View File

@@ -25,7 +25,7 @@ define_violation!(
/// the absence of the `__init__.py` file is probably an oversight.
///
/// ## Options
/// * `namespace-packages`
/// - `namespace-packages`
pub struct ImplicitNamespacePackage {
pub filename: String,
}

View File

@@ -72,7 +72,7 @@ define_violation!(
/// For example, compare the performance of `all` with a list comprehension against that
/// of a generator (~40x faster here):
///
/// ```python
/// ```console
/// In [1]: %timeit all([i for i in range(1000)])
/// 8.14 µs ± 25.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
///

View File

@@ -15,6 +15,8 @@ mod tests {
#[test_case(Rule::PrefixTypeParams, Path::new("PYI001.pyi"))]
#[test_case(Rule::PrefixTypeParams, Path::new("PYI001.py"))]
#[test_case(Rule::BadVersionInfoComparison, Path::new("PYI006.pyi"))]
#[test_case(Rule::BadVersionInfoComparison, Path::new("PYI006.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"))]

View File

@@ -0,0 +1,83 @@
use rustpython_parser::ast::{Cmpop, Expr};
use ruff_macros::{define_violation, derive_message_formats};
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
use crate::violation::Violation;
use crate::Range;
define_violation!(
/// ## What it does
/// Checks for usages of comparators other than `<` and `>=` for
/// `sys.version_info` checks in `.pyi` files. All other comparators, such
/// as `>`, `<=`, and `==`, are banned.
///
/// ## Why is this bad?
/// Comparing `sys.version_info` with `==` or `<=` has unexpected behavior
/// and can lead to bugs.
///
/// For example, `sys.version_info > (3, 8)` will also match `3.8.10`,
/// while `sys.version_info <= (3, 8)` will _not_ match `3.8.10`:
///
/// ```python
/// >>> import sys
/// >>> print(sys.version_info)
/// sys.version_info(major=3, minor=8, micro=10, releaselevel='final', serial=0)
/// >>> print(sys.version_info > (3, 8))
/// True
/// >>> print(sys.version_info == (3, 8))
/// False
/// >>> print(sys.version_info <= (3, 8))
/// False
/// >>> print(sys.version_info in (3, 8))
/// False
/// ```
///
/// ## Example
/// ```python
/// import sys
///
/// if sys.version_info > (3, 8):
/// ...
/// ```
///
/// Use instead:
/// ```python
/// import sys
///
/// if sys.version_info >= (3, 9):
/// ...
/// ```
pub struct BadVersionInfoComparison;
);
impl Violation for BadVersionInfoComparison {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use `<` or `>=` for version info comparisons")
}
}
/// PYI006
pub fn bad_version_info_comparison(
checker: &mut Checker,
expr: &Expr,
left: &Expr,
ops: &[Cmpop],
comparators: &[Expr],
) {
let ([op], [_right]) = (ops, comparators) else {
return;
};
if !checker.resolve_call_path(left).map_or(false, |call_path| {
call_path.as_slice() == ["sys", "version_info"]
}) {
return;
}
if !matches!(op, Cmpop::Lt | Cmpop::GtE) {
let diagnostic = Diagnostic::new(BadVersionInfoComparison, Range::from_located(expr));
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -1,3 +1,4 @@
pub use bad_version_info_comparison::{bad_version_info_comparison, BadVersionInfoComparison};
pub use docstring_in_stubs::{docstring_in_stubs, DocstringInStub};
pub use non_empty_stub_body::{non_empty_stub_body, NonEmptyStubBody};
pub use pass_statement_stub_body::{pass_statement_stub_body, PassStatementStubBody};
@@ -10,10 +11,10 @@ pub use unrecognized_platform::{
unrecognized_platform, UnrecognizedPlatformCheck, UnrecognizedPlatformName,
};
mod bad_version_info_comparison;
mod docstring_in_stubs;
mod non_empty_stub_body;
mod pass_statement_stub_body;
mod prefix_type_params;
mod unrecognized_platform;
mod simple_defaults;
mod unrecognized_platform;

View File

@@ -22,21 +22,25 @@ define_violation!(
/// ## Example
/// ```python
/// if sys.platform.startswith("linux"):
/// # Linux specific definitions
/// # Linux specific definitions
/// ...
/// else:
/// # Posix specific definitions
/// # 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)
/// - [PEP 484](https://peps.python.org/pep-0484/#version-and-platform-checking)
pub struct UnrecognizedPlatformCheck;
);
impl Violation for UnrecognizedPlatformCheck {
@@ -68,11 +72,11 @@ define_violation!(
/// Use instead:
/// ```python
/// if sys.platform == "linux":
/// ...
/// ...
/// ```
///
/// ## References
/// * [PEP 484](https://peps.python.org/pep-0484/#version-and-platform-checking)
/// - [PEP 484](https://peps.python.org/pep-0484/#version-and-platform-checking)
pub struct UnrecognizedPlatformName {
pub platform: String,
}

View File

@@ -0,0 +1,6 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
expression: diagnostics
---
[]

View File

@@ -0,0 +1,65 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
expression: diagnostics
---
- kind:
BadVersionInfoComparison: ~
location:
row: 8
column: 3
end_location:
row: 8
column: 29
fix: ~
parent: ~
- kind:
BadVersionInfoComparison: ~
location:
row: 10
column: 3
end_location:
row: 10
column: 29
fix: ~
parent: ~
- kind:
BadVersionInfoComparison: ~
location:
row: 12
column: 3
end_location:
row: 12
column: 30
fix: ~
parent: ~
- kind:
BadVersionInfoComparison: ~
location:
row: 14
column: 3
end_location:
row: 14
column: 30
fix: ~
parent: ~
- kind:
BadVersionInfoComparison: ~
location:
row: 16
column: 3
end_location:
row: 16
column: 29
fix: ~
parent: ~
- kind:
BadVersionInfoComparison: ~
location:
row: 18
column: 3
end_location:
row: 18
column: 27
fix: ~
parent: ~

View File

@@ -39,6 +39,7 @@ define_violation!(
/// def test_foo():
/// assert something and something_else
///
///
/// def test_bar():
/// assert not (something or something_else)
/// ```
@@ -49,6 +50,7 @@ define_violation!(
/// assert something
/// assert something_else
///
///
/// def test_bar():
/// assert not something
/// assert not something_else

View File

@@ -48,11 +48,11 @@ pub struct Options {
/// Expected type for multiple argument names in `@pytest.mark.parametrize`.
/// The following values are supported:
///
/// * `csv` — a comma-separated list, e.g.
/// - `csv` — a comma-separated list, e.g.
/// `@pytest.mark.parametrize('name1,name2', ...)`
/// * `tuple` (default) — e.g. `@pytest.mark.parametrize(('name1', 'name2'),
/// ...)`
/// * `list` — e.g. `@pytest.mark.parametrize(['name1', 'name2'], ...)`
/// - `tuple` (default) — e.g.
/// `@pytest.mark.parametrize(('name1', 'name2'), ...)`
/// - `list` — e.g. `@pytest.mark.parametrize(['name1', 'name2'], ...)`
pub parametrize_names_type: Option<types::ParametrizeNameType>,
#[option(
default = "list",
@@ -62,8 +62,8 @@ pub struct Options {
/// Expected type for the list of values rows in `@pytest.mark.parametrize`.
/// The following values are supported:
///
/// * `tuple` — e.g. `@pytest.mark.parametrize('name', (1, 2, 3))`
/// * `list` (default) — e.g. `@pytest.mark.parametrize('name', [1, 2, 3])`
/// - `tuple` — e.g. `@pytest.mark.parametrize('name', (1, 2, 3))`
/// - `list` (default) — e.g. `@pytest.mark.parametrize('name', [1, 2, 3])`
pub parametrize_values_type: Option<types::ParametrizeValuesType>,
#[option(
default = "tuple",
@@ -73,10 +73,10 @@ pub struct Options {
/// Expected type for each row of values in `@pytest.mark.parametrize` in
/// case of multiple parameters. The following values are supported:
///
/// * `tuple` (default) — e.g. `@pytest.mark.parametrize(('name1', 'name2'),
/// [(1, 2), (3, 4)])`
/// * `list` — e.g. `@pytest.mark.parametrize(('name1', 'name2'), [[1, 2],
/// [3, 4]])`
/// - `tuple` (default) — e.g.
/// `@pytest.mark.parametrize(('name1', 'name2'), [(1, 2), (3, 4)])`
/// - `list` — e.g.
/// `@pytest.mark.parametrize(('name1', 'name2'), [[1, 2], [3, 4]])`
pub parametrize_values_row_type: Option<types::ParametrizeValuesRowType>,
#[option(
default = r#"["BaseException", "Exception", "ValueError", "OSError", "IOError", "EnvironmentError", "socket.error"]"#,

View File

@@ -22,7 +22,7 @@ define_violation!(
/// strings, but be consistent.
///
/// ## Options
/// * `flake8-quotes.inline-quotes`
/// - `flake8-quotes.inline-quotes`
///
/// ## Example
/// ```python
@@ -67,7 +67,7 @@ define_violation!(
/// strings, but be consistent.
///
/// ## Options
/// * `flake8-quotes.multiline-quotes`
/// - `flake8-quotes.multiline-quotes`
///
/// ## Example
/// ```python
@@ -115,7 +115,7 @@ define_violation!(
/// strings, but be consistent.
///
/// ## Options
/// * `flake8-quotes.docstring-quotes`
/// - `flake8-quotes.docstring-quotes`
///
/// ## Example
/// ```python

View File

@@ -23,7 +23,7 @@ define_violation!(
/// behavior. Instead, use the class's public interface.
///
/// ## Options
/// * `flake8-self.ignore-names`
/// - `flake8-self.ignore-names`
///
/// ## Example
/// ```python
@@ -31,6 +31,7 @@ define_violation!(
/// def __init__(self):
/// self._private_member = "..."
///
///
/// var = Class()
/// print(var._private_member)
/// ```
@@ -41,12 +42,13 @@ define_violation!(
/// def __init__(self):
/// self.public_member = "..."
///
///
/// var = Class()
/// print(var.public_member)
/// ```
///
/// ## References
/// * [_What is the meaning of single or double underscores before an object name?_](https://stackoverflow.com/questions/1301346/what-is-the-meaning-of-single-and-double-underscore-before-an-object-name)
/// - [_What is the meaning of single or double underscores before an object name?_](https://stackoverflow.com/questions/1301346/what-is-the-meaning-of-single-and-double-underscore-before-an-object-name)
pub struct PrivateMemberAccess {
pub access: String,
}

View File

@@ -38,8 +38,7 @@ define_violation!(
/// ```
///
/// ## References
/// * [Python: "isinstance"](https://docs.python.org/3/library/functions.html#isinstance)
/// ```
/// - [Python: "isinstance"](https://docs.python.org/3/library/functions.html#isinstance)
pub struct DuplicateIsinstanceCall {
pub name: String,
}

View File

@@ -76,20 +76,20 @@ impl Violation for NeedlessBool {
}
define_violation!(
/// ### What it does
/// ## What it does
/// Checks for three or more consecutive if-statements with direct returns
///
/// ### Why is this bad?
/// ## Why is this bad?
/// These can be simplified by using a dictionary
///
/// ### Example
/// ## Example
/// ```python
/// if x == 1:
/// return "Hello"
/// elif x == 2:
/// return "Goodbye"
/// else:
/// return "Goodnight"
/// return "Goodnight"
/// ```
///
/// Use instead:
@@ -129,14 +129,14 @@ impl Violation for UseTernaryOperator {
}
define_violation!(
/// ### What it does
/// ## What it does
/// Checks for `if` branches with identical arm bodies.
///
/// ### Why is this bad?
/// ## 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
/// ## Example
/// ```python
/// if x == 1:
/// print("Hello")
@@ -592,6 +592,7 @@ pub fn manual_dict_lookup(
test: &Expr,
body: &[Stmt],
orelse: &[Stmt],
parent: Option<&Stmt>,
) {
// Throughout this rule:
// * Each if-statement's test must consist of a constant equality check with the same variable.
@@ -633,6 +634,36 @@ pub fn manual_dict_lookup(
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 mut constants: FxHashSet<ComparableConstant> = FxHashSet::default();
constants.insert(constant.into());

View File

@@ -38,7 +38,7 @@ define_violation!(
/// ```
///
/// ## References
/// * [Python: "The with statement"](https://docs.python.org/3/reference/compound_stmts.html#the-with-statement)
/// - [Python: "The with statement"](https://docs.python.org/3/reference/compound_stmts.html#the-with-statement)
pub struct MultipleWithStatements {
pub fixable: bool,
}

View File

@@ -1,7 +1,9 @@
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Excepthandler, ExcepthandlerKind, Located, Stmt, StmtKind};
use ruff_macros::{define_violation, derive_message_formats};
use crate::ast::helpers;
use crate::ast::helpers::compose_call_path;
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
@@ -52,9 +54,9 @@ pub fn use_contextlib_suppress(
let ExcepthandlerKind::ExceptHandler { body, .. } = &handler.node;
if body.len() == 1 {
if matches!(body[0].node, StmtKind::Pass) {
let handler_names: Vec<_> = helpers::extract_handler_names(handlers)
let handler_names: Vec<String> = helpers::extract_handled_exceptions(handlers)
.into_iter()
.map(|call_path| helpers::format_call_path(&call_path))
.filter_map(compose_call_path)
.collect();
let exception = if handler_names.is_empty() {
"Exception".to_string()

View File

@@ -160,8 +160,8 @@ pub fn yoda_conditions(
if checker.patch(diagnostic.kind.rule()) {
diagnostic.amend(Fix::replacement(
suggestion,
left.location,
right.end_location.unwrap(),
expr.location,
expr.end_location.unwrap(),
));
}
checker.diagnostics.push(diagnostic);

View File

@@ -62,4 +62,14 @@ expression: diagnostics
column: 23
fix: ~
parent: ~
- kind:
ManualDictLookup: ~
location:
row: 79
column: 0
end_location:
row: 86
column: 15
fix: ~
parent: ~

View File

@@ -216,6 +216,24 @@ expression: diagnostics
column: 0
end_location:
row: 13
column: 17
column: 18
parent: ~
- kind:
YodaConditions:
suggestion: (60 * 60) < SomeClass().settings.SOME_CONSTANT_VALUE
location:
row: 14
column: 0
end_location:
row: 14
column: 52
fix:
content: (60 * 60) < SomeClass().settings.SOME_CONSTANT_VALUE
location:
row: 14
column: 0
end_location:
row: 14
column: 52
parent: ~

View File

@@ -36,7 +36,7 @@ define_violation!(
/// automatic way.
///
/// ## Options
/// * `flake8-tidy-imports.banned-api`
/// - `flake8-tidy-imports.banned-api`
pub struct BannedApi {
pub name: String,
pub message: String,

View File

@@ -47,7 +47,7 @@ define_violation!(
/// > ```
///
/// ## Options
/// * `flake8-tidy-imports.ban-relative-imports`
/// - `flake8-tidy-imports.ban-relative-imports`
///
/// ## Example
/// ```python

View File

@@ -22,8 +22,9 @@ define_violation!(
///
/// ## Why is this bad?
/// In some projects, certain imports are required to be present in all
/// files. For example, some projects assume that `from __future__
/// import annotations` is enabled, and thus require that import to be
/// files. For example, some projects assume that
/// `from __future__ import annotations` is enabled,
/// and thus require that import to be
/// present in all files. Omitting a "required" import (as specified by
/// the user) can cause errors or unexpected behavior.
///

View File

@@ -180,8 +180,8 @@ pub struct Options {
///
/// The default ("furthest-to-closest") is equivalent to isort's
/// `reverse-relative` default (`reverse-relative = false`); setting
/// this to "closest-to-furthest" is equivalent to isort's `reverse-relative
/// = true`.
/// this to "closest-to-furthest" is equivalent to isort's
/// `reverse-relative = true`.
pub relative_imports_order: Option<RelativeImportsOrder>,
#[option(
default = r#"[]"#,

View File

@@ -20,7 +20,7 @@ define_violation!(
/// Functions with a high complexity are hard to understand and maintain.
///
/// ## Options
/// * `mccabe.max-complexity`
/// - `mccabe.max-complexity`
///
/// ## Example
/// ```python

View File

@@ -41,8 +41,8 @@ mod tests {
&indexer,
&directives,
&settings,
flags::Autofix::Enabled,
flags::Noqa::Enabled,
flags::Autofix::Enabled,
);
let actual = diagnostics
.iter()

View File

@@ -31,7 +31,7 @@ define_violation!(
/// ```
///
/// ## References
/// * [_Why You Should Probably Never Use pandas inplace=True_](https://towardsdatascience.com/why-you-should-probably-never-use-pandas-inplace-true-9f9f211849e4)
/// - [_Why You Should Probably Never Use pandas inplace=True_](https://towardsdatascience.com/why-you-should-probably-never-use-pandas-inplace-true-9f9f211849e4)
pub struct UseOfInplaceArgument;
);
impl AlwaysAutofixableViolation for UseOfInplaceArgument {

View File

@@ -17,7 +17,7 @@ define_violation!(
///
/// > ..."magic" objects or attributes that live in user-controlled
/// > namespaces. E.g. `__init__`, `__import__` or `__file__`. Never invent
/// such names; only use them as documented.
/// > such names; only use them as documented.
///
/// ## Example
/// ```python

View File

@@ -16,16 +16,16 @@ define_violation!(
/// [PEP 8] recommends the use of `cls` as the first argument for all class
/// methods:
///
/// > Always use cls for the first argument to class methods.
/// > Always use `cls` for the first argument to class methods.
/// >
/// > If a function arguments name clashes with a reserved keyword, it is generally better to
/// > append a single trailing underscore rather than use an abbreviation or spelling corruption.
/// > Thus class_ is better than clss. (Perhaps better is to avoid such clashes by using a synonym.)
/// > Thus `class_` is better than `clss`. (Perhaps better is to avoid such clashes by using a synonym.)
///
/// ## Options
/// * `pep8-naming.classmethod-decorators`
/// * `pep8-naming.staticmethod-decorators`
/// * `pep8-naming.ignore-names`
/// - `pep8-naming.classmethod-decorators`
/// - `pep8-naming.staticmethod-decorators`
/// - `pep8-naming.ignore-names`
///
/// ## Example
/// ```python

View File

@@ -20,12 +20,12 @@ define_violation!(
/// >
/// > If a function arguments name clashes with a reserved keyword, it is generally better to
/// > append a single trailing underscore rather than use an abbreviation or spelling corruption.
/// > Thus class_ is better than clss. (Perhaps better is to avoid such clashes by using a synonym.)
/// > Thus `class_` is better than `clss`. (Perhaps better is to avoid such clashes by using a synonym.)
///
/// ## Options
/// * `pep8-naming.classmethod-decorators`
/// * `pep8-naming.staticmethod-decorators`
/// * `pep8-naming.ignore-names`
/// - `pep8-naming.classmethod-decorators`
/// - `pep8-naming.staticmethod-decorators`
/// - `pep8-naming.ignore-names`
///
/// ## Example
/// ```python

View File

@@ -19,7 +19,7 @@ define_violation!(
/// > prevailing style (e.g. threading.py), to retain backwards compatibility.
///
/// ## Options
/// * `pep8-naming.ignore-names`
/// - `pep8-naming.ignore-names`
///
/// ## Example
/// ```python

View File

@@ -25,8 +25,8 @@ define_violation!(
/// > a leading underscore (e.g. `_socket`).
///
/// ## Example
/// * Instead of `example-module-name` or `example module name`, use `example_module_name`.
/// * Instead of `ExampleModule`, use `example_module`.
/// - Instead of `example-module-name` or `example module name`, use `example_module_name`.
/// - Instead of `ExampleModule`, use `example_module`.
///
/// [PEP 8]: https://peps.python.org/pep-0008/#package-and-module-names
pub struct InvalidModuleName {

View File

@@ -17,10 +17,10 @@ define_violation!(
/// > Function names should be lowercase, with words separated by underscores as necessary to
/// > improve readability. Variable names follow the same convention as function names. mixedCase
/// > is allowed only in contexts where that's already the prevailing style (e.g. threading.py),
/// to retain backwards compatibility.
/// > to retain backwards compatibility.
///
/// ## Options
/// * `pep8-naming.ignore-names`
/// - `pep8-naming.ignore-names`
///
/// ## Example
/// ```python

View File

@@ -61,40 +61,40 @@ pub fn is_overlong(
pub fn is_keyword_token(token: &Tok) -> bool {
matches!(
token,
Tok::False { .. }
| Tok::True { .. }
| Tok::None { .. }
| Tok::And { .. }
| Tok::As { .. }
| Tok::Assert { .. }
| Tok::Await { .. }
| Tok::Break { .. }
| Tok::Class { .. }
| Tok::Continue { .. }
| Tok::Def { .. }
| Tok::Del { .. }
| Tok::Elif { .. }
| Tok::Else { .. }
| Tok::Except { .. }
| Tok::Finally { .. }
| Tok::For { .. }
| Tok::From { .. }
| Tok::Global { .. }
| Tok::If { .. }
| Tok::Import { .. }
| Tok::In { .. }
| Tok::Is { .. }
| Tok::Lambda { .. }
| Tok::Nonlocal { .. }
| Tok::Not { .. }
| Tok::Or { .. }
| Tok::Pass { .. }
| Tok::Raise { .. }
| Tok::Return { .. }
| Tok::Try { .. }
| Tok::While { .. }
| Tok::With { .. }
| Tok::Yield { .. }
Tok::False
| Tok::True
| Tok::None
| Tok::And
| Tok::As
| Tok::Assert
| Tok::Await
| Tok::Break
| Tok::Class
| Tok::Continue
| Tok::Def
| Tok::Del
| Tok::Elif
| Tok::Else
| Tok::Except
| Tok::Finally
| Tok::For
| Tok::From
| Tok::Global
| Tok::If
| Tok::Import
| Tok::In
| Tok::Is
| Tok::Lambda
| Tok::Nonlocal
| Tok::Not
| Tok::Or
| Tok::Pass
| Tok::Raise
| Tok::Return
| Tok::Try
| Tok::While
| Tok::With
| Tok::Yield
)
}
@@ -104,3 +104,55 @@ pub fn is_singleton_token(token: &Tok) -> bool {
Tok::False { .. } | Tok::True { .. } | Tok::None { .. },
)
}
pub fn is_op_token(token: &Tok) -> bool {
matches!(
token,
Tok::Lpar
| Tok::Rpar
| Tok::Lsqb
| Tok::Rsqb
| Tok::Comma
| Tok::Semi
| Tok::Plus
| Tok::Minus
| Tok::Star
| Tok::Slash
| Tok::Vbar
| Tok::Amper
| Tok::Less
| Tok::Greater
| Tok::Equal
| Tok::Dot
| Tok::Percent
| Tok::Lbrace
| Tok::Rbrace
| Tok::NotEqual
| Tok::LessEqual
| Tok::GreaterEqual
| Tok::Tilde
| Tok::CircumFlex
| Tok::LeftShift
| Tok::RightShift
| Tok::DoubleStar
| Tok::PlusEqual
| Tok::MinusEqual
| Tok::StarEqual
| Tok::SlashEqual
| Tok::PercentEqual
| Tok::AmperEqual
| Tok::VbarEqual
| Tok::CircumflexEqual
| Tok::LeftShiftEqual
| Tok::RightShiftEqual
| Tok::DoubleStarEqual
| Tok::DoubleSlash
| Tok::DoubleSlashEqual
| Tok::At
| Tok::AtEqual
| Tok::Rarrow
| Tok::Ellipsis
| Tok::ColonEqual
| Tok::Colon
)
}

View File

@@ -3,6 +3,8 @@ use rustpython_parser::ast::Location;
use rustpython_parser::lexer::LexResult;
use rustpython_parser::Tok;
use crate::rules::pycodestyle::helpers::{is_keyword_token, is_op_token};
use crate::ast::types::Range;
use crate::source_code::Locator;
@@ -62,36 +64,7 @@ fn build_line<'a>(
continue;
}
if matches!(
tok,
Tok::Amper
| Tok::AmperEqual
| Tok::CircumFlex
| Tok::CircumflexEqual
| Tok::Colon
| Tok::ColonEqual
| Tok::DoubleSlash
| Tok::DoubleSlashEqual
| Tok::DoubleStar
| Tok::Equal
| Tok::Greater
| Tok::GreaterEqual
| Tok::Less
| Tok::LessEqual
| Tok::Minus
| Tok::MinusEqual
| Tok::NotEqual
| Tok::Percent
| Tok::PercentEqual
| Tok::Plus
| Tok::PlusEqual
| Tok::Slash
| Tok::SlashEqual
| Tok::Star
| Tok::StarEqual
| Tok::Vbar
| Tok::VbarEqual
) {
if is_op_token(tok) {
flags.insert(TokenFlags::OPERATOR);
}
@@ -106,44 +79,7 @@ fn build_line<'a>(
flags.insert(TokenFlags::PUNCTUATION);
}
if matches!(
tok,
Tok::False
| Tok::None
| Tok::True
| Tok::And
| Tok::As
| Tok::Assert
| Tok::Async
| Tok::Await
| Tok::Break
| Tok::Class
| Tok::Continue
| Tok::Def
| Tok::Del
| Tok::Elif
| Tok::Else
| Tok::Except
| Tok::Finally
| Tok::For
| Tok::From
| Tok::Global
| Tok::If
| Tok::Import
| Tok::In
| Tok::Is
| Tok::Lambda
| Tok::Nonlocal
| Tok::Not
| Tok::Or
| Tok::Pass
| Tok::Raise
| Tok::Return
| Tok::Try
| Tok::While
| Tok::With
| Tok::Yield
) {
if is_keyword_token(tok) {
flags.insert(TokenFlags::KEYWORD);
}

View File

@@ -42,6 +42,7 @@ mod tests {
#[test_case(Rule::NotInTest, Path::new("E713.py"))]
#[test_case(Rule::NotIsTest, Path::new("E714.py"))]
#[test_case(Rule::SyntaxError, Path::new("E999.py"))]
#[test_case(Rule::IndentationContainsTabs, Path::new("W19.py"))]
#[test_case(Rule::TrailingWhitespace, Path::new("W29.py"))]
#[test_case(Rule::TrueFalseComparison, Path::new("E712.py"))]
#[test_case(Rule::TypeComparison, Path::new("E721.py"))]
@@ -100,6 +101,11 @@ mod tests {
#[test_case(Rule::WhitespaceAfterOpenBracket, Path::new("E20.py"))]
#[test_case(Rule::WhitespaceBeforeCloseBracket, Path::new("E20.py"))]
#[test_case(Rule::WhitespaceBeforePunctuation, Path::new("E20.py"))]
#[test_case(
Rule::UnexpectedSpacesAroundKeywordParameterEquals,
Path::new("E25.py")
)]
#[test_case(Rule::MissingWhitespaceAroundParameterEquals, Path::new("E25.py"))]
fn logical(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -19,7 +19,7 @@ define_violation!(
/// ## Example
/// ```python
/// try:
/// raise(KeyboardInterrupt("You probably don't mean to break CTRL-C."))
/// raise KeyboardInterrupt("You probably don't mean to break CTRL-C.")
/// except:
/// print("But a bare `except` will ignore keyboard interrupts.")
/// ```
@@ -33,9 +33,9 @@ define_violation!(
/// ```
///
/// ## References
/// * [PEP 8](https://www.python.org/dev/peps/pep-0008/#programming-recommendations)
/// * [Python: "Exception hierarchy"](https://docs.python.org/3/library/exceptions.html#exception-hierarchy)
/// * [Google Python Style Guide: "Exceptions"](https://google.github.io/styleguide/pyguide.html#24-exceptions)
/// - [PEP 8](https://www.python.org/dev/peps/pep-0008/#programming-recommendations)
/// - [Python: "Exception hierarchy"](https://docs.python.org/3/library/exceptions.html#exception-hierarchy)
/// - [Google Python Style Guide: "Exceptions"](https://google.github.io/styleguide/pyguide.html#24-exceptions)
pub struct BareExcept;
);
impl Violation for BareExcept {

View File

@@ -0,0 +1,34 @@
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::ast::whitespace::leading_space;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct IndentationContainsTabs;
);
impl Violation for IndentationContainsTabs {
#[derive_message_formats]
fn message(&self) -> String {
format!("Indentation contains tabs")
}
}
/// W191
pub fn indentation_contains_tabs(lineno: usize, line: &str) -> Option<Diagnostic> {
let indent = leading_space(line);
if indent.contains('\t') {
Some(Diagnostic::new(
IndentationContainsTabs,
Range::new(
Location::new(lineno + 1, 0),
Location::new(lineno + 1, indent.chars().count()),
),
))
} else {
None
}
}

View File

@@ -21,6 +21,8 @@ pub use indentation::{
NoIndentedBlock, NoIndentedBlockComment, OverIndented, UnexpectedIndentation,
UnexpectedIndentationComment,
};
pub use indentation_contains_tabs::{indentation_contains_tabs, IndentationContainsTabs};
pub use invalid_escape_sequence::{invalid_escape_sequence, InvalidEscapeSequence};
pub use lambda_assignment::{lambda_assignment, LambdaAssignment};
pub use line_too_long::{line_too_long, LineTooLong};
@@ -48,6 +50,11 @@ pub use whitespace_before_comment::{
NoSpaceAfterInlineComment, TooFewSpacesBeforeInlineComment,
};
pub use whitespace_around_named_parameter_equals::{
whitespace_around_named_parameter_equals, MissingWhitespaceAroundParameterEquals,
UnexpectedSpacesAroundKeywordParameterEquals,
};
mod ambiguous_class_name;
mod ambiguous_function_name;
mod ambiguous_variable_name;
@@ -58,6 +65,7 @@ mod errors;
mod extraneous_whitespace;
mod imports;
mod indentation;
mod indentation_contains_tabs;
mod invalid_escape_sequence;
mod lambda_assignment;
mod line_too_long;
@@ -70,4 +78,5 @@ mod space_around_operator;
mod trailing_whitespace;
mod type_comparison;
mod whitespace_around_keywords;
mod whitespace_around_named_parameter_equals;
mod whitespace_before_comment;

View File

@@ -0,0 +1,114 @@
#![allow(dead_code)]
use rustpython_parser::ast::Location;
use rustpython_parser::Tok;
use once_cell::sync::Lazy;
use regex::Regex;
use ruff_macros::{define_violation, derive_message_formats};
#[cfg(feature = "logical_lines")]
use crate::rules::pycodestyle::helpers::is_op_token;
use crate::registry::DiagnosticKind;
use crate::violation::Violation;
define_violation!(
pub struct UnexpectedSpacesAroundKeywordParameterEquals;
);
impl Violation for UnexpectedSpacesAroundKeywordParameterEquals {
#[derive_message_formats]
fn message(&self) -> String {
format!("Unexpected spaces around keyword / parameter equals")
}
}
define_violation!(
pub struct MissingWhitespaceAroundParameterEquals;
);
impl Violation for MissingWhitespaceAroundParameterEquals {
#[derive_message_formats]
fn message(&self) -> String {
format!("Missing whitespace around parameter equals")
}
}
static STARTSWITH_DEF_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^(async\s+def|def)\b").unwrap());
/// E251, E252
#[cfg(feature = "logical_lines")]
pub fn whitespace_around_named_parameter_equals(
tokens: &[(Location, &Tok, Location)],
line: &str,
) -> Vec<(Location, DiagnosticKind)> {
let mut diagnostics = vec![];
let mut parens = 0;
let mut require_space = false;
let mut no_space = false;
let mut annotated_func_arg = false;
let mut prev_end: Option<&Location> = None;
let in_def = STARTSWITH_DEF_REGEX.is_match(line);
for (start, token, end) in tokens {
if **token == Tok::NonLogicalNewline {
continue;
}
if no_space {
no_space = false;
if Some(start) != prev_end {
diagnostics.push((
*(prev_end.unwrap()),
UnexpectedSpacesAroundKeywordParameterEquals.into(),
));
}
}
if require_space {
require_space = false;
if Some(start) == prev_end {
diagnostics.push((*start, MissingWhitespaceAroundParameterEquals.into()));
}
}
if is_op_token(token) {
if **token == Tok::Lpar || **token == Tok::Lsqb {
parens += 1;
} else if **token == Tok::Rpar || **token == Tok::Rsqb {
parens -= 1;
} else if in_def && **token == Tok::Colon && parens == 1 {
annotated_func_arg = true;
} else if parens == 1 && **token == Tok::Comma {
annotated_func_arg = false;
} else if parens > 0 && **token == Tok::Equal {
if annotated_func_arg && parens == 1 {
require_space = true;
if Some(start) == prev_end {
diagnostics.push((*start, MissingWhitespaceAroundParameterEquals.into()));
}
} else {
no_space = true;
if Some(start) != prev_end {
diagnostics.push((
*(prev_end.unwrap()),
UnexpectedSpacesAroundKeywordParameterEquals.into(),
));
}
}
}
if parens < 1 {
annotated_func_arg = false;
}
}
prev_end = Some(end);
}
diagnostics
}
#[cfg(not(feature = "logical_lines"))]
pub fn whitespace_around_named_parameter_equals(
_tokens: &[(Location, &Tok, Location)],
_line: &str,
) -> Vec<(Location, DiagnosticKind)> {
vec![]
}

View File

@@ -27,8 +27,8 @@ pub struct Options {
"#
)]
/// Whether line-length violations (`E501`) should be triggered for
/// comments starting with `task-tags` (by default: ["TODO", "FIXME",
/// and "XXX"]).
/// comments starting with `task-tags` (by default: \["TODO", "FIXME",
/// and "XXX"\]).
pub ignore_overlong_task_comments: Option<bool>,
}

View File

@@ -0,0 +1,115 @@
---
source: crates/ruff/src/rules/pycodestyle/mod.rs
expression: diagnostics
---
- kind:
UnexpectedSpacesAroundKeywordParameterEquals: ~
location:
row: 2
column: 11
end_location:
row: 2
column: 11
fix: ~
parent: ~
- kind:
UnexpectedSpacesAroundKeywordParameterEquals: ~
location:
row: 2
column: 13
end_location:
row: 2
column: 13
fix: ~
parent: ~
- kind:
UnexpectedSpacesAroundKeywordParameterEquals: ~
location:
row: 6
column: 8
end_location:
row: 6
column: 8
fix: ~
parent: ~
- kind:
UnexpectedSpacesAroundKeywordParameterEquals: ~
location:
row: 8
column: 7
end_location:
row: 8
column: 7
fix: ~
parent: ~
- kind:
UnexpectedSpacesAroundKeywordParameterEquals: ~
location:
row: 10
column: 7
end_location:
row: 10
column: 7
fix: ~
parent: ~
- kind:
UnexpectedSpacesAroundKeywordParameterEquals: ~
location:
row: 10
column: 9
end_location:
row: 10
column: 9
fix: ~
parent: ~
- kind:
UnexpectedSpacesAroundKeywordParameterEquals: ~
location:
row: 12
column: 13
end_location:
row: 12
column: 13
fix: ~
parent: ~
- kind:
UnexpectedSpacesAroundKeywordParameterEquals: ~
location:
row: 15
column: 28
end_location:
row: 15
column: 28
fix: ~
parent: ~
- kind:
UnexpectedSpacesAroundKeywordParameterEquals: ~
location:
row: 18
column: 44
end_location:
row: 18
column: 44
fix: ~
parent: ~
- kind:
UnexpectedSpacesAroundKeywordParameterEquals: ~
location:
row: 23
column: 7
end_location:
row: 23
column: 7
fix: ~
parent: ~
- kind:
UnexpectedSpacesAroundKeywordParameterEquals: ~
location:
row: 23
column: 9
end_location:
row: 23
column: 9
fix: ~
parent: ~

View File

@@ -0,0 +1,45 @@
---
source: crates/ruff/src/rules/pycodestyle/mod.rs
expression: diagnostics
---
- kind:
MissingWhitespaceAroundParameterEquals: ~
location:
row: 46
column: 14
end_location:
row: 46
column: 14
fix: ~
parent: ~
- kind:
MissingWhitespaceAroundParameterEquals: ~
location:
row: 46
column: 15
end_location:
row: 46
column: 15
fix: ~
parent: ~
- kind:
MissingWhitespaceAroundParameterEquals: ~
location:
row: 46
column: 26
end_location:
row: 46
column: 26
fix: ~
parent: ~
- kind:
MissingWhitespaceAroundParameterEquals: ~
location:
row: 46
column: 35
end_location:
row: 46
column: 35
fix: ~
parent: ~

View File

@@ -0,0 +1,385 @@
---
source: crates/ruff/src/rules/pycodestyle/mod.rs
expression: diagnostics
---
- kind:
IndentationContainsTabs: ~
location:
row: 3
column: 0
end_location:
row: 3
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 9
column: 0
end_location:
row: 9
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 16
column: 0
end_location:
row: 16
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 21
column: 0
end_location:
row: 21
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 26
column: 0
end_location:
row: 26
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 32
column: 0
end_location:
row: 32
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 38
column: 0
end_location:
row: 38
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 44
column: 0
end_location:
row: 44
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 45
column: 0
end_location:
row: 45
column: 9
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 54
column: 0
end_location:
row: 54
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 58
column: 0
end_location:
row: 58
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 61
column: 0
end_location:
row: 61
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 62
column: 0
end_location:
row: 62
column: 5
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 63
column: 0
end_location:
row: 63
column: 5
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 64
column: 0
end_location:
row: 64
column: 5
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 65
column: 0
end_location:
row: 65
column: 5
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 66
column: 0
end_location:
row: 66
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 73
column: 0
end_location:
row: 73
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 78
column: 0
end_location:
row: 78
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 83
column: 0
end_location:
row: 83
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 88
column: 0
end_location:
row: 88
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 91
column: 0
end_location:
row: 91
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 92
column: 0
end_location:
row: 92
column: 5
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 98
column: 0
end_location:
row: 98
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 99
column: 0
end_location:
row: 99
column: 8
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 102
column: 0
end_location:
row: 102
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 105
column: 0
end_location:
row: 105
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 110
column: 0
end_location:
row: 110
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 125
column: 0
end_location:
row: 125
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 131
column: 0
end_location:
row: 131
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 132
column: 0
end_location:
row: 132
column: 2
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 133
column: 0
end_location:
row: 133
column: 2
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 136
column: 0
end_location:
row: 136
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 137
column: 0
end_location:
row: 137
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 138
column: 0
end_location:
row: 138
column: 2
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 139
column: 0
end_location:
row: 139
column: 2
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 140
column: 0
end_location:
row: 140
column: 1
fix: ~
parent: ~
- kind:
IndentationContainsTabs: ~
location:
row: 143
column: 0
end_location:
row: 143
column: 1
fix: ~
parent: ~

View File

@@ -5,6 +5,7 @@ use ruff_macros::{define_violation, derive_message_formats};
use rustc_hash::FxHashSet;
use rustpython_parser::ast::StmtKind;
use crate::ast::helpers::identifier_range;
use crate::ast::types::Range;
use crate::ast::whitespace::LinesWithTrailingNewline;
use crate::ast::{cast, whitespace};
@@ -837,7 +838,7 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &
let names = missing_arg_names.into_iter().sorted().collect();
checker.diagnostics.push(Diagnostic::new(
UndocumentedParam { names },
Range::from_located(parent),
identifier_range(parent, checker.locator),
));
}
}

View File

@@ -8,10 +8,10 @@ expression: diagnostics
- y
location:
row: 283
column: 4
column: 8
end_location:
row: 293
column: 16
row: 283
column: 11
fix: ~
parent: ~
- kind:
@@ -20,10 +20,10 @@ expression: diagnostics
- y
location:
row: 300
column: 0
column: 4
end_location:
row: 306
column: 7
row: 300
column: 28
fix: ~
parent: ~
- kind:
@@ -34,10 +34,10 @@ expression: diagnostics
- z
location:
row: 324
column: 4
column: 8
end_location:
row: 330
column: 11
row: 324
column: 25
fix: ~
parent: ~
- kind:
@@ -48,10 +48,10 @@ expression: diagnostics
- z
location:
row: 336
column: 4
column: 8
end_location:
row: 343
column: 11
row: 336
column: 38
fix: ~
parent: ~
- kind:
@@ -62,10 +62,10 @@ expression: diagnostics
- z
location:
row: 349
column: 4
column: 8
end_location:
row: 355
column: 11
row: 349
column: 39
fix: ~
parent: ~
- kind:
@@ -75,10 +75,10 @@ expression: diagnostics
- b
location:
row: 361
column: 4
column: 8
end_location:
row: 367
column: 11
row: 361
column: 30
fix: ~
parent: ~
- kind:
@@ -87,10 +87,10 @@ expression: diagnostics
- y
location:
row: 389
column: 0
column: 4
end_location:
row: 398
column: 7
row: 389
column: 27
fix: ~
parent: ~
- kind:
@@ -101,10 +101,10 @@ expression: diagnostics
- z
location:
row: 425
column: 4
column: 8
end_location:
row: 434
column: 11
row: 425
column: 25
fix: ~
parent: ~
- kind:
@@ -115,10 +115,10 @@ expression: diagnostics
- z
location:
row: 440
column: 4
column: 8
end_location:
row: 453
column: 11
row: 440
column: 38
fix: ~
parent: ~
- kind:
@@ -128,10 +128,10 @@ expression: diagnostics
- z
location:
row: 459
column: 4
column: 8
end_location:
row: 469
column: 11
row: 459
column: 39
fix: ~
parent: ~
- kind:
@@ -140,10 +140,10 @@ expression: diagnostics
- y
location:
row: 489
column: 4
column: 8
end_location:
row: 497
column: 11
row: 489
column: 29
fix: ~
parent: ~

View File

@@ -9,10 +9,10 @@ expression: diagnostics
- z
location:
row: 1
column: 0
column: 4
end_location:
row: 11
column: 12
row: 1
column: 5
fix: ~
parent: ~
- kind:
@@ -22,10 +22,10 @@ expression: diagnostics
- z
location:
row: 14
column: 0
column: 4
end_location:
row: 24
column: 12
row: 14
column: 5
fix: ~
parent: ~
- kind:
@@ -35,10 +35,10 @@ expression: diagnostics
- z
location:
row: 27
column: 0
column: 4
end_location:
row: 36
column: 12
row: 27
column: 5
fix: ~
parent: ~
- kind:
@@ -48,10 +48,10 @@ expression: diagnostics
- z
location:
row: 39
column: 0
column: 4
end_location:
row: 49
column: 12
row: 39
column: 5
fix: ~
parent: ~
- kind:
@@ -60,10 +60,10 @@ expression: diagnostics
- y
location:
row: 52
column: 0
column: 4
end_location:
row: 62
column: 12
row: 52
column: 5
fix: ~
parent: ~
- kind:
@@ -72,10 +72,10 @@ expression: diagnostics
- y
location:
row: 65
column: 0
column: 4
end_location:
row: 74
column: 12
row: 65
column: 5
fix: ~
parent: ~
- kind:
@@ -84,10 +84,10 @@ expression: diagnostics
- y
location:
row: 77
column: 0
column: 4
end_location:
row: 84
column: 12
row: 77
column: 5
fix: ~
parent: ~
- kind:
@@ -96,10 +96,10 @@ expression: diagnostics
- x
location:
row: 98
column: 0
column: 4
end_location:
row: 105
column: 12
row: 98
column: 5
fix: ~
parent: ~
- kind:
@@ -108,10 +108,10 @@ expression: diagnostics
- "*args"
location:
row: 108
column: 0
column: 4
end_location:
row: 115
column: 12
row: 108
column: 5
fix: ~
parent: ~

View File

@@ -9,10 +9,10 @@ expression: diagnostics
- z
location:
row: 27
column: 0
column: 4
end_location:
row: 36
column: 12
row: 27
column: 5
fix: ~
parent: ~
- kind:
@@ -21,10 +21,10 @@ expression: diagnostics
- y
location:
row: 65
column: 0
column: 4
end_location:
row: 74
column: 12
row: 65
column: 5
fix: ~
parent: ~
- kind:
@@ -33,10 +33,10 @@ expression: diagnostics
- y
location:
row: 77
column: 0
column: 4
end_location:
row: 84
column: 12
row: 77
column: 5
fix: ~
parent: ~
- kind:
@@ -45,10 +45,10 @@ expression: diagnostics
- x
location:
row: 98
column: 0
column: 4
end_location:
row: 105
column: 12
row: 98
column: 5
fix: ~
parent: ~
- kind:
@@ -57,10 +57,10 @@ expression: diagnostics
- "*args"
location:
row: 108
column: 0
column: 4
end_location:
row: 115
column: 12
row: 108
column: 5
fix: ~
parent: ~

View File

@@ -32,6 +32,7 @@ mod tests {
#[test_case(Rule::UnusedImport, Path::new("F401_7.py"); "F401_7")]
#[test_case(Rule::UnusedImport, Path::new("F401_8.py"); "F401_8")]
#[test_case(Rule::UnusedImport, Path::new("F401_9.py"); "F401_9")]
#[test_case(Rule::UnusedImport, Path::new("F401_10.py"); "F401_10")]
#[test_case(Rule::ImportShadowedByLoopVar, Path::new("F402.py"); "F402")]
#[test_case(Rule::ImportStar, Path::new("F403.py"); "F403")]
#[test_case(Rule::LateFutureImport, Path::new("F404.py"); "F404")]
@@ -262,8 +263,8 @@ mod tests {
&indexer,
&directives,
&settings,
flags::Autofix::Enabled,
flags::Noqa::Enabled,
flags::Autofix::Enabled,
);
diagnostics.sort_by_key(|diagnostic| diagnostic.location);
let actual = diagnostics

View File

@@ -33,7 +33,7 @@ define_violation!(
/// ```
///
/// ## References
/// * [PEP 498](https://www.python.org/dev/peps/pep-0498/)
/// - [PEP 498](https://www.python.org/dev/peps/pep-0498/)
pub struct FStringMissingPlaceholders;
);
impl AlwaysAutofixableViolation for FStringMissingPlaceholders {

View File

@@ -26,7 +26,7 @@ define_violation!(
/// [`dummy-variable-rgx`] pattern.
///
/// ## Options
/// * `dummy-variable-rgx`
/// - `dummy-variable-rgx`
///
/// ## Example
/// ```python

View File

@@ -0,0 +1,6 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
expression: diagnostics
---
[]

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