Skip to content

Commit

Permalink
Import assertion (#40698)
Browse files Browse the repository at this point in the history
* Add parsing

* fix all api

* check gramma of import call

* Add more part of assertion

* Add some case

* Add baseline

* use module insted of target

* strip assertion in d.ts

* Update new baseline

* accept baseline

* Revert error number changes

* Update diagnostic message

* Accept baseline

* rename path

* Fix cr issues

* Accept baseline

* Accept baseline

* Error if assertion and typeonly import

* Accept baseline

* Make lint happy

* Add some comment

* Fix cr issues

* Fix more issue

* Incorporate PR feedback, fix module resolution for import()

* Add contextual type and completions for ImportCall options argument

Co-authored-by: Ron Buckton <ron.buckton@microsoft.com>
  • Loading branch information
Kingwl and rbuckton authored Sep 20, 2021
1 parent 5ef0439 commit ec114b8
Show file tree
Hide file tree
Showing 86 changed files with 2,954 additions and 605 deletions.
79 changes: 62 additions & 17 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,7 @@ namespace ts {
let deferredGlobalTemplateStringsArrayType: ObjectType | undefined;
let deferredGlobalImportMetaType: ObjectType;
let deferredGlobalImportMetaExpressionType: ObjectType;
let deferredGlobalImportCallOptionsType: ObjectType | undefined;
let deferredGlobalExtractSymbol: Symbol | undefined;
let deferredGlobalOmitSymbol: Symbol | undefined;
let deferredGlobalAwaitedSymbol: Symbol | undefined;
Expand Down Expand Up @@ -6568,7 +6569,7 @@ namespace ts {

function inlineExportModifiers(statements: Statement[]) {
// Pass 3: Move all `export {}`'s to `export` modifiers where possible
const index = findIndex(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !!d.exportClause && isNamedExports(d.exportClause));
const index = findIndex(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !d.assertClause && !!d.exportClause && isNamedExports(d.exportClause));
if (index >= 0) {
const exportDecl = statements[index] as ExportDeclaration & { readonly exportClause: NamedExports };
const replacements = mapDefined(exportDecl.exportClause.elements, e => {
Expand Down Expand Up @@ -6600,7 +6601,8 @@ namespace ts {
exportDecl.exportClause,
replacements
),
exportDecl.moduleSpecifier
exportDecl.moduleSpecifier,
exportDecl.assertClause
);
}
}
Expand Down Expand Up @@ -7260,7 +7262,8 @@ namespace ts {
propertyName && isIdentifier(propertyName) ? factory.createIdentifier(idText(propertyName)) : undefined,
factory.createIdentifier(localName)
)])),
factory.createStringLiteral(specifier)
factory.createStringLiteral(specifier),
/*importClause*/ undefined
), ModifierFlags.None);
break;
}
Expand Down Expand Up @@ -7336,15 +7339,17 @@ namespace ts {
// We use `target.parent || target` below as `target.parent` is unset when the target is a module which has been export assigned
// And then made into a default by the `esModuleInterop` or `allowSyntheticDefaultImports` flag
// In such cases, the `target` refers to the module itself already
factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context))
factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)),
/*assertClause*/ undefined
), ModifierFlags.None);
break;
case SyntaxKind.NamespaceImport:
addResult(factory.createImportDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
factory.createImportClause(/*isTypeOnly*/ false, /*importClause*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName))),
factory.createStringLiteral(getSpecifierForModuleSymbol(target, context))
factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)),
/*assertClause*/ undefined
), ModifierFlags.None);
break;
case SyntaxKind.NamespaceExport:
Expand All @@ -7369,7 +7374,8 @@ namespace ts {
factory.createIdentifier(localName)
)
])),
factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context))
factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)),
/*assertClause*/ undefined
), ModifierFlags.None);
break;
case SyntaxKind.ExportSpecifier:
Expand Down Expand Up @@ -13455,6 +13461,10 @@ namespace ts {
return deferredGlobalImportMetaExpressionType;
}

