@akanjs/language 3.1.2-rc.4 → 3.2.0-rc.1
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 +2 -1
- package/frameworkChannelReader.d.ts +16 -0
- package/frameworkMapBuilder.d.ts +20 -0
- package/frameworkMapDoc.d.ts +10 -0
- package/index.cjs +2012 -310
- package/index.d.ts +4 -0
- package/index.js +2010 -308
- package/moduleFacts.d.ts +46 -0
- package/package.json +1 -1
- package/routeFacts.d.ts +48 -0
- package/server.js +1110 -270
package/server.js
CHANGED
|
@@ -5,6 +5,721 @@ 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/moduleFacts.ts
|
|
9
|
+
var testFilePattern = /\.(?:test|spec)\.tsx?$/;
|
|
10
|
+
var moduleCallees = new Set([
|
|
11
|
+
"via",
|
|
12
|
+
"from",
|
|
13
|
+
"into",
|
|
14
|
+
"serve",
|
|
15
|
+
"slice",
|
|
16
|
+
"endpoint",
|
|
17
|
+
"internal",
|
|
18
|
+
"store",
|
|
19
|
+
"enumOf",
|
|
20
|
+
"adapt",
|
|
21
|
+
"dangerouslyAdapt"
|
|
22
|
+
]);
|
|
23
|
+
var intervalUnits = [
|
|
24
|
+
[86400000, "d"],
|
|
25
|
+
[3600000, "h"],
|
|
26
|
+
[60000, "m"],
|
|
27
|
+
[1000, "s"]
|
|
28
|
+
];
|
|
29
|
+
var argMethods = new Set(["param", "body", "search", "room", "msg"]);
|
|
30
|
+
var ownerOf = (relativePath) => {
|
|
31
|
+
const [root, name] = relativePath.split("/");
|
|
32
|
+
return (root === "apps" || root === "libs") && name ? `${root}/${name}` : null;
|
|
33
|
+
};
|
|
34
|
+
var createFactReader = (ts, ast) => {
|
|
35
|
+
const spanOf = (source, node) => {
|
|
36
|
+
const start = node.getStart(source);
|
|
37
|
+
const end = node.getEnd();
|
|
38
|
+
const head = source.getLineAndCharacterOfPosition(start);
|
|
39
|
+
const tail = source.getLineAndCharacterOfPosition(end);
|
|
40
|
+
return {
|
|
41
|
+
fileName: source.fileName,
|
|
42
|
+
start,
|
|
43
|
+
end,
|
|
44
|
+
line: head.line,
|
|
45
|
+
character: head.character,
|
|
46
|
+
endLine: tail.line,
|
|
47
|
+
endCharacter: tail.character
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
const extentOf = (source, node) => ({
|
|
51
|
+
start: node.getStart(source),
|
|
52
|
+
end: node.getEnd()
|
|
53
|
+
});
|
|
54
|
+
const unwrap = (expression) => {
|
|
55
|
+
let current = expression;
|
|
56
|
+
while (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isSatisfiesExpression(current))
|
|
57
|
+
current = current.expression;
|
|
58
|
+
return current;
|
|
59
|
+
};
|
|
60
|
+
const moduleRefOf = (expression, holder) => {
|
|
61
|
+
let current = expression && unwrap(expression);
|
|
62
|
+
while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression))
|
|
63
|
+
current = current.expression.expression;
|
|
64
|
+
if (current && ts.isStringLiteral(current))
|
|
65
|
+
return current.text;
|
|
66
|
+
if (!current || !ts.isPropertyAccessExpression(current))
|
|
67
|
+
return null;
|
|
68
|
+
return ts.isIdentifier(current.expression) && current.expression.text === holder ? current.name.text : null;
|
|
69
|
+
};
|
|
70
|
+
const returnedObject = (builder) => builder ? ast.firstObjectReturnedByArrow(builder) : null;
|
|
71
|
+
const builderOf = (call) => [...call.arguments].reverse().find((arg) => ts.isArrowFunction(arg) || ts.isFunctionExpression(arg));
|
|
72
|
+
const guardNameOf = (expression) => {
|
|
73
|
+
const value = unwrap(expression);
|
|
74
|
+
return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
|
|
75
|
+
};
|
|
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
|
+
const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
|
|
92
|
+
const chainOf = (initializer) => {
|
|
93
|
+
let root = unwrap(initializer);
|
|
94
|
+
const methods = [];
|
|
95
|
+
while (ts.isCallExpression(root) && ts.isPropertyAccessExpression(root.expression) && ts.isCallExpression(unwrap(root.expression.expression))) {
|
|
96
|
+
methods.push(root.expression.name.text);
|
|
97
|
+
root = unwrap(root.expression.expression);
|
|
98
|
+
}
|
|
99
|
+
return { root: ts.isCallExpression(root) ? root : null, methods };
|
|
100
|
+
};
|
|
101
|
+
const keyName = (property) => ts.isPropertyAssignment(property) || ts.isMethodDeclaration(property) || ts.isShorthandPropertyAssignment(property) ? property.name : null;
|
|
102
|
+
const objectKeys = (source, object) => (object?.properties ?? []).flatMap((property) => {
|
|
103
|
+
const name = ast.propertyName(property);
|
|
104
|
+
const node = keyName(property);
|
|
105
|
+
return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
|
|
106
|
+
});
|
|
107
|
+
const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
108
|
+
if (!ts.isPropertyAssignment(property))
|
|
109
|
+
return [];
|
|
110
|
+
const name = ast.nodeName(property.name);
|
|
111
|
+
if (!name)
|
|
112
|
+
return [];
|
|
113
|
+
const { root, methods } = chainOf(property.initializer);
|
|
114
|
+
return [
|
|
115
|
+
{
|
|
116
|
+
name,
|
|
117
|
+
span: spanOf(source, property.name),
|
|
118
|
+
extent: extentOf(source, property),
|
|
119
|
+
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
120
|
+
live: methods.includes("live"),
|
|
121
|
+
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
122
|
+
}
|
|
123
|
+
];
|
|
124
|
+
});
|
|
125
|
+
const verbReaderOf = (parameter) => {
|
|
126
|
+
const verbs = new Map;
|
|
127
|
+
if (parameter && ts.isObjectBindingPattern(parameter.name))
|
|
128
|
+
for (const element of parameter.name.elements) {
|
|
129
|
+
const verb = element.propertyName ?? element.name;
|
|
130
|
+
if (ts.isIdentifier(verb) && ts.isIdentifier(element.name))
|
|
131
|
+
verbs.set(element.name.text, verb.text);
|
|
132
|
+
}
|
|
133
|
+
const holder = parameter && ts.isIdentifier(parameter.name) ? parameter.name.text : null;
|
|
134
|
+
return (callee) => {
|
|
135
|
+
if (callee && ts.isIdentifier(callee))
|
|
136
|
+
return verbs.get(callee.text) ?? null;
|
|
137
|
+
if (callee && ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && callee.expression.text === holder)
|
|
138
|
+
return callee.name.text;
|
|
139
|
+
return null;
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
const numberOf = (expression) => {
|
|
143
|
+
const value = unwrap(expression);
|
|
144
|
+
if (ts.isNumericLiteral(value))
|
|
145
|
+
return Number(value.text.replace(/_/g, ""));
|
|
146
|
+
if (!ts.isBinaryExpression(value))
|
|
147
|
+
return null;
|
|
148
|
+
const [left, right] = [numberOf(value.left), numberOf(value.right)];
|
|
149
|
+
if (left === null || right === null)
|
|
150
|
+
return null;
|
|
151
|
+
const operator = value.operatorToken.kind;
|
|
152
|
+
if (operator === ts.SyntaxKind.AsteriskToken)
|
|
153
|
+
return left * right;
|
|
154
|
+
if (operator === ts.SyntaxKind.PlusToken)
|
|
155
|
+
return left + right;
|
|
156
|
+
if (operator === ts.SyntaxKind.MinusToken)
|
|
157
|
+
return left - right;
|
|
158
|
+
return operator === ts.SyntaxKind.SlashToken && right ? left / right : null;
|
|
159
|
+
};
|
|
160
|
+
const durationOf = (expression, source) => {
|
|
161
|
+
if (!expression)
|
|
162
|
+
return null;
|
|
163
|
+
const ms = numberOf(expression);
|
|
164
|
+
if (ms === null)
|
|
165
|
+
return expression.getText(source);
|
|
166
|
+
const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
|
|
167
|
+
return `${ms / size}${unit}`;
|
|
168
|
+
};
|
|
169
|
+
const modesOf = (option) => {
|
|
170
|
+
const object = option && unwrap(option);
|
|
171
|
+
if (!object || !ts.isObjectLiteralExpression(object))
|
|
172
|
+
return [];
|
|
173
|
+
return object.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
|
|
174
|
+
const name = ast.nodeName(property.name);
|
|
175
|
+
const value = unwrap(property.initializer);
|
|
176
|
+
if (name === "enabled")
|
|
177
|
+
return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
|
|
178
|
+
if (name === "once")
|
|
179
|
+
return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
180
|
+
if (name === "lock")
|
|
181
|
+
return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
182
|
+
if (name !== "serverMode" && name !== "operationMode")
|
|
183
|
+
return [];
|
|
184
|
+
const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
|
|
185
|
+
return values.flatMap((element) => ts.isStringLiteralLike(element) ? [element.text] : ts.isPropertyAccessExpression(element) ? [element.name.text] : []);
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
const internalKeysOf = (source, builder) => {
|
|
189
|
+
const verbOf = verbReaderOf(builder?.parameters[0]);
|
|
190
|
+
return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
191
|
+
if (!ts.isPropertyAssignment(property))
|
|
192
|
+
return [];
|
|
193
|
+
const name = ast.nodeName(property.name);
|
|
194
|
+
if (!name)
|
|
195
|
+
return [];
|
|
196
|
+
const { root } = chainOf(property.initializer);
|
|
197
|
+
const type = root ? verbOf(root.expression) : null;
|
|
198
|
+
const [first, second] = root?.arguments ?? [];
|
|
199
|
+
const schedule = type === "cron" && first ? ts.isStringLiteralLike(first) ? first.text : first.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
|
|
200
|
+
const option = type === "initialize" || type === "destroy" ? first : second;
|
|
201
|
+
return [
|
|
202
|
+
{
|
|
203
|
+
name,
|
|
204
|
+
span: spanOf(source, property.name),
|
|
205
|
+
extent: extentOf(source, property),
|
|
206
|
+
type,
|
|
207
|
+
schedule,
|
|
208
|
+
modes: modesOf(option)
|
|
209
|
+
}
|
|
210
|
+
];
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
const plugsOf = (source, builder) => {
|
|
214
|
+
const verbOf = verbReaderOf(builder?.parameters[0]);
|
|
215
|
+
return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
216
|
+
if (!ts.isPropertyAssignment(property))
|
|
217
|
+
return [];
|
|
218
|
+
const name = ast.nodeName(property.name);
|
|
219
|
+
const { root } = chainOf(property.initializer);
|
|
220
|
+
const [target] = root && verbOf(root.expression) === "plug" ? root.arguments : [];
|
|
221
|
+
const targetName = target && guardNameOf(target);
|
|
222
|
+
return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
const endpointKeysOf = (source, builder) => {
|
|
226
|
+
const verbOf = verbReaderOf(builder?.parameters[0]);
|
|
227
|
+
return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
228
|
+
if (!ts.isPropertyAssignment(property))
|
|
229
|
+
return [];
|
|
230
|
+
const name = ast.nodeName(property.name);
|
|
231
|
+
if (!name)
|
|
232
|
+
return [];
|
|
233
|
+
const { root, methods } = chainOf(property.initializer);
|
|
234
|
+
return [
|
|
235
|
+
{
|
|
236
|
+
name,
|
|
237
|
+
span: spanOf(source, property.name),
|
|
238
|
+
extent: extentOf(source, property),
|
|
239
|
+
verb: root ? verbOf(root.expression) : null,
|
|
240
|
+
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
241
|
+
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
242
|
+
}
|
|
243
|
+
];
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
const filterQueriesOf = (source, builder) => {
|
|
247
|
+
const query = returnedObject(builder)?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "query")?.initializer;
|
|
248
|
+
return query && ts.isObjectLiteralExpression(query) ? objectKeys(source, query) : [];
|
|
249
|
+
};
|
|
250
|
+
const typeOf = (expression) => {
|
|
251
|
+
const value = expression && unwrap(expression);
|
|
252
|
+
if (value && ts.isArrayLiteralExpression(value))
|
|
253
|
+
return { type: typeOf(value.elements[0]).type, array: true };
|
|
254
|
+
return { type: value ? ast.expressionName(value) : null, array: false };
|
|
255
|
+
};
|
|
256
|
+
const fieldsOf = (source, object) => (object?.properties ?? []).flatMap((property) => {
|
|
257
|
+
const name = ast.propertyName(property);
|
|
258
|
+
const node = keyName(property);
|
|
259
|
+
if (!name || !node)
|
|
260
|
+
return [];
|
|
261
|
+
const { root, methods } = ts.isPropertyAssignment(property) ? chainOf(property.initializer) : { root: null, methods: [] };
|
|
262
|
+
return [
|
|
263
|
+
{
|
|
264
|
+
name,
|
|
265
|
+
span: spanOf(source, node),
|
|
266
|
+
extent: extentOf(source, property),
|
|
267
|
+
...typeOf(root?.arguments[0]),
|
|
268
|
+
optional: methods.includes("optional")
|
|
269
|
+
}
|
|
270
|
+
];
|
|
271
|
+
});
|
|
272
|
+
const constantOf = (source, name, call) => {
|
|
273
|
+
const parents = call.arguments.map(unwrap).filter((arg) => ts.isIdentifier(arg) || ts.isPropertyAccessExpression(arg)).map((arg) => ast.expressionName(arg)).filter((parent) => parent !== null);
|
|
274
|
+
const fields = call.arguments.flatMap((arg) => fieldsOf(source, ast.firstObjectReturnedByArrow(arg)));
|
|
275
|
+
return { ...name, parents, fields };
|
|
276
|
+
};
|
|
277
|
+
const enumValuesOf = (call) => {
|
|
278
|
+
const list = call.arguments[1] && unwrap(call.arguments[1]);
|
|
279
|
+
if (!list || !ts.isArrayLiteralExpression(list))
|
|
280
|
+
return [];
|
|
281
|
+
return list.elements.flatMap((element) => ts.isStringLiteralLike(element) ? [element.text] : []);
|
|
282
|
+
};
|
|
283
|
+
const methodsOf = (source, statement) => statement.members.flatMap((member) => {
|
|
284
|
+
if (!ts.isMethodDeclaration(member) || !member.body)
|
|
285
|
+
return [];
|
|
286
|
+
const name = ast.nodeName(member.name);
|
|
287
|
+
if (!name || ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))
|
|
288
|
+
return [];
|
|
289
|
+
return [{ name, span: spanOf(source, member.name), extent: extentOf(source, member) }];
|
|
290
|
+
});
|
|
291
|
+
const importsOf = (statement) => {
|
|
292
|
+
const { importClause, moduleSpecifier } = statement;
|
|
293
|
+
if (!importClause || !ts.isStringLiteral(moduleSpecifier))
|
|
294
|
+
return [];
|
|
295
|
+
const from = moduleSpecifier.text;
|
|
296
|
+
const bindings = importClause.namedBindings;
|
|
297
|
+
return [
|
|
298
|
+
...importClause.name ? [{ name: importClause.name.text, imported: "default", from }] : [],
|
|
299
|
+
...bindings && ts.isNamespaceImport(bindings) ? [{ name: bindings.name.text, imported: "*", from }] : [],
|
|
300
|
+
...bindings && ts.isNamedImports(bindings) ? bindings.elements.map((element) => ({
|
|
301
|
+
name: element.name.text,
|
|
302
|
+
imported: (element.propertyName ?? element.name).text,
|
|
303
|
+
from
|
|
304
|
+
})) : []
|
|
305
|
+
];
|
|
306
|
+
};
|
|
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
|
+
const docOf = (source, node) => {
|
|
324
|
+
const { text } = source;
|
|
325
|
+
const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
|
|
326
|
+
const comments = [];
|
|
327
|
+
let end = node.getStart(source);
|
|
328
|
+
for (const range of [...ranges].reverse()) {
|
|
329
|
+
if ((text.slice(range.end, end).match(/\n/g)?.length ?? 0) > 1)
|
|
330
|
+
break;
|
|
331
|
+
comments.unshift(text.slice(range.pos, range.end));
|
|
332
|
+
end = range.pos;
|
|
333
|
+
}
|
|
334
|
+
const lines = comments.flatMap((comment) => comment.split(`
|
|
335
|
+
`)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
|
|
336
|
+
return lines.length ? lines.join(" ") : null;
|
|
337
|
+
};
|
|
338
|
+
const functionsOf = (source) => {
|
|
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
|
+
};
|
|
431
|
+
const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
|
|
432
|
+
const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
|
|
433
|
+
const read = (source, owner) => {
|
|
434
|
+
const imports = importsIn(source);
|
|
435
|
+
const facts = {
|
|
436
|
+
fileName: source.fileName,
|
|
437
|
+
owner,
|
|
438
|
+
imports,
|
|
439
|
+
...guardsOf(source, imports),
|
|
440
|
+
constants: [],
|
|
441
|
+
enums: [],
|
|
442
|
+
filters: [],
|
|
443
|
+
models: [],
|
|
444
|
+
services: [],
|
|
445
|
+
slices: [],
|
|
446
|
+
endpoints: [],
|
|
447
|
+
internals: [],
|
|
448
|
+
adaptors: [],
|
|
449
|
+
stores: []
|
|
450
|
+
};
|
|
451
|
+
for (const statement of source.statements) {
|
|
452
|
+
if (!ts.isClassDeclaration(statement) || !statement.name)
|
|
453
|
+
continue;
|
|
454
|
+
const heritage = ast.heritageCallNamed(statement, moduleCallees);
|
|
455
|
+
if (!heritage)
|
|
456
|
+
continue;
|
|
457
|
+
const { callee, call } = heritage;
|
|
458
|
+
const name = { name: statement.name.text, span: spanOf(source, statement.name) };
|
|
459
|
+
const builder = builderOf(call);
|
|
460
|
+
if (callee === "via")
|
|
461
|
+
facts.constants.push(constantOf(source, name, call));
|
|
462
|
+
else if (callee === "enumOf")
|
|
463
|
+
facts.enums.push({ ...name, values: enumValuesOf(call) });
|
|
464
|
+
else if (callee === "from")
|
|
465
|
+
facts.filters.push({ ...name, queries: filterQueriesOf(source, builder) });
|
|
466
|
+
else if (callee === "into")
|
|
467
|
+
facts.models.push({ ...name, model: name.name.endsWith("Model") ? lowerFirst(name.name.slice(0, -5)) : null });
|
|
468
|
+
else if (callee === "serve")
|
|
469
|
+
facts.services.push({
|
|
470
|
+
...name,
|
|
471
|
+
model: moduleRefOf(call.arguments[0], "db"),
|
|
472
|
+
methods: methodsOf(source, statement),
|
|
473
|
+
plugs: plugsOf(source, builder)
|
|
474
|
+
});
|
|
475
|
+
else if (callee === "slice")
|
|
476
|
+
facts.slices.push({
|
|
477
|
+
...name,
|
|
478
|
+
model: moduleRefOf(call.arguments[0], "srv"),
|
|
479
|
+
guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
|
|
480
|
+
keys: sliceKeysOf(source, builder)
|
|
481
|
+
});
|
|
482
|
+
else if (callee === "internal")
|
|
483
|
+
facts.internals.push({
|
|
484
|
+
...name,
|
|
485
|
+
model: moduleRefOf(call.arguments[0], "srv"),
|
|
486
|
+
keys: internalKeysOf(source, builder)
|
|
487
|
+
});
|
|
488
|
+
else if (callee === "adapt" || callee === "dangerouslyAdapt") {
|
|
489
|
+
const refName = call.arguments[0] && unwrap(call.arguments[0]);
|
|
490
|
+
facts.adaptors.push({
|
|
491
|
+
...name,
|
|
492
|
+
extent: extentOf(source, statement),
|
|
493
|
+
refName: refName && ts.isStringLiteralLike(refName) ? refName.text : null,
|
|
494
|
+
plugs: plugsOf(source, builder),
|
|
495
|
+
doc: docOf(source, statement)
|
|
496
|
+
});
|
|
497
|
+
} else if (callee === "endpoint")
|
|
498
|
+
facts.endpoints.push({
|
|
499
|
+
...name,
|
|
500
|
+
model: moduleRefOf(call.arguments[0], "srv"),
|
|
501
|
+
keys: endpointKeysOf(source, builder)
|
|
502
|
+
});
|
|
503
|
+
else if (callee === "store")
|
|
504
|
+
facts.stores.push({
|
|
505
|
+
...name,
|
|
506
|
+
model: moduleRefOf(call.arguments[0], "sig"),
|
|
507
|
+
stateKeys: objectKeys(source, returnedObject(builder)),
|
|
508
|
+
actions: methodsOf(source, statement)
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
return facts;
|
|
512
|
+
};
|
|
513
|
+
return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
// pkgs/akanjs/common/trimSlashes.ts
|
|
517
|
+
var trimTrailingSlashes = (value) => {
|
|
518
|
+
let end = value.length;
|
|
519
|
+
while (end > 0 && value[end - 1] === "/")
|
|
520
|
+
end -= 1;
|
|
521
|
+
return value.slice(0, end);
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
// pkgs/akanjs/common/routeConvention.ts
|
|
525
|
+
var ROUTE_SOURCE_RE = /^\.\/(.+)\.(tsx|ts|jsx|js)$/;
|
|
526
|
+
var RESERVED_ROUTE_FILES = new Set(["_layout", "_index", "_overrides"]);
|
|
527
|
+
var INTERNAL_ROOT_LAYOUT_LEAF = "__root_layout";
|
|
528
|
+
var IMPLICIT_LOCALE_SEGMENT = "[lang]";
|
|
529
|
+
var DIRECTORY_SCOPED_LEAVES = new Set(["_layout", "_index", "_overrides"]);
|
|
530
|
+
var PAGE_ROUTE_EXPORTS = new Set([
|
|
531
|
+
"default",
|
|
532
|
+
"pageConfig",
|
|
533
|
+
"head",
|
|
534
|
+
"generateHead",
|
|
535
|
+
"Loading",
|
|
536
|
+
"guard"
|
|
537
|
+
]);
|
|
538
|
+
var LAYOUT_ROUTE_EXPORTS = new Set([...PAGE_ROUTE_EXPORTS, "NotFound", "Error"]);
|
|
539
|
+
var ROOT_LAYOUT_ROUTE_EXPORTS = new Set([
|
|
540
|
+
...LAYOUT_ROUTE_EXPORTS,
|
|
541
|
+
"fonts",
|
|
542
|
+
"manifest",
|
|
543
|
+
"theme",
|
|
544
|
+
"reconnect",
|
|
545
|
+
"wsConnect",
|
|
546
|
+
"layoutStyle",
|
|
547
|
+
"viewport",
|
|
548
|
+
"McpAppRoot"
|
|
549
|
+
]);
|
|
550
|
+
var RESERVED_ROUTE_CONFIG_EXPORTS = new Set([...ROOT_LAYOUT_ROUTE_EXPORTS].filter((name) => name !== "default" && !/^[A-Z]/.test(name)));
|
|
551
|
+
function parseRouteModuleKey(key) {
|
|
552
|
+
const match = ROUTE_SOURCE_RE.exec(key);
|
|
553
|
+
if (!match)
|
|
554
|
+
throw new Error(`[route-convention] invalid route module key: ${key}`);
|
|
555
|
+
const file = match[1];
|
|
556
|
+
const ext = match[2];
|
|
557
|
+
const moduleSegments = file.split("/").filter(Boolean);
|
|
558
|
+
const leaf = moduleSegments.at(-1);
|
|
559
|
+
if (!leaf)
|
|
560
|
+
throw new Error(`[route-convention] invalid route module key: ${key}`);
|
|
561
|
+
if (moduleSegments.includes(IMPLICIT_LOCALE_SEGMENT)) {
|
|
562
|
+
throw new Error(`[route-convention] Akan.js injects \`[lang]\` automatically. Move files one level up: ${key}`);
|
|
563
|
+
}
|
|
564
|
+
const isInternalRootLayout = leaf === INTERNAL_ROOT_LAYOUT_LEAF;
|
|
565
|
+
const kind = leaf === "_layout" || isInternalRootLayout ? "layout" : leaf === "_overrides" ? "overrides" : "page";
|
|
566
|
+
if (leaf.startsWith("_") && !RESERVED_ROUTE_FILES.has(leaf) && !isInternalRootLayout) {
|
|
567
|
+
throw new Error(`[route-convention] unsupported reserved route file "${leaf}" in ${key}`);
|
|
568
|
+
}
|
|
569
|
+
const sourceRouteSegments = DIRECTORY_SCOPED_LEAVES.has(leaf) || isInternalRootLayout ? moduleSegments.slice(0, -1) : moduleSegments;
|
|
570
|
+
const routeSegments = [IMPLICIT_LOCALE_SEGMENT, ...sourceRouteSegments];
|
|
571
|
+
for (const segment of routeSegments)
|
|
572
|
+
validateRouteSegment(segment, key);
|
|
573
|
+
return {
|
|
574
|
+
key,
|
|
575
|
+
kind,
|
|
576
|
+
routeSegments,
|
|
577
|
+
moduleSegments,
|
|
578
|
+
sourceRouteSegments,
|
|
579
|
+
ext,
|
|
580
|
+
leaf,
|
|
581
|
+
pattern: normalizeRoutePattern(routeSegments),
|
|
582
|
+
isInternalRootLayout
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function tryParseRouteModuleKey(key) {
|
|
586
|
+
try {
|
|
587
|
+
return parseRouteModuleKey(key);
|
|
588
|
+
} catch {
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function normalizeRoutePattern(segments) {
|
|
593
|
+
const parts = segments.map(routeSegmentToPatternPart).filter((part) => part.length > 0);
|
|
594
|
+
return trimTrailingSlashes(`/${parts.join("/")}`) || "/";
|
|
595
|
+
}
|
|
596
|
+
function routeSegmentToPatternPart(segment) {
|
|
597
|
+
if (/^\(.+\)$/.test(segment))
|
|
598
|
+
return "";
|
|
599
|
+
return segment.replace(/^\[([^\]]+)\]$/, ":$1");
|
|
600
|
+
}
|
|
601
|
+
function validateRouteSegment(segment, key) {
|
|
602
|
+
if (segment.includes("[...") || segment.includes("[[...")) {
|
|
603
|
+
throw new Error(`[route-convention] catch-all routes are not supported yet: ${key}`);
|
|
604
|
+
}
|
|
605
|
+
const dynamic = /^\[(.+)\]$/.exec(segment);
|
|
606
|
+
if (dynamic && (dynamic[1]?.includes("[") || dynamic[1]?.includes("]") || dynamic[1]?.startsWith("..."))) {
|
|
607
|
+
throw new Error(`[route-convention] invalid dynamic route segment "${segment}" in ${key}`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// pkgs/@akanjs/language/routeFacts.ts
|
|
612
|
+
class RouteFactReader {
|
|
613
|
+
static #pagePattern = /^((?:apps|libs)\/[^/]+)\/page\/((?!(?:.*\/)?\(libs\)\/).+\.tsx?)$/;
|
|
614
|
+
static #guardPattern = /\.guard\s*\(/;
|
|
615
|
+
static #aliasPattern = /^export default ([A-Za-z_$][\w$]*)\s*;?\s*$/m;
|
|
616
|
+
static #workspacePattern = /^(?:\.|@(?:apps|libs)\/)/;
|
|
617
|
+
#ts;
|
|
618
|
+
#reader;
|
|
619
|
+
#parse;
|
|
620
|
+
constructor(ts, reader, parse) {
|
|
621
|
+
this.#ts = ts;
|
|
622
|
+
this.#reader = reader;
|
|
623
|
+
this.#parse = parse;
|
|
624
|
+
}
|
|
625
|
+
static fileOf(relative) {
|
|
626
|
+
const match = relative.match(RouteFactReader.#pagePattern);
|
|
627
|
+
return match ? { owner: match[1], key: `./${match[2]}` } : null;
|
|
628
|
+
}
|
|
629
|
+
read(fileName, info, text, words) {
|
|
630
|
+
const ts = this.#ts;
|
|
631
|
+
const parsed = tryParseRouteModuleKey(info.key);
|
|
632
|
+
if (!parsed || parsed.kind === "overrides" || parsed.isInternalRootLayout)
|
|
633
|
+
return null;
|
|
634
|
+
const starts = [...text.matchAll(/^export default\b/gm)].map(({ index }) => index);
|
|
635
|
+
const source = RouteFactReader.#guardPattern.test(text) || starts.length > 1 ? this.#parse(fileName, text) : null;
|
|
636
|
+
const exported = source ? this.#exportOf(source) : null;
|
|
637
|
+
const start = exported ? exported.getStart(source ?? undefined) : starts[0] ?? 0;
|
|
638
|
+
const head = text.slice(0, start);
|
|
639
|
+
return {
|
|
640
|
+
fileName,
|
|
641
|
+
owner: info.owner,
|
|
642
|
+
kind: parsed.kind,
|
|
643
|
+
pattern: normalizeRoutePattern(parsed.sourceRouteSegments),
|
|
644
|
+
dir: parsed.moduleSegments.slice(0, -1),
|
|
645
|
+
isRoot: words.includes(`
|
|
646
|
+
rootLayout
|
|
647
|
+
`),
|
|
648
|
+
line: head.split(`
|
|
649
|
+
`).length - 1,
|
|
650
|
+
character: start - head.lastIndexOf(`
|
|
651
|
+
`) - 1,
|
|
652
|
+
guard: source && exported ? this.#guardOf(source, exported) : null,
|
|
653
|
+
imports: source ? this.#reader.importsIn(source) : [],
|
|
654
|
+
alias: source ? exported && ts.isExportAssignment(exported) && ts.isIdentifier(exported.expression) ? exported.expression.text : null : text.match(RouteFactReader.#aliasPattern)?.[1] ?? null
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
#exportOf(source) {
|
|
658
|
+
const ts = this.#ts;
|
|
659
|
+
return source.statements.find((statement) => ts.isExportAssignment(statement) || ts.canHaveModifiers(statement) && !!ts.getModifiers(statement)?.some(({ kind }) => kind === ts.SyntaxKind.DefaultKeyword)) ?? null;
|
|
660
|
+
}
|
|
661
|
+
declarationIn(source, name) {
|
|
662
|
+
const ts = this.#ts;
|
|
663
|
+
for (const statement of source.statements) {
|
|
664
|
+
if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
|
|
665
|
+
return { statement, declared: statement.name, initializer: null };
|
|
666
|
+
if (!ts.isVariableStatement(statement))
|
|
667
|
+
continue;
|
|
668
|
+
const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
|
|
669
|
+
if (declaration)
|
|
670
|
+
return { statement, declared: declaration.name, initializer: declaration.initializer ?? null };
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
#guardOf(source, exported) {
|
|
675
|
+
const ts = this.#ts;
|
|
676
|
+
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
|
+
while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
682
|
+
const { name, expression } = current.expression;
|
|
683
|
+
const [stage] = current.arguments;
|
|
684
|
+
if (name.text === "guard" && stage)
|
|
685
|
+
return { calls: this.#callsIn(source, stage), span: this.#reader.spanOf(source, name) };
|
|
686
|
+
current = this.#reader.unwrap(expression);
|
|
687
|
+
}
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
#callsIn(source, stage) {
|
|
691
|
+
const ts = this.#ts;
|
|
692
|
+
const imported = new Set(this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from)).map(({ name }) => name));
|
|
693
|
+
const calls = new Map;
|
|
694
|
+
const callOf = (name) => {
|
|
695
|
+
if (calls.has(name))
|
|
696
|
+
return true;
|
|
697
|
+
const declared = this.declarationIn(source, name);
|
|
698
|
+
if (!declared && !imported.has(name))
|
|
699
|
+
return false;
|
|
700
|
+
calls.set(name, {
|
|
701
|
+
name,
|
|
702
|
+
local: declared && {
|
|
703
|
+
span: this.#reader.spanOf(source, declared.declared),
|
|
704
|
+
extent: this.#reader.extentOf(source, declared.statement),
|
|
705
|
+
doc: this.#reader.docOf(source, declared.statement)
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
return true;
|
|
709
|
+
};
|
|
710
|
+
const visit = (node) => {
|
|
711
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && callOf(node.expression.text))
|
|
712
|
+
return;
|
|
713
|
+
ts.forEachChild(node, visit);
|
|
714
|
+
};
|
|
715
|
+
if (ts.isIdentifier(stage))
|
|
716
|
+
callOf(stage.text);
|
|
717
|
+
else
|
|
718
|
+
visit(stage);
|
|
719
|
+
return [...calls.values()];
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
8
723
|
// pkgs/@akanjs/language/akanGraph.ts
|
|
9
724
|
var capitalize = (text) => text.charAt(0).toUpperCase() + text.slice(1);
|
|
10
725
|
var lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
|
|
@@ -14,6 +729,14 @@ var scalarPattern = /\/lib\/__scalar\/([^/]+)\/[^/]+\.constant\.ts$/;
|
|
|
14
729
|
var moduleDirPattern = /\/lib\/([^/]+)\//;
|
|
15
730
|
var typeKinds = new Set(["model", "scalar", "enum"]);
|
|
16
731
|
var clientRoots = new Set(["fetch", "st", "sig"]);
|
|
732
|
+
var builtinGuards = {
|
|
733
|
+
Public: { scope: "account", doc: "Passes every caller, signed in or not." },
|
|
734
|
+
None: { scope: "account", doc: "Refuses every caller." }
|
|
735
|
+
};
|
|
736
|
+
var frameworkOwner = "akanjs";
|
|
737
|
+
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
738
|
+
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
739
|
+
var maxMapDepth = 4;
|
|
17
740
|
|
|
18
741
|
class GraphBuilder {
|
|
19
742
|
#source;
|
|
@@ -21,8 +744,16 @@ class GraphBuilder {
|
|
|
21
744
|
#edges = new Map;
|
|
22
745
|
#contexts = new Map;
|
|
23
746
|
#members = new Map;
|
|
747
|
+
#guards = new Map;
|
|
748
|
+
#mapEntries = new Map;
|
|
749
|
+
#routeFiles = new Map;
|
|
750
|
+
#declarations = new Map;
|
|
751
|
+
#routes;
|
|
752
|
+
#adaptors = new Map;
|
|
753
|
+
#plugs = new Map;
|
|
24
754
|
constructor(source) {
|
|
25
755
|
this.#source = source;
|
|
756
|
+
this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
|
|
26
757
|
}
|
|
27
758
|
build() {
|
|
28
759
|
const { facts } = this.#source;
|
|
@@ -34,6 +765,17 @@ class GraphBuilder {
|
|
|
34
765
|
...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
|
|
35
766
|
];
|
|
36
767
|
const typed = [];
|
|
768
|
+
for (const file of facts)
|
|
769
|
+
for (const fact of file.adaptors) {
|
|
770
|
+
const declared = { fact, owner: file.owner, fileName: file.fileName };
|
|
771
|
+
this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
|
|
772
|
+
this.#adaptorNodeOf(declared);
|
|
773
|
+
}
|
|
774
|
+
for (const file of facts)
|
|
775
|
+
for (const fact of file.adaptors)
|
|
776
|
+
for (const plug of fact.plugs)
|
|
777
|
+
this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
|
|
778
|
+
this.#declareGuards(facts);
|
|
37
779
|
for (const file of facts) {
|
|
38
780
|
const { owner } = file;
|
|
39
781
|
const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
|
|
@@ -64,6 +806,10 @@ class GraphBuilder {
|
|
|
64
806
|
}
|
|
65
807
|
for (const method of service.methods)
|
|
66
808
|
this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
|
|
809
|
+
for (const plug of service.plugs)
|
|
810
|
+
this.#plugs.set(`${owner ?? ""}
|
|
811
|
+
${refName}
|
|
812
|
+
${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
67
813
|
}
|
|
68
814
|
for (const filter of file.filters) {
|
|
69
815
|
if (!filter.name.endsWith("Filter"))
|
|
@@ -79,12 +825,12 @@ class GraphBuilder {
|
|
|
79
825
|
continue;
|
|
80
826
|
const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards);
|
|
81
827
|
for (const guard of slice.guards)
|
|
82
|
-
this.#guard(fallback, guard);
|
|
828
|
+
this.#guard(fallback, guard, file);
|
|
83
829
|
for (const key of slice.keys) {
|
|
84
830
|
const detail = [...key.guards, ...key.live ? ["live"] : []];
|
|
85
831
|
const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
86
832
|
for (const guard of key.guards)
|
|
87
|
-
this.#guard(node, guard);
|
|
833
|
+
this.#guard(node, guard, file);
|
|
88
834
|
}
|
|
89
835
|
}
|
|
90
836
|
for (const endpoint of file.endpoints) {
|
|
@@ -95,9 +841,13 @@ class GraphBuilder {
|
|
|
95
841
|
const detail = [...key.verb ? [key.verb] : [], ...key.guards];
|
|
96
842
|
const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
97
843
|
for (const guard of key.guards)
|
|
98
|
-
this.#guard(node, guard);
|
|
844
|
+
this.#guard(node, guard, file);
|
|
99
845
|
}
|
|
100
846
|
}
|
|
847
|
+
for (const internal of file.internals)
|
|
848
|
+
for (const key of internal.keys)
|
|
849
|
+
if (internal.model && key.type !== "resolveField")
|
|
850
|
+
this.#addMember("internal", owner, internal.model, key, `${internal.name}.${key.name}`, file.fileName, [key.type, key.schedule, ...key.modes].filter((item) => item !== null));
|
|
101
851
|
for (const store of file.stores)
|
|
102
852
|
if (store.model)
|
|
103
853
|
for (const action of store.actions)
|
|
@@ -114,6 +864,7 @@ class GraphBuilder {
|
|
|
114
864
|
if (field.ref)
|
|
115
865
|
this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
|
|
116
866
|
}
|
|
867
|
+
this.#addRoutes();
|
|
117
868
|
for (const [fileName, words] of this.#source.files)
|
|
118
869
|
this.#walk(fileName, words);
|
|
119
870
|
return {
|
|
@@ -157,19 +908,23 @@ class GraphBuilder {
|
|
|
157
908
|
this.#members.set(byName, [...this.#members.get(byName) ?? [], node]);
|
|
158
909
|
return node;
|
|
159
910
|
}
|
|
160
|
-
#hashOf(
|
|
161
|
-
if (!this.#source.digests
|
|
911
|
+
#hashOf(...parts) {
|
|
912
|
+
if (!this.#source.digests)
|
|
162
913
|
return null;
|
|
163
914
|
let hash = 2166136261;
|
|
164
|
-
for (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
915
|
+
for (const [text, { start, end }] of parts) {
|
|
916
|
+
if (text === undefined)
|
|
917
|
+
return null;
|
|
918
|
+
for (let idx = start;idx < end; idx++) {
|
|
919
|
+
const code = text.charCodeAt(idx);
|
|
920
|
+
if (code !== 13)
|
|
921
|
+
hash = Math.imul(hash ^ code, 16777619);
|
|
922
|
+
}
|
|
168
923
|
}
|
|
169
924
|
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
170
925
|
}
|
|
171
926
|
#digest(node, text, extent) {
|
|
172
|
-
const digest = node.digest ? null : this.#hashOf(text, extent);
|
|
927
|
+
const digest = node.digest ? null : this.#hashOf([text, extent]);
|
|
173
928
|
if (digest)
|
|
174
929
|
node.digest = digest;
|
|
175
930
|
}
|
|
@@ -186,14 +941,296 @@ ${label ?? ""}`;
|
|
|
186
941
|
else
|
|
187
942
|
this.#edges.set(key, { from, to, kind, label, count: 1, at });
|
|
188
943
|
}
|
|
189
|
-
#guard(node, guard) {
|
|
190
|
-
const [role,
|
|
191
|
-
if (!
|
|
192
|
-
return;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
944
|
+
#guard(node, guard, file) {
|
|
945
|
+
const [role, local] = guard.includes(" ") ? guard.split(" ") : [null, guard];
|
|
946
|
+
if (!local || local === "?")
|
|
947
|
+
return;
|
|
948
|
+
this.#addEdge(node.id, this.#guardIdOf(local, file), "guard", role, node.at);
|
|
949
|
+
}
|
|
950
|
+
#declareGuards(facts) {
|
|
951
|
+
const factories = new Map;
|
|
952
|
+
for (const file of facts)
|
|
953
|
+
for (const fact of file.guardFactories) {
|
|
954
|
+
const factory = { fact, owner: file.owner, fileName: file.fileName, made: [] };
|
|
955
|
+
factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
|
|
956
|
+
}
|
|
957
|
+
const sameFile = (factory, name) => name ? factories.get(name)?.find((other) => other.fileName === factory.fileName) : undefined;
|
|
958
|
+
const declare = (guard, factory) => {
|
|
959
|
+
this.#guards.set(guard.name, [...this.#guards.get(guard.name) ?? [], guard]);
|
|
960
|
+
factory?.made.push(guard);
|
|
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);
|
|
976
|
+
}
|
|
977
|
+
for (const { name, span, extent, doc, callee } of file.guardCalls) {
|
|
978
|
+
const imported = file.imports.find((item) => item.name === callee);
|
|
979
|
+
if (!imported)
|
|
980
|
+
continue;
|
|
981
|
+
const from = this.#ownerOfSpecifier(fileName, imported.from);
|
|
982
|
+
const candidates = factories.get(imported.imported === "default" || imported.imported === "*" ? callee : imported.imported) ?? [];
|
|
983
|
+
const factory = candidates.find((other) => other.owner === from) ?? this.#soleVisible(candidates, from ?? owner);
|
|
984
|
+
if (factory)
|
|
985
|
+
declare({ name, owner, span, scope: factory.fact.scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
for (const list of factories.values())
|
|
989
|
+
for (const { fact, made } of list)
|
|
990
|
+
if (made.every((guard) => !guard.doc))
|
|
991
|
+
for (const guard of made)
|
|
992
|
+
guard.doc = fact.doc;
|
|
993
|
+
for (const list of this.#guards.values())
|
|
994
|
+
for (const guard of list)
|
|
995
|
+
this.#guardNodeOf(guard);
|
|
996
|
+
}
|
|
997
|
+
#guardIdOf(local, site) {
|
|
998
|
+
const found = this.#findGuard(local, site, 0);
|
|
999
|
+
if ("guard" in found)
|
|
1000
|
+
return this.#guardNodeOf(found.guard);
|
|
1001
|
+
const { name, from } = found;
|
|
1002
|
+
const isFramework = from === frameworkOwner || from === undefined && !!builtinGuards[name];
|
|
1003
|
+
const builtin = isFramework ? builtinGuards[name] : undefined;
|
|
1004
|
+
const owner = isFramework ? null : from ?? null;
|
|
1005
|
+
const detail = [...builtin ? [builtin.scope] : [], ...isFramework ? [frameworkOwner] : []];
|
|
1006
|
+
const node = this.#addNode(this.#key("guard", owner, name), "guard", owner, null, name, null, detail);
|
|
1007
|
+
if (builtin)
|
|
1008
|
+
node.doc = builtin.doc;
|
|
1009
|
+
return node.id;
|
|
1010
|
+
}
|
|
1011
|
+
#findGuard(local, site, depth) {
|
|
1012
|
+
const [head = local, member] = local.split(".");
|
|
1013
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
1014
|
+
const from = imported ? this.#ownerOfSpecifier(site.fileName, imported.from) : undefined;
|
|
1015
|
+
const holder = imported ? from : site.owner;
|
|
1016
|
+
if (member && imported?.imported !== "*" && holder !== undefined && holder !== frameworkOwner && depth < maxMapDepth) {
|
|
1017
|
+
const objectName = imported && imported.imported !== "default" ? imported.imported : head;
|
|
1018
|
+
for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
|
|
1019
|
+
const found = this.#findGuard(entry.name, entry.site, depth + 1);
|
|
1020
|
+
if ("guard" in found)
|
|
1021
|
+
return found;
|
|
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;
|
|
1104
|
+
}
|
|
1105
|
+
#addRoutes() {
|
|
1106
|
+
const routes = [];
|
|
1107
|
+
const layouts = new Map;
|
|
1108
|
+
for (const [fileName, words] of this.#source.files) {
|
|
1109
|
+
const relative = this.#source.relative(fileName);
|
|
1110
|
+
const info = RouteFactReader.fileOf(relative);
|
|
1111
|
+
const text = info ? this.#source.readText(fileName) : undefined;
|
|
1112
|
+
const fact = info && text !== undefined ? this.#routes.read(fileName, info, text, words) : null;
|
|
1113
|
+
if (!fact || text === undefined)
|
|
1114
|
+
continue;
|
|
1115
|
+
const span = {
|
|
1116
|
+
fileName,
|
|
1117
|
+
start: 0,
|
|
1118
|
+
end: 0,
|
|
1119
|
+
line: fact.line,
|
|
1120
|
+
character: fact.character,
|
|
1121
|
+
endLine: 0,
|
|
1122
|
+
endCharacter: 0
|
|
1123
|
+
};
|
|
1124
|
+
const dir = `/${fact.dir.join("/")}`;
|
|
1125
|
+
const guard = fact.guard ? ["guard"] : [];
|
|
1126
|
+
const node = fact.kind === "layout" ? this.#addNode(this.#key("layout", fact.owner, dir), "layout", fact.owner, null, dir, span, [...fact.isRoot ? ["root"] : [], ...guard], `${dir === "/" ? "" : dir}/_layout`) : this.#addNode(this.#key("route", fact.owner, fact.pattern), "route", fact.owner, null, fact.pattern, span, guard);
|
|
1127
|
+
this.#digest(node, text, { start: 0, end: text.length });
|
|
1128
|
+
this.#routeFiles.set(relative, { id: node.id, alias: fact.alias });
|
|
1129
|
+
if (fact.kind === "layout")
|
|
1130
|
+
layouts.set(`${fact.owner}
|
|
1131
|
+
${dir}`, node);
|
|
1132
|
+
routes.push({ fact, node });
|
|
1133
|
+
for (const call of fact.guard?.calls ?? []) {
|
|
1134
|
+
const id = this.#routeGuardIdOf(call, fact, text);
|
|
1135
|
+
if (id && fact.guard)
|
|
1136
|
+
this.#addEdge(node.id, id, "guard", null, this.#location(fact.guard.span));
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
const parentOf = ({ owner, dir, kind }) => {
|
|
1140
|
+
for (let depth = kind === "layout" ? dir.length - 1 : dir.length;depth >= 0; depth--) {
|
|
1141
|
+
const layout = layouts.get(`${owner}
|
|
1142
|
+
/${dir.slice(0, depth).join("/")}`);
|
|
1143
|
+
if (layout)
|
|
1144
|
+
return layout;
|
|
1145
|
+
}
|
|
1146
|
+
return null;
|
|
1147
|
+
};
|
|
1148
|
+
for (const { fact, node } of routes) {
|
|
1149
|
+
const parent = parentOf(fact);
|
|
1150
|
+
if (parent)
|
|
1151
|
+
this.#addEdge(parent.id, node.id, "wraps", null, node.at);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
#routeGuardIdOf({ name: local, local: declared }, fact, text) {
|
|
1155
|
+
if (declared) {
|
|
1156
|
+
const file = this.#source.relative(fact.fileName).slice(fact.owner.length + 1);
|
|
1157
|
+
const id = this.#key("guard", fact.owner, `${file}#${local}`);
|
|
1158
|
+
return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
|
|
1159
|
+
}
|
|
1160
|
+
const imported = fact.imports.find((item) => item.name === local);
|
|
1161
|
+
if (!imported)
|
|
1162
|
+
return null;
|
|
1163
|
+
const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
|
|
1164
|
+
const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
|
|
1165
|
+
if (!from || from === frameworkOwner)
|
|
1166
|
+
return null;
|
|
1167
|
+
const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
|
|
1168
|
+
if (!found)
|
|
1169
|
+
return null;
|
|
1170
|
+
const { reader } = this.#source;
|
|
1171
|
+
return this.#routeGuardNodeOf(this.#key("guard", from, name), from, name, reader.spanOf(found.source, found.declared), reader.docOf(found.source, found.statement), found.source.text, reader.extentOf(found.source, found.statement));
|
|
1172
|
+
}
|
|
1173
|
+
#routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
|
|
1174
|
+
const isNew = !this.#nodes.has(id);
|
|
1175
|
+
const node = this.#addNode(id, "guard", owner, null, name, span, ["route"]);
|
|
1176
|
+
if (isNew && doc)
|
|
1177
|
+
node.doc = doc;
|
|
1178
|
+
this.#digest(node, text, extent);
|
|
1179
|
+
return node.id;
|
|
1180
|
+
}
|
|
1181
|
+
#adaptorIdOf(local, file) {
|
|
1182
|
+
const [head, member] = local.split(".");
|
|
1183
|
+
const imported = file.imports.find((item) => item.name === head);
|
|
1184
|
+
const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1185
|
+
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1186
|
+
const declared = this.#adaptors.get(name) ?? [];
|
|
1187
|
+
const found = from === undefined ? declared.find((adaptor) => adaptor.owner === file.owner) ?? declared.find((adaptor) => this.#visible(adaptor.owner, file.owner)) : declared.find((adaptor) => adaptor.owner === from) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#visible(adaptor.owner, from)));
|
|
1188
|
+
if (found)
|
|
1189
|
+
return this.#adaptorNodeOf(found);
|
|
1190
|
+
const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
|
|
1191
|
+
const detail = from === frameworkOwner ? [frameworkOwner] : [];
|
|
1192
|
+
return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
|
|
1193
|
+
}
|
|
1194
|
+
#adaptorNodeOf({ fact, owner, fileName }) {
|
|
1195
|
+
const id = this.#key("adaptor", owner, fact.name);
|
|
1196
|
+
const detail = fact.refName ? [fact.refName] : [];
|
|
1197
|
+
const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
|
|
1198
|
+
if (fact.doc && !node.doc)
|
|
1199
|
+
node.doc = fact.doc;
|
|
1200
|
+
this.#digest(node, this.#source.readText(fileName), fact.extent);
|
|
1201
|
+
return node.id;
|
|
1202
|
+
}
|
|
1203
|
+
#guardNodeOf({ name, owner, span, scope, doc, parts }) {
|
|
1204
|
+
const node = this.#addNode(this.#key("guard", owner, name), "guard", owner, null, name, span, scope ? [scope] : []);
|
|
1205
|
+
if (doc && !node.doc)
|
|
1206
|
+
node.doc = doc;
|
|
1207
|
+
const digest = node.digest ? null : this.#hashOf(...parts.map(({ fileName, extent }) => [
|
|
1208
|
+
this.#source.readText(fileName),
|
|
1209
|
+
extent
|
|
1210
|
+
]));
|
|
1211
|
+
if (digest)
|
|
1212
|
+
node.digest = digest;
|
|
1213
|
+
return node.id;
|
|
1214
|
+
}
|
|
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("/");
|
|
197
1234
|
}
|
|
198
1235
|
#moduleDirOf(fileName) {
|
|
199
1236
|
const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
|
|
@@ -214,7 +1251,7 @@ ${label ?? ""}`;
|
|
|
214
1251
|
for (const field of current.fields) {
|
|
215
1252
|
if (fields.has(field.name))
|
|
216
1253
|
continue;
|
|
217
|
-
const digest = this.#hashOf(this.#source.readText(field.span.fileName), field.extent);
|
|
1254
|
+
const digest = this.#hashOf([this.#source.readText(field.span.fileName), field.extent]);
|
|
218
1255
|
fields.set(field.name, {
|
|
219
1256
|
name: field.name,
|
|
220
1257
|
type: field.type,
|
|
@@ -233,6 +1270,10 @@ ${label ?? ""}`;
|
|
|
233
1270
|
#visible(target, from) {
|
|
234
1271
|
return !from || !target || target === from || target.startsWith("libs/");
|
|
235
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
|
+
}
|
|
236
1277
|
#targetNodeOf({ family, owner, refName, sourceKey }) {
|
|
237
1278
|
const isDefault = /^[A-Z]/.test(sourceKey);
|
|
238
1279
|
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;
|
|
@@ -284,7 +1325,10 @@ st
|
|
|
284
1325
|
visit(source);
|
|
285
1326
|
}
|
|
286
1327
|
#link(fileName, source, identifier, chain, context, owner) {
|
|
287
|
-
const
|
|
1328
|
+
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#plugs.get(`${context.owner ?? ""}
|
|
1329
|
+
${context.module}
|
|
1330
|
+
${chain[1]}`) : undefined;
|
|
1331
|
+
const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
288
1332
|
const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
|
|
289
1333
|
if (!targets.length)
|
|
290
1334
|
return;
|
|
@@ -299,6 +1343,16 @@ st
|
|
|
299
1343
|
for (const target of new Set(own.length ? own : targets))
|
|
300
1344
|
this.#addEdge(context.id, target, kind, identifier.text, at);
|
|
301
1345
|
}
|
|
1346
|
+
#isServiceThis(identifier) {
|
|
1347
|
+
const { ts } = this.#source;
|
|
1348
|
+
for (let current = identifier.parent;!ts.isSourceFile(current); current = current.parent) {
|
|
1349
|
+
if (ts.isMethodDeclaration(current) || ts.isAccessor(current) || ts.isConstructorDeclaration(current))
|
|
1350
|
+
return ts.isClassDeclaration(current.parent) && ts.isSourceFile(current.parent.parent);
|
|
1351
|
+
if (ts.isFunctionExpression(current) || ts.isFunctionDeclaration(current) || ts.isClassLike(current))
|
|
1352
|
+
return false;
|
|
1353
|
+
}
|
|
1354
|
+
return false;
|
|
1355
|
+
}
|
|
302
1356
|
#chainOf(expression) {
|
|
303
1357
|
const { ts } = this.#source;
|
|
304
1358
|
if (ts.isNonNullExpression(expression) || ts.isParenthesizedExpression(expression))
|
|
@@ -322,14 +1376,13 @@ st
|
|
|
322
1376
|
const name = declared && ts.isIdentifier(declared) && !isDefaultExport ? declared.text : "default";
|
|
323
1377
|
const relative = this.#source.relative(source.fileName);
|
|
324
1378
|
const owner = this.#source.ownerOf(source.fileName);
|
|
1379
|
+
const extent = { start: top.getStart(source), end: top.getEnd() };
|
|
1380
|
+
const routeFile = this.#routeFiles.get(relative);
|
|
1381
|
+
const routeId = routeFile && (name === "default" || name === routeFile.alias) ? routeFile.id : undefined;
|
|
1382
|
+
if (routeId)
|
|
1383
|
+
return { id: routeId, kind: "component", owner, module: null, extent };
|
|
325
1384
|
const id = `component:${relative}#${name}`;
|
|
326
|
-
const context = {
|
|
327
|
-
id,
|
|
328
|
-
kind: "component",
|
|
329
|
-
owner,
|
|
330
|
-
module: null,
|
|
331
|
-
extent: { start: top.getStart(source), end: top.getEnd() }
|
|
332
|
-
};
|
|
1385
|
+
const context = { id, kind: "component", owner, module: null, extent };
|
|
333
1386
|
if (this.#nodes.has(id))
|
|
334
1387
|
return context;
|
|
335
1388
|
const pagePrefix = owner ? `${owner}/page/` : null;
|
|
@@ -416,242 +1469,6 @@ var createAst = (ts) => {
|
|
|
416
1469
|
};
|
|
417
1470
|
};
|
|
418
1471
|
|
|
419
|
-
// pkgs/@akanjs/language/moduleFacts.ts
|
|
420
|
-
var moduleCallees = new Set(["via", "from", "into", "serve", "slice", "endpoint", "store", "enumOf"]);
|
|
421
|
-
var argMethods = new Set(["param", "body", "search", "room", "msg"]);
|
|
422
|
-
var ownerOf = (relativePath) => {
|
|
423
|
-
const [root, name] = relativePath.split("/");
|
|
424
|
-
return (root === "apps" || root === "libs") && name ? `${root}/${name}` : null;
|
|
425
|
-
};
|
|
426
|
-
var createFactReader = (ts, ast) => {
|
|
427
|
-
const spanOf = (source, node) => {
|
|
428
|
-
const start = node.getStart(source);
|
|
429
|
-
const end = node.getEnd();
|
|
430
|
-
const head = source.getLineAndCharacterOfPosition(start);
|
|
431
|
-
const tail = source.getLineAndCharacterOfPosition(end);
|
|
432
|
-
return {
|
|
433
|
-
fileName: source.fileName,
|
|
434
|
-
start,
|
|
435
|
-
end,
|
|
436
|
-
line: head.line,
|
|
437
|
-
character: head.character,
|
|
438
|
-
endLine: tail.line,
|
|
439
|
-
endCharacter: tail.character
|
|
440
|
-
};
|
|
441
|
-
};
|
|
442
|
-
const extentOf = (source, node) => ({
|
|
443
|
-
start: node.getStart(source),
|
|
444
|
-
end: node.getEnd()
|
|
445
|
-
});
|
|
446
|
-
const unwrap = (expression) => {
|
|
447
|
-
let current = expression;
|
|
448
|
-
while (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isSatisfiesExpression(current))
|
|
449
|
-
current = current.expression;
|
|
450
|
-
return current;
|
|
451
|
-
};
|
|
452
|
-
const moduleRefOf = (expression, holder) => {
|
|
453
|
-
let current = expression && unwrap(expression);
|
|
454
|
-
while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression))
|
|
455
|
-
current = current.expression.expression;
|
|
456
|
-
if (current && ts.isStringLiteral(current))
|
|
457
|
-
return current.text;
|
|
458
|
-
if (!current || !ts.isPropertyAccessExpression(current))
|
|
459
|
-
return null;
|
|
460
|
-
return ts.isIdentifier(current.expression) && current.expression.text === holder ? current.name.text : null;
|
|
461
|
-
};
|
|
462
|
-
const returnedObject = (builder) => builder ? ast.firstObjectReturnedByArrow(builder) : null;
|
|
463
|
-
const builderOf = (call) => [...call.arguments].reverse().find((arg) => ts.isArrowFunction(arg) || ts.isFunctionExpression(arg));
|
|
464
|
-
const guardsIn = (object) => {
|
|
465
|
-
const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
|
|
466
|
-
if (!guards)
|
|
467
|
-
return [];
|
|
468
|
-
if (ts.isArrayLiteralExpression(guards))
|
|
469
|
-
return guards.elements.map((element) => ast.expressionName(element)).filter((name) => name !== null);
|
|
470
|
-
if (ts.isObjectLiteralExpression(guards))
|
|
471
|
-
return guards.properties.filter(ts.isPropertyAssignment).map((property) => `${ast.nodeName(property.name)} ${ast.expressionName(property.initializer) ?? "?"}`);
|
|
472
|
-
const single = ast.expressionName(guards);
|
|
473
|
-
return single ? [single] : [];
|
|
474
|
-
};
|
|
475
|
-
const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
|
|
476
|
-
const chainOf = (initializer) => {
|
|
477
|
-
let root = unwrap(initializer);
|
|
478
|
-
const methods = [];
|
|
479
|
-
while (ts.isCallExpression(root) && ts.isPropertyAccessExpression(root.expression) && ts.isCallExpression(unwrap(root.expression.expression))) {
|
|
480
|
-
methods.push(root.expression.name.text);
|
|
481
|
-
root = unwrap(root.expression.expression);
|
|
482
|
-
}
|
|
483
|
-
return { root: ts.isCallExpression(root) ? root : null, methods };
|
|
484
|
-
};
|
|
485
|
-
const keyName = (property) => ts.isPropertyAssignment(property) || ts.isMethodDeclaration(property) || ts.isShorthandPropertyAssignment(property) ? property.name : null;
|
|
486
|
-
const objectKeys = (source, object) => (object?.properties ?? []).flatMap((property) => {
|
|
487
|
-
const name = ast.propertyName(property);
|
|
488
|
-
const node = keyName(property);
|
|
489
|
-
return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
|
|
490
|
-
});
|
|
491
|
-
const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
492
|
-
if (!ts.isPropertyAssignment(property))
|
|
493
|
-
return [];
|
|
494
|
-
const name = ast.nodeName(property.name);
|
|
495
|
-
if (!name)
|
|
496
|
-
return [];
|
|
497
|
-
const { root, methods } = chainOf(property.initializer);
|
|
498
|
-
return [
|
|
499
|
-
{
|
|
500
|
-
name,
|
|
501
|
-
span: spanOf(source, property.name),
|
|
502
|
-
extent: extentOf(source, property),
|
|
503
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
504
|
-
live: methods.includes("live"),
|
|
505
|
-
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
506
|
-
}
|
|
507
|
-
];
|
|
508
|
-
});
|
|
509
|
-
const verbReaderOf = (parameter) => {
|
|
510
|
-
const verbs = new Map;
|
|
511
|
-
if (parameter && ts.isObjectBindingPattern(parameter.name))
|
|
512
|
-
for (const element of parameter.name.elements) {
|
|
513
|
-
const verb = element.propertyName ?? element.name;
|
|
514
|
-
if (ts.isIdentifier(verb) && ts.isIdentifier(element.name))
|
|
515
|
-
verbs.set(element.name.text, verb.text);
|
|
516
|
-
}
|
|
517
|
-
const holder = parameter && ts.isIdentifier(parameter.name) ? parameter.name.text : null;
|
|
518
|
-
return (callee) => {
|
|
519
|
-
if (callee && ts.isIdentifier(callee))
|
|
520
|
-
return verbs.get(callee.text) ?? null;
|
|
521
|
-
if (callee && ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && callee.expression.text === holder)
|
|
522
|
-
return callee.name.text;
|
|
523
|
-
return null;
|
|
524
|
-
};
|
|
525
|
-
};
|
|
526
|
-
const endpointKeysOf = (source, builder) => {
|
|
527
|
-
const verbOf = verbReaderOf(builder?.parameters[0]);
|
|
528
|
-
return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
529
|
-
if (!ts.isPropertyAssignment(property))
|
|
530
|
-
return [];
|
|
531
|
-
const name = ast.nodeName(property.name);
|
|
532
|
-
if (!name)
|
|
533
|
-
return [];
|
|
534
|
-
const { root, methods } = chainOf(property.initializer);
|
|
535
|
-
return [
|
|
536
|
-
{
|
|
537
|
-
name,
|
|
538
|
-
span: spanOf(source, property.name),
|
|
539
|
-
extent: extentOf(source, property),
|
|
540
|
-
verb: root ? verbOf(root.expression) : null,
|
|
541
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
542
|
-
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
543
|
-
}
|
|
544
|
-
];
|
|
545
|
-
});
|
|
546
|
-
};
|
|
547
|
-
const filterQueriesOf = (source, builder) => {
|
|
548
|
-
const query = returnedObject(builder)?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "query")?.initializer;
|
|
549
|
-
return query && ts.isObjectLiteralExpression(query) ? objectKeys(source, query) : [];
|
|
550
|
-
};
|
|
551
|
-
const typeOf = (expression) => {
|
|
552
|
-
const value = expression && unwrap(expression);
|
|
553
|
-
if (value && ts.isArrayLiteralExpression(value))
|
|
554
|
-
return { type: typeOf(value.elements[0]).type, array: true };
|
|
555
|
-
return { type: value ? ast.expressionName(value) : null, array: false };
|
|
556
|
-
};
|
|
557
|
-
const fieldsOf = (source, object) => (object?.properties ?? []).flatMap((property) => {
|
|
558
|
-
const name = ast.propertyName(property);
|
|
559
|
-
const node = keyName(property);
|
|
560
|
-
if (!name || !node)
|
|
561
|
-
return [];
|
|
562
|
-
const { root, methods } = ts.isPropertyAssignment(property) ? chainOf(property.initializer) : { root: null, methods: [] };
|
|
563
|
-
return [
|
|
564
|
-
{
|
|
565
|
-
name,
|
|
566
|
-
span: spanOf(source, node),
|
|
567
|
-
extent: extentOf(source, property),
|
|
568
|
-
...typeOf(root?.arguments[0]),
|
|
569
|
-
optional: methods.includes("optional")
|
|
570
|
-
}
|
|
571
|
-
];
|
|
572
|
-
});
|
|
573
|
-
const constantOf = (source, name, call) => {
|
|
574
|
-
const parents = call.arguments.map(unwrap).filter((arg) => ts.isIdentifier(arg) || ts.isPropertyAccessExpression(arg)).map((arg) => ast.expressionName(arg)).filter((parent) => parent !== null);
|
|
575
|
-
const fields = call.arguments.flatMap((arg) => fieldsOf(source, ast.firstObjectReturnedByArrow(arg)));
|
|
576
|
-
return { ...name, parents, fields };
|
|
577
|
-
};
|
|
578
|
-
const enumValuesOf = (call) => {
|
|
579
|
-
const list = call.arguments[1] && unwrap(call.arguments[1]);
|
|
580
|
-
if (!list || !ts.isArrayLiteralExpression(list))
|
|
581
|
-
return [];
|
|
582
|
-
return list.elements.flatMap((element) => ts.isStringLiteralLike(element) ? [element.text] : []);
|
|
583
|
-
};
|
|
584
|
-
const methodsOf = (source, statement) => statement.members.flatMap((member) => {
|
|
585
|
-
if (!ts.isMethodDeclaration(member) || !member.body)
|
|
586
|
-
return [];
|
|
587
|
-
const name = ast.nodeName(member.name);
|
|
588
|
-
if (!name || ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))
|
|
589
|
-
return [];
|
|
590
|
-
return [{ name, span: spanOf(source, member.name), extent: extentOf(source, member) }];
|
|
591
|
-
});
|
|
592
|
-
const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
|
|
593
|
-
const read = (source, owner) => {
|
|
594
|
-
const facts = {
|
|
595
|
-
fileName: source.fileName,
|
|
596
|
-
owner,
|
|
597
|
-
constants: [],
|
|
598
|
-
enums: [],
|
|
599
|
-
filters: [],
|
|
600
|
-
models: [],
|
|
601
|
-
services: [],
|
|
602
|
-
slices: [],
|
|
603
|
-
endpoints: [],
|
|
604
|
-
stores: []
|
|
605
|
-
};
|
|
606
|
-
for (const statement of source.statements) {
|
|
607
|
-
if (!ts.isClassDeclaration(statement) || !statement.name)
|
|
608
|
-
continue;
|
|
609
|
-
const heritage = ast.heritageCallNamed(statement, moduleCallees);
|
|
610
|
-
if (!heritage)
|
|
611
|
-
continue;
|
|
612
|
-
const { callee, call } = heritage;
|
|
613
|
-
const name = { name: statement.name.text, span: spanOf(source, statement.name) };
|
|
614
|
-
const builder = builderOf(call);
|
|
615
|
-
if (callee === "via")
|
|
616
|
-
facts.constants.push(constantOf(source, name, call));
|
|
617
|
-
else if (callee === "enumOf")
|
|
618
|
-
facts.enums.push({ ...name, values: enumValuesOf(call) });
|
|
619
|
-
else if (callee === "from")
|
|
620
|
-
facts.filters.push({ ...name, queries: filterQueriesOf(source, builder) });
|
|
621
|
-
else if (callee === "into")
|
|
622
|
-
facts.models.push({ ...name, model: name.name.endsWith("Model") ? lowerFirst(name.name.slice(0, -5)) : null });
|
|
623
|
-
else if (callee === "serve")
|
|
624
|
-
facts.services.push({
|
|
625
|
-
...name,
|
|
626
|
-
model: moduleRefOf(call.arguments[0], "db"),
|
|
627
|
-
methods: methodsOf(source, statement)
|
|
628
|
-
});
|
|
629
|
-
else if (callee === "slice")
|
|
630
|
-
facts.slices.push({
|
|
631
|
-
...name,
|
|
632
|
-
model: moduleRefOf(call.arguments[0], "srv"),
|
|
633
|
-
guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
|
|
634
|
-
keys: sliceKeysOf(source, builder)
|
|
635
|
-
});
|
|
636
|
-
else if (callee === "endpoint")
|
|
637
|
-
facts.endpoints.push({
|
|
638
|
-
...name,
|
|
639
|
-
model: moduleRefOf(call.arguments[0], "srv"),
|
|
640
|
-
keys: endpointKeysOf(source, builder)
|
|
641
|
-
});
|
|
642
|
-
else if (callee === "store")
|
|
643
|
-
facts.stores.push({
|
|
644
|
-
...name,
|
|
645
|
-
model: moduleRefOf(call.arguments[0], "sig"),
|
|
646
|
-
stateKeys: objectKeys(source, returnedObject(builder)),
|
|
647
|
-
actions: methodsOf(source, statement)
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
return facts;
|
|
651
|
-
};
|
|
652
|
-
return { read, spanOf, moduleRefOf, chainOf, unwrap };
|
|
653
|
-
};
|
|
654
|
-
|
|
655
1472
|
// pkgs/akanjs/common/capitalize.ts
|
|
656
1473
|
var capitalize2 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
657
1474
|
var lowerlize = (str) => str.charAt(0).toLowerCase() + str.slice(1);
|
|
@@ -1103,6 +1920,7 @@ class NameTable {
|
|
|
1103
1920
|
// pkgs/@akanjs/language/akanIndex.ts
|
|
1104
1921
|
var sourcePattern = /\.(constant|document|service|signal|store)\.ts$/;
|
|
1105
1922
|
var dictionaryPattern = /\.dictionary\.ts$/;
|
|
1923
|
+
var srvkitPattern = /\/srvkit\//;
|
|
1106
1924
|
var wordPattern = /[A-Za-z_$][\w$]*/g;
|
|
1107
1925
|
var identifierPattern = /^[A-Za-z_$][\w$]*$/;
|
|
1108
1926
|
var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
|
|
@@ -1138,21 +1956,26 @@ class AkanIndex {
|
|
|
1138
1956
|
}
|
|
1139
1957
|
setFile(givenName, text) {
|
|
1140
1958
|
const fileName = pathOf(givenName);
|
|
1141
|
-
|
|
1959
|
+
const words = `
|
|
1142
1960
|
${[...new Set(text.match(wordPattern))].join(`
|
|
1143
1961
|
`)}
|
|
1144
|
-
|
|
1145
|
-
|
|
1962
|
+
`;
|
|
1963
|
+
this.#fileWords.set(fileName, words);
|
|
1964
|
+
const isSource = AkanIndex.isSourcePath(fileName);
|
|
1965
|
+
if (!isSource && !AkanIndex.#holdsFacts(fileName, words)) {
|
|
1966
|
+
this.#facts.delete(fileName);
|
|
1146
1967
|
return;
|
|
1968
|
+
}
|
|
1147
1969
|
const source = this.#parsed.get(fileName)?.text === text ? this.#parse(fileName, text) : this.#parseUncached(fileName, text);
|
|
1148
1970
|
this.#facts.set(fileName, this.#reader.read(source, this.#ownerOf(fileName)));
|
|
1149
|
-
|
|
1971
|
+
if (isSource)
|
|
1972
|
+
this.#table = null;
|
|
1150
1973
|
}
|
|
1151
1974
|
removeFile(givenName) {
|
|
1152
1975
|
const fileName = pathOf(givenName);
|
|
1153
1976
|
this.#fileWords.delete(fileName);
|
|
1154
1977
|
this.#parsed.delete(fileName);
|
|
1155
|
-
if (this.#facts.delete(fileName))
|
|
1978
|
+
if (this.#facts.delete(fileName) && AkanIndex.isSourcePath(fileName))
|
|
1156
1979
|
this.#table = null;
|
|
1157
1980
|
}
|
|
1158
1981
|
definitionAt(givenName, text, offset) {
|
|
@@ -1200,7 +2023,8 @@ ${[...new Set(text.match(wordPattern))].join(`
|
|
|
1200
2023
|
return site ? this.#resolve(fileName, site) : [];
|
|
1201
2024
|
},
|
|
1202
2025
|
relative: (fileName) => this.#relative(fileName),
|
|
1203
|
-
ownerOf: (fileName) => this.#ownerOf(fileName)
|
|
2026
|
+
ownerOf: (fileName) => this.#ownerOf(fileName),
|
|
2027
|
+
reader: this.#reader
|
|
1204
2028
|
}).build();
|
|
1205
2029
|
}
|
|
1206
2030
|
renameTargetAt(givenName, offset) {
|
|
@@ -1223,6 +2047,22 @@ ${[...new Set(text.match(wordPattern))].join(`
|
|
|
1223
2047
|
}))
|
|
1224
2048
|
];
|
|
1225
2049
|
}
|
|
2050
|
+
static #holdsFacts(fileName, words) {
|
|
2051
|
+
if (testFilePattern.test(fileName))
|
|
2052
|
+
return false;
|
|
2053
|
+
if (srvkitPattern.test(fileName))
|
|
2054
|
+
return true;
|
|
2055
|
+
const holdsGuards = words.includes(`
|
|
2056
|
+
Guard
|
|
2057
|
+
`) && words.includes(`
|
|
2058
|
+
canPass
|
|
2059
|
+
`);
|
|
2060
|
+
return holdsGuards || words.includes(`
|
|
2061
|
+
adapt
|
|
2062
|
+
`) || words.includes(`
|
|
2063
|
+
dangerouslyAdapt
|
|
2064
|
+
`);
|
|
2065
|
+
}
|
|
1226
2066
|
static #sourceKeyOf({ family, sourceKey, label, detail, span }) {
|
|
1227
2067
|
return { family, sourceKey, label, detail, span };
|
|
1228
2068
|
}
|
|
@@ -1313,7 +2153,7 @@ ${dictionary.className}
|
|
|
1313
2153
|
return this.#nameTable().entriesTargeting(fileName).filter((entry) => entry.span.start <= offset && offset <= entry.span.end);
|
|
1314
2154
|
}
|
|
1315
2155
|
#nameTable() {
|
|
1316
|
-
this.#table ??= new NameTable(this.#facts.values());
|
|
2156
|
+
this.#table ??= new NameTable([...this.#facts.values()].filter(({ fileName }) => AkanIndex.isSourcePath(fileName)));
|
|
1317
2157
|
return this.#table;
|
|
1318
2158
|
}
|
|
1319
2159
|
#relative(fileName) {
|