Skip to content

Commit e81e6fb

Browse files
committed
improve import code fixes for UMD modules
- use default import under --allowSyntheticDefaultImports - import..require support - make make quick fix info match resulting import - make diagnostics
1 parent b5f292d commit e81e6fb

File tree

2 files changed

+53
-11
lines changed

2 files changed

+53
-11
lines changed

src/compiler/diagnosticMessages.json

+8
Original file line numberDiff line numberDiff line change
@@ -3781,5 +3781,13 @@
37813781
"Infer parameter types from usage.": {
37823782
"category": "Message",
37833783
"code": 95012
3784+
},
3785+
"Import '{0}' = require(\"{1}\").": {
3786+
"category": "Message",
3787+
"code": 95013
3788+
},
3789+
"Import * as '{0}' from \"{1}\".": {
3790+
"category": "Message",
3791+
"code": 95014
37843792
}
37853793
}

src/services/codefixes/importFixes.ts

+45-11
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ namespace ts.codefix {
181181
Named,
182182
Default,
183183
Namespace,
184+
Equals
184185
}
185186

186187
export function getCodeActionForImport(moduleSymbol: Symbol, context: ImportCodeFixOptions): ImportCodeAction[] {
@@ -212,7 +213,7 @@ namespace ts.codefix {
212213

213214
function getNamespaceImportName(declaration: AnyImportSyntax): Identifier {
214215
if (declaration.kind === SyntaxKind.ImportDeclaration) {
215-
const namedBindings = declaration.importClause && declaration.importClause.namedBindings;
216+
const namedBindings = declaration.importClause && isImportClause(declaration.importClause) && declaration.importClause.namedBindings;
216217
return namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport ? namedBindings.name : undefined;
217218
}
218219
else {
@@ -237,6 +238,8 @@ namespace ts.codefix {
237238
return parent as ImportDeclaration;
238239
case SyntaxKind.ExternalModuleReference:
239240
return (parent as ExternalModuleReference).parent;
241+
case SyntaxKind.ImportEqualsDeclaration:
242+
return parent as ImportEqualsDeclaration;
240243
default:
241244
Debug.assert(parent.kind === SyntaxKind.ExportDeclaration);
242245
// Ignore these, can't add imports to them.
@@ -249,11 +252,19 @@ namespace ts.codefix {
249252
const lastImportDeclaration = findLast(sourceFile.statements, isAnyImportSyntax);
250253

251254
const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier);
252-
const importDecl = createImportDeclaration(
253-
/*decorators*/ undefined,
254-
/*modifiers*/ undefined,
255-
createImportClauseOfKind(kind, symbolName),
256-
createStringLiteralWithQuoteStyle(sourceFile, moduleSpecifierWithoutQuotes));
255+
const quotedModuleSpecifier = createStringLiteralWithQuoteStyle(sourceFile, moduleSpecifierWithoutQuotes);
256+
const importDecl = kind !== ImportKind.Equals
257+
? createImportDeclaration(
258+
/*decorators*/ undefined,
259+
/*modifiers*/ undefined,
260+
createImportClauseOfKind(kind, symbolName),
261+
quotedModuleSpecifier)
262+
: createImportEqualsDeclaration(
263+
/*decorators*/ undefined,
264+
/*modifiers*/ undefined,
265+
createIdentifier(symbolName),
266+
createExternalModuleReference(quotedModuleSpecifier));
267+
257268
const changes = ChangeTracker.with(context, changeTracker => {
258269
if (lastImportDeclaration) {
259270
changeTracker.insertNodeAfter(sourceFile, lastImportDeclaration, importDecl, { suffix: newLineCharacter });
@@ -263,11 +274,17 @@ namespace ts.codefix {
263274
}
264275
});
265276

277+
const actionFormat = kind === ImportKind.Equals
278+
? Diagnostics.Import_0_require_1
279+
: kind === ImportKind.Namespace
280+
? Diagnostics.Import_Asterisk_as_0_from_1
281+
: Diagnostics.Import_0_from_1;
282+
266283
// if this file doesn't have any import statements, insert an import statement and then insert a new line
267284
// between the only import statement and user code. Otherwise just insert the statement because chances
268285
// are there are already a new line seperating code and import statements.
269286
return createCodeAction(
270-
Diagnostics.Import_0_from_1,
287+
actionFormat,
271288
[symbolName, moduleSpecifierWithoutQuotes],
272289
changes,
273290
"NewImport",
@@ -282,7 +299,7 @@ namespace ts.codefix {
282299
return literal;
283300
}
284301

285-
function createImportClauseOfKind(kind: ImportKind, symbolName: string) {
302+
function createImportClauseOfKind(kind: ImportKind.Default | ImportKind.Named | ImportKind.Namespace, symbolName: string) {
286303
const id = createIdentifier(symbolName);
287304
switch (kind) {
288305
case ImportKind.Default:
@@ -534,7 +551,7 @@ namespace ts.codefix {
534551
declarations: ReadonlyArray<AnyImportSyntax>): ImportCodeAction {
535552
const fromExistingImport = firstDefined(declarations, declaration => {
536553
if (declaration.kind === SyntaxKind.ImportDeclaration && declaration.importClause) {
537-
const changes = tryUpdateExistingImport(ctx, declaration.importClause);
554+
const changes = tryUpdateExistingImport(ctx, isImportClause(declaration.importClause) && declaration.importClause || undefined);
538555
if (changes) {
539556
const moduleSpecifierWithoutQuotes = stripQuotes(declaration.moduleSpecifier.getText());
540557
return createCodeAction(
@@ -564,9 +581,10 @@ namespace ts.codefix {
564581
return expression && isStringLiteral(expression) ? expression.text : undefined;
565582
}
566583

567-
function tryUpdateExistingImport(context: SymbolContext & { kind: ImportKind }, importClause: ImportClause): FileTextChanges[] | undefined {
584+
function tryUpdateExistingImport(context: SymbolContext & { kind: ImportKind }, importClause: ImportClause | ImportEqualsDeclaration): FileTextChanges[] | undefined {
568585
const { symbolName, sourceFile, kind } = context;
569-
const { name, namedBindings } = importClause;
586+
const { name } = importClause;
587+
const { namedBindings } = importClause.kind !== SyntaxKind.ImportEqualsDeclaration && importClause;
570588
switch (kind) {
571589
case ImportKind.Default:
572590
return name ? undefined : ChangeTracker.with(context, t =>
@@ -592,6 +610,9 @@ namespace ts.codefix {
592610
return namedBindings ? undefined : ChangeTracker.with(context, t =>
593611
t.replaceNode(sourceFile, importClause, createImportClause(name, createNamespaceImport(createIdentifier(symbolName)))));
594612

613+
case ImportKind.Equals:
614+
return undefined;
615+
595616
default:
596617
Debug.assertNever(kind);
597618
}
@@ -644,6 +665,19 @@ namespace ts.codefix {
644665
Debug.fail("Either the symbol or the JSX namespace should be a UMD global if we got here");
645666
}
646667

668+
const { module, allowSyntheticDefaultImports } = context.compilerOptions;
669+
670+
// Prefer to import as a synthetic `default` if available.
671+
if (allowSyntheticDefaultImports || module === ModuleKind.System && allowSyntheticDefaultImports !== false) {
672+
return getCodeActionForImport(symbol, { ...context, symbolName, kind: ImportKind.Default });
673+
}
674+
675+
// When a synthetic `default` is unavailable, use `import..require` if the module kind supports it.
676+
if (module === ModuleKind.AMD || module === ModuleKind.CommonJS || module === ModuleKind.UMD) {
677+
return getCodeActionForImport(symbol, { ...context, symbolName, kind: ImportKind.Equals });
678+
}
679+
680+
// Fall back to `* as ns` style imports.
647681
return getCodeActionForImport(symbol, { ...context, symbolName, kind: ImportKind.Namespace });
648682
}
649683

0 commit comments

Comments
 (0)