yuku-ast 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -181
- package/dist/identifier.d.mts +1 -1
- package/dist/identifier.mjs +1 -1
- package/dist/index.d.mts +222 -316
- package/dist/index.mjs +371 -875
- package/dist/utils.d.mts +2 -2
- package/dist/utils.mjs +1 -1
- package/package.json +10 -12
package/README.md
CHANGED
|
@@ -1,106 +1,33 @@
|
|
|
1
1
|
# yuku-ast
|
|
2
2
|
|
|
3
3
|
A fast, fully typed AST toolkit for [`yuku-parser`](https://www.npmjs.com/package/yuku-parser):
|
|
4
|
-
node builders, type guards,
|
|
4
|
+
node builders, type guards, and identifier validators.
|
|
5
5
|
|
|
6
|
-
| Import | Provides
|
|
7
|
-
| --------------------- |
|
|
8
|
-
| `yuku-ast` | `
|
|
9
|
-
| `yuku-ast/utils` | node utilities such as `nameOf`
|
|
10
|
-
| `yuku-ast/identifier` | identifier and reserved-word validators
|
|
6
|
+
| Import | Provides |
|
|
7
|
+
| --------------------- | --------------------------------------- |
|
|
8
|
+
| `yuku-ast` | `b` (node builders), `is` (type guards) |
|
|
9
|
+
| `yuku-ast/utils` | node utilities such as `nameOf` |
|
|
10
|
+
| `yuku-ast/identifier` | identifier and reserved-word validators |
|
|
11
11
|
|
|
12
12
|
```ts
|
|
13
13
|
import { parse } from "yuku-parser";
|
|
14
|
-
import {
|
|
14
|
+
import { b, is } from "yuku-ast";
|
|
15
15
|
|
|
16
|
-
const { program } = parse("const greet =
|
|
16
|
+
const { program } = parse("const greet = 1;");
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
18
|
+
const decl = program.body[0];
|
|
19
|
+
if (is.VariableDeclaration(decl)) {
|
|
20
|
+
// `decl` is narrowed; swap the initializer for a freshly built node.
|
|
21
|
+
decl.declarations[0].init = b.numericLiteral(42);
|
|
22
|
+
}
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Install
|
|
26
26
|
|
|
27
27
|
```sh
|
|
28
|
-
bun add yuku-ast
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
`yuku-parser` is a peer dependency.
|
|
32
|
-
|
|
33
|
-
## Visitors
|
|
34
|
-
|
|
35
|
-
A visitor is keyed by node `type`, by an alias group, or by the universal
|
|
36
|
-
`enter` / `leave`. Each handler receives the typed node and a [`path`](#the-path).
|
|
37
|
-
|
|
38
|
-
```ts
|
|
39
|
-
walk(program, {
|
|
40
|
-
// a concrete node type, `node` is precisely typed
|
|
41
|
-
CallExpression(node, path) {
|
|
42
|
-
if (path.parent?.type === "AwaitExpression") return;
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
// an alias group fires for a whole family
|
|
46
|
-
Function(node) {
|
|
47
|
-
node.params; // FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | ...
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
// enter/leave for any node type
|
|
51
|
-
MemberExpression: {
|
|
52
|
-
enter(node) {},
|
|
53
|
-
leave(node) {},
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
// run for every node
|
|
57
|
-
enter(node) {},
|
|
58
|
-
leave(node) {},
|
|
59
|
-
});
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Aliases: `Expression`, `Statement`, `Declaration`, `ModuleDeclaration`,
|
|
63
|
-
`Function`, `Class`, `Method`, `Loop`, `Pattern`, `JSX`, `TSType`.
|
|
64
|
-
|
|
65
|
-
When several handlers match a node, `enter` runs outermost first
|
|
66
|
-
(universal, alias, concrete) and `leave` runs in reverse.
|
|
67
|
-
|
|
68
|
-
## The path
|
|
69
|
-
|
|
70
|
-
Every visitor receives a `path` describing where it is and how to change the tree.
|
|
71
|
-
Each node has its own path, so it stays valid after the visit returns.
|
|
72
|
-
|
|
73
|
-
```ts
|
|
74
|
-
node: T // the current node
|
|
75
|
-
parent: Node | null // owner, or null at the root
|
|
76
|
-
key: string | null // field on the parent holding this node
|
|
77
|
-
index: number | null // position within an array field, else null
|
|
78
|
-
ancestors: readonly Node[] // root down to the parent
|
|
79
|
-
state: S // value threaded through the walk
|
|
80
|
-
|
|
81
|
-
skip() // do not descend into this node
|
|
82
|
-
stop() // end the walk
|
|
83
|
-
replace(next) // swap this node, then walk the replacement
|
|
84
|
-
remove() // detach from the parent
|
|
85
|
-
insertBefore(node) // add a sibling before (array fields)
|
|
86
|
-
insertAfter(node) // add a sibling after (array fields)
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
walk(program, {
|
|
91
|
-
DebuggerStatement(_node, path) {
|
|
92
|
-
path.remove();
|
|
93
|
-
},
|
|
94
|
-
Identifier(node, path) {
|
|
95
|
-
if (node.name === "foo") path.replace(b.identifier("bar"));
|
|
96
|
-
},
|
|
97
|
-
});
|
|
28
|
+
bun add yuku-ast
|
|
98
29
|
```
|
|
99
30
|
|
|
100
|
-
`replace` walks into the replacement's children but does not re-run its own
|
|
101
|
-
`enter`. A synthetic replacement (one built with `b.*`) inherits the replaced
|
|
102
|
-
node's source span, so generated output still maps back to the original.
|
|
103
|
-
|
|
104
31
|
## Type guards (`is`)
|
|
105
32
|
|
|
106
33
|
A guard for every node `type`, the alias families, and the variants that share a
|
|
@@ -116,19 +43,21 @@ is.StaticMemberExpression(node); // MemberExpression variants
|
|
|
116
43
|
|
|
117
44
|
is.Identifier(node, "this"); // an Identifier with that exact name
|
|
118
45
|
is.oneOf(node, ["CallExpression", "NewExpression"]); // any of these types
|
|
119
|
-
|
|
120
|
-
walk(program, {
|
|
121
|
-
Literal(node, path) {
|
|
122
|
-
if (is.ArrayExpression(path.parent)) {
|
|
123
|
-
// path.parent is narrowed to ArrayExpression
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
46
|
```
|
|
128
47
|
|
|
129
48
|
`is.oneOf(node, types)` narrows to the union of the listed types, and
|
|
130
|
-
`is.Identifier(node, name?)` narrows to `Identifier`.
|
|
131
|
-
|
|
49
|
+
`is.Identifier(node, name?)` narrows to `Identifier`. A matching guard narrows the
|
|
50
|
+
node for typed field access:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const element = arrayExpression.elements[0];
|
|
54
|
+
if (is.Identifier(element)) {
|
|
55
|
+
element.name; // `element` is narrowed to Identifier
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Aliases: `Expression`, `Statement`, `Declaration`, `ModuleDeclaration`,
|
|
60
|
+
`Function`, `Class`, `Method`, `Loop`, `Pattern`, `JSX`, `TSType`.
|
|
132
61
|
|
|
133
62
|
## Reading names (`nameOf`)
|
|
134
63
|
|
|
@@ -138,18 +67,13 @@ or `undefined`). It reads the common `Identifier | StringLiteral` slots, such as
|
|
|
138
67
|
a `ModuleExportName` or a static property or member key, in one call.
|
|
139
68
|
|
|
140
69
|
```ts
|
|
141
|
-
import { walk } from "yuku-ast";
|
|
142
70
|
import { nameOf } from "yuku-ast/utils";
|
|
143
71
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
Property(node) {
|
|
150
|
-
if (!node.computed) nameOf(node.key); // static key name, else null
|
|
151
|
-
},
|
|
152
|
-
});
|
|
72
|
+
// `export { x as y }` and `export { x as "z" }` both resolve.
|
|
73
|
+
nameOf(specifier.exported); // "y", "z"
|
|
74
|
+
|
|
75
|
+
// A static (non-computed) property key, else null.
|
|
76
|
+
if (!property.computed) nameOf(property.key);
|
|
153
77
|
```
|
|
154
78
|
|
|
155
79
|
## Builders (`b`)
|
|
@@ -162,82 +86,35 @@ import { b } from "yuku-ast";
|
|
|
162
86
|
|
|
163
87
|
b.identifier("x");
|
|
164
88
|
b.callExpression(b.identifier("f"), [b.numericLiteral(1)]);
|
|
165
|
-
b.arrowFunctionExpression([b.identifier("a"
|
|
166
|
-
b.variableDeclaration("const", [
|
|
167
|
-
b.variableDeclarator(b.identifier("x", "binding"), b.numericLiteral(0)),
|
|
168
|
-
]);
|
|
89
|
+
b.arrowFunctionExpression([b.identifier("a")], b.identifier("a"));
|
|
90
|
+
b.variableDeclaration("const", [b.variableDeclarator(b.identifier("x"), b.numericLiteral(0))]);
|
|
169
91
|
```
|
|
170
92
|
|
|
171
|
-
|
|
93
|
+
When you assign a synthetic node in place of an existing one, copy the original's
|
|
94
|
+
`start` / `end` first so [`yuku-codegen`](https://www.npmjs.com/package/yuku-codegen)
|
|
95
|
+
source maps still point back to the original input.
|
|
172
96
|
|
|
173
|
-
|
|
174
|
-
|
|
97
|
+
## Build and print
|
|
98
|
+
|
|
99
|
+
Pair it with [`yuku-codegen`](https://www.npmjs.com/package/yuku-codegen) to print
|
|
100
|
+
a built tree back to source.
|
|
175
101
|
|
|
176
102
|
```ts
|
|
177
|
-
import { parse } from "yuku-parser";
|
|
178
103
|
import { print } from "yuku-codegen";
|
|
179
|
-
import {
|
|
180
|
-
|
|
181
|
-
const { program } = parse("const x = 1; debugger; foo(x, 2);");
|
|
182
|
-
|
|
183
|
-
walk(program, {
|
|
184
|
-
DebuggerStatement(_node, path) {
|
|
185
|
-
path.remove();
|
|
186
|
-
},
|
|
187
|
-
Identifier(node, path) {
|
|
188
|
-
if (node.name === "foo") path.replace(b.identifier("bar"));
|
|
189
|
-
},
|
|
190
|
-
Literal(node, path) {
|
|
191
|
-
if (is.NumericLiteral(node)) path.replace(b.numericLiteral((node.value ?? 0) * 10));
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
console.log(print(program).code);
|
|
196
|
-
// const x = 10;
|
|
197
|
-
// bar(x, 20);
|
|
198
|
-
```
|
|
104
|
+
import { b } from "yuku-ast";
|
|
199
105
|
|
|
200
|
-
|
|
201
|
-
|
|
106
|
+
const program = b.program([
|
|
107
|
+
b.variableDeclaration("const", [b.variableDeclarator(b.identifier("x"), b.numericLiteral(42))]),
|
|
108
|
+
b.expressionStatement(
|
|
109
|
+
b.callExpression(b.memberExpression(b.identifier("console"), b.identifier("log")), [
|
|
110
|
+
b.identifier("x"),
|
|
111
|
+
]),
|
|
112
|
+
),
|
|
113
|
+
]);
|
|
202
114
|
|
|
203
|
-
```ts
|
|
204
|
-
import { b, walk, type Visitors } from "yuku-ast";
|
|
205
|
-
|
|
206
|
-
const stripDebugger: Visitors = {
|
|
207
|
-
DebuggerStatement: (_node, path) => path.remove(),
|
|
208
|
-
};
|
|
209
|
-
const inlineDevFlag: Visitors = {
|
|
210
|
-
Identifier(node, path) {
|
|
211
|
-
if (node.name === "__DEV__") path.replace(b.booleanLiteral(false));
|
|
212
|
-
},
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const { program } = parse("if (__DEV__) log(); debugger; run();");
|
|
216
|
-
for (const transform of [stripDebugger, inlineDevFlag]) {
|
|
217
|
-
walk(program, transform);
|
|
218
|
-
}
|
|
219
115
|
console.log(print(program).code);
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
Replacements built with `b.*` inherit the replaced node's source span, so
|
|
225
|
-
`yuku-codegen` source maps still point back to the original input.
|
|
226
|
-
|
|
227
|
-
## Async
|
|
228
|
-
|
|
229
|
-
`walkAsync` is the same walk with awaitable visitors, run sequentially in
|
|
230
|
-
depth-first order. Reach for it only when a visitor must await I/O; `walk` is
|
|
231
|
-
faster otherwise.
|
|
232
|
-
|
|
233
|
-
```ts
|
|
234
|
-
import { walkAsync } from "yuku-ast";
|
|
235
|
-
|
|
236
|
-
await walkAsync(program, {
|
|
237
|
-
async ImportDeclaration(node, path) {
|
|
238
|
-
if (!(await exists(node.source.value))) path.remove();
|
|
239
|
-
},
|
|
240
|
-
});
|
|
116
|
+
// const x = 42;
|
|
117
|
+
// console.log(x);
|
|
241
118
|
```
|
|
242
119
|
|
|
243
120
|
## Identifiers
|
|
@@ -264,13 +141,6 @@ isValidIdentifier("π"); // true
|
|
|
264
141
|
| `isStrictBindOnlyReservedWord(word)` | `eval` / `arguments` |
|
|
265
142
|
| `isStrictBindReservedWord(word, inModule?)` | strict reserved, plus `eval` / `arguments` |
|
|
266
143
|
|
|
267
|
-
## Performance
|
|
268
|
-
|
|
269
|
-
The walk reads a fixed table of child fields per node type rather than reflecting
|
|
270
|
-
over keys, and dispatches through a cached, per-type handler list. On a large
|
|
271
|
-
TypeScript file it walks several times faster than `@babel/traverse` and on par
|
|
272
|
-
with the lightest reflection walkers, while staying fully typed.
|
|
273
|
-
|
|
274
144
|
## License
|
|
275
145
|
|
|
276
146
|
MIT
|
package/dist/identifier.d.mts
CHANGED
package/dist/identifier.mjs
CHANGED