@akanjs/language 3.2.0-rc.1 → 3.2.0-rc.2

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/index.cjs CHANGED
@@ -60,7 +60,8 @@ var isEdited = (before, after) => before.digest !== undefined && after.digest !=
60
60
  var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
61
61
 
62
62
  class AkanGraphDiff {
63
- static between(base, head) {
63
+ static between(given, head) {
64
+ const base = AkanGraphDiff.#renamed(given, head);
64
65
  const baseNodes = new Map(base.nodes.map((node) => [node.id, node]));
65
66
  const headNodes = new Map(head.nodes.map((node) => [node.id, node]));
66
67
  const marked = new Map;
@@ -90,6 +91,40 @@ class AkanGraphDiff {
90
91
  summary[node.change] += 1;
91
92
  return { version: 1, nodes, edges, summary };
92
93
  }
94
+ static #renamed(base, head) {
95
+ const baseIds = new Set(base.nodes.map((node) => node.id));
96
+ const headIds = new Set(head.nodes.map((node) => node.id));
97
+ const pairKeyOf = ({ kind, owner, name, at }) => at ? `${kind}
98
+ ${owner ?? ""}
99
+ ${name}
100
+ ${at.file}` : null;
101
+ const shortIdOf = ({ kind, owner, name }) => `${kind}:${owner ?? ""}:${name}`;
102
+ const isFileKeyed = (node) => node.id !== shortIdOf(node) && node.id.startsWith(`${node.kind}:${node.owner ?? ""}:`) && node.id.endsWith(`#${node.name}`);
103
+ const fromBase = new Map(base.nodes.flatMap((node) => {
104
+ const key = headIds.has(node.id) ? null : pairKeyOf(node);
105
+ return key ? [[key, node]] : [];
106
+ }));
107
+ const renames = new Map;
108
+ for (const node of head.nodes) {
109
+ if (baseIds.has(node.id))
110
+ continue;
111
+ const key = pairKeyOf(node);
112
+ const before = key ? fromBase.get(key) : undefined;
113
+ if (!before)
114
+ continue;
115
+ const isPair = isFileKeyed(node) ? before.id === shortIdOf(node) : isFileKeyed(before) && node.id === shortIdOf(before);
116
+ if (isPair)
117
+ renames.set(before.id, node.id);
118
+ }
119
+ if (!renames.size)
120
+ return base;
121
+ const rename = (id) => renames.get(id) ?? id;
122
+ return {
123
+ ...base,
124
+ nodes: base.nodes.map((node) => renames.has(node.id) ? { ...node, id: rename(node.id) } : node),
125
+ edges: base.edges.map((edge) => ({ ...edge, from: rename(edge.from), to: rename(edge.to) }))
126
+ };
127
+ }
93
128
  static #mark(node, change) {
94
129
  return node.fields ? { ...node, change, fields: node.fields.map((field) => ({ ...field, change })) } : { ...node, change };
95
130
  }
@@ -123,6 +158,239 @@ class AkanGraphDiff {
123
158
  return node.fields ? { ...node, fields: node.fields.map(({ digest: _field, ...field }) => field) } : node;
124
159
  }
125
160
  }
161
+ // pkgs/@akanjs/language/guardFacts.ts
162
+ var guardLabelOf = (guard, labels) => {
163
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
164
+ const label = labels[local];
165
+ return label === undefined ? guard : role ? `${role} ${label}` : label;
166
+ };
167
+ var maxLabelArgs = 32;
168
+ var guardRefReaderOf = (ts, ast, unwrap) => {
169
+ const pathOf = (expression) => {
170
+ if (ts.isIdentifier(expression))
171
+ return expression.text;
172
+ if (!ts.isPropertyAccessExpression(expression))
173
+ return null;
174
+ const head = pathOf(expression.expression);
175
+ return head && `${head}.${expression.name.text}`;
176
+ };
177
+ const calleeOf = (call) => pathOf(unwrap(call.expression)) ?? "?";
178
+ const labelOf = (source, call) => {
179
+ const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
180
+ return `${calleeOf(call)}(${args.length <= maxLabelArgs && !args.includes(`
181
+ `) ? args : "…"})`;
182
+ };
183
+ const guardRefOf = (expression, made, member) => {
184
+ const value = unwrap(expression);
185
+ if (!ts.isCallExpression(value))
186
+ return pathOf(value) ?? ast.expressionName(value);
187
+ const prefix = `${member}:${calleeOf(value)}@`;
188
+ const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
189
+ made.push({ call: value, name });
190
+ return name;
191
+ };
192
+ const guardsIn = (source, object, made, member) => {
193
+ const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
194
+ if (!guards)
195
+ return [];
196
+ const refOf = (expression) => guardRefOf(expression, made, member);
197
+ if (ts.isArrayLiteralExpression(guards))
198
+ return guards.elements.map(refOf).filter((name) => name !== null);
199
+ if (ts.isObjectLiteralExpression(guards))
200
+ return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
201
+ const value = unwrap(property.initializer);
202
+ const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
203
+ return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
204
+ });
205
+ const single = refOf(guards);
206
+ return single ? [single] : [];
207
+ };
208
+ return { pathOf, labelOf, guardsIn };
209
+ };
210
+ var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf) => {
211
+ const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
212
+ const scopeOf = (node) => {
213
+ const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
214
+ const value = scope && unwrap(scope);
215
+ return value && ts.isStringLiteralLike(value) ? value.text : null;
216
+ };
217
+ const functionsOf = (source) => {
218
+ const functions = new Map;
219
+ for (const statement of source.statements) {
220
+ if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
221
+ functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
222
+ if (!ts.isVariableStatement(statement))
223
+ continue;
224
+ for (const declaration of statement.declarationList.declarations) {
225
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
226
+ if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
227
+ functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
228
+ }
229
+ }
230
+ return functions;
231
+ };
232
+ const returnedOf = (body, guardNames) => {
233
+ const locals = new Map;
234
+ const pending = [];
235
+ const visit = (node) => {
236
+ if (ts.isClassDeclaration(node) && node.name)
237
+ locals.set(node.name.text, node);
238
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
239
+ locals.set(node.name.text, node.initializer);
240
+ if (ts.isReturnStatement(node) && node.expression)
241
+ pending.push(node.expression);
242
+ if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
243
+ ts.forEachChild(node, visit);
244
+ };
245
+ if (ts.isBlock(body))
246
+ ts.forEachChild(body, visit);
247
+ else
248
+ pending.push(body);
249
+ let guard = null;
250
+ const calls = [];
251
+ const seen = new Set;
252
+ const passed = new Set;
253
+ for (let node = pending.pop();node; node = pending.pop()) {
254
+ if (seen.has(node))
255
+ continue;
256
+ seen.add(node);
257
+ const value = ts.isExpression(node) ? unwrap(node) : node;
258
+ if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
259
+ guard ??= value;
260
+ else if (ts.isConditionalExpression(value))
261
+ pending.push(value.whenTrue, value.whenFalse);
262
+ else if (ts.isBinaryExpression(value))
263
+ pending.push(value.left, value.right);
264
+ else if (ts.isIdentifier(value)) {
265
+ const local = locals.get(value.text);
266
+ if (local)
267
+ pending.push(local);
268
+ } else if (ts.isCallExpression(value)) {
269
+ const callee = pathOf(unwrap(value.expression));
270
+ if (callee && !passed.has(node) && !calls.includes(callee))
271
+ calls.push(callee);
272
+ for (const arg of value.arguments)
273
+ passed.add(arg);
274
+ pending.push(...value.arguments);
275
+ }
276
+ }
277
+ return { guard, calls };
278
+ };
279
+ const guardFactoriesOf = (source, guardNames, isImported) => {
280
+ const functions = functionsOf(source);
281
+ const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
282
+ const members = new Set;
283
+ const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
284
+ for (let isGrowing = true;isGrowing; ) {
285
+ isGrowing = false;
286
+ for (const [name, { guard, calls }] of returned) {
287
+ if (members.has(name) || !guard && !calls.some(isFactoryCall))
288
+ continue;
289
+ members.add(name);
290
+ isGrowing = true;
291
+ }
292
+ }
293
+ const factories = new Map;
294
+ for (const name of members) {
295
+ const found = functions.get(name);
296
+ const made = returned.get(name);
297
+ if (!found || !made)
298
+ continue;
299
+ factories.set(name, {
300
+ name,
301
+ span: spanOf(source, found.name),
302
+ extent: extentOf(source, found.statement),
303
+ scope: made.guard ? scopeOf(made.guard) : null,
304
+ doc: docOf(source, found.statement),
305
+ direct: !!made.guard,
306
+ via: made.calls.filter(isFactoryCall)
307
+ });
308
+ }
309
+ return factories;
310
+ };
311
+ return { implementsGuard, scopeOf, guardFactoriesOf };
312
+ };
313
+ var createGuardFactReader = (ts, ast, base) => {
314
+ const { spanOf, extentOf, unwrap, docOf } = base;
315
+ const { pathOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
316
+ const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf);
317
+ const guardsOf = (source, imports, madeInPlace) => {
318
+ const guardNames = new Set([
319
+ "Guard",
320
+ ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
321
+ ]);
322
+ const imported = new Set(imports.map(({ name }) => name));
323
+ const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
324
+ const factories = guardFactoriesOf(source, guardNames, isImported);
325
+ const guards = [];
326
+ const calls = [];
327
+ const makeOf = (made, value, isInPlace = false) => {
328
+ if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
329
+ guards.push({ ...made, scope: scopeOf(value), factory: null });
330
+ return;
331
+ }
332
+ const callee = ts.isCallExpression(value) ? pathOf(unwrap(value.expression)) : null;
333
+ const factory = callee && factories.get(callee);
334
+ if (factory)
335
+ guards.push({ ...made, scope: factory.scope, factory: factory.name });
336
+ else if (callee && (isImported(callee) || isInPlace))
337
+ calls.push({ ...made, callee, kind: "call", scope: null });
338
+ };
339
+ for (const statement of source.statements) {
340
+ if (ts.isClassDeclaration(statement) && statement.name) {
341
+ const declared = {
342
+ name: statement.name.text,
343
+ span: spanOf(source, statement.name),
344
+ extent: extentOf(source, statement),
345
+ scope: scopeOf(statement),
346
+ doc: docOf(source, statement)
347
+ };
348
+ const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
349
+ const callee = base && pathOf(base.expression);
350
+ if (implementsGuard(statement, guardNames))
351
+ guards.push({ ...declared, factory: null });
352
+ else if (callee)
353
+ calls.push({ ...declared, callee, kind: "extends" });
354
+ }
355
+ if (!ts.isVariableStatement(statement))
356
+ continue;
357
+ for (const declaration of statement.declarationList.declarations) {
358
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
359
+ if (!initializer || !ts.isIdentifier(declaration.name))
360
+ continue;
361
+ const holder = declaration.name.text;
362
+ makeOf({
363
+ name: holder,
364
+ span: spanOf(source, declaration.name),
365
+ extent: extentOf(source, statement),
366
+ doc: docOf(source, statement)
367
+ }, initializer);
368
+ if (!ts.isObjectLiteralExpression(initializer))
369
+ continue;
370
+ for (const property of initializer.properties) {
371
+ const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
372
+ if (key && ts.isPropertyAssignment(property))
373
+ makeOf({
374
+ name: `${holder}.${key}`,
375
+ span: spanOf(source, property.name),
376
+ extent: extentOf(source, property),
377
+ doc: docOf(source, property)
378
+ }, unwrap(property.initializer));
379
+ }
380
+ }
381
+ }
382
+ for (const { call, name } of madeInPlace)
383
+ makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
384
+ const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
385
+ return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
386
+ };
387
+ return { guardsIn, guardsOf };
388
+ };
389
+
390
+ // pkgs/@akanjs/language/escapeRegExp.ts
391
+ var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
392
+ var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
393
+
126
394
  // pkgs/@akanjs/language/moduleFacts.ts
