@akanjs/language 3.2.0-rc.1 → 3.2.0-rc.11
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.cjs
CHANGED
|
@@ -60,7 +60,8 @@ var isEdited = (before, after) => before.digest !== undefined && after.digest !=
|
|
|
60
60
|
var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
61
61
|
|
|
62
62
|
class AkanGraphDiff {
|
|
63
|
-
static between(
|
|
63
|
+
static between(given, head) {
|
|
64
|
+
const base = AkanGraphDiff.#renamed(given, head);
|
|
64
65
|
const baseNodes = new Map(base.nodes.map((node) => [node.id, node]));
|
|
65
66
|
const headNodes = new Map(head.nodes.map((node) => [node.id, node]));
|
|
66
67
|
const marked = new Map;
|
|
@@ -79,6 +80,9 @@ class AkanGraphDiff {
|
|
|
79
80
|
...head.edges.flatMap((edge) => !baseKeys.has(edgeKeyOf(edge)) ? [{ ...edge, change: "added" }] : marked.has(edge.from) || marked.has(edge.to) ? [edge] : []),
|
|
80
81
|
...base.edges.filter((edge) => !headKeys.has(edgeKeyOf(edge))).map((edge) => ({ ...edge, change: "removed" }))
|
|
81
82
|
];
|
|
83
|
+
const shown = new Set([...marked.keys(), ...edges.flatMap((edge) => [edge.from, edge.to])]);
|
|
84
|
+
const listed = new Set(edges.map(edgeKeyOf));
|
|
85
|
+
edges.push(...head.edges.filter((edge) => edge.kind === "binds" && shown.has(edge.from) && !listed.has(edgeKeyOf(edge))));
|
|
82
86
|
const context = edges.flatMap((edge) => [edge.from, edge.to]).filter((id) => !marked.has(id)).flatMap((id) => {
|
|
83
87
|
const node = headNodes.get(id) ?? baseNodes.get(id);
|
|
84
88
|
return node ? [node] : [];
|
|
@@ -90,6 +94,40 @@ class AkanGraphDiff {
|
|
|
90
94
|
summary[node.change] += 1;
|
|
91
95
|
return { version: 1, nodes, edges, summary };
|
|
92
96
|
}
|
|
97
|
+
static #renamed(base, head) {
|
|
98
|
+
const baseIds = new Set(base.nodes.map((node) => node.id));
|
|
99
|
+
const headIds = new Set(head.nodes.map((node) => node.id));
|
|
100
|
+
const pairKeyOf = ({ kind, owner, name, at }) => at ? `${kind}
|
|
101
|
+
${owner ?? ""}
|
|
102
|
+
${name}
|
|
103
|
+
${at.file}` : null;
|
|
104
|
+
const shortIdOf = ({ kind, owner, name }) => `${kind}:${owner ?? ""}:${name}`;
|
|
105
|
+
const isFileKeyed = (node) => node.id !== shortIdOf(node) && node.id.startsWith(`${node.kind}:${node.owner ?? ""}:`) && node.id.endsWith(`#${node.name}`);
|
|
106
|
+
const fromBase = new Map(base.nodes.flatMap((node) => {
|
|
107
|
+
const key = headIds.has(node.id) ? null : pairKeyOf(node);
|
|
108
|
+
return key ? [[key, node]] : [];
|
|
109
|
+
}));
|
|
110
|
+
const renames = new Map;
|
|
111
|
+
for (const node of head.nodes) {
|
|
112
|
+
if (baseIds.has(node.id))
|
|
113
|
+
continue;
|
|
114
|
+
const key = pairKeyOf(node);
|
|
115
|
+
const before = key ? fromBase.get(key) : undefined;
|
|
116
|
+
if (!before)
|
|
117
|
+
continue;
|
|
118
|
+
const isPair = isFileKeyed(node) ? before.id === shortIdOf(node) : isFileKeyed(before) && node.id === shortIdOf(before);
|
|
119
|
+
if (isPair)
|
|
120
|
+
renames.set(before.id, node.id);
|
|
121
|
+
}
|
|
122
|
+
if (!renames.size)
|
|
123
|
+
return base;
|
|
124
|
+
const rename = (id) => renames.get(id) ?? id;
|
|
125
|
+
return {
|
|
126
|
+
...base,
|
|
127
|
+
nodes: base.nodes.map((node) => renames.has(node.id) ? { ...node, id: rename(node.id) } : node),
|
|
128
|
+
edges: base.edges.map((edge) => ({ ...edge, from: rename(edge.from), to: rename(edge.to) }))
|
|
129
|
+
};
|
|
130
|
+
}
|
|
93
131
|
static #mark(node, change) {
|
|
94
132
|
return node.fields ? { ...node, change, fields: node.fields.map((field) => ({ ...field, change })) } : { ...node, change };
|
|
95
133
|
}
|
|
@@ -123,6 +161,261 @@ class AkanGraphDiff {
|
|
|
123
161
|
return node.fields ? { ...node, fields: node.fields.map(({ digest: _field, ...field }) => field) } : node;
|
|
124
162
|
}
|
|
125
163
|
}
|
|
164
|
+
// pkgs/@akanjs/language/escapeRegExp.ts
|
|
165
|
+
var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
|
|
166
|
+
var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
|
|
167
|
+
|
|
168
|
+
// pkgs/@akanjs/language/guardFacts.ts
|
|
169
|
+
var guardLabelOf = (guard, labels) => {
|
|
170
|
+
const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
|
|
171
|
+
const label = labels[local];
|
|
172
|
+
return label === undefined ? guard : role ? `${role} ${label}` : label;
|
|
173
|
+
};
|
|
174
|
+
var maxLabelArgs = 32;
|
|
175
|
+
var maxLayers = 4;
|
|
176
|
+
var guardRefReaderOf = (ts, ast, unwrap) => {
|
|
177
|
+
const pathOf = (expression) => {
|
|
178
|
+
if (ts.isIdentifier(expression))
|
|
179
|
+
return expression.text;
|
|
180
|
+
if (!ts.isPropertyAccessExpression(expression))
|
|
181
|
+
return null;
|
|
182
|
+
const head = pathOf(expression.expression);
|
|
183
|
+
return head && `${head}.${expression.name.text}`;
|
|
184
|
+
};
|
|
185
|
+
const layersOf = (call) => {
|
|
186
|
+
const inner = unwrap(call.expression);
|
|
187
|
+
if (!ts.isCallExpression(inner))
|
|
188
|
+
return { root: call, layers: 0 };
|
|
189
|
+
const { root, layers } = layersOf(inner);
|
|
190
|
+
return { root, layers: layers + 1 };
|
|
191
|
+
};
|
|
192
|
+
const calleeOf = (call) => pathOf(unwrap(layersOf(call).root.expression)) ?? "?";
|
|
193
|
+
const labelOf = (source, call) => {
|
|
194
|
+
const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
|
|
195
|
+
const inner = unwrap(call.expression);
|
|
196
|
+
const head = ts.isCallExpression(inner) ? labelOf(source, inner) : pathOf(inner) ?? "?";
|
|
197
|
+
return `${head}(${args.length <= maxLabelArgs && !args.includes(`
|
|
198
|
+
`) ? args : "…"})`;
|
|
199
|
+
};
|
|
200
|
+
const guardRefOf = (expression, made, member) => {
|
|
201
|
+
const value = unwrap(expression);
|
|
202
|
+
if (!ts.isCallExpression(value))
|
|
203
|
+
return pathOf(value) ?? ast.expressionName(value);
|
|
204
|
+
const prefix = `${member}:${calleeOf(value)}@`;
|
|
205
|
+
const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
|
|
206
|
+
made.push({ call: value, name });
|
|
207
|
+
return name;
|
|
208
|
+
};
|
|
209
|
+
const guardsIn = (source, object, made, member) => {
|
|
210
|
+
const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
|
|
211
|
+
if (!guards)
|
|
212
|
+
return [];
|
|
213
|
+
const refOf = (expression) => guardRefOf(expression, made, member);
|
|
214
|
+
if (ts.isArrayLiteralExpression(guards))
|
|
215
|
+
return guards.elements.map(refOf).filter((name) => name !== null);
|
|
216
|
+
if (ts.isObjectLiteralExpression(guards))
|
|
217
|
+
return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
|
|
218
|
+
const value = unwrap(property.initializer);
|
|
219
|
+
const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
|
|
220
|
+
return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
|
|
221
|
+
});
|
|
222
|
+
const single = refOf(guards);
|
|
223
|
+
return single ? [single] : [];
|
|
224
|
+
};
|
|
225
|
+
return { pathOf, layersOf, labelOf, guardsIn };
|
|
226
|
+
};
|
|
227
|
+
var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf, layersOf) => {
|
|
228
|
+
const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
|
|
229
|
+
const scopeOf = (node) => {
|
|
230
|
+
const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
|
|
231
|
+
const value = scope && unwrap(scope);
|
|
232
|
+
return value && ts.isStringLiteralLike(value) ? value.text : null;
|
|
233
|
+
};
|
|
234
|
+
const functionsOf = (source) => {
|
|
235
|
+
const functions = new Map;
|
|
236
|
+
for (const statement of source.statements) {
|
|
237
|
+
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
|
|
238
|
+
functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
|
|
239
|
+
if (!ts.isVariableStatement(statement))
|
|
240
|
+
continue;
|
|
241
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
242
|
+
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
243
|
+
if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
|
|
244
|
+
functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return functions;
|
|
248
|
+
};
|
|
249
|
+
const returnedOf = (body, guardNames, depth = 0) => {
|
|
250
|
+
const locals = new Map;
|
|
251
|
+
const pending = [];
|
|
252
|
+
const visit = (node) => {
|
|
253
|
+
if (ts.isClassDeclaration(node) && node.name)
|
|
254
|
+
locals.set(node.name.text, node);
|
|
255
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
|
|
256
|
+
locals.set(node.name.text, node.initializer);
|
|
257
|
+
if (ts.isReturnStatement(node) && node.expression)
|
|
258
|
+
pending.push(node.expression);
|
|
259
|
+
if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
|
|
260
|
+
ts.forEachChild(node, visit);
|
|
261
|
+
};
|
|
262
|
+
if (ts.isBlock(body))
|
|
263
|
+
ts.forEachChild(body, visit);
|
|
264
|
+
else
|
|
265
|
+
pending.push(body);
|
|
266
|
+
let guard = null;
|
|
267
|
+
const calls = [];
|
|
268
|
+
const seen = new Set;
|
|
269
|
+
const passed = new Set;
|
|
270
|
+
const returned = [];
|
|
271
|
+
for (let node = pending.pop();node; node = pending.pop()) {
|
|
272
|
+
if (seen.has(node))
|
|
273
|
+
continue;
|
|
274
|
+
seen.add(node);
|
|
275
|
+
const value = ts.isExpression(node) ? unwrap(node) : node;
|
|
276
|
+
if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
|
|
277
|
+
guard ??= value;
|
|
278
|
+
else if ((ts.isArrowFunction(value) || ts.isFunctionExpression(value)) && !passed.has(node) && depth < maxLayers)
|
|
279
|
+
returned.push(value.body);
|
|
280
|
+
else if (ts.isConditionalExpression(value))
|
|
281
|
+
pending.push(value.whenTrue, value.whenFalse);
|
|
282
|
+
else if (ts.isBinaryExpression(value))
|
|
283
|
+
pending.push(value.left, value.right);
|
|
284
|
+
else if (ts.isIdentifier(value)) {
|
|
285
|
+
const local = locals.get(value.text);
|
|
286
|
+
if (local)
|
|
287
|
+
pending.push(local);
|
|
288
|
+
} else if (ts.isCallExpression(value)) {
|
|
289
|
+
const { root, layers } = layersOf(value);
|
|
290
|
+
const callee = pathOf(unwrap(root.expression));
|
|
291
|
+
if (callee && !passed.has(node) && !calls.some((call) => call.callee === callee))
|
|
292
|
+
calls.push({ callee, layers });
|
|
293
|
+
for (let call = value;ts.isCallExpression(call); call = unwrap(call.expression)) {
|
|
294
|
+
for (const arg of call.arguments)
|
|
295
|
+
passed.add(arg);
|
|
296
|
+
pending.push(...call.arguments);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const [inner] = returned;
|
|
301
|
+
if (guard || calls.length || !inner)
|
|
302
|
+
return { guard, calls, layers: 0 };
|
|
303
|
+
const nested = returnedOf(inner, guardNames, depth + 1);
|
|
304
|
+
return { ...nested, layers: nested.layers + 1 };
|
|
305
|
+
};
|
|
306
|
+
const guardFactoriesOf = (source, guardNames, isImported) => {
|
|
307
|
+
const functions = functionsOf(source);
|
|
308
|
+
const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
|
|
309
|
+
const members = new Set;
|
|
310
|
+
const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
|
|
311
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
312
|
+
isGrowing = false;
|
|
313
|
+
for (const [name, { guard, calls }] of returned) {
|
|
314
|
+
if (members.has(name) || !guard && !calls.some(({ callee }) => isFactoryCall(callee)))
|
|
315
|
+
continue;
|
|
316
|
+
members.add(name);
|
|
317
|
+
isGrowing = true;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const factories = new Map;
|
|
321
|
+
for (const name of members) {
|
|
322
|
+
const found = functions.get(name);
|
|
323
|
+
const made = returned.get(name);
|
|
324
|
+
if (!found || !made)
|
|
325
|
+
continue;
|
|
326
|
+
factories.set(name, {
|
|
327
|
+
name,
|
|
328
|
+
span: spanOf(source, found.name),
|
|
329
|
+
extent: extentOf(source, found.statement),
|
|
330
|
+
scope: made.guard ? scopeOf(made.guard) : null,
|
|
331
|
+
doc: docOf(source, found.statement),
|
|
332
|
+
direct: !!made.guard,
|
|
333
|
+
via: made.calls.filter(({ callee }) => isFactoryCall(callee)),
|
|
334
|
+
layers: made.layers
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
return factories;
|
|
338
|
+
};
|
|
339
|
+
return { implementsGuard, scopeOf, guardFactoriesOf };
|
|
340
|
+
};
|
|
341
|
+
var createGuardFactReader = (ts, ast, base) => {
|
|
342
|
+
const { spanOf, extentOf, unwrap, docOf } = base;
|
|
343
|
+
const { pathOf, layersOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
|
|
344
|
+
const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf, layersOf);
|
|
345
|
+
const guardsOf = (source, imports, madeInPlace) => {
|
|
346
|
+
const guardNames = new Set([
|
|
347
|
+
"Guard",
|
|
348
|
+
...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
|
|
349
|
+
]);
|
|
350
|
+
const imported = new Set(imports.map(({ name }) => name));
|
|
351
|
+
const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
|
|
352
|
+
const factories = guardFactoriesOf(source, guardNames, isImported);
|
|
353
|
+
const guards = [];
|
|
354
|
+
const calls = [];
|
|
355
|
+
const makeOf = (made, value, isInPlace = false) => {
|
|
356
|
+
if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
|
|
357
|
+
guards.push({ ...made, scope: scopeOf(value), factory: null, layers: 0 });
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const { root, layers } = ts.isCallExpression(value) ? layersOf(value) : { root: null, layers: 0 };
|
|
361
|
+
const callee = root && pathOf(unwrap(root.expression));
|
|
362
|
+
const factory = callee && factories.get(callee);
|
|
363
|
+
if (factory)
|
|
364
|
+
guards.push({ ...made, scope: factory.scope, factory: factory.name, layers });
|
|
365
|
+
else if (callee && (isImported(callee) || isInPlace))
|
|
366
|
+
calls.push({ ...made, callee, kind: "call", scope: null, layers });
|
|
367
|
+
};
|
|
368
|
+
for (const statement of source.statements) {
|
|
369
|
+
if (ts.isClassDeclaration(statement) && statement.name) {
|
|
370
|
+
const declared = {
|
|
371
|
+
name: statement.name.text,
|
|
372
|
+
span: spanOf(source, statement.name),
|
|
373
|
+
extent: extentOf(source, statement),
|
|
374
|
+
scope: scopeOf(statement),
|
|
375
|
+
doc: docOf(source, statement)
|
|
376
|
+
};
|
|
377
|
+
const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
|
|
378
|
+
const callee = base && pathOf(base.expression);
|
|
379
|
+
if (implementsGuard(statement, guardNames))
|
|
380
|
+
guards.push({ ...declared, factory: null, layers: 0 });
|
|
381
|
+
else if (callee)
|
|
382
|
+
calls.push({ ...declared, callee, kind: "extends", layers: 0 });
|
|
383
|
+
}
|
|
384
|
+
if (!ts.isVariableStatement(statement))
|
|
385
|
+
continue;
|
|
386
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
387
|
+
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
388
|
+
if (!initializer || !ts.isIdentifier(declaration.name))
|
|
389
|
+
continue;
|
|
390
|
+
const holder = declaration.name.text;
|
|
391
|
+
makeOf({
|
|
392
|
+
name: holder,
|
|
393
|
+
span: spanOf(source, declaration.name),
|
|
394
|
+
extent: extentOf(source, statement),
|
|
395
|
+
doc: docOf(source, statement)
|
|
396
|
+
}, initializer);
|
|
397
|
+
if (!ts.isObjectLiteralExpression(initializer))
|
|
398
|
+
continue;
|
|
399
|
+
for (const property of initializer.properties) {
|
|
400
|
+
const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
|
|
401
|
+
if (key && ts.isPropertyAssignment(property))
|
|
402
|
+
makeOf({
|
|
403
|
+
name: `${holder}.${key}`,
|
|
404
|
+
span: spanOf(source, property.name),
|
|
405
|
+
extent: extentOf(source, property),
|
|
406
|
+
doc: docOf(source, property)
|
|
407
|
+
}, unwrap(property.initializer));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
for (const { call, name } of madeInPlace)
|
|
412
|
+
makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
|
|
413
|
+
const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
|
|
414
|
+
return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
|
|
415
|
+
};
|
|
416
|
+
return { guardsIn, guardsOf };
|
|
417
|
+
};
|
|
418
|
+
|
|
126
419
|
// pkgs/@akanjs/language/moduleFacts.ts
|
|
127
420
|
var testFilePattern = /\.(?:test|spec)\.tsx?$/;
|
|
128
421
|
var moduleCallees = new Set([
|
|
@@ -191,21 +484,6 @@ var createFactReader = (ts, ast) => {
|
|
|
191
484
|
const value = unwrap(expression);
|
|
192
485
|
return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
|
|
193
486
|
};
|
|
194
|
-
const guardsIn = (object) => {
|
|
195
|
-
const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
|
|
196
|
-
if (!guards)
|
|
197
|
-
return [];
|
|
198
|
-
if (ts.isArrayLiteralExpression(guards))
|
|
199
|
-
return guards.elements.map(guardNameOf).filter((name) => name !== null);
|
|
200
|
-
if (ts.isObjectLiteralExpression(guards))
|
|
201
|
-
return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
|
|
202
|
-
const value = unwrap(property.initializer);
|
|
203
|
-
const names = ts.isArrayLiteralExpression(value) ? value.elements.map(guardNameOf) : [guardNameOf(value)];
|
|
204
|
-
return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
|
|
205
|
-
});
|
|
206
|
-
const single = guardNameOf(guards);
|
|
207
|
-
return single ? [single] : [];
|
|
208
|
-
};
|
|
209
487
|
const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
|
|
210
488
|
const chainOf = (initializer) => {
|
|
211
489
|
let root = unwrap(initializer);
|
|
@@ -222,7 +500,7 @@ var createFactReader = (ts, ast) => {
|
|
|
222
500
|
const node = keyName(property);
|
|
223
501
|
return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
|
|
224
502
|
});
|
|
225
|
-
const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
503
|
+
const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
226
504
|
if (!ts.isPropertyAssignment(property))
|
|
227
505
|
return [];
|
|
228
506
|
const name = ast.nodeName(property.name);
|
|
@@ -234,7 +512,7 @@ var createFactReader = (ts, ast) => {
|
|
|
234
512
|
name,
|
|
235
513
|
span: spanOf(source, property.name),
|
|
236
514
|
extent: extentOf(source, property),
|
|
237
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
515
|
+
guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
|
|
238
516
|
live: methods.includes("live"),
|
|
239
517
|
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
240
518
|
}
|
|
@@ -284,7 +562,7 @@ var createFactReader = (ts, ast) => {
|
|
|
284
562
|
const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
|
|
285
563
|
return `${ms / size}${unit}`;
|
|
286
564
|
};
|
|
287
|
-
const modesOf = (option) => {
|
|
565
|
+
const modesOf = (option, type) => {
|
|
288
566
|
const object = option && unwrap(option);
|
|
289
567
|
if (!object || !ts.isObjectLiteralExpression(object))
|
|
290
568
|
return [];
|
|
@@ -294,9 +572,9 @@ var createFactReader = (ts, ast) => {
|
|
|
294
572
|
if (name === "enabled")
|
|
295
573
|
return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
|
|
296
574
|
if (name === "once")
|
|
297
|
-
return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
575
|
+
return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
298
576
|
if (name === "lock")
|
|
299
|
-
return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
577
|
+
return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
300
578
|
if (name !== "serverMode" && name !== "operationMode")
|
|
301
579
|
return [];
|
|
302
580
|
const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
|
|
@@ -314,7 +592,8 @@ var createFactReader = (ts, ast) => {
|
|
|
314
592
|
const { root } = chainOf(property.initializer);
|
|
315
593
|
const type = root ? verbOf(root.expression) : null;
|
|
316
594
|
const [first, second] = root?.arguments ?? [];
|
|
317
|
-
const
|
|
595
|
+
const cron = type === "cron" && first ? unwrap(first) : null;
|
|
596
|
+
const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
|
|
318
597
|
const option = type === "initialize" || type === "destroy" ? first : second;
|
|
319
598
|
return [
|
|
320
599
|
{
|
|
@@ -323,7 +602,7 @@ var createFactReader = (ts, ast) => {
|
|
|
323
602
|
extent: extentOf(source, property),
|
|
324
603
|
type,
|
|
325
604
|
schedule,
|
|
326
|
-
modes: modesOf(option)
|
|
605
|
+
modes: modesOf(option, type)
|
|
327
606
|
}
|
|
328
607
|
];
|
|
329
608
|
});
|
|
@@ -340,7 +619,20 @@ var createFactReader = (ts, ast) => {
|
|
|
340
619
|
return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
|
|
341
620
|
});
|
|
342
621
|
};
|
|
343
|
-
const
|
|
622
|
+
const adaptorBindingsIn = (source) => {
|
|
623
|
+
const bindings = [];
|
|
624
|
+
const visit = (node) => {
|
|
625
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
|
|
626
|
+
const [role, adaptor] = node.arguments.map(guardNameOf);
|
|
627
|
+
if (role && adaptor)
|
|
628
|
+
bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
|
|
629
|
+
}
|
|
630
|
+
ts.forEachChild(node, visit);
|
|
631
|
+
};
|
|
632
|
+
visit(source);
|
|
633
|
+
return bindings;
|
|
634
|
+
};
|
|
635
|
+
const endpointKeysOf = (source, builder, made, model) => {
|
|
344
636
|
const verbOf = verbReaderOf(builder?.parameters[0]);
|
|
345
637
|
return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
346
638
|
if (!ts.isPropertyAssignment(property))
|
|
@@ -355,7 +647,7 @@ var createFactReader = (ts, ast) => {
|
|
|
355
647
|
span: spanOf(source, property.name),
|
|
356
648
|
extent: extentOf(source, property),
|
|
357
649
|
verb: root ? verbOf(root.expression) : null,
|
|
358
|
-
guards: root ? guardsIn(optionObjectOf(root)) : [],
|
|
650
|
+
guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
|
|
359
651
|
argCount: methods.filter((method) => argMethods.has(method)).length
|
|
360
652
|
}
|
|
361
653
|
];
|
|
@@ -422,22 +714,6 @@ var createFactReader = (ts, ast) => {
|
|
|
422
714
|
})) : []
|
|
423
715
|
];
|
|
424
716
|
};
|
|
425
|
-
const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
|
|
426
|
-
const guardClassIn = (node, guardNames) => {
|
|
427
|
-
if ((ts.isClassExpression(node) || ts.isClassDeclaration(node)) && implementsGuard(node, guardNames))
|
|
428
|
-
return node;
|
|
429
|
-
return ts.forEachChild(node, (child) => guardClassIn(child, guardNames)) ?? null;
|
|
430
|
-
};
|
|
431
|
-
const callsIn = (node, names) => {
|
|
432
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text))
|
|
433
|
-
return node.expression.text;
|
|
434
|
-
return ts.forEachChild(node, (child) => callsIn(child, names)) ?? null;
|
|
435
|
-
};
|
|
436
|
-
const scopeOf = (node) => {
|
|
437
|
-
const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
|
|
438
|
-
const value = scope && unwrap(scope);
|
|
439
|
-
return value && ts.isStringLiteralLike(value) ? value.text : null;
|
|
440
|
-
};
|
|
441
717
|
const docOf = (source, node) => {
|
|
442
718
|
const { text } = source;
|
|
443
719
|
const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
|
|
@@ -453,108 +729,20 @@ var createFactReader = (ts, ast) => {
|
|
|
453
729
|
`)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
|
|
454
730
|
return lines.length ? lines.join(" ") : null;
|
|
455
731
|
};
|
|
456
|
-
const
|
|
457
|
-
const functions = new Map;
|
|
458
|
-
for (const statement of source.statements) {
|
|
459
|
-
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
|
|
460
|
-
functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
|
|
461
|
-
if (!ts.isVariableStatement(statement))
|
|
462
|
-
continue;
|
|
463
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
464
|
-
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
465
|
-
if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
|
|
466
|
-
functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
return functions;
|
|
470
|
-
};
|
|
471
|
-
const guardFactoriesOf = (source, guardNames) => {
|
|
472
|
-
const functions = functionsOf(source);
|
|
473
|
-
const factories = new Map;
|
|
474
|
-
const factoryOf = (name, scope, via) => {
|
|
475
|
-
const found = functions.get(name);
|
|
476
|
-
if (!found)
|
|
477
|
-
return;
|
|
478
|
-
factories.set(name, {
|
|
479
|
-
name,
|
|
480
|
-
span: spanOf(source, found.name),
|
|
481
|
-
extent: extentOf(source, found.statement),
|
|
482
|
-
scope,
|
|
483
|
-
doc: docOf(source, found.statement),
|
|
484
|
-
via
|
|
485
|
-
});
|
|
486
|
-
};
|
|
487
|
-
for (const [name, { body }] of functions) {
|
|
488
|
-
const guard = guardClassIn(body, guardNames);
|
|
489
|
-
if (guard)
|
|
490
|
-
factoryOf(name, scopeOf(guard), null);
|
|
491
|
-
}
|
|
492
|
-
for (let isGrowing = true;isGrowing; ) {
|
|
493
|
-
isGrowing = false;
|
|
494
|
-
for (const [name, { body }] of functions) {
|
|
495
|
-
const called = factories.has(name) ? null : callsIn(body, new Set(factories.keys()));
|
|
496
|
-
if (!called)
|
|
497
|
-
continue;
|
|
498
|
-
factoryOf(name, factories.get(called)?.scope ?? null, called);
|
|
499
|
-
isGrowing = true;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
return factories;
|
|
503
|
-
};
|
|
504
|
-
const guardsOf = (source, imports) => {
|
|
505
|
-
const guardNames = new Set([
|
|
506
|
-
"Guard",
|
|
507
|
-
...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
|
|
508
|
-
]);
|
|
509
|
-
const imported = new Set(imports.map(({ name }) => name));
|
|
510
|
-
const factories = guardFactoriesOf(source, guardNames);
|
|
511
|
-
const guards = [];
|
|
512
|
-
const calls = [];
|
|
513
|
-
for (const statement of source.statements) {
|
|
514
|
-
if (ts.isClassDeclaration(statement) && statement.name && implementsGuard(statement, guardNames))
|
|
515
|
-
guards.push({
|
|
516
|
-
name: statement.name.text,
|
|
517
|
-
span: spanOf(source, statement.name),
|
|
518
|
-
extent: extentOf(source, statement),
|
|
519
|
-
scope: scopeOf(statement),
|
|
520
|
-
doc: docOf(source, statement),
|
|
521
|
-
factory: null
|
|
522
|
-
});
|
|
523
|
-
if (!ts.isVariableStatement(statement))
|
|
524
|
-
continue;
|
|
525
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
526
|
-
const initializer = declaration.initializer && unwrap(declaration.initializer);
|
|
527
|
-
if (!initializer || !ts.isIdentifier(declaration.name))
|
|
528
|
-
continue;
|
|
529
|
-
const made = {
|
|
530
|
-
name: declaration.name.text,
|
|
531
|
-
span: spanOf(source, declaration.name),
|
|
532
|
-
extent: extentOf(source, statement),
|
|
533
|
-
doc: docOf(source, statement)
|
|
534
|
-
};
|
|
535
|
-
if (ts.isClassExpression(initializer) && implementsGuard(initializer, guardNames))
|
|
536
|
-
guards.push({ ...made, scope: scopeOf(initializer), factory: null });
|
|
537
|
-
if (!ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression))
|
|
538
|
-
continue;
|
|
539
|
-
const callee = initializer.expression.text;
|
|
540
|
-
const factory = factories.get(callee);
|
|
541
|
-
if (factory)
|
|
542
|
-
guards.push({ ...made, scope: factory.scope, factory: callee });
|
|
543
|
-
else if (imported.has(callee))
|
|
544
|
-
calls.push({ ...made, callee });
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
return { guards, guardFactories: [...factories.values()], guardCalls: calls };
|
|
548
|
-
};
|
|
732
|
+
const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
|
|
549
733
|
const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
|
|
550
734
|
const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
|
|
551
735
|
const read = (source, owner) => {
|
|
552
736
|
const imports = importsIn(source);
|
|
737
|
+
const madeInPlace = [];
|
|
553
738
|
const facts = {
|
|
554
739
|
fileName: source.fileName,
|
|
555
740
|
owner,
|
|
556
741
|
imports,
|
|
557
|
-
|
|
742
|
+
guards: [],
|
|
743
|
+
guardFactories: [],
|
|
744
|
+
guardCalls: [],
|
|
745
|
+
guardLabels: {},
|
|
558
746
|
constants: [],
|
|
559
747
|
enums: [],
|
|
560
748
|
filters: [],
|
|
@@ -564,6 +752,7 @@ var createFactReader = (ts, ast) => {
|
|
|
564
752
|
endpoints: [],
|
|
565
753
|
internals: [],
|
|
566
754
|
adaptors: [],
|
|
755
|
+
adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
|
|
567
756
|
stores: []
|
|
568
757
|
};
|
|
569
758
|
for (const statement of source.statements) {
|
|
@@ -588,16 +777,19 @@ var createFactReader = (ts, ast) => {
|
|
|
588
777
|
...name,
|
|
589
778
|
model: moduleRefOf(call.arguments[0], "db"),
|
|
590
779
|
methods: methodsOf(source, statement),
|
|
591
|
-
plugs: plugsOf(source, builder)
|
|
780
|
+
plugs: plugsOf(source, builder),
|
|
781
|
+
fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
|
|
592
782
|
});
|
|
593
|
-
else if (callee === "slice")
|
|
783
|
+
else if (callee === "slice") {
|
|
784
|
+
const model = moduleRefOf(call.arguments[0], "srv");
|
|
785
|
+
const member = model ?? name.name;
|
|
594
786
|
facts.slices.push({
|
|
595
787
|
...name,
|
|
596
|
-
model
|
|
597
|
-
guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
|
|
598
|
-
keys: sliceKeysOf(source, builder)
|
|
788
|
+
model,
|
|
789
|
+
guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
|
|
790
|
+
keys: sliceKeysOf(source, builder, madeInPlace, member)
|
|
599
791
|
});
|
|
600
|
-
else if (callee === "internal")
|
|
792
|
+
} else if (callee === "internal")
|
|
601
793
|
facts.internals.push({
|
|
602
794
|
...name,
|
|
603
795
|
model: moduleRefOf(call.arguments[0], "srv"),
|
|
@@ -612,13 +804,14 @@ var createFactReader = (ts, ast) => {
|
|
|
612
804
|
plugs: plugsOf(source, builder),
|
|
613
805
|
doc: docOf(source, statement)
|
|
614
806
|
});
|
|
615
|
-
} else if (callee === "endpoint")
|
|
807
|
+
} else if (callee === "endpoint") {
|
|
808
|
+
const model = moduleRefOf(call.arguments[0], "srv");
|
|
616
809
|
facts.endpoints.push({
|
|
617
810
|
...name,
|
|
618
|
-
model
|
|
619
|
-
keys: endpointKeysOf(source, builder)
|
|
811
|
+
model,
|
|
812
|
+
keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
|
|
620
813
|
});
|
|
621
|
-
else if (callee === "store")
|
|
814
|
+
} else if (callee === "store")
|
|
622
815
|
facts.stores.push({
|
|
623
816
|
...name,
|
|
624
817
|
model: moduleRefOf(call.arguments[0], "sig"),
|
|
@@ -626,10 +819,647 @@ var createFactReader = (ts, ast) => {
|
|
|
626
819
|
actions: methodsOf(source, statement)
|
|
627
820
|
});
|
|
628
821
|
}
|
|
629
|
-
return facts;
|
|
630
|
-
};
|
|
631
|
-
return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
|
|
632
|
-
};
|
|
822
|
+
return { ...facts, ...guardsOf(source, imports, madeInPlace) };
|
|
823
|
+
};
|
|
824
|
+
return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
// pkgs/@akanjs/language/guardResolver.ts
|
|
828
|
+
var frameworkOwner = "akanjs";
|
|
829
|
+
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
830
|
+
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
831
|
+
var maxMapDepth = 4;
|
|
832
|
+
var identifierPattern = /^[\p{ID_Start}$_][\p{ID_Continue}$\u200c\u200d]*$/u;
|
|
833
|
+
var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
|
|
834
|
+
var exportingPatternOf = (name) => new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
|
|
835
|
+
var declaringPatternOf = (name, isAnyBinding) => {
|
|
836
|
+
const escaped = escapeRegExp(name);
|
|
837
|
+
const braced = "\\{(?:[^}]*?[\\s,])?";
|
|
838
|
+
const leads = [
|
|
839
|
+
"function\\s*\\*?\\s*",
|
|
840
|
+
"(?:const|let|var)\\s+",
|
|
841
|
+
`export\\s*${braced}`,
|
|
842
|
+
...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
|
|
843
|
+
];
|
|
844
|
+
return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
class GuardResolver {
|
|
848
|
+
#source;
|
|
849
|
+
#routes;
|
|
850
|
+
#guards = new Map;
|
|
851
|
+
#madeInPlace = new Map;
|
|
852
|
+
#mapEntries = new Map;
|
|
853
|
+
#reexports = new Map;
|
|
854
|
+
#declarations = new Map;
|
|
855
|
+
#starExports = new Map;
|
|
856
|
+
#moduleExports = new Map;
|
|
857
|
+
#ownerFiles = null;
|
|
858
|
+
#modules = null;
|
|
859
|
+
constructor(source, routes) {
|
|
860
|
+
this.#source = source;
|
|
861
|
+
this.#routes = routes;
|
|
862
|
+
}
|
|
863
|
+
find(local, site) {
|
|
864
|
+
return this.#findGuard(local, site, 0);
|
|
865
|
+
}
|
|
866
|
+
madeInPlace(fileName, name) {
|
|
867
|
+
return this.#madeInPlace.get(`${fileName}
|
|
868
|
+
${name}`);
|
|
869
|
+
}
|
|
870
|
+
declare(facts) {
|
|
871
|
+
const factories = new Map;
|
|
872
|
+
const declaredFactories = [];
|
|
873
|
+
for (const file of facts)
|
|
874
|
+
for (const fact of file.guardFactories) {
|
|
875
|
+
const factory = {
|
|
876
|
+
fact,
|
|
877
|
+
owner: file.owner,
|
|
878
|
+
site: file,
|
|
879
|
+
made: [],
|
|
880
|
+
isReal: fact.direct,
|
|
881
|
+
scope: fact.scope,
|
|
882
|
+
layers: fact.layers,
|
|
883
|
+
targets: []
|
|
884
|
+
};
|
|
885
|
+
factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
|
|
886
|
+
declaredFactories.push(factory);
|
|
887
|
+
}
|
|
888
|
+
for (const factory of declaredFactories)
|
|
889
|
+
factory.targets = factory.fact.via.flatMap(({ callee, layers }) => {
|
|
890
|
+
const target = this.#factoryAt(factories, callee, factory.site);
|
|
891
|
+
return target ? [{ target, layers }] : [];
|
|
892
|
+
});
|
|
893
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
894
|
+
isGrowing = false;
|
|
895
|
+
for (const factory of declaredFactories) {
|
|
896
|
+
const via = factory.targets.find(({ target, layers }) => target.isReal && target.layers >= layers);
|
|
897
|
+
if (factory.isReal || !via)
|
|
898
|
+
continue;
|
|
899
|
+
factory.isReal = true;
|
|
900
|
+
factory.layers = factory.fact.layers + via.target.layers - via.layers;
|
|
901
|
+
factory.scope ??= via.target.scope;
|
|
902
|
+
isGrowing = true;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
const partsOf = (factory, seen = new Set) => {
|
|
906
|
+
if (seen.has(factory))
|
|
907
|
+
return [];
|
|
908
|
+
seen.add(factory);
|
|
909
|
+
return [
|
|
910
|
+
{ fileName: factory.site.fileName, extent: factory.fact.extent },
|
|
911
|
+
...factory.targets.filter(({ target }) => target.isReal).flatMap(({ target }) => partsOf(target, seen))
|
|
912
|
+
];
|
|
913
|
+
};
|
|
914
|
+
const declare = (file, made, scope, parts) => {
|
|
915
|
+
const { name, span, extent, doc } = made;
|
|
916
|
+
const label = file.guardLabels[name];
|
|
917
|
+
const own = { fileName: file.fileName, extent };
|
|
918
|
+
const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
|
|
919
|
+
if (name.includes("@"))
|
|
920
|
+
this.#madeInPlace.set(`${file.fileName}
|
|
921
|
+
${name}`, guard);
|
|
922
|
+
else
|
|
923
|
+
this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
|
|
924
|
+
return guard;
|
|
925
|
+
};
|
|
926
|
+
const extending = [];
|
|
927
|
+
for (const file of facts) {
|
|
928
|
+
for (const made of file.guards) {
|
|
929
|
+
const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
|
|
930
|
+
if (!made.factory)
|
|
931
|
+
declare(file, made, made.scope, []);
|
|
932
|
+
else if (factory?.isReal && factory.layers === made.layers)
|
|
933
|
+
factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
|
|
934
|
+
}
|
|
935
|
+
for (const made of file.guardCalls) {
|
|
936
|
+
if (made.kind === "extends") {
|
|
937
|
+
extending.push([file, made]);
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
const factory = this.#factoryAt(factories, made.callee, file);
|
|
941
|
+
if (factory?.isReal && factory.layers === made.layers)
|
|
942
|
+
factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
for (let isGrowing = true;isGrowing; ) {
|
|
946
|
+
isGrowing = false;
|
|
947
|
+
for (const [idx, entry] of extending.entries()) {
|
|
948
|
+
const [file, made] = entry;
|
|
949
|
+
const found = this.#findGuard(made.callee, file, 0, true);
|
|
950
|
+
if (!("guard" in found))
|
|
951
|
+
continue;
|
|
952
|
+
declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
|
|
953
|
+
extending.splice(idx, 1);
|
|
954
|
+
isGrowing = true;
|
|
955
|
+
break;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
for (const { fact, made, isReal } of declaredFactories)
|
|
959
|
+
if (isReal && made.every((guard) => !guard.doc))
|
|
960
|
+
for (const guard of made)
|
|
961
|
+
guard.doc = fact.doc;
|
|
962
|
+
for (const list of this.#guards.values())
|
|
963
|
+
for (const guard of list)
|
|
964
|
+
if (list.some((other) => other !== guard && other.owner === guard.owner))
|
|
965
|
+
guard.file = this.inOwner(guard.span.fileName, guard.owner);
|
|
966
|
+
for (const guard of this.#madeInPlace.values())
|
|
967
|
+
guard.file = this.inOwner(guard.span.fileName, guard.owner);
|
|
968
|
+
return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
|
|
969
|
+
}
|
|
970
|
+
#factoryAt(factories, callee, site, depth = 0) {
|
|
971
|
+
const [head = callee, member] = callee.split(".");
|
|
972
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
973
|
+
if (!imported)
|
|
974
|
+
return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
|
|
975
|
+
if (member && imported.imported !== "*")
|
|
976
|
+
return;
|
|
977
|
+
const from = this.ownerOfSpecifier(site.fileName, imported.from);
|
|
978
|
+
if (from === undefined || from === frameworkOwner)
|
|
979
|
+
return;
|
|
980
|
+
const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
|
|
981
|
+
const candidates = factories.get(name) ?? [];
|
|
982
|
+
const near = this.pathOfSpecifier(site.fileName, imported.from);
|
|
983
|
+
const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
|
|
984
|
+
if (own)
|
|
985
|
+
return own;
|
|
986
|
+
const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
|
|
987
|
+
if (reexport)
|
|
988
|
+
return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
|
|
989
|
+
return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
|
|
990
|
+
}
|
|
991
|
+
#findGuard(local, site, depth, isBase = false) {
|
|
992
|
+
const segments = local.split(".");
|
|
993
|
+
const head = segments[0] ?? local;
|
|
994
|
+
const imported = site.imports.find((item) => item.name === head);
|
|
995
|
+
const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
|
|
996
|
+
if (imported && from === undefined)
|
|
997
|
+
return { external: true };
|
|
998
|
+
const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
|
|
999
|
+
const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
|
|
1000
|
+
const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
|
|
1001
|
+
const holder = imported ? from ?? null : site.owner;
|
|
1002
|
+
const [objectName, member] = path;
|
|
1003
|
+
if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
|
|
1004
|
+
const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
|
|
1005
|
+
if (made)
|
|
1006
|
+
return { guard: made };
|
|
1007
|
+
for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
|
|
1008
|
+
const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
|
|
1009
|
+
if ("guard" in found)
|
|
1010
|
+
return found;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
const name = path[path.length - 1] ?? head;
|
|
1014
|
+
if (from === frameworkOwner)
|
|
1015
|
+
return { name, from };
|
|
1016
|
+
const declared = this.#guards.get(name) ?? [];
|
|
1017
|
+
const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
|
|
1018
|
+
if (own)
|
|
1019
|
+
return { guard: own };
|
|
1020
|
+
if (!imported) {
|
|
1021
|
+
const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
|
|
1022
|
+
return visible ? { guard: visible } : { name, from };
|
|
1023
|
+
}
|
|
1024
|
+
const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
|
|
1025
|
+
if (reexport)
|
|
1026
|
+
return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
|
|
1027
|
+
const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
|
|
1028
|
+
return reached ? { guard: reached } : { name, from };
|
|
1029
|
+
}
|
|
1030
|
+
reexportOf(owner, name, near) {
|
|
1031
|
+
const key = `${owner}
|
|
1032
|
+
${name}
|
|
1033
|
+
${near ?? ""}`;
|
|
1034
|
+
const cached = this.#reexports.get(key);
|
|
1035
|
+
if (cached !== undefined)
|
|
1036
|
+
return cached;
|
|
1037
|
+
let found = null;
|
|
1038
|
+
const exporting = exportingPatternOf(name);
|
|
1039
|
+
for (const fileName of this.#filesOf(owner, name, near)) {
|
|
1040
|
+
const text = this.#source.readText(fileName);
|
|
1041
|
+
if (text === undefined || !exporting.test(text))
|
|
1042
|
+
continue;
|
|
1043
|
+
const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
|
|
1044
|
+
if (!reexport)
|
|
1045
|
+
continue;
|
|
1046
|
+
const site = { fileName, owner, imports: [] };
|
|
1047
|
+
found = reexport.from ? {
|
|
1048
|
+
local: reexport.name,
|
|
1049
|
+
site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
|
|
1050
|
+
} : { local: reexport.name, site };
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
1053
|
+
this.#reexports.set(key, found);
|
|
1054
|
+
return found;
|
|
1055
|
+
}
|
|
1056
|
+
#reexportIn(source, name) {
|
|
1057
|
+
const { ts, reader } = this.#source;
|
|
1058
|
+
for (const statement of source.statements) {
|
|
1059
|
+
if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
|
|
1060
|
+
continue;
|
|
1061
|
+
const { exportClause, moduleSpecifier } = statement;
|
|
1062
|
+
if (!exportClause || !ts.isNamedExports(exportClause))
|
|
1063
|
+
continue;
|
|
1064
|
+
const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
|
|
1065
|
+
if (!element)
|
|
1066
|
+
continue;
|
|
1067
|
+
const original = (element.propertyName ?? element.name).text;
|
|
1068
|
+
if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
|
|
1069
|
+
return { name: original, from: moduleSpecifier.text };
|
|
1070
|
+
const imported = reader.importsIn(source).find((item) => item.name === original);
|
|
1071
|
+
if (!imported)
|
|
1072
|
+
return { name: original, from: null };
|
|
1073
|
+
return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
|
|
1074
|
+
}
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
#filesOf(owner, name, near) {
|
|
1078
|
+
const isNear = (fileName) => {
|
|
1079
|
+
const relative = this.#source.relative(fileName);
|
|
1080
|
+
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
1081
|
+
};
|
|
1082
|
+
if (!identifierPattern.test(name))
|
|
1083
|
+
return [];
|
|
1084
|
+
const candidates = this.#filesIn(owner).filter(([, words]) => words.includes(`
|
|
1085
|
+
${name}
|
|
1086
|
+
`)).map(([fileName]) => fileName);
|
|
1087
|
+
return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
|
|
1088
|
+
}
|
|
1089
|
+
#mapEntriesOf(objectName, member, fileName, owner) {
|
|
1090
|
+
const key = `${objectName}
|
|
1091
|
+
${member}
|
|
1092
|
+
${fileName ?? ""}
|
|
1093
|
+
${owner ?? ""}`;
|
|
1094
|
+
const cached = this.#mapEntries.get(key);
|
|
1095
|
+
if (cached)
|
|
1096
|
+
return cached;
|
|
1097
|
+
const { ts, reader } = this.#source;
|
|
1098
|
+
const entries = [];
|
|
1099
|
+
const fileNames = [
|
|
1100
|
+
...fileName ? [fileName] : [],
|
|
1101
|
+
...(owner === null ? [] : this.#filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
|
|
1102
|
+
${objectName}
|
|
1103
|
+
`) && words.includes(`
|
|
1104
|
+
${member}
|
|
1105
|
+
`)).map(([other]) => other)
|
|
1106
|
+
];
|
|
1107
|
+
for (const holder of fileNames) {
|
|
1108
|
+
const text = this.#source.readText(holder);
|
|
1109
|
+
if (text === undefined)
|
|
1110
|
+
continue;
|
|
1111
|
+
const source = this.#source.parse(holder, text);
|
|
1112
|
+
const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
|
|
1113
|
+
for (const statement of source.statements) {
|
|
1114
|
+
if (!ts.isVariableStatement(statement))
|
|
1115
|
+
continue;
|
|
1116
|
+
const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
|
|
1117
|
+
const object = map && reader.unwrap(map);
|
|
1118
|
+
if (!object || !ts.isObjectLiteralExpression(object))
|
|
1119
|
+
continue;
|
|
1120
|
+
for (const property of object.properties) {
|
|
1121
|
+
if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
|
|
1122
|
+
entries.push({ name: member, site });
|
|
1123
|
+
if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
|
|
1124
|
+
continue;
|
|
1125
|
+
const value = reader.unwrap(property.initializer);
|
|
1126
|
+
if (ts.isIdentifier(value))
|
|
1127
|
+
entries.push({ name: value.text, site });
|
|
1128
|
+
else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
|
|
1129
|
+
entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
this.#mapEntries.set(key, entries);
|
|
1134
|
+
return entries;
|
|
1135
|
+
}
|
|
1136
|
+
declarationOf(owner, name, near, depth = 0) {
|
|
1137
|
+
const key = `${owner}
|
|
1138
|
+
${name}
|
|
1139
|
+
${near ?? ""}`;
|
|
1140
|
+
const cached = this.#declarations.get(key);
|
|
1141
|
+
if (cached !== undefined)
|
|
1142
|
+
return cached;
|
|
1143
|
+
this.#declarations.set(key, null);
|
|
1144
|
+
const declaring = declaringPatternOf(name, false);
|
|
1145
|
+
let found = null;
|
|
1146
|
+
for (const fileName of this.#filesOf(owner, name, near)) {
|
|
1147
|
+
const text = this.#source.readText(fileName);
|
|
1148
|
+
if (text === undefined || !declaring.test(text))
|
|
1149
|
+
continue;
|
|
1150
|
+
const source = this.#source.parse(fileName, text);
|
|
1151
|
+
const declared = this.#routes.declarationIn(source, name);
|
|
1152
|
+
if (declared) {
|
|
1153
|
+
found = { source, ...declared, name };
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
const reexport = this.#reexportIn(source, name);
|
|
1157
|
+
if (!reexport)
|
|
1158
|
+
continue;
|
|
1159
|
+
if (!reexport.from) {
|
|
1160
|
+
const local = this.#routes.declarationIn(source, reexport.name);
|
|
1161
|
+
found = local ? { source, ...local, name: reexport.name } : null;
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
const next = this.ownerOfSpecifier(fileName, reexport.from);
|
|
1165
|
+
if (next && next !== frameworkOwner && depth < maxMapDepth)
|
|
1166
|
+
found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
|
|
1167
|
+
break;
|
|
1168
|
+
}
|
|
1169
|
+
this.#declarations.set(key, found);
|
|
1170
|
+
return found;
|
|
1171
|
+
}
|
|
1172
|
+
#throughStar(declared, owner, name, near) {
|
|
1173
|
+
if (!declared.length)
|
|
1174
|
+
return;
|
|
1175
|
+
const { owners, isOpaque } = this.starExportsOf(owner, near);
|
|
1176
|
+
const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
|
|
1177
|
+
return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
|
|
1178
|
+
}
|
|
1179
|
+
starExportsOf(owner, near) {
|
|
1180
|
+
const key = `${owner}
|
|
1181
|
+
${near}`;
|
|
1182
|
+
const cached = this.#starExports.get(key);
|
|
1183
|
+
if (cached)
|
|
1184
|
+
return cached;
|
|
1185
|
+
const found = { owners: [], packages: [], paths: [near], isOpaque: false };
|
|
1186
|
+
this.#starExports.set(key, found);
|
|
1187
|
+
for (let idx = 0;idx < found.paths.length; idx++)
|
|
1188
|
+
for (const fileName of this.#modulesAt(found.paths[idx] ?? ""))
|
|
1189
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
|
|
1190
|
+
const target = this.ownerOfSpecifier(fileName, specifier);
|
|
1191
|
+
const path = this.pathOfSpecifier(fileName, specifier);
|
|
1192
|
+
if (path === null && !found.packages.includes(specifier))
|
|
1193
|
+
found.packages.push(specifier);
|
|
1194
|
+
if (target === undefined || target === frameworkOwner || path === null)
|
|
1195
|
+
found.isOpaque = true;
|
|
1196
|
+
else if (!found.paths.includes(path))
|
|
1197
|
+
found.paths.push(path);
|
|
1198
|
+
if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
|
|
1199
|
+
found.owners.push(target);
|
|
1200
|
+
}
|
|
1201
|
+
return found;
|
|
1202
|
+
}
|
|
1203
|
+
exportsAt(path, name) {
|
|
1204
|
+
const key = `${path}
|
|
1205
|
+
${name}`;
|
|
1206
|
+
const cached = this.#moduleExports.get(key);
|
|
1207
|
+
if (cached)
|
|
1208
|
+
return cached;
|
|
1209
|
+
const found = { named: null, stars: [] };
|
|
1210
|
+
const exporting = exportingPatternOf(name);
|
|
1211
|
+
for (const fileName of this.#modulesAt(path)) {
|
|
1212
|
+
const text = this.#source.readText(fileName);
|
|
1213
|
+
if (text === undefined)
|
|
1214
|
+
continue;
|
|
1215
|
+
const named = identifierPattern.test(name) && exporting.test(text) ? this.#reexportIn(this.#source.parse(fileName, text), name) : null;
|
|
1216
|
+
if (named) {
|
|
1217
|
+
found.named = { ...named, fileName };
|
|
1218
|
+
found.stars = [];
|
|
1219
|
+
break;
|
|
1220
|
+
}
|
|
1221
|
+
for (const [, specifier = ""] of text.matchAll(starExportPattern)) {
|
|
1222
|
+
const target = this.pathOfSpecifier(fileName, specifier);
|
|
1223
|
+
if (target !== null && !found.stars.includes(target))
|
|
1224
|
+
found.stars.push(target);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
this.#moduleExports.set(key, found);
|
|
1228
|
+
return found;
|
|
1229
|
+
}
|
|
1230
|
+
#modulesAt(path) {
|
|
1231
|
+
if (!this.#modules) {
|
|
1232
|
+
this.#modules = new Map;
|
|
1233
|
+
for (const [fileName] of this.#source.files) {
|
|
1234
|
+
const module2 = this.#source.relative(fileName).replace(/\.tsx?$/, "");
|
|
1235
|
+
for (const key of module2.endsWith("/index") ? [module2, module2.slice(0, -6)] : [module2])
|
|
1236
|
+
this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
return this.#modules.get(path) ?? [];
|
|
1240
|
+
}
|
|
1241
|
+
hasModule(path) {
|
|
1242
|
+
return this.#modulesAt(path).length > 0;
|
|
1243
|
+
}
|
|
1244
|
+
packagesUnder(owner, folder) {
|
|
1245
|
+
const packages = new Set;
|
|
1246
|
+
for (const [fileName, words] of this.#filesIn(owner)) {
|
|
1247
|
+
if (!this.#source.relative(fileName).startsWith(`${folder}/`) || !words.includes(`
|
|
1248
|
+
export
|
|
1249
|
+
`))
|
|
1250
|
+
continue;
|
|
1251
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
|
|
1252
|
+
if (this.pathOfSpecifier(fileName, specifier) === null)
|
|
1253
|
+
packages.add(specifier);
|
|
1254
|
+
}
|
|
1255
|
+
return [...packages];
|
|
1256
|
+
}
|
|
1257
|
+
declares(owner, name) {
|
|
1258
|
+
const declaring = declaringPatternOf(name, true);
|
|
1259
|
+
return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
|
|
1260
|
+
}
|
|
1261
|
+
declaresAt(path, name) {
|
|
1262
|
+
const declaring = declaringPatternOf(name, true);
|
|
1263
|
+
return this.#modulesAt(path).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
|
|
1264
|
+
}
|
|
1265
|
+
#filesIn(owner) {
|
|
1266
|
+
if (!this.#ownerFiles) {
|
|
1267
|
+
this.#ownerFiles = new Map;
|
|
1268
|
+
for (const entry of this.#source.files) {
|
|
1269
|
+
const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
|
|
1270
|
+
const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
|
|
1271
|
+
if (files)
|
|
1272
|
+
files.push(entry);
|
|
1273
|
+
else if (fileOwner !== null)
|
|
1274
|
+
this.#ownerFiles.set(fileOwner, [entry]);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
return this.#ownerFiles.get(owner) ?? [];
|
|
1278
|
+
}
|
|
1279
|
+
#nearest(items, fileOf, near) {
|
|
1280
|
+
const isNear = (fileName) => {
|
|
1281
|
+
const relative = this.#source.relative(fileName);
|
|
1282
|
+
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
1283
|
+
};
|
|
1284
|
+
return items.find((item) => isNear(fileOf(item))) ?? items[0];
|
|
1285
|
+
}
|
|
1286
|
+
inOwner(fileName, owner) {
|
|
1287
|
+
const relative = this.#source.relative(fileName);
|
|
1288
|
+
return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
|
|
1289
|
+
}
|
|
1290
|
+
ownerOfSpecifier(fileName, specifier) {
|
|
1291
|
+
if (frameworkPattern.test(specifier))
|
|
1292
|
+
return frameworkOwner;
|
|
1293
|
+
const path = this.pathOfSpecifier(fileName, specifier);
|
|
1294
|
+
return path === null ? undefined : ownerOf(path) ?? undefined;
|
|
1295
|
+
}
|
|
1296
|
+
pathOfSpecifier(fileName, specifier) {
|
|
1297
|
+
const alias = specifier.match(aliasPattern);
|
|
1298
|
+
if (alias)
|
|
1299
|
+
return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
|
|
1300
|
+
if (!specifier.startsWith("."))
|
|
1301
|
+
return null;
|
|
1302
|
+
const segments = this.#source.relative(fileName).split("/").slice(0, -1);
|
|
1303
|
+
for (const segment of specifier.split("/"))
|
|
1304
|
+
if (segment === "..")
|
|
1305
|
+
segments.pop();
|
|
1306
|
+
else if (segment !== ".")
|
|
1307
|
+
segments.push(segment);
|
|
1308
|
+
return segments.join("/");
|
|
1309
|
+
}
|
|
1310
|
+
visible(target, from) {
|
|
1311
|
+
return !from || !target || target === from || target.startsWith("libs/");
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// pkgs/@akanjs/language/adaptorResolver.ts
|
|
1316
|
+
var maxModules = 64;
|
|
1317
|
+
|
|
1318
|
+
class AdaptorResolver {
|
|
1319
|
+
#source;
|
|
1320
|
+
#resolver;
|
|
1321
|
+
#sink;
|
|
1322
|
+
#adaptors = new Map;
|
|
1323
|
+
#plugs = new Map;
|
|
1324
|
+
#serviceFields = new Set;
|
|
1325
|
+
constructor(source, resolver, sink) {
|
|
1326
|
+
this.#source = source;
|
|
1327
|
+
this.#resolver = resolver;
|
|
1328
|
+
this.#sink = sink;
|
|
1329
|
+
}
|
|
1330
|
+
declare(facts) {
|
|
1331
|
+
const counts = new Map;
|
|
1332
|
+
for (const file of facts)
|
|
1333
|
+
for (const fact of file.adaptors) {
|
|
1334
|
+
const key = this.#sink.key("adaptor", file.owner, fact.name);
|
|
1335
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
1336
|
+
}
|
|
1337
|
+
const declared = facts.flatMap((file) => file.adaptors.map((fact) => {
|
|
1338
|
+
const key = this.#sink.key("adaptor", file.owner, fact.name);
|
|
1339
|
+
const id = (counts.get(key) ?? 0) > 1 ? this.#sink.key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
|
|
1340
|
+
const adaptor = { fact, owner: file.owner, fileName: file.fileName, id };
|
|
1341
|
+
this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], adaptor]);
|
|
1342
|
+
this.#nodeOf(adaptor);
|
|
1343
|
+
return { adaptor, file };
|
|
1344
|
+
}));
|
|
1345
|
+
for (const { adaptor, file } of declared)
|
|
1346
|
+
for (const plug of adaptor.fact.plugs)
|
|
1347
|
+
this.#sink.addEdge(adaptor.id, this.#idOf(plug.target, file), "plugs", plug.name, this.#sink.location(plug.span));
|
|
1348
|
+
for (const file of facts)
|
|
1349
|
+
for (const binding of file.adaptorBindings)
|
|
1350
|
+
this.#sink.addEdge(this.#idOf(binding.role, file), this.#idOf(binding.adaptor, file), "binds", file.owner, this.#sink.location(binding.span));
|
|
1351
|
+
}
|
|
1352
|
+
addService(refName, service, file) {
|
|
1353
|
+
for (const plug of service.plugs) {
|
|
1354
|
+
const key = `${refName}
|
|
1355
|
+
${plug.name}`;
|
|
1356
|
+
this.#plugs.set(key, [
|
|
1357
|
+
...this.#plugs.get(key) ?? [],
|
|
1358
|
+
{ owner: file.owner, target: this.#idOf(plug.target, file) }
|
|
1359
|
+
]);
|
|
1360
|
+
}
|
|
1361
|
+
for (const field of service.fields)
|
|
1362
|
+
this.#serviceFields.add(`${file.owner ?? ""}
|
|
1363
|
+
${refName}
|
|
1364
|
+
${field}`);
|
|
1365
|
+
}
|
|
1366
|
+
pluggedOf(owner, module2, field) {
|
|
1367
|
+
const plugs = this.#plugs.get(`${module2}
|
|
1368
|
+
${field}`) ?? [];
|
|
1369
|
+
const own = plugs.filter((plug) => plug.owner === owner);
|
|
1370
|
+
if (!own.length && this.#serviceFields.has(`${owner ?? ""}
|
|
1371
|
+
${module2}
|
|
1372
|
+
${field}`))
|
|
1373
|
+
return [];
|
|
1374
|
+
const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
|
|
1375
|
+
return [...new Set(inherited.map(({ target }) => target))];
|
|
1376
|
+
}
|
|
1377
|
+
#idOf(local, file) {
|
|
1378
|
+
const [head, member] = local.split(".");
|
|
1379
|
+
const imported = file.imports.find((item) => item.name === head);
|
|
1380
|
+
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1381
|
+
const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
|
|
1382
|
+
if (imported && path === null && !frameworkPattern.test(imported.from))
|
|
1383
|
+
return this.#packageAdaptorOf(name, imported.from);
|
|
1384
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1385
|
+
const declared = this.#adaptors.get(name) ?? [];
|
|
1386
|
+
const holder = from === undefined ? file.owner : from;
|
|
1387
|
+
const own = this.#adaptorIn(name, declared, holder, path, file.fileName);
|
|
1388
|
+
if (own && "from" in own)
|
|
1389
|
+
return this.#packageAdaptorOf(own.name, own.from);
|
|
1390
|
+
const found = own === undefined && from !== frameworkOwner ? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, holder)) : own;
|
|
1391
|
+
if (found)
|
|
1392
|
+
return found.id;
|
|
1393
|
+
const reexported = own === undefined && from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
|
|
1394
|
+
if (reexported)
|
|
1395
|
+
return this.#packageAdaptorOf(reexported.name, reexported.from);
|
|
1396
|
+
const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
|
|
1397
|
+
const detail = from === frameworkOwner ? [frameworkOwner] : [];
|
|
1398
|
+
return this.#sink.addNode(this.#sink.key("adaptor", owner, name), "adaptor", owner, name, null, detail).id;
|
|
1399
|
+
}
|
|
1400
|
+
#adaptorIn(name, declared, owner, path, fileName) {
|
|
1401
|
+
const own = declared.filter((adaptor) => adaptor.owner === owner);
|
|
1402
|
+
const [first] = own;
|
|
1403
|
+
if (path === null)
|
|
1404
|
+
return own.find((adaptor) => adaptor.fileName === fileName) ?? first;
|
|
1405
|
+
if (own.length === 1)
|
|
1406
|
+
return first;
|
|
1407
|
+
if (!this.#resolver.hasModule(path))
|
|
1408
|
+
return own.find((adaptor) => this.#source.relative(adaptor.fileName).startsWith(`${path}/`)) ?? first;
|
|
1409
|
+
const exported = this.#exportedAt(name, path, new Set).filter((item) => first || ("fact" in item));
|
|
1410
|
+
const [only] = exported;
|
|
1411
|
+
if (only)
|
|
1412
|
+
return exported.length === 1 ? only : null;
|
|
1413
|
+
return first ? null : undefined;
|
|
1414
|
+
}
|
|
1415
|
+
#exportedAt(name, path, seen) {
|
|
1416
|
+
const key = `${path}
|
|
1417
|
+
${name}`;
|
|
1418
|
+
if (seen.has(key) || seen.size >= maxModules)
|
|
1419
|
+
return [];
|
|
1420
|
+
seen.add(key);
|
|
1421
|
+
const here = (this.#adaptors.get(name) ?? []).filter((adaptor) => {
|
|
1422
|
+
const module2 = this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
|
|
1423
|
+
return module2 === path || module2 === `${path}/index`;
|
|
1424
|
+
});
|
|
1425
|
+
if (here.length)
|
|
1426
|
+
return here;
|
|
1427
|
+
const { named, stars } = this.#resolver.exportsAt(path, name);
|
|
1428
|
+
if (named?.from && this.#resolver.pathOfSpecifier(named.fileName, named.from) === null)
|
|
1429
|
+
return [{ name: named.name === "default" ? name : named.name, from: named.from }];
|
|
1430
|
+
if (named) {
|
|
1431
|
+
const target = named.from === null ? path : this.#resolver.pathOfSpecifier(named.fileName, named.from);
|
|
1432
|
+
return target === null ? [] : this.#exportedAt(named.name, target, seen);
|
|
1433
|
+
}
|
|
1434
|
+
return [...new Set(stars.flatMap((star) => this.#exportedAt(name, star, seen)))];
|
|
1435
|
+
}
|
|
1436
|
+
#packageAdaptorOf(name, specifier) {
|
|
1437
|
+
if (frameworkPattern.test(specifier))
|
|
1438
|
+
return this.#sink.addNode(this.#sink.key("adaptor", null, name), "adaptor", null, name, null, [frameworkOwner]).id;
|
|
1439
|
+
const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
|
1440
|
+
return this.#sink.addNode(this.#sink.key("adaptor", pkg, name), "adaptor", null, name, null, [pkg]).id;
|
|
1441
|
+
}
|
|
1442
|
+
#packageShimOf(owner, name, near) {
|
|
1443
|
+
const reexport = this.#resolver.reexportOf(owner, name, near);
|
|
1444
|
+
const [shim] = reexport?.site.imports ?? [];
|
|
1445
|
+
if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
|
|
1446
|
+
return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
|
|
1447
|
+
if (reexport || !near)
|
|
1448
|
+
return null;
|
|
1449
|
+
const { packages, paths } = this.#resolver.hasModule(near) ? this.#resolver.starExportsOf(owner, near) : { packages: this.#resolver.packagesUnder(owner, near), paths: [] };
|
|
1450
|
+
const [only] = packages;
|
|
1451
|
+
const isDeclared = this.#resolver.declares(owner, name) || paths.some((path) => this.#resolver.declaresAt(path, name));
|
|
1452
|
+
return packages.length === 1 && only && !isDeclared ? { name, from: only } : null;
|
|
1453
|
+
}
|
|
1454
|
+
#nodeOf({ fact, owner, fileName, id }) {
|
|
1455
|
+
const detail = fact.refName ? [fact.refName] : [];
|
|
1456
|
+
const node = this.#sink.addNode(id, "adaptor", owner, fact.name, fact.span, detail);
|
|
1457
|
+
if (fact.doc && !node.doc)
|
|
1458
|
+
node.doc = fact.doc;
|
|
1459
|
+
this.#sink.digest(node, this.#source.readText(fileName), fact.extent);
|
|
1460
|
+
return node.id;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
633
1463
|
|
|
634
1464
|
// pkgs/akanjs/common/trimSlashes.ts
|
|
635
1465
|
var trimTrailingSlashes = (value) => {
|
|
@@ -663,7 +1493,8 @@ var ROOT_LAYOUT_ROUTE_EXPORTS = new Set([
|
|
|
663
1493
|
"wsConnect",
|
|
664
1494
|
"layoutStyle",
|
|
665
1495
|
"viewport",
|
|
666
|
-
"McpAppRoot"
|
|
1496
|
+
"McpAppRoot",
|
|
1497
|
+
"LevelBoundary"
|
|
667
1498
|
]);
|
|
668
1499
|
var RESERVED_ROUTE_CONFIG_EXPORTS = new Set([...ROOT_LAYOUT_ROUTE_EXPORTS].filter((name) => name !== "default" && !/^[A-Z]/.test(name)));
|
|
669
1500
|
function parseRouteModuleKey(key) {
|
|
@@ -778,24 +1609,23 @@ rootLayout
|
|
|
778
1609
|
}
|
|
779
1610
|
declarationIn(source, name) {
|
|
780
1611
|
const ts = this.#ts;
|
|
1612
|
+
const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
|
|
1613
|
+
const [first] = overloads;
|
|
1614
|
+
const last = overloads.at(-1);
|
|
1615
|
+
if (first?.name && last)
|
|
1616
|
+
return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
|
|
781
1617
|
for (const statement of source.statements) {
|
|
782
|
-
if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
|
|
783
|
-
return { statement, declared: statement.name, initializer: null };
|
|
784
1618
|
if (!ts.isVariableStatement(statement))
|
|
785
1619
|
continue;
|
|
786
1620
|
const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
|
|
787
1621
|
if (declaration)
|
|
788
|
-
return { statement, declared: declaration.name,
|
|
1622
|
+
return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
|
|
789
1623
|
}
|
|
790
1624
|
return null;
|
|
791
1625
|
}
|
|
792
1626
|
#guardOf(source, exported) {
|
|
793
1627
|
const ts = this.#ts;
|
|
794
1628
|
let current = ts.isExportAssignment(exported) ? this.#reader.unwrap(exported.expression) : null;
|
|
795
|
-
if (current && ts.isIdentifier(current)) {
|
|
796
|
-
const initializer = this.declarationIn(source, current.text)?.initializer;
|
|
797
|
-
current = initializer ? this.#reader.unwrap(initializer) : null;
|
|
798
|
-
}
|
|
799
1629
|
while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
800
1630
|
const { name, expression } = current.expression;
|
|
801
1631
|
const [stage] = current.arguments;
|
|
@@ -807,9 +1637,20 @@ rootLayout
|
|
|
807
1637
|
}
|
|
808
1638
|
#callsIn(source, stage) {
|
|
809
1639
|
const ts = this.#ts;
|
|
810
|
-
const
|
|
1640
|
+
const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
|
|
1641
|
+
const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
|
|
1642
|
+
const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
|
|
811
1643
|
const calls = new Map;
|
|
812
|
-
const callOf = (
|
|
1644
|
+
const callOf = (callee, shadowed) => {
|
|
1645
|
+
if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
|
|
1646
|
+
const name = `${callee.expression.text}.${callee.name.text}`;
|
|
1647
|
+
if (!calls.has(name))
|
|
1648
|
+
calls.set(name, { name, local: null });
|
|
1649
|
+
return true;
|
|
1650
|
+
}
|
|
1651
|
+
if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
|
|
1652
|
+
return false;
|
|
1653
|
+
const name = callee.text;
|
|
813
1654
|
if (calls.has(name))
|
|
814
1655
|
return true;
|
|
815
1656
|
const declared = this.declarationIn(source, name);
|
|
@@ -819,23 +1660,71 @@ rootLayout
|
|
|
819
1660
|
name,
|
|
820
1661
|
local: declared && {
|
|
821
1662
|
span: this.#reader.spanOf(source, declared.declared),
|
|
822
|
-
extent:
|
|
1663
|
+
extent: declared.extent,
|
|
823
1664
|
doc: this.#reader.docOf(source, declared.statement)
|
|
824
1665
|
}
|
|
825
1666
|
});
|
|
826
1667
|
return true;
|
|
827
1668
|
};
|
|
828
|
-
const visit = (node) => {
|
|
829
|
-
|
|
1669
|
+
const visit = (node, outer) => {
|
|
1670
|
+
const shadowed = this.#scopeOf(node, outer);
|
|
1671
|
+
if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
|
|
830
1672
|
return;
|
|
831
|
-
ts.forEachChild(node, visit);
|
|
1673
|
+
ts.forEachChild(node, (child) => visit(child, shadowed));
|
|
832
1674
|
};
|
|
833
1675
|
if (ts.isIdentifier(stage))
|
|
834
|
-
callOf(stage
|
|
1676
|
+
callOf(stage, new Set);
|
|
835
1677
|
else
|
|
836
|
-
visit(stage);
|
|
1678
|
+
visit(stage, new Set);
|
|
837
1679
|
return [...calls.values()];
|
|
838
1680
|
}
|
|
1681
|
+
#scopeOf(node, outer) {
|
|
1682
|
+
const ts = this.#ts;
|
|
1683
|
+
const names = [];
|
|
1684
|
+
const bind = (name) => {
|
|
1685
|
+
if (ts.isIdentifier(name))
|
|
1686
|
+
names.push(name.text);
|
|
1687
|
+
else
|
|
1688
|
+
for (const element of name.elements)
|
|
1689
|
+
if (!ts.isOmittedExpression(element))
|
|
1690
|
+
bind(element.name);
|
|
1691
|
+
};
|
|
1692
|
+
const declare = (statement) => {
|
|
1693
|
+
if (ts.isVariableStatement(statement))
|
|
1694
|
+
for (const declaration of statement.declarationList.declarations)
|
|
1695
|
+
bind(declaration.name);
|
|
1696
|
+
else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
|
|
1697
|
+
names.push(statement.name.text);
|
|
1698
|
+
};
|
|
1699
|
+
const hoist = (child) => {
|
|
1700
|
+
if (ts.isFunctionLike(child) || ts.isClassLike(child))
|
|
1701
|
+
return;
|
|
1702
|
+
if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
|
|
1703
|
+
for (const declaration of child.declarations)
|
|
1704
|
+
bind(declaration.name);
|
|
1705
|
+
ts.forEachChild(child, hoist);
|
|
1706
|
+
};
|
|
1707
|
+
if (ts.isFunctionLike(node)) {
|
|
1708
|
+
for (const parameter of node.parameters)
|
|
1709
|
+
bind(parameter.name);
|
|
1710
|
+
if (ts.isFunctionExpression(node) && node.name)
|
|
1711
|
+
names.push(node.name.text);
|
|
1712
|
+
if ("body" in node && node.body)
|
|
1713
|
+
ts.forEachChild(node.body, hoist);
|
|
1714
|
+
} else if (ts.isClassExpression(node) && node.name)
|
|
1715
|
+
names.push(node.name.text);
|
|
1716
|
+
else if (ts.isBlock(node))
|
|
1717
|
+
node.statements.forEach(declare);
|
|
1718
|
+
else if (ts.isCaseBlock(node))
|
|
1719
|
+
for (const clause of node.clauses)
|
|
1720
|
+
clause.statements.forEach(declare);
|
|
1721
|
+
else if (ts.isCatchClause(node) && node.variableDeclaration)
|
|
1722
|
+
bind(node.variableDeclaration.name);
|
|
1723
|
+
else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
|
|
1724
|
+
for (const declaration of node.initializer.declarations)
|
|
1725
|
+
bind(declaration.name);
|
|
1726
|
+
return names.length ? new Set([...outer, ...names]) : outer;
|
|
1727
|
+
}
|
|
839
1728
|
}
|
|
840
1729
|
|
|
841
1730
|
// pkgs/@akanjs/language/akanGraph.ts
|
|
@@ -851,10 +1740,6 @@ var builtinGuards = {
|
|
|
851
1740
|
Public: { scope: "account", doc: "Passes every caller, signed in or not." },
|
|
852
1741
|
None: { scope: "account", doc: "Refuses every caller." }
|
|
853
1742
|
};
|
|
854
|
-
var frameworkOwner = "akanjs";
|
|
855
|
-
var frameworkPattern = /^akanjs(?:\/|$)/;
|
|
856
|
-
var aliasPattern = /^@(apps|libs)\/([^/]+)/;
|
|
857
|
-
var maxMapDepth = 4;
|
|
858
1743
|
|
|
859
1744
|
class GraphBuilder {
|
|
860
1745
|
#source;
|
|
@@ -862,16 +1747,21 @@ class GraphBuilder {
|
|
|
862
1747
|
#edges = new Map;
|
|
863
1748
|
#contexts = new Map;
|
|
864
1749
|
#members = new Map;
|
|
865
|
-
#guards = new Map;
|
|
866
|
-
#mapEntries = new Map;
|
|
867
1750
|
#routeFiles = new Map;
|
|
868
|
-
#declarations = new Map;
|
|
869
1751
|
#routes;
|
|
870
|
-
#
|
|
871
|
-
#
|
|
1752
|
+
#resolver;
|
|
1753
|
+
#adaptors;
|
|
872
1754
|
constructor(source) {
|
|
873
1755
|
this.#source = source;
|
|
874
1756
|
this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
|
|
1757
|
+
this.#resolver = new GuardResolver(source, this.#routes);
|
|
1758
|
+
this.#adaptors = new AdaptorResolver(source, this.#resolver, {
|
|
1759
|
+
key: (kind, owner, scope) => this.#key(kind, owner, scope),
|
|
1760
|
+
addNode: (id, kind, owner, name, span, detail) => this.#addNode(id, kind, owner, null, name, span, detail),
|
|
1761
|
+
addEdge: (from, to, kind, label, at) => this.#addEdge(from, to, kind, label, at),
|
|
1762
|
+
digest: (node, text, extent) => this.#digest(node, text, extent),
|
|
1763
|
+
location: (span) => this.#location(span)
|
|
1764
|
+
});
|
|
875
1765
|
}
|
|
876
1766
|
build() {
|
|
877
1767
|
const { facts } = this.#source;
|
|
@@ -883,17 +1773,9 @@ class GraphBuilder {
|
|
|
883
1773
|
...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
|
|
884
1774
|
];
|
|
885
1775
|
const typed = [];
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
|
|
890
|
-
this.#adaptorNodeOf(declared);
|
|
891
|
-
}
|
|
892
|
-
for (const file of facts)
|
|
893
|
-
for (const fact of file.adaptors)
|
|
894
|
-
for (const plug of fact.plugs)
|
|
895
|
-
this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
|
|
896
|
-
this.#declareGuards(facts);
|
|
1776
|
+
this.#adaptors.declare(facts);
|
|
1777
|
+
for (const guard of this.#resolver.declare(facts))
|
|
1778
|
+
this.#guardNodeOf(guard);
|
|
897
1779
|
for (const file of facts) {
|
|
898
1780
|
const { owner } = file;
|
|
899
1781
|
const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
|
|
@@ -924,10 +1806,7 @@ class GraphBuilder {
|
|
|
924
1806
|
}
|
|
925
1807
|
for (const method of service.methods)
|
|
926
1808
|
this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
|
|
927
|
-
|
|
928
|
-
this.#plugs.set(`${owner ?? ""}
|
|
929
|
-
${refName}
|
|
930
|
-
${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
1809
|
+
this.#adaptors.addService(refName, service, file);
|
|
931
1810
|
}
|
|
932
1811
|
for (const filter of file.filters) {
|
|
933
1812
|
if (!filter.name.endsWith("Filter"))
|
|
@@ -937,31 +1816,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
|
937
1816
|
for (const query of filter.queries)
|
|
938
1817
|
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);
|
|
939
1818
|
}
|
|
940
|
-
|
|
941
|
-
const refName = slice.model;
|
|
942
|
-
if (!refName)
|
|
943
|
-
continue;
|
|
944
|
-
const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards);
|
|
945
|
-
for (const guard of slice.guards)
|
|
946
|
-
this.#guard(fallback, guard, file);
|
|
947
|
-
for (const key of slice.keys) {
|
|
948
|
-
const detail = [...key.guards, ...key.live ? ["live"] : []];
|
|
949
|
-
const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
950
|
-
for (const guard of key.guards)
|
|
951
|
-
this.#guard(node, guard, file);
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
for (const endpoint of file.endpoints) {
|
|
955
|
-
const refName = endpoint.model;
|
|
956
|
-
if (!refName)
|
|
957
|
-
continue;
|
|
958
|
-
for (const key of endpoint.keys) {
|
|
959
|
-
const detail = [...key.verb ? [key.verb] : [], ...key.guards];
|
|
960
|
-
const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
961
|
-
for (const guard of key.guards)
|
|
962
|
-
this.#guard(node, guard, file);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
1819
|
+
this.#addGuarded(file);
|
|
965
1820
|
for (const internal of file.internals)
|
|
966
1821
|
for (const key of internal.keys)
|
|
967
1822
|
if (internal.model && key.type !== "resolveField")
|
|
@@ -977,7 +1832,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
|
|
|
977
1832
|
types.set(node.label, [...types.get(node.label) ?? [], node]);
|
|
978
1833
|
for (const node of typed)
|
|
979
1834
|
for (const field of node.fields ?? []) {
|
|
980
|
-
const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
|
|
1835
|
+
const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
|
|
981
1836
|
field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
|
|
982
1837
|
if (field.ref)
|
|
983
1838
|
this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
|
|
@@ -1059,61 +1914,52 @@ ${label ?? ""}`;
|
|
|
1059
1914
|
else
|
|
1060
1915
|
this.#edges.set(key, { from, to, kind, label, count: 1, at });
|
|
1061
1916
|
}
|
|
1062
|
-
#
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
for (const
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
};
|
|
1080
|
-
const madeBy = (factory, fileName, extent) => {
|
|
1081
|
-
const parts = [{ fileName, extent }];
|
|
1082
|
-
const seen = new Set;
|
|
1083
|
-
for (let current = factory;current && !seen.has(current); current = sameFile(current, current.fact.via)) {
|
|
1084
|
-
seen.add(current);
|
|
1085
|
-
parts.push({ fileName: current.fileName, extent: current.fact.extent });
|
|
1086
|
-
}
|
|
1087
|
-
return parts;
|
|
1088
|
-
};
|
|
1089
|
-
for (const file of facts) {
|
|
1090
|
-
const { owner, fileName } = file;
|
|
1091
|
-
for (const { name, span, extent, scope, doc, factory: callee } of file.guards) {
|
|
1092
|
-
const factory = callee ? factories.get(callee)?.find((other) => other.fileName === fileName) : undefined;
|
|
1093
|
-
declare({ name, owner, span, scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
|
|
1917
|
+
#addGuarded(file) {
|
|
1918
|
+
const { owner } = file;
|
|
1919
|
+
for (const slice of file.slices) {
|
|
1920
|
+
const refName = slice.model;
|
|
1921
|
+
if (!refName)
|
|
1922
|
+
continue;
|
|
1923
|
+
const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
|
|
1924
|
+
for (const guard of slice.guards)
|
|
1925
|
+
this.#guard(fallback, guard, file);
|
|
1926
|
+
for (const key of slice.keys) {
|
|
1927
|
+
const detail = [
|
|
1928
|
+
...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
|
|
1929
|
+
...key.live ? ["live"] : []
|
|
1930
|
+
];
|
|
1931
|
+
const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
1932
|
+
for (const guard of key.guards)
|
|
1933
|
+
this.#guard(node, guard, file);
|
|
1094
1934
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1935
|
+
}
|
|
1936
|
+
for (const endpoint of file.endpoints) {
|
|
1937
|
+
const refName = endpoint.model;
|
|
1938
|
+
if (!refName)
|
|
1939
|
+
continue;
|
|
1940
|
+
for (const key of endpoint.keys) {
|
|
1941
|
+
const detail = [
|
|
1942
|
+
...key.verb ? [key.verb] : [],
|
|
1943
|
+
...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
|
|
1944
|
+
];
|
|
1945
|
+
const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
|
|
1946
|
+
for (const guard of key.guards)
|
|
1947
|
+
this.#guard(node, guard, file);
|
|
1104
1948
|
}
|
|
1105
1949
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1950
|
+
}
|
|
1951
|
+
#guard(node, guard, file) {
|
|
1952
|
+
const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
|
|
1953
|
+
if (!local || local === "?")
|
|
1954
|
+
return;
|
|
1955
|
+
const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
|
|
1956
|
+
if (id)
|
|
1957
|
+
this.#addEdge(node.id, id, "guard", role, node.at);
|
|
1114
1958
|
}
|
|
1115
1959
|
#guardIdOf(local, site) {
|
|
1116
|
-
const found = this.#
|
|
1960
|
+
const found = this.#resolver.find(local, site);
|
|
1961
|
+
if ("external" in found)
|
|
1962
|
+
return null;
|
|
1117
1963
|
if ("guard" in found)
|
|
1118
1964
|
return this.#guardNodeOf(found.guard);
|
|
1119
1965
|
const { name, from } = found;
|
|
@@ -1126,99 +1972,18 @@ ${label ?? ""}`;
|
|
|
1126
1972
|
node.doc = builtin.doc;
|
|
1127
1973
|
return node.id;
|
|
1128
1974
|
}
|
|
1129
|
-
#
|
|
1130
|
-
const
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
const
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
}
|
|
1142
|
-
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1143
|
-
const declared = this.#guards.get(name) ?? [];
|
|
1144
|
-
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));
|
|
1145
|
-
return guard ? { guard } : { name, from };
|
|
1146
|
-
}
|
|
1147
|
-
#mapEntriesOf(objectName, member, fileName, owner) {
|
|
1148
|
-
const key = `${objectName}
|
|
1149
|
-
${member}
|
|
1150
|
-
${fileName ?? ""}
|
|
1151
|
-
${owner ?? ""}`;
|
|
1152
|
-
const cached = this.#mapEntries.get(key);
|
|
1153
|
-
if (cached)
|
|
1154
|
-
return cached;
|
|
1155
|
-
const { ts, reader } = this.#source;
|
|
1156
|
-
const entries = [];
|
|
1157
|
-
const fileNames = [
|
|
1158
|
-
...fileName ? [fileName] : [],
|
|
1159
|
-
...this.#source.files.filter(([other, words]) => other !== fileName && owner !== null && !testFilePattern.test(other) && words.includes(`
|
|
1160
|
-
${objectName}
|
|
1161
|
-
`) && words.includes(`
|
|
1162
|
-
${member}
|
|
1163
|
-
`) && this.#source.ownerOf(other) === owner).map(([other]) => other)
|
|
1164
|
-
];
|
|
1165
|
-
for (const holder of fileNames) {
|
|
1166
|
-
const text = this.#source.readText(holder);
|
|
1167
|
-
if (text === undefined)
|
|
1168
|
-
continue;
|
|
1169
|
-
const source = this.#source.parse(holder, text);
|
|
1170
|
-
const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
|
|
1171
|
-
for (const statement of source.statements) {
|
|
1172
|
-
if (!ts.isVariableStatement(statement))
|
|
1173
|
-
continue;
|
|
1174
|
-
const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
|
|
1175
|
-
const object = map && reader.unwrap(map);
|
|
1176
|
-
if (!object || !ts.isObjectLiteralExpression(object))
|
|
1177
|
-
continue;
|
|
1178
|
-
for (const property of object.properties) {
|
|
1179
|
-
if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
|
|
1180
|
-
entries.push({ name: member, site });
|
|
1181
|
-
if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
|
|
1182
|
-
continue;
|
|
1183
|
-
const value = reader.unwrap(property.initializer);
|
|
1184
|
-
if (ts.isIdentifier(value))
|
|
1185
|
-
entries.push({ name: value.text, site });
|
|
1186
|
-
else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
|
|
1187
|
-
entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
this.#mapEntries.set(key, entries);
|
|
1192
|
-
return entries;
|
|
1193
|
-
}
|
|
1194
|
-
#declarationOf(owner, name, near) {
|
|
1195
|
-
const key = `${owner}
|
|
1196
|
-
${name}
|
|
1197
|
-
${near ?? ""}`;
|
|
1198
|
-
const cached = this.#declarations.get(key);
|
|
1199
|
-
if (cached !== undefined)
|
|
1200
|
-
return cached;
|
|
1201
|
-
const isNear = (fileName) => {
|
|
1202
|
-
const relative = this.#source.relative(fileName);
|
|
1203
|
-
return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
|
|
1204
|
-
};
|
|
1205
|
-
const candidates = this.#source.files.filter(([fileName, words]) => !testFilePattern.test(fileName) && words.includes(`
|
|
1206
|
-
${name}
|
|
1207
|
-
`) && this.#source.ownerOf(fileName) === owner).map(([fileName]) => fileName);
|
|
1208
|
-
let found = null;
|
|
1209
|
-
for (const fileName of [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))]) {
|
|
1210
|
-
const text = this.#source.readText(fileName);
|
|
1211
|
-
if (text === undefined)
|
|
1212
|
-
continue;
|
|
1213
|
-
const source = this.#source.parse(fileName, text);
|
|
1214
|
-
const declared = this.#routes.declarationIn(source, name);
|
|
1215
|
-
if (!declared)
|
|
1216
|
-
continue;
|
|
1217
|
-
found = { source, statement: declared.statement, declared: declared.declared };
|
|
1218
|
-
break;
|
|
1219
|
-
}
|
|
1220
|
-
this.#declarations.set(key, found);
|
|
1221
|
-
return found;
|
|
1975
|
+
#madeInPlaceIdOf(name, file) {
|
|
1976
|
+
const made = this.#resolver.madeInPlace(file.fileName, name);
|
|
1977
|
+
if (made)
|
|
1978
|
+
return this.#guardNodeOf(made);
|
|
1979
|
+
const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
|
|
1980
|
+
const imported = file.imports.find((item) => item.name === head);
|
|
1981
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
|
|
1982
|
+
if (from === undefined || from === frameworkOwner)
|
|
1983
|
+
return null;
|
|
1984
|
+
const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
|
|
1985
|
+
const label = guardLabelOf(name, file.guardLabels);
|
|
1986
|
+
return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
|
|
1222
1987
|
}
|
|
1223
1988
|
#addRoutes() {
|
|
1224
1989
|
const routes = [];
|
|
@@ -1271,22 +2036,23 @@ ${dir}`, node);
|
|
|
1271
2036
|
}
|
|
1272
2037
|
#routeGuardIdOf({ name: local, local: declared }, fact, text) {
|
|
1273
2038
|
if (declared) {
|
|
1274
|
-
const
|
|
1275
|
-
const id = this.#key("guard", fact.owner, `${file}#${local}`);
|
|
2039
|
+
const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
|
|
1276
2040
|
return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
|
|
1277
2041
|
}
|
|
1278
|
-
const
|
|
1279
|
-
|
|
2042
|
+
const [head = local, member] = local.split(".");
|
|
2043
|
+
const imported = fact.imports.find((item) => item.name === head);
|
|
2044
|
+
if (!imported || member && imported.imported !== "*")
|
|
1280
2045
|
return null;
|
|
1281
|
-
const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
|
|
1282
|
-
const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
|
|
2046
|
+
const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
|
|
2047
|
+
const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
|
|
1283
2048
|
if (!from || from === frameworkOwner)
|
|
1284
2049
|
return null;
|
|
1285
|
-
const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
|
|
2050
|
+
const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
|
|
1286
2051
|
if (!found)
|
|
1287
2052
|
return null;
|
|
1288
2053
|
const { reader } = this.#source;
|
|
1289
|
-
|
|
2054
|
+
const owner = this.#source.ownerOf(found.source.fileName) ?? from;
|
|
2055
|
+
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);
|
|
1290
2056
|
}
|
|
1291
2057
|
#routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
|
|
1292
2058
|
const isNew = !this.#nodes.has(id);
|
|
@@ -1296,60 +2062,22 @@ ${dir}`, node);
|
|
|
1296
2062
|
this.#digest(node, text, extent);
|
|
1297
2063
|
return node.id;
|
|
1298
2064
|
}
|
|
1299
|
-
#
|
|
1300
|
-
const
|
|
1301
|
-
const
|
|
1302
|
-
const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1303
|
-
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1304
|
-
const declared = this.#adaptors.get(name) ?? [];
|
|
1305
|
-
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)));
|
|
1306
|
-
if (found)
|
|
1307
|
-
return this.#adaptorNodeOf(found);
|
|
1308
|
-
const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
|
|
1309
|
-
const detail = from === frameworkOwner ? [frameworkOwner] : [];
|
|
1310
|
-
return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
|
|
1311
|
-
}
|
|
1312
|
-
#adaptorNodeOf({ fact, owner, fileName }) {
|
|
1313
|
-
const id = this.#key("adaptor", owner, fact.name);
|
|
1314
|
-
const detail = fact.refName ? [fact.refName] : [];
|
|
1315
|
-
const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
|
|
1316
|
-
if (fact.doc && !node.doc)
|
|
1317
|
-
node.doc = fact.doc;
|
|
1318
|
-
this.#digest(node, this.#source.readText(fileName), fact.extent);
|
|
1319
|
-
return node.id;
|
|
1320
|
-
}
|
|
1321
|
-
#guardNodeOf({ name, owner, span, scope, doc, parts }) {
|
|
1322
|
-
const node = this.#addNode(this.#key("guard", owner, name), "guard", owner, null, name, span, scope ? [scope] : []);
|
|
2065
|
+
#guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
|
|
2066
|
+
const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
|
|
2067
|
+
const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
|
|
1323
2068
|
if (doc && !node.doc)
|
|
1324
2069
|
node.doc = doc;
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
2070
|
+
if (node.digest)
|
|
2071
|
+
return node.id;
|
|
2072
|
+
const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
|
|
2073
|
+
if (!digest)
|
|
2074
|
+
return node.id;
|
|
2075
|
+
node.digest = digest;
|
|
2076
|
+
const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
|
|
2077
|
+
if (sources.length)
|
|
2078
|
+
node.sources = sources;
|
|
1331
2079
|
return node.id;
|
|
1332
2080
|
}
|
|
1333
|
-
#ownerOfSpecifier(fileName, specifier) {
|
|
1334
|
-
if (frameworkPattern.test(specifier))
|
|
1335
|
-
return frameworkOwner;
|
|
1336
|
-
const path = this.#pathOfSpecifier(fileName, specifier);
|
|
1337
|
-
return path === null ? undefined : ownerOf(path) ?? undefined;
|
|
1338
|
-
}
|
|
1339
|
-
#pathOfSpecifier(fileName, specifier) {
|
|
1340
|
-
const alias = specifier.match(aliasPattern);
|
|
1341
|
-
if (alias)
|
|
1342
|
-
return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
|
|
1343
|
-
if (!specifier.startsWith("."))
|
|
1344
|
-
return null;
|
|
1345
|
-
const segments = this.#source.relative(fileName).split("/").slice(0, -1);
|
|
1346
|
-
for (const segment of specifier.split("/"))
|
|
1347
|
-
if (segment === "..")
|
|
1348
|
-
segments.pop();
|
|
1349
|
-
else if (segment !== ".")
|
|
1350
|
-
segments.push(segment);
|
|
1351
|
-
return segments.join("/");
|
|
1352
|
-
}
|
|
1353
2081
|
#moduleDirOf(fileName) {
|
|
1354
2082
|
const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
|
|
1355
2083
|
return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
|
|
@@ -1385,20 +2113,13 @@ ${dir}`, node);
|
|
|
1385
2113
|
visit(constant);
|
|
1386
2114
|
return [...fields.values()];
|
|
1387
2115
|
}
|
|
1388
|
-
#visible(target, from) {
|
|
1389
|
-
return !from || !target || target === from || target.startsWith("libs/");
|
|
1390
|
-
}
|
|
1391
|
-
#soleVisible(declared, from) {
|
|
1392
|
-
const visible = declared.filter((other) => this.#visible(other.owner, from));
|
|
1393
|
-
return visible.length === 1 ? visible[0] : undefined;
|
|
1394
|
-
}
|
|
1395
2116
|
#targetNodeOf({ family, owner, refName, sourceKey }) {
|
|
1396
2117
|
const isDefault = /^[A-Z]/.test(sourceKey);
|
|
1397
2118
|
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;
|
|
1398
2119
|
return id && this.#nodes.has(id) ? id : null;
|
|
1399
2120
|
}
|
|
1400
2121
|
#membersNamed(kind, name, owner, refName) {
|
|
1401
|
-
const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
|
|
2122
|
+
const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
|
|
1402
2123
|
const own = nodes.filter((node) => node.owner === owner);
|
|
1403
2124
|
return (own.length ? own : nodes).map((node) => node.id);
|
|
1404
2125
|
}
|
|
@@ -1443,10 +2164,8 @@ st
|
|
|
1443
2164
|
visit(source);
|
|
1444
2165
|
}
|
|
1445
2166
|
#link(fileName, source, identifier, chain, context, owner) {
|
|
1446
|
-
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#
|
|
1447
|
-
|
|
1448
|
-
${chain[1]}`) : undefined;
|
|
1449
|
-
const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
2167
|
+
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#adaptors.pluggedOf(context.owner, context.module, chain[1]) : [];
|
|
2168
|
+
const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
1450
2169
|
const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
|
|
1451
2170
|
if (!targets.length)
|
|
1452
2171
|
return;
|
|
@@ -1752,6 +2471,10 @@ var endpointNames = (key) => {
|
|
|
1752
2471
|
};
|
|
1753
2472
|
|
|
1754
2473
|
// pkgs/@akanjs/language/nameTable.ts
|
|
2474
|
+
var guardDetailOf = (facts, guards) => {
|
|
2475
|
+
const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
|
|
2476
|
+
return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
|
|
2477
|
+
};
|
|
1755
2478
|
var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
|
|
1756
2479
|
|
|
1757
2480
|
class NameTable {
|
|
@@ -1851,7 +2574,7 @@ class NameTable {
|
|
|
1851
2574
|
role: "sig",
|
|
1852
2575
|
gate: "sig",
|
|
1853
2576
|
label: sliceClass.name,
|
|
1854
|
-
detail:
|
|
2577
|
+
detail: guardDetailOf(facts, sliceClass.guards)
|
|
1855
2578
|
});
|
|
1856
2579
|
if (modelDoc)
|
|
1857
2580
|
this.#add({
|
|
@@ -1916,7 +2639,7 @@ class NameTable {
|
|
|
1916
2639
|
sourceKey: slice.name,
|
|
1917
2640
|
span: slice.span,
|
|
1918
2641
|
label: `slice ${refName} (default)`,
|
|
1919
|
-
detail:
|
|
2642
|
+
detail: guardDetailOf(facts, slice.guards)
|
|
1920
2643
|
};
|
|
1921
2644
|
this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
|
|
1922
2645
|
for (const key of slice.keys)
|
|
@@ -1926,7 +2649,7 @@ class NameTable {
|
|
|
1926
2649
|
sourceKey: key.name,
|
|
1927
2650
|
span: key.span,
|
|
1928
2651
|
label: `slice ${key.name}`,
|
|
1929
|
-
detail: [...
|
|
2652
|
+
detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
|
|
1930
2653
|
}, refName, key.name, modelNameSet, slice.name);
|
|
1931
2654
|
}
|
|
1932
2655
|
if (filterClass) {
|
|
@@ -1983,7 +2706,7 @@ class NameTable {
|
|
|
1983
2706
|
role: key.verb === "pubsub" ? "subscribe" : "listen",
|
|
1984
2707
|
gate: null,
|
|
1985
2708
|
label: `${key.verb} ${key.name}`,
|
|
1986
|
-
detail:
|
|
2709
|
+
detail: guardDetailOf(facts, key.guards)
|
|
1987
2710
|
});
|
|
1988
2711
|
}
|
|
1989
2712
|
}
|
|
@@ -2040,15 +2763,13 @@ var sourcePattern = /\.(constant|document|service|signal|store)\.ts$/;
|
|
|
2040
2763
|
var dictionaryPattern = /\.dictionary\.ts$/;
|
|
2041
2764
|
var srvkitPattern = /\/srvkit\//;
|
|
2042
2765
|
var wordPattern = /[A-Za-z_$][\w$]*/g;
|
|
2043
|
-
var
|
|
2044
|
-
var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
|
|
2766
|
+
var identifierPattern2 = /^[A-Za-z_$][\w$]*$/;
|
|
2045
2767
|
var stateReceivers = new Set(["use", "sel", "get", "do"]);
|
|
2046
2768
|
var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
|
|
2047
2769
|
var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
|
|
2048
2770
|
var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
|
|
2049
2771
|
var parseCacheSize = 64;
|
|
2050
2772
|
var pathOf = (fileName) => fileName.replace(/\\/g, "/");
|
|
2051
|
-
var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
|
|
2052
2773
|
var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
|
|
2053
2774
|
|
|
2054
2775
|
class AkanIndex {
|
|
@@ -2151,7 +2872,7 @@ ${[...new Set(text.match(wordPattern))].join(`
|
|
|
2151
2872
|
}
|
|
2152
2873
|
renameAt(givenName, offset, newKey, readText) {
|
|
2153
2874
|
const sources = this.#sourceEntriesAt(pathOf(givenName), offset).filter((entry) => entry.renamed);
|
|
2154
|
-
if (!sources.length || !
|
|
2875
|
+
if (!sources.length || !identifierPattern2.test(newKey))
|
|
2155
2876
|
return null;
|
|
2156
2877
|
return [
|
|
2157
2878
|
{ span: sources[0].span, newText: newKey },
|
|
@@ -2179,6 +2900,8 @@ canPass
|
|
|
2179
2900
|
adapt
|
|
2180
2901
|
`) || words.includes(`
|
|
2181
2902
|
dangerouslyAdapt
|
|
2903
|
+
`) || words.includes(`
|
|
2904
|
+
applyAdaptor
|
|
2182
2905
|
`);
|
|
2183
2906
|
}
|
|
2184
2907
|
static #sourceKeyOf({ family, sourceKey, label, detail, span }) {
|
|
@@ -2859,6 +3582,11 @@ ${union.name}`)));
|
|
|
2859
3582
|
const field = this.#memberOf(this.#classOf(receiver), receiver.name.text);
|
|
2860
3583
|
return field && ts.isPropertyDeclaration(field) ? this.#namesIn(field.type) : new Set;
|
|
2861
3584
|
}
|
|
3585
|
+
if (ts.isCallExpression(receiver) && ts.isPropertyAccessExpression(receiver.expression) && receiver.expression.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
3586
|
+
const field = this.#memberOf(this.#classOf(receiver), receiver.expression.name.text);
|
|
3587
|
+
const type = field && ts.isPropertyDeclaration(field) ? field.type : undefined;
|
|
3588
|
+
return type && ts.isFunctionTypeNode(type) ? this.#namesIn(type.type) : new Set;
|
|
3589
|
+
}
|
|
2862
3590
|
return this.#declaredTypeNames(receiver) ?? new Set;
|
|
2863
3591
|
}
|
|
2864
3592
|
#memberName(name) {
|