@akanjs/language 3.2.0-rc.0 → 3.2.0-rc.10
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/adaptorResolver.d.ts +18 -0
- package/akanGraph.d.ts +1 -1
- package/akanGraphDiff.d.ts +1 -1
- package/escapeRegExp.d.ts +1 -0
- package/guardFacts.d.ts +50 -0
- package/guardResolver.d.ts +75 -0
- package/index.cjs +1164 -436
- package/index.js +1164 -436
- package/moduleFacts.d.ts +9 -14
- package/package.json +1 -1
- package/routeFacts.d.ts +5 -2
- package/server.js +1120 -435
package/index.js
CHANGED
|
@@ -8,7 +8,8 @@ var isEdited = (before, after) => before.digest !== undefined && after.digest !=
|
|
|
8
8
|
var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
9
9
|
|
|
10
10
|
class AkanGraphDiff {
|
|
11
|
-
static between(
|
|
11
|
+
static between(given, head) {
|
|
12
|
+
const base = AkanGraphDiff.#renamed(given, head);
|
|
12
13
|
const baseNodes = new Map(base.nodes.map((node) => [node.id, node]));
|
|
13
14
|
const headNodes = new Map(head.nodes.map((node) => [node.id, node]));
|
|
14
15
|
const marked = new Map;
|
|
@@ -27,6 +28,9 @@ class AkanGraphDiff {
|
|
|
27
28
|
...head.edges.flatMap((edge) => !baseKeys.has(edgeKeyOf(edge)) ? [{ ...edge, change: "added" }] : marked.has(edge.from) || marked.has(edge.to) ? [edge] : []),
|
|
28
29
|
...base.edges.filter((edge) => !headKeys.has(edgeKeyOf(edge))).map((edge) => ({ ...edge, change: "removed" }))
|
|
29
30
|
];
|
|
31
|
+
const shown = new Set([...marked.keys(), ...edges.flatMap((edge) => [edge.from, edge.to])]);
|
|
32
|
+
const listed = new Set(edges.map(edgeKeyOf));
|
|
33
|
+
edges.push(...head.edges.filter((edge) => edge.kind === "binds" && shown.has(edge.from) && !listed.has(edgeKeyOf(edge))));
|
|
30
34
|
const context = edges.flatMap((edge) => [edge.from, edge.to]).filter((id) => !marked.has(id)).flatMap((id) => {
|
|
31
35
|
const node = headNodes.get(id) ?? baseNodes.get(id);
|
|
32
36
|
return node ? [node] : [];
|
|
@@ -38,6 +42,40 @@ class AkanGraphDiff {
|
|
|
38
42
|
summary[node.change] += 1;
|
|
39
43
|
return { version: 1, nodes, edges, summary };
|
|
40
44
|
}
|
|
45
|
+
static #renamed(base, head) {
|
|
46
|
+
const baseIds = new Set(base.nodes.map((node) => node.id));
|
|
47
|
+
const headIds = new Set(head.nodes.map((node) => node.id));
|
|
48
|
+
const pairKeyOf = ({ kind, owner, name, at }) => at ? `${kind}
|
|
49
|
+
${owner ?? ""}
|
|
50
|
+
${name}
|
|
51
|
+
${at.file}` : null;
|
|
52
|
+
const shortIdOf = ({ kind, owner, name }) => `${kind}:${owner ?? ""}:${name}`;
|
|
53
|
+
const isFileKeyed = (node) => node.id !== shortIdOf(node) && node.id.startsWith(`${node.kind}:${node.owner ?? ""}:`) && node.id.endsWith(`#${node.name}`);
|
|
54
|
+
const fromBase = new Map(base.nodes.flatMap((node) => {
|
|
55
|
+
const key = headIds.has(node.id) ? null : pairKeyOf(node);
|
|
56
|
+
return key ? [[key, node]] : [];
|
|
57
|
+
}));
|
|
58
|
+
const renames = new Map;
|
|
59
|
+
for (const node of head.nodes) {
|
|
60
|
+
if (baseIds.has(node.id))
|
|
61
|
+
continue;
|
|
62
|
+
const key = pairKeyOf(node);
|
|
63
|
+
const before = key ? fromBase.get(key) : undefined;
|
|
64
|
+
if (!before)
|
|
65
|
+
continue;
|
|
66
|
+
const isPair = isFileKeyed(node) ? before.id === shortIdOf(node) : isFileKeyed(before) && node.id === shortIdOf(before);
|
|
67
|
+
if (isPair)
|
|
68
|
+
renames.set(before.id, node.id);
|
|
69
|
+
}
|
|
70
|
+
if (!renames.size)
|
|
71
|
+
return base;
|
|
72
|
+
const rename = (id) => renames.get(id) ?? id;
|
|
73
|
+
return {
|
|
74
|
+
...base,
|
|
75
|
+
nodes: base.nodes.map((node) => renames.has(node.id) ? { ...node, id: rename(node.id) } : node),
|
|
76
|
+
edges: base.edges.map((edge) => ({ ...edge, from: rename(edge.from), to: rename(edge.to) }))
|
|
77
|
+
};
|
|
78
|
+
}
|
|
41
79
|
static #mark(node, change) {
|
|
42
80
|
return node.fields ? { ...node, change, fields: node.fields.map((field) => ({ ...field, change })) } : { ...node, change };
|
|
43
81
|
}
|
|
@@ -71,6 +109,261 @@ class AkanGraphDiff {
|
|
|
71
109
|
return node.fields ? { ...node, fields: node.fields.map(({ digest: _field, ...field }) => field) } : node;
|
|
72
110
|
}
|
|
73
111
|
}
|
|
112
|
+
// pkgs/@akanjs/language/escapeRegExp.ts
|
|
113
|
+
var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
|
|
114
|
+
var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
|
|
115
|
+
|
|
116
|
+
// pkgs/@akanjs/language/guardFacts.ts
|
|
117
|
+
var guardLabelOf = (guard, labels) => {
|
|
118
|
+
const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
|
|
119
|
+
const label = labels[local];
|
|
120
|
+
return label === undefined ? guard : role ? `${role} ${label}` : label;
|
|
121
|
+
};
|
|
122
|
+
var maxLabelArgs = 32;
|
|
123
|
+
var maxLayers = 4;
|
|
124
|
+
var guardRefReaderOf = (ts, ast, unwrap) => {
|
|
125
|
+
const pathOf = (expression) => {
|
|
126
|
+
if (ts.isIdentifier(expression))
|
|
127
|
+
return expression.text;
|
|
128
|
+
if (!ts.isPropertyAccessExpression(expression))
|
|
129
|
+
return null;
|
|
130
|
+
const head = pathOf(expression.expression);
|
|
131
|
+
return head && `${head}.${expression.name.text}`;
|
|
132
|
+
};
|
|
133
|
+
const layersOf = (call) => {
|
|
134
|
+
const inner = unwrap(call.expression);
|
|
135
|
+
if (!ts.isCallExpression(inner))
|
|
136
|
+
return { root: call, layers: 0 };
|
|
137
|
+
const { root, layers } = layersOf(inner);
|
|
138
|
+
return { root, layers: layers + 1 };
|
|
139
|
+
};
|
|
140
|
+
const calleeOf = (call) => pathOf(unwrap(layersOf(call).root.expression)) ?? "?";
|
|
141
|
+
const labelOf = (source, call) => {
|
|
142
|
+
const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
|
|
143
|
+
const inner = unwrap(call.expression);
|
|
144
|
+
const head = ts.isCallExpression(inner) ? labelOf(source, inner) : pathOf(inner) ?? "?";
|
|
145
|
+
return `${head}(${args.length <= maxLabelArgs && !args.includes(`
|
|
146
|
+
`) ? args : "…"})`;
|
|
147
|
+
};
|
|
148
|
+
const guardRefOf = (expression, made, member) => {
|
|
149
|
+
const value = unwrap(expression);
|
|
150
|
+
if (!ts.isCallExpression(value))
|
|
151
|
+
return pathOf(value) ?? ast.expressionName(value);
|
|
152
|
+
const prefix = `${member}:${calleeOf(value)}@`;
|
|
153
|
+
const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
|
|
154
|
+
made.push({ call: value, name });
|
|
155
|
+
return name;
|
|
156
|
+
};
|
|
157
|
+
const guardsIn = (source, object, made, member) => {
|
|
158
|
+
const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
|
|
159
|
+
if (!guards)
|
|
160
|
+
return [];
|
|
161
|
+
const refOf = (expression) => guardRefOf(expression, made, member);
|
|
162
|
+
if (ts.isArrayLiteralExpression(guards))
|
|
163
|
+
return guards.elements.map(refOf).filter((name) => name !== null);
|
|
164
|
+
if (ts.isObjectLiteralExpression(guards))
|
|
165
|
+
return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
|
|
166
|
+
const value = unwrap(property.initializer);
|
|
167
|
+
const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
|
|
168
|
+
return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
|
|
169
|
+
});
|
|
170
|
+
const single = refOf(guards);
|
|
171
|
+
return single ? [single] : [];
|
|
172
|
+
};
|
|
173
|
+
return { pathOf, layersOf, labelOf, guardsIn };
|
|
174
|
+
};
|
|
175
|
+
var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf, layersOf) => {
|
|
176
|
+
const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
|
|
177
|
+
const scopeOf = (node) => {
|
|
178
|
+
const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
|
|
179
|
+
const value = scope && unwrap(scope);
|
|
180
|
+
return value && ts.isStringLiteralLike(value) ? value.text : null;
|
|
181
|
+
};
|
|
182
|
+
const functionsOf = (source) => {
|
|
183
|
+
const functions = new Map;
|
|
184
|
+
for (const statement of source.statements) {
|
|
185
|
+
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
|
|
186
|
+
functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
|
|
187
|
+
if (!ts.isVariableStatement(statement))
|
|
188
|
+
continue;
|
|
189
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
190
|
+
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
191
|
+
if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
|
|
192
|
+
functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return functions;
|
|
196
|
+
};
|
|
197
|
+
const returnedOf = (body, guardNames, depth = 0) => {
|
|
198
|
+
const locals = new Map;
|
|
199
|
+
const pending = [];
|
|
200
|
+
const visit = (node) => {
|
|
201
|
+
if (ts.isClassDeclaration(node) && node.name)
|
|
202
|
+
locals.set(node.name.text, node);
|
|
203
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
|
|
204
|
+
locals.set(node.name.text, node.initializer);
|
|
205
|
+
if (ts.isReturnStatement(node) && node.expression)
|
|
206
|
+
pending.push(node.expression);
|
|
207
|
+
if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
|
|
208
|
+
ts.forEachChild(node, visit);
|
|
209
|
+
};
|
|
210
|
+
if (ts.isBlock(body))
|
|
211
|
+
ts.forEachChild(body, visit);
|
|
212
|
+
else
|
|
213
|
+
pending.push(body);
|
|
214
|
+
let guard = null;
|
|
215
|
+
const calls = [];
|
|
216
|
+
const seen = new Set;
|
|
217
|
+
const passed = new Set;
|
|
218
|
+
const returned = [];
|
|
219
|
+
for (let node = pending.pop();node; node = pending.pop()) {
|
|
220
|
+
if (seen.has(node))
|
|
221
|
+
continue;
|
|
222
|
+
seen.add(node);
|
|
223
|
+
const value = ts.isExpression(node) ? unwrap(node) : node;
|
|
224
|
+
if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
|
|
225
|
+
guard ??= value;
|
|
226
|
+
else if ((ts.isArrowFunction(value) || ts.isFunctionExpression(value)) && !passed.has(node) && depth < maxLayers)
|
|
227
|
+
returned.push(value.body);
|
|
228
|
+
else if (ts.isConditionalExpression(value))
|
|
229
|
+
pending.push(value.whenTrue, value.whenFalse);
|
|
230
|
+
else if (ts.isBinaryExpression(value))
|
|
231
|
+
pending.push(value.left, value.right);
|
|
232
|
+
else if (ts.isIdentifier(value)) {
|
|
233
|
+
const local = locals.get(value.text);
|
|
234
|
+
if (local)
|
|
235
|
+
pending.push(local);
|
|
236
|
+
} else if (ts.isCallExpression(value)) {
|
|
237
|
+
const { root, layers } = layersOf(value);
|
|
238
|
+
const callee = pathOf(unwrap(root.expression));
|
|
239
|
+
if (callee && !passed.has(node) && !calls.some((call) => call.callee === callee))
|
|
240
|
+
calls.push({ callee, layers });
|
|
241
|
+
for (let call = value;ts.isCallExpression(call); call = unwrap(call.expression)) {
|
|
242
|
+
for (const arg of call.arguments)
|
|
243
|
+
passed.add(arg);
|
|
244
|
+
pending.push(...call.arguments);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const [inner] = returned;
|
|
249
|
+
if (guard || calls.length || !inner)
|
|
250
|
+
return { guard, calls, layers: 0 };
|
|
251
|
+
const nested = returnedOf(inner, guardNames, depth + 1);
|
|
252
|
+
return { ...nested, layers: nested.layers + 1 };
|
|
253
|
+
};
|
|
254
|
+
const guardFactoriesOf = (source, guardNames, isImported) => {
|
|
255
|
+
const functions = functionsOf(source);
|
|
256
|
+
const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
|
|
257
|
+
const members = new Set;
|
|
258
|
+
const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
|
|
259
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
260
|
+
isGrowing = false;
|
|
261
|
+
for (const [name, { guard, calls }] of returned) {
|
|
262
|
+
if (members.has(name) || !guard && !calls.some(({ callee }) => isFactoryCall(callee)))
|
|
263
|
+
continue;
|
|
264
|
+
members.add(name);
|
|
265
|
+
isGrowing = true;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const factories = new Map;
|
|
269
|
+
for (const name of members) {
|
|
270
|
+
const found = functions.get(name);
|
|
271
|
+
const made = returned.get(name);
|
|
272
|
+
if (!found || !made)
|
|
273
|
+
continue;
|
|
274
|
+
factories.set(name, {
|
|
275
|
+
name,
|
|
276
|
+
span: spanOf(source, found.name),
|
|
277
|
+
extent: extentOf(source, found.statement),
|
|
278
|
+
scope: made.guard ? scopeOf(made.guard) : null,
|
|
279
|
+
doc: docOf(source, found.statement),
|
|
280
|
+
direct: !!made.guard,
|
|
281
|
+
via: made.calls.filter(({ callee }) => isFactoryCall(callee)),
|
|
282
|
+
layers: made.layers
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return factories;
|
|
286
|
+
};
|
|
287
|
+
return { implementsGuard, scopeOf, guardFactoriesOf };
|
|
288
|
+
};
|
|
289
|
+
var createGuardFactReader = (ts, ast, base) => {
|
|
290
|
+
const { spanOf, extentOf, unwrap, docOf } = base;
|
|
291
|
+
const { pathOf, layersOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
|
|
292
|
+
const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf, layersOf);
|
|
293
|
+
const guardsOf = (source, imports, madeInPlace) => {
|
|
294
|
+
const guardNames = new Set([
|
|
295
|
+
"Guard",
|
|
296
|
+
...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
|
|
297
|
+
]);
|
|
298
|
+
const imported = new Set(imports.map(({ name }) => name));
|
|
299
|
+
const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
|
|
300
|
+
const factories = guardFactoriesOf(source, guardNames, isImported);
|
|
301
|
+
const guards = [];
|
|
302
|
+
const calls = [];
|
|
303
|
+
const makeOf = (made, value, isInPlace = false) => {
|
|
304
|
+
if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
|
|
305
|
+
guards.push({ ...made, scope: scopeOf(value), factory: null, layers: 0 });
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const { root, layers } = ts.isCallExpression(value) ? layersOf(value) : { root: null, layers: 0 };
|
|
309
|
+
const callee = root && pathOf(unwrap(root.expression));
|
|
310
|
+
const factory = callee && factories.get(callee);
|
|
311
|
+
if (factory)
|
|
312
|
+
guards.push({ ...made, scope: factory.scope, factory: factory.name, layers });
|
|
313
|
+
else if (callee && (isImported(callee) || isInPlace))
|
|
314
|
+
calls.push({ ...made, callee, kind: "call", scope: null, layers });
|
|
315
|
+
};
|
|
316
|
+
for (const statement of source.statements) {
|
|
317
|
+
if (ts.isClassDeclaration(statement) && statement.name) {
|
|
318
|
+
const declared = {
|
|
319
|
+
name: statement.name.text,
|
|
320
|
+
span: spanOf(source, statement.name),
|
|
321
|
+
extent: extentOf(source, statement),
|
|
322
|
+
scope: scopeOf(statement),
|
|
323
|
+
doc: docOf(source, statement)
|
|
324
|
+
};
|
|
325
|
+
const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
|
|
326
|
+
const callee = base && pathOf(base.expression);
|
|
327
|
+
if (implementsGuard(statement, guardNames))
|
|
328
|
+
guards.push({ ...declared, factory: null, layers: 0 });
|
|
329
|
+
else if (callee)
|
|
330
|
+
calls.push({ ...declared, callee, kind: "extends", layers: 0 });
|
|
331
|
+
}
|
|
332
|
+
if (!ts.isVariableStatement(statement))
|
|
333
|
+
continue;
|
|
334
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
335
|
+
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
336
|
+
if (!initializer || !ts.isIdentifier(declaration.name))
|
|
337
|
+
continue;
|
|
338
|
+
const holder = declaration.name.text;
|
|
339
|
+
makeOf({
|
|
340
|
+
name: holder,
|
|
341
|
+
span: spanOf(source, declaration.name),
|
|
342
|
+
extent: extentOf(source, statement),
|
|
343
|
+
doc: docOf(source, statement)
|
|
344
|
+
}, initializer);
|
|
345
|
+
if (!ts.isObjectLiteralExpression(initializer))
|
|
346
|
+
continue;
|
|
347
|
+
for (const property of initializer.properties) {
|
|
348
|
+
const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
|
|
349
|
+
if (key && ts.isPropertyAssignment(property))
|
|
350
|
+
makeOf({
|
|
351
|
+
name: `${holder}.${key}`,
|
|
352
|
+
span: spanOf(source, property.name),
|
|
353
|
+
extent: extentOf(source, property),
|
|
354
|
+
doc: docOf(source, property)
|
|
355
|
+
}, unwrap(property.initializer));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
for (const { call, name } of madeInPlace)
|
|
360
|
+
makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
|
|
361
|
+
const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
|
|
362
|
+
return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
|
|
363
|
+
};
|
|
364
|
+
return { guardsIn, guardsOf };
|
|
365
|
+
};
|
|
366
|
+
|
|
74
367
|
// pkgs/@akanjs/language/moduleFacts.ts
|
|
75
368
|
var testFilePattern = /\.(?:test|spec)\.tsx?$/;
|
|
76
369
|
var moduleCallees = new Set([
|
|
@@ -139,21 +432,6 @@ var createFactReader = (ts, ast) => {
|
|
|
139
432
|
const value = unwrap(expression);
|
|
140
433
|
return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
|
|
141
434
|
};
|
|
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
435
|
const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
|
|
158
436
|
const chainOf = (initializer) => {
|
|
159
437
|
let root = unwrap(initializer);
|
|
@@ -170,7 +448,7 @@ var createFactReader = (ts, ast) => {
|
|
|
170
448
|
const node = keyName(property);
|
|
171
449
|
return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
|
|
172
450
|
});
|
|
173
|
-
const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
451
|
+
const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
174
452
|
if (!ts.isPropertyAssignment(property))
|
|
175
453
|
return [];
|
|
176
454
|
const name = ast.nodeName(property.name);
|
|
@@ -182,7 +460,7 @@ var createFactReader = (ts, ast) => {
|
|
|
182
460
|
name,
|
|
183
461
|
span: spanOf(source, property.name),
|
|
184
462
|
extent: extentOf(source, property),
|
|
185
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
463
|
+
guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
|
|
186
464
|
live: methods.includes("live"),
|
|
187
465
|
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
188
466
|
}
|
|
@@ -232,7 +510,7 @@ var createFactReader = (ts, ast) => {
|
|
|
232
510
|
const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
|
|
233
511
|
return `${ms / size}${unit}`;
|
|
234
512
|
};
|
|
235
|
-
const modesOf = (option) => {
|
|
513
|
+
const modesOf = (option, type) => {
|
|
236
514
|
const object = option && unwrap(option);
|
|
237
515
|
if (!object || !ts.isObjectLiteralExpression(object))
|
|
238
516
|
return [];
|
|
@@ -242,9 +520,9 @@ var createFactReader = (ts, ast) => {
|
|
|
242
520
|
if (name === "enabled")
|
|
243
521
|
return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
|
|
244
522
|
if (name === "once")
|
|
245
|
-
return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
523
|
+
return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
246
524
|
if (name === "lock")
|
|
247
|
-
return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
525
|
+
return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
248
526
|
if (name !== "serverMode" && name !== "operationMode")
|
|
249
527
|
return [];
|
|
250
528
|
const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
|
|
@@ -262,7 +540,8 @@ var createFactReader = (ts, ast) => {
|
|
|
262
540
|
const { root } = chainOf(property.initializer);
|
|
263
541
|
const type = root ? verbOf(root.expression) : null;
|
|
264
542
|
const [first, second] = root?.arguments ?? [];
|
|
265
|
-
const
|
|
543
|
+
const cron = type === "cron" && first ? unwrap(first) : null;
|
|
544
|
+
const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
|
|
266
545
|
const option = type === "initialize" || type === "destroy" ? first : second;
|
|
267
546
|
return [
|
|
268
547
|
{
|
|
@@ -271,7 +550,7 @@ var createFactReader = (ts, ast) => {
|
|
|
271
550
|
extent: extentOf(source, property),
|
|
272
551
|
type,
|
|
273
552
|
schedule,
|
|
274
|
-
modes: modesOf(option)
|
|
553
|
+
modes: modesOf(option, type)
|
|
275
554
|
}
|
|
276
555
|
];
|
|
277
556
|
});
|
|
@@ -288,7 +567,20 @@ var createFactReader = (ts, ast) => {
|
|
|
288
567
|
return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
|
|
289
568
|
});
|
|
290
569
|
};
|
|
291
|
-
const
|
|
570
|
+
const adaptorBindingsIn = (source) => {
|
|
571
|
+
const bindings = [];
|
|
572
|
+
const visit = (node) => {
|
|
573
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
|
|
574
|
+
const [role, adaptor] = node.arguments.map(guardNameOf);
|
|
575
|
+
if (role && adaptor)
|
|
576
|
+
bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
|
|
577
|
+
}
|
|
578
|
+
ts.forEachChild(node, visit);
|
|
579
|
+
};
|
|
580
|
+
visit(source);
|
|
581
|
+
return bindings;
|
|
582
|
+
};
|
|
583
|
+
const endpointKeysOf = (source, builder, made, model) => {
|
|
292
584
|
const verbOf = verbReaderOf(builder?.parameters[0]);
|
|
293
585
|
return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
294
586
|
if (!ts.isPropertyAssignment(property))
|
|
@@ -303,7 +595,7 @@ var createFactReader = (ts, ast) => {
|
|
|
303
595
|
span: spanOf(source, property.name),
|
|
304
596
|
extent: extentOf(source, property),
|
|
305
597
|
verb: root ? verbOf(root.expression) : null,
|
|
306
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
598
|
+
guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
|
|
307
599
|
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
308
600
|
}
|
|
309
601
|
];
|
|
@@ -370,22 +662,6 @@ var createFactReader = (ts, ast) => {
|
|
|
370
662
|
})) : []
|
|
371
663
|
];
|
|
372
664
|
};
|
|
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
665
|
const docOf = (source, node) => {
|
|
390
666
|
const { text } = source;
|
|
391
667
|
const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
|
|
@@ -401,108 +677,20 @@ var createFactReader = (ts, ast) => {
|
|
|
401
677
|
`)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
|
|
402
678
|
return lines.length ? lines.join(" ") : null;
|
|
403
679
|
};
|
|
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
|
-
};
|
|
680
|
+
const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
|
|
497
681
|
const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
|
|
498
682
|
const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
|
|
499
683
|
const read = (source, owner) => {
|
|
500
684
|
const imports = importsIn(source);
|
|
685
|
+
const madeInPlace = [];
|
|
501
686
|
const facts = {
|
|
502
687
|
fileName: source.fileName,
|
|
503
688
|
owner,
|
|
504
689
|
imports,
|
|
505
|
-
|
|
690
|
+
guards: [],
|
|
691
|
+
guardFactories: [],
|
|
692
|
+
guardCalls: [],
|
|
693
|
+
guardLabels: {},
|
|
506
694
|
constants: [],
|
|
507
695
|
enums: [],
|
|
508
696
|
filters: [],
|
|
@@ -512,6 +700,7 @@ var createFactReader = (ts, ast) => {
|
|
|
512
700
|
endpoints: [],
|
|
513
701
|
internals: [],
|
|
514
702
|
adaptors: [],
|
|
703
|
+
adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
|
|
515
704
|
stores: []
|
|
516
705
|
};
|
|
517
706
|
for (const statement of source.statements) {
|
|
@@ -536,16 +725,19 @@ var createFactReader = (ts, ast) => {
|
|
|
536
725
|
...name,
|
|
537
726
|
model: moduleRefOf(call.arguments[0], "db"),
|
|
538
727
|
methods: methodsOf(source, statement),
|
|
539
|
-
plugs: plugsOf(source, builder)
|
|
728
|
+
plugs: plugsOf(source, builder),
|
|
729
|
+
fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
|
|
540
730
|
});
|
|
541
|
-
else if (callee === "slice")
|
|
731
|
+
else if (callee === "slice") {
|
|
732
|
+
const model = moduleRefOf(call.arguments[0], "srv");
|
|
733
|
+
const member = model ?? name.name;
|
|
542
734
|
facts.slices.push({
|
|
543
735
|
...name,
|
|
544
|
-
model
|
|
545
|
-
guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
|
|
546
|
-
keys: sliceKeysOf(source, builder)
|
|
736
|
+
model,
|
|
737
|
+
guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
|
|
738
|
+
keys: sliceKeysOf(source, builder, madeInPlace, member)
|
|
547
739
|
});
|
|
548
|
-
else if (callee === "internal")
|
|
740
|
+
} else if (callee === "internal")
|
|
549
741
|
facts.internals.push({
|
|
550
742
|
...name,
|
|
551
743
|
model: moduleRefOf(call.arguments[0], "srv"),
|
|
@@ -560,13 +752,14 @@ var createFactReader = (ts, ast) => {
|
|
|
560
752
|
plugs: plugsOf(source, builder),
|
|
561
753
|
doc: docOf(source, statement)
|
|
562
754
|
});
|
|
563
|
-
} else if (callee === "endpoint")
|
|
755
|
+
} else if (callee === "endpoint") {
|
|
756
|
+
const model = moduleRefOf(call.arguments[0], "srv");
|
|
564
757
|
facts.endpoints.push({
|
|
565
758
|
...name,
|
|
566
|
-
model
|
|
567
|
-
keys: endpointKeysOf(source, builder)
|
|
759
|
+
model,
|
|
760
|
+
keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
|
|
568
761
|
});
|
|
569
|
-
else if (callee === "store")
|
|
762
|
+
} else if (callee === "store")
|
|
570
763
|
facts.stores.push({
|
|
571
764
|
...name,
|
|
572
765
|
model: moduleRefOf(call.arguments[0], "sig"),
|
|
@@ -574,10 +767,647 @@ var createFactReader = (ts, ast) => {
|
|
|
574
767
|
actions: methodsOf(source, statement)
|
|
575
768
|
});
|
|
576
769
|
}
|
|
577
|
-
return facts;
|
|
578
|
-
};
|
|
579
|
-
return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
|
|
580
|
-
};
|
|
770
|
+
return { ...facts, ...guardsOf(source, imports, madeInPlace) };
|
|
771
|
+
};
|
|
772
|
+
return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
// pkgs/@akanjs/language/guardResolver.ts
|
|
776
|
+
var frameworkOwner = "akanjs";
|
|
777
|
+
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
778
|
+
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
779
|
+
var maxMapDepth = 4;
|
|
780
|
+
var identifierPattern = /^[\p{ID_Start}$_][\p{ID_Continue}$\u200c\u200d]*$/u;
|
|
781
|
+
var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
|
|
782
|
+
var exportingPatternOf = (name) => new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
|
|
783
|
+
var declaringPatternOf = (name, isAnyBinding) => {
|
|
784
|
+
const escaped = escapeRegExp(name);
|
|
785
|
+
const braced = "\\{(?:[^}]*?[\\s,])?";
|
|
786
|
+
const leads = [
|
|
787
|
+
"function\\s*\\*?\\s*",
|
|
788
|
+
"(?:const|let|var)\\s+",
|
|
789
|
+
`export\\s*${braced}`,
|
|
790
|
+
...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
|
|
791
|
+
];
|
|
792
|
+
return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
class GuardResolver {
|
|
796
|
+
#source;
|
|
797
|
+
#routes;
|
|
798
|
+
#guards = new Map;
|
|
799
|
+
#madeInPlace = new Map;
|
|
800
|
+
#mapEntries = new Map;
|
|
801
|
+
#reexports = new Map;
|
|
802
|
+
#declarations = new Map;
|
|
803
|
+
#starExports = new Map;
|
|
804
|
+
#moduleExports = new Map;
|
|
805
|
+
#ownerFiles = null;
|
|
806
|
+
#modules = null;
|
|
807
|
+
constructor(source, routes) {
|
|
808
|
+
this.#source = source;
|
|
809
|
+
this.#routes = routes;
|
|
810
|
+
}
|
|
811
|
+
find(local, site) {
|
|
812
|
+
return this.#findGuard(local, site, 0);
|
|
813
|
+
}
|
|
814
|
+
madeInPlace(fileName, name) {
|
|
815
|
+
return this.#madeInPlace.get(`${fileName}
|
|
816
|
+
${name}`);
|
|
817
|
+
}
|
|
818
|
+
declare(facts) {
|
|
819
|
+
const factories = new Map;
|
|
820
|
+
const declaredFactories = [];
|
|
821
|
+
for (const file of facts)
|
|
822
|
+
for (const fact of file.guardFactories) {
|
|
823
|
+
const factory = {
|
|
824
|
+
fact,
|
|
825
|
+
owner: file.owner,
|
|
826
|
+
site: file,
|
|
827
|
+
made: [],
|
|
828
|
+
isReal: fact.direct,
|
|
829
|
+
scope: fact.scope,
|
|
830
|
+
layers: fact.layers,
|
|
831
|
+
targets: []
|
|
832
|
+
};
|
|
833
|
+
factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
|
|
834
|
+
declaredFactories.push(factory);
|
|
835
|
+
}
|
|
836
|
+
for (const factory of declaredFactories)
|
|
837
|
+
factory.targets = factory.fact.via.flatMap(({ callee, layers }) => {
|
|
838
|
+
const target = this.#factoryAt(factories, callee, factory.site);
|
|
839
|
+
return target ? [{ target, layers }] : [];
|
|
840
|
+
});
|
|
841
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
842
|
+
isGrowing = false;
|
|
843
|
+
for (const factory of declaredFactories) {
|
|
844
|
+
const via = factory.targets.find(({ target, layers }) => target.isReal && target.layers >= layers);
|
|
845
|
+
if (factory.isReal || !via)
|
|
846
|
+
continue;
|
|
847
|
+
factory.isReal = true;
|
|
848
|
+
factory.layers = factory.fact.layers + via.target.layers - via.layers;
|
|
849
|
+
factory.scope ??= via.target.scope;
|
|
850
|
+
isGrowing = true;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
const partsOf = (factory, seen = new Set) => {
|
|
854
|
+
if (seen.has(factory))
|
|
855
|
+
return [];
|
|
856
|
+
seen.add(factory);
|
|
857
|
+
return [
|
|
858
|
+
{ fileName: factory.site.fileName, extent: factory.fact.extent },
|
|
859
|
+
...factory.targets.filter(({ target }) => target.isReal).flatMap(({ target }) => partsOf(target, seen))
|
|
860
|
+
];
|
|
861
|
+
};
|
|
862
|
+
const declare = (file, made, scope, parts) => {
|
|
863
|
+
const { name, span, extent, doc } = made;
|
|
864
|
+
const label = file.guardLabels[name];
|
|
865
|
+
const own = { fileName: file.fileName, extent };
|
|
866
|
+
const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
|
|
867
|
+
if (name.includes("@"))
|
|
868
|
+
this.#madeInPlace.set(`${file.fileName}
|
|
869
|
+
${name}`, guard);
|
|
870
|
+
else
|
|
871
|
+
this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
|
|
872
|
+
return guard;
|
|
873
|
+
};
|
|
874
|
+
const extending = [];
|
|
875
|
+
for (const file of facts) {
|
|
876
|
+
for (const made of file.guards) {
|
|
877
|
+
const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
|
|
878
|
+
if (!made.factory)
|
|
879
|
+
declare(file, made, made.scope, []);
|
|
880
|
+
else if (factory?.isReal && factory.layers === made.layers)
|
|
881
|
+
factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
|
|
882
|
+
}
|
|
883
|
+
for (const made of file.guardCalls) {
|
|
884
|
+
if (made.kind === "extends") {
|
|
885
|
+
extending.push([file, made]);
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
const factory = this.#factoryAt(factories, made.callee, file);
|
|
889
|
+
if (factory?.isReal && factory.layers === made.layers)
|
|
890
|
+
factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
894
|
+
isGrowing = false;
|
|
895
|
+
for (const [idx, entry] of extending.entries()) {
|
|
896
|
+
const [file, made] = entry;
|
|
897
|
+
const found = this.#findGuard(made.callee, file, 0, true);
|
|
898
|
+
if (!("guard" in found))
|
|
899
|
+
continue;
|
|
900
|
+
declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
|
|
901
|
+
extending.splice(idx, 1);
|
|
902
|
+
isGrowing = true;
|
|
903
|
+
break;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
for (const { fact, made, isReal } of declaredFactories)
|
|
907
|
+
if (isReal && made.every((guard) => !guard.doc))
|
|
908
|
+
for (const guard of made)
|
|
909
|
+
guard.doc = fact.doc;
|
|
910
|
+
for (const list of this.#guards.values())
|
|
911
|
+
for (const guard of list)
|
|
912
|
+
if (list.some((other) => other !== guard && other.owner === guard.owner))
|
|
913
|
+
guard.file = this.inOwner(guard.span.fileName, guard.owner);
|
|
914
|
+
for (const guard of this.#madeInPlace.values())
|
|
915
|
+
guard.file = this.inOwner(guard.span.fileName, guard.owner);
|
|
916
|
+
return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
|
|
917
|
+
}
|
|
918
|
+
#factoryAt(factories, callee, site, depth = 0) {
|
|
919
|
+
const [head = callee, member] = callee.split(".");
|
|
920
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
921
|
+
if (!imported)
|
|
922
|
+
return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
|
|
923
|
+
if (member && imported.imported !== "*")
|
|
924
|
+
return;
|
|
925
|
+
const from = this.ownerOfSpecifier(site.fileName, imported.from);
|
|
926
|
+
if (from === undefined || from === frameworkOwner)
|
|
927
|
+
return;
|
|
928
|
+
const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
|
|
929
|
+
const candidates = factories.get(name) ?? [];
|
|
930
|
+
const near = this.pathOfSpecifier(site.fileName, imported.from);
|
|
931
|
+
const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
|
|
932
|
+
if (own)
|
|
933
|
+
return own;
|
|
934
|
+
const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
|
|
935
|
+
if (reexport)
|
|
936
|
+
return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
|
|
937
|
+
return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
|
|
938
|
+
}
|
|
939
|
+
#findGuard(local, site, depth, isBase = false) {
|
|
940
|
+
const segments = local.split(".");
|
|
941
|
+
const head = segments[0] ?? local;
|
|
942
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
943
|
+
const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
|
|
944
|
+
if (imported && from === undefined)
|
|
945
|
+
return { external: true };
|
|
946
|
+
const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
|
|
947
|
+
const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
|
|
948
|
+
const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
|
|
949
|
+
const holder = imported ? from ?? null : site.owner;
|
|
950
|
+
const [objectName, member] = path;
|
|
951
|
+
if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
|
|
952
|
+
const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
|
|
953
|
+
if (made)
|
|
954
|
+
return { guard: made };
|
|
955
|
+
for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
|
|
956
|
+
const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
|
|
957
|
+
if ("guard" in found)
|
|
958
|
+
return found;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
const name = path[path.length - 1] ?? head;
|
|
962
|
+
if (from === frameworkOwner)
|
|
963
|
+
return { name, from };
|
|
964
|
+
const declared = this.#guards.get(name) ?? [];
|
|
965
|
+
const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
|
|
966
|
+
if (own)
|
|
967
|
+
return { guard: own };
|
|
968
|
+
if (!imported) {
|
|
969
|
+
const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
|
|
970
|
+
return visible ? { guard: visible } : { name, from };
|
|
971
|
+
}
|
|
972
|
+
const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
|
|
973
|
+
if (reexport)
|
|
974
|
+
return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
|
|
975
|
+
const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
|
|
976
|
+
return reached ? { guard: reached } : { name, from };
|
|
977
|
+
}
|
|
978
|
+
reexportOf(owner, name, near) {
|
|
979
|
+
const key = `${owner}
|
|
980
|
+
${name}
|
|
981
|
+
${near ?? ""}`;
|
|
982
|
+
const cached = this.#reexports.get(key);
|
|
983
|
+
if (cached !== undefined)
|
|
984
|
+
return cached;
|
|
985
|
+
let found = null;
|
|
986
|
+
const exporting = exportingPatternOf(name);
|
|
987
|
+
for (const fileName of this.#filesOf(owner, name, near)) {
|
|
988
|
+
const text = this.#source.readText(fileName);
|
|
989
|
+
if (text === undefined || !exporting.test(text))
|
|
990
|
+
continue;
|
|
991
|
+
const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
|
|
992
|
+
if (!reexport)
|
|
993
|
+
continue;
|
|
994
|
+
const site = { fileName, owner, imports: [] };
|
|
995
|
+
found = reexport.from ? {
|
|
996
|
+
local: reexport.name,
|
|
997
|
+
site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
|
|
998
|
+
} : { local: reexport.name, site };
|
|
999
|
+
break;
|
|
1000
|
+
}
|
|
1001
|
+
this.#reexports.set(key, found);
|
|
1002
|
+
return found;
|
|
1003
|
+
}
|
|
1004
|
+
#reexportIn(source, name) {
|
|
1005
|
+
const { ts, reader } = this.#source;
|
|
1006
|
+
for (const statement of source.statements) {
|
|
1007
|
+
if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
|
|
1008
|
+
continue;
|
|
1009
|
+
const { exportClause, moduleSpecifier } = statement;
|
|
1010
|
+
if (!exportClause || !ts.isNamedExports(exportClause))
|
|
1011
|
+
continue;
|
|
1012
|
+
const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
|
|
1013
|
+
if (!element)
|
|
1014
|
+
continue;
|
|
1015
|
+
const original = (element.propertyName ?? element.name).text;
|
|
1016
|
+
if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
|
|
1017
|
+
return { name: original, from: moduleSpecifier.text };
|
|
1018
|
+
const imported = reader.importsIn(source).find((item) => item.name === original);
|
|
1019
|
+
if (!imported)
|
|
1020
|
+
return { name: original, from: null };
|
|
1021
|
+
return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
|
|
1022
|
+
}
|
|
1023
|
+
return null;
|
|
1024
|
+
}
|
|
1025
|
+
#filesOf(owner, name, near) {
|
|
1026
|
+
const isNear = (fileName) => {
|
|
1027
|
+
const relative = this.#source.relative(fileName);
|
|
1028
|
+
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
1029
|
+
};
|
|
1030
|
+
if (!identifierPattern.test(name))
|
|
1031
|
+
return [];
|
|
1032
|
+
const candidates = this.#filesIn(owner).filter(([, words]) => words.includes(`
|
|
1033
|
+
${name}
|
|
1034
|
+
`)).map(([fileName]) => fileName);
|
|
1035
|
+
return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
|
|
1036
|
+
}
|
|
1037
|
+
#mapEntriesOf(objectName, member, fileName, owner) {
|
|
1038
|
+
const key = `${objectName}
|
|
1039
|
+
${member}
|
|
1040
|
+
${fileName ?? ""}
|
|
1041
|
+
${owner ?? ""}`;
|
|
1042
|
+
const cached = this.#mapEntries.get(key);
|
|
1043
|
+
if (cached)
|
|
1044
|
+
return cached;
|
|
1045
|
+
const { ts, reader } = this.#source;
|
|
1046
|
+
const entries = [];
|
|
1047
|
+
const fileNames = [
|
|
1048
|
+
...fileName ? [fileName] : [],
|
|
1049
|
+
...(owner === null ? [] : this.#filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
|
|
1050
|
+
${objectName}
|
|
1051
|
+
`) && words.includes(`
|
|
1052
|
+
${member}
|
|
1053
|
+
`)).map(([other]) => other)
|
|
1054
|
+
];
|
|
1055
|
+
for (const holder of fileNames) {
|
|
1056
|
+
const text = this.#source.readText(holder);
|
|
1057
|
+
if (text === undefined)
|
|
1058
|
+
continue;
|
|
1059
|
+
const source = this.#source.parse(holder, text);
|
|
1060
|
+
const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
|
|
1061
|
+
for (const statement of source.statements) {
|
|
1062
|
+
if (!ts.isVariableStatement(statement))
|
|
1063
|
+
continue;
|
|
1064
|
+
const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
|
|
1065
|
+
const object = map && reader.unwrap(map);
|
|
1066
|
+
if (!object || !ts.isObjectLiteralExpression(object))
|
|
1067
|
+
continue;
|
|
1068
|
+
for (const property of object.properties) {
|
|
1069
|
+
if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
|
|
1070
|
+
entries.push({ name: member, site });
|
|
1071
|
+
if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
|
|
1072
|
+
continue;
|
|
1073
|
+
const value = reader.unwrap(property.initializer);
|
|
1074
|
+
if (ts.isIdentifier(value))
|
|
1075
|
+
entries.push({ name: value.text, site });
|
|
1076
|
+
else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
|
|
1077
|
+
entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
this.#mapEntries.set(key, entries);
|
|
1082
|
+
return entries;
|
|
1083
|
+
}
|
|
1084
|
+
declarationOf(owner, name, near, depth = 0) {
|
|
1085
|
+
const key = `${owner}
|
|
1086
|
+
${name}
|
|
1087
|
+
${near ?? ""}`;
|
|
1088
|
+
const cached = this.#declarations.get(key);
|
|
1089
|
+
if (cached !== undefined)
|
|
1090
|
+
return cached;
|
|
1091
|
+
this.#declarations.set(key, null);
|
|
1092
|
+
const declaring = declaringPatternOf(name, false);
|
|
1093
|
+
let found = null;
|
|
1094
|
+
for (const fileName of this.#filesOf(owner, name, near)) {
|
|
1095
|
+
const text = this.#source.readText(fileName);
|
|
1096
|
+
if (text === undefined || !declaring.test(text))
|
|
1097
|
+
continue;
|
|
1098
|
+
const source = this.#source.parse(fileName, text);
|
|
1099
|
+
const declared = this.#routes.declarationIn(source, name);
|
|
1100
|
+
if (declared) {
|
|
1101
|
+
found = { source, ...declared, name };
|
|
1102
|
+
break;
|
|
1103
|
+
}
|
|
1104
|
+
const reexport = this.#reexportIn(source, name);
|
|
1105
|
+
if (!reexport)
|
|
1106
|
+
continue;
|
|
1107
|
+
if (!reexport.from) {
|
|
1108
|
+
const local = this.#routes.declarationIn(source, reexport.name);
|
|
1109
|
+
found = local ? { source, ...local, name: reexport.name } : null;
|
|
1110
|
+
break;
|
|
1111
|
+
}
|
|
1112
|
+
const next = this.ownerOfSpecifier(fileName, reexport.from);
|
|
1113
|
+
if (next && next !== frameworkOwner && depth < maxMapDepth)
|
|
1114
|
+
found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
this.#declarations.set(key, found);
|
|
1118
|
+
return found;
|
|
1119
|
+
}
|
|
1120
|
+
#throughStar(declared, owner, name, near) {
|
|
1121
|
+
if (!declared.length)
|
|
1122
|
+
return;
|
|
1123
|
+
const { owners, isOpaque } = this.starExportsOf(owner, near);
|
|
1124
|
+
const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
|
|
1125
|
+
return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
|
|
1126
|
+
}
|
|
1127
|
+
starExportsOf(owner, near) {
|
|
1128
|
+
const key = `${owner}
|
|
1129
|
+
${near}`;
|
|
1130
|
+
const cached = this.#starExports.get(key);
|
|
1131
|
+
if (cached)
|
|
1132
|
+
return cached;
|
|
1133
|
+
const found = { owners: [], packages: [], paths: [near], isOpaque: false };
|
|
1134
|
+
this.#starExports.set(key, found);
|
|
1135
|
+
for (let idx = 0;idx < found.paths.length; idx++)
|
|
1136
|
+
for (const fileName of this.#modulesAt(found.paths[idx] ?? ""))
|
|
1137
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
|
|
1138
|
+
const target = this.ownerOfSpecifier(fileName, specifier);
|
|
1139
|
+
const path = this.pathOfSpecifier(fileName, specifier);
|
|
1140
|
+
if (path === null && !found.packages.includes(specifier))
|
|
1141
|
+
found.packages.push(specifier);
|
|
1142
|
+
if (target === undefined || target === frameworkOwner || path === null)
|
|
1143
|
+
found.isOpaque = true;
|
|
1144
|
+
else if (!found.paths.includes(path))
|
|
1145
|
+
found.paths.push(path);
|
|
1146
|
+
if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
|
|
1147
|
+
found.owners.push(target);
|
|
1148
|
+
}
|
|
1149
|
+
return found;
|
|
1150
|
+
}
|
|
1151
|
+
exportsAt(path, name) {
|
|
1152
|
+
const key = `${path}
|
|
1153
|
+
${name}`;
|
|
1154
|
+
const cached = this.#moduleExports.get(key);
|
|
1155
|
+
if (cached)
|
|
1156
|
+
return cached;
|
|
1157
|
+
const found = { named: null, stars: [] };
|
|
1158
|
+
const exporting = exportingPatternOf(name);
|
|
1159
|
+
for (const fileName of this.#modulesAt(path)) {
|
|
1160
|
+
const text = this.#source.readText(fileName);
|
|
1161
|
+
if (text === undefined)
|
|
1162
|
+
continue;
|
|
1163
|
+
const named = identifierPattern.test(name) && exporting.test(text) ? this.#reexportIn(this.#source.parse(fileName, text), name) : null;
|
|
1164
|
+
if (named) {
|
|
1165
|
+
found.named = { ...named, fileName };
|
|
1166
|
+
found.stars = [];
|
|
1167
|
+
break;
|
|
1168
|
+
}
|
|
1169
|
+
for (const [, specifier = ""] of text.matchAll(starExportPattern)) {
|
|
1170
|
+
const target = this.pathOfSpecifier(fileName, specifier);
|
|
1171
|
+
if (target !== null && !found.stars.includes(target))
|
|
1172
|
+
found.stars.push(target);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
this.#moduleExports.set(key, found);
|
|
1176
|
+
return found;
|
|
1177
|
+
}
|
|
1178
|
+
#modulesAt(path) {
|
|
1179
|
+
if (!this.#modules) {
|
|
1180
|
+
this.#modules = new Map;
|
|
1181
|
+
for (const [fileName] of this.#source.files) {
|
|
1182
|
+
const module = this.#source.relative(fileName).replace(/\.tsx?$/, "");
|
|
1183
|
+
for (const key of module.endsWith("/index") ? [module, module.slice(0, -6)] : [module])
|
|
1184
|
+
this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return this.#modules.get(path) ?? [];
|
|
1188
|
+
}
|
|
1189
|
+
hasModule(path) {
|
|
1190
|
+
return this.#modulesAt(path).length > 0;
|
|
1191
|
+
}
|
|
1192
|
+
packagesUnder(owner, folder) {
|
|
1193
|
+
const packages = new Set;
|
|
1194
|
+
for (const [fileName, words] of this.#filesIn(owner)) {
|
|
1195
|
+
if (!this.#source.relative(fileName).startsWith(`${folder}/`) || !words.includes(`
|
|
1196
|
+
export
|
|
1197
|
+
`))
|
|
1198
|
+
continue;
|
|
1199
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
|
|
1200
|
+
if (this.pathOfSpecifier(fileName, specifier) === null)
|
|
1201
|
+
packages.add(specifier);
|
|
1202
|
+
}
|
|
1203
|
+
return [...packages];
|
|
1204
|
+
}
|
|
1205
|
+
declares(owner, name) {
|
|
1206
|
+
const declaring = declaringPatternOf(name, true);
|
|
1207
|
+
return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
|
|
1208
|
+
}
|
|
1209
|
+
declaresAt(path, name) {
|
|
1210
|
+
const declaring = declaringPatternOf(name, true);
|
|
1211
|
+
return this.#modulesAt(path).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
|
|
1212
|
+
}
|
|
1213
|
+
#filesIn(owner) {
|
|
1214
|
+
if (!this.#ownerFiles) {
|
|
1215
|
+
this.#ownerFiles = new Map;
|
|
1216
|
+
for (const entry of this.#source.files) {
|
|
1217
|
+
const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
|
|
1218
|
+
const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
|
|
1219
|
+
if (files)
|
|
1220
|
+
files.push(entry);
|
|
1221
|
+
else if (fileOwner !== null)
|
|
1222
|
+
this.#ownerFiles.set(fileOwner, [entry]);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return this.#ownerFiles.get(owner) ?? [];
|
|
1226
|
+
}
|
|
1227
|
+
#nearest(items, fileOf, near) {
|
|
1228
|
+
const isNear = (fileName) => {
|
|
1229
|
+
const relative = this.#source.relative(fileName);
|
|
1230
|
+
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
1231
|
+
};
|
|
1232
|
+
return items.find((item) => isNear(fileOf(item))) ?? items[0];
|
|
1233
|
+
}
|
|
1234
|
+
inOwner(fileName, owner) {
|
|
1235
|
+
const relative = this.#source.relative(fileName);
|
|
1236
|
+
return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
|
|
1237
|
+
}
|
|
1238
|
+
ownerOfSpecifier(fileName, specifier) {
|
|
1239
|
+
if (frameworkPattern.test(specifier))
|
|
1240
|
+
return frameworkOwner;
|
|
1241
|
+
const path = this.pathOfSpecifier(fileName, specifier);
|
|
1242
|
+
return path === null ? undefined : ownerOf(path) ?? undefined;
|
|
1243
|
+
}
|
|
1244
|
+
pathOfSpecifier(fileName, specifier) {
|
|
1245
|
+
const alias = specifier.match(aliasPattern);
|
|
1246
|
+
if (alias)
|
|
1247
|
+
return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
|
|
1248
|
+
if (!specifier.startsWith("."))
|
|
1249
|
+
return null;
|
|
1250
|
+
const segments = this.#source.relative(fileName).split("/").slice(0, -1);
|
|
1251
|
+
for (const segment of specifier.split("/"))
|
|
1252
|
+
if (segment === "..")
|
|
1253
|
+
segments.pop();
|
|
1254
|
+
else if (segment !== ".")
|
|
1255
|
+
segments.push(segment);
|
|
1256
|
+
return segments.join("/");
|
|
1257
|
+
}
|
|
1258
|
+
visible(target, from) {
|
|
1259
|
+
return !from || !target || target === from || target.startsWith("libs/");
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// pkgs/@akanjs/language/adaptorResolver.ts
|
|
1264
|
+
var maxModules = 64;
|
|
1265
|
+
|
|
1266
|
+
class AdaptorResolver {
|
|
1267
|
+
#source;
|
|
1268
|
+
#resolver;
|
|
1269
|
+
#sink;
|
|
1270
|
+
#adaptors = new Map;
|
|
1271
|
+
#plugs = new Map;
|
|
1272
|
+
#serviceFields = new Set;
|
|
1273
|
+
constructor(source, resolver, sink) {
|
|
1274
|
+
this.#source = source;
|
|
1275
|
+
this.#resolver = resolver;
|
|
1276
|
+
this.#sink = sink;
|
|
1277
|
+
}
|
|
1278
|
+
declare(facts) {
|
|
1279
|
+
const counts = new Map;
|
|
1280
|
+
for (const file of facts)
|
|
1281
|
+
for (const fact of file.adaptors) {
|
|
1282
|
+
const key = this.#sink.key("adaptor", file.owner, fact.name);
|
|
1283
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
1284
|
+
}
|
|
1285
|
+
const declared = facts.flatMap((file) => file.adaptors.map((fact) => {
|
|
1286
|
+
const key = this.#sink.key("adaptor", file.owner, fact.name);
|
|
1287
|
+
const id = (counts.get(key) ?? 0) > 1 ? this.#sink.key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
|
|
1288
|
+
const adaptor = { fact, owner: file.owner, fileName: file.fileName, id };
|
|
1289
|
+
this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], adaptor]);
|
|
1290
|
+
this.#nodeOf(adaptor);
|
|
1291
|
+
return { adaptor, file };
|
|
1292
|
+
}));
|
|
1293
|
+
for (const { adaptor, file } of declared)
|
|
1294
|
+
for (const plug of adaptor.fact.plugs)
|
|
1295
|
+
this.#sink.addEdge(adaptor.id, this.#idOf(plug.target, file), "plugs", plug.name, this.#sink.location(plug.span));
|
|
1296
|
+
for (const file of facts)
|
|
1297
|
+
for (const binding of file.adaptorBindings)
|
|
1298
|
+
this.#sink.addEdge(this.#idOf(binding.role, file), this.#idOf(binding.adaptor, file), "binds", file.owner, this.#sink.location(binding.span));
|
|
1299
|
+
}
|
|
1300
|
+
addService(refName, service, file) {
|
|
1301
|
+
for (const plug of service.plugs) {
|
|
1302
|
+
const key = `${refName}
|
|
1303
|
+
${plug.name}`;
|
|
1304
|
+
this.#plugs.set(key, [
|
|
1305
|
+
...this.#plugs.get(key) ?? [],
|
|
1306
|
+
{ owner: file.owner, target: this.#idOf(plug.target, file) }
|
|
1307
|
+
]);
|
|
1308
|
+
}
|
|
1309
|
+
for (const field of service.fields)
|
|
1310
|
+
this.#serviceFields.add(`${file.owner ?? ""}
|
|
1311
|
+
${refName}
|
|
1312
|
+
${field}`);
|
|
1313
|
+
}
|
|
1314
|
+
pluggedOf(owner, module, field) {
|
|
1315
|
+
const plugs = this.#plugs.get(`${module}
|
|
1316
|
+
${field}`) ?? [];
|
|
1317
|
+
const own = plugs.filter((plug) => plug.owner === owner);
|
|
1318
|
+
if (!own.length && this.#serviceFields.has(`${owner ?? ""}
|
|
1319
|
+
${module}
|
|
1320
|
+
${field}`))
|
|
1321
|
+
return [];
|
|
1322
|
+
const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
|
|
1323
|
+
return [...new Set(inherited.map(({ target }) => target))];
|
|
1324
|
+
}
|
|
1325
|
+
#idOf(local, file) {
|
|
1326
|
+
const [head, member] = local.split(".");
|
|
1327
|
+
const imported = file.imports.find((item) => item.name === head);
|
|
1328
|
+
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1329
|
+
const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
|
|
1330
|
+
if (imported && path === null && !frameworkPattern.test(imported.from))
|
|
1331
|
+
return this.#packageAdaptorOf(name, imported.from);
|
|
1332
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1333
|
+
const declared = this.#adaptors.get(name) ?? [];
|
|
1334
|
+
const holder = from === undefined ? file.owner : from;
|
|
1335
|
+
const own = this.#adaptorIn(name, declared, holder, path, file.fileName);
|
|
1336
|
+
if (own && "from" in own)
|
|
1337
|
+
return this.#packageAdaptorOf(own.name, own.from);
|
|
1338
|
+
const found = own === undefined && from !== frameworkOwner ? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, holder)) : own;
|
|
1339
|
+
if (found)
|
|
1340
|
+
return found.id;
|
|
1341
|
+
const reexported = own === undefined && from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
|
|
1342
|
+
if (reexported)
|
|
1343
|
+
return this.#packageAdaptorOf(reexported.name, reexported.from);
|
|
1344
|
+
const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
|
|
1345
|
+
const detail = from === frameworkOwner ? [frameworkOwner] : [];
|
|
1346
|
+
return this.#sink.addNode(this.#sink.key("adaptor", owner, name), "adaptor", owner, name, null, detail).id;
|
|
1347
|
+
}
|
|
1348
|
+
#adaptorIn(name, declared, owner, path, fileName) {
|
|
1349
|
+
const own = declared.filter((adaptor) => adaptor.owner === owner);
|
|
1350
|
+
const [first] = own;
|
|
1351
|
+
if (path === null)
|
|
1352
|
+
return own.find((adaptor) => adaptor.fileName === fileName) ?? first;
|
|
1353
|
+
if (own.length === 1)
|
|
1354
|
+
return first;
|
|
1355
|
+
if (!this.#resolver.hasModule(path))
|
|
1356
|
+
return own.find((adaptor) => this.#source.relative(adaptor.fileName).startsWith(`${path}/`)) ?? first;
|
|
1357
|
+
const exported = this.#exportedAt(name, path, new Set).filter((item) => first || ("fact" in item));
|
|
1358
|
+
const [only] = exported;
|
|
1359
|
+
if (only)
|
|
1360
|
+
return exported.length === 1 ? only : null;
|
|
1361
|
+
return first ? null : undefined;
|
|
1362
|
+
}
|
|
1363
|
+
#exportedAt(name, path, seen) {
|
|
1364
|
+
const key = `${path}
|
|
1365
|
+
${name}`;
|
|
1366
|
+
if (seen.has(key) || seen.size >= maxModules)
|
|
1367
|
+
return [];
|
|
1368
|
+
seen.add(key);
|
|
1369
|
+
const here = (this.#adaptors.get(name) ?? []).filter((adaptor) => {
|
|
1370
|
+
const module = this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
|
|
1371
|
+
return module === path || module === `${path}/index`;
|
|
1372
|
+
});
|
|
1373
|
+
if (here.length)
|
|
1374
|
+
return here;
|
|
1375
|
+
const { named, stars } = this.#resolver.exportsAt(path, name);
|
|
1376
|
+
if (named?.from && this.#resolver.pathOfSpecifier(named.fileName, named.from) === null)
|
|
1377
|
+
return [{ name: named.name === "default" ? name : named.name, from: named.from }];
|
|
1378
|
+
if (named) {
|
|
1379
|
+
const target = named.from === null ? path : this.#resolver.pathOfSpecifier(named.fileName, named.from);
|
|
1380
|
+
return target === null ? [] : this.#exportedAt(named.name, target, seen);
|
|
1381
|
+
}
|
|
1382
|
+
return [...new Set(stars.flatMap((star) => this.#exportedAt(name, star, seen)))];
|
|
1383
|
+
}
|
|
1384
|
+
#packageAdaptorOf(name, specifier) {
|
|
1385
|
+
if (frameworkPattern.test(specifier))
|
|
1386
|
+
return this.#sink.addNode(this.#sink.key("adaptor", null, name), "adaptor", null, name, null, [frameworkOwner]).id;
|
|
1387
|
+
const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
|
1388
|
+
return this.#sink.addNode(this.#sink.key("adaptor", pkg, name), "adaptor", null, name, null, [pkg]).id;
|
|
1389
|
+
}
|
|
1390
|
+
#packageShimOf(owner, name, near) {
|
|
1391
|
+
const reexport = this.#resolver.reexportOf(owner, name, near);
|
|
1392
|
+
const [shim] = reexport?.site.imports ?? [];
|
|
1393
|
+
if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
|
|
1394
|
+
return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
|
|
1395
|
+
if (reexport || !near)
|
|
1396
|
+
return null;
|
|
1397
|
+
const { packages, paths } = this.#resolver.hasModule(near) ? this.#resolver.starExportsOf(owner, near) : { packages: this.#resolver.packagesUnder(owner, near), paths: [] };
|
|
1398
|
+
const [only] = packages;
|
|
1399
|
+
const isDeclared = this.#resolver.declares(owner, name) || paths.some((path) => this.#resolver.declaresAt(path, name));
|
|
1400
|
+
return packages.length === 1 && only && !isDeclared ? { name, from: only } : null;
|
|
1401
|
+
}
|
|
1402
|
+
#nodeOf({ fact, owner, fileName, id }) {
|
|
1403
|
+
const detail = fact.refName ? [fact.refName] : [];
|
|
1404
|
+
const node = this.#sink.addNode(id, "adaptor", owner, fact.name, fact.span, detail);
|
|
1405
|
+
if (fact.doc && !node.doc)
|
|
1406
|
+
node.doc = fact.doc;
|
|
1407
|
+
this.#sink.digest(node, this.#source.readText(fileName), fact.extent);
|
|
1408
|
+
return node.id;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
581
1411
|
|
|
582
1412
|
// pkgs/akanjs/common/trimSlashes.ts
|
|
583
1413
|
var trimTrailingSlashes = (value) => {
|
|
@@ -611,7 +1441,8 @@ var ROOT_LAYOUT_ROUTE_EXPORTS = new Set([
|
|
|
611
1441
|
"wsConnect",
|
|
612
1442
|
"layoutStyle",
|
|
613
1443
|
"viewport",
|
|
614
|
-
"McpAppRoot"
|
|
1444
|
+
"McpAppRoot",
|
|
1445
|
+
"LevelBoundary"
|
|
615
1446
|
]);
|
|
616
1447
|
var RESERVED_ROUTE_CONFIG_EXPORTS = new Set([...ROOT_LAYOUT_ROUTE_EXPORTS].filter((name) => name !== "default" && !/^[A-Z]/.test(name)));
|
|
617
1448
|
function parseRouteModuleKey(key) {
|
|
@@ -726,24 +1557,23 @@ rootLayout
|
|
|
726
1557
|
}
|
|
727
1558
|
declarationIn(source, name) {
|
|
728
1559
|
const ts = this.#ts;
|
|
1560
|
+
const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
|
|
1561
|
+
const [first] = overloads;
|
|
1562
|
+
const last = overloads.at(-1);
|
|
1563
|
+
if (first?.name && last)
|
|
1564
|
+
return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
|
|
729
1565
|
for (const statement of source.statements) {
|
|
730
|
-
if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
|
|
731
|
-
return { statement, declared: statement.name, initializer: null };
|
|
732
1566
|
if (!ts.isVariableStatement(statement))
|
|
733
1567
|
continue;
|
|
734
1568
|
const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
|
|
735
1569
|
if (declaration)
|
|
736
|
-
return { statement, declared: declaration.name,
|
|
1570
|
+
return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
|
|
737
1571
|
}
|
|
738
1572
|
return null;
|
|
739
1573
|
}
|
|
740
1574
|
#guardOf(source, exported) {
|
|
741
1575
|
const ts = this.#ts;
|
|
742
1576
|
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
1577
|
while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
748
1578
|
const { name, expression } = current.expression;
|
|
749
1579
|
const [stage] = current.arguments;
|
|
@@ -755,9 +1585,20 @@ rootLayout
|
|
|
755
1585
|
}
|
|
756
1586
|
#callsIn(source, stage) {
|
|
757
1587
|
const ts = this.#ts;
|
|
758
|
-
const
|
|
1588
|
+
const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
|
|
1589
|
+
const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
|
|
1590
|
+
const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
|
|
759
1591
|
const calls = new Map;
|
|
760
|
-
const callOf = (
|
|
1592
|
+
const callOf = (callee, shadowed) => {
|
|
1593
|
+
if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
|
|
1594
|
+
const name = `${callee.expression.text}.${callee.name.text}`;
|
|
1595
|
+
if (!calls.has(name))
|
|
1596
|
+
calls.set(name, { name, local: null });
|
|
1597
|
+
return true;
|
|
1598
|
+
}
|
|
1599
|
+
if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
|
|
1600
|
+
return false;
|
|
1601
|
+
const name = callee.text;
|
|
761
1602
|
if (calls.has(name))
|
|
762
1603
|
return true;
|
|
763
1604
|
const declared = this.declarationIn(source, name);
|
|
@@ -767,23 +1608,71 @@ rootLayout
|
|
|
767
1608
|
name,
|
|
768
1609
|
local: declared && {
|
|
769
1610
|
span: this.#reader.spanOf(source, declared.declared),
|
|
770
|
-
extent:
|
|
1611
|
+
extent: declared.extent,
|
|
771
1612
|
doc: this.#reader.docOf(source, declared.statement)
|
|
772
1613
|
}
|
|
773
1614
|
});
|
|
774
1615
|
return true;
|
|
775
1616
|
};
|
|
776
|
-
const visit = (node) => {
|
|
777
|
-
|
|
1617
|
+
const visit = (node, outer) => {
|
|
1618
|
+
const shadowed = this.#scopeOf(node, outer);
|
|
1619
|
+
if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
|
|
778
1620
|
return;
|
|
779
|
-
ts.forEachChild(node, visit);
|
|
1621
|
+
ts.forEachChild(node, (child) => visit(child, shadowed));
|
|
780
1622
|
};
|
|
781
1623
|
if (ts.isIdentifier(stage))
|
|
782
|
-
callOf(stage
|
|
1624
|
+
callOf(stage, new Set);
|
|
783
1625
|
else
|
|
784
|
-
visit(stage);
|
|
1626
|
+
visit(stage, new Set);
|
|
785
1627
|
return [...calls.values()];
|
|
786
1628
|
}
|
|
1629
|
+
#scopeOf(node, outer) {
|
|
1630
|
+
const ts = this.#ts;
|
|
1631
|
+
const names = [];
|
|
1632
|
+
const bind = (name) => {
|
|
1633
|
+
if (ts.isIdentifier(name))
|
|
1634
|
+
names.push(name.text);
|
|
1635
|
+
else
|
|
1636
|
+
for (const element of name.elements)
|
|
1637
|
+
if (!ts.isOmittedExpression(element))
|
|
1638
|
+
bind(element.name);
|
|
1639
|
+
};
|
|
1640
|
+
const declare = (statement) => {
|
|
1641
|
+
if (ts.isVariableStatement(statement))
|
|
1642
|
+
for (const declaration of statement.declarationList.declarations)
|
|
1643
|
+
bind(declaration.name);
|
|
1644
|
+
else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
|
|
1645
|
+
names.push(statement.name.text);
|
|
1646
|
+
};
|
|
1647
|
+
const hoist = (child) => {
|
|
1648
|
+
if (ts.isFunctionLike(child) || ts.isClassLike(child))
|
|
1649
|
+
return;
|
|
1650
|
+
if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
|
|
1651
|
+
for (const declaration of child.declarations)
|
|
1652
|
+
bind(declaration.name);
|
|
1653
|
+
ts.forEachChild(child, hoist);
|
|
1654
|
+
};
|
|
1655
|
+
if (ts.isFunctionLike(node)) {
|
|
1656
|
+
for (const parameter of node.parameters)
|
|
1657
|
+
bind(parameter.name);
|
|
1658
|
+
if (ts.isFunctionExpression(node) && node.name)
|
|
1659
|
+
names.push(node.name.text);
|
|
1660
|
+
if ("body" in node && node.body)
|
|
1661
|
+
ts.forEachChild(node.body, hoist);
|
|
1662
|
+
} else if (ts.isClassExpression(node) && node.name)
|
|
1663
|
+
names.push(node.name.text);
|
|
1664
|
+
else if (ts.isBlock(node))
|
|
1665
|
+
node.statements.forEach(declare);
|
|
1666
|
+
else if (ts.isCaseBlock(node))
|
|
1667
|
+
for (const clause of node.clauses)
|
|
1668
|
+
clause.statements.forEach(declare);
|
|
1669
|
+
else if (ts.isCatchClause(node) && node.variableDeclaration)
|
|
1670
|
+
bind(node.variableDeclaration.name);
|
|
1671
|
+
else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
|
|
1672
|
+
for (const declaration of node.initializer.declarations)
|
|
1673
|
+
bind(declaration.name);
|
|
1674
|
+
return names.length ? new Set([...outer, ...names]) : outer;
|
|
1675
|
+
}
|
|
787
1676
|
}
|
|
788
1677
|
|
|
789
1678
|
// pkgs/@akanjs/language/akanGraph.ts
|
|
@@ -799,10 +1688,6 @@ var builtinGuards = {
|
|
|
799
1688
|
Public: { scope: "account", doc: "Passes every caller, signed in or not." },
|
|
800
1689
|
None: { scope: "account", doc: "Refuses every caller." }
|
|
801
1690
|
};
|
|
802
|
-
var frameworkOwner = "akanjs";
|
|
803
|
-
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
804
|
-
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
805
|
-
var maxMapDepth = 4;
|
|
806
1691
|
|
|
807
1692
|
class GraphBuilder {
|
|
808
1693
|
#source;
|
|
@@ -810,16 +1695,21 @@ class GraphBuilder {
|
|
|
810
1695
|
#edges = new Map;
|
|
811
1696
|
#contexts = new Map;
|
|
812
1697
|
#members = new Map;
|
|
813
|
-
#guards = new Map;
|
|
814
|
-
#mapEntries = new Map;
|
|
815
1698
|
#routeFiles = new Map;
|
|
816
|
-
#declarations = new Map;
|
|
817
1699
|
#routes;
|
|
818
|
-
#
|
|
819
|
-
#
|
|
1700
|
+
#resolver;
|
|
1701
|
+
#adaptors;
|
|
820
1702
|
constructor(source) {
|
|
821
1703
|
this.#source = source;
|
|
822
1704
|
this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
|
|
1705
|
+
this.#resolver = new GuardResolver(source, this.#routes);
|
|
1706
|
+
this.#adaptors = new AdaptorResolver(source, this.#resolver, {
|
|
1707
|
+
key: (kind, owner, scope) => this.#key(kind, owner, scope),
|
|
1708
|
+
addNode: (id, kind, owner, name, span, detail) => this.#addNode(id, kind, owner, null, name, span, detail),
|
|
1709
|
+
addEdge: (from, to, kind, label, at) => this.#addEdge(from, to, kind, label, at),
|
|
1710
|
+
digest: (node, text, extent) => this.#digest(node, text, extent),
|
|
1711
|
+
location: (span) => this.#location(span)
|
|
1712
|
+
});
|
|
823
1713
|
}
|
|
824
1714
|
build() {
|
|
825
1715
|
const { facts } = this.#source;
|
|
@@ -831,17 +1721,9 @@ class GraphBuilder {
|
|
|
831
1721
|
...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
|
|
832
1722
|
];
|
|
833
1723
|
const typed = [];
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
|
|
838
|
-
this.#adaptorNodeOf(declared);
|
|
839
|
-
}
|
|
840
|
-
for (const file of facts)
|
|
841
|
-
for (const fact of file.adaptors)
|
|
842
|
-
for (const plug of fact.plugs)
|
|
843
|
-
this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
|
|
844
|
-
this.#declareGuards(facts);
|
|
1724
|
+
this.#adaptors.declare(facts);
|
|
1725
|
+
for (const guard of this.#resolver.declare(facts))
|
|
1726
|
+
this.#guardNodeOf(guard);
|
|
845
1727
|
for (const file of facts) {
|
|
846
1728
|
const { owner } = file;
|
|
847
1729
|
const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
|
|
@@ -872,10 +1754,7 @@ class GraphBuilder {
|
|
|
872
1754
|
}
|
|
873
1755
|
for (const method of service.methods)
|
|
874
1756
|
this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
|
|
875
|
-
|
|
876
|
-
this.#plugs.set(`${owner ?? ""}
|
|
877
|
-
${refName}
|
|
878
|
-
${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
1757
|
+
this.#adaptors.addService(refName, service, file);
|
|
879
1758
|
}
|
|
880
1759
|
for (const filter of file.filters) {
|
|
881
1760
|
if (!filter.name.endsWith("Filter"))
|
|
@@ -885,31 +1764,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
|
885
1764
|
for (const query of filter.queries)
|
|
886
1765
|
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
1766
|
}
|
|
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
|
-
}
|
|
1767
|
+
this.#addGuarded(file);
|
|
913
1768
|
for (const internal of file.internals)
|
|
914
1769
|
for (const key of internal.keys)
|
|
915
1770
|
if (internal.model && key.type !== "resolveField")
|
|
@@ -925,7 +1780,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
|
925
1780
|
types.set(node.label, [...types.get(node.label) ?? [], node]);
|
|
926
1781
|
for (const node of typed)
|
|
927
1782
|
for (const field of node.fields ?? []) {
|
|
928
|
-
const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
|
|
1783
|
+
const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
|
|
929
1784
|
field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
|
|
930
1785
|
if (field.ref)
|
|
931
1786
|
this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
|
|
@@ -1007,61 +1862,52 @@ ${label ?? ""}`;
|
|
|
1007
1862
|
else
|
|
1008
1863
|
this.#edges.set(key, { from, to, kind, label, count: 1, at });
|
|
1009
1864
|
}
|
|
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);
|
|
1865
|
+
#addGuarded(file) {
|
|
1866
|
+
const { owner } = file;
|
|
1867
|
+
for (const slice of file.slices) {
|
|
1868
|
+
const refName = slice.model;
|
|
1869
|
+
if (!refName)
|
|
1870
|
+
continue;
|
|
1871
|
+
const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
|
|
1872
|
+
for (const guard of slice.guards)
|
|
1873
|
+
this.#guard(fallback, guard, file);
|
|
1874
|
+
for (const key of slice.keys) {
|
|
1875
|
+
const detail = [
|
|
1876
|
+
...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
|
|
1877
|
+
...key.live ? ["live"] : []
|
|
1878
|
+
];
|
|
1879
|
+
const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
1880
|
+
for (const guard of key.guards)
|
|
1881
|
+
this.#guard(node, guard, file);
|
|
1042
1882
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1883
|
+
}
|
|
1884
|
+
for (const endpoint of file.endpoints) {
|
|
1885
|
+
const refName = endpoint.model;
|
|
1886
|
+
if (!refName)
|
|
1887
|
+
continue;
|
|
1888
|
+
for (const key of endpoint.keys) {
|
|
1889
|
+
const detail = [
|
|
1890
|
+
...key.verb ? [key.verb] : [],
|
|
1891
|
+
...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
|
|
1892
|
+
];
|
|
1893
|
+
const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
1894
|
+
for (const guard of key.guards)
|
|
1895
|
+
this.#guard(node, guard, file);
|
|
1052
1896
|
}
|
|
1053
1897
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1898
|
+
}
|
|
1899
|
+
#guard(node, guard, file) {
|
|
1900
|
+
const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
|
|
1901
|
+
if (!local || local === "?")
|
|
1902
|
+
return;
|
|
1903
|
+
const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
|
|
1904
|
+
if (id)
|
|
1905
|
+
this.#addEdge(node.id, id, "guard", role, node.at);
|
|
1062
1906
|
}
|
|
1063
1907
|
#guardIdOf(local, site) {
|
|
1064
|
-
const found = this.#
|
|
1908
|
+
const found = this.#resolver.find(local, site);
|
|
1909
|
+
if ("external" in found)
|
|
1910
|
+
return null;
|
|
1065
1911
|
if ("guard" in found)
|
|
1066
1912
|
return this.#guardNodeOf(found.guard);
|
|
1067
1913
|
const { name, from } = found;
|
|
@@ -1074,99 +1920,18 @@ ${label ?? ""}`;
|
|
|
1074
1920
|
node.doc = builtin.doc;
|
|
1075
1921
|
return node.id;
|
|
1076
1922
|
}
|
|
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;
|
|
1923
|
+
#madeInPlaceIdOf(name, file) {
|
|
1924
|
+
const made = this.#resolver.madeInPlace(file.fileName, name);
|
|
1925
|
+
if (made)
|
|
1926
|
+
return this.#guardNodeOf(made);
|
|
1927
|
+
const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
|
|
1928
|
+
const imported = file.imports.find((item) => item.name === head);
|
|
1929
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
|
|
1930
|
+
if (from === undefined || from === frameworkOwner)
|
|
1931
|
+
return null;
|
|
1932
|
+
const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
|
|
1933
|
+
const label = guardLabelOf(name, file.guardLabels);
|
|
1934
|
+
return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
|
|
1170
1935
|
}
|
|
1171
1936
|
#addRoutes() {
|
|
1172
1937
|
const routes = [];
|
|
@@ -1219,22 +1984,23 @@ ${dir}`, node);
|
|
|
1219
1984
|
}
|
|
1220
1985
|
#routeGuardIdOf({ name: local, local: declared }, fact, text) {
|
|
1221
1986
|
if (declared) {
|
|
1222
|
-
const
|
|
1223
|
-
const id = this.#key("guard", fact.owner, `${file}#${local}`);
|
|
1987
|
+
const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
|
|
1224
1988
|
return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
|
|
1225
1989
|
}
|
|
1226
|
-
const
|
|
1227
|
-
|
|
1990
|
+
const [head = local, member] = local.split(".");
|
|
1991
|
+
const imported = fact.imports.find((item) => item.name === head);
|
|
1992
|
+
if (!imported || member && imported.imported !== "*")
|
|
1228
1993
|
return null;
|
|
1229
|
-
const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
|
|
1230
|
-
const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
|
|
1994
|
+
const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
|
|
1995
|
+
const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
|
|
1231
1996
|
if (!from || from === frameworkOwner)
|
|
1232
1997
|
return null;
|
|
1233
|
-
const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
|
|
1998
|
+
const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
|
|
1234
1999
|
if (!found)
|
|
1235
2000
|
return null;
|
|
1236
2001
|
const { reader } = this.#source;
|
|
1237
|
-
|
|
2002
|
+
const owner = this.#source.ownerOf(found.source.fileName) ?? from;
|
|
2003
|
+
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
2004
|
}
|
|
1239
2005
|
#routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
|
|
1240
2006
|
const isNew = !this.#nodes.has(id);
|
|
@@ -1244,60 +2010,22 @@ ${dir}`, node);
|
|
|
1244
2010
|
this.#digest(node, text, extent);
|
|
1245
2011
|
return node.id;
|
|
1246
2012
|
}
|
|
1247
|
-
#
|
|
1248
|
-
const
|
|
1249
|
-
const
|
|
1250
|
-
const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1251
|
-
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1252
|
-
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)));
|
|
1254
|
-
if (found)
|
|
1255
|
-
return this.#adaptorNodeOf(found);
|
|
1256
|
-
const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
|
|
1257
|
-
const detail = from === frameworkOwner ? [frameworkOwner] : [];
|
|
1258
|
-
return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
|
|
1259
|
-
}
|
|
1260
|
-
#adaptorNodeOf({ fact, owner, fileName }) {
|
|
1261
|
-
const id = this.#key("adaptor", owner, fact.name);
|
|
1262
|
-
const detail = fact.refName ? [fact.refName] : [];
|
|
1263
|
-
const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
|
|
1264
|
-
if (fact.doc && !node.doc)
|
|
1265
|
-
node.doc = fact.doc;
|
|
1266
|
-
this.#digest(node, this.#source.readText(fileName), fact.extent);
|
|
1267
|
-
return node.id;
|
|
1268
|
-
}
|
|
1269
|
-
#guardNodeOf({ name, owner, span, scope, doc, parts }) {
|
|
1270
|
-
const node = this.#addNode(this.#key("guard", owner, name), "guard", owner, null, name, span, scope ? [scope] : []);
|
|
2013
|
+
#guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
|
|
2014
|
+
const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
|
|
2015
|
+
const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
|
|
1271
2016
|
if (doc && !node.doc)
|
|
1272
2017
|
node.doc = doc;
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
2018
|
+
if (node.digest)
|
|
2019
|
+
return node.id;
|
|
2020
|
+
const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
|
|
2021
|
+
if (!digest)
|
|
2022
|
+
return node.id;
|
|
2023
|
+
node.digest = digest;
|
|
2024
|
+
const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
|
|
2025
|
+
if (sources.length)
|
|
2026
|
+
node.sources = sources;
|
|
1279
2027
|
return node.id;
|
|
1280
2028
|
}
|
|
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
2029
|
#moduleDirOf(fileName) {
|
|
1302
2030
|
const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
|
|
1303
2031
|
return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
|
|
@@ -1333,20 +2061,13 @@ ${dir}`, node);
|
|
|
1333
2061
|
visit(constant);
|
|
1334
2062
|
return [...fields.values()];
|
|
1335
2063
|
}
|
|
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
2064
|
#targetNodeOf({ family, owner, refName, sourceKey }) {
|
|
1344
2065
|
const isDefault = /^[A-Z]/.test(sourceKey);
|
|
1345
2066
|
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
2067
|
return id && this.#nodes.has(id) ? id : null;
|
|
1347
2068
|
}
|
|
1348
2069
|
#membersNamed(kind, name, owner, refName) {
|
|
1349
|
-
const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
|
|
2070
|
+
const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
|
|
1350
2071
|
const own = nodes.filter((node) => node.owner === owner);
|
|
1351
2072
|
return (own.length ? own : nodes).map((node) => node.id);
|
|
1352
2073
|
}
|
|
@@ -1391,10 +2112,8 @@ st
|
|
|
1391
2112
|
visit(source);
|
|
1392
2113
|
}
|
|
1393
2114
|
#link(fileName, source, identifier, chain, context, owner) {
|
|
1394
|
-
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#
|
|
1395
|
-
|
|
1396
|
-
${chain[1]}`) : undefined;
|
|
1397
|
-
const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
2115
|
+
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#adaptors.pluggedOf(context.owner, context.module, chain[1]) : [];
|
|
2116
|
+
const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
1398
2117
|
const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
|
|
1399
2118
|
if (!targets.length)
|
|
1400
2119
|
return;
|
|
@@ -1700,6 +2419,10 @@ var endpointNames = (key) => {
|
|
|
1700
2419
|
};
|
|
1701
2420
|
|
|
1702
2421
|
// pkgs/@akanjs/language/nameTable.ts
|
|
2422
|
+
var guardDetailOf = (facts, guards) => {
|
|
2423
|
+
const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
|
|
2424
|
+
return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
|
|
2425
|
+
};
|
|
1703
2426
|
var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
|
|
1704
2427
|
|
|
1705
2428
|
class NameTable {
|
|
@@ -1799,7 +2522,7 @@ class NameTable {
|
|
|
1799
2522
|
role: "sig",
|
|
1800
2523
|
gate: "sig",
|
|
1801
2524
|
label: sliceClass.name,
|
|
1802
|
-
detail:
|
|
2525
|
+
detail: guardDetailOf(facts, sliceClass.guards)
|
|
1803
2526
|
});
|
|
1804
2527
|
if (modelDoc)
|
|
1805
2528
|
this.#add({
|
|
@@ -1864,7 +2587,7 @@ class NameTable {
|
|
|
1864
2587
|
sourceKey: slice.name,
|
|
1865
2588
|
span: slice.span,
|
|
1866
2589
|
label: `slice ${refName} (default)`,
|
|
1867
|
-
detail:
|
|
2590
|
+
detail: guardDetailOf(facts, slice.guards)
|
|
1868
2591
|
};
|
|
1869
2592
|
this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
|
|
1870
2593
|
for (const key of slice.keys)
|
|
@@ -1874,7 +2597,7 @@ class NameTable {
|
|
|
1874
2597
|
sourceKey: key.name,
|
|
1875
2598
|
span: key.span,
|
|
1876
2599
|
label: `slice ${key.name}`,
|
|
1877
|
-
detail: [...
|
|
2600
|
+
detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
|
|
1878
2601
|
}, refName, key.name, modelNameSet, slice.name);
|
|
1879
2602
|
}
|
|
1880
2603
|
if (filterClass) {
|
|
@@ -1931,7 +2654,7 @@ class NameTable {
|
|
|
1931
2654
|
role: key.verb === "pubsub" ? "subscribe" : "listen",
|
|
1932
2655
|
gate: null,
|
|
1933
2656
|
label: `${key.verb} ${key.name}`,
|
|
1934
|
-
detail:
|
|
2657
|
+
detail: guardDetailOf(facts, key.guards)
|
|
1935
2658
|
});
|
|
1936
2659
|
}
|
|
1937
2660
|
}
|
|
@@ -1988,15 +2711,13 @@ var sourcePattern = /\.(constant|document|service|signal|store)\.ts$/;
|
|
|
1988
2711
|
var dictionaryPattern = /\.dictionary\.ts$/;
|
|
1989
2712
|
var srvkitPattern = /\/srvkit\//;
|
|
1990
2713
|
var wordPattern = /[A-Za-z_$][\w$]*/g;
|
|
1991
|
-
var
|
|
1992
|
-
var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
|
|
2714
|
+
var identifierPattern2 = /^[A-Za-z_$][\w$]*$/;
|
|
1993
2715
|
var stateReceivers = new Set(["use", "sel", "get", "do"]);
|
|
1994
2716
|
var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
|
|
1995
2717
|
var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
|
|
1996
2718
|
var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
|
|
1997
2719
|
var parseCacheSize = 64;
|
|
1998
2720
|
var pathOf = (fileName) => fileName.replace(/\\/g, "/");
|
|
1999
|
-
var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
|
|
2000
2721
|
var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
|
|
2001
2722
|
|
|
2002
2723
|
class AkanIndex {
|
|
@@ -2099,7 +2820,7 @@ ${[...new Set(text.match(wordPattern))].join(`
|
|
|
2099
2820
|
}
|
|
2100
2821
|
renameAt(givenName, offset, newKey, readText) {
|
|
2101
2822
|
const sources = this.#sourceEntriesAt(pathOf(givenName), offset).filter((entry) => entry.renamed);
|
|
2102
|
-
if (!sources.length || !
|
|
2823
|
+
if (!sources.length || !identifierPattern2.test(newKey))
|
|
2103
2824
|
return null;
|
|
2104
2825
|
return [
|
|
2105
2826
|
{ span: sources[0].span, newText: newKey },
|
|
@@ -2127,6 +2848,8 @@ canPass
|
|
|
2127
2848
|
adapt
|
|
2128
2849
|
`) || words.includes(`
|
|
2129
2850
|
dangerouslyAdapt
|
|
2851
|
+
`) || words.includes(`
|
|
2852
|
+
applyAdaptor
|
|
2130
2853
|
`);
|
|
2131
2854
|
}
|
|
2132
2855
|
static #sourceKeyOf({ family, sourceKey, label, detail, span }) {
|
|
@@ -2807,6 +3530,11 @@ ${union.name}`)));
|
|
|
2807
3530
|
const field = this.#memberOf(this.#classOf(receiver), receiver.name.text);
|
|
2808
3531
|
return field && ts.isPropertyDeclaration(field) ? this.#namesIn(field.type) : new Set;
|
|
2809
3532
|
}
|
|
3533
|
+
if (ts.isCallExpression(receiver) && ts.isPropertyAccessExpression(receiver.expression) && receiver.expression.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
3534
|
+
const field = this.#memberOf(this.#classOf(receiver), receiver.expression.name.text);
|
|
3535
|
+
const type = field && ts.isPropertyDeclaration(field) ? field.type : undefined;
|
|
3536
|
+
return type && ts.isFunctionTypeNode(type) ? this.#namesIn(type.type) : new Set;
|
|
3537
|
+
}
|
|
2810
3538
|
return this.#declaredTypeNames(receiver) ?? new Set;
|
|
2811
3539
|
}
|
|
2812
3540
|
#memberName(name) {
|