mirror of
https://github.com/denoland/deno.git
synced 2024-12-18 13:22:55 -05:00
fix(lsp): include "node:" prefix for node builtin auto-imports (#27404)
This commit is contained in:
parent
d632ec9e70
commit
2820ba1e22
3 changed files with 110 additions and 12 deletions
|
@ -270,6 +270,10 @@ impl<'a> TsResponseImportMapper<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if specifier.scheme() == "node" {
|
||||||
|
return Some(specifier.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(jsr_path) = specifier.as_str().strip_prefix(jsr_url().as_str())
|
if let Some(jsr_path) = specifier.as_str().strip_prefix(jsr_url().as_str())
|
||||||
{
|
{
|
||||||
let mut segments = jsr_path.split('/');
|
let mut segments = jsr_path.split('/');
|
||||||
|
|
|
@ -64,6 +64,7 @@ use deno_core::OpState;
|
||||||
use deno_core::PollEventLoopOptions;
|
use deno_core::PollEventLoopOptions;
|
||||||
use deno_core::RuntimeOptions;
|
use deno_core::RuntimeOptions;
|
||||||
use deno_path_util::url_to_file_path;
|
use deno_path_util::url_to_file_path;
|
||||||
|
use deno_runtime::deno_node::SUPPORTED_BUILTIN_NODE_MODULES;
|
||||||
use deno_runtime::inspector_server::InspectorServer;
|
use deno_runtime::inspector_server::InspectorServer;
|
||||||
use deno_runtime::tokio_util::create_basic_runtime;
|
use deno_runtime::tokio_util::create_basic_runtime;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
@ -3411,10 +3412,18 @@ fn parse_code_actions(
|
||||||
additional_text_edits.extend(change.text_changes.iter().map(|tc| {
|
additional_text_edits.extend(change.text_changes.iter().map(|tc| {
|
||||||
let mut text_edit = tc.as_text_edit(asset_or_doc.line_index());
|
let mut text_edit = tc.as_text_edit(asset_or_doc.line_index());
|
||||||
if let Some(specifier_rewrite) = &data.specifier_rewrite {
|
if let Some(specifier_rewrite) = &data.specifier_rewrite {
|
||||||
text_edit.new_text = text_edit.new_text.replace(
|
let specifier_index = text_edit
|
||||||
&specifier_rewrite.old_specifier,
|
.new_text
|
||||||
&specifier_rewrite.new_specifier,
|
.char_indices()
|
||||||
);
|
.find_map(|(b, c)| (c == '\'' || c == '"').then_some(b));
|
||||||
|
if let Some(i) = specifier_index {
|
||||||
|
let mut specifier_part = text_edit.new_text.split_off(i);
|
||||||
|
specifier_part = specifier_part.replace(
|
||||||
|
&specifier_rewrite.old_specifier,
|
||||||
|
&specifier_rewrite.new_specifier,
|
||||||
|
);
|
||||||
|
text_edit.new_text.push_str(&specifier_part);
|
||||||
|
}
|
||||||
if let Some(deno_types_specifier) =
|
if let Some(deno_types_specifier) =
|
||||||
&specifier_rewrite.new_deno_types_specifier
|
&specifier_rewrite.new_deno_types_specifier
|
||||||
{
|
{
|
||||||
|
@ -3587,10 +3596,17 @@ impl CompletionEntryDetails {
|
||||||
&mut insert_replace_edit.new_text
|
&mut insert_replace_edit.new_text
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
*new_text = new_text.replace(
|
let specifier_index = new_text
|
||||||
&specifier_rewrite.old_specifier,
|
.char_indices()
|
||||||
&specifier_rewrite.new_specifier,
|
.find_map(|(b, c)| (c == '\'' || c == '"').then_some(b));
|
||||||
);
|
if let Some(i) = specifier_index {
|
||||||
|
let mut specifier_part = new_text.split_off(i);
|
||||||
|
specifier_part = specifier_part.replace(
|
||||||
|
&specifier_rewrite.old_specifier,
|
||||||
|
&specifier_rewrite.new_specifier,
|
||||||
|
);
|
||||||
|
new_text.push_str(&specifier_part);
|
||||||
|
}
|
||||||
if let Some(deno_types_specifier) =
|
if let Some(deno_types_specifier) =
|
||||||
&specifier_rewrite.new_deno_types_specifier
|
&specifier_rewrite.new_deno_types_specifier
|
||||||
{
|
{
|
||||||
|
@ -3729,7 +3745,7 @@ pub struct CompletionItemData {
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct CompletionEntryDataAutoImport {
|
struct CompletionEntryDataAutoImport {
|
||||||
module_specifier: String,
|
module_specifier: String,
|
||||||
file_name: String,
|
file_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -3786,9 +3802,20 @@ impl CompletionEntry {
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let Ok(normalized) = specifier_map.normalize(&raw.file_name) {
|
if let Some(file_name) = &raw.file_name {
|
||||||
self.auto_import_data =
|
if let Ok(normalized) = specifier_map.normalize(file_name) {
|
||||||
Some(CompletionNormalizedAutoImportData { raw, normalized });
|
self.auto_import_data =
|
||||||
|
Some(CompletionNormalizedAutoImportData { raw, normalized });
|
||||||
|
}
|
||||||
|
} else if SUPPORTED_BUILTIN_NODE_MODULES
|
||||||
|
.contains(&raw.module_specifier.as_str())
|
||||||
|
{
|
||||||
|
if let Ok(normalized) =
|
||||||
|
resolve_url(&format!("node:{}", &raw.module_specifier))
|
||||||
|
{
|
||||||
|
self.auto_import_data =
|
||||||
|
Some(CompletionNormalizedAutoImportData { raw, normalized });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7960,6 +7960,73 @@ fn lsp_completions_auto_import() {
|
||||||
client.shutdown();
|
client.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lsp_completions_auto_import_node_builtin() {
|
||||||
|
let context = TestContextBuilder::new()
|
||||||
|
.use_http_server()
|
||||||
|
.use_temp_cwd()
|
||||||
|
.build();
|
||||||
|
let temp_dir = context.temp_dir();
|
||||||
|
let mut client = context.new_lsp_command().build();
|
||||||
|
client.initialize_default();
|
||||||
|
client.did_open(json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": temp_dir.url().join("file.ts").unwrap(),
|
||||||
|
"languageId": "typescript",
|
||||||
|
"version": 1,
|
||||||
|
"text": r#"
|
||||||
|
import "npm:@types/node";
|
||||||
|
pathToFileURL
|
||||||
|
"#,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
client.write_request(
|
||||||
|
"workspace/executeCommand",
|
||||||
|
json!({
|
||||||
|
"command": "deno.cache",
|
||||||
|
"arguments": [[], temp_dir.url().join("file.ts").unwrap()],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let list = client.get_completion_list(
|
||||||
|
temp_dir.url().join("file.ts").unwrap(),
|
||||||
|
(2, 21),
|
||||||
|
json!({ "triggerKind": 2 }),
|
||||||
|
);
|
||||||
|
assert!(!list.is_incomplete);
|
||||||
|
let item = list
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.find(|item| item.label == "pathToFileURL")
|
||||||
|
.unwrap();
|
||||||
|
let res = client.write_request("completionItem/resolve", json!(item));
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
json!({
|
||||||
|
"label": "pathToFileURL",
|
||||||
|
"labelDetails": {
|
||||||
|
"description": "node:url",
|
||||||
|
},
|
||||||
|
"kind": 3,
|
||||||
|
"detail": "function pathToFileURL(path: string, options?: PathToFileUrlOptions): URL",
|
||||||
|
"documentation": {
|
||||||
|
"kind": "markdown",
|
||||||
|
"value": "This function ensures that `path` is resolved absolutely, and that the URL\ncontrol characters are correctly encoded when converting into a File URL.\n\n```js\nimport { pathToFileURL } from 'node:url';\n\nnew URL('/foo#1', 'file:'); // Incorrect: file:///foo#1\npathToFileURL('/foo#1'); // Correct: file:///foo#1 (POSIX)\n\nnew URL('/some/path%.c', 'file:'); // Incorrect: file:///some/path%.c\npathToFileURL('/some/path%.c'); // Correct: file:///some/path%.c (POSIX)\n```\n\n*@since* - v10.12.0 \n\n*@param* - path The path to convert to a File URL. \n\n*@return* - The file URL object.",
|
||||||
|
},
|
||||||
|
"sortText": "16_1",
|
||||||
|
"additionalTextEdits": [
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 2, "character": 0 },
|
||||||
|
"end": { "line": 2, "character": 0 },
|
||||||
|
},
|
||||||
|
"newText": " import { pathToFileURL } from \"node:url\";\n",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
client.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lsp_npm_completions_auto_import_and_quick_fix_no_import_map() {
|
fn lsp_npm_completions_auto_import_and_quick_fix_no_import_map() {
|
||||||
let context = TestContextBuilder::new()
|
let context = TestContextBuilder::new()
|
||||||
|
|
Loading…
Reference in a new issue