@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.js CHANGED
@@ -8,7 +8,8 @@ var isEdited = (before, after) => before.digest !== undefined && after.digest !=
8
8
  var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
9
9
 
10
10
  class AkanGraphDiff {
11
- static between(base, head) {
11
+ static between(given, head) {
12
+ const base = AkanGraphDiff.#renamed(given, head);
12
13
  const baseNodes = new Map(base.nodes.map((node) => [node.id, node]));
13
14
  const headNodes = new Map(head.nodes.map((node) => [node.id, node]));
14
15
  const marked = new Map;
@@ -38,6 +39,40 @@ class AkanGraphDiff {
38
39
  summary[node.change] += 1;
39
40
  return { version: 1, nodes, edges, summary };
40
41
  }
42
+ static #renamed(base, head) {
43
+ const baseIds = new Set(base.nodes.map((node) => node.id));
44
+ const headIds = new Set(head.nodes.map((node) => node.id));
45
+ const pairKeyOf = ({ kind, owner, name, at }) => at ? `${kind}
46
+ ${owner ?? ""}
47
+ ${name}
48
+ ${at.file}` : null;
49
+ const shortIdOf = ({ kind, owner, name }) => `${kind}:${owner ?? ""}:${name}`;
50
+ const isFileKeyed = (node) => node.id !== shortIdOf(node) && node.id.startsWith(`${node.kind}:${node.owner ?? ""}:`) && node.id.endsWith(`#${node.name}`);
51
+ const fromBase = new Map(base.nodes.flatMap((node) => {
52
+ const key = headIds.has(node.id) ? null : pairKeyOf(node);
53
+ return key ? [[key, node]] : [];
54
+ }));
55
+ const renames = new Map;
56
+ for (const node of head.nodes) {
57
+ if (baseIds.has(node.id))
58
+ continue;
59
+ const key = pairKeyOf(node);
60
+ const before = key ? fromBase.get(key) : undefined;
61
+ if (!before)
62
+ continue;
63
+ const isPair = isFileKeyed(node) ? before.id === shortIdOf(node) : isFileKeyed(before) && node.id === shortIdOf(before);
64
+ if (isPair)
65
+ renames.set(before.id, node.id);
66
+ }
67
+ if (!renames.size)
68
+ return base;
69
+ const rename = (id) => renames.get(id) ?? id;
70
+ return {
71
+ ...base,
72
+ nodes: base.nodes.map((node) => renames.has(node.id) ? { ...node, id: rename(node.id) } : node),
73
+ edges: base.edges.map((edge) => ({ ...edge, from: rename(edge.from), to: rename(edge.to) }))
74
+ };
75
+ }
41
76
  static #mark(node, change) {
42
77
  return node.fields ? { ...node, change, fields: node.fields.map((field) => ({ ...field, change })) } : { ...node, change };
43
78
  }
@@ -71,6 +106,239 @@ class AkanGraphDiff {
71
106
  return node.fields ? { ...node, fields: node.fields.map(({ digest: _field, ...field }) => field) } : node;
72
107
  }
73
108
  }
109
+ // pkgs/@akanjs/language/guardFacts.ts
110
+ var guardLabelOf = (guard, labels) => {
111
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
112
+ const label = labels[local];
113
+ return label === undefined ? guard : role ? `${role} ${label}` : label;
114
+ };
115
+ var maxLabelArgs = 32;
116
+ var guardRefReaderOf = (ts, ast, unwrap) => {
117
+ const pathOf = (expression) => {
118
+ if (ts.isIdentifier(expression))
119
+ return expression.text;
120
+ if (!ts.isPropertyAccessExpression(expression))
121
+ return null;
122
+ const head = pathOf(expression.expression);
123
+ return head && `${head}.${expression.name.text}`;
124
+ };
125
+ const calleeOf = (call) => pathOf(unwrap(call.expression)) ?? "?";
126
+ const labelOf = (source, call) => {
127
+ const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
128
+ return `${calleeOf(call)}(${args.length <= maxLabelArgs && !args.includes(`
129
+ `) ? args : "…"})`;
130
+ };
131
+ const guardRefOf = (expression, made, member) => {
132
+ const value = unwrap(expression);
133
+ if (!ts.isCallExpression(value))
134
+ return pathOf(value) ?? ast.expressionName(value);
135
+ const prefix = `${member}:${calleeOf(value)}@`;
136
+ const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
137
+ made.push({ call: value, name });
138
+ return name;
139
+ };
140
+ const guardsIn = (source, object, made, member) => {
141
+ const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
142
+ if (!guards)
143
+ return [];
144
+ const refOf = (expression) => guardRefOf(expression, made, member);
145
+ if (ts.isArrayLiteralExpression(guards))
146
+ return guards.elements.map(refOf).filter((name) => name !== null);
147
+ if (ts.isObjectLiteralExpression(guards))
148
+ return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
149
+ const value = unwrap(property.initializer);
150
+ const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
151
+ return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
152
+ });
153
+ const single = refOf(guards);
154
+ return single ? [single] : [];
155
+ };
156
+ return { pathOf, labelOf, guardsIn };
157
+ };
158
+ var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf) => {
159
+ const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
160
+ const scopeOf = (node) => {
161
+ const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
162
+ const value = scope && unwrap(scope);
163
+ return value && ts.isStringLiteralLike(value) ? value.text : null;
164
+ };
165
+ const functionsOf = (source) => {
166
+ const functions = new Map;
167
+ for (const statement of source.statements) {
168
+ if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
169
+ functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
170
+ if (!ts.isVariableStatement(statement))
171
+ continue;
172
+ for (const declaration of statement.declarationList.declarations) {
173
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
174
+ if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
175
+ functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
176
+ }
177
+ }
178
+ return functions;
179
+ };
180
+ const returnedOf = (body, guardNames) => {
181
+ const locals = new Map;
182
+ const pending = [];
183
+ const visit = (node) => {
184
+ if (ts.isClassDeclaration(node) && node.name)
185
+ locals.set(node.name.text, node);
186
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
187
+ locals.set(node.name.text, node.initializer);
188
+ if (ts.isReturnStatement(node) && node.expression)
189
+ pending.push(node.expression);
190
+ if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
191
+ ts.forEachChild(node, visit);
192
+ };
193
+ if (ts.isBlock(body))
194
+ ts.forEachChild(body, visit);
195
+ else
196
+ pending.push(body);
197
+ let guard = null;
198
+ const calls = [];
199
+ const seen = new Set;
200
+ const passed = new Set;
201
+ for (let node = pending.pop();node; node = pending.pop()) {
202
+ if (seen.has(node))
203
+ continue;
204
+ seen.add(node);
205
+ const value = ts.isExpression(node) ? unwrap(node) : node;
206
+ if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
207
+ guard ??= value;
208
+ else if (ts.isConditionalExpression(value))
209
+ pending.push(value.whenTrue, value.whenFalse);
210
+ else if (ts.isBinaryExpression(value))
211
+ pending.push(value.left, value.right);
212
+ else if (ts.isIdentifier(value)) {
213
+ const local = locals.get(value.text);
214
+ if (local)
215
+ pending.push(local);
216
+ } else if (ts.isCallExpression(value)) {
217
+ const callee = pathOf(unwrap(value.expression));
218
+ if (callee && !passed.has(node) && !calls.includes(callee))
219
+ calls.push(callee);
220
+ for (const arg of value.arguments)
221
+ passed.add(arg);
222
+ pending.push(...value.arguments);
223
+ }
224
+ }
225
+ return { guard, calls };
226
+ };
227
+ const guardFactoriesOf = (source, guardNames, isImported) => {
228
+ const functions = functionsOf(source);
229
+ const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
230
+ const members = new Set;
231
+ const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
232
+ for (let isGrowing = true;isGrowing; ) {
233
+ isGrowing = false;
234
+ for (const [name, { guard, calls }] of returned) {
235
+ if (members.has(name) || !guard && !calls.some(isFactoryCall))
236
+ continue;
237
+ members.add(name);
238
+ isGrowing = true;
239
+ }
240
+ }
241
+ const factories = new Map;
242
+ for (const name of members) {
243
+ const found = functions.get(name);
244
+ const made = returned.get(name);
245
+ if (!found || !made)
246
+ continue;
247
+ factories.set(name, {
248
+ name,
249
+ span: spanOf(source, found.name),
250
+ extent: extentOf(source, found.statement),
251
+ scope: made.guard ? scopeOf(made.guard) : null,
252
+ doc: docOf(source, found.statement),
253
+ direct: !!made.guard,
254
+ via: made.calls.filter(isFactoryCall)
255
+ });
256
+ }
257
+ return factories;
258
+ };
259
+ return { implementsGuard, scopeOf, guardFactoriesOf };
260
+ };
261
+ var createGuardFactReader = (ts, ast, base) => {
262
+ const { spanOf, extentOf, unwrap, docOf } = base;
263
+ const { pathOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
264
+ const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf);
265
+ const guardsOf = (source, imports, madeInPlace) => {
266
+ const guardNames = new Set([
267
+ "Guard",
268
+ ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
269
+ ]);
270
+ const imported = new Set(imports.map(({ name }) => name));
271
+ const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
272
+ const factories = guardFactoriesOf(source, guardNames, isImported);
273
+ const guards = [];
274
+ const calls = [];
275
+ const makeOf = (made, value, isInPlace = false) => {
276
+ if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
277
+ guards.push({ ...made, scope: scopeOf(value), factory: null });
278
+ return;
279
+ }
280
+ const callee = ts.isCallExpression(value) ? pathOf(unwrap(value.expression)) : null;
281
+ const factory = callee && factories.get(callee);
282
+ if (factory)
283
+ guards.push({ ...made, scope: factory.scope, factory: factory.name });
284
+ else if (callee && (isImported(callee) || isInPlace))
285
+ calls.push({ ...made, callee, kind: "call", scope: null });
286
+ };
287
+ for (const statement of source.statements) {
288
+ if (ts.isClassDeclaration(statement) && statement.name) {
289
+ const declared = {
290
+ name: statement.name.text,
291
+ span: spanOf(source, statement.name),
292
+ extent: extentOf(source, statement),
293
+ scope: scopeOf(statement),
294
+ doc: docOf(source, statement)
295
+ };
296
+ const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
297
+ const callee = base && pathOf(base.expression);
298
+ if (implementsGuard(statement, guardNames))
299
+ guards.push({ ...declared, factory: null });
300
+ else if (callee)
301
+ calls.push({ ...declared, callee, kind: "extends" });
302
+ }
303
+ if (!ts.isVariableStatement(statement))
304
+ continue;
305
+ for (const declaration of statement.declarationList.declarations) {
306
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
307
+ if (!initializer || !ts.isIdentifier(declaration.name))
308
+ continue;
309
+ const holder = declaration.name.text;
310
+ makeOf({
311
+ name: holder,
312
+ span: spanOf(source, declaration.name),
313
+ extent: extentOf(source, statement),
314
+ doc: docOf(source, statement)
315
+ }, initializer);
316
+ if (!ts.isObjectLiteralExpression(initializer))
317
+ continue;
318
+ for (const property of initializer.properties) {
319
+ const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
320
+ if (key && ts.isPropertyAssignment(property))
321
+ makeOf({
322
+ name: `${holder}.${key}`,
323
+ span: spanOf(source, property.name),
324
+ extent: extentOf(source, property),
325
+ doc: docOf(source, property)
326
+ }, unwrap(property.initializer));
327
+ }
328
+ }
329
+ }
330
+ for (const { call, name } of madeInPlace)
331
+ makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
332
+ const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
333
+ return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
334
+ };
335
+ return { guardsIn, guardsOf };
336
+ };
337
+
338
+ // pkgs/@akanjs/language/escapeRegExp.ts
339
+ var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
340
+ var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
341
+
74
342
  // pkgs/@akanjs/language/moduleFacts.ts
