Compare commits
82 Commits
v0.0.278
...
deps/parse
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35cc48a64c | ||
|
|
0d4f1d86ad | ||
|
|
834910947e | ||
|
|
e34cfeb475 | ||
|
|
bfaa1f9530 | ||
|
|
52aa2fc875 | ||
|
|
e574a6a769 | ||
|
|
b9346a4fd6 | ||
|
|
8001a2f121 | ||
|
|
7dd30f0270 | ||
|
|
21063544f7 | ||
|
|
fb336898a5 | ||
|
|
f5f8eb31ed | ||
|
|
be6c744856 | ||
|
|
94998aedef | ||
|
|
1c0376a72d | ||
|
|
de2a13fcd7 | ||
|
|
cfec636046 | ||
|
|
ae431df146 | ||
|
|
2cd117ba81 | ||
|
|
a956226d95 | ||
|
|
1dd52ad139 | ||
|
|
d692ed0896 | ||
|
|
01b05fe247 | ||
|
|
59dfd0e793 | ||
|
|
c7ff743d30 | ||
|
|
b01a4d8446 | ||
|
|
f012ed2d77 | ||
|
|
06b5c6c06f | ||
|
|
4782675bf9 | ||
|
|
f2e995f78d | ||
|
|
6824b67f44 | ||
|
|
8ccd697020 | ||
|
|
2de6f30929 | ||
|
|
df2efe81c8 | ||
|
|
fa4855e6fe | ||
|
|
3cda89ecaf | ||
|
|
e1c119fde3 | ||
|
|
daa4b72d5f | ||
|
|
f029f8b784 | ||
|
|
bf248ede93 | ||
|
|
086f8a3c12 | ||
|
|
3dc73395ea | ||
|
|
7c32e98d10 | ||
|
|
81b88dcfb9 | ||
|
|
8187bf9f7e | ||
|
|
513de13c46 | ||
|
|
816f7644a9 | ||
|
|
fb46579d30 | ||
|
|
a961f75e13 | ||
|
|
5a4516b812 | ||
|
|
875e04e369 | ||
|
|
12489d3305 | ||
|
|
73228e914c | ||
|
|
af2a087806 | ||
|
|
51a313cca4 | ||
|
|
48309cad08 | ||
|
|
2c2e5b2704 | ||
|
|
5d135d4e0e | ||
|
|
06a04c10e2 | ||
|
|
fee0f43925 | ||
|
|
25e491ad6f | ||
|
|
e7b059cc5c | ||
|
|
5dd5ee0c5b | ||
|
|
f48ab2d621 | ||
|
|
cf48ad7b21 | ||
|
|
f44acc047a | ||
|
|
8420008e79 | ||
|
|
932c9a4789 | ||
|
|
549173b395 | ||
|
|
b1781abffb | ||
|
|
68e0f97354 | ||
|
|
e9771c9c63 | ||
|
|
067b2a6ce6 | ||
|
|
30702c2977 | ||
|
|
34b79ead3d | ||
|
|
19f475ae1f | ||
|
|
2b03bd18f4 | ||
|
|
c87faca884 | ||
|
|
6dbc6d2e59 | ||
|
|
6ce252f0ed | ||
|
|
c029c8b37a |
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
sha:
|
||||
description: "Optionally, the full sha of the commit to be released"
|
||||
type: string
|
||||
push:
|
||||
pull_request:
|
||||
paths:
|
||||
# When we change pyproject.toml, we want to ensure that the maturin builds still work
|
||||
- pyproject.toml
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,7 +10,7 @@ schemastore
|
||||
# `maturin develop` and ecosystem_all_check.sh
|
||||
.venv*
|
||||
# Formatter debugging (crates/ruff_python_formatter/README.md)
|
||||
scratch.py
|
||||
scratch.*
|
||||
# Created by `perf` (CONTRIBUTING.md)
|
||||
perf.data
|
||||
perf.data.old
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# default to true for all rules
|
||||
default: true
|
||||
|
||||
# MD007/unordered-list-indent
|
||||
MD007:
|
||||
indent: 4
|
||||
|
||||
# MD033/no-inline-html
|
||||
MD033: false
|
||||
|
||||
@@ -8,7 +12,4 @@ MD033: false
|
||||
MD041: false
|
||||
|
||||
# MD013/line-length
|
||||
MD013:
|
||||
line_length: 100
|
||||
code_blocks: false
|
||||
ignore_code_blocks: true
|
||||
MD013: false
|
||||
|
||||
@@ -22,6 +22,7 @@ repos:
|
||||
hooks:
|
||||
- id: mdformat
|
||||
additional_dependencies:
|
||||
- mdformat-mkdocs
|
||||
- mdformat-black
|
||||
- black==23.1.0 # Must be the latest version of Black
|
||||
|
||||
|
||||
@@ -48,12 +48,12 @@ Taking `UP006` (rewrite `List[int]` to `list[int]`) as an example, the setting n
|
||||
follows:
|
||||
|
||||
- On Python 3.7 and Python 3.8, setting `keep-runtime-typing = true` will cause Ruff to ignore
|
||||
`UP006` violations, even if `from __future__ import annotations` is present in the file.
|
||||
While such annotations are valid in Python 3.7 and Python 3.8 when combined with
|
||||
`from __future__ import annotations`, they aren't supported by libraries like Pydantic and
|
||||
FastAPI, which rely on runtime type checking.
|
||||
`UP006` violations, even if `from __future__ import annotations` is present in the file.
|
||||
While such annotations are valid in Python 3.7 and Python 3.8 when combined with
|
||||
`from __future__ import annotations`, they aren't supported by libraries like Pydantic and
|
||||
FastAPI, which rely on runtime type checking.
|
||||
- On Python 3.9 and above, the setting has no effect, as `list[int]` is a valid type annotation,
|
||||
and libraries like Pydantic and FastAPI support it without issue.
|
||||
and libraries like Pydantic and FastAPI support it without issue.
|
||||
|
||||
In short: `keep-runtime-typing` can be used to ensure that Ruff doesn't introduce type annotations
|
||||
that are not supported at runtime by the current Python version, which are unsupported by libraries
|
||||
@@ -203,25 +203,25 @@ 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
|
||||
of silently ignoring them. For example, the following will now fail:
|
||||
of silently ignoring them. For example, the following will now fail:
|
||||
|
||||
```console
|
||||
ruff --clean --respect-gitignore
|
||||
```
|
||||
```console
|
||||
ruff --clean --respect-gitignore
|
||||
```
|
||||
|
||||
(the `clean` command doesn't support `--respect-gitignore`.)
|
||||
(the `clean` command doesn't support `--respect-gitignore`.)
|
||||
|
||||
- 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`.
|
||||
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.
|
||||
(The semantics of `ruff -- <arg>` have not changed.)
|
||||
(The semantics of `ruff -- <arg>` have not changed.)
|
||||
|
||||
- `--explain` previously treated `--format grouped` as a synonym for `--format text`.
|
||||
This is no longer supported; instead, use `--format text`.
|
||||
This is no longer supported; instead, use `--format text`.
|
||||
|
||||
## 0.0.226
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
- [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)
|
||||
- [1. Correction](#1-correction)
|
||||
- [2. Warning](#2-warning)
|
||||
- [3. Temporary Ban](#3-temporary-ban)
|
||||
- [4. Permanent Ban](#4-permanent-ban)
|
||||
- [Attribution](#attribution)
|
||||
|
||||
## Our Pledge
|
||||
@@ -33,20 +33,20 @@ community include:
|
||||
- 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
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
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
|
||||
address, without their explicit permission
|
||||
address, without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
|
||||
249
CONTRIBUTING.md
249
CONTRIBUTING.md
@@ -3,16 +3,29 @@
|
||||
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)
|
||||
- [Rule testing: fixtures and snapshots](#rule-testing-fixtures-and-snapshots)
|
||||
- [Example: Adding a new configuration option](#example-adding-a-new-configuration-option)
|
||||
- [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)
|
||||
- [Rule testing: fixtures and snapshots](#rule-testing-fixtures-and-snapshots)
|
||||
- [Example: Adding a new configuration option](#example-adding-a-new-configuration-option)
|
||||
- [MkDocs](#mkdocs)
|
||||
- [Release Process](#release-process)
|
||||
- [Benchmarks](#benchmarking-and-profiling)
|
||||
- [Creating a new release](#creating-a-new-release)
|
||||
- [Ecosystem CI](#ecosystem-ci)
|
||||
- [Benchmarking and Profiling](#benchmarking-and-profiling)
|
||||
- [CPython Benchmark](#cpython-benchmark)
|
||||
- [Microbenchmarks](#microbenchmarks)
|
||||
- [Benchmark-driven Development](#benchmark-driven-development)
|
||||
- [PR Summary](#pr-summary)
|
||||
- [Tips](#tips)
|
||||
- [Profiling Projects](#profiling-projects)
|
||||
- [Linux](#linux)
|
||||
- [Mac](#mac)
|
||||
- [`cargo dev`](#cargo-dev)
|
||||
- [Subsystems](#subsystems)
|
||||
- [Compilation Pipeline](#compilation-pipeline)
|
||||
|
||||
## The Basics
|
||||
|
||||
@@ -23,7 +36,10 @@ For small changes (e.g., bug fixes), feel free to submit a PR.
|
||||
For larger changes (e.g., new lint rules, new functionality, new configuration options), consider
|
||||
creating an [**issue**](https://github.com/astral-sh/ruff/issues) outlining your proposed change.
|
||||
You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5) to discuss your idea with the
|
||||
community.
|
||||
community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
||||
in the issue tracker, along with [bugs](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||
and [improvements](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Aaccepted)
|
||||
that are ready for contributions.
|
||||
|
||||
If you're looking for a place to start, we recommend implementing a new lint rule (see:
|
||||
[_Adding a new lint rule_](#example-adding-a-new-lint-rule), which will allow you to learn from and
|
||||
@@ -34,6 +50,8 @@ As a concrete example: consider taking on one of the rules from the [`flake8-pyi
|
||||
plugin, and looking to the originating [Python source](https://github.com/PyCQA/flake8-pyi) for
|
||||
guidance.
|
||||
|
||||
If you have suggestions on how we might improve the contributing documentation, [let us know](https://github.com/astral-sh/ruff/discussions/5693)!
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Ruff is written in Rust. You'll need to install the
|
||||
@@ -92,48 +110,56 @@ 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.
|
||||
If you're working on a rule, this is the crate for you.
|
||||
- `crates/ruff_benchmark`: binary crate for running micro-benchmarks.
|
||||
- `crates/ruff_cache`: library crate for caching lint results.
|
||||
- `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_diagnostics`: library crate for the lint diagnostics APIs.
|
||||
- `crates/ruff_formatter`: library crate for generic code formatting logic based on an intermediate
|
||||
representation.
|
||||
`cargo dev generate-all`), see the [`cargo dev`](#cargo-dev) section below.
|
||||
- `crates/ruff_diagnostics`: library crate for the rule-independent abstractions in the lint
|
||||
diagnostics APIs.
|
||||
- `crates/ruff_formatter`: library crate for language agnostic code formatting logic based on an
|
||||
intermediate representation. The backend for `ruff_python_formatter`.
|
||||
- `crates/ruff_index`: library crate inspired by `rustc_index`.
|
||||
- `crates/ruff_macros`: library crate containing macros used by Ruff.
|
||||
- `crates/ruff_python_ast`: library crate containing Python-specific AST types and utilities.
|
||||
- `crates/ruff_python_formatter`: library crate containing Python-specific code formatting logic.
|
||||
- `crates/ruff_macros`: proc macro crate containing macros used by Ruff.
|
||||
- `crates/ruff_python_ast`: library crate containing Python-specific AST types and utilities. Note
|
||||
that the AST schema itself is defined in the
|
||||
[rustpython-ast](https://github.com/astral-sh/RustPython-Parser) crate.
|
||||
- `crates/ruff_python_formatter`: library crate implementing the Python formatter. Emits an
|
||||
intermediate representation for each node, which `ruff_formatter` prints based on the configured
|
||||
line length.
|
||||
- `crates/ruff_python_semantic`: library crate containing Python-specific semantic analysis logic,
|
||||
including Ruff's semantic model.
|
||||
- `crates/ruff_python_stdlib`: library crate containing Python-specific standard library data.
|
||||
including Ruff's semantic model. Used to resolve queries like "What import does this variable
|
||||
refer to?"
|
||||
- `crates/ruff_python_stdlib`: library crate containing Python-specific standard library data, e.g.
|
||||
the names of all built-in exceptions and which standard library types are immutable.
|
||||
- `crates/ruff_python_whitespace`: library crate containing Python-specific whitespace analysis
|
||||
logic.
|
||||
logic (indentation and newlines).
|
||||
- `crates/ruff_rustpython`: library crate containing `RustPython`-specific utilities.
|
||||
- `crates/ruff_testing_macros`: library crate containing macros used for testing Ruff.
|
||||
- `crates/ruff_textwrap`: library crate to indent and dedent Python source code.
|
||||
- `crates/ruff_wasm`: library crate for exposing Ruff as a WebAssembly module.
|
||||
- `crates/ruff_wasm`: library crate for exposing Ruff as a WebAssembly module. Powers the
|
||||
[Ruff Playground](https://play.ruff.rs/).
|
||||
|
||||
### 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)
|
||||
(e.g., `AssertFalse`, as in, "allow `assert False`").
|
||||
(e.g., `AssertFalse`, as in, "allow `assert False`").
|
||||
|
||||
1. Create a file for your rule (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/assert_false.rs`).
|
||||
|
||||
1. In that file, define a violation struct (e.g., `pub struct AssertFalse`). You can grep for
|
||||
`#[violation]` to see examples.
|
||||
`#[violation]` to see examples.
|
||||
|
||||
1. In that file, define a function that adds the violation to the diagnostic list as appropriate
|
||||
(e.g., `pub(crate) fn assert_false`) based on whatever inputs are required for the rule (e.g.,
|
||||
an `ast::StmtAssert` node).
|
||||
(e.g., `pub(crate) fn assert_false`) based on whatever inputs are required for the rule (e.g.,
|
||||
an `ast::StmtAssert` node).
|
||||
|
||||
1. Define the logic for triggering the violation in `crates/ruff/src/checkers/ast/mod.rs` (for
|
||||
AST-based checks), `crates/ruff/src/checkers/tokens.rs` (for token-based checks),
|
||||
`crates/ruff/src/checkers/lines.rs` (for text-based checks), or
|
||||
`crates/ruff/src/checkers/filesystem.rs` (for filesystem-based checks).
|
||||
AST-based checks), `crates/ruff/src/checkers/tokens.rs` (for token-based checks),
|
||||
`crates/ruff/src/checkers/lines.rs` (for text-based checks), or
|
||||
`crates/ruff/src/checkers/filesystem.rs` (for filesystem-based checks).
|
||||
|
||||
1. Map the violation struct to a rule code in `crates/ruff/src/codes.rs` (e.g., `B011`).
|
||||
|
||||
@@ -166,13 +192,13 @@ suppression comment would be framed as "allow `assert False`".
|
||||
As such, rule names should...
|
||||
|
||||
- Highlight the pattern that is being linted against, rather than the preferred alternative.
|
||||
For example, `AssertFalse` guards against `assert False` statements.
|
||||
For example, `AssertFalse` guards against `assert False` statements.
|
||||
|
||||
- _Not_ contain instructions on how to fix the violation, which instead belong in the rule
|
||||
documentation and the `autofix_title`.
|
||||
documentation and the `autofix_title`.
|
||||
|
||||
- _Not_ contain a redundant prefix, like `Disallow` or `Banned`, which are already implied by the
|
||||
convention.
|
||||
convention.
|
||||
|
||||
When re-implementing rules from other linters, we prioritize adhering to this convention over
|
||||
preserving the original rule name.
|
||||
@@ -187,25 +213,25 @@ Ruff's output for each fixture, which you can then commit alongside your changes
|
||||
Once you've completed the code for the rule itself, you can define tests with the following steps:
|
||||
|
||||
1. Add a Python file to `crates/ruff/resources/test/fixtures/[linter]` that contains the code you
|
||||
want to test. The file name should match the rule name (e.g., `E402.py`), and it should include
|
||||
examples of both violations and non-violations.
|
||||
want to test. The file name should match the rule name (e.g., `E402.py`), and it should include
|
||||
examples of both violations and non-violations.
|
||||
|
||||
1. Run Ruff locally against your file and verify the output is as expected. Once you're satisfied
|
||||
with the output (you see the violations you expect, and no others), proceed to the next step.
|
||||
For example, if you're adding a new rule named `E402`, you would run:
|
||||
with the output (you see the violations you expect, and no others), proceed to the next step.
|
||||
For example, if you're adding a new rule named `E402`, you would run:
|
||||
|
||||
```shell
|
||||
cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache
|
||||
```
|
||||
```shell
|
||||
cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache
|
||||
```
|
||||
|
||||
1. Add the test to the relevant `crates/ruff/src/rules/[linter]/mod.rs` file. If you're contributing
|
||||
a rule to a pre-existing set, you should be able to find a similar example to pattern-match
|
||||
against. If you're adding a new linter, you'll need to create a new `mod.rs` file (see,
|
||||
e.g., `crates/ruff/src/rules/flake8_bugbear/mod.rs`)
|
||||
a rule to a pre-existing set, you should be able to find a similar example to pattern-match
|
||||
against. If you're adding a new linter, you'll need to create a new `mod.rs` file (see,
|
||||
e.g., `crates/ruff/src/rules/flake8_bugbear/mod.rs`)
|
||||
|
||||
1. Run `cargo test`. Your test will fail, but you'll be prompted to follow-up
|
||||
with `cargo insta review`. Run `cargo insta review`, review and accept the generated snapshot,
|
||||
then commit the snapshot file alongside the rest of your changes.
|
||||
with `cargo insta review`. Run `cargo insta review`, review and accept the generated snapshot,
|
||||
then commit the snapshot file alongside the rest of your changes.
|
||||
|
||||
1. Run `cargo test` again to ensure that your test passes.
|
||||
|
||||
@@ -243,25 +269,25 @@ To preview any changes to the documentation locally:
|
||||
|
||||
1. Install MkDocs and Material for MkDocs with:
|
||||
|
||||
```shell
|
||||
pip install -r docs/requirements.txt
|
||||
```
|
||||
```shell
|
||||
pip install -r docs/requirements.txt
|
||||
```
|
||||
|
||||
1. Generate the MkDocs site with:
|
||||
|
||||
```shell
|
||||
python scripts/generate_mkdocs.py
|
||||
```
|
||||
```shell
|
||||
python scripts/generate_mkdocs.py
|
||||
```
|
||||
|
||||
1. Run the development server with:
|
||||
|
||||
```shell
|
||||
# For contributors.
|
||||
mkdocs serve -f mkdocs.generated.yml
|
||||
```shell
|
||||
# For contributors.
|
||||
mkdocs serve -f mkdocs.generated.yml
|
||||
|
||||
# For members of the Astral org, which has access to MkDocs Insiders via sponsorship.
|
||||
mkdocs serve -f mkdocs.insiders.yml
|
||||
```
|
||||
# For members of the Astral org, which has access to MkDocs Insiders via sponsorship.
|
||||
mkdocs serve -f mkdocs.insiders.yml
|
||||
```
|
||||
|
||||
The documentation should then be available locally at
|
||||
[http://127.0.0.1:8000/docs/](http://127.0.0.1:8000/docs/).
|
||||
@@ -282,20 +308,19 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
|
||||
1. Create a PR with the version and `BREAKING_CHANGES.md` updated
|
||||
1. Merge the PR
|
||||
1. Run the release workflow with the version number (without starting `v`) as input. Make sure
|
||||
main has your merged PR as last commit
|
||||
main has your merged PR as last commit
|
||||
1. The release workflow will do the following:
|
||||
1. Build all the assets. If this fails (even though we tested in step 4), we haven’t tagged or
|
||||
uploaded anything, you can restart after pushing a fix
|
||||
1. Upload to pypi
|
||||
1. Create and push the git tag (from pyproject.toml). We create the git tag only here
|
||||
because we can't change it ([#4468](https://github.com/charliermarsh/ruff/issues/4468)), so
|
||||
we want to make sure everything up to and including publishing to pypi worked.
|
||||
1. Attach artifacts to draft GitHub release
|
||||
1. Trigger downstream repositories. This can fail without causing fallout, it is possible (if
|
||||
inconvenient) to trigger the downstream jobs manually
|
||||
1. Create release notes in GitHub UI and promote from draft to proper release(<https://github.com/charliermarsh/ruff/releases/new>)
|
||||
1. Build all the assets. If this fails (even though we tested in step 4), we haven’t tagged or
|
||||
uploaded anything, you can restart after pushing a fix.
|
||||
1. Upload to PyPI.
|
||||
1. Create and push the Git tag (as extracted from `pyproject.toml`). We create the Git tag only
|
||||
after building the wheels and uploading to PyPI, since we can't delete or modify the tag ([#4468](https://github.com/charliermarsh/ruff/issues/4468)).
|
||||
1. Attach artifacts to draft GitHub release
|
||||
1. Trigger downstream repositories. This can fail non-catastrophically, as we can run any
|
||||
downstream jobs manually if needed.
|
||||
1. Create release notes in GitHub UI and promote from draft.
|
||||
1. If needed, [update the schemastore](https://github.com/charliermarsh/ruff/blob/main/scripts/update_schemastore.py)
|
||||
1. If needed, update ruff-lsp and ruff-vscode
|
||||
1. If needed, update the `ruff-lsp` and `ruff-vscode` repositories.
|
||||
|
||||
## Ecosystem CI
|
||||
|
||||
@@ -394,6 +419,13 @@ Summary
|
||||
159.43 ± 2.48 times faster than 'pycodestyle crates/ruff/resources/test/cpython'
|
||||
```
|
||||
|
||||
To benchmark a subset of rules, e.g. `LineTooLong` and `DocLineTooLong`:
|
||||
|
||||
```shell
|
||||
cargo build --release && hyperfine --warmup 10 \
|
||||
"./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache -e --select W505,E501"
|
||||
```
|
||||
|
||||
You can run `poetry install` from `./scripts/benchmarks` to create a working environment for the
|
||||
above. All reported benchmarks were computed using the versions specified by
|
||||
`./scripts/benchmarks/pyproject.toml` on Python 3.11.
|
||||
@@ -438,7 +470,7 @@ Benchmark 1: find . -type f -name "*.py" | xargs -P 0 pyupgrade --py311-plus
|
||||
Range (min … max): 29.813 s … 30.356 s 10 runs
|
||||
```
|
||||
|
||||
## Microbenchmarks
|
||||
### Microbenchmarks
|
||||
|
||||
The `ruff_benchmark` crate benchmarks the linter and the formatter on individual files.
|
||||
|
||||
@@ -448,7 +480,7 @@ You can run the benchmarks with
|
||||
cargo benchmark
|
||||
```
|
||||
|
||||
### Benchmark driven Development
|
||||
#### Benchmark-driven Development
|
||||
|
||||
Ruff uses [Criterion.rs](https://bheisler.github.io/criterion.rs/book/) for benchmarks. You can use
|
||||
`--save-baseline=<name>` to store an initial baseline benchmark (e.g. on `main`) and then use
|
||||
@@ -463,7 +495,7 @@ cargo benchmark --save-baseline=main
|
||||
cargo benchmark --baseline=main
|
||||
```
|
||||
|
||||
### PR Summary
|
||||
#### PR Summary
|
||||
|
||||
You can use `--save-baseline` and `critcmp` to get a pretty comparison between two recordings.
|
||||
This is useful to illustrate the improvements of a PR.
|
||||
@@ -484,21 +516,21 @@ You must install [`critcmp`](https://github.com/BurntSushi/critcmp) for the comp
|
||||
cargo install critcmp
|
||||
```
|
||||
|
||||
### Tips
|
||||
#### Tips
|
||||
|
||||
- Use `cargo benchmark <filter>` to only run specific benchmarks. For example: `cargo benchmark linter/pydantic`
|
||||
to only run the pydantic tests.
|
||||
to only run the pydantic tests.
|
||||
- Use `cargo benchmark --quiet` for a more cleaned up output (without statistical relevance)
|
||||
- Use `cargo benchmark --quick` to get faster results (more prone to noise)
|
||||
|
||||
## Profiling Projects
|
||||
### Profiling Projects
|
||||
|
||||
You can either use the microbenchmarks from above or a project directory for benchmarking. There
|
||||
are a lot of profiling tools out there,
|
||||
[The Rust Performance Book](https://nnethercote.github.io/perf-book/profiling.html) lists some
|
||||
examples.
|
||||
|
||||
### Linux
|
||||
#### Linux
|
||||
|
||||
Install `perf` and build `ruff_benchmark` with the `release-debug` profile and then run it with perf
|
||||
|
||||
@@ -531,7 +563,7 @@ An alternative is to convert the perf data to `flamegraph.svg` using
|
||||
flamegraph --perfdata perf.data
|
||||
```
|
||||
|
||||
### Mac
|
||||
#### Mac
|
||||
|
||||
Install [`cargo-instruments`](https://crates.io/crates/cargo-instruments):
|
||||
|
||||
@@ -546,7 +578,7 @@ cargo instruments -t time --bench linter --profile release-debug -p ruff_benchma
|
||||
```
|
||||
|
||||
- `-t`: Specifies what to profile. Useful options are `time` to profile the wall time and `alloc`
|
||||
for profiling the allocations.
|
||||
for profiling the allocations.
|
||||
- You may want to pass an additional filter to run a single test file
|
||||
|
||||
Otherwise, follow the instructions from the linux section.
|
||||
@@ -557,10 +589,10 @@ Otherwise, follow the instructions from the linux section.
|
||||
utils with it:
|
||||
|
||||
- `cargo dev print-ast <file>`: Print the AST of a python file using the
|
||||
[RustPython parser](https://github.com/astral-sh/RustPython-Parser/tree/main/parser) that is
|
||||
mainly used in Ruff. For `if True: pass # comment`, you can see the syntax tree, the byte offsets
|
||||
for start and stop of each node and also how the `:` token, the comment and whitespace are not
|
||||
represented anymore:
|
||||
[RustPython parser](https://github.com/astral-sh/RustPython-Parser/tree/main/parser) that is
|
||||
mainly used in Ruff. For `if True: pass # comment`, you can see the syntax tree, the byte offsets
|
||||
for start and stop of each node and also how the `:` token, the comment and whitespace are not
|
||||
represented anymore:
|
||||
|
||||
```text
|
||||
[
|
||||
@@ -590,7 +622,7 @@ utils with it:
|
||||
```
|
||||
|
||||
- `cargo dev print-tokens <file>`: Print the tokens that the AST is built upon. Again for
|
||||
`if True: pass # comment`:
|
||||
`if True: pass # comment`:
|
||||
|
||||
```text
|
||||
0 If 2
|
||||
@@ -604,8 +636,8 @@ utils with it:
|
||||
```
|
||||
|
||||
- `cargo dev print-cst <file>`: Print the CST of a python file using
|
||||
[LibCST](https://github.com/Instagram/LibCST), which is used in addition to the RustPython parser
|
||||
in Ruff. E.g. for `if True: pass # comment` everything including the whitespace is represented:
|
||||
[LibCST](https://github.com/Instagram/LibCST), which is used in addition to the RustPython parser
|
||||
in Ruff. E.g. for `if True: pass # comment` everything including the whitespace is represented:
|
||||
|
||||
```text
|
||||
Module {
|
||||
@@ -671,13 +703,54 @@ Module {
|
||||
```
|
||||
|
||||
- `cargo dev generate-all`: Update `ruff.schema.json`, `docs/configuration.md` and `docs/rules`.
|
||||
You can also set `RUFF_UPDATE_SCHEMA=1` to update `ruff.schema.json` during `cargo test`.
|
||||
You can also set `RUFF_UPDATE_SCHEMA=1` to update `ruff.schema.json` during `cargo test`.
|
||||
- `cargo dev generate-cli-help`, `cargo dev generate-docs` and `cargo dev generate-json-schema`:
|
||||
Update just `docs/configuration.md`, `docs/rules` and `ruff.schema.json` respectively.
|
||||
Update just `docs/configuration.md`, `docs/rules` and `ruff.schema.json` respectively.
|
||||
- `cargo dev generate-options`: Generate a markdown-compatible table of all `pyproject.toml`
|
||||
options. Used for <https://beta.ruff.rs/docs/settings/>
|
||||
options. Used for <https://beta.ruff.rs/docs/settings/>
|
||||
- `cargo dev generate-rules-table`: Generate a markdown-compatible table of all rules. Used for <https://beta.ruff.rs/docs/rules/>
|
||||
- `cargo dev round-trip <python file or jupyter notebook>`: Read a Python file or Jupyter Notebook,
|
||||
parse it, serialize the parsed representation and write it back. Used to check how good our
|
||||
representation is so that fixes don't rewrite irrelevant parts of a file.
|
||||
parse it, serialize the parsed representation and write it back. Used to check how good our
|
||||
representation is so that fixes don't rewrite irrelevant parts of a file.
|
||||
- `cargo dev format_dev`: See ruff_python_formatter README.md
|
||||
|
||||
## Subsystems
|
||||
|
||||
### Compilation Pipeline
|
||||
|
||||
If we view Ruff as a compiler, in which the inputs are paths to Python files and the outputs are
|
||||
diagnostics, then our current compilation pipeline proceeds as follows:
|
||||
|
||||
1. **File discovery**: Given paths like `foo/`, locate all Python files in any specified subdirectories, taking into account our hierarchical settings system and any `exclude` options.
|
||||
|
||||
1. **Package resolution**: Determine the “package root” for every file by traversing over its parent directories and looking for `__init__.py` files.
|
||||
|
||||
1. **Cache initialization**: For every “package root”, initialize an empty cache.
|
||||
|
||||
1. **Analysis**: For every file, in parallel:
|
||||
|
||||
1. **Cache read**: If the file is cached (i.e., its modification timestamp hasn't changed since it was last analyzed), short-circuit, and return the cached diagnostics.
|
||||
|
||||
1. **Tokenization**: Run the lexer over the file to generate a token stream.
|
||||
|
||||
1. **Indexing**: Extract metadata from the token stream, such as: comment ranges, `# noqa` locations, `# isort: off` locations, “doc lines”, etc.
|
||||
|
||||
1. **Token-based rule evaluation**: Run any lint rules that are based on the contents of the token stream (e.g., commented-out code).
|
||||
|
||||
1. **Filesystem-based rule evaluation**: Run any lint rules that are based on the contents of the filesystem (e.g., lack of `__init__.py` file in a package).
|
||||
|
||||
1. **Logical line-based rule evaluation**: Run any lint rules that are based on logical lines (e.g., stylistic rules).
|
||||
|
||||
1. **Parsing**: Run the parser over the token stream to produce an AST. (This consumes the token stream, so anything that relies on the token stream needs to happen before parsing.)
|
||||
|
||||
1. **AST-based rule evaluation**: Run any lint rules that are based on the AST. This includes the vast majority of lint rules. As part of this step, we also build the semantic model for the current file as we traverse over the AST. Some lint rules are evaluated eagerly, as we iterate over the AST, while others are evaluated in a deferred manner (e.g., unused imports, since we can’t determine whether an import is unused until we’ve finished analyzing the entire file), after we’ve finished the initial traversal.
|
||||
|
||||
1. **Import-based rule evaluation**: Run any lint rules that are based on the module’s imports (e.g., import sorting). These could, in theory, be included in the AST-based rule evaluation phase — they’re just separated for simplicity.
|
||||
|
||||
1. **Physical line-based rule evaluation**: Run any lint rules that are based on physical lines (e.g., line-length).
|
||||
|
||||
1. **Suppression enforcement**: Remove any violations that are suppressed via `# noqa` directives or `per-file-ignores`.
|
||||
|
||||
1. **Cache write**: Write the generated diagnostics to the package cache using the file as a key.
|
||||
|
||||
1. **Reporting**: Print diagnostics in the specified format (text, JSON, etc.), to the specified output channel (stdout, a file, etc.).
|
||||
|
||||
284
Cargo.lock
generated
284
Cargo.lock
generated
@@ -14,15 +14,6 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.2"
|
||||
@@ -120,9 +111,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.71"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
|
||||
|
||||
[[package]]
|
||||
name = "argfile"
|
||||
@@ -135,9 +126,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.0.11"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86d6b683edf8d1119fe420a94f8a7e389239666aa72e65495d91c00462510151"
|
||||
checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"bstr",
|
||||
@@ -279,9 +270,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.3.11"
|
||||
version = "4.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d"
|
||||
checksum = "98330784c494e49850cb23b8e2afcca13587d2500b2e3f1f78ae20248059c9be"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -290,9 +281,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.3.11"
|
||||
version = "4.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b"
|
||||
checksum = "e182eb5f2562a67dda37e2c57af64d720a9e010c5e860ed87c056586aeafa52e"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -343,14 +334,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.3.2"
|
||||
version = "4.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f"
|
||||
checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -534,21 +525,11 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.1"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944"
|
||||
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
@@ -556,27 +537,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.1"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb"
|
||||
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.1"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
|
||||
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -646,9 +627,9 @@ checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.11"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
|
||||
checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
@@ -677,9 +658,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
@@ -804,11 +785,11 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.10"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
|
||||
checksum = "1391ab1f92ffcc08911957149833e682aa3fe252b9f45f966d2ef972274c97df"
|
||||
dependencies = [
|
||||
"aho-corasick 0.7.20",
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
@@ -963,6 +944,12 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4"
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.6"
|
||||
@@ -985,9 +972,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.30.0"
|
||||
version = "1.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28491f7753051e5704d4d0ae7860d45fae3238d7d235bc4289dcd45c48d3cec3"
|
||||
checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a"
|
||||
dependencies = [
|
||||
"console",
|
||||
"globset",
|
||||
@@ -1033,12 +1020,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix 0.38.3",
|
||||
"rustix 0.38.4",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@@ -1053,9 +1040,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
@@ -1380,20 +1367,11 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "output_vt100"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35"
|
||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||
|
||||
[[package]]
|
||||
name = "path-absolutize"
|
||||
@@ -1520,7 +1498,7 @@ dependencies = [
|
||||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1579,9 +1557,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.3.3"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794"
|
||||
checksum = "edc55135a600d700580e406b4de0d59cb9ad25e344a3a091a97ded2622ec4ec6"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
@@ -1613,13 +1591,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755"
|
||||
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
|
||||
dependencies = [
|
||||
"ctor",
|
||||
"diff",
|
||||
"output_vt100",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
@@ -1649,9 +1625,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.63"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1671,12 +1647,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quick-junit"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05b909fe9bf2abb1e3d6a97c9189a37c8105c61d03dca9ce6aace023e7d682bd"
|
||||
checksum = "6bf780b59d590c25f8c59b44c124166a2a93587868b619fb8f5b47fb15e9ed6d"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.0.0",
|
||||
"nextest-workspace-hack",
|
||||
"quick-xml",
|
||||
"thiserror",
|
||||
@@ -1685,18 +1661,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.26.0"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd"
|
||||
checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.29"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
|
||||
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -1769,11 +1745,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.0"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89089e897c013b3deb627116ae56a6955a72b8bed395c9526af31c9fe528b484"
|
||||
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
|
||||
dependencies = [
|
||||
"aho-corasick 1.0.2",
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
@@ -1781,20 +1757,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.0"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa250384981ea14565685dea16a9ccc4d1c541a13f82b9c168572264d1df8c56"
|
||||
checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
|
||||
dependencies = [
|
||||
"aho-corasick 1.0.2",
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846"
|
||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||
|
||||
[[package]]
|
||||
name = "result-like"
|
||||
@@ -1899,6 +1875,7 @@ dependencies = [
|
||||
"typed-arena",
|
||||
"unicode-width",
|
||||
"unicode_names2",
|
||||
"wsl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1986,6 +1963,7 @@ dependencies = [
|
||||
"clap",
|
||||
"ignore",
|
||||
"indicatif",
|
||||
"indoc",
|
||||
"itertools",
|
||||
"libcst",
|
||||
"log",
|
||||
@@ -2003,11 +1981,13 @@ dependencies = [
|
||||
"rustpython-format",
|
||||
"rustpython-parser",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"similar",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2051,7 +2031,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_textwrap",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2154,7 +2134,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruff_text_size"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=c174bbf1f29527edd43d432326327f16f47ab9e0#c174bbf1f29527edd43d432326327f16f47ab9e0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=126652b684910c29a7bcc32293d4ca0f81454e34#126652b684910c29a7bcc32293d4ca0f81454e34"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -2219,9 +2199,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.3"
|
||||
version = "0.38.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4"
|
||||
checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"errno",
|
||||
@@ -2232,13 +2212,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.21.2"
|
||||
version = "0.21.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f"
|
||||
checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"rustls-webpki",
|
||||
"rustls-webpki 0.101.1",
|
||||
"sct",
|
||||
]
|
||||
|
||||
@@ -2252,10 +2232,20 @@ dependencies = [
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.101.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=c174bbf1f29527edd43d432326327f16f47ab9e0#c174bbf1f29527edd43d432326327f16f47ab9e0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=126652b684910c29a7bcc32293d4ca0f81454e34#126652b684910c29a7bcc32293d4ca0f81454e34"
|
||||
dependencies = [
|
||||
"is-macro",
|
||||
"num-bigint",
|
||||
@@ -2266,7 +2256,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-format"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=c174bbf1f29527edd43d432326327f16f47ab9e0#c174bbf1f29527edd43d432326327f16f47ab9e0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=126652b684910c29a7bcc32293d4ca0f81454e34#126652b684910c29a7bcc32293d4ca0f81454e34"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"itertools",
|
||||
@@ -2278,7 +2268,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-literal"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=c174bbf1f29527edd43d432326327f16f47ab9e0#c174bbf1f29527edd43d432326327f16f47ab9e0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=126652b684910c29a7bcc32293d4ca0f81454e34#126652b684910c29a7bcc32293d4ca0f81454e34"
|
||||
dependencies = [
|
||||
"hexf-parse",
|
||||
"is-macro",
|
||||
@@ -2290,7 +2280,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=c174bbf1f29527edd43d432326327f16f47ab9e0#c174bbf1f29527edd43d432326327f16f47ab9e0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=126652b684910c29a7bcc32293d4ca0f81454e34#126652b684910c29a7bcc32293d4ca0f81454e34"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"is-macro",
|
||||
@@ -2313,7 +2303,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=c174bbf1f29527edd43d432326327f16f47ab9e0#c174bbf1f29527edd43d432326327f16f47ab9e0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=126652b684910c29a7bcc32293d4ca0f81454e34#126652b684910c29a7bcc32293d4ca0f81454e34"
|
||||
dependencies = [
|
||||
"is-macro",
|
||||
"memchr",
|
||||
@@ -2322,15 +2312,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f"
|
||||
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.14"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
@@ -2373,9 +2363,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
@@ -2389,15 +2379,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.17"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.166"
|
||||
version = "1.0.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8"
|
||||
checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -2415,13 +2405,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.166"
|
||||
version = "1.0.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6"
|
||||
checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2437,9 +2427,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.100"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c"
|
||||
checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -2457,9 +2447,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.0.0"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513"
|
||||
checksum = "21e47d95bc83ed33b2ecf84f4187ad1ab9685d18ff28db000c99deac8ce180e3"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"chrono",
|
||||
@@ -2468,19 +2458,19 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
"time 0.3.22",
|
||||
"time 0.3.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.0.0"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070"
|
||||
checksum = "ea3cee93715c2e266b9338b7544da68a9f24e227722ba482bd1c024367c77c65"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2506,9 +2496,9 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
@@ -2563,9 +2553,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.23"
|
||||
version = "2.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737"
|
||||
checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2675,7 +2665,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2721,9 +2711,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.22"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd"
|
||||
checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
@@ -2739,9 +2729,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.9"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b"
|
||||
checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
@@ -2782,9 +2772,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.5"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebafdf5ad1220cb59e7d17cf4d2c72015297b75b19a10472f99b89225089240"
|
||||
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@@ -2803,9 +2793,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.11"
|
||||
version = "0.19.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7"
|
||||
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
|
||||
dependencies = [
|
||||
"indexmap 2.0.0",
|
||||
"serde",
|
||||
@@ -2835,7 +2825,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2925,9 +2915,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.10"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
@@ -2969,7 +2959,7 @@ dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-webpki",
|
||||
"rustls-webpki 0.100.1",
|
||||
"url",
|
||||
"webpki-roots",
|
||||
]
|
||||
@@ -2994,9 +2984,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.4.0"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be"
|
||||
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
@@ -3056,7 +3046,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3090,7 +3080,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -3141,7 +3131,7 @@ version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338"
|
||||
dependencies = [
|
||||
"rustls-webpki",
|
||||
"rustls-webpki 0.100.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3338,13 +3328,19 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.7"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448"
|
||||
checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wsl"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dab7ac864710bdea6594becbea5b5050333cf34fefb0dc319567eb347950d4"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
|
||||
13
Cargo.toml
13
Cargo.toml
@@ -47,18 +47,19 @@ syn = { version = "2.0.15" }
|
||||
test-case = { version = "3.0.0" }
|
||||
thiserror = { version = "1.0.43" }
|
||||
toml = { version = "0.7.2" }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
# v1.0.1
|
||||
libcst = { git = "https://github.com/Instagram/LibCST.git", rev = "3cacca1a1029f05707e50703b49fe3dd860aa839", default-features = false }
|
||||
|
||||
# Please tag the RustPython version every time you update its revision here and in fuzz/Cargo.toml
|
||||
# Tagging the version ensures that older ruff versions continue to build from source even when we rebase our RustPython fork.
|
||||
# Current tag: v0.0.7
|
||||
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0" }
|
||||
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0" , default-features = false, features = ["num-bigint"]}
|
||||
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0", default-features = false, features = ["num-bigint"] }
|
||||
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0", default-features = false }
|
||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0" , default-features = false, features = ["full-lexer", "num-bigint"] }
|
||||
# Note: As of tag v0.0.8 we are cherry-picking commits instead of rebasing so the tag is not necessary
|
||||
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "126652b684910c29a7bcc32293d4ca0f81454e34" }
|
||||
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "126652b684910c29a7bcc32293d4ca0f81454e34" , default-features = false, features = ["num-bigint"]}
|
||||
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "126652b684910c29a7bcc32293d4ca0f81454e34", default-features = false, features = ["num-bigint"] }
|
||||
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "126652b684910c29a7bcc32293d4ca0f81454e34", default-features = false }
|
||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "126652b684910c29a7bcc32293d4ca0f81454e34" , default-features = false, features = ["full-lexer", "num-bigint"] }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
||||
12
README.md
12
README.md
@@ -32,10 +32,10 @@ An extremely fast Python linter, written in Rust.
|
||||
- 🔧 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
|
||||
built-in Flake8 rule set
|
||||
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
|
||||
- ⌨️ First-party [editor integrations](https://beta.ruff.rs/docs/editor-integrations/) for
|
||||
[VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
|
||||
[VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
|
||||
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://beta.ruff.rs/docs/configuration/#pyprojecttoml-discovery)
|
||||
|
||||
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
|
||||
@@ -364,8 +364,8 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [Great Expectations](https://github.com/great-expectations/great_expectations)
|
||||
- [HTTPX](https://github.com/encode/httpx)
|
||||
- Hugging Face ([Transformers](https://github.com/huggingface/transformers),
|
||||
[Datasets](https://github.com/huggingface/datasets),
|
||||
[Diffusers](https://github.com/huggingface/diffusers))
|
||||
[Datasets](https://github.com/huggingface/datasets),
|
||||
[Diffusers](https://github.com/huggingface/diffusers))
|
||||
- [Hatch](https://github.com/pypa/hatch)
|
||||
- [Home Assistant](https://github.com/home-assistant/core)
|
||||
- ING Bank ([popmon](https://github.com/ing-bank/popmon), [probatus](https://github.com/ing-bank/probatus))
|
||||
@@ -377,8 +377,8 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [MegaLinter](https://github.com/oxsecurity/megalinter)
|
||||
- Meltano ([Meltano CLI](https://github.com/meltano/meltano), [Singer SDK](https://github.com/meltano/sdk))
|
||||
- Microsoft ([Semantic Kernel](https://github.com/microsoft/semantic-kernel),
|
||||
[ONNX Runtime](https://github.com/microsoft/onnxruntime),
|
||||
[LightGBM](https://github.com/microsoft/LightGBM))
|
||||
[ONNX Runtime](https://github.com/microsoft/onnxruntime),
|
||||
[LightGBM](https://github.com/microsoft/LightGBM))
|
||||
- Modern Treasury ([Python SDK](https://github.com/Modern-Treasury/modern-treasury-python-sdk))
|
||||
- Mozilla ([Firefox](https://github.com/mozilla/gecko-dev))
|
||||
- [Mypy](https://github.com/python/mypy)
|
||||
|
||||
@@ -82,12 +82,12 @@ flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
|
||||
## Limitations
|
||||
|
||||
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.)
|
||||
ignore unsupported options in the `.flake8` file (or equivalent). (Similarly, Ruff has a few
|
||||
configuration options that don't exist in Flake8.)
|
||||
1. Ruff will omit any rule codes that are unimplemented or unsupported by Ruff, including rule
|
||||
codes from unsupported plugins. (See the
|
||||
[documentation](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) for the complete
|
||||
list of supported plugins.)
|
||||
codes from unsupported plugins. (See the
|
||||
[documentation](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) for the complete
|
||||
list of supported plugins.)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ toml = { workspace = true }
|
||||
typed-arena = { version = "2.0.2" }
|
||||
unicode-width = { version = "0.1.10" }
|
||||
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Type
|
||||
from typing import Annotated, Any, Optional, Type, Union
|
||||
from typing_extensions import override
|
||||
|
||||
# Error
|
||||
@@ -95,27 +95,27 @@ class Foo:
|
||||
def foo(self: "Foo", a: int, *params: str, **options: Any) -> int:
|
||||
pass
|
||||
|
||||
# ANN401
|
||||
# OK
|
||||
@override
|
||||
def foo(self: "Foo", a: Any, *params: str, **options: str) -> int:
|
||||
pass
|
||||
|
||||
# ANN401
|
||||
# OK
|
||||
@override
|
||||
def foo(self: "Foo", a: int, *params: str, **options: str) -> Any:
|
||||
pass
|
||||
|
||||
# ANN401
|
||||
# OK
|
||||
@override
|
||||
def foo(self: "Foo", a: int, *params: Any, **options: Any) -> int:
|
||||
pass
|
||||
|
||||
# ANN401
|
||||
# OK
|
||||
@override
|
||||
def foo(self: "Foo", a: int, *params: Any, **options: str) -> int:
|
||||
pass
|
||||
|
||||
# ANN401
|
||||
# OK
|
||||
@override
|
||||
def foo(self: "Foo", a: int, *params: str, **options: Any) -> int:
|
||||
pass
|
||||
@@ -137,3 +137,18 @@ class Foo:
|
||||
|
||||
# OK
|
||||
def f(*args: *tuple[int]) -> None: ...
|
||||
def f(a: object) -> None: ...
|
||||
def f(a: str | bytes) -> None: ...
|
||||
def f(a: Union[str, bytes]) -> None: ...
|
||||
def f(a: Optional[str]) -> None: ...
|
||||
def f(a: Annotated[str, ...]) -> None: ...
|
||||
def f(a: "Union[str, bytes]") -> None: ...
|
||||
def f(a: int + int) -> None: ...
|
||||
|
||||
# ANN401
|
||||
def f(a: Any | int) -> None: ...
|
||||
def f(a: int | Any) -> None: ...
|
||||
def f(a: Union[str, bytes, Any]) -> None: ...
|
||||
def f(a: Optional[Any]) -> None: ...
|
||||
def f(a: Annotated[Any, ...]) -> None: ...
|
||||
def f(a: "Union[str, bytes, Any]") -> None: ...
|
||||
|
||||
@@ -177,6 +177,9 @@ def str_okay(value=str("foo")):
|
||||
def bool_okay(value=bool("bar")):
|
||||
pass
|
||||
|
||||
# Allow immutable bytes() value
|
||||
def bytes_okay(value=bytes(1)):
|
||||
pass
|
||||
|
||||
# Allow immutable int() value
|
||||
def int_okay(value=int("12")):
|
||||
|
||||
@@ -14,9 +14,10 @@ except AssertionError:
|
||||
except Exception as err:
|
||||
assert err
|
||||
raise Exception("No cause here...")
|
||||
except BaseException as base_err:
|
||||
# Might use this instead of bare raise with the `.with_traceback()` method
|
||||
raise base_err
|
||||
except BaseException as err:
|
||||
raise err
|
||||
except BaseException as err:
|
||||
raise some_other_err
|
||||
finally:
|
||||
raise Exception("Nothing to chain from, so no warning here")
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ set(reversed(x))
|
||||
sorted(list(x))
|
||||
sorted(tuple(x))
|
||||
sorted(sorted(x))
|
||||
sorted(sorted(x, key=lambda y: y))
|
||||
sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
|
||||
sorted(sorted(x, reverse=True), reverse=True)
|
||||
sorted(reversed(x))
|
||||
sorted(list(x), key=lambda y: y)
|
||||
tuple(
|
||||
@@ -21,3 +22,9 @@ tuple(
|
||||
"o"]
|
||||
)
|
||||
)
|
||||
|
||||
# Nested sorts with differing keyword arguments. Not flagged.
|
||||
sorted(sorted(x, key=lambda y: y))
|
||||
sorted(sorted(x, key=lambda y: y), key=lambda x: x)
|
||||
sorted(sorted(x), reverse=True)
|
||||
sorted(sorted(x, reverse=False), reverse=True)
|
||||
|
||||
@@ -59,7 +59,6 @@ field18: typing.Union[
|
||||
],
|
||||
] # Error, newline and comment will not be emitted in message
|
||||
|
||||
|
||||
# Should emit in cases with `typing.Union` instead of `|`
|
||||
field19: typing.Union[int, int] # Error
|
||||
|
||||
@@ -71,3 +70,7 @@ field21: typing.Union[int, int | str] # Error
|
||||
|
||||
# Should emit only once in cases with multiple nested `typing.Union`
|
||||
field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error
|
||||
|
||||
# Should emit in cases with newlines
|
||||
field23: set[ # foo
|
||||
int] | set[int]
|
||||
|
||||
75
crates/ruff/resources/test/fixtures/flake8_pyi/PYI036.py
vendored
Normal file
75
crates/ruff/resources/test/fixtures/flake8_pyi/PYI036.py
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
import builtins
|
||||
import types
|
||||
import typing
|
||||
from collections.abc import Awaitable
|
||||
from types import TracebackType
|
||||
from typing import Any, Type
|
||||
|
||||
import _typeshed
|
||||
import typing_extensions
|
||||
from _typeshed import Unused
|
||||
|
||||
class GoodOne:
|
||||
def __exit__(self, *args: object) -> None: ...
|
||||
async def __aexit__(self, *args) -> str: ...
|
||||
|
||||
class GoodTwo:
|
||||
def __exit__(self, typ: type[builtins.BaseException] | None, *args: builtins.object) -> bool | None: ...
|
||||
async def __aexit__(self, /, typ: Type[BaseException] | None, *args: object, **kwargs) -> bool: ...
|
||||
|
||||
class GoodThree:
|
||||
def __exit__(self, __typ: typing.Type[BaseException] | None, exc: BaseException | None, *args: object) -> None: ...
|
||||
async def __aexit__(self, typ: typing_extensions.Type[BaseException] | None, __exc: BaseException | None, *args: object) -> None: ...
|
||||
|
||||
class GoodFour:
|
||||
def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ...
|
||||
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: types.TracebackType | None, *args: list[None]) -> None: ...
|
||||
|
||||
class GoodFive:
|
||||
def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, weird_extra_arg: int = ..., *args: int, **kwargs: str) -> None: ...
|
||||
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> Awaitable[None]: ...
|
||||
|
||||
class GoodSix:
|
||||
def __exit__(self, typ: object, exc: builtins.object, tb: object) -> None: ...
|
||||
async def __aexit__(self, typ: object, exc: object, tb: builtins.object) -> None: ...
|
||||
|
||||
class GoodSeven:
|
||||
def __exit__(self, *args: Unused) -> bool: ...
|
||||
async def __aexit__(self, typ: Type[BaseException] | None, *args: _typeshed.Unused) -> Awaitable[None]: ...
|
||||
|
||||
class GoodEight:
|
||||
def __exit__(self, __typ: typing.Type[BaseException] | None, exc: BaseException | None, *args: _typeshed.Unused) -> bool: ...
|
||||
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
|
||||
|
||||
class GoodNine:
|
||||
def __exit__(self, __typ: typing.Union[typing.Type[BaseException] , None], exc: typing.Union[BaseException , None], *args: _typeshed.Unused) -> bool: ...
|
||||
async def __aexit__(self, typ: typing.Union[typing.Type[BaseException], None], exc: typing.Union[BaseException , None], tb: typing.Union[TracebackType , None], weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
|
||||
|
||||
class GoodTen:
|
||||
def __exit__(self, __typ: typing.Optional[typing.Type[BaseException]], exc: typing.Optional[BaseException], *args: _typeshed.Unused) -> bool: ...
|
||||
async def __aexit__(self, typ: typing.Optional[typing.Type[BaseException]], exc: typing.Optional[BaseException], tb: typing.Optional[TracebackType], weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
|
||||
|
||||
|
||||
class BadOne:
|
||||
def __exit__(self, *args: Any) -> None: ... # PYI036: Bad star-args annotation
|
||||
async def __aexit__(self) -> None: ... # PYI036: Missing args
|
||||
|
||||
class BadTwo:
|
||||
def __exit__(self, typ, exc, tb, weird_extra_arg) -> None: ... # PYI036: Extra arg must have default
|
||||
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ...# PYI036: Extra arg must have default
|
||||
|
||||
class BadThree:
|
||||
def __exit__(self, typ: type[BaseException], exc: BaseException | None, tb: TracebackType | None) -> None: ... # PYI036: First arg has bad annotation
|
||||
async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException, __tb: TracebackType) -> bool | None: ... # PYI036: Second arg has bad annotation
|
||||
|
||||
class BadFour:
|
||||
def __exit__(self, typ: typing.Optional[type[BaseException]], exc: typing.Union[BaseException, None], tb: TracebackType) -> None: ... # PYI036: Third arg has bad annotation
|
||||
async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException | None, __tb: typing.Union[TracebackType, None, int]) -> bool | None: ... # PYI036: Third arg has bad annotation
|
||||
|
||||
class BadFive:
|
||||
def __exit__(self, typ: BaseException | None, *args: list[str]) -> bool: ... # PYI036: Bad star-args annotation
|
||||
async def __aexit__(self, /, typ: type[BaseException] | None, *args: Any) -> Awaitable[None]: ... # PYI036: Bad star-args annotation
|
||||
|
||||
class BadSix:
|
||||
def __exit__(self, typ, exc, tb, weird_extra_arg, extra_arg2 = None) -> None: ... # PYI036: Extra arg must have default
|
||||
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ... # PYI036: kwargs must have default
|
||||
75
crates/ruff/resources/test/fixtures/flake8_pyi/PYI036.pyi
vendored
Normal file
75
crates/ruff/resources/test/fixtures/flake8_pyi/PYI036.pyi
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
import builtins
|
||||
import types
|
||||
import typing
|
||||
from collections.abc import Awaitable
|
||||
from types import TracebackType
|
||||
from typing import Any, Type
|
||||
|
||||
import _typeshed
|
||||
import typing_extensions
|
||||
from _typeshed import Unused
|
||||
|
||||
class GoodOne:
|
||||
def __exit__(self, *args: object) -> None: ...
|
||||
async def __aexit__(self, *args) -> str: ...
|
||||
|
||||
class GoodTwo:
|
||||
def __exit__(self, typ: type[builtins.BaseException] | None, *args: builtins.object) -> bool | None: ...
|
||||
async def __aexit__(self, /, typ: Type[BaseException] | None, *args: object, **kwargs) -> bool: ...
|
||||
|
||||
class GoodThree:
|
||||
def __exit__(self, __typ: typing.Type[BaseException] | None, exc: BaseException | None, *args: object) -> None: ...
|
||||
async def __aexit__(self, typ: typing_extensions.Type[BaseException] | None, __exc: BaseException | None, *args: object) -> None: ...
|
||||
|
||||
class GoodFour:
|
||||
def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ...
|
||||
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: types.TracebackType | None, *args: list[None]) -> None: ...
|
||||
|
||||
class GoodFive:
|
||||
def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, weird_extra_arg: int = ..., *args: int, **kwargs: str) -> None: ...
|
||||
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> Awaitable[None]: ...
|
||||
|
||||
class GoodSix:
|
||||
def __exit__(self, typ: object, exc: builtins.object, tb: object) -> None: ...
|
||||
async def __aexit__(self, typ: object, exc: object, tb: builtins.object) -> None: ...
|
||||
|
||||
class GoodSeven:
|
||||
def __exit__(self, *args: Unused) -> bool: ...
|
||||
async def __aexit__(self, typ: Type[BaseException] | None, *args: _typeshed.Unused) -> Awaitable[None]: ...
|
||||
|
||||
class GoodEight:
|
||||
def __exit__(self, __typ: typing.Type[BaseException] | None, exc: BaseException | None, *args: _typeshed.Unused) -> bool: ...
|
||||
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
|
||||
|
||||
class GoodNine:
|
||||
def __exit__(self, __typ: typing.Union[typing.Type[BaseException] , None], exc: typing.Union[BaseException , None], *args: _typeshed.Unused) -> bool: ...
|
||||
async def __aexit__(self, typ: typing.Union[typing.Type[BaseException], None], exc: typing.Union[BaseException , None], tb: typing.Union[TracebackType , None], weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
|
||||
|
||||
class GoodTen:
|
||||
def __exit__(self, __typ: typing.Optional[typing.Type[BaseException]], exc: typing.Optional[BaseException], *args: _typeshed.Unused) -> bool: ...
|
||||
async def __aexit__(self, typ: typing.Optional[typing.Type[BaseException]], exc: typing.Optional[BaseException], tb: typing.Optional[TracebackType], weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
|
||||
|
||||
|
||||
class BadOne:
|
||||
def __exit__(self, *args: Any) -> None: ... # PYI036: Bad star-args annotation
|
||||
async def __aexit__(self) -> None: ... # PYI036: Missing args
|
||||
|
||||
class BadTwo:
|
||||
def __exit__(self, typ, exc, tb, weird_extra_arg) -> None: ... # PYI036: Extra arg must have default
|
||||
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg1, weird_extra_arg2) -> None: ...# PYI036: kwargs must have default
|
||||
|
||||
class BadThree:
|
||||
def __exit__(self, typ: type[BaseException], exc: BaseException | None, tb: TracebackType | None) -> None: ... # PYI036: First arg has bad annotation
|
||||
async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException, __tb: TracebackType) -> bool | None: ... # PYI036: Second arg has bad annotation
|
||||
|
||||
class BadFour:
|
||||
def __exit__(self, typ: typing.Optional[type[BaseException]], exc: typing.Union[BaseException, None], tb: TracebackType) -> None: ... # PYI036: Third arg has bad annotation
|
||||
async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException | None, __tb: typing.Union[TracebackType, None, int]) -> bool | None: ... # PYI036: Third arg has bad annotation
|
||||
|
||||
class BadFive:
|
||||
def __exit__(self, typ: BaseException | None, *args: list[str]) -> bool: ... # PYI036: Bad star-args annotation
|
||||
async def __aexit__(self, /, typ: type[BaseException] | None, *args: Any) -> Awaitable[None]: ... # PYI036: Bad star-args annotation
|
||||
|
||||
class BadSix:
|
||||
def __exit__(self, typ, exc, tb, weird_extra_arg, extra_arg2 = None) -> None: ... # PYI036: Extra arg must have default
|
||||
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ... # PYI036: kwargs must have default
|
||||
47
crates/ruff/resources/test/fixtures/flake8_pyi/PYI041.py
vendored
Normal file
47
crates/ruff/resources/test/fixtures/flake8_pyi/PYI041.py
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
from typing_extensions import (
|
||||
TypeAlias,
|
||||
)
|
||||
|
||||
TA0: TypeAlias = int
|
||||
TA1: TypeAlias = int | float | bool
|
||||
TA2: TypeAlias = Union[int, float, bool]
|
||||
|
||||
|
||||
def good1(arg: int) -> int | bool:
|
||||
...
|
||||
|
||||
|
||||
def good2(arg: int, arg2: int | bool) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f0(arg1: float | int) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f2(arg1: int, /, arg2: int | int | float) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f3(arg1: int, *args: Union[int | int | float]) -> None:
|
||||
...
|
||||
|
||||
|
||||
async def f4(**kwargs: int | int | float) -> None:
|
||||
...
|
||||
|
||||
|
||||
class Foo:
|
||||
def good(self, arg: int) -> None:
|
||||
...
|
||||
|
||||
def bad(self, arg: int | float | complex) -> None:
|
||||
...
|
||||
39
crates/ruff/resources/test/fixtures/flake8_pyi/PYI041.pyi
vendored
Normal file
39
crates/ruff/resources/test/fixtures/flake8_pyi/PYI041.pyi
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
from typing_extensions import (
|
||||
TypeAlias,
|
||||
)
|
||||
|
||||
# Type aliases not flagged
|
||||
TA0: TypeAlias = int
|
||||
TA1: TypeAlias = int | float | bool
|
||||
TA2: TypeAlias = Union[int, float, bool]
|
||||
|
||||
|
||||
def good1(arg: int) -> int | bool: ...
|
||||
|
||||
|
||||
def good2(arg: int, arg2: int | bool) -> None: ...
|
||||
|
||||
|
||||
def f0(arg1: float | int) -> None: ... # PYI041
|
||||
|
||||
|
||||
def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: ... # PYI041
|
||||
|
||||
|
||||
def f2(arg1: int, /, arg2: int | int | float) -> None: ... # PYI041
|
||||
|
||||
|
||||
def f3(arg1: int, *args: Union[int | int | float]) -> None: ... # PYI041
|
||||
|
||||
|
||||
async def f4(**kwargs: int | int | float) -> None: ... # PYI041
|
||||
|
||||
|
||||
class Foo:
|
||||
def good(self, arg: int) -> None: ...
|
||||
|
||||
def bad(self, arg: int | float | complex) -> None: ... # PYI041
|
||||
@@ -4,3 +4,10 @@ class Bad(str): # SLOT000
|
||||
|
||||
class Good(str): # Ok
|
||||
__slots__ = ["foo"]
|
||||
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Fine(str, Enum): # Ok
|
||||
__slots__ = ["foo"]
|
||||
|
||||
27
crates/ruff/resources/test/fixtures/pandas_vet/PD101.py
vendored
Normal file
27
crates/ruff/resources/test/fixtures/pandas_vet/PD101.py
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import pandas as pd
|
||||
|
||||
|
||||
data = pd.Series(range(1000))
|
||||
|
||||
# PD101
|
||||
data.nunique() <= 1
|
||||
data.nunique(dropna=True) <= 1
|
||||
data.nunique(dropna=False) <= 1
|
||||
data.nunique() == 1
|
||||
data.nunique(dropna=True) == 1
|
||||
data.nunique(dropna=False) == 1
|
||||
data.nunique() != 1
|
||||
data.nunique(dropna=True) != 1
|
||||
data.nunique(dropna=False) != 1
|
||||
data.nunique() > 1
|
||||
data.dropna().nunique() == 1
|
||||
data[data.notnull()].nunique() == 1
|
||||
|
||||
# No violation of this rule
|
||||
data.nunique() == 0 # empty
|
||||
data.nunique() >= 1 # not-empty
|
||||
data.nunique() < 1 # empty
|
||||
data.nunique() == 2 # not constant
|
||||
data.unique() == 1 # not `nunique`
|
||||
|
||||
{"hello": "world"}.nunique() == 1 # no pd.Series
|
||||
20
crates/ruff/resources/test/fixtures/pandas_vet/pandas_use_of_dot_read_table.py
vendored
Normal file
20
crates/ruff/resources/test/fixtures/pandas_vet/pandas_use_of_dot_read_table.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import pandas as pd
|
||||
|
||||
# Errors.
|
||||
df = pd.read_table("data.csv", sep=",")
|
||||
df = pd.read_table("data.csv", sep=",", header=0)
|
||||
filename = "data.csv"
|
||||
df = pd.read_table(filename, sep=",")
|
||||
df = pd.read_table(filename, sep=",", header=0)
|
||||
|
||||
# Non-errors.
|
||||
df = pd.read_csv("data.csv")
|
||||
df = pd.read_table("data.tsv")
|
||||
df = pd.read_table("data.tsv", sep="\t")
|
||||
df = pd.read_table("data.tsv", sep=",,")
|
||||
df = pd.read_table("data.tsv", sep=", ")
|
||||
df = pd.read_table("data.tsv", sep=" ,")
|
||||
df = pd.read_table("data.tsv", sep=" , ")
|
||||
not_pd.read_table("data.csv", sep=",")
|
||||
data = read_table("data.csv", sep=",")
|
||||
data = read_table
|
||||
@@ -1,71 +1,101 @@
|
||||
some_dict = {"a": 12, "b": 32, "c": 44}
|
||||
|
||||
for _, value in some_dict.items(): # PERF102
|
||||
print(value)
|
||||
|
||||
def f():
|
||||
for _, value in some_dict.items(): # PERF102
|
||||
print(value)
|
||||
|
||||
|
||||
for key, _ in some_dict.items(): # PERF102
|
||||
print(key)
|
||||
def f():
|
||||
for key, _ in some_dict.items(): # PERF102
|
||||
print(key)
|
||||
|
||||
|
||||
for weird_arg_name, _ in some_dict.items(): # PERF102
|
||||
print(weird_arg_name)
|
||||
def f():
|
||||
for weird_arg_name, _ in some_dict.items(): # PERF102
|
||||
print(weird_arg_name)
|
||||
|
||||
|
||||
for name, (_, _) in some_dict.items(): # PERF102
|
||||
pass
|
||||
def f():
|
||||
for name, (_, _) in some_dict.items(): # PERF102
|
||||
print(name)
|
||||
|
||||
|
||||
for name, (value1, _) in some_dict.items(): # OK
|
||||
pass
|
||||
def f():
|
||||
for name, (value1, _) in some_dict.items(): # OK
|
||||
print(name, value1)
|
||||
|
||||
|
||||
for (key1, _), (_, _) in some_dict.items(): # PERF102
|
||||
pass
|
||||
def f():
|
||||
for (key1, _), (_, _) in some_dict.items(): # PERF102
|
||||
print(key1)
|
||||
|
||||
|
||||
for (_, (_, _)), (value, _) in some_dict.items(): # PERF102
|
||||
pass
|
||||
def f():
|
||||
for (_, (_, _)), (value, _) in some_dict.items(): # PERF102
|
||||
print(value)
|
||||
|
||||
|
||||
for (_, key2), (value1, _) in some_dict.items(): # OK
|
||||
pass
|
||||
def f():
|
||||
for (_, key2), (value1, _) in some_dict.items(): # OK
|
||||
print(key2, value1)
|
||||
|
||||
|
||||
for ((_, key2), (value1, _)) in some_dict.items(): # OK
|
||||
pass
|
||||
def f():
|
||||
for ((_, key2), (value1, _)) in some_dict.items(): # OK
|
||||
print(key2, value1)
|
||||
|
||||
|
||||
for ((_, key2), (_, _)) in some_dict.items(): # PERF102
|
||||
pass
|
||||
def f():
|
||||
for ((_, key2), (_, _)) in some_dict.items(): # PERF102
|
||||
print(key2)
|
||||
|
||||
|
||||
for (_, _, _, variants), (r_language, _, _, _) in some_dict.items(): # OK
|
||||
pass
|
||||
def f():
|
||||
for (_, _, _, variants), (r_language, _, _, _) in some_dict.items(): # OK
|
||||
print(variants, r_language)
|
||||
|
||||
|
||||
for (_, _, (_, variants)), (_, (_, (r_language, _))) in some_dict.items(): # OK
|
||||
pass
|
||||
def f():
|
||||
for (_, _, (_, variants)), (_, (_, (r_language, _))) in some_dict.items(): # OK
|
||||
print(variants, r_language)
|
||||
|
||||
|
||||
for key, value in some_dict.items(): # OK
|
||||
print(key, value)
|
||||
def f():
|
||||
for key, value in some_dict.items(): # OK
|
||||
print(key, value)
|
||||
|
||||
|
||||
for _, value in some_dict.items(12): # OK
|
||||
print(value)
|
||||
def f():
|
||||
for _, value in some_dict.items(12): # OK
|
||||
print(value)
|
||||
|
||||
|
||||
for key in some_dict.keys(): # OK
|
||||
print(key)
|
||||
def f():
|
||||
for key in some_dict.keys(): # OK
|
||||
print(key)
|
||||
|
||||
|
||||
for value in some_dict.values(): # OK
|
||||
print(value)
|
||||
def f():
|
||||
for value in some_dict.values(): # OK
|
||||
print(value)
|
||||
|
||||
|
||||
for name, (_, _) in (some_function()).items(): # PERF102
|
||||
pass
|
||||
def f():
|
||||
for name, (_, _) in (some_function()).items(): # PERF102
|
||||
print(name)
|
||||
|
||||
for name, (_, _) in (some_function().some_attribute).items(): # PERF102
|
||||
pass
|
||||
|
||||
def f():
|
||||
for name, (_, _) in (some_function().some_attribute).items(): # PERF102
|
||||
print(name)
|
||||
|
||||
|
||||
def f():
|
||||
for name, unused_value in some_dict.items(): # PERF102
|
||||
print(name)
|
||||
|
||||
|
||||
def f():
|
||||
for unused_name, value in some_dict.items(): # PERF102
|
||||
print(value)
|
||||
|
||||
@@ -36,3 +36,4 @@ if (True) == TrueElement or x == TrueElement:
|
||||
assert (not foo) in bar
|
||||
assert {"x": not foo} in bar
|
||||
assert [42, not foo] in bar
|
||||
assert not (re.search(r"^.:\\Users\\[^\\]*\\Downloads\\.*") is None)
|
||||
|
||||
@@ -36,3 +36,4 @@ if (True) == TrueElement or x == TrueElement:
|
||||
assert (not foo) in bar
|
||||
assert {"x": not foo} in bar
|
||||
assert [42, not foo] in bar
|
||||
assert not (re.search(r"^.:\\Users\\[^\\]*\\Downloads\\.*") is None)
|
||||
|
||||
@@ -80,3 +80,8 @@ def multiple_assignment():
|
||||
global CONSTANT # [global-statement]
|
||||
CONSTANT = 1
|
||||
CONSTANT = 2
|
||||
|
||||
|
||||
def no_assignment():
|
||||
"""Shouldn't warn"""
|
||||
global CONSTANT
|
||||
|
||||
34
crates/ruff/resources/test/fixtures/pylint/repeated_equality_comparison_target.py
vendored
Normal file
34
crates/ruff/resources/test/fixtures/pylint/repeated_equality_comparison_target.py
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# Errors.
|
||||
foo == "a" or foo == "b"
|
||||
|
||||
foo != "a" and foo != "b"
|
||||
|
||||
foo == "a" or foo == "b" or foo == "c"
|
||||
|
||||
foo != "a" and foo != "b" and foo != "c"
|
||||
|
||||
foo == a or foo == "b" or foo == 3 # Mixed types.
|
||||
|
||||
# False negatives (the current implementation doesn't support Yoda conditions).
|
||||
"a" == foo or "b" == foo or "c" == foo
|
||||
|
||||
"a" != foo and "b" != foo and "c" != foo
|
||||
|
||||
"a" == foo or foo == "b" or "c" == foo
|
||||
|
||||
# OK
|
||||
foo == "a" and foo == "b" and foo == "c" # `and` mixed with `==`.
|
||||
|
||||
foo != "a" or foo != "b" or foo != "c" # `or` mixed with `!=`.
|
||||
|
||||
foo == a or foo == b() or foo == c # Call expression.
|
||||
|
||||
foo != a or foo() != b or foo != c # Call expression.
|
||||
|
||||
foo in {"a", "b", "c"} # Uses membership test already.
|
||||
|
||||
foo not in {"a", "b", "c"} # Uses membership test already.
|
||||
|
||||
foo == "a" # Single comparison.
|
||||
|
||||
foo != "a" # Single comparison.
|
||||
@@ -27,6 +27,14 @@ def f(x: typing.Union[(str, int), float]) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: typing.Union[(int,)]) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: typing.Union[()]) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: "Union[str, int, Union[float, bytes]]") -> None:
|
||||
...
|
||||
|
||||
|
||||
@@ -4,23 +4,9 @@ import typing
|
||||
# with complex annotations
|
||||
MyType = NamedTuple("MyType", [("a", int), ("b", tuple[str, ...])])
|
||||
|
||||
# with default values as list
|
||||
MyType = NamedTuple(
|
||||
"MyType",
|
||||
[("a", int), ("b", str), ("c", list[bool])],
|
||||
defaults=["foo", [True]],
|
||||
)
|
||||
|
||||
# with namespace
|
||||
MyType = typing.NamedTuple("MyType", [("a", int), ("b", str)])
|
||||
|
||||
# too many default values (OK)
|
||||
MyType = NamedTuple(
|
||||
"MyType",
|
||||
[("a", int), ("b", str)],
|
||||
defaults=[1, "bar", "baz"],
|
||||
)
|
||||
|
||||
# invalid identifiers (OK)
|
||||
MyType = NamedTuple("MyType", [("x-y", int), ("b", tuple[str, ...])])
|
||||
|
||||
@@ -29,3 +15,10 @@ MyType = typing.NamedTuple("MyType")
|
||||
|
||||
# empty fields
|
||||
MyType = typing.NamedTuple("MyType", [])
|
||||
|
||||
# keywords
|
||||
MyType = typing.NamedTuple("MyType", a=int, b=tuple[str, ...])
|
||||
|
||||
# unfixable
|
||||
MyType = typing.NamedTuple("MyType", [("a", int)], [("b", str)])
|
||||
MyType = typing.NamedTuple("MyType", [("a", int)], b=str)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""A mirror of UP037_1.py, with `from __future__ import annotations`."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import (
|
||||
108
crates/ruff/resources/test/fixtures/pyupgrade/UP037_1.py
vendored
Normal file
108
crates/ruff/resources/test/fixtures/pyupgrade/UP037_1.py
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
"""A mirror of UP037_0.py, without `from __future__ import annotations`."""
|
||||
|
||||
from typing import (
|
||||
Annotated,
|
||||
Callable,
|
||||
List,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
TypedDict,
|
||||
cast,
|
||||
)
|
||||
|
||||
from mypy_extensions import Arg, DefaultArg, DefaultNamedArg, NamedArg, VarArg
|
||||
|
||||
|
||||
def foo(var: "MyClass") -> "MyClass":
|
||||
x: "MyClass"
|
||||
|
||||
|
||||
def foo(*, inplace: "bool"):
|
||||
pass
|
||||
|
||||
|
||||
def foo(*args: "str", **kwargs: "int"):
|
||||
pass
|
||||
|
||||
|
||||
x: Tuple["MyClass"]
|
||||
|
||||
x: Callable[["MyClass"], None]
|
||||
|
||||
|
||||
class Foo(NamedTuple):
|
||||
x: "MyClass"
|
||||
|
||||
|
||||
class D(TypedDict):
|
||||
E: TypedDict("E", foo="int", total=False)
|
||||
|
||||
|
||||
class D(TypedDict):
|
||||
E: TypedDict("E", {"foo": "int"})
|
||||
|
||||
|
||||
x: Annotated["str", "metadata"]
|
||||
|
||||
x: Arg("str", "name")
|
||||
|
||||
x: DefaultArg("str", "name")
|
||||
|
||||
x: NamedArg("str", "name")
|
||||
|
||||
x: DefaultNamedArg("str", "name")
|
||||
|
||||
x: DefaultNamedArg("str", name="name")
|
||||
|
||||
x: VarArg("str")
|
||||
|
||||
x: List[List[List["MyClass"]]]
|
||||
|
||||
x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
|
||||
x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
|
||||
x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
|
||||
X: MyCallable("X")
|
||||
|
||||
|
||||
# OK
|
||||
class D(TypedDict):
|
||||
E: TypedDict("E")
|
||||
|
||||
|
||||
x: Annotated[()]
|
||||
|
||||
x: DefaultNamedArg(name="name", quox="str")
|
||||
|
||||
x: DefaultNamedArg(name="name")
|
||||
|
||||
x: NamedTuple("X", [("foo",), ("bar",)])
|
||||
|
||||
x: NamedTuple("X", ["foo", "bar"])
|
||||
|
||||
x: NamedTuple()
|
||||
|
||||
x: Literal["foo", "bar"]
|
||||
|
||||
x = cast(x, "str")
|
||||
|
||||
|
||||
def foo(x, *args, **kwargs):
|
||||
...
|
||||
|
||||
|
||||
def foo(*, inplace):
|
||||
...
|
||||
|
||||
|
||||
x: Annotated[1:2] = ...
|
||||
|
||||
x = TypeVar("x", "str", "int")
|
||||
|
||||
x = cast("str", x)
|
||||
|
||||
X = List["MyClass"]
|
||||
@@ -48,6 +48,10 @@ def f(arg: typing.Optional[int] = None):
|
||||
# Union
|
||||
|
||||
|
||||
def f(arg: Union[None] = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Union[None, int] = None):
|
||||
pass
|
||||
|
||||
@@ -68,6 +72,10 @@ def f(arg: Union = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Union[int] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Union[int, str] = None): # RUF013
|
||||
pass
|
||||
|
||||
@@ -106,10 +114,18 @@ def f(arg: None = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Literal[None] = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Literal[1, 2, None, 3] = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Literal[1] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Literal[1, "foo"] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,9 @@ use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
use ruff_python_whitespace::UniversalNewlines;
|
||||
|
||||
use crate::comments::shebang::ShebangDirective;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::flake8_copyright::rules::missing_copyright_notice;
|
||||
use crate::rules::flake8_executable::helpers::ShebangDirective;
|
||||
use crate::rules::flake8_executable::rules::{
|
||||
shebang_missing, shebang_newline, shebang_not_executable, shebang_python, shebang_whitespace,
|
||||
};
|
||||
@@ -115,7 +115,6 @@ pub(crate) fn check_physical_lines(
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +209,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "R0915") => (RuleGroup::Unspecified, rules::pylint::rules::TooManyStatements),
|
||||
(Pylint, "R1701") => (RuleGroup::Unspecified, rules::pylint::rules::RepeatedIsinstanceCalls),
|
||||
(Pylint, "R1711") => (RuleGroup::Unspecified, rules::pylint::rules::UselessReturn),
|
||||
(Pylint, "R1714") => (RuleGroup::Unspecified, rules::pylint::rules::RepeatedEqualityComparisonTarget),
|
||||
(Pylint, "R1722") => (RuleGroup::Unspecified, rules::pylint::rules::SysExitAlias),
|
||||
(Pylint, "R2004") => (RuleGroup::Unspecified, rules::pylint::rules::MagicValueComparison),
|
||||
(Pylint, "R5501") => (RuleGroup::Unspecified, rules::pylint::rules::CollapsibleElseIf),
|
||||
@@ -603,6 +604,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(PandasVet, "012") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfDotReadTable),
|
||||
(PandasVet, "013") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfDotStack),
|
||||
(PandasVet, "015") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfPdMerge),
|
||||
(PandasVet, "101") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasNuniqueConstantSeriesCheck),
|
||||
(PandasVet, "901") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasDfVariableName),
|
||||
|
||||
// flake8-errmsg
|
||||
@@ -637,6 +639,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Pyi, "033") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::TypeCommentInStub),
|
||||
(Flake8Pyi, "034") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NonSelfReturnType),
|
||||
(Flake8Pyi, "035") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnassignedSpecialVariableInStub),
|
||||
(Flake8Pyi, "036") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::BadExitAnnotation),
|
||||
(Flake8Pyi, "041") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::RedundantNumericUnion),
|
||||
(Flake8Pyi, "042") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::SnakeCaseTypeAlias),
|
||||
(Flake8Pyi, "043") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::TSuffixedTypeAlias),
|
||||
(Flake8Pyi, "044") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::FutureAnnotationsInStub),
|
||||
|
||||
1
crates/ruff/src/comments/mod.rs
Normal file
1
crates/ruff/src/comments/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub(crate) mod shebang;
|
||||
67
crates/ruff/src/comments/shebang.rs
Normal file
67
crates/ruff/src/comments/shebang.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use ruff_python_whitespace::{is_python_whitespace, Cursor};
|
||||
use ruff_text_size::{TextLen, TextSize};
|
||||
|
||||
/// A shebang directive (e.g., `#!/usr/bin/env python3`).
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) struct ShebangDirective<'a> {
|
||||
/// The offset of the directive contents (e.g., `/usr/bin/env python3`) from the start of the
|
||||
/// line.
|
||||
pub(crate) offset: TextSize,
|
||||
/// The contents of the directive (e.g., `"/usr/bin/env python3"`).
|
||||
pub(crate) contents: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> ShebangDirective<'a> {
|
||||
/// Parse a shebang directive from a line, or return `None` if the line does not contain a
|
||||
/// shebang directive.
|
||||
pub(crate) fn try_extract(line: &'a str) -> Option<Self> {
|
||||
let mut cursor = Cursor::new(line);
|
||||
|
||||
// Trim whitespace.
|
||||
cursor.eat_while(is_python_whitespace);
|
||||
|
||||
// Trim the `#!` prefix.
|
||||
if !cursor.eat_char('#') {
|
||||
return None;
|
||||
}
|
||||
if !cursor.eat_char('!') {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
offset: line.text_len() - cursor.text_len(),
|
||||
contents: cursor.chars().as_str(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
|
||||
use super::ShebangDirective;
|
||||
|
||||
#[test]
|
||||
fn shebang_non_match() {
|
||||
let source = "not a match";
|
||||
assert_debug_snapshot!(ShebangDirective::try_extract(source));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shebang_end_of_line() {
|
||||
let source = "print('test') #!/usr/bin/python";
|
||||
assert_debug_snapshot!(ShebangDirective::try_extract(source));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shebang_match() {
|
||||
let source = "#!/usr/bin/env python";
|
||||
assert_debug_snapshot!(ShebangDirective::try_extract(source));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shebang_leading_space() {
|
||||
let source = " #!/usr/bin/env python";
|
||||
assert_debug_snapshot!(ShebangDirective::try_extract(source));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_executable/helpers.rs
|
||||
source: crates/ruff/src/comments/shebang.rs
|
||||
expression: "ShebangDirective::try_extract(source)"
|
||||
---
|
||||
None
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_executable/helpers.rs
|
||||
source: crates/ruff/src/comments/shebang.rs
|
||||
expression: "ShebangDirective::try_extract(source)"
|
||||
---
|
||||
Some(
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_executable/helpers.rs
|
||||
source: crates/ruff/src/comments/shebang.rs
|
||||
expression: "ShebangDirective::try_extract(source)"
|
||||
---
|
||||
Some(
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_executable/helpers.rs
|
||||
source: crates/ruff/src/comments/shebang.rs
|
||||
expression: "ShebangDirective::try_extract(source)"
|
||||
---
|
||||
None
|
||||
@@ -14,6 +14,7 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
mod autofix;
|
||||
mod checkers;
|
||||
mod codes;
|
||||
mod comments;
|
||||
mod cst;
|
||||
pub mod directives;
|
||||
mod doc_lines;
|
||||
|
||||
@@ -51,11 +51,9 @@ pub(crate) fn variable_name_task_id(
|
||||
value: &Expr,
|
||||
) -> Option<Diagnostic> {
|
||||
// If we have more than one target, we can't do anything.
|
||||
if targets.len() != 1 {
|
||||
let [target] = targets else {
|
||||
return None;
|
||||
}
|
||||
|
||||
let target = &targets[0];
|
||||
};
|
||||
let Expr::Name(ast::ExprName { id, .. }) = target else {
|
||||
return None;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_parser::ast::{ArgWithDefault, Expr, Ranged, Stmt};
|
||||
use rustpython_parser::ast::{self, ArgWithDefault, Constant, Expr, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -6,12 +6,14 @@ use ruff_python_ast::cast;
|
||||
use ruff_python_ast::helpers::ReturnStatementVisitor;
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||
use ruff_python_ast::typing::parse_type_annotation;
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
use ruff_python_semantic::{Definition, Member, MemberKind, SemanticModel};
|
||||
use ruff_python_semantic::{Definition, Member, MemberKind};
|
||||
use ruff_python_stdlib::typing::simple_magic_return_type;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::ruff::typing::type_hint_resolves_to_any;
|
||||
|
||||
use super::super::fixes;
|
||||
use super::super::helpers::match_function_def;
|
||||
@@ -432,20 +434,46 @@ fn is_none_returning(body: &[Stmt]) -> bool {
|
||||
|
||||
/// ANN401
|
||||
fn check_dynamically_typed<F>(
|
||||
checker: &Checker,
|
||||
annotation: &Expr,
|
||||
func: F,
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
is_overridden: bool,
|
||||
semantic: &SemanticModel,
|
||||
) where
|
||||
F: FnOnce() -> String,
|
||||
{
|
||||
if !is_overridden && semantic.match_typing_expr(annotation, "Any") {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
AnyType { name: func() },
|
||||
annotation.range(),
|
||||
));
|
||||
};
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
range,
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
}) = annotation
|
||||
{
|
||||
// Quoted annotations
|
||||
if let Ok((parsed_annotation, _)) = parse_type_annotation(string, *range, checker.locator) {
|
||||
if type_hint_resolves_to_any(
|
||||
&parsed_annotation,
|
||||
checker.semantic(),
|
||||
checker.locator,
|
||||
checker.settings.target_version.minor(),
|
||||
) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
AnyType { name: func() },
|
||||
annotation.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if type_hint_resolves_to_any(
|
||||
annotation,
|
||||
checker.semantic(),
|
||||
checker.locator,
|
||||
checker.settings.target_version.minor(),
|
||||
) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
AnyType { name: func() },
|
||||
annotation.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate flake8-annotation checks for a given `Definition`.
|
||||
@@ -500,13 +528,12 @@ pub(crate) fn definition(
|
||||
// ANN401 for dynamically typed arguments
|
||||
if let Some(annotation) = &def.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if checker.enabled(Rule::AnyType) {
|
||||
if checker.enabled(Rule::AnyType) && !is_overridden {
|
||||
check_dynamically_typed(
|
||||
checker,
|
||||
annotation,
|
||||
|| def.arg.to_string(),
|
||||
&mut diagnostics,
|
||||
is_overridden,
|
||||
checker.semantic(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -530,15 +557,9 @@ pub(crate) fn definition(
|
||||
if let Some(expr) = &arg.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.enabled(Rule::AnyType) {
|
||||
if checker.enabled(Rule::AnyType) && !is_overridden {
|
||||
let name = &arg.arg;
|
||||
check_dynamically_typed(
|
||||
expr,
|
||||
|| format!("*{name}"),
|
||||
&mut diagnostics,
|
||||
is_overridden,
|
||||
checker.semantic(),
|
||||
);
|
||||
check_dynamically_typed(checker, expr, || format!("*{name}"), &mut diagnostics);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -562,14 +583,13 @@ pub(crate) fn definition(
|
||||
if let Some(expr) = &arg.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.enabled(Rule::AnyType) {
|
||||
if checker.enabled(Rule::AnyType) && !is_overridden {
|
||||
let name = &arg.arg;
|
||||
check_dynamically_typed(
|
||||
checker,
|
||||
expr,
|
||||
|| format!("**{name}"),
|
||||
&mut diagnostics,
|
||||
is_overridden,
|
||||
checker.semantic(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -629,14 +649,8 @@ pub(crate) fn definition(
|
||||
// ANN201, ANN202, ANN401
|
||||
if let Some(expr) = &returns {
|
||||
has_typed_return = true;
|
||||
if checker.enabled(Rule::AnyType) {
|
||||
check_dynamically_typed(
|
||||
expr,
|
||||
|| name.to_string(),
|
||||
&mut diagnostics,
|
||||
is_overridden,
|
||||
checker.semantic(),
|
||||
);
|
||||
if checker.enabled(Rule::AnyType) && !is_overridden {
|
||||
check_dynamically_typed(checker, expr, || name.to_string(), &mut diagnostics);
|
||||
}
|
||||
} else if !(
|
||||
// Allow omission of return annotation if the function only returns `None`
|
||||
|
||||
@@ -186,4 +186,60 @@ annotation_presence.py:134:13: ANN101 Missing type annotation for `self` in meth
|
||||
135 | pass
|
||||
|
|
||||
|
||||
annotation_presence.py:149:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
||||
|
|
||||
148 | # ANN401
|
||||
149 | def f(a: Any | int) -> None: ...
|
||||
| ^^^^^^^^^ ANN401
|
||||
150 | def f(a: int | Any) -> None: ...
|
||||
151 | def f(a: Union[str, bytes, Any]) -> None: ...
|
||||
|
|
||||
|
||||
annotation_presence.py:150:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
||||
|
|
||||
148 | # ANN401
|
||||
149 | def f(a: Any | int) -> None: ...
|
||||
150 | def f(a: int | Any) -> None: ...
|
||||
| ^^^^^^^^^ ANN401
|
||||
151 | def f(a: Union[str, bytes, Any]) -> None: ...
|
||||
152 | def f(a: Optional[Any]) -> None: ...
|
||||
|
|
||||
|
||||
annotation_presence.py:151:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
||||
|
|
||||
149 | def f(a: Any | int) -> None: ...
|
||||
150 | def f(a: int | Any) -> None: ...
|
||||
151 | def f(a: Union[str, bytes, Any]) -> None: ...
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ ANN401
|
||||
152 | def f(a: Optional[Any]) -> None: ...
|
||||
153 | def f(a: Annotated[Any, ...]) -> None: ...
|
||||
|
|
||||
|
||||
annotation_presence.py:152:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
||||
|
|
||||
150 | def f(a: int | Any) -> None: ...
|
||||
151 | def f(a: Union[str, bytes, Any]) -> None: ...
|
||||
152 | def f(a: Optional[Any]) -> None: ...
|
||||
| ^^^^^^^^^^^^^ ANN401
|
||||
153 | def f(a: Annotated[Any, ...]) -> None: ...
|
||||
154 | def f(a: "Union[str, bytes, Any]") -> None: ...
|
||||
|
|
||||
|
||||
annotation_presence.py:153:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
||||
|
|
||||
151 | def f(a: Union[str, bytes, Any]) -> None: ...
|
||||
152 | def f(a: Optional[Any]) -> None: ...
|
||||
153 | def f(a: Annotated[Any, ...]) -> None: ...
|
||||
| ^^^^^^^^^^^^^^^^^^^ ANN401
|
||||
154 | def f(a: "Union[str, bytes, Any]") -> None: ...
|
||||
|
|
||||
|
||||
annotation_presence.py:154:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
||||
|
|
||||
152 | def f(a: Optional[Any]) -> None: ...
|
||||
153 | def f(a: Annotated[Any, ...]) -> None: ...
|
||||
154 | def f(a: "Union[str, bytes, Any]") -> None: ...
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ ANN401
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use rustpython_parser::ast::{self, Expr, Operator, Ranged};
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::any_over_expr;
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -52,7 +53,7 @@ fn matches_sql_statement(string: &str) -> bool {
|
||||
SQL_REGEX.is_match(string)
|
||||
}
|
||||
|
||||
fn unparse_string_format_expression(checker: &mut Checker, expr: &Expr) -> Option<String> {
|
||||
fn matches_string_format_expression(expr: &Expr, model: &SemanticModel) -> bool {
|
||||
match expr {
|
||||
// "select * from table where val = " + "str" + ...
|
||||
// "select * from table where val = %s" % ...
|
||||
@@ -60,45 +61,37 @@ fn unparse_string_format_expression(checker: &mut Checker, expr: &Expr) -> Optio
|
||||
op: Operator::Add | Operator::Mod,
|
||||
..
|
||||
}) => {
|
||||
let Some(parent) = checker.semantic().expr_parent() else {
|
||||
if any_over_expr(expr, &has_string_literal) {
|
||||
return Some(checker.generator().expr(expr));
|
||||
}
|
||||
return None;
|
||||
};
|
||||
// Only evaluate the full BinOp, not the nested components.
|
||||
let Expr::BinOp(_) = parent else {
|
||||
if model
|
||||
.expr_parent()
|
||||
.map_or(true, |parent| !parent.is_bin_op_expr())
|
||||
{
|
||||
if any_over_expr(expr, &has_string_literal) {
|
||||
return Some(checker.generator().expr(expr));
|
||||
return true;
|
||||
}
|
||||
return None;
|
||||
};
|
||||
None
|
||||
}
|
||||
false
|
||||
}
|
||||
Expr::Call(ast::ExprCall { func, .. }) => {
|
||||
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func.as_ref() else {
|
||||
return None;
|
||||
return false;
|
||||
};
|
||||
// "select * from table where val = {}".format(...)
|
||||
if attr == "format" && string_literal(value).is_some() {
|
||||
return Some(checker.generator().expr(expr));
|
||||
};
|
||||
None
|
||||
attr == "format" && string_literal(value).is_some()
|
||||
}
|
||||
// f"select * from table where val = {val}"
|
||||
Expr::JoinedStr(_) => Some(checker.generator().expr(expr)),
|
||||
_ => None,
|
||||
Expr::JoinedStr(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// S608
|
||||
pub(crate) fn hardcoded_sql_expression(checker: &mut Checker, expr: &Expr) {
|
||||
match unparse_string_format_expression(checker, expr) {
|
||||
Some(string) if matches_sql_statement(&string) => {
|
||||
if matches_string_format_expression(expr, checker.semantic()) {
|
||||
if matches_sql_statement(&checker.generator().expr(expr)) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(HardcodedSQLExpression, expr.range()));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::SimpleCallArgs;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -30,12 +29,7 @@ impl Violation for Jinja2AutoescapeFalse {
|
||||
}
|
||||
|
||||
/// S701
|
||||
pub(crate) fn jinja2_autoescape_false(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
@@ -43,10 +37,13 @@ pub(crate) fn jinja2_autoescape_false(
|
||||
matches!(call_path.as_slice(), ["jinja2", "Environment"])
|
||||
})
|
||||
{
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
|
||||
if let Some(autoescape_arg) = call_args.keyword_argument("autoescape") {
|
||||
match autoescape_arg {
|
||||
if let Some(keyword) = keywords.iter().find(|keyword| {
|
||||
keyword
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |arg| arg.as_str() == "autoescape")
|
||||
}) {
|
||||
match &keyword.value {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Bool(true),
|
||||
..
|
||||
@@ -56,14 +53,14 @@ pub(crate) fn jinja2_autoescape_false(
|
||||
if id != "select_autoescape" {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
Jinja2AutoescapeFalse { value: true },
|
||||
autoescape_arg.range(),
|
||||
keyword.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => checker.diagnostics.push(Diagnostic::new(
|
||||
Jinja2AutoescapeFalse { value: true },
|
||||
autoescape_arg.range(),
|
||||
keyword.range(),
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2,7 +2,6 @@ use rustpython_parser::ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::SimpleCallArgs;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -20,7 +19,6 @@ impl Violation for LoggingConfigInsecureListen {
|
||||
pub(crate) fn logging_config_insecure_listen(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
if checker
|
||||
@@ -30,12 +28,17 @@ pub(crate) fn logging_config_insecure_listen(
|
||||
matches!(call_path.as_slice(), ["logging", "config", "listen"])
|
||||
})
|
||||
{
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
|
||||
if call_args.keyword_argument("verify").is_none() {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(LoggingConfigInsecureListen, func.range()));
|
||||
if keywords.iter().any(|keyword| {
|
||||
keyword
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |arg| arg.as_str() == "verify")
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(LoggingConfigInsecureListen, func.range()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,34 @@ use rustpython_parser::ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::{is_const_false, SimpleCallArgs};
|
||||
use ruff_python_ast::helpers::is_const_false;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for HTTPS requests that disable SSL certificate checks.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// If SSL certificates are not verified, an attacker could perform a "man in
|
||||
/// the middle" attack by intercepting and modifying traffic between the client
|
||||
/// and server.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import requests
|
||||
///
|
||||
/// requests.get("https://www.example.com", verify=False)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import requests
|
||||
///
|
||||
/// requests.get("https://www.example.com") # By default, `verify=True`.
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Common Weakness Enumeration: CWE-295](https://cwe.mitre.org/data/definitions/295.html)
|
||||
#[violation]
|
||||
pub struct RequestWithNoCertValidation {
|
||||
string: String,
|
||||
@@ -25,7 +49,6 @@ impl Violation for RequestWithNoCertValidation {
|
||||
pub(crate) fn request_with_no_cert_validation(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
if let Some(target) = checker
|
||||
@@ -40,14 +63,18 @@ pub(crate) fn request_with_no_cert_validation(
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
if let Some(verify_arg) = call_args.keyword_argument("verify") {
|
||||
if is_const_false(verify_arg) {
|
||||
if let Some(keyword) = keywords.iter().find(|keyword| {
|
||||
keyword
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |arg| arg.as_str() == "verify")
|
||||
}) {
|
||||
if is_const_false(&keyword.value) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
RequestWithNoCertValidation {
|
||||
string: target.to_string(),
|
||||
},
|
||||
verify_arg.range(),
|
||||
keyword.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use rustpython_parser::ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::{is_const_none, SimpleCallArgs};
|
||||
use ruff_python_ast::helpers::is_const_none;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -49,12 +49,7 @@ impl Violation for RequestWithoutTimeout {
|
||||
}
|
||||
|
||||
/// S113
|
||||
pub(crate) fn request_without_timeout(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
pub(crate) fn request_without_timeout(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
@@ -68,12 +63,16 @@ pub(crate) fn request_without_timeout(
|
||||
)
|
||||
})
|
||||
{
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
if let Some(timeout) = call_args.keyword_argument("timeout") {
|
||||
if is_const_none(timeout) {
|
||||
if let Some(keyword) = keywords.iter().find(|keyword| {
|
||||
keyword
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |arg| arg.as_str() == "timeout")
|
||||
}) {
|
||||
if is_const_none(&keyword.value) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
RequestWithoutTimeout { implicit: false },
|
||||
timeout.range(),
|
||||
keyword.range(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -3,10 +3,34 @@ use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::SimpleCallArgs;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of SNMPv1 or SNMPv2.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The SNMPv1 and SNMPv2 protocols are considered insecure as they do
|
||||
/// not support encryption. Instead, prefer SNMPv3, which supports
|
||||
/// encryption.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from pysnmp.hlapi import CommunityData
|
||||
///
|
||||
/// CommunityData("public", mpModel=0)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pysnmp.hlapi import CommunityData
|
||||
///
|
||||
/// CommunityData("public", mpModel=2)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Cybersecurity and Infrastructure Security Agency (CISA): Alert TA17-156A](https://www.cisa.gov/news-events/alerts/2017/06/05/reducing-risk-snmp-abuse)
|
||||
/// - [Common Weakness Enumeration: CWE-319](https://cwe.mitre.org/data/definitions/319.html)
|
||||
#[violation]
|
||||
pub struct SnmpInsecureVersion;
|
||||
|
||||
@@ -18,12 +42,7 @@ impl Violation for SnmpInsecureVersion {
|
||||
}
|
||||
|
||||
/// S508
|
||||
pub(crate) fn snmp_insecure_version(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
pub(crate) fn snmp_insecure_version(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
@@ -31,17 +50,21 @@ pub(crate) fn snmp_insecure_version(
|
||||
matches!(call_path.as_slice(), ["pysnmp", "hlapi", "CommunityData"])
|
||||
})
|
||||
{
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
if let Some(mp_model_arg) = call_args.keyword_argument("mpModel") {
|
||||
if let Some(keyword) = keywords.iter().find(|keyword| {
|
||||
keyword
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |arg| arg.as_str() == "mpModel")
|
||||
}) {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(value),
|
||||
..
|
||||
}) = &mp_model_arg
|
||||
}) = &keyword.value
|
||||
{
|
||||
if value.is_zero() || value.is_one() {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SnmpInsecureVersion, mp_model_arg.range()));
|
||||
.push(Diagnostic::new(SnmpInsecureVersion, keyword.range()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,29 @@ use ruff_python_ast::helpers::SimpleCallArgs;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of the SNMPv3 protocol without encryption.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Unencrypted SNMPv3 communication can be intercepted and read by
|
||||
/// unauthorized parties. Instead, enable encryption when using SNMPv3.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from pysnmp.hlapi import UsmUserData
|
||||
///
|
||||
/// UsmUserData("user")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pysnmp.hlapi import UsmUserData
|
||||
///
|
||||
/// UsmUserData("user", "authkey", "privkey")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Common Weakness Enumeration: CWE-319](https://cwe.mitre.org/data/definitions/319.html)
|
||||
#[violation]
|
||||
pub struct SnmpWeakCryptography;
|
||||
|
||||
|
||||
@@ -55,16 +55,14 @@ pub(crate) fn try_except_continue(
|
||||
checker: &mut Checker,
|
||||
except_handler: &ExceptHandler,
|
||||
type_: Option<&Expr>,
|
||||
_name: Option<&str>,
|
||||
body: &[Stmt],
|
||||
check_typed_exception: bool,
|
||||
) {
|
||||
if body.len() == 1
|
||||
&& body[0].is_continue_stmt()
|
||||
&& (check_typed_exception || is_untyped_exception(type_, checker.semantic()))
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(TryExceptContinue, except_handler.range()));
|
||||
if matches!(body, [Stmt::Continue(_)]) {
|
||||
if check_typed_exception || is_untyped_exception(type_, checker.semantic()) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(TryExceptContinue, except_handler.range()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,16 +51,14 @@ pub(crate) fn try_except_pass(
|
||||
checker: &mut Checker,
|
||||
except_handler: &ExceptHandler,
|
||||
type_: Option<&Expr>,
|
||||
_name: Option<&str>,
|
||||
body: &[Stmt],
|
||||
check_typed_exception: bool,
|
||||
) {
|
||||
if body.len() == 1
|
||||
&& body[0].is_pass_stmt()
|
||||
&& (check_typed_exception || is_untyped_exception(type_, checker.semantic()))
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(TryExceptPass, except_handler.range()));
|
||||
if matches!(body, [Stmt::Pass(_)]) {
|
||||
if check_typed_exception || is_untyped_exception(type_, checker.semantic()) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(TryExceptPass, except_handler.range()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,35 @@ use ruff_python_ast::helpers::SimpleCallArgs;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of the `yaml.load` function.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Running the `yaml.load` function over untrusted YAML files is insecure, as
|
||||
/// `yaml.load` allows for the creation of arbitrary Python objects, which can
|
||||
/// then be used to execute arbitrary code.
|
||||
///
|
||||
/// Instead, consider using `yaml.safe_load`, which allows for the creation of
|
||||
/// simple Python objects like integers and lists, but prohibits the creation of
|
||||
/// more complex objects like functions and classes.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import yaml
|
||||
///
|
||||
/// yaml.load(untrusted_yaml)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import yaml
|
||||
///
|
||||
/// yaml.safe_load(untrusted_yaml)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PyYAML documentation: Loading YAML](https://pyyaml.org/wiki/PyYAMLDocumentation)
|
||||
/// - [Common Weakness Enumeration: CWE-20](https://cwe.mitre.org/data/definitions/20.html)
|
||||
#[violation]
|
||||
pub struct UnsafeYAMLLoad {
|
||||
pub loader: Option<String>,
|
||||
|
||||
@@ -11,11 +11,11 @@ S113.py:3:1: S113 Probable use of requests call without timeout
|
||||
5 | requests.get('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
S113.py:4:43: S113 Probable use of requests call with timeout set to `None`
|
||||
S113.py:4:35: S113 Probable use of requests call with timeout set to `None`
|
||||
|
|
||||
3 | requests.get('https://gmail.com')
|
||||
4 | requests.get('https://gmail.com', timeout=None)
|
||||
| ^^^^ S113
|
||||
| ^^^^^^^^^^^^ S113
|
||||
5 | requests.get('https://gmail.com', timeout=5)
|
||||
6 | requests.post('https://gmail.com')
|
||||
|
|
||||
@@ -30,12 +30,12 @@ S113.py:6:1: S113 Probable use of requests call without timeout
|
||||
8 | requests.post('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
S113.py:7:44: S113 Probable use of requests call with timeout set to `None`
|
||||
S113.py:7:36: S113 Probable use of requests call with timeout set to `None`
|
||||
|
|
||||
5 | requests.get('https://gmail.com', timeout=5)
|
||||
6 | requests.post('https://gmail.com')
|
||||
7 | requests.post('https://gmail.com', timeout=None)
|
||||
| ^^^^ S113
|
||||
| ^^^^^^^^^^^^ S113
|
||||
8 | requests.post('https://gmail.com', timeout=5)
|
||||
9 | requests.put('https://gmail.com')
|
||||
|
|
||||
@@ -50,12 +50,12 @@ S113.py:9:1: S113 Probable use of requests call without timeout
|
||||
11 | requests.put('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
S113.py:10:43: S113 Probable use of requests call with timeout set to `None`
|
||||
S113.py:10:35: S113 Probable use of requests call with timeout set to `None`
|
||||
|
|
||||
8 | requests.post('https://gmail.com', timeout=5)
|
||||
9 | requests.put('https://gmail.com')
|
||||
10 | requests.put('https://gmail.com', timeout=None)
|
||||
| ^^^^ S113
|
||||
| ^^^^^^^^^^^^ S113
|
||||
11 | requests.put('https://gmail.com', timeout=5)
|
||||
12 | requests.delete('https://gmail.com')
|
||||
|
|
||||
@@ -70,12 +70,12 @@ S113.py:12:1: S113 Probable use of requests call without timeout
|
||||
14 | requests.delete('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
S113.py:13:46: S113 Probable use of requests call with timeout set to `None`
|
||||
S113.py:13:38: S113 Probable use of requests call with timeout set to `None`
|
||||
|
|
||||
11 | requests.put('https://gmail.com', timeout=5)
|
||||
12 | requests.delete('https://gmail.com')
|
||||
13 | requests.delete('https://gmail.com', timeout=None)
|
||||
| ^^^^ S113
|
||||
| ^^^^^^^^^^^^ S113
|
||||
14 | requests.delete('https://gmail.com', timeout=5)
|
||||
15 | requests.patch('https://gmail.com')
|
||||
|
|
||||
@@ -90,12 +90,12 @@ S113.py:15:1: S113 Probable use of requests call without timeout
|
||||
17 | requests.patch('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
S113.py:16:45: S113 Probable use of requests call with timeout set to `None`
|
||||
S113.py:16:37: S113 Probable use of requests call with timeout set to `None`
|
||||
|
|
||||
14 | requests.delete('https://gmail.com', timeout=5)
|
||||
15 | requests.patch('https://gmail.com')
|
||||
16 | requests.patch('https://gmail.com', timeout=None)
|
||||
| ^^^^ S113
|
||||
| ^^^^^^^^^^^^ S113
|
||||
17 | requests.patch('https://gmail.com', timeout=5)
|
||||
18 | requests.options('https://gmail.com')
|
||||
|
|
||||
@@ -110,12 +110,12 @@ S113.py:18:1: S113 Probable use of requests call without timeout
|
||||
20 | requests.options('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
S113.py:19:47: S113 Probable use of requests call with timeout set to `None`
|
||||
S113.py:19:39: S113 Probable use of requests call with timeout set to `None`
|
||||
|
|
||||
17 | requests.patch('https://gmail.com', timeout=5)
|
||||
18 | requests.options('https://gmail.com')
|
||||
19 | requests.options('https://gmail.com', timeout=None)
|
||||
| ^^^^ S113
|
||||
| ^^^^^^^^^^^^ S113
|
||||
20 | requests.options('https://gmail.com', timeout=5)
|
||||
21 | requests.head('https://gmail.com')
|
||||
|
|
||||
@@ -130,12 +130,12 @@ S113.py:21:1: S113 Probable use of requests call without timeout
|
||||
23 | requests.head('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
S113.py:22:44: S113 Probable use of requests call with timeout set to `None`
|
||||
S113.py:22:36: S113 Probable use of requests call with timeout set to `None`
|
||||
|
|
||||
20 | requests.options('https://gmail.com', timeout=5)
|
||||
21 | requests.head('https://gmail.com')
|
||||
22 | requests.head('https://gmail.com', timeout=None)
|
||||
| ^^^^ S113
|
||||
| ^^^^^^^^^^^^ S113
|
||||
23 | requests.head('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
|
||||
@@ -1,180 +1,180 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
S501.py:5:54: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:5:47: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
4 | requests.get('https://gmail.com', timeout=30, verify=True)
|
||||
5 | requests.get('https://gmail.com', timeout=30, verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
6 | requests.post('https://gmail.com', timeout=30, verify=True)
|
||||
7 | requests.post('https://gmail.com', timeout=30, verify=False)
|
||||
|
|
||||
|
||||
S501.py:7:55: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:7:48: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
5 | requests.get('https://gmail.com', timeout=30, verify=False)
|
||||
6 | requests.post('https://gmail.com', timeout=30, verify=True)
|
||||
7 | requests.post('https://gmail.com', timeout=30, verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
8 | requests.put('https://gmail.com', timeout=30, verify=True)
|
||||
9 | requests.put('https://gmail.com', timeout=30, verify=False)
|
||||
|
|
||||
|
||||
S501.py:9:54: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:9:47: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
7 | requests.post('https://gmail.com', timeout=30, verify=False)
|
||||
8 | requests.put('https://gmail.com', timeout=30, verify=True)
|
||||
9 | requests.put('https://gmail.com', timeout=30, verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
10 | requests.delete('https://gmail.com', timeout=30, verify=True)
|
||||
11 | requests.delete('https://gmail.com', timeout=30, verify=False)
|
||||
|
|
||||
|
||||
S501.py:11:57: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:11:50: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
9 | requests.put('https://gmail.com', timeout=30, verify=False)
|
||||
10 | requests.delete('https://gmail.com', timeout=30, verify=True)
|
||||
11 | requests.delete('https://gmail.com', timeout=30, verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
12 | requests.patch('https://gmail.com', timeout=30, verify=True)
|
||||
13 | requests.patch('https://gmail.com', timeout=30, verify=False)
|
||||
|
|
||||
|
||||
S501.py:13:56: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:13:49: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
11 | requests.delete('https://gmail.com', timeout=30, verify=False)
|
||||
12 | requests.patch('https://gmail.com', timeout=30, verify=True)
|
||||
13 | requests.patch('https://gmail.com', timeout=30, verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
14 | requests.options('https://gmail.com', timeout=30, verify=True)
|
||||
15 | requests.options('https://gmail.com', timeout=30, verify=False)
|
||||
|
|
||||
|
||||
S501.py:15:58: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:15:51: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
13 | requests.patch('https://gmail.com', timeout=30, verify=False)
|
||||
14 | requests.options('https://gmail.com', timeout=30, verify=True)
|
||||
15 | requests.options('https://gmail.com', timeout=30, verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
16 | requests.head('https://gmail.com', timeout=30, verify=True)
|
||||
17 | requests.head('https://gmail.com', timeout=30, verify=False)
|
||||
|
|
||||
|
||||
S501.py:17:55: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:17:48: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
15 | requests.options('https://gmail.com', timeout=30, verify=False)
|
||||
16 | requests.head('https://gmail.com', timeout=30, verify=True)
|
||||
17 | requests.head('https://gmail.com', timeout=30, verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
18 |
|
||||
19 | httpx.request('GET', 'https://gmail.com', verify=True)
|
||||
|
|
||||
|
||||
S501.py:20:50: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:20:43: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
19 | httpx.request('GET', 'https://gmail.com', verify=True)
|
||||
20 | httpx.request('GET', 'https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
21 | httpx.get('https://gmail.com', verify=True)
|
||||
22 | httpx.get('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:22:39: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:22:32: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
20 | httpx.request('GET', 'https://gmail.com', verify=False)
|
||||
21 | httpx.get('https://gmail.com', verify=True)
|
||||
22 | httpx.get('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
23 | httpx.options('https://gmail.com', verify=True)
|
||||
24 | httpx.options('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:24:43: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:24:36: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
22 | httpx.get('https://gmail.com', verify=False)
|
||||
23 | httpx.options('https://gmail.com', verify=True)
|
||||
24 | httpx.options('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
25 | httpx.head('https://gmail.com', verify=True)
|
||||
26 | httpx.head('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:26:40: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:26:33: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
24 | httpx.options('https://gmail.com', verify=False)
|
||||
25 | httpx.head('https://gmail.com', verify=True)
|
||||
26 | httpx.head('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
27 | httpx.post('https://gmail.com', verify=True)
|
||||
28 | httpx.post('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:28:40: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:28:33: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
26 | httpx.head('https://gmail.com', verify=False)
|
||||
27 | httpx.post('https://gmail.com', verify=True)
|
||||
28 | httpx.post('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
29 | httpx.put('https://gmail.com', verify=True)
|
||||
30 | httpx.put('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:30:39: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:30:32: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
28 | httpx.post('https://gmail.com', verify=False)
|
||||
29 | httpx.put('https://gmail.com', verify=True)
|
||||
30 | httpx.put('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
31 | httpx.patch('https://gmail.com', verify=True)
|
||||
32 | httpx.patch('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:32:41: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:32:34: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
30 | httpx.put('https://gmail.com', verify=False)
|
||||
31 | httpx.patch('https://gmail.com', verify=True)
|
||||
32 | httpx.patch('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
33 | httpx.delete('https://gmail.com', verify=True)
|
||||
34 | httpx.delete('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:34:42: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:34:35: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
32 | httpx.patch('https://gmail.com', verify=False)
|
||||
33 | httpx.delete('https://gmail.com', verify=True)
|
||||
34 | httpx.delete('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
35 | httpx.stream('https://gmail.com', verify=True)
|
||||
36 | httpx.stream('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:36:42: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:36:35: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
34 | httpx.delete('https://gmail.com', verify=False)
|
||||
35 | httpx.stream('https://gmail.com', verify=True)
|
||||
36 | httpx.stream('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
37 | httpx.Client()
|
||||
38 | httpx.Client(verify=False)
|
||||
|
|
||||
|
||||
S501.py:38:21: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:38:14: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
36 | httpx.stream('https://gmail.com', verify=False)
|
||||
37 | httpx.Client()
|
||||
38 | httpx.Client(verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
39 | httpx.AsyncClient()
|
||||
40 | httpx.AsyncClient(verify=False)
|
||||
|
|
||||
|
||||
S501.py:40:26: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:40:19: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
38 | httpx.Client(verify=False)
|
||||
39 | httpx.AsyncClient()
|
||||
40 | httpx.AsyncClient(verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
S508.py:3:33: S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
S508.py:3:25: S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
|
|
||||
1 | from pysnmp.hlapi import CommunityData
|
||||
2 |
|
||||
3 | CommunityData("public", mpModel=0) # S508
|
||||
| ^ S508
|
||||
| ^^^^^^^^^ S508
|
||||
4 | CommunityData("public", mpModel=1) # S508
|
||||
|
|
||||
|
||||
S508.py:4:33: S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
S508.py:4:25: S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
|
|
||||
3 | CommunityData("public", mpModel=0) # S508
|
||||
4 | CommunityData("public", mpModel=1) # S508
|
||||
| ^ S508
|
||||
| ^^^^^^^^^ S508
|
||||
5 |
|
||||
6 | CommunityData("public", mpModel=2) # OK
|
||||
|
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
S701.py:9:68: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
S701.py:9:57: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
|
|
||||
7 | templateEnv = jinja2.Environment(autoescape=True,
|
||||
8 | loader=templateLoader )
|
||||
9 | Environment(loader=templateLoader, load=templateLoader, autoescape=something) # S701
|
||||
| ^^^^^^^^^ S701
|
||||
| ^^^^^^^^^^^^^^^^^^^^ S701
|
||||
10 | templateEnv = jinja2.Environment(autoescape=False, loader=templateLoader ) # S701
|
||||
11 | Environment(loader=templateLoader,
|
||||
|
|
||||
|
||||
S701.py:10:45: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
S701.py:10:34: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
|
|
||||
8 | loader=templateLoader )
|
||||
9 | Environment(loader=templateLoader, load=templateLoader, autoescape=something) # S701
|
||||
10 | templateEnv = jinja2.Environment(autoescape=False, loader=templateLoader ) # S701
|
||||
| ^^^^^ S701
|
||||
| ^^^^^^^^^^^^^^^^ S701
|
||||
11 | Environment(loader=templateLoader,
|
||||
12 | load=templateLoader,
|
||||
|
|
||||
|
||||
S701.py:13:24: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
S701.py:13:13: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
|
|
||||
11 | Environment(loader=templateLoader,
|
||||
12 | load=templateLoader,
|
||||
13 | autoescape=False) # S701
|
||||
| ^^^^^ S701
|
||||
| ^^^^^^^^^^^^^^^^ S701
|
||||
14 |
|
||||
15 | Environment(loader=templateLoader, # S701
|
||||
|
|
||||
@@ -40,12 +40,12 @@ S701.py:15:1: S701 By default, jinja2 sets `autoescape` to `False`. Consider usi
|
||||
16 | load=templateLoader)
|
||||
|
|
||||
|
||||
S701.py:29:47: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
S701.py:29:36: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
|
|
||||
27 | def fake_func():
|
||||
28 | return 'foobar'
|
||||
29 | Environment(loader=templateLoader, autoescape=fake_func()) # S701
|
||||
| ^^^^^^^^^^^ S701
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ S701
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -47,12 +47,12 @@ impl Violation for AssignmentToOsEnviron {
|
||||
format!("Assigning to `os.environ` doesn't clear the environment")
|
||||
}
|
||||
}
|
||||
|
||||
/// B003
|
||||
pub(crate) fn assignment_to_os_environ(checker: &mut Checker, targets: &[Expr]) {
|
||||
if targets.len() != 1 {
|
||||
let [target] = targets else {
|
||||
return;
|
||||
}
|
||||
let target = &targets[0];
|
||||
};
|
||||
let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = target else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Identifier, Ranged};
|
||||
use rustpython_parser::ast::{self, Constant, Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -47,15 +46,6 @@ impl AlwaysAutofixableViolation for GetAttrWithConstant {
|
||||
"Replace `getattr` with attribute access".to_string()
|
||||
}
|
||||
}
|
||||
fn attribute(value: &Expr, attr: &str) -> Expr {
|
||||
ast::ExprAttribute {
|
||||
value: Box::new(value.clone()),
|
||||
attr: Identifier::new(attr.to_string(), TextRange::default()),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// B009
|
||||
pub(crate) fn getattr_with_constant(
|
||||
@@ -83,14 +73,14 @@ pub(crate) fn getattr_with_constant(
|
||||
if !is_identifier(value) {
|
||||
return;
|
||||
}
|
||||
if is_mangled_private(value.as_str()) {
|
||||
if is_mangled_private(value) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(GetAttrWithConstant, expr.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
checker.generator().expr(&attribute(obj, value)),
|
||||
format!("{}.{}", checker.locator.slice(obj.range()), value),
|
||||
expr.range(),
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ use rustpython_parser::ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::SimpleCallArgs;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -38,12 +37,7 @@ impl Violation for NoExplicitStacklevel {
|
||||
}
|
||||
|
||||
/// B028
|
||||
pub(crate) fn no_explicit_stacklevel(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
pub(crate) fn no_explicit_stacklevel(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
@@ -54,10 +48,12 @@ pub(crate) fn no_explicit_stacklevel(
|
||||
return;
|
||||
}
|
||||
|
||||
if SimpleCallArgs::new(args, keywords)
|
||||
.keyword_argument("stacklevel")
|
||||
.is_some()
|
||||
{
|
||||
if keywords.iter().any(|keyword| {
|
||||
keyword
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |arg| arg.as_str() == "stacklevel")
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use rustpython_parser::ast::{self, Expr, Stmt};
|
||||
use rustpython_parser::ast;
|
||||
use rustpython_parser::ast::Stmt;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::RaiseStatementVisitor;
|
||||
use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||
use ruff_python_stdlib::str::is_cased_lowercase;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -60,7 +60,11 @@ impl Violation for RaiseWithoutFromInsideExcept {
|
||||
}
|
||||
|
||||
/// B904
|
||||
pub(crate) fn raise_without_from_inside_except(checker: &mut Checker, body: &[Stmt]) {
|
||||
pub(crate) fn raise_without_from_inside_except(
|
||||
checker: &mut Checker,
|
||||
name: Option<&str>,
|
||||
body: &[Stmt],
|
||||
) {
|
||||
let raises = {
|
||||
let mut visitor = RaiseStatementVisitor::default();
|
||||
visitor.visit_body(body);
|
||||
@@ -70,14 +74,28 @@ pub(crate) fn raise_without_from_inside_except(checker: &mut Checker, body: &[St
|
||||
for (range, exc, cause) in raises {
|
||||
if cause.is_none() {
|
||||
if let Some(exc) = exc {
|
||||
match exc {
|
||||
Expr::Name(ast::ExprName { id, .. }) if is_cased_lowercase(id) => {}
|
||||
_ => {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(RaiseWithoutFromInsideExcept, range));
|
||||
// If the raised object is bound to the same name, it's a re-raise, which is
|
||||
// allowed (but may be flagged by other diagnostics).
|
||||
//
|
||||
// For example:
|
||||
// ```python
|
||||
// try:
|
||||
// ...
|
||||
// except ValueError as exc:
|
||||
// raise exc
|
||||
// ```
|
||||
if let Some(name) = name {
|
||||
if exc
|
||||
.as_name_expr()
|
||||
.map_or(false, |ast::ExprName { id, .. }| name == id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(RaiseWithoutFromInsideExcept, range));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,14 +55,11 @@ pub(crate) fn strip_with_multi_characters(
|
||||
if !matches!(attr.as_str(), "strip" | "lstrip" | "rstrip") {
|
||||
return;
|
||||
}
|
||||
if args.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
let [Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
}) = &args[0]
|
||||
})] = args
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use rustpython_parser::ast::{self, Constant, Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `hasattr` to test if an object is callable (e.g.,
|
||||
@@ -35,13 +36,19 @@ use crate::checkers::ast::Checker;
|
||||
pub struct UnreliableCallableCheck;
|
||||
|
||||
impl Violation for UnreliableCallableCheck {
|
||||
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"Using `hasattr(x, '__call__')` to test if x is callable is unreliable. Use \
|
||||
"Using `hasattr(x, \"__call__\")` to test if x is callable is unreliable. Use \
|
||||
`callable(x)` for consistent results."
|
||||
)
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
Some(format!("Replace with `callable()`"))
|
||||
}
|
||||
}
|
||||
|
||||
/// B004
|
||||
@@ -54,23 +61,33 @@ pub(crate) fn unreliable_callable_check(
|
||||
let Expr::Name(ast::ExprName { id, .. }) = func else {
|
||||
return;
|
||||
};
|
||||
if id != "getattr" && id != "hasattr" {
|
||||
if !matches!(id.as_str(), "hasattr" | "getattr") {
|
||||
return;
|
||||
}
|
||||
if args.len() < 2 {
|
||||
let [obj, attr, ..] = args else {
|
||||
return;
|
||||
};
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(s),
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
}) = &args[1]
|
||||
}) = attr
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if s != "__call__" {
|
||||
if string != "__call__" {
|
||||
return;
|
||||
}
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(UnreliableCallableCheck, expr.range()));
|
||||
|
||||
let mut diagnostic = Diagnostic::new(UnreliableCallableCheck, expr.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if id == "hasattr" {
|
||||
if checker.semantic().is_builtin("callable") {
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
|
||||
format!("callable({})", checker.locator.slice(obj.range())),
|
||||
expr.range(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -154,20 +154,23 @@ pub(crate) fn unused_loop_control_variable(checker: &mut Checker, target: &Expr,
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
if let Some(rename) = rename {
|
||||
if certainty.into() && checker.patch(diagnostic.kind.rule()) {
|
||||
// Avoid fixing if the variable, or any future bindings to the variable, are
|
||||
// used _after_ the loop.
|
||||
let scope = checker.semantic().scope();
|
||||
if scope
|
||||
.get_all(name)
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
.all(|binding| !binding.is_used())
|
||||
{
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
rename,
|
||||
expr.range(),
|
||||
)));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(rename) = rename {
|
||||
if certainty.into() {
|
||||
// Avoid fixing if the variable, or any future bindings to the variable, are
|
||||
// used _after_ the loop.
|
||||
let scope = checker.semantic().scope();
|
||||
if scope
|
||||
.get_all(name)
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
.filter(|binding| binding.range.start() >= expr.range().start())
|
||||
.all(|binding| !binding.is_used())
|
||||
{
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
rename,
|
||||
expr.range(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B004.py:3:8: B004 Using `hasattr(x, '__call__')` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
||||
B004.py:3:8: B004 [*] Using `hasattr(x, "__call__")` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
||||
|
|
||||
1 | def this_is_a_bug():
|
||||
2 | o = object()
|
||||
@@ -10,8 +10,18 @@ B004.py:3:8: B004 Using `hasattr(x, '__call__')` to test if x is callable is unr
|
||||
4 | print("Ooh, callable! Or is it?")
|
||||
5 | if getattr(o, "__call__", False):
|
||||
|
|
||||
= help: Replace with `callable()`
|
||||
|
||||
B004.py:5:8: B004 Using `hasattr(x, '__call__')` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
||||
ℹ Fix
|
||||
1 1 | def this_is_a_bug():
|
||||
2 2 | o = object()
|
||||
3 |- if hasattr(o, "__call__"):
|
||||
3 |+ if callable(o):
|
||||
4 4 | print("Ooh, callable! Or is it?")
|
||||
5 5 | if getattr(o, "__call__", False):
|
||||
6 6 | print("Ooh, callable! Or is it?")
|
||||
|
||||
B004.py:5:8: B004 Using `hasattr(x, "__call__")` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
||||
|
|
||||
3 | if hasattr(o, "__call__"):
|
||||
4 | print("Ooh, callable! Or is it?")
|
||||
@@ -19,5 +29,6 @@ B004.py:5:8: B004 Using `hasattr(x, '__call__')` to test if x is callable is unr
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B004
|
||||
6 | print("Ooh, callable! Or is it?")
|
||||
|
|
||||
= help: Replace with `callable()`
|
||||
|
||||
|
||||
|
||||
@@ -72,42 +72,42 @@ B006_B008.py:100:33: B006 Do not use mutable data structures for argument defaul
|
||||
101 | ...
|
||||
|
|
||||
|
||||
B006_B008.py:218:20: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:221:20: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
216 | # B006 and B008
|
||||
217 | # We should handle arbitrary nesting of these B008.
|
||||
218 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
219 | # B006 and B008
|
||||
220 | # We should handle arbitrary nesting of these B008.
|
||||
221 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||
219 | pass
|
||||
222 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:251:27: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:254:27: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
250 | def mutable_annotations(
|
||||
251 | a: list[int] | None = [],
|
||||
253 | def mutable_annotations(
|
||||
254 | a: list[int] | None = [],
|
||||
| ^^ B006
|
||||
252 | b: Optional[Dict[int, int]] = {},
|
||||
253 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
255 | b: Optional[Dict[int, int]] = {},
|
||||
256 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
|
|
||||
|
||||
B006_B008.py:252:35: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:255:35: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
250 | def mutable_annotations(
|
||||
251 | a: list[int] | None = [],
|
||||
252 | b: Optional[Dict[int, int]] = {},
|
||||
253 | def mutable_annotations(
|
||||
254 | a: list[int] | None = [],
|
||||
255 | b: Optional[Dict[int, int]] = {},
|
||||
| ^^ B006
|
||||
253 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
254 | ):
|
||||
256 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
257 | ):
|
||||
|
|
||||
|
||||
B006_B008.py:253:62: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:256:62: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
251 | a: list[int] | None = [],
|
||||
252 | b: Optional[Dict[int, int]] = {},
|
||||
253 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
254 | a: list[int] | None = [],
|
||||
255 | b: Optional[Dict[int, int]] = {},
|
||||
256 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^ B006
|
||||
254 | ):
|
||||
255 | pass
|
||||
257 | ):
|
||||
258 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -46,38 +46,38 @@ B006_B008.py:120:30: B008 Do not perform function call in argument defaults
|
||||
121 | ...
|
||||
|
|
||||
|
||||
B006_B008.py:218:31: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
B006_B008.py:221:31: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
|
|
||||
216 | # B006 and B008
|
||||
217 | # We should handle arbitrary nesting of these B008.
|
||||
218 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
219 | # B006 and B008
|
||||
220 | # We should handle arbitrary nesting of these B008.
|
||||
221 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
| ^^^^^^^^^^^^^^^^^ B008
|
||||
219 | pass
|
||||
222 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:224:22: B008 Do not perform function call `map` in argument defaults
|
||||
B006_B008.py:227:22: B008 Do not perform function call `map` in argument defaults
|
||||
|
|
||||
222 | # Don't flag nested B006 since we can't guarantee that
|
||||
223 | # it isn't made mutable by the outer operation.
|
||||
224 | def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
|
||||
225 | # Don't flag nested B006 since we can't guarantee that
|
||||
226 | # it isn't made mutable by the outer operation.
|
||||
227 | def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B008
|
||||
225 | pass
|
||||
228 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:229:19: B008 Do not perform function call `random.randint` in argument defaults
|
||||
B006_B008.py:232:19: B008 Do not perform function call `random.randint` in argument defaults
|
||||
|
|
||||
228 | # B008-ception.
|
||||
229 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
231 | # B008-ception.
|
||||
232 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B008
|
||||
230 | pass
|
||||
233 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:229:37: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
B006_B008.py:232:37: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
|
|
||||
228 | # B008-ception.
|
||||
229 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
231 | # B008-ception.
|
||||
232 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
| ^^^^^^^^^^^^^^^^^ B008
|
||||
230 | pass
|
||||
233 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -27,33 +27,43 @@ B904.py:16:5: B904 Within an `except` clause, raise exceptions with `raise ... f
|
||||
15 | assert err
|
||||
16 | raise Exception("No cause here...")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B904
|
||||
17 | except BaseException as base_err:
|
||||
18 | # Might use this instead of bare raise with the `.with_traceback()` method
|
||||
17 | except BaseException as err:
|
||||
18 | raise err
|
||||
|
|
||||
|
||||
B904.py:62:9: B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
|
||||
B904.py:20:5: B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
|
||||
|
|
||||
60 | except Exception as e:
|
||||
61 | if ...:
|
||||
62 | raise RuntimeError("boom!")
|
||||
18 | raise err
|
||||
19 | except BaseException as err:
|
||||
20 | raise some_other_err
|
||||
| ^^^^^^^^^^^^^^^^^^^^ B904
|
||||
21 | finally:
|
||||
22 | raise Exception("Nothing to chain from, so no warning here")
|
||||
|
|
||||
|
||||
B904.py:63:9: B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
|
||||
|
|
||||
61 | except Exception as e:
|
||||
62 | if ...:
|
||||
63 | raise RuntimeError("boom!")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ B904
|
||||
63 | else:
|
||||
64 | raise RuntimeError("bang!")
|
||||
64 | else:
|
||||
65 | raise RuntimeError("bang!")
|
||||
|
|
||||
|
||||
B904.py:64:9: B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
|
||||
B904.py:65:9: B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
|
||||
|
|
||||
62 | raise RuntimeError("boom!")
|
||||
63 | else:
|
||||
64 | raise RuntimeError("bang!")
|
||||
63 | raise RuntimeError("boom!")
|
||||
64 | else:
|
||||
65 | raise RuntimeError("bang!")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ B904
|
||||
|
|
||||
|
||||
B904.py:72:13: B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
|
||||
B904.py:73:13: B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
|
||||
|
|
||||
70 | match 0:
|
||||
71 | case 0:
|
||||
72 | raise RuntimeError("boom!")
|
||||
71 | match 0:
|
||||
72 | case 0:
|
||||
73 | raise RuntimeError("boom!")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ B904
|
||||
|
|
||||
|
||||
|
||||
@@ -1,44 +1,5 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast::{ExceptHandler, Expr, Ranged, Stmt};
|
||||
|
||||
use ruff_python_ast::identifier::{Identifier, TryIdentifier};
|
||||
use ruff_python_stdlib::builtins::BUILTINS;
|
||||
use ruff_python_stdlib::builtins::is_builtin;
|
||||
|
||||
pub(super) fn shadows_builtin(name: &str, ignorelist: &[String]) -> bool {
|
||||
BUILTINS.contains(&name) && ignorelist.iter().all(|ignore| ignore != name)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub(crate) enum AnyShadowing<'a> {
|
||||
Expression(&'a Expr),
|
||||
Statement(&'a Stmt),
|
||||
ExceptHandler(&'a ExceptHandler),
|
||||
}
|
||||
|
||||
impl Identifier for AnyShadowing<'_> {
|
||||
fn identifier(&self) -> TextRange {
|
||||
match self {
|
||||
AnyShadowing::Expression(expr) => expr.range(),
|
||||
AnyShadowing::Statement(stmt) => stmt.identifier(),
|
||||
AnyShadowing::ExceptHandler(handler) => handler.try_identifier().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Stmt> for AnyShadowing<'a> {
|
||||
fn from(value: &'a Stmt) -> Self {
|
||||
AnyShadowing::Statement(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Expr> for AnyShadowing<'a> {
|
||||
fn from(value: &'a Expr) -> Self {
|
||||
AnyShadowing::Expression(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ExceptHandler> for AnyShadowing<'a> {
|
||||
fn from(value: &'a ExceptHandler) -> Self {
|
||||
AnyShadowing::ExceptHandler(value)
|
||||
}
|
||||
is_builtin(name) && ignorelist.iter().all(|ignore| ignore != name)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use rustpython_parser::ast;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
use super::super::helpers::{shadows_builtin, AnyShadowing};
|
||||
use crate::rules::flake8_builtins::helpers::shadows_builtin;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for any class attributes that use the same name as a builtin.
|
||||
@@ -67,7 +67,7 @@ pub(crate) fn builtin_attribute_shadowing(
|
||||
checker: &mut Checker,
|
||||
class_def: &ast::StmtClassDef,
|
||||
name: &str,
|
||||
shadowing: AnyShadowing,
|
||||
range: TextRange,
|
||||
) {
|
||||
if shadows_builtin(name, &checker.settings.flake8_builtins.builtins_ignorelist) {
|
||||
// Ignore shadowing within `TypedDict` definitions, since these are only accessible through
|
||||
@@ -84,7 +84,7 @@ pub(crate) fn builtin_attribute_shadowing(
|
||||
BuiltinAttributeShadowing {
|
||||
name: name.to_string(),
|
||||
},
|
||||
shadowing.identifier(),
|
||||
range,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
use super::super::helpers::{shadows_builtin, AnyShadowing};
|
||||
use crate::rules::flake8_builtins::helpers::shadows_builtin;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for variable (and function) assignments that use the same name
|
||||
@@ -59,17 +59,13 @@ impl Violation for BuiltinVariableShadowing {
|
||||
}
|
||||
|
||||
/// A001
|
||||
pub(crate) fn builtin_variable_shadowing(
|
||||
checker: &mut Checker,
|
||||
name: &str,
|
||||
shadowing: AnyShadowing,
|
||||
) {
|
||||
pub(crate) fn builtin_variable_shadowing(checker: &mut Checker, name: &str, range: TextRange) {
|
||||
if shadows_builtin(name, &checker.settings.flake8_builtins.builtins_ignorelist) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BuiltinVariableShadowing {
|
||||
name: name.to_string(),
|
||||
},
|
||||
shadowing.identifier(),
|
||||
range,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_builtins/mod.rs
|
||||
---
|
||||
A001.py:1:1: A001 Variable `sum` is shadowing a Python builtin
|
||||
A001.py:1:16: A001 Variable `sum` is shadowing a Python builtin
|
||||
|
|
||||
1 | import some as sum
|
||||
| ^^^^^^^^^^^^^^^^^^ A001
|
||||
| ^^^ A001
|
||||
2 | from some import other as int
|
||||
3 | from directory import new as dir
|
||||
|
|
||||
|
||||
A001.py:2:1: A001 Variable `int` is shadowing a Python builtin
|
||||
A001.py:2:27: A001 Variable `int` is shadowing a Python builtin
|
||||
|
|
||||
1 | import some as sum
|
||||
2 | from some import other as int
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A001
|
||||
| ^^^ A001
|
||||
3 | from directory import new as dir
|
||||
|
|
||||
|
||||
A001.py:3:1: A001 Variable `dir` is shadowing a Python builtin
|
||||
A001.py:3:30: A001 Variable `dir` is shadowing a Python builtin
|
||||
|
|
||||
1 | import some as sum
|
||||
2 | from some import other as int
|
||||
3 | from directory import new as dir
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A001
|
||||
| ^^^ A001
|
||||
4 |
|
||||
5 | print = 1
|
||||
|
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_builtins/mod.rs
|
||||
---
|
||||
A001.py:1:1: A001 Variable `sum` is shadowing a Python builtin
|
||||
A001.py:1:16: A001 Variable `sum` is shadowing a Python builtin
|
||||
|
|
||||
1 | import some as sum
|
||||
| ^^^^^^^^^^^^^^^^^^ A001
|
||||
| ^^^ A001
|
||||
2 | from some import other as int
|
||||
3 | from directory import new as dir
|
||||
|
|
||||
|
||||
A001.py:2:1: A001 Variable `int` is shadowing a Python builtin
|
||||
A001.py:2:27: A001 Variable `int` is shadowing a Python builtin
|
||||
|
|
||||
1 | import some as sum
|
||||
2 | from some import other as int
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A001
|
||||
| ^^^ A001
|
||||
3 | from directory import new as dir
|
||||
|
|
||||
|
||||
|
||||
@@ -14,16 +14,16 @@ pub(super) fn exactly_one_argument_with_matching_function<'a>(
|
||||
args: &'a [Expr],
|
||||
keywords: &[Keyword],
|
||||
) -> Option<&'a Expr> {
|
||||
if !keywords.is_empty() {
|
||||
let [arg] = args else {
|
||||
return None;
|
||||
}
|
||||
if args.len() != 1 {
|
||||
};
|
||||
if !keywords.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if expr_name(func)? != name {
|
||||
return None;
|
||||
}
|
||||
Some(&args[0])
|
||||
Some(arg)
|
||||
}
|
||||
|
||||
pub(super) fn first_argument_with_matching_function<'a>(
|
||||
|
||||
@@ -82,10 +82,9 @@ pub(crate) fn unnecessary_dict_comprehension(
|
||||
value: &Expr,
|
||||
generators: &[Comprehension],
|
||||
) {
|
||||
if generators.len() != 1 {
|
||||
let [generator] = generators else {
|
||||
return;
|
||||
}
|
||||
let generator = &generators[0];
|
||||
};
|
||||
if !generator.ifs.is_empty() || generator.is_async {
|
||||
return;
|
||||
}
|
||||
@@ -123,10 +122,9 @@ pub(crate) fn unnecessary_list_set_comprehension(
|
||||
elt: &Expr,
|
||||
generators: &[Comprehension],
|
||||
) {
|
||||
if generators.len() != 1 {
|
||||
let [generator] = generators else {
|
||||
return;
|
||||
}
|
||||
let generator = &generators[0];
|
||||
};
|
||||
if !generator.ifs.is_empty() || generator.is_async {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use rustpython_parser::ast::{self, Expr, Ranged};
|
||||
use rustpython_parser::ast::{self, Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableKeyword;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
@@ -69,6 +70,7 @@ pub(crate) fn unnecessary_double_cast_or_process(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
outer_kw: &[Keyword],
|
||||
) {
|
||||
let Some(outer) = helpers::expr_name(func) else {
|
||||
return;
|
||||
@@ -84,7 +86,12 @@ pub(crate) fn unnecessary_double_cast_or_process(
|
||||
let Some(arg) = args.first() else {
|
||||
return;
|
||||
};
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = arg else {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
keywords: inner_kw,
|
||||
..
|
||||
}) = arg
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(inner) = helpers::expr_name(func) else {
|
||||
@@ -94,6 +101,21 @@ pub(crate) fn unnecessary_double_cast_or_process(
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid collapsing nested `sorted` calls with non-identical keyword arguments
|
||||
// (i.e., `key`, `reverse`).
|
||||
if inner == "sorted" && outer == "sorted" {
|
||||
if inner_kw.len() != outer_kw.len() {
|
||||
return;
|
||||
}
|
||||
if !inner_kw.iter().all(|inner| {
|
||||
outer_kw
|
||||
.iter()
|
||||
.any(|outer| ComparableKeyword::from(inner) == ComparableKeyword::from(outer))
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ex) set(tuple(...))
|
||||
// Ex) list(tuple(...))
|
||||
// Ex) set(set(...))
|
||||
|
||||
@@ -226,7 +226,7 @@ C414.py:12:1: C414 [*] Unnecessary `list` call within `sorted()`
|
||||
12 |+sorted(x)
|
||||
13 13 | sorted(tuple(x))
|
||||
14 14 | sorted(sorted(x))
|
||||
15 15 | sorted(sorted(x, key=lambda y: y))
|
||||
15 15 | sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
|
||||
|
||||
C414.py:13:1: C414 [*] Unnecessary `tuple` call within `sorted()`
|
||||
|
|
||||
@@ -235,7 +235,7 @@ C414.py:13:1: C414 [*] Unnecessary `tuple` call within `sorted()`
|
||||
13 | sorted(tuple(x))
|
||||
| ^^^^^^^^^^^^^^^^ C414
|
||||
14 | sorted(sorted(x))
|
||||
15 | sorted(sorted(x, key=lambda y: y))
|
||||
15 | sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
|
||||
|
|
||||
= help: Remove the inner `tuple` call
|
||||
|
||||
@@ -246,8 +246,8 @@ C414.py:13:1: C414 [*] Unnecessary `tuple` call within `sorted()`
|
||||
13 |-sorted(tuple(x))
|
||||
13 |+sorted(x)
|
||||
14 14 | sorted(sorted(x))
|
||||
15 15 | sorted(sorted(x, key=lambda y: y))
|
||||
16 16 | sorted(reversed(x))
|
||||
15 15 | sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
|
||||
16 16 | sorted(sorted(x, reverse=True), reverse=True)
|
||||
|
||||
C414.py:14:1: C414 [*] Unnecessary `sorted` call within `sorted()`
|
||||
|
|
||||
@@ -255,8 +255,8 @@ C414.py:14:1: C414 [*] Unnecessary `sorted` call within `sorted()`
|
||||
13 | sorted(tuple(x))
|
||||
14 | sorted(sorted(x))
|
||||
| ^^^^^^^^^^^^^^^^^ C414
|
||||
15 | sorted(sorted(x, key=lambda y: y))
|
||||
16 | sorted(reversed(x))
|
||||
15 | sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
|
||||
16 | sorted(sorted(x, reverse=True), reverse=True)
|
||||
|
|
||||
= help: Remove the inner `sorted` call
|
||||
|
||||
@@ -266,18 +266,18 @@ C414.py:14:1: C414 [*] Unnecessary `sorted` call within `sorted()`
|
||||
13 13 | sorted(tuple(x))
|
||||
14 |-sorted(sorted(x))
|
||||
14 |+sorted(x)
|
||||
15 15 | sorted(sorted(x, key=lambda y: y))
|
||||
16 16 | sorted(reversed(x))
|
||||
17 17 | sorted(list(x), key=lambda y: y)
|
||||
15 15 | sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
|
||||
16 16 | sorted(sorted(x, reverse=True), reverse=True)
|
||||
17 17 | sorted(reversed(x))
|
||||
|
||||
C414.py:15:1: C414 [*] Unnecessary `sorted` call within `sorted()`
|
||||
|
|
||||
13 | sorted(tuple(x))
|
||||
14 | sorted(sorted(x))
|
||||
15 | sorted(sorted(x, key=lambda y: y))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C414
|
||||
16 | sorted(reversed(x))
|
||||
17 | sorted(list(x), key=lambda y: y)
|
||||
15 | sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C414
|
||||
16 | sorted(sorted(x, reverse=True), reverse=True)
|
||||
17 | sorted(reversed(x))
|
||||
|
|
||||
= help: Remove the inner `sorted` call
|
||||
|
||||
@@ -285,77 +285,103 @@ C414.py:15:1: C414 [*] Unnecessary `sorted` call within `sorted()`
|
||||
12 12 | sorted(list(x))
|
||||
13 13 | sorted(tuple(x))
|
||||
14 14 | sorted(sorted(x))
|
||||
15 |-sorted(sorted(x, key=lambda y: y))
|
||||
15 |+sorted(x, )
|
||||
16 16 | sorted(reversed(x))
|
||||
17 17 | sorted(list(x), key=lambda y: y)
|
||||
18 18 | tuple(
|
||||
15 |-sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
|
||||
15 |+sorted(x, reverse=False, key=foo)
|
||||
16 16 | sorted(sorted(x, reverse=True), reverse=True)
|
||||
17 17 | sorted(reversed(x))
|
||||
18 18 | sorted(list(x), key=lambda y: y)
|
||||
|
||||
C414.py:16:1: C414 [*] Unnecessary `reversed` call within `sorted()`
|
||||
C414.py:16:1: C414 [*] Unnecessary `sorted` call within `sorted()`
|
||||
|
|
||||
14 | sorted(sorted(x))
|
||||
15 | sorted(sorted(x, key=lambda y: y))
|
||||
16 | sorted(reversed(x))
|
||||
| ^^^^^^^^^^^^^^^^^^^ C414
|
||||
17 | sorted(list(x), key=lambda y: y)
|
||||
18 | tuple(
|
||||
15 | sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
|
||||
16 | sorted(sorted(x, reverse=True), reverse=True)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C414
|
||||
17 | sorted(reversed(x))
|
||||
18 | sorted(list(x), key=lambda y: y)
|
||||
|
|
||||
= help: Remove the inner `reversed` call
|
||||
= help: Remove the inner `sorted` call
|
||||
|
||||
ℹ Suggested fix
|
||||
13 13 | sorted(tuple(x))
|
||||
14 14 | sorted(sorted(x))
|
||||
15 15 | sorted(sorted(x, key=lambda y: y))
|
||||
16 |-sorted(reversed(x))
|
||||
16 |+sorted(x)
|
||||
17 17 | sorted(list(x), key=lambda y: y)
|
||||
18 18 | tuple(
|
||||
19 19 | list(
|
||||
15 15 | sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
|
||||
16 |-sorted(sorted(x, reverse=True), reverse=True)
|
||||
16 |+sorted(x, reverse=True)
|
||||
17 17 | sorted(reversed(x))
|
||||
18 18 | sorted(list(x), key=lambda y: y)
|
||||
19 19 | tuple(
|
||||
|
||||
C414.py:17:1: C414 [*] Unnecessary `list` call within `sorted()`
|
||||
C414.py:17:1: C414 [*] Unnecessary `reversed` call within `sorted()`
|
||||
|
|
||||
15 | sorted(sorted(x, key=lambda y: y))
|
||||
16 | sorted(reversed(x))
|
||||
17 | sorted(list(x), key=lambda y: y)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C414
|
||||
18 | tuple(
|
||||
19 | list(
|
||||
15 | sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
|
||||
16 | sorted(sorted(x, reverse=True), reverse=True)
|
||||
17 | sorted(reversed(x))
|
||||
| ^^^^^^^^^^^^^^^^^^^ C414
|
||||
18 | sorted(list(x), key=lambda y: y)
|
||||
19 | tuple(
|
||||
|
|
||||
= help: Remove the inner `list` call
|
||||
= help: Remove the inner `reversed` call
|
||||
|
||||
ℹ Suggested fix
|
||||
14 14 | sorted(sorted(x))
|
||||
15 15 | sorted(sorted(x, key=lambda y: y))
|
||||
16 16 | sorted(reversed(x))
|
||||
17 |-sorted(list(x), key=lambda y: y)
|
||||
17 |+sorted(x, key=lambda y: y)
|
||||
18 18 | tuple(
|
||||
19 19 | list(
|
||||
20 20 | [x, 3, "hell"\
|
||||
15 15 | sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
|
||||
16 16 | sorted(sorted(x, reverse=True), reverse=True)
|
||||
17 |-sorted(reversed(x))
|
||||
17 |+sorted(x)
|
||||
18 18 | sorted(list(x), key=lambda y: y)
|
||||
19 19 | tuple(
|
||||
20 20 | list(
|
||||
|
||||
C414.py:18:1: C414 [*] Unnecessary `list` call within `tuple()`
|
||||
C414.py:18:1: C414 [*] Unnecessary `list` call within `sorted()`
|
||||
|
|
||||
16 | sorted(reversed(x))
|
||||
17 | sorted(list(x), key=lambda y: y)
|
||||
18 | / tuple(
|
||||
19 | | list(
|
||||
20 | | [x, 3, "hell"\
|
||||
21 | | "o"]
|
||||
22 | | )
|
||||
23 | | )
|
||||
| |_^ C414
|
||||
16 | sorted(sorted(x, reverse=True), reverse=True)
|
||||
17 | sorted(reversed(x))
|
||||
18 | sorted(list(x), key=lambda y: y)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C414
|
||||
19 | tuple(
|
||||
20 | list(
|
||||
|
|
||||
= help: Remove the inner `list` call
|
||||
|
||||
ℹ Suggested fix
|
||||
16 16 | sorted(reversed(x))
|
||||
17 17 | sorted(list(x), key=lambda y: y)
|
||||
18 18 | tuple(
|
||||
19 |- list(
|
||||
20 |- [x, 3, "hell"\
|
||||
19 |+ [x, 3, "hell"\
|
||||
21 20 | "o"]
|
||||
22 21 | )
|
||||
23 |-)
|
||||
15 15 | sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
|
||||
16 16 | sorted(sorted(x, reverse=True), reverse=True)
|
||||
17 17 | sorted(reversed(x))
|
||||
18 |-sorted(list(x), key=lambda y: y)
|
||||
18 |+sorted(x, key=lambda y: y)
|
||||
19 19 | tuple(
|
||||
20 20 | list(
|
||||
21 21 | [x, 3, "hell"\
|
||||
|
||||
C414.py:19:1: C414 [*] Unnecessary `list` call within `tuple()`
|
||||
|
|
||||
17 | sorted(reversed(x))
|
||||
18 | sorted(list(x), key=lambda y: y)
|
||||
19 | / tuple(
|
||||
20 | | list(
|
||||
21 | | [x, 3, "hell"\
|
||||
22 | | "o"]
|
||||
23 | | )
|
||||
24 | | )
|
||||
| |_^ C414
|
||||
25 |
|
||||
26 | # Nested sorts with differing keyword arguments. Not flagged.
|
||||
|
|
||||
= help: Remove the inner `list` call
|
||||
|
||||
ℹ Suggested fix
|
||||
17 17 | sorted(reversed(x))
|
||||
18 18 | sorted(list(x), key=lambda y: y)
|
||||
19 19 | tuple(
|
||||
20 |- list(
|
||||
21 |- [x, 3, "hell"\
|
||||
20 |+ [x, 3, "hell"\
|
||||
22 21 | "o"]
|
||||
23 22 | )
|
||||
24 |-)
|
||||
25 23 |
|
||||
26 24 | # Nested sorts with differing keyword arguments. Not flagged.
|
||||
27 25 | sorted(sorted(x, key=lambda y: y))
|
||||
|
||||
|
||||
|
||||
@@ -1,92 +1,12 @@
|
||||
#[cfg(target_family = "unix")]
|
||||
#![cfg(target_family = "unix")]
|
||||
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
#[cfg(target_family = "unix")]
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
use anyhow::Result;
|
||||
use ruff_text_size::{TextLen, TextSize};
|
||||
|
||||
/// A shebang directive (e.g., `#!/usr/bin/env python3`).
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) struct ShebangDirective<'a> {
|
||||
/// The offset of the directive contents (e.g., `/usr/bin/env python3`) from the start of the
|
||||
/// line.
|
||||
pub(crate) offset: TextSize,
|
||||
/// The contents of the directive (e.g., `"/usr/bin/env python3"`).
|
||||
pub(crate) contents: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> ShebangDirective<'a> {
|
||||
/// Parse a shebang directive from a line, or return `None` if the line does not contain a
|
||||
/// shebang directive.
|
||||
pub(crate) fn try_extract(line: &'a str) -> Option<Self> {
|
||||
// Trim whitespace.
|
||||
let directive = Self::lex_whitespace(line);
|
||||
|
||||
// Trim the `#!` prefix.
|
||||
let directive = Self::lex_char(directive, '#')?;
|
||||
let directive = Self::lex_char(directive, '!')?;
|
||||
|
||||
Some(Self {
|
||||
offset: line.text_len() - directive.text_len(),
|
||||
contents: directive,
|
||||
})
|
||||
}
|
||||
|
||||
/// Lex optional leading whitespace.
|
||||
#[inline]
|
||||
fn lex_whitespace(line: &str) -> &str {
|
||||
line.trim_start()
|
||||
}
|
||||
|
||||
/// Lex a specific character, or return `None` if the character is not the first character in
|
||||
/// the line.
|
||||
#[inline]
|
||||
fn lex_char(line: &str, c: char) -> Option<&str> {
|
||||
let mut chars = line.chars();
|
||||
if chars.next() == Some(c) {
|
||||
Some(chars.as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
pub(super) fn is_executable(filepath: &Path) -> Result<bool> {
|
||||
let metadata = filepath.metadata()?;
|
||||
let permissions = metadata.permissions();
|
||||
Ok(permissions.mode() & 0o111 != 0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
|
||||
use crate::rules::flake8_executable::helpers::ShebangDirective;
|
||||
|
||||
#[test]
|
||||
fn shebang_non_match() {
|
||||
let source = "not a match";
|
||||
assert_debug_snapshot!(ShebangDirective::try_extract(source));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shebang_end_of_line() {
|
||||
let source = "print('test') #!/usr/bin/python";
|
||||
assert_debug_snapshot!(ShebangDirective::try_extract(source));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shebang_match() {
|
||||
let source = "#!/usr/bin/env python";
|
||||
assert_debug_snapshot!(ShebangDirective::try_extract(source));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shebang_leading_space() {
|
||||
let source = " #!/usr/bin/env python";
|
||||
assert_debug_snapshot!(ShebangDirective::try_extract(source));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use wsl;
|
||||
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
@@ -42,6 +44,11 @@ impl Violation for ShebangMissingExecutableFile {
|
||||
/// EXE002
|
||||
#[cfg(target_family = "unix")]
|
||||
pub(crate) fn shebang_missing(filepath: &Path) -> Option<Diagnostic> {
|
||||
// WSL supports Windows file systems, which do not have executable bits.
|
||||
// Instead, everything is executable. Therefore, we skip this rule on WSL.
|
||||
if wsl::is_wsl() {
|
||||
return None;
|
||||
}
|
||||
if let Ok(true) = is_executable(filepath) {
|
||||
let diagnostic = Diagnostic::new(ShebangMissingExecutableFile, TextRange::default());
|
||||
return Some(diagnostic);
|
||||
|
||||
@@ -3,7 +3,7 @@ use ruff_text_size::{TextLen, TextRange};
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::rules::flake8_executable::helpers::ShebangDirective;
|
||||
use crate::comments::shebang::ShebangDirective;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for a shebang directive that is not at the beginning of the file.
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use wsl;
|
||||
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::comments::shebang::ShebangDirective;
|
||||
use crate::registry::AsRule;
|
||||
#[cfg(target_family = "unix")]
|
||||
use crate::rules::flake8_executable::helpers::is_executable;
|
||||
use crate::rules::flake8_executable::helpers::ShebangDirective;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for a shebang directive in a file that is not executable.
|
||||
@@ -48,6 +50,11 @@ pub(crate) fn shebang_not_executable(
|
||||
range: TextRange,
|
||||
shebang: &ShebangDirective,
|
||||
) -> Option<Diagnostic> {
|
||||
// WSL supports Windows file systems, which do not have executable bits.
|
||||
// Instead, everything is executable. Therefore, we skip this rule on WSL.
|
||||
if wsl::is_wsl() {
|
||||
return None;
|
||||
}
|
||||
let ShebangDirective { offset, contents } = shebang;
|
||||
|
||||
if let Ok(false) = is_executable(filepath) {
|
||||
|
||||
@@ -3,7 +3,7 @@ use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::rules::flake8_executable::helpers::ShebangDirective;
|
||||
use crate::comments::shebang::ShebangDirective;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for a shebang directive in `.py` files that does not contain `python`.
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use std::ops::Sub;
|
||||
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::rules::flake8_executable::helpers::ShebangDirective;
|
||||
use crate::comments::shebang::ShebangDirective;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for whitespace before a shebang directive.
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
//! Rules from [flake8-gettext](https://pypi.org/project/flake8-gettext/).
|
||||
use rustpython_parser::ast::{self, Expr};
|
||||
|
||||
pub(crate) mod rules;
|
||||
pub mod settings;
|
||||
|
||||
/// Returns true if the [`Expr`] is an internationalization function call.
|
||||
pub(crate) fn is_gettext_func_call(func: &Expr, functions_names: &[String]) -> bool {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
||||
functions_names.contains(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
@@ -5,6 +5,40 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for f-strings in `gettext` function calls.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In the `gettext` API, the `gettext` function (often aliased to `_`) returns
|
||||
/// a translation of its input argument by looking it up in a translation
|
||||
/// catalog.
|
||||
///
|
||||
/// Calling `gettext` with an f-string as its argument can cause unexpected
|
||||
/// behavior. Since the f-string is resolved before the function call, the
|
||||
/// translation catalog will look up the formatted string, rather than the
|
||||
/// f-string template.
|
||||
///
|
||||
/// Instead, format the value returned by the function call, rather than
|
||||
/// its argument.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from gettext import gettext as _
|
||||
///
|
||||
/// name = "Maria"
|
||||
/// _(f"Hello, {name}!") # Looks for "Hello, Maria!".
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from gettext import gettext as _
|
||||
///
|
||||
/// name = "Maria"
|
||||
/// _("Hello, %s!") % name # Looks for "Hello, %s!".
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: gettext](https://docs.python.org/3/library/gettext.html)
|
||||
#[violation]
|
||||
pub struct FStringInGetTextFuncCall;
|
||||
|
||||
|
||||
@@ -5,6 +5,40 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `str.format` calls in `gettext` function calls.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In the `gettext` API, the `gettext` function (often aliased to `_`) returns
|
||||
/// a translation of its input argument by looking it up in a translation
|
||||
/// catalog.
|
||||
///
|
||||
/// Calling `gettext` with a formatted string as its argument can cause
|
||||
/// unexpected behavior. Since the formatted string is resolved before the
|
||||
/// function call, the translation catalog will look up the formatted string,
|
||||
/// rather than the `str.format`-style template.
|
||||
///
|
||||
/// Instead, format the value returned by the function call, rather than
|
||||
/// its argument.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from gettext import gettext as _
|
||||
///
|
||||
/// name = "Maria"
|
||||
/// _("Hello, %s!" % name) # Looks for "Hello, Maria!".
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from gettext import gettext as _
|
||||
///
|
||||
/// name = "Maria"
|
||||
/// _("Hello, %s!") % name # Looks for "Hello, %s!".
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: gettext](https://docs.python.org/3/library/gettext.html)
|
||||
#[violation]
|
||||
pub struct FormatInGetTextFuncCall;
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
use rustpython_parser::ast::{self, Expr};
|
||||
|
||||
/// Returns true if the [`Expr`] is an internationalization function call.
|
||||
pub(crate) fn is_gettext_func_call(func: &Expr, functions_names: &[String]) -> bool {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
||||
functions_names.contains(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
pub(crate) use f_string_in_gettext_func_call::*;
|
||||
pub(crate) use format_in_gettext_func_call::*;
|
||||
pub(crate) use is_gettext_func_call::*;
|
||||
pub(crate) use printf_in_gettext_func_call::*;
|
||||
|
||||
mod f_string_in_gettext_func_call;
|
||||
mod format_in_gettext_func_call;
|
||||
mod is_gettext_func_call;
|
||||
mod printf_in_gettext_func_call;
|
||||
|
||||
@@ -4,6 +4,40 @@ use crate::checkers::ast::Checker;
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for printf-style formatted strings in `gettext` function calls.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In the `gettext` API, the `gettext` function (often aliased to `_`) returns
|
||||
/// a translation of its input argument by looking it up in a translation
|
||||
/// catalog.
|
||||
///
|
||||
/// Calling `gettext` with a formatted string as its argument can cause
|
||||
/// unexpected behavior. Since the formatted string is resolved before the
|
||||
/// function call, the translation catalog will look up the formatted string,
|
||||
/// rather than the printf-style template.
|
||||
///
|
||||
/// Instead, format the value returned by the function call, rather than
|
||||
/// its argument.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from gettext import gettext as _
|
||||
///
|
||||
/// name = "Maria"
|
||||
/// _("Hello, {}!".format(name)) # Looks for "Hello, Maria!".
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from gettext import gettext as _
|
||||
///
|
||||
/// name = "Maria"
|
||||
/// _("Hello, %s!") % name # Looks for "Hello, %s!".
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: gettext](https://docs.python.org/3/library/gettext.html)
|
||||
#[violation]
|
||||
pub struct PrintfInGetTextFuncCall;
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ impl Violation for SingleLineImplicitStringConcatenation {
|
||||
///
|
||||
/// By default, this rule will only trigger if the string literal is
|
||||
/// concatenated via a backslash. To disallow implicit string concatenation
|
||||
/// altogether, set the `flake8-implicit-str-concat.allow-multiline` option
|
||||
/// altogether, set the [`flake8-implicit-str-concat.allow-multiline`] option
|
||||
/// to `false`.
|
||||
///
|
||||
/// ## Example
|
||||
|
||||
@@ -61,10 +61,7 @@ pub(crate) fn duplicate_class_field_definition<'a, 'b>(
|
||||
// Extract the property name from the assignment statement.
|
||||
let target = match stmt {
|
||||
Stmt::Assign(ast::StmtAssign { targets, .. }) => {
|
||||
if targets.len() != 1 {
|
||||
continue;
|
||||
}
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = &targets[0] {
|
||||
if let [Expr::Name(ast::ExprName { id, .. })] = targets.as_slice() {
|
||||
id
|
||||
} else {
|
||||
continue;
|
||||
|
||||
@@ -19,6 +19,8 @@ mod tests {
|
||||
#[test_case(Rule::ArgumentDefaultInStub, Path::new("PYI014.pyi"))]
|
||||
#[test_case(Rule::AssignmentDefaultInStub, Path::new("PYI015.py"))]
|
||||
#[test_case(Rule::AssignmentDefaultInStub, Path::new("PYI015.pyi"))]
|
||||
#[test_case(Rule::BadExitAnnotation, Path::new("PYI036.py"))]
|
||||
#[test_case(Rule::BadExitAnnotation, Path::new("PYI036.pyi"))]
|
||||
#[test_case(Rule::BadVersionInfoComparison, Path::new("PYI006.py"))]
|
||||
#[test_case(Rule::BadVersionInfoComparison, Path::new("PYI006.pyi"))]
|
||||
#[test_case(Rule::CollectionsNamedTuple, Path::new("PYI024.py"))]
|
||||
@@ -47,6 +49,8 @@ mod tests {
|
||||
#[test_case(Rule::PassStatementStubBody, Path::new("PYI009.pyi"))]
|
||||
#[test_case(Rule::QuotedAnnotationInStub, Path::new("PYI020.py"))]
|
||||
#[test_case(Rule::QuotedAnnotationInStub, Path::new("PYI020.pyi"))]
|
||||
#[test_case(Rule::RedundantNumericUnion, Path::new("PYI041.py"))]
|
||||
#[test_case(Rule::RedundantNumericUnion, Path::new("PYI041.pyi"))]
|
||||
#[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.py"))]
|
||||
#[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.pyi"))]
|
||||
#[test_case(Rule::UnassignedSpecialVariableInStub, Path::new("PYI035.py"))]
|
||||
|
||||
@@ -52,10 +52,9 @@ pub(crate) fn duplicate_union_member<'a>(checker: &mut Checker, expr: &'a Expr)
|
||||
// If the parent node is not a `BinOp` we will not perform a fix
|
||||
if let Some(Expr::BinOp(ast::ExprBinOp { left, right, .. })) = parent {
|
||||
// Replace the parent with its non-duplicate child.
|
||||
let child = if expr == left.as_ref() { right } else { left };
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
|
||||
checker
|
||||
.generator()
|
||||
.expr(if expr == left.as_ref() { right } else { left }),
|
||||
checker.locator.slice(child.range()).to_string(),
|
||||
parent.unwrap().range(),
|
||||
)));
|
||||
}
|
||||
|
||||
346
crates/ruff/src/rules/flake8_pyi/rules/exit_annotations.rs
Normal file
346
crates/ruff/src/rules/flake8_pyi/rules/exit_annotations.rs
Normal file
@@ -0,0 +1,346 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use rustpython_parser::ast::{
|
||||
ArgWithDefault, Arguments, Expr, ExprBinOp, ExprSubscript, ExprTuple, Identifier, Operator,
|
||||
Ranged,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::is_const_none;
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for incorrect function signatures on `__exit__` and `__aexit__`
|
||||
/// methods.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Improperly-annotated `__exit__` and `__aexit__` methods can cause
|
||||
/// unexpected behavior when interacting with type checkers.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// def __exit__(self, typ, exc, tb, extra_arg) -> None:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// def __exit__(
|
||||
/// self,
|
||||
/// typ: type[BaseException] | None,
|
||||
/// exc: BaseException | None,
|
||||
/// tb: TracebackType | None,
|
||||
/// extra_arg: int = 0,
|
||||
/// ) -> None:
|
||||
/// ...
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct BadExitAnnotation {
|
||||
func_kind: FuncKind,
|
||||
error_kind: ErrorKind,
|
||||
}
|
||||
|
||||
impl Violation for BadExitAnnotation {
|
||||
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let method_name = self.func_kind.to_string();
|
||||
match self.error_kind {
|
||||
ErrorKind::StarArgsNotAnnotated => format!("Star-args in `{method_name}` should be annotated with `object`"),
|
||||
ErrorKind::MissingArgs => format!("If there are no star-args, `{method_name}` should have at least 3 non-keyword-only args (excluding `self`)"),
|
||||
ErrorKind::ArgsAfterFirstFourMustHaveDefault => format!("All arguments after the first four in `{method_name}` must have a default value"),
|
||||
ErrorKind::AllKwargsMustHaveDefault => format!("All keyword-only arguments in `{method_name}` must have a default value"),
|
||||
ErrorKind::FirstArgBadAnnotation => format!("The first argument in `{method_name}` should be annotated with `object` or `type[BaseException] | None`"),
|
||||
ErrorKind::SecondArgBadAnnotation => format!("The second argument in `{method_name}` should be annotated with `object` or `BaseException | None`"),
|
||||
ErrorKind::ThirdArgBadAnnotation => format!("The third argument in `{method_name}` should be annotated with `object` or `types.TracebackType | None`"),
|
||||
}
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
if matches!(self.error_kind, ErrorKind::StarArgsNotAnnotated) {
|
||||
Some("Annotate star-args with `object`".to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
enum FuncKind {
|
||||
Sync,
|
||||
Async,
|
||||
}
|
||||
|
||||
impl Display for FuncKind {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
FuncKind::Sync => write!(f, "__exit__"),
|
||||
FuncKind::Async => write!(f, "__aexit__"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
enum ErrorKind {
|
||||
StarArgsNotAnnotated,
|
||||
MissingArgs,
|
||||
FirstArgBadAnnotation,
|
||||
SecondArgBadAnnotation,
|
||||
ThirdArgBadAnnotation,
|
||||
ArgsAfterFirstFourMustHaveDefault,
|
||||
AllKwargsMustHaveDefault,
|
||||
}
|
||||
|
||||
/// PYI036
|
||||
pub(crate) fn bad_exit_annotation(
|
||||
checker: &mut Checker,
|
||||
is_async: bool,
|
||||
name: &Identifier,
|
||||
args: &Arguments,
|
||||
) {
|
||||
let func_kind = match name.as_str() {
|
||||
"__exit__" if !is_async => FuncKind::Sync,
|
||||
"__aexit__" if is_async => FuncKind::Async,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let positional_args = args
|
||||
.args
|
||||
.iter()
|
||||
.chain(args.posonlyargs.iter())
|
||||
.collect::<SmallVec<[&ArgWithDefault; 4]>>();
|
||||
|
||||
// If there are less than three positional arguments, at least one of them must be a star-arg,
|
||||
// and it must be annotated with `object`.
|
||||
if positional_args.len() < 4 {
|
||||
check_short_args_list(checker, args, func_kind);
|
||||
}
|
||||
|
||||
// Every positional argument (beyond the first four) must have a default.
|
||||
for arg_with_default in positional_args
|
||||
.iter()
|
||||
.skip(4)
|
||||
.filter(|arg_with_default| arg_with_default.default.is_none())
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BadExitAnnotation {
|
||||
func_kind,
|
||||
error_kind: ErrorKind::ArgsAfterFirstFourMustHaveDefault,
|
||||
},
|
||||
arg_with_default.range(),
|
||||
));
|
||||
}
|
||||
|
||||
// ...as should all keyword-only arguments.
|
||||
for arg_with_default in args.kwonlyargs.iter().filter(|arg| arg.default.is_none()) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BadExitAnnotation {
|
||||
func_kind,
|
||||
error_kind: ErrorKind::AllKwargsMustHaveDefault,
|
||||
},
|
||||
arg_with_default.range(),
|
||||
));
|
||||
}
|
||||
|
||||
check_positional_args(checker, &positional_args, func_kind);
|
||||
}
|
||||
|
||||
/// Determine whether a "short" argument list (i.e., an argument list with less than four elements)
|
||||
/// contains a star-args argument annotated with `object`. If not, report an error.
|
||||
fn check_short_args_list(checker: &mut Checker, args: &Arguments, func_kind: FuncKind) {
|
||||
if let Some(varargs) = &args.vararg {
|
||||
if let Some(annotation) = varargs
|
||||
.annotation
|
||||
.as_ref()
|
||||
.filter(|ann| !is_object_or_unused(ann, checker.semantic()))
|
||||
{
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
BadExitAnnotation {
|
||||
func_kind,
|
||||
error_kind: ErrorKind::StarArgsNotAnnotated,
|
||||
},
|
||||
annotation.range(),
|
||||
);
|
||||
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if checker.semantic().is_builtin("object") {
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
|
||||
"object".to_string(),
|
||||
annotation.range(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
} else {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BadExitAnnotation {
|
||||
func_kind,
|
||||
error_kind: ErrorKind::MissingArgs,
|
||||
},
|
||||
args.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines whether the positional arguments of an `__exit__` or `__aexit__` method are
|
||||
/// annotated correctly.
|
||||
fn check_positional_args(
|
||||
checker: &mut Checker,
|
||||
positional_args: &[&ArgWithDefault],
|
||||
kind: FuncKind,
|
||||
) {
|
||||
// For each argument, define the predicate against which to check the annotation.
|
||||
type AnnotationValidator = fn(&Expr, &SemanticModel) -> bool;
|
||||
|
||||
let validations: [(ErrorKind, AnnotationValidator); 3] = [
|
||||
(ErrorKind::FirstArgBadAnnotation, is_base_exception_type),
|
||||
(ErrorKind::SecondArgBadAnnotation, is_base_exception),
|
||||
(ErrorKind::ThirdArgBadAnnotation, is_traceback_type),
|
||||
];
|
||||
|
||||
for (arg, (error_info, predicate)) in positional_args.iter().skip(1).take(3).zip(validations) {
|
||||
let Some(annotation) = arg.def.annotation.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if is_object_or_unused(annotation, checker.semantic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there's an annotation that's not `object` or `Unused`, check that the annotated type
|
||||
// matches the predicate.
|
||||
if non_none_annotation_element(annotation, checker.semantic())
|
||||
.map_or(false, |elem| predicate(elem, checker.semantic()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BadExitAnnotation {
|
||||
func_kind: kind,
|
||||
error_kind: error_info,
|
||||
},
|
||||
annotation.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the non-`None` annotation element of a PEP 604-style union or `Optional` annotation.
|
||||
fn non_none_annotation_element<'a>(
|
||||
annotation: &'a Expr,
|
||||
model: &SemanticModel,
|
||||
) -> Option<&'a Expr> {
|
||||
// E.g., `typing.Union` or `typing.Optional`
|
||||
if let Expr::Subscript(ExprSubscript { value, slice, .. }) = annotation {
|
||||
if model.match_typing_expr(value, "Optional") {
|
||||
return if is_const_none(slice) {
|
||||
None
|
||||
} else {
|
||||
Some(slice)
|
||||
};
|
||||
}
|
||||
|
||||
if !model.match_typing_expr(value, "Union") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let Expr::Tuple(ExprTuple { elts, .. }) = slice.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let [left, right] = elts.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
return match (is_const_none(left), is_const_none(right)) {
|
||||
(false, true) => Some(left),
|
||||
(true, false) => Some(right),
|
||||
(true, true) => None,
|
||||
(false, false) => None,
|
||||
};
|
||||
}
|
||||
|
||||
// PEP 604-style union (e.g., `int | None`)
|
||||
if let Expr::BinOp(ExprBinOp {
|
||||
op: Operator::BitOr,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
}) = annotation
|
||||
{
|
||||
if !is_const_none(left) {
|
||||
return Some(left);
|
||||
}
|
||||
|
||||
if !is_const_none(right) {
|
||||
return Some(right);
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Expr`] is the `object` builtin or the `_typeshed.Unused` type.
|
||||
fn is_object_or_unused(expr: &Expr, model: &SemanticModel) -> bool {
|
||||
model
|
||||
.resolve_call_path(expr)
|
||||
.as_ref()
|
||||
.map_or(false, |call_path| {
|
||||
matches!(
|
||||
call_path.as_slice(),
|
||||
["" | "builtins", "object"] | ["_typeshed", "Unused"]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Expr`] is `BaseException`.
|
||||
fn is_base_exception(expr: &Expr, model: &SemanticModel) -> bool {
|
||||
model
|
||||
.resolve_call_path(expr)
|
||||
.as_ref()
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["" | "builtins", "BaseException"])
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Expr`] is the `types.TracebackType` type.
|
||||
fn is_traceback_type(expr: &Expr, model: &SemanticModel) -> bool {
|
||||
model
|
||||
.resolve_call_path(expr)
|
||||
.as_ref()
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["types", "TracebackType"])
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Expr`] is, e.g., `Type[BaseException]`.
|
||||
fn is_base_exception_type(expr: &Expr, model: &SemanticModel) -> bool {
|
||||
let Expr::Subscript(ExprSubscript { value, slice, .. }) = expr else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if model.match_typing_expr(value, "Type")
|
||||
|| model
|
||||
.resolve_call_path(value)
|
||||
.as_ref()
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["" | "builtins", "type"])
|
||||
})
|
||||
{
|
||||
is_base_exception(slice, model)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ pub(crate) use complex_if_statement_in_stub::*;
|
||||
pub(crate) use docstring_in_stubs::*;
|
||||
pub(crate) use duplicate_union_member::*;
|
||||
pub(crate) use ellipsis_in_non_empty_class_body::*;
|
||||
pub(crate) use exit_annotations::*;
|
||||
pub(crate) use future_annotations_in_stub::*;
|
||||
pub(crate) use iter_method_return_iterable::*;
|
||||
pub(crate) use no_return_argument_annotation::*;
|
||||
@@ -15,6 +16,7 @@ pub(crate) use pass_in_class_body::*;
|
||||
pub(crate) use pass_statement_stub_body::*;
|
||||
pub(crate) use prefix_type_params::*;
|
||||
pub(crate) use quoted_annotation_in_stub::*;
|
||||
pub(crate) use redundant_numeric_union::*;
|
||||
pub(crate) use simple_defaults::*;
|
||||
pub(crate) use str_or_repr_defined_in_stub::*;
|
||||
pub(crate) use string_or_bytes_too_long::*;
|
||||
@@ -33,6 +35,7 @@ mod complex_if_statement_in_stub;
|
||||
mod docstring_in_stubs;
|
||||
mod duplicate_union_member;
|
||||
mod ellipsis_in_non_empty_class_body;
|
||||
mod exit_annotations;
|
||||
mod future_annotations_in_stub;
|
||||
mod iter_method_return_iterable;
|
||||
mod no_return_argument_annotation;
|
||||
@@ -43,6 +46,7 @@ mod pass_in_class_body;
|
||||
mod pass_statement_stub_body;
|
||||
mod prefix_type_params;
|
||||
mod quoted_annotation_in_stub;
|
||||
mod redundant_numeric_union;
|
||||
mod simple_defaults;
|
||||
mod str_or_repr_defined_in_stub;
|
||||
mod string_or_bytes_too_long;
|
||||
|
||||
@@ -60,10 +60,10 @@ impl Violation for UnprefixedTypeParam {
|
||||
|
||||
/// PYI001
|
||||
pub(crate) fn prefix_type_params(checker: &mut Checker, value: &Expr, targets: &[Expr]) {
|
||||
if targets.len() != 1 {
|
||||
let [target] = targets else {
|
||||
return;
|
||||
}
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = &targets[0] {
|
||||
};
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = target {
|
||||
if id.starts_with('_') {
|
||||
return;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user