Skip to content

Commit 5366e78

Browse files
authored
feat: support "add missing imports on save" (#2744)
Fixes #2616 Adds the source action to addMissingImports, reusing the same quick fix action as "import all missing"
1 parent ddc62b8 commit 5366e78

File tree

5 files changed

+161
-9
lines changed

5 files changed

+161
-9
lines changed

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

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { internalHelpers } from 'svelte2tsx';
12
import ts from 'typescript';
23
import {
34
CancellationToken,
@@ -24,6 +25,7 @@ import {
2425
} from '../../../lib/documents';
2526
import { LSConfigManager } from '../../../ls-config';
2627
import {
28+
createGetCanonicalFileName,
2729
flatten,
2830
getIndent,
2931
isNotNullOrUndefined,
@@ -37,13 +39,15 @@ import {
3739
import { CodeActionsProvider } from '../../interfaces';
3840
import { DocumentSnapshot, SvelteDocumentSnapshot } from '../DocumentSnapshot';
3941
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
42+
import { LanguageServiceContainer } from '../service';
4043
import {
4144
changeSvelteComponentName,
4245
convertRange,
4346
isInScript,
4447
toGeneratedSvelteComponentName
4548
} from '../utils';
4649
import { CompletionsProviderImpl } from './CompletionProvider';
50+
import { DiagnosticCode } from './DiagnosticsProvider';
4751
import {
4852
findClosestContainingNode,
4953
FormatCodeBasis,
@@ -53,15 +57,12 @@ import {
5357
isTextSpanInGeneratedCode,
5458
SnapshotMap
5559
} from './utils';
56-
import { DiagnosticCode } from './DiagnosticsProvider';
57-
import { createGetCanonicalFileName } from '../../../utils';
58-
import { LanguageServiceContainer } from '../service';
59-
import { internalHelpers } from 'svelte2tsx';
6060

6161
/**
6262
* TODO change this to protocol constant if it's part of the protocol
6363
*/
6464
export const SORT_IMPORT_CODE_ACTION_KIND = 'source.sortImports';
65+
export const ADD_MISSING_IMPORTS_CODE_ACTION_KIND = 'source.addMissingImports';
6566

6667
interface RefactorArgs {
6768
type: 'refactor';
@@ -121,6 +122,10 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
121122
);
122123
}
123124

125+
if (context.only?.[0] === ADD_MISSING_IMPORTS_CODE_ACTION_KIND) {
126+
return await this.addMissingImports(document, cancellationToken);
127+
}
128+
124129
// for source action command (all source.xxx)
125130
// vscode would show different source code action kinds to choose from
126131
if (context.only?.[0] === CodeActionKind.Source) {
@@ -130,7 +135,8 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
130135
document,
131136
cancellationToken,
132137
/**skipDestructiveCodeActions */ true
133-
))
138+
)),
139+
...(await this.addMissingImports(document, cancellationToken))
134140
];
135141
}
136142

