-
-
Notifications
You must be signed in to change notification settings - Fork 79
/
Copy pathtraverse.ts
123 lines (109 loc) · 3.5 KB
/
traverse.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* @copyright 2017 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
import type { VisitorKeys } from "eslint-visitor-keys"
import * as Evk from "eslint-visitor-keys"
import type { Node } from "./nodes"
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
export const KEYS = Evk.unionWith({
VAttribute: ["key", "value"],
VDirectiveKey: ["name", "argument", "modifiers"],
VDocumentFragment: ["children"],
VElement: ["startTag", "children", "endTag"],
VEndTag: [],
VExpressionContainer: ["expression"],
VFilter: ["callee", "arguments"],
VFilterSequenceExpression: ["expression", "filters"],
VForExpression: ["left", "right"],
VIdentifier: [],
VLiteral: [],
VOnExpression: ["body"],
VSlotScopeExpression: ["params"],
VStartTag: ["attributes"],
VText: [],
VGenericExpression: ["expression"],
})
/**
* Check that the given key should be traversed or not.
* @this {Traversable}
* @param key The key to check.
* @returns `true` if the key should be traversed.
*/
function fallbackKeysFilter(this: any, key: string): boolean {
let value = null
return (
key !== "comments" &&
key !== "leadingComments" &&
key !== "loc" &&
key !== "parent" &&
key !== "range" &&
key !== "tokens" &&
key !== "trailingComments" &&
(value = this[key]) !== null &&
typeof value === "object" &&
(typeof value.type === "string" || Array.isArray(value))
)
}
/**
* Get the keys of the given node to traverse it.
* @param node The node to get.
* @returns The keys to traverse.
*/
function getFallbackKeys(node: Node): string[] {
return Object.keys(node).filter(fallbackKeysFilter, node)
}
/**
* Check wheather a given value is a node.
* @param x The value to check.
* @returns `true` if the value is a node.
*/
function isNode(x: any): x is Node {
return x !== null && typeof x === "object" && typeof x.type === "string"
}
/**
* Traverse the given node.
* @param node The node to traverse.
* @param parent The parent node.
* @param visitor The node visitor.
*/
function traverse(node: Node, parent: Node | null, visitor: Visitor): void {
let i = 0
let j = 0
visitor.enterNode(node, parent)
const keys =
(visitor.visitorKeys ?? KEYS)[node.type] ?? getFallbackKeys(node)
for (i = 0; i < keys.length; ++i) {
const child = (node as any)[keys[i]]
if (Array.isArray(child)) {
for (j = 0; j < child.length; ++j) {
if (isNode(child[j])) {
traverse(child[j], node, visitor)
}
}
} else if (isNode(child)) {
traverse(child, node, visitor)
}
}
visitor.leaveNode(node, parent)
}
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
export interface Visitor {
visitorKeys?: VisitorKeys
enterNode(node: Node, parent: Node | null): void
leaveNode(node: Node, parent: Node | null): void
}
/**
* Traverse the given AST tree.
* @param node Root node to traverse.
* @param visitor Visitor.
*/
export function traverseNodes(node: Node, visitor: Visitor): void {
traverse(node, null, visitor)
}
export { getFallbackKeys }