Skip to content

Commit 71a74cb

Browse files
authored
fix(lsp): import rewrites in moved file (#30628)
1 parent baf1850 commit 71a74cb

File tree

2 files changed

+81
-11
lines changed

2 files changed

+81
-11
lines changed

cli/lsp/analysis.rs

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@ impl<'a> TsResponseImportMapper<'a> {
547547
specifier: &str,
548548
referrer: &ModuleSpecifier,
549549
resolution_mode: ResolutionMode,
550+
new_file_hints: &[Url],
550551
) -> Option<String> {
551552
let specifier_stem = specifier.strip_suffix(".js").unwrap_or(specifier);
552553
let specifiers = std::iter::once(Cow::Borrowed(specifier)).chain(
@@ -569,9 +570,10 @@ impl<'a> TsResponseImportMapper<'a> {
569570
.ok()
570571
.and_then(|s| self.tsc_specifier_map.normalize(s.as_str()).ok())
571572
.filter(|s| {
572-
self
573-
.document_modules
574-
.specifier_exists(s, self.scope.as_deref())
573+
new_file_hints.contains(s)
574+
|| self
575+
.document_modules
576+
.specifier_exists(s, self.scope.as_deref())
575577
})
576578
&& let Some(specifier) = self
577579
.check_specifier(&specifier, referrer)
@@ -692,6 +694,11 @@ pub fn fix_ts_import_changes(
692694
token: &CancellationToken,
693695
) -> Result<Vec<tsc::FileTextChanges>, AnyError> {
694696
let mut r = Vec::new();
697+
let new_file_hints = changes
698+
.iter()
699+
.filter(|c| c.is_new_file.unwrap_or(false))
700+
.filter_map(|c| resolve_url(&c.file_name).ok())
701+
.collect::<Vec<_>>();
695702
for change in changes {
696703
if token.is_cancelled() {
697704
return Err(anyhow!("request cancelled"));
@@ -733,6 +740,7 @@ pub fn fix_ts_import_changes(
733740
specifier,
734741
&target_specifier,
735742
resolution_mode,
743+
&new_file_hints,
736744
)
737745
{
738746
line.replace(specifier, &new_specifier)
@@ -762,7 +770,7 @@ pub fn fix_ts_import_changes(
762770
pub fn fix_ts_import_changes_for_file_rename(
763771
changes: Vec<tsc::FileTextChanges>,
764772
new_uri: &str,
765-
module: &DocumentModule,
773+
old_module: &DocumentModule,
766774
language_server: &language_server::Inner,
767775
token: &CancellationToken,
768776
) -> Result<Vec<tsc::FileTextChanges>, AnyError> {
@@ -772,7 +780,7 @@ pub fn fix_ts_import_changes_for_file_rename(
772780
if !new_uri.scheme().is_some_and(|s| s.eq_lowercase("file")) {
773781
return Ok(Vec::new());
774782
}
775-
let new_url = uri_to_url(&new_uri);
783+
let new_file_hints = [uri_to_url(&new_uri)];
776784
let mut r = Vec::with_capacity(changes.len());
777785
for mut change in changes {
778786
if token.is_cancelled() {
@@ -781,13 +789,24 @@ pub fn fix_ts_import_changes_for_file_rename(
781789
let Ok(target_specifier) = resolve_url(&change.file_name) else {
782790
continue;
783791
};
784-
let import_mapper = language_server.get_ts_response_import_mapper(module);
792+
let Some(target_module) =
793+
language_server.document_modules.module_for_specifier(
794+
&target_specifier,
795+
old_module.scope.as_deref(),
796+
Some(&old_module.compiler_options_key),
797+
)
798+
else {
799+
continue;
800+
};
801+
let import_mapper =
802+
language_server.get_ts_response_import_mapper(&target_module);
785803
for text_change in &mut change.text_changes {
786-
if let Some(new_specifier) = import_mapper
787-
.check_specifier(&new_url, &target_specifier)
788-
.or_else(|| relative_specifier(&target_specifier, &new_url))
789-
.filter(|s| !s.contains("/node_modules/"))
790-
{
804+
if let Some(new_specifier) = import_mapper.check_unresolved_specifier(
805+
&text_change.new_text,
806+
&target_module.specifier,
807+
target_module.resolution_mode,
808+
&new_file_hints,
809+
) {
791810
text_change.new_text = new_specifier;
792811
}
793812
}
@@ -822,6 +841,12 @@ fn fix_ts_import_action<'a>(
822841
specifier,
823842
&module.specifier,
824843
module.resolution_mode,
844+
&action
845+
.changes
846+
.iter()
847+
.filter(|c| c.is_new_file.unwrap_or(false))
848+
.filter_map(|c| resolve_url(&c.file_name).ok())
849+
.collect::<Vec<_>>(),
825850
) {
826851
let description = action.description.replace(specifier, &new_specifier);
827852
let changes = action

tests/integration/lsp_tests.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19063,6 +19063,51 @@ fn lsp_will_rename_files_js_to_ts() {
1906319063
client.shutdown();
1906419064
}
1906519065

19066+
/// Regression test for https://github.com/denoland/deno/issues/30627.
19067+
#[test]
19068+
#[timeout(300_000)]
19069+
fn lsp_will_rename_files_move_to_different_dir() {
19070+
let context = TestContextBuilder::new().use_temp_cwd().build();
19071+
let temp_dir = context.temp_dir();
19072+
temp_dir.write("deno.json", json!({}).to_string());
19073+
let file = temp_dir.source_file("main.ts", "import \"./other.ts\";\n");
19074+
temp_dir.write("other.ts", "");
19075+
let mut client = context.new_lsp_command().build();
19076+
client.initialize_default();
19077+
client.did_open_file(&file);
19078+
let res = client.write_request(
19079+
"workspace/willRenameFiles",
19080+
json!({
19081+
"files": [
19082+
{
19083+
"oldUri": file.uri(),
19084+
"newUri": temp_dir.path().join("subdir/main.ts").uri_file(),
19085+
},
19086+
],
19087+
}),
19088+
);
19089+
assert_eq!(
19090+
res,
19091+
json!({
19092+
"documentChanges": [
19093+
{
19094+
"textDocument": { "uri": file.uri(), "version": 1 },
19095+
"edits": [
19096+
{
19097+
"range": {
19098+
"start": { "line": 0, "character": 8 },
19099+
"end": { "line": 0, "character": 18 },
19100+
},
19101+
"newText": "../other.ts",
19102+
},
19103+
],
19104+
},
19105+
],
19106+
}),
19107+
);
19108+
client.shutdown();
19109+
}
19110+
1906619111
#[test]
1906719112
#[timeout(300_000)]
1906819113
fn lsp_push_diagnostics() {

0 commit comments

Comments
 (0)