diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..39004fe --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# acorn5-object-spread changelog + +## 5.1.1 + +* Backport check for default values from acorn 5.3.0 + +## 5.1.0 + +* Make plugin compatible with acorn 5.3.x + +## 5.0.0 + +* Require acorn 5.2.x + +## 4.0.0 + +* Remove support for complex rest properties since they are forbidded by the + spec + +## 3.1.0 + +* Support complex rest properties like `{...{a = 5, ...as}}` +* Support rest properties in arrow function arguments +* Fail if rest property is not last property or has a trailing comma +* Detect duplicate exports with rest properties +* Don't complain about duplicate property names in patterns + +## 3.0.0 + +* Support rest properties +* Emit `SpreadElement` instead of `SpreadProperty` nodes diff --git a/README.md b/README.md index 2044a39..35f8e68 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,5 @@ -# ObjectSpread support in acorn +# Spread and rest properties support in acorn 5 -[![Build Status](https://travis-ci.org/UXtemple/acorn-object-spread.svg?branch=master)](https://travis-ci.org/UXtemple/acorn-object-spread) -[![NPM version](https://img.shields.io/npm/v/acorn-object-spread.svg)](https://www.npmjs.org/package/acorn-object-spread) +[![NPM version](https://img.shields.io/npm/v/acorn5-object-spread.svg)](https://www.npmjs.org/package/acorn5-object-spread) -This is plugin for [Acorn](http://marijnhaverbeke.nl/acorn/) - a tiny, fast JavaScript parser, written completely in JavaScript. - -## Usage - -You can use this module directly in order to get Acorn instance with plugin installed: - -```javascript -var acorn = require('acorn-object-spread'); -``` - -Or you can use `inject.js` for injecting plugin into your own version of Acorn like this: - -```javascript -var acorn = require('acorn-object-spread/inject')(require('./custom-acorn')); -``` - -Then, use the `plugins` option whenever you need to support objectSpread while parsing: - -```javascript -var ast = acorn.parse(code, { - plugins: { objectSpread: true } -}); -``` -## License - -This plugin is issued under the [MIT license](./LICENSE). - -With <3 by UXtemple. +Since spread and rest properties are part of ECMAScript 2018, acorn now supports them out of the box. Just make sure that you use acorn >= 5.4.1 and set `ecmaVersion` >= 9. This plugin is deprecated. diff --git a/inject.js b/inject.js index 424305c..2f95306 100644 --- a/inject.js +++ b/inject.js @@ -1,49 +1,99 @@ 'use strict'; module.exports = function(acorn) { + let acornVersion = acorn.version.match(/^5\.(\d+)\./) + if (!acornVersion || Number(acornVersion[1]) < 2) { + throw new Error("Unsupported acorn version " + acorn.version + ", please use acorn 5 >= 5.2"); + } var tt = acorn.tokTypes; - var pp = acorn.Parser.prototype; - // this is the same parseObj that acorn has with... - function parseObj(isPattern, refDestructuringErrors) { - let node = this.startNode(), first = true, propHash = {} - node.properties = [] - this.next() - while (!this.eat(tt.braceR)) { - if (!first) { - this.expect(tt.comma) - if (this.afterTrailingComma(tt.braceR)) break - } else first = false + const getCheckLVal = origCheckLVal => function (expr, bindingType, checkClashes) { + if (expr.type == "ObjectPattern") { + for (let prop of expr.properties) + this.checkLVal(prop, bindingType, checkClashes) + return + } else if (expr.type === "Property") { + // AssignmentProperty has type == "Property" + return this.checkLVal(expr.value, bindingType, checkClashes) + } + return origCheckLVal.apply(this, arguments) + } - let prop = this.startNode(), isGenerator, startPos, startLoc - if (this.options.ecmaVersion >= 6) { - // ...the spread logic borrowed from babylon :) - if (this.type === tt.ellipsis) { - prop = this.parseSpread() - prop.type = isPattern ? "RestProperty" : "SpreadProperty" - node.properties.push(prop) - continue + acorn.plugins.objectSpread = function objectSpreadPlugin(instance) { + instance.extend("parseProperty", nextMethod => function (isPattern, refDestructuringErrors) { + if (this.options.ecmaVersion >= 6 && this.type === tt.ellipsis) { + let prop + if (isPattern) { + prop = this.startNode() + this.next() + prop.argument = this.parseIdent() + this.finishNode(prop, "RestElement") + } else { + prop = this.parseSpread(refDestructuringErrors) + } + if (this.type === tt.comma) { + if (isPattern) { + this.raise(this.start, "Comma is not permitted after the rest element") + } else if (refDestructuringErrors && refDestructuringErrors.trailingComma < 0) { + refDestructuringErrors.trailingComma = this.start + } } + return prop + } - prop.method = false - prop.shorthand = false - if (isPattern || refDestructuringErrors) { - startPos = this.start - startLoc = this.startLoc + return nextMethod.apply(this, arguments) + }) + instance.extend("checkPropClash", nextMethod => function(prop, propHash) { + if (prop.type == "SpreadElement" || prop.type == "RestElement") return + return nextMethod.apply(this, arguments) + }) + instance.extend("checkLVal", getCheckLVal) + + // This backports toAssignable from 5.3.0 to 5.2.x + instance.extend("toAssignable", nextMethod => function(node, isBinding, refDestructuringErrors) { + if (this.options.ecmaVersion >= 6 && node) { + if (node.type == "ObjectExpression") { + node.type = "ObjectPattern" + if (refDestructuringErrors) this.checkPatternErrors(refDestructuringErrors, true) + for (let prop of node.properties) + this.toAssignable(prop, isBinding, refDestructuringErrors) + return node + } else if (node.type === "Property") { + // AssignmentProperty has type == "Property" + if (node.kind !== "init") this.raise(node.key.start, "Object pattern can't contain getter or setter") + return this.toAssignable(node.value, isBinding, refDestructuringErrors) + } else if (node.type === "SpreadElement") { + node.type = "RestElement" + this.toAssignable(node.argument, isBinding, refDestructuringErrors) + if (node.argument.type === "AssignmentPattern") + this.raise(node.argument.start, "Rest elements cannot have a default value") + return } - if (!isPattern) - isGenerator = this.eat(tt.star) } - this.parsePropertyName(prop) - this.parsePropertyValue(prop, isPattern, isGenerator, startPos, startLoc, refDestructuringErrors) - this.checkPropClash(prop, propHash) - node.properties.push(this.finishNode(prop, "Property")) - } - return this.finishNode(node, isPattern ? "ObjectPattern" : "ObjectExpression") - } + return nextMethod.apply(this, arguments) + }) + instance.extend("toAssignableList", nextMethod => function (exprList, isBinding) { + const result = nextMethod.call(this, exprList, isBinding) + if (exprList.length && exprList[exprList.length - 1] && exprList[exprList.length - 1].type === "RestElement") { + // Backport check from 5.3.0 + if (exprList[exprList.length - 1].argument.type === "AssignmentPattern") + this.raise(exprList[exprList.length - 1].argument.start, "Rest elements cannot have a default value") + } + return result + }) - acorn.plugins.objectSpread = function objectSpreadPlugin(instance) { - pp.parseObj = parseObj; + instance.extend("checkPatternExport", nextMethod => function(exports, pat) { + if (pat.type == "ObjectPattern") { + for (let prop of pat.properties) + this.checkPatternExport(exports, prop) + return + } else if (pat.type === "Property") { + return this.checkPatternExport(exports, pat.value) + } else if (pat.type === "RestElement") { + return this.checkPatternExport(exports, pat.argument) + } + nextMethod.apply(this, arguments) + }) }; return acorn; diff --git a/package.json b/package.json index a3bac7e..1b53eed 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,21 @@ { - "name": "acorn-object-spread", - "description": "Support object spread in acorn", - "homepage": "https://github.com/UXtemple/acorn-object-spread", - "author": "Darío Javier Cravero ", + "name": "acorn5-object-spread", + "description": "Support for rest and spread properties in acorn 5", + "homepage": "https://github.com/adrianheine/acorn5-object-spread", + "contributors": [ + "Darío Javier Cravero ", + "Adrian Heine " + ], "repository": { "type": "git", - "url": "https://github.com/UXtemple/acorn-object-spread" + "url": "https://github.com/adrianheine/acorn5-object-spread" }, "license": "MIT", "scripts": { "test": "node test/run.js" }, "dependencies": { - "acorn": "^3.1.0" + "acorn": "^5.2.1" }, - "devDependencies": { - "chai": "^3.0.0", - "mocha": "^2.2.5" - }, - "version": "1.0.0" + "version": "5.1.2" } diff --git a/test/driver.js b/test/driver.js index 921d739..49d7c5d 100644 --- a/test/driver.js +++ b/test/driver.js @@ -92,17 +92,11 @@ var misMatch = exports.misMatch = function(exp, act) { var mis = misMatch(exp[prop], act[prop]); if (mis) return addPath(mis, prop); } + for (var prop in act) { + if (!(prop in exp)) { + var mis = misMatch(exp[prop], act[prop]); + if (mis) return addPath(mis, prop); + } + } } }; - -function mangle(ast) { - if (typeof ast != "object" || !ast) return; - if (ast.slice) { - for (var i = 0; i < ast.length; ++i) mangle(ast[i]); - } else { - var loc = ast.start && ast.end && {start: ast.start, end: ast.end}; - if (loc) { delete ast.start; delete ast.end; } - for (var name in ast) if (ast.hasOwnProperty(name)) mangle(ast[name]); - if (loc) ast.loc = loc; - } -} diff --git a/test/tests-object-spread.js b/test/tests-object-spread.js index 541cf13..d570e47 100644 --- a/test/tests-object-spread.js +++ b/test/tests-object-spread.js @@ -7,90 +7,30 @@ var fbTestFixture = { "type": "VariableDeclaration", "start": 0, "end": 14, - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 1, - "column": 14 - } - }, "declarations": [ { "type": "VariableDeclarator", "start": 4, "end": 14, - "loc": { - "start": { - "line": 1, - "column": 4 - }, - "end": { - "line": 1, - "column": 14 - } - }, "id": { "type": "Identifier", "start": 4, "end": 5, - "loc": { - "start": { - "line": 1, - "column": 4 - }, - "end": { - "line": 1, - "column": 5 - } - }, "name": "z" }, "init": { "type": "ObjectExpression", "start": 8, "end": 14, - "loc": { - "start": { - "line": 1, - "column": 8 - }, - "end": { - "line": 1, - "column": 14 - } - }, "properties": [ { - "type": "SpreadProperty", + "type": "SpreadElement", "start": 9, "end": 13, - "loc": { - "start": { - "line": 1, - "column": 9 - }, - "end": { - "line": 1, - "column": 13 - } - }, "argument": { "type": "Identifier", "start": 12, "end": 13, - "loc": { - "start": { - "line": 1, - "column": 12 - }, - "end": { - "line": 1, - "column": 13 - } - }, "name": "x" } } @@ -104,76 +44,26 @@ var fbTestFixture = { "type": "ExpressionStatement", "start": 0, "end": 13, - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 1, - "column": 13 - } - }, "expression": { "type": "AssignmentExpression", "start": 0, "end": 13, - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 1, - "column": 13 - } - }, "operator": "=", "left": { "type": "Identifier", "start": 0, "end": 1, - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 1, - "column": 1 - } - }, "name": "z" }, "right": { "type": "ObjectExpression", "start": 4, "end": 13, - "loc": { - "start": { - "line": 1, - "column": 4 - }, - "end": { - "line": 1, - "column": 13 - } - }, "properties": [ { "type": "Property", "start": 5, "end": 6, - "loc": { - "start": { - "line": 1, - "column": 5 - }, - "end": { - "line": 1, - "column": 6 - } - }, "method": false, "shorthand": true, "computed": false, @@ -181,63 +71,24 @@ var fbTestFixture = { "type": "Identifier", "start": 5, "end": 6, - "loc": { - "start": { - "line": 1, - "column": 5 - }, - "end": { - "line": 1, - "column": 6 - } - }, "name": "x" }, "value": { "type": "Identifier", "start": 5, "end": 6, - "loc": { - "start": { - "line": 1, - "column": 5 - }, - "end": { - "line": 1, - "column": 6 - } - }, "name": "x" - } + }, + "kind": "init" }, { - "type": "SpreadProperty", + "type": "SpreadElement", "start": 8, "end": 12, - "loc": { - "start": { - "line": 1, - "column": 8 - }, - "end": { - "line": 1, - "column": 12 - } - }, "argument": { "type": "Identifier", "start": 11, "end": 12, - "loc": { - "start": { - "line": 1, - "column": 11 - }, - "end": { - "line": 1, - "column": 12 - } - }, "name": "y" } } @@ -249,45 +100,15 @@ var fbTestFixture = { "type": "ExpressionStatement", "start": 0, "end": 23, - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 1, - "column": 23 - } - }, "expression": { "type": "ObjectExpression", "start": 1, "end": 22, - "loc": { - "start": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 22 - } - }, "properties": [ { "type": "Property", "start": 2, "end": 3, - "loc": { - "start": { - "line": 1, - "column": 2 - }, - "end": { - "line": 1, - "column": 3 - } - }, "method": false, "shorthand": true, "computed": false, @@ -295,63 +116,24 @@ var fbTestFixture = { "type": "Identifier", "start": 2, "end": 3, - "loc": { - "start": { - "line": 1, - "column": 2 - }, - "end": { - "line": 1, - "column": 3 - } - }, "name": "x" }, "value": { "type": "Identifier", "start": 2, "end": 3, - "loc": { - "start": { - "line": 1, - "column": 2 - }, - "end": { - "line": 1, - "column": 3 - } - }, "name": "x" - } + }, + "kind": "init" }, { - "type": "SpreadProperty", + "type": "SpreadElement", "start": 5, "end": 9, - "loc": { - "start": { - "line": 1, - "column": 5 - }, - "end": { - "line": 1, - "column": 9 - } - }, "argument": { "type": "Identifier", "start": 8, "end": 9, - "loc": { - "start": { - "line": 1, - "column": 8 - }, - "end": { - "line": 1, - "column": 9 - } - }, "name": "y" } }, @@ -359,16 +141,6 @@ var fbTestFixture = { "type": "Property", "start": 11, "end": 12, - "loc": { - "start": { - "line": 1, - "column": 11 - }, - "end": { - "line": 1, - "column": 12 - } - }, "method": false, "shorthand": true, "computed": false, @@ -376,63 +148,24 @@ var fbTestFixture = { "type": "Identifier", "start": 11, "end": 12, - "loc": { - "start": { - "line": 1, - "column": 11 - }, - "end": { - "line": 1, - "column": 12 - } - }, "name": "a" }, "value": { "type": "Identifier", "start": 11, "end": 12, - "loc": { - "start": { - "line": 1, - "column": 11 - }, - "end": { - "line": 1, - "column": 12 - } - }, "name": "a" - } + }, + "kind": "init" }, { - "type": "SpreadProperty", + "type": "SpreadElement", "start": 14, "end": 18, - "loc": { - "start": { - "line": 1, - "column": 14 - }, - "end": { - "line": 1, - "column": 18 - } - }, "argument": { "type": "Identifier", "start": 17, "end": 18, - "loc": { - "start": { - "line": 1, - "column": 17 - }, - "end": { - "line": 1, - "column": 18 - } - }, "name": "b" } }, @@ -440,16 +173,6 @@ var fbTestFixture = { "type": "Property", "start": 20, "end": 21, - "loc": { - "start": { - "line": 1, - "column": 20 - }, - "end": { - "line": 1, - "column": 21 - } - }, "method": false, "shorthand": true, "computed": false, @@ -457,37 +180,335 @@ var fbTestFixture = { "type": "Identifier", "start": 20, "end": 21, - "loc": { - "start": { - "line": 1, - "column": 20 - }, - "end": { - "line": 1, - "column": 21 - } - }, "name": "c" }, "value": { "type": "Identifier", "start": 20, "end": 21, - "loc": { - "start": { - "line": 1, - "column": 20 + "name": "c" + }, + "kind": "init" + } + ] + } + }, + "var someObject = { someKey: { ...mapGetters([ 'some_val_1', 'some_val_2' ]) } }": { + "type": "VariableDeclaration", + "start": 0, + "end": 79, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 4, + "end": 79, + "id": { + "type": "Identifier", + "start": 4, + "end": 14, + "name": "someObject" + }, + "init": { + "type": "ObjectExpression", + "start": 17, + "end": 79, + "properties": [ + { + "type": "Property", + "start": 19, + "end": 77, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 19, + "end": 26, + "name": "someKey" + }, + "value": { + "type": "ObjectExpression", + "start": 28, + "end": 77, + "properties": [ + { + "type": "SpreadElement", + "start": 30, + "end": 75, + "argument": { + "type": "CallExpression", + "start": 33, + "end": 75, + "callee": { + "type": "Identifier", + "start": 33, + "end": 43, + "name": "mapGetters" + }, + "arguments": [ + { + "type": "ArrayExpression", + "start": 44, + "end": 74, + "elements": [ + { + "type": "Literal", + "start": 46, + "end": 58, + "value": "some_val_1", + "raw": "'some_val_1'" + }, + { + "type": "Literal", + "start": 60, + "end": 72, + "value": "some_val_2", + "raw": "'some_val_2'" + } + ] + } + ] + } + } + ] + }, + "kind": "init" + } + ] + } + } + ], + "kind": "var" + } + }, + 'ObjectRest': { + 'let {x, ...y} = v': { + "type": "VariableDeclaration", + "start": 0, + "end": 17, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 4, + "end": 17, + "id": { + "type": "ObjectPattern", + "start": 4, + "end": 13, + "properties": [ + { + "type": "Property", + "start": 5, + "end": 6, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 5, + "end": 6, + "name": "x" }, - "end": { - "line": 1, - "column": 21 + "kind": "init", + "value": { + "type": "Identifier", + "start": 5, + "end": 6, + "name": "x" } }, - "name": "c" - } + { + "type": "RestElement", + "start": 8, + "end": 12, + "argument": { + "type": "Identifier", + "start": 11, + "end": 12, + "name": "y" + } + } + ] + }, + "init": { + "type": "Identifier", + "start": 16, + "end": 17, + "name": "v" } - ] + } + ], + "kind": "let" + }, + '(function({x, ...y}) {})': { + "type": "ExpressionStatement", + "start": 0, + "end": 24, + "expression": { + "type": "FunctionExpression", + "start": 1, + "end": 23, + "id": null, + "generator": false, + "expression": false, + "params": [ + { + "type": "ObjectPattern", + "start": 10, + "end": 19, + "properties": [ + { + "type": "Property", + "start": 11, + "end": 12, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 11, + "end": 12, + "name": "x" + }, + "kind": "init", + "value": { + "type": "Identifier", + "start": 11, + "end": 12, + "name": "x" + } + }, + { + "type": "RestElement", + "start": 14, + "end": 18, + "argument": { + "type": "Identifier", + "start": 17, + "end": 18, + "name": "y" + } + } + ] + } + ], + "body": { + "type": "BlockStatement", + "start": 21, + "end": 23, + "body": [] + } } + }, + 'const fn = ({text = "default", ...props}) => text + props.children': { + "type": "VariableDeclaration", + "start": 0, + "end": 66, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 6, + "end": 66, + "id": { + "type": "Identifier", + "start": 6, + "end": 8, + "name": "fn" + }, + "init": { + "type": "ArrowFunctionExpression", + "start": 11, + "end": 66, + "id": null, + "generator": false, + "expression": true, + "params": [ + { + "type": "ObjectPattern", + "start": 12, + "end": 40, + "properties": [ + { + "type": "Property", + "start": 13, + "end": 29, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 13, + "end": 17, + "name": "text" + }, + "kind": "init", + "value": { + "type": "AssignmentPattern", + "start": 13, + "end": 29, + "left": { + "type": "Identifier", + "start": 13, + "end": 17, + "name": "text" + }, + "right": { + "type": "Literal", + "start": 20, + "end": 29, + "value": "default", + "raw": "\"default\"" + } + } + }, + { + "type": "RestElement", + "start": 31, + "end": 39, + "argument": { + "type": "Identifier", + "start": 34, + "end": 39, + "name": "props" + } + } + ] + } + ], + "body": { + "type": "BinaryExpression", + "start": 45, + "end": 66, + "left": { + "type": "Identifier", + "start": 45, + "end": 49, + "name": "text" + }, + "operator": "+", + "right": { + "type": "MemberExpression", + "start": 52, + "end": 66, + "object": { + "type": "Identifier", + "start": 52, + "end": 57, + "name": "props" + }, + "property": { + "type": "Identifier", + "start": 58, + "end": 66, + "name": "children" + }, + "computed": false + } + } + } + } + ], + "kind": "const" } } }; @@ -498,16 +519,30 @@ if (typeof exports !== "undefined") { var tokTypes = require("../").tokTypes; } +testFail("({get x() {}}) => {}", "Object pattern can't contain getter or setter (1:6)") +testFail("let {...x, ...y} = {}", "Comma is not permitted after the rest element (1:9)") +testFail("({...x,}) => z", "Comma is not permitted after the rest element (1:6)") +testFail("export const { foo, ...bar } = baz;\nexport const bar = 1;\n", "Identifier 'bar' has already been declared (2:13)", { + sourceType: "module" +}) +testFail("function ({...x,}) { z }", "Unexpected token (1:9)") +testFail("let {...{x, y}} = {}", "Unexpected token (1:8)") +testFail("let {...{...{x, y}}} = {}", "Unexpected token (1:8)") +testFail("0, {...rest, b} = {}", "Comma is not permitted after the rest element (1:11)") +testFail("(([a, ...b = 0]) => {})", "Rest elements cannot have a default value (1:9)") +testFail("(({a, ...b = 0}) => {})", "Rest elements cannot have a default value (1:9)") + for (var ns in fbTestFixture) { ns = fbTestFixture[ns]; for (var code in ns) { test(code, { type: 'Program', - body: [ns[code]] + body: [ns[code]], + start: 0, + end: code.length, + sourceType: "script" }, { - ecmaVersion: 7, - locations: true, - ranges: true + ecmaVersion: 7 }); } }