@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.js
CHANGED
|
@@ -8,7 +8,8 @@ var isEdited = (before, after) => before.digest !== undefined && after.digest !=
|
|
|
8
8
|
var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
9
9
|
|
|
10
10
|
class AkanGraphDiff {
|
|
11
|
-
static between(
|
|
11
|
+
static between(given, head) {
|
|
12
|
+
const base = AkanGraphDiff.#renamed(given, head);
|
|
12
13
|
const baseNodes = new Map(base.nodes.map((node) => [node.id, node]));
|
|
13
14
|
const headNodes = new Map(head.nodes.map((node) => [node.id, node]));
|
|
14
15
|
const marked = new Map;
|
|
@@ -27,6 +28,9 @@ class AkanGraphDiff {
|
|
|
27
28
|
...head.edges.flatMap((edge) => !baseKeys.has(edgeKeyOf(edge)) ? [{ ...edge, change: "added" }] : marked.has(edge.from) || marked.has(edge.to) ? [edge] : []),
|
|
28
29
|
...base.edges.filter((edge) => !headKeys.has(edgeKeyOf(edge))).map((edge) => ({ ...edge, change: "removed" }))
|
|
29
30
|
];
|
|
31
|
+
const shown = new Set([...marked.keys(), ...edges.flatMap((edge) => [edge.from, edge.to])]);
|
|
32
|
+
const listed = new Set(edges.map(edgeKeyOf));
|
|
33
|
+
edges.push(...head.edges.filter((edge) => edge.kind === "binds" && shown.has(edge.from) && !listed.has(edgeKeyOf(edge))));
|
|
30
34
|
const context = edges.flatMap((edge) => [edge.from, edge.to]).filter((id) => !marked.has(id)).flatMap((id) => {
|
|
31
35
|
const node = headNodes.get(id) ?? baseNodes.get(id);
|
|
32
36
|
return node ? [node] : [];
|
|
@@ -38,6 +42,40 @@ class AkanGraphDiff {
|
|
|
38
42
|
summary[node.change] += 1;
|
|
39
43
|
return { version: 1, nodes, edges, summary };
|
|
40
44
|
}
|
|
45
|
+
static #renamed(base, head) {
|
|
46
|
+
const baseIds = new Set(base.nodes.map((node) => node.id));
|
|
47
|
+
const headIds = new Set(head.nodes.map((node) => node.id));
|
|
48
|
+
const pairKeyOf = ({ kind, owner, name, at }) => at ? `${kind}
|
|
49
|
+
${owner ?? ""}
|
|
50
|
+
${name}
|
|
51
|
+
${at.file}` : null;
|
|
52
|
+
const shortIdOf = ({ kind, owner, name }) => `${kind}:${owner ?? ""}:${name}`;
|
|
53
|
+
const isFileKeyed = (node) => node.id !== shortIdOf(node) && node.id.startsWith(`${node.kind}:${node.owner ?? ""}:`) && node.id.endsWith(`#${node.name}`);
|
|
54
|
+
const fromBase = new Map(base.nodes.flatMap((node) => {
|
|
55
|
+
const key = headIds.has(node.id) ? null : pairKeyOf(node);
|
|
56
|
+
return key ? [[key, node]] : [];
|
|
57
|
+
}));
|
|
58
|
+
const renames = new Map;
|
|
59
|
+
for (const node of head.nodes) {
|
|
60
|
+
if (baseIds.has(node.id))
|
|
61
|
+
continue;
|
|
62
|
+
const key = pairKeyOf(node);
|
|
63
|
+
const before = key ? fromBase.get(key) : undefined;
|
|
64
|
+
if (!before)
|
|
65
|
+
continue;
|
|
66
|
+
const isPair = isFileKeyed(node) ? before.id === shortIdOf(node) : isFileKeyed(before) && node.id === shortIdOf(before);
|
|
67
|
+
if (isPair)
|
|
68
|
+
renames.set(before.id, node.id);
|
|
69
|
+
}
|
|
70
|
+
if (!renames.size)
|
|
71
|
+
return base;
|
|
72
|
+
const rename = (id) => renames.get(id) ?? id;
|
|
73
|
+
return {
|
|
74
|
+
...base,
|
|
75
|
+
nodes: base.nodes.map((node) => renames.has(node.id) ? { ...node, id: rename(node.id) } : node),
|
|
76
|
+
edges: base.edges.map((edge) => ({ ...edge, from: rename(edge.from), to: rename(edge.to) }))
|
|
77
|
+
};
|
|
78
|
+
}
|
|
41
79
|
static #mark(node, change) {
|
|
42
80
|
return node.fields ? { ...node, change, fields: node.fields.map((field) => ({ ...field, change })) } : { ...node, change };
|
|
43
81
|
}
|
|
@@ -71,6 +109,239 @@ class AkanGraphDiff {
|
|
|
71
109
|
return node.fields ? { ...node, fields: node.fields.map(({ digest: _field, ...field }) => field) } : node;
|
|
72
110
|
}
|
|
73
111
|
}
|
|
112
|
+
// pkgs/@akanjs/language/guardFacts.ts
|
|
113
|
+
var guardLabelOf = (guard, labels) => {
|
|
114
|
+
const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
|
|
115
|
+
const label = labels[local];
|
|
116
|
+
return label === undefined ? guard : role ? `${role} ${label}` : label;
|
|
117
|
+
};
|
|
118
|
+
var maxLabelArgs = 32;
|
|
119
|
+
var guardRefReaderOf = (ts, ast, unwrap) => {
|
|
120
|
+
const pathOf = (expression) => {
|
|
121
|
+
if (ts.isIdentifier(expression))
|
|
122
|
+
return expression.text;
|
|
123
|
+
if (!ts.isPropertyAccessExpression(expression))
|
|
124
|
+
return null;
|
|
125
|
+
const head = pathOf(expression.expression);
|
|
126
|
+
return head && `${head}.${expression.name.text}`;
|
|
127
|
+
};
|
|
128
|
+
const calleeOf = (call) => pathOf(unwrap(call.expression)) ?? "?";
|
|
129
|
+
const labelOf = (source, call) => {
|
|
130
|
+
const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
|
|
131
|
+
return `${calleeOf(call)}(${args.length <= maxLabelArgs && !args.includes(`
|
|
132
|
+
`) ? args : "…"})`;
|
|
133
|
+
};
|
|
134
|
+
const guardRefOf = (expression, made, member) => {
|
|
135
|
+
const value = unwrap(expression);
|
|
136
|
+
if (!ts.isCallExpression(value))
|
|
137
|
+
return pathOf(value) ?? ast.expressionName(value);
|
|
138
|
+
const prefix = `${member}:${calleeOf(value)}@`;
|
|
139
|
+
const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
|
|
140
|
+
made.push({ call: value, name });
|
|
141
|
+
return name;
|
|
142
|
+
};
|
|
143
|
+
const guardsIn = (source, object, made, member) => {
|
|
144
|
+
const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
|
|
145
|
+
if (!guards)
|
|
146
|
+
return [];
|
|
147
|
+
const refOf = (expression) => guardRefOf(expression, made, member);
|
|
148
|
+
if (ts.isArrayLiteralExpression(guards))
|
|
149
|
+
return guards.elements.map(refOf).filter((name) => name !== null);
|
|
150
|
+
if (ts.isObjectLiteralExpression(guards))
|
|
151
|
+
return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
|
|
152
|
+
const value = unwrap(property.initializer);
|
|
153
|
+
const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
|
|
154
|
+
return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
|
|
155
|
+
});
|
|
156
|
+
const single = refOf(guards);
|
|
157
|
+
return single ? [single] : [];
|
|
158
|
+
};
|
|
159
|
+
return { pathOf, labelOf, guardsIn };
|
|
160
|
+
};
|
|
161
|
+
var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf) => {
|
|
162
|
+
const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
|
|
163
|
+
const scopeOf = (node) => {
|
|
164
|
+
const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
|
|
165
|
+
const value = scope && unwrap(scope);
|
|
166
|
+
return value && ts.isStringLiteralLike(value) ? value.text : null;
|
|
167
|
+
};
|
|
168
|
+
const functionsOf = (source) => {
|
|
169
|
+
const functions = new Map;
|
|
170
|
+
for (const statement of source.statements) {
|
|
171
|
+
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
|
|
172
|
+
functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
|
|
173
|
+
if (!ts.isVariableStatement(statement))
|
|
174
|
+
continue;
|
|
175
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
176
|
+
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
177
|
+
if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
|
|
178
|
+
functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return functions;
|
|
182
|
+
};
|
|
183
|
+
const returnedOf = (body, guardNames) => {
|
|
184
|
+
const locals = new Map;
|
|
185
|
+
const pending = [];
|
|
186
|
+
const visit = (node) => {
|
|
187
|
+
if (ts.isClassDeclaration(node) && node.name)
|
|
188
|
+
locals.set(node.name.text, node);
|
|
189
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
|
|
190
|
+
locals.set(node.name.text, node.initializer);
|
|
191
|
+
if (ts.isReturnStatement(node) && node.expression)
|
|
192
|
+
pending.push(node.expression);
|
|
193
|
+
if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
|
|
194
|
+
ts.forEachChild(node, visit);
|
|
195
|
+
};
|
|
196
|
+
if (ts.isBlock(body))
|
|
197
|
+
ts.forEachChild(body, visit);
|
|
198
|
+
else
|
|
199
|
+
pending.push(body);
|
|
200
|
+
let guard = null;
|
|
201
|
+
const calls = [];
|
|
202
|
+
const seen = new Set;
|
|
203
|
+
const passed = new Set;
|
|
204
|
+
for (let node = pending.pop();node; node = pending.pop()) {
|
|
205
|
+
if (seen.has(node))
|
|
206
|
+
continue;
|
|
207
|
+
seen.add(node);
|
|
208
|
+
const value = ts.isExpression(node) ? unwrap(node) : node;
|
|
209
|
+
if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
|
|
210
|
+
guard ??= value;
|
|
211
|
+
else if (ts.isConditionalExpression(value))
|
|
212
|
+
pending.push(value.whenTrue, value.whenFalse);
|
|
213
|
+
else if (ts.isBinaryExpression(value))
|
|
214
|
+
pending.push(value.left, value.right);
|
|
215
|
+
else if (ts.isIdentifier(value)) {
|
|
216
|
+
const local = locals.get(value.text);
|
|
217
|
+
if (local)
|
|
218
|
+
pending.push(local);
|
|
219
|
+
} else if (ts.isCallExpression(value)) {
|
|
220
|
+
const callee = pathOf(unwrap(value.expression));
|
|
221
|
+
if (callee && !passed.has(node) && !calls.includes(callee))
|
|
222
|
+
calls.push(callee);
|
|
223
|
+
for (const arg of value.arguments)
|
|
224
|
+
passed.add(arg);
|
|
225
|
+
pending.push(...value.arguments);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return { guard, calls };
|
|
229
|
+
};
|
|
230
|
+
const guardFactoriesOf = (source, guardNames, isImported) => {
|
|
231
|
+
const functions = functionsOf(source);
|
|
232
|
+
const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
|
|
233
|
+
const members = new Set;
|
|
234
|
+
const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
|
|
235
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
236
|
+
isGrowing = false;
|
|
237
|
+
for (const [name, { guard, calls }] of returned) {
|
|
238
|
+
if (members.has(name) || !guard && !calls.some(isFactoryCall))
|
|
239
|
+
continue;
|
|
240
|
+
members.add(name);
|
|
241
|
+
isGrowing = true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const factories = new Map;
|
|
245
|
+
for (const name of members) {
|
|
246
|
+
const found = functions.get(name);
|
|
247
|
+
const made = returned.get(name);
|
|
248
|
+
if (!found || !made)
|
|
249
|
+
continue;
|
|
250
|
+
factories.set(name, {
|
|
251
|
+
name,
|
|
252
|
+
span: spanOf(source, found.name),
|
|
253
|
+
extent: extentOf(source, found.statement),
|
|
254
|
+
scope: made.guard ? scopeOf(made.guard) : null,
|
|
255
|
+
doc: docOf(source, found.statement),
|
|
256
|
+
direct: !!made.guard,
|
|
257
|
+
via: made.calls.filter(isFactoryCall)
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return factories;
|
|
261
|
+
};
|
|
262
|
+
return { implementsGuard, scopeOf, guardFactoriesOf };
|
|
263
|
+
};
|
|
264
|
+
var createGuardFactReader = (ts, ast, base) => {
|
|
265
|
+
const { spanOf, extentOf, unwrap, docOf } = base;
|
|
266
|
+
const { pathOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
|
|
267
|
+
const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf);
|
|
268
|
+
const guardsOf = (source, imports, madeInPlace) => {
|
|
269
|
+
const guardNames = new Set([
|
|
270
|
+
"Guard",
|
|
271
|
+
...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
|
|
272
|
+
]);
|
|
273
|
+
const imported = new Set(imports.map(({ name }) => name));
|
|
274
|
+
const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
|
|
275
|
+
const factories = guardFactoriesOf(source, guardNames, isImported);
|
|
276
|
+
const guards = [];
|
|
277
|
+
const calls = [];
|
|
278
|
+
const makeOf = (made, value, isInPlace = false) => {
|
|
279
|
+
if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
|
|
280
|
+
guards.push({ ...made, scope: scopeOf(value), factory: null });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const callee = ts.isCallExpression(value) ? pathOf(unwrap(value.expression)) : null;
|
|
284
|
+
const factory = callee && factories.get(callee);
|
|
285
|
+
if (factory)
|
|
286
|
+
guards.push({ ...made, scope: factory.scope, factory: factory.name });
|
|
287
|
+
else if (callee && (isImported(callee) || isInPlace))
|
|
288
|
+
calls.push({ ...made, callee, kind: "call", scope: null });
|
|
289
|
+
};
|
|
290
|
+
for (const statement of source.statements) {
|
|
291
|
+
if (ts.isClassDeclaration(statement) && statement.name) {
|
|
292
|
+
const declared = {
|
|
293
|
+
name: statement.name.text,
|
|
294
|
+
span: spanOf(source, statement.name),
|
|
295
|
+
extent: extentOf(source, statement),
|
|
296
|
+
scope: scopeOf(statement),
|
|
297
|
+
doc: docOf(source, statement)
|
|
298
|
+
};
|
|
299
|
+
const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
|
|
300
|
+
const callee = base && pathOf(base.expression);
|
|
301
|
+
if (implementsGuard(statement, guardNames))
|
|
302
|
+
guards.push({ ...declared, factory: null });
|
|
303
|
+
else if (callee)
|
|
304
|
+
calls.push({ ...declared, callee, kind: "extends" });
|
|
305
|
+
}
|
|
306
|
+
if (!ts.isVariableStatement(statement))
|
|
307
|
+
continue;
|
|
308
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
309
|
+
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
310
|
+
if (!initializer || !ts.isIdentifier(declaration.name))
|
|
311
|
+
continue;
|
|
312
|
+
const holder = declaration.name.text;
|
|
313
|
+
makeOf({
|
|
314
|
+
name: holder,
|
|
315
|
+
span: spanOf(source, declaration.name),
|
|
316
|
+
extent: extentOf(source, statement),
|
|
317
|
+
doc: docOf(source, statement)
|
|
318
|
+
}, initializer);
|
|
319
|
+
if (!ts.isObjectLiteralExpression(initializer))
|
|
320
|
+
continue;
|
|
321
|
+
for (const property of initializer.properties) {
|
|
322
|
+
const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
|
|
323
|
+
if (key && ts.isPropertyAssignment(property))
|
|
324
|
+
makeOf({
|
|
325
|
+
name: `${holder}.${key}`,
|
|
326
|
+
span: spanOf(source, property.name),
|
|
327
|
+
extent: extentOf(source, property),
|
|
328
|
+
doc: docOf(source, property)
|
|
329
|
+
}, unwrap(property.initializer));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
for (const { call, name } of madeInPlace)
|
|
334
|
+
makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
|
|
335
|
+
const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
|
|
336
|
+
return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
|
|
337
|
+
};
|
|
338
|
+
return { guardsIn, guardsOf };
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// pkgs/@akanjs/language/escapeRegExp.ts
|
|
342
|
+
var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
|
|
343
|
+
var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
|
|
344
|
+
|
|
74
345
|
// pkgs/@akanjs/language/moduleFacts.ts
|
|
75
346
|
var testFilePattern = /\.(?:test|spec)\.tsx?$/;
|
|
76
347
|
var moduleCallees = new Set([
|
|
@@ -139,21 +410,6 @@ var createFactReader = (ts, ast) => {
|
|
|
139
410
|
const value = unwrap(expression);
|
|
140
411
|
return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
|
|
141
412
|
};
|
|
142
|
-
const guardsIn = (object) => {
|
|
143
|
-
const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
|
|
144
|
-
if (!guards)
|
|
145
|
-
return [];
|
|
146
|
-
if (ts.isArrayLiteralExpression(guards))
|
|
147
|
-
return guards.elements.map(guardNameOf).filter((name) => name !== null);
|
|
148
|
-
if (ts.isObjectLiteralExpression(guards))
|
|
149
|
-
return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
|
|
150
|
-
const value = unwrap(property.initializer);
|
|
151
|
-
const names = ts.isArrayLiteralExpression(value) ? value.elements.map(guardNameOf) : [guardNameOf(value)];
|
|
152
|
-
return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
|
|
153
|
-
});
|
|
154
|
-
const single = guardNameOf(guards);
|
|
155
|
-
return single ? [single] : [];
|
|
156
|
-
};
|
|
157
413
|
const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
|
|
158
414
|
const chainOf = (initializer) => {
|
|
159
415
|
let root = unwrap(initializer);
|
|
@@ -170,7 +426,7 @@ var createFactReader = (ts, ast) => {
|
|
|
170
426
|
const node = keyName(property);
|
|
171
427
|
return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
|
|
172
428
|
});
|
|
173
|
-
const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
429
|
+
const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
174
430
|
if (!ts.isPropertyAssignment(property))
|
|
175
431
|
return [];
|
|
176
432
|
const name = ast.nodeName(property.name);
|
|
@@ -182,7 +438,7 @@ var createFactReader = (ts, ast) => {
|
|
|
182
438
|
name,
|
|
183
439
|
span: spanOf(source, property.name),
|
|
184
440
|
extent: extentOf(source, property),
|
|
185
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
441
|
+
guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
|
|
186
442
|
live: methods.includes("live"),
|
|
187
443
|
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
188
444
|
}
|
|
@@ -232,7 +488,7 @@ var createFactReader = (ts, ast) => {
|
|
|
232
488
|
const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
|
|
233
489
|
return `${ms / size}${unit}`;
|
|
234
490
|
};
|
|
235
|
-
const modesOf = (option) => {
|
|
491
|
+
const modesOf = (option, type) => {
|
|
236
492
|
const object = option && unwrap(option);
|
|
237
493
|
if (!object || !ts.isObjectLiteralExpression(object))
|
|
238
494
|
return [];
|
|
@@ -242,9 +498,9 @@ var createFactReader = (ts, ast) => {
|
|
|
242
498
|
if (name === "enabled")
|
|
243
499
|
return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
|
|
244
500
|
if (name === "once")
|
|
245
|
-
return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
501
|
+
return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
246
502
|
if (name === "lock")
|
|
247
|
-
return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
503
|
+
return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
248
504
|
if (name !== "serverMode" && name !== "operationMode")
|
|
249
505
|
return [];
|
|
250
506
|
const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
|
|
@@ -262,7 +518,8 @@ var createFactReader = (ts, ast) => {
|
|
|
262
518
|
const { root } = chainOf(property.initializer);
|
|
263
519
|
const type = root ? verbOf(root.expression) : null;
|
|
264
520
|
const [first, second] = root?.arguments ?? [];
|
|
265
|
-
const
|
|
521
|
+
const cron = type === "cron" && first ? unwrap(first) : null;
|
|
522
|
+
const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
|
|
266
523
|
const option = type === "initialize" || type === "destroy" ? first : second;
|
|
267
524
|
return [
|
|
268
525
|
{
|
|
@@ -271,7 +528,7 @@ var createFactReader = (ts, ast) => {
|
|
|
271
528
|
extent: extentOf(source, property),
|
|
272
529
|
type,
|
|
273
530
|
schedule,
|
|
274
|
-
modes: modesOf(option)
|
|
531
|
+
modes: modesOf(option, type)
|
|
275
532
|
}
|
|
276
533
|
];
|
|
277
534
|
});
|
|
@@ -288,7 +545,20 @@ var createFactReader = (ts, ast) => {
|
|
|
288
545
|
return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
|
|
289
546
|
});
|
|
290
547
|
};
|
|
291
|
-
const
|
|
548
|
+
const adaptorBindingsIn = (source) => {
|
|
549
|
+
const bindings = [];
|
|
550
|
+
const visit = (node) => {
|
|
551
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
|
|
552
|
+
const [role, adaptor] = node.arguments.map(guardNameOf);
|
|
553
|
+
if (role && adaptor)
|
|
554
|
+
bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
|
|
555
|
+
}
|
|
556
|
+
ts.forEachChild(node, visit);
|
|
557
|
+
};
|
|
558
|
+
visit(source);
|
|
559
|
+
return bindings;
|
|
560
|
+
};
|
|
561
|
+
const endpointKeysOf = (source, builder, made, model) => {
|
|
292
562
|
const verbOf = verbReaderOf(builder?.parameters[0]);
|
|
293
563
|
return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
294
564
|
if (!ts.isPropertyAssignment(property))
|
|
@@ -303,7 +573,7 @@ var createFactReader = (ts, ast) => {
|
|
|
303
573
|
span: spanOf(source, property.name),
|
|
304
574
|
extent: extentOf(source, property),
|
|
305
575
|
verb: root ? verbOf(root.expression) : null,
|
|
306
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
576
|
+
guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
|
|
307
577
|
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
308
578
|
}
|
|
309
579
|
];
|
|
@@ -370,22 +640,6 @@ var createFactReader = (ts, ast) => {
|
|
|
370
640
|
})) : []
|
|
371
641
|
];
|
|
372
642
|
};
|
|
373
|
-
const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
|
|
374
|
-
const guardClassIn = (node, guardNames) => {
|
|
375
|
-
if ((ts.isClassExpression(node) || ts.isClassDeclaration(node)) && implementsGuard(node, guardNames))
|
|
376
|
-
return node;
|
|
377
|
-
return ts.forEachChild(node, (child) => guardClassIn(child, guardNames)) ?? null;
|
|
378
|
-
};
|
|
379
|
-
const callsIn = (node, names) => {
|
|
380
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text))
|
|
381
|
-
return node.expression.text;
|
|
382
|
-
return ts.forEachChild(node, (child) => callsIn(child, names)) ?? null;
|
|
383
|
-
};
|
|
384
|
-
const scopeOf = (node) => {
|
|
385
|
-
const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
|
|
386
|
-
const value = scope && unwrap(scope);
|
|
387
|
-
return value && ts.isStringLiteralLike(value) ? value.text : null;
|
|
388
|
-
};
|
|
389
643
|
const docOf = (source, node) => {
|
|
390
644
|
const { text } = source;
|
|
391
645
|
const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
|
|
@@ -401,108 +655,20 @@ var createFactReader = (ts, ast) => {
|
|
|
401
655
|
`)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
|
|
402
656
|
return lines.length ? lines.join(" ") : null;
|
|
403
657
|
};
|
|
404
|
-
const
|
|
405
|
-
const functions = new Map;
|
|
406
|
-
for (const statement of source.statements) {
|
|
407
|
-
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
|
|
408
|
-
functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
|
|
409
|
-
if (!ts.isVariableStatement(statement))
|
|
410
|
-
continue;
|
|
411
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
412
|
-
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
413
|
-
if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
|
|
414
|
-
functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
return functions;
|
|
418
|
-
};
|
|
419
|
-
const guardFactoriesOf = (source, guardNames) => {
|
|
420
|
-
const functions = functionsOf(source);
|
|
421
|
-
const factories = new Map;
|
|
422
|
-
const factoryOf = (name, scope, via) => {
|
|
423
|
-
const found = functions.get(name);
|
|
424
|
-
if (!found)
|
|
425
|
-
return;
|
|
426
|
-
factories.set(name, {
|
|
427
|
-
name,
|
|
428
|
-
span: spanOf(source, found.name),
|
|
429
|
-
extent: extentOf(source, found.statement),
|
|
430
|
-
scope,
|
|
431
|
-
doc: docOf(source, found.statement),
|
|
432
|
-
via
|
|
433
|
-
});
|
|
434
|
-
};
|
|
435
|
-
for (const [name, { body }] of functions) {
|
|
436
|
-
const guard = guardClassIn(body, guardNames);
|
|
437
|
-
if (guard)
|
|
438
|
-
factoryOf(name, scopeOf(guard), null);
|
|
439
|
-
}
|
|
440
|
-
for (let isGrowing = true;isGrowing; ) {
|
|
441
|
-
isGrowing = false;
|
|
442
|
-
for (const [name, { body }] of functions) {
|
|
443
|
-
const called = factories.has(name) ? null : callsIn(body, new Set(factories.keys()));
|
|
444
|
-
if (!called)
|
|
445
|
-
continue;
|
|
446
|
-
factoryOf(name, factories.get(called)?.scope ?? null, called);
|
|
447
|
-
isGrowing = true;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
return factories;
|
|
451
|
-
};
|
|
452
|
-
const guardsOf = (source, imports) => {
|
|
453
|
-
const guardNames = new Set([
|
|
454
|
-
"Guard",
|
|
455
|
-
...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
|
|
456
|
-
]);
|
|
457
|
-
const imported = new Set(imports.map(({ name }) => name));
|
|
458
|
-
const factories = guardFactoriesOf(source, guardNames);
|
|
459
|
-
const guards = [];
|
|
460
|
-
const calls = [];
|
|
461
|
-
for (const statement of source.statements) {
|
|
462
|
-
if (ts.isClassDeclaration(statement) && statement.name && implementsGuard(statement, guardNames))
|
|
463
|
-
guards.push({
|
|
464
|
-
name: statement.name.text,
|
|
465
|
-
span: spanOf(source, statement.name),
|
|
466
|
-
extent: extentOf(source, statement),
|
|
467
|
-
scope: scopeOf(statement),
|
|
468
|
-
doc: docOf(source, statement),
|
|
469
|
-
factory: null
|
|
470
|
-
});
|
|
471
|
-
if (!ts.isVariableStatement(statement))
|
|
472
|
-
continue;
|
|
473
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
474
|
-
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
475
|
-
if (!initializer || !ts.isIdentifier(declaration.name))
|
|
476
|
-
continue;
|
|
477
|
-
const made = {
|
|
478
|
-
name: declaration.name.text,
|
|
479
|
-
span: spanOf(source, declaration.name),
|
|
480
|
-
extent: extentOf(source, statement),
|
|
481
|
-
doc: docOf(source, statement)
|
|
482
|
-
};
|
|
483
|
-
if (ts.isClassExpression(initializer) && implementsGuard(initializer, guardNames))
|
|
484
|
-
guards.push({ ...made, scope: scopeOf(initializer), factory: null });
|
|
485
|
-
if (!ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression))
|
|
486
|
-
continue;
|
|
487
|
-
const callee = initializer.expression.text;
|
|
488
|
-
const factory = factories.get(callee);
|
|
489
|
-
if (factory)
|
|
490
|
-
guards.push({ ...made, scope: factory.scope, factory: callee });
|
|
491
|
-
else if (imported.has(callee))
|
|
492
|
-
calls.push({ ...made, callee });
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
return { guards, guardFactories: [...factories.values()], guardCalls: calls };
|
|
496
|
-
};
|
|
658
|
+
const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
|
|
497
659
|
const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
|
|
498
660
|
const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
|
|
499
661
|
const read = (source, owner) => {
|
|
500
662
|
const imports = importsIn(source);
|
|
663
|
+
const madeInPlace = [];
|
|
501
664
|
const facts = {
|
|
502
665
|
fileName: source.fileName,
|
|
503
666
|
owner,
|
|
504
667
|
imports,
|
|
505
|
-
|
|
668
|
+
guards: [],
|
|
669
|
+
guardFactories: [],
|
|
670
|
+
guardCalls: [],
|
|
671
|
+
guardLabels: {},
|
|
506
672
|
constants: [],
|
|
507
673
|
enums: [],
|
|
508
674
|
filters: [],
|
|
@@ -512,6 +678,7 @@ var createFactReader = (ts, ast) => {
|
|
|
512
678
|
endpoints: [],
|
|
513
679
|
internals: [],
|
|
514
680
|
adaptors: [],
|
|
681
|
+
adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
|
|
515
682
|
stores: []
|
|
516
683
|
};
|
|
517
684
|
for (const statement of source.statements) {
|
|
@@ -536,16 +703,19 @@ var createFactReader = (ts, ast) => {
|
|
|
536
703
|
...name,
|
|
537
704
|
model: moduleRefOf(call.arguments[0], "db"),
|
|
538
705
|
methods: methodsOf(source, statement),
|
|
539
|
-
plugs: plugsOf(source, builder)
|
|
706
|
+
plugs: plugsOf(source, builder),
|
|
707
|
+
fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
|
|
540
708
|
});
|
|
541
|
-
else if (callee === "slice")
|
|
709
|
+
else if (callee === "slice") {
|
|
710
|
+
const model = moduleRefOf(call.arguments[0], "srv");
|
|
711
|
+
const member = model ?? name.name;
|
|
542
712
|
facts.slices.push({
|
|
543
713
|
...name,
|
|
544
|
-
model
|
|
545
|
-
guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
|
|
546
|
-
keys: sliceKeysOf(source, builder)
|
|
714
|
+
model,
|
|
715
|
+
guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
|
|
716
|
+
keys: sliceKeysOf(source, builder, madeInPlace, member)
|
|
547
717
|
});
|
|
548
|
-
else if (callee === "internal")
|
|
718
|
+
} else if (callee === "internal")
|
|
549
719
|
facts.internals.push({
|
|
550
720
|
...name,
|
|
551
721
|
model: moduleRefOf(call.arguments[0], "srv"),
|
|
@@ -560,13 +730,14 @@ var createFactReader = (ts, ast) => {
|
|
|
560
730
|
plugs: plugsOf(source, builder),
|
|
561
731
|
doc: docOf(source, statement)
|
|
562
732
|
});
|
|
563
|
-
} else if (callee === "endpoint")
|
|
733
|
+
} else if (callee === "endpoint") {
|
|
734
|
+
const model = moduleRefOf(call.arguments[0], "srv");
|
|
564
735
|
facts.endpoints.push({
|
|
565
736
|
...name,
|
|
566
|
-
model
|
|
567
|
-
keys: endpointKeysOf(source, builder)
|
|
737
|
+
model,
|
|
738
|
+
keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
|
|
568
739
|
});
|
|
569
|
-
else if (callee === "store")
|
|
740
|
+
} else if (callee === "store")
|
|
570
741
|
facts.stores.push({
|
|
571
742
|
...name,
|
|
572
743
|
model: moduleRefOf(call.arguments[0], "sig"),
|
|
@@ -574,10 +745,439 @@ var createFactReader = (ts, ast) => {
|
|
|
574
745
|
actions: methodsOf(source, statement)
|
|
575
746
|
});
|
|
576
747
|
}
|
|
577
|
-
return facts;
|
|
578
|
-
};
|
|
579
|
-
return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
|
|
580
|
-
};
|
|
748
|
+
return { ...facts, ...guardsOf(source, imports, madeInPlace) };
|
|
749
|
+
};
|
|
750
|
+
return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
// pkgs/@akanjs/language/guardResolver.ts
|
|
754
|
+
var frameworkOwner = "akanjs";
|
|
755
|
+
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
756
|
+
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
757
|
+
var maxMapDepth = 4;
|
|
758
|
+
var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
|
|
759
|
+
var declaringPatternOf = (name, isAnyBinding) => {
|
|
760
|
+
const escaped = escapeRegExp(name);
|
|
761
|
+
const braced = "\\{(?:[^}]*?[\\s,])?";
|
|
762
|
+
const leads = [
|
|
763
|
+
"function\\s*\\*?\\s*",
|
|
764
|
+
"(?:const|let|var)\\s+",
|
|
765
|
+
`export\\s*${braced}`,
|
|
766
|
+
...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
|
|
767
|
+
];
|
|
768
|
+
return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
class GuardResolver {
|
|
772
|
+
#source;
|
|
773
|
+
#routes;
|
|
774
|
+
#guards = new Map;
|
|
775
|
+
#madeInPlace = new Map;
|
|
776
|
+
#mapEntries = new Map;
|
|
777
|
+
#reexports = new Map;
|
|
778
|
+
#declarations = new Map;
|
|
779
|
+
#starExports = new Map;
|
|
780
|
+
#ownerFiles = null;
|
|
781
|
+
#modules = null;
|
|
782
|
+
constructor(source, routes) {
|
|
783
|
+
this.#source = source;
|
|
784
|
+
this.#routes = routes;
|
|
785
|
+
}
|
|
786
|
+
find(local, site) {
|
|
787
|
+
return this.#findGuard(local, site, 0);
|
|
788
|
+
}
|
|
789
|
+
madeInPlace(fileName, name) {
|
|
790
|
+
return this.#madeInPlace.get(`${fileName}
|
|
791
|
+
${name}`);
|
|
792
|
+
}
|
|
793
|
+
declare(facts) {
|
|
794
|
+
const factories = new Map;
|
|
795
|
+
const declaredFactories = [];
|
|
796
|
+
for (const file of facts)
|
|
797
|
+
for (const fact of file.guardFactories) {
|
|
798
|
+
const factory = {
|
|
799
|
+
fact,
|
|
800
|
+
owner: file.owner,
|
|
801
|
+
site: file,
|
|
802
|
+
made: [],
|
|
803
|
+
isReal: fact.direct,
|
|
804
|
+
scope: fact.scope,
|
|
805
|
+
targets: []
|
|
806
|
+
};
|
|
807
|
+
factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
|
|
808
|
+
declaredFactories.push(factory);
|
|
809
|
+
}
|
|
810
|
+
for (const factory of declaredFactories)
|
|
811
|
+
factory.targets = factory.fact.via.map((callee) => this.#factoryAt(factories, callee, factory.site)).filter((target) => target !== undefined);
|
|
812
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
813
|
+
isGrowing = false;
|
|
814
|
+
for (const factory of declaredFactories) {
|
|
815
|
+
const target = factory.targets.find((other) => other.isReal);
|
|
816
|
+
if (factory.isReal || !target)
|
|
817
|
+
continue;
|
|
818
|
+
factory.isReal = true;
|
|
819
|
+
factory.scope ??= target.scope;
|
|
820
|
+
isGrowing = true;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
const partsOf = (factory, seen = new Set) => {
|
|
824
|
+
if (seen.has(factory))
|
|
825
|
+
return [];
|
|
826
|
+
seen.add(factory);
|
|
827
|
+
return [
|
|
828
|
+
{ fileName: factory.site.fileName, extent: factory.fact.extent },
|
|
829
|
+
...factory.targets.filter((target) => target.isReal).flatMap((target) => partsOf(target, seen))
|
|
830
|
+
];
|
|
831
|
+
};
|
|
832
|
+
const declare = (file, made, scope, parts) => {
|
|
833
|
+
const { name, span, extent, doc } = made;
|
|
834
|
+
const label = file.guardLabels[name];
|
|
835
|
+
const own = { fileName: file.fileName, extent };
|
|
836
|
+
const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
|
|
837
|
+
if (name.includes("@"))
|
|
838
|
+
this.#madeInPlace.set(`${file.fileName}
|
|
839
|
+
${name}`, guard);
|
|
840
|
+
else
|
|
841
|
+
this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
|
|
842
|
+
return guard;
|
|
843
|
+
};
|
|
844
|
+
const extending = [];
|
|
845
|
+
for (const file of facts) {
|
|
846
|
+
for (const made of file.guards) {
|
|
847
|
+
const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
|
|
848
|
+
if (!made.factory)
|
|
849
|
+
declare(file, made, made.scope, []);
|
|
850
|
+
else if (factory?.isReal)
|
|
851
|
+
factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
|
|
852
|
+
}
|
|
853
|
+
for (const made of file.guardCalls) {
|
|
854
|
+
if (made.kind === "extends") {
|
|
855
|
+
extending.push([file, made]);
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
const factory = this.#factoryAt(factories, made.callee, file);
|
|
859
|
+
if (factory?.isReal)
|
|
860
|
+
factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
864
|
+
isGrowing = false;
|
|
865
|
+
for (const [idx, entry] of extending.entries()) {
|
|
866
|
+
const [file, made] = entry;
|
|
867
|
+
const found = this.#findGuard(made.callee, file, 0, true);
|
|
868
|
+
if (!("guard" in found))
|
|
869
|
+
continue;
|
|
870
|
+
declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
|
|
871
|
+
extending.splice(idx, 1);
|
|
872
|
+
isGrowing = true;
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
for (const { fact, made, isReal } of declaredFactories)
|
|
877
|
+
if (isReal && made.every((guard) => !guard.doc))
|
|
878
|
+
for (const guard of made)
|
|
879
|
+
guard.doc = fact.doc;
|
|
880
|
+
for (const list of this.#guards.values())
|
|
881
|
+
for (const guard of list)
|
|
882
|
+
if (list.some((other) => other !== guard && other.owner === guard.owner))
|
|
883
|
+
guard.file = this.inOwner(guard.span.fileName, guard.owner);
|
|
884
|
+
for (const guard of this.#madeInPlace.values())
|
|
885
|
+
guard.file = this.inOwner(guard.span.fileName, guard.owner);
|
|
886
|
+
return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
|
|
887
|
+
}
|
|
888
|
+
#factoryAt(factories, callee, site, depth = 0) {
|
|
889
|
+
const [head = callee, member] = callee.split(".");
|
|
890
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
891
|
+
if (!imported)
|
|
892
|
+
return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
|
|
893
|
+
if (member && imported.imported !== "*")
|
|
894
|
+
return;
|
|
895
|
+
const from = this.ownerOfSpecifier(site.fileName, imported.from);
|
|
896
|
+
if (from === undefined || from === frameworkOwner)
|
|
897
|
+
return;
|
|
898
|
+
const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
|
|
899
|
+
const candidates = factories.get(name) ?? [];
|
|
900
|
+
const near = this.pathOfSpecifier(site.fileName, imported.from);
|
|
901
|
+
const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
|
|
902
|
+
if (own)
|
|
903
|
+
return own;
|
|
904
|
+
const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
|
|
905
|
+
if (reexport)
|
|
906
|
+
return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
|
|
907
|
+
return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
|
|
908
|
+
}
|
|
909
|
+
#findGuard(local, site, depth, isBase = false) {
|
|
910
|
+
const segments = local.split(".");
|
|
911
|
+
const head = segments[0] ?? local;
|
|
912
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
913
|
+
const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
|
|
914
|
+
if (imported && from === undefined)
|
|
915
|
+
return { external: true };
|
|
916
|
+
const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
|
|
917
|
+
const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
|
|
918
|
+
const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
|
|
919
|
+
const holder = imported ? from ?? null : site.owner;
|
|
920
|
+
const [objectName, member] = path;
|
|
921
|
+
if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
|
|
922
|
+
const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
|
|
923
|
+
if (made)
|
|
924
|
+
return { guard: made };
|
|
925
|
+
for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
|
|
926
|
+
const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
|
|
927
|
+
if ("guard" in found)
|
|
928
|
+
return found;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
const name = path[path.length - 1] ?? head;
|
|
932
|
+
if (from === frameworkOwner)
|
|
933
|
+
return { name, from };
|
|
934
|
+
const declared = this.#guards.get(name) ?? [];
|
|
935
|
+
const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
|
|
936
|
+
if (own)
|
|
937
|
+
return { guard: own };
|
|
938
|
+
if (!imported) {
|
|
939
|
+
const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
|
|
940
|
+
return visible ? { guard: visible } : { name, from };
|
|
941
|
+
}
|
|
942
|
+
const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
|
|
943
|
+
if (reexport)
|
|
944
|
+
return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
|
|
945
|
+
const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
|
|
946
|
+
return reached ? { guard: reached } : { name, from };
|
|
947
|
+
}
|
|
948
|
+
reexportOf(owner, name, near) {
|
|
949
|
+
const key = `${owner}
|
|
950
|
+
${name}
|
|
951
|
+
${near ?? ""}`;
|
|
952
|
+
const cached = this.#reexports.get(key);
|
|
953
|
+
if (cached !== undefined)
|
|
954
|
+
return cached;
|
|
955
|
+
let found = null;
|
|
956
|
+
const exporting = new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
|
|
957
|
+
for (const fileName of this.#filesOf(owner, name, near)) {
|
|
958
|
+
const text = this.#source.readText(fileName);
|
|
959
|
+
if (text === undefined || !exporting.test(text))
|
|
960
|
+
continue;
|
|
961
|
+
const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
|
|
962
|
+
if (!reexport)
|
|
963
|
+
continue;
|
|
964
|
+
const site = { fileName, owner, imports: [] };
|
|
965
|
+
found = reexport.from ? {
|
|
966
|
+
local: reexport.name,
|
|
967
|
+
site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
|
|
968
|
+
} : { local: reexport.name, site };
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
971
|
+
this.#reexports.set(key, found);
|
|
972
|
+
return found;
|
|
973
|
+
}
|
|
974
|
+
#reexportIn(source, name) {
|
|
975
|
+
const { ts, reader } = this.#source;
|
|
976
|
+
for (const statement of source.statements) {
|
|
977
|
+
if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
|
|
978
|
+
continue;
|
|
979
|
+
const { exportClause, moduleSpecifier } = statement;
|
|
980
|
+
if (!exportClause || !ts.isNamedExports(exportClause))
|
|
981
|
+
continue;
|
|
982
|
+
const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
|
|
983
|
+
if (!element)
|
|
984
|
+
continue;
|
|
985
|
+
const original = (element.propertyName ?? element.name).text;
|
|
986
|
+
if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
|
|
987
|
+
return { name: original, from: moduleSpecifier.text };
|
|
988
|
+
const imported = reader.importsIn(source).find((item) => item.name === original);
|
|
989
|
+
if (!imported)
|
|
990
|
+
return { name: original, from: null };
|
|
991
|
+
return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
|
|
992
|
+
}
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
#filesOf(owner, name, near) {
|
|
996
|
+
const isNear = (fileName) => {
|
|
997
|
+
const relative = this.#source.relative(fileName);
|
|
998
|
+
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
999
|
+
};
|
|
1000
|
+
const candidates = this.filesIn(owner).filter(([, words]) => words.includes(`
|
|
1001
|
+
${name}
|
|
1002
|
+
`)).map(([fileName]) => fileName);
|
|
1003
|
+
return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
|
|
1004
|
+
}
|
|
1005
|
+
#mapEntriesOf(objectName, member, fileName, owner) {
|
|
1006
|
+
const key = `${objectName}
|
|
1007
|
+
${member}
|
|
1008
|
+
${fileName ?? ""}
|
|
1009
|
+
${owner ?? ""}`;
|
|
1010
|
+
const cached = this.#mapEntries.get(key);
|
|
1011
|
+
if (cached)
|
|
1012
|
+
return cached;
|
|
1013
|
+
const { ts, reader } = this.#source;
|
|
1014
|
+
const entries = [];
|
|
1015
|
+
const fileNames = [
|
|
1016
|
+
...fileName ? [fileName] : [],
|
|
1017
|
+
...(owner === null ? [] : this.filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
|
|
1018
|
+
${objectName}
|
|
1019
|
+
`) && words.includes(`
|
|
1020
|
+
${member}
|
|
1021
|
+
`)).map(([other]) => other)
|
|
1022
|
+
];
|
|
1023
|
+
for (const holder of fileNames) {
|
|
1024
|
+
const text = this.#source.readText(holder);
|
|
1025
|
+
if (text === undefined)
|
|
1026
|
+
continue;
|
|
1027
|
+
const source = this.#source.parse(holder, text);
|
|
1028
|
+
const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
|
|
1029
|
+
for (const statement of source.statements) {
|
|
1030
|
+
if (!ts.isVariableStatement(statement))
|
|
1031
|
+
continue;
|
|
1032
|
+
const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
|
|
1033
|
+
const object = map && reader.unwrap(map);
|
|
1034
|
+
if (!object || !ts.isObjectLiteralExpression(object))
|
|
1035
|
+
continue;
|
|
1036
|
+
for (const property of object.properties) {
|
|
1037
|
+
if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
|
|
1038
|
+
entries.push({ name: member, site });
|
|
1039
|
+
if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
|
|
1040
|
+
continue;
|
|
1041
|
+
const value = reader.unwrap(property.initializer);
|
|
1042
|
+
if (ts.isIdentifier(value))
|
|
1043
|
+
entries.push({ name: value.text, site });
|
|
1044
|
+
else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
|
|
1045
|
+
entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
this.#mapEntries.set(key, entries);
|
|
1050
|
+
return entries;
|
|
1051
|
+
}
|
|
1052
|
+
declarationOf(owner, name, near, depth = 0) {
|
|
1053
|
+
const key = `${owner}
|
|
1054
|
+
${name}
|
|
1055
|
+
${near ?? ""}`;
|
|
1056
|
+
const cached = this.#declarations.get(key);
|
|
1057
|
+
if (cached !== undefined)
|
|
1058
|
+
return cached;
|
|
1059
|
+
this.#declarations.set(key, null);
|
|
1060
|
+
const declaring = declaringPatternOf(name, false);
|
|
1061
|
+
let found = null;
|
|
1062
|
+
for (const fileName of this.#filesOf(owner, name, near)) {
|
|
1063
|
+
const text = this.#source.readText(fileName);
|
|
1064
|
+
if (text === undefined || !declaring.test(text))
|
|
1065
|
+
continue;
|
|
1066
|
+
const source = this.#source.parse(fileName, text);
|
|
1067
|
+
const declared = this.#routes.declarationIn(source, name);
|
|
1068
|
+
if (declared) {
|
|
1069
|
+
found = { source, ...declared, name };
|
|
1070
|
+
break;
|
|
1071
|
+
}
|
|
1072
|
+
const reexport = this.#reexportIn(source, name);
|
|
1073
|
+
if (!reexport)
|
|
1074
|
+
continue;
|
|
1075
|
+
if (!reexport.from) {
|
|
1076
|
+
const local = this.#routes.declarationIn(source, reexport.name);
|
|
1077
|
+
found = local ? { source, ...local, name: reexport.name } : null;
|
|
1078
|
+
break;
|
|
1079
|
+
}
|
|
1080
|
+
const next = this.ownerOfSpecifier(fileName, reexport.from);
|
|
1081
|
+
if (next && next !== frameworkOwner && depth < maxMapDepth)
|
|
1082
|
+
found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
|
|
1083
|
+
break;
|
|
1084
|
+
}
|
|
1085
|
+
this.#declarations.set(key, found);
|
|
1086
|
+
return found;
|
|
1087
|
+
}
|
|
1088
|
+
#throughStar(declared, owner, name, near) {
|
|
1089
|
+
if (!declared.length)
|
|
1090
|
+
return;
|
|
1091
|
+
const { owners, isOpaque } = this.#starExportsOf(owner, near);
|
|
1092
|
+
const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
|
|
1093
|
+
return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
|
|
1094
|
+
}
|
|
1095
|
+
#starExportsOf(owner, near) {
|
|
1096
|
+
const cached = this.#starExports.get(near);
|
|
1097
|
+
if (cached)
|
|
1098
|
+
return cached;
|
|
1099
|
+
const found = { owners: [], isOpaque: false };
|
|
1100
|
+
this.#starExports.set(near, found);
|
|
1101
|
+
const paths = [near];
|
|
1102
|
+
for (let idx = 0;idx < paths.length && !found.isOpaque; idx++)
|
|
1103
|
+
for (const fileName of this.#modulesAt(paths[idx] ?? "")) {
|
|
1104
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
|
|
1105
|
+
const target = this.ownerOfSpecifier(fileName, specifier);
|
|
1106
|
+
const path = this.pathOfSpecifier(fileName, specifier);
|
|
1107
|
+
if (target === undefined || target === frameworkOwner || path === null)
|
|
1108
|
+
found.isOpaque = true;
|
|
1109
|
+
else if (!paths.includes(path))
|
|
1110
|
+
paths.push(path);
|
|
1111
|
+
if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
|
|
1112
|
+
found.owners.push(target);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return found;
|
|
1116
|
+
}
|
|
1117
|
+
#modulesAt(path) {
|
|
1118
|
+
if (!this.#modules) {
|
|
1119
|
+
this.#modules = new Map;
|
|
1120
|
+
for (const [fileName] of this.#source.files) {
|
|
1121
|
+
const module = this.#source.relative(fileName).replace(/\.tsx?$/, "");
|
|
1122
|
+
for (const key of module.endsWith("/index") ? [module, module.slice(0, -6)] : [module])
|
|
1123
|
+
this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return this.#modules.get(path) ?? [];
|
|
1127
|
+
}
|
|
1128
|
+
declares(owner, name) {
|
|
1129
|
+
const declaring = declaringPatternOf(name, true);
|
|
1130
|
+
return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
|
|
1131
|
+
}
|
|
1132
|
+
filesIn(owner) {
|
|
1133
|
+
if (!this.#ownerFiles) {
|
|
1134
|
+
this.#ownerFiles = new Map;
|
|
1135
|
+
for (const entry of this.#source.files) {
|
|
1136
|
+
const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
|
|
1137
|
+
const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
|
|
1138
|
+
if (files)
|
|
1139
|
+
files.push(entry);
|
|
1140
|
+
else if (fileOwner !== null)
|
|
1141
|
+
this.#ownerFiles.set(fileOwner, [entry]);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return this.#ownerFiles.get(owner) ?? [];
|
|
1145
|
+
}
|
|
1146
|
+
#nearest(items, fileOf, near) {
|
|
1147
|
+
const isNear = (fileName) => {
|
|
1148
|
+
const relative = this.#source.relative(fileName);
|
|
1149
|
+
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
1150
|
+
};
|
|
1151
|
+
return items.find((item) => isNear(fileOf(item))) ?? items[0];
|
|
1152
|
+
}
|
|
1153
|
+
inOwner(fileName, owner) {
|
|
1154
|
+
const relative = this.#source.relative(fileName);
|
|
1155
|
+
return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
|
|
1156
|
+
}
|
|
1157
|
+
ownerOfSpecifier(fileName, specifier) {
|
|
1158
|
+
if (frameworkPattern.test(specifier))
|
|
1159
|
+
return frameworkOwner;
|
|
1160
|
+
const path = this.pathOfSpecifier(fileName, specifier);
|
|
1161
|
+
return path === null ? undefined : ownerOf(path) ?? undefined;
|
|
1162
|
+
}
|
|
1163
|
+
pathOfSpecifier(fileName, specifier) {
|
|
1164
|
+
const alias = specifier.match(aliasPattern);
|
|
1165
|
+
if (alias)
|
|
1166
|
+
return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
|
|
1167
|
+
if (!specifier.startsWith("."))
|
|
1168
|
+
return null;
|
|
1169
|
+
const segments = this.#source.relative(fileName).split("/").slice(0, -1);
|
|
1170
|
+
for (const segment of specifier.split("/"))
|
|
1171
|
+
if (segment === "..")
|
|
1172
|
+
segments.pop();
|
|
1173
|
+
else if (segment !== ".")
|
|
1174
|
+
segments.push(segment);
|
|
1175
|
+
return segments.join("/");
|
|
1176
|
+
}
|
|
1177
|
+
visible(target, from) {
|
|
1178
|
+
return !from || !target || target === from || target.startsWith("libs/");
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
581
1181
|
|
|
582
1182
|
// pkgs/akanjs/common/trimSlashes.ts
|
|
583
1183
|
var trimTrailingSlashes = (value) => {
|
|
@@ -726,24 +1326,23 @@ rootLayout
|
|
|
726
1326
|
}
|
|
727
1327
|
declarationIn(source, name) {
|
|
728
1328
|
const ts = this.#ts;
|
|
1329
|
+
const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
|
|
1330
|
+
const [first] = overloads;
|
|
1331
|
+
const last = overloads.at(-1);
|
|
1332
|
+
if (first?.name && last)
|
|
1333
|
+
return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
|
|
729
1334
|
for (const statement of source.statements) {
|
|
730
|
-
if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
|
|
731
|
-
return { statement, declared: statement.name, initializer: null };
|
|
732
1335
|
if (!ts.isVariableStatement(statement))
|
|
733
1336
|
continue;
|
|
734
1337
|
const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
|
|
735
1338
|
if (declaration)
|
|
736
|
-
return { statement, declared: declaration.name,
|
|
1339
|
+
return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
|
|
737
1340
|
}
|
|
738
1341
|
return null;
|
|
739
1342
|
}
|
|
740
1343
|
#guardOf(source, exported) {
|
|
741
1344
|
const ts = this.#ts;
|
|
742
1345
|
let current = ts.isExportAssignment(exported) ? this.#reader.unwrap(exported.expression) : null;
|
|
743
|
-
if (current && ts.isIdentifier(current)) {
|
|
744
|
-
const initializer = this.declarationIn(source, current.text)?.initializer;
|
|
745
|
-
current = initializer ? this.#reader.unwrap(initializer) : null;
|
|
746
|
-
}
|
|
747
1346
|
while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
748
1347
|
const { name, expression } = current.expression;
|
|
749
1348
|
const [stage] = current.arguments;
|
|
@@ -755,9 +1354,20 @@ rootLayout
|
|
|
755
1354
|
}
|
|
756
1355
|
#callsIn(source, stage) {
|
|
757
1356
|
const ts = this.#ts;
|
|
758
|
-
const
|
|
1357
|
+
const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
|
|
1358
|
+
const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
|
|
1359
|
+
const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
|
|
759
1360
|
const calls = new Map;
|
|
760
|
-
const callOf = (
|
|
1361
|
+
const callOf = (callee, shadowed) => {
|
|
1362
|
+
if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
|
|
1363
|
+
const name = `${callee.expression.text}.${callee.name.text}`;
|
|
1364
|
+
if (!calls.has(name))
|
|
1365
|
+
calls.set(name, { name, local: null });
|
|
1366
|
+
return true;
|
|
1367
|
+
}
|
|
1368
|
+
if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
|
|
1369
|
+
return false;
|
|
1370
|
+
const name = callee.text;
|
|
761
1371
|
if (calls.has(name))
|
|
762
1372
|
return true;
|
|
763
1373
|
const declared = this.declarationIn(source, name);
|
|
@@ -767,23 +1377,71 @@ rootLayout
|
|
|
767
1377
|
name,
|
|
768
1378
|
local: declared && {
|
|
769
1379
|
span: this.#reader.spanOf(source, declared.declared),
|
|
770
|
-
extent:
|
|
1380
|
+
extent: declared.extent,
|
|
771
1381
|
doc: this.#reader.docOf(source, declared.statement)
|
|
772
1382
|
}
|
|
773
1383
|
});
|
|
774
1384
|
return true;
|
|
775
1385
|
};
|
|
776
|
-
const visit = (node) => {
|
|
777
|
-
|
|
1386
|
+
const visit = (node, outer) => {
|
|
1387
|
+
const shadowed = this.#scopeOf(node, outer);
|
|
1388
|
+
if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
|
|
778
1389
|
return;
|
|
779
|
-
ts.forEachChild(node, visit);
|
|
1390
|
+
ts.forEachChild(node, (child) => visit(child, shadowed));
|
|
780
1391
|
};
|
|
781
1392
|
if (ts.isIdentifier(stage))
|
|
782
|
-
callOf(stage
|
|
1393
|
+
callOf(stage, new Set);
|
|
783
1394
|
else
|
|
784
|
-
visit(stage);
|
|
1395
|
+
visit(stage, new Set);
|
|
785
1396
|
return [...calls.values()];
|
|
786
1397
|
}
|
|
1398
|
+
#scopeOf(node, outer) {
|
|
1399
|
+
const ts = this.#ts;
|
|
1400
|
+
const names = [];
|
|
1401
|
+
const bind = (name) => {
|
|
1402
|
+
if (ts.isIdentifier(name))
|
|
1403
|
+
names.push(name.text);
|
|
1404
|
+
else
|
|
1405
|
+
for (const element of name.elements)
|
|
1406
|
+
if (!ts.isOmittedExpression(element))
|
|
1407
|
+
bind(element.name);
|
|
1408
|
+
};
|
|
1409
|
+
const declare = (statement) => {
|
|
1410
|
+
if (ts.isVariableStatement(statement))
|
|
1411
|
+
for (const declaration of statement.declarationList.declarations)
|
|
1412
|
+
bind(declaration.name);
|
|
1413
|
+
else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
|
|
1414
|
+
names.push(statement.name.text);
|
|
1415
|
+
};
|
|
1416
|
+
const hoist = (child) => {
|
|
1417
|
+
if (ts.isFunctionLike(child) || ts.isClassLike(child))
|
|
1418
|
+
return;
|
|
1419
|
+
if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
|
|
1420
|
+
for (const declaration of child.declarations)
|
|
1421
|
+
bind(declaration.name);
|
|
1422
|
+
ts.forEachChild(child, hoist);
|
|
1423
|
+
};
|
|
1424
|
+
if (ts.isFunctionLike(node)) {
|
|
1425
|
+
for (const parameter of node.parameters)
|
|
1426
|
+
bind(parameter.name);
|
|
1427
|
+
if (ts.isFunctionExpression(node) && node.name)
|
|
1428
|
+
names.push(node.name.text);
|
|
1429
|
+
if ("body" in node && node.body)
|
|
1430
|
+
ts.forEachChild(node.body, hoist);
|
|
1431
|
+
} else if (ts.isClassExpression(node) && node.name)
|
|
1432
|
+
names.push(node.name.text);
|
|
1433
|
+
else if (ts.isBlock(node))
|
|
1434
|
+
node.statements.forEach(declare);
|
|
1435
|
+
else if (ts.isCaseBlock(node))
|
|
1436
|
+
for (const clause of node.clauses)
|
|
1437
|
+
clause.statements.forEach(declare);
|
|
1438
|
+
else if (ts.isCatchClause(node) && node.variableDeclaration)
|
|
1439
|
+
bind(node.variableDeclaration.name);
|
|
1440
|
+
else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
|
|
1441
|
+
for (const declaration of node.initializer.declarations)
|
|
1442
|
+
bind(declaration.name);
|
|
1443
|
+
return names.length ? new Set([...outer, ...names]) : outer;
|
|
1444
|
+
}
|
|
787
1445
|
}
|
|
788
1446
|
|
|
789
1447
|
// pkgs/@akanjs/language/akanGraph.ts
|
|
@@ -799,10 +1457,6 @@ var builtinGuards = {
|
|
|
799
1457
|
Public: { scope: "account", doc: "Passes every caller, signed in or not." },
|
|
800
1458
|
None: { scope: "account", doc: "Refuses every caller." }
|
|
801
1459
|
};
|
|
802
|
-
var frameworkOwner = "akanjs";
|
|
803
|
-
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
804
|
-
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
805
|
-
var maxMapDepth = 4;
|
|
806
1460
|
|
|
807
1461
|
class GraphBuilder {
|
|
808
1462
|
#source;
|
|
@@ -810,16 +1464,16 @@ class GraphBuilder {
|
|
|
810
1464
|
#edges = new Map;
|
|
811
1465
|
#contexts = new Map;
|
|
812
1466
|
#members = new Map;
|
|
813
|
-
#guards = new Map;
|
|
814
|
-
#mapEntries = new Map;
|
|
815
1467
|
#routeFiles = new Map;
|
|
816
|
-
#declarations = new Map;
|
|
817
1468
|
#routes;
|
|
1469
|
+
#resolver;
|
|
818
1470
|
#adaptors = new Map;
|
|
819
1471
|
#plugs = new Map;
|
|
1472
|
+
#serviceFields = new Set;
|
|
820
1473
|
constructor(source) {
|
|
821
1474
|
this.#source = source;
|
|
822
1475
|
this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
|
|
1476
|
+
this.#resolver = new GuardResolver(source, this.#routes);
|
|
823
1477
|
}
|
|
824
1478
|
build() {
|
|
825
1479
|
const { facts } = this.#source;
|
|
@@ -831,17 +1485,28 @@ class GraphBuilder {
|
|
|
831
1485
|
...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
|
|
832
1486
|
];
|
|
833
1487
|
const typed = [];
|
|
1488
|
+
const adaptorCounts = new Map;
|
|
834
1489
|
for (const file of facts)
|
|
835
1490
|
for (const fact of file.adaptors) {
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
this.#adaptorNodeOf(declared);
|
|
1491
|
+
const key = this.#key("adaptor", file.owner, fact.name);
|
|
1492
|
+
adaptorCounts.set(key, (adaptorCounts.get(key) ?? 0) + 1);
|
|
839
1493
|
}
|
|
1494
|
+
const declaredAdaptors = facts.flatMap((file) => file.adaptors.map((fact) => {
|
|
1495
|
+
const key = this.#key("adaptor", file.owner, fact.name);
|
|
1496
|
+
const id = (adaptorCounts.get(key) ?? 0) > 1 ? this.#key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
|
|
1497
|
+
const declared = { fact, owner: file.owner, fileName: file.fileName, id };
|
|
1498
|
+
this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
|
|
1499
|
+
this.#adaptorNodeOf(declared);
|
|
1500
|
+
return { declared, file };
|
|
1501
|
+
}));
|
|
1502
|
+
for (const { declared, file } of declaredAdaptors)
|
|
1503
|
+
for (const plug of declared.fact.plugs)
|
|
1504
|
+
this.#addEdge(declared.id, this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
|
|
840
1505
|
for (const file of facts)
|
|
841
|
-
for (const
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
1506
|
+
for (const binding of file.adaptorBindings)
|
|
1507
|
+
this.#addEdge(this.#adaptorIdOf(binding.role, file), this.#adaptorIdOf(binding.adaptor, file), "binds", file.owner, this.#location(binding.span));
|
|
1508
|
+
for (const guard of this.#resolver.declare(facts))
|
|
1509
|
+
this.#guardNodeOf(guard);
|
|
845
1510
|
for (const file of facts) {
|
|
846
1511
|
const { owner } = file;
|
|
847
1512
|
const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
|
|
@@ -872,10 +1537,18 @@ class GraphBuilder {
|
|
|
872
1537
|
}
|
|
873
1538
|
for (const method of service.methods)
|
|
874
1539
|
this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
|
|
875
|
-
for (const plug of service.plugs)
|
|
876
|
-
|
|
1540
|
+
for (const plug of service.plugs) {
|
|
1541
|
+
const key = `${refName}
|
|
1542
|
+
${plug.name}`;
|
|
1543
|
+
this.#plugs.set(key, [
|
|
1544
|
+
...this.#plugs.get(key) ?? [],
|
|
1545
|
+
{ owner, target: this.#adaptorIdOf(plug.target, file) }
|
|
1546
|
+
]);
|
|
1547
|
+
}
|
|
1548
|
+
for (const field of service.fields)
|
|
1549
|
+
this.#serviceFields.add(`${owner ?? ""}
|
|
877
1550
|
${refName}
|
|
878
|
-
${
|
|
1551
|
+
${field}`);
|
|
879
1552
|
}
|
|
880
1553
|
for (const filter of file.filters) {
|
|
881
1554
|
if (!filter.name.endsWith("Filter"))
|
|
@@ -885,31 +1558,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
|
885
1558
|
for (const query of filter.queries)
|
|
886
1559
|
this.#digest(this.#addNode(this.#key("query", owner, refName, query.name), "query", owner, refName, query.name, query.span), this.#source.readText(file.fileName), query.extent);
|
|
887
1560
|
}
|
|
888
|
-
|
|
889
|
-
const refName = slice.model;
|
|
890
|
-
if (!refName)
|
|
891
|
-
continue;
|
|
892
|
-
const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards);
|
|
893
|
-
for (const guard of slice.guards)
|
|
894
|
-
this.#guard(fallback, guard, file);
|
|
895
|
-
for (const key of slice.keys) {
|
|
896
|
-
const detail = [...key.guards, ...key.live ? ["live"] : []];
|
|
897
|
-
const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
898
|
-
for (const guard of key.guards)
|
|
899
|
-
this.#guard(node, guard, file);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
for (const endpoint of file.endpoints) {
|
|
903
|
-
const refName = endpoint.model;
|
|
904
|
-
if (!refName)
|
|
905
|
-
continue;
|
|
906
|
-
for (const key of endpoint.keys) {
|
|
907
|
-
const detail = [...key.verb ? [key.verb] : [], ...key.guards];
|
|
908
|
-
const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
909
|
-
for (const guard of key.guards)
|
|
910
|
-
this.#guard(node, guard, file);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
1561
|
+
this.#addGuarded(file);
|
|
913
1562
|
for (const internal of file.internals)
|
|
914
1563
|
for (const key of internal.keys)
|
|
915
1564
|
if (internal.model && key.type !== "resolveField")
|
|
@@ -925,7 +1574,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
|
925
1574
|
types.set(node.label, [...types.get(node.label) ?? [], node]);
|
|
926
1575
|
for (const node of typed)
|
|
927
1576
|
for (const field of node.fields ?? []) {
|
|
928
|
-
const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
|
|
1577
|
+
const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
|
|
929
1578
|
field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
|
|
930
1579
|
if (field.ref)
|
|
931
1580
|
this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
|
|
@@ -1007,61 +1656,52 @@ ${label ?? ""}`;
|
|
|
1007
1656
|
else
|
|
1008
1657
|
this.#edges.set(key, { from, to, kind, label, count: 1, at });
|
|
1009
1658
|
}
|
|
1010
|
-
#
|
|
1011
|
-
const
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
for (const
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
};
|
|
1028
|
-
const madeBy = (factory, fileName, extent) => {
|
|
1029
|
-
const parts = [{ fileName, extent }];
|
|
1030
|
-
const seen = new Set;
|
|
1031
|
-
for (let current = factory;current && !seen.has(current); current = sameFile(current, current.fact.via)) {
|
|
1032
|
-
seen.add(current);
|
|
1033
|
-
parts.push({ fileName: current.fileName, extent: current.fact.extent });
|
|
1034
|
-
}
|
|
1035
|
-
return parts;
|
|
1036
|
-
};
|
|
1037
|
-
for (const file of facts) {
|
|
1038
|
-
const { owner, fileName } = file;
|
|
1039
|
-
for (const { name, span, extent, scope, doc, factory: callee } of file.guards) {
|
|
1040
|
-
const factory = callee ? factories.get(callee)?.find((other) => other.fileName === fileName) : undefined;
|
|
1041
|
-
declare({ name, owner, span, scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
|
|
1659
|
+
#addGuarded(file) {
|
|
1660
|
+
const { owner } = file;
|
|
1661
|
+
for (const slice of file.slices) {
|
|
1662
|
+
const refName = slice.model;
|
|
1663
|
+
if (!refName)
|
|
1664
|
+
continue;
|
|
1665
|
+
const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
|
|
1666
|
+
for (const guard of slice.guards)
|
|
1667
|
+
this.#guard(fallback, guard, file);
|
|
1668
|
+
for (const key of slice.keys) {
|
|
1669
|
+
const detail = [
|
|
1670
|
+
...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
|
|
1671
|
+
...key.live ? ["live"] : []
|
|
1672
|
+
];
|
|
1673
|
+
const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
1674
|
+
for (const guard of key.guards)
|
|
1675
|
+
this.#guard(node, guard, file);
|
|
1042
1676
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1677
|
+
}
|
|
1678
|
+
for (const endpoint of file.endpoints) {
|
|
1679
|
+
const refName = endpoint.model;
|
|
1680
|
+
if (!refName)
|
|
1681
|
+
continue;
|
|
1682
|
+
for (const key of endpoint.keys) {
|
|
1683
|
+
const detail = [
|
|
1684
|
+
...key.verb ? [key.verb] : [],
|
|
1685
|
+
...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
|
|
1686
|
+
];
|
|
1687
|
+
const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
1688
|
+
for (const guard of key.guards)
|
|
1689
|
+
this.#guard(node, guard, file);
|
|
1052
1690
|
}
|
|
1053
1691
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1692
|
+
}
|
|
1693
|
+
#guard(node, guard, file) {
|
|
1694
|
+
const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
|
|
1695
|
+
if (!local || local === "?")
|
|
1696
|
+
return;
|
|
1697
|
+
const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
|
|
1698
|
+
if (id)
|
|
1699
|
+
this.#addEdge(node.id, id, "guard", role, node.at);
|
|
1062
1700
|
}
|
|
1063
1701
|
#guardIdOf(local, site) {
|
|
1064
|
-
const found = this.#
|
|
1702
|
+
const found = this.#resolver.find(local, site);
|
|
1703
|
+
if ("external" in found)
|
|
1704
|
+
return null;
|
|
1065
1705
|
if ("guard" in found)
|
|
1066
1706
|
return this.#guardNodeOf(found.guard);
|
|
1067
1707
|
const { name, from } = found;
|
|
@@ -1074,99 +1714,18 @@ ${label ?? ""}`;
|
|
|
1074
1714
|
node.doc = builtin.doc;
|
|
1075
1715
|
return node.id;
|
|
1076
1716
|
}
|
|
1077
|
-
#
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
}
|
|
1090
|
-
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1091
|
-
const declared = this.#guards.get(name) ?? [];
|
|
1092
|
-
const guard = from === undefined ? declared.find((other) => other.owner === site.owner) ?? declared.find((other) => this.#visible(other.owner, site.owner)) : declared.find((other) => other.owner === from) ?? (from === frameworkOwner ? undefined : this.#soleVisible(declared, from));
|
|
1093
|
-
return guard ? { guard } : { name, from };
|
|
1094
|
-
}
|
|
1095
|
-
#mapEntriesOf(objectName, member, fileName, owner) {
|
|
1096
|
-
const key = `${objectName}
|
|
1097
|
-
${member}
|
|
1098
|
-
${fileName ?? ""}
|
|
1099
|
-
${owner ?? ""}`;
|
|
1100
|
-
const cached = this.#mapEntries.get(key);
|
|
1101
|
-
if (cached)
|
|
1102
|
-
return cached;
|
|
1103
|
-
const { ts, reader } = this.#source;
|
|
1104
|
-
const entries = [];
|
|
1105
|
-
const fileNames = [
|
|
1106
|
-
...fileName ? [fileName] : [],
|
|
1107
|
-
...this.#source.files.filter(([other, words]) => other !== fileName && owner !== null && !testFilePattern.test(other) && words.includes(`
|
|
1108
|
-
${objectName}
|
|
1109
|
-
`) && words.includes(`
|
|
1110
|
-
${member}
|
|
1111
|
-
`) && this.#source.ownerOf(other) === owner).map(([other]) => other)
|
|
1112
|
-
];
|
|
1113
|
-
for (const holder of fileNames) {
|
|
1114
|
-
const text = this.#source.readText(holder);
|
|
1115
|
-
if (text === undefined)
|
|
1116
|
-
continue;
|
|
1117
|
-
const source = this.#source.parse(holder, text);
|
|
1118
|
-
const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
|
|
1119
|
-
for (const statement of source.statements) {
|
|
1120
|
-
if (!ts.isVariableStatement(statement))
|
|
1121
|
-
continue;
|
|
1122
|
-
const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
|
|
1123
|
-
const object = map && reader.unwrap(map);
|
|
1124
|
-
if (!object || !ts.isObjectLiteralExpression(object))
|
|
1125
|
-
continue;
|
|
1126
|
-
for (const property of object.properties) {
|
|
1127
|
-
if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
|
|
1128
|
-
entries.push({ name: member, site });
|
|
1129
|
-
if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
|
|
1130
|
-
continue;
|
|
1131
|
-
const value = reader.unwrap(property.initializer);
|
|
1132
|
-
if (ts.isIdentifier(value))
|
|
1133
|
-
entries.push({ name: value.text, site });
|
|
1134
|
-
else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
|
|
1135
|
-
entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
this.#mapEntries.set(key, entries);
|
|
1140
|
-
return entries;
|
|
1141
|
-
}
|
|
1142
|
-
#declarationOf(owner, name, near) {
|
|
1143
|
-
const key = `${owner}
|
|
1144
|
-
${name}
|
|
1145
|
-
${near ?? ""}`;
|
|
1146
|
-
const cached = this.#declarations.get(key);
|
|
1147
|
-
if (cached !== undefined)
|
|
1148
|
-
return cached;
|
|
1149
|
-
const isNear = (fileName) => {
|
|
1150
|
-
const relative = this.#source.relative(fileName);
|
|
1151
|
-
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
1152
|
-
};
|
|
1153
|
-
const candidates = this.#source.files.filter(([fileName, words]) => !testFilePattern.test(fileName) && words.includes(`
|
|
1154
|
-
${name}
|
|
1155
|
-
`) && this.#source.ownerOf(fileName) === owner).map(([fileName]) => fileName);
|
|
1156
|
-
let found = null;
|
|
1157
|
-
for (const fileName of [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))]) {
|
|
1158
|
-
const text = this.#source.readText(fileName);
|
|
1159
|
-
if (text === undefined)
|
|
1160
|
-
continue;
|
|
1161
|
-
const source = this.#source.parse(fileName, text);
|
|
1162
|
-
const declared = this.#routes.declarationIn(source, name);
|
|
1163
|
-
if (!declared)
|
|
1164
|
-
continue;
|
|
1165
|
-
found = { source, statement: declared.statement, declared: declared.declared };
|
|
1166
|
-
break;
|
|
1167
|
-
}
|
|
1168
|
-
this.#declarations.set(key, found);
|
|
1169
|
-
return found;
|
|
1717
|
+
#madeInPlaceIdOf(name, file) {
|
|
1718
|
+
const made = this.#resolver.madeInPlace(file.fileName, name);
|
|
1719
|
+
if (made)
|
|
1720
|
+
return this.#guardNodeOf(made);
|
|
1721
|
+
const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
|
|
1722
|
+
const imported = file.imports.find((item) => item.name === head);
|
|
1723
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
|
|
1724
|
+
if (from === undefined || from === frameworkOwner)
|
|
1725
|
+
return null;
|
|
1726
|
+
const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
|
|
1727
|
+
const label = guardLabelOf(name, file.guardLabels);
|
|
1728
|
+
return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
|
|
1170
1729
|
}
|
|
1171
1730
|
#addRoutes() {
|
|
1172
1731
|
const routes = [];
|
|
@@ -1219,22 +1778,23 @@ ${dir}`, node);
|
|
|
1219
1778
|
}
|
|
1220
1779
|
#routeGuardIdOf({ name: local, local: declared }, fact, text) {
|
|
1221
1780
|
if (declared) {
|
|
1222
|
-
const
|
|
1223
|
-
const id = this.#key("guard", fact.owner, `${file}#${local}`);
|
|
1781
|
+
const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
|
|
1224
1782
|
return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
|
|
1225
1783
|
}
|
|
1226
|
-
const
|
|
1227
|
-
|
|
1784
|
+
const [head = local, member] = local.split(".");
|
|
1785
|
+
const imported = fact.imports.find((item) => item.name === head);
|
|
1786
|
+
if (!imported || member && imported.imported !== "*")
|
|
1228
1787
|
return null;
|
|
1229
|
-
const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
|
|
1230
|
-
const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
|
|
1788
|
+
const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
|
|
1789
|
+
const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
|
|
1231
1790
|
if (!from || from === frameworkOwner)
|
|
1232
1791
|
return null;
|
|
1233
|
-
const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
|
|
1792
|
+
const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
|
|
1234
1793
|
if (!found)
|
|
1235
1794
|
return null;
|
|
1236
1795
|
const { reader } = this.#source;
|
|
1237
|
-
|
|
1796
|
+
const owner = this.#source.ownerOf(found.source.fileName) ?? from;
|
|
1797
|
+
return this.#routeGuardNodeOf(this.#key("guard", owner, `${this.#resolver.inOwner(found.source.fileName, owner)}#${found.name}`), owner, found.name, reader.spanOf(found.source, found.declared), reader.docOf(found.source, found.statement), found.source.text, found.extent);
|
|
1238
1798
|
}
|
|
1239
1799
|
#routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
|
|
1240
1800
|
const isNew = !this.#nodes.has(id);
|
|
@@ -1247,18 +1807,70 @@ ${dir}`, node);
|
|
|
1247
1807
|
#adaptorIdOf(local, file) {
|
|
1248
1808
|
const [head, member] = local.split(".");
|
|
1249
1809
|
const imported = file.imports.find((item) => item.name === head);
|
|
1250
|
-
const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1251
1810
|
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1811
|
+
const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
|
|
1812
|
+
if (imported && path === null && !frameworkPattern.test(imported.from))
|
|
1813
|
+
return this.#packageAdaptorOf(name, imported.from);
|
|
1814
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1252
1815
|
const declared = this.#adaptors.get(name) ?? [];
|
|
1253
|
-
const found = from === undefined ? declared
|
|
1816
|
+
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)));
|
|
1254
1817
|
if (found)
|
|
1255
|
-
return
|
|
1818
|
+
return found.id;
|
|
1819
|
+
const reexported = from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
|
|
1820
|
+
if (reexported)
|
|
1821
|
+
return this.#packageAdaptorOf(reexported.name, reexported.from);
|
|
1256
1822
|
const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
|
|
1257
1823
|
const detail = from === frameworkOwner ? [frameworkOwner] : [];
|
|
1258
1824
|
return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
|
|
1259
1825
|
}
|
|
1260
|
-
#
|
|
1261
|
-
const
|
|
1826
|
+
#adaptorIn(declared, owner, path, fileName) {
|
|
1827
|
+
const own = declared.filter((adaptor) => adaptor.owner === owner);
|
|
1828
|
+
if (own.length < 2)
|
|
1829
|
+
return own.at(0);
|
|
1830
|
+
const pathOf = (adaptor) => this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
|
|
1831
|
+
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}/`));
|
|
1832
|
+
return named ?? own.at(0);
|
|
1833
|
+
}
|
|
1834
|
+
#pluggedOf({ owner, module }, field) {
|
|
1835
|
+
const plugs = this.#plugs.get(`${module}
|
|
1836
|
+
${field}`) ?? [];
|
|
1837
|
+
const own = plugs.filter((plug) => plug.owner === owner);
|
|
1838
|
+
if (!own.length && this.#serviceFields.has(`${owner ?? ""}
|
|
1839
|
+
${module}
|
|
1840
|
+
${field}`))
|
|
1841
|
+
return [];
|
|
1842
|
+
const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
|
|
1843
|
+
return [...new Set(inherited.map(({ target }) => target))];
|
|
1844
|
+
}
|
|
1845
|
+
#packageAdaptorOf(name, specifier) {
|
|
1846
|
+
if (frameworkPattern.test(specifier))
|
|
1847
|
+
return this.#addNode(this.#key("adaptor", null, name), "adaptor", null, null, name, null, [frameworkOwner]).id;
|
|
1848
|
+
const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
|
1849
|
+
return this.#addNode(this.#key("adaptor", pkg, name), "adaptor", null, null, name, null, [pkg]).id;
|
|
1850
|
+
}
|
|
1851
|
+
#packageShimOf(owner, name, near) {
|
|
1852
|
+
const reexport = this.#resolver.reexportOf(owner, name, near);
|
|
1853
|
+
const [shim] = reexport?.site.imports ?? [];
|
|
1854
|
+
if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
|
|
1855
|
+
return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
|
|
1856
|
+
if (reexport || !near)
|
|
1857
|
+
return null;
|
|
1858
|
+
const packages = new Set;
|
|
1859
|
+
for (const [fileName, words] of this.#resolver.filesIn(owner)) {
|
|
1860
|
+
const relative = this.#source.relative(fileName);
|
|
1861
|
+
const isNear = relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near;
|
|
1862
|
+
if (!isNear || !words.includes(`
|
|
1863
|
+
export
|
|
1864
|
+
`))
|
|
1865
|
+
continue;
|
|
1866
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
|
|
1867
|
+
if (this.#resolver.pathOfSpecifier(fileName, specifier) === null)
|
|
1868
|
+
packages.add(specifier);
|
|
1869
|
+
}
|
|
1870
|
+
const [only] = packages;
|
|
1871
|
+
return packages.size === 1 && only && !this.#resolver.declares(owner, name) ? { name, from: only } : null;
|
|
1872
|
+
}
|
|
1873
|
+
#adaptorNodeOf({ fact, owner, fileName, id }) {
|
|
1262
1874
|
const detail = fact.refName ? [fact.refName] : [];
|
|
1263
1875
|
const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
|
|
1264
1876
|
if (fact.doc && !node.doc)
|
|
@@ -1266,38 +1878,22 @@ ${dir}`, node);
|
|
|
1266
1878
|
this.#digest(node, this.#source.readText(fileName), fact.extent);
|
|
1267
1879
|
return node.id;
|
|
1268
1880
|
}
|
|
1269
|
-
#guardNodeOf({ name, owner, span, scope, doc, parts }) {
|
|
1270
|
-
const
|
|
1881
|
+
#guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
|
|
1882
|
+
const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
|
|
1883
|
+
const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
|
|
1271
1884
|
if (doc && !node.doc)
|
|
1272
1885
|
node.doc = doc;
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1886
|
+
if (node.digest)
|
|
1887
|
+
return node.id;
|
|
1888
|
+
const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
|
|
1889
|
+
if (!digest)
|
|
1890
|
+
return node.id;
|
|
1891
|
+
node.digest = digest;
|
|
1892
|
+
const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
|
|
1893
|
+
if (sources.length)
|
|
1894
|
+
node.sources = sources;
|
|
1279
1895
|
return node.id;
|
|
1280
1896
|
}
|
|
1281
|
-
#ownerOfSpecifier(fileName, specifier) {
|
|
1282
|
-
if (frameworkPattern.test(specifier))
|
|
1283
|
-
return frameworkOwner;
|
|
1284
|
-
const path = this.#pathOfSpecifier(fileName, specifier);
|
|
1285
|
-
return path === null ? undefined : ownerOf(path) ?? undefined;
|
|
1286
|
-
}
|
|
1287
|
-
#pathOfSpecifier(fileName, specifier) {
|
|
1288
|
-
const alias = specifier.match(aliasPattern);
|
|
1289
|
-
if (alias)
|
|
1290
|
-
return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
|
|
1291
|
-
if (!specifier.startsWith("."))
|
|
1292
|
-
return null;
|
|
1293
|
-
const segments = this.#source.relative(fileName).split("/").slice(0, -1);
|
|
1294
|
-
for (const segment of specifier.split("/"))
|
|
1295
|
-
if (segment === "..")
|
|
1296
|
-
segments.pop();
|
|
1297
|
-
else if (segment !== ".")
|
|
1298
|
-
segments.push(segment);
|
|
1299
|
-
return segments.join("/");
|
|
1300
|
-
}
|
|
1301
1897
|
#moduleDirOf(fileName) {
|
|
1302
1898
|
const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
|
|
1303
1899
|
return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
|
|
@@ -1333,20 +1929,13 @@ ${dir}`, node);
|
|
|
1333
1929
|
visit(constant);
|
|
1334
1930
|
return [...fields.values()];
|
|
1335
1931
|
}
|
|
1336
|
-
#visible(target, from) {
|
|
1337
|
-
return !from || !target || target === from || target.startsWith("libs/");
|
|
1338
|
-
}
|
|
1339
|
-
#soleVisible(declared, from) {
|
|
1340
|
-
const visible = declared.filter((other) => this.#visible(other.owner, from));
|
|
1341
|
-
return visible.length === 1 ? visible[0] : undefined;
|
|
1342
|
-
}
|
|
1343
1932
|
#targetNodeOf({ family, owner, refName, sourceKey }) {
|
|
1344
1933
|
const isDefault = /^[A-Z]/.test(sourceKey);
|
|
1345
1934
|
const id = family === "slice" || family === "query" ? this.#key(family, owner, refName, isDefault ? "" : sourceKey) : family === "model" || family === "formSetter" ? this.#key("model", owner, refName) : family === "endpoint" ? this.#key("endpoint", owner, refName, sourceKey) : null;
|
|
1346
1935
|
return id && this.#nodes.has(id) ? id : null;
|
|
1347
1936
|
}
|
|
1348
1937
|
#membersNamed(kind, name, owner, refName) {
|
|
1349
|
-
const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
|
|
1938
|
+
const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
|
|
1350
1939
|
const own = nodes.filter((node) => node.owner === owner);
|
|
1351
1940
|
return (own.length ? own : nodes).map((node) => node.id);
|
|
1352
1941
|
}
|
|
@@ -1391,10 +1980,8 @@ st
|
|
|
1391
1980
|
visit(source);
|
|
1392
1981
|
}
|
|
1393
1982
|
#link(fileName, source, identifier, chain, context, owner) {
|
|
1394
|
-
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#
|
|
1395
|
-
|
|
1396
|
-
${chain[1]}`) : undefined;
|
|
1397
|
-
const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
1983
|
+
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#pluggedOf(context, chain[1]) : [];
|
|
1984
|
+
const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
1398
1985
|
const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
|
|
1399
1986
|
if (!targets.length)
|
|
1400
1987
|
return;
|
|
@@ -1700,6 +2287,10 @@ var endpointNames = (key) => {
|
|
|
1700
2287
|
};
|
|
1701
2288
|
|
|
1702
2289
|
// pkgs/@akanjs/language/nameTable.ts
|
|
2290
|
+
var guardDetailOf = (facts, guards) => {
|
|
2291
|
+
const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
|
|
2292
|
+
return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
|
|
2293
|
+
};
|
|
1703
2294
|
var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
|
|
1704
2295
|
|
|
1705
2296
|
class NameTable {
|
|
@@ -1799,7 +2390,7 @@ class NameTable {
|
|
|
1799
2390
|
role: "sig",
|
|
1800
2391
|
gate: "sig",
|
|
1801
2392
|
label: sliceClass.name,
|
|
1802
|
-
detail:
|
|
2393
|
+
detail: guardDetailOf(facts, sliceClass.guards)
|
|
1803
2394
|
});
|
|
1804
2395
|
if (modelDoc)
|
|
1805
2396
|
this.#add({
|
|
@@ -1864,7 +2455,7 @@ class NameTable {
|
|
|
1864
2455
|
sourceKey: slice.name,
|
|
1865
2456
|
span: slice.span,
|
|
1866
2457
|
label: `slice ${refName} (default)`,
|
|
1867
|
-
detail:
|
|
2458
|
+
detail: guardDetailOf(facts, slice.guards)
|
|
1868
2459
|
};
|
|
1869
2460
|
this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
|
|
1870
2461
|
for (const key of slice.keys)
|
|
@@ -1874,7 +2465,7 @@ class NameTable {
|
|
|
1874
2465
|
sourceKey: key.name,
|
|
1875
2466
|
span: key.span,
|
|
1876
2467
|
label: `slice ${key.name}`,
|
|
1877
|
-
detail: [...
|
|
2468
|
+
detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
|
|
1878
2469
|
}, refName, key.name, modelNameSet, slice.name);
|
|
1879
2470
|
}
|
|
1880
2471
|
if (filterClass) {
|
|
@@ -1931,7 +2522,7 @@ class NameTable {
|
|
|
1931
2522
|
role: key.verb === "pubsub" ? "subscribe" : "listen",
|
|
1932
2523
|
gate: null,
|
|
1933
2524
|
label: `${key.verb} ${key.name}`,
|
|
1934
|
-
detail:
|
|
2525
|
+
detail: guardDetailOf(facts, key.guards)
|
|
1935
2526
|
});
|
|
1936
2527
|
}
|
|
1937
2528
|
}
|
|
@@ -1989,14 +2580,12 @@ var dictionaryPattern = /\.dictionary\.ts$/;
|
|
|
1989
2580
|
var srvkitPattern = /\/srvkit\//;
|
|
1990
2581
|
var wordPattern = /[A-Za-z_$][\w$]*/g;
|
|
1991
2582
|
var identifierPattern = /^[A-Za-z_$][\w$]*$/;
|
|
1992
|
-
var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
|
|
1993
2583
|
var stateReceivers = new Set(["use", "sel", "get", "do"]);
|
|
1994
2584
|
var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
|
|
1995
2585
|
var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
|
|
1996
2586
|
var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
|
|
1997
2587
|
var parseCacheSize = 64;
|
|
1998
2588
|
var pathOf = (fileName) => fileName.replace(/\\/g, "/");
|
|
1999
|
-
var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
|
|
2000
2589
|
var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
|
|
2001
2590
|
|
|
2002
2591
|
class AkanIndex {
|
|
@@ -2127,6 +2716,8 @@ canPass
|
|
|
2127
2716
|
adapt
|
|
2128
2717
|
`) || words.includes(`
|
|
2129
2718
|
dangerouslyAdapt
|
|
2719
|
+
`) || words.includes(`
|
|
2720
|
+
applyAdaptor
|
|
2130
2721
|
`);
|
|
2131
2722
|
}
|
|
2132
2723
|
static #sourceKeyOf({ family, sourceKey, label, detail, span }) {
|