diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 0b89c3c313c..e9a685d9d26 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -542,7 +542,7 @@ namespace ts { const newSourceFile = IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); // Because new source file node is created, it may not have the flag PossiblyContainDynamicImport. This is the case if there is no new edit to add dynamic import. // We will manually port the flag to the new source file. - newSourceFile.flags |= (sourceFile.flags & NodeFlags.PossiblyContainsDynamicImport); + newSourceFile.flags |= (sourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags); return newSourceFile; } @@ -2627,6 +2627,20 @@ namespace ts { return token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken; } + function nextTokenIsDot() { + return nextToken() === SyntaxKind.DotToken; + } + + function nextTokenIsOpenParenOrLessThanOrDot() { + switch (nextToken()) { + case SyntaxKind.OpenParenToken: + case SyntaxKind.LessThanToken: + case SyntaxKind.DotToken: + return true; + } + return false; + } + function parseTypeLiteral(): TypeLiteralNode { const node = createNode(SyntaxKind.TypeLiteral); node.members = parseObjectTypeMembers(); @@ -3095,7 +3109,7 @@ namespace ts { case SyntaxKind.Identifier: return true; case SyntaxKind.ImportKeyword: - return lookAhead(nextTokenIsOpenParenOrLessThan); + return lookAhead(nextTokenIsOpenParenOrLessThanOrDot); default: return isIdentifier(); } @@ -3957,14 +3971,31 @@ namespace ts { // 3)we have a MemberExpression which either completes the LeftHandSideExpression, // or starts the beginning of the first four CallExpression productions. let expression: MemberExpression; - if (token() === SyntaxKind.ImportKeyword && lookAhead(nextTokenIsOpenParenOrLessThan)) { - // We don't want to eagerly consume all import keyword as import call expression so we look a head to find "(" - // For example: - // var foo3 = require("subfolder - // import * as foo1 from "module-from-node - // We want this import to be a statement rather than import call expression - sourceFile.flags |= NodeFlags.PossiblyContainsDynamicImport; - expression = parseTokenNode(); + if (token() === SyntaxKind.ImportKeyword) { + if (lookAhead(nextTokenIsOpenParenOrLessThan)) { + // We don't want to eagerly consume all import keyword as import call expression so we look ahead to find "(" + // For example: + // var foo3 = require("subfolder + // import * as foo1 from "module-from-node + // We want this import to be a statement rather than import call expression + sourceFile.flags |= NodeFlags.PossiblyContainsDynamicImport; + expression = parseTokenNode(); + } + else if (lookAhead(nextTokenIsDot)) { + // This is an 'import.*' metaproperty (i.e. 'import.meta') + const fullStart = scanner.getStartPos(); + nextToken(); // advance past the 'import' + nextToken(); // advance past the dot + const node = createNode(SyntaxKind.MetaProperty, fullStart) as MetaProperty; + node.keywordToken = SyntaxKind.ImportKeyword; + node.name = parseIdentifierName(); + expression = finishNode(node); + + sourceFile.flags |= NodeFlags.PossiblyContainsImportMeta; + } + else { + expression = parseMemberExpressionOrHigher(); + } } else { expression = token() === SyntaxKind.SuperKeyword ? parseSuperExpression() : parseMemberExpressionOrHigher(); @@ -4508,7 +4539,7 @@ namespace ts { case SyntaxKind.FunctionKeyword: return parseFunctionExpression(); case SyntaxKind.NewKeyword: - return parseNewExpression(); + return parseNewExpressionOrNewDotTarget(); case SyntaxKind.SlashToken: case SyntaxKind.SlashEqualsToken: if (reScanSlashToken() === SyntaxKind.RegularExpressionLiteral) { @@ -4659,7 +4690,7 @@ namespace ts { return isIdentifier() ? parseIdentifier() : undefined; } - function parseNewExpression(): NewExpression | MetaProperty { + function parseNewExpressionOrNewDotTarget(): NewExpression | MetaProperty { const fullStart = scanner.getStartPos(); parseExpected(SyntaxKind.NewKeyword); if (parseOptional(SyntaxKind.DotToken)) { @@ -5093,7 +5124,7 @@ namespace ts { return true; case SyntaxKind.ImportKeyword: - return isStartOfDeclaration() || lookAhead(nextTokenIsOpenParenOrLessThan); + return isStartOfDeclaration() || lookAhead(nextTokenIsOpenParenOrLessThanOrDot); case SyntaxKind.ConstKeyword: case SyntaxKind.ExportKeyword: @@ -6079,14 +6110,29 @@ namespace ts { } function setExternalModuleIndicator(sourceFile: SourceFile) { - sourceFile.externalModuleIndicator = forEach(sourceFile.statements, node => - hasModifier(node, ModifierFlags.Export) - || node.kind === SyntaxKind.ImportEqualsDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference - || node.kind === SyntaxKind.ImportDeclaration - || node.kind === SyntaxKind.ExportAssignment - || node.kind === SyntaxKind.ExportDeclaration + // Usually we'd like to avoid a full tree walk, but it's possible + // that we have a deeper external module indicator (e.g. `import.meta`, + // and possibly nested import statements in the future). + // Ideally the first few statements will be an import/export anyway. + sourceFile.externalModuleIndicator = + !(sourceFile.flags & NodeFlags.PossiblyContainsImportMeta) ? + forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) : + walkTreeForExternalModuleIndicators(sourceFile); + } + + function isAnExternalModuleIndicatorNode(node: Node) { + return hasModifier(node, ModifierFlags.Export) + || node.kind === SyntaxKind.ImportEqualsDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference + || node.kind === SyntaxKind.ImportDeclaration + || node.kind === SyntaxKind.ExportAssignment + || node.kind === SyntaxKind.ExportDeclaration + || isMetaProperty(node) && node.keywordToken === SyntaxKind.ImportKeyword && node.name.escapedText === "meta" ? node - : undefined); + : undefined + } + + function walkTreeForExternalModuleIndicators(node: Node): Node { + return isAnExternalModuleIndicatorNode(node) ? node : forEachChild(node, walkTreeForExternalModuleIndicators); } const enum ParsingContext { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 31220c68d3c..57c69b77bb9 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -989,7 +989,7 @@ namespace ts { // moduleAugmentations has changed oldProgram.structureIsReused = StructureIsReused.SafeModules; } - if ((oldSourceFile.flags & NodeFlags.PossiblyContainsDynamicImport) !== (newSourceFile.flags & NodeFlags.PossiblyContainsDynamicImport)) { + if ((oldSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags) !== (newSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags)) { // dynamicImport has changed oldProgram.structureIsReused = StructureIsReused.SafeModules; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ac32d91c61a..42cf1325551 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -490,19 +490,21 @@ namespace ts { ThisNodeOrAnySubNodesHasError = 1 << 17, // If this node or any of its children had an error HasAggregatedChildData = 1 << 18, // If we've computed data from children and cached it in this node - // This flag will be set when the parser encounters a dynamic import expression so that module resolution - // will not have to walk the tree if the flag is not set. However, this flag is just a approximation because - // once it is set, the flag never gets cleared (hence why it's named "PossiblyContainsDynamicImport"). - // During editing, if dynamic import is removed, incremental parsing will *NOT* update this flag. This means that the tree will always be traversed - // during module resolution. However, the removal operation should not occur often and in the case of the + // These flags will be set when the parser encounters a dynamic import expression or 'import.meta' to avoid + // walking the tree if the flags are not set. However, these flags are just a approximation + // (hence why it's named "PossiblyContainsDynamicImport") because once set, the flags never get cleared. + // During editing, if a dynamic import is removed, incremental parsing will *NOT* clear this flag. + // This means that the tree will always be traversed during module resolution, or when looking for external module indicators. + // However, the removal operation should not occur often and in the case of the // removal, it is likely that users will add the import anyway. // The advantage of this approach is its simplicity. For the case of batch compilation, // we guarantee that users won't have to pay the price of walking the tree if a dynamic import isn't used. - /* @internal */ - PossiblyContainsDynamicImport = 1 << 19, - JSDoc = 1 << 20, // If node was parsed inside jsdoc - /* @internal */ Ambient = 1 << 21, // If node was inside an ambient context -- a declaration file, or inside something with the `declare` modifier. - /* @internal */ InWithStatement = 1 << 22, // If any ancestor of node was the `statement` of a WithStatement (not the `expression`) + /* @internal */ PossiblyContainsDynamicImport = 1 << 19, + /* @internal */ PossiblyContainsImportMeta = 1 << 20, + + JSDoc = 1 << 21, // If node was parsed inside jsdoc + /* @internal */ Ambient = 1 << 22, // If node was inside an ambient context -- a declaration file, or inside something with the `declare` modifier. + /* @internal */ InWithStatement = 1 << 23, // If any ancestor of node was the `statement` of a WithStatement (not the `expression`) BlockScoped = Let | Const, @@ -514,6 +516,11 @@ namespace ts { // Exclude these flags when parsing a Type TypeExcludesFlags = YieldContext | AwaitContext, + + // Represents all flags that are potentially set once and + // never cleared on SourceFiles which get re-used in between incremental parses. + // See the comment above on `PossiblyContainsDynamicImport` and `PossiblyContainsImportMeta`. + /* @internal */ PermanentlySetIncrementalFlags = PossiblyContainsDynamicImport | PossiblyContainsImportMeta, } export const enum ModifierFlags { @@ -1754,7 +1761,7 @@ namespace ts { // for the same reasons we treat NewExpression as a PrimaryExpression. export interface MetaProperty extends PrimaryExpression { kind: SyntaxKind.MetaProperty; - keywordToken: SyntaxKind.NewKeyword; + keywordToken: SyntaxKind.NewKeyword | SyntaxKind.ImportKeyword; name: Identifier; }