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.
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, were destructuring;
// if its 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 theyre 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 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'];
@ -5047,7 +5127,11 @@
}
astType() {
return 'SpreadElement';
if (this.lhs) {
return 'RestElement';
} else {
return 'SpreadElement';
}
}
astProperties() {

View File

@ -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, were destructuring;
# if its 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 theyre 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 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
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()

View File

@ -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',

View File

@ -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