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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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) {

View File

@ -1847,17 +1847,15 @@ export default (superClass: Class<Parser>): Class<Parser> =>
super.assertModuleNodeAllowed(node);
}
parseExport(
node: N.ExportNamedDeclaration | N.ExportAllDeclaration,
): N.ExportNamedDeclaration | N.ExportAllDeclaration {
node = super.parseExport(node);
parseExport(node: N.Node): N.AnyExport {
const decl = super.parseExport(node);
if (
node.type === "ExportNamedDeclaration" ||
node.type === "ExportAllDeclaration"
decl.type === "ExportNamedDeclaration" ||
decl.type === "ExportAllDeclaration"
) {
node.exportKind = node.exportKind || "value";
decl.exportKind = decl.exportKind || "value";
}
return node;
return decl;
}
parseExportDeclaration(node: N.ExportNamedDeclaration): ?N.Declaration {
@ -1893,27 +1891,26 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}
}
shouldParseExportStar(): boolean {
return (
super.shouldParseExportStar() ||
(this.isContextual("type") && this.lookahead().type === tt.star)
);
}
eatExportStar(node: N.Node): boolean {
if (super.eatExportStar(...arguments)) return true;
parseExportStar(node: N.ExportNamedDeclaration): void {
if (this.eatContextual("type")) {
if (this.isContextual("type") && this.lookahead().type === tt.star) {
node.exportKind = "type";
this.next();
this.next();
return true;
}
return super.parseExportStar(node);
return false;
}
parseExportNamespace(node: N.ExportNamedDeclaration) {
if (node.exportKind === "type") {
this.unexpected();
maybeParseExportNamespaceSpecifier(node: N.Node): boolean {
const pos = this.state.start;
const hasNamespace = super.maybeParseExportNamespaceSpecifier(node);
if (hasNamespace && node.exportKind === "type") {
this.unexpected(pos);
}
return super.parseExportNamespace(node);
return hasNamespace;
}
parseClassId(node: N.Class, isStatement: boolean, optionalId: ?boolean) {
@ -2225,7 +2222,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}
// parse typeof and type imports
parseImportSpecifiers(node: N.ImportDeclaration): void {
maybeParseDefaultImportSpecifier(node: N.ImportDeclaration): boolean {
node.importKind = "value";
let kind = null;
@ -2252,7 +2249,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}
}
super.parseImportSpecifiers(node);
return super.maybeParseDefaultImportSpecifier(node);
}
// parse import-type/typeof shorthand

View File

@ -1615,16 +1615,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
*/
checkDuplicateExports() {}
parseImport(
node: N.Node,
): N.ImportDeclaration | N.TsImportEqualsDeclaration {
parseImport(node: N.Node): N.AnyImport {
if (this.match(tt.name) && this.lookahead().type === tt.eq) {
return this.tsParseImportEqualsDeclaration(node);
}
return super.parseImport(node);
}
parseExport(node: N.Node): N.Node {
parseExport(node: N.Node): N.AnyExport {
if (this.match(tt._import)) {
// `export import A = B;`
this.expect(tt._import);

View File

@ -783,7 +783,9 @@ export type AnyExport =
| ExportNamedDeclaration
| ExportDefaultDeclaration
| ExportAllDeclaration
| TsExportAssignment;
| TsExportAssignment
| TsImportEqualsDeclaration
| TsNamespaceExportDeclaration;
export type ModuleSpecifier = NodeBase & {
local: Identifier,
@ -820,7 +822,7 @@ export type ImportNamespaceSpecifier = ModuleSpecifier & {
export type ExportNamedDeclaration = NodeBase & {
type: "ExportNamedDeclaration",
declaration: ?Declaration,
specifiers: $ReadOnlyArray<ExportSpecifier>,
specifiers: $ReadOnlyArray<ExportSpecifier | ExportDefaultSpecifier>,
source: ?Literal,
exportKind?: "type" | "value", // TODO: Not in spec
@ -831,6 +833,11 @@ export type ExportSpecifier = NodeBase & {
exported: Identifier,
};
export type ExportDefaultSpecifier = NodeBase & {
type: "ExportDefaultSpecifier",
exported: Identifier,
};
export type ExportDefaultDeclaration = NodeBase & {
type: "ExportDefaultDeclaration",
declaration:

View File

@ -145,7 +145,8 @@
"raw": "\"bar\""
},
"value": "bar"
}
},
"declaration": null
}
],
"directives": []

View File

@ -145,7 +145,8 @@
"raw": "\"bar\""
},
"value": "bar"
}
},
"declaration": null
}
],
"directives": []