yuku-ast 0.1.3 → 0.1.5
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 -182
- package/dist/identifier.d.mts +2 -5
- package/dist/identifier.mjs +2 -5
- package/dist/index.d.mts +223 -321
- package/dist/index.mjs +374 -883
- 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
|
|
@@ -113,23 +40,24 @@ is.CallExpression(node); // every concrete type
|
|
|
113
40
|
is.Expression(node); // families
|
|
114
41
|
is.StringLiteral(node); // Literal variants
|
|
115
42
|
is.StaticMemberExpression(node); // MemberExpression variants
|
|
116
|
-
is.BindingIdentifier(node); // Identifier roles, via `kind`
|
|
117
43
|
|
|
118
44
|
is.Identifier(node, "this"); // an Identifier with that exact name
|
|
119
45
|
is.oneOf(node, ["CallExpression", "NewExpression"]); // any of these types
|
|
120
|
-
|
|
121
|
-
walk(program, {
|
|
122
|
-
Literal(node, path) {
|
|
123
|
-
if (is.ArrayExpression(path.parent)) {
|
|
124
|
-
// path.parent is narrowed to ArrayExpression
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
});
|
|
128
46
|
```
|
|
129
47
|
|
|
130
48
|
`is.oneOf(node, types)` narrows to the union of the listed types, and
|
|
131
|
-
`is.Identifier(node, name?)` narrows to `Identifier`.
|
|
132
|
-
|
|
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`.
|
|
133
61
|
|
|
134
62
|
## Reading names (`nameOf`)
|
|
135
63
|
|
|
@@ -139,18 +67,13 @@ or `undefined`). It reads the common `Identifier | StringLiteral` slots, such as
|
|
|
139
67
|
a `ModuleExportName` or a static property or member key, in one call.
|
|
140
68
|
|
|
141
69
|
```ts
|
|
142
|
-
import { walk } from "yuku-ast";
|
|
143
70
|
import { nameOf } from "yuku-ast/utils";
|
|
144
71
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
Property(node) {
|
|
151
|
-
if (!node.computed) nameOf(node.key); // static key name, else null
|
|
152
|
-
},
|
|
153
|
-
});
|
|
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);
|
|
154
77
|
```
|
|
155
78
|
|
|
156
79
|
## Builders (`b`)
|
|
@@ -163,82 +86,35 @@ import { b } from "yuku-ast";
|
|
|
163
86
|
|
|
164
87
|
b.identifier("x");
|
|
165
88
|
b.callExpression(b.identifier("f"), [b.numericLiteral(1)]);
|
|
166
|
-
b.arrowFunctionExpression([b.identifier("a"
|
|
167
|
-
b.variableDeclaration("const", [
|
|
168
|
-
b.variableDeclarator(b.identifier("x", "binding"), b.numericLiteral(0)),
|
|
169
|
-
]);
|
|
89
|
+
b.arrowFunctionExpression([b.identifier("a")], b.identifier("a"));
|
|
90
|
+
b.variableDeclaration("const", [b.variableDeclarator(b.identifier("x"), b.numericLiteral(0))]);
|
|
170
91
|
```
|
|
171
92
|
|
|
172
|
-
|
|
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.
|
|
173
96
|
|
|
174
|
-
|
|
175
|
-
|
|
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.
|
|
176
101
|
|
|
177
102
|
```ts
|
|
178
|
-
import { parse } from "yuku-parser";
|
|
179
103
|
import { print } from "yuku-codegen";
|
|
180
|
-
import {
|
|
181
|
-
|
|
182
|
-
const { program } = parse("const x = 1; debugger; foo(x, 2);");
|
|
183
|
-
|
|
184
|
-
walk(program, {
|
|
185
|
-
DebuggerStatement(_node, path) {
|
|
186
|
-
path.remove();
|
|
187
|
-
},
|
|
188
|
-
Identifier(node, path) {
|
|
189
|
-
if (node.name === "foo") path.replace(b.identifier("bar"));
|
|
190
|
-
},
|
|
191
|
-
Literal(node, path) {
|
|
192
|
-
if (is.NumericLiteral(node)) path.replace(b.numericLiteral((node.value ?? 0) * 10));
|
|
193
|
-
},
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
console.log(print(program).code);
|
|
197
|
-
// const x = 10;
|
|
198
|
-
// bar(x, 20);
|
|
199
|
-
```
|
|
104
|
+
import { b } from "yuku-ast";
|
|
200
105
|
|
|
201
|
-
|
|
202
|
-
|
|
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
|
+
]);
|
|
203
114
|
|
|
204
|
-
```ts
|
|
205
|
-
import { b, walk, type Visitors } from "yuku-ast";
|
|
206
|
-
|
|
207
|
-
const stripDebugger: Visitors = {
|
|
208
|
-
DebuggerStatement: (_node, path) => path.remove(),
|
|
209
|
-
};
|
|
210
|
-
const inlineDevFlag: Visitors = {
|
|
211
|
-
Identifier(node, path) {
|
|
212
|
-
if (node.name === "__DEV__") path.replace(b.booleanLiteral(false));
|
|
213
|
-
},
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const { program } = parse("if (__DEV__) log(); debugger; run();");
|
|
217
|
-
for (const transform of [stripDebugger, inlineDevFlag]) {
|
|
218
|
-
walk(program, transform);
|
|
219
|
-
}
|
|
220
115
|
console.log(print(program).code);
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
Replacements built with `b.*` inherit the replaced node's source span, so
|
|
226
|
-
`yuku-codegen` source maps still point back to the original input.
|
|
227
|
-
|
|
228
|
-
## Async
|
|
229
|
-
|
|
230
|
-
`walkAsync` is the same walk with awaitable visitors, run sequentially in
|
|
231
|
-
depth-first order. Reach for it only when a visitor must await I/O; `walk` is
|
|
232
|
-
faster otherwise.
|
|
233
|
-
|
|
234
|
-
```ts
|
|
235
|
-
import { walkAsync } from "yuku-ast";
|
|
236
|
-
|
|
237
|
-
await walkAsync(program, {
|
|
238
|
-
async ImportDeclaration(node, path) {
|
|
239
|
-
if (!(await exists(node.source.value))) path.remove();
|
|
240
|
-
},
|
|
241
|
-
});
|
|
116
|
+
// const x = 42;
|
|
117
|
+
// console.log(x);
|
|
242
118
|
```
|
|
243
119
|
|
|
244
120
|
## Identifiers
|
|
@@ -265,13 +141,6 @@ isValidIdentifier("π"); // true
|
|
|
265
141
|
| `isStrictBindOnlyReservedWord(word)` | `eval` / `arguments` |
|
|
266
142
|
| `isStrictBindReservedWord(word, inModule?)` | strict reserved, plus `eval` / `arguments` |
|
|
267
143
|
|
|
268
|
-
## Performance
|
|
269
|
-
|
|
270
|
-
The walk reads a fixed table of child fields per node type rather than reflecting
|
|
271
|
-
over keys, and dispatches through a cached, per-type handler list. On a large
|
|
272
|
-
TypeScript file it walks several times faster than `@babel/traverse` and on par
|
|
273
|
-
with the lightest reflection walkers, while staying fully typed.
|
|
274
|
-
|
|
275
144
|
## License
|
|
276
145
|
|
|
277
146
|
MIT
|
package/dist/identifier.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/identifier
|
|
1
|
+
//#region src/identifier.d.ts
|
|
2
2
|
/**
|
|
3
3
|
* Returns `true` if the Unicode code point `cp` can **start** an identifier:
|
|
4
4
|
* any character with the Unicode `ID_Start` property, plus `$` and `_`.
|
|
@@ -19,10 +19,7 @@ declare function isIdentifierStart(cp: number): boolean;
|
|
|
19
19
|
declare function isIdentifierChar(cp: number): boolean;
|
|
20
20
|
/**
|
|
21
21
|
* Returns `true` if `name` is a valid ECMAScript `IdentifierName`: the grammar
|
|
22
|
-
* an identifier token must satisfy.
|
|
23
|
-
*
|
|
24
|
-
* Unlike the `is.IdentifierName` node guard, this validates a raw string rather
|
|
25
|
-
* than an AST node.
|
|
22
|
+
* an identifier token must satisfy. Validates a raw string, not an AST node.
|
|
26
23
|
*
|
|
27
24
|
* @example
|
|
28
25
|
* isIdentifierName("foo"); // true
|
package/dist/identifier.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/identifier
|
|
1
|
+
//#region src/identifier.ts
|
|
2
2
|
const ID_START = /^[$_\p{ID_Start}]$/u;
|
|
3
3
|
const ID_CONTINUE = /^[$\u200C\u200D\p{ID_Continue}]$/u;
|
|
4
4
|
const IDENTIFIER_NAME = /^[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*$/u;
|
|
@@ -29,10 +29,7 @@ function isIdentifierChar(cp) {
|
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
31
|
* Returns `true` if `name` is a valid ECMAScript `IdentifierName`: the grammar
|
|
32
|
-
* an identifier token must satisfy.
|
|
33
|
-
*
|
|
34
|
-
* Unlike the `is.IdentifierName` node guard, this validates a raw string rather
|
|
35
|
-
* than an AST node.
|
|
32
|
+
* an identifier token must satisfy. Validates a raw string, not an AST node.
|
|
36
33
|
*
|
|
37
34
|
* @example
|
|
38
35
|
* isIdentifierName("foo"); // true
|