@akanjs/language 3.2.0-rc.0 → 3.2.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/akanGraph.d.ts +1 -1
- package/akanGraphDiff.d.ts +1 -1
- package/escapeRegExp.d.ts +1 -0
- package/guardFacts.d.ts +43 -0
- package/guardResolver.d.ts +54 -0
- package/index.cjs +932 -431
- package/index.js +932 -431
- package/moduleFacts.d.ts +2 -14
- package/package.json +1 -1
- package/routeFacts.d.ts +5 -2
- package/server.js +896 -430
package/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
|
}
|
|
@@ -222,7 +440,7 @@ var createFactReader = (ts, ast) => {
|
|
|
222
440
|
return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
|
|
223
441
|
});
|
|
224
442
|
};
|
|
225
|
-
const endpointKeysOf = (source, builder) => {
|
|
443
|
+
const endpointKeysOf = (source, builder, made, model) => {
|
|
226
444
|
const verbOf = verbReaderOf(builder?.parameters[0]);
|
|
227
445
|
return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
228
446
|
if (!ts.isPropertyAssignment(property))
|
|
@@ -237,7 +455,7 @@ var createFactReader = (ts, ast) => {
|
|
|
237
455
|
span: spanOf(source, property.name),
|
|
238
456
|
extent: extentOf(source, property),
|
|
239
457
|
verb: root ? verbOf(root.expression) : null,
|
|
240
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
458
|
+
guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
|
|
241
459
|
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
242
460
|
}
|
|
243
461
|
];
|
|
@@ -304,22 +522,6 @@ var createFactReader = (ts, ast) => {
|
|
|
304
522
|
})) : []
|
|
305
523
|
];
|
|
306
524
|
};
|
|
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
525
|
const docOf = (source, node) => {
|
|
324
526
|
const { text } = source;
|
|
325
527
|
const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
|
|
@@ -335,108 +537,20 @@ var createFactReader = (ts, ast) => {
|
|
|
335
537
|
`)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
|
|
336
538
|
return lines.length ? lines.join(" ") : null;
|
|
337
539
|
};
|
|
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
|
-
};
|
|
540
|
+
const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
|
|
431
541
|
const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
|
|
432
542
|
const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
|
|
433
543
|
const read = (source, owner) => {
|
|
434
544
|
const imports = importsIn(source);
|
|
545
|
+
const madeInPlace = [];
|
|
435
546
|
const facts = {
|
|
436
547
|
fileName: source.fileName,
|
|
437
548
|
owner,
|
|
438
549
|
imports,
|
|
439
|
-
|
|
550
|
+
guards: [],
|
|
551
|
+
guardFactories: [],
|
|
552
|
+
guardCalls: [],
|
|
553
|
+
guardLabels: {},
|
|
440
554
|
constants: [],
|
|
441
555
|
enums: [],
|
|
442
556
|
filters: [],
|
|
@@ -454,64 +568,496 @@ var createFactReader = (ts, ast) => {
|
|
|
454
568
|
const heritage = ast.heritageCallNamed(statement, moduleCallees);
|
|
455
569
|
if (!heritage)
|
|
456
570
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
571
|
+
const { callee, call } = heritage;
|
|
572
|
+
const name = { name: statement.name.text, span: spanOf(source, statement.name) };
|
|
573
|
+
const builder = builderOf(call);
|
|
574
|
+
if (callee === "via")
|
|
575
|
+
facts.constants.push(constantOf(source, name, call));
|
|
576
|
+
else if (callee === "enumOf")
|
|
577
|
+
facts.enums.push({ ...name, values: enumValuesOf(call) });
|
|
578
|
+
else if (callee === "from")
|
|
579
|
+
facts.filters.push({ ...name, queries: filterQueriesOf(source, builder) });
|
|
580
|
+
else if (callee === "into")
|
|
581
|
+
facts.models.push({ ...name, model: name.name.endsWith("Model") ? lowerFirst(name.name.slice(0, -5)) : null });
|
|
582
|
+
else if (callee === "serve")
|
|
583
|
+
facts.services.push({
|
|
584
|
+
...name,
|
|
585
|
+
model: moduleRefOf(call.arguments[0], "db"),
|
|
586
|
+
methods: methodsOf(source, statement),
|
|
587
|
+
plugs: plugsOf(source, builder)
|
|
588
|
+
});
|
|
589
|
+
else if (callee === "slice") {
|
|
590
|
+
const model = moduleRefOf(call.arguments[0], "srv");
|
|
591
|
+
const member = model ?? name.name;
|
|
592
|
+
facts.slices.push({
|
|
593
|
+
...name,
|
|
594
|
+
model,
|
|
595
|
+
guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
|
|
596
|
+
keys: sliceKeysOf(source, builder, madeInPlace, member)
|
|
597
|
+
});
|
|
598
|
+
} else if (callee === "internal")
|
|
599
|
+
facts.internals.push({
|
|
600
|
+
...name,
|
|
601
|
+
model: moduleRefOf(call.arguments[0], "srv"),
|
|
602
|
+
keys: internalKeysOf(source, builder)
|
|
603
|
+
});
|
|
604
|
+
else if (callee === "adapt" || callee === "dangerouslyAdapt") {
|
|
605
|
+
const refName = call.arguments[0] && unwrap(call.arguments[0]);
|
|
606
|
+
facts.adaptors.push({
|
|
607
|
+
...name,
|
|
608
|
+
extent: extentOf(source, statement),
|
|
609
|
+
refName: refName && ts.isStringLiteralLike(refName) ? refName.text : null,
|
|
610
|
+
plugs: plugsOf(source, builder),
|
|
611
|
+
doc: docOf(source, statement)
|
|
612
|
+
});
|
|
613
|
+
} else if (callee === "endpoint") {
|
|
614
|
+
const model = moduleRefOf(call.arguments[0], "srv");
|
|
615
|
+
facts.endpoints.push({
|
|
616
|
+
...name,
|
|
617
|
+
model,
|
|
618
|
+
keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
|
|
619
|
+
});
|
|
620
|
+
} else if (callee === "store")
|
|
621
|
+
facts.stores.push({
|
|
622
|
+
...name,
|
|
623
|
+
model: moduleRefOf(call.arguments[0], "sig"),
|
|
624
|
+
stateKeys: objectKeys(source, returnedObject(builder)),
|
|
625
|
+
actions: methodsOf(source, statement)
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
return { ...facts, ...guardsOf(source, imports, madeInPlace) };
|
|
629
|
+
};
|
|
630
|
+
return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
// pkgs/@akanjs/language/guardResolver.ts
|
|
634
|
+
var frameworkOwner = "akanjs";
|
|
635
|
+
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
636
|
+
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
637
|
+
var maxMapDepth = 4;
|
|
638
|
+
var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
|
|
639
|
+
var declaringPatternOf = (name, isAnyBinding) => {
|
|
640
|
+
const escaped = escapeRegExp(name);
|
|
641
|
+
const braced = "\\{(?:[^}]*?[\\s,])?";
|
|
642
|
+
const leads = [
|
|
643
|
+
"function\\s*\\*?\\s*",
|
|
644
|
+
"(?:const|let|var)\\s+",
|
|
645
|
+
`export\\s*${braced}`,
|
|
646
|
+
...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
|
|
647
|
+
];
|
|
648
|
+
return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
class GuardResolver {
|
|
652
|
+
#source;
|
|
653
|
+
#routes;
|
|
654
|
+
#guards = new Map;
|
|
655
|
+
#madeInPlace = new Map;
|
|
656
|
+
#mapEntries = new Map;
|
|
657
|
+
#reexports = new Map;
|
|
658
|
+
#declarations = new Map;
|
|
659
|
+
#starExports = new Map;
|
|
660
|
+
#ownerFiles = null;
|
|
661
|
+
#modules = null;
|
|
662
|
+
constructor(source, routes) {
|
|
663
|
+
this.#source = source;
|
|
664
|
+
this.#routes = routes;
|
|
665
|
+
}
|
|
666
|
+
find(local, site) {
|
|
667
|
+
return this.#findGuard(local, site, 0);
|
|
668
|
+
}
|
|
669
|
+
madeInPlace(fileName, name) {
|
|
670
|
+
return this.#madeInPlace.get(`${fileName}
|
|
671
|
+
${name}`);
|
|
672
|
+
}
|
|
673
|
+
declare(facts) {
|
|
674
|
+
const factories = new Map;
|
|
675
|
+
const declaredFactories = [];
|
|
676
|
+
for (const file of facts)
|
|
677
|
+
for (const fact of file.guardFactories) {
|
|
678
|
+
const factory = {
|
|
679
|
+
fact,
|
|
680
|
+
owner: file.owner,
|
|
681
|
+
site: file,
|
|
682
|
+
made: [],
|
|
683
|
+
isReal: fact.direct,
|
|
684
|
+
scope: fact.scope,
|
|
685
|
+
targets: []
|
|
686
|
+
};
|
|
687
|
+
factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
|
|
688
|
+
declaredFactories.push(factory);
|
|
689
|
+
}
|
|
690
|
+
for (const factory of declaredFactories)
|
|
691
|
+
factory.targets = factory.fact.via.map((callee) => this.#factoryAt(factories, callee, factory.site)).filter((target) => target !== undefined);
|
|
692
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
693
|
+
isGrowing = false;
|
|
694
|
+
for (const factory of declaredFactories) {
|
|
695
|
+
const target = factory.targets.find((other) => other.isReal);
|
|
696
|
+
if (factory.isReal || !target)
|
|
697
|
+
continue;
|
|
698
|
+
factory.isReal = true;
|
|
699
|
+
factory.scope ??= target.scope;
|
|
700
|
+
isGrowing = true;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const partsOf = (factory, seen = new Set) => {
|
|
704
|
+
if (seen.has(factory))
|
|
705
|
+
return [];
|
|
706
|
+
seen.add(factory);
|
|
707
|
+
return [
|
|
708
|
+
{ fileName: factory.site.fileName, extent: factory.fact.extent },
|
|
709
|
+
...factory.targets.filter((target) => target.isReal).flatMap((target) => partsOf(target, seen))
|
|
710
|
+
];
|
|
711
|
+
};
|
|
712
|
+
const declare = (file, made, scope, parts) => {
|
|
713
|
+
const { name, span, extent, doc } = made;
|
|
714
|
+
const label = file.guardLabels[name];
|
|
715
|
+
const own = { fileName: file.fileName, extent };
|
|
716
|
+
const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
|
|
717
|
+
if (name.includes("@"))
|
|
718
|
+
this.#madeInPlace.set(`${file.fileName}
|
|
719
|
+
${name}`, guard);
|
|
720
|
+
else
|
|
721
|
+
this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
|
|
722
|
+
return guard;
|
|
723
|
+
};
|
|
724
|
+
const extending = [];
|
|
725
|
+
for (const file of facts) {
|
|
726
|
+
for (const made of file.guards) {
|
|
727
|
+
const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
|
|
728
|
+
if (!made.factory)
|
|
729
|
+
declare(file, made, made.scope, []);
|
|
730
|
+
else if (factory?.isReal)
|
|
731
|
+
factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
|
|
732
|
+
}
|
|
733
|
+
for (const made of file.guardCalls) {
|
|
734
|
+
if (made.kind === "extends") {
|
|
735
|
+
extending.push([file, made]);
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
const factory = this.#factoryAt(factories, made.callee, file);
|
|
739
|
+
if (factory?.isReal)
|
|
740
|
+
factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
744
|
+
isGrowing = false;
|
|
745
|
+
for (const [idx, entry] of extending.entries()) {
|
|
746
|
+
const [file, made] = entry;
|
|
747
|
+
const found = this.#findGuard(made.callee, file, 0, true);
|
|
748
|
+
if (!("guard" in found))
|
|
749
|
+
continue;
|
|
750
|
+
declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
|
|
751
|
+
extending.splice(idx, 1);
|
|
752
|
+
isGrowing = true;
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
for (const { fact, made, isReal } of declaredFactories)
|
|
757
|
+
if (isReal && made.every((guard) => !guard.doc))
|
|
758
|
+
for (const guard of made)
|
|
759
|
+
guard.doc = fact.doc;
|
|
760
|
+
for (const list of this.#guards.values())
|
|
761
|
+
for (const guard of list)
|
|
762
|
+
if (list.some((other) => other !== guard && other.owner === guard.owner))
|
|
763
|
+
guard.file = this.inOwner(guard.span.fileName, guard.owner);
|
|
764
|
+
for (const guard of this.#madeInPlace.values())
|
|
765
|
+
guard.file = this.inOwner(guard.span.fileName, guard.owner);
|
|
766
|
+
return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
|
|
767
|
+
}
|
|
768
|
+
#factoryAt(factories, callee, site, depth = 0) {
|
|
769
|
+
const [head = callee, member] = callee.split(".");
|
|
770
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
771
|
+
if (!imported)
|
|
772
|
+
return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
|
|
773
|
+
if (member && imported.imported !== "*")
|
|
774
|
+
return;
|
|
775
|
+
const from = this.ownerOfSpecifier(site.fileName, imported.from);
|
|
776
|
+
if (from === undefined || from === frameworkOwner)
|
|
777
|
+
return;
|
|
778
|
+
const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
|
|
779
|
+
const candidates = factories.get(name) ?? [];
|
|
780
|
+
const near = this.pathOfSpecifier(site.fileName, imported.from);
|
|
781
|
+
const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
|
|
782
|
+
if (own)
|
|
783
|
+
return own;
|
|
784
|
+
const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
|
|
785
|
+
if (reexport)
|
|
786
|
+
return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
|
|
787
|
+
return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
|
|
788
|
+
}
|
|
789
|
+
#findGuard(local, site, depth, isBase = false) {
|
|
790
|
+
const segments = local.split(".");
|
|
791
|
+
const head = segments[0] ?? local;
|
|
792
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
793
|
+
const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
|
|
794
|
+
if (imported && from === undefined)
|
|
795
|
+
return { external: true };
|
|
796
|
+
const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
|
|
797
|
+
const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
|
|
798
|
+
const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
|
|
799
|
+
const holder = imported ? from ?? null : site.owner;
|
|
800
|
+
const [objectName, member] = path;
|
|
801
|
+
if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
|
|
802
|
+
const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
|
|
803
|
+
if (made)
|
|
804
|
+
return { guard: made };
|
|
805
|
+
for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
|
|
806
|
+
const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
|
|
807
|
+
if ("guard" in found)
|
|
808
|
+
return found;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const name = path[path.length - 1] ?? head;
|
|
812
|
+
if (from === frameworkOwner)
|
|
813
|
+
return { name, from };
|
|
814
|
+
const declared = this.#guards.get(name) ?? [];
|
|
815
|
+
const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
|
|
816
|
+
if (own)
|
|
817
|
+
return { guard: own };
|
|
818
|
+
if (!imported) {
|
|
819
|
+
const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
|
|
820
|
+
return visible ? { guard: visible } : { name, from };
|
|
821
|
+
}
|
|
822
|
+
const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
|
|
823
|
+
if (reexport)
|
|
824
|
+
return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
|
|
825
|
+
const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
|
|
826
|
+
return reached ? { guard: reached } : { name, from };
|
|
827
|
+
}
|
|
828
|
+
reexportOf(owner, name, near) {
|
|
829
|
+
const key = `${owner}
|
|
830
|
+
${name}
|
|
831
|
+
${near ?? ""}`;
|
|
832
|
+
const cached = this.#reexports.get(key);
|
|
833
|
+
if (cached !== undefined)
|
|
834
|
+
return cached;
|
|
835
|
+
let found = null;
|
|
836
|
+
const exporting = new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
|
|
837
|
+
for (const fileName of this.#filesOf(owner, name, near)) {
|
|
838
|
+
const text = this.#source.readText(fileName);
|
|
839
|
+
if (text === undefined || !exporting.test(text))
|
|
840
|
+
continue;
|
|
841
|
+
const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
|
|
842
|
+
if (!reexport)
|
|
843
|
+
continue;
|
|
844
|
+
const site = { fileName, owner, imports: [] };
|
|
845
|
+
found = reexport.from ? {
|
|
846
|
+
local: reexport.name,
|
|
847
|
+
site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
|
|
848
|
+
} : { local: reexport.name, site };
|
|
849
|
+
break;
|
|
850
|
+
}
|
|
851
|
+
this.#reexports.set(key, found);
|
|
852
|
+
return found;
|
|
853
|
+
}
|
|
854
|
+
#reexportIn(source, name) {
|
|
855
|
+
const { ts, reader } = this.#source;
|
|
856
|
+
for (const statement of source.statements) {
|
|
857
|
+
if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
|
|
858
|
+
continue;
|
|
859
|
+
const { exportClause, moduleSpecifier } = statement;
|
|
860
|
+
if (!exportClause || !ts.isNamedExports(exportClause))
|
|
861
|
+
continue;
|
|
862
|
+
const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
|
|
863
|
+
if (!element)
|
|
864
|
+
continue;
|
|
865
|
+
const original = (element.propertyName ?? element.name).text;
|
|
866
|
+
if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
|
|
867
|
+
return { name: original, from: moduleSpecifier.text };
|
|
868
|
+
const imported = reader.importsIn(source).find((item) => item.name === original);
|
|
869
|
+
if (!imported)
|
|
870
|
+
return { name: original, from: null };
|
|
871
|
+
return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
|
|
872
|
+
}
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
#filesOf(owner, name, near) {
|
|
876
|
+
const isNear = (fileName) => {
|
|
877
|
+
const relative = this.#source.relative(fileName);
|
|
878
|
+
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
879
|
+
};
|
|
880
|
+
const candidates = this.filesIn(owner).filter(([, words]) => words.includes(`
|
|
881
|
+
${name}
|
|
882
|
+
`)).map(([fileName]) => fileName);
|
|
883
|
+
return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
|
|
884
|
+
}
|
|
885
|
+
#mapEntriesOf(objectName, member, fileName, owner) {
|
|
886
|
+
const key = `${objectName}
|
|
887
|
+
${member}
|
|
888
|
+
${fileName ?? ""}
|
|
889
|
+
${owner ?? ""}`;
|
|
890
|
+
const cached = this.#mapEntries.get(key);
|
|
891
|
+
if (cached)
|
|
892
|
+
return cached;
|
|
893
|
+
const { ts, reader } = this.#source;
|
|
894
|
+
const entries = [];
|
|
895
|
+
const fileNames = [
|
|
896
|
+
...fileName ? [fileName] : [],
|
|
897
|
+
...(owner === null ? [] : this.filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
|
|
898
|
+
${objectName}
|
|
899
|
+
`) && words.includes(`
|
|
900
|
+
${member}
|
|
901
|
+
`)).map(([other]) => other)
|
|
902
|
+
];
|
|
903
|
+
for (const holder of fileNames) {
|
|
904
|
+
const text = this.#source.readText(holder);
|
|
905
|
+
if (text === undefined)
|
|
906
|
+
continue;
|
|
907
|
+
const source = this.#source.parse(holder, text);
|
|
908
|
+
const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
|
|
909
|
+
for (const statement of source.statements) {
|
|
910
|
+
if (!ts.isVariableStatement(statement))
|
|
911
|
+
continue;
|
|
912
|
+
const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
|
|
913
|
+
const object = map && reader.unwrap(map);
|
|
914
|
+
if (!object || !ts.isObjectLiteralExpression(object))
|
|
915
|
+
continue;
|
|
916
|
+
for (const property of object.properties) {
|
|
917
|
+
if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
|
|
918
|
+
entries.push({ name: member, site });
|
|
919
|
+
if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
|
|
920
|
+
continue;
|
|
921
|
+
const value = reader.unwrap(property.initializer);
|
|
922
|
+
if (ts.isIdentifier(value))
|
|
923
|
+
entries.push({ name: value.text, site });
|
|
924
|
+
else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
|
|
925
|
+
entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
this.#mapEntries.set(key, entries);
|
|
930
|
+
return entries;
|
|
931
|
+
}
|
|
932
|
+
declarationOf(owner, name, near, depth = 0) {
|
|
933
|
+
const key = `${owner}
|
|
934
|
+
${name}
|
|
935
|
+
${near ?? ""}`;
|
|
936
|
+
const cached = this.#declarations.get(key);
|
|
937
|
+
if (cached !== undefined)
|
|
938
|
+
return cached;
|
|
939
|
+
this.#declarations.set(key, null);
|
|
940
|
+
const declaring = declaringPatternOf(name, false);
|
|
941
|
+
let found = null;
|
|
942
|
+
for (const fileName of this.#filesOf(owner, name, near)) {
|
|
943
|
+
const text = this.#source.readText(fileName);
|
|
944
|
+
if (text === undefined || !declaring.test(text))
|
|
945
|
+
continue;
|
|
946
|
+
const source = this.#source.parse(fileName, text);
|
|
947
|
+
const declared = this.#routes.declarationIn(source, name);
|
|
948
|
+
if (declared) {
|
|
949
|
+
found = { source, ...declared, name };
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
const reexport = this.#reexportIn(source, name);
|
|
953
|
+
if (!reexport)
|
|
954
|
+
continue;
|
|
955
|
+
if (!reexport.from) {
|
|
956
|
+
const local = this.#routes.declarationIn(source, reexport.name);
|
|
957
|
+
found = local ? { source, ...local, name: reexport.name } : null;
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
const next = this.ownerOfSpecifier(fileName, reexport.from);
|
|
961
|
+
if (next && next !== frameworkOwner && depth < maxMapDepth)
|
|
962
|
+
found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
|
|
963
|
+
break;
|
|
510
964
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
965
|
+
this.#declarations.set(key, found);
|
|
966
|
+
return found;
|
|
967
|
+
}
|
|
968
|
+
#throughStar(declared, owner, name, near) {
|
|
969
|
+
if (!declared.length)
|
|
970
|
+
return;
|
|
971
|
+
const { owners, isOpaque } = this.#starExportsOf(owner, near);
|
|
972
|
+
const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
|
|
973
|
+
return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
|
|
974
|
+
}
|
|
975
|
+
#starExportsOf(owner, near) {
|
|
976
|
+
const cached = this.#starExports.get(near);
|
|
977
|
+
if (cached)
|
|
978
|
+
return cached;
|
|
979
|
+
const found = { owners: [], isOpaque: false };
|
|
980
|
+
this.#starExports.set(near, found);
|
|
981
|
+
const paths = [near];
|
|
982
|
+
for (let idx = 0;idx < paths.length && !found.isOpaque; idx++)
|
|
983
|
+
for (const fileName of this.#modulesAt(paths[idx] ?? "")) {
|
|
984
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
|
|
985
|
+
const target = this.ownerOfSpecifier(fileName, specifier);
|
|
986
|
+
const path = this.pathOfSpecifier(fileName, specifier);
|
|
987
|
+
if (target === undefined || target === frameworkOwner || path === null)
|
|
988
|
+
found.isOpaque = true;
|
|
989
|
+
else if (!paths.includes(path))
|
|
990
|
+
paths.push(path);
|
|
991
|
+
if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
|
|
992
|
+
found.owners.push(target);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return found;
|
|
996
|
+
}
|
|
997
|
+
#modulesAt(path) {
|
|
998
|
+
if (!this.#modules) {
|
|
999
|
+
this.#modules = new Map;
|
|
1000
|
+
for (const [fileName] of this.#source.files) {
|
|
1001
|
+
const module = this.#source.relative(fileName).replace(/\.tsx?$/, "");
|
|
1002
|
+
for (const key of module.endsWith("/index") ? [module, module.slice(0, -6)] : [module])
|
|
1003
|
+
this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return this.#modules.get(path) ?? [];
|
|
1007
|
+
}
|
|
1008
|
+
declares(owner, name) {
|
|
1009
|
+
const declaring = declaringPatternOf(name, true);
|
|
1010
|
+
return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
|
|
1011
|
+
}
|
|
1012
|
+
filesIn(owner) {
|
|
1013
|
+
if (!this.#ownerFiles) {
|
|
1014
|
+
this.#ownerFiles = new Map;
|
|
1015
|
+
for (const entry of this.#source.files) {
|
|
1016
|
+
const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
|
|
1017
|
+
const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
|
|
1018
|
+
if (files)
|
|
1019
|
+
files.push(entry);
|
|
1020
|
+
else if (fileOwner !== null)
|
|
1021
|
+
this.#ownerFiles.set(fileOwner, [entry]);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return this.#ownerFiles.get(owner) ?? [];
|
|
1025
|
+
}
|
|
1026
|
+
#nearest(items, fileOf, near) {
|
|
1027
|
+
const isNear = (fileName) => {
|
|
1028
|
+
const relative = this.#source.relative(fileName);
|
|
1029
|
+
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
1030
|
+
};
|
|
1031
|
+
return items.find((item) => isNear(fileOf(item))) ?? items[0];
|
|
1032
|
+
}
|
|
1033
|
+
inOwner(fileName, owner) {
|
|
1034
|
+
const relative = this.#source.relative(fileName);
|
|
1035
|
+
return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
|
|
1036
|
+
}
|
|
1037
|
+
ownerOfSpecifier(fileName, specifier) {
|
|
1038
|
+
if (frameworkPattern.test(specifier))
|
|
1039
|
+
return frameworkOwner;
|
|
1040
|
+
const path = this.pathOfSpecifier(fileName, specifier);
|
|
1041
|
+
return path === null ? undefined : ownerOf(path) ?? undefined;
|
|
1042
|
+
}
|
|
1043
|
+
pathOfSpecifier(fileName, specifier) {
|
|
1044
|
+
const alias = specifier.match(aliasPattern);
|
|
1045
|
+
if (alias)
|
|
1046
|
+
return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
|
|
1047
|
+
if (!specifier.startsWith("."))
|
|
1048
|
+
return null;
|
|
1049
|
+
const segments = this.#source.relative(fileName).split("/").slice(0, -1);
|
|
1050
|
+
for (const segment of specifier.split("/"))
|
|
1051
|
+
if (segment === "..")
|
|
1052
|
+
segments.pop();
|
|
1053
|
+
else if (segment !== ".")
|
|
1054
|
+
segments.push(segment);
|
|
1055
|
+
return segments.join("/");
|
|
1056
|
+
}
|
|
1057
|
+
visible(target, from) {
|
|
1058
|
+
return !from || !target || target === from || target.startsWith("libs/");
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
515
1061
|
|
|
516
1062
|
// pkgs/akanjs/common/trimSlashes.ts
|
|
517
1063
|
var trimTrailingSlashes = (value) => {
|
|
@@ -660,24 +1206,23 @@ rootLayout
|
|
|
660
1206
|
}
|
|
661
1207
|
declarationIn(source, name) {
|
|
662
1208
|
const ts = this.#ts;
|
|
1209
|
+
const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
|
|
1210
|
+
const [first] = overloads;
|
|
1211
|
+
const last = overloads.at(-1);
|
|
1212
|
+
if (first?.name && last)
|
|
1213
|
+
return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
|
|
663
1214
|
for (const statement of source.statements) {
|
|
664
|
-
if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
|
|
665
|
-
return { statement, declared: statement.name, initializer: null };
|
|
666
1215
|
if (!ts.isVariableStatement(statement))
|
|
667
1216
|
continue;
|
|
668
1217
|
const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
|
|
669
1218
|
if (declaration)
|
|
670
|
-
return { statement, declared: declaration.name,
|
|
1219
|
+
return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
|
|
671
1220
|
}
|
|
672
1221
|
return null;
|
|
673
1222
|
}
|
|
674
1223
|
#guardOf(source, exported) {
|
|
675
1224
|
const ts = this.#ts;
|
|
676
1225
|
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
1226
|
while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
682
1227
|
const { name, expression } = current.expression;
|
|
683
1228
|
const [stage] = current.arguments;
|
|
@@ -689,9 +1234,20 @@ rootLayout
|
|
|
689
1234
|
}
|
|
690
1235
|
#callsIn(source, stage) {
|
|
691
1236
|
const ts = this.#ts;
|
|
692
|
-
const
|
|
1237
|
+
const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
|
|
1238
|
+
const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
|
|
1239
|
+
const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
|
|
693
1240
|
const calls = new Map;
|
|
694
|
-
const callOf = (
|
|
1241
|
+
const callOf = (callee, shadowed) => {
|
|
1242
|
+
if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
|
|
1243
|
+
const name = `${callee.expression.text}.${callee.name.text}`;
|
|
1244
|
+
if (!calls.has(name))
|
|
1245
|
+
calls.set(name, { name, local: null });
|
|
1246
|
+
return true;
|
|
1247
|
+
}
|
|
1248
|
+
if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
|
|
1249
|
+
return false;
|
|
1250
|
+
const name = callee.text;
|
|
695
1251
|
if (calls.has(name))
|
|
696
1252
|
return true;
|
|
697
1253
|
const declared = this.declarationIn(source, name);
|
|
@@ -701,23 +1257,71 @@ rootLayout
|
|
|
701
1257
|
name,
|
|
702
1258
|
local: declared && {
|
|
703
1259
|
span: this.#reader.spanOf(source, declared.declared),
|
|
704
|
-
extent:
|
|
1260
|
+
extent: declared.extent,
|
|
705
1261
|
doc: this.#reader.docOf(source, declared.statement)
|
|
706
1262
|
}
|
|
707
1263
|
});
|
|
708
1264
|
return true;
|
|
709
1265
|
};
|
|
710
|
-
const visit = (node) => {
|
|
711
|
-
|
|
1266
|
+
const visit = (node, outer) => {
|
|
1267
|
+
const shadowed = this.#scopeOf(node, outer);
|
|
1268
|
+
if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
|
|
712
1269
|
return;
|
|
713
|
-
ts.forEachChild(node, visit);
|
|
1270
|
+
ts.forEachChild(node, (child) => visit(child, shadowed));
|
|
714
1271
|
};
|
|
715
1272
|
if (ts.isIdentifier(stage))
|
|
716
|
-
callOf(stage
|
|
1273
|
+
callOf(stage, new Set);
|
|
717
1274
|
else
|
|
718
|
-
visit(stage);
|
|
1275
|
+
visit(stage, new Set);
|
|
719
1276
|
return [...calls.values()];
|
|
720
1277
|
}
|
|
1278
|
+
#scopeOf(node, outer) {
|
|
1279
|
+
const ts = this.#ts;
|
|
1280
|
+
const names = [];
|
|
1281
|
+
const bind = (name) => {
|
|
1282
|
+
if (ts.isIdentifier(name))
|
|
1283
|
+
names.push(name.text);
|
|
1284
|
+
else
|
|
1285
|
+
for (const element of name.elements)
|
|
1286
|
+
if (!ts.isOmittedExpression(element))
|
|
1287
|
+
bind(element.name);
|
|
1288
|
+
};
|
|
1289
|
+
const declare = (statement) => {
|
|
1290
|
+
if (ts.isVariableStatement(statement))
|
|
1291
|
+
for (const declaration of statement.declarationList.declarations)
|
|
1292
|
+
bind(declaration.name);
|
|
1293
|
+
else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
|
|
1294
|
+
names.push(statement.name.text);
|
|
1295
|
+
};
|
|
1296
|
+
const hoist = (child) => {
|
|
1297
|
+
if (ts.isFunctionLike(child) || ts.isClassLike(child))
|
|
1298
|
+
return;
|
|
1299
|
+
if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
|
|
1300
|
+
for (const declaration of child.declarations)
|
|
1301
|
+
bind(declaration.name);
|
|
1302
|
+
ts.forEachChild(child, hoist);
|
|
1303
|
+
};
|
|
1304
|
+
if (ts.isFunctionLike(node)) {
|
|
1305
|
+
for (const parameter of node.parameters)
|
|
1306
|
+
bind(parameter.name);
|
|
1307
|
+
if (ts.isFunctionExpression(node) && node.name)
|
|
1308
|
+
names.push(node.name.text);
|
|
1309
|
+
if ("body" in node && node.body)
|
|
1310
|
+
ts.forEachChild(node.body, hoist);
|
|
1311
|
+
} else if (ts.isClassExpression(node) && node.name)
|
|
1312
|
+
names.push(node.name.text);
|
|
1313
|
+
else if (ts.isBlock(node))
|
|
1314
|
+
node.statements.forEach(declare);
|
|
1315
|
+
else if (ts.isCaseBlock(node))
|
|
1316
|
+
for (const clause of node.clauses)
|
|
1317
|
+
clause.statements.forEach(declare);
|
|
1318
|
+
else if (ts.isCatchClause(node) && node.variableDeclaration)
|
|
1319
|
+
bind(node.variableDeclaration.name);
|
|
1320
|
+
else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
|
|
1321
|
+
for (const declaration of node.initializer.declarations)
|
|
1322
|
+
bind(declaration.name);
|
|
1323
|
+
return names.length ? new Set([...outer, ...names]) : outer;
|
|
1324
|
+
}
|
|
721
1325
|
}
|
|
722
1326
|
|
|
723
1327
|
// pkgs/@akanjs/language/akanGraph.ts
|
|
@@ -733,10 +1337,6 @@ var builtinGuards = {
|
|
|
733
1337
|
Public: { scope: "account", doc: "Passes every caller, signed in or not." },
|
|
734
1338
|
None: { scope: "account", doc: "Refuses every caller." }
|
|
735
1339
|
};
|
|
736
|
-
var frameworkOwner = "akanjs";
|
|
737
|
-
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
738
|
-
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
739
|
-
var maxMapDepth = 4;
|
|
740
1340
|
|
|
741
1341
|
class GraphBuilder {
|
|
742
1342
|
#source;
|
|
@@ -744,16 +1344,15 @@ class GraphBuilder {
|
|
|
744
1344
|
#edges = new Map;
|
|
745
1345
|
#contexts = new Map;
|
|
746
1346
|
#members = new Map;
|
|
747
|
-
#guards = new Map;
|
|
748
|
-
#mapEntries = new Map;
|
|
749
1347
|
#routeFiles = new Map;
|
|
750
|
-
#declarations = new Map;
|
|
751
1348
|
#routes;
|
|
1349
|
+
#resolver;
|
|
752
1350
|
#adaptors = new Map;
|
|
753
1351
|
#plugs = new Map;
|
|
754
1352
|
constructor(source) {
|
|
755
1353
|
this.#source = source;
|
|
756
1354
|
this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
|
|
1355
|
+
this.#resolver = new GuardResolver(source, this.#routes);
|
|
757
1356
|
}
|
|
758
1357
|
build() {
|
|
759
1358
|
const { facts } = this.#source;
|
|
@@ -775,7 +1374,8 @@ class GraphBuilder {
|
|
|
775
1374
|
for (const fact of file.adaptors)
|
|
776
1375
|
for (const plug of fact.plugs)
|
|
777
1376
|
this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
|
|
778
|
-
this.#
|
|
1377
|
+
for (const guard of this.#resolver.declare(facts))
|
|
1378
|
+
this.#guardNodeOf(guard);
|
|
779
1379
|
for (const file of facts) {
|
|
780
1380
|
const { owner } = file;
|
|
781
1381
|
const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
|
|
@@ -819,31 +1419,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
|
819
1419
|
for (const query of filter.queries)
|
|
820
1420
|
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
1421
|
}
|
|
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
|
-
}
|
|
1422
|
+
this.#addGuarded(file);
|
|
847
1423
|
for (const internal of file.internals)
|
|
848
1424
|
for (const key of internal.keys)
|
|
849
1425
|
if (internal.model && key.type !== "resolveField")
|
|
@@ -859,7 +1435,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
|
859
1435
|
types.set(node.label, [...types.get(node.label) ?? [], node]);
|
|
860
1436
|
for (const node of typed)
|
|
861
1437
|
for (const field of node.fields ?? []) {
|
|
862
|
-
const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
|
|
1438
|
+
const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
|
|
863
1439
|
field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
|
|
864
1440
|
if (field.ref)
|
|
865
1441
|
this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
|
|
@@ -941,61 +1517,52 @@ ${label ?? ""}`;
|
|
|
941
1517
|
else
|
|
942
1518
|
this.#edges.set(key, { from, to, kind, label, count: 1, at });
|
|
943
1519
|
}
|
|
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);
|
|
1520
|
+
#addGuarded(file) {
|
|
1521
|
+
const { owner } = file;
|
|
1522
|
+
for (const slice of file.slices) {
|
|
1523
|
+
const refName = slice.model;
|
|
1524
|
+
if (!refName)
|
|
1525
|
+
continue;
|
|
1526
|
+
const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
|
|
1527
|
+
for (const guard of slice.guards)
|
|
1528
|
+
this.#guard(fallback, guard, file);
|
|
1529
|
+
for (const key of slice.keys) {
|
|
1530
|
+
const detail = [
|
|
1531
|
+
...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
|
|
1532
|
+
...key.live ? ["live"] : []
|
|
1533
|
+
];
|
|
1534
|
+
const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
1535
|
+
for (const guard of key.guards)
|
|
1536
|
+
this.#guard(node, guard, file);
|
|
976
1537
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
|
|
1538
|
+
}
|
|
1539
|
+
for (const endpoint of file.endpoints) {
|
|
1540
|
+
const refName = endpoint.model;
|
|
1541
|
+
if (!refName)
|
|
1542
|
+
continue;
|
|
1543
|
+
for (const key of endpoint.keys) {
|
|
1544
|
+
const detail = [
|
|
1545
|
+
...key.verb ? [key.verb] : [],
|
|
1546
|
+
...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
|
|
1547
|
+
];
|
|
1548
|
+
const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
1549
|
+
for (const guard of key.guards)
|
|
1550
|
+
this.#guard(node, guard, file);
|
|
986
1551
|
}
|
|
987
1552
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1553
|
+
}
|
|
1554
|
+
#guard(node, guard, file) {
|
|
1555
|
+
const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
|
|
1556
|
+
if (!local || local === "?")
|
|
1557
|
+
return;
|
|
1558
|
+
const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
|
|
1559
|
+
if (id)
|
|
1560
|
+
this.#addEdge(node.id, id, "guard", role, node.at);
|
|
996
1561
|
}
|
|
997
1562
|
#guardIdOf(local, site) {
|
|
998
|
-
const found = this.#
|
|
1563
|
+
const found = this.#resolver.find(local, site);
|
|
1564
|
+
if ("external" in found)
|
|
1565
|
+
return null;
|
|
999
1566
|
if ("guard" in found)
|
|
1000
1567
|
return this.#guardNodeOf(found.guard);
|
|
1001
1568
|
const { name, from } = found;
|
|
@@ -1008,99 +1575,18 @@ ${label ?? ""}`;
|
|
|
1008
1575
|
node.doc = builtin.doc;
|
|
1009
1576
|
return node.id;
|
|
1010
1577
|
}
|
|
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;
|
|
1578
|
+
#madeInPlaceIdOf(name, file) {
|
|
1579
|
+
const made = this.#resolver.madeInPlace(file.fileName, name);
|
|
1580
|
+
if (made)
|
|
1581
|
+
return this.#guardNodeOf(made);
|
|
1582
|
+
const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
|
|
1583
|
+
const imported = file.imports.find((item) => item.name === head);
|
|
1584
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
|
|
1585
|
+
if (from === undefined || from === frameworkOwner)
|
|
1586
|
+
return null;
|
|
1587
|
+
const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
|
|
1588
|
+
const label = guardLabelOf(name, file.guardLabels);
|
|
1589
|
+
return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
|
|
1104
1590
|
}
|
|
1105
1591
|
#addRoutes() {
|
|
1106
1592
|
const routes = [];
|
|
@@ -1153,22 +1639,23 @@ ${dir}`, node);
|
|
|
1153
1639
|
}
|
|
1154
1640
|
#routeGuardIdOf({ name: local, local: declared }, fact, text) {
|
|
1155
1641
|
if (declared) {
|
|
1156
|
-
const
|
|
1157
|
-
const id = this.#key("guard", fact.owner, `${file}#${local}`);
|
|
1642
|
+
const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
|
|
1158
1643
|
return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
|
|
1159
1644
|
}
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1645
|
+
const [head = local, member] = local.split(".");
|
|
1646
|
+
const imported = fact.imports.find((item) => item.name === head);
|
|
1647
|
+
if (!imported || member && imported.imported !== "*")
|
|
1162
1648
|
return null;
|
|
1163
|
-
const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
|
|
1164
|
-
const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
|
|
1649
|
+
const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
|
|
1650
|
+
const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
|
|
1165
1651
|
if (!from || from === frameworkOwner)
|
|
1166
1652
|
return null;
|
|
1167
|
-
const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
|
|
1653
|
+
const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
|
|
1168
1654
|
if (!found)
|
|
1169
1655
|
return null;
|
|
1170
1656
|
const { reader } = this.#source;
|
|
1171
|
-
|
|
1657
|
+
const owner = this.#source.ownerOf(found.source.fileName) ?? from;
|
|
1658
|
+
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
1659
|
}
|
|
1173
1660
|
#routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
|
|
1174
1661
|
const isNew = !this.#nodes.has(id);
|
|
@@ -1181,10 +1668,10 @@ ${dir}`, node);
|
|
|
1181
1668
|
#adaptorIdOf(local, file) {
|
|
1182
1669
|
const [head, member] = local.split(".");
|
|
1183
1670
|
const imported = file.imports.find((item) => item.name === head);
|
|
1184
|
-
const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1671
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1185
1672
|
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1186
1673
|
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)));
|
|
1674
|
+
const found = from === undefined ? declared.find((adaptor) => adaptor.owner === file.owner) ?? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, file.owner)) : declared.find((adaptor) => adaptor.owner === from) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#resolver.visible(adaptor.owner, from)));
|
|
1188
1675
|
if (found)
|
|
1189
1676
|
return this.#adaptorNodeOf(found);
|
|
1190
1677
|
const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
|
|
@@ -1200,38 +1687,22 @@ ${dir}`, node);
|
|
|
1200
1687
|
this.#digest(node, this.#source.readText(fileName), fact.extent);
|
|
1201
1688
|
return node.id;
|
|
1202
1689
|
}
|
|
1203
|
-
#guardNodeOf({ name, owner, span, scope, doc, parts }) {
|
|
1204
|
-
const
|
|
1690
|
+
#guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
|
|
1691
|
+
const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
|
|
1692
|
+
const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
|
|
1205
1693
|
if (doc && !node.doc)
|
|
1206
1694
|
node.doc = doc;
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1695
|
+
if (node.digest)
|
|
1696
|
+
return node.id;
|
|
1697
|
+
const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
|
|
1698
|
+
if (!digest)
|
|
1699
|
+
return node.id;
|
|
1700
|
+
node.digest = digest;
|
|
1701
|
+
const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
|
|
1702
|
+
if (sources.length)
|
|
1703
|
+
node.sources = sources;
|
|
1213
1704
|
return node.id;
|
|
1214
1705
|
}
|
|
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
1706
|
#moduleDirOf(fileName) {
|
|
1236
1707
|
const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
|
|
1237
1708
|
return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
|
|
@@ -1267,20 +1738,13 @@ ${dir}`, node);
|
|
|
1267
1738
|
visit(constant);
|
|
1268
1739
|
return [...fields.values()];
|
|
1269
1740
|
}
|
|
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
1741
|
#targetNodeOf({ family, owner, refName, sourceKey }) {
|
|
1278
1742
|
const isDefault = /^[A-Z]/.test(sourceKey);
|
|
1279
1743
|
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
1744
|
return id && this.#nodes.has(id) ? id : null;
|
|
1281
1745
|
}
|
|
1282
1746
|
#membersNamed(kind, name, owner, refName) {
|
|
1283
|
-
const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
|
|
1747
|
+
const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
|
|
1284
1748
|
const own = nodes.filter((node) => node.owner === owner);
|
|
1285
1749
|
return (own.length ? own : nodes).map((node) => node.id);
|
|
1286
1750
|
}
|
|
@@ -1634,6 +2098,10 @@ var endpointNames = (key) => {
|
|
|
1634
2098
|
};
|
|
1635
2099
|
|
|
1636
2100
|
// pkgs/@akanjs/language/nameTable.ts
|
|
2101
|
+
var guardDetailOf = (facts, guards) => {
|
|
2102
|
+
const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
|
|
2103
|
+
return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
|
|
2104
|
+
};
|
|
1637
2105
|
var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
|
|
1638
2106
|
|
|
1639
2107
|
class NameTable {
|
|
@@ -1733,7 +2201,7 @@ class NameTable {
|
|
|
1733
2201
|
role: "sig",
|
|
1734
2202
|
gate: "sig",
|
|
1735
2203
|
label: sliceClass.name,
|
|
1736
|
-
detail:
|
|
2204
|
+
detail: guardDetailOf(facts, sliceClass.guards)
|
|
1737
2205
|
});
|
|
1738
2206
|
if (modelDoc)
|
|
1739
2207
|
this.#add({
|
|
@@ -1798,7 +2266,7 @@ class NameTable {
|
|
|
1798
2266
|
sourceKey: slice.name,
|
|
1799
2267
|
span: slice.span,
|
|
1800
2268
|
label: `slice ${refName} (default)`,
|
|
1801
|
-
detail:
|
|
2269
|
+
detail: guardDetailOf(facts, slice.guards)
|
|
1802
2270
|
};
|
|
1803
2271
|
this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
|
|
1804
2272
|
for (const key of slice.keys)
|
|
@@ -1808,7 +2276,7 @@ class NameTable {
|
|
|
1808
2276
|
sourceKey: key.name,
|
|
1809
2277
|
span: key.span,
|
|
1810
2278
|
label: `slice ${key.name}`,
|
|
1811
|
-
detail: [...
|
|
2279
|
+
detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
|
|
1812
2280
|
}, refName, key.name, modelNameSet, slice.name);
|
|
1813
2281
|
}
|
|
1814
2282
|
if (filterClass) {
|
|
@@ -1865,7 +2333,7 @@ class NameTable {
|
|
|
1865
2333
|
role: key.verb === "pubsub" ? "subscribe" : "listen",
|
|
1866
2334
|
gate: null,
|
|
1867
2335
|
label: `${key.verb} ${key.name}`,
|
|
1868
|
-
detail:
|
|
2336
|
+
detail: guardDetailOf(facts, key.guards)
|
|
1869
2337
|
});
|
|
1870
2338
|
}
|
|
1871
2339
|
}
|
|
@@ -1923,14 +2391,12 @@ var dictionaryPattern = /\.dictionary\.ts$/;
|
|
|
1923
2391
|
var srvkitPattern = /\/srvkit\//;
|
|
1924
2392
|
var wordPattern = /[A-Za-z_$][\w$]*/g;
|
|
1925
2393
|
var identifierPattern = /^[A-Za-z_$][\w$]*$/;
|
|
1926
|
-
var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
|
|
1927
2394
|
var stateReceivers = new Set(["use", "sel", "get", "do"]);
|
|
1928
2395
|
var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
|
|
1929
2396
|
var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
|
|
1930
2397
|
var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
|
|
1931
2398
|
var parseCacheSize = 64;
|
|
1932
2399
|
var pathOf = (fileName) => fileName.replace(/\\/g, "/");
|
|
1933
|
-
var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
|
|
1934
2400
|
var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
|
|
1935
2401
|
|
|
1936
2402
|
class AkanIndex {
|