diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 4e84cf197ad..ad04d6444e5 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2497,7 +2497,7 @@ namespace ts { function bindCallExpression(node: CallExpression) { // We're only inspecting call expressions to detect CommonJS modules, so we can skip // this check if we've already seen the module indicator - if (!file.commonJsModuleIndicator && isRequireCall(node, /*checkArgumentIsStringLiteral*/ false)) { + if (!file.commonJsModuleIndicator && isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ false)) { setCommonJsModuleIndicator(node); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index da547337ae5..35b28e04520 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18229,7 +18229,7 @@ namespace ts { } function isCommonJsRequire(node: Node): boolean { - if (!isRequireCall(node, /*checkArgumentIsStringLiteral*/ true)) { + if (!isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { return false; } @@ -19689,7 +19689,7 @@ namespace ts { function getTypeOfExpression(node: Expression, cache?: boolean) { // Optimize for the common case of a call to a function with a single non-generic call // signature where we can just fetch the return type without checking the arguments. - if (node.kind === SyntaxKind.CallExpression && (node).expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(node, /*checkArgumentIsStringLiteral*/ true) && !isSymbolOrSymbolForCall(node)) { + if (node.kind === SyntaxKind.CallExpression && (node).expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(node)) { const funcType = checkNonNullExpression((node).expression); const signature = getSingleCallSignature(funcType); if (signature && !signature.typeParameters) { @@ -25039,7 +25039,7 @@ namespace ts { // 3). Dynamic import call or require in javascript if ((isExternalModuleImportEqualsDeclaration(node.parent.parent) && getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node) || ((node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration) && (node.parent).moduleSpecifier === node) || - ((isInJavaScriptFile(node) && isRequireCall(node.parent, /*checkArgumentIsStringLiteral*/ false)) || isImportCall(node.parent))) { + ((isInJavaScriptFile(node) && isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false)) || isImportCall(node.parent))) { return resolveExternalModuleName(node, node); } // falls through @@ -25879,8 +25879,8 @@ namespace ts { } } - function getExternalModuleFileFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration): SourceFile { - const specifier = getExternalModuleName(declaration); + function getExternalModuleFileFromDeclaration(declaration: AnyImportOrReExport | ModuleDeclaration): SourceFile { + const specifier = declaration.kind === SyntaxKind.ModuleDeclaration ? tryCast(declaration.name, isStringLiteral) : getExternalModuleName(declaration); const moduleSymbol = resolveExternalModuleNameWorker(specifier, specifier, /*moduleNotFoundError*/ undefined); if (!moduleSymbol) { return undefined; diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 59936f0eb1e..ce610537ec4 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1984,7 +1984,7 @@ namespace ts { decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, importClause: ImportClause | undefined, - moduleSpecifier?: Expression): ImportDeclaration { + moduleSpecifier: Expression): ImportDeclaration { const node = createSynthesizedNode(SyntaxKind.ImportDeclaration); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 35772b2f040..c7c705f8d4d 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1574,10 +1574,10 @@ namespace ts { return a.fileName === b.fileName; } - function moduleNameIsEqualTo(a: StringLiteral | Identifier, b: StringLiteral | Identifier): boolean { - return a.kind === SyntaxKind.StringLiteral - ? b.kind === SyntaxKind.StringLiteral && a.text === b.text - : b.kind === SyntaxKind.Identifier && a.escapedText === b.escapedText; + function moduleNameIsEqualTo(a: StringLiteralLike | Identifier, b: StringLiteralLike | Identifier): boolean { + return a.kind === SyntaxKind.Identifier + ? b.kind === SyntaxKind.Identifier && a.escapedText === b.escapedText + : b.kind === SyntaxKind.StringLiteral && a.text === b.text; } function collectExternalModuleReferences(file: SourceFile): void { @@ -1589,7 +1589,7 @@ namespace ts { const isExternalModuleFile = isExternalModule(file); // file.imports may not be undefined if there exists dynamic import - let imports: StringLiteral[]; + let imports: StringLiteralLike[] | undefined; let moduleAugmentations: (StringLiteral | Identifier)[]; let ambientModules: string[]; @@ -1600,7 +1600,7 @@ namespace ts { && !file.isDeclarationFile) { // synthesize 'import "tslib"' declaration const externalHelpersModuleReference = createLiteral(externalHelpersModuleNameText); - const importDecl = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*importClause*/ undefined); + const importDecl = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*importClause*/ undefined, externalHelpersModuleReference); addEmitFlags(importDecl, EmitFlags.NeverApplyImportHelper); externalHelpersModuleReference.parent = importDecl; importDecl.parent = file; @@ -1621,66 +1621,55 @@ namespace ts { return; function collectModuleReferences(node: Statement, inAmbientModule: boolean): void { - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ExportDeclaration: - const moduleNameExpr = getExternalModuleName(node); - if (!moduleNameExpr || !isStringLiteral(moduleNameExpr)) { - break; + if (isAnyImportOrReExport(node)) { + const moduleNameExpr = getExternalModuleName(node); + // TypeScript 1.0 spec (April 2014): 12.1.6 + // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference other external modules + // only through top - level external module names. Relative external module names are not permitted. + if (moduleNameExpr && isStringLiteral(moduleNameExpr) && moduleNameExpr.text && (!inAmbientModule || !isExternalModuleNameRelative(moduleNameExpr.text))) { + imports = append(imports, moduleNameExpr); + } + } + else if (isModuleDeclaration(node)) { + if (isAmbientModule(node) && (inAmbientModule || hasModifier(node, ModifierFlags.Ambient) || file.isDeclarationFile)) { + const nameText = getTextOfIdentifierOrLiteral(node.name); + // Ambient module declarations can be interpreted as augmentations for some existing external modules. + // This will happen in two cases: + // - if current file is external module then module augmentation is a ambient module declaration defined in the top level scope + // - if current file is not external module then module augmentation is an ambient module declaration with non-relative module name + // immediately nested in top level ambient module declaration . + if (isExternalModuleFile || (inAmbientModule && !isExternalModuleNameRelative(nameText))) { + (moduleAugmentations || (moduleAugmentations = [])).push(node.name); } - if (!moduleNameExpr.text) { - break; - } - - // TypeScript 1.0 spec (April 2014): 12.1.6 - // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference other external modules - // only through top - level external module names. Relative external module names are not permitted. - if (!inAmbientModule || !isExternalModuleNameRelative(moduleNameExpr.text)) { - (imports || (imports = [])).push(moduleNameExpr); - } - break; - case SyntaxKind.ModuleDeclaration: - if (isAmbientModule(node) && (inAmbientModule || hasModifier(node, ModifierFlags.Ambient) || file.isDeclarationFile)) { - const moduleName = (node).name; - const nameText = getTextOfIdentifierOrLiteral(moduleName); - // Ambient module declarations can be interpreted as augmentations for some existing external modules. - // This will happen in two cases: - // - if current file is external module then module augmentation is a ambient module declaration defined in the top level scope - // - if current file is not external module then module augmentation is an ambient module declaration with non-relative module name - // immediately nested in top level ambient module declaration . - if (isExternalModuleFile || (inAmbientModule && !isExternalModuleNameRelative(nameText))) { - (moduleAugmentations || (moduleAugmentations = [])).push(moduleName); + else if (!inAmbientModule) { + if (file.isDeclarationFile) { + // for global .d.ts files record name of ambient module + (ambientModules || (ambientModules = [])).push(nameText); } - else if (!inAmbientModule) { - if (file.isDeclarationFile) { - // for global .d.ts files record name of ambient module - (ambientModules || (ambientModules = [])).push(nameText); - } - // An AmbientExternalModuleDeclaration declares an external module. - // This type of declaration is permitted only in the global module. - // The StringLiteral must specify a top - level external module name. - // Relative external module names are not permitted + // An AmbientExternalModuleDeclaration declares an external module. + // This type of declaration is permitted only in the global module. + // The StringLiteral must specify a top - level external module name. + // Relative external module names are not permitted - // NOTE: body of ambient module is always a module block, if it exists - const body = (node).body; - if (body) { - for (const statement of body.statements) { - collectModuleReferences(statement, /*inAmbientModule*/ true); - } + // NOTE: body of ambient module is always a module block, if it exists + const body = (node).body; + if (body) { + for (const statement of body.statements) { + collectModuleReferences(statement, /*inAmbientModule*/ true); } } } + } } } function collectDynamicImportOrRequireCalls(node: Node): void { - if (isRequireCall(node, /*checkArgumentIsStringLiteral*/ true)) { - (imports || (imports = [])).push((node).arguments[0]); + if (isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { + imports = append(imports, node.arguments[0]); } // we have to check the argument list has length of 1. We will still have to process these even though we have parsing error. - else if (isImportCall(node) && node.arguments.length === 1 && node.arguments[0].kind === SyntaxKind.StringLiteral) { - (imports || (imports = [])).push((node).arguments[0]); + else if (isImportCall(node) && node.arguments.length === 1 && isStringLiteralLike(node.arguments[0])) { + imports = append(imports, node.arguments[0] as StringLiteralLike); } else { forEachChild(node, collectDynamicImportOrRequireCalls); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0dca91f3fd4..7ed0f30916c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2568,7 +2568,7 @@ namespace ts { // Content of this field should never be used directly - use getResolvedModuleFileName/setResolvedModuleFileName functions instead /* @internal */ resolvedModules: Map; /* @internal */ resolvedTypeReferenceDirectiveNames: Map; - /* @internal */ imports: ReadonlyArray; + /* @internal */ imports: ReadonlyArray; // Identifier only if `declare global` /* @internal */ moduleAugmentations: ReadonlyArray; /* @internal */ patternAmbientModules?: PatternAmbientModule[]; @@ -3159,6 +3159,18 @@ namespace ts { /* @internal */ export type AnyImportSyntax = ImportDeclaration | ImportEqualsDeclaration; + /* @internal */ + export type AnyImportOrReExport = AnyImportSyntax | ExportDeclaration; + + /* @internal */ + export type AnyValidImportOrReExport = + | (ImportDeclaration | ExportDeclaration) & { moduleSpecifier: StringLiteral } + | ImportEqualsDeclaration & { moduleReference: ExternalModuleReference & { expression: StringLiteral } } + | RequireOrImportCall; + + /* @internal */ + export type RequireOrImportCall = CallExpression & { arguments: [StringLiteralLike] }; + /* @internal */ export interface SymbolVisibilityResult { accessibility: SymbolAccessibility; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 60b140adace..4418b6c922c 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -538,6 +538,10 @@ namespace ts { } } + export function isAnyImportOrReExport(node: Node): node is AnyImportOrReExport { + return isAnyImportSyntax(node) || isExportDeclaration(node); + } + // Gets the nearest enclosing block scope container that has the provided node // as a descendant, that is not the provided node. export function getEnclosingBlockScopeContainer(node: Node): Node { @@ -1452,9 +1456,9 @@ namespace ts { * exactly one argument (of the form 'require("name")'). * This function does not test if the node is in a JavaScript file or not. */ - export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteral: true): callExpression is CallExpression & { expression: Identifier, arguments: [StringLiteralLike] }; - export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteral: boolean): callExpression is CallExpression; - export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteral: boolean): callExpression is CallExpression { + export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteralLike: true): callExpression is RequireOrImportCall & { expression: Identifier, arguments: [StringLiteralLike] }; + export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteralLike: boolean): callExpression is CallExpression; + export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteralLike: boolean): callExpression is CallExpression { if (callExpression.kind !== SyntaxKind.CallExpression) { return false; } @@ -1468,14 +1472,14 @@ namespace ts { return false; } const arg = args[0]; - return !checkArgumentIsStringLiteral || arg.kind === SyntaxKind.StringLiteral || arg.kind === SyntaxKind.NoSubstitutionTemplateLiteral; + return !checkArgumentIsStringLiteralLike || isStringLiteralLike(arg); } export function isSingleOrDoubleQuote(charCode: number) { return charCode === CharacterCodes.singleQuote || charCode === CharacterCodes.doubleQuote; } - export function isStringDoubleQuoted(str: StringLiteral, sourceFile: SourceFile): boolean { + export function isStringDoubleQuoted(str: StringLiteralLike, sourceFile: SourceFile): boolean { return getSourceTextOfNodeFromSourceFile(sourceFile, str).charCodeAt(0) === CharacterCodes.doubleQuote; } @@ -1658,21 +1662,29 @@ namespace ts { !!getJSDocTypeTag(expr.parent); } - export function getExternalModuleName(node: Node): Expression { - if (node.kind === SyntaxKind.ImportDeclaration) { - return (node).moduleSpecifier; + export function importFromModuleSpecifier(node: StringLiteralLike): AnyValidImportOrReExport { + switch (node.parent.kind) { + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + return node.parent as AnyValidImportOrReExport; + case SyntaxKind.ExternalModuleReference: + return (node.parent as ExternalModuleReference).parent as AnyValidImportOrReExport; + case SyntaxKind.CallExpression: + return node.parent as AnyValidImportOrReExport; + default: + return Debug.fail(Debug.showSyntaxKind(node)); } - if (node.kind === SyntaxKind.ImportEqualsDeclaration) { - const reference = (node).moduleReference; - if (reference.kind === SyntaxKind.ExternalModuleReference) { - return reference.expression; - } - } - if (node.kind === SyntaxKind.ExportDeclaration) { - return (node).moduleSpecifier; - } - if (isModuleWithStringLiteralName(node)) { - return node.name; + } + + export function getExternalModuleName(node: AnyImportOrReExport): Expression { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + return node.moduleSpecifier; + case SyntaxKind.ImportEqualsDeclaration: + return node.moduleReference.kind === SyntaxKind.ExternalModuleReference ? node.moduleReference.expression : undefined; + default: + return Debug.assertNever(node); } } diff --git a/src/services/codefixes/convertToEs6Module.ts b/src/services/codefixes/convertToEs6Module.ts index fcde80e81fb..c998c6940c5 100644 --- a/src/services/codefixes/convertToEs6Module.ts +++ b/src/services/codefixes/convertToEs6Module.ts @@ -20,25 +20,22 @@ namespace ts.codefix { function fixImportOfModuleExports(importingFile: SourceFile, exportingFile: SourceFile, changes: textChanges.ChangeTracker) { for (const moduleSpecifier of importingFile.imports) { - const imported = getResolvedModule(importingFile, moduleSpecifier.text); + const { text } = moduleSpecifier; + const imported = getResolvedModule(importingFile, text); if (!imported || imported.resolvedFileName !== exportingFile.fileName) { continue; } - const { parent } = moduleSpecifier; - switch (parent.kind) { - case SyntaxKind.ExternalModuleReference: { - const importEq = (parent as ExternalModuleReference).parent; - changes.replaceNode(importingFile, importEq, makeImport(importEq.name, /*namedImports*/ undefined, moduleSpecifier.text)); + const importNode = importFromModuleSpecifier(moduleSpecifier); + switch (importNode.kind) { + case SyntaxKind.ImportEqualsDeclaration: + changes.replaceNode(importingFile, importNode, makeImport(importNode.name, /*namedImports*/ undefined, text)); break; - } - case SyntaxKind.CallExpression: { - const call = parent as CallExpression; - if (isRequireCall(call, /*checkArgumentIsStringLiteral*/ false)) { - changes.replaceNode(importingFile, parent, createPropertyAccess(getSynthesizedDeepClone(call), "default")); + case SyntaxKind.CallExpression: + if (isRequireCall(importNode, /*checkArgumentIsStringLiteralLike*/ false)) { + changes.replaceNode(importingFile, importNode, createPropertyAccess(getSynthesizedDeepClone(importNode), "default")); } break; - } } } } @@ -112,7 +109,7 @@ namespace ts.codefix { const { expression } = statement as ExpressionStatement; switch (expression.kind) { case SyntaxKind.CallExpression: { - if (isRequireCall(expression, /*checkArgumentIsStringLiteral*/ true)) { + if (isRequireCall(expression, /*checkArgumentIsStringLiteralLike*/ true)) { // For side-effecting require() call, just make a side-effecting import. changes.replaceNode(sourceFile, statement, makeImport(/*name*/ undefined, /*namedImports*/ undefined, expression.arguments[0].text)); } @@ -140,11 +137,11 @@ namespace ts.codefix { foundImport = true; return []; } - if (isRequireCall(initializer, /*checkArgumentIsStringLiteral*/ true)) { + if (isRequireCall(initializer, /*checkArgumentIsStringLiteralLike*/ true)) { foundImport = true; return convertSingleImport(sourceFile, name, initializer.arguments[0].text, changes, checker, identifiers, target); } - else if (isPropertyAccessExpression(initializer) && isRequireCall(initializer.expression, /*checkArgumentIsStringLiteral*/ true)) { + else if (isPropertyAccessExpression(initializer) && isRequireCall(initializer.expression, /*checkArgumentIsStringLiteralLike*/ true)) { foundImport = true; return convertPropertyAccessImport(name, initializer.name.text, initializer.expression.arguments[0].text, identifiers); } @@ -279,7 +276,7 @@ namespace ts.codefix { return [[classExpressionToDeclaration(cls.name && cls.name.text, modifiers, cls)], true]; } case SyntaxKind.CallExpression: - if (isRequireCall(exported, /*checkArgumentIsStringLiteral*/ true)) { + if (isRequireCall(exported, /*checkArgumentIsStringLiteralLike*/ true)) { return convertReExportAll(exported.arguments[0], checker); } // falls through diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index b0366a58980..b571e7a9902 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -158,29 +158,15 @@ namespace ts.codefix { const moduleSymbolId = getUniqueSymbolId(moduleSymbol, checker); let cached = cachedImportDeclarations[moduleSymbolId]; if (!cached) { - cached = cachedImportDeclarations[moduleSymbolId] = mapDefined(imports, importModuleSpecifier => { - const declaration = checker.getSymbolAtLocation(importModuleSpecifier) === moduleSymbol ? getImportDeclaration(importModuleSpecifier) : undefined; - return declaration && { declaration, importKind }; + cached = cachedImportDeclarations[moduleSymbolId] = mapDefined(imports, moduleSpecifier => { + const i = importFromModuleSpecifier(moduleSpecifier); + return (i.kind === SyntaxKind.ImportDeclaration || i.kind === SyntaxKind.ImportEqualsDeclaration) + && checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind } : undefined; }); } return cached; } - function getImportDeclaration({ parent }: LiteralExpression): AnyImportSyntax | undefined { - switch (parent.kind) { - case SyntaxKind.ImportDeclaration: - return parent as ImportDeclaration; - case SyntaxKind.ExternalModuleReference: - return (parent as ExternalModuleReference).parent; - case SyntaxKind.ExportDeclaration: - case SyntaxKind.CallExpression: // For "require()" calls - // Ignore these, can't add imports to them. - return undefined; - default: - Debug.fail(); - } - } - function getCodeActionForNewImport(context: SymbolContext, { moduleSpecifier, importKind }: NewImportInfo): CodeFixAction { const { sourceFile, symbolName } = context; const lastImportDeclaration = findLast(sourceFile.statements, isAnyImportSyntax); diff --git a/src/services/completions.ts b/src/services/completions.ts index a376ad93ffc..9402fd4f38f 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -406,7 +406,7 @@ namespace ts.Completions { case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: - if (!isRequireCall(node.parent, /*checkArgumentIsStringLiteral*/ false) && !isImportCall(node.parent)) { + if (!isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false) && !isImportCall(node.parent)) { const argumentInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(node, position, sourceFile); // Get string literal completions from specialized signatures of the target // i.e. declare function f(a: 'A'); diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 3c34bce4526..eb2998c6e40 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -278,7 +278,7 @@ namespace ts.FindAllReferences.Core { case SyntaxKind.ExportDeclaration: return true; case SyntaxKind.CallExpression: - return isRequireCall(node.parent as CallExpression, /*checkArgumentIsStringLiteral*/ false) || isImportCall(node.parent as CallExpression); + return isRequireCall(node.parent as CallExpression, /*checkArgumentIsStringLiteralLike*/ false) || isImportCall(node.parent as CallExpression); default: return false; } diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index 807506af24e..b5ffaaf9cea 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -33,17 +33,17 @@ namespace ts.FindAllReferences { interface AmbientModuleDeclaration extends ModuleDeclaration { body?: ModuleBlock; } type SourceFileLike = SourceFile | AmbientModuleDeclaration; // Identifier for the case of `const x = require("y")`. - type Importer = AnyImportSyntax | Identifier | ExportDeclaration; + type Importer = AnyImportOrReExport | Identifier; type ImporterOrCallExpression = Importer | CallExpression; /** Returns import statements that directly reference the exporting module, and a list of files that may access the module through a namespace. */ function getImportersForExport( - sourceFiles: ReadonlyArray, - allDirectImports: Map, - { exportingModuleSymbol, exportKind }: ExportInfo, - checker: TypeChecker, - cancellationToken: CancellationToken - ): { directImports: Importer[], indirectUsers: ReadonlyArray } { + sourceFiles: ReadonlyArray, + allDirectImports: Map, + { exportingModuleSymbol, exportKind }: ExportInfo, + checker: TypeChecker, + cancellationToken: CancellationToken + ): { directImports: Importer[], indirectUsers: ReadonlyArray } { const markSeenDirectImport = nodeSeenTracker(); const markSeenIndirectUser = nodeSeenTracker(); const directImports: Importer[] = []; @@ -321,7 +321,7 @@ namespace ts.FindAllReferences { export type ModuleReference = /** "import" also includes require() calls. */ - | { kind: "import", literal: StringLiteral } + | { kind: "import", literal: StringLiteralLike } /** or */ | { kind: "reference", referencingFile: SourceFile, ref: FileReference }; export function findModuleReferences(program: Program, sourceFiles: ReadonlyArray, searchModuleSymbol: Symbol): ModuleReference[] { @@ -382,10 +382,10 @@ namespace ts.FindAllReferences { } /** Calls `action` for each import, re-export, or require() in a file. */ - function forEachImport(sourceFile: SourceFile, action: (importStatement: ImporterOrCallExpression, imported: StringLiteral) => void): void { + function forEachImport(sourceFile: SourceFile, action: (importStatement: ImporterOrCallExpression, imported: StringLiteralLike) => void): void { if (sourceFile.externalModuleIndicator || sourceFile.imports !== undefined) { - for (const moduleSpecifier of sourceFile.imports) { - action(importerFromModuleSpecifier(moduleSpecifier), moduleSpecifier); + for (const i of sourceFile.imports) { + action(importFromModuleSpecifier(i), i); } } else { @@ -414,20 +414,6 @@ namespace ts.FindAllReferences { } } - function importerFromModuleSpecifier(moduleSpecifier: StringLiteral): ImporterOrCallExpression { - const decl = moduleSpecifier.parent; - switch (decl.kind) { - case SyntaxKind.CallExpression: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportDeclaration: - return decl as ImportDeclaration | ExportDeclaration | CallExpression; - case SyntaxKind.ExternalModuleReference: - return (decl as ExternalModuleReference).parent; - default: - Debug.fail("Unexpected module specifier parent: " + decl.kind); - } - } - export interface ImportedSymbol { kind: ImportExport.Import; symbol: Symbol; diff --git a/src/services/services.ts b/src/services/services.ts index ed8e79a9069..894447b9bed 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -646,7 +646,7 @@ namespace ts { public nameTable: UnderscoreEscapedMap; public resolvedModules: Map; public resolvedTypeReferenceDirectiveNames: Map; - public imports: StringLiteral[]; + public imports: ReadonlyArray; public moduleAugmentations: StringLiteral[]; private namedDeclarations: Map; public ambientModuleNames: string[]; diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 3de43d7d40f..1fc1d671e43 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -33,10 +33,11 @@ namespace ts { check(sourceFile); if (getAllowSyntheticDefaultImports(program.getCompilerOptions())) { - for (const importNode of sourceFile.imports) { - const name = importNameForConvertToDefaultImport(importNode.parent); + for (const moduleSpecifier of sourceFile.imports) { + const importNode = importFromModuleSpecifier(moduleSpecifier); + const name = importNameForConvertToDefaultImport(importNode); if (!name) continue; - const module = getResolvedModule(sourceFile, importNode.text); + const module = getResolvedModule(sourceFile, moduleSpecifier.text); const resolvedFile = module && program.getSourceFile(module.resolvedFileName); if (resolvedFile && resolvedFile.externalModuleIndicator && isExportAssignment(resolvedFile.externalModuleIndicator) && resolvedFile.externalModuleIndicator.isExportEquals) { diags.push(createDiagnosticForNode(name, Diagnostics.Import_may_be_converted_to_a_default_import)); @@ -47,15 +48,17 @@ namespace ts { return diags.concat(checker.getSuggestionDiagnostics(sourceFile)); } - function importNameForConvertToDefaultImport(node: Node): Identifier | undefined { - if (isExternalModuleReference(node)) { - return node.parent.name; - } - if (isImportDeclaration(node)) { - const { importClause, moduleSpecifier } = node; - return importClause && !importClause.name && importClause.namedBindings.kind === SyntaxKind.NamespaceImport && isStringLiteral(moduleSpecifier) - ? importClause.namedBindings.name - : undefined; + function importNameForConvertToDefaultImport(node: AnyValidImportOrReExport): Identifier | undefined { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + const { importClause, moduleSpecifier } = node; + return importClause && !importClause.name && importClause.namedBindings.kind === SyntaxKind.NamespaceImport && isStringLiteral(moduleSpecifier) + ? importClause.namedBindings.name + : undefined; + case SyntaxKind.ImportEqualsDeclaration: + return node.name; + default: + return undefined; } } } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3b4767a8a5d..0f56329ddf9 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3651,7 +3651,7 @@ declare namespace ts { function updateNamespaceExportDeclaration(node: NamespaceExportDeclaration, name: Identifier): NamespaceExportDeclaration; function createImportEqualsDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration; function updateImportEqualsDeclaration(node: ImportEqualsDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration; - function createImportDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, importClause: ImportClause | undefined, moduleSpecifier?: Expression): ImportDeclaration; + function createImportDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression): ImportDeclaration; function updateImportDeclaration(node: ImportDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression | undefined): ImportDeclaration; function createImportClause(name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause; function updateImportClause(node: ImportClause, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 4d17de3ca42..8fd304658a8 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3598,7 +3598,7 @@ declare namespace ts { function updateNamespaceExportDeclaration(node: NamespaceExportDeclaration, name: Identifier): NamespaceExportDeclaration; function createImportEqualsDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration; function updateImportEqualsDeclaration(node: ImportEqualsDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration; - function createImportDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, importClause: ImportClause | undefined, moduleSpecifier?: Expression): ImportDeclaration; + function createImportDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression): ImportDeclaration; function updateImportDeclaration(node: ImportDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression | undefined): ImportDeclaration; function createImportClause(name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause; function updateImportClause(node: ImportClause, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause;