mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[ty] Implemented support for "rename" language server feature (#19551)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
This PR adds support for the "rename" language server feature. It builds upon existing functionality used for "go to references". The "rename" feature involves two language server requests. The first is a "prepare rename" request that determines whether renaming should be possible for the identifier at the current offset. The second is a "rename" request that returns a list of file ranges where the rename should be applied. Care must be taken when attempting to rename symbols that span files, especially if the symbols are defined in files that are not part of the project. We don't want to modify code in the user's Python environment or in the vendored stub files. I found a few bugs in the "go to references" feature when implementing "rename", and those bug fixes are included in this PR. --------- Co-authored-by: UnboundVariable <unbound@gmail.com>
This commit is contained in:
parent
b96aa4605b
commit
b005cdb7ff
16 changed files with 1065 additions and 73 deletions
|
@ -18,8 +18,8 @@ pub use python_platform::PythonPlatform;
|
|||
pub use semantic_model::{Completion, CompletionKind, HasType, NameKind, SemanticModel};
|
||||
pub use site_packages::{PythonEnvironment, SitePackagesPaths, SysPrefixPathOrigin};
|
||||
pub use types::ide_support::{
|
||||
ResolvedDefinition, definitions_for_attribute, definitions_for_imported_symbol,
|
||||
definitions_for_name, map_stub_definition,
|
||||
ImportAliasResolution, ResolvedDefinition, definitions_for_attribute,
|
||||
definitions_for_imported_symbol, definitions_for_name, map_stub_definition,
|
||||
};
|
||||
pub use util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ use ruff_python_ast::name::Name;
|
|||
use ruff_text_size::{Ranged, TextRange};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
pub use resolve_definition::{ResolvedDefinition, map_stub_definition};
|
||||
pub use resolve_definition::{ImportAliasResolution, ResolvedDefinition, map_stub_definition};
|
||||
use resolve_definition::{find_symbol_in_scope, resolve_definition};
|
||||
|
||||
pub(crate) fn all_declarations_and_bindings<'db>(
|
||||
|
@ -517,7 +517,12 @@ pub fn definitions_for_name<'db>(
|
|||
let mut resolved_definitions = Vec::new();
|
||||
|
||||
for definition in &all_definitions {
|
||||
let resolved = resolve_definition(db, *definition, Some(name_str));
|
||||
let resolved = resolve_definition(
|
||||
db,
|
||||
*definition,
|
||||
Some(name_str),
|
||||
ImportAliasResolution::ResolveAliases,
|
||||
);
|
||||
resolved_definitions.extend(resolved);
|
||||
}
|
||||
|
||||
|
@ -528,7 +533,14 @@ pub fn definitions_for_name<'db>(
|
|||
};
|
||||
find_symbol_in_scope(db, builtins_scope, name_str)
|
||||
.into_iter()
|
||||
.flat_map(|def| resolve_definition(db, def, Some(name_str)))
|
||||
.flat_map(|def| {
|
||||
resolve_definition(
|
||||
db,
|
||||
def,
|
||||
Some(name_str),
|
||||
ImportAliasResolution::ResolveAliases,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
resolved_definitions
|
||||
|
@ -577,7 +589,12 @@ pub fn definitions_for_attribute<'db>(
|
|||
if let Some(module_file) = module_literal.module(db).file(db) {
|
||||
let module_scope = global_scope(db, module_file);
|
||||
for def in find_symbol_in_scope(db, module_scope, name_str) {
|
||||
resolved.extend(resolve_definition(db, def, Some(name_str)));
|
||||
resolved.extend(resolve_definition(
|
||||
db,
|
||||
def,
|
||||
Some(name_str),
|
||||
ImportAliasResolution::ResolveAliases,
|
||||
));
|
||||
}
|
||||
}
|
||||
continue;
|
||||
|
@ -613,7 +630,12 @@ pub fn definitions_for_attribute<'db>(
|
|||
// Check declarations first
|
||||
for decl in use_def.all_reachable_symbol_declarations(place_id) {
|
||||
if let Some(def) = decl.declaration.definition() {
|
||||
resolved.extend(resolve_definition(db, def, Some(name_str)));
|
||||
resolved.extend(resolve_definition(
|
||||
db,
|
||||
def,
|
||||
Some(name_str),
|
||||
ImportAliasResolution::ResolveAliases,
|
||||
));
|
||||
break 'scopes;
|
||||
}
|
||||
}
|
||||
|
@ -621,7 +643,12 @@ pub fn definitions_for_attribute<'db>(
|
|||
// If no declarations found, check bindings
|
||||
for binding in use_def.all_reachable_symbol_bindings(place_id) {
|
||||
if let Some(def) = binding.binding.definition() {
|
||||
resolved.extend(resolve_definition(db, def, Some(name_str)));
|
||||
resolved.extend(resolve_definition(
|
||||
db,
|
||||
def,
|
||||
Some(name_str),
|
||||
ImportAliasResolution::ResolveAliases,
|
||||
));
|
||||
break 'scopes;
|
||||
}
|
||||
}
|
||||
|
@ -640,7 +667,12 @@ pub fn definitions_for_attribute<'db>(
|
|||
// Check declarations first
|
||||
for decl in use_def.all_reachable_member_declarations(place_id) {
|
||||
if let Some(def) = decl.declaration.definition() {
|
||||
resolved.extend(resolve_definition(db, def, Some(name_str)));
|
||||
resolved.extend(resolve_definition(
|
||||
db,
|
||||
def,
|
||||
Some(name_str),
|
||||
ImportAliasResolution::ResolveAliases,
|
||||
));
|
||||
break 'scopes;
|
||||
}
|
||||
}
|
||||
|
@ -648,7 +680,12 @@ pub fn definitions_for_attribute<'db>(
|
|||
// If no declarations found, check bindings
|
||||
for binding in use_def.all_reachable_member_bindings(place_id) {
|
||||
if let Some(def) = binding.binding.definition() {
|
||||
resolved.extend(resolve_definition(db, def, Some(name_str)));
|
||||
resolved.extend(resolve_definition(
|
||||
db,
|
||||
def,
|
||||
Some(name_str),
|
||||
ImportAliasResolution::ResolveAliases,
|
||||
));
|
||||
break 'scopes;
|
||||
}
|
||||
}
|
||||
|
@ -715,11 +752,15 @@ pub fn definitions_for_keyword_argument<'db>(
|
|||
/// Find the definitions for a symbol imported via `from x import y as z` statement.
|
||||
/// This function handles the case where the cursor is on the original symbol name `y`.
|
||||
/// Returns the same definitions as would be found for the alias `z`.
|
||||
/// The `alias_resolution` parameter controls whether symbols imported with local import
|
||||
/// aliases (like "x" in "from a import b as x") are resolved to their targets or kept
|
||||
/// as aliases.
|
||||
pub fn definitions_for_imported_symbol<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: File,
|
||||
import_node: &ast::StmtImportFrom,
|
||||
symbol_name: &str,
|
||||
alias_resolution: ImportAliasResolution,
|
||||
) -> Vec<ResolvedDefinition<'db>> {
|
||||
let mut visited = FxHashSet::default();
|
||||
resolve_definition::resolve_from_import_definitions(
|
||||
|
@ -728,6 +769,7 @@ pub fn definitions_for_imported_symbol<'db>(
|
|||
import_node,
|
||||
symbol_name,
|
||||
&mut visited,
|
||||
alias_resolution,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -821,6 +863,15 @@ mod resolve_definition {
|
|||
//! "resolved definitions". This is done recursively to find the original
|
||||
//! definition targeted by the import.
|
||||
|
||||
/// Controls whether local import aliases should be resolved to their targets or returned as-is.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ImportAliasResolution {
|
||||
/// Resolve import aliases to their original definitions
|
||||
ResolveAliases,
|
||||
/// Keep import aliases as-is, don't resolve to original definitions
|
||||
PreserveAliases,
|
||||
}
|
||||
|
||||
use indexmap::IndexSet;
|
||||
use ruff_db::files::{File, FileRange};
|
||||
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
||||
|
@ -865,9 +916,16 @@ mod resolve_definition {
|
|||
db: &'db dyn Db,
|
||||
definition: Definition<'db>,
|
||||
symbol_name: Option<&str>,
|
||||
alias_resolution: ImportAliasResolution,
|
||||
) -> Vec<ResolvedDefinition<'db>> {
|
||||
let mut visited = FxHashSet::default();
|
||||
let resolved = resolve_definition_recursive(db, definition, &mut visited, symbol_name);
|
||||
let resolved = resolve_definition_recursive(
|
||||
db,
|
||||
definition,
|
||||
&mut visited,
|
||||
symbol_name,
|
||||
alias_resolution,
|
||||
);
|
||||
|
||||
// If resolution failed, return the original definition as fallback
|
||||
if resolved.is_empty() {
|
||||
|
@ -883,6 +941,7 @@ mod resolve_definition {
|
|||
definition: Definition<'db>,
|
||||
visited: &mut FxHashSet<Definition<'db>>,
|
||||
symbol_name: Option<&str>,
|
||||
alias_resolution: ImportAliasResolution,
|
||||
) -> Vec<ResolvedDefinition<'db>> {
|
||||
// Prevent infinite recursion if there are circular imports
|
||||
if visited.contains(&definition) {
|
||||
|
@ -928,7 +987,14 @@ mod resolve_definition {
|
|||
|
||||
// For `ImportFrom`, we need to resolve the original imported symbol name
|
||||
// (alias.name), not the local alias (symbol_name)
|
||||
resolve_from_import_definitions(db, file, import_node, &alias.name, visited)
|
||||
resolve_from_import_definitions(
|
||||
db,
|
||||
file,
|
||||
import_node,
|
||||
&alias.name,
|
||||
visited,
|
||||
alias_resolution,
|
||||
)
|
||||
}
|
||||
|
||||
// For star imports, try to resolve to the specific symbol being accessed
|
||||
|
@ -939,7 +1005,14 @@ mod resolve_definition {
|
|||
|
||||
// If we have a symbol name, use the helper to resolve it in the target module
|
||||
if let Some(symbol_name) = symbol_name {
|
||||
resolve_from_import_definitions(db, file, import_node, symbol_name, visited)
|
||||
resolve_from_import_definitions(
|
||||
db,
|
||||
file,
|
||||
import_node,
|
||||
symbol_name,
|
||||
visited,
|
||||
alias_resolution,
|
||||
)
|
||||
} else {
|
||||
// No symbol context provided, can't resolve star import
|
||||
Vec::new()
|
||||
|
@ -958,7 +1031,21 @@ mod resolve_definition {
|
|||
import_node: &ast::StmtImportFrom,
|
||||
symbol_name: &str,
|
||||
visited: &mut FxHashSet<Definition<'db>>,
|
||||
alias_resolution: ImportAliasResolution,
|
||||
) -> Vec<ResolvedDefinition<'db>> {
|
||||
if alias_resolution == ImportAliasResolution::PreserveAliases {
|
||||
for alias in &import_node.names {
|
||||
if let Some(asname) = &alias.asname {
|
||||
if asname.as_str() == symbol_name {
|
||||
return vec![ResolvedDefinition::FileWithRange(FileRange::new(
|
||||
file,
|
||||
asname.range,
|
||||
))];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the target module file
|
||||
let module_file = {
|
||||
// Resolve the module being imported from (handles both relative and absolute imports)
|
||||
|
@ -987,7 +1074,13 @@ mod resolve_definition {
|
|||
} else {
|
||||
let mut resolved_definitions = Vec::new();
|
||||
for def in definitions_in_module {
|
||||
let resolved = resolve_definition_recursive(db, def, visited, Some(symbol_name));
|
||||
let resolved = resolve_definition_recursive(
|
||||
db,
|
||||
def,
|
||||
visited,
|
||||
Some(symbol_name),
|
||||
alias_resolution,
|
||||
);
|
||||
resolved_definitions.extend(resolved);
|
||||
}
|
||||
resolved_definitions
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue