Compare commits
175 Commits
charlie/sm
...
fix-set-ch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd1732767e | ||
|
|
89c1dc39f7 | ||
|
|
ad4ae49ea2 | ||
|
|
2a6d7cd71c | ||
|
|
2b5fb70482 | ||
|
|
8c048b463c | ||
|
|
19abee086b | ||
|
|
1ed5d7e437 | ||
|
|
3f032cf09d | ||
|
|
775326790e | ||
|
|
7b0fb1a3b4 | ||
|
|
c2a3e97b7f | ||
|
|
805b2eb0b7 | ||
|
|
0c7ea800af | ||
|
|
c67029ded9 | ||
|
|
a70afa7de7 | ||
|
|
d1b8fe6af2 | ||
|
|
913b9d1fcf | ||
|
|
f9e82f2578 | ||
|
|
79ae1840af | ||
|
|
8938b2d555 | ||
|
|
33434fcb9c | ||
|
|
8a3a269eef | ||
|
|
d31eb87877 | ||
|
|
72245960a1 | ||
|
|
e6b00f0c4e | ||
|
|
f952bef1ad | ||
|
|
dc223fd3ca | ||
|
|
209aaa5add | ||
|
|
ff37d7af23 | ||
|
|
c65f47d7c4 | ||
|
|
d1d06960f0 | ||
|
|
576e0c7b80 | ||
|
|
1fba98681e | ||
|
|
95e61987d1 | ||
|
|
a0721912a4 | ||
|
|
694bf7f5b8 | ||
|
|
466719247b | ||
|
|
3fa4440d87 | ||
|
|
14e06f9f8b | ||
|
|
e7a2e0f437 | ||
|
|
67b43ab72a | ||
|
|
5ae4667fd5 | ||
|
|
d8a6109b69 | ||
|
|
fcacd3cd95 | ||
|
|
42c071d302 | ||
|
|
c14896b42c | ||
|
|
935094c2ff | ||
|
|
2c41c54e0c | ||
|
|
d6daa61563 | ||
|
|
cb6788ab5f | ||
|
|
e82160a83a | ||
|
|
26b1dd0ca2 | ||
|
|
fcfd6ad129 | ||
|
|
6a0cebdf7b | ||
|
|
0a5dfcb26a | ||
|
|
3ff1f003f4 | ||
|
|
ebdc4afc33 | ||
|
|
03ee6033f9 | ||
|
|
a401989b7a | ||
|
|
4cd4b37e74 | ||
|
|
602b4b3519 | ||
|
|
c4fdbf8903 | ||
|
|
5d939222db | ||
|
|
b2498c576f | ||
|
|
c89d2f835e | ||
|
|
211d8e170d | ||
|
|
b92be59ffe | ||
|
|
b030c70dda | ||
|
|
10ba79489a | ||
|
|
ea3cbcc362 | ||
|
|
b8f45c93b4 | ||
|
|
621718784a | ||
|
|
fcbf5c3fae | ||
|
|
c68686b1de | ||
|
|
583411a29f | ||
|
|
6d94aa89e3 | ||
|
|
8d5d34c6d1 | ||
|
|
edadd7814f | ||
|
|
3180f9978a | ||
|
|
bdff4a66ac | ||
|
|
ab26f2dc9d | ||
|
|
0099f9720f | ||
|
|
d9fdcebfc1 | ||
|
|
b7038cee13 | ||
|
|
be740106e0 | ||
|
|
63d892f1e4 | ||
|
|
28aad95414 | ||
|
|
5f4bce6d2b | ||
|
|
4ea4fd1984 | ||
|
|
d4027d8b65 | ||
|
|
9bf168c0a4 | ||
|
|
59148344be | ||
|
|
0945803427 | ||
|
|
b7294b48e7 | ||
|
|
be31d71849 | ||
|
|
46c3b3af94 | ||
|
|
3d34d9298d | ||
|
|
1156c65be1 | ||
|
|
1a53996f53 | ||
|
|
4bd395a850 | ||
|
|
bb4f3dedf4 | ||
|
|
01470d9045 | ||
|
|
0b471197dc | ||
|
|
399eb84d5e | ||
|
|
35cd57d0fc | ||
|
|
2b2812c4f2 | ||
|
|
9d0ffd33ca | ||
|
|
e209b5fc5f | ||
|
|
c1286d61df | ||
|
|
6c1ff6a85f | ||
|
|
06bcb85f81 | ||
|
|
d7a4999915 | ||
|
|
d4e54cff05 | ||
|
|
e1b6f6e57e | ||
|
|
50053f60f3 | ||
|
|
a4f73ea8c7 | ||
|
|
04a95cb9ee | ||
|
|
f9b3f10456 | ||
|
|
f47a517e79 | ||
|
|
ea31229be0 | ||
|
|
0854543328 | ||
|
|
0bc3d99298 | ||
|
|
0cd453bdf0 | ||
|
|
84a5584888 | ||
|
|
6146b75dd0 | ||
|
|
236074fdde | ||
|
|
e323bb015b | ||
|
|
80fa3f2bfa | ||
|
|
1846d90bbd | ||
|
|
0106bce02f | ||
|
|
2695d0561a | ||
|
|
5f715417e0 | ||
|
|
f7c2d25205 | ||
|
|
6e096f216a | ||
|
|
d0ad4be20e | ||
|
|
6425fe8c12 | ||
|
|
68db74b3c5 | ||
|
|
9646bc7d7f | ||
|
|
5756829344 | ||
|
|
cb45b25879 | ||
|
|
0911ce4cbc | ||
|
|
51f04ee6ef | ||
|
|
dbeadd99a8 | ||
|
|
79b35fc3cc | ||
|
|
9f16ae354e | ||
|
|
9741f788c7 | ||
|
|
901060fa96 | ||
|
|
f069eb9e3d | ||
|
|
fe72bde23c | ||
|
|
1268ddca92 | ||
|
|
af433ac14d | ||
|
|
ccca11839a | ||
|
|
12e45498e8 | ||
|
|
33a7ed058f | ||
|
|
52deeb36ee | ||
|
|
0f610f2cf7 | ||
|
|
741e180e2d | ||
|
|
b6a382eeaf | ||
|
|
050350527c | ||
|
|
c9b39e31fc | ||
|
|
28a5e607b4 | ||
|
|
09c50c311c | ||
|
|
252506f8ed | ||
|
|
f4572fe40b | ||
|
|
8c9215489e | ||
|
|
dcd2bfaab7 | ||
|
|
f0e173d9fd | ||
|
|
f4f1b1d0ee | ||
|
|
edc6c4058f | ||
|
|
4233f6ec91 | ||
|
|
fcdc7bdd33 | ||
|
|
86ced3516b | ||
|
|
6943beee66 | ||
|
|
85f094f592 |
@@ -1,6 +1,6 @@
|
||||
[alias]
|
||||
dev = "run --package ruff_dev --bin ruff_dev"
|
||||
benchmark = "bench -p ruff_benchmark --"
|
||||
benchmark = "bench -p ruff_benchmark --bench linter --bench formatter --"
|
||||
|
||||
[target.'cfg(all())']
|
||||
rustflags = [
|
||||
|
||||
46
.devcontainer/devcontainer.json
Normal file
46
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,46 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/rust
|
||||
{
|
||||
"name": "Ruff",
|
||||
"image": "mcr.microsoft.com/devcontainers/rust:0-1-bullseye",
|
||||
"mounts": [
|
||||
{
|
||||
"source": "devcontainer-cargo-cache-${devcontainerId}",
|
||||
"target": "/usr/local/cargo",
|
||||
"type": "volume"
|
||||
}
|
||||
],
|
||||
"customizations": {
|
||||
"codespaces": {
|
||||
"openFiles": [
|
||||
"CONTRIBUTING.md"
|
||||
]
|
||||
},
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"rust-lang.rust-analyzer",
|
||||
"serayuzgur.crates",
|
||||
"tamasfe.even-better-toml",
|
||||
"Swellaby.vscode-rust-test-adapter",
|
||||
"charliermarsh.ruff"
|
||||
],
|
||||
"settings": {
|
||||
"rust-analyzer.updates.askBeforeDownload": false
|
||||
}
|
||||
}
|
||||
},
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/python": {
|
||||
"installTools": false
|
||||
}
|
||||
},
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
"postCreateCommand": ".devcontainer/post-create.sh"
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
||||
8
.devcontainer/post-create.sh
Executable file
8
.devcontainer/post-create.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
rustup default < rust-toolchain
|
||||
rustup component add clippy rustfmt
|
||||
cargo install cargo-insta
|
||||
cargo fetch
|
||||
|
||||
pip install maturin pre-commit
|
||||
@@ -14,4 +14,7 @@ indent_size = 2
|
||||
indent_size = 4
|
||||
|
||||
[*.snap]
|
||||
trim_trailing_whitespace = false
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.md]
|
||||
max_line_length = 100
|
||||
5
.github/release.yml
vendored
5
.github/release.yml
vendored
@@ -1,5 +1,9 @@
|
||||
# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes
|
||||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- internal
|
||||
- documentation
|
||||
categories:
|
||||
- title: Breaking Changes
|
||||
labels:
|
||||
@@ -11,6 +15,7 @@ changelog:
|
||||
- title: Settings
|
||||
labels:
|
||||
- configuration
|
||||
- cli
|
||||
- title: Bug Fixes
|
||||
labels:
|
||||
- bug
|
||||
|
||||
4
.github/workflows/docs.yaml
vendored
4
.github/workflows/docs.yaml
vendored
@@ -1,9 +1,9 @@
|
||||
name: mkdocs
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
jobs:
|
||||
mkdocs:
|
||||
|
||||
2
.github/workflows/flake8-to-ruff.yaml
vendored
2
.github/workflows/flake8-to-ruff.yaml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
- name: "Build wheels - universal2"
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
args: --release --universal2 --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
|
||||
args: --release --target universal2-apple-darwin --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
|
||||
- name: "Install built wheel - universal2"
|
||||
run: |
|
||||
pip install dist/${{ env.CRATE_NAME }}-*universal2.whl --force-reinstall
|
||||
|
||||
4
.github/workflows/playground.yaml
vendored
4
.github/workflows/playground.yaml
vendored
@@ -2,8 +2,8 @@ name: "[Playground] Release"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
20
.github/workflows/release.yaml
vendored
20
.github/workflows/release.yaml
vendored
@@ -94,7 +94,7 @@ jobs:
|
||||
- name: "Build wheels - universal2"
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
args: --release --universal2 --out dist
|
||||
args: --release --target universal2-apple-darwin --out dist
|
||||
- name: "Test wheel - universal2"
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*universal2.whl --force-reinstall
|
||||
@@ -394,18 +394,22 @@ jobs:
|
||||
- musllinux
|
||||
- musllinux-cross
|
||||
if: "startsWith(github.ref, 'refs/tags/')"
|
||||
environment:
|
||||
name: release
|
||||
permissions:
|
||||
# For pypi trusted publishing
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
- uses: actions/setup-python@v4
|
||||
path: wheels
|
||||
- name: "Publish to PyPi"
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.RUFF_TOKEN }}
|
||||
run: |
|
||||
pip install --upgrade twine
|
||||
twine upload --skip-existing *
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
skip-existing: true
|
||||
packages-dir: wheels
|
||||
verbose: true
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: binaries
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@ ruff-old
|
||||
github_search*.jsonl
|
||||
schemastore
|
||||
.venv*
|
||||
scratch.py
|
||||
|
||||
###
|
||||
# Rust.gitignore
|
||||
|
||||
14
.markdownlint.yaml
Normal file
14
.markdownlint.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
# default to true for all rules
|
||||
default: true
|
||||
|
||||
# MD033/no-inline-html
|
||||
MD033: false
|
||||
|
||||
# MD041/first-line-h1
|
||||
MD041: false
|
||||
|
||||
# MD013/line-length
|
||||
MD013:
|
||||
line_length: 100
|
||||
code_blocks: false
|
||||
ignore_code_blocks: true
|
||||
@@ -1,4 +1,12 @@
|
||||
fail_fast: true
|
||||
|
||||
exclude: |
|
||||
(?x)^(
|
||||
crates/ruff/resources/.*|
|
||||
crates/ruff_python_formatter/resources/.*|
|
||||
crates/ruff_python_formatter/src/snapshots/.*
|
||||
)$
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.12.1
|
||||
@@ -17,15 +25,9 @@ repos:
|
||||
rev: v0.33.0
|
||||
hooks:
|
||||
- id: markdownlint-fix
|
||||
args:
|
||||
- --disable
|
||||
- MD013 # line-length
|
||||
- MD033 # no-inline-html
|
||||
- MD041 # first-line-h1
|
||||
- --
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.14.8
|
||||
rev: v1.14.12
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
|
||||
@@ -86,7 +86,8 @@ the intention of adding a stable public API in the future.
|
||||
### `select`, `extend-select`, `ignore`, and `extend-ignore` have new semantics ([#2312](https://github.com/charliermarsh/ruff/pull/2312))
|
||||
|
||||
Previously, the interplay between `select` and its related options could lead to unexpected
|
||||
behavior. For example, `ruff --select E501 --ignore ALL` and `ruff --select E501 --extend-ignore ALL` behaved differently. (See [#2312](https://github.com/charliermarsh/ruff/pull/2312) for more
|
||||
behavior. For example, `ruff --select E501 --ignore ALL` and `ruff --select E501 --extend-ignore ALL`
|
||||
behaved differently. (See [#2312](https://github.com/charliermarsh/ruff/pull/2312) for more
|
||||
examples.)
|
||||
|
||||
When Ruff determines the enabled rule set, it has to reconcile `select` and `ignore` from a variety
|
||||
|
||||
@@ -8,6 +8,7 @@ Welcome! We're happy to have you here. Thank you in advance for your contributio
|
||||
- [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)
|
||||
@@ -93,9 +94,11 @@ At time of writing, the repository includes the following crates:
|
||||
|
||||
- `crates/ruff`: library crate containing all lint rules and the core logic for running them.
|
||||
- `crates/ruff_cli`: binary crate containing Ruff's command-line interface.
|
||||
- `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g., `cargo dev generate-all`).
|
||||
- `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g.,
|
||||
`cargo dev generate-all`).
|
||||
- `crates/ruff_macros`: library crate containing macros used by Ruff.
|
||||
- `crates/ruff_python`: library crate implementing Python-specific functionality (e.g., lists of standard library modules by versionb).
|
||||
- `crates/ruff_python`: library crate implementing Python-specific functionality (e.g., lists of
|
||||
standard library modules by version).
|
||||
- `crates/flake8_to_ruff`: binary crate for generating Ruff configuration from Flake8 configuration.
|
||||
|
||||
### Example: Adding a new lint rule
|
||||
@@ -103,14 +106,20 @@ At time of writing, the repository includes the following crates:
|
||||
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).
|
||||
|
||||
1. Create a file for your rule (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`).
|
||||
|
||||
1. In that file, define a violation struct. You can grep for `#[violation]` to see examples.
|
||||
1. Map the violation struct to a rule code in `crates/ruff/src/registry.rs` (e.g., `E402`).
|
||||
1. Define the logic for triggering the violation in `crates/ruff/src/checkers/ast/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).
|
||||
1. Add a test fixture.
|
||||
|
||||
1. Map the violation struct to a rule code in `crates/ruff/src/codes.rs` (e.g., `E402`).
|
||||
|
||||
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).
|
||||
|
||||
1. Add proper [testing](#rule-testing-fixtures-and-snapshots) for your rule.
|
||||
|
||||
1. Update the generated files (documentation and generated code).
|
||||
|
||||
To define the violation, start by creating a dedicated file for your rule under the appropriate
|
||||
@@ -125,18 +134,8 @@ collecting diagnostics as it goes.
|
||||
If you need to inspect the AST, you can run `cargo dev print-ast` with a Python file. Grep
|
||||
for the `Check::new` invocations to understand how other, similar rules are implemented.
|
||||
|
||||
To add a test fixture, create a file under `crates/ruff/resources/test/fixtures/[linter]`, named to match
|
||||
the code you defined earlier (e.g., `crates/ruff/resources/test/fixtures/pycodestyle/E402.py`). This file should
|
||||
contain a variety of violations and non-violations designed to evaluate and demonstrate the behavior
|
||||
of your lint rule.
|
||||
|
||||
Run `cargo dev generate-all` to generate the code for your new fixture. Then run Ruff
|
||||
locally with (e.g.) `cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402`.
|
||||
|
||||
Once you're satisfied with the output, codify the behavior as a snapshot test by adding a new
|
||||
`test_case` macro in the relevant `crates/ruff/src/rules/[linter]/mod.rs` file. Then, run `cargo test`.
|
||||
Your test will fail, but you'll be prompted to follow-up with `cargo insta review`. Accept the
|
||||
generated snapshot, then commit the snapshot file alongside the rest of your changes.
|
||||
Once you're satisfied with your code, add tests for your rule. See [rule testing](#rule-testing-fixtures-and-snapshots)
|
||||
for more details.
|
||||
|
||||
Finally, regenerate the documentation and generated code with `cargo dev generate-all`.
|
||||
|
||||
@@ -154,6 +153,38 @@ This implies that rule names:
|
||||
When re-implementing rules from other linters, this convention is given more importance than
|
||||
preserving the original rule name.
|
||||
|
||||
#### Rule testing: fixtures and snapshots
|
||||
|
||||
To test rules, Ruff uses snapshots of Ruff's output for a given file (fixture). Generally, there
|
||||
will be one file per rule (e.g., `E402.py`), and each file will contain all necessary examples of
|
||||
both violations and non-violations. `cargo insta review` will generate a snapshot file containing
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
```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`)
|
||||
|
||||
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.
|
||||
|
||||
1. Run `cargo test` again to ensure that your test passes.
|
||||
|
||||
### Example: Adding a new configuration option
|
||||
|
||||
Ruff's user-facing settings live in a few different places.
|
||||
@@ -184,6 +215,8 @@ Finally, regenerate the documentation and generated code with `cargo dev generat
|
||||
|
||||
To preview any changes to the documentation locally:
|
||||
|
||||
1. Install the [Rust toolchain](https://www.rust-lang.org/tools/install).
|
||||
|
||||
1. Install MkDocs and Material for MkDocs with:
|
||||
|
||||
```shell
|
||||
|
||||
386
Cargo.lock
generated
386
Cargo.lock
generated
@@ -14,17 +14,6 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
@@ -43,6 +32,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
@@ -199,9 +194,9 @@ checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
|
||||
checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"once_cell",
|
||||
@@ -211,9 +206,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.12.2"
|
||||
version = "3.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b"
|
||||
checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
|
||||
|
||||
[[package]]
|
||||
name = "cachedir"
|
||||
@@ -253,13 +248,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.24"
|
||||
version = "0.4.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
|
||||
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time",
|
||||
"wasm-bindgen",
|
||||
@@ -295,21 +290,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.25"
|
||||
version = "4.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"clap_lex 0.2.4",
|
||||
"indexmap",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938"
|
||||
checksum = "b4ed2379f8603fa2b7509891660e802b88c70a79a6427a70abb5968054de2c28"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -318,24 +301,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.2.7"
|
||||
version = "4.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd"
|
||||
checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"bitflags 1.3.2",
|
||||
"clap_lex 0.4.1",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.2.3"
|
||||
version = "4.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1594fe2312ec4abf402076e407628f5c313e54c32ade058521df4ee34ecac8a8"
|
||||
checksum = "7f6b5c519bab3ea61843a7923d074b04245624bb84a64a8c150f5deb014e388b"
|
||||
dependencies = [
|
||||
"clap 4.2.7",
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -344,7 +327,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "183495371ea78d4c9ff638bfc6497d46fed2396e4f9c50aebc1278a4a9919a3d"
|
||||
dependencies = [
|
||||
"clap 4.2.7",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"clap_complete_fig",
|
||||
"clap_complete_nushell",
|
||||
@@ -352,50 +335,41 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete_fig"
|
||||
version = "4.2.0"
|
||||
version = "4.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3af28956330989baa428ed4d3471b853715d445c62de21b67292e22cf8a41fa"
|
||||
checksum = "99fee1d30a51305a6c2ed3fc5709be3c8af626c9c958e04dd9ae94e27bcbce9f"
|
||||
dependencies = [
|
||||
"clap 4.2.7",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete_nushell"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fa41f5e6aa83bd151b70fd0ceaee703d68cd669522795dc812df9edad1252c"
|
||||
checksum = "5d02bc8b1a18ee47c4d2eec3fb5ac034dc68ebea6125b1509e9ccdffcddce66e"
|
||||
dependencies = [
|
||||
"clap 4.2.7",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.2.0"
|
||||
version = "4.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4"
|
||||
checksum = "59e9ef9a08ee1c0e1f2e162121665ac45ac3783b0f897db7244ae75ad9a8f65b"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
"syn 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
|
||||
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
|
||||
|
||||
[[package]]
|
||||
name = "clearscreen"
|
||||
@@ -435,14 +409,14 @@ checksum = "5458d9d1a587efaf5091602c59d299696a3877a439c8f6d461a2d3cce11df87a"
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.5"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60"
|
||||
checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"windows-sys 0.42.0",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -471,6 +445,12 @@ version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||
|
||||
[[package]]
|
||||
name = "countme"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
@@ -482,19 +462,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.4.0"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb"
|
||||
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"atty",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap 3.2.25",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"is-terminal",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"oorandom",
|
||||
"plotters",
|
||||
"rayon",
|
||||
@@ -711,10 +691,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.269"
|
||||
version = "0.0.271"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.2.7",
|
||||
"clap",
|
||||
"colored",
|
||||
"configparser",
|
||||
"once_cell",
|
||||
@@ -769,10 +749,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -805,9 +783,6 @@ name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -913,6 +888,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -959,9 +935,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.10"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
|
||||
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.1",
|
||||
"libc",
|
||||
@@ -1010,9 +986,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.62"
|
||||
version = "0.3.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68c16e1bfd491478ab155fd8b4896b86f9ede344949b641e61501e07c2b8b4d5"
|
||||
checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -1127,18 +1103,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
|
||||
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de"
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
@@ -1187,14 +1160,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.6"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
|
||||
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1239,9 +1212,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "5.1.0"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58ea850aa68a06e48fdb069c0ec44d0d64c8dbffa49bf3b6f7f0a901fdea1ba9"
|
||||
checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crossbeam-channel",
|
||||
@@ -1252,7 +1225,7 @@ dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"walkdir",
|
||||
"windows-sys 0.42.0",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1297,9 +1270,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.1"
|
||||
version = "1.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
@@ -1401,6 +1374,22 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pep508_rs"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "969679a29dfdc8278a449f75b3dd45edf57e649bd59f7502429c2840751c46d8"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pep440_rs",
|
||||
"regex",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"unicode-width",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
@@ -1556,13 +1545,26 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.56"
|
||||
version = "1.0.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyproject-toml"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f04dbbb336bd88583943c7cd973a32fed323578243a7569f40cb0c7da673321b"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"pep440_rs",
|
||||
"pep508_rs",
|
||||
"serde",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-junit"
|
||||
version = "0.3.2"
|
||||
@@ -1588,9 +1590,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.27"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
|
||||
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -1663,9 +1665,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.8.1"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
|
||||
checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390"
|
||||
dependencies = [
|
||||
"aho-corasick 1.0.1",
|
||||
"memchr",
|
||||
@@ -1680,9 +1682,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
||||
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
|
||||
|
||||
[[package]]
|
||||
name = "result-like"
|
||||
@@ -1723,13 +1725,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.269"
|
||||
version = "0.0.271"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
"bitflags 2.3.1",
|
||||
"chrono",
|
||||
"clap 4.2.7",
|
||||
"clap",
|
||||
"colored",
|
||||
"dirs 5.0.1",
|
||||
"fern",
|
||||
@@ -1751,17 +1753,20 @@ dependencies = [
|
||||
"pathdiff",
|
||||
"pep440_rs",
|
||||
"pretty_assertions",
|
||||
"pyproject-toml",
|
||||
"quick-junit",
|
||||
"regex",
|
||||
"result-like",
|
||||
"ruff_cache",
|
||||
"ruff_diagnostics",
|
||||
"ruff_macros",
|
||||
"ruff_newlines",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_semantic",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_rustpython",
|
||||
"ruff_text_size",
|
||||
"ruff_textwrap",
|
||||
"rustc-hash",
|
||||
"rustpython-format",
|
||||
"rustpython-parser",
|
||||
@@ -1775,7 +1780,6 @@ dependencies = [
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"test-case",
|
||||
"textwrap",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"typed-arena",
|
||||
@@ -1792,6 +1796,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"ruff",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"rustpython-parser",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1813,7 +1818,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.269"
|
||||
version = "0.0.271"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1824,7 +1829,7 @@ dependencies = [
|
||||
"bitflags 2.3.1",
|
||||
"cachedir",
|
||||
"chrono",
|
||||
"clap 4.2.7",
|
||||
"clap",
|
||||
"clap_complete_command",
|
||||
"clearscreen",
|
||||
"colored",
|
||||
@@ -1842,15 +1847,16 @@ dependencies = [
|
||||
"ruff_cache",
|
||||
"ruff_diagnostics",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_text_size",
|
||||
"ruff_textwrap",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"similar",
|
||||
"strum",
|
||||
"textwrap",
|
||||
"tikv-jemallocator",
|
||||
"ureq",
|
||||
"walkdir",
|
||||
@@ -1862,7 +1868,7 @@ name = "ruff_dev"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.2.7",
|
||||
"clap",
|
||||
"itertools",
|
||||
"libcst",
|
||||
"once_cell",
|
||||
@@ -1871,13 +1877,13 @@ dependencies = [
|
||||
"ruff",
|
||||
"ruff_cli",
|
||||
"ruff_diagnostics",
|
||||
"ruff_textwrap",
|
||||
"rustpython-format",
|
||||
"rustpython-parser",
|
||||
"schemars",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1900,6 +1906,7 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"tracing",
|
||||
"unicode-width",
|
||||
]
|
||||
@@ -1919,8 +1926,16 @@ dependencies = [
|
||||
"itertools",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
"textwrap",
|
||||
"ruff_textwrap",
|
||||
"syn 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_newlines"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"ruff_text_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1929,6 +1944,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.3.1",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"log",
|
||||
@@ -1936,6 +1952,7 @@ dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"ruff_newlines",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"rustpython-ast",
|
||||
@@ -1950,14 +1967,15 @@ name = "ruff_python_formatter"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.2.7",
|
||||
"clap",
|
||||
"countme",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"once_cell",
|
||||
"ruff_formatter",
|
||||
"ruff_newlines",
|
||||
"ruff_python_ast",
|
||||
"ruff_rustpython",
|
||||
"ruff_testing_macros",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
@@ -1973,6 +1991,7 @@ dependencies = [
|
||||
"bitflags 2.3.1",
|
||||
"is-macro",
|
||||
"nohash-hasher",
|
||||
"num-traits",
|
||||
"ruff_index",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_stdlib",
|
||||
@@ -2005,25 +2024,32 @@ dependencies = [
|
||||
"glob",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
"syn 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_text_size"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=335780aeeac1e6fcd85994ba001d7b8ce99fcf65#335780aeeac1e6fcd85994ba001d7b8ce99fcf65"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd#7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_textwrap"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"ruff_newlines",
|
||||
"ruff_text_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"getrandom",
|
||||
"js-sys",
|
||||
"log",
|
||||
"ruff",
|
||||
@@ -2082,7 +2108,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=335780aeeac1e6fcd85994ba001d7b8ce99fcf65#335780aeeac1e6fcd85994ba001d7b8ce99fcf65"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd#7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd"
|
||||
dependencies = [
|
||||
"is-macro",
|
||||
"num-bigint",
|
||||
@@ -2093,7 +2119,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-format"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=335780aeeac1e6fcd85994ba001d7b8ce99fcf65#335780aeeac1e6fcd85994ba001d7b8ce99fcf65"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd#7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd"
|
||||
dependencies = [
|
||||
"bitflags 2.3.1",
|
||||
"itertools",
|
||||
@@ -2105,7 +2131,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-literal"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=335780aeeac1e6fcd85994ba001d7b8ce99fcf65#335780aeeac1e6fcd85994ba001d7b8ce99fcf65"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd#7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd"
|
||||
dependencies = [
|
||||
"hexf-parse",
|
||||
"is-macro",
|
||||
@@ -2117,7 +2143,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=335780aeeac1e6fcd85994ba001d7b8ce99fcf65#335780aeeac1e6fcd85994ba001d7b8ce99fcf65"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd#7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"is-macro",
|
||||
@@ -2140,7 +2166,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=335780aeeac1e6fcd85994ba001d7b8ce99fcf65#335780aeeac1e6fcd85994ba001d7b8ce99fcf65"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd#7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd"
|
||||
dependencies = [
|
||||
"is-macro",
|
||||
"ruff_text_size",
|
||||
@@ -2247,7 +2273,7 @@ checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
"syn 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2275,9 +2301,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.1"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
|
||||
checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -2309,12 +2335,6 @@ version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
@@ -2368,9 +2388,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.15"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
||||
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2453,17 +2473,6 @@ dependencies = [
|
||||
"test-case-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
|
||||
dependencies = [
|
||||
"smawk",
|
||||
"unicode-linebreak",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.40"
|
||||
@@ -2481,7 +2490,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
"syn 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2561,9 +2570,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
|
||||
checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@@ -2573,18 +2582,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.1"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
|
||||
checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.8"
|
||||
version = "0.19.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
|
||||
checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -2600,6 +2609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
@@ -2613,7 +2623,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
"syn 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2703,19 +2713,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-linebreak"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"regex",
|
||||
]
|
||||
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
@@ -2771,6 +2771,7 @@ dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2781,9 +2782,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.3.2"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2"
|
||||
checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
@@ -2824,9 +2825,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.85"
|
||||
version = "0.2.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b6cb788c4e39112fbe1822277ef6fb3c55cd86b95cb3d3c4c1c9597e4ac74b4"
|
||||
checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
@@ -2834,24 +2835,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.85"
|
||||
version = "0.2.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35e522ed4105a9d626d885b35d62501b30d9666283a5c8be12c14a8bdafe7822"
|
||||
checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
"syn 2.0.18",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.35"
|
||||
version = "0.4.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "083abe15c5d88556b77bdf7aef403625be9e327ad37c62c4e4129af740168163"
|
||||
checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -2861,9 +2862,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.85"
|
||||
version = "0.2.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "358a79a0cb89d21db8120cbfb91392335913e4890665b1a7981d9e956903b434"
|
||||
checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -2871,28 +2872,28 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.85"
|
||||
version = "0.2.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869"
|
||||
checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
"syn 2.0.18",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.85"
|
||||
version = "0.2.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a901d592cafaa4d711bc324edfaff879ac700b19c3dfd60058d2b445be2691eb"
|
||||
checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.3.35"
|
||||
version = "0.3.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b27e15b4a3030b9944370ba1d8cec6f21f66a1ad4fd14725c5685600460713ec"
|
||||
checksum = "c9e636f3a428ff62b3742ebc3c70e254dfe12b8c2b469d688ea59cdd4abcf502"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
@@ -2904,9 +2905,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.3.35"
|
||||
version = "0.3.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dbaa9b9a574eac00c4f3a9c4941ac051f07632ecd0484a8588abd95af6b99d2"
|
||||
checksum = "f18c1fad2f7c4958e7bcce014fa212f59a65d5e3721d0f77e6c0b27ede936ba3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2914,9 +2915,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.62"
|
||||
version = "0.3.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16b5f940c7edfdc6d12126d98c9ef4d1b3d470011c47c76a6581df47ad9ba721"
|
||||
checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -3001,21 +3002,6 @@ dependencies = [
|
||||
"windows-targets 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
|
||||
16
Cargo.toml
16
Cargo.toml
@@ -3,7 +3,7 @@ members = ["crates/*"]
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
rust-version = "1.69"
|
||||
rust-version = "1.70"
|
||||
homepage = "https://beta.ruff.rs/docs/"
|
||||
documentation = "https://beta.ruff.rs/docs/"
|
||||
repository = "https://github.com/charliermarsh/ruff"
|
||||
@@ -24,18 +24,21 @@ is-macro = { version = "0.2.2" }
|
||||
itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
|
||||
log = { version = "0.4.17" }
|
||||
memchr = "2.5.0"
|
||||
nohash-hasher = { version = "0.2.0" }
|
||||
num-bigint = { version = "0.4.3" }
|
||||
num-traits = { version = "0.2.15" }
|
||||
once_cell = { version = "1.17.1" }
|
||||
path-absolutize = { version = "3.0.14" }
|
||||
proc-macro2 = { version = "1.0.51" }
|
||||
quote = { version = "1.0.23" }
|
||||
regex = { version = "1.7.1" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "335780aeeac1e6fcd85994ba001d7b8ce99fcf65" }
|
||||
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "335780aeeac1e6fcd85994ba001d7b8ce99fcf65", default-features = false, features = ["all-nodes-with-ranges"]}
|
||||
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "335780aeeac1e6fcd85994ba001d7b8ce99fcf65" }
|
||||
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "335780aeeac1e6fcd85994ba001d7b8ce99fcf65" }
|
||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "335780aeeac1e6fcd85994ba001d7b8ce99fcf65", default-features = false, features = ["full-lexer", "all-nodes-with-ranges"] }
|
||||
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" }
|
||||
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd", default-features = false, features = ["all-nodes-with-ranges"]}
|
||||
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" }
|
||||
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" }
|
||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd", default-features = false, features = ["full-lexer", "all-nodes-with-ranges"] }
|
||||
schemars = { version = "0.8.12" }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = { version = "1.0.93", features = ["preserve_order"] }
|
||||
@@ -46,7 +49,6 @@ strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
syn = { version = "2.0.15" }
|
||||
test-case = { version = "3.0.0" }
|
||||
textwrap = { version = "0.16.0" }
|
||||
toml = { version = "0.7.2" }
|
||||
|
||||
[profile.release]
|
||||
|
||||
41
README.md
41
README.md
@@ -24,17 +24,18 @@ An extremely fast Python linter, written in Rust.
|
||||
<i>Linting the CPython codebase from scratch.</i>
|
||||
</p>
|
||||
|
||||
- ⚡️ 10-100x faster than existing linters
|
||||
- 🐍 Installable via `pip`
|
||||
- 🛠️ `pyproject.toml` support
|
||||
- 🤝 Python 3.11 compatibility
|
||||
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
||||
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
- 📏 Over [500 built-in rules](https://beta.ruff.rs/docs/rules/)
|
||||
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
|
||||
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
|
||||
- ⌨️ First-party editor integrations for [VS Code](https://github.com/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)
|
||||
- ⚡️ 10-100x faster than existing linters
|
||||
- 🐍 Installable via `pip`
|
||||
- 🛠️ `pyproject.toml` support
|
||||
- 🤝 Python 3.11 compatibility
|
||||
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
||||
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
- 📏 Over [500 built-in rules](https://beta.ruff.rs/docs/rules/)
|
||||
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the
|
||||
built-in Flake8 rule set
|
||||
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
|
||||
- ⌨️ First-party editor integrations for [VS Code](https://github.com/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
|
||||
functionality behind a single, common interface.
|
||||
@@ -84,7 +85,8 @@ of [Conda](https://docs.conda.io/en/latest/):
|
||||
[**Timothy Crosley**](https://twitter.com/timothycrosley/status/1606420868514877440),
|
||||
creator of [isort](https://github.com/PyCQA/isort):
|
||||
|
||||
> Just switched my first project to Ruff. Only one downside so far: it's so fast I couldn't believe it was working till I intentionally introduced some errors.
|
||||
> Just switched my first project to Ruff. Only one downside so far: it's so fast I couldn't believe
|
||||
> it was working till I intentionally introduced some errors.
|
||||
|
||||
[**Tim Abbott**](https://github.com/charliermarsh/ruff/issues/465#issuecomment-1317400028), lead
|
||||
developer of [Zulip](https://github.com/zulip/zulip):
|
||||
@@ -137,7 +139,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.269'
|
||||
rev: v0.0.271
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -242,6 +244,8 @@ stylistic rules made obsolete by the use of an autoformatter, like
|
||||
If you're just getting started with Ruff, **the default rule set is a great place to start**: it
|
||||
catches a wide variety of common errors (like unused imports) with zero configuration.
|
||||
|
||||
<!-- End section: Rules -->
|
||||
|
||||
Beyond the defaults, Ruff re-implements some of the most popular Flake8 plugins and related code
|
||||
quality tools, including:
|
||||
|
||||
@@ -291,12 +295,11 @@ quality tools, including:
|
||||
- [pep8-naming](https://pypi.org/project/pep8-naming/)
|
||||
- [pydocstyle](https://pypi.org/project/pydocstyle/)
|
||||
- [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks)
|
||||
- [pylint-airflow](https://pypi.org/project/pylint-airflow/)
|
||||
- [pyupgrade](https://pypi.org/project/pyupgrade/)
|
||||
- [tryceratops](https://pypi.org/project/tryceratops/)
|
||||
- [yesqa](https://pypi.org/project/yesqa/)
|
||||
|
||||
<!-- End section: Rules -->
|
||||
|
||||
For a complete enumeration of the supported rules, see [_Rules_](https://beta.ruff.rs/docs/rules/).
|
||||
|
||||
## Contributing
|
||||
@@ -352,7 +355,9 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [FastAPI](https://github.com/tiangolo/fastapi)
|
||||
- [Gradio](https://github.com/gradio-app/gradio)
|
||||
- [Great Expectations](https://github.com/great-expectations/great_expectations)
|
||||
- Hugging Face ([Transformers](https://github.com/huggingface/transformers), [Datasets](https://github.com/huggingface/datasets), [Diffusers](https://github.com/huggingface/diffusers))
|
||||
- Hugging Face ([Transformers](https://github.com/huggingface/transformers),
|
||||
[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)
|
||||
- [Ibis](https://github.com/ibis-project/ibis)
|
||||
@@ -364,7 +369,9 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- Modern Treasury ([Python SDK](https://github.com/Modern-Treasury/modern-treasury-python-sdk))
|
||||
- Mozilla ([Firefox](https://github.com/mozilla/gecko-dev))
|
||||
- [MegaLinter](https://github.com/oxsecurity/megalinter)
|
||||
- Microsoft ([Semantic Kernel](https://github.com/microsoft/semantic-kernel), [ONNX Runtime](https://github.com/microsoft/onnxruntime), [LightGBM](https://github.com/microsoft/LightGBM))
|
||||
- Microsoft ([Semantic Kernel](https://github.com/microsoft/semantic-kernel),
|
||||
[ONNX Runtime](https://github.com/microsoft/onnxruntime),
|
||||
[LightGBM](https://github.com/microsoft/LightGBM))
|
||||
- Netflix ([Dispatch](https://github.com/Netflix/dispatch))
|
||||
- [Neon](https://github.com/neondatabase/neon)
|
||||
- [ONNX](https://github.com/onnx/onnx)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[files]
|
||||
extend-exclude = ["snapshots", "black"]
|
||||
extend-exclude = ["resources", "snapshots"]
|
||||
|
||||
[default.extend-words]
|
||||
trivias = "trivias"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.269"
|
||||
version = "0.0.271"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.269"
|
||||
version = "0.0.271"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
@@ -17,11 +17,13 @@ name = "ruff"
|
||||
ruff_cache = { path = "../ruff_cache" }
|
||||
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
|
||||
ruff_macros = { path = "../ruff_macros" }
|
||||
ruff_newlines = { path = "../ruff_newlines" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] }
|
||||
ruff_python_semantic = { path = "../ruff_python_semantic" }
|
||||
ruff_python_stdlib = { path = "../ruff_python_stdlib" }
|
||||
ruff_rustpython = { path = "../ruff_rustpython" }
|
||||
ruff_text_size = { workspace = true }
|
||||
ruff_textwrap = { path = "../ruff_textwrap" }
|
||||
|
||||
annotate-snippets = { version = "0.9.1", features = ["color"] }
|
||||
anyhow = { workspace = true }
|
||||
@@ -41,8 +43,8 @@ libcst = { workspace = true }
|
||||
log = { workspace = true }
|
||||
natord = { version = "1.0.9" }
|
||||
nohash-hasher = { workspace = true }
|
||||
num-bigint = { version = "0.4.3" }
|
||||
num-traits = { version = "0.2.15" }
|
||||
num-bigint = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
path-absolutize = { workspace = true, features = [
|
||||
"once_cell_cache",
|
||||
@@ -50,6 +52,7 @@ path-absolutize = { workspace = true, features = [
|
||||
] }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.3.1", features = ["serde"] }
|
||||
pyproject-toml = { version = "0.6.0" }
|
||||
quick-junit = { version = "0.3.2" }
|
||||
regex = { workspace = true }
|
||||
result-like = { version = "0.4.6" }
|
||||
@@ -65,7 +68,6 @@ shellexpand = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
textwrap = { workspace = true }
|
||||
thiserror = { version = "1.0.38" }
|
||||
toml = { workspace = true }
|
||||
typed-arena = { version = "2.0.2" }
|
||||
|
||||
16
crates/ruff/resources/test/fixtures/airflow/AIR001.py
vendored
Normal file
16
crates/ruff/resources/test/fixtures/airflow/AIR001.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
from airflow.operators import PythonOperator
|
||||
|
||||
|
||||
def my_callable():
|
||||
pass
|
||||
|
||||
|
||||
my_task = PythonOperator(task_id="my_task", callable=my_callable)
|
||||
my_task_2 = PythonOperator(callable=my_callable, task_id="my_task_2")
|
||||
|
||||
incorrect_name = PythonOperator(task_id="my_task")
|
||||
incorrect_name_2 = PythonOperator(callable=my_callable, task_id="my_task_2")
|
||||
|
||||
from my_module import MyClass
|
||||
|
||||
incorrect_name = MyClass(task_id="my_task")
|
||||
8
crates/ruff/resources/test/fixtures/flake8_bandit/S609.py
vendored
Normal file
8
crates/ruff/resources/test/fixtures/flake8_bandit/S609.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
os.popen("chmod +w foo*")
|
||||
subprocess.Popen("/bin/chown root: *", shell=True)
|
||||
subprocess.Popen(["/usr/local/bin/rsync", "*", "some_where:"], shell=True)
|
||||
subprocess.Popen("/usr/local/bin/rsync * no_injection_here:")
|
||||
os.system("tar cf foo.tar bar/*")
|
||||
@@ -57,12 +57,16 @@ dict.fromkeys(("world",), True)
|
||||
{}.deploy(True, False)
|
||||
getattr(someobj, attrname, False)
|
||||
mylist.index(True)
|
||||
bool(False)
|
||||
int(True)
|
||||
str(int(False))
|
||||
cfg.get("hello", True)
|
||||
cfg.getint("hello", True)
|
||||
cfg.getfloat("hello", True)
|
||||
cfg.getboolean("hello", True)
|
||||
os.set_blocking(0, False)
|
||||
g_action.set_enabled(True)
|
||||
settings.set_enable_developer_extras(True)
|
||||
|
||||
|
||||
class Registry:
|
||||
@@ -80,3 +84,6 @@ class Registry:
|
||||
# FBT001: Boolean positional arg in function definition
|
||||
def foo(self, value: bool) -> None:
|
||||
pass
|
||||
|
||||
def foo(self) -> None:
|
||||
object.__setattr__(self, "flag", True)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import collections
|
||||
import datetime as dt
|
||||
from decimal import Decimal
|
||||
from fractions import Fraction
|
||||
import logging
|
||||
import operator
|
||||
from pathlib import Path
|
||||
@@ -158,12 +159,37 @@ def float_infinity_literal(value=float("1e999")):
|
||||
pass
|
||||
|
||||
|
||||
# But don't allow standard floats
|
||||
def float_int_is_wrong(value=float(3)):
|
||||
# Allow standard floats
|
||||
def float_int_okay(value=float(3)):
|
||||
pass
|
||||
|
||||
|
||||
def float_str_not_inf_or_nan_is_wrong(value=float("3.14")):
|
||||
def float_str_not_inf_or_nan_okay(value=float("3.14")):
|
||||
pass
|
||||
|
||||
|
||||
# Allow immutable str() value
|
||||
def str_okay(value=str("foo")):
|
||||
pass
|
||||
|
||||
|
||||
# Allow immutable bool() value
|
||||
def bool_okay(value=bool("bar")):
|
||||
pass
|
||||
|
||||
|
||||
# Allow immutable int() value
|
||||
def int_okay(value=int("12")):
|
||||
pass
|
||||
|
||||
|
||||
# Allow immutable complex() value
|
||||
def complex_okay(value=complex(1,2)):
|
||||
pass
|
||||
|
||||
|
||||
# Allow immutable Fraction() value
|
||||
def fraction_okay(value=Fraction(1,2)):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -120,3 +120,11 @@ class AbstractClass(ABC):
|
||||
@abstractmethod
|
||||
def empty_1(self, foo: Union[str, int, list, float]):
|
||||
...
|
||||
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Foo(ABC): # noqa: B024
|
||||
...
|
||||
|
||||
@@ -9,6 +9,10 @@ def f_a_short():
|
||||
raise RuntimeError("Error")
|
||||
|
||||
|
||||
def f_a_empty():
|
||||
raise RuntimeError("")
|
||||
|
||||
|
||||
def f_b():
|
||||
example = "example"
|
||||
raise RuntimeError(f"This is an {example} exception")
|
||||
|
||||
8
crates/ruff/resources/test/fixtures/flake8_fixme/T00.py
vendored
Normal file
8
crates/ruff/resources/test/fixtures/flake8_fixme/T00.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# TODO: todo
|
||||
# todo: todo
|
||||
# XXX: xxx
|
||||
# xxx: xxx
|
||||
# HACK: hack
|
||||
# hack: hack
|
||||
# FIXME: fixme
|
||||
# fixme: fixme
|
||||
9
crates/ruff/resources/test/fixtures/flake8_pyi/PYI024.py
vendored
Normal file
9
crates/ruff/resources/test/fixtures/flake8_pyi/PYI024.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import collections
|
||||
|
||||
person: collections.namedtuple # OK
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
person: namedtuple # OK
|
||||
|
||||
person = namedtuple("Person", ["name", "age"]) # OK
|
||||
11
crates/ruff/resources/test/fixtures/flake8_pyi/PYI024.pyi
vendored
Normal file
11
crates/ruff/resources/test/fixtures/flake8_pyi/PYI024.pyi
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import collections
|
||||
|
||||
person: collections.namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
person: namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
|
||||
|
||||
person = namedtuple(
|
||||
"Person", ["name", "age"]
|
||||
) # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
|
||||
19
crates/ruff/resources/test/fixtures/flake8_pyi/PYI025.py
vendored
Normal file
19
crates/ruff/resources/test/fixtures/flake8_pyi/PYI025.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
from collections.abc import Set as AbstractSet # Ok
|
||||
|
||||
|
||||
from collections.abc import Set # Ok
|
||||
|
||||
|
||||
from collections.abc import (
|
||||
Container,
|
||||
Sized,
|
||||
Set, # Ok
|
||||
ValuesView
|
||||
)
|
||||
|
||||
from collections.abc import (
|
||||
Container,
|
||||
Sized,
|
||||
Set as AbstractSet, # Ok
|
||||
ValuesView
|
||||
)
|
||||
19
crates/ruff/resources/test/fixtures/flake8_pyi/PYI025.pyi
vendored
Normal file
19
crates/ruff/resources/test/fixtures/flake8_pyi/PYI025.pyi
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
from collections.abc import Set as AbstractSet # Ok
|
||||
|
||||
|
||||
from collections.abc import Set # PYI025
|
||||
|
||||
|
||||
from collections.abc import (
|
||||
Container,
|
||||
Sized,
|
||||
Set, # PYI025
|
||||
ValuesView
|
||||
)
|
||||
|
||||
from collections.abc import (
|
||||
Container,
|
||||
Sized,
|
||||
Set as AbstractSet,
|
||||
ValuesView # Ok
|
||||
)
|
||||
57
crates/ruff/resources/test/fixtures/flake8_pyi/PYI029.py
vendored
Normal file
57
crates/ruff/resources/test/fixtures/flake8_pyi/PYI029.py
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
import builtins
|
||||
from abc import abstractmethod
|
||||
|
||||
|
||||
def __repr__(self) -> str:
|
||||
...
|
||||
|
||||
|
||||
def __str__(self) -> builtins.str:
|
||||
...
|
||||
|
||||
|
||||
def __repr__(self, /, foo) -> str:
|
||||
...
|
||||
|
||||
|
||||
def __repr__(self, *, foo) -> str:
|
||||
...
|
||||
|
||||
|
||||
class ShouldRemoveSingle:
|
||||
def __str__(self) -> builtins.str:
|
||||
...
|
||||
|
||||
|
||||
class ShouldRemove:
|
||||
def __repr__(self) -> str:
|
||||
...
|
||||
|
||||
def __str__(self) -> builtins.str:
|
||||
...
|
||||
|
||||
|
||||
class NoReturnSpecified:
|
||||
def __str__(self):
|
||||
...
|
||||
|
||||
def __repr__(self):
|
||||
...
|
||||
|
||||
|
||||
class NonMatchingArgs:
|
||||
def __str__(self, *, extra) -> builtins.str:
|
||||
...
|
||||
|
||||
def __repr__(self, /, extra) -> str:
|
||||
...
|
||||
|
||||
|
||||
class MatchingArgsButAbstract:
|
||||
@abstractmethod
|
||||
def __str__(self) -> builtins.str:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def __repr__(self) -> str:
|
||||
...
|
||||
28
crates/ruff/resources/test/fixtures/flake8_pyi/PYI029.pyi
vendored
Normal file
28
crates/ruff/resources/test/fixtures/flake8_pyi/PYI029.pyi
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import builtins
|
||||
from abc import abstractmethod
|
||||
|
||||
def __repr__(self) -> str: ...
|
||||
def __str__(self) -> builtins.str: ...
|
||||
def __repr__(self, /, foo) -> str: ...
|
||||
def __repr__(self, *, foo) -> str: ...
|
||||
|
||||
class ShouldRemoveSingle:
|
||||
def __str__(self) -> builtins.str: ... # Error: PYI029
|
||||
|
||||
class ShouldRemove:
|
||||
def __repr__(self) -> str: ... # Error: PYI029
|
||||
def __str__(self) -> builtins.str: ... # Error: PYI029
|
||||
|
||||
class NoReturnSpecified:
|
||||
def __str__(self): ...
|
||||
def __repr__(self): ...
|
||||
|
||||
class NonMatchingArgs:
|
||||
def __str__(self, *, extra) -> builtins.str: ...
|
||||
def __repr__(self, /, extra) -> str: ...
|
||||
|
||||
class MatchingArgsButAbstract:
|
||||
@abstractmethod
|
||||
def __str__(self) -> builtins.str: ...
|
||||
@abstractmethod
|
||||
def __repr__(self) -> str: ...
|
||||
24
crates/ruff/resources/test/fixtures/flake8_pyi/PYI032.py
vendored
Normal file
24
crates/ruff/resources/test/fixtures/flake8_pyi/PYI032.py
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
from typing import Any
|
||||
import typing
|
||||
|
||||
|
||||
class Bad:
|
||||
def __eq__(self, other: Any) -> bool: ... # Fine because not a stub file
|
||||
def __ne__(self, other: typing.Any) -> typing.Any: ... # Fine because not a stub file
|
||||
|
||||
|
||||
class Good:
|
||||
def __eq__(self, other: object) -> bool: ...
|
||||
|
||||
def __ne__(self, obj: object) -> int: ...
|
||||
|
||||
|
||||
class WeirdButFine:
|
||||
def __eq__(self, other: Any, strange_extra_arg: list[str]) -> Any: ...
|
||||
def __ne__(self, *, kw_only_other: Any) -> bool: ...
|
||||
|
||||
|
||||
class Unannotated:
|
||||
def __eq__(self) -> Any: ...
|
||||
def __ne__(self) -> bool: ...
|
||||
|
||||
24
crates/ruff/resources/test/fixtures/flake8_pyi/PYI032.pyi
vendored
Normal file
24
crates/ruff/resources/test/fixtures/flake8_pyi/PYI032.pyi
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
from typing import Any
|
||||
import typing
|
||||
|
||||
|
||||
class Bad:
|
||||
def __eq__(self, other: Any) -> bool: ... # Y032
|
||||
def __ne__(self, other: typing.Any) -> typing.Any: ... # Y032
|
||||
|
||||
|
||||
class Good:
|
||||
def __eq__(self, other: object) -> bool: ...
|
||||
|
||||
def __ne__(self, obj: object) -> int: ...
|
||||
|
||||
|
||||
class WeirdButFine:
|
||||
def __eq__(self, other: Any, strange_extra_arg: list[str]) -> Any: ...
|
||||
def __ne__(self, *, kw_only_other: Any) -> bool: ...
|
||||
|
||||
|
||||
class Unannotated:
|
||||
def __eq__(self) -> Any: ...
|
||||
def __ne__(self) -> bool: ...
|
||||
|
||||
280
crates/ruff/resources/test/fixtures/flake8_pyi/PYI034.py
vendored
Normal file
280
crates/ruff/resources/test/fixtures/flake8_pyi/PYI034.py
vendored
Normal file
@@ -0,0 +1,280 @@
|
||||
# flags: --extend-ignore=Y023
|
||||
|
||||
import abc
|
||||
import builtins
|
||||
import collections.abc
|
||||
import typing
|
||||
from abc import abstractmethod
|
||||
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator
|
||||
from typing import Any, overload
|
||||
|
||||
import typing_extensions
|
||||
from _typeshed import Self
|
||||
from typing_extensions import final
|
||||
|
||||
|
||||
class Bad(
|
||||
object
|
||||
): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> Bad:
|
||||
... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
|
||||
|
||||
def __repr__(self) -> str:
|
||||
... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
|
||||
|
||||
def __str__(self) -> builtins.str:
|
||||
... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
... # Y032 Prefer "object" to "Any" for the second parameter in "__eq__" methods
|
||||
|
||||
def __ne__(self, other: typing.Any) -> typing.Any:
|
||||
... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
|
||||
|
||||
def __enter__(self) -> Bad:
|
||||
... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
|
||||
|
||||
async def __aenter__(self) -> Bad:
|
||||
... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
||||
|
||||
def __iadd__(self, other: Bad) -> Bad:
|
||||
... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
|
||||
|
||||
|
||||
class AlsoBad(int, builtins.object):
|
||||
... # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
|
||||
|
||||
|
||||
class Good:
|
||||
def __new__(cls: type[Self], *args: Any, **kwargs: Any) -> Self:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def __str__(self) -> str:
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def __repr__(self) -> str:
|
||||
...
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
...
|
||||
|
||||
def __ne__(self, obj: object) -> int:
|
||||
...
|
||||
|
||||
def __enter__(self: Self) -> Self:
|
||||
...
|
||||
|
||||
async def __aenter__(self: Self) -> Self:
|
||||
...
|
||||
|
||||
def __ior__(self: Self, other: Self) -> Self:
|
||||
...
|
||||
|
||||
|
||||
class Fine:
|
||||
@overload
|
||||
def __new__(cls, foo: int) -> FineSubclass:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> Fine:
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def __str__(self) -> str:
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def __repr__(self) -> str:
|
||||
...
|
||||
|
||||
def __eq__(self, other: Any, strange_extra_arg: list[str]) -> Any:
|
||||
...
|
||||
|
||||
def __ne__(self, *, kw_only_other: Any) -> bool:
|
||||
...
|
||||
|
||||
def __enter__(self) -> None:
|
||||
...
|
||||
|
||||
async def __aenter__(self) -> bool:
|
||||
...
|
||||
|
||||
|
||||
class FineSubclass(Fine):
|
||||
...
|
||||
|
||||
|
||||
class StrangeButAcceptable(str):
|
||||
@typing_extensions.overload
|
||||
def __new__(cls, foo: int) -> StrangeButAcceptableSubclass:
|
||||
...
|
||||
|
||||
@typing_extensions.overload
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> StrangeButAcceptable:
|
||||
...
|
||||
|
||||
def __str__(self) -> StrangeButAcceptable:
|
||||
...
|
||||
|
||||
def __repr__(self) -> StrangeButAcceptable:
|
||||
...
|
||||
|
||||
|
||||
class StrangeButAcceptableSubclass(StrangeButAcceptable):
|
||||
...
|
||||
|
||||
|
||||
class FineAndDandy:
|
||||
def __str__(self, weird_extra_arg) -> str:
|
||||
...
|
||||
|
||||
def __repr__(self, weird_extra_arg_with_default=...) -> str:
|
||||
...
|
||||
|
||||
|
||||
@final
|
||||
class WillNotBeSubclassed:
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> WillNotBeSubclassed:
|
||||
...
|
||||
|
||||
def __enter__(self) -> WillNotBeSubclassed:
|
||||
...
|
||||
|
||||
async def __aenter__(self) -> WillNotBeSubclassed:
|
||||
...
|
||||
|
||||
|
||||
# we don't emit an error for these; out of scope for a linter
|
||||
class InvalidButPluginDoesNotCrash:
|
||||
def __new__() -> InvalidButPluginDoesNotCrash:
|
||||
...
|
||||
|
||||
def __enter__() -> InvalidButPluginDoesNotCrash:
|
||||
...
|
||||
|
||||
async def __aenter__() -> InvalidButPluginDoesNotCrash:
|
||||
...
|
||||
|
||||
|
||||
class BadIterator1(Iterator[int]):
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
|
||||
|
||||
class BadIterator2(
|
||||
typing.Iterator[int]
|
||||
): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
|
||||
|
||||
class BadIterator3(
|
||||
typing.Iterator[int]
|
||||
): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
|
||||
def __iter__(self) -> collections.abc.Iterator[int]:
|
||||
... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
|
||||
|
||||
class BadIterator4(Iterator[int]):
|
||||
# Note: *Iterable*, not *Iterator*, returned!
|
||||
def __iter__(self) -> Iterable[int]:
|
||||
... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
|
||||
|
||||
class IteratorReturningIterable:
|
||||
def __iter__(self) -> Iterable[str]:
|
||||
... # Y045 "__iter__" methods should return an Iterator, not an Iterable
|
||||
|
||||
|
||||
class BadAsyncIterator(collections.abc.AsyncIterator[str]):
|
||||
def __aiter__(self) -> typing.AsyncIterator[str]:
|
||||
... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
|
||||
|
||||
|
||||
class AsyncIteratorReturningAsyncIterable:
|
||||
def __aiter__(self) -> AsyncIterable[str]:
|
||||
... # Y045 "__aiter__" methods should return an AsyncIterator, not an AsyncIterable
|
||||
|
||||
|
||||
class Abstract(Iterator[str]):
|
||||
@abstractmethod
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def __enter__(self) -> Abstract:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def __aenter__(self) -> Abstract:
|
||||
...
|
||||
|
||||
|
||||
class GoodIterator(Iterator[str]):
|
||||
def __iter__(self: Self) -> Self:
|
||||
...
|
||||
|
||||
|
||||
class GoodAsyncIterator(AsyncIterator[int]):
|
||||
def __aiter__(self: Self) -> Self:
|
||||
...
|
||||
|
||||
|
||||
class DoesNotInheritFromIterator:
|
||||
def __iter__(self) -> DoesNotInheritFromIterator:
|
||||
...
|
||||
|
||||
|
||||
class Unannotated:
|
||||
def __new__(cls, *args, **kwargs):
|
||||
...
|
||||
|
||||
def __iter__(self):
|
||||
...
|
||||
|
||||
def __aiter__(self):
|
||||
...
|
||||
|
||||
async def __aenter__(self):
|
||||
...
|
||||
|
||||
def __repr__(self):
|
||||
...
|
||||
|
||||
def __str__(self):
|
||||
...
|
||||
|
||||
def __eq__(self):
|
||||
...
|
||||
|
||||
def __ne__(self):
|
||||
...
|
||||
|
||||
def __iadd__(self):
|
||||
...
|
||||
|
||||
def __ior__(self):
|
||||
...
|
||||
|
||||
|
||||
def __repr__(self) -> str:
|
||||
...
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
...
|
||||
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
...
|
||||
|
||||
|
||||
def __ne__(self, other: Any) -> bool:
|
||||
...
|
||||
|
||||
|
||||
def __imul__(self, other: Any) -> list[str]:
|
||||
...
|
||||
188
crates/ruff/resources/test/fixtures/flake8_pyi/PYI034.pyi
vendored
Normal file
188
crates/ruff/resources/test/fixtures/flake8_pyi/PYI034.pyi
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
# flags: --extend-ignore=Y023
|
||||
|
||||
import abc
|
||||
import builtins
|
||||
import collections.abc
|
||||
import typing
|
||||
from abc import abstractmethod
|
||||
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator
|
||||
from typing import Any, overload
|
||||
|
||||
import typing_extensions
|
||||
from _typeshed import Self
|
||||
from typing_extensions import final
|
||||
|
||||
class Bad(
|
||||
object
|
||||
): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
|
||||
def __new__(
|
||||
cls, *args: Any, **kwargs: Any
|
||||
) -> Bad: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
|
||||
def __repr__(
|
||||
self,
|
||||
) -> str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
|
||||
def __str__(
|
||||
self,
|
||||
) -> builtins.str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
|
||||
def __eq__(
|
||||
self, other: Any
|
||||
) -> bool: ... # Y032 Prefer "object" to "Any" for the second parameter in "__eq__" methods
|
||||
def __ne__(
|
||||
self, other: typing.Any
|
||||
) -> typing.Any: ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
|
||||
def __enter__(
|
||||
self,
|
||||
) -> Bad: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
|
||||
async def __aenter__(
|
||||
self,
|
||||
) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
||||
def __iadd__(
|
||||
self, other: Bad
|
||||
) -> Bad: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
|
||||
|
||||
class AlsoBad(
|
||||
int, builtins.object
|
||||
): ... # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
|
||||
|
||||
class Good:
|
||||
def __new__(cls: type[Self], *args: Any, **kwargs: Any) -> Self: ...
|
||||
@abstractmethod
|
||||
def __str__(self) -> str: ...
|
||||
@abc.abstractmethod
|
||||
def __repr__(self) -> str: ...
|
||||
def __eq__(self, other: object) -> bool: ...
|
||||
def __ne__(self, obj: object) -> int: ...
|
||||
def __enter__(self: Self) -> Self: ...
|
||||
async def __aenter__(self: Self) -> Self: ...
|
||||
def __ior__(self: Self, other: Self) -> Self: ...
|
||||
|
||||
class Fine:
|
||||
@overload
|
||||
def __new__(cls, foo: int) -> FineSubclass: ...
|
||||
@overload
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> Fine: ...
|
||||
@abc.abstractmethod
|
||||
def __str__(self) -> str: ...
|
||||
@abc.abstractmethod
|
||||
def __repr__(self) -> str: ...
|
||||
def __eq__(self, other: Any, strange_extra_arg: list[str]) -> Any: ...
|
||||
def __ne__(self, *, kw_only_other: Any) -> bool: ...
|
||||
def __enter__(self) -> None: ...
|
||||
async def __aenter__(self) -> bool: ...
|
||||
|
||||
class FineSubclass(Fine): ...
|
||||
|
||||
class StrangeButAcceptable(str):
|
||||
@typing_extensions.overload
|
||||
def __new__(cls, foo: int) -> StrangeButAcceptableSubclass: ...
|
||||
@typing_extensions.overload
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> StrangeButAcceptable: ...
|
||||
def __str__(self) -> StrangeButAcceptable: ...
|
||||
def __repr__(self) -> StrangeButAcceptable: ...
|
||||
|
||||
class StrangeButAcceptableSubclass(StrangeButAcceptable): ...
|
||||
|
||||
class FineAndDandy:
|
||||
def __str__(self, weird_extra_arg) -> str: ...
|
||||
def __repr__(self, weird_extra_arg_with_default=...) -> str: ...
|
||||
|
||||
@final
|
||||
class WillNotBeSubclassed:
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> WillNotBeSubclassed: ...
|
||||
def __enter__(self) -> WillNotBeSubclassed: ...
|
||||
async def __aenter__(self) -> WillNotBeSubclassed: ...
|
||||
|
||||
# we don't emit an error for these; out of scope for a linter
|
||||
class InvalidButPluginDoesNotCrash:
|
||||
def __new__() -> InvalidButPluginDoesNotCrash: ...
|
||||
def __enter__() -> InvalidButPluginDoesNotCrash: ...
|
||||
async def __aenter__() -> InvalidButPluginDoesNotCrash: ...
|
||||
|
||||
class BadIterator1(Iterator[int]):
|
||||
def __iter__(
|
||||
self,
|
||||
) -> Iterator[
|
||||
int
|
||||
]: ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
|
||||
class BadIterator2(
|
||||
typing.Iterator[int]
|
||||
): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
|
||||
def __iter__(
|
||||
self,
|
||||
) -> Iterator[
|
||||
int
|
||||
]: ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
|
||||
class BadIterator3(
|
||||
typing.Iterator[int]
|
||||
): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
|
||||
def __iter__(
|
||||
self,
|
||||
) -> collections.abc.Iterator[
|
||||
int
|
||||
]: ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
|
||||
class BadIterator4(Iterator[int]):
|
||||
# Note: *Iterable*, not *Iterator*, returned!
|
||||
def __iter__(
|
||||
self,
|
||||
) -> Iterable[
|
||||
int
|
||||
]: ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
|
||||
class IteratorReturningIterable:
|
||||
def __iter__(
|
||||
self,
|
||||
) -> Iterable[
|
||||
str
|
||||
]: ... # Y045 "__iter__" methods should return an Iterator, not an Iterable
|
||||
|
||||
class BadAsyncIterator(collections.abc.AsyncIterator[str]):
|
||||
def __aiter__(
|
||||
self,
|
||||
) -> typing.AsyncIterator[
|
||||
str
|
||||
]: ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
|
||||
|
||||
class AsyncIteratorReturningAsyncIterable:
|
||||
def __aiter__(
|
||||
self,
|
||||
) -> AsyncIterable[
|
||||
str
|
||||
]: ... # Y045 "__aiter__" methods should return an AsyncIterator, not an AsyncIterable
|
||||
|
||||
class Abstract(Iterator[str]):
|
||||
@abstractmethod
|
||||
def __iter__(self) -> Iterator[str]: ...
|
||||
@abstractmethod
|
||||
def __enter__(self) -> Abstract: ...
|
||||
@abstractmethod
|
||||
async def __aenter__(self) -> Abstract: ...
|
||||
|
||||
class GoodIterator(Iterator[str]):
|
||||
def __iter__(self: Self) -> Self: ...
|
||||
|
||||
class GoodAsyncIterator(AsyncIterator[int]):
|
||||
def __aiter__(self: Self) -> Self: ...
|
||||
|
||||
class DoesNotInheritFromIterator:
|
||||
def __iter__(self) -> DoesNotInheritFromIterator: ...
|
||||
|
||||
class Unannotated:
|
||||
def __new__(cls, *args, **kwargs): ...
|
||||
def __iter__(self): ...
|
||||
def __aiter__(self): ...
|
||||
async def __aenter__(self): ...
|
||||
def __repr__(self): ...
|
||||
def __str__(self): ...
|
||||
def __eq__(self): ...
|
||||
def __ne__(self): ...
|
||||
def __iadd__(self): ...
|
||||
def __ior__(self): ...
|
||||
|
||||
def __repr__(self) -> str: ...
|
||||
def __str__(self) -> str: ...
|
||||
def __eq__(self, other: Any) -> bool: ...
|
||||
def __ne__(self, other: Any) -> bool: ...
|
||||
def __imul__(self, other: Any) -> list[str]: ...
|
||||
15
crates/ruff/resources/test/fixtures/flake8_pyi/PYI035.py
vendored
Normal file
15
crates/ruff/resources/test/fixtures/flake8_pyi/PYI035.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
__all__: list[str]
|
||||
|
||||
__all__: list[str] = ["foo"]
|
||||
|
||||
|
||||
class Foo:
|
||||
__all__: list[str]
|
||||
__match_args__: tuple[str, ...]
|
||||
__slots__: tuple[str, ...]
|
||||
|
||||
|
||||
class Bar:
|
||||
__all__: list[str] = ["foo"]
|
||||
__match_args__: tuple[str, ...] = (1,)
|
||||
__slots__: tuple[str, ...] = "foo"
|
||||
13
crates/ruff/resources/test/fixtures/flake8_pyi/PYI035.pyi
vendored
Normal file
13
crates/ruff/resources/test/fixtures/flake8_pyi/PYI035.pyi
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
__all__: list[str] # Error: PYI035
|
||||
|
||||
__all__: list[str] = ["foo"]
|
||||
|
||||
class Foo:
|
||||
__all__: list[str]
|
||||
__match_args__: tuple[str, ...] # Error: PYI035
|
||||
__slots__: tuple[str, ...] # Error: PYI035
|
||||
|
||||
class Bar:
|
||||
__all__: list[str] = ["foo"]
|
||||
__match_args__: tuple[str, ...] = (1,)
|
||||
__slots__: tuple[str, ...] = "foo"
|
||||
85
crates/ruff/resources/test/fixtures/flake8_pyi/PYI045.py
vendored
Normal file
85
crates/ruff/resources/test/fixtures/flake8_pyi/PYI045.py
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
import collections.abc
|
||||
import typing
|
||||
from collections.abc import Iterator, Iterable
|
||||
|
||||
|
||||
class NoReturn:
|
||||
def __iter__(self):
|
||||
...
|
||||
|
||||
|
||||
class TypingIterableTReturn:
|
||||
def __iter__(self) -> typing.Iterable[int]:
|
||||
...
|
||||
|
||||
def not_iter(self) -> typing.Iterable[int]:
|
||||
...
|
||||
|
||||
|
||||
class TypingIterableReturn:
|
||||
def __iter__(self) -> typing.Iterable:
|
||||
...
|
||||
|
||||
def not_iter(self) -> typing.Iterable:
|
||||
...
|
||||
|
||||
|
||||
class CollectionsIterableTReturn:
|
||||
def __iter__(self) -> collections.abc.Iterable[int]:
|
||||
...
|
||||
|
||||
def not_iter(self) -> collections.abc.Iterable[int]:
|
||||
...
|
||||
|
||||
|
||||
class CollectionsIterableReturn:
|
||||
def __iter__(self) -> collections.abc.Iterable:
|
||||
...
|
||||
|
||||
def not_iter(self) -> collections.abc.Iterable:
|
||||
...
|
||||
|
||||
|
||||
class IterableReturn:
|
||||
def __iter__(self) -> Iterable:
|
||||
...
|
||||
|
||||
|
||||
class IteratorReturn:
|
||||
def __iter__(self) -> Iterator:
|
||||
...
|
||||
|
||||
|
||||
class IteratorTReturn:
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
...
|
||||
|
||||
|
||||
class TypingIteratorReturn:
|
||||
def __iter__(self) -> typing.Iterator:
|
||||
...
|
||||
|
||||
|
||||
class TypingIteratorTReturn:
|
||||
def __iter__(self) -> typing.Iterator[int]:
|
||||
...
|
||||
|
||||
|
||||
class CollectionsIteratorReturn:
|
||||
def __iter__(self) -> collections.abc.Iterator:
|
||||
...
|
||||
|
||||
|
||||
class CollectionsIteratorTReturn:
|
||||
def __iter__(self) -> collections.abc.Iterator[int]:
|
||||
...
|
||||
|
||||
|
||||
class TypingAsyncIterableTReturn:
|
||||
def __aiter__(self) -> typing.AsyncIterable[int]:
|
||||
...
|
||||
|
||||
|
||||
class TypingAsyncIterableReturn:
|
||||
def __aiter__(self) -> typing.AsyncIterable:
|
||||
...
|
||||
49
crates/ruff/resources/test/fixtures/flake8_pyi/PYI045.pyi
vendored
Normal file
49
crates/ruff/resources/test/fixtures/flake8_pyi/PYI045.pyi
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
import collections.abc
|
||||
import typing
|
||||
from collections.abc import Iterator, Iterable
|
||||
|
||||
class NoReturn:
|
||||
def __iter__(self): ...
|
||||
|
||||
class TypingIterableTReturn:
|
||||
def __iter__(self) -> typing.Iterable[int]: ... # Error: PYI045
|
||||
def not_iter(self) -> typing.Iterable[int]: ...
|
||||
|
||||
class TypingIterableReturn:
|
||||
def __iter__(self) -> typing.Iterable: ... # Error: PYI045
|
||||
def not_iter(self) -> typing.Iterable: ...
|
||||
|
||||
class CollectionsIterableTReturn:
|
||||
def __iter__(self) -> collections.abc.Iterable[int]: ... # Error: PYI045
|
||||
def not_iter(self) -> collections.abc.Iterable[int]: ...
|
||||
|
||||
class CollectionsIterableReturn:
|
||||
def __iter__(self) -> collections.abc.Iterable: ... # Error: PYI045
|
||||
def not_iter(self) -> collections.abc.Iterable: ...
|
||||
|
||||
class IterableReturn:
|
||||
def __iter__(self) -> Iterable: ... # Error: PYI045
|
||||
|
||||
class IteratorReturn:
|
||||
def __iter__(self) -> Iterator: ...
|
||||
|
||||
class IteratorTReturn:
|
||||
def __iter__(self) -> Iterator[int]: ...
|
||||
|
||||
class TypingIteratorReturn:
|
||||
def __iter__(self) -> typing.Iterator: ...
|
||||
|
||||
class TypingIteratorTReturn:
|
||||
def __iter__(self) -> typing.Iterator[int]: ...
|
||||
|
||||
class CollectionsIteratorReturn:
|
||||
def __iter__(self) -> collections.abc.Iterator: ...
|
||||
|
||||
class CollectionsIteratorTReturn:
|
||||
def __iter__(self) -> collections.abc.Iterator[int]: ...
|
||||
|
||||
class TypingAsyncIterableTReturn:
|
||||
def __aiter__(self) -> typing.AsyncIterable[int]: ... # Error: PYI045
|
||||
|
||||
class TypingAsyncIterableReturn:
|
||||
def __aiter__(self) -> typing.AsyncIterable: ... # Error: PYI045
|
||||
19
crates/ruff/resources/test/fixtures/flake8_pyi/PYI048.py
vendored
Normal file
19
crates/ruff/resources/test/fixtures/flake8_pyi/PYI048.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
def bar(): # OK
|
||||
...
|
||||
|
||||
|
||||
def oof(): # OK, docstrings are handled by another rule
|
||||
"""oof"""
|
||||
print("foo")
|
||||
|
||||
|
||||
def foo(): # Ok not in Stub file
|
||||
"""foo"""
|
||||
print("foo")
|
||||
print("foo")
|
||||
|
||||
|
||||
def buzz(): # Ok not in Stub file
|
||||
print("fizz")
|
||||
print("buzz")
|
||||
print("test")
|
||||
20
crates/ruff/resources/test/fixtures/flake8_pyi/PYI048.pyi
vendored
Normal file
20
crates/ruff/resources/test/fixtures/flake8_pyi/PYI048.pyi
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
def bar():
|
||||
... # OK
|
||||
|
||||
|
||||
def oof(): # OK, docstrings are handled by another rule
|
||||
"""oof"""
|
||||
print("foo")
|
||||
|
||||
|
||||
|
||||
def foo(): # ERROR PYI048
|
||||
"""foo"""
|
||||
print("foo")
|
||||
print("foo")
|
||||
|
||||
|
||||
def buzz(): # ERROR PYI048
|
||||
print("fizz")
|
||||
print("buzz")
|
||||
print("test")
|
||||
38
crates/ruff/resources/test/fixtures/flake8_pyi/PYI053.py
vendored
Normal file
38
crates/ruff/resources/test/fixtures/flake8_pyi/PYI053.py
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f2(x: str = "51 character stringgggggggggggggggggggggggggggggggg") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f3(x: str = "50 character stringggggggggggggggggggggggggggggg\U0001f600") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f4(x: str = "51 character stringgggggggggggggggggggggggggggggg\U0001f600") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f5(x: bytes = b"50 character byte stringgggggggggggggggggggggggggg") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f6(x: bytes = b"51 character byte stringgggggggggggggggggggggggggg") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f7(x: bytes = b"50 character byte stringggggggggggggggggggggggggg\xff") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f8(x: bytes = b"50 character byte stringgggggggggggggggggggggggggg\xff") -> None:
|
||||
...
|
||||
|
||||
|
||||
foo: str = "50 character stringggggggggggggggggggggggggggggggg"
|
||||
bar: str = "51 character stringgggggggggggggggggggggggggggggggg"
|
||||
|
||||
baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg"
|
||||
|
||||
qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff"
|
||||
30
crates/ruff/resources/test/fixtures/flake8_pyi/PYI053.pyi
vendored
Normal file
30
crates/ruff/resources/test/fixtures/flake8_pyi/PYI053.pyi
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
|
||||
def f2(
|
||||
x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
|
||||
) -> None: ...
|
||||
def f3(
|
||||
x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK
|
||||
) -> None: ...
|
||||
def f4(
|
||||
x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
|
||||
) -> None: ...
|
||||
def f5(
|
||||
x: bytes = b"50 character byte stringgggggggggggggggggggggggggg", # OK
|
||||
) -> None: ...
|
||||
def f6(
|
||||
x: bytes = b"51 character byte stringgggggggggggggggggggggggggg", # Error: PYI053
|
||||
) -> None: ...
|
||||
def f7(
|
||||
x: bytes = b"50 character byte stringggggggggggggggggggggggggg\xff", # OK
|
||||
) -> None: ...
|
||||
def f8(
|
||||
x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
|
||||
) -> None: ...
|
||||
|
||||
foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
|
||||
bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
|
||||
|
||||
baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
|
||||
|
||||
qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
|
||||
20
crates/ruff/resources/test/fixtures/flake8_pyi/PYI054.py
vendored
Normal file
20
crates/ruff/resources/test/fixtures/flake8_pyi/PYI054.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
field01: int = 0xFFFFFFFF
|
||||
field02: int = 0xFFFFFFFFF
|
||||
field03: int = -0xFFFFFFFF
|
||||
field04: int = -0xFFFFFFFFF
|
||||
|
||||
field05: int = 1234567890
|
||||
field06: int = 12_456_890
|
||||
field07: int = 12345678901
|
||||
field08: int = -1234567801
|
||||
field09: int = -234_567_890
|
||||
|
||||
field10: float = 123.456789
|
||||
field11: float = 123.4567890
|
||||
field12: float = -123.456789
|
||||
field13: float = -123.567_890
|
||||
|
||||
field14: complex = 1e1234567j
|
||||
field15: complex = 1e12345678j
|
||||
field16: complex = -1e1234567j
|
||||
field17: complex = 1e123456789j
|
||||
20
crates/ruff/resources/test/fixtures/flake8_pyi/PYI054.pyi
vendored
Normal file
20
crates/ruff/resources/test/fixtures/flake8_pyi/PYI054.pyi
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
field01: int = 0xFFFFFFFF
|
||||
field02: int = 0xFFFFFFFFF # Error: PYI054
|
||||
field03: int = -0xFFFFFFFF
|
||||
field04: int = -0xFFFFFFFFF # Error: PYI054
|
||||
|
||||
field05: int = 1234567890
|
||||
field06: int = 12_456_890
|
||||
field07: int = 12345678901 # Error: PYI054
|
||||
field08: int = -1234567801
|
||||
field09: int = -234_567_890 # Error: PYI054
|
||||
|
||||
field10: float = 123.456789
|
||||
field11: float = 123.4567890 # Error: PYI054
|
||||
field12: float = -123.456789
|
||||
field13: float = -123.567_890 # Error: PYI054
|
||||
|
||||
field14: complex = 1e1234567j
|
||||
field15: complex = 1e12345678j # Error: PYI054
|
||||
field16: complex = -1e1234567j
|
||||
field17: complex = 1e123456789j # Error: PYI054
|
||||
@@ -272,3 +272,34 @@ def str_to_bool(val):
|
||||
if isinstance(val, bool):
|
||||
return some_obj
|
||||
return val
|
||||
|
||||
|
||||
# Mixed assignments
|
||||
def function_assignment(x):
|
||||
def f(): ...
|
||||
|
||||
return f
|
||||
|
||||
|
||||
def class_assignment(x):
|
||||
class Foo: ...
|
||||
|
||||
return Foo
|
||||
|
||||
|
||||
def mixed_function_assignment(x):
|
||||
if x:
|
||||
def f(): ...
|
||||
else:
|
||||
f = 42
|
||||
|
||||
return f
|
||||
|
||||
|
||||
def mixed_class_assignment(x):
|
||||
if x:
|
||||
class Foo: ...
|
||||
else:
|
||||
Foo = 42
|
||||
|
||||
return Foo
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
# T001 - errors
|
||||
# XXX (evanrittenhouse): this is not fine
|
||||
# FIXME (evanrittenhouse): this is not fine
|
||||
# foo # XXX: this isn't fine either
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
# TODO: this has no author
|
||||
# FIXME: neither does this
|
||||
# TODO : and neither does this
|
||||
# foo # TODO: this doesn't either
|
||||
|
||||
@@ -26,4 +26,6 @@ def foo(x):
|
||||
# TODO: followed by a new TODO with an issue link
|
||||
# TDO-3870
|
||||
|
||||
# foo # TODO: no link!
|
||||
|
||||
# TODO: here's a TODO on the last line with no link
|
||||
|
||||
@@ -4,3 +4,5 @@
|
||||
# TODO this has no colon
|
||||
# TODO(evanrittenhouse 😀) this has no colon
|
||||
# FIXME add a colon
|
||||
# foo # TODO add a colon
|
||||
# TODO this has a colon but it doesn't terminate the tag, so this should throw. https://www.google.com
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
# TODO(evanrittenhouse):
|
||||
# TODO(evanrittenhouse)
|
||||
# FIXME
|
||||
# foo # TODO
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
# TDO006 - error
|
||||
# ToDo (evanrittenhouse): invalid capitalization
|
||||
# todo (evanrittenhouse): another invalid capitalization
|
||||
# foo # todo: invalid capitalization
|
||||
|
||||
@@ -6,3 +6,5 @@
|
||||
# TODO (evanrittenhouse):this doesn't either
|
||||
# TODO:neither does this
|
||||
# FIXME:and lastly neither does this
|
||||
# foo # TODO:this is really the last one
|
||||
# TODO this colon doesn't terminate the tag, so don't check it. https://www.google.com
|
||||
|
||||
@@ -150,3 +150,17 @@ def f():
|
||||
|
||||
def f():
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def f():
|
||||
from pandas import DataFrame # noqa: TCH002
|
||||
|
||||
x: DataFrame = 2
|
||||
|
||||
|
||||
def f():
|
||||
from pandas import ( # noqa: TCH002
|
||||
DataFrame,
|
||||
)
|
||||
|
||||
x: DataFrame = 2
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
|
||||
def f():
|
||||
# Even in strict mode, this shouldn't rase an error, since `pkg` is used at runtime,
|
||||
# Even in strict mode, this shouldn't raise an error, since `pkg` is used at runtime,
|
||||
# and implicitly imports `pkg.bar`.
|
||||
import pkg
|
||||
import pkg.bar
|
||||
@@ -12,7 +12,7 @@ def f():
|
||||
|
||||
|
||||
def f():
|
||||
# Even in strict mode, this shouldn't rase an error, since `pkg.bar` is used at
|
||||
# Even in strict mode, this shouldn't raise an error, since `pkg.bar` is used at
|
||||
# runtime, and implicitly imports `pkg`.
|
||||
import pkg
|
||||
import pkg.bar
|
||||
@@ -22,7 +22,7 @@ def f():
|
||||
|
||||
|
||||
def f():
|
||||
# In un-strict mode, this shouldn't rase an error, since `pkg` is used at runtime.
|
||||
# In un-strict mode, this shouldn't raise an error, since `pkg` is used at runtime.
|
||||
import pkg
|
||||
from pkg import A
|
||||
|
||||
@@ -31,7 +31,7 @@ def f():
|
||||
|
||||
|
||||
def f():
|
||||
# In un-strict mode, this shouldn't rase an error, since `pkg` is used at runtime.
|
||||
# In un-strict mode, this shouldn't raise an error, since `pkg` is used at runtime.
|
||||
from pkg import A, B
|
||||
|
||||
def test(value: A):
|
||||
@@ -39,7 +39,7 @@ def f():
|
||||
|
||||
|
||||
def f():
|
||||
# Even in strict mode, this shouldn't rase an error, since `pkg.baz` is used at
|
||||
# Even in strict mode, this shouldn't raise an error, since `pkg.baz` is used at
|
||||
# runtime, and implicitly imports `pkg.bar`.
|
||||
import pkg.bar
|
||||
import pkg.baz
|
||||
@@ -49,9 +49,56 @@ def f():
|
||||
|
||||
|
||||
def f():
|
||||
# In un-strict mode, this _should_ rase an error, since `pkg` is used at runtime.
|
||||
# In un-strict mode, this _should_ raise an error, since `pkg.bar` isn't used at runtime
|
||||
import pkg
|
||||
from pkg.bar import A
|
||||
|
||||
def test(value: A):
|
||||
return pkg.B()
|
||||
|
||||
|
||||
def f():
|
||||
# In un-strict mode, this shouldn't raise an error, since `pkg.bar` is used at runtime.
|
||||
import pkg
|
||||
import pkg.bar as B
|
||||
|
||||
def test(value: pkg.A):
|
||||
return B()
|
||||
|
||||
|
||||
def f():
|
||||
# In un-strict mode, this shouldn't raise an error, since `pkg.foo.bar` is used at runtime.
|
||||
import pkg.foo as F
|
||||
import pkg.foo.bar as B
|
||||
|
||||
def test(value: F.Foo):
|
||||
return B()
|
||||
|
||||
|
||||
def f():
|
||||
# In un-strict mode, this shouldn't raise an error, since `pkg.foo.bar` is used at runtime.
|
||||
import pkg
|
||||
import pkg.foo.bar as B
|
||||
|
||||
def test(value: pkg.A):
|
||||
return B()
|
||||
|
||||
|
||||
def f():
|
||||
# In un-strict mode, this _should_ raise an error, since `pkg` isn't used at runtime.
|
||||
# Note that `pkg` is a prefix of `pkgfoo` which are both different modules. This is
|
||||
# testing the implementation.
|
||||
import pkg
|
||||
import pkgfoo.bar as B
|
||||
|
||||
def test(value: pkg.A):
|
||||
return B()
|
||||
|
||||
|
||||
def f():
|
||||
# In un-strict mode, this shouldn't raise an error, since `pkg` is used at runtime.
|
||||
import pkg.bar as B
|
||||
import pkg.foo as F
|
||||
|
||||
def test(value: F.Foo):
|
||||
return B.Bar()
|
||||
|
||||
9
crates/ruff/resources/test/fixtures/pyflakes/F401_15.py
vendored
Normal file
9
crates/ruff/resources/test/fixtures/pyflakes/F401_15.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from django.db.models import ForeignKey
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class Foo:
|
||||
var = ForeignKey["Path"]()
|
||||
15
crates/ruff/resources/test/fixtures/pyflakes/F401_16.py
vendored
Normal file
15
crates/ruff/resources/test/fixtures/pyflakes/F401_16.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Test that `__all__` exports are respected even with multiple declarations."""
|
||||
|
||||
import random
|
||||
|
||||
|
||||
def some_dependency_check():
|
||||
return random.uniform(0.0, 1.0) > 0.49999
|
||||
|
||||
|
||||
if some_dependency_check():
|
||||
import math
|
||||
|
||||
__all__ = ["math"]
|
||||
else:
|
||||
__all__ = []
|
||||
@@ -2,3 +2,6 @@
|
||||
"{bar}{}".format(1, bar=2, spam=3) # F522
|
||||
"{bar:{spam}}".format(bar=2, spam=3) # No issues
|
||||
"{bar:{spam}}".format(bar=2, spam=3, eggs=4, ham=5) # F522
|
||||
# Not fixable
|
||||
(''
|
||||
.format(x=2))
|
||||
@@ -17,3 +17,17 @@
|
||||
"{0}{1}".format(1, *args) # No issues
|
||||
"{0}{1}".format(1, 2, *args) # No issues
|
||||
"{0}{1}".format(1, 2, 3, *args) # F523
|
||||
|
||||
# With nested quotes
|
||||
"''1{0}".format(1, 2, 3) # F523
|
||||
"\"\"{1}{0}".format(1, 2, 3) # F523
|
||||
'""{1}{0}'.format(1, 2, 3) # F523
|
||||
|
||||
# With modified indexes
|
||||
"{1}{2}".format(1, 2, 3) # F523, # F524
|
||||
"{1}{3}".format(1, 2, 3, 4) # F523, # F524
|
||||
"{1} {8}".format(0, 1) # F523, # F524
|
||||
|
||||
# Not fixable
|
||||
(''
|
||||
.format(2))
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
"{0} {bar}".format(1) # F524
|
||||
"{0} {bar}".format() # F524
|
||||
"{bar} {0}".format() # F524
|
||||
"{1} {8}".format(0, 1)
|
||||
|
||||
@@ -83,6 +83,11 @@ def f():
|
||||
pass
|
||||
|
||||
|
||||
def f():
|
||||
with (Nested(m)) as (cm):
|
||||
pass
|
||||
|
||||
|
||||
def f():
|
||||
toplevel = tt = lexer.get_token()
|
||||
if not tt:
|
||||
|
||||
28
crates/ruff/resources/test/fixtures/pylint/invalid_return_type_str.py
vendored
Normal file
28
crates/ruff/resources/test/fixtures/pylint/invalid_return_type_str.py
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
class Str:
|
||||
def __str__(self):
|
||||
return 1
|
||||
|
||||
class Float:
|
||||
def __str__(self):
|
||||
return 3.05
|
||||
|
||||
class Int:
|
||||
def __str__(self):
|
||||
return 0
|
||||
|
||||
class Bool:
|
||||
def __str__(self):
|
||||
return False
|
||||
|
||||
class Str2:
|
||||
def __str__(self):
|
||||
x = "ruff"
|
||||
return x
|
||||
|
||||
# TODO fixme once Ruff has better type checking
|
||||
def return_int():
|
||||
return 3
|
||||
|
||||
class ComplexReturn:
|
||||
def __str__(self):
|
||||
return return_int()
|
||||
38
crates/ruff/resources/test/fixtures/pylint/iteration_over_set.py
vendored
Normal file
38
crates/ruff/resources/test/fixtures/pylint/iteration_over_set.py
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# Errors
|
||||
|
||||
for item in {"apples", "lemons", "water"}: # flags in-line set literals
|
||||
print(f"I like {item}.")
|
||||
|
||||
numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
|
||||
numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
|
||||
numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
|
||||
numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
|
||||
# Non-errors
|
||||
|
||||
items = {"apples", "lemons", "water"}
|
||||
for item in items: # only complains about in-line sets (as per Pylint)
|
||||
print(f"I like {item}.")
|
||||
|
||||
for item in ["apples", "lemons", "water"]: # lists are fine
|
||||
print(f"I like {item}.")
|
||||
|
||||
for item in ("apples", "lemons", "water"): # tuples are fine
|
||||
print(f"I like {item}.")
|
||||
|
||||
numbers_list = [i for i in [1, 2, 3]] # lists in comprehensions are fine
|
||||
|
||||
numbers_set = {i for i in (1, 2, 3)} # tuples in comprehensions are fine
|
||||
|
||||
numbers_dict = {str(i): i for i in [1, 2, 3]} # lists in dict comprehensions are fine
|
||||
|
||||
numbers_gen = (i for i in (1, 2, 3)) # tuples in generator expressions are fine
|
||||
|
||||
for item in set(("apples", "lemons", "water")): # set constructor is fine
|
||||
print(f"I like {item}.")
|
||||
|
||||
for number in {i for i in range(10)}: # set comprehensions are fine
|
||||
print(number)
|
||||
@@ -25,3 +25,14 @@ min(1, min(a))
|
||||
min(1, min(i for i in range(10)))
|
||||
max(1, max(a))
|
||||
max(1, max(i for i in range(10)))
|
||||
|
||||
tuples_list = [
|
||||
(1, 2),
|
||||
(2, 3),
|
||||
(3, 4),
|
||||
(4, 5),
|
||||
(5, 6),
|
||||
]
|
||||
|
||||
min(min(tuples_list))
|
||||
max(max(tuples_list))
|
||||
|
||||
@@ -17,3 +17,30 @@ def f():
|
||||
|
||||
def f():
|
||||
nonlocal y
|
||||
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
|
||||
def g():
|
||||
nonlocal x
|
||||
|
||||
del x
|
||||
|
||||
|
||||
def f():
|
||||
def g():
|
||||
nonlocal x
|
||||
|
||||
del x
|
||||
|
||||
|
||||
def f():
|
||||
try:
|
||||
pass
|
||||
except Exception as x:
|
||||
pass
|
||||
|
||||
def g():
|
||||
nonlocal x
|
||||
x = 2
|
||||
|
||||
8
crates/ruff/resources/test/fixtures/pylint/sys_exit_alias_10.py
vendored
Normal file
8
crates/ruff/resources/test/fixtures/pylint/sys_exit_alias_10.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sys import exit as bar
|
||||
|
||||
|
||||
def main():
|
||||
exit(0)
|
||||
7
crates/ruff/resources/test/fixtures/pylint/yield_from_in_async_function.py
vendored
Normal file
7
crates/ruff/resources/test/fixtures/pylint/yield_from_in_async_function.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
async def success():
|
||||
yield 42
|
||||
|
||||
|
||||
async def fail():
|
||||
l = (1, 2, 3)
|
||||
yield from l
|
||||
10
crates/ruff/resources/test/fixtures/pyupgrade/UP006_1.py
vendored
Normal file
10
crates/ruff/resources/test/fixtures/pyupgrade/UP006_1.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def f(x: typing.DefaultDict[str, str]) -> None:
|
||||
...
|
||||
8
crates/ruff/resources/test/fixtures/pyupgrade/UP006_2.py
vendored
Normal file
8
crates/ruff/resources/test/fixtures/pyupgrade/UP006_2.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def f(x: typing.DefaultDict[str, str]) -> None:
|
||||
...
|
||||
8
crates/ruff/resources/test/fixtures/pyupgrade/UP006_3.py
vendored
Normal file
8
crates/ruff/resources/test/fixtures/pyupgrade/UP006_3.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def f(x: "typing.DefaultDict[str, str]") -> None:
|
||||
...
|
||||
@@ -15,6 +15,7 @@ bytes("foo", **a)
|
||||
bytes(b"foo"
|
||||
b"bar")
|
||||
bytes("foo")
|
||||
f"{f'{str()}'}"
|
||||
|
||||
# These become string or byte literals
|
||||
str()
|
||||
|
||||
30
crates/ruff/resources/test/fixtures/pyupgrade/UP036_5.py
vendored
Normal file
30
crates/ruff/resources/test/fixtures/pyupgrade/UP036_5.py
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
|
||||
def a():
|
||||
if b:
|
||||
print(1)
|
||||
elif c:
|
||||
print(2)
|
||||
return None
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
pass
|
||||
|
||||
else:
|
||||
|
||||
def a():
|
||||
if b:
|
||||
print(1)
|
||||
elif c:
|
||||
print(2)
|
||||
else:
|
||||
print(3)
|
||||
return None
|
||||
@@ -2,10 +2,10 @@ import datetime
|
||||
import re
|
||||
import typing
|
||||
from dataclasses import dataclass, field
|
||||
from fractions import Fraction
|
||||
from pathlib import Path
|
||||
from typing import ClassVar, NamedTuple
|
||||
|
||||
|
||||
def default_function() -> list[int]:
|
||||
return []
|
||||
|
||||
@@ -25,7 +25,12 @@ class A:
|
||||
fine_timedelta: datetime.timedelta = datetime.timedelta(hours=7)
|
||||
fine_tuple: tuple[int] = tuple([1])
|
||||
fine_regex: re.Pattern = re.compile(r".*")
|
||||
|
||||
fine_float: float = float('-inf')
|
||||
fine_int: int = int(12)
|
||||
fine_complex: complex = complex(1, 2)
|
||||
fine_str: str = str("foo")
|
||||
fine_bool: bool = bool("foo")
|
||||
fine_fraction: Fraction = Fraction(1,2)
|
||||
|
||||
DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES = ImmutableType(40)
|
||||
DEFAULT_A_FOR_ALL_DATACLASSES = A([1, 2, 3])
|
||||
|
||||
@@ -12,6 +12,8 @@ f"{str(d['a'])}, {repr(d['b'])}, {ascii(d['c'])}" # RUF010
|
||||
|
||||
f"{(str(bla))}, {(repr(bla))}, {(ascii(bla))}" # RUF010
|
||||
|
||||
f"{bla!s}, {(repr(bla))}, {(ascii(bla))}" # RUF010
|
||||
|
||||
f"{foo(bla)}" # OK
|
||||
|
||||
f"{str(bla, 'ascii')}, {str(bla, encoding='cp1255')}" # OK
|
||||
|
||||
7
crates/ruff/resources/test/fixtures/ruff/pyproject_toml/bleach/pyproject.toml
vendored
Normal file
7
crates/ruff/resources/test/fixtures/ruff/pyproject_toml/bleach/pyproject.toml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "hello-world"
|
||||
version = "0.1.0"
|
||||
# There's a comma missing here
|
||||
dependencies = [
|
||||
"tinycss2>=1.1.0<1.2",
|
||||
]
|
||||
7
crates/ruff/resources/test/fixtures/ruff/pyproject_toml/invalid_author/pyproject.toml
vendored
Normal file
7
crates/ruff/resources/test/fixtures/ruff/pyproject_toml/invalid_author/pyproject.toml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "hello-world"
|
||||
version = "0.1.0"
|
||||
# Ensure that the spans from toml handle utf-8 correctly
|
||||
authors = [
|
||||
{ name = "Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘", email = 1 }
|
||||
]
|
||||
57
crates/ruff/resources/test/fixtures/ruff/pyproject_toml/maturin/pyproject.toml
vendored
Normal file
57
crates/ruff/resources/test/fixtures/ruff/pyproject_toml/maturin/pyproject.toml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
# This is a valid pyproject.toml
|
||||
# https://github.com/PyO3/maturin/blob/87ac3d9f74dd79ef2df9a20880b9f1fa23f9a437/pyproject.toml
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel>=0.36.2", "tomli>=1.1.0 ; python_version<'3.11'", "setuptools-rust>=1.4.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "maturin"
|
||||
requires-python = ">=3.7"
|
||||
classifiers = [
|
||||
"Topic :: Software Development :: Build Tools",
|
||||
"Programming Language :: Rust",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
]
|
||||
dependencies = ["tomli>=1.1.0 ; python_version<'3.11'"]
|
||||
dynamic = [
|
||||
"authors",
|
||||
"description",
|
||||
"license",
|
||||
"readme",
|
||||
"version"
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
zig = [
|
||||
"ziglang~=0.10.0",
|
||||
]
|
||||
patchelf = [
|
||||
"patchelf",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
"Source Code" = "https://github.com/PyO3/maturin"
|
||||
Issues = "https://github.com/PyO3/maturin/issues"
|
||||
Documentation = "https://maturin.rs"
|
||||
Changelog = "https://maturin.rs/changelog.html"
|
||||
|
||||
[tool.maturin]
|
||||
bindings = "bin"
|
||||
|
||||
[tool.black]
|
||||
target_version = ['py37']
|
||||
extend-exclude = '''
|
||||
# Ignore cargo-generate templates
|
||||
^/src/templates
|
||||
'''
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
target-version = "py37"
|
||||
|
||||
[tool.mypy]
|
||||
disallow_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
warn_no_return = true
|
||||
ignore_missing_imports = true
|
||||
39
crates/ruff/resources/test/fixtures/ruff/pyproject_toml/maturin_gh_1615/pyproject.toml
vendored
Normal file
39
crates/ruff/resources/test/fixtures/ruff/pyproject_toml/maturin_gh_1615/pyproject.toml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
# license-files is wrong here
|
||||
# https://github.com/PyO3/maturin/issues/1615
|
||||
[build-system]
|
||||
requires = [ "maturin>=0.14", "numpy", "wheel", "patchelf",]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "..."
|
||||
license-files = [ "license.txt",]
|
||||
requires-python = ">=3.8"
|
||||
requires-dist = [ "maturin>=0.14", "...",]
|
||||
dependencies = [ "packaging", "...",]
|
||||
zip-safe = false
|
||||
version = "..."
|
||||
readme = "..."
|
||||
description = "..."
|
||||
classifiers = [ "...",]
|
||||
[[project.authors]]
|
||||
name = "..."
|
||||
email = "..."
|
||||
|
||||
[project.urls]
|
||||
homepage = "..."
|
||||
documentation = "..."
|
||||
repository = "..."
|
||||
|
||||
[project.optional-dependencies]
|
||||
test = [ "coverage", "...",]
|
||||
docs = [ "sphinx", "sphinx-rtd-theme",]
|
||||
devel = []
|
||||
|
||||
[tool.maturin]
|
||||
include = [ "...",]
|
||||
bindings = "pyo3"
|
||||
compatibility = "manylinux2014"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = [ "...",]
|
||||
addopts = "--color=yes --tb=native --cov-report term --cov-report html:docs/dist_coverage --cov=aisdb --doctest-modules --envfile .env"
|
||||
@@ -4,6 +4,7 @@ Use '.exception' over '.error' inside except blocks
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -60,3 +61,17 @@ def good():
|
||||
a = 1
|
||||
except Exception:
|
||||
foo.exception("Context message here")
|
||||
|
||||
|
||||
def fine():
|
||||
try:
|
||||
a = 1
|
||||
except Exception:
|
||||
logger.error("Context message here", exc_info=True)
|
||||
|
||||
|
||||
def fine():
|
||||
try:
|
||||
a = 1
|
||||
except Exception:
|
||||
logger.error("Context message here", exc_info=sys.exc_info())
|
||||
|
||||
214
crates/ruff/src/autofix/codemods.rs
Normal file
214
crates/ruff/src/autofix/codemods.rs
Normal file
@@ -0,0 +1,214 @@
|
||||
//! Interface for editing code snippets. These functions take statements or expressions as input,
|
||||
//! and return the modified code snippet as output.
|
||||
use anyhow::{bail, Result};
|
||||
use libcst_native::{
|
||||
Codegen, CodegenState, ImportNames, ParenthesizableWhitespace, SmallStatement, Statement,
|
||||
};
|
||||
use rustpython_parser::ast::{Ranged, Stmt};
|
||||
|
||||
use ruff_python_ast::source_code::{Locator, Stylist};
|
||||
|
||||
use crate::cst::helpers::compose_module_path;
|
||||
use crate::cst::matchers::match_statement;
|
||||
|
||||
/// Glue code to make libcst codegen work with ruff's Stylist
|
||||
pub(crate) trait CodegenStylist<'a>: Codegen<'a> {
|
||||
fn codegen_stylist(&self, stylist: &'a Stylist) -> String;
|
||||
}
|
||||
|
||||
impl<'a, T: Codegen<'a>> CodegenStylist<'a> for T {
|
||||
fn codegen_stylist(&self, stylist: &'a Stylist) -> String {
|
||||
let mut state = CodegenState {
|
||||
default_newline: stylist.line_ending().as_str(),
|
||||
default_indent: stylist.indentation(),
|
||||
..Default::default()
|
||||
};
|
||||
self.codegen(&mut state);
|
||||
state.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Given an import statement, remove any imports that are specified in the `imports` iterator.
|
||||
///
|
||||
/// Returns `Ok(None)` if the statement is empty after removing the imports.
|
||||
pub(crate) fn remove_imports<'a>(
|
||||
imports: impl Iterator<Item = &'a str>,
|
||||
stmt: &Stmt,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
) -> Result<Option<String>> {
|
||||
let module_text = locator.slice(stmt.range());
|
||||
let mut tree = match_statement(module_text)?;
|
||||
|
||||
let Statement::Simple(body) = &mut tree else {
|
||||
bail!("Expected Statement::Simple");
|
||||
};
|
||||
|
||||
let (aliases, import_module) = match body.body.first_mut() {
|
||||
Some(SmallStatement::Import(import_body)) => (&mut import_body.names, None),
|
||||
Some(SmallStatement::ImportFrom(import_body)) => {
|
||||
if let ImportNames::Aliases(names) = &mut import_body.names {
|
||||
(
|
||||
names,
|
||||
Some((&import_body.relative, import_body.module.as_ref())),
|
||||
)
|
||||
} else if let ImportNames::Star(..) = &import_body.names {
|
||||
// Special-case: if the import is a `from ... import *`, then we delete the
|
||||
// entire statement.
|
||||
let mut found_star = false;
|
||||
for import in imports {
|
||||
let qualified_name = match import_body.module.as_ref() {
|
||||
Some(module_name) => format!("{}.*", compose_module_path(module_name)),
|
||||
None => "*".to_string(),
|
||||
};
|
||||
if import == qualified_name {
|
||||
found_star = true;
|
||||
} else {
|
||||
bail!("Expected \"*\" for unused import (got: \"{}\")", import);
|
||||
}
|
||||
}
|
||||
if !found_star {
|
||||
bail!("Expected \'*\' for unused import");
|
||||
}
|
||||
return Ok(None);
|
||||
} else {
|
||||
bail!("Expected: ImportNames::Aliases | ImportNames::Star");
|
||||
}
|
||||
}
|
||||
_ => bail!("Expected: SmallStatement::ImportFrom | SmallStatement::Import"),
|
||||
};
|
||||
|
||||
// Preserve the trailing comma (or not) from the last entry.
|
||||
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
|
||||
|
||||
for import in imports {
|
||||
let alias_index = aliases.iter().position(|alias| {
|
||||
let qualified_name = match import_module {
|
||||
Some((relative, module)) => {
|
||||
let module = module.map(compose_module_path);
|
||||
let member = compose_module_path(&alias.name);
|
||||
let mut qualified_name = String::with_capacity(
|
||||
relative.len() + module.as_ref().map_or(0, String::len) + member.len() + 1,
|
||||
);
|
||||
for _ in 0..relative.len() {
|
||||
qualified_name.push('.');
|
||||
}
|
||||
if let Some(module) = module {
|
||||
qualified_name.push_str(&module);
|
||||
qualified_name.push('.');
|
||||
}
|
||||
qualified_name.push_str(&member);
|
||||
qualified_name
|
||||
}
|
||||
None => compose_module_path(&alias.name),
|
||||
};
|
||||
qualified_name == import
|
||||
});
|
||||
|
||||
if let Some(index) = alias_index {
|
||||
aliases.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
// But avoid destroying any trailing comments.
|
||||
if let Some(alias) = aliases.last_mut() {
|
||||
let has_comment = if let Some(comma) = &alias.comma {
|
||||
match &comma.whitespace_after {
|
||||
ParenthesizableWhitespace::SimpleWhitespace(_) => false,
|
||||
ParenthesizableWhitespace::ParenthesizedWhitespace(whitespace) => {
|
||||
whitespace.first_line.comment.is_some()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !has_comment {
|
||||
alias.comma = trailing_comma;
|
||||
}
|
||||
}
|
||||
|
||||
if aliases.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some(tree.codegen_stylist(stylist)))
|
||||
}
|
||||
|
||||
/// Given an import statement, remove any imports that are not specified in the `imports` slice.
|
||||
///
|
||||
/// Returns the modified import statement.
|
||||
pub(crate) fn retain_imports(
|
||||
imports: &[&str],
|
||||
stmt: &Stmt,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
) -> Result<String> {
|
||||
let module_text = locator.slice(stmt.range());
|
||||
let mut tree = match_statement(module_text)?;
|
||||
|
||||
let Statement::Simple(body) = &mut tree else {
|
||||
bail!("Expected Statement::Simple");
|
||||
};
|
||||
|
||||
let (aliases, import_module) = match body.body.first_mut() {
|
||||
Some(SmallStatement::Import(import_body)) => (&mut import_body.names, None),
|
||||
Some(SmallStatement::ImportFrom(import_body)) => {
|
||||
if let ImportNames::Aliases(names) = &mut import_body.names {
|
||||
(
|
||||
names,
|
||||
Some((&import_body.relative, import_body.module.as_ref())),
|
||||
)
|
||||
} else {
|
||||
bail!("Expected: ImportNames::Aliases");
|
||||
}
|
||||
}
|
||||
_ => bail!("Expected: SmallStatement::ImportFrom | SmallStatement::Import"),
|
||||
};
|
||||
|
||||
// Preserve the trailing comma (or not) from the last entry.
|
||||
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
|
||||
|
||||
aliases.retain(|alias| {
|
||||
imports.iter().any(|import| {
|
||||
let qualified_name = match import_module {
|
||||
Some((relative, module)) => {
|
||||
let module = module.map(compose_module_path);
|
||||
let member = compose_module_path(&alias.name);
|
||||
let mut qualified_name = String::with_capacity(
|
||||
relative.len() + module.as_ref().map_or(0, String::len) + member.len() + 1,
|
||||
);
|
||||
for _ in 0..relative.len() {
|
||||
qualified_name.push('.');
|
||||
}
|
||||
if let Some(module) = module {
|
||||
qualified_name.push_str(&module);
|
||||
qualified_name.push('.');
|
||||
}
|
||||
qualified_name.push_str(&member);
|
||||
qualified_name
|
||||
}
|
||||
None => compose_module_path(&alias.name),
|
||||
};
|
||||
qualified_name == *import
|
||||
})
|
||||
});
|
||||
|
||||
// But avoid destroying any trailing comments.
|
||||
if let Some(alias) = aliases.last_mut() {
|
||||
let has_comment = if let Some(comma) = &alias.comma {
|
||||
match &comma.whitespace_after {
|
||||
ParenthesizableWhitespace::SimpleWhitespace(_) => false,
|
||||
ParenthesizableWhitespace::ParenthesizedWhitespace(whitespace) => {
|
||||
whitespace.first_line.comment.is_some()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !has_comment {
|
||||
alias.comma = trailing_comma;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tree.codegen_stylist(stylist))
|
||||
}
|
||||
@@ -1,156 +1,15 @@
|
||||
//! Interface for generating autofix edits from higher-level actions (e.g., "remove an argument").
|
||||
use anyhow::{bail, Result};
|
||||
use itertools::Itertools;
|
||||
use libcst_native::{
|
||||
Codegen, CodegenState, ImportNames, ParenthesizableWhitespace, SmallStatement, Statement,
|
||||
};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use rustpython_parser::ast::{self, Excepthandler, Expr, Keyword, Ranged, Stmt};
|
||||
use rustpython_parser::{lexer, Mode, Tok};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_newlines::NewlineWithTrailingNewline;
|
||||
use ruff_python_ast::helpers;
|
||||
use ruff_python_ast::newlines::NewlineWithTrailingNewline;
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
|
||||
use crate::cst::helpers::compose_module_path;
|
||||
use crate::cst::matchers::match_statement;
|
||||
|
||||
/// Determine if a body contains only a single statement, taking into account
|
||||
/// deleted.
|
||||
fn has_single_child(body: &[Stmt], deleted: &[&Stmt]) -> bool {
|
||||
body.iter().filter(|child| !deleted.contains(child)).count() == 1
|
||||
}
|
||||
|
||||
/// Determine if a child is the only statement in its body.
|
||||
fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool> {
|
||||
match parent {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef { body, .. })
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. })
|
||||
| Stmt::ClassDef(ast::StmtClassDef { body, .. })
|
||||
| Stmt::With(ast::StmtWith { body, .. })
|
||||
| Stmt::AsyncWith(ast::StmtAsyncWith { body, .. }) => {
|
||||
if body.iter().contains(child) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
} else {
|
||||
bail!("Unable to find child in parent body")
|
||||
}
|
||||
}
|
||||
Stmt::For(ast::StmtFor { body, orelse, .. })
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor { body, orelse, .. })
|
||||
| Stmt::While(ast::StmtWhile { body, orelse, .. })
|
||||
| Stmt::If(ast::StmtIf { body, orelse, .. }) => {
|
||||
if body.iter().contains(child) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
} else if orelse.iter().contains(child) {
|
||||
Ok(has_single_child(orelse, deleted))
|
||||
} else {
|
||||
bail!("Unable to find child in parent body")
|
||||
}
|
||||
}
|
||||
Stmt::Try(ast::StmtTry {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
range: _,
|
||||
})
|
||||
| Stmt::TryStar(ast::StmtTryStar {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
range: _,
|
||||
}) => {
|
||||
if body.iter().contains(child) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
} else if orelse.iter().contains(child) {
|
||||
Ok(has_single_child(orelse, deleted))
|
||||
} else if finalbody.iter().contains(child) {
|
||||
Ok(has_single_child(finalbody, deleted))
|
||||
} else if let Some(body) = handlers.iter().find_map(|handler| match handler {
|
||||
Excepthandler::ExceptHandler(ast::ExcepthandlerExceptHandler { body, .. }) => {
|
||||
if body.iter().contains(child) {
|
||||
Some(body)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
} else {
|
||||
bail!("Unable to find child in parent body")
|
||||
}
|
||||
}
|
||||
Stmt::Match(ast::StmtMatch { cases, .. }) => {
|
||||
if let Some(body) = cases.iter().find_map(|case| {
|
||||
if case.body.iter().contains(child) {
|
||||
Some(&case.body)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
} else {
|
||||
bail!("Unable to find child in parent body")
|
||||
}
|
||||
}
|
||||
_ => bail!("Unable to find child in parent body"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the location of a trailing semicolon following a `Stmt`, if it's part
|
||||
/// of a multi-statement line.
|
||||
fn trailing_semicolon(stmt: &Stmt, locator: &Locator) -> Option<TextSize> {
|
||||
let contents = locator.after(stmt.end());
|
||||
|
||||
for line in NewlineWithTrailingNewline::from(contents) {
|
||||
let trimmed = line.trim_start();
|
||||
|
||||
if trimmed.starts_with(';') {
|
||||
let colon_offset = line.text_len() - trimmed.text_len();
|
||||
return Some(stmt.end() + line.start() + colon_offset);
|
||||
}
|
||||
|
||||
if !trimmed.starts_with('\\') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Find the next valid break for a `Stmt` after a semicolon.
|
||||
fn next_stmt_break(semicolon: TextSize, locator: &Locator) -> TextSize {
|
||||
let start_location = semicolon + TextSize::from(1);
|
||||
|
||||
let contents = &locator.contents()[usize::from(start_location)..];
|
||||
for line in NewlineWithTrailingNewline::from(contents) {
|
||||
let trimmed = line.trim();
|
||||
// Skip past any continuations.
|
||||
if trimmed.starts_with('\\') {
|
||||
continue;
|
||||
}
|
||||
|
||||
return start_location
|
||||
+ if trimmed.is_empty() {
|
||||
// If the line is empty, then despite the previous statement ending in a
|
||||
// semicolon, we know that it's not a multi-statement line.
|
||||
line.start()
|
||||
} else {
|
||||
// Otherwise, find the start of the next statement. (Or, anything that isn't
|
||||
// whitespace.)
|
||||
let relative_offset = line.find(|c: char| !c.is_whitespace()).unwrap();
|
||||
line.start() + TextSize::try_from(relative_offset).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
locator.line_end(start_location)
|
||||
}
|
||||
|
||||
/// Return `true` if a `Stmt` occurs at the end of a file.
|
||||
fn is_end_of_file(stmt: &Stmt, locator: &Locator) -> bool {
|
||||
stmt.end() == locator.contents().text_len()
|
||||
}
|
||||
use crate::autofix::codemods;
|
||||
|
||||
/// Return the `Fix` to use when deleting a `Stmt`.
|
||||
///
|
||||
@@ -168,21 +27,19 @@ fn is_end_of_file(stmt: &Stmt, locator: &Locator) -> bool {
|
||||
pub(crate) fn delete_stmt(
|
||||
stmt: &Stmt,
|
||||
parent: Option<&Stmt>,
|
||||
deleted: &[&Stmt],
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
stylist: &Stylist,
|
||||
) -> Result<Edit> {
|
||||
) -> Edit {
|
||||
if parent
|
||||
.map(|parent| is_lone_child(stmt, parent, deleted))
|
||||
.map_or(Ok(None), |v| v.map(Some))?
|
||||
.map(|parent| is_lone_child(stmt, parent))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
// If removing this node would lead to an invalid syntax tree, replace
|
||||
// it with a `pass`.
|
||||
Ok(Edit::range_replacement("pass".to_string(), stmt.range()))
|
||||
Edit::range_replacement("pass".to_string(), stmt.range())
|
||||
} else {
|
||||
Ok(if let Some(semicolon) = trailing_semicolon(stmt, locator) {
|
||||
if let Some(semicolon) = trailing_semicolon(stmt, locator) {
|
||||
let next = next_stmt_break(semicolon, locator);
|
||||
Edit::deletion(stmt.start(), next)
|
||||
} else if helpers::has_leading_content(stmt, locator) {
|
||||
@@ -197,124 +54,22 @@ pub(crate) fn delete_stmt(
|
||||
} else {
|
||||
let range = locator.full_lines_range(stmt.range());
|
||||
Edit::range_deletion(range)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a `Fix` to remove any unused imports from an `import` statement.
|
||||
/// Generate a `Fix` to remove the specified imports from an `import` statement.
|
||||
pub(crate) fn remove_unused_imports<'a>(
|
||||
unused_imports: impl Iterator<Item = &'a str>,
|
||||
stmt: &Stmt,
|
||||
parent: Option<&Stmt>,
|
||||
deleted: &[&Stmt],
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
stylist: &Stylist,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(stmt.range());
|
||||
let mut tree = match_statement(module_text)?;
|
||||
|
||||
let Statement::Simple(body) = &mut tree else {
|
||||
bail!("Expected Statement::Simple");
|
||||
};
|
||||
|
||||
let (aliases, import_module) = match body.body.first_mut() {
|
||||
Some(SmallStatement::Import(import_body)) => (&mut import_body.names, None),
|
||||
Some(SmallStatement::ImportFrom(import_body)) => {
|
||||
if let ImportNames::Aliases(names) = &mut import_body.names {
|
||||
(
|
||||
names,
|
||||
Some((&import_body.relative, import_body.module.as_ref())),
|
||||
)
|
||||
} else if let ImportNames::Star(..) = &import_body.names {
|
||||
// Special-case: if the import is a `from ... import *`, then we delete the
|
||||
// entire statement.
|
||||
let mut found_star = false;
|
||||
for unused_import in unused_imports {
|
||||
let full_name = match import_body.module.as_ref() {
|
||||
Some(module_name) => format!("{}.*", compose_module_path(module_name)),
|
||||
None => "*".to_string(),
|
||||
};
|
||||
if unused_import == full_name {
|
||||
found_star = true;
|
||||
} else {
|
||||
bail!(
|
||||
"Expected \"*\" for unused import (got: \"{}\")",
|
||||
unused_import
|
||||
);
|
||||
}
|
||||
}
|
||||
if !found_star {
|
||||
bail!("Expected \'*\' for unused import");
|
||||
}
|
||||
return delete_stmt(stmt, parent, deleted, locator, indexer, stylist);
|
||||
} else {
|
||||
bail!("Expected: ImportNames::Aliases | ImportNames::Star");
|
||||
}
|
||||
}
|
||||
_ => bail!("Expected: SmallStatement::ImportFrom | SmallStatement::Import"),
|
||||
};
|
||||
|
||||
// Preserve the trailing comma (or not) from the last entry.
|
||||
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
|
||||
|
||||
for unused_import in unused_imports {
|
||||
let alias_index = aliases.iter().position(|alias| {
|
||||
let full_name = match import_module {
|
||||
Some((relative, module)) => {
|
||||
let module = module.map(compose_module_path);
|
||||
let member = compose_module_path(&alias.name);
|
||||
let mut full_name = String::with_capacity(
|
||||
relative.len() + module.as_ref().map_or(0, String::len) + member.len() + 1,
|
||||
);
|
||||
for _ in 0..relative.len() {
|
||||
full_name.push('.');
|
||||
}
|
||||
if let Some(module) = module {
|
||||
full_name.push_str(&module);
|
||||
full_name.push('.');
|
||||
}
|
||||
full_name.push_str(&member);
|
||||
full_name
|
||||
}
|
||||
None => compose_module_path(&alias.name),
|
||||
};
|
||||
full_name == unused_import
|
||||
});
|
||||
|
||||
if let Some(index) = alias_index {
|
||||
aliases.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
// But avoid destroying any trailing comments.
|
||||
if let Some(alias) = aliases.last_mut() {
|
||||
let has_comment = if let Some(comma) = &alias.comma {
|
||||
match &comma.whitespace_after {
|
||||
ParenthesizableWhitespace::SimpleWhitespace(_) => false,
|
||||
ParenthesizableWhitespace::ParenthesizedWhitespace(whitespace) => {
|
||||
whitespace.first_line.comment.is_some()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !has_comment {
|
||||
alias.comma = trailing_comma;
|
||||
}
|
||||
}
|
||||
|
||||
if aliases.is_empty() {
|
||||
delete_stmt(stmt, parent, deleted, locator, indexer, stylist)
|
||||
} else {
|
||||
let mut state = CodegenState {
|
||||
default_newline: &stylist.line_ending(),
|
||||
default_indent: stylist.indentation(),
|
||||
..CodegenState::default()
|
||||
};
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Edit::range_replacement(state.to_string(), stmt.range()))
|
||||
match codemods::remove_imports(unused_imports, stmt, locator, stylist)? {
|
||||
None => Ok(delete_stmt(stmt, parent, locator, indexer, stylist)),
|
||||
Some(content) => Ok(Edit::range_replacement(content, stmt.range())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +100,7 @@ pub(crate) fn remove_argument(
|
||||
|
||||
if n_arguments == 1 {
|
||||
// Case 1: there is only one argument.
|
||||
let mut count: usize = 0;
|
||||
let mut count = 0u32;
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if matches!(tok, Tok::Lpar) {
|
||||
if count == 0 {
|
||||
@@ -355,11 +110,11 @@ pub(crate) fn remove_argument(
|
||||
range.start() + TextSize::from(1)
|
||||
});
|
||||
}
|
||||
count += 1;
|
||||
count = count.saturating_add(1);
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Rpar) {
|
||||
count -= 1;
|
||||
count = count.saturating_sub(1);
|
||||
if count == 0 {
|
||||
fix_end = Some(if remove_parentheses {
|
||||
range.end()
|
||||
@@ -420,32 +175,147 @@ pub(crate) fn remove_argument(
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if a vector contains only one, specific element.
|
||||
fn is_only<T: PartialEq>(vec: &[T], value: &T) -> bool {
|
||||
vec.len() == 1 && vec[0] == *value
|
||||
}
|
||||
|
||||
/// Determine if a child is the only statement in its body.
|
||||
fn is_lone_child(child: &Stmt, parent: &Stmt) -> bool {
|
||||
match parent {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef { body, .. })
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. })
|
||||
| Stmt::ClassDef(ast::StmtClassDef { body, .. })
|
||||
| Stmt::With(ast::StmtWith { body, .. })
|
||||
| Stmt::AsyncWith(ast::StmtAsyncWith { body, .. }) => {
|
||||
if is_only(body, child) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::For(ast::StmtFor { body, orelse, .. })
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor { body, orelse, .. })
|
||||
| Stmt::While(ast::StmtWhile { body, orelse, .. })
|
||||
| Stmt::If(ast::StmtIf { body, orelse, .. }) => {
|
||||
if is_only(body, child) || is_only(orelse, child) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::Try(ast::StmtTry {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
range: _,
|
||||
})
|
||||
| Stmt::TryStar(ast::StmtTryStar {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
range: _,
|
||||
}) => {
|
||||
if is_only(body, child)
|
||||
|| is_only(orelse, child)
|
||||
|| is_only(finalbody, child)
|
||||
|| handlers.iter().any(|handler| match handler {
|
||||
Excepthandler::ExceptHandler(ast::ExcepthandlerExceptHandler {
|
||||
body, ..
|
||||
}) => is_only(body, child),
|
||||
})
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::Match(ast::StmtMatch { cases, .. }) => {
|
||||
if cases.iter().any(|case| is_only(&case.body, child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Return the location of a trailing semicolon following a `Stmt`, if it's part
|
||||
/// of a multi-statement line.
|
||||
fn trailing_semicolon(stmt: &Stmt, locator: &Locator) -> Option<TextSize> {
|
||||
let contents = locator.after(stmt.end());
|
||||
|
||||
for line in NewlineWithTrailingNewline::from(contents) {
|
||||
let trimmed = line.trim_start();
|
||||
|
||||
if trimmed.starts_with(';') {
|
||||
let colon_offset = line.text_len() - trimmed.text_len();
|
||||
return Some(stmt.end() + line.start() + colon_offset);
|
||||
}
|
||||
|
||||
if !trimmed.starts_with('\\') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Find the next valid break for a `Stmt` after a semicolon.
|
||||
fn next_stmt_break(semicolon: TextSize, locator: &Locator) -> TextSize {
|
||||
let start_location = semicolon + TextSize::from(1);
|
||||
|
||||
let contents = &locator.contents()[usize::from(start_location)..];
|
||||
for line in NewlineWithTrailingNewline::from(contents) {
|
||||
let trimmed = line.trim();
|
||||
// Skip past any continuations.
|
||||
if trimmed.starts_with('\\') {
|
||||
continue;
|
||||
}
|
||||
|
||||
return start_location
|
||||
+ if trimmed.is_empty() {
|
||||
// If the line is empty, then despite the previous statement ending in a
|
||||
// semicolon, we know that it's not a multi-statement line.
|
||||
line.start()
|
||||
} else {
|
||||
// Otherwise, find the start of the next statement. (Or, anything that isn't
|
||||
// whitespace.)
|
||||
let relative_offset = line.find(|c: char| !c.is_whitespace()).unwrap();
|
||||
line.start() + TextSize::try_from(relative_offset).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
locator.line_end(start_location)
|
||||
}
|
||||
|
||||
/// Return `true` if a `Stmt` occurs at the end of a file.
|
||||
fn is_end_of_file(stmt: &Stmt, locator: &Locator) -> bool {
|
||||
stmt.end() == locator.contents().text_len()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use ruff_text_size::TextSize;
|
||||
use rustpython_parser as parser;
|
||||
use rustpython_parser::ast::Suite;
|
||||
use rustpython_parser::Parse;
|
||||
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
use crate::autofix::actions::{next_stmt_break, trailing_semicolon};
|
||||
use crate::autofix::edits::{next_stmt_break, trailing_semicolon};
|
||||
|
||||
#[test]
|
||||
fn find_semicolon() -> Result<()> {
|
||||
let contents = "x = 1";
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let program = Suite::parse(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = Locator::new(contents);
|
||||
assert_eq!(trailing_semicolon(stmt, &locator), None);
|
||||
|
||||
let contents = "x = 1; y = 1";
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let program = Suite::parse(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = Locator::new(contents);
|
||||
assert_eq!(trailing_semicolon(stmt, &locator), Some(TextSize::from(5)));
|
||||
|
||||
let contents = "x = 1 ; y = 1";
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let program = Suite::parse(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = Locator::new(contents);
|
||||
assert_eq!(trailing_semicolon(stmt, &locator), Some(TextSize::from(6)));
|
||||
@@ -455,7 +325,7 @@ x = 1 \
|
||||
; y = 1
|
||||
"#
|
||||
.trim();
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let program = Suite::parse(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = Locator::new(contents);
|
||||
assert_eq!(trailing_semicolon(stmt, &locator), Some(TextSize::from(10)));
|
||||
@@ -1,16 +1,18 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
use nohash_hasher::IntSet;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, IsolationLevel};
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
use crate::linter::FixTable;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
pub(crate) mod actions;
|
||||
pub(crate) mod codemods;
|
||||
pub(crate) mod edits;
|
||||
|
||||
/// Auto-fix errors in a file, and write the fixed source code to disk.
|
||||
pub(crate) fn fix_file(
|
||||
@@ -37,6 +39,7 @@ fn apply_fixes<'a>(
|
||||
let mut output = String::with_capacity(locator.len());
|
||||
let mut last_pos: Option<TextSize> = None;
|
||||
let mut applied: BTreeSet<&Edit> = BTreeSet::default();
|
||||
let mut isolated: IntSet<u32> = IntSet::default();
|
||||
let mut fixed = FxHashMap::default();
|
||||
|
||||
for (rule, fix) in diagnostics
|
||||
@@ -64,7 +67,19 @@ fn apply_fixes<'a>(
|
||||
continue;
|
||||
}
|
||||
|
||||
for edit in fix.edits() {
|
||||
// If this fix requires isolation, and we've already applied another fix in the
|
||||
// same isolation group, skip it.
|
||||
if let IsolationLevel::Group(id) = fix.isolation() {
|
||||
if !isolated.insert(id) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for edit in fix
|
||||
.edits()
|
||||
.iter()
|
||||
.sorted_unstable_by_key(|edit| edit.start())
|
||||
{
|
||||
// Add all contents from `last_pos` to `fix.location`.
|
||||
let slice = locator.slice(TextRange::new(last_pos.unwrap_or_default(), edit.start()));
|
||||
output.push_str(slice);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -39,9 +39,15 @@ pub(crate) fn check_logical_lines(
|
||||
let mut context = LogicalLinesContext::new(settings);
|
||||
|
||||
let should_fix_missing_whitespace = settings.rules.should_fix(Rule::MissingWhitespace);
|
||||
|
||||
let should_fix_whitespace_before_parameters =
|
||||
settings.rules.should_fix(Rule::WhitespaceBeforeParameters);
|
||||
let should_fix_whitespace_after_open_bracket =
|
||||
settings.rules.should_fix(Rule::WhitespaceAfterOpenBracket);
|
||||
let should_fix_whitespace_before_close_bracket = settings
|
||||
.rules
|
||||
.should_fix(Rule::WhitespaceBeforeCloseBracket);
|
||||
let should_fix_whitespace_before_punctuation =
|
||||
settings.rules.should_fix(Rule::WhitespaceBeforePunctuation);
|
||||
|
||||
let mut prev_line = None;
|
||||
let mut prev_indent_level = None;
|
||||
@@ -59,7 +65,13 @@ pub(crate) fn check_logical_lines(
|
||||
.flags()
|
||||
.intersects(TokenFlags::OPERATOR | TokenFlags::BRACKET | TokenFlags::PUNCTUATION)
|
||||
{
|
||||
extraneous_whitespace(&line, &mut context);
|
||||
extraneous_whitespace(
|
||||
&line,
|
||||
&mut context,
|
||||
should_fix_whitespace_after_open_bracket,
|
||||
should_fix_whitespace_before_close_bracket,
|
||||
should_fix_whitespace_before_punctuation,
|
||||
);
|
||||
}
|
||||
|
||||
if line.flags().contains(TokenFlags::KEYWORD) {
|
||||
|
||||
@@ -18,10 +18,9 @@ pub(crate) fn check_noqa(
|
||||
locator: &Locator,
|
||||
comment_ranges: &[TextRange],
|
||||
noqa_line_for: &NoqaMapping,
|
||||
analyze_directives: bool,
|
||||
settings: &Settings,
|
||||
) -> Vec<usize> {
|
||||
let enforce_noqa = settings.rules.enabled(Rule::UnusedNOQA);
|
||||
|
||||
// Identify any codes that are globally exempted (within the current file).
|
||||
let exemption = noqa::file_exemption(locator.contents(), comment_ranges);
|
||||
|
||||
@@ -93,7 +92,7 @@ pub(crate) fn check_noqa(
|
||||
}
|
||||
|
||||
// Enforce that the noqa directive was actually used (RUF100).
|
||||
if enforce_noqa {
|
||||
if analyze_directives && settings.rules.enabled(Rule::UnusedNOQA) {
|
||||
for line in noqa_directives.lines() {
|
||||
match &line.directive {
|
||||
Directive::All(leading_spaces, noqa_range, trailing_spaces) => {
|
||||
|
||||
@@ -4,7 +4,7 @@ use ruff_text_size::TextSize;
|
||||
use std::path::Path;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::newlines::StrExt;
|
||||
use ruff_newlines::StrExt;
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
|
||||
use crate::registry::Rule;
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use rustpython_parser::Tok;
|
||||
|
||||
use crate::directives::TodoComment;
|
||||
use crate::lex::docstring_detection::StateMachine;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::ruff::rules::Context;
|
||||
use crate::rules::{
|
||||
eradicate, flake8_commas, flake8_implicit_str_concat, flake8_pyi, flake8_quotes, flake8_todos,
|
||||
pycodestyle, pylint, pyupgrade, ruff,
|
||||
eradicate, flake8_commas, flake8_fixme, flake8_implicit_str_concat, flake8_pyi, flake8_quotes,
|
||||
flake8_todos, pycodestyle, pylint, pyupgrade, ruff,
|
||||
};
|
||||
use crate::settings::Settings;
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
@@ -60,6 +61,8 @@ pub(crate) fn check_tokens(
|
||||
]);
|
||||
let enforce_extraneous_parenthesis = settings.rules.enabled(Rule::ExtraneousParentheses);
|
||||
let enforce_type_comment_in_stub = settings.rules.enabled(Rule::TypeCommentInStub);
|
||||
|
||||
// Combine flake8_todos and flake8_fixme so that we can reuse detected [`TodoDirective`]s.
|
||||
let enforce_todos = settings.rules.any_enabled(&[
|
||||
Rule::InvalidTodoTag,
|
||||
Rule::MissingTodoAuthor,
|
||||
@@ -68,6 +71,10 @@ pub(crate) fn check_tokens(
|
||||
Rule::MissingTodoDescription,
|
||||
Rule::InvalidTodoCapitalization,
|
||||
Rule::MissingSpaceAfterTodoColon,
|
||||
Rule::LineContainsFixme,
|
||||
Rule::LineContainsXxx,
|
||||
Rule::LineContainsTodo,
|
||||
Rule::LineContainsHack,
|
||||
]);
|
||||
|
||||
// RUF001, RUF002, RUF003
|
||||
@@ -184,9 +191,26 @@ pub(crate) fn check_tokens(
|
||||
}
|
||||
|
||||
// TD001, TD002, TD003, TD004, TD005, TD006, TD007
|
||||
// T001, T002, T003, T004
|
||||
if enforce_todos {
|
||||
let todo_comments: Vec<TodoComment> = indexer
|
||||
.comment_ranges()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, comment_range)| {
|
||||
let comment = locator.slice(*comment_range);
|
||||
TodoComment::from_comment(comment, *comment_range, i)
|
||||
})
|
||||
.collect();
|
||||
|
||||
diagnostics.extend(
|
||||
flake8_todos::rules::todos(indexer, locator, settings)
|
||||
flake8_todos::rules::todos(&todo_comments, indexer, locator, settings)
|
||||
.into_iter()
|
||||
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
|
||||
);
|
||||
|
||||
diagnostics.extend(
|
||||
flake8_fixme::rules::todos(&todo_comments)
|
||||
.into_iter()
|
||||
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ use libcst_native::{
|
||||
Arg, Attribute, Call, Comparison, CompoundStatement, Dict, Expression, FormattedString,
|
||||
FormattedStringContent, FormattedStringExpression, FunctionDef, GeneratorExp, If, Import,
|
||||
ImportAlias, ImportFrom, ImportNames, IndentedBlock, Lambda, ListComp, Module, Name,
|
||||
SimpleString, SmallStatement, Statement, Suite, Tuple, With,
|
||||
SmallStatement, Statement, Suite, Tuple, With,
|
||||
};
|
||||
|
||||
pub(crate) fn match_module(module_text: &str) -> Result<Module> {
|
||||
@@ -109,16 +109,6 @@ pub(crate) fn match_attribute<'a, 'b>(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_simple_string<'a, 'b>(
|
||||
expression: &'a mut Expression<'b>,
|
||||
) -> Result<&'a mut SimpleString<'b>> {
|
||||
if let Expression::SimpleString(simple_string) = expression {
|
||||
Ok(simple_string)
|
||||
} else {
|
||||
bail!("Expected Expression::SimpleString")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_formatted_string<'a, 'b>(
|
||||
expression: &'a mut Expression<'b>,
|
||||
) -> Result<&'a mut FormattedString<'b>> {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
//! Extract `# noqa` and `# isort: skip` directives from tokenized source.
|
||||
//! Extract `# noqa`, `# isort: skip`, and `# TODO` directives from tokenized source.
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
@@ -217,6 +219,133 @@ fn extract_isort_directives(lxr: &[LexResult], locator: &Locator) -> IsortDirect
|
||||
}
|
||||
}
|
||||
|
||||
/// A comment that contains a [`TodoDirective`]
|
||||
pub(crate) struct TodoComment<'a> {
|
||||
/// The comment's text
|
||||
pub(crate) content: &'a str,
|
||||
/// The directive found within the comment.
|
||||
pub(crate) directive: TodoDirective<'a>,
|
||||
/// The comment's actual [`TextRange`].
|
||||
pub(crate) range: TextRange,
|
||||
/// The comment range's position in [`Indexer`].comment_ranges()
|
||||
pub(crate) range_index: usize,
|
||||
}
|
||||
|
||||
impl<'a> TodoComment<'a> {
|
||||
/// Attempt to transform a normal comment into a [`TodoComment`].
|
||||
pub(crate) fn from_comment(
|
||||
content: &'a str,
|
||||
range: TextRange,
|
||||
range_index: usize,
|
||||
) -> Option<Self> {
|
||||
TodoDirective::from_comment(content, range).map(|directive| Self {
|
||||
content,
|
||||
directive,
|
||||
range,
|
||||
range_index,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct TodoDirective<'a> {
|
||||
/// The actual directive
|
||||
pub(crate) content: &'a str,
|
||||
/// The directive's [`TextRange`] in the file.
|
||||
pub(crate) range: TextRange,
|
||||
/// The directive's kind: HACK, XXX, FIXME, or TODO.
|
||||
pub(crate) kind: TodoDirectiveKind,
|
||||
}
|
||||
|
||||
impl<'a> TodoDirective<'a> {
|
||||
/// Extract a [`TodoDirective`] from a comment.
|
||||
pub(crate) fn from_comment(comment: &'a str, comment_range: TextRange) -> Option<Self> {
|
||||
// The directive's offset from the start of the comment.
|
||||
let mut relative_offset = TextSize::new(0);
|
||||
let mut subset_opt = Some(comment);
|
||||
|
||||
// Loop over `#`-delimited sections of the comment to check for directives. This will
|
||||
// correctly handle cases like `# foo # TODO`.
|
||||
while let Some(subset) = subset_opt {
|
||||
let trimmed = subset.trim_start_matches('#').trim_start();
|
||||
|
||||
let offset = subset.text_len() - trimmed.text_len();
|
||||
relative_offset += offset;
|
||||
|
||||
// If we detect a TodoDirectiveKind variant substring in the comment, construct and
|
||||
// return the appropriate TodoDirective
|
||||
if let Ok(directive_kind) = trimmed.parse::<TodoDirectiveKind>() {
|
||||
let len = directive_kind.len();
|
||||
|
||||
return Some(Self {
|
||||
content: &comment[TextRange::at(relative_offset, len)],
|
||||
range: TextRange::at(comment_range.start() + relative_offset, len),
|
||||
kind: directive_kind,
|
||||
});
|
||||
}
|
||||
|
||||
// Shrink the subset to check for the next phrase starting with "#".
|
||||
subset_opt = if let Some(new_offset) = trimmed.find('#') {
|
||||
relative_offset += TextSize::try_from(new_offset).unwrap();
|
||||
subset.get(relative_offset.to_usize()..)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum TodoDirectiveKind {
|
||||
Todo,
|
||||
Fixme,
|
||||
Xxx,
|
||||
Hack,
|
||||
}
|
||||
|
||||
impl FromStr for TodoDirectiveKind {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// The lengths of the respective variant strings: TODO, FIXME, HACK, XXX
|
||||
for length in [3, 4, 5] {
|
||||
let Some(substr) = s.get(..length) else {
|
||||
break;
|
||||
};
|
||||
|
||||
match substr.to_lowercase().as_str() {
|
||||
"fixme" => {
|
||||
return Ok(TodoDirectiveKind::Fixme);
|
||||
}
|
||||
"hack" => {
|
||||
return Ok(TodoDirectiveKind::Hack);
|
||||
}
|
||||
"todo" => {
|
||||
return Ok(TodoDirectiveKind::Todo);
|
||||
}
|
||||
"xxx" => {
|
||||
return Ok(TodoDirectiveKind::Xxx);
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TodoDirectiveKind {
|
||||
fn len(&self) -> TextSize {
|
||||
match self {
|
||||
TodoDirectiveKind::Xxx => TextSize::new(3),
|
||||
TodoDirectiveKind::Hack | TodoDirectiveKind::Todo => TextSize::new(4),
|
||||
TodoDirectiveKind::Fixme => TextSize::new(5),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
@@ -225,7 +354,9 @@ mod tests {
|
||||
|
||||
use ruff_python_ast::source_code::{Indexer, Locator};
|
||||
|
||||
use crate::directives::{extract_isort_directives, extract_noqa_line_for};
|
||||
use crate::directives::{
|
||||
extract_isort_directives, extract_noqa_line_for, TodoDirective, TodoDirectiveKind,
|
||||
};
|
||||
use crate::noqa::NoqaMapping;
|
||||
|
||||
fn noqa_mappings(contents: &str) -> NoqaMapping {
|
||||
@@ -428,4 +559,62 @@ z = x + 1";
|
||||
vec![TextSize::from(13)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn todo_directives() {
|
||||
let test_comment = "# TODO: todo tag";
|
||||
let test_comment_range = TextRange::at(TextSize::new(0), test_comment.text_len());
|
||||
let expected = TodoDirective {
|
||||
content: "TODO",
|
||||
range: TextRange::new(TextSize::new(2), TextSize::new(6)),
|
||||
kind: TodoDirectiveKind::Todo,
|
||||
};
|
||||
assert_eq!(
|
||||
expected,
|
||||
TodoDirective::from_comment(test_comment, test_comment_range).unwrap()
|
||||
);
|
||||
|
||||
let test_comment = "#TODO: todo tag";
|
||||
let test_comment_range = TextRange::at(TextSize::new(0), test_comment.text_len());
|
||||
let expected = TodoDirective {
|
||||
content: "TODO",
|
||||
range: TextRange::new(TextSize::new(1), TextSize::new(5)),
|
||||
kind: TodoDirectiveKind::Todo,
|
||||
};
|
||||
assert_eq!(
|
||||
expected,
|
||||
TodoDirective::from_comment(test_comment, test_comment_range).unwrap()
|
||||
);
|
||||
|
||||
let test_comment = "# fixme: fixme tag";
|
||||
let test_comment_range = TextRange::at(TextSize::new(0), test_comment.text_len());
|
||||
let expected = TodoDirective {
|
||||
content: "fixme",
|
||||
range: TextRange::new(TextSize::new(2), TextSize::new(7)),
|
||||
kind: TodoDirectiveKind::Fixme,
|
||||
};
|
||||
assert_eq!(
|
||||
expected,
|
||||
TodoDirective::from_comment(test_comment, test_comment_range).unwrap()
|
||||
);
|
||||
|
||||
let test_comment = "# noqa # TODO: todo";
|
||||
let test_comment_range = TextRange::at(TextSize::new(0), test_comment.text_len());
|
||||
let expected = TodoDirective {
|
||||
content: "TODO",
|
||||
range: TextRange::new(TextSize::new(9), TextSize::new(13)),
|
||||
kind: TodoDirectiveKind::Todo,
|
||||
};
|
||||
assert_eq!(
|
||||
expected,
|
||||
TodoDirective::from_comment(test_comment, test_comment_range).unwrap()
|
||||
);
|
||||
|
||||
let test_comment = "# no directive";
|
||||
let test_comment_range = TextRange::at(TextSize::new(0), test_comment.text_len());
|
||||
assert_eq!(
|
||||
None,
|
||||
TodoDirective::from_comment(test_comment, test_comment_range)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use rustpython_parser::ast::{self, Constant, Expr, Ranged, Stmt, Suite};
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use rustpython_parser::Tok;
|
||||
|
||||
use ruff_python_ast::newlines::UniversalNewlineIterator;
|
||||
use ruff_newlines::UniversalNewlineIterator;
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::iter::FusedIterator;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
use ruff_python_ast::newlines::{StrExt, UniversalNewlineIterator};
|
||||
use ruff_newlines::{StrExt, UniversalNewlineIterator};
|
||||
use ruff_python_ast::whitespace;
|
||||
|
||||
use crate::docstrings::styles::SectionStyle;
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::rules::flake8_pytest_style::types::{
|
||||
ParametrizeNameType, ParametrizeValuesRowType, ParametrizeValuesType,
|
||||
};
|
||||
use crate::rules::flake8_quotes::settings::Quote;
|
||||
use crate::rules::flake8_tidy_imports::relative_imports::Strictness;
|
||||
use crate::rules::flake8_tidy_imports::settings::Strictness;
|
||||
use crate::rules::pydocstyle::settings::Convention;
|
||||
use crate::rules::{
|
||||
flake8_annotations, flake8_bugbear, flake8_builtins, flake8_errmsg, flake8_pytest_style,
|
||||
|
||||
@@ -1,24 +1,86 @@
|
||||
//! Insert statements into Python code.
|
||||
use std::ops::Add;
|
||||
|
||||
use ruff_text_size::TextSize;
|
||||
use rustpython_parser::ast::{Ranged, Stmt};
|
||||
use rustpython_parser::{lexer, Mode, Tok};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_newlines::UniversalNewlineIterator;
|
||||
use ruff_python_ast::helpers::is_docstring_stmt;
|
||||
use ruff_python_ast::source_code::{Locator, Stylist};
|
||||
use ruff_textwrap::indent;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) struct Insertion {
|
||||
pub(super) enum Placement<'a> {
|
||||
/// The content will be inserted inline with the existing code (i.e., within semicolon-delimited
|
||||
/// statements).
|
||||
Inline,
|
||||
/// The content will be inserted on its own line.
|
||||
OwnLine,
|
||||
/// The content will be inserted as an indented block.
|
||||
Indented(&'a str),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) struct Insertion<'a> {
|
||||
/// The content to add before the insertion.
|
||||
prefix: &'static str,
|
||||
prefix: &'a str,
|
||||
/// The location at which to insert.
|
||||
location: TextSize,
|
||||
/// The content to add after the insertion.
|
||||
suffix: &'static str,
|
||||
suffix: &'a str,
|
||||
/// The line placement of insertion.
|
||||
placement: Placement<'a>,
|
||||
}
|
||||
|
||||
impl Insertion {
|
||||
/// Create an [`Insertion`] to insert (e.g.) an import after the end of the given [`Stmt`],
|
||||
/// along with a prefix and suffix to use for the insertion.
|
||||
impl<'a> Insertion<'a> {
|
||||
/// Create an [`Insertion`] to insert (e.g.) an import statement at the start of a given
|
||||
/// file, along with a prefix and suffix to use for the insertion.
|
||||
///
|
||||
/// For example, given the following code:
|
||||
///
|
||||
/// ```python
|
||||
/// """Hello, world!"""
|
||||
///
|
||||
/// import os
|
||||
/// ```
|
||||
///
|
||||
/// The insertion returned will begin at the start of the `import os` statement, and will
|
||||
/// include a trailing newline.
|
||||
pub(super) fn start_of_file(
|
||||
body: &[Stmt],
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
) -> Insertion<'static> {
|
||||
// Skip over any docstrings.
|
||||
let mut location = if let Some(location) = match_docstring_end(body) {
|
||||
// If the first token after the docstring is a semicolon, insert after the semicolon as
|
||||
// an inline statement.
|
||||
if let Some(offset) = match_leading_semicolon(locator.after(location)) {
|
||||
return Insertion::inline(" ", location.add(offset).add(TextSize::of(';')), ";");
|
||||
}
|
||||
|
||||
// Otherwise, advance to the next row.
|
||||
locator.full_line_end(location)
|
||||
} else {
|
||||
TextSize::default()
|
||||
};
|
||||
|
||||
// Skip over commented lines.
|
||||
for line in UniversalNewlineIterator::with_offset(locator.after(location), location) {
|
||||
if line.trim_start().starts_with('#') {
|
||||
location = line.full_end();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Insertion::own_line("", location, stylist.line_ending().as_str())
|
||||
}
|
||||
|
||||
/// Create an [`Insertion`] to insert (e.g.) an import after the end of the given
|
||||
/// [`Stmt`], along with a prefix and suffix to use for the insertion.
|
||||
///
|
||||
/// For example, given the following code:
|
||||
///
|
||||
@@ -34,18 +96,22 @@ impl Insertion {
|
||||
/// ```
|
||||
///
|
||||
/// The insertion returned will begin after the newline after the last import statement, which
|
||||
/// in this case is the line after `import math`, and will include a trailing newline suffix.
|
||||
pub(super) fn end_of_statement(stmt: &Stmt, locator: &Locator, stylist: &Stylist) -> Insertion {
|
||||
/// in this case is the line after `import math`, and will include a trailing newline.
|
||||
///
|
||||
/// The statement itself is assumed to be at the top-level of the module.
|
||||
pub(super) fn end_of_statement(
|
||||
stmt: &Stmt,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
) -> Insertion<'static> {
|
||||
let location = stmt.end();
|
||||
let mut tokens =
|
||||
lexer::lex_starts_at(locator.after(location), Mode::Module, location).flatten();
|
||||
if let Some((Tok::Semi, range)) = tokens.next() {
|
||||
// If the first token after the docstring is a semicolon, insert after the semicolon as an
|
||||
// inline statement;
|
||||
Insertion::new(" ", range.end(), ";")
|
||||
if let Some(offset) = match_leading_semicolon(locator.after(location)) {
|
||||
// If the first token after the statement is a semicolon, insert after the semicolon as
|
||||
// an inline statement.
|
||||
Insertion::inline(" ", location.add(offset).add(TextSize::of(';')), ";")
|
||||
} else {
|
||||
// Otherwise, insert on the next line.
|
||||
Insertion::new(
|
||||
Insertion::own_line(
|
||||
"",
|
||||
locator.full_line_end(location),
|
||||
stylist.line_ending().as_str(),
|
||||
@@ -53,57 +119,89 @@ impl Insertion {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an [`Insertion`] to insert (e.g.) an import statement at the "top" of a given file,
|
||||
/// along with a prefix and suffix to use for the insertion.
|
||||
/// Create an [`Insertion`] to insert (e.g.) an import statement at the start of a given
|
||||
/// block, along with a prefix and suffix to use for the insertion.
|
||||
///
|
||||
/// For example, given the following code:
|
||||
///
|
||||
/// ```python
|
||||
/// """Hello, world!"""
|
||||
///
|
||||
/// import os
|
||||
/// if TYPE_CHECKING:
|
||||
/// import os
|
||||
/// ```
|
||||
///
|
||||
/// The insertion returned will begin at the start of the `import os` statement, and will
|
||||
/// include a trailing newline suffix.
|
||||
pub(super) fn top_of_file(body: &[Stmt], locator: &Locator, stylist: &Stylist) -> Insertion {
|
||||
// Skip over any docstrings.
|
||||
let mut location = if let Some(location) = match_docstring_end(body) {
|
||||
// If the first token after the docstring is a semicolon, insert after the semicolon as an
|
||||
// inline statement;
|
||||
let first_token = lexer::lex_starts_at(locator.after(location), Mode::Module, location)
|
||||
.flatten()
|
||||
.next();
|
||||
if let Some((Tok::Semi, range)) = first_token {
|
||||
return Insertion::new(" ", range.end(), ";");
|
||||
}
|
||||
/// include a trailing newline.
|
||||
///
|
||||
/// The block itself is assumed to be at the top-level of the module.
|
||||
pub(super) fn start_of_block(
|
||||
mut location: TextSize,
|
||||
locator: &Locator<'a>,
|
||||
stylist: &Stylist,
|
||||
) -> Insertion<'a> {
|
||||
enum Awaiting {
|
||||
Colon(u32),
|
||||
Newline,
|
||||
Indent,
|
||||
}
|
||||
|
||||
// Otherwise, advance to the next row.
|
||||
locator.full_line_end(location)
|
||||
} else {
|
||||
TextSize::default()
|
||||
};
|
||||
|
||||
// Skip over any comments and empty lines.
|
||||
let mut state = Awaiting::Colon(0);
|
||||
for (tok, range) in
|
||||
lexer::lex_starts_at(locator.after(location), Mode::Module, location).flatten()
|
||||
{
|
||||
if matches!(tok, Tok::Comment(..) | Tok::Newline) {
|
||||
location = locator.full_line_end(range.end());
|
||||
} else {
|
||||
break;
|
||||
match state {
|
||||
// Iterate until we find the colon indicating the start of the block body.
|
||||
Awaiting::Colon(depth) => match tok {
|
||||
Tok::Colon if depth == 0 => {
|
||||
state = Awaiting::Newline;
|
||||
}
|
||||
Tok::Lpar | Tok::Lbrace | Tok::Lsqb => {
|
||||
state = Awaiting::Colon(depth.saturating_add(1));
|
||||
}
|
||||
Tok::Rpar | Tok::Rbrace | Tok::Rsqb => {
|
||||
state = Awaiting::Colon(depth.saturating_sub(1));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
// Once we've seen the colon, we're looking for a newline; otherwise, there's no
|
||||
// block body (e.g. `if True: pass`).
|
||||
Awaiting::Newline => match tok {
|
||||
Tok::Comment(..) => {}
|
||||
Tok::Newline => {
|
||||
state = Awaiting::Indent;
|
||||
}
|
||||
_ => {
|
||||
location = range.start();
|
||||
break;
|
||||
}
|
||||
},
|
||||
// Once we've seen the newline, we're looking for the indentation of the block body.
|
||||
Awaiting::Indent => match tok {
|
||||
Tok::Comment(..) => {}
|
||||
Tok::NonLogicalNewline => {}
|
||||
Tok::Indent => {
|
||||
// This is like:
|
||||
// ```py
|
||||
// if True:
|
||||
// pass
|
||||
// ```
|
||||
// Where `range` is the indentation before the `pass` token.
|
||||
return Insertion::indented(
|
||||
"",
|
||||
range.start(),
|
||||
stylist.line_ending().as_str(),
|
||||
locator.slice(range),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
location = range.start();
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Insertion::new("", location, stylist.line_ending().as_str())
|
||||
}
|
||||
|
||||
fn new(prefix: &'static str, location: TextSize, suffix: &'static str) -> Self {
|
||||
Self {
|
||||
prefix,
|
||||
location,
|
||||
suffix,
|
||||
}
|
||||
// This is like: `if True: pass`, where `location` is the start of the `pass` token.
|
||||
Insertion::inline("", location, "; ")
|
||||
}
|
||||
|
||||
/// Convert this [`Insertion`] into an [`Edit`] that inserts the given content.
|
||||
@@ -112,8 +210,59 @@ impl Insertion {
|
||||
prefix,
|
||||
location,
|
||||
suffix,
|
||||
placement,
|
||||
} = self;
|
||||
Edit::insertion(format!("{prefix}{content}{suffix}"), location)
|
||||
let content = format!("{prefix}{content}{suffix}");
|
||||
Edit::insertion(
|
||||
match placement {
|
||||
Placement::Indented(indentation) if !indentation.is_empty() => {
|
||||
indent(&content, indentation).to_string()
|
||||
}
|
||||
_ => content,
|
||||
},
|
||||
location,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if this [`Insertion`] is inline.
|
||||
pub(super) fn is_inline(&self) -> bool {
|
||||
matches!(self.placement, Placement::Inline)
|
||||
}
|
||||
|
||||
/// Create an [`Insertion`] that inserts content inline (i.e., within semicolon-delimited
|
||||
/// statements).
|
||||
fn inline(prefix: &'a str, location: TextSize, suffix: &'a str) -> Self {
|
||||
Self {
|
||||
prefix,
|
||||
location,
|
||||
suffix,
|
||||
placement: Placement::Inline,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an [`Insertion`] that starts on its own line.
|
||||
fn own_line(prefix: &'a str, location: TextSize, suffix: &'a str) -> Self {
|
||||
Self {
|
||||
prefix,
|
||||
location,
|
||||
suffix,
|
||||
placement: Placement::OwnLine,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an [`Insertion`] that starts on its own line, with the given indentation.
|
||||
fn indented(
|
||||
prefix: &'a str,
|
||||
location: TextSize,
|
||||
suffix: &'a str,
|
||||
indentation: &'a str,
|
||||
) -> Self {
|
||||
Self {
|
||||
prefix,
|
||||
location,
|
||||
suffix,
|
||||
placement: Placement::Indented(indentation),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,32 +284,45 @@ fn match_docstring_end(body: &[Stmt]) -> Option<TextSize> {
|
||||
Some(stmt.end())
|
||||
}
|
||||
|
||||
/// If a line starts with a semicolon, return its offset.
|
||||
fn match_leading_semicolon(s: &str) -> Option<TextSize> {
|
||||
for (offset, c) in s.char_indices() {
|
||||
match c {
|
||||
' ' | '\t' => continue,
|
||||
';' => return Some(TextSize::try_from(offset).unwrap()),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use ruff_text_size::TextSize;
|
||||
use rustpython_parser as parser;
|
||||
use rustpython_parser::ast::Suite;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use rustpython_parser::Parse;
|
||||
|
||||
use ruff_python_ast::newlines::LineEnding;
|
||||
use ruff_newlines::LineEnding;
|
||||
use ruff_python_ast::source_code::{Locator, Stylist};
|
||||
|
||||
use super::Insertion;
|
||||
|
||||
fn insert(contents: &str) -> Result<Insertion> {
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
|
||||
let locator = Locator::new(contents);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
Ok(Insertion::top_of_file(&program, &locator, &stylist))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn top_of_file() -> Result<()> {
|
||||
fn start_of_file() -> Result<()> {
|
||||
fn insert(contents: &str) -> Result<Insertion> {
|
||||
let program = Suite::parse(contents, "<filename>")?;
|
||||
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
|
||||
let locator = Locator::new(contents);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
Ok(Insertion::start_of_file(&program, &locator, &stylist))
|
||||
}
|
||||
|
||||
let contents = "";
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", TextSize::from(0), LineEnding::default().as_str())
|
||||
Insertion::own_line("", TextSize::from(0), LineEnding::default().as_str())
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
@@ -168,7 +330,7 @@ mod tests {
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", TextSize::from(19), LineEnding::default().as_str())
|
||||
Insertion::own_line("", TextSize::from(19), LineEnding::default().as_str())
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
@@ -177,7 +339,7 @@ mod tests {
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", TextSize::from(20), "\n")
|
||||
Insertion::own_line("", TextSize::from(20), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
@@ -187,7 +349,7 @@ mod tests {
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", TextSize::from(40), "\n")
|
||||
Insertion::own_line("", TextSize::from(40), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
@@ -196,7 +358,7 @@ x = 1
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", TextSize::from(0), "\n")
|
||||
Insertion::own_line("", TextSize::from(0), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
@@ -205,7 +367,7 @@ x = 1
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", TextSize::from(23), "\n")
|
||||
Insertion::own_line("", TextSize::from(23), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
@@ -215,7 +377,7 @@ x = 1
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", TextSize::from(43), "\n")
|
||||
Insertion::own_line("", TextSize::from(43), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
@@ -225,7 +387,7 @@ x = 1
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", TextSize::from(43), "\n")
|
||||
Insertion::own_line("", TextSize::from(43), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
@@ -234,7 +396,7 @@ x = 1
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new("", TextSize::from(0), "\n")
|
||||
Insertion::own_line("", TextSize::from(0), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
@@ -243,7 +405,7 @@ x = 1
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new(" ", TextSize::from(20), ";")
|
||||
Insertion::inline(" ", TextSize::from(20), ";")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
@@ -253,9 +415,35 @@ x = 1
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::new(" ", TextSize::from(20), ";")
|
||||
Insertion::inline(" ", TextSize::from(20), ";")
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_of_block() {
|
||||
fn insert(contents: &str, offset: TextSize) -> Insertion {
|
||||
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
|
||||
let locator = Locator::new(contents);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
Insertion::start_of_block(offset, &locator, &stylist)
|
||||
}
|
||||
|
||||
let contents = "if True: pass";
|
||||
assert_eq!(
|
||||
insert(contents, TextSize::from(0)),
|
||||
Insertion::inline("", TextSize::from(9), "; ")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents, TextSize::from(0)),
|
||||
Insertion::indented("", TextSize::from(9), "\n", " ")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
//! Add and modify import statements to make module members available during fix execution.
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use libcst_native::{Codegen, CodegenState, ImportAlias, Name, NameOrAttribute};
|
||||
use std::error::Error;
|
||||
|
||||
use anyhow::Result;
|
||||
use libcst_native::{ImportAlias, Name, NameOrAttribute};
|
||||
use ruff_text_size::TextSize;
|
||||
use rustpython_parser::ast::{self, Ranged, Stmt, Suite};
|
||||
|
||||
use crate::autofix;
|
||||
use crate::autofix::codemods::CodegenStylist;
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::imports::{AnyImport, Import};
|
||||
use ruff_python_ast::imports::{AnyImport, Import, ImportFrom};
|
||||
use ruff_python_ast::source_code::{Locator, Stylist};
|
||||
use ruff_python_semantic::model::SemanticModel;
|
||||
use ruff_textwrap::indent;
|
||||
|
||||
use crate::cst::matchers::{match_aliases, match_import_from, match_statement};
|
||||
use crate::importer::insertion::Insertion;
|
||||
@@ -16,10 +21,16 @@ use crate::importer::insertion::Insertion;
|
||||
mod insertion;
|
||||
|
||||
pub(crate) struct Importer<'a> {
|
||||
/// The Python AST to which we are adding imports.
|
||||
python_ast: &'a Suite,
|
||||
/// The [`Locator`] for the Python AST.
|
||||
locator: &'a Locator<'a>,
|
||||
/// The [`Stylist`] for the Python AST.
|
||||
stylist: &'a Stylist<'a>,
|
||||
ordered_imports: Vec<&'a Stmt>,
|
||||
/// The list of visited, top-level runtime imports in the Python AST.
|
||||
runtime_imports: Vec<&'a Stmt>,
|
||||
/// The list of visited, top-level `if TYPE_CHECKING:` blocks in the Python AST.
|
||||
type_checking_blocks: Vec<&'a Stmt>,
|
||||
}
|
||||
|
||||
impl<'a> Importer<'a> {
|
||||
@@ -32,13 +43,19 @@ impl<'a> Importer<'a> {
|
||||
python_ast,
|
||||
locator,
|
||||
stylist,
|
||||
ordered_imports: Vec::default(),
|
||||
runtime_imports: Vec::default(),
|
||||
type_checking_blocks: Vec::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Visit a top-level import statement.
|
||||
pub(crate) fn visit_import(&mut self, import: &'a Stmt) {
|
||||
self.ordered_imports.push(import);
|
||||
self.runtime_imports.push(import);
|
||||
}
|
||||
|
||||
/// Visit a top-level type-checking block.
|
||||
pub(crate) fn visit_type_checking_block(&mut self, type_checking_block: &'a Stmt) {
|
||||
self.type_checking_blocks.push(type_checking_block);
|
||||
}
|
||||
|
||||
/// Add an import statement to import the given module.
|
||||
@@ -53,52 +70,134 @@ impl<'a> Importer<'a> {
|
||||
Insertion::end_of_statement(stmt, self.locator, self.stylist)
|
||||
.into_edit(&required_import)
|
||||
} else {
|
||||
// Insert at the top of the file.
|
||||
Insertion::top_of_file(self.python_ast, self.locator, self.stylist)
|
||||
// Insert at the start of the file.
|
||||
Insertion::start_of_file(self.python_ast, self.locator, self.stylist)
|
||||
.into_edit(&required_import)
|
||||
}
|
||||
}
|
||||
|
||||
/// Move an existing import to the top-level, thereby making it available at runtime.
|
||||
///
|
||||
/// If there are no existing imports, the new import will be added at the top
|
||||
/// of the file. Otherwise, it will be added after the most recent top-level
|
||||
/// import statement.
|
||||
pub(crate) fn runtime_import_edit(
|
||||
&self,
|
||||
import: &StmtImport,
|
||||
at: TextSize,
|
||||
) -> Result<RuntimeImportEdit> {
|
||||
// Generate the modified import statement.
|
||||
let content = autofix::codemods::retain_imports(
|
||||
&[import.qualified_name],
|
||||
import.stmt,
|
||||
self.locator,
|
||||
self.stylist,
|
||||
)?;
|
||||
|
||||
// Add the import to the top-level.
|
||||
let insertion = if let Some(stmt) = self.preceding_import(at) {
|
||||
// Insert after the last top-level import.
|
||||
Insertion::end_of_statement(stmt, self.locator, self.stylist)
|
||||
} else {
|
||||
// Insert at the start of the file.
|
||||
Insertion::start_of_file(self.python_ast, self.locator, self.stylist)
|
||||
};
|
||||
let add_import_edit = insertion.into_edit(&content);
|
||||
|
||||
Ok(RuntimeImportEdit { add_import_edit })
|
||||
}
|
||||
|
||||
/// Move an existing import into a `TYPE_CHECKING` block.
|
||||
///
|
||||
/// If there are no existing `TYPE_CHECKING` blocks, a new one will be added at the top
|
||||
/// of the file. Otherwise, it will be added after the most recent top-level
|
||||
/// `TYPE_CHECKING` block.
|
||||
pub(crate) fn typing_import_edit(
|
||||
&self,
|
||||
import: &StmtImport,
|
||||
at: TextSize,
|
||||
semantic_model: &SemanticModel,
|
||||
) -> Result<TypingImportEdit> {
|
||||
// Generate the modified import statement.
|
||||
let content = autofix::codemods::retain_imports(
|
||||
&[import.qualified_name],
|
||||
import.stmt,
|
||||
self.locator,
|
||||
self.stylist,
|
||||
)?;
|
||||
|
||||
// Import the `TYPE_CHECKING` symbol from the typing module.
|
||||
let (type_checking_edit, type_checking) = self.get_or_import_symbol(
|
||||
&ImportRequest::import_from("typing", "TYPE_CHECKING"),
|
||||
at,
|
||||
semantic_model,
|
||||
)?;
|
||||
|
||||
// Add the import to a `TYPE_CHECKING` block.
|
||||
let add_import_edit = if let Some(block) = self.preceding_type_checking_block(at) {
|
||||
// Add the import to the `TYPE_CHECKING` block.
|
||||
self.add_to_type_checking_block(&content, block.start())
|
||||
} else {
|
||||
// Add the import to a new `TYPE_CHECKING` block.
|
||||
self.add_type_checking_block(
|
||||
&format!(
|
||||
"{}if {type_checking}:{}{}",
|
||||
self.stylist.line_ending().as_str(),
|
||||
self.stylist.line_ending().as_str(),
|
||||
indent(&content, self.stylist.indentation())
|
||||
),
|
||||
at,
|
||||
)?
|
||||
};
|
||||
|
||||
Ok(TypingImportEdit {
|
||||
type_checking_edit,
|
||||
add_import_edit,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate an [`Edit`] to reference the given symbol. Returns the [`Edit`] necessary to make
|
||||
/// the symbol available in the current scope along with the bound name of the symbol.
|
||||
///
|
||||
/// Attempts to reuse existing imports when possible.
|
||||
pub(crate) fn get_or_import_symbol(
|
||||
&self,
|
||||
module: &str,
|
||||
member: &str,
|
||||
symbol: &ImportRequest,
|
||||
at: TextSize,
|
||||
semantic_model: &SemanticModel,
|
||||
) -> Result<(Edit, String)> {
|
||||
self.get_symbol(module, member, at, semantic_model)?
|
||||
.map_or_else(
|
||||
|| self.import_symbol(module, member, at, semantic_model),
|
||||
Ok,
|
||||
)
|
||||
) -> Result<(Edit, String), ResolutionError> {
|
||||
match self.get_symbol(symbol, at, semantic_model) {
|
||||
Some(result) => result,
|
||||
None => self.import_symbol(symbol, at, semantic_model),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an [`Edit`] to reference an existing symbol, if it's present in the given [`SemanticModel`].
|
||||
fn get_symbol(
|
||||
&self,
|
||||
module: &str,
|
||||
member: &str,
|
||||
symbol: &ImportRequest,
|
||||
at: TextSize,
|
||||
semantic_model: &SemanticModel,
|
||||
) -> Result<Option<(Edit, String)>> {
|
||||
) -> Option<Result<(Edit, String), ResolutionError>> {
|
||||
// If the symbol is already available in the current scope, use it.
|
||||
let Some((source, binding)) = semantic_model.resolve_qualified_import_name(module, member) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let imported_name =
|
||||
semantic_model.resolve_qualified_import_name(symbol.module, symbol.member)?;
|
||||
|
||||
// The exception: the symbol source (i.e., the import statement) comes after the current
|
||||
// location. For example, we could be generating an edit within a function, and the import
|
||||
// If the symbol source (i.e., the import statement) comes after the current location,
|
||||
// abort. For example, we could be generating an edit within a function, and the import
|
||||
// could be defined in the module scope, but after the function definition. In this case,
|
||||
// it's unclear whether we can use the symbol (the function could be called between the
|
||||
// import and the current location, and thus the symbol would not be available). It's also
|
||||
// unclear whether should add an import statement at the top of the file, since it could
|
||||
// unclear whether should add an import statement at the start of the file, since it could
|
||||
// be shadowed between the import and the current location.
|
||||
if source.start() > at {
|
||||
bail!("Unable to use existing symbol `{binding}` due to late-import");
|
||||
if imported_name.range().start() > at {
|
||||
return Some(Err(ResolutionError::ImportAfterUsage));
|
||||
}
|
||||
|
||||
// If the symbol source (i.e., the import statement) is in a typing-only context, but we're
|
||||
// in a runtime context, abort.
|
||||
if imported_name.context().is_typing() && semantic_model.execution_context().is_runtime() {
|
||||
return Some(Err(ResolutionError::IncompatibleContext));
|
||||
}
|
||||
|
||||
// We also add a no-op edit to force conflicts with any other fixes that might try to
|
||||
@@ -118,66 +217,81 @@ impl<'a> Importer<'a> {
|
||||
// By adding this no-op edit, we force the `unused-imports` fix to conflict with the
|
||||
// `sys-exit-alias` fix, and thus will avoid applying both fixes in the same pass.
|
||||
let import_edit = Edit::range_replacement(
|
||||
self.locator.slice(source.range()).to_string(),
|
||||
source.range(),
|
||||
self.locator.slice(imported_name.range()).to_string(),
|
||||
imported_name.range(),
|
||||
);
|
||||
Ok(Some((import_edit, binding)))
|
||||
Some(Ok((import_edit, imported_name.into_name())))
|
||||
}
|
||||
|
||||
/// Generate an [`Edit`] to reference the given symbol. Returns the [`Edit`] necessary to make
|
||||
/// the symbol available in the current scope along with the bound name of the symbol.
|
||||
///
|
||||
/// For example, assuming `module` is `"functools"` and `member` is `"lru_cache"`, this function
|
||||
/// could return an [`Edit`] to add `import functools` to the top of the file, alongside with
|
||||
/// could return an [`Edit`] to add `import functools` to the start of the file, alongside with
|
||||
/// the name on which the `lru_cache` symbol would be made available (`"functools.lru_cache"`).
|
||||
fn import_symbol(
|
||||
&self,
|
||||
module: &str,
|
||||
member: &str,
|
||||
symbol: &ImportRequest,
|
||||
at: TextSize,
|
||||
semantic_model: &SemanticModel,
|
||||
) -> Result<(Edit, String)> {
|
||||
if let Some(stmt) = self.find_import_from(module, at) {
|
||||
) -> Result<(Edit, String), ResolutionError> {
|
||||
if let Some(stmt) = self.find_import_from(symbol.module, at) {
|
||||
// Case 1: `from functools import lru_cache` is in scope, and we're trying to reference
|
||||
// `functools.cache`; thus, we add `cache` to the import, and return `"cache"` as the
|
||||
// bound name.
|
||||
if semantic_model
|
||||
.find_binding(member)
|
||||
.map_or(true, |binding| binding.kind.is_builtin())
|
||||
{
|
||||
let import_edit = self.add_member(stmt, member)?;
|
||||
Ok((import_edit, member.to_string()))
|
||||
if semantic_model.is_unbound(symbol.member) {
|
||||
let Ok(import_edit) = self.add_member(stmt, symbol.member) else {
|
||||
return Err(ResolutionError::InvalidEdit);
|
||||
};
|
||||
Ok((import_edit, symbol.member.to_string()))
|
||||
} else {
|
||||
bail!("Unable to insert `{member}` into scope due to name conflict")
|
||||
Err(ResolutionError::ConflictingName(symbol.member.to_string()))
|
||||
}
|
||||
} else {
|
||||
// Case 2: No `functools` import is in scope; thus, we add `import functools`, and
|
||||
// return `"functools.cache"` as the bound name.
|
||||
if semantic_model
|
||||
.find_binding(module)
|
||||
.map_or(true, |binding| binding.kind.is_builtin())
|
||||
{
|
||||
let import_edit = self.add_import(&AnyImport::Import(Import::module(module)), at);
|
||||
Ok((import_edit, format!("{module}.{member}")))
|
||||
} else {
|
||||
bail!("Unable to insert `{module}` into scope due to name conflict")
|
||||
match symbol.style {
|
||||
ImportStyle::Import => {
|
||||
// Case 2a: No `functools` import is in scope; thus, we add `import functools`,
|
||||
// and return `"functools.cache"` as the bound name.
|
||||
if semantic_model.is_unbound(symbol.module) {
|
||||
let import_edit =
|
||||
self.add_import(&AnyImport::Import(Import::module(symbol.module)), at);
|
||||
Ok((
|
||||
import_edit,
|
||||
format!(
|
||||
"{module}.{member}",
|
||||
module = symbol.module,
|
||||
member = symbol.member
|
||||
),
|
||||
))
|
||||
} else {
|
||||
Err(ResolutionError::ConflictingName(symbol.module.to_string()))
|
||||
}
|
||||
}
|
||||
ImportStyle::ImportFrom => {
|
||||
// Case 2b: No `functools` import is in scope; thus, we add
|
||||
// `from functools import cache`, and return `"cache"` as the bound name.
|
||||
if semantic_model.is_unbound(symbol.member) {
|
||||
let import_edit = self.add_import(
|
||||
&AnyImport::ImportFrom(ImportFrom::member(
|
||||
symbol.module,
|
||||
symbol.member,
|
||||
)),
|
||||
at,
|
||||
);
|
||||
Ok((import_edit, symbol.member.to_string()))
|
||||
} else {
|
||||
Err(ResolutionError::ConflictingName(symbol.member.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the import statement that precedes the given position, if any.
|
||||
fn preceding_import(&self, at: TextSize) -> Option<&Stmt> {
|
||||
self.ordered_imports
|
||||
.partition_point(|stmt| stmt.start() < at)
|
||||
.checked_sub(1)
|
||||
.map(|idx| self.ordered_imports[idx])
|
||||
}
|
||||
|
||||
/// Return the top-level [`Stmt`] that imports the given module using `Stmt::ImportFrom`
|
||||
/// preceding the given position, if any.
|
||||
fn find_import_from(&self, module: &str, at: TextSize) -> Option<&Stmt> {
|
||||
let mut import_from = None;
|
||||
for stmt in &self.ordered_imports {
|
||||
for stmt in &self.runtime_imports {
|
||||
if stmt.start() >= at {
|
||||
break;
|
||||
}
|
||||
@@ -211,12 +325,163 @@ impl<'a> Importer<'a> {
|
||||
asname: None,
|
||||
comma: aliases.last().and_then(|alias| alias.comma.clone()),
|
||||
});
|
||||
let mut state = CodegenState {
|
||||
default_newline: &self.stylist.line_ending(),
|
||||
default_indent: self.stylist.indentation(),
|
||||
..CodegenState::default()
|
||||
Ok(Edit::range_replacement(
|
||||
statement.codegen_stylist(self.stylist),
|
||||
stmt.range(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Add a `TYPE_CHECKING` block to the given module.
|
||||
fn add_type_checking_block(&self, content: &str, at: TextSize) -> Result<Edit> {
|
||||
let insertion = if let Some(stmt) = self.preceding_import(at) {
|
||||
// Insert after the last top-level import.
|
||||
Insertion::end_of_statement(stmt, self.locator, self.stylist)
|
||||
} else {
|
||||
// Insert at the start of the file.
|
||||
Insertion::start_of_file(self.python_ast, self.locator, self.stylist)
|
||||
};
|
||||
statement.codegen(&mut state);
|
||||
Ok(Edit::range_replacement(state.to_string(), stmt.range()))
|
||||
if insertion.is_inline() {
|
||||
Err(anyhow::anyhow!(
|
||||
"Cannot insert `TYPE_CHECKING` block inline"
|
||||
))
|
||||
} else {
|
||||
Ok(insertion.into_edit(content))
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an import statement to an existing `TYPE_CHECKING` block.
|
||||
fn add_to_type_checking_block(&self, content: &str, at: TextSize) -> Edit {
|
||||
Insertion::start_of_block(at, self.locator, self.stylist).into_edit(content)
|
||||
}
|
||||
|
||||
/// Return the import statement that precedes the given position, if any.
|
||||
fn preceding_import(&self, at: TextSize) -> Option<&'a Stmt> {
|
||||
self.runtime_imports
|
||||
.partition_point(|stmt| stmt.start() < at)
|
||||
.checked_sub(1)
|
||||
.map(|idx| self.runtime_imports[idx])
|
||||
}
|
||||
|
||||
/// Return the `TYPE_CHECKING` block that precedes the given position, if any.
|
||||
fn preceding_type_checking_block(&self, at: TextSize) -> Option<&'a Stmt> {
|
||||
let block = self.type_checking_blocks.first()?;
|
||||
if block.start() <= at {
|
||||
Some(block)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An edit to the top-level of a module, making it available at runtime.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RuntimeImportEdit {
|
||||
/// The edit to add the import to the top-level of the module.
|
||||
add_import_edit: Edit,
|
||||
}
|
||||
|
||||
impl RuntimeImportEdit {
|
||||
pub(crate) fn into_edits(self) -> Vec<Edit> {
|
||||
vec![self.add_import_edit]
|
||||
}
|
||||
}
|
||||
|
||||
/// An edit to an import to a typing-only context.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TypingImportEdit {
|
||||
/// The edit to add the `TYPE_CHECKING` symbol to the module.
|
||||
type_checking_edit: Edit,
|
||||
/// The edit to add the import to a `TYPE_CHECKING` block.
|
||||
add_import_edit: Edit,
|
||||
}
|
||||
|
||||
impl TypingImportEdit {
|
||||
pub(crate) fn into_edits(self) -> Vec<Edit> {
|
||||
vec![self.type_checking_edit, self.add_import_edit]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ImportStyle {
|
||||
/// Import the symbol using the `import` statement (e.g. `import foo; foo.bar`).
|
||||
Import,
|
||||
/// Import the symbol using the `from` statement (e.g. `from foo import bar; bar`).
|
||||
ImportFrom,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ImportRequest<'a> {
|
||||
/// The module from which the symbol can be imported (e.g., `foo`, in `from foo import bar`).
|
||||
module: &'a str,
|
||||
/// The member to import (e.g., `bar`, in `from foo import bar`).
|
||||
member: &'a str,
|
||||
/// The preferred style to use when importing the symbol (e.g., `import foo` or
|
||||
/// `from foo import bar`), if it's not already in scope.
|
||||
style: ImportStyle,
|
||||
}
|
||||
|
||||
impl<'a> ImportRequest<'a> {
|
||||
/// Create a new `ImportRequest` from a module and member. If not present in the scope,
|
||||
/// the symbol should be imported using the "import" statement.
|
||||
pub(crate) fn import(module: &'a str, member: &'a str) -> Self {
|
||||
Self {
|
||||
module,
|
||||
member,
|
||||
style: ImportStyle::Import,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `ImportRequest` from a module and member. If not present in the scope,
|
||||
/// the symbol should be imported using the "import from" statement.
|
||||
pub(crate) fn import_from(module: &'a str, member: &'a str) -> Self {
|
||||
Self {
|
||||
module,
|
||||
member,
|
||||
style: ImportStyle::ImportFrom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An existing module or member import, located within an import statement.
|
||||
pub(crate) struct StmtImport<'a> {
|
||||
/// The import statement.
|
||||
pub(crate) stmt: &'a Stmt,
|
||||
/// The "full name" of the imported module or member.
|
||||
pub(crate) qualified_name: &'a str,
|
||||
}
|
||||
|
||||
/// The result of an [`Importer::get_or_import_symbol`] call.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ResolutionError {
|
||||
/// The symbol is imported, but the import came after the current location.
|
||||
ImportAfterUsage,
|
||||
/// The symbol is imported, but in an incompatible context (e.g., in typing-only context, while
|
||||
/// we're in a runtime context).
|
||||
IncompatibleContext,
|
||||
/// The symbol can't be imported, because another symbol is bound to the same name.
|
||||
ConflictingName(String),
|
||||
/// The symbol can't be imported due to an error in editing an existing import statement.
|
||||
InvalidEdit,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ResolutionError {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ResolutionError::ImportAfterUsage => {
|
||||
fmt.write_str("Unable to use existing symbol due to late binding")
|
||||
}
|
||||
ResolutionError::IncompatibleContext => {
|
||||
fmt.write_str("Unable to use existing symbol due to incompatible context")
|
||||
}
|
||||
ResolutionError::ConflictingName(binding) => std::write!(
|
||||
fmt,
|
||||
"Unable to insert `{binding}` into scope due to name conflict"
|
||||
),
|
||||
ResolutionError::InvalidEdit => {
|
||||
fmt.write_str("Unable to modify existing import statement")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ResolutionError {}
|
||||
|
||||
@@ -79,7 +79,7 @@ impl StateMachine {
|
||||
}
|
||||
|
||||
Tok::Lpar | Tok::Lbrace | Tok::Lsqb => {
|
||||
self.bracket_count += 1;
|
||||
self.bracket_count = self.bracket_count.saturating_add(1);
|
||||
if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
@@ -92,7 +92,7 @@ impl StateMachine {
|
||||
}
|
||||
|
||||
Tok::Rpar | Tok::Rbrace | Tok::Rsqb => {
|
||||
self.bracket_count -= 1;
|
||||
self.bracket_count = self.bracket_count.saturating_sub(1);
|
||||
if matches!(
|
||||
self.state,
|
||||
State::ExpectModuleDocstring
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user