Skip to content

Commit

Permalink
Normative: Arbitrary module namespace identifier names
Browse files Browse the repository at this point in the history
  • Loading branch information
Jack-Works committed Jun 13, 2023
1 parent 2a37eb2 commit 89b405b
Show file tree
Hide file tree
Showing 37 changed files with 693 additions and 279 deletions.
6 changes: 4 additions & 2 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ import {
ModifierFlags,
ModuleBlock,
ModuleDeclaration,
moduleExportNameText,
moduleExportNameTextEscaped,
Mutable,
NamespaceExportDeclaration,
Node,
Expand Down Expand Up @@ -432,7 +434,7 @@ function getModuleInstanceStateForAliasTarget(specifier: ExportSpecifier, visite
const statements = p.statements;
let found: ModuleInstanceState | undefined;
for (const statement of statements) {
if (nodeHasName(statement, name)) {
if (nodeHasName(statement, moduleExportNameText(name))) {
if (!statement.parent) {
setParent(statement, p);
setParentRecursive(statement, /*incremental*/ false);
Expand Down Expand Up @@ -738,7 +740,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean, isComputedName?: boolean): Symbol {
Debug.assert(isComputedName || !hasDynamicName(node));

const isDefaultExport = hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node) && node.name.escapedText === "default";
const isDefaultExport = hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node) && moduleExportNameTextEscaped(node.name) === "default";

// The exported symbol for an export default function/class node is always named "default"
const name = isComputedName ? InternalSymbolName.Computed
Expand Down
148 changes: 101 additions & 47 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1601,6 +1601,10 @@
"category": "Error",
"code": 1490
},
"String literal module export names are not allowed when the 'module' option is set to 'es2020' or lower.": {
"category": "Error",
"code": 1491
},

"The types of '{0}' are incompatible between these types.": {
"category": "Error",
Expand Down Expand Up @@ -1659,6 +1663,10 @@
"category": "Message",
"code": 2212
},
"String literal module export names must be followed by a 'from' clause.": {
"category": "Error",
"code": 2213
},

"Duplicate identifier '{0}'.": {
"category": "Error",
Expand Down
27 changes: 19 additions & 8 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ import {
isHoistedFunction,
isHoistedVariableStatement,
isIdentifier,
isIdentifierText,
isImportDeclaration,
isImportEqualsDeclaration,
isImportKeyword,
Expand Down Expand Up @@ -313,6 +314,7 @@ import {
ModuleBlock,
ModuleBody,
ModuleDeclaration,
ModuleExportName,
ModuleKind,
ModuleName,
ModuleReference,
Expand Down Expand Up @@ -983,6 +985,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
get createLogicalNot() { return getPrefixUnaryCreateFunction(SyntaxKind.ExclamationToken); },
get createPostfixIncrement() { return getPostfixUnaryCreateFunction(SyntaxKind.PlusPlusToken); },
get createPostfixDecrement() { return getPostfixUnaryCreateFunction(SyntaxKind.MinusMinusToken); },
createModuleExportName,

// Compound nodes
createImmediatelyInvokedFunctionExpression,
Expand Down Expand Up @@ -4713,7 +4716,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
}

// @api
function createNamespaceExport(name: Identifier): NamespaceExport {
function createNamespaceExport(name: ModuleExportName): NamespaceExport {
const node = createBaseDeclaration<NamespaceExport>(SyntaxKind.NamespaceExport);
node.name = name;
node.transformFlags |=
Expand All @@ -4724,7 +4727,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
}

// @api
function updateNamespaceExport(node: NamespaceExport, name: Identifier) {
function updateNamespaceExport(node: NamespaceExport, name: ModuleExportName) {
return node.name !== name
? update(createNamespaceExport(name), node)
: node;
Expand All @@ -4747,7 +4750,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
}

// @api
function createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
function createImportSpecifier(isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: Identifier) {
const node = createBaseDeclaration<ImportSpecifier>(SyntaxKind.ImportSpecifier);
node.isTypeOnly = isTypeOnly;
node.propertyName = propertyName;
Expand All @@ -4760,7 +4763,15 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
}

// @api
function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
function createModuleExportName(name: string, languageVersion: ScriptTarget): ModuleExportName;
function createModuleExportName(name: string | undefined, languageVersion: ScriptTarget): ModuleExportName | undefined;
function createModuleExportName(name: string | undefined, languageVersion: ScriptTarget): ModuleExportName | undefined {
if (name === undefined) return undefined;
return isIdentifierText(name, languageVersion) ? createIdentifier(name) : createStringLiteral(name);
}

// @api
function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: Identifier) {
return node.isTypeOnly !== isTypeOnly
|| node.propertyName !== propertyName
|| node.name !== name
Expand Down Expand Up @@ -4868,11 +4879,11 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
}

// @api
function createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier) {
function createExportSpecifier(isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: ModuleExportName) {
const node = createBaseNode<ExportSpecifier>(SyntaxKind.ExportSpecifier);
node.isTypeOnly = isTypeOnly;
node.propertyName = asName(propertyName);
node.name = asName(name);
node.propertyName = propertyName;
node.name = name;
node.transformFlags |=
propagateChildFlags(node.propertyName) |
propagateChildFlags(node.name);
Expand All @@ -4883,7 +4894,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
}

// @api
function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: ModuleExportName) {
return node.isTypeOnly !== isTypeOnly
|| node.propertyName !== propertyName
|| node.name !== name
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ import {
MissingDeclaration,
ModuleBlock,
ModuleDeclaration,
ModuleExportName,
NamedExports,
NamedImports,
NamedTupleMember,
Expand Down Expand Up @@ -321,6 +322,10 @@ export function isPrivateIdentifier(node: Node): node is PrivateIdentifier {
return node.kind === SyntaxKind.PrivateIdentifier;
}

export function isModuleExportName(node: Node): node is ModuleExportName {
return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.StringLiteral;
}

// Reserved Words

/** @internal */
Expand Down
8 changes: 6 additions & 2 deletions src/compiler/factory/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -792,15 +792,19 @@ export function getOrCreateExternalHelpersModuleNameIfNeeded(factory: NodeFactor
}

/**
* Get the name of that target module from an import or export declaration
* Get the name of that target module from an import or export declaration.
*
* This is only used in AMD and SystemJS emit.
*
* @internal
*/
export function getLocalNameForExternalImport(factory: NodeFactory, node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration, sourceFile: SourceFile): Identifier | undefined {
const namespaceDeclaration = getNamespaceDeclarationNode(node);
if (namespaceDeclaration && !isDefaultImport(node) && !isExportNamespaceAsDefaultDeclaration(node)) {
const name = namespaceDeclaration.name;
return isGeneratedIdentifier(name) ? name : factory.createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, name) || idText(name));
if (isGeneratedIdentifier(name)) return name;
if (name.kind === SyntaxKind.StringLiteral) return factory.getGeneratedNameForNode(name);
return factory.createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, name) || idText(name));
}
if (node.kind === SyntaxKind.ImportDeclaration && node.importClause) {
return factory.getGeneratedNameForNode(node);
Expand Down
75 changes: 59 additions & 16 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ import {
ImportClause,
ImportDeclaration,
ImportEqualsDeclaration,
ImportOrExportSpecifier,
ImportSpecifier,
ImportTypeAssertionContainer,
ImportTypeNode,
Expand Down Expand Up @@ -253,6 +252,7 @@ import {
modifiersToFlags,
ModuleBlock,
ModuleDeclaration,
ModuleExportName,
ModuleKind,
Mutable,
NamedExportBindings,
Expand Down Expand Up @@ -2869,7 +2869,13 @@ namespace Parser {
case ParsingContext.HeritageClauses:
return isHeritageClause();
case ParsingContext.ImportOrExportSpecifiers:
return tokenIsIdentifierOrKeyword(token());
// bail out if the next token is Identifier(from) StringLiteral.
// That means we're in something like `import { from "mod"`
// In this case we don't want parse it as import { from <missing SyntaxKind.AsKeyword> "mod", ...
if (token() === SyntaxKind.FromKeyword && lookAhead(nextTokenIsStringLiteral)) {
return false;
}
return token() === SyntaxKind.StringLiteral || tokenIsIdentifierOrKeyword(token());
case ParsingContext.JsxAttributes:
return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBraceToken;
case ParsingContext.JsxChildren:
Expand Down Expand Up @@ -3390,7 +3396,11 @@ namespace Parser {
case ParsingContext.TypeArguments: return parseErrorAtCurrentToken(Diagnostics.Type_argument_expected);
case ParsingContext.TupleElementTypes: return parseErrorAtCurrentToken(Diagnostics.Type_expected);
case ParsingContext.HeritageClauses: return parseErrorAtCurrentToken(Diagnostics.Unexpected_token_expected);
case ParsingContext.ImportOrExportSpecifiers: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected);
case ParsingContext.ImportOrExportSpecifiers:
if (token() === SyntaxKind.FromKeyword) {
return parseErrorAtCurrentToken(Diagnostics._0_expected, "}");
}
return parseErrorAtCurrentToken(Diagnostics.Identifier_expected);
case ParsingContext.JsxAttributes: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected);
case ParsingContext.JsxChildren: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected);
case ParsingContext.AssertEntries: return parseErrorAtCurrentToken(Diagnostics.Identifier_or_string_literal_expected); // AssertionKey.
Expand Down Expand Up @@ -7358,6 +7368,9 @@ namespace Parser {
}
}

function nextTokenIsStringLiteral() {
return nextToken() === SyntaxKind.StringLiteral;
}
function nextTokenIsIdentifierOrStringLiteralOnSameLine() {
nextToken();
return !scanner.hasPrecedingLineBreak() && (isIdentifier() || token() === SyntaxKind.StringLiteral);
Expand Down Expand Up @@ -8345,29 +8358,34 @@ namespace Parser {
return parseImportOrExportSpecifier(SyntaxKind.ImportSpecifier) as ImportSpecifier;
}

function parseImportOrExportSpecifier(kind: SyntaxKind): ImportOrExportSpecifier {
function parseImportOrExportSpecifier(kind: SyntaxKind) {
const pos = getNodePos();
// ModuleExportName:
// Identifier
// StringLiteral
// ImportSpecifier:
// BindingIdentifier
// IdentifierName as BindingIdentifier
// ModuleExportName as BindingIdentifier
// ExportSpecifier:
// IdentifierName
// IdentifierName as IdentifierName
// ModuleExportName
// ModuleExportName as ModuleExportName
let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier();
let checkIdentifierStart = scanner.getTokenStart();
let checkIdentifierEnd = scanner.getTokenEnd();
let isTypeOnly = false;
let propertyName: Identifier | undefined;
let propertyName: ModuleExportName | undefined;
let canParseAsKeyword = true;
let name = parseIdentifierName();
if (name.escapedText === "type") {
let mustParseAsKeyword = false;
let name = parseModuleExportName(parseIdentifierName);
if (name.kind === SyntaxKind.Identifier && name.escapedText === "type") {
// If the first token of an import specifier is 'type', there are a lot of possibilities,
// especially if we see 'as' afterwards:
//
// import { type } from "mod"; - isTypeOnly: false, name: type
// import { type as } from "mod"; - isTypeOnly: true, name: as
// import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type
// import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as
// export { type as as "s" } from "mod";- isTypeOnly: true, name: "s", propertyName: as
if (token() === SyntaxKind.AsKeyword) {
// { type as ...? }
const firstAs = parseIdentifierName();
Expand All @@ -8376,9 +8394,10 @@ namespace Parser {
const secondAs = parseIdentifierName();
if (tokenIsIdentifierOrKeyword(token())) {
// { type as as something }
// { type as as "something" } (only in exports)
isTypeOnly = true;
propertyName = firstAs;
name = parseNameWithKeywordCheck();
name = parseModuleExportNameOnlyForExports();
canParseAsKeyword = false;
}
else {
Expand All @@ -8392,31 +8411,43 @@ namespace Parser {
// { type as something }
propertyName = name;
canParseAsKeyword = false;
name = parseNameWithKeywordCheck();
name = parseModuleExportNameOnlyForExports();
}
else {
// { type as }
isTypeOnly = true;
name = firstAs;
}
}
// export { type "x" }
// import { type "x" as ... }
else if (token() === SyntaxKind.StringLiteral) {
isTypeOnly = true;
if (kind === SyntaxKind.ImportSpecifier) mustParseAsKeyword = true;
name = parseModuleExportName(parseNameWithKeywordCheck);
}
else if (tokenIsIdentifierOrKeyword(token())) {
// { type something ...? }
isTypeOnly = true;
name = parseNameWithKeywordCheck();
}
}
// import { "x" as ... }
else if (kind === SyntaxKind.ImportSpecifier && name.kind === SyntaxKind.StringLiteral) {
mustParseAsKeyword = true;
}

if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) {
if (mustParseAsKeyword || (canParseAsKeyword && token() === SyntaxKind.AsKeyword)) {
propertyName = name;
parseExpected(SyntaxKind.AsKeyword);
name = parseNameWithKeywordCheck();
name = parseModuleExportNameOnlyForExports();
}
if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) {
parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected);
}
if (kind === SyntaxKind.ImportSpecifier) Debug.assert(name.kind === SyntaxKind.Identifier);
const node = kind === SyntaxKind.ImportSpecifier
? factory.createImportSpecifier(isTypeOnly, propertyName, name)
? factory.createImportSpecifier(isTypeOnly, propertyName, name as Identifier)
: factory.createExportSpecifier(isTypeOnly, propertyName, name);
return finishNode(node, pos);

Expand All @@ -8426,10 +8457,22 @@ namespace Parser {
checkIdentifierEnd = scanner.getTokenEnd();
return parseIdentifierName();
}
function parseModuleExportNameOnlyForExports() {
if (kind === SyntaxKind.ImportSpecifier) return parseNameWithKeywordCheck();
return parseModuleExportName(parseNameWithKeywordCheck);
}
function parseModuleExportName(parser: () => Identifier): ModuleExportName {
if (token() === SyntaxKind.StringLiteral) return parseStringLiteral();
return parser();
}
function parseStringLiteral(): StringLiteral {
// TODO: the spec requires it pass IsStringWellFormedUnicode
return parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral;
}
}

function parseNamespaceExport(pos: number): NamespaceExport {
return finishNode(factory.createNamespaceExport(parseIdentifierName()), pos);
return finishNode(factory.createNamespaceExport(token() === SyntaxKind.StringLiteral ? parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral : parseIdentifierName()), pos);
}

function parseExportDeclaration(pos: number, hasJSDoc: boolean, modifiers: NodeArray<ModifierLike> | undefined): ExportDeclaration {
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
getDirectoryPath,
getEffectiveBaseTypeNode,
getEffectiveModifierFlags,
getEmitScriptTarget,
getExternalModuleImportEqualsDeclarationExpression,
getExternalModuleNameFromDeclaration,
getFirstConstructorWithBody,
Expand Down Expand Up @@ -1520,7 +1521,7 @@ export function transformDeclarations(context: TransformationContext) {
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
factory.createNamedExports(map(exportMappings, ([gen, exp]) => {
return factory.createExportSpecifier(/*isTypeOnly*/ false, gen, exp);
return factory.createExportSpecifier(/*isTypeOnly*/ false, gen, factory.createModuleExportName(exp, getEmitScriptTarget(context.getCompilerOptions())));
}))
));
}
Expand Down
Loading

0 comments on commit 89b405b

Please sign in to comment.