From 4dada50d04dbbd351a81aca41535e64af6a6d727 Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Thu, 5 Dec 2019 23:30:33 -0500 Subject: [PATCH 1/6] @babel/eslint-parser: fix BigIntLiteral node to match ESTree spec --- eslint/babel-eslint-parser/package.json | 1 + .../src/babylon-to-espree/convertAST.js | 1 - .../test/babel-eslint-parser.js | 8 ++++++- .../test/fixtures/config/babel.config.js | 1 + packages/babel-parser/src/plugins/estree.js | 24 ++++++++++++++++--- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/eslint/babel-eslint-parser/package.json b/eslint/babel-eslint-parser/package.json index e24970d36b8c..0205facb646d 100644 --- a/eslint/babel-eslint-parser/package.json +++ b/eslint/babel-eslint-parser/package.json @@ -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", diff --git a/eslint/babel-eslint-parser/src/babylon-to-espree/convertAST.js b/eslint/babel-eslint-parser/src/babylon-to-espree/convertAST.js index 4acce5fb5036..50d472803a0b 100644 --- a/eslint/babel-eslint-parser/src/babylon-to-espree/convertAST.js +++ b/eslint/babel-eslint-parser/src/babylon-to-espree/convertAST.js @@ -70,7 +70,6 @@ const astTransformVisitor = { } // modules - if (path.isImportDeclaration()) { delete node.isType; } diff --git a/eslint/babel-eslint-parser/test/babel-eslint-parser.js b/eslint/babel-eslint-parser/test/babel-eslint-parser.js index 37f15c03d310..d3aa67223b18 100644 --- a/eslint/babel-eslint-parser/test/babel-eslint-parser.js +++ b/eslint/babel-eslint-parser/test/babel-eslint-parser.js @@ -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; + `); + }); }); }); diff --git a/eslint/babel-eslint-parser/test/fixtures/config/babel.config.js b/eslint/babel-eslint-parser/test/fixtures/config/babel.config.js index 4d49158dba90..56a2f07d9707 100644 --- a/eslint/babel-eslint-parser/test/fixtures/config/babel.config.js +++ b/eslint/babel-eslint-parser/test/fixtures/config/babel.config.js @@ -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", ], }; diff --git a/packages/babel-parser/src/plugins/estree.js b/packages/babel-parser/src/plugins/estree.js index 60bc1effffa7..5c792bf71712 100644 --- a/packages/babel-parser/src/plugins/estree.js +++ b/packages/babel-parser/src/plugins/estree.js @@ -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,19 @@ export default (superClass: Class): Class => return node; } + estreeParseBigIntLiteral(value: any): N.Node { + // https://github.com/estree/estree/blob/master/es2020.md#bigintliteral + const bigInt = typeof BigInt !== "undefined" ? BigInt(value) : null; + + // Espree creates a "Numeric" token for BigIntLiterals. + this.state.type = tt.num; + + 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 +259,16 @@ export default (superClass: Class): Class => 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); From 444f51fe2a7a81be08171fabd4dfa7e1f40ca908 Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Fri, 6 Dec 2019 12:27:40 -0500 Subject: [PATCH 2/6] Move token conversion to @babel/eslint-parser --- .../src/babylon-to-espree/convertToken.js | 3 +++ packages/babel-parser/src/plugins/estree.js | 4 ---- .../options.json | 5 ++++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/eslint/babel-eslint-parser/src/babylon-to-espree/convertToken.js b/eslint/babel-eslint-parser/src/babylon-to-espree/convertToken.js index 46a8656dd0e1..54527136801a 100644 --- a/eslint/babel-eslint-parser/src/babylon-to-espree/convertToken.js +++ b/eslint/babel-eslint-parser/src/babylon-to-espree/convertToken.js @@ -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; diff --git a/packages/babel-parser/src/plugins/estree.js b/packages/babel-parser/src/plugins/estree.js index 5c792bf71712..6d150390315f 100644 --- a/packages/babel-parser/src/plugins/estree.js +++ b/packages/babel-parser/src/plugins/estree.js @@ -36,10 +36,6 @@ export default (superClass: Class): Class => estreeParseBigIntLiteral(value: any): N.Node { // https://github.com/estree/estree/blob/master/es2020.md#bigintliteral const bigInt = typeof BigInt !== "undefined" ? BigInt(value) : null; - - // Espree creates a "Numeric" token for BigIntLiterals. - this.state.type = tt.num; - const node = this.estreeParseLiteral(bigInt); node.bigint = String(node.value || value); diff --git a/packages/babel-parser/test/fixtures/experimental/class-properties/arguments-in-nested-class-decorator-call-expression/options.json b/packages/babel-parser/test/fixtures/experimental/class-properties/arguments-in-nested-class-decorator-call-expression/options.json index c61fa716aeb5..4ee4c944603d 100644 --- a/packages/babel-parser/test/fixtures/experimental/class-properties/arguments-in-nested-class-decorator-call-expression/options.json +++ b/packages/babel-parser/test/fixtures/experimental/class-properties/arguments-in-nested-class-decorator-call-expression/options.json @@ -1,3 +1,6 @@ { - "plugins": ["classProperties", ["decorators", { "decoratorsBeforeExport": false }]] + "plugins": [ + "classProperties", + ["decorators", { "decoratorsBeforeExport": false }] + ] } From ccd0c7d6798ac4e0eb27339883ddc5209890b84b Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Fri, 6 Dec 2019 13:05:34 -0500 Subject: [PATCH 3/6] Add estree plugin tests --- .../fixtures/estree/bigInt/basic/input.js | 1 + .../fixtures/estree/bigInt/basic/output.json | 102 ++++++++++++++++++ .../test/fixtures/estree/bigInt/options.json | 6 ++ .../test/helpers/runFixtureTests.js | 29 +++-- 4 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/estree/bigInt/basic/input.js create mode 100644 packages/babel-parser/test/fixtures/estree/bigInt/basic/output.json create mode 100644 packages/babel-parser/test/fixtures/estree/bigInt/options.json diff --git a/packages/babel-parser/test/fixtures/estree/bigInt/basic/input.js b/packages/babel-parser/test/fixtures/estree/bigInt/basic/input.js new file mode 100644 index 000000000000..b01de9749f99 --- /dev/null +++ b/packages/babel-parser/test/fixtures/estree/bigInt/basic/input.js @@ -0,0 +1 @@ +const a = 1n; diff --git a/packages/babel-parser/test/fixtures/estree/bigInt/basic/output.json b/packages/babel-parser/test/fixtures/estree/bigInt/basic/output.json new file mode 100644 index 000000000000..5f2108ad0862 --- /dev/null +++ b/packages/babel-parser/test/fixtures/estree/bigInt/basic/output.json @@ -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": null, + "raw": "1n", + "bigint": "1" + } + } + ], + "kind": "const" + } + ] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/estree/bigInt/options.json b/packages/babel-parser/test/fixtures/estree/bigInt/options.json new file mode 100644 index 000000000000..da7e9e174f39 --- /dev/null +++ b/packages/babel-parser/test/fixtures/estree/bigInt/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "estree", + "bigInt" + ] +} diff --git a/packages/babel-parser/test/helpers/runFixtureTests.js b/packages/babel-parser/test/helpers/runFixtureTests.js index f08ed2312101..e22584368be5 100644 --- a/packages/babel-parser/test/helpers/runFixtureTests.js +++ b/packages/babel-parser/test/helpers/runFixtureTests.js @@ -41,6 +41,11 @@ class FixtureError extends Error { } } +// https://github.com/estree/estree/blob/master/es2020.md#bigintliteral +function bigIntReplacer(key, value) { + return typeof value === "bigint" ? null : value; +} + export function runFixtureTests(fixturesPath, parseFunction) { const fixtures = getFixtures(fixturesPath); @@ -61,7 +66,10 @@ export function runFixtureTests(fixturesPath, parseFunction) { /^.*Got error message: /, "", ); - fs.writeFileSync(fn, JSON.stringify(task.options, null, " ")); + fs.writeFileSync( + fn, + JSON.stringify(task.options, bigIntReplacer, " "), + ); } } @@ -110,7 +118,10 @@ 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, " ")), + fs.writeFileSync( + test.expect.loc, + JSON.stringify(ast, bigIntReplacer, " "), + ), ), ); } @@ -143,7 +154,10 @@ 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, bigIntReplacer, " "), + ); return; } @@ -172,11 +186,14 @@ 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, bigIntReplacer, " "); if (contents === "{}") { fs.unlinkSync(fn); } else { - fs.writeFileSync(fn, JSON.stringify(test.options, null, " ")); + fs.writeFileSync( + fn, + JSON.stringify(test.options, bigIntReplacer, " "), + ); } test.expect.loc += "on"; return save(test, ast); @@ -199,7 +216,7 @@ function runTest(test, parseFunction) { function ppJSON(v) { v = v instanceof RegExp || v instanceof Error ? v.toString() : v; - return JSON.stringify(v, null, 2); + return JSON.stringify(v, bigIntReplacer, 2); } function addPath(str, pt) { From 45d2b073f8725fdba64d4eb327d136227558e6be Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Fri, 6 Dec 2019 15:07:53 -0500 Subject: [PATCH 4/6] Update test helpers to handle BigInt serializing --- .../fixtures/estree/bigInt/basic/output.json | 2 +- .../test/helpers/runFixtureTests.js | 137 +++++++++--------- 2 files changed, 72 insertions(+), 67 deletions(-) diff --git a/packages/babel-parser/test/fixtures/estree/bigInt/basic/output.json b/packages/babel-parser/test/fixtures/estree/bigInt/basic/output.json index 5f2108ad0862..8723c3f23444 100644 --- a/packages/babel-parser/test/fixtures/estree/bigInt/basic/output.json +++ b/packages/babel-parser/test/fixtures/estree/bigInt/basic/output.json @@ -89,7 +89,7 @@ "column": 12 } }, - "value": null, + "value": "1", "raw": "1n", "bigint": "1" } diff --git a/packages/babel-parser/test/helpers/runFixtureTests.js b/packages/babel-parser/test/helpers/runFixtureTests.js index e22584368be5..288ecbb2fe0a 100644 --- a/packages/babel-parser/test/helpers/runFixtureTests.js +++ b/packages/babel-parser/test/helpers/runFixtureTests.js @@ -1,3 +1,5 @@ +/* global BigInt */ + import { multiple as getFixtures } from "@babel/helper-fixtures"; import { codeFrameColumns } from "@babel/code-frame"; import fs from "fs"; @@ -41,11 +43,6 @@ class FixtureError extends Error { } } -// https://github.com/estree/estree/blob/master/es2020.md#bigintliteral -function bigIntReplacer(key, value) { - return typeof value === "bigint" ? null : value; -} - export function runFixtureTests(fixturesPath, parseFunction) { const fixtures = getFixtures(fixturesPath); @@ -66,10 +63,7 @@ export function runFixtureTests(fixturesPath, parseFunction) { /^.*Got error message: /, "", ); - fs.writeFileSync( - fn, - JSON.stringify(task.options, bigIntReplacer, " "), - ); + fs.writeFileSync(fn, JSON.stringify(task.options, null, 2)); } } @@ -115,22 +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, bigIntReplacer, " "), - ), - ), + 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) { @@ -154,10 +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, bigIntReplacer, " "), - ); + fs.writeFileSync(fn, JSON.stringify(test.options, null, 2)); return; } @@ -186,14 +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, bigIntReplacer, " "); + const contents = JSON.stringify(test.options, null, 2); if (contents === "{}") { fs.unlinkSync(fn); } else { - fs.writeFileSync( - fn, - JSON.stringify(test.options, bigIntReplacer, " "), - ); + fs.writeFileSync(fn, JSON.stringify(test.options, null, 2)); } test.expect.loc += "on"; return save(test, ast); @@ -215,8 +214,7 @@ function runTest(test, parseFunction) { } function ppJSON(v) { - v = v instanceof RegExp || v instanceof Error ? v.toString() : v; - return JSON.stringify(v, bigIntReplacer, 2); + return JSON.stringify(v, null, 2); } function addPath(str, pt) { @@ -228,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); + } + + 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}'`; + if (!(prop in exp) && act[prop] !== undefined) { + return `Did not expect a property '${prop}'`; + } } } - } + }); } From 99e066afe47c529488b3f440be6c3a1d70cec370 Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Fri, 6 Dec 2019 15:26:31 -0500 Subject: [PATCH 5/6] Update Literal union type to include BigIntLiteral --- packages/babel-parser/src/types.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/babel-parser/src/types.js b/packages/babel-parser/src/types.js index 82815b1443d6..71e83d142958 100644 --- a/packages/babel-parser/src/types.js +++ b/packages/babel-parser/src/types.js @@ -96,7 +96,8 @@ export type Literal = | NullLiteral | StringLiteral | BooleanLiteral - | NumericLiteral; + | NumericLiteral + | BigIntLiteral; export type RegExpLiteral = NodeBase & { type: "RegExpLiteral", From 4ead529e8e541d0ff206b660bb455aa9164c26b6 Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Sat, 7 Dec 2019 00:31:05 -0500 Subject: [PATCH 6/6] Add FlowIgnore comment for BigInt --- packages/babel-parser/src/plugins/estree.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/babel-parser/src/plugins/estree.js b/packages/babel-parser/src/plugins/estree.js index 6d150390315f..9a7ae0cf5918 100644 --- a/packages/babel-parser/src/plugins/estree.js +++ b/packages/babel-parser/src/plugins/estree.js @@ -35,6 +35,7 @@ export default (superClass: Class): Class => 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);