From 57cdfaf1a196b02f293f3ffb754529880c9fcd2e Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Fri, 21 Jul 2017 13:04:31 +0530 Subject: [PATCH 01/21] Add syntax plugin for decorators2 --- .../.npmignore | 3 ++ .../README.md | 35 +++++++++++++++++++ .../package.json | 13 +++++++ .../src/index.js | 7 ++++ 4 files changed, 58 insertions(+) create mode 100644 packages/babel-plugin-syntax-decorators-2/.npmignore create mode 100644 packages/babel-plugin-syntax-decorators-2/README.md create mode 100644 packages/babel-plugin-syntax-decorators-2/package.json create mode 100644 packages/babel-plugin-syntax-decorators-2/src/index.js diff --git a/packages/babel-plugin-syntax-decorators-2/.npmignore b/packages/babel-plugin-syntax-decorators-2/.npmignore new file mode 100644 index 000000000000..f9806945836e --- /dev/null +++ b/packages/babel-plugin-syntax-decorators-2/.npmignore @@ -0,0 +1,3 @@ +src +test +*.log diff --git a/packages/babel-plugin-syntax-decorators-2/README.md b/packages/babel-plugin-syntax-decorators-2/README.md new file mode 100644 index 000000000000..99b86968b3e4 --- /dev/null +++ b/packages/babel-plugin-syntax-decorators-2/README.md @@ -0,0 +1,35 @@ +# babel-plugin-syntax-decorators-2 + +> Updated parsing of decorators. + +## Installation + +```sh +npm install --save-dev babel-plugin-syntax-decorators-2 +``` + +## Usage + +### Via `.babelrc` (Recommended) + +**.babelrc** + +```json +{ + "plugins": ["syntax-decorators-2"] +} +``` + +### Via CLI + +```sh +babel --plugins syntax-decorators-2 script.js +``` + +### Via Node API + +```javascript +require("babel-core").transform("code", { + plugins: ["syntax-decorators-2"] +}); +``` diff --git a/packages/babel-plugin-syntax-decorators-2/package.json b/packages/babel-plugin-syntax-decorators-2/package.json new file mode 100644 index 000000000000..005833819675 --- /dev/null +++ b/packages/babel-plugin-syntax-decorators-2/package.json @@ -0,0 +1,13 @@ +{ + "name": "babel-plugin-syntax-decorators-2", + "version": "7.0.0-alpha.15", + "description": "Updated parsing of decorators", + "repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-decorators-2", + "license": "MIT", + "main": "lib/index.js", + "keywords": [ + "babel-plugin" + ], + "dependencies": {}, + "devDependencies": {} +} diff --git a/packages/babel-plugin-syntax-decorators-2/src/index.js b/packages/babel-plugin-syntax-decorators-2/src/index.js new file mode 100644 index 000000000000..4d0405790040 --- /dev/null +++ b/packages/babel-plugin-syntax-decorators-2/src/index.js @@ -0,0 +1,7 @@ +export default function() { + return { + manipulateOptions(opts, parserOpts) { + parserOpts.plugins.push("decorators2"); + }, + }; +} From 9904b61f6e1b75f15208f1eb0e1c02076ccba1ea Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Fri, 21 Jul 2017 13:08:38 +0530 Subject: [PATCH 02/21] WIP decorator transform initial commit --- .../package.json | 20 +++ .../src/index.js | 125 ++++++++++++++++++ .../test/fixtures/options.json | 3 + .../test/fixtures/wip/wip/actual.js | 7 + .../test/fixtures/wip/wip/expected.js | 26 ++++ .../test/index.js | 3 + 6 files changed, 184 insertions(+) create mode 100644 packages/babel-plugin-transform-decorators-2/package.json create mode 100644 packages/babel-plugin-transform-decorators-2/src/index.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/options.json create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/actual.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/index.js diff --git a/packages/babel-plugin-transform-decorators-2/package.json b/packages/babel-plugin-transform-decorators-2/package.json new file mode 100644 index 000000000000..e42296272eec --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/package.json @@ -0,0 +1,20 @@ +{ + "name": "babel-plugin-transform-decorators-2", + "version": "7.0.0-alpha.15", + "author": "Peeyush Kushwaha ", + "license": "MIT", + "description": "Transformer for stage-2 decorators", + "repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-decorators-2", + "main": "lib/index.js", + "keywords": [ + "babel", + "babel-plugin", + "decorators" + ], + "dependencies": { + "babel-plugin-syntax-decorators-2": "7.0.0-alpha.15" + }, + "devDependencies": { + "babel-helper-plugin-test-runner": "7.0.0-alpha.15" + } +} diff --git a/packages/babel-plugin-transform-decorators-2/src/index.js b/packages/babel-plugin-transform-decorators-2/src/index.js new file mode 100644 index 000000000000..59598df8c930 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/src/index.js @@ -0,0 +1,125 @@ +import syntaxDecorators2 from "babel-plugin-syntax-decorators-2"; + +//TODO: will have to check for dot (.) access to reserved keywords in decorator members +export default function({ types: t }) { + // goes over the methods of current class and prepares decorators + // expects path of a ClassExpression + function takeMethodDecorators(path) { + const body = path.get("body.body"); + + // a collection of [decoratedKey, decorators] or [decoratedKey, decorators, true] for static properties + const entries = []; + + for (const method of body) { + const { node } = method; + if (!method.isClassMethod()) continue; + if (!node.decorators || !method.node.decorators.length) continue; + + const decorators = method.node.decorators + .map(d => d.expression) + .reverse(); // reverse for correct evaluation order + node.decorators = []; //TODO: should we remove from path? method.get("decorators") doesn't work + + const entry = []; + if (node.computed) { + // if it is computed, we need an identifier to refer to it later + // so we can avoid evaluating the expression twice + if (t.isAssignmentExpression(node.key)) { + entry.push(node.key.left); + } else { + const parent = path.findParent(p => p.parentPath.isBlock()); + const ref = method.scope.generateUidIdentifier("key"); + parent.insertBefore( + t.variableDeclaration("let", [t.variableDeclarator(ref)]), + ); + const replacement = t.sequenceExpression([ + t.assignmentExpression("=", ref, node.key), + ]); + //method.get("key").replaceWith(); FIXME: this should work + node.key = replacement; + entry.push(ref); + } + } else { + entry.push(t.stringLiteral(node.key.name)); + } + + entry.push(t.arrayExpression(decorators)); + + if (node.static) { + entry.push(t.booleanLiteral(true)); + } + + entries.push(t.arrayExpression(entry)); + } + + return t.arrayExpression(entries); + } + + // expects path of a ClassExpression + function takeClassDecorators(path) { + // reverse for correct decorator evaluation order + const decorators = path.node.decorators.map(d => d.expression).reverse(); + path.node.decorators = []; + return t.arrayExpression(decorators); + } + + return { + inherits: syntaxDecorators2, + visitor: { + // export default is a special case since it expects and expression rather than a declaration + ExportDefaultDeclaration(path) { + const classPath = path.get("declaration"); + if (!classPath.isClassDeclaration()) return; + + if (classPath.node.id) { + const ref = classPath.node.id; + path.insertBefore( + t.variableDeclaration("let", [t.variableDeclarator(ref)]), + ); + classPath.replaceWith( + t.assignmentExpression("=", ref, t.toExpression(classPath.node)), + ); + } else { + classPath.replaceWith(t.toExpression(classPath)); + } + }, + + // replace declaration with a let declaration + ClassDeclaration(path) { + const { node } = path; + const ref = node.id || path.scope.generateUidIdentifier("class"); + + path.replaceWith( + t.variableDeclaration("let", [ + t.variableDeclarator(ref, t.toExpression(node)), + ]), + ); + }, + + ClassExpression(path) { + const methodDecorators = takeMethodDecorators(path); + const classDecorators = takeClassDecorators(path); + if ( + methodDecorators.elements.length == 0 && + classDecorators.elements.length == 0 + ) { + return; + } + path.replaceWith( + t.callExpression(t.identifier("decorate"), [ + path.node, + methodDecorators, + classDecorators, + ]), + ); + }, + + ClassMethod(path) { + if (path.get("decorators")) { + path.get("decorators").forEach(p => p.remove()); + path.get("body").unshiftContainer("body", t.identifier("yo")); + } + }, + }, + }; +} diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/options.json b/packages/babel-plugin-transform-decorators-2/test/fixtures/options.json new file mode 100644 index 000000000000..46ff9f3d5227 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["transform-decorators-2"] +} diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/actual.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/actual.js new file mode 100644 index 000000000000..f3913b9db341 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/actual.js @@ -0,0 +1,7 @@ +let decorate = () => void 0, dec, bar, foo = {bar: () => void 0}, baz, decorator; //just to supress ReferenceErrors + +@decorator class Bizz { + @dec m1() {}; + @bar @foo.bar(baz) m2() {}; + @dec static [3 + 7] () {}; +} diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js new file mode 100644 index 000000000000..c6acc0291e8a --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js @@ -0,0 +1,26 @@ +let decorate = () => void 0, + dec, + bar, + foo = { + bar: () => void 0 +}, + baz, + decorator; //just to supress ReferenceErrors + + +let _key; + +let Bizz = decorate(class Bizz { + m1() { + yo + } + + m2() { + yo + } + + static [(_key = 3 + 7)]() { + yo + } + +}, [["m1", [dec]], ["m2", [foo.bar(baz), bar]], [_key, [dec], true]], [decorator]); diff --git a/packages/babel-plugin-transform-decorators-2/test/index.js b/packages/babel-plugin-transform-decorators-2/test/index.js new file mode 100644 index 000000000000..09cfbc31f54e --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/index.js @@ -0,0 +1,3 @@ +import runner from "babel-helper-plugin-test-runner"; + +runner(__dirname); From f214d1dab25ed665bc66e65faa73b9a791799ae4 Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Mon, 31 Jul 2017 06:02:31 +0530 Subject: [PATCH 03/21] wip commiting code before major changes --- .../src/index.js | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/packages/babel-plugin-transform-decorators-2/src/index.js b/packages/babel-plugin-transform-decorators-2/src/index.js index 59598df8c930..deac82e2b8f8 100644 --- a/packages/babel-plugin-transform-decorators-2/src/index.js +++ b/packages/babel-plugin-transform-decorators-2/src/index.js @@ -1,7 +1,141 @@ +import template from 'babel-template'; import syntaxDecorators2 from "babel-plugin-syntax-decorators-2"; //TODO: will have to check for dot (.) access to reserved keywords in decorator members +//NOTE: convention is 'descriptor' is used for element descriptors, while 'propertyDescriptor' is used for property descriptor + +/** manual testing code +class A { method() {console.log("method exec hua");} static estatic() {console.log("estatic hua");}} +function methDec(descriptor) {console.log("methDec hua", arguments); return {descriptor, extras: [], finishers: []}} +function methDec2(descriptor) {console.log("methDec2 hua", arguments); return {descriptor, extras: [], finishers: []}} +function classDec(constructor, heritage, memberDescriptors) {console.log("class hua", arguments); return {constructor, elements: memberDescriptors, finishers: []}} +decorate(A, [], [["method", [methDec, methDec2]], ["estatic", [methDec], true]])([classDec]) +**/ export default function({ types: t }) { + + // injected + function makeElementDescriptor(kind, key, isStatic, descriptor, finisher) { + return {kind, key, isStatic, descriptor, finisher}; + } + + // injected + function decorateElement(descriptor, decorators) { //spec uses the param "element" instead of "descriptor" and finds descriptor from it + let extras = []; + let finishers = []; + + let previousDescriptor = descriptor; + + for (let i = decorators.length - 1; i >= 0; i--) { + let decorator = decorators[i]; + let result = decorator(previousDescriptor); + let currentDescriptor = result.descriptor; + + if (result.finisher) { + finishers.push(current.finisher); + result.finisher = undefined; + } + + previousDescriptor = currentDescriptor; + + let extrasObject = result.extras; + + if (extrasObject) { + for (let extra of extrasObject) { + extras.push(extra); + } + } + } + + extras = mergeDuplicateElements(extras); + + return {descriptor: previousDescriptor, extras, finishers} + } + + // injected + function mergeDuplicateElements(elements) { return elements; /*TODO*/} + + // injected + function decorateClass(constructor, decorators, heritage, elementDescriptors) { + let elements = []; + let finishers = []; + + let previousConstructor = constructor; + let previousDescriptors = elementDescriptors; + + for (let i = decorators.length - 1; i >= 0; i--) { + let decorator = decorators[i]; + let result = decorator(previousConstructor, heritage, previousDescriptors); + + previousConstructor = result.constructor; + if (result.finishers) {// result.finishers is called 'finisher' in the spec + finishers = finishers.concat(result.finishers); + } + + if (result.elements) { + for (let element of result.elements) { + elements.push(element); + } + } + + elements = mergeDuplicateElements(elements); + } + + return {constructor: previousConstructor, elements, finishers}; + } + + function decorate(constructor, undecorated, memberDecorators, heritage) { + const prototype = constructor.prototype; + let finishers = []; + const elementDescriptors = {}; // elementDescriptors is meant to be an array, so this will be converted later + //TODO: merging of elementDescriptors + + for (let [key, isStatic] of undecorated) { + const target = isStatic? constructor : prototype; + let propertyDescriptor = Object.getOwnPropertyDescriptor(target, key); + elementDescriptors[key] = makeElementDescriptor("property", key, isStatic, propertyDescriptor); + } + + for (let [key, decorators, isStatic] of memberDecorators) { + let target = isStatic? constructor : prototype; + let propertyDescriptor = elementDescriptors[key] || Object.getOwnPropertyDescriptor(target, key); + let elementDescriptor = makeElementDescriptor("property", key, isStatic, propertyDescriptor); + let decorated = decorateElement(elementDescriptor, decorators); + + elementDescriptors[key] = decorated.descriptor; + + for (let extra of decorated.extras) { // extras is an array of element descriptors + elementDescriptors[extra.key] = extra; + } + + finishers = finishers.concat(decorated.finishers); + } + + return function (classDecorators) { + let result = decorateClass(constructor, classDecorators, heritage, Object.values(elementDescriptors)); + finishers = finishers.concat(result.finishers); + //TODO: heritage hacks so result.constructor has the correct prototype and instanceof results + //TODO: step 38 and 39, what do they mean "initialize"? + + for (let elementDescriptor of result.elements) { + let target = elementDescriptor.isStatic? constructor : prototype; + Object.defineOwnProperty(target, elementDescriptor.key, elementDescriptor.descriptor); + } + + return result.constructor; + } + } + + + function classDefintionEvaluation(path) { + // step 20, 21 + for (const method of body) { + const { node } = method; + if (!method.isClassMethod()) continue; + if (!node.decorators || !method.node.decorators.length) continue; + if (node.kind === "constructor") continue; + } + } + // goes over the methods of current class and prepares decorators // expects path of a ClassExpression function takeMethodDecorators(path) { From edb7ff208ee7b83df3288cf921dac27d2694aaac Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Mon, 31 Jul 2017 19:41:32 +0530 Subject: [PATCH 04/21] Transform works great, move stuff to helpers --- packages/babel-helpers/src/helpers.js | 148 ++++++++++++ .../src/index.js | 213 ++++++------------ .../test/fixtures/options.json | 2 +- .../test/fixtures/wip/wip/actual.js | 2 + .../test/fixtures/wip/wip/expected.js | 20 +- 5 files changed, 227 insertions(+), 158 deletions(-) diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index 8ff523b25250..7d5b5d4fc27e 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -270,6 +270,141 @@ helpers.createClass = template(` })() `); +helpers.decorate = template(` + (function (constructor, undecorated, memberDecorators, heritage) { + const prototype = constructor.prototype; + let finishers = []; + const elementDescriptors = {}; // elementDescriptors is meant to be an array, so this will be converted later + //TODO: merging of elementDescriptors + + for (const [key, isStatic] of undecorated) { + const target = isStatic ? constructor : prototype; + const propertyDescriptor = Object.getOwnPropertyDescriptor(target, key); + elementDescriptors[key] = makeElementDescriptor( + "property", + key, + isStatic, + propertyDescriptor, + ); + } + + for (const [key, decorators, isStatic] of memberDecorators) { + const target = isStatic ? constructor : prototype; + const propertyDescriptor = + elementDescriptors[key] || Object.getOwnPropertyDescriptor(target, key); + const elementDescriptor = makeElementDescriptor( + "property", + key, + isStatic, + propertyDescriptor, + ); + const decorated = decorateElement(elementDescriptor, decorators); + + elementDescriptors[key] = decorated.descriptor; + + for (const extra of decorated.extras) { + // extras is an array of element descriptors + elementDescriptors[extra.key] = extra; + } + + finishers = finishers.concat(decorated.finishers); + } + + return function(classDecorators) { + const result = decorateClass( + constructor, + classDecorators, + heritage, + Object.values(elementDescriptors), + ); + finishers = finishers.concat(result.finishers); + //TODO: heritage hacks so result.constructor has the correct prototype and instanceof results + //TODO: step 38 and 39, what do they mean "initialize"? + + for (const elementDescriptor of result.elements) { + const target = elementDescriptor.isStatic ? constructor : prototype; + Object.defineOwnProperty( + target, + elementDescriptor.key, + elementDescriptor.descriptor, + ); + } + + return result.constructor; + }; + }); +`); + +helpers.decorateElement = template(` + (function (descriptor, decorators) { + //spec uses the param "element" instead of "descriptor" and finds descriptor from it + let extras = []; + const finishers = []; + + let previousDescriptor = descriptor; + + for (let i = decorators.length - 1; i >= 0; i--) { + const decorator = decorators[i]; + const result = decorator(previousDescriptor); + const currentDescriptor = result.descriptor; + + if (result.finisher) { + finishers.push(current.finisher); + result.finisher = undefined; + } + + previousDescriptor = currentDescriptor; + + const extrasObject = result.extras; + + if (extrasObject) { + for (const extra of extrasObject) { + extras.push(extra); + } + } + } + + extras = mergeDuplicateElements(extras); + + return { descriptor: previousDescriptor, extras, finishers }; + }); +`); + +helpers.decorateClass = template(` + (function (constructor, decorators, heritage, elementDescriptors) { + let elements = []; + let finishers = []; + + let previousConstructor = constructor; + const previousDescriptors = elementDescriptors; + + for (let i = decorators.length - 1; i >= 0; i--) { + const decorator = decorators[i]; + const result = decorator( + previousConstructor, + heritage, + previousDescriptors, + ); + + previousConstructor = result.constructor; + if (result.finishers) { + // result.finishers is called 'finisher' in the spec + finishers = finishers.concat(result.finishers); + } + + if (result.elements) { + for (const element of result.elements) { + elements.push(element); + } + } + + elements = mergeDuplicateElements(elements); + } + + return { constructor: previousConstructor, elements, finishers }; + }); +`); + helpers.defineEnumerableProperties = template(` (function (obj, descs) { for (var key in descs) { @@ -417,6 +552,19 @@ helpers.interopRequireWildcard = template(` }) `); +helpers.makeElementDescriptor = template(` + (function (kind, key, isStatic, descriptor, finisher) { + return { kind, key, isStatic, descriptor, finisher }; + }); +`); + +//TODO +helpers.mergeDuplicateElements = template(` + (function (elements) { + return elements; + }); +`); + helpers.newArrowCheck = template(` (function (innerThis, boundThis) { if (innerThis !== boundThis) { diff --git a/packages/babel-plugin-transform-decorators-2/src/index.js b/packages/babel-plugin-transform-decorators-2/src/index.js index deac82e2b8f8..589ef67ed64f 100644 --- a/packages/babel-plugin-transform-decorators-2/src/index.js +++ b/packages/babel-plugin-transform-decorators-2/src/index.js @@ -1,8 +1,10 @@ -import template from 'babel-template'; +import template from "babel-template"; import syntaxDecorators2 from "babel-plugin-syntax-decorators-2"; //TODO: will have to check for dot (.) access to reserved keywords in decorator members //NOTE: convention is 'descriptor' is used for element descriptors, while 'propertyDescriptor' is used for property descriptor +//TODO: check if we need the 'define' + 'Property' and 'ke' + 'ys' hacks as seen in +//https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy/blob/master/src/index.js#L69 /** manual testing code class A { method() {console.log("method exec hua");} static estatic() {console.log("estatic hua");}} @@ -12,127 +14,24 @@ function classDec(constructor, heritage, memberDescriptors) {console.log("class decorate(A, [], [["method", [methDec, methDec2]], ["estatic", [methDec], true]])([classDec]) **/ export default function({ types: t }) { + // converts [(expression)] to [(let key = (expression))] if expression is impure + // so as to avoid recomputation when the key is needed later + function addKeysToComputedMembers(path) { + const body = path.get("body.body"); - // injected - function makeElementDescriptor(kind, key, isStatic, descriptor, finisher) { - return {kind, key, isStatic, descriptor, finisher}; - } - - // injected - function decorateElement(descriptor, decorators) { //spec uses the param "element" instead of "descriptor" and finds descriptor from it - let extras = []; - let finishers = []; - - let previousDescriptor = descriptor; - - for (let i = decorators.length - 1; i >= 0; i--) { - let decorator = decorators[i]; - let result = decorator(previousDescriptor); - let currentDescriptor = result.descriptor; - - if (result.finisher) { - finishers.push(current.finisher); - result.finisher = undefined; - } - - previousDescriptor = currentDescriptor; - - let extrasObject = result.extras; - - if (extrasObject) { - for (let extra of extrasObject) { - extras.push(extra); - } - } - } - - extras = mergeDuplicateElements(extras); - - return {descriptor: previousDescriptor, extras, finishers} - } - - // injected - function mergeDuplicateElements(elements) { return elements; /*TODO*/} - - // injected - function decorateClass(constructor, decorators, heritage, elementDescriptors) { - let elements = []; - let finishers = []; - - let previousConstructor = constructor; - let previousDescriptors = elementDescriptors; - - for (let i = decorators.length - 1; i >= 0; i--) { - let decorator = decorators[i]; - let result = decorator(previousConstructor, heritage, previousDescriptors); - - previousConstructor = result.constructor; - if (result.finishers) {// result.finishers is called 'finisher' in the spec - finishers = finishers.concat(result.finishers); - } - - if (result.elements) { - for (let element of result.elements) { - elements.push(element); - } - } - - elements = mergeDuplicateElements(elements); - } - - return {constructor: previousConstructor, elements, finishers}; - } - - function decorate(constructor, undecorated, memberDecorators, heritage) { - const prototype = constructor.prototype; - let finishers = []; - const elementDescriptors = {}; // elementDescriptors is meant to be an array, so this will be converted later - //TODO: merging of elementDescriptors - - for (let [key, isStatic] of undecorated) { - const target = isStatic? constructor : prototype; - let propertyDescriptor = Object.getOwnPropertyDescriptor(target, key); - elementDescriptors[key] = makeElementDescriptor("property", key, isStatic, propertyDescriptor); - } - - for (let [key, decorators, isStatic] of memberDecorators) { - let target = isStatic? constructor : prototype; - let propertyDescriptor = elementDescriptors[key] || Object.getOwnPropertyDescriptor(target, key); - let elementDescriptor = makeElementDescriptor("property", key, isStatic, propertyDescriptor); - let decorated = decorateElement(elementDescriptor, decorators); - - elementDescriptors[key] = decorated.descriptor; - - for (let extra of decorated.extras) { // extras is an array of element descriptors - elementDescriptors[extra.key] = extra; - } - - finishers = finishers.concat(decorated.finishers); - } - - return function (classDecorators) { - let result = decorateClass(constructor, classDecorators, heritage, Object.values(elementDescriptors)); - finishers = finishers.concat(result.finishers); - //TODO: heritage hacks so result.constructor has the correct prototype and instanceof results - //TODO: step 38 and 39, what do they mean "initialize"? - - for (let elementDescriptor of result.elements) { - let target = elementDescriptor.isStatic? constructor : prototype; - Object.defineOwnProperty(target, elementDescriptor.key, elementDescriptor.descriptor); + for (const member of body) { + if (member.node.computed && member.get("key").isPure()) { + const parent = path.findParent(p => p.parentPath.isBlock()); + const ref = member.scope.generateUidIdentifier("key"); + parent.insertBefore( + t.variableDeclaration("let", [t.variableDeclarator(ref)]), + ); + const replacement = t.sequenceExpression([ + t.assignmentExpression("=", ref, member.node.key), + ]); + //member.get("key").replaceWith(replacement); FIXME: should work, but doesn't accept sequenceexpression type + member.node.key = replacement; } - - return result.constructor; - } - } - - - function classDefintionEvaluation(path) { - // step 20, 21 - for (const method of body) { - const { node } = method; - if (!method.isClassMethod()) continue; - if (!node.decorators || !method.node.decorators.length) continue; - if (node.kind === "constructor") continue; } } @@ -156,22 +55,12 @@ export default function({ types: t }) { const entry = []; if (node.computed) { - // if it is computed, we need an identifier to refer to it later - // so we can avoid evaluating the expression twice + // if it is computed, it has been processed by addKeysToComputedMembers if (t.isAssignmentExpression(node.key)) { entry.push(node.key.left); } else { - const parent = path.findParent(p => p.parentPath.isBlock()); - const ref = method.scope.generateUidIdentifier("key"); - parent.insertBefore( - t.variableDeclaration("let", [t.variableDeclarator(ref)]), - ); - const replacement = t.sequenceExpression([ - t.assignmentExpression("=", ref, node.key), - ]); - //method.get("key").replaceWith(); FIXME: this should work - node.key = replacement; - entry.push(ref); + // it's pure + entry.push(node.key); } } else { entry.push(t.stringLiteral(node.key.name)); @@ -197,6 +86,25 @@ export default function({ types: t }) { return t.arrayExpression(decorators); } + function undecoratedMethods(path) { + const body = path.get("body.body"); + const result = []; // shape: [[method1], [method2], [staticMethod1, true]] + + for (const method of body) { + if (method.node.decorators && method.node.decorators.length > 0) continue; + + if (method.node.static) { + result.push( + t.arrayExpression([method.node.key, t.booleanLiteral(true)]), + ); + } else { + result.push(t.arrayExpression([method.node.key])); + } + } + + return t.arrayExpression(result); + } + return { inherits: syntaxDecorators2, visitor: { @@ -230,30 +138,43 @@ export default function({ types: t }) { ); }, - ClassExpression(path) { + ClassExpression(path, file) { + file.addHelper("makeElementDescriptor"); + file.addHelper("decorateElement"); + file.addHelper("mergeDuplicateElements"); + file.addHelper("decorateClass"); + const decorateIdentifier = file.addHelper("decorate"); + + addKeysToComputedMembers(path); + + const undecorated = undecoratedMethods(path); const methodDecorators = takeMethodDecorators(path); const classDecorators = takeClassDecorators(path); + if ( methodDecorators.elements.length == 0 && classDecorators.elements.length == 0 ) { return; } + + const superClass = + path.node.superClass == null + ? path.scope.buildUndefinedNode() + : path.node.superClass; + path.replaceWith( - t.callExpression(t.identifier("decorate"), [ - path.node, - methodDecorators, - classDecorators, - ]), + t.callExpression( + t.callExpression(decorateIdentifier, [ + path.node, + undecorated, + methodDecorators, + superClass, + ]), + [classDecorators], + ), ); }, - - ClassMethod(path) { - if (path.get("decorators")) { - path.get("decorators").forEach(p => p.remove()); - path.get("body").unshiftContainer("body", t.identifier("yo")); - } - }, }, }; } diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/options.json b/packages/babel-plugin-transform-decorators-2/test/fixtures/options.json index 46ff9f3d5227..1de0ea87cd38 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/options.json +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/options.json @@ -1,3 +1,3 @@ { - "plugins": ["transform-decorators-2"] + "plugins": ["transform-decorators-2", "external-helpers"] } diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/actual.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/actual.js index f3913b9db341..c486ba7c609e 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/actual.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/actual.js @@ -4,4 +4,6 @@ let decorate = () => void 0, dec, bar, foo = {bar: () => void 0}, baz, decorator @dec m1() {}; @bar @foo.bar(baz) m2() {}; @dec static [3 + 7] () {}; + andAnUndecoratedMethod () {}; + [calculated + and + undecorated] () {}; } diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js index c6acc0291e8a..a91f67d7db82 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js @@ -10,17 +10,15 @@ let decorate = () => void 0, let _key; -let Bizz = decorate(class Bizz { - m1() { - yo - } +let Bizz = babelHelpers.decorate(class Bizz { + m1() {} - m2() { - yo - } + m2() {} - static [(_key = 3 + 7)]() { - yo - } + static [(_key = 3 + 7)]() {} -}, [["m1", [dec]], ["m2", [foo.bar(baz), bar]], [_key, [dec], true]], [decorator]); + andAnUndecoratedMethod() {} + + [calculated + and + undecorated]() {} + +}, [[andAnUndecoratedMethod], [calculated + and + undecorated]], [["m1", [dec]], ["m2", [foo.bar(baz), bar]], [(_key = 3 + 7), [dec], true]], void 0)([decorator]); \ No newline at end of file From 10d763e3b98e18462e368de4424f52d861736baf Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Mon, 31 Jul 2017 20:09:49 +0530 Subject: [PATCH 05/21] Clean up code --- packages/babel-helpers/src/helpers.js | 2 + .../src/index.js | 24 ++++++------ .../test/fixtures/wip/wip2/actual.js | 24 ++++++++++++ .../test/fixtures/wip/wip2/expected.js | 37 +++++++++++++++++++ 4 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/actual.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/expected.js diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index 7d5b5d4fc27e..5a08989aa8e4 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -270,6 +270,8 @@ helpers.createClass = template(` })() `); +//NOTE: convention is 'descriptor' is used for element descriptors, while 'propertyDescriptor' is used for +//property descriptor in all helpers related to decorators-2 helpers.decorate = template(` (function (constructor, undecorated, memberDecorators, heritage) { const prototype = constructor.prototype; diff --git a/packages/babel-plugin-transform-decorators-2/src/index.js b/packages/babel-plugin-transform-decorators-2/src/index.js index 589ef67ed64f..86444afb624b 100644 --- a/packages/babel-plugin-transform-decorators-2/src/index.js +++ b/packages/babel-plugin-transform-decorators-2/src/index.js @@ -1,17 +1,10 @@ -import template from "babel-template"; import syntaxDecorators2 from "babel-plugin-syntax-decorators-2"; //TODO: will have to check for dot (.) access to reserved keywords in decorator members -//NOTE: convention is 'descriptor' is used for element descriptors, while 'propertyDescriptor' is used for property descriptor //TODO: check if we need the 'define' + 'Property' and 'ke' + 'ys' hacks as seen in //https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy/blob/master/src/index.js#L69 /** manual testing code -class A { method() {console.log("method exec hua");} static estatic() {console.log("estatic hua");}} -function methDec(descriptor) {console.log("methDec hua", arguments); return {descriptor, extras: [], finishers: []}} -function methDec2(descriptor) {console.log("methDec2 hua", arguments); return {descriptor, extras: [], finishers: []}} -function classDec(constructor, heritage, memberDescriptors) {console.log("class hua", arguments); return {constructor, elements: memberDescriptors, finishers: []}} -decorate(A, [], [["method", [methDec, methDec2]], ["estatic", [methDec], true]])([classDec]) **/ export default function({ types: t }) { // converts [(expression)] to [(let key = (expression))] if expression is impure @@ -29,7 +22,8 @@ export default function({ types: t }) { const replacement = t.sequenceExpression([ t.assignmentExpression("=", ref, member.node.key), ]); - //member.get("key").replaceWith(replacement); FIXME: should work, but doesn't accept sequenceexpression type + // FIXME: the following should work: member.get("key").replaceWith(replacement); + // but doesn't accept sequenceexpression type so we have to directly manipulate node member.node.key = replacement; } } @@ -51,7 +45,7 @@ export default function({ types: t }) { const decorators = method.node.decorators .map(d => d.expression) .reverse(); // reverse for correct evaluation order - node.decorators = []; //TODO: should we remove from path? method.get("decorators") doesn't work + node.decorators = []; //TODO: should we remove using path methods? method.get("decorators") doesn't work const entry = []; if (node.computed) { @@ -80,10 +74,14 @@ export default function({ types: t }) { // expects path of a ClassExpression function takeClassDecorators(path) { - // reverse for correct decorator evaluation order - const decorators = path.node.decorators.map(d => d.expression).reverse(); - path.node.decorators = []; - return t.arrayExpression(decorators); + if (path.node.decorators && path.node.decorators.length) { + // reverse for correct decorator evaluation order + const decorators = path.node.decorators.map(d => d.expression).reverse(); + path.node.decorators = []; + return t.arrayExpression(decorators); + } else { + return t.arrayExpression(); + } } function undecoratedMethods(path) { diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/actual.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/actual.js new file mode 100644 index 000000000000..8d6b41897bce --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/actual.js @@ -0,0 +1,24 @@ +class A { + @methDec @methDec2 method() { + console.log("method executed"); + } + + @methDec static estatic() { + console.log("estatic executed"); + } +} + +function methDec(descriptor) { + console.log("methDec executed", arguments); + return {descriptor, extras: [], finishers: []} +} + +function methDec2(descriptor) { + console.log("methDec2 executed", arguments); + return {descriptor, extras: [], finishers: []} +} + +function classDec(constructor, heritage, memberDescriptors) { + console.log("class executed", arguments); + return {constructor, elements: memberDescriptors, finishers: []} +} diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/expected.js new file mode 100644 index 000000000000..563ccba0b79b --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/expected.js @@ -0,0 +1,37 @@ +let A = babelHelpers.decorate(class A { + method() { + console.log("method executed"); + } + + static estatic() { + console.log("estatic executed"); + } + +}, [], [["method", [methDec2, methDec]], ["estatic", [methDec], true]], void 0)([]); + +function methDec(descriptor) { + console.log("methDec executed", arguments); + return { + descriptor, + extras: [], + finishers: [] + }; +} + +function methDec2(descriptor) { + console.log("methDec2 executed", arguments); + return { + descriptor, + extras: [], + finishers: [] + }; +} + +function classDec(constructor, heritage, memberDescriptors) { + console.log("class executed", arguments); + return { + constructor, + elements: memberDescriptors, + finishers: [] + }; +} \ No newline at end of file From 358cf70cc7758cb5c2ed93bb52474450d8618018 Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Tue, 1 Aug 2017 11:16:31 +0530 Subject: [PATCH 06/21] Add feature to pass array of deps to a helper --- .../src/transformation/file/index.js | 18 +++++++++++++++--- packages/babel-helpers/src/index.js | 4 ++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/babel-core/src/transformation/file/index.js b/packages/babel-core/src/transformation/file/index.js index 3461647bc9a8..4eab4dbbdf4a 100644 --- a/packages/babel-core/src/transformation/file/index.js +++ b/packages/babel-core/src/transformation/file/index.js @@ -211,13 +211,12 @@ export default class File extends Store { return id; } - addHelper(name: string): Object { + addHelper(name: string, deps: Array): Object { const declar = this.declarations[name]; if (declar) return declar; if (!this.usedHelpers[name]) { this.metadata.usedHelpers.push(name); - this.usedHelpers[name] = true; } const generator = this.get("helperGenerator"); @@ -229,7 +228,18 @@ export default class File extends Store { return t.memberExpression(runtime, t.identifier(name)); } - const ref = getHelper(name); + const opts = {}; + if (deps) { + for (const dep in deps) { + if (!this.usedHelpers[name]) { + throw "Helper dependencies must be added to the file first"; + } else { + opts["babelHelpers." + dep] = this.usedHelpers[name]; + } + } + } + + const ref = getHelper(name, opts); const uid = (this.declarations[name] = this.scope.generateUidIdentifier( name, )); @@ -248,6 +258,8 @@ export default class File extends Store { }); } + this.usedHelpers[name] = uid.name; + return uid; } diff --git a/packages/babel-helpers/src/index.js b/packages/babel-helpers/src/index.js index 58dc5e85aa82..5e66edf70431 100644 --- a/packages/babel-helpers/src/index.js +++ b/packages/babel-helpers/src/index.js @@ -1,10 +1,10 @@ import helpers from "./helpers"; -export function get(name) { +export function get(name, opts) { const fn = helpers[name]; if (!fn) throw new ReferenceError(`Unknown helper ${name}`); - return fn().expression; + return fn(opts).expression; } export const list = Object.keys(helpers) From 626b92e3b11ec5a4a3191e594791b287c5c29bd5 Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Sun, 6 Aug 2017 23:29:16 +0530 Subject: [PATCH 07/21] Add exec tests --- packages/babel-helpers/src/helpers.js | 14 ++++----- .../src/index.js | 9 ++++-- .../test/fixtures/wip/wip2/exec.js | 29 +++++++++++++++++++ 3 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/exec.js diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index 5a08989aa8e4..4d7d83689204 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -282,7 +282,7 @@ helpers.decorate = template(` for (const [key, isStatic] of undecorated) { const target = isStatic ? constructor : prototype; const propertyDescriptor = Object.getOwnPropertyDescriptor(target, key); - elementDescriptors[key] = makeElementDescriptor( + elementDescriptors[key] = babelHelpers.makeElementDescriptor( "property", key, isStatic, @@ -294,13 +294,13 @@ helpers.decorate = template(` const target = isStatic ? constructor : prototype; const propertyDescriptor = elementDescriptors[key] || Object.getOwnPropertyDescriptor(target, key); - const elementDescriptor = makeElementDescriptor( + const elementDescriptor = babelHelpers.makeElementDescriptor( "property", key, isStatic, propertyDescriptor, - ); - const decorated = decorateElement(elementDescriptor, decorators); + ) + const decorated = babelHelpers.decorateElement(elementDescriptor, decorators); elementDescriptors[key] = decorated.descriptor; @@ -313,7 +313,7 @@ helpers.decorate = template(` } return function(classDecorators) { - const result = decorateClass( + const result = babelHelpers.decorateClass( constructor, classDecorators, heritage, @@ -366,7 +366,7 @@ helpers.decorateElement = template(` } } - extras = mergeDuplicateElements(extras); + extras = babelHelpers.mergeDuplicateElements(extras); return { descriptor: previousDescriptor, extras, finishers }; }); @@ -400,7 +400,7 @@ helpers.decorateClass = template(` } } - elements = mergeDuplicateElements(elements); + elements = babelHelpers.mergeDuplicateElements(elements); } return { constructor: previousConstructor, elements, finishers }; diff --git a/packages/babel-plugin-transform-decorators-2/src/index.js b/packages/babel-plugin-transform-decorators-2/src/index.js index 86444afb624b..d386696e0fbf 100644 --- a/packages/babel-plugin-transform-decorators-2/src/index.js +++ b/packages/babel-plugin-transform-decorators-2/src/index.js @@ -138,10 +138,13 @@ export default function({ types: t }) { ClassExpression(path, file) { file.addHelper("makeElementDescriptor"); - file.addHelper("decorateElement"); file.addHelper("mergeDuplicateElements"); - file.addHelper("decorateClass"); - const decorateIdentifier = file.addHelper("decorate"); + file.addHelper("decorateClass", ["mergeDuplicateElements"]); + file.addHelper("decorateElement", ["mergeDuplicateElements"]); + const decorateIdentifier = file.addHelper("decorate", [ + "decorateClass", + "decorateElement", + ]); addKeysToComputedMembers(path); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/exec.js new file mode 100644 index 000000000000..4f04e2893e65 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/exec.js @@ -0,0 +1,29 @@ +var log = []; +var console = {log: (...args) => log.push(args)}; + +class A { + @methDec @methDec2 method() { + console.log("method executed"); + } + + @methDec static estatic() { + console.log("estatic executed"); + } +} + +function methDec(descriptor) { + console.log("methDec executed", arguments); + return {descriptor, extras: [], finishers: []} +} + +function methDec2(descriptor) { + console.log("methDec2 executed", arguments); + return {descriptor, extras: [], finishers: []} +} + +function classDec(constructor, heritage, memberDescriptors) { + console.log("class executed", arguments); + return {constructor, elements: memberDescriptors, finishers: []} +} + +assert.deepEqual(log, []); From d8cebbac0b21dd2d4e8a410cfa4cb7af43ebe658 Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Mon, 7 Aug 2017 12:51:05 +0530 Subject: [PATCH 08/21] Add tests for evaluation order and fix transformation --- .../src/index.js | 8 ++-- .../decorator-list-evaluation/exec.js | 27 ++++++++++++++ .../evalutation-order/execution/exec.js | 28 ++++++++++++++ .../test/fixtures/wip/wip/expected.js | 2 +- .../test/fixtures/wip/wip2/actual.js | 24 ------------ .../test/fixtures/wip/wip2/exec.js | 29 --------------- .../test/fixtures/wip/wip2/expected.js | 37 ------------------- 7 files changed, 59 insertions(+), 96 deletions(-) create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/decorator-list-evaluation/exec.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/execution/exec.js delete mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/actual.js delete mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/exec.js delete mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/expected.js diff --git a/packages/babel-plugin-transform-decorators-2/src/index.js b/packages/babel-plugin-transform-decorators-2/src/index.js index d386696e0fbf..59722a4f865c 100644 --- a/packages/babel-plugin-transform-decorators-2/src/index.js +++ b/packages/babel-plugin-transform-decorators-2/src/index.js @@ -42,9 +42,8 @@ export default function({ types: t }) { if (!method.isClassMethod()) continue; if (!node.decorators || !method.node.decorators.length) continue; - const decorators = method.node.decorators - .map(d => d.expression) - .reverse(); // reverse for correct evaluation order + const decorators = method.node.decorators.map(d => d.expression); + node.decorators = []; //TODO: should we remove using path methods? method.get("decorators") doesn't work const entry = []; @@ -75,8 +74,7 @@ export default function({ types: t }) { // expects path of a ClassExpression function takeClassDecorators(path) { if (path.node.decorators && path.node.decorators.length) { - // reverse for correct decorator evaluation order - const decorators = path.node.decorators.map(d => d.expression).reverse(); + const decorators = path.node.decorators.map(d => d.expression); path.node.decorators = []; return t.arrayExpression(decorators); } else { diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/decorator-list-evaluation/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/decorator-list-evaluation/exec.js new file mode 100644 index 000000000000..b1978abda38c --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/decorator-list-evaluation/exec.js @@ -0,0 +1,27 @@ +const calls = []; + +function dec(id){ + calls.push(id); + return function(descriptor) { return {descriptor, extras:[], finishers:[] };}; +} + +class Foo { + @dec(1) + @dec(2) + method1() {} + + @dec(3) + @dec(4) + method2() {} + + @dec(5) + @dec(6) + method3() {} + + @dec(7) + @dec(8) + method4() {} +} + +assert.deepEqual(calls, [1, 2, 3, 4, 5, 6, 7, 8]); + diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/execution/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/execution/exec.js new file mode 100644 index 000000000000..7f64b5fcb039 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/execution/exec.js @@ -0,0 +1,28 @@ +const calls = []; + +function dec(id){ + return function(descriptor) { + calls.push(id); + return {descriptor, extras:[], finishers:[] }; + }; +} + +class Foo { + @dec(2) + @dec(1) + method1() {} + + @dec(4) + @dec(3) + method2() {} + + @dec(6) + @dec(5) + method3() {} + + @dec(8) + @dec(7) + method4() {} +} + +assert.deepEqual(calls, [1, 2, 3, 4, 5, 6, 7, 8]); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js index a91f67d7db82..e85c99bccd07 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js @@ -21,4 +21,4 @@ let Bizz = babelHelpers.decorate(class Bizz { [calculated + and + undecorated]() {} -}, [[andAnUndecoratedMethod], [calculated + and + undecorated]], [["m1", [dec]], ["m2", [foo.bar(baz), bar]], [(_key = 3 + 7), [dec], true]], void 0)([decorator]); \ No newline at end of file +}, [[andAnUndecoratedMethod], [calculated + and + undecorated]], [["m1", [dec]], ["m2", [bar, foo.bar(baz)]], [(_key = 3 + 7), [dec], true]], void 0)([decorator]); \ No newline at end of file diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/actual.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/actual.js deleted file mode 100644 index 8d6b41897bce..000000000000 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/actual.js +++ /dev/null @@ -1,24 +0,0 @@ -class A { - @methDec @methDec2 method() { - console.log("method executed"); - } - - @methDec static estatic() { - console.log("estatic executed"); - } -} - -function methDec(descriptor) { - console.log("methDec executed", arguments); - return {descriptor, extras: [], finishers: []} -} - -function methDec2(descriptor) { - console.log("methDec2 executed", arguments); - return {descriptor, extras: [], finishers: []} -} - -function classDec(constructor, heritage, memberDescriptors) { - console.log("class executed", arguments); - return {constructor, elements: memberDescriptors, finishers: []} -} diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/exec.js deleted file mode 100644 index 4f04e2893e65..000000000000 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/exec.js +++ /dev/null @@ -1,29 +0,0 @@ -var log = []; -var console = {log: (...args) => log.push(args)}; - -class A { - @methDec @methDec2 method() { - console.log("method executed"); - } - - @methDec static estatic() { - console.log("estatic executed"); - } -} - -function methDec(descriptor) { - console.log("methDec executed", arguments); - return {descriptor, extras: [], finishers: []} -} - -function methDec2(descriptor) { - console.log("methDec2 executed", arguments); - return {descriptor, extras: [], finishers: []} -} - -function classDec(constructor, heritage, memberDescriptors) { - console.log("class executed", arguments); - return {constructor, elements: memberDescriptors, finishers: []} -} - -assert.deepEqual(log, []); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/expected.js deleted file mode 100644 index 563ccba0b79b..000000000000 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip2/expected.js +++ /dev/null @@ -1,37 +0,0 @@ -let A = babelHelpers.decorate(class A { - method() { - console.log("method executed"); - } - - static estatic() { - console.log("estatic executed"); - } - -}, [], [["method", [methDec2, methDec]], ["estatic", [methDec], true]], void 0)([]); - -function methDec(descriptor) { - console.log("methDec executed", arguments); - return { - descriptor, - extras: [], - finishers: [] - }; -} - -function methDec2(descriptor) { - console.log("methDec2 executed", arguments); - return { - descriptor, - extras: [], - finishers: [] - }; -} - -function classDec(constructor, heritage, memberDescriptors) { - console.log("class executed", arguments); - return { - constructor, - elements: memberDescriptors, - finishers: [] - }; -} \ No newline at end of file From ef4eba5d7abd3d98890c60f7925ad48095057d7d Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Wed, 9 Aug 2017 17:47:48 +0530 Subject: [PATCH 09/21] Add tests for class decorators; minor fixes --- packages/babel-helpers/src/helpers.js | 6 ++-- .../src/index.js | 1 + .../class-and-method-decorators/actual.js | 25 +++++++++++++++ .../class-and-method-decorators/exec.js | 25 +++++++++++++++ .../class-and-method-decorators/expected.js | 31 +++++++++++++++++++ 5 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/actual.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/exec.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/expected.js diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index 4d7d83689204..470402292151 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -317,7 +317,7 @@ helpers.decorate = template(` constructor, classDecorators, heritage, - Object.values(elementDescriptors), + Object.values(elementDescriptors) ); finishers = finishers.concat(result.finishers); //TODO: heritage hacks so result.constructor has the correct prototype and instanceof results @@ -325,10 +325,10 @@ helpers.decorate = template(` for (const elementDescriptor of result.elements) { const target = elementDescriptor.isStatic ? constructor : prototype; - Object.defineOwnProperty( + Object.defineProperty( target, elementDescriptor.key, - elementDescriptor.descriptor, + elementDescriptor.descriptor ); } diff --git a/packages/babel-plugin-transform-decorators-2/src/index.js b/packages/babel-plugin-transform-decorators-2/src/index.js index 59722a4f865c..9cb0c9f80f93 100644 --- a/packages/babel-plugin-transform-decorators-2/src/index.js +++ b/packages/babel-plugin-transform-decorators-2/src/index.js @@ -88,6 +88,7 @@ export default function({ types: t }) { for (const method of body) { if (method.node.decorators && method.node.decorators.length > 0) continue; + if (method.node.key.name == "constructor") continue; if (method.node.static) { result.push( diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/actual.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/actual.js new file mode 100644 index 000000000000..be624a3a3587 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/actual.js @@ -0,0 +1,25 @@ +const calls = []; + +function classDec(constructor, heritage, elements) { + calls.push("classDec evaluated"); + return {constructor, elements, finishers: []}; +} + +function methDec(descriptor){ + calls.push("methDec evaluated"); + return {descriptor, extras:[], finishers:[] }; +} + + + +@classDec class A { + constructor () { + calls.push("ctor called"); + } + + @methDec method1() { + calls.push("method1 called"); + } +} + +assert.deepEqual(["methDec evaluated", "classDec evaluated"], calls); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/exec.js new file mode 100644 index 000000000000..be624a3a3587 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/exec.js @@ -0,0 +1,25 @@ +const calls = []; + +function classDec(constructor, heritage, elements) { + calls.push("classDec evaluated"); + return {constructor, elements, finishers: []}; +} + +function methDec(descriptor){ + calls.push("methDec evaluated"); + return {descriptor, extras:[], finishers:[] }; +} + + + +@classDec class A { + constructor () { + calls.push("ctor called"); + } + + @methDec method1() { + calls.push("method1 called"); + } +} + +assert.deepEqual(["methDec evaluated", "classDec evaluated"], calls); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/expected.js new file mode 100644 index 000000000000..d3674151e402 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/expected.js @@ -0,0 +1,31 @@ +const calls = []; + +function classDec(constructor, heritage, elements) { + calls.push("classDec evaluated"); + return { + constructor, + elements, + finishers: [] + }; +} + +function methDec(descriptor) { + calls.push("methDec evaluated"); + return { + descriptor, + extras: [], + finishers: [] + }; +} + +let A = babelHelpers.decorate(class A { + constructor() { + calls.push("ctor called"); + } + + method1() { + calls.push("method1 called"); + } + +}, [], [["method1", [methDec]]], void 0)([classDec]); +assert.deepEqual(["methDec evaluated", "classDec evaluated"], calls); \ No newline at end of file From b1092ca65ad87d292d97d12dcd202ac07f3cd441 Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Fri, 11 Aug 2017 14:41:51 +0530 Subject: [PATCH 10/21] Add test for enumerability decorator --- packages/babel-helpers/src/helpers.js | 27 +++++++----- .../src/index.js | 41 ++++++++++--------- .../class-and-method-decorators-order/exec.js | 26 ++++++++++++ .../decorator-execution-order}/exec.js | 0 .../decorator-list-evaluation-order}/exec.js | 0 .../class-and-method-decorators/actual.js | 25 ----------- .../class-and-method-decorators/exec.js | 25 ----------- .../class-and-method-decorators/expected.js | 31 -------------- .../examples/change-enumerability/exec.js | 22 ++++++++++ .../class-decorators/actual.js | 7 ++++ .../class-decorators/expected.js | 8 ++++ .../method-and-class-decorators/actual.js | 4 ++ .../method-and-class-decorators/expected.js | 6 +++ .../method-decorators/actual.js | 6 +++ .../method-decorators/expected.js | 10 +++++ .../test/fixtures/wip/wip/expected.js | 2 +- 16 files changed, 128 insertions(+), 112 deletions(-) create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/basic/class-and-method-decorators-order/exec.js rename packages/babel-plugin-transform-decorators-2/test/fixtures/{evalutation-order/execution => basic/decorator-execution-order}/exec.js (100%) rename packages/babel-plugin-transform-decorators-2/test/fixtures/{evalutation-order/decorator-list-evaluation => basic/decorator-list-evaluation-order}/exec.js (100%) delete mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/actual.js delete mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/exec.js delete mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/expected.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/examples/change-enumerability/exec.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/class-decorators/actual.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/class-decorators/expected.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-and-class-decorators/actual.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-and-class-decorators/expected.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-decorators/actual.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-decorators/expected.js diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index 470402292151..3ff50b783ded 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -293,7 +293,9 @@ helpers.decorate = template(` for (const [key, decorators, isStatic] of memberDecorators) { const target = isStatic ? constructor : prototype; const propertyDescriptor = - elementDescriptors[key] || Object.getOwnPropertyDescriptor(target, key); + elementDescriptors[key] && elementDescriptors[key].descriptor + || Object.getOwnPropertyDescriptor(target, key); + const elementDescriptor = babelHelpers.makeElementDescriptor( "property", key, @@ -347,15 +349,14 @@ helpers.decorateElement = template(` for (let i = decorators.length - 1; i >= 0; i--) { const decorator = decorators[i]; - const result = decorator(previousDescriptor); - const currentDescriptor = result.descriptor; + const result = decorator(previousDescriptor.descriptor); if (result.finisher) { finishers.push(current.finisher); result.finisher = undefined; } - previousDescriptor = currentDescriptor; + previousDescriptor.descriptor = result.descriptor; // just change the property descriptor const extrasObject = result.extras; @@ -374,30 +375,28 @@ helpers.decorateElement = template(` helpers.decorateClass = template(` (function (constructor, decorators, heritage, elementDescriptors) { - let elements = []; + let elements = elementDescriptors; let finishers = []; let previousConstructor = constructor; - const previousDescriptors = elementDescriptors; for (let i = decorators.length - 1; i >= 0; i--) { const decorator = decorators[i]; const result = decorator( previousConstructor, heritage, - previousDescriptors, + elements ); previousConstructor = result.constructor; + if (result.finishers) { // result.finishers is called 'finisher' in the spec finishers = finishers.concat(result.finishers); } if (result.elements) { - for (const element of result.elements) { - elements.push(element); - } + elements = elements.concat(result.elements); //FIXME: for some reason using for of exhausts heap here } elements = babelHelpers.mergeDuplicateElements(elements); @@ -563,7 +562,13 @@ helpers.makeElementDescriptor = template(` //TODO helpers.mergeDuplicateElements = template(` (function (elements) { - return elements; + let elementMap = {}; + + for (let elementDescriptor of elements) { + elementMap[elementDescriptor.key] = elementDescriptor; + } + + return Object.values(elementMap); }); `); diff --git a/packages/babel-plugin-transform-decorators-2/src/index.js b/packages/babel-plugin-transform-decorators-2/src/index.js index 9cb0c9f80f93..a6a4b948c02a 100644 --- a/packages/babel-plugin-transform-decorators-2/src/index.js +++ b/packages/babel-plugin-transform-decorators-2/src/index.js @@ -29,6 +29,21 @@ export default function({ types: t }) { } } + // expects a method node + function getKey(node) { + if (node.computed) { + // if it is computed, it has been processed by addKeysToComputedMembers + if (t.isAssignmentExpression(node.key)) { + return node.key.left; + } else { + // it's pure + return node.key; + } + } else { + return t.stringLiteral(node.key.name); + } + } + // goes over the methods of current class and prepares decorators // expects path of a ClassExpression function takeMethodDecorators(path) { @@ -46,20 +61,7 @@ export default function({ types: t }) { node.decorators = []; //TODO: should we remove using path methods? method.get("decorators") doesn't work - const entry = []; - if (node.computed) { - // if it is computed, it has been processed by addKeysToComputedMembers - if (t.isAssignmentExpression(node.key)) { - entry.push(node.key.left); - } else { - // it's pure - entry.push(node.key); - } - } else { - entry.push(t.stringLiteral(node.key.name)); - } - - entry.push(t.arrayExpression(decorators)); + const entry = [getKey(node), t.arrayExpression(decorators)]; if (node.static) { entry.push(t.booleanLiteral(true)); @@ -82,20 +84,21 @@ export default function({ types: t }) { } } + // assume:addKeysToComputedMembers has been executed function undecoratedMethods(path) { const body = path.get("body.body"); - const result = []; // shape: [[method1], [method2], [staticMethod1, true]] + const result = []; // shape: [["method1"], ["method2"], ["staticMethod1", true]] for (const method of body) { if (method.node.decorators && method.node.decorators.length > 0) continue; if (method.node.key.name == "constructor") continue; + const key = getKey(method.node); + if (method.node.static) { - result.push( - t.arrayExpression([method.node.key, t.booleanLiteral(true)]), - ); + result.push(t.arrayExpression([key, t.booleanLiteral(true)])); } else { - result.push(t.arrayExpression([method.node.key])); + result.push(t.arrayExpression([key])); } } diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/class-and-method-decorators-order/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/class-and-method-decorators-order/exec.js new file mode 100644 index 000000000000..ed3b5b3929f6 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/class-and-method-decorators-order/exec.js @@ -0,0 +1,26 @@ +const calls = []; + +function classDec(arg) { + calls.push(3); + return function (constructor, heritage, elements) { + calls.push(4); + return {constructor, elements, finishers: []}; + } +} + +function methDec(arg) { + calls.push(1); + return function (descriptor){ + calls.push(2); + return {descriptor, extras:[], finishers:[] }; + } +} + + + +@classDec("arg") class A { + constructor () {} + @methDec("arg") method1() {} +} + +assert.deepEqual(calls, [1, 2, 3, 4]); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/execution/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/decorator-execution-order/exec.js similarity index 100% rename from packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/execution/exec.js rename to packages/babel-plugin-transform-decorators-2/test/fixtures/basic/decorator-execution-order/exec.js diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/decorator-list-evaluation/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/decorator-list-evaluation-order/exec.js similarity index 100% rename from packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/decorator-list-evaluation/exec.js rename to packages/babel-plugin-transform-decorators-2/test/fixtures/basic/decorator-list-evaluation-order/exec.js diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/actual.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/actual.js deleted file mode 100644 index be624a3a3587..000000000000 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/actual.js +++ /dev/null @@ -1,25 +0,0 @@ -const calls = []; - -function classDec(constructor, heritage, elements) { - calls.push("classDec evaluated"); - return {constructor, elements, finishers: []}; -} - -function methDec(descriptor){ - calls.push("methDec evaluated"); - return {descriptor, extras:[], finishers:[] }; -} - - - -@classDec class A { - constructor () { - calls.push("ctor called"); - } - - @methDec method1() { - calls.push("method1 called"); - } -} - -assert.deepEqual(["methDec evaluated", "classDec evaluated"], calls); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/exec.js deleted file mode 100644 index be624a3a3587..000000000000 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/exec.js +++ /dev/null @@ -1,25 +0,0 @@ -const calls = []; - -function classDec(constructor, heritage, elements) { - calls.push("classDec evaluated"); - return {constructor, elements, finishers: []}; -} - -function methDec(descriptor){ - calls.push("methDec evaluated"); - return {descriptor, extras:[], finishers:[] }; -} - - - -@classDec class A { - constructor () { - calls.push("ctor called"); - } - - @methDec method1() { - calls.push("method1 called"); - } -} - -assert.deepEqual(["methDec evaluated", "classDec evaluated"], calls); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/expected.js deleted file mode 100644 index d3674151e402..000000000000 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/evalutation-order/class-and-method-decorators/expected.js +++ /dev/null @@ -1,31 +0,0 @@ -const calls = []; - -function classDec(constructor, heritage, elements) { - calls.push("classDec evaluated"); - return { - constructor, - elements, - finishers: [] - }; -} - -function methDec(descriptor) { - calls.push("methDec evaluated"); - return { - descriptor, - extras: [], - finishers: [] - }; -} - -let A = babelHelpers.decorate(class A { - constructor() { - calls.push("ctor called"); - } - - method1() { - calls.push("method1 called"); - } - -}, [], [["method1", [methDec]]], void 0)([classDec]); -assert.deepEqual(["methDec evaluated", "classDec evaluated"], calls); \ No newline at end of file diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/change-enumerability/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/change-enumerability/exec.js new file mode 100644 index 000000000000..dbba987e4eab --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/change-enumerability/exec.js @@ -0,0 +1,22 @@ +function enumerable(bool) { + return function(descriptor) { + descriptor.enumerable = bool; + return {descriptor, finishers: [], extras: []}; + } +} + +class Foo { + @enumerable(false) falsy() {} + @enumerable(true) @enumerable(false) truthy() {} + falsyByDefault() {} +} + +//existence +assert(Foo.prototype.falsy); +assert(Foo.prototype.truthy); + +//enumerability +assert.equal(Foo.prototype.propertyIsEnumerable("falsyByDefault"), false); +assert.equal(Foo.prototype.propertyIsEnumerable("falsy"), false); +assert.equal(Foo.prototype.propertyIsEnumerable("truthy"), true); +assert.deepEqual(Object.keys(Foo.prototype), ["truthy"]); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/class-decorators/actual.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/class-decorators/actual.js new file mode 100644 index 000000000000..3674083d4a11 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/class-decorators/actual.js @@ -0,0 +1,7 @@ +@classDecOuter("args") @classDecInner class Foo { + someMethod() {} +} + +class Undecorated { + someMethod() {} +} diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/class-decorators/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/class-decorators/expected.js new file mode 100644 index 000000000000..79c6344892fe --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/class-decorators/expected.js @@ -0,0 +1,8 @@ +let Foo = babelHelpers.decorate(class Foo { + someMethod() {} + +}, [["someMethod"]], [], void 0)([classDecOuter("args"), classDecInner]); +let Undecorated = class Undecorated { + someMethod() {} + +}; \ No newline at end of file diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-and-class-decorators/actual.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-and-class-decorators/actual.js new file mode 100644 index 000000000000..01cd5da8ab3b --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-and-class-decorators/actual.js @@ -0,0 +1,4 @@ +@classDec class Foo { + @methDec method() {} + undecorated() {} +} diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-and-class-decorators/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-and-class-decorators/expected.js new file mode 100644 index 000000000000..610da2991ef0 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-and-class-decorators/expected.js @@ -0,0 +1,6 @@ +let Foo = babelHelpers.decorate(class Foo { + method() {} + + undecorated() {} + +}, [["undecorated"]], [["method", [methDec]]], void 0)([classDec]); \ No newline at end of file diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-decorators/actual.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-decorators/actual.js new file mode 100644 index 000000000000..5cff777a74eb --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-decorators/actual.js @@ -0,0 +1,6 @@ +class Foo { + @dec1 method1() {} + @dec2(a, b, c) method2() {} + @dec3outer @dec3inner method3() {} + undecorated() {} +} diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-decorators/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-decorators/expected.js new file mode 100644 index 000000000000..95d0b14d1b30 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/method-decorators/expected.js @@ -0,0 +1,10 @@ +let Foo = babelHelpers.decorate(class Foo { + method1() {} + + method2() {} + + method3() {} + + undecorated() {} + +}, [["undecorated"]], [["method1", [dec1]], ["method2", [dec2(a, b, c)]], ["method3", [dec3outer, dec3inner]]], void 0)([]); \ No newline at end of file diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js index e85c99bccd07..14444812071a 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js @@ -21,4 +21,4 @@ let Bizz = babelHelpers.decorate(class Bizz { [calculated + and + undecorated]() {} -}, [[andAnUndecoratedMethod], [calculated + and + undecorated]], [["m1", [dec]], ["m2", [bar, foo.bar(baz)]], [(_key = 3 + 7), [dec], true]], void 0)([decorator]); \ No newline at end of file +}, [["andAnUndecoratedMethod"], [calculated + and + undecorated]], [["m1", [dec]], ["m2", [bar, foo.bar(baz)]], [(_key = 3 + 7), [dec], true]], void 0)([decorator]); \ No newline at end of file From a55e374de78681f87ef791dd6868e1c3fdd16475 Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Tue, 15 Aug 2017 18:54:19 +0530 Subject: [PATCH 11/21] Add examples, & tests for extras, finishers --- packages/babel-helpers/src/helpers.js | 48 ++++++++++++------- .../test/fixtures/basic/extras/exec.js | 37 ++++++++++++++ .../test/fixtures/basic/finishers/exec.js | 35 ++++++++++++++ .../test/fixtures/examples/logger/exec.js | 31 ++++++++++++ .../test/fixtures/examples/overrider/exec.js | 17 +++++++ .../test/fixtures/examples/spare/exec.js | 30 ++++++++++++ 6 files changed, 181 insertions(+), 17 deletions(-) create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/basic/extras/exec.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/basic/finishers/exec.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/examples/logger/exec.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/examples/overrider/exec.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/examples/spare/exec.js diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index 3ff50b783ded..31bc71772f18 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -276,24 +276,27 @@ helpers.decorate = template(` (function (constructor, undecorated, memberDecorators, heritage) { const prototype = constructor.prototype; let finishers = []; - const elementDescriptors = {}; // elementDescriptors is meant to be an array, so this will be converted later - //TODO: merging of elementDescriptors + const elementDescriptors = new Map(); // elementDescriptors is meant to be an array, so this will be converted later for (const [key, isStatic] of undecorated) { const target = isStatic ? constructor : prototype; const propertyDescriptor = Object.getOwnPropertyDescriptor(target, key); - elementDescriptors[key] = babelHelpers.makeElementDescriptor( - "property", - key, - isStatic, - propertyDescriptor, + elementDescriptors.set( + [key, isStatic], + babelHelpers.makeElementDescriptor( + "property", + key, + isStatic, + propertyDescriptor, + ) ); } + // decorate and store in elementDescriptors for (const [key, decorators, isStatic] of memberDecorators) { const target = isStatic ? constructor : prototype; const propertyDescriptor = - elementDescriptors[key] && elementDescriptors[key].descriptor + elementDescriptors.has([key, isStatic]) && elementDescriptors.get([key, isStatic]).descriptor || Object.getOwnPropertyDescriptor(target, key); const elementDescriptor = babelHelpers.makeElementDescriptor( @@ -304,11 +307,11 @@ helpers.decorate = template(` ) const decorated = babelHelpers.decorateElement(elementDescriptor, decorators); - elementDescriptors[key] = decorated.descriptor; + elementDescriptors.set([key, isStatic], decorated.descriptor); for (const extra of decorated.extras) { // extras is an array of element descriptors - elementDescriptors[extra.key] = extra; + elementDescriptors.set([extra.key, extra.isStatic], extra); } finishers = finishers.concat(decorated.finishers); @@ -319,8 +322,9 @@ helpers.decorate = template(` constructor, classDecorators, heritage, - Object.values(elementDescriptors) + Array.from(elementDescriptors.values()) ); + finishers = finishers.concat(result.finishers); //TODO: heritage hacks so result.constructor has the correct prototype and instanceof results //TODO: step 38 and 39, what do they mean "initialize"? @@ -334,6 +338,10 @@ helpers.decorate = template(` ); } + for (let finisher of finishers) { + finisher.call(undefined, result.constructor); + } + return result.constructor; }; }); @@ -343,7 +351,7 @@ helpers.decorateElement = template(` (function (descriptor, decorators) { //spec uses the param "element" instead of "descriptor" and finds descriptor from it let extras = []; - const finishers = []; + let finishers = []; let previousDescriptor = descriptor; @@ -351,9 +359,10 @@ helpers.decorateElement = template(` const decorator = decorators[i]; const result = decorator(previousDescriptor.descriptor); - if (result.finisher) { - finishers.push(current.finisher); - result.finisher = undefined; + //TODO: why does .finisher exist on an elementDescriptor? the following conditional deviates from + //the spec because it uses result.finishers rather than result.descriptor.finisher + if (result.finishers) { + finishers = finishers.concat(result.finishers); } previousDescriptor.descriptor = result.descriptor; // just change the property descriptor @@ -563,12 +572,17 @@ helpers.makeElementDescriptor = template(` helpers.mergeDuplicateElements = template(` (function (elements) { let elementMap = {}; + let staticElementMap = {}; for (let elementDescriptor of elements) { - elementMap[elementDescriptor.key] = elementDescriptor; + if (elementDescriptor.isStatic) { + staticElementMap[elementDescriptor.key] = elementDescriptor; + } else { + elementMap[elementDescriptor.key] = elementDescriptor; + } } - return Object.values(elementMap); + return Object.values(elementMap).concat(Object.values(staticElementMap)); }); `); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/extras/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/extras/exec.js new file mode 100644 index 000000000000..b0dcbcbb6ffa --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/extras/exec.js @@ -0,0 +1,37 @@ +function dec(descriptor) { + const extra = { + kind: "property", + key: "magic", + isStatic: false, + descriptor: { + enumerable: true, + configurable: true, + writable: true, + value: function () {return "extra"} + } + } + + const extraStatic = { + kind: "property", + key: "magic", + isStatic: true, + descriptor: { + enumerable: true, + configurable: true, + writable: true, + value: function () {return "extraStatic"} + } + } + + return {descriptor, extras: [extra, extraStatic], finishers: []} +} + +class Foo { + @dec method() {return "method"} +} + +var x = new Foo(); + +assert.equal(x.method(), "method"); +assert.equal(x.magic(), "extra"); +assert.equal(Foo.magic(), "extraStatic"); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/finishers/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/finishers/exec.js new file mode 100644 index 000000000000..04ea5cf47646 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/finishers/exec.js @@ -0,0 +1,35 @@ +const logs = []; + +function overrider(fn) { + return function(descriptor) { + descriptor.value = fn; + return {descriptor, extras: [], finishers: []}; + } +} + +function dec(log) { + return function (descriptor) { + const finisher = function (ctor) { + const x = new ctor(); + assert(x.method1); + assert(x.method2); + assert.equal(x.method1(), "method1"); + // making sure that ctor passed to the finisher has all the decorators applied + assert.equal(x.method2(), "method2 overridden"); + logs.push("finisher called " + log); + } + + return {descriptor, extras: [], finishers: [finisher]} + } +} + +function classDec(constructor, heritage, elements) { + return {constructor, elements, finishers: [() => logs.push("finisher called classDec")]}; +} + +@classDec class Foo { + @dec(1) method1() {return "method1"} + @dec(2) @overrider(() => "method2 overridden") method2() {return "method2"}; +} + +assert.deepEqual(logs, ["finisher called 1", "finisher called 2", "finisher called classDec"]); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/logger/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/logger/exec.js new file mode 100644 index 000000000000..2df2d3ceda54 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/logger/exec.js @@ -0,0 +1,31 @@ +const calls = []; + +function log(message) { + return function (descriptor) { + let oldFunc = descriptor.value; + descriptor.value = function(...args) { + calls.push(message + " entered"); + let result = oldFunc.apply(this, args); + calls.push(message + " exited"); + return result; + } + return {descriptor, extras: [], finishers: []}; + } +} + +class Foo { + @log("method1") method1() { + assert.equal(this.method2(), 6); + return 4; + } + @log("method2") method2() {return 6;} + @log("method3") method3() {return 8;} +} + +let foo = new Foo(); + +assert.equal(foo.method1(), 4); +assert.equal(foo.method3(), 8); +assert.deepEqual(calls, ["method1 entered", "method2 entered", "method2 exited", "method1 exited", "method3 entered", "method3 exited"]); + + diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/overrider/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/overrider/exec.js new file mode 100644 index 000000000000..ff279de23059 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/overrider/exec.js @@ -0,0 +1,17 @@ +function overrider(fn) { + return function(descriptor) { + descriptor.value = fn; + return {descriptor, extras: [], finishers: []}; + } +} + +class Foo { + @overrider(() => 3) method() { + return 2; + } +} + +const x = new Foo(); +assert.equal(x.method(), 3); + + diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/spare/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/spare/exec.js new file mode 100644 index 000000000000..a82ea40e1875 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/spare/exec.js @@ -0,0 +1,30 @@ +// makes an undecorated copy of the method +function spare(name, isStatic) { + return function(descriptor) { + const clone = { + kind: "property", + isStatic: !!isStatic, + key: name, + descriptor: Object.assign({}, descriptor) + } + return {descriptor, extras: [clone], finishers: []}; + } +} + +function overrider(fn) { + return function(descriptor) { + descriptor.value = fn; + return {descriptor, extras: [], finishers: []}; + } +} + +class Foo { + @overrider(() => 3) @spare("spared") method() { + return 2; + } +} + +const x = new Foo(); + +assert.equal(x.spared(), 2); +assert.equal(x.method(), 3); From 8707f4e45762db1a5465aa82dac0aa3c044133a1 Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Wed, 16 Aug 2017 20:47:52 +0530 Subject: [PATCH 12/21] Update versions to make tests pass --- .../package.json | 2 +- .../package.json | 8 +- .../expected.js | 2 +- yarn.lock | 178 +++++++++++++++++- 4 files changed, 182 insertions(+), 8 deletions(-) diff --git a/packages/babel-plugin-syntax-decorators-2/package.json b/packages/babel-plugin-syntax-decorators-2/package.json index 005833819675..2ae9d9b30c28 100644 --- a/packages/babel-plugin-syntax-decorators-2/package.json +++ b/packages/babel-plugin-syntax-decorators-2/package.json @@ -1,6 +1,6 @@ { "name": "babel-plugin-syntax-decorators-2", - "version": "7.0.0-alpha.15", + "version": "7.0.0-alpha.19", "description": "Updated parsing of decorators", "repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-decorators-2", "license": "MIT", diff --git a/packages/babel-plugin-transform-decorators-2/package.json b/packages/babel-plugin-transform-decorators-2/package.json index e42296272eec..2bb9fc28d48d 100644 --- a/packages/babel-plugin-transform-decorators-2/package.json +++ b/packages/babel-plugin-transform-decorators-2/package.json @@ -1,6 +1,6 @@ { "name": "babel-plugin-transform-decorators-2", - "version": "7.0.0-alpha.15", + "version": "7.0.0-alpha.19", "author": "Peeyush Kushwaha ", "license": "MIT", "description": "Transformer for stage-2 decorators", @@ -12,9 +12,11 @@ "decorators" ], "dependencies": { - "babel-plugin-syntax-decorators-2": "7.0.0-alpha.15" + "babel-plugin-syntax-decorators-2": "7.0.0-alpha.19" }, "devDependencies": { - "babel-helper-plugin-test-runner": "7.0.0-alpha.15" + "babel-helper-plugin-test-runner": "7.0.0-alpha.19", + "babel-helper-transform-fixture-test-runner": "7.0.0-alpha.19", + "babel-plugin-transform-decorators-2": "7.0.0-alpha.19" } } diff --git a/packages/babel-plugin-transform-es2015-parameters/test/fixtures/parameters/rest-member-expression-optimisation/expected.js b/packages/babel-plugin-transform-es2015-parameters/test/fixtures/parameters/rest-member-expression-optimisation/expected.js index 62aab77ce7ff..736437e2edf7 100644 --- a/packages/babel-plugin-transform-es2015-parameters/test/fixtures/parameters/rest-member-expression-optimisation/expected.js +++ b/packages/babel-plugin-transform-es2015-parameters/test/fixtures/parameters/rest-member-expression-optimisation/expected.js @@ -21,6 +21,6 @@ function t() { function t() { for (var i = 0; i < arguments.length; i++) { - return arguments.length <= i ? undefined : arguments[i]; + return i < 0 || arguments.length <= i ? undefined : arguments[i]; } } diff --git a/yarn.lock b/yarn.lock index 7f28248b3c69..ab39b20c7603 100644 --- a/yarn.lock +++ b/yarn.lock @@ -389,6 +389,26 @@ babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: esutils "^2.0.2" js-tokens "^3.0.0" +babel-core@7.0.0-alpha.15: + version "7.0.0-alpha.15" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-alpha.15.tgz#4c5e608b3de921dbfea68f7ca196d297931acb23" + dependencies: + babel-code-frame "7.0.0-alpha.15" + babel-generator "7.0.0-alpha.15" + babel-helpers "7.0.0-alpha.15" + babel-messages "7.0.0-alpha.15" + babel-template "7.0.0-alpha.15" + babel-traverse "7.0.0-alpha.15" + babel-types "7.0.0-alpha.15" + babylon "7.0.0-beta.15" + convert-source-map "^1.1.0" + debug "^2.1.1" + json5 "^0.5.0" + lodash "^4.2.0" + micromatch "^2.3.11" + resolve "^1.3.2" + source-map "^0.5.0" + babel-core@7.0.0-alpha.18: version "7.0.0-alpha.18" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-alpha.18.tgz#416cb65ac7c95b59c9230a65c4c595b6d5e30b4a" @@ -409,6 +429,30 @@ babel-core@7.0.0-alpha.18: resolve "^1.3.2" source-map "^0.5.0" +babel-core@^6.24.1: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.25.0.tgz#7dd42b0463c742e9d5296deb3ec67a9322dad729" + dependencies: + babel-code-frame "^6.22.0" + babel-generator "^6.25.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.25.0" + babel-traverse "^6.25.0" + babel-types "^6.25.0" + babylon "^6.17.2" + convert-source-map "^1.1.0" + debug "^2.1.1" + json5 "^0.5.0" + lodash "^4.2.0" + minimatch "^3.0.2" + path-is-absolute "^1.0.0" + private "^0.1.6" + slash "^1.0.0" + source-map "^0.5.0" + babel-eslint@8.0.0-alpha.15: version "8.0.0-alpha.15" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.0.0-alpha.15.tgz#7cf5c1c81eaf40662d40aef51b0b488dfd199b2d" @@ -418,6 +462,17 @@ babel-eslint@8.0.0-alpha.15: babel-types "7.0.0-alpha.15" babylon "7.0.0-beta.16" +babel-generator@7.0.0-alpha.15: + version "7.0.0-alpha.15" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-7.0.0-alpha.15.tgz#27884018a9cc8300e20497cad17c4bc1f416a9fc" + dependencies: + babel-messages "7.0.0-alpha.15" + babel-types "7.0.0-alpha.15" + jsesc "^1.3.0" + lodash "^4.2.0" + source-map "^0.5.0" + trim-right "^1.0.1" + babel-generator@7.0.0-alpha.18: version "7.0.0-alpha.18" resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-7.0.0-alpha.18.tgz#107566ad9824b1e5192bee3185a1c2f6c7437cee" @@ -429,7 +484,7 @@ babel-generator@7.0.0-alpha.18: source-map "^0.5.0" trim-right "^1.0.1" -babel-generator@^6.18.0: +babel-generator@^6.18.0, babel-generator@^6.25.0: version "6.25.0" resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.25.0.tgz#33a1af70d5f2890aeb465a4a7793c1df6a9ea9fc" dependencies: @@ -472,6 +527,22 @@ babel-helper-explode-assignable-expression@7.0.0-alpha.18: babel-traverse "7.0.0-alpha.18" babel-types "7.0.0-alpha.18" +babel-helper-fixtures@7.0.0-alpha.15: + version "7.0.0-alpha.15" + resolved "https://registry.yarnpkg.com/babel-helper-fixtures/-/babel-helper-fixtures-7.0.0-alpha.15.tgz#f6014b506c98cb6c706bb38b2fb5264f004ba438" + dependencies: + lodash "^4.2.0" + semver "^5.3.0" + try-resolve "^1.0.0" + +babel-helper-fixtures@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-helper-fixtures/-/babel-helper-fixtures-6.22.0.tgz#3cb9eaf5feae29f001d2754ab43b14a9dfa304ff" + dependencies: + babel-runtime "^6.22.0" + lodash "^4.2.0" + try-resolve "^1.0.0" + babel-helper-function-name@7.0.0-alpha.15: version "7.0.0-alpha.15" resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-7.0.0-alpha.15.tgz#087bb6bb6677acde36b3c19f6bc1afedb3d12e30" @@ -514,6 +585,12 @@ babel-helper-optimise-call-expression@7.0.0-alpha.18: dependencies: babel-types "7.0.0-alpha.18" +babel-helper-plugin-test-runner@7.0.0-alpha.15: + version "7.0.0-alpha.15" + resolved "https://registry.yarnpkg.com/babel-helper-plugin-test-runner/-/babel-helper-plugin-test-runner-7.0.0-alpha.15.tgz#2a318588f35f25ef8b278b8946e56544f1dce121" + dependencies: + babel-helper-transform-fixture-test-runner "7.0.0-alpha.15" + babel-helper-regex@7.0.0-alpha.18: version "7.0.0-alpha.18" resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-7.0.0-alpha.18.tgz#c54f5cc91a10deef88924381e5a8bf4157cd480d" @@ -548,6 +625,32 @@ babel-helper-replace-supers@7.0.0-alpha.18: babel-traverse "7.0.0-alpha.18" babel-types "7.0.0-alpha.18" +babel-helper-transform-fixture-test-runner@7.0.0-alpha.15: + version "7.0.0-alpha.15" + resolved "https://registry.yarnpkg.com/babel-helper-transform-fixture-test-runner/-/babel-helper-transform-fixture-test-runner-7.0.0-alpha.15.tgz#45cdb7d4e1094c93b151779906d65dae7f6b24a4" + dependencies: + babel-code-frame "7.0.0-alpha.15" + babel-core "7.0.0-alpha.15" + babel-helper-fixtures "7.0.0-alpha.15" + babel-polyfill "7.0.0-alpha.15" + chai "^3.0.0" + lodash "^4.2.0" + resolve "^1.3.2" + source-map "^0.5.0" + +babel-helper-transform-fixture-test-runner@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-transform-fixture-test-runner/-/babel-helper-transform-fixture-test-runner-6.24.1.tgz#47eaa699acbe98a25c0f868e2d5666de8d33b4e5" + dependencies: + babel-code-frame "^6.22.0" + babel-core "^6.24.1" + babel-helper-fixtures "^6.22.0" + babel-polyfill "^6.23.0" + babel-runtime "^6.22.0" + chai "^3.0.0" + lodash "^4.2.0" + source-map "^0.5.0" + babel-helper-wrap-function@7.0.0-alpha.18: version "7.0.0-alpha.18" resolved "https://registry.yarnpkg.com/babel-helper-wrap-function/-/babel-helper-wrap-function-7.0.0-alpha.18.tgz#47d194f19d0fab899a60e768214b7e1fc71471b0" @@ -557,12 +660,25 @@ babel-helper-wrap-function@7.0.0-alpha.18: babel-traverse "7.0.0-alpha.18" babel-types "7.0.0-alpha.18" +babel-helpers@7.0.0-alpha.15: + version "7.0.0-alpha.15" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-7.0.0-alpha.15.tgz#60e08a396d7ce79104dd2da4651a61e625468ebe" + dependencies: + babel-template "7.0.0-alpha.15" + babel-helpers@7.0.0-alpha.18: version "7.0.0-alpha.18" resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-7.0.0-alpha.18.tgz#22e466d3f48e8ea777d47b50fdc2c98befc801e0" dependencies: babel-template "7.0.0-alpha.18" +babel-helpers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-loader@7.1.1, babel-loader@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.1.tgz#b87134c8b12e3e4c2a94e0546085bc680a2b8488" @@ -916,6 +1032,13 @@ babel-plugin-transform-unicode-property-regex@^2.0.2: babel-runtime "^6.23.0" regexpu-core "^4.1.1" +babel-polyfill@7.0.0-alpha.15: + version "7.0.0-alpha.15" + resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-7.0.0-alpha.15.tgz#4d1c8e3c86b4189591a6818bae90d1ab7eaab584" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.10.0" + babel-polyfill@7.0.0-alpha.18: version "7.0.0-alpha.18" resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-7.0.0-alpha.18.tgz#e5673adef75bf3c9014e9b7d0cb23520e58bd94a" @@ -923,6 +1046,14 @@ babel-polyfill@7.0.0-alpha.18: core-js "^2.4.0" regenerator-runtime "^0.10.0" +babel-polyfill@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d" + dependencies: + babel-runtime "^6.22.0" + core-js "^2.4.0" + regenerator-runtime "^0.10.0" + babel-preset-env@2.0.0-alpha.18: version "2.0.0-alpha.18" resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-2.0.0-alpha.18.tgz#1ff927011521e9bc97819d921700ccd3faa6c676" @@ -1048,6 +1179,18 @@ babel-register@7.0.0-alpha.18: pirates "^3.0.1" source-map-support "^0.4.2" +babel-register@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.24.1.tgz#7e10e13a2f71065bdfad5a1787ba45bca6ded75f" + dependencies: + babel-core "^6.24.1" + babel-runtime "^6.22.0" + core-js "^2.4.0" + home-or-tmp "^2.0.0" + lodash "^4.2.0" + mkdirp "^0.5.1" + source-map-support "^0.4.2" + babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0: version "6.25.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.25.0.tgz#33b98eaa5d482bb01a8d1aa6b437ad2b01aec41c" @@ -1073,7 +1216,7 @@ babel-template@7.0.0-alpha.18: babylon "7.0.0-beta.18" lodash "^4.2.0" -babel-template@^6.16.0: +babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.25.0: version "6.25.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071" dependencies: @@ -1490,6 +1633,14 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" +chai@^3.0.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" + dependencies: + assertion-error "^1.0.1" + deep-eql "^0.1.3" + type-detect "^1.0.0" + chai@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.1.tgz#66e21279e6f3c6415ff8231878227900e2171b39" @@ -2089,6 +2240,12 @@ dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" +deep-eql@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" + dependencies: + type-detect "0.1.1" + deep-eql@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-2.0.2.tgz#b1bac06e56f0a76777686d50c9feb75c2ed7679a" @@ -3344,6 +3501,13 @@ hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" +home-or-tmp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.1" + home-or-tmp@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-3.0.0.tgz#57a8fe24cf33cdd524860a15821ddc25c86671fb" @@ -4880,7 +5044,7 @@ os-locale@^2.0.0: lcid "^1.0.0" mem "^1.1.0" -os-tmpdir@^1.0.0, os-tmpdir@~1.0.1: +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -6121,6 +6285,14 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +type-detect@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" + +type-detect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" + type-detect@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-3.0.0.tgz#46d0cc8553abb7b13a352b0d6dea2fd58f2d9b55" From 6ec36a1428f47d8082148526b360be8031faf0b3 Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Sun, 20 Aug 2017 15:42:11 +0530 Subject: [PATCH 13/21] Improvements in handling of computed keys - don't use sequenceExpressions - use single declaration - use keys based on the node --- .../src/index.js | 37 +++++++++++++------ .../transformations/computed-keys/actual.js | 7 ++++ .../transformations/computed-keys/expected.js | 12 ++++++ .../test/fixtures/wip/wip/actual.js | 3 +- .../test/fixtures/wip/wip/expected.js | 20 +++------- 5 files changed, 51 insertions(+), 28 deletions(-) create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/computed-keys/actual.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/computed-keys/expected.js diff --git a/packages/babel-plugin-transform-decorators-2/src/index.js b/packages/babel-plugin-transform-decorators-2/src/index.js index a6a4b948c02a..d03c5ad66da2 100644 --- a/packages/babel-plugin-transform-decorators-2/src/index.js +++ b/packages/babel-plugin-transform-decorators-2/src/index.js @@ -7,24 +7,35 @@ import syntaxDecorators2 from "babel-plugin-syntax-decorators-2"; /** manual testing code **/ export default function({ types: t }) { + // a class expression with decorators is wrapped in a `decorate(..)` call. The following is to avoid + // processing the same class node again + const processedClassNodes = []; + // converts [(expression)] to [(let key = (expression))] if expression is impure // so as to avoid recomputation when the key is needed later + let injectedKeyDeclaration; function addKeysToComputedMembers(path) { const body = path.get("body.body"); for (const member of body) { - if (member.node.computed && member.get("key").isPure()) { - const parent = path.findParent(p => p.parentPath.isBlock()); - const ref = member.scope.generateUidIdentifier("key"); - parent.insertBefore( - t.variableDeclaration("let", [t.variableDeclarator(ref)]), + if (member.node.computed && !member.get("key").isPure()) { + const ref = member.scope.generateUidIdentifierBasedOnNode( + member.node.key, ); - const replacement = t.sequenceExpression([ - t.assignmentExpression("=", ref, member.node.key), - ]); - // FIXME: the following should work: member.get("key").replaceWith(replacement); - // but doesn't accept sequenceexpression type so we have to directly manipulate node - member.node.key = replacement; + + if (!injectedKeyDeclaration) { + const parent = path.getStatementParent(); + parent.insertBefore(t.variableDeclaration("let", [])); + injectedKeyDeclaration = parent.getSibling(parent.key - 1); + } + + injectedKeyDeclaration.pushContainer( + "declarations", + t.variableDeclarator(ref), + ); + + const replacement = t.assignmentExpression("=", ref, member.node.key); + member.get("key").replaceWith(replacement); } } } @@ -139,6 +150,8 @@ export default function({ types: t }) { }, ClassExpression(path, file) { + if (processedClassNodes.indexOf(path.node) > -1) return; + file.addHelper("makeElementDescriptor"); file.addHelper("mergeDuplicateElements"); file.addHelper("decorateClass", ["mergeDuplicateElements"]); @@ -166,6 +179,8 @@ export default function({ types: t }) { ? path.scope.buildUndefinedNode() : path.node.superClass; + processedClassNodes.push(path.node); + path.replaceWith( t.callExpression( t.callExpression(decorateIdentifier, [ diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/computed-keys/actual.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/computed-keys/actual.js new file mode 100644 index 000000000000..ca10ce4a4472 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/computed-keys/actual.js @@ -0,0 +1,7 @@ +class Bizz { + @dec uncomputed() {}; + @dec static [3 + 7] () {}; + @dec [foo()] () {} + [computed + and + undecorated] () {}; +} + diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/computed-keys/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/computed-keys/expected.js new file mode 100644 index 000000000000..f5368ccb0423 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/computed-keys/expected.js @@ -0,0 +1,12 @@ +let _foo, _ref; + +let Bizz = babelHelpers.decorate(class Bizz { + uncomputed() {} + + static [3 + 7]() {} + + [_foo = foo()]() {} + + [_ref = computed + and + undecorated]() {} + +}, [[_ref]], [["uncomputed", [dec]], [3 + 7, [dec], true], [_foo, [dec]]], void 0)([]); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/actual.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/actual.js index c486ba7c609e..bada88422a88 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/actual.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/actual.js @@ -1,9 +1,8 @@ -let decorate = () => void 0, dec, bar, foo = {bar: () => void 0}, baz, decorator; //just to supress ReferenceErrors - @decorator class Bizz { @dec m1() {}; @bar @foo.bar(baz) m2() {}; @dec static [3 + 7] () {}; andAnUndecoratedMethod () {}; [calculated + and + undecorated] () {}; + @dec [foo()] () {} } diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js index 14444812071a..0a639295d20b 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js @@ -1,24 +1,14 @@ -let decorate = () => void 0, - dec, - bar, - foo = { - bar: () => void 0 -}, - baz, - decorator; //just to supress ReferenceErrors - - -let _key; - let Bizz = babelHelpers.decorate(class Bizz { m1() {} m2() {} - static [(_key = 3 + 7)]() {} + static [3 + 7]() {} andAnUndecoratedMethod() {} - [calculated + and + undecorated]() {} + [_ref = calculated + and + undecorated]() {} + + [_foo = foo()]() {} -}, [["andAnUndecoratedMethod"], [calculated + and + undecorated]], [["m1", [dec]], ["m2", [bar, foo.bar(baz)]], [(_key = 3 + 7), [dec], true]], void 0)([decorator]); \ No newline at end of file +}, [["andAnUndecoratedMethod"], [_ref]], [["m1", [dec]], ["m2", [bar, foo.bar(baz)]], [3 + 7, [dec], true], [_foo, [dec]]], void 0)([decorator]); From 2e7c6a14cf7d91add1516fb0f0cfd840f59dc83c Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Sun, 20 Aug 2017 21:24:15 +0530 Subject: [PATCH 14/21] Add predicate hasDecorators (for early exit) --- .../src/index.js | 28 ++++++++++++++----- .../class-decorators/expected.js | 5 ++-- .../transformations/no-decorators/actual.js | 5 ++++ .../transformations/no-decorators/expected.js | 8 ++++++ 4 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/no-decorators/actual.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/no-decorators/expected.js diff --git a/packages/babel-plugin-transform-decorators-2/src/index.js b/packages/babel-plugin-transform-decorators-2/src/index.js index d03c5ad66da2..9f898ac1ccf2 100644 --- a/packages/babel-plugin-transform-decorators-2/src/index.js +++ b/packages/babel-plugin-transform-decorators-2/src/index.js @@ -7,10 +7,6 @@ import syntaxDecorators2 from "babel-plugin-syntax-decorators-2"; /** manual testing code **/ export default function({ types: t }) { - // a class expression with decorators is wrapped in a `decorate(..)` call. The following is to avoid - // processing the same class node again - const processedClassNodes = []; - // converts [(expression)] to [(let key = (expression))] if expression is impure // so as to avoid recomputation when the key is needed later let injectedKeyDeclaration; @@ -40,6 +36,22 @@ export default function({ types: t }) { } } + //a predicate which tells if a path has any decorators at all or not. + function hasDecorators(path) { + if (path.node.decorators && path.node.decorators.length > 0) return true; + + const body = path.node.body.body; + for (let i = 0; i < body.length; i++) { + const method = body[i]; + if (!t.isClassMethod(method)) continue; + if (method.decorators && method.decorators.length > 0) { + return true; + } + } + + return false; + } + // expects a method node function getKey(node) { if (node.computed) { @@ -121,6 +133,8 @@ export default function({ types: t }) { visitor: { // export default is a special case since it expects and expression rather than a declaration ExportDefaultDeclaration(path) { + if (!hasDecorators(path)) return; + const classPath = path.get("declaration"); if (!classPath.isClassDeclaration()) return; @@ -139,6 +153,8 @@ export default function({ types: t }) { // replace declaration with a let declaration ClassDeclaration(path) { + if (!hasDecorators(path)) return; + const { node } = path; const ref = node.id || path.scope.generateUidIdentifier("class"); @@ -150,7 +166,7 @@ export default function({ types: t }) { }, ClassExpression(path, file) { - if (processedClassNodes.indexOf(path.node) > -1) return; + if (!hasDecorators(path)) return; file.addHelper("makeElementDescriptor"); file.addHelper("mergeDuplicateElements"); @@ -179,8 +195,6 @@ export default function({ types: t }) { ? path.scope.buildUndefinedNode() : path.node.superClass; - processedClassNodes.push(path.node); - path.replaceWith( t.callExpression( t.callExpression(decorateIdentifier, [ diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/class-decorators/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/class-decorators/expected.js index 79c6344892fe..f18518c4a98d 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/class-decorators/expected.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/class-decorators/expected.js @@ -2,7 +2,8 @@ let Foo = babelHelpers.decorate(class Foo { someMethod() {} }, [["someMethod"]], [], void 0)([classDecOuter("args"), classDecInner]); -let Undecorated = class Undecorated { + +class Undecorated { someMethod() {} -}; \ No newline at end of file +} diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/no-decorators/actual.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/no-decorators/actual.js new file mode 100644 index 000000000000..2389fa0a8474 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/no-decorators/actual.js @@ -0,0 +1,5 @@ +class Foo { + method() {} + [3 + 7] () {} + static method() {} +} diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/no-decorators/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/no-decorators/expected.js new file mode 100644 index 000000000000..ccbddf1010e5 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/no-decorators/expected.js @@ -0,0 +1,8 @@ +class Foo { + method() {} + + [3 + 7]() {} + + static method() {} + +} From 8e28ca7e49240644d913190661710cd009c2db38 Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Sun, 20 Aug 2017 22:14:48 +0530 Subject: [PATCH 15/21] use path.scope.parent.push & other review suggestions --- .../src/index.js | 16 +++------------- .../transformations/computed-keys/expected.js | 2 +- .../transformations/literal-keys/actual.js | 4 ++++ .../transformations/literal-keys/expected.js | 6 ++++++ .../test/fixtures/wip/wip/expected.js | 2 ++ 5 files changed, 16 insertions(+), 14 deletions(-) create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/literal-keys/actual.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/literal-keys/expected.js diff --git a/packages/babel-plugin-transform-decorators-2/src/index.js b/packages/babel-plugin-transform-decorators-2/src/index.js index 9f898ac1ccf2..0d937b41c66c 100644 --- a/packages/babel-plugin-transform-decorators-2/src/index.js +++ b/packages/babel-plugin-transform-decorators-2/src/index.js @@ -9,7 +9,6 @@ import syntaxDecorators2 from "babel-plugin-syntax-decorators-2"; export default function({ types: t }) { // converts [(expression)] to [(let key = (expression))] if expression is impure // so as to avoid recomputation when the key is needed later - let injectedKeyDeclaration; function addKeysToComputedMembers(path) { const body = path.get("body.body"); @@ -19,17 +18,7 @@ export default function({ types: t }) { member.node.key, ); - if (!injectedKeyDeclaration) { - const parent = path.getStatementParent(); - parent.insertBefore(t.variableDeclaration("let", [])); - injectedKeyDeclaration = parent.getSibling(parent.key - 1); - } - - injectedKeyDeclaration.pushContainer( - "declarations", - t.variableDeclarator(ref), - ); - + path.scope.parent.push({ id: ref }); const replacement = t.assignmentExpression("=", ref, member.node.key); member.get("key").replaceWith(replacement); } @@ -63,7 +52,7 @@ export default function({ types: t }) { return node.key; } } else { - return t.stringLiteral(node.key.name); + return t.stringLiteral(node.key.name || String(node.key.value)); } } @@ -175,6 +164,7 @@ export default function({ types: t }) { const decorateIdentifier = file.addHelper("decorate", [ "decorateClass", "decorateElement", + "makeElementDescriptor", ]); addKeysToComputedMembers(path); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/computed-keys/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/computed-keys/expected.js index f5368ccb0423..bb8aac7c4f42 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/computed-keys/expected.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/computed-keys/expected.js @@ -1,4 +1,4 @@ -let _foo, _ref; +var _foo, _ref; let Bizz = babelHelpers.decorate(class Bizz { uncomputed() {} diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/literal-keys/actual.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/literal-keys/actual.js new file mode 100644 index 000000000000..bb1c981d9ea3 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/literal-keys/actual.js @@ -0,0 +1,4 @@ +@dec class Foo { + 3 () {} + "yo" () {} +} diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/literal-keys/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/literal-keys/expected.js new file mode 100644 index 000000000000..77e3c148d42c --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/literal-keys/expected.js @@ -0,0 +1,6 @@ +let Foo = babelHelpers.decorate(class Foo { + 3() {} + + "yo"() {} + +}, [["3"], ["yo"]], [], void 0)([dec]); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js index 0a639295d20b..31ca54bd0493 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/wip/wip/expected.js @@ -1,3 +1,5 @@ +var _ref, _foo; + let Bizz = babelHelpers.decorate(class Bizz { m1() {} From 1776e2474493ecf06b5b16e9206683f49f3eb90d Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Thu, 24 Aug 2017 20:23:38 +0530 Subject: [PATCH 16/21] Account for getters and setters --- .../src/index.js | 12 +++++++ .../transformations/accessors/actual.js | 24 +++++++++++++ .../transformations/accessors/expected.js | 34 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/accessors/actual.js create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/accessors/expected.js diff --git a/packages/babel-plugin-transform-decorators-2/src/index.js b/packages/babel-plugin-transform-decorators-2/src/index.js index 0d937b41c66c..5fc24be40e10 100644 --- a/packages/babel-plugin-transform-decorators-2/src/index.js +++ b/packages/babel-plugin-transform-decorators-2/src/index.js @@ -1,4 +1,5 @@ import syntaxDecorators2 from "babel-plugin-syntax-decorators-2"; +import _ from "lodash"; //TODO: will have to check for dot (.) access to reserved keywords in decorator members //TODO: check if we need the 'define' + 'Property' and 'ke' + 'ys' hacks as seen in @@ -100,6 +101,7 @@ export default function({ types: t }) { function undecoratedMethods(path) { const body = path.get("body.body"); const result = []; // shape: [["method1"], ["method2"], ["staticMethod1", true]] + const avoidDuplicates = []; // to avoid duplicates when both a getter and a setter are involved for (const method of body) { if (method.node.decorators && method.node.decorators.length > 0) continue; @@ -107,6 +109,16 @@ export default function({ types: t }) { const key = getKey(method.node); + if (method.node.kind == "get" || method.node.kind == "set") { + const record = [key.value, method.node.static]; + + if (_.find(avoidDuplicates, r => _.isEqual(record, r))) { + continue; + } else { + avoidDuplicates.push(record); + } + } + if (method.node.static) { result.push(t.arrayExpression([key, t.booleanLiteral(true)])); } else { diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/accessors/actual.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/accessors/actual.js new file mode 100644 index 000000000000..398085bdb57a --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/accessors/actual.js @@ -0,0 +1,24 @@ +class Foo { + @decorated method() {} + get bar() {} + get baz() {} + set baz(val) {} +} + +class Bar { + @decorated method() {} + get 3() {} + set 3(val) {} +} + +class Baz { + @decorated method() {} + get 3() {} + set "3"(val) {} +} + +class Bam { + @decorated method() {} + get yo() {} + set "yo"(val) {} +} diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/accessors/expected.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/accessors/expected.js new file mode 100644 index 000000000000..0eb940e3bd88 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/transformations/accessors/expected.js @@ -0,0 +1,34 @@ +let Foo = babelHelpers.decorate(class Foo { + method() {} + + get bar() {} + + get baz() {} + + set baz(val) {} + +}, [["bar"], ["baz"]], [["method", [decorated]]], void 0)([]); +let Bar = babelHelpers.decorate(class Bar { + method() {} + + get 3() {} + + set 3(val) {} + +}, [["3"]], [["method", [decorated]]], void 0)([]); +let Baz = babelHelpers.decorate(class Baz { + method() {} + + get 3() {} + + set "3"(val) {} + +}, [["3"]], [["method", [decorated]]], void 0)([]); +let Bam = babelHelpers.decorate(class Bam { + method() {} + + get yo() {} + + set "yo"(val) {} + +}, [["yo"]], [["method", [decorated]]], void 0)([]); From 4e527a334cfbe205b0a94df6e382a4685114d5d2 Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Thu, 24 Aug 2017 23:48:17 +0530 Subject: [PATCH 17/21] Pass elementDescriptor to decorators not propertyDescriptor --- packages/babel-helpers/src/helpers.js | 4 ++-- .../test/fixtures/basic/finishers/exec.js | 2 +- .../test/fixtures/examples/change-enumerability/exec.js | 2 +- .../test/fixtures/examples/logger/exec.js | 5 +++-- .../test/fixtures/examples/overrider/exec.js | 2 +- .../test/fixtures/examples/spare/exec.js | 9 +++++---- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index 97b1027824ba..f040424d56f4 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -357,7 +357,7 @@ helpers.decorateElement = template(` for (let i = decorators.length - 1; i >= 0; i--) { const decorator = decorators[i]; - const result = decorator(previousDescriptor.descriptor); + const result = decorator(previousDescriptor); //TODO: why does .finisher exist on an elementDescriptor? the following conditional deviates from //the spec because it uses result.finishers rather than result.descriptor.finisher @@ -365,7 +365,7 @@ helpers.decorateElement = template(` finishers = finishers.concat(result.finishers); } - previousDescriptor.descriptor = result.descriptor; // just change the property descriptor + previousDescriptor = result.descriptor; const extrasObject = result.extras; diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/finishers/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/finishers/exec.js index 04ea5cf47646..d3cc91439472 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/finishers/exec.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/finishers/exec.js @@ -2,7 +2,7 @@ const logs = []; function overrider(fn) { return function(descriptor) { - descriptor.value = fn; + descriptor.descriptor.value = fn; return {descriptor, extras: [], finishers: []}; } } diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/change-enumerability/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/change-enumerability/exec.js index dbba987e4eab..6d338d262825 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/change-enumerability/exec.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/change-enumerability/exec.js @@ -1,6 +1,6 @@ function enumerable(bool) { return function(descriptor) { - descriptor.enumerable = bool; + descriptor.descriptor.enumerable = bool; return {descriptor, finishers: [], extras: []}; } } diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/logger/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/logger/exec.js index 2df2d3ceda54..823903c8d6ea 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/logger/exec.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/logger/exec.js @@ -2,8 +2,9 @@ const calls = []; function log(message) { return function (descriptor) { - let oldFunc = descriptor.value; - descriptor.value = function(...args) { + let propertyDescriptor = descriptor.descriptor; + let oldFunc = propertyDescriptor.value; + propertyDescriptor.value = function(...args) { calls.push(message + " entered"); let result = oldFunc.apply(this, args); calls.push(message + " exited"); diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/overrider/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/overrider/exec.js index ff279de23059..1c916ebaaed4 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/overrider/exec.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/overrider/exec.js @@ -1,6 +1,6 @@ function overrider(fn) { return function(descriptor) { - descriptor.value = fn; + descriptor.descriptor.value = fn; return {descriptor, extras: [], finishers: []}; } } diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/spare/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/spare/exec.js index a82ea40e1875..0bea3dab3fa6 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/spare/exec.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/examples/spare/exec.js @@ -1,11 +1,12 @@ // makes an undecorated copy of the method function spare(name, isStatic) { return function(descriptor) { + name = name || descriptor.key + "_spare"; const clone = { kind: "property", isStatic: !!isStatic, key: name, - descriptor: Object.assign({}, descriptor) + descriptor: Object.assign({}, descriptor.descriptor) } return {descriptor, extras: [clone], finishers: []}; } @@ -13,18 +14,18 @@ function spare(name, isStatic) { function overrider(fn) { return function(descriptor) { - descriptor.value = fn; + descriptor.descriptor.value = fn; return {descriptor, extras: [], finishers: []}; } } class Foo { - @overrider(() => 3) @spare("spared") method() { + @overrider(() => 3) @spare() method() { return 2; } } const x = new Foo(); -assert.equal(x.spared(), 2); +assert.equal(x.method_spare(), 2); assert.equal(x.method(), 3); From 44465f2fcbc38bca77b9583ab9dff23369bf8471 Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Tue, 29 Aug 2017 20:05:16 +0530 Subject: [PATCH 18/21] configurability checks --- packages/babel-helpers/src/helpers.js | 19 +++++++++ .../fixtures/basic/configurability/exec.js | 40 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/basic/configurability/exec.js diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index f040424d56f4..9e5476cae496 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -357,8 +357,27 @@ helpers.decorateElement = template(` for (let i = decorators.length - 1; i >= 0; i--) { const decorator = decorators[i]; + const shallowClone = { + configurable: previousDescriptor.descriptor.configurable, + enumerable: previousDescriptor.descriptor.enumerable, + writable: previousDescriptor.descriptor.writable, + value: previousDescriptor.descriptor.value + } + const result = decorator(previousDescriptor); + if (!shallowClone.configurable) { + let check = + result.descriptor.configurable === shallowClone.configurable && + result.descriptor.enumerable === shallowClone.enumerable && + result.descriptor.writable === shallowClone.writable && + result.descriptor.value === shallowClone.value; + + if (!check) { + throw new Error("Decorator tried to change unconfigurable property descriptor"); + } + } + //TODO: why does .finisher exist on an elementDescriptor? the following conditional deviates from //the spec because it uses result.finishers rather than result.descriptor.finisher if (result.finishers) { diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/configurability/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/configurability/exec.js new file mode 100644 index 000000000000..d8ebb1246a40 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/configurability/exec.js @@ -0,0 +1,40 @@ +function set(prop, value) { + return function(descriptor) { + descriptor.descriptor[prop] = value; + return {descriptor, extras: [], finishers: []}; + } +} + +const msg = "Decorator tried to change unconfigurable property descriptor"; + +assert.throws(() => { + class Foo { + @set('configurable', true) @set('configurable', false) method() {} + } +}, msg); + +assert.throws(() => { + class Foo { + @set('writable', false) @set('configurable', false) method() {} + } +}, msg); + +assert.throws(() => { + class Foo { + @set('enumerable', true) @set('configurable', false) method() {} + } +}, msg); + +assert.throws(() => { + class Foo { + @set('value', (x) => x) @set('configurable', false) method() {} + } +}, msg); + +assert.doesNotThrow(() => { + class Foo { + @set('configurable', true) method1() {} + @set('writable', true) method2() {} + @set('enumerable', false) method3() {} + } +}, msg); From f86b327d913d00f6fdc19db027842ec65a9aef2c Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Sat, 16 Sep 2017 15:19:31 +0530 Subject: [PATCH 19/21] Merge getters and setters; Map->object in decorate And other review suggestions --- packages/babel-helpers/src/helpers.js | 74 +++++++++++++------ .../src/index.js | 7 -- .../test/fixtures/basic/merging/exec.js | 57 ++++++++++++++ 3 files changed, 108 insertions(+), 30 deletions(-) create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/basic/merging/exec.js diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index 9e5476cae496..89355b004a01 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -276,42 +276,44 @@ helpers.decorate = template(` (function (constructor, undecorated, memberDecorators, heritage) { const prototype = constructor.prototype; let finishers = []; - const elementDescriptors = new Map(); // elementDescriptors is meant to be an array, so this will be converted later + const elementDescriptors = {}; // elementDescriptors is meant to be an array, so this will be converted later + const staticElementDescriptors = {}; for (const [key, isStatic] of undecorated) { + const map = isStatic? staticElementDescriptors : elementDescriptors; const target = isStatic ? constructor : prototype; const propertyDescriptor = Object.getOwnPropertyDescriptor(target, key); - elementDescriptors.set( - [key, isStatic], - babelHelpers.makeElementDescriptor( - "property", - key, - isStatic, - propertyDescriptor, - ) + map[key] = babelHelpers.makeElementDescriptor( + "property", + key, + isStatic, + propertyDescriptor, ); } - // decorate and store in elementDescriptors + // decorate and store in elementDescriptors or staticElementDescriptors for (const [key, decorators, isStatic] of memberDecorators) { + const map = isStatic ? staticElementDescriptors : elementDescriptors; const target = isStatic ? constructor : prototype; - const propertyDescriptor = - elementDescriptors.has([key, isStatic]) && elementDescriptors.get([key, isStatic]).descriptor - || Object.getOwnPropertyDescriptor(target, key); + const propertyDescriptor = + map[key] && map[key].descriptor || Object.getOwnPropertyDescriptor(target, key); const elementDescriptor = babelHelpers.makeElementDescriptor( "property", key, isStatic, propertyDescriptor, - ) + ); + const decorated = babelHelpers.decorateElement(elementDescriptor, decorators); - elementDescriptors.set([key, isStatic], decorated.descriptor); + map[key] = decorated.descriptor; for (const extra of decorated.extras) { // extras is an array of element descriptors - elementDescriptors.set([extra.key, extra.isStatic], extra); + const map = extra.isStatic? staticElementDescriptors : elementDescriptors; + // TODO: refactor to use proper merging logic here. currently it's just overriding + map[extra.key] = extra; } finishers = finishers.concat(decorated.finishers); @@ -322,12 +324,13 @@ helpers.decorate = template(` constructor, classDecorators, heritage, - Array.from(elementDescriptors.values()) + Object.values(elementDescriptors).concat(Object.values(staticElementDescriptors)) ); finishers = finishers.concat(result.finishers); //TODO: heritage hacks so result.constructor has the correct prototype and instanceof results - //TODO: step 38 and 39, what do they mean "initialize"? + //NOTE: step 38 and 39 in spec, refer to some "initialize" state which hasn't been implemented, + //because it's unlikely to stay in future versions of the spec for (const elementDescriptor of result.elements) { const target = elementDescriptor.isStatic ? constructor : prototype; @@ -347,6 +350,7 @@ helpers.decorate = template(` }); `); +//TODO guarding against malformed descriptors returned by decorators helpers.decorateElement = template(` (function (descriptor, decorators) { //spec uses the param "element" instead of "descriptor" and finds descriptor from it @@ -583,21 +587,45 @@ helpers.interopRequireWildcard = template(` helpers.makeElementDescriptor = template(` (function (kind, key, isStatic, descriptor, finisher) { - return { kind, key, isStatic, descriptor, finisher }; + var elementDescriptor = { kind, key, isStatic, descriptor}; + if (finisher) { + elementDescriptor.finisher = finisher; + } + return elementDescriptor; }); `); -//TODO +/** + * NOTE: Details for merging haven't been decided on by TC39. + * The following function implements a reasonable merging strategy - which + * is to merge getters and setters, and to override everything else + **/ helpers.mergeDuplicateElements = template(` (function (elements) { let elementMap = {}; let staticElementMap = {}; for (let elementDescriptor of elements) { - if (elementDescriptor.isStatic) { - staticElementMap[elementDescriptor.key] = elementDescriptor; + let map = elementDescriptor.isStatic? staticElementMap : elementMap; + + if (!map[elementDescriptor.key]) { + map[elementDescriptor.key] = elementDescriptor; } else { - elementMap[elementDescriptor.key] = elementDescriptor; + let prev = map[elementDescriptor.key]; + let isDataDescriptor = !!prev.descriptor.value; + let newIsDataDescriptor = !!elementDescriptor.descriptor.value; + + if (!newIsDataDescriptor && !isDataDescriptor) { + // if both are accessor descriptors, merge getters and setters + let get = elementDescriptor.descriptor.get || prev.descriptor.get; + let set = elementDescriptor.descriptor.set || prev.descriptor.set; + + let descriptor = Object.assign({}, elementDescriptor.descriptor, {get, set}); + + map[elementDescriptor.key] = Object.assign({}, elementDescriptor, {descriptor}); + } else { + map[elementDescriptor.key] = elementDescriptor; // overrideprevious descriptor + } } } diff --git a/packages/babel-plugin-transform-decorators-2/src/index.js b/packages/babel-plugin-transform-decorators-2/src/index.js index 5fc24be40e10..9eb2f61bd8b3 100644 --- a/packages/babel-plugin-transform-decorators-2/src/index.js +++ b/packages/babel-plugin-transform-decorators-2/src/index.js @@ -185,13 +185,6 @@ export default function({ types: t }) { const methodDecorators = takeMethodDecorators(path); const classDecorators = takeClassDecorators(path); - if ( - methodDecorators.elements.length == 0 && - classDecorators.elements.length == 0 - ) { - return; - } - const superClass = path.node.superClass == null ? path.scope.buildUndefinedNode() diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/merging/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/merging/exec.js new file mode 100644 index 000000000000..03e8e5826fe1 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/merging/exec.js @@ -0,0 +1,57 @@ +let calls = []; + +function getter(name) { + return function (descriptor) { + const extra = { + kind: "property", + key: name, + isStatic: false, + descriptor: { + enumerable: true, + configurable: true, + get: function () { + calls.push("getter called for " + name); + } + } + } + + return {descriptor, extras: [extra], finishers: []} + } +} + +function setter(name) { + return function (descriptor) { + const extra = { + kind: "property", + key: name, + isStatic: false, + descriptor: { + enumerable: true, + configurable: true, + set: function (value) { + calls.push("setter called for " + name); + } + } + } + + return {descriptor, extras: [extra], finishers: []} + } +} + +class Foo { + @setter("sets") @getter("gets") @getter("both") @setter("both") bar () {return "hello"} +} + +let x = new Foo(); +assert.equal(x.bar(), "hello"); + +x.both; +x.both = 3; +x.sets; +x.sets = 5; +x.gets; +x.gets = 7; + +// getters and setters are merged +assert.deepEqual(calls, ["getter called for both", "setter called for both", "setter called for sets", "getter called for gets"]); + From 96f51aad229eae0f817d7e436e50b330c5d97dae Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Sat, 16 Sep 2017 15:21:50 +0530 Subject: [PATCH 20/21] Add failing test for instanceof --- .../test/fixtures/basic/parentage/exec.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 packages/babel-plugin-transform-decorators-2/test/fixtures/basic/parentage/exec.js diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/parentage/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/parentage/exec.js new file mode 100644 index 000000000000..4ff6cf658a51 --- /dev/null +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/parentage/exec.js @@ -0,0 +1,20 @@ +function noopClassDec(constructor, heritage, elements) { + return {constructor, elements, finishers: []} +} + +function noopMethodDec(descriptor) { + return {descriptor, extras: [], finishers: []} +} + +@noopClassDec class Bar { + @noopMethodDec foo() {} +} + +@noopClassDec class Foo { + @noopMethodDec bar() {} +} + +let x = new Foo(); + +assert.ok(x instanceof Foo); +assert.ok(x instanceof Bar); From 76f5d3cca8d00e23bed36c418246fdec1ef0ba04 Mon Sep 17 00:00:00 2001 From: Peeyush Kushwaha Date: Wed, 20 Sep 2017 23:51:58 +0530 Subject: [PATCH 21/21] Fix parentage tests. It's not an issue I had missed extends in the test case but the asserts were expecting it. I thought that tests were failing for more sinister reasons, but thankfully it isn't so --- .../test/fixtures/basic/parentage/exec.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/parentage/exec.js b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/parentage/exec.js index 4ff6cf658a51..1d8c896bd355 100644 --- a/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/parentage/exec.js +++ b/packages/babel-plugin-transform-decorators-2/test/fixtures/basic/parentage/exec.js @@ -10,11 +10,15 @@ function noopMethodDec(descriptor) { @noopMethodDec foo() {} } -@noopClassDec class Foo { +@noopClassDec class Foo extends Bar { @noopMethodDec bar() {} } let x = new Foo(); +let y = new Bar(); assert.ok(x instanceof Foo); assert.ok(x instanceof Bar); + +assert.ok(y instanceof Bar); +assert.equal(y instanceof Foo, false);