diff --git a/src/acorn/plugins/flow.js b/src/acorn/plugins/flow.js index bb54b9c44a..2021a7a4d9 100644 --- a/src/acorn/plugins/flow.js +++ b/src/acorn/plugins/flow.js @@ -710,27 +710,29 @@ acorn.plugins.flow = function (instance) { } }) + instance.extend("parseClassProperty", function (inner) { + return function (node) { + if (this.type === tt.colon) { + node.typeAnnotation = this.flow_parseTypeAnnotation() + } + return inner.call(this, node) + } + }) + instance.extend("isClassProperty", function (inner) { + return function () { + return this.type === tt.colon || inner.call(this) + } + }) + instance.extend("parseClassMethod", function (inner) { return function (classBody, method, isGenerator, isAsync) { - var classProperty = false - - if (this.type === tt.colon) { - method.typeAnnotation = this.flow_parseTypeAnnotation() - classProperty = true - } - - if (classProperty) { - this.semicolon() - classBody.body.push(this.finishNode(method, "ClassProperty")) - } else { - var typeParameters - if (this.isRelational("<")) { - typeParameters = this.flow_parseTypeParameterDeclaration() - } - method.value = this.parseMethod(isGenerator, isAsync) - method.value.typeParameters = typeParameters - classBody.body.push(this.finishNode(method, "MethodDefinition")) + var typeParameters + if (this.isRelational("<")) { + typeParameters = this.flow_parseTypeParameterDeclaration() } + method.value = this.parseMethod(isGenerator, isAsync) + method.value.typeParameters = typeParameters + classBody.body.push(this.finishNode(method, "MethodDefinition")) } }) diff --git a/src/acorn/src/expression.js b/src/acorn/src/expression.js index a0dbb57831..871403e355 100755 --- a/src/acorn/src/expression.js +++ b/src/acorn/src/expression.js @@ -418,6 +418,7 @@ pp.parseParenAndDistinguishExpression = function(start, isAsync) { par.expression = val return this.finishNode(par, "ParenthesizedExpression") } else { + val.parenthesizedExpression = true return val } } diff --git a/src/acorn/src/lval.js b/src/acorn/src/lval.js index b7c4f2707d..3f5eff3532 100755 --- a/src/acorn/src/lval.js +++ b/src/acorn/src/lval.js @@ -9,6 +9,14 @@ const pp = Parser.prototype // if possible. pp.toAssignable = function(node, isBinding) { + if (node.parenthesizedExpression) { + if (node.type === "ObjectExpression") { + this.raise(node.start, "You're trying to assign to a parenthesized expression, instead of `({ foo }) = {}` use `({ foo } = {})`"); + } else { + this.raise(node.start, "Parenthesized left hand expressions are illegal"); + } + } + if (this.options.ecmaVersion >= 6 && node) { switch (node.type) { case "Identifier": diff --git a/src/acorn/src/statement.js b/src/acorn/src/statement.js index 0e77cdc308..e6e8cee49f 100755 --- a/src/acorn/src/statement.js +++ b/src/acorn/src/statement.js @@ -150,10 +150,17 @@ pp.parseDebuggerStatement = function(node) { } pp.parseDoStatement = function(node) { + let start = this.markPosition() this.next() this.labels.push(loopLabel) node.body = this.parseStatement(false) this.labels.pop() + if (this.options.features["es7.doExpressions"] && this.type !== tt._while) { + let container = this.startNodeAt(start) + container.expression = this.finishNode(node, "DoExpression") + this.semicolon() + return this.finishNode(container, "ExpressionStatement") + } this.expect(tt._while) node.test = this.parseParenExpression() if (this.options.ecmaVersion >= 6) @@ -455,13 +462,17 @@ pp.parseClass = function(node, isStatement) { while (!this.eat(tt.braceR)) { if (this.eat(tt.semi)) continue if (this.options.features["es7.decorators"] && this.type === tt.at) { - decorators.push(this.parseDecorator()); + decorators.push(this.parseDecorator()) continue; } var method = this.startNode() + if (this.options.features["es7.decorators"] && decorators.length) { + method.decorators = decorators + decorators = [] + } var isGenerator = this.eat(tt.star), isAsync = false this.parsePropertyName(method) - if (this.type !== tt.parenL && !method.computed && method.key.type === "Identifier" && + if (this.options.features["es7.classProperties"] && this.type !== tt.parenL && !method.computed && method.key.type === "Identifier" && method.key.name === "static") { if (isGenerator) this.unexpected() method['static'] = true @@ -470,6 +481,10 @@ pp.parseClass = function(node, isStatement) { } else { method['static'] = false } + if (!isGenerator && method.key.type === "Identifier" && !method.computed && this.isClassProperty()) { + classBody.body.push(this.parseClassProperty(method)) + continue + } if (this.options.features["es7.asyncFunctions"] && this.type !== tt.parenL && !method.computed && method.key.type === "Identifier" && method.key.name === "async") { isAsync = true @@ -488,9 +503,8 @@ pp.parseClass = function(node, isStatement) { method.kind = "constructor" } } - if (this.options.features["es7.decorators"] && decorators.length) { - method.decorators = decorators - decorators = [] + if (method.kind === "constructor" && method.decorators) { + this.raise(method.start, "You can't attach decorators to a class constructor") } this.parseClassMethod(classBody, method, isGenerator, isAsync) } @@ -501,16 +515,32 @@ pp.parseClass = function(node, isStatement) { return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression") } -pp.parseClassMethod = function (classBody, method, isGenerator, isAsync) { +pp.isClassProperty = function() { + return this.type === tt.eq || (this.type === tt.semi || this.canInsertSemicolon()) +} + +pp.parseClassProperty = function(node) { + if (this.type === tt.eq) { + if (!this.options.features["es7.classProperties"]) this.unexpected() + this.next() + node.value = this.parseMaybeAssign(); + } else { + node.value = null; + } + this.semicolon() + return this.finishNode(node, "ClassProperty") +} + +pp.parseClassMethod = function(classBody, method, isGenerator, isAsync) { method.value = this.parseMethod(isGenerator, isAsync) classBody.body.push(this.finishNode(method, "MethodDefinition")) } -pp.parseClassId = function (node, isStatement) { +pp.parseClassId = function(node, isStatement) { node.id = this.type === tt.name ? this.parseIdent() : isStatement ? this.unexpected() : null } -pp.parseClassSuper = function (node) { +pp.parseClassSuper = function(node) { node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts() : null } diff --git a/src/babel/generation/generators/expressions.js b/src/babel/generation/generators/expressions.js index f23e8a98e4..6324869537 100644 --- a/src/babel/generation/generators/expressions.js +++ b/src/babel/generation/generators/expressions.js @@ -19,6 +19,12 @@ export function UnaryExpression(node, print) { print(node.argument); } +export function DoExpression(node, print) { + this.push("do"); + this.space(); + print(node.body); +} + export function UpdateExpression(node, print) { if (node.prefix) { this.push(node.operator); diff --git a/src/babel/transformation/helpers/define-map.js b/src/babel/transformation/helpers/define-map.js index 4dcf58d87a..8f09a27e18 100644 --- a/src/babel/transformation/helpers/define-map.js +++ b/src/babel/transformation/helpers/define-map.js @@ -51,7 +51,7 @@ export function toClassObject(mutatorMap) { if (key[0] === "_") return; var inheritNode = node; - if (t.isMethodDefinition(node)) node = node.value; + if (t.isMethodDefinition(node) || t.isClassProperty(node)) node = node.value; var prop = t.property("init", t.identifier(key), node); t.inheritsComments(prop, inheritNode); diff --git a/src/babel/transformation/templates/call-instance-decorator.js b/src/babel/transformation/templates/call-instance-decorator.js new file mode 100644 index 0000000000..0fcedc4e7d --- /dev/null +++ b/src/babel/transformation/templates/call-instance-decorator.js @@ -0,0 +1 @@ +this.KEY = INITIALIZERS.KEY.call(this); diff --git a/src/babel/transformation/templates/call-static-decorator.js b/src/babel/transformation/templates/call-static-decorator.js new file mode 100644 index 0000000000..cca17bbf3b --- /dev/null +++ b/src/babel/transformation/templates/call-static-decorator.js @@ -0,0 +1 @@ +CONSTRUCTOR.KEY = INITIALIZERS.KEY.call(CONSTRUCTOR); diff --git a/src/babel/transformation/templates/helper-create-decorated-class.js b/src/babel/transformation/templates/helper-create-decorated-class.js index af5b6ae992..cab51157e0 100644 --- a/src/babel/transformation/templates/helper-create-decorated-class.js +++ b/src/babel/transformation/templates/helper-create-decorated-class.js @@ -1,5 +1,5 @@ (function() { - function defineProperties(target, descriptors) { + function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i ++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; @@ -23,15 +23,16 @@ throw new TypeError("The decorator for method " + descriptor.key + " is of the invalid type " + typeof decorator); } } + initializers[key] = descriptor.initializer; } Object.defineProperty(target, key, descriptor); } } - return function (Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); + return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { + if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); + if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })() diff --git a/src/babel/transformation/transformers/es6/block-scoping.js b/src/babel/transformation/transformers/es6/block-scoping.js index 7223dc5bd6..79ef58db37 100644 --- a/src/babel/transformation/transformers/es6/block-scoping.js +++ b/src/babel/transformation/transformers/es6/block-scoping.js @@ -375,7 +375,7 @@ class BlockScoping { var hasAsync = traverse.hasType(fn.body, this.scope, "AwaitExpression", t.FUNCTION_TYPES); if (hasAsync) { fn.async = true; - call = t.awaitExpression(call, true); + call = t.awaitExpression(call); } this.build(ret, call); diff --git a/src/babel/transformation/transformers/es6/classes.js b/src/babel/transformation/transformers/es6/classes.js index a123c57dd1..54e82e8f01 100644 --- a/src/babel/transformation/transformers/es6/classes.js +++ b/src/babel/transformation/transformers/es6/classes.js @@ -4,6 +4,8 @@ import * as defineMap from "../../helpers/define-map"; import * as messages from "../../../messages"; import * as util from "../../../util"; import traverse from "../../../traversal"; +import each from "lodash/collection/each"; +import has from "lodash/object/has"; import * as t from "../../../types"; export var check = t.isClass; @@ -35,6 +37,7 @@ var verifyConstructorVisitor = traverse.explode({ enter(node, parent, scope, state) { if (this.get("callee").isSuper()) { state.hasBareSuper = true; + state.bareSuper = this; if (!state.hasSuper) { throw this.errorWithNode("super call is only allowed in derived constructor"); @@ -65,14 +68,18 @@ class ClassTransformer { this.path = path; this.file = file; - this.hasInstanceMutators = false; - this.hasStaticMutators = false; - this.hasDecorators = false; + this.hasInstanceDescriptors = false; + this.hasStaticDescriptors = false; this.instanceMutatorMap = {}; this.staticMutatorMap = {}; + this.instancePropBody = []; + this.staticPropBody = []; + this.body = []; + this.hasConstructor = false; + this.hasDecorators = false; this.className = this.node.id; this.classRef = this.node.id || this.scope.generateUidIdentifier("class"); @@ -97,17 +104,11 @@ class ClassTransformer { // - var body = this.body = []; + var body = this.body; // - var constructorBody = t.blockStatement([ - t.expressionStatement(t.callExpression(file.addHelper("class-call-check"), [ - t.thisExpression(), - classRef - ])) - ]); - + var constructorBody = this.constructorBody = t.blockStatement([]); var constructor; if (this.className) { @@ -140,6 +141,13 @@ class ClassTransformer { this.buildBody(); + constructorBody.body.unshift(t.expressionStatement(t.callExpression(file.addHelper("class-call-check"), [ + t.thisExpression(), + classRef + ]))); + + // + var decorators = this.node.decorators; if (decorators) { for (var i = 0; i < decorators.length; i++) { @@ -165,6 +173,8 @@ class ClassTransformer { t.inheritsComments(body[0], this.node); } + body = body.concat(this.staticPropBody); + // body.push(t.returnStatement(classRef)); @@ -175,16 +185,69 @@ class ClassTransformer { ); } + /** + * Description + */ + + pushToMap(node, enumerable, kind = "value") { + var mutatorMap; + if (node.static) { + this.hasStaticDescriptors = true; + mutatorMap = this.staticMutatorMap; + } else { + this.hasInstanceDescriptors = true; + mutatorMap = this.instanceMutatorMap; + } + + var alias = t.toKeyAlias(node); + + // + + var map = {}; + if (has(mutatorMap, alias)) map = mutatorMap[alias]; + mutatorMap[alias] = map; + + // + + map._inherits ||= []; + map._inherits.push(node); + + map._key = node.key; + + if (enumerable) { + map.enumerable = t.literal(true) + } + + if (node.computed) { + map._computed = true; + } + + if (node.decorators) { + this.hasDecorators = true; + var decorators = map.decorators ||= t.arrayExpression([]); + decorators.elements = decorators.elements.concat(node.decorators.map(dec => dec.expression)); + } + + if (map.value || map.initializer) { + throw this.file.errorWithNode(node, "Key conflict with sibling node"); + } + + if (node.kind === "get") kind = "get"; + if (node.kind === "set") kind = "set"; + map[kind] = node.value; + } + /** * Description */ buildBody() { - var constructor = this.constructor; - var className = this.className; - var superName = this.superName; - var classBody = this.node.body.body; - var body = this.body; + var constructorBody = this.constructorBody; + var constructor = this.constructor; + var className = this.className; + var superName = this.superName; + var classBody = this.node.body.body; + var body = this.body; var classBodyPaths = this.path.get("body").get("body"); @@ -220,22 +283,30 @@ class ClassTransformer { if (!this.hasConstructor && this.hasSuper) { var helperName = "class-super-constructor-call"; if (this.isLoose) helperName += "-loose"; - constructor.body.body.push(util.template(helperName, { + constructorBody.body.push(util.template(helperName, { CLASS_NAME: className, SUPER_NAME: this.superName }, true)); } + // + this.placePropertyInitializers(); + + // + if (this.userConstructor) { + constructorBody.body = constructorBody.body.concat(this.userConstructor.body.body); + } + var instanceProps; var staticProps; var classHelper = "create-class"; if (this.hasDecorators) classHelper = "create-decorated-class"; - if (this.hasInstanceMutators) { + if (this.hasInstanceDescriptors) { instanceProps = defineMap.toClassObject(this.instanceMutatorMap); } - if (this.hasStaticMutators) { + if (this.hasStaticDescriptors) { staticProps = defineMap.toClassObject(this.staticMutatorMap); } @@ -243,10 +314,30 @@ class ClassTransformer { if (instanceProps) instanceProps = defineMap.toComputedObjectFromClass(instanceProps); if (staticProps) staticProps = defineMap.toComputedObjectFromClass(staticProps); - instanceProps ||= t.literal(null); + var nullNode = t.literal(null); + + // (Constructor, instanceDescriptors, staticDescriptors, instanceInitializers, staticInitializers) + var args = [this.classRef, nullNode, nullNode, nullNode, nullNode]; + + if (instanceProps) args[1] = instanceProps; + if (staticProps) args[2] = staticProps; + + if (this.instanceInitializersId) { + args[3] = this.instanceInitializersId; + body.unshift(this.buildObjectAssignment(this.instanceInitializersId)); + } + + if (this.staticInitializersId) { + args[4] = this.staticInitializersId; + body.unshift(this.buildObjectAssignment(this.staticInitializersId)); + } + + var lastNonNullIndex = 0; + for (var i = 0; i < args.length; i++) { + if (args[i] !== nullNode) lastNonNullIndex = i; + } + args = args.slice(0, lastNonNullIndex + 1); - var args = [this.classRef, instanceProps]; - if (staticProps) args.push(staticProps); body.push(t.expressionStatement( t.callExpression(this.file.addHelper(classHelper), args) @@ -254,6 +345,32 @@ class ClassTransformer { } } + buildObjectAssignment(id) { + return t.variableDeclaration("var", [ + t.variableDeclarator(id, t.objectExpression([])) + ]); + } + + /** + * Description + */ + + placePropertyInitializers() { + var body = this.instancePropBody; + if (body.length) { + // todo: check for scope conflicts and shift into a method + if (this.hasSuper) { + if (this.bareSuper) { + this.bareSuper.insertAfter(body); + } else { + this.constructorBody.body = this.constructorBody.body.concat(body); + } + } else { + this.constructorBody.body = body.concat(this.constructorBody.body); + } + } + } + /** * Description */ @@ -261,12 +378,15 @@ class ClassTransformer { verifyConstructor(path: TraversalPath) { var state = { hasBareSuper: false, + bareSuper: null, hasSuper: this.hasSuper, file: this.file }; path.traverse(verifyConstructorVisitor, state); + this.bareSuper = state.bareSuper; + if (!state.hasBareSuper && this.hasSuper) { throw path.errorWithNode("Derived constructor must call super()"); } @@ -277,11 +397,7 @@ class ClassTransformer { */ pushMethod(node: { type: "MethodDefinition" }) { - var methodName = node.key; - - var kind = node.kind; - - if (kind === "method") { + if (node.kind === "method") { nameMethod.property(node, this.file, this.scope); if (this.isLoose) { @@ -289,56 +405,56 @@ class ClassTransformer { var classRef = this.classRef; if (!node.static) classRef = t.memberExpression(classRef, t.identifier("prototype")); - methodName = t.memberExpression(classRef, methodName, node.computed); + var methodName = t.memberExpression(classRef, node.key, node.computed); var expr = t.expressionStatement(t.assignmentExpression("=", methodName, node.value)); t.inheritsComments(expr, node); this.body.push(expr); return; } - - kind = "value"; } - var mutatorMap = this.instanceMutatorMap; - if (node.static) { - this.hasStaticMutators = true; - mutatorMap = this.staticMutatorMap; - } else { - this.hasInstanceMutators = true; - } - - defineMap.push(mutatorMap, methodName, kind, node.computed, node); - - var decorators = node.decorators; - if (decorators && decorators.length) { - for (var i = 0; i < decorators.length; i++) { - decorators[i] = decorators[i].expression; - } - defineMap.push(mutatorMap, methodName, "decorators", node.computed, t.arrayExpression(decorators)); - this.hasDecorators = true; - } + this.pushToMap(node); } /** * Description */ - pushProperty(node: Object) { - if (!node.value) return; + pushProperty(node: { type: "ClassProperty" }) { + if (!node.value && !node.decorators) return; var key; - if (node.static) { - key = t.memberExpression(this.classRef, node.key); - this.body.push( - t.expressionStatement(t.assignmentExpression("=", key, node.value)) - ); + if (node.decorators) { + var body = []; + if (node.value) body.push(t.returnStatement(node.value)); + node.value = t.functionExpression(null, [], t.blockStatement(body)); + this.pushToMap(node, true, "initializer"); + + if (node.static) { + this.staticPropBody.push(util.template("call-static-decorator", { + INITIALIZERS: this.staticInitializersId ||= this.scope.generateUidIdentifier("staticInitializers"), + CONSTRUCTOR: this.classRef, + KEY: node.key, + }, true)); + } else { + this.instancePropBody.push(util.template("call-instance-decorator", { + INITIALIZERS: this.instanceInitializersId ||= this.scope.generateUidIdentifier("instanceInitializers"), + KEY: node.key + }, true)); + } } else { - key = t.memberExpression(t.thisExpression(), node.key); - this.constructor.body.body.unshift( - t.expressionStatement(t.assignmentExpression("=", key, node.value)) - ); + if (node.static) { + // can just be added to the static map + this.pushToMap(node, true); + } else { + // add this to the instancePropBody which will be added after the super call in a derived constructor + // or at the start of a constructor for a non-derived constructor + this.instancePropBody.push(t.expressionStatement( + t.assignmentExpression("=", t.memberExpression(t.thisExpression(), node.key), node.value) + )); + } } } @@ -350,7 +466,8 @@ class ClassTransformer { var construct = this.constructor; var fn = method.value; - this.hasConstructor = true; + this.userConstructor = fn; + this.hasConstructor = true; t.inherits(construct, fn); t.inheritsComments(construct, method); @@ -359,6 +476,5 @@ class ClassTransformer { construct.params = fn.params; t.inherits(construct.body, fn.body); - construct.body.body = construct.body.body.concat(fn.body.body); } } diff --git a/src/babel/transformation/transformers/es7/class-properties.js b/src/babel/transformation/transformers/es7/class-properties.js new file mode 100644 index 0000000000..6ef6177fb6 --- /dev/null +++ b/src/babel/transformation/transformers/es7/class-properties.js @@ -0,0 +1,8 @@ +export var metadata = { + experimental: true, + optional: true +}; + +export function check() { + return false; +} diff --git a/src/babel/transformation/transformers/es7/do-expressions.js b/src/babel/transformation/transformers/es7/do-expressions.js index 3ebb275c68..f413402446 100644 --- a/src/babel/transformation/transformers/es7/do-expressions.js +++ b/src/babel/transformation/transformers/es7/do-expressions.js @@ -5,6 +5,8 @@ export var metadata = { optional: true }; +export var check = t.isDoExpression; + export function DoExpression(node) { var body = node.body.body; if (body.length) { diff --git a/src/babel/transformation/transformers/index.js b/src/babel/transformation/transformers/index.js index cad654c1e9..fe996f682f 100644 --- a/src/babel/transformation/transformers/index.js +++ b/src/babel/transformation/transformers/index.js @@ -1,4 +1,5 @@ export default { + "es7.classProperties": require("./es7/class-properties"), "es7.asyncFunctions": require("./es7/async-functions"), "es7.decorators": require("./es7/decorators"), @@ -89,7 +90,7 @@ export default { // needs to be before `_shadowFunctions` "es6.arrowFunctions": require("./es6/arrow-functions"), - _shadowFunctions: require("./internal/alias-functions"), + _shadowFunctions: require("./internal/shadow-functions"), "es7.doExpressions": require("./es7/do-expressions"), diff --git a/src/babel/transformation/transformers/internal/alias-functions.js b/src/babel/transformation/transformers/internal/shadow-functions.js similarity index 97% rename from src/babel/transformation/transformers/internal/alias-functions.js rename to src/babel/transformation/transformers/internal/shadow-functions.js index cf5f4bf59f..bf06ab43d0 100644 --- a/src/babel/transformation/transformers/internal/alias-functions.js +++ b/src/babel/transformation/transformers/internal/shadow-functions.js @@ -2,6 +2,10 @@ import * as t from "../../../types"; var functionChildrenVisitor = { enter(node, parent, scope, state) { + if (this.isClass(node)) { + return this.skip(); + } + if (this.isFunction() && !node.shadow) { return this.skip(); } diff --git a/src/babel/transformation/transformers/other/strict.js b/src/babel/transformation/transformers/other/strict.js index 1ebde501e6..1c8acc2651 100644 --- a/src/babel/transformation/transformers/other/strict.js +++ b/src/babel/transformation/transformers/other/strict.js @@ -13,6 +13,7 @@ export function FunctionExpression() { } export { FunctionExpression as FunctionDeclaration }; +export { FunctionExpression as Class }; export function ThisExpression() { return t.identifier("undefined"); diff --git a/src/babel/traversal/context.js b/src/babel/traversal/context.js index f1031718c8..ba910babff 100644 --- a/src/babel/traversal/context.js +++ b/src/babel/traversal/context.js @@ -34,6 +34,7 @@ export default class TraversalContext { return; } + // todo: handle nodes popping in and out of existence for (var i = 0; i < nodes.length; i++) { if (nodes[i] && this.visitNode(node, nodes, i)) { return true; diff --git a/src/babel/traversal/path/index.js b/src/babel/traversal/path/index.js index fb75e6c12a..542541df8b 100644 --- a/src/babel/traversal/path/index.js +++ b/src/babel/traversal/path/index.js @@ -78,12 +78,41 @@ export default class TraversalPath { return ourScope; } - insertBefore(node) { - + getParentArrayPath() { + var path = this; + while (!Array.isArray(path.container)) { + path = path.parentPath; + } + return path; } - insertAfter(node) { + insertBefore(nodes) { + this.getParentArrayPath()._insertBefore(nodes); + } + insertAfter(nodes) { + this.getParentArrayPath()._insertAfter(nodes); + } + + _insertBefore(nodes) { + throw new Error("to be implemented"); + } + + _insertAfter(nodes) { + // todo: in a statement context ie. BlockStatement or Program create individual statements instead + // of trying to join them + + var key = this.key + 1; + this.container.splice(key, 0, null); + + var paths = this.container._paths; + for (var i = 0; i > paths.length; i++) { + let path = paths[path]; + if (path.key >= key) path.key++; + } + + var path = TraversalPath.get(this.parentPath, null, this.parent, this.container, key, this.file); + path.setStatementsToExpression(nodes); } setData(key, val) { @@ -172,7 +201,7 @@ export default class TraversalPath { // inherit comments from original node to the first replacement node var inheritTo = replacements[0]; - if (inheritTo) t.inheritsComments(inheritTo, oldNode); + if (inheritTo && oldNode) t.inheritsComments(inheritTo, oldNode); // if (t.isStatement(replacements[0]) && t.isType(this.type, "Expression")) { @@ -314,7 +343,12 @@ export default class TraversalPath { } else { // "foo" var path = this; for (var i = 0; i > parts.length; i++) { - path = path.get(parts[i]); + var part = parts[i]; + if (part === ".") { + path = path.parentPath; + } else { + path = path.get(parts[i]); + } } return path; } diff --git a/src/babel/types/builder-keys.json b/src/babel/types/builder-keys.json index 65fee94c49..3bca84fa55 100644 --- a/src/babel/types/builder-keys.json +++ b/src/babel/types/builder-keys.json @@ -49,7 +49,8 @@ "id": null, "params": null, "body": null, - "generator": false + "generator": false, + "async": false }, "FunctionDeclaration": { diff --git a/src/babel/types/retrievers.js b/src/babel/types/retrievers.js index 384e705f86..b88a82bfe3 100644 --- a/src/babel/types/retrievers.js +++ b/src/babel/types/retrievers.js @@ -72,8 +72,11 @@ export function getLastStatements(node: Object): Array { if (t.isIfStatement(node)) { add(node.consequent); add(node.alternate); + } else if (t.isFor(node) || t.isWhile(node)) { + add(node.body); } else if (t.isProgram(node) || t.isBlockStatement(node)) { add(node.body[node.body.length - 1]); + } else if (t.isLoop()) { } else if (node) { nodes.push(node); } diff --git a/src/babel/types/visitor-keys.json b/src/babel/types/visitor-keys.json index 86c6f73ff0..f67fb969c0 100644 --- a/src/babel/types/visitor-keys.json +++ b/src/babel/types/visitor-keys.json @@ -1,7 +1,7 @@ { "ArrayExpression": ["elements"], "ArrayPattern": ["elements", "typeAnnotation"], - "ArrowFunctionExpression": ["params", "defaults", "rest", "body", "returnType"], + "ArrowFunctionExpression": ["params", "body", "returnType"], "AssignmentExpression": ["left", "right"], "AssignmentPattern": ["left", "right"], "AwaitExpression": ["argument"], @@ -31,8 +31,8 @@ "ForInStatement": ["left", "right", "body"], "ForOfStatement": ["left", "right", "body"], "ForStatement": ["init", "test", "update", "body"], - "FunctionDeclaration": ["id", "params", "defaults", "rest", "body", "returnType", "typeParameters"], - "FunctionExpression": ["id", "params", "defaults", "rest", "body", "returnType", "typeParameters"], + "FunctionDeclaration": ["id", "params", "body", "returnType", "typeParameters"], + "FunctionExpression": ["id", "params", "body", "returnType", "typeParameters"], "Identifier": ["typeAnnotation"], "IfStatement": ["test", "consequent", "alternate"], "ImportDefaultSpecifier": ["local"], diff --git a/test/fixtures/transformation/es7-decorators/class/actual.js b/test/fixtures/transformation/es7-decorators/class/actual.js new file mode 100644 index 0000000000..f8856dc3dc --- /dev/null +++ b/test/fixtures/transformation/es7-decorators/class/actual.js @@ -0,0 +1,6 @@ +@foo +class Foo {} + +@foo +@bar +class Bar {} diff --git a/test/fixtures/transformation/es7-decorators/class/expected.js b/test/fixtures/transformation/es7-decorators/class/expected.js new file mode 100644 index 0000000000..08fe7b6438 --- /dev/null +++ b/test/fixtures/transformation/es7-decorators/class/expected.js @@ -0,0 +1,20 @@ +"use strict"; + +var Foo = (function () { + function Foo() { + babelHelpers.classCallCheck(this, Foo); + } + + Foo = foo(Foo) || Foo; + return Foo; +})(); + +var Bar = (function () { + function Bar() { + babelHelpers.classCallCheck(this, Bar); + } + + Bar = foo(Bar) || Bar; + Bar = bar(Bar) || Bar; + return Bar; +})(); diff --git a/test/fixtures/transformation/es7-decorators/exec-class-method-autobind/exec.js b/test/fixtures/transformation/es7-decorators/exec-class-method-autobind/exec.js new file mode 100644 index 0000000000..64b7e2df45 --- /dev/null +++ b/test/fixtures/transformation/es7-decorators/exec-class-method-autobind/exec.js @@ -0,0 +1,22 @@ +var autobind = function (target, name, descriptor) { + var fn = descriptor.value; + delete descriptor.value; + delete descriptor.writable; + descriptor.get = function () { + return fn.bind(this); + }; +}; + +class Person { + constructor() { + this.first = "Sebastian"; + this.last = "McKenzie"; + } + + @autobind + getName() { + return `${this.first} ${this.last}`; + } +} + +assert.equal(new Person().getName.call(null), "Sebastian McKenzie") diff --git a/test/fixtures/transformation/es7-decorators/options.json b/test/fixtures/transformation/es7-decorators/options.json new file mode 100644 index 0000000000..1e9fd6e0c2 --- /dev/null +++ b/test/fixtures/transformation/es7-decorators/options.json @@ -0,0 +1,4 @@ +{ + "externalHelpers": true, + "optional": ["es7.decorators"] +} diff --git a/test/fixtures/transformation/es7-do-expressions/do-while.js b/test/fixtures/transformation/es7-do-expressions/do-while.js new file mode 100644 index 0000000000..6416b6dcfb --- /dev/null +++ b/test/fixtures/transformation/es7-do-expressions/do-while.js @@ -0,0 +1 @@ +assert.ok(false); diff --git a/test/fixtures/transformation/es7-do-expressions/empty.js b/test/fixtures/transformation/es7-do-expressions/empty.js new file mode 100644 index 0000000000..906776c43b --- /dev/null +++ b/test/fixtures/transformation/es7-do-expressions/empty.js @@ -0,0 +1 @@ +assert.equal(do {}, undefined); diff --git a/test/fixtures/transformation/es7-do-expressions/for-in.js b/test/fixtures/transformation/es7-do-expressions/for-in.js new file mode 100644 index 0000000000..9945600d55 --- /dev/null +++ b/test/fixtures/transformation/es7-do-expressions/for-in.js @@ -0,0 +1,6 @@ +assert.equal(do { + var obj = { foo: "bar", bar: "foo" }; + for (var key in obj) { + obj[key]; + } +}, "foo"); diff --git a/test/fixtures/transformation/es7-do-expressions/for.js b/test/fixtures/transformation/es7-do-expressions/for.js new file mode 100644 index 0000000000..aa7fcf2c17 --- /dev/null +++ b/test/fixtures/transformation/es7-do-expressions/for.js @@ -0,0 +1,5 @@ +assert.equal(do { + for (var i = 0; i < 5; i++) { + i; + } +}, 5); diff --git a/test/fixtures/transformation/es7-do-expressions/if-else-if.js b/test/fixtures/transformation/es7-do-expressions/if-else-if.js new file mode 100644 index 0000000000..ce324555ac --- /dev/null +++ b/test/fixtures/transformation/es7-do-expressions/if-else-if.js @@ -0,0 +1,7 @@ +assert.equal(do { + if (false) { + "foo"; + } else if (true) { + "bar"; + } +}, "bar"); diff --git a/test/fixtures/transformation/es7-do-expressions/if-else.js b/test/fixtures/transformation/es7-do-expressions/if-else.js new file mode 100644 index 0000000000..7f29ff878a --- /dev/null +++ b/test/fixtures/transformation/es7-do-expressions/if-else.js @@ -0,0 +1,7 @@ +assert.equal(do { + if (false) { + "foo"; + } else { + "bar"; + } +}, "bar"); diff --git a/test/fixtures/transformation/es7-do-expressions/if.js b/test/fixtures/transformation/es7-do-expressions/if.js new file mode 100644 index 0000000000..1db4e45884 --- /dev/null +++ b/test/fixtures/transformation/es7-do-expressions/if.js @@ -0,0 +1,5 @@ +assert.equal(do { + if (true) { + "bar"; + } +}, "bar"); diff --git a/test/fixtures/transformation/es7-do-expressions/nested-dos.js b/test/fixtures/transformation/es7-do-expressions/nested-dos.js new file mode 100644 index 0000000000..6e465e2100 --- /dev/null +++ b/test/fixtures/transformation/es7-do-expressions/nested-dos.js @@ -0,0 +1,3 @@ +assert.equal(do { + do { "foo" }; +}, "foo"); diff --git a/test/fixtures/transformation/es7-do-expressions/options.json b/test/fixtures/transformation/es7-do-expressions/options.json new file mode 100644 index 0000000000..560a4cc08d --- /dev/null +++ b/test/fixtures/transformation/es7-do-expressions/options.json @@ -0,0 +1,3 @@ +{ + "optional": ["es7.doExpressions"] +} diff --git a/test/fixtures/transformation/es7-do-expressions/single-expression.js b/test/fixtures/transformation/es7-do-expressions/single-expression.js new file mode 100644 index 0000000000..c528da6f0f --- /dev/null +++ b/test/fixtures/transformation/es7-do-expressions/single-expression.js @@ -0,0 +1,3 @@ +assert.equal(do { + "foo"; +}, "foo"); diff --git a/test/fixtures/transformation/es7-do-expressions/variable-declaration-end.js b/test/fixtures/transformation/es7-do-expressions/variable-declaration-end.js new file mode 100644 index 0000000000..8001c7af88 --- /dev/null +++ b/test/fixtures/transformation/es7-do-expressions/variable-declaration-end.js @@ -0,0 +1,3 @@ +assert.equal(do { + var bar = "foo"; +}, undefined); diff --git a/test/fixtures/transformation/es7-do-expressions/variable-declaration-start.js b/test/fixtures/transformation/es7-do-expressions/variable-declaration-start.js new file mode 100644 index 0000000000..2dad90a410 --- /dev/null +++ b/test/fixtures/transformation/es7-do-expressions/variable-declaration-start.js @@ -0,0 +1,4 @@ +assert.equal(do { + var bar = "foo"; + bar; +}, "foo"); diff --git a/test/fixtures/transformation/es7-do-expressions/while.js b/test/fixtures/transformation/es7-do-expressions/while.js new file mode 100644 index 0000000000..6416b6dcfb --- /dev/null +++ b/test/fixtures/transformation/es7-do-expressions/while.js @@ -0,0 +1 @@ +assert.ok(false);