75
343
  var testFilePattern = /\.(?:test|spec)\.tsx?$/;
76
344
  var moduleCallees = new Set([
@@ -139,21 +407,6 @@ var createFactReader = (ts, ast) => {
139
407
  const value = unwrap(expression);
140
408
  return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
141
409
  };
142
- const guardsIn = (object) => {
143
- const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
144
- if (!guards)
145
- return [];
146
- if (ts.isArrayLiteralExpression(guards))
147
- return guards.elements.map(guardNameOf).filter((name) => name !== null);
148
- if (ts.isObjectLiteralExpression(guards))
149
- return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
150
- const value = unwrap(property.initializer);
151
- const names = ts.isArrayLiteralExpression(value) ? value.elements.map(guardNameOf) : [guardNameOf(value)];
152
- return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
153
- });
154
- const single = guardNameOf(guards);
155
- return single ? [single] : [];
156
- };
157
410
  const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
158
411
  const chainOf = (initializer) => {
159
412
  let root = unwrap(initializer);
@@ -170,7 +423,7 @@ var createFactReader = (ts, ast) => {
170
423
  const node = keyName(property);
171
424
  return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
172
425
  });
173
- const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
426
+ const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
174
427
  if (!ts.isPropertyAssignment(property))
175
428
  return [];
176
429
  const name = ast.nodeName(property.name);
@@ -182,7 +435,7 @@ var createFactReader = (ts, ast) => {
182
435
  name,
183
436
  span: spanOf(source, property.name),
184
437
  extent: extentOf(source, property),
185
- guards: root ? guardsIn(optionObjectOf(root)) : [],
438
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
186
439
  live: methods.includes("live"),
187
440
  argCount: methods.filter((method) => argMethods.has(method)).length
188
441
  }
@@ -288,7 +541,7 @@ var createFactReader = (ts, ast) => {
288
541
  return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
289
542
  });
290
543
  };
291
- const endpointKeysOf = (source, builder) => {
544
+ const endpointKeysOf = (source, builder, made, model) => {
292
545
  const verbOf = verbReaderOf(builder?.parameters[0]);
293
546
  return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
294
547
  if (!ts.isPropertyAssignment(property))
@@ -303,7 +556,7 @@ var createFactReader = (ts, ast) => {
303
556
  span: spanOf(source, property.name),
304
557
  extent: extentOf(source, property),
305
558
  verb: root ? verbOf(root.expression) : null,
306
- guards: root ? guardsIn(optionObjectOf(root)) : [],
559
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
307
560
  argCount: methods.filter((method) => argMethods.has(method)).length
308
561
  }
309
562
  ];
@@ -370,22 +623,6 @@ var createFactReader = (ts, ast) => {
370
623
  })) : []
371
624
  ];
372
625
  };
