yuku-analyzer 0.6.5 → 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 +26 -135
- package/decode.js +5 -188
- package/index.d.ts +61 -8
- package/index.js +8 -1
- package/module.js +17 -4
- package/package.json +14 -14
- package/walk.js +5 -1
- package/engine.js +0 -184
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
+
const analyzer = new Analyzer();
|
|
145
38
|
|
|
146
|
-
|
|
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
|
-
|
|
42
|
+
const helper = main.rootScope.find("helper");
|
|
43
|
+
helper.has(SymbolFlags.Import); // true
|
|
149
44
|
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
235
|
-
TypeSpace:
|
|
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
|
|
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
|
-
|
|
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";
|
|
@@ -354,13 +354,22 @@ export class Module {
|
|
|
354
354
|
return parent < 0 ? null : this.#r.nodeOf(parent);
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
//
|
|
358
|
-
//
|
|
359
|
-
//
|
|
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
|
|
360
360
|
resolve(name, from = this.rootScope, space = "value") {
|
|
361
|
+
const argumentsBarrier =
|
|
362
|
+
name === "arguments" && (space === "value" || space === "typeof");
|
|
361
363
|
for (let s = from; s; s = s.parent) {
|
|
362
364
|
const found = s.find(name);
|
|
363
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
|
+
}
|
|
364
373
|
}
|
|
365
374
|
return null;
|
|
366
375
|
}
|
|
@@ -421,6 +430,10 @@ export class Module {
|
|
|
421
430
|
walkModule(this, visitor, root);
|
|
422
431
|
}
|
|
423
432
|
|
|
433
|
+
walkAsync(visitor, root) {
|
|
434
|
+
return walkModuleAsync(this, visitor, root);
|
|
435
|
+
}
|
|
436
|
+
|
|
424
437
|
findAll(types) {
|
|
425
438
|
const single = typeof types === "string" ? types : null;
|
|
426
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.
|
|
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.
|
|
25
|
-
"@yuku-analyzer/binding-linux-arm64-gnu": "0.6.
|
|
26
|
-
"@yuku-analyzer/binding-linux-arm-gnu": "0.6.
|
|
27
|
-
"@yuku-analyzer/binding-linux-x64-musl": "0.6.
|
|
28
|
-
"@yuku-analyzer/binding-linux-arm64-musl": "0.6.
|
|
29
|
-
"@yuku-analyzer/binding-linux-arm-musl": "0.6.
|
|
30
|
-
"@yuku-analyzer/binding-darwin-x64": "0.6.
|
|
31
|
-
"@yuku-analyzer/binding-darwin-arm64": "0.6.
|
|
32
|
-
"@yuku-analyzer/binding-win32-x64": "0.6.
|
|
33
|
-
"@yuku-analyzer/binding-win32-arm64": "0.6.
|
|
34
|
-
"@yuku-analyzer/binding-freebsd-x64": "0.6.
|
|
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
|
|
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 "
|
|
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
|
-
}
|