From 2ebed242b7e7100122c6a770df3cb21d920579a1 Mon Sep 17 00:00:00 2001 From: Timofey Kachalov Date: Thu, 6 Aug 2020 02:11:45 +0300 Subject: [PATCH] add support for optional chaining (#412) Co-authored-by: sanex3339 --- escodegen.js | 39 +++++++++++---- package.json | 5 +- test/compare-acorn-es2020.js | 27 ++++++----- .../optional-chaining.expected.js | 47 +++++++++++++++++++ .../optional-chaining.expected.min.js | 1 + .../compare-acorn-es2020/optional-chaining.js | 47 +++++++++++++++++++ 6 files changed, 143 insertions(+), 23 deletions(-) create mode 100644 test/compare-acorn-es2020/optional-chaining.expected.js create mode 100644 test/compare-acorn-es2020/optional-chaining.expected.min.js create mode 100644 test/compare-acorn-es2020/optional-chaining.js diff --git a/escodegen.js b/escodegen.js index 7a94557f..3656fa2c 100644 --- a/escodegen.js +++ b/escodegen.js @@ -99,11 +99,12 @@ Await: 14, Unary: 14, Postfix: 15, - Call: 16, - New: 17, - TaggedTemplate: 18, - Member: 19, - Primary: 20 + OptionalChaining: 16, + Call: 17, + New: 18, + TaggedTemplate: 19, + Member: 20, + Primary: 21 }; BinaryPrecedence = { @@ -1880,8 +1881,14 @@ CallExpression: function (expr, precedence, flags) { var result, i, iz; + // F_ALLOW_UNPARATH_NEW becomes false. result = [this.generateExpression(expr.callee, Precedence.Call, E_TTF)]; + + if (expr.optional) { + result.push('?.'); + } + result.push('('); for (i = 0, iz = expr['arguments'].length; i < iz; ++i) { result.push(this.generateExpression(expr['arguments'][i], Precedence.Assignment, E_TTT)); @@ -1894,9 +1901,20 @@ if (!(flags & F_ALLOW_CALL)) { return ['(', result, ')']; } + return parenthesize(result, Precedence.Call, precedence); }, + ChainExpression: function (expr, precedence, flags) { + if (Precedence.OptionalChaining < precedence) { + flags |= F_ALLOW_CALL; + } + + var result = this.generateExpression(expr.expression, Precedence.OptionalChaining, flags); + + return parenthesize(result, Precedence.OptionalChaining, precedence); + }, + NewExpression: function (expr, precedence, flags) { var result, length, i, iz, itemFlags; length = expr['arguments'].length; @@ -1931,11 +1949,15 @@ result = [this.generateExpression(expr.object, Precedence.Call, (flags & F_ALLOW_CALL) ? E_TTF : E_TFF)]; if (expr.computed) { + if (expr.optional) { + result.push('?.'); + } + result.push('['); result.push(this.generateExpression(expr.property, Precedence.Sequence, flags & F_ALLOW_CALL ? E_TTT : E_TFT)); result.push(']'); } else { - if (expr.object.type === Syntax.Literal && typeof expr.object.value === 'number') { + if (!expr.optional && expr.object.type === Syntax.Literal && typeof expr.object.value === 'number') { fragment = toSourceNodeWhenNeeded(result).toString(); // When the following conditions are all true, // 1. No floating point @@ -1952,7 +1974,7 @@ result.push(' '); } } - result.push('.'); + result.push(expr.optional ? '?.' : '.'); result.push(generateIdentifier(expr.property)); } @@ -2466,8 +2488,7 @@ this.generateExpression(expr.source, Precedence.Assignment, E_TTT), ')' ], Precedence.Call, precedence); - }, - + } }; merge(CodeGenerator.prototype, CodeGenerator.Expression); diff --git a/package.json b/package.json index 843d354b..2be7b901 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "url": "http://github.com/estools/escodegen.git" }, "dependencies": { - "estraverse": "^4.2.0", + "estraverse": "^5.2.0", "esutils": "^2.0.2", "esprima": "^4.0.1", "optionator": "^0.8.1" @@ -42,7 +42,8 @@ "acorn": "^8.0.1", "bluebird": "^3.4.7", "bower-registry-client": "^1.0.0", - "chai": "^3.5.0", + "chai": "^4.2.0", + "chai-exclude": "^2.0.2", "commonjs-everywhere": "^0.9.7", "gulp": "^3.8.10", "gulp-eslint": "^3.0.1", diff --git a/test/compare-acorn-es2020.js b/test/compare-acorn-es2020.js index f3c26091..25dd1b63 100644 --- a/test/compare-acorn-es2020.js +++ b/test/compare-acorn-es2020.js @@ -28,16 +28,16 @@ var fs = require('fs'), acorn = require('acorn'), escodegen = require('./loader'), chai = require('chai'), + chaiExclude = require('chai-exclude'), expect = chai.expect; -function test(code, expected) { - var tree, actual, options, StringObject; +chai.use(chaiExclude); - // alias, so that JSLint does not complain. - StringObject = String; +function test(code, expected) { + var tree, actual, actualTree, options; options = { - ranges: true, + ranges: false, locations: false, ecmaVersion: 11, sourceType: 'module' @@ -46,18 +46,18 @@ function test(code, expected) { tree = acorn.parse(code, options); // for UNIX text comment - actual = escodegen.generate(tree).replace(/[\n\r]$/, '') + '\n'; + actual = escodegen.generate(tree); + actualTree = acorn.parse(actual, options); + expect(actual).to.be.equal(expected); + expect(tree).excludingEvery(['start', 'end']).to.deep.equal(actualTree); } function testMin(code, expected) { - var tree, actual, options, StringObject; - - // alias, so that JSLint does not complain. - StringObject = String; + var tree, actual, actualTree, options; options = { - ranges: true, + ranges: false, locations: false, ecmaVersion: 11, sourceType: 'module' @@ -70,7 +70,10 @@ function testMin(code, expected) { format: escodegen.FORMAT_MINIFY, raw: false }).replace(/[\n\r]$/, '') + '\n'; + actualTree = acorn.parse(actual, options); + expect(actual).to.be.equal(expected); + expect(tree).excludingEvery(['start', 'end']).to.deep.equal(actualTree); } describe('compare acorn es2020 test', function () { @@ -91,4 +94,4 @@ describe('compare acorn es2020 test', function () { } }); }); -/* vim: set sw=4 ts=4 et tw=80 : */ +/* vim: set sw=4 ts=4 et tw=80 : */ \ No newline at end of file diff --git a/test/compare-acorn-es2020/optional-chaining.expected.js b/test/compare-acorn-es2020/optional-chaining.expected.js new file mode 100644 index 00000000..7428f592 --- /dev/null +++ b/test/compare-acorn-es2020/optional-chaining.expected.js @@ -0,0 +1,47 @@ +obj.aaa.bbb; +obj.aaa?.bbb; +obj?.aaa.bbb; +obj?.aaa?.bbb; +obj.aaa.bbb; +obj.aaa?.bbb; +(obj?.aaa).bbb; +(obj?.aaa)?.bbb; +(obj?.aaa).bbb.ccc.ddd; +((obj?.aaa).bbb?.ccc).ddd; +(obj?.aaa)?.bbb; +obj[aaa][bbb]; +obj[aaa]?.[bbb]; +obj?.[aaa][bbb]; +obj?.[aaa]?.[bbb]; +obj[aaa][bbb]; +obj[aaa]?.[bbb]; +(obj?.[aaa])[bbb]; +(obj?.[aaa])?.[bbb]; +obj[aaa][bbb][ccc][ddd]; +((obj?.[aaa])[bbb]?.[ccc])[ddd]; +1?.a; +obj()(); +obj()?.(); +obj?.()(); +obj?.()?.(); +obj()(); +obj()?.(); +(obj?.())(); +(obj?.())?.(); +obj()()()(); +((obj?.())()?.())(); +(a?.b)(); +a?.b(); +a?.b?.(); +(a?.b)?.(); +a?.().b; +(a?.()).b; +a?.b.c(); +(a?.b.c)(); +a.b?.().c; +(a.b?.()).c; +(a.b?.())?.c; +new (a?.b().c)(); +new (a?.b())(); +new (a?.b().c)(); +new (a?.b())(); \ No newline at end of file diff --git a/test/compare-acorn-es2020/optional-chaining.expected.min.js b/test/compare-acorn-es2020/optional-chaining.expected.min.js new file mode 100644 index 00000000..cfabe2b8 --- /dev/null +++ b/test/compare-acorn-es2020/optional-chaining.expected.min.js @@ -0,0 +1 @@ +obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;(obj?.aaa)?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];(obj?.[aaa])?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd];1?.a;obj()();obj()?.();obj?.()();obj?.()?.();obj()();obj()?.();(obj?.())();(obj?.())?.();obj()()()();((obj?.())()?.())();(a?.b)();a?.b();a?.b?.();(a?.b)?.();a?.().b;(a?.()).b;a?.b.c();(a?.b.c)();a.b?.().c;(a.b?.()).c;(a.b?.())?.c;new(a?.b().c);new(a?.b());new(a?.b().c);new(a?.b()) diff --git a/test/compare-acorn-es2020/optional-chaining.js b/test/compare-acorn-es2020/optional-chaining.js new file mode 100644 index 00000000..584776c9 --- /dev/null +++ b/test/compare-acorn-es2020/optional-chaining.js @@ -0,0 +1,47 @@ +obj.aaa.bbb; +obj.aaa?.bbb; +obj?.aaa.bbb; +obj?.aaa?.bbb; +(obj.aaa).bbb; +(obj.aaa)?.bbb; +(obj?.aaa).bbb; +(obj?.aaa)?.bbb; +((obj?.aaa).bbb.ccc).ddd; +((obj?.aaa).bbb?.ccc).ddd; +(obj?.aaa)?.bbb; +obj[aaa][bbb]; +obj[aaa]?.[bbb]; +obj?.[aaa][bbb]; +obj?.[aaa]?.[bbb]; +(obj[aaa])[bbb]; +(obj[aaa])?.[bbb]; +(obj?.[aaa])[bbb]; +(obj?.[aaa])?.[bbb]; +((obj[aaa])[bbb][ccc])[ddd]; +((obj?.[aaa])[bbb]?.[ccc])[ddd]; +1?.a; +obj()(); +obj()?.(); +obj?.()(); +obj?.()?.(); +(obj())(); +(obj())?.(); +(obj?.())(); +(obj?.())?.(); +((obj())()())(); +((obj?.())()?.())(); +(a?.b)(); +a?.b(); +a?.b?.(); +(a?.b)?.(); +a?.().b; +(a?.()).b; +a?.b.c(); +(a?.b.c)(); +a.b?.().c; +(a.b?.()).c; +(a.b?.())?.c; +new (a?.b().c); +new (a?.b()); +new (a?.b().c)(); +new (a?.b())();