127
395
  var testFilePattern = /\.(?:test|spec)\.tsx?$/;
128
396
  var moduleCallees = new Set([
@@ -191,21 +459,6 @@ var createFactReader = (ts, ast) => {
191
459
  const value = unwrap(expression);
192
460
  return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
193
461
  };
194
- const guardsIn = (object) => {
195
- const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
196
- if (!guards)
197
- return [];
198
- if (ts.isArrayLiteralExpression(guards))
199
- return guards.elements.map(guardNameOf).filter((name) => name !== null);
200
- if (ts.isObjectLiteralExpression(guards))
201
- return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
202
- const value = unwrap(property.initializer);
203
- const names = ts.isArrayLiteralExpression(value) ? value.elements.map(guardNameOf) : [guardNameOf(value)];
204
- return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
205
- });
206
- const single = guardNameOf(guards);
207
- return single ? [single] : [];
208
- };
209
462
  const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
210
463
  const chainOf = (initializer) => {
211
464
  let root = unwrap(initializer);
@@ -222,7 +475,7 @@ var createFactReader = (ts, ast) => {
222
475
  const node = keyName(property);
223
476
  return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
224
477
  });
225
- const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
478
+ const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
226
479
  if (!ts.isPropertyAssignment(property))
227
480
  return [];
228
481
  const name = ast.nodeName(property.name);
@@ -234,7 +487,7 @@ var createFactReader = (ts, ast) => {
234
487
  name,
235
488
  span: spanOf(source, property.name),
236
489
  extent: extentOf(source, property),
237
- guards: root ? guardsIn(optionObjectOf(root)) : [],
490
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
238
491
  live: methods.includes("live"),
239
492
  argCount: methods.filter((method) => argMethods.has(method)).length
240
493
  }
@@ -340,7 +593,7 @@ var createFactReader = (ts, ast) => {
340
593
  return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
341
594
  });
342
595
  };
343
- const endpointKeysOf = (source, builder) => {
596
+ const endpointKeysOf = (source, builder, made, model) => {
344
597
  const verbOf = verbReaderOf(builder?.parameters[0]);
345
598
  return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
346
599
  if (!ts.isPropertyAssignment(property))
@@ -355,7 +608,7 @@ var createFactReader = (ts, ast) => {
355
608
  span: spanOf(source, property.name),
356
609
  extent: extentOf(source, property),
357
610
  verb: root ? verbOf(root.expression) : null,
358
- guards: root ? guardsIn(optionObjectOf(root)) : [],
611
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
359
612
  argCount: methods.filter((method) => argMethods.has(method)).length
360
613
  }
361
614
  ];
@@ -422,22 +675,6 @@ var createFactReader = (ts, ast) => {
422
675
  })) : []
423
676
  ];
424
677
  };
