yuku-analyzer 0.5.32 → 0.5.36
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 +1 -0
- package/decode.js +35 -1
- package/engine.js +2 -9
- package/index.d.ts +7 -1
- package/module.js +16 -55
- package/package.json +33 -33
- package/walk.js +3 -21
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ And node-level queries that work directly on AST nodes:
|
|
|
62
62
|
module.symbolOf(node) // the symbol a node declares or references
|
|
63
63
|
module.referenceOf(node) // the reference recorded for an identifier
|
|
64
64
|
module.scopeOf(node) // the innermost scope containing the node
|
|
65
|
+
module.parentOf(node) // the node that structurally contains it, or null
|
|
65
66
|
module.resolve("name") // scope-chain lookup, like the engine does at runtime
|
|
66
67
|
```
|
|
67
68
|
|
package/decode.js
CHANGED
|
@@ -1331,6 +1331,35 @@ function decode(buffer, source) {
|
|
|
1331
1331
|
}
|
|
1332
1332
|
})(progIdx);
|
|
1333
1333
|
}
|
|
1334
|
+
let _parentArr;
|
|
1335
|
+
function _parents() {
|
|
1336
|
+
if (_parentArr !== undefined) return _parentArr;
|
|
1337
|
+
const p = new Int32Array(nodeCount).fill(-1);
|
|
1338
|
+
(function visit(i, parent) {
|
|
1339
|
+
const o = _nodesOff + i * 48;
|
|
1340
|
+
const tag = _u8[o];
|
|
1341
|
+
if (TAG_TYPES[tag] !== null) { p[i] = parent; parent = i; }
|
|
1342
|
+
const ops = SCAN_CHILDREN[tag];
|
|
1343
|
+
const b = o >> 2;
|
|
1344
|
+
for (let q = 0; q < ops.length; q += 2) {
|
|
1345
|
+
const slot = ops[q + 1];
|
|
1346
|
+
if (ops[q] === 0) {
|
|
1347
|
+
const c = _u32[b + slot];
|
|
1348
|
+
if (c !== NULL) visit(c, parent);
|
|
1349
|
+
} else {
|
|
1350
|
+
const s = _u32[b + slot];
|
|
1351
|
+
const len = ops[q] === 1
|
|
1352
|
+
? _u32[b + slot + 1]
|
|
1353
|
+
: _u8[o + 4] | (_u8[o + 5] << 8);
|
|
1354
|
+
for (let j = 0; j < len; j++) {
|
|
1355
|
+
const c = _u32[_extraBase + s + j];
|
|
1356
|
+
if (c !== NULL) visit(c, parent);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
})(progIdx, -1);
|
|
1361
|
+
return (_parentArr = p);
|
|
1362
|
+
}
|
|
1334
1363
|
let _semView;
|
|
1335
1364
|
function _semantic() {
|
|
1336
1365
|
if (_semView !== undefined) return _semView;
|
|
@@ -1349,7 +1378,8 @@ function decode(buffer, source) {
|
|
|
1349
1378
|
let o = ((dp + 3) & ~3) >> 2;
|
|
1350
1379
|
const scopeCount = _u32[o], symbolCount = _u32[o + 1],
|
|
1351
1380
|
referenceCount = _u32[o + 2], declNodeCount = _u32[o + 3],
|
|
1352
|
-
importCount = _u32[o + 4], exportCount = _u32[o + 5]
|
|
1381
|
+
importCount = _u32[o + 4], exportCount = _u32[o + 5],
|
|
1382
|
+
nodeScopeCount = _u32[o + 6];
|
|
1353
1383
|
o += 8;
|
|
1354
1384
|
const scopes = _u32.subarray(o, o + scopeCount * 4);
|
|
1355
1385
|
o += scopeCount * 4;
|
|
@@ -1363,6 +1393,8 @@ function decode(buffer, source) {
|
|
|
1363
1393
|
o += importCount * 8;
|
|
1364
1394
|
const exports = _u32.subarray(o, o + exportCount * 10);
|
|
1365
1395
|
o += exportCount * 10;
|
|
1396
|
+
const nodeScopes = _u32.subarray(o, o + nodeScopeCount);
|
|
1397
|
+
o += nodeScopeCount;
|
|
1366
1398
|
const _id = (v) => v === NULL ? null : v;
|
|
1367
1399
|
return (_semView = {
|
|
1368
1400
|
scope: {
|
|
@@ -1421,6 +1453,7 @@ function decode(buffer, source) {
|
|
|
1421
1453
|
symbolId: (i) => _id(exports[i * 10 + 3]),
|
|
1422
1454
|
node: (i) => node(exports[i * 10 + 8]),
|
|
1423
1455
|
},
|
|
1456
|
+
nodeScope: (i) => nodeScopes[i],
|
|
1424
1457
|
});
|
|
1425
1458
|
}
|
|
1426
1459
|
let _program, _lineStarts, _diagnostics, _comments;
|
|
@@ -1455,6 +1488,7 @@ function decode(buffer, source) {
|
|
|
1455
1488
|
scan,
|
|
1456
1489
|
nodeOf: node,
|
|
1457
1490
|
indexOf: (n) => _nodeIndexes.get(n),
|
|
1491
|
+
parentIndex: (i) => _parents()[i],
|
|
1458
1492
|
startOf, endOf, str,
|
|
1459
1493
|
get semantic() { return _semantic(); },
|
|
1460
1494
|
};
|
package/engine.js
CHANGED
|
@@ -91,15 +91,11 @@ function createDispatch(visitors) {
|
|
|
91
91
|
* in place. Returns the root.
|
|
92
92
|
*/
|
|
93
93
|
export function walk(root, visitors, state) {
|
|
94
|
-
_walk(root, visitors, state,
|
|
94
|
+
_walk(root, visitors, state, new WalkContext());
|
|
95
95
|
return root;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
// (enter returns a token passed to exit, which runs after the node is
|
|
100
|
-
// handled, removal included) and a WalkContext subclass. exits are
|
|
101
|
-
// skipped once stopped.
|
|
102
|
-
export function _walk(root, visitors, state, hooks, ctx) {
|
|
98
|
+
export function _walk(root, visitors, state, ctx) {
|
|
103
99
|
const d = createDispatch(visitors);
|
|
104
100
|
ctx.state = state;
|
|
105
101
|
const ancestors = ctx._ancestors;
|
|
@@ -133,7 +129,6 @@ export function _walk(root, visitors, state, hooks, ctx) {
|
|
|
133
129
|
|
|
134
130
|
(function visit(node, key, list, frame) {
|
|
135
131
|
let typed = d.typed(node.type);
|
|
136
|
-
const token = hooks === null ? undefined : hooks.enter(node);
|
|
137
132
|
const parent = ctx.parent;
|
|
138
133
|
|
|
139
134
|
position(node, key, list, frame);
|
|
@@ -147,7 +142,6 @@ export function _walk(root, visitors, state, hooks, ctx) {
|
|
|
147
142
|
}
|
|
148
143
|
if (ctx._removed) {
|
|
149
144
|
applyRemove(parent, key, list, frame);
|
|
150
|
-
if (hooks !== null) hooks.exit(token);
|
|
151
145
|
return true;
|
|
152
146
|
}
|
|
153
147
|
if (ctx._replacement !== null) {
|
|
@@ -192,7 +186,6 @@ export function _walk(root, visitors, state, hooks, ctx) {
|
|
|
192
186
|
if (ctx._removed) applyRemove(parent, key, list, frame);
|
|
193
187
|
else if (ctx._replacement !== null) applyReplace(parent, key, list, frame);
|
|
194
188
|
|
|
195
|
-
if (hooks !== null) hooks.exit(token);
|
|
196
189
|
return true;
|
|
197
190
|
})(root, null, null, null);
|
|
198
191
|
}
|
package/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
SourceLocation,
|
|
18
18
|
SourceType,
|
|
19
19
|
WalkContext as BaseWalkContext,
|
|
20
|
-
} from "@yuku/types";
|
|
20
|
+
} from "@yuku-toolchain/types";
|
|
21
21
|
|
|
22
22
|
/** A diagnostic produced by {@link Analyzer.link}. */
|
|
23
23
|
interface LinkDiagnostic {
|
|
@@ -412,6 +412,12 @@ interface Module {
|
|
|
412
412
|
referenceOf(node: Node): Reference | null;
|
|
413
413
|
/** The innermost scope whose extent contains `node`. */
|
|
414
414
|
scopeOf(node: Node): Scope;
|
|
415
|
+
/**
|
|
416
|
+
* The node that structurally contains `node`. Null at the program
|
|
417
|
+
* root and for a node not part of this module's AST. Lets you walk
|
|
418
|
+
* upward from a node you already hold, with no ancestor stack.
|
|
419
|
+
*/
|
|
420
|
+
parentOf(node: Node): Node | null;
|
|
415
421
|
/**
|
|
416
422
|
* Walks the scope chain from `from` (default: the root scope) to
|
|
417
423
|
* find the nearest binding of `name`.
|
package/module.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// One analyzed file, the AST plus the scope/symbol/reference/import/
|
|
2
2
|
// export object graph, built lazily over the decoded analyzer buffer.
|
|
3
|
-
// All wire reads go through the generated `semantic` accessors from
|
|
4
|
-
// decode.js. Nothing in this file knows the buffer layout.
|
|
5
3
|
|
|
6
4
|
import binding from "./binding.js";
|
|
7
5
|
import { decode, SymbolFlags } from "./decode.js";
|
|
@@ -268,8 +266,6 @@ export class Module {
|
|
|
268
266
|
#symbolReferences = null;
|
|
269
267
|
#declToSymbol = null;
|
|
270
268
|
#nodeToReference = null;
|
|
271
|
-
#functionScopes = null;
|
|
272
|
-
#scopeMap = null;
|
|
273
269
|
#exportMap = null;
|
|
274
270
|
#starExports = null;
|
|
275
271
|
#importBySymbol = null;
|
|
@@ -355,22 +351,18 @@ export class Module {
|
|
|
355
351
|
|
|
356
352
|
scopeOf(node) {
|
|
357
353
|
const index = this.#r.indexOf(node);
|
|
354
|
+
// a node inserted after analysis has no recorded scope
|
|
358
355
|
if (index === undefined) return this.rootScope;
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
best = this.scopes[i];
|
|
370
|
-
bestDepth = depth;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return best;
|
|
356
|
+
return this.scopes[this.#sem.nodeScope(index)];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// the node that structurally contains `node`, or null at the program
|
|
360
|
+
// root or for a node not part of this module's AST.
|
|
361
|
+
parentOf(node) {
|
|
362
|
+
const index = this.#r.indexOf(node);
|
|
363
|
+
if (index === undefined) return null;
|
|
364
|
+
const parent = this.#r.parentIndex(index);
|
|
365
|
+
return parent < 0 ? null : this.#r.nodeOf(parent);
|
|
374
366
|
}
|
|
375
367
|
|
|
376
368
|
resolve(name, from = this.rootScope) {
|
|
@@ -403,9 +395,9 @@ export class Module {
|
|
|
403
395
|
// the free variables of a function: every binding referenced inside
|
|
404
396
|
// `fn` (nested functions included, value positions only) that is
|
|
405
397
|
// declared outside its scope subtree, deduplicated by symbol with
|
|
406
|
-
// `isWritten` OR-ed across the references. only bindings count
|
|
398
|
+
// `isWritten` OR-ed across the references. only bindings count.
|
|
407
399
|
// `this`, `arguments`, and unresolved/global names carry no symbol
|
|
408
|
-
// and never appear
|
|
400
|
+
// and never appear. module-scope and import bindings count like any
|
|
409
401
|
// other outer binding. computed method keys evaluate in the enclosing
|
|
410
402
|
// scope, outside the function node's span, so they are not captures.
|
|
411
403
|
capturesOf(fn) {
|
|
@@ -413,13 +405,13 @@ export class Module {
|
|
|
413
405
|
if (index === undefined) {
|
|
414
406
|
throw new TypeError("capturesOf: node does not belong to this module's AST");
|
|
415
407
|
}
|
|
416
|
-
const
|
|
417
|
-
|
|
408
|
+
const { scope, symbol, reference } = this.#sem;
|
|
409
|
+
const fnScopeId = this.#sem.nodeScope(index);
|
|
410
|
+
if (scope.kind(fnScopeId) !== "function" || scope.nodeIndex(fnScopeId) !== index) {
|
|
418
411
|
throw new TypeError("capturesOf: node does not create a function scope");
|
|
419
412
|
}
|
|
420
413
|
const start = this.#r.startOf(index);
|
|
421
414
|
const end = this.#r.endOf(index);
|
|
422
|
-
const { scope, symbol, reference } = this.#sem;
|
|
423
415
|
const captures = new Map();
|
|
424
416
|
for (let i = 0; i < reference.count; i++) {
|
|
425
417
|
const symbolId = reference.symbolId(i);
|
|
@@ -511,22 +503,6 @@ export class Module {
|
|
|
511
503
|
return this.#symbolReferences[symbolId];
|
|
512
504
|
}
|
|
513
505
|
|
|
514
|
-
// scope-creating node object -> innermost Scope (keep-last covers
|
|
515
|
-
// shared nodes, global/module on program, expression_name/function).
|
|
516
|
-
_scopeMap() {
|
|
517
|
-
if (this.#scopeMap === null) {
|
|
518
|
-
const byNode = new WeakMap();
|
|
519
|
-
const types = new Set();
|
|
520
|
-
for (const scope of this.scopes) {
|
|
521
|
-
const node = this.#sem.scope.node(scope.id);
|
|
522
|
-
byNode.set(node, scope);
|
|
523
|
-
types.add(node.type);
|
|
524
|
-
}
|
|
525
|
-
this.#scopeMap = { byNode, types };
|
|
526
|
-
}
|
|
527
|
-
return this.#scopeMap;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
506
|
// the spec's export-entry partition (ParseModule, 16.2.1.7.1): a
|
|
531
507
|
// name-keyed map of the local + indirect entries and the star-entry
|
|
532
508
|
// list (`export *` only). name-less records (`export *`, TS
|
|
@@ -591,21 +567,6 @@ export class Module {
|
|
|
591
567
|
}
|
|
592
568
|
return this.#nodeToReference;
|
|
593
569
|
}
|
|
594
|
-
|
|
595
|
-
// node index -> the function scope it creates. named function
|
|
596
|
-
// expressions share the node with their expression_name scope. the
|
|
597
|
-
// function scope has the higher id, so keep-last wins.
|
|
598
|
-
#functionScopeOf(nodeIndex) {
|
|
599
|
-
if (this.#functionScopes === null) {
|
|
600
|
-
const map = new Map();
|
|
601
|
-
const { scope } = this.#sem;
|
|
602
|
-
for (let i = 0; i < scope.count; i++) {
|
|
603
|
-
if (scope.kind(i) === "function") map.set(scope.nodeIndex(i), i);
|
|
604
|
-
}
|
|
605
|
-
this.#functionScopes = map;
|
|
606
|
-
}
|
|
607
|
-
return this.#functionScopes.get(nodeIndex);
|
|
608
|
-
}
|
|
609
570
|
}
|
|
610
571
|
|
|
611
572
|
export { SymbolFlags };
|
package/package.json
CHANGED
|
@@ -1,27 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yuku-analyzer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.36",
|
|
4
4
|
"description": "High-performance JavaScript/TypeScript semantic analyzer: per-file scopes, symbols, references, and cross-file module linking",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"analyzer",
|
|
7
|
-
"ast",
|
|
8
|
-
"bindings",
|
|
9
|
-
"compiler",
|
|
10
|
-
"cross-file",
|
|
11
|
-
"javascript",
|
|
12
|
-
"linker",
|
|
13
|
-
"module-graph",
|
|
14
|
-
"references",
|
|
15
|
-
"scope",
|
|
16
|
-
"semantic",
|
|
17
|
-
"symbols",
|
|
18
|
-
"typescript"
|
|
19
|
-
],
|
|
20
5
|
"license": "MIT",
|
|
21
6
|
"repository": {
|
|
22
7
|
"type": "git",
|
|
23
8
|
"url": "https://github.com/yuku-toolchain/yuku"
|
|
24
9
|
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "index.js",
|
|
12
|
+
"types": "index.d.ts",
|
|
25
13
|
"files": [
|
|
26
14
|
"index.js",
|
|
27
15
|
"index.d.ts",
|
|
@@ -32,23 +20,35 @@
|
|
|
32
20
|
"binding.js",
|
|
33
21
|
"decode.js"
|
|
34
22
|
],
|
|
35
|
-
"type": "module",
|
|
36
|
-
"main": "index.js",
|
|
37
|
-
"types": "index.d.ts",
|
|
38
|
-
"dependencies": {
|
|
39
|
-
"@yuku/types": "workspace:*"
|
|
40
|
-
},
|
|
41
23
|
"optionalDependencies": {
|
|
42
|
-
"@yuku-analyzer/binding-
|
|
43
|
-
"@yuku-analyzer/binding-
|
|
44
|
-
"@yuku-analyzer/binding-
|
|
45
|
-
"@yuku-analyzer/binding-linux-
|
|
46
|
-
"@yuku-analyzer/binding-linux-
|
|
47
|
-
"@yuku-analyzer/binding-linux-
|
|
48
|
-
"@yuku-analyzer/binding-
|
|
49
|
-
"@yuku-analyzer/binding-
|
|
50
|
-
"@yuku-analyzer/binding-
|
|
51
|
-
"@yuku-analyzer/binding-win32-arm64": "0.5.
|
|
52
|
-
"@yuku-analyzer/binding-
|
|
24
|
+
"@yuku-analyzer/binding-linux-x64-gnu": "0.5.36",
|
|
25
|
+
"@yuku-analyzer/binding-linux-arm64-gnu": "0.5.36",
|
|
26
|
+
"@yuku-analyzer/binding-linux-arm-gnu": "0.5.36",
|
|
27
|
+
"@yuku-analyzer/binding-linux-x64-musl": "0.5.36",
|
|
28
|
+
"@yuku-analyzer/binding-linux-arm64-musl": "0.5.36",
|
|
29
|
+
"@yuku-analyzer/binding-linux-arm-musl": "0.5.36",
|
|
30
|
+
"@yuku-analyzer/binding-darwin-x64": "0.5.36",
|
|
31
|
+
"@yuku-analyzer/binding-darwin-arm64": "0.5.36",
|
|
32
|
+
"@yuku-analyzer/binding-win32-x64": "0.5.36",
|
|
33
|
+
"@yuku-analyzer/binding-win32-arm64": "0.5.36",
|
|
34
|
+
"@yuku-analyzer/binding-freebsd-x64": "0.5.36"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"analyzer",
|
|
38
|
+
"ast",
|
|
39
|
+
"bindings",
|
|
40
|
+
"compiler",
|
|
41
|
+
"cross-file",
|
|
42
|
+
"javascript",
|
|
43
|
+
"linker",
|
|
44
|
+
"module-graph",
|
|
45
|
+
"references",
|
|
46
|
+
"scope",
|
|
47
|
+
"semantic",
|
|
48
|
+
"symbols",
|
|
49
|
+
"typescript"
|
|
50
|
+
],
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@yuku-toolchain/types": "0.5.32"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/walk.js
CHANGED
|
@@ -2,17 +2,15 @@ import { WalkContext, _walk } from "./engine.js";
|
|
|
2
2
|
|
|
3
3
|
class SemanticWalkContext extends WalkContext {
|
|
4
4
|
#module;
|
|
5
|
-
|
|
6
|
-
constructor(module, scopes) {
|
|
5
|
+
constructor(module) {
|
|
7
6
|
super();
|
|
8
7
|
this.#module = module;
|
|
9
|
-
this.#scopes = scopes;
|
|
10
8
|
}
|
|
11
9
|
get module() {
|
|
12
10
|
return this.#module;
|
|
13
11
|
}
|
|
14
12
|
get scope() {
|
|
15
|
-
return this.#
|
|
13
|
+
return this.#module.scopeOf(this._node);
|
|
16
14
|
}
|
|
17
15
|
get symbol() {
|
|
18
16
|
return this.#module.symbolOf(this._node);
|
|
@@ -23,21 +21,5 @@ class SemanticWalkContext extends WalkContext {
|
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
export function walkModule(module, visitors, root) {
|
|
26
|
-
|
|
27
|
-
const start = root ?? module.ast;
|
|
28
|
-
const scopes = [module.scopeOf(start)];
|
|
29
|
-
const hooks = {
|
|
30
|
-
// the scope was created for the original node. a replacement keeps
|
|
31
|
-
// the same lexical position, so push/pop stays balanced on it
|
|
32
|
-
enter(node) {
|
|
33
|
-
const scope = types.has(node.type) ? byNode.get(node) : undefined;
|
|
34
|
-
if (scope === undefined) return false;
|
|
35
|
-
scopes.push(scope);
|
|
36
|
-
return true;
|
|
37
|
-
},
|
|
38
|
-
exit(pushed) {
|
|
39
|
-
if (pushed) scopes.pop();
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
_walk(start, visitors, undefined, hooks, new SemanticWalkContext(module, scopes));
|
|
24
|
+
_walk(root ?? module.ast, visitors, undefined, new SemanticWalkContext(module));
|
|
43
25
|
}
|