yuku-ast 0.1.4 → 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 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, a walker, and identifier validators. No runtime dependencies.
4
+ node builders, type guards, and identifier validators.
5
5
 
6
- | Import | Provides |
7
- | --------------------- | ------------------------------------------------------------ |
8
- | `yuku-ast` | `walk`, `walkAsync`, `b` (node builders), `is` (type guards) |
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 { walk } from "yuku-ast";
14
+ import { b, is } from "yuku-ast";
15
15
 
16
- const { program } = parse("const greet = (name) => `hi ${name}`;");
16
+ const { program } = parse("const greet = 1;");
17
17
 
18
- walk(program, {
19
- Identifier(node) {
20
- console.log(node.name); // greet, name, name
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 yuku-parser
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`. Both accept `null` or
131
- `undefined`.
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
- walk(program, {
145
- ExportSpecifier(node) {
146
- // `export { x as y }` and `export { x as "z" }` both resolve.
147
- nameOf(node.exported); // "y", "z"
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", "binding")], 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
- ## Transform and print
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
- Pair it with [`yuku-codegen`](https://www.npmjs.com/package/yuku-codegen) to parse,
174
- rewrite, and print back to source.
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 { is, b, walk } from "yuku-ast";
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
- `walk` edits the tree in place, so a transform is just a reusable visitor object,
201
- and passes compose: run as many as you like, then print once.
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
- // if (false) log();
221
- // run();
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
@@ -1,4 +1,4 @@
1
- //#region src/identifier/index.d.ts
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 `_`.
@@ -1,4 +1,4 @@
1
- //#region src/identifier/index.ts
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;