Compare commits
143 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09c50c311c | ||
|
|
252506f8ed | ||
|
|
f4572fe40b | ||
|
|
8c9215489e | ||
|
|
dcd2bfaab7 | ||
|
|
f0e173d9fd | ||
|
|
f4f1b1d0ee | ||
|
|
edc6c4058f | ||
|
|
4233f6ec91 | ||
|
|
fcdc7bdd33 | ||
|
|
86ced3516b | ||
|
|
6943beee66 | ||
|
|
85f094f592 | ||
|
|
17d938f078 | ||
|
|
5cedf0f724 | ||
|
|
38297c08b4 | ||
|
|
30e90838d0 | ||
|
|
040fb9cef4 | ||
|
|
8961d8eb6f | ||
|
|
31bddef98f | ||
|
|
a59d252246 | ||
|
|
5b9d4f18ae | ||
|
|
c6a760e298 | ||
|
|
3644695bf2 | ||
|
|
b1d01b1950 | ||
|
|
4e84e8a8e2 | ||
|
|
a256fdb9f4 | ||
|
|
7479dfd815 | ||
|
|
ba4c0a21fa | ||
|
|
73e179ffab | ||
|
|
3cbaaa4795 | ||
|
|
2681c0e633 | ||
|
|
f3bdd2e7be | ||
|
|
652c644c2a | ||
|
|
04d273bcc7 | ||
|
|
154439728a | ||
|
|
1ddc577204 | ||
|
|
74effb40b9 | ||
|
|
6c3724ab98 | ||
|
|
3b8121379d | ||
|
|
5ba47c3302 | ||
|
|
b613460fe5 | ||
|
|
daadd24bde | ||
|
|
9308e939f4 | ||
|
|
04c9348de0 | ||
|
|
2d3766d928 | ||
|
|
550b643e33 | ||
|
|
cbe344f4d5 | ||
|
|
063431cb0f | ||
|
|
c6e5fed658 | ||
|
|
f73b398776 | ||
|
|
55c4020ba9 | ||
|
|
d70f899f71 | ||
|
|
19c4b7bee6 | ||
|
|
3238743a7b | ||
|
|
f22c269ccf | ||
|
|
8ca3977602 | ||
|
|
6db05d8cc6 | ||
|
|
fc63c6f2e2 | ||
|
|
f7f5bc9085 | ||
|
|
6b85430a14 | ||
|
|
a68c865010 | ||
|
|
fe7f2e2e4d | ||
|
|
0a3cf8ba11 | ||
|
|
bf5b463c0d | ||
|
|
6aa9900c03 | ||
|
|
9e21414294 | ||
|
|
bb4e674415 | ||
|
|
b42ff08612 | ||
|
|
03fb62c174 | ||
|
|
2dfc645ea9 | ||
|
|
fe8e2bb237 | ||
|
|
a9ed8d5391 | ||
|
|
41a681531d | ||
|
|
837e70677b | ||
|
|
7ebe372122 | ||
|
|
625849b846 | ||
|
|
32f1edc555 | ||
|
|
2f35099f81 | ||
|
|
ce8fd31a8f | ||
|
|
fdb241cad2 | ||
|
|
ab303f4e09 | ||
|
|
15cb21a6f4 | ||
|
|
2e2ba2cb16 | ||
|
|
d4c0a41b00 | ||
|
|
8702b5a40a | ||
|
|
bab818e801 | ||
|
|
a3aa841fc9 | ||
|
|
fdd894145b | ||
|
|
85f67b2ee3 | ||
|
|
e9c6f16c56 | ||
|
|
d3b18345c5 | ||
|
|
0e4d174551 | ||
|
|
73efbeb581 | ||
|
|
2fb312bb2b | ||
|
|
e8e66f3824 | ||
|
|
a8d080c825 | ||
|
|
ddd541b198 | ||
|
|
3090aec97d | ||
|
|
14c6419bc1 | ||
|
|
3bc29d6c0c | ||
|
|
67c5086aba | ||
|
|
cd82b83f89 | ||
|
|
39fb2cc732 | ||
|
|
9c732c7946 | ||
|
|
2332ea5753 | ||
|
|
6b1062ccc3 | ||
|
|
39fa38cb35 | ||
|
|
ddf7de7e86 | ||
|
|
e5101e8eac | ||
|
|
d9c3f8e249 | ||
|
|
7e0d018b35 | ||
|
|
4b05ca1198 | ||
|
|
f0465bf106 | ||
|
|
8134ec25f0 | ||
|
|
6049aabe27 | ||
|
|
badade3ccc | ||
|
|
fa26860296 | ||
|
|
140e0acf54 | ||
|
|
c711db11ce | ||
|
|
1fe6954150 | ||
|
|
2414469ac3 | ||
|
|
838ba1ca3d | ||
|
|
8f3f8d3e0b | ||
|
|
8ba9eb83af | ||
|
|
2c6efc2f5f | ||
|
|
d6930ca991 | ||
|
|
f70c286e6a | ||
|
|
dcff515ad8 | ||
|
|
b9e387013f | ||
|
|
a69451ff46 | ||
|
|
01b372a75c | ||
|
|
cd2e7fa72a | ||
|
|
fdf0b999cd | ||
|
|
45b5fa573f | ||
|
|
a0258f2205 | ||
|
|
0a68636de3 | ||
|
|
2f53781a77 | ||
|
|
7e7be05ddf | ||
|
|
f5afa8198c | ||
|
|
eeabfd6d18 | ||
|
|
490301f9fe | ||
|
|
f5be3d8e5b |
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<!--
|
||||
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:
|
||||
|
||||
- Does this pull request include a summary of the change? (See below.)
|
||||
- Does this pull request include a descriptive title?
|
||||
- Does this pull request include references to any relevant issues?
|
||||
-->
|
||||
|
||||
## Summary
|
||||
|
||||
<!-- What's the purpose of the change? What does it do, and why? -->
|
||||
|
||||
## Test Plan
|
||||
|
||||
<!-- How was it tested? -->
|
||||
36
.github/workflows/ci.yaml
vendored
36
.github/workflows/ci.yaml
vendored
@@ -15,6 +15,8 @@ env:
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
PACKAGE_NAME: ruff
|
||||
PYTHON_VERSION: "3.7" # to build abi3 wheels
|
||||
|
||||
jobs:
|
||||
cargo-fmt:
|
||||
@@ -181,18 +183,30 @@ jobs:
|
||||
- name: "Install cargo-udeps"
|
||||
uses: taiki-e/install-action@cargo-udeps
|
||||
- name: "Run cargo-udeps"
|
||||
run: cargo +nightly-2023-03-30 udeps
|
||||
|
||||
|
||||
python-package:
|
||||
name: "python package"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
manylinux: auto
|
||||
args: --out dist
|
||||
- name: "Test wheel"
|
||||
run: |
|
||||
unused_dependencies=$(cargo +nightly-2023-03-30 udeps > unused.txt && cat unused.txt | cut -d $'\n' -f 2-)
|
||||
if [ -z "$unused_dependencies" ]; then
|
||||
echo "No unused dependencies found" > $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
else
|
||||
echo "Found unused dependencies" > $GITHUB_STEP_SUMMARY
|
||||
echo '```console' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$unused_dependencies" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
|
||||
pre-commit:
|
||||
name: "pre-commit"
|
||||
|
||||
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
|
||||
|
||||
57
.github/workflows/release.yaml
vendored
57
.github/workflows/release.yaml
vendored
@@ -1,9 +1,9 @@
|
||||
name: "[ruff] Release"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -18,6 +18,31 @@ env:
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
jobs:
|
||||
sdist:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build sdist"
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
command: sdist
|
||||
args: --out dist
|
||||
- name: "Test sdist"
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.tar.gz --force-reinstall
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload sdist"
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
macos-x86_64:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
@@ -32,7 +57,7 @@ jobs:
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: x86_64
|
||||
args: --release --out dist --sdist
|
||||
args: --release --out dist
|
||||
- name: "Test wheel - x86_64"
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
@@ -69,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
|
||||
@@ -381,9 +406,6 @@ jobs:
|
||||
run: |
|
||||
pip install --upgrade twine
|
||||
twine upload --skip-existing *
|
||||
- name: "Update pre-commit mirror"
|
||||
run: |
|
||||
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.RUFF_PRE_COMMIT_PAT }}" -H "X-GitHub-Api-Version: 2022-11-28" https://api.github.com/repos/charliermarsh/ruff-pre-commit/dispatches --data '{"event_type": "pypi_release"}'
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: binaries
|
||||
@@ -392,3 +414,22 @@ jobs:
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: binaries/*
|
||||
|
||||
# After the release has been published, we update downstream repositories
|
||||
# This is separate because if this fails the release is still fine, we just need to do some manual workflow triggers
|
||||
update-dependents:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: release
|
||||
steps:
|
||||
- name: "Update pre-commit mirror"
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.RUFF_PRE_COMMIT_PAT }}
|
||||
script: |
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
owner: 'astral-sh',
|
||||
repo: 'ruff-pre-commit',
|
||||
workflow_id: 'main.yml',
|
||||
ref: 'main',
|
||||
})
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,10 +1,10 @@
|
||||
# Local cache
|
||||
.ruff_cache
|
||||
crates/ruff/resources/test/cpython
|
||||
mkdocs.yml
|
||||
.overrides
|
||||
ruff-old
|
||||
github_search*.jsonl
|
||||
schemastore
|
||||
.venv*
|
||||
|
||||
###
|
||||
# Rust.gitignore
|
||||
|
||||
@@ -21,6 +21,7 @@ repos:
|
||||
- --disable
|
||||
- MD013 # line-length
|
||||
- MD033 # no-inline-html
|
||||
- MD041 # first-line-h1
|
||||
- --
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.0.268
|
||||
|
||||
### The `keep-runtime-typing` setting has been removed ([#4427](https://github.com/charliermarsh/ruff/pull/4427))
|
||||
|
||||
Enabling the `keep-runtime-typing` option, located under the `pyupgrade` section, is equivalent
|
||||
to ignoring the `UP006` and `UP007` rules via Ruff's standard `ignore` mechanism. As there's no
|
||||
need for a dedicated setting to disable these rules, the `keep-runtime-typing` option has been
|
||||
removed.
|
||||
|
||||
## 0.0.267
|
||||
|
||||
### `update-check` is no longer a valid configuration option ([#4313](https://github.com/charliermarsh/ruff/pull/4313))
|
||||
|
||||
@@ -106,7 +106,7 @@ At a high level, the steps involved in adding a new lint rule are as follows:
|
||||
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.rs` (for AST-based
|
||||
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).
|
||||
@@ -134,7 +134,7 @@ Run `cargo dev generate-all` to generate the code for your new fixture. Then run
|
||||
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/[linter]/mod.rs` file. Then, run `cargo test`.
|
||||
`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.
|
||||
|
||||
@@ -148,7 +148,7 @@ This implies that rule names:
|
||||
|
||||
- should state the bad thing being checked for
|
||||
|
||||
- should not contain instructions on what you what you should use instead
|
||||
- should not contain instructions on what you should use instead
|
||||
(these belong in the rule documentation and the `autofix_title` for rules that have autofix)
|
||||
|
||||
When re-implementing rules from other linters, this convention is given more importance than
|
||||
|
||||
470
Cargo.lock
generated
470
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
11
Cargo.toml
@@ -11,7 +11,7 @@ authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = { version = "1.0.69" }
|
||||
bitflags = { version = "2.2.1" }
|
||||
bitflags = { version = "2.3.1" }
|
||||
chrono = { version = "0.4.23", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.1.8", features = ["derive"] }
|
||||
colored = { version = "2.0.0" }
|
||||
@@ -30,11 +30,12 @@ path-absolutize = { version = "3.0.14" }
|
||||
proc-macro2 = { version = "1.0.51" }
|
||||
quote = { version = "1.0.23" }
|
||||
regex = { version = "1.7.1" }
|
||||
ruff_text_size = { git = "https://github.com/RustPython/Parser.git", rev = "947fb53d0b41fec465db3d8e725bdb2eec1299ec" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "f3e4d3409253660bd4fa7f3d24d3db747e7dca61" }
|
||||
rustpython-literal = { git = "https://github.com/RustPython/Parser.git", rev = "947fb53d0b41fec465db3d8e725bdb2eec1299ec" }
|
||||
rustpython-parser = { git = "https://github.com/RustPython/Parser.git", rev = "947fb53d0b41fec465db3d8e725bdb2eec1299ec" , default-features = false}
|
||||
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"] }
|
||||
schemars = { version = "0.8.12" }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = { version = "1.0.93", features = ["preserve_order"] }
|
||||
|
||||
56
LICENSE
56
LICENSE
@@ -354,6 +354,37 @@ are:
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-todos, licensed as follows:
|
||||
"""
|
||||
Copyright (c) 2019 EclecticIQ. All rights reserved.
|
||||
Copyright (c) 2020 Gram <gram@orsinium.dev>. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
|
||||
- flake8-unused-arguments, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
@@ -783,6 +814,31 @@ are:
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-async, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Cooper Lees
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-type-checking, licensed as follows:
|
||||
"""
|
||||
Copyright (c) 2021, Sondre Lillebø Gundersen
|
||||
|
||||
17
README.md
17
README.md
@@ -33,7 +33,7 @@ An extremely fast Python linter, written in Rust.
|
||||
- 📏 Over [500 built-in rules](https://beta.ruff.rs/docs/rules/)
|
||||
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
|
||||
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
|
||||
- ⌨️ First-party editor integrations for [VS Code](https://github.com/charliermarsh/ruff-vscode) and [more](https://github.com/charliermarsh/ruff-lsp)
|
||||
- ⌨️ 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
|
||||
@@ -135,15 +135,15 @@ ruff check path/to/code/to/file.py # Lint `file.py`
|
||||
Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.267'
|
||||
rev: v0.0.270
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
Ruff can also be used as a [VS Code extension](https://github.com/charliermarsh/ruff-vscode) or
|
||||
alongside any other editor through the [Ruff LSP](https://github.com/charliermarsh/ruff-lsp).
|
||||
Ruff can also be used as a [VS Code extension](https://github.com/astral-sh/ruff-vscode) or
|
||||
alongside any other editor through the [Ruff LSP](https://github.com/astral-sh/ruff-lsp).
|
||||
|
||||
Ruff can also be used as a [GitHub Action](https://github.com/features/actions) via
|
||||
[`ruff-action`](https://github.com/chartboost/ruff-action):
|
||||
@@ -249,6 +249,7 @@ quality tools, including:
|
||||
- [eradicate](https://pypi.org/project/eradicate/)
|
||||
- [flake8-2020](https://pypi.org/project/flake8-2020/)
|
||||
- [flake8-annotations](https://pypi.org/project/flake8-annotations/)
|
||||
- [flake8-async](https://pypi.org/project/flake8-async)
|
||||
- [flake8-bandit](https://pypi.org/project/flake8-bandit/) ([#1646](https://github.com/charliermarsh/ruff/issues/1646))
|
||||
- [flake8-blind-except](https://pypi.org/project/flake8-blind-except/)
|
||||
- [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap/)
|
||||
@@ -263,6 +264,7 @@ quality tools, including:
|
||||
- [flake8-eradicate](https://pypi.org/project/flake8-eradicate/)
|
||||
- [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
|
||||
- [flake8-executable](https://pypi.org/project/flake8-executable/)
|
||||
- [flake8-future-annotations](https://pypi.org/project/flake8-future-annotations/)
|
||||
- [flake8-gettext](https://pypi.org/project/flake8-gettext/)
|
||||
- [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
|
||||
- [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions)
|
||||
@@ -279,6 +281,7 @@ quality tools, including:
|
||||
- [flake8-simplify](https://pypi.org/project/flake8-simplify/)
|
||||
- [flake8-super](https://pypi.org/project/flake8-super/)
|
||||
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
|
||||
- [flake8-todos](https://pypi.org/project/flake8-todos/)
|
||||
- [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
|
||||
- [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
|
||||
- [flynt](https://pypi.org/project/flynt/) ([#2102](https://github.com/charliermarsh/ruff/issues/2102))
|
||||
@@ -361,7 +364,7 @@ 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))
|
||||
- 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)
|
||||
@@ -385,7 +388,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [SciPy](https://github.com/scipy/scipy)
|
||||
- [Sphinx](https://github.com/sphinx-doc/sphinx)
|
||||
- [Stable Baselines3](https://github.com/DLR-RM/stable-baselines3)
|
||||
- [Starlite](https://github.com/starlite-api/starlite)
|
||||
- [Litestar](https://litestar.dev/)
|
||||
- [The Algorithms](https://github.com/TheAlgorithms/Python)
|
||||
- [Vega-Altair](https://github.com/altair-viz/altair)
|
||||
- WordPress ([Openverse](https://github.com/WordPress/openverse))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.267"
|
||||
version = "0.0.270"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ requires-python = ">=3.7"
|
||||
repository = "https://github.com/charliermarsh/ruff#subdirectory=crates/flake8_to_ruff"
|
||||
|
||||
[build-system]
|
||||
requires = ["maturin>=0.15.1,<0.16"]
|
||||
requires = ["maturin>=1.0,<2.0"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[tool.maturin]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.267"
|
||||
version = "0.0.270"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
@@ -54,7 +54,7 @@ quick-junit = { version = "0.3.2" }
|
||||
regex = { workspace = true }
|
||||
result-like = { version = "0.4.6" }
|
||||
rustc-hash = { workspace = true }
|
||||
rustpython-common = { workspace = true }
|
||||
rustpython-format = { workspace = true }
|
||||
rustpython-parser = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
semver = { version = "1.0.16" }
|
||||
@@ -70,6 +70,7 @@ thiserror = { version = "1.0.38" }
|
||||
toml = { workspace = true }
|
||||
typed-arena = { version = "2.0.2" }
|
||||
unicode-width = { version = "0.1.10" }
|
||||
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true }
|
||||
@@ -81,6 +82,4 @@ colored = { workspace = true, features = ["no-color"] }
|
||||
[features]
|
||||
default = []
|
||||
schemars = ["dep:schemars"]
|
||||
logical_lines = []
|
||||
jupyter_notebook = []
|
||||
ecosystem_ci = []
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Any, Type
|
||||
from typing_extensions import override
|
||||
|
||||
# Error
|
||||
def foo(a, b):
|
||||
@@ -94,6 +95,31 @@ class Foo:
|
||||
def foo(self: "Foo", a: int, *params: str, **options: Any) -> int:
|
||||
pass
|
||||
|
||||
# ANN401
|
||||
@override
|
||||
def foo(self: "Foo", a: Any, *params: str, **options: str) -> int:
|
||||
pass
|
||||
|
||||
# ANN401
|
||||
@override
|
||||
def foo(self: "Foo", a: int, *params: str, **options: str) -> Any:
|
||||
pass
|
||||
|
||||
# ANN401
|
||||
@override
|
||||
def foo(self: "Foo", a: int, *params: Any, **options: Any) -> int:
|
||||
pass
|
||||
|
||||
# ANN401
|
||||
@override
|
||||
def foo(self: "Foo", a: int, *params: Any, **options: str) -> int:
|
||||
pass
|
||||
|
||||
# ANN401
|
||||
@override
|
||||
def foo(self: "Foo", a: int, *params: str, **options: Any) -> int:
|
||||
pass
|
||||
|
||||
# OK
|
||||
@classmethod
|
||||
def foo(cls: Type["Foo"], a: int, b: int) -> int:
|
||||
|
||||
23
crates/ruff/resources/test/fixtures/flake8_async/ASYNC100.py
vendored
Normal file
23
crates/ruff/resources/test/fixtures/flake8_async/ASYNC100.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import urllib.request
|
||||
import requests
|
||||
import httpx
|
||||
|
||||
|
||||
async def foo():
|
||||
urllib.request.urlopen("http://example.com/foo/bar").read()
|
||||
|
||||
|
||||
async def foo():
|
||||
requests.get()
|
||||
|
||||
|
||||
async def foo():
|
||||
httpx.get()
|
||||
|
||||
|
||||
async def foo():
|
||||
requests.post()
|
||||
|
||||
|
||||
async def foo():
|
||||
httpx.post()
|
||||
31
crates/ruff/resources/test/fixtures/flake8_async/ASYNC101.py
vendored
Normal file
31
crates/ruff/resources/test/fixtures/flake8_async/ASYNC101.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
|
||||
async def foo():
|
||||
open("foo")
|
||||
|
||||
|
||||
async def foo():
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
async def foo():
|
||||
subprocess.run("foo")
|
||||
|
||||
|
||||
async def foo():
|
||||
subprocess.call("foo")
|
||||
|
||||
|
||||
async def foo():
|
||||
subprocess.foo(0)
|
||||
|
||||
|
||||
async def foo():
|
||||
os.wait4(10)
|
||||
|
||||
|
||||
async def foo():
|
||||
os.wait(12)
|
||||
13
crates/ruff/resources/test/fixtures/flake8_async/ASYNC102.py
vendored
Normal file
13
crates/ruff/resources/test/fixtures/flake8_async/ASYNC102.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import os
|
||||
|
||||
|
||||
async def foo():
|
||||
os.popen()
|
||||
|
||||
|
||||
async def foo():
|
||||
os.spawnl()
|
||||
|
||||
|
||||
async def foo():
|
||||
os.fspath("foo")
|
||||
3
crates/ruff/resources/test/fixtures/flake8_bandit/S601.py
vendored
Normal file
3
crates/ruff/resources/test/fixtures/flake8_bandit/S601.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import paramiko
|
||||
|
||||
paramiko.exec_command('something; really; unsafe')
|
||||
@@ -74,8 +74,8 @@ def query40():
|
||||
|
||||
def query41():
|
||||
return (
|
||||
"SELECT *"
|
||||
"FROM table"
|
||||
"SELECT * "
|
||||
"FROM table "
|
||||
f"WHERE var = {var}"
|
||||
)
|
||||
|
||||
@@ -84,7 +84,7 @@ query42 = cursor.execute("SELECT * FROM table WHERE var = %s" % var)
|
||||
query43 = cursor.execute(f"SELECT * FROM table WHERE var = {var}")
|
||||
query44 = cursor.execute("SELECT * FROM table WHERE var = {}".format(var))
|
||||
query45 = cursor.executemany("SELECT * FROM table WHERE var = %s" % var, [])
|
||||
|
||||
|
||||
# # pass
|
||||
query = "SELECT * FROM table WHERE id = 1"
|
||||
query = "DELETE FROM table WHERE id = 1"
|
||||
@@ -93,3 +93,12 @@ query = "UPDATE table SET id = 1"
|
||||
cursor.execute('SELECT * FROM table WHERE id = %s', var)
|
||||
cursor.execute('SELECT * FROM table WHERE id = 1')
|
||||
cursor.executemany('SELECT * FROM table WHERE id = %s', [var, var2])
|
||||
|
||||
# # INSERT without INTO (e.g. MySQL and derivatives)
|
||||
query = "INSERT table VALUES (%s)" % (var,)
|
||||
|
||||
# # REPLACE (e.g. MySQL and derivatives, SQLite)
|
||||
query = "REPLACE INTO table VALUES (%s)" % (var,)
|
||||
query = "REPLACE table VALUES (%s)" % (var,)
|
||||
|
||||
query = "Deselect something that is not SQL even though it has a ' from ' somewhere in %s." % "there"
|
||||
|
||||
@@ -73,7 +73,18 @@ def f():
|
||||
|
||||
|
||||
def f():
|
||||
# Fixable.
|
||||
# Unfixable.
|
||||
for foo, bar, baz in (["1", "2", "3"],):
|
||||
if foo or baz:
|
||||
break
|
||||
else:
|
||||
bar = 1
|
||||
|
||||
print(bar)
|
||||
|
||||
|
||||
def f():
|
||||
# Unfixable (false negative) due to usage of `bar` outside of loop.
|
||||
for foo, bar, baz in (["1", "2", "3"],):
|
||||
if foo or baz:
|
||||
break
|
||||
@@ -85,4 +96,4 @@ def f():
|
||||
# Unfixable due to trailing underscore (`_line_` wouldn't be considered an ignorable
|
||||
# variable name).
|
||||
for line_ in range(self.header_lines):
|
||||
fp.readline()
|
||||
fp.readline()
|
||||
|
||||
@@ -631,3 +631,11 @@ result = function(
|
||||
the_first_one = next(
|
||||
(i for i in range(10) if i // 2 == 0) # COM812 fix should include the final bracket
|
||||
)
|
||||
|
||||
foo = namedtuple(
|
||||
name="foo",
|
||||
status="bar",
|
||||
message="sfdsdfsdgs fsdfsdf output!dsfdfsdjkg ghfskdjghkdssd sd fsdf s\n"[
|
||||
:20
|
||||
],
|
||||
)
|
||||
|
||||
7
crates/ruff/resources/test/fixtures/flake8_future_annotations/edge_case.py
vendored
Normal file
7
crates/ruff/resources/test/fixtures/flake8_future_annotations/edge_case.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
from typing import List
|
||||
import typing as t
|
||||
|
||||
|
||||
def main(_: List[int]) -> None:
|
||||
a_list: t.List[str] = []
|
||||
a_list.append("hello")
|
||||
6
crates/ruff/resources/test/fixtures/flake8_future_annotations/from_typing_import.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/flake8_future_annotations/from_typing_import.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
from typing import List
|
||||
|
||||
|
||||
def main() -> None:
|
||||
a_list: List[str] = []
|
||||
a_list.append("hello")
|
||||
8
crates/ruff/resources/test/fixtures/flake8_future_annotations/from_typing_import_many.py
vendored
Normal file
8
crates/ruff/resources/test/fixtures/flake8_future_annotations/from_typing_import_many.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
from typing import Dict, List, Optional, Set, Union, cast
|
||||
|
||||
|
||||
def main() -> None:
|
||||
a_list: List[Optional[str]] = []
|
||||
a_list.append("hello")
|
||||
a_dict = cast(Dict[int | None, Union[int, Set[bool]]], {})
|
||||
a_dict[1] = {True, False}
|
||||
6
crates/ruff/resources/test/fixtures/flake8_future_annotations/import_typing.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/flake8_future_annotations/import_typing.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import typing
|
||||
|
||||
|
||||
def main() -> None:
|
||||
a_list: typing.List[str] = []
|
||||
a_list.append("hello")
|
||||
6
crates/ruff/resources/test/fixtures/flake8_future_annotations/import_typing_as.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/flake8_future_annotations/import_typing_as.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import typing as t
|
||||
|
||||
|
||||
def main() -> None:
|
||||
a_list: t.List[str] = []
|
||||
a_list.append("hello")
|
||||
@@ -0,0 +1,7 @@
|
||||
def main() -> None:
|
||||
a_list: list[str] = []
|
||||
a_list.append("hello")
|
||||
|
||||
|
||||
def hello(y: dict[str, int]) -> None:
|
||||
del y
|
||||
7
crates/ruff/resources/test/fixtures/flake8_future_annotations/no_future_import_uses_union.py
vendored
Normal file
7
crates/ruff/resources/test/fixtures/flake8_future_annotations/no_future_import_uses_union.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
def main() -> None:
|
||||
a_list: list[str] | None = []
|
||||
a_list.append("hello")
|
||||
|
||||
|
||||
def hello(y: dict[str, int] | None) -> None:
|
||||
del y
|
||||
@@ -0,0 +1,8 @@
|
||||
def main() -> None:
|
||||
a_list: list[str | None] = []
|
||||
a_list.append("hello")
|
||||
|
||||
|
||||
def hello(y: dict[str | None, int]) -> None:
|
||||
z: tuple[str, str | None, str] = tuple(y)
|
||||
del z
|
||||
3
crates/ruff/resources/test/fixtures/flake8_future_annotations/ok_no_types.py
vendored
Normal file
3
crates/ruff/resources/test/fixtures/flake8_future_annotations/ok_no_types.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
def main() -> str:
|
||||
a_str = "hello"
|
||||
return a_str
|
||||
10
crates/ruff/resources/test/fixtures/flake8_future_annotations/ok_non_simplifiable_types.py
vendored
Normal file
10
crates/ruff/resources/test/fixtures/flake8_future_annotations/ok_non_simplifiable_types.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
class Stuff(NamedTuple):
|
||||
x: int
|
||||
|
||||
|
||||
def main() -> None:
|
||||
a_list = Stuff(5)
|
||||
print(a_list)
|
||||
6
crates/ruff/resources/test/fixtures/flake8_future_annotations/ok_uses_future.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/flake8_future_annotations/ok_uses_future.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def main() -> None:
|
||||
a_list: list[str] = []
|
||||
a_list.append("hello")
|
||||
8
crates/ruff/resources/test/fixtures/flake8_future_annotations/ok_variable_name.py
vendored
Normal file
8
crates/ruff/resources/test/fixtures/flake8_future_annotations/ok_variable_name.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import typing
|
||||
|
||||
IRRELEVANT = typing.TypeVar
|
||||
|
||||
|
||||
def main() -> None:
|
||||
List: list[str] = []
|
||||
List.append("hello")
|
||||
65
crates/ruff/resources/test/fixtures/flake8_pyi/PYI013.py
vendored
Normal file
65
crates/ruff/resources/test/fixtures/flake8_pyi/PYI013.py
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
class OneAttributeClass:
|
||||
value: int
|
||||
...
|
||||
|
||||
|
||||
class OneAttributeClass2:
|
||||
...
|
||||
value: int
|
||||
|
||||
|
||||
class TwoEllipsesClass:
|
||||
...
|
||||
...
|
||||
|
||||
|
||||
class DocstringClass:
|
||||
"""
|
||||
My body only contains an ellipsis.
|
||||
"""
|
||||
|
||||
...
|
||||
|
||||
|
||||
class NonEmptyChild(Exception):
|
||||
value: int
|
||||
...
|
||||
|
||||
|
||||
class NonEmptyChild2(Exception):
|
||||
...
|
||||
value: int
|
||||
|
||||
|
||||
class NonEmptyWithInit:
|
||||
value: int
|
||||
...
|
||||
|
||||
def __init__():
|
||||
pass
|
||||
|
||||
|
||||
class EmptyClass:
|
||||
...
|
||||
|
||||
|
||||
class EmptyEllipsis:
|
||||
...
|
||||
|
||||
|
||||
class Dog:
|
||||
eyes: int = 2
|
||||
|
||||
|
||||
class WithInit:
|
||||
value: int = 0
|
||||
|
||||
def __init__():
|
||||
...
|
||||
|
||||
|
||||
def function():
|
||||
...
|
||||
|
||||
|
||||
...
|
||||
56
crates/ruff/resources/test/fixtures/flake8_pyi/PYI013.pyi
vendored
Normal file
56
crates/ruff/resources/test/fixtures/flake8_pyi/PYI013.pyi
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
# Violations of PYI013
|
||||
|
||||
class OneAttributeClass:
|
||||
value: int
|
||||
... # Error
|
||||
|
||||
class OneAttributeClass2:
|
||||
... # Error
|
||||
value: int
|
||||
|
||||
class MyClass:
|
||||
...
|
||||
value: int
|
||||
|
||||
class TwoEllipsesClass:
|
||||
...
|
||||
... # Error
|
||||
|
||||
class DocstringClass:
|
||||
"""
|
||||
My body only contains an ellipsis.
|
||||
"""
|
||||
|
||||
... # Error
|
||||
|
||||
class NonEmptyChild(Exception):
|
||||
value: int
|
||||
... # Error
|
||||
|
||||
class NonEmptyChild2(Exception):
|
||||
... # Error
|
||||
value: int
|
||||
|
||||
class NonEmptyWithInit:
|
||||
value: int
|
||||
... # Error
|
||||
|
||||
def __init__():
|
||||
pass
|
||||
|
||||
# Not violations
|
||||
|
||||
class EmptyClass: ...
|
||||
class EmptyEllipsis: ...
|
||||
|
||||
class Dog:
|
||||
eyes: int = 2
|
||||
|
||||
class WithInit:
|
||||
value: int = 0
|
||||
|
||||
def __init__(): ...
|
||||
|
||||
def function(): ...
|
||||
|
||||
...
|
||||
93
crates/ruff/resources/test/fixtures/flake8_pyi/PYI052.py
vendored
Normal file
93
crates/ruff/resources/test/fixtures/flake8_pyi/PYI052.py
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
import builtins
|
||||
import typing
|
||||
from typing import TypeAlias, Final
|
||||
|
||||
field1: int
|
||||
field2: int = ...
|
||||
field3 = ... # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||
field4: int = 0
|
||||
field41: int = 0xFFFFFFFF
|
||||
field42: int = 1234567890
|
||||
field43: int = -0xFFFFFFFF
|
||||
field44: int = -1234567890
|
||||
field5 = 0 # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") # Y052 Need type annotation for "field5"
|
||||
field6 = 0 # Y052 Need type annotation for "field6"
|
||||
field7 = b"" # Y052 Need type annotation for "field7"
|
||||
field71 = "foo" # Y052 Need type annotation for "field71"
|
||||
field72: str = "foo"
|
||||
field8 = False # Y052 Need type annotation for "field8"
|
||||
field81 = -1 # Y052 Need type annotation for "field81"
|
||||
field82: float = -98.43
|
||||
field83 = -42j # Y052 Need type annotation for "field83"
|
||||
field84 = 5 + 42j # Y052 Need type annotation for "field84"
|
||||
field85 = -5 - 42j # Y052 Need type annotation for "field85"
|
||||
field9 = None # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "field9: TypeAlias = None"
|
||||
Field95: TypeAlias = None
|
||||
Field96: TypeAlias = int | None
|
||||
Field97: TypeAlias = None | typing.SupportsInt | builtins.str | float | bool
|
||||
field19 = [1, 2, 3] # Y052 Need type annotation for "field19"
|
||||
field191: list[int] = [1, 2, 3]
|
||||
field20 = (1, 2, 3) # Y052 Need type annotation for "field20"
|
||||
field201: tuple[int, ...] = (1, 2, 3)
|
||||
field21 = {1, 2, 3} # Y052 Need type annotation for "field21"
|
||||
field211: set[int] = {1, 2, 3}
|
||||
field212 = {"foo": "bar"} # Y052 Need type annotation for "field212"
|
||||
field213: dict[str, str] = {"foo": "bar"}
|
||||
field22: Final = {"foo": 5}
|
||||
field221: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] # Y015 Only simple default values are allowed for assignments
|
||||
field223: list[int] = [*range(10)] # Y015 Only simple default values are allowed for assignments
|
||||
field224: list[int] = list(range(10)) # Y015 Only simple default values are allowed for assignments
|
||||
field225: list[object] = [{}, 1, 2] # Y015 Only simple default values are allowed for assignments
|
||||
field226: tuple[str | tuple[str, ...], ...] = ("foo", ("foo", "bar")) # Y015 Only simple default values are allowed for assignments
|
||||
field227: dict[str, object] = {"foo": {"foo": "bar"}} # Y015 Only simple default values are allowed for assignments
|
||||
field228: dict[str, list[object]] = {"foo": []} # Y015 Only simple default values are allowed for assignments
|
||||
# When parsed, this case results in `None` being placed in the `.keys` list for the `ast.Dict` node
|
||||
field229: dict[int, int] = {1: 2, **{3: 4}} # Y015 Only simple default values are allowed for assignments
|
||||
field23 = "foo" + "bar" # Y015 Only simple default values are allowed for assignments
|
||||
field24 = b"foo" + b"bar" # Y015 Only simple default values are allowed for assignments
|
||||
field25 = 5 * 5 # Y015 Only simple default values are allowed for assignments
|
||||
|
||||
# We shouldn't emit Y015 within functions
|
||||
def f():
|
||||
field26: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
||||
|
||||
|
||||
# We shouldn't emit Y015 for __slots__ or __match_args__
|
||||
class Class1:
|
||||
__slots__ = (
|
||||
'_one',
|
||||
'_two',
|
||||
'_three',
|
||||
'_four',
|
||||
'_five',
|
||||
'_six',
|
||||
'_seven',
|
||||
'_eight',
|
||||
'_nine',
|
||||
'_ten',
|
||||
'_eleven',
|
||||
)
|
||||
|
||||
__match_args__ = (
|
||||
'one',
|
||||
'two',
|
||||
'three',
|
||||
'four',
|
||||
'five',
|
||||
'six',
|
||||
'seven',
|
||||
'eight',
|
||||
'nine',
|
||||
'ten',
|
||||
'eleven',
|
||||
)
|
||||
|
||||
# We shouldn't emit Y015 for __all__
|
||||
__all__ = ["Class1"]
|
||||
|
||||
# Ignore the following for PYI015
|
||||
field26 = typing.Sequence[int]
|
||||
field27 = list[str]
|
||||
field28 = builtins.str
|
||||
field29 = str
|
||||
field30 = str | bytes | None
|
||||
100
crates/ruff/resources/test/fixtures/flake8_pyi/PYI052.pyi
vendored
Normal file
100
crates/ruff/resources/test/fixtures/flake8_pyi/PYI052.pyi
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
import builtins
|
||||
import typing
|
||||
from typing import TypeAlias, Final, NewType, TypeVar, TypeVarTuple, ParamSpec
|
||||
|
||||
# We shouldn't emit Y015 for simple default values
|
||||
field1: int
|
||||
field2: int = ...
|
||||
field3 = ... # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
|
||||
field4: int = 0
|
||||
field41: int = 0xFFFFFFFF
|
||||
field42: int = 1234567890
|
||||
field43: int = -0xFFFFFFFF
|
||||
field44: int = -1234567890
|
||||
field5 = 0 # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") # Y052 Need type annotation for "field5"
|
||||
field6 = 0 # Y052 Need type annotation for "field6"
|
||||
field7 = b"" # Y052 Need type annotation for "field7"
|
||||
field71 = "foo" # Y052 Need type annotation for "field71"
|
||||
field72: str = "foo"
|
||||
field8 = False # Y052 Need type annotation for "field8"
|
||||
field81 = -1 # Y052 Need type annotation for "field81"
|
||||
field82: float = -98.43
|
||||
field83 = -42j # Y052 Need type annotation for "field83"
|
||||
field84 = 5 + 42j # Y052 Need type annotation for "field84"
|
||||
field85 = -5 - 42j # Y052 Need type annotation for "field85"
|
||||
field9 = None # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "field9: TypeAlias = None"
|
||||
Field95: TypeAlias = None
|
||||
Field96: TypeAlias = int | None
|
||||
Field97: TypeAlias = None | typing.SupportsInt | builtins.str | float | bool
|
||||
Field98 = NewType('MyInt', int)
|
||||
Field99 = TypeVar('Field99')
|
||||
Field100 = TypeVarTuple('Field100')
|
||||
Field101 = ParamSpec('Field101')
|
||||
field19 = [1, 2, 3] # Y052 Need type annotation for "field19"
|
||||
field191: list[int] = [1, 2, 3]
|
||||
field20 = (1, 2, 3) # Y052 Need type annotation for "field20"
|
||||
field201: tuple[int, ...] = (1, 2, 3)
|
||||
field21 = {1, 2, 3} # Y052 Need type annotation for "field21"
|
||||
field211: set[int] = {1, 2, 3}
|
||||
field212 = {"foo": "bar"} # Y052 Need type annotation for "field212"
|
||||
field213: dict[str, str] = {"foo": "bar"}
|
||||
field22: Final = {"foo": 5}
|
||||
|
||||
# We *should* emit Y015 for more complex default values
|
||||
field221: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] # Y015 Only simple default values are allowed for assignments
|
||||
field223: list[int] = [*range(10)] # Y015 Only simple default values are allowed for assignments
|
||||
field224: list[int] = list(range(10)) # Y015 Only simple default values are allowed for assignments
|
||||
field225: list[object] = [{}, 1, 2] # Y015 Only simple default values are allowed for assignments
|
||||
field226: tuple[str | tuple[str, ...], ...] = ("foo", ("foo", "bar")) # Y015 Only simple default values are allowed for assignments
|
||||
field227: dict[str, object] = {"foo": {"foo": "bar"}} # Y015 Only simple default values are allowed for assignments
|
||||
field228: dict[str, list[object]] = {"foo": []} # Y015 Only simple default values are allowed for assignments
|
||||
# When parsed, this case results in `None` being placed in the `.keys` list for the `ast.Dict` node
|
||||
field229: dict[int, int] = {1: 2, **{3: 4}} # Y015 Only simple default values are allowed for assignments
|
||||
field23 = "foo" + "bar" # Y015 Only simple default values are allowed for assignments
|
||||
field24 = b"foo" + b"bar" # Y015 Only simple default values are allowed for assignments
|
||||
field25 = 5 * 5 # Y015 Only simple default values are allowed for assignments
|
||||
|
||||
# We shouldn't emit Y015 within functions
|
||||
def f():
|
||||
field26: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
||||
|
||||
|
||||
# We shouldn't emit Y015 for __slots__ or __match_args__
|
||||
class Class1:
|
||||
__slots__ = (
|
||||
'_one',
|
||||
'_two',
|
||||
'_three',
|
||||
'_four',
|
||||
'_five',
|
||||
'_six',
|
||||
'_seven',
|
||||
'_eight',
|
||||
'_nine',
|
||||
'_ten',
|
||||
'_eleven',
|
||||
)
|
||||
|
||||
__match_args__ = (
|
||||
'one',
|
||||
'two',
|
||||
'three',
|
||||
'four',
|
||||
'five',
|
||||
'six',
|
||||
'seven',
|
||||
'eight',
|
||||
'nine',
|
||||
'ten',
|
||||
'eleven',
|
||||
)
|
||||
|
||||
# We shouldn't emit Y015 for __all__
|
||||
__all__ = ["Class1"]
|
||||
|
||||
# Ignore the following for PYI015
|
||||
field26 = typing.Sequence[int]
|
||||
field27 = list[str]
|
||||
field28 = builtins.str
|
||||
field29 = str
|
||||
field30 = str | bytes | None
|
||||
@@ -39,3 +39,8 @@ def test_error():
|
||||
message
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
assert something # OK
|
||||
assert something and something_else # Error
|
||||
assert something and something_else and something_third # Error
|
||||
|
||||
@@ -86,9 +86,16 @@ while x > 0:
|
||||
):
|
||||
print("Bad module!")
|
||||
|
||||
# SIM102
|
||||
if node.module:
|
||||
if node.module == "multiprocessing" or node.module.startswith(
|
||||
# SIM102 (auto-fixable)
|
||||
if node.module012345678:
|
||||
if node.module == "multiprocß9💣2ℝ" or node.module.startswith(
|
||||
"multiprocessing."
|
||||
):
|
||||
print("Bad module!")
|
||||
|
||||
# SIM102 (not auto-fixable)
|
||||
if node.module0123456789:
|
||||
if node.module == "multiprocß9💣2ℝ" or node.module.startswith(
|
||||
"multiprocessing."
|
||||
):
|
||||
print("Bad module!")
|
||||
|
||||
@@ -80,17 +80,25 @@ else:
|
||||
|
||||
# SIM108
|
||||
if a:
|
||||
b = cccccccccccccccccccccccccccccccccccc
|
||||
b = "cccccccccccccccccccccccccccccccccß"
|
||||
else:
|
||||
b = ddddddddddddddddddddddddddddddddddddd
|
||||
b = "ddddddddddddddddddddddddddddddddd💣"
|
||||
|
||||
|
||||
# OK (too long)
|
||||
if True:
|
||||
if a:
|
||||
b = cccccccccccccccccccccccccccccccccccc
|
||||
b = ccccccccccccccccccccccccccccccccccc
|
||||
else:
|
||||
b = ddddddddddddddddddddddddddddddddddddd
|
||||
b = ddddddddddddddddddddddddddddddddddd
|
||||
|
||||
|
||||
# OK (too long with tabs)
|
||||
if True:
|
||||
if a:
|
||||
b = ccccccccccccccccccccccccccccccccccc
|
||||
else:
|
||||
b = ddddddddddddddddddddddddddddddddddd
|
||||
|
||||
|
||||
# SIM108 (without fix due to trailing comment)
|
||||
|
||||
@@ -155,3 +155,19 @@ def f():
|
||||
if check(x):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# SIM110
|
||||
for x in "012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ":
|
||||
if x.isdigit():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
# OK (too long)
|
||||
for x in "012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9":
|
||||
if x.isdigit():
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -171,3 +171,19 @@ def f():
|
||||
if x > y:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# SIM111
|
||||
for x in "012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9":
|
||||
if x.isdigit():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# OK (too long)
|
||||
for x in "012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß90":
|
||||
if x.isdigit():
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -90,3 +90,13 @@ with (
|
||||
D() as d,
|
||||
):
|
||||
print("hello")
|
||||
|
||||
# SIM117 (auto-fixable)
|
||||
with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as a:
|
||||
with B("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as b:
|
||||
print("hello")
|
||||
|
||||
# SIM117 (not auto-fixable too long)
|
||||
with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ890") as a:
|
||||
with B("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as b:
|
||||
print("hello")
|
||||
@@ -14,7 +14,7 @@ if key not in a_dict:
|
||||
else:
|
||||
var = a_dict[key]
|
||||
|
||||
# SIM401 (default with a complex expression)
|
||||
# OK (default contains effect)
|
||||
if key in a_dict:
|
||||
var = a_dict[key]
|
||||
else:
|
||||
@@ -36,12 +36,18 @@ else:
|
||||
if key in a_dict:
|
||||
vars[idx] = a_dict[key]
|
||||
else:
|
||||
vars[idx] = "default"
|
||||
vars[idx] = "defaultß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789"
|
||||
|
||||
###
|
||||
# Negative cases
|
||||
###
|
||||
|
||||
# OK (too long)
|
||||
if key in a_dict:
|
||||
vars[idx] = a_dict[key]
|
||||
else:
|
||||
vars[idx] = "defaultß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß9💣2ℝ6789ß"
|
||||
|
||||
# OK (false negative)
|
||||
if not key in a_dict:
|
||||
var = "default"
|
||||
|
||||
8
crates/ruff/resources/test/fixtures/flake8_todos/TD001.py
vendored
Normal file
8
crates/ruff/resources/test/fixtures/flake8_todos/TD001.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# T001 - accepted
|
||||
# TODO (evanrittenhouse): this is a valid TODO
|
||||
# SOME_OTHER_TAG: this is impossible to determine
|
||||
# this is not a TODO
|
||||
|
||||
# T001 - errors
|
||||
# XXX (evanrittenhouse): this is not fine
|
||||
# FIXME (evanrittenhouse): this is not fine
|
||||
7
crates/ruff/resources/test/fixtures/flake8_todos/TD002.py
vendored
Normal file
7
crates/ruff/resources/test/fixtures/flake8_todos/TD002.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# T002 - accepted
|
||||
# TODO (evanrittenhouse): this has an author
|
||||
# TODO(evanrittenhouse): this also has an author
|
||||
# T002 - errors
|
||||
# TODO: this has no author
|
||||
# FIXME: neither does this
|
||||
# TODO : and neither does this
|
||||
29
crates/ruff/resources/test/fixtures/flake8_todos/TD003.py
vendored
Normal file
29
crates/ruff/resources/test/fixtures/flake8_todos/TD003.py
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# TDO003 - accepted
|
||||
# TODO: this comment has a link
|
||||
# https://github.com/charliermarsh/ruff/issues/3870
|
||||
|
||||
# TODO: this comment has an issue
|
||||
# TDO-3870
|
||||
|
||||
# TDO003 - errors
|
||||
# TODO: this comment has no
|
||||
# link after it
|
||||
|
||||
# TODO: here's a TODO with no link after it
|
||||
def foo(x):
|
||||
return x
|
||||
|
||||
# TODO: here's a TODO on the last line with no link
|
||||
# Here's more content.
|
||||
# TDO-3870
|
||||
|
||||
# TODO: here's a TODO on the last line with no link
|
||||
# Here's more content, with a space.
|
||||
|
||||
# TDO-3870
|
||||
|
||||
# TODO: here's a TODO without an issue link
|
||||
# TODO: followed by a new TODO with an issue link
|
||||
# TDO-3870
|
||||
|
||||
# TODO: here's a TODO on the last line with no link
|
||||
6
crates/ruff/resources/test/fixtures/flake8_todos/TD004.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/flake8_todos/TD004.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# T004 - accepted
|
||||
# TODO(evanrittenhouse): this has a colon
|
||||
# T004 - errors
|
||||
# TODO this has no colon
|
||||
# TODO(evanrittenhouse 😀) this has no colon
|
||||
# FIXME add a colon
|
||||
6
crates/ruff/resources/test/fixtures/flake8_todos/TD005.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/flake8_todos/TD005.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# T005 - accepted
|
||||
# TODO(evanrittenhouse): this has text, while the errors do not
|
||||
# T005 - errors
|
||||
# TODO(evanrittenhouse):
|
||||
# TODO(evanrittenhouse)
|
||||
# FIXME
|
||||
5
crates/ruff/resources/test/fixtures/flake8_todos/TD006.py
vendored
Normal file
5
crates/ruff/resources/test/fixtures/flake8_todos/TD006.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# TDO006 - accepted
|
||||
# TODO (evanrittenhouse): this is a valid TODO
|
||||
# TDO006 - error
|
||||
# ToDo (evanrittenhouse): invalid capitalization
|
||||
# todo (evanrittenhouse): another invalid capitalization
|
||||
8
crates/ruff/resources/test/fixtures/flake8_todos/TD007.py
vendored
Normal file
8
crates/ruff/resources/test/fixtures/flake8_todos/TD007.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# T007 - accepted
|
||||
# TODO(evanrittenhouse): this has a space after a colon
|
||||
# TODO: so does this
|
||||
# T007 - errors
|
||||
# TODO(evanrittenhouse):this has no space after a colon
|
||||
# TODO (evanrittenhouse):this doesn't either
|
||||
# TODO:neither does this
|
||||
# FIXME:and lastly neither does this
|
||||
@@ -146,3 +146,7 @@ def f():
|
||||
import pandas as pd
|
||||
|
||||
x = dict[pd.DataFrame, pd.DataFrame]
|
||||
|
||||
|
||||
def f():
|
||||
import pandas as pd
|
||||
|
||||
@@ -2,3 +2,7 @@ import a
|
||||
# Don't take this comment into account when determining whether the next import can fit on one line.
|
||||
from b import c
|
||||
from d import e # Do take this comment into account when determining whether the next import can fit on one line.
|
||||
# The next import fits on one line.
|
||||
from f import g # 012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ
|
||||
# The next import doesn't fit on one line.
|
||||
from h import i # 012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9012ß9💣2ℝ9
|
||||
|
||||
4
crates/ruff/resources/test/fixtures/isort/required_imports/off.py
vendored
Normal file
4
crates/ruff/resources/test/fixtures/isort/required_imports/off.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# isort: off
|
||||
|
||||
x = 1
|
||||
# isort: on
|
||||
@@ -6,7 +6,16 @@ import f
|
||||
import c
|
||||
import d
|
||||
|
||||
# isort: split
|
||||
# isort: split
|
||||
|
||||
import a
|
||||
import b
|
||||
|
||||
if True:
|
||||
import C
|
||||
import A
|
||||
|
||||
# isort: split
|
||||
|
||||
import D
|
||||
import B
|
||||
|
||||
@@ -40,3 +40,27 @@ def start():
|
||||
#: E117 W191
|
||||
def start():
|
||||
print()
|
||||
#: E112
|
||||
if False: #
|
||||
print()
|
||||
#:
|
||||
if False:
|
||||
print()
|
||||
#:
|
||||
if False: #
|
||||
print()
|
||||
#:
|
||||
if False:
|
||||
print()
|
||||
|
||||
print()
|
||||
#:
|
||||
if False:
|
||||
print()
|
||||
if False:
|
||||
|
||||
print()
|
||||
#:
|
||||
if False:
|
||||
|
||||
print()
|
||||
|
||||
@@ -76,3 +76,11 @@ if x == 4:
|
||||
a[b1, :] == a[b1, ...]
|
||||
b = a[:, b1]
|
||||
#:
|
||||
|
||||
#: E201:1:6
|
||||
spam[ ~ham]
|
||||
|
||||
#: Okay
|
||||
x = [ #
|
||||
'some value',
|
||||
]
|
||||
|
||||
@@ -160,6 +160,7 @@ if alpha[:-i]:
|
||||
*a, b = (1, 2, 3)
|
||||
|
||||
|
||||
@decorator
|
||||
def squares(n):
|
||||
return (i**2 for i in range(n))
|
||||
|
||||
@@ -168,4 +169,14 @@ ENG_PREFIXES = {
|
||||
-6: "\u03bc", # Greek letter mu
|
||||
-3: "m",
|
||||
}
|
||||
|
||||
i = (
|
||||
i + #
|
||||
1
|
||||
)
|
||||
|
||||
x[~y]
|
||||
|
||||
if i == -1:
|
||||
pass
|
||||
#:
|
||||
|
||||
@@ -18,3 +18,13 @@ def foo() -> None:
|
||||
#: E231
|
||||
if (1,2):
|
||||
pass
|
||||
|
||||
#: Okay
|
||||
a = (1,\
|
||||
2)
|
||||
|
||||
#: E231:2:20
|
||||
mdtypes_template = {
|
||||
'tag_full': [('mdtype', 'u4'), ('byte_count', 'u4')],
|
||||
'tag_smalldata':[('byte_count_mdtype', 'u4'), ('data', 'S4')],
|
||||
}
|
||||
|
||||
@@ -64,3 +64,11 @@ a = 42 # (One space one NBSP)
|
||||
#: E262:2:9
|
||||
# (Two spaces) Ok for block comment
|
||||
a = 42 # (Two spaces)
|
||||
|
||||
#: E265:5:1
|
||||
### Means test is not done yet
|
||||
# E Means test is giving error (E)
|
||||
# F Means test is failing (F)
|
||||
# EF Means test is giving error and Failing
|
||||
#! Means test is segfaulting
|
||||
# 8 Means test runs forever
|
||||
|
||||
@@ -56,3 +56,7 @@ if True:
|
||||
def f():
|
||||
print((yield))
|
||||
x = (yield)
|
||||
#: Okay
|
||||
if (a and
|
||||
b):
|
||||
pass
|
||||
|
||||
@@ -19,7 +19,7 @@ if x > 0:
|
||||
else:
|
||||
import e
|
||||
|
||||
y = x + 1
|
||||
__some__magic = 1
|
||||
|
||||
import f
|
||||
|
||||
|
||||
11
crates/ruff/resources/test/fixtures/pycodestyle/E501_2.py
vendored
Normal file
11
crates/ruff/resources/test/fixtures/pycodestyle/E501_2.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
a = """ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
||||
a = """ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
||||
|
||||
b = """ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
||||
b = """ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
||||
|
||||
c = """2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
||||
c = """2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
||||
|
||||
d = """💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
||||
d = """💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A67ß9💣2ℝ4A6"""
|
||||
31
crates/ruff/resources/test/fixtures/pycodestyle/W505_utf_8.py
vendored
Normal file
31
crates/ruff/resources/test/fixtures/pycodestyle/W505_utf_8.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Here's a top-level ß9💣2ℝing that's over theß9💣2ℝ."""
|
||||
|
||||
|
||||
def f1():
|
||||
"""Here's a ß9💣2ℝing that's also over theß9💣2ℝ."""
|
||||
|
||||
x = 1 # Here's a comment that's over theß9💣2ℝ, but it's not standalone.
|
||||
|
||||
# Here's a standalone comment that's over theß9💣2ℝ.
|
||||
|
||||
x = 2
|
||||
# Another standalone that is preceded by a newline and indent toke and is over theß9💣2ℝ.
|
||||
|
||||
print("Here's a string that's over theß9💣2ℝ, but it's not a ß9💣2ℝing.")
|
||||
|
||||
|
||||
"This is also considered a ß9💣2ℝing, and is over theß9💣2ℝ."
|
||||
|
||||
|
||||
def f2():
|
||||
"""Here's a multi-line ß9💣2ℝing.
|
||||
|
||||
It's over theß9💣2ℝ on this line, which isn't the first line in the ß9💣2ℝing.
|
||||
"""
|
||||
|
||||
|
||||
def f3():
|
||||
"""Here's a multi-line ß9💣2ℝing.
|
||||
|
||||
It's over theß9💣2ℝ on this line, which isn't the first line in the ß9💣2ℝing."""
|
||||
4
crates/ruff/resources/test/fixtures/pycodestyle/shebang.py
vendored
Normal file
4
crates/ruff/resources/test/fixtures/pycodestyle/shebang.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
#!
|
||||
#:
|
||||
10
crates/ruff/resources/test/fixtures/pyflakes/F401_12.py
vendored
Normal file
10
crates/ruff/resources/test/fixtures/pyflakes/F401_12.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
"""Test: module bindings are preferred over local bindings, for deferred annotations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Class:
|
||||
datetime: Optional[datetime.datetime]
|
||||
12
crates/ruff/resources/test/fixtures/pyflakes/F401_13.py
vendored
Normal file
12
crates/ruff/resources/test/fixtures/pyflakes/F401_13.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
"""Test: module bindings are preferred over local bindings, for deferred annotations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TypeAlias, List
|
||||
|
||||
|
||||
class Class:
|
||||
List: TypeAlias = List
|
||||
|
||||
def bar(self) -> List:
|
||||
pass
|
||||
8
crates/ruff/resources/test/fixtures/pyflakes/F401_14.py
vendored
Normal file
8
crates/ruff/resources/test/fixtures/pyflakes/F401_14.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Test: module bindings are preferred over local bindings, for deferred annotations."""
|
||||
|
||||
import datetime
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Class:
|
||||
datetime: "Optional[datetime.datetime]"
|
||||
4
crates/ruff/resources/test/fixtures/pyflakes/F811_23.py
vendored
Normal file
4
crates/ruff/resources/test/fixtures/pyflakes/F811_23.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
"""Test that shadowing an explicit re-export produces a warning."""
|
||||
|
||||
import foo as foo
|
||||
import bar as foo
|
||||
5
crates/ruff/resources/test/fixtures/pyflakes/F811_24.py
vendored
Normal file
5
crates/ruff/resources/test/fixtures/pyflakes/F811_24.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Test that shadowing a `__future__` import does not produce a warning."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import annotations
|
||||
24
crates/ruff/resources/test/fixtures/pylint/duplicate_bases.py
vendored
Normal file
24
crates/ruff/resources/test/fixtures/pylint/duplicate_bases.py
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
###
|
||||
# Errors.
|
||||
###
|
||||
class A:
|
||||
...
|
||||
|
||||
|
||||
class B(A, A):
|
||||
...
|
||||
|
||||
|
||||
###
|
||||
# Non-errors.
|
||||
###
|
||||
class C:
|
||||
...
|
||||
|
||||
|
||||
class D(C):
|
||||
...
|
||||
|
||||
|
||||
class E(A, C):
|
||||
...
|
||||
11
crates/ruff/resources/test/fixtures/pylint/duplicate_value.py
vendored
Normal file
11
crates/ruff/resources/test/fixtures/pylint/duplicate_value.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
###
|
||||
# Errors.
|
||||
###
|
||||
incorrect_set = {"value1", 23, 5, "value1"}
|
||||
incorrect_set = {1, 1}
|
||||
|
||||
###
|
||||
# Non-errors.
|
||||
###
|
||||
correct_set = {"value1", 23, 5}
|
||||
correct_set = {5, "5"}
|
||||
19
crates/ruff/resources/test/fixtures/pylint/named_expr_without_context.py
vendored
Normal file
19
crates/ruff/resources/test/fixtures/pylint/named_expr_without_context.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# Errors
|
||||
(a := 42)
|
||||
if True:
|
||||
(b := 1)
|
||||
|
||||
|
||||
class Foo:
|
||||
(c := 1)
|
||||
|
||||
|
||||
# OK
|
||||
if a := 42:
|
||||
print("Success")
|
||||
|
||||
a = 0
|
||||
while (a := a + 1) < 10:
|
||||
print("Correct")
|
||||
|
||||
a = (b := 1)
|
||||
@@ -19,3 +19,9 @@ min(
|
||||
1, # This is a comment.
|
||||
min(2, 3),
|
||||
)
|
||||
|
||||
# Handle iterable expressions.
|
||||
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)))
|
||||
|
||||
@@ -56,3 +56,11 @@ def f(x: "List['Li' 'st[str]']") -> None:
|
||||
|
||||
def f(x: "Li" "st['List[str]']") -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: typing.Deque[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: typing.DefaultDict[str, str]) -> None:
|
||||
...
|
||||
|
||||
@@ -25,3 +25,4 @@ bytes()
|
||||
bytes(b"foo")
|
||||
bytes(b"""
|
||||
foo""")
|
||||
f"{str()}"
|
||||
|
||||
28
crates/ruff/resources/test/fixtures/pyupgrade/UP032_2.py
vendored
Normal file
28
crates/ruff/resources/test/fixtures/pyupgrade/UP032_2.py
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# Errors
|
||||
"{.real}".format(1)
|
||||
"{0.real}".format(1)
|
||||
"{a.real}".format(a=1)
|
||||
|
||||
"{.real}".format(1.0)
|
||||
"{0.real}".format(1.0)
|
||||
"{a.real}".format(a=1.0)
|
||||
|
||||
"{.real}".format(1j)
|
||||
"{0.real}".format(1j)
|
||||
"{a.real}".format(a=1j)
|
||||
|
||||
"{.real}".format(0b01)
|
||||
"{0.real}".format(0b01)
|
||||
"{a.real}".format(a=0b01)
|
||||
|
||||
"{}".format(1 + 2)
|
||||
"{}".format([1, 2])
|
||||
"{}".format({1, 2})
|
||||
"{}".format({1: 2, 3: 4})
|
||||
"{}".format((i for i in range(2)))
|
||||
|
||||
"{.real}".format(1 + 2)
|
||||
"{.real}".format([1, 2])
|
||||
"{.real}".format({1, 2})
|
||||
"{.real}".format({1: 2, 3: 4})
|
||||
"{}".format((i for i in range(2)))
|
||||
@@ -1,26 +1,10 @@
|
||||
class Fun:
|
||||
words = ("how", "fun!")
|
||||
|
||||
def yay(self):
|
||||
return self.words
|
||||
|
||||
yay = Fun().yay
|
||||
|
||||
foo = [4, 5, 6]
|
||||
bar = [1, 2, 3] + foo
|
||||
zoob = tuple(bar)
|
||||
quux = (7, 8, 9) + zoob
|
||||
spam = quux + (10, 11, 12)
|
||||
spom = list(spam)
|
||||
eggs = spom + [13, 14, 15]
|
||||
elatement = ("we all say", ) + yay()
|
||||
excitement = ("we all think", ) + Fun().yay()
|
||||
astonishment = ("we all feel", ) + Fun.words
|
||||
|
||||
chain = ['a', 'b', 'c'] + eggs + list(('yes', 'no', 'pants') + zoob)
|
||||
|
||||
baz = () + zoob
|
||||
|
||||
###
|
||||
# Non-fixable Errors.
|
||||
###
|
||||
foo + [ # This will be preserved.
|
||||
]
|
||||
[*foo] + [ # This will be preserved.
|
||||
]
|
||||
first = [
|
||||
# The order
|
||||
1, # here
|
||||
@@ -38,8 +22,47 @@ second = first + [
|
||||
6,
|
||||
]
|
||||
|
||||
|
||||
###
|
||||
# Fixable errors.
|
||||
###
|
||||
class Fun:
|
||||
words = ("how", "fun!")
|
||||
|
||||
def yay(self):
|
||||
return self.words
|
||||
|
||||
|
||||
yay = Fun().yay
|
||||
|
||||
foo = [4, 5, 6]
|
||||
bar = [1, 2, 3] + foo
|
||||
zoob = tuple(bar)
|
||||
quux = (7, 8, 9) + zoob
|
||||
spam = quux + (10, 11, 12)
|
||||
spom = list(spam)
|
||||
eggs = spom + [13, 14, 15]
|
||||
elatement = ("we all say",) + yay()
|
||||
excitement = ("we all think",) + Fun().yay()
|
||||
astonishment = ("we all feel",) + Fun.words
|
||||
|
||||
chain = ["a", "b", "c"] + eggs + list(("yes", "no", "pants") + zoob)
|
||||
|
||||
baz = () + zoob
|
||||
|
||||
[] + foo + [
|
||||
]
|
||||
|
||||
[] + foo + [ # This will be preserved, but doesn't prevent the fix
|
||||
]
|
||||
pylint_call = [sys.executable, "-m", "pylint"] + args + [path]
|
||||
pylint_call_tuple = (sys.executable, "-m", "pylint") + args + (path, path2)
|
||||
b = a + [2, 3] + [4]
|
||||
|
||||
# Uses the non-preferred quote style, which should be retained.
|
||||
f"{a() + ['b']}"
|
||||
|
||||
###
|
||||
# Non-errors.
|
||||
###
|
||||
a = (1,) + [2]
|
||||
a = [1, 2] + (3, 4)
|
||||
a = ([1, 2, 3] + b) + (4, 5, 6)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
bla = b"bla"
|
||||
d = {"a": b"bla", "b": b"bla", "c": b"bla"}
|
||||
|
||||
|
||||
def foo(one_arg):
|
||||
@@ -7,6 +8,10 @@ def foo(one_arg):
|
||||
|
||||
f"{str(bla)}, {repr(bla)}, {ascii(bla)}" # RUF010
|
||||
|
||||
f"{str(d['a'])}, {repr(d['b'])}, {ascii(d['c'])}" # RUF010
|
||||
|
||||
f"{(str(bla))}, {(repr(bla))}, {(ascii(bla))}" # RUF010
|
||||
|
||||
f"{foo(bla)}" # OK
|
||||
|
||||
f"{str(bla, 'ascii')}, {str(bla, encoding='cp1255')}" # OK
|
||||
|
||||
@@ -88,3 +88,12 @@ import sys # noqa: F401, RUF100
|
||||
print(sys.path)
|
||||
|
||||
"shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]" # noqa: F401
|
||||
|
||||
|
||||
def f():
|
||||
# Ensure that the `noqa` applies to both the overlong line _and_ the unused
|
||||
# variable.
|
||||
a = """Lorem ipsum dolor sit amet.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa
|
||||
|
||||
@@ -8,7 +8,24 @@ def f():
|
||||
...
|
||||
|
||||
|
||||
def g():
|
||||
def f():
|
||||
"""Here's a docstring with a greek rho: ρ"""
|
||||
# And here's a comment with a greek alpha: ∗
|
||||
...
|
||||
|
||||
|
||||
x = "𝐁ad string"
|
||||
x = "−"
|
||||
|
||||
# This should be ignored, since it contains an unambiguous unicode character, and no
|
||||
# ASCII.
|
||||
x = "Русский"
|
||||
|
||||
# The first word should be ignored, while the second should be included, since it
|
||||
# contains ASCII.
|
||||
x = "βα Bαd"
|
||||
|
||||
# The two characters should be flagged here. The first character is a "word"
|
||||
# consisting of a single ambiguous character, while the second character is a "word
|
||||
# boundary" (whitespace) that it itself ambiguous.
|
||||
x = "Р усский"
|
||||
|
||||
23
crates/ruff/resources/test/fixtures/ruff/noqa.py
vendored
Normal file
23
crates/ruff/resources/test/fixtures/ruff/noqa.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
def f():
|
||||
# These should both be ignored by the `noqa`.
|
||||
I = 1 # noqa: E741, F841
|
||||
|
||||
|
||||
def f():
|
||||
# These should both be ignored by the `noqa`.
|
||||
I = 1 # noqa: E741,F841
|
||||
|
||||
|
||||
def f():
|
||||
# These should both be ignored by the `noqa`.
|
||||
I = 1 # noqa: E741 F841
|
||||
|
||||
|
||||
def f():
|
||||
# These should both be ignored by the `noqa`.
|
||||
I = 1 # noqa: E741 , F841
|
||||
|
||||
|
||||
def f():
|
||||
# Only `E741` should be ignored by the `noqa`.
|
||||
I = 1 # noqa: E741.F841
|
||||
@@ -1,3 +1,8 @@
|
||||
"""
|
||||
Violation:
|
||||
|
||||
Checks for `raise` statements within `try` blocks.
|
||||
"""
|
||||
class MyException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
123
crates/ruff/resources/test/fixtures/tryceratops/TRY302.py
vendored
Normal file
123
crates/ruff/resources/test/fixtures/tryceratops/TRY302.py
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
Violation:
|
||||
|
||||
Checks for uses of `raise` directly after a `rescue`.
|
||||
"""
|
||||
class MyException(Exception):
|
||||
pass
|
||||
|
||||
def bad():
|
||||
try:
|
||||
process()
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
def bad():
|
||||
try:
|
||||
process()
|
||||
except Exception:
|
||||
raise
|
||||
print("this code is pointless!")
|
||||
|
||||
def bad():
|
||||
try:
|
||||
process()
|
||||
except:
|
||||
# I am a comment, not a statement!
|
||||
raise
|
||||
|
||||
def bad():
|
||||
try:
|
||||
process()
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
def bad():
|
||||
try:
|
||||
process()
|
||||
except Exception as e:
|
||||
raise
|
||||
|
||||
def bad():
|
||||
try:
|
||||
process()
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def bad():
|
||||
try:
|
||||
process()
|
||||
except MyException:
|
||||
raise
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
def bad():
|
||||
try:
|
||||
process()
|
||||
except MyException as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def bad():
|
||||
try:
|
||||
process()
|
||||
except MyException as ex:
|
||||
raise ex
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def fine():
|
||||
try:
|
||||
process()
|
||||
except Exception as e:
|
||||
raise e from None
|
||||
|
||||
def fine():
|
||||
try:
|
||||
process()
|
||||
except Exception as e:
|
||||
raise e from Exception
|
||||
|
||||
def fine():
|
||||
try:
|
||||
process()
|
||||
except Exception as e:
|
||||
raise ex
|
||||
|
||||
def fine():
|
||||
try:
|
||||
process()
|
||||
except MyException:
|
||||
raise
|
||||
except Exception:
|
||||
print("bar")
|
||||
|
||||
def fine():
|
||||
try:
|
||||
process()
|
||||
except Exception:
|
||||
print("initiating rapid unscheduled disassembly of program")
|
||||
|
||||
def fine():
|
||||
try:
|
||||
process()
|
||||
except MyException:
|
||||
print("oh no!")
|
||||
raise
|
||||
|
||||
def fine():
|
||||
try:
|
||||
process()
|
||||
except Exception:
|
||||
if True:
|
||||
raise
|
||||
|
||||
def fine():
|
||||
try:
|
||||
process()
|
||||
finally:
|
||||
# I am a comment, not a statement!
|
||||
print("but i am a statement")
|
||||
raise
|
||||
@@ -5,19 +5,16 @@ use libcst_native::{
|
||||
Codegen, CodegenState, ImportNames, ParenthesizableWhitespace, SmallStatement, Statement,
|
||||
};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use rustpython_parser::ast::{self, ExcepthandlerKind, Expr, Keyword, Stmt, StmtKind};
|
||||
use rustpython_parser::ast::{self, Excepthandler, Expr, Keyword, Ranged, Stmt};
|
||||
use rustpython_parser::{lexer, Mode, Tok};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::helpers;
|
||||
use ruff_python_ast::imports::{AnyImport, Import};
|
||||
use ruff_python_ast::newlines::NewlineWithTrailingNewline;
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
use ruff_python_semantic::context::Context;
|
||||
|
||||
use crate::cst::helpers::compose_module_path;
|
||||
use crate::cst::matchers::match_module;
|
||||
use crate::importer::Importer;
|
||||
use crate::cst::matchers::match_statement;
|
||||
|
||||
/// Determine if a body contains only a single statement, taking into account
|
||||
/// deleted.
|
||||
@@ -27,22 +24,22 @@ fn has_single_child(body: &[Stmt], deleted: &[&Stmt]) -> bool {
|
||||
|
||||
/// 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.node {
|
||||
StmtKind::FunctionDef(ast::StmtFunctionDef { body, .. })
|
||||
| StmtKind::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. })
|
||||
| StmtKind::ClassDef(ast::StmtClassDef { body, .. })
|
||||
| StmtKind::With(ast::StmtWith { body, .. })
|
||||
| StmtKind::AsyncWith(ast::StmtAsyncWith { body, .. }) => {
|
||||
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")
|
||||
}
|
||||
}
|
||||
StmtKind::For(ast::StmtFor { body, orelse, .. })
|
||||
| StmtKind::AsyncFor(ast::StmtAsyncFor { body, orelse, .. })
|
||||
| StmtKind::While(ast::StmtWhile { body, orelse, .. })
|
||||
| StmtKind::If(ast::StmtIf { body, orelse, .. }) => {
|
||||
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) {
|
||||
@@ -51,17 +48,19 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
|
||||
bail!("Unable to find child in parent body")
|
||||
}
|
||||
}
|
||||
StmtKind::Try(ast::StmtTry {
|
||||
Stmt::Try(ast::StmtTry {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
range: _,
|
||||
})
|
||||
| StmtKind::TryStar(ast::StmtTryStar {
|
||||
| Stmt::TryStar(ast::StmtTryStar {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
range: _,
|
||||
}) => {
|
||||
if body.iter().contains(child) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
@@ -69,10 +68,8 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
|
||||
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.node {
|
||||
ExcepthandlerKind::ExceptHandler(ast::ExcepthandlerExceptHandler {
|
||||
body, ..
|
||||
}) => {
|
||||
} 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 {
|
||||
@@ -85,7 +82,7 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
|
||||
bail!("Unable to find child in parent body")
|
||||
}
|
||||
}
|
||||
StmtKind::Match(ast::StmtMatch { cases, .. }) => {
|
||||
Stmt::Match(ast::StmtMatch { cases, .. }) => {
|
||||
if let Some(body) = cases.iter().find_map(|case| {
|
||||
if case.body.iter().contains(child) {
|
||||
Some(&case.body)
|
||||
@@ -215,9 +212,9 @@ pub(crate) fn remove_unused_imports<'a>(
|
||||
stylist: &Stylist,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(stmt.range());
|
||||
let mut tree = match_module(module_text)?;
|
||||
let mut tree = match_statement(module_text)?;
|
||||
|
||||
let Some(Statement::Simple(body)) = tree.body.first_mut() else {
|
||||
let Statement::Simple(body) = &mut tree else {
|
||||
bail!("Expected Statement::Simple");
|
||||
};
|
||||
|
||||
@@ -268,10 +265,7 @@ pub(crate) fn remove_unused_imports<'a>(
|
||||
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, std::string::String::len)
|
||||
+ member.len()
|
||||
+ 1,
|
||||
relative.len() + module.as_ref().map_or(0, String::len) + member.len() + 1,
|
||||
);
|
||||
for _ in 0..relative.len() {
|
||||
full_name.push('.');
|
||||
@@ -426,86 +420,6 @@ pub(crate) fn remove_argument(
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 the
|
||||
/// name on which the `lru_cache` symbol would be made available (`"functools.lru_cache"`).
|
||||
///
|
||||
/// Attempts to reuse existing imports when possible.
|
||||
pub(crate) fn get_or_import_symbol(
|
||||
module: &str,
|
||||
member: &str,
|
||||
at: TextSize,
|
||||
context: &Context,
|
||||
importer: &Importer,
|
||||
locator: &Locator,
|
||||
) -> Result<(Edit, String)> {
|
||||
if let Some((source, binding)) = context.resolve_qualified_import_name(module, member) {
|
||||
// If the symbol is already available in the current scope, use it.
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// be shadowed between the import and the current location.
|
||||
if source.start() > at {
|
||||
bail!("Unable to use existing symbol `{binding}` due to late-import");
|
||||
}
|
||||
|
||||
// We also add a no-op edit to force conflicts with any other fixes that might try to
|
||||
// remove the import. Consider:
|
||||
//
|
||||
// ```py
|
||||
// import sys
|
||||
//
|
||||
// quit()
|
||||
// ```
|
||||
//
|
||||
// Assume you omit this no-op edit. If you run Ruff with `unused-imports` and
|
||||
// `sys-exit-alias` over this snippet, it will generate two fixes: (1) remove the unused
|
||||
// `sys` import; and (2) replace `quit()` with `sys.exit()`, under the assumption that `sys`
|
||||
// is already imported and available.
|
||||
//
|
||||
// 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(locator.slice(source.range()).to_string(), source.range());
|
||||
Ok((import_edit, binding))
|
||||
} else {
|
||||
if let Some(stmt) = importer.find_import_from(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 context
|
||||
.find_binding(member)
|
||||
.map_or(true, |binding| binding.kind.is_builtin())
|
||||
{
|
||||
let import_edit = importer.add_member(stmt, member)?;
|
||||
Ok((import_edit, member.to_string()))
|
||||
} else {
|
||||
bail!("Unable to insert `{member}` into scope due to name conflict")
|
||||
}
|
||||
} else {
|
||||
// Case 2: No `functools` import is in scope; thus, we add `import functools`, and
|
||||
// return `"functools.cache"` as the bound name.
|
||||
if context
|
||||
.find_binding(module)
|
||||
.map_or(true, |binding| binding.kind.is_builtin())
|
||||
{
|
||||
let import_edit =
|
||||
importer.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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast::Expr;
|
||||
|
||||
use ruff_python_semantic::context::Snapshot;
|
||||
use ruff_python_semantic::model::Snapshot;
|
||||
|
||||
/// A collection of AST nodes that are deferred for later analysis.
|
||||
/// Used to, e.g., store functions, whose bodies shouldn't be analyzed until all
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
|
||||
use rustpython_parser::ast::{self, StmtKind, Suite};
|
||||
use rustpython_parser::ast::{self, Ranged, Stmt, Suite};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::helpers::to_module_path;
|
||||
@@ -14,7 +14,7 @@ use ruff_python_stdlib::path::is_python_stub_file;
|
||||
use crate::directives::IsortDirectives;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::isort;
|
||||
use crate::rules::isort::track::{Block, ImportTracker};
|
||||
use crate::rules::isort::block::{Block, BlockBuilder};
|
||||
use crate::settings::Settings;
|
||||
|
||||
fn extract_import_map(path: &Path, package: Option<&Path>, blocks: &[&Block]) -> Option<ImportMap> {
|
||||
@@ -28,18 +28,19 @@ fn extract_import_map(path: &Path, package: Option<&Path>, blocks: &[&Block]) ->
|
||||
let num_imports = blocks.iter().map(|block| block.imports.len()).sum();
|
||||
let mut module_imports = Vec::with_capacity(num_imports);
|
||||
for stmt in blocks.iter().flat_map(|block| &block.imports) {
|
||||
match &stmt.node {
|
||||
StmtKind::Import(ast::StmtImport { names }) => {
|
||||
match stmt {
|
||||
Stmt::Import(ast::StmtImport { names, range: _ }) => {
|
||||
module_imports.extend(
|
||||
names
|
||||
.iter()
|
||||
.map(|name| ModuleImport::new(name.node.name.to_string(), stmt.range())),
|
||||
.map(|name| ModuleImport::new(name.name.to_string(), stmt.range())),
|
||||
);
|
||||
}
|
||||
StmtKind::ImportFrom(ast::StmtImportFrom {
|
||||
Stmt::ImportFrom(ast::StmtImportFrom {
|
||||
module,
|
||||
names,
|
||||
level,
|
||||
range: _,
|
||||
}) => {
|
||||
let level = level.map_or(0, |level| level.to_usize());
|
||||
let module = if let Some(module) = module {
|
||||
@@ -60,10 +61,10 @@ fn extract_import_map(path: &Path, package: Option<&Path>, blocks: &[&Block]) ->
|
||||
Cow::Owned(module_path[..module_path.len() - level].join("."))
|
||||
};
|
||||
module_imports.extend(names.iter().map(|name| {
|
||||
ModuleImport::new(format!("{}.{}", module, name.node.name), name.range())
|
||||
ModuleImport::new(format!("{}.{}", module, name.name), name.range())
|
||||
}));
|
||||
}
|
||||
_ => panic!("Expected StmtKind::Import | StmtKind::ImportFrom"),
|
||||
_ => panic!("Expected Stmt::Import | Stmt::ImportFrom"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,9 +86,9 @@ pub(crate) fn check_imports(
|
||||
) -> (Vec<Diagnostic>, Option<ImportMap>) {
|
||||
let is_stub = is_python_stub_file(path);
|
||||
|
||||
// Extract all imports from the AST.
|
||||
// Extract all import blocks from the AST.
|
||||
let tracker = {
|
||||
let mut tracker = ImportTracker::new(locator, directives, is_stub);
|
||||
let mut tracker = BlockBuilder::new(locator, directives, is_stub);
|
||||
tracker.visit_body(python_ast);
|
||||
tracker
|
||||
};
|
||||
@@ -108,7 +109,7 @@ pub(crate) fn check_imports(
|
||||
}
|
||||
if settings.rules.enabled(Rule::MissingRequiredImport) {
|
||||
diagnostics.extend(isort::rules::add_required_imports(
|
||||
&blocks, python_ast, locator, stylist, settings, is_stub,
|
||||
python_ast, locator, stylist, settings, is_stub,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -38,19 +38,11 @@ pub(crate) fn check_logical_lines(
|
||||
) -> Vec<Diagnostic> {
|
||||
let mut context = LogicalLinesContext::new(settings);
|
||||
|
||||
#[cfg(feature = "logical_lines")]
|
||||
let should_fix_missing_whitespace = settings.rules.should_fix(Rule::MissingWhitespace);
|
||||
|
||||
#[cfg(not(feature = "logical_lines"))]
|
||||
let should_fix_missing_whitespace = false;
|
||||
|
||||
#[cfg(feature = "logical_lines")]
|
||||
let should_fix_whitespace_before_parameters =
|
||||
settings.rules.should_fix(Rule::WhitespaceBeforeParameters);
|
||||
|
||||
#[cfg(not(feature = "logical_lines"))]
|
||||
let should_fix_whitespace_before_parameters = false;
|
||||
|
||||
let mut prev_line = None;
|
||||
let mut prev_indent_level = None;
|
||||
let indent_char = stylist.indentation().as_char();
|
||||
@@ -65,17 +57,18 @@ pub(crate) fn check_logical_lines(
|
||||
|
||||
if line
|
||||
.flags()
|
||||
.contains(TokenFlags::OPERATOR | TokenFlags::PUNCTUATION)
|
||||
.intersects(TokenFlags::OPERATOR | TokenFlags::BRACKET | TokenFlags::PUNCTUATION)
|
||||
{
|
||||
extraneous_whitespace(&line, &mut context);
|
||||
}
|
||||
|
||||
if line.flags().contains(TokenFlags::KEYWORD) {
|
||||
whitespace_around_keywords(&line, &mut context);
|
||||
missing_whitespace_after_keyword(&line, &mut context);
|
||||
}
|
||||
|
||||
if line.flags().contains(TokenFlags::COMMENT) {
|
||||
whitespace_before_comment(&line, locator, prev_line.is_none(), &mut context);
|
||||
whitespace_before_comment(&line, locator, &mut context);
|
||||
}
|
||||
|
||||
if line.flags().contains(TokenFlags::BRACKET) {
|
||||
@@ -154,97 +147,3 @@ impl<'a> LogicalLinesContext<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use rustpython_parser::{lexer, Mode};
|
||||
|
||||
use crate::rules::pycodestyle::rules::logical_lines::LogicalLines;
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
#[test]
|
||||
fn split_logical_lines() {
|
||||
let contents = r#"
|
||||
x = 1
|
||||
y = 2
|
||||
z = x + 1"#;
|
||||
let lxr: Vec<LexResult> = lexer::lex(contents, Mode::Module).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = LogicalLines::from_tokens(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text_trimmed().to_string())
|
||||
.collect();
|
||||
let expected = vec![
|
||||
"x = 1".to_string(),
|
||||
"y = 2".to_string(),
|
||||
"z = x + 1".to_string(),
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let contents = r#"
|
||||
x = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
y = 2
|
||||
z = x + 1"#;
|
||||
let lxr: Vec<LexResult> = lexer::lex(contents, Mode::Module).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = LogicalLines::from_tokens(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text_trimmed().to_string())
|
||||
.collect();
|
||||
let expected = vec![
|
||||
"x = [\n 1,\n 2,\n 3,\n]".to_string(),
|
||||
"y = 2".to_string(),
|
||||
"z = x + 1".to_string(),
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let contents = "x = 'abc'";
|
||||
let lxr: Vec<LexResult> = lexer::lex(contents, Mode::Module).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = LogicalLines::from_tokens(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text_trimmed().to_string())
|
||||
.collect();
|
||||
let expected = vec!["x = 'abc'".to_string()];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let contents = r#"
|
||||
def f():
|
||||
x = 1
|
||||
f()"#;
|
||||
let lxr: Vec<LexResult> = lexer::lex(contents, Mode::Module).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = LogicalLines::from_tokens(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text_trimmed().to_string())
|
||||
.collect();
|
||||
let expected = vec!["def f():", "x = 1", "f()"];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let contents = r#"
|
||||
def f():
|
||||
"""Docstring goes here."""
|
||||
# Comment goes here.
|
||||
x = 1
|
||||
f()"#;
|
||||
let lxr: Vec<LexResult> = lexer::lex(contents, Mode::Module).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = LogicalLines::from_tokens(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text_trimmed().to_string())
|
||||
.collect();
|
||||
let expected = vec![
|
||||
"def f():",
|
||||
"\"\"\"Docstring goes here.\"\"\"",
|
||||
"",
|
||||
"x = 1",
|
||||
"f()",
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pub(crate) mod ast;
|
||||
pub(crate) mod filesystem;
|
||||
pub(crate) mod imports;
|
||||
#[cfg(feature = "logical_lines")]
|
||||
pub(crate) mod logical_lines;
|
||||
pub(crate) mod noqa;
|
||||
pub(crate) mod physical_lines;
|
||||
|
||||
@@ -55,7 +55,6 @@ pub(crate) fn check_physical_lines(
|
||||
|
||||
let mut commented_lines_iter = indexer.comment_ranges().iter().peekable();
|
||||
let mut doc_lines_iter = doc_lines.iter().peekable();
|
||||
let string_lines = indexer.triple_quoted_string_ranges();
|
||||
|
||||
for (index, line) in locator.contents().universal_newlines().enumerate() {
|
||||
while commented_lines_iter
|
||||
@@ -151,7 +150,7 @@ pub(crate) fn check_physical_lines(
|
||||
}
|
||||
|
||||
if enforce_tab_indentation {
|
||||
if let Some(diagnostic) = tab_indentation(&line, string_lines) {
|
||||
if let Some(diagnostic) = tab_indentation(&line, indexer) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -184,6 +183,7 @@ mod tests {
|
||||
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
|
||||
use crate::line_width::LineLength;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::Settings;
|
||||
|
||||
@@ -197,7 +197,7 @@ mod tests {
|
||||
let indexer = Indexer::from_tokens(&tokens, &locator);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
|
||||
let check_with_max_line_length = |line_length: usize| {
|
||||
let check_with_max_line_length = |line_length: LineLength| {
|
||||
check_physical_lines(
|
||||
Path::new("foo.py"),
|
||||
&locator,
|
||||
@@ -210,7 +210,8 @@ mod tests {
|
||||
},
|
||||
)
|
||||
};
|
||||
assert_eq!(check_with_max_line_length(8), vec![]);
|
||||
assert_eq!(check_with_max_line_length(8), vec![]);
|
||||
let line_length = LineLength::from(8);
|
||||
assert_eq!(check_with_max_line_length(line_length), vec![]);
|
||||
assert_eq!(check_with_max_line_length(line_length), vec![]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,16 @@ 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, pycodestyle,
|
||||
pylint, pyupgrade, ruff,
|
||||
eradicate, flake8_commas, flake8_implicit_str_concat, flake8_pyi, flake8_quotes, flake8_todos,
|
||||
pycodestyle, pylint, pyupgrade, ruff,
|
||||
};
|
||||
use crate::settings::Settings;
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
use ruff_python_ast::source_code::{Indexer, Locator};
|
||||
|
||||
pub(crate) fn check_tokens(
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
tokens: &[LexResult],
|
||||
settings: &Settings,
|
||||
is_stub: bool,
|
||||
@@ -59,6 +60,15 @@ 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);
|
||||
let enforce_todos = settings.rules.any_enabled(&[
|
||||
Rule::InvalidTodoTag,
|
||||
Rule::MissingTodoAuthor,
|
||||
Rule::MissingTodoLink,
|
||||
Rule::MissingTodoColon,
|
||||
Rule::MissingTodoDescription,
|
||||
Rule::InvalidTodoCapitalization,
|
||||
Rule::MissingSpaceAfterTodoColon,
|
||||
]);
|
||||
|
||||
// RUF001, RUF002, RUF003
|
||||
if enforce_ambiguous_unicode_character {
|
||||
@@ -91,15 +101,9 @@ pub(crate) fn check_tokens(
|
||||
|
||||
// ERA001
|
||||
if enforce_commented_out_code {
|
||||
for (tok, range) in tokens.iter().flatten() {
|
||||
if matches!(tok, Tok::Comment(_)) {
|
||||
if let Some(diagnostic) =
|
||||
eradicate::rules::commented_out_code(locator, *range, settings)
|
||||
{
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
diagnostics.extend(eradicate::rules::commented_out_code(
|
||||
indexer, locator, settings,
|
||||
));
|
||||
}
|
||||
|
||||
// W605
|
||||
@@ -176,7 +180,16 @@ pub(crate) fn check_tokens(
|
||||
|
||||
// PYI033
|
||||
if enforce_type_comment_in_stub && is_stub {
|
||||
diagnostics.extend(flake8_pyi::rules::type_comment_in_stub(tokens));
|
||||
diagnostics.extend(flake8_pyi::rules::type_comment_in_stub(indexer, locator));
|
||||
}
|
||||
|
||||
// TD001, TD002, TD003, TD004, TD005, TD006, TD007
|
||||
if enforce_todos {
|
||||
diagnostics.extend(
|
||||
flake8_todos::rules::todos(indexer, locator, settings)
|
||||
.into_iter()
|
||||
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
|
||||
);
|
||||
}
|
||||
|
||||
diagnostics
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
use libcst_native::{Expression, NameOrAttribute};
|
||||
|
||||
fn compose_call_path_inner<'a>(expr: &'a Expression, parts: &mut Vec<&'a str>) {
|
||||
match &expr {
|
||||
match expr {
|
||||
Expression::Call(expr) => {
|
||||
compose_call_path_inner(&expr.func, parts);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use anyhow::{bail, Result};
|
||||
use libcst_native::{
|
||||
Attribute, Call, Comparison, Dict, Expr, Expression, Import, ImportAlias, ImportFrom,
|
||||
ImportNames, Module, SimpleString, SmallStatement, Statement,
|
||||
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,
|
||||
};
|
||||
|
||||
pub(crate) fn match_module(module_text: &str) -> Result<Module> {
|
||||
@@ -14,24 +16,19 @@ pub(crate) fn match_module(module_text: &str) -> Result<Module> {
|
||||
pub(crate) fn match_expression(expression_text: &str) -> Result<Expression> {
|
||||
match libcst_native::parse_expression(expression_text) {
|
||||
Ok(expression) => Ok(expression),
|
||||
Err(_) => bail!("Failed to extract CST from source"),
|
||||
Err(_) => bail!("Failed to extract expression from source"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_expr<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Expr<'b>> {
|
||||
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
|
||||
if let Some(SmallStatement::Expr(expr)) = expr.body.first_mut() {
|
||||
Ok(expr)
|
||||
} else {
|
||||
bail!("Expected SmallStatement::Expr")
|
||||
}
|
||||
} else {
|
||||
bail!("Expected Statement::Simple")
|
||||
pub(crate) fn match_statement(statement_text: &str) -> Result<Statement> {
|
||||
match libcst_native::parse_statement(statement_text) {
|
||||
Ok(statement) => Ok(statement),
|
||||
Err(_) => bail!("Failed to extract statement from source"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_import<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Import<'b>> {
|
||||
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
|
||||
pub(crate) fn match_import<'a, 'b>(statement: &'a mut Statement<'b>) -> Result<&'a mut Import<'b>> {
|
||||
if let Statement::Simple(expr) = statement {
|
||||
if let Some(SmallStatement::Import(expr)) = expr.body.first_mut() {
|
||||
Ok(expr)
|
||||
} else {
|
||||
@@ -43,9 +40,9 @@ pub(crate) fn match_import<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut
|
||||
}
|
||||
|
||||
pub(crate) fn match_import_from<'a, 'b>(
|
||||
module: &'a mut Module<'b>,
|
||||
statement: &'a mut Statement<'b>,
|
||||
) -> Result<&'a mut ImportFrom<'b>> {
|
||||
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
|
||||
if let Statement::Simple(expr) = statement {
|
||||
if let Some(SmallStatement::ImportFrom(expr)) = expr.body.first_mut() {
|
||||
Ok(expr)
|
||||
} else {
|
||||
@@ -66,7 +63,17 @@ pub(crate) fn match_aliases<'a, 'b>(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_call<'a, 'b>(expression: &'a mut Expression<'b>) -> Result<&'a mut Call<'b>> {
|
||||
pub(crate) fn match_call<'a, 'b>(expression: &'a Expression<'b>) -> Result<&'a Call<'b>> {
|
||||
if let Expression::Call(call) = expression {
|
||||
Ok(call)
|
||||
} else {
|
||||
bail!("Expected Expression::Call")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_call_mut<'a, 'b>(
|
||||
expression: &'a mut Expression<'b>,
|
||||
) -> Result<&'a mut Call<'b>> {
|
||||
if let Expression::Call(call) = expression {
|
||||
Ok(call)
|
||||
} else {
|
||||
@@ -111,3 +118,123 @@ pub(crate) fn match_simple_string<'a, 'b>(
|
||||
bail!("Expected Expression::SimpleString")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_formatted_string<'a, 'b>(
|
||||
expression: &'a mut Expression<'b>,
|
||||
) -> Result<&'a mut FormattedString<'b>> {
|
||||
if let Expression::FormattedString(formatted_string) = expression {
|
||||
Ok(formatted_string)
|
||||
} else {
|
||||
bail!("Expected Expression::FormattedString")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_formatted_string_expression<'a, 'b>(
|
||||
formatted_string_content: &'a mut FormattedStringContent<'b>,
|
||||
) -> Result<&'a mut FormattedStringExpression<'b>> {
|
||||
if let FormattedStringContent::Expression(formatted_string_expression) =
|
||||
formatted_string_content
|
||||
{
|
||||
Ok(formatted_string_expression)
|
||||
} else {
|
||||
bail!("Expected FormattedStringContent::Expression")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_name<'a, 'b>(expression: &'a Expression<'b>) -> Result<&'a Name<'b>> {
|
||||
if let Expression::Name(name) = expression {
|
||||
Ok(name)
|
||||
} else {
|
||||
bail!("Expected Expression::Name")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_arg<'a, 'b>(call: &'a Call<'b>) -> Result<&'a Arg<'b>> {
|
||||
if let Some(arg) = call.args.first() {
|
||||
Ok(arg)
|
||||
} else {
|
||||
bail!("Expected Arg")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_generator_exp<'a, 'b>(
|
||||
expression: &'a Expression<'b>,
|
||||
) -> Result<&'a GeneratorExp<'b>> {
|
||||
if let Expression::GeneratorExp(generator_exp) = expression {
|
||||
Ok(generator_exp)
|
||||
} else {
|
||||
bail!("Expected Expression::GeneratorExp")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_tuple<'a, 'b>(expression: &'a Expression<'b>) -> Result<&'a Tuple<'b>> {
|
||||
if let Expression::Tuple(tuple) = expression {
|
||||
Ok(tuple)
|
||||
} else {
|
||||
bail!("Expected Expression::Tuple")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_list_comp<'a, 'b>(expression: &'a Expression<'b>) -> Result<&'a ListComp<'b>> {
|
||||
if let Expression::ListComp(list_comp) = expression {
|
||||
Ok(list_comp)
|
||||
} else {
|
||||
bail!("Expected Expression::ListComp")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_lambda<'a, 'b>(expression: &'a Expression<'b>) -> Result<&'a Lambda<'b>> {
|
||||
if let Expression::Lambda(lambda) = expression {
|
||||
Ok(lambda)
|
||||
} else {
|
||||
bail!("Expected Expression::Lambda")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_function_def<'a, 'b>(
|
||||
statement: &'a mut Statement<'b>,
|
||||
) -> Result<&'a mut FunctionDef<'b>> {
|
||||
if let Statement::Compound(compound) = statement {
|
||||
if let CompoundStatement::FunctionDef(function_def) = compound {
|
||||
Ok(function_def)
|
||||
} else {
|
||||
bail!("Expected CompoundStatement::FunctionDef")
|
||||
}
|
||||
} else {
|
||||
bail!("Expected Statement::Compound")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_indented_block<'a, 'b>(
|
||||
suite: &'a mut Suite<'b>,
|
||||
) -> Result<&'a mut IndentedBlock<'b>> {
|
||||
if let Suite::IndentedBlock(indented_block) = suite {
|
||||
Ok(indented_block)
|
||||
} else {
|
||||
bail!("Expected Suite::IndentedBlock")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_with<'a, 'b>(statement: &'a mut Statement<'b>) -> Result<&'a mut With<'b>> {
|
||||
if let Statement::Compound(compound) = statement {
|
||||
if let CompoundStatement::With(with) = compound {
|
||||
Ok(with)
|
||||
} else {
|
||||
bail!("Expected CompoundStatement::With")
|
||||
}
|
||||
} else {
|
||||
bail!("Expected Statement::Compound")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn match_if<'a, 'b>(statement: &'a mut Statement<'b>) -> Result<&'a mut If<'b>> {
|
||||
if let Statement::Compound(compound) = statement {
|
||||
if let CompoundStatement::If(if_) = compound {
|
||||
Ok(if_)
|
||||
} else {
|
||||
bail!("Expected CompoundStatement::If")
|
||||
}
|
||||
} else {
|
||||
bail!("Expected Statement::Compound")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
//! Extract `# noqa` and `# isort: skip` directives from tokenized source.
|
||||
|
||||
use crate::noqa::NoqaMapping;
|
||||
use bitflags::bitflags;
|
||||
use ruff_python_ast::source_code::{Indexer, Locator};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use rustpython_parser::Tok;
|
||||
|
||||
use ruff_python_ast::source_code::{Indexer, Locator};
|
||||
|
||||
use crate::noqa::NoqaMapping;
|
||||
use crate::settings::Settings;
|
||||
|
||||
bitflags! {
|
||||
@@ -82,11 +83,7 @@ pub fn extract_directives(
|
||||
}
|
||||
|
||||
/// Extract a mapping from logical line to noqa line.
|
||||
pub fn extract_noqa_line_for(
|
||||
lxr: &[LexResult],
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
) -> NoqaMapping {
|
||||
fn extract_noqa_line_for(lxr: &[LexResult], locator: &Locator, indexer: &Indexer) -> NoqaMapping {
|
||||
let mut string_mappings = Vec::new();
|
||||
|
||||
for (tok, range) in lxr.iter().flatten() {
|
||||
@@ -102,7 +99,10 @@ pub fn extract_noqa_line_for(
|
||||
..
|
||||
} => {
|
||||
if locator.contains_line_break(*range) {
|
||||
string_mappings.push(*range);
|
||||
string_mappings.push(TextRange::new(
|
||||
locator.line_start(range.start()),
|
||||
range.end(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ pub fn extract_noqa_line_for(
|
||||
}
|
||||
|
||||
/// Extract a set of ranges over which to disable isort.
|
||||
pub fn extract_isort_directives(lxr: &[LexResult], locator: &Locator) -> IsortDirectives {
|
||||
fn extract_isort_directives(lxr: &[LexResult], locator: &Locator) -> IsortDirectives {
|
||||
let mut exclusions: Vec<TextRange> = Vec::default();
|
||||
let mut splits: Vec<TextSize> = Vec::default();
|
||||
let mut off: Option<TextSize> = None;
|
||||
@@ -219,11 +219,12 @@ pub fn extract_isort_directives(lxr: &[LexResult], locator: &Locator) -> IsortDi
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_python_ast::source_code::{Indexer, Locator};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use rustpython_parser::{lexer, Mode};
|
||||
|
||||
use ruff_python_ast::source_code::{Indexer, Locator};
|
||||
|
||||
use crate::directives::{extract_isort_directives, extract_noqa_line_for};
|
||||
use crate::noqa::NoqaMapping;
|
||||
|
||||
@@ -271,7 +272,7 @@ y = 2
|
||||
z = x + 1";
|
||||
assert_eq!(
|
||||
noqa_mappings(contents),
|
||||
NoqaMapping::from_iter([TextRange::new(TextSize::from(4), TextSize::from(22)),])
|
||||
NoqaMapping::from_iter([TextRange::new(TextSize::from(0), TextSize::from(22)),])
|
||||
);
|
||||
|
||||
let contents = "x = 1
|
||||
@@ -282,7 +283,7 @@ ghi
|
||||
z = 2";
|
||||
assert_eq!(
|
||||
noqa_mappings(contents),
|
||||
NoqaMapping::from_iter([TextRange::new(TextSize::from(10), TextSize::from(28))])
|
||||
NoqaMapping::from_iter([TextRange::new(TextSize::from(6), TextSize::from(28))])
|
||||
);
|
||||
|
||||
let contents = "x = 1
|
||||
@@ -292,7 +293,7 @@ ghi
|
||||
'''";
|
||||
assert_eq!(
|
||||
noqa_mappings(contents),
|
||||
NoqaMapping::from_iter([TextRange::new(TextSize::from(10), TextSize::from(28))])
|
||||
NoqaMapping::from_iter([TextRange::new(TextSize::from(6), TextSize::from(28))])
|
||||
);
|
||||
|
||||
let contents = r#"x = \
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user