373
- const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
374
- const guardClassIn = (node, guardNames) => {
375
- if ((ts.isClassExpression(node) || ts.isClassDeclaration(node)) && implementsGuard(node, guardNames))
376
- return node;
377
- return ts.forEachChild(node, (child) => guardClassIn(child, guardNames)) ?? null;
378
- };
379
- const callsIn = (node, names) => {
380
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text))
381
- return node.expression.text;
382
- return ts.forEachChild(node, (child) => callsIn(child, names)) ?? null;
383
- };
384
- const scopeOf = (node) => {
385
- const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
386
- const value = scope && unwrap(scope);
387
- return value && ts.isStringLiteralLike(value) ? value.text : null;
388
- };
389
626
  const docOf = (source, node) => {
390
627
  const { text } = source;
391
628
  const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
@@ -401,108 +638,20 @@ var createFactReader = (ts, ast) => {
401
638
  `)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
402
639
  return lines.length ? lines.join(" ") : null;
403
640
  };
404
- const functionsOf = (source) => {
405
- const functions = new Map;
406
- for (const statement of source.statements) {
407
- if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
408
- functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
409
- if (!ts.isVariableStatement(statement))
410
- continue;
411
- for (const declaration of statement.declarationList.declarations) {
412
- const initializer = declaration.initializer && unwrap(declaration.initializer);
413
- if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
414
- functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
415
- }
416
- }
417
- return functions;
418
- };
419
- const guardFactoriesOf = (source, guardNames) => {
420
- const functions = functionsOf(source);
421
- const factories = new Map;
422
- const factoryOf = (name, scope, via) => {
423
- const found = functions.get(name);
424
- if (!found)
425
- return;
426
- factories.set(name, {
427
- name,
428
- span: spanOf(source, found.name),
429
- extent: extentOf(source, found.statement),
430
- scope,
431
- doc: docOf(source, found.statement),
432
- via
433
- });
434
- };
435
- for (const [name, { body }] of functions) {
436
- const guard = guardClassIn(body, guardNames);
437
- if (guard)
438
- factoryOf(name, scopeOf(guard), null);
439
- }
440
- for (let isGrowing = true;isGrowing; ) {
441
- isGrowing = false;
442
- for (const [name, { body }] of functions) {
443
- const called = factories.has(name) ? null : callsIn(body, new Set(factories.keys()));
444
- if (!called)
445
- continue;
446
- factoryOf(name, factories.get(called)?.scope ?? null, called);
447
- isGrowing = true;
448
- }
449
- }
450
- return factories;
451
- };
452
- const guardsOf = (source, imports) => {
453
- const guardNames = new Set([
454
- "Guard",
455
- ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
456
- ]);
457
- const imported = new Set(imports.map(({ name }) => name));
458
- const factories = guardFactoriesOf(source, guardNames);
459
- const guards = [];
460
- const calls = [];
461
- for (const statement of source.statements) {
462
- if (ts.isClassDeclaration(statement) && statement.name && implementsGuard(statement, guardNames))
463
- guards.push({
464
- name: statement.name.text,
465
- span: spanOf(source, statement.name),
466
- extent: extentOf(source, statement),
467
- scope: scopeOf(statement),
468
- doc: docOf(source, statement),
469
- factory: null
470
- });
471
- if (!ts.isVariableStatement(statement))
472
- continue;
473
- for (const declaration of statement.declarationList.declarations) {
474
- const initializer = declaration.initializer && unwrap(declaration.initializer);
475
- if (!initializer || !ts.isIdentifier(declaration.name))
476
- continue;
477
- const made = {
478
- name: declaration.name.text,
479
- span: spanOf(source, declaration.name),
480
- extent: extentOf(source, statement),
481
- doc: docOf(source, statement)
482
- };
483
- if (ts.isClassExpression(initializer) && implementsGuard(initializer, guardNames))
484
- guards.push({ ...made, scope: scopeOf(initializer), factory: null });
485
- if (!ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression))
486
- continue;
487
- const callee = initializer.expression.text;
488
- const factory = factories.get(callee);
489
- if (factory)
490
- guards.push({ ...made, scope: factory.scope, factory: callee });
491
- else if (imported.has(callee))
492
- calls.push({ ...made, callee });
493
- }
494
- }
495
- return { guards, guardFactories: [...factories.values()], guardCalls: calls };
496
- };
641
+ const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
497
642
  const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
498
643
  const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
499
644
  const read = (source, owner) => {
500
645
  const imports = importsIn(source);
646
+ const madeInPlace = [];
501
647
  const facts = {
502
648
  fileName: source.fileName,
503
649
  owner,
504
650
  imports,
505
- ...guardsOf(source, imports),
651
+ guards: [],
652
+ guardFactories: [],
653
+ guardCalls: [],
654
+ guardLabels: {},
506
655
  constants: [],
507
656
  enums: [],
508
657
  filters: [],
@@ -520,64 +669,496 @@ var createFactReader = (ts, ast) => {
520
669
  const heritage = ast.heritageCallNamed(statement, moduleCallees);
521
670
  if (!heritage)
522
671
  continue;
523
- const { callee, call } = heritage;
524
- const name = { name: statement.name.text, span: spanOf(source, statement.name) };
525
- const builder = builderOf(call);
526
- if (callee === "via")
527
- facts.constants.push(constantOf(source, name, call));
528
- else if (callee === "enumOf")
529
- facts.enums.push({ ...name, values: enumValuesOf(call) });
530
- else if (callee === "from")
531
- facts.filters.push({ ...name, queries: filterQueriesOf(source, builder) });
532
- else if (callee === "into")
533
- facts.models.push({ ...name, model: name.name.endsWith("Model") ? lowerFirst(name.name.slice(0, -5)) : null });
534
- else if (callee === "serve")
535
- facts.services.push({
536
- ...name,
537
- model: moduleRefOf(call.arguments[0], "db"),
538
- methods: methodsOf(source, statement),
539
- plugs: plugsOf(source, builder)
540
- });
541
- else if (callee === "slice")
542
- facts.slices.push({
543
- ...name,
544
- model: moduleRefOf(call.arguments[0], "srv"),
545
- guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
546
- keys: sliceKeysOf(source, builder)
547
- });
548
- else if (callee === "internal")
549
- facts.internals.push({
550
- ...name,
551
- model: moduleRefOf(call.arguments[0], "srv"),
552
- keys: internalKeysOf(source, builder)
553
- });
554
- else if (callee === "adapt" || callee === "dangerouslyAdapt") {
555
- const refName = call.arguments[0] && unwrap(call.arguments[0]);
556
- facts.adaptors.push({
557
- ...name,
558
- extent: extentOf(source, statement),
559
- refName: refName && ts.isStringLiteralLike(refName) ? refName.text : null,
560
- plugs: plugsOf(source, builder),
561
- doc: docOf(source, statement)
562
- });
563
- } else if (callee === "endpoint")
564
- facts.endpoints.push({
565
- ...name,
566
- model: moduleRefOf(call.arguments[0], "srv"),
567
- keys: endpointKeysOf(source, builder)
568
- });
569
- else if (callee === "store")
570
- facts.stores.push({
571
- ...name,
572
- model: moduleRefOf(call.arguments[0], "sig"),
573
- stateKeys: objectKeys(source, returnedObject(builder)),
574
- actions: methodsOf(source, statement)
575
- });
672
+ const { callee, call } = heritage;
673
+ const name = { name: statement.name.text, span: spanOf(source, statement.name) };
674
+ const builder = builderOf(call);
675
+ if (callee === "via")
676
+ facts.constants.push(constantOf(source, name, call));
677
+ else if (callee === "enumOf")
678
+ facts.enums.push({ ...name, values: enumValuesOf(call) });
679
+ else if (callee === "from")
680
+ facts.filters.push({ ...name, queries: filterQueriesOf(source, builder) });
681
+ else if (callee === "into")
682
+ facts.models.push({ ...name, model: name.name.endsWith("Model") ? lowerFirst(name.name.slice(0, -5)) : null });
683
+ else if (callee === "serve")
684
+ facts.services.push({
685
+ ...name,
686
+ model: moduleRefOf(call.arguments[0], "db"),
687
+ methods: methodsOf(source, statement),
688
+ plugs: plugsOf(source, builder)
689
+ });
690
+ else if (callee === "slice") {
691
+ const model = moduleRefOf(call.arguments[0], "srv");
692
+ const member = model ?? name.name;
693
+ facts.slices.push({
694
+ ...name,
695
+ model,
696
+ guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
697
+ keys: sliceKeysOf(source, builder, madeInPlace, member)
698
+ });
699
+ } else if (callee === "internal")
700
+ facts.internals.push({
701
+ ...name,
702
+ model: moduleRefOf(call.arguments[0], "srv"),
703
+ keys: internalKeysOf(source, builder)
704
+ });
705
+ else if (callee === "adapt" || callee === "dangerouslyAdapt") {
706
+ const refName = call.arguments[0] && unwrap(call.arguments[0]);
707
+ facts.adaptors.push({
708
+ ...name,
709
+ extent: extentOf(source, statement),
710
+ refName: refName && ts.isStringLiteralLike(refName) ? refName.text : null,
711
+ plugs: plugsOf(source, builder),
712
+ doc: docOf(source, statement)
713
+ });
714
+ } else if (callee === "endpoint") {
715
+ const model = moduleRefOf(call.arguments[0], "srv");
716
+ facts.endpoints.push({
717
+ ...name,
718
+ model,
719
+ keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
720
+ });
721
+ } else if (callee === "store")
722
+ facts.stores.push({
723
+ ...name,
724
+ model: moduleRefOf(call.arguments[0], "sig"),
725
+ stateKeys: objectKeys(source, returnedObject(builder)),
726
+ actions: methodsOf(source, statement)
727
+ });
728
+ }
729
+ return { ...facts, ...guardsOf(source, imports, madeInPlace) };
730
+ };
731
+ return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
732
+ };
733
+
734
+ // pkgs/@akanjs/language/guardResolver.ts
735
+ var frameworkOwner = "akanjs";
736
+ var frameworkPattern = /^akanjs(?:\/|$)/;
737
+ var aliasPattern = /^@(apps|libs)\/([^/]+)/;
738
+ var maxMapDepth = 4;
739
+ var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
740
+ var declaringPatternOf = (name, isAnyBinding) => {
741
+ const escaped = escapeRegExp(name);
742
+ const braced = "\\{(?:[^}]*?[\\s,])?";
743
+ const leads = [
744
+ "function\\s*\\*?\\s*",
745
+ "(?:const|let|var)\\s+",
746
+ `export\\s*${braced}`,
747
+ ...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
748
+ ];
749
+ return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
750
+ };
751
+
752
+ class GuardResolver {
753
+ #source;
754
+ #routes;
755
+ #guards = new Map;
756
+ #madeInPlace = new Map;
757
+ #mapEntries = new Map;
758
+ #reexports = new Map;
759
+ #declarations = new Map;
760
+ #starExports = new Map;
761
+ #ownerFiles = null;
762
+ #modules = null;
763
+ constructor(source, routes) {
764
+ this.#source = source;
765
+ this.#routes = routes;
766
+ }
767
+ find(local, site) {
768
+ return this.#findGuard(local, site, 0);
769
+ }
770
+ madeInPlace(fileName, name) {
771
+ return this.#madeInPlace.get(`${fileName}
772
+ ${name}`);
773
+ }
774
+ declare(facts) {
775
+ const factories = new Map;
776
+ const declaredFactories = [];
777
+ for (const file of facts)
778
+ for (const fact of file.guardFactories) {
779
+ const factory = {
780
+ fact,
781
+ owner: file.owner,
782
+ site: file,
783
+ made: [],
784
+ isReal: fact.direct,
785
+ scope: fact.scope,
786
+ targets: []
787
+ };
788
+ factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
789
+ declaredFactories.push(factory);
790
+ }
791
+ for (const factory of declaredFactories)
792
+ factory.targets = factory.fact.via.map((callee) => this.#factoryAt(factories, callee, factory.site)).filter((target) => target !== undefined);
793
+ for (let isGrowing = true;isGrowing; ) {
794
+ isGrowing = false;
795
+ for (const factory of declaredFactories) {
796
+ const target = factory.targets.find((other) => other.isReal);
797
+ if (factory.isReal || !target)
798
+ continue;
799
+ factory.isReal = true;
800
+ factory.scope ??= target.scope;
801
+ isGrowing = true;
802
+ }
803
+ }
804
+ const partsOf = (factory, seen = new Set) => {
805
+ if (seen.has(factory))
806
+ return [];
807
+ seen.add(factory);
808
+ return [
809
+ { fileName: factory.site.fileName, extent: factory.fact.extent },
810
+ ...factory.targets.filter((target) => target.isReal).flatMap((target) => partsOf(target, seen))
811
+ ];
812
+ };
813
+ const declare = (file, made, scope, parts) => {
814
+ const { name, span, extent, doc } = made;
815
+ const label = file.guardLabels[name];
816
+ const own = { fileName: file.fileName, extent };
817
+ const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
818
+ if (name.includes("@"))
819
+ this.#madeInPlace.set(`${file.fileName}
820
+ ${name}`, guard);
821
+ else
822
+ this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
823
+ return guard;
824
+ };
825
+ const extending = [];
826
+ for (const file of facts) {
827
+ for (const made of file.guards) {
828
+ const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
829
+ if (!made.factory)
830
+ declare(file, made, made.scope, []);
831
+ else if (factory?.isReal)
832
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
833
+ }
834
+ for (const made of file.guardCalls) {
835
+ if (made.kind === "extends") {
836
+ extending.push([file, made]);
837
+ continue;
838
+ }
839
+ const factory = this.#factoryAt(factories, made.callee, file);
840
+ if (factory?.isReal)
841
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
842
+ }
843
+ }
844
+ for (let isGrowing = true;isGrowing; ) {
845
+ isGrowing = false;
846
+ for (const [idx, entry] of extending.entries()) {
847
+ const [file, made] = entry;
848
+ const found = this.#findGuard(made.callee, file, 0, true);
849
+ if (!("guard" in found))
850
+ continue;
851
+ declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
852
+ extending.splice(idx, 1);
853
+ isGrowing = true;
854
+ break;
855
+ }
856
+ }
857
+ for (const { fact, made, isReal } of declaredFactories)
858
+ if (isReal && made.every((guard) => !guard.doc))
859
+ for (const guard of made)
860
+ guard.doc = fact.doc;
861
+ for (const list of this.#guards.values())
862
+ for (const guard of list)
863
+ if (list.some((other) => other !== guard && other.owner === guard.owner))
864
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
865
+ for (const guard of this.#madeInPlace.values())
866
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
867
+ return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
868
+ }
869
+ #factoryAt(factories, callee, site, depth = 0) {
870
+ const [head = callee, member] = callee.split(".");
871
+ const imported = site.imports.find((item) => item.name === head);
872
+ if (!imported)
873
+ return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
874
+ if (member && imported.imported !== "*")
875
+ return;
876
+ const from = this.ownerOfSpecifier(site.fileName, imported.from);
877
+ if (from === undefined || from === frameworkOwner)
878
+ return;
879
+ const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
880
+ const candidates = factories.get(name) ?? [];
881
+ const near = this.pathOfSpecifier(site.fileName, imported.from);
882
+ const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
883
+ if (own)
884
+ return own;
885
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
886
+ if (reexport)
887
+ return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
888
+ return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
889
+ }
890
+ #findGuard(local, site, depth, isBase = false) {
891
+ const segments = local.split(".");
892
+ const head = segments[0] ?? local;
893
+ const imported = site.imports.find((item) => item.name === head);
894
+ const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
895
+ if (imported && from === undefined)
896
+ return { external: true };
897
+ const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
898
+ const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
899
+ const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
900
+ const holder = imported ? from ?? null : site.owner;
901
+ const [objectName, member] = path;
902
+ if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
903
+ const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
904
+ if (made)
905
+ return { guard: made };
906
+ for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
907
+ const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
908
+ if ("guard" in found)
909
+ return found;
910
+ }
911
+ }
912
+ const name = path[path.length - 1] ?? head;
913
+ if (from === frameworkOwner)
914
+ return { name, from };
915
+ const declared = this.#guards.get(name) ?? [];
916
+ const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
917
+ if (own)
918
+ return { guard: own };
919
+ if (!imported) {
920
+ const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
921
+ return visible ? { guard: visible } : { name, from };
922
+ }
923
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
924
+ if (reexport)
925
+ return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
926
+ const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
927
+ return reached ? { guard: reached } : { name, from };
928
+ }
929
+ reexportOf(owner, name, near) {
930
+ const key = `${owner}
931
+ ${name}
932
+ ${near ?? ""}`;
933
+ const cached = this.#reexports.get(key);
934
+ if (cached !== undefined)
935
+ return cached;
936
+ let found = null;
937
+ const exporting = new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
938
+ for (const fileName of this.#filesOf(owner, name, near)) {
939
+ const text = this.#source.readText(fileName);
940
+ if (text === undefined || !exporting.test(text))
941
+ continue;
942
+ const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
943
+ if (!reexport)
944
+ continue;
945
+ const site = { fileName, owner, imports: [] };
946
+ found = reexport.from ? {
947
+ local: reexport.name,
948
+ site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
949
+ } : { local: reexport.name, site };
950
+ break;
951
+ }
952
+ this.#reexports.set(key, found);
953
+ return found;
954
+ }
955
+ #reexportIn(source, name) {
956
+ const { ts, reader } = this.#source;
957
+ for (const statement of source.statements) {
958
+ if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
959
+ continue;
960
+ const { exportClause, moduleSpecifier } = statement;
961
+ if (!exportClause || !ts.isNamedExports(exportClause))
962
+ continue;
963
+ const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
964
+ if (!element)
965
+ continue;
966
+ const original = (element.propertyName ?? element.name).text;
967
+ if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
968
+ return { name: original, from: moduleSpecifier.text };
969
+ const imported = reader.importsIn(source).find((item) => item.name === original);
970
+ if (!imported)
971
+ return { name: original, from: null };
972
+ return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
973
+ }
974
+ return null;
975
+ }
976
+ #filesOf(owner, name, near) {
977
+ const isNear = (fileName) => {
978
+ const relative = this.#source.relative(fileName);
979
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
980
+ };
981
+ const candidates = this.filesIn(owner).filter(([, words]) => words.includes(`
982
+ ${name}
983
+ `)).map(([fileName]) => fileName);
984
+ return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
985
+ }
986
+ #mapEntriesOf(objectName, member, fileName, owner) {
987
+ const key = `${objectName}
988
+ ${member}
989
+ ${fileName ?? ""}
990
+ ${owner ?? ""}`;
991
+ const cached = this.#mapEntries.get(key);
992
+ if (cached)
993
+ return cached;
994
+ const { ts, reader } = this.#source;
995
+ const entries = [];
996
+ const fileNames = [
997
+ ...fileName ? [fileName] : [],
998
+ ...(owner === null ? [] : this.filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
999
+ ${objectName}
1000
+ `) && words.includes(`
1001
+ ${member}
1002
+ `)).map(([other]) => other)
1003
+ ];
1004
+ for (const holder of fileNames) {
1005
+ const text = this.#source.readText(holder);
1006
+ if (text === undefined)
1007
+ continue;
1008
+ const source = this.#source.parse(holder, text);
1009
+ const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
1010
+ for (const statement of source.statements) {
1011
+ if (!ts.isVariableStatement(statement))
1012
+ continue;
1013
+ const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
1014
+ const object = map && reader.unwrap(map);
1015
+ if (!object || !ts.isObjectLiteralExpression(object))
1016
+ continue;
1017
+ for (const property of object.properties) {
1018
+ if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
1019
+ entries.push({ name: member, site });
1020
+ if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
1021
+ continue;
1022
+ const value = reader.unwrap(property.initializer);
1023
+ if (ts.isIdentifier(value))
1024
+ entries.push({ name: value.text, site });
1025
+ else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
1026
+ entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
1027
+ }
1028
+ }
1029
+ }
1030
+ this.#mapEntries.set(key, entries);
1031
+ return entries;
1032
+ }
1033
+ declarationOf(owner, name, near, depth = 0) {
1034
+ const key = `${owner}
1035
+ ${name}
1036
+ ${near ?? ""}`;
1037
+ const cached = this.#declarations.get(key);
1038
+ if (cached !== undefined)
1039
+ return cached;
1040
+ this.#declarations.set(key, null);
1041
+ const declaring = declaringPatternOf(name, false);
1042
+ let found = null;
1043
+ for (const fileName of this.#filesOf(owner, name, near)) {
1044
+ const text = this.#source.readText(fileName);
1045
+ if (text === undefined || !declaring.test(text))
1046
+ continue;
1047
+ const source = this.#source.parse(fileName, text);
1048
+ const declared = this.#routes.declarationIn(source, name);
1049
+ if (declared) {
1050
+ found = { source, ...declared, name };
1051
+ break;
1052
+ }
1053
+ const reexport = this.#reexportIn(source, name);
1054
+ if (!reexport)
1055
+ continue;
1056
+ if (!reexport.from) {
1057
+ const local = this.#routes.declarationIn(source, reexport.name);
1058
+ found = local ? { source, ...local, name: reexport.name } : null;
1059
+ break;
1060
+ }
1061
+ const next = this.ownerOfSpecifier(fileName, reexport.from);
1062
+ if (next && next !== frameworkOwner && depth < maxMapDepth)
1063
+ found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
1064
+ break;
576
1065
  }
577
- return facts;
578
- };
579
- return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
580
- };
1066
+ this.#declarations.set(key, found);
1067
+ return found;
1068
+ }
1069
+ #throughStar(declared, owner, name, near) {
1070
+ if (!declared.length)
1071
+ return;
1072
+ const { owners, isOpaque } = this.#starExportsOf(owner, near);
1073
+ const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
1074
+ return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
1075
+ }
1076
+ #starExportsOf(owner, near) {
1077
+ const cached = this.#starExports.get(near);
1078
+ if (cached)
1079
+ return cached;
1080
+ const found = { owners: [], isOpaque: false };
1081
+ this.#starExports.set(near, found);
1082
+ const paths = [near];
1083
+ for (let idx = 0;idx < paths.length && !found.isOpaque; idx++)
1084
+ for (const fileName of this.#modulesAt(paths[idx] ?? "")) {
1085
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
1086
+ const target = this.ownerOfSpecifier(fileName, specifier);
1087
+ const path = this.pathOfSpecifier(fileName, specifier);
1088
+ if (target === undefined || target === frameworkOwner || path === null)
1089
+ found.isOpaque = true;
1090
+ else if (!paths.includes(path))
1091
+ paths.push(path);
1092
+ if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
1093
+ found.owners.push(target);
1094
+ }
1095
+ }
1096
+ return found;
1097
+ }
1098
+ #modulesAt(path) {
1099
+ if (!this.#modules) {
1100
+ this.#modules = new Map;
1101
+ for (const [fileName] of this.#source.files) {
1102
+ const module = this.#source.relative(fileName).replace(/\.tsx?$/, "");
1103
+ for (const key of module.endsWith("/index") ? [module, module.slice(0, -6)] : [module])
1104
+ this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
1105
+ }
1106
+ }
1107
+ return this.#modules.get(path) ?? [];
1108
+ }
1109
+ declares(owner, name) {
1110
+ const declaring = declaringPatternOf(name, true);
1111
+ return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
1112
+ }
1113
+ filesIn(owner) {
1114
+ if (!this.#ownerFiles) {
1115
+ this.#ownerFiles = new Map;
1116
+ for (const entry of this.#source.files) {
1117
+ const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
1118
+ const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
1119
+ if (files)
1120
+ files.push(entry);
1121
+ else if (fileOwner !== null)
1122
+ this.#ownerFiles.set(fileOwner, [entry]);
1123
+ }
1124
+ }
1125
+ return this.#ownerFiles.get(owner) ?? [];
1126
+ }
1127
+ #nearest(items, fileOf, near) {
1128
+ const isNear = (fileName) => {
1129
+ const relative = this.#source.relative(fileName);
1130
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1131
+ };
1132
+ return items.find((item) => isNear(fileOf(item))) ?? items[0];
1133
+ }
1134
+ inOwner(fileName, owner) {
1135
+ const relative = this.#source.relative(fileName);
1136
+ return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
1137
+ }
1138
+ ownerOfSpecifier(fileName, specifier) {
1139
+ if (frameworkPattern.test(specifier))
1140
+ return frameworkOwner;
1141
+ const path = this.pathOfSpecifier(fileName, specifier);
1142
+ return path === null ? undefined : ownerOf(path) ?? undefined;
1143
+ }
1144
+ pathOfSpecifier(fileName, specifier) {
1145
+ const alias = specifier.match(aliasPattern);
1146
+ if (alias)
1147
+ return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1148
+ if (!specifier.startsWith("."))
1149
+ return null;
1150
+ const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1151
+ for (const segment of specifier.split("/"))
1152
+ if (segment === "..")
1153
+ segments.pop();
1154
+ else if (segment !== ".")
1155
+ segments.push(segment);
1156
+ return segments.join("/");
1157
+ }
1158
+ visible(target, from) {
1159
+ return !from || !target || target === from || target.startsWith("libs/");
1160
+ }
1161
+ }
581
1162
 