425
- const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
426
- const guardClassIn = (node, guardNames) => {
427
- if ((ts.isClassExpression(node) || ts.isClassDeclaration(node)) && implementsGuard(node, guardNames))
428
- return node;
429
- return ts.forEachChild(node, (child) => guardClassIn(child, guardNames)) ?? null;
430
- };
431
- const callsIn = (node, names) => {
432
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text))
433
- return node.expression.text;
434
- return ts.forEachChild(node, (child) => callsIn(child, names)) ?? null;
435
- };
436
- const scopeOf = (node) => {
437
- const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
438
- const value = scope && unwrap(scope);
439
- return value && ts.isStringLiteralLike(value) ? value.text : null;
440
- };
441
678
  const docOf = (source, node) => {
442
679
  const { text } = source;
443
680
  const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
@@ -453,108 +690,20 @@ var createFactReader = (ts, ast) => {
453
690
  `)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
454
691
  return lines.length ? lines.join(" ") : null;
455
692
  };
456
- const functionsOf = (source) => {
457
- const functions = new Map;
458
- for (const statement of source.statements) {
459
- if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
460
- functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
461
- if (!ts.isVariableStatement(statement))
462
- continue;
463
- for (const declaration of statement.declarationList.declarations) {
464
- const initializer = declaration.initializer && unwrap(declaration.initializer);
465
- if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
466
- functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
467
- }
468
- }
469
- return functions;
470
- };
471
- const guardFactoriesOf = (source, guardNames) => {
472
- const functions = functionsOf(source);
473
- const factories = new Map;
474
- const factoryOf = (name, scope, via) => {
475
- const found = functions.get(name);
476
- if (!found)
477
- return;
478
- factories.set(name, {
479
- name,
480
- span: spanOf(source, found.name),
481
- extent: extentOf(source, found.statement),
482
- scope,
483
- doc: docOf(source, found.statement),
484
- via
485
- });
486
- };
487
- for (const [name, { body }] of functions) {
488
- const guard = guardClassIn(body, guardNames);
489
- if (guard)
490
- factoryOf(name, scopeOf(guard), null);
491
- }
492
- for (let isGrowing = true;isGrowing; ) {
493
- isGrowing = false;
494
- for (const [name, { body }] of functions) {
495
- const called = factories.has(name) ? null : callsIn(body, new Set(factories.keys()));
496
- if (!called)
497
- continue;
498
- factoryOf(name, factories.get(called)?.scope ?? null, called);
499
- isGrowing = true;
500
- }
501
- }
502
- return factories;
503
- };
504
- const guardsOf = (source, imports) => {
505
- const guardNames = new Set([
506
- "Guard",
507
- ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
508
- ]);
509
- const imported = new Set(imports.map(({ name }) => name));
510
- const factories = guardFactoriesOf(source, guardNames);
511
- const guards = [];
512
- const calls = [];
513
- for (const statement of source.statements) {
514
- if (ts.isClassDeclaration(statement) && statement.name && implementsGuard(statement, guardNames))
515
- guards.push({
516
- name: statement.name.text,
517
- span: spanOf(source, statement.name),
518
- extent: extentOf(source, statement),
519
- scope: scopeOf(statement),
520
- doc: docOf(source, statement),
521
- factory: null
522
- });
523
- if (!ts.isVariableStatement(statement))
524
- continue;
525
- for (const declaration of statement.declarationList.declarations) {
526
- const initializer = declaration.initializer && unwrap(declaration.initializer);
527
- if (!initializer || !ts.isIdentifier(declaration.name))
528
- continue;
529
- const made = {
530
- name: declaration.name.text,
531
- span: spanOf(source, declaration.name),
532
- extent: extentOf(source, statement),
533
- doc: docOf(source, statement)
534
- };
535
- if (ts.isClassExpression(initializer) && implementsGuard(initializer, guardNames))
536
- guards.push({ ...made, scope: scopeOf(initializer), factory: null });
537
- if (!ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression))
538
- continue;
539
- const callee = initializer.expression.text;
540
- const factory = factories.get(callee);
541
- if (factory)
542
- guards.push({ ...made, scope: factory.scope, factory: callee });
543
- else if (imported.has(callee))
544
- calls.push({ ...made, callee });
545
- }
546
- }
547
- return { guards, guardFactories: [...factories.values()], guardCalls: calls };
548
- };
693
+ const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
549
694
  const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
550
695
  const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
551
696
  const read = (source, owner) => {
552
697
  const imports = importsIn(source);
698
+ const madeInPlace = [];
553
699
  const facts = {
554
700
  fileName: source.fileName,
555
701
  owner,
556
702
  imports,
557
- ...guardsOf(source, imports),
703
+ guards: [],
704
+ guardFactories: [],
705
+ guardCalls: [],
706
+ guardLabels: {},
558
707
  constants: [],
559
708
  enums: [],
560
709
  filters: [],
@@ -572,64 +721,496 @@ var createFactReader = (ts, ast) => {
572
721
  const heritage = ast.heritageCallNamed(statement, moduleCallees);
573
722
  if (!heritage)
574
723
  continue;
575
- const { callee, call } = heritage;
576
- const name = { name: statement.name.text, span: spanOf(source, statement.name) };
577
- const builder = builderOf(call);
578
- if (callee === "via")
579
- facts.constants.push(constantOf(source, name, call));
580
- else if (callee === "enumOf")
581
- facts.enums.push({ ...name, values: enumValuesOf(call) });
582
- else if (callee === "from")
583
- facts.filters.push({ ...name, queries: filterQueriesOf(source, builder) });
584
- else if (callee === "into")
585
- facts.models.push({ ...name, model: name.name.endsWith("Model") ? lowerFirst(name.name.slice(0, -5)) : null });
586
- else if (callee === "serve")
587
- facts.services.push({
588
- ...name,
589
- model: moduleRefOf(call.arguments[0], "db"),
590
- methods: methodsOf(source, statement),
591
- plugs: plugsOf(source, builder)
592
- });
593
- else if (callee === "slice")
594
- facts.slices.push({
595
- ...name,
596
- model: moduleRefOf(call.arguments[0], "srv"),
597
- guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
598
- keys: sliceKeysOf(source, builder)
599
- });
600
- else if (callee === "internal")
601
- facts.internals.push({
602
- ...name,
603
- model: moduleRefOf(call.arguments[0], "srv"),
604
- keys: internalKeysOf(source, builder)
605
- });
606
- else if (callee === "adapt" || callee === "dangerouslyAdapt") {
607
- const refName = call.arguments[0] && unwrap(call.arguments[0]);
608
- facts.adaptors.push({
609
- ...name,
610
- extent: extentOf(source, statement),
611
- refName: refName && ts.isStringLiteralLike(refName) ? refName.text : null,
612
- plugs: plugsOf(source, builder),
613
- doc: docOf(source, statement)
614
- });
615
- } else if (callee === "endpoint")
616
- facts.endpoints.push({
617
- ...name,
618
- model: moduleRefOf(call.arguments[0], "srv"),
619
- keys: endpointKeysOf(source, builder)
620
- });
621
- else if (callee === "store")
622
- facts.stores.push({
623
- ...name,
624
- model: moduleRefOf(call.arguments[0], "sig"),
625
- stateKeys: objectKeys(source, returnedObject(builder)),
626
- actions: methodsOf(source, statement)
627
- });
724
+ const { callee, call } = heritage;
725
+ const name = { name: statement.name.text, span: spanOf(source, statement.name) };
726
+ const builder = builderOf(call);
727
+ if (callee === "via")
728
+ facts.constants.push(constantOf(source, name, call));
729
+ else if (callee === "enumOf")
730
+ facts.enums.push({ ...name, values: enumValuesOf(call) });
731
+ else if (callee === "from")
732
+ facts.filters.push({ ...name, queries: filterQueriesOf(source, builder) });
733
+ else if (callee === "into")
734
+ facts.models.push({ ...name, model: name.name.endsWith("Model") ? lowerFirst(name.name.slice(0, -5)) : null });
735
+ else if (callee === "serve")
736
+ facts.services.push({
737
+ ...name,
738
+ model: moduleRefOf(call.arguments[0], "db"),
739
+ methods: methodsOf(source, statement),
740
+ plugs: plugsOf(source, builder)
741
+ });
742
+ else if (callee === "slice") {
743
+ const model = moduleRefOf(call.arguments[0], "srv");
744
+ const member = model ?? name.name;
745
+ facts.slices.push({
746
+ ...name,
747
+ model,
748
+ guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
749
+ keys: sliceKeysOf(source, builder, madeInPlace, member)
750
+ });
751
+ } else if (callee === "internal")
752
+ facts.internals.push({
753
+ ...name,
754
+ model: moduleRefOf(call.arguments[0], "srv"),
755
+ keys: internalKeysOf(source, builder)
756
+ });
757
+ else if (callee === "adapt" || callee === "dangerouslyAdapt") {
758
+ const refName = call.arguments[0] && unwrap(call.arguments[0]);
759
+ facts.adaptors.push({
760
+ ...name,
761
+ extent: extentOf(source, statement),
762
+ refName: refName && ts.isStringLiteralLike(refName) ? refName.text : null,
763
+ plugs: plugsOf(source, builder),
764
+ doc: docOf(source, statement)
765
+ });
766
+ } else if (callee === "endpoint") {
767
+ const model = moduleRefOf(call.arguments[0], "srv");
768
+ facts.endpoints.push({
769
+ ...name,
770
+ model,
771
+ keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
772
+ });
773
+ } else if (callee === "store")
774
+ facts.stores.push({
775
+ ...name,
776
+ model: moduleRefOf(call.arguments[0], "sig"),
777
+ stateKeys: objectKeys(source, returnedObject(builder)),
778
+ actions: methodsOf(source, statement)
779
+ });
780
+ }
781
+ return { ...facts, ...guardsOf(source, imports, madeInPlace) };
782
+ };
783
+ return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
784
+ };
785
+
786
+ // pkgs/@akanjs/language/guardResolver.ts
787
+ var frameworkOwner = "akanjs";
788
+ var frameworkPattern = /^akanjs(?:\/|$)/;
789
+ var aliasPattern = /^@(apps|libs)\/([^/]+)/;
790
+ var maxMapDepth = 4;
791
+ var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
792
+ var declaringPatternOf = (name, isAnyBinding) => {
793
+ const escaped = escapeRegExp(name);
794
+ const braced = "\\{(?:[^}]*?[\\s,])?";
795
+ const leads = [
796
+ "function\\s*\\*?\\s*",
797
+ "(?:const|let|var)\\s+",
798
+ `export\\s*${braced}`,
799
+ ...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
800
+ ];
801
+ return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
802
+ };
803
+
804
+ class GuardResolver {
805
+ #source;
806
+ #routes;
807
+ #guards = new Map;
808
+ #madeInPlace = new Map;
809
+ #mapEntries = new Map;
810
+ #reexports = new Map;
811
+ #declarations = new Map;
812
+ #starExports = new Map;
813
+ #ownerFiles = null;
814
+ #modules = null;
815
+ constructor(source, routes) {
816
+ this.#source = source;
817
+ this.#routes = routes;
818
+ }
819
+ find(local, site) {
820
+ return this.#findGuard(local, site, 0);
821
+ }
822
+ madeInPlace(fileName, name) {
823
+ return this.#madeInPlace.get(`${fileName}
824
+ ${name}`);
825
+ }
826
+ declare(facts) {
827
+ const factories = new Map;
828
+ const declaredFactories = [];
829
+ for (const file of facts)
830
+ for (const fact of file.guardFactories) {
831
+ const factory = {
832
+ fact,
833
+ owner: file.owner,
834
+ site: file,
835
+ made: [],
836
+ isReal: fact.direct,
837
+ scope: fact.scope,
838
+ targets: []
839
+ };
840
+ factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
841
+ declaredFactories.push(factory);
842
+ }
843
+ for (const factory of declaredFactories)
844
+ factory.targets = factory.fact.via.map((callee) => this.#factoryAt(factories, callee, factory.site)).filter((target) => target !== undefined);
845
+ for (let isGrowing = true;isGrowing; ) {
846
+ isGrowing = false;
847
+ for (const factory of declaredFactories) {
848
+ const target = factory.targets.find((other) => other.isReal);
849
+ if (factory.isReal || !target)
850
+ continue;
851
+ factory.isReal = true;
852
+ factory.scope ??= target.scope;
853
+ isGrowing = true;
854
+ }
855
+ }
856
+ const partsOf = (factory, seen = new Set) => {
857
+ if (seen.has(factory))
858
+ return [];
859
+ seen.add(factory);
860
+ return [
861
+ { fileName: factory.site.fileName, extent: factory.fact.extent },
862
+ ...factory.targets.filter((target) => target.isReal).flatMap((target) => partsOf(target, seen))
863
+ ];
864
+ };
865
+ const declare = (file, made, scope, parts) => {
866
+ const { name, span, extent, doc } = made;
867
+ const label = file.guardLabels[name];
868
+ const own = { fileName: file.fileName, extent };
869
+ const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
870
+ if (name.includes("@"))
871
+ this.#madeInPlace.set(`${file.fileName}
872
+ ${name}`, guard);
873
+ else
874
+ this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
875
+ return guard;
876
+ };
877
+ const extending = [];
878
+ for (const file of facts) {
879
+ for (const made of file.guards) {
880
+ const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
881
+ if (!made.factory)
882
+ declare(file, made, made.scope, []);
883
+ else if (factory?.isReal)
884
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
885
+ }
886
+ for (const made of file.guardCalls) {
887
+ if (made.kind === "extends") {
888
+ extending.push([file, made]);
889
+ continue;
890
+ }
891
+ const factory = this.#factoryAt(factories, made.callee, file);
892
+ if (factory?.isReal)
893
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
894
+ }
895
+ }
896
+ for (let isGrowing = true;isGrowing; ) {
897
+ isGrowing = false;
898
+ for (const [idx, entry] of extending.entries()) {
899
+ const [file, made] = entry;
900
+ const found = this.#findGuard(made.callee, file, 0, true);
901
+ if (!("guard" in found))
902
+ continue;
903
+ declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
904
+ extending.splice(idx, 1);
905
+ isGrowing = true;
906
+ break;
907
+ }
908
+ }
909
+ for (const { fact, made, isReal } of declaredFactories)
910
+ if (isReal && made.every((guard) => !guard.doc))
911
+ for (const guard of made)
912
+ guard.doc = fact.doc;
913
+ for (const list of this.#guards.values())
914
+ for (const guard of list)
915
+ if (list.some((other) => other !== guard && other.owner === guard.owner))
916
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
917
+ for (const guard of this.#madeInPlace.values())
918
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
919
+ return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
920
+ }
921
+ #factoryAt(factories, callee, site, depth = 0) {
922
+ const [head = callee, member] = callee.split(".");
923
+ const imported = site.imports.find((item) => item.name === head);
924
+ if (!imported)
925
+ return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
926
+ if (member && imported.imported !== "*")
927
+ return;
928
+ const from = this.ownerOfSpecifier(site.fileName, imported.from);
929
+ if (from === undefined || from === frameworkOwner)
930
+ return;
931
+ const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
932
+ const candidates = factories.get(name) ?? [];
933
+ const near = this.pathOfSpecifier(site.fileName, imported.from);
934
+ const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
935
+ if (own)
936
+ return own;
937
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
938
+ if (reexport)
939
+ return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
940
+ return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
941
+ }
942
+ #findGuard(local, site, depth, isBase = false) {
943
+ const segments = local.split(".");
944
+ const head = segments[0] ?? local;
945
+ const imported = site.imports.find((item) => item.name === head);
946
+ const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
947
+ if (imported && from === undefined)
948
+ return { external: true };
949
+ const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
950
+ const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
951
+ const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
952
+ const holder = imported ? from ?? null : site.owner;
953
+ const [objectName, member] = path;
954
+ if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
955
+ const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
956
+ if (made)
957
+ return { guard: made };
958
+ for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
959
+ const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
960
+ if ("guard" in found)
961
+ return found;
962
+ }
963
+ }
964
+ const name = path[path.length - 1] ?? head;
965
+ if (from === frameworkOwner)
966
+ return { name, from };
967
+ const declared = this.#guards.get(name) ?? [];
968
+ const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
969
+ if (own)
970
+ return { guard: own };
971
+ if (!imported) {
972
+ const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
973
+ return visible ? { guard: visible } : { name, from };
974
+ }
975
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
976
+ if (reexport)
977
+ return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
978
+ const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
979
+ return reached ? { guard: reached } : { name, from };
980
+ }
981
+ reexportOf(owner, name, near) {
982
+ const key = `${owner}
983
+ ${name}
984
+ ${near ?? ""}`;
985
+ const cached = this.#reexports.get(key);
986
+ if (cached !== undefined)
987
+ return cached;
988
+ let found = null;
989
+ const exporting = new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
990
+ for (const fileName of this.#filesOf(owner, name, near)) {
991
+ const text = this.#source.readText(fileName);
992
+ if (text === undefined || !exporting.test(text))
993
+ continue;
994
+ const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
995
+ if (!reexport)
996
+ continue;
997
+ const site = { fileName, owner, imports: [] };
998
+ found = reexport.from ? {
999
+ local: reexport.name,
1000
+ site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
1001
+ } : { local: reexport.name, site };
1002
+ break;
1003
+ }
1004
+ this.#reexports.set(key, found);
1005
+ return found;
1006
+ }
1007
+ #reexportIn(source, name) {
1008
+ const { ts, reader } = this.#source;
1009
+ for (const statement of source.statements) {
1010
+ if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
1011
+ continue;
1012
+ const { exportClause, moduleSpecifier } = statement;
1013
+ if (!exportClause || !ts.isNamedExports(exportClause))
1014
+ continue;
1015
+ const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
1016
+ if (!element)
1017
+ continue;
1018
+ const original = (element.propertyName ?? element.name).text;
1019
+ if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
1020
+ return { name: original, from: moduleSpecifier.text };
1021
+ const imported = reader.importsIn(source).find((item) => item.name === original);
1022
+ if (!imported)
1023
+ return { name: original, from: null };
1024
+ return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
1025
+ }
1026
+ return null;
1027
+ }
1028
+ #filesOf(owner, name, near) {
1029
+ const isNear = (fileName) => {
1030
+ const relative = this.#source.relative(fileName);
1031
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1032
+ };
1033
+ const candidates = this.filesIn(owner).filter(([, words]) => words.includes(`
1034
+ ${name}
1035
+ `)).map(([fileName]) => fileName);
1036
+ return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
1037
+ }
1038
+ #mapEntriesOf(objectName, member, fileName, owner) {
1039
+ const key = `${objectName}
1040
+ ${member}
1041
+ ${fileName ?? ""}
1042
+ ${owner ?? ""}`;
1043
+ const cached = this.#mapEntries.get(key);
1044
+ if (cached)
1045
+ return cached;
1046
+ const { ts, reader } = this.#source;
1047
+ const entries = [];
1048
+ const fileNames = [
1049
+ ...fileName ? [fileName] : [],
1050
+ ...(owner === null ? [] : this.filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
1051
+ ${objectName}
1052
+ `) && words.includes(`
1053
+ ${member}
1054
+ `)).map(([other]) => other)
1055
+ ];
1056
+ for (const holder of fileNames) {
1057
+ const text = this.#source.readText(holder);
1058
+ if (text === undefined)
1059
+ continue;
1060
+ const source = this.#source.parse(holder, text);
1061
+ const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
1062
+ for (const statement of source.statements) {
1063
+ if (!ts.isVariableStatement(statement))
1064
+ continue;
1065
+ const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
1066
+ const object = map && reader.unwrap(map);
1067
+ if (!object || !ts.isObjectLiteralExpression(object))
1068
+ continue;
1069
+ for (const property of object.properties) {
1070
+ if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
1071
+ entries.push({ name: member, site });
1072
+ if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
1073
+ continue;
1074
+ const value = reader.unwrap(property.initializer);
1075
+ if (ts.isIdentifier(value))
1076
+ entries.push({ name: value.text, site });
1077
+ else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
1078
+ entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
1079
+ }
1080
+ }
1081
+ }
1082
+ this.#mapEntries.set(key, entries);
1083
+ return entries;
1084
+ }
1085
+ declarationOf(owner, name, near, depth = 0) {
1086
+ const key = `${owner}
1087
+ ${name}
1088
+ ${near ?? ""}`;
1089
+ const cached = this.#declarations.get(key);
1090
+ if (cached !== undefined)
1091
+ return cached;
1092
+ this.#declarations.set(key, null);
1093
+ const declaring = declaringPatternOf(name, false);
1094
+ let found = null;
1095
+ for (const fileName of this.#filesOf(owner, name, near)) {
1096
+ const text = this.#source.readText(fileName);
1097
+ if (text === undefined || !declaring.test(text))
1098
+ continue;
1099
+ const source = this.#source.parse(fileName, text);
1100
+ const declared = this.#routes.declarationIn(source, name);
1101
+ if (declared) {
1102
+ found = { source, ...declared, name };
1103
+ break;
1104
+ }
1105
+ const reexport = this.#reexportIn(source, name);
1106
+ if (!reexport)
1107
+ continue;
1108
+ if (!reexport.from) {
1109
+ const local = this.#routes.declarationIn(source, reexport.name);
1110
+ found = local ? { source, ...local, name: reexport.name } : null;
1111
+ break;
1112
+ }
1113
+ const next = this.ownerOfSpecifier(fileName, reexport.from);
1114
+ if (next && next !== frameworkOwner && depth < maxMapDepth)
1115
+ found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
1116
+ break;
628
1117
  }
629
- return facts;
630
- };
631
- return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
632
- };
1118
+ this.#declarations.set(key, found);
1119
+ return found;
1120
+ }
1121
+ #throughStar(declared, owner, name, near) {
1122
+ if (!declared.length)
1123
+ return;
1124
+ const { owners, isOpaque } = this.#starExportsOf(owner, near);
1125
+ const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
1126
+ return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
1127
+ }
1128
+ #starExportsOf(owner, near) {
1129
+ const cached = this.#starExports.get(near);
1130
+ if (cached)
1131
+ return cached;
1132
+ const found = { owners: [], isOpaque: false };
1133
+ this.#starExports.set(near, found);
1134
+ const paths = [near];
1135
+ for (let idx = 0;idx < paths.length && !found.isOpaque; idx++)
1136
+ for (const fileName of this.#modulesAt(paths[idx] ?? "")) {
1137
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
1138
+ const target = this.ownerOfSpecifier(fileName, specifier);
1139
+ const path = this.pathOfSpecifier(fileName, specifier);
1140
+ if (target === undefined || target === frameworkOwner || path === null)
1141
+ found.isOpaque = true;
1142
+ else if (!paths.includes(path))
1143
+ paths.push(path);
1144
+ if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
1145
+ found.owners.push(target);
1146
+ }
1147
+ }
1148
+ return found;
1149
+ }
1150
+ #modulesAt(path) {
1151
+ if (!this.#modules) {
1152
+ this.#modules = new Map;
1153
+ for (const [fileName] of this.#source.files) {
1154
+ const module2 = this.#source.relative(fileName).replace(/\.tsx?$/, "");
1155
+ for (const key of module2.endsWith("/index") ? [module2, module2.slice(0, -6)] : [module2])
1156
+ this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
1157
+ }
1158
+ }
1159
+ return this.#modules.get(path) ?? [];
1160
+ }
1161
+ declares(owner, name) {
1162
+ const declaring = declaringPatternOf(name, true);
1163
+ return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
1164
+ }
1165
+ filesIn(owner) {
1166
+ if (!this.#ownerFiles) {
1167
+ this.#ownerFiles = new Map;
1168
+ for (const entry of this.#source.files) {
1169
+ const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
1170
+ const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
1171
+ if (files)
1172
+ files.push(entry);
1173
+ else if (fileOwner !== null)
1174
+ this.#ownerFiles.set(fileOwner, [entry]);
1175
+ }
1176
+ }
1177
+ return this.#ownerFiles.get(owner) ?? [];
1178
+ }
1179
+ #nearest(items, fileOf, near) {
1180
+ const isNear = (fileName) => {
1181
+ const relative = this.#source.relative(fileName);
1182
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1183
+ };
1184
+ return items.find((item) => isNear(fileOf(item))) ?? items[0];
1185
+ }
1186
+ inOwner(fileName, owner) {
1187
+ const relative = this.#source.relative(fileName);
1188
+ return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
1189
+ }
1190
+ ownerOfSpecifier(fileName, specifier) {
1191
+ if (frameworkPattern.test(specifier))
1192
+ return frameworkOwner;
1193
+ const path = this.pathOfSpecifier(fileName, specifier);
1194
+ return path === null ? undefined : ownerOf(path) ?? undefined;
1195
+ }
1196
+ pathOfSpecifier(fileName, specifier) {
1197
+ const alias = specifier.match(aliasPattern);
1198
+ if (alias)
1199
+ return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1200
+ if (!specifier.startsWith("."))
1201
+ return null;
1202
+ const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1203
+ for (const segment of specifier.split("/"))
1204
+ if (segment === "..")
1205
+ segments.pop();
1206
+ else if (segment !== ".")
1207
+ segments.push(segment);
1208
+ return segments.join("/");
1209
+ }
1210
+ visible(target, from) {
1211
+ return !from || !target || target === from || target.startsWith("libs/");
1212
+ }
1213
+ }
633
1214
 
