如何将TemplateLiteral类型的AST节点还原为模板字符串源代码
模板字符串AST节点还原方案
对JS AST做标准化处理来降低转译到特定目标语言的难度的过程中,需要通过打印函数将处理后的AST还原为JS源代码做测试,目前绝大多数节点场景都已覆盖,仅TemplateLiteral节点的结构特殊处理受阻。
示例场景
现有如下模板字符串代码:
const str = 'const b83 = `foo${a100} bar ${b82}`'
解析为AST后,TemplateLiteral节点结构如下:
{ "type": "TemplateLiteral", "start": 2692, "end": 2715, "expressions": [ { "type": "Identifier", "start": 2698, "end": 2702, "name": "a100" }, { "type": "Identifier", "start": 2710, "end": 2713, "name": "b82" } ], "quasis": [ { "type": "TemplateElement", "start": 2693, "end": 2696, "value": { "raw": "foo", "cooked": "foo" }, "tail": false }, { "type": "TemplateElement", "start": 2703, "end": 2708, "value": { "raw": " bar ", "cooked": " bar " }, "tail": false }, { "type": "TemplateElement", "start": 2714, "end": 2714, "value": { "raw": "", "cooked": "" }, "tail": true } ] }
还原逻辑
TemplateLiteral节点的expressions和quasis虽然是两个独立数组,但遵循ECMAScript AST规范的固定规则:quasis数组的长度永远比expressions数组的长度大1,且两个数组的元素顺序严格对应模板字符串中静态片段、插值的出现顺序,完全不需要依赖不可靠的start/end位置信息即可完成拼接。
具体拼接步骤如下:
- 开头输出模板字符串的起始反引号
` - 遍历
quasis数组的每一个元素:- 先输出当前
quasis元素的value.raw值(必须用raw而非cooked,才能保留原模板的转义写法,避免转义字符被解析成实际值) - 如果当前遍历的不是
quasis数组的最后一个元素,拼接${,递归调用现有AST打印逻辑输出对应索引的expressions节点源码,再拼接}
- 先输出当前
- 遍历完成后输出模板字符串的结束反引号
`
实现示例
// printNode为现有处理其他AST节点的通用打印函数 function generateTemplateLiteral(node, printNode) { let code = '`'; for (let i = 0; i < node.quasis.length; i++) { code += node.quasis[i].value.raw; if (i < node.expressions.length) { code += '${'; code += printNode(node.expressions[i]); code += '}'; } } code += '`'; return code; }
示例节点代入后输出结果为 `foo${a100} bar ${b82}`,和原始模板字符串完全一致。
内容的提问来源于stack exchange,提问作者Lance Pollard
相关产品推荐
相关产品推荐

