Skip to content

Commit

Permalink
refactor: add parse*Literal parser routines (#13333)
Browse files Browse the repository at this point in the history
* refactor: simplify parseLiteral interface

* refactor: extract specific methods on parsing literals

* fix: avoid StringLiteral type comparison

* add test cases

* fix: remove redundant node

* Update packages/babel-parser/src/plugins/flow/index.js

Co-authored-by: Federico Ciardi <fed.ciardi@gmail.com>

* update test fixtures

* fix: refine parseLiteral typings

Co-authored-by: Federico Ciardi <fed.ciardi@gmail.com>
  • Loading branch information
JLHwung and fedeci committed May 19, 2021
1 parent 053f94f commit 461ba25
Show file tree
Hide file tree
Showing 11 changed files with 308 additions and 101 deletions.
87 changes: 57 additions & 30 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -1068,33 +1068,28 @@ export default class ExpressionParser extends LValParser {
}

case tt.regexp: {
const value = this.state.value;
node = this.parseLiteral(value.value, "RegExpLiteral");
node.pattern = value.pattern;
node.flags = value.flags;
return node;
return this.parseRegExpLiteral(this.state.value);
}

case tt.num:
return this.parseLiteral(this.state.value, "NumericLiteral");
return this.parseNumericLiteral(this.state.value);

case tt.bigint:
return this.parseLiteral(this.state.value, "BigIntLiteral");
return this.parseBigIntLiteral(this.state.value);

case tt.decimal:
return this.parseLiteral(this.state.value, "DecimalLiteral");
return this.parseDecimalLiteral(this.state.value);

case tt.string:
return this.parseLiteral(this.state.value, "StringLiteral");
return this.parseStringLiteral(this.state.value);

case tt._null:
node = this.startNode();
this.next();
return this.finishNode(node, "NullLiteral");
return this.parseNullLiteral();

case tt._true:
return this.parseBooleanLiteral(true);
case tt._false:
return this.parseBooleanLiteral();
return this.parseBooleanLiteral(false);

case tt.parenL:
return this.parseParenAndDistinguishExpression(canBeArrow);
Expand Down Expand Up @@ -1290,13 +1285,6 @@ export default class ExpressionParser extends LValParser {
return this.finishNode(node, "Super");
}

parseBooleanLiteral(): N.BooleanLiteral {
const node = this.startNode();
node.value = this.match(tt._true);
this.next();
return this.finishNode(node, "BooleanLiteral");
}

parseMaybePrivateName(
isPrivateNameAllowed: boolean,
): N.PrivateName | N.Identifier {
Expand Down Expand Up @@ -1398,21 +1386,60 @@ export default class ExpressionParser extends LValParser {
return this.parseMetaProperty(node, id, "meta");
}

parseLiteral<T: N.Literal>(
parseLiteralAtNode<T: N.Node>(
value: any,
type: /*T["kind"]*/ string,
startPos?: number,
startLoc?: Position,
type: $ElementType<T, "type">,
node: any,
): T {
startPos = startPos || this.state.start;
startLoc = startLoc || this.state.startLoc;

const node = this.startNodeAt(startPos, startLoc);
this.addExtra(node, "rawValue", value);
this.addExtra(node, "raw", this.input.slice(startPos, this.state.end));
this.addExtra(node, "raw", this.input.slice(node.start, this.state.end));
node.value = value;
this.next();
return this.finishNode(node, type);
return this.finishNode<T>(node, type);
}

parseLiteral<T: N.Node>(value: any, type: $ElementType<T, "type">): T {
const node = this.startNode();
return this.parseLiteralAtNode(value, type, node);
}

parseStringLiteral(value: any) {
return this.parseLiteral<N.StringLiteral>(value, "StringLiteral");
}

parseNumericLiteral(value: any) {
return this.parseLiteral<N.NumericLiteral>(value, "NumericLiteral");
}

parseBigIntLiteral(value: any) {
return this.parseLiteral<N.BigIntLiteral>(value, "BigIntLiteral");
}

parseDecimalLiteral(value: any) {
return this.parseLiteral<N.DecimalLiteral>(value, "DecimalLiteral");
}

parseRegExpLiteral(value: { value: any, pattern: string, flags: string }) {
const node = this.parseLiteral<N.RegExpLiteral>(
value.value,
"RegExpLiteral",
);
node.pattern = value.pattern;
node.flags = value.flags;
return node;
}

parseBooleanLiteral(value: boolean) {
const node = this.startNode();
node.value = value;
this.next();
return this.finishNode<N.BooleanLiteral>(node, "BooleanLiteral");
}

parseNullLiteral() {
const node = this.startNode();
this.next();
return this.finishNode<N.NullLiteral>(node, "NullLiteral");
}

// https://tc39.es/ecma262/#prod-CoverParenthesizedExpressionAndArrowParameterList
Expand Down
19 changes: 7 additions & 12 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -2041,7 +2041,7 @@ export default class StatementParser extends ExpressionParser {
// $FlowIgnore
if (!isFrom && specifier.local) {
const { local } = specifier;
if (local.type === "StringLiteral") {
if (local.type !== "Identifier") {
this.raise(
specifier.start,
Errors.ExportBindingIsString,
Expand Down Expand Up @@ -2157,10 +2157,7 @@ export default class StatementParser extends ExpressionParser {
// https://tc39.es/ecma262/#prod-ModuleExportName
parseModuleExportName(): N.StringLiteral | N.Identifier {
if (this.match(tt.string)) {
const result = this.parseLiteral<N.StringLiteral>(
this.state.value,
"StringLiteral",
);
const result = this.parseStringLiteral(this.state.value);
const surrogate = result.value.match(loneSurrogate);
if (surrogate) {
this.raise(
Expand Down Expand Up @@ -2259,7 +2256,7 @@ export default class StatementParser extends ExpressionParser {
// parse AssertionKey : IdentifierName, StringLiteral
const keyName = this.state.value;
if (this.match(tt.string)) {
node.key = this.parseLiteral<N.StringLiteral>(keyName, "StringLiteral");
node.key = this.parseStringLiteral(keyName);
} else {
node.key = this.parseIdentifier(true);
}
Expand Down Expand Up @@ -2291,10 +2288,7 @@ export default class StatementParser extends ExpressionParser {
Errors.ModuleAttributeInvalidValue,
);
}
node.value = this.parseLiteral<N.StringLiteral>(
this.state.value,
"StringLiteral",
);
node.value = this.parseStringLiteral(this.state.value);
this.finishNode<N.ImportAttribute>(node, "ImportAttribute");
attrs.push(node);
} while (this.eat(tt.comma));
Expand Down Expand Up @@ -2345,7 +2339,7 @@ export default class StatementParser extends ExpressionParser {
Errors.ModuleAttributeInvalidValue,
);
}
node.value = this.parseLiteral(this.state.value, "StringLiteral");
node.value = this.parseStringLiteral(this.state.value);
this.finishNode(node, "ImportAttribute");
attrs.push(node);
} while (this.eat(tt.comma));
Expand Down Expand Up @@ -2424,12 +2418,13 @@ export default class StatementParser extends ExpressionParser {
// https://tc39.es/ecma262/#prod-ImportSpecifier
parseImportSpecifier(node: N.ImportDeclaration): void {
const specifier = this.startNode();
const importedIsString = this.match(tt.string);
specifier.imported = this.parseModuleExportName();
if (this.eatContextual("as")) {
specifier.local = this.parseIdentifier();
} else {
const { imported } = specifier;
if (imported.type === "StringLiteral") {
if (importedIsString) {
throw this.raise(
specifier.start,
Errors.ImportBindingIsString,
Expand Down
70 changes: 26 additions & 44 deletions packages/babel-parser/src/plugins/estree.js
@@ -1,6 +1,6 @@
// @flow

import { types as tt, TokenType } from "../tokenizer/types";
import { TokenType } from "../tokenizer/types";
import type Parser from "../parser";
import type { ExpressionErrors } from "../parser/util";
import * as N from "../types";
Expand All @@ -9,21 +9,21 @@ import { Errors } from "../parser/error";

export default (superClass: Class<Parser>): Class<Parser> =>
class extends superClass {
estreeParseRegExpLiteral({ pattern, flags }: N.RegExpLiteral): N.Node {
parseRegExpLiteral({ pattern, flags }): 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);
const node = this.estreeParseLiteral<N.EstreeRegExpLiteral>(regex);
node.regex = { pattern, flags };

return node;
}

estreeParseBigIntLiteral(value: any): N.Node {
parseBigIntLiteral(value: any): N.Node {
// https://github.com/estree/estree/blob/master/es2020.md#bigintliteral
let bigInt;
try {
Expand All @@ -32,13 +32,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} catch {
bigInt = null;
}
const node = this.estreeParseLiteral(bigInt);
const node = this.estreeParseLiteral<N.EstreeBigIntLiteral>(bigInt);
node.bigint = String(node.value || value);

return node;
}

estreeParseDecimalLiteral(value: any): N.Node {
parseDecimalLiteral(value: any): N.Node {
// https://github.com/estree/estree/blob/master/experimental/decimal.md
// todo: use BigDecimal when node supports it.
const decimal = null;
Expand All @@ -48,8 +48,24 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return node;
}

estreeParseLiteral(value: any): N.Node {
return this.parseLiteral(value, "Literal");
estreeParseLiteral<T: N.Node>(value: any) {
return this.parseLiteral<T>(value, "Literal");
}

parseStringLiteral(value: any): N.Node {
return this.estreeParseLiteral(value);
}

parseNumericLiteral(value: any): any {
return this.estreeParseLiteral(value);
}

parseNullLiteral(): N.Node {
return this.estreeParseLiteral(null);
}

parseBooleanLiteral(value: boolean): N.BooleanLiteral {
return this.estreeParseLiteral(value);
}

directiveToStmt(directive: N.Directive): N.ExpressionStatement {
Expand Down Expand Up @@ -165,35 +181,6 @@ export default (superClass: Class<Parser>): Class<Parser> =>
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);
}
}

parseMaybePrivateName(...args: [boolean]): any {
const node = super.parseMaybePrivateName(...args);
if (
Expand Down Expand Up @@ -230,13 +217,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return node.name;
}

parseLiteral<T: N.Literal>(
value: any,
type: /*T["kind"]*/ string,
startPos?: number,
startLoc?: Position,
): T {
const node = super.parseLiteral(value, type, startPos, startLoc);
parseLiteral<T: N.Node>(value: any, type: $ElementType<T, "type">): T {
const node = super.parseLiteral<T>(value, type);
node.raw = node.extra.raw;
delete node.extra;
Expand Down

0 comments on commit 461ba25

Please sign in to comment.