Skip to content

Commit

Permalink
initial support for Normative: Arbitrary module namespace identifier …
Browse files Browse the repository at this point in the history
…names
  • Loading branch information
Jack-Works committed May 29, 2022
1 parent 80832a8 commit 27c4aae
Show file tree
Hide file tree
Showing 18 changed files with 204 additions and 114 deletions.
4 changes: 2 additions & 2 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ namespace ts {
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 @@ -413,7 +413,7 @@ namespace ts {
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
33 changes: 19 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1826,8 +1826,8 @@ namespace ts {
nameArg: __String | Identifier | undefined,
isUse: boolean,
excludeGlobals = false,
getSpellingSuggstions = true): Symbol | undefined {
return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSpellingSuggstions, getSymbol);
getSpellingSuggestions = true): Symbol | undefined {
return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSpellingSuggestions, getSymbol);
}

function resolveNameHelper(
Expand Down Expand Up @@ -2683,7 +2683,7 @@ namespace ts {
? Diagnostics._0_was_exported_here
: Diagnostics._0_was_imported_here;

const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name.escapedText);
const name = moduleExportNameText(typeOnlyDeclaration.name);
addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name));
}
}
Expand Down Expand Up @@ -3305,7 +3305,7 @@ namespace ts {
/**
* Resolves a qualified name and any involved aliases.
*/
function resolveEntityName(name: EntityNameOrEntityNameExpression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean, location?: Node): Symbol | undefined {
function resolveEntityName(name: EntityNameOrEntityNameExpression | ModuleExportName, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean, location?: Node): Symbol | undefined {
if (nodeIsMissing(name)) {
return undefined;
}
Expand Down Expand Up @@ -3390,6 +3390,11 @@ namespace ts {
return undefined;
}
}
else if (name.kind === SyntaxKind.StringLiteral) {
const message = Diagnostics.Cannot_find_name_0;
symbol = getMergedSymbol(resolveName(location || name, moduleExportNameTextEscaped(name), meaning, ignoreErrors ? undefined : message, moduleExportNameTextEscaped(name), /*isUse*/ true, /** excludeGlobals */true));
if (!symbol) return undefined;
}
else {
throw Debug.assertNever(name, "Unknown entity name kind.");
}
Expand Down Expand Up @@ -6952,7 +6957,7 @@ namespace ts {
if (!e.propertyName) {
// export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it
const indices = indicesOf(statements);
const associatedIndices = filter(indices, i => nodeHasName(statements[i], e.name));
const associatedIndices = filter(indices, i => nodeHasName(statements[i], moduleExportNameText(e.name)));
if (length(associatedIndices) && every(associatedIndices, i => canHaveExportModifier(statements[i]))) {
for (const index of associatedIndices) {
statements[index] = addExportModifier(statements[index] as Extract<HasModifiers, Statement>);
Expand Down Expand Up @@ -7615,7 +7620,7 @@ namespace ts {
function getSomeTargetNameFromDeclarations(declarations: Declaration[] | undefined) {
return firstDefined(declarations, d => {
if (isImportSpecifier(d) || isExportSpecifier(d)) {
return idText(d.propertyName || d.name);
return moduleExportNameText(d.propertyName || d.name);
}
if (isBinaryExpression(d) || isExportAssignment(d)) {
const expression = isExportAssignment(d) ? d.expression : d.right;
Expand Down Expand Up @@ -8513,10 +8518,10 @@ namespace ts {
}
}

function collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined {
function collectLinkedAliases(node: ModuleExportName, setVisibility?: boolean): Node[] | undefined {
let exportSymbol: Symbol | undefined;
if (node.parent && node.parent.kind === SyntaxKind.ExportAssignment) {
exportSymbol = resolveName(node, node.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, node, /*isUse*/ false);
exportSymbol = resolveName(node, moduleExportNameTextEscaped(node), SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, moduleExportNameTextEscaped(node), /*isUse*/ false);
}
else if (node.parent.kind === SyntaxKind.ExportSpecifier) {
exportSymbol = getTargetOfExportSpecifier(node.parent as ExportSpecifier, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias);
Expand Down Expand Up @@ -40863,7 +40868,7 @@ namespace ts {
const message = isType
? Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled
: Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled;
const name = idText(node.kind === SyntaxKind.ImportSpecifier ? node.propertyName || node.name : node.name);
const name = moduleExportNameText(node.kind === SyntaxKind.ImportSpecifier ? node.propertyName || node.name : node.name);
addTypeOnlyDeclarationRelatedInfo(
error(node, message, name),
isType ? undefined : typeOnlyAlias,
Expand All @@ -40883,7 +40888,7 @@ namespace ts {
const message = isType
? Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type
: Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_re_exported_using_a_type_only_re_export_when_isolatedModules_is_enabled;
const name = idText(node.propertyName || node.name);
const name = moduleExportNameText(node.propertyName || node.name);
addTypeOnlyDeclarationRelatedInfo(
error(node, message, name),
isType ? undefined : typeOnlyAlias,
Expand Down Expand Up @@ -40941,7 +40946,7 @@ namespace ts {
checkCollisionsForDeclarationName(node, node.name);
checkAliasSymbol(node);
if (node.kind === SyntaxKind.ImportSpecifier &&
idText(node.propertyName || node.name) === "default" &&
moduleExportNameTextEscaped(node.propertyName || node.name) === "default" &&
getESModuleInterop(compilerOptions) &&
moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault);
Expand Down Expand Up @@ -41178,10 +41183,10 @@ namespace ts {
if (!node.parent.parent.moduleSpecifier) {
const exportedName = node.propertyName || node.name;
// find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases)
const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias,
const symbol = resolveName(exportedName, moduleExportNameTextEscaped(exportedName), SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias,
/*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true);
if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) {
error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName));
error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, moduleExportNameText(exportedName));
}
else {
markExportAsReferenced(node);
Expand All @@ -41195,7 +41200,7 @@ namespace ts {
if (getESModuleInterop(compilerOptions) &&
moduleKind !== ModuleKind.System &&
(moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) &&
idText(node.propertyName || node.name) === "default") {
moduleExportNameTextEscaped(node.propertyName || node.name) === "default") {
checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault);
}
}
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 @@ -637,6 +637,11 @@ namespace ts {
return node.kind === SyntaxKind.ExportSpecifier;
}

/** @internal */
export function isModuleExportName(node: Node): node is ModuleExportName {
return isIdentifier(node) || isStringLiteral(node);
}

export function isMissingDeclaration(node: Node): node is MissingDeclaration {
return node.kind === SyntaxKind.MissingDeclaration;
}
Expand Down
63 changes: 48 additions & 15 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2209,7 +2209,7 @@ namespace ts {
case ParsingContext.HeritageClauses:
return isHeritageClause();
case ParsingContext.ImportOrExportSpecifiers:
return tokenIsIdentifierOrKeyword(token());
return token() === SyntaxKind.StringLiteral || tokenIsIdentifierOrKeyword(token());
case ParsingContext.JsxAttributes:
return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBraceToken;
case ParsingContext.JsxChildren:
Expand Down Expand Up @@ -7540,36 +7540,44 @@ namespace ts {

function parseExportSpecifier() {
const hasJSDoc = hasPrecedingJSDocComment();
return withJSDoc(parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier) as ExportSpecifier, hasJSDoc);
return withJSDoc(parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier), hasJSDoc);
}

function parseImportSpecifier() {
return parseImportOrExportSpecifier(SyntaxKind.ImportSpecifier) as ImportSpecifier;
return parseImportOrExportSpecifier(SyntaxKind.ImportSpecifier);
}

function parseImportOrExportSpecifier(kind: SyntaxKind): ImportOrExportSpecifier {
function parseImportOrExportSpecifier(kind: SyntaxKind.ImportSpecifier): ImportSpecifier;
function parseImportOrExportSpecifier(kind: SyntaxKind.ExportSpecifier): ExportSpecifier;
function parseImportOrExportSpecifier(kind: SyntaxKind.ImportSpecifier | SyntaxKind.ExportSpecifier): ImportOrExportSpecifier {
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.getTokenPos();
let checkIdentifierEnd = scanner.getTextPos();
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 @@ -7578,9 +7586,10 @@ namespace ts {
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 @@ -7594,31 +7603,42 @@ namespace ts {
// { type as something }
propertyName = name;
canParseAsKeyword = false;
name = parseNameWithKeywordCheck();
name = parseModuleExportNameOnlyForExports();
}
else {
// { type as }
isTypeOnly = true;
name = firstAs;
}
}
// import { type "x" as y } from "mod";
else if (token() === SyntaxKind.StringLiteral) {
// { type "x" ...? }
isTypeOnly = true;
if (kind === SyntaxKind.ImportSpecifier) mustParseAsKeyword = true;
name = parseModuleExportName(parseNameWithKeywordCheck);
}
else if (tokenIsIdentifierOrKeyword(token())) {
// { type something ...? }
isTypeOnly = true;
name = parseNameWithKeywordCheck();
}
}
else if (kind === SyntaxKind.ImportSpecifier && name.kind === SyntaxKind.StringLiteral) {
mustParseAsKeyword = true;
}

if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) {
if ((canParseAsKeyword && token() === SyntaxKind.AsKeyword) || mustParseAsKeyword) {
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.assertNode(name, isIdentifier);
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 @@ -7628,6 +7648,19 @@ namespace ts {
checkIdentifierEnd = scanner.getTextPos();
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 {
return parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral;
}
}

function parseNamespaceExport(pos: number): NamespaceExport {
Expand Down
Loading

0 comments on commit 27c4aae

Please sign in to comment.