634
1215
  // pkgs/akanjs/common/trimSlashes.ts
635
1216
  var trimTrailingSlashes = (value) => {
@@ -778,24 +1359,23 @@ rootLayout
778
1359
  }
779
1360
  declarationIn(source, name) {
780
1361
  const ts = this.#ts;
1362
+ const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
1363
+ const [first] = overloads;
1364
+ const last = overloads.at(-1);
1365
+ if (first?.name && last)
1366
+ return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
781
1367
  for (const statement of source.statements) {
782
- if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
783
- return { statement, declared: statement.name, initializer: null };
784
1368
  if (!ts.isVariableStatement(statement))
785
1369
  continue;
786
1370
  const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
787
1371
  if (declaration)
788
- return { statement, declared: declaration.name, initializer: declaration.initializer ?? null };
1372
+ return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
789
1373
  }
790
1374
  return null;
791
1375
  }
792
1376
  #guardOf(source, exported) {
793
1377
  const ts = this.#ts;
794
1378
  let current = ts.isExportAssignment(exported) ? this.#reader.unwrap(exported.expression) : null;
795
- if (current && ts.isIdentifier(current)) {
796
- const initializer = this.declarationIn(source, current.text)?.initializer;
797
- current = initializer ? this.#reader.unwrap(initializer) : null;
798
- }
799
1379
  while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
