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

View File

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

View File

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

View File

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

View File

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

View File

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