Reputation: 79268
Given this expression in JavaScript:
ipad[15] = opad[15] = some[12] = some[13] = undefined
I get this AST (from acornjs):
{
"type": "Program",
"body": [
{
"type": "AssignmentExpression",
"left": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"start": 3943,
"end": 3947,
"name": "ipad"
},
"property": {
"type": "Literal",
"start": 3948,
"end": 3950,
"value": 15,
"raw": "15"
},
"computed": true
},
"right": {
"type": "AssignmentExpression",
"left": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"start": 3954,
"end": 3958,
"name": "opad"
},
"property": {
"type": "Literal",
"start": 3959,
"end": 3961,
"value": 15,
"raw": "15"
},
"computed": true
},
"right": {
"type": "AssignmentExpression",
"left": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"start": 3965,
"end": 3969,
"name": "some"
},
"property": {
"type": "Literal",
"start": 3970,
"end": 3972,
"value": 12,
"raw": "12"
},
"computed": true
},
"right": {
"type": "AssignmentExpression",
"left": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"start": 3976,
"end": 3980,
"name": "some"
},
"property": {
"type": "Literal",
"start": 3981,
"end": 3983,
"value": 13,
"raw": "13"
},
"computed": true
},
"right": {
"type": "Identifier",
"start": 3987,
"end": 3996,
"name": "undefined"
},
"operator": "="
},
"operator": "="
},
"operator": "="
},
"operator": "="
}
]
}
How can I convert this AST to something that when reserialized would produce:
ipad[15] = undefined
opad[15] = undefined
some[12] = undefined
some[13] = undefined
Basically, flattening the AST. How can it be done? I have been thinking about this for hours and trying to modify this source code to get it working, but it's a bit mind bending.
Each AssignmentExpression
has a right property. So I feel like just setting them at the top level to the last right item would work, but it's for some reason eluding me.
I am logging in that normalize_AssignmentExpression
function, and it is showing:
RIGHT Node { type: 'Identifier', start: 3987, end: 3996, name: 'undefined' }
RIGHT {
type: 'AssignmentExpression',
left: {
type: 'MemberExpression',
object: Node { type: 'Identifier', start: 3976, end: 3980, name: 'some' },
property: Node {
type: 'Literal',
start: 3981,
end: 3983,
value: 13,
raw: '13'
},
computed: true
},
right: Node {
type: 'Identifier',
start: 3987,
end: 3996,
name: 'undefined'
},
operator: '='
}
RIGHT {
type: 'AssignmentExpression',
left: {
type: 'MemberExpression',
object: Node { type: 'Identifier', start: 3965, end: 3969, name: 'some' },
property: Node {
type: 'Literal',
start: 3970,
end: 3972,
value: 12,
raw: '12'
},
computed: true
},
right: {
type: 'AssignmentExpression',
left: {
type: 'MemberExpression',
object: [Node],
property: [Node],
computed: true
},
right: Node {
type: 'Identifier',
start: 3987,
end: 3996,
name: 'undefined'
},
operator: '='
},
operator: '='
}
RIGHT {
type: 'AssignmentExpression',
left: {
type: 'MemberExpression',
object: Node { type: 'Identifier', start: 3954, end: 3958, name: 'opad' },
property: Node {
type: 'Literal',
start: 3959,
end: 3961,
value: 15,
raw: '15'
},
computed: true
},
right: {
type: 'AssignmentExpression',
left: {
type: 'MemberExpression',
object: [Node],
property: [Node],
computed: true
},
right: {
type: 'AssignmentExpression',
left: [Object],
right: [Node],
operator: '='
},
operator: '='
},
operator: '='
}
Not sure if that helps.
I try to make it like this, but it just outputs one:
function normalize_AssignmentExpression(node, scope) {
const [left, leftExps] = normalizeProperty(node.type, 'left', node.left.type, node.left, scope)
let [right, rightExps] = normalizeProperty(node.type, 'right', node.right.type, node.right, scope)
const exps = [
...leftExps,
...rightExps
]
let furthestRight = right
while (furthestRight.type === 'AssignmentExpression') {
furthestRight = furthestRight.right
}
if (left.type === 'ArrayPattern') {
const assignments = []
left.elements.forEach(el => {
assignments.push(
createAssignmentExpression(
el,
createMemberExpression(right, el),
node.operator
)
)
})
return [
assignments,
exps
]
} else {
console.log('RIGHT', right)
const assignment = createAssignmentExpression(left, furthestRight, node.operator)
return [
assignment,
exps
]
}
}
Upvotes: 1
Views: 405
Reputation: 50797
I saw this when it was posted, but didn't have time to look at it, then the OP posted an answer that worked, and I ignored it. But it was recently reopened by another answer, and I thought it interesting. Here is one fairly clean solution:
const getValue = (assignment) =>
assignment .right .type == 'AssignmentExpression'
? getValue (assignment .right)
: assignment .right
const handleExpression = (expression) =>
expression.type === 'AssignmentExpression'
? [
{...expression, right: getValue (expression)},
... handleExpression (expression .right)
]
: expression
const process = (ast) =>
({...ast, body: ast .body .flatMap (handleExpression)})
const ast = {type: "Program", body: [{type: "AssignmentExpression", left: {type: "MemberExpression", object: {type: "Identifier", start: 3943, end: 3947, name: "ipad"}, property: {type: "Literal", start: 3948, end: 3950, value: 15, raw: "15"}, computed: true}, right: {type: "AssignmentExpression", left: {type: "MemberExpression", object: {type: "Identifier", start: 3954, end: 3958, name: "opad"}, property: {type: "Literal", start: 3959, end: 3961, value: 15, raw: "15"}, computed: true}, right: {type: "AssignmentExpression", left: {type: "MemberExpression", object: {type: "Identifier", start: 3965, end: 3969, name: "some"}, property: {type: "Literal", start: 3970, end: 3972, value: 12, raw: "12"}, computed: true}, right: {type: "AssignmentExpression", left: {type: "MemberExpression", object: {type: "Identifier", start: 3976, end: 3980, name: "some"}, property: {type: "Literal", start: 3981, end: 3983, value: 13, raw: "13"}, computed: true}, right: {type: "Identifier", start: 3987, end: 3996, name: "undefined"}, operator: "="}, operator: "="}, operator: "="}, operator: "="}]}
console .log (JSON.stringify (process (ast), null, 2))
.as-console-wrapper {max-height: 100% !important; top: 0}
getValue
traverses down the list of nested Assignment expressions until it finds something other than an AssignmentExpression
for right
, and returns it.
handleExpression
takes an expression and returns an array of expressions, either the original, or, if the type is AssignmentExpression
, then a flattened list of this sort of chain. There is some inefficiency here in recalculating getValue
for the nested nodes. I don't know how much work it would be to get rid of this.
process
is probably throw-away code, to demonstrate in this simple document. You would have to determine how to apply handleExpression
.
For instance, when I ran Acorn against the sample, I got an extra layer of ExpressionStatement
wrapped around the AssignmentExpression
, and needed something more like this:
const process = (ast) => ({
...ast,
body: ast .body .flatMap (
(expr) => expr .type == 'ExpressionStatement' ? handleExpression (expr .expression) : expr
)
})
This does not attempt to deal with start
and end
nodes. It will now get them wrong, and it's probably worth removing them. (Rewriting them correctly would be a huge task, I imagine.)
Finally, are you sure this is how you want to process these nodes? It strikes me that you might also have to deal with something like:
ipad[15] = opad[15] = some[12] = some[13] = someTimeConsumingFunction()
and you wouldn't want this to become
ipad[15] = someTimeConsumingFunction()
opad[15] = someTimeConsumingFunction()
some[12] = someTimeConsumingFunction()
some[13] = someTimeConsumingFunction()
Instead, I personally would prefer:
some[13] = someTimeConsumingFunction()
some[12] = some[13]
opad[15] = some[12]
ipad[15] = opad[12]
And this could probably be done by a version like this, which doesn't need the getValue
helper:
const handleExpression = (expression) =>
expression.type === 'AssignmentExpression'
? [... handleExpression (expression .right), {...expression, right: expression .right .left}]
: expression
Upvotes: 1
Reputation: 827
Here is simplest possible transformation using 🐊Putout
code transformer I'm working on, it's not scalable for infinite assignments, but good for simple cases like one from example :
module.exports.replace = () => ({
'__a = __b = __c = __d = __e': `{
__a = __e;
__b = __e;
__c = __e;
__d = __e;
}`
});
It looks this way:
You can try it in 🐊Putout Editor
.
To remove nested blocks use @putout/plugin-remove-nested-blocks:
import putout from 'putout';
putout('ipad[15] = opad[15] = some[12] = some[13] = undefined', {
plugins: [
'remove-nested-blocks',
['flatten-sequence-assignment', {
report: () => 'flatten assignments',
replace: () => ({
'__a = __b = __c = __d = __e': `{
__a = __e;
__b = __e;
__c = __e;
__d = __e;
}`
}),
}]
]
});
Upvotes: 0
Reputation: 79268
This does it:
function normalize_AssignmentExpression(node, scope) {
const [left, leftExps] = normalizeProperty(node.type, 'left', node.left.type, node.left, scope)
let [right, rightExps] = normalizeProperty(node.type, 'right', node.right.type, node.right, scope)
const exps = [
...leftExps,
...rightExps
]
let furthestRight = Array.isArray(right) ? right[0] : right
let rights = Array.isArray(right) ? right.slice(1) : []
let lefts = [left]
while (furthestRight.type === 'AssignmentExpression') {
lefts.push(furthestRight.left)
furthestRight = furthestRight.right
}
if (left.type === 'ArrayPattern') {
const assignments = []
left.elements.forEach(el => {
assignments.push(
createAssignmentExpression(
el,
createMemberExpression(right, el),
node.operator
)
)
})
return [
assignments,
exps
]
} else {
const assignments = []
lefts.forEach(l => {
const assignment = createAssignmentExpression(l, furthestRight, node.operator)
assignments.push(assignment)
})
assignments.push(...rights)
return [
assignments,
exps
]
}
}
Upvotes: 1