800
1380
  const { name, expression } = current.expression;
801
1381
  const [stage] = current.arguments;
@@ -807,9 +1387,20 @@ rootLayout
807
1387
  }
808
1388
  #callsIn(source, stage) {
809
1389
  const ts = this.#ts;
810
- const imported = new Set(this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from)).map(({ name }) => name));
1390
+ const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
1391
+ const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
1392
+ const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
811
1393
  const calls = new Map;
812
- const callOf = (name) => {
1394
+ const callOf = (callee, shadowed) => {
1395
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
1396
+ const name = `${callee.expression.text}.${callee.name.text}`;
1397
+ if (!calls.has(name))
1398
+ calls.set(name, { name, local: null });
1399
+ return true;
1400
+ }
1401
+ if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
1402
+ return false;
1403
+ const name = callee.text;
813
1404
  if (calls.has(name))
814
1405
  return true;
815
1406
  const declared = this.declarationIn(source, name);
@@ -819,23 +1410,71 @@ rootLayout
819
1410
  name,
820
1411
  local: declared && {
821
1412
  span: this.#reader.spanOf(source, declared.declared),
822
- extent: this.#reader.extentOf(source, declared.statement),
1413
+ extent: declared.extent,
823
1414
  doc: this.#reader.docOf(source, declared.statement)
824
1415
  }
825
1416
  });
