mirror of https://github.com/astral-sh/ruff
[ty] Support renaming import aliases (#21792)
This commit is contained in:
parent
b2fb421ddd
commit
9714c589e1
|
|
@ -73,19 +73,29 @@ pub(crate) enum GotoTarget<'a> {
|
||||||
/// ```
|
/// ```
|
||||||
ImportModuleAlias {
|
ImportModuleAlias {
|
||||||
alias: &'a ast::Alias,
|
alias: &'a ast::Alias,
|
||||||
|
asname: &'a ast::Identifier,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// In an import statement, the named under which the symbol is exported
|
||||||
|
/// in the imported file.
|
||||||
|
///
|
||||||
|
/// ```py
|
||||||
|
/// from foo import bar as baz
|
||||||
|
/// ^^^
|
||||||
|
/// ```
|
||||||
|
ImportExportedName {
|
||||||
|
alias: &'a ast::Alias,
|
||||||
|
import_from: &'a ast::StmtImportFrom,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Import alias in from import statement
|
/// Import alias in from import statement
|
||||||
/// ```py
|
/// ```py
|
||||||
/// from foo import bar as baz
|
/// from foo import bar as baz
|
||||||
/// ^^^
|
|
||||||
/// from foo import bar as baz
|
|
||||||
/// ^^^
|
/// ^^^
|
||||||
/// ```
|
/// ```
|
||||||
ImportSymbolAlias {
|
ImportSymbolAlias {
|
||||||
alias: &'a ast::Alias,
|
alias: &'a ast::Alias,
|
||||||
range: TextRange,
|
asname: &'a ast::Identifier,
|
||||||
import_from: &'a ast::StmtImportFrom,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Go to on the exception handler variable
|
/// Go to on the exception handler variable
|
||||||
|
|
@ -290,8 +300,9 @@ impl GotoTarget<'_> {
|
||||||
GotoTarget::FunctionDef(function) => function.inferred_type(model),
|
GotoTarget::FunctionDef(function) => function.inferred_type(model),
|
||||||
GotoTarget::ClassDef(class) => class.inferred_type(model),
|
GotoTarget::ClassDef(class) => class.inferred_type(model),
|
||||||
GotoTarget::Parameter(parameter) => parameter.inferred_type(model),
|
GotoTarget::Parameter(parameter) => parameter.inferred_type(model),
|
||||||
GotoTarget::ImportSymbolAlias { alias, .. } => alias.inferred_type(model),
|
GotoTarget::ImportSymbolAlias { alias, .. }
|
||||||
GotoTarget::ImportModuleAlias { alias } => alias.inferred_type(model),
|
| GotoTarget::ImportModuleAlias { alias, .. }
|
||||||
|
| GotoTarget::ImportExportedName { alias, .. } => alias.inferred_type(model),
|
||||||
GotoTarget::ExceptVariable(except) => except.inferred_type(model),
|
GotoTarget::ExceptVariable(except) => except.inferred_type(model),
|
||||||
GotoTarget::KeywordArgument { keyword, .. } => keyword.value.inferred_type(model),
|
GotoTarget::KeywordArgument { keyword, .. } => keyword.value.inferred_type(model),
|
||||||
// When asking the type of a callable, usually you want the callable itself?
|
// When asking the type of a callable, usually you want the callable itself?
|
||||||
|
|
@ -378,7 +389,9 @@ impl GotoTarget<'_> {
|
||||||
alias_resolution: ImportAliasResolution,
|
alias_resolution: ImportAliasResolution,
|
||||||
) -> Option<Definitions<'db>> {
|
) -> Option<Definitions<'db>> {
|
||||||
let definitions = match self {
|
let definitions = match self {
|
||||||
GotoTarget::Expression(expression) => definitions_for_expression(model, *expression),
|
GotoTarget::Expression(expression) => {
|
||||||
|
definitions_for_expression(model, *expression, alias_resolution)
|
||||||
|
}
|
||||||
// For already-defined symbols, they are their own definitions
|
// For already-defined symbols, they are their own definitions
|
||||||
GotoTarget::FunctionDef(function) => Some(vec![ResolvedDefinition::Definition(
|
GotoTarget::FunctionDef(function) => Some(vec![ResolvedDefinition::Definition(
|
||||||
function.definition(model),
|
function.definition(model),
|
||||||
|
|
@ -393,22 +406,21 @@ impl GotoTarget<'_> {
|
||||||
)]),
|
)]),
|
||||||
|
|
||||||
// For import aliases (offset within 'y' or 'z' in "from x import y as z")
|
// For import aliases (offset within 'y' or 'z' in "from x import y as z")
|
||||||
GotoTarget::ImportSymbolAlias {
|
GotoTarget::ImportSymbolAlias { asname, .. } => Some(definitions_for_name(
|
||||||
alias, import_from, ..
|
model,
|
||||||
} => {
|
asname.as_str(),
|
||||||
if let Some(asname) = alias.asname.as_ref()
|
AnyNodeRef::from(*asname),
|
||||||
&& alias_resolution == ImportAliasResolution::PreserveAliases
|
alias_resolution,
|
||||||
{
|
)),
|
||||||
Some(definitions_for_name(model, asname.as_str(), asname.into()))
|
|
||||||
} else {
|
GotoTarget::ImportExportedName { alias, import_from } => {
|
||||||
let symbol_name = alias.name.as_str();
|
let symbol_name = alias.name.as_str();
|
||||||
Some(definitions_for_imported_symbol(
|
Some(definitions_for_imported_symbol(
|
||||||
model,
|
model,
|
||||||
import_from,
|
import_from,
|
||||||
symbol_name,
|
symbol_name,
|
||||||
alias_resolution,
|
alias_resolution,
|
||||||
))
|
))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GotoTarget::ImportModuleComponent {
|
GotoTarget::ImportModuleComponent {
|
||||||
|
|
@ -423,15 +435,12 @@ impl GotoTarget<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle import aliases (offset within 'z' in "import x.y as z")
|
// Handle import aliases (offset within 'z' in "import x.y as z")
|
||||||
GotoTarget::ImportModuleAlias { alias } => {
|
GotoTarget::ImportModuleAlias { asname, .. } => Some(definitions_for_name(
|
||||||
if let Some(asname) = alias.asname.as_ref()
|
model,
|
||||||
&& alias_resolution == ImportAliasResolution::PreserveAliases
|
asname.as_str(),
|
||||||
{
|
AnyNodeRef::from(*asname),
|
||||||
Some(definitions_for_name(model, asname.as_str(), asname.into()))
|
alias_resolution,
|
||||||
} else {
|
)),
|
||||||
definitions_for_module(model, Some(alias.name.as_str()), 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle keyword arguments in call expressions
|
// Handle keyword arguments in call expressions
|
||||||
GotoTarget::KeywordArgument {
|
GotoTarget::KeywordArgument {
|
||||||
|
|
@ -454,12 +463,22 @@ impl GotoTarget<'_> {
|
||||||
// because they're not expressions
|
// because they're not expressions
|
||||||
GotoTarget::PatternMatchRest(pattern_mapping) => {
|
GotoTarget::PatternMatchRest(pattern_mapping) => {
|
||||||
pattern_mapping.rest.as_ref().map(|name| {
|
pattern_mapping.rest.as_ref().map(|name| {
|
||||||
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
|
definitions_for_name(
|
||||||
|
model,
|
||||||
|
name.as_str(),
|
||||||
|
AnyNodeRef::Identifier(name),
|
||||||
|
alias_resolution,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
GotoTarget::PatternMatchAsName(pattern_as) => pattern_as.name.as_ref().map(|name| {
|
GotoTarget::PatternMatchAsName(pattern_as) => pattern_as.name.as_ref().map(|name| {
|
||||||
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
|
definitions_for_name(
|
||||||
|
model,
|
||||||
|
name.as_str(),
|
||||||
|
AnyNodeRef::Identifier(name),
|
||||||
|
alias_resolution,
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
GotoTarget::PatternKeywordArgument(pattern_keyword) => {
|
GotoTarget::PatternKeywordArgument(pattern_keyword) => {
|
||||||
|
|
@ -468,12 +487,18 @@ impl GotoTarget<'_> {
|
||||||
model,
|
model,
|
||||||
name.as_str(),
|
name.as_str(),
|
||||||
AnyNodeRef::Identifier(name),
|
AnyNodeRef::Identifier(name),
|
||||||
|
alias_resolution,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
GotoTarget::PatternMatchStarName(pattern_star) => {
|
GotoTarget::PatternMatchStarName(pattern_star) => {
|
||||||
pattern_star.name.as_ref().map(|name| {
|
pattern_star.name.as_ref().map(|name| {
|
||||||
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
|
definitions_for_name(
|
||||||
|
model,
|
||||||
|
name.as_str(),
|
||||||
|
AnyNodeRef::Identifier(name),
|
||||||
|
alias_resolution,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -481,9 +506,18 @@ impl GotoTarget<'_> {
|
||||||
//
|
//
|
||||||
// Prefer the function impl over the callable so that its docstrings win if defined.
|
// Prefer the function impl over the callable so that its docstrings win if defined.
|
||||||
GotoTarget::Call { callable, call } => {
|
GotoTarget::Call { callable, call } => {
|
||||||
let mut definitions = definitions_for_callable(model, call);
|
let mut definitions = Vec::new();
|
||||||
|
|
||||||
|
// We prefer the specific overload for hover, go-to-def etc. However,
|
||||||
|
// `definitions_for_callable` always resolves import aliases. That's why we
|
||||||
|
// skip it in cases import alias resolution is turned of (rename, highlight references).
|
||||||
|
if alias_resolution == ImportAliasResolution::ResolveAliases {
|
||||||
|
definitions.extend(definitions_for_callable(model, call));
|
||||||
|
}
|
||||||
|
|
||||||
let expr_definitions =
|
let expr_definitions =
|
||||||
definitions_for_expression(model, *callable).unwrap_or_default();
|
definitions_for_expression(model, *callable, alias_resolution)
|
||||||
|
.unwrap_or_default();
|
||||||
definitions.extend(expr_definitions);
|
definitions.extend(expr_definitions);
|
||||||
|
|
||||||
if definitions.is_empty() {
|
if definitions.is_empty() {
|
||||||
|
|
@ -517,7 +551,7 @@ impl GotoTarget<'_> {
|
||||||
let subexpr = covering_node(subast.syntax().into(), *subrange)
|
let subexpr = covering_node(subast.syntax().into(), *subrange)
|
||||||
.node()
|
.node()
|
||||||
.as_expr_ref()?;
|
.as_expr_ref()?;
|
||||||
definitions_for_expression(&submodel, subexpr)
|
definitions_for_expression(&submodel, subexpr, alias_resolution)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nonlocal and global are essentially loads, but again they're statements,
|
// nonlocal and global are essentially loads, but again they're statements,
|
||||||
|
|
@ -527,6 +561,7 @@ impl GotoTarget<'_> {
|
||||||
model,
|
model,
|
||||||
identifier.as_str(),
|
identifier.as_str(),
|
||||||
AnyNodeRef::Identifier(identifier),
|
AnyNodeRef::Identifier(identifier),
|
||||||
|
alias_resolution,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -537,6 +572,7 @@ impl GotoTarget<'_> {
|
||||||
model,
|
model,
|
||||||
name.as_str(),
|
name.as_str(),
|
||||||
AnyNodeRef::Identifier(name),
|
AnyNodeRef::Identifier(name),
|
||||||
|
alias_resolution,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -546,6 +582,7 @@ impl GotoTarget<'_> {
|
||||||
model,
|
model,
|
||||||
name.as_str(),
|
name.as_str(),
|
||||||
AnyNodeRef::Identifier(name),
|
AnyNodeRef::Identifier(name),
|
||||||
|
alias_resolution,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -555,6 +592,7 @@ impl GotoTarget<'_> {
|
||||||
model,
|
model,
|
||||||
name.as_str(),
|
name.as_str(),
|
||||||
AnyNodeRef::Identifier(name),
|
AnyNodeRef::Identifier(name),
|
||||||
|
alias_resolution,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -580,12 +618,9 @@ impl GotoTarget<'_> {
|
||||||
GotoTarget::FunctionDef(function) => Some(Cow::Borrowed(function.name.as_str())),
|
GotoTarget::FunctionDef(function) => Some(Cow::Borrowed(function.name.as_str())),
|
||||||
GotoTarget::ClassDef(class) => Some(Cow::Borrowed(class.name.as_str())),
|
GotoTarget::ClassDef(class) => Some(Cow::Borrowed(class.name.as_str())),
|
||||||
GotoTarget::Parameter(parameter) => Some(Cow::Borrowed(parameter.name.as_str())),
|
GotoTarget::Parameter(parameter) => Some(Cow::Borrowed(parameter.name.as_str())),
|
||||||
GotoTarget::ImportSymbolAlias { alias, .. } => {
|
GotoTarget::ImportSymbolAlias { asname, .. } => Some(Cow::Borrowed(asname.as_str())),
|
||||||
if let Some(asname) = &alias.asname {
|
GotoTarget::ImportExportedName { alias, .. } => {
|
||||||
Some(Cow::Borrowed(asname.as_str()))
|
Some(Cow::Borrowed(alias.name.as_str()))
|
||||||
} else {
|
|
||||||
Some(Cow::Borrowed(alias.name.as_str()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
GotoTarget::ImportModuleComponent {
|
GotoTarget::ImportModuleComponent {
|
||||||
module_name,
|
module_name,
|
||||||
|
|
@ -599,13 +634,7 @@ impl GotoTarget<'_> {
|
||||||
Some(Cow::Borrowed(module_name))
|
Some(Cow::Borrowed(module_name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GotoTarget::ImportModuleAlias { alias } => {
|
GotoTarget::ImportModuleAlias { asname, .. } => Some(Cow::Borrowed(asname.as_str())),
|
||||||
if let Some(asname) = &alias.asname {
|
|
||||||
Some(Cow::Borrowed(asname.as_str()))
|
|
||||||
} else {
|
|
||||||
Some(Cow::Borrowed(alias.name.as_str()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GotoTarget::ExceptVariable(except) => {
|
GotoTarget::ExceptVariable(except) => {
|
||||||
Some(Cow::Borrowed(except.name.as_ref()?.as_str()))
|
Some(Cow::Borrowed(except.name.as_ref()?.as_str()))
|
||||||
}
|
}
|
||||||
|
|
@ -667,7 +696,7 @@ impl GotoTarget<'_> {
|
||||||
// Is the offset within the alias name (asname) part?
|
// Is the offset within the alias name (asname) part?
|
||||||
if let Some(asname) = &alias.asname {
|
if let Some(asname) = &alias.asname {
|
||||||
if asname.range.contains_inclusive(offset) {
|
if asname.range.contains_inclusive(offset) {
|
||||||
return Some(GotoTarget::ImportModuleAlias { alias });
|
return Some(GotoTarget::ImportModuleAlias { alias, asname });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -699,21 +728,13 @@ impl GotoTarget<'_> {
|
||||||
// Is the offset within the alias name (asname) part?
|
// Is the offset within the alias name (asname) part?
|
||||||
if let Some(asname) = &alias.asname {
|
if let Some(asname) = &alias.asname {
|
||||||
if asname.range.contains_inclusive(offset) {
|
if asname.range.contains_inclusive(offset) {
|
||||||
return Some(GotoTarget::ImportSymbolAlias {
|
return Some(GotoTarget::ImportSymbolAlias { alias, asname });
|
||||||
alias,
|
|
||||||
range: asname.range,
|
|
||||||
import_from,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the offset in the original name part?
|
// Is the offset in the original name part?
|
||||||
if alias.name.range.contains_inclusive(offset) {
|
if alias.name.range.contains_inclusive(offset) {
|
||||||
return Some(GotoTarget::ImportSymbolAlias {
|
return Some(GotoTarget::ImportExportedName { alias, import_from });
|
||||||
alias,
|
|
||||||
range: alias.name.range,
|
|
||||||
import_from,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|
@ -893,12 +914,13 @@ impl Ranged for GotoTarget<'_> {
|
||||||
GotoTarget::FunctionDef(function) => function.name.range,
|
GotoTarget::FunctionDef(function) => function.name.range,
|
||||||
GotoTarget::ClassDef(class) => class.name.range,
|
GotoTarget::ClassDef(class) => class.name.range,
|
||||||
GotoTarget::Parameter(parameter) => parameter.name.range,
|
GotoTarget::Parameter(parameter) => parameter.name.range,
|
||||||
GotoTarget::ImportSymbolAlias { range, .. } => *range,
|
GotoTarget::ImportSymbolAlias { asname, .. } => asname.range,
|
||||||
|
Self::ImportExportedName { alias, .. } => alias.name.range,
|
||||||
GotoTarget::ImportModuleComponent {
|
GotoTarget::ImportModuleComponent {
|
||||||
component_range, ..
|
component_range, ..
|
||||||
} => *component_range,
|
} => *component_range,
|
||||||
GotoTarget::StringAnnotationSubexpr { subrange, .. } => *subrange,
|
GotoTarget::StringAnnotationSubexpr { subrange, .. } => *subrange,
|
||||||
GotoTarget::ImportModuleAlias { alias } => alias.asname.as_ref().unwrap().range,
|
GotoTarget::ImportModuleAlias { asname, .. } => asname.range,
|
||||||
GotoTarget::ExceptVariable(except) => except.name.as_ref().unwrap().range,
|
GotoTarget::ExceptVariable(except) => except.name.as_ref().unwrap().range,
|
||||||
GotoTarget::KeywordArgument { keyword, .. } => keyword.arg.as_ref().unwrap().range,
|
GotoTarget::KeywordArgument { keyword, .. } => keyword.arg.as_ref().unwrap().range,
|
||||||
GotoTarget::PatternMatchRest(rest) => rest.rest.as_ref().unwrap().range,
|
GotoTarget::PatternMatchRest(rest) => rest.rest.as_ref().unwrap().range,
|
||||||
|
|
@ -955,12 +977,14 @@ fn convert_resolved_definitions_to_targets<'db>(
|
||||||
fn definitions_for_expression<'db>(
|
fn definitions_for_expression<'db>(
|
||||||
model: &SemanticModel<'db>,
|
model: &SemanticModel<'db>,
|
||||||
expression: ruff_python_ast::ExprRef<'_>,
|
expression: ruff_python_ast::ExprRef<'_>,
|
||||||
|
alias_resolution: ImportAliasResolution,
|
||||||
) -> Option<Vec<ResolvedDefinition<'db>>> {
|
) -> Option<Vec<ResolvedDefinition<'db>>> {
|
||||||
match expression {
|
match expression {
|
||||||
ast::ExprRef::Name(name) => Some(definitions_for_name(
|
ast::ExprRef::Name(name) => Some(definitions_for_name(
|
||||||
model,
|
model,
|
||||||
name.id.as_str(),
|
name.id.as_str(),
|
||||||
expression.into(),
|
expression.into(),
|
||||||
|
alias_resolution,
|
||||||
)),
|
)),
|
||||||
ast::ExprRef::Attribute(attribute) => Some(ty_python_semantic::definitions_for_attribute(
|
ast::ExprRef::Attribute(attribute) => Some(ty_python_semantic::definitions_for_attribute(
|
||||||
model, attribute,
|
model, attribute,
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,38 @@ pub enum ReferencesMode {
|
||||||
DocumentHighlights,
|
DocumentHighlights,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ReferencesMode {
|
||||||
|
pub(super) fn to_import_alias_resolution(self) -> ImportAliasResolution {
|
||||||
|
match self {
|
||||||
|
// Resolve import aliases for find references:
|
||||||
|
// ```py
|
||||||
|
// from warnings import deprecated as my_deprecated
|
||||||
|
//
|
||||||
|
// @my_deprecated
|
||||||
|
// def foo
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// When finding references on `my_deprecated`, we want to find all usages of `deprecated` across the entire
|
||||||
|
// project.
|
||||||
|
Self::References | Self::ReferencesSkipDeclaration => {
|
||||||
|
ImportAliasResolution::ResolveAliases
|
||||||
|
}
|
||||||
|
// For rename, don't resolve import aliases.
|
||||||
|
//
|
||||||
|
// ```py
|
||||||
|
// from warnings import deprecated as my_deprecated
|
||||||
|
//
|
||||||
|
// @my_deprecated
|
||||||
|
// def foo
|
||||||
|
// ```
|
||||||
|
// When renaming `my_deprecated`, only rename the alias, but not the original definition in `warnings`.
|
||||||
|
Self::Rename | Self::RenameMultiFile | Self::DocumentHighlights => {
|
||||||
|
ImportAliasResolution::PreserveAliases
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Find all references to a symbol at the given position.
|
/// Find all references to a symbol at the given position.
|
||||||
/// Search for references across all files in the project.
|
/// Search for references across all files in the project.
|
||||||
pub(crate) fn references(
|
pub(crate) fn references(
|
||||||
|
|
@ -45,12 +77,9 @@ pub(crate) fn references(
|
||||||
goto_target: &GotoTarget,
|
goto_target: &GotoTarget,
|
||||||
mode: ReferencesMode,
|
mode: ReferencesMode,
|
||||||
) -> Option<Vec<ReferenceTarget>> {
|
) -> Option<Vec<ReferenceTarget>> {
|
||||||
// Get the definitions for the symbol at the cursor position
|
|
||||||
|
|
||||||
// When finding references, do not resolve any local aliases.
|
|
||||||
let model = SemanticModel::new(db, file);
|
let model = SemanticModel::new(db, file);
|
||||||
let target_definitions = goto_target
|
let target_definitions = goto_target
|
||||||
.get_definition_targets(&model, ImportAliasResolution::PreserveAliases)?
|
.get_definition_targets(&model, mode.to_import_alias_resolution())?
|
||||||
.declaration_targets(db)?;
|
.declaration_targets(db)?;
|
||||||
|
|
||||||
// Extract the target text from the goto target for fast comparison
|
// Extract the target text from the goto target for fast comparison
|
||||||
|
|
@ -318,7 +347,7 @@ impl LocalReferencesFinder<'_> {
|
||||||
{
|
{
|
||||||
// Get the definitions for this goto target
|
// Get the definitions for this goto target
|
||||||
if let Some(current_definitions) = goto_target
|
if let Some(current_definitions) = goto_target
|
||||||
.get_definition_targets(self.model, ImportAliasResolution::PreserveAliases)
|
.get_definition_targets(self.model, self.mode.to_import_alias_resolution())
|
||||||
.and_then(|definitions| definitions.declaration_targets(self.model.db()))
|
.and_then(|definitions| definitions.declaration_targets(self.model.db()))
|
||||||
{
|
{
|
||||||
// Check if any of the current definitions match our target definitions
|
// Check if any of the current definitions match our target definitions
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::references::{ReferencesMode, references};
|
||||||
use crate::{Db, ReferenceTarget};
|
use crate::{Db, ReferenceTarget};
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_text_size::{Ranged, TextSize};
|
use ruff_text_size::{Ranged, TextSize};
|
||||||
use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
use ty_python_semantic::SemanticModel;
|
||||||
|
|
||||||
/// Returns the range of the symbol if it can be renamed, None if not.
|
/// Returns the range of the symbol if it can be renamed, None if not.
|
||||||
pub fn can_rename(db: &dyn Db, file: File, offset: TextSize) -> Option<ruff_text_size::TextRange> {
|
pub fn can_rename(db: &dyn Db, file: File, offset: TextSize) -> Option<ruff_text_size::TextRange> {
|
||||||
|
|
@ -24,26 +24,22 @@ pub fn can_rename(db: &dyn Db, file: File, offset: TextSize) -> Option<ruff_text
|
||||||
|
|
||||||
let current_file_in_project = is_file_in_project(db, file);
|
let current_file_in_project = is_file_in_project(db, file);
|
||||||
|
|
||||||
if let Some(definition_targets) = goto_target
|
let definition_targets = goto_target
|
||||||
.get_definition_targets(&model, ImportAliasResolution::PreserveAliases)
|
.get_definition_targets(&model, ReferencesMode::Rename.to_import_alias_resolution())?
|
||||||
.and_then(|definitions| definitions.declaration_targets(db))
|
.declaration_targets(db)?;
|
||||||
{
|
|
||||||
for target in &definition_targets {
|
|
||||||
let target_file = target.file();
|
|
||||||
|
|
||||||
// If definition is outside the project, refuse rename
|
for target in &definition_targets {
|
||||||
if !is_file_in_project(db, target_file) {
|
let target_file = target.file();
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If current file is not in project and any definition is outside current file, refuse rename
|
// If definition is outside the project, refuse rename
|
||||||
if !current_file_in_project && target_file != file {
|
if !is_file_in_project(db, target_file) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If current file is not in project and any definition is outside current file, refuse rename
|
||||||
|
if !current_file_in_project && target_file != file {
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// No definition targets found. This happens for keywords, so refuse rename
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(goto_target.range())
|
Some(goto_target.range())
|
||||||
|
|
@ -1186,7 +1182,6 @@ result = func(10, y=20)
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Should rename the alias
|
|
||||||
#[test]
|
#[test]
|
||||||
fn import_alias() {
|
fn import_alias() {
|
||||||
let test = CursorTest::builder()
|
let test = CursorTest::builder()
|
||||||
|
|
@ -1202,10 +1197,80 @@ result = func(10, y=20)
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assert_snapshot!(test.rename("z"), @"Cannot rename");
|
assert_snapshot!(test.rename("z"), @r"
|
||||||
|
info[rename]: Rename symbol (found 2 locations)
|
||||||
|
--> main.py:3:20
|
||||||
|
|
|
||||||
|
2 | import warnings
|
||||||
|
3 | import warnings as abc
|
||||||
|
| ^^^
|
||||||
|
4 |
|
||||||
|
5 | x = abc
|
||||||
|
| ---
|
||||||
|
6 | y = warnings
|
||||||
|
|
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn import_alias_to_first_party_definition() {
|
||||||
|
let test = CursorTest::builder()
|
||||||
|
.source("lib.py", "def deprecated(): pass")
|
||||||
|
.source(
|
||||||
|
"main.py",
|
||||||
|
r#"
|
||||||
|
import lib as lib2<CURSOR>
|
||||||
|
|
||||||
|
x = lib2
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_snapshot!(test.rename("z"), @r"
|
||||||
|
info[rename]: Rename symbol (found 2 locations)
|
||||||
|
--> main.py:2:15
|
||||||
|
|
|
||||||
|
2 | import lib as lib2
|
||||||
|
| ^^^^
|
||||||
|
3 |
|
||||||
|
4 | x = lib2
|
||||||
|
| ----
|
||||||
|
|
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn imported_first_party_definition() {
|
||||||
|
let test = CursorTest::builder()
|
||||||
|
.source("lib.py", "def deprecated(): pass")
|
||||||
|
.source(
|
||||||
|
"main.py",
|
||||||
|
r#"
|
||||||
|
from lib import deprecated<CURSOR>
|
||||||
|
|
||||||
|
x = deprecated
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_snapshot!(test.rename("z"), @r"
|
||||||
|
info[rename]: Rename symbol (found 3 locations)
|
||||||
|
--> main.py:2:17
|
||||||
|
|
|
||||||
|
2 | from lib import deprecated
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
3 |
|
||||||
|
4 | x = deprecated
|
||||||
|
| ----------
|
||||||
|
|
|
||||||
|
::: lib.py:1:5
|
||||||
|
|
|
||||||
|
1 | def deprecated(): pass
|
||||||
|
| ----------
|
||||||
|
|
|
||||||
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Should rename the alias
|
|
||||||
#[test]
|
#[test]
|
||||||
fn import_alias_use() {
|
fn import_alias_use() {
|
||||||
let test = CursorTest::builder()
|
let test = CursorTest::builder()
|
||||||
|
|
@ -1221,7 +1286,19 @@ result = func(10, y=20)
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assert_snapshot!(test.rename("z"), @"Cannot rename");
|
assert_snapshot!(test.rename("z"), @r"
|
||||||
|
info[rename]: Rename symbol (found 2 locations)
|
||||||
|
--> main.py:3:20
|
||||||
|
|
|
||||||
|
2 | import warnings
|
||||||
|
3 | import warnings as abc
|
||||||
|
| ^^^
|
||||||
|
4 |
|
||||||
|
5 | x = abc
|
||||||
|
| ---
|
||||||
|
6 | y = warnings
|
||||||
|
|
|
||||||
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -259,7 +259,11 @@ impl<'db> SemanticTokenVisitor<'db> {
|
||||||
|
|
||||||
fn classify_name(&self, name: &ast::ExprName) -> (SemanticTokenType, SemanticTokenModifier) {
|
fn classify_name(&self, name: &ast::ExprName) -> (SemanticTokenType, SemanticTokenModifier) {
|
||||||
// First try to classify the token based on its definition kind.
|
// First try to classify the token based on its definition kind.
|
||||||
let definition = definition_for_name(self.model, name);
|
let definition = definition_for_name(
|
||||||
|
self.model,
|
||||||
|
name,
|
||||||
|
ty_python_semantic::ImportAliasResolution::ResolveAliases,
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(definition) = definition {
|
if let Some(definition) = definition {
|
||||||
let name_str = name.id.as_str();
|
let name_str = name.id.as_str();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::FxIndexSet;
|
||||||
use crate::place::builtins_module_scope;
|
use crate::place::builtins_module_scope;
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::semantic_index::definition::DefinitionKind;
|
use crate::semantic_index::definition::DefinitionKind;
|
||||||
|
|
@ -24,8 +25,9 @@ use resolve_definition::{find_symbol_in_scope, resolve_definition};
|
||||||
pub fn definition_for_name<'db>(
|
pub fn definition_for_name<'db>(
|
||||||
model: &SemanticModel<'db>,
|
model: &SemanticModel<'db>,
|
||||||
name: &ast::ExprName,
|
name: &ast::ExprName,
|
||||||
|
alias_resolution: ImportAliasResolution,
|
||||||
) -> Option<Definition<'db>> {
|
) -> Option<Definition<'db>> {
|
||||||
let definitions = definitions_for_name(model, name.id.as_str(), name.into());
|
let definitions = definitions_for_name(model, name.id.as_str(), name.into(), alias_resolution);
|
||||||
|
|
||||||
// Find the first valid definition and return its kind
|
// Find the first valid definition and return its kind
|
||||||
for declaration in definitions {
|
for declaration in definitions {
|
||||||
|
|
@ -43,6 +45,7 @@ pub fn definitions_for_name<'db>(
|
||||||
model: &SemanticModel<'db>,
|
model: &SemanticModel<'db>,
|
||||||
name_str: &str,
|
name_str: &str,
|
||||||
node: AnyNodeRef<'_>,
|
node: AnyNodeRef<'_>,
|
||||||
|
alias_resolution: ImportAliasResolution,
|
||||||
) -> Vec<ResolvedDefinition<'db>> {
|
) -> Vec<ResolvedDefinition<'db>> {
|
||||||
let db = model.db();
|
let db = model.db();
|
||||||
let file = model.file();
|
let file = model.file();
|
||||||
|
|
@ -53,7 +56,7 @@ pub fn definitions_for_name<'db>(
|
||||||
return vec![];
|
return vec![];
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut all_definitions = Vec::new();
|
let mut all_definitions = FxIndexSet::default();
|
||||||
|
|
||||||
// Search through the scope hierarchy: start from the current scope and
|
// Search through the scope hierarchy: start from the current scope and
|
||||||
// traverse up through parent scopes to find definitions
|
// traverse up through parent scopes to find definitions
|
||||||
|
|
@ -89,13 +92,13 @@ pub fn definitions_for_name<'db>(
|
||||||
|
|
||||||
for binding in global_bindings {
|
for binding in global_bindings {
|
||||||
if let Some(def) = binding.binding.definition() {
|
if let Some(def) = binding.binding.definition() {
|
||||||
all_definitions.push(def);
|
all_definitions.insert(def);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for declaration in global_declarations {
|
for declaration in global_declarations {
|
||||||
if let Some(def) = declaration.declaration.definition() {
|
if let Some(def) = declaration.declaration.definition() {
|
||||||
all_definitions.push(def);
|
all_definitions.insert(def);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -116,13 +119,13 @@ pub fn definitions_for_name<'db>(
|
||||||
|
|
||||||
for binding in bindings {
|
for binding in bindings {
|
||||||
if let Some(def) = binding.binding.definition() {
|
if let Some(def) = binding.binding.definition() {
|
||||||
all_definitions.push(def);
|
all_definitions.insert(def);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for declaration in declarations {
|
for declaration in declarations {
|
||||||
if let Some(def) = declaration.declaration.definition() {
|
if let Some(def) = declaration.declaration.definition() {
|
||||||
all_definitions.push(def);
|
all_definitions.insert(def);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,21 +139,14 @@ pub fn definitions_for_name<'db>(
|
||||||
let mut resolved_definitions = Vec::new();
|
let mut resolved_definitions = Vec::new();
|
||||||
|
|
||||||
for definition in &all_definitions {
|
for definition in &all_definitions {
|
||||||
let resolved = resolve_definition(
|
let resolved = resolve_definition(db, *definition, Some(name_str), alias_resolution);
|
||||||
db,
|
|
||||||
*definition,
|
|
||||||
Some(name_str),
|
|
||||||
ImportAliasResolution::ResolveAliases,
|
|
||||||
);
|
|
||||||
resolved_definitions.extend(resolved);
|
resolved_definitions.extend(resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we didn't find any definitions in scopes, fallback to builtins
|
// If we didn't find any definitions in scopes, fallback to builtins
|
||||||
if resolved_definitions.is_empty() {
|
if resolved_definitions.is_empty()
|
||||||
let Some(builtins_scope) = builtins_module_scope(db) else {
|
&& let Some(builtins_scope) = builtins_module_scope(db)
|
||||||
return resolved_definitions;
|
{
|
||||||
};
|
|
||||||
|
|
||||||
// Special cases for `float` and `complex` in type annotation positions.
|
// Special cases for `float` and `complex` in type annotation positions.
|
||||||
// We don't know whether we're in a type annotation position, so we'll just ask `Name`'s type,
|
// We don't know whether we're in a type annotation position, so we'll just ask `Name`'s type,
|
||||||
// which resolves to `int | float` or `int | float | complex` if `float` or `complex` is used in
|
// which resolves to `int | float` or `int | float | complex` if `float` or `complex` is used in
|
||||||
|
|
@ -932,6 +928,12 @@ mod resolve_definition {
|
||||||
let module = parsed_module(db, file).load(db);
|
let module = parsed_module(db, file).load(db);
|
||||||
let alias = import_def.alias(&module);
|
let alias = import_def.alias(&module);
|
||||||
|
|
||||||
|
if alias.asname.is_some()
|
||||||
|
&& alias_resolution == ImportAliasResolution::PreserveAliases
|
||||||
|
{
|
||||||
|
return vec![ResolvedDefinition::Definition(definition)];
|
||||||
|
}
|
||||||
|
|
||||||
// Get the full module name being imported
|
// Get the full module name being imported
|
||||||
let Some(module_name) = ModuleName::new(&alias.name) else {
|
let Some(module_name) = ModuleName::new(&alias.name) else {
|
||||||
return Vec::new(); // Invalid module name, return empty list
|
return Vec::new(); // Invalid module name, return empty list
|
||||||
|
|
@ -955,7 +957,13 @@ mod resolve_definition {
|
||||||
let file = definition.file(db);
|
let file = definition.file(db);
|
||||||
let module = parsed_module(db, file).load(db);
|
let module = parsed_module(db, file).load(db);
|
||||||
let import_node = import_from_def.import(&module);
|
let import_node = import_from_def.import(&module);
|
||||||
let name = &import_from_def.alias(&module).name;
|
let alias = import_from_def.alias(&module);
|
||||||
|
|
||||||
|
if alias.asname.is_some()
|
||||||
|
&& alias_resolution == ImportAliasResolution::PreserveAliases
|
||||||
|
{
|
||||||
|
return vec![ResolvedDefinition::Definition(definition)];
|
||||||
|
}
|
||||||
|
|
||||||
// For `ImportFrom`, we need to resolve the original imported symbol name
|
// For `ImportFrom`, we need to resolve the original imported symbol name
|
||||||
// (alias.name), not the local alias (symbol_name)
|
// (alias.name), not the local alias (symbol_name)
|
||||||
|
|
@ -963,7 +971,7 @@ mod resolve_definition {
|
||||||
db,
|
db,
|
||||||
file,
|
file,
|
||||||
import_node,
|
import_node,
|
||||||
name,
|
&alias.name,
|
||||||
visited,
|
visited,
|
||||||
alias_resolution,
|
alias_resolution,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue