Refactor import and export parsing (#9326)

* [parser] Refactor import parsing

* [parser] Refactor export parsing

* Fix types
This commit is contained in:
Nicolò Ribaudo
2019-01-22 19:52:56 +01:00
committed by GitHub
parent f77c450cda
commit 65febdd13a
6 changed files with 172 additions and 136 deletions

View File

@@ -1470,35 +1470,109 @@ export default class StatementParser extends ExpressionParser {
// Parses module export declaration.
// TODO: better type. Node is an N.AnyExport.
parseExport(node: N.Node): N.Node {
// export * from '...'
if (this.shouldParseExportStar()) {
this.parseExportStar(node);
if (node.type === "ExportAllDeclaration") return node;
} else if (this.isExportDefaultSpecifier()) {
this.expectPlugin("exportDefaultFrom");
const specifier = this.startNode();
specifier.exported = this.parseIdentifier(true);
const specifiers = [this.finishNode(specifier, "ExportDefaultSpecifier")];
node.specifiers = specifiers;
if (this.match(tt.comma) && this.lookahead().type === tt.star) {
this.expect(tt.comma);
const specifier = this.startNode();
this.expect(tt.star);
this.expectContextual("as");
specifier.exported = this.parseIdentifier();
specifiers.push(this.finishNode(specifier, "ExportNamespaceSpecifier"));
} else {
this.parseExportSpecifiersMaybe(node);
}
parseExport(node: N.Node): N.AnyExport {
const hasDefault = this.maybeParseExportDefaultSpecifier(node);
const parseAfterDefault = !hasDefault || this.eat(tt.comma);
const hasStar = parseAfterDefault && this.eatExportStar(node);
const hasNamespace =
hasStar && this.maybeParseExportNamespaceSpecifier(node);
const parseAfterNamespace =
parseAfterDefault && (!hasNamespace || this.eat(tt.comma));
const isFromRequired = hasDefault || hasStar;
if (hasStar && !hasNamespace) {
if (hasDefault) this.unexpected();
this.parseExportFrom(node, true);
} else if (this.eat(tt._default)) {
return this.finishNode(node, "ExportAllDeclaration");
}
const hasSpecifiers = this.maybeParseExportNamedSpecifiers(node);
if (
(hasDefault && parseAfterDefault && !hasStar && !hasSpecifiers) ||
(hasNamespace && parseAfterNamespace && !hasSpecifiers)
) {
throw this.unexpected(null, tt.braceL);
}
let hasDeclaration;
if (isFromRequired || hasSpecifiers) {
hasDeclaration = false;
this.parseExportFrom(node, isFromRequired);
} else {
hasDeclaration = this.maybeParseExportDeclaration(node);
}
if (isFromRequired || hasSpecifiers || hasDeclaration) {
this.checkExport(node, true);
return this.finishNode(node, "ExportNamedDeclaration");
}
if (this.eat(tt._default)) {
// export default ...
node.declaration = this.parseExportDefaultExpression();
this.checkExport(node, true, true);
return this.finishNode(node, "ExportDefaultDeclaration");
} else if (this.shouldParseExportDeclaration()) {
}
throw this.unexpected(null, tt.braceL);
}
// eslint-disable-next-line no-unused-vars
eatExportStar(node: N.Node): boolean {
return this.eat(tt.star);
}
maybeParseExportDefaultSpecifier(node: N.Node): boolean {
if (this.isExportDefaultSpecifier()) {
// export defaultObj ...
this.expectPlugin("exportDefaultFrom");
const specifier = this.startNode();
specifier.exported = this.parseIdentifier(true);
node.specifiers = [this.finishNode(specifier, "ExportDefaultSpecifier")];
return true;
}
return false;
}
maybeParseExportNamespaceSpecifier(node: N.Node): boolean {
if (this.isContextual("as")) {
if (!node.specifiers) node.specifiers = [];
this.expectPlugin("exportNamespaceFrom");
const specifier = this.startNodeAt(
this.state.lastTokStart,
this.state.lastTokStartLoc,
);
this.next();
specifier.exported = this.parseIdentifier(true);
node.specifiers.push(
this.finishNode(specifier, "ExportNamespaceSpecifier"),
);
return true;
}
return false;
}
maybeParseExportNamedSpecifiers(node: N.Node): boolean {
if (this.match(tt.braceL)) {
if (!node.specifiers) node.specifiers = [];
node.specifiers.push(...this.parseExportSpecifiers());
node.source = null;
node.declaration = null;
return true;
}
return false;
}
maybeParseExportDeclaration(node: N.Node): boolean {
if (this.shouldParseExportDeclaration()) {
if (this.isContextual("async")) {
const next = this.lookahead();
@@ -1511,14 +1585,10 @@ export default class StatementParser extends ExpressionParser {
node.specifiers = [];
node.source = null;
node.declaration = this.parseExportDeclaration(node);
} else {
// export { x, y as z } [from '...']
node.declaration = null;
node.specifiers = this.parseExportSpecifiers();
this.parseExportFrom(node);
return true;
}
this.checkExport(node, true);
return this.finishNode(node, "ExportNamedDeclaration");
return false;
}
isAsyncFunction() {
@@ -1605,17 +1675,9 @@ export default class StatementParser extends ExpressionParser {
);
}
parseExportSpecifiersMaybe(node: N.ExportNamedDeclaration): void {
if (this.eat(tt.comma)) {
node.specifiers = node.specifiers.concat(this.parseExportSpecifiers());
}
}
parseExportFrom(node: N.ExportNamedDeclaration, expect?: boolean): void {
if (this.eatContextual("from")) {
node.source = this.match(tt.string)
? this.parseExprAtom()
: this.unexpected();
node.source = this.parseImportSource();
this.checkExport(node);
} else {
if (expect) {
@@ -1628,39 +1690,6 @@ export default class StatementParser extends ExpressionParser {
this.semicolon();
}
shouldParseExportStar(): boolean {
return this.match(tt.star);
}
parseExportStar(node: N.ExportNamedDeclaration): void {
this.expect(tt.star);
if (this.isContextual("as")) {
this.parseExportNamespace(node);
} else {
this.parseExportFrom(node, true);
this.finishNode(node, "ExportAllDeclaration");
}
}
parseExportNamespace(node: N.ExportNamedDeclaration): void {
this.expectPlugin("exportNamespaceFrom");
const specifier = this.startNodeAt(
this.state.lastTokStart,
this.state.lastTokStartLoc,
);
this.next();
specifier.exported = this.parseIdentifier(true);
node.specifiers = [this.finishNode(specifier, "ExportNamespaceSpecifier")];
this.parseExportSpecifiersMaybe(node);
this.parseExportFrom(node, true);
}
shouldParseExportDeclaration(): boolean {
if (this.match(tt.at)) {
this.expectOnePlugin(["decorators", "decorators-legacy"]);
@@ -1760,27 +1789,24 @@ export default class StatementParser extends ExpressionParser {
}
checkDuplicateExports(
node: N.Identifier | N.ExportNamedDeclaration | N.ExportSpecifier,
node:
| N.Identifier
| N.ExportNamedDeclaration
| N.ExportSpecifier
| N.ExportDefaultSpecifier,
name: string,
): void {
if (this.state.exportedIdentifiers.indexOf(name) > -1) {
this.raiseDuplicateExportError(node, name);
throw this.raise(
node.start,
name === "default"
? "Only one default export allowed per module."
: `\`${name}\` has already been exported. Exported identifiers must be unique.`,
);
}
this.state.exportedIdentifiers.push(name);
}
raiseDuplicateExportError(
node: N.Identifier | N.ExportNamedDeclaration | N.ExportSpecifier,
name: string,
): empty {
throw this.raise(
node.start,
name === "default"
? "Only one default export allowed per module."
: `\`${name}\` has already been exported. Exported identifiers must be unique.`,
);
}
// Parses a comma-separated list of module exports.
parseExportSpecifiers(): Array<N.ExportSpecifier> {
@@ -1820,23 +1846,26 @@ export default class StatementParser extends ExpressionParser {
// Parses import declaration.
parseImport(node: N.Node): N.ImportDeclaration | N.TsImportEqualsDeclaration {
parseImport(node: N.Node): N.AnyImport {
// import '...'
if (this.match(tt.string)) {
node.specifiers = [];
node.source = this.parseExprAtom();
} else {
node.specifiers = [];
this.parseImportSpecifiers(node);
node.specifiers = [];
if (!this.match(tt.string)) {
const hasDefault = this.maybeParseDefaultImportSpecifier(node);
const parseNext = !hasDefault || this.eat(tt.comma);
const hasStar = parseNext && this.maybeParseStarImportSpecifier(node);
if (parseNext && !hasStar) this.parseNamedImportSpecifiers(node);
this.expectContextual("from");
node.source = this.match(tt.string)
? this.parseExprAtom()
: this.unexpected();
}
node.source = this.parseImportSource();
this.semicolon();
return this.finishNode(node, "ImportDeclaration");
}
parseImportSource(): N.StringLiteral {
if (!this.match(tt.string)) this.unexpected();
return this.parseExprAtom();
}
// eslint-disable-next-line no-unused-vars
shouldParseDefaultImport(node: N.ImportDeclaration): boolean {
return this.match(tt.name);
@@ -1853,9 +1882,7 @@ export default class StatementParser extends ExpressionParser {
node.specifiers.push(this.finishNode(specifier, type));
}
// Parses a comma-separated list of module imports.
parseImportSpecifiers(node: N.ImportDeclaration): void {
let first = true;
maybeParseDefaultImportSpecifier(node: N.ImportDeclaration): boolean {
if (this.shouldParseDefaultImport(node)) {
// import defaultObj, { x, y as z } from '...'
this.parseImportSpecifierLocal(
@@ -1864,10 +1891,12 @@ export default class StatementParser extends ExpressionParser {
"ImportDefaultSpecifier",
"default import specifier",
);
if (!this.eat(tt.comma)) return;
return true;
}
return false;
}
maybeParseStarImportSpecifier(node: N.ImportDeclaration): boolean {
if (this.match(tt.star)) {
const specifier = this.startNode();
this.next();
@@ -1879,10 +1908,13 @@ export default class StatementParser extends ExpressionParser {
"ImportNamespaceSpecifier",
"import namespace specifier",
);
return;
return true;
}
return false;
}
parseNamedImportSpecifiers(node: N.ImportDeclaration) {
let first = true;
this.expect(tt.braceL);
while (!this.eat(tt.braceR)) {
if (first) {