Assign AST (#5126)

* updated grammar

* tests

* ObjectProperty

* LHS shorthand with default needs @value

* remove unused Assign @shorthand

* assign ast

* test operator

* Format comment
This commit is contained in:
Julian Rosse 2018-10-30 18:46:11 -04:00 committed by Geoffrey Booth
parent fb539579c3
commit 0e37130f2e
4 changed files with 453 additions and 102 deletions

View File

@ -2613,10 +2613,9 @@
// An object literal, nothing fancy. // An object literal, nothing fancy.
exports.Obj = Obj = (function() { exports.Obj = Obj = (function() {
class Obj extends Base { class Obj extends Base {
constructor(props, generated = false, lhs1 = false) { constructor(props, generated = false) {
super(); super();
this.generated = generated; this.generated = generated;
this.lhs = lhs1;
this.objects = this.properties = props || []; this.objects = this.properties = props || [];
} }
@ -2682,7 +2681,7 @@
} }
compileNode(o) { 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) { if (this.hasSplat() && this.lhs) {
this.reorderProperties(); this.reorderProperties();
} }
@ -2703,32 +2702,18 @@
} }
// If this object is the left-hand side of an assignment, all its children // If this object is the left-hand side of an assignment, all its children
// are too. // are too.
if (this.lhs) { this.propagateLhs();
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;
}
}
}
isCompact = true; isCompact = true;
ref1 = this.properties; ref1 = this.properties;
for (l = 0, len3 = ref1.length; l < len3; l++) { for (k = 0, len2 = ref1.length; k < len2; k++) {
prop = ref1[l]; prop = ref1[k];
if (prop instanceof Assign && prop.context === 'object') { if (prop instanceof Assign && prop.context === 'object') {
isCompact = false; isCompact = false;
} }
} }
answer = []; answer = [];
answer.push(this.makeCode(isCompact ? '' : '\n')); 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]; prop = props[i];
join = i === props.length - 1 ? '' : isCompact ? ', ' : prop === lastNode ? '\n' : ',\n'; join = i === props.length - 1 ? '' : isCompact ? ', ' : prop === lastNode ? '\n' : ',\n';
indent = isCompact ? '' : idt; indent = isCompact ? '' : idt;
@ -2875,8 +2860,46 @@
return results; 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() { astType() {
return 'ObjectExpression'; if (this.lhs) {
return 'ObjectPattern';
} else {
return 'ObjectExpression';
}
} }
astProperties() { astProperties() {
@ -2955,6 +2978,7 @@
super(); super();
this.lhs = lhs1; this.lhs = lhs1;
this.objects = objs || []; this.objects = objs || [];
this.propagateLhs();
} }
hasElision() { hasElision() {
@ -3014,13 +3038,6 @@
}).length === 0) { }).length === 0) {
unwrappedObj.includeCommentFragments = YES; 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() { compiledObjs = (function() {
var k, len2, ref2, results; var k, len2, ref2, results;
@ -3107,8 +3124,41 @@
return results; 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() { astType() {
return 'ArrayExpression'; if (this.lhs) {
return 'ArrayPattern';
} else {
return 'ArrayExpression';
}
} }
astProperties() { astProperties() {
@ -3918,6 +3968,7 @@
this.value = value1; this.value = value1;
this.context = context1; this.context = context1;
({param: this.param, subpattern: this.subpattern, operatorToken: this.operatorToken, moduleDeclaration: this.moduleDeclaration, originalContext: this.originalContext = this.context} = options); ({param: this.param, subpattern: this.subpattern, operatorToken: this.operatorToken, moduleDeclaration: this.moduleDeclaration, originalContext: this.originalContext = this.context} = options);
this.propagateLhs();
} }
isStatement(o) { isStatement(o) {
@ -3946,17 +3997,11 @@
var answer, compiledName, isValue, name, properties, prototype, ref1, ref2, ref3, ref4, ref5, val, varBase; var answer, compiledName, isValue, name, properties, prototype, ref1, ref2, ref3, ref4, ref5, val, varBase;
isValue = this.variable instanceof Value; isValue = this.variable instanceof Value;
if (isValue) { 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, were destructuring; // If `@variable` is an array or an object, were destructuring;
// if its also `isAssignable()`, the destructuring syntax is supported // if its also `isAssignable()`, the destructuring syntax is supported
// in ES and we can output it as is; otherwise we `@compileDestructuring` // in ES and we can output it as is; otherwise we `@compileDestructuring`
// and convert this ES-unsupported destructuring into acceptable output. // and convert this ES-unsupported destructuring into acceptable output.
if (this.variable.isArray() || this.variable.isObject()) { 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 theyre assignable as
// destructured variables.
this.variable.base.lhs = true;
if (!this.variable.isAssignable()) { if (!this.variable.isAssignable()) {
if (this.variable.isObject() && this.variable.base.hasSplat()) { if (this.variable.isObject() && this.variable.base.hasSplat()) {
return this.compileObjectDestruct(o); return this.compileObjectDestruct(o);
@ -4397,6 +4442,41 @@
return this.variable.unwrapAll().eachName(iterator); 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 theyre 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']; Assign.prototype.children = ['variable', 'value'];
@ -5047,7 +5127,11 @@
} }
astType() { astType() {
return 'SpreadElement'; if (this.lhs) {
return 'RestElement';
} else {
return 'SpreadElement';
}
} }
astProperties() { astProperties() {

View File

@ -1763,7 +1763,7 @@ exports.Slice = class Slice extends Base
# An object literal, nothing fancy. # An object literal, nothing fancy.
exports.Obj = class Obj extends Base exports.Obj = class Obj extends Base
constructor: (props, @generated = no, @lhs = no) -> constructor: (props, @generated = no) ->
super() super()
@objects = @properties = props or [] @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 # If this object is the left-hand side of an assignment, all its children
# are too. # are too.
if @lhs @propagateLhs()
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
isCompact = yes isCompact = yes
for prop in @properties for prop in @properties
@ -1921,7 +1914,29 @@ exports.Obj = class Obj extends Base
expandProperties: -> expandProperties: ->
@expandProperty(property) for property in @properties @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: -> astProperties: ->
return return
@ -1966,6 +1981,7 @@ exports.Arr = class Arr extends Base
constructor: (objs, @lhs = no) -> constructor: (objs, @lhs = no) ->
super() super()
@objects = objs or [] @objects = objs or []
@propagateLhs()
children: ['objects'] children: ['objects']
@ -1999,10 +2015,6 @@ exports.Arr = class Arr extends Base
if unwrappedObj.comments and if unwrappedObj.comments and
unwrappedObj.comments.filter((comment) -> not comment.here).length is 0 unwrappedObj.comments.filter((comment) -> not comment.here).length is 0
unwrappedObj.includeCommentFragments = YES 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) compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in @objects)
olen = compiledObjs.length olen = compiledObjs.length
@ -2050,7 +2062,24 @@ exports.Arr = class Arr extends Base
obj = obj.unwrapAll() obj = obj.unwrapAll()
obj.eachName iterator 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: -> astProperties: ->
return return
@ -2585,6 +2614,7 @@ exports.Assign = class Assign extends Base
constructor: (@variable, @value, @context, options = {}) -> constructor: (@variable, @value, @context, options = {}) ->
super() super()
{@param, @subpattern, @operatorToken, @moduleDeclaration, @originalContext = @context} = options {@param, @subpattern, @operatorToken, @moduleDeclaration, @originalContext = @context} = options
@propagateLhs()
children: ['variable', 'value'] children: ['variable', 'value']
@ -2611,18 +2641,11 @@ exports.Assign = class Assign extends Base
compileNode: (o) -> compileNode: (o) ->
isValue = @variable instanceof Value isValue = @variable instanceof Value
if isValue 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, were destructuring; # If `@variable` is an array or an object, were destructuring;
# if its also `isAssignable()`, the destructuring syntax is supported # if its also `isAssignable()`, the destructuring syntax is supported
# in ES and we can output it as is; otherwise we `@compileDestructuring` # in ES and we can output it as is; otherwise we `@compileDestructuring`
# and convert this ES-unsupported destructuring into acceptable output. # and convert this ES-unsupported destructuring into acceptable output.
if @variable.isArray() or @variable.isObject() 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 theyre assignable as
# destructured variables.
@variable.base.lhs = yes
unless @variable.isAssignable() unless @variable.isAssignable()
if @variable.isObject() and @variable.base.hasSplat() if @variable.isObject() and @variable.base.hasSplat()
return @compileObjectDestruct o return @compileObjectDestruct o
@ -2920,6 +2943,31 @@ exports.Assign = class Assign extends Base
eachName: (iterator) -> eachName: (iterator) ->
@variable.unwrapAll().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 theyre 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 #### FuncGlyph
exports.FuncGlyph = class FuncGlyph extends Base exports.FuncGlyph = class FuncGlyph extends Base
@ -3369,7 +3417,11 @@ exports.Splat = class Splat extends Base
unwrap: -> @name unwrap: -> @name
astType: -> 'SpreadElement' astType: ->
if @lhs
'RestElement'
else
'SpreadElement'
astProperties: -> { astProperties: -> {
argument: @name.ast() argument: @name.ast()

View File

@ -1108,24 +1108,93 @@ test "AST as expected for ImportNamespaceSpecifier node", ->
type: 'StringLiteral' type: 'StringLiteral'
value: 'react' value: 'react'
# test "AST as expected for Assign node", -> test "AST as expected for Assign node", ->
# testExpression 'a = 1', testExpression 'a = b',
# type: 'Assign' type: 'AssignmentExpression'
# variable: left:
# value: 'a' type: 'Identifier'
# value: name: 'a'
# value: '1' right:
type: 'Identifier'
name: 'b'
operator: '='
# testExpression 'a: 1', testExpression 'a += b',
# properties: [ type: 'AssignmentExpression'
# type: 'Assign' left:
# context: 'object' type: 'Identifier'
# originalContext: 'object' name: 'a'
# variable: right:
# value: 'a' type: 'Identifier'
# value: name: 'b'
# value: '1' 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. # # `FuncGlyph` node isn't exported.
@ -1218,31 +1287,24 @@ test "AST as expected for Elision node", ->
name: 'b' name: 'b'
] ]
# testExpression '[,,,a,,,b] = "asdfqwer"', testExpression '[,,,a,,,b] = "asdfqwer"',
# type: 'Assign' type: 'AssignmentExpression'
# variable: left:
# type: 'Arr' type: 'ArrayPattern'
# lhs: no elements: [
# objects: [ null, null, null
# {type: 'Elision'} ,
# {type: 'Elision'} type: 'Identifier'
# {type: 'Elision'} name: 'a'
# { ,
# type: 'IdentifierLiteral' null, null
# value: 'a' ,
# } type: 'Identifier'
# {type: 'Elision'} name: 'b'
# {type: 'Elision'} ]
# { right:
# type: 'IdentifierLiteral' type: 'StringLiteral'
# value: 'b' value: 'asdfqwer'
# }
# ]
# value:
# type: 'StringLiteral'
# value: '"asdfqwer"'
# originalValue: 'asdfqwer'
# quote: '"'
# test "AST as expected for While node", -> # test "AST as expected for While node", ->
# testExpression 'loop 1', # testExpression 'loop 1',

View File

@ -1902,3 +1902,156 @@ test "AST location data as expected for Obj node", ->
end: end:
line: 1 line: 1
column: 4 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