826
1417
  return true;
827
1418
  };
828
- const visit = (node) => {
829
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && callOf(node.expression.text))
1419
+ const visit = (node, outer) => {
1420
+ const shadowed = this.#scopeOf(node, outer);
1421
+ if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
830
1422
  return;
831
- ts.forEachChild(node, visit);
1423
+ ts.forEachChild(node, (child) => visit(child, shadowed));
832
1424
  };
833
1425
  if (ts.isIdentifier(stage))
834
- callOf(stage.text);
1426
+ callOf(stage, new Set);
835
1427
  else
836
- visit(stage);
1428
+ visit(stage, new Set);
837
1429
  return [...calls.values()];
838
1430
  }
1431
+ #scopeOf(node, outer) {
1432
+ const ts = this.#ts;
1433
+ const names = [];
1434
+ const bind = (name) => {
1435
+ if (ts.isIdentifier(name))
1436
+ names.push(name.text);
1437
+ else
1438
+ for (const element of name.elements)
1439
+ if (!ts.isOmittedExpression(element))
1440
+ bind(element.name);
1441
+ };
1442
+ const declare = (statement) => {
1443
+ if (ts.isVariableStatement(statement))
1444
+ for (const declaration of statement.declarationList.declarations)
1445
+ bind(declaration.name);
1446
+ else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
1447
+ names.push(statement.name.text);
1448
+ };
1449
+ const hoist = (child) => {
1450
+ if (ts.isFunctionLike(child) || ts.isClassLike(child))
1451
+ return;
1452
+ if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
1453
+ for (const declaration of child.declarations)
1454
+ bind(declaration.name);
1455
+ ts.forEachChild(child, hoist);
1456
+ };
1457
+ if (ts.isFunctionLike(node)) {
1458
+ for (const parameter of node.parameters)
1459
+ bind(parameter.name);
1460
+ if (ts.isFunctionExpression(node) && node.name)
1461
+ names.push(node.name.text);
1462
+ if ("body" in node && node.body)
1463
+ ts.forEachChild(node.body, hoist);
1464
+ } else if (ts.isClassExpression(node) && node.name)
1465
+ names.push(node.name.text);
1466
+ else if (ts.isBlock(node))
1467
+ node.statements.forEach(declare);
1468
+ else if (ts.isCaseBlock(node))
1469
+ for (const clause of node.clauses)
1470
+ clause.statements.forEach(declare);
1471
+ else if (ts.isCatchClause(node) && node.variableDeclaration)
1472
+ bind(node.variableDeclaration.name);
1473
+ else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
1474
+ for (const declaration of node.initializer.declarations)
1475
+ bind(declaration.name);
1476
+ return names.length ? new Set([...outer, ...names]) : outer;
1477
+ }
839
1478
  }
840
1479
 
841
1480
  // pkgs/@akanjs/language/akanGraph.ts
@@ -851,10 +1490,6 @@ var builtinGuards = {
851
1490
  Public: { scope: "account", doc: "Passes every caller, signed in or not." },
852
1491
  None: { scope: "account", doc: "Refuses every caller." }
853
1492
  };
854
- var frameworkOwner = "akanjs";
855
- var frameworkPattern = /^akanjs(?:\/|$)/;
856
- var aliasPattern = /^@(apps|libs)\/([^/]+)/;
857
- var maxMapDepth = 4;
858
1493
 
