Compare commits

...

20 Commits

Author SHA1 Message Date
Ibraheem Ahmed
992e77e4d0 suggestions from code review 2025-08-22 19:36:40 -04:00
Ibraheem Ahmed
8d05367d60 reload Project after deserialization 2025-08-22 19:09:22 -04:00
Ibraheem Ahmed
2402831223 experimental persistent caching 2025-08-22 19:09:21 -04:00
Ibraheem Ahmed
7abc41727b [ty] Shrink size of AstNodeRef (#20028)
## Summary

Removes the `module_ptr` field from `AstNodeRef` in release mode, and
change `NodeIndex` to a `NonZeroU32` to reduce the size of
`Option<AstNodeRef<_>>` fields.

I believe CI runs in debug mode, so this won't show up in the memory
report, but this reduces memory by ~2% in release mode.
2025-08-22 17:03:22 -04:00
chiri
886c4e4773 [flake8-use-pathlib] Fix PTH211 autofix (#20049)
## Summary
Part of #20009
2025-08-22 13:35:08 -05:00
Alex Waygood
bc6ea68733 [ty] Add precise iteration and unpacking inference for string literals and bytes literals (#20023)
## Summary

Previously we held off from doing this because we weren't sure that it
was worth the added complexity cost. But our code has changed in the
months since we made that initial decision, and I think the structure of
the code is such that it no longer really leads to much added complexity
to add precise inference when unpacking a string literal or a bytes
literal.

The improved inference we gain from this has real benefits to users (see
the mypy_primer report), and this PR doesn't appear to have a
performance impact.

## Test plan

mdtests
2025-08-22 19:33:08 +01:00
Micha Reiser
796819e7a0 [ty] Disallow std::env and io methods in most ty crates (#20046)
## Summary

We use the `System` abstraction in ty to abstract away the host/system
on which ty runs.
This has a few benefits:

* Tests can run in full isolation using a memory system (that uses an
in-memory file system)
* The LSP has a custom implementation where `read_to_string` returns the
content as seen by the editor (e.g. unsaved changes) instead of always
returning the content as it is stored on disk
* We don't require any file system polyfills for wasm in the browser


However, it does require extra care that we don't accidentally use
`std::fs` or `std::env` (etc.) methods in ty's code base (which is very
easy).

This PR sets up Clippy and disallows the most common methods, instead
pointing users towards the corresponding `System` methods.

The setup is a bit awkward because clippy doesn't support inheriting
configurations. That means, a crate can only override the entire
workspace configuration or not at all.
The approach taken in this PR is:

* Configure the disallowed methods at the workspace level
* Allow `disallowed_methods` at the workspace level
* Enable the lint at the crate level using the warn attribute (in code)


The obvious downside is that it won't work if we ever want to disallow
other methods, but we can figure that out once we reach that point.

What about false positives: Just add an `allow` and move on with your
life :) This isn't something that we have to enforce strictly; the goal
is to catch accidental misuse.

## Test Plan

Clippy found a place where we incorrectly used `std::fs::read_to_string`
2025-08-22 11:13:47 -07:00
Vivek Dasari
5508e8e528 Add testing helper to compare stable vs preview snapshots (#19715)
## Summary
This PR implements a diff test helper `assert_diagnostics_diff` as
described in #19351. The diff file includes both the settings ( e.g.
`+linter.preview = enabled`) and the snapshot data itself.

The current implementation looks for each old diagnostic in the new
snapshot. This works when the preview behavior adds/removes a couple
diagnostics. This implementation does not work well when every
diagnostic is modified (e.g. a "fix" is added).
https://github.com/astral-sh/ruff/pull/19715#discussion_r2259410763 has
ideas for future improvements to this implementation.

The example usage in this PR writes the diff to `preview_diff` file
instead of `preview` file, which might be a useful convention to keep.


## Test Plan
- Included a unit test at:
https://github.com/astral-sh/ruff/pull/19715/files#diff-d49487fe3e8a8585529f62c2df2a2b0a4c44267a1f93d1e859dff1d9f8771d36R523
- Example usage of this new test helper:
https://github.com/astral-sh/ruff/pull/19715/files#diff-2a33ac11146d1794c01a29549a6041d3af6fb6f9b423a31ade12a88d1951b0c2R1
2025-08-22 12:49:34 -05:00
chiri
0be3e1fbbf [flake8-use-pathlib] Add autofix for PTH211 (#20009)
## Summary
Part of https://github.com/astral-sh/ruff/issues/2331
2025-08-22 12:38:37 -05:00
Micha Reiser
5d217b7f46 [ty] Add type as detail to completion items (#20047)
## Summary

@BurntSushi was so kind as to find me an easy task to do some coding
before I'm off to PTO.

This PR adds the type to completion items (see the gray little text at
the end of a completion item).



https://github.com/user-attachments/assets/c0a86061-fa12-47b4-b43c-3c646771a69d
2025-08-22 12:32:53 -04:00
Dylan
0b6ce1c788 [ruff] Handle empty t-strings in unnecessary-empty-iterable-within-deque-call (RUF037) (#20045)
Adds a method to `TStringValue` to detect whether the t-string is empty
_as an iterable_. Note the subtlety here that, unlike f-strings, an
empty t-string is still truthy (i.e. `bool(t"")==True`).

Closes #19951
2025-08-22 10:23:49 -05:00
Matthew Mckee
0e9d77e43a Fix incorrect lsp inlay hint type (#20044) 2025-08-22 17:12:49 +02:00
Carl Meyer
8b827c3c6c [ty] rename BareTypeAliasType to ManualPEP695TypeAliasType (#20037)
## Summary

Rename `TypeAliasType::Bare` to `TypeAliasType::ManualPEP695`, and
`BareTypeAliasType` to `ManualPEP695TypeAliasType`.

Why?

Both existing variants of `TypeAliasType` are specific to features added
in PEP 695 (which introduced both the `type` statement and
`types.TypeAliasType`), so it doesn't make sense to name one with the
name `PEP695` and not the other.

A "bare" type alias, in my mind, is a legacy type alias like `IntOrStr =
int | str`, which is "bare" in that there is nothing at all
distinguishing it as a type alias. I will want to use the "bare" name
for this variant, in a future PR.

The renamed variant here describes a type alias created with `IntOrStr =
types.TypeAliasType("IntOrStr", int | str)`, which is not "bare", it's
just "manually" instantiated instead of using the `type` statement
syntax sugar. (This is useful when using the `typing_extensions`
backport of `TypeAliasType` on older Python versions.)

## Test Plan

Pure rename, existing tests pass.
2025-08-22 07:40:29 -07:00
Max Mynter
c22395dbc6 [ruff] Fix false positive for t-strings in default-factory-kwarg (RUF026) (#20032)
Closes #19993

## Summary
Recognize t strings as never being callable to avoid false positives on
RUF026.
2025-08-22 09:29:42 -05:00
Micha Reiser
11f521c768 [ty] Close signature help after ) (#20017) 2025-08-22 16:09:22 +02:00
Micha Reiser
c5e05df966 [ty] Cancel background tasks when shutdown is requested (#20039) 2025-08-22 10:20:13 +02:00
github-actions[bot]
7a44ea680e [ty] Sync vendored typeshed stubs (#20031)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-08-21 21:32:48 +00:00
Alex Waygood
f82025d919 [ty] Improve diagnostics for bad calls to functions (#20022) 2025-08-21 22:00:44 +01:00
Micha Reiser
365f521c37 [ty] Fix incorrect docstring in call signature completion (#20021)
## Summary

This PR fixes https://github.com/astral-sh/ty/issues/1071

The core issue is that `CallableType` is a salsa interned but
`Signature` (which `CallableType` stores) ignores the `Definition` in
its `Eq` and `Hash` implementation.

This PR tries to simplest fix by removing the custom `Eq` and `Hash`
implementation. The main downside of this fix is that it can increase
memory usage because `CallableType`s that are equal except for their
`Definition` are now interned separately.

The alternative is to remove `Definition` from `CallableType` and
instead, call `bindings` directly on the callee (call_expression.func).
However, this would require
addressing the TODO 

here
39ee71c2a5/crates/ty_python_semantic/src/types.rs (L4582-L4586)

This might probably be worth addressing anyway, but is the more involved
fix. That's why I opted for removing the custom `Eq` implementation.

We already "ignore" the definition during normalization, thank's to
Alex's work in https://github.com/astral-sh/ruff/pull/19615

## Test Plan



https://github.com/user-attachments/assets/248d1cb1-12fd-4441-adab-b7e0866d23eb
2025-08-21 16:36:40 -04:00
Aria Desires
fc5321e000 [ty] fix GotoTargets for keyword args in nested function calls (#20013)
While implementing similar logic for initializers I noticed that this
code appeared to be walking the ancestors in the wrong direction, and so
if you have nested function calls it would always grab the outermost one
instead of the closest-ancestor.

The four copies of the test are because there's something really evil in
our caching that can't seem to be demonstrated in our cursor testing
framework, which I'm filing a followup for.
2025-08-21 20:19:52 +00:00
911 changed files with 25648 additions and 24080 deletions

35
Cargo.lock generated
View File

@@ -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"

View File

@@ -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

View File

@@ -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" },
]

View File

@@ -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"] }

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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`.

View File

@@ -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),

View File

@@ -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))

View File

@@ -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;
}

View File

@@ -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),

View File

@@ -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))
}

View File

@@ -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(),
})
});

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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),

View File

@@ -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 {

View File

@@ -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,
})
}

View File

@@ -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,
})

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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(())
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,
})
}

View File

@@ -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(

View File

@@ -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(

View File

@@ -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)),
});

View File

@@ -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,
})),

View File

@@ -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);

View File

@@ -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())

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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(

View File

@@ -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))
}

View File

@@ -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()
};

View File

@@ -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(

View File

@@ -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()),

View File

@@ -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()),

View File

@@ -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());

View File

@@ -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()],
})

View File

@@ -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,
})
});

View File

@@ -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())
}

View File

@@ -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,
})
}

View File

@@ -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(

View File

@@ -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,
}))

View File

@@ -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__{}_{}",

View File

@@ -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;

View File

@@ -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,
))
});
}

View File

@@ -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

View File

@@ -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(...)`

View File

@@ -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(...)`

View File

@@ -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()
}
}

View File

@@ -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,
})
}

View File

@@ -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())

View File

@@ -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);

View File

@@ -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()),

View File

@@ -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),

View File

@@ -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(),
)));

View File

@@ -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,

View File

@@ -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()
}

View File

@@ -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))
}

View File

@@ -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(),

View File

@@ -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()))

View File

@@ -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,
}),

View File

@@ -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()))

View File

@@ -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(),
),

View File

@@ -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);

View File

@@ -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())
}

View File

@@ -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()))
}

View File

@@ -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,
}
}

View File

@@ -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())
}

View File

@@ -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())
}

View File

@@ -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()))
}

View File

@@ -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(),
},
)]

View File

@@ -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(),

View File

@@ -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

View File

@@ -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,

View File

@@ -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())),

View File

@@ -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())
}

View File

@@ -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(),

View File

@@ -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 {

View File

@@ -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(

View File

@@ -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,
})
}
}

View File

@@ -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

View File

@@ -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(())
}
}

View File

@@ -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()),
});

View File

@@ -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);
}
}

View File

@@ -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,
}
}

View File

@@ -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

View File

@@ -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