@@ -1553,4 +1559,48 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
15531559
private async getLSAndTSDoc(document: Document) {
15541560
return this.lsAndTsDocResolver.getLSAndTSDoc(document);
15551561
}
1562+
1563+
private async addMissingImports(
1564+
document: Document,
1565+
cancellationToken?: CancellationToken
1566+
): Promise<CodeAction[]> {
1567+
// Re-introduce LS/TSDoc resolution and diagnostic check
1568+
const { lang, tsDoc } = await this.getLSAndTSDoc(document);
1569+
if (cancellationToken?.isCancellationRequested) {
1570+
return [];
1571+
}
1572+
1573+
// Check if there are any relevant "cannot find name" diagnostics
1574+
const diagnostics = lang.getSemanticDiagnostics(tsDoc.filePath);
1575+
const hasMissingImports = diagnostics.some(
1576+
(diag) =>
1577+
(diag.code === DiagnosticCode.CANNOT_FIND_NAME ||
1578+
diag.code === DiagnosticCode.CANNOT_FIND_NAME_X_DID_YOU_MEAN_Y) &&
1579+
// Ensure the diagnostic is not in generated code
1580+
!isTextSpanInGeneratedCode(tsDoc.getFullText(), {
1581+
start: diag.start ?? 0,
1582+
length: diag.length ?? 0
1583+
})
1584+
);
1585+
1586+
// Only return the action if there are potential imports to add
1587+
if (!hasMissingImports) {
1588+
return [];
1589+
}
1590+
1591+
// If imports might be needed, create the deferred action
1592+
const codeAction = CodeAction.create(
1593+
FIX_IMPORT_FIX_DESCRIPTION,
1594+
ADD_MISSING_IMPORTS_CODE_ACTION_KIND
1595+
);
1596+
1597+
const data: QuickFixAllResolveInfo = {
1598+
uri: document.uri,
1599+
fixName: FIX_IMPORT_FIX_NAME,
1600+
fixId: FIX_IMPORT_FIX_ID
1601+
};
1602+
codeAction.data = data;
1603+
1604+
return [codeAction];
1605+
}
15561606
}

packages/language-server/src/server.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ import { debounceThrottle, isNotNullOrUndefined, normalizeUri, urlToPath } from
4545
import { FallbackWatcher } from './lib/FallbackWatcher';
4646
import { configLoader } from './lib/documents/configLoader';
4747
import { setIsTrusted } from './importPackage';
48-
import { SORT_IMPORT_CODE_ACTION_KIND } from './plugins/typescript/features/CodeActionsProvider';
48+
import {
49+
SORT_IMPORT_CODE_ACTION_KIND,
50+
ADD_MISSING_IMPORTS_CODE_ACTION_KIND
51+
} from './plugins/typescript/features/CodeActionsProvider';
4952
import { createLanguageServices } from './plugins/css/service';
5053
import { FileSystemProvider } from './plugins/css/FileSystemProvider';
5154