859
1494
  class GraphBuilder {
860
1495
  #source;
@@ -862,16 +1497,15 @@ class GraphBuilder {
862
1497
  #edges = new Map;
863
1498
  #contexts = new Map;
864
1499
  #members = new Map;
865
- #guards = new Map;
866
- #mapEntries = new Map;
867
1500
  #routeFiles = new Map;
868
- #declarations = new Map;
869
1501
  #routes;
1502
+ #resolver;
870
1503
  #adaptors = new Map;
871
1504
  #plugs = new Map;
872
1505
  constructor(source) {
873
1506
  this.#source = source;
874
1507
  this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
1508
+ this.#resolver = new GuardResolver(source, this.#routes);
875
1509
  }
876
1510
  build() {
877
1511
  const { facts } = this.#source;
@@ -893,7 +1527,8 @@ class GraphBuilder {
893
1527
  for (const fact of file.adaptors)
894
1528
  for (const plug of fact.plugs)
895
1529
  this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
896
- this.#declareGuards(facts);
1530
+ for (const guard of this.#resolver.declare(facts))
1531
+ this.#guardNodeOf(guard);
897
1532
  for (const file of facts) {
898
1533
  const { owner } = file;
899
1534
  const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
@@ -937,31 +1572,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
937
1572
  for (const query of filter.queries)
938
1573
  this.#digest(this.#addNode(this.#key("query", owner, refName, query.name), "query", owner, refName, query.name, query.span), this.#source.readText(file.fileName), query.extent);
939
1574
  }
940
- for (const slice of file.slices) {
941
- const refName = slice.model;
942
- if (!refName)
943
- continue;
944
- const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards);
945
- for (const guard of slice.guards)
946
- this.#guard(fallback, guard, file);
947
- for (const key of slice.keys) {
948
- const detail = [...key.guards, ...key.live ? ["live"] : []];
949
- const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
950
- for (const guard of key.guards)
951
- this.#guard(node, guard, file);
952
- }
953
- }
954
- for (const endpoint of file.endpoints) {
955
- const refName = endpoint.model;
956
- if (!refName)
957
- continue;
958
- for (const key of endpoint.keys) {
959
- const detail = [...key.verb ? [key.verb] : [], ...key.guards];
960
- const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
961
- for (const guard of key.guards)
962
- this.#guard(node, guard, file);
963
- }
964
- }
1575
+ this.#addGuarded(file);
965
1576
  for (const internal of file.internals)
966
1577
  for (const key of internal.keys)
967
1578
  if (internal.model && key.type !== "resolveField")
@@ -977,7 +1588,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
977
1588
  types.set(node.label, [...types.get(node.label) ?? [], node]);
978
1589
  for (const node of typed)
979
1590
  for (const field of node.fields ?? []) {
980
- const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
1591
+ const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
981
1592
  field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
982
1593
  if (field.ref)
983
1594
  this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
@@ -1059,61 +1670,52 @@ ${label ?? ""}`;
1059
1670
  else
1060
1671
  this.#edges.set(key, { from, to, kind, label, count: 1, at });
1061
1672
  }
1062
- #guard(node, guard, file) {
1063
- const [role, local] = guard.includes(" ") ? guard.split(" ") : [null, guard];
1064
- if (!local || local === "?")
1065
- return;
1066
- this.#addEdge(node.id, this.#guardIdOf(local, file), "guard", role, node.at);
1067
- }
1068
- #declareGuards(facts) {
1069
- const factories = new Map;
1070
- for (const file of facts)
1071
- for (const fact of file.guardFactories) {
1072
- const factory = { fact, owner: file.owner, fileName: file.fileName, made: [] };
1073
- factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
1074
- }
1075
- const sameFile = (factory, name) => name ? factories.get(name)?.find((other) => other.fileName === factory.fileName) : undefined;
1076
- const declare = (guard, factory) => {
1077
- this.#guards.set(guard.name, [...this.#guards.get(guard.name) ?? [], guard]);
1078
- factory?.made.push(guard);
1079
- };
1080
- const madeBy = (factory, fileName, extent) => {
1081
- const parts = [{ fileName, extent }];
1082
- const seen = new Set;
1083
- for (let current = factory;current && !seen.has(current); current = sameFile(current, current.fact.via)) {
1084
- seen.add(current);
1085
- parts.push({ fileName: current.fileName, extent: current.fact.extent });
1086
- }
1087
- return parts;
1088
- };
1089
- for (const file of facts) {
1090
- const { owner, fileName } = file;
1091
- for (const { name, span, extent, scope, doc, factory: callee } of file.guards) {
1092
- const factory = callee ? factories.get(callee)?.find((other) => other.fileName === fileName) : undefined;
1093
- declare({ name, owner, span, scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1673
+ #addGuarded(file) {
1674
+ const { owner } = file;
1675
+ for (const slice of file.slices) {
1676
+ const refName = slice.model;
1677
+ if (!refName)
1678
+ continue;
1679
+ const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
1680
+ for (const guard of slice.guards)
1681
+ this.#guard(fallback, guard, file);
1682
+ for (const key of slice.keys) {
1683
+ const detail = [
1684
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
1685
+ ...key.live ? ["live"] : []
1686
+ ];
1687
+ const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1688
+ for (const guard of key.guards)
1689
+ this.#guard(node, guard, file);
1094
1690
  }
1095
- for (const { name, span, extent, doc, callee } of file.guardCalls) {
1096
- const imported = file.imports.find((item) => item.name === callee);
1097
- if (!imported)
1098
- continue;
1099
- const from = this.#ownerOfSpecifier(fileName, imported.from);
1100
- const candidates = factories.get(imported.imported === "default" || imported.imported === "*" ? callee : imported.imported) ?? [];
1101
- const factory = candidates.find((other) => other.owner === from) ?? this.#soleVisible(candidates, from ?? owner);
1102
- if (factory)
1103
- declare({ name, owner, span, scope: factory.fact.scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1691
+ }
1692
+ for (const endpoint of file.endpoints) {
1693
+ const refName = endpoint.model;
1694
+ if (!refName)
1695
+ continue;
1696
+ for (const key of endpoint.keys) {
1697
+ const detail = [
1698
+ ...key.verb ? [key.verb] : [],
1699
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
1700
+ ];
1701
+ const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1702
+ for (const guard of key.guards)
1703
+ this.#guard(node, guard, file);
1104
1704
  }
1105
1705
  }
1106
- for (const list of factories.values())
1107
- for (const { fact, made } of list)
1108
- if (made.every((guard) => !guard.doc))
1109
- for (const guard of made)
1110
- guard.doc = fact.doc;
1111
- for (const list of this.#guards.values())
1112
- for (const guard of list)
1113
- this.#guardNodeOf(guard);
1706
+ }
1707
+ #guard(node, guard, file) {
1708
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
1709
+ if (!local || local === "?")
1710
+ return;
1711
+ const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
1712
+ if (id)
1713
+ this.#addEdge(node.id, id, "guard", role, node.at);
1114
1714
  }
1115
1715
  #guardIdOf(local, site) {
1116
- const found = this.#findGuard(local, site, 0);
1716
+ const found = this.#resolver.find(local, site);
1717
+ if ("external" in found)
1718
+ return null;
1117
1719
  if ("guard" in found)
1118
1720
  return this.#guardNodeOf(found.guard);
1119
1721
  const { name, from } = found;
@@ -1126,99 +1728,18 @@ ${label ?? ""}`;
1126
1728
  node.doc = builtin.doc;
1127
1729
  return node.id;
1128
1730
  }
1129
- #findGuard(local, site, depth) {
1130
- const [head = local, member] = local.split(".");
1131
- const imported = site.imports.find((item) => item.name === head);
1132
- const from = imported ? this.#ownerOfSpecifier(site.fileName, imported.from) : undefined;
1133
- const holder = imported ? from : site.owner;
1134
- if (member && imported?.imported !== "*" && holder !== undefined && holder !== frameworkOwner && depth < maxMapDepth) {
1135
- const objectName = imported && imported.imported !== "default" ? imported.imported : head;
1136
- for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
1137
- const found = this.#findGuard(entry.name, entry.site, depth + 1);
1138
- if ("guard" in found)
1139
- return found;
1140
- }
1141
- }
1142
- const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1143
- const declared = this.#guards.get(name) ?? [];
1144
- const guard = from === undefined ? declared.find((other) => other.owner === site.owner) ?? declared.find((other) => this.#visible(other.owner, site.owner)) : declared.find((other) => other.owner === from) ?? (from === frameworkOwner ? undefined : this.#soleVisible(declared, from));
1145
- return guard ? { guard } : { name, from };
1146
- }
1147
- #mapEntriesOf(objectName, member, fileName, owner) {
1148
- const key = `${objectName}
1149
- ${member}
1150
- ${fileName ?? ""}
1151
- ${owner ?? ""}`;
1152
- const cached = this.#mapEntries.get(key);
1153
- if (cached)
1154
- return cached;
1155
- const { ts, reader } = this.#source;
1156
- const entries = [];
1157
- const fileNames = [
1158
- ...fileName ? [fileName] : [],
1159
- ...this.#source.files.filter(([other, words]) => other !== fileName && owner !== null && !testFilePattern.test(other) && words.includes(`
1160
- ${objectName}
1161
- `) && words.includes(`
1162
- ${member}
1163
- `) && this.#source.ownerOf(other) === owner).map(([other]) => other)
1164
- ];
1165
- for (const holder of fileNames) {
1166
- const text = this.#source.readText(holder);
1167
- if (text === undefined)
1168
- continue;
1169
- const source = this.#source.parse(holder, text);
1170
- const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
1171
- for (const statement of source.statements) {
1172
- if (!ts.isVariableStatement(statement))
1173
- continue;
1174
- const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
1175
- const object = map && reader.unwrap(map);
1176
- if (!object || !ts.isObjectLiteralExpression(object))
1177
- continue;
1178
- for (const property of object.properties) {
1179
- if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
1180
- entries.push({ name: member, site });
1181
- if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
1182
- continue;
1183
- const value = reader.unwrap(property.initializer);
1184
- if (ts.isIdentifier(value))
1185
- entries.push({ name: value.text, site });
1186
- else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
1187
- entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
1188
- }
1189
- }
1190
- }
1191
- this.#mapEntries.set(key, entries);
1192
- return entries;
1193
- }
1194
- #declarationOf(owner, name, near) {
1195
- const key = `${owner}
1196
- ${name}
1197
- ${near ?? ""}`;
1198
- const cached = this.#declarations.get(key);
1199
- if (cached !== undefined)
1200
- return cached;
1201
- const isNear = (fileName) => {
1202
- const relative = this.#source.relative(fileName);
1203
- return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1204
- };
1205
- const candidates = this.#source.files.filter(([fileName, words]) => !testFilePattern.test(fileName) && words.includes(`
1206
- ${name}
1207
- `) && this.#source.ownerOf(fileName) === owner).map(([fileName]) => fileName);
1208
- let found = null;
1209
- for (const fileName of [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))]) {
1210
- const text = this.#source.readText(fileName);
1211
- if (text === undefined)
1212
- continue;
1213
- const source = this.#source.parse(fileName, text);
1214
- const declared = this.#routes.declarationIn(source, name);
1215
- if (!declared)
1216
- continue;
1217
- found = { source, statement: declared.statement, declared: declared.declared };
1218
- break;
1219
- }
1220
- this.#declarations.set(key, found);
1221
- return found;
1731
+ #madeInPlaceIdOf(name, file) {
1732
+ const made = this.#resolver.madeInPlace(file.fileName, name);
1733
+ if (made)
1734
+ return this.#guardNodeOf(made);
1735
+ const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
1736
+ const imported = file.imports.find((item) => item.name === head);
1737
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
1738
+ if (from === undefined || from === frameworkOwner)
1739
+ return null;
1740
+ const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
1741
+ const label = guardLabelOf(name, file.guardLabels);
1742
+ return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
1222
1743
  }
1223
1744
  #addRoutes() {
1224
1745
  const routes = [];
@@ -1271,22 +1792,23 @@ ${dir}`, node);
1271
1792
  }
1272
1793
  #routeGuardIdOf({ name: local, local: declared }, fact, text) {
1273
1794
  if (declared) {
1274
- const file = this.#source.relative(fact.fileName).slice(fact.owner.length + 1);
1275
- const id = this.#key("guard", fact.owner, `${file}#${local}`);
1795
+ const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
1276
1796
  return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
1277
1797
  }
1278
- const imported = fact.imports.find((item) => item.name === local);
1279
- if (!imported)
1798
+ const [head = local, member] = local.split(".");
1799
+ const imported = fact.imports.find((item) => item.name === head);
1800
+ if (!imported || member && imported.imported !== "*")
1280
1801
  return null;
1281
- const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
1282
- const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
1802
+ const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
1803
+ const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
1283
1804
  if (!from || from === frameworkOwner)
1284
1805
  return null;
1285
- const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
1806
+ const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
1286
1807
  if (!found)
1287
1808
  return null;
1288
1809
  const { reader } = this.#source;
1289
- return this.#routeGuardNodeOf(this.#key("guard", from, name), from, name, reader.spanOf(found.source, found.declared), reader.docOf(found.source, found.statement), found.source.text, reader.extentOf(found.source, found.statement));
1810
+ const owner = this.#source.ownerOf(found.source.fileName) ?? from;
1811
+ return this.#routeGuardNodeOf(this.#key("guard", owner, `${this.#resolver.inOwner(found.source.fileName, owner)}#${found.name}`), owner, found.name, reader.spanOf(found.source, found.declared), reader.docOf(found.source, found.statement), found.source.text, found.extent);
1290
1812
  }
1291
1813
  #routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
1292
1814
  const isNew = !this.#nodes.has(id);
@@ -1299,10 +1821,10 @@ ${dir}`, node);
1299
1821
  #adaptorIdOf(local, file) {
1300
1822
  const [head, member] = local.split(".");
1301
1823
  const imported = file.imports.find((item) => item.name === head);
1302
- const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
1824
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
1303
1825
  const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1304
1826
  const declared = this.#adaptors.get(name) ?? [];
1305
- const found = from === undefined ? declared.find((adaptor) => adaptor.owner === file.owner) ?? declared.find((adaptor) => this.#visible(adaptor.owner, file.owner)) : declared.find((adaptor) => adaptor.owner === from) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#visible(adaptor.owner, from)));
1827
+ const found = from === undefined ? declared.find((adaptor) => adaptor.owner === file.owner) ?? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, file.owner)) : declared.find((adaptor) => adaptor.owner === from) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#resolver.visible(adaptor.owner, from)));
1306
1828
  if (found)
1307
1829
  return this.#adaptorNodeOf(found);
1308
1830
  const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
@@ -1318,38 +1840,22 @@ ${dir}`, node);
1318
1840
  this.#digest(node, this.#source.readText(fileName), fact.extent);
