@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/server.js
CHANGED
|
@@ -5,6 +5,239 @@ import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "n
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
7
|
|
|
8
|
+
// pkgs/@akanjs/language/guardFacts.ts
|
|
9
|
+
var guardLabelOf = (guard, labels) => {
|
|
10
|
+
const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
|
|
11
|
+
const label = labels[local];
|
|
12
|
+
return label === undefined ? guard : role ? `${role} ${label}` : label;
|
|
13
|
+
};
|
|
14
|
+
var maxLabelArgs = 32;
|
|
15
|
+
var guardRefReaderOf = (ts, ast, unwrap) => {
|
|
16
|
+
const pathOf = (expression) => {
|
|
17
|
+
if (ts.isIdentifier(expression))
|
|
18
|
+
return expression.text;
|
|
19
|
+
if (!ts.isPropertyAccessExpression(expression))
|
|
20
|
+
return null;
|
|
21
|
+
const head = pathOf(expression.expression);
|
|
22
|
+
return head && `${head}.${expression.name.text}`;
|
|
23
|
+
};
|
|
24
|
+
const calleeOf = (call) => pathOf(unwrap(call.expression)) ?? "?";
|
|
25
|
+
const labelOf = (source, call) => {
|
|
26
|
+
const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
|
|
27
|
+
return `${calleeOf(call)}(${args.length <= maxLabelArgs && !args.includes(`
|
|
28
|
+
`) ? args : "…"})`;
|
|
29
|
+
};
|
|
30
|
+
const guardRefOf = (expression, made, member) => {
|
|
31
|
+
const value = unwrap(expression);
|
|
32
|
+
if (!ts.isCallExpression(value))
|
|
33
|
+
return pathOf(value) ?? ast.expressionName(value);
|
|
34
|
+
const prefix = `${member}:${calleeOf(value)}@`;
|
|
35
|
+
const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
|
|
36
|
+
made.push({ call: value, name });
|
|
37
|
+
return name;
|
|
38
|
+
};
|
|
39
|
+
const guardsIn = (source, object, made, member) => {
|
|
40
|
+
const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
|
|
41
|
+
if (!guards)
|
|
42
|
+
return [];
|
|
43
|
+
const refOf = (expression) => guardRefOf(expression, made, member);
|
|
44
|
+
if (ts.isArrayLiteralExpression(guards))
|
|
45
|
+
return guards.elements.map(refOf).filter((name) => name !== null);
|
|
46
|
+
if (ts.isObjectLiteralExpression(guards))
|
|
47
|
+
return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
|
|
48
|
+
const value = unwrap(property.initializer);
|
|
49
|
+
const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
|
|
50
|
+
return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
|
|
51
|
+
});
|
|
52
|
+
const single = refOf(guards);
|
|
53
|
+
return single ? [single] : [];
|
|
54
|
+
};
|
|
55
|
+
return { pathOf, labelOf, guardsIn };
|
|
56
|
+
};
|
|
57
|
+
var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf) => {
|
|
58
|
+
const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
|
|
59
|
+
const scopeOf = (node) => {
|
|
60
|
+
const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
|
|
61
|
+
const value = scope && unwrap(scope);
|
|
62
|
+
return value && ts.isStringLiteralLike(value) ? value.text : null;
|
|
63
|
+
};
|
|
64
|
+
const functionsOf = (source) => {
|
|
65
|
+
const functions = new Map;
|
|
66
|
+
for (const statement of source.statements) {
|
|
67
|
+
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
|
|
68
|
+
functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
|
|
69
|
+
if (!ts.isVariableStatement(statement))
|
|
70
|
+
continue;
|
|
71
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
72
|
+
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
73
|
+
if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
|
|
74
|
+
functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return functions;
|
|
78
|
+
};
|
|
79
|
+
const returnedOf = (body, guardNames) => {
|
|
80
|
+
const locals = new Map;
|
|
81
|
+
const pending = [];
|
|
82
|
+
const visit = (node) => {
|
|
83
|
+
if (ts.isClassDeclaration(node) && node.name)
|
|
84
|
+
locals.set(node.name.text, node);
|
|
85
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
|
|
86
|
+
locals.set(node.name.text, node.initializer);
|
|
87
|
+
if (ts.isReturnStatement(node) && node.expression)
|
|
88
|
+
pending.push(node.expression);
|
|
89
|
+
if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
|
|
90
|
+
ts.forEachChild(node, visit);
|
|
91
|
+
};
|
|
92
|
+
if (ts.isBlock(body))
|
|
93
|
+
ts.forEachChild(body, visit);
|
|
94
|
+
else
|
|
95
|
+
pending.push(body);
|
|
96
|
+
let guard = null;
|
|
97
|
+
const calls = [];
|
|
98
|
+
const seen = new Set;
|
|
99
|
+
const passed = new Set;
|
|
100
|
+
for (let node = pending.pop();node; node = pending.pop()) {
|
|
101
|
+
if (seen.has(node))
|
|
102
|
+
continue;
|
|
103
|
+
seen.add(node);
|
|
104
|
+
const value = ts.isExpression(node) ? unwrap(node) : node;
|
|
105
|
+
if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
|
|
106
|
+
guard ??= value;
|
|
107
|
+
else if (ts.isConditionalExpression(value))
|
|
108
|
+
pending.push(value.whenTrue, value.whenFalse);
|
|
109
|
+
else if (ts.isBinaryExpression(value))
|
|
110
|
+
pending.push(value.left, value.right);
|
|
111
|
+
else if (ts.isIdentifier(value)) {
|
|
112
|
+
const local = locals.get(value.text);
|
|
113
|
+
if (local)
|
|
114
|
+
pending.push(local);
|
|
115
|
+
} else if (ts.isCallExpression(value)) {
|
|
116
|
+
const callee = pathOf(unwrap(value.expression));
|
|
117
|
+
if (callee && !passed.has(node) && !calls.includes(callee))
|
|
118
|
+
calls.push(callee);
|
|
119
|
+
for (const arg of value.arguments)
|
|
120
|
+
passed.add(arg);
|
|
121
|
+
pending.push(...value.arguments);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { guard, calls };
|
|
125
|
+
};
|
|
126
|
+
const guardFactoriesOf = (source, guardNames, isImported) => {
|
|
127
|
+
const functions = functionsOf(source);
|
|
128
|
+
const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
|
|
129
|
+
const members = new Set;
|
|
130
|
+
const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
|
|
131
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
132
|
+
isGrowing = false;
|
|
133
|
+
for (const [name, { guard, calls }] of returned) {
|
|
134
|
+
if (members.has(name) || !guard && !calls.some(isFactoryCall))
|
|
135
|
+
continue;
|
|
136
|
+
members.add(name);
|
|
137
|
+
isGrowing = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const factories = new Map;
|
|
141
|
+
for (const name of members) {
|
|
142
|
+
const found = functions.get(name);
|
|
143
|
+
const made = returned.get(name);
|
|
144
|
+
if (!found || !made)
|
|
145
|
+
continue;
|
|
146
|
+
factories.set(name, {
|
|
147
|
+
name,
|
|
148
|
+
span: spanOf(source, found.name),
|
|
149
|
+
extent: extentOf(source, found.statement),
|
|
150
|
+
scope: made.guard ? scopeOf(made.guard) : null,
|
|
151
|
+
doc: docOf(source, found.statement),
|
|
152
|
+
direct: !!made.guard,
|
|
153
|
+
via: made.calls.filter(isFactoryCall)
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return factories;
|
|
157
|
+
};
|
|
158
|
+
return { implementsGuard, scopeOf, guardFactoriesOf };
|
|
159
|
+
};
|
|
160
|
+
var createGuardFactReader = (ts, ast, base) => {
|
|
161
|
+
const { spanOf, extentOf, unwrap, docOf } = base;
|
|
162
|
+
const { pathOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
|
|
163
|
+
const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf);
|
|
164
|
+
const guardsOf = (source, imports, madeInPlace) => {
|
|
165
|
+
const guardNames = new Set([
|
|
166
|
+
"Guard",
|
|
167
|
+
...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
|
|
168
|
+
]);
|
|
169
|
+
const imported = new Set(imports.map(({ name }) => name));
|
|
170
|
+
const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
|
|
171
|
+
const factories = guardFactoriesOf(source, guardNames, isImported);
|
|
172
|
+
const guards = [];
|
|
173
|
+
const calls = [];
|
|
174
|
+
const makeOf = (made, value, isInPlace = false) => {
|
|
175
|
+
if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
|
|
176
|
+
guards.push({ ...made, scope: scopeOf(value), factory: null });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const callee = ts.isCallExpression(value) ? pathOf(unwrap(value.expression)) : null;
|
|
180
|
+
const factory = callee && factories.get(callee);
|
|
181
|
+
if (factory)
|
|
182
|
+
guards.push({ ...made, scope: factory.scope, factory: factory.name });
|
|
183
|
+
else if (callee && (isImported(callee) || isInPlace))
|
|
184
|
+
calls.push({ ...made, callee, kind: "call", scope: null });
|
|
185
|
+
};
|
|
186
|
+
for (const statement of source.statements) {
|
|
187
|
+
if (ts.isClassDeclaration(statement) && statement.name) {
|
|
188
|
+
const declared = {
|
|
189
|
+
name: statement.name.text,
|
|
190
|
+
span: spanOf(source, statement.name),
|
|
191
|
+
extent: extentOf(source, statement),
|
|
192
|
+
scope: scopeOf(statement),
|
|
193
|
+
doc: docOf(source, statement)
|
|
194
|
+
};
|
|
195
|
+
const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
|
|
196
|
+
const callee = base && pathOf(base.expression);
|
|
197
|
+
if (implementsGuard(statement, guardNames))
|
|
198
|
+
guards.push({ ...declared, factory: null });
|
|
199
|
+
else if (callee)
|
|
200
|
+
calls.push({ ...declared, callee, kind: "extends" });
|
|
201
|
+
}
|
|
202
|
+
if (!ts.isVariableStatement(statement))
|
|
203
|
+
continue;
|
|
204
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
205
|
+
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
206
|
+
if (!initializer || !ts.isIdentifier(declaration.name))
|
|
207
|
+
continue;
|
|
208
|
+
const holder = declaration.name.text;
|
|
209
|
+
makeOf({
|
|
210
|
+
name: holder,
|
|
211
|
+
span: spanOf(source, declaration.name),
|
|
212
|
+
extent: extentOf(source, statement),
|
|
213
|
+
doc: docOf(source, statement)
|
|
214
|
+
}, initializer);
|
|
215
|
+
if (!ts.isObjectLiteralExpression(initializer))
|
|
216
|
+
continue;
|
|
217
|
+
for (const property of initializer.properties) {
|
|
218
|
+
const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
|
|
219
|
+
if (key && ts.isPropertyAssignment(property))
|
|
220
|
+
makeOf({
|
|
221
|
+
name: `${holder}.${key}`,
|
|
222
|
+
span: spanOf(source, property.name),
|
|
223
|
+
extent: extentOf(source, property),
|
|
224
|
+
doc: docOf(source, property)
|
|
225
|
+
}, unwrap(property.initializer));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const { call, name } of madeInPlace)
|
|
230
|
+
makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
|
|
231
|
+
const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
|
|
232
|
+
return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
|
|
233
|
+
};
|
|
234
|
+
return { guardsIn, guardsOf };
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// pkgs/@akanjs/language/escapeRegExp.ts
|
|
238
|
+
var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
|
|
239
|
+
var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
|
|
240
|
+
|
|
8
241
|
// pkgs/@akanjs/language/moduleFacts.ts
|
|
9
242
|
var testFilePattern = /\.(?:test|spec)\.tsx?$/;
|
|
10
243
|
var moduleCallees = new Set([
|
|
@@ -73,21 +306,6 @@ var createFactReader = (ts, ast) => {
|
|
|
73
306
|
const value = unwrap(expression);
|
|
74
307
|
return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
|
|
75
308
|
};
|
|
76
|
-
const guardsIn = (object) => {
|
|
77
|
-
const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
|
|
78
|
-
if (!guards)
|
|
79
|
-
return [];
|
|
80
|
-
if (ts.isArrayLiteralExpression(guards))
|
|
81
|
-
return guards.elements.map(guardNameOf).filter((name) => name !== null);
|
|
82
|
-
if (ts.isObjectLiteralExpression(guards))
|
|
83
|
-
return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
|
|
84
|
-
const value = unwrap(property.initializer);
|
|
85
|
-
const names = ts.isArrayLiteralExpression(value) ? value.elements.map(guardNameOf) : [guardNameOf(value)];
|
|
86
|
-
return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
|
|
87
|
-
});
|
|
88
|
-
const single = guardNameOf(guards);
|
|
89
|
-
return single ? [single] : [];
|
|
90
|
-
};
|
|
91
309
|
const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
|
|
92
310
|
const chainOf = (initializer) => {
|
|
93
311
|
let root = unwrap(initializer);
|
|
@@ -104,7 +322,7 @@ var createFactReader = (ts, ast) => {
|
|
|
104
322
|
const node = keyName(property);
|
|
105
323
|
return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
|
|
106
324
|
});
|
|
107
|
-
const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
325
|
+
const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
108
326
|
if (!ts.isPropertyAssignment(property))
|
|
109
327
|
return [];
|
|
110
328
|
const name = ast.nodeName(property.name);
|
|
@@ -116,7 +334,7 @@ var createFactReader = (ts, ast) => {
|
|
|
116
334
|
name,
|
|
117
335
|
span: spanOf(source, property.name),
|
|
118
336
|
extent: extentOf(source, property),
|
|
119
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
337
|
+
guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
|
|
120
338
|
live: methods.includes("live"),
|
|
121
339
|
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
122
340
|
}
|
|
@@ -166,7 +384,7 @@ var createFactReader = (ts, ast) => {
|
|
|
166
384
|
const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
|
|
167
385
|
return `${ms / size}${unit}`;
|
|
168
386
|
};
|
|
169
|
-
const modesOf = (option) => {
|
|
387
|
+
const modesOf = (option, type) => {
|
|
170
388
|
const object = option && unwrap(option);
|
|
171
389
|
if (!object || !ts.isObjectLiteralExpression(object))
|
|
172
390
|
return [];
|
|
@@ -176,9 +394,9 @@ var createFactReader = (ts, ast) => {
|
|
|
176
394
|
if (name === "enabled")
|
|
177
395
|
return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
|
|
178
396
|
if (name === "once")
|
|
179
|
-
return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
397
|
+
return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
180
398
|
if (name === "lock")
|
|
181
|
-
return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
399
|
+
return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
182
400
|
if (name !== "serverMode" && name !== "operationMode")
|
|
183
401
|
return [];
|
|
184
402
|
const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
|
|
@@ -196,7 +414,8 @@ var createFactReader = (ts, ast) => {
|
|
|
196
414
|
const { root } = chainOf(property.initializer);
|
|
197
415
|
const type = root ? verbOf(root.expression) : null;
|
|
198
416
|
const [first, second] = root?.arguments ?? [];
|
|
199
|
-
const
|
|
417
|
+
const cron = type === "cron" && first ? unwrap(first) : null;
|
|
418
|
+
const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
|
|
200
419
|
const option = type === "initialize" || type === "destroy" ? first : second;
|
|
201
420
|
return [
|
|
202
421
|
{
|
|
@@ -205,7 +424,7 @@ var createFactReader = (ts, ast) => {
|
|
|
205
424
|
extent: extentOf(source, property),
|
|
206
425
|
type,
|
|
207
426
|
schedule,
|
|
208
|
-
modes: modesOf(option)
|
|
427
|
+
modes: modesOf(option, type)
|
|
209
428
|
}
|
|
210
429
|
];
|
|
211
430
|
});
|
|
@@ -222,7 +441,20 @@ var createFactReader = (ts, ast) => {
|
|
|
222
441
|
return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
|
|
223
442
|
});
|
|
224
443
|
};
|
|
225
|
-
const
|
|
444
|
+
const adaptorBindingsIn = (source) => {
|
|
445
|
+
const bindings = [];
|
|
446
|
+
const visit = (node) => {
|
|
447
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
|
|
448
|
+
const [role, adaptor] = node.arguments.map(guardNameOf);
|
|
449
|
+
if (role && adaptor)
|
|
450
|
+
bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
|
|
451
|
+
}
|
|
452
|
+
ts.forEachChild(node, visit);
|
|
453
|
+
};
|
|
454
|
+
visit(source);
|
|
455
|
+
return bindings;
|
|
456
|
+
};
|
|
457
|
+
const endpointKeysOf = (source, builder, made, model) => {
|
|
226
458
|
const verbOf = verbReaderOf(builder?.parameters[0]);
|
|
227
459
|
return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
228
460
|
if (!ts.isPropertyAssignment(property))
|
|
@@ -237,7 +469,7 @@ var createFactReader = (ts, ast) => {
|
|
|
237
469
|
span: spanOf(source, property.name),
|
|
238
470
|
extent: extentOf(source, property),
|
|
239
471
|
verb: root ? verbOf(root.expression) : null,
|
|
240
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
472
|
+
guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
|
|
241
473
|
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
242
474
|
}
|
|
243
475
|
];
|
|
@@ -304,22 +536,6 @@ var createFactReader = (ts, ast) => {
|
|
|
304
536
|
})) : []
|
|
305
537
|
];
|
|
306
538
|
};
|
|
307
|
-
const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
|
|
308
|
-
const guardClassIn = (node, guardNames) => {
|
|
309
|
-
if ((ts.isClassExpression(node) || ts.isClassDeclaration(node)) && implementsGuard(node, guardNames))
|
|
310
|
-
return node;
|
|
311
|
-
return ts.forEachChild(node, (child) => guardClassIn(child, guardNames)) ?? null;
|
|
312
|
-
};
|
|
313
|
-
const callsIn = (node, names) => {
|
|
314
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text))
|
|
315
|
-
return node.expression.text;
|
|
316
|
-
return ts.forEachChild(node, (child) => callsIn(child, names)) ?? null;
|
|
317
|
-
};
|
|
318
|
-
const scopeOf = (node) => {
|
|
319
|
-
const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
|
|
320
|
-
const value = scope && unwrap(scope);
|
|
321
|
-
return value && ts.isStringLiteralLike(value) ? value.text : null;
|
|
322
|
-
};
|
|
323
539
|
const docOf = (source, node) => {
|
|
324
540
|
const { text } = source;
|
|
325
541
|
const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
|
|
@@ -335,108 +551,20 @@ var createFactReader = (ts, ast) => {
|
|
|
335
551
|
`)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
|
|
336
552
|
return lines.length ? lines.join(" ") : null;
|
|
337
553
|
};
|
|
338
|
-
const
|
|
339
|
-
const functions = new Map;
|
|
340
|
-
for (const statement of source.statements) {
|
|
341
|
-
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
|
|
342
|
-
functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
|
|
343
|
-
if (!ts.isVariableStatement(statement))
|
|
344
|
-
continue;
|
|
345
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
346
|
-
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
347
|
-
if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
|
|
348
|
-
functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
return functions;
|
|
352
|
-
};
|
|
353
|
-
const guardFactoriesOf = (source, guardNames) => {
|
|
354
|
-
const functions = functionsOf(source);
|
|
355
|
-
const factories = new Map;
|
|
356
|
-
const factoryOf = (name, scope, via) => {
|
|
357
|
-
const found = functions.get(name);
|
|
358
|
-
if (!found)
|
|
359
|
-
return;
|
|
360
|
-
factories.set(name, {
|
|
361
|
-
name,
|
|
362
|
-
span: spanOf(source, found.name),
|
|
363
|
-
extent: extentOf(source, found.statement),
|
|
364
|
-
scope,
|
|
365
|
-
doc: docOf(source, found.statement),
|
|
366
|
-
via
|
|
367
|
-
});
|
|
368
|
-
};
|
|
369
|
-
for (const [name, { body }] of functions) {
|
|
370
|
-
const guard = guardClassIn(body, guardNames);
|
|
371
|
-
if (guard)
|
|
372
|
-
factoryOf(name, scopeOf(guard), null);
|
|
373
|
-
}
|
|
374
|
-
for (let isGrowing = true;isGrowing; ) {
|
|
375
|
-
isGrowing = false;
|
|
376
|
-
for (const [name, { body }] of functions) {
|
|
377
|
-
const called = factories.has(name) ? null : callsIn(body, new Set(factories.keys()));
|
|
378
|
-
if (!called)
|
|
379
|
-
continue;
|
|
380
|
-
factoryOf(name, factories.get(called)?.scope ?? null, called);
|
|
381
|
-
isGrowing = true;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return factories;
|
|
385
|
-
};
|
|
386
|
-
const guardsOf = (source, imports) => {
|
|
387
|
-
const guardNames = new Set([
|
|
388
|
-
"Guard",
|
|
389
|
-
...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
|
|
390
|
-
]);
|
|
391
|
-
const imported = new Set(imports.map(({ name }) => name));
|
|
392
|
-
const factories = guardFactoriesOf(source, guardNames);
|
|
393
|
-
const guards = [];
|
|
394
|
-
const calls = [];
|
|
395
|
-
for (const statement of source.statements) {
|
|
396
|
-
if (ts.isClassDeclaration(statement) && statement.name && implementsGuard(statement, guardNames))
|
|
397
|
-
guards.push({
|
|
398
|
-
name: statement.name.text,
|
|
399
|
-
span: spanOf(source, statement.name),
|
|
400
|
-
extent: extentOf(source, statement),
|
|
401
|
-
scope: scopeOf(statement),
|
|
402
|
-
doc: docOf(source, statement),
|
|
403
|
-
factory: null
|
|
404
|
-
});
|
|
405
|
-
if (!ts.isVariableStatement(statement))
|
|
406
|
-
continue;
|
|
407
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
408
|
-
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
409
|
-
if (!initializer || !ts.isIdentifier(declaration.name))
|
|
410
|
-
continue;
|
|
411
|
-
const made = {
|
|
412
|
-
name: declaration.name.text,
|
|
413
|
-
span: spanOf(source, declaration.name),
|
|
414
|
-
extent: extentOf(source, statement),
|
|
415
|
-
doc: docOf(source, statement)
|
|
416
|
-
};
|
|
417
|
-
if (ts.isClassExpression(initializer) && implementsGuard(initializer, guardNames))
|
|
418
|
-
guards.push({ ...made, scope: scopeOf(initializer), factory: null });
|
|
419
|
-
if (!ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression))
|
|
420
|
-
continue;
|
|
421
|
-
const callee = initializer.expression.text;
|
|
422
|
-
const factory = factories.get(callee);
|
|
423
|
-
if (factory)
|
|
424
|
-
guards.push({ ...made, scope: factory.scope, factory: callee });
|
|
425
|
-
else if (imported.has(callee))
|
|
426
|
-
calls.push({ ...made, callee });
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
return { guards, guardFactories: [...factories.values()], guardCalls: calls };
|
|
430
|
-
};
|
|
554
|
+
const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
|
|
431
555
|
const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
|
|
432
556
|
const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
|
|
433
557
|
const read = (source, owner) => {
|
|
434
558
|
const imports = importsIn(source);
|
|
559
|
+
const madeInPlace = [];
|
|
435
560
|
const facts = {
|
|
436
561
|
fileName: source.fileName,
|
|
437
562
|
owner,
|
|
438
563
|
imports,
|
|
439
|
-
|
|
564
|
+
guards: [],
|
|
565
|
+
guardFactories: [],
|
|
566
|
+
guardCalls: [],
|
|
567
|
+
guardLabels: {},
|
|
440
568
|
constants: [],
|
|
441
569
|
enums: [],
|
|
442
570
|
filters: [],
|
|
@@ -446,6 +574,7 @@ var createFactReader = (ts, ast) => {
|
|
|
446
574
|
endpoints: [],
|
|
447
575
|
internals: [],
|
|
448
576
|
adaptors: [],
|
|
577
|
+
adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
|
|
449
578
|
stores: []
|
|
450
579
|
};
|
|
451
580
|
for (const statement of source.statements) {
|
|
@@ -470,16 +599,19 @@ var createFactReader = (ts, ast) => {
|
|
|
470
599
|
...name,
|
|
471
600
|
model: moduleRefOf(call.arguments[0], "db"),
|
|
472
601
|
methods: methodsOf(source, statement),
|
|
473
|
-
plugs: plugsOf(source, builder)
|
|
602
|
+
plugs: plugsOf(source, builder),
|
|
603
|
+
fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
|
|
474
604
|
});
|
|
475
|
-
else if (callee === "slice")
|
|
605
|
+
else if (callee === "slice") {
|
|
606
|
+
const model = moduleRefOf(call.arguments[0], "srv");
|
|
607
|
+
const member = model ?? name.name;
|
|
476
608
|
facts.slices.push({
|
|
477
609
|
...name,
|
|
478
|
-
model
|
|
479
|
-
guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
|
|
480
|
-
keys: sliceKeysOf(source, builder)
|
|
610
|
+
model,
|
|
611
|
+
guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
|
|
612
|
+
keys: sliceKeysOf(source, builder, madeInPlace, member)
|
|
481
613
|
});
|
|
482
|
-
else if (callee === "internal")
|
|
614
|
+
} else if (callee === "internal")
|
|
483
615
|
facts.internals.push({
|
|
484
616
|
...name,
|
|
485
617
|
model: moduleRefOf(call.arguments[0], "srv"),
|
|
@@ -494,13 +626,14 @@ var createFactReader = (ts, ast) => {
|
|
|
494
626
|
plugs: plugsOf(source, builder),
|
|
495
627
|
doc: docOf(source, statement)
|
|
496
628
|
});
|
|
497
|
-
} else if (callee === "endpoint")
|
|
629
|
+
} else if (callee === "endpoint") {
|
|
630
|
+
const model = moduleRefOf(call.arguments[0], "srv");
|
|
498
631
|
facts.endpoints.push({
|
|
499
632
|
...name,
|
|
500
|
-
model
|
|
501
|
-
keys: endpointKeysOf(source, builder)
|
|
633
|
+
model,
|
|
634
|
+
keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
|
|
502
635
|
});
|
|
503
|
-
else if (callee === "store")
|
|
636
|
+
} else if (callee === "store")
|
|
504
637
|
facts.stores.push({
|
|
505
638
|
...name,
|
|
506
639
|
model: moduleRefOf(call.arguments[0], "sig"),
|
|
@@ -508,10 +641,439 @@ var createFactReader = (ts, ast) => {
|
|
|
508
641
|
actions: methodsOf(source, statement)
|
|
509
642
|
});
|
|
510
643
|
}
|
|
511
|
-
return facts;
|
|
512
|
-
};
|
|
513
|
-
return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
|
|
514
|
-
};
|
|
644
|
+
return { ...facts, ...guardsOf(source, imports, madeInPlace) };
|
|
645
|
+
};
|
|
646
|
+
return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
// pkgs/@akanjs/language/guardResolver.ts
|
|
650
|
+
var frameworkOwner = "akanjs";
|
|
651
|
+
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
652
|
+
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
653
|
+
var maxMapDepth = 4;
|
|
654
|
+
var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
|
|
655
|
+
var declaringPatternOf = (name, isAnyBinding) => {
|
|
656
|
+
const escaped = escapeRegExp(name);
|
|
657
|
+
const braced = "\\{(?:[^}]*?[\\s,])?";
|
|
658
|
+
const leads = [
|
|
659
|
+
"function\\s*\\*?\\s*",
|
|
660
|
+
"(?:const|let|var)\\s+",
|
|
661
|
+
`export\\s*${braced}`,
|
|
662
|
+
...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
|
|
663
|
+
];
|
|
664
|
+
return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
class GuardResolver {
|
|
668
|
+
#source;
|
|
669
|
+
#routes;
|
|
670
|
+
#guards = new Map;
|
|
671
|
+
#madeInPlace = new Map;
|
|
672
|
+
#mapEntries = new Map;
|
|
673
|
+
#reexports = new Map;
|
|
674
|
+
#declarations = new Map;
|
|
675
|
+
#starExports = new Map;
|
|
676
|
+
#ownerFiles = null;
|
|
677
|
+
#modules = null;
|
|
678
|
+
constructor(source, routes) {
|
|
679
|
+
this.#source = source;
|
|
680
|
+
this.#routes = routes;
|
|
681
|
+
}
|
|
682
|
+
find(local, site) {
|
|
683
|
+
return this.#findGuard(local, site, 0);
|
|
684
|
+
}
|
|
685
|
+
madeInPlace(fileName, name) {
|
|
686
|
+
return this.#madeInPlace.get(`${fileName}
|
|
687
|
+
${name}`);
|
|
688
|
+
}
|
|
689
|
+
declare(facts) {
|
|
690
|
+
const factories = new Map;
|
|
691
|
+
const declaredFactories = [];
|
|
692
|
+
for (const file of facts)
|
|
693
|
+
for (const fact of file.guardFactories) {
|
|
694
|
+
const factory = {
|
|
695
|
+
fact,
|
|
696
|
+
owner: file.owner,
|
|
697
|
+
site: file,
|
|
698
|
+
made: [],
|
|
699
|
+
isReal: fact.direct,
|
|
700
|
+
scope: fact.scope,
|
|
701
|
+
targets: []
|
|
702
|
+
};
|
|
703
|
+
factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
|
|
704
|
+
declaredFactories.push(factory);
|
|
705
|
+
}
|
|
706
|
+
for (const factory of declaredFactories)
|
|
707
|
+
factory.targets = factory.fact.via.map((callee) => this.#factoryAt(factories, callee, factory.site)).filter((target) => target !== undefined);
|
|
708
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
709
|
+
isGrowing = false;
|
|
710
|
+
for (const factory of declaredFactories) {
|
|
711
|
+
const target = factory.targets.find((other) => other.isReal);
|
|
712
|
+
if (factory.isReal || !target)
|
|
713
|
+
continue;
|
|
714
|
+
factory.isReal = true;
|
|
715
|
+
factory.scope ??= target.scope;
|
|
716
|
+
isGrowing = true;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
const partsOf = (factory, seen = new Set) => {
|
|
720
|
+
if (seen.has(factory))
|
|
721
|
+
return [];
|
|
722
|
+
seen.add(factory);
|
|
723
|
+
return [
|
|
724
|
+
{ fileName: factory.site.fileName, extent: factory.fact.extent },
|
|
725
|
+
...factory.targets.filter((target) => target.isReal).flatMap((target) => partsOf(target, seen))
|
|
726
|
+
];
|
|
727
|
+
};
|
|
728
|
+
const declare = (file, made, scope, parts) => {
|
|
729
|
+
const { name, span, extent, doc } = made;
|
|
730
|
+
const label = file.guardLabels[name];
|
|
731
|
+
const own = { fileName: file.fileName, extent };
|
|
732
|
+
const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
|
|
733
|
+
if (name.includes("@"))
|
|
734
|
+
this.#madeInPlace.set(`${file.fileName}
|
|
735
|
+
${name}`, guard);
|
|
736
|
+
else
|
|
737
|
+
this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
|
|
738
|
+
return guard;
|
|
739
|
+
};
|
|
740
|
+
const extending = [];
|
|
741
|
+
for (const file of facts) {
|
|
742
|
+
for (const made of file.guards) {
|
|
743
|
+
const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
|
|
744
|
+
if (!made.factory)
|
|
745
|
+
declare(file, made, made.scope, []);
|
|
746
|
+
else if (factory?.isReal)
|
|
747
|
+
factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
|
|
748
|
+
}
|
|
749
|
+
for (const made of file.guardCalls) {
|
|
750
|
+
if (made.kind === "extends") {
|
|
751
|
+
extending.push([file, made]);
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
const factory = this.#factoryAt(factories, made.callee, file);
|
|
755
|
+
if (factory?.isReal)
|
|
756
|
+
factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
760
|
+
isGrowing = false;
|
|
761
|
+
for (const [idx, entry] of extending.entries()) {
|
|
762
|
+
const [file, made] = entry;
|
|
763
|
+
const found = this.#findGuard(made.callee, file, 0, true);
|
|
764
|
+
if (!("guard" in found))
|
|
765
|
+
continue;
|
|
766
|
+
declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
|
|
767
|
+
extending.splice(idx, 1);
|
|
768
|
+
isGrowing = true;
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
for (const { fact, made, isReal } of declaredFactories)
|
|
773
|
+
if (isReal && made.every((guard) => !guard.doc))
|
|
774
|
+
for (const guard of made)
|
|
775
|
+
guard.doc = fact.doc;
|
|
776
|
+
for (const list of this.#guards.values())
|
|
777
|
+
for (const guard of list)
|
|
778
|
+
if (list.some((other) => other !== guard && other.owner === guard.owner))
|
|
779
|
+
guard.file = this.inOwner(guard.span.fileName, guard.owner);
|
|
780
|
+
for (const guard of this.#madeInPlace.values())
|
|
781
|
+
guard.file = this.inOwner(guard.span.fileName, guard.owner);
|
|
782
|
+
return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
|
|
783
|
+
}
|
|
784
|
+
#factoryAt(factories, callee, site, depth = 0) {
|
|
785
|
+
const [head = callee, member] = callee.split(".");
|
|
786
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
787
|
+
if (!imported)
|
|
788
|
+
return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
|
|
789
|
+
if (member && imported.imported !== "*")
|
|
790
|
+
return;
|
|
791
|
+
const from = this.ownerOfSpecifier(site.fileName, imported.from);
|
|
792
|
+
if (from === undefined || from === frameworkOwner)
|
|
793
|
+
return;
|
|
794
|
+
const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
|
|
795
|
+
const candidates = factories.get(name) ?? [];
|
|
796
|
+
const near = this.pathOfSpecifier(site.fileName, imported.from);
|
|
797
|
+
const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
|
|
798
|
+
if (own)
|
|
799
|
+
return own;
|
|
800
|
+
const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
|
|
801
|
+
if (reexport)
|
|
802
|
+
return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
|
|
803
|
+
return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
|
|
804
|
+
}
|
|
805
|
+
#findGuard(local, site, depth, isBase = false) {
|
|
806
|
+
const segments = local.split(".");
|
|
807
|
+
const head = segments[0] ?? local;
|
|
808
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
809
|
+
const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
|
|
810
|
+
if (imported && from === undefined)
|
|
811
|
+
return { external: true };
|
|
812
|
+
const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
|
|
813
|
+
const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
|
|
814
|
+
const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
|
|
815
|
+
const holder = imported ? from ?? null : site.owner;
|
|
816
|
+
const [objectName, member] = path;
|
|
817
|
+
if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
|
|
818
|
+
const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
|
|
819
|
+
if (made)
|
|
820
|
+
return { guard: made };
|
|
821
|
+
for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
|
|
822
|
+
const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
|
|
823
|
+
if ("guard" in found)
|
|
824
|
+
return found;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const name = path[path.length - 1] ?? head;
|
|
828
|
+
if (from === frameworkOwner)
|
|
829
|
+
return { name, from };
|
|
830
|
+
const declared = this.#guards.get(name) ?? [];
|
|
831
|
+
const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
|
|
832
|
+
if (own)
|
|
833
|
+
return { guard: own };
|
|
834
|
+
if (!imported) {
|
|
835
|
+
const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
|
|
836
|
+
return visible ? { guard: visible } : { name, from };
|
|
837
|
+
}
|
|
838
|
+
const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
|
|
839
|
+
if (reexport)
|
|
840
|
+
return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
|
|
841
|
+
const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
|
|
842
|
+
return reached ? { guard: reached } : { name, from };
|
|
843
|
+
}
|
|
844
|
+
reexportOf(owner, name, near) {
|
|
845
|
+
const key = `${owner}
|
|
846
|
+
${name}
|
|
847
|
+
${near ?? ""}`;
|
|
848
|
+
const cached = this.#reexports.get(key);
|
|
849
|
+
if (cached !== undefined)
|
|
850
|
+
return cached;
|
|
851
|
+
let found = null;
|
|
852
|
+
const exporting = new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
|
|
853
|
+
for (const fileName of this.#filesOf(owner, name, near)) {
|
|
854
|
+
const text = this.#source.readText(fileName);
|
|
855
|
+
if (text === undefined || !exporting.test(text))
|
|
856
|
+
continue;
|
|
857
|
+
const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
|
|
858
|
+
if (!reexport)
|
|
859
|
+
continue;
|
|
860
|
+
const site = { fileName, owner, imports: [] };
|
|
861
|
+
found = reexport.from ? {
|
|
862
|
+
local: reexport.name,
|
|
863
|
+
site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
|
|
864
|
+
} : { local: reexport.name, site };
|
|
865
|
+
break;
|
|
866
|
+
}
|
|
867
|
+
this.#reexports.set(key, found);
|
|
868
|
+
return found;
|
|
869
|
+
}
|
|
870
|
+
#reexportIn(source, name) {
|
|
871
|
+
const { ts, reader } = this.#source;
|
|
872
|
+
for (const statement of source.statements) {
|
|
873
|
+
if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
|
|
874
|
+
continue;
|
|
875
|
+
const { exportClause, moduleSpecifier } = statement;
|
|
876
|
+
if (!exportClause || !ts.isNamedExports(exportClause))
|
|
877
|
+
continue;
|
|
878
|
+
const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
|
|
879
|
+
if (!element)
|
|
880
|
+
continue;
|
|
881
|
+
const original = (element.propertyName ?? element.name).text;
|
|
882
|
+
if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
|
|
883
|
+
return { name: original, from: moduleSpecifier.text };
|
|
884
|
+
const imported = reader.importsIn(source).find((item) => item.name === original);
|
|
885
|
+
if (!imported)
|
|
886
|
+
return { name: original, from: null };
|
|
887
|
+
return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
|
|
888
|
+
}
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
891
|
+
#filesOf(owner, name, near) {
|
|
892
|
+
const isNear = (fileName) => {
|
|
893
|
+
const relative = this.#source.relative(fileName);
|
|
894
|
+
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
895
|
+
};
|
|
896
|
+
const candidates = this.filesIn(owner).filter(([, words]) => words.includes(`
|
|
897
|
+
${name}
|
|
898
|
+
`)).map(([fileName]) => fileName);
|
|
899
|
+
return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
|
|
900
|
+
}
|
|
901
|
+
#mapEntriesOf(objectName, member, fileName, owner) {
|
|
902
|
+
const key = `${objectName}
|
|
903
|
+
${member}
|
|
904
|
+
${fileName ?? ""}
|
|
905
|
+
${owner ?? ""}`;
|
|
906
|
+
const cached = this.#mapEntries.get(key);
|
|
907
|
+
if (cached)
|
|
908
|
+
return cached;
|
|
909
|
+
const { ts, reader } = this.#source;
|
|
910
|
+
const entries = [];
|
|
911
|
+
const fileNames = [
|
|
912
|
+
...fileName ? [fileName] : [],
|
|
913
|
+
...(owner === null ? [] : this.filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
|
|
914
|
+
${objectName}
|
|
915
|
+
`) && words.includes(`
|
|
916
|
+
${member}
|
|
917
|
+
`)).map(([other]) => other)
|
|
918
|
+
];
|
|
919
|
+
for (const holder of fileNames) {
|
|
920
|
+
const text = this.#source.readText(holder);
|
|
921
|
+
if (text === undefined)
|
|
922
|
+
continue;
|
|
923
|
+
const source = this.#source.parse(holder, text);
|
|
924
|
+
const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
|
|
925
|
+
for (const statement of source.statements) {
|
|
926
|
+
if (!ts.isVariableStatement(statement))
|
|
927
|
+
continue;
|
|
928
|
+
const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
|
|
929
|
+
const object = map && reader.unwrap(map);
|
|
930
|
+
if (!object || !ts.isObjectLiteralExpression(object))
|
|
931
|
+
continue;
|
|
932
|
+
for (const property of object.properties) {
|
|
933
|
+
if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
|
|
934
|
+
entries.push({ name: member, site });
|
|
935
|
+
if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
|
|
936
|
+
continue;
|
|
937
|
+
const value = reader.unwrap(property.initializer);
|
|
938
|
+
if (ts.isIdentifier(value))
|
|
939
|
+
entries.push({ name: value.text, site });
|
|
940
|
+
else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
|
|
941
|
+
entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
this.#mapEntries.set(key, entries);
|
|
946
|
+
return entries;
|
|
947
|
+
}
|
|
948
|
+
declarationOf(owner, name, near, depth = 0) {
|
|
949
|
+
const key = `${owner}
|
|
950
|
+
${name}
|
|
951
|
+
${near ?? ""}`;
|
|
952
|
+
const cached = this.#declarations.get(key);
|
|
953
|
+
if (cached !== undefined)
|
|
954
|
+
return cached;
|
|
955
|
+
this.#declarations.set(key, null);
|
|
956
|
+
const declaring = declaringPatternOf(name, false);
|
|
957
|
+
let found = null;
|
|
958
|
+
for (const fileName of this.#filesOf(owner, name, near)) {
|
|
959
|
+
const text = this.#source.readText(fileName);
|
|
960
|
+
if (text === undefined || !declaring.test(text))
|
|
961
|
+
continue;
|
|
962
|
+
const source = this.#source.parse(fileName, text);
|
|
963
|
+
const declared = this.#routes.declarationIn(source, name);
|
|
964
|
+
if (declared) {
|
|
965
|
+
found = { source, ...declared, name };
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
const reexport = this.#reexportIn(source, name);
|
|
969
|
+
if (!reexport)
|
|
970
|
+
continue;
|
|
971
|
+
if (!reexport.from) {
|
|
972
|
+
const local = this.#routes.declarationIn(source, reexport.name);
|
|
973
|
+
found = local ? { source, ...local, name: reexport.name } : null;
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
const next = this.ownerOfSpecifier(fileName, reexport.from);
|
|
977
|
+
if (next && next !== frameworkOwner && depth < maxMapDepth)
|
|
978
|
+
found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
|
|
979
|
+
break;
|
|
980
|
+
}
|
|
981
|
+
this.#declarations.set(key, found);
|
|
982
|
+
return found;
|
|
983
|
+
}
|
|
984
|
+
#throughStar(declared, owner, name, near) {
|
|
985
|
+
if (!declared.length)
|
|
986
|
+
return;
|
|
987
|
+
const { owners, isOpaque } = this.#starExportsOf(owner, near);
|
|
988
|
+
const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
|
|
989
|
+
return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
|
|
990
|
+
}
|
|
991
|
+
#starExportsOf(owner, near) {
|
|
992
|
+
const cached = this.#starExports.get(near);
|
|
993
|
+
if (cached)
|
|
994
|
+
return cached;
|
|
995
|
+
const found = { owners: [], isOpaque: false };
|
|
996
|
+
this.#starExports.set(near, found);
|
|
997
|
+
const paths = [near];
|
|
998
|
+
for (let idx = 0;idx < paths.length && !found.isOpaque; idx++)
|
|
999
|
+
for (const fileName of this.#modulesAt(paths[idx] ?? "")) {
|
|
1000
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
|
|
1001
|
+
const target = this.ownerOfSpecifier(fileName, specifier);
|
|
1002
|
+
const path = this.pathOfSpecifier(fileName, specifier);
|
|
1003
|
+
if (target === undefined || target === frameworkOwner || path === null)
|
|
1004
|
+
found.isOpaque = true;
|
|
1005
|
+
else if (!paths.includes(path))
|
|
1006
|
+
paths.push(path);
|
|
1007
|
+
if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
|
|
1008
|
+
found.owners.push(target);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return found;
|
|
1012
|
+
}
|
|
1013
|
+
#modulesAt(path) {
|
|
1014
|
+
if (!this.#modules) {
|
|
1015
|
+
this.#modules = new Map;
|
|
1016
|
+
for (const [fileName] of this.#source.files) {
|
|
1017
|
+
const module = this.#source.relative(fileName).replace(/\.tsx?$/, "");
|
|
1018
|
+
for (const key of module.endsWith("/index") ? [module, module.slice(0, -6)] : [module])
|
|
1019
|
+
this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
return this.#modules.get(path) ?? [];
|
|
1023
|
+
}
|
|
1024
|
+
declares(owner, name) {
|
|
1025
|
+
const declaring = declaringPatternOf(name, true);
|
|
1026
|
+
return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
|
|
1027
|
+
}
|
|
1028
|
+
filesIn(owner) {
|
|
1029
|
+
if (!this.#ownerFiles) {
|
|
1030
|
+
this.#ownerFiles = new Map;
|
|
1031
|
+
for (const entry of this.#source.files) {
|
|
1032
|
+
const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
|
|
1033
|
+
const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
|
|
1034
|
+
if (files)
|
|
1035
|
+
files.push(entry);
|
|
1036
|
+
else if (fileOwner !== null)
|
|
1037
|
+
this.#ownerFiles.set(fileOwner, [entry]);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return this.#ownerFiles.get(owner) ?? [];
|
|
1041
|
+
}
|
|
1042
|
+
#nearest(items, fileOf, near) {
|
|
1043
|
+
const isNear = (fileName) => {
|
|
1044
|
+
const relative = this.#source.relative(fileName);
|
|
1045
|
+
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
1046
|
+
};
|
|
1047
|
+
return items.find((item) => isNear(fileOf(item))) ?? items[0];
|
|
1048
|
+
}
|
|
1049
|
+
inOwner(fileName, owner) {
|
|
1050
|
+
const relative = this.#source.relative(fileName);
|
|
1051
|
+
return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
|
|
1052
|
+
}
|
|
1053
|
+
ownerOfSpecifier(fileName, specifier) {
|
|
1054
|
+
if (frameworkPattern.test(specifier))
|
|
1055
|
+
return frameworkOwner;
|
|
1056
|
+
const path = this.pathOfSpecifier(fileName, specifier);
|
|
1057
|
+
return path === null ? undefined : ownerOf(path) ?? undefined;
|
|
1058
|
+
}
|
|
1059
|
+
pathOfSpecifier(fileName, specifier) {
|
|
1060
|
+
const alias = specifier.match(aliasPattern);
|
|
1061
|
+
if (alias)
|
|
1062
|
+
return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
|
|
1063
|
+
if (!specifier.startsWith("."))
|
|
1064
|
+
return null;
|
|
1065
|
+
const segments = this.#source.relative(fileName).split("/").slice(0, -1);
|
|
1066
|
+
for (const segment of specifier.split("/"))
|
|
1067
|
+
if (segment === "..")
|
|
1068
|
+
segments.pop();
|
|
1069
|
+
else if (segment !== ".")
|
|
1070
|
+
segments.push(segment);
|
|
1071
|
+
return segments.join("/");
|
|
1072
|
+
}
|
|
1073
|
+
visible(target, from) {
|
|
1074
|
+
return !from || !target || target === from || target.startsWith("libs/");
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
515
1077
|
|
|
516
1078
|
// pkgs/akanjs/common/trimSlashes.ts
|
|
517
1079
|
var trimTrailingSlashes = (value) => {
|
|
@@ -660,24 +1222,23 @@ rootLayout
|
|
|
660
1222
|
}
|
|
661
1223
|
declarationIn(source, name) {
|
|
662
1224
|
const ts = this.#ts;
|
|
1225
|
+
const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
|
|
1226
|
+
const [first] = overloads;
|
|
1227
|
+
const last = overloads.at(-1);
|
|
1228
|
+
if (first?.name && last)
|
|
1229
|
+
return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
|
|
663
1230
|
for (const statement of source.statements) {
|
|
664
|
-
if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
|
|
665
|
-
return { statement, declared: statement.name, initializer: null };
|
|
666
1231
|
if (!ts.isVariableStatement(statement))
|
|
667
1232
|
continue;
|
|
668
1233
|
const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
|
|
669
1234
|
if (declaration)
|
|
670
|
-
return { statement, declared: declaration.name,
|
|
1235
|
+
return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
|
|
671
1236
|
}
|
|
672
1237
|
return null;
|
|
673
1238
|
}
|
|
674
1239
|
#guardOf(source, exported) {
|
|
675
1240
|
const ts = this.#ts;
|
|
676
1241
|
let current = ts.isExportAssignment(exported) ? this.#reader.unwrap(exported.expression) : null;
|
|
677
|
-
if (current && ts.isIdentifier(current)) {
|
|
678
|
-
const initializer = this.declarationIn(source, current.text)?.initializer;
|
|
679
|
-
current = initializer ? this.#reader.unwrap(initializer) : null;
|
|
680
|
-
}
|
|
681
1242
|
while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
682
1243
|
const { name, expression } = current.expression;
|
|
683
1244
|
const [stage] = current.arguments;
|
|
@@ -689,9 +1250,20 @@ rootLayout
|
|
|
689
1250
|
}
|
|
690
1251
|
#callsIn(source, stage) {
|
|
691
1252
|
const ts = this.#ts;
|
|
692
|
-
const
|
|
1253
|
+
const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
|
|
1254
|
+
const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
|
|
1255
|
+
const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
|
|
693
1256
|
const calls = new Map;
|
|
694
|
-
const callOf = (
|
|
1257
|
+
const callOf = (callee, shadowed) => {
|
|
1258
|
+
if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
|
|
1259
|
+
const name = `${callee.expression.text}.${callee.name.text}`;
|
|
1260
|
+
if (!calls.has(name))
|
|
1261
|
+
calls.set(name, { name, local: null });
|
|
1262
|
+
return true;
|
|
1263
|
+
}
|
|
1264
|
+
if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
|
|
1265
|
+
return false;
|
|
1266
|
+
const name = callee.text;
|
|
695
1267
|
if (calls.has(name))
|
|
696
1268
|
return true;
|
|
697
1269
|
const declared = this.declarationIn(source, name);
|
|
@@ -701,23 +1273,71 @@ rootLayout
|
|
|
701
1273
|
name,
|
|
702
1274
|
local: declared && {
|
|
703
1275
|
span: this.#reader.spanOf(source, declared.declared),
|
|
704
|
-
extent:
|
|
1276
|
+
extent: declared.extent,
|
|
705
1277
|
doc: this.#reader.docOf(source, declared.statement)
|
|
706
1278
|
}
|
|
707
1279
|
});
|
|
708
1280
|
return true;
|
|
709
1281
|
};
|
|
710
|
-
const visit = (node) => {
|
|
711
|
-
|
|
1282
|
+
const visit = (node, outer) => {
|
|
1283
|
+
const shadowed = this.#scopeOf(node, outer);
|
|
1284
|
+
if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
|
|
712
1285
|
return;
|
|
713
|
-
ts.forEachChild(node, visit);
|
|
1286
|
+
ts.forEachChild(node, (child) => visit(child, shadowed));
|
|
714
1287
|
};
|
|
715
1288
|
if (ts.isIdentifier(stage))
|
|
716
|
-
callOf(stage
|
|
1289
|
+
callOf(stage, new Set);
|
|
717
1290
|
else
|
|
718
|
-
visit(stage);
|
|
1291
|
+
visit(stage, new Set);
|
|
719
1292
|
return [...calls.values()];
|
|
720
1293
|
}
|
|
1294
|
+
#scopeOf(node, outer) {
|
|
1295
|
+
const ts = this.#ts;
|
|
1296
|
+
const names = [];
|
|
1297
|
+
const bind = (name) => {
|
|
1298
|
+
if (ts.isIdentifier(name))
|
|
1299
|
+
names.push(name.text);
|
|
1300
|
+
else
|
|
1301
|
+
for (const element of name.elements)
|
|
1302
|
+
if (!ts.isOmittedExpression(element))
|
|
1303
|
+
bind(element.name);
|
|
1304
|
+
};
|
|
1305
|
+
const declare = (statement) => {
|
|
1306
|
+
if (ts.isVariableStatement(statement))
|
|
1307
|
+
for (const declaration of statement.declarationList.declarations)
|
|
1308
|
+
bind(declaration.name);
|
|
1309
|
+
else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
|
|
1310
|
+
names.push(statement.name.text);
|
|
1311
|
+
};
|
|
1312
|
+
const hoist = (child) => {
|
|
1313
|
+
if (ts.isFunctionLike(child) || ts.isClassLike(child))
|
|
1314
|
+
return;
|
|
1315
|
+
if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
|
|
1316
|
+
for (const declaration of child.declarations)
|
|
1317
|
+
bind(declaration.name);
|
|
1318
|
+
ts.forEachChild(child, hoist);
|
|
1319
|
+
};
|
|
1320
|
+
if (ts.isFunctionLike(node)) {
|
|
1321
|
+
for (const parameter of node.parameters)
|
|
1322
|
+
bind(parameter.name);
|
|
1323
|
+
if (ts.isFunctionExpression(node) && node.name)
|
|
1324
|
+
names.push(node.name.text);
|
|
1325
|
+
if ("body" in node && node.body)
|
|
1326
|
+
ts.forEachChild(node.body, hoist);
|
|
1327
|
+
} else if (ts.isClassExpression(node) && node.name)
|
|
1328
|
+
names.push(node.name.text);
|
|
1329
|
+
else if (ts.isBlock(node))
|
|
1330
|
+
node.statements.forEach(declare);
|
|
1331
|
+
else if (ts.isCaseBlock(node))
|
|
1332
|
+
for (const clause of node.clauses)
|
|
1333
|
+
clause.statements.forEach(declare);
|
|
1334
|
+
else if (ts.isCatchClause(node) && node.variableDeclaration)
|
|
1335
|
+
bind(node.variableDeclaration.name);
|
|
1336
|
+
else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
|
|
1337
|
+
for (const declaration of node.initializer.declarations)
|
|
1338
|
+
bind(declaration.name);
|
|
1339
|
+
return names.length ? new Set([...outer, ...names]) : outer;
|
|
1340
|
+
}
|
|
721
1341
|
}
|
|
722
1342
|
|
|
723
1343
|
// pkgs/@akanjs/language/akanGraph.ts
|
|
@@ -733,10 +1353,6 @@ var builtinGuards = {
|
|
|
733
1353
|
Public: { scope: "account", doc: "Passes every caller, signed in or not." },
|
|
734
1354
|
None: { scope: "account", doc: "Refuses every caller." }
|
|
735
1355
|
};
|
|
736
|
-
var frameworkOwner = "akanjs";
|
|
737
|
-
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
738
|
-
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
739
|
-
var maxMapDepth = 4;
|
|
740
1356
|
|
|
741
1357
|
class GraphBuilder {
|
|
742
1358
|
#source;
|
|
@@ -744,16 +1360,16 @@ class GraphBuilder {
|
|
|
744
1360
|
#edges = new Map;
|
|
745
1361
|
#contexts = new Map;
|
|
746
1362
|
#members = new Map;
|
|
747
|
-
#guards = new Map;
|
|
748
|
-
#mapEntries = new Map;
|
|
749
1363
|
#routeFiles = new Map;
|
|
750
|
-
#declarations = new Map;
|
|
751
1364
|
#routes;
|
|
1365
|
+
#resolver;
|
|
752
1366
|
#adaptors = new Map;
|
|
753
1367
|
#plugs = new Map;
|
|
1368
|
+
#serviceFields = new Set;
|
|
754
1369
|
constructor(source) {
|
|
755
1370
|
this.#source = source;
|
|
756
1371
|
this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
|
|
1372
|
+
this.#resolver = new GuardResolver(source, this.#routes);
|
|
757
1373
|
}
|
|
758
1374
|
build() {
|
|
759
1375
|
const { facts } = this.#source;
|
|
@@ -765,17 +1381,28 @@ class GraphBuilder {
|
|
|
765
1381
|
...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
|
|
766
1382
|
];
|
|
767
1383
|
const typed = [];
|
|
1384
|
+
const adaptorCounts = new Map;
|
|
768
1385
|
for (const file of facts)
|
|
769
1386
|
for (const fact of file.adaptors) {
|
|
770
|
-
const
|
|
771
|
-
|
|
772
|
-
this.#adaptorNodeOf(declared);
|
|
1387
|
+
const key = this.#key("adaptor", file.owner, fact.name);
|
|
1388
|
+
adaptorCounts.set(key, (adaptorCounts.get(key) ?? 0) + 1);
|
|
773
1389
|
}
|
|
1390
|
+
const declaredAdaptors = facts.flatMap((file) => file.adaptors.map((fact) => {
|
|
1391
|
+
const key = this.#key("adaptor", file.owner, fact.name);
|
|
1392
|
+
const id = (adaptorCounts.get(key) ?? 0) > 1 ? this.#key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
|
|
1393
|
+
const declared = { fact, owner: file.owner, fileName: file.fileName, id };
|
|
1394
|
+
this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
|
|
1395
|
+
this.#adaptorNodeOf(declared);
|
|
1396
|
+
return { declared, file };
|
|
1397
|
+
}));
|
|
1398
|
+
for (const { declared, file } of declaredAdaptors)
|
|
1399
|
+
for (const plug of declared.fact.plugs)
|
|
1400
|
+
this.#addEdge(declared.id, this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
|
|
774
1401
|
for (const file of facts)
|
|
775
|
-
for (const
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
1402
|
+
for (const binding of file.adaptorBindings)
|
|
1403
|
+
this.#addEdge(this.#adaptorIdOf(binding.role, file), this.#adaptorIdOf(binding.adaptor, file), "binds", file.owner, this.#location(binding.span));
|
|
1404
|
+
for (const guard of this.#resolver.declare(facts))
|
|
1405
|
+
this.#guardNodeOf(guard);
|
|
779
1406
|
for (const file of facts) {
|
|
780
1407
|
const { owner } = file;
|
|
781
1408
|
const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
|
|
@@ -806,10 +1433,18 @@ class GraphBuilder {
|
|
|
806
1433
|
}
|
|
807
1434
|
for (const method of service.methods)
|
|
808
1435
|
this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
|
|
809
|
-
for (const plug of service.plugs)
|
|
810
|
-
|
|
1436
|
+
for (const plug of service.plugs) {
|
|
1437
|
+
const key = `${refName}
|
|
1438
|
+
${plug.name}`;
|
|
1439
|
+
this.#plugs.set(key, [
|
|
1440
|
+
...this.#plugs.get(key) ?? [],
|
|
1441
|
+
{ owner, target: this.#adaptorIdOf(plug.target, file) }
|
|
1442
|
+
]);
|
|
1443
|
+
}
|
|
1444
|
+
for (const field of service.fields)
|
|
1445
|
+
this.#serviceFields.add(`${owner ?? ""}
|
|
811
1446
|
${refName}
|
|
812
|
-
${
|
|
1447
|
+
${field}`);
|
|
813
1448
|
}
|
|
814
1449
|
for (const filter of file.filters) {
|
|
815
1450
|
if (!filter.name.endsWith("Filter"))
|
|
@@ -819,31 +1454,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
|
819
1454
|
for (const query of filter.queries)
|
|
820
1455
|
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);
|
|
821
1456
|
}
|
|
822
|
-
|
|
823
|
-
const refName = slice.model;
|
|
824
|
-
if (!refName)
|
|
825
|
-
continue;
|
|
826
|
-
const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards);
|
|
827
|
-
for (const guard of slice.guards)
|
|
828
|
-
this.#guard(fallback, guard, file);
|
|
829
|
-
for (const key of slice.keys) {
|
|
830
|
-
const detail = [...key.guards, ...key.live ? ["live"] : []];
|
|
831
|
-
const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
832
|
-
for (const guard of key.guards)
|
|
833
|
-
this.#guard(node, guard, file);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
for (const endpoint of file.endpoints) {
|
|
837
|
-
const refName = endpoint.model;
|
|
838
|
-
if (!refName)
|
|
839
|
-
continue;
|
|
840
|
-
for (const key of endpoint.keys) {
|
|
841
|
-
const detail = [...key.verb ? [key.verb] : [], ...key.guards];
|
|
842
|
-
const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
843
|
-
for (const guard of key.guards)
|
|
844
|
-
this.#guard(node, guard, file);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
1457
|
+
this.#addGuarded(file);
|
|
847
1458
|
for (const internal of file.internals)
|
|
848
1459
|
for (const key of internal.keys)
|
|
849
1460
|
if (internal.model && key.type !== "resolveField")
|
|
@@ -859,7 +1470,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
|
859
1470
|
types.set(node.label, [...types.get(node.label) ?? [], node]);
|
|
860
1471
|
for (const node of typed)
|
|
861
1472
|
for (const field of node.fields ?? []) {
|
|
862
|
-
const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
|
|
1473
|
+
const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
|
|
863
1474
|
field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
|
|
864
1475
|
if (field.ref)
|
|
865
1476
|
this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
|
|
@@ -941,61 +1552,52 @@ ${label ?? ""}`;
|
|
|
941
1552
|
else
|
|
942
1553
|
this.#edges.set(key, { from, to, kind, label, count: 1, at });
|
|
943
1554
|
}
|
|
944
|
-
#
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
for (const
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
};
|
|
962
|
-
const madeBy = (factory, fileName, extent) => {
|
|
963
|
-
const parts = [{ fileName, extent }];
|
|
964
|
-
const seen = new Set;
|
|
965
|
-
for (let current = factory;current && !seen.has(current); current = sameFile(current, current.fact.via)) {
|
|
966
|
-
seen.add(current);
|
|
967
|
-
parts.push({ fileName: current.fileName, extent: current.fact.extent });
|
|
968
|
-
}
|
|
969
|
-
return parts;
|
|
970
|
-
};
|
|
971
|
-
for (const file of facts) {
|
|
972
|
-
const { owner, fileName } = file;
|
|
973
|
-
for (const { name, span, extent, scope, doc, factory: callee } of file.guards) {
|
|
974
|
-
const factory = callee ? factories.get(callee)?.find((other) => other.fileName === fileName) : undefined;
|
|
975
|
-
declare({ name, owner, span, scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
|
|
1555
|
+
#addGuarded(file) {
|
|
1556
|
+
const { owner } = file;
|
|
1557
|
+
for (const slice of file.slices) {
|
|
1558
|
+
const refName = slice.model;
|
|
1559
|
+
if (!refName)
|
|
1560
|
+
continue;
|
|
1561
|
+
const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
|
|
1562
|
+
for (const guard of slice.guards)
|
|
1563
|
+
this.#guard(fallback, guard, file);
|
|
1564
|
+
for (const key of slice.keys) {
|
|
1565
|
+
const detail = [
|
|
1566
|
+
...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
|
|
1567
|
+
...key.live ? ["live"] : []
|
|
1568
|
+
];
|
|
1569
|
+
const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
1570
|
+
for (const guard of key.guards)
|
|
1571
|
+
this.#guard(node, guard, file);
|
|
976
1572
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
|
|
1573
|
+
}
|
|
1574
|
+
for (const endpoint of file.endpoints) {
|
|
1575
|
+
const refName = endpoint.model;
|
|
1576
|
+
if (!refName)
|
|
1577
|
+
continue;
|
|
1578
|
+
for (const key of endpoint.keys) {
|
|
1579
|
+
const detail = [
|
|
1580
|
+
...key.verb ? [key.verb] : [],
|
|
1581
|
+
...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
|
|
1582
|
+
];
|
|
1583
|
+
const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
1584
|
+
for (const guard of key.guards)
|
|
1585
|
+
this.#guard(node, guard, file);
|
|
986
1586
|
}
|
|
987
1587
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1588
|
+
}
|
|
1589
|
+
#guard(node, guard, file) {
|
|
1590
|
+
const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
|
|
1591
|
+
if (!local || local === "?")
|
|
1592
|
+
return;
|
|
1593
|
+
const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
|
|
1594
|
+
if (id)
|
|
1595
|
+
this.#addEdge(node.id, id, "guard", role, node.at);
|
|
996
1596
|
}
|
|
997
1597
|
#guardIdOf(local, site) {
|
|
998
|
-
const found = this.#
|
|
1598
|
+
const found = this.#resolver.find(local, site);
|
|
1599
|
+
if ("external" in found)
|
|
1600
|
+
return null;
|
|
999
1601
|
if ("guard" in found)
|
|
1000
1602
|
return this.#guardNodeOf(found.guard);
|
|
1001
1603
|
const { name, from } = found;
|
|
@@ -1008,99 +1610,18 @@ ${label ?? ""}`;
|
|
|
1008
1610
|
node.doc = builtin.doc;
|
|
1009
1611
|
return node.id;
|
|
1010
1612
|
}
|
|
1011
|
-
#
|
|
1012
|
-
const
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
const
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
}
|
|
1024
|
-
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1025
|
-
const declared = this.#guards.get(name) ?? [];
|
|
1026
|
-
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));
|
|
1027
|
-
return guard ? { guard } : { name, from };
|
|
1028
|
-
}
|
|
1029
|
-
#mapEntriesOf(objectName, member, fileName, owner) {
|
|
1030
|
-
const key = `${objectName}
|
|
1031
|
-
${member}
|
|
1032
|
-
${fileName ?? ""}
|
|
1033
|
-
${owner ?? ""}`;
|
|
1034
|
-
const cached = this.#mapEntries.get(key);
|
|
1035
|
-
if (cached)
|
|
1036
|
-
return cached;
|
|
1037
|
-
const { ts, reader } = this.#source;
|
|
1038
|
-
const entries = [];
|
|
1039
|
-
const fileNames = [
|
|
1040
|
-
...fileName ? [fileName] : [],
|
|
1041
|
-
...this.#source.files.filter(([other, words]) => other !== fileName && owner !== null && !testFilePattern.test(other) && words.includes(`
|
|
1042
|
-
${objectName}
|
|
1043
|
-
`) && words.includes(`
|
|
1044
|
-
${member}
|
|
1045
|
-
`) && this.#source.ownerOf(other) === owner).map(([other]) => other)
|
|
1046
|
-
];
|
|
1047
|
-
for (const holder of fileNames) {
|
|
1048
|
-
const text = this.#source.readText(holder);
|
|
1049
|
-
if (text === undefined)
|
|
1050
|
-
continue;
|
|
1051
|
-
const source = this.#source.parse(holder, text);
|
|
1052
|
-
const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
|
|
1053
|
-
for (const statement of source.statements) {
|
|
1054
|
-
if (!ts.isVariableStatement(statement))
|
|
1055
|
-
continue;
|
|
1056
|
-
const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
|
|
1057
|
-
const object = map && reader.unwrap(map);
|
|
1058
|
-
if (!object || !ts.isObjectLiteralExpression(object))
|
|
1059
|
-
continue;
|
|
1060
|
-
for (const property of object.properties) {
|
|
1061
|
-
if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
|
|
1062
|
-
entries.push({ name: member, site });
|
|
1063
|
-
if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
|
|
1064
|
-
continue;
|
|
1065
|
-
const value = reader.unwrap(property.initializer);
|
|
1066
|
-
if (ts.isIdentifier(value))
|
|
1067
|
-
entries.push({ name: value.text, site });
|
|
1068
|
-
else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
|
|
1069
|
-
entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
this.#mapEntries.set(key, entries);
|
|
1074
|
-
return entries;
|
|
1075
|
-
}
|
|
1076
|
-
#declarationOf(owner, name, near) {
|
|
1077
|
-
const key = `${owner}
|
|
1078
|
-
${name}
|
|
1079
|
-
${near ?? ""}`;
|
|
1080
|
-
const cached = this.#declarations.get(key);
|
|
1081
|
-
if (cached !== undefined)
|
|
1082
|
-
return cached;
|
|
1083
|
-
const isNear = (fileName) => {
|
|
1084
|
-
const relative = this.#source.relative(fileName);
|
|
1085
|
-
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
1086
|
-
};
|
|
1087
|
-
const candidates = this.#source.files.filter(([fileName, words]) => !testFilePattern.test(fileName) && words.includes(`
|
|
1088
|
-
${name}
|
|
1089
|
-
`) && this.#source.ownerOf(fileName) === owner).map(([fileName]) => fileName);
|
|
1090
|
-
let found = null;
|
|
1091
|
-
for (const fileName of [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))]) {
|
|
1092
|
-
const text = this.#source.readText(fileName);
|
|
1093
|
-
if (text === undefined)
|
|
1094
|
-
continue;
|
|
1095
|
-
const source = this.#source.parse(fileName, text);
|
|
1096
|
-
const declared = this.#routes.declarationIn(source, name);
|
|
1097
|
-
if (!declared)
|
|
1098
|
-
continue;
|
|
1099
|
-
found = { source, statement: declared.statement, declared: declared.declared };
|
|
1100
|
-
break;
|
|
1101
|
-
}
|
|
1102
|
-
this.#declarations.set(key, found);
|
|
1103
|
-
return found;
|
|
1613
|
+
#madeInPlaceIdOf(name, file) {
|
|
1614
|
+
const made = this.#resolver.madeInPlace(file.fileName, name);
|
|
1615
|
+
if (made)
|
|
1616
|
+
return this.#guardNodeOf(made);
|
|
1617
|
+
const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
|
|
1618
|
+
const imported = file.imports.find((item) => item.name === head);
|
|
1619
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
|
|
1620
|
+
if (from === undefined || from === frameworkOwner)
|
|
1621
|
+
return null;
|
|
1622
|
+
const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
|
|
1623
|
+
const label = guardLabelOf(name, file.guardLabels);
|
|
1624
|
+
return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
|
|
1104
1625
|
}
|
|
1105
1626
|
#addRoutes() {
|
|
1106
1627
|
const routes = [];
|
|
@@ -1153,22 +1674,23 @@ ${dir}`, node);
|
|
|
1153
1674
|
}
|
|
1154
1675
|
#routeGuardIdOf({ name: local, local: declared }, fact, text) {
|
|
1155
1676
|
if (declared) {
|
|
1156
|
-
const
|
|
1157
|
-
const id = this.#key("guard", fact.owner, `${file}#${local}`);
|
|
1677
|
+
const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
|
|
1158
1678
|
return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
|
|
1159
1679
|
}
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1680
|
+
const [head = local, member] = local.split(".");
|
|
1681
|
+
const imported = fact.imports.find((item) => item.name === head);
|
|
1682
|
+
if (!imported || member && imported.imported !== "*")
|
|
1162
1683
|
return null;
|
|
1163
|
-
const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
|
|
1164
|
-
const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
|
|
1684
|
+
const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
|
|
1685
|
+
const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
|
|
1165
1686
|
if (!from || from === frameworkOwner)
|
|
1166
1687
|
return null;
|
|
1167
|
-
const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
|
|
1688
|
+
const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
|
|
1168
1689
|
if (!found)
|
|
1169
1690
|
return null;
|
|
1170
1691
|
const { reader } = this.#source;
|
|
1171
|
-
|
|
1692
|
+
const owner = this.#source.ownerOf(found.source.fileName) ?? from;
|
|
1693
|
+
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);
|
|
1172
1694
|
}
|
|
1173
1695
|
#routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
|
|
1174
1696
|
const isNew = !this.#nodes.has(id);
|
|
@@ -1181,18 +1703,70 @@ ${dir}`, node);
|
|
|
1181
1703
|
#adaptorIdOf(local, file) {
|
|
1182
1704
|
const [head, member] = local.split(".");
|
|
1183
1705
|
const imported = file.imports.find((item) => item.name === head);
|
|
1184
|
-
const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1185
1706
|
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1707
|
+
const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
|
|
1708
|
+
if (imported && path === null && !frameworkPattern.test(imported.from))
|
|
1709
|
+
return this.#packageAdaptorOf(name, imported.from);
|
|
1710
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1186
1711
|
const declared = this.#adaptors.get(name) ?? [];
|
|
1187
|
-
const found = from === undefined ? declared
|
|
1712
|
+
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)));
|
|
1188
1713
|
if (found)
|
|
1189
|
-
return
|
|
1714
|
+
return found.id;
|
|
1715
|
+
const reexported = from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
|
|
1716
|
+
if (reexported)
|
|
1717
|
+
return this.#packageAdaptorOf(reexported.name, reexported.from);
|
|
1190
1718
|
const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
|
|
1191
1719
|
const detail = from === frameworkOwner ? [frameworkOwner] : [];
|
|
1192
1720
|
return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
|
|
1193
1721
|
}
|
|
1194
|
-
#
|
|
1195
|
-
const
|
|
1722
|
+
#adaptorIn(declared, owner, path, fileName) {
|
|
1723
|
+
const own = declared.filter((adaptor) => adaptor.owner === owner);
|
|
1724
|
+
if (own.length < 2)
|
|
1725
|
+
return own.at(0);
|
|
1726
|
+
const pathOf = (adaptor) => this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
|
|
1727
|
+
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}/`));
|
|
1728
|
+
return named ?? own.at(0);
|
|
1729
|
+
}
|
|
1730
|
+
#pluggedOf({ owner, module }, field) {
|
|
1731
|
+
const plugs = this.#plugs.get(`${module}
|
|
1732
|
+
${field}`) ?? [];
|
|
1733
|
+
const own = plugs.filter((plug) => plug.owner === owner);
|
|
1734
|
+
if (!own.length && this.#serviceFields.has(`${owner ?? ""}
|
|
1735
|
+
${module}
|
|
1736
|
+
${field}`))
|
|
1737
|
+
return [];
|
|
1738
|
+
const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
|
|
1739
|
+
return [...new Set(inherited.map(({ target }) => target))];
|
|
1740
|
+
}
|
|
1741
|
+
#packageAdaptorOf(name, specifier) {
|
|
1742
|
+
if (frameworkPattern.test(specifier))
|
|
1743
|
+
return this.#addNode(this.#key("adaptor", null, name), "adaptor", null, null, name, null, [frameworkOwner]).id;
|
|
1744
|
+
const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
|
1745
|
+
return this.#addNode(this.#key("adaptor", pkg, name), "adaptor", null, null, name, null, [pkg]).id;
|
|
1746
|
+
}
|
|
1747
|
+
#packageShimOf(owner, name, near) {
|
|
1748
|
+
const reexport = this.#resolver.reexportOf(owner, name, near);
|
|
1749
|
+
const [shim] = reexport?.site.imports ?? [];
|
|
1750
|
+
if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
|
|
1751
|
+
return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
|
|
1752
|
+
if (reexport || !near)
|
|
1753
|
+
return null;
|
|
1754
|
+
const packages = new Set;
|
|
1755
|
+
for (const [fileName, words] of this.#resolver.filesIn(owner)) {
|
|
1756
|
+
const relative = this.#source.relative(fileName);
|
|
1757
|
+
const isNear = relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near;
|
|
1758
|
+
if (!isNear || !words.includes(`
|
|
1759
|
+
export
|
|
1760
|
+
`))
|
|
1761
|
+
continue;
|
|
1762
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
|
|
1763
|
+
if (this.#resolver.pathOfSpecifier(fileName, specifier) === null)
|
|
1764
|
+
packages.add(specifier);
|
|
1765
|
+
}
|
|
1766
|
+
const [only] = packages;
|
|
1767
|
+
return packages.size === 1 && only && !this.#resolver.declares(owner, name) ? { name, from: only } : null;
|
|
1768
|
+
}
|
|
1769
|
+
#adaptorNodeOf({ fact, owner, fileName, id }) {
|
|
1196
1770
|
const detail = fact.refName ? [fact.refName] : [];
|
|
1197
1771
|
const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
|
|
1198
1772
|
if (fact.doc && !node.doc)
|
|
@@ -1200,38 +1774,22 @@ ${dir}`, node);
|
|
|
1200
1774
|
this.#digest(node, this.#source.readText(fileName), fact.extent);
|
|
1201
1775
|
return node.id;
|
|
1202
1776
|
}
|
|
1203
|
-
#guardNodeOf({ name, owner, span, scope, doc, parts }) {
|
|
1204
|
-
const
|
|
1777
|
+
#guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
|
|
1778
|
+
const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
|
|
1779
|
+
const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
|
|
1205
1780
|
if (doc && !node.doc)
|
|
1206
1781
|
node.doc = doc;
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1782
|
+
if (node.digest)
|
|
1783
|
+
return node.id;
|
|
1784
|
+
const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
|
|
1785
|
+
if (!digest)
|
|
1786
|
+
return node.id;
|
|
1787
|
+
node.digest = digest;
|
|
1788
|
+
const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
|
|
1789
|
+
if (sources.length)
|
|
1790
|
+
node.sources = sources;
|
|
1213
1791
|
return node.id;
|
|
1214
1792
|
}
|
|
1215
|
-
#ownerOfSpecifier(fileName, specifier) {
|
|
1216
|
-
if (frameworkPattern.test(specifier))
|
|
1217
|
-
return frameworkOwner;
|
|
1218
|
-
const path = this.#pathOfSpecifier(fileName, specifier);
|
|
1219
|
-
return path === null ? undefined : ownerOf(path) ?? undefined;
|
|
1220
|
-
}
|
|
1221
|
-
#pathOfSpecifier(fileName, specifier) {
|
|
1222
|
-
const alias = specifier.match(aliasPattern);
|
|
1223
|
-
if (alias)
|
|
1224
|
-
return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
|
|
1225
|
-
if (!specifier.startsWith("."))
|
|
1226
|
-
return null;
|
|
1227
|
-
const segments = this.#source.relative(fileName).split("/").slice(0, -1);
|
|
1228
|
-
for (const segment of specifier.split("/"))
|
|
1229
|
-
if (segment === "..")
|
|
1230
|
-
segments.pop();
|
|
1231
|
-
else if (segment !== ".")
|
|
1232
|
-
segments.push(segment);
|
|
1233
|
-
return segments.join("/");
|
|
1234
|
-
}
|
|
1235
1793
|
#moduleDirOf(fileName) {
|
|
1236
1794
|
const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
|
|
1237
1795
|
return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
|
|
@@ -1267,20 +1825,13 @@ ${dir}`, node);
|
|
|
1267
1825
|
visit(constant);
|
|
1268
1826
|
return [...fields.values()];
|
|
1269
1827
|
}
|
|
1270
|
-
#visible(target, from) {
|
|
1271
|
-
return !from || !target || target === from || target.startsWith("libs/");
|
|
1272
|
-
}
|
|
1273
|
-
#soleVisible(declared, from) {
|
|
1274
|
-
const visible = declared.filter((other) => this.#visible(other.owner, from));
|
|
1275
|
-
return visible.length === 1 ? visible[0] : undefined;
|
|
1276
|
-
}
|
|
1277
1828
|
#targetNodeOf({ family, owner, refName, sourceKey }) {
|
|
1278
1829
|
const isDefault = /^[A-Z]/.test(sourceKey);
|
|
1279
1830
|
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;
|
|
1280
1831
|
return id && this.#nodes.has(id) ? id : null;
|
|
1281
1832
|
}
|
|
1282
1833
|
#membersNamed(kind, name, owner, refName) {
|
|
1283
|
-
const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
|
|
1834
|
+
const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
|
|
1284
1835
|
const own = nodes.filter((node) => node.owner === owner);
|
|
1285
1836
|
return (own.length ? own : nodes).map((node) => node.id);
|
|
1286
1837
|
}
|
|
@@ -1325,10 +1876,8 @@ st
|
|
|
1325
1876
|
visit(source);
|
|
1326
1877
|
}
|
|
1327
1878
|
#link(fileName, source, identifier, chain, context, owner) {
|
|
1328
|
-
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#
|
|
1329
|
-
|
|
1330
|
-
${chain[1]}`) : undefined;
|
|
1331
|
-
const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
1879
|
+
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#pluggedOf(context, chain[1]) : [];
|
|
1880
|
+
const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
1332
1881
|
const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
|
|
1333
1882
|
if (!targets.length)
|
|
1334
1883
|
return;
|
|
@@ -1634,6 +2183,10 @@ var endpointNames = (key) => {
|
|
|
1634
2183
|
};
|
|
1635
2184
|
|
|
1636
2185
|
// pkgs/@akanjs/language/nameTable.ts
|
|
2186
|
+
var guardDetailOf = (facts, guards) => {
|
|
2187
|
+
const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
|
|
2188
|
+
return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
|
|
2189
|
+
};
|
|
1637
2190
|
var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
|
|
1638
2191
|
|
|
1639
2192
|
class NameTable {
|
|
@@ -1733,7 +2286,7 @@ class NameTable {
|
|
|
1733
2286
|
role: "sig",
|
|
1734
2287
|
gate: "sig",
|
|
1735
2288
|
label: sliceClass.name,
|
|
1736
|
-
detail:
|
|
2289
|
+
detail: guardDetailOf(facts, sliceClass.guards)
|
|
1737
2290
|
});
|
|
1738
2291
|
if (modelDoc)
|
|
1739
2292
|
this.#add({
|
|
@@ -1798,7 +2351,7 @@ class NameTable {
|
|
|
1798
2351
|
sourceKey: slice.name,
|
|
1799
2352
|
span: slice.span,
|
|
1800
2353
|
label: `slice ${refName} (default)`,
|
|
1801
|
-
detail:
|
|
2354
|
+
detail: guardDetailOf(facts, slice.guards)
|
|
1802
2355
|
};
|
|
1803
2356
|
this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
|
|
1804
2357
|
for (const key of slice.keys)
|
|
@@ -1808,7 +2361,7 @@ class NameTable {
|
|
|
1808
2361
|
sourceKey: key.name,
|
|
1809
2362
|
span: key.span,
|
|
1810
2363
|
label: `slice ${key.name}`,
|
|
1811
|
-
detail: [...
|
|
2364
|
+
detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
|
|
1812
2365
|
}, refName, key.name, modelNameSet, slice.name);
|
|
1813
2366
|
}
|
|
1814
2367
|
if (filterClass) {
|
|
@@ -1865,7 +2418,7 @@ class NameTable {
|
|
|
1865
2418
|
role: key.verb === "pubsub" ? "subscribe" : "listen",
|
|
1866
2419
|
gate: null,
|
|
1867
2420
|
label: `${key.verb} ${key.name}`,
|
|
1868
|
-
detail:
|
|
2421
|
+
detail: guardDetailOf(facts, key.guards)
|
|
1869
2422
|
});
|
|
1870
2423
|
}
|
|
1871
2424
|
}
|
|
@@ -1923,14 +2476,12 @@ var dictionaryPattern = /\.dictionary\.ts$/;
|
|
|
1923
2476
|
var srvkitPattern = /\/srvkit\//;
|
|
1924
2477
|
var wordPattern = /[A-Za-z_$][\w$]*/g;
|
|
1925
2478
|
var identifierPattern = /^[A-Za-z_$][\w$]*$/;
|
|
1926
|
-
var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
|
|
1927
2479
|
var stateReceivers = new Set(["use", "sel", "get", "do"]);
|
|
1928
2480
|
var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
|
|
1929
2481
|
var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
|
|
1930
2482
|
var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
|
|
1931
2483
|
var parseCacheSize = 64;
|
|
1932
2484
|
var pathOf = (fileName) => fileName.replace(/\\/g, "/");
|
|
1933
|
-
var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
|
|
1934
2485
|
var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
|
|
1935
2486
|
|
|
1936
2487
|
class AkanIndex {
|
|
@@ -2061,6 +2612,8 @@ canPass
|
|
|
2061
2612
|
adapt
|
|
2062
2613
|
`) || words.includes(`
|
|
2063
2614
|
dangerouslyAdapt
|
|
2615
|
+
`) || words.includes(`
|
|
2616
|
+
applyAdaptor
|
|
2064
2617
|
`);
|
|
2065
2618
|
}
|
|
2066
2619
|
static #sourceKeyOf({ family, sourceKey, label, detail, span }) {
|