@babel/eslint-parser: fix BigIntLiteral node to match ESTree spec (#10827)

* @babel/eslint-parser: fix BigIntLiteral node to match ESTree spec

* Move token conversion to @babel/eslint-parser

* Add estree plugin tests

* Update test helpers to handle BigInt serializing

* Update Literal union type to include BigIntLiteral

* Add FlowIgnore comment for BigInt
This commit is contained in:
Kai Cataldo
2019-12-07 20:59:18 -05:00
committed by Nicolò Ribaudo
parent b2429fe203
commit fb100eee41
12 changed files with 215 additions and 55 deletions

View File

@@ -34,6 +34,7 @@
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
"@babel/plugin-proposal-pipeline-operator": "^7.0.0",
"@babel/plugin-syntax-bigint": "^7.7.4",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-syntax-export-default-from": "^7.0.0",
"@babel/plugin-syntax-export-namespace-from": "^7.0.0",

View File

@@ -70,7 +70,6 @@ const astTransformVisitor = {
}
// modules
if (path.isImportDeclaration()) {
delete node.isType;
}

View File

@@ -77,6 +77,9 @@ export default function(token, tt, source) {
flags: value.flags,
};
token.value = `/${value.pattern}/${value.flags}`;
} else if (type === tt.bigint) {
token.type = "Numeric";
token.value = `${token.value}n`;
}
return token;

View File

