yuku-analyzer 0.6.4 → 0.6.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 CHANGED
@@ -6,170 +6,61 @@ Full semantic analysis for JavaScript and TypeScript: scopes, symbols, resolved
6
6
 
7
7
  **At native speed.** Up to ~15× faster per file than `eslint-scope`, `@typescript-eslint/scope-manager`, and `@babel/traverse`, with zero per-query cost after the single native call. Stitch those separate tools together yourself and the gap only widens: each re-walks the AST, you re-parse to resolve across files, and you keep the indexes between them in sync by hand. `yuku-analyzer` pays all of that once, in Zig.
8
8
 
9
+ ## Install
10
+
9
11
  ```bash
10
12
  npm install yuku-analyzer
11
13
  ```
12
14
 
13
15
  ## Quick start
14
16
 
15
- ```js
16
- import { Analyzer, SymbolFlags } from "yuku-analyzer";
17
-
18
- const analyzer = new Analyzer();
19
-
20
- analyzer.addFile("lib.ts", `export const helper = (x: number) => x * 2;`);
21
- const main = analyzer.addFile(
22
- "main.ts",
23
- `import { helper } from "./lib.ts";
24
- export const out = helper(21);`,
25
- );
26
-
27
- // per-file semantics
28
- const helperSym = main.rootScope.find("helper");
29
- console.log(helperSym.has(SymbolFlags.Import)); // true
30
- console.log(helperSym.references.length); // 1, the call site
31
-
32
- // cross-file: follow the import to where helper is actually defined
33
- const def = helperSym.definition();
34
- console.log(def.module.path); // "lib.ts"
35
- console.log(def.symbol.has(SymbolFlags.Const)); // true
36
- ```
37
-
38
- ## How it works
39
-
40
- Nothing semantic is reimplemented in JavaScript. The binder, scope tree, reference resolution, and module records are computed by the same well-tested native analyzer that powers the rest of Yuku, then shipped to JavaScript as one compact buffer that the JS side only decodes, through lazy zero-copy views. That handoff is usually where native tooling stalls: either every query pays an FFI round trip, or the semantics get a hand-written JS twin that slowly drifts. Here there is one implementation and one crossing, so it cannot drift, and the corner cases arrive already correct: catch-clause scope sharing, named function expression scopes, `var` hoist targets, TS declaration merging, space-aware resolution (a `const T` never captures a type-position `T` away from an outer `type T`), and write detection through destructuring patterns.
41
-
42
- ## What one `addFile` gives you
17
+ For one file, `analyze` returns the full per-file semantics in a call:
43
18
 
44
19
  ```js
45
- const module = analyzer.addFile("app.tsx", source);
46
-
47
- module.ast // ESTree / TS-ESTree program
48
- module.scopes // every lexical scope, as a tree
49
- module.symbols // every declared binding
50
- module.references // every identifier use, resolved to its symbol
51
- module.unresolvedReferences // free names and globals
52
- module.imports // import records, dynamic import() and require() included
53
- module.exports // spec-true export records
54
- module.moduleFlags // CommonJS classification signals
55
- module.diagnostics // syntax + semantic errors
56
- ```
57
-
58
- And node-level queries that work directly on AST nodes:
20
+ import { analyze } from "yuku-analyzer";
59
21
 