1319
1841
  return node.id;
1320
1842
  }
1321
- #guardNodeOf({ name, owner, span, scope, doc, parts }) {
1322
- const node = this.#addNode(this.#key("guard", owner, name), "guard", owner, null, name, span, scope ? [scope] : []);
1843
+ #guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
1844
+ const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
1845
+ const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
1323
1846
  if (doc && !node.doc)
1324
1847
  node.doc = doc;
1325
- const digest = node.digest ? null : this.#hashOf(...parts.map(({ fileName, extent }) => [
1326
- this.#source.readText(fileName),
1327
- extent
1328
- ]));
1329
- if (digest)
1330
- node.digest = digest;
1848
+ if (node.digest)
1849
+ return node.id;
1850
+ const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
1851
+ if (!digest)
1852
+ return node.id;
1853
+ node.digest = digest;
1854
+ const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
1855
+ if (sources.length)
1856
+ node.sources = sources;
1331
1857
  return node.id;
1332
1858
  }
1333
- #ownerOfSpecifier(fileName, specifier) {
1334
- if (frameworkPattern.test(specifier))
1335
- return frameworkOwner;
1336
- const path = this.#pathOfSpecifier(fileName, specifier);
1337
- return path === null ? undefined : ownerOf(path) ?? undefined;
1338
- }
1339
- #pathOfSpecifier(fileName, specifier) {
1340
- const alias = specifier.match(aliasPattern);
1341
- if (alias)
1342
- return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1343
- if (!specifier.startsWith("."))
1344
- return null;
1345
- const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1346
- for (const segment of specifier.split("/"))
1347
- if (segment === "..")
1348
- segments.pop();
1349
- else if (segment !== ".")
1350
- segments.push(segment);
1351
- return segments.join("/");
1352
- }
1353
1859
  #moduleDirOf(fileName) {
1354
1860
  const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
1355
1861
  return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
@@ -1385,20 +1891,13 @@ ${dir}`, node);
1385
1891
  visit(constant);
1386
1892
  return [...fields.values()];
1387
1893
  }
1388
- #visible(target, from) {
1389
- return !from || !target || target === from || target.startsWith("libs/");
1390
- }
1391
- #soleVisible(declared, from) {
1392
- const visible = declared.filter((other) => this.#visible(other.owner, from));
1393
- return visible.length === 1 ? visible[0] : undefined;
1394
- }
1395
1894
  #targetNodeOf({ family, owner, refName, sourceKey }) {
1396
1895
  const isDefault = /^[A-Z]/.test(sourceKey);
1397
1896
  const id = family === "slice" || family === "query" ? this.#key(family, owner, refName, isDefault ? "" : sourceKey) : family === "model" || family === "formSetter" ? this.#key("model", owner, refName) : family === "endpoint" ? this.#key("endpoint", owner, refName, sourceKey) : null;
1398
1897
  return id && this.#nodes.has(id) ? id : null;
1399
1898
  }
1400
1899
  #membersNamed(kind, name, owner, refName) {
1401
- const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
1900
+ const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
1402
1901
  const own = nodes.filter((node) => node.owner === owner);
1403
1902
  return (own.length ? own : nodes).map((node) => node.id);
1404
1903
  }
@@ -1752,6 +2251,10 @@ var endpointNames = (key) => {
1752
2251
  };
1753
2252
 
1754
2253
  // pkgs/@akanjs/language/nameTable.ts
2254
+ var guardDetailOf = (facts, guards) => {
2255
+ const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
2256
+ return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
2257
+ };
1755
2258
  var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
1756
2259
 
1757
2260
  class NameTable {
@@ -1851,7 +2354,7 @@ class NameTable {
1851
2354
  role: "sig",
1852
2355
  gate: "sig",
1853
2356
  label: sliceClass.name,
1854
- detail: sliceClass.guards.length ? [`guards: ${sliceClass.guards.join(", ")}`] : []
2357
+ detail: guardDetailOf(facts, sliceClass.guards)
1855
2358
  });
1856
2359
  if (modelDoc)
1857
2360
  this.#add({
@@ -1916,7 +2419,7 @@ class NameTable {
1916
2419
  sourceKey: slice.name,
1917
2420
  span: slice.span,
1918
2421
  label: `slice ${refName} (default)`,
1919
- detail: slice.guards.length ? [`guards: ${slice.guards.join(", ")}`] : []
2422
+ detail: guardDetailOf(facts, slice.guards)
1920
2423
  };
1921
2424
  this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
1922
2425
  for (const key of slice.keys)
@@ -1926,7 +2429,7 @@ class NameTable {
1926
2429
  sourceKey: key.name,
1927
2430
  span: key.span,
1928
2431
  label: `slice ${key.name}`,
1929
- detail: [...key.guards.length ? [`guards: ${key.guards.join(", ")}`] : [], ...key.live ? ["live"] : []]
2432
+ detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
1930
2433
  }, refName, key.name, modelNameSet, slice.name);
1931
2434
  }
1932
2435
  if (filterClass) {
@@ -1983,7 +2486,7 @@ class NameTable {
1983
2486
  role: key.verb === "pubsub" ? "subscribe" : "listen",
1984
2487
  gate: null,
1985
2488
  label: `${key.verb} ${key.name}`,
1986
- detail: key.guards.length ? [`guards: ${key.guards.join(", ")}`] : []
2489
+ detail: guardDetailOf(facts, key.guards)
1987
2490
  });
1988
2491
  }
1989
2492
  }
@@ -2041,14 +2544,12 @@ var dictionaryPattern = /\.dictionary\.ts$/;
2041
2544
  var srvkitPattern = /\/srvkit\//;
2042
2545
  var wordPattern = /[A-Za-z_$][\w$]*/g;
2043
2546
  var identifierPattern = /^[A-Za-z_$][\w$]*$/;
2044
- var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
2045
2547
  var stateReceivers = new Set(["use", "sel", "get", "do"]);
2046
2548
  var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
2047
2549
  var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
2048
2550
  var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
2049
2551
  var parseCacheSize = 64;
2050
2552
  var pathOf = (fileName) => fileName.replace(/\\/g, "/");
2051
- var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
2052
2553
  var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
2053
2554
 
2054
2555
  class AkanIndex {