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 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, null, new WalkContext());
94
+ _walk(root, visitors, state, new WalkContext());
95
95
  return root;
96
96
  }
97
97
 
98
- // internal entry point. yuku-analyzer layers scope replay via `hooks`
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
- const start = this.#r.startOf(index);
360
- const end = this.#r.endOf(index);
361
- const { scope } = this.#sem;
362
- let best = this.scopes[0];
363
- let bestDepth = 0;
364
- for (let i = 1; i < scope.count; i++) {
365
- if (scope.start(i) > start || scope.end(i) < end) continue;
366
- let depth = 0;
367
- for (let p = i; p !== null; p = scope.parentId(p)) depth++;
368
- if (depth > bestDepth) {
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; module-scope and import bindings count like any
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 fnScopeId = this.#functionScopeOf(index);
417
- if (fnScopeId === undefined) {
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.32",
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-darwin-arm64": "0.5.32",
43
- "@yuku-analyzer/binding-darwin-x64": "0.5.32",
44
- "@yuku-analyzer/binding-freebsd-x64": "0.5.32",
45
- "@yuku-analyzer/binding-linux-arm-gnu": "0.5.32",
46
- "@yuku-analyzer/binding-linux-arm-musl": "0.5.32",
47
- "@yuku-analyzer/binding-linux-arm64-gnu": "0.5.32",
48
- "@yuku-analyzer/binding-linux-arm64-musl": "0.5.32",
49
- "@yuku-analyzer/binding-linux-x64-gnu": "0.5.32",
50
- "@yuku-analyzer/binding-linux-x64-musl": "0.5.32",
51
- "@yuku-analyzer/binding-win32-arm64": "0.5.32",
52
- "@yuku-analyzer/binding-win32-x64": "0.5.32"
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
- #scopes;
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.#scopes[this.#scopes.length - 1];
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
- const { byNode, types } = module._scopeMap();
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
  }