@saptools/service-flow 0.1.70 → 0.1.72
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/CHANGELOG.md +13 -0
- package/README.md +9 -5
- package/TECHNICAL-NOTE.md +13 -0
- package/dist/{chunk-GSLFY6J2.js → chunk-Z6D433R5.js} +8696 -6476
- package/dist/chunk-Z6D433R5.js.map +1 -0
- package/dist/cli.js +685 -142
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +53 -14
- package/dist/index.js +2 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/doctor-event-quality.ts +371 -0
- package/src/cli/doctor.ts +4 -6
- package/src/cli.ts +1 -1
- package/src/db/call-fact-repository.ts +6 -3
- package/src/db/current-fact-semantics.ts +5 -5
- package/src/db/event-fact-semantics.ts +347 -0
- package/src/db/event-surface-invalidation.ts +38 -0
- package/src/db/fact-json-inventory.ts +16 -0
- package/src/db/migrations.ts +12 -1
- package/src/db/package-target-invalidation.ts +79 -0
- package/src/db/repositories.ts +28 -2
- package/src/db/schema-structure.ts +41 -1
- package/src/db/schema.ts +6 -2
- package/src/indexer/repository-indexer.ts +45 -6
- package/src/linker/cross-repo-linker.ts +25 -3
- package/src/linker/event-environment-link.ts +211 -0
- package/src/linker/event-shape-candidate-linker.ts +161 -0
- package/src/linker/event-subscription-handler-linker.ts +123 -19
- package/src/linker/event-template-link.ts +40 -6
- package/src/linker/package-event-constant-resolver.ts +298 -0
- package/src/output/table-output.ts +13 -1
- package/src/parsers/decorator-parser.ts +9 -53
- package/src/parsers/environment-declarations.ts +327 -0
- package/src/parsers/event-call-analysis.ts +242 -0
- package/src/parsers/event-environment-reference.ts +231 -0
- package/src/parsers/event-loop-registration.ts +132 -0
- package/src/parsers/event-name-import-resolution.ts +243 -0
- package/src/parsers/event-receiver-analysis.ts +404 -0
- package/src/parsers/event-subscription-facts.ts +4 -0
- package/src/parsers/generated-constants-parser.ts +80 -14
- package/src/parsers/outbound-call-classifier.ts +27 -124
- package/src/parsers/outbound-call-parser.ts +13 -1
- package/src/parsers/outbound-expression-analysis.ts +2 -2
- package/src/parsers/stable-local-value.ts +30 -9
- package/src/parsers/string-constant-lookups.ts +358 -0
- package/src/trace/event-runtime-resolution.ts +24 -3
- package/src/trace/event-shape-candidate-trace.ts +172 -0
- package/src/trace/event-subscriber-traversal.ts +19 -7
- package/src/trace/evidence.ts +7 -0
- package/src/trace/trace-scope-execution.ts +21 -29
- package/src/types.ts +17 -1
- package/src/utils/event-skeleton.ts +207 -0
- package/src/version.ts +1 -1
- package/dist/chunk-GSLFY6J2.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -8,14 +8,20 @@ import {
|
|
|
8
8
|
bindingSite,
|
|
9
9
|
classifyODataPathIntent,
|
|
10
10
|
classifyOutboundCallsInSource,
|
|
11
|
+
collectEnvironmentDeclarations,
|
|
12
|
+
collectStringConstantLookups,
|
|
11
13
|
collectSymbolImportBindings,
|
|
12
14
|
compactTrace,
|
|
13
15
|
containsSupportedOutboundCall,
|
|
14
16
|
createBindingLexicalIndex,
|
|
17
|
+
createEventEnvironmentReferenceResolver,
|
|
18
|
+
createImportedEventNameResolver,
|
|
15
19
|
derivedMemberImportReference,
|
|
16
20
|
discoverRepositories,
|
|
21
|
+
emptyEnvironmentDeclarations,
|
|
17
22
|
executableSymbolCandidates,
|
|
18
23
|
factLifecycleDiagnostic,
|
|
24
|
+
generatedConstantFacts,
|
|
19
25
|
identifierMatchesDeclaration,
|
|
20
26
|
implementationHintSuggestions,
|
|
21
27
|
insertCalls,
|
|
@@ -42,13 +48,15 @@ import {
|
|
|
42
48
|
projectBoundedInOrder,
|
|
43
49
|
recordPreparedSnapshotFailure,
|
|
44
50
|
redactValue,
|
|
51
|
+
resolveBinding,
|
|
45
52
|
selectCallOwner,
|
|
46
53
|
selectVisibleBinding,
|
|
47
54
|
selectorRepoAmbiguousDiagnostic,
|
|
55
|
+
sha256Text,
|
|
48
56
|
stableLocalValueReference,
|
|
49
57
|
symbolImportReference,
|
|
50
58
|
trace
|
|
51
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-Z6D433R5.js";
|
|
52
60
|
|
|
53
61
|
// src/cli.ts
|
|
54
62
|
import { Command, Option } from "commander";
|
|
@@ -250,8 +258,8 @@ function upsertRepository(db, workspaceId, r) {
|
|
|
250
258
|
db.prepare(
|
|
251
259
|
`INSERT INTO repositories(workspace_id,name,absolute_path,relative_path,
|
|
252
260
|
package_name,package_version,dependencies_json,
|
|
253
|
-
package_public_surface_json,kind,is_git_repo)
|
|
254
|
-
VALUES(
|
|
261
|
+
package_public_surface_json,environment_declarations_json,kind,is_git_repo)
|
|
262
|
+
VALUES(?,?,?,?,?,?,?,?,?,?,?)
|
|
255
263
|
ON CONFLICT(workspace_id,absolute_path) DO UPDATE SET
|
|
256
264
|
name=excluded.name,relative_path=excluded.relative_path,
|
|
257
265
|
package_name=excluded.package_name,
|
|
@@ -266,6 +274,7 @@ function upsertRepository(db, workspaceId, r) {
|
|
|
266
274
|
r.packageVersion,
|
|
267
275
|
JSON.stringify(r.dependencies ?? {}),
|
|
268
276
|
initialPackagePublicSurface(r.packageName),
|
|
277
|
+
JSON.stringify(emptyEnvironmentDeclarations()),
|
|
269
278
|
r.kind ?? "unknown",
|
|
270
279
|
r.isGitRepo ? 1 : 0
|
|
271
280
|
);
|
|
@@ -292,6 +301,7 @@ function clearRepoFacts(db, repoId) {
|
|
|
292
301
|
"symbol_calls",
|
|
293
302
|
"handler_registrations",
|
|
294
303
|
"service_bindings",
|
|
304
|
+
"generated_constants",
|
|
295
305
|
"symbols",
|
|
296
306
|
"diagnostics",
|
|
297
307
|
"files"
|
|
@@ -299,6 +309,32 @@ function clearRepoFacts(db, repoId) {
|
|
|
299
309
|
db.prepare(`DELETE FROM ${t} WHERE repo_id=?`).run(repoId);
|
|
300
310
|
db.prepare("DELETE FROM search_index WHERE repo=?").run(String(repoId));
|
|
301
311
|
}
|
|
312
|
+
function insertGeneratedConstants(db, repoId, rows) {
|
|
313
|
+
const stmt = db.prepare(`INSERT INTO generated_constants(
|
|
314
|
+
repo_id,source_file,source_line,name,container_name,member_name,value,
|
|
315
|
+
constant_kind,exported,stable,resolution_status,unresolved_reason,
|
|
316
|
+
declaration_start_offset,declaration_end_offset,
|
|
317
|
+
value_start_offset,value_end_offset
|
|
318
|
+
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`);
|
|
319
|
+
for (const row of rows) stmt.run(
|
|
320
|
+
repoId,
|
|
321
|
+
row.sourceFile,
|
|
322
|
+
row.sourceLine,
|
|
323
|
+
row.name,
|
|
324
|
+
row.containerName ?? null,
|
|
325
|
+
row.memberName ?? null,
|
|
326
|
+
row.value ?? null,
|
|
327
|
+
row.constantKind,
|
|
328
|
+
row.exported ? 1 : 0,
|
|
329
|
+
row.stable ? 1 : 0,
|
|
330
|
+
row.resolutionStatus,
|
|
331
|
+
row.unresolvedReason ?? null,
|
|
332
|
+
row.declarationStartOffset,
|
|
333
|
+
row.declarationEndOffset,
|
|
334
|
+
row.valueStartOffset,
|
|
335
|
+
row.valueEndOffset
|
|
336
|
+
);
|
|
337
|
+
}
|
|
302
338
|
function insertRequires(db, repoId, rows) {
|
|
303
339
|
const stmt = db.prepare(
|
|
304
340
|
"INSERT INTO cds_requires(repo_id,alias,kind,model,destination,service_path,request_timeout,raw_json) VALUES(?,?,?,?,?,?,?,?)"
|
|
@@ -612,10 +648,10 @@ import path5 from "path";
|
|
|
612
648
|
// src/parsers/symbol-parser.ts
|
|
613
649
|
import fs4 from "fs/promises";
|
|
614
650
|
import path4 from "path";
|
|
615
|
-
import
|
|
651
|
+
import ts9 from "typescript";
|
|
616
652
|
|
|
617
653
|
// src/parsers/event-subscription-facts.ts
|
|
618
|
-
import
|
|
654
|
+
import ts3 from "typescript";
|
|
619
655
|
|
|
620
656
|
// src/parsers/local-symbol-reference.ts
|
|
621
657
|
import ts from "typescript";
|
|
@@ -752,6 +788,89 @@ function localSymbolTarget(expression, source, symbols) {
|
|
|
752
788
|
return declaration ? exactPropertyTarget(expression, declaration, symbols, source) : void 0;
|
|
753
789
|
}
|
|
754
790
|
|
|
791
|
+
// src/parsers/event-loop-registration.ts
|
|
792
|
+
import ts2 from "typescript";
|
|
793
|
+
var loopValueCap = 32;
|
|
794
|
+
var loopExpressionLimit = 256;
|
|
795
|
+
function unwrap(expression) {
|
|
796
|
+
if (ts2.isParenthesizedExpression(expression) || ts2.isAsExpression(expression) || ts2.isSatisfiesExpression(expression) || ts2.isTypeAssertionExpression(expression))
|
|
797
|
+
return unwrap(expression.expression);
|
|
798
|
+
return expression;
|
|
799
|
+
}
|
|
800
|
+
function stringArray(expression) {
|
|
801
|
+
const value = unwrap(expression);
|
|
802
|
+
if (!ts2.isArrayLiteralExpression(value)) return void 0;
|
|
803
|
+
const strings = value.elements.flatMap((element) => {
|
|
804
|
+
const item = ts2.isSpreadElement(element) ? void 0 : unwrap(element);
|
|
805
|
+
return item && ts2.isStringLiteralLike(item) ? [item.text] : [];
|
|
806
|
+
});
|
|
807
|
+
return strings.length === value.elements.length ? strings : void 0;
|
|
808
|
+
}
|
|
809
|
+
function identifierArray(identifier) {
|
|
810
|
+
const binding = resolveBinding(identifier, identifier);
|
|
811
|
+
return binding.immutable && binding.initializer ? stringArray(binding.initializer) : void 0;
|
|
812
|
+
}
|
|
813
|
+
function orderedValues(values) {
|
|
814
|
+
return [...values].sort((left, right) => left.declarationStartOffset - right.declarationStartOffset).map((item) => item.value);
|
|
815
|
+
}
|
|
816
|
+
function objectValues(expression, source) {
|
|
817
|
+
if (!ts2.isPropertyAccessExpression(expression.expression))
|
|
818
|
+
return void 0;
|
|
819
|
+
const root = expression.expression.expression;
|
|
820
|
+
if (!ts2.isIdentifier(root) || root.text !== "Object" || lexicalIdentifierDeclaration(root) || expression.expression.name.text !== "values" || expression.arguments.length !== 1) return void 0;
|
|
821
|
+
const argument = expression.arguments[0];
|
|
822
|
+
if (!argument || !ts2.isIdentifier(argument)) return void 0;
|
|
823
|
+
const lookups = collectStringConstantLookups(source);
|
|
824
|
+
const prefix = `${argument.text}.`;
|
|
825
|
+
const refused = [...lookups.refusedMembers.keys()].some((key) => key.startsWith(prefix));
|
|
826
|
+
const values = [
|
|
827
|
+
...lookups.enumMembers.values(),
|
|
828
|
+
...lookups.objectProperties.values()
|
|
829
|
+
].filter((item) => item.key.startsWith(prefix));
|
|
830
|
+
return !refused && values.length > 0 ? orderedValues(values) : void 0;
|
|
831
|
+
}
|
|
832
|
+
function staticCollectionValues(expression, source) {
|
|
833
|
+
const value = unwrap(expression);
|
|
834
|
+
if (ts2.isArrayLiteralExpression(value)) return stringArray(value);
|
|
835
|
+
if (ts2.isIdentifier(value)) return identifierArray(value);
|
|
836
|
+
return ts2.isCallExpression(value) ? objectValues(value, source) : void 0;
|
|
837
|
+
}
|
|
838
|
+
function enclosingForEach(node) {
|
|
839
|
+
let current = node.parent;
|
|
840
|
+
while (current && !ts2.isSourceFile(current)) {
|
|
841
|
+
if ((ts2.isArrowFunction(current) || ts2.isFunctionExpression(current)) && ts2.isCallExpression(current.parent) && current.parent.arguments.includes(current) && ts2.isPropertyAccessExpression(current.parent.expression) && current.parent.expression.name.text === "forEach")
|
|
842
|
+
return current.parent;
|
|
843
|
+
if (ts2.isFunctionLike(current)) return void 0;
|
|
844
|
+
current = current.parent;
|
|
845
|
+
}
|
|
846
|
+
return void 0;
|
|
847
|
+
}
|
|
848
|
+
function boundedExpression(expression, source) {
|
|
849
|
+
return expression.getText(source).slice(0, loopExpressionLimit);
|
|
850
|
+
}
|
|
851
|
+
function eventLoopRegistrationEvidence(node, source) {
|
|
852
|
+
const loop = enclosingForEach(node);
|
|
853
|
+
if (!loop || !ts2.isPropertyAccessExpression(loop.expression)) return {};
|
|
854
|
+
const collection = loop.expression.expression;
|
|
855
|
+
const values = staticCollectionValues(collection, source);
|
|
856
|
+
if (!values) return {
|
|
857
|
+
subscriptionRegisteredInLoop: true,
|
|
858
|
+
subscriptionLoopRegistrationStatus: "unresolved",
|
|
859
|
+
subscriptionLoopUnresolvedReason: "subscription_loop_collection_not_statically_enumerable",
|
|
860
|
+
subscriptionLoopCollectionExpression: boundedExpression(collection, source)
|
|
861
|
+
};
|
|
862
|
+
const shown = values.slice(0, loopValueCap);
|
|
863
|
+
return {
|
|
864
|
+
subscriptionRegisteredInLoop: true,
|
|
865
|
+
subscriptionLoopRegistrationStatus: "enumerated",
|
|
866
|
+
subscriptionLoopCollectionExpression: boundedExpression(collection, source),
|
|
867
|
+
subscriptionLoopRegistrationCount: values.length,
|
|
868
|
+
subscriptionLoopValues: shown,
|
|
869
|
+
shownSubscriptionLoopValueCount: shown.length,
|
|
870
|
+
omittedSubscriptionLoopValueCount: Math.max(0, values.length - shown.length)
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
|
|
755
874
|
// src/parsers/event-subscription-facts.ts
|
|
756
875
|
function lineOf(source, node) {
|
|
757
876
|
return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
|
|
@@ -766,7 +885,7 @@ function importRelation(binding) {
|
|
|
766
885
|
function directTarget(expression, source, imports, symbols) {
|
|
767
886
|
const imported = symbolImportReference(expression, imports);
|
|
768
887
|
if (imported) return importedTarget(expression, source, imported);
|
|
769
|
-
if (
|
|
888
|
+
if (ts3.isIdentifier(expression)) {
|
|
770
889
|
const exact = localSymbolTarget(expression, source, symbols);
|
|
771
890
|
return {
|
|
772
891
|
calleeExpression: expression.text,
|
|
@@ -789,7 +908,7 @@ function importedTarget(expression, source, binding) {
|
|
|
789
908
|
};
|
|
790
909
|
}
|
|
791
910
|
function propertyTarget(expression, source, symbols) {
|
|
792
|
-
if (!
|
|
911
|
+
if (!ts3.isPropertyAccessExpression(expression) || expression.questionDotToken || !ts3.isIdentifier(expression.expression)) return void 0;
|
|
793
912
|
const exact = localSymbolTarget(expression, source, symbols);
|
|
794
913
|
return {
|
|
795
914
|
calleeExpression: expression.getText(source),
|
|
@@ -809,7 +928,7 @@ function classifyHandlerReference(expression, source, imports = collectSymbolImp
|
|
|
809
928
|
"handler_argument_missing",
|
|
810
929
|
"missing"
|
|
811
930
|
);
|
|
812
|
-
if (
|
|
931
|
+
if (ts3.isArrowFunction(expression) || ts3.isFunctionExpression(expression))
|
|
813
932
|
return unsupportedClassification(
|
|
814
933
|
"unsupported_inline",
|
|
815
934
|
"inline_handler_body_not_indexed",
|
|
@@ -818,7 +937,7 @@ function classifyHandlerReference(expression, source, imports = collectSymbolImp
|
|
|
818
937
|
const direct = directTarget(expression, source, imports, symbols);
|
|
819
938
|
if (direct)
|
|
820
939
|
return { status: "role_required", referenceShape: direct.referenceShape, target: direct };
|
|
821
|
-
if (!
|
|
940
|
+
if (!ts3.isCallExpression(expression))
|
|
822
941
|
return unsupportedClassification(
|
|
823
942
|
"unsupported_reference_shape",
|
|
824
943
|
"handler_reference_shape_unsupported",
|
|
@@ -906,6 +1025,7 @@ function enrichSubscription(source, classified, symbols) {
|
|
|
906
1025
|
);
|
|
907
1026
|
const evidence = {
|
|
908
1027
|
...classified.fact.evidence ?? {},
|
|
1028
|
+
...eventLoopRegistrationEvidence(classified.node, source),
|
|
909
1029
|
handlerReferenceStatus: classification.status,
|
|
910
1030
|
...classification.reason ? { handlerReferenceReason: classification.reason } : {},
|
|
911
1031
|
handlerReferenceShape: classification.referenceShape
|
|
@@ -939,7 +1059,7 @@ function reconcileEventSubscriptions(source, classifications, symbols, calls) {
|
|
|
939
1059
|
import "typescript";
|
|
940
1060
|
|
|
941
1061
|
// src/parsers/binding-identity.ts
|
|
942
|
-
import
|
|
1062
|
+
import ts4 from "typescript";
|
|
943
1063
|
var scopeChainCap = 16;
|
|
944
1064
|
function matchFactsToSites(index, facts) {
|
|
945
1065
|
return facts.map((fact) => {
|
|
@@ -1059,9 +1179,9 @@ function assertUniqueSites(bindings) {
|
|
|
1059
1179
|
function callNodeMap(source) {
|
|
1060
1180
|
const calls = /* @__PURE__ */ new Map();
|
|
1061
1181
|
const visit = (node) => {
|
|
1062
|
-
if (
|
|
1182
|
+
if (ts4.isCallExpression(node))
|
|
1063
1183
|
calls.set(`${node.getStart(source)}:${node.getEnd()}`, node);
|
|
1064
|
-
|
|
1184
|
+
ts4.forEachChild(node, visit);
|
|
1065
1185
|
};
|
|
1066
1186
|
visit(source);
|
|
1067
1187
|
return calls;
|
|
@@ -1254,7 +1374,7 @@ function reconcileSourceFacts(source, classifications, bindings, outboundCalls,
|
|
|
1254
1374
|
}
|
|
1255
1375
|
|
|
1256
1376
|
// src/parsers/symbol-call-facts.ts
|
|
1257
|
-
import
|
|
1377
|
+
import ts6 from "typescript";
|
|
1258
1378
|
var commonTerminalMembers = /* @__PURE__ */ new Set([
|
|
1259
1379
|
"push",
|
|
1260
1380
|
"includes",
|
|
@@ -1322,8 +1442,8 @@ var cdsFrameworkPrefixes = [
|
|
|
1322
1442
|
"cds.parse."
|
|
1323
1443
|
];
|
|
1324
1444
|
function symbolCallName(expr) {
|
|
1325
|
-
if (
|
|
1326
|
-
if (!
|
|
1445
|
+
if (ts6.isIdentifier(expr)) return { expression: expr.text, local: expr.text };
|
|
1446
|
+
if (!ts6.isPropertyAccessExpression(expr))
|
|
1327
1447
|
return { expression: expr.getText() };
|
|
1328
1448
|
const left = expr.expression.getText();
|
|
1329
1449
|
return {
|
|
@@ -1355,25 +1475,25 @@ function ignoredFrameworkCall(callee) {
|
|
|
1355
1475
|
}
|
|
1356
1476
|
function argumentEvidence(args) {
|
|
1357
1477
|
return args.map((arg) => {
|
|
1358
|
-
if (
|
|
1359
|
-
if (
|
|
1478
|
+
if (ts6.isIdentifier(arg)) return { kind: "identifier", name: arg.text };
|
|
1479
|
+
if (ts6.isArrayLiteralExpression(arg)) return {
|
|
1360
1480
|
kind: "array_literal",
|
|
1361
|
-
elements: arg.elements.flatMap((item, index) =>
|
|
1481
|
+
elements: arg.elements.flatMap((item, index) => ts6.isIdentifier(item) ? [{ index, kind: "identifier", name: item.text }] : [])
|
|
1362
1482
|
};
|
|
1363
|
-
if (
|
|
1483
|
+
if (ts6.isObjectLiteralExpression(arg))
|
|
1364
1484
|
return objectArgumentEvidence(arg);
|
|
1365
1485
|
return { kind: "unsupported", expression: arg.getText() };
|
|
1366
1486
|
});
|
|
1367
1487
|
}
|
|
1368
1488
|
function objectArgumentEvidence(argument) {
|
|
1369
1489
|
const properties = argument.properties.flatMap((property) => {
|
|
1370
|
-
if (
|
|
1490
|
+
if (ts6.isShorthandPropertyAssignment(property))
|
|
1371
1491
|
return [{
|
|
1372
1492
|
kind: "shorthand",
|
|
1373
1493
|
property: property.name.text,
|
|
1374
1494
|
argument: property.name.text
|
|
1375
1495
|
}];
|
|
1376
|
-
if (!
|
|
1496
|
+
if (!ts6.isPropertyAssignment(property) || !ts6.isIdentifier(property.initializer)) return [];
|
|
1377
1497
|
const name = propertyName(property.name);
|
|
1378
1498
|
return name ? [{
|
|
1379
1499
|
kind: "property_assignment",
|
|
@@ -1384,7 +1504,7 @@ function objectArgumentEvidence(argument) {
|
|
|
1384
1504
|
return { kind: "object_literal", properties };
|
|
1385
1505
|
}
|
|
1386
1506
|
function propertyName(name) {
|
|
1387
|
-
return
|
|
1507
|
+
return ts6.isIdentifier(name) || ts6.isStringLiteralLike(name) || ts6.isNumericLiteral(name) ? name.text : void 0;
|
|
1388
1508
|
}
|
|
1389
1509
|
function exactCaller(collection, node) {
|
|
1390
1510
|
const selected = selectCallOwner(
|
|
@@ -1445,9 +1565,9 @@ function callFact(collection, node) {
|
|
|
1445
1565
|
}
|
|
1446
1566
|
function expressionRoot(expression) {
|
|
1447
1567
|
let current = expression;
|
|
1448
|
-
while (
|
|
1568
|
+
while (ts6.isPropertyAccessExpression(current))
|
|
1449
1569
|
current = current.expression;
|
|
1450
|
-
return
|
|
1570
|
+
return ts6.isIdentifier(current) ? current : void 0;
|
|
1451
1571
|
}
|
|
1452
1572
|
function exactDeclaredContext(identifier, candidates) {
|
|
1453
1573
|
const matches = candidates.filter((candidate) => identifierMatchesDeclaration(
|
|
@@ -1478,8 +1598,8 @@ function callInstance(collection, node, callee) {
|
|
|
1478
1598
|
}
|
|
1479
1599
|
function enclosingClass(node) {
|
|
1480
1600
|
let current = node.parent;
|
|
1481
|
-
while (current && !
|
|
1482
|
-
if (
|
|
1601
|
+
while (current && !ts6.isSourceFile(current)) {
|
|
1602
|
+
if (ts6.isClassLike(current)) return current;
|
|
1483
1603
|
current = current.parent;
|
|
1484
1604
|
}
|
|
1485
1605
|
return void 0;
|
|
@@ -1619,26 +1739,26 @@ function parserCandidateStrategy(context) {
|
|
|
1619
1739
|
function collectSymbolCallFacts(collection) {
|
|
1620
1740
|
const calls = [];
|
|
1621
1741
|
const visit = (node) => {
|
|
1622
|
-
if (
|
|
1742
|
+
if (ts6.isCallExpression(node)) {
|
|
1623
1743
|
const call = callFact(collection, node);
|
|
1624
1744
|
if (call) calls.push(call);
|
|
1625
1745
|
}
|
|
1626
|
-
|
|
1746
|
+
ts6.forEachChild(node, visit);
|
|
1627
1747
|
};
|
|
1628
1748
|
visit(collection.source);
|
|
1629
1749
|
return calls;
|
|
1630
1750
|
}
|
|
1631
1751
|
|
|
1632
1752
|
// src/parsers/executable-body-eligibility.ts
|
|
1633
|
-
import
|
|
1753
|
+
import ts7 from "typescript";
|
|
1634
1754
|
function hasModifier(node, kind) {
|
|
1635
|
-
return
|
|
1755
|
+
return ts7.canHaveModifiers(node) && Boolean(ts7.getModifiers(node)?.some((item) => item.kind === kind));
|
|
1636
1756
|
}
|
|
1637
1757
|
function executableBodyEligibility(node, source) {
|
|
1638
1758
|
if (node.body) return { eligible: true, reason: "body_present" };
|
|
1639
|
-
if (hasModifier(node,
|
|
1759
|
+
if (hasModifier(node, ts7.SyntaxKind.AbstractKeyword))
|
|
1640
1760
|
return { eligible: false, reason: "abstract_bodyless" };
|
|
1641
|
-
if (hasModifier(node,
|
|
1761
|
+
if (hasModifier(node, ts7.SyntaxKind.DeclareKeyword))
|
|
1642
1762
|
return { eligible: false, reason: "ambient_declaration" };
|
|
1643
1763
|
return {
|
|
1644
1764
|
eligible: false,
|
|
@@ -1647,7 +1767,7 @@ function executableBodyEligibility(node, source) {
|
|
|
1647
1767
|
}
|
|
1648
1768
|
|
|
1649
1769
|
// src/parsers/symbol-derived-contexts.ts
|
|
1650
|
-
import
|
|
1770
|
+
import ts8 from "typescript";
|
|
1651
1771
|
var builtInConstructors = /* @__PURE__ */ new Set([
|
|
1652
1772
|
"Set",
|
|
1653
1773
|
"Map",
|
|
@@ -1688,23 +1808,23 @@ function appendNamed(values, name, value) {
|
|
|
1688
1808
|
values.set(name, matches);
|
|
1689
1809
|
}
|
|
1690
1810
|
function assignmentOperator2(kind) {
|
|
1691
|
-
return kind >=
|
|
1811
|
+
return kind >= ts8.SyntaxKind.FirstAssignment && kind <= ts8.SyntaxKind.LastAssignment;
|
|
1692
1812
|
}
|
|
1693
1813
|
function mutationUnary2(node) {
|
|
1694
|
-
return node.operator ===
|
|
1814
|
+
return node.operator === ts8.SyntaxKind.PlusPlusToken || node.operator === ts8.SyntaxKind.MinusMinusToken;
|
|
1695
1815
|
}
|
|
1696
1816
|
function receiverIdentifier(expression) {
|
|
1697
1817
|
let current = expression;
|
|
1698
|
-
while (
|
|
1818
|
+
while (ts8.isPropertyAccessExpression(current) || ts8.isElementAccessExpression(current))
|
|
1699
1819
|
current = current.expression;
|
|
1700
|
-
return
|
|
1820
|
+
return ts8.isIdentifier(current) ? current : void 0;
|
|
1701
1821
|
}
|
|
1702
1822
|
function nodeWritesMember(node, matches) {
|
|
1703
|
-
if (
|
|
1823
|
+
if (ts8.isBinaryExpression(node))
|
|
1704
1824
|
return assignmentOperator2(node.operatorToken.kind) && matches(node.left);
|
|
1705
|
-
if (
|
|
1825
|
+
if (ts8.isPrefixUnaryExpression(node) || ts8.isPostfixUnaryExpression(node))
|
|
1706
1826
|
return mutationUnary2(node) && matches(node.operand);
|
|
1707
|
-
return
|
|
1827
|
+
return ts8.isDeleteExpression(node) && matches(node.expression);
|
|
1708
1828
|
}
|
|
1709
1829
|
function memberWrite(source, declaration) {
|
|
1710
1830
|
const start = declaration.getStart(source);
|
|
@@ -1717,16 +1837,16 @@ function memberWrite(source, declaration) {
|
|
|
1717
1837
|
const visit = (node) => {
|
|
1718
1838
|
if (written) return;
|
|
1719
1839
|
written = nodeWritesMember(node, matches);
|
|
1720
|
-
if (!written)
|
|
1840
|
+
if (!written) ts8.forEachChild(node, visit);
|
|
1721
1841
|
};
|
|
1722
1842
|
visit(source);
|
|
1723
1843
|
return written;
|
|
1724
1844
|
}
|
|
1725
1845
|
function stableVariable(collection, node) {
|
|
1726
|
-
return
|
|
1846
|
+
return ts8.isIdentifier(node.name) && ts8.isVariableDeclarationList(node.parent) && (node.parent.flags & ts8.NodeFlags.Const) !== 0 && !memberWrite(collection.source, node.name);
|
|
1727
1847
|
}
|
|
1728
1848
|
function collectProxy(collection, node) {
|
|
1729
|
-
if (!
|
|
1849
|
+
if (!ts8.isVariableDeclaration(node) || !stableVariable(collection, node) || !node.initializer || !ts8.isCallExpression(node.initializer) || !ts8.isPropertyAccessExpression(node.initializer.expression)) return;
|
|
1730
1850
|
const callee = symbolCallName(node.initializer.expression);
|
|
1731
1851
|
const binding = symbolImportReference(
|
|
1732
1852
|
node.initializer.expression,
|
|
@@ -1743,8 +1863,8 @@ function collectProxy(collection, node) {
|
|
|
1743
1863
|
});
|
|
1744
1864
|
}
|
|
1745
1865
|
function propertyContainer2(declaration, propertyName3) {
|
|
1746
|
-
const property = propertyName3 &&
|
|
1747
|
-
return property &&
|
|
1866
|
+
const property = propertyName3 && ts8.isPropertyDeclaration(declaration.parent) ? declaration.parent : void 0;
|
|
1867
|
+
return property && ts8.isClassLike(property.parent) ? property.parent : void 0;
|
|
1748
1868
|
}
|
|
1749
1869
|
function eligibleClassName(collection, className, imported) {
|
|
1750
1870
|
if (builtInConstructors.has(className)) return false;
|
|
@@ -1775,43 +1895,43 @@ function rememberInstance(collection, declaration, classExpression, propertyName
|
|
|
1775
1895
|
});
|
|
1776
1896
|
}
|
|
1777
1897
|
function collectVariableInstance(collection, node) {
|
|
1778
|
-
if (!
|
|
1898
|
+
if (!ts8.isVariableDeclaration(node) || !stableVariable(collection, node))
|
|
1779
1899
|
return;
|
|
1780
1900
|
const initializer = node.initializer;
|
|
1781
|
-
if (initializer &&
|
|
1901
|
+
if (initializer && ts8.isNewExpression(initializer) && ts8.isIdentifier(initializer.expression))
|
|
1782
1902
|
rememberInstance(collection, node.name, initializer.expression);
|
|
1783
1903
|
}
|
|
1784
1904
|
function propertyName2(name) {
|
|
1785
|
-
return
|
|
1905
|
+
return ts8.isIdentifier(name) || ts8.isStringLiteralLike(name) || ts8.isNumericLiteral(name) ? name.text : void 0;
|
|
1786
1906
|
}
|
|
1787
1907
|
function thisPropertyWrite(expression, name) {
|
|
1788
|
-
if (
|
|
1789
|
-
return expression.expression.kind ===
|
|
1790
|
-
return
|
|
1908
|
+
if (ts8.isPropertyAccessExpression(expression))
|
|
1909
|
+
return expression.expression.kind === ts8.SyntaxKind.ThisKeyword && expression.name.text === name;
|
|
1910
|
+
return ts8.isElementAccessExpression(expression) && expression.expression.kind === ts8.SyntaxKind.ThisKeyword && Boolean(expression.argumentExpression && ts8.isStringLiteralLike(expression.argumentExpression) && expression.argumentExpression.text === name);
|
|
1791
1911
|
}
|
|
1792
1912
|
function nodeWritesThisProperty(node, name) {
|
|
1793
|
-
if (
|
|
1913
|
+
if (ts8.isBinaryExpression(node))
|
|
1794
1914
|
return assignmentOperator2(node.operatorToken.kind) && thisPropertyWrite(node.left, name);
|
|
1795
|
-
if (
|
|
1915
|
+
if (ts8.isPrefixUnaryExpression(node) || ts8.isPostfixUnaryExpression(node))
|
|
1796
1916
|
return mutationUnary2(node) && thisPropertyWrite(node.operand, name);
|
|
1797
|
-
return
|
|
1917
|
+
return ts8.isDeleteExpression(node) && thisPropertyWrite(node.expression, name);
|
|
1798
1918
|
}
|
|
1799
1919
|
function stableProperty(node, name) {
|
|
1800
|
-
const flags =
|
|
1801
|
-
if ((flags & (
|
|
1920
|
+
const flags = ts8.getCombinedModifierFlags(node);
|
|
1921
|
+
if ((flags & (ts8.ModifierFlags.Private | ts8.ModifierFlags.Readonly)) === 0 || !ts8.isClassLike(node.parent)) return false;
|
|
1802
1922
|
let written = false;
|
|
1803
1923
|
const visit = (child) => {
|
|
1804
1924
|
if (written || child === node) return;
|
|
1805
1925
|
written = nodeWritesThisProperty(child, name);
|
|
1806
|
-
if (!written)
|
|
1926
|
+
if (!written) ts8.forEachChild(child, visit);
|
|
1807
1927
|
};
|
|
1808
1928
|
visit(node.parent);
|
|
1809
1929
|
return !written;
|
|
1810
1930
|
}
|
|
1811
1931
|
function collectPropertyInstance(collection, node) {
|
|
1812
|
-
if (!
|
|
1932
|
+
if (!ts8.isPropertyDeclaration(node)) return;
|
|
1813
1933
|
const initializer = node.initializer;
|
|
1814
|
-
if (!initializer || !
|
|
1934
|
+
if (!initializer || !ts8.isNewExpression(initializer) || !ts8.isIdentifier(initializer.expression)) return;
|
|
1815
1935
|
const name = propertyName2(node.name);
|
|
1816
1936
|
if (name && stableProperty(node, name)) rememberInstance(
|
|
1817
1937
|
collection,
|
|
@@ -1825,7 +1945,7 @@ function collectDerivedSymbolContexts(collection) {
|
|
|
1825
1945
|
collectProxy(collection, node);
|
|
1826
1946
|
collectVariableInstance(collection, node);
|
|
1827
1947
|
collectPropertyInstance(collection, node);
|
|
1828
|
-
|
|
1948
|
+
ts8.forEachChild(node, visit);
|
|
1829
1949
|
};
|
|
1830
1950
|
visit(collection.source);
|
|
1831
1951
|
}
|
|
@@ -1836,51 +1956,51 @@ function lineOf2(source, pos) {
|
|
|
1836
1956
|
}
|
|
1837
1957
|
function nameOf(node) {
|
|
1838
1958
|
if (!node) return void 0;
|
|
1839
|
-
if (
|
|
1959
|
+
if (ts9.isIdentifier(node) || ts9.isStringLiteral(node) || ts9.isNumericLiteral(node)) return node.text;
|
|
1840
1960
|
return void 0;
|
|
1841
1961
|
}
|
|
1842
1962
|
function isFunctionLike(node) {
|
|
1843
|
-
return
|
|
1963
|
+
return ts9.isFunctionDeclaration(node) || ts9.isMethodDeclaration(node) || ts9.isFunctionExpression(node) || ts9.isArrowFunction(node);
|
|
1844
1964
|
}
|
|
1845
1965
|
function exported(node) {
|
|
1846
|
-
return Boolean(
|
|
1966
|
+
return Boolean(ts9.getCombinedModifierFlags(node) & ts9.ModifierFlags.Export);
|
|
1847
1967
|
}
|
|
1848
1968
|
function defaultExported(node) {
|
|
1849
1969
|
return Boolean(
|
|
1850
|
-
|
|
1970
|
+
ts9.getCombinedModifierFlags(node) & ts9.ModifierFlags.Default
|
|
1851
1971
|
);
|
|
1852
1972
|
}
|
|
1853
1973
|
function isPublicClassMethod(node) {
|
|
1854
|
-
const flags =
|
|
1855
|
-
return (flags &
|
|
1974
|
+
const flags = ts9.getCombinedModifierFlags(node);
|
|
1975
|
+
return (flags & ts9.ModifierFlags.Private) === 0 && (flags & ts9.ModifierFlags.Protected) === 0;
|
|
1856
1976
|
}
|
|
1857
1977
|
function exportDeclarations(source) {
|
|
1858
1978
|
const exports = /* @__PURE__ */ new Map();
|
|
1859
1979
|
const visit = (node) => {
|
|
1860
|
-
if (
|
|
1980
|
+
if (ts9.isExportDeclaration(node) && node.exportClause && ts9.isNamedExports(node.exportClause)) {
|
|
1861
1981
|
for (const el of node.exportClause.elements) exports.set((el.propertyName ?? el.name).text, el.name.text);
|
|
1862
1982
|
}
|
|
1863
|
-
|
|
1983
|
+
ts9.forEachChild(node, visit);
|
|
1864
1984
|
};
|
|
1865
1985
|
visit(source);
|
|
1866
1986
|
return exports;
|
|
1867
1987
|
}
|
|
1868
1988
|
function isObjectFunction(node) {
|
|
1869
|
-
return
|
|
1989
|
+
return ts9.isFunctionExpression(node) || ts9.isArrowFunction(node) || ts9.isMethodDeclaration(node);
|
|
1870
1990
|
}
|
|
1871
1991
|
function requireSource(expr) {
|
|
1872
|
-
if (!
|
|
1992
|
+
if (!ts9.isCallExpression(expr) || !ts9.isIdentifier(expr.expression) || expr.expression.text !== "require") return void 0;
|
|
1873
1993
|
const first = expr.arguments[0];
|
|
1874
|
-
return first &&
|
|
1994
|
+
return first && ts9.isStringLiteral(first) ? first.text : void 0;
|
|
1875
1995
|
}
|
|
1876
1996
|
function bindingLocalName(name, initializer) {
|
|
1877
|
-
if (
|
|
1878
|
-
if (initializer &&
|
|
1997
|
+
if (ts9.isIdentifier(name)) return name.text;
|
|
1998
|
+
if (initializer && ts9.isIdentifier(initializer)) return initializer.text;
|
|
1879
1999
|
return void 0;
|
|
1880
2000
|
}
|
|
1881
2001
|
function objectPatternAliases(pattern, parameter, source, lineNode) {
|
|
1882
2002
|
return pattern.elements.flatMap((element) => {
|
|
1883
|
-
if (element.dotDotDotToken ||
|
|
2003
|
+
if (element.dotDotDotToken || ts9.isObjectBindingPattern(element.name) || ts9.isArrayBindingPattern(element.name)) return [];
|
|
1884
2004
|
const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
|
|
1885
2005
|
if (!property) return [];
|
|
1886
2006
|
const local = bindingLocalName(element.name, element.initializer);
|
|
@@ -1888,21 +2008,21 @@ function objectPatternAliases(pattern, parameter, source, lineNode) {
|
|
|
1888
2008
|
});
|
|
1889
2009
|
}
|
|
1890
2010
|
function parameterPropertyAliases(fn, source) {
|
|
1891
|
-
const parameterNames = new Set(fn.parameters.flatMap((param) =>
|
|
2011
|
+
const parameterNames = new Set(fn.parameters.flatMap((param) => ts9.isIdentifier(param.name) ? [param.name.text] : []));
|
|
1892
2012
|
if (!fn.body || parameterNames.size === 0) return [];
|
|
1893
2013
|
const aliases = [];
|
|
1894
2014
|
const addFromAssignment = (left, right, node) => {
|
|
1895
|
-
if (!
|
|
2015
|
+
if (!ts9.isObjectLiteralExpression(left) || !ts9.isIdentifier(right) || !parameterNames.has(right.text)) return;
|
|
1896
2016
|
for (const prop of left.properties) {
|
|
1897
|
-
if (!
|
|
2017
|
+
if (!ts9.isPropertyAssignment(prop)) continue;
|
|
1898
2018
|
const property = nameOf(prop.name);
|
|
1899
|
-
if (property &&
|
|
2019
|
+
if (property && ts9.isIdentifier(prop.initializer)) aliases.push({ parameter: right.text, property, local: prop.initializer.text, kind: "object_parameter_destructure", line: lineOf2(source, node.getStart(source)) });
|
|
1900
2020
|
}
|
|
1901
2021
|
};
|
|
1902
2022
|
const visit = (node) => {
|
|
1903
|
-
if (
|
|
1904
|
-
if (
|
|
1905
|
-
|
|
2023
|
+
if (ts9.isVariableDeclaration(node) && ts9.isObjectBindingPattern(node.name) && node.initializer && ts9.isIdentifier(node.initializer) && parameterNames.has(node.initializer.text)) aliases.push(...objectPatternAliases(node.name, node.initializer.text, source, node));
|
|
2024
|
+
if (ts9.isBinaryExpression(node) && node.operatorToken.kind === ts9.SyntaxKind.EqualsToken) addFromAssignment(ts9.isParenthesizedExpression(node.left) ? node.left.expression : node.left, node.right, node);
|
|
2025
|
+
ts9.forEachChild(node, visit);
|
|
1906
2026
|
};
|
|
1907
2027
|
visit(fn.body);
|
|
1908
2028
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1915,10 +2035,10 @@ function parameterPropertyAliases(fn, source) {
|
|
|
1915
2035
|
}
|
|
1916
2036
|
function parameterBindings(params) {
|
|
1917
2037
|
return params.flatMap((param, index) => {
|
|
1918
|
-
if (
|
|
1919
|
-
if (
|
|
2038
|
+
if (ts9.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
|
|
2039
|
+
if (ts9.isObjectBindingPattern(param.name)) {
|
|
1920
2040
|
const properties = param.name.elements.flatMap((element) => {
|
|
1921
|
-
if (element.dotDotDotToken ||
|
|
2041
|
+
if (element.dotDotDotToken || ts9.isObjectBindingPattern(element.name) || ts9.isArrayBindingPattern(element.name)) return [];
|
|
1922
2042
|
const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
|
|
1923
2043
|
if (!property) return [];
|
|
1924
2044
|
const local = bindingLocalName(element.name, element.initializer);
|
|
@@ -1926,8 +2046,8 @@ function parameterBindings(params) {
|
|
|
1926
2046
|
});
|
|
1927
2047
|
return properties.length > 0 ? [{ index, kind: "object_pattern", properties }] : [];
|
|
1928
2048
|
}
|
|
1929
|
-
if (
|
|
1930
|
-
const elements = param.name.elements.flatMap((element, elementIndex) =>
|
|
2049
|
+
if (ts9.isArrayBindingPattern(param.name)) {
|
|
2050
|
+
const elements = param.name.elements.flatMap((element, elementIndex) => ts9.isBindingElement(element) && !element.dotDotDotToken && ts9.isIdentifier(element.name) ? [{ index: elementIndex, local: element.name.text }] : []);
|
|
1931
2051
|
return elements.length > 0 ? [{ index, kind: "array_pattern", elements }] : [];
|
|
1932
2052
|
}
|
|
1933
2053
|
return [];
|
|
@@ -1938,9 +2058,9 @@ function symbolSourceEvidence(collection, node, options) {
|
|
|
1938
2058
|
if (options.classMemberExported) return {
|
|
1939
2059
|
source: "exported_class_member",
|
|
1940
2060
|
exportedClass: options.parentRoot,
|
|
1941
|
-
memberKind:
|
|
2061
|
+
memberKind: ts9.getCombinedModifierFlags(node) & ts9.ModifierFlags.Static ? "static_method" : "class_method"
|
|
1942
2062
|
};
|
|
1943
|
-
if (options.classContainerExported &&
|
|
2063
|
+
if (options.classContainerExported && ts9.isMethodDeclaration(node) && isPublicClassMethod(node)) return {
|
|
1944
2064
|
source: "exported_class_instance_member",
|
|
1945
2065
|
exportedClass: options.parentRoot,
|
|
1946
2066
|
exportedClassExportKind: options.classContainerDefaultExported ? "default" : "named",
|
|
@@ -1964,8 +2084,8 @@ function executableEvidence(node, source) {
|
|
|
1964
2084
|
};
|
|
1965
2085
|
}
|
|
1966
2086
|
function exportedClassMember(collection, kind, parentName, parentRoot, node) {
|
|
1967
|
-
if (kind !== "method" || !parentName || !collection.exportedClasses.has(parentRoot) || !
|
|
1968
|
-
return Boolean(
|
|
2087
|
+
if (kind !== "method" || !parentName || !collection.exportedClasses.has(parentRoot) || !ts9.isMethodDeclaration(node)) return false;
|
|
2088
|
+
return Boolean(ts9.getCombinedModifierFlags(node) & ts9.ModifierFlags.Static) && isPublicClassMethod(node);
|
|
1969
2089
|
}
|
|
1970
2090
|
function classExportState(collection, parentName, parentRoot) {
|
|
1971
2091
|
return {
|
|
@@ -2062,41 +2182,41 @@ function addAliasSymbol(collection, objectName, propertyName3, node) {
|
|
|
2062
2182
|
}
|
|
2063
2183
|
function collectImportSources(collection) {
|
|
2064
2184
|
const visit = (node) => {
|
|
2065
|
-
if (
|
|
2185
|
+
if (ts9.isImportDeclaration(node) && ts9.isStringLiteral(node.moduleSpecifier))
|
|
2066
2186
|
collectEsmImportSources(collection.imports, node);
|
|
2067
|
-
if (
|
|
2187
|
+
if (ts9.isVariableStatement(node))
|
|
2068
2188
|
collectCjsImportSources(collection.imports, node);
|
|
2069
|
-
|
|
2189
|
+
ts9.forEachChild(node, visit);
|
|
2070
2190
|
};
|
|
2071
2191
|
visit(collection.source);
|
|
2072
2192
|
}
|
|
2073
2193
|
function collectEsmImportSources(imports, node) {
|
|
2074
|
-
if (!
|
|
2194
|
+
if (!ts9.isStringLiteral(node.moduleSpecifier)) return;
|
|
2075
2195
|
const source = node.moduleSpecifier.text;
|
|
2076
2196
|
const clause = node.importClause;
|
|
2077
2197
|
if (clause?.name) imports.set(clause.name.text, source);
|
|
2078
2198
|
const named = clause?.namedBindings;
|
|
2079
|
-
if (named &&
|
|
2199
|
+
if (named && ts9.isNamedImports(named))
|
|
2080
2200
|
for (const item of named.elements) imports.set(item.name.text, source);
|
|
2081
|
-
if (named &&
|
|
2201
|
+
if (named && ts9.isNamespaceImport(named))
|
|
2082
2202
|
imports.set(named.name.text, source);
|
|
2083
2203
|
}
|
|
2084
2204
|
function collectCjsImportSources(imports, node) {
|
|
2085
2205
|
for (const declaration of node.declarationList.declarations) {
|
|
2086
2206
|
const source = declaration.initializer ? requireSource(declaration.initializer) : void 0;
|
|
2087
2207
|
if (!source) continue;
|
|
2088
|
-
if (
|
|
2208
|
+
if (ts9.isIdentifier(declaration.name))
|
|
2089
2209
|
imports.set(declaration.name.text, source);
|
|
2090
|
-
if (
|
|
2210
|
+
if (ts9.isObjectBindingPattern(declaration.name)) {
|
|
2091
2211
|
for (const item of declaration.name.elements)
|
|
2092
|
-
if (
|
|
2212
|
+
if (ts9.isIdentifier(item.name)) imports.set(item.name.text, source);
|
|
2093
2213
|
}
|
|
2094
2214
|
}
|
|
2095
2215
|
}
|
|
2096
2216
|
function classPropertySymbol(collection, node, parentClass) {
|
|
2097
2217
|
const initializer = node.initializer;
|
|
2098
2218
|
const localName = nameOf(node.name);
|
|
2099
|
-
if (!localName || !initializer || !
|
|
2219
|
+
if (!localName || !initializer || !ts9.isArrowFunction(initializer) && !ts9.isFunctionExpression(initializer)) return;
|
|
2100
2220
|
const staticPublic = publicStaticProperty(collection, node, parentClass);
|
|
2101
2221
|
const memberKind = propertyMemberKind(initializer, staticPublic);
|
|
2102
2222
|
addExecutableSymbol(
|
|
@@ -2110,22 +2230,22 @@ function classPropertySymbol(collection, node, parentClass) {
|
|
|
2110
2230
|
);
|
|
2111
2231
|
}
|
|
2112
2232
|
function publicStaticProperty(collection, node, parentClass) {
|
|
2113
|
-
const flags =
|
|
2114
|
-
return collection.exportedClasses.has(parentClass) && Boolean(flags &
|
|
2233
|
+
const flags = ts9.getCombinedModifierFlags(node);
|
|
2234
|
+
return collection.exportedClasses.has(parentClass) && Boolean(flags & ts9.ModifierFlags.Static) && (flags & ts9.ModifierFlags.Private) === 0 && (flags & ts9.ModifierFlags.Protected) === 0;
|
|
2115
2235
|
}
|
|
2116
2236
|
function propertyMemberKind(initializer, staticPublic) {
|
|
2117
|
-
if (
|
|
2237
|
+
if (ts9.isArrowFunction(initializer))
|
|
2118
2238
|
return staticPublic ? "static_arrow_function" : "arrow_function_property";
|
|
2119
2239
|
return staticPublic ? "static_function_expression" : "function_expression_property";
|
|
2120
2240
|
}
|
|
2121
2241
|
function objectCallable(property) {
|
|
2122
|
-
if (
|
|
2123
|
-
return
|
|
2242
|
+
if (ts9.isMethodDeclaration(property)) return property;
|
|
2243
|
+
return ts9.isPropertyAssignment(property) && isObjectFunction(property.initializer) ? property.initializer : void 0;
|
|
2124
2244
|
}
|
|
2125
2245
|
function objectLiteralSymbols(collection, objectName, object, objectIsExported) {
|
|
2126
2246
|
if (objectIsExported) collection.objectExports.add(objectName);
|
|
2127
2247
|
for (const property of object.properties) {
|
|
2128
|
-
if (objectIsExported &&
|
|
2248
|
+
if (objectIsExported && ts9.isShorthandPropertyAssignment(property))
|
|
2129
2249
|
addAliasSymbol(collection, objectName, property.name.text, property.name);
|
|
2130
2250
|
const callable = objectCallable(property);
|
|
2131
2251
|
const propertyName3 = callable ? nameOf(property.name) : void 0;
|
|
@@ -2152,7 +2272,7 @@ function variableSymbols(collection, node) {
|
|
|
2152
2272
|
void 0,
|
|
2153
2273
|
exported(node) ? localName : collection.exportNames.get(localName)
|
|
2154
2274
|
);
|
|
2155
|
-
if (
|
|
2275
|
+
if (ts9.isObjectLiteralExpression(initializer))
|
|
2156
2276
|
objectLiteralSymbols(
|
|
2157
2277
|
collection,
|
|
2158
2278
|
localName,
|
|
@@ -2162,7 +2282,7 @@ function variableSymbols(collection, node) {
|
|
|
2162
2282
|
}
|
|
2163
2283
|
}
|
|
2164
2284
|
function collectClassDeclaration(collection, node) {
|
|
2165
|
-
if (!
|
|
2285
|
+
if (!ts9.isClassDeclaration(node) || !node.name) return false;
|
|
2166
2286
|
collection.declaredClasses.add(node.name.text);
|
|
2167
2287
|
if (exported(node) || collection.exportNames.has(node.name.text))
|
|
2168
2288
|
collection.exportedClasses.add(node.name.text);
|
|
@@ -2173,7 +2293,7 @@ function collectClassDeclaration(collection, node) {
|
|
|
2173
2293
|
return true;
|
|
2174
2294
|
}
|
|
2175
2295
|
function collectMethodDeclaration(collection, node, parentClass) {
|
|
2176
|
-
if (!
|
|
2296
|
+
if (!ts9.isMethodDeclaration(node)) return false;
|
|
2177
2297
|
const localName = nameOf(node.name);
|
|
2178
2298
|
if (localName)
|
|
2179
2299
|
addExecutableSymbol(collection, "method", localName, node, parentClass);
|
|
@@ -2182,11 +2302,11 @@ function collectMethodDeclaration(collection, node, parentClass) {
|
|
|
2182
2302
|
function visitDeclaredSymbol(collection, node, parentClass) {
|
|
2183
2303
|
if (collectClassDeclaration(collection, node)) return;
|
|
2184
2304
|
if (collectMethodDeclaration(collection, node, parentClass)) return;
|
|
2185
|
-
if (
|
|
2305
|
+
if (ts9.isPropertyDeclaration(node)) {
|
|
2186
2306
|
if (parentClass) classPropertySymbol(collection, node, parentClass);
|
|
2187
2307
|
return;
|
|
2188
2308
|
}
|
|
2189
|
-
if (
|
|
2309
|
+
if (ts9.isFunctionDeclaration(node) && node.name) {
|
|
2190
2310
|
addExecutableSymbol(
|
|
2191
2311
|
collection,
|
|
2192
2312
|
"function",
|
|
@@ -2197,17 +2317,17 @@ function visitDeclaredSymbol(collection, node, parentClass) {
|
|
|
2197
2317
|
);
|
|
2198
2318
|
return;
|
|
2199
2319
|
}
|
|
2200
|
-
if (
|
|
2320
|
+
if (ts9.isVariableStatement(node)) {
|
|
2201
2321
|
variableSymbols(collection, node);
|
|
2202
2322
|
return;
|
|
2203
2323
|
}
|
|
2204
|
-
|
|
2324
|
+
ts9.forEachChild(node, (child) => visitDeclaredSymbol(collection, child, parentClass));
|
|
2205
2325
|
}
|
|
2206
2326
|
function collectDeclaredSymbols(collection) {
|
|
2207
2327
|
visitDeclaredSymbol(collection, collection.source);
|
|
2208
2328
|
}
|
|
2209
2329
|
function isTopLevelCallback(node) {
|
|
2210
|
-
if (!
|
|
2330
|
+
if (!ts9.isArrowFunction(node) && !ts9.isFunctionExpression(node) || !ts9.isCallExpression(node.parent)) return false;
|
|
2211
2331
|
const callee = symbolCallName(node.parent.expression);
|
|
2212
2332
|
const member = callee.member ?? callee.local;
|
|
2213
2333
|
return Boolean(member && [
|
|
@@ -2249,7 +2369,7 @@ function collectCallbackSymbols(collection) {
|
|
|
2249
2369
|
}
|
|
2250
2370
|
});
|
|
2251
2371
|
}
|
|
2252
|
-
|
|
2372
|
+
ts9.forEachChild(node, visit);
|
|
2253
2373
|
};
|
|
2254
2374
|
visit(collection.source);
|
|
2255
2375
|
}
|
|
@@ -2279,12 +2399,12 @@ function populateCollection(collection) {
|
|
|
2279
2399
|
async function sourceFile(repoPath, filePath, context) {
|
|
2280
2400
|
const snapshot = context?.get(filePath);
|
|
2281
2401
|
const text = snapshot?.text ?? await fs4.readFile(path4.join(repoPath, filePath), "utf8");
|
|
2282
|
-
return snapshot?.sourceFile() ??
|
|
2402
|
+
return snapshot?.sourceFile() ?? ts9.createSourceFile(
|
|
2283
2403
|
filePath,
|
|
2284
2404
|
text,
|
|
2285
|
-
|
|
2405
|
+
ts9.ScriptTarget.Latest,
|
|
2286
2406
|
true,
|
|
2287
|
-
filePath.endsWith(".ts") ?
|
|
2407
|
+
filePath.endsWith(".ts") ? ts9.ScriptKind.TS : ts9.ScriptKind.JS
|
|
2288
2408
|
);
|
|
2289
2409
|
}
|
|
2290
2410
|
async function parseExecutableSymbols(repoPath, filePath, context, preparedOutboundCalls) {
|
|
@@ -2366,13 +2486,6 @@ function mergePackageSymbolEvidence(symbols, analysis) {
|
|
|
2366
2486
|
});
|
|
2367
2487
|
}
|
|
2368
2488
|
|
|
2369
|
-
// src/utils/hashing.ts
|
|
2370
|
-
import { createHash } from "crypto";
|
|
2371
|
-
import { readFile } from "fs/promises";
|
|
2372
|
-
function sha256Text(text) {
|
|
2373
|
-
return createHash("sha256").update(text).digest("hex");
|
|
2374
|
-
}
|
|
2375
|
-
|
|
2376
2489
|
// src/db/package-target-invalidation.ts
|
|
2377
2490
|
var resolverKeys = /* @__PURE__ */ new Set([
|
|
2378
2491
|
"candidateStrategy",
|
|
@@ -2403,6 +2516,11 @@ function parsedEvidence(value) {
|
|
|
2403
2516
|
function packageName(evidence) {
|
|
2404
2517
|
return parsePackageImportReference(evidence.importBinding)?.requestedPackageName ?? void 0;
|
|
2405
2518
|
}
|
|
2519
|
+
function eventPackageName(evidence) {
|
|
2520
|
+
return parsePackageImportReference(
|
|
2521
|
+
evidence.eventNameConstantImportBinding
|
|
2522
|
+
)?.requestedPackageName ?? void 0;
|
|
2523
|
+
}
|
|
2406
2524
|
function packageCallEvidenceValid(evidence) {
|
|
2407
2525
|
const binding = record(evidence.importBinding);
|
|
2408
2526
|
const classified = evidence.relation === "package_import" || binding?.moduleKind === "package";
|
|
@@ -2439,6 +2557,50 @@ function pendingEvidence(evidence) {
|
|
|
2439
2557
|
unresolvedReason: "package_resolution_pending"
|
|
2440
2558
|
});
|
|
2441
2559
|
}
|
|
2560
|
+
function currentEventCalls(db, workspaceId, targetRepoId) {
|
|
2561
|
+
const rows = db.prepare(`SELECT c.id,c.repo_id repoId,
|
|
2562
|
+
c.unresolved_reason unresolvedReason,c.evidence_json evidenceJson
|
|
2563
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
2564
|
+
WHERE r.workspace_id=? AND r.id<>? AND r.fact_analyzer_version=?
|
|
2565
|
+
AND json_extract(c.evidence_json,
|
|
2566
|
+
'$.eventNameConstantImportBinding.moduleKind')='package'
|
|
2567
|
+
ORDER BY c.id`).all(workspaceId, targetRepoId, ANALYZER_VERSION);
|
|
2568
|
+
return rows.flatMap((row) => {
|
|
2569
|
+
const evidence = parsedEvidence(row.evidenceJson);
|
|
2570
|
+
return evidence && eventPackageName(evidence) && typeof row.id === "number" && typeof row.repoId === "number" ? [{
|
|
2571
|
+
id: row.id,
|
|
2572
|
+
repoId: row.repoId,
|
|
2573
|
+
evidence,
|
|
2574
|
+
unresolvedReason: typeof row.unresolvedReason === "string" ? row.unresolvedReason : null
|
|
2575
|
+
}] : [];
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
function pendingEventEvidence(evidence) {
|
|
2579
|
+
const parser = { ...evidence };
|
|
2580
|
+
delete parser.eventNameConstant;
|
|
2581
|
+
delete parser.eventNamePackageConstantResolution;
|
|
2582
|
+
parser.eventNameUnresolvedReason = "event_name_constant_resolution_pending";
|
|
2583
|
+
parser.eventNameStatus = "dynamic";
|
|
2584
|
+
parser.eventNameSourceKind = "dynamic_expression";
|
|
2585
|
+
parser.eventNamePlaceholderKeys = [];
|
|
2586
|
+
return JSON.stringify(parser);
|
|
2587
|
+
}
|
|
2588
|
+
function resetPackageEventCalls(db, workspaceId, targetRepoId, names, batch) {
|
|
2589
|
+
const update = db.prepare(`UPDATE outbound_calls SET event_name_expr=?,
|
|
2590
|
+
unresolved_reason=?,evidence_json=? WHERE id=?`);
|
|
2591
|
+
let matched = false;
|
|
2592
|
+
for (const call of currentEventCalls(db, workspaceId, targetRepoId)) {
|
|
2593
|
+
if (!names.has(eventPackageName(call.evidence) ?? "")) continue;
|
|
2594
|
+
const source = call.evidence.eventNameConstantSourceExpression;
|
|
2595
|
+
if (typeof source !== "string" || source.length === 0)
|
|
2596
|
+
throw new Error("invalid_current_package_event_constant_evidence");
|
|
2597
|
+
const reason = call.evidence.receiverClassification === "unproven" ? call.unresolvedReason : "event_name_constant_resolution_pending";
|
|
2598
|
+
update.run(source, reason, pendingEventEvidence(call.evidence), call.id);
|
|
2599
|
+
batch.affectedCallerRepoIds.add(call.repoId);
|
|
2600
|
+
matched = true;
|
|
2601
|
+
}
|
|
2602
|
+
return matched;
|
|
2603
|
+
}
|
|
2442
2604
|
function targetWorkspace(db, repoId) {
|
|
2443
2605
|
const row = db.prepare(`SELECT workspace_id workspaceId,
|
|
2444
2606
|
package_name packageName FROM repositories WHERE id=?`).get(repoId);
|
|
@@ -2473,6 +2635,13 @@ function invalidatePackageTargetFacts(db, targetRepoId, newPackageName, batch) {
|
|
|
2473
2635
|
batch.affectedCallerRepoIds.add(call.repoId);
|
|
2474
2636
|
matched = true;
|
|
2475
2637
|
}
|
|
2638
|
+
matched = resetPackageEventCalls(
|
|
2639
|
+
db,
|
|
2640
|
+
target.workspaceId,
|
|
2641
|
+
targetRepoId,
|
|
2642
|
+
names,
|
|
2643
|
+
batch
|
|
2644
|
+
) || matched;
|
|
2476
2645
|
if (matched || packageIdentityChanged(
|
|
2477
2646
|
target.packageName,
|
|
2478
2647
|
newPackageName
|
|
@@ -2503,6 +2672,29 @@ function finalizePackageTargetInvalidations(db, batch) {
|
|
|
2503
2672
|
stale.run(workspaceId);
|
|
2504
2673
|
}
|
|
2505
2674
|
|
|
2675
|
+
// src/db/event-surface-invalidation.ts
|
|
2676
|
+
function priorEventFactCount(db, repoId) {
|
|
2677
|
+
const row = db.prepare(`SELECT COUNT(*) count FROM outbound_calls
|
|
2678
|
+
WHERE repo_id=? AND call_type IN ('async_emit','async_subscribe')`).get(
|
|
2679
|
+
repoId
|
|
2680
|
+
);
|
|
2681
|
+
return Number(row?.count ?? 0);
|
|
2682
|
+
}
|
|
2683
|
+
function repositoryEnvironment(db, repoId) {
|
|
2684
|
+
const row = db.prepare(`SELECT environment_declarations_json value
|
|
2685
|
+
FROM repositories WHERE id=?`).get(repoId);
|
|
2686
|
+
return typeof row?.value === "string" ? row.value : null;
|
|
2687
|
+
}
|
|
2688
|
+
function invalidateEventSurfaceFacts(db, repoId, calls, nextEnvironmentJson) {
|
|
2689
|
+
const hasNewEvents = calls.some((call) => call.callType === "async_emit" || call.callType === "async_subscribe");
|
|
2690
|
+
const environmentChanged = repositoryEnvironment(db, repoId) !== nextEnvironmentJson;
|
|
2691
|
+
if (!hasNewEvents && priorEventFactCount(db, repoId) === 0 && !environmentChanged) return;
|
|
2692
|
+
db.prepare(`UPDATE repositories SET
|
|
2693
|
+
graph_stale_reason='event_surface_facts_changed',
|
|
2694
|
+
graph_stale_at=datetime('now')
|
|
2695
|
+
WHERE workspace_id=(SELECT workspace_id FROM repositories WHERE id=?)`).run(repoId);
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2506
2698
|
// src/indexer/repository-indexer.ts
|
|
2507
2699
|
async function prepareRepositoryIndex(repo, force, instrumentation) {
|
|
2508
2700
|
const sourceFiles = await findSourceFiles(repo.absolute_path);
|
|
@@ -2539,6 +2731,7 @@ async function prepareRepositoryIndex(repo, force, instrumentation) {
|
|
|
2539
2731
|
kind: await classifyRepository(repo.absolute_path, packageFacts),
|
|
2540
2732
|
parsed,
|
|
2541
2733
|
packagePublicSurface: packageSurface.surface,
|
|
2734
|
+
environmentDeclarations: collectEnvironmentDeclarations(sources),
|
|
2542
2735
|
fileCount: sourceFiles.length,
|
|
2543
2736
|
diagnosticCount: parsed.handlers.filter((handler) => handler.hasHandlerDecorator && (handler.methods.length === 0 || handler.methods.some((method) => !handlerMethodIsExecutable(method)))).length,
|
|
2544
2737
|
skipped: false
|
|
@@ -2546,23 +2739,32 @@ async function prepareRepositoryIndex(repo, force, instrumentation) {
|
|
|
2546
2739
|
}
|
|
2547
2740
|
function publishPreparedRepositoryIndex(db, prepared, invalidations) {
|
|
2548
2741
|
if (prepared.skipped) return;
|
|
2549
|
-
if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint || !prepared.kind || !prepared.packagePublicSurface)
|
|
2742
|
+
if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint || !prepared.kind || !prepared.packagePublicSurface || !prepared.environmentDeclarations)
|
|
2550
2743
|
throw new Error("Prepared repository index is missing publication facts");
|
|
2551
2744
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2552
2745
|
const repoId = prepared.repo.id;
|
|
2746
|
+
const environmentJson = JSON.stringify(prepared.environmentDeclarations);
|
|
2553
2747
|
invalidatePackageTargetFacts(
|
|
2554
2748
|
db,
|
|
2555
2749
|
repoId,
|
|
2556
2750
|
prepared.packageFacts.packageName,
|
|
2557
2751
|
invalidations
|
|
2558
2752
|
);
|
|
2753
|
+
invalidateEventSurfaceFacts(
|
|
2754
|
+
db,
|
|
2755
|
+
repoId,
|
|
2756
|
+
prepared.parsed.calls,
|
|
2757
|
+
environmentJson
|
|
2758
|
+
);
|
|
2559
2759
|
db.prepare(`UPDATE repositories SET package_name=?, package_version=?,
|
|
2560
|
-
dependencies_json=?,package_public_surface_json=?,
|
|
2760
|
+
dependencies_json=?,package_public_surface_json=?,
|
|
2761
|
+
environment_declarations_json=?,kind=?,index_status=?
|
|
2561
2762
|
WHERE id=?`).run(
|
|
2562
2763
|
prepared.packageFacts.packageName,
|
|
2563
2764
|
prepared.packageFacts.packageVersion,
|
|
2564
2765
|
JSON.stringify(prepared.packageFacts.dependencies),
|
|
2565
2766
|
JSON.stringify(prepared.packagePublicSurface),
|
|
2767
|
+
environmentJson,
|
|
2566
2768
|
prepared.kind,
|
|
2567
2769
|
"indexing",
|
|
2568
2770
|
repoId
|
|
@@ -2578,6 +2780,7 @@ function publishPreparedRepositoryIndex(db, prepared, invalidations) {
|
|
|
2578
2780
|
insertRegistrations(db, repoId, prepared.parsed.registrations);
|
|
2579
2781
|
insertBindings(db, repoId, prepared.parsed.bindings);
|
|
2580
2782
|
insertCalls(db, repoId, prepared.parsed.calls);
|
|
2783
|
+
insertGeneratedConstants(db, repoId, prepared.parsed.generatedConstants);
|
|
2581
2784
|
db.prepare("UPDATE repositories SET last_indexed_at=?, index_status='indexed', error_count=0, fingerprint=?, fact_generation=COALESCE(fact_generation,0)+1, graph_stale_reason='facts_changed', graph_stale_at=?, fact_analyzer_version=? WHERE id=?").run(now, prepared.fingerprint, now, ANALYZER_VERSION, repoId);
|
|
2582
2785
|
}
|
|
2583
2786
|
function publishOneRepository(db, prepared, invalidations) {
|
|
@@ -2620,14 +2823,22 @@ function recordIndexFailure(db, repoId, error) {
|
|
|
2620
2823
|
db.prepare("INSERT INTO diagnostics(repo_id,severity,code,message) VALUES(?,?,?,?)").run(repoId, "error", "source_read_failed", `Index failed before publication; previous facts and fingerprint were preserved. ${message}`);
|
|
2621
2824
|
}
|
|
2622
2825
|
async function parseAllSourceFacts(root, sources) {
|
|
2623
|
-
const facts = { services: [], handlers: [], registrations: [], bindings: [], calls: [], symbols: [], symbolCalls: [], fileRecords: [] };
|
|
2826
|
+
const facts = { services: [], handlers: [], registrations: [], bindings: [], calls: [], symbols: [], symbolCalls: [], generatedConstants: [], fileRecords: [] };
|
|
2624
2827
|
for (const snapshot of sources.entries()) {
|
|
2625
2828
|
const file = snapshot.filePath;
|
|
2626
2829
|
facts.fileRecords.push({ relativePath: normalizePath(file), extension: path5.extname(file), sha256: sha256Text(snapshot.text), sizeBytes: snapshot.sizeBytes });
|
|
2627
2830
|
if (file.endsWith(".cds")) facts.services.push(...await parseCdsFile(root, file, sources));
|
|
2628
2831
|
if (/\.[jt]s$/.test(file)) {
|
|
2629
2832
|
const source = snapshot.sourceFile();
|
|
2630
|
-
|
|
2833
|
+
facts.generatedConstants.push(...generatedConstantFacts(source, file));
|
|
2834
|
+
const classified = classifyOutboundCallsInSource(source, file, {
|
|
2835
|
+
importedEventNameResolver: createImportedEventNameResolver(
|
|
2836
|
+
sources,
|
|
2837
|
+
source,
|
|
2838
|
+
file
|
|
2839
|
+
),
|
|
2840
|
+
eventEnvironmentReferenceResolver: createEventEnvironmentReferenceResolver(sources, source, file)
|
|
2841
|
+
});
|
|
2631
2842
|
facts.handlers.push(...await parseDecorators(root, file, sources));
|
|
2632
2843
|
facts.registrations.push(...await parseHandlerRegistrations(root, file, sources));
|
|
2633
2844
|
const bindings = await parseServiceBindings(root, file, sources);
|
|
@@ -2668,12 +2879,15 @@ async function findSourceFiles(root) {
|
|
|
2668
2879
|
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
2669
2880
|
if (e.isDirectory()) {
|
|
2670
2881
|
if (!["node_modules", "dist", "gen", "coverage", ".git"].includes(e.name)) await walk(path5.join(dir, e.name), rel);
|
|
2671
|
-
} else if (
|
|
2882
|
+
} else if (isRepositoryFactInput(e.name) && !isDefaultTestFile(rel)) out.push(rel);
|
|
2672
2883
|
}
|
|
2673
2884
|
}
|
|
2674
2885
|
await walk(root);
|
|
2675
2886
|
return out.sort();
|
|
2676
2887
|
}
|
|
2888
|
+
function isRepositoryFactInput(name) {
|
|
2889
|
+
return /\.(cds|ts|js)$/.test(name) || ["nodemon.json", ".env", "mta.yaml", "manifest.yml"].includes(name);
|
|
2890
|
+
}
|
|
2677
2891
|
function isDefaultTestFile(relativeFile) {
|
|
2678
2892
|
const parts = relativeFile.split("/");
|
|
2679
2893
|
if (parts.some((part) => ["test", "tests", "__tests__"].includes(part))) return true;
|
|
@@ -3249,6 +3463,332 @@ function symbolCallQuality(db) {
|
|
|
3249
3463
|
};
|
|
3250
3464
|
}
|
|
3251
3465
|
|
|
3466
|
+
// src/cli/doctor-event-quality.ts
|
|
3467
|
+
var eventNameReason = `COALESCE(
|
|
3468
|
+
json_extract(c.evidence_json,'$.eventNameUnresolvedReason'),
|
|
3469
|
+
CASE WHEN c.unresolved_reason GLOB 'dynamic_event_name_*'
|
|
3470
|
+
OR c.unresolved_reason GLOB 'event_name_*'
|
|
3471
|
+
THEN c.unresolved_reason END
|
|
3472
|
+
)`;
|
|
3473
|
+
function workspacePredicate(alias) {
|
|
3474
|
+
return `(? IS NULL OR ${alias}.workspace_id=?)`;
|
|
3475
|
+
}
|
|
3476
|
+
function count2(value) {
|
|
3477
|
+
return Number(value ?? 0);
|
|
3478
|
+
}
|
|
3479
|
+
function unresolvedEventNameExamples(db, workspaceId) {
|
|
3480
|
+
return db.prepare(`SELECT r.name repositoryName,
|
|
3481
|
+
c.source_file sourceFile,c.source_line sourceLine,
|
|
3482
|
+
c.event_name_expr eventName,${eventNameReason} reason
|
|
3483
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
3484
|
+
WHERE c.call_type='async_emit' AND ${workspacePredicate("r")}
|
|
3485
|
+
AND ${eventNameReason} IS NOT NULL
|
|
3486
|
+
ORDER BY r.name COLLATE BINARY,c.source_file COLLATE BINARY,
|
|
3487
|
+
c.source_line,c.id LIMIT 5`).all(
|
|
3488
|
+
workspaceId,
|
|
3489
|
+
workspaceId
|
|
3490
|
+
);
|
|
3491
|
+
}
|
|
3492
|
+
function eventNameResolutionQuality(db, workspaceId) {
|
|
3493
|
+
const aggregate = db.prepare(`SELECT COUNT(*) total,
|
|
3494
|
+
SUM(CASE WHEN reason IS NOT NULL THEN 1 ELSE 0 END) unresolved
|
|
3495
|
+
FROM (SELECT ${eventNameReason} reason
|
|
3496
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
3497
|
+
WHERE c.call_type='async_emit' AND ${workspacePredicate("r")})`).get(
|
|
3498
|
+
workspaceId,
|
|
3499
|
+
workspaceId
|
|
3500
|
+
);
|
|
3501
|
+
const reasons = db.prepare(`SELECT ${eventNameReason} reason,COUNT(*) count
|
|
3502
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
3503
|
+
WHERE c.call_type='async_emit' AND ${workspacePredicate("r")}
|
|
3504
|
+
AND ${eventNameReason} IS NOT NULL
|
|
3505
|
+
GROUP BY reason ORDER BY count DESC,reason COLLATE BINARY LIMIT 16`).all(
|
|
3506
|
+
workspaceId,
|
|
3507
|
+
workspaceId
|
|
3508
|
+
);
|
|
3509
|
+
const unresolved = count2(aggregate?.unresolved);
|
|
3510
|
+
return {
|
|
3511
|
+
severity: unresolved > 0 ? "warning" : "info",
|
|
3512
|
+
code: "strict_event_name_resolution_quality",
|
|
3513
|
+
message: "Event publication name-resolution aggregate",
|
|
3514
|
+
publicationTotal: count2(aggregate?.total),
|
|
3515
|
+
unresolvedPublicationCount: unresolved,
|
|
3516
|
+
reasonBuckets: reasons,
|
|
3517
|
+
examples: unresolvedEventNameExamples(db, workspaceId),
|
|
3518
|
+
exampleCount: unresolved
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3521
|
+
function dynamicEventExamples(db, workspaceId) {
|
|
3522
|
+
return db.prepare(`SELECT r.name repositoryName,c.call_type callType,
|
|
3523
|
+
c.source_file sourceFile,c.source_line sourceLine,
|
|
3524
|
+
e.to_kind targetKind,e.to_id targetId,e.unresolved_reason reason
|
|
3525
|
+
FROM graph_edges e JOIN outbound_calls c
|
|
3526
|
+
ON e.from_kind='call' AND c.id=CAST(e.from_id AS INTEGER)
|
|
3527
|
+
JOIN repositories r ON r.id=c.repo_id
|
|
3528
|
+
WHERE c.call_type IN ('async_emit','async_subscribe')
|
|
3529
|
+
AND ${workspacePredicate("r")}
|
|
3530
|
+
AND (e.to_kind='event_candidate'
|
|
3531
|
+
OR e.edge_type='DYNAMIC_EDGE_CANDIDATE')
|
|
3532
|
+
ORDER BY r.name COLLATE BINARY,c.source_file COLLATE BINARY,
|
|
3533
|
+
c.source_line,c.id LIMIT 5`).all(
|
|
3534
|
+
workspaceId,
|
|
3535
|
+
workspaceId
|
|
3536
|
+
);
|
|
3537
|
+
}
|
|
3538
|
+
function dynamicEventQuality(db, workspaceId) {
|
|
3539
|
+
const row = db.prepare(`SELECT
|
|
3540
|
+
COUNT(DISTINCT CASE WHEN e.to_kind='event_candidate'
|
|
3541
|
+
THEN e.workspace_id || ':' || e.to_id END) eventCandidateNodeCount,
|
|
3542
|
+
COUNT(*) dynamicEventEdgeCount,
|
|
3543
|
+
SUM(CASE WHEN json_array_length(e.evidence_json,
|
|
3544
|
+
'$.eventTemplateResolution.placeholders')>0 THEN 1 ELSE 0 END)
|
|
3545
|
+
variableRecoverableCount
|
|
3546
|
+
FROM graph_edges e JOIN outbound_calls c
|
|
3547
|
+
ON e.from_kind='call' AND c.id=CAST(e.from_id AS INTEGER)
|
|
3548
|
+
JOIN repositories r ON r.id=c.repo_id
|
|
3549
|
+
WHERE c.call_type IN ('async_emit','async_subscribe')
|
|
3550
|
+
AND ${workspacePredicate("r")}
|
|
3551
|
+
AND (e.to_kind='event_candidate'
|
|
3552
|
+
OR e.edge_type='DYNAMIC_EDGE_CANDIDATE')`).get(
|
|
3553
|
+
workspaceId,
|
|
3554
|
+
workspaceId
|
|
3555
|
+
);
|
|
3556
|
+
const total = count2(row?.dynamicEventEdgeCount);
|
|
3557
|
+
const recoverable = count2(row?.variableRecoverableCount);
|
|
3558
|
+
return {
|
|
3559
|
+
severity: total > 0 ? "warning" : "info",
|
|
3560
|
+
code: "strict_event_dynamic_candidate_quality",
|
|
3561
|
+
message: "Dynamic event candidate aggregate",
|
|
3562
|
+
eventCandidateNodeCount: count2(row?.eventCandidateNodeCount),
|
|
3563
|
+
dynamicEventEdgeCount: total,
|
|
3564
|
+
eventCandidateCount: total,
|
|
3565
|
+
variableRecoverableCount: recoverable,
|
|
3566
|
+
nonVariableRecoverableCount: Math.max(0, total - recoverable),
|
|
3567
|
+
examples: dynamicEventExamples(db, workspaceId),
|
|
3568
|
+
exampleCount: total
|
|
3569
|
+
};
|
|
3570
|
+
}
|
|
3571
|
+
function unmatchedPublicationRows(db, workspaceId) {
|
|
3572
|
+
return db.prepare(`SELECT r.name repositoryName,c.source_file sourceFile,
|
|
3573
|
+
c.source_line sourceLine,e.to_id eventName
|
|
3574
|
+
FROM graph_edges e JOIN outbound_calls c
|
|
3575
|
+
ON e.from_kind='call' AND c.id=CAST(e.from_id AS INTEGER)
|
|
3576
|
+
JOIN repositories r ON r.id=c.repo_id
|
|
3577
|
+
WHERE c.call_type='async_emit' AND e.edge_type='HANDLER_EMITS_EVENT'
|
|
3578
|
+
AND e.to_kind='event' AND ${workspacePredicate("r")}
|
|
3579
|
+
AND NOT EXISTS (SELECT 1 FROM graph_edges subscription
|
|
3580
|
+
WHERE subscription.workspace_id=e.workspace_id
|
|
3581
|
+
AND subscription.generation=e.generation
|
|
3582
|
+
AND subscription.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
3583
|
+
AND subscription.from_kind='event'
|
|
3584
|
+
AND subscription.from_id=e.to_id)
|
|
3585
|
+
ORDER BY r.name COLLATE BINARY,c.source_file COLLATE BINARY,
|
|
3586
|
+
c.source_line,c.id LIMIT 5`).all(
|
|
3587
|
+
workspaceId,
|
|
3588
|
+
workspaceId
|
|
3589
|
+
);
|
|
3590
|
+
}
|
|
3591
|
+
function unmatchedPublicationQuality(db, workspaceId) {
|
|
3592
|
+
const row = db.prepare(`SELECT COUNT(*) count
|
|
3593
|
+
FROM graph_edges e JOIN outbound_calls c
|
|
3594
|
+
ON e.from_kind='call' AND c.id=CAST(e.from_id AS INTEGER)
|
|
3595
|
+
JOIN repositories r ON r.id=c.repo_id
|
|
3596
|
+
WHERE c.call_type='async_emit' AND e.edge_type='HANDLER_EMITS_EVENT'
|
|
3597
|
+
AND e.to_kind='event' AND ${workspacePredicate("r")}
|
|
3598
|
+
AND NOT EXISTS (SELECT 1 FROM graph_edges subscription
|
|
3599
|
+
WHERE subscription.workspace_id=e.workspace_id
|
|
3600
|
+
AND subscription.generation=e.generation
|
|
3601
|
+
AND subscription.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
3602
|
+
AND subscription.from_kind='event'
|
|
3603
|
+
AND subscription.from_id=e.to_id)`).get(workspaceId, workspaceId);
|
|
3604
|
+
const total = count2(row?.count);
|
|
3605
|
+
return {
|
|
3606
|
+
severity: total > 0 ? "warning" : "info",
|
|
3607
|
+
code: "strict_event_publication_without_subscription_quality",
|
|
3608
|
+
message: "Resolved event publications without a matching subscription",
|
|
3609
|
+
unmatchedPublicationCount: total,
|
|
3610
|
+
examples: unmatchedPublicationRows(db, workspaceId),
|
|
3611
|
+
exampleCount: total
|
|
3612
|
+
};
|
|
3613
|
+
}
|
|
3614
|
+
function unmatchedSubscriptionRows(db, workspaceId) {
|
|
3615
|
+
return db.prepare(`SELECT COALESCE(json_extract(e.evidence_json,
|
|
3616
|
+
'$.subscriptionConsumerRepositoryName'),
|
|
3617
|
+
json_extract(e.evidence_json,'$.repositoryName')) repositoryName,
|
|
3618
|
+
json_extract(e.evidence_json,'$.sourceFile') sourceFile,
|
|
3619
|
+
json_extract(e.evidence_json,'$.sourceLine') sourceLine,
|
|
3620
|
+
e.from_id eventName,
|
|
3621
|
+
json_extract(c.evidence_json,'$.subscriptionLoopRegistrationStatus')
|
|
3622
|
+
loopRegistrationStatus,
|
|
3623
|
+
json_extract(c.evidence_json,'$.subscriptionLoopRegistrationCount')
|
|
3624
|
+
loopRegistrationCount
|
|
3625
|
+
FROM graph_edges e LEFT JOIN outbound_calls c
|
|
3626
|
+
ON c.id=CAST(json_extract(e.evidence_json,'$.subscribeCallId') AS INTEGER)
|
|
3627
|
+
WHERE e.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
3628
|
+
AND ${workspacePredicate("e")}
|
|
3629
|
+
AND NOT EXISTS (SELECT 1 FROM graph_edges publication
|
|
3630
|
+
WHERE publication.workspace_id=e.workspace_id
|
|
3631
|
+
AND publication.generation=e.generation
|
|
3632
|
+
AND publication.edge_type='HANDLER_EMITS_EVENT'
|
|
3633
|
+
AND publication.to_kind='event'
|
|
3634
|
+
AND publication.to_id=e.from_id)
|
|
3635
|
+
ORDER BY repositoryName COLLATE BINARY,sourceFile COLLATE BINARY,
|
|
3636
|
+
sourceLine,e.id LIMIT 5`).all(
|
|
3637
|
+
workspaceId,
|
|
3638
|
+
workspaceId
|
|
3639
|
+
);
|
|
3640
|
+
}
|
|
3641
|
+
function unmatchedSubscriptionQuality(db, workspaceId) {
|
|
3642
|
+
const row = db.prepare(`SELECT COUNT(*) siteCount,
|
|
3643
|
+
SUM(CASE
|
|
3644
|
+
WHEN json_extract(c.evidence_json,
|
|
3645
|
+
'$.subscriptionLoopRegistrationStatus')='enumerated'
|
|
3646
|
+
THEN CAST(json_extract(c.evidence_json,
|
|
3647
|
+
'$.subscriptionLoopRegistrationCount') AS INTEGER)
|
|
3648
|
+
WHEN json_extract(c.evidence_json,
|
|
3649
|
+
'$.subscriptionLoopRegistrationStatus')='unresolved' THEN 0
|
|
3650
|
+
ELSE 1 END) count,
|
|
3651
|
+
SUM(CASE WHEN json_extract(c.evidence_json,
|
|
3652
|
+
'$.subscriptionLoopRegistrationStatus')='unresolved'
|
|
3653
|
+
THEN 1 ELSE 0 END) unknownMultiplicitySiteCount
|
|
3654
|
+
FROM graph_edges e LEFT JOIN outbound_calls c
|
|
3655
|
+
ON c.id=CAST(json_extract(e.evidence_json,'$.subscribeCallId') AS INTEGER)
|
|
3656
|
+
WHERE e.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
3657
|
+
AND ${workspacePredicate("e")}
|
|
3658
|
+
AND NOT EXISTS (SELECT 1 FROM graph_edges publication
|
|
3659
|
+
WHERE publication.workspace_id=e.workspace_id
|
|
3660
|
+
AND publication.generation=e.generation
|
|
3661
|
+
AND publication.edge_type='HANDLER_EMITS_EVENT'
|
|
3662
|
+
AND publication.to_kind='event'
|
|
3663
|
+
AND publication.to_id=e.from_id)`).get(workspaceId, workspaceId);
|
|
3664
|
+
const total = count2(row?.count);
|
|
3665
|
+
const siteCount = count2(row?.siteCount);
|
|
3666
|
+
return {
|
|
3667
|
+
severity: siteCount > 0 ? "warning" : "info",
|
|
3668
|
+
code: "strict_event_subscription_without_publication_quality",
|
|
3669
|
+
message: "Event subscriptions without a matching publication",
|
|
3670
|
+
unmatchedSubscriptionCount: total,
|
|
3671
|
+
unmatchedSubscriptionSiteCount: siteCount,
|
|
3672
|
+
unknownMultiplicitySiteCount: count2(row?.unknownMultiplicitySiteCount),
|
|
3673
|
+
examples: unmatchedSubscriptionRows(db, workspaceId),
|
|
3674
|
+
exampleCount: siteCount
|
|
3675
|
+
};
|
|
3676
|
+
}
|
|
3677
|
+
function receiverProofQuality(db, workspaceId) {
|
|
3678
|
+
const row = db.prepare(`SELECT COUNT(*) eventTotal,
|
|
3679
|
+
SUM(CASE WHEN json_extract(c.evidence_json,
|
|
3680
|
+
'$.receiverClassification')='cap_evidence' THEN 1 ELSE 0 END) proven,
|
|
3681
|
+
SUM(CASE WHEN json_extract(c.evidence_json,
|
|
3682
|
+
'$.receiverClassification')='name_fallback' THEN 1 ELSE 0 END)
|
|
3683
|
+
nameFallback,
|
|
3684
|
+
SUM(CASE WHEN json_extract(c.evidence_json,
|
|
3685
|
+
'$.receiverClassification')='unproven' THEN 1 ELSE 0 END) unproven
|
|
3686
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
3687
|
+
WHERE c.call_type IN ('async_emit','async_subscribe')
|
|
3688
|
+
AND ${workspacePredicate("r")}`).get(workspaceId, workspaceId);
|
|
3689
|
+
const buckets = db.prepare(`SELECT CASE
|
|
3690
|
+
WHEN json_extract(c.evidence_json,
|
|
3691
|
+
'$.receiverClassification')='name_fallback' THEN 'name_fallback'
|
|
3692
|
+
WHEN json_extract(c.evidence_json,
|
|
3693
|
+
'$.receiverClassification')='unproven'
|
|
3694
|
+
THEN COALESCE(c.unresolved_reason,'unproven')
|
|
3695
|
+
ELSE 'missing' END reason,COUNT(*) count
|
|
3696
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
3697
|
+
WHERE c.call_type IN ('async_emit','async_subscribe')
|
|
3698
|
+
AND ${workspacePredicate("r")}
|
|
3699
|
+
AND json_extract(c.evidence_json,'$.receiverClassification')
|
|
3700
|
+
<>'cap_evidence'
|
|
3701
|
+
GROUP BY reason ORDER BY count DESC,reason COLLATE BINARY LIMIT 16`).all(
|
|
3702
|
+
workspaceId,
|
|
3703
|
+
workspaceId
|
|
3704
|
+
);
|
|
3705
|
+
const questionable = count2(row?.nameFallback) + count2(row?.unproven);
|
|
3706
|
+
return {
|
|
3707
|
+
severity: questionable > 0 ? "warning" : "info",
|
|
3708
|
+
code: "strict_event_receiver_classification_quality",
|
|
3709
|
+
message: "CAP event receiver proof aggregate",
|
|
3710
|
+
eventTotal: count2(row?.eventTotal),
|
|
3711
|
+
proven: count2(row?.proven),
|
|
3712
|
+
nameFallback: count2(row?.nameFallback),
|
|
3713
|
+
unproven: count2(row?.unproven),
|
|
3714
|
+
questionable,
|
|
3715
|
+
reasonBuckets: buckets,
|
|
3716
|
+
examples: receiverProofExamples(db, workspaceId),
|
|
3717
|
+
exampleCount: questionable
|
|
3718
|
+
};
|
|
3719
|
+
}
|
|
3720
|
+
function receiverProofExamples(db, workspaceId) {
|
|
3721
|
+
return db.prepare(`SELECT r.name repositoryName,c.call_type callType,
|
|
3722
|
+
c.source_file sourceFile,c.source_line sourceLine,
|
|
3723
|
+
json_extract(c.evidence_json,'$.receiverClassification')
|
|
3724
|
+
receiverClassification,
|
|
3725
|
+
c.unresolved_reason reason
|
|
3726
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
3727
|
+
WHERE c.call_type IN ('async_emit','async_subscribe')
|
|
3728
|
+
AND ${workspacePredicate("r")}
|
|
3729
|
+
AND json_extract(c.evidence_json,'$.receiverClassification')
|
|
3730
|
+
<>'cap_evidence'
|
|
3731
|
+
ORDER BY r.name COLLATE BINARY,c.source_file COLLATE BINARY,
|
|
3732
|
+
c.source_line,c.id LIMIT 5`).all(
|
|
3733
|
+
workspaceId,
|
|
3734
|
+
workspaceId
|
|
3735
|
+
);
|
|
3736
|
+
}
|
|
3737
|
+
function shapeEnvironmentExamples(db, workspaceId) {
|
|
3738
|
+
return db.prepare(`SELECT e.edge_type edgeType,e.from_id fromId,
|
|
3739
|
+
e.to_kind toKind,e.to_id toId,
|
|
3740
|
+
json_extract(e.evidence_json,
|
|
3741
|
+
'$.eventEnvironmentResolution.status') environmentStatus,
|
|
3742
|
+
json_extract(e.evidence_json,
|
|
3743
|
+
'$.eventEnvironmentResolution.collisionCount') collisionCount
|
|
3744
|
+
FROM graph_edges e WHERE ${workspacePredicate("e")}
|
|
3745
|
+
AND (e.edge_type='EVENT_SHAPE_CANDIDATE_SUBSCRIBER'
|
|
3746
|
+
OR json_extract(e.evidence_json,
|
|
3747
|
+
'$.eventEnvironmentResolution.status')='ambiguous'
|
|
3748
|
+
OR CAST(json_extract(e.evidence_json,
|
|
3749
|
+
'$.eventEnvironmentResolution.collisionCount') AS INTEGER)>1)
|
|
3750
|
+
ORDER BY e.edge_type COLLATE BINARY,e.from_id COLLATE BINARY,
|
|
3751
|
+
e.to_id COLLATE BINARY,e.id LIMIT 5`).all(
|
|
3752
|
+
workspaceId,
|
|
3753
|
+
workspaceId
|
|
3754
|
+
);
|
|
3755
|
+
}
|
|
3756
|
+
function shapeEnvironmentQuality(db, workspaceId) {
|
|
3757
|
+
const row = db.prepare(`SELECT
|
|
3758
|
+
SUM(CASE WHEN e.edge_type='EVENT_SHAPE_CANDIDATE_SUBSCRIBER'
|
|
3759
|
+
THEN 1 ELSE 0 END) shapeCandidates,
|
|
3760
|
+
SUM(CASE WHEN json_extract(e.evidence_json,
|
|
3761
|
+
'$.eventEnvironmentResolution.status')='ambiguous'
|
|
3762
|
+
OR CAST(json_extract(e.evidence_json,
|
|
3763
|
+
'$.eventEnvironmentResolution.collisionCount') AS INTEGER)>1
|
|
3764
|
+
THEN 1 ELSE 0 END) environmentAmbiguities
|
|
3765
|
+
FROM graph_edges e WHERE ${workspacePredicate("e")}`).get(
|
|
3766
|
+
workspaceId,
|
|
3767
|
+
workspaceId
|
|
3768
|
+
);
|
|
3769
|
+
const shapes = count2(row?.shapeCandidates);
|
|
3770
|
+
const ambiguous = count2(row?.environmentAmbiguities);
|
|
3771
|
+
return {
|
|
3772
|
+
severity: shapes + ambiguous > 0 ? "warning" : "info",
|
|
3773
|
+
code: "strict_event_shape_environment_quality",
|
|
3774
|
+
message: "Event skeleton candidate and environment ambiguity aggregate",
|
|
3775
|
+
skeletonCandidateCount: shapes,
|
|
3776
|
+
environmentBindingAmbiguityCount: ambiguous,
|
|
3777
|
+
examples: shapeEnvironmentExamples(db, workspaceId),
|
|
3778
|
+
exampleCount: shapes + ambiguous
|
|
3779
|
+
};
|
|
3780
|
+
}
|
|
3781
|
+
function eventSurfaceQualityDiagnostics(db, workspaceId) {
|
|
3782
|
+
return [
|
|
3783
|
+
eventNameResolutionQuality(db, workspaceId),
|
|
3784
|
+
dynamicEventQuality(db, workspaceId),
|
|
3785
|
+
unmatchedPublicationQuality(db, workspaceId),
|
|
3786
|
+
unmatchedSubscriptionQuality(db, workspaceId),
|
|
3787
|
+
receiverProofQuality(db, workspaceId),
|
|
3788
|
+
shapeEnvironmentQuality(db, workspaceId)
|
|
3789
|
+
];
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3252
3792
|
// src/cli/doctor.ts
|
|
3253
3793
|
function doctorDiagnostics(db, strict, options = {}) {
|
|
3254
3794
|
const lifecycle = factLifecycleDiagnostic(db, options.workspaceId);
|
|
@@ -3387,7 +3927,7 @@ function parserQualityDiagnostics(db, strict, options) {
|
|
|
3387
3927
|
externalHttpTargetQuality(db),
|
|
3388
3928
|
odataInvocationResolutionQuality(db),
|
|
3389
3929
|
...jsonEvidenceQuality(db),
|
|
3390
|
-
|
|
3930
|
+
...eventSurfaceQualityDiagnostics(db, options.workspaceId),
|
|
3391
3931
|
graphDynamicFlagQuality(db),
|
|
3392
3932
|
symbol,
|
|
3393
3933
|
dbq,
|
|
@@ -3422,10 +3962,6 @@ function outboundOwnershipQuality(db) {
|
|
|
3422
3962
|
const ratio = total === 0 ? 0 : Number((without / total).toFixed(4));
|
|
3423
3963
|
return { severity: ratio > 0.01 ? "warning" : "info", code: "strict_outbound_source_ownership_quality", message: "Outbound call source-symbol ownership aggregate", total, withoutOwnership: without, withoutOwnershipRatio: ratio, withoutOwnershipRatioThreshold: 0.01, ownerlessByType: byType, ownerlessExamples: examples };
|
|
3424
3964
|
}
|
|
3425
|
-
function eventReceiverQuality(db) {
|
|
3426
|
-
const row = db.prepare("SELECT SUM(CASE WHEN call_type IN ('async_emit','async_subscribe') THEN 1 ELSE 0 END) eventTotal, SUM(CASE WHEN call_type IN ('async_emit','async_subscribe') AND (json_extract(evidence_json,'$.receiverClassification') IS NULL OR json_extract(evidence_json,'$.receiverClassification') <> 'cap_evidence') THEN 1 ELSE 0 END) questionable FROM outbound_calls").get();
|
|
3427
|
-
return { severity: Number(row.questionable ?? 0) > 0 ? "warning" : "info", code: "strict_event_receiver_classification_quality", message: "CAP event receiver classification aggregate", eventTotal: Number(row.eventTotal ?? 0), questionable: Number(row.questionable ?? 0) };
|
|
3428
|
-
}
|
|
3429
3965
|
function graphDynamicFlagQuality(db) {
|
|
3430
3966
|
const row = db.prepare("SELECT COUNT(*) count FROM graph_edges WHERE status='terminal' AND is_dynamic=1").get();
|
|
3431
3967
|
return { severity: Number(row.count ?? 0) > 0 ? "warning" : "info", code: "strict_graph_dynamic_flag_consistency", message: "Graph dynamic flag consistency aggregate", dynamicTerminalEdges: Number(row.count ?? 0) };
|
|
@@ -3861,7 +4397,7 @@ function location(evidence) {
|
|
|
3861
4397
|
function renderTraceTable(result) {
|
|
3862
4398
|
const lines = ["Step Type From To Evidence"];
|
|
3863
4399
|
for (const e of result.edges) {
|
|
3864
|
-
lines.push(`${String(e.step).padEnd(5)} ${e.type.padEnd(20)} ${e.from.slice(0, 34).padEnd(35)} ${e.to.slice(0, 35).padEnd(36)} ${
|
|
4400
|
+
lines.push(`${String(e.step).padEnd(5)} ${e.type.padEnd(20)} ${e.from.slice(0, 34).padEnd(35)} ${e.to.slice(0, 35).padEnd(36)} ${evidenceSummary(e.evidence)}`);
|
|
3865
4401
|
if (e.unresolvedReason)
|
|
3866
4402
|
lines.push(...hintLines(e.evidence).map((hint) => ` ${hint}`));
|
|
3867
4403
|
}
|
|
@@ -3869,6 +4405,13 @@ function renderTraceTable(result) {
|
|
|
3869
4405
|
return `${lines.join("\n")}
|
|
3870
4406
|
`;
|
|
3871
4407
|
}
|
|
4408
|
+
function evidenceSummary(evidence) {
|
|
4409
|
+
const labels = [
|
|
4410
|
+
typeof evidence.dispatchScope === "string" ? `scope=${evidence.dispatchScope}` : void 0,
|
|
4411
|
+
typeof evidence.dispatchCertainty === "string" ? `certainty=${evidence.dispatchCertainty}` : void 0
|
|
4412
|
+
].filter((value) => Boolean(value));
|
|
4413
|
+
return labels.length > 0 ? `${location(evidence)} [${labels.join(",")}]` : location(evidence);
|
|
4414
|
+
}
|
|
3872
4415
|
function diagnosticLines(diagnostic) {
|
|
3873
4416
|
const first = `${String(diagnostic.severity ?? "info")} ${String(diagnostic.code ?? "diagnostic")} ${String(diagnostic.message ?? "")}`;
|
|
3874
4417
|
const details = diagnosticDetailLines(diagnostic);
|
|
@@ -3906,8 +4449,8 @@ function hintLines(evidence) {
|
|
|
3906
4449
|
}
|
|
3907
4450
|
function dynamicHintLines(evidence) {
|
|
3908
4451
|
const exploration = isRecord(evidence.dynamicTargetExploration) ? evidence.dynamicTargetExploration : evidence;
|
|
3909
|
-
const
|
|
3910
|
-
if (
|
|
4452
|
+
const count3 = numberValue(exploration.candidateCount);
|
|
4453
|
+
if (count3 === 0) return [];
|
|
3911
4454
|
const shown = numberValue(exploration.shownCandidateCount);
|
|
3912
4455
|
const omitted = numberValue(exploration.omittedCandidateCount);
|
|
3913
4456
|
const rejected = numberValue(exploration.rejectedCandidateCount);
|
|
@@ -3915,7 +4458,7 @@ function dynamicHintLines(evidence) {
|
|
|
3915
4458
|
`viable candidates: ${shown} shown, ${omitted} omitted; rejected: ${rejected}`
|
|
3916
4459
|
];
|
|
3917
4460
|
lines.push(...varSetHints(exploration.suggestedVarSets));
|
|
3918
|
-
if (omitted > 0 || rejected > 0 || shown <
|
|
4461
|
+
if (omitted > 0 || rejected > 0 || shown < count3)
|
|
3919
4462
|
lines.push("use --dynamic-mode candidates --max-dynamic-candidates 20 to explore candidate branches");
|
|
3920
4463
|
return lines;
|
|
3921
4464
|
}
|
|
@@ -3985,9 +4528,9 @@ function diagnosticLocation(diagnostic) {
|
|
|
3985
4528
|
}
|
|
3986
4529
|
function compactMessage(diagnostic) {
|
|
3987
4530
|
const message = String(diagnostic.message ?? "");
|
|
3988
|
-
const
|
|
4531
|
+
const count3 = typeof diagnostic.count === "number" ? ` count=${diagnostic.count}` : "";
|
|
3989
4532
|
const total = typeof diagnostic.total === "number" ? ` total=${diagnostic.total}` : "";
|
|
3990
|
-
return `${message}${
|
|
4533
|
+
return `${message}${count3}${total}`.trim();
|
|
3991
4534
|
}
|
|
3992
4535
|
function suggestedHintLines(diagnostic) {
|
|
3993
4536
|
const direct = cliHints(diagnostic.suggestedHints);
|
|
@@ -4348,7 +4891,7 @@ function registerLinkCommand(program) {
|
|
|
4348
4891
|
const upgradeWarnings = linkUpgradeWarnings(db, workspaceId);
|
|
4349
4892
|
writeStdout(
|
|
4350
4893
|
`${upgradeWarnings.length ? `Warnings: ${upgradeWarnings.map((item) => String(item.code)).join(", ")}. Run service-flow doctor --strict for remediation.
|
|
4351
|
-
` : ""}Linked ${r.edgeCount} edges: ${r.remoteResolvedCount} remote operation calls resolved, ${r.localResolvedCount} local operation calls resolved, ${r.unresolvedCount} unresolved operation calls, ${r.ambiguousCount} ambiguous operation calls, ${r.dynamicCount} dynamic operation calls, ${r.terminalCount} terminal call edges, ${r.dependencyResolvedCount} dependency resolved, ${r.dependencyAmbiguousCount} dependency ambiguous, ${r.implementationResolvedCount} implementation resolved, ${r.implementationAmbiguousCount} implementation ambiguous, ${r.implementationUnresolvedCount} implementation unresolved, ${r.subscriptionHandlerResolvedCount} subscription handlers resolved, ${r.subscriptionHandlerAmbiguousCount} subscription handlers ambiguous, ${r.subscriptionHandlerUnresolvedCount} subscription handlers unresolved, ${r.subscriptionHandlerMissingAssociationCount} subscription handler associations missing
|
|
4894
|
+
` : ""}Linked ${r.edgeCount} edges: ${r.remoteResolvedCount} remote operation calls resolved, ${r.localResolvedCount} local operation calls resolved, ${r.unresolvedCount} unresolved operation calls, ${r.ambiguousCount} ambiguous operation calls, ${r.dynamicCount} dynamic operation calls, ${r.terminalCount} terminal call edges, ${r.dependencyResolvedCount} dependency resolved, ${r.dependencyAmbiguousCount} dependency ambiguous, ${r.implementationResolvedCount} implementation resolved, ${r.implementationAmbiguousCount} implementation ambiguous, ${r.implementationUnresolvedCount} implementation unresolved, ${r.subscriptionHandlerResolvedCount} subscription handlers resolved, ${r.subscriptionHandlerAmbiguousCount} subscription handlers ambiguous, ${r.subscriptionHandlerUnresolvedCount} subscription handlers unresolved, ${r.subscriptionHandlerMissingAssociationCount} subscription handler associations missing, ${r.eventShapeCandidateCount} event shape candidates
|
|
4352
4895
|
`
|
|
4353
4896
|
);
|
|
4354
4897
|
}).catch(fail)
|