@@ -27,7 +27,7 @@ function parseAndAssertSame(code) {
loc: true,
range: true,
comment: true,
ecmaVersion: 2018,
ecmaVersion: 2020,
sourceType: "module",
});
const babylonAST = parseForESLint(code, {
@@ -518,5 +518,11 @@ describe("babylon-to-espree", () => {
}
`);
});
it("BigInt", () => {
parseAndAssertSame(`
const a = 1n;
`);
});
});
});

View File

@@ -17,5 +17,6 @@ module.exports = {
"@babel/plugin-syntax-export-namespace-from",
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: false }],
["@babel/plugin-proposal-pipeline-operator", { proposal: "minimal" }],
"@babel/plugin-syntax-bigint",
],
};

View File

@@ -1,5 +1,7 @@
// @flow
/* global BigInt */
import { types as tt, TokenType } from "../tokenizer/types";
import type Parser from "../parser";
import * as N from "../types";
@@ -31,6 +33,16 @@ export default (superClass: Class<Parser>): Class<Parser> =>
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;
}
estreeParseLiteral(value: any): N.Node {
return this.parseLiteral(value, "Literal");
}
@@ -244,13 +256,16 @@ export default (superClass: Class<Parser>): Class<Parser> =>
parseExprAtom(refShorthandDefaultPos?: ?Pos): N.Expression {
switch (this.state.type) {
case tt.regexp:
return this.estreeParseRegExpLiteral(this.state.value);
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._null:
return this.estreeParseLiteral(null);

View File

@@ -96,7 +96,8 @@ export type Literal =
| NullLiteral
| StringLiteral
| BooleanLiteral
| NumericLiteral;
| NumericLiteral
| BigIntLiteral;
export type RegExpLiteral = NodeBase & {
type: "RegExpLiteral",

View File

@@ -0,0 +1 @@
const a = 1n;

View File

@@ -0,0 +1,102 @@
{
"type": "File",
"start": 0,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 13
}
},
"program": {
"type": "Program",
"start": 0,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 13
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 13
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 6,
"end": 12,
"loc": {
"start": {
"line": 1,
"column": 6
},
"end": {
"line": 1,
"column": 12
}
},
"id": {
"type": "Identifier",
"start": 6,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 6
},
"end": {
"line": 1,
"column": 7
},
"identifierName": "a"
},
"name": "a"
},
"init": {
"type": "Literal",
"start": 10,
"end": 12,
"loc": {
"start": {
"line": 1,
"column": 10
},
"end": {
"line": 1,
"column": 12
}
},
"value": "1",
"raw": "1n",
"bigint": "1"
}
}
],
"kind": "const"
}
]
}
}

View File

@@ -0,0 +1,6 @@
{
"plugins": [
"estree",
"bigInt"
]
}

View File

@@ -1,3 +1,6 @@
{
"plugins": ["classProperties", ["decorators", { "decoratorsBeforeExport": false }]]
"plugins": [
"classProperties",
["decorators", { "decoratorsBeforeExport": false }]
]
}

View File

@@ -1,3 +1,5 @@
/* global BigInt */
import { multiple as getFixtures } from "@babel/helper-fixtures";
import { codeFrameColumns } from "@babel/code-frame";
import fs from "fs";
@@ -61,7 +63,7 @@ export function runFixtureTests(fixturesPath, parseFunction) {
/^.*Got error message: /,
"",
);
fs.writeFileSync(fn, JSON.stringify(task.options, null, " "));
fs.writeFileSync(fn, JSON.stringify(task.options, null, 2));
}
}
@@ -107,19 +109,33 @@ export function runThrowTestsWithEstree(fixturesPath, parseFunction) {
}
function save(test, ast) {
// Ensure that RegExp and Errors are serialized as strings
forceToString(RegExp, () =>
forceToString(Error, () =>
fs.writeFileSync(test.expect.loc, JSON.stringify(ast, null, " ")),
),
overrideToJSON(() =>
fs.writeFileSync(test.expect.loc, JSON.stringify(ast, null, 2)),
);
}
function forceToString(obj, cb) {
const { toJSON } = obj.prototype;
obj.prototype.toJSON = obj.prototype.toString;
// Ensure that RegExp, BigInt, and Errors are serialized as strings
function overrideToJSON(cb) {
const originalToJSONMap = new Map();
const notJSONparseableObj = [RegExp, Error];
if (typeof BigInt !== "undefined") {
notJSONparseableObj.push(BigInt);
}
for (const obj of notJSONparseableObj) {
const { toJSON } = obj.prototype;
originalToJSONMap.set(obj, toJSON);
obj.prototype.toJSON = function() {
return this.toString();
};
}
cb();
obj.prototype.toJSON = toJSON;
for (const obj of notJSONparseableObj) {
obj.prototype.toJSON = originalToJSONMap.get(obj);
}
}
function runTest(test, parseFunction) {
@@ -143,7 +159,7 @@ function runTest(test, parseFunction) {
const fn = path.dirname(test.expect.loc) + "/options.json";
test.options = test.options || {};
test.options.throws = err.message;
fs.writeFileSync(fn, JSON.stringify(test.options, null, " "));
fs.writeFileSync(fn, JSON.stringify(test.options, null, 2));
return;
}
@@ -172,11 +188,11 @@ function runTest(test, parseFunction) {
const fn = path.dirname(test.expect.loc) + "/options.json";
test.options = test.options || {};
delete test.options.throws;
const contents = JSON.stringify(test.options, null, " ");
const contents = JSON.stringify(test.options, null, 2);
if (contents === "{}") {
fs.unlinkSync(fn);
} else {
fs.writeFileSync(fn, JSON.stringify(test.options, null, " "));
fs.writeFileSync(fn, JSON.stringify(test.options, null, 2));
}
test.expect.loc += "on";
return save(test, ast);
@@ -198,7 +214,6 @@ function runTest(test, parseFunction) {
}
function ppJSON(v) {
v = v instanceof RegExp || v instanceof Error ? v.toString() : v;
return JSON.stringify(v, null, 2);
}
@@ -211,42 +226,49 @@ function addPath(str, pt) {
}
function misMatch(exp, act) {
if (
exp instanceof RegExp ||
act instanceof RegExp ||
exp instanceof Error ||
act instanceof Error
) {
const left = ppJSON(exp);
const right = ppJSON(act);
if (left !== right) return left + " !== " + right;
} else if (Array.isArray(exp)) {
if (!Array.isArray(act)) return ppJSON(exp) + " != " + ppJSON(act);
if (act.length != exp.length) {
return "array length mismatch " + exp.length + " != " + act.length;
}
for (let i = 0; i < act.length; ++i) {
const mis = misMatch(exp[i], act[i]);
if (mis) return addPath(mis, i);
}
} else if (!exp || !act || typeof exp != "object" || typeof act != "object") {
if (exp !== act && typeof exp != "function") {
return ppJSON(exp) + " !== " + ppJSON(act);
}
} else {
for (const prop of Object.keys(exp)) {
const mis = misMatch(exp[prop], act[prop]);
if (mis) return addPath(mis, prop);
}
for (const prop of Object.keys(act)) {
if (typeof act[prop] === "function") {
continue;
overrideToJSON(() => {
if (
exp instanceof RegExp ||
act instanceof RegExp ||
exp instanceof Error ||
act instanceof Error
) {
const left = ppJSON(exp);
const right = ppJSON(act);
if (left !== right) return left + " !== " + right;
} else if (Array.isArray(exp)) {
if (!Array.isArray(act)) return ppJSON(exp) + " != " + ppJSON(act);
if (act.length != exp.length) {
return "array length mismatch " + exp.length + " != " + act.length;
}
for (let i = 0; i < act.length; ++i) {
const mis = misMatch(exp[i], act[i]);
if (mis) return addPath(mis, i);
}
} else if (
!exp ||
!act ||
typeof exp != "object" ||
typeof act != "object"
) {
if (exp !== act && typeof exp != "function") {
return ppJSON(exp) + " !== " + ppJSON(act);
}
} else {
for (const prop of Object.keys(exp)) {
const mis = misMatch(exp[prop], act[prop]);
if (mis) return addPath(mis, prop);
}
if (!(prop in exp) && act[prop] !== undefined) {
return `Did not expect a property '${prop}'`;
for (const prop of Object.keys(act)) {
if (typeof act[prop] === "function") {
continue;
}
if (!(prop in exp) && act[prop] !== undefined) {
return `Did not expect a property '${prop}'`;
}
}
}
}
});
}