Skip to content

Commit e642d9e

Browse files
authored
(fix) Component auto Import when the import specifier config is js (#1242)
Related to #1187 TypeScript throws a debug assertion error if the importModuleSpecifierEnding config is 'js' and there's an unknown file extension (like '.svelte'). Although it's probably better to fix this on the TypeScript side, that it would probably take a while. So this is a workaround for now. Not all cases are fixed, but at least it's less likely to happen.
1 parent 1f7efec commit e642d9e

File tree

5 files changed

+110
-2
lines changed

5 files changed

+110
-2
lines changed

packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,9 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
328328

329329
const name = node.tagName.getText();
330330
const suffixedName = name + '__SvelteComponent_';
331+
const errorPreventingUserPreferences =
332+
this.completionProvider.fixUserPreferencesForSvelteComponentImport(userPreferences);
333+
331334
const toFix = (c: ts.CompletionEntry) =>
332335
lang
333336
.getCompletionEntryDetails(
@@ -336,7 +339,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
336339
c.name,
337340
{},
338341
c.source,
339-
userPreferences,
342+
errorPreventingUserPreferences,
340343
c.data
341344
)
342345
?.codeActions?.map((a) => ({

packages/language-server/src/plugins/typescript/features/CompletionProvider.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,24 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
392392
return completionItem;
393393
}
394394

395+
/**
396+
* TypeScript throws a debug assertion error if the importModuleSpecifierEnding config is
397+
* 'js' and there's an unknown file extension - which is the case for `.svelte`. Therefore
398+
* rewrite the importModuleSpecifierEnding for this case to silence the error.
399+
*/
400+
fixUserPreferencesForSvelteComponentImport(
401+
userPreferences: ts.UserPreferences
402+
): ts.UserPreferences {
403+
if (userPreferences.importModuleSpecifierEnding === 'js') {
404+
return {
405+
...userPreferences,
406+
importModuleSpecifierEnding: 'index'
407+
};
408+
}
409+
410+
return userPreferences;
411+
}
412+
395413
async resolveCompletion(
396414
document: Document,
397415
completionItem: AppCompletionItem<CompletionEntryWithIdentifer>,
@@ -409,14 +427,17 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
409427
}
410428

411429
const fragment = await tsDoc.getFragment();
430+
const errorPreventingUserPreferences = comp.source?.endsWith('.svelte')
431+
? this.fixUserPreferencesForSvelteComponentImport(userPreferences)
432+
: userPreferences;
412433

413434
const detail = lang.getCompletionEntryDetails(
414435
filePath,
415436
fragment.offsetAt(fragment.getGeneratedPosition(comp.position)),
416437
comp.name,
417438
{},
418439
comp.source,
419-
userPreferences,
440+
errorPreventingUserPreferences,
420441
comp.data
421442
);
422443

packages/language-server/test/plugins/typescript/features/preferences.test.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,80 @@ describe('ts user preferences', () => {
132132
const item = completions?.items.find((item) => item.label === 'definition');
133133
assert.strictEqual(item, undefined, 'Expected no auto import suggestions');
134134
});
135+
136+
const expectedComponentImportEdit = "import Imports from '~/imports.svelte';";
137+
138+
function setupImportModuleSpecifierEndingJs() {
139+
const { docManager, document } = setup('module-specifier-js.svelte');
140+
const lsAndTsDocResolver = createLSAndTSDocResolver(docManager, {
141+
preferences: {
142+
importModuleSpecifier: 'non-relative',
143+
importModuleSpecifierEnding: 'js',
144+
quoteStyle: 'single'
145+
}
146+
});
147+
148+
return { document, lsAndTsDocResolver };
149+
}
150+
151+
it('provides auto import for svelte component when importModuleSpecifierEnding is js', async () => {
152+
const { document, lsAndTsDocResolver } = setupImportModuleSpecifierEndingJs();
153+
154+
const completionProvider = new CompletionsProviderImpl(lsAndTsDocResolver);
155+
156+
const completions = await completionProvider.getCompletions(
157+
document,
158+
Position.create(4, 8)
159+
);
160+
161+
const item = completions?.items.find((item) => item.label === 'Imports');
162+
const { additionalTextEdits } = await completionProvider.resolveCompletion(document, item!);
163+
assert.strictEqual(additionalTextEdits![0].newText.trim(), expectedComponentImportEdit);
164+
});
165+
166+
it('provides auto import for context="module" export when importModuleSpecifierEnding is js', async () => {
167+
const { document, lsAndTsDocResolver } = setupImportModuleSpecifierEndingJs();
168+
169+
const completionProvider = new CompletionsProviderImpl(lsAndTsDocResolver);
170+
171+
const completions = await completionProvider.getCompletions(
172+
document,
173+
Position.create(1, 6)
174+
);
175+
176+
const item = completions?.items.find((item) => item.label === 'hi');
177+
const { additionalTextEdits } = await completionProvider.resolveCompletion(document, item!);
178+
assert.strictEqual(
179+
additionalTextEdits![0].newText.trim(),
180+
"import { hi } from '~/with-context-module.svelte';"
181+
);
182+
});
183+
184+
it('provides import code action for svelte component when importModuleSpecifierEnding is js', async () => {
185+
const range = Range.create(Position.create(4, 1), Position.create(4, 8));
186+
const { document, lsAndTsDocResolver } = setupImportModuleSpecifierEndingJs();
187+
188+
const completionProvider = new CompletionsProviderImpl(lsAndTsDocResolver);
189+
const codeActionProvider = new CodeActionsProviderImpl(
190+
lsAndTsDocResolver,
191+
completionProvider
192+
);
193+
194+
const codeAction = await codeActionProvider.getCodeActions(document, range, {
195+
diagnostics: [
196+
Diagnostic.create(
197+
range,
198+
"Cannot find name 'Imports'",
199+
DiagnosticSeverity.Error,
200+
2304,
201+
'ts'
202+
)
203+
]
204+
});
205+
206+
const documentChange = codeAction[0].edit?.documentChanges?.[0] as
207+
| TextDocumentEdit
208+
| undefined;
209+
assert.strictEqual(documentChange?.edits[0].newText.trim(), expectedComponentImportEdit);
210+
});
135211
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts">
2+
hi
3+
</script>
4+
5+
<Imports></Imports>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<script lang="ts" context="module">
2+
export function hi() {}
3+
</script>

0 commit comments

Comments
 (0)