mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
refactor(lsp): dedup import map lookup for auto-imports (#20538)
This commit is contained in:
parent
788bc8d021
commit
b9b4ad31d9
1 changed files with 22 additions and 117 deletions
139
cli/lsp/tsc.rs
139
cli/lsp/tsc.rs
|
@ -1,7 +1,6 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
use super::analysis::CodeActionData;
|
use super::analysis::CodeActionData;
|
||||||
use super::analysis::TsResponseImportMapper;
|
|
||||||
use super::code_lens;
|
use super::code_lens;
|
||||||
use super::config;
|
use super::config;
|
||||||
use super::documents::AssetOrDocument;
|
use super::documents::AssetOrDocument;
|
||||||
|
@ -2416,11 +2415,13 @@ fn parse_code_actions(
|
||||||
let change_specifier = normalize_specifier(&change.file_name)?;
|
let change_specifier = normalize_specifier(&change.file_name)?;
|
||||||
if data.specifier == change_specifier {
|
if data.specifier == change_specifier {
|
||||||
additional_text_edits.extend(change.text_changes.iter().map(|tc| {
|
additional_text_edits.extend(change.text_changes.iter().map(|tc| {
|
||||||
update_import_statement(
|
let mut text_edit = tc.as_text_edit(asset_or_doc.line_index());
|
||||||
tc.as_text_edit(asset_or_doc.line_index()),
|
if let Some(specifier_rewrite) = &data.specifier_rewrite {
|
||||||
data,
|
text_edit.new_text = text_edit
|
||||||
Some(&language_server.get_ts_response_import_mapper()),
|
.new_text
|
||||||
)
|
.replace(&specifier_rewrite.0, &specifier_rewrite.1);
|
||||||
|
}
|
||||||
|
text_edit
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
has_remaining_commands_or_edits = true;
|
has_remaining_commands_or_edits = true;
|
||||||
|
@ -2655,6 +2656,11 @@ pub struct CompletionItemData {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub source: Option<String>,
|
pub source: Option<String>,
|
||||||
|
/// If present, the code action / text edit corresponding to this item should
|
||||||
|
/// be rewritten by replacing the first string with the second. Intended for
|
||||||
|
/// auto-import specifiers to be reverse-import-mapped.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub specifier_rewrite: Option<(String, String)>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub data: Option<Value>,
|
pub data: Option<Value>,
|
||||||
pub use_code_snippet: bool,
|
pub use_code_snippet: bool,
|
||||||
|
@ -2667,37 +2673,6 @@ struct CompletionEntryDataImport {
|
||||||
file_name: String,
|
file_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modify an import statement text replacement to have the correct import
|
|
||||||
/// specifier to work with Deno module resolution.
|
|
||||||
fn update_import_statement(
|
|
||||||
mut text_edit: lsp::TextEdit,
|
|
||||||
item_data: &CompletionItemData,
|
|
||||||
maybe_import_mapper: Option<&TsResponseImportMapper>,
|
|
||||||
) -> lsp::TextEdit {
|
|
||||||
if let Some(data) = &item_data.data {
|
|
||||||
if let Ok(import_data) =
|
|
||||||
serde_json::from_value::<CompletionEntryDataImport>(data.clone())
|
|
||||||
{
|
|
||||||
if let Ok(import_specifier) = normalize_specifier(&import_data.file_name)
|
|
||||||
{
|
|
||||||
if let Some(new_module_specifier) = maybe_import_mapper
|
|
||||||
.and_then(|m| {
|
|
||||||
m.check_specifier(&import_specifier, &item_data.specifier)
|
|
||||||
})
|
|
||||||
.or_else(|| {
|
|
||||||
relative_specifier(&item_data.specifier, &import_specifier)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
text_edit.new_text = text_edit
|
|
||||||
.new_text
|
|
||||||
.replace(&import_data.module_specifier, &new_module_specifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text_edit
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CompletionEntry {
|
pub struct CompletionEntry {
|
||||||
|
@ -2825,6 +2800,7 @@ impl CompletionEntry {
|
||||||
let mut label_details: Option<lsp::CompletionItemLabelDetails> = None;
|
let mut label_details: Option<lsp::CompletionItemLabelDetails> = None;
|
||||||
let mut kind: Option<lsp::CompletionItemKind> =
|
let mut kind: Option<lsp::CompletionItemKind> =
|
||||||
Some(self.kind.clone().into());
|
Some(self.kind.clone().into());
|
||||||
|
let mut specifier_rewrite = None;
|
||||||
|
|
||||||
let mut sort_text = if self.source.is_some() {
|
let mut sort_text = if self.source.is_some() {
|
||||||
format!("\u{ffff}{}", self.sort_text)
|
format!("\u{ffff}{}", self.sort_text)
|
||||||
|
@ -2879,7 +2855,7 @@ impl CompletionEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(source) = &self.source {
|
if let Some(source) = &self.source {
|
||||||
let mut source = source.clone();
|
let mut display_source = source.clone();
|
||||||
if let Some(data) = &self.data {
|
if let Some(data) = &self.data {
|
||||||
if let Ok(import_data) =
|
if let Ok(import_data) =
|
||||||
serde_json::from_value::<CompletionEntryDataImport>(data.clone())
|
serde_json::from_value::<CompletionEntryDataImport>(data.clone())
|
||||||
|
@ -2892,21 +2868,25 @@ impl CompletionEntry {
|
||||||
.check_specifier(&import_specifier, specifier)
|
.check_specifier(&import_specifier, specifier)
|
||||||
.or_else(|| relative_specifier(specifier, &import_specifier))
|
.or_else(|| relative_specifier(specifier, &import_specifier))
|
||||||
{
|
{
|
||||||
source = new_module_specifier;
|
display_source = new_module_specifier.clone();
|
||||||
|
if new_module_specifier != import_data.module_specifier {
|
||||||
|
specifier_rewrite =
|
||||||
|
Some((import_data.module_specifier, new_module_specifier));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We want relative or bare (import-mapped or otherwise) specifiers to
|
// We want relative or bare (import-mapped or otherwise) specifiers to
|
||||||
// appear at the top.
|
// appear at the top.
|
||||||
if resolve_url(&source).is_err() {
|
if resolve_url(&display_source).is_err() {
|
||||||
sort_text += "_0";
|
sort_text += "_0";
|
||||||
} else {
|
} else {
|
||||||
sort_text += "_1";
|
sort_text += "_1";
|
||||||
}
|
}
|
||||||
label_details
|
label_details
|
||||||
.get_or_insert_with(Default::default)
|
.get_or_insert_with(Default::default)
|
||||||
.description = Some(source);
|
.description = Some(display_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
let text_edit =
|
let text_edit =
|
||||||
|
@ -2927,6 +2907,7 @@ impl CompletionEntry {
|
||||||
position,
|
position,
|
||||||
name: self.name.clone(),
|
name: self.name.clone(),
|
||||||
source: self.source.clone(),
|
source: self.source.clone(),
|
||||||
|
specifier_rewrite,
|
||||||
data: self.data.clone(),
|
data: self.data.clone(),
|
||||||
use_code_snippet,
|
use_code_snippet,
|
||||||
};
|
};
|
||||||
|
@ -5142,82 +5123,6 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_update_import_statement() {
|
|
||||||
let fixtures = vec![
|
|
||||||
(
|
|
||||||
"file:///a/a.ts",
|
|
||||||
"./b",
|
|
||||||
"file:///a/b.ts",
|
|
||||||
"import { b } from \"./b\";\n\n",
|
|
||||||
"import { b } from \"./b.ts\";\n\n",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"file:///a/a.ts",
|
|
||||||
"../b/b",
|
|
||||||
"file:///b/b.ts",
|
|
||||||
"import { b } from \"../b/b\";\n\n",
|
|
||||||
"import { b } from \"../b/b.ts\";\n\n",
|
|
||||||
),
|
|
||||||
("file:///a/a.ts", "./b", "file:///a/b.ts", ", b", ", b"),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (
|
|
||||||
specifier_text,
|
|
||||||
module_specifier,
|
|
||||||
file_name,
|
|
||||||
orig_text,
|
|
||||||
expected_text,
|
|
||||||
) in fixtures
|
|
||||||
{
|
|
||||||
let specifier = ModuleSpecifier::parse(specifier_text).unwrap();
|
|
||||||
let item_data = CompletionItemData {
|
|
||||||
specifier: specifier.clone(),
|
|
||||||
position: 0,
|
|
||||||
name: "b".to_string(),
|
|
||||||
source: None,
|
|
||||||
data: Some(json!({
|
|
||||||
"moduleSpecifier": module_specifier,
|
|
||||||
"fileName": file_name,
|
|
||||||
})),
|
|
||||||
use_code_snippet: false,
|
|
||||||
};
|
|
||||||
let actual = update_import_statement(
|
|
||||||
lsp::TextEdit {
|
|
||||||
range: lsp::Range {
|
|
||||||
start: lsp::Position {
|
|
||||||
line: 0,
|
|
||||||
character: 0,
|
|
||||||
},
|
|
||||||
end: lsp::Position {
|
|
||||||
line: 0,
|
|
||||||
character: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
new_text: orig_text.to_string(),
|
|
||||||
},
|
|
||||||
&item_data,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
actual,
|
|
||||||
lsp::TextEdit {
|
|
||||||
range: lsp::Range {
|
|
||||||
start: lsp::Position {
|
|
||||||
line: 0,
|
|
||||||
character: 0,
|
|
||||||
},
|
|
||||||
end: lsp::Position {
|
|
||||||
line: 0,
|
|
||||||
character: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
new_text: expected_text.to_string(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn include_suppress_inlay_hit_settings() {
|
fn include_suppress_inlay_hit_settings() {
|
||||||
let mut settings = WorkspaceSettings::default();
|
let mut settings = WorkspaceSettings::default();
|
||||||
|
|
Loading…
Reference in a new issue