mirror of https://github.com/astral-sh/ruff
1793 lines
42 KiB
Rust
1793 lines
42 KiB
Rust
use crate::goto::find_goto_target;
|
|
use crate::{Db, NavigationTargets, RangedValue};
|
|
use ruff_db::files::{File, FileRange};
|
|
use ruff_db::parsed::parsed_module;
|
|
use ruff_text_size::{Ranged, TextSize};
|
|
use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
|
|
|
/// Navigate to the definition of a symbol.
|
|
///
|
|
/// A "definition" is the actual implementation of a symbol, potentially in a source file
|
|
/// rather than a stub file. This differs from "declaration" which may navigate to stub files.
|
|
/// When possible, this function will map from stub file declarations to their corresponding
|
|
/// source file implementations using the `StubMapper`.
|
|
pub fn goto_definition(
|
|
db: &dyn Db,
|
|
file: File,
|
|
offset: TextSize,
|
|
) -> Option<RangedValue<NavigationTargets>> {
|
|
let module = parsed_module(db, file).load(db);
|
|
let model = SemanticModel::new(db, file);
|
|
let goto_target = find_goto_target(&model, &module, offset)?;
|
|
let definition_targets = goto_target
|
|
.get_definition_targets(&model, ImportAliasResolution::ResolveAliases)?
|
|
.definition_targets(db)?;
|
|
|
|
Some(RangedValue {
|
|
range: FileRange::new(file, goto_target.range()),
|
|
value: definition_targets,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(super) mod test {
|
|
|
|
use crate::tests::{CursorTest, IntoDiagnostic};
|
|
use crate::{NavigationTargets, RangedValue, goto_definition};
|
|
use insta::assert_snapshot;
|
|
use ruff_db::diagnostic::{
|
|
Annotation, Diagnostic, DiagnosticId, LintName, Severity, Span, SubDiagnostic,
|
|
SubDiagnosticSeverity,
|
|
};
|
|
use ruff_text_size::Ranged;
|
|
|
|
/// goto-definition on a module should go to the .py not the .pyi
|
|
///
|
|
/// TODO: this currently doesn't work right! This is especially surprising
|
|
/// because [`goto_definition_stub_map_module_ref`] works fine.
|
|
#[test]
|
|
fn goto_definition_stub_map_module_import() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
from mymo<CURSOR>dule import my_function
|
|
",
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
def my_function():
|
|
return "hello"
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
def my_function(): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:2:6
|
|
|
|
|
2 | from mymodule import my_function
|
|
| ^^^^^^^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:1:1
|
|
|
|
|
1 |
|
|
| -
|
|
2 | def my_function():
|
|
3 | return "hello"
|
|
|
|
|
"#);
|
|
}
|
|
|
|
/// goto-definition on a module ref should go to the .py not the .pyi
|
|
#[test]
|
|
fn goto_definition_stub_map_module_ref() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
import mymodule
|
|
x = mymo<CURSOR>dule
|
|
",
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
def my_function():
|
|
return "hello"
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
def my_function(): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:3:5
|
|
|
|
|
2 | import mymodule
|
|
3 | x = mymodule
|
|
| ^^^^^^^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:1:1
|
|
|
|
|
1 |
|
|
| -
|
|
2 | def my_function():
|
|
3 | return "hello"
|
|
|
|
|
"#);
|
|
}
|
|
|
|
/// goto-definition on a function call should go to the .py not the .pyi
|
|
#[test]
|
|
fn goto_definition_stub_map_function() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
from mymodule import my_function
|
|
print(my_func<CURSOR>tion())
|
|
",
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
def my_function():
|
|
return "hello"
|
|
|
|
def other_function():
|
|
return "other"
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
def my_function(): ...
|
|
|
|
def other_function(): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:3:7
|
|
|
|
|
2 | from mymodule import my_function
|
|
3 | print(my_function())
|
|
| ^^^^^^^^^^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:2:5
|
|
|
|
|
2 | def my_function():
|
|
| -----------
|
|
3 | return "hello"
|
|
|
|
|
"#);
|
|
}
|
|
|
|
/// goto-definition on a function definition in a .pyi should go to the .py
|
|
#[test]
|
|
fn goto_definition_stub_map_function_def() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
def my_function():
|
|
return "hello"
|
|
|
|
def other_function():
|
|
return "other"
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
def my_fun<CURSOR>ction(): ...
|
|
|
|
def other_function(): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> mymodule.pyi:2:5
|
|
|
|
|
2 | def my_function(): ...
|
|
| ^^^^^^^^^^^ Clicking here
|
|
3 |
|
|
4 | def other_function(): ...
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:2:5
|
|
|
|
|
2 | def my_function():
|
|
| -----------
|
|
3 | return "hello"
|
|
|
|
|
"#);
|
|
}
|
|
|
|
/// goto-definition on a function that's redefined many times in the impl .py
|
|
///
|
|
/// Currently this yields all instances. There's an argument for only yielding
|
|
/// the final one since that's the one "exported" but, this is consistent for
|
|
/// how we do file-local goto-definition.
|
|
#[test]
|
|
fn goto_definition_stub_map_function_redefine() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
from mymodule import my_function
|
|
print(my_func<CURSOR>tion())
|
|
",
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
def my_function():
|
|
return "hello"
|
|
|
|
def my_function():
|
|
return "hello again"
|
|
|
|
def my_function():
|
|
return "we can't keep doing this"
|
|
|
|
def other_function():
|
|
return "other"
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
def my_function(): ...
|
|
|
|
def other_function(): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:3:7
|
|
|
|
|
2 | from mymodule import my_function
|
|
3 | print(my_function())
|
|
| ^^^^^^^^^^^ Clicking here
|
|
|
|
|
info: Found 3 definitions
|
|
--> mymodule.py:2:5
|
|
|
|
|
2 | def my_function():
|
|
| -----------
|
|
3 | return "hello"
|
|
4 |
|
|
5 | def my_function():
|
|
| -----------
|
|
6 | return "hello again"
|
|
7 |
|
|
8 | def my_function():
|
|
| -----------
|
|
9 | return "we can't keep doing this"
|
|
|
|
|
"#);
|
|
}
|
|
|
|
/// goto-definition on a class ref go to the .py not the .pyi
|
|
#[test]
|
|
fn goto_definition_stub_map_class_ref() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
from mymodule import MyClass
|
|
x = MyC<CURSOR>lass
|
|
",
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
class MyClass:
|
|
def __init__(self, val):
|
|
self.val = val
|
|
|
|
class MyOtherClass:
|
|
def __init__(self, val):
|
|
self.val = val + 1
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
class MyClass:
|
|
def __init__(self, val: bool): ...
|
|
|
|
class MyOtherClass:
|
|
def __init__(self, val: bool): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:3:5
|
|
|
|
|
2 | from mymodule import MyClass
|
|
3 | x = MyClass
|
|
| ^^^^^^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:2:7
|
|
|
|
|
2 | class MyClass:
|
|
| -------
|
|
3 | def __init__(self, val):
|
|
4 | self.val = val
|
|
|
|
|
");
|
|
}
|
|
|
|
/// goto-definition on a class def in a .pyi should go to the .py
|
|
#[test]
|
|
fn goto_definition_stub_map_class_def() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
class MyClass:
|
|
def __init__(self, val):
|
|
self.val = val
|
|
|
|
class MyOtherClass:
|
|
def __init__(self, val):
|
|
self.val = val + 1
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
class MyCl<CURSOR>ass:
|
|
def __init__(self, val: bool): ...
|
|
|
|
class MyOtherClass:
|
|
def __init__(self, val: bool): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> mymodule.pyi:2:7
|
|
|
|
|
2 | class MyClass:
|
|
| ^^^^^^^ Clicking here
|
|
3 | def __init__(self, val: bool): ...
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:2:7
|
|
|
|
|
2 | class MyClass:
|
|
| -------
|
|
3 | def __init__(self, val):
|
|
4 | self.val = val
|
|
|
|
|
");
|
|
}
|
|
|
|
/// goto-definition on a class init should go to the .py not the .pyi
|
|
#[test]
|
|
fn goto_definition_stub_map_class_init() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
from mymodule import MyClass
|
|
x = MyCl<CURSOR>ass(0)
|
|
",
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
class MyClass:
|
|
def __init__(self, val):
|
|
self.val = val
|
|
|
|
class MyOtherClass:
|
|
def __init__(self, val):
|
|
self.val = val + 1
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
class MyClass:
|
|
def __init__(self, val: bool): ...
|
|
|
|
class MyOtherClass:
|
|
def __init__(self, val: bool): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:3:5
|
|
|
|
|
2 | from mymodule import MyClass
|
|
3 | x = MyClass(0)
|
|
| ^^^^^^^ Clicking here
|
|
|
|
|
info: Found 2 definitions
|
|
--> mymodule.py:2:7
|
|
|
|
|
2 | class MyClass:
|
|
| -------
|
|
3 | def __init__(self, val):
|
|
| --------
|
|
4 | self.val = val
|
|
|
|
|
");
|
|
}
|
|
|
|
/// goto-definition on a class method should go to the .py not the .pyi
|
|
#[test]
|
|
fn goto_definition_stub_map_class_method() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
from mymodule import MyClass
|
|
x = MyClass(0)
|
|
x.act<CURSOR>ion()
|
|
",
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
class MyClass:
|
|
def __init__(self, val):
|
|
self.val = val
|
|
def action(self):
|
|
print(self.val)
|
|
|
|
class MyOtherClass:
|
|
def __init__(self, val):
|
|
self.val = val + 1
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
class MyClass:
|
|
def __init__(self, val: bool): ...
|
|
def action(self): ...
|
|
|
|
class MyOtherClass:
|
|
def __init__(self, val: bool): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:4:3
|
|
|
|
|
2 | from mymodule import MyClass
|
|
3 | x = MyClass(0)
|
|
4 | x.action()
|
|
| ^^^^^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:5:9
|
|
|
|
|
3 | def __init__(self, val):
|
|
4 | self.val = val
|
|
5 | def action(self):
|
|
| ------
|
|
6 | print(self.val)
|
|
|
|
|
");
|
|
}
|
|
|
|
/// goto-definition on a class function should go to the .py not the .pyi
|
|
#[test]
|
|
fn goto_definition_stub_map_class_function() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
from mymodule import MyClass
|
|
x = MyClass.act<CURSOR>ion()
|
|
",
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
class MyClass:
|
|
def __init__(self, val):
|
|
self.val = val
|
|
def action():
|
|
print("hi!")
|
|
|
|
class MyOtherClass:
|
|
def __init__(self, val):
|
|
self.val = val + 1
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
class MyClass:
|
|
def __init__(self, val: bool): ...
|
|
def action(): ...
|
|
|
|
class MyOtherClass:
|
|
def __init__(self, val: bool): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:3:13
|
|
|
|
|
2 | from mymodule import MyClass
|
|
3 | x = MyClass.action()
|
|
| ^^^^^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:5:9
|
|
|
|
|
3 | def __init__(self, val):
|
|
4 | self.val = val
|
|
5 | def action():
|
|
| ------
|
|
6 | print("hi!")
|
|
|
|
|
"#);
|
|
}
|
|
|
|
/// goto-definition on a class import should go to the .py not the .pyi
|
|
#[test]
|
|
fn goto_definition_stub_map_class_import() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
from mymodule import MyC<CURSOR>lass
|
|
",
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
class MyClass: ...
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
class MyClass: ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:2:22
|
|
|
|
|
2 | from mymodule import MyClass
|
|
| ^^^^^^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:2:7
|
|
|
|
|
2 | class MyClass: ...
|
|
| -------
|
|
|
|
|
");
|
|
}
|
|
|
|
/// goto-definition on a nested call using a keyword arg where both funcs have that arg name
|
|
///
|
|
/// In this case they ultimately have different signatures.
|
|
#[test]
|
|
fn goto_definition_nested_keyword_arg1() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
r#"
|
|
def my_func(ab, y, z = None): ...
|
|
def my_other_func(ab, y): ...
|
|
|
|
my_other_func(my_func(a<CURSOR>b=5, y=2), 0)
|
|
my_func(my_other_func(ab=5, y=2), 0)
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:5:23
|
|
|
|
|
3 | def my_other_func(ab, y): ...
|
|
4 |
|
|
5 | my_other_func(my_func(ab=5, y=2), 0)
|
|
| ^^ Clicking here
|
|
6 | my_func(my_other_func(ab=5, y=2), 0)
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:2:13
|
|
|
|
|
2 | def my_func(ab, y, z = None): ...
|
|
| --
|
|
3 | def my_other_func(ab, y): ...
|
|
|
|
|
");
|
|
}
|
|
|
|
/// goto-definition on a nested call using a keyword arg where both funcs have that arg name
|
|
///
|
|
/// In this case they ultimately have different signatures.
|
|
#[test]
|
|
fn goto_definition_nested_keyword_arg2() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
r#"
|
|
def my_func(ab, y, z = None): ...
|
|
def my_other_func(ab, y): ...
|
|
|
|
my_other_func(my_func(ab=5, y=2), 0)
|
|
my_func(my_other_func(a<CURSOR>b=5, y=2), 0)
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:6:23
|
|
|
|
|
5 | my_other_func(my_func(ab=5, y=2), 0)
|
|
6 | my_func(my_other_func(ab=5, y=2), 0)
|
|
| ^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:3:19
|
|
|
|
|
2 | def my_func(ab, y, z = None): ...
|
|
3 | def my_other_func(ab, y): ...
|
|
| --
|
|
4 |
|
|
5 | my_other_func(my_func(ab=5, y=2), 0)
|
|
|
|
|
");
|
|
}
|
|
|
|
/// goto-definition on a nested call using a keyword arg where both funcs have that arg name
|
|
///
|
|
/// In this case they have identical signatures.
|
|
#[test]
|
|
fn goto_definition_nested_keyword_arg3() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
r#"
|
|
def my_func(ab, y): ...
|
|
def my_other_func(ab, y): ...
|
|
|
|
my_other_func(my_func(a<CURSOR>b=5, y=2), 0)
|
|
my_func(my_other_func(ab=5, y=2), 0)
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:5:23
|
|
|
|
|
3 | def my_other_func(ab, y): ...
|
|
4 |
|
|
5 | my_other_func(my_func(ab=5, y=2), 0)
|
|
| ^^ Clicking here
|
|
6 | my_func(my_other_func(ab=5, y=2), 0)
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:2:13
|
|
|
|
|
2 | def my_func(ab, y): ...
|
|
| --
|
|
3 | def my_other_func(ab, y): ...
|
|
|
|
|
");
|
|
}
|
|
|
|
/// goto-definition on a nested call using a keyword arg where both funcs have that arg name
|
|
///
|
|
/// In this case they have identical signatures.
|
|
#[test]
|
|
fn goto_definition_nested_keyword_arg4() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
r#"
|
|
def my_func(ab, y): ...
|
|
def my_other_func(ab, y): ...
|
|
|
|
my_other_func(my_func(ab=5, y=2), 0)
|
|
my_func(my_other_func(a<CURSOR>b=5, y=2), 0)
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:6:23
|
|
|
|
|
5 | my_other_func(my_func(ab=5, y=2), 0)
|
|
6 | my_func(my_other_func(ab=5, y=2), 0)
|
|
| ^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:3:19
|
|
|
|
|
2 | def my_func(ab, y): ...
|
|
3 | def my_other_func(ab, y): ...
|
|
| --
|
|
4 |
|
|
5 | my_other_func(my_func(ab=5, y=2), 0)
|
|
|
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_overload_type_disambiguated1() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
from mymodule import ab
|
|
|
|
a<CURSOR>b(1)
|
|
",
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
def ab(a):
|
|
"""the real implementation!"""
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
from typing import overload
|
|
|
|
@overload
|
|
def ab(a: int): ...
|
|
|
|
@overload
|
|
def ab(a: str): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:4:1
|
|
|
|
|
2 | from mymodule import ab
|
|
3 |
|
|
4 | ab(1)
|
|
| ^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:2:5
|
|
|
|
|
2 | def ab(a):
|
|
| --
|
|
3 | """the real implementation!"""
|
|
|
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_overload_type_disambiguated2() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
r#"
|
|
from mymodule import ab
|
|
|
|
a<CURSOR>b("hello")
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
def ab(a):
|
|
"""the real implementation!"""
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
from typing import overload
|
|
|
|
@overload
|
|
def ab(a: int): ...
|
|
|
|
@overload
|
|
def ab(a: str): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:4:1
|
|
|
|
|
2 | from mymodule import ab
|
|
3 |
|
|
4 | ab("hello")
|
|
| ^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:2:5
|
|
|
|
|
2 | def ab(a):
|
|
| --
|
|
3 | """the real implementation!"""
|
|
|
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_overload_arity_disambiguated1() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
from mymodule import ab
|
|
|
|
a<CURSOR>b(1, 2)
|
|
",
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
def ab(a, b = None):
|
|
"""the real implementation!"""
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
from typing import overload
|
|
|
|
@overload
|
|
def ab(a: int, b: int): ...
|
|
|
|
@overload
|
|
def ab(a: int): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:4:1
|
|
|
|
|
2 | from mymodule import ab
|
|
3 |
|
|
4 | ab(1, 2)
|
|
| ^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:2:5
|
|
|
|
|
2 | def ab(a, b = None):
|
|
| --
|
|
3 | """the real implementation!"""
|
|
|
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_overload_arity_disambiguated2() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
from mymodule import ab
|
|
|
|
a<CURSOR>b(1)
|
|
",
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
def ab(a, b = None):
|
|
"""the real implementation!"""
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
from typing import overload
|
|
|
|
@overload
|
|
def ab(a: int, b: int): ...
|
|
|
|
@overload
|
|
def ab(a: int): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:4:1
|
|
|
|
|
2 | from mymodule import ab
|
|
3 |
|
|
4 | ab(1)
|
|
| ^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:2:5
|
|
|
|
|
2 | def ab(a, b = None):
|
|
| --
|
|
3 | """the real implementation!"""
|
|
|
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_overload_keyword_disambiguated1() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
from mymodule import ab
|
|
|
|
a<CURSOR>b(1, b=2)
|
|
",
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
def ab(a, *, b = None, c = None):
|
|
"""the real implementation!"""
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
from typing import overload
|
|
|
|
@overload
|
|
def ab(a: int): ...
|
|
|
|
@overload
|
|
def ab(a: int, *, b: int): ...
|
|
|
|
@overload
|
|
def ab(a: int, *, c: int): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:4:1
|
|
|
|
|
2 | from mymodule import ab
|
|
3 |
|
|
4 | ab(1, b=2)
|
|
| ^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:2:5
|
|
|
|
|
2 | def ab(a, *, b = None, c = None):
|
|
| --
|
|
3 | """the real implementation!"""
|
|
|
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_overload_keyword_disambiguated2() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
from mymodule import ab
|
|
|
|
a<CURSOR>b(1, c=2)
|
|
",
|
|
)
|
|
.source(
|
|
"mymodule.py",
|
|
r#"
|
|
def ab(a, *, b = None, c = None):
|
|
"""the real implementation!"""
|
|
"#,
|
|
)
|
|
.source(
|
|
"mymodule.pyi",
|
|
r#"
|
|
from typing import overload
|
|
|
|
@overload
|
|
def ab(a: int): ...
|
|
|
|
@overload
|
|
def ab(a: int, *, b: int): ...
|
|
|
|
@overload
|
|
def ab(a: int, *, c: int): ...
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:4:1
|
|
|
|
|
2 | from mymodule import ab
|
|
3 |
|
|
4 | ab(1, c=2)
|
|
| ^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> mymodule.py:2:5
|
|
|
|
|
2 | def ab(a, *, b = None, c = None):
|
|
| --
|
|
3 | """the real implementation!"""
|
|
|
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_binary_operator() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
class Test:
|
|
def __add__(self, other):
|
|
return Test()
|
|
|
|
|
|
a = Test()
|
|
b = Test()
|
|
|
|
a <CURSOR>+ b
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:10:3
|
|
|
|
|
8 | b = Test()
|
|
9 |
|
|
10 | a + b
|
|
| ^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:3:9
|
|
|
|
|
2 | class Test:
|
|
3 | def __add__(self, other):
|
|
| -------
|
|
4 | return Test()
|
|
|
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_binary_operator_reflected_dunder() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
class A:
|
|
def __radd__(self, other) -> A:
|
|
return self
|
|
|
|
class B: ...
|
|
|
|
B() <CURSOR>+ A()
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:8:5
|
|
|
|
|
6 | class B: ...
|
|
7 |
|
|
8 | B() + A()
|
|
| ^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:3:9
|
|
|
|
|
2 | class A:
|
|
3 | def __radd__(self, other) -> A:
|
|
| --------
|
|
4 | return self
|
|
|
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_binary_operator_no_spaces_before_operator() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
class Test:
|
|
def __add__(self, other):
|
|
return Test()
|
|
|
|
|
|
a = Test()
|
|
b = Test()
|
|
|
|
a<CURSOR>+b
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:10:2
|
|
|
|
|
8 | b = Test()
|
|
9 |
|
|
10 | a+b
|
|
| ^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:3:9
|
|
|
|
|
2 | class Test:
|
|
3 | def __add__(self, other):
|
|
| -------
|
|
4 | return Test()
|
|
|
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_binary_operator_no_spaces_after_operator() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
class Test:
|
|
def __add__(self, other):
|
|
return Test()
|
|
|
|
|
|
a = Test()
|
|
b = Test()
|
|
|
|
a+<CURSOR>b
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:10:3
|
|
|
|
|
8 | b = Test()
|
|
9 |
|
|
10 | a+b
|
|
| ^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:8:1
|
|
|
|
|
7 | a = Test()
|
|
8 | b = Test()
|
|
| -
|
|
9 |
|
|
10 | a+b
|
|
|
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_binary_operator_comment() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
class Test:
|
|
def __add__(self, other):
|
|
return Test()
|
|
|
|
|
|
(
|
|
Test() <CURSOR># comment
|
|
+ Test()
|
|
)
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_unary_operator() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
class Test:
|
|
def __invert__(self) -> 'Test': ...
|
|
|
|
a = Test()
|
|
|
|
<CURSOR>~a
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:7:1
|
|
|
|
|
5 | a = Test()
|
|
6 |
|
|
7 | ~a
|
|
| ^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:3:9
|
|
|
|
|
2 | class Test:
|
|
3 | def __invert__(self) -> 'Test': ...
|
|
| ----------
|
|
4 |
|
|
5 | a = Test()
|
|
|
|
|
");
|
|
}
|
|
|
|
/// We jump to the `__invert__` definition here even though its signature is incorrect.
|
|
#[test]
|
|
fn goto_definition_unary_operator_with_bad_dunder_definition() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
class Test:
|
|
def __invert__(self, extra_arg) -> 'Test': ...
|
|
|
|
a = Test()
|
|
|
|
<CURSOR>~a
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:7:1
|
|
|
|
|
5 | a = Test()
|
|
6 |
|
|
7 | ~a
|
|
| ^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:3:9
|
|
|
|
|
2 | class Test:
|
|
3 | def __invert__(self, extra_arg) -> 'Test': ...
|
|
| ----------
|
|
4 |
|
|
5 | a = Test()
|
|
|
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_unary_after_operator() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
class Test:
|
|
def __invert__(self) -> 'Test': ...
|
|
|
|
a = Test()
|
|
|
|
~<CURSOR> a
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:7:1
|
|
|
|
|
5 | a = Test()
|
|
6 |
|
|
7 | ~ a
|
|
| ^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:3:9
|
|
|
|
|
2 | class Test:
|
|
3 | def __invert__(self) -> 'Test': ...
|
|
| ----------
|
|
4 |
|
|
5 | a = Test()
|
|
|
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_unary_between_operator_and_operand() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
class Test:
|
|
def __invert__(self) -> 'Test': ...
|
|
|
|
a = Test()
|
|
|
|
-<CURSOR>a
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:7:2
|
|
|
|
|
5 | a = Test()
|
|
6 |
|
|
7 | -a
|
|
| ^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:5:1
|
|
|
|
|
3 | def __invert__(self) -> 'Test': ...
|
|
4 |
|
|
5 | a = Test()
|
|
| -
|
|
6 |
|
|
7 | -a
|
|
|
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_unary_not_with_dunder_bool() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
class Test:
|
|
def __bool__(self) -> bool: ...
|
|
|
|
a = Test()
|
|
|
|
<CURSOR>not a
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:7:1
|
|
|
|
|
5 | a = Test()
|
|
6 |
|
|
7 | not a
|
|
| ^^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:3:9
|
|
|
|
|
2 | class Test:
|
|
3 | def __bool__(self) -> bool: ...
|
|
| --------
|
|
4 |
|
|
5 | a = Test()
|
|
|
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn goto_definition_unary_not_with_dunder_len() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
class Test:
|
|
def __len__(self) -> 42: ...
|
|
|
|
a = Test()
|
|
|
|
<CURSOR>not a
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:7:1
|
|
|
|
|
5 | a = Test()
|
|
6 |
|
|
7 | not a
|
|
| ^^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:3:9
|
|
|
|
|
2 | class Test:
|
|
3 | def __len__(self) -> 42: ...
|
|
| -------
|
|
4 |
|
|
5 | a = Test()
|
|
|
|
|
");
|
|
}
|
|
|
|
/// If `__bool__` is defined incorrectly, `not` does not fallback to `__len__`.
|
|
/// Instead, we jump to the `__bool__` definition as usual.
|
|
/// The fallback only occurs if `__bool__` is not defined at all.
|
|
#[test]
|
|
fn goto_definition_unary_not_with_bad_dunder_bool_and_dunder_len() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
class Test:
|
|
def __bool__(self, extra_arg) -> bool: ...
|
|
def __len__(self) -> 42: ...
|
|
|
|
a = Test()
|
|
|
|
<CURSOR>not a
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:8:1
|
|
|
|
|
6 | a = Test()
|
|
7 |
|
|
8 | not a
|
|
| ^^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:3:9
|
|
|
|
|
2 | class Test:
|
|
3 | def __bool__(self, extra_arg) -> bool: ...
|
|
| --------
|
|
4 | def __len__(self) -> 42: ...
|
|
|
|
|
");
|
|
}
|
|
|
|
/// Same as for unary operators that only use a single dunder,
|
|
/// we still jump to `__len__` for `not` goto-definition even if
|
|
/// the `__len__` signature is incorrect (but only if there is no
|
|
/// `__bool__` definition).
|
|
#[test]
|
|
fn goto_definition_unary_not_with_no_dunder_bool_and_bad_dunder_len() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
class Test:
|
|
def __len__(self, extra_arg) -> 42: ...
|
|
|
|
a = Test()
|
|
|
|
<CURSOR>not a
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:7:1
|
|
|
|
|
5 | a = Test()
|
|
6 |
|
|
7 | not a
|
|
| ^^^ Clicking here
|
|
|
|
|
info: Found 1 definition
|
|
--> main.py:3:9
|
|
|
|
|
2 | class Test:
|
|
3 | def __len__(self, extra_arg) -> 42: ...
|
|
| -------
|
|
4 |
|
|
5 | a = Test()
|
|
|
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn float_annotation() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
a: float<CURSOR> = 3.14
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:2:4
|
|
|
|
|
2 | a: float = 3.14
|
|
| ^^^^^ Clicking here
|
|
|
|
|
info: Found 2 definitions
|
|
--> stdlib/builtins.pyi:348:7
|
|
|
|
|
347 | @disjoint_base
|
|
348 | class int:
|
|
| ---
|
|
349 | """int([x]) -> integer
|
|
350 | int(x, base=10) -> integer
|
|
|
|
|
::: stdlib/builtins.pyi:661:7
|
|
|
|
|
660 | @disjoint_base
|
|
661 | class float:
|
|
| -----
|
|
662 | """Convert a string or number to a floating-point number, if possible."""
|
|
|
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn complex_annotation() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
a: complex<CURSOR> = 3.14
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:2:4
|
|
|
|
|
2 | a: complex = 3.14
|
|
| ^^^^^^^ Clicking here
|
|
|
|
|
info: Found 3 definitions
|
|
--> stdlib/builtins.pyi:348:7
|
|
|
|
|
347 | @disjoint_base
|
|
348 | class int:
|
|
| ---
|
|
349 | """int([x]) -> integer
|
|
350 | int(x, base=10) -> integer
|
|
|
|
|
::: stdlib/builtins.pyi:661:7
|
|
|
|
|
660 | @disjoint_base
|
|
661 | class float:
|
|
| -----
|
|
662 | """Convert a string or number to a floating-point number, if possible."""
|
|
|
|
|
::: stdlib/builtins.pyi:822:7
|
|
|
|
|
821 | @disjoint_base
|
|
822 | class complex:
|
|
| -------
|
|
823 | """Create a complex number from a string or numbers.
|
|
|
|
|
"#);
|
|
}
|
|
|
|
/// Regression test for <https://github.com/astral-sh/ty/issues/1451>.
|
|
/// We must ensure we respect re-import convention for stub files for
|
|
/// imports in builtins.pyi.
|
|
#[test]
|
|
fn goto_definition_unimported_symbol_imported_in_builtins() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
"
|
|
Traceb<CURSOR>ackType
|
|
",
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
|
}
|
|
|
|
// TODO: Should only list `a: int`
|
|
#[test]
|
|
fn redeclarations() {
|
|
let test = CursorTest::builder()
|
|
.source(
|
|
"main.py",
|
|
r#"
|
|
a: str = "test"
|
|
|
|
a: int = 10
|
|
|
|
print(a<CURSOR>)
|
|
|
|
a: bool = True
|
|
"#,
|
|
)
|
|
.build();
|
|
|
|
assert_snapshot!(test.goto_definition(), @r#"
|
|
info[goto-definition]: Go to definition
|
|
--> main.py:6:7
|
|
|
|
|
4 | a: int = 10
|
|
5 |
|
|
6 | print(a)
|
|
| ^ Clicking here
|
|
7 |
|
|
8 | a: bool = True
|
|
|
|
|
info: Found 3 definitions
|
|
--> main.py:2:1
|
|
|
|
|
2 | a: str = "test"
|
|
| -
|
|
3 |
|
|
4 | a: int = 10
|
|
| -
|
|
5 |
|
|
6 | print(a)
|
|
7 |
|
|
8 | a: bool = True
|
|
| -
|
|
|
|
|
"#);
|
|
}
|
|
|
|
impl CursorTest {
|
|
fn goto_definition(&self) -> String {
|
|
let Some(targets) = salsa::attach(&self.db, || {
|
|
goto_definition(&self.db, self.cursor.file, self.cursor.offset)
|
|
}) else {
|
|
return "No goto target found".to_string();
|
|
};
|
|
|
|
if targets.is_empty() {
|
|
return "No definitions found".to_string();
|
|
}
|
|
|
|
self.render_diagnostics([GotoDiagnostic::new(GotoAction::Definition, targets)])
|
|
}
|
|
}
|
|
|
|
pub(crate) struct GotoDiagnostic {
|
|
action: GotoAction,
|
|
targets: RangedValue<NavigationTargets>,
|
|
}
|
|
|
|
impl GotoDiagnostic {
|
|
pub(crate) fn new(action: GotoAction, targets: RangedValue<NavigationTargets>) -> Self {
|
|
Self { action, targets }
|
|
}
|
|
}
|
|
|
|
impl IntoDiagnostic for GotoDiagnostic {
|
|
fn into_diagnostic(self) -> Diagnostic {
|
|
let source = self.targets.range;
|
|
let mut main = Diagnostic::new(
|
|
DiagnosticId::Lint(LintName::of(self.action.name())),
|
|
Severity::Info,
|
|
self.action.label().to_string(),
|
|
);
|
|
|
|
main.annotate(
|
|
Annotation::primary(Span::from(source.file()).with_range(source.range()))
|
|
.message("Clicking here"),
|
|
);
|
|
|
|
let mut sub = SubDiagnostic::new(
|
|
SubDiagnosticSeverity::Info,
|
|
format_args!(
|
|
"Found {} {}{}",
|
|
self.targets.len(),
|
|
self.action.item_label(),
|
|
if self.targets.len() == 1 { "" } else { "s" }
|
|
),
|
|
);
|
|
|
|
for target in self.targets {
|
|
sub.annotate(Annotation::secondary(
|
|
Span::from(target.file()).with_range(target.focus_range()),
|
|
));
|
|
}
|
|
|
|
main.sub(sub);
|
|
|
|
main
|
|
}
|
|
}
|
|
|
|
pub(crate) enum GotoAction {
|
|
Definition,
|
|
Declaration,
|
|
TypeDefinition,
|
|
}
|
|
|
|
impl GotoAction {
|
|
fn name(&self) -> &'static str {
|
|
match self {
|
|
GotoAction::Definition => "goto-definition",
|
|
GotoAction::Declaration => "goto-declaration",
|
|
GotoAction::TypeDefinition => "goto-type definition",
|
|
}
|
|
}
|
|
|
|
fn label(&self) -> &'static str {
|
|
match self {
|
|
GotoAction::Definition => "Go to definition",
|
|
GotoAction::Declaration => "Go to declaration",
|
|
GotoAction::TypeDefinition => "Go to type definition",
|
|
}
|
|
}
|
|
|
|
fn item_label(&self) -> &'static str {
|
|
match self {
|
|
GotoAction::Definition => "definition",
|
|
GotoAction::Declaration => "declaration",
|
|
GotoAction::TypeDefinition => "type definition",
|
|
}
|
|
}
|
|
}
|
|
}
|