refactor: add parse*Literal parser routines (#13333)

* 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>
This commit is contained in:
Huáng Jùnliàng 2021-05-19 16:00:24 -04:00 committed by GitHub
parent 053f94fc77
commit 461ba2531a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 308 additions and 101 deletions

View File

@ -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);
@ -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 {
@ -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

View File

@ -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,
@ -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(
@ -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);
}
@ -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));
@ -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));
@ -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,

View File

@ -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";
@ -9,7 +9,7 @@ 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);
@ -17,13 +17,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// 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 {
@ -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;
@ -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 {
@ -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 (
@ -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;

View File

@ -1536,7 +1536,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return this.finishNode(node, "FunctionTypeAnnotation");
case tt.string:
return this.parseLiteral(
return this.parseLiteral<N.StringLiteralTypeAnnotation>(
this.state.value,
"StringLiteralTypeAnnotation",
);
@ -1545,26 +1545,27 @@ export default (superClass: Class<Parser>): Class<Parser> =>
case tt._false:
node.value = this.match(tt._true);
this.next();
return this.finishNode(node, "BooleanLiteralTypeAnnotation");
return this.finishNode<N.BooleanLiteralTypeAnnotation>(
node,
"BooleanLiteralTypeAnnotation",
);
case tt.plusMin:
if (this.state.value === "-") {
this.next();
if (this.match(tt.num)) {
return this.parseLiteral(
return this.parseLiteralAtNode<N.NumberLiteralTypeAnnotation>(
-this.state.value,
"NumberLiteralTypeAnnotation",
node.start,
node.loc.start,
node,
);
}
if (this.match(tt.bigint)) {
return this.parseLiteral(
return this.parseLiteralAtNode<N.BigIntLiteralTypeAnnotation>(
-this.state.value,
"BigIntLiteralTypeAnnotation",
node.start,
node.loc.start,
node,
);
}
@ -2669,7 +2670,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// parse import-type/typeof shorthand
parseImportSpecifier(node: N.ImportDeclaration): void {
const specifier = this.startNode();
const firstIdentLoc = this.state.start;
const firstIdentIsString = this.match(tt.string);
const firstIdent = this.parseModuleExportName();
let specifierTypeKind = null;
@ -2713,13 +2714,15 @@ export default (superClass: Class<Parser>): Class<Parser> =>
specifier.local = specifier.imported.__clone();
}
} else {
if (firstIdent.type === "StringLiteral") {
if (firstIdentIsString) {
/*:: invariant(firstIdent instanceof N.StringLiteral) */
throw this.raise(
specifier.start,
Errors.ImportBindingIsString,
firstIdent.value,
);
}
/*:: invariant(firstIdent instanceof N.Node) */
isBinding = true;
specifier.imported = firstIdent;
specifier.importKind = null;
@ -2731,7 +2734,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
if (nodeIsTypeImport && specifierIsTypeImport) {
this.raise(
firstIdentLoc,
specifier.start,
FlowErrors.ImportTypeShorthandOnlyInPureImport,
);
}
@ -3388,14 +3391,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
const endOfInit = () => this.match(tt.comma) || this.match(tt.braceR);
switch (this.state.type) {
case tt.num: {
const literal = this.parseLiteral(this.state.value, "NumericLiteral");
const literal = this.parseNumericLiteral(this.state.value);
if (endOfInit()) {
return { type: "number", pos: literal.start, value: literal };
}
return { type: "invalid", pos: startPos };
}
case tt.string: {
const literal = this.parseLiteral(this.state.value, "StringLiteral");
const literal = this.parseStringLiteral(this.state.value);
if (endOfInit()) {
return { type: "string", pos: literal.start, value: literal };
}
@ -3403,7 +3406,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}
case tt._true:
case tt._false: {
const literal = this.parseBooleanLiteral();
const literal = this.parseBooleanLiteral(this.match(tt._true));
if (endOfInit()) {
return {
type: "boolean",

View File

@ -98,7 +98,8 @@ export type Literal =
| StringLiteral
| BooleanLiteral
| NumericLiteral
| BigIntLiteral;
| BigIntLiteral
| DecimalLiteral;
export type RegExpLiteral = NodeBase & {
type: "RegExpLiteral",
@ -130,6 +131,11 @@ export type BigIntLiteral = NodeBase & {
value: number,
};
export type DecimalLiteral = NodeBase & {
type: "DecimalLiteral",
value: number,
};
export type ParserOutput = {
comments: $ReadOnlyArray<Comment>,
errors: Array<ParsingError>,
@ -1066,7 +1072,44 @@ export type FlowOptionalIndexedAccessType = Node & {
optional: boolean,
};
export type StringLiteralTypeAnnotation = NodeBase & {
type: "StringLiteralTypeAnnotation",
value: string,
};
export type BooleanLiteralTypeAnnotation = NodeBase & {
type: "BooleanLiteralTypeAnnotation",
value: boolean,
};
export type NumberLiteralTypeAnnotation = NodeBase & {
type: "NumberLiteralTypeAnnotation",
value: number,
};
export type BigIntLiteralTypeAnnotation = NodeBase & {
type: "BigIntLiteralTypeAnnotation",
//todo(flow): use bigint when Flow supports BigInt
value: number,
};
// ESTree
export type EstreeLiteral = NodeBase & {
type: "Literal",
value: any,
};
type EstreeRegExpLiteralRegex = {
pattern: string,
flags: string,
};
export type EstreeRegExpLiteral = EstreeLiteral & {
regex: EstreeRegExpLiteralRegex,
};
export type EstreeBigIntLiteral = EstreeLiteral & {
value: number | null,
bigint: string,
};
export type EstreeProperty = NodeBase & {
type: "Property",

View File

@ -0,0 +1 @@
import foo from "foo.json" assert { type: "json" };

View File

@ -0,0 +1,4 @@
{
"plugins": ["flow", "jsx", "estree", "importAssertions"],
"sourceType": "module"
}

View File

@ -0,0 +1,51 @@
{
"type": "File",
"start":0,"end":51,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":51}},
"program": {
"type": "Program",
"start":0,"end":51,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":51}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ImportDeclaration",
"start":0,"end":51,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":51}},
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"start":7,"end":10,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":10}},
"local": {
"type": "Identifier",
"start":7,"end":10,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":10},"identifierName":"foo"},
"name": "foo"
}
}
],
"importKind": "value",
"source": {
"type": "Literal",
"start":16,"end":26,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":26}},
"value": "foo.json",
"raw": "\"foo.json\""
},
"assertions": [
{
"type": "ImportAttribute",
"start":36,"end":48,"loc":{"start":{"line":1,"column":36},"end":{"line":1,"column":48}},
"key": {
"type": "Identifier",
"start":36,"end":40,"loc":{"start":{"line":1,"column":36},"end":{"line":1,"column":40},"identifierName":"type"},
"name": "type"
},
"value": {
"type": "Literal",
"start":42,"end":48,"loc":{"start":{"line":1,"column":42},"end":{"line":1,"column":48}},
"value": "json",
"raw": "\"json\""
}
}
]
}
]
}
}

View File

@ -0,0 +1,2 @@
import { "foo" as bar, "default" as qux } from "module-a";
export * as "foo", { default as "quux" } from "module-b";

View File

@ -0,0 +1,3 @@
{
"sourceType": "module"
}

View File

@ -0,0 +1,96 @@
{
"type": "File",
"start":0,"end":116,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":57}},
"program": {
"type": "Program",
"start":0,"end":116,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":57}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ImportDeclaration",
"start":0,"end":58,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":58}},
"specifiers": [
{
"type": "ImportSpecifier",
"start":9,"end":21,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":21}},
"imported": {
"type": "Literal",
"start":9,"end":14,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":14}},
"value": "foo",
"raw": "\"foo\""
},
"importKind": null,
"local": {
"type": "Identifier",
"start":18,"end":21,"loc":{"start":{"line":1,"column":18},"end":{"line":1,"column":21},"identifierName":"bar"},
"name": "bar"
}
},
{
"type": "ImportSpecifier",
"start":23,"end":39,"loc":{"start":{"line":1,"column":23},"end":{"line":1,"column":39}},
"imported": {
"type": "Literal",
"start":23,"end":32,"loc":{"start":{"line":1,"column":23},"end":{"line":1,"column":32}},
"value": "default",
"raw": "\"default\""
},
"importKind": null,
"local": {
"type": "Identifier",
"start":36,"end":39,"loc":{"start":{"line":1,"column":36},"end":{"line":1,"column":39},"identifierName":"qux"},
"name": "qux"
}
}
],
"importKind": "value",
"source": {
"type": "Literal",
"start":47,"end":57,"loc":{"start":{"line":1,"column":47},"end":{"line":1,"column":57}},
"value": "module-a",
"raw": "\"module-a\""
}
},
{
"type": "ExportNamedDeclaration",
"start":59,"end":116,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":57}},
"specifiers": [
{
"type": "ExportNamespaceSpecifier",
"start":66,"end":76,"loc":{"start":{"line":2,"column":7},"end":{"line":2,"column":17}},
"exported": {
"type": "Literal",
"start":71,"end":76,"loc":{"start":{"line":2,"column":12},"end":{"line":2,"column":17}},
"value": "foo",
"raw": "\"foo\""
}
},
{
"type": "ExportSpecifier",
"start":80,"end":97,"loc":{"start":{"line":2,"column":21},"end":{"line":2,"column":38}},
"local": {
"type": "Identifier",
"start":80,"end":87,"loc":{"start":{"line":2,"column":21},"end":{"line":2,"column":28},"identifierName":"default"},
"name": "default"
},
"exported": {
"type": "Literal",
"start":91,"end":97,"loc":{"start":{"line":2,"column":32},"end":{"line":2,"column":38}},
"value": "quux",
"raw": "\"quux\""
}
}
],
"source": {
"type": "Literal",
"start":105,"end":115,"loc":{"start":{"line":2,"column":46},"end":{"line":2,"column":56}},
"value": "module-b",
"raw": "\"module-b\""
},
"declaration": null,
"exportKind": "value"
}
]
}
}