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/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/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..9a7ae0cf5918 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,16 @@ export default (superClass: Class): Class => 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): 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); 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", 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..8723c3f23444 --- /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": "1", + "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/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 }] + ] } diff --git a/packages/babel-parser/test/helpers/runFixtureTests.js b/packages/babel-parser/test/helpers/runFixtureTests.js index f08ed2312101..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"; @@ -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}'`; + } } } - } + }); }