跳到主要内容

traverse.js

traverse 功能为遍历 AST 抽象语法树

Demo示例一


分析

<h1 id="title"><span>hello</span>world</h1> 生成 AST 抽象语法树,并实现遍历操作

通过 parse 编译器生成的抽象语法树为:

{
"type": "Program",
"start": 0,
"end": 43,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 43,
"expression": {
"type": "JSXElement",
"start": 0,
"end": 43,
"openingElement": {
"type": "JSXOpeningElement",
"start": 0,
"end": 15,
"attributes": [
{
"type": "JSXAttribute",
"start": 4,
"end": 14,
"name": {
"type": "JSXIdentifier",
"start": 4,
"end": 6,
"name": "id"
},
"value": {
"type": "Literal",
"start": 7,
"end": 14,
"value": "title",
"raw": "\"title\""
}
}
],
"name": {
"type": "JSXIdentifier",
"start": 1,
"end": 3,
"name": "h1"
},
"selfClosing": false
},
"closingElement": {
"type": "JSXClosingElement",
"start": 38,
"end": 43,
"name": {
"type": "JSXIdentifier",
"start": 40,
"end": 42,
"name": "h1"
}
},
"children": [
{
"type": "JSXElement",
"start": 15,
"end": 33,
"openingElement": {
"type": "JSXOpeningElement",
"start": 15,
"end": 21,
"attributes": [],
"name": {
"type": "JSXIdentifier",
"start": 16,
"end": 20,
"name": "span"
},
"selfClosing": false
},
"closingElement": {
"type": "JSXClosingElement",
"start": 26,
"end": 33,
"name": {
"type": "JSXIdentifier",
"start": 28,
"end": 32,
"name": "span"
}
},
"children": [
{
"type": "JSXText",
"start": 21,
"end": 26,
"value": "hello",
"raw": "hello"
}
]
},
{
"type": "JSXText",
"start": 33,
"end": 38,
"value": "world",
"raw": "world"
}
]
}
}
],
"sourceType": "module"
}

根据 AST 抽象语法树结构,依次实现 traverse 即可

实现

const nodeTypes = require("./nodeTypes.md");

function replace(parent, oldNode, newNode) {
if (parent) {
for (const key in parent) {
if (parent.hasOwnProperty(key)) {
if (parent[key] === oldNode) {
parent[key] = newNode;
}
}
}
}
}

function traverse(ast, visitor) {
function traverseArray(array, parent) {
array.forEach((child) => traverseNode(child, parent));
}
function traverseNode(node, parent) {
let replaceWith = replace.bind(null, parent, node);
let method = visitor[node.type];
if (method) {
if (typeof method === "function") {
method({ node, replaceWith }, parent);
} else {
method.enter({ node, replaceWith }, parent);
}
}
switch (node.type) {
case nodeTypes.Program:
traverseArray(node.body, node);
break;
case nodeTypes.ExpressionStatement:
traverseNode(node.expression, node);
break;
case nodeTypes.JSXElement:
traverseNode(node.openingElement, node);
traverseArray(node.children, node);
traverseNode(node.closingElement, node);
break;
case nodeTypes.OpeningElement:
traverseNode(node.name, node);
traverseNode(node.attributes, node);
break;
case nodeTypes.JSXAttribute:
traverseNode(node.name, node);
traverseNode(node.value, node);
break;
case nodeTypes.ClosingElement:
traverseNode(node.name, node);
break;
case nodeTypes.JSXIdentifier:
case nodeTypes.JSXText:
case nodeTypes.Literal:
break;
default:
break;
}
if (method && method.exit) {
method.exit({ node }, parent);
}
}
traverseNode(ast, null);
}

module.exports = traverse;

测试

const parser = require("./parser");
const traverse = require("./traverse");

const sourceCode = '<h1 id="title"><span>hello</span>world</h1>';
const ast = parser(sourceCode);
traverse(ast,{
JSXOpeningElement:{
enter(nodePath,parent){
console.log("对象方式进入",nodePath);
},
exit(nodePath,parent){
console.log("对象方式离开",nodePath)
}
},
JSXClosingElement(nodePath,parent){
console.log("函数方式进入",nodePath);
}
});