mirror of
https://github.com/astral-sh/ruff.git
synced 2026-10-28 10:45:37 +08:00
[ty] Find member references across files (#28669)
This commit is contained in:
1 parent
ebd8ad3158
commit
b61c8b0357
3 files changed
+139
-18
No files matched your search
@@ -1359,6 +1359,76 @@ def process_model():
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_file_instance_attribute_references() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"lib.py",
|
||||
r#"
|
||||
class C:
|
||||
def __init__(self) -> None:
|
||||
self.<CURSOR>value: int = 42
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
from lib import C
|
||||
|
||||
print(C().value)
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.references(), @"
|
||||
info[references]: Found 2 references
|
||||
--> lib.py:4:14
|
||||
|
|
||||
4 | self.value: int = 42
|
||||
| -----
|
||||
|
|
||||
::: main.py:4:11
|
||||
|
|
||||
4 | print(C().value)
|
||||
| -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_file_comprehension_attribute_references() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"lib.py",
|
||||
r#"
|
||||
class C:
|
||||
def __init__(self) -> None:
|
||||
[None for self.<CURSOR>value in [42]]
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
from lib import C
|
||||
|
||||
print(C().value)
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.references(), @"
|
||||
info[references]: Found 2 references
|
||||
--> lib.py:4:24
|
||||
|
|
||||
4 | [None for self.value in [42]]
|
||||
| -----
|
||||
|
|
||||
::: main.py:4:11
|
||||
|
|
||||
4 | print(C().value)
|
||||
| -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_file_parameter_references_include_keyword_argument_labels() {
|
||||
let test = CursorTest::builder()
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
//! symbol's name, this is a "semantic search" where the text and the semantic
|
||||
//! meaning must match.
|
||||
//!
|
||||
//! Some symbols (such as parameters and local variables) are visible only
|
||||
//! within their scope. All other symbols, such as those defined at the global
|
||||
//! scope or within classes, are visible outside the module. Finding
|
||||
//! all references to these externally-visible symbols therefore requires
|
||||
//! an expensive search of all source files in the workspace.
|
||||
//! Some symbols (local variables) are visible only within their scope/file.
|
||||
//! Other symbols can have references in other modules, requiring an expensive cross-module
|
||||
//! search.
|
||||
//! * Members can be visible outside the module, even when assigned inside a function.
|
||||
//! * Parameters can also have cross-file references through keyword argument labels.
|
||||
|
||||
use crate::goto::{Definitions, GotoTarget};
|
||||
use crate::{Db, ReferenceKind, ReferenceTarget};
|
||||
@@ -330,20 +330,30 @@ pub(crate) fn has_any_external_visible_definitions(
|
||||
definitions: &Definitions<'_>,
|
||||
) -> bool {
|
||||
definitions.iter().any(|definition| match definition {
|
||||
ResolvedDefinition::Definition(definition) => match definition.scope(db).scope(db).kind() {
|
||||
ScopeKind::Module | ScopeKind::Class => true,
|
||||
ScopeKind::Comprehension => {
|
||||
matches!(definition.kind(db), DefinitionKind::NamedExpression(_))
|
||||
&& definition.place(db).as_symbol().is_some_and(|symbol_id| {
|
||||
ty_python_core::semantic_index(db, definition.program_file(db))
|
||||
.symbol_resolves_to_global_scope(symbol_id, definition.file_scope(db))
|
||||
})
|
||||
ResolvedDefinition::Definition(definition) => {
|
||||
// A member's accessibility does not depend on the scope of its assignment.
|
||||
if definition.place(db).is_member() {
|
||||
return true;
|
||||
}
|
||||
ScopeKind::TypeParams
|
||||
| ScopeKind::Function
|
||||
| ScopeKind::Lambda
|
||||
| ScopeKind::TypeAlias => false,
|
||||
},
|
||||
|
||||
match definition.scope(db).scope(db).kind() {
|
||||
ScopeKind::Module | ScopeKind::Class => true,
|
||||
ScopeKind::Comprehension => {
|
||||
matches!(definition.kind(db), DefinitionKind::NamedExpression(_))
|
||||
&& definition.place(db).as_symbol().is_some_and(|symbol_id| {
|
||||
ty_python_core::semantic_index(db, definition.program_file(db))
|
||||
.symbol_resolves_to_global_scope(
|
||||
symbol_id,
|
||||
definition.file_scope(db),
|
||||
)
|
||||
})
|
||||
}
|
||||
ScopeKind::TypeParams
|
||||
| ScopeKind::Function
|
||||
| ScopeKind::Lambda
|
||||
| ScopeKind::TypeAlias => false,
|
||||
}
|
||||
}
|
||||
ResolvedDefinition::Module(_) | ResolvedDefinition::FileWithRange(_) => true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2760,6 +2760,47 @@ DC(f=1)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_file_attribute_rename_updates_slots() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"lib.py",
|
||||
r#"
|
||||
class Box:
|
||||
__slots__ = ("value",)
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.value = 42
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
from lib import Box
|
||||
|
||||
print(Box().<CURSOR>value)
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.rename("count"), @r#"
|
||||
info[rename]: Rename symbol (found 3 locations)
|
||||
--> main.py:4:13
|
||||
|
|
||||
4 | print(Box().value)
|
||||
| ^^^^^
|
||||
|
|
||||
::: lib.py:3:19
|
||||
|
|
||||
3 | __slots__ = ("value",)
|
||||
| -----
|
||||
4 |
|
||||
5 | def __init__(self) -> None:
|
||||
6 | self.value = 42
|
||||
| -----
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_attribute_updates_slots_tuple() {
|
||||
let test = cursor_test(
|
||||
|
||||
Reference in new issue
Block a user