Skip to content

Commit

Permalink
Merge pull request #23327 from Microsoft/importDotMeta
Browse files Browse the repository at this point in the history
Support 'import.meta'
  • Loading branch information
DanielRosenwasser authored Apr 28, 2018
2 parents 8c5ad24 + 12a3e39 commit 443c1c7
Show file tree
Hide file tree
Showing 18 changed files with 1,146 additions and 51 deletions.
58 changes: 46 additions & 12 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ namespace ts {

const compilerOptions = host.getCompilerOptions();
const languageVersion = getEmitScriptTarget(compilerOptions);
const modulekind = getEmitModuleKind(compilerOptions);
const moduleKind = getEmitModuleKind(compilerOptions);
const allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions);
const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks");
const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes");
Expand Down Expand Up @@ -443,6 +443,7 @@ namespace ts {
let deferredGlobalAsyncIteratorType: GenericType;
let deferredGlobalAsyncIterableIteratorType: GenericType;
let deferredGlobalTemplateStringsArrayType: ObjectType;
let deferredGlobalImportMetaType: ObjectType;
let deferredGlobalExtractSymbol: Symbol;

let deferredNodes: Node[];
Expand Down Expand Up @@ -1094,7 +1095,7 @@ namespace ts {
const declarationFile = getSourceFileOfNode(declaration);
const useFile = getSourceFileOfNode(usage);
if (declarationFile !== useFile) {
if ((modulekind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) ||
if ((moduleKind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) ||
(!compilerOptions.outFile && !compilerOptions.out) ||
isInTypeQuery(usage) ||
declaration.flags & NodeFlags.Ambient) {
Expand Down Expand Up @@ -7860,6 +7861,10 @@ namespace ts {
return deferredGlobalTemplateStringsArrayType || (deferredGlobalTemplateStringsArrayType = getGlobalType("TemplateStringsArray" as __String, /*arity*/ 0, /*reportErrors*/ true)) || emptyObjectType;
}

function getGlobalImportMetaType() {
return deferredGlobalImportMetaType || (deferredGlobalImportMetaType = getGlobalType("ImportMeta" as __String, /*arity*/ 0, /*reportErrors*/ true)) || emptyObjectType;
}

function getGlobalESSymbolConstructorSymbol(reportErrors: boolean) {
return deferredGlobalESSymbolConstructorSymbol || (deferredGlobalESSymbolConstructorSymbol = getGlobalValueSymbol("Symbol" as __String, reportErrors));
}
Expand Down Expand Up @@ -18905,6 +18910,17 @@ namespace ts {

function checkMetaProperty(node: MetaProperty) {
checkGrammarMetaProperty(node);

if (node.keywordToken === SyntaxKind.NewKeyword) {
return checkNewTargetMetaProperty(node);
}

if (node.keywordToken === SyntaxKind.ImportKeyword) {
return checkImportMetaProperty(node);
}
}

function checkNewTargetMetaProperty(node: MetaProperty) {
const container = getNewTargetContainer(node);
if (!container) {
error(node, Diagnostics.Meta_property_0_is_only_allowed_in_the_body_of_a_function_declaration_function_expression_or_constructor, "new.target");
Expand All @@ -18920,6 +18936,16 @@ namespace ts {
}
}

function checkImportMetaProperty(node: MetaProperty) {
if (languageVersion < ScriptTarget.ESNext || moduleKind < ModuleKind.ESNext) {
error(node, Diagnostics.The_import_meta_meta_property_is_only_allowed_using_ESNext_for_the_target_and_module_compiler_options);
}
const file = getSourceFileOfNode(node);
Debug.assert(!!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag.");
Debug.assert(!!file.externalModuleIndicator, "Containing file should be a module.");
return node.name.escapedText === "meta" ? getGlobalImportMetaType() : unknownType;
}

function getTypeOfParameter(symbol: Symbol) {
const type = getTypeOfSymbol(symbol);
if (strictNullChecks) {
Expand Down Expand Up @@ -22542,7 +22568,7 @@ namespace ts {

function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier) {
// No need to check for require or exports for ES6 modules and later
if (modulekind >= ModuleKind.ES2015 || compilerOptions.noEmit) {
if (moduleKind >= ModuleKind.ES2015 || compilerOptions.noEmit) {
return;
}

Expand Down Expand Up @@ -24738,7 +24764,7 @@ namespace ts {
}
}
else {
if (modulekind >= ModuleKind.ES2015 && !(node.flags & NodeFlags.Ambient)) {
if (moduleKind >= ModuleKind.ES2015 && !(node.flags & NodeFlags.Ambient)) {
// Import equals declaration is deprecated in es6 or above
grammarErrorOnNode(node, Diagnostics.Import_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead);
}
Expand Down Expand Up @@ -24776,7 +24802,7 @@ namespace ts {
error(node.moduleSpecifier, Diagnostics.Module_0_uses_export_and_cannot_be_used_with_export_Asterisk, symbolToString(moduleSymbol));
}

if (modulekind !== ModuleKind.System && modulekind !== ModuleKind.ES2015 && modulekind !== ModuleKind.ESNext) {
if (moduleKind !== ModuleKind.System && moduleKind !== ModuleKind.ES2015 && moduleKind !== ModuleKind.ESNext) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.ExportStar);
}
}
Expand Down Expand Up @@ -24849,11 +24875,11 @@ namespace ts {
}

if (node.isExportEquals && !(node.flags & NodeFlags.Ambient)) {
if (modulekind >= ModuleKind.ES2015) {
if (moduleKind >= ModuleKind.ES2015) {
// export assignment is not supported in es6 modules
grammarErrorOnNode(node, Diagnostics.Export_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_export_default_or_another_module_format_instead);
}
else if (modulekind === ModuleKind.System) {
else if (moduleKind === ModuleKind.System) {
// system modules does not support export assignment
grammarErrorOnNode(node, Diagnostics.Export_assignment_is_not_supported_when_module_flag_is_system);
}
Expand Down Expand Up @@ -27801,10 +27827,18 @@ namespace ts {
}

function checkGrammarMetaProperty(node: MetaProperty) {
if (node.keywordToken === SyntaxKind.NewKeyword) {
if (node.name.escapedText !== "target") {
return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "target");
}
const escapedText = node.name.escapedText;
switch (node.keywordToken) {
case SyntaxKind.NewKeyword:
if (escapedText !== "target") {
return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "target");
}
break;
case SyntaxKind.ImportKeyword:
if (escapedText !== "meta") {
return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "meta");
}
break;
}
}

Expand Down Expand Up @@ -28003,7 +28037,7 @@ namespace ts {
}

function checkGrammarImportCallExpression(node: ImportCall): boolean {
if (modulekind === ModuleKind.ES2015) {
if (moduleKind === ModuleKind.ES2015) {
return grammarErrorOnNode(node, Diagnostics.Dynamic_import_cannot_be_used_when_targeting_ECMAScript_2015_modules);
}

Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,10 @@
"category": "Error",
"code": 1342
},
"The 'import.meta' meta-property is only allowed using 'ESNext' for the 'target' and 'module' compiler options.": {
"category": "Error",
"code": 1343
},

"Duplicate identifier '{0}'.": {
"category": "Error",
Expand Down
92 changes: 72 additions & 20 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,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;
}

Expand Down Expand Up @@ -2625,6 +2625,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();
Expand Down Expand Up @@ -3093,7 +3107,7 @@ namespace ts {
case SyntaxKind.Identifier:
return true;
case SyntaxKind.ImportKeyword:
return lookAhead(nextTokenIsOpenParenOrLessThan);
return lookAhead(nextTokenIsOpenParenOrLessThanOrDot);
default:
return isIdentifier();
}
Expand Down Expand Up @@ -3955,14 +3969,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();
Expand Down Expand Up @@ -4523,7 +4554,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) {
Expand Down Expand Up @@ -4674,7 +4705,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)) {
Expand Down Expand Up @@ -5122,7 +5153,7 @@ namespace ts {
return true;

case SyntaxKind.ImportKeyword:
return isStartOfDeclaration() || lookAhead(nextTokenIsOpenParenOrLessThan);
return isStartOfDeclaration() || lookAhead(nextTokenIsOpenParenOrLessThanOrDot);

case SyntaxKind.ConstKeyword:
case SyntaxKind.ExportKeyword:
Expand Down Expand Up @@ -6108,14 +6139,35 @@ 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
// Try to use the first top-level import/export when available, then
// fall back to looking for an 'import.meta' somewhere in the tree if necessary.
sourceFile.externalModuleIndicator =
forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) ||
getImportMetaIfNecessary(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
? node
: undefined);
: undefined;
}

function getImportMetaIfNecessary(sourceFile: SourceFile) {
return sourceFile.flags & NodeFlags.PossiblyContainsImportMeta ?
walkTreeForExternalModuleIndicators(sourceFile) :
undefined;
}

function walkTreeForExternalModuleIndicators(node: Node): Node {
return isImportMeta(node) ? node : forEachChild(node, walkTreeForExternalModuleIndicators);
}

function isImportMeta(node: Node): boolean {
return isMetaProperty(node) && node.keywordToken === SyntaxKind.ImportKeyword && node.name.escapedText === "meta";
}

const enum ParsingContext {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,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;
}
Expand Down
Loading

0 comments on commit 443c1c7

Please sign in to comment.