diff --git a/cli/lsp/capabilities.rs b/cli/lsp/capabilities.rs index e05cc27b84..8f17a6aff4 100644 --- a/cli/lsp/capabilities.rs +++ b/cli/lsp/capabilities.rs @@ -27,6 +27,7 @@ use lspower::lsp::SignatureHelpOptions; use lspower::lsp::TextDocumentSyncCapability; use lspower::lsp::TextDocumentSyncKind; use lspower::lsp::TextDocumentSyncOptions; +use lspower::lsp::TypeDefinitionProviderCapability; use lspower::lsp::WorkDoneProgressOptions; use lspower::lsp::WorkspaceFoldersServerCapabilities; use lspower::lsp::WorkspaceServerCapabilities; @@ -109,7 +110,9 @@ pub fn server_capabilities( }), declaration_provider: None, definition_provider: Some(OneOf::Left(true)), - type_definition_provider: None, + type_definition_provider: Some(TypeDefinitionProviderCapability::Simple( + true, + )), implementation_provider: Some(ImplementationProviderCapability::Simple( true, )), diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index fb2b042148..a0d0ee0add 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -1622,6 +1622,54 @@ impl Inner { } } + async fn goto_type_definition( + &mut self, + params: GotoTypeDefinitionParams, + ) -> LspResult> { + let specifier = self + .url_map + .normalize_url(¶ms.text_document_position_params.text_document.uri); + if !self.is_diagnosable(&specifier) + || !self.config.specifier_enabled(&specifier) + { + return Ok(None); + } + + let mark = self.performance.mark("goto_definition", Some(¶ms)); + let asset_or_doc = self.get_cached_asset_or_document(&specifier)?; + let line_index = asset_or_doc.line_index(); + let req = tsc::RequestMethod::GetTypeDefinition { + specifier, + position: line_index + .offset_tsc(params.text_document_position_params.position)?, + }; + let maybe_definition_info: Option> = self + .ts_server + .request(self.snapshot()?, req) + .await + .map_err(|err| { + error!("Unable to get type definition from TypeScript: {}", err); + LspError::internal_error() + })?; + + let response = if let Some(definition_info) = maybe_definition_info { + let mut location_links = Vec::new(); + for info in definition_info { + if let Some(link) = + info.document_span.to_link(line_index.clone(), self).await + { + location_links.push(link); + } + } + Some(GotoTypeDefinitionResponse::Link(location_links)) + } else { + None + }; + + self.performance.measure(mark); + Ok(response) + } + async fn completion( &mut self, params: CompletionParams, @@ -2428,6 +2476,13 @@ impl lspower::LanguageServer for LanguageServer { self.0.lock().await.goto_definition(params).await } + async fn goto_type_definition( + &self, + params: GotoTypeDefinitionParams, + ) -> LspResult> { + self.0.lock().await.goto_type_definition(params).await + } + async fn completion( &self, params: CompletionParams, diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index e56b7ab686..9647a79fc3 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -2769,6 +2769,11 @@ pub enum RequestMethod { GetSmartSelectionRange((ModuleSpecifier, u32)), /// Get the diagnostic codes that support some form of code fix. GetSupportedCodeFixes, + /// Get the type definition information for a specific position. + GetTypeDefinition { + specifier: ModuleSpecifier, + position: u32, + }, /// Resolve a call hierarchy item for a specific position. PrepareCallHierarchy((ModuleSpecifier, u32)), /// Resolve incoming call hierarchy items for a specific position. @@ -2811,7 +2816,7 @@ impl RequestMethod { "id": id, "method": "getApplicableRefactors", "specifier": state.denormalize_specifier(specifier), - "range": { "pos": span.start, "end": span.start + span.length}, + "range": { "pos": span.start, "end": span.start + span.length }, "kind": kind, }), RequestMethod::GetEditsForRefactor(( @@ -2950,6 +2955,15 @@ impl RequestMethod { "id": id, "method": "getSupportedCodeFixes", }), + RequestMethod::GetTypeDefinition { + specifier, + position, + } => json!({ + "id": id, + "method": "getTypeDefinition", + "specifier": state.denormalize_specifier(specifier), + "position": position + }), RequestMethod::PrepareCallHierarchy((specifier, position)) => { json!({ "id": id, diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs index 8e7de02863..3d55ac28c0 100644 --- a/cli/tests/integration/lsp_tests.rs +++ b/cli/tests/integration/lsp_tests.rs @@ -1266,6 +1266,66 @@ fn lsp_hover_typescript_types() { shutdown(&mut client); } +#[test] +fn lsp_goto_type_definition() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "interface A {\n a: string;\n}\n\nexport class B implements A {\n a = \"a\";\n log() {\n console.log(this.a);\n }\n}\n\nconst b = new B();\nb;\n", + } + }), + ); + let (maybe_res, maybe_error) = client + .write_request::<_, _, Value>( + "textDocument/typeDefinition", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 12, + "character": 1 + } + }), + ) + .unwrap(); + assert!(maybe_error.is_none()); + assert_eq!( + maybe_res, + Some(json!([ + { + "targetUri": "file:///a/file.ts", + "targetRange": { + "start": { + "line": 4, + "character": 0 + }, + "end": { + "line": 9, + "character": 1 + } + }, + "targetSelectionRange": { + "start": { + "line": 4, + "character": 13 + }, + "end": { + "line": 4, + "character": 14 + } + } + } + ])) + ); + shutdown(&mut client); +} + #[test] fn lsp_call_hierarchy() { let mut client = init("initialize_params.json"); diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js index 1b3999bf9c..d309956cb3 100644 --- a/cli/tsc/99_main_compiler.js +++ b/cli/tsc/99_main_compiler.js @@ -810,6 +810,15 @@ delete Object.prototype.__proto__; ts.getSupportedCodeFixes(), ); } + case "getTypeDefinition": { + return respond( + id, + languageService.getTypeDefinitionAtPosition( + request.specifier, + request.position, + ), + ); + } case "prepareCallHierarchy": { return respond( id, diff --git a/cli/tsc/compiler.d.ts b/cli/tsc/compiler.d.ts index 8e6b6d4170..d206c0b6b2 100644 --- a/cli/tsc/compiler.d.ts +++ b/cli/tsc/compiler.d.ts @@ -66,6 +66,7 @@ declare global { | GetSignatureHelpItemsRequest | GetSmartSelectionRange | GetSupportedCodeFixes + | GetTypeDefinitionRequest | PrepareCallHierarchy | ProvideCallHierarchyIncomingCalls | ProvideCallHierarchyOutgoingCalls; @@ -220,6 +221,12 @@ declare global { method: "getSupportedCodeFixes"; } + interface GetTypeDefinitionRequest extends BaseLanguageServerRequest { + method: "getTypeDefinition"; + specifier: string; + position: number; + } + interface PrepareCallHierarchy extends BaseLanguageServerRequest { method: "prepareCallHierarchy"; specifier: string;