@akanjs/language 3.2.0-rc.1 → 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/index.js
CHANGED
|
@@ -8,7 +8,8 @@ var isEdited = (before, after) => before.digest !== undefined && after.digest !=
|
|
|
8
8
|
var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
9
9
|
|
|
10
10
|
class AkanGraphDiff {
|
|
11
|
-
static between(
|
|
11
|
+
static between(given, head) {
|
|
12
|
+
const base = AkanGraphDiff.#renamed(given, head);
|
|
12
13
|
const baseNodes = new Map(base.nodes.map((node) => [node.id, node]));
|
|
13
14
|
const headNodes = new Map(head.nodes.map((node) => [node.id, node]));
|
|
14
15
|
const marked = new Map;
|
|
@@ -38,6 +39,40 @@ class AkanGraphDiff {
|
|
|
38
39
|
summary[node.change] += 1;
|
|
39
40
|
return { version: 1, nodes, edges, summary };
|
|
40
41
|
}
|
|
42
|
+
static #renamed(base, head) {
|
|
43
|
+
const baseIds = new Set(base.nodes.map((node) => node.id));
|
|
44
|
+
const headIds = new Set(head.nodes.map((node) => node.id));
|
|
45
|
+
const pairKeyOf = ({ kind, owner, name, at }) => at ? `${kind}
|
|
46
|
+
${owner ?? ""}
|
|
47
|
+
${name}
|
|
48
|
+
${at.file}` : null;
|
|
49
|
+
const shortIdOf = ({ kind, owner, name }) => `${kind}:${owner ?? ""}:${name}`;
|
|
50
|
+
const isFileKeyed = (node) => node.id !== shortIdOf(node) && node.id.startsWith(`${node.kind}:${node.owner ?? ""}:`) && node.id.endsWith(`#${node.name}`);
|
|
51
|
+
const fromBase = new Map(base.nodes.flatMap((node) => {
|
|
52
|
+
const key = headIds.has(node.id) ? null : pairKeyOf(node);
|
|
53
|
+
return key ? [[key, node]] : [];
|
|
54
|
+
}));
|
|
55
|
+
const renames = new Map;
|
|
56
|
+
for (const node of head.nodes) {
|
|
57
|
+
if (baseIds.has(node.id))
|
|
58
|
+
continue;
|
|
59
|
+
const key = pairKeyOf(node);
|
|
60
|
+
const before = key ? fromBase.get(key) : undefined;
|
|
61
|
+
if (!before)
|
|
62
|
+
continue;
|
|
63
|
+
const isPair = isFileKeyed(node) ? before.id === shortIdOf(node) : isFileKeyed(before) && node.id === shortIdOf(before);
|
|
64
|
+
if (isPair)
|
|
65
|
+
renames.set(before.id, node.id);
|
|
66
|
+
}
|
|
67
|
+
if (!renames.size)
|
|
68
|
+
return base;
|
|
69
|
+
const rename = (id) => renames.get(id) ?? id;
|
|
70
|
+
return {
|
|
71
|
+
...base,
|
|
72
|
+
nodes: base.nodes.map((node) => renames.has(node.id) ? { ...node, id: rename(node.id) } : node),
|
|
73
|
+
edges: base.edges.map((edge) => ({ ...edge, from: rename(edge.from), to: rename(edge.to) }))
|
|
74
|
+
};
|
|
75
|
+
}
|
|
41
76
|
static #mark(node, change) {
|
|
42
77
|
return node.fields ? { ...node, change, fields: node.fields.map((field) => ({ ...field, change })) } : { ...node, change };
|
|
43
78
|
}
|
|
@@ -71,6 +106,239 @@ class AkanGraphDiff {
|
|
|
71
106
|
return node.fields ? { ...node, fields: node.fields.map(({ digest: _field, ...field }) => field) } : node;
|
|
72
107
|
}
|
|
73
108
|
}
|
|
109
|
+
// pkgs/@akanjs/language/guardFacts.ts
|
|
110
|
+
var guardLabelOf = (guard, labels) => {
|
|
111
|
+
const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
|
|
112
|
+
const label = labels[local];
|
|
113
|
+
return label === undefined ? guard : role ? `${role} ${label}` : label;
|
|
114
|
+
};
|
|
115
|
+
var maxLabelArgs = 32;
|
|
116
|
+
var guardRefReaderOf = (ts, ast, unwrap) => {
|
|
117
|
+
const pathOf = (expression) => {
|
|
118
|
+
if (ts.isIdentifier(expression))
|
|
119
|
+
return expression.text;
|
|
120
|
+
if (!ts.isPropertyAccessExpression(expression))
|
|
121
|
+
return null;
|
|
122
|
+
const head = pathOf(expression.expression);
|
|
123
|
+
return head && `${head}.${expression.name.text}`;
|
|
124
|
+
};
|
|
125
|
+
const calleeOf = (call) => pathOf(unwrap(call.expression)) ?? "?";
|
|
126
|
+
const labelOf = (source, call) => {
|
|
127
|
+
const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
|
|
128
|
+
return `${calleeOf(call)}(${args.length <= maxLabelArgs && !args.includes(`
|
|
129
|
+
`) ? args : "…"})`;
|
|
130
|
+
};
|
|
131
|
+
const guardRefOf = (expression, made, member) => {
|
|
132
|
+
const value = unwrap(expression);
|
|
133
|
+
if (!ts.isCallExpression(value))
|
|
134
|
+
return pathOf(value) ?? ast.expressionName(value);
|
|
135
|
+
const prefix = `${member}:${calleeOf(value)}@`;
|
|
136
|
+
const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
|
|
137
|
+
made.push({ call: value, name });
|
|
138
|
+
return name;
|
|
139
|
+
};
|
|
140
|
+
const guardsIn = (source, object, made, member) => {
|
|
141
|
+
const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
|
|
142
|
+
if (!guards)
|
|
143
|
+
return [];
|
|
144
|
+
const refOf = (expression) => guardRefOf(expression, made, member);
|
|
145
|
+
if (ts.isArrayLiteralExpression(guards))
|
|
146
|
+
return guards.elements.map(refOf).filter((name) => name !== null);
|
|
147
|
+
if (ts.isObjectLiteralExpression(guards))
|
|
148
|
+
return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
|
|
149
|
+
const value = unwrap(property.initializer);
|
|
150
|
+
const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
|
|
151
|
+
return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
|
|
152
|
+
});
|
|
153
|
+
const single = refOf(guards);
|
|
154
|
+
return single ? [single] : [];
|
|
155
|
+
};
|
|
156
|
+
return { pathOf, labelOf, guardsIn };
|
|
157
|
+
};
|
|
158
|
+
var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf) => {
|
|
159
|
+
const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
|
|
160
|
+
const scopeOf = (node) => {
|
|
161
|
+
const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
|
|
162
|
+
const value = scope && unwrap(scope);
|
|
163
|
+
return value && ts.isStringLiteralLike(value) ? value.text : null;
|
|
164
|
+
};
|
|
165
|
+
const functionsOf = (source) => {
|
|
166
|
+
const functions = new Map;
|
|
167
|
+
for (const statement of source.statements) {
|
|
168
|
+
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
|
|
169
|
+
functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
|
|
170
|
+
if (!ts.isVariableStatement(statement))
|
|
171
|
+
continue;
|
|
172
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
173
|
+
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
174
|
+
if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
|
|
175
|
+
functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return functions;
|
|
179
|
+
};
|
|
180
|
+
const returnedOf = (body, guardNames) => {
|
|
181
|
+
const locals = new Map;
|
|
182
|
+
const pending = [];
|
|
183
|
+
const visit = (node) => {
|
|
184
|
+
if (ts.isClassDeclaration(node) && node.name)
|
|
185
|
+
locals.set(node.name.text, node);
|
|
186
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
|
|
187
|
+
locals.set(node.name.text, node.initializer);
|
|
188
|
+
if (ts.isReturnStatement(node) && node.expression)
|
|
189
|
+
pending.push(node.expression);
|
|
190
|
+
if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
|
|
191
|
+
ts.forEachChild(node, visit);
|
|
192
|
+
};
|
|
193
|
+
if (ts.isBlock(body))
|
|
194
|
+
ts.forEachChild(body, visit);
|
|
195
|
+
else
|
|
196
|
+
pending.push(body);
|
|
197
|
+
let guard = null;
|
|
198
|
+
const calls = [];
|
|
199
|
+
const seen = new Set;
|
|
200
|
+
const passed = new Set;
|
|
201
|
+
for (let node = pending.pop();node; node = pending.pop()) {
|
|
202
|
+
if (seen.has(node))
|
|
203
|
+
continue;
|
|
204
|
+
seen.add(node);
|
|
205
|
+
const value = ts.isExpression(node) ? unwrap(node) : node;
|
|
206
|
+
if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
|
|
207
|
+
guard ??= value;
|
|
208
|
+
else if (ts.isConditionalExpression(value))
|
|
209
|
+
pending.push(value.whenTrue, value.whenFalse);
|
|
210
|
+
else if (ts.isBinaryExpression(value))
|
|
211
|
+
pending.push(value.left, value.right);
|
|
212
|
+
else if (ts.isIdentifier(value)) {
|
|
213
|
+
const local = locals.get(value.text);
|
|
214
|
+
if (local)
|
|
215
|
+
pending.push(local);
|
|
216
|
+
} else if (ts.isCallExpression(value)) {
|
|
217
|
+
const callee = pathOf(unwrap(value.expression));
|
|
218
|
+
if (callee && !passed.has(node) && !calls.includes(callee))
|
|
219
|
+
calls.push(callee);
|
|
220
|
+
for (const arg of value.arguments)
|
|
221
|
+
passed.add(arg);
|
|
222
|
+
pending.push(...value.arguments);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return { guard, calls };
|
|
226
|
+
};
|
|
227
|
+
const guardFactoriesOf = (source, guardNames, isImported) => {
|
|
228
|
+
const functions = functionsOf(source);
|
|
229
|
+
const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
|
|
230
|
+
const members = new Set;
|
|
231
|
+
const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
|
|
232
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
233
|
+
isGrowing = false;
|
|
234
|
+
for (const [name, { guard, calls }] of returned) {
|
|
235
|
+
if (members.has(name) || !guard && !calls.some(isFactoryCall))
|
|
236
|
+
continue;
|
|
237
|
+
members.add(name);
|
|
238
|
+
isGrowing = true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const factories = new Map;
|
|
242
|
+
for (const name of members) {
|
|
243
|
+
const found = functions.get(name);
|
|
244
|
+
const made = returned.get(name);
|
|
245
|
+
if (!found || !made)
|
|
246
|
+
continue;
|
|
247
|
+
factories.set(name, {
|
|
248
|
+
name,
|
|
249
|
+
span: spanOf(source, found.name),
|
|
250
|
+
extent: extentOf(source, found.statement),
|
|
251
|
+
scope: made.guard ? scopeOf(made.guard) : null,
|
|
252
|
+
doc: docOf(source, found.statement),
|
|
253
|
+
direct: !!made.guard,
|
|
254
|
+
via: made.calls.filter(isFactoryCall)
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return factories;
|
|
258
|
+
};
|
|
259
|
+
return { implementsGuard, scopeOf, guardFactoriesOf };
|
|
260
|
+
};
|
|
261
|
+
var createGuardFactReader = (ts, ast, base) => {
|
|
262
|
+
const { spanOf, extentOf, unwrap, docOf } = base;
|
|
263
|
+
const { pathOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
|
|
264
|
+
const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf);
|
|
265
|
+
const guardsOf = (source, imports, madeInPlace) => {
|
|
266
|
+
const guardNames = new Set([
|
|
267
|
+
"Guard",
|
|
268
|
+
...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
|
|
269
|
+
]);
|
|
270
|
+
const imported = new Set(imports.map(({ name }) => name));
|
|
271
|
+
const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
|
|
272
|
+
const factories = guardFactoriesOf(source, guardNames, isImported);
|
|
273
|
+
const guards = [];
|
|
274
|
+
const calls = [];
|
|
275
|
+
const makeOf = (made, value, isInPlace = false) => {
|
|
276
|
+
if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
|
|
277
|
+
guards.push({ ...made, scope: scopeOf(value), factory: null });
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const callee = ts.isCallExpression(value) ? pathOf(unwrap(value.expression)) : null;
|
|
281
|
+
const factory = callee && factories.get(callee);
|
|
282
|
+
if (factory)
|
|
283
|
+
guards.push({ ...made, scope: factory.scope, factory: factory.name });
|
|
284
|
+
else if (callee && (isImported(callee) || isInPlace))
|
|
285
|
+
calls.push({ ...made, callee, kind: "call", scope: null });
|
|
286
|
+
};
|
|
287
|
+
for (const statement of source.statements) {
|
|
288
|
+
if (ts.isClassDeclaration(statement) && statement.name) {
|
|
289
|
+
const declared = {
|
|
290
|
+
name: statement.name.text,
|
|
291
|
+
span: spanOf(source, statement.name),
|
|
292
|
+
extent: extentOf(source, statement),
|
|
293
|
+
scope: scopeOf(statement),
|
|
294
|
+
doc: docOf(source, statement)
|
|
295
|
+
};
|
|
296
|
+
const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
|
|
297
|
+
const callee = base && pathOf(base.expression);
|
|
298
|
+
if (implementsGuard(statement, guardNames))
|
|
299
|
+
guards.push({ ...declared, factory: null });
|
|
300
|
+
else if (callee)
|
|
301
|
+
calls.push({ ...declared, callee, kind: "extends" });
|
|
302
|
+
}
|
|
303
|
+
if (!ts.isVariableStatement(statement))
|
|
304
|
+
continue;
|
|
305
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
306
|
+
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
307
|
+
if (!initializer || !ts.isIdentifier(declaration.name))
|
|
308
|
+
continue;
|
|
309
|
+
const holder = declaration.name.text;
|
|
310
|
+
makeOf({
|
|
311
|
+
name: holder,
|
|
312
|
+
span: spanOf(source, declaration.name),
|
|
313
|
+
extent: extentOf(source, statement),
|
|
314
|
+
doc: docOf(source, statement)
|
|
315
|
+
}, initializer);
|
|
316
|
+
if (!ts.isObjectLiteralExpression(initializer))
|
|
317
|
+
continue;
|
|
318
|
+
for (const property of initializer.properties) {
|
|
319
|
+
const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
|
|
320
|
+
if (key && ts.isPropertyAssignment(property))
|
|
321
|
+
makeOf({
|
|
322
|
+
name: `${holder}.${key}`,
|
|
323
|
+
span: spanOf(source, property.name),
|
|
324
|
+
extent: extentOf(source, property),
|
|
325
|
+
doc: docOf(source, property)
|
|
326
|
+
}, unwrap(property.initializer));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
for (const { call, name } of madeInPlace)
|
|
331
|
+
makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
|
|
332
|
+
const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
|
|
333
|
+
return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
|
|
334
|
+
};
|
|
335
|
+
return { guardsIn, guardsOf };
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// pkgs/@akanjs/language/escapeRegExp.ts
|
|
339
|
+
var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
|
|
340
|
+
var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
|
|
341
|
+
|
|
74
342
|
// pkgs/@akanjs/language/moduleFacts.ts
|
|
75
343
|
var testFilePattern = /\.(?:test|spec)\.tsx?$/;
|
|
76
344
|
var moduleCallees = new Set([
|
|
@@ -139,21 +407,6 @@ var createFactReader = (ts, ast) => {
|
|
|
139
407
|
const value = unwrap(expression);
|
|
140
408
|
return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
|
|
141
409
|
};
|
|
142
|
-
const guardsIn = (object) => {
|
|
143
|
-
const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
|
|
144
|
-
if (!guards)
|
|
145
|
-
return [];
|
|
146
|
-
if (ts.isArrayLiteralExpression(guards))
|
|
147
|
-
return guards.elements.map(guardNameOf).filter((name) => name !== null);
|
|
148
|
-
if (ts.isObjectLiteralExpression(guards))
|
|
149
|
-
return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
|
|
150
|
-
const value = unwrap(property.initializer);
|
|
151
|
-
const names = ts.isArrayLiteralExpression(value) ? value.elements.map(guardNameOf) : [guardNameOf(value)];
|
|
152
|
-
return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
|
|
153
|
-
});
|
|
154
|
-
const single = guardNameOf(guards);
|
|
155
|
-
return single ? [single] : [];
|
|
156
|
-
};
|
|
157
410
|
const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
|
|
158
411
|
const chainOf = (initializer) => {
|
|
159
412
|
let root = unwrap(initializer);
|
|
@@ -170,7 +423,7 @@ var createFactReader = (ts, ast) => {
|
|
|
170
423
|
const node = keyName(property);
|
|
171
424
|
return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
|
|
172
425
|
});
|
|
173
|
-
const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
426
|
+
const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
174
427
|
if (!ts.isPropertyAssignment(property))
|
|
175
428
|
return [];
|
|
176
429
|
const name = ast.nodeName(property.name);
|
|
@@ -182,7 +435,7 @@ var createFactReader = (ts, ast) => {
|
|
|
182
435
|
name,
|
|
183
436
|
span: spanOf(source, property.name),
|
|
184
437
|
extent: extentOf(source, property),
|
|
185
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
438
|
+
guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
|
|
186
439
|
live: methods.includes("live"),
|
|
187
440
|
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
188
441
|
}
|
|
@@ -288,7 +541,7 @@ var createFactReader = (ts, ast) => {
|
|
|
288
541
|
return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
|
|
289
542
|
});
|
|
290
543
|
};
|
|
291
|
-
const endpointKeysOf = (source, builder) => {
|
|
544
|
+
const endpointKeysOf = (source, builder, made, model) => {
|
|
292
545
|
const verbOf = verbReaderOf(builder?.parameters[0]);
|
|
293
546
|
return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
294
547
|
if (!ts.isPropertyAssignment(property))
|
|
@@ -303,7 +556,7 @@ var createFactReader = (ts, ast) => {
|
|
|
303
556
|
span: spanOf(source, property.name),
|
|
304
557
|
extent: extentOf(source, property),
|
|
305
558
|
verb: root ? verbOf(root.expression) : null,
|
|
306
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
559
|
+
guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
|
|
307
560
|
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
308
561
|
}
|
|
309
562
|
];
|
|
@@ -370,22 +623,6 @@ var createFactReader = (ts, ast) => {
|
|
|
370
623
|
})) : []
|
|
371
624
|
];
|
|
372
625
|
};
|
|
373
|
-
const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
|
|
374
|
-
const guardClassIn = (node, guardNames) => {
|
|
375
|
-
if ((ts.isClassExpression(node) || ts.isClassDeclaration(node)) && implementsGuard(node, guardNames))
|
|
376
|
-
return node;
|
|
377
|
-
return ts.forEachChild(node, (child) => guardClassIn(child, guardNames)) ?? null;
|
|
378
|
-
};
|
|
379
|
-
const callsIn = (node, names) => {
|
|
380
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text))
|
|
381
|
-
return node.expression.text;
|
|
382
|
-
return ts.forEachChild(node, (child) => callsIn(child, names)) ?? null;
|
|
383
|
-
};
|
|
384
|
-
const scopeOf = (node) => {
|
|
385
|
-
const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
|
|
386
|
-
const value = scope && unwrap(scope);
|
|
387
|
-
return value && ts.isStringLiteralLike(value) ? value.text : null;
|
|
388
|
-
};
|
|
389
626
|
const docOf = (source, node) => {
|
|
390
627
|
const { text } = source;
|
|
391
628
|
const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
|
|
@@ -401,108 +638,20 @@ var createFactReader = (ts, ast) => {
|
|
|
401
638
|
`)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
|
|
402
639
|
return lines.length ? lines.join(" ") : null;
|
|
403
640
|
};
|
|
404
|
-
const
|
|
405
|
-
const functions = new Map;
|
|
406
|
-
for (const statement of source.statements) {
|
|
407
|
-
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
|
|
408
|
-
functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
|
|
409
|
-
if (!ts.isVariableStatement(statement))
|
|
410
|
-
continue;
|
|
411
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
412
|
-
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
413
|
-
if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
|
|
414
|
-
functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
return functions;
|
|
418
|
-
};
|
|
419
|
-
const guardFactoriesOf = (source, guardNames) => {
|
|
420
|
-
const functions = functionsOf(source);
|
|
421
|
-
const factories = new Map;
|
|
422
|
-
const factoryOf = (name, scope, via) => {
|
|
423
|
-
const found = functions.get(name);
|
|
424
|
-
if (!found)
|
|
425
|
-
return;
|
|
426
|
-
factories.set(name, {
|
|
427
|
-
name,
|
|
428
|
-
span: spanOf(source, found.name),
|
|
429
|
-
extent: extentOf(source, found.statement),
|
|
430
|
-
scope,
|
|
431
|
-
doc: docOf(source, found.statement),
|
|
432
|
-
via
|
|
433
|
-
});
|
|
434
|
-
};
|
|
435
|
-
for (const [name, { body }] of functions) {
|
|
436
|
-
const guard = guardClassIn(body, guardNames);
|
|
437
|
-
if (guard)
|
|
438
|
-
factoryOf(name, scopeOf(guard), null);
|
|
439
|
-
}
|
|
440
|
-
for (let isGrowing = true;isGrowing; ) {
|
|
441
|
-
isGrowing = false;
|
|
442
|
-
for (const [name, { body }] of functions) {
|
|
443
|
-
const called = factories.has(name) ? null : callsIn(body, new Set(factories.keys()));
|
|
444
|
-
if (!called)
|
|
445
|
-
continue;
|
|
446
|
-
factoryOf(name, factories.get(called)?.scope ?? null, called);
|
|
447
|
-
isGrowing = true;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
return factories;
|
|
451
|
-
};
|
|
452
|
-
const guardsOf = (source, imports) => {
|
|
453
|
-
const guardNames = new Set([
|
|
454
|
-
"Guard",
|
|
455
|
-
...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
|
|
456
|
-
]);
|
|
457
|
-
const imported = new Set(imports.map(({ name }) => name));
|
|
458
|
-
const factories = guardFactoriesOf(source, guardNames);
|
|
459
|
-
const guards = [];
|
|
460
|
-
const calls = [];
|
|
461
|
-
for (const statement of source.statements) {
|
|
462
|
-
if (ts.isClassDeclaration(statement) && statement.name && implementsGuard(statement, guardNames))
|
|
463
|
-
guards.push({
|
|
464
|
-
name: statement.name.text,
|
|
465
|
-
span: spanOf(source, statement.name),
|
|
466
|
-
extent: extentOf(source, statement),
|
|
467
|
-
scope: scopeOf(statement),
|
|
468
|
-
doc: docOf(source, statement),
|
|
469
|
-
factory: null
|
|
470
|
-
});
|
|
471
|
-
if (!ts.isVariableStatement(statement))
|
|
472
|
-
continue;
|
|
473
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
474
|
-
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
475
|
-
if (!initializer || !ts.isIdentifier(declaration.name))
|
|
476
|
-
continue;
|
|
477
|
-
const made = {
|
|
478
|
-
name: declaration.name.text,
|
|
479
|
-
span: spanOf(source, declaration.name),
|
|
480
|
-
extent: extentOf(source, statement),
|
|
481
|
-
doc: docOf(source, statement)
|
|
482
|
-
};
|
|
483
|
-
if (ts.isClassExpression(initializer) && implementsGuard(initializer, guardNames))
|
|
484
|
-
guards.push({ ...made, scope: scopeOf(initializer), factory: null });
|
|
485
|
-
if (!ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression))
|
|
486
|
-
continue;
|
|
487
|
-
const callee = initializer.expression.text;
|
|
488
|
-
const factory = factories.get(callee);
|
|
489
|
-
if (factory)
|
|
490
|
-
guards.push({ ...made, scope: factory.scope, factory: callee });
|
|
491
|
-
else if (imported.has(callee))
|
|
492
|
-
calls.push({ ...made, callee });
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
return { guards, guardFactories: [...factories.values()], guardCalls: calls };
|
|
496
|
-
};
|
|
641
|
+
const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
|
|
497
642
|
const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
|
|
498
643
|
const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
|
|
499
644
|
const read = (source, owner) => {
|
|
500
645
|
const imports = importsIn(source);
|
|
646
|
+
const madeInPlace = [];
|
|
501
647
|
const facts = {
|
|
502
648
|
fileName: source.fileName,
|
|
503
649
|
owner,
|
|
504
650
|
imports,
|
|
505
|
-
|
|
651
|
+
guards: [],
|
|
652
|
+
guardFactories: [],
|
|
653
|
+
guardCalls: [],
|
|
654
|
+
guardLabels: {},
|
|
506
655
|
constants: [],
|
|
507
656
|
enums: [],
|
|
508
657
|
filters: [],
|
|
@@ -520,64 +669,496 @@ var createFactReader = (ts, ast) => {
|
|
|
520
669
|
const heritage = ast.heritageCallNamed(statement, moduleCallees);
|
|
521
670
|
if (!heritage)
|
|
522
671
|
continue;
|
|
523
|
-
const { callee, call } = heritage;
|
|
524
|
-
const name = { name: statement.name.text, span: spanOf(source, statement.name) };
|
|
525
|
-
const builder = builderOf(call);
|
|
526
|
-
if (callee === "via")
|
|
527
|
-
facts.constants.push(constantOf(source, name, call));
|
|
528
|
-
else if (callee === "enumOf")
|
|
529
|
-
facts.enums.push({ ...name, values: enumValuesOf(call) });
|
|
530
|
-
else if (callee === "from")
|
|
531
|
-
facts.filters.push({ ...name, queries: filterQueriesOf(source, builder) });
|
|
532
|
-
else if (callee === "into")
|
|
533
|
-
facts.models.push({ ...name, model: name.name.endsWith("Model") ? lowerFirst(name.name.slice(0, -5)) : null });
|
|
534
|
-
else if (callee === "serve")
|
|
535
|
-
facts.services.push({
|
|
536
|
-
...name,
|
|
537
|
-
model: moduleRefOf(call.arguments[0], "db"),
|
|
538
|
-
methods: methodsOf(source, statement),
|
|
539
|
-
plugs: plugsOf(source, builder)
|
|
540
|
-
});
|
|
541
|
-
else if (callee === "slice")
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
672
|
+
const { callee, call } = heritage;
|
|
673
|
+
const name = { name: statement.name.text, span: spanOf(source, statement.name) };
|
|
674
|
+
const builder = builderOf(call);
|
|
675
|
+
if (callee === "via")
|
|
676
|
+
facts.constants.push(constantOf(source, name, call));
|
|
677
|
+
else if (callee === "enumOf")
|
|
678
|
+
facts.enums.push({ ...name, values: enumValuesOf(call) });
|
|
679
|
+
else if (callee === "from")
|
|
680
|
+
facts.filters.push({ ...name, queries: filterQueriesOf(source, builder) });
|
|
681
|
+
else if (callee === "into")
|
|
682
|
+
facts.models.push({ ...name, model: name.name.endsWith("Model") ? lowerFirst(name.name.slice(0, -5)) : null });
|
|
683
|
+
else if (callee === "serve")
|
|
684
|
+
facts.services.push({
|
|
685
|
+
...name,
|
|
686
|
+
model: moduleRefOf(call.arguments[0], "db"),
|
|
687
|
+
methods: methodsOf(source, statement),
|
|
688
|
+
plugs: plugsOf(source, builder)
|
|
689
|
+
});
|
|
690
|
+
else if (callee === "slice") {
|
|
691
|
+
const model = moduleRefOf(call.arguments[0], "srv");
|
|
692
|
+
const member = model ?? name.name;
|
|
693
|
+
facts.slices.push({
|
|
694
|
+
...name,
|
|
695
|
+
model,
|
|
696
|
+
guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
|
|
697
|
+
keys: sliceKeysOf(source, builder, madeInPlace, member)
|
|
698
|
+
});
|
|
699
|
+
} else if (callee === "internal")
|
|
700
|
+
facts.internals.push({
|
|
701
|
+
...name,
|
|
702
|
+
model: moduleRefOf(call.arguments[0], "srv"),
|
|
703
|
+
keys: internalKeysOf(source, builder)
|
|
704
|
+
});
|
|
705
|
+
else if (callee === "adapt" || callee === "dangerouslyAdapt") {
|
|
706
|
+
const refName = call.arguments[0] && unwrap(call.arguments[0]);
|
|
707
|
+
facts.adaptors.push({
|
|
708
|
+
...name,
|
|
709
|
+
extent: extentOf(source, statement),
|
|
710
|
+
refName: refName && ts.isStringLiteralLike(refName) ? refName.text : null,
|
|
711
|
+
plugs: plugsOf(source, builder),
|
|
712
|
+
doc: docOf(source, statement)
|
|
713
|
+
});
|
|
714
|
+
} else if (callee === "endpoint") {
|
|
715
|
+
const model = moduleRefOf(call.arguments[0], "srv");
|
|
716
|
+
facts.endpoints.push({
|
|
717
|
+
...name,
|
|
718
|
+
model,
|
|
719
|
+
keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
|
|
720
|
+
});
|
|
721
|
+
} else if (callee === "store")
|
|
722
|
+
facts.stores.push({
|
|
723
|
+
...name,
|
|
724
|
+
model: moduleRefOf(call.arguments[0], "sig"),
|
|
725
|
+
stateKeys: objectKeys(source, returnedObject(builder)),
|
|
726
|
+
actions: methodsOf(source, statement)
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
return { ...facts, ...guardsOf(source, imports, madeInPlace) };
|
|
730
|
+
};
|
|
731
|
+
return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
// pkgs/@akanjs/language/guardResolver.ts
|
|
735
|
+
var frameworkOwner = "akanjs";
|
|
736
|
+
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
737
|
+
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
738
|
+
var maxMapDepth = 4;
|
|
739
|
+
var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
|
|
740
|
+
var declaringPatternOf = (name, isAnyBinding) => {
|
|
741
|
+
const escaped = escapeRegExp(name);
|
|
742
|
+
const braced = "\\{(?:[^}]*?[\\s,])?";
|
|
743
|
+
const leads = [
|
|
744
|
+
"function\\s*\\*?\\s*",
|
|
745
|
+
"(?:const|let|var)\\s+",
|
|
746
|
+
`export\\s*${braced}`,
|
|
747
|
+
...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
|
|
748
|
+
];
|
|
749
|
+
return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
class GuardResolver {
|
|
753
|
+
#source;
|
|
754
|
+
#routes;
|
|
755
|
+
#guards = new Map;
|
|
756
|
+
#madeInPlace = new Map;
|
|
757
|
+
#mapEntries = new Map;
|
|
758
|
+
#reexports = new Map;
|
|
759
|
+
#declarations = new Map;
|
|
760
|
+
#starExports = new Map;
|
|
761
|
+
#ownerFiles = null;
|
|
762
|
+
#modules = null;
|
|
763
|
+
constructor(source, routes) {
|
|
764
|
+
this.#source = source;
|
|
765
|
+
this.#routes = routes;
|
|
766
|
+
}
|
|
767
|
+
find(local, site) {
|
|
768
|
+
return this.#findGuard(local, site, 0);
|
|
769
|
+
}
|
|
770
|
+
madeInPlace(fileName, name) {
|
|
771
|
+
return this.#madeInPlace.get(`${fileName}
|
|
772
|
+
${name}`);
|
|
773
|
+
}
|
|
774
|
+
declare(facts) {
|
|
775
|
+
const factories = new Map;
|
|
776
|
+
const declaredFactories = [];
|
|
777
|
+
for (const file of facts)
|
|
778
|
+
for (const fact of file.guardFactories) {
|
|
779
|
+
const factory = {
|
|
780
|
+
fact,
|
|
781
|
+
owner: file.owner,
|
|
782
|
+
site: file,
|
|
783
|
+
made: [],
|
|
784
|
+
isReal: fact.direct,
|
|
785
|
+
scope: fact.scope,
|
|
786
|
+
targets: []
|
|
787
|
+
};
|
|
788
|
+
factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
|
|
789
|
+
declaredFactories.push(factory);
|
|
790
|
+
}
|
|
791
|
+
for (const factory of declaredFactories)
|
|
792
|
+
factory.targets = factory.fact.via.map((callee) => this.#factoryAt(factories, callee, factory.site)).filter((target) => target !== undefined);
|
|
793
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
794
|
+
isGrowing = false;
|
|
795
|
+
for (const factory of declaredFactories) {
|
|
796
|
+
const target = factory.targets.find((other) => other.isReal);
|
|
797
|
+
if (factory.isReal || !target)
|
|
798
|
+
continue;
|
|
799
|
+
factory.isReal = true;
|
|
800
|
+
factory.scope ??= target.scope;
|
|
801
|
+
isGrowing = true;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
const partsOf = (factory, seen = new Set) => {
|
|
805
|
+
if (seen.has(factory))
|
|
806
|
+
return [];
|
|
807
|
+
seen.add(factory);
|
|
808
|
+
return [
|
|
809
|
+
{ fileName: factory.site.fileName, extent: factory.fact.extent },
|
|
810
|
+
...factory.targets.filter((target) => target.isReal).flatMap((target) => partsOf(target, seen))
|
|
811
|
+
];
|
|
812
|
+
};
|
|
813
|
+
const declare = (file, made, scope, parts) => {
|
|
814
|
+
const { name, span, extent, doc } = made;
|
|
815
|
+
const label = file.guardLabels[name];
|
|
816
|
+
const own = { fileName: file.fileName, extent };
|
|
817
|
+
const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
|
|
818
|
+
if (name.includes("@"))
|
|
819
|
+
this.#madeInPlace.set(`${file.fileName}
|
|
820
|
+
${name}`, guard);
|
|
821
|
+
else
|
|
822
|
+
this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
|
|
823
|
+
return guard;
|
|
824
|
+
};
|
|
825
|
+
const extending = [];
|
|
826
|
+
for (const file of facts) {
|
|
827
|
+
for (const made of file.guards) {
|
|
828
|
+
const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
|
|
829
|
+
if (!made.factory)
|
|
830
|
+
declare(file, made, made.scope, []);
|
|
831
|
+
else if (factory?.isReal)
|
|
832
|
+
factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
|
|
833
|
+
}
|
|
834
|
+
for (const made of file.guardCalls) {
|
|
835
|
+
if (made.kind === "extends") {
|
|
836
|
+
extending.push([file, made]);
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
const factory = this.#factoryAt(factories, made.callee, file);
|
|
840
|
+
if (factory?.isReal)
|
|
841
|
+
factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
845
|
+
isGrowing = false;
|
|
846
|
+
for (const [idx, entry] of extending.entries()) {
|
|
847
|
+
const [file, made] = entry;
|
|
848
|
+
const found = this.#findGuard(made.callee, file, 0, true);
|
|
849
|
+
if (!("guard" in found))
|
|
850
|
+
continue;
|
|
851
|
+
declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
|
|
852
|
+
extending.splice(idx, 1);
|
|
853
|
+
isGrowing = true;
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
for (const { fact, made, isReal } of declaredFactories)
|
|
858
|
+
if (isReal && made.every((guard) => !guard.doc))
|
|
859
|
+
for (const guard of made)
|
|
860
|
+
guard.doc = fact.doc;
|
|
861
|
+
for (const list of this.#guards.values())
|
|
862
|
+
for (const guard of list)
|
|
863
|
+
if (list.some((other) => other !== guard && other.owner === guard.owner))
|
|
864
|
+
guard.file = this.inOwner(guard.span.fileName, guard.owner);
|
|
865
|
+
for (const guard of this.#madeInPlace.values())
|
|
866
|
+
guard.file = this.inOwner(guard.span.fileName, guard.owner);
|
|
867
|
+
return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
|
|
868
|
+
}
|
|
869
|
+
#factoryAt(factories, callee, site, depth = 0) {
|
|
870
|
+
const [head = callee, member] = callee.split(".");
|
|
871
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
872
|
+
if (!imported)
|
|
873
|
+
return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
|
|
874
|
+
if (member && imported.imported !== "*")
|
|
875
|
+
return;
|
|
876
|
+
const from = this.ownerOfSpecifier(site.fileName, imported.from);
|
|
877
|
+
if (from === undefined || from === frameworkOwner)
|
|
878
|
+
return;
|
|
879
|
+
const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
|
|
880
|
+
const candidates = factories.get(name) ?? [];
|
|
881
|
+
const near = this.pathOfSpecifier(site.fileName, imported.from);
|
|
882
|
+
const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
|
|
883
|
+
if (own)
|
|
884
|
+
return own;
|
|
885
|
+
const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
|
|
886
|
+
if (reexport)
|
|
887
|
+
return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
|
|
888
|
+
return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
|
|
889
|
+
}
|
|
890
|
+
#findGuard(local, site, depth, isBase = false) {
|
|
891
|
+
const segments = local.split(".");
|
|
892
|
+
const head = segments[0] ?? local;
|
|
893
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
894
|
+
const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
|
|
895
|
+
if (imported && from === undefined)
|
|
896
|
+
return { external: true };
|
|
897
|
+
const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
|
|
898
|
+
const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
|
|
899
|
+
const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
|
|
900
|
+
const holder = imported ? from ?? null : site.owner;
|
|
901
|
+
const [objectName, member] = path;
|
|
902
|
+
if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
|
|
903
|
+
const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
|
|
904
|
+
if (made)
|
|
905
|
+
return { guard: made };
|
|
906
|
+
for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
|
|
907
|
+
const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
|
|
908
|
+
if ("guard" in found)
|
|
909
|
+
return found;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
const name = path[path.length - 1] ?? head;
|
|
913
|
+
if (from === frameworkOwner)
|
|
914
|
+
return { name, from };
|
|
915
|
+
const declared = this.#guards.get(name) ?? [];
|
|
916
|
+
const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
|
|
917
|
+
if (own)
|
|
918
|
+
return { guard: own };
|
|
919
|
+
if (!imported) {
|
|
920
|
+
const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
|
|
921
|
+
return visible ? { guard: visible } : { name, from };
|
|
922
|
+
}
|
|
923
|
+
const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
|
|
924
|
+
if (reexport)
|
|
925
|
+
return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
|
|
926
|
+
const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
|
|
927
|
+
return reached ? { guard: reached } : { name, from };
|
|
928
|
+
}
|
|
929
|
+
reexportOf(owner, name, near) {
|
|
930
|
+
const key = `${owner}
|
|
931
|
+
${name}
|
|
932
|
+
${near ?? ""}`;
|
|
933
|
+
const cached = this.#reexports.get(key);
|
|
934
|
+
if (cached !== undefined)
|
|
935
|
+
return cached;
|
|
936
|
+
let found = null;
|
|
937
|
+
const exporting = new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
|
|
938
|
+
for (const fileName of this.#filesOf(owner, name, near)) {
|
|
939
|
+
const text = this.#source.readText(fileName);
|
|
940
|
+
if (text === undefined || !exporting.test(text))
|
|
941
|
+
continue;
|
|
942
|
+
const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
|
|
943
|
+
if (!reexport)
|
|
944
|
+
continue;
|
|
945
|
+
const site = { fileName, owner, imports: [] };
|
|
946
|
+
found = reexport.from ? {
|
|
947
|
+
local: reexport.name,
|
|
948
|
+
site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
|
|
949
|
+
} : { local: reexport.name, site };
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
this.#reexports.set(key, found);
|
|
953
|
+
return found;
|
|
954
|
+
}
|
|
955
|
+
#reexportIn(source, name) {
|
|
956
|
+
const { ts, reader } = this.#source;
|
|
957
|
+
for (const statement of source.statements) {
|
|
958
|
+
if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
|
|
959
|
+
continue;
|
|
960
|
+
const { exportClause, moduleSpecifier } = statement;
|
|
961
|
+
if (!exportClause || !ts.isNamedExports(exportClause))
|
|
962
|
+
continue;
|
|
963
|
+
const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
|
|
964
|
+
if (!element)
|
|
965
|
+
continue;
|
|
966
|
+
const original = (element.propertyName ?? element.name).text;
|
|
967
|
+
if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
|
|
968
|
+
return { name: original, from: moduleSpecifier.text };
|
|
969
|
+
const imported = reader.importsIn(source).find((item) => item.name === original);
|
|
970
|
+
if (!imported)
|
|
971
|
+
return { name: original, from: null };
|
|
972
|
+
return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
|
|
973
|
+
}
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
#filesOf(owner, name, near) {
|
|
977
|
+
const isNear = (fileName) => {
|
|
978
|
+
const relative = this.#source.relative(fileName);
|
|
979
|
+
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
980
|
+
};
|
|
981
|
+
const candidates = this.filesIn(owner).filter(([, words]) => words.includes(`
|
|
982
|
+
${name}
|
|
983
|
+
`)).map(([fileName]) => fileName);
|
|
984
|
+
return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
|
|
985
|
+
}
|
|
986
|
+
#mapEntriesOf(objectName, member, fileName, owner) {
|
|
987
|
+
const key = `${objectName}
|
|
988
|
+
${member}
|
|
989
|
+
${fileName ?? ""}
|
|
990
|
+
${owner ?? ""}`;
|
|
991
|
+
const cached = this.#mapEntries.get(key);
|
|
992
|
+
if (cached)
|
|
993
|
+
return cached;
|
|
994
|
+
const { ts, reader } = this.#source;
|
|
995
|
+
const entries = [];
|
|
996
|
+
const fileNames = [
|
|
997
|
+
...fileName ? [fileName] : [],
|
|
998
|
+
...(owner === null ? [] : this.filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
|
|
999
|
+
${objectName}
|
|
1000
|
+
`) && words.includes(`
|
|
1001
|
+
${member}
|
|
1002
|
+
`)).map(([other]) => other)
|
|
1003
|
+
];
|
|
1004
|
+
for (const holder of fileNames) {
|
|
1005
|
+
const text = this.#source.readText(holder);
|
|
1006
|
+
if (text === undefined)
|
|
1007
|
+
continue;
|
|
1008
|
+
const source = this.#source.parse(holder, text);
|
|
1009
|
+
const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
|
|
1010
|
+
for (const statement of source.statements) {
|
|
1011
|
+
if (!ts.isVariableStatement(statement))
|
|
1012
|
+
continue;
|
|
1013
|
+
const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
|
|
1014
|
+
const object = map && reader.unwrap(map);
|
|
1015
|
+
if (!object || !ts.isObjectLiteralExpression(object))
|
|
1016
|
+
continue;
|
|
1017
|
+
for (const property of object.properties) {
|
|
1018
|
+
if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
|
|
1019
|
+
entries.push({ name: member, site });
|
|
1020
|
+
if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
|
|
1021
|
+
continue;
|
|
1022
|
+
const value = reader.unwrap(property.initializer);
|
|
1023
|
+
if (ts.isIdentifier(value))
|
|
1024
|
+
entries.push({ name: value.text, site });
|
|
1025
|
+
else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
|
|
1026
|
+
entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
this.#mapEntries.set(key, entries);
|
|
1031
|
+
return entries;
|
|
1032
|
+
}
|
|
1033
|
+
declarationOf(owner, name, near, depth = 0) {
|
|
1034
|
+
const key = `${owner}
|
|
1035
|
+
${name}
|
|
1036
|
+
${near ?? ""}`;
|
|
1037
|
+
const cached = this.#declarations.get(key);
|
|
1038
|
+
if (cached !== undefined)
|
|
1039
|
+
return cached;
|
|
1040
|
+
this.#declarations.set(key, null);
|
|
1041
|
+
const declaring = declaringPatternOf(name, false);
|
|
1042
|
+
let found = null;
|
|
1043
|
+
for (const fileName of this.#filesOf(owner, name, near)) {
|
|
1044
|
+
const text = this.#source.readText(fileName);
|
|
1045
|
+
if (text === undefined || !declaring.test(text))
|
|
1046
|
+
continue;
|
|
1047
|
+
const source = this.#source.parse(fileName, text);
|
|
1048
|
+
const declared = this.#routes.declarationIn(source, name);
|
|
1049
|
+
if (declared) {
|
|
1050
|
+
found = { source, ...declared, name };
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
1053
|
+
const reexport = this.#reexportIn(source, name);
|
|
1054
|
+
if (!reexport)
|
|
1055
|
+
continue;
|
|
1056
|
+
if (!reexport.from) {
|
|
1057
|
+
const local = this.#routes.declarationIn(source, reexport.name);
|
|
1058
|
+
found = local ? { source, ...local, name: reexport.name } : null;
|
|
1059
|
+
break;
|
|
1060
|
+
}
|
|
1061
|
+
const next = this.ownerOfSpecifier(fileName, reexport.from);
|
|
1062
|
+
if (next && next !== frameworkOwner && depth < maxMapDepth)
|
|
1063
|
+
found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
|
|
1064
|
+
break;
|
|
576
1065
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
1066
|
+
this.#declarations.set(key, found);
|
|
1067
|
+
return found;
|
|
1068
|
+
}
|
|
1069
|
+
#throughStar(declared, owner, name, near) {
|
|
1070
|
+
if (!declared.length)
|
|
1071
|
+
return;
|
|
1072
|
+
const { owners, isOpaque } = this.#starExportsOf(owner, near);
|
|
1073
|
+
const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
|
|
1074
|
+
return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
|
|
1075
|
+
}
|
|
1076
|
+
#starExportsOf(owner, near) {
|
|
1077
|
+
const cached = this.#starExports.get(near);
|
|
1078
|
+
if (cached)
|
|
1079
|
+
return cached;
|
|
1080
|
+
const found = { owners: [], isOpaque: false };
|
|
1081
|
+
this.#starExports.set(near, found);
|
|
1082
|
+
const paths = [near];
|
|
1083
|
+
for (let idx = 0;idx < paths.length && !found.isOpaque; idx++)
|
|
1084
|
+
for (const fileName of this.#modulesAt(paths[idx] ?? "")) {
|
|
1085
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
|
|
1086
|
+
const target = this.ownerOfSpecifier(fileName, specifier);
|
|
1087
|
+
const path = this.pathOfSpecifier(fileName, specifier);
|
|
1088
|
+
if (target === undefined || target === frameworkOwner || path === null)
|
|
1089
|
+
found.isOpaque = true;
|
|
1090
|
+
else if (!paths.includes(path))
|
|
1091
|
+
paths.push(path);
|
|
1092
|
+
if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
|
|
1093
|
+
found.owners.push(target);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return found;
|
|
1097
|
+
}
|
|
1098
|
+
#modulesAt(path) {
|
|
1099
|
+
if (!this.#modules) {
|
|
1100
|
+
this.#modules = new Map;
|
|
1101
|
+
for (const [fileName] of this.#source.files) {
|
|
1102
|
+
const module = this.#source.relative(fileName).replace(/\.tsx?$/, "");
|
|
1103
|
+
for (const key of module.endsWith("/index") ? [module, module.slice(0, -6)] : [module])
|
|
1104
|
+
this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
return this.#modules.get(path) ?? [];
|
|
1108
|
+
}
|
|
1109
|
+
declares(owner, name) {
|
|
1110
|
+
const declaring = declaringPatternOf(name, true);
|
|
1111
|
+
return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
|
|
1112
|
+
}
|
|
1113
|
+
filesIn(owner) {
|
|
1114
|
+
if (!this.#ownerFiles) {
|
|
1115
|
+
this.#ownerFiles = new Map;
|
|
1116
|
+
for (const entry of this.#source.files) {
|
|
1117
|
+
const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
|
|
1118
|
+
const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
|
|
1119
|
+
if (files)
|
|
1120
|
+
files.push(entry);
|
|
1121
|
+
else if (fileOwner !== null)
|
|
1122
|
+
this.#ownerFiles.set(fileOwner, [entry]);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
return this.#ownerFiles.get(owner) ?? [];
|
|
1126
|
+
}
|
|
1127
|
+
#nearest(items, fileOf, near) {
|
|
1128
|
+
const isNear = (fileName) => {
|
|
1129
|
+
const relative = this.#source.relative(fileName);
|
|
1130
|
+
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
1131
|
+
};
|
|
1132
|
+
return items.find((item) => isNear(fileOf(item))) ?? items[0];
|
|
1133
|
+
}
|
|
1134
|
+
inOwner(fileName, owner) {
|
|
1135
|
+
const relative = this.#source.relative(fileName);
|
|
1136
|
+
return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
|
|
1137
|
+
}
|
|
1138
|
+
ownerOfSpecifier(fileName, specifier) {
|
|
1139
|
+
if (frameworkPattern.test(specifier))
|
|
1140
|
+
return frameworkOwner;
|
|
1141
|
+
const path = this.pathOfSpecifier(fileName, specifier);
|
|
1142
|
+
return path === null ? undefined : ownerOf(path) ?? undefined;
|
|
1143
|
+
}
|
|
1144
|
+
pathOfSpecifier(fileName, specifier) {
|
|
1145
|
+
const alias = specifier.match(aliasPattern);
|
|
1146
|
+
if (alias)
|
|
1147
|
+
return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
|
|
1148
|
+
if (!specifier.startsWith("."))
|
|
1149
|
+
return null;
|
|
1150
|
+
const segments = this.#source.relative(fileName).split("/").slice(0, -1);
|
|
1151
|
+
for (const segment of specifier.split("/"))
|
|
1152
|
+
if (segment === "..")
|
|
1153
|
+
segments.pop();
|
|
1154
|
+
else if (segment !== ".")
|
|
1155
|
+
segments.push(segment);
|
|
1156
|
+
return segments.join("/");
|
|
1157
|
+
}
|
|
1158
|
+
visible(target, from) {
|
|
1159
|
+
return !from || !target || target === from || target.startsWith("libs/");
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
581
1162
|
|
|
582
1163
|
// pkgs/akanjs/common/trimSlashes.ts
|
|
583
1164
|
var trimTrailingSlashes = (value) => {
|
|
@@ -726,24 +1307,23 @@ rootLayout
|
|
|
726
1307
|
}
|
|
727
1308
|
declarationIn(source, name) {
|
|
728
1309
|
const ts = this.#ts;
|
|
1310
|
+
const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
|
|
1311
|
+
const [first] = overloads;
|
|
1312
|
+
const last = overloads.at(-1);
|
|
1313
|
+
if (first?.name && last)
|
|
1314
|
+
return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
|
|
729
1315
|
for (const statement of source.statements) {
|
|
730
|
-
if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
|
|
731
|
-
return { statement, declared: statement.name, initializer: null };
|
|
732
1316
|
if (!ts.isVariableStatement(statement))
|
|
733
1317
|
continue;
|
|
734
1318
|
const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
|
|
735
1319
|
if (declaration)
|
|
736
|
-
return { statement, declared: declaration.name,
|
|
1320
|
+
return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
|
|
737
1321
|
}
|
|
738
1322
|
return null;
|
|
739
1323
|
}
|
|
740
1324
|
#guardOf(source, exported) {
|
|
741
1325
|
const ts = this.#ts;
|
|
742
1326
|
let current = ts.isExportAssignment(exported) ? this.#reader.unwrap(exported.expression) : null;
|
|
743
|
-
if (current && ts.isIdentifier(current)) {
|
|
744
|
-
const initializer = this.declarationIn(source, current.text)?.initializer;
|
|
745
|
-
current = initializer ? this.#reader.unwrap(initializer) : null;
|
|
746
|
-
}
|
|
747
1327
|
while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
748
1328
|
const { name, expression } = current.expression;
|
|
749
1329
|
const [stage] = current.arguments;
|
|
@@ -755,9 +1335,20 @@ rootLayout
|
|
|
755
1335
|
}
|
|
756
1336
|
#callsIn(source, stage) {
|
|
757
1337
|
const ts = this.#ts;
|
|
758
|
-
const
|
|
1338
|
+
const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
|
|
1339
|
+
const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
|
|
1340
|
+
const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
|
|
759
1341
|
const calls = new Map;
|
|
760
|
-
const callOf = (
|
|
1342
|
+
const callOf = (callee, shadowed) => {
|
|
1343
|
+
if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
|
|
1344
|
+
const name = `${callee.expression.text}.${callee.name.text}`;
|
|
1345
|
+
if (!calls.has(name))
|
|
1346
|
+
calls.set(name, { name, local: null });
|
|
1347
|
+
return true;
|
|
1348
|
+
}
|
|
1349
|
+
if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
|
|
1350
|
+
return false;
|
|
1351
|
+
const name = callee.text;
|
|
761
1352
|
if (calls.has(name))
|
|
762
1353
|
return true;
|
|
763
1354
|
const declared = this.declarationIn(source, name);
|
|
@@ -767,23 +1358,71 @@ rootLayout
|
|
|
767
1358
|
name,
|
|
768
1359
|
local: declared && {
|
|
769
1360
|
span: this.#reader.spanOf(source, declared.declared),
|
|
770
|
-
extent:
|
|
1361
|
+
extent: declared.extent,
|
|
771
1362
|
doc: this.#reader.docOf(source, declared.statement)
|
|
772
1363
|
}
|
|
773
1364
|
});
|
|
774
1365
|
return true;
|
|
775
1366
|
};
|
|
776
|
-
const visit = (node) => {
|
|
777
|
-
|
|
1367
|
+
const visit = (node, outer) => {
|
|
1368
|
+
const shadowed = this.#scopeOf(node, outer);
|
|
1369
|
+
if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
|
|
778
1370
|
return;
|
|
779
|
-
ts.forEachChild(node, visit);
|
|
1371
|
+
ts.forEachChild(node, (child) => visit(child, shadowed));
|
|
780
1372
|
};
|
|
781
1373
|
if (ts.isIdentifier(stage))
|
|
782
|
-
callOf(stage
|
|
1374
|
+
callOf(stage, new Set);
|
|
783
1375
|
else
|
|
784
|
-
visit(stage);
|
|
1376
|
+
visit(stage, new Set);
|
|
785
1377
|
return [...calls.values()];
|
|
786
1378
|
}
|
|
1379
|
+
#scopeOf(node, outer) {
|
|
1380
|
+
const ts = this.#ts;
|
|
1381
|
+
const names = [];
|
|
1382
|
+
const bind = (name) => {
|
|
1383
|
+
if (ts.isIdentifier(name))
|
|
1384
|
+
names.push(name.text);
|
|
1385
|
+
else
|
|
1386
|
+
for (const element of name.elements)
|
|
1387
|
+
if (!ts.isOmittedExpression(element))
|
|
1388
|
+
bind(element.name);
|
|
1389
|
+
};
|
|
1390
|
+
const declare = (statement) => {
|
|
1391
|
+
if (ts.isVariableStatement(statement))
|
|
1392
|
+
for (const declaration of statement.declarationList.declarations)
|
|
1393
|
+
bind(declaration.name);
|
|
1394
|
+
else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
|
|
1395
|
+
names.push(statement.name.text);
|
|
1396
|
+
};
|
|
1397
|
+
const hoist = (child) => {
|
|
1398
|
+
if (ts.isFunctionLike(child) || ts.isClassLike(child))
|
|
1399
|
+
return;
|
|
1400
|
+
if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
|
|
1401
|
+
for (const declaration of child.declarations)
|
|
1402
|
+
bind(declaration.name);
|
|
1403
|
+
ts.forEachChild(child, hoist);
|
|
1404
|
+
};
|
|
1405
|
+
if (ts.isFunctionLike(node)) {
|
|
1406
|
+
for (const parameter of node.parameters)
|
|
1407
|
+
bind(parameter.name);
|
|
1408
|
+
if (ts.isFunctionExpression(node) && node.name)
|
|
1409
|
+
names.push(node.name.text);
|
|
1410
|
+
if ("body" in node && node.body)
|
|
1411
|
+
ts.forEachChild(node.body, hoist);
|
|
1412
|
+
} else if (ts.isClassExpression(node) && node.name)
|
|
1413
|
+
names.push(node.name.text);
|
|
1414
|
+
else if (ts.isBlock(node))
|
|
1415
|
+
node.statements.forEach(declare);
|
|
1416
|
+
else if (ts.isCaseBlock(node))
|
|
1417
|
+
for (const clause of node.clauses)
|
|
1418
|
+
clause.statements.forEach(declare);
|
|
1419
|
+
else if (ts.isCatchClause(node) && node.variableDeclaration)
|
|
1420
|
+
bind(node.variableDeclaration.name);
|
|
1421
|
+
else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
|
|
1422
|
+
for (const declaration of node.initializer.declarations)
|
|
1423
|
+
bind(declaration.name);
|
|
1424
|
+
return names.length ? new Set([...outer, ...names]) : outer;
|
|
1425
|
+
}
|
|
787
1426
|
}
|
|
788
1427
|
|
|
789
1428
|
// pkgs/@akanjs/language/akanGraph.ts
|
|
@@ -799,10 +1438,6 @@ var builtinGuards = {
|
|
|
799
1438
|
Public: { scope: "account", doc: "Passes every caller, signed in or not." },
|
|
800
1439
|
None: { scope: "account", doc: "Refuses every caller." }
|
|
801
1440
|
};
|
|
802
|
-
var frameworkOwner = "akanjs";
|
|
803
|
-
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
804
|
-
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
805
|
-
var maxMapDepth = 4;
|
|
806
1441
|
|
|
807
1442
|
class GraphBuilder {
|
|
808
1443
|
#source;
|
|
@@ -810,16 +1445,15 @@ class GraphBuilder {
|
|
|
810
1445
|
#edges = new Map;
|
|
811
1446
|
#contexts = new Map;
|
|
812
1447
|
#members = new Map;
|
|
813
|
-
#guards = new Map;
|
|
814
|
-
#mapEntries = new Map;
|
|
815
1448
|
#routeFiles = new Map;
|
|
816
|
-
#declarations = new Map;
|
|
817
1449
|
#routes;
|
|
1450
|
+
#resolver;
|
|
818
1451
|
#adaptors = new Map;
|
|
819
1452
|
#plugs = new Map;
|
|
820
1453
|
constructor(source) {
|
|
821
1454
|
this.#source = source;
|
|
822
1455
|
this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
|
|
1456
|
+
this.#resolver = new GuardResolver(source, this.#routes);
|
|
823
1457
|
}
|
|
824
1458
|
build() {
|
|
825
1459
|
const { facts } = this.#source;
|
|
@@ -841,7 +1475,8 @@ class GraphBuilder {
|
|
|
841
1475
|
for (const fact of file.adaptors)
|
|
842
1476
|
for (const plug of fact.plugs)
|
|
843
1477
|
this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
|
|
844
|
-
this.#
|
|
1478
|
+
for (const guard of this.#resolver.declare(facts))
|
|
1479
|
+
this.#guardNodeOf(guard);
|
|
845
1480
|
for (const file of facts) {
|
|
846
1481
|
const { owner } = file;
|
|
847
1482
|
const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
|
|
@@ -885,31 +1520,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
|
885
1520
|
for (const query of filter.queries)
|
|
886
1521
|
this.#digest(this.#addNode(this.#key("query", owner, refName, query.name), "query", owner, refName, query.name, query.span), this.#source.readText(file.fileName), query.extent);
|
|
887
1522
|
}
|
|
888
|
-
|
|
889
|
-
const refName = slice.model;
|
|
890
|
-
if (!refName)
|
|
891
|
-
continue;
|
|
892
|
-
const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards);
|
|
893
|
-
for (const guard of slice.guards)
|
|
894
|
-
this.#guard(fallback, guard, file);
|
|
895
|
-
for (const key of slice.keys) {
|
|
896
|
-
const detail = [...key.guards, ...key.live ? ["live"] : []];
|
|
897
|
-
const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
898
|
-
for (const guard of key.guards)
|
|
899
|
-
this.#guard(node, guard, file);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
for (const endpoint of file.endpoints) {
|
|
903
|
-
const refName = endpoint.model;
|
|
904
|
-
if (!refName)
|
|
905
|
-
continue;
|
|
906
|
-
for (const key of endpoint.keys) {
|
|
907
|
-
const detail = [...key.verb ? [key.verb] : [], ...key.guards];
|
|
908
|
-
const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
909
|
-
for (const guard of key.guards)
|
|
910
|
-
this.#guard(node, guard, file);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
1523
|
+
this.#addGuarded(file);
|
|
913
1524
|
for (const internal of file.internals)
|
|
914
1525
|
for (const key of internal.keys)
|
|
915
1526
|
if (internal.model && key.type !== "resolveField")
|
|
@@ -925,7 +1536,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
|
925
1536
|
types.set(node.label, [...types.get(node.label) ?? [], node]);
|
|
926
1537
|
for (const node of typed)
|
|
927
1538
|
for (const field of node.fields ?? []) {
|
|
928
|
-
const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
|
|
1539
|
+
const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
|
|
929
1540
|
field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
|
|
930
1541
|
if (field.ref)
|
|
931
1542
|
this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
|
|
@@ -1007,61 +1618,52 @@ ${label ?? ""}`;
|
|
|
1007
1618
|
else
|
|
1008
1619
|
this.#edges.set(key, { from, to, kind, label, count: 1, at });
|
|
1009
1620
|
}
|
|
1010
|
-
#
|
|
1011
|
-
const
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
for (const
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
};
|
|
1028
|
-
const madeBy = (factory, fileName, extent) => {
|
|
1029
|
-
const parts = [{ fileName, extent }];
|
|
1030
|
-
const seen = new Set;
|
|
1031
|
-
for (let current = factory;current && !seen.has(current); current = sameFile(current, current.fact.via)) {
|
|
1032
|
-
seen.add(current);
|
|
1033
|
-
parts.push({ fileName: current.fileName, extent: current.fact.extent });
|
|
1034
|
-
}
|
|
1035
|
-
return parts;
|
|
1036
|
-
};
|
|
1037
|
-
for (const file of facts) {
|
|
1038
|
-
const { owner, fileName } = file;
|
|
1039
|
-
for (const { name, span, extent, scope, doc, factory: callee } of file.guards) {
|
|
1040
|
-
const factory = callee ? factories.get(callee)?.find((other) => other.fileName === fileName) : undefined;
|
|
1041
|
-
declare({ name, owner, span, scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
|
|
1621
|
+
#addGuarded(file) {
|
|
1622
|
+
const { owner } = file;
|
|
1623
|
+
for (const slice of file.slices) {
|
|
1624
|
+
const refName = slice.model;
|
|
1625
|
+
if (!refName)
|
|
1626
|
+
continue;
|
|
1627
|
+
const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
|
|
1628
|
+
for (const guard of slice.guards)
|
|
1629
|
+
this.#guard(fallback, guard, file);
|
|
1630
|
+
for (const key of slice.keys) {
|
|
1631
|
+
const detail = [
|
|
1632
|
+
...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
|
|
1633
|
+
...key.live ? ["live"] : []
|
|
1634
|
+
];
|
|
1635
|
+
const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
1636
|
+
for (const guard of key.guards)
|
|
1637
|
+
this.#guard(node, guard, file);
|
|
1042
1638
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1639
|
+
}
|
|
1640
|
+
for (const endpoint of file.endpoints) {
|
|
1641
|
+
const refName = endpoint.model;
|
|
1642
|
+
if (!refName)
|
|
1643
|
+
continue;
|
|
1644
|
+
for (const key of endpoint.keys) {
|
|
1645
|
+
const detail = [
|
|
1646
|
+
...key.verb ? [key.verb] : [],
|
|
1647
|
+
...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
|
|
1648
|
+
];
|
|
1649
|
+
const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
1650
|
+
for (const guard of key.guards)
|
|
1651
|
+
this.#guard(node, guard, file);
|
|
1052
1652
|
}
|
|
1053
1653
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1654
|
+
}
|
|
1655
|
+
#guard(node, guard, file) {
|
|
1656
|
+
const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
|
|
1657
|
+
if (!local || local === "?")
|
|
1658
|
+
return;
|
|
1659
|
+
const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
|
|
1660
|
+
if (id)
|
|
1661
|
+
this.#addEdge(node.id, id, "guard", role, node.at);
|
|
1062
1662
|
}
|
|
1063
1663
|
#guardIdOf(local, site) {
|
|
1064
|
-
const found = this.#
|
|
1664
|
+
const found = this.#resolver.find(local, site);
|
|
1665
|
+
if ("external" in found)
|
|
1666
|
+
return null;
|
|
1065
1667
|
if ("guard" in found)
|
|
1066
1668
|
return this.#guardNodeOf(found.guard);
|
|
1067
1669
|
const { name, from } = found;
|
|
@@ -1074,99 +1676,18 @@ ${label ?? ""}`;
|
|
|
1074
1676
|
node.doc = builtin.doc;
|
|
1075
1677
|
return node.id;
|
|
1076
1678
|
}
|
|
1077
|
-
#
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
}
|
|
1090
|
-
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1091
|
-
const declared = this.#guards.get(name) ?? [];
|
|
1092
|
-
const guard = from === undefined ? declared.find((other) => other.owner === site.owner) ?? declared.find((other) => this.#visible(other.owner, site.owner)) : declared.find((other) => other.owner === from) ?? (from === frameworkOwner ? undefined : this.#soleVisible(declared, from));
|
|
1093
|
-
return guard ? { guard } : { name, from };
|
|
1094
|
-
}
|
|
1095
|
-
#mapEntriesOf(objectName, member, fileName, owner) {
|
|
1096
|
-
const key = `${objectName}
|
|
1097
|
-
${member}
|
|
1098
|
-
${fileName ?? ""}
|
|
1099
|
-
${owner ?? ""}`;
|
|
1100
|
-
const cached = this.#mapEntries.get(key);
|
|
1101
|
-
if (cached)
|
|
1102
|
-
return cached;
|
|
1103
|
-
const { ts, reader } = this.#source;
|
|
1104
|
-
const entries = [];
|
|
1105
|
-
const fileNames = [
|
|
1106
|
-
...fileName ? [fileName] : [],
|
|
1107
|
-
...this.#source.files.filter(([other, words]) => other !== fileName && owner !== null && !testFilePattern.test(other) && words.includes(`
|
|
1108
|
-
${objectName}
|
|
1109
|
-
`) && words.includes(`
|
|
1110
|
-
${member}
|
|
1111
|
-
`) && this.#source.ownerOf(other) === owner).map(([other]) => other)
|
|
1112
|
-
];
|
|
1113
|
-
for (const holder of fileNames) {
|
|
1114
|
-
const text = this.#source.readText(holder);
|
|
1115
|
-
if (text === undefined)
|
|
1116
|
-
continue;
|
|
1117
|
-
const source = this.#source.parse(holder, text);
|
|
1118
|
-
const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
|
|
1119
|
-
for (const statement of source.statements) {
|
|
1120
|
-
if (!ts.isVariableStatement(statement))
|
|
1121
|
-
continue;
|
|
1122
|
-
const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
|
|
1123
|
-
const object = map && reader.unwrap(map);
|
|
1124
|
-
if (!object || !ts.isObjectLiteralExpression(object))
|
|
1125
|
-
continue;
|
|
1126
|
-
for (const property of object.properties) {
|
|
1127
|
-
if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
|
|
1128
|
-
entries.push({ name: member, site });
|
|
1129
|
-
if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
|
|
1130
|
-
continue;
|
|
1131
|
-
const value = reader.unwrap(property.initializer);
|
|
1132
|
-
if (ts.isIdentifier(value))
|
|
1133
|
-
entries.push({ name: value.text, site });
|
|
1134
|
-
else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
|
|
1135
|
-
entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
this.#mapEntries.set(key, entries);
|
|
1140
|
-
return entries;
|
|
1141
|
-
}
|
|
1142
|
-
#declarationOf(owner, name, near) {
|
|
1143
|
-
const key = `${owner}
|
|
1144
|
-
${name}
|
|
1145
|
-
${near ?? ""}`;
|
|
1146
|
-
const cached = this.#declarations.get(key);
|
|
1147
|
-
if (cached !== undefined)
|
|
1148
|
-
return cached;
|
|
1149
|
-
const isNear = (fileName) => {
|
|
1150
|
-
const relative = this.#source.relative(fileName);
|
|
1151
|
-
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
1152
|
-
};
|
|
1153
|
-
const candidates = this.#source.files.filter(([fileName, words]) => !testFilePattern.test(fileName) && words.includes(`
|
|
1154
|
-
${name}
|
|
1155
|
-
`) && this.#source.ownerOf(fileName) === owner).map(([fileName]) => fileName);
|
|
1156
|
-
let found = null;
|
|
1157
|
-
for (const fileName of [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))]) {
|
|
1158
|
-
const text = this.#source.readText(fileName);
|
|
1159
|
-
if (text === undefined)
|
|
1160
|
-
continue;
|
|
1161
|
-
const source = this.#source.parse(fileName, text);
|
|
1162
|
-
const declared = this.#routes.declarationIn(source, name);
|
|
1163
|
-
if (!declared)
|
|
1164
|
-
continue;
|
|
1165
|
-
found = { source, statement: declared.statement, declared: declared.declared };
|
|
1166
|
-
break;
|
|
1167
|
-
}
|
|
1168
|
-
this.#declarations.set(key, found);
|
|
1169
|
-
return found;
|
|
1679
|
+
#madeInPlaceIdOf(name, file) {
|
|
1680
|
+
const made = this.#resolver.madeInPlace(file.fileName, name);
|
|
1681
|
+
if (made)
|
|
1682
|
+
return this.#guardNodeOf(made);
|
|
1683
|
+
const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
|
|
1684
|
+
const imported = file.imports.find((item) => item.name === head);
|
|
1685
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
|
|
1686
|
+
if (from === undefined || from === frameworkOwner)
|
|
1687
|
+
return null;
|
|
1688
|
+
const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
|
|
1689
|
+
const label = guardLabelOf(name, file.guardLabels);
|
|
1690
|
+
return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
|
|
1170
1691
|
}
|
|
1171
1692
|
#addRoutes() {
|
|
1172
1693
|
const routes = [];
|
|
@@ -1219,22 +1740,23 @@ ${dir}`, node);
|
|
|
1219
1740
|
}
|
|
1220
1741
|
#routeGuardIdOf({ name: local, local: declared }, fact, text) {
|
|
1221
1742
|
if (declared) {
|
|
1222
|
-
const
|
|
1223
|
-
const id = this.#key("guard", fact.owner, `${file}#${local}`);
|
|
1743
|
+
const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
|
|
1224
1744
|
return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
|
|
1225
1745
|
}
|
|
1226
|
-
const
|
|
1227
|
-
|
|
1746
|
+
const [head = local, member] = local.split(".");
|
|
1747
|
+
const imported = fact.imports.find((item) => item.name === head);
|
|
1748
|
+
if (!imported || member && imported.imported !== "*")
|
|
1228
1749
|
return null;
|
|
1229
|
-
const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
|
|
1230
|
-
const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
|
|
1750
|
+
const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
|
|
1751
|
+
const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
|
|
1231
1752
|
if (!from || from === frameworkOwner)
|
|
1232
1753
|
return null;
|
|
1233
|
-
const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
|
|
1754
|
+
const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
|
|
1234
1755
|
if (!found)
|
|
1235
1756
|
return null;
|
|
1236
1757
|
const { reader } = this.#source;
|
|
1237
|
-
|
|
1758
|
+
const owner = this.#source.ownerOf(found.source.fileName) ?? from;
|
|
1759
|
+
return this.#routeGuardNodeOf(this.#key("guard", owner, `${this.#resolver.inOwner(found.source.fileName, owner)}#${found.name}`), owner, found.name, reader.spanOf(found.source, found.declared), reader.docOf(found.source, found.statement), found.source.text, found.extent);
|
|
1238
1760
|
}
|
|
1239
1761
|
#routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
|
|
1240
1762
|
const isNew = !this.#nodes.has(id);
|
|
@@ -1247,10 +1769,10 @@ ${dir}`, node);
|
|
|
1247
1769
|
#adaptorIdOf(local, file) {
|
|
1248
1770
|
const [head, member] = local.split(".");
|
|
1249
1771
|
const imported = file.imports.find((item) => item.name === head);
|
|
1250
|
-
const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1772
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1251
1773
|
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1252
1774
|
const declared = this.#adaptors.get(name) ?? [];
|
|
1253
|
-
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)));
|
|
1775
|
+
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)));
|
|
1254
1776
|
if (found)
|
|
1255
1777
|
return this.#adaptorNodeOf(found);
|
|
1256
1778
|
const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
|
|
@@ -1266,38 +1788,22 @@ ${dir}`, node);
|
|
|
1266
1788
|
this.#digest(node, this.#source.readText(fileName), fact.extent);
|
|
1267
1789
|
return node.id;
|
|
1268
1790
|
}
|
|
1269
|
-
#guardNodeOf({ name, owner, span, scope, doc, parts }) {
|
|
1270
|
-
const
|
|
1791
|
+
#guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
|
|
1792
|
+
const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
|
|
1793
|
+
const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
|
|
1271
1794
|
if (doc && !node.doc)
|
|
1272
1795
|
node.doc = doc;
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1796
|
+
if (node.digest)
|
|
1797
|
+
return node.id;
|
|
1798
|
+
const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
|
|
1799
|
+
if (!digest)
|
|
1800
|
+
return node.id;
|
|
1801
|
+
node.digest = digest;
|
|
1802
|
+
const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
|
|
1803
|
+
if (sources.length)
|
|
1804
|
+
node.sources = sources;
|
|
1279
1805
|
return node.id;
|
|
1280
1806
|
}
|
|
1281
|
-
#ownerOfSpecifier(fileName, specifier) {
|
|
1282
|
-
if (frameworkPattern.test(specifier))
|
|
1283
|
-
return frameworkOwner;
|
|
1284
|
-
const path = this.#pathOfSpecifier(fileName, specifier);
|
|
1285
|
-
return path === null ? undefined : ownerOf(path) ?? undefined;
|
|
1286
|
-
}
|
|
1287
|
-
#pathOfSpecifier(fileName, specifier) {
|
|
1288
|
-
const alias = specifier.match(aliasPattern);
|
|
1289
|
-
if (alias)
|
|
1290
|
-
return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
|
|
1291
|
-
if (!specifier.startsWith("."))
|
|
1292
|
-
return null;
|
|
1293
|
-
const segments = this.#source.relative(fileName).split("/").slice(0, -1);
|
|
1294
|
-
for (const segment of specifier.split("/"))
|
|
1295
|
-
if (segment === "..")
|
|
1296
|
-
segments.pop();
|
|
1297
|
-
else if (segment !== ".")
|
|
1298
|
-
segments.push(segment);
|
|
1299
|
-
return segments.join("/");
|
|
1300
|
-
}
|
|
1301
1807
|
#moduleDirOf(fileName) {
|
|
1302
1808
|
const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
|
|
1303
1809
|
return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
|
|
@@ -1333,20 +1839,13 @@ ${dir}`, node);
|
|
|
1333
1839
|
visit(constant);
|
|
1334
1840
|
return [...fields.values()];
|
|
1335
1841
|
}
|
|
1336
|
-
#visible(target, from) {
|
|
1337
|
-
return !from || !target || target === from || target.startsWith("libs/");
|
|
1338
|
-
}
|
|
1339
|
-
#soleVisible(declared, from) {
|
|
1340
|
-
const visible = declared.filter((other) => this.#visible(other.owner, from));
|
|
1341
|
-
return visible.length === 1 ? visible[0] : undefined;
|
|
1342
|
-
}
|
|
1343
1842
|
#targetNodeOf({ family, owner, refName, sourceKey }) {
|
|
1344
1843
|
const isDefault = /^[A-Z]/.test(sourceKey);
|
|
1345
1844
|
const id = family === "slice" || family === "query" ? this.#key(family, owner, refName, isDefault ? "" : sourceKey) : family === "model" || family === "formSetter" ? this.#key("model", owner, refName) : family === "endpoint" ? this.#key("endpoint", owner, refName, sourceKey) : null;
|
|
1346
1845
|
return id && this.#nodes.has(id) ? id : null;
|
|
1347
1846
|
}
|
|
1348
1847
|
#membersNamed(kind, name, owner, refName) {
|
|
1349
|
-
const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
|
|
1848
|
+
const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
|
|
1350
1849
|
const own = nodes.filter((node) => node.owner === owner);
|
|
1351
1850
|
return (own.length ? own : nodes).map((node) => node.id);
|
|
1352
1851
|
}
|
|
@@ -1700,6 +2199,10 @@ var endpointNames = (key) => {
|
|
|
1700
2199
|
};
|
|
1701
2200
|
|
|
1702
2201
|
// pkgs/@akanjs/language/nameTable.ts
|
|
2202
|
+
var guardDetailOf = (facts, guards) => {
|
|
2203
|
+
const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
|
|
2204
|
+
return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
|
|
2205
|
+
};
|
|
1703
2206
|
var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
|
|
1704
2207
|
|
|
1705
2208
|
class NameTable {
|
|
@@ -1799,7 +2302,7 @@ class NameTable {
|
|
|
1799
2302
|
role: "sig",
|
|
1800
2303
|
gate: "sig",
|
|
1801
2304
|
label: sliceClass.name,
|
|
1802
|
-
detail:
|
|
2305
|
+
detail: guardDetailOf(facts, sliceClass.guards)
|
|
1803
2306
|
});
|
|
1804
2307
|
if (modelDoc)
|
|
1805
2308
|
this.#add({
|
|
@@ -1864,7 +2367,7 @@ class NameTable {
|
|
|
1864
2367
|
sourceKey: slice.name,
|
|
1865
2368
|
span: slice.span,
|
|
1866
2369
|
label: `slice ${refName} (default)`,
|
|
1867
|
-
detail:
|
|
2370
|
+
detail: guardDetailOf(facts, slice.guards)
|
|
1868
2371
|
};
|
|
1869
2372
|
this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
|
|
1870
2373
|
for (const key of slice.keys)
|
|
@@ -1874,7 +2377,7 @@ class NameTable {
|
|
|
1874
2377
|
sourceKey: key.name,
|
|
1875
2378
|
span: key.span,
|
|
1876
2379
|
label: `slice ${key.name}`,
|
|
1877
|
-
detail: [...
|
|
2380
|
+
detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
|
|
1878
2381
|
}, refName, key.name, modelNameSet, slice.name);
|
|
1879
2382
|
}
|
|
1880
2383
|
if (filterClass) {
|
|
@@ -1931,7 +2434,7 @@ class NameTable {
|
|
|
1931
2434
|
role: key.verb === "pubsub" ? "subscribe" : "listen",
|
|
1932
2435
|
gate: null,
|
|
1933
2436
|
label: `${key.verb} ${key.name}`,
|
|
1934
|
-
detail:
|
|
2437
|
+
detail: guardDetailOf(facts, key.guards)
|
|
1935
2438
|
});
|
|
1936
2439
|
}
|
|
1937
2440
|
}
|
|
@@ -1989,14 +2492,12 @@ var dictionaryPattern = /\.dictionary\.ts$/;
|
|
|
1989
2492
|
var srvkitPattern = /\/srvkit\//;
|
|
1990
2493
|
var wordPattern = /[A-Za-z_$][\w$]*/g;
|
|
1991
2494
|
var identifierPattern = /^[A-Za-z_$][\w$]*$/;
|
|
1992
|
-
var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
|
|
1993
2495
|
var stateReceivers = new Set(["use", "sel", "get", "do"]);
|
|
1994
2496
|
var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
|
|
1995
2497
|
var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
|
|
1996
2498
|
var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
|
|
1997
2499
|
var parseCacheSize = 64;
|
|
1998
2500
|
var pathOf = (fileName) => fileName.replace(/\\/g, "/");
|
|
1999
|
-
var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
|
|
2000
2501
|
var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
|
|
2001
2502
|
|
|
2002
2503
|
class AkanIndex {
|