582
1163
  // pkgs/akanjs/common/trimSlashes.ts
583
1164
  var trimTrailingSlashes = (value) => {
@@ -726,24 +1307,23 @@ rootLayout
726
1307
  }
727
1308
  declarationIn(source, name) {
728
1309
  const ts = this.#ts;
1310
+ const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
1311
+ const [first] = overloads;
1312
+ const last = overloads.at(-1);
1313
+ if (first?.name && last)
1314
+ return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
729
1315
  for (const statement of source.statements) {
730
- if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
731
- return { statement, declared: statement.name, initializer: null };
732
1316
  if (!ts.isVariableStatement(statement))
733
1317
  continue;
734
1318
  const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
735
1319
  if (declaration)
736
- return { statement, declared: declaration.name, initializer: declaration.initializer ?? null };
1320
+ return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
737
1321
  }
738
1322
  return null;
739
1323
  }
740
1324
  #guardOf(source, exported) {
741
1325
  const ts = this.#ts;
742
1326
  let current = ts.isExportAssignment(exported) ? this.#reader.unwrap(exported.expression) : null;
743
- if (current && ts.isIdentifier(current)) {
744
- const initializer = this.declarationIn(source, current.text)?.initializer;
745
- current = initializer ? this.#reader.unwrap(initializer) : null;
746
- }
747
1327
  while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
