Compare commits

...

56 Commits

Author SHA1 Message Date
konstin
a3aa841fc9 Overhaul sdist handling (#4439)
* Reduce sdist size

`maturin sdist && du -sh target/wheels/ruff-0.0.267.tar.gz`:
Before: 1,1M
After: 668K

* Test sdist before release

* Update maturin to fix the sdist
2023-05-18 19:02:22 +02:00
Ville Skyttä
fdd894145b S608 improvements (#4499) 2023-05-18 11:27:22 -04:00
Charlie Marsh
85f67b2ee3 Make the AST Checker pub(crate) (#4498) 2023-05-18 15:17:26 +00:00
Charlie Marsh
e9c6f16c56 Move unparse utility methods onto Generator (#4497) 2023-05-18 15:00:46 +00:00
Charlie Marsh
d3b18345c5 Move triple-quoted string detection into Indexer method (#4495) 2023-05-18 14:42:05 +00:00
Jonathan Plasse
0e4d174551 Fix COM812 false positive in string subscript (#4493) 2023-05-18 14:35:41 +00:00
Charlie Marsh
73efbeb581 Invert quote-style when generating code within f-strings (#4487) 2023-05-18 14:33:33 +00:00
Charlie Marsh
2fb312bb2b Fix scoping of comprehensions within classes (#4494) 2023-05-18 14:30:02 +00:00
Charlie Marsh
e8e66f3824 Remove unnecessary path prefixes (#4492) 2023-05-18 10:19:09 -04:00
Charlie Marsh
a8d080c825 Extend multi-line noqa directives to start-of-line (#4490) 2023-05-18 13:05:27 +00:00
Charlie Marsh
ddd541b198 Move Insertion into its own module (#4478) 2023-05-17 21:11:41 +00:00
Tom Kuson
3090aec97d Add PLW docs (#4469) 2023-05-17 18:30:45 +00:00
Charlie Marsh
14c6419bc1 Bring pycodestyle rules into full compatibility (on SciPy) (#4472) 2023-05-17 16:51:55 +00:00
Charlie Marsh
3bc29d6c0c Allow shebang comments at start-of-file (#4473) 2023-05-17 16:32:12 +00:00
Charlie Marsh
67c5086aba Include precise tokens for extraneous-whitespace diagnostics (#4471) 2023-05-17 16:25:17 +00:00
Charlie Marsh
cd82b83f89 Avoid triggering pd#at and friends on non-subscripts (#4474) 2023-05-17 16:20:58 +00:00
Charlie Marsh
39fb2cc732 Remove special-casing for whitespace-around-@ (#4458) 2023-05-17 15:32:08 +00:00
John Kelly
9c732c7946 Implement TRY302 - raise after except (#4461) 2023-05-17 01:36:10 +00:00
Charlie Marsh
2332ea5753 Remove type-complexity ignores from map_codes.rs (#4463) 2023-05-17 01:02:24 +00:00
Charlie Marsh
6b1062ccc3 Enable pycodestyle rules under new "nursery" category (#4407) 2023-05-16 21:21:58 +00:00
Charlie Marsh
39fa38cb35 Enable pycodestyle rules (#3689) 2023-05-16 20:39:43 +00:00
Micha Reiser
ddf7de7e86 Prototype Black's string joining/splitting (#4449) 2023-05-16 18:42:40 +01:00
Charlie Marsh
e5101e8eac Split logical lines tests into one test per assertion (#4457) 2023-05-16 17:40:39 +00:00
Charlie Marsh
d9c3f8e249 Avoid flagging missing whitespace for decorators (#4454) 2023-05-16 13:15:01 -04:00
Charlie Marsh
7e0d018b35 Avoid emitting empty logical lines (#4452) 2023-05-16 16:33:33 +00:00
Jeong, YunWon
4b05ca1198 Specialize ConversionFlag (#4450) 2023-05-16 18:00:13 +02:00
Charlie Marsh
f0465bf106 Emit non-logical newlines for "empty" lines (#4444) 2023-05-16 14:58:56 +00:00
Charlie Marsh
8134ec25f0 Fix expected-indentation errors with end-of-line comments (#4438) 2023-05-16 10:45:54 -04:00
Jeong, YunWon
6049aabe27 Update RustPyhon and enable full-lexer feature (#4442) 2023-05-16 07:19:57 +00:00
Jeong, YunWon
badade3ccc Impl Default for SourceLocation (#4328)
Co-authored-by: Micha Reiser <micha@reiser.io>
2023-05-16 07:03:43 +00:00
Micha Reiser
fa26860296 Refactor range from Attributed to Nodes (#4422) 2023-05-16 06:36:32 +00:00
James Lamb
140e0acf54 Add LightGBM to user list (#4446) 2023-05-16 04:04:37 +00:00
Sladyn
c711db11ce [flake8-pyi] Implement unannotated-assignment-in-stub (PY052) (#4293) 2023-05-16 02:06:55 +00:00
Charlie Marsh
1fe6954150 Fix bidirectional-unicode formatting (#4445) 2023-05-15 22:36:25 +00:00
Charlie Marsh
2414469ac3 Enable automatic rewrites of typing.Deque and typing.DefaultDict (#4420) 2023-05-15 22:33:24 +00:00
Tom Kuson
838ba1ca3d Add PLE rule docs (#4437) 2023-05-15 19:48:18 +00:00
Charlie Marsh
8f3f8d3e0b Revert change to re-run release on tag update (#4441) 2023-05-15 15:48:45 +00:00
qdegraaf
8ba9eb83af Implement flake8-async plugin (#4432) 2023-05-15 09:15:28 -04:00
Zanie Adkins
2c6efc2f5f Update C419 to be a suggested fix (#4424) 2023-05-15 10:30:40 +02:00
Ben Doerry
d6930ca991 Merge subsettings when extending configurations (#4431) 2023-05-15 02:34:58 +00:00
Yanks Yoon
f70c286e6a docs: update contributing guide (#4428) 2023-05-15 02:21:37 +00:00
Charlie Marsh
dcff515ad8 Make extend_function_names an Option type (#4434) 2023-05-15 02:15:02 +00:00
Jonathan Plasse
b9e387013f Fix RUF010 autofix within f-strings (#4423) 2023-05-15 02:08:30 +00:00
Charlie Marsh
a69451ff46 [pyupgrade] Remove keep-runtime-typing setting (#4427) 2023-05-14 03:12:52 +00:00
Tyler Yep
01b372a75c Implement flake8-future-annotations FA100 (#3979) 2023-05-14 03:00:06 +00:00
Charlie Marsh
cd2e7fa72a Use TextSize for flake8-todos Directive methods (#4426) 2023-05-13 22:05:51 -04:00
Charlie Marsh
fdf0b999cd Replace TODO tag regex with a lexer (#4413) 2023-05-13 15:23:46 +00:00
Jonathan Plasse
45b5fa573f Ignore ANN401 for overridden methods (#4409) 2023-05-13 15:20:04 +00:00
Jonathan Plasse
a0258f2205 [pylint] Fix PLW3301 auto-fix with generators (#4412) 2023-05-13 11:17:13 -04:00
alm
0a68636de3 [pylint] Add duplicate-bases rule (#4411) 2023-05-13 14:28:03 +00:00
Evan Rittenhouse
2f53781a77 Implement flake8_todos (#3921) 2023-05-13 14:19:06 +00:00
Micha Reiser
7e7be05ddf Upgrade dependencies (#4389) 2023-05-13 13:00:25 +00:00
Micha Reiser
f5afa8198c Use new rustpython_format crate over rustpython-common (#4388) 2023-05-13 12:35:02 +00:00
Charlie Marsh
eeabfd6d18 Enable autofix for split-assertions at top level (#4405) 2023-05-12 17:35:49 -04:00
Charlie Marsh
490301f9fe Replace macro_rules! visitors with dedicated methods (#4402) 2023-05-12 17:05:59 -04:00
Zanie Adkins
f5be3d8e5b Update CI to test Python wheel on Linux (#4398) 2023-05-12 16:27:18 -04:00
539 changed files with 11822 additions and 7064 deletions

View File

@@ -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:
@@ -194,6 +196,28 @@ jobs:
exit 1
fi
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: |
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
ruff --help
python -m ruff --help
pre-commit:
name: "pre-commit"
runs-on: ubuntu-latest

View File

@@ -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

View File

@@ -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))

View File

@@ -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).

437
Cargo.lock generated
View File

@@ -76,9 +76,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.3.0"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371"
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -115,9 +115,9 @@ dependencies = [
[[package]]
name = "anstyle-wincon"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd"
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
dependencies = [
"anstyle",
"windows-sys 0.48.0",
@@ -125,9 +125,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.70"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "argfile"
@@ -138,12 +138,6 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "ascii"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
[[package]]
name = "assert_cmd"
version = "2.0.11"
@@ -151,7 +145,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86d6b683edf8d1119fe420a94f8a7e389239666aa72e65495d91c00462510151"
dependencies = [
"anstyle",
"bstr 1.4.0",
"bstr",
"doc-comment",
"predicates",
"predicates-core",
@@ -203,17 +197,6 @@ version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813"
[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr",
"regex-automata",
]
[[package]]
name = "bstr"
version = "1.4.0"
@@ -228,9 +211,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.12.1"
version = "3.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b"
[[package]]
name = "cachedir"
@@ -285,9 +268,9 @@ dependencies = [
[[package]]
name = "ciborium"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f"
checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926"
dependencies = [
"ciborium-io",
"ciborium-ll",
@@ -296,15 +279,15 @@ dependencies = [
[[package]]
name = "ciborium-io"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369"
checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656"
[[package]]
name = "ciborium-ll"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b"
checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b"
dependencies = [
"ciborium-io",
"half",
@@ -312,9 +295,9 @@ dependencies = [
[[package]]
name = "clap"
version = "3.2.23"
version = "3.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
dependencies = [
"bitflags 1.3.2",
"clap_lex 0.2.4",
@@ -324,9 +307,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.2.4"
version = "4.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62"
checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938"
dependencies = [
"clap_builder",
"clap_derive",
@@ -335,9 +318,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.2.4"
version = "4.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749"
checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd"
dependencies = [
"anstream",
"anstyle",
@@ -348,11 +331,11 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.2.1"
version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a19591b2ab0e3c04b588a0e04ddde7b9eaa423646d1b4a8092879216bf47473"
checksum = "1594fe2312ec4abf402076e407628f5c313e54c32ade058521df4ee34ecac8a8"
dependencies = [
"clap 4.2.4",
"clap 4.2.7",
]
[[package]]
@@ -361,7 +344,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "183495371ea78d4c9ff638bfc6497d46fed2396e4f9c50aebc1278a4a9919a3d"
dependencies = [
"clap 4.2.4",
"clap 4.2.7",
"clap_complete",
"clap_complete_fig",
"clap_complete_nushell",
@@ -373,7 +356,7 @@ version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3af28956330989baa428ed4d3471b853715d445c62de21b67292e22cf8a41fa"
dependencies = [
"clap 4.2.4",
"clap 4.2.7",
"clap_complete",
]
@@ -383,7 +366,7 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fa41f5e6aa83bd151b70fd0ceaee703d68cd669522795dc812df9edad1252c"
dependencies = [
"clap 4.2.4",
"clap 4.2.7",
"clap_complete",
]
@@ -427,16 +410,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "colorchoice"
version = "1.0.0"
@@ -517,7 +490,7 @@ dependencies = [
"atty",
"cast",
"ciborium",
"clap 3.2.23",
"clap 3.2.25",
"criterion-plot",
"itertools",
"lazy_static",
@@ -602,50 +575,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "cxx"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn 2.0.15",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb"
[[package]]
name = "cxxbridge-macro"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "diff"
version = "0.1.13"
@@ -669,11 +598,11 @@ dependencies = [
[[package]]
name = "dirs"
version = "5.0.0"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys 0.4.0",
"dirs-sys 0.4.1",
]
[[package]]
@@ -689,13 +618,14 @@ dependencies = [
[[package]]
name = "dirs-sys"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.45.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -784,7 +714,7 @@ name = "flake8-to-ruff"
version = "0.0.267"
dependencies = [
"anyhow",
"clap 4.2.4",
"clap 4.2.7",
"colored",
"configparser",
"once_cell",
@@ -800,9 +730,9 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.25"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -858,7 +788,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
dependencies = [
"aho-corasick 0.7.20",
"bstr 1.4.0",
"bstr",
"fnv",
"log",
"regex",
@@ -931,12 +861,11 @@ dependencies = [
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cxx",
"cxx-build",
"cc",
]
[[package]]
@@ -1081,9 +1010,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "js-sys"
version = "0.3.61"
version = "0.3.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
checksum = "68c16e1bfd491478ab155fd8b4896b86f9ede344949b641e61501e07c2b8b4d5"
dependencies = [
"wasm-bindgen",
]
@@ -1152,9 +1081,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.142"
version = "0.2.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
[[package]]
name = "libcst"
@@ -1182,23 +1111,14 @@ dependencies = [
[[package]]
name = "libmimalloc-sys"
version = "0.1.32"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a558e3d911bc3c7bfc8c78bc580b404d6e51c1cefbf656e176a94b49b0df40"
checksum = "f4ac0e912c8ef1b735e92369695618dc5b1819f5a7bf3f167301a3ba1cea515e"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "link-cplusplus"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
dependencies = [
"cc",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
@@ -1207,19 +1127,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.3.3"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c"
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
[[package]]
name = "log"
@@ -1230,15 +1140,6 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "lz4_flex"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a8cbbb2831780bc3b9c15a41f5b49222ef756b6730a95f3decfdd15903eb5a3"
dependencies = [
"twox-hash",
]
[[package]]
name = "matches"
version = "0.1.10"
@@ -1262,9 +1163,9 @@ dependencies = [
[[package]]
name = "mimalloc"
version = "0.1.36"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d88dad3f985ec267a3fcb7a1726f5cb1a7e8cad8b646e70a84f967210df23da"
checksum = "4e2894987a3459f3ffb755608bd82188f8ed00d0ae077f1edea29c068d639d98"
dependencies = [
"libmimalloc-sys",
]
@@ -1277,9 +1178,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.6.2"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
@@ -1365,15 +1266,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.45"
@@ -1415,6 +1307,12 @@ version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "os_str_bytes"
version = "6.5.0"
@@ -1441,18 +1339,18 @@ checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
[[package]]
name = "path-absolutize"
version = "3.0.14"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f1d4993b16f7325d90c18c3c6a3327db7808752db8d208cea0acee0abd52c52"
checksum = "43eb3595c63a214e1b37b44f44b0a84900ef7ae0b4c5efce59e123d246d7a0de"
dependencies = [
"path-dedot",
]
[[package]]
name = "path-dedot"
version = "3.0.18"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a81540d94551664b72b72829b12bd167c73c9d25fbac0e04fafa8023f7e4901"
checksum = "9d55e486337acb9973cdea3ec5638c1b3bcb22e573b2b7b41969e0c744d5a15e"
dependencies = [
"once_cell",
]
@@ -1492,9 +1390,9 @@ checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
[[package]]
name = "pep440_rs"
version = "0.3.5"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aac177a025c60a4dd25d638bf33e746d1ead5f7123f6650f35b4394c7ce1a104"
checksum = "fe1d15693a11422cfa7d401b00dc9ae9fb8edbfbcb711a77130663f4ddf67650"
dependencies = [
"lazy_static",
"regex",
@@ -1592,12 +1490,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "predicates"
version = "3.0.3"
@@ -1696,37 +1588,19 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.26"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
@@ -1735,9 +1609,6 @@ name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rayon"
@@ -1858,9 +1729,9 @@ dependencies = [
"anyhow",
"bitflags 2.2.1",
"chrono",
"clap 4.2.4",
"clap 4.2.7",
"colored",
"dirs 5.0.0",
"dirs 5.0.1",
"fern",
"glob",
"globset",
@@ -1892,7 +1763,7 @@ dependencies = [
"ruff_rustpython",
"ruff_text_size",
"rustc-hash",
"rustpython-common",
"rustpython-format",
"rustpython-parser",
"schemars",
"semver",
@@ -1952,7 +1823,7 @@ dependencies = [
"bitflags 2.2.1",
"cachedir",
"chrono",
"clap 4.2.4",
"clap 4.2.7",
"clap_complete_command",
"clearscreen",
"colored",
@@ -1990,7 +1861,7 @@ name = "ruff_dev"
version = "0.0.0"
dependencies = [
"anyhow",
"clap 4.2.4",
"clap 4.2.7",
"itertools",
"libcst",
"once_cell",
@@ -1999,7 +1870,7 @@ dependencies = [
"ruff",
"ruff_cli",
"ruff_diagnostics",
"rustpython-common",
"rustpython-format",
"rustpython-parser",
"schemars",
"serde_json",
@@ -2059,7 +1930,7 @@ dependencies = [
"regex",
"ruff_text_size",
"rustc-hash",
"rustpython-literal 0.2.0 (git+https://github.com/RustPython/Parser.git?rev=947fb53d0b41fec465db3d8e725bdb2eec1299ec)",
"rustpython-literal",
"rustpython-parser",
"serde",
"smallvec",
@@ -2070,7 +1941,7 @@ name = "ruff_python_formatter"
version = "0.0.0"
dependencies = [
"anyhow",
"clap 4.2.4",
"clap 4.2.7",
"insta",
"is-macro",
"itertools",
@@ -2130,7 +2001,7 @@ dependencies = [
[[package]]
name = "ruff_text_size"
version = "0.0.0"
source = "git+https://github.com/RustPython/Parser.git?rev=947fb53d0b41fec465db3d8e725bdb2eec1299ec#947fb53d0b41fec465db3d8e725bdb2eec1299ec"
source = "git+https://github.com/RustPython/Parser.git?rev=e820928f11a2453314ad4d5ce23f066d1d3faf73#e820928f11a2453314ad4d5ce23f066d1d3faf73"
dependencies = [
"schemars",
"serde",
@@ -2174,9 +2045,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.37.13"
version = "0.37.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0"
checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
dependencies = [
"bitflags 1.3.2",
"errno",
@@ -2201,54 +2072,33 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/Parser.git?rev=947fb53d0b41fec465db3d8e725bdb2eec1299ec#947fb53d0b41fec465db3d8e725bdb2eec1299ec"
source = "git+https://github.com/RustPython/Parser.git?rev=e820928f11a2453314ad4d5ce23f066d1d3faf73#e820928f11a2453314ad4d5ce23f066d1d3faf73"
dependencies = [
"is-macro",
"num-bigint",
"rustpython-parser-core",
"static_assertions",
]
[[package]]
name = "rustpython-common"
name = "rustpython-format"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=f3e4d3409253660bd4fa7f3d24d3db747e7dca61#f3e4d3409253660bd4fa7f3d24d3db747e7dca61"
source = "git+https://github.com/RustPython/Parser.git?rev=e820928f11a2453314ad4d5ce23f066d1d3faf73#e820928f11a2453314ad4d5ce23f066d1d3faf73"
dependencies = [
"ascii",
"bitflags 2.2.1",
"bstr 0.2.17",
"cfg-if",
"itertools",
"libc",
"lock_api",
"num-bigint",
"num-complex",
"num-traits",
"once_cell",
"radium",
"rand",
"rustpython-literal 0.2.0 (git+https://github.com/youknowone/RustPython-parser.git?rev=5b2af304a2baa53598e594097824165d4ac7a119)",
"siphasher",
"volatile",
"widestring",
"rustpython-literal",
]
[[package]]
name = "rustpython-literal"
version = "0.2.0"
source = "git+https://github.com/youknowone/RustPython-parser.git?rev=5b2af304a2baa53598e594097824165d4ac7a119#5b2af304a2baa53598e594097824165d4ac7a119"
dependencies = [
"hexf-parse",
"lexical-parse-float",
"num-traits",
"unic-ucd-category",
]
[[package]]
name = "rustpython-literal"
version = "0.2.0"
source = "git+https://github.com/RustPython/Parser.git?rev=947fb53d0b41fec465db3d8e725bdb2eec1299ec#947fb53d0b41fec465db3d8e725bdb2eec1299ec"
source = "git+https://github.com/RustPython/Parser.git?rev=e820928f11a2453314ad4d5ce23f066d1d3faf73#e820928f11a2453314ad4d5ce23f066d1d3faf73"
dependencies = [
"hexf-parse",
"is-macro",
"lexical-parse-float",
"num-traits",
"unic-ucd-category",
@@ -2257,9 +2107,10 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/Parser.git?rev=947fb53d0b41fec465db3d8e725bdb2eec1299ec#947fb53d0b41fec465db3d8e725bdb2eec1299ec"
source = "git+https://github.com/RustPython/Parser.git?rev=e820928f11a2453314ad4d5ce23f066d1d3faf73#e820928f11a2453314ad4d5ce23f066d1d3faf73"
dependencies = [
"anyhow",
"is-macro",
"itertools",
"lalrpop-util",
"log",
@@ -2279,12 +2130,9 @@ dependencies = [
[[package]]
name = "rustpython-parser-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/Parser.git?rev=947fb53d0b41fec465db3d8e725bdb2eec1299ec#947fb53d0b41fec465db3d8e725bdb2eec1299ec"
source = "git+https://github.com/RustPython/Parser.git?rev=e820928f11a2453314ad4d5ce23f066d1d3faf73#e820928f11a2453314ad4d5ce23f066d1d3faf73"
dependencies = [
"itertools",
"lz4_flex",
"num-bigint",
"num-complex",
"is-macro",
"ruff_text_size",
]
@@ -2345,12 +2193,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "scratch"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
[[package]]
name = "sct"
version = "0.7.0"
@@ -2369,9 +2211,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
[[package]]
name = "serde"
version = "1.0.160"
version = "1.0.163"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2"
dependencies = [
"serde_derive",
]
@@ -2389,9 +2231,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.160"
version = "1.0.163"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
dependencies = [
"proc-macro2",
"quote",
@@ -2436,7 +2278,7 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
dependencies = [
"dirs 5.0.0",
"dirs 5.0.1",
]
[[package]]
@@ -2547,15 +2389,6 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "terminfo"
version = "0.8.0"
@@ -2764,34 +2597,24 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.23"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.15",
]
[[package]]
name = "tracing-core"
version = "0.1.30"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
dependencies = [
"once_cell",
]
[[package]]
name = "twox-hash"
version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [
"cfg-if",
"static_assertions",
]
[[package]]
name = "typed-arena"
version = "2.0.2"
@@ -2948,9 +2771,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.3.1"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb"
checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2"
[[package]]
name = "version_check"
@@ -2958,12 +2781,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "volatile"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8e76fae08f03f96e166d2dfda232190638c10e0383841252416f9cfe2ae60e6"
[[package]]
name = "wait-timeout"
version = "0.2.0"
@@ -2997,9 +2814,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
checksum = "5b6cb788c4e39112fbe1822277ef6fb3c55cd86b95cb3d3c4c1c9597e4ac74b4"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@@ -3007,24 +2824,24 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
checksum = "35e522ed4105a9d626d885b35d62501b30d9666283a5c8be12c14a8bdafe7822"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.15",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.34"
version = "0.4.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
checksum = "083abe15c5d88556b77bdf7aef403625be9e327ad37c62c4e4129af740168163"
dependencies = [
"cfg-if",
"js-sys",
@@ -3034,9 +2851,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
checksum = "358a79a0cb89d21db8120cbfb91392335913e4890665b1a7981d9e956903b434"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -3044,28 +2861,28 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.15",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
checksum = "a901d592cafaa4d711bc324edfaff879ac700b19c3dfd60058d2b445be2691eb"
[[package]]
name = "wasm-bindgen-test"
version = "0.3.34"
version = "0.3.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b"
checksum = "b27e15b4a3030b9944370ba1d8cec6f21f66a1ad4fd14725c5685600460713ec"
dependencies = [
"console_error_panic_hook",
"js-sys",
@@ -3077,9 +2894,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.34"
version = "0.3.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9"
checksum = "1dbaa9b9a574eac00c4f3a9c4941ac051f07632ecd0484a8588abd95af6b99d2"
dependencies = [
"proc-macro2",
"quote",
@@ -3087,9 +2904,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.61"
version = "0.3.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
checksum = "16b5f940c7edfdc6d12126d98c9ef4d1b3d470011c47c76a6581df47ad9ba721"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -3125,12 +2942,6 @@ dependencies = [
"once_cell",
]
[[package]]
name = "widestring"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983"
[[package]]
name = "wild"
version = "2.1.0"
@@ -3329,9 +3140,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winnow"
version = "0.4.1"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
dependencies = [
"memchr",
]

View File

@@ -30,11 +30,11 @@ 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/RustPython/Parser.git", rev = "e820928f11a2453314ad4d5ce23f066d1d3faf73" }
rustpython-format = { git = "https://github.com/RustPython/Parser.git", rev = "e820928f11a2453314ad4d5ce23f066d1d3faf73" }
rustpython-literal = { git = "https://github.com/RustPython/Parser.git", rev = "e820928f11a2453314ad4d5ce23f066d1d3faf73" }
rustpython-parser = { git = "https://github.com/RustPython/Parser.git", rev = "e820928f11a2453314ad4d5ce23f066d1d3faf73", 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
View File

@@ -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

View File

@@ -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)

View File

@@ -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>=0.15.2,<0.16"]
build-backend = "maturin"
[tool.maturin]

View File

@@ -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" }
@@ -81,6 +81,5 @@ colored = { workspace = true, features = ["no-color"] }
[features]
default = []
schemars = ["dep:schemars"]
logical_lines = []
jupyter_notebook = []
ecosystem_ci = []

View File

@@ -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:

View 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()

View 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)

View File

@@ -0,0 +1,13 @@
import os
async def foo():
os.popen()
async def foo():
os.spawnl()
async def foo():
os.fspath("foo")

View File

@@ -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"

View File

@@ -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
],
)

View 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")

View File

@@ -0,0 +1,6 @@
from typing import List
def main() -> None:
a_list: List[str] = []
a_list.append("hello")

View 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}

View File

@@ -0,0 +1,6 @@
import typing
def main() -> None:
a_list: typing.List[str] = []
a_list.append("hello")

View File

@@ -0,0 +1,6 @@
import typing as t
def main() -> None:
a_list: t.List[str] = []
a_list.append("hello")

View File

@@ -0,0 +1,7 @@
def main() -> None:
a_list: list[str] = []
a_list.append("hello")
def hello(y: dict[str, int]) -> None:
del y

View 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

View File

@@ -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

View File

@@ -0,0 +1,3 @@
def main() -> str:
a_str = "hello"
return a_str

View File

@@ -0,0 +1,10 @@
from typing import NamedTuple
class Stuff(NamedTuple):
x: int
def main() -> None:
a_list = Stuff(5)
print(a_list)

View File

@@ -0,0 +1,6 @@
from __future__ import annotations
def main() -> None:
a_list: list[str] = []
a_list.append("hello")

View File

@@ -0,0 +1,8 @@
import typing
IRRELEVANT = typing.TypeVar
def main() -> None:
List: list[str] = []
List.append("hello")

View 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

View 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

View File

@@ -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

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1,6 @@
# T005 - accepted
# TODO(evanrittenhouse): this has text, while the errors do not
# T005 - errors
# TODO(evanrittenhouse):
# TODO(evanrittenhouse)
# FIXME

View 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

View 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

View File

@@ -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()

View File

@@ -76,3 +76,11 @@ if x == 4:
a[b1, :] == a[b1, ...]
b = a[:, b1]
#:
#: E201:1:6
spam[ ~ham]
#: Okay
x = [ #
'some value',
]

View File

@@ -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
#:

View File

@@ -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')],
}

View File

@@ -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

View File

@@ -56,3 +56,7 @@ if True:
def f():
print((yield))
x = (yield)
#: Okay
if (a and
b):
pass

View File

@@ -0,0 +1,4 @@
#!/usr/bin/python
#
#!
#:

View File

@@ -0,0 +1,24 @@
###
# Errors.
###
class A:
...
class B(A, A):
...
###
# Non-errors.
###
class C:
...
class D(C):
...
class E(A, C):
...

View File

@@ -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)))

View File

@@ -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:
...

View File

@@ -25,3 +25,4 @@ bytes()
bytes(b"foo")
bytes(b"""
foo""")
f"{str()}"

View File

@@ -43,3 +43,6 @@ second = first + [
[] + foo + [ # This will be preserved, but doesn't prevent the fix
]
# Uses the non-preferred quote style, which should be retained.
f"{[*a(), 'b']}"

View File

@@ -1,4 +1,5 @@
bla = b"bla"
d = {"a": b"bla", "b": b"bla", "c": b"bla"}
def foo(one_arg):
@@ -7,6 +8,8 @@ def foo(one_arg):
f"{str(bla)}, {repr(bla)}, {ascii(bla)}" # RUF010
f"{str(d['a'])}, {repr(d['b'])}, {ascii(d['c'])}" # RUF010
f"{foo(bla)}" # OK
f"{str(bla, 'ascii')}, {str(bla, encoding='cp1255')}" # OK

View File

@@ -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

View File

@@ -1,3 +1,8 @@
"""
Violation:
Checks for `raise` statements within `try` blocks.
"""
class MyException(Exception):
pass

View File

@@ -0,0 +1,111 @@
"""
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 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

View File

@@ -5,7 +5,7 @@ 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;
@@ -27,22 +27,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 +51,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 +71,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 +85,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)
@@ -268,10 +268,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('.');

File diff suppressed because it is too large Load Diff

View File

@@ -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;
@@ -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"),
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -7,8 +7,8 @@ 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;
@@ -59,6 +59,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 {
@@ -179,5 +188,14 @@ pub(crate) fn check_tokens(
diagnostics.extend(flake8_pyi::rules::type_comment_in_stub(tokens));
}
// TD001, TD002, TD003, TD004, TD005, TD006, TD007
if enforce_todos {
diagnostics.extend(
flake8_todos::rules::todos(tokens, settings)
.into_iter()
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
);
}
diagnostics
}

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}

View File

@@ -14,7 +14,7 @@ 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"),
}
}

View File

@@ -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! {
@@ -102,7 +103,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(),
));
}
}
@@ -219,11 +223,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 +276,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 +287,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 +297,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 = \

View File

@@ -3,8 +3,8 @@
use std::iter::FusedIterator;
use ruff_text_size::{TextRange, TextSize};
use rustpython_parser::ast::{self, Constant, ExprKind, Stmt, StmtKind, Suite};
use ruff_text_size::TextSize;
use rustpython_parser::ast::{self, Constant, Expr, Ranged, Stmt, Suite};
use rustpython_parser::lexer::LexResult;
use rustpython_parser::Tok;
@@ -13,24 +13,19 @@ use ruff_python_ast::source_code::Locator;
use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor};
/// Extract doc lines (standalone comments) from a token sequence.
pub(crate) fn doc_lines_from_tokens<'a>(
lxr: &'a [LexResult],
locator: &'a Locator<'a>,
) -> DocLines<'a> {
DocLines::new(lxr, locator)
pub(crate) fn doc_lines_from_tokens(lxr: &[LexResult]) -> DocLines {
DocLines::new(lxr)
}
pub(crate) struct DocLines<'a> {
inner: std::iter::Flatten<core::slice::Iter<'a, LexResult>>,
locator: &'a Locator<'a>,
prev: TextSize,
}
impl<'a> DocLines<'a> {
fn new(lxr: &'a [LexResult], locator: &'a Locator) -> Self {
fn new(lxr: &'a [LexResult]) -> Self {
Self {
inner: lxr.iter().flatten(),
locator,
prev: TextSize::default(),
}
}
@@ -46,15 +41,11 @@ impl Iterator for DocLines<'_> {
match tok {
Tok::Comment(..) => {
if at_start_of_line
|| self
.locator
.contains_line_break(TextRange::new(self.prev, range.start()))
{
if at_start_of_line {
break Some(range.start());
}
}
Tok::Newline => {
Tok::Newline | Tok::NonLogicalNewline => {
at_start_of_line = true;
}
Tok::Indent | Tok::Dedent => {
@@ -79,11 +70,11 @@ struct StringLinesVisitor<'a> {
impl StatementVisitor<'_> for StringLinesVisitor<'_> {
fn visit_stmt(&mut self, stmt: &Stmt) {
if let StmtKind::Expr(ast::StmtExpr { value }) = &stmt.node {
if let ExprKind::Constant(ast::ExprConstant {
if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt {
if let Expr::Constant(ast::ExprConstant {
value: Constant::Str(..),
..
}) = &value.node
}) = value.as_ref()
{
for line in UniversalNewlineIterator::with_offset(
self.locator.slice(value.range()),

View File

@@ -1,6 +1,6 @@
//! Extract docstrings from an AST.
use rustpython_parser::ast::{self, Constant, Expr, ExprKind, Stmt, StmtKind};
use rustpython_parser::ast::{self, Constant, Expr, Stmt};
use ruff_python_semantic::definition::{Definition, DefinitionId, Definitions, Member, MemberKind};
@@ -8,13 +8,13 @@ use ruff_python_semantic::definition::{Definition, DefinitionId, Definitions, Me
pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
let stmt = suite.first()?;
// Require the docstring to be a standalone expression.
let StmtKind::Expr(ast::StmtExpr { value }) = &stmt.node else {
let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else {
return None;
};
// Only match strings.
if !matches!(
&value.node,
ExprKind::Constant(ast::ExprConstant {
value.as_ref(),
Expr::Constant(ast::ExprConstant {
value: Constant::Str(_),
..
})
@@ -29,10 +29,9 @@ pub(crate) fn extract_docstring<'a>(definition: &'a Definition<'a>) -> Option<&'
match definition {
Definition::Module(module) => docstring_from(module.python_ast),
Definition::Member(member) => {
if let StmtKind::ClassDef(ast::StmtClassDef { body, .. })
| StmtKind::FunctionDef(ast::StmtFunctionDef { body, .. })
| StmtKind::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. }) =
&member.stmt.node
if let Stmt::ClassDef(ast::StmtClassDef { body, .. })
| Stmt::FunctionDef(ast::StmtFunctionDef { body, .. })
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. }) = &member.stmt
{
docstring_from(body)
} else {

View File

@@ -2,7 +2,7 @@ use std::fmt::{Debug, Formatter};
use std::ops::Deref;
use ruff_text_size::{TextRange, TextSize};
use rustpython_parser::ast::Expr;
use rustpython_parser::ast::{Expr, Ranged};
use ruff_python_semantic::definition::Definition;
@@ -29,15 +29,15 @@ impl<'a> Docstring<'a> {
DocstringBody { docstring: self }
}
pub(crate) const fn start(&self) -> TextSize {
pub(crate) fn start(&self) -> TextSize {
self.expr.start()
}
pub(crate) const fn end(&self) -> TextSize {
pub(crate) fn end(&self) -> TextSize {
self.expr.end()
}
pub(crate) const fn range(&self) -> TextRange {
pub(crate) fn range(&self) -> TextRange {
self.expr.range()
}

View File

@@ -1,365 +0,0 @@
//! Add and modify import statements to make module members available during fix execution.
use anyhow::Result;
use libcst_native::{Codegen, CodegenState, ImportAlias, Name, NameOrAttribute};
use ruff_text_size::TextSize;
use rustpython_parser::ast::{self, Stmt, StmtKind, Suite};
use rustpython_parser::{lexer, Mode, Tok};
use ruff_diagnostics::Edit;
use ruff_python_ast::helpers::is_docstring_stmt;
use ruff_python_ast::imports::AnyImport;
use ruff_python_ast::source_code::{Locator, Stylist};
use crate::cst::matchers::{match_aliases, match_import_from, match_module};
pub struct Importer<'a> {
python_ast: &'a Suite,
locator: &'a Locator<'a>,
stylist: &'a Stylist<'a>,
ordered_imports: Vec<&'a Stmt>,
}
impl<'a> Importer<'a> {
pub fn new(python_ast: &'a Suite, locator: &'a Locator<'a>, stylist: &'a Stylist<'a>) -> Self {
Self {
python_ast,
locator,
stylist,
ordered_imports: Vec::default(),
}
}
/// Visit a top-level import statement.
pub fn visit_import(&mut self, import: &'a Stmt) {
self.ordered_imports.push(import);
}
/// Return the import statement that precedes the given position, if any.
fn preceding_import(&self, at: TextSize) -> Option<&Stmt> {
self.ordered_imports
.partition_point(|stmt| stmt.start() < at)
.checked_sub(1)
.map(|idx| self.ordered_imports[idx])
}
/// Add an import statement to import the given module.
///
/// If there are no existing imports, the new import will be added at the top
/// of the file. Otherwise, it will be added after the most recent top-level
/// import statement.
pub fn add_import(&self, import: &AnyImport, at: TextSize) -> Edit {
let required_import = import.to_string();
if let Some(stmt) = self.preceding_import(at) {
// Insert after the last top-level import.
let Insertion {
prefix,
location,
suffix,
} = end_of_statement_insertion(stmt, self.locator, self.stylist);
let content = format!("{prefix}{required_import}{suffix}");
Edit::insertion(content, location)
} else {
// Insert at the top of the file.
let Insertion {
prefix,
location,
suffix,
} = top_of_file_insertion(self.python_ast, self.locator, self.stylist);
let content = format!("{prefix}{required_import}{suffix}");
Edit::insertion(content, location)
}
}
/// Return the top-level [`Stmt`] that imports the given module using `StmtKind::ImportFrom`
/// preceding the given position, if any.
pub fn find_import_from(&self, module: &str, at: TextSize) -> Option<&Stmt> {
let mut import_from = None;
for stmt in &self.ordered_imports {
if stmt.start() >= at {
break;
}
if let StmtKind::ImportFrom(ast::StmtImportFrom {
module: name,
level,
..
}) = &stmt.node
{
if level.map_or(true, |level| level.to_u32() == 0)
&& name.as_ref().map_or(false, |name| name == module)
{
import_from = Some(*stmt);
}
}
}
import_from
}
/// Add the given member to an existing `StmtKind::ImportFrom` statement.
pub fn add_member(&self, stmt: &Stmt, member: &str) -> Result<Edit> {
let mut tree = match_module(self.locator.slice(stmt.range()))?;
let import_from = match_import_from(&mut tree)?;
let aliases = match_aliases(import_from)?;
aliases.push(ImportAlias {
name: NameOrAttribute::N(Box::new(Name {
value: member,
lpar: vec![],
rpar: vec![],
})),
asname: None,
comma: aliases.last().and_then(|alias| alias.comma.clone()),
});
let mut state = CodegenState {
default_newline: &self.stylist.line_ending(),
default_indent: self.stylist.indentation(),
..CodegenState::default()
};
tree.codegen(&mut state);
Ok(Edit::range_replacement(state.to_string(), stmt.range()))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Insertion {
/// The content to add before the insertion.
prefix: &'static str,
/// The location at which to insert.
location: TextSize,
/// The content to add after the insertion.
suffix: &'static str,
}
impl Insertion {
fn new(prefix: &'static str, location: TextSize, suffix: &'static str) -> Self {
Self {
prefix,
location,
suffix,
}
}
}
/// Find the end of the last docstring.
fn match_docstring_end(body: &[Stmt]) -> Option<TextSize> {
let mut iter = body.iter();
let Some(mut stmt) = iter.next() else {
return None;
};
if !is_docstring_stmt(stmt) {
return None;
}
for next in iter {
if !is_docstring_stmt(next) {
break;
}
stmt = next;
}
Some(stmt.end())
}
/// Find the location at which an "end-of-statement" import should be inserted,
/// along with a prefix and suffix to use for the insertion.
///
/// For example, given the following code:
///
/// ```python
/// """Hello, world!"""
///
/// import os
/// import math
///
///
/// def foo():
/// pass
/// ```
///
/// The location returned will be the start of new line after the last
/// import statement, which in this case is the line after `import math`,
/// along with a trailing newline suffix.
fn end_of_statement_insertion(stmt: &Stmt, locator: &Locator, stylist: &Stylist) -> Insertion {
let location = stmt.end();
let mut tokens =
lexer::lex_starts_at(locator.after(location), Mode::Module, location).flatten();
if let Some((Tok::Semi, range)) = tokens.next() {
// If the first token after the docstring is a semicolon, insert after the semicolon as an
// inline statement;
Insertion::new(" ", range.end(), ";")
} else {
// Otherwise, insert on the next line.
Insertion::new(
"",
locator.full_line_end(location),
stylist.line_ending().as_str(),
)
}
}
/// Find the location at which a "top-of-file" import should be inserted,
/// along with a prefix and suffix to use for the insertion.
///
/// For example, given the following code:
///
/// ```python
/// """Hello, world!"""
///
/// import os
/// ```
///
/// The location returned will be the start of the `import os` statement,
/// along with a trailing newline suffix.
fn top_of_file_insertion(body: &[Stmt], locator: &Locator, stylist: &Stylist) -> Insertion {
// Skip over any docstrings.
let mut location = if let Some(location) = match_docstring_end(body) {
// If the first token after the docstring is a semicolon, insert after the semicolon as an
// inline statement;
let first_token = lexer::lex_starts_at(locator.after(location), Mode::Module, location)
.flatten()
.next();
if let Some((Tok::Semi, range)) = first_token {
return Insertion::new(" ", range.end(), ";");
}
// Otherwise, advance to the next row.
locator.full_line_end(location)
} else {
TextSize::default()
};
// Skip over any comments and empty lines.
for (tok, range) in
lexer::lex_starts_at(locator.after(location), Mode::Module, location).flatten()
{
if matches!(tok, Tok::Comment(..) | Tok::Newline) {
location = locator.full_line_end(range.end());
} else {
break;
}
}
return Insertion::new("", location, stylist.line_ending().as_str());
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use ruff_text_size::TextSize;
use rustpython_parser as parser;
use rustpython_parser::lexer::LexResult;
use ruff_python_ast::newlines::LineEnding;
use ruff_python_ast::source_code::{Locator, Stylist};
use crate::importer::{top_of_file_insertion, Insertion};
fn insert(contents: &str) -> Result<Insertion> {
let program = parser::parse_program(contents, "<filename>")?;
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
let locator = Locator::new(contents);
let stylist = Stylist::from_tokens(&tokens, &locator);
Ok(top_of_file_insertion(&program, &locator, &stylist))
}
#[test]
fn top_of_file_insertions() -> Result<()> {
let contents = "";
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(0), LineEnding::default().as_str())
);
let contents = r#"
"""Hello, world!""""#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(19), LineEnding::default().as_str())
);
let contents = r#"
"""Hello, world!"""
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(20), "\n")
);
let contents = r#"
"""Hello, world!"""
"""Hello, world!"""
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(40), "\n")
);
let contents = r#"
x = 1
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(0), "\n")
);
let contents = r#"
#!/usr/bin/env python3
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(23), "\n")
);
let contents = r#"
#!/usr/bin/env python3
"""Hello, world!"""
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(43), "\n")
);
let contents = r#"
"""Hello, world!"""
#!/usr/bin/env python3
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(43), "\n")
);
let contents = r#"
"""%s""" % "Hello, world!"
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(0), "\n")
);
let contents = r#"
"""Hello, world!"""; x = 1
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new(" ", TextSize::from(20), ";")
);
let contents = r#"
"""Hello, world!"""; x = 1; y = \
2
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new(" ", TextSize::from(20), ";")
);
Ok(())
}
}

View File

@@ -0,0 +1,261 @@
use ruff_diagnostics::Edit;
use ruff_text_size::TextSize;
use rustpython_parser::ast::{Ranged, Stmt};
use rustpython_parser::{lexer, Mode, Tok};
use ruff_python_ast::helpers::is_docstring_stmt;
use ruff_python_ast::source_code::{Locator, Stylist};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct Insertion {
/// The content to add before the insertion.
prefix: &'static str,
/// The location at which to insert.
location: TextSize,
/// The content to add after the insertion.
suffix: &'static str,
}
impl Insertion {
/// Create an [`Insertion`] to insert (e.g.) an import after the end of the given [`Stmt`],
/// along with a prefix and suffix to use for the insertion.
///
/// For example, given the following code:
///
/// ```python
/// """Hello, world!"""
///
/// import os
/// import math
///
///
/// def foo():
/// pass
/// ```
///
/// The insertion returned will begin after the newline after the last import statement, which
/// in this case is the line after `import math`, and will include a trailing newline suffix.
pub(super) fn end_of_statement(stmt: &Stmt, locator: &Locator, stylist: &Stylist) -> Insertion {
let location = stmt.end();
let mut tokens =
lexer::lex_starts_at(locator.after(location), Mode::Module, location).flatten();
if let Some((Tok::Semi, range)) = tokens.next() {
// If the first token after the docstring is a semicolon, insert after the semicolon as an
// inline statement;
Insertion::new(" ", range.end(), ";")
} else {
// Otherwise, insert on the next line.
Insertion::new(
"",
locator.full_line_end(location),
stylist.line_ending().as_str(),
)
}
}
/// Create an [`Insertion`] to insert (e.g.) an import statement at the "top" of a given file,
/// along with a prefix and suffix to use for the insertion.
///
/// For example, given the following code:
///
/// ```python
/// """Hello, world!"""
///
/// import os
/// ```
///
/// The insertion returned will begin at the start of the `import os` statement, and will
/// include a trailing newline suffix.
pub(super) fn top_of_file(body: &[Stmt], locator: &Locator, stylist: &Stylist) -> Insertion {
// Skip over any docstrings.
let mut location = if let Some(location) = match_docstring_end(body) {
// If the first token after the docstring is a semicolon, insert after the semicolon as an
// inline statement;
let first_token = lexer::lex_starts_at(locator.after(location), Mode::Module, location)
.flatten()
.next();
if let Some((Tok::Semi, range)) = first_token {
return Insertion::new(" ", range.end(), ";");
}
// Otherwise, advance to the next row.
locator.full_line_end(location)
} else {
TextSize::default()
};
// Skip over any comments and empty lines.
for (tok, range) in
lexer::lex_starts_at(locator.after(location), Mode::Module, location).flatten()
{
if matches!(tok, Tok::Comment(..) | Tok::Newline) {
location = locator.full_line_end(range.end());
} else {
break;
}
}
Insertion::new("", location, stylist.line_ending().as_str())
}
fn new(prefix: &'static str, location: TextSize, suffix: &'static str) -> Self {
Self {
prefix,
location,
suffix,
}
}
/// Convert this [`Insertion`] into an [`Edit`] that inserts the given content.
pub(super) fn into_edit(self, content: &str) -> Edit {
let Insertion {
prefix,
location,
suffix,
} = self;
Edit::insertion(format!("{prefix}{content}{suffix}"), location)
}
}
/// Find the end of the last docstring.
fn match_docstring_end(body: &[Stmt]) -> Option<TextSize> {
let mut iter = body.iter();
let Some(mut stmt) = iter.next() else {
return None;
};
if !is_docstring_stmt(stmt) {
return None;
}
for next in iter {
if !is_docstring_stmt(next) {
break;
}
stmt = next;
}
Some(stmt.end())
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use ruff_text_size::TextSize;
use rustpython_parser as parser;
use rustpython_parser::lexer::LexResult;
use ruff_python_ast::newlines::LineEnding;
use ruff_python_ast::source_code::{Locator, Stylist};
use super::Insertion;
fn insert(contents: &str) -> Result<Insertion> {
let program = parser::parse_program(contents, "<filename>")?;
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
let locator = Locator::new(contents);
let stylist = Stylist::from_tokens(&tokens, &locator);
Ok(Insertion::top_of_file(&program, &locator, &stylist))
}
#[test]
fn top_of_file() -> Result<()> {
let contents = "";
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(0), LineEnding::default().as_str())
);
let contents = r#"
"""Hello, world!""""#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(19), LineEnding::default().as_str())
);
let contents = r#"
"""Hello, world!"""
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(20), "\n")
);
let contents = r#"
"""Hello, world!"""
"""Hello, world!"""
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(40), "\n")
);
let contents = r#"
x = 1
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(0), "\n")
);
let contents = r#"
#!/usr/bin/env python3
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(23), "\n")
);
let contents = r#"
#!/usr/bin/env python3
"""Hello, world!"""
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(43), "\n")
);
let contents = r#"
"""Hello, world!"""
#!/usr/bin/env python3
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(43), "\n")
);
let contents = r#"
"""%s""" % "Hello, world!"
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new("", TextSize::from(0), "\n")
);
let contents = r#"
"""Hello, world!"""; x = 1
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new(" ", TextSize::from(20), ";")
);
let contents = r#"
"""Hello, world!"""; x = 1; y = \
2
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::new(" ", TextSize::from(20), ";")
);
Ok(())
}
}

View File

@@ -0,0 +1,115 @@
//! Add and modify import statements to make module members available during fix execution.
use anyhow::Result;
use libcst_native::{Codegen, CodegenState, ImportAlias, Name, NameOrAttribute};
use ruff_text_size::TextSize;
use rustpython_parser::ast::{self, Ranged, Stmt, Suite};
use ruff_diagnostics::Edit;
use ruff_python_ast::imports::AnyImport;
use ruff_python_ast::source_code::{Locator, Stylist};
use crate::cst::matchers::{match_aliases, match_import_from, match_module};
use crate::importer::insertion::Insertion;
mod insertion;
pub(crate) struct Importer<'a> {
python_ast: &'a Suite,
locator: &'a Locator<'a>,
stylist: &'a Stylist<'a>,
ordered_imports: Vec<&'a Stmt>,
}
impl<'a> Importer<'a> {
pub(crate) fn new(
python_ast: &'a Suite,
locator: &'a Locator<'a>,
stylist: &'a Stylist<'a>,
) -> Self {
Self {
python_ast,
locator,
stylist,
ordered_imports: Vec::default(),
}
}
/// Visit a top-level import statement.
pub(crate) fn visit_import(&mut self, import: &'a Stmt) {
self.ordered_imports.push(import);
}
/// Return the import statement that precedes the given position, if any.
fn preceding_import(&self, at: TextSize) -> Option<&Stmt> {
self.ordered_imports
.partition_point(|stmt| stmt.start() < at)
.checked_sub(1)
.map(|idx| self.ordered_imports[idx])
}
/// Add an import statement to import the given module.
///
/// If there are no existing imports, the new import will be added at the top
/// of the file. Otherwise, it will be added after the most recent top-level
/// import statement.
pub(crate) fn add_import(&self, import: &AnyImport, at: TextSize) -> Edit {
let required_import = import.to_string();
if let Some(stmt) = self.preceding_import(at) {
// Insert after the last top-level import.
Insertion::end_of_statement(stmt, self.locator, self.stylist)
.into_edit(&required_import)
} else {
// Insert at the top of the file.
Insertion::top_of_file(self.python_ast, self.locator, self.stylist)
.into_edit(&required_import)
}
}
/// Return the top-level [`Stmt`] that imports the given module using `Stmt::ImportFrom`
/// preceding the given position, if any.
pub(crate) fn find_import_from(&self, module: &str, at: TextSize) -> Option<&Stmt> {
let mut import_from = None;
for stmt in &self.ordered_imports {
if stmt.start() >= at {
break;
}
if let Stmt::ImportFrom(ast::StmtImportFrom {
module: name,
level,
..
}) = stmt
{
if level.map_or(true, |level| level.to_u32() == 0)
&& name.as_ref().map_or(false, |name| name == module)
{
import_from = Some(*stmt);
}
}
}
import_from
}
/// Add the given member to an existing `Stmt::ImportFrom` statement.
pub(crate) fn add_member(&self, stmt: &Stmt, member: &str) -> Result<Edit> {
let mut tree = match_module(self.locator.slice(stmt.range()))?;
let import_from = match_import_from(&mut tree)?;
let aliases = match_aliases(import_from)?;
aliases.push(ImportAlias {
name: NameOrAttribute::N(Box::new(Name {
value: member,
lpar: vec![],
rpar: vec![],
})),
asname: None,
comma: aliases.last().and_then(|alias| alias.comma.clone()),
});
let mut state = CodegenState {
default_newline: &self.stylist.line_ending(),
default_indent: self.stylist.indentation(),
..CodegenState::default()
};
tree.codegen(&mut state);
Ok(Edit::range_replacement(state.to_string(), stmt.range()))
}
}

View File

@@ -88,7 +88,7 @@ pub fn check_path(
let use_doc_lines = settings.rules.enabled(Rule::DocLineTooLong);
let mut doc_lines = vec![];
if use_doc_lines {
doc_lines.extend(doc_lines_from_tokens(&tokens, locator));
doc_lines.extend(doc_lines_from_tokens(&tokens));
}
// Run the token-based rules.
@@ -116,7 +116,6 @@ pub fn check_path(
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_logical_lines())
{
#[cfg(feature = "logical_lines")]
diagnostics.extend(crate::checkers::logical_lines::check_logical_lines(
&tokens, locator, stylist, settings,
));

View File

@@ -200,11 +200,11 @@ struct TruncateAtNewline<'a>(&'a dyn Display);
impl Display for TruncateAtNewline<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
struct TruncateAdapter<'a> {
inner: &'a mut dyn std::fmt::Write,
inner: &'a mut dyn Write,
after_new_line: bool,
}
impl std::fmt::Write for TruncateAdapter<'_> {
impl Write for TruncateAdapter<'_> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
if self.after_new_line {
Ok(())

View File

@@ -1,6 +1,6 @@
use crate::message::{Emitter, EmitterContext, Message};
use crate::registry::AsRule;
use ruff_python_ast::source_code::{OneIndexed, SourceLocation};
use ruff_python_ast::source_code::SourceLocation;
use std::io::Write;
/// Generate error logging commands for Azure Pipelines format.
@@ -19,10 +19,7 @@ impl Emitter for AzureEmitter {
let location = if context.is_jupyter_notebook(message.filename()) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
SourceLocation {
row: OneIndexed::from_zero_indexed(0),
column: OneIndexed::from_zero_indexed(0),
}
SourceLocation::default()
} else {
message.compute_start_location()
};

View File

@@ -1,7 +1,7 @@
use crate::fs::relativize_path;
use crate::message::{Emitter, EmitterContext, Message};
use crate::registry::AsRule;
use ruff_python_ast::source_code::{OneIndexed, SourceLocation};
use ruff_python_ast::source_code::SourceLocation;
use std::io::Write;
/// Generate error workflow command in GitHub Actions format.
@@ -21,10 +21,7 @@ impl Emitter for GithubEmitter {
let location = if context.is_jupyter_notebook(message.filename()) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
SourceLocation {
row: OneIndexed::from_zero_indexed(0),
column: OneIndexed::from_zero_indexed(0),
}
SourceLocation::default()
} else {
source_location.clone()
};

View File

@@ -3,7 +3,7 @@ use crate::message::{
};
use crate::registry::AsRule;
use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite};
use ruff_python_ast::source_code::{OneIndexed, SourceLocation};
use ruff_python_ast::source_code::SourceLocation;
use std::io::Write;
use std::path::Path;
@@ -35,10 +35,7 @@ impl Emitter for JunitEmitter {
let location = if context.is_jupyter_notebook(message.filename()) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
SourceLocation {
row: OneIndexed::from_zero_indexed(0),
column: OneIndexed::from_zero_indexed(0),
}
SourceLocation::default()
} else {
start_location
};

View File

@@ -118,7 +118,7 @@ pub(super) struct RuleCodeAndBody<'a> {
}
impl Display for RuleCodeAndBody<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let kind = &self.message.kind;
if self.show_fix_status && self.message.fix.is_some() {

View File

@@ -14,67 +14,36 @@ pub use rule_set::{RuleSet, RuleSetIterator};
ruff_macros::register_rules!(
// pycodestyle errors
rules::pycodestyle::rules::MixedSpacesAndTabs,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultiple,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::NoIndentedBlock,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::UnexpectedIndentation,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultipleComment,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::NoIndentedBlockComment,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::UnexpectedIndentationComment,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::OverIndented,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::WhitespaceAfterOpenBracket,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::WhitespaceBeforeCloseBracket,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::WhitespaceBeforePunctuation,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::TabBeforeOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::TabAfterOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::TooFewSpacesBeforeInlineComment,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::NoSpaceAfterInlineComment,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::NoSpaceAfterBlockComment,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::MultipleLeadingHashesForBlockComment,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterKeyword,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::MissingWhitespace,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::MissingWhitespaceAfterKeyword,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeKeyword,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundArithmeticOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundBitwiseOrShiftOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundModuloOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::TabAfterKeyword,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::UnexpectedSpacesAroundKeywordParameterEquals,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundParameterEquals,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::WhitespaceBeforeParameters,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::logical_lines::TabBeforeKeyword,
rules::pycodestyle::rules::MultipleImportsOnOneLine,
rules::pycodestyle::rules::ModuleImportNotAtTopOfFile,
@@ -190,6 +159,11 @@ ruff_macros::register_rules!(
rules::pylint::rules::LoggingTooManyArgs,
rules::pylint::rules::UnexpectedSpecialMethodSignature,
rules::pylint::rules::NestedMinMax,
rules::pylint::rules::DuplicateBases,
// flake8-async
rules::flake8_async::rules::BlockingHttpCallInAsyncFunction,
rules::flake8_async::rules::OpenSleepOrSubprocessInAsyncFunction,
rules::flake8_async::rules::BlockingOsCallInAsyncFunction,
// flake8-builtins
rules::flake8_builtins::rules::BuiltinVariableShadowing,
rules::flake8_builtins::rules::BuiltinArgumentShadowing,
@@ -289,6 +263,8 @@ ruff_macros::register_rules!(
rules::flake8_annotations::rules::MissingReturnTypeStaticMethod,
rules::flake8_annotations::rules::MissingReturnTypeClassMethod,
rules::flake8_annotations::rules::AnyType,
// flake8-future-annotations
rules::flake8_future_annotations::rules::MissingFutureAnnotationsImport,
// flake8-2020
rules::flake8_2020::rules::SysVersionSlice3,
rules::flake8_2020::rules::SysVersion2,
@@ -533,18 +509,19 @@ ruff_macros::register_rules!(
rules::flake8_pyi::rules::AssignmentDefaultInStub,
rules::flake8_pyi::rules::BadVersionInfoComparison,
rules::flake8_pyi::rules::DocstringInStub,
rules::flake8_pyi::rules::NonEmptyStubBody,
rules::flake8_pyi::rules::PassStatementStubBody,
rules::flake8_pyi::rules::TypeCommentInStub,
rules::flake8_pyi::rules::TypedArgumentDefaultInStub,
rules::flake8_pyi::rules::UnprefixedTypeParam,
rules::flake8_pyi::rules::UnrecognizedPlatformCheck,
rules::flake8_pyi::rules::UnrecognizedPlatformName,
rules::flake8_pyi::rules::PassInClassBody,
rules::flake8_pyi::rules::DuplicateUnionMember,
rules::flake8_pyi::rules::NonEmptyStubBody,
rules::flake8_pyi::rules::PassInClassBody,
rules::flake8_pyi::rules::PassStatementStubBody,
rules::flake8_pyi::rules::QuotedAnnotationInStub,
rules::flake8_pyi::rules::SnakeCaseTypeAlias,
rules::flake8_pyi::rules::TSuffixedTypeAlias,
rules::flake8_pyi::rules::TypeCommentInStub,
rules::flake8_pyi::rules::TypedArgumentDefaultInStub,
rules::flake8_pyi::rules::UnannotatedAssignmentInStub,
rules::flake8_pyi::rules::UnprefixedTypeParam,
rules::flake8_pyi::rules::UnrecognizedPlatformCheck,
rules::flake8_pyi::rules::UnrecognizedPlatformName,
// flake8-pytest-style
rules::flake8_pytest_style::rules::PytestFixtureIncorrectParenthesesStyle,
rules::flake8_pytest_style::rules::PytestFixturePositionalArgs,
@@ -604,6 +581,7 @@ ruff_macros::register_rules!(
rules::tryceratops::rules::ReraiseNoCause,
rules::tryceratops::rules::VerboseRaise,
rules::tryceratops::rules::TryConsiderElse,
rules::tryceratops::rules::UselessTryExcept,
rules::tryceratops::rules::RaiseWithinTry,
rules::tryceratops::rules::ErrorInsteadOfException,
rules::tryceratops::rules::VerboseLogMessage,
@@ -674,6 +652,14 @@ ruff_macros::register_rules!(
rules::flake8_django::rules::DjangoNonLeadingReceiverDecorator,
// flynt
rules::flynt::rules::StaticJoinToFString,
// flake8-todo
rules::flake8_todos::rules::InvalidTodoTag,
rules::flake8_todos::rules::MissingTodoAuthor,
rules::flake8_todos::rules::MissingTodoLink,
rules::flake8_todos::rules::MissingTodoColon,
rules::flake8_todos::rules::MissingTodoDescription,
rules::flake8_todos::rules::InvalidTodoCapitalization,
rules::flake8_todos::rules::MissingSpaceAfterTodoColon,
);
pub trait AsRule {
@@ -724,6 +710,9 @@ pub enum Linter {
/// [flake8-annotations](https://pypi.org/project/flake8-annotations/)
#[prefix = "ANN"]
Flake8Annotations,
/// [flake8-async](https://pypi.org/project/flake8-async/)
#[prefix = "ASYNC"]
Flake8Async,
/// [flake8-bandit](https://pypi.org/project/flake8-bandit/)
#[prefix = "S"]
Flake8Bandit,
@@ -760,6 +749,9 @@ pub enum Linter {
/// [flake8-executable](https://pypi.org/project/flake8-executable/)
#[prefix = "EXE"]
Flake8Executable,
/// [flake8-future-annotations](https://pypi.org/project/flake8-future-annotations/)
#[prefix = "FA"]
Flake8FutureAnnotations,
/// [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
#[prefix = "ISC"]
Flake8ImplicitStrConcat,
@@ -814,6 +806,9 @@ pub enum Linter {
/// [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
#[prefix = "PTH"]
Flake8UsePathlib,
/// [flake8-todos](https://github.com/orsinium-labs/flake8-todos/)
#[prefix = "TD"]
Flake8Todo,
/// [eradicate](https://pypi.org/project/eradicate/)
#[prefix = "ERA"]
Eradicate,
@@ -938,11 +933,17 @@ impl Rule {
| Rule::UselessSemicolon
| Rule::MultipleStatementsOnOneLineSemicolon
| Rule::ProhibitedTrailingComma
| Rule::TypeCommentInStub => LintSource::Tokens,
| Rule::TypeCommentInStub
| Rule::InvalidTodoTag
| Rule::MissingTodoAuthor
| Rule::MissingTodoLink
| Rule::MissingTodoColon
| Rule::MissingTodoDescription
| Rule::InvalidTodoCapitalization
| Rule::MissingSpaceAfterTodoColon => LintSource::Tokens,
Rule::IOError => LintSource::Io,
Rule::UnsortedImports | Rule::MissingRequiredImport => LintSource::Imports,
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => LintSource::Filesystem,
#[cfg(feature = "logical_lines")]
Rule::IndentationWithInvalidMultiple
| Rule::IndentationWithInvalidMultipleComment
| Rule::MissingWhitespace

View File

@@ -119,8 +119,7 @@ impl Visitor<'_> for SelectorVisitor {
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str(
"expected a string code identifying a linter or specific rule, or a partial rule code \
or ALL to refer to all rules",
"expected a string code identifying a linter or specific rule, or a partial rule code or ALL to refer to all rules",
)
}
@@ -141,13 +140,22 @@ impl From<RuleCodePrefix> for RuleSelector {
}
}
/// Returns `true` if the given rule should be selected by the `RuleSelector::All` selector.
fn select_all(rule: Rule) -> bool {
// Nursery rules have to be explicitly selected, so we ignore them when looking at
// prefixes.
!rule.is_nursery()
}
impl IntoIterator for &RuleSelector {
type IntoIter = RuleSelectorIter;
type Item = Rule;
type IntoIter = RuleSelectorIter;
fn into_iter(self) -> Self::IntoIter {
match self {
RuleSelector::All => RuleSelectorIter::All(Rule::iter()),
RuleSelector::All => {
RuleSelectorIter::All(Rule::iter().filter(|rule| select_all(*rule)))
}
RuleSelector::C => RuleSelectorIter::Chain(
Linter::Flake8Comprehensions
.into_iter()
@@ -165,7 +173,7 @@ impl IntoIterator for &RuleSelector {
}
pub enum RuleSelectorIter {
All(RuleIter),
All(std::iter::Filter<RuleIter, fn(&Rule) -> bool>),
Chain(std::iter::Chain<std::vec::IntoIter<Rule>, std::vec::IntoIter<Rule>>),
Vec(std::vec::IntoIter<Rule>),
}
@@ -196,15 +204,16 @@ pub(crate) const fn prefix_to_selector(prefix: RuleCodePrefix) -> RuleSelector {
#[cfg(feature = "schemars")]
mod schema {
use crate::registry::RuleNamespace;
use crate::rule_selector::{Linter, Rule, RuleCodePrefix};
use crate::RuleSelector;
use itertools::Itertools;
use schemars::JsonSchema;
use schemars::_serde_json::Value;
use schemars::schema::{InstanceType, Schema, SchemaObject};
use schemars::JsonSchema;
use strum::IntoEnumIterator;
use crate::registry::RuleNamespace;
use crate::rule_selector::{Linter, RuleCodePrefix};
use crate::RuleSelector;
impl JsonSchema for RuleSelector {
fn schema_name() -> String {
"RuleSelector".to_string()
@@ -228,20 +237,6 @@ mod schema {
.into_iter()
.chain(
RuleCodePrefix::iter()
.filter(|p| {
// Once logical lines are active by default, please remove this.
// This is here because generate-all output otherwise depends on
// the feature sets which makes the test running with
// `--all-features` fail
!Rule::from_code(&format!(
"{}{}",
p.linter().common_prefix(),
p.short_code()
))
.unwrap()
.lint_source()
.is_logical_lines()
})
.map(|p| {
let prefix = p.linter().common_prefix();
let code = p.short_code();

View File

@@ -1,5 +1,5 @@
use num_bigint::BigInt;
use rustpython_parser::ast::{self, Attributed, Cmpop, Constant, Expr, ExprKind};
use rustpython_parser::ast::{self, Cmpop, Constant, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -123,16 +123,17 @@ fn is_sys(checker: &Checker, expr: &Expr, target: &str) -> bool {
/// YTT101, YTT102, YTT301, YTT303
pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
if is_sys(checker, value, "version") {
match &slice.node {
ExprKind::Slice(ast::ExprSlice {
match slice {
Expr::Slice(ast::ExprSlice {
lower: None,
upper: Some(upper),
step: None,
range: _,
}) => {
if let ExprKind::Constant(ast::ExprConstant {
if let Expr::Constant(ast::ExprConstant {
value: Constant::Int(i),
..
}) = &upper.node
}) = upper.as_ref()
{
if *i == BigInt::from(1)
&& checker.settings.rules.enabled(Rule::SysVersionSlice1)
@@ -150,7 +151,7 @@ pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
}
}
ExprKind::Constant(ast::ExprConstant {
Expr::Constant(ast::ExprConstant {
value: Constant::Int(i),
..
}) => {
@@ -173,26 +174,22 @@ pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
/// YTT103, YTT201, YTT203, YTT204, YTT302
pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &[Expr]) {
match &left.node {
ExprKind::Subscript(ast::ExprSubscript { value, slice, .. })
match left {
Expr::Subscript(ast::ExprSubscript { value, slice, .. })
if is_sys(checker, value, "version_info") =>
{
if let ExprKind::Constant(ast::ExprConstant {
if let Expr::Constant(ast::ExprConstant {
value: Constant::Int(i),
..
}) = &slice.node
}) = slice.as_ref()
{
if *i == BigInt::from(0) {
if let (
[Cmpop::Eq | Cmpop::NotEq],
[Attributed {
node:
ExprKind::Constant(ast::ExprConstant {
value: Constant::Int(n),
..
}),
[Expr::Constant(ast::ExprConstant {
value: Constant::Int(n),
..
}],
})],
) = (ops, comparators)
{
if *n == BigInt::from(3)
@@ -206,14 +203,10 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], compara
} else if *i == BigInt::from(1) {
if let (
[Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE],
[Attributed {
node:
ExprKind::Constant(ast::ExprConstant {
value: Constant::Int(_),
..
}),
[Expr::Constant(ast::ExprConstant {
value: Constant::Int(_),
..
}],
})],
) = (ops, comparators)
{
if checker.settings.rules.enabled(Rule::SysVersionInfo1CmpInt) {
@@ -226,19 +219,15 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], compara
}
}
ExprKind::Attribute(ast::ExprAttribute { value, attr, .. })
Expr::Attribute(ast::ExprAttribute { value, attr, .. })
if is_sys(checker, value, "version_info") && attr == "minor" =>
{
if let (
[Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE],
[Attributed {
node:
ExprKind::Constant(ast::ExprConstant {
value: Constant::Int(_),
..
}),
[Expr::Constant(ast::ExprConstant {
value: Constant::Int(_),
..
}],
})],
) = (ops, comparators)
{
if checker
@@ -259,14 +248,10 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], compara
if is_sys(checker, left, "version") {
if let (
[Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE],
[Attributed {
node:
ExprKind::Constant(ast::ExprConstant {
value: Constant::Str(s),
..
}),
[Expr::Constant(ast::ExprConstant {
value: Constant::Str(s),
..
}],
})],
) = (ops, comparators)
{
if s.len() == 1 {

View File

@@ -1,5 +1,5 @@
use anyhow::{bail, Result};
use rustpython_parser::ast::Stmt;
use rustpython_parser::ast::{Ranged, Stmt};
use rustpython_parser::{lexer, Mode, Tok};
use ruff_diagnostics::Edit;

View File

@@ -1,4 +1,4 @@
use rustpython_parser::ast::{self, Arguments, Expr, Stmt, StmtKind};
use rustpython_parser::ast::{self, Arguments, Expr, Stmt};
use ruff_python_ast::cast;
use ruff_python_semantic::analyze::visibility;
@@ -6,22 +6,32 @@ use ruff_python_semantic::definition::{Definition, Member, MemberKind};
use crate::checkers::ast::Checker;
pub(super) fn match_function_def(stmt: &Stmt) -> (&str, &Arguments, Option<&Expr>, &Vec<Stmt>) {
match &stmt.node {
StmtKind::FunctionDef(ast::StmtFunctionDef {
pub(super) fn match_function_def(
stmt: &Stmt,
) -> (&str, &Arguments, Option<&Expr>, &[Stmt], &[Expr]) {
match stmt {
Stmt::FunctionDef(ast::StmtFunctionDef {
name,
args,
returns,
body,
decorator_list,
..
})
| StmtKind::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
name,
args,
returns,
body,
decorator_list,
..
}) => (name, args, returns.as_ref().map(|expr| &**expr), body),
}) => (
name,
args,
returns.as_ref().map(|expr| &**expr),
body,
decorator_list,
),
_ => panic!("Found non-FunctionDef in match_name"),
}
}

View File

@@ -1,4 +1,4 @@
use rustpython_parser::ast::{Expr, ExprKind, Stmt};
use rustpython_parser::ast::{Expr, Ranged, Stmt};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -419,8 +419,8 @@ fn is_none_returning(body: &[Stmt]) -> bool {
visitor.visit_body(body);
for expr in visitor.returns.into_iter().flatten() {
if !matches!(
expr.node,
ExprKind::Constant(ref constant) if constant.value.is_none()
expr,
Expr::Constant(ref constant) if constant.value.is_none()
) {
return false;
}
@@ -434,10 +434,11 @@ fn check_dynamically_typed<F>(
annotation: &Expr,
func: F,
diagnostics: &mut Vec<Diagnostic>,
is_overridden: bool,
) where
F: FnOnce() -> String,
{
if checker.ctx.match_typing_expr(annotation, "Any") {
if !is_overridden && checker.ctx.match_typing_expr(annotation, "Any") {
diagnostics.push(Diagnostic::new(
AnyType { name: func() },
annotation.range(),
@@ -468,7 +469,7 @@ pub(crate) fn definition(
_ => return vec![],
};
let (name, args, returns, body) = match_function_def(stmt);
let (name, args, returns, body, decorator_list) = match_function_def(stmt);
// Keep track of whether we've seen any typed arguments or return values.
let mut has_any_typed_arg = false; // Any argument has been typed?
let mut has_typed_return = false; // Return value has been typed?
@@ -478,6 +479,8 @@ pub(crate) fn definition(
// unless configured to suppress ANN* for declarations that are fully untyped.
let mut diagnostics = Vec::new();
let is_overridden = visibility::is_override(&checker.ctx, decorator_list);
// ANN001, ANN401
for arg in args
.posonlyargs
@@ -492,19 +495,20 @@ pub(crate) fn definition(
)
{
// ANN401 for dynamically typed arguments
if let Some(annotation) = &arg.node.annotation {
if let Some(annotation) = &arg.annotation {
has_any_typed_arg = true;
if checker.settings.rules.enabled(Rule::AnyType) {
check_dynamically_typed(
checker,
annotation,
|| arg.node.arg.to_string(),
|| arg.arg.to_string(),
&mut diagnostics,
is_overridden,
);
}
} else {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
&& checker.settings.dummy_variable_rgx.is_match(&arg.arg))
{
if checker
.settings
@@ -513,7 +517,7 @@ pub(crate) fn definition(
{
diagnostics.push(Diagnostic::new(
MissingTypeFunctionArgument {
name: arg.node.arg.to_string(),
name: arg.arg.to_string(),
},
arg.range(),
));
@@ -524,22 +528,28 @@ pub(crate) fn definition(
// ANN002, ANN401
if let Some(arg) = &args.vararg {
if let Some(expr) = &arg.node.annotation {
if let Some(expr) = &arg.annotation {
has_any_typed_arg = true;
if !checker.settings.flake8_annotations.allow_star_arg_any {
if checker.settings.rules.enabled(Rule::AnyType) {
let name = &arg.node.arg;
check_dynamically_typed(checker, expr, || format!("*{name}"), &mut diagnostics);
let name = &arg.arg;
check_dynamically_typed(
checker,
expr,
|| format!("*{name}"),
&mut diagnostics,
is_overridden,
);
}
}
} else {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
&& checker.settings.dummy_variable_rgx.is_match(&arg.arg))
{
if checker.settings.rules.enabled(Rule::MissingTypeArgs) {
diagnostics.push(Diagnostic::new(
MissingTypeArgs {
name: arg.node.arg.to_string(),
name: arg.arg.to_string(),
},
arg.range(),
));
@@ -550,27 +560,28 @@ pub(crate) fn definition(
// ANN003, ANN401
if let Some(arg) = &args.kwarg {
if let Some(expr) = &arg.node.annotation {
if let Some(expr) = &arg.annotation {
has_any_typed_arg = true;
if !checker.settings.flake8_annotations.allow_star_arg_any {
if checker.settings.rules.enabled(Rule::AnyType) {
let name = &arg.node.arg;
let name = &arg.arg;
check_dynamically_typed(
checker,
expr,
|| format!("**{name}"),
&mut diagnostics,
is_overridden,
);
}
}
} else {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
&& checker.settings.dummy_variable_rgx.is_match(&arg.arg))
{
if checker.settings.rules.enabled(Rule::MissingTypeKwargs) {
diagnostics.push(Diagnostic::new(
MissingTypeKwargs {
name: arg.node.arg.to_string(),
name: arg.arg.to_string(),
},
arg.range(),
));
@@ -582,12 +593,12 @@ pub(crate) fn definition(
// ANN101, ANN102
if is_method && !visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt)) {
if let Some(arg) = args.posonlyargs.first().or_else(|| args.args.first()) {
if arg.node.annotation.is_none() {
if arg.annotation.is_none() {
if visibility::is_classmethod(&checker.ctx, cast::decorator_list(stmt)) {
if checker.settings.rules.enabled(Rule::MissingTypeCls) {
diagnostics.push(Diagnostic::new(
MissingTypeCls {
name: arg.node.arg.to_string(),
name: arg.arg.to_string(),
},
arg.range(),
));
@@ -596,7 +607,7 @@ pub(crate) fn definition(
if checker.settings.rules.enabled(Rule::MissingTypeSelf) {
diagnostics.push(Diagnostic::new(
MissingTypeSelf {
name: arg.node.arg.to_string(),
name: arg.arg.to_string(),
},
arg.range(),
));
@@ -612,7 +623,13 @@ pub(crate) fn definition(
if let Some(expr) = &returns {
has_typed_return = true;
if checker.settings.rules.enabled(Rule::AnyType) {
check_dynamically_typed(checker, expr, || name.to_string(), &mut diagnostics);
check_dynamically_typed(
checker,
expr,
|| name.to_string(),
&mut diagnostics,
is_overridden,
);
}
} else if !(
// Allow omission of return annotation if the function only returns `None`

View File

@@ -2,10 +2,11 @@
use serde::{Deserialize, Serialize};
use ruff_macros::CacheKey;
use ruff_macros::ConfigurationOptions;
use ruff_macros::{CacheKey, CombineOptions, ConfigurationOptions};
#[derive(Debug, PartialEq, Eq, Default, Serialize, Deserialize, ConfigurationOptions)]
#[derive(
Debug, PartialEq, Eq, Default, Serialize, Deserialize, ConfigurationOptions, CombineOptions,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",

View File

@@ -1,189 +1,189 @@
---
source: crates/ruff/src/rules/flake8_annotations/mod.rs
---
annotation_presence.py:4:5: ANN201 Missing return type annotation for public function `foo`
annotation_presence.py:5:5: ANN201 Missing return type annotation for public function `foo`
|
4 | # Error
5 | def foo(a, b):
5 | # Error
6 | def foo(a, b):
| ^^^ ANN201
6 | pass
7 | pass
|
annotation_presence.py:4:9: ANN001 Missing type annotation for function argument `a`
annotation_presence.py:5:9: ANN001 Missing type annotation for function argument `a`
|
4 | # Error
5 | def foo(a, b):
5 | # Error
6 | def foo(a, b):
| ^ ANN001
6 | pass
7 | pass
|
annotation_presence.py:4:12: ANN001 Missing type annotation for function argument `b`
annotation_presence.py:5:12: ANN001 Missing type annotation for function argument `b`
|
4 | # Error
5 | def foo(a, b):
5 | # Error
6 | def foo(a, b):
| ^ ANN001
6 | pass
7 | pass
|
annotation_presence.py:9:5: ANN201 Missing return type annotation for public function `foo`
annotation_presence.py:10:5: ANN201 Missing return type annotation for public function `foo`
|
9 | # Error
10 | def foo(a: int, b):
10 | # Error
11 | def foo(a: int, b):
| ^^^ ANN201
11 | pass
12 | pass
|
annotation_presence.py:9:17: ANN001 Missing type annotation for function argument `b`
annotation_presence.py:10:17: ANN001 Missing type annotation for function argument `b`
|
9 | # Error
10 | def foo(a: int, b):
10 | # Error
11 | def foo(a: int, b):
| ^ ANN001
11 | pass
12 | pass
|
annotation_presence.py:14:17: ANN001 Missing type annotation for function argument `b`
annotation_presence.py:15:17: ANN001 Missing type annotation for function argument `b`
|
14 | # Error
15 | def foo(a: int, b) -> int:
15 | # Error
16 | def foo(a: int, b) -> int:
| ^ ANN001
16 | pass
17 | pass
|
annotation_presence.py:19:5: ANN201 Missing return type annotation for public function `foo`
annotation_presence.py:20:5: ANN201 Missing return type annotation for public function `foo`
|
19 | # Error
20 | def foo(a: int, b: int):
20 | # Error
21 | def foo(a: int, b: int):
| ^^^ ANN201
21 | pass
22 | pass
|
annotation_presence.py:24:5: ANN201 Missing return type annotation for public function `foo`
annotation_presence.py:25:5: ANN201 Missing return type annotation for public function `foo`
|
24 | # Error
25 | def foo():
25 | # Error
26 | def foo():
| ^^^ ANN201
26 | pass
27 | pass
|
annotation_presence.py:44:12: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
annotation_presence.py:45:12: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
44 | # ANN401
45 | def foo(a: Any, *args: str, **kwargs: str) -> int:
45 | # ANN401
46 | def foo(a: Any, *args: str, **kwargs: str) -> int:
| ^^^ ANN401
46 | pass
47 | pass
|
annotation_presence.py:49:47: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `foo`
annotation_presence.py:50:47: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `foo`
|
49 | # ANN401
50 | def foo(a: int, *args: str, **kwargs: str) -> Any:
50 | # ANN401
51 | def foo(a: int, *args: str, **kwargs: str) -> Any:
| ^^^ ANN401
51 | pass
52 | pass
|
annotation_presence.py:54:24: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*args`
annotation_presence.py:55:24: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*args`
|
54 | # ANN401
55 | def foo(a: int, *args: Any, **kwargs: Any) -> int:
55 | # ANN401
56 | def foo(a: int, *args: Any, **kwargs: Any) -> int:
| ^^^ ANN401
56 | pass
57 | pass
|
annotation_presence.py:54:39: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**kwargs`
annotation_presence.py:55:39: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**kwargs`
|
54 | # ANN401
55 | def foo(a: int, *args: Any, **kwargs: Any) -> int:
55 | # ANN401
56 | def foo(a: int, *args: Any, **kwargs: Any) -> int:
| ^^^ ANN401
56 | pass
57 | pass
|
annotation_presence.py:59:24: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*args`
annotation_presence.py:60:24: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*args`
|
59 | # ANN401
60 | def foo(a: int, *args: Any, **kwargs: str) -> int:
60 | # ANN401
61 | def foo(a: int, *args: Any, **kwargs: str) -> int:
| ^^^ ANN401
61 | pass
62 | pass
|
annotation_presence.py:64:39: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**kwargs`
annotation_presence.py:65:39: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**kwargs`
|
64 | # ANN401
65 | def foo(a: int, *args: str, **kwargs: Any) -> int:
65 | # ANN401
66 | def foo(a: int, *args: str, **kwargs: Any) -> int:
| ^^^ ANN401
66 | pass
67 | pass
|
annotation_presence.py:74:13: ANN101 Missing type annotation for `self` in method
annotation_presence.py:75:13: ANN101 Missing type annotation for `self` in method
|
74 | # ANN101
75 | def foo(self, a: int, b: int) -> int:
75 | # ANN101
76 | def foo(self, a: int, b: int) -> int:
| ^^^^ ANN101
76 | pass
77 | pass
|
annotation_presence.py:78:29: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
annotation_presence.py:79:29: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
78 | # ANN401
79 | def foo(self: "Foo", a: Any, *params: str, **options: str) -> int:
79 | # ANN401
80 | def foo(self: "Foo", a: Any, *params: str, **options: str) -> int:
| ^^^ ANN401
80 | pass
81 | pass
|
annotation_presence.py:82:67: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `foo`
annotation_presence.py:83:67: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `foo`
|
82 | # ANN401
83 | def foo(self: "Foo", a: int, *params: str, **options: str) -> Any:
83 | # ANN401
84 | def foo(self: "Foo", a: int, *params: str, **options: str) -> Any:
| ^^^ ANN401
84 | pass
85 | pass
|
annotation_presence.py:86:43: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*params`
annotation_presence.py:87:43: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*params`
|
86 | # ANN401
87 | def foo(self: "Foo", a: int, *params: Any, **options: Any) -> int:
87 | # ANN401
88 | def foo(self: "Foo", a: int, *params: Any, **options: Any) -> int:
| ^^^ ANN401
88 | pass
89 | pass
|
annotation_presence.py:86:59: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**options`
annotation_presence.py:87:59: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**options`
|
86 | # ANN401
87 | def foo(self: "Foo", a: int, *params: Any, **options: Any) -> int:
87 | # ANN401
88 | def foo(self: "Foo", a: int, *params: Any, **options: Any) -> int:
| ^^^ ANN401
88 | pass
89 | pass
|
annotation_presence.py:90:43: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*params`
annotation_presence.py:91:43: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*params`
|
90 | # ANN401
91 | def foo(self: "Foo", a: int, *params: Any, **options: str) -> int:
91 | # ANN401
92 | def foo(self: "Foo", a: int, *params: Any, **options: str) -> int:
| ^^^ ANN401
92 | pass
93 | pass
|
annotation_presence.py:94:59: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**options`
annotation_presence.py:95:59: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**options`
|
94 | # ANN401
95 | def foo(self: "Foo", a: int, *params: str, **options: Any) -> int:
95 | # ANN401
96 | def foo(self: "Foo", a: int, *params: str, **options: Any) -> int:
| ^^^ ANN401
96 | pass
97 | pass
|
annotation_presence.py:104:13: ANN102 Missing type annotation for `cls` in classmethod
annotation_presence.py:130:13: ANN102 Missing type annotation for `cls` in classmethod
|
104 | # ANN102
105 | @classmethod
106 | def foo(cls, a: int, b: int) -> int:
130 | # ANN102
131 | @classmethod
132 | def foo(cls, a: int, b: int) -> int:
| ^^^ ANN102
107 | pass
133 | pass
|
annotation_presence.py:108:13: ANN101 Missing type annotation for `self` in method
annotation_presence.py:134:13: ANN101 Missing type annotation for `self` in method
|
108 | # ANN101
109 | def foo(self, /, a: int, b: int) -> int:
134 | # ANN101
135 | def foo(self, /, a: int, b: int) -> int:
| ^^^^ ANN101
110 | pass
136 | pass
|

View File

@@ -0,0 +1,28 @@
//! Rules from [flake8-async](https://pypi.org/project/flake8-async/).
pub(crate) mod rules;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::assert_messages;
use crate::registry::Rule;
use crate::settings::Settings;
use crate::test::test_path;
#[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC100.py"); "ASYNC100")]
#[test_case(Rule::OpenSleepOrSubprocessInAsyncFunction, Path::new("ASYNC101.py"); "ASYNC101")]
#[test_case(Rule::BlockingOsCallInAsyncFunction, Path::new("ASYNC102.py"); "ASYNC102")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_async").join(path).as_path(),
&Settings::for_rule(rule_code),
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -0,0 +1,225 @@
use rustpython_parser::ast;
use rustpython_parser::ast::{Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::context::Context;
use ruff_python_semantic::scope::{FunctionDef, ScopeKind};
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks that async functions do not contain blocking HTTP calls.
///
/// ## Why is this bad?
/// Blocking an async function via a blocking HTTP call will block the entire
/// event loop, preventing it from executing other tasks while waiting for the
/// HTTP response, negating the benefits of asynchronous programming.
///
/// Instead of making a blocking HTTP call, use an asynchronous HTTP client
/// library such as `aiohttp` or `httpx`.
///
/// ## Example
/// ```python
/// async def fetch():
/// urllib.request.urlopen("https://example.com/foo/bar").read()
/// ```
///
/// Use instead:
/// ```python
/// async def fetch():
/// async with aiohttp.ClientSession() as session:
/// async with session.get("https://example.com/foo/bar") as resp:
/// ...
/// ```
#[violation]
pub struct BlockingHttpCallInAsyncFunction;
impl Violation for BlockingHttpCallInAsyncFunction {
#[derive_message_formats]
fn message(&self) -> String {
format!("Async functions should not call blocking HTTP methods")
}
}
const BLOCKING_HTTP_CALLS: &[&[&str]] = &[
&["urllib", "request", "urlopen"],
&["httpx", "get"],
&["httpx", "post"],
&["httpx", "delete"],
&["httpx", "patch"],
&["httpx", "put"],
&["httpx", "head"],
&["httpx", "connect"],
&["httpx", "options"],
&["httpx", "trace"],
&["requests", "get"],
&["requests", "post"],
&["requests", "delete"],
&["requests", "patch"],
&["requests", "put"],
&["requests", "head"],
&["requests", "connect"],
&["requests", "options"],
&["requests", "trace"],
];
/// ASYNC100
pub(crate) fn blocking_http_call(checker: &mut Checker, expr: &Expr) {
if in_async_function(&checker.ctx) {
if let Expr::Call(ast::ExprCall { func, .. }) = expr {
if let Some(call_path) = checker.ctx.resolve_call_path(func) {
if BLOCKING_HTTP_CALLS.contains(&call_path.as_slice()) {
checker.diagnostics.push(Diagnostic::new(
BlockingHttpCallInAsyncFunction,
func.range(),
));
}
}
}
}
}
/// ## What it does
/// Checks that async functions do not contain calls to `open`, `time.sleep`,
/// or `subprocess` methods.
///
/// ## Why is this bad?
/// Blocking an async function via a blocking call will block the entire
/// event loop, preventing it from executing other tasks while waiting for the
/// call to complete, negating the benefits of asynchronous programming.
///
/// Instead of making a blocking call, use an equivalent asynchronous library
/// or function.
///
/// ## Example
/// ```python
/// async def foo():
/// time.sleep(1000)
/// ```
///
/// Use instead:
/// ```python
/// async def foo():
/// await asyncio.sleep(1000)
/// ```
#[violation]
pub struct OpenSleepOrSubprocessInAsyncFunction;
impl Violation for OpenSleepOrSubprocessInAsyncFunction {
#[derive_message_formats]
fn message(&self) -> String {
format!("Async functions should not call `open`, `time.sleep`, or `subprocess` methods")
}
}
const OPEN_SLEEP_OR_SUBPROCESS_CALL: &[&[&str]] = &[
&["", "open"],
&["time", "sleep"],
&["subprocess", "run"],
&["subprocess", "Popen"],
// Deprecated subprocess calls:
&["subprocess", "call"],
&["subprocess", "check_call"],
&["subprocess", "check_output"],
&["subprocess", "getoutput"],
&["subprocess", "getstatusoutput"],
&["os", "wait"],
&["os", "wait3"],
&["os", "wait4"],
&["os", "waitid"],
&["os", "waitpid"],
];
/// ASYNC101
pub(crate) fn open_sleep_or_subprocess_call(checker: &mut Checker, expr: &Expr) {
if in_async_function(&checker.ctx) {
if let Expr::Call(ast::ExprCall { func, .. }) = expr {
if let Some(call_path) = checker.ctx.resolve_call_path(func) {
if OPEN_SLEEP_OR_SUBPROCESS_CALL.contains(&call_path.as_slice()) {
checker.diagnostics.push(Diagnostic::new(
OpenSleepOrSubprocessInAsyncFunction,
func.range(),
));
}
}
}
}
}
/// ## What it does
/// Checks that async functions do not contain calls to blocking synchronous
/// process calls via the `os` module.
///
/// ## Why is this bad?
/// Blocking an async function via a blocking call will block the entire
/// event loop, preventing it from executing other tasks while waiting for the
/// call to complete, negating the benefits of asynchronous programming.
///
/// Instead of making a blocking call, use an equivalent asynchronous library
/// or function.
///
/// ## Example
/// ```python
/// async def foo():
/// os.popen()
/// ```
///
/// Use instead:
/// ```python
/// def foo():
/// os.popen()
/// ```
#[violation]
pub struct BlockingOsCallInAsyncFunction;
impl Violation for BlockingOsCallInAsyncFunction {
#[derive_message_formats]
fn message(&self) -> String {
format!("Async functions should not call synchronous `os` methods")
}
}
const UNSAFE_OS_METHODS: &[&[&str]] = &[
&["os", "popen"],
&["os", "posix_spawn"],
&["os", "posix_spawnp"],
&["os", "spawnl"],
&["os", "spawnle"],
&["os", "spawnlp"],
&["os", "spawnlpe"],
&["os", "spawnv"],
&["os", "spawnve"],
&["os", "spawnvp"],
&["os", "spawnvpe"],
&["os", "system"],
];
/// ASYNC102
pub(crate) fn blocking_os_call(checker: &mut Checker, expr: &Expr) {
if in_async_function(&checker.ctx) {
if let Expr::Call(ast::ExprCall { func, .. }) = expr {
if let Some(call_path) = checker.ctx.resolve_call_path(func) {
if UNSAFE_OS_METHODS.contains(&call_path.as_slice()) {
checker
.diagnostics
.push(Diagnostic::new(BlockingOsCallInAsyncFunction, func.range()));
}
}
}
}
}
/// Return `true` if the [`Context`] is inside an async function definition.
fn in_async_function(context: &Context) -> bool {
context
.scopes()
.find_map(|scope| {
if let ScopeKind::Function(FunctionDef { async_, .. }) = &scope.kind {
Some(*async_)
} else {
None
}
})
.unwrap_or(false)
}

View File

@@ -0,0 +1,39 @@
---
source: crates/ruff/src/rules/flake8_async/mod.rs
---
ASYNC100.py:7:5: ASYNC100 Async functions should not call blocking HTTP methods
|
7 | async def foo():
8 | urllib.request.urlopen("http://example.com/foo/bar").read()
| ^^^^^^^^^^^^^^^^^^^^^^ ASYNC100
|
ASYNC100.py:11:5: ASYNC100 Async functions should not call blocking HTTP methods
|
11 | async def foo():
12 | requests.get()
| ^^^^^^^^^^^^ ASYNC100
|
ASYNC100.py:15:5: ASYNC100 Async functions should not call blocking HTTP methods
|
15 | async def foo():
16 | httpx.get()
| ^^^^^^^^^ ASYNC100
|
ASYNC100.py:19:5: ASYNC100 Async functions should not call blocking HTTP methods
|
19 | async def foo():
20 | requests.post()
| ^^^^^^^^^^^^^ ASYNC100
|
ASYNC100.py:23:5: ASYNC100 Async functions should not call blocking HTTP methods
|
23 | async def foo():
24 | httpx.post()
| ^^^^^^^^^^ ASYNC100
|

View File

@@ -0,0 +1,46 @@
---
source: crates/ruff/src/rules/flake8_async/mod.rs
---
ASYNC101.py:7:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
7 | async def foo():
8 | open("foo")
| ^^^^ ASYNC101
|
ASYNC101.py:11:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
11 | async def foo():
12 | time.sleep(1)
| ^^^^^^^^^^ ASYNC101
|
ASYNC101.py:15:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
15 | async def foo():
16 | subprocess.run("foo")
| ^^^^^^^^^^^^^^ ASYNC101
|
ASYNC101.py:19:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
19 | async def foo():
20 | subprocess.call("foo")
| ^^^^^^^^^^^^^^^ ASYNC101
|
ASYNC101.py:27:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
27 | async def foo():
28 | os.wait4(10)
| ^^^^^^^^ ASYNC101
|
ASYNC101.py:31:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods
|
31 | async def foo():
32 | os.wait(12)
| ^^^^^^^ ASYNC101
|

View File

@@ -0,0 +1,18 @@
---
source: crates/ruff/src/rules/flake8_async/mod.rs
---
ASYNC102.py:5:5: ASYNC102 Async functions should not call synchronous `os` methods
|
5 | async def foo():
6 | os.popen()
| ^^^^^^^^ ASYNC102
|
ASYNC102.py:9:5: ASYNC102 Async functions should not call synchronous `os` methods
|
9 | async def foo():
10 | os.spawnl()
| ^^^^^^^^^ ASYNC102
|

View File

@@ -1,6 +1,6 @@
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_parser::ast::{self, Constant, Expr, ExprKind};
use rustpython_parser::ast::{self, Constant, Expr};
use crate::checkers::ast::Checker;
@@ -9,8 +9,8 @@ static PASSWORD_CANDIDATE_REGEX: Lazy<Regex> = Lazy::new(|| {
});
pub(crate) fn string_literal(expr: &Expr) -> Option<&str> {
match &expr.node {
ExprKind::Constant(ast::ExprConstant {
match expr {
Expr::Constant(ast::ExprConstant {
value: Constant::Str(string),
..
}) => Some(string),
@@ -24,7 +24,7 @@ pub(crate) fn matches_password_name(string: &str) -> bool {
pub(crate) fn is_untyped_exception(type_: Option<&Expr>, checker: &Checker) -> bool {
type_.map_or(true, |type_| {
if let ExprKind::Tuple(ast::ExprTuple { elts, .. }) = &type_.node {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &type_ {
elts.iter().any(|type_| {
checker
.ctx

View File

@@ -1,5 +1,5 @@
use ruff_text_size::{TextLen, TextRange};
use rustpython_parser::ast::Stmt;
use rustpython_parser::ast::{Ranged, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};

View File

@@ -1,7 +1,7 @@
use num_traits::ToPrimitive;
use once_cell::sync::Lazy;
use rustc_hash::FxHashMap;
use rustpython_parser::ast::{self, Constant, Expr, ExprKind, Keyword, Operator};
use rustpython_parser::ast::{self, Constant, Expr, Keyword, Operator, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -69,15 +69,20 @@ static PYSTAT_MAPPING: Lazy<FxHashMap<&'static str, u16>> = Lazy::new(|| {
});
fn get_int_value(expr: &Expr) -> Option<u16> {
match &expr.node {
ExprKind::Constant(ast::ExprConstant {
match expr {
Expr::Constant(ast::ExprConstant {
value: Constant::Int(value),
..
}) => value.to_u16(),
ExprKind::Attribute(_) => {
Expr::Attribute(_) => {
compose_call_path(expr).and_then(|path| PYSTAT_MAPPING.get(path.as_str()).copied())
}
ExprKind::BinOp(ast::ExprBinOp { left, op, right }) => {
Expr::BinOp(ast::ExprBinOp {
left,
op,
right,
range: _,
}) => {
if let (Some(left_value), Some(right_value)) =
(get_int_value(left), get_int_value(right))
{

View File

@@ -1,4 +1,4 @@
use rustpython_parser::ast::{self, Expr, ExprKind};
use rustpython_parser::ast::{self, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -15,7 +15,7 @@ impl Violation for ExecBuiltin {
/// S102
pub(crate) fn exec_used(expr: &Expr, func: &Expr) -> Option<Diagnostic> {
let ExprKind::Name(ast::ExprName { id, .. }) = &func.node else {
let Expr::Name(ast::ExprName { id, .. }) = func else {
return None;
};
if id != "exec" {

View File

@@ -1,4 +1,4 @@
use rustpython_parser::ast::{Arg, Arguments, Expr};
use rustpython_parser::ast::{Arg, Arguments, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -23,7 +23,7 @@ impl Violation for HardcodedPasswordDefault {
fn check_password_kwarg(arg: &Arg, default: &Expr) -> Option<Diagnostic> {
string_literal(default).filter(|string| !string.is_empty())?;
let kwarg_name = &arg.node.arg;
let kwarg_name = &arg.arg;
if !matches_password_name(kwarg_name) {
return None;
}

View File

@@ -1,4 +1,4 @@
use rustpython_parser::ast::Keyword;
use rustpython_parser::ast::{Keyword, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -26,8 +26,8 @@ pub(crate) fn hardcoded_password_func_arg(keywords: &[Keyword]) -> Vec<Diagnosti
keywords
.iter()
.filter_map(|keyword| {
string_literal(&keyword.node.value).filter(|string| !string.is_empty())?;
let arg = keyword.node.arg.as_ref()?;
string_literal(&keyword.value).filter(|string| !string.is_empty())?;
let arg = keyword.arg.as_ref()?;
if !matches_password_name(arg) {
return None;
}

View File

@@ -1,4 +1,4 @@
use rustpython_parser::ast::{self, Constant, Expr, ExprKind};
use rustpython_parser::ast::{self, Constant, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -22,19 +22,19 @@ impl Violation for HardcodedPasswordString {
}
fn password_target(target: &Expr) -> Option<&str> {
let target_name = match &target.node {
let target_name = match target {
// variable = "s3cr3t"
ExprKind::Name(ast::ExprName { id, .. }) => id.as_str(),
Expr::Name(ast::ExprName { id, .. }) => id.as_str(),
// d["password"] = "s3cr3t"
ExprKind::Subscript(ast::ExprSubscript { slice, .. }) => match &slice.node {
ExprKind::Constant(ast::ExprConstant {
Expr::Subscript(ast::ExprSubscript { slice, .. }) => match slice.as_ref() {
Expr::Constant(ast::ExprConstant {
value: Constant::Str(string),
..
}) => string,
_ => return None,
},
// obj.password = "s3cr3t"
ExprKind::Attribute(ast::ExprAttribute { attr, .. }) => attr,
Expr::Attribute(ast::ExprAttribute { attr, .. }) => attr,
_ => return None,
};

View File

@@ -1,17 +1,17 @@
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_parser::ast::{self, Expr, ExprKind, Operator};
use rustpython_parser::ast::{self, Expr, Operator, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::{any_over_expr, unparse_expr};
use ruff_python_ast::helpers::any_over_expr;
use crate::checkers::ast::Checker;
use super::super::helpers::string_literal;
static SQL_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?i)(select\s.*from\s|delete\s+from\s|insert\s+into\s.*values\s|update\s.*set\s)")
Regex::new(r"(?i)\b(select\s.+\sfrom\s|delete\s+from\s|(insert|replace)\s.+\svalues\s|update\s.+\sset\s)")
.unwrap()
});
@@ -53,40 +53,40 @@ fn matches_sql_statement(string: &str) -> bool {
}
fn unparse_string_format_expression(checker: &mut Checker, expr: &Expr) -> Option<String> {
match &expr.node {
match expr {
// "select * from table where val = " + "str" + ...
// "select * from table where val = %s" % ...
ExprKind::BinOp(ast::ExprBinOp {
Expr::BinOp(ast::ExprBinOp {
op: Operator::Add | Operator::Mod,
..
}) => {
let Some(parent) = checker.ctx.expr_parent() else {
if any_over_expr(expr, &has_string_literal) {
return Some(unparse_expr(expr, checker.stylist));
return Some(checker.generator().expr(expr));
}
return None;
};
// Only evaluate the full BinOp, not the nested components.
let ExprKind::BinOp(_ )= &parent.node else {
let Expr::BinOp(_ )= parent else {
if any_over_expr(expr, &has_string_literal) {
return Some(unparse_expr(expr, checker.stylist));
return Some(checker.generator().expr(expr));
}
return None;
};
None
}
ExprKind::Call(ast::ExprCall { func, .. }) => {
let ExprKind::Attribute(ast::ExprAttribute { attr, value, .. }) = &func.node else {
Expr::Call(ast::ExprCall { func, .. }) => {
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func.as_ref() else {
return None;
};
// "select * from table where val = {}".format(...)
if attr == "format" && string_literal(value).is_some() {
return Some(unparse_expr(expr, checker.stylist));
return Some(checker.generator().expr(expr));
};
None
}
// f"select * from table where val = {val}"
ExprKind::JoinedStr(_) => Some(unparse_expr(expr, checker.stylist)),
Expr::JoinedStr(_) => Some(checker.generator().expr(expr)),
_ => None,
}
}

View File

@@ -1,4 +1,4 @@
use rustpython_parser::ast::Expr;
use rustpython_parser::ast::{Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};

View File

@@ -1,4 +1,4 @@
use rustpython_parser::ast::{self, Constant, Expr, ExprKind, Keyword};
use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -26,8 +26,8 @@ const WEAK_HASHES: [&str; 4] = ["md4", "md5", "sha", "sha1"];
fn is_used_for_security(call_args: &SimpleCallArgs) -> bool {
match call_args.keyword_argument("usedforsecurity") {
Some(expr) => !matches!(
&expr.node,
ExprKind::Constant(ast::ExprConstant {
expr,
Expr::Constant(ast::ExprConstant {
value: Constant::Bool(false),
..
})

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