Compare commits
51 Commits
dhruv/unpa
...
alex/known
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ea95e9f24 | ||
|
|
040bb0f932 | ||
|
|
0150ac964d | ||
|
|
1dcdf7f41d | ||
|
|
d00697621e | ||
|
|
76619b96e5 | ||
|
|
6e25cfba2b | ||
|
|
76387295a5 | ||
|
|
d04e63a6d9 | ||
|
|
86fd9b634e | ||
|
|
c0beb3412f | ||
|
|
b01003f81d | ||
|
|
2362263d5e | ||
|
|
170ccd80b4 | ||
|
|
4a5715b97a | ||
|
|
c77e72ea1a | ||
|
|
5d546c600a | ||
|
|
8b22992988 | ||
|
|
f6def1c86d | ||
|
|
5aab49880a | ||
|
|
7783cea14f | ||
|
|
c1fed55d51 | ||
|
|
689797a984 | ||
|
|
66dbea90f1 | ||
|
|
d2684a00c6 | ||
|
|
2a0c5669f2 | ||
|
|
cb152b4725 | ||
|
|
90f47e9b7b | ||
|
|
62975b3ab2 | ||
|
|
eee5a5a3d6 | ||
|
|
66f50fb04b | ||
|
|
919af9628d | ||
|
|
9d8cba4e8b | ||
|
|
d89f75f9cc | ||
|
|
e44c489273 | ||
|
|
3220242dec | ||
|
|
6abafcb565 | ||
|
|
cef1a522dc | ||
|
|
40731f0589 | ||
|
|
1461137407 | ||
|
|
47653ca88a | ||
|
|
02ae8e1210 | ||
|
|
0edbd6c390 | ||
|
|
833be2e66a | ||
|
|
0194452928 | ||
|
|
2c4c015f74 | ||
|
|
66fc7c8fc0 | ||
|
|
237a5821ba | ||
|
|
27eee5a1a8 | ||
|
|
fd2cc37f90 | ||
|
|
ca7933804e |
33
Cargo.lock
generated
33
Cargo.lock
generated
@@ -930,35 +930,12 @@ 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"
|
||||
@@ -3046,16 +3023,6 @@ 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"
|
||||
|
||||
@@ -75,7 +75,6 @@ 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" }
|
||||
|
||||
@@ -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.noqa_code().and_then(|code| code.rule())?, msg)))
|
||||
.filter_map(|msg| Some((msg.name().parse().ok()?, msg)))
|
||||
.map(|(rule, msg)| {
|
||||
// Make sure that all message use the same source file.
|
||||
assert_eq!(
|
||||
|
||||
@@ -17,6 +17,7 @@ fn command() -> Command {
|
||||
command.arg("analyze");
|
||||
command.arg("graph");
|
||||
command.arg("--preview");
|
||||
command.env_clear();
|
||||
command
|
||||
}
|
||||
|
||||
@@ -565,8 +566,8 @@ fn venv() -> Result<()> {
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
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
|
||||
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)
|
||||
");
|
||||
});
|
||||
|
||||
|
||||
@@ -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 = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1);
|
||||
let next = char_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| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1))
|
||||
.map(|ch| char_width(ch).unwrap_or(1))
|
||||
.sum();
|
||||
|
||||
let mut annotations = annotations.clone();
|
||||
@@ -1393,6 +1393,7 @@ 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![],
|
||||
@@ -1452,12 +1453,12 @@ fn format_body<'m>(
|
||||
let annotation_start_col = line
|
||||
[0..(start - line_start_index).min(line_length)]
|
||||
.chars()
|
||||
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
|
||||
.map(|c| char_width(c).unwrap_or(0))
|
||||
.sum::<usize>();
|
||||
let mut annotation_end_col = line
|
||||
[0..(end - line_start_index).min(line_length)]
|
||||
.chars()
|
||||
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
|
||||
.map(|c| char_width(c).unwrap_or(0))
|
||||
.sum::<usize>();
|
||||
if annotation_start_col == annotation_end_col {
|
||||
// At least highlight something
|
||||
@@ -1499,7 +1500,7 @@ fn format_body<'m>(
|
||||
let annotation_start_col = line
|
||||
[0..(start - line_start_index).min(line_length)]
|
||||
.chars()
|
||||
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
|
||||
.map(|c| char_width(c).unwrap_or(0))
|
||||
.sum::<usize>();
|
||||
let annotation_end_col = annotation_start_col + 1;
|
||||
|
||||
@@ -1558,7 +1559,7 @@ fn format_body<'m>(
|
||||
{
|
||||
let end_mark = line[0..(end - line_start_index).min(line_length)]
|
||||
.chars()
|
||||
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
|
||||
.map(|c| char_width(c).unwrap_or(0))
|
||||
.sum::<usize>()
|
||||
.saturating_sub(1);
|
||||
// If the annotation ends on a line-end character, we
|
||||
@@ -1754,3 +1755,11 @@ fn format_inline_marks(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn char_width(c: char) -> Option<usize> {
|
||||
if c == '\t' {
|
||||
Some(4)
|
||||
} else {
|
||||
unicode_width::UnicodeWidthChar::width(c)
|
||||
}
|
||||
}
|
||||
|
||||
36
crates/ruff_annotate_snippets/tests/fixtures/color/regression_leading_tab_long_line.svg
vendored
Normal file
36
crates/ruff_annotate_snippets/tests/fixtures/color/regression_leading_tab_long_line.svg
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
<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">--></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>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
20
crates/ruff_annotate_snippets/tests/fixtures/color/regression_leading_tab_long_line.toml
vendored
Normal file
20
crates/ruff_annotate_snippets/tests/fixtures/color/regression_leading_tab_long_line.toml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
[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
|
||||
@@ -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">--></tspan><tspan> $DIR/non-whitespace-trimming.rs:4:242</tspan>
|
||||
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> $DIR/non-whitespace-trimming.rs:4:238</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 |
@@ -13,12 +13,12 @@ origin = "$DIR/non-whitespace-trimming.rs"
|
||||
[[message.snippets.annotations]]
|
||||
label = "expected `()`, found integer"
|
||||
level = "Error"
|
||||
range = [241, 243]
|
||||
range = [237, 239]
|
||||
|
||||
[[message.snippets.annotations]]
|
||||
label = "expected due to this"
|
||||
level = "Error"
|
||||
range = [236, 238]
|
||||
range = [232, 234]
|
||||
|
||||
|
||||
[renderer]
|
||||
|
||||
@@ -348,6 +348,58 @@ 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,
|
||||
@@ -377,8 +429,7 @@ impl<'a> ProjectBenchmark<'a> {
|
||||
metadata.apply_options(Options {
|
||||
environment: Some(EnvironmentOptions {
|
||||
python_version: Some(RangedValue::cli(self.project.config.python_version)),
|
||||
python: (!self.project.config().dependencies.is_empty())
|
||||
.then_some(RelativePathBuf::cli(SystemPath::new(".venv"))),
|
||||
python: Some(RelativePathBuf::cli(SystemPath::new(".venv"))),
|
||||
..EnvironmentOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
@@ -483,6 +534,7 @@ 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);
|
||||
|
||||
@@ -36,8 +36,7 @@ impl<'a> Benchmark<'a> {
|
||||
metadata.apply_options(Options {
|
||||
environment: Some(EnvironmentOptions {
|
||||
python_version: Some(RangedValue::cli(self.project.config.python_version)),
|
||||
python: (!self.project.config().dependencies.is_empty())
|
||||
.then_some(RelativePathBuf::cli(SystemPath::new(".venv"))),
|
||||
python: Some(RelativePathBuf::cli(SystemPath::new(".venv"))),
|
||||
..EnvironmentOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
|
||||
@@ -74,19 +74,17 @@ impl<'a> RealWorldProject<'a> {
|
||||
};
|
||||
|
||||
// Install dependencies if specified
|
||||
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!(
|
||||
"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()
|
||||
);
|
||||
|
||||
tracing::debug!("Project setup took: {:.2}s", start.elapsed().as_secs_f64());
|
||||
|
||||
@@ -281,6 +279,14 @@ 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([
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -735,6 +735,9 @@ pub enum DiagnosticId {
|
||||
/// # no `[overrides.rules]`
|
||||
/// ```
|
||||
UselessOverridesSection,
|
||||
|
||||
/// Use of a deprecated setting.
|
||||
DeprecatedSetting,
|
||||
}
|
||||
|
||||
impl DiagnosticId {
|
||||
@@ -773,6 +776,7 @@ impl DiagnosticId {
|
||||
DiagnosticId::EmptyInclude => "empty-include",
|
||||
DiagnosticId::UnnecessaryOverridesSection => "unnecessary-overrides-section",
|
||||
DiagnosticId::UselessOverridesSection => "useless-overrides-section",
|
||||
DiagnosticId::DeprecatedSetting => "deprecated-setting",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, 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, PythonPath, PythonPlatform, PythonVersionSource,
|
||||
Db, Program, ProgramSettings, PythonEnvironment, PythonPlatform, PythonVersionSource,
|
||||
PythonVersionWithSource, SearchPathSettings, SysPrefixPathOrigin, default_lint_registry,
|
||||
};
|
||||
|
||||
@@ -35,24 +35,32 @@ impl ModuleDb {
|
||||
python_version: PythonVersion,
|
||||
venv_path: Option<SystemPathBuf>,
|
||||
) -> Result<Self> {
|
||||
let mut search_paths = SearchPathSettings::new(src_roots);
|
||||
if let Some(venv_path) = venv_path {
|
||||
search_paths.python_path =
|
||||
PythonPath::sys_prefix(venv_path, SysPrefixPathOrigin::PythonCliFlag);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
let search_paths = search_paths
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.context("Invalid search path settings")?;
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: Some(PythonVersionWithSource {
|
||||
python_version: PythonVersionWithSource {
|
||||
version: python_version,
|
||||
source: PythonVersionSource::default(),
|
||||
}),
|
||||
},
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths,
|
||||
},
|
||||
)?;
|
||||
);
|
||||
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
6
crates/ruff_linter/resources/test/fixtures/flake8_errmsg/EM101_byte_string.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/flake8_errmsg/EM101_byte_string.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
def f_byte():
|
||||
raise RuntimeError(b"This is an example exception")
|
||||
|
||||
|
||||
def f_byte_empty():
|
||||
raise RuntimeError(b"")
|
||||
@@ -2,13 +2,81 @@ 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"))
|
||||
5
crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH202_2.py
vendored
Normal file
5
crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH202_2.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import os
|
||||
|
||||
os.path.getsize(filename="filename")
|
||||
os.path.getsize(filename=b"filename")
|
||||
os.path.getsize(filename=__file__)
|
||||
@@ -163,4 +163,10 @@ def foo():
|
||||
|
||||
for k, v in src:
|
||||
if lambda: 0:
|
||||
dst[k] = v
|
||||
dst[k] = v
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18859
|
||||
def foo():
|
||||
v = {}
|
||||
for o,(x,)in():
|
||||
v[x,]=o
|
||||
|
||||
@@ -14,3 +14,6 @@ hidden = {"a": "!"}
|
||||
"" % {
|
||||
'test1': '',
|
||||
'test2': '',
|
||||
}
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18806
|
||||
|
||||
@@ -4,3 +4,11 @@
|
||||
"{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))
|
||||
|
||||
@@ -35,3 +35,11 @@
|
||||
# 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")
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Mock objects
|
||||
# ============
|
||||
# Errors
|
||||
assert my_mock.not_called()
|
||||
assert my_mock.called_once_with()
|
||||
@@ -17,3 +19,25 @@ 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`"""
|
||||
|
||||
@@ -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))
|
||||
|
||||
# Don't provide a fix if there are comments within the call.
|
||||
# The fix is already unsafe, so deleting comments is okay.
|
||||
min(
|
||||
1, # This is a comment.
|
||||
min(2, 3),
|
||||
|
||||
@@ -20,6 +20,12 @@ _ = 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)
|
||||
|
||||
@@ -36,5 +36,19 @@ f"{ascii(bla)}" # OK
|
||||
)
|
||||
|
||||
|
||||
# OK
|
||||
# https://github.com/astral-sh/ruff/issues/16325
|
||||
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)}"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from collections import deque
|
||||
import collections
|
||||
from collections import deque
|
||||
|
||||
|
||||
def f():
|
||||
@@ -91,3 +91,14 @@ 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
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
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 ruff_python_semantic::{Binding, ScopeKind};
|
||||
|
||||
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,
|
||||
@@ -94,260 +89,19 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
|
||||
}
|
||||
|
||||
if checker.is_rule_enabled(Rule::GlobalVariableNotAssigned) {
|
||||
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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
pylint::rules::global_variable_not_assigned(checker, scope);
|
||||
}
|
||||
|
||||
if checker.is_rule_enabled(Rule::RedefinedArgumentFromLocal) {
|
||||
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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
pylint::rules::redefined_argument_from_local(checker, scope_id, scope);
|
||||
}
|
||||
|
||||
if checker.is_rule_enabled(Rule::ImportShadowedByLoopVar) {
|
||||
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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
pyflakes::rules::import_shadowed_by_loop_var(checker, scope_id, scope);
|
||||
}
|
||||
|
||||
if checker.is_rule_enabled(Rule::RedefinedWhileUnused) {
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
pyflakes::rules::redefined_while_unused(checker, scope_id, scope);
|
||||
}
|
||||
|
||||
if checker.source_type.is_stub()
|
||||
|
||||
@@ -539,14 +539,13 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
let location = expr.range();
|
||||
match pyflakes::format::FormatSummary::try_from(string_value.to_str()) {
|
||||
Err(e) => {
|
||||
if checker.is_rule_enabled(Rule::StringDotFormatInvalidFormat) {
|
||||
checker.report_diagnostic(
|
||||
pyflakes::rules::StringDotFormatInvalidFormat {
|
||||
message: pyflakes::format::error_to_string(&e),
|
||||
},
|
||||
location,
|
||||
);
|
||||
}
|
||||
// F521
|
||||
checker.report_diagnostic_if_enabled(
|
||||
pyflakes::rules::StringDotFormatInvalidFormat {
|
||||
message: pyflakes::format::error_to_string(&e),
|
||||
},
|
||||
location,
|
||||
);
|
||||
}
|
||||
Ok(summary) => {
|
||||
if checker
|
||||
@@ -1056,7 +1055,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
Rule::OsPathSplitext,
|
||||
Rule::BuiltinOpen,
|
||||
Rule::PyPath,
|
||||
Rule::OsPathGetsize,
|
||||
Rule::OsPathGetatime,
|
||||
Rule::OsPathGetmtime,
|
||||
Rule::OsPathGetctime,
|
||||
@@ -1066,6 +1064,9 @@ 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);
|
||||
}
|
||||
@@ -1313,26 +1314,22 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
typ: CFormatErrorType::UnsupportedFormatChar(c),
|
||||
..
|
||||
}) => {
|
||||
if checker
|
||||
.is_rule_enabled(Rule::PercentFormatUnsupportedFormatCharacter)
|
||||
{
|
||||
checker.report_diagnostic(
|
||||
pyflakes::rules::PercentFormatUnsupportedFormatCharacter {
|
||||
char: c,
|
||||
},
|
||||
location,
|
||||
);
|
||||
}
|
||||
// F509
|
||||
checker.report_diagnostic_if_enabled(
|
||||
pyflakes::rules::PercentFormatUnsupportedFormatCharacter {
|
||||
char: c,
|
||||
},
|
||||
location,
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
if checker.is_rule_enabled(Rule::PercentFormatInvalidFormat) {
|
||||
checker.report_diagnostic(
|
||||
pyflakes::rules::PercentFormatInvalidFormat {
|
||||
message: e.to_string(),
|
||||
},
|
||||
location,
|
||||
);
|
||||
}
|
||||
// F501
|
||||
checker.report_diagnostic_if_enabled(
|
||||
pyflakes::rules::PercentFormatInvalidFormat {
|
||||
message: e.to_string(),
|
||||
},
|
||||
location,
|
||||
);
|
||||
}
|
||||
Ok(summary) => {
|
||||
if checker.is_rule_enabled(Rule::PercentFormatExpectedMapping) {
|
||||
|
||||
@@ -45,18 +45,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::NonlocalWithoutBinding) {
|
||||
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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
pylint::rules::nonlocal_without_binding(checker, nonlocal);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::NonlocalAndGlobal) {
|
||||
pylint::rules::nonlocal_and_global(checker, nonlocal);
|
||||
@@ -828,6 +817,7 @@ 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(
|
||||
@@ -838,14 +828,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
);
|
||||
}
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::UndefinedLocalWithImportStar) {
|
||||
checker.report_diagnostic(
|
||||
pyflakes::rules::UndefinedLocalWithImportStar {
|
||||
name: helpers::format_import_from(level, module).to_string(),
|
||||
},
|
||||
stmt.range(),
|
||||
);
|
||||
}
|
||||
// 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::RelativeImports) {
|
||||
flake8_tidy_imports::rules::banned_relative_import(
|
||||
|
||||
@@ -13,15 +13,15 @@ pub(crate) fn unresolved_references(checker: &Checker) {
|
||||
|
||||
for reference in checker.semantic.unresolved_references() {
|
||||
if reference.is_wildcard_import() {
|
||||
if checker.is_rule_enabled(Rule::UndefinedLocalWithImportStarUsage) {
|
||||
checker.report_diagnostic(
|
||||
pyflakes::rules::UndefinedLocalWithImportStarUsage {
|
||||
name: reference.name(checker.source()).to_string(),
|
||||
},
|
||||
reference.range(),
|
||||
);
|
||||
}
|
||||
// F406
|
||||
checker.report_diagnostic_if_enabled(
|
||||
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;
|
||||
|
||||
@@ -28,7 +28,7 @@ use itertools::Itertools;
|
||||
use log::debug;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use ruff_diagnostics::IsolationLevel;
|
||||
use ruff_diagnostics::{Applicability, Fix, 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 a [`Diagnostic`], so it can be used to further modify the 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 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.
|
||||
///
|
||||
/// Prefer [`Checker::report_diagnostic`] in general because the conversion from a `Diagnostic`
|
||||
/// to a `Rule` is somewhat expensive.
|
||||
/// 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`.
|
||||
pub(crate) fn report_diagnostic_if_enabled<'chk, T: Violation>(
|
||||
&'chk self,
|
||||
kind: T,
|
||||
@@ -625,6 +625,7 @@ 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);
|
||||
}
|
||||
@@ -646,6 +647,7 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::ReturnOutsideFunction => {
|
||||
// F706
|
||||
if self.is_rule_enabled(Rule::ReturnOutsideFunction) {
|
||||
self.report_diagnostic(ReturnOutsideFunction, error.range);
|
||||
}
|
||||
@@ -2808,6 +2810,7 @@ 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 {
|
||||
@@ -2955,6 +2958,7 @@ 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 {
|
||||
@@ -2965,6 +2969,7 @@ 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")
|
||||
@@ -3145,7 +3150,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 collector on `Drop`.
|
||||
/// before it is added to the collection in the context on `Drop`.
|
||||
pub(crate) fn report_diagnostic<'chk, T: Violation>(
|
||||
&'chk self,
|
||||
kind: T,
|
||||
@@ -3154,23 +3159,26 @@ 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.
|
||||
///
|
||||
/// Prefer [`DiagnosticsCollector::report_diagnostic`] in general because the conversion from an
|
||||
/// `OldDiagnostic` to a `Rule` is somewhat expensive.
|
||||
/// 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`.
|
||||
pub(crate) fn report_diagnostic_if_enabled<'chk, T: Violation>(
|
||||
&'chk self,
|
||||
kind: T,
|
||||
range: TextRange,
|
||||
) -> Option<DiagnosticGuard<'chk, 'a>> {
|
||||
if self.is_rule_enabled(T::rule()) {
|
||||
let rule = T::rule();
|
||||
if self.is_rule_enabled(rule) {
|
||||
Some(DiagnosticGuard {
|
||||
context: self,
|
||||
diagnostic: Some(OldDiagnostic::new(kind, range, &self.source_file)),
|
||||
rule,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
@@ -3222,6 +3230,7 @@ pub(crate) struct DiagnosticGuard<'a, 'b> {
|
||||
///
|
||||
/// This is always `Some` until the `Drop` (or `defuse`) call.
|
||||
diagnostic: Option<OldDiagnostic>,
|
||||
rule: Rule,
|
||||
}
|
||||
|
||||
impl DiagnosticGuard<'_, '_> {
|
||||
@@ -3234,6 +3243,50 @@ 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;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ use crate::{Edit, Fix, Locator};
|
||||
|
||||
use super::ast::LintContext;
|
||||
|
||||
/// RUF100
|
||||
pub(crate) fn check_noqa(
|
||||
context: &mut LintContext,
|
||||
path: &Path,
|
||||
|
||||
@@ -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, Name,
|
||||
SmallStatement, Statement, Suite, Tuple, With,
|
||||
ImportAlias, ImportFrom, ImportNames, IndentedBlock, Lambda, ListComp, Module, SmallStatement,
|
||||
Statement, Suite, Tuple, With,
|
||||
};
|
||||
use ruff_python_codegen::Stylist;
|
||||
|
||||
@@ -104,14 +104,6 @@ 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)
|
||||
|
||||
@@ -749,15 +749,16 @@ x = 1 \
|
||||
let diag = {
|
||||
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
|
||||
let mut iter = edits.into_iter();
|
||||
OldDiagnostic::new(
|
||||
let mut diagnostic = OldDiagnostic::new(
|
||||
MissingNewlineAtEndOfFile, // The choice of rule here is arbitrary.
|
||||
TextRange::default(),
|
||||
&SourceFileBuilder::new("<filename>", "<code>").finish(),
|
||||
)
|
||||
.with_fix(Fix::safe_edits(
|
||||
);
|
||||
diagnostic.fix = Some(Fix::safe_edits(
|
||||
iter.next().ok_or(anyhow!("expected edits nonempty"))?,
|
||||
iter,
|
||||
))
|
||||
));
|
||||
diagnostic
|
||||
};
|
||||
assert_eq!(apply_fixes([diag].iter(), &locator).code, expect);
|
||||
Ok(())
|
||||
|
||||
@@ -186,12 +186,13 @@ mod tests {
|
||||
edit.into_iter()
|
||||
.map(|edit| {
|
||||
// The choice of rule here is arbitrary.
|
||||
let diagnostic = OldDiagnostic::new(
|
||||
let mut diagnostic = OldDiagnostic::new(
|
||||
MissingNewlineAtEndOfFile,
|
||||
edit.range(),
|
||||
&SourceFileBuilder::new(filename, source).finish(),
|
||||
);
|
||||
diagnostic.with_fix(Fix::safe_edit(edit))
|
||||
diagnostic.fix = Some(Fix::safe_edit(edit));
|
||||
diagnostic
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -378,32 +378,7 @@ pub fn check_path(
|
||||
|
||||
let (mut diagnostics, source_file) = context.into_parts();
|
||||
|
||||
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 {
|
||||
if !parsed.has_valid_syntax() {
|
||||
// Avoid fixing in case the source code contains syntax errors.
|
||||
for diagnostic in &mut diagnostics {
|
||||
diagnostic.fix = None;
|
||||
|
||||
@@ -186,41 +186,6 @@ 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]
|
||||
|
||||
@@ -50,6 +50,11 @@ 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 {
|
||||
@@ -84,3 +89,13 @@ 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()
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ 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,
|
||||
|
||||
@@ -215,6 +215,12 @@ 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.
|
||||
|
||||
@@ -265,6 +265,7 @@ mod schema {
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::RuleSelector;
|
||||
use crate::registry::RuleNamespace;
|
||||
use crate::rule_selector::{Linter, RuleCodePrefix};
|
||||
|
||||
impl JsonSchema for RuleSelector {
|
||||
|
||||
@@ -273,9 +273,7 @@ pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators
|
||||
],
|
||||
) = (ops, comparators)
|
||||
{
|
||||
if checker.is_rule_enabled(Rule::SysVersionInfo1CmpInt) {
|
||||
checker.report_diagnostic(SysVersionInfo1CmpInt, left.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(SysVersionInfo1CmpInt, left.range());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -294,9 +292,7 @@ pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators
|
||||
],
|
||||
) = (ops, comparators)
|
||||
{
|
||||
if checker.is_rule_enabled(Rule::SysVersionInfoMinorCmpInt) {
|
||||
checker.report_diagnostic(SysVersionInfoMinorCmpInt, left.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(SysVersionInfoMinorCmpInt, left.range());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,11 +306,9 @@ pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators
|
||||
) = (ops, comparators)
|
||||
{
|
||||
if value.len() == 1 {
|
||||
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());
|
||||
checker.report_diagnostic_if_enabled(SysVersionCmpStr10, left.range());
|
||||
} else {
|
||||
checker.report_diagnostic_if_enabled(SysVersionCmpStr3, left.range());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -738,6 +738,7 @@ 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) {
|
||||
@@ -765,6 +766,7 @@ 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
|
||||
@@ -791,6 +793,7 @@ 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) {
|
||||
|
||||
@@ -312,25 +312,21 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) {
|
||||
Some(ShellKeyword {
|
||||
truthiness: truthiness @ (Truthiness::True | Truthiness::Truthy),
|
||||
}) => {
|
||||
if checker.is_rule_enabled(Rule::SubprocessPopenWithShellEqualsTrue) {
|
||||
checker.report_diagnostic(
|
||||
SubprocessPopenWithShellEqualsTrue {
|
||||
safety: Safety::from(arg),
|
||||
is_exact: matches!(truthiness, Truthiness::True),
|
||||
},
|
||||
call.func.range(),
|
||||
);
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(
|
||||
SubprocessPopenWithShellEqualsTrue {
|
||||
safety: Safety::from(arg),
|
||||
is_exact: matches!(truthiness, Truthiness::True),
|
||||
},
|
||||
call.func.range(),
|
||||
);
|
||||
}
|
||||
// S603
|
||||
_ => {
|
||||
if !is_trusted_input(arg) {
|
||||
if checker.is_rule_enabled(Rule::SubprocessWithoutShellEqualsTrue) {
|
||||
checker.report_diagnostic(
|
||||
SubprocessWithoutShellEqualsTrue,
|
||||
call.func.range(),
|
||||
);
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(
|
||||
SubprocessWithoutShellEqualsTrue,
|
||||
call.func.range(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,14 +336,12 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) {
|
||||
}) = shell_keyword
|
||||
{
|
||||
// S604
|
||||
if checker.is_rule_enabled(Rule::CallWithShellEqualsTrue) {
|
||||
checker.report_diagnostic(
|
||||
CallWithShellEqualsTrue {
|
||||
is_exact: matches!(truthiness, Truthiness::True),
|
||||
},
|
||||
call.func.range(),
|
||||
);
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(
|
||||
CallWithShellEqualsTrue {
|
||||
is_exact: matches!(truthiness, Truthiness::True),
|
||||
},
|
||||
call.func.range(),
|
||||
);
|
||||
}
|
||||
|
||||
// S605
|
||||
|
||||
@@ -51,6 +51,7 @@ 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;
|
||||
|
||||
@@ -46,7 +46,7 @@ impl Violation for StaticKeyDictComprehension {
|
||||
}
|
||||
}
|
||||
|
||||
/// RUF011
|
||||
/// B035, 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 = {
|
||||
|
||||
@@ -58,6 +58,7 @@ 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;
|
||||
|
||||
@@ -57,6 +57,7 @@ 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;
|
||||
|
||||
@@ -69,6 +69,7 @@ impl Violation for CallDatetimeFromtimestamp {
|
||||
}
|
||||
}
|
||||
|
||||
/// DTZ006
|
||||
pub(crate) fn call_datetime_fromtimestamp(checker: &Checker, call: &ast::ExprCall) {
|
||||
if !checker.semantic().seen_module(Modules::DATETIME) {
|
||||
return;
|
||||
|
||||
@@ -67,6 +67,7 @@ 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;
|
||||
|
||||
@@ -56,6 +56,7 @@ 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;
|
||||
|
||||
@@ -60,6 +60,7 @@ 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;
|
||||
|
||||
@@ -63,6 +63,7 @@ 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;
|
||||
|
||||
@@ -46,6 +46,7 @@ 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) =
|
||||
@@ -64,6 +65,7 @@ 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 {
|
||||
|
||||
@@ -9,6 +9,7 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_diagnostics, settings};
|
||||
|
||||
@@ -44,4 +45,17 @@ 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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,16 @@ 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).
|
||||
@@ -47,6 +51,8 @@ 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;
|
||||
|
||||
@@ -203,6 +209,28 @@ 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) {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
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)
|
||||
@@ -1,9 +1,11 @@
|
||||
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::Violation;
|
||||
use crate::AlwaysFixableViolation;
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
@@ -61,6 +63,10 @@ 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)]
|
||||
@@ -68,12 +74,16 @@ pub(crate) struct FutureRewritableTypeAnnotation {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for FutureRewritableTypeAnnotation {
|
||||
impl AlwaysFixableViolation 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
|
||||
@@ -83,7 +93,17 @@ pub(crate) fn future_rewritable_type_annotation(checker: &Checker, expr: &Expr)
|
||||
.resolve_qualified_name(expr)
|
||||
.map(|binding| binding.to_string());
|
||||
|
||||
if let Some(name) = name {
|
||||
checker.report_diagnostic(FutureRewritableTypeAnnotation { name }, expr.range());
|
||||
}
|
||||
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()),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
---
|
||||
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`
|
||||
|
||||
edge_case.py:6:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
|
||||
ℹ 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`
|
||||
|
|
||||
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 |
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
---
|
||||
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 |
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
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]] = []
|
||||
@@ -10,8 +9,15 @@ from_typing_import_many.py:5:13: FA100 Add `from __future__ import annotations`
|
||||
6 | a_list.append("hello")
|
||||
7 | a_dict = cast(Dict[int | None, Union[int, Set[bool]]], {})
|
||||
|
|
||||
= help: Add `from __future__ import annotations`
|
||||
|
||||
from_typing_import_many.py:5:18: FA100 Add `from __future__ import annotations` to simplify `typing.Optional`
|
||||
ℹ 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`
|
||||
|
|
||||
4 | def main() -> None:
|
||||
5 | a_list: List[Optional[str]] = []
|
||||
@@ -19,3 +25,10 @@ from_typing_import_many.py:5:18: FA100 Add `from __future__ import annotations`
|
||||
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 |
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
---
|
||||
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 |
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
---
|
||||
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 |
|
||||
|
||||
@@ -59,6 +59,7 @@ impl Violation for ExcInfoOutsideExceptHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// LOG014
|
||||
pub(crate) fn exc_info_outside_except_handler(checker: &Checker, call: &ExprCall) {
|
||||
let semantic = checker.semantic();
|
||||
|
||||
|
||||
@@ -47,22 +47,16 @@ fn check_msg(checker: &Checker, msg: &Expr) {
|
||||
// Check for string concatenation and percent format.
|
||||
Expr::BinOp(ast::ExprBinOp { op, .. }) => match op {
|
||||
Operator::Add => {
|
||||
if checker.is_rule_enabled(Rule::LoggingStringConcat) {
|
||||
checker.report_diagnostic(LoggingStringConcat, msg.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(LoggingStringConcat, msg.range());
|
||||
}
|
||||
Operator::Mod => {
|
||||
if checker.is_rule_enabled(Rule::LoggingPercentFormat) {
|
||||
checker.report_diagnostic(LoggingPercentFormat, msg.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(LoggingPercentFormat, msg.range());
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
// Check for f-strings.
|
||||
Expr::FString(_) => {
|
||||
if checker.is_rule_enabled(Rule::LoggingFString) {
|
||||
checker.report_diagnostic(LoggingFString, msg.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(LoggingFString, msg.range());
|
||||
}
|
||||
// Check for .format() calls.
|
||||
Expr::Call(ast::ExprCall { func, .. }) => {
|
||||
@@ -171,7 +165,7 @@ pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) {
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// G001 - G004
|
||||
// G001, G002, G003, 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);
|
||||
@@ -209,14 +203,10 @@ pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) {
|
||||
if let LoggingCallType::LevelCall(logging_level) = logging_call_type {
|
||||
match logging_level {
|
||||
LoggingLevel::Error => {
|
||||
if checker.is_rule_enabled(Rule::LoggingExcInfo) {
|
||||
checker.report_diagnostic(LoggingExcInfo, range);
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(LoggingExcInfo, range);
|
||||
}
|
||||
LoggingLevel::Exception => {
|
||||
if checker.is_rule_enabled(Rule::LoggingRedundantExcInfo) {
|
||||
checker.report_diagnostic(LoggingRedundantExcInfo, exc_info.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(LoggingRedundantExcInfo, exc_info.range());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -148,8 +148,6 @@ pub(crate) fn bad_version_info_comparison(checker: &Checker, test: &Expr, has_el
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if checker.is_rule_enabled(Rule::BadVersionInfoComparison) {
|
||||
checker.report_diagnostic(BadVersionInfoComparison, test.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(BadVersionInfoComparison, test.range());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,9 +114,7 @@ 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) {
|
||||
if checker.is_rule_enabled(Rule::UnrecognizedPlatformCheck) {
|
||||
checker.report_diagnostic(UnrecognizedPlatformCheck, test.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(UnrecognizedPlatformCheck, test.range());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -134,8 +132,6 @@ pub(crate) fn unrecognized_platform(checker: &Checker, test: &Expr) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if checker.is_rule_enabled(Rule::UnrecognizedPlatformCheck) {
|
||||
checker.report_diagnostic(UnrecognizedPlatformCheck, test.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(UnrecognizedPlatformCheck, test.range());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,9 +147,7 @@ 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 {
|
||||
if checker.is_rule_enabled(Rule::UnrecognizedVersionInfoCheck) {
|
||||
checker.report_diagnostic(UnrecognizedVersionInfoCheck, test.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(UnrecognizedVersionInfoCheck, test.range());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,33 +161,25 @@ fn version_check(
|
||||
// Single digit comparison, e.g., `sys.version_info[0] == 2`.
|
||||
if expected == ExpectedComparator::MajorDigit {
|
||||
if !is_int_constant(comparator) {
|
||||
if checker.is_rule_enabled(Rule::UnrecognizedVersionInfoCheck) {
|
||||
checker.report_diagnostic(UnrecognizedVersionInfoCheck, test.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(UnrecognizedVersionInfoCheck, test.range());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Tuple comparison, e.g., `sys.version_info == (3, 4)`.
|
||||
let Expr::Tuple(tuple) = comparator else {
|
||||
if checker.is_rule_enabled(Rule::UnrecognizedVersionInfoCheck) {
|
||||
checker.report_diagnostic(UnrecognizedVersionInfoCheck, test.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(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)`.
|
||||
if checker.is_rule_enabled(Rule::UnrecognizedVersionInfoCheck) {
|
||||
checker.report_diagnostic(UnrecognizedVersionInfoCheck, test.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(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)`.
|
||||
if checker.is_rule_enabled(Rule::PatchVersionComparison) {
|
||||
checker.report_diagnostic(PatchVersionComparison, test.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(PatchVersionComparison, test.range());
|
||||
}
|
||||
|
||||
if checker.is_rule_enabled(Rule::WrongTupleLengthVersionComparison) {
|
||||
|
||||
@@ -55,6 +55,7 @@ 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
|
||||
|
||||
@@ -225,6 +225,7 @@ 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 =
|
||||
|
||||
@@ -170,6 +170,7 @@ 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) {
|
||||
@@ -205,6 +206,7 @@ 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()),
|
||||
|
||||
@@ -12,6 +12,7 @@ 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"))]
|
||||
@@ -58,6 +59,7 @@ 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"))]
|
||||
@@ -76,4 +78,23 @@ 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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
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 crate::Violation;
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_python_ast::{Expr, ExprCall};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.getsize`.
|
||||
@@ -32,6 +37,9 @@ use crate::Violation;
|
||||
/// 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)
|
||||
@@ -43,8 +51,77 @@ use crate::Violation;
|
||||
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"]))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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, OsPathGetsize,
|
||||
Glob, OsPathGetatime, OsPathGetctime, OsPathGetmtime,
|
||||
};
|
||||
use crate::rules::flake8_use_pathlib::violations::{
|
||||
BuiltinOpen, Joiner, OsChmod, OsGetcwd, OsListdir, OsMakedirs, OsMkdir, OsPathAbspath,
|
||||
@@ -194,8 +194,6 @@ 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
|
||||
|
||||
@@ -1,74 +1,357 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
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`
|
||||
PTH202.py:10:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
7 | os.path.getsize(b"filename")
|
||||
8 | os.path.getsize(Path("filename"))
|
||||
9 | os.path.getsize(__file__)
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
10 |
|
||||
11 | getsize("filename")
|
||||
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`
|
||||
|
||||
PTH202.py:11:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
9 | os.path.getsize(__file__)
|
||||
10 |
|
||||
11 | getsize("filename")
|
||||
| ^^^^^^^ PTH202
|
||||
12 | getsize(b"filename")
|
||||
13 | getsize(Path("filename"))
|
||||
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`
|
||||
|
||||
PTH202.py:12:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
11 | getsize("filename")
|
||||
12 | getsize(b"filename")
|
||||
| ^^^^^^^ PTH202
|
||||
13 | getsize(Path("filename"))
|
||||
14 | getsize(__file__)
|
||||
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`
|
||||
|
||||
PTH202.py:13:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
11 | getsize("filename")
|
||||
12 | getsize(b"filename")
|
||||
13 | getsize(Path("filename"))
|
||||
| ^^^^^^^ PTH202
|
||||
14 | getsize(__file__)
|
||||
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`
|
||||
|
||||
PTH202.py:14:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
PTH202.py:15:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
12 | getsize(b"filename")
|
||||
13 | getsize(Path("filename"))
|
||||
14 | getsize(__file__)
|
||||
| ^^^^^^^ PTH202
|
||||
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`
|
||||
|
||||
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`
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
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`
|
||||
@@ -0,0 +1,697 @@
|
||||
---
|
||||
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
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
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
|
||||
@@ -154,6 +154,7 @@ fn get_complexity_number(stmts: &[Stmt]) -> usize {
|
||||
complexity
|
||||
}
|
||||
|
||||
/// C901
|
||||
pub(crate) fn function_is_too_complex(
|
||||
checker: &Checker,
|
||||
stmt: &Stmt,
|
||||
|
||||
@@ -181,15 +181,19 @@ 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);
|
||||
}
|
||||
|
||||
@@ -163,12 +163,15 @@ 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)
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_manual_dict_comprehension_enabled;
|
||||
@@ -139,28 +139,11 @@ 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
|
||||
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())
|
||||
};
|
||||
// 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;
|
||||
}
|
||||
|
||||
match target {
|
||||
Expr::Tuple(tuple) => {
|
||||
@@ -176,10 +159,6 @@ 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) {
|
||||
@@ -188,12 +167,6 @@ 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,
|
||||
}
|
||||
@@ -380,9 +353,18 @@ 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())
|
||||
);
|
||||
|
||||
@@ -473,3 +455,25 @@ 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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -146,5 +146,16 @@ 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
|
||||
|
||||
@@ -340,6 +340,8 @@ 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`
|
||||
|
||||
@@ -351,3 +353,24 @@ 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 ()}
|
||||
|
||||
@@ -4,7 +4,6 @@ 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.
|
||||
@@ -44,9 +43,6 @@ pub(crate) fn not_empty(checker: &Checker, docstring: &Docstring) -> bool {
|
||||
if !docstring.body().trim().is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if checker.is_rule_enabled(Rule::EmptyDocstring) {
|
||||
checker.report_diagnostic(EmptyDocstring, docstring.range());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(EmptyDocstring, docstring.range());
|
||||
false
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ 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.
|
||||
@@ -551,36 +550,29 @@ pub(crate) fn not_missing(
|
||||
if checker.source_type.is_ipynb() {
|
||||
return true;
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::UndocumentedPublicModule) {
|
||||
checker.report_diagnostic(UndocumentedPublicModule, TextRange::default());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(UndocumentedPublicModule, TextRange::default());
|
||||
false
|
||||
}
|
||||
Definition::Module(Module {
|
||||
kind: ModuleKind::Package,
|
||||
..
|
||||
}) => {
|
||||
if checker.is_rule_enabled(Rule::UndocumentedPublicPackage) {
|
||||
checker.report_diagnostic(UndocumentedPublicPackage, TextRange::default());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(UndocumentedPublicPackage, TextRange::default());
|
||||
false
|
||||
}
|
||||
Definition::Member(Member {
|
||||
kind: MemberKind::Class(class),
|
||||
..
|
||||
}) => {
|
||||
if checker.is_rule_enabled(Rule::UndocumentedPublicClass) {
|
||||
checker.report_diagnostic(UndocumentedPublicClass, class.identifier());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(UndocumentedPublicClass, class.identifier());
|
||||
false
|
||||
}
|
||||
Definition::Member(Member {
|
||||
kind: MemberKind::NestedClass(function),
|
||||
..
|
||||
}) => {
|
||||
if checker.is_rule_enabled(Rule::UndocumentedPublicNestedClass) {
|
||||
checker.report_diagnostic(UndocumentedPublicNestedClass, function.identifier());
|
||||
}
|
||||
checker
|
||||
.report_diagnostic_if_enabled(UndocumentedPublicNestedClass, function.identifier());
|
||||
false
|
||||
}
|
||||
Definition::Member(Member {
|
||||
@@ -590,9 +582,10 @@ pub(crate) fn not_missing(
|
||||
if is_overload(&function.decorator_list, checker.semantic()) {
|
||||
true
|
||||
} else {
|
||||
if checker.is_rule_enabled(Rule::UndocumentedPublicFunction) {
|
||||
checker.report_diagnostic(UndocumentedPublicFunction, function.identifier());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(
|
||||
UndocumentedPublicFunction,
|
||||
function.identifier(),
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -605,24 +598,19 @@ pub(crate) fn not_missing(
|
||||
{
|
||||
true
|
||||
} else if is_init(&function.name) {
|
||||
if checker.is_rule_enabled(Rule::UndocumentedPublicInit) {
|
||||
checker.report_diagnostic(UndocumentedPublicInit, function.identifier());
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(UndocumentedPublicInit, function.identifier());
|
||||
true
|
||||
} else if is_new(&function.name) || is_call(&function.name) {
|
||||
if checker.is_rule_enabled(Rule::UndocumentedPublicMethod) {
|
||||
checker.report_diagnostic(UndocumentedPublicMethod, function.identifier());
|
||||
}
|
||||
checker
|
||||
.report_diagnostic_if_enabled(UndocumentedPublicMethod, function.identifier());
|
||||
true
|
||||
} else if is_magic(&function.name) {
|
||||
if checker.is_rule_enabled(Rule::UndocumentedMagicMethod) {
|
||||
checker.report_diagnostic(UndocumentedMagicMethod, function.identifier());
|
||||
}
|
||||
checker
|
||||
.report_diagnostic_if_enabled(UndocumentedMagicMethod, function.identifier());
|
||||
true
|
||||
} else {
|
||||
if checker.is_rule_enabled(Rule::UndocumentedPublicMethod) {
|
||||
checker.report_diagnostic(UndocumentedPublicMethod, function.identifier());
|
||||
}
|
||||
checker
|
||||
.report_diagnostic_if_enabled(UndocumentedPublicMethod, function.identifier());
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1434,14 +1434,12 @@ fn blanks_and_section_underline(
|
||||
}
|
||||
|
||||
if following_lines.peek().is_none() {
|
||||
if checker.is_rule_enabled(Rule::EmptyDocstringSection) {
|
||||
checker.report_diagnostic(
|
||||
EmptyDocstringSection {
|
||||
name: context.section_name().to_string(),
|
||||
},
|
||||
context.section_name_range(),
|
||||
);
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(
|
||||
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:
|
||||
@@ -1495,14 +1493,12 @@ fn blanks_and_section_underline(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if checker.is_rule_enabled(Rule::EmptyDocstringSection) {
|
||||
checker.report_diagnostic(
|
||||
EmptyDocstringSection {
|
||||
name: context.section_name().to_string(),
|
||||
},
|
||||
context.section_name_range(),
|
||||
);
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(
|
||||
EmptyDocstringSection {
|
||||
name: context.section_name().to_string(),
|
||||
},
|
||||
context.section_name_range(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if style.is_numpy() && checker.is_rule_enabled(Rule::MissingDashedUnderlineAfterSection)
|
||||
@@ -1618,14 +1614,12 @@ fn blanks_and_section_underline(
|
||||
context.summary_range().end(),
|
||||
)));
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::EmptyDocstringSection) {
|
||||
checker.report_diagnostic(
|
||||
EmptyDocstringSection {
|
||||
name: context.section_name().to_string(),
|
||||
},
|
||||
context.section_name_range(),
|
||||
);
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(
|
||||
EmptyDocstringSection {
|
||||
name: context.section_name().to_string(),
|
||||
},
|
||||
context.section_name_range(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ impl Violation for FutureFeatureNotDefined {
|
||||
}
|
||||
}
|
||||
|
||||
/// F407
|
||||
pub(crate) fn future_feature_not_defined(checker: &Checker, alias: &Alias) {
|
||||
if is_feature_name(&alias.name) {
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
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.
|
||||
@@ -44,6 +47,48 @@ 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.
|
||||
///
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
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::{FixAvailability, Violation};
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits;
|
||||
use crate::{Fix, FixAvailability, Violation};
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for variable definitions that redefine (or "shadow") unused
|
||||
@@ -43,3 +50,154 @@ 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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};
|
||||
@@ -138,6 +140,16 @@ 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)]
|
||||
@@ -379,6 +391,16 @@ 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)]
|
||||
@@ -420,6 +442,16 @@ 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)]
|
||||
@@ -603,15 +635,24 @@ 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::safe_edit(edit))
|
||||
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
|
||||
},
|
||||
))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -734,16 +775,19 @@ pub(crate) fn string_dot_format_extra_named_arguments(
|
||||
return;
|
||||
}
|
||||
|
||||
let keywords = keywords
|
||||
let keyword_names = keywords
|
||||
.iter()
|
||||
.filter_map(|Keyword { arg, .. }| arg.as_ref());
|
||||
.filter_map(|Keyword { arg, value, .. }| Some((arg.as_ref()?, value)));
|
||||
|
||||
let missing: Vec<(usize, &Name)> = keywords
|
||||
let mut side_effects = false;
|
||||
let missing: Vec<(usize, &Name)> = keyword_names
|
||||
.enumerate()
|
||||
.filter_map(|(index, keyword)| {
|
||||
.filter_map(|(index, (keyword, value))| {
|
||||
if summary.keywords.contains(keyword.id()) {
|
||||
None
|
||||
} else {
|
||||
side_effects |=
|
||||
contains_effect(value, |id| checker.semantic().has_builtin_binding(id));
|
||||
Some((index, &keyword.id))
|
||||
}
|
||||
})
|
||||
@@ -758,7 +802,7 @@ pub(crate) fn string_dot_format_extra_named_arguments(
|
||||
StringDotFormatExtraNamedArguments { missing: names },
|
||||
call.range(),
|
||||
);
|
||||
let indexes: Vec<usize> = missing.iter().map(|(index, _)| *index).collect();
|
||||
let indexes: Vec<usize> = missing.into_iter().map(|(index, _)| index).collect();
|
||||
diagnostic.try_set_fix(|| {
|
||||
let edit = remove_unused_keyword_arguments_from_format_call(
|
||||
&indexes,
|
||||
@@ -766,7 +810,16 @@ pub(crate) fn string_dot_format_extra_named_arguments(
|
||||
checker.locator(),
|
||||
checker.stylist(),
|
||||
)?;
|
||||
Ok(Fix::safe_edit(edit))
|
||||
|
||||
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
|
||||
},
|
||||
))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -802,13 +855,17 @@ 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, _)| i)
|
||||
.map(|(i, arg)| {
|
||||
side_effects |= contains_effect(arg, |id| checker.semantic().has_builtin_binding(id));
|
||||
i
|
||||
})
|
||||
.collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
@@ -833,7 +890,15 @@ pub(crate) fn string_dot_format_extra_positional_arguments(
|
||||
checker.locator(),
|
||||
checker.stylist(),
|
||||
)?;
|
||||
Ok(Fix::safe_edit(edit))
|
||||
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
|
||||
},
|
||||
))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +276,7 @@ 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__
|
||||
|
||||
@@ -89,6 +89,8 @@ F504.py:14:1: F504 [*] `%`-format string has unused named argument(s): test1, te
|
||||
15 | | 'test1': '',
|
||||
'test2': '',
|
||||
16 | | }
|
||||
| |_^ F504
|
||||
17 |
|
||||
18 | # https://github.com/astral-sh/ruff/issues/18806
|
||||
|
|
||||
= help: Remove extra named arguments: test1, test2
|
||||
@@ -99,3 +101,20 @@ F504.py:14:1: F504 [*] `%`-format string has unused named argument(s): test1, te
|
||||
14 14 | "" % {
|
||||
15 |- 'test1': '',
|
||||
16 |- 'test2': '',
|
||||
15 |+
|
||||
17 16 | }
|
||||
18 17 |
|
||||
19 18 | # https://github.com/astral-sh/ruff/issues/18806
|
||||
|
||||
F504.py:20:1: F504 [*] `%`-format string has unused named argument(s): greeting
|
||||
|
|
||||
19 | # https://github.com/astral-sh/ruff/issues/18806
|
||||
20 | "Hello, %(name)s" % {"greeting": print(1), "name": "World"}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ F504
|
||||
|
|
||||
= help: Remove extra named arguments: greeting
|
||||
|
||||
ℹ Unsafe fix
|
||||
17 17 | }
|
||||
18 18 |
|
||||
19 19 | # https://github.com/astral-sh/ruff/issues/18806
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
F522.py:1:1: F522 [*] `.format` call has unused named argument(s): bar
|
||||
|
|
||||
@@ -55,6 +54,7 @@ F522.py:4:1: F522 [*] `.format` call has unused named argument(s): eggs, ham
|
||||
4 |+"{bar:{spam}}".format(bar=2, spam=3, ) # F522
|
||||
5 5 | (''
|
||||
6 6 | .format(x=2)) # F522
|
||||
7 7 |
|
||||
|
||||
F522.py:5:2: F522 [*] `.format` call has unused named argument(s): x
|
||||
|
|
||||
@@ -64,6 +64,8 @@ F522.py:5:2: F522 [*] `.format` call has unused named argument(s): x
|
||||
| __^
|
||||
6 | | .format(x=2)) # F522
|
||||
| |_____________^ F522
|
||||
7 |
|
||||
8 | # https://github.com/astral-sh/ruff/issues/18806
|
||||
|
|
||||
= help: Remove extra named arguments: x
|
||||
|
||||
@@ -73,3 +75,43 @@ F522.py:5:2: F522 [*] `.format` call has unused named argument(s): x
|
||||
5 5 | (''
|
||||
6 |- .format(x=2)) # F522
|
||||
6 |+ .format()) # F522
|
||||
7 7 |
|
||||
8 8 | # https://github.com/astral-sh/ruff/issues/18806
|
||||
9 9 | # The fix here is unsafe because the unused argument has side effect
|
||||
|
||||
F522.py:10:1: F522 [*] `.format` call has unused named argument(s): greeting
|
||||
|
|
||||
8 | # https://github.com/astral-sh/ruff/issues/18806
|
||||
9 | # The fix here is unsafe because the unused argument has side effect
|
||||
10 | "Hello, {name}".format(greeting=print(1), name="World")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ F522
|
||||
11 |
|
||||
12 | # The fix here is safe because the unused argument has no side effect,
|
||||
|
|
||||
= help: Remove extra named arguments: greeting
|
||||
|
||||
ℹ Unsafe fix
|
||||
7 7 |
|
||||
8 8 | # https://github.com/astral-sh/ruff/issues/18806
|
||||
9 9 | # The fix here is unsafe because the unused argument has side effect
|
||||
10 |-"Hello, {name}".format(greeting=print(1), name="World")
|
||||
10 |+"Hello, {name}".format(name="World")
|
||||
11 11 |
|
||||
12 12 | # The fix here is safe because the unused argument has no side effect,
|
||||
13 13 | # even though the used argument has a side effect
|
||||
|
||||
F522.py:14:1: F522 [*] `.format` call has unused named argument(s): greeting
|
||||
|
|
||||
12 | # The fix here is safe because the unused argument has no side effect,
|
||||
13 | # even though the used argument has a side effect
|
||||
14 | "Hello, {name}".format(greeting="Pikachu", name=print(1))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ F522
|
||||
|
|
||||
= help: Remove extra named arguments: greeting
|
||||
|
||||
ℹ Safe fix
|
||||
11 11 |
|
||||
12 12 | # The fix here is safe because the unused argument has no side effect,
|
||||
13 13 | # even though the used argument has a side effect
|
||||
14 |-"Hello, {name}".format(greeting="Pikachu", name=print(1))
|
||||
14 |+"Hello, {name}".format(name=print(1))
|
||||
|
||||
@@ -286,6 +286,8 @@ F523.py:36:1: F523 [*] `.format` call has unused arguments at position(s): 0
|
||||
36 |-"Hello".format("world")
|
||||
36 |+"Hello"
|
||||
37 37 | "Hello".format("world", key="value")
|
||||
38 38 |
|
||||
39 39 | # https://github.com/astral-sh/ruff/issues/18806
|
||||
|
||||
F523.py:37:1: F523 [*] `.format` call has unused arguments at position(s): 0
|
||||
|
|
||||
@@ -293,6 +295,8 @@ F523.py:37:1: F523 [*] `.format` call has unused arguments at position(s): 0
|
||||
36 | "Hello".format("world")
|
||||
37 | "Hello".format("world", key="value")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ F523
|
||||
38 |
|
||||
39 | # https://github.com/astral-sh/ruff/issues/18806
|
||||
|
|
||||
= help: Remove extra positional arguments at position(s): 0
|
||||
|
||||
@@ -302,3 +306,43 @@ F523.py:37:1: F523 [*] `.format` call has unused arguments at position(s): 0
|
||||
36 36 | "Hello".format("world")
|
||||
37 |-"Hello".format("world", key="value")
|
||||
37 |+"Hello".format(key="value")
|
||||
38 38 |
|
||||
39 39 | # https://github.com/astral-sh/ruff/issues/18806
|
||||
40 40 | # The fix here is unsafe because the unused argument has side effect
|
||||
|
||||
F523.py:41:1: F523 [*] `.format` call has unused arguments at position(s): 1
|
||||
|
|
||||
39 | # https://github.com/astral-sh/ruff/issues/18806
|
||||
40 | # The fix here is unsafe because the unused argument has side effect
|
||||
41 | "Hello, {0}".format("world", print(1))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ F523
|
||||
42 |
|
||||
43 | # The fix here is safe because the unused argument has no side effect,
|
||||
|
|
||||
= help: Remove extra positional arguments at position(s): 1
|
||||
|
||||
ℹ Unsafe fix
|
||||
38 38 |
|
||||
39 39 | # https://github.com/astral-sh/ruff/issues/18806
|
||||
40 40 | # The fix here is unsafe because the unused argument has side effect
|
||||
41 |-"Hello, {0}".format("world", print(1))
|
||||
41 |+"Hello, {0}".format("world", )
|
||||
42 42 |
|
||||
43 43 | # The fix here is safe because the unused argument has no side effect,
|
||||
44 44 | # even though the used argument has a side effect
|
||||
|
||||
F523.py:45:1: F523 [*] `.format` call has unused arguments at position(s): 1
|
||||
|
|
||||
43 | # The fix here is safe because the unused argument has no side effect,
|
||||
44 | # even though the used argument has a side effect
|
||||
45 | "Hello, {0}".format(print(1), "Pikachu")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ F523
|
||||
|
|
||||
= help: Remove extra positional arguments at position(s): 1
|
||||
|
||||
ℹ Safe fix
|
||||
42 42 |
|
||||
43 43 | # The fix here is safe because the unused argument has no side effect,
|
||||
44 44 | # even though the used argument has a side effect
|
||||
45 |-"Hello, {0}".format(print(1), "Pikachu")
|
||||
45 |+"Hello, {0}".format(print(1), )
|
||||
|
||||
@@ -10,6 +10,7 @@ mod tests {
|
||||
|
||||
use crate::registry::Rule;
|
||||
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_diagnostics, settings};
|
||||
|
||||
@@ -29,4 +30,22 @@ mod tests {
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::InvalidMockAccess, Path::new("PGH005_0.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("pygrep_hooks").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_invalid_async_mock_access_check_enabled;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum Reason {
|
||||
@@ -51,7 +52,7 @@ impl Violation for InvalidMockAccess {
|
||||
/// PGH005
|
||||
pub(crate) fn uncalled_mock_method(checker: &Checker, expr: &Expr) {
|
||||
if let Expr::Attribute(ast::ExprAttribute { attr, .. }) = expr {
|
||||
if matches!(
|
||||
let is_uncalled_mock_method = matches!(
|
||||
attr.as_str(),
|
||||
"assert_any_call"
|
||||
| "assert_called"
|
||||
@@ -60,7 +61,20 @@ pub(crate) fn uncalled_mock_method(checker: &Checker, expr: &Expr) {
|
||||
| "assert_called_with"
|
||||
| "assert_has_calls"
|
||||
| "assert_not_called"
|
||||
) {
|
||||
);
|
||||
let is_uncalled_async_mock_method =
|
||||
is_invalid_async_mock_access_check_enabled(checker.settings())
|
||||
&& matches!(
|
||||
attr.as_str(),
|
||||
"assert_awaited"
|
||||
| "assert_awaited_once"
|
||||
| "assert_awaited_with"
|
||||
| "assert_awaited_once_with"
|
||||
| "assert_any_await"
|
||||
| "assert_has_awaits"
|
||||
| "assert_not_awaited"
|
||||
);
|
||||
if is_uncalled_mock_method || is_uncalled_async_mock_method {
|
||||
checker.report_diagnostic(
|
||||
InvalidMockAccess {
|
||||
reason: Reason::UncalledMethod(attr.to_string()),
|
||||
@@ -81,7 +95,7 @@ pub(crate) fn non_existent_mock_method(checker: &Checker, test: &Expr) {
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
if matches!(
|
||||
let is_missing_mock_method = matches!(
|
||||
attr.as_str(),
|
||||
"any_call"
|
||||
| "called_once"
|
||||
@@ -89,7 +103,20 @@ pub(crate) fn non_existent_mock_method(checker: &Checker, test: &Expr) {
|
||||
| "called_with"
|
||||
| "has_calls"
|
||||
| "not_called"
|
||||
) {
|
||||
);
|
||||
let is_missing_async_mock_method =
|
||||
is_invalid_async_mock_access_check_enabled(checker.settings())
|
||||
&& matches!(
|
||||
attr.as_str(),
|
||||
"awaited"
|
||||
| "awaited_once"
|
||||
| "awaited_with"
|
||||
| "awaited_once_with"
|
||||
| "any_await"
|
||||
| "has_awaits"
|
||||
| "not_awaited"
|
||||
);
|
||||
if is_missing_mock_method || is_missing_async_mock_method {
|
||||
checker.report_diagnostic(
|
||||
InvalidMockAccess {
|
||||
reason: Reason::NonExistentMethod(attr.to_string()),
|
||||
|
||||
@@ -1,90 +1,91 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pygrep_hooks/mod.rs
|
||||
---
|
||||
PGH005_0.py:2:8: PGH005 Non-existent mock method: `not_called`
|
||||
|
|
||||
1 | # Errors
|
||||
2 | assert my_mock.not_called()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
3 | assert my_mock.called_once_with()
|
||||
4 | assert my_mock.not_called
|
||||
|
|
||||
|
||||
PGH005_0.py:3:8: PGH005 Non-existent mock method: `called_once_with`
|
||||
|
|
||||
1 | # Errors
|
||||
2 | assert my_mock.not_called()
|
||||
3 | assert my_mock.called_once_with()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
4 | assert my_mock.not_called
|
||||
5 | assert my_mock.called_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:4:8: PGH005 Non-existent mock method: `not_called`
|
||||
|
|
||||
2 | assert my_mock.not_called()
|
||||
3 | assert my_mock.called_once_with()
|
||||
4 | assert my_mock.not_called
|
||||
| ^^^^^^^^^^^^^^^^^^ PGH005
|
||||
5 | assert my_mock.called_once_with
|
||||
6 | my_mock.assert_not_called
|
||||
2 | # ============
|
||||
3 | # Errors
|
||||
4 | assert my_mock.not_called()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
5 | assert my_mock.called_once_with()
|
||||
6 | assert my_mock.not_called
|
||||
|
|
||||
|
||||
PGH005_0.py:5:8: PGH005 Non-existent mock method: `called_once_with`
|
||||
|
|
||||
3 | assert my_mock.called_once_with()
|
||||
4 | assert my_mock.not_called
|
||||
5 | assert my_mock.called_once_with
|
||||
3 | # Errors
|
||||
4 | assert my_mock.not_called()
|
||||
5 | assert my_mock.called_once_with()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
6 | assert my_mock.not_called
|
||||
7 | assert my_mock.called_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:6:8: PGH005 Non-existent mock method: `not_called`
|
||||
|
|
||||
4 | assert my_mock.not_called()
|
||||
5 | assert my_mock.called_once_with()
|
||||
6 | assert my_mock.not_called
|
||||
| ^^^^^^^^^^^^^^^^^^ PGH005
|
||||
7 | assert my_mock.called_once_with
|
||||
8 | my_mock.assert_not_called
|
||||
|
|
||||
|
||||
PGH005_0.py:7:8: PGH005 Non-existent mock method: `called_once_with`
|
||||
|
|
||||
5 | assert my_mock.called_once_with()
|
||||
6 | assert my_mock.not_called
|
||||
7 | assert my_mock.called_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
6 | my_mock.assert_not_called
|
||||
7 | my_mock.assert_called
|
||||
8 | my_mock.assert_not_called
|
||||
9 | my_mock.assert_called
|
||||
|
|
||||
|
||||
PGH005_0.py:6:1: PGH005 Mock method should be called: `assert_not_called`
|
||||
|
|
||||
4 | assert my_mock.not_called
|
||||
5 | assert my_mock.called_once_with
|
||||
6 | my_mock.assert_not_called
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
7 | my_mock.assert_called
|
||||
8 | my_mock.assert_called_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:7:1: PGH005 Mock method should be called: `assert_called`
|
||||
|
|
||||
5 | assert my_mock.called_once_with
|
||||
6 | my_mock.assert_not_called
|
||||
7 | my_mock.assert_called
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
8 | my_mock.assert_called_once_with
|
||||
9 | my_mock.assert_called_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:8:1: PGH005 Mock method should be called: `assert_called_once_with`
|
||||
PGH005_0.py:8:1: PGH005 Mock method should be called: `assert_not_called`
|
||||
|
|
||||
6 | my_mock.assert_not_called
|
||||
7 | my_mock.assert_called
|
||||
8 | my_mock.assert_called_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
9 | my_mock.assert_called_once_with
|
||||
10 | MyMock.assert_called_once_with
|
||||
6 | assert my_mock.not_called
|
||||
7 | assert my_mock.called_once_with
|
||||
8 | my_mock.assert_not_called
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
9 | my_mock.assert_called
|
||||
10 | my_mock.assert_called_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:9:1: PGH005 Mock method should be called: `assert_called_once_with`
|
||||
PGH005_0.py:9:1: PGH005 Mock method should be called: `assert_called`
|
||||
|
|
||||
7 | my_mock.assert_called
|
||||
8 | my_mock.assert_called_once_with
|
||||
9 | my_mock.assert_called_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
10 | MyMock.assert_called_once_with
|
||||
7 | assert my_mock.called_once_with
|
||||
8 | my_mock.assert_not_called
|
||||
9 | my_mock.assert_called
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
10 | my_mock.assert_called_once_with
|
||||
11 | my_mock.assert_called_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:10:1: PGH005 Mock method should be called: `assert_called_once_with`
|
||||
|
|
||||
8 | my_mock.assert_called_once_with
|
||||
9 | my_mock.assert_called_once_with
|
||||
10 | MyMock.assert_called_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
11 |
|
||||
12 | # OK
|
||||
8 | my_mock.assert_not_called
|
||||
9 | my_mock.assert_called
|
||||
10 | my_mock.assert_called_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
11 | my_mock.assert_called_once_with
|
||||
12 | MyMock.assert_called_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:11:1: PGH005 Mock method should be called: `assert_called_once_with`
|
||||
|
|
||||
9 | my_mock.assert_called
|
||||
10 | my_mock.assert_called_once_with
|
||||
11 | my_mock.assert_called_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
12 | MyMock.assert_called_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:12:1: PGH005 Mock method should be called: `assert_called_once_with`
|
||||
|
|
||||
10 | my_mock.assert_called_once_with
|
||||
11 | my_mock.assert_called_once_with
|
||||
12 | MyMock.assert_called_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
13 |
|
||||
14 | # OK
|
||||
|
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pygrep_hooks/mod.rs
|
||||
---
|
||||
PGH005_0.py:4:8: PGH005 Non-existent mock method: `not_called`
|
||||
|
|
||||
2 | # ============
|
||||
3 | # Errors
|
||||
4 | assert my_mock.not_called()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
5 | assert my_mock.called_once_with()
|
||||
6 | assert my_mock.not_called
|
||||
|
|
||||
|
||||
PGH005_0.py:5:8: PGH005 Non-existent mock method: `called_once_with`
|
||||
|
|
||||
3 | # Errors
|
||||
4 | assert my_mock.not_called()
|
||||
5 | assert my_mock.called_once_with()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
6 | assert my_mock.not_called
|
||||
7 | assert my_mock.called_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:6:8: PGH005 Non-existent mock method: `not_called`
|
||||
|
|
||||
4 | assert my_mock.not_called()
|
||||
5 | assert my_mock.called_once_with()
|
||||
6 | assert my_mock.not_called
|
||||
| ^^^^^^^^^^^^^^^^^^ PGH005
|
||||
7 | assert my_mock.called_once_with
|
||||
8 | my_mock.assert_not_called
|
||||
|
|
||||
|
||||
PGH005_0.py:7:8: PGH005 Non-existent mock method: `called_once_with`
|
||||
|
|
||||
5 | assert my_mock.called_once_with()
|
||||
6 | assert my_mock.not_called
|
||||
7 | assert my_mock.called_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
8 | my_mock.assert_not_called
|
||||
9 | my_mock.assert_called
|
||||
|
|
||||
|
||||
PGH005_0.py:8:1: PGH005 Mock method should be called: `assert_not_called`
|
||||
|
|
||||
6 | assert my_mock.not_called
|
||||
7 | assert my_mock.called_once_with
|
||||
8 | my_mock.assert_not_called
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
9 | my_mock.assert_called
|
||||
10 | my_mock.assert_called_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:9:1: PGH005 Mock method should be called: `assert_called`
|
||||
|
|
||||
7 | assert my_mock.called_once_with
|
||||
8 | my_mock.assert_not_called
|
||||
9 | my_mock.assert_called
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
10 | my_mock.assert_called_once_with
|
||||
11 | my_mock.assert_called_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:10:1: PGH005 Mock method should be called: `assert_called_once_with`
|
||||
|
|
||||
8 | my_mock.assert_not_called
|
||||
9 | my_mock.assert_called
|
||||
10 | my_mock.assert_called_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
11 | my_mock.assert_called_once_with
|
||||
12 | MyMock.assert_called_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:11:1: PGH005 Mock method should be called: `assert_called_once_with`
|
||||
|
|
||||
9 | my_mock.assert_called
|
||||
10 | my_mock.assert_called_once_with
|
||||
11 | my_mock.assert_called_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
12 | MyMock.assert_called_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:12:1: PGH005 Mock method should be called: `assert_called_once_with`
|
||||
|
|
||||
10 | my_mock.assert_called_once_with
|
||||
11 | my_mock.assert_called_once_with
|
||||
12 | MyMock.assert_called_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
13 |
|
||||
14 | # OK
|
||||
|
|
||||
|
||||
PGH005_0.py:26:8: PGH005 Non-existent mock method: `not_awaited`
|
||||
|
|
||||
24 | # =================
|
||||
25 | # Errors
|
||||
26 | assert my_mock.not_awaited()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
27 | assert my_mock.awaited_once_with()
|
||||
28 | assert my_mock.not_awaited
|
||||
|
|
||||
|
||||
PGH005_0.py:27:8: PGH005 Non-existent mock method: `awaited_once_with`
|
||||
|
|
||||
25 | # Errors
|
||||
26 | assert my_mock.not_awaited()
|
||||
27 | assert my_mock.awaited_once_with()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
28 | assert my_mock.not_awaited
|
||||
29 | assert my_mock.awaited_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:28:8: PGH005 Non-existent mock method: `not_awaited`
|
||||
|
|
||||
26 | assert my_mock.not_awaited()
|
||||
27 | assert my_mock.awaited_once_with()
|
||||
28 | assert my_mock.not_awaited
|
||||
| ^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
29 | assert my_mock.awaited_once_with
|
||||
30 | my_mock.assert_not_awaited
|
||||
|
|
||||
|
||||
PGH005_0.py:29:8: PGH005 Non-existent mock method: `awaited_once_with`
|
||||
|
|
||||
27 | assert my_mock.awaited_once_with()
|
||||
28 | assert my_mock.not_awaited
|
||||
29 | assert my_mock.awaited_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
30 | my_mock.assert_not_awaited
|
||||
31 | my_mock.assert_awaited
|
||||
|
|
||||
|
||||
PGH005_0.py:30:1: PGH005 Mock method should be called: `assert_not_awaited`
|
||||
|
|
||||
28 | assert my_mock.not_awaited
|
||||
29 | assert my_mock.awaited_once_with
|
||||
30 | my_mock.assert_not_awaited
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
31 | my_mock.assert_awaited
|
||||
32 | my_mock.assert_awaited_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:31:1: PGH005 Mock method should be called: `assert_awaited`
|
||||
|
|
||||
29 | assert my_mock.awaited_once_with
|
||||
30 | my_mock.assert_not_awaited
|
||||
31 | my_mock.assert_awaited
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
32 | my_mock.assert_awaited_once_with
|
||||
33 | my_mock.assert_awaited_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:32:1: PGH005 Mock method should be called: `assert_awaited_once_with`
|
||||
|
|
||||
30 | my_mock.assert_not_awaited
|
||||
31 | my_mock.assert_awaited
|
||||
32 | my_mock.assert_awaited_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
33 | my_mock.assert_awaited_once_with
|
||||
34 | MyMock.assert_awaited_once_with
|
||||
|
|
||||
|
||||
PGH005_0.py:33:1: PGH005 Mock method should be called: `assert_awaited_once_with`
|
||||
|
|
||||
31 | my_mock.assert_awaited
|
||||
32 | my_mock.assert_awaited_once_with
|
||||
33 | my_mock.assert_awaited_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
34 | MyMock.assert_awaited_once_with
|
||||
35 | assert my_mock.awaited
|
||||
|
|
||||
|
||||
PGH005_0.py:34:1: PGH005 Mock method should be called: `assert_awaited_once_with`
|
||||
|
|
||||
32 | my_mock.assert_awaited_once_with
|
||||
33 | my_mock.assert_awaited_once_with
|
||||
34 | MyMock.assert_awaited_once_with
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
|
||||
35 | assert my_mock.awaited
|
||||
|
|
||||
|
||||
PGH005_0.py:35:8: PGH005 Non-existent mock method: `awaited`
|
||||
|
|
||||
33 | my_mock.assert_awaited_once_with
|
||||
34 | MyMock.assert_awaited_once_with
|
||||
35 | assert my_mock.awaited
|
||||
| ^^^^^^^^^^^^^^^ PGH005
|
||||
36 |
|
||||
37 | # OK
|
||||
|
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user