Compare commits

..

1 Commits

Author SHA1 Message Date
Dhruv Manilawala
ae4f960a47 Create Definition for all place expressions 2025-06-24 11:09:54 +05:30
317 changed files with 8580 additions and 10271 deletions

View File

@@ -1,81 +1,5 @@
# Changelog
## 0.12.1
### Preview features
- \[`flake8-errmsg`\] Extend `EM101` to support byte strings ([#18867](https://github.com/astral-sh/ruff/pull/18867))
- \[`flake8-use-pathlib`\] Add autofix for `PTH202` ([#18763](https://github.com/astral-sh/ruff/pull/18763))
- \[`pygrep-hooks`\] Add `AsyncMock` methods to `invalid-mock-access` (`PGH005`) ([#18547](https://github.com/astral-sh/ruff/pull/18547))
- \[`pylint`\] Ignore `__init__.py` files in (`PLC0414`) ([#18400](https://github.com/astral-sh/ruff/pull/18400))
- \[`ruff`\] Trigger `RUF037` for empty string and byte strings ([#18862](https://github.com/astral-sh/ruff/pull/18862))
- [formatter] Fix missing blank lines before decorated classes in `.pyi` files ([#18888](https://github.com/astral-sh/ruff/pull/18888))
### Bug fixes
- Avoid generating diagnostics with per-file ignores ([#18801](https://github.com/astral-sh/ruff/pull/18801))
- Handle parenthesized arguments in `remove_argument` ([#18805](https://github.com/astral-sh/ruff/pull/18805))
- \[`flake8-logging`\] Avoid false positive for `exc_info=True` outside `logger.exception` (`LOG014`) ([#18737](https://github.com/astral-sh/ruff/pull/18737))
- \[`flake8-pytest-style`\] Enforce `pytest` import for decorators ([#18779](https://github.com/astral-sh/ruff/pull/18779))
- \[`flake8-pytest-style`\] Mark autofix for `PT001` and `PT023` as unsafe if there's comments in the decorator ([#18792](https://github.com/astral-sh/ruff/pull/18792))
- \[`flake8-pytest-style`\] `PT001`/`PT023` fix makes syntax error on parenthesized decorator ([#18782](https://github.com/astral-sh/ruff/pull/18782))
- \[`flake8-raise`\] Make fix unsafe if it deletes comments (`RSE102`) ([#18788](https://github.com/astral-sh/ruff/pull/18788))
- \[`flake8-simplify`\] Fix `SIM911` autofix creating a syntax error ([#18793](https://github.com/astral-sh/ruff/pull/18793))
- \[`flake8-simplify`\] Fix false negatives for shadowed bindings (`SIM910`, `SIM911`) ([#18794](https://github.com/astral-sh/ruff/pull/18794))
- \[`flake8-simplify`\] Preserve original behavior for `except ()` and bare `except` (`SIM105`) ([#18213](https://github.com/astral-sh/ruff/pull/18213))
- \[`flake8-pyi`\] Fix `PYI041`'s fix causing `TypeError` with `None | None | ...` ([#18637](https://github.com/astral-sh/ruff/pull/18637))
- \[`perflint`\] Fix `PERF101` autofix creating a syntax error and mark autofix as unsafe if there are comments in the `list` call expr ([#18803](https://github.com/astral-sh/ruff/pull/18803))
- \[`perflint`\] Fix false negative in `PERF401` ([#18866](https://github.com/astral-sh/ruff/pull/18866))
- \[`pylint`\] Avoid flattening nested `min`/`max` when outer call has single argument (`PLW3301`) ([#16885](https://github.com/astral-sh/ruff/pull/16885))
- \[`pylint`\] Fix `PLC2801` autofix creating a syntax error ([#18857](https://github.com/astral-sh/ruff/pull/18857))
- \[`pylint`\] Mark `PLE0241` autofix as unsafe if there's comments in the base classes ([#18832](https://github.com/astral-sh/ruff/pull/18832))
- \[`pylint`\] Suppress `PLE2510`/`PLE2512`/`PLE2513`/`PLE2514`/`PLE2515` autofix if the text contains an odd number of backslashes ([#18856](https://github.com/astral-sh/ruff/pull/18856))
- \[`refurb`\] Detect more exotic float literals in `FURB164` ([#18925](https://github.com/astral-sh/ruff/pull/18925))
- \[`refurb`\] Fix `FURB163` autofix creating a syntax error for `yield` expressions ([#18756](https://github.com/astral-sh/ruff/pull/18756))
- \[`refurb`\] Mark `FURB129` autofix as unsafe if there's comments in the `readlines` call ([#18858](https://github.com/astral-sh/ruff/pull/18858))
- \[`ruff`\] Fix false positives and negatives in `RUF010` ([#18690](https://github.com/astral-sh/ruff/pull/18690))
- Fix casing of `analyze.direction` variant names ([#18892](https://github.com/astral-sh/ruff/pull/18892))
### Rule changes
- Fix f-string interpolation escaping in generated fixes ([#18882](https://github.com/astral-sh/ruff/pull/18882))
- \[`flake8-return`\] Mark `RET501` fix unsafe if comments are inside ([#18780](https://github.com/astral-sh/ruff/pull/18780))
- \[`flake8-async`\] Fix detection for large integer sleep durations in `ASYNC116` rule ([#18767](https://github.com/astral-sh/ruff/pull/18767))
- \[`flake8-async`\] Mark autofix for `ASYNC115` as unsafe if the call expression contains comments ([#18753](https://github.com/astral-sh/ruff/pull/18753))
- \[`flake8-bugbear`\] Mark autofix for `B004` as unsafe if the `hasattr` call expr contains comments ([#18755](https://github.com/astral-sh/ruff/pull/18755))
- \[`flake8-comprehension`\] Mark autofix for `C420` as unsafe if there's comments inside the dict comprehension ([#18768](https://github.com/astral-sh/ruff/pull/18768))
- \[`flake8-comprehensions`\] Handle template strings for comprehension fixes ([#18710](https://github.com/astral-sh/ruff/pull/18710))
- \[`flake8-future-annotations`\] Add autofix (`FA100`) ([#18903](https://github.com/astral-sh/ruff/pull/18903))
- \[`pyflakes`\] Mark `F504`/`F522`/`F523` autofix as unsafe if there's a call with side effect ([#18839](https://github.com/astral-sh/ruff/pull/18839))
- \[`pylint`\] Allow fix with comments and document performance implications (`PLW3301`) ([#18936](https://github.com/astral-sh/ruff/pull/18936))
- \[`pylint`\] Detect more exotic `NaN` literals in `PLW0177` ([#18630](https://github.com/astral-sh/ruff/pull/18630))
- \[`pylint`\] Fix `PLC1802` autofix creating a syntax error and mark autofix as unsafe if there's comments in the `len` call ([#18836](https://github.com/astral-sh/ruff/pull/18836))
- \[`pyupgrade`\] Extend version detection to include `sys.version_info.major` (`UP036`) ([#18633](https://github.com/astral-sh/ruff/pull/18633))
- \[`ruff`\] Add lint rule `RUF064` for calling `chmod` with non-octal integers ([#18541](https://github.com/astral-sh/ruff/pull/18541))
- \[`ruff`\] Added `cls.__dict__.get('__annotations__')` check (`RUF063`) ([#18233](https://github.com/astral-sh/ruff/pull/18233))
- \[`ruff`\] Frozen `dataclass` default should be valid (`RUF009`) ([#18735](https://github.com/astral-sh/ruff/pull/18735))
### Server
- Consider virtual path for various server actions ([#18910](https://github.com/astral-sh/ruff/pull/18910))
### Documentation
- Add fix safety sections ([#18940](https://github.com/astral-sh/ruff/pull/18940),[#18841](https://github.com/astral-sh/ruff/pull/18841),[#18802](https://github.com/astral-sh/ruff/pull/18802),[#18837](https://github.com/astral-sh/ruff/pull/18837),[#18800](https://github.com/astral-sh/ruff/pull/18800),[#18415](https://github.com/astral-sh/ruff/pull/18415),[#18853](https://github.com/astral-sh/ruff/pull/18853),[#18842](https://github.com/astral-sh/ruff/pull/18842))
- Use updated pre-commit id ([#18718](https://github.com/astral-sh/ruff/pull/18718))
- \[`perflint`\] Small docs improvement to `PERF401` ([#18786](https://github.com/astral-sh/ruff/pull/18786))
- \[`pyupgrade`\]: Use `super()`, not `__super__` in error messages (`UP008`) ([#18743](https://github.com/astral-sh/ruff/pull/18743))
- \[`flake8-pie`\] Small docs fix to `PIE794` ([#18829](https://github.com/astral-sh/ruff/pull/18829))
- \[`flake8-pyi`\] Correct `collections-named-tuple` example to use PascalCase assignment ([#16884](https://github.com/astral-sh/ruff/pull/16884))
- \[`flake8-pie`\] Add note on type checking benefits to `unnecessary-dict-kwargs` (`PIE804`) ([#18666](https://github.com/astral-sh/ruff/pull/18666))
- \[`pycodestyle`\] Clarify PEP 8 relationship to `whitespace-around-operator` rules ([#18870](https://github.com/astral-sh/ruff/pull/18870))
### Other changes
- Disallow newlines in format specifiers of single quoted f- or t-strings ([#18708](https://github.com/astral-sh/ruff/pull/18708))
- \[`flake8-logging`\] Add fix safety section to `LOG002` ([#18840](https://github.com/astral-sh/ruff/pull/18840))
- \[`pyupgrade`\] Add fix safety section to `UP010` ([#18838](https://github.com/astral-sh/ruff/pull/18838))
## 0.12.0
Check out the [blog post](https://astral.sh/blog/ruff-v0.12.0) for a migration
@@ -92,7 +16,7 @@ guide and overview of the changes!
- **New default Python version handling for syntax errors**
Ruff will default to the *latest* supported Python version (3.13) when
Ruff will default to the _latest_ supported Python version (3.13) when
checking for the version-related syntax errors mentioned above to prevent
false positives in projects without a Python version configured. The default
in all other cases, like applying lint rules, is unchanged and remains at the
@@ -147,7 +71,7 @@ The following rules have been stabilized and are no longer in preview:
- [`class-with-mixed-type-vars`](https://docs.astral.sh/ruff/rules/class-with-mixed-type-vars) (`RUF053`)
- [`unnecessary-round`](https://docs.astral.sh/ruff/rules/unnecessary-round) (`RUF057`)
- [`starmap-zip`](https://docs.astral.sh/ruff/rules/starmap-zip) (`RUF058`)
- [`non-pep604-annotation-optional`] (`UP045`)
- [`non-pep604-annotation-optional`](https://docs.astral.sh/ruff/rules/non-pep604-annotation-optional) (`UP045`)
- [`non-pep695-generic-class`](https://docs.astral.sh/ruff/rules/non-pep695-generic-class) (`UP046`)
- [`non-pep695-generic-function`](https://docs.astral.sh/ruff/rules/non-pep695-generic-function) (`UP047`)
- [`private-type-parameter`](https://docs.astral.sh/ruff/rules/private-type-parameter) (`UP049`)

39
Cargo.lock generated
View File

@@ -930,12 +930,35 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_home"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
[[package]]
name = "env_logger"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -2559,7 +2582,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.12.1"
version = "0.12.0"
dependencies = [
"anyhow",
"argfile",
@@ -2802,7 +2825,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.12.1"
version = "0.12.0"
dependencies = [
"aho-corasick",
"anyhow",
@@ -3023,6 +3046,16 @@ dependencies = [
"walkdir",
]
[[package]]
name = "ruff_python_resolver"
version = "0.0.0"
dependencies = [
"env_logger",
"insta",
"log",
"tempfile",
]
[[package]]
name = "ruff_python_semantic"
version = "0.0.0"
@@ -3129,7 +3162,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.12.1"
version = "0.12.0"
dependencies = [
"console_error_panic_hook",
"console_log",

View File

@@ -75,6 +75,7 @@ dashmap = { version = "6.0.1" }
dir-test = { version = "0.4.0" }
dunce = { version = "1.0.5" }
drop_bomb = { version = "0.1.5" }
env_logger = { version = "0.11.0" }
etcetera = { version = "0.10.0" }
fern = { version = "0.7.0" }
filetime = { version = "0.2.23" }

View File

@@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.12.1/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.1/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.12.0/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.0/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.1
rev: v0.12.0
hooks:
# Run the linter.
- id: ruff-check

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.12.1"
version = "0.12.0"
publish = true
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -442,7 +442,7 @@ impl LintCacheData {
// Parse the kebab-case rule name into a `Rule`. This will fail for syntax errors, so
// this also serves to filter them out, but we shouldn't be caching files with syntax
// errors anyway.
.filter_map(|msg| Some((msg.name().parse().ok()?, msg)))
.filter_map(|msg| Some((msg.noqa_code().and_then(|code| code.rule())?, msg)))
.map(|(rule, msg)| {
// Make sure that all message use the same source file.
assert_eq!(

View File

@@ -17,7 +17,6 @@ fn command() -> Command {
command.arg("analyze");
command.arg("graph");
command.arg("--preview");
command.env_clear();
command
}
@@ -566,8 +565,8 @@ fn venv() -> Result<()> {
----- stderr -----
ruff failed
Cause: Invalid `--python` argument `none`: does not point to a Python executable or a directory on disk
Cause: No such file or directory (os error 2)
Cause: Invalid search path settings
Cause: Failed to discover the site-packages directory: Invalid `--python` argument `none`: does not point to a Python executable or a directory on disk
");
});

View File

@@ -352,7 +352,7 @@ impl DisplaySet<'_> {
// FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char`
// is. For now, just accept that sometimes the code line will be longer than
// desired.
let next = char_width(ch).unwrap_or(1);
let next = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1);
if taken + next > right - left {
was_cut_right = true;
break;
@@ -377,7 +377,7 @@ impl DisplaySet<'_> {
let left: usize = text
.chars()
.take(left)
.map(|ch| char_width(ch).unwrap_or(1))
.map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1))
.sum();
let mut annotations = annotations.clone();
@@ -1393,7 +1393,6 @@ fn format_body<'m>(
let line_length: usize = line.len();
let line_range = (current_index, current_index + line_length);
let end_line_size = end_line.len();
body.push(DisplayLine::Source {
lineno: Some(current_line),
inline_marks: vec![],
@@ -1453,12 +1452,12 @@ fn format_body<'m>(
let annotation_start_col = line
[0..(start - line_start_index).min(line_length)]
.chars()
.map(|c| char_width(c).unwrap_or(0))
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
.sum::<usize>();
let mut annotation_end_col = line
[0..(end - line_start_index).min(line_length)]
.chars()
.map(|c| char_width(c).unwrap_or(0))
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
.sum::<usize>();
if annotation_start_col == annotation_end_col {
// At least highlight something
@@ -1500,7 +1499,7 @@ fn format_body<'m>(
let annotation_start_col = line
[0..(start - line_start_index).min(line_length)]
.chars()
.map(|c| char_width(c).unwrap_or(0))
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
.sum::<usize>();
let annotation_end_col = annotation_start_col + 1;
@@ -1559,7 +1558,7 @@ fn format_body<'m>(
{
let end_mark = line[0..(end - line_start_index).min(line_length)]
.chars()
.map(|c| char_width(c).unwrap_or(0))
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
.sum::<usize>()
.saturating_sub(1);
// If the annotation ends on a line-end character, we
@@ -1755,11 +1754,3 @@ fn format_inline_marks(
}
Ok(())
}
fn char_width(c: char) -> Option<usize> {
if c == '\t' {
Some(4)
} else {
unicode_width::UnicodeWidthChar::width(c)
}
}

View File

@@ -1,38 +0,0 @@
<svg width="740px" height="146px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { background: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
tspan {
font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
white-space: pre;
line-height: 18px;
}
</style>
<rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
<text xml:space="preserve" class="container fg">
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">call-non-callable</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/main.py:5:9</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4 |</tspan><tspan> def f():</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">5 |</tspan><tspan> return (1 == '2')() # Tab indented</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,45 +0,0 @@
# [crates/ruff_db/src/diagnostic/render.rs:123:47] diag.to_annotate() = Message {
# level: Error,
# id: Some(
# "call-non-callable",
# ),
# title: "Object of type `bool` is not callable",
# snippets: [
# Snippet {
# origin: Some(
# "main.py",
# ),
# line_start: 1,
# source: "def f():\n\treturn (1 == '2')() # Tab indented\n",
# annotations: [
# Annotation {
# range: 17..29,
# label: None,
# level: Error,
# },
# ],
# fold: false,
# },
# ],
# footer: [],
# }
[message]
level = "Error"
id = "E0308"
title = "call-non-callable"
[[message.snippets]]
source = "def f():\n\treturn (1 == '2')() # Tab indented\n"
line_start = 4
origin = "$DIR/main.py"
[[message.snippets.annotations]]
label = ""
level = "Error"
range = [17, 29]
[renderer]
# anonymized_line_numbers = true
color = true

View File

@@ -1,36 +0,0 @@
<svg width="1196px" height="128px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { background: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
tspan {
font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
white-space: pre;
line-height: 18px;
}
</style>
<rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
<text xml:space="preserve" class="container fg">
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/non-whitespace-trimming.rs:4:6</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4 |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> s_data['d_dails'] = bb['contacted'][hostip]['ansible_facts']['ansible_devices']['vda']['vendor'] + " " + bb['contacted'][hostip</tspan><tspan class="fg-bright-blue bold">...</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,20 +0,0 @@
[message]
level = "Error"
id = "E0308"
title = "mismatched types"
[[message.snippets]]
source = """
s_data['d_dails'] = bb['contacted'][hostip]['ansible_facts']['ansible_devices']['vda']['vendor'] + " " + bb['contacted'][hostip]['an
"""
line_start = 4
origin = "$DIR/non-whitespace-trimming.rs"
[[message.snippets.annotations]]
label = ""
level = "Error"
range = [5, 11]
[renderer]
# anonymized_line_numbers = true
color = true

View File

@@ -21,7 +21,7 @@
<text xml:space="preserve" class="container fg">
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/non-whitespace-trimming.rs:4:238</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/non-whitespace-trimming.rs:4:242</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -13,12 +13,12 @@ origin = "$DIR/non-whitespace-trimming.rs"
[[message.snippets.annotations]]
label = "expected `()`, found integer"
level = "Error"
range = [237, 239]
range = [241, 243]
[[message.snippets.annotations]]
label = "expected due to this"
level = "Error"
range = [232, 234]
range = [236, 238]
[renderer]

View File

@@ -348,58 +348,6 @@ fn benchmark_many_tuple_assignments(criterion: &mut Criterion) {
});
}
fn benchmark_many_attribute_assignments(criterion: &mut Criterion) {
setup_rayon();
criterion.bench_function("ty_micro[many_attribute_assignments]", |b| {
b.iter_batched_ref(
|| {
// This is a regression benchmark for https://github.com/astral-sh/ty/issues/627.
// Before this was fixed, the following sample would take >1s to type check.
setup_micro_case(
r#"
class C:
def f(self: "C"):
if isinstance(self.a, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
"#,
)
},
|case| {
let Case { db, .. } = case;
let result = db.check();
assert!(!result.is_empty());
},
BatchSize::SmallInput,
);
});
}
struct ProjectBenchmark<'a> {
project: InstalledProject<'a>,
fs: MemoryFileSystem,
@@ -429,7 +377,8 @@ impl<'a> ProjectBenchmark<'a> {
metadata.apply_options(Options {
environment: Some(EnvironmentOptions {
python_version: Some(RangedValue::cli(self.project.config.python_version)),
python: Some(RelativePathBuf::cli(SystemPath::new(".venv"))),
python: (!self.project.config().dependencies.is_empty())
.then_some(RelativePathBuf::cli(SystemPath::new(".venv"))),
..EnvironmentOptions::default()
}),
..Options::default()
@@ -534,7 +483,6 @@ criterion_group!(
micro,
benchmark_many_string_assignments,
benchmark_many_tuple_assignments,
benchmark_many_attribute_assignments,
);
criterion_group!(project, anyio, attrs, hydra);
criterion_main!(check_file, micro, project);

View File

@@ -36,7 +36,8 @@ impl<'a> Benchmark<'a> {
metadata.apply_options(Options {
environment: Some(EnvironmentOptions {
python_version: Some(RangedValue::cli(self.project.config.python_version)),
python: Some(RelativePathBuf::cli(SystemPath::new(".venv"))),
python: (!self.project.config().dependencies.is_empty())
.then_some(RelativePathBuf::cli(SystemPath::new(".venv"))),
..EnvironmentOptions::default()
}),
..Options::default()

View File

@@ -74,17 +74,19 @@ impl<'a> RealWorldProject<'a> {
};
// Install dependencies if specified
tracing::debug!(
"Installing {} dependencies for project '{}'...",
checkout.project().dependencies.len(),
checkout.project().name
);
let start_install = std::time::Instant::now();
install_dependencies(&checkout)?;
tracing::debug!(
"Dependency installation completed in {:.2}s",
start_install.elapsed().as_secs_f64()
);
if !checkout.project().dependencies.is_empty() {
tracing::debug!(
"Installing {} dependencies for project '{}'...",
checkout.project().dependencies.len(),
checkout.project().name
);
let start = std::time::Instant::now();
install_dependencies(&checkout)?;
tracing::debug!(
"Dependency installation completed in {:.2}s",
start.elapsed().as_secs_f64()
);
}
tracing::debug!("Project setup took: {:.2}s", start.elapsed().as_secs_f64());
@@ -279,14 +281,6 @@ fn install_dependencies(checkout: &Checkout) -> Result<()> {
String::from_utf8_lossy(&output.stderr)
);
if checkout.project().dependencies.is_empty() {
tracing::debug!(
"No dependencies to install for project '{}'",
checkout.project().name
);
return Ok(());
}
// Install dependencies with date constraint in the isolated environment
let mut cmd = Command::new("uv");
cmd.args([

View File

@@ -30,14 +30,14 @@ filetime = { workspace = true }
glob = { workspace = true }
ignore = { workspace = true, optional = true }
matchit = { workspace = true }
path-slash = { workspace = true }
rustc-hash = { workspace = true }
salsa = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
path-slash = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, optional = true }
rustc-hash = { workspace = true }
zip = { workspace = true }
[target.'cfg(target_arch="wasm32")'.dependencies]

View File

@@ -735,9 +735,6 @@ pub enum DiagnosticId {
/// # no `[overrides.rules]`
/// ```
UselessOverridesSection,
/// Use of a deprecated setting.
DeprecatedSetting,
}
impl DiagnosticId {
@@ -776,7 +773,6 @@ impl DiagnosticId {
DiagnosticId::EmptyInclude => "empty-include",
DiagnosticId::UnnecessaryOverridesSection => "unnecessary-overrides-section",
DiagnosticId::UselessOverridesSection => "useless-overrides-section",
DiagnosticId::DeprecatedSetting => "deprecated-setting",
}
}

View File

@@ -1,4 +1,4 @@
use anyhow::{Context, Result};
use anyhow::Result;
use std::sync::Arc;
use zip::CompressionMethod;
@@ -9,7 +9,7 @@ use ruff_db::{Db as SourceDb, Upcast};
use ruff_python_ast::PythonVersion;
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{
Db, Program, ProgramSettings, PythonEnvironment, PythonPlatform, PythonVersionSource,
Db, Program, ProgramSettings, PythonPath, PythonPlatform, PythonVersionSource,
PythonVersionWithSource, SearchPathSettings, SysPrefixPathOrigin, default_lint_registry,
};
@@ -35,32 +35,24 @@ impl ModuleDb {
python_version: PythonVersion,
venv_path: Option<SystemPathBuf>,
) -> Result<Self> {
let db = Self::default();
let mut search_paths = SearchPathSettings::new(src_roots);
// TODO: Consider calling `PythonEnvironment::discover` if the `venv_path` is not provided.
if let Some(venv_path) = venv_path {
let environment =
PythonEnvironment::new(venv_path, SysPrefixPathOrigin::PythonCliFlag, db.system())?;
search_paths.site_packages_paths = environment
.site_packages_paths(db.system())
.context("Failed to discover the site-packages directory")?
.into_vec();
search_paths.python_path =
PythonPath::sys_prefix(venv_path, SysPrefixPathOrigin::PythonCliFlag);
}
let search_paths = search_paths
.to_search_paths(db.system(), db.vendored())
.context("Invalid search path settings")?;
let db = Self::default();
Program::from_settings(
&db,
ProgramSettings {
python_version: PythonVersionWithSource {
python_version: Some(PythonVersionWithSource {
version: python_version,
source: PythonVersionSource::default(),
},
}),
python_platform: PythonPlatform::default(),
search_paths,
},
);
)?;
Ok(db)
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.12.1"
version = "0.12.0"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -1,6 +0,0 @@
def f_byte():
raise RuntimeError(b"This is an example exception")
def f_byte_empty():
raise RuntimeError(b"")

View File

@@ -2,81 +2,13 @@ import os.path
from pathlib import Path
from os.path import getsize
filename = "filename"
filename1 = __file__
filename2 = Path("filename")
os.path.getsize("filename")
os.path.getsize(b"filename")
os.path.getsize(Path("filename"))
os.path.getsize(__file__)
os.path.getsize(filename)
os.path.getsize(filename1)
os.path.getsize(filename2)
os.path.getsize(filename="filename")
os.path.getsize(filename=b"filename")
os.path.getsize(filename=Path("filename"))
os.path.getsize(filename=__file__)
getsize("filename")
getsize(b"filename")
getsize(Path("filename"))
getsize(__file__)
getsize(filename="filename")
getsize(filename=b"filename")
getsize(filename=Path("filename"))
getsize(filename=__file__)
getsize(filename)
getsize(filename1)
getsize(filename2)
os.path.getsize(
"filename", # comment
)
os.path.getsize(
# comment
"filename"
,
# comment
)
os.path.getsize(
# comment
b"filename"
# comment
)
os.path.getsize( # comment
Path(__file__)
# comment
) # comment
getsize( # comment
"filename")
getsize( # comment
b"filename",
#comment
)
os.path.getsize("file" + "name")
getsize \
\
\
( # comment
"filename",
)
getsize(Path("filename").resolve())
import pathlib
os.path.getsize(pathlib.Path("filename"))

View File

@@ -1,5 +0,0 @@
import os
os.path.getsize(filename="filename")
os.path.getsize(filename=b"filename")
os.path.getsize(filename=__file__)

View File

@@ -163,10 +163,4 @@ def foo():
for k, v in src:
if lambda: 0:
dst[k] = v
# https://github.com/astral-sh/ruff/issues/18859
def foo():
v = {}
for o,(x,)in():
v[x,]=o
dst[k] = v

View File

@@ -14,6 +14,3 @@ hidden = {"a": "!"}
"" % {
'test1': '',
'test2': '',
}
# https://github.com/astral-sh/ruff/issues/18806

View File

@@ -4,11 +4,3 @@
"{bar:{spam}}".format(bar=2, spam=3, eggs=4, ham=5) # F522
(''
.format(x=2)) # F522
# https://github.com/astral-sh/ruff/issues/18806
# The fix here is unsafe because the unused argument has side effect
"Hello, {name}".format(greeting=print(1), name="World")
# The fix here is safe because the unused argument has no side effect,
# even though the used argument has a side effect
"Hello, {name}".format(greeting="Pikachu", name=print(1))

View File

@@ -35,11 +35,3 @@
# Removing the final argument.
"Hello".format("world")
"Hello".format("world", key="value")
# https://github.com/astral-sh/ruff/issues/18806
# The fix here is unsafe because the unused argument has side effect
"Hello, {0}".format("world", print(1))
# The fix here is safe because the unused argument has no side effect,
# even though the used argument has a side effect
"Hello, {0}".format(print(1), "Pikachu")

View File

@@ -1,5 +1,3 @@
# Mock objects
# ============
# Errors
assert my_mock.not_called()
assert my_mock.called_once_with()
@@ -19,25 +17,3 @@ my_mock.assert_called()
my_mock.assert_called_once_with()
"""like :meth:`Mock.assert_called_once_with`"""
"""like :meth:`MagicMock.assert_called_once_with`"""
# AsyncMock objects
# =================
# Errors
assert my_mock.not_awaited()
assert my_mock.awaited_once_with()
assert my_mock.not_awaited
assert my_mock.awaited_once_with
my_mock.assert_not_awaited
my_mock.assert_awaited
my_mock.assert_awaited_once_with
my_mock.assert_awaited_once_with
MyMock.assert_awaited_once_with
assert my_mock.awaited
# OK
assert my_mock.await_count == 1
my_mock.assert_not_awaited()
my_mock.assert_awaited()
my_mock.assert_awaited_once_with()
"""like :meth:`Mock.assert_awaited_once_with`"""
"""like :meth:`MagicMock.assert_awaited_once_with`"""

View File

@@ -14,7 +14,7 @@ min(1, min(2, 3, key=test))
# This will still trigger, to merge the calls without keyword args.
min(1, min(2, 3, key=test), min(4, 5))
# The fix is already unsafe, so deleting comments is okay.
# Don't provide a fix if there are comments within the call.
min(
1, # This is a comment.
min(2, 3),

View File

@@ -20,12 +20,6 @@ _ = Decimal.from_float(float("-inf"))
_ = Decimal.from_float(float("Infinity"))
_ = Decimal.from_float(float("-Infinity"))
_ = Decimal.from_float(float("nan"))
_ = Decimal.from_float(float("-NaN"))
_ = Decimal.from_float(float(" \n+nan \t"))
_ = Decimal.from_float(float(" iNf\n\t "))
_ = Decimal.from_float(float(" -inF\n \t"))
_ = Decimal.from_float(float(" InfinIty\n\t "))
_ = Decimal.from_float(float(" -InfinIty\n \t"))
# OK
_ = Fraction(0.1)

View File

@@ -36,19 +36,5 @@ f"{ascii(bla)}" # OK
)
# https://github.com/astral-sh/ruff/issues/16325
# OK
f"{str({})}"
f"{str({} | {})}"
import builtins
f"{builtins.repr(1)}"
f"{repr(1)=}"
f"{repr(lambda: 1)}"
f"{repr(x := 2)}"
f"{str(object=3)}"

View File

@@ -1,5 +1,5 @@
import collections
from collections import deque
import collections
def f():
@@ -91,14 +91,3 @@ def f():
def f():
deque([], iterable=[])
# https://github.com/astral-sh/ruff/issues/18854
deque("")
deque(b"")
deque(f"")
deque(f"" "")
deque(f"" f"")
deque("abc") # OK
deque(b"abc") # OK
deque(f"" "a") # OK
deque(f"{x}" "") # OK

View File

@@ -1,7 +1,12 @@
use ruff_python_semantic::{Binding, ScopeKind};
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::{Binding, BindingKind, Imported, ResolvedReference, ScopeKind};
use ruff_text_size::Ranged;
use rustc_hash::FxHashMap;
use crate::Fix;
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::fix;
use crate::rules::{
flake8_builtins, flake8_pyi, flake8_type_checking, flake8_unused_arguments, pep8_naming,
pyflakes, pylint, ruff,
@@ -89,19 +94,260 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
}
if checker.is_rule_enabled(Rule::GlobalVariableNotAssigned) {
pylint::rules::global_variable_not_assigned(checker, scope);
for (name, binding_id) in scope.bindings() {
let binding = checker.semantic.binding(binding_id);
// If the binding is a `global`, then it's a top-level `global` that was never
// assigned in the current scope. If it were assigned, the `global` would be
// shadowed by the assignment.
if binding.kind.is_global() {
// If the binding was conditionally deleted, it will include a reference within
// a `Del` context, but won't be shadowed by a `BindingKind::Deletion`, as in:
// ```python
// if condition:
// del var
// ```
if binding
.references
.iter()
.map(|id| checker.semantic.reference(*id))
.all(ResolvedReference::is_load)
{
checker.report_diagnostic(
pylint::rules::GlobalVariableNotAssigned {
name: (*name).to_string(),
},
binding.range(),
);
}
}
}
}
if checker.is_rule_enabled(Rule::RedefinedArgumentFromLocal) {
pylint::rules::redefined_argument_from_local(checker, scope_id, scope);
for (name, binding_id) in scope.bindings() {
for shadow in checker.semantic.shadowed_bindings(scope_id, binding_id) {
let binding = &checker.semantic.bindings[shadow.binding_id()];
if !matches!(
binding.kind,
BindingKind::LoopVar
| BindingKind::BoundException
| BindingKind::WithItemVar
) {
continue;
}
let shadowed = &checker.semantic.bindings[shadow.shadowed_id()];
if !shadowed.kind.is_argument() {
continue;
}
if checker.settings().dummy_variable_rgx.is_match(name) {
continue;
}
let scope = &checker.semantic.scopes[binding.scope];
if scope.kind.is_generator() {
continue;
}
checker.report_diagnostic(
pylint::rules::RedefinedArgumentFromLocal {
name: name.to_string(),
},
binding.range(),
);
}
}
}
if checker.is_rule_enabled(Rule::ImportShadowedByLoopVar) {
pyflakes::rules::import_shadowed_by_loop_var(checker, scope_id, scope);
for (name, binding_id) in scope.bindings() {
for shadow in checker.semantic.shadowed_bindings(scope_id, binding_id) {
// If the shadowing binding isn't a loop variable, abort.
let binding = &checker.semantic.bindings[shadow.binding_id()];
if !binding.kind.is_loop_var() {
continue;
}
// If the shadowed binding isn't an import, abort.
let shadowed = &checker.semantic.bindings[shadow.shadowed_id()];
if !matches!(
shadowed.kind,
BindingKind::Import(..)
| BindingKind::FromImport(..)
| BindingKind::SubmoduleImport(..)
| BindingKind::FutureImport
) {
continue;
}
// If the bindings are in different forks, abort.
if shadowed.source.is_none_or(|left| {
binding
.source
.is_none_or(|right| !checker.semantic.same_branch(left, right))
}) {
continue;
}
checker.report_diagnostic(
pyflakes::rules::ImportShadowedByLoopVar {
name: name.to_string(),
row: checker.compute_source_row(shadowed.start()),
},
binding.range(),
);
}
}
}
if checker.is_rule_enabled(Rule::RedefinedWhileUnused) {
pyflakes::rules::redefined_while_unused(checker, scope_id, scope);
// Index the redefined bindings by statement.
let mut redefinitions = FxHashMap::default();
for (name, binding_id) in scope.bindings() {
for shadow in checker.semantic.shadowed_bindings(scope_id, binding_id) {
// If the shadowing binding is a loop variable, abort, to avoid overlap
// with F402.
let binding = &checker.semantic.bindings[shadow.binding_id()];
if binding.kind.is_loop_var() {
continue;
}
// If the shadowed binding is used, abort.
let shadowed = &checker.semantic.bindings[shadow.shadowed_id()];
if shadowed.is_used() {
continue;
}
// If the shadowing binding isn't considered a "redefinition" of the
// shadowed binding, abort.
if !binding.redefines(shadowed) {
continue;
}
if shadow.same_scope() {
// If the symbol is a dummy variable, abort, unless the shadowed
// binding is an import.
if !matches!(
shadowed.kind,
BindingKind::Import(..)
| BindingKind::FromImport(..)
| BindingKind::SubmoduleImport(..)
| BindingKind::FutureImport
) && checker.settings().dummy_variable_rgx.is_match(name)
{
continue;
}
let Some(node_id) = shadowed.source else {
continue;
};
// If this is an overloaded function, abort.
if shadowed.kind.is_function_definition() {
if checker
.semantic
.statement(node_id)
.as_function_def_stmt()
.is_some_and(|function| {
visibility::is_overload(
&function.decorator_list,
&checker.semantic,
)
})
{
continue;
}
}
} else {
// Only enforce cross-scope shadowing for imports.
if !matches!(
shadowed.kind,
BindingKind::Import(..)
| BindingKind::FromImport(..)
| BindingKind::SubmoduleImport(..)
| BindingKind::FutureImport
) {
continue;
}
}
// If the bindings are in different forks, abort.
if shadowed.source.is_none_or(|left| {
binding
.source
.is_none_or(|right| !checker.semantic.same_branch(left, right))
}) {
continue;
}
redefinitions
.entry(binding.source)
.or_insert_with(Vec::new)
.push((shadowed, binding));
}
}
// Create a fix for each source statement.
let mut fixes = FxHashMap::default();
for (source, entries) in &redefinitions {
let Some(source) = source else {
continue;
};
let member_names = entries
.iter()
.filter_map(|(shadowed, binding)| {
if let Some(shadowed_import) = shadowed.as_any_import() {
if let Some(import) = binding.as_any_import() {
if shadowed_import.qualified_name() == import.qualified_name() {
return Some(import.member_name());
}
}
}
None
})
.collect::<Vec<_>>();
if !member_names.is_empty() {
let statement = checker.semantic.statement(*source);
let parent = checker.semantic.parent_statement(*source);
let Ok(edit) = fix::edits::remove_unused_imports(
member_names.iter().map(std::convert::AsRef::as_ref),
statement,
parent,
checker.locator(),
checker.stylist(),
checker.indexer(),
) else {
continue;
};
fixes.insert(
*source,
Fix::safe_edit(edit).isolate(Checker::isolation(
checker.semantic().parent_statement_id(*source),
)),
);
}
}
// Create diagnostics for each statement.
for (source, entries) in &redefinitions {
for (shadowed, binding) in entries {
let mut diagnostic = checker.report_diagnostic(
pyflakes::rules::RedefinedWhileUnused {
name: binding.name(checker.source()).to_string(),
row: checker.compute_source_row(shadowed.start()),
},
binding.range(),
);
if let Some(range) = binding.parent_range(&checker.semantic) {
diagnostic.set_parent(range.start());
}
if let Some(fix) = source.as_ref().and_then(|source| fixes.get(source)) {
diagnostic.set_fix(fix.clone());
}
}
}
}
if checker.source_type.is_stub()

View File

@@ -539,13 +539,14 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
let location = expr.range();
match pyflakes::format::FormatSummary::try_from(string_value.to_str()) {
Err(e) => {
// F521
checker.report_diagnostic_if_enabled(
pyflakes::rules::StringDotFormatInvalidFormat {
message: pyflakes::format::error_to_string(&e),
},
location,
);
if checker.is_rule_enabled(Rule::StringDotFormatInvalidFormat) {
checker.report_diagnostic(
pyflakes::rules::StringDotFormatInvalidFormat {
message: pyflakes::format::error_to_string(&e),
},
location,
);
}
}
Ok(summary) => {
if checker
@@ -1055,6 +1056,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
Rule::OsPathSplitext,
Rule::BuiltinOpen,
Rule::PyPath,
Rule::OsPathGetsize,
Rule::OsPathGetatime,
Rule::OsPathGetmtime,
Rule::OsPathGetctime,
@@ -1064,9 +1066,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
]) {
flake8_use_pathlib::rules::replaceable_by_pathlib(checker, call);
}
if checker.is_rule_enabled(Rule::OsPathGetsize) {
flake8_use_pathlib::rules::os_path_getsize(checker, call);
}
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
flake8_use_pathlib::rules::path_constructor_current_directory(checker, call);
}
@@ -1314,22 +1313,26 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
typ: CFormatErrorType::UnsupportedFormatChar(c),
..
}) => {
// F509
checker.report_diagnostic_if_enabled(
pyflakes::rules::PercentFormatUnsupportedFormatCharacter {
char: c,
},
location,
);
if checker
.is_rule_enabled(Rule::PercentFormatUnsupportedFormatCharacter)
{
checker.report_diagnostic(
pyflakes::rules::PercentFormatUnsupportedFormatCharacter {
char: c,
},
location,
);
}
}
Err(e) => {
// F501
checker.report_diagnostic_if_enabled(
pyflakes::rules::PercentFormatInvalidFormat {
message: e.to_string(),
},
location,
);
if checker.is_rule_enabled(Rule::PercentFormatInvalidFormat) {
checker.report_diagnostic(
pyflakes::rules::PercentFormatInvalidFormat {
message: e.to_string(),
},
location,
);
}
}
Ok(summary) => {
if checker.is_rule_enabled(Rule::PercentFormatExpectedMapping) {

View File

@@ -45,7 +45,18 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
if checker.is_rule_enabled(Rule::NonlocalWithoutBinding) {
pylint::rules::nonlocal_without_binding(checker, nonlocal);
if !checker.semantic.scope_id.is_global() {
for name in names {
if checker.semantic.nonlocal(name).is_none() {
checker.report_diagnostic(
pylint::rules::NonlocalWithoutBinding {
name: name.to_string(),
},
name.range(),
);
}
}
}
}
if checker.is_rule_enabled(Rule::NonlocalAndGlobal) {
pylint::rules::nonlocal_and_global(checker, nonlocal);
@@ -817,7 +828,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
pyflakes::rules::future_feature_not_defined(checker, alias);
}
} else if &alias.name == "*" {
// F406
if checker.is_rule_enabled(Rule::UndefinedLocalWithNestedImportStarUsage) {
if !matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
checker.report_diagnostic(
@@ -828,13 +838,14 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
);
}
}
// F403
checker.report_diagnostic_if_enabled(
pyflakes::rules::UndefinedLocalWithImportStar {
name: helpers::format_import_from(level, module).to_string(),
},
stmt.range(),
);
if checker.is_rule_enabled(Rule::UndefinedLocalWithImportStar) {
checker.report_diagnostic(
pyflakes::rules::UndefinedLocalWithImportStar {
name: helpers::format_import_from(level, module).to_string(),
},
stmt.range(),
);
}
}
if checker.is_rule_enabled(Rule::RelativeImports) {
flake8_tidy_imports::rules::banned_relative_import(

View File

@@ -13,15 +13,15 @@ pub(crate) fn unresolved_references(checker: &Checker) {
for reference in checker.semantic.unresolved_references() {
if reference.is_wildcard_import() {
// F406
checker.report_diagnostic_if_enabled(
pyflakes::rules::UndefinedLocalWithImportStarUsage {
name: reference.name(checker.source()).to_string(),
},
reference.range(),
);
if checker.is_rule_enabled(Rule::UndefinedLocalWithImportStarUsage) {
checker.report_diagnostic(
pyflakes::rules::UndefinedLocalWithImportStarUsage {
name: reference.name(checker.source()).to_string(),
},
reference.range(),
);
}
} else {
// F821
if checker.is_rule_enabled(Rule::UndefinedName) {
if checker.semantic.in_no_type_check() {
continue;

View File

@@ -28,7 +28,7 @@ use itertools::Itertools;
use log::debug;
use rustc_hash::{FxHashMap, FxHashSet};
use ruff_diagnostics::{Applicability, Fix, IsolationLevel};
use ruff_diagnostics::IsolationLevel;
use ruff_notebook::{CellOffsets, NotebookIndex};
use ruff_python_ast::helpers::{collect_import_from_member, is_docstring_stmt, to_module_path};
use ruff_python_ast::identifier::Identifier;
@@ -388,7 +388,7 @@ impl<'a> Checker<'a> {
/// Return a [`DiagnosticGuard`] for reporting a diagnostic.
///
/// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic
/// The guard derefs to a [`Diagnostic`], so it can be used to further modify the diagnostic
/// before it is added to the collection in the checker on `Drop`.
pub(crate) fn report_diagnostic<'chk, T: Violation>(
&'chk self,
@@ -401,8 +401,8 @@ impl<'a> Checker<'a> {
/// Return a [`DiagnosticGuard`] for reporting a diagnostic if the corresponding rule is
/// enabled.
///
/// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic
/// before it is added to the collection in the checker on `Drop`.
/// Prefer [`Checker::report_diagnostic`] in general because the conversion from a `Diagnostic`
/// to a `Rule` is somewhat expensive.
pub(crate) fn report_diagnostic_if_enabled<'chk, T: Violation>(
&'chk self,
kind: T,
@@ -625,7 +625,6 @@ impl SemanticSyntaxContext for Checker<'_> {
fn report_semantic_error(&self, error: SemanticSyntaxError) {
match error.kind {
SemanticSyntaxErrorKind::LateFutureImport => {
// F404
if self.is_rule_enabled(Rule::LateFutureImport) {
self.report_diagnostic(LateFutureImport, error.range);
}
@@ -647,7 +646,6 @@ impl SemanticSyntaxContext for Checker<'_> {
}
}
SemanticSyntaxErrorKind::ReturnOutsideFunction => {
// F706
if self.is_rule_enabled(Rule::ReturnOutsideFunction) {
self.report_diagnostic(ReturnOutsideFunction, error.range);
}
@@ -2810,7 +2808,6 @@ impl<'a> Checker<'a> {
Err(parse_error) => {
self.semantic.restore(snapshot);
// F722
if self.is_rule_enabled(Rule::ForwardAnnotationSyntaxError) {
self.report_type_diagnostic(
pyflakes::rules::ForwardAnnotationSyntaxError {
@@ -2958,7 +2955,6 @@ impl<'a> Checker<'a> {
self.semantic.flags -= SemanticModelFlags::DUNDER_ALL_DEFINITION;
} else {
if self.semantic.global_scope().uses_star_imports() {
// F405
if self.is_rule_enabled(Rule::UndefinedLocalWithImportStarUsage) {
self.report_diagnostic(
pyflakes::rules::UndefinedLocalWithImportStarUsage {
@@ -2969,7 +2965,6 @@ impl<'a> Checker<'a> {
.set_parent(definition.start());
}
} else {
// F822
if self.is_rule_enabled(Rule::UndefinedExport) {
if is_undefined_export_in_dunder_init_enabled(self.settings())
|| !self.path.ends_with("__init__.py")
@@ -3150,7 +3145,7 @@ impl<'a> LintContext<'a> {
/// Return a [`DiagnosticGuard`] for reporting a diagnostic.
///
/// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic
/// before it is added to the collection in the context on `Drop`.
/// before it is added to the collection in the collector on `Drop`.
pub(crate) fn report_diagnostic<'chk, T: Violation>(
&'chk self,
kind: T,
@@ -3159,26 +3154,23 @@ impl<'a> LintContext<'a> {
DiagnosticGuard {
context: self,
diagnostic: Some(OldDiagnostic::new(kind, range, &self.source_file)),
rule: T::rule(),
}
}
/// Return a [`DiagnosticGuard`] for reporting a diagnostic if the corresponding rule is
/// enabled.
///
/// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic
/// before it is added to the collection in the context on `Drop`.
/// Prefer [`DiagnosticsCollector::report_diagnostic`] in general because the conversion from an
/// `OldDiagnostic` to a `Rule` is somewhat expensive.
pub(crate) fn report_diagnostic_if_enabled<'chk, T: Violation>(
&'chk self,
kind: T,
range: TextRange,
) -> Option<DiagnosticGuard<'chk, 'a>> {
let rule = T::rule();
if self.is_rule_enabled(rule) {
if self.is_rule_enabled(T::rule()) {
Some(DiagnosticGuard {
context: self,
diagnostic: Some(OldDiagnostic::new(kind, range, &self.source_file)),
rule,
})
} else {
None
@@ -3230,7 +3222,6 @@ pub(crate) struct DiagnosticGuard<'a, 'b> {
///
/// This is always `Some` until the `Drop` (or `defuse`) call.
diagnostic: Option<OldDiagnostic>,
rule: Rule,
}
impl DiagnosticGuard<'_, '_> {
@@ -3243,50 +3234,6 @@ impl DiagnosticGuard<'_, '_> {
}
}
impl DiagnosticGuard<'_, '_> {
fn resolve_applicability(&self, fix: &Fix) -> Applicability {
self.context
.settings
.fix_safety
.resolve_applicability(self.rule, fix.applicability())
}
/// Set the [`Fix`] used to fix the diagnostic.
#[inline]
pub(crate) fn set_fix(&mut self, fix: Fix) {
if !self.context.rules.should_fix(self.rule) {
self.fix = None;
return;
}
let applicability = self.resolve_applicability(&fix);
self.fix = Some(fix.with_applicability(applicability));
}
/// Set the [`Fix`] used to fix the diagnostic, if the provided function returns `Ok`.
/// Otherwise, log the error.
#[inline]
pub(crate) fn try_set_fix(&mut self, func: impl FnOnce() -> anyhow::Result<Fix>) {
match func() {
Ok(fix) => self.set_fix(fix),
Err(err) => log::debug!("Failed to create fix for {}: {}", self.name(), err),
}
}
/// Set the [`Fix`] used to fix the diagnostic, if the provided function returns `Ok`.
/// Otherwise, log the error.
#[inline]
pub(crate) fn try_set_optional_fix(
&mut self,
func: impl FnOnce() -> anyhow::Result<Option<Fix>>,
) {
match func() {
Ok(None) => {}
Ok(Some(fix)) => self.set_fix(fix),
Err(err) => log::debug!("Failed to create fix for {}: {}", self.name(), err),
}
}
}
impl std::ops::Deref for DiagnosticGuard<'_, '_> {
type Target = OldDiagnostic;

View File

@@ -22,7 +22,6 @@ use crate::{Edit, Fix, Locator};
use super::ast::LintContext;
/// RUF100
pub(crate) fn check_noqa(
context: &mut LintContext,
path: &Path,

View File

@@ -3,8 +3,8 @@ use anyhow::{Result, bail};
use libcst_native::{
Arg, Attribute, Call, Comparison, CompoundStatement, Dict, Expression, FormattedString,
FormattedStringContent, FormattedStringExpression, FunctionDef, GeneratorExp, If, Import,
ImportAlias, ImportFrom, ImportNames, IndentedBlock, Lambda, ListComp, Module, SmallStatement,
Statement, Suite, Tuple, With,
ImportAlias, ImportFrom, ImportNames, IndentedBlock, Lambda, ListComp, Module, Name,
SmallStatement, Statement, Suite, Tuple, With,
};
use ruff_python_codegen::Stylist;
@@ -104,6 +104,14 @@ pub(crate) fn match_attribute<'a, 'b>(
}
}
pub(crate) fn match_name<'a, 'b>(expression: &'a Expression<'b>) -> Result<&'a Name<'b>> {
if let Expression::Name(name) = expression {
Ok(name)
} else {
bail!("Expected Expression::Name")
}
}
pub(crate) fn match_arg<'a, 'b>(call: &'a Call<'b>) -> Result<&'a Arg<'b>> {
if let Some(arg) = call.args.first() {
Ok(arg)

View File

@@ -749,16 +749,15 @@ x = 1 \
let diag = {
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
let mut iter = edits.into_iter();
let mut diagnostic = OldDiagnostic::new(
OldDiagnostic::new(
MissingNewlineAtEndOfFile, // The choice of rule here is arbitrary.
TextRange::default(),
&SourceFileBuilder::new("<filename>", "<code>").finish(),
);
diagnostic.fix = Some(Fix::safe_edits(
)
.with_fix(Fix::safe_edits(
iter.next().ok_or(anyhow!("expected edits nonempty"))?,
iter,
));
diagnostic
))
};
assert_eq!(apply_fixes([diag].iter(), &locator).code, expect);
Ok(())

View File

@@ -186,13 +186,12 @@ mod tests {
edit.into_iter()
.map(|edit| {
// The choice of rule here is arbitrary.
let mut diagnostic = OldDiagnostic::new(
let diagnostic = OldDiagnostic::new(
MissingNewlineAtEndOfFile,
edit.range(),
&SourceFileBuilder::new(filename, source).finish(),
);
diagnostic.fix = Some(Fix::safe_edit(edit));
diagnostic
diagnostic.with_fix(Fix::safe_edit(edit))
})
.collect()
}

View File

@@ -378,7 +378,32 @@ pub fn check_path(
let (mut diagnostics, source_file) = context.into_parts();
if !parsed.has_valid_syntax() {
if parsed.has_valid_syntax() {
// Remove fixes for any rules marked as unfixable.
for diagnostic in &mut diagnostics {
if diagnostic
.noqa_code()
.and_then(|code| code.rule())
.is_none_or(|rule| !settings.rules.should_fix(rule))
{
diagnostic.fix = None;
}
}
// Update fix applicability to account for overrides
if !settings.fix_safety.is_empty() {
for diagnostic in &mut diagnostics {
if let Some(fix) = diagnostic.fix.take() {
if let Some(rule) = diagnostic.noqa_code().and_then(|code| code.rule()) {
let fixed_applicability = settings
.fix_safety
.resolve_applicability(rule, fix.applicability());
diagnostic.set_fix(fix.with_applicability(fixed_applicability));
}
}
}
}
} else {
// Avoid fixing in case the source code contains syntax errors.
for diagnostic in &mut diagnostics {
diagnostic.fix = None;

View File

@@ -186,6 +186,41 @@ impl OldDiagnostic {
)
}
/// Consumes `self` and returns a new `Diagnostic` with the given `fix`.
#[inline]
#[must_use]
pub fn with_fix(mut self, fix: Fix) -> Self {
self.set_fix(fix);
self
}
/// Set the [`Fix`] used to fix the diagnostic.
#[inline]
pub fn set_fix(&mut self, fix: Fix) {
self.fix = Some(fix);
}
/// Set the [`Fix`] used to fix the diagnostic, if the provided function returns `Ok`.
/// Otherwise, log the error.
#[inline]
pub fn try_set_fix(&mut self, func: impl FnOnce() -> anyhow::Result<Fix>) {
match func() {
Ok(fix) => self.fix = Some(fix),
Err(err) => log::debug!("Failed to create fix for {}: {}", self.name(), err),
}
}
/// Set the [`Fix`] used to fix the diagnostic, if the provided function returns `Ok`.
/// Otherwise, log the error.
#[inline]
pub fn try_set_optional_fix(&mut self, func: impl FnOnce() -> anyhow::Result<Option<Fix>>) {
match func() {
Ok(None) => {}
Ok(Some(fix)) => self.fix = Some(fix),
Err(err) => log::debug!("Failed to create fix for {}: {}", self.name(), err),
}
}
/// Consumes `self` and returns a new `Diagnostic` with the given parent node.
#[inline]
#[must_use]

View File

@@ -50,11 +50,6 @@ pub(crate) const fn is_fix_manual_list_comprehension_enabled(settings: &LinterSe
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/18763
pub(crate) const fn is_fix_os_path_getsize_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/11436
// https://github.com/astral-sh/ruff/pull/11168
pub(crate) const fn is_dunder_init_fix_unused_import_enabled(settings: &LinterSettings) -> bool {
@@ -89,13 +84,3 @@ pub(crate) const fn is_ignore_init_files_in_useless_alias_enabled(
) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/18547
pub(crate) const fn is_invalid_async_mock_access_check_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/18867
pub(crate) const fn is_raise_exception_byte_string_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}

View File

@@ -11,7 +11,6 @@ use crate::registry::Rule;
use crate::rules::ruff::rules::InvalidPyprojectToml;
use crate::settings::LinterSettings;
/// RUF200
pub fn lint_pyproject_toml(
source_file: &SourceFile,
settings: &LinterSettings,

View File

@@ -215,12 +215,6 @@ pub enum Linter {
}
pub trait RuleNamespace: Sized {
/// Returns the prefix that every single code that ruff uses to identify
/// rules from this linter starts with. In the case that multiple
/// `#[prefix]`es are configured for the variant in the `Linter` enum
/// definition this is the empty string.
fn common_prefix(&self) -> &'static str;
/// Attempts to parse the given rule code. If the prefix is recognized
/// returns the respective variant along with the code with the common
/// prefix stripped.

View File

@@ -265,7 +265,6 @@ mod schema {
use strum::IntoEnumIterator;
use crate::RuleSelector;
use crate::registry::RuleNamespace;
use crate::rule_selector::{Linter, RuleCodePrefix};
impl JsonSchema for RuleSelector {

View File

@@ -59,17 +59,11 @@ use ruff_python_ast::PythonVersion;
/// return commons
/// ```
///
/// ## Fix safety
/// This fix is always unsafe, as adding/removing/changing a function parameter's
/// default value can change runtime behavior. Additionally, comments inside the
/// deprecated uses might be removed.
///
/// ## Availability
///
/// Because this rule relies on the third-party `typing_extensions` module for Python versions
/// before 3.9, if the target version is < 3.9 and `typing_extensions` imports have been
/// disabled by the [`lint.typing-extensions`] linter option the diagnostic will not be emitted
/// and no fix will be offered.
/// before 3.9, its diagnostic will not be emitted, and no fix will be offered, if
/// `typing_extensions` imports have been disabled by the [`lint.typing-extensions`] linter option.
///
/// ## Options
///

View File

@@ -273,7 +273,9 @@ pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators
],
) = (ops, comparators)
{
checker.report_diagnostic_if_enabled(SysVersionInfo1CmpInt, left.range());
if checker.is_rule_enabled(Rule::SysVersionInfo1CmpInt) {
checker.report_diagnostic(SysVersionInfo1CmpInt, left.range());
}
}
}
}
@@ -292,7 +294,9 @@ pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators
],
) = (ops, comparators)
{
checker.report_diagnostic_if_enabled(SysVersionInfoMinorCmpInt, left.range());
if checker.is_rule_enabled(Rule::SysVersionInfoMinorCmpInt) {
checker.report_diagnostic(SysVersionInfoMinorCmpInt, left.range());
}
}
}
@@ -306,9 +310,11 @@ pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators
) = (ops, comparators)
{
if value.len() == 1 {
checker.report_diagnostic_if_enabled(SysVersionCmpStr10, left.range());
} else {
checker.report_diagnostic_if_enabled(SysVersionCmpStr3, left.range());
if checker.is_rule_enabled(Rule::SysVersionCmpStr10) {
checker.report_diagnostic(SysVersionCmpStr10, left.range());
}
} else if checker.is_rule_enabled(Rule::SysVersionCmpStr3) {
checker.report_diagnostic(SysVersionCmpStr3, left.range());
}
}
}

View File

@@ -738,7 +738,6 @@ pub(crate) fn definition(
.suppress_none_returning
&& is_none_returning(body)
) {
// ANN206
if is_method && visibility::is_classmethod(decorator_list, checker.semantic()) {
if checker.is_rule_enabled(Rule::MissingReturnTypeClassMethod) {
let return_type = if is_stub_function(function, checker) {
@@ -766,7 +765,6 @@ pub(crate) fn definition(
diagnostics.push(diagnostic);
}
} else if is_method && visibility::is_staticmethod(decorator_list, checker.semantic()) {
// ANN205
if checker.is_rule_enabled(Rule::MissingReturnTypeStaticMethod) {
let return_type = if is_stub_function(function, checker) {
None
@@ -793,7 +791,6 @@ pub(crate) fn definition(
diagnostics.push(diagnostic);
}
} else if is_method && visibility::is_init(name) {
// ANN204
// Allow omission of return annotation in `__init__` functions, as long as at
// least one argument is typed.
if checker.is_rule_enabled(Rule::MissingReturnTypeSpecialMethod) {

View File

@@ -312,21 +312,25 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) {
Some(ShellKeyword {
truthiness: truthiness @ (Truthiness::True | Truthiness::Truthy),
}) => {
checker.report_diagnostic_if_enabled(
SubprocessPopenWithShellEqualsTrue {
safety: Safety::from(arg),
is_exact: matches!(truthiness, Truthiness::True),
},
call.func.range(),
);
if checker.is_rule_enabled(Rule::SubprocessPopenWithShellEqualsTrue) {
checker.report_diagnostic(
SubprocessPopenWithShellEqualsTrue {
safety: Safety::from(arg),
is_exact: matches!(truthiness, Truthiness::True),
},
call.func.range(),
);
}
}
// S603
_ => {
if !is_trusted_input(arg) {
checker.report_diagnostic_if_enabled(
SubprocessWithoutShellEqualsTrue,
call.func.range(),
);
if checker.is_rule_enabled(Rule::SubprocessWithoutShellEqualsTrue) {
checker.report_diagnostic(
SubprocessWithoutShellEqualsTrue,
call.func.range(),
);
}
}
}
}
@@ -336,12 +340,14 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) {
}) = shell_keyword
{
// S604
checker.report_diagnostic_if_enabled(
CallWithShellEqualsTrue {
is_exact: matches!(truthiness, Truthiness::True),
},
call.func.range(),
);
if checker.is_rule_enabled(Rule::CallWithShellEqualsTrue) {
checker.report_diagnostic(
CallWithShellEqualsTrue {
is_exact: matches!(truthiness, Truthiness::True),
},
call.func.range(),
);
}
}
// S605

View File

@@ -51,7 +51,6 @@ impl Violation for BooleanPositionalValueInCall {
}
}
/// FBT003
pub(crate) fn boolean_positional_value_in_call(checker: &Checker, call: &ast::ExprCall) {
if allow_boolean_trap(call, checker) {
return;

View File

@@ -46,7 +46,7 @@ impl Violation for StaticKeyDictComprehension {
}
}
/// B035, RUF011
/// RUF011
pub(crate) fn static_key_dict_comprehension(checker: &Checker, dict_comp: &ast::ExprDictComp) {
// Collect the bound names in the comprehension's generators.
let names = {

View File

@@ -58,7 +58,6 @@ impl Violation for CallDateFromtimestamp {
}
}
/// DTZ012
pub(crate) fn call_date_fromtimestamp(checker: &Checker, func: &Expr, location: TextRange) {
if !checker.semantic().seen_module(Modules::DATETIME) {
return;

View File

@@ -57,7 +57,6 @@ impl Violation for CallDateToday {
}
}
/// DTZ011
pub(crate) fn call_date_today(checker: &Checker, func: &Expr, location: TextRange) {
if !checker.semantic().seen_module(Modules::DATETIME) {
return;

View File

@@ -69,7 +69,6 @@ impl Violation for CallDatetimeFromtimestamp {
}
}
/// DTZ006
pub(crate) fn call_datetime_fromtimestamp(checker: &Checker, call: &ast::ExprCall) {
if !checker.semantic().seen_module(Modules::DATETIME) {
return;

View File

@@ -67,7 +67,6 @@ impl Violation for CallDatetimeNowWithoutTzinfo {
}
}
/// DTZ005
pub(crate) fn call_datetime_now_without_tzinfo(checker: &Checker, call: &ast::ExprCall) {
if !checker.semantic().seen_module(Modules::DATETIME) {
return;

View File

@@ -56,7 +56,6 @@ impl Violation for CallDatetimeToday {
}
}
/// DTZ002
pub(crate) fn call_datetime_today(checker: &Checker, func: &Expr, location: TextRange) {
if !checker.semantic().seen_module(Modules::DATETIME) {
return;

View File

@@ -60,7 +60,6 @@ impl Violation for CallDatetimeUtcfromtimestamp {
}
}
/// DTZ004
pub(crate) fn call_datetime_utcfromtimestamp(checker: &Checker, func: &Expr, location: TextRange) {
if !checker.semantic().seen_module(Modules::DATETIME) {
return;

View File

@@ -63,7 +63,6 @@ impl Violation for CallDatetimeWithoutTzinfo {
}
}
/// DTZ001
pub(crate) fn call_datetime_without_tzinfo(checker: &Checker, call: &ast::ExprCall) {
if !checker.semantic().seen_module(Modules::DATETIME) {
return;

View File

@@ -46,7 +46,6 @@ impl Violation for Debugger {
}
}
/// T100
/// Checks for the presence of a debugger call.
pub(crate) fn debugger_call(checker: &Checker, expr: &Expr, func: &Expr) {
if let Some(using_type) =
@@ -65,7 +64,6 @@ pub(crate) fn debugger_call(checker: &Checker, expr: &Expr, func: &Expr) {
}
}
/// T100
/// Checks for the presence of a debugger import.
pub(crate) fn debugger_import(checker: &Checker, stmt: &Stmt, module: Option<&str>, name: &str) {
if let Some(module) = module {

View File

@@ -9,7 +9,6 @@ mod tests {
use anyhow::Result;
use crate::registry::Rule;
use crate::settings::types::PreviewMode;
use crate::test::test_path;
use crate::{assert_diagnostics, settings};
@@ -45,17 +44,4 @@ mod tests {
assert_diagnostics!("custom", diagnostics);
Ok(())
}
#[test]
fn preview_string_exception() -> Result<()> {
let diagnostics = test_path(
Path::new("flake8_errmsg/EM101_byte_string.py"),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
..settings::LinterSettings::for_rule(Rule::RawStringInException)
},
)?;
assert_diagnostics!("preview", diagnostics);
Ok(())
}
}

View File

@@ -7,16 +7,12 @@ use ruff_text_size::Ranged;
use crate::Locator;
use crate::checkers::ast::Checker;
use crate::preview::is_raise_exception_byte_string_enabled;
use crate::registry::Rule;
use crate::{Edit, Fix, FixAvailability, Violation};
/// ## What it does
/// Checks for the use of string literals in exception constructors.
///
/// In [preview], this rule checks for byte string literals in
/// exception constructors.
///
/// ## Why is this bad?
/// Python includes the `raise` in the default traceback (and formatters
/// like Rich and IPython do too).
@@ -51,8 +47,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
/// raise RuntimeError(msg)
/// RuntimeError: 'Some value' is incorrect
/// ```
///
/// [preview]: https://docs.astral.sh/ruff/preview/
#[derive(ViolationMetadata)]
pub(crate) struct RawStringInException;
@@ -209,28 +203,6 @@ pub(crate) fn string_in_exception(checker: &Checker, stmt: &Stmt, exc: &Expr) {
}
}
}
// Check for byte string literals.
Expr::BytesLiteral(ast::ExprBytesLiteral { value: bytes, .. }) => {
if checker.settings().rules.enabled(Rule::RawStringInException) {
if bytes.len() >= checker.settings().flake8_errmsg.max_string_length
&& is_raise_exception_byte_string_enabled(checker.settings())
{
let mut diagnostic =
checker.report_diagnostic(RawStringInException, first.range());
if let Some(indentation) =
whitespace::indentation(checker.source(), stmt)
{
diagnostic.set_fix(generate_fix(
stmt,
first,
indentation,
checker.stylist(),
checker.locator(),
));
}
}
}
}
// Check for f-strings.
Expr::FString(_) => {
if checker.is_rule_enabled(Rule::FStringInException) {

View File

@@ -1,35 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_errmsg/mod.rs
---
EM101_byte_string.py:2:24: EM101 [*] Exception must not use a string literal, assign to variable first
|
1 | def f_byte():
2 | raise RuntimeError(b"This is an example exception")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ EM101
|
= help: Assign to variable; remove string literal
Unsafe fix
1 1 | def f_byte():
2 |- raise RuntimeError(b"This is an example exception")
2 |+ msg = b"This is an example exception"
3 |+ raise RuntimeError(msg)
3 4 |
4 5 |
5 6 | def f_byte_empty():
EM101_byte_string.py:6:24: EM101 [*] Exception must not use a string literal, assign to variable first
|
5 | def f_byte_empty():
6 | raise RuntimeError(b"")
| ^^^ EM101
|
= help: Assign to variable; remove string literal
Unsafe fix
3 3 |
4 4 |
5 5 | def f_byte_empty():
6 |- raise RuntimeError(b"")
6 |+ msg = b""
7 |+ raise RuntimeError(msg)

View File

@@ -1,11 +1,9 @@
use ruff_diagnostics::Fix;
use ruff_python_ast::Expr;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_semantic::{MemberNameImport, NameImport};
use ruff_text_size::Ranged;
use crate::AlwaysFixableViolation;
use crate::Violation;
use crate::checkers::ast::Checker;
/// ## What it does
@@ -63,10 +61,6 @@ use crate::checkers::ast::Checker;
/// def func(obj: dict[str, int | None]) -> None: ...
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as adding `from __future__ import annotations`
/// may change the semantics of the program.
///
/// ## Options
/// - `target-version`
#[derive(ViolationMetadata)]
@@ -74,16 +68,12 @@ pub(crate) struct FutureRewritableTypeAnnotation {
name: String,
}
impl AlwaysFixableViolation for FutureRewritableTypeAnnotation {
impl Violation for FutureRewritableTypeAnnotation {
#[derive_message_formats]
fn message(&self) -> String {
let FutureRewritableTypeAnnotation { name } = self;
format!("Add `from __future__ import annotations` to simplify `{name}`")
}
fn fix_title(&self) -> String {
"Add `from __future__ import annotations`".to_string()
}
}
/// FA100
@@ -93,17 +83,7 @@ pub(crate) fn future_rewritable_type_annotation(checker: &Checker, expr: &Expr)
.resolve_qualified_name(expr)
.map(|binding| binding.to_string());
let Some(name) = name else { return };
let import = &NameImport::ImportFrom(MemberNameImport::member(
"__future__".to_string(),
"annotations".to_string(),
));
checker
.report_diagnostic(FutureRewritableTypeAnnotation { name }, expr.range())
.set_fix(Fix::unsafe_edit(
checker
.importer()
.add_import(import, ruff_text_size::TextSize::default()),
));
if let Some(name) = name {
checker.report_diagnostic(FutureRewritableTypeAnnotation { name }, expr.range());
}
}

View File

@@ -1,32 +1,19 @@
---
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
snapshot_kind: text
---
edge_case.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
edge_case.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
|
5 | def main(_: List[int]) -> None:
| ^^^^ FA100
6 | a_list: t.List[str] = []
7 | a_list.append("hello")
|
= help: Add `from __future__ import annotations`
Unsafe fix
1 |+from __future__ import annotations
1 2 | from typing import List
2 3 | import typing as t
3 4 |
edge_case.py:6:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
edge_case.py:6:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
|
5 | def main(_: List[int]) -> None:
6 | a_list: t.List[str] = []
| ^^^^^^ FA100
7 | a_list.append("hello")
|
= help: Add `from __future__ import annotations`
Unsafe fix
1 |+from __future__ import annotations
1 2 | from typing import List
2 3 | import typing as t
3 4 |

View File

@@ -1,17 +1,11 @@
---
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
snapshot_kind: text
---
from_typing_import.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
from_typing_import.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
|
4 | def main() -> None:
5 | a_list: List[str] = []
| ^^^^ FA100
6 | a_list.append("hello")
|
= help: Add `from __future__ import annotations`
Unsafe fix
1 |+from __future__ import annotations
1 2 | from typing import List
2 3 |
3 4 |

View File

@@ -1,7 +1,8 @@
---
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
snapshot_kind: text
---
from_typing_import_many.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
from_typing_import_many.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
|
4 | def main() -> None:
5 | a_list: List[Optional[str]] = []
@@ -9,15 +10,8 @@ from_typing_import_many.py:5:13: FA100 [*] Add `from __future__ import annotatio
6 | a_list.append("hello")
7 | a_dict = cast(Dict[int | None, Union[int, Set[bool]]], {})
|
= help: Add `from __future__ import annotations`
Unsafe fix
1 |+from __future__ import annotations
1 2 | from typing import Dict, List, Optional, Set, Union, cast
2 3 |
3 4 |
from_typing_import_many.py:5:18: FA100 [*] Add `from __future__ import annotations` to simplify `typing.Optional`
from_typing_import_many.py:5:18: FA100 Add `from __future__ import annotations` to simplify `typing.Optional`
|
4 | def main() -> None:
5 | a_list: List[Optional[str]] = []
@@ -25,10 +19,3 @@ from_typing_import_many.py:5:18: FA100 [*] Add `from __future__ import annotatio
6 | a_list.append("hello")
7 | a_dict = cast(Dict[int | None, Union[int, Set[bool]]], {})
|
= help: Add `from __future__ import annotations`
Unsafe fix
1 |+from __future__ import annotations
1 2 | from typing import Dict, List, Optional, Set, Union, cast
2 3 |
3 4 |

View File

@@ -1,17 +1,11 @@
---
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
snapshot_kind: text
---
import_typing.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
import_typing.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
|
4 | def main() -> None:
5 | a_list: typing.List[str] = []
| ^^^^^^^^^^^ FA100
6 | a_list.append("hello")
|
= help: Add `from __future__ import annotations`
Unsafe fix
1 |+from __future__ import annotations
1 2 | import typing
2 3 |
3 4 |

View File

@@ -1,17 +1,11 @@
---
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
snapshot_kind: text
---
import_typing_as.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
import_typing_as.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
|
4 | def main() -> None:
5 | a_list: t.List[str] = []
| ^^^^^^ FA100
6 | a_list.append("hello")
|
= help: Add `from __future__ import annotations`
Unsafe fix
1 |+from __future__ import annotations
1 2 | import typing as t
2 3 |
3 4 |

View File

@@ -59,7 +59,6 @@ impl Violation for ExcInfoOutsideExceptHandler {
}
}
/// LOG014
pub(crate) fn exc_info_outside_except_handler(checker: &Checker, call: &ExprCall) {
let semantic = checker.semantic();

View File

@@ -47,16 +47,22 @@ fn check_msg(checker: &Checker, msg: &Expr) {
// Check for string concatenation and percent format.
Expr::BinOp(ast::ExprBinOp { op, .. }) => match op {
Operator::Add => {
checker.report_diagnostic_if_enabled(LoggingStringConcat, msg.range());
if checker.is_rule_enabled(Rule::LoggingStringConcat) {
checker.report_diagnostic(LoggingStringConcat, msg.range());
}
}
Operator::Mod => {
checker.report_diagnostic_if_enabled(LoggingPercentFormat, msg.range());
if checker.is_rule_enabled(Rule::LoggingPercentFormat) {
checker.report_diagnostic(LoggingPercentFormat, msg.range());
}
}
_ => {}
},
// Check for f-strings.
Expr::FString(_) => {
checker.report_diagnostic_if_enabled(LoggingFString, msg.range());
if checker.is_rule_enabled(Rule::LoggingFString) {
checker.report_diagnostic(LoggingFString, msg.range());
}
}
// Check for .format() calls.
Expr::Call(ast::ExprCall { func, .. }) => {
@@ -165,7 +171,7 @@ pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) {
_ => return,
};
// G001, G002, G003, G004
// G001 - G004
let msg_pos = usize::from(matches!(logging_call_type, LoggingCallType::LogCall));
if let Some(format_arg) = call.arguments.find_argument_value("msg", msg_pos) {
check_msg(checker, format_arg);
@@ -203,10 +209,14 @@ pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) {
if let LoggingCallType::LevelCall(logging_level) = logging_call_type {
match logging_level {
LoggingLevel::Error => {
checker.report_diagnostic_if_enabled(LoggingExcInfo, range);
if checker.is_rule_enabled(Rule::LoggingExcInfo) {
checker.report_diagnostic(LoggingExcInfo, range);
}
}
LoggingLevel::Exception => {
checker.report_diagnostic_if_enabled(LoggingRedundantExcInfo, exc_info.range());
if checker.is_rule_enabled(Rule::LoggingRedundantExcInfo) {
checker.report_diagnostic(LoggingRedundantExcInfo, exc_info.range());
}
}
_ => {}
}

View File

@@ -148,6 +148,8 @@ pub(crate) fn bad_version_info_comparison(checker: &Checker, test: &Expr, has_el
}
}
} else {
checker.report_diagnostic_if_enabled(BadVersionInfoComparison, test.range());
if checker.is_rule_enabled(Rule::BadVersionInfoComparison) {
checker.report_diagnostic(BadVersionInfoComparison, test.range());
}
}
}

View File

@@ -114,7 +114,9 @@ pub(crate) fn unrecognized_platform(checker: &Checker, test: &Expr) {
// "in" might also make sense but we don't currently have one.
if !matches!(op, CmpOp::Eq | CmpOp::NotEq) {
checker.report_diagnostic_if_enabled(UnrecognizedPlatformCheck, test.range());
if checker.is_rule_enabled(Rule::UnrecognizedPlatformCheck) {
checker.report_diagnostic(UnrecognizedPlatformCheck, test.range());
}
return;
}
@@ -132,6 +134,8 @@ pub(crate) fn unrecognized_platform(checker: &Checker, test: &Expr) {
}
}
} else {
checker.report_diagnostic_if_enabled(UnrecognizedPlatformCheck, test.range());
if checker.is_rule_enabled(Rule::UnrecognizedPlatformCheck) {
checker.report_diagnostic(UnrecognizedPlatformCheck, test.range());
}
}
}

View File

@@ -147,7 +147,9 @@ pub(crate) fn unrecognized_version_info(checker: &Checker, test: &Expr) {
if let Some(expected) = ExpectedComparator::try_from(left) {
version_check(checker, expected, test, *op, comparator);
} else {
checker.report_diagnostic_if_enabled(UnrecognizedVersionInfoCheck, test.range());
if checker.is_rule_enabled(Rule::UnrecognizedVersionInfoCheck) {
checker.report_diagnostic(UnrecognizedVersionInfoCheck, test.range());
}
}
}
@@ -161,25 +163,33 @@ fn version_check(
// Single digit comparison, e.g., `sys.version_info[0] == 2`.
if expected == ExpectedComparator::MajorDigit {
if !is_int_constant(comparator) {
checker.report_diagnostic_if_enabled(UnrecognizedVersionInfoCheck, test.range());
if checker.is_rule_enabled(Rule::UnrecognizedVersionInfoCheck) {
checker.report_diagnostic(UnrecognizedVersionInfoCheck, test.range());
}
}
return;
}
// Tuple comparison, e.g., `sys.version_info == (3, 4)`.
let Expr::Tuple(tuple) = comparator else {
checker.report_diagnostic_if_enabled(UnrecognizedVersionInfoCheck, test.range());
if checker.is_rule_enabled(Rule::UnrecognizedVersionInfoCheck) {
checker.report_diagnostic(UnrecognizedVersionInfoCheck, test.range());
}
return;
};
if !tuple.iter().all(is_int_constant) {
// All tuple elements must be integers, e.g., `sys.version_info == (3, 4)` instead of
// `sys.version_info == (3.0, 4)`.
checker.report_diagnostic_if_enabled(UnrecognizedVersionInfoCheck, test.range());
if checker.is_rule_enabled(Rule::UnrecognizedVersionInfoCheck) {
checker.report_diagnostic(UnrecognizedVersionInfoCheck, test.range());
}
} else if tuple.len() > 2 {
// Must compare against major and minor version only, e.g., `sys.version_info == (3, 4)`
// instead of `sys.version_info == (3, 4, 0)`.
checker.report_diagnostic_if_enabled(PatchVersionComparison, test.range());
if checker.is_rule_enabled(Rule::PatchVersionComparison) {
checker.report_diagnostic(PatchVersionComparison, test.range());
}
}
if checker.is_rule_enabled(Rule::WrongTupleLengthVersionComparison) {

View File

@@ -55,7 +55,6 @@ impl Violation for PytestFailWithoutMessage {
}
}
/// PT016
pub(crate) fn fail_call(checker: &Checker, call: &ast::ExprCall) {
if is_pytest_fail(&call.func, checker.semantic()) {
// Allow either `pytest.fail(reason="...")` (introduced in pytest 7.0) or

View File

@@ -225,7 +225,6 @@ fn check_useless_usefixtures(checker: &Checker, decorator: &Decorator, marker: &
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_deletion(decorator.range())));
}
/// PT023, PT026
pub(crate) fn marks(checker: &Checker, decorators: &[Decorator]) {
let enforce_parentheses = checker.is_rule_enabled(Rule::PytestIncorrectMarkParenthesesStyle);
let enforce_useless_usefixtures =

View File

@@ -170,7 +170,6 @@ const fn is_non_trivial_with_body(body: &[Stmt]) -> bool {
}
}
/// PT010
pub(crate) fn raises_call(checker: &Checker, call: &ast::ExprCall) {
if is_pytest_raises(&call.func, checker.semantic()) {
if checker.is_rule_enabled(Rule::PytestRaisesWithoutException) {
@@ -206,7 +205,6 @@ pub(crate) fn raises_call(checker: &Checker, call: &ast::ExprCall) {
}
}
/// PT012
pub(crate) fn complex_raises(checker: &Checker, stmt: &Stmt, items: &[WithItem], body: &[Stmt]) {
let raises_called = items.iter().any(|item| match &item.context_expr {
Expr::Call(ast::ExprCall { func, .. }) => is_pytest_raises(func, checker.semantic()),

View File

@@ -12,7 +12,6 @@ mod tests {
use crate::assert_diagnostics;
use crate::registry::Rule;
use crate::settings;
use crate::settings::types::PreviewMode;
use crate::test::test_path;
#[test_case(Path::new("full_name.py"))]
@@ -59,7 +58,6 @@ mod tests {
#[test_case(Rule::PyPath, Path::new("py_path_2.py"))]
#[test_case(Rule::PathConstructorCurrentDirectory, Path::new("PTH201.py"))]
#[test_case(Rule::OsPathGetsize, Path::new("PTH202.py"))]
#[test_case(Rule::OsPathGetsize, Path::new("PTH202_2.py"))]
#[test_case(Rule::OsPathGetatime, Path::new("PTH203.py"))]
#[test_case(Rule::OsPathGetmtime, Path::new("PTH204.py"))]
#[test_case(Rule::OsPathGetctime, Path::new("PTH205.py"))]
@@ -78,23 +76,4 @@ mod tests {
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::OsPathGetsize, Path::new("PTH202.py"))]
#[test_case(Rule::OsPathGetsize, Path::new("PTH202_2.py"))]
fn preview_flake8_use_pathlib(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("flake8_use_pathlib").join(path).as_path(),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -1,11 +1,6 @@
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::preview::is_fix_os_path_getsize_enabled;
use crate::{Applicability, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{Expr, ExprCall};
use ruff_text_size::Ranged;
use crate::Violation;
/// ## What it does
/// Checks for uses of `os.path.getsize`.
@@ -37,9 +32,6 @@ use ruff_text_size::Ranged;
/// it can be less performant than the lower-level alternatives that work directly with strings,
/// especially on older versions of Python.
///
/// ## Fix Safety
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
///
/// ## References
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
/// - [Python documentation: `os.path.getsize`](https://docs.python.org/3/library/os.path.html#os.path.getsize)
@@ -51,77 +43,8 @@ use ruff_text_size::Ranged;
pub(crate) struct OsPathGetsize;
impl Violation for OsPathGetsize {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
"`os.path.getsize` should be replaced by `Path.stat().st_size`".to_string()
}
fn fix_title(&self) -> Option<String> {
Some("Replace with `Path(...).stat().st_size`".to_string())
}
}
/// PTH202
pub(crate) fn os_path_getsize(checker: &Checker, call: &ExprCall) {
if !matches!(
checker
.semantic()
.resolve_qualified_name(&call.func)
.as_ref()
.map(QualifiedName::segments),
Some(["os", "path", "getsize"])
) {
return;
}
if call.arguments.len() != 1 {
return;
}
let Some(arg) = call.arguments.find_argument_value("filename", 0) else {
return;
};
let arg_code = checker.locator().slice(arg.range());
let range = call.range();
let applicability = if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
} else {
Applicability::Safe
};
let mut diagnostic = checker.report_diagnostic(OsPathGetsize, range);
if is_fix_os_path_getsize_enabled(checker.settings()) {
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("pathlib", "Path"),
call.start(),
checker.semantic(),
)?;
let replacement = if is_path_call(checker, arg) {
format!("{arg_code}.stat().st_size")
} else {
format!("{binding}({arg_code}).stat().st_size")
};
Ok(
Fix::safe_edits(Edit::range_replacement(replacement, range), [import_edit])
.with_applicability(applicability),
)
});
}
}
fn is_path_call(checker: &Checker, expr: &Expr) -> bool {
expr.as_call_expr().is_some_and(|expr_call| {
checker
.semantic()
.resolve_qualified_name(&expr_call.func)
.is_some_and(|name| matches!(name.segments(), ["pathlib", "Path"]))
})
}

View File

@@ -5,7 +5,7 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::rules::flake8_use_pathlib::rules::{
Glob, OsPathGetatime, OsPathGetctime, OsPathGetmtime,
Glob, OsPathGetatime, OsPathGetctime, OsPathGetmtime, OsPathGetsize,
};
use crate::rules::flake8_use_pathlib::violations::{
BuiltinOpen, Joiner, OsChmod, OsGetcwd, OsListdir, OsMakedirs, OsMkdir, OsPathAbspath,
@@ -194,6 +194,8 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
["os", "path", "samefile"] => checker.report_diagnostic_if_enabled(OsPathSamefile, range),
// PTH122
["os", "path", "splitext"] => checker.report_diagnostic_if_enabled(OsPathSplitext, range),
// PTH202
["os", "path", "getsize"] => checker.report_diagnostic_if_enabled(OsPathGetsize, range),
// PTH203
["os", "path", "getatime"] => checker.report_diagnostic_if_enabled(OsPathGetatime, range),
// PTH204

View File

@@ -1,357 +1,74 @@
---
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
---
PTH202.py:10:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
PTH202.py:6:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
6 | os.path.getsize("filename")
| ^^^^^^^^^^^^^^^ PTH202
7 | os.path.getsize(b"filename")
8 | os.path.getsize(Path("filename"))
|
PTH202.py:7:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
6 | os.path.getsize("filename")
7 | os.path.getsize(b"filename")
| ^^^^^^^^^^^^^^^ PTH202
8 | os.path.getsize(Path("filename"))
9 | os.path.getsize(__file__)
|
PTH202.py:8:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
6 | os.path.getsize("filename")
7 | os.path.getsize(b"filename")
8 | os.path.getsize(Path("filename"))
| ^^^^^^^^^^^^^^^ PTH202
9 | os.path.getsize(__file__)
|
PTH202.py:9:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
10 | os.path.getsize("filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
11 | os.path.getsize(b"filename")
12 | os.path.getsize(Path("filename"))
7 | os.path.getsize(b"filename")
8 | os.path.getsize(Path("filename"))
9 | os.path.getsize(__file__)
| ^^^^^^^^^^^^^^^ PTH202
10 |
11 | getsize("filename")
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:11:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
10 | os.path.getsize("filename")
11 | os.path.getsize(b"filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
12 | os.path.getsize(Path("filename"))
13 | os.path.getsize(__file__)
9 | os.path.getsize(__file__)
10 |
11 | getsize("filename")
| ^^^^^^^ PTH202
12 | getsize(b"filename")
13 | getsize(Path("filename"))
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:12:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
10 | os.path.getsize("filename")
11 | os.path.getsize(b"filename")
12 | os.path.getsize(Path("filename"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
13 | os.path.getsize(__file__)
11 | getsize("filename")
12 | getsize(b"filename")
| ^^^^^^^ PTH202
13 | getsize(Path("filename"))
14 | getsize(__file__)
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:13:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
11 | os.path.getsize(b"filename")
12 | os.path.getsize(Path("filename"))
13 | os.path.getsize(__file__)
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
14 |
15 | os.path.getsize(filename)
11 | getsize("filename")
12 | getsize(b"filename")
13 | getsize(Path("filename"))
| ^^^^^^^ PTH202
14 | getsize(__file__)
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:15:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
PTH202.py:14:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
13 | os.path.getsize(__file__)
14 |
15 | os.path.getsize(filename)
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
16 | os.path.getsize(filename1)
17 | os.path.getsize(filename2)
12 | getsize(b"filename")
13 | getsize(Path("filename"))
14 | getsize(__file__)
| ^^^^^^^ PTH202
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:16:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
15 | os.path.getsize(filename)
16 | os.path.getsize(filename1)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
17 | os.path.getsize(filename2)
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:17:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
15 | os.path.getsize(filename)
16 | os.path.getsize(filename1)
17 | os.path.getsize(filename2)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
18 |
19 | os.path.getsize(filename="filename")
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:19:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
17 | os.path.getsize(filename2)
18 |
19 | os.path.getsize(filename="filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
20 | os.path.getsize(filename=b"filename")
21 | os.path.getsize(filename=Path("filename"))
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:20:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
19 | os.path.getsize(filename="filename")
20 | os.path.getsize(filename=b"filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
21 | os.path.getsize(filename=Path("filename"))
22 | os.path.getsize(filename=__file__)
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:21:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
19 | os.path.getsize(filename="filename")
20 | os.path.getsize(filename=b"filename")
21 | os.path.getsize(filename=Path("filename"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
22 | os.path.getsize(filename=__file__)
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:22:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
20 | os.path.getsize(filename=b"filename")
21 | os.path.getsize(filename=Path("filename"))
22 | os.path.getsize(filename=__file__)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
23 |
24 | getsize("filename")
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:24:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
22 | os.path.getsize(filename=__file__)
23 |
24 | getsize("filename")
| ^^^^^^^^^^^^^^^^^^^ PTH202
25 | getsize(b"filename")
26 | getsize(Path("filename"))
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:25:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
24 | getsize("filename")
25 | getsize(b"filename")
| ^^^^^^^^^^^^^^^^^^^^ PTH202
26 | getsize(Path("filename"))
27 | getsize(__file__)
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:26:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
24 | getsize("filename")
25 | getsize(b"filename")
26 | getsize(Path("filename"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
27 | getsize(__file__)
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:27:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
25 | getsize(b"filename")
26 | getsize(Path("filename"))
27 | getsize(__file__)
| ^^^^^^^^^^^^^^^^^ PTH202
28 |
29 | getsize(filename="filename")
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:29:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
27 | getsize(__file__)
28 |
29 | getsize(filename="filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
30 | getsize(filename=b"filename")
31 | getsize(filename=Path("filename"))
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:30:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
29 | getsize(filename="filename")
30 | getsize(filename=b"filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
31 | getsize(filename=Path("filename"))
32 | getsize(filename=__file__)
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:31:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
29 | getsize(filename="filename")
30 | getsize(filename=b"filename")
31 | getsize(filename=Path("filename"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
32 | getsize(filename=__file__)
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:32:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
30 | getsize(filename=b"filename")
31 | getsize(filename=Path("filename"))
32 | getsize(filename=__file__)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
33 |
34 | getsize(filename)
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:34:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
32 | getsize(filename=__file__)
33 |
34 | getsize(filename)
| ^^^^^^^^^^^^^^^^^ PTH202
35 | getsize(filename1)
36 | getsize(filename2)
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:35:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
34 | getsize(filename)
35 | getsize(filename1)
| ^^^^^^^^^^^^^^^^^^ PTH202
36 | getsize(filename2)
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:36:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
34 | getsize(filename)
35 | getsize(filename1)
36 | getsize(filename2)
| ^^^^^^^^^^^^^^^^^^ PTH202
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:39:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
39 | / os.path.getsize(
40 | | "filename", # comment
41 | | )
| |_^ PTH202
42 |
43 | os.path.getsize(
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:43:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
41 | )
42 |
43 | / os.path.getsize(
44 | | # comment
45 | | "filename"
46 | | ,
47 | | # comment
48 | | )
| |_^ PTH202
49 |
50 | os.path.getsize(
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:50:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
48 | )
49 |
50 | / os.path.getsize(
51 | | # comment
52 | | b"filename"
53 | | # comment
54 | | )
| |_^ PTH202
55 |
56 | os.path.getsize( # comment
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:56:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
54 | )
55 |
56 | / os.path.getsize( # comment
57 | | Path(__file__)
58 | | # comment
59 | | ) # comment
| |_^ PTH202
60 |
61 | getsize( # comment
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:61:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
59 | ) # comment
60 |
61 | / getsize( # comment
62 | | "filename")
| |_______________^ PTH202
63 |
64 | getsize( # comment
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:64:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
62 | "filename")
63 |
64 | / getsize( # comment
65 | | b"filename",
66 | | #comment
67 | | )
| |_^ PTH202
68 |
69 | os.path.getsize("file" + "name")
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:69:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
67 | )
68 |
69 | os.path.getsize("file" + "name")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
70 |
71 | getsize \
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:71:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
69 | os.path.getsize("file" + "name")
70 |
71 | / getsize \
72 | | \
73 | | \
74 | | ( # comment
75 | | "filename",
76 | | )
| |_____^ PTH202
77 |
78 | getsize(Path("filename").resolve())
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:78:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
76 | )
77 |
78 | getsize(Path("filename").resolve())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
79 |
80 | import pathlib
|
= help: Replace with `Path(...).stat().st_size`
PTH202.py:82:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
80 | import pathlib
81 |
82 | os.path.getsize(pathlib.Path("filename"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
= help: Replace with `Path(...).stat().st_size`

View File

@@ -1,31 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
---
PTH202_2.py:3:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
1 | import os
2 |
3 | os.path.getsize(filename="filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
4 | os.path.getsize(filename=b"filename")
5 | os.path.getsize(filename=__file__)
|
= help: Replace with `Path(...).stat().st_size`
PTH202_2.py:4:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
3 | os.path.getsize(filename="filename")
4 | os.path.getsize(filename=b"filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
5 | os.path.getsize(filename=__file__)
|
= help: Replace with `Path(...).stat().st_size`
PTH202_2.py:5:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
3 | os.path.getsize(filename="filename")
4 | os.path.getsize(filename=b"filename")
5 | os.path.getsize(filename=__file__)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
= help: Replace with `Path(...).stat().st_size`

View File

@@ -1,697 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
---
PTH202.py:10:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
10 | os.path.getsize("filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
11 | os.path.getsize(b"filename")
12 | os.path.getsize(Path("filename"))
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
7 7 | filename2 = Path("filename")
8 8 |
9 9 |
10 |-os.path.getsize("filename")
10 |+Path("filename").stat().st_size
11 11 | os.path.getsize(b"filename")
12 12 | os.path.getsize(Path("filename"))
13 13 | os.path.getsize(__file__)
PTH202.py:11:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
10 | os.path.getsize("filename")
11 | os.path.getsize(b"filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
12 | os.path.getsize(Path("filename"))
13 | os.path.getsize(__file__)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
8 8 |
9 9 |
10 10 | os.path.getsize("filename")
11 |-os.path.getsize(b"filename")
11 |+Path(b"filename").stat().st_size
12 12 | os.path.getsize(Path("filename"))
13 13 | os.path.getsize(__file__)
14 14 |
PTH202.py:12:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
10 | os.path.getsize("filename")
11 | os.path.getsize(b"filename")
12 | os.path.getsize(Path("filename"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
13 | os.path.getsize(__file__)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
9 9 |
10 10 | os.path.getsize("filename")
11 11 | os.path.getsize(b"filename")
12 |-os.path.getsize(Path("filename"))
12 |+Path("filename").stat().st_size
13 13 | os.path.getsize(__file__)
14 14 |
15 15 | os.path.getsize(filename)
PTH202.py:13:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
11 | os.path.getsize(b"filename")
12 | os.path.getsize(Path("filename"))
13 | os.path.getsize(__file__)
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
14 |
15 | os.path.getsize(filename)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
10 10 | os.path.getsize("filename")
11 11 | os.path.getsize(b"filename")
12 12 | os.path.getsize(Path("filename"))
13 |-os.path.getsize(__file__)
13 |+Path(__file__).stat().st_size
14 14 |
15 15 | os.path.getsize(filename)
16 16 | os.path.getsize(filename1)
PTH202.py:15:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
13 | os.path.getsize(__file__)
14 |
15 | os.path.getsize(filename)
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
16 | os.path.getsize(filename1)
17 | os.path.getsize(filename2)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
12 12 | os.path.getsize(Path("filename"))
13 13 | os.path.getsize(__file__)
14 14 |
15 |-os.path.getsize(filename)
15 |+Path(filename).stat().st_size
16 16 | os.path.getsize(filename1)
17 17 | os.path.getsize(filename2)
18 18 |
PTH202.py:16:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
15 | os.path.getsize(filename)
16 | os.path.getsize(filename1)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
17 | os.path.getsize(filename2)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
13 13 | os.path.getsize(__file__)
14 14 |
15 15 | os.path.getsize(filename)
16 |-os.path.getsize(filename1)
16 |+Path(filename1).stat().st_size
17 17 | os.path.getsize(filename2)
18 18 |
19 19 | os.path.getsize(filename="filename")
PTH202.py:17:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
15 | os.path.getsize(filename)
16 | os.path.getsize(filename1)
17 | os.path.getsize(filename2)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
18 |
19 | os.path.getsize(filename="filename")
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
14 14 |
15 15 | os.path.getsize(filename)
16 16 | os.path.getsize(filename1)
17 |-os.path.getsize(filename2)
17 |+Path(filename2).stat().st_size
18 18 |
19 19 | os.path.getsize(filename="filename")
20 20 | os.path.getsize(filename=b"filename")
PTH202.py:19:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
17 | os.path.getsize(filename2)
18 |
19 | os.path.getsize(filename="filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
20 | os.path.getsize(filename=b"filename")
21 | os.path.getsize(filename=Path("filename"))
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
16 16 | os.path.getsize(filename1)
17 17 | os.path.getsize(filename2)
18 18 |
19 |-os.path.getsize(filename="filename")
19 |+Path("filename").stat().st_size
20 20 | os.path.getsize(filename=b"filename")
21 21 | os.path.getsize(filename=Path("filename"))
22 22 | os.path.getsize(filename=__file__)
PTH202.py:20:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
19 | os.path.getsize(filename="filename")
20 | os.path.getsize(filename=b"filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
21 | os.path.getsize(filename=Path("filename"))
22 | os.path.getsize(filename=__file__)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
17 17 | os.path.getsize(filename2)
18 18 |
19 19 | os.path.getsize(filename="filename")
20 |-os.path.getsize(filename=b"filename")
20 |+Path(b"filename").stat().st_size
21 21 | os.path.getsize(filename=Path("filename"))
22 22 | os.path.getsize(filename=__file__)
23 23 |
PTH202.py:21:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
19 | os.path.getsize(filename="filename")
20 | os.path.getsize(filename=b"filename")
21 | os.path.getsize(filename=Path("filename"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
22 | os.path.getsize(filename=__file__)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
18 18 |
19 19 | os.path.getsize(filename="filename")
20 20 | os.path.getsize(filename=b"filename")
21 |-os.path.getsize(filename=Path("filename"))
21 |+Path("filename").stat().st_size
22 22 | os.path.getsize(filename=__file__)
23 23 |
24 24 | getsize("filename")
PTH202.py:22:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
20 | os.path.getsize(filename=b"filename")
21 | os.path.getsize(filename=Path("filename"))
22 | os.path.getsize(filename=__file__)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
23 |
24 | getsize("filename")
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
19 19 | os.path.getsize(filename="filename")
20 20 | os.path.getsize(filename=b"filename")
21 21 | os.path.getsize(filename=Path("filename"))
22 |-os.path.getsize(filename=__file__)
22 |+Path(__file__).stat().st_size
23 23 |
24 24 | getsize("filename")
25 25 | getsize(b"filename")
PTH202.py:24:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
22 | os.path.getsize(filename=__file__)
23 |
24 | getsize("filename")
| ^^^^^^^^^^^^^^^^^^^ PTH202
25 | getsize(b"filename")
26 | getsize(Path("filename"))
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
21 21 | os.path.getsize(filename=Path("filename"))
22 22 | os.path.getsize(filename=__file__)
23 23 |
24 |-getsize("filename")
24 |+Path("filename").stat().st_size
25 25 | getsize(b"filename")
26 26 | getsize(Path("filename"))
27 27 | getsize(__file__)
PTH202.py:25:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
24 | getsize("filename")
25 | getsize(b"filename")
| ^^^^^^^^^^^^^^^^^^^^ PTH202
26 | getsize(Path("filename"))
27 | getsize(__file__)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
22 22 | os.path.getsize(filename=__file__)
23 23 |
24 24 | getsize("filename")
25 |-getsize(b"filename")
25 |+Path(b"filename").stat().st_size
26 26 | getsize(Path("filename"))
27 27 | getsize(__file__)
28 28 |
PTH202.py:26:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
24 | getsize("filename")
25 | getsize(b"filename")
26 | getsize(Path("filename"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
27 | getsize(__file__)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
23 23 |
24 24 | getsize("filename")
25 25 | getsize(b"filename")
26 |-getsize(Path("filename"))
26 |+Path("filename").stat().st_size
27 27 | getsize(__file__)
28 28 |
29 29 | getsize(filename="filename")
PTH202.py:27:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
25 | getsize(b"filename")
26 | getsize(Path("filename"))
27 | getsize(__file__)
| ^^^^^^^^^^^^^^^^^ PTH202
28 |
29 | getsize(filename="filename")
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
24 24 | getsize("filename")
25 25 | getsize(b"filename")
26 26 | getsize(Path("filename"))
27 |-getsize(__file__)
27 |+Path(__file__).stat().st_size
28 28 |
29 29 | getsize(filename="filename")
30 30 | getsize(filename=b"filename")
PTH202.py:29:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
27 | getsize(__file__)
28 |
29 | getsize(filename="filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
30 | getsize(filename=b"filename")
31 | getsize(filename=Path("filename"))
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
26 26 | getsize(Path("filename"))
27 27 | getsize(__file__)
28 28 |
29 |-getsize(filename="filename")
29 |+Path("filename").stat().st_size
30 30 | getsize(filename=b"filename")
31 31 | getsize(filename=Path("filename"))
32 32 | getsize(filename=__file__)
PTH202.py:30:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
29 | getsize(filename="filename")
30 | getsize(filename=b"filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
31 | getsize(filename=Path("filename"))
32 | getsize(filename=__file__)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
27 27 | getsize(__file__)
28 28 |
29 29 | getsize(filename="filename")
30 |-getsize(filename=b"filename")
30 |+Path(b"filename").stat().st_size
31 31 | getsize(filename=Path("filename"))
32 32 | getsize(filename=__file__)
33 33 |
PTH202.py:31:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
29 | getsize(filename="filename")
30 | getsize(filename=b"filename")
31 | getsize(filename=Path("filename"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
32 | getsize(filename=__file__)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
28 28 |
29 29 | getsize(filename="filename")
30 30 | getsize(filename=b"filename")
31 |-getsize(filename=Path("filename"))
31 |+Path("filename").stat().st_size
32 32 | getsize(filename=__file__)
33 33 |
34 34 | getsize(filename)
PTH202.py:32:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
30 | getsize(filename=b"filename")
31 | getsize(filename=Path("filename"))
32 | getsize(filename=__file__)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
33 |
34 | getsize(filename)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
29 29 | getsize(filename="filename")
30 30 | getsize(filename=b"filename")
31 31 | getsize(filename=Path("filename"))
32 |-getsize(filename=__file__)
32 |+Path(__file__).stat().st_size
33 33 |
34 34 | getsize(filename)
35 35 | getsize(filename1)
PTH202.py:34:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
32 | getsize(filename=__file__)
33 |
34 | getsize(filename)
| ^^^^^^^^^^^^^^^^^ PTH202
35 | getsize(filename1)
36 | getsize(filename2)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
31 31 | getsize(filename=Path("filename"))
32 32 | getsize(filename=__file__)
33 33 |
34 |-getsize(filename)
34 |+Path(filename).stat().st_size
35 35 | getsize(filename1)
36 36 | getsize(filename2)
37 37 |
PTH202.py:35:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
34 | getsize(filename)
35 | getsize(filename1)
| ^^^^^^^^^^^^^^^^^^ PTH202
36 | getsize(filename2)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
32 32 | getsize(filename=__file__)
33 33 |
34 34 | getsize(filename)
35 |-getsize(filename1)
35 |+Path(filename1).stat().st_size
36 36 | getsize(filename2)
37 37 |
38 38 |
PTH202.py:36:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
34 | getsize(filename)
35 | getsize(filename1)
36 | getsize(filename2)
| ^^^^^^^^^^^^^^^^^^ PTH202
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
33 33 |
34 34 | getsize(filename)
35 35 | getsize(filename1)
36 |-getsize(filename2)
36 |+Path(filename2).stat().st_size
37 37 |
38 38 |
39 39 | os.path.getsize(
PTH202.py:39:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
39 | / os.path.getsize(
40 | | "filename", # comment
41 | | )
| |_^ PTH202
42 |
43 | os.path.getsize(
|
= help: Replace with `Path(...).stat().st_size`
Unsafe fix
36 36 | getsize(filename2)
37 37 |
38 38 |
39 |-os.path.getsize(
40 |- "filename", # comment
41 |-)
39 |+Path("filename").stat().st_size
42 40 |
43 41 | os.path.getsize(
44 42 | # comment
PTH202.py:43:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
41 | )
42 |
43 | / os.path.getsize(
44 | | # comment
45 | | "filename"
46 | | ,
47 | | # comment
48 | | )
| |_^ PTH202
49 |
50 | os.path.getsize(
|
= help: Replace with `Path(...).stat().st_size`
Unsafe fix
40 40 | "filename", # comment
41 41 | )
42 42 |
43 |-os.path.getsize(
44 |- # comment
45 |- "filename"
46 |- ,
47 |- # comment
48 |-)
43 |+Path("filename").stat().st_size
49 44 |
50 45 | os.path.getsize(
51 46 | # comment
PTH202.py:50:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
48 | )
49 |
50 | / os.path.getsize(
51 | | # comment
52 | | b"filename"
53 | | # comment
54 | | )
| |_^ PTH202
55 |
56 | os.path.getsize( # comment
|
= help: Replace with `Path(...).stat().st_size`
Unsafe fix
47 47 | # comment
48 48 | )
49 49 |
50 |-os.path.getsize(
51 |- # comment
52 |- b"filename"
53 |- # comment
54 |-)
50 |+Path(b"filename").stat().st_size
55 51 |
56 52 | os.path.getsize( # comment
57 53 | Path(__file__)
PTH202.py:56:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
54 | )
55 |
56 | / os.path.getsize( # comment
57 | | Path(__file__)
58 | | # comment
59 | | ) # comment
| |_^ PTH202
60 |
61 | getsize( # comment
|
= help: Replace with `Path(...).stat().st_size`
Unsafe fix
53 53 | # comment
54 54 | )
55 55 |
56 |-os.path.getsize( # comment
57 |- Path(__file__)
58 |- # comment
59 |-) # comment
56 |+Path(__file__).stat().st_size # comment
60 57 |
61 58 | getsize( # comment
62 59 | "filename")
PTH202.py:61:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
59 | ) # comment
60 |
61 | / getsize( # comment
62 | | "filename")
| |_______________^ PTH202
63 |
64 | getsize( # comment
|
= help: Replace with `Path(...).stat().st_size`
Unsafe fix
58 58 | # comment
59 59 | ) # comment
60 60 |
61 |-getsize( # comment
62 |- "filename")
61 |+Path("filename").stat().st_size
63 62 |
64 63 | getsize( # comment
65 64 | b"filename",
PTH202.py:64:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
62 | "filename")
63 |
64 | / getsize( # comment
65 | | b"filename",
66 | | #comment
67 | | )
| |_^ PTH202
68 |
69 | os.path.getsize("file" + "name")
|
= help: Replace with `Path(...).stat().st_size`
Unsafe fix
61 61 | getsize( # comment
62 62 | "filename")
63 63 |
64 |-getsize( # comment
65 |- b"filename",
66 |- #comment
67 |-)
64 |+Path(b"filename").stat().st_size
68 65 |
69 66 | os.path.getsize("file" + "name")
70 67 |
PTH202.py:69:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
67 | )
68 |
69 | os.path.getsize("file" + "name")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
70 |
71 | getsize \
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
66 66 | #comment
67 67 | )
68 68 |
69 |-os.path.getsize("file" + "name")
69 |+Path("file" + "name").stat().st_size
70 70 |
71 71 | getsize \
72 72 | \
PTH202.py:71:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
69 | os.path.getsize("file" + "name")
70 |
71 | / getsize \
72 | | \
73 | | \
74 | | ( # comment
75 | | "filename",
76 | | )
| |_____^ PTH202
77 |
78 | getsize(Path("filename").resolve())
|
= help: Replace with `Path(...).stat().st_size`
Unsafe fix
68 68 |
69 69 | os.path.getsize("file" + "name")
70 70 |
71 |-getsize \
72 |-\
73 |-\
74 |- ( # comment
75 |- "filename",
76 |- )
71 |+Path("filename").stat().st_size
77 72 |
78 73 | getsize(Path("filename").resolve())
79 74 |
PTH202.py:78:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
76 | )
77 |
78 | getsize(Path("filename").resolve())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
79 |
80 | import pathlib
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
75 75 | "filename",
76 76 | )
77 77 |
78 |-getsize(Path("filename").resolve())
78 |+Path(Path("filename").resolve()).stat().st_size
79 79 |
80 80 | import pathlib
81 81 |
PTH202.py:82:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
80 | import pathlib
81 |
82 | os.path.getsize(pathlib.Path("filename"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
79 79 |
80 80 | import pathlib
81 81 |
82 |-os.path.getsize(pathlib.Path("filename"))
82 |+pathlib.Path("filename").stat().st_size

View File

@@ -1,58 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
---
PTH202_2.py:3:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
1 | import os
2 |
3 | os.path.getsize(filename="filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
4 | os.path.getsize(filename=b"filename")
5 | os.path.getsize(filename=__file__)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
1 1 | import os
2 |+import pathlib
2 3 |
3 |-os.path.getsize(filename="filename")
4 |+pathlib.Path("filename").stat().st_size
4 5 | os.path.getsize(filename=b"filename")
5 6 | os.path.getsize(filename=__file__)
PTH202_2.py:4:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
3 | os.path.getsize(filename="filename")
4 | os.path.getsize(filename=b"filename")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
5 | os.path.getsize(filename=__file__)
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
1 1 | import os
2 |+import pathlib
2 3 |
3 4 | os.path.getsize(filename="filename")
4 |-os.path.getsize(filename=b"filename")
5 |+pathlib.Path(b"filename").stat().st_size
5 6 | os.path.getsize(filename=__file__)
PTH202_2.py:5:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
3 | os.path.getsize(filename="filename")
4 | os.path.getsize(filename=b"filename")
5 | os.path.getsize(filename=__file__)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
= help: Replace with `Path(...).stat().st_size`
Safe fix
1 1 | import os
2 |+import pathlib
2 3 |
3 4 | os.path.getsize(filename="filename")
4 5 | os.path.getsize(filename=b"filename")
5 |-os.path.getsize(filename=__file__)
6 |+pathlib.Path(__file__).stat().st_size

View File

@@ -154,7 +154,6 @@ fn get_complexity_number(stmts: &[Stmt]) -> usize {
complexity
}
/// C901
pub(crate) fn function_is_too_complex(
checker: &Checker,
stmt: &Stmt,

View File

@@ -181,19 +181,15 @@ pub(crate) fn call(checker: &Checker, func: &Expr) {
let range = func.range();
match attr.as_str() {
// PD003
"isnull" if checker.is_rule_enabled(Rule::PandasUseOfDotIsNull) => {
checker.report_diagnostic(PandasUseOfDotIsNull, range);
}
// PD004
"notnull" if checker.is_rule_enabled(Rule::PandasUseOfDotNotNull) => {
checker.report_diagnostic(PandasUseOfDotNotNull, range);
}
// PD010
"pivot" | "unstack" if checker.is_rule_enabled(Rule::PandasUseOfDotPivotOrUnstack) => {
checker.report_diagnostic(PandasUseOfDotPivotOrUnstack, range);
}
// PD013
"stack" if checker.is_rule_enabled(Rule::PandasUseOfDotStack) => {
checker.report_diagnostic(PandasUseOfDotStack, range);
}

View File

@@ -163,15 +163,12 @@ pub(crate) fn subscript(checker: &Checker, value: &Expr, expr: &Expr) {
let range = expr.range();
match attr.as_str() {
// PD007
"ix" if checker.is_rule_enabled(Rule::PandasUseOfDotIx) => {
checker.report_diagnostic(PandasUseOfDotIx, range)
}
// PD008
"at" if checker.is_rule_enabled(Rule::PandasUseOfDotAt) => {
checker.report_diagnostic(PandasUseOfDotAt, range)
}
// PD009
"iat" if checker.is_rule_enabled(Rule::PandasUseOfDotIat) => {
checker.report_diagnostic(PandasUseOfDotIat, range)
}

View File

@@ -4,7 +4,7 @@ use ruff_python_ast::{
};
use ruff_python_semantic::{Binding, analyze::typing::is_dict};
use ruff_source_file::LineRanges;
use ruff_text_size::{Ranged, TextRange, TextSize};
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
use crate::preview::is_fix_manual_dict_comprehension_enabled;
@@ -139,11 +139,28 @@ pub(crate) fn manual_dict_comprehension(checker: &Checker, for_stmt: &ast::StmtF
};
// If any references to a target variable are after the loop,
// then removing the loop would cause a NameError. Make sure none
// of the variables are used outside the for loop.
if has_post_loop_references(checker, target, for_stmt.end()) {
return;
}
// then removing the loop would cause a NameError
let any_references_after_for_loop = |target: &Expr| {
let target_binding = checker
.semantic()
.bindings
.iter()
.find(|binding| target.range() == binding.range);
debug_assert!(
target_binding.is_some(),
"for-loop target binding must exist"
);
let Some(target_binding) = target_binding else {
// All uses of this function will early-return if this returns true, so this must early-return the rule
return true;
};
target_binding
.references()
.map(|reference| checker.semantic().reference(reference))
.any(|other_reference| other_reference.start() > for_stmt.end())
};
match target {
Expr::Tuple(tuple) => {
@@ -159,6 +176,10 @@ pub(crate) fn manual_dict_comprehension(checker: &Checker, for_stmt: &ast::StmtF
{
return;
}
// Make sure none of the variables are used outside the for loop
if tuple.iter().any(any_references_after_for_loop) {
return;
}
}
Expr::Name(_) => {
if ComparableExpr::from(key) != ComparableExpr::from(target) {
@@ -167,6 +188,12 @@ pub(crate) fn manual_dict_comprehension(checker: &Checker, for_stmt: &ast::StmtF
if ComparableExpr::from(value) != ComparableExpr::from(target) {
return;
}
// We know that `target` contains an ExprName, but closures can't take `&impl Ranged`,
// so we pass `target` itself instead of the inner ExprName
if any_references_after_for_loop(target) {
return;
}
}
_ => return,
}
@@ -353,18 +380,9 @@ fn convert_to_dict_comprehension(
} else {
"for"
};
// Handles the case where `key` has a trailing comma, e.g, `dict[x,] = y`
let key_range = if let Expr::Tuple(ast::ExprTuple { elts, .. }) = key {
let [expr] = elts.as_slice() else {
return None;
};
expr.range()
} else {
key.range()
};
let elt_str = format!(
"{}: {}",
locator.slice(key_range),
locator.slice(key.range()),
locator.slice(value.range())
);
@@ -455,25 +473,3 @@ enum DictComprehensionType {
Update,
Comprehension,
}
fn has_post_loop_references(checker: &Checker, expr: &Expr, loop_end: TextSize) -> bool {
any_over_expr(expr, &|expr| match expr {
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts
.iter()
.any(|expr| has_post_loop_references(checker, expr, loop_end)),
Expr::Name(name) => {
let target_binding = checker
.semantic()
.bindings
.iter()
.find(|binding| name.range() == binding.range)
.expect("for-loop target binding must exist");
target_binding
.references()
.map(|reference| checker.semantic().reference(reference))
.any(|other_reference| other_reference.start() > loop_end)
}
_ => false,
})
}

View File

@@ -146,16 +146,5 @@ PERF403.py:166:13: PERF403 Use a dictionary comprehension instead of a for-loop
165 | if lambda: 0:
166 | dst[k] = v
| ^^^^^^^^^^ PERF403
167 |
168 | # https://github.com/astral-sh/ruff/issues/18859
|
= help: Replace for loop with dict comprehension
PERF403.py:172:9: PERF403 Use a dictionary comprehension instead of a for-loop
|
170 | v = {}
171 | for o,(x,)in():
172 | v[x,]=o
| ^^^^^^^ PERF403
|
= help: Replace for loop with dict comprehension

View File

@@ -340,8 +340,6 @@ PERF403.py:166:13: PERF403 [*] Use `dict.update` instead of a for-loop
165 | if lambda: 0:
166 | dst[k] = v
| ^^^^^^^^^^ PERF403
167 |
168 | # https://github.com/astral-sh/ruff/issues/18859
|
= help: Replace for loop with `dict.update`
@@ -353,24 +351,3 @@ PERF403.py:166:13: PERF403 [*] Use `dict.update` instead of a for-loop
165 |- if lambda: 0:
166 |- dst[k] = v
164 |+ dst.update({k: v for k, v in src if (lambda: 0)})
167 165 |
168 166 | # https://github.com/astral-sh/ruff/issues/18859
169 167 | def foo():
PERF403.py:172:9: PERF403 [*] Use a dictionary comprehension instead of a for-loop
|
170 | v = {}
171 | for o,(x,)in():
172 | v[x,]=o
| ^^^^^^^ PERF403
|
= help: Replace for loop with dict comprehension
Unsafe fix
167 167 |
168 168 | # https://github.com/astral-sh/ruff/issues/18859
169 169 | def foo():
170 |- v = {}
171 |- for o,(x,)in():
172 |- v[x,]=o
170 |+ v = {x: o for o,(x,) in ()}

View File

@@ -4,6 +4,7 @@ use ruff_text_size::Ranged;
use crate::Violation;
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::registry::Rule;
/// ## What it does
/// Checks for empty docstrings.
@@ -43,6 +44,9 @@ pub(crate) fn not_empty(checker: &Checker, docstring: &Docstring) -> bool {
if !docstring.body().trim().is_empty() {
return true;
}
checker.report_diagnostic_if_enabled(EmptyDocstring, docstring.range());
if checker.is_rule_enabled(Rule::EmptyDocstring) {
checker.report_diagnostic(EmptyDocstring, docstring.range());
}
false
}

View File

@@ -8,6 +8,7 @@ use ruff_text_size::TextRange;
use crate::Violation;
use crate::checkers::ast::Checker;
use crate::registry::Rule;
/// ## What it does
/// Checks for undocumented public module definitions.
@@ -550,29 +551,36 @@ pub(crate) fn not_missing(
if checker.source_type.is_ipynb() {
return true;
}
checker.report_diagnostic_if_enabled(UndocumentedPublicModule, TextRange::default());
if checker.is_rule_enabled(Rule::UndocumentedPublicModule) {
checker.report_diagnostic(UndocumentedPublicModule, TextRange::default());
}
false
}
Definition::Module(Module {
kind: ModuleKind::Package,
..
}) => {
checker.report_diagnostic_if_enabled(UndocumentedPublicPackage, TextRange::default());
if checker.is_rule_enabled(Rule::UndocumentedPublicPackage) {
checker.report_diagnostic(UndocumentedPublicPackage, TextRange::default());
}
false
}
Definition::Member(Member {
kind: MemberKind::Class(class),
..
}) => {
checker.report_diagnostic_if_enabled(UndocumentedPublicClass, class.identifier());
if checker.is_rule_enabled(Rule::UndocumentedPublicClass) {
checker.report_diagnostic(UndocumentedPublicClass, class.identifier());
}
false
}
Definition::Member(Member {
kind: MemberKind::NestedClass(function),
..
}) => {
checker
.report_diagnostic_if_enabled(UndocumentedPublicNestedClass, function.identifier());
if checker.is_rule_enabled(Rule::UndocumentedPublicNestedClass) {
checker.report_diagnostic(UndocumentedPublicNestedClass, function.identifier());
}
false
}
Definition::Member(Member {
@@ -582,10 +590,9 @@ pub(crate) fn not_missing(
if is_overload(&function.decorator_list, checker.semantic()) {
true
} else {
checker.report_diagnostic_if_enabled(
UndocumentedPublicFunction,
function.identifier(),
);
if checker.is_rule_enabled(Rule::UndocumentedPublicFunction) {
checker.report_diagnostic(UndocumentedPublicFunction, function.identifier());
}
false
}
}
@@ -598,19 +605,24 @@ pub(crate) fn not_missing(
{
true
} else if is_init(&function.name) {
checker.report_diagnostic_if_enabled(UndocumentedPublicInit, function.identifier());
if checker.is_rule_enabled(Rule::UndocumentedPublicInit) {
checker.report_diagnostic(UndocumentedPublicInit, function.identifier());
}
true
} else if is_new(&function.name) || is_call(&function.name) {
checker
.report_diagnostic_if_enabled(UndocumentedPublicMethod, function.identifier());
if checker.is_rule_enabled(Rule::UndocumentedPublicMethod) {
checker.report_diagnostic(UndocumentedPublicMethod, function.identifier());
}
true
} else if is_magic(&function.name) {
checker
.report_diagnostic_if_enabled(UndocumentedMagicMethod, function.identifier());
if checker.is_rule_enabled(Rule::UndocumentedMagicMethod) {
checker.report_diagnostic(UndocumentedMagicMethod, function.identifier());
}
true
} else {
checker
.report_diagnostic_if_enabled(UndocumentedPublicMethod, function.identifier());
if checker.is_rule_enabled(Rule::UndocumentedPublicMethod) {
checker.report_diagnostic(UndocumentedPublicMethod, function.identifier());
}
true
}
}

View File

@@ -1434,12 +1434,14 @@ fn blanks_and_section_underline(
}
if following_lines.peek().is_none() {
checker.report_diagnostic_if_enabled(
EmptyDocstringSection {
name: context.section_name().to_string(),
},
context.section_name_range(),
);
if checker.is_rule_enabled(Rule::EmptyDocstringSection) {
checker.report_diagnostic(
EmptyDocstringSection {
name: context.section_name().to_string(),
},
context.section_name_range(),
);
}
} else if checker.is_rule_enabled(Rule::BlankLinesBetweenHeaderAndContent) {
// If the section is followed by exactly one line, and then a
// reStructuredText directive, the blank lines should be preserved, as in:
@@ -1493,12 +1495,14 @@ fn blanks_and_section_underline(
}
}
} else {
checker.report_diagnostic_if_enabled(
EmptyDocstringSection {
name: context.section_name().to_string(),
},
context.section_name_range(),
);
if checker.is_rule_enabled(Rule::EmptyDocstringSection) {
checker.report_diagnostic(
EmptyDocstringSection {
name: context.section_name().to_string(),
},
context.section_name_range(),
);
}
}
} else {
if style.is_numpy() && checker.is_rule_enabled(Rule::MissingDashedUnderlineAfterSection)
@@ -1614,12 +1618,14 @@ fn blanks_and_section_underline(
context.summary_range().end(),
)));
}
checker.report_diagnostic_if_enabled(
EmptyDocstringSection {
name: context.section_name().to_string(),
},
context.section_name_range(),
);
if checker.is_rule_enabled(Rule::EmptyDocstringSection) {
checker.report_diagnostic(
EmptyDocstringSection {
name: context.section_name().to_string(),
},
context.section_name_range(),
);
}
}
}

View File

@@ -30,7 +30,6 @@ impl Violation for FutureFeatureNotDefined {
}
}
/// F407
pub(crate) fn future_feature_not_defined(checker: &Checker, alias: &Alias) {
if is_feature_name(&alias.name) {
return;

View File

@@ -1,10 +1,7 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_semantic::{BindingKind, Scope, ScopeId};
use ruff_source_file::SourceRow;
use ruff_text_size::Ranged;
use crate::Violation;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for import bindings that are shadowed by loop variables.
@@ -47,48 +44,6 @@ impl Violation for ImportShadowedByLoopVar {
}
}
/// F402
pub(crate) fn import_shadowed_by_loop_var(checker: &Checker, scope_id: ScopeId, scope: &Scope) {
for (name, binding_id) in scope.bindings() {
for shadow in checker.semantic().shadowed_bindings(scope_id, binding_id) {
// If the shadowing binding isn't a loop variable, abort.
let binding = &checker.semantic().bindings[shadow.binding_id()];
if !binding.kind.is_loop_var() {
continue;
}
// If the shadowed binding isn't an import, abort.
let shadowed = &checker.semantic().bindings[shadow.shadowed_id()];
if !matches!(
shadowed.kind,
BindingKind::Import(..)
| BindingKind::FromImport(..)
| BindingKind::SubmoduleImport(..)
| BindingKind::FutureImport
) {
continue;
}
// If the bindings are in different forks, abort.
if shadowed.source.is_none_or(|left| {
binding
.source
.is_none_or(|right| !checker.semantic().same_branch(left, right))
}) {
continue;
}
checker.report_diagnostic(
ImportShadowedByLoopVar {
name: name.to_string(),
row: checker.compute_source_row(shadowed.start()),
},
binding.range(),
);
}
}
}
/// ## What it does
/// Checks for the use of wildcard imports.
///

View File

@@ -1,14 +1,7 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::{BindingKind, Imported, Scope, ScopeId};
use ruff_source_file::SourceRow;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::fix::edits;
use crate::{Fix, FixAvailability, Violation};
use rustc_hash::FxHashMap;
use crate::{FixAvailability, Violation};
/// ## What it does
/// Checks for variable definitions that redefine (or "shadow") unused
@@ -50,154 +43,3 @@ impl Violation for RedefinedWhileUnused {
Some(format!("Remove definition: `{name}`"))
}
}
/// F811
pub(crate) fn redefined_while_unused(checker: &Checker, scope_id: ScopeId, scope: &Scope) {
// Index the redefined bindings by statement.
let mut redefinitions = FxHashMap::default();
for (name, binding_id) in scope.bindings() {
for shadow in checker.semantic().shadowed_bindings(scope_id, binding_id) {
// If the shadowing binding is a loop variable, abort, to avoid overlap
// with F402.
let binding = &checker.semantic().bindings[shadow.binding_id()];
if binding.kind.is_loop_var() {
continue;
}
// If the shadowed binding is used, abort.
let shadowed = &checker.semantic().bindings[shadow.shadowed_id()];
if shadowed.is_used() {
continue;
}
// If the shadowing binding isn't considered a "redefinition" of the
// shadowed binding, abort.
if !binding.redefines(shadowed) {
continue;
}
if shadow.same_scope() {
// If the symbol is a dummy variable, abort, unless the shadowed
// binding is an import.
if !matches!(
shadowed.kind,
BindingKind::Import(..)
| BindingKind::FromImport(..)
| BindingKind::SubmoduleImport(..)
| BindingKind::FutureImport
) && checker.settings().dummy_variable_rgx.is_match(name)
{
continue;
}
let Some(node_id) = shadowed.source else {
continue;
};
// If this is an overloaded function, abort.
if shadowed.kind.is_function_definition() {
if checker
.semantic()
.statement(node_id)
.as_function_def_stmt()
.is_some_and(|function| {
visibility::is_overload(&function.decorator_list, checker.semantic())
})
{
continue;
}
}
} else {
// Only enforce cross-scope shadowing for imports.
if !matches!(
shadowed.kind,
BindingKind::Import(..)
| BindingKind::FromImport(..)
| BindingKind::SubmoduleImport(..)
| BindingKind::FutureImport
) {
continue;
}
}
// If the bindings are in different forks, abort.
if shadowed.source.is_none_or(|left| {
binding
.source
.is_none_or(|right| !checker.semantic().same_branch(left, right))
}) {
continue;
}
redefinitions
.entry(binding.source)
.or_insert_with(Vec::new)
.push((shadowed, binding));
}
}
// Create a fix for each source statement.
let mut fixes = FxHashMap::default();
for (source, entries) in &redefinitions {
let Some(source) = source else {
continue;
};
let member_names = entries
.iter()
.filter_map(|(shadowed, binding)| {
if let Some(shadowed_import) = shadowed.as_any_import() {
if let Some(import) = binding.as_any_import() {
if shadowed_import.qualified_name() == import.qualified_name() {
return Some(import.member_name());
}
}
}
None
})
.collect::<Vec<_>>();
if !member_names.is_empty() {
let statement = checker.semantic().statement(*source);
let parent = checker.semantic().parent_statement(*source);
let Ok(edit) = edits::remove_unused_imports(
member_names.iter().map(std::convert::AsRef::as_ref),
statement,
parent,
checker.locator(),
checker.stylist(),
checker.indexer(),
) else {
continue;
};
fixes.insert(
*source,
Fix::safe_edit(edit).isolate(Checker::isolation(
checker.semantic().parent_statement_id(*source),
)),
);
}
}
// Create diagnostics for each statement.
for (source, entries) in &redefinitions {
for (shadowed, binding) in entries {
let mut diagnostic = checker.report_diagnostic(
RedefinedWhileUnused {
name: binding.name(checker.source()).to_string(),
row: checker.compute_source_row(shadowed.start()),
},
binding.range(),
);
if let Some(range) = binding.parent_range(checker.semantic()) {
diagnostic.set_parent(range.start());
}
if let Some(fix) = source.as_ref().and_then(|source| fixes.get(source)) {
diagnostic.set_fix(fix.clone());
}
}
}
}

View File

@@ -1,7 +1,5 @@
use std::string::ToString;
use ruff_diagnostics::Applicability;
use ruff_python_ast::helpers::contains_effect;
use rustc_hash::FxHashSet;
use ruff_macros::{ViolationMetadata, derive_message_formats};
@@ -140,16 +138,6 @@ impl Violation for PercentFormatExpectedSequence {
/// "Hello, %(name)s" % {"name": "World"}
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe for mapping key
/// containing function calls with potential side effects,
/// because removing such arguments could change the behavior of the code.
///
/// For example, the fix would be marked as unsafe in the following case:
/// ```python
/// "Hello, %(name)s" % {"greeting": print(1), "name": "World"}
/// ```
///
/// ## References
/// - [Python documentation: `printf`-style String Formatting](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting)
#[derive(ViolationMetadata)]
@@ -391,16 +379,6 @@ impl Violation for StringDotFormatInvalidFormat {
/// "Hello, {name}".format(name="World")
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe if the unused keyword argument
/// contains a function call with potential side effects,
/// because removing such arguments could change the behavior of the code.
///
/// For example, the fix would be marked as unsafe in the following case:
/// ```python
/// "Hello, {name}".format(greeting=print(1), name="World")
/// ```
///
/// ## References
/// - [Python documentation: `str.format`](https://docs.python.org/3/library/stdtypes.html#str.format)
#[derive(ViolationMetadata)]
@@ -442,16 +420,6 @@ impl Violation for StringDotFormatExtraNamedArguments {
/// "Hello, {0}".format("world")
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe if the unused positional argument
/// contains a function call with potential side effects,
/// because removing such arguments could change the behavior of the code.
///
/// For example, the fix would be marked as unsafe in the following case:
/// ```python
/// "Hello, {0}".format("world", print(1))
/// ```
///
/// ## References
/// - [Python documentation: `str.format`](https://docs.python.org/3/library/stdtypes.html#str.format)
#[derive(ViolationMetadata)]
@@ -635,24 +603,15 @@ pub(crate) fn percent_format_extra_named_arguments(
PercentFormatExtraNamedArguments { missing: names },
location,
);
let indexes: Vec<usize> = missing.iter().map(|(index, _)| *index).collect();
diagnostic.try_set_fix(|| {
let indexes: Vec<usize> = missing.iter().map(|(index, _)| *index).collect();
let edit = remove_unused_format_arguments_from_dict(
&indexes,
dict,
checker.locator(),
checker.stylist(),
)?;
Ok(Fix::applicable_edit(
edit,
// Mark fix as unsafe if `dict` contains a call with side effect
if contains_effect(right, |id| checker.semantic().has_builtin_binding(id)) {
Applicability::Unsafe
} else {
Applicability::Safe
},
))
Ok(Fix::safe_edit(edit))
});
}
@@ -775,19 +734,16 @@ pub(crate) fn string_dot_format_extra_named_arguments(
return;
}
let keyword_names = keywords
let keywords = keywords
.iter()
.filter_map(|Keyword { arg, value, .. }| Some((arg.as_ref()?, value)));
.filter_map(|Keyword { arg, .. }| arg.as_ref());
let mut side_effects = false;
let missing: Vec<(usize, &Name)> = keyword_names
let missing: Vec<(usize, &Name)> = keywords
.enumerate()
.filter_map(|(index, (keyword, value))| {
.filter_map(|(index, keyword)| {
if summary.keywords.contains(keyword.id()) {
None
} else {
side_effects |=
contains_effect(value, |id| checker.semantic().has_builtin_binding(id));
Some((index, &keyword.id))
}
})
@@ -802,7 +758,7 @@ pub(crate) fn string_dot_format_extra_named_arguments(
StringDotFormatExtraNamedArguments { missing: names },
call.range(),
);
let indexes: Vec<usize> = missing.into_iter().map(|(index, _)| index).collect();
let indexes: Vec<usize> = missing.iter().map(|(index, _)| *index).collect();
diagnostic.try_set_fix(|| {
let edit = remove_unused_keyword_arguments_from_format_call(
&indexes,
@@ -810,16 +766,7 @@ pub(crate) fn string_dot_format_extra_named_arguments(
checker.locator(),
checker.stylist(),
)?;
Ok(Fix::applicable_edit(
edit,
// Mark fix as unsafe if the `format` call contains an argument with side effect
if side_effects {
Applicability::Unsafe
} else {
Applicability::Safe
},
))
Ok(Fix::safe_edit(edit))
});
}
@@ -855,17 +802,13 @@ pub(crate) fn string_dot_format_extra_positional_arguments(
true
}
let mut side_effects = false;
let missing: Vec<usize> = args
.iter()
.enumerate()
.filter(|(i, arg)| {
!(arg.is_starred_expr() || summary.autos.contains(i) || summary.indices.contains(i))
})
.map(|(i, arg)| {
side_effects |= contains_effect(arg, |id| checker.semantic().has_builtin_binding(id));
i
})
.map(|(i, _)| i)
.collect();
if missing.is_empty() {
@@ -890,15 +833,7 @@ pub(crate) fn string_dot_format_extra_positional_arguments(
checker.locator(),
checker.stylist(),
)?;
Ok(Fix::applicable_edit(
edit,
// Mark fix as unsafe if the `format` call contains an argument with side effect
if side_effects {
Applicability::Unsafe
} else {
Applicability::Safe
},
))
Ok(Fix::safe_edit(edit))
});
}
}

View File

@@ -276,7 +276,6 @@ fn find_dunder_all_exprs<'a>(semantic: &'a SemanticModel) -> Vec<&'a ast::Expr>
.collect()
}
/// F401
/// For some unused binding in an import statement...
///
/// __init__.py ∧ 1stpty → safe, if one __all__, add to __all__

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