748
1328
  const { name, expression } = current.expression;
749
1329
  const [stage] = current.arguments;
@@ -755,9 +1335,20 @@ rootLayout
755
1335
  }
756
1336
  #callsIn(source, stage) {
757
1337
  const ts = this.#ts;
758
- const imported = new Set(this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from)).map(({ name }) => name));
1338
+ const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
1339
+ const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
1340
+ const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
759
1341
  const calls = new Map;
760
- const callOf = (name) => {
1342
+ const callOf = (callee, shadowed) => {
1343
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
1344
+ const name = `${callee.expression.text}.${callee.name.text}`;
1345
+ if (!calls.has(name))
1346
+ calls.set(name, { name, local: null });
1347
+ return true;
1348
+ }
1349
+ if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
1350
+ return false;
1351
+ const name = callee.text;
761
1352
  if (calls.has(name))
762
1353
  return true;
763
1354
  const declared = this.declarationIn(source, name);
@@ -767,23 +1358,71 @@ rootLayout
767
1358
  name,
768
1359
  local: declared && {
769
1360
  span: this.#reader.spanOf(source, declared.declared),
770
- extent: this.#reader.extentOf(source, declared.statement),
1361
+ extent: declared.extent,
771
1362
  doc: this.#reader.docOf(source, declared.statement)
772
1363
  }
773
1364
  });
774
1365
  return true;
775
1366
  };
776
- const visit = (node) => {
777
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && callOf(node.expression.text))
1367
+ const visit = (node, outer) => {
1368
+ const shadowed = this.#scopeOf(node, outer);
1369
+ if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
778
1370
  return;
779
- ts.forEachChild(node, visit);
1371
+ ts.forEachChild(node, (child) => visit(child, shadowed));
780
1372
  };
781
1373
  if (ts.isIdentifier(stage))
782
- callOf(stage.text);
1374
+ callOf(stage, new Set);
783
1375
  else
784
- visit(stage);
1376
+ visit(stage, new Set);
785
1377
  return [...calls.values()];
786
1378
  }
1379
+ #scopeOf(node, outer) {
1380
+ const ts = this.#ts;
1381
+ const names = [];
1382
+ const bind = (name) => {
1383
+ if (ts.isIdentifier(name))
1384
+ names.push(name.text);
1385
+ else
1386
+ for (const element of name.elements)
1387
+ if (!ts.isOmittedExpression(element))
1388
+ bind(element.name);
1389
+ };
1390
+ const declare = (statement) => {
1391
+ if (ts.isVariableStatement(statement))
1392
+ for (const declaration of statement.declarationList.declarations)
1393
+ bind(declaration.name);
1394
+ else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
1395
+ names.push(statement.name.text);
1396
+ };
1397
+ const hoist = (child) => {
1398
+ if (ts.isFunctionLike(child) || ts.isClassLike(child))
1399
+ return;
1400
+ if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
1401
+ for (const declaration of child.declarations)
1402
+ bind(declaration.name);
1403
+ ts.forEachChild(child, hoist);
1404
+ };
1405
+ if (ts.isFunctionLike(node)) {
1406
+ for (const parameter of node.parameters)
1407
+ bind(parameter.name);
1408
+ if (ts.isFunctionExpression(node) && node.name)
1409
+ names.push(node.name.text);
1410
+ if ("body" in node && node.body)
1411
+ ts.forEachChild(node.body, hoist);
1412
+ } else if (ts.isClassExpression(node) && node.name)
1413
+ names.push(node.name.text);
1414
+ else if (ts.isBlock(node))
1415
+ node.statements.forEach(declare);
1416
+ else if (ts.isCaseBlock(node))
1417
+ for (const clause of node.clauses)
1418
+ clause.statements.forEach(declare);
1419
+ else if (ts.isCatchClause(node) && node.variableDeclaration)
1420
+ bind(node.variableDeclaration.name);
1421
+ else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
1422
+ for (const declaration of node.initializer.declarations)
1423
+ bind(declaration.name);
1424
+ return names.length ? new Set([...outer, ...names]) : outer;
1425
+ }
787
1426
  }
788
1427
 
789
1428
  // pkgs/@akanjs/language/akanGraph.ts
@@ -799,10 +1438,6 @@ var builtinGuards = {
799
1438
  Public: { scope: "account", doc: "Passes every caller, signed in or not." },
800
1439
  None: { scope: "account", doc: "Refuses every caller." }
801
1440
  };
802
- var frameworkOwner = "akanjs";
803
- var frameworkPattern = /^akanjs(?:\/|$)/;
804
- var aliasPattern = /^@(apps|libs)\/([^/]+)/;
805
- var maxMapDepth = 4;
806
1441
 
807
1442
  class GraphBuilder {
808
1443
  #source;
@@ -810,16 +1445,15 @@ class GraphBuilder {
810
1445
  #edges = new Map;
811
1446
  #contexts = new Map;
812
1447
  #members = new Map;
813
- #guards = new Map;
814
- #mapEntries = new Map;
815
1448
  #routeFiles = new Map;
816
- #declarations = new Map;
817
1449
  #routes;
1450
+ #resolver;
818
1451
  #adaptors = new Map;
819
1452
  #plugs = new Map;
820
1453
  constructor(source) {
821
1454
  this.#source = source;
822
1455
  this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
1456
+ this.#resolver = new GuardResolver(source, this.#routes);
823
1457
  }
824
1458
  build() {
825
1459
  const { facts } = this.#source;
@@ -841,7 +1475,8 @@ class GraphBuilder {
841
1475
  for (const fact of file.adaptors)
842
1476
  for (const plug of fact.plugs)
843
1477
  this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
844
- this.#declareGuards(facts);
1478
+ for (const guard of this.#resolver.declare(facts))
1479
+ this.#guardNodeOf(guard);
845
1480
  for (const file of facts) {
846
1481
  const { owner } = file;
847
1482
  const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
@@ -885,31 +1520,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
885
1520
  for (const query of filter.queries)
886
1521
  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);
887
1522
  }
888
- for (const slice of file.slices) {
889
- const refName = slice.model;
890
- if (!refName)
891
- continue;
892
- const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards);
893
- for (const guard of slice.guards)
894
- this.#guard(fallback, guard, file);
895
- for (const key of slice.keys) {
896
- const detail = [...key.guards, ...key.live ? ["live"] : []];
897
- const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
898
- for (const guard of key.guards)
899
- this.#guard(node, guard, file);
900
- }
901
- }
902
- for (const endpoint of file.endpoints) {
903
- const refName = endpoint.model;
904
- if (!refName)
905
- continue;
906
- for (const key of endpoint.keys) {
907
- const detail = [...key.verb ? [key.verb] : [], ...key.guards];
908
- const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
909
- for (const guard of key.guards)
910
- this.#guard(node, guard, file);
911
- }
912
- }
1523
+ this.#addGuarded(file);
913
1524
  for (const internal of file.internals)
914
1525
  for (const key of internal.keys)
915
1526
  if (internal.model && key.type !== "resolveField")
@@ -925,7 +1536,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
925
1536
  types.set(node.label, [...types.get(node.label) ?? [], node]);
926
1537
  for (const node of typed)
927
1538
  for (const field of node.fields ?? []) {
928
- const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
1539
+ const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
929
1540
  field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
930
1541
  if (field.ref)
931
1542
  this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
@@ -1007,61 +1618,52 @@ ${label ?? ""}`;
1007
1618
  else
1008
1619
  this.#edges.set(key, { from, to, kind, label, count: 1, at });
1009
1620
  }
1010
- #guard(node, guard, file) {
1011
- const [role, local] = guard.includes(" ") ? guard.split(" ") : [null, guard];
1012
- if (!local || local === "?")
1013
- return;
1014
- this.#addEdge(node.id, this.#guardIdOf(local, file), "guard", role, node.at);
1015
- }
1016
- #declareGuards(facts) {
1017
- const factories = new Map;
1018
- for (const file of facts)
1019
- for (const fact of file.guardFactories) {
1020
- const factory = { fact, owner: file.owner, fileName: file.fileName, made: [] };
1021
- factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
1022
- }
1023
- const sameFile = (factory, name) => name ? factories.get(name)?.find((other) => other.fileName === factory.fileName) : undefined;
1024
- const declare = (guard, factory) => {
1025
- this.#guards.set(guard.name, [...this.#guards.get(guard.name) ?? [], guard]);
1026
- factory?.made.push(guard);
1027
- };
1028
- const madeBy = (factory, fileName, extent) => {
1029
- const parts = [{ fileName, extent }];
1030
- const seen = new Set;
1031
- for (let current = factory;current && !seen.has(current); current = sameFile(current, current.fact.via)) {
1032
- seen.add(current);
1033
- parts.push({ fileName: current.fileName, extent: current.fact.extent });
1034
- }
1035
- return parts;
1036
- };
1037
- for (const file of facts) {
1038
- const { owner, fileName } = file;
1039
- for (const { name, span, extent, scope, doc, factory: callee } of file.guards) {
1040
- const factory = callee ? factories.get(callee)?.find((other) => other.fileName === fileName) : undefined;
1041
- declare({ name, owner, span, scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1621
+ #addGuarded(file) {
1622
+ const { owner } = file;
1623
+ for (const slice of file.slices) {
1624
+ const refName = slice.model;
1625
+ if (!refName)
1626
+ continue;
1627
+ const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
1628
+ for (const guard of slice.guards)
1629
+ this.#guard(fallback, guard, file);
1630
+ for (const key of slice.keys) {
1631
+ const detail = [
1632
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
1633
+ ...key.live ? ["live"] : []
1634
+ ];
1635
+ const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1636
+ for (const guard of key.guards)
1637
+ this.#guard(node, guard, file);
1042
1638
  }
1043
- for (const { name, span, extent, doc, callee } of file.guardCalls) {
1044
- const imported = file.imports.find((item) => item.name === callee);
1045
- if (!imported)
1046
- continue;
1047
- const from = this.#ownerOfSpecifier(fileName, imported.from);
1048
- const candidates = factories.get(imported.imported === "default" || imported.imported === "*" ? callee : imported.imported) ?? [];
1049
- const factory = candidates.find((other) => other.owner === from) ?? this.#soleVisible(candidates, from ?? owner);
1050
- if (factory)
1051
- declare({ name, owner, span, scope: factory.fact.scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1639
+ }
1640
+ for (const endpoint of file.endpoints) {
1641
+ const refName = endpoint.model;
1642
+ if (!refName)
1643
+ continue;
1644
+ for (const key of endpoint.keys) {
1645
+ const detail = [
1646
+ ...key.verb ? [key.verb] : [],
1647
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
1648
+ ];
1649
+ const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1650
+ for (const guard of key.guards)
1651
+ this.#guard(node, guard, file);
1052
1652
  }
1053
1653
  }
1054
- for (const list of factories.values())
1055
- for (const { fact, made } of list)
1056
- if (made.every((guard) => !guard.doc))
1057
- for (const guard of made)
1058
- guard.doc = fact.doc;
1059
- for (const list of this.#guards.values())
1060
- for (const guard of list)
1061
- this.#guardNodeOf(guard);
1654
+ }
1655
+ #guard(node, guard, file) {
1656
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
1657
+ if (!local || local === "?")
1658
+ return;
1659
+ const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
1660
+ if (id)
1661
+ this.#addEdge(node.id, id, "guard", role, node.at);
1062
1662
  }
1063
1663
  #guardIdOf(local, site) {
1064
- const found = this.#findGuard(local, site, 0);
1664
+ const found = this.#resolver.find(local, site);
1665
+ if ("external" in found)
1666
+ return null;
1065
1667
  if ("guard" in found)
1066
1668
  return this.#guardNodeOf(found.guard);
1067
1669
  const { name, from } = found;
@@ -1074,99 +1676,18 @@ ${label ?? ""}`;
1074
1676
  node.doc = builtin.doc;
1075
1677
  return node.id;
1076
1678
  }
1077
- #findGuard(local, site, depth) {
1078
- const [head = local, member] = local.split(".");
1079
- const imported = site.imports.find((item) => item.name === head);
1080
- const from = imported ? this.#ownerOfSpecifier(site.fileName, imported.from) : undefined;
1081
- const holder = imported ? from : site.owner;
1082
- if (member && imported?.imported !== "*" && holder !== undefined && holder !== frameworkOwner && depth < maxMapDepth) {
1083
- const objectName = imported && imported.imported !== "default" ? imported.imported : head;
1084
- for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
1085
- const found = this.#findGuard(entry.name, entry.site, depth + 1);
1086
- if ("guard" in found)
1087
- return found;
1088
- }
1089
- }
1090
- const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1091
- const declared = this.#guards.get(name) ?? [];
1092
- 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));
1093
- return guard ? { guard } : { name, from };
1094
- }
1095
- #mapEntriesOf(objectName, member, fileName, owner) {
1096
- const key = `${objectName}
1097
- ${member}
1098
- ${fileName ?? ""}
1099
- ${owner ?? ""}`;
1100
- const cached = this.#mapEntries.get(key);
1101
- if (cached)
1102
- return cached;
1103
- const { ts, reader } = this.#source;
1104
- const entries = [];
1105
- const fileNames = [
1106
- ...fileName ? [fileName] : [],
1107
- ...this.#source.files.filter(([other, words]) => other !== fileName && owner !== null && !testFilePattern.test(other) && words.includes(`
1108
- ${objectName}
1109
- `) && words.includes(`
1110
- ${member}
1111
- `) && this.#source.ownerOf(other) === owner).map(([other]) => other)
1112
- ];
1113
- for (const holder of fileNames) {
1114
- const text = this.#source.readText(holder);
1115
- if (text === undefined)
1116
- continue;
1117
- const source = this.#source.parse(holder, text);
1118
- const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
1119
- for (const statement of source.statements) {
1120
- if (!ts.isVariableStatement(statement))
1121
- continue;
1122
- const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
1123
- const object = map && reader.unwrap(map);
1124
- if (!object || !ts.isObjectLiteralExpression(object))
1125
- continue;
1126
- for (const property of object.properties) {
1127
- if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
1128
- entries.push({ name: member, site });
1129
- if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
1130
- continue;
1131
- const value = reader.unwrap(property.initializer);
1132
- if (ts.isIdentifier(value))
1133
- entries.push({ name: value.text, site });
1134
- else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
1135
- entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
1136
- }
1137
- }
1138
- }
1139
- this.#mapEntries.set(key, entries);
1140
- return entries;
1141
- }
1142
- #declarationOf(owner, name, near) {
1143
- const key = `${owner}
1144
- ${name}
1145
- ${near ?? ""}`;
1146
- const cached = this.#declarations.get(key);
1147
- if (cached !== undefined)
1148
- return cached;
1149
- const isNear = (fileName) => {
1150
- const relative = this.#source.relative(fileName);
1151
- return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1152
- };
1153
- const candidates = this.#source.files.filter(([fileName, words]) => !testFilePattern.test(fileName) && words.includes(`
1154
- ${name}
1155
- `) && this.#source.ownerOf(fileName) === owner).map(([fileName]) => fileName);
1156
- let found = null;
1157
- for (const fileName of [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))]) {
1158
- const text = this.#source.readText(fileName);
1159
- if (text === undefined)
1160
- continue;
1161
- const source = this.#source.parse(fileName, text);
1162
- const declared = this.#routes.declarationIn(source, name);
1163
- if (!declared)
1164
- continue;
1165
- found = { source, statement: declared.statement, declared: declared.declared };
1166
- break;
1167
- }
1168
- this.#declarations.set(key, found);
1169
- return found;
1679
+ #madeInPlaceIdOf(name, file) {
1680
+ const made = this.#resolver.madeInPlace(file.fileName, name);
1681
+ if (made)
1682
+ return this.#guardNodeOf(made);
1683
+ const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
1684
+ const imported = file.imports.find((item) => item.name === head);
1685
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
1686
+ if (from === undefined || from === frameworkOwner)
1687
+ return null;
1688
+ const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
1689
+ const label = guardLabelOf(name, file.guardLabels);
1690
+ return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
1170
1691
  }
1171
1692
  #addRoutes() {
1172
1693
  const routes = [];
@@ -1219,22 +1740,23 @@ ${dir}`, node);
1219
1740
  }
1220
1741
  #routeGuardIdOf({ name: local, local: declared }, fact, text) {
1221
1742
  if (declared) {
1222
- const file = this.#source.relative(fact.fileName).slice(fact.owner.length + 1);
1223
- const id = this.#key("guard", fact.owner, `${file}#${local}`);
1743
+ const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
1224
1744
  return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
1225
1745
  }
1226
- const imported = fact.imports.find((item) => item.name === local);
1227
- if (!imported)
1746
+ const [head = local, member] = local.split(".");
1747
+ const imported = fact.imports.find((item) => item.name === head);
1748
+ if (!imported || member && imported.imported !== "*")
1228
1749
  return null;
1229
- const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
1230
- const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
1750
+ const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
1751
+ const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
1231
1752
  if (!from || from === frameworkOwner)
1232
1753
  return null;
1233
- const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
1754
+ const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
1234
1755
  if (!found)
1235
1756
  return null;
1236
1757
  const { reader } = this.#source;
1237
- 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));
1758
+ const owner = this.#source.ownerOf(found.source.fileName) ?? from;
1759
+ 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);
1238
1760
  }
1239
1761
  #routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
1240
1762
  const isNew = !this.#nodes.has(id);
@@ -1247,10 +1769,10 @@ ${dir}`, node);
1247
1769
  #adaptorIdOf(local, file) {
1248
1770
  const [head, member] = local.split(".");
1249
1771
  const imported = file.imports.find((item) => item.name === head);
1250
- const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
1772
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
1251
1773
  const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1252
1774
  const declared = this.#adaptors.get(name) ?? [];
1253
- 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)));
1775
+ 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)));
1254
1776
  if (found)
1255
1777
  return this.#adaptorNodeOf(found);
1256
1778
  const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
@@ -1266,38 +1788,22 @@ ${dir}`, node);
1266
1788
  this.#digest(node, this.#source.readText(fileName), fact.extent);
1267
1789
  return node.id;
1268
1790
  }
1269
- #guardNodeOf({ name, owner, span, scope, doc, parts }) {
1270
- const node = this.#addNode(this.#key("guard", owner, name), "guard", owner, null, name, span, scope ? [scope] : []);
1791
+ #guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
1792
+ const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
1793
+ const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
1271
1794
  if (doc && !node.doc)
1272
1795
  node.doc = doc;
1273
- const digest = node.digest ? null : this.#hashOf(...parts.map(({ fileName, extent }) => [
1274
- this.#source.readText(fileName),
1275
- extent
1276
- ]));
1277
- if (digest)
1278
- node.digest = digest;
1796
+ if (node.digest)
1797
+ return node.id;
1798
+ const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
1799
+ if (!digest)
1800
+ return node.id;
1801
+ node.digest = digest;
1802
+ const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
1803
+ if (sources.length)
1804
+ node.sources = sources;
1279
1805
  return node.id;
1280
1806
  }
1281
- #ownerOfSpecifier(fileName, specifier) {
1282
- if (frameworkPattern.test(specifier))
1283
- return frameworkOwner;
1284
- const path = this.#pathOfSpecifier(fileName, specifier);
1285
- return path === null ? undefined : ownerOf(path) ?? undefined;
1286
- }
1287
- #pathOfSpecifier(fileName, specifier) {
1288
- const alias = specifier.match(aliasPattern);
1289
- if (alias)
1290
- return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1291
- if (!specifier.startsWith("."))
1292
- return null;
1293
- const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1294
- for (const segment of specifier.split("/"))
1295
- if (segment === "..")
1296
- segments.pop();
1297
- else if (segment !== ".")
1298
- segments.push(segment);
1299
- return segments.join("/");
1300
- }
1301
1807
  #moduleDirOf(fileName) {
1302
1808
  const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
1303
1809
  return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
@@ -1333,20 +1839,13 @@ ${dir}`, node);
1333
1839
  visit(constant);
1334
1840
  return [...fields.values()];
1335
1841
  }
1336
- #visible(target, from) {
1337
- return !from || !target || target === from || target.startsWith("libs/");
1338
- }
1339
- #soleVisible(declared, from) {
1340
- const visible = declared.filter((other) => this.#visible(other.owner, from));
1341
- return visible.length === 1 ? visible[0] : undefined;
1342
- }
1343
1842
  #targetNodeOf({ family, owner, refName, sourceKey }) {
1344
1843
  const isDefault = /^[A-Z]/.test(sourceKey);
1345
1844
  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;
1346
1845
  return id && this.#nodes.has(id) ? id : null;
1347
1846
  }
1348
1847
  #membersNamed(kind, name, owner, refName) {
1349
- const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
1848
+ const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
1350
1849
  const own = nodes.filter((node) => node.owner === owner);
1351
1850
  return (own.length ? own : nodes).map((node) => node.id);
1352
1851
  }
@@ -1700,6 +2199,10 @@ var endpointNames = (key) => {
1700
2199
  };
1701
2200
 
1702
2201
  // pkgs/@akanjs/language/nameTable.ts
2202
+ var guardDetailOf = (facts, guards) => {
2203
+ const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
2204
+ return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
2205
+ };
1703
2206
  var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
1704
2207
 
1705
2208
  class NameTable {
@@ -1799,7 +2302,7 @@ class NameTable {
1799
2302
  role: "sig",
1800
2303
  gate: "sig",
1801
2304
  label: sliceClass.name,
1802
- detail: sliceClass.guards.length ? [`guards: ${sliceClass.guards.join(", ")}`] : []
2305
+ detail: guardDetailOf(facts, sliceClass.guards)
1803
2306
  });
1804
2307
  if (modelDoc)
1805
2308
  this.#add({
@@ -1864,7 +2367,7 @@ class NameTable {
1864
2367
  sourceKey: slice.name,
1865
2368
  span: slice.span,
1866
2369
  label: `slice ${refName} (default)`,
1867
- detail: slice.guards.length ? [`guards: ${slice.guards.join(", ")}`] : []
2370
+ detail: guardDetailOf(facts, slice.guards)
1868
2371
  };
1869
2372
  this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
1870
2373
  for (const key of slice.keys)
@@ -1874,7 +2377,7 @@ class NameTable {
1874
2377
  sourceKey: key.name,
1875
2378
  span: key.span,
1876
2379
  label: `slice ${key.name}`,
1877
- detail: [...key.guards.length ? [`guards: ${key.guards.join(", ")}`] : [], ...key.live ? ["live"] : []]
2380
+ detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
1878
2381
  }, refName, key.name, modelNameSet, slice.name);
1879
2382
  }
1880
2383
  if (filterClass) {
@@ -1931,7 +2434,7 @@ class NameTable {
1931
2434
  role: key.verb === "pubsub" ? "subscribe" : "listen",
1932
2435
  gate: null,
1933
2436
  label: `${key.verb} ${key.name}`,
1934
- detail: key.guards.length ? [`guards: ${key.guards.join(", ")}`] : []
2437
+ detail: guardDetailOf(facts, key.guards)
1935
2438
  });
1936
2439
  }
1937
2440
  }
@@ -1989,14 +2492,12 @@ var dictionaryPattern = /\.dictionary\.ts$/;
1989
2492
  var srvkitPattern = /\/srvkit\//;
1990
2493
  var wordPattern = /[A-Za-z_$][\w$]*/g;
1991
2494
  var identifierPattern = /^[A-Za-z_$][\w$]*$/;
1992
- var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
1993
2495
  var stateReceivers = new Set(["use", "sel", "get", "do"]);
1994
2496
  var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
1995
2497
  var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
1996
2498
  var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
1997
2499
  var parseCacheSize = 64;
1998
2500
  var pathOf = (fileName) => fileName.replace(/\\/g, "/");
1999
- var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
2000
2501
  var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
2001
2502
 
2002
2503
  class AkanIndex {