@akanjs/language 3.2.0-rc.1 → 3.2.0-rc.3
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 +56 -0
- package/index.cjs +1000 -409
- package/index.js +1000 -409
- package/moduleFacts.d.ts +9 -14
- package/package.json +1 -1
- package/routeFacts.d.ts +5 -2
- package/server.js +961 -408
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;
|
|
@@ -79,6 +80,9 @@ class AkanGraphDiff {
|
|
|
79
80
|
...head.edges.flatMap((edge) => !baseKeys.has(edgeKeyOf(edge)) ? [{ ...edge, change: "added" }] : marked.has(edge.from) || marked.has(edge.to) ? [edge] : []),
|
|
80
81
|
...base.edges.filter((edge) => !headKeys.has(edgeKeyOf(edge))).map((edge) => ({ ...edge, change: "removed" }))
|
|
81
82
|
];
|
|
83
|
+
const shown = new Set([...marked.keys(), ...edges.flatMap((edge) => [edge.from, edge.to])]);
|
|
84
|
+
const listed = new Set(edges.map(edgeKeyOf));
|
|
85
|
+
edges.push(...head.edges.filter((edge) => edge.kind === "binds" && shown.has(edge.from) && !listed.has(edgeKeyOf(edge))));
|
|
82
86
|
const context = edges.flatMap((edge) => [edge.from, edge.to]).filter((id) => !marked.has(id)).flatMap((id) => {
|
|
83
87
|
const node = headNodes.get(id) ?? baseNodes.get(id);
|
|
84
88
|
return node ? [node] : [];
|
|
@@ -90,6 +94,40 @@ class AkanGraphDiff {
|
|
|
90
94
|
summary[node.change] += 1;
|
|
91
95
|
return { version: 1, nodes, edges, summary };
|
|
92
96
|
}
|
|
97
|
+
static #renamed(base, head) {
|
|
98
|
+
const baseIds = new Set(base.nodes.map((node) => node.id));
|
|
99
|
+
const headIds = new Set(head.nodes.map((node) => node.id));
|
|
100
|
+
const pairKeyOf = ({ kind, owner, name, at }) => at ? `${kind}
|
|
101
|
+
${owner ?? ""}
|
|
102
|
+
${name}
|
|
103
|
+
${at.file}` : null;
|
|
104
|
+
const shortIdOf = ({ kind, owner, name }) => `${kind}:${owner ?? ""}:${name}`;
|
|
105
|
+
const isFileKeyed = (node) => node.id !== shortIdOf(node) && node.id.startsWith(`${node.kind}:${node.owner ?? ""}:`) && node.id.endsWith(`#${node.name}`);
|
|
106
|
+
const fromBase = new Map(base.nodes.flatMap((node) => {
|
|
107
|
+
const key = headIds.has(node.id) ? null : pairKeyOf(node);
|
|
108
|
+
return key ? [[key, node]] : [];
|
|
109
|
+
}));
|
|
110
|
+
const renames = new Map;
|
|
111
|
+
for (const node of head.nodes) {
|
|
112
|
+
if (baseIds.has(node.id))
|
|
113
|
+
continue;
|
|
114
|
+
const key = pairKeyOf(node);
|
|
115
|
+
const before = key ? fromBase.get(key) : undefined;
|
|
116
|
+
if (!before)
|
|
117
|
+
continue;
|
|
118
|
+
const isPair = isFileKeyed(node) ? before.id === shortIdOf(node) : isFileKeyed(before) && node.id === shortIdOf(before);
|
|
119
|
+
if (isPair)
|
|
120
|
+
renames.set(before.id, node.id);
|
|
121
|
+
}
|
|
122
|
+
if (!renames.size)
|
|
123
|
+
return base;
|
|
124
|
+
const rename = (id) => renames.get(id) ?? id;
|
|
125
|
+
return {
|
|
126
|
+
...base,
|
|
127
|
+
nodes: base.nodes.map((node) => renames.has(node.id) ? { ...node, id: rename(node.id) } : node),
|
|
128
|
+
edges: base.edges.map((edge) => ({ ...edge, from: rename(edge.from), to: rename(edge.to) }))
|
|
129
|
+
};
|
|
130
|
+
}
|
|
93
131
|
static #mark(node, change) {
|
|
94
132
|
return node.fields ? { ...node, change, fields: node.fields.map((field) => ({ ...field, change })) } : { ...node, change };
|
|
95
133
|
}
|
|
@@ -123,6 +161,239 @@ class AkanGraphDiff {
|
|
|
123
161
|
return node.fields ? { ...node, fields: node.fields.map(({ digest: _field, ...field }) => field) } : node;
|
|
124
162
|
}
|
|
125
163
|
}
|
|
164
|
+
// pkgs/@akanjs/language/guardFacts.ts
|
|
165
|
+
var guardLabelOf = (guard, labels) => {
|
|
166
|
+
const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
|
|
167
|
+
const label = labels[local];
|
|
168
|
+
return label === undefined ? guard : role ? `${role} ${label}` : label;
|
|
169
|
+
};
|
|
170
|
+
var maxLabelArgs = 32;
|
|
171
|
+
var guardRefReaderOf = (ts, ast, unwrap) => {
|
|
172
|
+
const pathOf = (expression) => {
|
|
173
|
+
if (ts.isIdentifier(expression))
|
|
174
|
+
return expression.text;
|
|
175
|
+
if (!ts.isPropertyAccessExpression(expression))
|
|
176
|
+
return null;
|
|
177
|
+
const head = pathOf(expression.expression);
|
|
178
|
+
return head && `${head}.${expression.name.text}`;
|
|
179
|
+
};
|
|
180
|
+
const calleeOf = (call) => pathOf(unwrap(call.expression)) ?? "?";
|
|
181
|
+
const labelOf = (source, call) => {
|
|
182
|
+
const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
|
|
183
|
+
return `${calleeOf(call)}(${args.length <= maxLabelArgs && !args.includes(`
|
|
184
|
+
`) ? args : "…"})`;
|
|
185
|
+
};
|
|
186
|
+
const guardRefOf = (expression, made, member) => {
|
|
187
|
+
const value = unwrap(expression);
|
|
188
|
+
if (!ts.isCallExpression(value))
|
|
189
|
+
return pathOf(value) ?? ast.expressionName(value);
|
|
190
|
+
const prefix = `${member}:${calleeOf(value)}@`;
|
|
191
|
+
const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
|
|
192
|
+
made.push({ call: value, name });
|
|
193
|
+
return name;
|
|
194
|
+
};
|
|
195
|
+
const guardsIn = (source, object, made, member) => {
|
|
196
|
+
const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
|
|
197
|
+
if (!guards)
|
|
198
|
+
return [];
|
|
199
|
+
const refOf = (expression) => guardRefOf(expression, made, member);
|
|
200
|
+
if (ts.isArrayLiteralExpression(guards))
|
|
201
|
+
return guards.elements.map(refOf).filter((name) => name !== null);
|
|
202
|
+
if (ts.isObjectLiteralExpression(guards))
|
|
203
|
+
return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
|
|
204
|
+
const value = unwrap(property.initializer);
|
|
205
|
+
const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
|
|
206
|
+
return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
|
|
207
|
+
});
|
|
208
|
+
const single = refOf(guards);
|
|
209
|
+
return single ? [single] : [];
|
|
210
|
+
};
|
|
211
|
+
return { pathOf, labelOf, guardsIn };
|
|
212
|
+
};
|
|
213
|
+
var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf) => {
|
|
214
|
+
const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
|
|
215
|
+
const scopeOf = (node) => {
|
|
216
|
+
const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
|
|
217
|
+
const value = scope && unwrap(scope);
|
|
218
|
+
return value && ts.isStringLiteralLike(value) ? value.text : null;
|
|
219
|
+
};
|
|
220
|
+
const functionsOf = (source) => {
|
|
221
|
+
const functions = new Map;
|
|
222
|
+
for (const statement of source.statements) {
|
|
223
|
+
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
|
|
224
|
+
functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
|
|
225
|
+
if (!ts.isVariableStatement(statement))
|
|
226
|
+
continue;
|
|
227
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
228
|
+
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
229
|
+
if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
|
|
230
|
+
functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return functions;
|
|
234
|
+
};
|
|
235
|
+
const returnedOf = (body, guardNames) => {
|
|
236
|
+
const locals = new Map;
|
|
237
|
+
const pending = [];
|
|
238
|
+
const visit = (node) => {
|
|
239
|
+
if (ts.isClassDeclaration(node) && node.name)
|
|
240
|
+
locals.set(node.name.text, node);
|
|
241
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
|
|
242
|
+
locals.set(node.name.text, node.initializer);
|
|
243
|
+
if (ts.isReturnStatement(node) && node.expression)
|
|
244
|
+
pending.push(node.expression);
|
|
245
|
+
if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
|
|
246
|
+
ts.forEachChild(node, visit);
|
|
247
|
+
};
|
|
248
|
+
if (ts.isBlock(body))
|
|
249
|
+
ts.forEachChild(body, visit);
|
|
250
|
+
else
|
|
251
|
+
pending.push(body);
|
|
252
|
+
let guard = null;
|
|
253
|
+
const calls = [];
|
|
254
|
+
const seen = new Set;
|
|
255
|
+
const passed = new Set;
|
|
256
|
+
for (let node = pending.pop();node; node = pending.pop()) {
|
|
257
|
+
if (seen.has(node))
|
|
258
|
+
continue;
|
|
259
|
+
seen.add(node);
|
|
260
|
+
const value = ts.isExpression(node) ? unwrap(node) : node;
|
|
261
|
+
if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
|
|
262
|
+
guard ??= value;
|
|
263
|
+
else if (ts.isConditionalExpression(value))
|
|
264
|
+
pending.push(value.whenTrue, value.whenFalse);
|
|
265
|
+
else if (ts.isBinaryExpression(value))
|
|
266
|
+
pending.push(value.left, value.right);
|
|
267
|
+
else if (ts.isIdentifier(value)) {
|
|
268
|
+
const local = locals.get(value.text);
|
|
269
|
+
if (local)
|
|
270
|
+
pending.push(local);
|
|
271
|
+
} else if (ts.isCallExpression(value)) {
|
|
272
|
+
const callee = pathOf(unwrap(value.expression));
|
|
273
|
+
if (callee && !passed.has(node) && !calls.includes(callee))
|
|
274
|
+
calls.push(callee);
|
|
275
|
+
for (const arg of value.arguments)
|
|
276
|
+
passed.add(arg);
|
|
277
|
+
pending.push(...value.arguments);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return { guard, calls };
|
|
281
|
+
};
|
|
282
|
+
const guardFactoriesOf = (source, guardNames, isImported) => {
|
|
283
|
+
const functions = functionsOf(source);
|
|
284
|
+
const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
|
|
285
|
+
const members = new Set;
|
|
286
|
+
const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
|
|
287
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
288
|
+
isGrowing = false;
|
|
289
|
+
for (const [name, { guard, calls }] of returned) {
|
|
290
|
+
if (members.has(name) || !guard && !calls.some(isFactoryCall))
|
|
291
|
+
continue;
|
|
292
|
+
members.add(name);
|
|
293
|
+
isGrowing = true;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const factories = new Map;
|
|
297
|
+
for (const name of members) {
|
|
298
|
+
const found = functions.get(name);
|
|
299
|
+
const made = returned.get(name);
|
|
300
|
+
if (!found || !made)
|
|
301
|
+
continue;
|
|
302
|
+
factories.set(name, {
|
|
303
|
+
name,
|
|
304
|
+
span: spanOf(source, found.name),
|
|
305
|
+
extent: extentOf(source, found.statement),
|
|
306
|
+
scope: made.guard ? scopeOf(made.guard) : null,
|
|
307
|
+
doc: docOf(source, found.statement),
|
|
308
|
+
direct: !!made.guard,
|
|
309
|
+
via: made.calls.filter(isFactoryCall)
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return factories;
|
|
313
|
+
};
|
|
314
|
+
return { implementsGuard, scopeOf, guardFactoriesOf };
|
|
315
|
+
};
|
|
316
|
+
var createGuardFactReader = (ts, ast, base) => {
|
|
317
|
+
const { spanOf, extentOf, unwrap, docOf } = base;
|
|
318
|
+
const { pathOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
|
|
319
|
+
const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf);
|
|
320
|
+
const guardsOf = (source, imports, madeInPlace) => {
|
|
321
|
+
const guardNames = new Set([
|
|
322
|
+
"Guard",
|
|
323
|
+
...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
|
|
324
|
+
]);
|
|
325
|
+
const imported = new Set(imports.map(({ name }) => name));
|
|
326
|
+
const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
|
|
327
|
+
const factories = guardFactoriesOf(source, guardNames, isImported);
|
|
328
|
+
const guards = [];
|
|
329
|
+
const calls = [];
|
|
330
|
+
const makeOf = (made, value, isInPlace = false) => {
|
|
331
|
+
if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
|
|
332
|
+
guards.push({ ...made, scope: scopeOf(value), factory: null });
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const callee = ts.isCallExpression(value) ? pathOf(unwrap(value.expression)) : null;
|
|
336
|
+
const factory = callee && factories.get(callee);
|
|
337
|
+
if (factory)
|
|
338
|
+
guards.push({ ...made, scope: factory.scope, factory: factory.name });
|
|
339
|
+
else if (callee && (isImported(callee) || isInPlace))
|
|
340
|
+
calls.push({ ...made, callee, kind: "call", scope: null });
|
|
341
|
+
};
|
|
342
|
+
for (const statement of source.statements) {
|
|
343
|
+
if (ts.isClassDeclaration(statement) && statement.name) {
|
|
344
|
+
const declared = {
|
|
345
|
+
name: statement.name.text,
|
|
346
|
+
span: spanOf(source, statement.name),
|
|
347
|
+
extent: extentOf(source, statement),
|
|
348
|
+
scope: scopeOf(statement),
|
|
349
|
+
doc: docOf(source, statement)
|
|
350
|
+
};
|
|
351
|
+
const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
|
|
352
|
+
const callee = base && pathOf(base.expression);
|
|
353
|
+
if (implementsGuard(statement, guardNames))
|
|
354
|
+
guards.push({ ...declared, factory: null });
|
|
355
|
+
else if (callee)
|
|
356
|
+
calls.push({ ...declared, callee, kind: "extends" });
|
|
357
|
+
}
|
|
358
|
+
if (!ts.isVariableStatement(statement))
|
|
359
|
+
continue;
|
|
360
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
361
|
+
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
362
|
+
if (!initializer || !ts.isIdentifier(declaration.name))
|
|
363
|
+
continue;
|
|
364
|
+
const holder = declaration.name.text;
|
|
365
|
+
makeOf({
|
|
366
|
+
name: holder,
|
|
367
|
+
span: spanOf(source, declaration.name),
|
|
368
|
+
extent: extentOf(source, statement),
|
|
369
|
+
doc: docOf(source, statement)
|
|
370
|
+
}, initializer);
|
|
371
|
+
if (!ts.isObjectLiteralExpression(initializer))
|
|
372
|
+
continue;
|
|
373
|
+
for (const property of initializer.properties) {
|
|
374
|
+
const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
|
|
375
|
+
if (key && ts.isPropertyAssignment(property))
|
|
376
|
+
makeOf({
|
|
377
|
+
name: `${holder}.${key}`,
|
|
378
|
+
span: spanOf(source, property.name),
|
|
379
|
+
extent: extentOf(source, property),
|
|
380
|
+
doc: docOf(source, property)
|
|
381
|
+
}, unwrap(property.initializer));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
for (const { call, name } of madeInPlace)
|
|
386
|
+
makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
|
|
387
|
+
const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
|
|
388
|
+
return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
|
|
389
|
+
};
|
|
390
|
+
return { guardsIn, guardsOf };
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// pkgs/@akanjs/language/escapeRegExp.ts
|
|
394
|
+
var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
|
|
395
|
+
var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
|
|
396
|
+
|
|
126
397
|
// pkgs/@akanjs/language/moduleFacts.ts
|
|
127
398
|
var testFilePattern = /\.(?:test|spec)\.tsx?$/;
|
|
128
399
|
var moduleCallees = new Set([
|
|
@@ -191,21 +462,6 @@ var createFactReader = (ts, ast) => {
|
|
|
191
462
|
const value = unwrap(expression);
|
|
192
463
|
return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
|
|
193
464
|
};
|
|
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
465
|
const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
|
|
210
466
|
const chainOf = (initializer) => {
|
|
211
467
|
let root = unwrap(initializer);
|
|
@@ -222,7 +478,7 @@ var createFactReader = (ts, ast) => {
|
|
|
222
478
|
const node = keyName(property);
|
|
223
479
|
return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
|
|
224
480
|
});
|
|
225
|
-
const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
481
|
+
const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
226
482
|
if (!ts.isPropertyAssignment(property))
|
|
227
483
|
return [];
|
|
228
484
|
const name = ast.nodeName(property.name);
|
|
@@ -234,7 +490,7 @@ var createFactReader = (ts, ast) => {
|
|
|
234
490
|
name,
|
|
235
491
|
span: spanOf(source, property.name),
|
|
236
492
|
extent: extentOf(source, property),
|
|
237
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
493
|
+
guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
|
|
238
494
|
live: methods.includes("live"),
|
|
239
495
|
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
240
496
|
}
|
|
@@ -284,7 +540,7 @@ var createFactReader = (ts, ast) => {
|
|
|
284
540
|
const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
|
|
285
541
|
return `${ms / size}${unit}`;
|
|
286
542
|
};
|
|
287
|
-
const modesOf = (option) => {
|
|
543
|
+
const modesOf = (option, type) => {
|
|
288
544
|
const object = option && unwrap(option);
|
|
289
545
|
if (!object || !ts.isObjectLiteralExpression(object))
|
|
290
546
|
return [];
|
|
@@ -294,9 +550,9 @@ var createFactReader = (ts, ast) => {
|
|
|
294
550
|
if (name === "enabled")
|
|
295
551
|
return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
|
|
296
552
|
if (name === "once")
|
|
297
|
-
return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
553
|
+
return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
298
554
|
if (name === "lock")
|
|
299
|
-
return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
555
|
+
return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
300
556
|
if (name !== "serverMode" && name !== "operationMode")
|
|
301
557
|
return [];
|
|
302
558
|
const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
|
|
@@ -314,7 +570,8 @@ var createFactReader = (ts, ast) => {
|
|
|
314
570
|
const { root } = chainOf(property.initializer);
|
|
315
571
|
const type = root ? verbOf(root.expression) : null;
|
|
316
572
|
const [first, second] = root?.arguments ?? [];
|
|
317
|
-
const
|
|
573
|
+
const cron = type === "cron" && first ? unwrap(first) : null;
|
|
574
|
+
const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
|
|
318
575
|
const option = type === "initialize" || type === "destroy" ? first : second;
|
|
319
576
|
return [
|
|
320
577
|
{
|
|
@@ -323,7 +580,7 @@ var createFactReader = (ts, ast) => {
|
|
|
323
580
|
extent: extentOf(source, property),
|
|
324
581
|
type,
|
|
325
582
|
schedule,
|
|
326
|
-
modes: modesOf(option)
|
|
583
|
+
modes: modesOf(option, type)
|
|
327
584
|
}
|
|
328
585
|
];
|
|
329
586
|
});
|
|
@@ -340,7 +597,20 @@ var createFactReader = (ts, ast) => {
|
|
|
340
597
|
return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
|
|
341
598
|
});
|
|
342
599
|
};
|
|
343
|
-
const
|
|
600
|
+
const adaptorBindingsIn = (source) => {
|
|
601
|
+
const bindings = [];
|
|
602
|
+
const visit = (node) => {
|
|
603
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
|
|
604
|
+
const [role, adaptor] = node.arguments.map(guardNameOf);
|
|
605
|
+
if (role && adaptor)
|
|
606
|
+
bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
|
|
607
|
+
}
|
|
608
|
+
ts.forEachChild(node, visit);
|
|
609
|
+
};
|
|
610
|
+
visit(source);
|
|
611
|
+
return bindings;
|
|
612
|
+
};
|
|
613
|
+
const endpointKeysOf = (source, builder, made, model) => {
|
|
344
614
|
const verbOf = verbReaderOf(builder?.parameters[0]);
|
|
345
615
|
return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
346
616
|
if (!ts.isPropertyAssignment(property))
|
|
@@ -355,7 +625,7 @@ var createFactReader = (ts, ast) => {
|
|
|
355
625
|
span: spanOf(source, property.name),
|
|
356
626
|
extent: extentOf(source, property),
|
|
357
627
|
verb: root ? verbOf(root.expression) : null,
|
|
358
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
628
|
+
guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
|
|
359
629
|
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
360
630
|
}
|
|
361
631
|
];
|
|
@@ -422,22 +692,6 @@ var createFactReader = (ts, ast) => {
|
|
|
422
692
|
})) : []
|
|
423
693
|
];
|
|
424
694
|
};
|
|
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
695
|
const docOf = (source, node) => {
|
|
442
696
|
const { text } = source;
|
|
443
697
|
const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
|
|
@@ -453,108 +707,20 @@ var createFactReader = (ts, ast) => {
|
|
|
453
707
|
`)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
|
|
454
708
|
return lines.length ? lines.join(" ") : null;
|
|
455
709
|
};
|
|
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
|
-
};
|
|
710
|
+
const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
|
|
549
711
|
const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
|
|
550
712
|
const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
|
|
551
713
|
const read = (source, owner) => {
|
|
552
714
|
const imports = importsIn(source);
|
|
715
|
+
const madeInPlace = [];
|
|
553
716
|
const facts = {
|
|
554
717
|
fileName: source.fileName,
|
|
555
718
|
owner,
|
|
556
719
|
imports,
|
|
557
|
-
|
|
720
|
+
guards: [],
|
|
721
|
+
guardFactories: [],
|
|
722
|
+
guardCalls: [],
|
|
723
|
+
guardLabels: {},
|
|
558
724
|
constants: [],
|
|
559
725
|
enums: [],
|
|
560
726
|
filters: [],
|
|
@@ -564,6 +730,7 @@ var createFactReader = (ts, ast) => {
|
|
|
564
730
|
endpoints: [],
|
|
565
731
|
internals: [],
|
|
566
732
|
adaptors: [],
|
|
733
|
+
adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
|
|
567
734
|
stores: []
|
|
568
735
|
};
|
|
569
736
|
for (const statement of source.statements) {
|
|
@@ -588,16 +755,19 @@ var createFactReader = (ts, ast) => {
|
|
|
588
755
|
...name,
|
|
589
756
|
model: moduleRefOf(call.arguments[0], "db"),
|
|
590
757
|
methods: methodsOf(source, statement),
|
|
591
|
-
plugs: plugsOf(source, builder)
|
|
758
|
+
plugs: plugsOf(source, builder),
|
|
759
|
+
fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
|
|
592
760
|
});
|
|
593
|
-
else if (callee === "slice")
|
|
761
|
+
else if (callee === "slice") {
|
|
762
|
+
const model = moduleRefOf(call.arguments[0], "srv");
|
|
763
|
+
const member = model ?? name.name;
|
|
594
764
|
facts.slices.push({
|
|
595
765
|
...name,
|
|
596
|
-
model
|
|
597
|
-
guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
|
|
598
|
-
keys: sliceKeysOf(source, builder)
|
|
766
|
+
model,
|
|
767
|
+
guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
|
|
768
|
+
keys: sliceKeysOf(source, builder, madeInPlace, member)
|
|
599
769
|
});
|
|
600
|
-
else if (callee === "internal")
|
|
770
|
+
} else if (callee === "internal")
|
|
601
771
|
facts.internals.push({
|
|
602
772
|
...name,
|
|
603
773
|
model: moduleRefOf(call.arguments[0], "srv"),
|
|
@@ -612,13 +782,14 @@ var createFactReader = (ts, ast) => {
|
|
|
612
782
|
plugs: plugsOf(source, builder),
|
|
613
783
|
doc: docOf(source, statement)
|
|
614
784
|
});
|
|
615
|
-
} else if (callee === "endpoint")
|
|
785
|
+
} else if (callee === "endpoint") {
|
|
786
|
+
const model = moduleRefOf(call.arguments[0], "srv");
|
|
616
787
|
facts.endpoints.push({
|
|
617
788
|
...name,
|
|
618
|
-
model
|
|
619
|
-
keys: endpointKeysOf(source, builder)
|
|
789
|
+
model,
|
|
790
|
+
keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
|
|
620
791
|
});
|
|
621
|
-
else if (callee === "store")
|
|
792
|
+
} else if (callee === "store")
|
|
622
793
|
facts.stores.push({
|
|
623
794
|
...name,
|
|
624
795
|
model: moduleRefOf(call.arguments[0], "sig"),
|
|
@@ -626,10 +797,439 @@ var createFactReader = (ts, ast) => {
|
|
|
626
797
|
actions: methodsOf(source, statement)
|
|
627
798
|
});
|
|
628
799
|
}
|
|
629
|
-
return facts;
|
|
630
|
-
};
|
|
631
|
-
return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
|
|
632
|
-
};
|
|
800
|
+
return { ...facts, ...guardsOf(source, imports, madeInPlace) };
|
|
801
|
+
};
|
|
802
|
+
return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
// pkgs/@akanjs/language/guardResolver.ts
|
|
806
|
+
var frameworkOwner = "akanjs";
|
|
807
|
+
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
808
|
+
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
809
|
+
var maxMapDepth = 4;
|
|
810
|
+
var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
|
|
811
|
+
var declaringPatternOf = (name, isAnyBinding) => {
|
|
812
|
+
const escaped = escapeRegExp(name);
|
|
813
|
+
const braced = "\\{(?:[^}]*?[\\s,])?";
|
|
814
|
+
const leads = [
|
|
815
|
+
"function\\s*\\*?\\s*",
|
|
816
|
+
"(?:const|let|var)\\s+",
|
|
817
|
+
`export\\s*${braced}`,
|
|
818
|
+
...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
|
|
819
|
+
];
|
|
820
|
+
return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
class GuardResolver {
|
|
824
|
+
#source;
|
|
825
|
+
#routes;
|
|
826
|
+
#guards = new Map;
|
|
827
|
+
#madeInPlace = new Map;
|
|
828
|
+
#mapEntries = new Map;
|
|
829
|
+
#reexports = new Map;
|
|
830
|
+
#declarations = new Map;
|
|
831
|
+
#starExports = new Map;
|
|
832
|
+
#ownerFiles = null;
|
|
833
|
+
#modules = null;
|
|
834
|
+
constructor(source, routes) {
|
|
835
|
+
this.#source = source;
|
|
836
|
+
this.#routes = routes;
|
|
837
|
+
}
|
|
838
|
+
find(local, site) {
|
|
839
|
+
return this.#findGuard(local, site, 0);
|
|
840
|
+
}
|
|
841
|
+
madeInPlace(fileName, name) {
|
|
842
|
+
return this.#madeInPlace.get(`${fileName}
|
|
843
|
+
${name}`);
|
|
844
|
+
}
|
|
845
|
+
declare(facts) {
|
|
846
|
+
const factories = new Map;
|
|
847
|
+
const declaredFactories = [];
|
|
848
|
+
for (const file of facts)
|
|
849
|
+
for (const fact of file.guardFactories) {
|
|
850
|
+
const factory = {
|
|
851
|
+
fact,
|
|
852
|
+
owner: file.owner,
|
|
853
|
+
site: file,
|
|
854
|
+
made: [],
|
|
855
|
+
isReal: fact.direct,
|
|
856
|
+
scope: fact.scope,
|
|
857
|
+
targets: []
|
|
858
|
+
};
|
|
859
|
+
factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
|
|
860
|
+
declaredFactories.push(factory);
|
|
861
|
+
}
|
|
862
|
+
for (const factory of declaredFactories)
|
|
863
|
+
factory.targets = factory.fact.via.map((callee) => this.#factoryAt(factories, callee, factory.site)).filter((target) => target !== undefined);
|
|
864
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
865
|
+
isGrowing = false;
|
|
866
|
+
for (const factory of declaredFactories) {
|
|
867
|
+
const target = factory.targets.find((other) => other.isReal);
|
|
868
|
+
if (factory.isReal || !target)
|
|
869
|
+
continue;
|
|
870
|
+
factory.isReal = true;
|
|
871
|
+
factory.scope ??= target.scope;
|
|
872
|
+
isGrowing = true;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
const partsOf = (factory, seen = new Set) => {
|
|
876
|
+
if (seen.has(factory))
|
|
877
|
+
return [];
|
|
878
|
+
seen.add(factory);
|
|
879
|
+
return [
|
|
880
|
+
{ fileName: factory.site.fileName, extent: factory.fact.extent },
|
|
881
|
+
...factory.targets.filter((target) => target.isReal).flatMap((target) => partsOf(target, seen))
|
|
882
|
+
];
|
|
883
|
+
};
|
|
884
|
+
const declare = (file, made, scope, parts) => {
|
|
885
|
+
const { name, span, extent, doc } = made;
|
|
886
|
+
const label = file.guardLabels[name];
|
|
887
|
+
const own = { fileName: file.fileName, extent };
|
|
888
|
+
const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
|
|
889
|
+
if (name.includes("@"))
|
|
890
|
+
this.#madeInPlace.set(`${file.fileName}
|
|
891
|
+
${name}`, guard);
|
|
892
|
+
else
|
|
893
|
+
this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
|
|
894
|
+
return guard;
|
|
895
|
+
};
|
|
896
|
+
const extending = [];
|
|
897
|
+
for (const file of facts) {
|
|
898
|
+
for (const made of file.guards) {
|
|
899
|
+
const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
|
|
900
|
+
if (!made.factory)
|
|
901
|
+
declare(file, made, made.scope, []);
|
|
902
|
+
else if (factory?.isReal)
|
|
903
|
+
factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
|
|
904
|
+
}
|
|
905
|
+
for (const made of file.guardCalls) {
|
|
906
|
+
if (made.kind === "extends") {
|
|
907
|
+
extending.push([file, made]);
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
const factory = this.#factoryAt(factories, made.callee, file);
|
|
911
|
+
if (factory?.isReal)
|
|
912
|
+
factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
916
|
+
isGrowing = false;
|
|
917
|
+
for (const [idx, entry] of extending.entries()) {
|
|
918
|
+
const [file, made] = entry;
|
|
919
|
+
const found = this.#findGuard(made.callee, file, 0, true);
|
|
920
|
+
if (!("guard" in found))
|
|
921
|
+
continue;
|
|
922
|
+
declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
|
|
923
|
+
extending.splice(idx, 1);
|
|
924
|
+
isGrowing = true;
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
for (const { fact, made, isReal } of declaredFactories)
|
|
929
|
+
if (isReal && made.every((guard) => !guard.doc))
|
|
930
|
+
for (const guard of made)
|
|
931
|
+
guard.doc = fact.doc;
|
|
932
|
+
for (const list of this.#guards.values())
|
|
933
|
+
for (const guard of list)
|
|
934
|
+
if (list.some((other) => other !== guard && other.owner === guard.owner))
|
|
935
|
+
guard.file = this.inOwner(guard.span.fileName, guard.owner);
|
|
936
|
+
for (const guard of this.#madeInPlace.values())
|
|
937
|
+
guard.file = this.inOwner(guard.span.fileName, guard.owner);
|
|
938
|
+
return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
|
|
939
|
+
}
|
|
940
|
+
#factoryAt(factories, callee, site, depth = 0) {
|
|
941
|
+
const [head = callee, member] = callee.split(".");
|
|
942
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
943
|
+
if (!imported)
|
|
944
|
+
return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
|
|
945
|
+
if (member && imported.imported !== "*")
|
|
946
|
+
return;
|
|
947
|
+
const from = this.ownerOfSpecifier(site.fileName, imported.from);
|
|
948
|
+
if (from === undefined || from === frameworkOwner)
|
|
949
|
+
return;
|
|
950
|
+
const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
|
|
951
|
+
const candidates = factories.get(name) ?? [];
|
|
952
|
+
const near = this.pathOfSpecifier(site.fileName, imported.from);
|
|
953
|
+
const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
|
|
954
|
+
if (own)
|
|
955
|
+
return own;
|
|
956
|
+
const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
|
|
957
|
+
if (reexport)
|
|
958
|
+
return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
|
|
959
|
+
return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
|
|
960
|
+
}
|
|
961
|
+
#findGuard(local, site, depth, isBase = false) {
|
|
962
|
+
const segments = local.split(".");
|
|
963
|
+
const head = segments[0] ?? local;
|
|
964
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
965
|
+
const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
|
|
966
|
+
if (imported && from === undefined)
|
|
967
|
+
return { external: true };
|
|
968
|
+
const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
|
|
969
|
+
const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
|
|
970
|
+
const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
|
|
971
|
+
const holder = imported ? from ?? null : site.owner;
|
|
972
|
+
const [objectName, member] = path;
|
|
973
|
+
if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
|
|
974
|
+
const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
|
|
975
|
+
if (made)
|
|
976
|
+
return { guard: made };
|
|
977
|
+
for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
|
|
978
|
+
const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
|
|
979
|
+
if ("guard" in found)
|
|
980
|
+
return found;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
const name = path[path.length - 1] ?? head;
|
|
984
|
+
if (from === frameworkOwner)
|
|
985
|
+
return { name, from };
|
|
986
|
+
const declared = this.#guards.get(name) ?? [];
|
|
987
|
+
const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
|
|
988
|
+
if (own)
|
|
989
|
+
return { guard: own };
|
|
990
|
+
if (!imported) {
|
|
991
|
+
const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
|
|
992
|
+
return visible ? { guard: visible } : { name, from };
|
|
993
|
+
}
|
|
994
|
+
const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
|
|
995
|
+
if (reexport)
|
|
996
|
+
return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
|
|
997
|
+
const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
|
|
998
|
+
return reached ? { guard: reached } : { name, from };
|
|
999
|
+
}
|
|
1000
|
+
reexportOf(owner, name, near) {
|
|
1001
|
+
const key = `${owner}
|
|
1002
|
+
${name}
|
|
1003
|
+
${near ?? ""}`;
|
|
1004
|
+
const cached = this.#reexports.get(key);
|
|
1005
|
+
if (cached !== undefined)
|
|
1006
|
+
return cached;
|
|
1007
|
+
let found = null;
|
|
1008
|
+
const exporting = new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
|
|
1009
|
+
for (const fileName of this.#filesOf(owner, name, near)) {
|
|
1010
|
+
const text = this.#source.readText(fileName);
|
|
1011
|
+
if (text === undefined || !exporting.test(text))
|
|
1012
|
+
continue;
|
|
1013
|
+
const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
|
|
1014
|
+
if (!reexport)
|
|
1015
|
+
continue;
|
|
1016
|
+
const site = { fileName, owner, imports: [] };
|
|
1017
|
+
found = reexport.from ? {
|
|
1018
|
+
local: reexport.name,
|
|
1019
|
+
site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
|
|
1020
|
+
} : { local: reexport.name, site };
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
this.#reexports.set(key, found);
|
|
1024
|
+
return found;
|
|
1025
|
+
}
|
|
1026
|
+
#reexportIn(source, name) {
|
|
1027
|
+
const { ts, reader } = this.#source;
|
|
1028
|
+
for (const statement of source.statements) {
|
|
1029
|
+
if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
|
|
1030
|
+
continue;
|
|
1031
|
+
const { exportClause, moduleSpecifier } = statement;
|
|
1032
|
+
if (!exportClause || !ts.isNamedExports(exportClause))
|
|
1033
|
+
continue;
|
|
1034
|
+
const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
|
|
1035
|
+
if (!element)
|
|
1036
|
+
continue;
|
|
1037
|
+
const original = (element.propertyName ?? element.name).text;
|
|
1038
|
+
if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
|
|
1039
|
+
return { name: original, from: moduleSpecifier.text };
|
|
1040
|
+
const imported = reader.importsIn(source).find((item) => item.name === original);
|
|
1041
|
+
if (!imported)
|
|
1042
|
+
return { name: original, from: null };
|
|
1043
|
+
return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
|
|
1044
|
+
}
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1047
|
+
#filesOf(owner, name, near) {
|
|
1048
|
+
const isNear = (fileName) => {
|
|
1049
|
+
const relative = this.#source.relative(fileName);
|
|
1050
|
+
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
1051
|
+
};
|
|
1052
|
+
const candidates = this.filesIn(owner).filter(([, words]) => words.includes(`
|
|
1053
|
+
${name}
|
|
1054
|
+
`)).map(([fileName]) => fileName);
|
|
1055
|
+
return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
|
|
1056
|
+
}
|
|
1057
|
+
#mapEntriesOf(objectName, member, fileName, owner) {
|
|
1058
|
+
const key = `${objectName}
|
|
1059
|
+
${member}
|
|
1060
|
+
${fileName ?? ""}
|
|
1061
|
+
${owner ?? ""}`;
|
|
1062
|
+
const cached = this.#mapEntries.get(key);
|
|
1063
|
+
if (cached)
|
|
1064
|
+
return cached;
|
|
1065
|
+
const { ts, reader } = this.#source;
|
|
1066
|
+
const entries = [];
|
|
1067
|
+
const fileNames = [
|
|
1068
|
+
...fileName ? [fileName] : [],
|
|
1069
|
+
...(owner === null ? [] : this.filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
|
|
1070
|
+
${objectName}
|
|
1071
|
+
`) && words.includes(`
|
|
1072
|
+
${member}
|
|
1073
|
+
`)).map(([other]) => other)
|
|
1074
|
+
];
|
|
1075
|
+
for (const holder of fileNames) {
|
|
1076
|
+
const text = this.#source.readText(holder);
|
|
1077
|
+
if (text === undefined)
|
|
1078
|
+
continue;
|
|
1079
|
+
const source = this.#source.parse(holder, text);
|
|
1080
|
+
const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
|
|
1081
|
+
for (const statement of source.statements) {
|
|
1082
|
+
if (!ts.isVariableStatement(statement))
|
|
1083
|
+
continue;
|
|
1084
|
+
const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
|
|
1085
|
+
const object = map && reader.unwrap(map);
|
|
1086
|
+
if (!object || !ts.isObjectLiteralExpression(object))
|
|
1087
|
+
continue;
|
|
1088
|
+
for (const property of object.properties) {
|
|
1089
|
+
if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
|
|
1090
|
+
entries.push({ name: member, site });
|
|
1091
|
+
if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
|
|
1092
|
+
continue;
|
|
1093
|
+
const value = reader.unwrap(property.initializer);
|
|
1094
|
+
if (ts.isIdentifier(value))
|
|
1095
|
+
entries.push({ name: value.text, site });
|
|
1096
|
+
else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
|
|
1097
|
+
entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
this.#mapEntries.set(key, entries);
|
|
1102
|
+
return entries;
|
|
1103
|
+
}
|
|
1104
|
+
declarationOf(owner, name, near, depth = 0) {
|
|
1105
|
+
const key = `${owner}
|
|
1106
|
+
${name}
|
|
1107
|
+
${near ?? ""}`;
|
|
1108
|
+
const cached = this.#declarations.get(key);
|
|
1109
|
+
if (cached !== undefined)
|
|
1110
|
+
return cached;
|
|
1111
|
+
this.#declarations.set(key, null);
|
|
1112
|
+
const declaring = declaringPatternOf(name, false);
|
|
1113
|
+
let found = null;
|
|
1114
|
+
for (const fileName of this.#filesOf(owner, name, near)) {
|
|
1115
|
+
const text = this.#source.readText(fileName);
|
|
1116
|
+
if (text === undefined || !declaring.test(text))
|
|
1117
|
+
continue;
|
|
1118
|
+
const source = this.#source.parse(fileName, text);
|
|
1119
|
+
const declared = this.#routes.declarationIn(source, name);
|
|
1120
|
+
if (declared) {
|
|
1121
|
+
found = { source, ...declared, name };
|
|
1122
|
+
break;
|
|
1123
|
+
}
|
|
1124
|
+
const reexport = this.#reexportIn(source, name);
|
|
1125
|
+
if (!reexport)
|
|
1126
|
+
continue;
|
|
1127
|
+
if (!reexport.from) {
|
|
1128
|
+
const local = this.#routes.declarationIn(source, reexport.name);
|
|
1129
|
+
found = local ? { source, ...local, name: reexport.name } : null;
|
|
1130
|
+
break;
|
|
1131
|
+
}
|
|
1132
|
+
const next = this.ownerOfSpecifier(fileName, reexport.from);
|
|
1133
|
+
if (next && next !== frameworkOwner && depth < maxMapDepth)
|
|
1134
|
+
found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
|
|
1135
|
+
break;
|
|
1136
|
+
}
|
|
1137
|
+
this.#declarations.set(key, found);
|
|
1138
|
+
return found;
|
|
1139
|
+
}
|
|
1140
|
+
#throughStar(declared, owner, name, near) {
|
|
1141
|
+
if (!declared.length)
|
|
1142
|
+
return;
|
|
1143
|
+
const { owners, isOpaque } = this.#starExportsOf(owner, near);
|
|
1144
|
+
const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
|
|
1145
|
+
return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
|
|
1146
|
+
}
|
|
1147
|
+
#starExportsOf(owner, near) {
|
|
1148
|
+
const cached = this.#starExports.get(near);
|
|
1149
|
+
if (cached)
|
|
1150
|
+
return cached;
|
|
1151
|
+
const found = { owners: [], isOpaque: false };
|
|
1152
|
+
this.#starExports.set(near, found);
|
|
1153
|
+
const paths = [near];
|
|
1154
|
+
for (let idx = 0;idx < paths.length && !found.isOpaque; idx++)
|
|
1155
|
+
for (const fileName of this.#modulesAt(paths[idx] ?? "")) {
|
|
1156
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
|
|
1157
|
+
const target = this.ownerOfSpecifier(fileName, specifier);
|
|
1158
|
+
const path = this.pathOfSpecifier(fileName, specifier);
|
|
1159
|
+
if (target === undefined || target === frameworkOwner || path === null)
|
|
1160
|
+
found.isOpaque = true;
|
|
1161
|
+
else if (!paths.includes(path))
|
|
1162
|
+
paths.push(path);
|
|
1163
|
+
if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
|
|
1164
|
+
found.owners.push(target);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return found;
|
|
1168
|
+
}
|
|
1169
|
+
#modulesAt(path) {
|
|
1170
|
+
if (!this.#modules) {
|
|
1171
|
+
this.#modules = new Map;
|
|
1172
|
+
for (const [fileName] of this.#source.files) {
|
|
1173
|
+
const module2 = this.#source.relative(fileName).replace(/\.tsx?$/, "");
|
|
1174
|
+
for (const key of module2.endsWith("/index") ? [module2, module2.slice(0, -6)] : [module2])
|
|
1175
|
+
this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return this.#modules.get(path) ?? [];
|
|
1179
|
+
}
|
|
1180
|
+
declares(owner, name) {
|
|
1181
|
+
const declaring = declaringPatternOf(name, true);
|
|
1182
|
+
return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
|
|
1183
|
+
}
|
|
1184
|
+
filesIn(owner) {
|
|
1185
|
+
if (!this.#ownerFiles) {
|
|
1186
|
+
this.#ownerFiles = new Map;
|
|
1187
|
+
for (const entry of this.#source.files) {
|
|
1188
|
+
const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
|
|
1189
|
+
const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
|
|
1190
|
+
if (files)
|
|
1191
|
+
files.push(entry);
|
|
1192
|
+
else if (fileOwner !== null)
|
|
1193
|
+
this.#ownerFiles.set(fileOwner, [entry]);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return this.#ownerFiles.get(owner) ?? [];
|
|
1197
|
+
}
|
|
1198
|
+
#nearest(items, fileOf, near) {
|
|
1199
|
+
const isNear = (fileName) => {
|
|
1200
|
+
const relative = this.#source.relative(fileName);
|
|
1201
|
+
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
1202
|
+
};
|
|
1203
|
+
return items.find((item) => isNear(fileOf(item))) ?? items[0];
|
|
1204
|
+
}
|
|
1205
|
+
inOwner(fileName, owner) {
|
|
1206
|
+
const relative = this.#source.relative(fileName);
|
|
1207
|
+
return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
|
|
1208
|
+
}
|
|
1209
|
+
ownerOfSpecifier(fileName, specifier) {
|
|
1210
|
+
if (frameworkPattern.test(specifier))
|
|
1211
|
+
return frameworkOwner;
|
|
1212
|
+
const path = this.pathOfSpecifier(fileName, specifier);
|
|
1213
|
+
return path === null ? undefined : ownerOf(path) ?? undefined;
|
|
1214
|
+
}
|
|
1215
|
+
pathOfSpecifier(fileName, specifier) {
|
|
1216
|
+
const alias = specifier.match(aliasPattern);
|
|
1217
|
+
if (alias)
|
|
1218
|
+
return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
|
|
1219
|
+
if (!specifier.startsWith("."))
|
|
1220
|
+
return null;
|
|
1221
|
+
const segments = this.#source.relative(fileName).split("/").slice(0, -1);
|
|
1222
|
+
for (const segment of specifier.split("/"))
|
|
1223
|
+
if (segment === "..")
|
|
1224
|
+
segments.pop();
|
|
1225
|
+
else if (segment !== ".")
|
|
1226
|
+
segments.push(segment);
|
|
1227
|
+
return segments.join("/");
|
|
1228
|
+
}
|
|
1229
|
+
visible(target, from) {
|
|
1230
|
+
return !from || !target || target === from || target.startsWith("libs/");
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
633
1233
|
|
|
634
1234
|
// pkgs/akanjs/common/trimSlashes.ts
|
|
635
1235
|
var trimTrailingSlashes = (value) => {
|
|
@@ -778,24 +1378,23 @@ rootLayout
|
|
|
778
1378
|
}
|
|
779
1379
|
declarationIn(source, name) {
|
|
780
1380
|
const ts = this.#ts;
|
|
1381
|
+
const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
|
|
1382
|
+
const [first] = overloads;
|
|
1383
|
+
const last = overloads.at(-1);
|
|
1384
|
+
if (first?.name && last)
|
|
1385
|
+
return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
|
|
781
1386
|
for (const statement of source.statements) {
|
|
782
|
-
if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
|
|
783
|
-
return { statement, declared: statement.name, initializer: null };
|
|
784
1387
|
if (!ts.isVariableStatement(statement))
|
|
785
1388
|
continue;
|
|
786
1389
|
const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
|
|
787
1390
|
if (declaration)
|
|
788
|
-
return { statement, declared: declaration.name,
|
|
1391
|
+
return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
|
|
789
1392
|
}
|
|
790
1393
|
return null;
|
|
791
1394
|
}
|
|
792
1395
|
#guardOf(source, exported) {
|
|
793
1396
|
const ts = this.#ts;
|
|
794
1397
|
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
1398
|
while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
800
1399
|
const { name, expression } = current.expression;
|
|
801
1400
|
const [stage] = current.arguments;
|
|
@@ -807,9 +1406,20 @@ rootLayout
|
|
|
807
1406
|
}
|
|
808
1407
|
#callsIn(source, stage) {
|
|
809
1408
|
const ts = this.#ts;
|
|
810
|
-
const
|
|
1409
|
+
const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
|
|
1410
|
+
const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
|
|
1411
|
+
const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
|
|
811
1412
|
const calls = new Map;
|
|
812
|
-
const callOf = (
|
|
1413
|
+
const callOf = (callee, shadowed) => {
|
|
1414
|
+
if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
|
|
1415
|
+
const name = `${callee.expression.text}.${callee.name.text}`;
|
|
1416
|
+
if (!calls.has(name))
|
|
1417
|
+
calls.set(name, { name, local: null });
|
|
1418
|
+
return true;
|
|
1419
|
+
}
|
|
1420
|
+
if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
|
|
1421
|
+
return false;
|
|
1422
|
+
const name = callee.text;
|
|
813
1423
|
if (calls.has(name))
|
|
814
1424
|
return true;
|
|
815
1425
|
const declared = this.declarationIn(source, name);
|
|
@@ -819,23 +1429,71 @@ rootLayout
|
|
|
819
1429
|
name,
|
|
820
1430
|
local: declared && {
|
|
821
1431
|
span: this.#reader.spanOf(source, declared.declared),
|
|
822
|
-
extent:
|
|
1432
|
+
extent: declared.extent,
|
|
823
1433
|
doc: this.#reader.docOf(source, declared.statement)
|
|
824
1434
|
}
|
|
825
1435
|
});
|
|
826
1436
|
return true;
|
|
827
1437
|
};
|
|
828
|
-
const visit = (node) => {
|
|
829
|
-
|
|
1438
|
+
const visit = (node, outer) => {
|
|
1439
|
+
const shadowed = this.#scopeOf(node, outer);
|
|
1440
|
+
if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
|
|
830
1441
|
return;
|
|
831
|
-
ts.forEachChild(node, visit);
|
|
1442
|
+
ts.forEachChild(node, (child) => visit(child, shadowed));
|
|
832
1443
|
};
|
|
833
1444
|
if (ts.isIdentifier(stage))
|
|
834
|
-
callOf(stage
|
|
1445
|
+
callOf(stage, new Set);
|
|
835
1446
|
else
|
|
836
|
-
visit(stage);
|
|
1447
|
+
visit(stage, new Set);
|
|
837
1448
|
return [...calls.values()];
|
|
838
1449
|
}
|
|
1450
|
+
#scopeOf(node, outer) {
|
|
1451
|
+
const ts = this.#ts;
|
|
1452
|
+
const names = [];
|
|
1453
|
+
const bind = (name) => {
|
|
1454
|
+
if (ts.isIdentifier(name))
|
|
1455
|
+
names.push(name.text);
|
|
1456
|
+
else
|
|
1457
|
+
for (const element of name.elements)
|
|
1458
|
+
if (!ts.isOmittedExpression(element))
|
|
1459
|
+
bind(element.name);
|
|
1460
|
+
};
|
|
1461
|
+
const declare = (statement) => {
|
|
1462
|
+
if (ts.isVariableStatement(statement))
|
|
1463
|
+
for (const declaration of statement.declarationList.declarations)
|
|
1464
|
+
bind(declaration.name);
|
|
1465
|
+
else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
|
|
1466
|
+
names.push(statement.name.text);
|
|
1467
|
+
};
|
|
1468
|
+
const hoist = (child) => {
|
|
1469
|
+
if (ts.isFunctionLike(child) || ts.isClassLike(child))
|
|
1470
|
+
return;
|
|
1471
|
+
if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
|
|
1472
|
+
for (const declaration of child.declarations)
|
|
1473
|
+
bind(declaration.name);
|
|
1474
|
+
ts.forEachChild(child, hoist);
|
|
1475
|
+
};
|
|
1476
|
+
if (ts.isFunctionLike(node)) {
|
|
1477
|
+
for (const parameter of node.parameters)
|
|
1478
|
+
bind(parameter.name);
|
|
1479
|
+
if (ts.isFunctionExpression(node) && node.name)
|
|
1480
|
+
names.push(node.name.text);
|
|
1481
|
+
if ("body" in node && node.body)
|
|
1482
|
+
ts.forEachChild(node.body, hoist);
|
|
1483
|
+
} else if (ts.isClassExpression(node) && node.name)
|
|
1484
|
+
names.push(node.name.text);
|
|
1485
|
+
else if (ts.isBlock(node))
|
|
1486
|
+
node.statements.forEach(declare);
|
|
1487
|
+
else if (ts.isCaseBlock(node))
|
|
1488
|
+
for (const clause of node.clauses)
|
|
1489
|
+
clause.statements.forEach(declare);
|
|
1490
|
+
else if (ts.isCatchClause(node) && node.variableDeclaration)
|
|
1491
|
+
bind(node.variableDeclaration.name);
|
|
1492
|
+
else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
|
|
1493
|
+
for (const declaration of node.initializer.declarations)
|
|
1494
|
+
bind(declaration.name);
|
|
1495
|
+
return names.length ? new Set([...outer, ...names]) : outer;
|
|
1496
|
+
}
|
|
839
1497
|
}
|
|
840
1498
|
|
|
841
1499
|
// pkgs/@akanjs/language/akanGraph.ts
|
|
@@ -851,10 +1509,6 @@ var builtinGuards = {
|
|
|
851
1509
|
Public: { scope: "account", doc: "Passes every caller, signed in or not." },
|
|
852
1510
|
None: { scope: "account", doc: "Refuses every caller." }
|
|
853
1511
|
};
|
|
854
|
-
var frameworkOwner = "akanjs";
|
|
855
|
-
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
856
|
-
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
857
|
-
var maxMapDepth = 4;
|
|
858
1512
|
|
|
859
1513
|
class GraphBuilder {
|
|
860
1514
|
#source;
|
|
@@ -862,16 +1516,16 @@ class GraphBuilder {
|
|
|
862
1516
|
#edges = new Map;
|
|
863
1517
|
#contexts = new Map;
|
|
864
1518
|
#members = new Map;
|
|
865
|
-
#guards = new Map;
|
|
866
|
-
#mapEntries = new Map;
|
|
867
1519
|
#routeFiles = new Map;
|
|
868
|
-
#declarations = new Map;
|
|
869
1520
|
#routes;
|
|
1521
|
+
#resolver;
|
|
870
1522
|
#adaptors = new Map;
|
|
871
1523
|
#plugs = new Map;
|
|
1524
|
+
#serviceFields = new Set;
|
|
872
1525
|
constructor(source) {
|
|
873
1526
|
this.#source = source;
|
|
874
1527
|
this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
|
|
1528
|
+
this.#resolver = new GuardResolver(source, this.#routes);
|
|
875
1529
|
}
|
|
876
1530
|
build() {
|
|
877
1531
|
const { facts } = this.#source;
|
|
@@ -883,17 +1537,28 @@ class GraphBuilder {
|
|
|
883
1537
|
...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
|
|
884
1538
|
];
|
|
885
1539
|
const typed = [];
|
|
1540
|
+
const adaptorCounts = new Map;
|
|
886
1541
|
for (const file of facts)
|
|
887
1542
|
for (const fact of file.adaptors) {
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
this.#adaptorNodeOf(declared);
|
|
1543
|
+
const key = this.#key("adaptor", file.owner, fact.name);
|
|
1544
|
+
adaptorCounts.set(key, (adaptorCounts.get(key) ?? 0) + 1);
|
|
891
1545
|
}
|
|
1546
|
+
const declaredAdaptors = facts.flatMap((file) => file.adaptors.map((fact) => {
|
|
1547
|
+
const key = this.#key("adaptor", file.owner, fact.name);
|
|
1548
|
+
const id = (adaptorCounts.get(key) ?? 0) > 1 ? this.#key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
|
|
1549
|
+
const declared = { fact, owner: file.owner, fileName: file.fileName, id };
|
|
1550
|
+
this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
|
|
1551
|
+
this.#adaptorNodeOf(declared);
|
|
1552
|
+
return { declared, file };
|
|
1553
|
+
}));
|
|
1554
|
+
for (const { declared, file } of declaredAdaptors)
|
|
1555
|
+
for (const plug of declared.fact.plugs)
|
|
1556
|
+
this.#addEdge(declared.id, this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
|
|
892
1557
|
for (const file of facts)
|
|
893
|
-
for (const
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1558
|
+
for (const binding of file.adaptorBindings)
|
|
1559
|
+
this.#addEdge(this.#adaptorIdOf(binding.role, file), this.#adaptorIdOf(binding.adaptor, file), "binds", file.owner, this.#location(binding.span));
|
|
1560
|
+
for (const guard of this.#resolver.declare(facts))
|
|
1561
|
+
this.#guardNodeOf(guard);
|
|
897
1562
|
for (const file of facts) {
|
|
898
1563
|
const { owner } = file;
|
|
899
1564
|
const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
|
|
@@ -924,10 +1589,18 @@ class GraphBuilder {
|
|
|
924
1589
|
}
|
|
925
1590
|
for (const method of service.methods)
|
|
926
1591
|
this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
|
|
927
|
-
for (const plug of service.plugs)
|
|
928
|
-
|
|
1592
|
+
for (const plug of service.plugs) {
|
|
1593
|
+
const key = `${refName}
|
|
1594
|
+
${plug.name}`;
|
|
1595
|
+
this.#plugs.set(key, [
|
|
1596
|
+
...this.#plugs.get(key) ?? [],
|
|
1597
|
+
{ owner, target: this.#adaptorIdOf(plug.target, file) }
|
|
1598
|
+
]);
|
|
1599
|
+
}
|
|
1600
|
+
for (const field of service.fields)
|
|
1601
|
+
this.#serviceFields.add(`${owner ?? ""}
|
|
929
1602
|
${refName}
|
|
930
|
-
${
|
|
1603
|
+
${field}`);
|
|
931
1604
|
}
|
|
932
1605
|
for (const filter of file.filters) {
|
|
933
1606
|
if (!filter.name.endsWith("Filter"))
|
|
@@ -937,31 +1610,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
|
937
1610
|
for (const query of filter.queries)
|
|
938
1611
|
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
1612
|
}
|
|
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
|
-
}
|
|
1613
|
+
this.#addGuarded(file);
|
|
965
1614
|
for (const internal of file.internals)
|
|
966
1615
|
for (const key of internal.keys)
|
|
967
1616
|
if (internal.model && key.type !== "resolveField")
|
|
@@ -977,7 +1626,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
|
977
1626
|
types.set(node.label, [...types.get(node.label) ?? [], node]);
|
|
978
1627
|
for (const node of typed)
|
|
979
1628
|
for (const field of node.fields ?? []) {
|
|
980
|
-
const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
|
|
1629
|
+
const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
|
|
981
1630
|
field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
|
|
982
1631
|
if (field.ref)
|
|
983
1632
|
this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
|
|
@@ -1059,61 +1708,52 @@ ${label ?? ""}`;
|
|
|
1059
1708
|
else
|
|
1060
1709
|
this.#edges.set(key, { from, to, kind, label, count: 1, at });
|
|
1061
1710
|
}
|
|
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);
|
|
1711
|
+
#addGuarded(file) {
|
|
1712
|
+
const { owner } = file;
|
|
1713
|
+
for (const slice of file.slices) {
|
|
1714
|
+
const refName = slice.model;
|
|
1715
|
+
if (!refName)
|
|
1716
|
+
continue;
|
|
1717
|
+
const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
|
|
1718
|
+
for (const guard of slice.guards)
|
|
1719
|
+
this.#guard(fallback, guard, file);
|
|
1720
|
+
for (const key of slice.keys) {
|
|
1721
|
+
const detail = [
|
|
1722
|
+
...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
|
|
1723
|
+
...key.live ? ["live"] : []
|
|
1724
|
+
];
|
|
1725
|
+
const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
1726
|
+
for (const guard of key.guards)
|
|
1727
|
+
this.#guard(node, guard, file);
|
|
1094
1728
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1729
|
+
}
|
|
1730
|
+
for (const endpoint of file.endpoints) {
|
|
1731
|
+
const refName = endpoint.model;
|
|
1732
|
+
if (!refName)
|
|
1733
|
+
continue;
|
|
1734
|
+
for (const key of endpoint.keys) {
|
|
1735
|
+
const detail = [
|
|
1736
|
+
...key.verb ? [key.verb] : [],
|
|
1737
|
+
...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
|
|
1738
|
+
];
|
|
1739
|
+
const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
1740
|
+
for (const guard of key.guards)
|
|
1741
|
+
this.#guard(node, guard, file);
|
|
1104
1742
|
}
|
|
1105
1743
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1744
|
+
}
|
|
1745
|
+
#guard(node, guard, file) {
|
|
1746
|
+
const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
|
|
1747
|
+
if (!local || local === "?")
|
|
1748
|
+
return;
|
|
1749
|
+
const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
|
|
1750
|
+
if (id)
|
|
1751
|
+
this.#addEdge(node.id, id, "guard", role, node.at);
|
|
1114
1752
|
}
|
|
1115
1753
|
#guardIdOf(local, site) {
|
|
1116
|
-
const found = this.#
|
|
1754
|
+
const found = this.#resolver.find(local, site);
|
|
1755
|
+
if ("external" in found)
|
|
1756
|
+
return null;
|
|
1117
1757
|
if ("guard" in found)
|
|
1118
1758
|
return this.#guardNodeOf(found.guard);
|
|
1119
1759
|
const { name, from } = found;
|
|
@@ -1126,99 +1766,18 @@ ${label ?? ""}`;
|
|
|
1126
1766
|
node.doc = builtin.doc;
|
|
1127
1767
|
return node.id;
|
|
1128
1768
|
}
|
|
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;
|
|
1769
|
+
#madeInPlaceIdOf(name, file) {
|
|
1770
|
+
const made = this.#resolver.madeInPlace(file.fileName, name);
|
|
1771
|
+
if (made)
|
|
1772
|
+
return this.#guardNodeOf(made);
|
|
1773
|
+
const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
|
|
1774
|
+
const imported = file.imports.find((item) => item.name === head);
|
|
1775
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
|
|
1776
|
+
if (from === undefined || from === frameworkOwner)
|
|
1777
|
+
return null;
|
|
1778
|
+
const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
|
|
1779
|
+
const label = guardLabelOf(name, file.guardLabels);
|
|
1780
|
+
return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
|
|
1222
1781
|
}
|
|
1223
1782
|
#addRoutes() {
|
|
1224
1783
|
const routes = [];
|
|
@@ -1271,22 +1830,23 @@ ${dir}`, node);
|
|
|
1271
1830
|
}
|
|
1272
1831
|
#routeGuardIdOf({ name: local, local: declared }, fact, text) {
|
|
1273
1832
|
if (declared) {
|
|
1274
|
-
const
|
|
1275
|
-
const id = this.#key("guard", fact.owner, `${file}#${local}`);
|
|
1833
|
+
const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
|
|
1276
1834
|
return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
|
|
1277
1835
|
}
|
|
1278
|
-
const
|
|
1279
|
-
|
|
1836
|
+
const [head = local, member] = local.split(".");
|
|
1837
|
+
const imported = fact.imports.find((item) => item.name === head);
|
|
1838
|
+
if (!imported || member && imported.imported !== "*")
|
|
1280
1839
|
return null;
|
|
1281
|
-
const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
|
|
1282
|
-
const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
|
|
1840
|
+
const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
|
|
1841
|
+
const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
|
|
1283
1842
|
if (!from || from === frameworkOwner)
|
|
1284
1843
|
return null;
|
|
1285
|
-
const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
|
|
1844
|
+
const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
|
|
1286
1845
|
if (!found)
|
|
1287
1846
|
return null;
|
|
1288
1847
|
const { reader } = this.#source;
|
|
1289
|
-
|
|
1848
|
+
const owner = this.#source.ownerOf(found.source.fileName) ?? from;
|
|
1849
|
+
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
1850
|
}
|
|
1291
1851
|
#routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
|
|
1292
1852
|
const isNew = !this.#nodes.has(id);
|
|
@@ -1299,18 +1859,70 @@ ${dir}`, node);
|
|
|
1299
1859
|
#adaptorIdOf(local, file) {
|
|
1300
1860
|
const [head, member] = local.split(".");
|
|
1301
1861
|
const imported = file.imports.find((item) => item.name === head);
|
|
1302
|
-
const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1303
1862
|
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1863
|
+
const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
|
|
1864
|
+
if (imported && path === null && !frameworkPattern.test(imported.from))
|
|
1865
|
+
return this.#packageAdaptorOf(name, imported.from);
|
|
1866
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1304
1867
|
const declared = this.#adaptors.get(name) ?? [];
|
|
1305
|
-
const found = from === undefined ? declared
|
|
1868
|
+
const found = from === undefined ? this.#adaptorIn(declared, file.owner, path, file.fileName) ?? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, file.owner)) : this.#adaptorIn(declared, from, path, file.fileName) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#resolver.visible(adaptor.owner, from)));
|
|
1306
1869
|
if (found)
|
|
1307
|
-
return
|
|
1870
|
+
return found.id;
|
|
1871
|
+
const reexported = from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
|
|
1872
|
+
if (reexported)
|
|
1873
|
+
return this.#packageAdaptorOf(reexported.name, reexported.from);
|
|
1308
1874
|
const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
|
|
1309
1875
|
const detail = from === frameworkOwner ? [frameworkOwner] : [];
|
|
1310
1876
|
return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
|
|
1311
1877
|
}
|
|
1312
|
-
#
|
|
1313
|
-
const
|
|
1878
|
+
#adaptorIn(declared, owner, path, fileName) {
|
|
1879
|
+
const own = declared.filter((adaptor) => adaptor.owner === owner);
|
|
1880
|
+
if (own.length < 2)
|
|
1881
|
+
return own.at(0);
|
|
1882
|
+
const pathOf = (adaptor) => this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
|
|
1883
|
+
const named = path === null ? own.find((adaptor) => adaptor.fileName === fileName) : own.find((adaptor) => pathOf(adaptor) === path || pathOf(adaptor) === `${path}/index`) ?? own.find((adaptor) => pathOf(adaptor).startsWith(`${path}/`));
|
|
1884
|
+
return named ?? own.at(0);
|
|
1885
|
+
}
|
|
1886
|
+
#pluggedOf({ owner, module: module2 }, field) {
|
|
1887
|
+
const plugs = this.#plugs.get(`${module2}
|
|
1888
|
+
${field}`) ?? [];
|
|
1889
|
+
const own = plugs.filter((plug) => plug.owner === owner);
|
|
1890
|
+
if (!own.length && this.#serviceFields.has(`${owner ?? ""}
|
|
1891
|
+
${module2}
|
|
1892
|
+
${field}`))
|
|
1893
|
+
return [];
|
|
1894
|
+
const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
|
|
1895
|
+
return [...new Set(inherited.map(({ target }) => target))];
|
|
1896
|
+
}
|
|
1897
|
+
#packageAdaptorOf(name, specifier) {
|
|
1898
|
+
if (frameworkPattern.test(specifier))
|
|
1899
|
+
return this.#addNode(this.#key("adaptor", null, name), "adaptor", null, null, name, null, [frameworkOwner]).id;
|
|
1900
|
+
const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
|
1901
|
+
return this.#addNode(this.#key("adaptor", pkg, name), "adaptor", null, null, name, null, [pkg]).id;
|
|
1902
|
+
}
|
|
1903
|
+
#packageShimOf(owner, name, near) {
|
|
1904
|
+
const reexport = this.#resolver.reexportOf(owner, name, near);
|
|
1905
|
+
const [shim] = reexport?.site.imports ?? [];
|
|
1906
|
+
if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
|
|
1907
|
+
return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
|
|
1908
|
+
if (reexport || !near)
|
|
1909
|
+
return null;
|
|
1910
|
+
const packages = new Set;
|
|
1911
|
+
for (const [fileName, words] of this.#resolver.filesIn(owner)) {
|
|
1912
|
+
const relative = this.#source.relative(fileName);
|
|
1913
|
+
const isNear = relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near;
|
|
1914
|
+
if (!isNear || !words.includes(`
|
|
1915
|
+
export
|
|
1916
|
+
`))
|
|
1917
|
+
continue;
|
|
1918
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
|
|
1919
|
+
if (this.#resolver.pathOfSpecifier(fileName, specifier) === null)
|
|
1920
|
+
packages.add(specifier);
|
|
1921
|
+
}
|
|
1922
|
+
const [only] = packages;
|
|
1923
|
+
return packages.size === 1 && only && !this.#resolver.declares(owner, name) ? { name, from: only } : null;
|
|
1924
|
+
}
|
|
1925
|
+
#adaptorNodeOf({ fact, owner, fileName, id }) {
|
|
1314
1926
|
const detail = fact.refName ? [fact.refName] : [];
|
|
1315
1927
|
const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
|
|
1316
1928
|
if (fact.doc && !node.doc)
|
|
@@ -1318,38 +1930,22 @@ ${dir}`, node);
|
|
|
1318
1930
|
this.#digest(node, this.#source.readText(fileName), fact.extent);
|
|
1319
1931
|
return node.id;
|
|
1320
1932
|
}
|
|
1321
|
-
#guardNodeOf({ name, owner, span, scope, doc, parts }) {
|
|
1322
|
-
const
|
|
1933
|
+
#guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
|
|
1934
|
+
const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
|
|
1935
|
+
const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
|
|
1323
1936
|
if (doc && !node.doc)
|
|
1324
1937
|
node.doc = doc;
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1938
|
+
if (node.digest)
|
|
1939
|
+
return node.id;
|
|
1940
|
+
const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
|
|
1941
|
+
if (!digest)
|
|
1942
|
+
return node.id;
|
|
1943
|
+
node.digest = digest;
|
|
1944
|
+
const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
|
|
1945
|
+
if (sources.length)
|
|
1946
|
+
node.sources = sources;
|
|
1331
1947
|
return node.id;
|
|
1332
1948
|
}
|
|
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
1949
|
#moduleDirOf(fileName) {
|
|
1354
1950
|
const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
|
|
1355
1951
|
return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
|
|
@@ -1385,20 +1981,13 @@ ${dir}`, node);
|
|
|
1385
1981
|
visit(constant);
|
|
1386
1982
|
return [...fields.values()];
|
|
1387
1983
|
}
|
|
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
1984
|
#targetNodeOf({ family, owner, refName, sourceKey }) {
|
|
1396
1985
|
const isDefault = /^[A-Z]/.test(sourceKey);
|
|
1397
1986
|
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
1987
|
return id && this.#nodes.has(id) ? id : null;
|
|
1399
1988
|
}
|
|
1400
1989
|
#membersNamed(kind, name, owner, refName) {
|
|
1401
|
-
const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
|
|
1990
|
+
const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
|
|
1402
1991
|
const own = nodes.filter((node) => node.owner === owner);
|
|
1403
1992
|
return (own.length ? own : nodes).map((node) => node.id);
|
|
1404
1993
|
}
|
|
@@ -1443,10 +2032,8 @@ st
|
|
|
1443
2032
|
visit(source);
|
|
1444
2033
|
}
|
|
1445
2034
|
#link(fileName, source, identifier, chain, context, owner) {
|
|
1446
|
-
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#
|
|
1447
|
-
|
|
1448
|
-
${chain[1]}`) : undefined;
|
|
1449
|
-
const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
2035
|
+
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#pluggedOf(context, chain[1]) : [];
|
|
2036
|
+
const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
1450
2037
|
const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
|
|
1451
2038
|
if (!targets.length)
|
|
1452
2039
|
return;
|
|
@@ -1752,6 +2339,10 @@ var endpointNames = (key) => {
|
|
|
1752
2339
|
};
|
|
1753
2340
|
|
|
1754
2341
|
// pkgs/@akanjs/language/nameTable.ts
|
|
2342
|
+
var guardDetailOf = (facts, guards) => {
|
|
2343
|
+
const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
|
|
2344
|
+
return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
|
|
2345
|
+
};
|
|
1755
2346
|
var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
|
|
1756
2347
|
|
|
1757
2348
|
class NameTable {
|
|
@@ -1851,7 +2442,7 @@ class NameTable {
|
|
|
1851
2442
|
role: "sig",
|
|
1852
2443
|
gate: "sig",
|
|
1853
2444
|
label: sliceClass.name,
|
|
1854
|
-
detail:
|
|
2445
|
+
detail: guardDetailOf(facts, sliceClass.guards)
|
|
1855
2446
|
});
|
|
1856
2447
|
if (modelDoc)
|
|
1857
2448
|
this.#add({
|
|
@@ -1916,7 +2507,7 @@ class NameTable {
|
|
|
1916
2507
|
sourceKey: slice.name,
|
|
1917
2508
|
span: slice.span,
|
|
1918
2509
|
label: `slice ${refName} (default)`,
|
|
1919
|
-
detail:
|
|
2510
|
+
detail: guardDetailOf(facts, slice.guards)
|
|
1920
2511
|
};
|
|
1921
2512
|
this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
|
|
1922
2513
|
for (const key of slice.keys)
|
|
@@ -1926,7 +2517,7 @@ class NameTable {
|
|
|
1926
2517
|
sourceKey: key.name,
|
|
1927
2518
|
span: key.span,
|
|
1928
2519
|
label: `slice ${key.name}`,
|
|
1929
|
-
detail: [...
|
|
2520
|
+
detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
|
|
1930
2521
|
}, refName, key.name, modelNameSet, slice.name);
|
|
1931
2522
|
}
|
|
1932
2523
|
if (filterClass) {
|
|
@@ -1983,7 +2574,7 @@ class NameTable {
|
|
|
1983
2574
|
role: key.verb === "pubsub" ? "subscribe" : "listen",
|
|
1984
2575
|
gate: null,
|
|
1985
2576
|
label: `${key.verb} ${key.name}`,
|
|
1986
|
-
detail:
|
|
2577
|
+
detail: guardDetailOf(facts, key.guards)
|
|
1987
2578
|
});
|
|
1988
2579
|
}
|
|
1989
2580
|
}
|
|
@@ -2041,14 +2632,12 @@ var dictionaryPattern = /\.dictionary\.ts$/;
|
|
|
2041
2632
|
var srvkitPattern = /\/srvkit\//;
|
|
2042
2633
|
var wordPattern = /[A-Za-z_$][\w$]*/g;
|
|
2043
2634
|
var identifierPattern = /^[A-Za-z_$][\w$]*$/;
|
|
2044
|
-
var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
|
|
2045
2635
|
var stateReceivers = new Set(["use", "sel", "get", "do"]);
|
|
2046
2636
|
var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
|
|
2047
2637
|
var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
|
|
2048
2638
|
var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
|
|
2049
2639
|
var parseCacheSize = 64;
|
|
2050
2640
|
var pathOf = (fileName) => fileName.replace(/\\/g, "/");
|
|
2051
|
-
var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
|
|
2052
2641
|
var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
|
|
2053
2642
|
|
|
2054
2643
|
class AkanIndex {
|
|
@@ -2179,6 +2768,8 @@ canPass
|
|
|
2179
2768
|
adapt
|
|
2180
2769
|
`) || words.includes(`
|
|
2181
2770
|
dangerouslyAdapt
|
|
2771
|
+
`) || words.includes(`
|
|
2772
|
+
applyAdaptor
|
|
2182
2773
|
`);
|
|
2183
2774
|
}
|
|
2184
2775
|
static #sourceKeyOf({ family, sourceKey, label, detail, span }) {
|