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:
Huáng Jùnliàng
2020-09-21 16:50:51 -04:00
committed by Nicolò Ribaudo
parent 1b90d90fcc
commit 21d7ee2610
152 changed files with 1872 additions and 96 deletions

View File

@@ -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",

View File

@@ -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,