* refactor: parseMaybeUnary => parseUnary * refactor: extract shouldExitDescending method * refactor: add parseUpdate * refactor: avoid comparing with hardcoded token value * refactor: add ParseNewOrNewTarget * refactor: add parseCoverCallAndAsyncArrowHead * add parseBind * refactor: polish parseTaggedTemplateExpression interface * refactor: add parseMember method * refactor: add parseSuper method * refactor: add parseAsyncArrowUnaryFunction method * fix: disallow line break before async binding arrow * refactor: simplify tt.name logic * refactor: add parseDo method * refactor: misc * refactor: rename parseObjectMember by parsePropertyDefinition * refactor: unify set/get/async keyword parsing in ObjectMethod * refactor: misc * refactor: add parseArrayLike method * refactor: move fsharp epilogure and prologue inside parseObjectLike * fixup * refactor: rename parseFunctionExpression to parseFunctionOrFunctionSent * refactor: remove redundant logic * refactor: rename parseClassPropertyName by parseClassElementName * refactor: avoid unecessary lookahead when parsing tt._export * fix: export-default-from should support escaped async as export binding * address review comments * parseUnary -> parseMaybeUnary
470 lines
13 KiB
JavaScript
470 lines
13 KiB
JavaScript
// @flow
|
|
|
|
import { types as tt, TokenType } from "../tokenizer/types";
|
|
import type Parser from "../parser";
|
|
import type { ExpressionErrors } from "../parser/util";
|
|
import * as N from "../types";
|
|
import type { Position } from "../util/location";
|
|
import { type BindingTypes, BIND_NONE } from "../util/scopeflags";
|
|
import { Errors } from "../parser/error";
|
|
|
|
function isSimpleProperty(node: N.Node): boolean {
|
|
return (
|
|
node != null &&
|
|
node.type === "Property" &&
|
|
node.kind === "init" &&
|
|
node.method === false
|
|
);
|
|
}
|
|
|
|
export default (superClass: Class<Parser>): Class<Parser> =>
|
|
class extends superClass {
|
|
estreeParseRegExpLiteral({ pattern, flags }: N.RegExpLiteral): N.Node {
|
|
let regex = null;
|
|
try {
|
|
regex = new RegExp(pattern, flags);
|
|
} catch (e) {
|
|
// In environments that don't support these flags value will
|
|
// be null as the regex can't be represented natively.
|
|
}
|
|
const node = this.estreeParseLiteral(regex);
|
|
node.regex = { pattern, flags };
|
|
|
|
return node;
|
|
}
|
|
|
|
estreeParseBigIntLiteral(value: any): N.Node {
|
|
// https://github.com/estree/estree/blob/master/es2020.md#bigintliteral
|
|
// $FlowIgnore
|
|
const bigInt = typeof BigInt !== "undefined" ? BigInt(value) : null;
|
|
const node = this.estreeParseLiteral(bigInt);
|
|
node.bigint = String(node.value || value);
|
|
|
|
return node;
|
|
}
|
|
|
|
estreeParseDecimalLiteral(value: any): N.Node {
|
|
// https://github.com/estree/estree/blob/master/experimental/decimal.md
|
|
// $FlowIgnore
|
|
// todo: use BigDecimal when node supports it.
|
|
const decimal = null;
|
|
const node = this.estreeParseLiteral(decimal);
|
|
node.decimal = String(node.value || value);
|
|
|
|
return node;
|
|
}
|
|
|
|
estreeParseLiteral(value: any): N.Node {
|
|
return this.parseLiteral(value, "Literal");
|
|
}
|
|
|
|
directiveToStmt(directive: N.Directive): N.ExpressionStatement {
|
|
const directiveLiteral = directive.value;
|
|
|
|
const stmt = this.startNodeAt(directive.start, directive.loc.start);
|
|
const expression = this.startNodeAt(
|
|
directiveLiteral.start,
|
|
directiveLiteral.loc.start,
|
|
);
|
|
|
|
expression.value = directiveLiteral.value;
|
|
expression.raw = directiveLiteral.extra.raw;
|
|
|
|
stmt.expression = this.finishNodeAt(
|
|
expression,
|
|
"Literal",
|
|
directiveLiteral.end,
|
|
directiveLiteral.loc.end,
|
|
);
|
|
stmt.directive = directiveLiteral.extra.raw.slice(1, -1);
|
|
|
|
return this.finishNodeAt(
|
|
stmt,
|
|
"ExpressionStatement",
|
|
directive.end,
|
|
directive.loc.end,
|
|
);
|
|
}
|
|
|
|
// ==================================
|
|
// Overrides
|
|
// ==================================
|
|
|
|
initFunction(
|
|
node: N.BodilessFunctionOrMethodBase,
|
|
isAsync: ?boolean,
|
|
): void {
|
|
super.initFunction(node, isAsync);
|
|
node.expression = false;
|
|
}
|
|
|
|
checkDeclaration(node: N.Pattern | N.ObjectProperty): void {
|
|
if (isSimpleProperty(node)) {
|
|
this.checkDeclaration(((node: any): N.EstreeProperty).value);
|
|
} else {
|
|
super.checkDeclaration(node);
|
|
}
|
|
}
|
|
|
|
checkGetterSetterParams(method: N.ObjectMethod | N.ClassMethod): void {
|
|
const prop = ((method: any): N.EstreeProperty | N.EstreeMethodDefinition);
|
|
const paramCount = prop.kind === "get" ? 0 : 1;
|
|
const start = prop.start;
|
|
if (prop.value.params.length !== paramCount) {
|
|
if (method.kind === "get") {
|
|
this.raise(start, Errors.BadGetterArity);
|
|
} else {
|
|
this.raise(start, Errors.BadSetterArity);
|
|
}
|
|
} else if (
|
|
prop.kind === "set" &&
|
|
prop.value.params[0].type === "RestElement"
|
|
) {
|
|
this.raise(start, Errors.BadSetterRestParameter);
|
|
}
|
|
}
|
|
|
|
checkLVal(
|
|
expr: N.Expression,
|
|
bindingType: BindingTypes = BIND_NONE,
|
|
checkClashes: ?{ [key: string]: boolean },
|
|
contextDescription: string,
|
|
disallowLetBinding?: boolean,
|
|
): void {
|
|
switch (expr.type) {
|
|
case "ObjectPattern":
|
|
expr.properties.forEach(prop => {
|
|
this.checkLVal(
|
|
prop.type === "Property" ? prop.value : prop,
|
|
bindingType,
|
|
checkClashes,
|
|
"object destructuring pattern",
|
|
disallowLetBinding,
|
|
);
|
|
});
|
|
break;
|
|
default:
|
|
super.checkLVal(
|
|
expr,
|
|
bindingType,
|
|
checkClashes,
|
|
contextDescription,
|
|
disallowLetBinding,
|
|
);
|
|
}
|
|
}
|
|
|
|
checkProto(
|
|
prop: N.ObjectMember | N.SpreadElement,
|
|
isRecord: boolean,
|
|
protoRef: { used: boolean },
|
|
refExpressionErrors: ?ExpressionErrors,
|
|
): void {
|
|
// $FlowIgnore: check prop.method and fallback to super method
|
|
if (prop.method) {
|
|
return;
|
|
}
|
|
super.checkProto(prop, isRecord, protoRef, refExpressionErrors);
|
|
}
|
|
|
|
isValidDirective(stmt: N.Statement): boolean {
|
|
return (
|
|
stmt.type === "ExpressionStatement" &&
|
|
stmt.expression.type === "Literal" &&
|
|
typeof stmt.expression.value === "string" &&
|
|
!stmt.expression.extra?.parenthesized
|
|
);
|
|
}
|
|
|
|
stmtToDirective(stmt: N.Statement): N.Directive {
|
|
const directive = super.stmtToDirective(stmt);
|
|
const value = stmt.expression.value;
|
|
|
|
// Reset value to the actual value as in estree mode we want
|
|
// the stmt to have the real value and not the raw value
|
|
directive.value.value = value;
|
|
|
|
return directive;
|
|
}
|
|
|
|
parseBlockBody(
|
|
node: N.BlockStatementLike,
|
|
allowDirectives: ?boolean,
|
|
topLevel: boolean,
|
|
end: TokenType,
|
|
): void {
|
|
super.parseBlockBody(node, allowDirectives, topLevel, end);
|
|
|
|
const directiveStatements = node.directives.map(d =>
|
|
this.directiveToStmt(d),
|
|
);
|
|
node.body = directiveStatements.concat(node.body);
|
|
// $FlowIgnore - directives isn't optional in the type definition
|
|
delete node.directives;
|
|
}
|
|
|
|
pushClassMethod(
|
|
classBody: N.ClassBody,
|
|
method: N.ClassMethod,
|
|
isGenerator: boolean,
|
|
isAsync: boolean,
|
|
isConstructor: boolean,
|
|
allowsDirectSuper: boolean,
|
|
): void {
|
|
this.parseMethod(
|
|
method,
|
|
isGenerator,
|
|
isAsync,
|
|
isConstructor,
|
|
allowsDirectSuper,
|
|
"ClassMethod",
|
|
true,
|
|
);
|
|
if (method.typeParameters) {
|
|
// $FlowIgnore
|
|
method.value.typeParameters = method.typeParameters;
|
|
delete method.typeParameters;
|
|
}
|
|
classBody.body.push(method);
|
|
}
|
|
|
|
parseExprAtom(refExpressionErrors?: ?ExpressionErrors): N.Expression {
|
|
switch (this.state.type) {
|
|
case tt.num:
|
|
case tt.string:
|
|
return this.estreeParseLiteral(this.state.value);
|
|
|
|
case tt.regexp:
|
|
return this.estreeParseRegExpLiteral(this.state.value);
|
|
|
|
case tt.bigint:
|
|
return this.estreeParseBigIntLiteral(this.state.value);
|
|
|
|
case tt.decimal:
|
|
return this.estreeParseDecimalLiteral(this.state.value);
|
|
|
|
case tt._null:
|
|
return this.estreeParseLiteral(null);
|
|
|
|
case tt._true:
|
|
return this.estreeParseLiteral(true);
|
|
|
|
case tt._false:
|
|
return this.estreeParseLiteral(false);
|
|
|
|
default:
|
|
return super.parseExprAtom(refExpressionErrors);
|
|
}
|
|
}
|
|
|
|
parseLiteral<T: N.Literal>(
|
|
value: any,
|
|
type: /*T["kind"]*/ string,
|
|
startPos?: number,
|
|
startLoc?: Position,
|
|
): T {
|
|
const node = super.parseLiteral(value, type, startPos, startLoc);
|
|
node.raw = node.extra.raw;
|
|
delete node.extra;
|
|
|
|
return node;
|
|
}
|
|
|
|
parseFunctionBody(
|
|
node: N.Function,
|
|
allowExpression: ?boolean,
|
|
isMethod?: boolean = false,
|
|
): void {
|
|
super.parseFunctionBody(node, allowExpression, isMethod);
|
|
node.expression = node.body.type !== "BlockStatement";
|
|
}
|
|
|
|
parseMethod<T: N.MethodLike>(
|
|
node: T,
|
|
isGenerator: boolean,
|
|
isAsync: boolean,
|
|
isConstructor: boolean,
|
|
allowDirectSuper: boolean,
|
|
type: string,
|
|
inClassScope: boolean = false,
|
|
): T {
|
|
let funcNode = this.startNode();
|
|
funcNode.kind = node.kind; // provide kind, so super method correctly sets state
|
|
funcNode = super.parseMethod(
|
|
funcNode,
|
|
isGenerator,
|
|
isAsync,
|
|
isConstructor,
|
|
allowDirectSuper,
|
|
type,
|
|
inClassScope,
|
|
);
|
|
funcNode.type = "FunctionExpression";
|
|
delete funcNode.kind;
|
|
// $FlowIgnore
|
|
node.value = funcNode;
|
|
|
|
type = type === "ClassMethod" ? "MethodDefinition" : type;
|
|
return this.finishNode(node, type);
|
|
}
|
|
|
|
parseObjectMethod(
|
|
prop: N.ObjectMethod,
|
|
isGenerator: boolean,
|
|
isAsync: boolean,
|
|
isPattern: boolean,
|
|
isAccessor: boolean,
|
|
): ?N.ObjectMethod {
|
|
const node: N.EstreeProperty = (super.parseObjectMethod(
|
|
prop,
|
|
isGenerator,
|
|
isAsync,
|
|
isPattern,
|
|
isAccessor,
|
|
): any);
|
|
|
|
if (node) {
|
|
node.type = "Property";
|
|
if (((node: any): N.ClassMethod).kind === "method") node.kind = "init";
|
|
node.shorthand = false;
|
|
}
|
|
|
|
return (node: any);
|
|
}
|
|
|
|
parseObjectProperty(
|
|
prop: N.ObjectProperty,
|
|
startPos: ?number,
|
|
startLoc: ?Position,
|
|
isPattern: boolean,
|
|
refExpressionErrors: ?ExpressionErrors,
|
|
): ?N.ObjectProperty {
|
|
const node: N.EstreeProperty = (super.parseObjectProperty(
|
|
prop,
|
|
startPos,
|
|
startLoc,
|
|
isPattern,
|
|
refExpressionErrors,
|
|
): any);
|
|
|
|
if (node) {
|
|
node.kind = "init";
|
|
node.type = "Property";
|
|
}
|
|
|
|
return (node: any);
|
|
}
|
|
|
|
toAssignable(node: N.Node): N.Node {
|
|
if (isSimpleProperty(node)) {
|
|
this.toAssignable(node.value);
|
|
|
|
return node;
|
|
}
|
|
|
|
return super.toAssignable(node);
|
|
}
|
|
|
|
toAssignableObjectExpressionProp(prop: N.Node, isLast: boolean) {
|
|
if (prop.kind === "get" || prop.kind === "set") {
|
|
throw this.raise(prop.key.start, Errors.PatternHasAccessor);
|
|
} else if (prop.method) {
|
|
throw this.raise(prop.key.start, Errors.PatternHasMethod);
|
|
} else {
|
|
super.toAssignableObjectExpressionProp(prop, isLast);
|
|
}
|
|
}
|
|
|
|
finishCallExpression<T: N.CallExpression | N.OptionalCallExpression>(
|
|
node: T,
|
|
optional: boolean,
|
|
): N.Expression {
|
|
super.finishCallExpression(node, optional);
|
|
|
|
if (node.callee.type === "Import") {
|
|
((node: N.Node): N.EstreeImportExpression).type = "ImportExpression";
|
|
((node: N.Node): N.EstreeImportExpression).source = node.arguments[0];
|
|
// $FlowIgnore - arguments isn't optional in the type definition
|
|
delete node.arguments;
|
|
// $FlowIgnore - callee isn't optional in the type definition
|
|
delete node.callee;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
toReferencedListDeep(
|
|
exprList: $ReadOnlyArray<?N.Expression>,
|
|
isParenthesizedExpr?: boolean,
|
|
): void {
|
|
// ImportExpressions do not have an arguments array.
|
|
if (!exprList) {
|
|
return;
|
|
}
|
|
|
|
super.toReferencedListDeep(exprList, isParenthesizedExpr);
|
|
}
|
|
|
|
parseExport(node: N.Node) {
|
|
super.parseExport(node);
|
|
|
|
switch (node.type) {
|
|
case "ExportAllDeclaration":
|
|
node.exported = null;
|
|
break;
|
|
|
|
case "ExportNamedDeclaration":
|
|
if (
|
|
node.specifiers.length === 1 &&
|
|
node.specifiers[0].type === "ExportNamespaceSpecifier"
|
|
) {
|
|
node.type = "ExportAllDeclaration";
|
|
node.exported = node.specifiers[0].exported;
|
|
delete node.specifiers;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
parseSubscript(
|
|
base: N.Expression,
|
|
startPos: number,
|
|
startLoc: Position,
|
|
noCalls: ?boolean,
|
|
state: N.ParseSubscriptState,
|
|
) {
|
|
const node = super.parseSubscript(
|
|
base,
|
|
startPos,
|
|
startLoc,
|
|
noCalls,
|
|
state,
|
|
);
|
|
|
|
if (state.optionalChainMember) {
|
|
// https://github.com/estree/estree/blob/master/es2020.md#chainexpression
|
|
if (
|
|
node.type === "OptionalMemberExpression" ||
|
|
node.type === "OptionalCallExpression"
|
|
) {
|
|
node.type = node.type.substring(8); // strip Optional prefix
|
|
}
|
|
if (state.stop) {
|
|
const chain = this.startNodeAtNode(node);
|
|
chain.expression = node;
|
|
return this.finishNode(chain, "ChainExpression");
|
|
}
|
|
} else if (
|
|
node.type === "MemberExpression" ||
|
|
node.type === "CallExpression"
|
|
) {
|
|
node.optional = false;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
};
|