function getGlobalImportCallOptionsType(reportErrors: boolean) {
return (deferredGlobalImportCallOptionsType ||= getGlobalType("ImportCallOptions" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
}

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

function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number): Type {
if (isImportCall(callTarget)) {
return argIndex === 0 ? stringType :
argIndex === 1 ? getGlobalImportCallOptionsType(/*reportErrors*/ false) :
anyType;
}

// If we're already in the process of resolving the given signature, don't resolve again as
// that could cause infinite recursion. Instead, return anySignature.
const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget);
Expand Down Expand Up @@ -26007,10 +26023,6 @@ namespace ts {
case SyntaxKind.AwaitExpression:
return getContextualTypeForAwaitOperand(parent as AwaitExpression, contextFlags);
case SyntaxKind.CallExpression:
if ((parent as CallExpression).expression.kind === SyntaxKind.ImportKeyword) {
return stringType;
}
/* falls through */
case SyntaxKind.NewExpression:
return getContextualTypeForArgument(parent as CallExpression | NewExpression, node);
case SyntaxKind.TypeAssertionExpression:
Expand Down Expand Up @@ -30606,17 +30618,26 @@ namespace ts {
if (node.arguments.length === 0) {
return createPromiseReturnType(node, anyType);
}

const specifier = node.arguments[0];
const specifierType = checkExpressionCached(specifier);
const optionsType = node.arguments.length > 1 ? checkExpressionCached(node.arguments[1]) : undefined;
// Even though multiple arguments is grammatically incorrect, type-check extra arguments for completion
for (let i = 1; i < node.arguments.length; ++i) {
for (let i = 2; i < node.arguments.length; ++i) {
checkExpressionCached(node.arguments[i]);
}

if (specifierType.flags & TypeFlags.Undefined || specifierType.flags & TypeFlags.Null || !isTypeAssignableTo(specifierType, stringType)) {
error(specifier, Diagnostics.Dynamic_import_s_specifier_must_be_of_type_string_but_here_has_type_0, typeToString(specifierType));
}

if (optionsType) {
const importCallOptionsType = getGlobalImportCallOptionsType(/*reportErrors*/ true);
if (importCallOptionsType !== emptyObjectType) {
checkTypeAssignableTo(optionsType, getNullableType(importCallOptionsType, TypeFlags.Undefined), node.arguments[1]);
}
}

// resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal
const moduleSymbol = resolveExternalModuleName(node, specifier);
if (moduleSymbol) {
Expand Down Expand Up @@ -39039,6 +39060,18 @@ namespace ts {
}
}

function checkAssertClause(declaration: ImportDeclaration | ExportDeclaration) {
if (declaration.assertClause) {
if (moduleKind !== ModuleKind.ESNext) {
return grammarErrorOnNode(declaration.assertClause, Diagnostics.Import_assertions_are_only_supported_when_the_module_option_is_set_to_esnext);
}

if (isImportDeclaration(declaration) ? declaration.importClause?.isTypeOnly : declaration.isTypeOnly) {
return grammarErrorOnNode(declaration.assertClause, Diagnostics.Import_assertions_cannot_be_used_with_type_only_imports_or_exports);
}
}
}

function checkImportDeclaration(node: ImportDeclaration) {
if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) {
// If we hit an import declaration in an illegal context, just bail out to avoid cascading errors.
Expand Down Expand Up @@ -39070,7 +39103,7 @@ namespace ts {
}
}
}

checkAssertClause(node);
}

function checkImportEqualsDeclaration(node: ImportEqualsDeclaration) {
Expand Down Expand Up @@ -39165,6 +39198,7 @@ namespace ts {
}
}
}
checkAssertClause(node);
}

