Compare commits
20 Commits
0.12.10
...
ibraheem/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
992e77e4d0 | ||
|
|
8d05367d60 | ||
|
|
2402831223 | ||
|
|
7abc41727b | ||
|
|
886c4e4773 | ||
|
|
bc6ea68733 | ||
|
|
796819e7a0 | ||
|
|
5508e8e528 | ||
|
|
0be3e1fbbf | ||
|
|
5d217b7f46 | ||
|
|
0b6ce1c788 | ||
|
|
0e9d77e43a | ||
|
|
8b827c3c6c | ||
|
|
c22395dbc6 | ||
|
|
11f521c768 | ||
|
|
c5e05df966 | ||
|
|
7a44ea680e | ||
|
|
f82025d919 | ||
|
|
365f521c37 | ||
|
|
fc5321e000 |
35
Cargo.lock
generated
35
Cargo.lock
generated
@@ -260,6 +260,9 @@ name = "bitflags"
|
||||
version = "2.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
@@ -1028,6 +1031,16 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"typeid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.13"
|
||||
@@ -3451,12 +3464,13 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a3ffa22cb26756473d56f867aedec3fd907c4dd9#a3ffa22cb26756473d56f867aedec3fd907c4dd9"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
"erased-serde",
|
||||
"hashbrown 0.15.5",
|
||||
"hashlink",
|
||||
"indexmap",
|
||||
@@ -3467,6 +3481,7 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"salsa-macro-rules",
|
||||
"salsa-macros",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thin-vec",
|
||||
"tracing",
|
||||
@@ -3475,12 +3490,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a3ffa22cb26756473d56f867aedec3fd907c4dd9#a3ffa22cb26756473d56f867aedec3fd907c4dd9"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a3ffa22cb26756473d56f867aedec3fd907c4dd9#a3ffa22cb26756473d56f867aedec3fd907c4dd9"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3700,6 +3715,9 @@ name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snapbox"
|
||||
@@ -3904,6 +3922,9 @@ name = "thin-vec"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
@@ -4261,6 +4282,7 @@ name = "ty_project"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode 2.0.1",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"crossbeam",
|
||||
@@ -4290,6 +4312,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"ty_combine",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
@@ -4461,6 +4484,12 @@ version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
|
||||
18
Cargo.toml
18
Cargo.toml
@@ -57,8 +57,8 @@ anyhow = { version = "1.0.80" }
|
||||
arc-swap = { version = "1.7.1" }
|
||||
assert_fs = { version = "1.1.0" }
|
||||
argfile = { version = "0.2.0" }
|
||||
bincode = { version = "2.0.0" }
|
||||
bitflags = { version = "2.5.0" }
|
||||
bincode = { version = "2.0.0", features = ["serde"] }
|
||||
bitflags = { version = "2.5.0", features = ["serde"] }
|
||||
bitvec = { version = "1.0.1", default-features = false, features = [
|
||||
"alloc",
|
||||
] }
|
||||
@@ -126,7 +126,7 @@ memchr = { version = "2.7.1" }
|
||||
mimalloc = { version = "0.1.39" }
|
||||
natord = { version = "1.0.9" }
|
||||
notify = { version = "8.0.0" }
|
||||
ordermap = { version = "0.5.0" }
|
||||
ordermap = { version = "0.5.0", features = ["serde"] }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
path-slash = { version = "0.2.1" }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
@@ -143,24 +143,25 @@ regex-automata = { version = "0.4.9" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "a3ffa22cb26756473d56f867aedec3fd907c4dd9", default-features = false, features = [
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "a0e7a06", default-features = false, features = [
|
||||
"compact_str",
|
||||
"macros",
|
||||
"salsa_unstable",
|
||||
"inventory",
|
||||
"persistence",
|
||||
] }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version = "4.1.0" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
serde-wasm-bindgen = { version = "0.6.4" }
|
||||
serde_json = { version = "1.0.113" }
|
||||
serde_json = { version = "1.0.142" }
|
||||
serde_test = { version = "1.0.152" }
|
||||
serde_with = { version = "3.6.0", default-features = false, features = [
|
||||
"macros",
|
||||
] }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.4.0", features = ["inline"] }
|
||||
smallvec = { version = "1.13.2", features = ["union", "const_generics", "const_new"] }
|
||||
smallvec = { version = "1.13.2", features = ["union", "const_generics", "const_new", "serde"] }
|
||||
snapbox = { version = "0.6.0", features = [
|
||||
"diff",
|
||||
"term-svg",
|
||||
@@ -215,6 +216,8 @@ unexpected_cfgs = { level = "warn", check-cfg = [
|
||||
|
||||
[workspace.lints.clippy]
|
||||
pedantic = { level = "warn", priority = -2 }
|
||||
# Enabled at the crate level
|
||||
disallowed_methods = "allow"
|
||||
# Allowed pedantic lints
|
||||
char_lit_as_u8 = "allow"
|
||||
collapsible_else_if = "allow"
|
||||
@@ -253,6 +256,7 @@ unused_peekable = "warn"
|
||||
# Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved.
|
||||
large_stack_arrays = "allow"
|
||||
|
||||
|
||||
[profile.release]
|
||||
# Note that we set these explicitly, and these values
|
||||
# were chosen based on a trade-off between compile times
|
||||
|
||||
17
clippy.toml
17
clippy.toml
@@ -24,3 +24,20 @@ ignore-interior-mutability = [
|
||||
# The expression is read-only.
|
||||
"ruff_python_ast::hashable::HashableExpr",
|
||||
]
|
||||
|
||||
disallowed-methods = [
|
||||
{ path = "std::env::var", reason = "Use System::env_var instead in ty crates" },
|
||||
{ path = "std::env::current_dir", reason = "Use System::current_directory instead in ty crates" },
|
||||
{ path = "std::fs::read_to_string", reason = "Use System::read_to_string instead in ty crates" },
|
||||
{ path = "std::fs::metadata", reason = "Use System::path_metadata instead in ty crates" },
|
||||
{ path = "std::fs::canonicalize", reason = "Use System::canonicalize_path instead in ty crates" },
|
||||
{ path = "dunce::canonicalize", reason = "Use System::canonicalize_path instead in ty crates" },
|
||||
{ path = "std::fs::read_dir", reason = "Use System::read_directory instead in ty crates" },
|
||||
{ path = "std::fs::write", reason = "Use WritableSystem::write_file instead in ty crates" },
|
||||
{ path = "std::fs::create_dir_all", reason = "Use WritableSystem::create_directory_all instead in ty crates" },
|
||||
{ path = "std::fs::File::create_new", reason = "Use WritableSystem::create_new_file instead in ty crates" },
|
||||
# Path methods that have System trait equivalents
|
||||
{ path = "std::path::Path::exists", reason = "Use System::path_exists instead in ty crates" },
|
||||
{ path = "std::path::Path::is_dir", reason = "Use System::is_directory instead in ty crates" },
|
||||
{ path = "std::path::Path::is_file", reason = "Use System::is_file instead in ty crates" },
|
||||
]
|
||||
|
||||
@@ -31,7 +31,7 @@ ruff_workspace = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
argfile = { workspace = true }
|
||||
bincode = { workspace = true, features = ["serde"] }
|
||||
bincode = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
cachedir = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive", "env", "wrap_help"] }
|
||||
|
||||
@@ -22,6 +22,7 @@ mod stylesheet;
|
||||
/// a characteristic is a deficiency. An example of a characteristic that is
|
||||
/// _not_ a deficiency is the `reveal_type` diagnostic for our type checker.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Diagnostic {
|
||||
/// The actual diagnostic.
|
||||
///
|
||||
@@ -500,6 +501,7 @@ impl Diagnostic {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
struct DiagnosticInner {
|
||||
id: DiagnosticId,
|
||||
severity: Severity,
|
||||
@@ -576,6 +578,7 @@ impl Eq for RenderingSortKey<'_> {}
|
||||
/// another (for a single parent diagnostic) is the order in which they were
|
||||
/// attached to the diagnostic.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct SubDiagnostic {
|
||||
/// Like with `Diagnostic`, we box the `SubDiagnostic` to make it
|
||||
/// pointer-sized.
|
||||
@@ -685,6 +688,7 @@ impl SubDiagnostic {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
struct SubDiagnosticInner {
|
||||
severity: SubDiagnosticSeverity,
|
||||
message: DiagnosticMessage,
|
||||
@@ -713,6 +717,7 @@ struct SubDiagnosticInner {
|
||||
/// Messages attached to annotations should also be as brief and specific as
|
||||
/// possible. Long messages could negative impact the quality of rendering.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Annotation {
|
||||
/// The span of this annotation, corresponding to some subsequence of the
|
||||
/// user's input that we want to highlight.
|
||||
@@ -855,6 +860,7 @@ impl Annotation {
|
||||
/// These tags are used to provide additional information about the annotation.
|
||||
/// and are passed through to the language server protocol.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum DiagnosticTag {
|
||||
/// Unused or unnecessary code. Used for unused parameters, unreachable code, etc.
|
||||
Unnecessary,
|
||||
@@ -869,6 +875,7 @@ pub enum DiagnosticTag {
|
||||
///
|
||||
/// Rules use kebab case, e.g. `no-foo`.
|
||||
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
|
||||
pub struct LintName(&'static str);
|
||||
|
||||
impl LintName {
|
||||
@@ -881,6 +888,66 @@ impl LintName {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
pub use lint_name_serde::LintRegistryGuard;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
mod lint_name_serde {
|
||||
use super::LintName;
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
thread_local! {
|
||||
/// Serde doesn't provide any easy means to pass a value to a [`Deserialize`] implementation,
|
||||
/// but we need a way to retrieve static [`LintName`]s from the lint registry when deserializing.
|
||||
///
|
||||
/// Use the [`LintRegistryGuard`] to initialize the thread local before calling into any
|
||||
/// deserialization code. It ensures that the thread local variable gets cleaned up
|
||||
/// once deserialization is done (once the guard gets dropped).
|
||||
static LINT_REGISTRY: RefCell<Option<LintRegistry>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
type LintRegistry = fn(&str) -> Option<LintName>;
|
||||
|
||||
/// Guard to safely change the lint registry for the current thread.
|
||||
#[must_use]
|
||||
pub struct LintRegistryGuard {
|
||||
prev_value: Option<LintRegistry>,
|
||||
}
|
||||
|
||||
impl LintRegistryGuard {
|
||||
pub fn new(registry: LintRegistry) -> Self {
|
||||
let prev = LINT_REGISTRY.replace(Some(registry));
|
||||
Self { prev_value: prev }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LintRegistryGuard {
|
||||
fn drop(&mut self) {
|
||||
LINT_REGISTRY.set(self.prev_value.take());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for LintName {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let name: &str = serde::Deserialize::deserialize(deserializer)?;
|
||||
|
||||
LINT_REGISTRY.with_borrow(|registry| {
|
||||
let registry = registry
|
||||
.expect("must set the `LintRegistryGuard` when deserializing a `LintName`");
|
||||
|
||||
registry(name).ok_or(serde::de::Error::custom(format!(
|
||||
"invalid `LintName` {name}"
|
||||
)))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for LintName {
|
||||
type Target = str;
|
||||
|
||||
@@ -909,6 +976,7 @@ impl PartialEq<&str> for LintName {
|
||||
|
||||
/// Uniquely identifies the kind of a diagnostic.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum DiagnosticId {
|
||||
Panic,
|
||||
|
||||
@@ -1097,6 +1165,30 @@ impl UnifiedFile {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for UnifiedFile {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
UnifiedFile::Ty(file) => serde::Serialize::serialize(file, serializer),
|
||||
// Persistent caching is only used in ty.
|
||||
UnifiedFile::Ruff(..) => panic!("Ruff files are not persistable"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for UnifiedFile {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
serde::Deserialize::deserialize(deserializer).map(UnifiedFile::Ty)
|
||||
}
|
||||
}
|
||||
|
||||
/// A unified wrapper for types that can be converted to a [`SourceCode`].
|
||||
///
|
||||
/// As with [`UnifiedFile`], ruff and ty use slightly different representations for source code.
|
||||
@@ -1128,6 +1220,7 @@ impl DiagnosticSource {
|
||||
/// range isn't present, it semantically implies that the diagnostic refers to
|
||||
/// the entire file. For example, when the file should be executable but isn't.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Span {
|
||||
file: UnifiedFile,
|
||||
range: Option<TextRange>,
|
||||
@@ -1206,6 +1299,7 @@ impl From<crate::files::FileRange> for Span {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum Severity {
|
||||
Info,
|
||||
Warning,
|
||||
@@ -1241,6 +1335,7 @@ impl Severity {
|
||||
/// used for main diagnostics. If we want to add `Severity::Help` in the future, this type could be
|
||||
/// deleted and the two combined again.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum SubDiagnosticSeverity {
|
||||
Help,
|
||||
Info,
|
||||
@@ -1489,6 +1584,7 @@ impl std::fmt::Display for ConciseMessage<'_> {
|
||||
/// a blanket trait implementation for `IntoDiagnosticMessage` for
|
||||
/// anything that implements `std::fmt::Display`.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct DiagnosticMessage(Box<str>);
|
||||
|
||||
impl DiagnosticMessage {
|
||||
@@ -1552,7 +1648,11 @@ impl<T: std::fmt::Display> IntoDiagnosticMessage for T {
|
||||
///
|
||||
/// For Ruff rules this means the noqa code.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Serialize, serde::Deserialize),
|
||||
serde(transparent)
|
||||
)]
|
||||
pub struct SecondaryCode(String);
|
||||
|
||||
impl SecondaryCode {
|
||||
|
||||
@@ -9,7 +9,17 @@ use crate::system::file_time_now;
|
||||
/// * The last modification time of the file.
|
||||
/// * The hash of the file's content.
|
||||
/// * The revision as it comes from an external system, for example the LSP.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, get_size2::GetSize)]
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Default,
|
||||
get_size2::GetSize,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
)]
|
||||
pub struct FileRevision(u128);
|
||||
|
||||
impl FileRevision {
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::diagnostic::{Span, UnifiedFile};
|
||||
use crate::file_revision::FileRevision;
|
||||
use crate::files::file_root::FileRoots;
|
||||
use crate::files::private::FileStatus;
|
||||
use crate::system::{SystemPath, SystemPathBuf, SystemVirtualPath, SystemVirtualPathBuf};
|
||||
use crate::system::{FileType, SystemPath, SystemPathBuf, SystemVirtualPath, SystemVirtualPathBuf};
|
||||
use crate::vendored::{VendoredPath, VendoredPathBuf};
|
||||
use crate::{Db, FxDashMap, vendored};
|
||||
|
||||
@@ -139,6 +139,7 @@ impl Files {
|
||||
};
|
||||
|
||||
tracing::trace!("Adding vendored file `{}`", path);
|
||||
|
||||
let file = File::builder(FilePath::Vendored(path.to_path_buf()))
|
||||
.permissions(Some(0o444))
|
||||
.revision(metadata.revision())
|
||||
@@ -200,7 +201,15 @@ impl Files {
|
||||
let mut roots = self.inner.roots.write().unwrap();
|
||||
|
||||
let absolute = SystemPath::absolute(path, db.system().current_directory());
|
||||
roots.try_add(db, absolute, kind)
|
||||
|
||||
let (Ok(root) | Err(root)) = roots.try_add(db, absolute, |absolute| {
|
||||
FileRoot::builder(absolute, kind, FileRevision::now())
|
||||
.durability(Durability::HIGH)
|
||||
.revision_durability(kind.durability())
|
||||
.new(db)
|
||||
});
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
/// Updates the revision of the root for `path`.
|
||||
@@ -259,6 +268,51 @@ impl Files {
|
||||
root.set_revision(db).to(FileRevision::now());
|
||||
}
|
||||
}
|
||||
|
||||
/// Seed the files with an existing [`File`] instance.
|
||||
pub fn seed(&self, file: File, db: &dyn Db) {
|
||||
let seeded = match file.path(db) {
|
||||
FilePath::System(path) => self
|
||||
.inner
|
||||
.system_by_path
|
||||
.insert(path.clone(), file)
|
||||
.is_none(),
|
||||
FilePath::SystemVirtual(path) => self
|
||||
.inner
|
||||
.system_virtual_by_path
|
||||
.insert(path.clone(), VirtualFile(file))
|
||||
.is_none(),
|
||||
FilePath::Vendored(path) => self
|
||||
.inner
|
||||
.vendored_by_path
|
||||
.insert(path.clone(), file)
|
||||
.is_none(),
|
||||
};
|
||||
|
||||
// Recreating a `File` input means the persisted queries depending on that file
|
||||
// will be invalidated.
|
||||
assert!(
|
||||
seeded,
|
||||
"unexpected `File` input recreated for path `{}`",
|
||||
file.path(db)
|
||||
);
|
||||
}
|
||||
|
||||
/// Seed the files with an existing [`FileRoot`] instance.
|
||||
pub fn seed_root(&self, root: FileRoot, db: &dyn Db) {
|
||||
let mut roots = self.inner.roots.write().unwrap();
|
||||
let seeded = roots
|
||||
.try_add(db, root.path(db).to_path_buf(), |_| root)
|
||||
.is_ok();
|
||||
|
||||
// Recreating a `FileRoot` input means the persisted queries depending on that file
|
||||
// root will be invalidated.
|
||||
assert!(
|
||||
seeded,
|
||||
"unexpected `FileRoot` input recreated for path `{}`",
|
||||
root.path(db)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Files {
|
||||
@@ -290,7 +344,7 @@ impl std::panic::RefUnwindSafe for Files {}
|
||||
/// # Ordering
|
||||
/// Ordering is based on the file's salsa-assigned id and not on its values.
|
||||
/// The id may change between runs.
|
||||
#[salsa::input(heap_size=ruff_memory_usage::heap_size)]
|
||||
#[salsa::input(persist, heap_size=ruff_memory_usage::heap_size)]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct File {
|
||||
/// The path of the file (immutable).
|
||||
@@ -414,6 +468,15 @@ impl File {
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads all existing [`File`]s in the database.
|
||||
pub fn load_all(db: &dyn Db) -> Vec<File> {
|
||||
// TODO: Prune deleted paths.
|
||||
File::ingredient(db)
|
||||
.entries(db.zalsa())
|
||||
.map(|entry| entry.as_struct())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Private method providing the implementation for [`Self::sync_path`] and [`Self::sync`] for
|
||||
/// system paths.
|
||||
fn sync_system_path(db: &mut dyn Db, path: &SystemPath, file: Option<File>) {
|
||||
@@ -522,7 +585,17 @@ impl VirtualFile {
|
||||
// The types in here need to be public because they're salsa ingredients but we
|
||||
// don't want them to be publicly accessible. That's why we put them into a private module.
|
||||
mod private {
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, get_size2::GetSize)]
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Default,
|
||||
get_size2::GetSize,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
)]
|
||||
pub enum FileStatus {
|
||||
/// The file exists.
|
||||
#[default]
|
||||
@@ -536,6 +609,16 @@ mod private {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FileType> for FileStatus {
|
||||
fn from(value: FileType) -> Self {
|
||||
match value {
|
||||
FileType::File => FileStatus::Exists,
|
||||
FileType::Symlink => FileStatus::Exists,
|
||||
FileType::Directory => FileStatus::IsADirectory,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum FileError {
|
||||
IsADirectory,
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::system::{SystemPath, SystemPathBuf};
|
||||
/// The main usage of file roots is to determine a file's durability. But it can also be used
|
||||
/// to make a salsa query dependent on whether a file in a root has changed without writing any
|
||||
/// manual invalidation logic.
|
||||
#[salsa::input(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
#[salsa::input(persist, debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub struct FileRoot {
|
||||
/// The path of a root is guaranteed to never change.
|
||||
#[returns(deref)]
|
||||
@@ -35,9 +35,20 @@ impl FileRoot {
|
||||
pub fn durability(self, db: &dyn Db) -> salsa::Durability {
|
||||
self.kind_at_time_of_creation(db).durability()
|
||||
}
|
||||
|
||||
/// Loads all existing [`FileRoot`]s in the database.
|
||||
pub fn load_all(db: &dyn Db) -> Vec<FileRoot> {
|
||||
// TODO: Prune deleted paths.
|
||||
FileRoot::ingredient(db)
|
||||
.entries(db.zalsa())
|
||||
.map(|entry| entry.as_struct())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, get_size2::GetSize)]
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Eq, PartialEq, get_size2::GetSize, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
pub enum FileRootKind {
|
||||
/// The root of a project.
|
||||
Project,
|
||||
@@ -47,7 +58,7 @@ pub enum FileRootKind {
|
||||
}
|
||||
|
||||
impl FileRootKind {
|
||||
const fn durability(self) -> Durability {
|
||||
pub const fn durability(self) -> Durability {
|
||||
match self {
|
||||
FileRootKind::Project => Durability::LOW,
|
||||
FileRootKind::LibrarySearchPath => Durability::HIGH,
|
||||
@@ -62,34 +73,34 @@ pub(super) struct FileRoots {
|
||||
}
|
||||
|
||||
impl FileRoots {
|
||||
/// Tries to add a new root for `path` and returns the root.
|
||||
/// Tries to add a new root for `path`.
|
||||
///
|
||||
/// The root isn't added nor is the file root's kind updated if a root for `path` already exists.
|
||||
///
|
||||
/// Returns `Ok(root)` if the `FileRoot` was successfully added, and returns `Err(root)` with
|
||||
/// the previous root if one already existed at that path.
|
||||
pub(super) fn try_add(
|
||||
&mut self,
|
||||
db: &dyn Db,
|
||||
path: SystemPathBuf,
|
||||
kind: FileRootKind,
|
||||
) -> FileRoot {
|
||||
create_root: impl FnOnce(SystemPathBuf) -> FileRoot,
|
||||
) -> Result<FileRoot, FileRoot> {
|
||||
// SAFETY: Guaranteed to succeed because `path` is a UTF-8 that only contains Unicode characters.
|
||||
let normalized_path = path.as_std_path().to_slash().unwrap();
|
||||
|
||||
if let Ok(existing) = self.by_path.at(&normalized_path) {
|
||||
// Only if it is an exact match
|
||||
if existing.value.path(db) == &*path {
|
||||
return *existing.value;
|
||||
return Err(*existing.value);
|
||||
}
|
||||
}
|
||||
|
||||
// normalize the path to use `/` separators and escape the '{' and '}' characters,
|
||||
// which matchit uses for routing parameters
|
||||
// Normalize the path to use `/` separators and escape the '{' and '}' characters,
|
||||
// which `matchit` uses for routing parameters.
|
||||
let mut route = normalized_path.replace('{', "{{").replace('}', "}}");
|
||||
|
||||
// Insert a new source root
|
||||
let root = FileRoot::builder(path, kind, FileRevision::now())
|
||||
.durability(Durability::HIGH)
|
||||
.revision_durability(kind.durability())
|
||||
.new(db);
|
||||
let root = create_root(path);
|
||||
|
||||
// Insert a path that matches the root itself
|
||||
self.by_path.insert(route.clone(), root).unwrap();
|
||||
@@ -100,7 +111,7 @@ impl FileRoots {
|
||||
self.by_path.insert(route, root).unwrap();
|
||||
self.roots.push(root);
|
||||
|
||||
root
|
||||
Ok(root)
|
||||
}
|
||||
|
||||
/// Returns the closest root for `path` or `None` if no root contains `path`.
|
||||
|
||||
@@ -11,7 +11,9 @@ use std::fmt::{Display, Formatter};
|
||||
/// * a file stored on the [host system](crate::system::System).
|
||||
/// * a virtual file stored on the [host system](crate::system::System).
|
||||
/// * a vendored file stored in the [vendored file system](crate::vendored::VendoredFileSystem).
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[derive(
|
||||
Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
pub enum FilePath {
|
||||
/// Path to a file on the [host system](crate::system::System).
|
||||
System(SystemPathBuf),
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
#![warn(
|
||||
clippy::disallowed_methods,
|
||||
reason = "Prefer System trait methods over std methods"
|
||||
)]
|
||||
|
||||
use crate::files::Files;
|
||||
use crate::system::System;
|
||||
use crate::vendored::VendoredFileSystem;
|
||||
@@ -65,6 +70,10 @@ pub trait Db: salsa::Database {
|
||||
/// to process work in parallel. For example, to index a directory or checking the files of a project.
|
||||
/// ty can still spawn more threads for other tasks, e.g. to wait for a Ctrl+C signal or
|
||||
/// watching the files for changes.
|
||||
#[expect(
|
||||
clippy::disallowed_methods,
|
||||
reason = "We don't have access to System here, but this is also only used by the CLI and the server which always run on a real system."
|
||||
)]
|
||||
pub fn max_parallelism() -> NonZeroUsize {
|
||||
std::env::var(EnvVars::TY_MAX_PARALLELISM)
|
||||
.or_else(|_| std::env::var(EnvVars::RAYON_NUM_THREADS))
|
||||
|
||||
@@ -92,14 +92,14 @@ impl ParsedModule {
|
||||
self.inner.store(None);
|
||||
}
|
||||
|
||||
/// Returns a pointer for this [`ParsedModule`].
|
||||
/// Returns the pointer address of this [`ParsedModule`].
|
||||
///
|
||||
/// The pointer uniquely identifies the module within the current Salsa revision,
|
||||
/// regardless of whether particular [`ParsedModuleRef`] instances are garbage collected.
|
||||
pub fn as_ptr(&self) -> *const () {
|
||||
pub fn addr(&self) -> usize {
|
||||
// Note that the outer `Arc` in `inner` is stable across garbage collection, while the inner
|
||||
// `Arc` within the `ArcSwap` may change.
|
||||
Arc::as_ptr(&self.inner).cast()
|
||||
Arc::as_ptr(&self.inner).addr()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,9 +202,13 @@ mod indexed {
|
||||
|
||||
/// Returns the node at the given index.
|
||||
pub fn get_by_index<'ast>(&'ast self, index: NodeIndex) -> AnyRootNodeRef<'ast> {
|
||||
let index = index
|
||||
.as_u32()
|
||||
.expect("attempted to access uninitialized `NodeIndex`");
|
||||
|
||||
// Note that this method restores the correct lifetime: the nodes are valid for as
|
||||
// long as the reference to `IndexedModule` is alive.
|
||||
self.index[index.as_usize()]
|
||||
self.index[index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +224,7 @@ mod indexed {
|
||||
T: HasNodeIndex + std::fmt::Debug,
|
||||
AnyRootNodeRef<'a>: From<&'a T>,
|
||||
{
|
||||
node.node_index().set(self.index);
|
||||
node.node_index().set(NodeIndex::from(self.index));
|
||||
self.nodes.push(AnyRootNodeRef::from(node));
|
||||
self.index += 1;
|
||||
}
|
||||
|
||||
@@ -148,7 +148,16 @@ impl From<Notebook> for SourceTextKind {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone, get_size2::GetSize)]
|
||||
#[derive(
|
||||
Debug,
|
||||
thiserror::Error,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
get_size2::GetSize,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
)]
|
||||
pub enum SourceTextError {
|
||||
#[error("Failed to read notebook: {0}`")]
|
||||
FailedToReadNotebook(String),
|
||||
|
||||
@@ -46,7 +46,7 @@ pub type Result<T> = std::io::Result<T>;
|
||||
/// * File watching isn't supported.
|
||||
///
|
||||
/// Abstracting the system also enables tests to use a more efficient in-memory file system.
|
||||
pub trait System: Debug {
|
||||
pub trait System: Debug + Sync + Send {
|
||||
/// Reads the metadata of the file or directory at `path`.
|
||||
///
|
||||
/// This function will traverse symbolic links to query information about the destination file.
|
||||
@@ -66,6 +66,9 @@ pub trait System: Debug {
|
||||
/// See [dunce::canonicalize] for more information.
|
||||
fn canonicalize_path(&self, path: &SystemPath) -> Result<SystemPathBuf>;
|
||||
|
||||
/// Reads the content of the file at `path` into a bytes buffer.
|
||||
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>>;
|
||||
|
||||
/// Reads the content of the file at `path` into a [`String`].
|
||||
fn read_to_string(&self, path: &SystemPath) -> Result<String>;
|
||||
|
||||
@@ -197,6 +200,8 @@ pub trait System: Debug {
|
||||
fn as_any(&self) -> &dyn std::any::Any;
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
|
||||
|
||||
fn dyn_clone(&self) -> Box<dyn System>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
@@ -240,7 +245,7 @@ pub trait WritableSystem: System {
|
||||
fn create_new_file(&self, path: &SystemPath) -> Result<()>;
|
||||
|
||||
/// Writes the given content to the file at the given path.
|
||||
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()>;
|
||||
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()>;
|
||||
|
||||
/// Creates a directory at `path` as well as any intermediate directories.
|
||||
fn create_directory_all(&self, path: &SystemPath) -> Result<()>;
|
||||
@@ -276,7 +281,7 @@ pub trait WritableSystem: System {
|
||||
// ensures that only one thread/process ever attempts to write to it to avoid corrupting
|
||||
// the cache.
|
||||
self.create_new_file(&cache_path)?;
|
||||
self.write_file(&cache_path, &contents)?;
|
||||
self.write_file(&cache_path, contents.as_bytes())?;
|
||||
|
||||
Ok(Some(cache_path))
|
||||
}
|
||||
|
||||
@@ -114,8 +114,8 @@ impl MemoryFileSystem {
|
||||
matches!(by_path.get(&normalized), Some(Entry::Directory(_)))
|
||||
}
|
||||
|
||||
pub fn read_to_string(&self, path: impl AsRef<SystemPath>) -> Result<String> {
|
||||
fn read_to_string(fs: &MemoryFileSystem, path: &SystemPath) -> Result<String> {
|
||||
pub fn read_to_end(&self, path: impl AsRef<SystemPath>) -> Result<Vec<u8>> {
|
||||
fn read_to_end(fs: &MemoryFileSystem, path: &SystemPath) -> Result<Vec<u8>> {
|
||||
let by_path = fs.inner.by_path.read().unwrap();
|
||||
let normalized = fs.normalize_path(path);
|
||||
|
||||
@@ -127,13 +127,18 @@ impl MemoryFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
read_to_string(self, path.as_ref())
|
||||
read_to_end(self, path.as_ref())
|
||||
}
|
||||
|
||||
pub(crate) fn read_virtual_path_to_string(
|
||||
pub fn read_to_string(&self, path: impl AsRef<SystemPath>) -> Result<String> {
|
||||
self.read_to_end(path)
|
||||
.and_then(|bytes| String::from_utf8(bytes).map_err(io::Error::other))
|
||||
}
|
||||
|
||||
pub(crate) fn read_virtual_path_to_end(
|
||||
&self,
|
||||
path: impl AsRef<SystemVirtualPath>,
|
||||
) -> Result<String> {
|
||||
) -> Result<Vec<u8>> {
|
||||
let virtual_files = self.inner.virtual_files.read().unwrap();
|
||||
let file = virtual_files
|
||||
.get(&path.as_ref().to_path_buf())
|
||||
@@ -142,6 +147,14 @@ impl MemoryFileSystem {
|
||||
Ok(file.content.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn read_virtual_path_to_string(
|
||||
&self,
|
||||
path: impl AsRef<SystemVirtualPath>,
|
||||
) -> Result<String> {
|
||||
self.read_virtual_path_to_end(path)
|
||||
.and_then(|bytes| String::from_utf8(bytes).map_err(io::Error::other))
|
||||
}
|
||||
|
||||
pub fn exists(&self, path: &SystemPath) -> bool {
|
||||
let by_path = self.inner.by_path.read().unwrap();
|
||||
let normalized = self.normalize_path(path);
|
||||
@@ -161,7 +174,7 @@ impl MemoryFileSystem {
|
||||
match by_path.entry(normalized) {
|
||||
btree_map::Entry::Vacant(entry) => {
|
||||
entry.insert(Entry::File(File {
|
||||
content: String::new(),
|
||||
content: Vec::new(),
|
||||
last_modified: file_time_now(),
|
||||
}));
|
||||
|
||||
@@ -177,13 +190,17 @@ impl MemoryFileSystem {
|
||||
/// Stores a new file in the file system.
|
||||
///
|
||||
/// The operation overrides the content for an existing file with the same normalized `path`.
|
||||
pub fn write_file(&self, path: impl AsRef<SystemPath>, content: impl ToString) -> Result<()> {
|
||||
pub fn write_file(
|
||||
&self,
|
||||
path: impl AsRef<SystemPath>,
|
||||
content: impl Into<Vec<u8>>,
|
||||
) -> Result<()> {
|
||||
let mut by_path = self.inner.by_path.write().unwrap();
|
||||
|
||||
let normalized = self.normalize_path(path.as_ref());
|
||||
|
||||
let file = get_or_create_file(&mut by_path, &normalized)?;
|
||||
file.content = content.to_string();
|
||||
file.content = content.into();
|
||||
file.last_modified = file_time_now();
|
||||
|
||||
Ok(())
|
||||
@@ -214,7 +231,7 @@ impl MemoryFileSystem {
|
||||
pub fn write_file_all(
|
||||
&self,
|
||||
path: impl AsRef<SystemPath>,
|
||||
content: impl ToString,
|
||||
content: impl Into<Vec<u8>>,
|
||||
) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
|
||||
@@ -228,19 +245,23 @@ impl MemoryFileSystem {
|
||||
/// Stores a new virtual file in the file system.
|
||||
///
|
||||
/// The operation overrides the content for an existing virtual file with the same `path`.
|
||||
pub fn write_virtual_file(&self, path: impl AsRef<SystemVirtualPath>, content: impl ToString) {
|
||||
pub fn write_virtual_file(
|
||||
&self,
|
||||
path: impl AsRef<SystemVirtualPath>,
|
||||
content: impl Into<Vec<u8>>,
|
||||
) {
|
||||
let path = path.as_ref();
|
||||
let mut virtual_files = self.inner.virtual_files.write().unwrap();
|
||||
|
||||
match virtual_files.entry(path.to_path_buf()) {
|
||||
std::collections::hash_map::Entry::Vacant(entry) => {
|
||||
entry.insert(File {
|
||||
content: content.to_string(),
|
||||
content: content.into(),
|
||||
last_modified: file_time_now(),
|
||||
});
|
||||
}
|
||||
std::collections::hash_map::Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().content = content.to_string();
|
||||
entry.get_mut().content = content.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -468,7 +489,7 @@ impl Entry {
|
||||
|
||||
#[derive(Debug)]
|
||||
struct File {
|
||||
content: String,
|
||||
content: Vec<u8>,
|
||||
last_modified: FileTime,
|
||||
}
|
||||
|
||||
@@ -533,7 +554,7 @@ fn get_or_create_file<'a>(
|
||||
|
||||
let entry = paths.entry(normalized.to_path_buf()).or_insert_with(|| {
|
||||
Entry::File(File {
|
||||
content: String::new(),
|
||||
content: Vec::new(),
|
||||
last_modified: file_time_now(),
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::disallowed_methods)]
|
||||
|
||||
use super::walk_directory::{
|
||||
self, DirectoryWalker, WalkDirectoryBuilder, WalkDirectoryConfiguration,
|
||||
WalkDirectoryVisitorBuilder, WalkState,
|
||||
@@ -91,6 +93,10 @@ impl System for OsSystem {
|
||||
})
|
||||
}
|
||||
|
||||
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
|
||||
std::fs::read(path.as_std_path())
|
||||
}
|
||||
|
||||
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
|
||||
std::fs::read_to_string(path.as_std_path())
|
||||
}
|
||||
@@ -255,6 +261,10 @@ impl System for OsSystem {
|
||||
fn env_var(&self, name: &str) -> std::result::Result<String, std::env::VarError> {
|
||||
std::env::var(name)
|
||||
}
|
||||
|
||||
fn dyn_clone(&self) -> Box<dyn System> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl OsSystem {
|
||||
@@ -351,7 +361,7 @@ impl WritableSystem for OsSystem {
|
||||
std::fs::File::create_new(path).map(drop)
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
|
||||
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
|
||||
std::fs::write(path.as_std_path(), content)
|
||||
}
|
||||
|
||||
|
||||
@@ -762,7 +762,17 @@ impl SystemVirtualPath {
|
||||
}
|
||||
|
||||
/// An owned, virtual path on [`System`](`super::System`) (akin to [`String`]).
|
||||
#[derive(Eq, PartialEq, Clone, Hash, PartialOrd, Ord, get_size2::GetSize)]
|
||||
#[derive(
|
||||
Eq,
|
||||
PartialEq,
|
||||
Clone,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
get_size2::GetSize,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
)]
|
||||
pub struct SystemVirtualPathBuf(String);
|
||||
|
||||
impl SystemVirtualPathBuf {
|
||||
|
||||
@@ -75,6 +75,10 @@ impl System for TestSystem {
|
||||
self.system().canonicalize_path(path)
|
||||
}
|
||||
|
||||
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
|
||||
self.system().read_to_end(path)
|
||||
}
|
||||
|
||||
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
|
||||
self.system().read_to_string(path)
|
||||
}
|
||||
@@ -146,6 +150,10 @@ impl System for TestSystem {
|
||||
fn case_sensitivity(&self) -> CaseSensitivity {
|
||||
self.system().case_sensitivity()
|
||||
}
|
||||
|
||||
fn dyn_clone(&self) -> Box<dyn System> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TestSystem {
|
||||
@@ -161,7 +169,7 @@ impl WritableSystem for TestSystem {
|
||||
self.system().create_new_file(path)
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
|
||||
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
|
||||
self.system().write_file(path, content)
|
||||
}
|
||||
|
||||
@@ -181,7 +189,9 @@ pub trait DbWithWritableSystem: Db + Sized {
|
||||
/// Writes the content of the given file and notifies the Db about the change.
|
||||
fn write_file(&mut self, path: impl AsRef<SystemPath>, content: impl AsRef<str>) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
match self.writable_system().write_file(path, content.as_ref()) {
|
||||
let content = content.as_ref();
|
||||
|
||||
match self.writable_system().write_file(path, content.as_bytes()) {
|
||||
Ok(()) => {
|
||||
File::sync_path(self, path);
|
||||
Ok(())
|
||||
@@ -194,7 +204,8 @@ pub trait DbWithWritableSystem: Db + Sized {
|
||||
File::sync_path(self, ancestor);
|
||||
}
|
||||
|
||||
self.writable_system().write_file(path, content.as_ref())?;
|
||||
self.writable_system()
|
||||
.write_file(path, content.as_bytes())?;
|
||||
File::sync_path(self, path);
|
||||
|
||||
Ok(())
|
||||
@@ -239,8 +250,14 @@ pub trait DbWithTestSystem: Db + Sized {
|
||||
///
|
||||
/// ## Panics
|
||||
/// If the db isn't using the [`InMemorySystem`].
|
||||
fn write_virtual_file(&mut self, path: impl AsRef<SystemVirtualPath>, content: impl ToString) {
|
||||
fn write_virtual_file(
|
||||
&mut self,
|
||||
path: impl AsRef<SystemVirtualPath>,
|
||||
content: impl Into<Vec<u8>>,
|
||||
) {
|
||||
let path = path.as_ref();
|
||||
let content = content.into();
|
||||
|
||||
self.test_system()
|
||||
.memory_file_system()
|
||||
.write_virtual_file(path, content);
|
||||
@@ -318,6 +335,10 @@ impl System for InMemorySystem {
|
||||
self.memory_fs.canonicalize(path)
|
||||
}
|
||||
|
||||
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
|
||||
self.memory_fs.read_to_end(path)
|
||||
}
|
||||
|
||||
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
|
||||
self.memory_fs.read_to_string(path)
|
||||
}
|
||||
@@ -394,6 +415,13 @@ impl System for InMemorySystem {
|
||||
fn case_sensitivity(&self) -> CaseSensitivity {
|
||||
CaseSensitivity::CaseSensitive
|
||||
}
|
||||
|
||||
fn dyn_clone(&self) -> Box<dyn System> {
|
||||
Box::new(Self {
|
||||
user_config_directory: Mutex::new(self.user_config_directory.lock().unwrap().clone()),
|
||||
memory_fs: self.memory_fs.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WritableSystem for InMemorySystem {
|
||||
@@ -401,7 +429,7 @@ impl WritableSystem for InMemorySystem {
|
||||
self.memory_fs.create_new_file(path)
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
|
||||
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
|
||||
self.memory_fs.write_file(path, content)
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ impl ToOwned for VendoredPath {
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub struct VendoredPathBuf(Utf8PathBuf);
|
||||
|
||||
impl get_size2::GetSize for VendoredPathBuf {
|
||||
|
||||
@@ -13,3 +13,11 @@ Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
||||
fd = os.open(".", os.O_RDONLY)
|
||||
os.symlink("source.txt", "link.txt", dir_fd=fd) # Ok: dir_fd is not supported by pathlib
|
||||
os.close(fd)
|
||||
|
||||
os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
|
||||
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
||||
|
||||
os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
|
||||
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
||||
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
||||
|
||||
@@ -118,3 +118,10 @@ def func():
|
||||
return lambda: value
|
||||
|
||||
defaultdict(constant_factory("<missing>"))
|
||||
|
||||
def func():
|
||||
defaultdict(default_factory=t"") # OK
|
||||
|
||||
|
||||
def func():
|
||||
defaultdict(default_factory=t"hello") # OK
|
||||
|
||||
@@ -102,3 +102,8 @@ deque("abc") # OK
|
||||
deque(b"abc") # OK
|
||||
deque(f"" "a") # OK
|
||||
deque(f"{x}" "") # OK
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19951
|
||||
deque(t"")
|
||||
deque(t"" t"")
|
||||
deque(t"{""}") # OK
|
||||
|
||||
@@ -1124,6 +1124,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::OsMakedirs) {
|
||||
flake8_use_pathlib::rules::os_makedirs(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsSymlink) {
|
||||
flake8_use_pathlib::rules::os_symlink(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(
|
||||
checker, call, segments,
|
||||
|
||||
@@ -954,7 +954,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8UsePathlib, "207") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::Glob),
|
||||
(Flake8UsePathlib, "208") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsListdir),
|
||||
(Flake8UsePathlib, "210") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::InvalidPathlibWithSuffix),
|
||||
(Flake8UsePathlib, "211") => (RuleGroup::Preview, rules::flake8_use_pathlib::violations::OsSymlink),
|
||||
(Flake8UsePathlib, "211") => (RuleGroup::Preview, rules::flake8_use_pathlib::rules::OsSymlink),
|
||||
|
||||
// flake8-logging-format
|
||||
(Flake8LoggingFormat, "001") => (RuleGroup::Stable, rules::flake8_logging_format::violations::LoggingStringFormat),
|
||||
|
||||
@@ -169,6 +169,11 @@ pub(crate) const fn is_fix_os_makedirs_enabled(settings: &LinterSettings) -> boo
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/20009
|
||||
pub(crate) const fn is_fix_os_symlink_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/11436
|
||||
// https://github.com/astral-sh/ruff/pull/11168
|
||||
pub(crate) const fn is_dunder_init_fix_unused_import_enabled(settings: &LinterSettings) -> bool {
|
||||
|
||||
@@ -137,7 +137,7 @@ impl AutoPythonType {
|
||||
let expr = Expr::Name(ast::ExprName {
|
||||
id: Name::from(binding),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
});
|
||||
Some((expr, vec![no_return_edit]))
|
||||
@@ -204,7 +204,7 @@ fn type_expr(python_type: PythonType) -> Option<Expr> {
|
||||
Expr::Name(ast::ExprName {
|
||||
id: name.into(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -52,13 +52,13 @@ impl AlwaysFixableViolation for AssertFalse {
|
||||
fn assertion_error(msg: Option<&Expr>) -> Stmt {
|
||||
Stmt::Raise(ast::StmtRaise {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
exc: Some(Box::new(Expr::Call(ast::ExprCall {
|
||||
func: Box::new(Expr::Name(ast::ExprName {
|
||||
id: "AssertionError".into(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})),
|
||||
arguments: Arguments {
|
||||
args: if let Some(msg) = msg {
|
||||
@@ -68,10 +68,10 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
|
||||
},
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}))),
|
||||
cause: None,
|
||||
})
|
||||
|
||||
@@ -113,7 +113,7 @@ fn type_pattern(elts: Vec<&Expr>) -> Expr {
|
||||
elts: elts.into_iter().cloned().collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
}
|
||||
.into()
|
||||
|
||||
@@ -53,11 +53,11 @@ fn assignment(obj: &Expr, name: &str, value: &Expr, generator: Generator) -> Str
|
||||
attr: Identifier::new(name.to_string(), TextRange::default()),
|
||||
ctx: ExprContext::Store,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})],
|
||||
value: Box::new(value.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
generator.stmt(&stmt)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ mod tests {
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_diagnostics, settings};
|
||||
use crate::{assert_diagnostics, assert_diagnostics_diff, settings};
|
||||
|
||||
#[test_case(Path::new("COM81.py"))]
|
||||
#[test_case(Path::new("COM81_syntax_error.py"))]
|
||||
@@ -31,19 +31,24 @@ mod tests {
|
||||
#[test_case(Path::new("COM81.py"))]
|
||||
#[test_case(Path::new("COM81_syntax_error.py"))]
|
||||
fn preview_rules(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("preview__{}", path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
let snapshot = format!("preview_diff__{}", path.to_string_lossy());
|
||||
let rules = vec![
|
||||
Rule::MissingTrailingComma,
|
||||
Rule::TrailingCommaOnBareTuple,
|
||||
Rule::ProhibitedTrailingComma,
|
||||
];
|
||||
let settings_before = settings::LinterSettings::for_rules(rules.clone());
|
||||
let settings_after = settings::LinterSettings {
|
||||
preview: crate::settings::types::PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rules(rules)
|
||||
};
|
||||
|
||||
assert_diagnostics_diff!(
|
||||
snapshot,
|
||||
Path::new("flake8_commas").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: crate::settings::types::PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rules(vec![
|
||||
Rule::MissingTrailingComma,
|
||||
Rule::TrailingCommaOnBareTuple,
|
||||
Rule::ProhibitedTrailingComma,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
&settings_before,
|
||||
&settings_after
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,33 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
|
||||
---
|
||||
invalid-syntax: Starred expression cannot be used here
|
||||
--> COM81_syntax_error.py:3:5
|
||||
|
|
||||
1 | # Check for `flake8-commas` violation for a file containing syntax errors.
|
||||
2 | (
|
||||
3 | *args
|
||||
| ^^^^^
|
||||
4 | )
|
||||
|
|
||||
|
||||
invalid-syntax: Type parameter list cannot be empty
|
||||
--> COM81_syntax_error.py:6:9
|
||||
|
|
||||
4 | )
|
||||
5 |
|
||||
6 | def foo[(param1='test', param2='test',):
|
||||
| ^
|
||||
7 | pass
|
||||
|
|
||||
|
||||
COM819 Trailing comma prohibited
|
||||
--> COM81_syntax_error.py:6:38
|
||||
|
|
||||
4 | )
|
||||
5 |
|
||||
6 | def foo[(param1='test', param2='test',):
|
||||
| ^
|
||||
7 | pass
|
||||
|
|
||||
help: Remove trailing comma
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 6
|
||||
|
||||
--- Added ---
|
||||
COM812 [*] Trailing comma missing
|
||||
--> COM81.py:655:6
|
||||
|
|
||||
654 | type X[
|
||||
655 | T
|
||||
| ^
|
||||
656 | ] = T
|
||||
657 | def f[
|
||||
|
|
||||
help: Add trailing comma
|
||||
|
||||
ℹ Safe fix
|
||||
652 652 | }"""
|
||||
653 653 |
|
||||
654 654 | type X[
|
||||
655 |- T
|
||||
655 |+ T,
|
||||
656 656 | ] = T
|
||||
657 657 | def f[
|
||||
658 658 | T
|
||||
|
||||
|
||||
COM812 [*] Trailing comma missing
|
||||
--> COM81.py:658:6
|
||||
|
|
||||
656 | ] = T
|
||||
657 | def f[
|
||||
658 | T
|
||||
| ^
|
||||
659 | ](): pass
|
||||
660 | class C[
|
||||
|
|
||||
help: Add trailing comma
|
||||
|
||||
ℹ Safe fix
|
||||
655 655 | T
|
||||
656 656 | ] = T
|
||||
657 657 | def f[
|
||||
658 |- T
|
||||
658 |+ T,
|
||||
659 659 | ](): pass
|
||||
660 660 | class C[
|
||||
661 661 | T
|
||||
|
||||
|
||||
COM812 [*] Trailing comma missing
|
||||
--> COM81.py:661:6
|
||||
|
|
||||
659 | ](): pass
|
||||
660 | class C[
|
||||
661 | T
|
||||
| ^
|
||||
662 | ]: pass
|
||||
|
|
||||
help: Add trailing comma
|
||||
|
||||
ℹ Safe fix
|
||||
658 658 | T
|
||||
659 659 | ](): pass
|
||||
660 660 | class C[
|
||||
661 |- T
|
||||
661 |+ T,
|
||||
662 662 | ]: pass
|
||||
663 663 |
|
||||
664 664 | type X[T,] = T
|
||||
|
||||
|
||||
COM819 [*] Trailing comma prohibited
|
||||
--> COM81.py:664:9
|
||||
|
|
||||
662 | ]: pass
|
||||
663 |
|
||||
664 | type X[T,] = T
|
||||
| ^
|
||||
665 | def f[T,](): pass
|
||||
666 | class C[T,]: pass
|
||||
|
|
||||
help: Remove trailing comma
|
||||
|
||||
ℹ Safe fix
|
||||
661 661 | T
|
||||
662 662 | ]: pass
|
||||
663 663 |
|
||||
664 |-type X[T,] = T
|
||||
664 |+type X[T] = T
|
||||
665 665 | def f[T,](): pass
|
||||
666 666 | class C[T,]: pass
|
||||
|
||||
|
||||
COM819 [*] Trailing comma prohibited
|
||||
--> COM81.py:665:8
|
||||
|
|
||||
664 | type X[T,] = T
|
||||
665 | def f[T,](): pass
|
||||
| ^
|
||||
666 | class C[T,]: pass
|
||||
|
|
||||
help: Remove trailing comma
|
||||
|
||||
ℹ Safe fix
|
||||
662 662 | ]: pass
|
||||
663 663 |
|
||||
664 664 | type X[T,] = T
|
||||
665 |-def f[T,](): pass
|
||||
665 |+def f[T](): pass
|
||||
666 666 | class C[T,]: pass
|
||||
|
||||
|
||||
COM819 [*] Trailing comma prohibited
|
||||
--> COM81.py:666:10
|
||||
|
|
||||
664 | type X[T,] = T
|
||||
665 | def f[T,](): pass
|
||||
666 | class C[T,]: pass
|
||||
| ^
|
||||
|
|
||||
help: Remove trailing comma
|
||||
|
||||
ℹ Safe fix
|
||||
663 663 |
|
||||
664 664 | type X[T,] = T
|
||||
665 665 | def f[T,](): pass
|
||||
666 |-class C[T,]: pass
|
||||
666 |+class C[T]: pass
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 0
|
||||
@@ -209,18 +209,18 @@ fn fix_unnecessary_dict_comprehension(value: &Expr, generator: &Comprehension) -
|
||||
},
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
Expr::Call(ExprCall {
|
||||
func: Box::new(Expr::Name(ExprName {
|
||||
id: "dict.fromkeys".into(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})),
|
||||
arguments: args,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -178,21 +178,21 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
});
|
||||
let node1 = Expr::Name(ast::ExprName {
|
||||
id: arg_name.into(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
let node2 = Expr::Attribute(ast::ExprAttribute {
|
||||
value: Box::new(node1),
|
||||
attr: Identifier::new(attr_name.to_string(), TextRange::default()),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
let node3 = Expr::Call(ast::ExprCall {
|
||||
func: Box::new(node2),
|
||||
@@ -200,10 +200,10 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
|
||||
args: Box::from([node]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
let call = node3;
|
||||
|
||||
@@ -223,7 +223,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
|
||||
})
|
||||
.collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
let bool_op = node;
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
|
||||
@@ -92,14 +92,14 @@ pub(crate) fn duplicate_literal_member<'a>(checker: &Checker, expr: &'a Expr) {
|
||||
Expr::Tuple(ast::ExprTuple {
|
||||
elts: unique_nodes.into_iter().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: false,
|
||||
})
|
||||
}),
|
||||
value: subscript.value.clone(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
});
|
||||
let fix = Fix::applicable_edit(
|
||||
|
||||
@@ -187,7 +187,7 @@ fn generate_pep604_fix(
|
||||
op: Operator::BitOr,
|
||||
right: Box::new(right.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}))
|
||||
} else {
|
||||
Some(right.clone())
|
||||
@@ -202,7 +202,7 @@ fn generate_pep604_fix(
|
||||
}
|
||||
|
||||
static VIRTUAL_NONE_LITERAL: Expr = Expr::NoneLiteral(ExprNoneLiteral {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
range: TextRange::new(TextSize::new(0), TextSize::new(0)),
|
||||
});
|
||||
|
||||
|
||||
@@ -133,17 +133,17 @@ fn generate_union_fix(
|
||||
// Construct the expression as `Subscript[typing.Union, Tuple[expr, [expr, ...]]]`
|
||||
let new_expr = Expr::Subscript(ExprSubscript {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
value: Box::new(Expr::Name(ExprName {
|
||||
id: Name::new(binding),
|
||||
ctx: ExprContext::Store,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})),
|
||||
slice: Box::new(Expr::Tuple(ExprTuple {
|
||||
elts: nodes.into_iter().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: false,
|
||||
})),
|
||||
|
||||
@@ -205,13 +205,13 @@ fn create_fix(
|
||||
let new_literal_expr = Expr::Subscript(ast::ExprSubscript {
|
||||
value: Box::new(literal_subscript.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
slice: Box::new(if literal_elements.len() > 1 {
|
||||
Expr::Tuple(ast::ExprTuple {
|
||||
elts: literal_elements.into_iter().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: true,
|
||||
})
|
||||
@@ -235,7 +235,7 @@ fn create_fix(
|
||||
UnionKind::BitOr => {
|
||||
let none_expr = Expr::NoneLiteral(ExprNoneLiteral {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
let union_expr = pep_604_union(&[new_literal_expr, none_expr]);
|
||||
let content = checker.generator().expr(&union_expr);
|
||||
|
||||
@@ -261,7 +261,7 @@ fn generate_pep604_fix(
|
||||
op: Operator::BitOr,
|
||||
right: Box::new(right.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}))
|
||||
} else {
|
||||
Some(right.clone())
|
||||
|
||||
@@ -140,12 +140,12 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &Checker, expr: &'a Expr) {
|
||||
slice: Box::new(Expr::Tuple(ast::ExprTuple {
|
||||
elts: literal_exprs.into_iter().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: true,
|
||||
})),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
});
|
||||
|
||||
@@ -164,12 +164,12 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &Checker, expr: &'a Expr) {
|
||||
slice: Box::new(Expr::Tuple(ast::ExprTuple {
|
||||
elts,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: true,
|
||||
})),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
}))
|
||||
} else {
|
||||
|
||||
@@ -134,12 +134,12 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) {
|
||||
id: Name::new_static("type"),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})),
|
||||
slice: Box::new(pep_604_union(&elts)),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
|
||||
if other_exprs.is_empty() {
|
||||
@@ -159,7 +159,7 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) {
|
||||
id: Name::new_static("type"),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})),
|
||||
slice: Box::new(Expr::Subscript(ast::ExprSubscript {
|
||||
value: subscript.value.clone(),
|
||||
@@ -171,22 +171,22 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) {
|
||||
id: type_member,
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
})),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
|
||||
if other_exprs.is_empty() {
|
||||
@@ -202,12 +202,12 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) {
|
||||
elts: exprs.into_iter().cloned().collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
})),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
|
||||
checker.generator().expr(&union)
|
||||
|
||||
@@ -301,7 +301,7 @@ fn elts_to_csv(elts: &[Expr], generator: Generator, flags: StringLiteralFlags) -
|
||||
})
|
||||
.into_boxed_str(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
flags,
|
||||
});
|
||||
Some(generator.expr(&node))
|
||||
@@ -367,14 +367,14 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
|
||||
Expr::from(ast::StringLiteral {
|
||||
value: Box::from(*name),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
flags: checker.default_string_flags(),
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
});
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
@@ -404,14 +404,14 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
|
||||
Expr::from(ast::StringLiteral {
|
||||
value: Box::from(*name),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
flags: checker.default_string_flags(),
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
checker.generator().expr(&node),
|
||||
@@ -440,7 +440,7 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
|
||||
elts: elts.clone(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
checker.generator().expr(&node),
|
||||
@@ -485,7 +485,7 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
|
||||
elts: elts.clone(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
});
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
|
||||
@@ -166,7 +166,7 @@ fn assert(expr: &Expr, msg: Option<&Expr>) -> Stmt {
|
||||
test: Box::new(expr.clone()),
|
||||
msg: msg.map(|msg| Box::new(msg.clone())),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ fn compare(left: &Expr, cmp_op: CmpOp, right: &Expr) -> Expr {
|
||||
ops: Box::from([cmp_op]),
|
||||
comparators: Box::from([right.clone()]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ impl UnittestAssert {
|
||||
op: UnaryOp::Not,
|
||||
operand: Box::new(expr.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}),
|
||||
msg,
|
||||
)
|
||||
@@ -370,7 +370,7 @@ impl UnittestAssert {
|
||||
};
|
||||
let node = Expr::NoneLiteral(ast::ExprNoneLiteral {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
let expr = compare(expr, cmp_op, &node);
|
||||
Ok(assert(&expr, msg))
|
||||
@@ -387,7 +387,7 @@ impl UnittestAssert {
|
||||
id: Name::new_static("isinstance"),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let node1 = ast::ExprCall {
|
||||
func: Box::new(node.into()),
|
||||
@@ -395,10 +395,10 @@ impl UnittestAssert {
|
||||
args: Box::from([(**obj).clone(), (**cls).clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let isinstance = node1.into();
|
||||
if matches!(self, UnittestAssert::IsInstance) {
|
||||
@@ -408,7 +408,7 @@ impl UnittestAssert {
|
||||
op: UnaryOp::Not,
|
||||
operand: Box::new(isinstance),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let expr = node.into();
|
||||
Ok(assert(&expr, msg))
|
||||
@@ -429,14 +429,14 @@ impl UnittestAssert {
|
||||
id: Name::new_static("re"),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let node1 = ast::ExprAttribute {
|
||||
value: Box::new(node.into()),
|
||||
attr: Identifier::new("search".to_string(), TextRange::default()),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let node2 = ast::ExprCall {
|
||||
func: Box::new(node1.into()),
|
||||
@@ -444,10 +444,10 @@ impl UnittestAssert {
|
||||
args: Box::from([(**regex).clone(), (**text).clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let re_search = node2.into();
|
||||
if matches!(self, UnittestAssert::Regex | UnittestAssert::RegexpMatches) {
|
||||
@@ -457,7 +457,7 @@ impl UnittestAssert {
|
||||
op: UnaryOp::Not,
|
||||
operand: Box::new(re_search),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
Ok(assert(&node.into(), msg))
|
||||
}
|
||||
|
||||
@@ -421,7 +421,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) {
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
};
|
||||
let isinstance_call = ast::ExprCall {
|
||||
@@ -430,7 +430,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) {
|
||||
id: Name::new_static("isinstance"),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
@@ -438,10 +438,10 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) {
|
||||
args: Box::from([target.clone(), tuple.into()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into();
|
||||
|
||||
@@ -458,7 +458,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) {
|
||||
.chain(after)
|
||||
.collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into();
|
||||
let fixed_source = checker.generator().expr(&bool_op);
|
||||
@@ -552,21 +552,21 @@ pub(crate) fn compare_with_tuple(checker: &Checker, expr: &Expr) {
|
||||
elts: comparators.into_iter().cloned().collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
};
|
||||
let node1 = ast::ExprName {
|
||||
id: id.clone(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let node2 = ast::ExprCompare {
|
||||
left: Box::new(node1.into()),
|
||||
ops: Box::from([CmpOp::In]),
|
||||
comparators: Box::from([node.into()]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let in_expr = node2.into();
|
||||
let mut diagnostic = checker.report_diagnostic(
|
||||
@@ -589,7 +589,7 @@ pub(crate) fn compare_with_tuple(checker: &Checker, expr: &Expr) {
|
||||
op: BoolOp::Or,
|
||||
values: iter::once(in_expr).chain(unmatched).collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
node.into()
|
||||
};
|
||||
|
||||
@@ -232,7 +232,7 @@ fn check_os_environ_subscript(checker: &Checker, expr: &Expr) {
|
||||
}
|
||||
}),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let new_env_var = node.into();
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
|
||||
@@ -188,7 +188,7 @@ pub(crate) fn if_expr_with_true_false(
|
||||
id: Name::new_static("bool"),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
@@ -196,10 +196,10 @@ pub(crate) fn if_expr_with_true_false(
|
||||
args: Box::from([test.clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
@@ -227,7 +227,7 @@ pub(crate) fn if_expr_with_false_true(
|
||||
op: UnaryOp::Not,
|
||||
operand: Box::new(test.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
@@ -282,7 +282,7 @@ pub(crate) fn twisted_arms_in_ifexpr(
|
||||
body: Box::new(node1),
|
||||
orelse: Box::new(node),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
checker.generator().expr(&node3.into()),
|
||||
|
||||
@@ -186,7 +186,7 @@ pub(crate) fn negation_with_equal_op(checker: &Checker, expr: &Expr, op: UnaryOp
|
||||
ops: Box::from([CmpOp::NotEq]),
|
||||
comparators: comparators.clone(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
checker.generator().expr(&node.into()),
|
||||
@@ -242,7 +242,7 @@ pub(crate) fn negation_with_not_equal_op(
|
||||
ops: Box::from([CmpOp::Eq]),
|
||||
comparators: comparators.clone(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
checker.generator().expr(&node.into()),
|
||||
@@ -284,7 +284,7 @@ pub(crate) fn double_negation(checker: &Checker, expr: &Expr, op: UnaryOp, opera
|
||||
id: Name::new_static("bool"),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let node1 = ast::ExprCall {
|
||||
func: Box::new(node.into()),
|
||||
@@ -292,10 +292,10 @@ pub(crate) fn double_negation(checker: &Checker, expr: &Expr, op: UnaryOp, opera
|
||||
args: Box::from([*operand.clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
checker.generator().expr(&node1.into()),
|
||||
|
||||
@@ -190,7 +190,7 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &Checker, stmt_if: &ast
|
||||
attr: Identifier::new("get".to_string(), TextRange::default()),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let node3 = ast::ExprCall {
|
||||
func: Box::new(node2.into()),
|
||||
@@ -198,17 +198,17 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &Checker, stmt_if: &ast
|
||||
args: Box::from([node1, node]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let node4 = expected_var.clone();
|
||||
let node5 = ast::StmtAssign {
|
||||
targets: vec![node4],
|
||||
value: Box::new(node3.into()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let contents = checker.generator().stmt(&node5.into());
|
||||
|
||||
@@ -299,7 +299,7 @@ pub(crate) fn if_exp_instead_of_dict_get(
|
||||
attr: Identifier::new("get".to_string(), TextRange::default()),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let fixed_node = ast::ExprCall {
|
||||
func: Box::new(dict_get_node.into()),
|
||||
@@ -307,10 +307,10 @@ pub(crate) fn if_exp_instead_of_dict_get(
|
||||
args: Box::from([dict_key_node, default_value_node]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
|
||||
let contents = checker.generator().expr(&fixed_node.into());
|
||||
|
||||
@@ -266,13 +266,13 @@ fn assignment_ternary(
|
||||
body: Box::new(body_value.clone()),
|
||||
orelse: Box::new(orelse_value.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let node1 = ast::StmtAssign {
|
||||
targets: vec![target_var.clone()],
|
||||
value: Box::new(node.into()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
node1.into()
|
||||
}
|
||||
@@ -282,13 +282,13 @@ fn assignment_binary_and(target_var: &Expr, left_value: &Expr, right_value: &Exp
|
||||
op: BoolOp::And,
|
||||
values: vec![left_value.clone(), right_value.clone()],
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let node1 = ast::StmtAssign {
|
||||
targets: vec![target_var.clone()],
|
||||
value: Box::new(node.into()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
node1.into()
|
||||
}
|
||||
@@ -296,12 +296,12 @@ fn assignment_binary_and(target_var: &Expr, left_value: &Expr, right_value: &Exp
|
||||
fn assignment_binary_or(target_var: &Expr, left_value: &Expr, right_value: &Expr) -> Stmt {
|
||||
(ast::StmtAssign {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
targets: vec![target_var.clone()],
|
||||
value: Box::new(
|
||||
(ast::ExprBoolOp {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
op: BoolOp::Or,
|
||||
values: vec![left_value.clone(), right_value.clone()],
|
||||
})
|
||||
|
||||
@@ -256,7 +256,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
|
||||
left: left.clone(),
|
||||
comparators: Box::new([right.clone()]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
|
||||
op: ast::UnaryOp::Not,
|
||||
operand: Box::new(if_test.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})),
|
||||
}
|
||||
} else if if_test.is_compare_expr() {
|
||||
@@ -277,7 +277,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
|
||||
id: Name::new_static("bool"),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let call_node = ast::ExprCall {
|
||||
func: Box::new(func_node.into()),
|
||||
@@ -285,10 +285,10 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
|
||||
args: Box::from([if_test.clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
Some(Expr::Call(call_node))
|
||||
} else {
|
||||
@@ -301,7 +301,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
|
||||
Stmt::Return(ast::StmtReturn {
|
||||
value: Some(Box::new(expr.clone())),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) {
|
||||
ops: Box::from([op]),
|
||||
comparators: Box::from([comparator.clone()]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
node.into()
|
||||
} else {
|
||||
@@ -173,7 +173,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) {
|
||||
op: UnaryOp::Not,
|
||||
operand: Box::new(loop_.test.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
node.into()
|
||||
}
|
||||
@@ -182,7 +182,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) {
|
||||
op: UnaryOp::Not,
|
||||
operand: Box::new(loop_.test.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
node.into()
|
||||
}
|
||||
@@ -406,17 +406,17 @@ fn return_stmt(id: Name, test: &Expr, target: &Expr, iter: &Expr, generator: Gen
|
||||
ifs: vec![],
|
||||
is_async: false,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}],
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: false,
|
||||
};
|
||||
let node1 = ast::ExprName {
|
||||
id,
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let node2 = ast::ExprCall {
|
||||
func: Box::new(node1.into()),
|
||||
@@ -424,15 +424,15 @@ fn return_stmt(id: Name, test: &Expr, target: &Expr, iter: &Expr, generator: Gen
|
||||
args: Box::from([node.into()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let node3 = ast::StmtReturn {
|
||||
value: Some(Box::new(node2.into())),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
generator.stmt(&node3.into())
|
||||
}
|
||||
|
||||
@@ -163,14 +163,14 @@ fn construct_replacement(elts: &[&str], flags: StringLiteralFlags) -> Expr {
|
||||
Expr::from(StringLiteral {
|
||||
value: Box::from(*elt),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
flags: element_flags,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ fn fix_banned_relative_import(
|
||||
names: names.clone(),
|
||||
level: 0,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let content = generator.stmt(&node.into());
|
||||
Some(Fix::unsafe_edit(Edit::range_replacement(
|
||||
|
||||
@@ -436,7 +436,7 @@ impl<'a> QuoteAnnotator<'a> {
|
||||
let annotation = subgenerator.expr(&expr_without_forward_references);
|
||||
generator.expr(&Expr::from(ast::StringLiteral {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
value: annotation.into_boxed_str(),
|
||||
flags: self.flags,
|
||||
}))
|
||||
|
||||
@@ -129,6 +129,7 @@ mod tests {
|
||||
#[test_case(Rule::OsPathGetatime, Path::new("PTH203.py"))]
|
||||
#[test_case(Rule::OsPathGetmtime, Path::new("PTH204.py"))]
|
||||
#[test_case(Rule::OsPathGetctime, Path::new("PTH205.py"))]
|
||||
#[test_case(Rule::OsSymlink, Path::new("PTH211.py"))]
|
||||
fn preview_flake8_use_pathlib(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
|
||||
@@ -24,6 +24,7 @@ pub(crate) use os_rename::*;
|
||||
pub(crate) use os_replace::*;
|
||||
pub(crate) use os_rmdir::*;
|
||||
pub(crate) use os_sep_split::*;
|
||||
pub(crate) use os_symlink::*;
|
||||
pub(crate) use os_unlink::*;
|
||||
pub(crate) use path_constructor_current_directory::*;
|
||||
pub(crate) use replaceable_by_pathlib::*;
|
||||
@@ -54,6 +55,7 @@ mod os_rename;
|
||||
mod os_replace;
|
||||
mod os_rmdir;
|
||||
mod os_sep_split;
|
||||
mod os_symlink;
|
||||
mod os_unlink;
|
||||
mod path_constructor_current_directory;
|
||||
mod replaceable_by_pathlib;
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
use ruff_diagnostics::{Applicability, Edit, Fix};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::preview::is_fix_os_symlink_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
has_unknown_keywords_or_starred_expr, is_keyword_only_argument_non_default,
|
||||
is_pathlib_path_call,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.symlink`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.symlink`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.symlink("usr/bin/python", "tmp/python", target_is_directory=False)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("tmp/python").symlink_to("usr/bin/python")
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.symlink_to`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.symlink_to)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsSymlink;
|
||||
|
||||
impl Violation for OsSymlink {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.symlink` should be replaced by `Path.symlink_to`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).symlink_to(...)`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH211
|
||||
pub(crate) fn os_symlink(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "symlink"] {
|
||||
return;
|
||||
}
|
||||
|
||||
// `dir_fd` is not supported by pathlib, so check if there are non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.symlink)
|
||||
// ```text
|
||||
// 0 1 2 3
|
||||
// os.symlink(src, dst, target_is_directory=False, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
|
||||
let range = call.range();
|
||||
let mut diagnostic = checker.report_diagnostic(OsSymlink, call.func.range());
|
||||
|
||||
if !is_fix_os_symlink_enabled(checker.settings()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if call.arguments.len() > 3 {
|
||||
return;
|
||||
}
|
||||
|
||||
if has_unknown_keywords_or_starred_expr(
|
||||
&call.arguments,
|
||||
&["src", "dst", "target_is_directory", "dir_fd"],
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let (Some(src), Some(dst)) = (
|
||||
call.arguments.find_argument_value("src", 0),
|
||||
call.arguments.find_argument_value("dst", 1),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let target_is_directory_arg = call.arguments.find_argument_value("target_is_directory", 2);
|
||||
|
||||
if let Some(expr) = &target_is_directory_arg {
|
||||
if expr.as_boolean_literal_expr().is_none() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import("pathlib", "Path"),
|
||||
call.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
|
||||
let applicability = if checker.comment_ranges().intersects(range) {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
};
|
||||
|
||||
let locator = checker.locator();
|
||||
let src_code = locator.slice(src.range());
|
||||
let dst_code = locator.slice(dst.range());
|
||||
|
||||
let target_is_directory = target_is_directory_arg
|
||||
.and_then(|expr| {
|
||||
let code = locator.slice(expr.range());
|
||||
expr.as_boolean_literal_expr()
|
||||
.is_none_or(|bl| bl.value)
|
||||
.then_some(format!(", target_is_directory={code}"))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let replacement = if is_pathlib_path_call(checker, dst) {
|
||||
format!("{dst_code}.symlink_to({src_code}{target_is_directory})")
|
||||
} else {
|
||||
format!("{binding}({dst_code}).symlink_to({src_code}{target_is_directory})")
|
||||
};
|
||||
|
||||
Ok(Fix::applicable_edits(
|
||||
Edit::range_replacement(replacement, range),
|
||||
[import_edit],
|
||||
applicability,
|
||||
))
|
||||
});
|
||||
}
|
||||
@@ -7,9 +7,7 @@ use crate::rules::flake8_use_pathlib::helpers::{
|
||||
};
|
||||
use crate::rules::flake8_use_pathlib::{
|
||||
rules::Glob,
|
||||
violations::{
|
||||
BuiltinOpen, Joiner, OsListdir, OsPathJoin, OsPathSplitext, OsStat, OsSymlink, PyPath,
|
||||
},
|
||||
violations::{BuiltinOpen, Joiner, OsListdir, OsPathJoin, OsPathSplitext, OsStat, PyPath},
|
||||
};
|
||||
|
||||
pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
@@ -62,20 +60,6 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
),
|
||||
// PTH122
|
||||
["os", "path", "splitext"] => checker.report_diagnostic_if_enabled(OsPathSplitext, range),
|
||||
// PTH211
|
||||
["os", "symlink"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if there are non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.symlink)
|
||||
// ```text
|
||||
// 0 1 2 3
|
||||
// os.symlink(src, dst, target_is_directory=False, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(OsSymlink, range)
|
||||
}
|
||||
|
||||
// PTH123
|
||||
["" | "builtins", "open"] => {
|
||||
// `closefd` and `opener` are not supported by pathlib, so check if they
|
||||
|
||||
@@ -9,6 +9,7 @@ PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
6 | os.symlink(b"usr/bin/python", b"tmp/python")
|
||||
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:6:1
|
||||
@@ -18,6 +19,7 @@ PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
| ^^^^^^^^^^
|
||||
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:9:1
|
||||
@@ -29,6 +31,7 @@ PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
|
||||
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:10:1
|
||||
@@ -38,3 +41,58 @@ PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
| ^^^^^^^^^^
|
||||
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:17:1
|
||||
|
|
||||
15 | os.close(fd)
|
||||
16 |
|
||||
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
|
||||
| ^^^^^^^^^^
|
||||
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:18:1
|
||||
|
|
||||
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
|
||||
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
||||
| ^^^^^^^^^^
|
||||
19 |
|
||||
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:20:1
|
||||
|
|
||||
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
||||
19 |
|
||||
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
| ^^^^^^^^^^
|
||||
21 |
|
||||
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:22:1
|
||||
|
|
||||
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
21 |
|
||||
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
||||
| ^^^^^^^^^^
|
||||
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:23:1
|
||||
|
|
||||
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
||||
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:5:1
|
||||
|
|
||||
5 | os.symlink("usr/bin/python", "tmp/python")
|
||||
| ^^^^^^^^^^
|
||||
6 | os.symlink(b"usr/bin/python", b"tmp/python")
|
||||
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from pathlib import Path
|
||||
3 3 |
|
||||
4 4 |
|
||||
5 |-os.symlink("usr/bin/python", "tmp/python")
|
||||
5 |+Path("tmp/python").symlink_to("usr/bin/python")
|
||||
6 6 | os.symlink(b"usr/bin/python", b"tmp/python")
|
||||
7 7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
8 8 |
|
||||
|
||||
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:6:1
|
||||
|
|
||||
5 | os.symlink("usr/bin/python", "tmp/python")
|
||||
6 | os.symlink(b"usr/bin/python", b"tmp/python")
|
||||
| ^^^^^^^^^^
|
||||
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 |
|
||||
4 4 |
|
||||
5 5 | os.symlink("usr/bin/python", "tmp/python")
|
||||
6 |-os.symlink(b"usr/bin/python", b"tmp/python")
|
||||
6 |+Path(b"tmp/python").symlink_to(b"usr/bin/python")
|
||||
7 7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
8 8 |
|
||||
9 9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
|
||||
|
||||
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:9:1
|
||||
|
|
||||
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
8 |
|
||||
9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
|
||||
| ^^^^^^^^^^
|
||||
10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
|
||||
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
ℹ Safe fix
|
||||
6 6 | os.symlink(b"usr/bin/python", b"tmp/python")
|
||||
7 7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
8 8 |
|
||||
9 |-os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
|
||||
9 |+Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True)
|
||||
10 10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
|
||||
11 11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
||||
12 12 |
|
||||
|
||||
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:10:1
|
||||
|
|
||||
9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
|
||||
10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
|
||||
| ^^^^^^^^^^
|
||||
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
8 8 |
|
||||
9 9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
|
||||
10 |-os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
|
||||
10 |+Path(b"tmp/python").symlink_to(b"usr/bin/python", target_is_directory=True)
|
||||
11 11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
||||
12 12 |
|
||||
13 13 | fd = os.open(".", os.O_RDONLY)
|
||||
|
||||
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:17:1
|
||||
|
|
||||
15 | os.close(fd)
|
||||
16 |
|
||||
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
|
||||
| ^^^^^^^^^^
|
||||
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:18:1
|
||||
|
|
||||
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
|
||||
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
||||
| ^^^^^^^^^^
|
||||
19 |
|
||||
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
ℹ Safe fix
|
||||
15 15 | os.close(fd)
|
||||
16 16 |
|
||||
17 17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
|
||||
18 |-os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
||||
18 |+Path("tmp/python").symlink_to("usr/bin/python")
|
||||
19 19 |
|
||||
20 20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
21 21 |
|
||||
|
||||
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:20:1
|
||||
|
|
||||
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
||||
19 |
|
||||
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
| ^^^^^^^^^^
|
||||
21 |
|
||||
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
ℹ Safe fix
|
||||
17 17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
|
||||
18 18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
||||
19 19 |
|
||||
20 |-os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
20 |+Path("tmp/python").symlink_to("usr/bin/python")
|
||||
21 21 |
|
||||
22 22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
||||
23 23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
||||
|
||||
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:22:1
|
||||
|
|
||||
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
21 |
|
||||
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
||||
| ^^^^^^^^^^
|
||||
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
ℹ Safe fix
|
||||
19 19 |
|
||||
20 20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
21 21 |
|
||||
22 |-os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
||||
22 |+Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True)
|
||||
23 23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
||||
|
||||
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:23:1
|
||||
|
|
||||
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
||||
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
@@ -310,45 +310,3 @@ impl Violation for OsListdir {
|
||||
"Use `pathlib.Path.iterdir()` instead.".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.symlink`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.symlink`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.symlink("usr/bin/python", "tmp/python", target_is_directory=False)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("tmp/python").symlink_to("usr/bin/python")
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.symlink_to`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.symlink_to)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsSymlink;
|
||||
|
||||
impl Violation for OsSymlink {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.symlink` should be replaced by `Path.symlink_to`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ fn to_interpolated_string_interpolation_element(inner: &Expr) -> ast::Interpolat
|
||||
conversion: ConversionFlag::None,
|
||||
format_spec: None,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ pub(super) fn to_interpolated_string_literal_element(s: &str) -> ast::Interpolat
|
||||
ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement {
|
||||
value: Box::from(s),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ fn build_fstring(joiner: &str, joinees: &[Expr], flags: FStringFlags) -> Option<
|
||||
.into_boxed_str(),
|
||||
flags: flags?,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
return Some(node.into());
|
||||
}
|
||||
@@ -114,7 +114,7 @@ fn build_fstring(joiner: &str, joinees: &[Expr], flags: FStringFlags) -> Option<
|
||||
let node = ast::FString {
|
||||
elements: f_string_elements.into(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
flags,
|
||||
};
|
||||
Some(node.into())
|
||||
|
||||
@@ -182,7 +182,7 @@ fn function(
|
||||
ExprEllipsisLiteral::default(),
|
||||
))),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
let parameters = lambda.parameters.as_deref().cloned().unwrap_or_default();
|
||||
if let Some(annotation) = annotation {
|
||||
@@ -230,7 +230,7 @@ fn function(
|
||||
returns: Some(Box::new(return_type)),
|
||||
type_params: None,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
let generated = checker.generator().stmt(&func);
|
||||
|
||||
@@ -246,7 +246,7 @@ fn function(
|
||||
returns: None,
|
||||
type_params: None,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
let generated = checker.generator().stmt(&function);
|
||||
|
||||
|
||||
@@ -72,11 +72,11 @@ pub(crate) fn manual_from_import(checker: &Checker, stmt: &Stmt, alias: &Alias,
|
||||
name: asname.clone(),
|
||||
asname: None,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}],
|
||||
level: 0,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
checker.generator().stmt(&node.into()),
|
||||
|
||||
@@ -147,7 +147,7 @@ fn collect_nested_args(min_max: MinMax, args: &[Expr], semantic: &SemanticModel)
|
||||
value: Box::new(arg.clone()),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
new_args.push(new_arg);
|
||||
continue;
|
||||
@@ -204,10 +204,10 @@ pub(crate) fn nested_min_max(
|
||||
args: collect_nested_args(min_max, args, checker.semantic()).into_boxed_slice(),
|
||||
keywords: Box::from(keywords),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
checker.generator().expr(&flattened_expr),
|
||||
|
||||
@@ -170,13 +170,13 @@ pub(crate) fn repeated_equality_comparison(checker: &Checker, bool_op: &ast::Exp
|
||||
Expr::Set(ast::ExprSet {
|
||||
elts: comparators.iter().copied().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
})
|
||||
} else {
|
||||
Expr::Tuple(ast::ExprTuple {
|
||||
elts: comparators.iter().copied().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: true,
|
||||
})
|
||||
@@ -194,12 +194,12 @@ pub(crate) fn repeated_equality_comparison(checker: &Checker, bool_op: &ast::Exp
|
||||
},
|
||||
comparators: Box::from([comparator]),
|
||||
range: bool_op.range(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
})))
|
||||
.chain(after)
|
||||
.collect(),
|
||||
range: bool_op.range(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
})),
|
||||
bool_op.range(),
|
||||
)));
|
||||
|
||||
@@ -188,7 +188,7 @@ fn generate_keyword_fix(checker: &Checker, call: &ast::ExprCall) -> Fix {
|
||||
value: Box::from("utf-8"),
|
||||
flags: checker.default_string_flags(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}))
|
||||
),
|
||||
&call.arguments,
|
||||
|
||||
@@ -87,7 +87,7 @@ pub(crate) fn convert_named_tuple_functional_to_class(
|
||||
// Ex) `NamedTuple("MyType")`
|
||||
([_typename], []) => vec![Stmt::Pass(ast::StmtPass {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})],
|
||||
// Ex) `NamedTuple("MyType", [("a", int), ("b", str)])`
|
||||
([_typename, fields], []) => {
|
||||
@@ -165,7 +165,7 @@ fn create_field_assignment_stmt(field: Name, annotation: &Expr) -> Stmt {
|
||||
id: field,
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
@@ -173,7 +173,7 @@ fn create_field_assignment_stmt(field: Name, annotation: &Expr) -> Stmt {
|
||||
value: None,
|
||||
simple: true,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -184,7 +184,7 @@ fn create_fields_from_fields_arg(fields: &Expr) -> Option<Vec<Stmt>> {
|
||||
if fields.is_empty() {
|
||||
let node = Stmt::Pass(ast::StmtPass {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
Some(vec![node])
|
||||
} else {
|
||||
@@ -236,13 +236,13 @@ fn create_class_def_stmt(typename: &str, body: Vec<Stmt>, base_class: &Expr) ->
|
||||
args: Box::from([base_class.clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})),
|
||||
body,
|
||||
type_params: None,
|
||||
decorator_list: vec![],
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ fn create_field_assignment_stmt(field: &str, annotation: &Expr) -> Stmt {
|
||||
id: field.into(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
@@ -158,7 +158,7 @@ fn create_field_assignment_stmt(field: &str, annotation: &Expr) -> Stmt {
|
||||
value: None,
|
||||
simple: true,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -179,13 +179,13 @@ fn create_class_def_stmt(
|
||||
None => Box::from([]),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})),
|
||||
body,
|
||||
type_params: None,
|
||||
decorator_list: vec![],
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -194,7 +194,7 @@ fn fields_from_dict_literal(items: &[ast::DictItem]) -> Option<Vec<Stmt>> {
|
||||
if items.is_empty() {
|
||||
let node = Stmt::Pass(ast::StmtPass {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
Some(vec![node])
|
||||
} else {
|
||||
@@ -228,7 +228,7 @@ fn fields_from_dict_call(func: &Expr, keywords: &[Keyword]) -> Option<Vec<Stmt>>
|
||||
if keywords.is_empty() {
|
||||
let node = Stmt::Pass(ast::StmtPass {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
Some(vec![node])
|
||||
} else {
|
||||
@@ -241,7 +241,7 @@ fn fields_from_keywords(keywords: &[Keyword]) -> Option<Vec<Stmt>> {
|
||||
if keywords.is_empty() {
|
||||
let node = Stmt::Pass(ast::StmtPass {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
return Some(vec![node]);
|
||||
}
|
||||
@@ -282,7 +282,7 @@ fn match_fields_and_total(arguments: &Arguments) -> Option<(Vec<Stmt>, Option<&K
|
||||
([_typename], []) => {
|
||||
let node = Stmt::Pass(ast::StmtPass {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
Some((vec![node], None))
|
||||
}
|
||||
|
||||
@@ -39,27 +39,27 @@ impl LiteralType {
|
||||
LiteralType::Str => ast::StringLiteral {
|
||||
value: Box::default(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
flags: checker.default_string_flags(),
|
||||
}
|
||||
.into(),
|
||||
LiteralType::Bytes => ast::BytesLiteral {
|
||||
value: Box::default(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
flags: checker.default_bytes_flags(),
|
||||
}
|
||||
.into(),
|
||||
LiteralType::Int => ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(Int::from(0u8)),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into(),
|
||||
LiteralType::Float => ast::ExprNumberLiteral {
|
||||
value: ast::Number::Float(0.0),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into(),
|
||||
LiteralType::Bool => ast::ExprBooleanLiteral::default().into(),
|
||||
|
||||
@@ -116,7 +116,7 @@ fn tuple_diagnostic(checker: &Checker, tuple: &ast::ExprTuple, aliases: &[&Expr]
|
||||
id: Name::new_static("OSError"),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
remaining.insert(0, node.into());
|
||||
}
|
||||
@@ -128,7 +128,7 @@ fn tuple_diagnostic(checker: &Checker, tuple: &ast::ExprTuple, aliases: &[&Expr]
|
||||
elts: remaining,
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
};
|
||||
format!("({})", checker.generator().expr(&node.into()))
|
||||
|
||||
@@ -140,14 +140,14 @@ impl<'a> From<&'a TypeVar<'a>> for TypeParam {
|
||||
TypeParamKind::TypeVar => {
|
||||
TypeParam::TypeVar(TypeParamTypeVar {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
name: Identifier::new(*name, TextRange::default()),
|
||||
bound: match restriction {
|
||||
Some(TypeVarRestriction::Bound(bound)) => Some(Box::new((*bound).clone())),
|
||||
Some(TypeVarRestriction::Constraint(constraints)) => {
|
||||
Some(Box::new(Expr::Tuple(ast::ExprTuple {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
elts: constraints.iter().map(|expr| (*expr).clone()).collect(),
|
||||
ctx: ast::ExprContext::Load,
|
||||
parenthesized: true,
|
||||
@@ -156,17 +156,17 @@ impl<'a> From<&'a TypeVar<'a>> for TypeParam {
|
||||
Some(TypeVarRestriction::AnyStr) => {
|
||||
Some(Box::new(Expr::Tuple(ast::ExprTuple {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
elts: vec![
|
||||
Expr::Name(ExprName {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
id: Name::from("str"),
|
||||
ctx: ast::ExprContext::Load,
|
||||
}),
|
||||
Expr::Name(ExprName {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
id: Name::from("bytes"),
|
||||
ctx: ast::ExprContext::Load,
|
||||
}),
|
||||
@@ -184,13 +184,13 @@ impl<'a> From<&'a TypeVar<'a>> for TypeParam {
|
||||
}
|
||||
TypeParamKind::TypeVarTuple => TypeParam::TypeVarTuple(TypeParamTypeVarTuple {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
name: Identifier::new(*name, TextRange::default()),
|
||||
default: None,
|
||||
}),
|
||||
TypeParamKind::ParamSpec => TypeParam::ParamSpec(TypeParamParamSpec {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
name: Identifier::new(*name, TextRange::default()),
|
||||
default: None,
|
||||
}),
|
||||
|
||||
@@ -130,7 +130,7 @@ fn tuple_diagnostic(checker: &Checker, tuple: &ast::ExprTuple, aliases: &[&Expr]
|
||||
id: Name::new_static("TimeoutError"),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
remaining.insert(0, node.into());
|
||||
}
|
||||
@@ -142,7 +142,7 @@ fn tuple_diagnostic(checker: &Checker, tuple: &ast::ExprTuple, aliases: &[&Expr]
|
||||
elts: remaining,
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
};
|
||||
format!("({})", checker.generator().expr(&node.into()))
|
||||
|
||||
@@ -127,13 +127,13 @@ pub(crate) fn unnecessary_default_type_args(checker: &Checker, expr: &Expr) {
|
||||
elts: valid_elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
})
|
||||
}),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})),
|
||||
expr.range(),
|
||||
),
|
||||
|
||||
@@ -17,7 +17,7 @@ pub(super) fn generate_method_call(name: Name, method: &str, generator: Generato
|
||||
id: name,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
// Construct `name.method`.
|
||||
let attr = ast::ExprAttribute {
|
||||
@@ -25,7 +25,7 @@ pub(super) fn generate_method_call(name: Name, method: &str, generator: Generato
|
||||
attr: ast::Identifier::new(method.to_string(), TextRange::default()),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
// Make it into a call `name.method()`
|
||||
let call = ast::ExprCall {
|
||||
@@ -34,16 +34,16 @@ pub(super) fn generate_method_call(name: Name, method: &str, generator: Generato
|
||||
args: Box::from([]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
// And finally, turn it into a statement.
|
||||
let stmt = ast::StmtExpr {
|
||||
value: Box::new(call.into()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
generator.stmt(&stmt.into())
|
||||
}
|
||||
@@ -69,7 +69,7 @@ pub(super) fn replace_with_identity_check(
|
||||
ops: [op].into(),
|
||||
comparators: [ast::ExprNoneLiteral::default().into()].into(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
|
||||
let new_content = generator.expr(&new_expr);
|
||||
|
||||
@@ -185,7 +185,7 @@ fn make_suggestion(set: &ast::ExprName, element: &Expr, generator: Generator) ->
|
||||
attr: ast::Identifier::new("discard".to_string(), TextRange::default()),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
// Make the actual call `set.discard(element)`
|
||||
let call = ast::ExprCall {
|
||||
@@ -194,16 +194,16 @@ fn make_suggestion(set: &ast::ExprName, element: &Expr, generator: Generator) ->
|
||||
args: Box::from([element.clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
// And finally, turn it into a statement.
|
||||
let stmt = ast::StmtExpr {
|
||||
value: Box::new(call.into()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
generator.stmt(&stmt.into())
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ fn make_suggestion(open: &FileOpen<'_>, generator: Generator) -> SourceCodeSnipp
|
||||
id: open.mode.pathlib_method(),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let call = ast::ExprCall {
|
||||
func: Box::new(name.into()),
|
||||
@@ -138,10 +138,10 @@ fn make_suggestion(open: &FileOpen<'_>, generator: Generator) -> SourceCodeSnipp
|
||||
args: Box::from([]),
|
||||
keywords: open.keywords.iter().copied().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
SourceCodeSnippet::from_str(&generator.expr(&call.into()))
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ fn construct_starmap_call(starmap_binding: Name, iter: &Expr, func: &Expr) -> as
|
||||
id: starmap_binding,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
ast::ExprCall {
|
||||
func: Box::new(starmap.into()),
|
||||
@@ -306,10 +306,10 @@ fn construct_starmap_call(starmap_binding: Name, iter: &Expr, func: &Expr) -> as
|
||||
args: Box::from([func.clone(), iter.clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ fn wrap_with_call_to(call: ast::ExprCall, func_name: Name) -> ast::ExprCall {
|
||||
id: func_name,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
ast::ExprCall {
|
||||
func: Box::new(name.into()),
|
||||
@@ -327,10 +327,10 @@ fn wrap_with_call_to(call: ast::ExprCall, func_name: Name) -> ast::ExprCall {
|
||||
args: Box::from([call.into()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -342,7 +342,7 @@ fn make_suggestion(group: &AppendGroup, generator: Generator) -> String {
|
||||
elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
};
|
||||
// Make `var.extend`.
|
||||
@@ -352,7 +352,7 @@ fn make_suggestion(group: &AppendGroup, generator: Generator) -> String {
|
||||
attr: ast::Identifier::new("extend".to_string(), TextRange::default()),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
// Make the actual call `var.extend((elt1, elt2, ..., eltN))`
|
||||
let call = ast::ExprCall {
|
||||
@@ -361,16 +361,16 @@ fn make_suggestion(group: &AppendGroup, generator: Generator) -> String {
|
||||
args: Box::from([tuple.into()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
// And finally, turn it into a statement.
|
||||
let stmt = ast::StmtExpr {
|
||||
value: Box::new(call.into()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
generator.stmt(&stmt.into())
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ fn generate_range_len_call(name: Name, generator: Generator) -> String {
|
||||
id: name,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
// Construct `len(name)`.
|
||||
let len = ast::ExprCall {
|
||||
@@ -241,7 +241,7 @@ fn generate_range_len_call(name: Name, generator: Generator) -> String {
|
||||
id: Name::new_static("len"),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
@@ -249,10 +249,10 @@ fn generate_range_len_call(name: Name, generator: Generator) -> String {
|
||||
args: Box::from([var.into()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
// Construct `range(len(name))`.
|
||||
let range = ast::ExprCall {
|
||||
@@ -261,7 +261,7 @@ fn generate_range_len_call(name: Name, generator: Generator) -> String {
|
||||
id: Name::new_static("range"),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
@@ -269,16 +269,16 @@ fn generate_range_len_call(name: Name, generator: Generator) -> String {
|
||||
args: Box::from([len.into()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
// And finally, turn it into a statement.
|
||||
let stmt = ast::StmtExpr {
|
||||
value: Box::new(range.into()),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
generator.stmt(&stmt.into())
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ fn make_suggestion(open: &FileOpen<'_>, arg: &Expr, generator: Generator) -> Sou
|
||||
id: open.mode.pathlib_method(),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let mut arg = arg.clone();
|
||||
relocate_expr(&mut arg, TextRange::default());
|
||||
@@ -158,10 +158,10 @@ fn make_suggestion(open: &FileOpen<'_>, arg: &Expr, generator: Generator) -> Sou
|
||||
args: Box::new([arg]),
|
||||
keywords: open.keywords.iter().copied().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
SourceCodeSnippet::from_str(&generator.expr(&call.into()))
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ pub(crate) fn assert_with_print_message(checker: &Checker, stmt: &ast::StmtAsser
|
||||
test: stmt.test.clone(),
|
||||
msg: print_arguments::to_expr(&call.arguments, checker).map(Box::new),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})),
|
||||
// We have to replace the entire statement,
|
||||
// as the `print` could be empty and thus `call.range()`
|
||||
@@ -114,7 +114,7 @@ mod print_arguments {
|
||||
InterpolatedStringElement::Literal(InterpolatedStringLiteralElement {
|
||||
value: part.value.clone(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
@@ -131,7 +131,7 @@ mod print_arguments {
|
||||
conversion: ConversionFlag::None,
|
||||
format_spec: None,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
)],
|
||||
}
|
||||
@@ -154,7 +154,7 @@ mod print_arguments {
|
||||
value: literal.value.clone(),
|
||||
flags,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
Some(acc)
|
||||
} else {
|
||||
@@ -212,7 +212,7 @@ mod print_arguments {
|
||||
value: combined_string.into(),
|
||||
flags,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -246,10 +246,10 @@ mod print_arguments {
|
||||
elements: InterpolatedStringElements::from(fstring_elements),
|
||||
flags,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ mod print_arguments {
|
||||
vec![InterpolatedStringElement::Literal(
|
||||
InterpolatedStringLiteralElement {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
value: " ".into(),
|
||||
},
|
||||
)]
|
||||
|
||||
@@ -78,7 +78,7 @@ fn make_splat_elts(
|
||||
value: Box::from(splat_element.clone()),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
let splat = node.into();
|
||||
if splat_at_left {
|
||||
@@ -166,14 +166,14 @@ fn concatenate_expressions(expr: &Expr) -> Option<(Expr, Type)> {
|
||||
elts: new_elts,
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into(),
|
||||
Type::Tuple => ast::ExprTuple {
|
||||
elts: new_elts,
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
}
|
||||
.into(),
|
||||
|
||||
@@ -123,7 +123,8 @@ fn is_non_callable_value(value: &Expr) -> bool {
|
||||
| Expr::SetComp(_)
|
||||
| Expr::DictComp(_)
|
||||
| Expr::Generator(_)
|
||||
| Expr::FString(_))
|
||||
| Expr::FString(_)
|
||||
| Expr::TString(_))
|
||||
}
|
||||
|
||||
/// Generate an [`Expr`] to replace `defaultdict(default_factory=callable)` with
|
||||
|
||||
@@ -140,7 +140,7 @@ fn generate_fix(checker: &Checker, conversion_type: ConversionType, expr: &Expr)
|
||||
op: Operator::BitOr,
|
||||
right: Box::new(Expr::NoneLiteral(ast::ExprNoneLiteral::default())),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
let content = checker.generator().expr(&new_expr);
|
||||
let edit = Edit::range_replacement(content, expr.range());
|
||||
@@ -160,12 +160,12 @@ fn generate_fix(checker: &Checker, conversion_type: ConversionType, expr: &Expr)
|
||||
let (import_edit, binding) = importer.import(expr.start())?;
|
||||
let new_expr = Expr::Subscript(ast::ExprSubscript {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
value: Box::new(Expr::Name(ast::ExprName {
|
||||
id: Name::new(binding),
|
||||
ctx: ast::ExprContext::Store,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})),
|
||||
slice: Box::new(expr.clone()),
|
||||
ctx: ast::ExprContext::Load,
|
||||
|
||||
@@ -245,16 +245,16 @@ fn generate_with_statement(
|
||||
});
|
||||
|
||||
let context_call = ast::ExprCall {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
range: TextRange::default(),
|
||||
func: legacy_call.func.clone(),
|
||||
arguments: ast::Arguments {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
range: TextRange::default(),
|
||||
args: expected.cloned().as_slice().into(),
|
||||
keywords: match_arg
|
||||
.map(|expr| ast::Keyword {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
// Take range from the original expression so that the keyword
|
||||
// argument is generated after positional arguments
|
||||
range: expr.range(),
|
||||
@@ -267,11 +267,11 @@ fn generate_with_statement(
|
||||
};
|
||||
|
||||
let func_call = ast::ExprCall {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
range: TextRange::default(),
|
||||
func: Box::new(func.clone()),
|
||||
arguments: ast::Arguments {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
range: TextRange::default(),
|
||||
args: func_args.into(),
|
||||
keywords: func_keywords.into(),
|
||||
@@ -280,25 +280,25 @@ fn generate_with_statement(
|
||||
|
||||
let body = if let Some(assign_targets) = assign_targets {
|
||||
Stmt::Assign(ast::StmtAssign {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
range: TextRange::default(),
|
||||
targets: assign_targets.to_vec(),
|
||||
value: Box::new(func_call.into()),
|
||||
})
|
||||
} else {
|
||||
Stmt::Expr(StmtExpr {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
range: TextRange::default(),
|
||||
value: Box::new(func_call.into()),
|
||||
})
|
||||
};
|
||||
|
||||
Some(StmtWith {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
range: TextRange::default(),
|
||||
is_async: false,
|
||||
items: vec![WithItem {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
range: TextRange::default(),
|
||||
context_expr: context_call.into(),
|
||||
optional_vars: optional_vars.map(|var| Box::new(var.clone())),
|
||||
|
||||
@@ -102,7 +102,7 @@ fn generate_dict_comprehension(keys: &Expr, value: &Expr, generator: Generator)
|
||||
id: Name::new_static("key"),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
// Construct `key in keys`.
|
||||
let comp = ast::Comprehension {
|
||||
@@ -110,7 +110,7 @@ fn generate_dict_comprehension(keys: &Expr, value: &Expr, generator: Generator)
|
||||
iter: keys.clone(),
|
||||
ifs: vec![],
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
is_async: false,
|
||||
};
|
||||
// Construct the dict comprehension.
|
||||
@@ -119,7 +119,7 @@ fn generate_dict_comprehension(keys: &Expr, value: &Expr, generator: Generator)
|
||||
value: Box::new(value.clone()),
|
||||
generators: vec![comp],
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
};
|
||||
generator.expr(&dict_comp.into())
|
||||
}
|
||||
|
||||
@@ -164,12 +164,12 @@ pub(crate) fn never_union(checker: &Checker, expr: &Expr) {
|
||||
elts: rest,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
})),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
}))
|
||||
},
|
||||
expr.range(),
|
||||
|
||||
@@ -103,6 +103,7 @@ pub(crate) fn unnecessary_literal_within_deque_call(checker: &Checker, deque: &a
|
||||
Expr::StringLiteral(string) => string.value.is_empty(),
|
||||
Expr::BytesLiteral(bytes) => bytes.value.is_empty(),
|
||||
Expr::FString(fstring) => fstring.value.is_empty_literal(),
|
||||
Expr::TString(tstring) => tstring.value.is_empty_iterable(),
|
||||
_ => false,
|
||||
};
|
||||
if !is_empty_literal {
|
||||
|
||||
@@ -116,14 +116,14 @@ pub(crate) fn unnecessary_nested_literal<'a>(checker: &Checker, literal_expr: &'
|
||||
Expr::Tuple(ExprTuple {
|
||||
elts: nodes.into_iter().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: false,
|
||||
})
|
||||
}),
|
||||
value: subscript.value.clone(),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
});
|
||||
let fix = Fix::applicable_edit(
|
||||
|
||||
@@ -303,7 +303,7 @@ impl<'a> ReFunc<'a> {
|
||||
op: UnaryOp::Not,
|
||||
operand: Box::new(expr),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
Some(negated_expr)
|
||||
}
|
||||
@@ -327,7 +327,7 @@ impl<'a> ReFunc<'a> {
|
||||
ops: Box::new([op]),
|
||||
comparators: Box::new([right.clone()]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -339,7 +339,7 @@ impl<'a> ReFunc<'a> {
|
||||
attr: Identifier::new(method, TextRange::default()),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
Expr::Call(ExprCall {
|
||||
func: Box::new(method),
|
||||
@@ -347,10 +347,10 @@ impl<'a> ReFunc<'a> {
|
||||
args: args.into_boxed_slice(),
|
||||
keywords: Box::new([]),
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
},
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,3 +383,42 @@ help: Replace with `deque()`
|
||||
101 101 | deque("abc") # OK
|
||||
102 102 | deque(b"abc") # OK
|
||||
103 103 | deque(f"" "a") # OK
|
||||
|
||||
RUF037 [*] Unnecessary empty iterable within a deque call
|
||||
--> RUF037.py:107:1
|
||||
|
|
||||
106 | # https://github.com/astral-sh/ruff/issues/19951
|
||||
107 | deque(t"")
|
||||
| ^^^^^^^^^^
|
||||
108 | deque(t"" t"")
|
||||
109 | deque(t"{""}") # OK
|
||||
|
|
||||
help: Replace with `deque()`
|
||||
|
||||
ℹ Safe fix
|
||||
104 104 | deque(f"{x}" "") # OK
|
||||
105 105 |
|
||||
106 106 | # https://github.com/astral-sh/ruff/issues/19951
|
||||
107 |-deque(t"")
|
||||
107 |+deque()
|
||||
108 108 | deque(t"" t"")
|
||||
109 109 | deque(t"{""}") # OK
|
||||
|
||||
RUF037 [*] Unnecessary empty iterable within a deque call
|
||||
--> RUF037.py:108:1
|
||||
|
|
||||
106 | # https://github.com/astral-sh/ruff/issues/19951
|
||||
107 | deque(t"")
|
||||
108 | deque(t"" t"")
|
||||
| ^^^^^^^^^^^^^^^
|
||||
109 | deque(t"{""}") # OK
|
||||
|
|
||||
help: Replace with `deque()`
|
||||
|
||||
ℹ Safe fix
|
||||
105 105 |
|
||||
106 106 | # https://github.com/astral-sh/ruff/issues/19951
|
||||
107 107 | deque(t"")
|
||||
108 |-deque(t"" t"")
|
||||
108 |+deque()
|
||||
109 109 | deque(t"{""}") # OK
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! Helper functions for the tests of rule implementations.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(not(fuzzing))]
|
||||
@@ -32,6 +33,85 @@ use crate::source_kind::SourceKind;
|
||||
use crate::{Applicability, FixAvailability};
|
||||
use crate::{Locator, directives};
|
||||
|
||||
/// Represents the difference between two diagnostic runs.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DiagnosticsDiff {
|
||||
/// Diagnostics that were removed (present in 'before' but not in 'after')
|
||||
removed: Vec<Diagnostic>,
|
||||
/// Diagnostics that were added (present in 'after' but not in 'before')
|
||||
added: Vec<Diagnostic>,
|
||||
/// Settings used before the change
|
||||
settings_before: LinterSettings,
|
||||
/// Settings used after the change
|
||||
settings_after: LinterSettings,
|
||||
}
|
||||
|
||||
impl fmt::Display for DiagnosticsDiff {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "--- Linter settings ---")?;
|
||||
let settings_before_str = format!("{}", self.settings_before);
|
||||
let settings_after_str = format!("{}", self.settings_after);
|
||||
let diff = similar::TextDiff::from_lines(&settings_before_str, &settings_after_str);
|
||||
for change in diff.iter_all_changes() {
|
||||
match change.tag() {
|
||||
similar::ChangeTag::Delete => write!(f, "-{change}")?,
|
||||
similar::ChangeTag::Insert => write!(f, "+{change}")?,
|
||||
similar::ChangeTag::Equal => (),
|
||||
}
|
||||
}
|
||||
writeln!(f)?;
|
||||
|
||||
writeln!(f, "--- Summary ---")?;
|
||||
writeln!(f, "Removed: {}", self.removed.len())?;
|
||||
writeln!(f, "Added: {}", self.added.len())?;
|
||||
writeln!(f)?;
|
||||
|
||||
if !self.removed.is_empty() {
|
||||
writeln!(f, "--- Removed ---")?;
|
||||
for diagnostic in &self.removed {
|
||||
writeln!(f, "{}", print_messages(std::slice::from_ref(diagnostic)))?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
if !self.added.is_empty() {
|
||||
writeln!(f, "--- Added ---")?;
|
||||
for diagnostic in &self.added {
|
||||
writeln!(f, "{}", print_messages(std::slice::from_ref(diagnostic)))?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Compare two sets of diagnostics and return the differences
|
||||
fn diff_diagnostics(
|
||||
before: Vec<Diagnostic>,
|
||||
after: Vec<Diagnostic>,
|
||||
settings_before: &LinterSettings,
|
||||
settings_after: &LinterSettings,
|
||||
) -> DiagnosticsDiff {
|
||||
let mut removed = Vec::new();
|
||||
let mut added = after;
|
||||
|
||||
for old_diag in before {
|
||||
let Some(pos) = added.iter().position(|diag| diag == &old_diag) else {
|
||||
removed.push(old_diag);
|
||||
continue;
|
||||
};
|
||||
added.remove(pos);
|
||||
}
|
||||
|
||||
DiagnosticsDiff {
|
||||
removed,
|
||||
added,
|
||||
settings_before: settings_before.clone(),
|
||||
settings_after: settings_after.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(fuzzing))]
|
||||
pub(crate) fn test_resource_path(path: impl AsRef<Path>) -> std::path::PathBuf {
|
||||
Path::new("./resources/test/").join(path)
|
||||
@@ -49,6 +129,30 @@ pub(crate) fn test_path(
|
||||
Ok(test_contents(&source_kind, &path, settings).0)
|
||||
}
|
||||
|
||||
/// Test a file with two different settings and return the differences
|
||||
#[cfg(not(fuzzing))]
|
||||
pub(crate) fn test_path_with_settings_diff(
|
||||
path: impl AsRef<Path>,
|
||||
settings_before: &LinterSettings,
|
||||
settings_after: &LinterSettings,
|
||||
) -> Result<DiagnosticsDiff> {
|
||||
assert!(
|
||||
format!("{settings_before}") != format!("{settings_after}"),
|
||||
"Settings must be different for differential testing"
|
||||
);
|
||||
|
||||
let diagnostics_before = test_path(&path, settings_before)?;
|
||||
let diagnostic_after = test_path(&path, settings_after)?;
|
||||
|
||||
let diff = diff_diagnostics(
|
||||
diagnostics_before,
|
||||
diagnostic_after,
|
||||
settings_before,
|
||||
settings_after,
|
||||
);
|
||||
Ok(diff)
|
||||
}
|
||||
|
||||
#[cfg(not(fuzzing))]
|
||||
pub(crate) struct TestedNotebook {
|
||||
pub(crate) diagnostics: Vec<Diagnostic>,
|
||||
@@ -400,3 +504,59 @@ macro_rules! assert_diagnostics {
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_diagnostics_diff {
|
||||
($snapshot:expr, $path:expr, $settings_before:expr, $settings_after:expr) => {{
|
||||
let diff = $crate::test::test_path_with_settings_diff($path, $settings_before, $settings_after)?;
|
||||
insta::with_settings!({ omit_expression => true }, {
|
||||
insta::assert_snapshot!($snapshot, format!("{}", diff));
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_diff_diagnostics() -> Result<()> {
|
||||
use crate::codes::Rule;
|
||||
use ruff_db::diagnostic::{DiagnosticId, LintName};
|
||||
|
||||
let settings_before = LinterSettings::for_rule(Rule::Print);
|
||||
let settings_after = LinterSettings::for_rule(Rule::UnusedImport);
|
||||
|
||||
let test_code = r#"
|
||||
import sys
|
||||
import unused_module
|
||||
|
||||
def main():
|
||||
print(sys.version)
|
||||
"#;
|
||||
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let test_file = temp_dir.join("test_diff.py");
|
||||
std::fs::write(&test_file, test_code)?;
|
||||
|
||||
let diff =
|
||||
super::test_path_with_settings_diff(&test_file, &settings_before, &settings_after)?;
|
||||
|
||||
assert_eq!(diff.removed.len(), 1, "Should remove 1 print diagnostic");
|
||||
assert_eq!(
|
||||
diff.removed[0].id(),
|
||||
DiagnosticId::Lint(LintName::of("print")),
|
||||
"Should remove the print diagnostic"
|
||||
);
|
||||
assert_eq!(diff.added.len(), 1, "Should add 1 unused import diagnostic");
|
||||
assert_eq!(
|
||||
diff.added[0].id(),
|
||||
DiagnosticId::Lint(LintName::of("unused-import")),
|
||||
"Should add the unused import diagnostic"
|
||||
);
|
||||
|
||||
std::fs::remove_file(test_file)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1489,7 +1489,7 @@ pub fn pep_604_optional(expr: &Expr) -> Expr {
|
||||
op: Operator::BitOr,
|
||||
right: Box::new(Expr::NoneLiteral(ast::ExprNoneLiteral::default())),
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -1501,7 +1501,7 @@ pub fn pep_604_union(elts: &[Expr]) -> Expr {
|
||||
elts: vec![],
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
parenthesized: true,
|
||||
}),
|
||||
[Expr::Tuple(ast::ExprTuple { elts, .. })] => pep_604_union(elts),
|
||||
@@ -1511,7 +1511,7 @@ pub fn pep_604_union(elts: &[Expr]) -> Expr {
|
||||
op: Operator::BitOr,
|
||||
right: Box::new(pep_604_union(std::slice::from_ref(elt))),
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -1522,13 +1522,13 @@ pub fn typing_optional(elt: Expr, binding: Name) -> Expr {
|
||||
value: Box::new(Expr::Name(ast::ExprName {
|
||||
id: binding,
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
})),
|
||||
slice: Box::new(elt),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1541,19 +1541,19 @@ pub fn typing_union(elts: &[Expr], binding: Name) -> Expr {
|
||||
value: Box::new(Expr::Name(ast::ExprName {
|
||||
id: binding,
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
})),
|
||||
slice: Box::new(Expr::Tuple(ast::ExprTuple {
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
elts: elts.to_vec(),
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: false,
|
||||
})),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1686,34 +1686,34 @@ mod tests {
|
||||
let name = Expr::Name(ExprName {
|
||||
id: "x".into(),
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
ctx: ExprContext::Load,
|
||||
});
|
||||
let constant_one = Expr::NumberLiteral(ExprNumberLiteral {
|
||||
value: Number::Int(Int::from(1u8)),
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
});
|
||||
let constant_two = Expr::NumberLiteral(ExprNumberLiteral {
|
||||
value: Number::Int(Int::from(2u8)),
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
});
|
||||
let constant_three = Expr::NumberLiteral(ExprNumberLiteral {
|
||||
value: Number::Int(Int::from(3u8)),
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
});
|
||||
let type_var_one = TypeParam::TypeVar(TypeParamTypeVar {
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
bound: Some(Box::new(constant_one.clone())),
|
||||
default: None,
|
||||
name: Identifier::new("x", TextRange::default()),
|
||||
});
|
||||
let type_var_two = TypeParam::TypeVar(TypeParamTypeVar {
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
bound: None,
|
||||
default: Some(Box::new(constant_two.clone())),
|
||||
name: Identifier::new("x", TextRange::default()),
|
||||
@@ -1723,11 +1723,11 @@ mod tests {
|
||||
type_params: Some(Box::new(TypeParams {
|
||||
type_params: vec![type_var_one, type_var_two],
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
})),
|
||||
value: Box::new(constant_three.clone()),
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
});
|
||||
assert!(!any_over_stmt(&type_alias, &|expr| {
|
||||
seen.borrow_mut().push(expr.clone());
|
||||
@@ -1743,7 +1743,7 @@ mod tests {
|
||||
fn any_over_type_param_type_var() {
|
||||
let type_var_no_bound = TypeParam::TypeVar(TypeParamTypeVar {
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
bound: None,
|
||||
default: None,
|
||||
name: Identifier::new("x", TextRange::default()),
|
||||
@@ -1753,12 +1753,12 @@ mod tests {
|
||||
let constant = Expr::NumberLiteral(ExprNumberLiteral {
|
||||
value: Number::Int(Int::ONE),
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
});
|
||||
|
||||
let type_var_with_bound = TypeParam::TypeVar(TypeParamTypeVar {
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
bound: Some(Box::new(constant.clone())),
|
||||
default: None,
|
||||
name: Identifier::new("x", TextRange::default()),
|
||||
@@ -1776,7 +1776,7 @@ mod tests {
|
||||
|
||||
let type_var_with_default = TypeParam::TypeVar(TypeParamTypeVar {
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
default: Some(Box::new(constant.clone())),
|
||||
bound: None,
|
||||
name: Identifier::new("x", TextRange::default()),
|
||||
@@ -1797,7 +1797,7 @@ mod tests {
|
||||
fn any_over_type_param_type_var_tuple() {
|
||||
let type_var_tuple = TypeParam::TypeVarTuple(TypeParamTypeVarTuple {
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
name: Identifier::new("x", TextRange::default()),
|
||||
default: None,
|
||||
});
|
||||
@@ -1809,12 +1809,12 @@ mod tests {
|
||||
let constant = Expr::NumberLiteral(ExprNumberLiteral {
|
||||
value: Number::Int(Int::ONE),
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
});
|
||||
|
||||
let type_var_tuple_with_default = TypeParam::TypeVarTuple(TypeParamTypeVarTuple {
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
default: Some(Box::new(constant.clone())),
|
||||
name: Identifier::new("x", TextRange::default()),
|
||||
});
|
||||
@@ -1834,7 +1834,7 @@ mod tests {
|
||||
fn any_over_type_param_param_spec() {
|
||||
let type_param_spec = TypeParam::ParamSpec(TypeParamParamSpec {
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
name: Identifier::new("x", TextRange::default()),
|
||||
default: None,
|
||||
});
|
||||
@@ -1846,12 +1846,12 @@ mod tests {
|
||||
let constant = Expr::NumberLiteral(ExprNumberLiteral {
|
||||
value: Number::Int(Int::ONE),
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
});
|
||||
|
||||
let param_spec_with_default = TypeParam::TypeVarTuple(TypeParamTypeVarTuple {
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
default: Some(Box::new(constant.clone())),
|
||||
name: Identifier::new("x", TextRange::default()),
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::num::NonZeroU32;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
/// An AST node that has an index.
|
||||
@@ -16,64 +17,83 @@ where
|
||||
}
|
||||
|
||||
/// A unique index for a node within an AST.
|
||||
///
|
||||
/// This type is interiorly mutable to allow assigning node indices
|
||||
/// on-demand after parsing.
|
||||
#[derive(Default)]
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
|
||||
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
||||
pub struct AtomicNodeIndex(AtomicU32);
|
||||
|
||||
impl AtomicNodeIndex {
|
||||
/// Returns a placeholder `AtomicNodeIndex`.
|
||||
pub const fn dummy() -> AtomicNodeIndex {
|
||||
AtomicNodeIndex(AtomicU32::new(u32::MAX))
|
||||
}
|
||||
|
||||
/// Load the current value of the `AtomicNodeIndex`.
|
||||
pub fn load(&self) -> NodeIndex {
|
||||
NodeIndex(self.0.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// Set the value of the `AtomicNodeIndex`.
|
||||
pub fn set(&self, value: u32) {
|
||||
self.0.store(value, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// A unique index for a node within an AST.
|
||||
#[derive(PartialEq, Eq, Debug, PartialOrd, Ord, Clone, Copy, Hash)]
|
||||
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
||||
pub struct NodeIndex(u32);
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct NodeIndex(NonZeroU32);
|
||||
|
||||
impl NodeIndex {
|
||||
pub fn as_usize(self) -> usize {
|
||||
self.0 as _
|
||||
}
|
||||
/// A placeholder `NodeIndex`.
|
||||
pub const NONE: NodeIndex = NodeIndex(NonZeroU32::new(NodeIndex::_NONE).unwrap());
|
||||
|
||||
pub fn as_u32(self) -> u32 {
|
||||
self.0
|
||||
// Note that the index `u32::MAX` is reserved for the `NonZeroU32` niche, and
|
||||
// this placeholder also reserves the second highest index.
|
||||
const _NONE: u32 = u32::MAX - 1;
|
||||
|
||||
/// Returns the index as a `u32`. or `None` for `NodeIndex::NONE`.
|
||||
pub fn as_u32(self) -> Option<u32> {
|
||||
if self == NodeIndex::NONE {
|
||||
None
|
||||
} else {
|
||||
Some(self.0.get() - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for NodeIndex {
|
||||
fn from(value: u32) -> Self {
|
||||
NodeIndex(value)
|
||||
match NonZeroU32::new(value + 1).map(NodeIndex) {
|
||||
None | Some(NodeIndex::NONE) => panic!("exceeded maximum `NodeIndex`"),
|
||||
Some(index) => index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for AtomicNodeIndex {
|
||||
fn from(value: u32) -> Self {
|
||||
AtomicNodeIndex(AtomicU32::from(value))
|
||||
impl std::fmt::Debug for NodeIndex {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if *self == Self::NONE {
|
||||
f.debug_tuple("NodeIndex(None)").finish()
|
||||
} else {
|
||||
f.debug_tuple("NodeIndex").field(&self.0).finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A unique index for a node within an AST.
|
||||
///
|
||||
/// This type is interiorly mutable to allow assigning node indices
|
||||
/// on-demand after parsing.
|
||||
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
||||
pub struct AtomicNodeIndex(AtomicU32);
|
||||
|
||||
#[allow(clippy::declare_interior_mutable_const)]
|
||||
impl AtomicNodeIndex {
|
||||
/// A placeholder `AtomicNodeIndex`.
|
||||
pub const NONE: AtomicNodeIndex = AtomicNodeIndex(AtomicU32::new(NodeIndex::_NONE));
|
||||
|
||||
/// Load the current value of the `AtomicNodeIndex`.
|
||||
pub fn load(&self) -> NodeIndex {
|
||||
let index = NonZeroU32::new(self.0.load(Ordering::Relaxed))
|
||||
.expect("value stored was a valid `NodeIndex`");
|
||||
|
||||
NodeIndex(index)
|
||||
}
|
||||
|
||||
/// Set the value of the `AtomicNodeIndex`.
|
||||
pub fn set(&self, index: NodeIndex) {
|
||||
self.0.store(index.0.get(), Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AtomicNodeIndex {
|
||||
fn default() -> Self {
|
||||
Self::NONE
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AtomicNodeIndex {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if *self == AtomicNodeIndex::dummy() {
|
||||
f.debug_tuple("AtomicNodeIndex").finish_non_exhaustive()
|
||||
} else {
|
||||
f.debug_tuple("AtomicNodeIndex").field(&self.0).finish()
|
||||
}
|
||||
std::fmt::Debug::fmt(&self.load(), f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,3 +128,26 @@ impl Clone for AtomicNodeIndex {
|
||||
Self(AtomicU32::from(self.0.load(Ordering::Relaxed)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{AtomicNodeIndex, NodeIndex};
|
||||
|
||||
#[test]
|
||||
fn test_node_index() {
|
||||
let index = AtomicNodeIndex::NONE;
|
||||
|
||||
assert_eq!(index.load(), NodeIndex::NONE);
|
||||
assert_eq!(format!("{index:?}"), "NodeIndex(None)");
|
||||
|
||||
index.set(NodeIndex::from(1));
|
||||
assert_eq!(index.load(), NodeIndex::from(1));
|
||||
assert_eq!(index.load().as_u32(), Some(1));
|
||||
|
||||
let index = NodeIndex::from(0);
|
||||
assert_eq!(index.as_u32(), Some(0));
|
||||
|
||||
let index = NodeIndex::NONE;
|
||||
assert_eq!(index.as_u32(), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,7 +515,7 @@ impl FStringValue {
|
||||
|
||||
/// Returns `true` if the node represents an empty f-string literal.
|
||||
///
|
||||
/// Noteh that a [`FStringValue`] node will always have >= 1 [`FStringPart`]s inside it.
|
||||
/// Note that a [`FStringValue`] node will always have >= 1 [`FStringPart`]s inside it.
|
||||
/// This method checks whether the value of the concatenated parts is equal to the empty
|
||||
/// f-string, not whether the f-string has 0 parts inside it.
|
||||
pub fn is_empty_literal(&self) -> bool {
|
||||
@@ -681,6 +681,22 @@ impl TStringValue {
|
||||
pub fn elements(&self) -> impl Iterator<Item = &InterpolatedStringElement> {
|
||||
self.iter().flat_map(|tstring| tstring.elements.iter())
|
||||
}
|
||||
|
||||
/// Returns `true` if the node represents an empty t-string in the
|
||||
/// sense that `__iter__` returns an empty iterable.
|
||||
///
|
||||
/// Beware that empty t-strings are still truthy, i.e. `bool(t"") == True`.
|
||||
///
|
||||
/// Note that a [`TStringValue`] node will always contain at least one
|
||||
/// [`TString`] node. This method checks whether each of the constituent
|
||||
/// t-strings (in an implicitly concatenated t-string) are empty
|
||||
/// in the above sense.
|
||||
pub fn is_empty_iterable(&self) -> bool {
|
||||
match &self.inner {
|
||||
TStringValueInner::Single(tstring) => tstring.is_empty(),
|
||||
TStringValueInner::Concatenated(tstrings) => tstrings.iter().all(TString::is_empty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a TStringValue {
|
||||
@@ -1182,6 +1198,10 @@ impl TString {
|
||||
pub fn quote_style(&self) -> Quote {
|
||||
self.flags.quote_style()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.elements.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TString> for Expr {
|
||||
@@ -1582,7 +1602,7 @@ impl StringLiteral {
|
||||
Self {
|
||||
range,
|
||||
value: "".into(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
flags: StringLiteralFlags::empty().with_invalid(),
|
||||
}
|
||||
}
|
||||
@@ -1602,7 +1622,7 @@ impl From<StringLiteral> for Expr {
|
||||
fn from(payload: StringLiteral) -> Self {
|
||||
ExprStringLiteral {
|
||||
range: payload.range,
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
value: StringLiteralValue::single(payload),
|
||||
}
|
||||
.into()
|
||||
@@ -1981,7 +2001,7 @@ impl BytesLiteral {
|
||||
Self {
|
||||
range,
|
||||
value: Box::new([]),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
flags: BytesLiteralFlags::empty().with_invalid(),
|
||||
}
|
||||
}
|
||||
@@ -1991,7 +2011,7 @@ impl From<BytesLiteral> for Expr {
|
||||
fn from(payload: BytesLiteral) -> Self {
|
||||
ExprBytesLiteral {
|
||||
range: payload.range,
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
value: BytesLiteralValue::single(payload),
|
||||
}
|
||||
.into()
|
||||
@@ -3525,7 +3545,7 @@ impl Identifier {
|
||||
pub fn new(id: impl Into<Name>, range: TextRange) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
range,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,12 +196,12 @@ mod tests {
|
||||
fn debug() {
|
||||
let continue_statement = StmtContinue {
|
||||
range: TextRange::new(TextSize::new(18), TextSize::new(26)),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
};
|
||||
|
||||
let break_statement = StmtBreak {
|
||||
range: TextRange::new(TextSize::new(55), TextSize::new(60)),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
};
|
||||
|
||||
let source = r"# leading comment
|
||||
|
||||
@@ -69,7 +69,7 @@ mod tests {
|
||||
fn equality() {
|
||||
let continue_statement = StmtContinue {
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
};
|
||||
|
||||
let ref_a = NodeRefEqualityKey::from_ref(AnyNodeRef::from(&continue_statement));
|
||||
@@ -83,7 +83,7 @@ mod tests {
|
||||
fn inequality() {
|
||||
let continue_statement = StmtContinue {
|
||||
range: TextRange::default(),
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
};
|
||||
|
||||
let boxed = Box::new(continue_statement.clone());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user