Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e7878ff48 | ||
|
|
5113ded22a | ||
|
|
bf7bf7aa17 | ||
|
|
560c00ff9d | ||
|
|
befe64a10e | ||
|
|
4eccfdeb69 | ||
|
|
4123ba9851 | ||
|
|
e727c24f79 | ||
|
|
bd3b40688f | ||
|
|
b5549382a7 | ||
|
|
8cf745045f | ||
|
|
f6992cc98c | ||
|
|
3cc74c0564 | ||
|
|
887b9aa840 | ||
|
|
faf8556a5c | ||
|
|
1888f6d41b |
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.109
|
||||
rev: v0.0.112
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -933,7 +933,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.109-dev.0"
|
||||
version = "0.0.112-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.22",
|
||||
@@ -1622,6 +1622,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nohash-hasher"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.2"
|
||||
@@ -2234,11 +2240,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.109"
|
||||
version = "0.0.112"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
"bincode",
|
||||
"bitflags",
|
||||
"cacache",
|
||||
"chrono",
|
||||
"clap 4.0.22",
|
||||
@@ -2255,6 +2262,7 @@ dependencies = [
|
||||
"itertools",
|
||||
"libcst",
|
||||
"log",
|
||||
"nohash-hasher",
|
||||
"notify",
|
||||
"num-bigint",
|
||||
"once_cell",
|
||||
@@ -2279,7 +2287,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.109"
|
||||
version = "0.0.112"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.22",
|
||||
|
||||
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.109"
|
||||
version = "0.0.112"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -15,6 +15,7 @@ name = "ruff"
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.66" }
|
||||
bincode = { version = "1.3.3" }
|
||||
bitflags = { version = "1.3.2" }
|
||||
chrono = { version = "0.4.21" }
|
||||
clap = { version = "4.0.1", features = ["derive"] }
|
||||
colored = { version = "2.0.0" }
|
||||
@@ -26,13 +27,14 @@ glob = { version = "0.3.0" }
|
||||
itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "a13ec97dd4eb925bde4d426c6e422582793b260c" }
|
||||
log = { version = "0.4.17" }
|
||||
nohash-hasher = { version = "0.2.0" }
|
||||
notify = { version = "4.0.17" }
|
||||
num-bigint = { version = "0.4.3" }
|
||||
once_cell = { version = "1.16.0" }
|
||||
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
ropey = { version = "1.5.0" }
|
||||
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb" }
|
||||
@@ -43,7 +45,7 @@ strum_macros = { version = "0.24.3" }
|
||||
textwrap = { version = "0.16.0" }
|
||||
titlecase = { version = "2.2.1" }
|
||||
toml = { version = "0.5.9" }
|
||||
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
|
||||
update-informer = { version = "0.5.0", default-features = false, features = ["pypi"], optional = true }
|
||||
walkdir = { version = "2.3.2" }
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
|
||||
65
README.md
65
README.md
@@ -27,9 +27,10 @@ An extremely fast Python linter, written in Rust.
|
||||
|
||||
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
|
||||
functionality behind a single, common interface. Ruff can be used to replace Flake8 (plus a variety
|
||||
of plugins), [`pydocstyle`](https://pypi.org/project/pydocstyle/), [`yesqa`](https://github.com/asottile/yesqa),
|
||||
and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/) and [`autoflake`](https://pypi.org/project/autoflake/)
|
||||
all while executing tens or hundreds of times faster than any individual tool.
|
||||
of plugins), [`isort`](https://pypi.org/project/isort/), [`pydocstyle`](https://pypi.org/project/pydocstyle/),
|
||||
[`yesqa`](https://github.com/asottile/yesqa), and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/)
|
||||
and [`autoflake`](https://pypi.org/project/autoflake/) all while executing tens or hundreds of times
|
||||
faster than any individual tool.
|
||||
|
||||
(Coming from Flake8? Try [`flake8-to-ruff`](https://pypi.org/project/flake8-to-ruff/) to
|
||||
automatically convert your existing configuration.)
|
||||
@@ -97,7 +98,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.109
|
||||
rev: v0.0.112
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -285,16 +286,16 @@ Ruff supports several workflows to aid in `noqa` management.
|
||||
|
||||
First, Ruff provides a special error code, `M001`, to enforce that your `noqa` directives are
|
||||
"valid", in that the errors they _say_ they ignore are actually being triggered on that line (and
|
||||
thus suppressed). **You can run `ruff /path/to/file.py --extend-select M001` to flag unused `noqa`
|
||||
directives.**
|
||||
thus suppressed). You can run `ruff /path/to/file.py --extend-select M001` to flag unused `noqa`
|
||||
directives.
|
||||
|
||||
Second, Ruff can _automatically remove_ unused `noqa` directives via its autofix functionality.
|
||||
**You can run `ruff /path/to/file.py --extend-select M001 --fix` to automatically remove unused
|
||||
`noqa` directives.**
|
||||
You can run `ruff /path/to/file.py --extend-select M001 --fix` to automatically remove unused
|
||||
`noqa` directives.
|
||||
|
||||
Third, Ruff can _automatically add_ `noqa` directives to all failing lines. This is useful when
|
||||
migrating a new codebase to Ruff. **You can run `ruff /path/to/file.py --add-noqa` to automatically
|
||||
add `noqa` directives to all failing lines, with the appropriate error codes.**
|
||||
migrating a new codebase to Ruff. You can run `ruff /path/to/file.py --add-noqa` to automatically
|
||||
add `noqa` directives to all failing lines, with the appropriate error codes.
|
||||
|
||||
## Supported Rules
|
||||
|
||||
@@ -365,6 +366,14 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI
|
||||
| W292 | NoNewLineAtEndOfFile | No newline at end of file | |
|
||||
| W605 | InvalidEscapeSequence | Invalid escape sequence: '\c' | |
|
||||
|
||||
### isort
|
||||
|
||||
For more, see [isort](https://pypi.org/project/isort/5.10.1/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| I001 | UnsortedImports | Import block is un-sorted or un-formatted | 🛠 |
|
||||
|
||||
### pydocstyle
|
||||
|
||||
For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
|
||||
@@ -492,6 +501,8 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/
|
||||
| B006 | MutableArgumentDefault | Do not use mutable data structures for argument defaults. | |
|
||||
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 |
|
||||
| B008 | FunctionCallArgumentDefault | Do not perform function calls in argument defaults. | |
|
||||
| B009 | GetAttrWithConstant | Do not call `getattr` with a constant attribute value, it is not any safer than normal property access. | 🛠 |
|
||||
| B010 | SetAttrWithConstant | Do not call `setattr` with a constant attribute value, it is not any safer than normal property access. | |
|
||||
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
|
||||
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError:` instead of `except (ValueError,):`. | |
|
||||
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
|
||||
@@ -655,7 +666,7 @@ including:
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (18/32)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (20/32)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/34)
|
||||
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
|
||||
|
||||
@@ -678,9 +689,9 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (18/32)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (20/32)
|
||||
|
||||
Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa),
|
||||
Ruff can also replace [`isort`](https://pypi.org/project/isort/), [`yesqa`](https://github.com/asottile/yesqa),
|
||||
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/34).
|
||||
|
||||
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
|
||||
@@ -701,6 +712,34 @@ on Rust at all.
|
||||
Ruff does not yet support third-party plugins, though a plugin system is within-scope for the
|
||||
project. See [#283](https://github.com/charliermarsh/ruff/issues/283) for more.
|
||||
|
||||
### How does Ruff's import sorting compare to [`isort`](https://pypi.org/project/isort/)?
|
||||
|
||||
Ruff's import sorting is intended to be equivalent to `isort` when used `profile = "black"`, and a
|
||||
few other settings (`combine_as_imports = true`, `order_by_type = false`, and
|
||||
`case_sensitive` = true`).
|
||||
|
||||
Like `isort`, Ruff's import sorting is compatible with Black.
|
||||
|
||||
Ruff is less configurable than `isort`, but supports the `known-first-party`, `known-third-party`,
|
||||
`extra-standard-library`, and `src` settings, like so:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
select = [
|
||||
# Pyflakes
|
||||
"F",
|
||||
# Pycodestyle
|
||||
"E",
|
||||
"W",
|
||||
# isort
|
||||
"I"
|
||||
]
|
||||
src = ["src", "tests"]
|
||||
|
||||
[tool.ruff.isort]
|
||||
known-first-party = ["my_module1", "my_module2"]
|
||||
```
|
||||
|
||||
### Does Ruff support NumPy- or Google-style docstrings?
|
||||
|
||||
Yes! To enable a specific docstring convention, start by enabling all `pydocstyle` error codes, and
|
||||
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.109"
|
||||
version = "0.0.112"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.109"
|
||||
version = "0.0.112"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.109-dev.0"
|
||||
version = "0.0.112-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -208,6 +208,7 @@ mod tests {
|
||||
let actual = convert(&HashMap::from([]), None)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
src: None,
|
||||
fix: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
@@ -224,6 +225,7 @@ mod tests {
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -239,6 +241,7 @@ mod tests {
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: Some(100),
|
||||
src: None,
|
||||
fix: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
@@ -255,6 +258,7 @@ mod tests {
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -270,6 +274,7 @@ mod tests {
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: Some(100),
|
||||
src: None,
|
||||
fix: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
@@ -286,6 +291,7 @@ mod tests {
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -301,6 +307,7 @@ mod tests {
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
src: None,
|
||||
fix: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
@@ -317,6 +324,7 @@ mod tests {
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -332,6 +340,7 @@ mod tests {
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
src: None,
|
||||
fix: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
@@ -353,6 +362,7 @@ mod tests {
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -371,6 +381,7 @@ mod tests {
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
src: None,
|
||||
fix: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
@@ -422,6 +433,7 @@ mod tests {
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -437,6 +449,7 @@ mod tests {
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
src: None,
|
||||
fix: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
@@ -459,6 +472,7 @@ mod tests {
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
@@ -32,3 +32,7 @@ build-backend = "maturin"
|
||||
bindings = "bin"
|
||||
sdist-include = ["Cargo.lock"]
|
||||
strip = true
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
known_third_party = ["fastapi", "pydantic", "starlette"]
|
||||
|
||||
36
resources/test/fixtures/B009_B010.py
vendored
Normal file
36
resources/test/fixtures/B009_B010.py
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
Should emit:
|
||||
B009 - Line 18, 19, 20, 21, 22
|
||||
B010 - Line 33, 34, 35, 36
|
||||
"""
|
||||
|
||||
# Valid getattr usage
|
||||
getattr(foo, bar)
|
||||
getattr(foo, "bar", None)
|
||||
getattr(foo, "bar{foo}".format(foo="a"), None)
|
||||
getattr(foo, "bar{foo}".format(foo="a"))
|
||||
getattr(foo, bar, None)
|
||||
getattr(foo, "123abc")
|
||||
getattr(foo, r"123\abc")
|
||||
getattr(foo, "except")
|
||||
|
||||
# Invalid usage
|
||||
getattr(foo, "bar")
|
||||
getattr(foo, "_123abc")
|
||||
getattr(foo, "abc123")
|
||||
getattr(foo, r"abc123")
|
||||
_ = lambda x: getattr(x, "bar")
|
||||
|
||||
# Valid setattr usage
|
||||
setattr(foo, bar, None)
|
||||
setattr(foo, "bar{foo}".format(foo="a"), None)
|
||||
setattr(foo, "123abc", None)
|
||||
setattr(foo, r"123\abc", None)
|
||||
setattr(foo, "except", None)
|
||||
_ = lambda x: setattr(x, "bar", 1)
|
||||
|
||||
# Invalid usage
|
||||
setattr(foo, "bar", None)
|
||||
setattr(foo, "_123abc", None)
|
||||
setattr(foo, "abc123", None)
|
||||
setattr(foo, r"abc123", None)
|
||||
9
resources/test/fixtures/excluded.py
vendored
9
resources/test/fixtures/excluded.py
vendored
@@ -1,9 +0,0 @@
|
||||
a = "abc"
|
||||
b = f"ghi{'jkl'}"
|
||||
|
||||
c = f"def"
|
||||
d = f"def" + "ghi"
|
||||
e = (
|
||||
f"def" +
|
||||
"ghi"
|
||||
)
|
||||
5
resources/test/fixtures/isort/combine_import_froms.py
vendored
Normal file
5
resources/test/fixtures/isort/combine_import_froms.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
from collections import Awaitable
|
||||
from collections import AsyncIterable
|
||||
from collections import Collection
|
||||
from collections import ChainMap
|
||||
from collections import MutableSequence, MutableMapping
|
||||
4
resources/test/fixtures/isort/deduplicate_imports.py
vendored
Normal file
4
resources/test/fixtures/isort/deduplicate_imports.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import os
|
||||
import os
|
||||
import os as os1
|
||||
import os as os2
|
||||
1
resources/test/fixtures/isort/fit_line_length.py
vendored
Normal file
1
resources/test/fixtures/isort/fit_line_length.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
from collections import Collection
|
||||
2
resources/test/fixtures/isort/import_from_after_import.py
vendored
Normal file
2
resources/test/fixtures/isort/import_from_after_import.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
from collections import Collection
|
||||
import os
|
||||
6
resources/test/fixtures/isort/leading_prefix.py
vendored
Normal file
6
resources/test/fixtures/isort/leading_prefix.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
x = 1; import sys
|
||||
import os
|
||||
|
||||
if True:
|
||||
x = 1; import sys
|
||||
import os
|
||||
3
resources/test/fixtures/isort/no_reorder_within_section.py
vendored
Normal file
3
resources/test/fixtures/isort/no_reorder_within_section.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# OK
|
||||
import os
|
||||
import sys
|
||||
6
resources/test/fixtures/isort/preserve_indentation.py
vendored
Normal file
6
resources/test/fixtures/isort/preserve_indentation.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
if True:
|
||||
import sys
|
||||
import os
|
||||
else:
|
||||
import sys
|
||||
import os
|
||||
2
resources/test/fixtures/isort/reorder_within_section.py
vendored
Normal file
2
resources/test/fixtures/isort/reorder_within_section.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import sys
|
||||
import os
|
||||
5
resources/test/fixtures/isort/separate_first_party_imports.py
vendored
Normal file
5
resources/test/fixtures/isort/separate_first_party_imports.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import sys
|
||||
import leading_prefix
|
||||
import numpy as np
|
||||
import os
|
||||
from leading_prefix import Class
|
||||
3
resources/test/fixtures/isort/separate_future_imports.py
vendored
Normal file
3
resources/test/fixtures/isort/separate_future_imports.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import sys
|
||||
import os
|
||||
from __future__ import annotations
|
||||
4
resources/test/fixtures/isort/separate_third_party_imports.py
vendored
Normal file
4
resources/test/fixtures/isort/separate_third_party_imports.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import pandas as pd
|
||||
import sys
|
||||
import numpy as np
|
||||
import os
|
||||
10
resources/test/fixtures/isort/skip.py
vendored
Normal file
10
resources/test/fixtures/isort/skip.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# isort: off
|
||||
import sys
|
||||
import os
|
||||
import collections
|
||||
# isort: on
|
||||
|
||||
import sys
|
||||
import os # isort: skip
|
||||
import collections
|
||||
import abc
|
||||
6
resources/test/fixtures/isort/trailing_suffix.py
vendored
Normal file
6
resources/test/fixtures/isort/trailing_suffix.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import sys
|
||||
import os; x = 1
|
||||
|
||||
if True:
|
||||
import sys
|
||||
import os; x = 1
|
||||
4
resources/test/fixtures/pyproject.toml
vendored
4
resources/test/fixtures/pyproject.toml
vendored
@@ -1,9 +1,9 @@
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
extend-exclude = [
|
||||
"excluded.py",
|
||||
"excluded_file.py",
|
||||
"migrations",
|
||||
"directory/also_excluded.py",
|
||||
"with_excluded_file/other_excluded_file.py",
|
||||
]
|
||||
per-file-ignores = { "__init__.py" = ["F401"] }
|
||||
|
||||
|
||||
7
ruff/__main__.py
Normal file
7
ruff/__main__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import os
|
||||
import sys
|
||||
import sysconfig
|
||||
|
||||
if __name__ == "__main__":
|
||||
ruff = os.path.join(sysconfig.get_path("scripts"), "ruff")
|
||||
os.spawnv(os.P_WAIT, ruff, [ruff, *sys.argv[1:]])
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.109"
|
||||
version = "0.0.112"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -46,6 +46,7 @@ pub enum ScopeKind<'a> {
|
||||
Generator,
|
||||
Module,
|
||||
Arg,
|
||||
Lambda,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -115,6 +115,7 @@ impl<'a> Checker<'a> {
|
||||
deferred_functions: Default::default(),
|
||||
deferred_lambdas: Default::default(),
|
||||
deferred_assignments: Default::default(),
|
||||
// Internal, derivative state.
|
||||
visible_scope: VisibleScope {
|
||||
modifier: Modifier::Module,
|
||||
visibility: module_visibility(path),
|
||||
@@ -1064,6 +1065,19 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::B005) {
|
||||
flake8_bugbear::plugins::strip_with_multi_characters(self, expr, func, args);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B009) {
|
||||
flake8_bugbear::plugins::getattr_with_constant(self, expr, func, args);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B010) {
|
||||
if !self
|
||||
.scope_stack
|
||||
.iter()
|
||||
.rev()
|
||||
.any(|index| matches!(self.scopes[*index].kind, ScopeKind::Lambda))
|
||||
{
|
||||
flake8_bugbear::plugins::setattr_with_constant(self, expr, func, args);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B026) {
|
||||
flake8_bugbear::plugins::star_arg_unpacking_after_keyword_arg(
|
||||
self, args, keywords,
|
||||
@@ -1451,8 +1465,8 @@ where
|
||||
for expr in &args.defaults {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.push_scope(Scope::new(ScopeKind::Lambda))
|
||||
}
|
||||
|
||||
ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => {
|
||||
if self.settings.enabled.contains(&CheckCode::C416) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_comprehension(
|
||||
@@ -1468,7 +1482,6 @@ where
|
||||
}
|
||||
self.push_scope(Scope::new(ScopeKind::Generator))
|
||||
}
|
||||
|
||||
ExprKind::GeneratorExp { .. } | ExprKind::DictComp { .. } => {
|
||||
self.push_scope(Scope::new(ScopeKind::Generator))
|
||||
}
|
||||
@@ -1639,7 +1652,8 @@ where
|
||||
|
||||
// Post-visit.
|
||||
match &expr.node {
|
||||
ExprKind::GeneratorExp { .. }
|
||||
ExprKind::Lambda { .. }
|
||||
| ExprKind::GeneratorExp { .. }
|
||||
| ExprKind::ListComp { .. }
|
||||
| ExprKind::DictComp { .. }
|
||||
| ExprKind::SetComp { .. } => {
|
||||
@@ -2231,7 +2245,7 @@ impl<'a> Checker<'a> {
|
||||
while let Some((expr, scopes, parents)) = self.deferred_lambdas.pop() {
|
||||
self.parent_stack = parents;
|
||||
self.scope_stack = scopes;
|
||||
self.push_scope(Scope::new(ScopeKind::Function(Default::default())));
|
||||
self.push_scope(Scope::new(ScopeKind::Lambda));
|
||||
|
||||
if let ExprKind::Lambda { args, body } = &expr.node {
|
||||
self.visit_arguments(args);
|
||||
@@ -2588,5 +2602,8 @@ pub fn check_ast(
|
||||
// Check docstrings.
|
||||
checker.check_definitions();
|
||||
|
||||
// Check import blocks.
|
||||
// checker.check_import_blocks();
|
||||
|
||||
checker.checks
|
||||
}
|
||||
|
||||
43
src/check_imports.rs
Normal file
43
src/check_imports.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
//! Lint rules based on import analysis.
|
||||
|
||||
use nohash_hasher::IntSet;
|
||||
use rustpython_parser::ast::Suite;
|
||||
|
||||
use crate::ast::visitor::Visitor;
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::Check;
|
||||
use crate::isort;
|
||||
use crate::isort::track::ImportTracker;
|
||||
use crate::settings::Settings;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
fn check_import_blocks(
|
||||
tracker: ImportTracker,
|
||||
locator: &SourceCodeLocator,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Vec<Check> {
|
||||
let mut checks = vec![];
|
||||
for block in tracker.into_iter() {
|
||||
if !block.is_empty() {
|
||||
if let Some(check) = isort::plugins::check_imports(block, locator, settings, autofix) {
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
checks
|
||||
}
|
||||
|
||||
pub fn check_imports(
|
||||
python_ast: &Suite,
|
||||
locator: &SourceCodeLocator,
|
||||
exclusions: &IntSet<usize>,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Vec<Check> {
|
||||
let mut tracker = ImportTracker::new(exclusions);
|
||||
for stmt in python_ast {
|
||||
tracker.visit_stmt(stmt);
|
||||
}
|
||||
check_import_blocks(tracker, locator, settings, autofix)
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Lint rules based on checking raw physical lines.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use nohash_hasher::IntMap;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast::Location;
|
||||
@@ -36,7 +35,7 @@ fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
|
||||
pub fn check_lines(
|
||||
checks: &mut Vec<Check>,
|
||||
contents: &str,
|
||||
noqa_line_for: &[usize],
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) {
|
||||
@@ -44,18 +43,23 @@ pub fn check_lines(
|
||||
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
|
||||
let enforce_noqa = settings.enabled.contains(&CheckCode::M001);
|
||||
|
||||
let mut noqa_directives: BTreeMap<usize, (Directive, Vec<&str>)> = BTreeMap::new();
|
||||
|
||||
let mut noqa_directives: IntMap<usize, (Directive, Vec<&str>)> = IntMap::default();
|
||||
let mut line_checks = vec![];
|
||||
let mut ignored = vec![];
|
||||
|
||||
checks.sort_by_key(|check| check.location);
|
||||
let mut checks_iter = checks.iter().enumerate().peekable();
|
||||
if let Some((_index, check)) = checks_iter.peek() {
|
||||
assert!(check.location.row() >= 1);
|
||||
}
|
||||
|
||||
let lines: Vec<&str> = contents.lines().collect();
|
||||
for (lineno, line) in lines.iter().enumerate() {
|
||||
// Grab the noqa (logical) line number for the current (physical) line.
|
||||
// If there are newlines at the end of the file, they won't be represented in
|
||||
// `noqa_line_for`, so fallback to the current line.
|
||||
let noqa_lineno = noqa_line_for
|
||||
.get(lineno)
|
||||
.get(&lineno)
|
||||
.map(|lineno| lineno - 1)
|
||||
.unwrap_or(lineno);
|
||||
|
||||
@@ -90,26 +94,25 @@ pub fn check_lines(
|
||||
}
|
||||
|
||||
// Remove any ignored checks.
|
||||
// TODO(charlie): Only validate checks for the current line.
|
||||
for (index, check) in checks.iter().enumerate() {
|
||||
if check.location.row() == lineno + 1 {
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
while let Some((index, check)) =
|
||||
checks_iter.next_if(|(_index, check)| check.location.row() == lineno + 1)
|
||||
{
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
|
||||
match noqa {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index)
|
||||
}
|
||||
(Directive::Codes(_, _, codes), matches) => {
|
||||
if codes.contains(&check.kind.code().as_ref()) {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index);
|
||||
}
|
||||
}
|
||||
(Directive::None, _) => {}
|
||||
match noqa {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index)
|
||||
}
|
||||
(Directive::Codes(_, _, codes), matches) => {
|
||||
if codes.contains(&check.kind.code().as_ref()) {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index);
|
||||
}
|
||||
}
|
||||
(Directive::None, _) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +156,7 @@ pub fn check_lines(
|
||||
if let Some(line) = lines.last() {
|
||||
let lineno = lines.len() - 1;
|
||||
let noqa_lineno = noqa_line_for
|
||||
.get(lineno)
|
||||
.get(&lineno)
|
||||
.map(|lineno| lineno - 1)
|
||||
.unwrap_or(lineno);
|
||||
|
||||
@@ -257,6 +260,8 @@ pub fn check_lines(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nohash_hasher::IntMap;
|
||||
|
||||
use super::check_lines;
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
@@ -265,7 +270,7 @@ mod tests {
|
||||
#[test]
|
||||
fn e501_non_ascii_char() {
|
||||
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
|
||||
let noqa_line_for: Vec<usize> = vec![1];
|
||||
let noqa_line_for: IntMap<usize, usize> = Default::default();
|
||||
let check_with_max_line_length = |line_length: usize| {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
check_lines(
|
||||
|
||||
@@ -84,6 +84,8 @@ pub enum CheckCode {
|
||||
B006,
|
||||
B007,
|
||||
B008,
|
||||
B009,
|
||||
B010,
|
||||
B011,
|
||||
B013,
|
||||
B014,
|
||||
@@ -203,6 +205,8 @@ pub enum CheckCode {
|
||||
N816,
|
||||
N817,
|
||||
N818,
|
||||
// isort
|
||||
I001,
|
||||
// Ruff
|
||||
RUF001,
|
||||
RUF002,
|
||||
@@ -215,6 +219,7 @@ pub enum CheckCode {
|
||||
pub enum CheckCategory {
|
||||
Pyflakes,
|
||||
Pycodestyle,
|
||||
Isort,
|
||||
Pydocstyle,
|
||||
Pyupgrade,
|
||||
PEP8Naming,
|
||||
@@ -233,6 +238,7 @@ impl CheckCategory {
|
||||
match self {
|
||||
CheckCategory::Pycodestyle => "pycodestyle",
|
||||
CheckCategory::Pyflakes => "Pyflakes",
|
||||
CheckCategory::Isort => "isort",
|
||||
CheckCategory::Flake8Builtins => "flake8-builtins",
|
||||
CheckCategory::Flake8Bugbear => "flake8-bugbear",
|
||||
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
|
||||
@@ -251,6 +257,7 @@ impl CheckCategory {
|
||||
match self {
|
||||
CheckCategory::Pycodestyle => Some("https://pypi.org/project/pycodestyle/2.9.1/"),
|
||||
CheckCategory::Pyflakes => Some("https://pypi.org/project/pyflakes/2.5.0/"),
|
||||
CheckCategory::Isort => Some("https://pypi.org/project/isort/5.10.1/"),
|
||||
CheckCategory::Flake8Builtins => {
|
||||
Some("https://pypi.org/project/flake8-builtins/2.0.1/")
|
||||
}
|
||||
@@ -280,6 +287,7 @@ pub enum LintSource {
|
||||
FileSystem,
|
||||
Lines,
|
||||
Tokens,
|
||||
Imports,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -349,6 +357,8 @@ pub enum CheckKind {
|
||||
MutableArgumentDefault,
|
||||
UnusedLoopControlVariable(String),
|
||||
FunctionCallArgumentDefault,
|
||||
GetAttrWithConstant,
|
||||
SetAttrWithConstant,
|
||||
DoNotAssertFalse,
|
||||
RedundantTupleInExceptionHandler(String),
|
||||
DuplicateHandlerException(Vec<String>),
|
||||
@@ -468,6 +478,8 @@ pub enum CheckKind {
|
||||
MixedCaseVariableInGlobalScope(String),
|
||||
CamelcaseImportedAsAcronym(String, String),
|
||||
ErrorSuffixOnExceptionName(String),
|
||||
// isort
|
||||
UnsortedImports,
|
||||
// Ruff
|
||||
AmbiguousUnicodeCharacterString(char, char),
|
||||
AmbiguousUnicodeCharacterDocstring(char, char),
|
||||
@@ -493,6 +505,7 @@ impl CheckCode {
|
||||
| CheckCode::RUF002
|
||||
| CheckCode::RUF003 => &LintSource::Tokens,
|
||||
CheckCode::E902 => &LintSource::FileSystem,
|
||||
CheckCode::I001 => &LintSource::Imports,
|
||||
_ => &LintSource::AST,
|
||||
}
|
||||
}
|
||||
@@ -561,6 +574,8 @@ impl CheckCode {
|
||||
CheckCode::B006 => CheckKind::MutableArgumentDefault,
|
||||
CheckCode::B007 => CheckKind::UnusedLoopControlVariable("i".to_string()),
|
||||
CheckCode::B008 => CheckKind::FunctionCallArgumentDefault,
|
||||
CheckCode::B009 => CheckKind::GetAttrWithConstant,
|
||||
CheckCode::B010 => CheckKind::SetAttrWithConstant,
|
||||
CheckCode::B011 => CheckKind::DoNotAssertFalse,
|
||||
CheckCode::B013 => {
|
||||
CheckKind::RedundantTupleInExceptionHandler("ValueError".to_string())
|
||||
@@ -714,6 +729,8 @@ impl CheckCode {
|
||||
CheckKind::CamelcaseImportedAsAcronym("...".to_string(), "...".to_string())
|
||||
}
|
||||
CheckCode::N818 => CheckKind::ErrorSuffixOnExceptionName("...".to_string()),
|
||||
// isort
|
||||
CheckCode::I001 => CheckKind::UnsortedImports,
|
||||
// Ruff
|
||||
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
|
||||
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
|
||||
@@ -779,6 +796,8 @@ impl CheckCode {
|
||||
CheckCode::B006 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B007 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B008 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B009 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B010 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B011 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B013 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B014 => CheckCategory::Flake8Bugbear,
|
||||
@@ -891,6 +910,7 @@ impl CheckCode {
|
||||
CheckCode::N816 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N817 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N818 => CheckCategory::PEP8Naming,
|
||||
CheckCode::I001 => CheckCategory::Isort,
|
||||
CheckCode::RUF001 => CheckCategory::Ruff,
|
||||
CheckCode::RUF002 => CheckCategory::Ruff,
|
||||
CheckCode::RUF003 => CheckCategory::Ruff,
|
||||
@@ -961,6 +981,8 @@ impl CheckKind {
|
||||
CheckKind::MutableArgumentDefault => &CheckCode::B006,
|
||||
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
|
||||
CheckKind::FunctionCallArgumentDefault => &CheckCode::B008,
|
||||
CheckKind::GetAttrWithConstant => &CheckCode::B009,
|
||||
CheckKind::SetAttrWithConstant => &CheckCode::B010,
|
||||
CheckKind::DoNotAssertFalse => &CheckCode::B011,
|
||||
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
|
||||
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
|
||||
@@ -1080,6 +1102,8 @@ impl CheckKind {
|
||||
CheckKind::MixedCaseVariableInGlobalScope(..) => &CheckCode::N816,
|
||||
CheckKind::CamelcaseImportedAsAcronym(..) => &CheckCode::N817,
|
||||
CheckKind::ErrorSuffixOnExceptionName(..) => &CheckCode::N818,
|
||||
// isort
|
||||
CheckKind::UnsortedImports => &CheckCode::I001,
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
|
||||
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
|
||||
@@ -1264,6 +1288,14 @@ impl CheckKind {
|
||||
CheckKind::FunctionCallArgumentDefault => {
|
||||
"Do not perform function calls in argument defaults.".to_string()
|
||||
}
|
||||
CheckKind::GetAttrWithConstant => "Do not call `getattr` with a constant attribute \
|
||||
value, it is not any safer than normal property \
|
||||
access."
|
||||
.to_string(),
|
||||
CheckKind::SetAttrWithConstant => "Do not call `setattr` with a constant attribute \
|
||||
value, it is not any safer than normal property \
|
||||
access."
|
||||
.to_string(),
|
||||
CheckKind::DoNotAssertFalse => "Do not `assert False` (`python -O` removes these \
|
||||
calls), raise `AssertionError()`"
|
||||
.to_string(),
|
||||
@@ -1635,6 +1667,8 @@ impl CheckKind {
|
||||
CheckKind::PEP3120UnnecessaryCodingComment => {
|
||||
"utf-8 encoding declaration is unnecessary".to_string()
|
||||
}
|
||||
// isort
|
||||
CheckKind::UnsortedImports => "Import block is un-sorted or un-formatted".to_string(),
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => {
|
||||
format!(
|
||||
@@ -1709,6 +1743,7 @@ impl CheckKind {
|
||||
| CheckKind::DeprecatedUnittestAlias(_, _)
|
||||
| CheckKind::DoNotAssertFalse
|
||||
| CheckKind::DuplicateHandlerException(_)
|
||||
| CheckKind::GetAttrWithConstant
|
||||
| CheckKind::IsLiteral
|
||||
| CheckKind::NewLineAfterLastParagraph
|
||||
| CheckKind::NewLineAfterSectionName(_)
|
||||
@@ -1740,12 +1775,13 @@ impl CheckKind {
|
||||
| CheckKind::UnnecessaryGeneratorSet
|
||||
| CheckKind::UnnecessaryLRUCacheParams
|
||||
| CheckKind::UnnecessaryListCall
|
||||
| CheckKind::UnnecessaryListComprehensionSet
|
||||
| CheckKind::UnnecessaryListComprehensionDict
|
||||
| CheckKind::UnnecessaryListComprehensionSet
|
||||
| CheckKind::UnnecessaryLiteralDict(_)
|
||||
| CheckKind::UnnecessaryLiteralSet(_)
|
||||
| CheckKind::UnnecessaryLiteralWithinListCall(_)
|
||||
| CheckKind::UnnecessaryLiteralWithinTupleCall(_)
|
||||
| CheckKind::UnsortedImports
|
||||
| CheckKind::UnusedImport(_, false)
|
||||
| CheckKind::UnusedLoopControlVariable(_)
|
||||
| CheckKind::UnusedNOQA(_)
|
||||
|
||||
@@ -43,7 +43,9 @@ pub enum CheckCodePrefix {
|
||||
B006,
|
||||
B007,
|
||||
B008,
|
||||
B009,
|
||||
B01,
|
||||
B010,
|
||||
B011,
|
||||
B013,
|
||||
B014,
|
||||
@@ -202,6 +204,10 @@ pub enum CheckCodePrefix {
|
||||
F9,
|
||||
F90,
|
||||
F901,
|
||||
I,
|
||||
I0,
|
||||
I00,
|
||||
I001,
|
||||
M,
|
||||
M0,
|
||||
M00,
|
||||
@@ -336,6 +342,8 @@ impl CheckCodePrefix {
|
||||
CheckCode::B006,
|
||||
CheckCode::B007,
|
||||
CheckCode::B008,
|
||||
CheckCode::B009,
|
||||
CheckCode::B010,
|
||||
CheckCode::B011,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
@@ -354,6 +362,8 @@ impl CheckCodePrefix {
|
||||
CheckCode::B006,
|
||||
CheckCode::B007,
|
||||
CheckCode::B008,
|
||||
CheckCode::B009,
|
||||
CheckCode::B010,
|
||||
CheckCode::B011,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
@@ -372,6 +382,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::B006,
|
||||
CheckCode::B007,
|
||||
CheckCode::B008,
|
||||
CheckCode::B009,
|
||||
],
|
||||
CheckCodePrefix::B002 => vec![CheckCode::B002],
|
||||
CheckCodePrefix::B003 => vec![CheckCode::B003],
|
||||
@@ -380,7 +391,9 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B006 => vec![CheckCode::B006],
|
||||
CheckCodePrefix::B007 => vec![CheckCode::B007],
|
||||
CheckCodePrefix::B008 => vec![CheckCode::B008],
|
||||
CheckCodePrefix::B009 => vec![CheckCode::B009],
|
||||
CheckCodePrefix::B01 => vec![
|
||||
CheckCode::B010,
|
||||
CheckCode::B011,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
@@ -389,6 +402,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::B017,
|
||||
CheckCode::B018,
|
||||
],
|
||||
CheckCodePrefix::B010 => vec![CheckCode::B010],
|
||||
CheckCodePrefix::B011 => vec![CheckCode::B011],
|
||||
CheckCodePrefix::B013 => vec![CheckCode::B013],
|
||||
CheckCodePrefix::B014 => vec![CheckCode::B014],
|
||||
@@ -847,6 +861,10 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::F9 => vec![CheckCode::F901],
|
||||
CheckCodePrefix::F90 => vec![CheckCode::F901],
|
||||
CheckCodePrefix::F901 => vec![CheckCode::F901],
|
||||
CheckCodePrefix::I => vec![CheckCode::I001],
|
||||
CheckCodePrefix::I0 => vec![CheckCode::I001],
|
||||
CheckCodePrefix::I00 => vec![CheckCode::I001],
|
||||
CheckCodePrefix::I001 => vec![CheckCode::I001],
|
||||
CheckCodePrefix::M => vec![CheckCode::M001],
|
||||
CheckCodePrefix::M0 => vec![CheckCode::M001],
|
||||
CheckCodePrefix::M00 => vec![CheckCode::M001],
|
||||
@@ -1051,7 +1069,9 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B006 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B007 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B008 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B009 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B01 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::B010 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B011 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B013 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B014 => PrefixSpecificity::Explicit,
|
||||
@@ -1210,6 +1230,10 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::F9 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::F90 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::F901 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::I => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::I0 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::I00 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::I001 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::M => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::M0 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::M00 => PrefixSpecificity::Tens,
|
||||
|
||||
206
src/directives.rs
Normal file
206
src/directives.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
//! Extract `# noqa` and `# isort: skip` directives from tokenized source.
|
||||
|
||||
use bitflags::bitflags;
|
||||
use nohash_hasher::{IntMap, IntSet};
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_parser::lexer::{LexResult, Tok};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::LintSource;
|
||||
use crate::{Settings, SourceCodeLocator};
|
||||
|
||||
bitflags! {
|
||||
pub struct Flags: u32 {
|
||||
const NOQA = 0b00000001;
|
||||
const ISORT = 0b00000010;
|
||||
}
|
||||
}
|
||||
|
||||
impl Flags {
|
||||
pub fn from_settings(settings: &Settings) -> Self {
|
||||
if settings
|
||||
.enabled
|
||||
.iter()
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::Imports))
|
||||
{
|
||||
Flags::NOQA | Flags::ISORT
|
||||
} else {
|
||||
Flags::NOQA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Directives {
|
||||
pub noqa_line_for: IntMap<usize, usize>,
|
||||
pub isort_exclusions: IntSet<usize>,
|
||||
}
|
||||
|
||||
pub fn extract_directives(
|
||||
lxr: &[LexResult],
|
||||
locator: &SourceCodeLocator,
|
||||
flags: &Flags,
|
||||
) -> Directives {
|
||||
Directives {
|
||||
noqa_line_for: if flags.contains(Flags::NOQA) {
|
||||
extract_noqa_line_for(lxr)
|
||||
} else {
|
||||
Default::default()
|
||||
},
|
||||
isort_exclusions: if flags.contains(Flags::ISORT) {
|
||||
extract_isort_exclusions(lxr, locator)
|
||||
} else {
|
||||
Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract a mapping from logical line to noqa line.
|
||||
pub fn extract_noqa_line_for(lxr: &[LexResult]) -> IntMap<usize, usize> {
|
||||
let mut noqa_line_for: IntMap<usize, usize> = IntMap::default();
|
||||
for (start, tok, end) in lxr.iter().flatten() {
|
||||
if matches!(tok, Tok::EndOfFile) {
|
||||
break;
|
||||
}
|
||||
// For multi-line strings, we expect `noqa` directives on the last line of the
|
||||
// string.
|
||||
if matches!(tok, Tok::String { .. }) && end.row() > start.row() {
|
||||
for i in start.row()..end.row() {
|
||||
noqa_line_for.insert(i, end.row());
|
||||
}
|
||||
}
|
||||
}
|
||||
noqa_line_for
|
||||
}
|
||||
|
||||
/// Extract a set of lines over which to disable isort.
|
||||
pub fn extract_isort_exclusions(lxr: &[LexResult], locator: &SourceCodeLocator) -> IntSet<usize> {
|
||||
let mut exclusions: IntSet<usize> = IntSet::default();
|
||||
let mut off: Option<&Location> = None;
|
||||
for (start, tok, end) in lxr.iter().flatten() {
|
||||
// TODO(charlie): Modify RustPython to include the comment text in the token.
|
||||
if matches!(tok, Tok::Comment) {
|
||||
let comment_text = locator.slice_source_code_range(&Range {
|
||||
location: *start,
|
||||
end_location: *end,
|
||||
});
|
||||
if off.is_some() {
|
||||
if comment_text == "# isort: on" {
|
||||
if let Some(start) = off {
|
||||
for row in start.row() + 1..=end.row() {
|
||||
exclusions.insert(row);
|
||||
}
|
||||
}
|
||||
off = None;
|
||||
}
|
||||
} else {
|
||||
if comment_text.contains("isort: skip") || comment_text.contains("isort:skip") {
|
||||
exclusions.insert(start.row());
|
||||
} else if comment_text == "# isort: off" {
|
||||
off = Some(start);
|
||||
}
|
||||
}
|
||||
} else if matches!(tok, Tok::EndOfFile) {
|
||||
if let Some(start) = off {
|
||||
for row in start.row() + 1..=end.row() {
|
||||
exclusions.insert(row);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
exclusions
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use nohash_hasher::IntMap;
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use crate::directives::extract_noqa_line_for;
|
||||
|
||||
#[test]
|
||||
fn extraction() -> Result<()> {
|
||||
let empty: IntMap<usize, usize> = Default::default();
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"
|
||||
x = 1
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = 2
|
||||
z = x + 1
|
||||
",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
|
||||
y = 2
|
||||
z = x + 1
|
||||
",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = '''abc
|
||||
def
|
||||
ghi
|
||||
'''
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
extract_noqa_line_for(&lxr),
|
||||
IntMap::from_iter([(1, 4), (2, 4), (3, 4)])
|
||||
);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = '''abc
|
||||
def
|
||||
ghi
|
||||
'''
|
||||
z = 2",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
extract_noqa_line_for(&lxr),
|
||||
IntMap::from_iter([(2, 5), (3, 5), (4, 5)])
|
||||
);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = '''abc
|
||||
def
|
||||
ghi
|
||||
'''",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
extract_noqa_line_for(&lxr),
|
||||
IntMap::from_iter([(2, 5), (3, 5), (4, 5)])
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_ast::{Expr, Location};
|
||||
use rustpython_ast::{Located, Location};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
@@ -24,9 +24,9 @@ pub fn leading_space(line: &str) -> String {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Extract the leading indentation from a docstring.
|
||||
pub fn indentation<'a>(checker: &'a Checker, docstring: &Expr) -> String {
|
||||
let range = Range::from_located(docstring);
|
||||
/// Extract the leading indentation from a line.
|
||||
pub fn indentation<'a, T>(checker: &'a Checker, located: &Located<T>) -> String {
|
||||
let range = Range::from_located(located);
|
||||
checker
|
||||
.locator
|
||||
.slice_source_code_range(&Range {
|
||||
|
||||
@@ -6,32 +6,15 @@ mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::linter::tokenize;
|
||||
use crate::{flake8_annotations, fs, linter, noqa, Settings, SourceCodeLocator};
|
||||
|
||||
fn check_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
linter::check_path(
|
||||
path,
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&noqa_line_for,
|
||||
settings,
|
||||
autofix,
|
||||
)
|
||||
}
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::{flake8_annotations, Settings};
|
||||
|
||||
#[test]
|
||||
fn defaults() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_annotations/annotation_presence.py"),
|
||||
&Settings {
|
||||
..Settings::for_rules(vec![
|
||||
@@ -57,7 +40,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn suppress_dummy_args() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_annotations/suppress_dummy_args.py"),
|
||||
&Settings {
|
||||
flake8_annotations: flake8_annotations::settings::Settings {
|
||||
@@ -83,7 +66,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn mypy_init_return() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_annotations/mypy_init_return.py"),
|
||||
&Settings {
|
||||
flake8_annotations: flake8_annotations::settings::Settings {
|
||||
@@ -109,7 +92,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn suppress_none_returning() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_annotations/suppress_none_returning.py"),
|
||||
&Settings {
|
||||
flake8_annotations: flake8_annotations::settings::Settings {
|
||||
@@ -135,7 +118,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn allow_star_arg_any() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_annotations/allow_star_arg_any.py"),
|
||||
&Settings {
|
||||
flake8_annotations: flake8_annotations::settings::Settings {
|
||||
|
||||
5
src/flake8_bugbear/constants.rs
Normal file
5
src/flake8_bugbear/constants.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
pub static IDENTIFIER_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap());
|
||||
@@ -1 +1,2 @@
|
||||
mod constants;
|
||||
pub mod plugins;
|
||||
|
||||
53
src/flake8_bugbear/plugins/getattr_with_constant.rs
Normal file
53
src/flake8_bugbear/plugins/getattr_with_constant.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::flake8_bugbear::constants::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
|
||||
fn attribute(value: &Expr, attr: &str) -> Expr {
|
||||
Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
ExprKind::Attribute {
|
||||
value: Box::new(value.clone()),
|
||||
attr: attr.to_string(),
|
||||
ctx: ExprContext::Load,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "getattr" {
|
||||
if let [obj, arg] = args {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} = &arg.node
|
||||
{
|
||||
if IDENTIFIER_REGEX.is_match(value) && !KWLIST.contains(&value.as_str()) {
|
||||
let mut check =
|
||||
Check::new(CheckKind::GetAttrWithConstant, Range::from_located(expr));
|
||||
if checker.patch() {
|
||||
let mut generator = SourceGenerator::new();
|
||||
if let Ok(()) = generator.unparse_expr(&attribute(obj, value), 0) {
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,10 @@ pub use assignment_to_os_environ::assignment_to_os_environ;
|
||||
pub use cannot_raise_literal::cannot_raise_literal;
|
||||
pub use duplicate_exceptions::{duplicate_exceptions, duplicate_handler_exceptions};
|
||||
pub use function_call_argument_default::function_call_argument_default;
|
||||
pub use getattr_with_constant::getattr_with_constant;
|
||||
pub use mutable_argument_default::mutable_argument_default;
|
||||
pub use redundant_tuple_in_exception_handler::redundant_tuple_in_exception_handler;
|
||||
pub use setattr_with_constant::setattr_with_constant;
|
||||
pub use star_arg_unpacking_after_keyword_arg::star_arg_unpacking_after_keyword_arg;
|
||||
pub use strip_with_multi_characters::strip_with_multi_characters;
|
||||
pub use unary_prefix_increment::unary_prefix_increment;
|
||||
@@ -20,8 +22,10 @@ mod assignment_to_os_environ;
|
||||
mod cannot_raise_literal;
|
||||
mod duplicate_exceptions;
|
||||
mod function_call_argument_default;
|
||||
mod getattr_with_constant;
|
||||
mod mutable_argument_default;
|
||||
mod redundant_tuple_in_exception_handler;
|
||||
mod setattr_with_constant;
|
||||
mod star_arg_unpacking_after_keyword_arg;
|
||||
mod strip_with_multi_characters;
|
||||
mod unary_prefix_increment;
|
||||
|
||||
29
src/flake8_bugbear/plugins/setattr_with_constant.rs
Normal file
29
src/flake8_bugbear/plugins/setattr_with_constant.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::flake8_bugbear::constants::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
|
||||
/// B010
|
||||
pub fn setattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "setattr" {
|
||||
if let [_, arg, _] = args {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} = &arg.node
|
||||
{
|
||||
if IDENTIFIER_REGEX.is_match(value) && !KWLIST.contains(&value.as_str()) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SetAttrWithConstant,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,30 +6,13 @@ mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::checks::CheckCode;
|
||||
use crate::flake8_quotes::settings::Quote;
|
||||
use crate::linter::tokenize;
|
||||
use crate::{flake8_quotes, fs, linter, noqa, Settings, SourceCodeLocator};
|
||||
|
||||
fn check_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
linter::check_path(
|
||||
path,
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&noqa_line_for,
|
||||
settings,
|
||||
autofix,
|
||||
)
|
||||
}
|
||||
use crate::linter::test_path;
|
||||
use crate::{flake8_quotes, Settings};
|
||||
|
||||
#[test_case(Path::new("doubles.py"))]
|
||||
#[test_case(Path::new("doubles_escaped.py"))]
|
||||
@@ -38,7 +21,7 @@ mod tests {
|
||||
#[test_case(Path::new("doubles_wrapped.py"))]
|
||||
fn doubles(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("doubles_{}", path.to_string_lossy());
|
||||
let mut checks = check_path(
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_quotes")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
@@ -70,7 +53,7 @@ mod tests {
|
||||
#[test_case(Path::new("singles_wrapped.py"))]
|
||||
fn singles(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("singles_{}", path.to_string_lossy());
|
||||
let mut checks = check_path(
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_quotes")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
@@ -107,7 +90,7 @@ mod tests {
|
||||
#[test_case(Path::new("docstring_singles_function.py"))]
|
||||
fn double_docstring(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("double_docstring_{}", path.to_string_lossy());
|
||||
let mut checks = check_path(
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_quotes")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
@@ -144,7 +127,7 @@ mod tests {
|
||||
#[test_case(Path::new("docstring_singles_function.py"))]
|
||||
fn single_docstring(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("single_docstring_{}", path.to_string_lossy());
|
||||
let mut checks = check_path(
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_quotes")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
|
||||
68
src/isort/categorize.rs
Normal file
68
src/isort/categorize.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::python::sys::KNOWN_STANDARD_LIBRARY;
|
||||
|
||||
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone)]
|
||||
pub enum ImportType {
|
||||
Future,
|
||||
StandardLibrary,
|
||||
ThirdParty,
|
||||
FirstParty,
|
||||
}
|
||||
|
||||
pub fn categorize(
|
||||
module_base: &str,
|
||||
src: &[PathBuf],
|
||||
known_first_party: &BTreeSet<String>,
|
||||
known_third_party: &BTreeSet<String>,
|
||||
extra_standard_library: &BTreeSet<String>,
|
||||
) -> ImportType {
|
||||
if known_first_party.contains(module_base) {
|
||||
ImportType::FirstParty
|
||||
} else if known_third_party.contains(module_base) {
|
||||
ImportType::ThirdParty
|
||||
} else if extra_standard_library.contains(module_base) {
|
||||
ImportType::StandardLibrary
|
||||
} else if let Some(import_type) = STATIC_CLASSIFICATIONS.get(module_base) {
|
||||
import_type.clone()
|
||||
} else if KNOWN_STANDARD_LIBRARY.contains(module_base) {
|
||||
ImportType::StandardLibrary
|
||||
} else {
|
||||
if find_local(src, module_base) {
|
||||
ImportType::FirstParty
|
||||
} else {
|
||||
ImportType::ThirdParty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static STATIC_CLASSIFICATIONS: Lazy<BTreeMap<&'static str, ImportType>> = Lazy::new(|| {
|
||||
BTreeMap::from([
|
||||
("__future__", ImportType::Future),
|
||||
("__main__", ImportType::FirstParty),
|
||||
// Force `disutils` to be considered third-party.
|
||||
("disutils", ImportType::ThirdParty),
|
||||
// Relative imports (e.g., `from . import module`).
|
||||
("", ImportType::FirstParty),
|
||||
])
|
||||
});
|
||||
|
||||
fn find_local(paths: &[PathBuf], base: &str) -> bool {
|
||||
for path in paths {
|
||||
if let Ok(metadata) = fs::metadata(path.join(base)) {
|
||||
if metadata.is_dir() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Ok(metadata) = fs::metadata(path.join(format!("{base}.py"))) {
|
||||
if metadata.is_file() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
240
src/isort/mod.rs
Normal file
240
src/isort/mod.rs
Normal file
@@ -0,0 +1,240 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use ropey::RopeBuilder;
|
||||
use rustpython_ast::{Stmt, StmtKind};
|
||||
|
||||
use crate::isort::categorize::{categorize, ImportType};
|
||||
use crate::isort::types::{AliasData, ImportBlock, ImportFromData, Importable};
|
||||
|
||||
mod categorize;
|
||||
pub mod plugins;
|
||||
pub mod settings;
|
||||
pub mod track;
|
||||
mod types;
|
||||
|
||||
// Hard-code four-space indentation for the imports themselves, to match Black.
|
||||
const INDENT: &str = " ";
|
||||
|
||||
fn normalize_imports<'a>(imports: &'a [&'a Stmt]) -> ImportBlock<'a> {
|
||||
let mut block: ImportBlock = Default::default();
|
||||
for import in imports {
|
||||
match &import.node {
|
||||
StmtKind::Import { names } => {
|
||||
for name in names {
|
||||
block.import.insert(AliasData {
|
||||
name: &name.node.name,
|
||||
asname: &name.node.asname,
|
||||
});
|
||||
}
|
||||
}
|
||||
StmtKind::ImportFrom {
|
||||
module,
|
||||
names,
|
||||
level,
|
||||
} => {
|
||||
let targets = block
|
||||
.import_from
|
||||
.entry(ImportFromData { module, level })
|
||||
.or_default();
|
||||
for name in names {
|
||||
targets.insert(AliasData {
|
||||
name: &name.node.name,
|
||||
asname: &name.node.asname,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => unreachable!("Expected StmtKind::Import | StmtKind::ImportFrom"),
|
||||
}
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
fn categorize_imports<'a>(
|
||||
block: ImportBlock<'a>,
|
||||
src: &[PathBuf],
|
||||
known_first_party: &BTreeSet<String>,
|
||||
known_third_party: &BTreeSet<String>,
|
||||
extra_standard_library: &BTreeSet<String>,
|
||||
) -> BTreeMap<ImportType, ImportBlock<'a>> {
|
||||
let mut block_by_type: BTreeMap<ImportType, ImportBlock> = Default::default();
|
||||
// Categorize `StmtKind::Import`.
|
||||
for alias in block.import {
|
||||
let import_type = categorize(
|
||||
&alias.module_base(),
|
||||
src,
|
||||
known_first_party,
|
||||
known_third_party,
|
||||
extra_standard_library,
|
||||
);
|
||||
block_by_type
|
||||
.entry(import_type)
|
||||
.or_default()
|
||||
.import
|
||||
.insert(alias);
|
||||
}
|
||||
// Categorize `StmtKind::ImportFrom`.
|
||||
for (import_from, aliases) in block.import_from {
|
||||
let classification = categorize(
|
||||
&import_from.module_base(),
|
||||
src,
|
||||
known_first_party,
|
||||
known_third_party,
|
||||
extra_standard_library,
|
||||
);
|
||||
block_by_type
|
||||
.entry(classification)
|
||||
.or_default()
|
||||
.import_from
|
||||
.insert(import_from, aliases);
|
||||
}
|
||||
block_by_type
|
||||
}
|
||||
|
||||
pub fn sort_imports(
|
||||
block: Vec<&Stmt>,
|
||||
line_length: &usize,
|
||||
src: &[PathBuf],
|
||||
known_first_party: &BTreeSet<String>,
|
||||
known_third_party: &BTreeSet<String>,
|
||||
extra_standard_library: &BTreeSet<String>,
|
||||
) -> String {
|
||||
// Normalize imports (i.e., deduplicate, aggregate `from` imports).
|
||||
let block = normalize_imports(&block);
|
||||
|
||||
// Categorize by type (e.g., first-party vs. third-party).
|
||||
let block_by_type = categorize_imports(
|
||||
block,
|
||||
src,
|
||||
known_first_party,
|
||||
known_third_party,
|
||||
extra_standard_library,
|
||||
);
|
||||
|
||||
// Generate replacement source code.
|
||||
let mut output = RopeBuilder::new();
|
||||
let mut first_block = true;
|
||||
for import_type in [
|
||||
ImportType::Future,
|
||||
ImportType::StandardLibrary,
|
||||
ImportType::ThirdParty,
|
||||
ImportType::FirstParty,
|
||||
] {
|
||||
if let Some(import_block) = block_by_type.get(&import_type) {
|
||||
// Add a blank line between every section.
|
||||
if !first_block {
|
||||
output.append("\n");
|
||||
} else {
|
||||
first_block = false;
|
||||
}
|
||||
|
||||
// Format `StmtKind::Import` statements.
|
||||
for AliasData { name, asname } in import_block.import.iter() {
|
||||
if let Some(asname) = asname {
|
||||
output.append(&format!("import {} as {}\n", name, asname));
|
||||
} else {
|
||||
output.append(&format!("import {}\n", name));
|
||||
}
|
||||
}
|
||||
|
||||
// Format `StmtKind::ImportFrom` statements.
|
||||
for (import_from, aliases) in import_block.import_from.iter() {
|
||||
let prelude: String = format!("from {} import ", import_from.module_name());
|
||||
let members: Vec<String> = aliases
|
||||
.iter()
|
||||
.map(|AliasData { name, asname }| {
|
||||
if let Some(asname) = asname {
|
||||
format!("{} as {}", name, asname)
|
||||
} else {
|
||||
name.to_string()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Can we fit the import on a single line?
|
||||
let expected_len: usize =
|
||||
// `from base import `
|
||||
prelude.len()
|
||||
// `member( as alias)?`
|
||||
+ members.iter().map(|part| part.len()).sum::<usize>()
|
||||
// `, `
|
||||
+ 2 * (members.len() - 1);
|
||||
|
||||
if expected_len <= *line_length {
|
||||
// `from base import `
|
||||
output.append(&prelude);
|
||||
// `member( as alias)?(, )?`
|
||||
for (index, part) in members.into_iter().enumerate() {
|
||||
if index > 0 {
|
||||
output.append(", ");
|
||||
}
|
||||
output.append(&part);
|
||||
}
|
||||
// `\n`
|
||||
output.append("\n");
|
||||
} else {
|
||||
// `from base import (\n`
|
||||
output.append(&prelude);
|
||||
output.append("(");
|
||||
output.append("\n");
|
||||
|
||||
// ` member( as alias)?,\n`
|
||||
for part in members {
|
||||
output.append(INDENT);
|
||||
output.append(&part);
|
||||
output.append(",");
|
||||
output.append("\n");
|
||||
}
|
||||
|
||||
// `)\n`
|
||||
output.append(")");
|
||||
output.append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
output.finish().to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::Settings;
|
||||
|
||||
#[test_case(Path::new("combine_import_froms.py"))]
|
||||
#[test_case(Path::new("deduplicate_imports.py"))]
|
||||
#[test_case(Path::new("fit_line_length.py"))]
|
||||
#[test_case(Path::new("import_from_after_import.py"))]
|
||||
#[test_case(Path::new("leading_prefix.py"))]
|
||||
#[test_case(Path::new("no_reorder_within_section.py"))]
|
||||
#[test_case(Path::new("preserve_indentation.py"))]
|
||||
#[test_case(Path::new("reorder_within_section.py"))]
|
||||
#[test_case(Path::new("separate_first_party_imports.py"))]
|
||||
#[test_case(Path::new("separate_future_imports.py"))]
|
||||
#[test_case(Path::new("separate_third_party_imports.py"))]
|
||||
#[test_case(Path::new("skip.py"))]
|
||||
#[test_case(Path::new("trailing_suffix.py"))]
|
||||
fn isort(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}", path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/isort")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&Settings {
|
||||
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
|
||||
..Settings::for_rule(CheckCode::I001)
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
116
src/isort/plugins.rs
Normal file
116
src/isort/plugins.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use rustpython_ast::{Location, Stmt};
|
||||
use textwrap::{dedent, indent};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::{fixer, Fix};
|
||||
use crate::checks::CheckKind;
|
||||
use crate::docstrings::helpers::leading_space;
|
||||
use crate::isort::sort_imports;
|
||||
use crate::{Check, Settings, SourceCodeLocator};
|
||||
|
||||
fn extract_range(body: &[&Stmt]) -> Range {
|
||||
let location = body.first().unwrap().location;
|
||||
let end_location = body.last().unwrap().end_location.unwrap();
|
||||
Range {
|
||||
location,
|
||||
end_location,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_indentation(body: &[&Stmt], locator: &SourceCodeLocator) -> String {
|
||||
let location = body.first().unwrap().location;
|
||||
let range = Range {
|
||||
location: Location::new(location.row(), 0),
|
||||
end_location: location,
|
||||
};
|
||||
let existing = locator.slice_source_code_range(&range);
|
||||
leading_space(&existing)
|
||||
}
|
||||
|
||||
fn match_leading_content(body: &[&Stmt], locator: &SourceCodeLocator) -> bool {
|
||||
let location = body.first().unwrap().location;
|
||||
let range = Range {
|
||||
location: Location::new(location.row(), 0),
|
||||
end_location: location,
|
||||
};
|
||||
let prefix = locator.slice_source_code_range(&range);
|
||||
prefix.chars().any(|char| !char.is_whitespace())
|
||||
}
|
||||
|
||||
fn match_trailing_content(body: &[&Stmt], locator: &SourceCodeLocator) -> bool {
|
||||
let end_location = body.last().unwrap().end_location.unwrap();
|
||||
let range = Range {
|
||||
location: end_location,
|
||||
end_location: Location::new(end_location.row() + 1, 0),
|
||||
};
|
||||
let suffix = locator.slice_source_code_range(&range);
|
||||
suffix.chars().any(|char| !char.is_whitespace())
|
||||
}
|
||||
|
||||
/// I001
|
||||
pub fn check_imports(
|
||||
body: Vec<&Stmt>,
|
||||
locator: &SourceCodeLocator,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Option<Check> {
|
||||
let range = extract_range(&body);
|
||||
let indentation = extract_indentation(&body, locator);
|
||||
|
||||
// Special-cases: there's leading or trailing content in the import block.
|
||||
let has_leading_content = match_leading_content(&body, locator);
|
||||
let has_trailing_content = match_trailing_content(&body, locator);
|
||||
|
||||
// Generate the sorted import block.
|
||||
let expected = sort_imports(
|
||||
body,
|
||||
&settings.line_length,
|
||||
&settings.src,
|
||||
&settings.isort.known_first_party,
|
||||
&settings.isort.known_third_party,
|
||||
&settings.isort.extra_standard_library,
|
||||
);
|
||||
|
||||
if has_leading_content || has_trailing_content {
|
||||
let mut check = Check::new(CheckKind::UnsortedImports, range);
|
||||
if autofix.patch() {
|
||||
let mut content = String::new();
|
||||
if has_leading_content {
|
||||
content.push('\n');
|
||||
}
|
||||
content.push_str(&indent(&expected, &indentation));
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
// Preserve leading prefix (but put the imports on a new line).
|
||||
if has_leading_content {
|
||||
range.location
|
||||
} else {
|
||||
Location::new(range.location.row(), 0)
|
||||
},
|
||||
// TODO(charlie): Preserve trailing suffixes. Right now, we strip them.
|
||||
Location::new(range.end_location.row() + 1, 0),
|
||||
));
|
||||
}
|
||||
Some(check)
|
||||
} else {
|
||||
// Expand the span the entire range, including leading and trailing space.
|
||||
let range = Range {
|
||||
location: Location::new(range.location.row(), 0),
|
||||
end_location: Location::new(range.end_location.row() + 1, 0),
|
||||
};
|
||||
let actual = dedent(&locator.slice_source_code_range(&range));
|
||||
if actual != expected {
|
||||
let mut check = Check::new(CheckKind::UnsortedImports, range);
|
||||
if autofix.patch() {
|
||||
check.amend(Fix::replacement(
|
||||
indent(&expected, &indentation),
|
||||
range.location,
|
||||
range.end_location,
|
||||
));
|
||||
}
|
||||
Some(check)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/isort/settings.rs
Normal file
32
src/isort/settings.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! Settings for the `isort` plugin.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
pub known_first_party: Option<Vec<String>>,
|
||||
pub known_third_party: Option<Vec<String>>,
|
||||
pub extra_standard_library: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, Default)]
|
||||
pub struct Settings {
|
||||
pub known_first_party: BTreeSet<String>,
|
||||
pub known_third_party: BTreeSet<String>,
|
||||
pub extra_standard_library: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn from_options(options: Options) -> Self {
|
||||
Self {
|
||||
known_first_party: BTreeSet::from_iter(options.known_first_party.unwrap_or_default()),
|
||||
known_third_party: BTreeSet::from_iter(options.known_third_party.unwrap_or_default()),
|
||||
extra_standard_library: BTreeSet::from_iter(
|
||||
options.extra_standard_library.unwrap_or_default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: "from collections import (\n AsyncIterable,\n Awaitable,\n ChainMap,\n Collection,\n MutableMapping,\n MutableSequence,\n)\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: "import os\nimport os as os1\nimport os as os2\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: "import os\nfrom collections import Collection\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 7
|
||||
end_location:
|
||||
row: 2
|
||||
column: 9
|
||||
fix:
|
||||
patch:
|
||||
content: "\nimport os\nimport sys\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 7
|
||||
end_location:
|
||||
row: 3
|
||||
column: 0
|
||||
applied: false
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 5
|
||||
column: 11
|
||||
end_location:
|
||||
row: 6
|
||||
column: 13
|
||||
fix:
|
||||
patch:
|
||||
content: "\n import os\n import sys\n"
|
||||
location:
|
||||
row: 5
|
||||
column: 11
|
||||
end_location:
|
||||
row: 7
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: " import os\n import sys\n"
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 0
|
||||
applied: false
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: " import os\n import sys\n"
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: "import os\nimport sys\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: "import os\nimport sys\n\nimport numpy as np\n\nimport leading_prefix\nfrom leading_prefix import Class\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: "from __future__ import annotations\n\nimport os\nimport sys\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: "import os\nimport sys\n\nimport numpy as np\nimport pandas as pd\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
22
src/isort/snapshots/ruff__isort__tests__skip.py.snap
Normal file
22
src/isort/snapshots/ruff__isort__tests__skip.py.snap
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: "import abc\nimport collections\n"
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
column: 9
|
||||
fix:
|
||||
patch:
|
||||
content: "import os\nimport sys\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 0
|
||||
applied: false
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 5
|
||||
column: 4
|
||||
end_location:
|
||||
row: 6
|
||||
column: 13
|
||||
fix:
|
||||
patch:
|
||||
content: " import os\n import sys\n"
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
211
src/isort/track.rs
Normal file
211
src/isort/track.rs
Normal file
@@ -0,0 +1,211 @@
|
||||
use nohash_hasher::IntSet;
|
||||
use rustpython_ast::{
|
||||
Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Excepthandler,
|
||||
ExcepthandlerKind, Expr, ExprContext, Keyword, MatchCase, Operator, Pattern, Stmt, StmtKind,
|
||||
Unaryop, Withitem,
|
||||
};
|
||||
|
||||
use crate::ast::visitor::Visitor;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ImportTracker<'a> {
|
||||
exclusions: &'a IntSet<usize>,
|
||||
blocks: Vec<Vec<&'a Stmt>>,
|
||||
}
|
||||
|
||||
impl<'a> ImportTracker<'a> {
|
||||
pub fn new(exclusions: &'a IntSet<usize>) -> Self {
|
||||
Self {
|
||||
exclusions,
|
||||
blocks: vec![vec![]],
|
||||
}
|
||||
}
|
||||
|
||||
fn track_import(&mut self, stmt: &'a Stmt) {
|
||||
let index = self.blocks.len() - 1;
|
||||
self.blocks[index].push(stmt);
|
||||
}
|
||||
|
||||
fn finalize(&mut self) {
|
||||
let index = self.blocks.len() - 1;
|
||||
if !self.blocks[index].is_empty() {
|
||||
self.blocks.push(vec![]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_iter(self) -> impl IntoIterator<Item = Vec<&'a Stmt>> {
|
||||
self.blocks.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Visitor<'b> for ImportTracker<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn visit_stmt(&mut self, stmt: &'b Stmt) {
|
||||
// Track imports.
|
||||
if matches!(
|
||||
stmt.node,
|
||||
StmtKind::Import { .. } | StmtKind::ImportFrom { .. }
|
||||
) && !self.exclusions.contains(&stmt.location.row())
|
||||
{
|
||||
self.track_import(stmt);
|
||||
} else {
|
||||
self.finalize();
|
||||
}
|
||||
|
||||
// Track scope.
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { body, .. } => {
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
}
|
||||
StmtKind::AsyncFunctionDef { body, .. } => {
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
}
|
||||
StmtKind::ClassDef { body, .. } => {
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
}
|
||||
StmtKind::For { body, orelse, .. } => {
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
|
||||
for stmt in orelse {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
}
|
||||
StmtKind::AsyncFor { body, orelse, .. } => {
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
|
||||
for stmt in orelse {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
}
|
||||
StmtKind::While { body, orelse, .. } => {
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
|
||||
for stmt in orelse {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
}
|
||||
StmtKind::If { body, orelse, .. } => {
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
|
||||
for stmt in orelse {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
}
|
||||
StmtKind::With { body, .. } => {
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
}
|
||||
StmtKind::AsyncWith { body, .. } => {
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
}
|
||||
StmtKind::Match { cases, .. } => {
|
||||
for match_case in cases {
|
||||
self.visit_match_case(match_case);
|
||||
}
|
||||
}
|
||||
StmtKind::Try {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
} => {
|
||||
for excepthandler in handlers {
|
||||
self.visit_excepthandler(excepthandler)
|
||||
}
|
||||
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
|
||||
for stmt in orelse {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
|
||||
for stmt in finalbody {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_annotation(&mut self, _: &'b Expr) {}
|
||||
|
||||
fn visit_expr(&mut self, _: &'b Expr) {}
|
||||
|
||||
fn visit_constant(&mut self, _: &'b Constant) {}
|
||||
|
||||
fn visit_expr_context(&mut self, _: &'b ExprContext) {}
|
||||
|
||||
fn visit_boolop(&mut self, _: &'b Boolop) {}
|
||||
|
||||
fn visit_operator(&mut self, _: &'b Operator) {}
|
||||
|
||||
fn visit_unaryop(&mut self, _: &'b Unaryop) {}
|
||||
|
||||
fn visit_cmpop(&mut self, _: &'b Cmpop) {}
|
||||
|
||||
fn visit_comprehension(&mut self, _: &'b Comprehension) {}
|
||||
|
||||
fn visit_excepthandler(&mut self, excepthandler: &'b Excepthandler) {
|
||||
let ExcepthandlerKind::ExceptHandler { body, .. } = &excepthandler.node;
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
}
|
||||
|
||||
fn visit_arguments(&mut self, _: &'b Arguments) {}
|
||||
|
||||
fn visit_arg(&mut self, _: &'b Arg) {}
|
||||
|
||||
fn visit_keyword(&mut self, _: &'b Keyword) {}
|
||||
|
||||
fn visit_alias(&mut self, _: &'b Alias) {}
|
||||
|
||||
fn visit_withitem(&mut self, _: &'b Withitem) {}
|
||||
|
||||
fn visit_match_case(&mut self, match_case: &'b MatchCase) {
|
||||
for stmt in &match_case.body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize();
|
||||
}
|
||||
|
||||
fn visit_pattern(&mut self, _: &'b Pattern) {}
|
||||
}
|
||||
55
src/isort/types.rs
Normal file
55
src/isort/types.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct ImportFromData<'a> {
|
||||
pub module: &'a Option<String>,
|
||||
pub level: &'a Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct AliasData<'a> {
|
||||
pub name: &'a str,
|
||||
pub asname: &'a Option<String>,
|
||||
}
|
||||
|
||||
pub trait Importable {
|
||||
fn module_name(&self) -> String;
|
||||
fn module_base(&self) -> String;
|
||||
}
|
||||
|
||||
impl Importable for AliasData<'_> {
|
||||
fn module_name(&self) -> String {
|
||||
self.name.to_string()
|
||||
}
|
||||
|
||||
fn module_base(&self) -> String {
|
||||
self.module_name().split('.').next().unwrap().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Importable for ImportFromData<'_> {
|
||||
fn module_name(&self) -> String {
|
||||
let mut module_name = String::new();
|
||||
if let Some(level) = self.level {
|
||||
if level > &0 {
|
||||
module_name.push_str(&".".repeat(*level));
|
||||
}
|
||||
}
|
||||
if let Some(module) = self.module {
|
||||
module_name.push_str(module);
|
||||
}
|
||||
module_name
|
||||
}
|
||||
|
||||
fn module_base(&self) -> String {
|
||||
self.module_name().split('.').next().unwrap().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ImportBlock<'a> {
|
||||
// Map from (module, level) to `AliasData`.
|
||||
pub import_from: BTreeMap<ImportFromData<'a>, BTreeSet<AliasData<'a>>>,
|
||||
// Set of (name, asname).
|
||||
pub import: BTreeSet<AliasData<'a>>,
|
||||
}
|
||||
13
src/lib.rs
13
src/lib.rs
@@ -17,6 +17,7 @@ mod ast;
|
||||
pub mod autofix;
|
||||
pub mod cache;
|
||||
pub mod check_ast;
|
||||
mod check_imports;
|
||||
mod check_lines;
|
||||
mod check_tokens;
|
||||
pub mod checks;
|
||||
@@ -24,6 +25,7 @@ pub mod checks_gen;
|
||||
pub mod cli;
|
||||
pub mod code_gen;
|
||||
mod cst;
|
||||
mod directives;
|
||||
mod docstrings;
|
||||
pub mod flake8_annotations;
|
||||
mod flake8_bugbear;
|
||||
@@ -32,6 +34,7 @@ mod flake8_comprehensions;
|
||||
mod flake8_print;
|
||||
pub mod flake8_quotes;
|
||||
pub mod fs;
|
||||
mod isort;
|
||||
mod lex;
|
||||
pub mod linter;
|
||||
pub mod logging;
|
||||
@@ -72,8 +75,12 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
|
||||
// Determine the noqa line for every line in the source.
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
&directives::Flags::from_settings(&settings),
|
||||
);
|
||||
|
||||
// Generate checks.
|
||||
let checks = check_path(
|
||||
@@ -81,7 +88,7 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&noqa_line_for,
|
||||
&directives,
|
||||
&settings,
|
||||
&if autofix { Mode::Generate } else { Mode::None },
|
||||
)?;
|
||||
|
||||
136
src/linter.rs
136
src/linter.rs
@@ -16,15 +16,17 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::fixer;
|
||||
use crate::autofix::fixer::fix_file;
|
||||
use crate::check_ast::check_ast;
|
||||
use crate::check_imports::check_imports;
|
||||
use crate::check_lines::check_lines;
|
||||
use crate::check_tokens::check_tokens;
|
||||
use crate::checks::{Check, CheckCode, CheckKind, LintSource};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::directives::Directives;
|
||||
use crate::message::Message;
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::settings::Settings;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::{cache, fs, noqa};
|
||||
use crate::{cache, directives, fs};
|
||||
|
||||
/// Collect tokens up to and including the first error.
|
||||
pub(crate) fn tokenize(contents: &str) -> Vec<LexResult> {
|
||||
@@ -55,7 +57,7 @@ pub(crate) fn check_path(
|
||||
contents: &str,
|
||||
tokens: Vec<LexResult>,
|
||||
locator: &SourceCodeLocator,
|
||||
noqa_line_for: &[usize],
|
||||
directives: &Directives,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Result<Vec<Check>> {
|
||||
@@ -63,23 +65,38 @@ pub(crate) fn check_path(
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
// Run the token-based checks.
|
||||
if settings
|
||||
let use_tokens = settings
|
||||
.enabled
|
||||
.iter()
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::Tokens))
|
||||
{
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::Tokens));
|
||||
if use_tokens {
|
||||
check_tokens(&mut checks, locator, &tokens, settings, autofix);
|
||||
}
|
||||
|
||||
// Run the AST-based checks.
|
||||
if settings
|
||||
let use_ast = settings
|
||||
.enabled
|
||||
.iter()
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::AST))
|
||||
{
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::AST));
|
||||
let use_imports = settings
|
||||
.enabled
|
||||
.iter()
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::Imports));
|
||||
if use_ast || use_imports {
|
||||
match parse_program_tokens(tokens, "<filename>") {
|
||||
Ok(python_ast) => {
|
||||
checks.extend(check_ast(&python_ast, locator, settings, autofix, path))
|
||||
if use_ast {
|
||||
checks.extend(check_ast(&python_ast, locator, settings, autofix, path));
|
||||
}
|
||||
if use_imports {
|
||||
checks.extend(check_imports(
|
||||
&python_ast,
|
||||
locator,
|
||||
&directives.isort_exclusions,
|
||||
settings,
|
||||
autofix,
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(parse_error) => {
|
||||
if settings.enabled.contains(&CheckCode::E999) {
|
||||
@@ -96,7 +113,13 @@ pub(crate) fn check_path(
|
||||
}
|
||||
|
||||
// Run the lines-based checks.
|
||||
check_lines(&mut checks, contents, noqa_line_for, settings, autofix);
|
||||
check_lines(
|
||||
&mut checks,
|
||||
contents,
|
||||
&directives.noqa_line_for,
|
||||
settings,
|
||||
autofix,
|
||||
);
|
||||
|
||||
// Create path ignores.
|
||||
if !checks.is_empty() && !settings.per_file_ignores.is_empty() {
|
||||
@@ -124,8 +147,12 @@ pub fn lint_stdin(
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(stdin);
|
||||
|
||||
// Determine the noqa line for every line in the source.
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
&directives::Flags::from_settings(settings),
|
||||
);
|
||||
|
||||
// Generate checks.
|
||||
let mut checks = check_path(
|
||||
@@ -133,7 +160,7 @@ pub fn lint_stdin(
|
||||
stdin,
|
||||
tokens,
|
||||
&locator,
|
||||
&noqa_line_for,
|
||||
&directives,
|
||||
settings,
|
||||
autofix,
|
||||
)?;
|
||||
@@ -178,8 +205,12 @@ pub fn lint_path(
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
|
||||
// Determine the noqa line for every line in the source.
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
// Determine the noqa and isort exclusions.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
&directives::Flags::from_settings(settings),
|
||||
);
|
||||
|
||||
// Generate checks.
|
||||
let mut checks = check_path(
|
||||
@@ -187,7 +218,7 @@ pub fn lint_path(
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&noqa_line_for,
|
||||
&directives,
|
||||
settings,
|
||||
autofix,
|
||||
)?;
|
||||
@@ -220,8 +251,12 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
|
||||
// Determine the noqa line for every line in the source.
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
&directives::Flags::from_settings(settings),
|
||||
);
|
||||
|
||||
// Generate checks.
|
||||
let checks = check_path(
|
||||
@@ -229,12 +264,12 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&noqa_line_for,
|
||||
&directives,
|
||||
settings,
|
||||
&fixer::Mode::None,
|
||||
)?;
|
||||
|
||||
add_noqa(&checks, &contents, &noqa_line_for, path)
|
||||
add_noqa(&checks, &contents, &directives.noqa_line_for, path)
|
||||
}
|
||||
|
||||
pub fn autoformat_path(path: &Path) -> Result<()> {
|
||||
@@ -253,6 +288,27 @@ pub fn autoformat_path(path: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
&directives::Flags::from_settings(settings),
|
||||
);
|
||||
check_path(
|
||||
path,
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&directives,
|
||||
settings,
|
||||
autofix,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::AsRef;
|
||||
@@ -260,34 +316,12 @@ mod tests {
|
||||
|
||||
use anyhow::Result;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::linter::tokenize;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::{fs, linter, noqa, settings};
|
||||
|
||||
fn check_path(
|
||||
path: &Path,
|
||||
settings: &settings::Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
linter::check_path(
|
||||
path,
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&noqa_line_for,
|
||||
settings,
|
||||
autofix,
|
||||
)
|
||||
}
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::settings;
|
||||
|
||||
#[test_case(CheckCode::A001, Path::new("A001.py"); "A001")]
|
||||
#[test_case(CheckCode::A002, Path::new("A002.py"); "A002")]
|
||||
@@ -299,6 +333,8 @@ mod tests {
|
||||
#[test_case(CheckCode::B006, Path::new("B006_B008.py"); "B006")]
|
||||
#[test_case(CheckCode::B007, Path::new("B007.py"); "B007")]
|
||||
#[test_case(CheckCode::B008, Path::new("B006_B008.py"); "B008")]
|
||||
#[test_case(CheckCode::B009, Path::new("B009_B010.py"); "B009")]
|
||||
#[test_case(CheckCode::B010, Path::new("B009_B010.py"); "B010")]
|
||||
#[test_case(CheckCode::B011, Path::new("B011.py"); "B011")]
|
||||
#[test_case(CheckCode::B013, Path::new("B013.py"); "B013")]
|
||||
#[test_case(CheckCode::B014, Path::new("B014.py"); "B014")]
|
||||
@@ -459,7 +495,7 @@ mod tests {
|
||||
#[test_case(CheckCode::RUF003, Path::new("RUF003.py"); "RUF003")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let mut checks = check_path(
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures").join(path).as_path(),
|
||||
&settings::Settings::for_rule(check_code.clone()),
|
||||
&fixer::Mode::Generate,
|
||||
@@ -471,7 +507,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn f841_dummy_variable_rgx() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/F841.py"),
|
||||
&settings::Settings {
|
||||
dummy_variable_rgx: Regex::new(r"^z$").unwrap(),
|
||||
@@ -486,7 +522,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn m001() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/M001.py"),
|
||||
&settings::Settings::for_rules(vec![CheckCode::M001, CheckCode::E501, CheckCode::F841]),
|
||||
&fixer::Mode::Generate,
|
||||
@@ -498,7 +534,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn init() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/__init__.py"),
|
||||
&settings::Settings::for_rules(vec![CheckCode::F821, CheckCode::F822]),
|
||||
&fixer::Mode::Generate,
|
||||
@@ -510,7 +546,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn future_annotations() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/future_annotations.py"),
|
||||
&settings::Settings::for_rules(vec![CheckCode::F401, CheckCode::F821]),
|
||||
&fixer::Mode::Generate,
|
||||
|
||||
133
src/noqa.rs
133
src/noqa.rs
@@ -3,14 +3,14 @@ use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use nohash_hasher::IntMap;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::lexer::{LexResult, Tok};
|
||||
|
||||
use crate::checks::{Check, CheckCode};
|
||||
|
||||
static NO_QA_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"(?i)(?P<noqa>\s*# noqa(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?)")
|
||||
Regex::new(r"(?P<noqa>\s*# noqa(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?)")
|
||||
.expect("Invalid regex")
|
||||
});
|
||||
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").expect("Invalid regex"));
|
||||
@@ -43,30 +43,21 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_noqa_line_for(lxr: &[LexResult]) -> Vec<usize> {
|
||||
let mut noqa_line_for: Vec<usize> = vec![];
|
||||
for (start, tok, end) in lxr.iter().flatten() {
|
||||
if matches!(tok, Tok::EndOfFile) {
|
||||
break;
|
||||
}
|
||||
// For multi-line strings, we expect `noqa` directives on the last line of the
|
||||
// string. By definition, we can't have multiple multi-line strings on
|
||||
// the same line, so we don't need to verify that we haven't already
|
||||
// traversed past the current line.
|
||||
if matches!(tok, Tok::String { .. }) && end.row() > start.row() {
|
||||
for i in (noqa_line_for.len())..(start.row() - 1) {
|
||||
noqa_line_for.push(i + 1);
|
||||
}
|
||||
noqa_line_for.extend(vec![end.row(); (end.row() + 1) - start.row()]);
|
||||
}
|
||||
}
|
||||
noqa_line_for
|
||||
pub fn add_noqa(
|
||||
checks: &[Check],
|
||||
contents: &str,
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
path: &Path,
|
||||
) -> Result<usize> {
|
||||
let (count, output) = add_noqa_inner(checks, contents, noqa_line_for)?;
|
||||
fs::write(path, output)?;
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn add_noqa_inner(
|
||||
checks: &[Check],
|
||||
contents: &str,
|
||||
noqa_line_for: &[usize],
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
) -> Result<(usize, String)> {
|
||||
let lines: Vec<&str> = contents.lines().collect();
|
||||
let mut matches_by_line: BTreeMap<usize, BTreeSet<&CheckCode>> = BTreeMap::new();
|
||||
@@ -82,7 +73,7 @@ fn add_noqa_inner(
|
||||
// If there are newlines at the end of the file, they won't be represented in
|
||||
// `noqa_line_for`, so fallback to the current line.
|
||||
let noqa_lineno = noqa_line_for
|
||||
.get(lineno)
|
||||
.get(&lineno)
|
||||
.map(|lineno| lineno - 1)
|
||||
.unwrap_or(lineno);
|
||||
|
||||
@@ -120,108 +111,20 @@ fn add_noqa_inner(
|
||||
Ok((count, output))
|
||||
}
|
||||
|
||||
pub fn add_noqa(
|
||||
checks: &[Check],
|
||||
contents: &str,
|
||||
noqa_line_for: &[usize],
|
||||
path: &Path,
|
||||
) -> Result<usize> {
|
||||
let (count, output) = add_noqa_inner(checks, contents, noqa_line_for)?;
|
||||
fs::write(path, output)?;
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use rustpython_parser::ast::Location;
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::noqa::{add_noqa_inner, extract_noqa_line_for};
|
||||
|
||||
#[test]
|
||||
fn extraction() -> Result<()> {
|
||||
let empty: Vec<usize> = Default::default();
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"
|
||||
x = 1
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = 2
|
||||
z = x + 1
|
||||
",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
|
||||
y = 2
|
||||
z = x + 1
|
||||
",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = '''abc
|
||||
def
|
||||
ghi
|
||||
'''
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![4, 4, 4, 4]);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = '''abc
|
||||
def
|
||||
ghi
|
||||
'''
|
||||
z = 2",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 5, 5, 5, 5]);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = '''abc
|
||||
def
|
||||
ghi
|
||||
'''",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 5, 5, 5, 5]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
use crate::noqa::add_noqa_inner;
|
||||
|
||||
#[test]
|
||||
fn modification() -> Result<()> {
|
||||
let checks = vec![];
|
||||
let contents = "x = 1";
|
||||
let noqa_line_for = vec![1];
|
||||
let noqa_line_for = Default::default();
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
|
||||
assert_eq!(count, 0);
|
||||
assert_eq!(output.trim(), contents.trim());
|
||||
@@ -234,7 +137,7 @@ ghi
|
||||
},
|
||||
)];
|
||||
let contents = "x = 1";
|
||||
let noqa_line_for = vec![1];
|
||||
let noqa_line_for = Default::default();
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output.trim(), "x = 1 # noqa: F841".trim());
|
||||
@@ -256,7 +159,7 @@ ghi
|
||||
),
|
||||
];
|
||||
let contents = "x = 1 # noqa: E741";
|
||||
let noqa_line_for = vec![1];
|
||||
let noqa_line_for = Default::default();
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
|
||||
@@ -278,7 +181,7 @@ ghi
|
||||
),
|
||||
];
|
||||
let contents = "x = 1 # noqa";
|
||||
let noqa_line_for = vec![1];
|
||||
let noqa_line_for = Default::default();
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
|
||||
|
||||
7
src/python/keyword.rs
Normal file
7
src/python/keyword.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
// See: https://github.com/python/cpython/blob/9d692841691590c25e6cf5b2250a594d3bf54825/Lib/keyword.py#L18
|
||||
pub const KWLIST: [&str; 35] = [
|
||||
"False", "None", "True", "and", "as", "assert", "async", "await", "break", "class", "continue",
|
||||
"def", "del", "elif", "else", "except", "finally", "for", "from", "global", "if", "import",
|
||||
"in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return", "try", "while",
|
||||
"with", "yield",
|
||||
];
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod builtins;
|
||||
pub mod future;
|
||||
pub mod keyword;
|
||||
pub mod sys;
|
||||
pub mod typing;
|
||||
|
||||
228
src/python/sys.rs
Normal file
228
src/python/sys.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
// See: https://pycqa.github.io/isort/docs/configuration/options.html#known-standard-library
|
||||
pub static KNOWN_STANDARD_LIBRARY: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
"_ast",
|
||||
"_dummy_thread",
|
||||
"_thread",
|
||||
"abc",
|
||||
"aifc",
|
||||
"argparse",
|
||||
"array",
|
||||
"ast",
|
||||
"asynchat",
|
||||
"asyncio",
|
||||
"asyncore",
|
||||
"atexit",
|
||||
"audioop",
|
||||
"base64",
|
||||
"bdb",
|
||||
"binascii",
|
||||
"binhex",
|
||||
"bisect",
|
||||
"builtins",
|
||||
"bz2",
|
||||
"cProfile",
|
||||
"calendar",
|
||||
"cgi",
|
||||
"cgitb",
|
||||
"chunk",
|
||||
"cmath",
|
||||
"cmd",
|
||||
"code",
|
||||
"codecs",
|
||||
"codeop",
|
||||
"collections",
|
||||
"colorsys",
|
||||
"compileall",
|
||||
"concurrent",
|
||||
"configparser",
|
||||
"contextlib",
|
||||
"contextvars",
|
||||
"copy",
|
||||
"copyreg",
|
||||
"crypt",
|
||||
"csv",
|
||||
"ctypes",
|
||||
"curses",
|
||||
"dataclasses",
|
||||
"datetime",
|
||||
"dbm",
|
||||
"decimal",
|
||||
"difflib",
|
||||
"dis",
|
||||
"distutils",
|
||||
"doctest",
|
||||
"dummy_threading",
|
||||
"email",
|
||||
"encodings",
|
||||
"ensurepip",
|
||||
"enum",
|
||||
"errno",
|
||||
"faulthandler",
|
||||
"fcntl",
|
||||
"filecmp",
|
||||
"fileinput",
|
||||
"fnmatch",
|
||||
"formatter",
|
||||
"fpectl",
|
||||
"fractions",
|
||||
"ftplib",
|
||||
"functools",
|
||||
"gc",
|
||||
"getopt",
|
||||
"getpass",
|
||||
"gettext",
|
||||
"glob",
|
||||
"graphlib",
|
||||
"grp",
|
||||
"gzip",
|
||||
"hashlib",
|
||||
"heapq",
|
||||
"hmac",
|
||||
"html",
|
||||
"http",
|
||||
"imaplib",
|
||||
"imghdr",
|
||||
"imp",
|
||||
"importlib",
|
||||
"inspect",
|
||||
"io",
|
||||
"ipaddress",
|
||||
"itertools",
|
||||
"json",
|
||||
"keyword",
|
||||
"lib2to3",
|
||||
"linecache",
|
||||
"locale",
|
||||
"logging",
|
||||
"lzma",
|
||||
"macpath",
|
||||
"mailbox",
|
||||
"mailcap",
|
||||
"marshal",
|
||||
"math",
|
||||
"mimetypes",
|
||||
"mmap",
|
||||
"modulefinder",
|
||||
"msilib",
|
||||
"msvcrt",
|
||||
"multiprocessing",
|
||||
"netrc",
|
||||
"nis",
|
||||
"nntplib",
|
||||
"ntpath",
|
||||
"numbers",
|
||||
"operator",
|
||||
"optparse",
|
||||
"os",
|
||||
"ossaudiodev",
|
||||
"parser",
|
||||
"pathlib",
|
||||
"pdb",
|
||||
"pickle",
|
||||
"pickletools",
|
||||
"pipes",
|
||||
"pkgutil",
|
||||
"platform",
|
||||
"plistlib",
|
||||
"poplib",
|
||||
"posix",
|
||||
"posixpath",
|
||||
"pprint",
|
||||
"profile",
|
||||
"pstats",
|
||||
"pty",
|
||||
"pwd",
|
||||
"py_compile",
|
||||
"pyclbr",
|
||||
"pydoc",
|
||||
"queue",
|
||||
"quopri",
|
||||
"random",
|
||||
"re",
|
||||
"readline",
|
||||
"reprlib",
|
||||
"resource",
|
||||
"rlcompleter",
|
||||
"runpy",
|
||||
"sched",
|
||||
"secrets",
|
||||
"select",
|
||||
"selectors",
|
||||
"shelve",
|
||||
"shlex",
|
||||
"shutil",
|
||||
"signal",
|
||||
"site",
|
||||
"smtpd",
|
||||
"smtplib",
|
||||
"sndhdr",
|
||||
"socket",
|
||||
"socketserver",
|
||||
"spwd",
|
||||
"sqlite3",
|
||||
"sre",
|
||||
"sre_compile",
|
||||
"sre_constants",
|
||||
"sre_parse",
|
||||
"ssl",
|
||||
"stat",
|
||||
"statistics",
|
||||
"string",
|
||||
"stringprep",
|
||||
"struct",
|
||||
"subprocess",
|
||||
"sunau",
|
||||
"symbol",
|
||||
"symtable",
|
||||
"sys",
|
||||
"sysconfig",
|
||||
"syslog",
|
||||
"tabnanny",
|
||||
"tarfile",
|
||||
"telnetlib",
|
||||
"tempfile",
|
||||
"termios",
|
||||
"test",
|
||||
"textwrap",
|
||||
"threading",
|
||||
"time",
|
||||
"timeit",
|
||||
"tkinter",
|
||||
"token",
|
||||
"tokenize",
|
||||
"trace",
|
||||
"traceback",
|
||||
"tracemalloc",
|
||||
"tty",
|
||||
"turtle",
|
||||
"turtledemo",
|
||||
"types",
|
||||
"typing",
|
||||
"unicodedata",
|
||||
"unittest",
|
||||
"urllib",
|
||||
"uu",
|
||||
"uuid",
|
||||
"venv",
|
||||
"warnings",
|
||||
"wave",
|
||||
"weakref",
|
||||
"webbrowser",
|
||||
"winreg",
|
||||
"winsound",
|
||||
"wsgiref",
|
||||
"xdrlib",
|
||||
"xml",
|
||||
"xmlrpc",
|
||||
"zipapp",
|
||||
"zipfile",
|
||||
"zipimport",
|
||||
"zlib",
|
||||
"zoneinfo",
|
||||
])
|
||||
});
|
||||
@@ -2,16 +2,17 @@
|
||||
//! command-line options. Structure mirrors the user-facing representation of
|
||||
//! the various parameters.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use once_cell::sync::Lazy;
|
||||
use path_absolutize::path_dedot;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::settings::pyproject::load_options;
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
|
||||
use crate::{flake8_annotations, flake8_quotes, pep8_naming};
|
||||
use crate::{flake8_annotations, flake8_quotes, fs, isort, pep8_naming};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Configuration {
|
||||
@@ -25,10 +26,12 @@ pub struct Configuration {
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub select: Vec<CheckCodePrefix>,
|
||||
pub src: Vec<PathBuf>,
|
||||
pub target_version: PythonVersion,
|
||||
// Plugins
|
||||
pub flake8_annotations: flake8_annotations::settings::Settings,
|
||||
pub flake8_quotes: flake8_quotes::settings::Settings,
|
||||
pub isort: isort::settings::Settings,
|
||||
pub pep8_naming: pep8_naming::settings::Settings,
|
||||
}
|
||||
|
||||
@@ -71,6 +74,25 @@ impl Configuration {
|
||||
.map_err(|e| anyhow!("Invalid dummy-variable-rgx value: {e}"))?,
|
||||
None => DEFAULT_DUMMY_VARIABLE_RGX.clone(),
|
||||
},
|
||||
src: options
|
||||
.src
|
||||
.map(|src| {
|
||||
src.iter()
|
||||
.map(|path| {
|
||||
let path = Path::new(path);
|
||||
match project_root {
|
||||
Some(project_root) => fs::normalize_path_to(path, project_root),
|
||||
None => fs::normalize_path(path),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
vec![match project_root {
|
||||
Some(project_root) => project_root.clone(),
|
||||
None => path_dedot::CWD.clone(),
|
||||
}]
|
||||
}),
|
||||
target_version: options.target_version.unwrap_or(PythonVersion::Py310),
|
||||
exclude: options
|
||||
.exclude
|
||||
@@ -115,6 +137,10 @@ impl Configuration {
|
||||
.flake8_quotes
|
||||
.map(flake8_quotes::settings::Settings::from_options)
|
||||
.unwrap_or_default(),
|
||||
isort: options
|
||||
.isort
|
||||
.map(isort::settings::Settings::from_options)
|
||||
.unwrap_or_default(),
|
||||
pep8_naming: options
|
||||
.pep8_naming
|
||||
.map(pep8_naming::settings::Settings::from_options)
|
||||
|
||||
@@ -4,14 +4,16 @@
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use path_absolutize::path_dedot;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::checks_gen::{CheckCodePrefix, PrefixSpecificity};
|
||||
use crate::settings::configuration::Configuration;
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
|
||||
use crate::{flake8_annotations, flake8_quotes, pep8_naming};
|
||||
use crate::{flake8_annotations, flake8_quotes, isort, pep8_naming};
|
||||
|
||||
pub mod configuration;
|
||||
pub mod options;
|
||||
@@ -27,10 +29,12 @@ pub struct Settings {
|
||||
pub extend_exclude: Vec<FilePattern>,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub src: Vec<PathBuf>,
|
||||
pub target_version: PythonVersion,
|
||||
// Plugins
|
||||
pub flake8_annotations: flake8_annotations::settings::Settings,
|
||||
pub flake8_quotes: flake8_quotes::settings::Settings,
|
||||
pub isort: isort::settings::Settings,
|
||||
pub pep8_naming: pep8_naming::settings::Settings,
|
||||
}
|
||||
|
||||
@@ -48,9 +52,11 @@ impl Settings {
|
||||
extend_exclude: config.extend_exclude,
|
||||
flake8_annotations: config.flake8_annotations,
|
||||
flake8_quotes: config.flake8_quotes,
|
||||
isort: config.isort,
|
||||
line_length: config.line_length,
|
||||
pep8_naming: config.pep8_naming,
|
||||
per_file_ignores: config.per_file_ignores,
|
||||
src: config.src,
|
||||
target_version: config.target_version,
|
||||
}
|
||||
}
|
||||
@@ -63,9 +69,11 @@ impl Settings {
|
||||
extend_exclude: Default::default(),
|
||||
line_length: 88,
|
||||
per_file_ignores: Default::default(),
|
||||
src: vec![path_dedot::CWD.clone()],
|
||||
target_version: PythonVersion::Py310,
|
||||
flake8_annotations: Default::default(),
|
||||
flake8_quotes: Default::default(),
|
||||
isort: Default::default(),
|
||||
pep8_naming: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -78,9 +86,11 @@ impl Settings {
|
||||
extend_exclude: Default::default(),
|
||||
line_length: 88,
|
||||
per_file_ignores: Default::default(),
|
||||
src: vec![path_dedot::CWD.clone()],
|
||||
target_version: PythonVersion::Py310,
|
||||
flake8_annotations: Default::default(),
|
||||
flake8_quotes: Default::default(),
|
||||
isort: Default::default(),
|
||||
pep8_naming: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -101,6 +111,7 @@ impl Hash for Settings {
|
||||
// Add plugin properties in alphabetical order.
|
||||
self.flake8_annotations.hash(state);
|
||||
self.flake8_quotes.hash(state);
|
||||
self.isort.hash(state);
|
||||
self.pep8_naming.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::{flake8_annotations, flake8_quotes, pep8_naming};
|
||||
use crate::{flake8_annotations, flake8_quotes, isort, pep8_naming};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
@@ -21,9 +21,11 @@ pub struct Options {
|
||||
pub line_length: Option<usize>,
|
||||
pub per_file_ignores: Option<BTreeMap<String, Vec<CheckCodePrefix>>>,
|
||||
pub select: Option<Vec<CheckCodePrefix>>,
|
||||
pub src: Option<Vec<String>>,
|
||||
pub target_version: Option<PythonVersion>,
|
||||
// Plugins
|
||||
pub flake8_annotations: Option<flake8_annotations::settings::Options>,
|
||||
pub flake8_quotes: Option<flake8_quotes::settings::Options>,
|
||||
pub isort: Option<isort::settings::Options>,
|
||||
pub pep8_naming: Option<pep8_naming::settings::Options>,
|
||||
}
|
||||
|
||||
@@ -143,9 +143,11 @@ mod tests {
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
src: None,
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
})
|
||||
})
|
||||
@@ -172,9 +174,11 @@ line-length = 79
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
src: None,
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
})
|
||||
})
|
||||
@@ -201,9 +205,11 @@ exclude = ["foo.py"]
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
src: None,
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
})
|
||||
})
|
||||
@@ -230,9 +236,11 @@ select = ["E501"]
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
src: None,
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
})
|
||||
})
|
||||
@@ -260,9 +268,11 @@ ignore = ["E501"]
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
src: None,
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
})
|
||||
})
|
||||
@@ -323,9 +333,9 @@ other-attribute = 1
|
||||
fix: None,
|
||||
exclude: None,
|
||||
extend_exclude: Some(vec![
|
||||
"excluded.py".to_string(),
|
||||
"excluded_file.py".to_string(),
|
||||
"migrations".to_string(),
|
||||
"directory/also_excluded.py".to_string(),
|
||||
"with_excluded_file/other_excluded_file.py".to_string(),
|
||||
]),
|
||||
select: None,
|
||||
extend_select: None,
|
||||
@@ -336,6 +346,7 @@ other-attribute = 1
|
||||
vec![CheckCodePrefix::F401]
|
||||
),])),
|
||||
dummy_variable_rgx: None,
|
||||
src: None,
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_quotes: Some(flake8_quotes::settings::Options {
|
||||
@@ -344,6 +355,7 @@ other-attribute = 1
|
||||
docstring_quotes: Some(Quote::Double),
|
||||
avoid_escape: Some(true),
|
||||
}),
|
||||
isort: None,
|
||||
pep8_naming: Some(pep8_naming::settings::Options {
|
||||
ignore_names: Some(vec![
|
||||
"setUp".to_string(),
|
||||
|
||||
@@ -7,7 +7,7 @@ use regex::Regex;
|
||||
use crate::checks::CheckCode;
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::settings::types::{FilePattern, PythonVersion};
|
||||
use crate::{flake8_annotations, flake8_quotes, pep8_naming, Configuration};
|
||||
use crate::{flake8_annotations, flake8_quotes, isort, pep8_naming, Configuration};
|
||||
|
||||
/// Struct to render user-facing exclusion patterns.
|
||||
#[derive(Debug)]
|
||||
@@ -45,10 +45,12 @@ pub struct UserConfiguration {
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<(Exclusion, Vec<CheckCode>)>,
|
||||
pub select: Vec<CheckCodePrefix>,
|
||||
pub src: Vec<PathBuf>,
|
||||
pub target_version: PythonVersion,
|
||||
// Plugins
|
||||
pub flake8_annotations: flake8_annotations::settings::Settings,
|
||||
pub flake8_quotes: flake8_quotes::settings::Settings,
|
||||
pub isort: isort::settings::Settings,
|
||||
pub pep8_naming: pep8_naming::settings::Settings,
|
||||
// Non-settings exposed to the user
|
||||
pub project_root: Option<PathBuf>,
|
||||
@@ -89,9 +91,11 @@ impl UserConfiguration {
|
||||
})
|
||||
.collect(),
|
||||
select: configuration.select,
|
||||
src: configuration.src,
|
||||
target_version: configuration.target_version,
|
||||
flake8_annotations: configuration.flake8_annotations,
|
||||
flake8_quotes: configuration.flake8_quotes,
|
||||
isort: configuration.isort,
|
||||
pep8_naming: configuration.pep8_naming,
|
||||
project_root,
|
||||
pyproject,
|
||||
|
||||
90
src/snapshots/ruff__linter__tests__B009_B009_B010.py.snap
Normal file
90
src/snapshots/ruff__linter__tests__B009_B009_B010.py.snap
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: GetAttrWithConstant
|
||||
location:
|
||||
row: 18
|
||||
column: 0
|
||||
end_location:
|
||||
row: 18
|
||||
column: 19
|
||||
fix:
|
||||
patch:
|
||||
content: foo.bar
|
||||
location:
|
||||
row: 18
|
||||
column: 0
|
||||
end_location:
|
||||
row: 18
|
||||
column: 19
|
||||
applied: false
|
||||
- kind: GetAttrWithConstant
|
||||
location:
|
||||
row: 19
|
||||
column: 0
|
||||
end_location:
|
||||
row: 19
|
||||
column: 23
|
||||
fix:
|
||||
patch:
|
||||
content: foo._123abc
|
||||
location:
|
||||
row: 19
|
||||
column: 0
|
||||
end_location:
|
||||
row: 19
|
||||
column: 23
|
||||
applied: false
|
||||
- kind: GetAttrWithConstant
|
||||
location:
|
||||
row: 20
|
||||
column: 0
|
||||
end_location:
|
||||
row: 20
|
||||
column: 22
|
||||
fix:
|
||||
patch:
|
||||
content: foo.abc123
|
||||
location:
|
||||
row: 20
|
||||
column: 0
|
||||
end_location:
|
||||
row: 20
|
||||
column: 22
|
||||
applied: false
|
||||
- kind: GetAttrWithConstant
|
||||
location:
|
||||
row: 21
|
||||
column: 0
|
||||
end_location:
|
||||
row: 21
|
||||
column: 23
|
||||
fix:
|
||||
patch:
|
||||
content: foo.abc123
|
||||
location:
|
||||
row: 21
|
||||
column: 0
|
||||
end_location:
|
||||
row: 21
|
||||
column: 23
|
||||
applied: false
|
||||
- kind: GetAttrWithConstant
|
||||
location:
|
||||
row: 22
|
||||
column: 14
|
||||
end_location:
|
||||
row: 22
|
||||
column: 31
|
||||
fix:
|
||||
patch:
|
||||
content: x.bar
|
||||
location:
|
||||
row: 22
|
||||
column: 14
|
||||
end_location:
|
||||
row: 22
|
||||
column: 31
|
||||
applied: false
|
||||
|
||||
37
src/snapshots/ruff__linter__tests__B010_B009_B010.py.snap
Normal file
37
src/snapshots/ruff__linter__tests__B010_B009_B010.py.snap
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: SetAttrWithConstant
|
||||
location:
|
||||
row: 33
|
||||
column: 0
|
||||
end_location:
|
||||
row: 33
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: SetAttrWithConstant
|
||||
location:
|
||||
row: 34
|
||||
column: 0
|
||||
end_location:
|
||||
row: 34
|
||||
column: 29
|
||||
fix: ~
|
||||
- kind: SetAttrWithConstant
|
||||
location:
|
||||
row: 35
|
||||
column: 0
|
||||
end_location:
|
||||
row: 35
|
||||
column: 28
|
||||
fix: ~
|
||||
- kind: SetAttrWithConstant
|
||||
location:
|
||||
row: 36
|
||||
column: 0
|
||||
end_location:
|
||||
row: 36
|
||||
column: 29
|
||||
fix: ~
|
||||
|
||||
@@ -17,7 +17,7 @@ impl<'a> SourceCodeLocator<'a> {
|
||||
pub fn new(contents: &'a str) -> Self {
|
||||
SourceCodeLocator {
|
||||
contents,
|
||||
rope: OnceCell::new(),
|
||||
rope: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user