function checkGrammarExportDeclaration(node: ExportDeclaration): boolean {
Expand Down Expand Up @@ -43201,14 +43235,25 @@ namespace ts {
}

const nodeArguments = node.arguments;
if (nodeArguments.length !== 1) {
return grammarErrorOnNode(node, Diagnostics.Dynamic_import_must_have_one_specifier_as_an_argument);
if (moduleKind !== ModuleKind.ESNext) {
// We are allowed trailing comma after proposal-import-assertions.
checkGrammarForDisallowedTrailingComma(nodeArguments);

if (nodeArguments.length > 1) {
const assertionArgument = nodeArguments[1];
return grammarErrorOnNode(assertionArgument, Diagnostics.Dynamic_imports_only_support_a_second_argument_when_the_module_option_is_set_to_esnext);
}
}
checkGrammarForDisallowedTrailingComma(nodeArguments);

if (nodeArguments.length === 0 || nodeArguments.length > 2) {
return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_can_only_accept_a_module_specifier_and_an_optional_assertion_as_arguments);
}

// see: parseArgumentOrArrayLiteralElement...we use this function which parse arguments of callExpression to parse specifier for dynamic import.
// parseArgumentOrArrayLiteralElement allows spread element to be in an argument list which is not allowed as specifier in dynamic import.
if (isSpreadElement(nodeArguments[0])) {
return grammarErrorOnNode(nodeArguments[0], Diagnostics.Specifier_of_dynamic_import_cannot_be_spread_element);
const spreadElement = find(nodeArguments, isSpreadElement);
if (spreadElement) {
return grammarErrorOnNode(spreadElement, Diagnostics.Argument_of_dynamic_import_cannot_be_spread_element);
}
return false;
}
Expand Down
16 changes: 14 additions & 2 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -924,11 +924,11 @@
"category": "Error",
"code": 1323
},
"Dynamic import must have one specifier as an argument.": {
"Dynamic imports only support a second argument when the '--module' option is set to 'esnext'.": {
"category": "Error",
"code": 1324
},
"Specifier of dynamic import cannot be spread element.": {
"Argument of dynamic import cannot be spread element.": {
"category": "Error",
"code": 1325
},
Expand Down Expand Up @@ -1388,6 +1388,10 @@
"category": "Message",
"code": 1449
},
"Dynamic imports can only accept a module specifier and an optional assertion as arguments": {
"category": "Message",
"code": 1450
},

"The types of '{0}' are incompatible between these types.": {
"category": "Error",
Expand Down Expand Up @@ -3304,6 +3308,14 @@
"category": "Error",
"code": 2820
},
"Import assertions are only supported when the '--module' option is set to 'esnext'.": {
"category": "Error",
"code": 2821
},
"Import assertions cannot be used with type-only imports or exports.": {
"category": "Error",
"code": 2822
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
31 changes: 31 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1515,6 +1515,10 @@ namespace ts {
return emitNamedExports(node as NamedExports);
case SyntaxKind.ExportSpecifier:
return emitExportSpecifier(node as ExportSpecifier);
case SyntaxKind.AssertClause:
return emitAssertClause(node as AssertClause);
case SyntaxKind.AssertEntry:
return emitAssertEntry(node as AssertEntry);
case SyntaxKind.MissingDeclaration:
return;

Expand Down Expand Up @@ -3322,6 +3326,9 @@ namespace ts {
writeSpace();
}
emitExpression(node.moduleSpecifier);
if (node.assertClause) {
emitWithLeadingSpace(node.assertClause);
}
writeTrailingSemicolon();
}

Expand Down Expand Up @@ -3390,9 +3397,33 @@ namespace ts {
writeSpace();
emitExpression(node.moduleSpecifier);
}
if (node.assertClause) {
emitWithLeadingSpace(node.assertClause);
}
writeTrailingSemicolon();
}

function emitAssertClause(node: AssertClause) {
emitTokenWithComment(SyntaxKind.AssertKeyword, node.pos, writeKeyword, node);
writeSpace();
const elements = node.elements;
emitList(node, elements, ListFormat.ImportClauseEntries);
}

function emitAssertEntry(node: AssertEntry) {
emit(node.name);
writePunctuation(":");
writeSpace();

const value = node.value;
/** @see {emitPropertyAssignment} */
if ((getEmitFlags(value) & EmitFlags.NoLeadingComments) === 0) {
const commentRange = getCommentRange(value);
emitTrailingCommentsOfPosition(commentRange.pos);
}
emit(value);
}

function emitNamespaceExportDeclaration(node: NamespaceExportDeclaration) {
let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node);
writeSpace();
Expand Down
Loading

0 comments on commit ec114b8

Please sign in to comment.