@saptools/service-flow 0.1.70 → 0.1.73
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 +21 -0
- package/README.md +23 -6
- package/TECHNICAL-NOTE.md +24 -2
- package/dist/{chunk-GSLFY6J2.js → chunk-32WOTGTS.js} +12061 -9288
- package/dist/chunk-32WOTGTS.js.map +1 -0
- package/dist/cli.js +854 -161
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +54 -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 +446 -0
- package/src/cli/doctor.ts +4 -6
- package/src/cli.ts +35 -13
- package/src/config/workspace-config.ts +15 -0
- package/src/db/call-fact-repository.ts +6 -3
- package/src/db/current-fact-semantics.ts +5 -29
- package/src/db/event-fact-semantics.ts +546 -0
- package/src/db/event-site-semantics.ts +62 -0
- package/src/db/event-surface-invalidation.ts +38 -0
- package/src/db/fact-json-inventory.ts +16 -0
- package/src/db/fact-lifecycle.ts +70 -12
- package/src/db/migrations.ts +15 -1
- package/src/db/package-target-invalidation.ts +80 -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 +93 -10
- package/src/indexer/workspace-indexer.ts +13 -3
- package/src/linker/cross-repo-linker.ts +27 -3
- package/src/linker/event-environment-link.ts +211 -0
- package/src/linker/event-shape-candidate-linker.ts +204 -0
- package/src/linker/event-subscription-handler-linker.ts +220 -25
- package/src/linker/event-template-link.ts +40 -6
- package/src/linker/package-event-constant-resolver.ts +302 -0
- package/src/output/repository-inspection.ts +11 -0
- package/src/output/table-output.ts +13 -1
- package/src/parsers/decorator-parser.ts +9 -53
- package/src/parsers/environment-declarations.ts +404 -0
- package/src/parsers/event-call-analysis.ts +370 -0
- package/src/parsers/event-environment-reference.ts +242 -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 +394 -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 +42 -10
- package/src/parsers/string-constant-lookups.ts +408 -0
- package/src/trace/edge-target.ts +65 -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 +90 -8
- package/src/trace/evidence.ts +7 -28
- package/src/trace/trace-scope-execution.ts +22 -30
- package/src/types.ts +19 -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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ANALYZER_VERSION,
|
|
4
|
+
DEFAULT_EVENT_ENVIRONMENT_KEYS,
|
|
5
|
+
EVENT_ENVIRONMENT_KEY_CAP,
|
|
4
6
|
PreparedRepositorySnapshotError,
|
|
5
7
|
VERSION,
|
|
6
8
|
analyzePackagePublicSurface,
|
|
@@ -8,14 +10,20 @@ import {
|
|
|
8
10
|
bindingSite,
|
|
9
11
|
classifyODataPathIntent,
|
|
10
12
|
classifyOutboundCallsInSource,
|
|
13
|
+
collectEnvironmentDeclarations,
|
|
14
|
+
collectStringConstantLookups,
|
|
11
15
|
collectSymbolImportBindings,
|
|
12
16
|
compactTrace,
|
|
13
17
|
containsSupportedOutboundCall,
|
|
14
18
|
createBindingLexicalIndex,
|
|
19
|
+
createEventEnvironmentReferenceResolver,
|
|
20
|
+
createImportedEventNameResolver,
|
|
15
21
|
derivedMemberImportReference,
|
|
16
22
|
discoverRepositories,
|
|
23
|
+
emptyEnvironmentDeclarations,
|
|
17
24
|
executableSymbolCandidates,
|
|
18
25
|
factLifecycleDiagnostic,
|
|
26
|
+
generatedConstantFacts,
|
|
19
27
|
identifierMatchesDeclaration,
|
|
20
28
|
implementationHintSuggestions,
|
|
21
29
|
insertCalls,
|
|
@@ -28,6 +36,7 @@ import {
|
|
|
28
36
|
loadPackageJsonSnapshot,
|
|
29
37
|
loadRepositorySourceContext,
|
|
30
38
|
migrate,
|
|
39
|
+
normalizeEventEnvironmentKeys,
|
|
31
40
|
normalizeODataOperationInvocationPath,
|
|
32
41
|
normalizePath,
|
|
33
42
|
parseCdsFile,
|
|
@@ -42,13 +51,16 @@ import {
|
|
|
42
51
|
projectBoundedInOrder,
|
|
43
52
|
recordPreparedSnapshotFailure,
|
|
44
53
|
redactValue,
|
|
54
|
+
resolveBinding,
|
|
45
55
|
selectCallOwner,
|
|
46
56
|
selectVisibleBinding,
|
|
47
57
|
selectorRepoAmbiguousDiagnostic,
|
|
58
|
+
sha256Text,
|
|
48
59
|
stableLocalValueReference,
|
|
49
60
|
symbolImportReference,
|
|
50
|
-
trace
|
|
51
|
-
|
|
61
|
+
trace,
|
|
62
|
+
validEventEnvironmentKey
|
|
63
|
+
} from "./chunk-32WOTGTS.js";
|
|
52
64
|
|
|
53
65
|
// src/cli.ts
|
|
54
66
|
import { Command, Option } from "commander";
|
|
@@ -73,10 +85,18 @@ var DEFAULT_DB_FILE = "service-flow.db";
|
|
|
73
85
|
import fs from "fs/promises";
|
|
74
86
|
import path from "path";
|
|
75
87
|
import { z } from "zod";
|
|
88
|
+
var environmentKeys = z.array(
|
|
89
|
+
z.string().refine(validEventEnvironmentKey)
|
|
90
|
+
).min(1).max(EVENT_ENVIRONMENT_KEY_CAP).transform(
|
|
91
|
+
normalizeEventEnvironmentKeys
|
|
92
|
+
);
|
|
76
93
|
var schema = z.object({
|
|
77
94
|
rootPath: z.string(),
|
|
78
95
|
dbPath: z.string(),
|
|
79
96
|
ignore: z.array(z.string()),
|
|
97
|
+
eventEnvironmentKeys: environmentKeys.default(
|
|
98
|
+
[...DEFAULT_EVENT_ENVIRONMENT_KEYS]
|
|
99
|
+
),
|
|
80
100
|
createdAt: z.string(),
|
|
81
101
|
updatedAt: z.string()
|
|
82
102
|
});
|
|
@@ -113,6 +133,7 @@ function createWorkspaceConfig(rootPath, dbPath, ignore = [...DEFAULT_IGNORES])
|
|
|
113
133
|
rootPath: root,
|
|
114
134
|
dbPath: path.resolve(dbPath ?? defaultDbPath(root)),
|
|
115
135
|
ignore,
|
|
136
|
+
eventEnvironmentKeys: [...DEFAULT_EVENT_ENVIRONMENT_KEYS],
|
|
116
137
|
createdAt: now,
|
|
117
138
|
updatedAt: now
|
|
118
139
|
};
|
|
@@ -250,8 +271,8 @@ function upsertRepository(db, workspaceId, r) {
|
|
|
250
271
|
db.prepare(
|
|
251
272
|
`INSERT INTO repositories(workspace_id,name,absolute_path,relative_path,
|
|
252
273
|
package_name,package_version,dependencies_json,
|
|
253
|
-
package_public_surface_json,kind,is_git_repo)
|
|
254
|
-
VALUES(
|
|
274
|
+
package_public_surface_json,environment_declarations_json,kind,is_git_repo)
|
|
275
|
+
VALUES(?,?,?,?,?,?,?,?,?,?,?)
|
|
255
276
|
ON CONFLICT(workspace_id,absolute_path) DO UPDATE SET
|
|
256
277
|
name=excluded.name,relative_path=excluded.relative_path,
|
|
257
278
|
package_name=excluded.package_name,
|
|
@@ -266,6 +287,7 @@ function upsertRepository(db, workspaceId, r) {
|
|
|
266
287
|
r.packageVersion,
|
|
267
288
|
JSON.stringify(r.dependencies ?? {}),
|
|
268
289
|
initialPackagePublicSurface(r.packageName),
|
|
290
|
+
JSON.stringify(emptyEnvironmentDeclarations()),
|
|
269
291
|
r.kind ?? "unknown",
|
|
270
292
|
r.isGitRepo ? 1 : 0
|
|
271
293
|
);
|
|
@@ -292,6 +314,7 @@ function clearRepoFacts(db, repoId) {
|
|
|
292
314
|
"symbol_calls",
|
|
293
315
|
"handler_registrations",
|
|
294
316
|
"service_bindings",
|
|
317
|
+
"generated_constants",
|
|
295
318
|
"symbols",
|
|
296
319
|
"diagnostics",
|
|
297
320
|
"files"
|
|
@@ -299,6 +322,32 @@ function clearRepoFacts(db, repoId) {
|
|
|
299
322
|
db.prepare(`DELETE FROM ${t} WHERE repo_id=?`).run(repoId);
|
|
300
323
|
db.prepare("DELETE FROM search_index WHERE repo=?").run(String(repoId));
|
|
301
324
|
}
|
|
325
|
+
function insertGeneratedConstants(db, repoId, rows) {
|
|
326
|
+
const stmt = db.prepare(`INSERT INTO generated_constants(
|
|
327
|
+
repo_id,source_file,source_line,name,container_name,member_name,value,
|
|
328
|
+
constant_kind,exported,stable,resolution_status,unresolved_reason,
|
|
329
|
+
declaration_start_offset,declaration_end_offset,
|
|
330
|
+
value_start_offset,value_end_offset
|
|
331
|
+
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`);
|
|
332
|
+
for (const row of rows) stmt.run(
|
|
333
|
+
repoId,
|
|
334
|
+
row.sourceFile,
|
|
335
|
+
row.sourceLine,
|
|
336
|
+
row.name,
|
|
337
|
+
row.containerName ?? null,
|
|
338
|
+
row.memberName ?? null,
|
|
339
|
+
row.value ?? null,
|
|
340
|
+
row.constantKind,
|
|
341
|
+
row.exported ? 1 : 0,
|
|
342
|
+
row.stable ? 1 : 0,
|
|
343
|
+
row.resolutionStatus,
|
|
344
|
+
row.unresolvedReason ?? null,
|
|
345
|
+
row.declarationStartOffset,
|
|
346
|
+
row.declarationEndOffset,
|
|
347
|
+
row.valueStartOffset,
|
|
348
|
+
row.valueEndOffset
|
|
349
|
+
);
|
|
350
|
+
}
|
|
302
351
|
function insertRequires(db, repoId, rows) {
|
|
303
352
|
const stmt = db.prepare(
|
|
304
353
|
"INSERT INTO cds_requires(repo_id,alias,kind,model,destination,service_path,request_timeout,raw_json) VALUES(?,?,?,?,?,?,?,?)"
|
|
@@ -612,10 +661,10 @@ import path5 from "path";
|
|
|
612
661
|
// src/parsers/symbol-parser.ts
|
|
613
662
|
import fs4 from "fs/promises";
|
|
614
663
|
import path4 from "path";
|
|
615
|
-
import
|
|
664
|
+
import ts9 from "typescript";
|
|
616
665
|
|
|
617
666
|
// src/parsers/event-subscription-facts.ts
|
|
618
|
-
import
|
|
667
|
+
import ts3 from "typescript";
|
|
619
668
|
|
|
620
669
|
// src/parsers/local-symbol-reference.ts
|
|
621
670
|
import ts from "typescript";
|
|
@@ -752,6 +801,89 @@ function localSymbolTarget(expression, source, symbols) {
|
|
|
752
801
|
return declaration ? exactPropertyTarget(expression, declaration, symbols, source) : void 0;
|
|
753
802
|
}
|
|
754
803
|
|
|
804
|
+
// src/parsers/event-loop-registration.ts
|
|
805
|
+
import ts2 from "typescript";
|
|
806
|
+
var loopValueCap = 32;
|
|
807
|
+
var loopExpressionLimit = 256;
|
|
808
|
+
function unwrap(expression) {
|
|
809
|
+
if (ts2.isParenthesizedExpression(expression) || ts2.isAsExpression(expression) || ts2.isSatisfiesExpression(expression) || ts2.isTypeAssertionExpression(expression))
|
|
810
|
+
return unwrap(expression.expression);
|
|
811
|
+
return expression;
|
|
812
|
+
}
|
|
813
|
+
function stringArray(expression) {
|
|
814
|
+
const value = unwrap(expression);
|
|
815
|
+
if (!ts2.isArrayLiteralExpression(value)) return void 0;
|
|
816
|
+
const strings = value.elements.flatMap((element) => {
|
|
817
|
+
const item = ts2.isSpreadElement(element) ? void 0 : unwrap(element);
|
|
818
|
+
return item && ts2.isStringLiteralLike(item) ? [item.text] : [];
|
|
819
|
+
});
|
|
820
|
+
return strings.length === value.elements.length ? strings : void 0;
|
|
821
|
+
}
|
|
822
|
+
function identifierArray(identifier) {
|
|
823
|
+
const binding = resolveBinding(identifier, identifier);
|
|
824
|
+
return binding.immutable && binding.initializer ? stringArray(binding.initializer) : void 0;
|
|
825
|
+
}
|
|
826
|
+
function orderedValues(values) {
|
|
827
|
+
return [...values].sort((left, right) => left.declarationStartOffset - right.declarationStartOffset).map((item) => item.value);
|
|
828
|
+
}
|
|
829
|
+
function objectValues(expression, source) {
|
|
830
|
+
if (!ts2.isPropertyAccessExpression(expression.expression))
|
|
831
|
+
return void 0;
|
|
832
|
+
const root = expression.expression.expression;
|
|
833
|
+
if (!ts2.isIdentifier(root) || root.text !== "Object" || lexicalIdentifierDeclaration(root) || expression.expression.name.text !== "values" || expression.arguments.length !== 1) return void 0;
|
|
834
|
+
const argument = expression.arguments[0];
|
|
835
|
+
if (!argument || !ts2.isIdentifier(argument)) return void 0;
|
|
836
|
+
const lookups = collectStringConstantLookups(source);
|
|
837
|
+
const prefix = `${argument.text}.`;
|
|
838
|
+
const refused = [...lookups.refusedMembers.keys()].some((key) => key.startsWith(prefix));
|
|
839
|
+
const values = [
|
|
840
|
+
...lookups.enumMembers.values(),
|
|
841
|
+
...lookups.objectProperties.values()
|
|
842
|
+
].filter((item) => item.key.startsWith(prefix));
|
|
843
|
+
return !refused && values.length > 0 ? orderedValues(values) : void 0;
|
|
844
|
+
}
|
|
845
|
+
function staticCollectionValues(expression, source) {
|
|
846
|
+
const value = unwrap(expression);
|
|
847
|
+
if (ts2.isArrayLiteralExpression(value)) return stringArray(value);
|
|
848
|
+
if (ts2.isIdentifier(value)) return identifierArray(value);
|
|
849
|
+
return ts2.isCallExpression(value) ? objectValues(value, source) : void 0;
|
|
850
|
+
}
|
|
851
|
+
function enclosingForEach(node) {
|
|
852
|
+
let current = node.parent;
|
|
853
|
+
while (current && !ts2.isSourceFile(current)) {
|
|
854
|
+
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")
|
|
855
|
+
return current.parent;
|
|
856
|
+
if (ts2.isFunctionLike(current)) return void 0;
|
|
857
|
+
current = current.parent;
|
|
858
|
+
}
|
|
859
|
+
return void 0;
|
|
860
|
+
}
|
|
861
|
+
function boundedExpression(expression, source) {
|
|
862
|
+
return expression.getText(source).slice(0, loopExpressionLimit);
|
|
863
|
+
}
|
|
864
|
+
function eventLoopRegistrationEvidence(node, source) {
|
|
865
|
+
const loop = enclosingForEach(node);
|
|
866
|
+
if (!loop || !ts2.isPropertyAccessExpression(loop.expression)) return {};
|
|
867
|
+
const collection = loop.expression.expression;
|
|
868
|
+
const values = staticCollectionValues(collection, source);
|
|
869
|
+
if (!values) return {
|
|
870
|
+
subscriptionRegisteredInLoop: true,
|
|
871
|
+
subscriptionLoopRegistrationStatus: "unresolved",
|
|
872
|
+
subscriptionLoopUnresolvedReason: "subscription_loop_collection_not_statically_enumerable",
|
|
873
|
+
subscriptionLoopCollectionExpression: boundedExpression(collection, source)
|
|
874
|
+
};
|
|
875
|
+
const shown = values.slice(0, loopValueCap);
|
|
876
|
+
return {
|
|
877
|
+
subscriptionRegisteredInLoop: true,
|
|
878
|
+
subscriptionLoopRegistrationStatus: "enumerated",
|
|
879
|
+
subscriptionLoopCollectionExpression: boundedExpression(collection, source),
|
|
880
|
+
subscriptionLoopRegistrationCount: values.length,
|
|
881
|
+
subscriptionLoopValues: shown,
|
|
882
|
+
shownSubscriptionLoopValueCount: shown.length,
|
|
883
|
+
omittedSubscriptionLoopValueCount: Math.max(0, values.length - shown.length)
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
755
887
|
// src/parsers/event-subscription-facts.ts
|
|
756
888
|
function lineOf(source, node) {
|
|
757
889
|
return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
|
|
@@ -766,7 +898,7 @@ function importRelation(binding) {
|
|
|
766
898
|
function directTarget(expression, source, imports, symbols) {
|
|
767
899
|
const imported = symbolImportReference(expression, imports);
|
|
768
900
|
if (imported) return importedTarget(expression, source, imported);
|
|
769
|
-
if (
|
|
901
|
+
if (ts3.isIdentifier(expression)) {
|
|
770
902
|
const exact = localSymbolTarget(expression, source, symbols);
|
|
771
903
|
return {
|
|
772
904
|
calleeExpression: expression.text,
|
|
@@ -789,7 +921,7 @@ function importedTarget(expression, source, binding) {
|
|
|
789
921
|
};
|
|
790
922
|
}
|
|
791
923
|
function propertyTarget(expression, source, symbols) {
|
|
792
|
-
if (!
|
|
924
|
+
if (!ts3.isPropertyAccessExpression(expression) || expression.questionDotToken || !ts3.isIdentifier(expression.expression)) return void 0;
|
|
793
925
|
const exact = localSymbolTarget(expression, source, symbols);
|
|
794
926
|
return {
|
|
795
927
|
calleeExpression: expression.getText(source),
|
|
@@ -809,7 +941,7 @@ function classifyHandlerReference(expression, source, imports = collectSymbolImp
|
|
|
809
941
|
"handler_argument_missing",
|
|
810
942
|
"missing"
|
|
811
943
|
);
|
|
812
|
-
if (
|
|
944
|
+
if (ts3.isArrowFunction(expression) || ts3.isFunctionExpression(expression))
|
|
813
945
|
return unsupportedClassification(
|
|
814
946
|
"unsupported_inline",
|
|
815
947
|
"inline_handler_body_not_indexed",
|
|
@@ -818,7 +950,7 @@ function classifyHandlerReference(expression, source, imports = collectSymbolImp
|
|
|
818
950
|
const direct = directTarget(expression, source, imports, symbols);
|
|
819
951
|
if (direct)
|
|
820
952
|
return { status: "role_required", referenceShape: direct.referenceShape, target: direct };
|
|
821
|
-
if (!
|
|
953
|
+
if (!ts3.isCallExpression(expression))
|
|
822
954
|
return unsupportedClassification(
|
|
823
955
|
"unsupported_reference_shape",
|
|
824
956
|
"handler_reference_shape_unsupported",
|
|
@@ -906,6 +1038,7 @@ function enrichSubscription(source, classified, symbols) {
|
|
|
906
1038
|
);
|
|
907
1039
|
const evidence = {
|
|
908
1040
|
...classified.fact.evidence ?? {},
|
|
1041
|
+
...eventLoopRegistrationEvidence(classified.node, source),
|
|
909
1042
|
handlerReferenceStatus: classification.status,
|
|
910
1043
|
...classification.reason ? { handlerReferenceReason: classification.reason } : {},
|
|
911
1044
|
handlerReferenceShape: classification.referenceShape
|
|
@@ -939,7 +1072,7 @@ function reconcileEventSubscriptions(source, classifications, symbols, calls) {
|
|
|
939
1072
|
import "typescript";
|
|
940
1073
|
|
|
941
1074
|
// src/parsers/binding-identity.ts
|
|
942
|
-
import
|
|
1075
|
+
import ts4 from "typescript";
|
|
943
1076
|
var scopeChainCap = 16;
|
|
944
1077
|
function matchFactsToSites(index, facts) {
|
|
945
1078
|
return facts.map((fact) => {
|
|
@@ -1059,9 +1192,9 @@ function assertUniqueSites(bindings) {
|
|
|
1059
1192
|
function callNodeMap(source) {
|
|
1060
1193
|
const calls = /* @__PURE__ */ new Map();
|
|
1061
1194
|
const visit = (node) => {
|
|
1062
|
-
if (
|
|
1195
|
+
if (ts4.isCallExpression(node))
|
|
1063
1196
|
calls.set(`${node.getStart(source)}:${node.getEnd()}`, node);
|
|
1064
|
-
|
|
1197
|
+
ts4.forEachChild(node, visit);
|
|
1065
1198
|
};
|
|
1066
1199
|
visit(source);
|
|
1067
1200
|
return calls;
|
|
@@ -1254,7 +1387,7 @@ function reconcileSourceFacts(source, classifications, bindings, outboundCalls,
|
|
|
1254
1387
|
}
|
|
1255
1388
|
|
|
1256
1389
|
// src/parsers/symbol-call-facts.ts
|
|
1257
|
-
import
|
|
1390
|
+
import ts6 from "typescript";
|
|
1258
1391
|
var commonTerminalMembers = /* @__PURE__ */ new Set([
|
|
1259
1392
|
"push",
|
|
1260
1393
|
"includes",
|
|
@@ -1322,8 +1455,8 @@ var cdsFrameworkPrefixes = [
|
|
|
1322
1455
|
"cds.parse."
|
|
1323
1456
|
];
|
|
1324
1457
|
function symbolCallName(expr) {
|
|
1325
|
-
if (
|
|
1326
|
-
if (!
|
|
1458
|
+
if (ts6.isIdentifier(expr)) return { expression: expr.text, local: expr.text };
|
|
1459
|
+
if (!ts6.isPropertyAccessExpression(expr))
|
|
1327
1460
|
return { expression: expr.getText() };
|
|
1328
1461
|
const left = expr.expression.getText();
|
|
1329
1462
|
return {
|
|
@@ -1355,25 +1488,25 @@ function ignoredFrameworkCall(callee) {
|
|
|
1355
1488
|
}
|
|
1356
1489
|
function argumentEvidence(args) {
|
|
1357
1490
|
return args.map((arg) => {
|
|
1358
|
-
if (
|
|
1359
|
-
if (
|
|
1491
|
+
if (ts6.isIdentifier(arg)) return { kind: "identifier", name: arg.text };
|
|
1492
|
+
if (ts6.isArrayLiteralExpression(arg)) return {
|
|
1360
1493
|
kind: "array_literal",
|
|
1361
|
-
elements: arg.elements.flatMap((item, index) =>
|
|
1494
|
+
elements: arg.elements.flatMap((item, index) => ts6.isIdentifier(item) ? [{ index, kind: "identifier", name: item.text }] : [])
|
|
1362
1495
|
};
|
|
1363
|
-
if (
|
|
1496
|
+
if (ts6.isObjectLiteralExpression(arg))
|
|
1364
1497
|
return objectArgumentEvidence(arg);
|
|
1365
1498
|
return { kind: "unsupported", expression: arg.getText() };
|
|
1366
1499
|
});
|
|
1367
1500
|
}
|
|
1368
1501
|
function objectArgumentEvidence(argument) {
|
|
1369
1502
|
const properties = argument.properties.flatMap((property) => {
|
|
1370
|
-
if (
|
|
1503
|
+
if (ts6.isShorthandPropertyAssignment(property))
|
|
1371
1504
|
return [{
|
|
1372
1505
|
kind: "shorthand",
|
|
1373
1506
|
property: property.name.text,
|
|
1374
1507
|
argument: property.name.text
|
|
1375
1508
|
}];
|
|
1376
|
-
if (!
|
|
1509
|
+
if (!ts6.isPropertyAssignment(property) || !ts6.isIdentifier(property.initializer)) return [];
|
|
1377
1510
|
const name = propertyName(property.name);
|
|
1378
1511
|
return name ? [{
|
|
1379
1512
|
kind: "property_assignment",
|
|
@@ -1384,7 +1517,7 @@ function objectArgumentEvidence(argument) {
|
|
|
1384
1517
|
return { kind: "object_literal", properties };
|
|
1385
1518
|
}
|
|
1386
1519
|
function propertyName(name) {
|
|
1387
|
-
return
|
|
1520
|
+
return ts6.isIdentifier(name) || ts6.isStringLiteralLike(name) || ts6.isNumericLiteral(name) ? name.text : void 0;
|
|
1388
1521
|
}
|
|
1389
1522
|
function exactCaller(collection, node) {
|
|
1390
1523
|
const selected = selectCallOwner(
|
|
@@ -1445,9 +1578,9 @@ function callFact(collection, node) {
|
|
|
1445
1578
|
}
|
|
1446
1579
|
function expressionRoot(expression) {
|
|
1447
1580
|
let current = expression;
|
|
1448
|
-
while (
|
|
1581
|
+
while (ts6.isPropertyAccessExpression(current))
|
|
1449
1582
|
current = current.expression;
|
|
1450
|
-
return
|
|
1583
|
+
return ts6.isIdentifier(current) ? current : void 0;
|
|
1451
1584
|
}
|
|
1452
1585
|
function exactDeclaredContext(identifier, candidates) {
|
|
1453
1586
|
const matches = candidates.filter((candidate) => identifierMatchesDeclaration(
|
|
@@ -1478,8 +1611,8 @@ function callInstance(collection, node, callee) {
|
|
|
1478
1611
|
}
|
|
1479
1612
|
function enclosingClass(node) {
|
|
1480
1613
|
let current = node.parent;
|
|
1481
|
-
while (current && !
|
|
1482
|
-
if (
|
|
1614
|
+
while (current && !ts6.isSourceFile(current)) {
|
|
1615
|
+
if (ts6.isClassLike(current)) return current;
|
|
1483
1616
|
current = current.parent;
|
|
1484
1617
|
}
|
|
1485
1618
|
return void 0;
|
|
@@ -1619,26 +1752,26 @@ function parserCandidateStrategy(context) {
|
|
|
1619
1752
|
function collectSymbolCallFacts(collection) {
|
|
1620
1753
|
const calls = [];
|
|
1621
1754
|
const visit = (node) => {
|
|
1622
|
-
if (
|
|
1755
|
+
if (ts6.isCallExpression(node)) {
|
|
1623
1756
|
const call = callFact(collection, node);
|
|
1624
1757
|
if (call) calls.push(call);
|
|
1625
1758
|
}
|
|
1626
|
-
|
|
1759
|
+
ts6.forEachChild(node, visit);
|
|
1627
1760
|
};
|
|
1628
1761
|
visit(collection.source);
|
|
1629
1762
|
return calls;
|
|
1630
1763
|
}
|
|
1631
1764
|
|
|
1632
1765
|
// src/parsers/executable-body-eligibility.ts
|
|
1633
|
-
import
|
|
1766
|
+
import ts7 from "typescript";
|
|
1634
1767
|
function hasModifier(node, kind) {
|
|
1635
|
-
return
|
|
1768
|
+
return ts7.canHaveModifiers(node) && Boolean(ts7.getModifiers(node)?.some((item) => item.kind === kind));
|
|
1636
1769
|
}
|
|
1637
1770
|
function executableBodyEligibility(node, source) {
|
|
1638
1771
|
if (node.body) return { eligible: true, reason: "body_present" };
|
|
1639
|
-
if (hasModifier(node,
|
|
1772
|
+
if (hasModifier(node, ts7.SyntaxKind.AbstractKeyword))
|
|
1640
1773
|
return { eligible: false, reason: "abstract_bodyless" };
|
|
1641
|
-
if (hasModifier(node,
|
|
1774
|
+
if (hasModifier(node, ts7.SyntaxKind.DeclareKeyword))
|
|
1642
1775
|
return { eligible: false, reason: "ambient_declaration" };
|
|
1643
1776
|
return {
|
|
1644
1777
|
eligible: false,
|
|
@@ -1647,7 +1780,7 @@ function executableBodyEligibility(node, source) {
|
|
|
1647
1780
|
}
|
|
1648
1781
|
|
|
1649
1782
|
// src/parsers/symbol-derived-contexts.ts
|
|
1650
|
-
import
|
|
1783
|
+
import ts8 from "typescript";
|
|
1651
1784
|
var builtInConstructors = /* @__PURE__ */ new Set([
|
|
1652
1785
|
"Set",
|
|
1653
1786
|
"Map",
|
|
@@ -1688,23 +1821,23 @@ function appendNamed(values, name, value) {
|
|
|
1688
1821
|
values.set(name, matches);
|
|
1689
1822
|
}
|
|
1690
1823
|
function assignmentOperator2(kind) {
|
|
1691
|
-
return kind >=
|
|
1824
|
+
return kind >= ts8.SyntaxKind.FirstAssignment && kind <= ts8.SyntaxKind.LastAssignment;
|
|
1692
1825
|
}
|
|
1693
1826
|
function mutationUnary2(node) {
|
|
1694
|
-
return node.operator ===
|
|
1827
|
+
return node.operator === ts8.SyntaxKind.PlusPlusToken || node.operator === ts8.SyntaxKind.MinusMinusToken;
|
|
1695
1828
|
}
|
|
1696
1829
|
function receiverIdentifier(expression) {
|
|
1697
1830
|
let current = expression;
|
|
1698
|
-
while (
|
|
1831
|
+
while (ts8.isPropertyAccessExpression(current) || ts8.isElementAccessExpression(current))
|
|
1699
1832
|
current = current.expression;
|
|
1700
|
-
return
|
|
1833
|
+
return ts8.isIdentifier(current) ? current : void 0;
|
|
1701
1834
|
}
|
|
1702
1835
|
function nodeWritesMember(node, matches) {
|
|
1703
|
-
if (
|
|
1836
|
+
if (ts8.isBinaryExpression(node))
|
|
1704
1837
|
return assignmentOperator2(node.operatorToken.kind) && matches(node.left);
|
|
1705
|
-
if (
|
|
1838
|
+
if (ts8.isPrefixUnaryExpression(node) || ts8.isPostfixUnaryExpression(node))
|
|
1706
1839
|
return mutationUnary2(node) && matches(node.operand);
|
|
1707
|
-
return
|
|
1840
|
+
return ts8.isDeleteExpression(node) && matches(node.expression);
|
|
1708
1841
|
}
|
|
1709
1842
|
function memberWrite(source, declaration) {
|
|
1710
1843
|
const start = declaration.getStart(source);
|
|
@@ -1717,16 +1850,16 @@ function memberWrite(source, declaration) {
|
|
|
1717
1850
|
const visit = (node) => {
|
|
1718
1851
|
if (written) return;
|
|
1719
1852
|
written = nodeWritesMember(node, matches);
|
|
1720
|
-
if (!written)
|
|
1853
|
+
if (!written) ts8.forEachChild(node, visit);
|
|
1721
1854
|
};
|
|
1722
1855
|
visit(source);
|
|
1723
1856
|
return written;
|
|
1724
1857
|
}
|
|
1725
1858
|
function stableVariable(collection, node) {
|
|
1726
|
-
return
|
|
1859
|
+
return ts8.isIdentifier(node.name) && ts8.isVariableDeclarationList(node.parent) && (node.parent.flags & ts8.NodeFlags.Const) !== 0 && !memberWrite(collection.source, node.name);
|
|
1727
1860
|
}
|
|
1728
1861
|
function collectProxy(collection, node) {
|
|
1729
|
-
if (!
|
|
1862
|
+
if (!ts8.isVariableDeclaration(node) || !stableVariable(collection, node) || !node.initializer || !ts8.isCallExpression(node.initializer) || !ts8.isPropertyAccessExpression(node.initializer.expression)) return;
|
|
1730
1863
|
const callee = symbolCallName(node.initializer.expression);
|
|
1731
1864
|
const binding = symbolImportReference(
|
|
1732
1865
|
node.initializer.expression,
|
|
@@ -1743,8 +1876,8 @@ function collectProxy(collection, node) {
|
|
|
1743
1876
|
});
|
|
1744
1877
|
}
|
|
1745
1878
|
function propertyContainer2(declaration, propertyName3) {
|
|
1746
|
-
const property = propertyName3 &&
|
|
1747
|
-
return property &&
|
|
1879
|
+
const property = propertyName3 && ts8.isPropertyDeclaration(declaration.parent) ? declaration.parent : void 0;
|
|
1880
|
+
return property && ts8.isClassLike(property.parent) ? property.parent : void 0;
|
|
1748
1881
|
}
|
|
1749
1882
|
function eligibleClassName(collection, className, imported) {
|
|
1750
1883
|
if (builtInConstructors.has(className)) return false;
|
|
@@ -1775,43 +1908,43 @@ function rememberInstance(collection, declaration, classExpression, propertyName
|
|
|
1775
1908
|
});
|
|
1776
1909
|
}
|
|
1777
1910
|
function collectVariableInstance(collection, node) {
|
|
1778
|
-
if (!
|
|
1911
|
+
if (!ts8.isVariableDeclaration(node) || !stableVariable(collection, node))
|
|
1779
1912
|
return;
|
|
1780
1913
|
const initializer = node.initializer;
|
|
1781
|
-
if (initializer &&
|
|
1914
|
+
if (initializer && ts8.isNewExpression(initializer) && ts8.isIdentifier(initializer.expression))
|
|
1782
1915
|
rememberInstance(collection, node.name, initializer.expression);
|
|
1783
1916
|
}
|
|
1784
1917
|
function propertyName2(name) {
|
|
1785
|
-
return
|
|
1918
|
+
return ts8.isIdentifier(name) || ts8.isStringLiteralLike(name) || ts8.isNumericLiteral(name) ? name.text : void 0;
|
|
1786
1919
|
}
|
|
1787
1920
|
function thisPropertyWrite(expression, name) {
|
|
1788
|
-
if (
|
|
1789
|
-
return expression.expression.kind ===
|
|
1790
|
-
return
|
|
1921
|
+
if (ts8.isPropertyAccessExpression(expression))
|
|
1922
|
+
return expression.expression.kind === ts8.SyntaxKind.ThisKeyword && expression.name.text === name;
|
|
1923
|
+
return ts8.isElementAccessExpression(expression) && expression.expression.kind === ts8.SyntaxKind.ThisKeyword && Boolean(expression.argumentExpression && ts8.isStringLiteralLike(expression.argumentExpression) && expression.argumentExpression.text === name);
|
|
1791
1924
|
}
|
|
1792
1925
|
function nodeWritesThisProperty(node, name) {
|
|
1793
|
-
if (
|
|
1926
|
+
if (ts8.isBinaryExpression(node))
|
|
1794
1927
|
return assignmentOperator2(node.operatorToken.kind) && thisPropertyWrite(node.left, name);
|
|
1795
|
-
if (
|
|
1928
|
+
if (ts8.isPrefixUnaryExpression(node) || ts8.isPostfixUnaryExpression(node))
|
|
1796
1929
|
return mutationUnary2(node) && thisPropertyWrite(node.operand, name);
|
|
1797
|
-
return
|
|
1930
|
+
return ts8.isDeleteExpression(node) && thisPropertyWrite(node.expression, name);
|
|
1798
1931
|
}
|
|
1799
1932
|
function stableProperty(node, name) {
|
|
1800
|
-
const flags =
|
|
1801
|
-
if ((flags & (
|
|
1933
|
+
const flags = ts8.getCombinedModifierFlags(node);
|
|
1934
|
+
if ((flags & (ts8.ModifierFlags.Private | ts8.ModifierFlags.Readonly)) === 0 || !ts8.isClassLike(node.parent)) return false;
|
|
1802
1935
|
let written = false;
|
|
1803
1936
|
const visit = (child) => {
|
|
1804
1937
|
if (written || child === node) return;
|
|
1805
1938
|
written = nodeWritesThisProperty(child, name);
|
|
1806
|
-
if (!written)
|
|
1939
|
+
if (!written) ts8.forEachChild(child, visit);
|
|
1807
1940
|
};
|
|
1808
1941
|
visit(node.parent);
|
|
1809
1942
|
return !written;
|
|
1810
1943
|
}
|
|
1811
1944
|
function collectPropertyInstance(collection, node) {
|
|
1812
|
-
if (!
|
|
1945
|
+
if (!ts8.isPropertyDeclaration(node)) return;
|
|
1813
1946
|
const initializer = node.initializer;
|
|
1814
|
-
if (!initializer || !
|
|
1947
|
+
if (!initializer || !ts8.isNewExpression(initializer) || !ts8.isIdentifier(initializer.expression)) return;
|
|
1815
1948
|
const name = propertyName2(node.name);
|
|
1816
1949
|
if (name && stableProperty(node, name)) rememberInstance(
|
|
1817
1950
|
collection,
|
|
@@ -1825,7 +1958,7 @@ function collectDerivedSymbolContexts(collection) {
|
|
|
1825
1958
|
collectProxy(collection, node);
|
|
1826
1959
|
collectVariableInstance(collection, node);
|
|
1827
1960
|
collectPropertyInstance(collection, node);
|
|
1828
|
-
|
|
1961
|
+
ts8.forEachChild(node, visit);
|
|
1829
1962
|
};
|
|
1830
1963
|
visit(collection.source);
|
|
1831
1964
|
}
|
|
@@ -1836,51 +1969,51 @@ function lineOf2(source, pos) {
|
|
|
1836
1969
|
}
|
|
1837
1970
|
function nameOf(node) {
|
|
1838
1971
|
if (!node) return void 0;
|
|
1839
|
-
if (
|
|
1972
|
+
if (ts9.isIdentifier(node) || ts9.isStringLiteral(node) || ts9.isNumericLiteral(node)) return node.text;
|
|
1840
1973
|
return void 0;
|
|
1841
1974
|
}
|
|
1842
1975
|
function isFunctionLike(node) {
|
|
1843
|
-
return
|
|
1976
|
+
return ts9.isFunctionDeclaration(node) || ts9.isMethodDeclaration(node) || ts9.isFunctionExpression(node) || ts9.isArrowFunction(node);
|
|
1844
1977
|
}
|
|
1845
1978
|
function exported(node) {
|
|
1846
|
-
return Boolean(
|
|
1979
|
+
return Boolean(ts9.getCombinedModifierFlags(node) & ts9.ModifierFlags.Export);
|
|
1847
1980
|
}
|
|
1848
1981
|
function defaultExported(node) {
|
|
1849
1982
|
return Boolean(
|
|
1850
|
-
|
|
1983
|
+
ts9.getCombinedModifierFlags(node) & ts9.ModifierFlags.Default
|
|
1851
1984
|
);
|
|
1852
1985
|
}
|
|
1853
1986
|
function isPublicClassMethod(node) {
|
|
1854
|
-
const flags =
|
|
1855
|
-
return (flags &
|
|
1987
|
+
const flags = ts9.getCombinedModifierFlags(node);
|
|
1988
|
+
return (flags & ts9.ModifierFlags.Private) === 0 && (flags & ts9.ModifierFlags.Protected) === 0;
|
|
1856
1989
|
}
|
|
1857
1990
|
function exportDeclarations(source) {
|
|
1858
1991
|
const exports = /* @__PURE__ */ new Map();
|
|
1859
1992
|
const visit = (node) => {
|
|
1860
|
-
if (
|
|
1993
|
+
if (ts9.isExportDeclaration(node) && node.exportClause && ts9.isNamedExports(node.exportClause)) {
|
|
1861
1994
|
for (const el of node.exportClause.elements) exports.set((el.propertyName ?? el.name).text, el.name.text);
|
|
1862
1995
|
}
|
|
1863
|
-
|
|
1996
|
+
ts9.forEachChild(node, visit);
|
|
1864
1997
|
};
|
|
1865
1998
|
visit(source);
|
|
1866
1999
|
return exports;
|
|
1867
2000
|
}
|
|
1868
2001
|
function isObjectFunction(node) {
|
|
1869
|
-
return
|
|
2002
|
+
return ts9.isFunctionExpression(node) || ts9.isArrowFunction(node) || ts9.isMethodDeclaration(node);
|
|
1870
2003
|
}
|
|
1871
2004
|
function requireSource(expr) {
|
|
1872
|
-
if (!
|
|
2005
|
+
if (!ts9.isCallExpression(expr) || !ts9.isIdentifier(expr.expression) || expr.expression.text !== "require") return void 0;
|
|
1873
2006
|
const first = expr.arguments[0];
|
|
1874
|
-
return first &&
|
|
2007
|
+
return first && ts9.isStringLiteral(first) ? first.text : void 0;
|
|
1875
2008
|
}
|
|
1876
2009
|
function bindingLocalName(name, initializer) {
|
|
1877
|
-
if (
|
|
1878
|
-
if (initializer &&
|
|
2010
|
+
if (ts9.isIdentifier(name)) return name.text;
|
|
2011
|
+
if (initializer && ts9.isIdentifier(initializer)) return initializer.text;
|
|
1879
2012
|
return void 0;
|
|
1880
2013
|
}
|
|
1881
2014
|
function objectPatternAliases(pattern, parameter, source, lineNode) {
|
|
1882
2015
|
return pattern.elements.flatMap((element) => {
|
|
1883
|
-
if (element.dotDotDotToken ||
|
|
2016
|
+
if (element.dotDotDotToken || ts9.isObjectBindingPattern(element.name) || ts9.isArrayBindingPattern(element.name)) return [];
|
|
1884
2017
|
const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
|
|
1885
2018
|
if (!property) return [];
|
|
1886
2019
|
const local = bindingLocalName(element.name, element.initializer);
|
|
@@ -1888,21 +2021,21 @@ function objectPatternAliases(pattern, parameter, source, lineNode) {
|
|
|
1888
2021
|
});
|
|
1889
2022
|
}
|
|
1890
2023
|
function parameterPropertyAliases(fn, source) {
|
|
1891
|
-
const parameterNames = new Set(fn.parameters.flatMap((param) =>
|
|
2024
|
+
const parameterNames = new Set(fn.parameters.flatMap((param) => ts9.isIdentifier(param.name) ? [param.name.text] : []));
|
|
1892
2025
|
if (!fn.body || parameterNames.size === 0) return [];
|
|
1893
2026
|
const aliases = [];
|
|
1894
2027
|
const addFromAssignment = (left, right, node) => {
|
|
1895
|
-
if (!
|
|
2028
|
+
if (!ts9.isObjectLiteralExpression(left) || !ts9.isIdentifier(right) || !parameterNames.has(right.text)) return;
|
|
1896
2029
|
for (const prop of left.properties) {
|
|
1897
|
-
if (!
|
|
2030
|
+
if (!ts9.isPropertyAssignment(prop)) continue;
|
|
1898
2031
|
const property = nameOf(prop.name);
|
|
1899
|
-
if (property &&
|
|
2032
|
+
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
2033
|
}
|
|
1901
2034
|
};
|
|
1902
2035
|
const visit = (node) => {
|
|
1903
|
-
if (
|
|
1904
|
-
if (
|
|
1905
|
-
|
|
2036
|
+
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));
|
|
2037
|
+
if (ts9.isBinaryExpression(node) && node.operatorToken.kind === ts9.SyntaxKind.EqualsToken) addFromAssignment(ts9.isParenthesizedExpression(node.left) ? node.left.expression : node.left, node.right, node);
|
|
2038
|
+
ts9.forEachChild(node, visit);
|
|
1906
2039
|
};
|
|
1907
2040
|
visit(fn.body);
|
|
1908
2041
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1915,10 +2048,10 @@ function parameterPropertyAliases(fn, source) {
|
|
|
1915
2048
|
}
|
|
1916
2049
|
function parameterBindings(params) {
|
|
1917
2050
|
return params.flatMap((param, index) => {
|
|
1918
|
-
if (
|
|
1919
|
-
if (
|
|
2051
|
+
if (ts9.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
|
|
2052
|
+
if (ts9.isObjectBindingPattern(param.name)) {
|
|
1920
2053
|
const properties = param.name.elements.flatMap((element) => {
|
|
1921
|
-
if (element.dotDotDotToken ||
|
|
2054
|
+
if (element.dotDotDotToken || ts9.isObjectBindingPattern(element.name) || ts9.isArrayBindingPattern(element.name)) return [];
|
|
1922
2055
|
const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
|
|
1923
2056
|
if (!property) return [];
|
|
1924
2057
|
const local = bindingLocalName(element.name, element.initializer);
|
|
@@ -1926,8 +2059,8 @@ function parameterBindings(params) {
|
|
|
1926
2059
|
});
|
|
1927
2060
|
return properties.length > 0 ? [{ index, kind: "object_pattern", properties }] : [];
|
|
1928
2061
|
}
|
|
1929
|
-
if (
|
|
1930
|
-
const elements = param.name.elements.flatMap((element, elementIndex) =>
|
|
2062
|
+
if (ts9.isArrayBindingPattern(param.name)) {
|
|
2063
|
+
const elements = param.name.elements.flatMap((element, elementIndex) => ts9.isBindingElement(element) && !element.dotDotDotToken && ts9.isIdentifier(element.name) ? [{ index: elementIndex, local: element.name.text }] : []);
|
|
1931
2064
|
return elements.length > 0 ? [{ index, kind: "array_pattern", elements }] : [];
|
|
1932
2065
|
}
|
|
1933
2066
|
return [];
|
|
@@ -1938,9 +2071,9 @@ function symbolSourceEvidence(collection, node, options) {
|
|
|
1938
2071
|
if (options.classMemberExported) return {
|
|
1939
2072
|
source: "exported_class_member",
|
|
1940
2073
|
exportedClass: options.parentRoot,
|
|
1941
|
-
memberKind:
|
|
2074
|
+
memberKind: ts9.getCombinedModifierFlags(node) & ts9.ModifierFlags.Static ? "static_method" : "class_method"
|
|
1942
2075
|
};
|
|
1943
|
-
if (options.classContainerExported &&
|
|
2076
|
+
if (options.classContainerExported && ts9.isMethodDeclaration(node) && isPublicClassMethod(node)) return {
|
|
1944
2077
|
source: "exported_class_instance_member",
|
|
1945
2078
|
exportedClass: options.parentRoot,
|
|
1946
2079
|
exportedClassExportKind: options.classContainerDefaultExported ? "default" : "named",
|
|
@@ -1964,8 +2097,8 @@ function executableEvidence(node, source) {
|
|
|
1964
2097
|
};
|
|
1965
2098
|
}
|
|
1966
2099
|
function exportedClassMember(collection, kind, parentName, parentRoot, node) {
|
|
1967
|
-
if (kind !== "method" || !parentName || !collection.exportedClasses.has(parentRoot) || !
|
|
1968
|
-
return Boolean(
|
|
2100
|
+
if (kind !== "method" || !parentName || !collection.exportedClasses.has(parentRoot) || !ts9.isMethodDeclaration(node)) return false;
|
|
2101
|
+
return Boolean(ts9.getCombinedModifierFlags(node) & ts9.ModifierFlags.Static) && isPublicClassMethod(node);
|
|
1969
2102
|
}
|
|
1970
2103
|
function classExportState(collection, parentName, parentRoot) {
|
|
1971
2104
|
return {
|
|
@@ -2062,41 +2195,41 @@ function addAliasSymbol(collection, objectName, propertyName3, node) {
|
|
|
2062
2195
|
}
|
|
2063
2196
|
function collectImportSources(collection) {
|
|
2064
2197
|
const visit = (node) => {
|
|
2065
|
-
if (
|
|
2198
|
+
if (ts9.isImportDeclaration(node) && ts9.isStringLiteral(node.moduleSpecifier))
|
|
2066
2199
|
collectEsmImportSources(collection.imports, node);
|
|
2067
|
-
if (
|
|
2200
|
+
if (ts9.isVariableStatement(node))
|
|
2068
2201
|
collectCjsImportSources(collection.imports, node);
|
|
2069
|
-
|
|
2202
|
+
ts9.forEachChild(node, visit);
|
|
2070
2203
|
};
|
|
2071
2204
|
visit(collection.source);
|
|
2072
2205
|
}
|
|
2073
2206
|
function collectEsmImportSources(imports, node) {
|
|
2074
|
-
if (!
|
|
2207
|
+
if (!ts9.isStringLiteral(node.moduleSpecifier)) return;
|
|
2075
2208
|
const source = node.moduleSpecifier.text;
|
|
2076
2209
|
const clause = node.importClause;
|
|
2077
2210
|
if (clause?.name) imports.set(clause.name.text, source);
|
|
2078
2211
|
const named = clause?.namedBindings;
|
|
2079
|
-
if (named &&
|
|
2212
|
+
if (named && ts9.isNamedImports(named))
|
|
2080
2213
|
for (const item of named.elements) imports.set(item.name.text, source);
|
|
2081
|
-
if (named &&
|
|
2214
|
+
if (named && ts9.isNamespaceImport(named))
|
|
2082
2215
|
imports.set(named.name.text, source);
|
|
2083
2216
|
}
|
|
2084
2217
|
function collectCjsImportSources(imports, node) {
|
|
2085
2218
|
for (const declaration of node.declarationList.declarations) {
|
|
2086
2219
|
const source = declaration.initializer ? requireSource(declaration.initializer) : void 0;
|
|
2087
2220
|
if (!source) continue;
|
|
2088
|
-
if (
|
|
2221
|
+
if (ts9.isIdentifier(declaration.name))
|
|
2089
2222
|
imports.set(declaration.name.text, source);
|
|
2090
|
-
if (
|
|
2223
|
+
if (ts9.isObjectBindingPattern(declaration.name)) {
|
|
2091
2224
|
for (const item of declaration.name.elements)
|
|
2092
|
-
if (
|
|
2225
|
+
if (ts9.isIdentifier(item.name)) imports.set(item.name.text, source);
|
|
2093
2226
|
}
|
|
2094
2227
|
}
|
|
2095
2228
|
}
|
|
2096
2229
|
function classPropertySymbol(collection, node, parentClass) {
|
|
2097
2230
|
const initializer = node.initializer;
|
|
2098
2231
|
const localName = nameOf(node.name);
|
|
2099
|
-
if (!localName || !initializer || !
|
|
2232
|
+
if (!localName || !initializer || !ts9.isArrowFunction(initializer) && !ts9.isFunctionExpression(initializer)) return;
|
|
2100
2233
|
const staticPublic = publicStaticProperty(collection, node, parentClass);
|
|
2101
2234
|
const memberKind = propertyMemberKind(initializer, staticPublic);
|
|
2102
2235
|
addExecutableSymbol(
|
|
@@ -2110,22 +2243,22 @@ function classPropertySymbol(collection, node, parentClass) {
|
|
|
2110
2243
|
);
|
|
2111
2244
|
}
|
|
2112
2245
|
function publicStaticProperty(collection, node, parentClass) {
|
|
2113
|
-
const flags =
|
|
2114
|
-
return collection.exportedClasses.has(parentClass) && Boolean(flags &
|
|
2246
|
+
const flags = ts9.getCombinedModifierFlags(node);
|
|
2247
|
+
return collection.exportedClasses.has(parentClass) && Boolean(flags & ts9.ModifierFlags.Static) && (flags & ts9.ModifierFlags.Private) === 0 && (flags & ts9.ModifierFlags.Protected) === 0;
|
|
2115
2248
|
}
|
|
2116
2249
|
function propertyMemberKind(initializer, staticPublic) {
|
|
2117
|
-
if (
|
|
2250
|
+
if (ts9.isArrowFunction(initializer))
|
|
2118
2251
|
return staticPublic ? "static_arrow_function" : "arrow_function_property";
|
|
2119
2252
|
return staticPublic ? "static_function_expression" : "function_expression_property";
|
|
2120
2253
|
}
|
|
2121
2254
|
function objectCallable(property) {
|
|
2122
|
-
if (
|
|
2123
|
-
return
|
|
2255
|
+
if (ts9.isMethodDeclaration(property)) return property;
|
|
2256
|
+
return ts9.isPropertyAssignment(property) && isObjectFunction(property.initializer) ? property.initializer : void 0;
|
|
2124
2257
|
}
|
|
2125
2258
|
function objectLiteralSymbols(collection, objectName, object, objectIsExported) {
|
|
2126
2259
|
if (objectIsExported) collection.objectExports.add(objectName);
|
|
2127
2260
|
for (const property of object.properties) {
|
|
2128
|
-
if (objectIsExported &&
|
|
2261
|
+
if (objectIsExported && ts9.isShorthandPropertyAssignment(property))
|
|
2129
2262
|
addAliasSymbol(collection, objectName, property.name.text, property.name);
|
|
2130
2263
|
const callable = objectCallable(property);
|
|
2131
2264
|
const propertyName3 = callable ? nameOf(property.name) : void 0;
|
|
@@ -2152,7 +2285,7 @@ function variableSymbols(collection, node) {
|
|
|
2152
2285
|
void 0,
|
|
2153
2286
|
exported(node) ? localName : collection.exportNames.get(localName)
|
|
2154
2287
|
);
|
|
2155
|
-
if (
|
|
2288
|
+
if (ts9.isObjectLiteralExpression(initializer))
|
|
2156
2289
|
objectLiteralSymbols(
|
|
2157
2290
|
collection,
|
|
2158
2291
|
localName,
|
|
@@ -2162,7 +2295,7 @@ function variableSymbols(collection, node) {
|
|
|
2162
2295
|
}
|
|
2163
2296
|
}
|
|
2164
2297
|
function collectClassDeclaration(collection, node) {
|
|
2165
|
-
if (!
|
|
2298
|
+
if (!ts9.isClassDeclaration(node) || !node.name) return false;
|
|
2166
2299
|
collection.declaredClasses.add(node.name.text);
|
|
2167
2300
|
if (exported(node) || collection.exportNames.has(node.name.text))
|
|
2168
2301
|
collection.exportedClasses.add(node.name.text);
|
|
@@ -2173,7 +2306,7 @@ function collectClassDeclaration(collection, node) {
|
|
|
2173
2306
|
return true;
|
|
2174
2307
|
}
|
|
2175
2308
|
function collectMethodDeclaration(collection, node, parentClass) {
|
|
2176
|
-
if (!
|
|
2309
|
+
if (!ts9.isMethodDeclaration(node)) return false;
|
|
2177
2310
|
const localName = nameOf(node.name);
|
|
2178
2311
|
if (localName)
|
|
2179
2312
|
addExecutableSymbol(collection, "method", localName, node, parentClass);
|
|
@@ -2182,11 +2315,11 @@ function collectMethodDeclaration(collection, node, parentClass) {
|
|
|
2182
2315
|
function visitDeclaredSymbol(collection, node, parentClass) {
|
|
2183
2316
|
if (collectClassDeclaration(collection, node)) return;
|
|
2184
2317
|
if (collectMethodDeclaration(collection, node, parentClass)) return;
|
|
2185
|
-
if (
|
|
2318
|
+
if (ts9.isPropertyDeclaration(node)) {
|
|
2186
2319
|
if (parentClass) classPropertySymbol(collection, node, parentClass);
|
|
2187
2320
|
return;
|
|
2188
2321
|
}
|
|
2189
|
-
if (
|
|
2322
|
+
if (ts9.isFunctionDeclaration(node) && node.name) {
|
|
2190
2323
|
addExecutableSymbol(
|
|
2191
2324
|
collection,
|
|
2192
2325
|
"function",
|
|
@@ -2197,17 +2330,17 @@ function visitDeclaredSymbol(collection, node, parentClass) {
|
|
|
2197
2330
|
);
|
|
2198
2331
|
return;
|
|
2199
2332
|
}
|
|
2200
|
-
if (
|
|
2333
|
+
if (ts9.isVariableStatement(node)) {
|
|
2201
2334
|
variableSymbols(collection, node);
|
|
2202
2335
|
return;
|
|
2203
2336
|
}
|
|
2204
|
-
|
|
2337
|
+
ts9.forEachChild(node, (child) => visitDeclaredSymbol(collection, child, parentClass));
|
|
2205
2338
|
}
|
|
2206
2339
|
function collectDeclaredSymbols(collection) {
|
|
2207
2340
|
visitDeclaredSymbol(collection, collection.source);
|
|
2208
2341
|
}
|
|
2209
2342
|
function isTopLevelCallback(node) {
|
|
2210
|
-
if (!
|
|
2343
|
+
if (!ts9.isArrowFunction(node) && !ts9.isFunctionExpression(node) || !ts9.isCallExpression(node.parent)) return false;
|
|
2211
2344
|
const callee = symbolCallName(node.parent.expression);
|
|
2212
2345
|
const member = callee.member ?? callee.local;
|
|
2213
2346
|
return Boolean(member && [
|
|
@@ -2249,7 +2382,7 @@ function collectCallbackSymbols(collection) {
|
|
|
2249
2382
|
}
|
|
2250
2383
|
});
|
|
2251
2384
|
}
|
|
2252
|
-
|
|
2385
|
+
ts9.forEachChild(node, visit);
|
|
2253
2386
|
};
|
|
2254
2387
|
visit(collection.source);
|
|
2255
2388
|
}
|
|
@@ -2279,12 +2412,12 @@ function populateCollection(collection) {
|
|
|
2279
2412
|
async function sourceFile(repoPath, filePath, context) {
|
|
2280
2413
|
const snapshot = context?.get(filePath);
|
|
2281
2414
|
const text = snapshot?.text ?? await fs4.readFile(path4.join(repoPath, filePath), "utf8");
|
|
2282
|
-
return snapshot?.sourceFile() ??
|
|
2415
|
+
return snapshot?.sourceFile() ?? ts9.createSourceFile(
|
|
2283
2416
|
filePath,
|
|
2284
2417
|
text,
|
|
2285
|
-
|
|
2418
|
+
ts9.ScriptTarget.Latest,
|
|
2286
2419
|
true,
|
|
2287
|
-
filePath.endsWith(".ts") ?
|
|
2420
|
+
filePath.endsWith(".ts") ? ts9.ScriptKind.TS : ts9.ScriptKind.JS
|
|
2288
2421
|
);
|
|
2289
2422
|
}
|
|
2290
2423
|
async function parseExecutableSymbols(repoPath, filePath, context, preparedOutboundCalls) {
|
|
@@ -2366,13 +2499,6 @@ function mergePackageSymbolEvidence(symbols, analysis) {
|
|
|
2366
2499
|
});
|
|
2367
2500
|
}
|
|
2368
2501
|
|
|
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
2502
|
// src/db/package-target-invalidation.ts
|
|
2377
2503
|
var resolverKeys = /* @__PURE__ */ new Set([
|
|
2378
2504
|
"candidateStrategy",
|
|
@@ -2403,6 +2529,11 @@ function parsedEvidence(value) {
|
|
|
2403
2529
|
function packageName(evidence) {
|
|
2404
2530
|
return parsePackageImportReference(evidence.importBinding)?.requestedPackageName ?? void 0;
|
|
2405
2531
|
}
|
|
2532
|
+
function eventPackageName(evidence) {
|
|
2533
|
+
return parsePackageImportReference(
|
|
2534
|
+
evidence.eventNameConstantImportBinding
|
|
2535
|
+
)?.requestedPackageName ?? void 0;
|
|
2536
|
+
}
|
|
2406
2537
|
function packageCallEvidenceValid(evidence) {
|
|
2407
2538
|
const binding = record(evidence.importBinding);
|
|
2408
2539
|
const classified = evidence.relation === "package_import" || binding?.moduleKind === "package";
|
|
@@ -2439,6 +2570,54 @@ function pendingEvidence(evidence) {
|
|
|
2439
2570
|
unresolvedReason: "package_resolution_pending"
|
|
2440
2571
|
});
|
|
2441
2572
|
}
|
|
2573
|
+
function currentEventCalls(db, workspaceId, targetRepoId) {
|
|
2574
|
+
const rows = db.prepare(`SELECT c.id,c.repo_id repoId,
|
|
2575
|
+
c.unresolved_reason unresolvedReason,c.evidence_json evidenceJson
|
|
2576
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
2577
|
+
WHERE r.workspace_id=? AND r.id<>? AND r.fact_analyzer_version=?
|
|
2578
|
+
AND json_extract(c.evidence_json,
|
|
2579
|
+
'$.eventNameConstantImportBinding.moduleKind')='package'
|
|
2580
|
+
ORDER BY c.id`).all(workspaceId, targetRepoId, ANALYZER_VERSION);
|
|
2581
|
+
return rows.flatMap((row) => {
|
|
2582
|
+
const evidence = parsedEvidence(row.evidenceJson);
|
|
2583
|
+
return evidence && eventPackageName(evidence) && typeof row.id === "number" && typeof row.repoId === "number" ? [{
|
|
2584
|
+
id: row.id,
|
|
2585
|
+
repoId: row.repoId,
|
|
2586
|
+
evidence,
|
|
2587
|
+
unresolvedReason: typeof row.unresolvedReason === "string" ? row.unresolvedReason : null
|
|
2588
|
+
}] : [];
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
function pendingEventEvidence(evidence) {
|
|
2592
|
+
const parser = { ...evidence };
|
|
2593
|
+
delete parser.eventNameConstant;
|
|
2594
|
+
delete parser.eventNamePackageConstantResolution;
|
|
2595
|
+
parser.eventNameUnresolvedReason = "event_name_constant_resolution_pending";
|
|
2596
|
+
parser.eventNameStatus = "dynamic";
|
|
2597
|
+
parser.eventNameSourceKind = "dynamic_expression";
|
|
2598
|
+
parser.eventNamePlaceholderKeys = [];
|
|
2599
|
+
return JSON.stringify(parser);
|
|
2600
|
+
}
|
|
2601
|
+
function resetPackageEventCalls(db, workspaceId, targetRepoId, names, batch) {
|
|
2602
|
+
const update = db.prepare(`UPDATE outbound_calls SET event_name_expr=?,
|
|
2603
|
+
unresolved_reason=?,evidence_json=? WHERE id=?`);
|
|
2604
|
+
let matched = false;
|
|
2605
|
+
for (const call of currentEventCalls(db, workspaceId, targetRepoId)) {
|
|
2606
|
+
if (!names.has(eventPackageName(call.evidence) ?? "")) continue;
|
|
2607
|
+
const source = call.evidence.eventNameConstantSourceExpression;
|
|
2608
|
+
if (typeof source !== "string" || source.length === 0)
|
|
2609
|
+
throw new Error("invalid_current_package_event_constant_evidence");
|
|
2610
|
+
update.run(
|
|
2611
|
+
source,
|
|
2612
|
+
"event_name_constant_resolution_pending",
|
|
2613
|
+
pendingEventEvidence(call.evidence),
|
|
2614
|
+
call.id
|
|
2615
|
+
);
|
|
2616
|
+
batch.affectedCallerRepoIds.add(call.repoId);
|
|
2617
|
+
matched = true;
|
|
2618
|
+
}
|
|
2619
|
+
return matched;
|
|
2620
|
+
}
|
|
2442
2621
|
function targetWorkspace(db, repoId) {
|
|
2443
2622
|
const row = db.prepare(`SELECT workspace_id workspaceId,
|
|
2444
2623
|
package_name packageName FROM repositories WHERE id=?`).get(repoId);
|
|
@@ -2473,6 +2652,13 @@ function invalidatePackageTargetFacts(db, targetRepoId, newPackageName, batch) {
|
|
|
2473
2652
|
batch.affectedCallerRepoIds.add(call.repoId);
|
|
2474
2653
|
matched = true;
|
|
2475
2654
|
}
|
|
2655
|
+
matched = resetPackageEventCalls(
|
|
2656
|
+
db,
|
|
2657
|
+
target.workspaceId,
|
|
2658
|
+
targetRepoId,
|
|
2659
|
+
names,
|
|
2660
|
+
batch
|
|
2661
|
+
) || matched;
|
|
2476
2662
|
if (matched || packageIdentityChanged(
|
|
2477
2663
|
target.packageName,
|
|
2478
2664
|
newPackageName
|
|
@@ -2503,8 +2689,31 @@ function finalizePackageTargetInvalidations(db, batch) {
|
|
|
2503
2689
|
stale.run(workspaceId);
|
|
2504
2690
|
}
|
|
2505
2691
|
|
|
2692
|
+
// src/db/event-surface-invalidation.ts
|
|
2693
|
+
function priorEventFactCount(db, repoId) {
|
|
2694
|
+
const row = db.prepare(`SELECT COUNT(*) count FROM outbound_calls
|
|
2695
|
+
WHERE repo_id=? AND call_type IN ('async_emit','async_subscribe')`).get(
|
|
2696
|
+
repoId
|
|
2697
|
+
);
|
|
2698
|
+
return Number(row?.count ?? 0);
|
|
2699
|
+
}
|
|
2700
|
+
function repositoryEnvironment(db, repoId) {
|
|
2701
|
+
const row = db.prepare(`SELECT environment_declarations_json value
|
|
2702
|
+
FROM repositories WHERE id=?`).get(repoId);
|
|
2703
|
+
return typeof row?.value === "string" ? row.value : null;
|
|
2704
|
+
}
|
|
2705
|
+
function invalidateEventSurfaceFacts(db, repoId, calls, nextEnvironmentJson) {
|
|
2706
|
+
const hasNewEvents = calls.some((call) => call.callType === "async_emit" || call.callType === "async_subscribe");
|
|
2707
|
+
const environmentChanged = repositoryEnvironment(db, repoId) !== nextEnvironmentJson;
|
|
2708
|
+
if (!hasNewEvents && priorEventFactCount(db, repoId) === 0 && !environmentChanged) return;
|
|
2709
|
+
db.prepare(`UPDATE repositories SET
|
|
2710
|
+
graph_stale_reason='event_surface_facts_changed',
|
|
2711
|
+
graph_stale_at=datetime('now')
|
|
2712
|
+
WHERE workspace_id=(SELECT workspace_id FROM repositories WHERE id=?)`).run(repoId);
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2506
2715
|
// src/indexer/repository-indexer.ts
|
|
2507
|
-
async function prepareRepositoryIndex(repo, force, instrumentation) {
|
|
2716
|
+
async function prepareRepositoryIndex(repo, force, instrumentation, eventEnvironmentKeys) {
|
|
2508
2717
|
const sourceFiles = await findSourceFiles(repo.absolute_path);
|
|
2509
2718
|
const packageSnapshot = await loadPackageJsonSnapshot(repo.absolute_path, {
|
|
2510
2719
|
strict: true,
|
|
@@ -2516,13 +2725,23 @@ async function prepareRepositoryIndex(repo, force, instrumentation) {
|
|
|
2516
2725
|
sourceFiles,
|
|
2517
2726
|
instrumentation
|
|
2518
2727
|
);
|
|
2728
|
+
const environmentDeclarations = collectEnvironmentDeclarations(
|
|
2729
|
+
sources,
|
|
2730
|
+
eventEnvironmentKeys
|
|
2731
|
+
);
|
|
2519
2732
|
const fingerprint = repositoryFingerprint(
|
|
2520
2733
|
sources,
|
|
2521
2734
|
packageFacts,
|
|
2522
|
-
packageSnapshot.rawText
|
|
2735
|
+
packageSnapshot.rawText,
|
|
2736
|
+
environmentDeclarations
|
|
2523
2737
|
);
|
|
2524
2738
|
if (!force && repo.fingerprint === fingerprint) return { repo, fileCount: 0, diagnosticCount: 0, skipped: true };
|
|
2525
|
-
const parsedFacts = await parseAllSourceFacts(
|
|
2739
|
+
const parsedFacts = await parseAllSourceFacts(
|
|
2740
|
+
repo.absolute_path,
|
|
2741
|
+
sources,
|
|
2742
|
+
environmentDeclarations,
|
|
2743
|
+
eventEnvironmentKeys
|
|
2744
|
+
);
|
|
2526
2745
|
const packageSurface = analyzeRepositoryPackageSurface(
|
|
2527
2746
|
packageFacts,
|
|
2528
2747
|
packageSnapshot.manifest,
|
|
@@ -2539,6 +2758,7 @@ async function prepareRepositoryIndex(repo, force, instrumentation) {
|
|
|
2539
2758
|
kind: await classifyRepository(repo.absolute_path, packageFacts),
|
|
2540
2759
|
parsed,
|
|
2541
2760
|
packagePublicSurface: packageSurface.surface,
|
|
2761
|
+
environmentDeclarations,
|
|
2542
2762
|
fileCount: sourceFiles.length,
|
|
2543
2763
|
diagnosticCount: parsed.handlers.filter((handler) => handler.hasHandlerDecorator && (handler.methods.length === 0 || handler.methods.some((method) => !handlerMethodIsExecutable(method)))).length,
|
|
2544
2764
|
skipped: false
|
|
@@ -2546,23 +2766,32 @@ async function prepareRepositoryIndex(repo, force, instrumentation) {
|
|
|
2546
2766
|
}
|
|
2547
2767
|
function publishPreparedRepositoryIndex(db, prepared, invalidations) {
|
|
2548
2768
|
if (prepared.skipped) return;
|
|
2549
|
-
if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint || !prepared.kind || !prepared.packagePublicSurface)
|
|
2769
|
+
if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint || !prepared.kind || !prepared.packagePublicSurface || !prepared.environmentDeclarations)
|
|
2550
2770
|
throw new Error("Prepared repository index is missing publication facts");
|
|
2551
2771
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2552
2772
|
const repoId = prepared.repo.id;
|
|
2773
|
+
const environmentJson = JSON.stringify(prepared.environmentDeclarations);
|
|
2553
2774
|
invalidatePackageTargetFacts(
|
|
2554
2775
|
db,
|
|
2555
2776
|
repoId,
|
|
2556
2777
|
prepared.packageFacts.packageName,
|
|
2557
2778
|
invalidations
|
|
2558
2779
|
);
|
|
2780
|
+
invalidateEventSurfaceFacts(
|
|
2781
|
+
db,
|
|
2782
|
+
repoId,
|
|
2783
|
+
prepared.parsed.calls,
|
|
2784
|
+
environmentJson
|
|
2785
|
+
);
|
|
2559
2786
|
db.prepare(`UPDATE repositories SET package_name=?, package_version=?,
|
|
2560
|
-
dependencies_json=?,package_public_surface_json=?,
|
|
2787
|
+
dependencies_json=?,package_public_surface_json=?,
|
|
2788
|
+
environment_declarations_json=?,kind=?,index_status=?
|
|
2561
2789
|
WHERE id=?`).run(
|
|
2562
2790
|
prepared.packageFacts.packageName,
|
|
2563
2791
|
prepared.packageFacts.packageVersion,
|
|
2564
2792
|
JSON.stringify(prepared.packageFacts.dependencies),
|
|
2565
2793
|
JSON.stringify(prepared.packagePublicSurface),
|
|
2794
|
+
environmentJson,
|
|
2566
2795
|
prepared.kind,
|
|
2567
2796
|
"indexing",
|
|
2568
2797
|
repoId
|
|
@@ -2578,6 +2807,7 @@ function publishPreparedRepositoryIndex(db, prepared, invalidations) {
|
|
|
2578
2807
|
insertRegistrations(db, repoId, prepared.parsed.registrations);
|
|
2579
2808
|
insertBindings(db, repoId, prepared.parsed.bindings);
|
|
2580
2809
|
insertCalls(db, repoId, prepared.parsed.calls);
|
|
2810
|
+
insertGeneratedConstants(db, repoId, prepared.parsed.generatedConstants);
|
|
2581
2811
|
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
2812
|
}
|
|
2583
2813
|
function publishOneRepository(db, prepared, invalidations) {
|
|
@@ -2619,15 +2849,33 @@ function recordIndexFailure(db, repoId, error) {
|
|
|
2619
2849
|
)`).run(repoId);
|
|
2620
2850
|
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
2851
|
}
|
|
2622
|
-
async function parseAllSourceFacts(root, sources) {
|
|
2623
|
-
const facts = { services: [], handlers: [], registrations: [], bindings: [], calls: [], symbols: [], symbolCalls: [], fileRecords: [] };
|
|
2852
|
+
async function parseAllSourceFacts(root, sources, environmentDeclarations, eventEnvironmentKeys) {
|
|
2853
|
+
const facts = { services: [], handlers: [], registrations: [], bindings: [], calls: [], symbols: [], symbolCalls: [], generatedConstants: [], fileRecords: [] };
|
|
2624
2854
|
for (const snapshot of sources.entries()) {
|
|
2625
2855
|
const file = snapshot.filePath;
|
|
2626
|
-
facts.fileRecords.push({
|
|
2856
|
+
facts.fileRecords.push({
|
|
2857
|
+
relativePath: normalizePath(file),
|
|
2858
|
+
extension: path5.extname(file),
|
|
2859
|
+
sha256: sourceFactHash(snapshot, environmentDeclarations),
|
|
2860
|
+
sizeBytes: snapshot.sizeBytes
|
|
2861
|
+
});
|
|
2627
2862
|
if (file.endsWith(".cds")) facts.services.push(...await parseCdsFile(root, file, sources));
|
|
2628
2863
|
if (/\.[jt]s$/.test(file)) {
|
|
2629
2864
|
const source = snapshot.sourceFile();
|
|
2630
|
-
|
|
2865
|
+
facts.generatedConstants.push(...generatedConstantFacts(source, file));
|
|
2866
|
+
const classified = classifyOutboundCallsInSource(source, file, {
|
|
2867
|
+
importedEventNameResolver: createImportedEventNameResolver(
|
|
2868
|
+
sources,
|
|
2869
|
+
source,
|
|
2870
|
+
file
|
|
2871
|
+
),
|
|
2872
|
+
eventEnvironmentReferenceResolver: createEventEnvironmentReferenceResolver(
|
|
2873
|
+
sources,
|
|
2874
|
+
source,
|
|
2875
|
+
file,
|
|
2876
|
+
eventEnvironmentKeys
|
|
2877
|
+
)
|
|
2878
|
+
});
|
|
2631
2879
|
facts.handlers.push(...await parseDecorators(root, file, sources));
|
|
2632
2880
|
facts.registrations.push(...await parseHandlerRegistrations(root, file, sources));
|
|
2633
2881
|
const bindings = await parseServiceBindings(root, file, sources);
|
|
@@ -2668,18 +2916,21 @@ async function findSourceFiles(root) {
|
|
|
2668
2916
|
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
2669
2917
|
if (e.isDirectory()) {
|
|
2670
2918
|
if (!["node_modules", "dist", "gen", "coverage", ".git"].includes(e.name)) await walk(path5.join(dir, e.name), rel);
|
|
2671
|
-
} else if (
|
|
2919
|
+
} else if (isRepositoryFactInput(e.name) && !isDefaultTestFile(rel)) out.push(rel);
|
|
2672
2920
|
}
|
|
2673
2921
|
}
|
|
2674
2922
|
await walk(root);
|
|
2675
2923
|
return out.sort();
|
|
2676
2924
|
}
|
|
2925
|
+
function isRepositoryFactInput(name) {
|
|
2926
|
+
return /\.(cds|ts|js)$/.test(name) || ["nodemon.json", ".env", "mta.yaml", "manifest.yml"].includes(name);
|
|
2927
|
+
}
|
|
2677
2928
|
function isDefaultTestFile(relativeFile) {
|
|
2678
2929
|
const parts = relativeFile.split("/");
|
|
2679
2930
|
if (parts.some((part) => ["test", "tests", "__tests__"].includes(part))) return true;
|
|
2680
2931
|
return /\.(test|spec)\.[jt]s$/.test(parts.at(-1) ?? "");
|
|
2681
2932
|
}
|
|
2682
|
-
function repositoryFingerprint(sources, facts, packageJsonText) {
|
|
2933
|
+
function repositoryFingerprint(sources, facts, packageJsonText, environmentDeclarations) {
|
|
2683
2934
|
const normalizedFacts = {
|
|
2684
2935
|
analyzerVersion: ANALYZER_VERSION,
|
|
2685
2936
|
packageName: facts.packageName,
|
|
@@ -2688,13 +2939,33 @@ function repositoryFingerprint(sources, facts, packageJsonText) {
|
|
|
2688
2939
|
cdsRequires: [...facts.cdsRequires].sort((a, b) => a.alias.localeCompare(b.alias)),
|
|
2689
2940
|
scripts: Object.fromEntries(Object.entries(facts.scripts).sort()),
|
|
2690
2941
|
includeTests: false,
|
|
2942
|
+
eventEnvironmentKeys: environmentDeclarations.allowedKeys,
|
|
2691
2943
|
packageJsonHash: sha256Text(packageJsonText)
|
|
2692
2944
|
};
|
|
2693
2945
|
const entries = [`facts:${JSON.stringify(normalizedFacts)}`];
|
|
2694
2946
|
for (const snapshot of sources.entries())
|
|
2695
|
-
entries.push(
|
|
2947
|
+
entries.push(
|
|
2948
|
+
`${snapshot.filePath}:${sourceFactHash(snapshot, environmentDeclarations)}`
|
|
2949
|
+
);
|
|
2696
2950
|
return sha256Text(entries.join("\n"));
|
|
2697
2951
|
}
|
|
2952
|
+
function sourceFactHash(snapshot, environmentDeclarations) {
|
|
2953
|
+
if (!isEnvironmentFactInput(snapshot.filePath))
|
|
2954
|
+
return sha256Text(snapshot.text);
|
|
2955
|
+
const declarations = environmentDeclarations.declarations.filter(
|
|
2956
|
+
(item) => item.sourceFile === snapshot.filePath
|
|
2957
|
+
);
|
|
2958
|
+
return sha256Text(JSON.stringify({
|
|
2959
|
+
allowedKeys: environmentDeclarations.allowedKeys,
|
|
2960
|
+
declarations
|
|
2961
|
+
}));
|
|
2962
|
+
}
|
|
2963
|
+
function isEnvironmentFactInput(filePath) {
|
|
2964
|
+
const name = filePath.split("/").at(-1);
|
|
2965
|
+
return ["nodemon.json", ".env", "mta.yaml", "manifest.yml"].includes(
|
|
2966
|
+
name ?? ""
|
|
2967
|
+
);
|
|
2968
|
+
}
|
|
2698
2969
|
|
|
2699
2970
|
// src/indexer/cds-extension-resolver.ts
|
|
2700
2971
|
function materializeCdsExtensionOperations(db, workspaceId, excludedRepoIds = /* @__PURE__ */ new Set()) {
|
|
@@ -2874,7 +3145,12 @@ async function indexWorkspace(db, workspaceId, options) {
|
|
|
2874
3145
|
rows: []
|
|
2875
3146
|
};
|
|
2876
3147
|
try {
|
|
2877
|
-
await prepareRepositories(
|
|
3148
|
+
await prepareRepositories(
|
|
3149
|
+
repos,
|
|
3150
|
+
options.force,
|
|
3151
|
+
state,
|
|
3152
|
+
options.eventEnvironmentKeys
|
|
3153
|
+
);
|
|
2878
3154
|
return publishPreparedWorkspaceRows(
|
|
2879
3155
|
db,
|
|
2880
3156
|
workspaceId,
|
|
@@ -2901,10 +3177,15 @@ function selectedRepositories(db, workspaceId, repoName) {
|
|
|
2901
3177
|
);
|
|
2902
3178
|
return repos;
|
|
2903
3179
|
}
|
|
2904
|
-
async function prepareRepositories(repos, force, state) {
|
|
3180
|
+
async function prepareRepositories(repos, force, state, eventEnvironmentKeys) {
|
|
2905
3181
|
for (const repo of repos) {
|
|
2906
3182
|
state.activeRepoId = repo.id;
|
|
2907
|
-
const result = await prepareRepositoryIndex(
|
|
3183
|
+
const result = await prepareRepositoryIndex(
|
|
3184
|
+
repo,
|
|
3185
|
+
force,
|
|
3186
|
+
void 0,
|
|
3187
|
+
eventEnvironmentKeys
|
|
3188
|
+
);
|
|
2908
3189
|
state.rows.push(result);
|
|
2909
3190
|
state.fileCount += result.fileCount;
|
|
2910
3191
|
state.diagnosticCount += result.diagnosticCount;
|
|
@@ -3249,6 +3530,392 @@ function symbolCallQuality(db) {
|
|
|
3249
3530
|
};
|
|
3250
3531
|
}
|
|
3251
3532
|
|
|
3533
|
+
// src/cli/doctor-event-quality.ts
|
|
3534
|
+
var eventNameReason = `COALESCE(
|
|
3535
|
+
json_extract(c.evidence_json,'$.eventNameUnresolvedReason'),
|
|
3536
|
+
CASE WHEN c.unresolved_reason GLOB 'dynamic_event_name_*'
|
|
3537
|
+
OR c.unresolved_reason GLOB 'event_name_*'
|
|
3538
|
+
THEN c.unresolved_reason END
|
|
3539
|
+
)`;
|
|
3540
|
+
var receiverReason = `CASE
|
|
3541
|
+
WHEN json_extract(c.evidence_json,
|
|
3542
|
+
'$.receiverClassification')='name_fallback'
|
|
3543
|
+
THEN COALESCE(json_extract(c.evidence_json,
|
|
3544
|
+
'$.receiverFallbackRefusedReason'),'name_fallback')
|
|
3545
|
+
WHEN json_extract(c.evidence_json,
|
|
3546
|
+
'$.receiverClassification')='unproven'
|
|
3547
|
+
THEN COALESCE(json_extract(c.evidence_json,
|
|
3548
|
+
'$.receiverUnresolvedReason'),'unproven')
|
|
3549
|
+
ELSE 'missing' END`;
|
|
3550
|
+
function workspacePredicate(alias) {
|
|
3551
|
+
return `(? IS NULL OR ${alias}.workspace_id=?)`;
|
|
3552
|
+
}
|
|
3553
|
+
function count2(value) {
|
|
3554
|
+
return Number(value ?? 0);
|
|
3555
|
+
}
|
|
3556
|
+
function unresolvedEventNameExamples(db, workspaceId) {
|
|
3557
|
+
return db.prepare(`SELECT r.name repositoryName,
|
|
3558
|
+
c.source_file sourceFile,c.source_line sourceLine,
|
|
3559
|
+
c.event_name_expr eventName,${eventNameReason} reason
|
|
3560
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
3561
|
+
WHERE c.call_type='async_emit' AND ${workspacePredicate("r")}
|
|
3562
|
+
AND ${eventNameReason} IS NOT NULL
|
|
3563
|
+
ORDER BY r.name COLLATE BINARY,c.source_file COLLATE BINARY,
|
|
3564
|
+
c.source_line,c.id LIMIT 5`).all(
|
|
3565
|
+
workspaceId,
|
|
3566
|
+
workspaceId
|
|
3567
|
+
);
|
|
3568
|
+
}
|
|
3569
|
+
function eventNameResolutionQuality(db, workspaceId) {
|
|
3570
|
+
const aggregate = db.prepare(`SELECT COUNT(*) total,
|
|
3571
|
+
SUM(CASE WHEN reason IS NOT NULL THEN 1 ELSE 0 END) unresolved
|
|
3572
|
+
FROM (SELECT ${eventNameReason} reason
|
|
3573
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
3574
|
+
WHERE c.call_type='async_emit' AND ${workspacePredicate("r")})`).get(
|
|
3575
|
+
workspaceId,
|
|
3576
|
+
workspaceId
|
|
3577
|
+
);
|
|
3578
|
+
const reasons = db.prepare(`SELECT ${eventNameReason} reason,COUNT(*) count
|
|
3579
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
3580
|
+
WHERE c.call_type='async_emit' AND ${workspacePredicate("r")}
|
|
3581
|
+
AND ${eventNameReason} IS NOT NULL
|
|
3582
|
+
GROUP BY reason ORDER BY count DESC,reason COLLATE BINARY LIMIT 16`).all(
|
|
3583
|
+
workspaceId,
|
|
3584
|
+
workspaceId
|
|
3585
|
+
);
|
|
3586
|
+
const unresolved = count2(aggregate?.unresolved);
|
|
3587
|
+
const reasonCount = count2(db.prepare(`SELECT COUNT(DISTINCT reason) count
|
|
3588
|
+
FROM (SELECT ${eventNameReason} reason
|
|
3589
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
3590
|
+
WHERE c.call_type='async_emit' AND ${workspacePredicate("r")}
|
|
3591
|
+
AND ${eventNameReason} IS NOT NULL)`).get(
|
|
3592
|
+
workspaceId,
|
|
3593
|
+
workspaceId
|
|
3594
|
+
)?.count);
|
|
3595
|
+
return {
|
|
3596
|
+
severity: unresolved > 0 ? "warning" : "info",
|
|
3597
|
+
code: "strict_event_name_resolution_quality",
|
|
3598
|
+
message: "Event publication name-resolution aggregate",
|
|
3599
|
+
publicationTotal: count2(aggregate?.total),
|
|
3600
|
+
unresolvedPublicationCount: unresolved,
|
|
3601
|
+
reasonBuckets: reasons,
|
|
3602
|
+
reasonBucketCount: reasonCount,
|
|
3603
|
+
shownReasonBucketCount: reasons.length,
|
|
3604
|
+
omittedReasonBucketCount: Math.max(0, reasonCount - reasons.length),
|
|
3605
|
+
examples: unresolvedEventNameExamples(db, workspaceId),
|
|
3606
|
+
exampleCount: unresolved
|
|
3607
|
+
};
|
|
3608
|
+
}
|
|
3609
|
+
function dynamicEventExamples(db, workspaceId) {
|
|
3610
|
+
return db.prepare(`SELECT r.name repositoryName,c.call_type callType,
|
|
3611
|
+
c.source_file sourceFile,c.source_line sourceLine,
|
|
3612
|
+
e.to_kind targetKind,e.to_id targetId,e.unresolved_reason reason
|
|
3613
|
+
FROM graph_edges e JOIN outbound_calls c
|
|
3614
|
+
ON e.from_kind='call' AND c.id=CAST(e.from_id AS INTEGER)
|
|
3615
|
+
JOIN repositories r ON r.id=c.repo_id
|
|
3616
|
+
WHERE c.call_type IN ('async_emit','async_subscribe')
|
|
3617
|
+
AND ${workspacePredicate("r")}
|
|
3618
|
+
AND (e.to_kind='event_candidate'
|
|
3619
|
+
OR e.edge_type='DYNAMIC_EDGE_CANDIDATE')
|
|
3620
|
+
ORDER BY r.name COLLATE BINARY,c.source_file COLLATE BINARY,
|
|
3621
|
+
c.source_line,c.id LIMIT 5`).all(
|
|
3622
|
+
workspaceId,
|
|
3623
|
+
workspaceId
|
|
3624
|
+
);
|
|
3625
|
+
}
|
|
3626
|
+
function dynamicEventQuality(db, workspaceId) {
|
|
3627
|
+
const row = db.prepare(`SELECT
|
|
3628
|
+
COUNT(DISTINCT CASE WHEN e.to_kind='event_candidate'
|
|
3629
|
+
THEN e.workspace_id || ':' || e.to_id END) eventCandidateNodeCount,
|
|
3630
|
+
COUNT(*) dynamicEventEdgeCount,
|
|
3631
|
+
SUM(CASE WHEN json_array_length(e.evidence_json,
|
|
3632
|
+
'$.eventTemplateResolution.placeholders')>0 THEN 1 ELSE 0 END)
|
|
3633
|
+
variableRecoverableCount
|
|
3634
|
+
FROM graph_edges e JOIN outbound_calls c
|
|
3635
|
+
ON e.from_kind='call' AND c.id=CAST(e.from_id AS INTEGER)
|
|
3636
|
+
JOIN repositories r ON r.id=c.repo_id
|
|
3637
|
+
WHERE c.call_type IN ('async_emit','async_subscribe')
|
|
3638
|
+
AND ${workspacePredicate("r")}
|
|
3639
|
+
AND (e.to_kind='event_candidate'
|
|
3640
|
+
OR e.edge_type='DYNAMIC_EDGE_CANDIDATE')`).get(
|
|
3641
|
+
workspaceId,
|
|
3642
|
+
workspaceId
|
|
3643
|
+
);
|
|
3644
|
+
const total = count2(row?.dynamicEventEdgeCount);
|
|
3645
|
+
const recoverable = count2(row?.variableRecoverableCount);
|
|
3646
|
+
return {
|
|
3647
|
+
severity: total > 0 ? "warning" : "info",
|
|
3648
|
+
code: "strict_event_dynamic_candidate_quality",
|
|
3649
|
+
message: "Dynamic event candidate aggregate",
|
|
3650
|
+
eventCandidateNodeCount: count2(row?.eventCandidateNodeCount),
|
|
3651
|
+
dynamicEventEdgeCount: total,
|
|
3652
|
+
eventCandidateCount: total,
|
|
3653
|
+
variableRecoverableCount: recoverable,
|
|
3654
|
+
nonVariableRecoverableCount: Math.max(0, total - recoverable),
|
|
3655
|
+
examples: dynamicEventExamples(db, workspaceId),
|
|
3656
|
+
exampleCount: total
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
function unmatchedPublicationRows(db, workspaceId) {
|
|
3660
|
+
return db.prepare(`SELECT r.name repositoryName,c.source_file sourceFile,
|
|
3661
|
+
c.source_line sourceLine,e.to_id eventName
|
|
3662
|
+
FROM graph_edges e JOIN outbound_calls c
|
|
3663
|
+
ON e.from_kind='call' AND c.id=CAST(e.from_id AS INTEGER)
|
|
3664
|
+
JOIN repositories r ON r.id=c.repo_id
|
|
3665
|
+
WHERE c.call_type='async_emit' AND e.edge_type='HANDLER_EMITS_EVENT'
|
|
3666
|
+
AND e.to_kind='event' AND ${workspacePredicate("r")}
|
|
3667
|
+
AND NOT EXISTS (SELECT 1 FROM graph_edges subscription
|
|
3668
|
+
WHERE subscription.workspace_id=e.workspace_id
|
|
3669
|
+
AND subscription.generation=e.generation
|
|
3670
|
+
AND subscription.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
3671
|
+
AND subscription.from_kind='event'
|
|
3672
|
+
AND subscription.from_id=e.to_id)
|
|
3673
|
+
ORDER BY r.name COLLATE BINARY,c.source_file COLLATE BINARY,
|
|
3674
|
+
c.source_line,c.id LIMIT 5`).all(
|
|
3675
|
+
workspaceId,
|
|
3676
|
+
workspaceId
|
|
3677
|
+
);
|
|
3678
|
+
}
|
|
3679
|
+
function unmatchedPublicationQuality(db, workspaceId) {
|
|
3680
|
+
const row = db.prepare(`SELECT COUNT(*) count
|
|
3681
|
+
FROM graph_edges e JOIN outbound_calls c
|
|
3682
|
+
ON e.from_kind='call' AND c.id=CAST(e.from_id AS INTEGER)
|
|
3683
|
+
JOIN repositories r ON r.id=c.repo_id
|
|
3684
|
+
WHERE c.call_type='async_emit' AND e.edge_type='HANDLER_EMITS_EVENT'
|
|
3685
|
+
AND e.to_kind='event' AND ${workspacePredicate("r")}
|
|
3686
|
+
AND NOT EXISTS (SELECT 1 FROM graph_edges subscription
|
|
3687
|
+
WHERE subscription.workspace_id=e.workspace_id
|
|
3688
|
+
AND subscription.generation=e.generation
|
|
3689
|
+
AND subscription.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
3690
|
+
AND subscription.from_kind='event'
|
|
3691
|
+
AND subscription.from_id=e.to_id)`).get(workspaceId, workspaceId);
|
|
3692
|
+
const total = count2(row?.count);
|
|
3693
|
+
return {
|
|
3694
|
+
severity: total > 0 ? "warning" : "info",
|
|
3695
|
+
code: "strict_event_publication_without_subscription_quality",
|
|
3696
|
+
message: "Resolved event publications without a matching subscription",
|
|
3697
|
+
unmatchedPublicationCount: total,
|
|
3698
|
+
examples: unmatchedPublicationRows(db, workspaceId),
|
|
3699
|
+
exampleCount: total
|
|
3700
|
+
};
|
|
3701
|
+
}
|
|
3702
|
+
function unmatchedSubscriptionRows(db, workspaceId) {
|
|
3703
|
+
return db.prepare(`SELECT COALESCE(json_extract(e.evidence_json,
|
|
3704
|
+
'$.subscriptionConsumerRepositoryName'),
|
|
3705
|
+
json_extract(e.evidence_json,'$.repositoryName')) repositoryName,
|
|
3706
|
+
json_extract(e.evidence_json,'$.sourceFile') sourceFile,
|
|
3707
|
+
json_extract(e.evidence_json,'$.sourceLine') sourceLine,
|
|
3708
|
+
e.from_id eventName,
|
|
3709
|
+
json_extract(c.evidence_json,'$.subscriptionLoopRegistrationStatus')
|
|
3710
|
+
loopRegistrationStatus,
|
|
3711
|
+
json_extract(c.evidence_json,'$.subscriptionLoopRegistrationCount')
|
|
3712
|
+
loopRegistrationCount
|
|
3713
|
+
FROM graph_edges e LEFT JOIN outbound_calls c
|
|
3714
|
+
ON c.id=CAST(json_extract(e.evidence_json,'$.subscribeCallId') AS INTEGER)
|
|
3715
|
+
WHERE e.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
3716
|
+
AND ${workspacePredicate("e")}
|
|
3717
|
+
AND NOT EXISTS (SELECT 1 FROM graph_edges publication
|
|
3718
|
+
WHERE publication.workspace_id=e.workspace_id
|
|
3719
|
+
AND publication.generation=e.generation
|
|
3720
|
+
AND publication.edge_type='HANDLER_EMITS_EVENT'
|
|
3721
|
+
AND publication.to_kind='event'
|
|
3722
|
+
AND publication.to_id=e.from_id)
|
|
3723
|
+
ORDER BY repositoryName COLLATE BINARY,sourceFile COLLATE BINARY,
|
|
3724
|
+
sourceLine,e.id LIMIT 5`).all(
|
|
3725
|
+
workspaceId,
|
|
3726
|
+
workspaceId
|
|
3727
|
+
);
|
|
3728
|
+
}
|
|
3729
|
+
function unmatchedSubscriptionQuality(db, workspaceId) {
|
|
3730
|
+
const row = db.prepare(`SELECT
|
|
3731
|
+
COUNT(DISTINCT json_extract(e.evidence_json,'$.subscribeCallId'))
|
|
3732
|
+
siteCount,
|
|
3733
|
+
SUM(CASE
|
|
3734
|
+
WHEN json_extract(e.evidence_json,'$.materializedLoopEventName')
|
|
3735
|
+
IS NOT NULL THEN 1
|
|
3736
|
+
WHEN json_extract(c.evidence_json,
|
|
3737
|
+
'$.subscriptionLoopRegistrationStatus')='enumerated'
|
|
3738
|
+
THEN CAST(json_extract(c.evidence_json,
|
|
3739
|
+
'$.subscriptionLoopRegistrationCount') AS INTEGER)
|
|
3740
|
+
WHEN json_extract(c.evidence_json,
|
|
3741
|
+
'$.subscriptionLoopRegistrationStatus')='unresolved' THEN 0
|
|
3742
|
+
ELSE 1 END) count,
|
|
3743
|
+
SUM(CASE WHEN json_extract(c.evidence_json,
|
|
3744
|
+
'$.subscriptionLoopRegistrationStatus')='unresolved'
|
|
3745
|
+
THEN 1 ELSE 0 END) unknownMultiplicityEdgeCount
|
|
3746
|
+
FROM graph_edges e LEFT JOIN outbound_calls c
|
|
3747
|
+
ON c.id=CAST(json_extract(e.evidence_json,'$.subscribeCallId') AS INTEGER)
|
|
3748
|
+
WHERE e.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
3749
|
+
AND ${workspacePredicate("e")}
|
|
3750
|
+
AND NOT EXISTS (SELECT 1 FROM graph_edges publication
|
|
3751
|
+
WHERE publication.workspace_id=e.workspace_id
|
|
3752
|
+
AND publication.generation=e.generation
|
|
3753
|
+
AND publication.edge_type='HANDLER_EMITS_EVENT'
|
|
3754
|
+
AND publication.to_kind='event'
|
|
3755
|
+
AND publication.to_id=e.from_id)`).get(workspaceId, workspaceId);
|
|
3756
|
+
const total = count2(row?.count);
|
|
3757
|
+
const siteCount = count2(row?.siteCount);
|
|
3758
|
+
const unknownSites = unknownMultiplicitySiteCount(db, workspaceId);
|
|
3759
|
+
return {
|
|
3760
|
+
severity: siteCount > 0 ? "warning" : "info",
|
|
3761
|
+
code: "strict_event_subscription_without_publication_quality",
|
|
3762
|
+
message: "Event subscriptions without a matching publication",
|
|
3763
|
+
unmatchedSubscriptionCount: total,
|
|
3764
|
+
unmatchedSubscriptionSiteCount: siteCount,
|
|
3765
|
+
unknownMultiplicitySiteCount: unknownSites,
|
|
3766
|
+
examples: unmatchedSubscriptionRows(db, workspaceId),
|
|
3767
|
+
exampleCount: siteCount
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3770
|
+
function unknownMultiplicitySiteCount(db, workspaceId) {
|
|
3771
|
+
const row = db.prepare(`SELECT COUNT(DISTINCT
|
|
3772
|
+
json_extract(e.evidence_json,'$.subscribeCallId')) count
|
|
3773
|
+
FROM graph_edges e LEFT JOIN outbound_calls c
|
|
3774
|
+
ON c.id=CAST(json_extract(e.evidence_json,'$.subscribeCallId') AS INTEGER)
|
|
3775
|
+
WHERE e.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
3776
|
+
AND ${workspacePredicate("e")}
|
|
3777
|
+
AND json_extract(c.evidence_json,
|
|
3778
|
+
'$.subscriptionLoopRegistrationStatus')='unresolved'
|
|
3779
|
+
AND NOT EXISTS (SELECT 1 FROM graph_edges publication
|
|
3780
|
+
WHERE publication.workspace_id=e.workspace_id
|
|
3781
|
+
AND publication.generation=e.generation
|
|
3782
|
+
AND publication.edge_type='HANDLER_EMITS_EVENT'
|
|
3783
|
+
AND publication.to_kind='event'
|
|
3784
|
+
AND publication.to_id=e.from_id)`).get(workspaceId, workspaceId);
|
|
3785
|
+
return count2(row?.count);
|
|
3786
|
+
}
|
|
3787
|
+
function receiverProofAggregate(db, workspaceId) {
|
|
3788
|
+
return db.prepare(`SELECT COUNT(*) eventTotal,
|
|
3789
|
+
SUM(CASE WHEN json_extract(c.evidence_json,
|
|
3790
|
+
'$.receiverClassification')='cap_evidence' THEN 1 ELSE 0 END) proven,
|
|
3791
|
+
SUM(CASE WHEN json_extract(c.evidence_json,
|
|
3792
|
+
'$.receiverClassification')='name_fallback' THEN 1 ELSE 0 END)
|
|
3793
|
+
nameFallback,
|
|
3794
|
+
SUM(CASE WHEN json_extract(c.evidence_json,
|
|
3795
|
+
'$.receiverClassification')='unproven' THEN 1 ELSE 0 END) unproven
|
|
3796
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
3797
|
+
WHERE c.call_type IN ('async_emit','async_subscribe')
|
|
3798
|
+
AND ${workspacePredicate("r")}`).get(workspaceId, workspaceId);
|
|
3799
|
+
}
|
|
3800
|
+
function receiverReasonBuckets(db, workspaceId) {
|
|
3801
|
+
return db.prepare(`SELECT ${receiverReason} reason,COUNT(*) count
|
|
3802
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
3803
|
+
WHERE c.call_type IN ('async_emit','async_subscribe')
|
|
3804
|
+
AND ${workspacePredicate("r")}
|
|
3805
|
+
AND json_extract(c.evidence_json,'$.receiverClassification')
|
|
3806
|
+
<>'cap_evidence'
|
|
3807
|
+
GROUP BY reason ORDER BY count DESC,reason COLLATE BINARY LIMIT 16`).all(
|
|
3808
|
+
workspaceId,
|
|
3809
|
+
workspaceId
|
|
3810
|
+
);
|
|
3811
|
+
}
|
|
3812
|
+
function receiverReasonBucketCount(db, workspaceId) {
|
|
3813
|
+
const row = db.prepare(`SELECT COUNT(DISTINCT reason) count
|
|
3814
|
+
FROM (SELECT ${receiverReason} reason
|
|
3815
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
3816
|
+
WHERE c.call_type IN ('async_emit','async_subscribe')
|
|
3817
|
+
AND ${workspacePredicate("r")}
|
|
3818
|
+
AND json_extract(c.evidence_json,'$.receiverClassification')
|
|
3819
|
+
<>'cap_evidence')`).get(workspaceId, workspaceId);
|
|
3820
|
+
return count2(row?.count);
|
|
3821
|
+
}
|
|
3822
|
+
function receiverProofQuality(db, workspaceId) {
|
|
3823
|
+
const row = receiverProofAggregate(db, workspaceId);
|
|
3824
|
+
const buckets = receiverReasonBuckets(db, workspaceId);
|
|
3825
|
+
const bucketCount = receiverReasonBucketCount(db, workspaceId);
|
|
3826
|
+
const questionable = count2(row?.nameFallback) + count2(row?.unproven);
|
|
3827
|
+
return {
|
|
3828
|
+
severity: questionable > 0 ? "warning" : "info",
|
|
3829
|
+
code: "strict_event_receiver_classification_quality",
|
|
3830
|
+
message: "CAP event receiver proof aggregate",
|
|
3831
|
+
eventTotal: count2(row?.eventTotal),
|
|
3832
|
+
proven: count2(row?.proven),
|
|
3833
|
+
nameFallback: count2(row?.nameFallback),
|
|
3834
|
+
unproven: count2(row?.unproven),
|
|
3835
|
+
questionable,
|
|
3836
|
+
reasonBuckets: buckets,
|
|
3837
|
+
reasonBucketCount: bucketCount,
|
|
3838
|
+
shownReasonBucketCount: buckets.length,
|
|
3839
|
+
omittedReasonBucketCount: Math.max(0, bucketCount - buckets.length),
|
|
3840
|
+
examples: receiverProofExamples(db, workspaceId),
|
|
3841
|
+
exampleCount: questionable
|
|
3842
|
+
};
|
|
3843
|
+
}
|
|
3844
|
+
function receiverProofExamples(db, workspaceId) {
|
|
3845
|
+
return db.prepare(`SELECT r.name repositoryName,c.call_type callType,
|
|
3846
|
+
c.source_file sourceFile,c.source_line sourceLine,
|
|
3847
|
+
json_extract(c.evidence_json,'$.receiverClassification')
|
|
3848
|
+
receiverClassification,
|
|
3849
|
+
COALESCE(json_extract(c.evidence_json,'$.receiverUnresolvedReason'),
|
|
3850
|
+
json_extract(c.evidence_json,'$.receiverFallbackRefusedReason')) reason,
|
|
3851
|
+
json_array_length(json_extract(c.evidence_json,
|
|
3852
|
+
'$.consideredBindingSites')) consideredBindingSiteCount
|
|
3853
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
3854
|
+
WHERE c.call_type IN ('async_emit','async_subscribe')
|
|
3855
|
+
AND ${workspacePredicate("r")}
|
|
3856
|
+
AND json_extract(c.evidence_json,'$.receiverClassification')
|
|
3857
|
+
<>'cap_evidence'
|
|
3858
|
+
ORDER BY r.name COLLATE BINARY,c.source_file COLLATE BINARY,
|
|
3859
|
+
c.source_line,c.id LIMIT 5`).all(
|
|
3860
|
+
workspaceId,
|
|
3861
|
+
workspaceId
|
|
3862
|
+
);
|
|
3863
|
+
}
|
|
3864
|
+
function shapeEnvironmentExamples(db, workspaceId) {
|
|
3865
|
+
return db.prepare(`SELECT e.edge_type edgeType,e.from_id fromId,
|
|
3866
|
+
e.to_kind toKind,e.to_id toId,
|
|
3867
|
+
json_extract(e.evidence_json,
|
|
3868
|
+
'$.eventEnvironmentResolution.status') environmentStatus,
|
|
3869
|
+
json_extract(e.evidence_json,
|
|
3870
|
+
'$.eventEnvironmentResolution.collisionCount') collisionCount
|
|
3871
|
+
FROM graph_edges e WHERE ${workspacePredicate("e")}
|
|
3872
|
+
AND (e.edge_type='EVENT_SHAPE_CANDIDATE_SUBSCRIBER'
|
|
3873
|
+
OR json_extract(e.evidence_json,
|
|
3874
|
+
'$.eventEnvironmentResolution.status')='ambiguous'
|
|
3875
|
+
OR CAST(json_extract(e.evidence_json,
|
|
3876
|
+
'$.eventEnvironmentResolution.collisionCount') AS INTEGER)>1)
|
|
3877
|
+
ORDER BY e.edge_type COLLATE BINARY,e.from_id COLLATE BINARY,
|
|
3878
|
+
e.to_id COLLATE BINARY,e.id LIMIT 5`).all(
|
|
3879
|
+
workspaceId,
|
|
3880
|
+
workspaceId
|
|
3881
|
+
);
|
|
3882
|
+
}
|
|
3883
|
+
function shapeEnvironmentQuality(db, workspaceId) {
|
|
3884
|
+
const row = db.prepare(`SELECT
|
|
3885
|
+
SUM(CASE WHEN e.edge_type='EVENT_SHAPE_CANDIDATE_SUBSCRIBER'
|
|
3886
|
+
THEN 1 ELSE 0 END) shapeCandidates,
|
|
3887
|
+
SUM(CASE WHEN json_extract(e.evidence_json,
|
|
3888
|
+
'$.eventEnvironmentResolution.status')='ambiguous'
|
|
3889
|
+
OR CAST(json_extract(e.evidence_json,
|
|
3890
|
+
'$.eventEnvironmentResolution.collisionCount') AS INTEGER)>1
|
|
3891
|
+
THEN 1 ELSE 0 END) environmentAmbiguities
|
|
3892
|
+
FROM graph_edges e WHERE ${workspacePredicate("e")}`).get(
|
|
3893
|
+
workspaceId,
|
|
3894
|
+
workspaceId
|
|
3895
|
+
);
|
|
3896
|
+
const shapes = count2(row?.shapeCandidates);
|
|
3897
|
+
const ambiguous = count2(row?.environmentAmbiguities);
|
|
3898
|
+
return {
|
|
3899
|
+
severity: shapes + ambiguous > 0 ? "warning" : "info",
|
|
3900
|
+
code: "strict_event_shape_environment_quality",
|
|
3901
|
+
message: "Event skeleton candidate and environment ambiguity aggregate",
|
|
3902
|
+
skeletonCandidateCount: shapes,
|
|
3903
|
+
environmentBindingAmbiguityCount: ambiguous,
|
|
3904
|
+
examples: shapeEnvironmentExamples(db, workspaceId),
|
|
3905
|
+
exampleCount: shapes + ambiguous
|
|
3906
|
+
};
|
|
3907
|
+
}
|
|
3908
|
+
function eventSurfaceQualityDiagnostics(db, workspaceId) {
|
|
3909
|
+
return [
|
|
3910
|
+
eventNameResolutionQuality(db, workspaceId),
|
|
3911
|
+
dynamicEventQuality(db, workspaceId),
|
|
3912
|
+
unmatchedPublicationQuality(db, workspaceId),
|
|
3913
|
+
unmatchedSubscriptionQuality(db, workspaceId),
|
|
3914
|
+
receiverProofQuality(db, workspaceId),
|
|
3915
|
+
shapeEnvironmentQuality(db, workspaceId)
|
|
3916
|
+
];
|
|
3917
|
+
}
|
|
3918
|
+
|
|
3252
3919
|
// src/cli/doctor.ts
|
|
3253
3920
|
function doctorDiagnostics(db, strict, options = {}) {
|
|
3254
3921
|
const lifecycle = factLifecycleDiagnostic(db, options.workspaceId);
|
|
@@ -3387,7 +4054,7 @@ function parserQualityDiagnostics(db, strict, options) {
|
|
|
3387
4054
|
externalHttpTargetQuality(db),
|
|
3388
4055
|
odataInvocationResolutionQuality(db),
|
|
3389
4056
|
...jsonEvidenceQuality(db),
|
|
3390
|
-
|
|
4057
|
+
...eventSurfaceQualityDiagnostics(db, options.workspaceId),
|
|
3391
4058
|
graphDynamicFlagQuality(db),
|
|
3392
4059
|
symbol,
|
|
3393
4060
|
dbq,
|
|
@@ -3422,10 +4089,6 @@ function outboundOwnershipQuality(db) {
|
|
|
3422
4089
|
const ratio = total === 0 ? 0 : Number((without / total).toFixed(4));
|
|
3423
4090
|
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
4091
|
}
|
|
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
4092
|
function graphDynamicFlagQuality(db) {
|
|
3430
4093
|
const row = db.prepare("SELECT COUNT(*) count FROM graph_edges WHERE status='terminal' AND is_dynamic=1").get();
|
|
3431
4094
|
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 +4524,7 @@ function location(evidence) {
|
|
|
3861
4524
|
function renderTraceTable(result) {
|
|
3862
4525
|
const lines = ["Step Type From To Evidence"];
|
|
3863
4526
|
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)} ${
|
|
4527
|
+
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
4528
|
if (e.unresolvedReason)
|
|
3866
4529
|
lines.push(...hintLines(e.evidence).map((hint) => ` ${hint}`));
|
|
3867
4530
|
}
|
|
@@ -3869,6 +4532,13 @@ function renderTraceTable(result) {
|
|
|
3869
4532
|
return `${lines.join("\n")}
|
|
3870
4533
|
`;
|
|
3871
4534
|
}
|
|
4535
|
+
function evidenceSummary(evidence) {
|
|
4536
|
+
const labels = [
|
|
4537
|
+
typeof evidence.dispatchScope === "string" ? `scope=${evidence.dispatchScope}` : void 0,
|
|
4538
|
+
typeof evidence.dispatchCertainty === "string" ? `certainty=${evidence.dispatchCertainty}` : void 0
|
|
4539
|
+
].filter((value) => Boolean(value));
|
|
4540
|
+
return labels.length > 0 ? `${location(evidence)} [${labels.join(",")}]` : location(evidence);
|
|
4541
|
+
}
|
|
3872
4542
|
function diagnosticLines(diagnostic) {
|
|
3873
4543
|
const first = `${String(diagnostic.severity ?? "info")} ${String(diagnostic.code ?? "diagnostic")} ${String(diagnostic.message ?? "")}`;
|
|
3874
4544
|
const details = diagnosticDetailLines(diagnostic);
|
|
@@ -3906,8 +4576,8 @@ function hintLines(evidence) {
|
|
|
3906
4576
|
}
|
|
3907
4577
|
function dynamicHintLines(evidence) {
|
|
3908
4578
|
const exploration = isRecord(evidence.dynamicTargetExploration) ? evidence.dynamicTargetExploration : evidence;
|
|
3909
|
-
const
|
|
3910
|
-
if (
|
|
4579
|
+
const count3 = numberValue(exploration.candidateCount);
|
|
4580
|
+
if (count3 === 0) return [];
|
|
3911
4581
|
const shown = numberValue(exploration.shownCandidateCount);
|
|
3912
4582
|
const omitted = numberValue(exploration.omittedCandidateCount);
|
|
3913
4583
|
const rejected = numberValue(exploration.rejectedCandidateCount);
|
|
@@ -3915,7 +4585,7 @@ function dynamicHintLines(evidence) {
|
|
|
3915
4585
|
`viable candidates: ${shown} shown, ${omitted} omitted; rejected: ${rejected}`
|
|
3916
4586
|
];
|
|
3917
4587
|
lines.push(...varSetHints(exploration.suggestedVarSets));
|
|
3918
|
-
if (omitted > 0 || rejected > 0 || shown <
|
|
4588
|
+
if (omitted > 0 || rejected > 0 || shown < count3)
|
|
3919
4589
|
lines.push("use --dynamic-mode candidates --max-dynamic-candidates 20 to explore candidate branches");
|
|
3920
4590
|
return lines;
|
|
3921
4591
|
}
|
|
@@ -3985,9 +4655,9 @@ function diagnosticLocation(diagnostic) {
|
|
|
3985
4655
|
}
|
|
3986
4656
|
function compactMessage(diagnostic) {
|
|
3987
4657
|
const message = String(diagnostic.message ?? "");
|
|
3988
|
-
const
|
|
4658
|
+
const count3 = typeof diagnostic.count === "number" ? ` count=${diagnostic.count}` : "";
|
|
3989
4659
|
const total = typeof diagnostic.total === "number" ? ` total=${diagnostic.total}` : "";
|
|
3990
|
-
return `${message}${
|
|
4660
|
+
return `${message}${count3}${total}`.trim();
|
|
3991
4661
|
}
|
|
3992
4662
|
function suggestedHintLines(diagnostic) {
|
|
3993
4663
|
const direct = cliHints(diagnostic.suggestedHints);
|
|
@@ -4102,6 +4772,15 @@ function renderCompactJson(value) {
|
|
|
4102
4772
|
`;
|
|
4103
4773
|
}
|
|
4104
4774
|
|
|
4775
|
+
// src/output/repository-inspection.ts
|
|
4776
|
+
function projectRepositoryInspection(repository) {
|
|
4777
|
+
return Object.fromEntries(
|
|
4778
|
+
Object.entries(repository).filter(
|
|
4779
|
+
([key]) => key !== "environment_declarations_json"
|
|
4780
|
+
)
|
|
4781
|
+
);
|
|
4782
|
+
}
|
|
4783
|
+
|
|
4105
4784
|
// src/cli/clean.ts
|
|
4106
4785
|
import fs6 from "fs/promises";
|
|
4107
4786
|
import path6 from "path";
|
|
@@ -4215,7 +4894,7 @@ async function withWorkspace(workspace, fn) {
|
|
|
4215
4894
|
try {
|
|
4216
4895
|
const row = getWorkspace(db, config.rootPath);
|
|
4217
4896
|
const workspaceId = row?.id ?? upsertWorkspace(db, config.rootPath, config.dbPath);
|
|
4218
|
-
return await fn(db, workspaceId, config.rootPath);
|
|
4897
|
+
return await fn(db, workspaceId, config.rootPath, config);
|
|
4219
4898
|
} finally {
|
|
4220
4899
|
db.close();
|
|
4221
4900
|
}
|
|
@@ -4330,10 +5009,11 @@ function registerInitCommand(program) {
|
|
|
4330
5009
|
}
|
|
4331
5010
|
function registerIndexCommand(program) {
|
|
4332
5011
|
program.command("index").option("--workspace <path>").option("--repo <name>").option("--force").action(
|
|
4333
|
-
(opts) => void withWorkspace(opts.workspace, async (db, workspaceId) => {
|
|
5012
|
+
(opts) => void withWorkspace(opts.workspace, async (db, workspaceId, _root, config) => {
|
|
4334
5013
|
const r = await indexWorkspace(db, workspaceId, {
|
|
4335
5014
|
repo: opts.repo,
|
|
4336
|
-
force: Boolean(opts.force)
|
|
5015
|
+
force: Boolean(opts.force),
|
|
5016
|
+
eventEnvironmentKeys: config.eventEnvironmentKeys
|
|
4337
5017
|
});
|
|
4338
5018
|
const outcome = indexCommandOutcome(r);
|
|
4339
5019
|
writeStdout(outcome.stdout);
|
|
@@ -4348,7 +5028,7 @@ function registerLinkCommand(program) {
|
|
|
4348
5028
|
const upgradeWarnings = linkUpgradeWarnings(db, workspaceId);
|
|
4349
5029
|
writeStdout(
|
|
4350
5030
|
`${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
|
|
5031
|
+
` : ""}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, ${r.eventShapeCandidateOmittedCount} event shape candidates omitted by the link cap
|
|
4352
5032
|
`
|
|
4353
5033
|
);
|
|
4354
5034
|
}).catch(fail)
|
|
@@ -4358,18 +5038,20 @@ function registerTraceCommand(program) {
|
|
|
4358
5038
|
program.command("trace").option("--workspace <path>").option("--repo <name>").option("--operation <name>").option("--service <path>").option("--path <operationPath>").option("--handler <name>").option("--depth <n>", "trace depth", "25").addOption(traceFormatOption()).option("--include-external").option("--include-db").option("--include-async").option("--implementation-repo <name>").option("--implementation-hint <scope>", "scoped implementation hint", collect, []).option("--var <key=value>", "dynamic variable", collect, []).option("--dynamic-mode <mode>", "strict|candidates|infer", "strict").option("--max-dynamic-candidates <n>", "maximum dynamic candidates to show", "5").action((opts) => void runTraceCommand(opts).catch(fail));
|
|
4359
5039
|
}
|
|
4360
5040
|
function listRepositoriesCommand(opts) {
|
|
4361
|
-
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) =>
|
|
4362
|
-
|
|
5041
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
5042
|
+
if (writeLifecycleBlock(db, workspaceId)) return;
|
|
5043
|
+
writeStdout(renderJson(
|
|
4363
5044
|
listRepositories(db, workspaceId).map((repo) => ({
|
|
4364
5045
|
name: repo.name,
|
|
4365
5046
|
kind: repo.kind,
|
|
4366
5047
|
packageName: repo.package_name
|
|
4367
5048
|
}))
|
|
4368
|
-
)
|
|
4369
|
-
)
|
|
5049
|
+
));
|
|
5050
|
+
});
|
|
4370
5051
|
}
|
|
4371
5052
|
function listServicesCommand(opts) {
|
|
4372
5053
|
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
5054
|
+
if (writeLifecycleBlock(db, workspaceId)) return;
|
|
4373
5055
|
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
4374
5056
|
if (selection.diagnostic) {
|
|
4375
5057
|
writeStdout(renderJson([selection.diagnostic]));
|
|
@@ -4384,6 +5066,7 @@ function listServicesCommand(opts) {
|
|
|
4384
5066
|
}
|
|
4385
5067
|
function listOperationsCommand(opts) {
|
|
4386
5068
|
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
5069
|
+
if (writeLifecycleBlock(db, workspaceId)) return;
|
|
4387
5070
|
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
4388
5071
|
if (selection.diagnostic) {
|
|
4389
5072
|
writeStdout(renderJson([selection.diagnostic]));
|
|
@@ -4404,6 +5087,7 @@ function listOperationsCommand(opts) {
|
|
|
4404
5087
|
}
|
|
4405
5088
|
function listCallsCommand(opts) {
|
|
4406
5089
|
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
5090
|
+
if (writeLifecycleBlock(db, workspaceId)) return;
|
|
4407
5091
|
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
4408
5092
|
if (selection.diagnostic) {
|
|
4409
5093
|
writeStdout(renderJson([selection.diagnostic]));
|
|
@@ -4444,20 +5128,29 @@ function registerGraphCommand(program) {
|
|
|
4444
5128
|
}
|
|
4445
5129
|
function inspectRepositoryCommand(name, opts) {
|
|
4446
5130
|
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
5131
|
+
if (writeLifecycleBlock(db, workspaceId)) return;
|
|
4447
5132
|
const selection = selectRepository(db, name, workspaceId);
|
|
4448
5133
|
writeStdout(renderJson(
|
|
4449
|
-
selection.repo
|
|
5134
|
+
selection.repo ? projectRepositoryInspection(selection.repo) : selection.diagnostic ?? { error: "repo not found" }
|
|
4450
5135
|
));
|
|
4451
5136
|
});
|
|
4452
5137
|
}
|
|
4453
5138
|
function inspectOperationCommand(selector, opts) {
|
|
4454
5139
|
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
5140
|
+
if (writeLifecycleBlock(db, workspaceId)) return;
|
|
4455
5141
|
const rows = db.prepare(
|
|
4456
5142
|
"SELECT o.* FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (o.operation_name=? OR o.operation_path=?)"
|
|
4457
5143
|
).all(workspaceId, selector, selector);
|
|
4458
5144
|
writeStdout(renderJson(rows));
|
|
4459
5145
|
});
|
|
4460
5146
|
}
|
|
5147
|
+
function writeLifecycleBlock(db, workspaceId) {
|
|
5148
|
+
const diagnostic = factLifecycleDiagnostic(db, workspaceId);
|
|
5149
|
+
if (!diagnostic) return false;
|
|
5150
|
+
writeStdout(renderJson([diagnostic]));
|
|
5151
|
+
process.exitCode = 1;
|
|
5152
|
+
return true;
|
|
5153
|
+
}
|
|
4461
5154
|
function registerInspectCommands(program) {
|
|
4462
5155
|
const inspect = program.command("inspect");
|
|
4463
5156
|
inspect.command("repo").argument("<name>").option("--workspace <path>").action(
|