60
- ```js
61
- module.symbolOf(node) // the symbol a node declares or references
62
- module.referenceOf(node) // the reference recorded for an identifier
63
- module.scopeOf(node) // the innermost scope containing the node
64
- module.parentOf(node) // the node that structurally contains it, or null
65
- module.resolve("name") // scope-chain lookup, like the engine at runtime
66
- module.resolve("T", scope, "type") // or in another declaration space
67
- ```
68
-
69
- Every reference carries the declaration space its position resolves in (`ref.space`: `"value"`, `"type"`, `"namespace"`, `"typeof"`, or `"any"`), and resolution is space-aware the way TypeScript's is: an inner `const T` does not capture a type annotation's `T` away from an outer `type T`.
70
-
71
- Node identity is exact: the node you reach by walking `module.ast` and the node a semantic query returns are the same JavaScript object, so `===` always works.
72
-
73
- ## Editing the AST
74
-
75
- That identity is the payoff. Because the `node` on a symbol or reference _is_ the AST node, a refactor is a plain assignment, and [`yuku-codegen`](https://www.npmjs.com/package/yuku-codegen) prints the mutated tree back to source. No visitor to register, no separate model to translate back.
76
-
77
- ```js
78
- import { print } from "yuku-codegen";
79
-
80
- const m = analyzer.addFile("util.ts", `const tmp = load();\nexport const data = tmp.value + tmp.size;`);
81
- const tmp = m.rootScope.find("tmp");
82
- tmp.declarations[0] === m.ast.body[0].declarations[0].id; // true, literally the same node
83
-
84
- tmp.declarations[0].name = "raw"; // rename the binding
85
- for (const ref of tmp.references) ref.node.name = "raw"; // and every resolved use
86
-
87
- // across files, analyzer.referencesOf(symbol) hands back these same live nodes for every use
88
- print(m.ast).code;
89
- // const raw = load();
90
- // export const data = raw.value + raw.size;
91
- ```
22
+ const module = analyze(`const double = (n: number) => n * 2; double(21);`, { lang: "ts" });
92
23
 
93
- The uses come from resolved references, not a name search, so a shadowing inner `tmp` is left untouched: the rename only reaches the identifiers that actually bind to this symbol.
94
-
95
- ## Walking with semantic context
96
-
97
- `module.walk` is a typed visitor walk where every handler also receives the current scope, symbol, and reference. No manual scope tracking, ever:
98
-
99
- ```js
24
+ module.rootScope.find("double").references.length; // 1
100
25
  module.walk({
101
26
  Identifier(node, ctx) {
102
- if (ctx.reference?.isWrite && ctx.symbol?.has(SymbolFlags.Import)) {
103
- console.log(`${node.name} assigns to an import`);
104
- }
105
- },
106
- FunctionDeclaration: {
107
- enter(node, ctx) {
108
- console.log(node.id.name, "declared in a", ctx.scope.kind, "scope");
109
- },
27
+ console.log(node.name, ctx.scope.kind, ctx.symbol, ctx.reference);
110
28
  },
111
29
  });
112
30
  ```
113
31
 
114
- The walk mutates in place: `ctx.replace(node)`, `ctx.remove()`, `ctx.insertBefore(node)`, `ctx.insertAfter(node)`, plus `ctx.skip()` and `ctx.stop()`. Transform the AST during the walk and print it with [`yuku-codegen`](https://www.npmjs.com/package/yuku-codegen).
115
-
116
- ## Closure analysis
117
-
118
- `capturesOf` reports the free variables of any function: every outer binding it closes over, with the capturing reference sites and whether the function writes to the binding. Shadowing and aliasing are handled by the resolved reference table, not by name matching:
119
-
120
- ```js
121
- const [fn] = main.findAll("FunctionDeclaration");
122
- for (const capture of main.capturesOf(fn)) {
123
- console.log(capture.symbol.name, capture.isWritten ? "(written)" : "");
124
- }
125
- ```
126
-
127
- ## Cross-file analysis
128
-
129
- The analyzer joins imports to exports across every added file with the spec's ResolveExport semantics: re-export and `export *` chains are followed per name, `default` never travels through `export *`, and a name supplied by multiple `export *` declarations through different bindings is reported as ambiguous.
32
+ For a project, `Analyzer` adds files and links them:
130
33
 
131
34
  ```js
132
- // where is this binding actually defined?
133
- analyzer.definitionOf(symbol); // { module, symbol } or null for external modules
134
-
135
- // every use across the whole graph, imports followed back
136
- analyzer.referencesOf(symbol); // [{ module, reference }, ...]
137
-
138
- module.exportedNames(); // every exported name, `export *` chains included
139
- module.dependencies; // modules this file imports from
140
- module.dependents; // modules that import this file
141
- analyzer.diagnostics; // e.g. "Module './lib.ts' has no export 'helpr'"
142
- ```
35
+ import { Analyzer, SymbolFlags } from "yuku-analyzer";
143
36
 
144
- Linking is automatic: every cross-file surface relinks on demand after files change. Re-adding a path replaces its module with a new object and relinks, and removing it relinks too. Call `analyzer.link()` explicitly if you want to control when the work happens.
37
+ const analyzer = new Analyzer();
145
38
 
146
- Module records and linking model ECMAScript and TypeScript module syntax, so CommonJS `require` and `module.exports` are ordinary code rather than records and take no part in linking. Per-file scopes, symbols, and references are still computed for CommonJS sources.
39
+ analyzer.addFile("lib.ts", `export const helper = (x: number) => x * 2;`);
40
+ const main = analyzer.addFile("main.ts", `import { helper } from "./lib.ts"; helper(21);`);
147
41
 
148
- Module resolution is pluggable. The default resolves relative specifiers among added files with standard extension and index probing. Pass your own resolver for anything else:
42
+ const helper = main.rootScope.find("helper");
43
+ helper.has(SymbolFlags.Import); // true
149
44
 
150
- ```js
151
- const analyzer = new Analyzer({
152
- resolve: (specifier, importerPath) => myResolver(specifier, importerPath),
153
- });
45
+ const def = helper.definition(); // follow the import across files
46
+ def.module.path; // "lib.ts"
154
47
  ```
155
48
 
156
- ## Performance
157
-
158
- Analysis runs in the native parser pass, so full semantics cost roughly half of parsing time on top of the parse itself. Validated against every file in the [parser test corpus](https://yuku.fyi/testing/).
49
+ ## What you get
159
50
 
160
- Concretely, on an Apple M-series machine: parsing plus complete semantic analysis of a typical source file lands well under a millisecond, walking sustains tens of millions of nodes per second, and linking a 2,000-module graph takes about a millisecond.
51
+ - **Scopes** as a tree, with `var` hoist targets, catch-clause sharing, and named-expression scopes exact.
52
+ - **Symbols** with TypeScript declaration merging, queried through one `SymbolFlags` bitset.
53
+ - **References** resolved and space-aware, so a value never captures a same-named type.
54
+ - **Node queries** on object identity: `symbolOf`, `referenceOf`, `scopeOf`, `parentOf`, `resolve`.
55
+ - **A semantic walk** and `walkAsync`, every handler receiving the current scope, symbol, and reference.
56
+ - **Closure analysis** via `capturesOf`, shadowing- and alias-correct.
57
+ - **Cross-file linking**: `definition()`, `referencesOf`, `exportedNames`, and spec-true `ResolveExport` across the graph.
161
58
 
162
- ## TypeScript
163
-
164
- Everything is fully typed. Visitor handlers receive exact node types, and the semantic surface (`Module`, `Scope`, `Symbol`, `Reference`, `Import`, `Export`, `Capture`) is exported:
165
-
166
- ```ts
167
- import type { Module, Symbol, Capture } from "yuku-analyzer";
168
- ```
59
+ Node identity is exact throughout: the node a semantic query returns is the same object you reach by walking `module.ast`, so a rename is a plain assignment and [`yuku-codegen`](https://www.npmjs.com/package/yuku-codegen) prints it back.
169
60
 
170
61
  ## Documentation
171
62
 
172
- The full documentation, including the architecture, every type, and the design decisions: [yuku.fyi/analyzer](https://yuku.fyi/analyzer).
63
+ The full guide, with the architecture, every API, and the design decisions: **[yuku.fyi/analyzer](https://yuku.fyi/analyzer)**.
173
64
 
174
65
  ## License
175
66
 
package/decode.js CHANGED
@@ -19,191 +19,7 @@ const TS_METHOD_SIGNATURE_KINDS = ["method", "get", "set"];
19
19
  const TS_MODULE_KINDS = ["namespace", "module"];
20
20
  const TS_MAPPED_OPTIONAL = [false, true, "+", "-"];
21
21
  const TS_MAPPED_READONLY = [null, true, "+", "-"];
22
- const CHILD_KEYS = Object.create(null);
23
- function _ck(type, keys) {
24
- const prev = CHILD_KEYS[type];
25
- if (prev === undefined) CHILD_KEYS[type] = keys;
26
- else for (const k of keys) if (!prev.includes(k)) prev.push(k);
27
- }
28
- _ck("SequenceExpression", ["expressions"]);
29
- _ck("ParenthesizedExpression", ["expression"]);
30
- _ck("ArrowFunctionExpression", ["typeParameters", "params", "returnType", "body"]);
31
- _ck("FunctionDeclaration", ["id", "typeParameters", "params", "returnType", "body"]);
32
- _ck("FunctionExpression", ["id", "typeParameters", "params", "returnType", "body"]);
33
- _ck("TSDeclareFunction", ["id", "typeParameters", "params", "returnType", "body"]);
34
- _ck("TSEmptyBodyFunctionExpression", ["id", "typeParameters", "params", "returnType", "body"]);
35
- _ck("BlockStatement", ["body"]);
36
- _ck("BlockStatement", ["body"]);
37
- _ck("BinaryExpression", ["left", "right"]);
38
- _ck("LogicalExpression", ["left", "right"]);
39
- _ck("ConditionalExpression", ["test", "consequent", "alternate"]);
40
- _ck("UnaryExpression", ["argument"]);
41
- _ck("UpdateExpression", ["argument"]);
42
- _ck("AssignmentExpression", ["left", "right"]);
43
- _ck("ArrayExpression", ["elements"]);
44
- _ck("ObjectExpression", ["properties"]);
45
- _ck("SpreadElement", ["argument"]);
46
- _ck("Property", ["key", "value"]);
47
- _ck("MemberExpression", ["object", "property"]);
48
- _ck("CallExpression", ["callee", "typeArguments", "arguments"]);
49
- _ck("ChainExpression", ["expression"]);
50
- _ck("TaggedTemplateExpression", ["tag", "typeArguments", "quasi"]);
51
- _ck("NewExpression", ["callee", "typeArguments", "arguments"]);
52
- _ck("AwaitExpression", ["argument"]);
53
- _ck("YieldExpression", ["argument"]);
54
- _ck("MetaProperty", ["meta", "property"]);
55
- _ck("Decorator", ["expression"]);
56
- _ck("ClassDeclaration", ["decorators", "id", "typeParameters", "superClass", "superTypeArguments", "implements", "body"]);
57
- _ck("ClassExpression", ["decorators", "id", "typeParameters", "superClass", "superTypeArguments", "implements", "body"]);
58
- _ck("ClassBody", ["body"]);
59
- _ck("MethodDefinition", ["decorators", "key", "value"]);
60
- _ck("TSAbstractMethodDefinition", ["decorators", "key", "value"]);
61
- _ck("PropertyDefinition", ["decorators", "key", "typeAnnotation", "value"]);
62
- _ck("AccessorProperty", ["decorators", "key", "typeAnnotation", "value"]);
63
- _ck("TSAbstractPropertyDefinition", ["decorators", "key", "typeAnnotation", "value"]);
64
- _ck("TSAbstractAccessorProperty", ["decorators", "key", "typeAnnotation", "value"]);
65
- _ck("StaticBlock", ["body"]);
66
- _ck("Super", []);
67
- _ck("Literal", []);
68
- _ck("Literal", []);
69
- _ck("Literal", []);
70
- _ck("Literal", []);
71
- _ck("Literal", []);
72
- _ck("ThisExpression", []);
73
- _ck("Literal", []);
74
- _ck("TemplateLiteral", ["quasis", "expressions"]);
75
- _ck("TemplateElement", []);
76
- _ck("Identifier", []);
77
- _ck("PrivateIdentifier", []);
78
- _ck("Identifier", ["decorators", "typeAnnotation"]);
79
- _ck("Identifier", []);
80
- _ck("Identifier", []);
81
- _ck("ExpressionStatement", ["expression"]);
82
- _ck("IfStatement", ["test", "consequent", "alternate"]);
83
- _ck("SwitchStatement", ["discriminant", "cases"]);
84
- _ck("SwitchCase", ["test", "consequent"]);
85
- _ck("ForStatement", ["init", "test", "update", "body"]);
86
- _ck("ForInStatement", ["left", "right", "body"]);
87
- _ck("ForOfStatement", ["left", "right", "body"]);
88
- _ck("WhileStatement", ["test", "body"]);
89
- _ck("DoWhileStatement", ["body", "test"]);
90
- _ck("BreakStatement", ["label"]);
91
- _ck("ContinueStatement", ["label"]);
92
- _ck("LabeledStatement", ["label", "body"]);
93
- _ck("WithStatement", ["object", "body"]);
94
- _ck("ReturnStatement", ["argument"]);
95
- _ck("ThrowStatement", ["argument"]);
96
- _ck("TryStatement", ["block", "handler", "finalizer"]);
97
- _ck("CatchClause", ["param", "body"]);
98
- _ck("DebuggerStatement", []);
99
- _ck("EmptyStatement", []);
100
- _ck("VariableDeclaration", ["declarations"]);
101
- _ck("VariableDeclarator", ["id", "init"]);
102
- _ck("ExpressionStatement", ["expression"]);
103
- _ck("AssignmentPattern", ["decorators", "left", "typeAnnotation", "right"]);
104
- _ck("RestElement", ["decorators", "argument", "typeAnnotation"]);
105
- _ck("ArrayPattern", ["decorators", "elements", "typeAnnotation"]);
106
- _ck("ObjectPattern", ["decorators", "properties", "typeAnnotation"]);
107
- _ck("Property", ["key", "value"]);
108
- _ck("Program", ["hashbang", "body"]);
109
- _ck("ImportExpression", ["source", "options"]);
110
- _ck("ImportDeclaration", ["specifiers", "source", "attributes"]);
111
- _ck("ImportSpecifier", ["imported", "local"]);
112
- _ck("ImportDefaultSpecifier", ["local"]);
113
- _ck("ImportNamespaceSpecifier", ["local"]);
114
- _ck("ImportAttribute", ["key", "value"]);
115
- _ck("ExportNamedDeclaration", ["declaration", "specifiers", "source", "attributes"]);
116
- _ck("ExportDefaultDeclaration", ["declaration"]);
117
- _ck("ExportAllDeclaration", ["exported", "source", "attributes"]);
118
- _ck("ExportSpecifier", ["local", "exported"]);
119
- _ck("TSTypeAnnotation", ["typeAnnotation"]);
120
- _ck("TSAnyKeyword", []);
121
- _ck("TSUnknownKeyword", []);
122
- _ck("TSNeverKeyword", []);
123
- _ck("TSVoidKeyword", []);
124
- _ck("TSNullKeyword", []);
125
- _ck("TSUndefinedKeyword", []);
126
- _ck("TSStringKeyword", []);
127
- _ck("TSNumberKeyword", []);
128
- _ck("TSBigIntKeyword", []);
129
- _ck("TSBooleanKeyword", []);
130
- _ck("TSSymbolKeyword", []);
131
- _ck("TSObjectKeyword", []);
132
- _ck("TSIntrinsicKeyword", []);
133
- _ck("TSThisType", []);
134
- _ck("TSTypeReference", ["typeName", "typeArguments"]);
135
- _ck("TSQualifiedName", ["left", "right"]);
136
- _ck("TSTypeQuery", ["exprName", "typeArguments"]);
137
- _ck("TSImportType", ["source", "options", "qualifier", "typeArguments"]);
138
- _ck("TSTypeParameter", ["name", "constraint", "default"]);
139
- _ck("TSTypeParameterDeclaration", ["params"]);
140
- _ck("TSTypeParameterInstantiation", ["params"]);
141
- _ck("TSLiteralType", ["literal"]);
142
- _ck("TSTemplateLiteralType", ["quasis", "types"]);
143
- _ck("TSArrayType", ["elementType"]);
144
- _ck("TSIndexedAccessType", ["objectType", "indexType"]);
145
- _ck("TSTupleType", ["elementTypes"]);
146
- _ck("TSNamedTupleMember", ["label", "elementType"]);
147
- _ck("TSOptionalType", ["typeAnnotation"]);
148
- _ck("TSRestType", ["typeAnnotation"]);
149
- _ck("TSJSDocNullableType", ["typeAnnotation"]);
150
- _ck("TSJSDocNonNullableType", ["typeAnnotation"]);
151
- _ck("TSJSDocUnknownType", []);
152
- _ck("TSUnionType", ["types"]);
153
- _ck("TSIntersectionType", ["types"]);
154
- _ck("TSConditionalType", ["checkType", "extendsType", "trueType", "falseType"]);
155
- _ck("TSInferType", ["typeParameter"]);
156
- _ck("TSTypeOperator", ["typeAnnotation"]);
157
- _ck("TSParenthesizedType", ["typeAnnotation"]);
158
- _ck("TSFunctionType", ["typeParameters", "params", "returnType"]);
159
- _ck("TSConstructorType", ["typeParameters", "params", "returnType"]);
160
- _ck("TSTypePredicate", ["parameterName", "typeAnnotation"]);
161
- _ck("TSTypeLiteral", ["members"]);
162
- _ck("TSMappedType", ["key", "constraint", "nameType", "typeAnnotation"]);
163
- _ck("TSPropertySignature", ["key", "typeAnnotation"]);
164
- _ck("TSMethodSignature", ["key", "typeParameters", "params", "returnType"]);
165
- _ck("TSCallSignatureDeclaration", ["typeParameters", "params", "returnType"]);
166
- _ck("TSConstructSignatureDeclaration", ["typeParameters", "params", "returnType"]);
167
- _ck("TSIndexSignature", ["parameters", "typeAnnotation"]);
168
- _ck("TSTypeAliasDeclaration", ["id", "typeParameters", "typeAnnotation"]);
169
- _ck("TSInterfaceDeclaration", ["id", "typeParameters", "extends", "body"]);
170
- _ck("TSInterfaceBody", ["body"]);
171
- _ck("TSInterfaceHeritage", ["expression", "typeArguments"]);
172
- _ck("TSClassImplements", ["expression", "typeArguments"]);
173
- _ck("TSEnumDeclaration", ["id", "body"]);
174
- _ck("TSEnumBody", ["members"]);
175
- _ck("TSEnumMember", ["id", "initializer"]);
176
- _ck("TSModuleDeclaration", ["id", "body"]);
177
- _ck("TSModuleBlock", ["body"]);
178
- _ck("TSModuleDeclaration", ["id", "body"]);
179
- _ck("TSParameterProperty", ["decorators", "parameter"]);
180
- _ck("Identifier", ["typeAnnotation"]);
181
- _ck("TSAsExpression", ["expression", "typeAnnotation"]);
182
- _ck("TSSatisfiesExpression", ["expression", "typeAnnotation"]);
183
- _ck("TSTypeAssertion", ["typeAnnotation", "expression"]);
184
- _ck("TSNonNullExpression", ["expression"]);
185
- _ck("TSInstantiationExpression", ["expression", "typeArguments"]);
186
- _ck("TSExportAssignment", ["expression"]);
187
- _ck("TSNamespaceExportDeclaration", ["id"]);
188
- _ck("TSImportEqualsDeclaration", ["id", "moduleReference"]);
189
- _ck("TSExternalModuleReference", ["expression"]);
190
- _ck("JSXElement", ["openingElement", "children", "closingElement"]);
191
- _ck("JSXOpeningElement", ["name", "typeArguments", "attributes"]);
192
- _ck("JSXClosingElement", ["name"]);
193
- _ck("JSXFragment", ["openingFragment", "children", "closingFragment"]);
194
- _ck("JSXOpeningFragment", []);
195
- _ck("JSXClosingFragment", []);
196
- _ck("JSXIdentifier", []);
197
- _ck("JSXNamespacedName", ["namespace", "name"]);
198
- _ck("JSXMemberExpression", ["object", "property"]);
199
- _ck("JSXAttribute", ["name", "value"]);
200
- _ck("JSXSpreadAttribute", ["argument"]);
201
- _ck("JSXExpressionContainer", ["expression"]);
202
- _ck("JSXEmptyExpression", []);
203
- _ck("JSXText", []);
204
- _ck("JSXSpreadChild", ["expression"]);
205
- _ck("Hashbang", []);
206
- const SCOPE_KINDS = ["global", "module", "function", "block", "class", "staticBlock", "expressionName", "tsModule"];
22
+ const SCOPE_KINDS = ["global", "module", "function", "block", "class", "staticBlock", "expressionName", "tsModule", "functionBody"];
207
23
  const IMPORT_PHASES = ["source", "defer"];
208
24
  const IMPORT_KINDS = ["named", "namespace", "sideEffect", "importEquals", "dynamic", "require"];
209
25
  const EXPORT_KINDS = ["named", "reExport", "namespace", "star", "equals", "global"];
@@ -229,10 +45,11 @@ const SymbolFlags = Object.freeze({
229
45
  CatchVariable: 1 << 16,
230
46
  Exported: 1 << 17,
231
47
  Default: 1 << 18,
48
+ EnumMember: 1 << 19,
232
49
  Variable: 3,
233
50
  Import: 6144,
234
- ValueSpace: 127,
235
- TypeSpace: 952,
51
+ ValueSpace: 524415,
52
+ TypeSpace: 525240,
236
53
  NamespaceSpace: 1136,
237
54
  });
238
55
  const CHILD_SLOTS = [
@@ -1376,4 +1193,4 @@ function decode(buffer, source) {
1376
1193
  get semantic() { return _semantic(); },
1377
1194
  };
1378
1195
  }
1379
- export { decode, SymbolFlags, CHILD_KEYS };
1196
+ export { decode, SymbolFlags };
package/index.d.ts CHANGED
@@ -1,7 +1,3 @@
1
- /**
2
- * yuku-analyzer: semantic analysis for JavaScript and TypeScript.
3
- */
4
-
5
1
  import type {
6
2
  Comment,
7
3
  Diagnostic,
@@ -114,14 +110,16 @@ declare const SymbolFlags: {
114
110
  readonly Exported: number;
115
111
  /** The default export. */
116
112
  readonly Default: number;
113
+ /** A TS enum member, declared in its enum's body scope. */
114
+ readonly EnumMember: number;
117
115
 
118
116
  /** Composite: any variable (`var` / `let` / `const`, params, catch). */
119
117
  readonly Variable: number;
120
118
  /** Composite: any import binding, value or `import type`. */
121
119
  readonly Import: number;
122
- /** Composite: visible at runtime (var, function, class, enum, value namespace). */
120
+ /** Composite: visible at runtime (var, function, class, enum and its members, value namespace). */
123
121
  readonly ValueSpace: number;
124
- /** Composite: referencable from a type position (class, enum, interface, alias, type param). */
122
+ /** Composite: referencable from a type position (class, enum and its members, interface, alias, type param). */
125
123
  readonly TypeSpace: number;
126
124
  /** Composite: what a dotted type name starts from (namespace, enum). */
127
125
  readonly NamespaceSpace: number;
@@ -151,7 +149,8 @@ type ScopeKind =
151
149
  | "class"
152
150
  | "staticBlock"
153
151
  | "expressionName"
154
- | "tsModule";
152
+ | "tsModule"
153
+ | "functionBody";
155
154
 
156
155
  /** A lexical scope in a module's scope tree. */
157
156
  interface Scope {
@@ -301,6 +300,23 @@ type Visitors = {
301
300
  leave?: WalkHandler;
302
301
  };
303
302
 
303
+ /** Handler for one node type in an async walk, free to return a promise. */
304
+ type AsyncWalkHandler<T extends Node = Node> = (node: T, ctx: WalkContext<T>) => void | Promise<void>;
305
+
306
+ /** Enter/leave pair for one node type in an async walk. */
307
+ interface AsyncWalkHooks<T extends Node = Node> {
308
+ enter?: AsyncWalkHandler<T>;
309
+ leave?: AsyncWalkHandler<T>;
310
+ }
311
+
312
+ /** {@link Visitors}, with handlers that may return promises. */
313
+ type AsyncVisitors = {
314
+ [K in NodeType]?: AsyncWalkHandler<NodeOfType<K>> | AsyncWalkHooks<NodeOfType<K>>;
315
+ } & {
316
+ enter?: AsyncWalkHandler;
317
+ leave?: AsyncWalkHandler;
318
+ };
319
+
304
320
  /** A free variable of a function, as reported by {@link Module.capturesOf}. */
305
321
  interface Capture {
306
322
  /** The outer binding being closed over. */
@@ -431,7 +447,9 @@ interface Module {
431
447
  * find the nearest binding of `name` visible in `space` (default:
432
448
  * `"value"`, resolving like runtime code). A binding outside the
433
449
  * space does not shadow, the walk keeps going. `"any"` matches by
434
- * name alone.
450
+ * name alone. A value-position `arguments` lookup stops at the
451
+ * first non-arrow function or static block, where the implicit
452
+ * arguments object shadows any outer binding of that name.
435
453
  */
436
454
  resolve(name: string, from?: Scope, space?: Space): Symbol | null;
437
455
  /**
@@ -463,6 +481,13 @@ interface Module {
463
481
  */
464
482
  walk(visitors: Visitors, root?: Node): void;
465
483
 
484
+ /**
485
+ * The async counterpart of {@link Module.walk}: identical traversal
486
+ * order and mutation semantics, with every handler awaited before
487
+ * the walk moves on.
488
+ */
489
+ walkAsync(visitors: AsyncVisitors, root?: Node): Promise<void>;
490
+
466
491
  /** Collects every node of the given type(s), in source order. */
467
492
  findAll<K extends NodeType>(type: K): NodeOfType<K>[];
468
493
  findAll<K extends NodeType>(types: Iterable<K>): NodeOfType<K>[];
@@ -493,6 +518,29 @@ interface ModuleReference {
493
518
  readonly reference: Reference;
494
519
  }
495
520
 
521
+ /** Options for {@link analyze}: {@link AddFileOptions} plus the module path. */
522
+ interface AnalyzeOptions extends AddFileOptions {
523
+ /**
524
+ * The path recorded on the module, also the default source of
525
+ * `lang` and `sourceType`.
526
+ * @default "input.js"
527
+ */
528
+ path?: string;
529
+ }
530
+
531
+ /**
532
+ * Parses and analyzes a single file, the shorthand for one-file
533
+ * semantics: scopes, symbols, resolved references, and a semantic
534
+ * walk. Cross-file surfaces stay empty, use {@link Analyzer} and
535
+ * {@link Analyzer.addFile} for multi-file projects and linking.
536
+ *
537
+ * ```ts
538
+ * const module = analyze(`const x = 1; x;`, { lang: "ts" });
539
+ * module.walk({ Identifier(node, ctx) { ctx.symbol; } });
540
+ * ```
541
+ */
542
+ declare function analyze(source: string, options?: AnalyzeOptions): Module;
543
+
496
544
  /**
497
545
  * The project: a set of analyzed modules and the links between them.
498
546
  *
@@ -559,12 +607,17 @@ declare function langFromPath(path: string): SourceLang;
559
607
  declare function sourceTypeFromPath(path: string): SourceType;
560
608
 
561
609
  export {
610
+ analyze,
562
611
  Analyzer,
563
612
  SymbolFlags,
564
613
  langFromPath,
565
614
  sourceTypeFromPath,
566
615
  type AddFileOptions,
616
+ type AnalyzeOptions,
567
617
  type AnalyzerOptions,
618
+ type AsyncVisitors,
619
+ type AsyncWalkHandler,
620
+ type AsyncWalkHooks,
568
621
  type Capture,
569
622
  type Definition,
570
623
  type Export,
package/index.js CHANGED
@@ -1,2 +1,9 @@
1
- export { Analyzer } from "./analyzer.js";
1
+ import { Analyzer } from "./analyzer.js";
2
+
3
+ export { Analyzer };
2
4
  export { SymbolFlags, langFromPath, sourceTypeFromPath } from "./module.js";
5
+
6
+ export function analyze(source, options = {}) {
7
+ const { path = "input.js", ...rest } = options;
8
+ return new Analyzer().addFile(path, source, rest);
9
+ }
package/module.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // one analyzed file, its AST plus a lazily built semantic graph
2
2
  import binding from "./binding.js";
3
3
  import { decode, SymbolFlags } from "./decode.js";
4
- import { walkModule } from "./walk.js";
4
+ import { walkModule, walkModuleAsync } from "./walk.js";
5
5
 
6
6
  export function langFromPath(path) {
7
7
  if (path.endsWith(".d.ts") || path.endsWith(".d.mts") || path.endsWith(".d.cts")) return "dts";
@@ -344,19 +344,32 @@ export class Module {
344
344
 
345
345
  // structural parent, or null at the root or for a foreign node
346
346
  parentOf(node) {
347
+ // the synthesized hashbang has no native index, its parent is the program
348
+ if (node?.type === "Hashbang") {
349
+ return node === this.ast.hashbang ? this.ast : null;
350
+ }
347
351
  const index = this.#r.indexOf(node);
348
352
  if (index === undefined) return null;
349
353
  const parent = this.#r.parentIndex(index);
350
354
  return parent < 0 ? null : this.#r.nodeOf(parent);
351
355
  }
352
356
 
353
- // the space filters what the name can see, exactly like reference
354
- // resolution: a binding outside the space does not shadow, the walk
355
- // keeps going. "any" matches by name alone
357
+ // mirrors reference resolution: a binding outside the space does not
358
+ // shadow, "any" matches by name alone, and a value-position arguments
359
+ // lookup stops where the implicit arguments object shadows
356
360
  resolve(name, from = this.rootScope, space = "value") {
361
+ const argumentsBarrier =
362
+ name === "arguments" && (space === "value" || space === "typeof");
357
363
  for (let s = from; s; s = s.parent) {
358
364
  const found = s.find(name);
359
365
  if (found && found.visibleIn(space)) return found;
366
+ if (
367
+ argumentsBarrier &&
368
+ (s.kind === "staticBlock" ||
369
+ (s.kind === "function" && s.node.type !== "ArrowFunctionExpression"))
370
+ ) {
371
+ return null;
372
+ }
360
373
  }
361
374
  return null;
362
375
  }
@@ -417,6 +430,10 @@ export class Module {
417
430
  walkModule(this, visitor, root);
418
431
  }
419
432
 
433
+ walkAsync(visitor, root) {
434
+ return walkModuleAsync(this, visitor, root);
435
+ }
436
+
420
437
  findAll(types) {
421
438
  const single = typeof types === "string" ? types : null;
422
439
  const set = single === null ? new Set(types) : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yuku-analyzer",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "description": "Full JavaScript and TypeScript semantic analysis: scopes, symbols, resolved references, closures, and cross-file module linking, computed natively in Zig and queried as plain JavaScript objects",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -16,22 +16,21 @@
16
16
  "analyzer.js",
17
17
  "module.js",
18
18
  "walk.js",
19
- "engine.js",
20
19
  "binding.js",
21
20
  "decode.js"
22
21
  ],
23
22
  "optionalDependencies": {
24
- "@yuku-analyzer/binding-linux-x64-gnu": "0.6.4",
25
- "@yuku-analyzer/binding-linux-arm64-gnu": "0.6.4",
26
- "@yuku-analyzer/binding-linux-arm-gnu": "0.6.4",
27
- "@yuku-analyzer/binding-linux-x64-musl": "0.6.4",
28
- "@yuku-analyzer/binding-linux-arm64-musl": "0.6.4",
29
- "@yuku-analyzer/binding-linux-arm-musl": "0.6.4",
30
- "@yuku-analyzer/binding-darwin-x64": "0.6.4",
31
- "@yuku-analyzer/binding-darwin-arm64": "0.6.4",
32
- "@yuku-analyzer/binding-win32-x64": "0.6.4",
33
- "@yuku-analyzer/binding-win32-arm64": "0.6.4",
34
- "@yuku-analyzer/binding-freebsd-x64": "0.6.4"
23
+ "@yuku-analyzer/binding-linux-x64-gnu": "0.6.6",
24
+ "@yuku-analyzer/binding-linux-arm64-gnu": "0.6.6",
25
+ "@yuku-analyzer/binding-linux-arm-gnu": "0.6.6",
26
+ "@yuku-analyzer/binding-linux-x64-musl": "0.6.6",
27
+ "@yuku-analyzer/binding-linux-arm64-musl": "0.6.6",
28
+ "@yuku-analyzer/binding-linux-arm-musl": "0.6.6",
29
+ "@yuku-analyzer/binding-darwin-x64": "0.6.6",
30
+ "@yuku-analyzer/binding-darwin-arm64": "0.6.6",
31
+ "@yuku-analyzer/binding-win32-x64": "0.6.6",
32
+ "@yuku-analyzer/binding-win32-arm64": "0.6.6",
33
+ "@yuku-analyzer/binding-freebsd-x64": "0.6.6"
35
34
  },
36
35
  "keywords": [
37
36
  "analyzer",
@@ -49,6 +48,7 @@
49
48
  "typescript"
50
49
  ],
51
50
  "dependencies": {
52
- "@yuku-toolchain/types": "0.5.43"
51
+ "@yuku-toolchain/types": "0.6.5",
52
+ "yuku-ast": "0.6.5"
53
53
  }
54
54
  }
package/walk.js CHANGED
@@ -1,4 +1,4 @@
1
- import { WalkContext, _walk } from "./engine.js";
1
+ import { WalkContext, _walk, _walkAsync } from "yuku-ast";
2
2
 
3
3
  class SemanticWalkContext extends WalkContext {
4
4
  #module;
@@ -23,3 +23,7 @@ class SemanticWalkContext extends WalkContext {
23
23
  export function walkModule(module, visitors, root) {
24
24
  _walk(root ?? module.ast, visitors, undefined, new SemanticWalkContext(module));
25
25
  }
26
+
27
+ export function walkModuleAsync(module, visitors, root) {
28
+ return _walkAsync(root ?? module.ast, visitors, undefined, new SemanticWalkContext(module));
29
+ }
package/engine.js DELETED
@@ -1,184 +0,0 @@
1
- import { CHILD_KEYS } from "./decode.js";
2
-
3
- export class WalkContext {
4
- _ancestors = [];
5
- _node = null;
6
- _key = null;
7
- _list = null;
8
- _frame = null;
9
- _skip = false;
10
- _stopped = false;
11
- _removed = false;
12
- _replacement = null;
13
- state;
14
-
15
- get node() {
16
- return this._node;
17
- }
18
- get parent() {
19
- const a = this._ancestors;
20
- return a.length === 0 ? null : a[a.length - 1];
21
- }
22
- get key() {
23
- return this._key;
24
- }
25
- get index() {
26
- return this._list === null ? null : this._frame.i;
27
- }
28
- ancestors() {
29
- return [...this._ancestors];
30
- }
31
- skip() {
32
- this._skip = true;
33
- }
34
- stop() {
35
- this._stopped = true;
36
- }
37
- replace(node) {
38
- if (node === null || typeof node !== "object" || typeof node.type !== "string") {
39
- throw new TypeError("replace: expected an AST node");
40
- }
41
- if (this.parent === null) {
42
- throw new TypeError("replace: cannot replace the walk root");
43
- }
44
- // synthetic nodes inherit the original span, for source maps
45
- if (node.start === 0 && node.end === 0) {
46
- node.start = this._node.start;
47
- node.end = this._node.end;
48
- }
49
- this._replacement = node;
50
- }
51
- remove() {
52
- if (this.parent === null) {
53
- throw new TypeError("remove: cannot remove the walk root");
54
- }
55
- this._removed = true;
56
- }
57
- insertBefore(node) {
58
- // advance past the insert so the current node keeps its turn
59
- this.#insert(node, 0);
60
- this._frame.i++;
61
- }
62
- insertAfter(node) {
63
- // lands at the next slot, so the walk visits it
64
- this.#insert(node, 1);
65
- }
66
- #insert(node, offset) {
67
- if (this._list === null) {
68
- throw new TypeError("insertBefore/insertAfter require a node in an array field");
69
- }
70
- this._list.splice(this._frame.i + offset, 0, node);
71
- }
72
- }
73
-
74
- function createDispatch(visitors) {
75
- let enter = null;
76
- let leave = null;
77
- const concrete = new Map();
78
- for (const [name, value] of Object.entries(visitors)) {
79
- if (value == null) continue;
80
- if (name === "enter") enter = value;
81
- else if (name === "leave") leave = value;
82
- else if (typeof value === "function") concrete.set(name, { enter: value, leave: null });
83
- else concrete.set(name, { enter: value.enter ?? null, leave: value.leave ?? null });
84
- }
85
- return { enter, leave, typed: (type) => concrete.get(type) };
86
- }
87
-
88
- export function walk(root, visitors, state) {
89
- _walk(root, visitors, state, new WalkContext());
90
- return root;
91
- }
92
-
93
- export function _walk(root, visitors, state, ctx) {
94
- const d = createDispatch(visitors);
95
- ctx.state = state;
96
- const ancestors = ctx._ancestors;
97
-
98
- function position(node, key, list, frame) {
99
- ctx._node = node;
100
- ctx._key = key;
101
- ctx._list = list;
102
- ctx._frame = frame;
103
- }
104
-
105
- function applyReplace(parent, key, list, frame) {
106
- const next = ctx._replacement;
107
- ctx._replacement = null;
108
- if (list !== null) list[frame.i] = next;
109
- else parent[key] = next;
110
- return next;
111
- }
112
-
113
- function applyRemove(parent, key, list, frame) {
114
- ctx._removed = false;
115
- if (list !== null) {
116
- list.splice(frame.i, 1);
117
- frame.i--;
118
- } else {
119
- parent[key] = null;
120
- }
121
- }
122
-
123
- (function visit(node, key, list, frame) {
124
- let typed = d.typed(node.type);
125
- const parent = ctx.parent;
126
-
127
- position(node, key, list, frame);
128
- if (d.enter !== null) {
129
- d.enter(node, ctx);
130
- if (ctx._stopped) return false;
131
- }
132
- if (typed !== undefined && typed.enter !== null) {
133
- typed.enter(node, ctx);
134
- if (ctx._stopped) return false;
135
- }
136
- if (ctx._removed) {
137
- applyRemove(parent, key, list, frame);
138
- return true;
139
- }
140
- if (ctx._replacement !== null) {
141
- node = applyReplace(parent, key, list, frame);
142
- typed = d.typed(node.type);
143
- }
144
-
145
- const skipped = ctx._skip;
146
- ctx._skip = false;
147
- if (!skipped) {
148
- const keys = CHILD_KEYS[node.type];
149
- if (keys !== undefined && keys.length > 0) {
150
- ancestors.push(node);
151
- for (let k = 0; k < keys.length; k++) {
152
- const key2 = keys[k];
153
- const value = node[key2];
154
- if (value === null || value === undefined || typeof value !== "object") continue;
155
- if (Array.isArray(value)) {
156
- const childFrame = { i: 0 };
157
- for (; childFrame.i < value.length; childFrame.i++) {
158
- const item = value[childFrame.i];
159
- // pattern element holes are null
160
- if (item !== null && !visit(item, key2, value, childFrame)) return false;
161
- }
162
- } else if (!visit(value, key2, null, null)) {
163
- return false;
164
- }
165
- }
166
- ancestors.pop();
167
- }
168
- }
169
-
170
- position(node, key, list, frame);
171
- if (typed !== undefined && typed.leave !== null) {
172
- typed.leave(node, ctx);
173
- if (ctx._stopped) return false;
174
- }
175
- if (d.leave !== null) {
176
- d.leave(node, ctx);
177
- if (ctx._stopped) return false;
178
- }
179
- if (ctx._removed) applyRemove(parent, key, list, frame);
180
- else if (ctx._replacement !== null) applyReplace(parent, key, list, frame);
181
-
182
- return true;
183
- })(root, null, null, null);
184
- }