@@ -270,6 +273,7 @@ export function startServer(options?: LSOptions) {
270273
CodeActionKind.QuickFix,
271274
CodeActionKind.SourceOrganizeImports,
272275
SORT_IMPORT_CODE_ACTION_KIND,
276+
ADD_MISSING_IMPORTS_CODE_ACTION_KIND,
273277
...(clientSupportApplyEditCommand ? [CodeActionKind.Refactor] : [])
274278
].filter(
275279
clientSupportedCodeActionKinds &&

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

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import * as assert from 'assert';
22
import * as path from 'path';
3+
import { VERSION } from 'svelte/compiler';
4+
import { internalHelpers } from 'svelte2tsx';
35
import ts from 'typescript';
46
import {
57
CancellationTokenSource,
@@ -12,17 +14,16 @@ import {
1214
import { Document, DocumentManager } from '../../../../src/lib/documents';
1315
import { LSConfigManager } from '../../../../src/ls-config';
1416
import {
17+
ADD_MISSING_IMPORTS_CODE_ACTION_KIND,
1518
CodeActionsProviderImpl,
1619
SORT_IMPORT_CODE_ACTION_KIND
1720
} from '../../../../src/plugins/typescript/features/CodeActionsProvider';
1821
import { CompletionsProviderImpl } from '../../../../src/plugins/typescript/features/CompletionProvider';
22+
import { DiagnosticCode } from '../../../../src/plugins/typescript/features/DiagnosticsProvider';
1923
import { LSAndTSDocResolver } from '../../../../src/plugins/typescript/LSAndTSDocResolver';
2024
import { __resetCache } from '../../../../src/plugins/typescript/service';
2125
import { pathToUrl } from '../../../../src/utils';
2226
import { recursiveServiceWarmup } from '../test-utils';
23-
import { DiagnosticCode } from '../../../../src/plugins/typescript/features/DiagnosticsProvider';
24-
import { VERSION } from 'svelte/compiler';
25-
import { internalHelpers } from 'svelte2tsx';
2627

2728
const testDir = path.join(__dirname, '..');
2829
const indent = ' '.repeat(4);
@@ -2229,4 +2230,84 @@ describe('CodeActionsProvider', function () {
22292230
after(() => {
22302231
__resetCache();
22312232
});
2233+
2234+
it('provides source action for adding all missing imports', async () => {
2235+
const { provider, document } = setup('codeaction-custom-fix-all-component5.svelte');
2236+
2237+
const range = Range.create(Position.create(4, 1), Position.create(4, 15));
2238+
2239+
// Request the specific source action
2240+
const codeActions = await provider.getCodeActions(document, range, {
2241+
diagnostics: [], // Diagnostics might not be needed here if we only want the source action by kind
2242+
only: [ADD_MISSING_IMPORTS_CODE_ACTION_KIND]
2243+
});
2244+
2245+
assert.ok(codeActions.length > 0, 'No code actions found');
2246+
2247+
// Find the action by its kind
2248+
const addImportsAction = codeActions.find((action) => action.data);
2249+
2250+
// Ensure the action was found and has data (as it's now deferred)
2251+
assert.ok(addImportsAction, 'Add missing imports action should be found');
2252+
assert.ok(
2253+
addImportsAction.data,
2254+
'Add missing imports action should have data for resolution'
2255+
);
2256+
2257+
// Resolve the action to get the edits
2258+
const resolvedAction = await provider.resolveCodeAction(document, addImportsAction);
2259+
2260+
// Assert the edits on the resolved action
2261+
assert.ok(resolvedAction.edit, 'Resolved action should have an edit');
2262+
(<TextDocumentEdit>resolvedAction.edit?.documentChanges?.[0])?.edits.forEach(
2263+
(edit) => (edit.newText = harmonizeNewLines(edit.newText))
2264+
);
2265+
2266+
assert.deepStrictEqual(resolvedAction.edit, {
2267+
documentChanges: [
2268+
{
2269+
edits: [
2270+
{
2271+
newText:
2272+
`\n${indent}import FixAllImported from \"./importing/FixAllImported.svelte\";\n` +
2273+
`${indent}import FixAllImported2 from \"./importing/FixAllImported2.svelte\";\n`,
2274+
range: {
2275+
start: {
2276+
character: 18,
2277+
line: 0
2278+
},
2279+
end: {
2280+
character: 18,
2281+
line: 0
2282+
}
2283+
}
2284+
}
2285+
],
2286+
textDocument: {
2287+
uri: getUri('codeaction-custom-fix-all-component5.svelte'),
2288+
version: null
2289+
}
2290+
}
2291+
]
2292+
});
2293+
2294+
// Optional: Verify the kind and title remain correct on the resolved action
2295+
assert.strictEqual(resolvedAction.kind, ADD_MISSING_IMPORTS_CODE_ACTION_KIND);
2296+
assert.strictEqual(resolvedAction.title, 'Add all missing imports');
2297+
});
2298+
2299+
it('provides source action for adding all missing imports only when imports are missing', async () => {
2300+
const { provider, document } = setup('codeaction-custom-fix-all-component6.svelte');
2301+
2302+
const codeActions = await provider.getCodeActions(
2303+
document,
2304+
Range.create(Position.create(1, 4), Position.create(1, 5)),
2305+
{
2306+
diagnostics: [], // No diagnostics = no missing imports
2307+
only: [ADD_MISSING_IMPORTS_CODE_ACTION_KIND]
2308+
}
2309+
);
2310+
2311+
assert.deepStrictEqual(codeActions, []);
2312+
});
22322313
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script lang="ts">
2+
3+
</script>
4+
5+
<FixAllImported />
6+
<FixAllImported2 />
7+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script lang="ts">
2+
import FixAllImported from './importing/FixAllImported.svelte';
3+
import FixAllImported2 from './importing/FixAllImported2.svelte';
4+
5+
6+
</script>
7+
8+
<FixAllImported />
9+
<FixAllImported2 />
10+

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy