diff --git a/lib/coffeescript/nodes.js b/lib/coffeescript/nodes.js index 5e2edd69..9f453d74 100644 --- a/lib/coffeescript/nodes.js +++ b/lib/coffeescript/nodes.js @@ -2613,10 +2613,9 @@ // An object literal, nothing fancy. exports.Obj = Obj = (function() { class Obj extends Base { - constructor(props, generated = false, lhs1 = false) { + constructor(props, generated = false) { super(); this.generated = generated; - this.lhs = lhs1; this.objects = this.properties = props || []; } @@ -2682,7 +2681,7 @@ } compileNode(o) { - var answer, i, idt, indent, isCompact, j, join, k, key, l, lastNode, len1, len2, len3, len4, node, p, prop, props, ref1, unwrappedVal, value; + var answer, i, idt, indent, isCompact, j, join, k, key, l, lastNode, len1, len2, len3, node, prop, props, ref1, value; if (this.hasSplat() && this.lhs) { this.reorderProperties(); } @@ -2703,32 +2702,18 @@ } // If this object is the left-hand side of an assignment, all its children // are too. - if (this.lhs) { - for (k = 0, len2 = props.length; k < len2; k++) { - prop = props[k]; - if (!(prop instanceof Assign)) { - continue; - } - ({value} = prop); - unwrappedVal = value.unwrapAll(); - if (unwrappedVal instanceof Arr || unwrappedVal instanceof Obj) { - unwrappedVal.lhs = true; - } else if (unwrappedVal instanceof Assign) { - unwrappedVal.nestedLhs = true; - } - } - } + this.propagateLhs(); isCompact = true; ref1 = this.properties; - for (l = 0, len3 = ref1.length; l < len3; l++) { - prop = ref1[l]; + for (k = 0, len2 = ref1.length; k < len2; k++) { + prop = ref1[k]; if (prop instanceof Assign && prop.context === 'object') { isCompact = false; } } answer = []; answer.push(this.makeCode(isCompact ? '' : '\n')); - for (i = p = 0, len4 = props.length; p < len4; i = ++p) { + for (i = l = 0, len3 = props.length; l < len3; i = ++l) { prop = props[i]; join = i === props.length - 1 ? '' : isCompact ? ', ' : prop === lastNode ? '\n' : ',\n'; indent = isCompact ? '' : idt; @@ -2875,8 +2860,46 @@ return results; } + propagateLhs(setLhs) { + var j, len1, property, ref1, results, unwrappedValue, value; + if (setLhs) { + this.lhs = true; + } + if (!this.lhs) { + return; + } + ref1 = this.properties; + results = []; + for (j = 0, len1 = ref1.length; j < len1; j++) { + property = ref1[j]; + if (property instanceof Assign && property.context === 'object') { + ({value} = property); + unwrappedValue = value.unwrapAll(); + if (unwrappedValue instanceof Arr || unwrappedValue instanceof Obj) { + results.push(unwrappedValue.propagateLhs(true)); + } else if (unwrappedValue instanceof Assign) { + results.push(unwrappedValue.nestedLhs = true); + } else { + results.push(void 0); + } + } else if (property instanceof Assign) { + // Shorthand property with default, e.g. `{a = 1} = b`. + results.push(property.nestedLhs = true); + } else if (property instanceof Splat) { + results.push(property.lhs = true); + } else { + results.push(void 0); + } + } + return results; + } + astType() { - return 'ObjectExpression'; + if (this.lhs) { + return 'ObjectPattern'; + } else { + return 'ObjectExpression'; + } } astProperties() { @@ -2955,6 +2978,7 @@ super(); this.lhs = lhs1; this.objects = objs || []; + this.propagateLhs(); } hasElision() { @@ -3014,13 +3038,6 @@ }).length === 0) { unwrappedObj.includeCommentFragments = YES; } - // If this array is the left-hand side of an assignment, all its children - // are too. - if (this.lhs) { - if (unwrappedObj instanceof Arr || unwrappedObj instanceof Obj) { - unwrappedObj.lhs = true; - } - } } compiledObjs = (function() { var k, len2, ref2, results; @@ -3107,8 +3124,41 @@ return results; } + // If this array is the left-hand side of an assignment, all its children + // are too. + propagateLhs(setLhs) { + var j, len1, object, ref1, results, unwrappedObject; + if (setLhs) { + this.lhs = true; + } + if (!this.lhs) { + return; + } + ref1 = this.objects; + results = []; + for (j = 0, len1 = ref1.length; j < len1; j++) { + object = ref1[j]; + if (object instanceof Splat) { + object.lhs = true; + } + unwrappedObject = object.unwrapAll(); + if (unwrappedObject instanceof Arr || unwrappedObject instanceof Obj) { + results.push(unwrappedObject.propagateLhs(true)); + } else if (unwrappedObject instanceof Assign) { + results.push(unwrappedObject.nestedLhs = true); + } else { + results.push(void 0); + } + } + return results; + } + astType() { - return 'ArrayExpression'; + if (this.lhs) { + return 'ArrayPattern'; + } else { + return 'ArrayExpression'; + } } astProperties() { @@ -3918,6 +3968,7 @@ this.value = value1; this.context = context1; ({param: this.param, subpattern: this.subpattern, operatorToken: this.operatorToken, moduleDeclaration: this.moduleDeclaration, originalContext: this.originalContext = this.context} = options); + this.propagateLhs(); } isStatement(o) { @@ -3946,17 +3997,11 @@ var answer, compiledName, isValue, name, properties, prototype, ref1, ref2, ref3, ref4, ref5, val, varBase; isValue = this.variable instanceof Value; if (isValue) { - // When compiling `@variable`, remember if it is part of a function parameter. - this.variable.param = this.param; // If `@variable` is an array or an object, we’re destructuring; // if it’s also `isAssignable()`, the destructuring syntax is supported // in ES and we can output it as is; otherwise we `@compileDestructuring` // and convert this ES-unsupported destructuring into acceptable output. if (this.variable.isArray() || this.variable.isObject()) { - // This is the left-hand side of an assignment; let `Arr` and `Obj` - // know that, so that those nodes know that they’re assignable as - // destructured variables. - this.variable.base.lhs = true; if (!this.variable.isAssignable()) { if (this.variable.isObject() && this.variable.base.hasSplat()) { return this.compileObjectDestruct(o); @@ -4397,6 +4442,41 @@ return this.variable.unwrapAll().eachName(iterator); } + isDefaultAssignment() { + return this.param || this.nestedLhs; + } + + propagateLhs() { + var ref1, ref2; + if (!(((ref1 = this.variable) != null ? typeof ref1.isArray === "function" ? ref1.isArray() : void 0 : void 0) || ((ref2 = this.variable) != null ? typeof ref2.isObject === "function" ? ref2.isObject() : void 0 : void 0))) { + return; + } + // This is the left-hand side of an assignment; let `Arr` and `Obj` + // know that, so that those nodes know that they’re assignable as + // destructured variables. + return this.variable.base.propagateLhs(true); + } + + astType() { + if (this.isDefaultAssignment()) { + return 'AssignmentPattern'; + } else { + return 'AssignmentExpression'; + } + } + + astProperties() { + var ref1, ret; + ret = { + right: this.value.ast(), + left: this.variable.ast() + }; + if (!this.isDefaultAssignment()) { + ret.operator = (ref1 = this.originalContext) != null ? ref1 : '='; + } + return ret; + } + }; Assign.prototype.children = ['variable', 'value']; @@ -5047,7 +5127,11 @@ } astType() { - return 'SpreadElement'; + if (this.lhs) { + return 'RestElement'; + } else { + return 'SpreadElement'; + } } astProperties() { diff --git a/src/nodes.coffee b/src/nodes.coffee index f8d7360f..85ecf9be 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1763,7 +1763,7 @@ exports.Slice = class Slice extends Base # An object literal, nothing fancy. exports.Obj = class Obj extends Base - constructor: (props, @generated = no, @lhs = no) -> + constructor: (props, @generated = no) -> super() @objects = @properties = props or [] @@ -1815,14 +1815,7 @@ exports.Obj = class Obj extends Base # If this object is the left-hand side of an assignment, all its children # are too. - if @lhs - for prop in props when prop instanceof Assign - {value} = prop - unwrappedVal = value.unwrapAll() - if unwrappedVal instanceof Arr or unwrappedVal instanceof Obj - unwrappedVal.lhs = yes - else if unwrappedVal instanceof Assign - unwrappedVal.nestedLhs = yes + @propagateLhs() isCompact = yes for prop in @properties @@ -1921,7 +1914,29 @@ exports.Obj = class Obj extends Base expandProperties: -> @expandProperty(property) for property in @properties - astType: -> 'ObjectExpression' + propagateLhs: (setLhs) -> + @lhs = yes if setLhs + return unless @lhs + + for property in @properties + if property instanceof Assign and property.context is 'object' + {value} = property + unwrappedValue = value.unwrapAll() + if unwrappedValue instanceof Arr or unwrappedValue instanceof Obj + unwrappedValue.propagateLhs yes + else if unwrappedValue instanceof Assign + unwrappedValue.nestedLhs = yes + else if property instanceof Assign + # Shorthand property with default, e.g. `{a = 1} = b`. + property.nestedLhs = yes + else if property instanceof Splat + property.lhs = yes + + astType: -> + if @lhs + 'ObjectPattern' + else + 'ObjectExpression' astProperties: -> return @@ -1966,6 +1981,7 @@ exports.Arr = class Arr extends Base constructor: (objs, @lhs = no) -> super() @objects = objs or [] + @propagateLhs() children: ['objects'] @@ -1999,10 +2015,6 @@ exports.Arr = class Arr extends Base if unwrappedObj.comments and unwrappedObj.comments.filter((comment) -> not comment.here).length is 0 unwrappedObj.includeCommentFragments = YES - # If this array is the left-hand side of an assignment, all its children - # are too. - if @lhs - unwrappedObj.lhs = yes if unwrappedObj instanceof Arr or unwrappedObj instanceof Obj compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in @objects) olen = compiledObjs.length @@ -2050,7 +2062,24 @@ exports.Arr = class Arr extends Base obj = obj.unwrapAll() obj.eachName iterator - astType: -> 'ArrayExpression' + # If this array is the left-hand side of an assignment, all its children + # are too. + propagateLhs: (setLhs) -> + @lhs = yes if setLhs + return unless @lhs + for object in @objects + object.lhs = yes if object instanceof Splat + unwrappedObject = object.unwrapAll() + if unwrappedObject instanceof Arr or unwrappedObject instanceof Obj + unwrappedObject.propagateLhs yes + else if unwrappedObject instanceof Assign + unwrappedObject.nestedLhs = yes + + astType: -> + if @lhs + 'ArrayPattern' + else + 'ArrayExpression' astProperties: -> return @@ -2585,6 +2614,7 @@ exports.Assign = class Assign extends Base constructor: (@variable, @value, @context, options = {}) -> super() {@param, @subpattern, @operatorToken, @moduleDeclaration, @originalContext = @context} = options + @propagateLhs() children: ['variable', 'value'] @@ -2611,18 +2641,11 @@ exports.Assign = class Assign extends Base compileNode: (o) -> isValue = @variable instanceof Value if isValue - # When compiling `@variable`, remember if it is part of a function parameter. - @variable.param = @param - # If `@variable` is an array or an object, we’re destructuring; # if it’s also `isAssignable()`, the destructuring syntax is supported # in ES and we can output it as is; otherwise we `@compileDestructuring` # and convert this ES-unsupported destructuring into acceptable output. if @variable.isArray() or @variable.isObject() - # This is the left-hand side of an assignment; let `Arr` and `Obj` - # know that, so that those nodes know that they’re assignable as - # destructured variables. - @variable.base.lhs = yes unless @variable.isAssignable() if @variable.isObject() and @variable.base.hasSplat() return @compileObjectDestruct o @@ -2920,6 +2943,31 @@ exports.Assign = class Assign extends Base eachName: (iterator) -> @variable.unwrapAll().eachName iterator + isDefaultAssignment: -> @param or @nestedLhs + + propagateLhs: -> + return unless @variable?.isArray?() or @variable?.isObject?() + # This is the left-hand side of an assignment; let `Arr` and `Obj` + # know that, so that those nodes know that they’re assignable as + # destructured variables. + @variable.base.propagateLhs yes + + astType: -> + if @isDefaultAssignment() + 'AssignmentPattern' + else + 'AssignmentExpression' + + astProperties: -> + ret = + right: @value.ast() + left: @variable.ast() + + unless @isDefaultAssignment() + ret.operator = @originalContext ? '=' + + ret + #### FuncGlyph exports.FuncGlyph = class FuncGlyph extends Base @@ -3369,7 +3417,11 @@ exports.Splat = class Splat extends Base unwrap: -> @name - astType: -> 'SpreadElement' + astType: -> + if @lhs + 'RestElement' + else + 'SpreadElement' astProperties: -> { argument: @name.ast() diff --git a/test/abstract_syntax_tree.coffee b/test/abstract_syntax_tree.coffee index 27809a40..5cb5272a 100644 --- a/test/abstract_syntax_tree.coffee +++ b/test/abstract_syntax_tree.coffee @@ -1108,24 +1108,93 @@ test "AST as expected for ImportNamespaceSpecifier node", -> type: 'StringLiteral' value: 'react' -# test "AST as expected for Assign node", -> -# testExpression 'a = 1', -# type: 'Assign' -# variable: -# value: 'a' -# value: -# value: '1' +test "AST as expected for Assign node", -> + testExpression 'a = b', + type: 'AssignmentExpression' + left: + type: 'Identifier' + name: 'a' + right: + type: 'Identifier' + name: 'b' + operator: '=' -# testExpression 'a: 1', -# properties: [ -# type: 'Assign' -# context: 'object' -# originalContext: 'object' -# variable: -# value: 'a' -# value: -# value: '1' -# ] + testExpression 'a += b', + type: 'AssignmentExpression' + left: + type: 'Identifier' + name: 'a' + right: + type: 'Identifier' + name: 'b' + operator: '+=' + + testExpression '[@a = 2, {b: {c = 3} = {}, d...}, ...e] = f', + type: 'AssignmentExpression' + left: + type: 'ArrayPattern' + elements: [ + type: 'AssignmentPattern' + left: + type: 'MemberExpression' + object: + type: 'ThisExpression' + property: + name: 'a' + right: + type: 'NumericLiteral' + , + type: 'ObjectPattern' + properties: [ + type: 'ObjectProperty' + key: + name: 'b' + value: + type: 'AssignmentPattern' + left: + type: 'ObjectPattern' + properties: [ + type: 'ObjectProperty' + key: + name: 'c' + value: + type: 'AssignmentPattern' + left: + name: 'c' + right: + value: 3 + shorthand: yes + ] + right: + type: 'ObjectExpression' + properties: [] + , + type: 'RestElement' + postfix: yes + ] + , + type: 'RestElement' + postfix: no + ] + right: + name: 'f' + + testExpression '{a: [...b]} = c', + type: 'AssignmentExpression' + left: + type: 'ObjectPattern' + properties: [ + type: 'ObjectProperty' + key: + name: 'a' + value: + type: 'ArrayPattern' + elements: [ + type: 'RestElement' + ] + ] + right: + name: 'c' # # `FuncGlyph` node isn't exported. @@ -1218,31 +1287,24 @@ test "AST as expected for Elision node", -> name: 'b' ] - # testExpression '[,,,a,,,b] = "asdfqwer"', - # type: 'Assign' - # variable: - # type: 'Arr' - # lhs: no - # objects: [ - # {type: 'Elision'} - # {type: 'Elision'} - # {type: 'Elision'} - # { - # type: 'IdentifierLiteral' - # value: 'a' - # } - # {type: 'Elision'} - # {type: 'Elision'} - # { - # type: 'IdentifierLiteral' - # value: 'b' - # } - # ] - # value: - # type: 'StringLiteral' - # value: '"asdfqwer"' - # originalValue: 'asdfqwer' - # quote: '"' + testExpression '[,,,a,,,b] = "asdfqwer"', + type: 'AssignmentExpression' + left: + type: 'ArrayPattern' + elements: [ + null, null, null + , + type: 'Identifier' + name: 'a' + , + null, null + , + type: 'Identifier' + name: 'b' + ] + right: + type: 'StringLiteral' + value: 'asdfqwer' # test "AST as expected for While node", -> # testExpression 'loop 1', diff --git a/test/abstract_syntax_tree_location_data.coffee b/test/abstract_syntax_tree_location_data.coffee index 0958927d..5679e49f 100644 --- a/test/abstract_syntax_tree_location_data.coffee +++ b/test/abstract_syntax_tree_location_data.coffee @@ -1902,3 +1902,156 @@ test "AST location data as expected for Obj node", -> end: line: 1 column: 4 + +test "AST location data as expected for Assign node", -> + testAstLocationData 'a = b', + type: 'AssignmentExpression' + left: + start: 0 + end: 1 + range: [0, 1] + loc: + start: + line: 1 + column: 0 + end: + line: 1 + column: 1 + right: + start: 4 + end: 5 + range: [4, 5] + loc: + start: + line: 1 + column: 4 + end: + line: 1 + column: 5 + start: 0 + end: 5 + range: [0, 5] + loc: + start: + line: 1 + column: 0 + end: + line: 1 + column: 5 + + testAstLocationData 'a += b', + type: 'AssignmentExpression' + left: + start: 0 + end: 1 + range: [0, 1] + loc: + start: + line: 1 + column: 0 + end: + line: 1 + column: 1 + right: + start: 5 + end: 6 + range: [5, 6] + loc: + start: + line: 1 + column: 5 + end: + line: 1 + column: 6 + start: 0 + end: 6 + range: [0, 6] + loc: + start: + line: 1 + column: 0 + end: + line: 1 + column: 6 + + testAstLocationData '{a: [...b]} = c', + type: 'AssignmentExpression' + left: + properties: [ + type: 'ObjectProperty' + key: + start: 1 + end: 2 + range: [1, 2] + loc: + start: + line: 1 + column: 1 + end: + line: 1 + column: 2 + value: + elements: [ + start: 5 + end: 9 + range: [5, 9] + loc: + start: + line: 1 + column: 5 + end: + line: 1 + column: 9 + ] + start: 4 + end: 10 + range: [4, 10] + loc: + start: + line: 1 + column: 4 + end: + line: 1 + column: 10 + start: 1 + end: 10 + range: [1, 10] + loc: + start: + line: 1 + column: 1 + end: + line: 1 + column: 10 + ] + start: 0 + end: 11 + range: [0, 11] + loc: + start: + line: 1 + column: 0 + end: + line: 1 + column: 11 + right: + start: 14 + end: 15 + range: [14, 15] + loc: + start: + line: 1 + column: 14 + end: + line: 1 + column: 15 + start: 0 + end: 15 + range: [0, 15] + loc: + start: + line: 1 + column: 0 + end: + line: 1 + column: 15