Basic parsing/emitting support for 'import.meta'.

This commit is contained in:
Daniel Rosenwasser 2018-04-09 17:03:05 -07:00
parent 9b8670cf92
commit 9e2bbb6f8a
3 changed files with 85 additions and 32 deletions

View File

@ -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 = <TypeLiteralNode>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<PrimaryExpression>();
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<PrimaryExpression>();
}
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 && (<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 && (<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 {

View File

@ -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;
}

View File

@ -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;
}