mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 14:21:24 +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
|
@ -19,6 +19,7 @@ use ruff_python_ast::{
|
|||
visitor::source_order::{SourceOrderVisitor, TraversalSignal},
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ty_python_semantic::ImportAliasResolution;
|
||||
|
||||
/// Mode for references search behavior
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -27,8 +28,10 @@ pub enum ReferencesMode {
|
|||
References,
|
||||
/// Find all references but skip the declaration
|
||||
ReferencesSkipDeclaration,
|
||||
/// Find references for rename operations (behavior differs for imported symbols)
|
||||
/// Find references for rename operations, limited to current file only
|
||||
Rename,
|
||||
/// Find references for multi-file rename operations (searches across all files)
|
||||
RenameMultiFile,
|
||||
/// Find references for document highlights (limits search to current file)
|
||||
DocumentHighlights,
|
||||
}
|
||||
|
@ -42,7 +45,14 @@ pub(crate) fn references(
|
|||
mode: ReferencesMode,
|
||||
) -> Option<Vec<ReferenceTarget>> {
|
||||
// Get the definitions for the symbol at the cursor position
|
||||
let target_definitions_nav = goto_target.get_definition_targets(file, db, None)?;
|
||||
|
||||
// When finding references, do not resolve any local aliases.
|
||||
let target_definitions_nav = goto_target.get_definition_targets(
|
||||
file,
|
||||
db,
|
||||
None,
|
||||
ImportAliasResolution::PreserveAliases,
|
||||
)?;
|
||||
let target_definitions: Vec<NavigationTarget> = target_definitions_nav.into_iter().collect();
|
||||
|
||||
// Extract the target text from the goto target for fast comparison
|
||||
|
@ -60,7 +70,12 @@ pub(crate) fn references(
|
|||
);
|
||||
|
||||
// Check if we should search across files based on the mode
|
||||
let search_across_files = !matches!(mode, ReferencesMode::DocumentHighlights);
|
||||
let search_across_files = matches!(
|
||||
mode,
|
||||
ReferencesMode::References
|
||||
| ReferencesMode::ReferencesSkipDeclaration
|
||||
| ReferencesMode::RenameMultiFile
|
||||
);
|
||||
|
||||
// Check if the symbol is potentially visible outside of this module
|
||||
if search_across_files && is_symbol_externally_visible(goto_target) {
|
||||
|
@ -211,6 +226,17 @@ impl<'a> SourceOrderVisitor<'a> for LocalReferencesFinder<'a> {
|
|||
self.check_identifier_reference(rest_name);
|
||||
}
|
||||
}
|
||||
AnyNodeRef::Alias(alias) if self.should_include_declaration() => {
|
||||
// Handle import alias declarations
|
||||
if let Some(asname) = &alias.asname {
|
||||
self.check_identifier_reference(asname);
|
||||
}
|
||||
// Only check the original name if it matches our target text
|
||||
// This is for cases where we're renaming the imported symbol name itself
|
||||
if alias.name.id == self.target_text {
|
||||
self.check_identifier_reference(&alias.name);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
@ -231,6 +257,7 @@ impl LocalReferencesFinder<'_> {
|
|||
ReferencesMode::References
|
||||
| ReferencesMode::DocumentHighlights
|
||||
| ReferencesMode::Rename
|
||||
| ReferencesMode::RenameMultiFile
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -259,21 +286,21 @@ impl LocalReferencesFinder<'_> {
|
|||
let offset = covering_node.node().start();
|
||||
|
||||
if let Some(goto_target) = GotoTarget::from_covering_node(covering_node, offset) {
|
||||
// Use the range of the covering node (the identifier) rather than the goto target
|
||||
// This ensures we highlight just the identifier, not the entire expression
|
||||
let range = covering_node.node().range();
|
||||
|
||||
// Get the definitions for this goto target
|
||||
if let Some(current_definitions_nav) =
|
||||
goto_target.get_definition_targets(self.file, self.db, None)
|
||||
{
|
||||
if let Some(current_definitions_nav) = goto_target.get_definition_targets(
|
||||
self.file,
|
||||
self.db,
|
||||
None,
|
||||
ImportAliasResolution::PreserveAliases,
|
||||
) {
|
||||
let current_definitions: Vec<NavigationTarget> =
|
||||
current_definitions_nav.into_iter().collect();
|
||||
// Check if any of the current definitions match our target definitions
|
||||
if self.navigation_targets_match(¤t_definitions) {
|
||||
// Determine if this is a read or write reference
|
||||
let kind = self.determine_reference_kind(covering_node);
|
||||
let target = ReferenceTarget::new(self.file, range, kind);
|
||||
let target =
|
||||
ReferenceTarget::new(self.file, covering_node.node().range(), kind);
|
||||
self.references.push(target);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue