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 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
@@ -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`. Both accept `null` or
132
- `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`.
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
- walk(program, {
146
- ExportSpecifier(node) {
147
- // `export { x as y }` and `export { x as "z" }` both resolve.
148
- nameOf(node.exported); // "y", "z"
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", "binding")], 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
- ## 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.
173
96
 
174
- Pair it with [`yuku-codegen`](https://www.npmjs.com/package/yuku-codegen) to parse,
175
- 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.
176
101
 
177
102
  ```ts
178
- import { parse } from "yuku-parser";
179
103
  import { print } from "yuku-codegen";
180
- import { is, b, walk } from "yuku-ast";
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
- `walk` edits the tree in place, so a transform is just a reusable visitor object,
202
- 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
+ ]);
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
- // if (false) log();
222
- // run();
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
@@ -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 `_`.
@@ -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
@@ -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;
@@ -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