315 lines
9.8 KiB
JavaScript
315 lines
9.8 KiB
JavaScript
// @flow
|
|
|
|
import * as charCodes from "charcodes";
|
|
|
|
import { types as tt, TokenType } from "../tokenizer/types";
|
|
import type Parser from "../parser";
|
|
import * as N from "../types";
|
|
|
|
tt.placeholder = new TokenType("%%", { startsExpr: true });
|
|
|
|
export type PlaceholderTypes =
|
|
| "Identifier"
|
|
| "StringLiteral"
|
|
| "Expression"
|
|
| "Statement"
|
|
| "Declaration"
|
|
| "BlockStatement"
|
|
| "ClassBody"
|
|
| "Pattern";
|
|
|
|
// $PropertyType doesn't support enums. Use a fake "switch" (GetPlaceholderNode)
|
|
//type MaybePlaceholder<T: PlaceholderTypes> = $PropertyType<N, T> | N.Placeholder<T>;
|
|
|
|
type _Switch<Value, Cases, Index> = $Call<
|
|
(
|
|
$ElementType<$ElementType<Cases, Index>, 0>,
|
|
) => $ElementType<$ElementType<Cases, Index>, 1>,
|
|
Value,
|
|
>;
|
|
type $Switch<Value, Cases> = _Switch<Value, Cases, *>;
|
|
|
|
type NodeOf<T: PlaceholderTypes> = $Switch<
|
|
T,
|
|
[
|
|
["Identifier", N.Identifier],
|
|
["StringLiteral", N.StringLiteral],
|
|
["Expression", N.Expression],
|
|
["Statement", N.Statement],
|
|
["Declaration", N.Declaration],
|
|
["BlockStatement", N.BlockStatement],
|
|
["ClassBody", N.ClassBody],
|
|
["Pattern", N.Pattern],
|
|
],
|
|
>;
|
|
|
|
// Placeholder<T> breaks everything, because its type is incompatible with
|
|
// the substituted nodes.
|
|
type MaybePlaceholder<T: PlaceholderTypes> = NodeOf<T>; // | Placeholder<T>
|
|
|
|
export default (superClass: Class<Parser>): Class<Parser> =>
|
|
class extends superClass {
|
|
parsePlaceholder<T: PlaceholderTypes>(
|
|
expectedNode: T,
|
|
): /*?N.Placeholder<T>*/ ?MaybePlaceholder<T> {
|
|
if (this.match(tt.placeholder)) {
|
|
const node = this.startNode();
|
|
this.next();
|
|
this.assertNoSpace("Unexpected space in placeholder.");
|
|
|
|
// We can't use this.parseIdentifier because
|
|
// we don't want nested placeholders.
|
|
node.name = super.parseIdentifier(/* liberal */ true);
|
|
|
|
this.assertNoSpace("Unexpected space in placeholder.");
|
|
this.expect(tt.placeholder);
|
|
return this.finishPlaceholder(node, expectedNode);
|
|
}
|
|
}
|
|
|
|
finishPlaceholder<T: PlaceholderTypes>(
|
|
node: N.Node,
|
|
expectedNode: T,
|
|
): /*N.Placeholder<T>*/ MaybePlaceholder<T> {
|
|
node.expectedNode = expectedNode;
|
|
return this.finishNode(node, "Placeholder");
|
|
}
|
|
|
|
/* ============================================================ *
|
|
* tokenizer/index.js *
|
|
* ============================================================ */
|
|
|
|
getTokenFromCode(code: number) {
|
|
if (
|
|
code === charCodes.percentSign &&
|
|
this.input.charCodeAt(this.state.pos + 1) === charCodes.percentSign
|
|
) {
|
|
return this.finishOp(tt.placeholder, 2);
|
|
}
|
|
|
|
return super.getTokenFromCode(...arguments);
|
|
}
|
|
|
|
/* ============================================================ *
|
|
* parser/expression.js *
|
|
* ============================================================ */
|
|
|
|
parseExprAtom(): MaybePlaceholder<"Expression"> {
|
|
return (
|
|
this.parsePlaceholder("Expression") || super.parseExprAtom(...arguments)
|
|
);
|
|
}
|
|
|
|
parseIdentifier(): MaybePlaceholder<"Identifier"> {
|
|
// NOTE: This function only handles identifiers outside of
|
|
// expressions and binding patterns, since they are already
|
|
// handled by the parseExprAtom and parseBindingAtom functions.
|
|
// This is needed, for example, to parse "class %%NAME%% {}".
|
|
return (
|
|
this.parsePlaceholder("Identifier") ||
|
|
super.parseIdentifier(...arguments)
|
|
);
|
|
}
|
|
|
|
checkReservedWord(word: string): void {
|
|
// Sometimes we call #checkReservedWord(node.name), expecting
|
|
// that node is an Identifier. If it is a Placeholder, name
|
|
// will be undefined.
|
|
if (word !== undefined) super.checkReservedWord(...arguments);
|
|
}
|
|
|
|
/* ============================================================ *
|
|
* parser/lval.js *
|
|
* ============================================================ */
|
|
|
|
parseBindingAtom(): MaybePlaceholder<"Pattern"> {
|
|
return (
|
|
this.parsePlaceholder("Pattern") || super.parseBindingAtom(...arguments)
|
|
);
|
|
}
|
|
|
|
checkLVal(expr: N.Expression): void {
|
|
if (expr.type !== "Placeholder") super.checkLVal(...arguments);
|
|
}
|
|
|
|
toAssignable(node: N.Node): N.Node {
|
|
if (
|
|
node &&
|
|
node.type === "Placeholder" &&
|
|
node.expectedNode === "Expression"
|
|
) {
|
|
node.expectedNode = "Pattern";
|
|
return node;
|
|
}
|
|
return super.toAssignable(...arguments);
|
|
}
|
|
|
|
/* ============================================================ *
|
|
* parser/statement.js *
|
|
* ============================================================ */
|
|
|
|
verifyBreakContinue(node: N.BreakStatement | N.ContinueStatement) {
|
|
if (node.label && node.label.type === "Placeholder") return;
|
|
super.verifyBreakContinue(...arguments);
|
|
}
|
|
|
|
parseExpressionStatement(
|
|
node: MaybePlaceholder<"Statement">,
|
|
expr: N.Expression,
|
|
): MaybePlaceholder<"Statement"> {
|
|
if (
|
|
expr.type !== "Placeholder" ||
|
|
(expr.extra && expr.extra.parenthesized)
|
|
) {
|
|
return super.parseExpressionStatement(...arguments);
|
|
}
|
|
|
|
if (this.match(tt.colon)) {
|
|
const stmt: N.LabeledStatement = node;
|
|
stmt.label = this.finishPlaceholder(expr, "Identifier");
|
|
this.next();
|
|
stmt.body = this.parseStatement("label");
|
|
return this.finishNode(stmt, "LabeledStatement");
|
|
}
|
|
|
|
this.semicolon();
|
|
|
|
node.name = expr.name;
|
|
return this.finishPlaceholder(node, "Statement");
|
|
}
|
|
|
|
parseBlock(): MaybePlaceholder<"BlockStatement"> {
|
|
return (
|
|
this.parsePlaceholder("BlockStatement") ||
|
|
super.parseBlock(...arguments)
|
|
);
|
|
}
|
|
|
|
parseFunctionId(): ?MaybePlaceholder<"Identifier"> {
|
|
return (
|
|
this.parsePlaceholder("Identifier") ||
|
|
super.parseFunctionId(...arguments)
|
|
);
|
|
}
|
|
|
|
parseClass<T: N.Class>(
|
|
node: T,
|
|
isStatement: /* T === ClassDeclaration */ boolean,
|
|
optionalId?: boolean,
|
|
): T {
|
|
const type = isStatement ? "ClassDeclaration" : "ClassExpression";
|
|
|
|
this.next();
|
|
this.takeDecorators(node);
|
|
|
|
const placeholder = this.parsePlaceholder("Identifier");
|
|
if (placeholder) {
|
|
if (
|
|
this.match(tt._extends) ||
|
|
this.match(tt.placeholder) ||
|
|
this.match(tt.braceL)
|
|
) {
|
|
node.id = placeholder;
|
|
} else if (optionalId || !isStatement) {
|
|
node.id = null;
|
|
node.body = this.finishPlaceholder(placeholder, "ClassBody");
|
|
return this.finishNode(node, type);
|
|
} else {
|
|
this.unexpected(null, "A class name is required");
|
|
}
|
|
} else {
|
|
this.parseClassId(node, isStatement, optionalId);
|
|
}
|
|
|
|
this.parseClassSuper(node);
|
|
node.body =
|
|
this.parsePlaceholder("ClassBody") ||
|
|
this.parseClassBody(!!node.superClass);
|
|
return this.finishNode(node, type);
|
|
}
|
|
|
|
parseExport(node: N.Node): N.Node {
|
|
const placeholder = this.parsePlaceholder("Identifier");
|
|
if (!placeholder) return super.parseExport(...arguments);
|
|
|
|
if (!this.isContextual("from") && !this.match(tt.comma)) {
|
|
// export %%DECL%%;
|
|
node.specifiers = [];
|
|
node.source = null;
|
|
node.declaration = this.finishPlaceholder(placeholder, "Declaration");
|
|
return this.finishNode(node, "ExportNamedDeclaration");
|
|
}
|
|
|
|
// export %%NAME%% from "foo";
|
|
this.expectPlugin("exportDefaultFrom");
|
|
const specifier = this.startNode();
|
|
specifier.exported = placeholder;
|
|
node.specifiers = [this.finishNode(specifier, "ExportDefaultSpecifier")];
|
|
|
|
return super.parseExport(node);
|
|
}
|
|
|
|
maybeParseExportDefaultSpecifier(node: N.Node): boolean {
|
|
if (node.specifiers && node.specifiers.length > 0) {
|
|
// "export %%NAME%%" has already been parsed by #parseExport.
|
|
return true;
|
|
}
|
|
return super.maybeParseExportDefaultSpecifier(...arguments);
|
|
}
|
|
|
|
checkExport(node: N.ExportNamedDeclaration): void {
|
|
const { specifiers } = node;
|
|
if (specifiers && specifiers.length) {
|
|
node.specifiers = specifiers.filter(
|
|
node => node.exported.type === "Placeholder",
|
|
);
|
|
}
|
|
super.checkExport(node);
|
|
node.specifiers = specifiers;
|
|
}
|
|
|
|
parseImport(
|
|
node: N.Node,
|
|
): N.ImportDeclaration | N.TsImportEqualsDeclaration {
|
|
const placeholder = this.parsePlaceholder("Identifier");
|
|
if (!placeholder) return super.parseImport(...arguments);
|
|
|
|
node.specifiers = [];
|
|
|
|
if (!this.isContextual("from") && !this.match(tt.comma)) {
|
|
// import %%STRING%%;
|
|
node.source = this.finishPlaceholder(placeholder, "StringLiteral");
|
|
this.semicolon();
|
|
return this.finishNode(node, "ImportDeclaration");
|
|
}
|
|
|
|
// import %%DEFAULT%% ...
|
|
const specifier = this.startNodeAtNode(placeholder);
|
|
specifier.local = placeholder;
|
|
this.finishNode(specifier, "ImportDefaultSpecifier");
|
|
node.specifiers.push(specifier);
|
|
|
|
if (this.eat(tt.comma)) {
|
|
// import %%DEFAULT%%, * as ...
|
|
const hasStarImport = this.maybeParseStarImportSpecifier(node);
|
|
|
|
// import %%DEFAULT%%, { ...
|
|
if (!hasStarImport) this.parseNamedImportSpecifiers(node);
|
|
}
|
|
|
|
this.expectContextual("from");
|
|
node.source = this.parseImportSource();
|
|
this.semicolon();
|
|
return this.finishNode(node, "ImportDeclaration");
|
|
}
|
|
|
|
parseImportSource(): MaybePlaceholder<"StringLiteral"> {
|
|
// import ... from %%STRING%%;
|
|
|
|
return (
|
|
this.parsePlaceholder("StringLiteral") ||
|
|
super.parseImportSource(...arguments)
|
|
);
|
|
}
|
|
};
|