-
Notifications
You must be signed in to change notification settings - Fork 43
/
ast-visitor.ts
101 lines (89 loc) · 2.87 KB
/
ast-visitor.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
import * as a from './syntax/ast';
import { AstDefaultMapper, IAstPartialMapper, astMapper, IAstMapper } from './ast-mapper';
import { ReplaceReturnType } from './utils';
export type IAstPartialVisitor = { [key in keyof IAstPartialMapper]: ReplaceReturnType<IAstPartialMapper[key], any> }
export type IAstFullVisitor = {
[key in keyof IAstPartialVisitor]-?: IAstPartialVisitor[key];
}
export type IAstVisitor = IAstFullVisitor & {
super(): IAstVisitor;
}
class Visitor {
mapper?: IAstMapper;
visitor?: IAstPartialVisitor;
super() {
return new SkipVisitor(this);
}
}
// =============== auto implement the mapper
const mapperProto = AstDefaultMapper.prototype as any;
for (const k of Object.getOwnPropertyNames(mapperProto)) {
const orig = mapperProto[k] as Function;
if (k === 'constructor' || k === 'super' || typeof orig !== 'function') {
continue;
}
Object.defineProperty(Visitor.prototype, k, {
configurable: false,
get() {
return function (this: Visitor, ...args: any[]) {
const impl = (this.visitor as any)[k] as Function;
if (!impl) {
// just ignore & forward call to mapper
return orig.apply(this, args);
}
// return first argument
// ...which means "I dont wana change anything"
// in the ast-modifier language.
impl.apply(this.visitor, args);
return args[0];
}
}
})
}
// ====== auto implement the skip mechanism
class SkipVisitor {
constructor(readonly parent: Visitor) {
}
}
for (const k of Object.getOwnPropertyNames(mapperProto)) {
const orig = mapperProto[k] as Function;
if (k === 'constructor' || k === 'super' || typeof orig !== 'function') {
continue;
}
Object.defineProperty(SkipVisitor.prototype, k, {
configurable: false,
get() {
return function (this: SkipVisitor, ...args: []) {
return orig.apply(this.parent, args);
}
}
});
}
/**
* Builds an AST visitor based on the default implementation, merged with the one you provide.
*
* Example of visitor which counts references to a column 'foo':
*
* ```ts
* let cnt = 0;
* const visitor = astVisitor(v => ({
* ref: r => {
* if (r.name === 'foo') {
* cnt++;
* }
* v.super().ref(r);
* }
* }));
*
* visitor.statement(myStatementToCount);
* console.log(`${cnt} references to foo !`);
* ```
*/
export function astVisitor<T extends IAstPartialVisitor = IAstPartialVisitor>(visitorBuilder: (defaultImplem: IAstVisitor) => T): IAstVisitor {
return astMapper(m => {
const ret = new Visitor();
ret.mapper = m;
ret.visitor = visitorBuilder(ret as any);
return ret as any;
})
}