mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
feat(lsp): implement textDocument/prepareCallHierarchy (#10061)
This commit is contained in:
parent
0552eaf569
commit
65a2a04d3b
9 changed files with 747 additions and 3 deletions
|
@ -5,6 +5,7 @@
|
|||
///! language server, which helps determine what messages are sent from the
|
||||
///! client.
|
||||
///!
|
||||
use lspower::lsp::CallHierarchyServerCapability;
|
||||
use lspower::lsp::ClientCapabilities;
|
||||
use lspower::lsp::CodeActionKind;
|
||||
use lspower::lsp::CodeActionOptions;
|
||||
|
@ -114,7 +115,7 @@ pub fn server_capabilities(
|
|||
document_link_provider: None,
|
||||
color_provider: None,
|
||||
execute_command_provider: None,
|
||||
call_hierarchy_provider: None,
|
||||
call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)),
|
||||
semantic_tokens_provider: None,
|
||||
workspace: None,
|
||||
experimental: None,
|
||||
|
|
|
@ -1643,6 +1643,192 @@ impl Inner {
|
|||
Ok(response)
|
||||
}
|
||||
|
||||
async fn incoming_calls(
|
||||
&mut self,
|
||||
params: CallHierarchyIncomingCallsParams,
|
||||
) -> LspResult<Option<Vec<CallHierarchyIncomingCall>>> {
|
||||
if !self.enabled() {
|
||||
return Ok(None);
|
||||
}
|
||||
let mark = self.performance.mark("incoming_calls");
|
||||
let specifier = self.url_map.normalize_url(¶ms.item.uri);
|
||||
|
||||
let line_index =
|
||||
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
||||
line_index
|
||||
} else {
|
||||
return Err(LspError::invalid_params(format!(
|
||||
"An unexpected specifier ({}) was provided.",
|
||||
specifier
|
||||
)));
|
||||
};
|
||||
|
||||
let req = tsc::RequestMethod::ProvideCallHierarchyIncomingCalls((
|
||||
specifier.clone(),
|
||||
line_index.offset_tsc(params.item.selection_range.start)?,
|
||||
));
|
||||
let incoming_calls: Vec<tsc::CallHierarchyIncomingCall> = self
|
||||
.ts_server
|
||||
.request(self.snapshot(), req)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to request to tsserver {}", err);
|
||||
LspError::invalid_request()
|
||||
})?;
|
||||
|
||||
let maybe_root_path_owned = self
|
||||
.config
|
||||
.root_uri
|
||||
.as_ref()
|
||||
.and_then(|uri| uri.to_file_path().ok());
|
||||
let mut resolved_items = Vec::<CallHierarchyIncomingCall>::new();
|
||||
for item in incoming_calls.iter() {
|
||||
if let Some(resolved) = item
|
||||
.try_resolve_call_hierarchy_incoming_call(
|
||||
self,
|
||||
maybe_root_path_owned.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
resolved_items.push(resolved);
|
||||
}
|
||||
}
|
||||
self.performance.measure(mark);
|
||||
Ok(Some(resolved_items))
|
||||
}
|
||||
|
||||
async fn outgoing_calls(
|
||||
&mut self,
|
||||
params: CallHierarchyOutgoingCallsParams,
|
||||
) -> LspResult<Option<Vec<CallHierarchyOutgoingCall>>> {
|
||||
if !self.enabled() {
|
||||
return Ok(None);
|
||||
}
|
||||
let mark = self.performance.mark("outgoing_calls");
|
||||
let specifier = self.url_map.normalize_url(¶ms.item.uri);
|
||||
|
||||
let line_index =
|
||||
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
||||
line_index
|
||||
} else {
|
||||
return Err(LspError::invalid_params(format!(
|
||||
"An unexpected specifier ({}) was provided.",
|
||||
specifier
|
||||
)));
|
||||
};
|
||||
|
||||
let req = tsc::RequestMethod::ProvideCallHierarchyOutgoingCalls((
|
||||
specifier.clone(),
|
||||
line_index.offset_tsc(params.item.selection_range.start)?,
|
||||
));
|
||||
let outgoing_calls: Vec<tsc::CallHierarchyOutgoingCall> = self
|
||||
.ts_server
|
||||
.request(self.snapshot(), req)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to request to tsserver {}", err);
|
||||
LspError::invalid_request()
|
||||
})?;
|
||||
|
||||
let maybe_root_path_owned = self
|
||||
.config
|
||||
.root_uri
|
||||
.as_ref()
|
||||
.and_then(|uri| uri.to_file_path().ok());
|
||||
let mut resolved_items = Vec::<CallHierarchyOutgoingCall>::new();
|
||||
for item in outgoing_calls.iter() {
|
||||
if let Some(resolved) = item
|
||||
.try_resolve_call_hierarchy_outgoing_call(
|
||||
&line_index,
|
||||
self,
|
||||
maybe_root_path_owned.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
resolved_items.push(resolved);
|
||||
}
|
||||
}
|
||||
self.performance.measure(mark);
|
||||
Ok(Some(resolved_items))
|
||||
}
|
||||
|
||||
async fn prepare_call_hierarchy(
|
||||
&mut self,
|
||||
params: CallHierarchyPrepareParams,
|
||||
) -> LspResult<Option<Vec<CallHierarchyItem>>> {
|
||||
if !self.enabled() {
|
||||
return Ok(None);
|
||||
}
|
||||
let mark = self.performance.mark("prepare_call_hierarchy");
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
||||
|
||||
let line_index =
|
||||
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
||||
line_index
|
||||
} else {
|
||||
return Err(LspError::invalid_params(format!(
|
||||
"An unexpected specifier ({}) was provided.",
|
||||
specifier
|
||||
)));
|
||||
};
|
||||
|
||||
let req = tsc::RequestMethod::PrepareCallHierarchy((
|
||||
specifier.clone(),
|
||||
line_index.offset_tsc(params.text_document_position_params.position)?,
|
||||
));
|
||||
let maybe_one_or_many: Option<tsc::OneOrMany<tsc::CallHierarchyItem>> =
|
||||
self
|
||||
.ts_server
|
||||
.request(self.snapshot(), req)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to request to tsserver {}", err);
|
||||
LspError::invalid_request()
|
||||
})?;
|
||||
|
||||
let response = if let Some(one_or_many) = maybe_one_or_many {
|
||||
let maybe_root_path_owned = self
|
||||
.config
|
||||
.root_uri
|
||||
.as_ref()
|
||||
.and_then(|uri| uri.to_file_path().ok());
|
||||
let mut resolved_items = Vec::<CallHierarchyItem>::new();
|
||||
match one_or_many {
|
||||
tsc::OneOrMany::One(item) => {
|
||||
if let Some(resolved) = item
|
||||
.try_resolve_call_hierarchy_item(
|
||||
self,
|
||||
maybe_root_path_owned.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
resolved_items.push(resolved)
|
||||
}
|
||||
}
|
||||
tsc::OneOrMany::Many(items) => {
|
||||
for item in items.iter() {
|
||||
if let Some(resolved) = item
|
||||
.try_resolve_call_hierarchy_item(
|
||||
self,
|
||||
maybe_root_path_owned.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
resolved_items.push(resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(resolved_items)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.performance.measure(mark);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn rename(
|
||||
&mut self,
|
||||
params: RenameParams,
|
||||
|
@ -1971,6 +2157,27 @@ impl lspower::LanguageServer for LanguageServer {
|
|||
self.0.lock().await.folding_range(params).await
|
||||
}
|
||||
|
||||
async fn incoming_calls(
|
||||
&self,
|
||||
params: CallHierarchyIncomingCallsParams,
|
||||
) -> LspResult<Option<Vec<CallHierarchyIncomingCall>>> {
|
||||
self.0.lock().await.incoming_calls(params).await
|
||||
}
|
||||
|
||||
async fn outgoing_calls(
|
||||
&self,
|
||||
params: CallHierarchyOutgoingCallsParams,
|
||||
) -> LspResult<Option<Vec<CallHierarchyOutgoingCall>>> {
|
||||
self.0.lock().await.outgoing_calls(params).await
|
||||
}
|
||||
|
||||
async fn prepare_call_hierarchy(
|
||||
&self,
|
||||
params: CallHierarchyPrepareParams,
|
||||
) -> LspResult<Option<Vec<CallHierarchyItem>>> {
|
||||
self.0.lock().await.prepare_call_hierarchy(params).await
|
||||
}
|
||||
|
||||
async fn rename(
|
||||
&self,
|
||||
params: RenameParams,
|
||||
|
@ -2471,6 +2678,154 @@ mod tests {
|
|||
harness.run().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_call_hierarchy() {
|
||||
let mut harness = LspTestHarness::new(vec![
|
||||
("initialize_request.json", LspResponse::RequestAny),
|
||||
("initialized_notification.json", LspResponse::None),
|
||||
(
|
||||
"prepare_call_hierarchy_did_open_notification.json",
|
||||
LspResponse::None,
|
||||
),
|
||||
(
|
||||
"prepare_call_hierarchy_request.json",
|
||||
LspResponse::Request(
|
||||
2,
|
||||
json!([
|
||||
{
|
||||
"name": "baz",
|
||||
"kind": 6,
|
||||
"detail": "Bar",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 5,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 7,
|
||||
"character": 3
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 5,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 5,
|
||||
"character": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
]),
|
||||
),
|
||||
),
|
||||
(
|
||||
"incoming_calls_request.json",
|
||||
LspResponse::Request(
|
||||
4,
|
||||
json!([
|
||||
{
|
||||
"from": {
|
||||
"name": "main",
|
||||
"kind": 12,
|
||||
"detail": "",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 10,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 13,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 10,
|
||||
"character": 9
|
||||
},
|
||||
"end": {
|
||||
"line": 10,
|
||||
"character": 13
|
||||
}
|
||||
}
|
||||
},
|
||||
"fromRanges": [
|
||||
{
|
||||
"start": {
|
||||
"line": 12,
|
||||
"character": 6
|
||||
},
|
||||
"end": {
|
||||
"line": 12,
|
||||
"character": 9
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]),
|
||||
),
|
||||
),
|
||||
(
|
||||
"outgoing_calls_request.json",
|
||||
LspResponse::Request(
|
||||
5,
|
||||
json!([
|
||||
{
|
||||
"to": {
|
||||
"name": "foo",
|
||||
"kind": 12,
|
||||
"detail": "",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 9
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 12
|
||||
}
|
||||
}
|
||||
},
|
||||
"fromRanges": [
|
||||
{
|
||||
"start": {
|
||||
"line": 6,
|
||||
"character": 11
|
||||
},
|
||||
"end": {
|
||||
"line": 6,
|
||||
"character": 14
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]),
|
||||
),
|
||||
),
|
||||
(
|
||||
"shutdown_request.json",
|
||||
LspResponse::Request(3, json!(null)),
|
||||
),
|
||||
("exit_notification.json", LspResponse::None),
|
||||
]);
|
||||
harness.run().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_format_mbc() {
|
||||
let mut harness = LspTestHarness::new(vec![
|
||||
|
|
248
cli/lsp/tsc.rs
248
cli/lsp/tsc.rs
|
@ -35,10 +35,10 @@ use log::warn;
|
|||
use lspower::lsp;
|
||||
use regex::Captures;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::thread;
|
||||
use std::{borrow::Cow, cmp};
|
||||
use std::{collections::HashMap, path::Path};
|
||||
use text_size::TextSize;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::oneshot;
|
||||
|
@ -283,6 +283,13 @@ fn parse_kind_modifier(kind_modifiers: &str) -> HashSet<&str> {
|
|||
re.split(kind_modifiers).collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum OneOrMany<T> {
|
||||
One(T),
|
||||
Many(Vec<T>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub enum ScriptElementKind {
|
||||
#[serde(rename = "")]
|
||||
|
@ -411,6 +418,33 @@ impl From<ScriptElementKind> for lsp::CompletionItemKind {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ScriptElementKind> for lsp::SymbolKind {
|
||||
fn from(kind: ScriptElementKind) -> Self {
|
||||
match kind {
|
||||
ScriptElementKind::ModuleElement => lsp::SymbolKind::Module,
|
||||
ScriptElementKind::ClassElement => lsp::SymbolKind::Class,
|
||||
ScriptElementKind::EnumElement => lsp::SymbolKind::Enum,
|
||||
ScriptElementKind::InterfaceElement => lsp::SymbolKind::Interface,
|
||||
ScriptElementKind::MemberFunctionElement => lsp::SymbolKind::Method,
|
||||
ScriptElementKind::MemberVariableElement => lsp::SymbolKind::Property,
|
||||
ScriptElementKind::MemberGetAccessorElement => lsp::SymbolKind::Property,
|
||||
ScriptElementKind::MemberSetAccessorElement => lsp::SymbolKind::Property,
|
||||
ScriptElementKind::VariableElement => lsp::SymbolKind::Variable,
|
||||
ScriptElementKind::ConstElement => lsp::SymbolKind::Variable,
|
||||
ScriptElementKind::LocalVariableElement => lsp::SymbolKind::Variable,
|
||||
ScriptElementKind::FunctionElement => lsp::SymbolKind::Function,
|
||||
ScriptElementKind::LocalFunctionElement => lsp::SymbolKind::Function,
|
||||
ScriptElementKind::ConstructSignatureElement => {
|
||||
lsp::SymbolKind::Constructor
|
||||
}
|
||||
ScriptElementKind::ConstructorImplementationElement => {
|
||||
lsp::SymbolKind::Constructor
|
||||
}
|
||||
_ => lsp::SymbolKind::Variable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TextSpan {
|
||||
|
@ -917,6 +951,182 @@ impl ReferenceEntry {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CallHierarchyItem {
|
||||
name: String,
|
||||
kind: ScriptElementKind,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
kind_modifiers: Option<String>,
|
||||
file: String,
|
||||
span: TextSpan,
|
||||
selection_span: TextSpan,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
container_name: Option<String>,
|
||||
}
|
||||
|
||||
impl CallHierarchyItem {
|
||||
pub(crate) async fn try_resolve_call_hierarchy_item(
|
||||
&self,
|
||||
language_server: &mut language_server::Inner,
|
||||
maybe_root_path: Option<&Path>,
|
||||
) -> Option<lsp::CallHierarchyItem> {
|
||||
let target_specifier = resolve_url(&self.file).unwrap();
|
||||
let target_line_index = language_server
|
||||
.get_line_index(target_specifier)
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
Some(self.to_call_hierarchy_item(
|
||||
&target_line_index,
|
||||
language_server,
|
||||
maybe_root_path,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn to_call_hierarchy_item(
|
||||
&self,
|
||||
line_index: &LineIndex,
|
||||
language_server: &mut language_server::Inner,
|
||||
maybe_root_path: Option<&Path>,
|
||||
) -> lsp::CallHierarchyItem {
|
||||
let target_specifier = resolve_url(&self.file).unwrap();
|
||||
let uri = language_server
|
||||
.url_map
|
||||
.normalize_specifier(&target_specifier)
|
||||
.unwrap();
|
||||
|
||||
let use_file_name = self.is_source_file_item();
|
||||
let maybe_file_path = if uri.scheme() == "file" {
|
||||
uri.to_file_path().ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let name = if use_file_name {
|
||||
if let Some(file_path) = maybe_file_path.as_ref() {
|
||||
file_path.file_name().unwrap().to_string_lossy().to_string()
|
||||
} else {
|
||||
uri.to_string()
|
||||
}
|
||||
} else {
|
||||
self.name.clone()
|
||||
};
|
||||
let detail = if use_file_name {
|
||||
if let Some(file_path) = maybe_file_path.as_ref() {
|
||||
// TODO: update this to work with multi root workspaces
|
||||
let parent_dir = file_path.parent().unwrap();
|
||||
if let Some(root_path) = maybe_root_path {
|
||||
parent_dir
|
||||
.strip_prefix(root_path)
|
||||
.unwrap_or(parent_dir)
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
} else {
|
||||
parent_dir.to_string_lossy().to_string()
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
} else {
|
||||
self.container_name.as_ref().cloned().unwrap_or_default()
|
||||
};
|
||||
|
||||
let mut tags: Option<Vec<lsp::SymbolTag>> = None;
|
||||
if let Some(modifiers) = self.kind_modifiers.as_ref() {
|
||||
let kind_modifiers = parse_kind_modifier(modifiers);
|
||||
if kind_modifiers.contains("deprecated") {
|
||||
tags = Some(vec![lsp::SymbolTag::Deprecated]);
|
||||
}
|
||||
}
|
||||
|
||||
lsp::CallHierarchyItem {
|
||||
name,
|
||||
tags,
|
||||
uri,
|
||||
detail: Some(detail),
|
||||
kind: self.kind.clone().into(),
|
||||
range: self.span.to_range(line_index),
|
||||
selection_range: self.selection_span.to_range(line_index),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_source_file_item(&self) -> bool {
|
||||
self.kind == ScriptElementKind::ScriptElement
|
||||
|| self.kind == ScriptElementKind::ModuleElement
|
||||
&& self.selection_span.start == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CallHierarchyIncomingCall {
|
||||
from: CallHierarchyItem,
|
||||
from_spans: Vec<TextSpan>,
|
||||
}
|
||||
|
||||
impl CallHierarchyIncomingCall {
|
||||
pub(crate) async fn try_resolve_call_hierarchy_incoming_call(
|
||||
&self,
|
||||
language_server: &mut language_server::Inner,
|
||||
maybe_root_path: Option<&Path>,
|
||||
) -> Option<lsp::CallHierarchyIncomingCall> {
|
||||
let target_specifier = resolve_url(&self.from.file).unwrap();
|
||||
let target_line_index = language_server
|
||||
.get_line_index(target_specifier)
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
Some(lsp::CallHierarchyIncomingCall {
|
||||
from: self.from.to_call_hierarchy_item(
|
||||
&target_line_index,
|
||||
language_server,
|
||||
maybe_root_path,
|
||||
),
|
||||
from_ranges: self
|
||||
.from_spans
|
||||
.iter()
|
||||
.map(|span| span.to_range(&target_line_index))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CallHierarchyOutgoingCall {
|
||||
to: CallHierarchyItem,
|
||||
from_spans: Vec<TextSpan>,
|
||||
}
|
||||
|
||||
impl CallHierarchyOutgoingCall {
|
||||
pub(crate) async fn try_resolve_call_hierarchy_outgoing_call(
|
||||
&self,
|
||||
line_index: &LineIndex,
|
||||
language_server: &mut language_server::Inner,
|
||||
maybe_root_path: Option<&Path>,
|
||||
) -> Option<lsp::CallHierarchyOutgoingCall> {
|
||||
let target_specifier = resolve_url(&self.to.file).unwrap();
|
||||
let target_line_index = language_server
|
||||
.get_line_index(target_specifier)
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
Some(lsp::CallHierarchyOutgoingCall {
|
||||
to: self.to.to_call_hierarchy_item(
|
||||
&target_line_index,
|
||||
language_server,
|
||||
maybe_root_path,
|
||||
),
|
||||
from_ranges: self
|
||||
.from_spans
|
||||
.iter()
|
||||
.map(|span| span.to_range(&line_index))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionEntryDetails {
|
||||
|
@ -1956,6 +2166,12 @@ pub enum RequestMethod {
|
|||
GetSmartSelectionRange((ModuleSpecifier, u32)),
|
||||
/// Get the diagnostic codes that support some form of code fix.
|
||||
GetSupportedCodeFixes,
|
||||
/// Resolve a call hierarchy item for a specific position.
|
||||
PrepareCallHierarchy((ModuleSpecifier, u32)),
|
||||
/// Resolve incoming call hierarchy items for a specific position.
|
||||
ProvideCallHierarchyIncomingCalls((ModuleSpecifier, u32)),
|
||||
/// Resolve outgoing call hierarchy items for a specific position.
|
||||
ProvideCallHierarchyOutgoingCalls((ModuleSpecifier, u32)),
|
||||
}
|
||||
|
||||
impl RequestMethod {
|
||||
|
@ -2092,6 +2308,36 @@ impl RequestMethod {
|
|||
"id": id,
|
||||
"method": "getSupportedCodeFixes",
|
||||
}),
|
||||
RequestMethod::PrepareCallHierarchy((specifier, position)) => {
|
||||
json!({
|
||||
"id": id,
|
||||
"method": "prepareCallHierarchy",
|
||||
"specifier": specifier,
|
||||
"position": position
|
||||
})
|
||||
}
|
||||
RequestMethod::ProvideCallHierarchyIncomingCalls((
|
||||
specifier,
|
||||
position,
|
||||
)) => {
|
||||
json!({
|
||||
"id": id,
|
||||
"method": "provideCallHierarchyIncomingCalls",
|
||||
"specifier": specifier,
|
||||
"position": position
|
||||
})
|
||||
}
|
||||
RequestMethod::ProvideCallHierarchyOutgoingCalls((
|
||||
specifier,
|
||||
position,
|
||||
)) => {
|
||||
json!({
|
||||
"id": id,
|
||||
"method": "provideCallHierarchyOutgoingCalls",
|
||||
"specifier": specifier,
|
||||
"position": position
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
33
cli/tests/lsp/incoming_calls_request.json
Normal file
33
cli/tests/lsp/incoming_calls_request.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"method": "callHierarchy/incomingCalls",
|
||||
"params": {
|
||||
"item": {
|
||||
"name": "baz",
|
||||
"kind": 6,
|
||||
"detail": "Bar",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 5,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 7,
|
||||
"character": 3
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 5,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 5,
|
||||
"character": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
cli/tests/lsp/outgoing_calls_request.json
Normal file
33
cli/tests/lsp/outgoing_calls_request.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 5,
|
||||
"method": "callHierarchy/outgoingCalls",
|
||||
"params": {
|
||||
"item": {
|
||||
"name": "baz",
|
||||
"kind": 6,
|
||||
"detail": "Bar",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 5,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 7,
|
||||
"character": 3
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 5,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 5,
|
||||
"character": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "textDocument/didOpen",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts",
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": "function foo() {\n return false;\n}\n\nclass Bar {\n baz() {\n return foo();\n }\n}\n\nfunction main() {\n const bar = new Bar();\n bar.baz();\n}\n\nmain();"
|
||||
}
|
||||
}
|
||||
}
|
14
cli/tests/lsp/prepare_call_hierarchy_request.json
Normal file
14
cli/tests/lsp/prepare_call_hierarchy_request.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "textDocument/prepareCallHierarchy",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts"
|
||||
},
|
||||
"position": {
|
||||
"line": 5,
|
||||
"character": 3
|
||||
}
|
||||
}
|
||||
}
|
|
@ -726,6 +726,33 @@ delete Object.prototype.__proto__;
|
|||
ts.getSupportedCodeFixes(),
|
||||
);
|
||||
}
|
||||
case "prepareCallHierarchy": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.prepareCallHierarchy(
|
||||
request.specifier,
|
||||
request.position,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "provideCallHierarchyIncomingCalls": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.provideCallHierarchyIncomingCalls(
|
||||
request.specifier,
|
||||
request.position,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "provideCallHierarchyOutgoingCalls": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.provideCallHierarchyOutgoingCalls(
|
||||
request.specifier,
|
||||
request.position,
|
||||
),
|
||||
);
|
||||
}
|
||||
default:
|
||||
throw new TypeError(
|
||||
// @ts-ignore exhausted case statement sets type to never
|
||||
|
|
25
cli/tsc/compiler.d.ts
vendored
25
cli/tsc/compiler.d.ts
vendored
|
@ -63,7 +63,10 @@ declare global {
|
|||
| GetReferencesRequest
|
||||
| GetSignatureHelpItemsRequest
|
||||
| GetSmartSelectionRange
|
||||
| GetSupportedCodeFixes;
|
||||
| GetSupportedCodeFixes
|
||||
| PrepareCallHierarchy
|
||||
| ProvideCallHierarchyIncomingCalls
|
||||
| ProvideCallHierarchyOutgoingCalls;
|
||||
|
||||
interface BaseLanguageServerRequest {
|
||||
id: number;
|
||||
|
@ -185,4 +188,24 @@ declare global {
|
|||
interface GetSupportedCodeFixes extends BaseLanguageServerRequest {
|
||||
method: "getSupportedCodeFixes";
|
||||
}
|
||||
|
||||
interface PrepareCallHierarchy extends BaseLanguageServerRequest {
|
||||
method: "prepareCallHierarchy";
|
||||
specifier: string;
|
||||
position: number;
|
||||
}
|
||||
|
||||
interface ProvideCallHierarchyIncomingCalls
|
||||
extends BaseLanguageServerRequest {
|
||||
method: "provideCallHierarchyIncomingCalls";
|
||||
specifier: string;
|
||||
position: number;
|
||||
}
|
||||
|
||||
interface ProvideCallHierarchyOutgoingCalls
|
||||
extends BaseLanguageServerRequest {
|
||||
method: "provideCallHierarchyOutgoingCalls";
|
||||
specifier: string;
|
||||
position: number;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue