String import/export specifier (#12091)
* feat: parse moduleExportName * feat: add validators * Support string specifier name in commonjs transform * Support string specifier name in export-ns-from * test: add loose testcases * test: add testcases for amd and umd * feat: support systemjs * test: update fixtures fixed in #12110 * add plugin name typings * test: rename test layout * feat: implement under moduleStringNames flag * chore: add plugin syntax module string names * feat: support ModuleExportName as ModuleExportName * test: update test fixtures * fix flow errors * docs: update AST spec * feat: support { "some imports" as "some exports" } * feat: support { "some imports" as "some exports" } in systemjs * test: add test on `import { "foo" }` * Address review comments * add moduleStringNames to missing plugin helpers * Apply suggestions from code review * update test fixtures * Update packages/babel-parser/src/parser/error-message.js * update test fixtures Co-Authored-By: Kai Cataldo <kai@kaicataldo.com> Co-authored-by: Brian Ng <bng412@gmail.com>
This commit is contained in:
committed by
Nicolò Ribaudo
parent
1b90d90fcc
commit
21d7ee2610
@@ -43,6 +43,8 @@ export const ErrorMessages = Object.freeze({
|
||||
DuplicateRegExpFlags: "Duplicate regular expression flag",
|
||||
ElementAfterRest: "Rest element must be last element",
|
||||
EscapedCharNotAnIdentifier: "Invalid Unicode escape",
|
||||
ExportBindingIsString:
|
||||
"A string literal cannot be used as an exported binding without `from`.\n- Did you mean `export { %0 as '%1' } from 'some-module'`?",
|
||||
ExportDefaultFromAsIdentifier:
|
||||
"'from' is not allowed as an identifier after 'export default'",
|
||||
ForInOfLoopInitializer:
|
||||
@@ -53,6 +55,8 @@ export const ErrorMessages = Object.freeze({
|
||||
IllegalLanguageModeDirective:
|
||||
"Illegal 'use strict' directive in function with non-simple parameter list",
|
||||
IllegalReturn: "'return' outside of function",
|
||||
ImportBindingIsString:
|
||||
'A string literal cannot be used as an imported binding.\n- Did you mean `import { "%0" as foo }`?',
|
||||
ImportCallArgumentTrailingComma:
|
||||
"Trailing comma is disallowed inside import(...) arguments",
|
||||
ImportCallArity: "import() requires exactly %0",
|
||||
@@ -95,6 +99,8 @@ export const ErrorMessages = Object.freeze({
|
||||
"Only string literals are allowed as module attribute values",
|
||||
ModuleAttributesWithDuplicateKeys:
|
||||
'Duplicate key "%0" is not allowed in module attributes',
|
||||
ModuleExportNameHasLoneSurrogate:
|
||||
"An export name cannot include a lone surrogate, found '\\u%0'",
|
||||
ModuleExportUndefined: "Export '%0' is not defined",
|
||||
MultipleDefaultsInSwitch: "Multiple default clauses",
|
||||
NewlineAfterThrow: "Illegal newline after throw",
|
||||
|
||||
@@ -39,6 +39,8 @@ const FUNC_NO_FLAGS = 0b000,
|
||||
FUNC_HANGING_STATEMENT = 0b010,
|
||||
FUNC_NULLABLE_ID = 0b100;
|
||||
|
||||
const loneSurrogate = /[\uD800-\uDFFF]/u;
|
||||
|
||||
export default class StatementParser extends ExpressionParser {
|
||||
// ### Statement parsing
|
||||
|
||||
@@ -1745,7 +1747,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
|
||||
this.next();
|
||||
|
||||
specifier.exported = this.parseIdentifier(true);
|
||||
specifier.exported = this.parseModuleExportName();
|
||||
node.specifiers.push(
|
||||
this.finishNode(specifier, "ExportNamespaceSpecifier"),
|
||||
);
|
||||
@@ -1938,19 +1940,27 @@ export default class StatementParser extends ExpressionParser {
|
||||
} else if (node.specifiers && node.specifiers.length) {
|
||||
// Named exports
|
||||
for (const specifier of node.specifiers) {
|
||||
this.checkDuplicateExports(specifier, specifier.exported.name);
|
||||
const { exported } = specifier;
|
||||
const exportedName =
|
||||
exported.type === "Identifier" ? exported.name : exported.value;
|
||||
this.checkDuplicateExports(specifier, exportedName);
|
||||
// $FlowIgnore
|
||||
if (!isFrom && specifier.local) {
|
||||
// check for keywords used as local names
|
||||
this.checkReservedWord(
|
||||
specifier.local.name,
|
||||
specifier.local.start,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
// check if export is defined
|
||||
// $FlowIgnore
|
||||
this.scope.checkLocalExport(specifier.local);
|
||||
const { local } = specifier;
|
||||
if (local.type === "StringLiteral") {
|
||||
this.raise(
|
||||
specifier.start,
|
||||
Errors.ExportBindingIsString,
|
||||
local.extra.raw,
|
||||
exportedName,
|
||||
);
|
||||
} else {
|
||||
// check for keywords used as local names
|
||||
this.checkReservedWord(local.name, local.start, true, false);
|
||||
// check if export is defined
|
||||
// $FlowIgnore
|
||||
this.scope.checkLocalExport(local);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (node.declaration) {
|
||||
@@ -2006,6 +2016,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
checkDuplicateExports(
|
||||
node:
|
||||
| N.Identifier
|
||||
| N.StringLiteral
|
||||
| N.ExportNamedDeclaration
|
||||
| N.ExportSpecifier
|
||||
| N.ExportDefaultSpecifier,
|
||||
@@ -2041,9 +2052,9 @@ export default class StatementParser extends ExpressionParser {
|
||||
}
|
||||
|
||||
const node = this.startNode();
|
||||
node.local = this.parseIdentifier(true);
|
||||
node.local = this.parseModuleExportName();
|
||||
node.exported = this.eatContextual("as")
|
||||
? this.parseIdentifier(true)
|
||||
? this.parseModuleExportName()
|
||||
: node.local.__clone();
|
||||
nodes.push(this.finishNode(node, "ExportSpecifier"));
|
||||
}
|
||||
@@ -2051,6 +2062,27 @@ export default class StatementParser extends ExpressionParser {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#prod-ModuleExportName
|
||||
parseModuleExportName(): N.StringLiteral | N.Identifier {
|
||||
if (this.match(tt.string)) {
|
||||
this.expectPlugin("moduleStringNames");
|
||||
const result = this.parseLiteral<N.StringLiteral>(
|
||||
this.state.value,
|
||||
"StringLiteral",
|
||||
);
|
||||
const surrogate = result.value.match(loneSurrogate);
|
||||
if (surrogate) {
|
||||
this.raise(
|
||||
result.start,
|
||||
Errors.ModuleExportNameHasLoneSurrogate,
|
||||
surrogate[0].charCodeAt(0).toString(16),
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return this.parseIdentifier(true);
|
||||
}
|
||||
|
||||
// Parses import declaration.
|
||||
// https://tc39.es/ecma262/#prod-ImportDeclaration
|
||||
|
||||
@@ -2220,17 +2252,20 @@ export default class StatementParser extends ExpressionParser {
|
||||
// https://tc39.es/ecma262/#prod-ImportSpecifier
|
||||
parseImportSpecifier(node: N.ImportDeclaration): void {
|
||||
const specifier = this.startNode();
|
||||
specifier.imported = this.parseIdentifier(true);
|
||||
specifier.imported = this.parseModuleExportName();
|
||||
if (this.eatContextual("as")) {
|
||||
specifier.local = this.parseIdentifier();
|
||||
} else {
|
||||
this.checkReservedWord(
|
||||
specifier.imported.name,
|
||||
specifier.start,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
specifier.local = specifier.imported.__clone();
|
||||
const { imported } = specifier;
|
||||
if (imported.type === "StringLiteral") {
|
||||
throw this.raise(
|
||||
specifier.start,
|
||||
Errors.ImportBindingIsString,
|
||||
imported.value,
|
||||
);
|
||||
}
|
||||
this.checkReservedWord(imported.name, specifier.start, true, true);
|
||||
specifier.local = imported.__clone();
|
||||
}
|
||||
this.checkLVal(
|
||||
specifier.local,
|
||||
|
||||
Reference in New Issue
Block a user