Skip to content

Commit 3e61718

Browse files
authored
fix autoimports crash: generate namespace and other module symbol imports (#60333)
1 parent 32513a7 commit 3e61718

21 files changed

+2361
-22
lines changed

src/services/codefixes/importFixes.ts

+76-2
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,15 @@ import {
8080
ImportSpecifier,
8181
insertImports,
8282
InternalSymbolName,
83+
isDefaultImport,
8384
isExternalModuleReference,
8485
isFullSourceFile,
8586
isIdentifier,
8687
isImportable,
88+
isImportClause,
8789
isImportDeclaration,
8890
isImportEqualsDeclaration,
91+
isImportSpecifier,
8992
isIntrinsicJsxName,
9093
isJSDocImportTag,
9194
isJsxClosingElement,
@@ -228,6 +231,7 @@ export interface ImportAdder {
228231
addImportFromDiagnostic: (diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) => void;
229232
addImportFromExportedSymbol: (exportedSymbol: Symbol, isValidTypeOnlyUseSite?: boolean, referenceImport?: ImportOrRequireAliasDeclaration) => void;
230233
addImportForNonExistentExport: (exportName: string, exportingFileName: string, exportKind: ExportKind, exportedMeanings: SymbolFlags, isImportUsageValidAsTypeOnly: boolean) => void;
234+
addImportForModuleSymbol: (symbolAlias: Symbol, isValidTypeOnlyUseSite: boolean, referenceImport: ImportOrRequireAliasDeclaration) => void;
231235
addImportForUnresolvedIdentifier: (context: CodeFixContextBase, symbolToken: Identifier, useAutoImportProvider: boolean) => void;
232236
addVerbatimImport: (declaration: AnyImportOrRequireStatement | ImportOrRequireAliasDeclaration) => void;
233237
removeExistingImport: (declaration: ImportOrRequireAliasDeclaration) => void;
@@ -257,7 +261,7 @@ function createImportAdderWorker(sourceFile: SourceFile | FutureSourceFile, prog
257261
type NewImportsKey = `${0 | 1}|${string}`;
258262
/** Use `getNewImportEntry` for access */
259263
const newImports = new Map<NewImportsKey, Mutable<ImportsCollection & { useRequire: boolean; }>>();
260-
return { addImportFromDiagnostic, addImportFromExportedSymbol, writeFixes, hasFixes, addImportForUnresolvedIdentifier, addImportForNonExistentExport, removeExistingImport, addVerbatimImport };
264+
return { addImportFromDiagnostic, addImportFromExportedSymbol, addImportForModuleSymbol, writeFixes, hasFixes, addImportForUnresolvedIdentifier, addImportForNonExistentExport, removeExistingImport, addVerbatimImport };
261265

262266
function addVerbatimImport(declaration: AnyImportOrRequireStatement | ImportOrRequireAliasDeclaration) {
263267
verbatimImports.add(declaration);
@@ -276,7 +280,7 @@ function createImportAdderWorker(sourceFile: SourceFile | FutureSourceFile, prog
276280
}
277281

278282
function addImportFromExportedSymbol(exportedSymbol: Symbol, isValidTypeOnlyUseSite?: boolean, referenceImport?: ImportOrRequireAliasDeclaration) {
279-
const moduleSymbol = Debug.checkDefined(exportedSymbol.parent);
283+
const moduleSymbol = Debug.checkDefined(exportedSymbol.parent, "Expected exported symbol to have module symbol as parent");
280284
const symbolName = getNameForExportedSymbol(exportedSymbol, getEmitScriptTarget(compilerOptions));
281285
const checker = program.getTypeChecker();
282286
const symbol = checker.getMergedSymbol(skipAlias(exportedSymbol, checker));
@@ -317,6 +321,74 @@ function createImportAdderWorker(sourceFile: SourceFile | FutureSourceFile, prog
317321
}
318322
}
319323

324+
function addImportForModuleSymbol(symbolAlias: Symbol, isValidTypeOnlyUseSite: boolean, referenceImport: ImportOrRequireAliasDeclaration) {
325+
// Adds import for module, import alias will be symbolAlias.name
326+
const checker = program.getTypeChecker();
327+
const moduleSymbol = checker.getAliasedSymbol(symbolAlias);
328+
Debug.assert(moduleSymbol.flags & SymbolFlags.Module, "Expected symbol to be a module");
329+
const moduleSpecifierResolutionHost = createModuleSpecifierResolutionHost(program, host);
330+
const moduleSpecifierResult = moduleSpecifiers.getModuleSpecifiersWithCacheInfo(moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences, /*options*/ undefined, /*forAutoImport*/ true);
331+
332+
const useRequire = shouldUseRequire(sourceFile, program);
333+
334+
// Copy the type-only status from the reference import
335+
let addAsTypeOnly = getAddAsTypeOnly(
336+
isValidTypeOnlyUseSite,
337+
/*isForNewImportDeclaration*/ true,
338+
/*symbol*/ undefined,
339+
symbolAlias.flags,
340+
program.getTypeChecker(),
341+
compilerOptions,
342+
);
343+
addAsTypeOnly = addAsTypeOnly === AddAsTypeOnly.Allowed && isTypeOnlyImportDeclaration(referenceImport) ? AddAsTypeOnly.Required : AddAsTypeOnly.Allowed;
344+
345+
// Copy the kind of import
346+
const importKind = isImportDeclaration(referenceImport) ?
347+
isDefaultImport(referenceImport) ? ImportKind.Default : ImportKind.Namespace :
348+
isImportSpecifier(referenceImport) ? ImportKind.Named :
349+
isImportClause(referenceImport) && !!referenceImport.name ? ImportKind.Default : ImportKind.Namespace;
350+
351+
const exportInfo = [{
352+
symbol: symbolAlias,
353+
moduleSymbol,
354+
moduleFileName: moduleSymbol.declarations?.[0]?.getSourceFile()?.fileName,
355+
exportKind: ExportKind.Module,
356+
targetFlags: symbolAlias.flags,
357+
isFromPackageJson: false,
358+
}];
359+
360+
const existingFix = getImportFixForSymbol(
361+
sourceFile,
362+
exportInfo,
363+
program,
364+
/*position*/ undefined,
365+
!!isValidTypeOnlyUseSite,
366+
useRequire,
367+
host,
368+
preferences,
369+
);
370+
371+
let fix: FixAddNewImport | ImportFixWithModuleSpecifier;
372+
if (existingFix && importKind !== ImportKind.Namespace) {
373+
fix = {
374+
...existingFix,
375+
addAsTypeOnly,
376+
importKind,
377+
};
378+
}
379+
else {
380+
fix = {
381+
kind: ImportFixKind.AddNew,
382+
moduleSpecifierKind: existingFix !== undefined ? existingFix.moduleSpecifierKind : moduleSpecifierResult.kind,
383+
moduleSpecifier: existingFix !== undefined ? existingFix.moduleSpecifier : first(moduleSpecifierResult.moduleSpecifiers),
384+
importKind,
385+
addAsTypeOnly,
386+
useRequire,
387+
};
388+
}
389+
addImport({ fix, symbolName: symbolAlias.name, errorIdentifierText: undefined });
390+
}
391+
320392
function addImportForNonExistentExport(exportName: string, exportingFileName: string, exportKind: ExportKind, exportedMeanings: SymbolFlags, isImportUsageValidAsTypeOnly: boolean) {
321393
const exportingSourceFile = program.getSourceFile(exportingFileName);
322394
const useRequire = shouldUseRequire(sourceFile, program);
@@ -1452,6 +1524,8 @@ export function getImportKind(importingFile: SourceFile | FutureSourceFile, expo
14521524
return getExportEqualsImportKind(importingFile, program.getCompilerOptions(), !!forceImportKeyword);
14531525
case ExportKind.UMD:
14541526
return getUmdImportKind(importingFile, program, !!forceImportKeyword);
1527+
case ExportKind.Module:
1528+
return ImportKind.Namespace;
14551529
default:
14561530
return Debug.assertNever(exportKind);
14571531
}

src/services/exportInfoMap.ts

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export const enum ExportKind {
7474
Default,
7575
ExportEquals,
7676
UMD,
77+
Module,
7778
}
7879

7980
/** @internal */

src/services/refactors/helpers.ts

+4
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ export function addTargetFileImports(
8080
if (checker.isUnknownSymbol(targetSymbol)) {
8181
importAdder.addVerbatimImport(Debug.checkDefined(declaration ?? findAncestor(symbol.declarations?.[0], isAnyImportOrRequireStatement)));
8282
}
83+
else if (targetSymbol.parent === undefined) {
84+
Debug.assert(declaration !== undefined, "expected module symbol to have a declaration");
85+
importAdder.addImportForModuleSymbol(symbol, isValidTypeOnlyUseSite, declaration);
86+
}
8387
else {
8488
importAdder.addImportFromExportedSymbol(targetSymbol, isValidTypeOnlyUseSite, declaration);
8589
}

tests/baselines/reference/tsserver/fourslashServer/pasteEdits_blankTargetFile.js

+16-16
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,28 @@ console.log(abc);
2222

2323
console.log("abc");
2424

25-
//// [/home/src/workspaces/project/c.ts]
25+
//// [/home/src/workspaces/project/folder/c.ts]
2626

2727

2828
//// [/home/src/workspaces/project/tsconfig.json]
29-
{ "files": ["c.ts", "a.ts", "b.ts"] }
29+
{ "files": ["folder/c.ts", "a.ts", "b.ts"] }
3030

3131

3232
Info seq [hh:mm:ss:mss] request:
3333
{
3434
"seq": 0,
3535
"type": "request",
3636
"arguments": {
37-
"file": "/home/src/workspaces/project/c.ts"
37+
"file": "/home/src/workspaces/project/folder/c.ts"
3838
},
3939
"command": "open"
4040
}
41-
Info seq [hh:mm:ss:mss] getConfigFileNameForFile:: File: /home/src/workspaces/project/c.ts ProjectRootPath: undefined:: Result: /home/src/workspaces/project/tsconfig.json
41+
Info seq [hh:mm:ss:mss] getConfigFileNameForFile:: File: /home/src/workspaces/project/folder/c.ts ProjectRootPath: undefined:: Result: /home/src/workspaces/project/tsconfig.json
4242
Info seq [hh:mm:ss:mss] Creating ConfiguredProject: /home/src/workspaces/project/tsconfig.json, currentDirectory: /home/src/workspaces/project
4343
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /home/src/workspaces/project/tsconfig.json 2000 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Config file
4444
Info seq [hh:mm:ss:mss] Config: /home/src/workspaces/project/tsconfig.json : {
4545
"rootNames": [
46-
"/home/src/workspaces/project/c.ts",
46+
"/home/src/workspaces/project/folder/c.ts",
4747
"/home/src/workspaces/project/a.ts",
4848
"/home/src/workspaces/project/b.ts"
4949
],
@@ -58,7 +58,7 @@ Info seq [hh:mm:ss:mss] event:
5858
"event": "projectLoadingStart",
5959
"body": {
6060
"projectName": "/home/src/workspaces/project/tsconfig.json",
61-
"reason": "Creating possible configured project for /home/src/workspaces/project/c.ts to open"
61+
"reason": "Creating possible configured project for /home/src/workspaces/project/folder/c.ts to open"
6262
}
6363
}
6464
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /home/src/workspaces/project/a.ts 500 undefined WatchType: Closed Script info
@@ -81,7 +81,7 @@ Info seq [hh:mm:ss:mss] Files (6)
8181
/home/src/tslibs/TS/Lib/lib.d.ts Text-1 lib.d.ts-Text
8282
/home/src/tslibs/TS/Lib/lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text
8383
/home/src/tslibs/TS/Lib/lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text
84-
/home/src/workspaces/project/c.ts SVC-1-0 ""
84+
/home/src/workspaces/project/folder/c.ts SVC-1-0 ""
8585
/home/src/workspaces/project/a.ts Text-1 "export const abc = 10;"
8686
/home/src/workspaces/project/b.ts Text-1 "import { abc } from \"./a\";\n\nconsole.log(abc);\n\n\nconsole.log(\"abc\");"
8787

@@ -92,7 +92,7 @@ Info seq [hh:mm:ss:mss] Files (6)
9292
Library referenced via 'decorators' from file '../../tslibs/TS/Lib/lib.d.ts'
9393
../../tslibs/TS/Lib/lib.decorators.legacy.d.ts
9494
Library referenced via 'decorators.legacy' from file '../../tslibs/TS/Lib/lib.d.ts'
95-
c.ts
95+
folder/c.ts
9696
Part of 'files' list in tsconfig.json
9797
a.ts
9898
Part of 'files' list in tsconfig.json
@@ -116,7 +116,7 @@ Info seq [hh:mm:ss:mss] event:
116116
"type": "event",
117117
"event": "configFileDiag",
118118
"body": {
119-
"triggerFile": "/home/src/workspaces/project/c.ts",
119+
"triggerFile": "/home/src/workspaces/project/folder/c.ts",
120120
"configFile": "/home/src/workspaces/project/tsconfig.json",
121121
"diagnostics": []
122122
}
@@ -126,7 +126,7 @@ Info seq [hh:mm:ss:mss] Files (6)
126126

127127
Info seq [hh:mm:ss:mss] -----------------------------------------------
128128
Info seq [hh:mm:ss:mss] Open files:
129-
Info seq [hh:mm:ss:mss] FileName: /home/src/workspaces/project/c.ts ProjectRootPath: undefined
129+
Info seq [hh:mm:ss:mss] FileName: /home/src/workspaces/project/folder/c.ts ProjectRootPath: undefined
130130
Info seq [hh:mm:ss:mss] Projects: /home/src/workspaces/project/tsconfig.json
131131
Info seq [hh:mm:ss:mss] response:
132132
{
@@ -191,7 +191,7 @@ ScriptInfos::
191191
version: Text-1
192192
containingProjects: 1
193193
/home/src/workspaces/project/tsconfig.json
194-
/home/src/workspaces/project/c.ts (Open) *new*
194+
/home/src/workspaces/project/folder/c.ts (Open) *new*
195195
version: SVC-1-0
196196
containingProjects: 1
197197
/home/src/workspaces/project/tsconfig.json *default*
@@ -242,7 +242,7 @@ Info seq [hh:mm:ss:mss] request:
242242
"seq": 2,
243243
"type": "request",
244244
"arguments": {
245-
"file": "/home/src/workspaces/project/c.ts",
245+
"file": "/home/src/workspaces/project/folder/c.ts",
246246
"pastedText": [
247247
"console.log(abc);"
248248
],
@@ -283,7 +283,7 @@ Info seq [hh:mm:ss:mss] Files (6)
283283
/home/src/tslibs/TS/Lib/lib.d.ts Text-1 lib.d.ts-Text
284284
/home/src/tslibs/TS/Lib/lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text
285285
/home/src/tslibs/TS/Lib/lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text
286-
/home/src/workspaces/project/c.ts SVC-1-1 "console.log(abc);"
286+
/home/src/workspaces/project/folder/c.ts SVC-1-1 "console.log(abc);"
287287
/home/src/workspaces/project/a.ts Text-1 "export const abc = 10;"
288288
/home/src/workspaces/project/b.ts Text-1 "import { abc } from \"./a\";\n\nconsole.log(abc);\n\n\nconsole.log(\"abc\");"
289289

@@ -303,7 +303,7 @@ Info seq [hh:mm:ss:mss] response:
303303
"body": {
304304
"edits": [
305305
{
306-
"fileName": "/home/src/workspaces/project/c.ts",
306+
"fileName": "/home/src/workspaces/project/folder/c.ts",
307307
"textChanges": [
308308
{
309309
"start": {
@@ -314,7 +314,7 @@ Info seq [hh:mm:ss:mss] response:
314314
"line": 1,
315315
"offset": 1
316316
},
317-
"newText": "import { abc } from \"./a\";\n\n"
317+
"newText": "import { abc } from \"../a\";\n\n"
318318
},
319319
{
320320
"start": {
@@ -362,7 +362,7 @@ ScriptInfos::
362362
version: Text-1
363363
containingProjects: 1
364364
/home/src/workspaces/project/tsconfig.json
365-
/home/src/workspaces/project/c.ts (Open) *changed*
365+
/home/src/workspaces/project/folder/c.ts (Open) *changed*
366366
version: SVC-1-2 *changed*
367367
containingProjects: 1
368368
/home/src/workspaces/project/tsconfig.json *default*

0 commit comments

Comments
 (0)