mirror of
https://github.com/denoland/deno.git
synced 2024-12-25 16:49:18 -05:00
fix(lsp): textDocument/references
should respect includeDeclaration
(#18496)
This commit is contained in:
parent
cc7f5c1015
commit
c4f82cab31
7 changed files with 339 additions and 98 deletions
|
@ -297,70 +297,72 @@ async fn resolve_references_code_lens(
|
|||
data: CodeLensData,
|
||||
language_server: &language_server::Inner,
|
||||
) -> Result<lsp::CodeLens, AnyError> {
|
||||
let asset_or_document =
|
||||
language_server.get_asset_or_document(&data.specifier)?;
|
||||
let line_index = asset_or_document.line_index();
|
||||
let req = tsc::RequestMethod::GetReferences((
|
||||
data.specifier.clone(),
|
||||
line_index.offset_tsc(code_lens.range.start)?,
|
||||
));
|
||||
let snapshot = language_server.snapshot();
|
||||
let maybe_references: Option<Vec<tsc::ReferenceEntry>> =
|
||||
language_server.ts_server.request(snapshot, req).await?;
|
||||
if let Some(references) = maybe_references {
|
||||
fn get_locations(
|
||||
maybe_referenced_symbols: Option<Vec<tsc::ReferencedSymbol>>,
|
||||
language_server: &language_server::Inner,
|
||||
) -> Result<Vec<lsp::Location>, AnyError> {
|
||||
let symbols = match maybe_referenced_symbols {
|
||||
Some(symbols) => symbols,
|
||||
None => return Ok(Vec::new()),
|
||||
};
|
||||
let mut locations = Vec::new();
|
||||
for reference in references {
|
||||
for reference in symbols.iter().flat_map(|s| &s.references) {
|
||||
if reference.is_definition {
|
||||
continue;
|
||||
}
|
||||
let reference_specifier =
|
||||
resolve_url(&reference.document_span.file_name)?;
|
||||
resolve_url(&reference.entry.document_span.file_name)?;
|
||||
let asset_or_doc =
|
||||
language_server.get_asset_or_document(&reference_specifier)?;
|
||||
locations.push(
|
||||
reference
|
||||
.entry
|
||||
.to_location(asset_or_doc.line_index(), &language_server.url_map),
|
||||
);
|
||||
}
|
||||
let command = if !locations.is_empty() {
|
||||
let title = if locations.len() > 1 {
|
||||
format!("{} references", locations.len())
|
||||
} else {
|
||||
"1 reference".to_string()
|
||||
};
|
||||
lsp::Command {
|
||||
title,
|
||||
command: "deno.showReferences".to_string(),
|
||||
arguments: Some(vec![
|
||||
json!(data.specifier),
|
||||
json!(code_lens.range.start),
|
||||
json!(locations),
|
||||
]),
|
||||
}
|
||||
} else {
|
||||
lsp::Command {
|
||||
title: "0 references".to_string(),
|
||||
command: "".to_string(),
|
||||
arguments: None,
|
||||
}
|
||||
};
|
||||
Ok(lsp::CodeLens {
|
||||
range: code_lens.range,
|
||||
command: Some(command),
|
||||
data: None,
|
||||
})
|
||||
} else {
|
||||
let command = lsp::Command {
|
||||
title: "0 references".to_string(),
|
||||
command: "".to_string(),
|
||||
arguments: None,
|
||||
};
|
||||
Ok(lsp::CodeLens {
|
||||
range: code_lens.range,
|
||||
command: Some(command),
|
||||
data: None,
|
||||
})
|
||||
Ok(locations)
|
||||
}
|
||||
|
||||
let asset_or_document =
|
||||
language_server.get_asset_or_document(&data.specifier)?;
|
||||
let line_index = asset_or_document.line_index();
|
||||
let snapshot = language_server.snapshot();
|
||||
let maybe_referenced_symbols = language_server
|
||||
.ts_server
|
||||
.find_references(
|
||||
snapshot,
|
||||
&data.specifier,
|
||||
line_index.offset_tsc(code_lens.range.start)?,
|
||||
)
|
||||
.await?;
|
||||
let locations = get_locations(maybe_referenced_symbols, language_server)?;
|
||||
let title = if locations.len() == 1 {
|
||||
"1 reference".to_string()
|
||||
} else {
|
||||
format!("{} references", locations.len())
|
||||
};
|
||||
let command = if locations.is_empty() {
|
||||
lsp::Command {
|
||||
title,
|
||||
command: String::new(),
|
||||
arguments: None,
|
||||
}
|
||||
} else {
|
||||
lsp::Command {
|
||||
title,
|
||||
command: "deno.showReferences".to_string(),
|
||||
arguments: Some(vec![
|
||||
json!(data.specifier),
|
||||
json!(code_lens.range.start),
|
||||
json!(locations),
|
||||
]),
|
||||
}
|
||||
};
|
||||
Ok(lsp::CodeLens {
|
||||
range: code_lens.range,
|
||||
command: Some(command),
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn resolve_code_lens(
|
||||
|
|
|
@ -1953,27 +1953,23 @@ impl Inner {
|
|||
let mark = self.performance.mark("references", Some(¶ms));
|
||||
let asset_or_doc = self.get_asset_or_document(&specifier)?;
|
||||
let line_index = asset_or_doc.line_index();
|
||||
let req = tsc::RequestMethod::GetReferences((
|
||||
specifier.clone(),
|
||||
line_index.offset_tsc(params.text_document_position.position)?,
|
||||
));
|
||||
let maybe_references: Option<Vec<tsc::ReferenceEntry>> = self
|
||||
let maybe_referenced_symbols = self
|
||||
.ts_server
|
||||
.request(self.snapshot(), req)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Unable to get references from TypeScript: {}", err);
|
||||
LspError::internal_error()
|
||||
})?;
|
||||
.find_references(
|
||||
self.snapshot(),
|
||||
&specifier,
|
||||
line_index.offset_tsc(params.text_document_position.position)?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(references) = maybe_references {
|
||||
if let Some(symbols) = maybe_referenced_symbols {
|
||||
let mut results = Vec::new();
|
||||
for reference in references {
|
||||
for reference in symbols.iter().flat_map(|s| &s.references) {
|
||||
if !params.context.include_declaration && reference.is_definition {
|
||||
continue;
|
||||
}
|
||||
let reference_specifier =
|
||||
resolve_url(&reference.document_span.file_name).unwrap();
|
||||
resolve_url(&reference.entry.document_span.file_name).unwrap();
|
||||
let reference_line_index = if reference_specifier == specifier {
|
||||
line_index.clone()
|
||||
} else {
|
||||
|
@ -1981,8 +1977,11 @@ impl Inner {
|
|||
self.get_asset_or_document(&reference_specifier)?;
|
||||
asset_or_doc.line_index()
|
||||
};
|
||||
results
|
||||
.push(reference.to_location(reference_line_index, &self.url_map));
|
||||
results.push(
|
||||
reference
|
||||
.entry
|
||||
.to_location(reference_line_index, &self.url_map),
|
||||
);
|
||||
}
|
||||
|
||||
self.performance.measure(mark);
|
||||
|
|
|
@ -149,7 +149,28 @@ impl TsServer {
|
|||
if self.0.send((req, snapshot, tx, token)).is_err() {
|
||||
return Err(anyhow!("failed to send request to tsc thread"));
|
||||
}
|
||||
rx.await?.map(|v| serde_json::from_value::<R>(v).unwrap())
|
||||
let value = rx.await??;
|
||||
Ok(serde_json::from_value::<R>(value)?)
|
||||
}
|
||||
|
||||
// todo(dsherret): refactor the rest of the request methods to have
|
||||
// methods to call on this struct, then make `RequestMethod` and
|
||||
// friends internal
|
||||
|
||||
pub async fn find_references(
|
||||
&self,
|
||||
snapshot: Arc<StateSnapshot>,
|
||||
specifier: &ModuleSpecifier,
|
||||
position: u32,
|
||||
) -> Result<Option<Vec<ReferencedSymbol>>, LspError> {
|
||||
let req = RequestMethod::FindReferences {
|
||||
specifier: specifier.clone(),
|
||||
position,
|
||||
};
|
||||
self.request(snapshot, req).await.map_err(|err| {
|
||||
log::error!("Unable to get references from TypeScript: {}", err);
|
||||
LspError::internal_error()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1688,10 +1709,31 @@ pub struct CombinedCodeActions {
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReferenceEntry {
|
||||
// is_write_access: bool,
|
||||
pub struct ReferencedSymbol {
|
||||
pub definition: ReferencedSymbolDefinitionInfo,
|
||||
pub references: Vec<ReferencedSymbolEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReferencedSymbolDefinitionInfo {
|
||||
#[serde(flatten)]
|
||||
pub definition_info: DefinitionInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReferencedSymbolEntry {
|
||||
#[serde(default)]
|
||||
pub is_definition: bool,
|
||||
#[serde(flatten)]
|
||||
pub entry: ReferenceEntry,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReferenceEntry {
|
||||
// is_write_access: bool,
|
||||
// is_in_string: Option<bool>,
|
||||
#[serde(flatten)]
|
||||
pub document_span: DocumentSpan,
|
||||
|
@ -3178,8 +3220,11 @@ pub enum RequestMethod {
|
|||
GetOutliningSpans(ModuleSpecifier),
|
||||
/// Return quick info at position (hover information).
|
||||
GetQuickInfo((ModuleSpecifier, u32)),
|
||||
/// Get document references for a specific position.
|
||||
GetReferences((ModuleSpecifier, u32)),
|
||||
/// Finds the document references for a specific position.
|
||||
FindReferences {
|
||||
specifier: ModuleSpecifier,
|
||||
position: u32,
|
||||
},
|
||||
/// Get signature help items for a specific position.
|
||||
GetSignatureHelpItems((ModuleSpecifier, u32, SignatureHelpItemsOptions)),
|
||||
/// Get a selection range for a specific position.
|
||||
|
@ -3349,9 +3394,12 @@ impl RequestMethod {
|
|||
"specifier": state.denormalize_specifier(specifier),
|
||||
"position": position,
|
||||
}),
|
||||
RequestMethod::GetReferences((specifier, position)) => json!({
|
||||
RequestMethod::FindReferences {
|
||||
specifier,
|
||||
position,
|
||||
} => json!({
|
||||
"id": id,
|
||||
"method": "getReferences",
|
||||
"method": "findReferences",
|
||||
"specifier": state.denormalize_specifier(specifier),
|
||||
"position": position,
|
||||
}),
|
||||
|
|
|
@ -2367,16 +2367,32 @@ fn lsp_semantic_tokens() {
|
|||
fn lsp_code_lens() {
|
||||
let mut client = LspClientBuilder::new().build();
|
||||
client.initialize_default();
|
||||
client.did_open(
|
||||
json!({
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts",
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": "class A {\n a = \"a\";\n\n b() {\n console.log(this.a);\n }\n\n c() {\n this.a = \"c\";\n }\n}\n\nconst a = new A();\na.b();\n"
|
||||
}
|
||||
}),
|
||||
);
|
||||
client.did_open(json!({
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts",
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": concat!(
|
||||
"class A {\n",
|
||||
" a = \"a\";\n",
|
||||
"\n",
|
||||
" b() {\n",
|
||||
" console.log(this.a);\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" c() {\n",
|
||||
" this.a = \"c\";\n",
|
||||
" }\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"const a = new A();\n",
|
||||
"a.b();\n",
|
||||
"const b = 2;\n",
|
||||
"const c = 3;\n",
|
||||
"c; c;",
|
||||
),
|
||||
}
|
||||
}));
|
||||
let res = client.write_request(
|
||||
"textDocument/codeLens",
|
||||
json!({
|
||||
|
@ -2428,18 +2444,12 @@ fn lsp_code_lens() {
|
|||
"end": { "line": 0, "character": 7 }
|
||||
},
|
||||
"command": {
|
||||
"title": "2 references",
|
||||
"title": "1 reference",
|
||||
"command": "deno.showReferences",
|
||||
"arguments": [
|
||||
"file:///a/file.ts",
|
||||
{ "line": 0, "character": 6 },
|
||||
[{
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 6 },
|
||||
"end": { "line": 0, "character": 7 }
|
||||
}
|
||||
}, {
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 12, "character": 14 },
|
||||
|
@ -2450,6 +2460,80 @@ fn lsp_code_lens() {
|
|||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 0 references
|
||||
let res = client.write_request(
|
||||
"codeLens/resolve",
|
||||
json!({
|
||||
"range": {
|
||||
"start": { "line": 14, "character": 6 },
|
||||
"end": { "line": 14, "character": 7 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
json!({
|
||||
"range": {
|
||||
"start": { "line": 14, "character": 6 },
|
||||
"end": { "line": 14, "character": 7 }
|
||||
},
|
||||
"command": {
|
||||
"title": "0 references",
|
||||
"command": "",
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 2 references
|
||||
let res = client.write_request(
|
||||
"codeLens/resolve",
|
||||
json!({
|
||||
"range": {
|
||||
"start": { "line": 15, "character": 6 },
|
||||
"end": { "line": 15, "character": 7 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
json!({
|
||||
"range": {
|
||||
"start": { "line": 15, "character": 6 },
|
||||
"end": { "line": 15, "character": 7 }
|
||||
},
|
||||
"command": {
|
||||
"title": "2 references",
|
||||
"command": "deno.showReferences",
|
||||
"arguments": [
|
||||
"file:///a/file.ts",
|
||||
{ "line": 15, "character": 6 },
|
||||
[{
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 16, "character": 0 },
|
||||
"end": { "line": 16, "character": 1 }
|
||||
}
|
||||
},{
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 16, "character": 3 },
|
||||
"end": { "line": 16, "character": 4 }
|
||||
}
|
||||
}]
|
||||
]
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
client.shutdown();
|
||||
}
|
||||
|
||||
|
@ -3091,6 +3175,114 @@ fn lsp_nav_tree_updates() {
|
|||
client.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_find_references() {
|
||||
let mut client = LspClientBuilder::new().build();
|
||||
client.initialize_default();
|
||||
client.did_open(json!({
|
||||
"textDocument": {
|
||||
"uri": "file:///a/mod.ts",
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": r#"export const a = 1;\nconst b = 2;"#
|
||||
}
|
||||
}));
|
||||
client.did_open(json!({
|
||||
"textDocument": {
|
||||
"uri": "file:///a/mod.test.ts",
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": r#"import { a } from './mod.ts'; console.log(a);"#
|
||||
}
|
||||
}));
|
||||
|
||||
// test without including the declaration
|
||||
let res = client.write_request(
|
||||
"textDocument/references",
|
||||
json!({
|
||||
"textDocument": {
|
||||
"uri": "file:///a/mod.ts",
|
||||
},
|
||||
"position": { "line": 0, "character": 13 },
|
||||
"context": {
|
||||
"includeDeclaration": false
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
json!([{
|
||||
"uri": "file:///a/mod.test.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 9 },
|
||||
"end": { "line": 0, "character": 10 }
|
||||
}
|
||||
}, {
|
||||
"uri": "file:///a/mod.test.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 42 },
|
||||
"end": { "line": 0, "character": 43 }
|
||||
}
|
||||
}])
|
||||
);
|
||||
|
||||
// test with including the declaration
|
||||
let res = client.write_request(
|
||||
"textDocument/references",
|
||||
json!({
|
||||
"textDocument": {
|
||||
"uri": "file:///a/mod.ts",
|
||||
},
|
||||
"position": { "line": 0, "character": 13 },
|
||||
"context": {
|
||||
"includeDeclaration": true
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
json!([{
|
||||
"uri": "file:///a/mod.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 13 },
|
||||
"end": { "line": 0, "character": 14 }
|
||||
}
|
||||
}, {
|
||||
"uri": "file:///a/mod.test.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 9 },
|
||||
"end": { "line": 0, "character": 10 }
|
||||
}
|
||||
}, {
|
||||
"uri": "file:///a/mod.test.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 42 },
|
||||
"end": { "line": 0, "character": 43 }
|
||||
}
|
||||
}])
|
||||
);
|
||||
|
||||
// test 0 references
|
||||
let res = client.write_request(
|
||||
"textDocument/references",
|
||||
json!({
|
||||
"textDocument": {
|
||||
"uri": "file:///a/mod.ts",
|
||||
},
|
||||
"position": { "line": 1, "character": 6 },
|
||||
"context": {
|
||||
"includeDeclaration": false
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(res, json!(null)); // seems it always returns null for this, which is ok
|
||||
|
||||
client.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_signature_help() {
|
||||
let mut client = LspClientBuilder::new().build();
|
||||
|
|
|
@ -905,7 +905,7 @@ fn package_json_uncached_no_error() {
|
|||
);
|
||||
test_context.new_command().with_pty(|mut console| {
|
||||
console.write_line("console.log(123 + 456);");
|
||||
console.expect("579");
|
||||
console.expect_all(&["579", "undefined"]);
|
||||
assert_not_contains!(
|
||||
console.all_output(),
|
||||
"Could not set npm package requirements",
|
||||
|
@ -914,7 +914,7 @@ fn package_json_uncached_no_error() {
|
|||
// should support getting the package now though
|
||||
console
|
||||
.write_line("import { getValue, setValue } from '@denotest/esm-basic';");
|
||||
console.expect("undefined");
|
||||
console.expect_all(&["undefined", "Initialize"]);
|
||||
console.write_line("setValue(12 + 30);");
|
||||
console.expect("undefined");
|
||||
console.write_line("getValue()");
|
||||
|
|
|
@ -1121,10 +1121,10 @@ delete Object.prototype.__proto__;
|
|||
),
|
||||
);
|
||||
}
|
||||
case "getReferences": {
|
||||
case "findReferences": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.getReferencesAtPosition(
|
||||
languageService.findReferences(
|
||||
request.specifier,
|
||||
request.position,
|
||||
),
|
||||
|
|
6
cli/tsc/compiler.d.ts
vendored
6
cli/tsc/compiler.d.ts
vendored
|
@ -75,7 +75,7 @@ declare global {
|
|||
| GetNavigationTree
|
||||
| GetOutliningSpans
|
||||
| GetQuickInfoRequest
|
||||
| GetReferencesRequest
|
||||
| FindReferencesRequest
|
||||
| GetSignatureHelpItemsRequest
|
||||
| GetSmartSelectionRange
|
||||
| GetSupportedCodeFixes
|
||||
|
@ -212,8 +212,8 @@ declare global {
|
|||
position: number;
|
||||
}
|
||||
|
||||
interface GetReferencesRequest extends BaseLanguageServerRequest {
|
||||
method: "getReferences";
|
||||
interface FindReferencesRequest extends BaseLanguageServerRequest {
|
||||
method: "findReferences";
|
||||
specifier: string;
|
||||
position: number;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue