@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/akanGraph.d.ts +1 -1
- package/akanGraphDiff.d.ts +1 -1
- package/escapeRegExp.d.ts +1 -0
- package/guardFacts.d.ts +43 -0
- package/guardResolver.d.ts +54 -0
- package/index.cjs +932 -431
- package/index.js +932 -431
- package/moduleFacts.d.ts +2 -14
- package/package.json +1 -1
- package/routeFacts.d.ts +5 -2
- package/server.js +896 -430
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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,
|
|
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
|
|
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 = (
|
|
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:
|
|
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
|
-
|
|
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
|
|
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.#
|
|
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
|
-
|
|
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
|
-
#
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
for (const
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
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
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
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
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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.#
|
|
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
|
-
#
|
|
1130
|
-
const
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
const
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
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
|
|
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
|
|
1279
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
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:
|
|
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:
|
|
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: [...
|
|
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:
|
|
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 {
|