如何在 JavaScript AST 中展平一系列赋值?
How do you flatten a sequence of assignments in a JavaScript AST?
给定 JavaScript 中的表达式:
ipad[15] = opad[15] = some[12] = some[13] = undefined
我得到这个 AST(来自 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": "="
}
]
}
如何将此 AST 转换为重新序列化时会产生的内容:
ipad[15] = undefined
opad[15] = undefined
some[12] = undefined
some[13] = undefined
基本上,扁平化 AST。如何做呢?几个小时以来,我一直在思考这个问题,并试图修改 this source code 以使其正常工作,但这有点令人费解。
每个AssignmentExpression
都有一个权利属性。所以我觉得只要将它们设置在顶层到最后一个正确的项目就可以了,但由于某种原因我无法做到这一点。
我正在登录 normalize_AssignmentExpression
函数,它显示:
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: '='
}
不确定是否有帮助。
我试着做成这样,但它只输出一个:
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
]
}
}
这样做:
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
]
}
}
这是使用我正在处理的 Putout
代码转换器的最简单可能的转换,它不可扩展以进行无限分配,但适用于简单的情况,例如示例中的一个:
module.exports.replace = () => ({
'__a = __b = __c = __d = __e': `{
__a = __e;
__b = __e;
__c = __e;
__d = __e;
}`
});
看起来是这样的:
您可以在Putout Editor
中尝试。
删除嵌套块使用@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;
}`
}),
}]
]
});
发帖的时候看到了,没时间看,然后OP发了一个有用的答案,我就忽略了。不过最近被另一个回答重新打开了,觉得挺有意思的。这是一个相当干净的解决方案:
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
向下遍历嵌套赋值表达式列表,直到找到 right
的 AssignmentExpression
和 returns 以外的其他内容。
handleExpression
接受一个表达式和 returns 一个表达式数组,或者是原始的,或者,如果类型是 AssignmentExpression
,则为这种链的展平列表。为嵌套节点重新计算 getValue
时效率低下。我不知道摆脱这个需要多少工作。
process
大概是 throw-away 代码,在这个简单的文档中进行演示。您必须确定如何应用 handleExpression
.
例如,当我 运行 Acorn 对抗样本时,我在 AssignmentExpression
周围多了一层 ExpressionStatement
包裹,并且需要更多类似这样的东西:
const process = (ast) => ({
...ast,
body: ast .body .flatMap (
(expr) => expr .type == 'ExpressionStatement' ? handleExpression (expr .expression) : expr
)
})
这不会尝试处理 start
和 end
节点。它现在会弄错它们,可能值得删除它们。 (我想正确地重写它们将是一项艰巨的任务。)
最后,您确定要这样处理这些节点吗?让我印象深刻的是,您可能还必须处理类似的事情:
ipad[15] = opad[15] = some[12] = some[13] = someTimeConsumingFunction()
你不希望它变成
ipad[15] = someTimeConsumingFunction()
opad[15] = someTimeConsumingFunction()
some[12] = someTimeConsumingFunction()
some[13] = someTimeConsumingFunction()
相反,我个人更喜欢:
some[13] = someTimeConsumingFunction()
some[12] = some[13]
opad[15] = some[12]
ipad[15] = opad[12]
这可能可以通过这样的版本来完成,它不需要 getValue
助手:
const handleExpression = (expression) =>
expression.type === 'AssignmentExpression'
? [... handleExpression (expression .right), {...expression, right: expression .right .left}]
: expression
给定 JavaScript 中的表达式:
ipad[15] = opad[15] = some[12] = some[13] = undefined
我得到这个 AST(来自 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": "="
}
]
}
如何将此 AST 转换为重新序列化时会产生的内容:
ipad[15] = undefined
opad[15] = undefined
some[12] = undefined
some[13] = undefined
基本上,扁平化 AST。如何做呢?几个小时以来,我一直在思考这个问题,并试图修改 this source code 以使其正常工作,但这有点令人费解。
每个AssignmentExpression
都有一个权利属性。所以我觉得只要将它们设置在顶层到最后一个正确的项目就可以了,但由于某种原因我无法做到这一点。
我正在登录 normalize_AssignmentExpression
函数,它显示:
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: '='
}
不确定是否有帮助。
我试着做成这样,但它只输出一个:
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
]
}
}
这样做:
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
]
}
}
这是使用我正在处理的 Putout
代码转换器的最简单可能的转换,它不可扩展以进行无限分配,但适用于简单的情况,例如示例中的一个:
module.exports.replace = () => ({
'__a = __b = __c = __d = __e': `{
__a = __e;
__b = __e;
__c = __e;
__d = __e;
}`
});
看起来是这样的:
您可以在Putout Editor
中尝试。
删除嵌套块使用@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;
}`
}),
}]
]
});
发帖的时候看到了,没时间看,然后OP发了一个有用的答案,我就忽略了。不过最近被另一个回答重新打开了,觉得挺有意思的。这是一个相当干净的解决方案:
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
向下遍历嵌套赋值表达式列表,直到找到 right
的 AssignmentExpression
和 returns 以外的其他内容。
handleExpression
接受一个表达式和 returns 一个表达式数组,或者是原始的,或者,如果类型是 AssignmentExpression
,则为这种链的展平列表。为嵌套节点重新计算 getValue
时效率低下。我不知道摆脱这个需要多少工作。
process
大概是 throw-away 代码,在这个简单的文档中进行演示。您必须确定如何应用 handleExpression
.
例如,当我 运行 Acorn 对抗样本时,我在 AssignmentExpression
周围多了一层 ExpressionStatement
包裹,并且需要更多类似这样的东西:
const process = (ast) => ({
...ast,
body: ast .body .flatMap (
(expr) => expr .type == 'ExpressionStatement' ? handleExpression (expr .expression) : expr
)
})
这不会尝试处理 start
和 end
节点。它现在会弄错它们,可能值得删除它们。 (我想正确地重写它们将是一项艰巨的任务。)
最后,您确定要这样处理这些节点吗?让我印象深刻的是,您可能还必须处理类似的事情:
ipad[15] = opad[15] = some[12] = some[13] = someTimeConsumingFunction()
你不希望它变成
ipad[15] = someTimeConsumingFunction()
opad[15] = someTimeConsumingFunction()
some[12] = someTimeConsumingFunction()
some[13] = someTimeConsumingFunction()
相反,我个人更喜欢:
some[13] = someTimeConsumingFunction()
some[12] = some[13]
opad[15] = some[12]
ipad[15] = opad[12]
这可能可以通过这样的版本来完成,它不需要 getValue
助手:
const handleExpression = (expression) =>
expression.type === 'AssignmentExpression'
? [... handleExpression (expression .right), {...expression, right: expression .right .left}]
: expression