mirror of
https://github.com/denoland/deno.git
synced 2025-01-03 04:48:52 -05:00
fix(lsp): rewrite imports for 'Move to a new file' action (#27427)
This commit is contained in:
parent
3c147d6be1
commit
feb94d09e7
4 changed files with 147 additions and 33 deletions
|
@ -603,18 +603,24 @@ fn try_reverse_map_package_json_exports(
|
||||||
/// For a set of tsc changes, can them for any that contain something that looks
|
/// For a set of tsc changes, can them for any that contain something that looks
|
||||||
/// like an import and rewrite the import specifier to include the extension
|
/// like an import and rewrite the import specifier to include the extension
|
||||||
pub fn fix_ts_import_changes(
|
pub fn fix_ts_import_changes(
|
||||||
referrer: &ModuleSpecifier,
|
|
||||||
resolution_mode: ResolutionMode,
|
|
||||||
changes: &[tsc::FileTextChanges],
|
changes: &[tsc::FileTextChanges],
|
||||||
language_server: &language_server::Inner,
|
language_server: &language_server::Inner,
|
||||||
) -> Result<Vec<tsc::FileTextChanges>, AnyError> {
|
) -> Result<Vec<tsc::FileTextChanges>, AnyError> {
|
||||||
let import_mapper = language_server.get_ts_response_import_mapper(referrer);
|
|
||||||
let mut r = Vec::new();
|
let mut r = Vec::new();
|
||||||
for change in changes {
|
for change in changes {
|
||||||
|
let Ok(referrer) = ModuleSpecifier::parse(&change.file_name) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let referrer_doc = language_server.get_asset_or_document(&referrer).ok();
|
||||||
|
let resolution_mode = referrer_doc
|
||||||
|
.as_ref()
|
||||||
|
.map(|d| d.resolution_mode())
|
||||||
|
.unwrap_or(ResolutionMode::Import);
|
||||||
|
let import_mapper =
|
||||||
|
language_server.get_ts_response_import_mapper(&referrer);
|
||||||
let mut text_changes = Vec::new();
|
let mut text_changes = Vec::new();
|
||||||
for text_change in &change.text_changes {
|
for text_change in &change.text_changes {
|
||||||
let lines = text_change.new_text.split('\n');
|
let lines = text_change.new_text.split('\n');
|
||||||
|
|
||||||
let new_lines: Vec<String> = lines
|
let new_lines: Vec<String> = lines
|
||||||
.map(|line| {
|
.map(|line| {
|
||||||
// This assumes that there's only one import per line.
|
// This assumes that there's only one import per line.
|
||||||
|
@ -622,7 +628,7 @@ pub fn fix_ts_import_changes(
|
||||||
let specifier =
|
let specifier =
|
||||||
captures.iter().skip(1).find_map(|s| s).unwrap().as_str();
|
captures.iter().skip(1).find_map(|s| s).unwrap().as_str();
|
||||||
if let Some(new_specifier) = import_mapper
|
if let Some(new_specifier) = import_mapper
|
||||||
.check_unresolved_specifier(specifier, referrer, resolution_mode)
|
.check_unresolved_specifier(specifier, &referrer, resolution_mode)
|
||||||
{
|
{
|
||||||
line.replace(specifier, &new_specifier)
|
line.replace(specifier, &new_specifier)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -251,6 +251,13 @@ impl AssetOrDocument {
|
||||||
pub fn document_lsp_version(&self) -> Option<i32> {
|
pub fn document_lsp_version(&self) -> Option<i32> {
|
||||||
self.document().and_then(|d| d.maybe_lsp_version())
|
self.document().and_then(|d| d.maybe_lsp_version())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resolution_mode(&self) -> ResolutionMode {
|
||||||
|
match self {
|
||||||
|
AssetOrDocument::Asset(_) => ResolutionMode::Import,
|
||||||
|
AssetOrDocument::Document(d) => d.resolution_mode(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModuleResult = Result<deno_graph::JsModule, deno_graph::ModuleGraphError>;
|
type ModuleResult = Result<deno_graph::JsModule, deno_graph::ModuleGraphError>;
|
||||||
|
|
|
@ -1855,20 +1855,12 @@ impl Inner {
|
||||||
}
|
}
|
||||||
|
|
||||||
let changes = if code_action_data.fix_id == "fixMissingImport" {
|
let changes = if code_action_data.fix_id == "fixMissingImport" {
|
||||||
fix_ts_import_changes(
|
fix_ts_import_changes(&combined_code_actions.changes, self).map_err(
|
||||||
&code_action_data.specifier,
|
|err| {
|
||||||
maybe_asset_or_doc
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|d| d.document())
|
|
||||||
.map(|d| d.resolution_mode())
|
|
||||||
.unwrap_or(ResolutionMode::Import),
|
|
||||||
&combined_code_actions.changes,
|
|
||||||
self,
|
|
||||||
)
|
|
||||||
.map_err(|err| {
|
|
||||||
error!("Unable to remap changes: {:#}", err);
|
error!("Unable to remap changes: {:#}", err);
|
||||||
LspError::internal_error()
|
LspError::internal_error()
|
||||||
})?
|
},
|
||||||
|
)?
|
||||||
} else {
|
} else {
|
||||||
combined_code_actions.changes
|
combined_code_actions.changes
|
||||||
};
|
};
|
||||||
|
@ -1912,20 +1904,16 @@ impl Inner {
|
||||||
asset_or_doc.scope().cloned(),
|
asset_or_doc.scope().cloned(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
if kind_suffix == ".rewrite.function.returnType" {
|
if kind_suffix == ".rewrite.function.returnType"
|
||||||
refactor_edit_info.edits = fix_ts_import_changes(
|
|| kind_suffix == ".move.newFile"
|
||||||
&action_data.specifier,
|
{
|
||||||
asset_or_doc
|
refactor_edit_info.edits =
|
||||||
.document()
|
fix_ts_import_changes(&refactor_edit_info.edits, self).map_err(
|
||||||
.map(|d| d.resolution_mode())
|
|err| {
|
||||||
.unwrap_or(ResolutionMode::Import),
|
|
||||||
&refactor_edit_info.edits,
|
|
||||||
self,
|
|
||||||
)
|
|
||||||
.map_err(|err| {
|
|
||||||
error!("Unable to remap changes: {:#}", err);
|
error!("Unable to remap changes: {:#}", err);
|
||||||
LspError::internal_error()
|
LspError::internal_error()
|
||||||
})?
|
},
|
||||||
|
)?
|
||||||
}
|
}
|
||||||
code_action.edit = refactor_edit_info.to_workspace_edit(self)?;
|
code_action.edit = refactor_edit_info.to_workspace_edit(self)?;
|
||||||
code_action
|
code_action
|
||||||
|
|
|
@ -6066,6 +6066,119 @@ fn lsp_jsr_code_action_missing_declaration() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lsp_jsr_code_action_move_to_new_file() {
|
||||||
|
let context = TestContextBuilder::new()
|
||||||
|
.use_http_server()
|
||||||
|
.use_temp_cwd()
|
||||||
|
.build();
|
||||||
|
let temp_dir = context.temp_dir();
|
||||||
|
let file = source_file(
|
||||||
|
temp_dir.path().join("file.ts"),
|
||||||
|
r#"
|
||||||
|
import { someFunction } from "jsr:@denotest/types-file";
|
||||||
|
export const someValue = someFunction();
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let mut client = context.new_lsp_command().build();
|
||||||
|
client.initialize_default();
|
||||||
|
client.write_request(
|
||||||
|
"workspace/executeCommand",
|
||||||
|
json!({
|
||||||
|
"command": "deno.cache",
|
||||||
|
"arguments": [[], file.url()],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
client.did_open_file(&file);
|
||||||
|
let list = client
|
||||||
|
.write_request_with_res_as::<Option<lsp::CodeActionResponse>>(
|
||||||
|
"textDocument/codeAction",
|
||||||
|
json!({
|
||||||
|
"textDocument": { "uri": file.url() },
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 2, "character": 19 },
|
||||||
|
"end": { "line": 2, "character": 28 },
|
||||||
|
},
|
||||||
|
"context": { "diagnostics": [] },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let action = list
|
||||||
|
.iter()
|
||||||
|
.find_map(|c| match c {
|
||||||
|
lsp::CodeActionOrCommand::CodeAction(a)
|
||||||
|
if &a.title == "Move to a new file" =>
|
||||||
|
{
|
||||||
|
Some(a)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
let res = client.write_request("codeAction/resolve", json!(action));
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
json!({
|
||||||
|
"title": "Move to a new file",
|
||||||
|
"kind": "refactor.move.newFile",
|
||||||
|
"edit": {
|
||||||
|
"documentChanges": [
|
||||||
|
{
|
||||||
|
"textDocument": { "uri": file.url(), "version": 1 },
|
||||||
|
"edits": [
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 1, "character": 6 },
|
||||||
|
"end": { "line": 2, "character": 0 },
|
||||||
|
},
|
||||||
|
"newText": "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 2, "character": 0 },
|
||||||
|
"end": { "line": 3, "character": 4 },
|
||||||
|
},
|
||||||
|
"newText": "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "create",
|
||||||
|
"uri": file.url().join("someValue.ts").unwrap(),
|
||||||
|
"options": {
|
||||||
|
"ignoreIfExists": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"textDocument": {
|
||||||
|
"uri": file.url().join("someValue.ts").unwrap(),
|
||||||
|
"version": null,
|
||||||
|
},
|
||||||
|
"edits": [
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 0 },
|
||||||
|
"end": { "line": 0, "character": 0 },
|
||||||
|
},
|
||||||
|
"newText": "import { someFunction } from \"jsr:@denotest/types-file\";\n\nexport const someValue = someFunction();\n",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"isPreferred": false,
|
||||||
|
"data": {
|
||||||
|
"specifier": file.url(),
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 2, "character": 19 },
|
||||||
|
"end": { "line": 2, "character": 28 },
|
||||||
|
},
|
||||||
|
"refactorName": "Move to a new file",
|
||||||
|
"actionName": "Move to a new file",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lsp_code_actions_deno_cache_npm() {
|
fn lsp_code_actions_deno_cache_npm() {
|
||||||
let context = TestContextBuilder::new().use_temp_cwd().build();
|
let context = TestContextBuilder::new().use_temp_cwd().build();
|
||||||
|
|
Loading…
Reference in a new issue