Compare commits
1 Commits
simplify-S
...
charlie/st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3311f300e |
103
Cargo.lock
generated
103
Cargo.lock
generated
@@ -123,9 +123,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.79"
|
||||
version = "1.0.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
|
||||
|
||||
[[package]]
|
||||
name = "argfile"
|
||||
@@ -312,9 +312,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.13"
|
||||
version = "4.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52bdc885e4cacc7f7c9eedc1ef6da641603180c783c41a15c264944deeaab642"
|
||||
checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -382,7 +382,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -600,7 +600,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -611,7 +611,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -748,12 +748,23 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno-dragonfly"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1096,7 +1107,7 @@ dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1239,9 +1250,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.152"
|
||||
version = "0.2.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
|
||||
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
@@ -1286,9 +1297,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.12"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
||||
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
@@ -1685,7 +1696,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1770,9 +1781,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.76"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||
checksum = "2dd5e8a1f1029c43224ad5898e50140c2aebb1705f19e67c918ebf5b9e797fe1"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1816,9 +1827,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -1985,7 +1996,7 @@ dependencies = [
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2237,7 +2248,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_python_trivia",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2528,15 +2539,15 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.28"
|
||||
version = "0.38.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
||||
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2642,9 +2653,9 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.195"
|
||||
version = "1.0.193"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
|
||||
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -2662,13 +2673,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.195"
|
||||
version = "1.0.193"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
|
||||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2730,7 +2741,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2834,7 +2845,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2850,9 +2861,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
version = "2.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
checksum = "13fa70a4ee923979ffb522cacce59d34421ebdea5625e1073c4326ef9d2dd42e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2861,15 +2872,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.9.0"
|
||||
version = "3.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
|
||||
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall 0.4.1",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2930,7 +2941,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2942,7 +2953,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
"test-case-core",
|
||||
]
|
||||
|
||||
@@ -2963,7 +2974,7 @@ checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3100,7 +3111,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3318,7 +3329,7 @@ checksum = "f49e7f3f3db8040a100710a11932239fd30697115e2ba4107080d8252939845e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3412,7 +3423,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3446,7 +3457,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -3479,7 +3490,7 @@ checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3806,5 +3817,5 @@ checksum = "be912bf68235a88fbefd1b73415cb218405958d1655b2ece9035a19920bdf6ba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
@@ -14,14 +14,14 @@ license = "MIT"
|
||||
[workspace.dependencies]
|
||||
aho-corasick = { version = "1.1.2" }
|
||||
annotate-snippets = { version = "0.9.2", features = ["color"] }
|
||||
anyhow = { version = "1.0.79" }
|
||||
anyhow = { version = "1.0.76" }
|
||||
argfile = { version = "0.1.6" }
|
||||
assert_cmd = { version = "2.0.8" }
|
||||
bincode = { version = "1.3.3" }
|
||||
bitflags = { version = "2.4.1" }
|
||||
cachedir = { version = "0.3.1" }
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.13", features = ["derive"] }
|
||||
clap = { version = "4.4.12", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.5.1" }
|
||||
clearscreen = { version = "2.0.0" }
|
||||
codspeed-criterion-compat = { version = "2.3.3", default-features = false }
|
||||
@@ -76,7 +76,7 @@ rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version ="4.1.0"}
|
||||
semver = { version = "1.0.20" }
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
serde = { version = "1.0.193", features = ["derive"] }
|
||||
serde-wasm-bindgen = { version = "0.6.3" }
|
||||
serde_json = { version = "1.0.109" }
|
||||
serde_test = { version = "1.0.152" }
|
||||
@@ -89,7 +89,7 @@ static_assertions = "1.1.0"
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.3" }
|
||||
syn = { version = "2.0.40" }
|
||||
tempfile = { version ="3.9.0"}
|
||||
tempfile = { version ="3.8.1"}
|
||||
test-case = { version = "3.3.1" }
|
||||
thiserror = { version = "1.0.51" }
|
||||
tikv-jemallocator = { version ="0.5.0"}
|
||||
|
||||
@@ -418,7 +418,6 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- Netflix ([Dispatch](https://github.com/Netflix/dispatch))
|
||||
- [Neon](https://github.com/neondatabase/neon)
|
||||
- [NoneBot](https://github.com/nonebot/nonebot2)
|
||||
- [NumPyro](https://github.com/pyro-ppl/numpyro)
|
||||
- [ONNX](https://github.com/onnx/onnx)
|
||||
- [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
|
||||
- [PDM](https://github.com/pdm-project/pdm)
|
||||
@@ -430,7 +429,6 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [PostHog](https://github.com/PostHog/posthog)
|
||||
- Prefect ([Python SDK](https://github.com/PrefectHQ/prefect), [Marvin](https://github.com/PrefectHQ/marvin))
|
||||
- [PyInstaller](https://github.com/pyinstaller/pyinstaller)
|
||||
- [PyMC](https://github.com/pymc-devs/pymc/)
|
||||
- [PyMC-Marketing](https://github.com/pymc-labs/pymc-marketing)
|
||||
- [PyTorch](https://github.com/pytorch/pytorch)
|
||||
- [Pydantic](https://github.com/pydantic/pydantic)
|
||||
|
||||
@@ -55,7 +55,7 @@ fn benchmark_linter(mut group: BenchmarkGroup, settings: &LinterSettings) {
|
||||
&case,
|
||||
|b, case| {
|
||||
// Tokenize the source.
|
||||
let tokens: Vec<_> = lexer::lex(case.code(), Mode::Module).collect();
|
||||
let tokens = lexer::lex(case.code(), Mode::Module).collect::<Vec<_>>();
|
||||
|
||||
// Parse the source.
|
||||
let ast = parse_program_tokens(tokens.clone(), case.code(), false).unwrap();
|
||||
|
||||
@@ -25,9 +25,10 @@ use ruff_notebook::NotebookIndex;
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_workspace::resolver::Resolver;
|
||||
use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy, Resolver};
|
||||
use ruff_workspace::Settings;
|
||||
|
||||
use crate::cache;
|
||||
use crate::diagnostics::Diagnostics;
|
||||
|
||||
/// [`Path`] that is relative to the package root in [`PackageCache`].
|
||||
@@ -85,7 +86,6 @@ pub(crate) struct Cache {
|
||||
changes: Mutex<Vec<Change>>,
|
||||
/// The "current" timestamp used as cache for the updates of
|
||||
/// [`FileCache::last_seen`]
|
||||
#[allow(clippy::struct_field_names)]
|
||||
last_seen_cache: u64,
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ pub(super) struct CacheMessage {
|
||||
pub(crate) trait PackageCaches {
|
||||
fn get(&self, package_root: &Path) -> Option<&Cache>;
|
||||
|
||||
fn persist(self) -> Result<()>;
|
||||
fn persist(self) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
impl<T> PackageCaches for Option<T>
|
||||
@@ -468,17 +468,27 @@ pub(crate) struct PackageCacheMap<'a>(FxHashMap<&'a Path, Cache>);
|
||||
|
||||
impl<'a> PackageCacheMap<'a> {
|
||||
pub(crate) fn init(
|
||||
pyproject_config: &PyprojectConfig,
|
||||
package_roots: &FxHashMap<&'a Path, Option<&'a Path>>,
|
||||
resolver: &Resolver,
|
||||
) -> Self {
|
||||
fn init_cache(path: &Path) {
|
||||
if let Err(e) = init(path) {
|
||||
if let Err(e) = cache::init(path) {
|
||||
error!("Failed to initialize cache at {}: {e:?}", path.display());
|
||||
}
|
||||
}
|
||||
|
||||
for settings in resolver.settings() {
|
||||
init_cache(&settings.cache_dir);
|
||||
match pyproject_config.strategy {
|
||||
PyprojectDiscoveryStrategy::Fixed => {
|
||||
init_cache(&pyproject_config.settings.cache_dir);
|
||||
}
|
||||
PyprojectDiscoveryStrategy::Hierarchical => {
|
||||
for settings in
|
||||
std::iter::once(&pyproject_config.settings).chain(resolver.settings())
|
||||
{
|
||||
init_cache(&settings.cache_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self(
|
||||
@@ -488,7 +498,7 @@ impl<'a> PackageCacheMap<'a> {
|
||||
.unique()
|
||||
.par_bridge()
|
||||
.map(|cache_root| {
|
||||
let settings = resolver.resolve(cache_root);
|
||||
let settings = resolver.resolve(cache_root, pyproject_config);
|
||||
let cache = Cache::open(cache_root.to_path_buf(), settings);
|
||||
(cache_root, cache)
|
||||
})
|
||||
|
||||
@@ -38,6 +38,7 @@ pub(crate) fn add_noqa(
|
||||
.flatten()
|
||||
.map(ResolvedFile::path)
|
||||
.collect::<Vec<_>>(),
|
||||
pyproject_config,
|
||||
);
|
||||
|
||||
let start = Instant::now();
|
||||
@@ -56,7 +57,7 @@ pub(crate) fn add_noqa(
|
||||
.parent()
|
||||
.and_then(|parent| package_roots.get(parent))
|
||||
.and_then(|package| *package);
|
||||
let settings = resolver.resolve(path);
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
let source_kind = match SourceKind::from_path(path, source_type) {
|
||||
Ok(Some(source_kind)) => source_kind,
|
||||
Ok(None) => return None,
|
||||
|
||||
@@ -57,11 +57,16 @@ pub(crate) fn check(
|
||||
.flatten()
|
||||
.map(ResolvedFile::path)
|
||||
.collect::<Vec<_>>(),
|
||||
pyproject_config,
|
||||
);
|
||||
|
||||
// Load the caches.
|
||||
let caches = if bool::from(cache) {
|
||||
Some(PackageCacheMap::init(&package_roots, &resolver))
|
||||
Some(PackageCacheMap::init(
|
||||
pyproject_config,
|
||||
&package_roots,
|
||||
&resolver,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -76,7 +81,7 @@ pub(crate) fn check(
|
||||
.and_then(|parent| package_roots.get(parent))
|
||||
.and_then(|package| *package);
|
||||
|
||||
let settings = resolver.resolve(path);
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
|
||||
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||
&& match_exclusion(
|
||||
@@ -123,7 +128,7 @@ pub(crate) fn check(
|
||||
|
||||
Some(result.unwrap_or_else(|(path, message)| {
|
||||
if let Some(path) = &path {
|
||||
let settings = resolver.resolve(path);
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
if settings.linter.rules.enabled(Rule::IOError) {
|
||||
let dummy =
|
||||
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::Result;
|
||||
|
||||
use ruff_linter::packaging;
|
||||
use ruff_linter::settings::flags;
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig, Resolver};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::diagnostics::{lint_stdin, Diagnostics};
|
||||
@@ -18,20 +18,20 @@ pub(crate) fn check_stdin(
|
||||
noqa: flags::Noqa,
|
||||
fix_mode: flags::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
let mut resolver = Resolver::new(pyproject_config);
|
||||
|
||||
if resolver.force_exclude() {
|
||||
if pyproject_config.settings.file_resolver.force_exclude {
|
||||
if let Some(filename) = filename {
|
||||
if !python_file_at_path(filename, &mut resolver, overrides)? {
|
||||
if !python_file_at_path(filename, pyproject_config, overrides)? {
|
||||
if fix_mode.is_apply() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
|
||||
if filename.file_name().is_some_and(|name| {
|
||||
match_exclusion(filename, name, &resolver.base_settings().linter.exclude)
|
||||
}) {
|
||||
let lint_settings = &pyproject_config.settings.linter;
|
||||
if filename
|
||||
.file_name()
|
||||
.is_some_and(|name| match_exclusion(filename, name, &lint_settings.exclude))
|
||||
{
|
||||
if fix_mode.is_apply() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
@@ -41,13 +41,13 @@ pub(crate) fn check_stdin(
|
||||
}
|
||||
let stdin = read_from_stdin()?;
|
||||
let package_root = filename.and_then(Path::parent).and_then(|path| {
|
||||
packaging::detect_package_root(path, &resolver.base_settings().linter.namespace_packages)
|
||||
packaging::detect_package_root(path, &pyproject_config.settings.linter.namespace_packages)
|
||||
});
|
||||
let mut diagnostics = lint_stdin(
|
||||
filename,
|
||||
package_root,
|
||||
stdin,
|
||||
resolver.base_settings(),
|
||||
&pyproject_config.settings,
|
||||
noqa,
|
||||
fix_mode,
|
||||
)?;
|
||||
|
||||
@@ -25,7 +25,9 @@ use ruff_linter::warn_user_once;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_python_formatter::{format_module_source, FormatModuleError, QuoteStyle};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_files_in_path, ResolvedFile, Resolver};
|
||||
use ruff_workspace::resolver::{
|
||||
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile, Resolver,
|
||||
};
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::args::{CliOverrides, FormatArguments};
|
||||
@@ -77,7 +79,7 @@ pub(crate) fn format(
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
warn_incompatible_formatter_settings(&resolver);
|
||||
warn_incompatible_formatter_settings(&pyproject_config, Some(&resolver));
|
||||
|
||||
// Discover the package root for each Python file.
|
||||
let package_roots = resolver.package_roots(
|
||||
@@ -86,6 +88,7 @@ pub(crate) fn format(
|
||||
.flatten()
|
||||
.map(ResolvedFile::path)
|
||||
.collect::<Vec<_>>(),
|
||||
&pyproject_config,
|
||||
);
|
||||
|
||||
let caches = if cli.no_cache {
|
||||
@@ -96,7 +99,11 @@ pub(crate) fn format(
|
||||
#[cfg(debug_assertions)]
|
||||
crate::warn_user!("Detected debug build without --no-cache.");
|
||||
|
||||
Some(PackageCacheMap::init(&package_roots, &resolver))
|
||||
Some(PackageCacheMap::init(
|
||||
&pyproject_config,
|
||||
&package_roots,
|
||||
&resolver,
|
||||
))
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
@@ -111,7 +118,7 @@ pub(crate) fn format(
|
||||
return None;
|
||||
};
|
||||
|
||||
let settings = resolver.resolve(path);
|
||||
let settings = resolver.resolve(path, &pyproject_config);
|
||||
|
||||
// Ignore files that are excluded from formatting
|
||||
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||
@@ -716,10 +723,15 @@ impl Display for FormatCommandError {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
||||
pub(super) fn warn_incompatible_formatter_settings(
|
||||
pyproject_config: &PyprojectConfig,
|
||||
resolver: Option<&Resolver>,
|
||||
) {
|
||||
// First, collect all rules that are incompatible regardless of the linter-specific settings.
|
||||
let mut incompatible_rules = FxHashSet::default();
|
||||
for setting in resolver.settings() {
|
||||
for setting in std::iter::once(&pyproject_config.settings)
|
||||
.chain(resolver.iter().flat_map(|resolver| resolver.settings()))
|
||||
{
|
||||
for rule in [
|
||||
// The formatter might collapse implicit string concatenation on a single line.
|
||||
Rule::SingleLineImplicitStringConcatenation,
|
||||
@@ -748,7 +760,9 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
||||
}
|
||||
|
||||
// Next, validate settings-specific incompatibilities.
|
||||
for setting in resolver.settings() {
|
||||
for setting in std::iter::once(&pyproject_config.settings)
|
||||
.chain(resolver.iter().flat_map(|resolver| resolver.settings()))
|
||||
{
|
||||
// Validate all rules that rely on tab styles.
|
||||
if setting.linter.rules.enabled(Rule::TabIndentation)
|
||||
&& setting.formatter.indent_style.is_tab()
|
||||
|
||||
@@ -6,7 +6,7 @@ use log::error;
|
||||
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, Resolver};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path};
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::args::{CliOverrides, FormatArguments};
|
||||
@@ -27,23 +27,24 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
|
||||
let mut resolver = Resolver::new(&pyproject_config);
|
||||
warn_incompatible_formatter_settings(&resolver);
|
||||
warn_incompatible_formatter_settings(&pyproject_config, None);
|
||||
|
||||
let mode = FormatMode::from_cli(cli);
|
||||
|
||||
if resolver.force_exclude() {
|
||||
if pyproject_config.settings.file_resolver.force_exclude {
|
||||
if let Some(filename) = cli.stdin_filename.as_deref() {
|
||||
if !python_file_at_path(filename, &mut resolver, overrides)? {
|
||||
if !python_file_at_path(filename, &pyproject_config, overrides)? {
|
||||
if mode.is_write() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
if filename.file_name().is_some_and(|name| {
|
||||
match_exclusion(filename, name, &resolver.base_settings().formatter.exclude)
|
||||
}) {
|
||||
let format_settings = &pyproject_config.settings.formatter;
|
||||
if filename
|
||||
.file_name()
|
||||
.is_some_and(|name| match_exclusion(filename, name, &format_settings.exclude))
|
||||
{
|
||||
if mode.is_write() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
@@ -62,7 +63,12 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
};
|
||||
|
||||
// Format the file.
|
||||
match format_source_code(path, &resolver.base_settings().formatter, source_type, mode) {
|
||||
match format_source_code(
|
||||
path,
|
||||
&pyproject_config.settings.formatter,
|
||||
source_type,
|
||||
mode,
|
||||
) {
|
||||
Ok(result) => match mode {
|
||||
FormatMode::Write => Ok(ExitStatus::Success),
|
||||
FormatMode::Check | FormatMode::Diff => {
|
||||
|
||||
@@ -18,7 +18,6 @@ struct Explanation<'a> {
|
||||
summary: &'a str,
|
||||
message_formats: &'a [&'a str],
|
||||
fix: String,
|
||||
#[allow(clippy::struct_field_names)]
|
||||
explanation: Option<&'a str>,
|
||||
preview: bool,
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ pub(crate) fn show_settings(
|
||||
bail!("No files found under the given path");
|
||||
};
|
||||
|
||||
let settings = resolver.resolve(&path);
|
||||
let settings = resolver.resolve(&path, pyproject_config);
|
||||
|
||||
writeln!(writer, "Resolved settings for: {path:?}")?;
|
||||
if let Some(settings_path) = pyproject_config.path.as_ref() {
|
||||
|
||||
@@ -27,7 +27,7 @@ use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use ruff_cli::args::{CliOverrides, FormatArguments, FormatCommand, LogLevelArgs};
|
||||
use ruff_cli::args::{FormatCommand, LogLevelArgs};
|
||||
use ruff_cli::resolve::resolve;
|
||||
use ruff_formatter::{FormatError, LineWidth, PrintError};
|
||||
use ruff_linter::logging::LogLevel;
|
||||
@@ -38,24 +38,24 @@ use ruff_python_formatter::{
|
||||
use ruff_python_parser::ParseError;
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile, Resolver};
|
||||
|
||||
fn parse_cli(dirs: &[PathBuf]) -> anyhow::Result<(FormatArguments, CliOverrides)> {
|
||||
/// Find files that ruff would check so we can format them. Adapted from `ruff_cli`.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn ruff_check_paths(
|
||||
dirs: &[PathBuf],
|
||||
) -> anyhow::Result<(
|
||||
Vec<Result<ResolvedFile, ignore::Error>>,
|
||||
Resolver,
|
||||
PyprojectConfig,
|
||||
)> {
|
||||
let args_matches = FormatCommand::command()
|
||||
.no_binary_name(true)
|
||||
.get_matches_from(dirs);
|
||||
let arguments: FormatCommand = FormatCommand::from_arg_matches(&args_matches)?;
|
||||
let (cli, overrides) = arguments.partition();
|
||||
Ok((cli, overrides))
|
||||
}
|
||||
|
||||
/// Find the [`PyprojectConfig`] to use for formatting.
|
||||
fn find_pyproject_config(
|
||||
cli: &FormatArguments,
|
||||
overrides: &CliOverrides,
|
||||
) -> anyhow::Result<PyprojectConfig> {
|
||||
let mut pyproject_config = resolve(
|
||||
cli.isolated,
|
||||
cli.config.as_deref(),
|
||||
overrides,
|
||||
&overrides,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
// We don't want to format pyproject.toml
|
||||
@@ -64,18 +64,11 @@ fn find_pyproject_config(
|
||||
FilePattern::Builtin("*.pyi"),
|
||||
])
|
||||
.unwrap();
|
||||
Ok(pyproject_config)
|
||||
}
|
||||
|
||||
/// Find files that ruff would check so we can format them. Adapted from `ruff_cli`.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn ruff_check_paths<'a>(
|
||||
pyproject_config: &'a PyprojectConfig,
|
||||
cli: &FormatArguments,
|
||||
overrides: &CliOverrides,
|
||||
) -> anyhow::Result<(Vec<Result<ResolvedFile, ignore::Error>>, Resolver<'a>)> {
|
||||
let (paths, resolver) = python_files_in_path(&cli.files, pyproject_config, overrides)?;
|
||||
Ok((paths, resolver))
|
||||
let (paths, resolver) = python_files_in_path(&cli.files, &pyproject_config, &overrides)?;
|
||||
if paths.is_empty() {
|
||||
bail!("no python files in {:?}", dirs)
|
||||
}
|
||||
Ok((paths, resolver, pyproject_config))
|
||||
}
|
||||
|
||||
/// Collects statistics over the formatted files to compute the Jaccard index or the similarity
|
||||
@@ -223,7 +216,6 @@ pub(crate) struct Args {
|
||||
#[arg(long)]
|
||||
pub(crate) files_with_errors: Option<u32>,
|
||||
#[clap(flatten)]
|
||||
#[allow(clippy::struct_field_names)]
|
||||
pub(crate) log_level_args: LogLevelArgs,
|
||||
}
|
||||
|
||||
@@ -459,17 +451,11 @@ fn format_dev_project(
|
||||
files[0].display()
|
||||
);
|
||||
|
||||
// TODO(konstin): Respect black's excludes.
|
||||
// TODO(konstin): black excludes
|
||||
|
||||
// Find files to check (or in this case, format twice). Adapted from ruff_cli
|
||||
// First argument is ignored
|
||||
let (cli, overrides) = parse_cli(files)?;
|
||||
let pyproject_config = find_pyproject_config(&cli, &overrides)?;
|
||||
let (paths, resolver) = ruff_check_paths(&pyproject_config, &cli, &overrides)?;
|
||||
|
||||
if paths.is_empty() {
|
||||
bail!("No Python files found under the given path(s)");
|
||||
}
|
||||
let (paths, resolver, pyproject_config) = ruff_check_paths(files)?;
|
||||
|
||||
let results = {
|
||||
let pb_span =
|
||||
@@ -482,7 +468,14 @@ fn format_dev_project(
|
||||
#[cfg(feature = "singlethreaded")]
|
||||
let iter = { paths.into_iter() };
|
||||
iter.map(|path| {
|
||||
let result = format_dir_entry(path, stability_check, write, &black_options, &resolver);
|
||||
let result = format_dir_entry(
|
||||
path,
|
||||
stability_check,
|
||||
write,
|
||||
&black_options,
|
||||
&resolver,
|
||||
&pyproject_config,
|
||||
);
|
||||
pb_span.pb_inc(1);
|
||||
result
|
||||
})
|
||||
@@ -532,13 +525,14 @@ fn format_dev_project(
|
||||
})
|
||||
}
|
||||
|
||||
/// Error handling in between walkdir and `format_dev_file`.
|
||||
/// Error handling in between walkdir and `format_dev_file`
|
||||
fn format_dir_entry(
|
||||
resolved_file: Result<ResolvedFile, ignore::Error>,
|
||||
stability_check: bool,
|
||||
write: bool,
|
||||
options: &BlackOptions,
|
||||
resolver: &Resolver,
|
||||
pyproject_config: &PyprojectConfig,
|
||||
) -> anyhow::Result<(Result<Statistics, CheckFileError>, PathBuf), Error> {
|
||||
let resolved_file = resolved_file.context("Iterating the files in the repository failed")?;
|
||||
// For some reason it does not filter in the beginning
|
||||
@@ -549,7 +543,7 @@ fn format_dir_entry(
|
||||
let path = resolved_file.into_path();
|
||||
let mut options = options.to_py_format_options(&path);
|
||||
|
||||
let settings = resolver.resolve(&path);
|
||||
let settings = resolver.resolve(&path, pyproject_config);
|
||||
// That's a bad way of doing this but it's not worth doing something better for format_dev
|
||||
if settings.formatter.line_width != LineWidth::default() {
|
||||
options = options.with_line_width(settings.formatter.line_width);
|
||||
|
||||
@@ -1472,11 +1472,6 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
||||
}
|
||||
|
||||
fn fits_text(&mut self, text: Text, args: PrintElementArgs) -> Fits {
|
||||
fn exceeds_width(fits: &FitsMeasurer, args: PrintElementArgs) -> bool {
|
||||
fits.state.line_width > fits.options().line_width.into()
|
||||
&& !args.measure_mode().allows_text_overflow()
|
||||
}
|
||||
|
||||
let indent = std::mem::take(&mut self.state.pending_indent);
|
||||
self.state.line_width +=
|
||||
u32::from(indent.level()) * self.options().indent_width() + u32::from(indent.align());
|
||||
@@ -1498,13 +1493,7 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
||||
return Fits::No;
|
||||
}
|
||||
match args.measure_mode() {
|
||||
MeasureMode::FirstLine => {
|
||||
return if exceeds_width(self, args) {
|
||||
Fits::No
|
||||
} else {
|
||||
Fits::Yes
|
||||
};
|
||||
}
|
||||
MeasureMode::FirstLine => return Fits::Yes,
|
||||
MeasureMode::AllLines
|
||||
| MeasureMode::AllLinesAllowTextOverflow => {
|
||||
self.state.line_width = 0;
|
||||
@@ -1522,7 +1511,9 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
||||
}
|
||||
}
|
||||
|
||||
if exceeds_width(self, args) {
|
||||
if self.state.line_width > self.options().line_width.into()
|
||||
&& !args.measure_mode().allows_text_overflow()
|
||||
{
|
||||
return Fits::No;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,8 +71,6 @@ foo.is_(True)
|
||||
bar.is_not(False)
|
||||
next(iter([]), False)
|
||||
sa.func.coalesce(tbl.c.valid, False)
|
||||
setVisible(True)
|
||||
set_visible(True)
|
||||
|
||||
|
||||
class Registry:
|
||||
@@ -116,6 +114,3 @@ from typing import override
|
||||
@override
|
||||
def func(x: bool):
|
||||
pass
|
||||
|
||||
|
||||
settings(True)
|
||||
|
||||
@@ -8,14 +8,46 @@ class MyClass:
|
||||
self.id = 10
|
||||
self.dir = "."
|
||||
|
||||
def int(self):
|
||||
pass
|
||||
|
||||
def str(self):
|
||||
pass
|
||||
|
||||
def method_usage(self) -> str:
|
||||
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class MyClass(TypedDict):
|
||||
id: int
|
||||
|
||||
|
||||
from threading import Event
|
||||
|
||||
|
||||
class CustomEvent(Event):
|
||||
def set(self) -> None:
|
||||
...
|
||||
|
||||
def str(self) -> None:
|
||||
...
|
||||
|
||||
|
||||
from logging import Filter, LogRecord
|
||||
|
||||
|
||||
class CustomFilter(Filter):
|
||||
def filter(self, record: LogRecord) -> bool:
|
||||
...
|
||||
|
||||
def str(self) -> None:
|
||||
...
|
||||
|
||||
|
||||
from typing_extensions import override
|
||||
|
||||
|
||||
class MyClass:
|
||||
@override
|
||||
def str(self):
|
||||
pass
|
||||
|
||||
def attribute_usage(self) -> id:
|
||||
def int(self):
|
||||
pass
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import warnings
|
||||
import typing_extensions
|
||||
from collections.abc import Callable
|
||||
from warnings import deprecated
|
||||
|
||||
|
||||
def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None:
|
||||
...
|
||||
|
||||
@@ -51,24 +45,3 @@ class Demo:
|
||||
|
||||
def func() -> None:
|
||||
"""Docstrings are excluded from this rule. Some padding."""
|
||||
|
||||
|
||||
@warnings.deprecated("Veeeeeeeeeeeeeeeeeeeeeeery long deprecation message, but that's okay")
|
||||
def deprecated_function() -> None: ...
|
||||
|
||||
|
||||
@typing_extensions.deprecated("Another loooooooooooooooooooooong deprecation message, it's still okay")
|
||||
def another_deprecated_function() -> None: ...
|
||||
|
||||
|
||||
@deprecated("A third loooooooooooooooooooooooooooooong deprecation message")
|
||||
def a_third_deprecated_function() -> None: ...
|
||||
|
||||
|
||||
def not_warnings_dot_deprecated(
|
||||
msg: str
|
||||
) -> Callable[[Callable[[], None]], Callable[[], None]]: ...
|
||||
|
||||
|
||||
@not_warnings_dot_deprecated("Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!")
|
||||
def not_a_deprecated_function() -> None: ...
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import warnings
|
||||
import typing_extensions
|
||||
from typing_extensions import deprecated
|
||||
|
||||
def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
|
||||
def f2(
|
||||
x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
|
||||
@@ -42,25 +38,3 @@ class Demo:
|
||||
|
||||
def func() -> None:
|
||||
"""Docstrings are excluded from this rule. Some padding.""" # OK
|
||||
|
||||
@warnings.deprecated(
|
||||
"Veeeeeeeeeeeeeeeeeeeeeeery long deprecation message, but that's okay" # OK
|
||||
)
|
||||
def deprecated_function() -> None: ...
|
||||
|
||||
@typing_extensions.deprecated(
|
||||
"Another loooooooooooooooooooooong deprecation message, it's still okay" # OK
|
||||
)
|
||||
def another_deprecated_function() -> None: ...
|
||||
|
||||
@deprecated("A third loooooooooooooooooooooooooooooong deprecation message") # OK
|
||||
def a_third_deprecated_function() -> None: ...
|
||||
|
||||
def not_warnings_dot_deprecated(
|
||||
msg: str
|
||||
) -> Callable[[Callable[[], None]], Callable[[], None]]: ...
|
||||
|
||||
@not_warnings_dot_deprecated(
|
||||
"Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
|
||||
)
|
||||
def not_a_deprecated_function() -> None: ...
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
def foo(d: dict[str, str]) -> None:
|
||||
for k, v in zip(d.keys(), d.values()): # SIM911
|
||||
...
|
||||
|
||||
for k, v in zip(d.keys(), d.values(), strict=True): # SIM911
|
||||
...
|
||||
|
||||
for k, v in zip(d.keys(), d.values(), struct=True): # OK
|
||||
...
|
||||
|
||||
|
||||
d1 = d2 = {}
|
||||
|
||||
for k, v in zip(d1.keys(), d2.values()): # OK
|
||||
...
|
||||
|
||||
for k, v in zip(d1.items(), d2.values()): # OK
|
||||
...
|
||||
|
||||
for k, v in zip(d2.keys(), d2.values()): # SIM911
|
||||
...
|
||||
|
||||
items = zip(x.keys(), x.values()) # OK
|
||||
@@ -150,21 +150,3 @@ class Test:
|
||||
Args:
|
||||
arg1: some description of arg
|
||||
"""
|
||||
|
||||
|
||||
def select_data(
|
||||
query: str,
|
||||
args: tuple,
|
||||
database: str,
|
||||
auto_save: bool,
|
||||
) -> None:
|
||||
"""This function has an argument `args`, which shouldn't be mistaken for a section.
|
||||
|
||||
Args:
|
||||
query:
|
||||
Query template.
|
||||
args:
|
||||
A list of arguments.
|
||||
database:
|
||||
Which database to connect to ("origin" or "destination").
|
||||
"""
|
||||
|
||||
@@ -536,29 +536,9 @@ def non_empty_blank_line_before_section(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
The function's description.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
A value of some sort.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def lowercase_sub_section_header():
|
||||
"""Below, `returns:` should _not_ be considered a section header.
|
||||
|
||||
Args:
|
||||
Here's a note.
|
||||
|
||||
returns:
|
||||
"""
|
||||
|
||||
|
||||
def titlecase_sub_section_header():
|
||||
"""Below, `Returns:` should be considered a section header.
|
||||
|
||||
Args:
|
||||
Here's a note.
|
||||
|
||||
Returns:
|
||||
"""
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import os
|
||||
|
||||
x = 1
|
||||
|
||||
if x > 0:
|
||||
import os
|
||||
@@ -91,12 +91,3 @@ from typing_extensions import dataclass_transform
|
||||
|
||||
# UP035
|
||||
from backports.strenum import StrEnum
|
||||
|
||||
# UP035
|
||||
from typing_extensions import override
|
||||
|
||||
# UP035
|
||||
from typing_extensions import Buffer
|
||||
|
||||
# UP035
|
||||
from typing_extensions import get_original_bases
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import typing
|
||||
from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
from typing import Annotated, Any, Literal, Optional, Tuple, Union
|
||||
|
||||
|
||||
def f(arg: int):
|
||||
@@ -257,13 +257,3 @@ from custom_typing import MaybeInt
|
||||
|
||||
def f(arg: MaybeInt = None):
|
||||
pass
|
||||
|
||||
|
||||
# Hashable
|
||||
|
||||
def f(arg: Hashable = None): # OK
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Hashable | int = None): # OK
|
||||
pass
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
# See https://docs.python.org/3/reference/expressions.html#operator-precedence
|
||||
# for the official docs on operator precedence.
|
||||
#
|
||||
# Most importantly, `and` *always* takes precedence over `or`.
|
||||
#
|
||||
# `not` (the third boolean/logical operator) takes precedence over both,
|
||||
# but the rule there is easier to remember,
|
||||
# so we don't emit a diagnostic if a `not` expression is unparenthesized
|
||||
# as part of a chain.
|
||||
|
||||
a, b, c = 1, 0, 2
|
||||
x = a or b and c # RUF021: => `a or (b and c)`
|
||||
x = a or b and c # looooooooooooooooooooooooooooooong comment but it won't prevent an autofix
|
||||
|
||||
a, b, c = 0, 1, 2
|
||||
y = a and b or c # RUF021: => `(a and b) or c`
|
||||
|
||||
a, b, c, d = 1, 2, 0, 3
|
||||
if a or b or c and d: # RUF021: => `a or b or (c and d)`
|
||||
pass
|
||||
|
||||
a, b, c, d = 0, 0, 2, 3
|
||||
|
||||
if bool():
|
||||
pass
|
||||
elif a or b and c or d: # RUF021: => `a or (b and c) or d`
|
||||
pass
|
||||
|
||||
a, b, c, d = 0, 1, 0, 2
|
||||
while a and b or c and d: # RUF021: => `(and b) or (c and d)`
|
||||
pass
|
||||
|
||||
b, c, d, e = 2, 3, 0, 4
|
||||
# RUF021: => `a or b or c or (d and e)`:
|
||||
z = [a for a in range(5) if a or b or c or d and e]
|
||||
|
||||
a, b, c, d = 0, 1, 3, 0
|
||||
assert not a and b or c or d # RUF021: => `(not a and b) or c or d`
|
||||
|
||||
if (not a) and b or c or d: # RUF021: => `((not a) and b) or c or d`
|
||||
if (not a and b) or c or d: # OK
|
||||
pass
|
||||
|
||||
if (
|
||||
some_reasonably_long_condition
|
||||
or some_other_reasonably_long_condition
|
||||
and some_third_reasonably_long_condition
|
||||
or some_fourth_reasonably_long_condition
|
||||
and some_fifth_reasonably_long_condition
|
||||
# a commment
|
||||
and some_sixth_reasonably_long_condition
|
||||
and some_seventh_reasonably_long_condition
|
||||
# another comment
|
||||
or some_eighth_reasonably_long_condition
|
||||
):
|
||||
pass
|
||||
|
||||
#############################################
|
||||
# If they're all the same operator, it's fine
|
||||
#############################################
|
||||
|
||||
x = not a and c # OK
|
||||
|
||||
if a or b or c: # OK
|
||||
pass
|
||||
|
||||
while a and b and c: # OK
|
||||
pass
|
||||
|
||||
###########################################################
|
||||
# We don't consider `not` as part of a chain as problematic
|
||||
###########################################################
|
||||
|
||||
x = not a or not b or not c # OK
|
||||
|
||||
#####################################
|
||||
# If they're parenthesized, it's fine
|
||||
#####################################
|
||||
|
||||
a, b, c = 1, 0, 2
|
||||
x = a or (b and c) # OK
|
||||
x2 = (a or b) and c # OK
|
||||
x3 = (a or b) or c # OK
|
||||
x4 = (a and b) and c # OK
|
||||
|
||||
a, b, c = 0, 1, 2
|
||||
y = (a and b) or c # OK
|
||||
yy = a and (b or c) # OK
|
||||
|
||||
a, b, c, d = 1, 2, 0, 3
|
||||
if a or b or (c and d): # OK
|
||||
pass
|
||||
|
||||
a, b, c, d = 0, 0, 2, 3
|
||||
|
||||
if bool():
|
||||
pass
|
||||
elif a or (b and c) or d: # OK
|
||||
pass
|
||||
|
||||
a, b, c, d = 0, 1, 0, 2
|
||||
while (a and b) or (c and d): # OK
|
||||
pass
|
||||
|
||||
b, c, d, e = 2, 3, 0, 4
|
||||
z = [a for a in range(5) if a or b or c or (d and e)] # OK
|
||||
|
||||
a, b = 1, 2
|
||||
if (not a) or b: # OK
|
||||
if (not a) and b: # OK
|
||||
pass
|
||||
|
||||
a, b, c, d = 0, 1, 3, 0
|
||||
assert ((not a) and b) or c or d # OK
|
||||
@@ -1,14 +1,12 @@
|
||||
use ruff_diagnostics::{Diagnostic, Fix};
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
use ruff_python_semantic::{Binding, BindingKind, Imported, ScopeKind};
|
||||
use ruff_python_semantic::{Binding, BindingKind, ScopeKind};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
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, pyflakes, pylint,
|
||||
ruff,
|
||||
flake8_pyi, flake8_type_checking, flake8_unused_arguments, pyflakes, pylint, ruff,
|
||||
};
|
||||
|
||||
/// Run lint rules over all deferred scopes in the [`SemanticModel`].
|
||||
@@ -28,7 +26,6 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
Rule::UndefinedLocal,
|
||||
Rule::UnusedAnnotation,
|
||||
Rule::UnusedClassMethodArgument,
|
||||
Rule::BuiltinAttributeShadowing,
|
||||
Rule::UnusedFunctionArgument,
|
||||
Rule::UnusedImport,
|
||||
Rule::UnusedLambdaArgument,
|
||||
@@ -147,9 +144,9 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
|
||||
// If the bindings are in different forks, abort.
|
||||
if shadowed.source.map_or(true, |left| {
|
||||
binding
|
||||
.source
|
||||
.map_or(true, |right| !checker.semantic.same_branch(left, right))
|
||||
binding.source.map_or(true, |right| {
|
||||
checker.semantic.different_branches(left, right)
|
||||
})
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
@@ -236,9 +233,9 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
|
||||
// If the bindings are in different forks, abort.
|
||||
if shadowed.source.map_or(true, |left| {
|
||||
binding
|
||||
.source
|
||||
.map_or(true, |right| !checker.semantic.same_branch(left, right))
|
||||
binding.source.map_or(true, |right| {
|
||||
checker.semantic.different_branches(left, right)
|
||||
})
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
@@ -250,33 +247,9 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
},
|
||||
binding.range(),
|
||||
);
|
||||
|
||||
if let Some(range) = binding.parent_range(&checker.semantic) {
|
||||
diagnostic.set_parent(range.start());
|
||||
}
|
||||
|
||||
if checker.settings.preview.is_enabled() {
|
||||
if let Some(import) = binding.as_any_import() {
|
||||
if let Some(source) = binding.source {
|
||||
diagnostic.try_set_fix(|| {
|
||||
let statement = checker.semantic().statement(source);
|
||||
let parent = checker.semantic().parent_statement(source);
|
||||
let edit = fix::edits::remove_unused_imports(
|
||||
std::iter::once(import.member_name().as_ref()),
|
||||
statement,
|
||||
parent,
|
||||
checker.locator(),
|
||||
checker.stylist(),
|
||||
checker.indexer(),
|
||||
)?;
|
||||
Ok(Fix::safe_edit(edit).isolate(Checker::isolation(
|
||||
checker.semantic().parent_statement_id(source),
|
||||
)))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -299,18 +272,6 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
ruff::rules::asyncio_dangling_binding(scope, &checker.semantic, &mut diagnostics);
|
||||
}
|
||||
|
||||
if let Some(class_def) = scope.kind.as_class() {
|
||||
if checker.enabled(Rule::BuiltinAttributeShadowing) {
|
||||
flake8_builtins::rules::builtin_attribute_shadowing(
|
||||
checker,
|
||||
scope_id,
|
||||
scope,
|
||||
class_def,
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Lambda(_)) {
|
||||
if checker.enabled(Rule::UnusedVariable) {
|
||||
pyflakes::rules::unused_variable(checker, scope, &mut diagnostics);
|
||||
|
||||
@@ -242,7 +242,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if !checker.semantic.current_scope().kind.is_class() {
|
||||
if let ScopeKind::Class(class_def) = checker.semantic.current_scope().kind {
|
||||
if checker.enabled(Rule::BuiltinAttributeShadowing) {
|
||||
flake8_builtins::rules::builtin_attribute_shadowing(
|
||||
checker, class_def, id, *range,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if checker.enabled(Rule::BuiltinVariableShadowing) {
|
||||
flake8_builtins::rules::builtin_variable_shadowing(checker, id, *range);
|
||||
}
|
||||
@@ -718,7 +724,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::BooleanPositionalValueInCall) {
|
||||
flake8_boolean_trap::rules::boolean_positional_value_in_call(checker, call);
|
||||
flake8_boolean_trap::rules::boolean_positional_value_in_call(checker, args, func);
|
||||
}
|
||||
if checker.enabled(Rule::Debugger) {
|
||||
flake8_debugger::rules::debugger_call(checker, expr, func);
|
||||
@@ -863,9 +869,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::DictGetWithNoneDefault) {
|
||||
flake8_simplify::rules::dict_get_with_none_default(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::ZipDictKeysAndValues) {
|
||||
flake8_simplify::rules::zip_dict_keys_and_values(checker, call);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::OsPathAbspath,
|
||||
Rule::OsChmod,
|
||||
@@ -1489,9 +1492,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryKeyCheck) {
|
||||
ruff::rules::unnecessary_key_check(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::ParenthesizeChainedOperators) {
|
||||
ruff::rules::parenthesize_chained_logical_operators(checker, bool_op);
|
||||
}
|
||||
}
|
||||
Expr::NamedExpr(..) => {
|
||||
if checker.enabled(Rule::AssignmentInAssert) {
|
||||
|
||||
@@ -347,7 +347,17 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::FStringDocstring) {
|
||||
flake8_bugbear::rules::f_string_docstring(checker, body);
|
||||
}
|
||||
if !checker.semantic.current_scope().kind.is_class() {
|
||||
if let ScopeKind::Class(class_def) = checker.semantic.current_scope().kind {
|
||||
if checker.enabled(Rule::BuiltinAttributeShadowing) {
|
||||
flake8_builtins::rules::builtin_method_shadowing(
|
||||
checker,
|
||||
class_def,
|
||||
name,
|
||||
decorator_list,
|
||||
name.range(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if checker.enabled(Rule::BuiltinVariableShadowing) {
|
||||
flake8_builtins::rules::builtin_variable_shadowing(checker, name, name.range());
|
||||
}
|
||||
|
||||
@@ -472,7 +472,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Simplify, "300") => (RuleGroup::Stable, rules::flake8_simplify::rules::YodaConditions),
|
||||
(Flake8Simplify, "401") => (RuleGroup::Stable, rules::flake8_simplify::rules::IfElseBlockInsteadOfDictGet),
|
||||
(Flake8Simplify, "910") => (RuleGroup::Stable, rules::flake8_simplify::rules::DictGetWithNoneDefault),
|
||||
(Flake8Simplify, "911") => (RuleGroup::Preview, rules::flake8_simplify::rules::ZipDictKeysAndValues),
|
||||
|
||||
// flake8-copyright
|
||||
#[allow(deprecated)]
|
||||
@@ -924,7 +923,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "018") => (RuleGroup::Preview, rules::ruff::rules::AssignmentInAssert),
|
||||
(Ruff, "019") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryKeyCheck),
|
||||
(Ruff, "020") => (RuleGroup::Preview, rules::ruff::rules::NeverUnion),
|
||||
(Ruff, "021") => (RuleGroup::Preview, rules::ruff::rules::ParenthesizeChainedOperators),
|
||||
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "200") => (RuleGroup::Stable, rules::ruff::rules::InvalidPyprojectToml),
|
||||
|
||||
|
||||
@@ -153,17 +153,13 @@ impl<'a> SectionContexts<'a> {
|
||||
while let Some(line) = lines.next() {
|
||||
if let Some(section_kind) = suspected_as_section(&line, style) {
|
||||
let indent = leading_space(&line);
|
||||
let indent_size = indent.text_len();
|
||||
|
||||
let section_name = leading_words(&line);
|
||||
let section_name_size = section_name.text_len();
|
||||
|
||||
let section_name_range = TextRange::at(indent.text_len(), section_name.text_len());
|
||||
|
||||
if is_docstring_section(
|
||||
&line,
|
||||
indent_size,
|
||||
section_name_size,
|
||||
section_kind,
|
||||
last.as_ref(),
|
||||
section_name_range,
|
||||
previous_line.as_ref(),
|
||||
lines.peek(),
|
||||
) {
|
||||
@@ -174,8 +170,7 @@ impl<'a> SectionContexts<'a> {
|
||||
|
||||
last = Some(SectionContextData {
|
||||
kind: section_kind,
|
||||
indent_size: indent.text_len(),
|
||||
name_range: TextRange::at(line.start() + indent_size, section_name_size),
|
||||
name_range: section_name_range + line.start(),
|
||||
range: TextRange::empty(line.start()),
|
||||
summary_full_end: line.full_end(),
|
||||
});
|
||||
@@ -209,8 +204,8 @@ impl<'a> SectionContexts<'a> {
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a SectionContexts<'a> {
|
||||
type Item = SectionContext<'a>;
|
||||
type IntoIter = SectionContextsIter<'a>;
|
||||
type Item = SectionContext<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
@@ -262,9 +257,6 @@ impl ExactSizeIterator for SectionContextsIter<'_> {}
|
||||
struct SectionContextData {
|
||||
kind: SectionKind,
|
||||
|
||||
/// The size of the indentation of the section name.
|
||||
indent_size: TextSize,
|
||||
|
||||
/// Range of the section name, relative to the [`Docstring::body`]
|
||||
name_range: TextRange,
|
||||
|
||||
@@ -409,15 +401,12 @@ fn suspected_as_section(line: &str, style: SectionStyle) -> Option<SectionKind>
|
||||
/// Check if the suspected context is really a section header.
|
||||
fn is_docstring_section(
|
||||
line: &Line,
|
||||
indent_size: TextSize,
|
||||
section_name_size: TextSize,
|
||||
section_kind: SectionKind,
|
||||
previous_section: Option<&SectionContextData>,
|
||||
section_name_range: TextRange,
|
||||
previous_line: Option<&Line>,
|
||||
next_line: Option<&Line>,
|
||||
) -> bool {
|
||||
// Determine whether the current line looks like a section header, e.g., "Args:".
|
||||
let section_name_suffix = line[usize::from(indent_size + section_name_size)..].trim();
|
||||
let section_name_suffix = line[usize::from(section_name_range.end())..].trim();
|
||||
let this_looks_like_a_section_name =
|
||||
section_name_suffix == ":" || section_name_suffix.is_empty();
|
||||
if !this_looks_like_a_section_name {
|
||||
@@ -450,25 +439,5 @@ fn is_docstring_section(
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine if this is a sub-section within another section, like `args` in:
|
||||
// ```python
|
||||
// def func(args: tuple[int]):
|
||||
// """Toggle the gizmo.
|
||||
//
|
||||
// Args:
|
||||
// args: The arguments to the function.
|
||||
// """
|
||||
// ```
|
||||
// However, if the header is an _exact_ match (like `Returns:`, as opposed to `returns:`), then
|
||||
// continue to treat it as a section header.
|
||||
if let Some(previous_section) = previous_section {
|
||||
if previous_section.indent_size < indent_size {
|
||||
let verbatim = &line[TextRange::at(indent_size, section_name_size)];
|
||||
if section_kind.as_str() != verbatim {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
@@ -51,29 +51,13 @@ pub(super) fn is_allowed_func_def(name: &str) -> bool {
|
||||
/// Returns `true` if an argument is allowed to use a boolean trap. To return
|
||||
/// `true`, the function name must be explicitly allowed, and the argument must
|
||||
/// be either the first or second argument in the call.
|
||||
pub(super) fn allow_boolean_trap(call: &ast::ExprCall) -> bool {
|
||||
let func_name = match call.func.as_ref() {
|
||||
Expr::Attribute(ast::ExprAttribute { attr, .. }) => attr.as_str(),
|
||||
Expr::Name(ast::ExprName { id, .. }) => id.as_str(),
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
// If the function name is explicitly allowed, then the boolean trap is
|
||||
// allowed.
|
||||
if is_allowed_func_call(func_name) {
|
||||
return true;
|
||||
pub(super) fn allow_boolean_trap(func: &Expr) -> bool {
|
||||
if let Expr::Attribute(ast::ExprAttribute { attr, .. }) = func {
|
||||
return is_allowed_func_call(attr);
|
||||
}
|
||||
|
||||
// If the function appears to be a setter (e.g., `set_visible` or `setVisible`), then the
|
||||
// boolean trap is allowed. We want to avoid raising a violation for cases in which the argument
|
||||
// is positional-only and third-party, and this tends to be the case for setters.
|
||||
if call.arguments.args.len() == 1 {
|
||||
if func_name
|
||||
.strip_prefix("set")
|
||||
.is_some_and(|suffix| suffix.starts_with(|c: char| c == '_' || c.is_ascii_uppercase()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
||||
return is_allowed_func_call(id);
|
||||
}
|
||||
|
||||
false
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -45,16 +45,11 @@ impl Violation for BooleanPositionalValueInCall {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn boolean_positional_value_in_call(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if allow_boolean_trap(call) {
|
||||
pub(crate) fn boolean_positional_value_in_call(checker: &mut Checker, args: &[Expr], func: &Expr) {
|
||||
if allow_boolean_trap(func) {
|
||||
return;
|
||||
}
|
||||
for arg in call
|
||||
.arguments
|
||||
.args
|
||||
.iter()
|
||||
.filter(|arg| arg.is_boolean_literal_expr())
|
||||
{
|
||||
for arg in args.iter().filter(|arg| arg.is_boolean_literal_expr()) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(BooleanPositionalValueInCall, arg.range()));
|
||||
|
||||
@@ -81,12 +81,12 @@ FBT.py:19:5: FBT001 Boolean-typed positional argument in function definition
|
||||
21 | kwonly_nonvalued_nohint,
|
||||
|
|
||||
|
||||
FBT.py:91:19: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:89:19: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
90 | # FBT001: Boolean positional arg in function definition
|
||||
91 | def foo(self, value: bool) -> None:
|
||||
88 | # FBT001: Boolean positional arg in function definition
|
||||
89 | def foo(self, value: bool) -> None:
|
||||
| ^^^^^ FBT001
|
||||
92 | pass
|
||||
90 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -28,10 +28,14 @@ FBT.py:57:17: FBT003 Boolean positional value in function call
|
||||
59 | mylist.index(True)
|
||||
|
|
||||
|
||||
FBT.py:121:10: FBT003 Boolean positional value in function call
|
||||
|
|
||||
121 | settings(True)
|
||||
| ^^^^ FBT003
|
||||
|
|
||||
FBT.py:69:38: FBT003 Boolean positional value in function call
|
||||
|
|
||||
67 | os.set_blocking(0, False)
|
||||
68 | g_action.set_enabled(True)
|
||||
69 | settings.set_enable_developer_extras(True)
|
||||
| ^^^^ FBT003
|
||||
70 | foo.is_(True)
|
||||
71 | bar.is_not(False)
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -81,26 +81,26 @@ FBT.py:19:5: FBT001 Boolean-typed positional argument in function definition
|
||||
21 | kwonly_nonvalued_nohint,
|
||||
|
|
||||
|
||||
FBT.py:91:19: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:89:19: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
90 | # FBT001: Boolean positional arg in function definition
|
||||
91 | def foo(self, value: bool) -> None:
|
||||
88 | # FBT001: Boolean positional arg in function definition
|
||||
89 | def foo(self, value: bool) -> None:
|
||||
| ^^^^^ FBT001
|
||||
92 | pass
|
||||
90 | pass
|
||||
|
|
||||
|
||||
FBT.py:101:10: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:99:10: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
101 | def func(x: Union[list, Optional[int | str | float | bool]]):
|
||||
99 | def func(x: Union[list, Optional[int | str | float | bool]]):
|
||||
| ^ FBT001
|
||||
102 | pass
|
||||
100 | pass
|
||||
|
|
||||
|
||||
FBT.py:105:10: FBT001 Boolean-typed positional argument in function definition
|
||||
FBT.py:103:10: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
105 | def func(x: bool | str):
|
||||
103 | def func(x: bool | str):
|
||||
| ^ FBT001
|
||||
106 | pass
|
||||
104 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Arguments, Decorator};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_semantic::{BindingKind, Scope, ScopeId};
|
||||
use ruff_source_file::SourceRow;
|
||||
use ruff_text_size::Ranged;
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_builtins::helpers::shadows_builtin;
|
||||
@@ -19,23 +20,6 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin;
|
||||
/// non-obvious errors, as readers may mistake the attribute for the
|
||||
/// builtin and vice versa.
|
||||
///
|
||||
/// Since methods and class attributes typically cannot be referenced directly
|
||||
/// from outside the class scope, this rule only applies to those methods
|
||||
/// and attributes that both shadow a builtin _and_ are referenced from within
|
||||
/// the class scope, as in the following example, where the `list[int]` return
|
||||
/// type annotation resolves to the `list` method, rather than the builtin:
|
||||
///
|
||||
/// ```python
|
||||
/// class Class:
|
||||
/// @staticmethod
|
||||
/// def list() -> None:
|
||||
/// pass
|
||||
///
|
||||
/// @staticmethod
|
||||
/// def repeat(value: int, times: int) -> list[int]:
|
||||
/// return [value] * times
|
||||
/// ```
|
||||
///
|
||||
/// Builtins can be marked as exceptions to this rule via the
|
||||
/// [`flake8-builtins.builtins-ignorelist`] configuration option, or
|
||||
/// converted to the appropriate dunder method. Methods decorated with
|
||||
@@ -44,112 +28,135 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Class:
|
||||
/// @staticmethod
|
||||
/// def list() -> None:
|
||||
/// pass
|
||||
/// class Shadow:
|
||||
/// def int():
|
||||
/// return 0
|
||||
/// ```
|
||||
///
|
||||
/// @staticmethod
|
||||
/// def repeat(value: int, times: int) -> list[int]:
|
||||
/// return [value] * times
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class Shadow:
|
||||
/// def to_int():
|
||||
/// return 0
|
||||
/// ```
|
||||
///
|
||||
/// Or:
|
||||
/// ```python
|
||||
/// class Shadow:
|
||||
/// # Callable as `int(shadow)`
|
||||
/// def __int__():
|
||||
/// return 0
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `flake8-builtins.builtins-ignorelist`
|
||||
///
|
||||
/// ## References
|
||||
/// - [_Is it bad practice to use a built-in function name as an attribute or method identifier?_](https://stackoverflow.com/questions/9109333/is-it-bad-practice-to-use-a-built-in-function-name-as-an-attribute-or-method-ide)
|
||||
/// - [_Why is it a bad idea to name a variable `id` in Python?_](https://stackoverflow.com/questions/77552/id-is-a-bad-variable-name-in-python)
|
||||
#[violation]
|
||||
pub struct BuiltinAttributeShadowing {
|
||||
kind: Kind,
|
||||
name: String,
|
||||
row: SourceRow,
|
||||
}
|
||||
|
||||
impl Violation for BuiltinAttributeShadowing {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let BuiltinAttributeShadowing { kind, name, row } = self;
|
||||
match kind {
|
||||
Kind::Attribute => {
|
||||
format!("Python builtin is shadowed by class attribute `{name}` from {row}")
|
||||
}
|
||||
Kind::Method => {
|
||||
format!("Python builtin is shadowed by method `{name}` from {row}")
|
||||
}
|
||||
}
|
||||
let BuiltinAttributeShadowing { name } = self;
|
||||
format!("Class attribute `{name}` is shadowing a Python builtin")
|
||||
}
|
||||
}
|
||||
|
||||
/// A003
|
||||
pub(crate) fn builtin_attribute_shadowing(
|
||||
checker: &Checker,
|
||||
scope_id: ScopeId,
|
||||
scope: &Scope,
|
||||
checker: &mut Checker,
|
||||
class_def: &ast::StmtClassDef,
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
name: &str,
|
||||
range: TextRange,
|
||||
) {
|
||||
for (name, binding_id) in scope.all_bindings() {
|
||||
let binding = checker.semantic().binding(binding_id);
|
||||
|
||||
// We only care about methods and attributes.
|
||||
let kind = match binding.kind {
|
||||
BindingKind::Assignment | BindingKind::Annotation => Kind::Attribute,
|
||||
BindingKind::FunctionDefinition(_) => Kind::Method,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if shadows_builtin(
|
||||
name,
|
||||
&checker.settings.flake8_builtins.builtins_ignorelist,
|
||||
checker.source_type,
|
||||
) {
|
||||
// Ignore explicit overrides.
|
||||
if class_def.decorator_list.iter().any(|decorator| {
|
||||
checker
|
||||
.semantic()
|
||||
.match_typing_expr(&decorator.expression, "override")
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Class scopes are special, in that you can only reference a binding defined in a
|
||||
// class scope from within the class scope itself. As such, we can safely ignore
|
||||
// methods that weren't referenced from within the class scope. In other words, we're
|
||||
// only trying to identify shadowing as in:
|
||||
// ```python
|
||||
// class Class:
|
||||
// @staticmethod
|
||||
// def list() -> None:
|
||||
// pass
|
||||
//
|
||||
// @staticmethod
|
||||
// def repeat(value: int, times: int) -> list[int]:
|
||||
// return [value] * times
|
||||
// ```
|
||||
for reference in binding
|
||||
.references
|
||||
.iter()
|
||||
.map(|reference_id| checker.semantic().reference(*reference_id))
|
||||
.filter(|reference| {
|
||||
checker
|
||||
.semantic()
|
||||
.first_non_type_parent_scope_id(reference.scope_id())
|
||||
== Some(scope_id)
|
||||
})
|
||||
{
|
||||
diagnostics.push(Diagnostic::new(
|
||||
BuiltinAttributeShadowing {
|
||||
kind,
|
||||
name: name.to_string(),
|
||||
row: checker.compute_source_row(binding.start()),
|
||||
},
|
||||
reference.range(),
|
||||
));
|
||||
}
|
||||
if shadows_builtin(
|
||||
name,
|
||||
&checker.settings.flake8_builtins.builtins_ignorelist,
|
||||
checker.source_type,
|
||||
) {
|
||||
// Ignore shadowing within `TypedDict` definitions, since these are only accessible through
|
||||
// subscripting and not through attribute access.
|
||||
if class_def
|
||||
.bases()
|
||||
.iter()
|
||||
.any(|base| checker.semantic().match_typing_expr(base, "TypedDict"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BuiltinAttributeShadowing {
|
||||
name: name.to_string(),
|
||||
},
|
||||
range,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum Kind {
|
||||
Attribute,
|
||||
Method,
|
||||
/// A003
|
||||
pub(crate) fn builtin_method_shadowing(
|
||||
checker: &mut Checker,
|
||||
class_def: &ast::StmtClassDef,
|
||||
name: &str,
|
||||
decorator_list: &[Decorator],
|
||||
range: TextRange,
|
||||
) {
|
||||
if shadows_builtin(
|
||||
name,
|
||||
&checker.settings.flake8_builtins.builtins_ignorelist,
|
||||
checker.source_type,
|
||||
) {
|
||||
// Ignore some standard-library methods. Ideally, we'd ignore all overridden methods, since
|
||||
// those should be flagged on the superclass, but that's more difficult.
|
||||
if is_standard_library_override(name, class_def, checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore explicit overrides.
|
||||
if decorator_list.iter().any(|decorator| {
|
||||
checker
|
||||
.semantic()
|
||||
.match_typing_expr(&decorator.expression, "override")
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BuiltinAttributeShadowing {
|
||||
name: name.to_string(),
|
||||
},
|
||||
range,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if an attribute appears to be an override of a standard-library method.
|
||||
fn is_standard_library_override(
|
||||
name: &str,
|
||||
class_def: &ast::StmtClassDef,
|
||||
semantic: &SemanticModel,
|
||||
) -> bool {
|
||||
let Some(Arguments { args: bases, .. }) = class_def.arguments.as_deref() else {
|
||||
return false;
|
||||
};
|
||||
match name {
|
||||
// Ex) `Event.set`
|
||||
"set" => bases.iter().any(|base| {
|
||||
semantic
|
||||
.resolve_call_path(base)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["threading", "Event"]))
|
||||
}),
|
||||
// Ex) `Filter.filter`
|
||||
"filter" => bases.iter().any(|base| {
|
||||
semantic
|
||||
.resolve_call_path(base)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "Filter"]))
|
||||
}),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,68 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
|
||||
---
|
||||
A003.py:17:31: A003 Python builtin is shadowed by method `str` from line 14
|
||||
A003.py:2:5: A003 Class attribute `ImportError` is shadowing a Python builtin
|
||||
|
|
||||
1 | class MyClass:
|
||||
2 | ImportError = 4
|
||||
| ^^^^^^^^^^^ A003
|
||||
3 | id: int
|
||||
4 | dir = "/"
|
||||
|
|
||||
|
||||
A003.py:3:5: A003 Class attribute `id` is shadowing a Python builtin
|
||||
|
|
||||
1 | class MyClass:
|
||||
2 | ImportError = 4
|
||||
3 | id: int
|
||||
| ^^ A003
|
||||
4 | dir = "/"
|
||||
|
|
||||
|
||||
A003.py:4:5: A003 Class attribute `dir` is shadowing a Python builtin
|
||||
|
|
||||
2 | ImportError = 4
|
||||
3 | id: int
|
||||
4 | dir = "/"
|
||||
| ^^^ A003
|
||||
5 |
|
||||
6 | def __init__(self):
|
||||
|
|
||||
|
||||
A003.py:11:9: A003 Class attribute `str` is shadowing a Python builtin
|
||||
|
|
||||
15 | pass
|
||||
16 |
|
||||
17 | def method_usage(self) -> str:
|
||||
| ^^^ A003
|
||||
18 | pass
|
||||
9 | self.dir = "."
|
||||
10 |
|
||||
11 | def str(self):
|
||||
| ^^^ A003
|
||||
12 | pass
|
||||
|
|
||||
|
||||
A003.py:20:34: A003 Python builtin is shadowed by class attribute `id` from line 3
|
||||
A003.py:29:9: A003 Class attribute `str` is shadowing a Python builtin
|
||||
|
|
||||
18 | pass
|
||||
19 |
|
||||
20 | def attribute_usage(self) -> id:
|
||||
| ^^ A003
|
||||
21 | pass
|
||||
27 | ...
|
||||
28 |
|
||||
29 | def str(self) -> None:
|
||||
| ^^^ A003
|
||||
30 | ...
|
||||
|
|
||||
|
||||
A003.py:40:9: A003 Class attribute `str` is shadowing a Python builtin
|
||||
|
|
||||
38 | ...
|
||||
39 |
|
||||
40 | def str(self) -> None:
|
||||
| ^^^ A003
|
||||
41 | ...
|
||||
|
|
||||
|
||||
A003.py:52:9: A003 Class attribute `int` is shadowing a Python builtin
|
||||
|
|
||||
50 | pass
|
||||
51 |
|
||||
52 | def int(self):
|
||||
| ^^^ A003
|
||||
53 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,49 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
|
||||
---
|
||||
A003.py:17:31: A003 Python builtin is shadowed by method `str` from line 14
|
||||
A003.py:2:5: A003 Class attribute `ImportError` is shadowing a Python builtin
|
||||
|
|
||||
1 | class MyClass:
|
||||
2 | ImportError = 4
|
||||
| ^^^^^^^^^^^ A003
|
||||
3 | id: int
|
||||
4 | dir = "/"
|
||||
|
|
||||
|
||||
A003.py:11:9: A003 Class attribute `str` is shadowing a Python builtin
|
||||
|
|
||||
15 | pass
|
||||
16 |
|
||||
17 | def method_usage(self) -> str:
|
||||
| ^^^ A003
|
||||
18 | pass
|
||||
9 | self.dir = "."
|
||||
10 |
|
||||
11 | def str(self):
|
||||
| ^^^ A003
|
||||
12 | pass
|
||||
|
|
||||
|
||||
A003.py:29:9: A003 Class attribute `str` is shadowing a Python builtin
|
||||
|
|
||||
27 | ...
|
||||
28 |
|
||||
29 | def str(self) -> None:
|
||||
| ^^^ A003
|
||||
30 | ...
|
||||
|
|
||||
|
||||
A003.py:40:9: A003 Class attribute `str` is shadowing a Python builtin
|
||||
|
|
||||
38 | ...
|
||||
39 |
|
||||
40 | def str(self) -> None:
|
||||
| ^^^ A003
|
||||
41 | ...
|
||||
|
|
||||
|
||||
A003.py:52:9: A003 Class attribute `int` is shadowing a Python builtin
|
||||
|
|
||||
50 | pass
|
||||
51 |
|
||||
52 | def int(self):
|
||||
| ^^^ A003
|
||||
53 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -367,7 +367,7 @@ pub(crate) fn fix_unnecessary_literal_dict(expr: &Expr, checker: &Checker) -> Re
|
||||
comma,
|
||||
} = element
|
||||
{
|
||||
if let Some(Element::Simple { value: key, .. }) = tuple.elements.first() {
|
||||
if let Some(Element::Simple { value: key, .. }) = tuple.elements.get(0) {
|
||||
if let Some(Element::Simple { value, .. }) = tuple.elements.get(1) {
|
||||
return Ok(DictElement::Simple {
|
||||
key: key.clone(),
|
||||
|
||||
@@ -134,7 +134,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
|
||||
format!("Indices should only contain `{attr_name}` calls")
|
||||
)
|
||||
};
|
||||
args.first()
|
||||
args.get(0)
|
||||
.unwrap_or_else(|| panic!("`{attr_name}` should have one argument"))
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -2,7 +2,6 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::is_docstring_stmt;
|
||||
use ruff_python_ast::{self as ast, StringLike};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -45,14 +44,8 @@ impl AlwaysFixableViolation for StringOrBytesTooLong {
|
||||
|
||||
/// PYI053
|
||||
pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, string: StringLike) {
|
||||
let semantic = checker.semantic();
|
||||
|
||||
// Ignore docstrings.
|
||||
if is_docstring_stmt(semantic.current_statement()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if is_warnings_dot_deprecated(semantic.current_expression_parent(), semantic) {
|
||||
if is_docstring_stmt(checker.semantic().current_statement()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -74,21 +67,3 @@ pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, string: StringLike
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
fn is_warnings_dot_deprecated(expr: Option<&ast::Expr>, semantic: &SemanticModel) -> bool {
|
||||
// Does `expr` represent a call to `warnings.deprecated` or `typing_extensions.deprecated`?
|
||||
let Some(expr) = expr else {
|
||||
return false;
|
||||
};
|
||||
let Some(call) = expr.as_call_expr() else {
|
||||
return false;
|
||||
};
|
||||
semantic
|
||||
.resolve_call_path(&call.func)
|
||||
.is_some_and(|call_path| {
|
||||
matches!(
|
||||
call_path.as_slice(),
|
||||
["warnings" | "typing_extensions", "deprecated"]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,148 +1,128 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI053.pyi:7:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||
PYI053.pyi:3:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||
|
|
||||
5 | def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
|
||||
6 | def f2(
|
||||
7 | x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
|
||||
1 | def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
|
||||
2 | def f2(
|
||||
3 | x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
|
||||
8 | ) -> None: ...
|
||||
9 | def f3(
|
||||
4 | ) -> None: ...
|
||||
5 | def f3(
|
||||
|
|
||||
= help: Replace with `...`
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 |
|
||||
5 5 | def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
|
||||
6 6 | def f2(
|
||||
7 |- x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
|
||||
7 |+ x: str = ..., # Error: PYI053
|
||||
8 8 | ) -> None: ...
|
||||
9 9 | def f3(
|
||||
10 10 | x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK
|
||||
1 1 | def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
|
||||
2 2 | def f2(
|
||||
3 |- x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
|
||||
3 |+ x: str = ..., # Error: PYI053
|
||||
4 4 | ) -> None: ...
|
||||
5 5 | def f3(
|
||||
6 6 | x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK
|
||||
|
||||
PYI053.pyi:13:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||
PYI053.pyi:9:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||
|
|
||||
11 | ) -> None: ...
|
||||
12 | def f4(
|
||||
13 | x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
|
||||
7 | ) -> None: ...
|
||||
8 | def f4(
|
||||
9 | x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
|
||||
14 | ) -> None: ...
|
||||
15 | def f5(
|
||||
10 | ) -> None: ...
|
||||
11 | def f5(
|
||||
|
|
||||
= help: Replace with `...`
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 | x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK
|
||||
11 11 | ) -> None: ...
|
||||
12 12 | def f4(
|
||||
13 |- x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
|
||||
13 |+ x: str = ..., # Error: PYI053
|
||||
14 14 | ) -> None: ...
|
||||
15 15 | def f5(
|
||||
16 16 | x: bytes = b"50 character byte stringgggggggggggggggggggggggggg", # OK
|
||||
6 6 | x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK
|
||||
7 7 | ) -> None: ...
|
||||
8 8 | def f4(
|
||||
9 |- x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
|
||||
9 |+ x: str = ..., # Error: PYI053
|
||||
10 10 | ) -> None: ...
|
||||
11 11 | def f5(
|
||||
12 12 | x: bytes = b"50 character byte stringgggggggggggggggggggggggggg", # OK
|
||||
|
||||
PYI053.pyi:25:16: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||
PYI053.pyi:21:16: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||
|
|
||||
23 | ) -> None: ...
|
||||
24 | def f8(
|
||||
25 | x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
|
||||
19 | ) -> None: ...
|
||||
20 | def f8(
|
||||
21 | x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
|
||||
26 | ) -> None: ...
|
||||
22 | ) -> None: ...
|
||||
|
|
||||
= help: Replace with `...`
|
||||
|
||||
ℹ Safe fix
|
||||
22 22 | x: bytes = b"50 character byte stringggggggggggggggggggggggggg\xff", # OK
|
||||
23 23 | ) -> None: ...
|
||||
24 24 | def f8(
|
||||
25 |- x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
|
||||
25 |+ x: bytes = ..., # Error: PYI053
|
||||
26 26 | ) -> None: ...
|
||||
27 27 |
|
||||
28 28 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
18 18 | x: bytes = b"50 character byte stringggggggggggggggggggggggggg\xff", # OK
|
||||
19 19 | ) -> None: ...
|
||||
20 20 | def f8(
|
||||
21 |- x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
|
||||
21 |+ x: bytes = ..., # Error: PYI053
|
||||
22 22 | ) -> None: ...
|
||||
23 23 |
|
||||
24 24 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
|
||||
PYI053.pyi:30:12: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||
PYI053.pyi:26:12: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||
|
|
||||
28 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
29 |
|
||||
30 | bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
|
||||
24 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
25 |
|
||||
26 | bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
|
||||
27 |
|
||||
28 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
|
||||
|
|
||||
= help: Replace with `...`
|
||||
|
||||
ℹ Safe fix
|
||||
23 23 |
|
||||
24 24 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
25 25 |
|
||||
26 |-bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
|
||||
26 |+bar: str = ... # Error: PYI053
|
||||
27 27 |
|
||||
28 28 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
|
||||
29 29 |
|
||||
|
||||
PYI053.pyi:30:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||
|
|
||||
28 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
|
||||
29 |
|
||||
30 | qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
|
||||
31 |
|
||||
32 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
|
||||
32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
|
|
||||
= help: Replace with `...`
|
||||
|
||||
ℹ Safe fix
|
||||
27 27 |
|
||||
28 28 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
28 28 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
|
||||
29 29 |
|
||||
30 |-bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
|
||||
30 |+bar: str = ... # Error: PYI053
|
||||
30 |-qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
|
||||
30 |+qux: bytes = ... # Error: PYI053
|
||||
31 31 |
|
||||
32 32 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
|
||||
32 32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
33 33 |
|
||||
|
||||
PYI053.pyi:34:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||
PYI053.pyi:34:15: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||
|
|
||||
32 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
|
||||
32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
33 |
|
||||
34 | qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
|
||||
34 | fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
|
||||
35 |
|
||||
36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
36 | class Demo:
|
||||
|
|
||||
= help: Replace with `...`
|
||||
|
||||
ℹ Safe fix
|
||||
31 31 |
|
||||
32 32 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
|
||||
32 32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
33 33 |
|
||||
34 |-qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
|
||||
34 |+qux: bytes = ... # Error: PYI053
|
||||
34 |-fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
|
||||
34 |+fbar: str = f"..." # Error: PYI053
|
||||
35 35 |
|
||||
36 36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
37 37 |
|
||||
|
||||
PYI053.pyi:38:15: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||
|
|
||||
36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
37 |
|
||||
38 | fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
|
||||
39 |
|
||||
40 | class Demo:
|
||||
|
|
||||
= help: Replace with `...`
|
||||
|
||||
ℹ Safe fix
|
||||
35 35 |
|
||||
36 36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||
37 37 |
|
||||
38 |-fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
|
||||
38 |+fbar: str = f"..." # Error: PYI053
|
||||
39 39 |
|
||||
40 40 | class Demo:
|
||||
41 41 | """Docstrings are excluded from this rule. Some padding.""" # OK
|
||||
|
||||
PYI053.pyi:64:5: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||
|
|
||||
63 | @not_warnings_dot_deprecated(
|
||||
64 | "Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
|
||||
65 | )
|
||||
66 | def not_a_deprecated_function() -> None: ...
|
||||
|
|
||||
= help: Replace with `...`
|
||||
|
||||
ℹ Safe fix
|
||||
61 61 | ) -> Callable[[Callable[[], None]], Callable[[], None]]: ...
|
||||
62 62 |
|
||||
63 63 | @not_warnings_dot_deprecated(
|
||||
64 |- "Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
|
||||
64 |+ ... # Error: PYI053
|
||||
65 65 | )
|
||||
66 66 | def not_a_deprecated_function() -> None: ...
|
||||
36 36 | class Demo:
|
||||
37 37 | """Docstrings are excluded from this rule. Some padding.""" # OK
|
||||
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ pub(crate) fn avoidable_escaped_quote(
|
||||
|
||||
match tok {
|
||||
Tok::String {
|
||||
value: string_contents,
|
||||
value,
|
||||
kind,
|
||||
triple_quoted,
|
||||
} => {
|
||||
@@ -176,6 +176,8 @@ pub(crate) fn avoidable_escaped_quote(
|
||||
continue;
|
||||
}
|
||||
|
||||
let string_contents = locator.slice(&value);
|
||||
|
||||
// Check if we're using the preferred quotation style.
|
||||
if !leading_quote(locator.slice(tok_range)).is_some_and(|text| {
|
||||
contains_quote(text, quotes_settings.inline_quotes.as_char())
|
||||
@@ -312,7 +314,7 @@ pub(crate) fn unnecessary_escaped_quote(
|
||||
|
||||
match tok {
|
||||
Tok::String {
|
||||
value: string_contents,
|
||||
value,
|
||||
kind,
|
||||
triple_quoted,
|
||||
} => {
|
||||
@@ -320,6 +322,8 @@ pub(crate) fn unnecessary_escaped_quote(
|
||||
continue;
|
||||
}
|
||||
|
||||
let string_contents = locator.slice(&value);
|
||||
|
||||
let leading = match leading_quote(locator.slice(tok_range)) {
|
||||
Some("\"") => Quote::Double,
|
||||
Some("'") => Quote::Single,
|
||||
|
||||
@@ -379,8 +379,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
|
||||
..
|
||||
}) = &values[indices[0]]
|
||||
{
|
||||
args.first()
|
||||
.expect("`isinstance` should have two arguments")
|
||||
args.get(0).expect("`isinstance` should have two arguments")
|
||||
} else {
|
||||
unreachable!("Indices should only contain `isinstance` calls")
|
||||
};
|
||||
|
||||
@@ -142,7 +142,7 @@ pub(crate) fn use_capital_environment_variables(checker: &mut Checker, expr: &Ex
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(arg) = args.first() else {
|
||||
let Some(arg) = args.get(0) else {
|
||||
return;
|
||||
};
|
||||
let Expr::StringLiteral(ast::ExprStringLiteral { value: env_var, .. }) = arg else {
|
||||
@@ -249,7 +249,7 @@ pub(crate) fn dict_get_with_none_default(checker: &mut Checker, expr: &Expr) {
|
||||
if attr != "get" {
|
||||
return;
|
||||
}
|
||||
let Some(key) = args.first() else {
|
||||
let Some(key) = args.get(0) else {
|
||||
return;
|
||||
};
|
||||
if !(key.is_literal_expr() || key.is_name_expr()) {
|
||||
|
||||
@@ -15,7 +15,6 @@ pub(crate) use reimplemented_builtin::*;
|
||||
pub(crate) use return_in_try_except_finally::*;
|
||||
pub(crate) use suppressible_exception::*;
|
||||
pub(crate) use yoda_conditions::*;
|
||||
pub(crate) use zip_dict_keys_and_values::*;
|
||||
|
||||
mod ast_bool_op;
|
||||
mod ast_expr;
|
||||
@@ -35,4 +34,3 @@ mod reimplemented_builtin;
|
||||
mod return_in_try_except_finally;
|
||||
mod suppressible_exception;
|
||||
mod yoda_conditions;
|
||||
mod zip_dict_keys_and_values;
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
use ast::{ExprAttribute, ExprName, Identifier};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, ExprCall};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::{checkers::ast::Checker, fix::snippet::SourceCodeSnippet};
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_python_semantic::analyze::typing::is_dict;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for use of `zip()` to iterate over keys and values of a dictionary at once.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The `dict` type provides an `.items()` method which is faster and more readable.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// flag_stars = {"USA": 50, "Slovenia": 3, "Panama": 2, "Australia": 6}
|
||||
///
|
||||
/// for country, stars in zip(flag_stars.keys(), flag_stars.values()):
|
||||
/// print(f"{country}'s flag has {stars} stars.")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// flag_stars = {"USA": 50, "Slovenia": 3, "Panama": 2, "Australia": 6}
|
||||
///
|
||||
/// for country, stars in flag_stars.items():
|
||||
/// print(f"{country}'s flag has {stars} stars.")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `dict.items`](https://docs.python.org/3/library/stdtypes.html#dict.items)
|
||||
#[violation]
|
||||
pub struct ZipDictKeysAndValues {
|
||||
expected: SourceCodeSnippet,
|
||||
actual: SourceCodeSnippet,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for ZipDictKeysAndValues {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let ZipDictKeysAndValues { expected, actual } = self;
|
||||
if let (Some(expected), Some(actual)) = (expected.full_display(), actual.full_display()) {
|
||||
format!("Use `{expected}` instead of `{actual}`")
|
||||
} else {
|
||||
format!("Use `dict.items()` instead of `zip(dict.keys(), dict.values())`")
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
let ZipDictKeysAndValues { expected, actual } = self;
|
||||
if let (Some(expected), Some(actual)) = (expected.full_display(), actual.full_display()) {
|
||||
format!("Replace `{actual}` with `{expected}`")
|
||||
} else {
|
||||
"Replace `zip(dict.keys(), dict.values())` with `dict.items()`".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// SIM911
|
||||
pub(crate) fn zip_dict_keys_and_values(checker: &mut Checker, expr: &ExprCall) {
|
||||
let ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
..
|
||||
} = expr;
|
||||
match &keywords[..] {
|
||||
[] => {}
|
||||
[ast::Keyword {
|
||||
arg: Some(name), ..
|
||||
}] if name.as_str() == "strict" => {}
|
||||
_ => return,
|
||||
};
|
||||
if matches!(func.as_ref(), Expr::Name(ExprName { id, .. }) if id != "zip") {
|
||||
return;
|
||||
}
|
||||
let [arg1, arg2] = &args[..] else {
|
||||
return;
|
||||
};
|
||||
let Some((var1, attr1)) = get_var_attr(arg1) else {
|
||||
return;
|
||||
};
|
||||
let Some((var2, attr2)) = get_var_attr(arg2) else {
|
||||
return;
|
||||
};
|
||||
if var1.id != var2.id || attr1 != "keys" || attr2 != "values" {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(binding) = checker
|
||||
.semantic()
|
||||
.only_binding(var1)
|
||||
.map(|id| checker.semantic().binding(id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !is_dict(binding, checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let expected = format!("{}.items()", checker.locator().slice(var1));
|
||||
let actual = checker.locator().slice(expr);
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
ZipDictKeysAndValues {
|
||||
expected: SourceCodeSnippet::new(expected.clone()),
|
||||
actual: SourceCodeSnippet::from_str(actual),
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
expected,
|
||||
expr.range(),
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
fn get_var_attr(expr: &Expr) -> Option<(&ExprName, &Identifier)> {
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
|
||||
return None;
|
||||
};
|
||||
let Expr::Attribute(ExprAttribute { value, attr, .. }) = func.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
let Expr::Name(var_name) = value.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
Some((var_name, attr))
|
||||
}
|
||||
@@ -27,27 +27,4 @@ sections.py:144:5: D214 [*] Section is over-indented ("Returns")
|
||||
148 148 | A value of some sort.
|
||||
149 149 |
|
||||
|
||||
sections.py:558:5: D214 [*] Section is over-indented ("Returns")
|
||||
|
|
||||
557 | def titlecase_sub_section_header():
|
||||
558 | """Below, `Returns:` should be considered a section header.
|
||||
| _____^
|
||||
559 | |
|
||||
560 | | Args:
|
||||
561 | | Here's a note.
|
||||
562 | |
|
||||
563 | | Returns:
|
||||
564 | | """
|
||||
| |_______^ D214
|
||||
|
|
||||
= help: Remove over-indentation from "Returns"
|
||||
|
||||
ℹ Safe fix
|
||||
560 560 | Args:
|
||||
561 561 | Here's a note.
|
||||
562 562 |
|
||||
563 |- Returns:
|
||||
563 |+ Returns:
|
||||
564 564 | """
|
||||
|
||||
|
||||
|
||||
@@ -498,74 +498,4 @@ sections.py:527:5: D407 [*] Missing dashed underline after section ("Parameters"
|
||||
531 532 | """
|
||||
532 533 |
|
||||
|
||||
sections.py:548:5: D407 [*] Missing dashed underline after section ("Args")
|
||||
|
|
||||
547 | def lowercase_sub_section_header():
|
||||
548 | """Below, `returns:` should _not_ be considered a section header.
|
||||
| _____^
|
||||
549 | |
|
||||
550 | | Args:
|
||||
551 | | Here's a note.
|
||||
552 | |
|
||||
553 | | returns:
|
||||
554 | | """
|
||||
| |_______^ D407
|
||||
|
|
||||
= help: Add dashed line under "Args"
|
||||
|
||||
ℹ Safe fix
|
||||
548 548 | """Below, `returns:` should _not_ be considered a section header.
|
||||
549 549 |
|
||||
550 550 | Args:
|
||||
551 |+ ----
|
||||
551 552 | Here's a note.
|
||||
552 553 |
|
||||
553 554 | returns:
|
||||
|
||||
sections.py:558:5: D407 [*] Missing dashed underline after section ("Args")
|
||||
|
|
||||
557 | def titlecase_sub_section_header():
|
||||
558 | """Below, `Returns:` should be considered a section header.
|
||||
| _____^
|
||||
559 | |
|
||||
560 | | Args:
|
||||
561 | | Here's a note.
|
||||
562 | |
|
||||
563 | | Returns:
|
||||
564 | | """
|
||||
| |_______^ D407
|
||||
|
|
||||
= help: Add dashed line under "Args"
|
||||
|
||||
ℹ Safe fix
|
||||
558 558 | """Below, `Returns:` should be considered a section header.
|
||||
559 559 |
|
||||
560 560 | Args:
|
||||
561 |+ ----
|
||||
561 562 | Here's a note.
|
||||
562 563 |
|
||||
563 564 | Returns:
|
||||
|
||||
sections.py:558:5: D407 [*] Missing dashed underline after section ("Returns")
|
||||
|
|
||||
557 | def titlecase_sub_section_header():
|
||||
558 | """Below, `Returns:` should be considered a section header.
|
||||
| _____^
|
||||
559 | |
|
||||
560 | | Args:
|
||||
561 | | Here's a note.
|
||||
562 | |
|
||||
563 | | Returns:
|
||||
564 | | """
|
||||
| |_______^ D407
|
||||
|
|
||||
= help: Add dashed line under "Returns"
|
||||
|
||||
ℹ Safe fix
|
||||
561 561 | Here's a note.
|
||||
562 562 |
|
||||
563 563 | Returns:
|
||||
564 |+ -------
|
||||
564 565 | """
|
||||
|
||||
|
||||
|
||||
@@ -97,18 +97,4 @@ sections.py:261:5: D414 Section has no content ("Returns")
|
||||
| |_______^ D414
|
||||
|
|
||||
|
||||
sections.py:558:5: D414 Section has no content ("Returns")
|
||||
|
|
||||
557 | def titlecase_sub_section_header():
|
||||
558 | """Below, `Returns:` should be considered a section header.
|
||||
| _____^
|
||||
559 | |
|
||||
560 | | Args:
|
||||
561 | | Here's a note.
|
||||
562 | |
|
||||
563 | | Returns:
|
||||
564 | | """
|
||||
| |_______^ D414
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -64,12 +64,4 @@ D417.py:108:5: D417 Missing argument description in the docstring for `f`: `*arg
|
||||
109 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:155:5: D417 Missing argument description in the docstring for `select_data`: `auto_save`
|
||||
|
|
||||
155 | def select_data(
|
||||
| ^^^^^^^^^^^ D417
|
||||
156 | query: str,
|
||||
157 | args: tuple,
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -64,12 +64,4 @@ D417.py:108:5: D417 Missing argument description in the docstring for `f`: `*arg
|
||||
109 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:155:5: D417 Missing argument description in the docstring for `select_data`: `auto_save`
|
||||
|
|
||||
155 | def select_data(
|
||||
| ^^^^^^^^^^^ D417
|
||||
156 | query: str,
|
||||
157 | args: tuple,
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -120,7 +120,6 @@ mod tests {
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_24.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_25.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_26.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_27.py"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_0.py"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_1.py"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_2.py"))]
|
||||
@@ -168,34 +167,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_0.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_1.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_10.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_11.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_12.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_13.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_14.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_15.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_16.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_17.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_18.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_19.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_2.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_20.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_21.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_22.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_23.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_24.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_25.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_26.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_27.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_3.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_4.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_5.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_6.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_7.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_8.py"))]
|
||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_9.py"))]
|
||||
#[test_case(Rule::UnusedVariable, Path::new("F841_4.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
|
||||
@@ -4,8 +4,8 @@ use ruff_python_ast::{CmpOp, Expr};
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers;
|
||||
use ruff_python_parser::{lexer, Mode, Tok};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ruff_python_parser::locate_cmp_ops;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::settings::types::PreviewMode;
|
||||
@@ -138,207 +138,3 @@ impl From<&CmpOp> for IsCmpOp {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract all [`CmpOp`] operators from an expression snippet, with appropriate
|
||||
/// ranges.
|
||||
///
|
||||
/// `RustPython` doesn't include line and column information on [`CmpOp`] nodes.
|
||||
/// `CPython` doesn't either. This method iterates over the token stream and
|
||||
/// re-identifies [`CmpOp`] nodes, annotating them with valid ranges.
|
||||
fn locate_cmp_ops(expr: &Expr, source: &str) -> Vec<LocatedCmpOp> {
|
||||
// If `Expr` is a multi-line expression, we need to parenthesize it to
|
||||
// ensure that it's lexed correctly.
|
||||
let contents = &source[expr.range()];
|
||||
let parenthesized_contents = format!("({contents})");
|
||||
let mut tok_iter = lexer::lex(&parenthesized_contents, Mode::Expression)
|
||||
.flatten()
|
||||
.skip(1)
|
||||
.map(|(tok, range)| (tok, range - TextSize::from(1)))
|
||||
.filter(|(tok, _)| !matches!(tok, Tok::NonLogicalNewline | Tok::Comment(_)))
|
||||
.peekable();
|
||||
|
||||
let mut ops: Vec<LocatedCmpOp> = vec![];
|
||||
|
||||
// Track the bracket depth.
|
||||
let mut par_count = 0u32;
|
||||
let mut sqb_count = 0u32;
|
||||
let mut brace_count = 0u32;
|
||||
|
||||
loop {
|
||||
let Some((tok, range)) = tok_iter.next() else {
|
||||
break;
|
||||
};
|
||||
|
||||
match tok {
|
||||
Tok::Lpar => {
|
||||
par_count = par_count.saturating_add(1);
|
||||
}
|
||||
Tok::Rpar => {
|
||||
par_count = par_count.saturating_sub(1);
|
||||
}
|
||||
Tok::Lsqb => {
|
||||
sqb_count = sqb_count.saturating_add(1);
|
||||
}
|
||||
Tok::Rsqb => {
|
||||
sqb_count = sqb_count.saturating_sub(1);
|
||||
}
|
||||
Tok::Lbrace => {
|
||||
brace_count = brace_count.saturating_add(1);
|
||||
}
|
||||
Tok::Rbrace => {
|
||||
brace_count = brace_count.saturating_sub(1);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if par_count > 0 || sqb_count > 0 || brace_count > 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
match tok {
|
||||
Tok::Not => {
|
||||
if let Some((_, next_range)) = tok_iter.next_if(|(tok, _)| tok.is_in()) {
|
||||
ops.push(LocatedCmpOp::new(
|
||||
TextRange::new(range.start(), next_range.end()),
|
||||
CmpOp::NotIn,
|
||||
));
|
||||
}
|
||||
}
|
||||
Tok::In => {
|
||||
ops.push(LocatedCmpOp::new(range, CmpOp::In));
|
||||
}
|
||||
Tok::Is => {
|
||||
let op = if let Some((_, next_range)) = tok_iter.next_if(|(tok, _)| tok.is_not()) {
|
||||
LocatedCmpOp::new(
|
||||
TextRange::new(range.start(), next_range.end()),
|
||||
CmpOp::IsNot,
|
||||
)
|
||||
} else {
|
||||
LocatedCmpOp::new(range, CmpOp::Is)
|
||||
};
|
||||
ops.push(op);
|
||||
}
|
||||
Tok::NotEqual => {
|
||||
ops.push(LocatedCmpOp::new(range, CmpOp::NotEq));
|
||||
}
|
||||
Tok::EqEqual => {
|
||||
ops.push(LocatedCmpOp::new(range, CmpOp::Eq));
|
||||
}
|
||||
Tok::GreaterEqual => {
|
||||
ops.push(LocatedCmpOp::new(range, CmpOp::GtE));
|
||||
}
|
||||
Tok::Greater => {
|
||||
ops.push(LocatedCmpOp::new(range, CmpOp::Gt));
|
||||
}
|
||||
Tok::LessEqual => {
|
||||
ops.push(LocatedCmpOp::new(range, CmpOp::LtE));
|
||||
}
|
||||
Tok::Less => {
|
||||
ops.push(LocatedCmpOp::new(range, CmpOp::Lt));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
ops
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct LocatedCmpOp {
|
||||
range: TextRange,
|
||||
op: CmpOp,
|
||||
}
|
||||
|
||||
impl LocatedCmpOp {
|
||||
fn new<T: Into<TextRange>>(range: T, op: CmpOp) -> Self {
|
||||
Self {
|
||||
range: range.into(),
|
||||
op,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
use ruff_python_ast::CmpOp;
|
||||
use ruff_python_parser::parse_expression;
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use super::{locate_cmp_ops, LocatedCmpOp};
|
||||
|
||||
#[test]
|
||||
fn extract_cmp_op_location() -> Result<()> {
|
||||
let contents = "x == 1";
|
||||
let expr = parse_expression(contents)?;
|
||||
assert_eq!(
|
||||
locate_cmp_ops(&expr, contents),
|
||||
vec![LocatedCmpOp::new(
|
||||
TextSize::from(2)..TextSize::from(4),
|
||||
CmpOp::Eq
|
||||
)]
|
||||
);
|
||||
|
||||
let contents = "x != 1";
|
||||
let expr = parse_expression(contents)?;
|
||||
assert_eq!(
|
||||
locate_cmp_ops(&expr, contents),
|
||||
vec![LocatedCmpOp::new(
|
||||
TextSize::from(2)..TextSize::from(4),
|
||||
CmpOp::NotEq
|
||||
)]
|
||||
);
|
||||
|
||||
let contents = "x is 1";
|
||||
let expr = parse_expression(contents)?;
|
||||
assert_eq!(
|
||||
locate_cmp_ops(&expr, contents),
|
||||
vec![LocatedCmpOp::new(
|
||||
TextSize::from(2)..TextSize::from(4),
|
||||
CmpOp::Is
|
||||
)]
|
||||
);
|
||||
|
||||
let contents = "x is not 1";
|
||||
let expr = parse_expression(contents)?;
|
||||
assert_eq!(
|
||||
locate_cmp_ops(&expr, contents),
|
||||
vec![LocatedCmpOp::new(
|
||||
TextSize::from(2)..TextSize::from(8),
|
||||
CmpOp::IsNot
|
||||
)]
|
||||
);
|
||||
|
||||
let contents = "x in 1";
|
||||
let expr = parse_expression(contents)?;
|
||||
assert_eq!(
|
||||
locate_cmp_ops(&expr, contents),
|
||||
vec![LocatedCmpOp::new(
|
||||
TextSize::from(2)..TextSize::from(4),
|
||||
CmpOp::In
|
||||
)]
|
||||
);
|
||||
|
||||
let contents = "x not in 1";
|
||||
let expr = parse_expression(contents)?;
|
||||
assert_eq!(
|
||||
locate_cmp_ops(&expr, contents),
|
||||
vec![LocatedCmpOp::new(
|
||||
TextSize::from(2)..TextSize::from(8),
|
||||
CmpOp::NotIn
|
||||
)]
|
||||
);
|
||||
|
||||
let contents = "x != (1 is not 2)";
|
||||
let expr = parse_expression(contents)?;
|
||||
assert_eq!(
|
||||
locate_cmp_ops(&expr, contents),
|
||||
vec![LocatedCmpOp::new(
|
||||
TextSize::from(2)..TextSize::from(4),
|
||||
CmpOp::NotEq
|
||||
)]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_diagnostics::{FixAvailability, Violation};
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_source_file::SourceRow;
|
||||
|
||||
@@ -29,16 +29,9 @@ pub struct RedefinedWhileUnused {
|
||||
}
|
||||
|
||||
impl Violation for RedefinedWhileUnused {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let RedefinedWhileUnused { name, row } = self;
|
||||
format!("Redefinition of unused `{name}` from {row}")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let RedefinedWhileUnused { name, .. } = self;
|
||||
Some(format!("Remove definition: `{name}`"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,5 @@ F811_0.py:10:5: F811 Redefinition of unused `bar` from line 6
|
||||
| ^^^ F811
|
||||
11 | pass
|
||||
|
|
||||
= help: Remove definition: `bar`
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,5 @@ F811_1.py:1:25: F811 Redefinition of unused `FU` from line 1
|
||||
1 | import fu as FU, bar as FU
|
||||
| ^^ F811
|
||||
|
|
||||
= help: Remove definition: `FU`
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,5 @@ F811_12.py:6:20: F811 Redefinition of unused `mixer` from line 2
|
||||
| ^^^^^ F811
|
||||
7 | mixer(123)
|
||||
|
|
||||
= help: Remove definition: `mixer`
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,5 @@ F811_15.py:4:5: F811 Redefinition of unused `fu` from line 1
|
||||
| ^^ F811
|
||||
5 | pass
|
||||
|
|
||||
= help: Remove definition: `fu`
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,5 @@ F811_16.py:8:13: F811 Redefinition of unused `fu` from line 3
|
||||
| ^^ F811
|
||||
9 | pass
|
||||
|
|
||||
= help: Remove definition: `fu`
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ F811_17.py:6:12: F811 Redefinition of unused `fu` from line 2
|
||||
7 |
|
||||
8 | def baz():
|
||||
|
|
||||
= help: Remove definition: `fu`
|
||||
|
||||
F811_17.py:9:13: F811 Redefinition of unused `fu` from line 6
|
||||
|
|
||||
@@ -18,6 +17,5 @@ F811_17.py:9:13: F811 Redefinition of unused `fu` from line 6
|
||||
| ^^ F811
|
||||
10 | pass
|
||||
|
|
||||
= help: Remove definition: `fu`
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,5 @@ F811_2.py:1:34: F811 Redefinition of unused `FU` from line 1
|
||||
1 | from moo import fu as FU, bar as FU
|
||||
| ^^ F811
|
||||
|
|
||||
= help: Remove definition: `FU`
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,5 @@ F811_21.py:32:5: F811 Redefinition of unused `Sequence` from line 26
|
||||
| ^^^^^^^^ F811
|
||||
33 | )
|
||||
|
|
||||
= help: Remove definition: `Sequence`
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,5 @@ F811_23.py:4:15: F811 Redefinition of unused `foo` from line 3
|
||||
4 | import bar as foo
|
||||
| ^^^ F811
|
||||
|
|
||||
= help: Remove definition: `foo`
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,5 @@ F811_26.py:5:9: F811 Redefinition of unused `func` from line 2
|
||||
| ^^^^ F811
|
||||
6 | pass
|
||||
|
|
||||
= help: Remove definition: `func`
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -6,6 +6,5 @@ F811_3.py:1:12: F811 Redefinition of unused `fu` from line 1
|
||||
1 | import fu; fu = 3
|
||||
| ^^ F811
|
||||
|
|
||||
= help: Remove definition: `fu`
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,5 @@ F811_4.py:1:12: F811 Redefinition of unused `fu` from line 1
|
||||
1 | import fu; fu, bar = 3
|
||||
| ^^ F811
|
||||
|
|
||||
= help: Remove definition: `fu`
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,5 @@ F811_5.py:1:13: F811 Redefinition of unused `fu` from line 1
|
||||
1 | import fu; [fu, bar] = 3
|
||||
| ^^ F811
|
||||
|
|
||||
= help: Remove definition: `fu`
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,5 @@ F811_6.py:6:12: F811 Redefinition of unused `os` from line 5
|
||||
| ^^ F811
|
||||
7 | os.path
|
||||
|
|
||||
= help: Remove definition: `os`
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,5 @@ F811_8.py:5:12: F811 Redefinition of unused `os` from line 4
|
||||
6 | except:
|
||||
7 | pass
|
||||
|
|
||||
= help: Remove definition: `os`
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,5 @@ source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
6 |
|
||||
7 | # Despite this `del`, `import os` in `f` should still be flagged as shadowing an unused
|
||||
|
|
||||
= help: Remove definition: `os`
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +24,5 @@ source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
| ^^ F811
|
||||
6 | print(os)
|
||||
|
|
||||
= help: Remove definition: `os`
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,5 @@ source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
5 |
|
||||
6 | # Despite this `del`, `import os` should still be flagged as shadowing an unused
|
||||
|
|
||||
= help: Remove definition: `os`
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F811_0.py:10:5: F811 Redefinition of unused `bar` from line 6
|
||||
|
|
||||
10 | def bar():
|
||||
| ^^^ F811
|
||||
11 | pass
|
||||
|
|
||||
= help: Remove definition: `bar`
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F811_1.py:1:25: F811 [*] Redefinition of unused `FU` from line 1
|
||||
|
|
||||
1 | import fu as FU, bar as FU
|
||||
| ^^ F811
|
||||
|
|
||||
= help: Remove definition: `FU`
|
||||
|
||||
ℹ Safe fix
|
||||
1 |-import fu as FU, bar as FU
|
||||
1 |+import fu as FU
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F811_12.py:6:20: F811 [*] Redefinition of unused `mixer` from line 2
|
||||
|
|
||||
4 | pass
|
||||
5 | else:
|
||||
6 | from bb import mixer
|
||||
| ^^^^^ F811
|
||||
7 | mixer(123)
|
||||
|
|
||||
= help: Remove definition: `mixer`
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 | except ImportError:
|
||||
4 4 | pass
|
||||
5 5 | else:
|
||||
6 |- from bb import mixer
|
||||
6 |+ pass
|
||||
7 7 | mixer(123)
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F811_15.py:4:5: F811 Redefinition of unused `fu` from line 1
|
||||
|
|
||||
4 | def fu():
|
||||
| ^^ F811
|
||||
5 | pass
|
||||
|
|
||||
= help: Remove definition: `fu`
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F811_16.py:8:13: F811 Redefinition of unused `fu` from line 3
|
||||
|
|
||||
6 | def bar():
|
||||
7 | def baz():
|
||||
8 | def fu():
|
||||
| ^^ F811
|
||||
9 | pass
|
||||
|
|
||||
= help: Remove definition: `fu`
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F811_17.py:6:12: F811 [*] Redefinition of unused `fu` from line 2
|
||||
|
|
||||
5 | def bar():
|
||||
6 | import fu
|
||||
| ^^ F811
|
||||
7 |
|
||||
8 | def baz():
|
||||
|
|
||||
= help: Remove definition: `fu`
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 |
|
||||
4 4 |
|
||||
5 5 | def bar():
|
||||
6 |- import fu
|
||||
7 6 |
|
||||
8 7 | def baz():
|
||||
9 8 | def fu():
|
||||
|
||||
F811_17.py:9:13: F811 Redefinition of unused `fu` from line 6
|
||||
|
|
||||
8 | def baz():
|
||||
9 | def fu():
|
||||
| ^^ F811
|
||||
10 | pass
|
||||
|
|
||||
= help: Remove definition: `fu`
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F811_2.py:1:34: F811 [*] Redefinition of unused `FU` from line 1
|
||||
|
|
||||
1 | from moo import fu as FU, bar as FU
|
||||
| ^^ F811
|
||||
|
|
||||
= help: Remove definition: `FU`
|
||||
|
||||
ℹ Safe fix
|
||||
1 |-from moo import fu as FU, bar as FU
|
||||
1 |+from moo import fu as FU
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F811_21.py:32:5: F811 [*] Redefinition of unused `Sequence` from line 26
|
||||
|
|
||||
30 | from typing import (
|
||||
31 | List, # noqa: F811
|
||||
32 | Sequence,
|
||||
| ^^^^^^^^ F811
|
||||
33 | )
|
||||
|
|
||||
= help: Remove definition: `Sequence`
|
||||
|
||||
ℹ Safe fix
|
||||
29 29 | # This should ignore the first error.
|
||||
30 30 | from typing import (
|
||||
31 31 | List, # noqa: F811
|
||||
32 |- Sequence,
|
||||
33 |-)
|
||||
32 |+ )
|
||||
34 33 |
|
||||
35 34 | # This should ignore both errors.
|
||||
36 35 | from typing import ( # noqa
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F811_23.py:4:15: F811 [*] Redefinition of unused `foo` from line 3
|
||||
|
|
||||
3 | import foo as foo
|
||||
4 | import bar as foo
|
||||
| ^^^ F811
|
||||
|
|
||||
= help: Remove definition: `foo`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | """Test that shadowing an explicit re-export produces a warning."""
|
||||
2 2 |
|
||||
3 3 | import foo as foo
|
||||
4 |-import bar as foo
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F811_26.py:5:9: F811 Redefinition of unused `func` from line 2
|
||||
|
|
||||
3 | pass
|
||||
4 |
|
||||
5 | def func(self):
|
||||
| ^^^^ F811
|
||||
6 | pass
|
||||
|
|
||||
= help: Remove definition: `func`
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F811_3.py:1:12: F811 Redefinition of unused `fu` from line 1
|
||||
|
|
||||
1 | import fu; fu = 3
|
||||
| ^^ F811
|
||||
|
|
||||
= help: Remove definition: `fu`
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F811_4.py:1:12: F811 Redefinition of unused `fu` from line 1
|
||||
|
|
||||
1 | import fu; fu, bar = 3
|
||||
| ^^ F811
|
||||
|
|
||||
= help: Remove definition: `fu`
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F811_5.py:1:13: F811 Redefinition of unused `fu` from line 1
|
||||
|
|
||||
1 | import fu; [fu, bar] = 3
|
||||
| ^^ F811
|
||||
|
|
||||
= help: Remove definition: `fu`
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F811_6.py:6:12: F811 [*] Redefinition of unused `os` from line 5
|
||||
|
|
||||
4 | if i == 1:
|
||||
5 | import os
|
||||
6 | import os
|
||||
| ^^ F811
|
||||
7 | os.path
|
||||
|
|
||||
= help: Remove definition: `os`
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 | i = 2
|
||||
4 4 | if i == 1:
|
||||
5 5 | import os
|
||||
6 |- import os
|
||||
7 6 | os.path
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user