@saptools/service-flow 0.1.67 → 0.1.69

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.
Files changed (101) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +27 -9
  3. package/TECHNICAL-NOTE.md +36 -0
  4. package/dist/chunk-3N3B5KHV.js +19596 -0
  5. package/dist/chunk-3N3B5KHV.js.map +1 -0
  6. package/dist/cli.js +2645 -521
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +67 -2
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/001-index-summary.ts +22 -0
  12. package/src/cli/003-doctor-package-resolution.ts +68 -0
  13. package/src/cli/doctor.ts +25 -14
  14. package/src/cli.ts +151 -87
  15. package/src/db/000-call-fact-repository.ts +499 -340
  16. package/src/db/001-fact-lifecycle.ts +60 -30
  17. package/src/db/002-fact-json-inventory.ts +169 -0
  18. package/src/db/003-current-fact-semantics.ts +699 -0
  19. package/src/db/004-package-target-invalidation.ts +183 -0
  20. package/src/db/005-schema-structure.ts +201 -0
  21. package/src/db/006-relative-symbol-resolution.ts +464 -0
  22. package/src/db/007-package-fact-semantics.ts +573 -0
  23. package/src/db/008-relative-fact-semantics.ts +210 -0
  24. package/src/db/009-binding-fact-semantics.ts +352 -0
  25. package/src/db/010-package-symbol-surface-semantics.ts +320 -0
  26. package/src/db/011-symbol-call-semantics.ts +144 -0
  27. package/src/db/012-binding-reference-proof.ts +268 -0
  28. package/src/db/013-index-publication-failure.ts +91 -0
  29. package/src/db/014-binding-helper-provenance.ts +17 -0
  30. package/src/db/migrations.ts +16 -3
  31. package/src/db/repositories.ts +130 -6
  32. package/src/db/schema.ts +4 -2
  33. package/src/index.ts +12 -0
  34. package/src/indexer/cds-extension-resolver.ts +27 -3
  35. package/src/indexer/repository-indexer.ts +135 -13
  36. package/src/indexer/workspace-indexer.ts +237 -34
  37. package/src/linker/003-package-import-symbol-resolver.ts +363 -131
  38. package/src/linker/004-event-subscription-handler-linker.ts +34 -11
  39. package/src/linker/005-odata-path-structure.ts +371 -0
  40. package/src/linker/006-event-template-link.ts +72 -0
  41. package/src/linker/007-call-edge-insertion.ts +568 -0
  42. package/src/linker/cross-repo-linker.ts +4 -166
  43. package/src/linker/odata-path-normalizer.ts +273 -180
  44. package/src/linker/service-resolver.ts +197 -77
  45. package/src/parsers/000-direct-query-execution.ts +11 -0
  46. package/src/parsers/002-symbol-import-bindings.ts +516 -0
  47. package/src/parsers/003-package-public-surface.ts +661 -0
  48. package/src/parsers/004-fact-identity.ts +108 -0
  49. package/src/parsers/005-event-subscription-facts.ts +281 -0
  50. package/src/parsers/006-binding-identity.ts +348 -0
  51. package/src/parsers/007-source-fact-reconciliation.ts +105 -0
  52. package/src/parsers/008-package-surface-publication.ts +82 -0
  53. package/src/parsers/009-symbol-call-facts.ts +528 -0
  54. package/src/parsers/010-package-public-surface-analysis.ts +352 -0
  55. package/src/parsers/011-binding-lexical-scope.ts +583 -0
  56. package/src/parsers/012-package-fact-contract.ts +306 -0
  57. package/src/parsers/013-executable-body-eligibility.ts +35 -0
  58. package/src/parsers/014-service-binding-helper-flow.ts +306 -0
  59. package/src/parsers/015-service-binding-collector.ts +693 -0
  60. package/src/parsers/016-local-symbol-reference.ts +261 -0
  61. package/src/parsers/017-symbol-derived-contexts.ts +268 -0
  62. package/src/parsers/018-package-commonjs-syntax.ts +142 -0
  63. package/src/parsers/019-binding-assignment-targets.ts +76 -0
  64. package/src/parsers/020-stable-local-value.ts +217 -0
  65. package/src/parsers/021-binding-visibility.ts +168 -0
  66. package/src/parsers/022-outbound-expression-analysis.ts +700 -0
  67. package/src/parsers/023-outbound-call-classifier.ts +692 -0
  68. package/src/parsers/operation-path-analysis.ts +6 -1
  69. package/src/parsers/outbound-call-parser.ts +162 -512
  70. package/src/parsers/package-json-parser.ts +45 -3
  71. package/src/parsers/service-binding-parser-helpers.ts +86 -15
  72. package/src/parsers/service-binding-parser.ts +147 -597
  73. package/src/parsers/symbol-parser.ts +513 -352
  74. package/src/trace/002-trace-diagnostics.ts +36 -1
  75. package/src/trace/007-implementation-start-diagnostic.ts +1 -0
  76. package/src/trace/011-event-subscriber-traversal.ts +100 -8
  77. package/src/trace/013-trace-root-scopes.ts +2 -3
  78. package/src/trace/014-compact-contract.ts +6 -0
  79. package/src/trace/015-trace-edge-recorder.ts +61 -4
  80. package/src/trace/016-compact-projector.ts +15 -17
  81. package/src/trace/019-trace-edge-semantics.ts +6 -10
  82. package/src/trace/020-compact-field-projection.ts +122 -38
  83. package/src/trace/021-compact-decision-normalization.ts +171 -0
  84. package/src/trace/022-trace-fact-preflight.ts +21 -0
  85. package/src/trace/023-nested-event-scopes.ts +23 -0
  86. package/src/trace/024-compact-observation-decision.ts +81 -0
  87. package/src/trace/025-trace-implementation-scope.ts +123 -0
  88. package/src/trace/026-trace-start-scope.ts +336 -0
  89. package/src/trace/027-trace-scope-execution.ts +566 -0
  90. package/src/trace/028-trace-operation-execution.ts +336 -0
  91. package/src/trace/029-trace-start-implementation.ts +172 -0
  92. package/src/trace/030-event-runtime-resolution.ts +151 -0
  93. package/src/trace/031-local-call-expansion.ts +37 -0
  94. package/src/trace/implementation-hints.ts +9 -6
  95. package/src/trace/selectors.ts +1 -0
  96. package/src/trace/trace-engine.ts +122 -624
  97. package/src/types.ts +57 -0
  98. package/src/utils/001-placeholders.ts +188 -10
  99. package/src/version.ts +1 -1
  100. package/dist/chunk-ZQABU7MR.js +0 -12151
  101. package/dist/chunk-ZQABU7MR.js.map +0 -1
@@ -0,0 +1,306 @@
1
+ import {
2
+ PACKAGE_PUBLIC_SURFACE_RECORD_CAP,
3
+ PACKAGE_PUBLIC_SURFACE_SCHEMA,
4
+ } from './003-package-public-surface.js';
5
+ import type {
6
+ ExecutableBodyEligibility,
7
+ PackagePublicEntry,
8
+ PackagePublicScope,
9
+ PackagePublicSurfaceFact,
10
+ PublicSurfaceTarget,
11
+ } from './003-package-public-surface.js';
12
+ import {
13
+ packageModuleRequest,
14
+ type SymbolImportBindingKind,
15
+ type SymbolImportReference,
16
+ type SymbolImportReferenceShape,
17
+ } from './002-symbol-import-bindings.js';
18
+
19
+ const bindingKinds = new Set<SymbolImportBindingKind>([
20
+ 'esm_named', 'esm_default', 'esm_namespace',
21
+ 'cjs_destructured', 'cjs_namespace',
22
+ ]);
23
+ const referenceShapes = new Set<SymbolImportReferenceShape>([
24
+ 'identifier', 'namespace_member', 'static_member', 'default_member',
25
+ ]);
26
+ const bodyReasons = new Set([
27
+ 'body_present', 'declaration_only', 'ambient_declaration',
28
+ 'abstract_bodyless', 'overload_signature',
29
+ ]);
30
+ const surfaceReasons = new Set([
31
+ 'anonymous_default_export_without_symbol_identity',
32
+ 'unsupported_namespace_reexport',
33
+ 'unsupported_export_declaration',
34
+ 'unsupported_export_assignment',
35
+ 'unsupported_commonjs_export_shape',
36
+ 'unsupported_mutable_export_binding',
37
+ 'unsupported_external_reexport',
38
+ 'unsupported_exports_map_shape',
39
+ 'unsupported_package_entrypoint_shape',
40
+ 'public_surface_entry_target_not_indexed',
41
+ 'public_surface_main_module_conflict',
42
+ 'public_surface_module_resolution_ambiguous',
43
+ 'public_surface_module_not_indexed',
44
+ 'public_surface_reexport_cycle',
45
+ 'public_surface_reexport_depth_exceeded',
46
+ ]);
47
+
48
+ function record(value: unknown): Record<string, unknown> | undefined {
49
+ return value && typeof value === 'object' && !Array.isArray(value)
50
+ ? value as Record<string, unknown> : undefined;
51
+ }
52
+
53
+ function nonEmpty(value: unknown): value is string {
54
+ return typeof value === 'string' && value.length > 0;
55
+ }
56
+
57
+ function nullableString(value: unknown): value is string | null {
58
+ return value === null || typeof value === 'string';
59
+ }
60
+
61
+ function nonNegativeInteger(value: unknown): value is number {
62
+ return Number.isInteger(value) && Number(value) >= 0;
63
+ }
64
+
65
+ function all(values: readonly boolean[]): boolean {
66
+ return values.every(Boolean);
67
+ }
68
+
69
+ function bindingShapeValid(value: Record<string, unknown>): boolean {
70
+ const kind = value.bindingKind as SymbolImportBindingKind;
71
+ const imported = value.importedName;
72
+ if (!all([
73
+ bindingKinds.has(kind),
74
+ nonEmpty(value.localName),
75
+ typeof value.typeOnly === 'boolean',
76
+ ])) return false;
77
+ if (kind === 'esm_namespace' || kind === 'cjs_namespace')
78
+ return imported === null;
79
+ if (!nonEmpty(imported)) return false;
80
+ return kind !== 'esm_default' || imported === 'default';
81
+ }
82
+
83
+ function memberReferenceValid(
84
+ value: Record<string, unknown>,
85
+ shape: SymbolImportReferenceShape,
86
+ ): boolean {
87
+ const kind = value.bindingKind as SymbolImportBindingKind;
88
+ const member = value.referencedMemberName;
89
+ if (!nonEmpty(member)) return false;
90
+ if (shape === 'namespace_member') return all([
91
+ kind === 'esm_namespace' || kind === 'cjs_namespace',
92
+ value.requestedPublicName === member,
93
+ ]);
94
+ if (!nonEmpty(value.importedName)) return false;
95
+ return all([
96
+ value.requestedPublicName === `${value.importedName}.${member}`,
97
+ shape !== 'default_member' || kind === 'esm_default',
98
+ ]);
99
+ }
100
+
101
+ function referenceShapeValid(value: Record<string, unknown>): boolean {
102
+ const shape = value.referenceShape as SymbolImportReferenceShape;
103
+ if (!referenceShapes.has(shape)
104
+ || !nonEmpty(value.requestedPublicName)) return false;
105
+ return shape === 'identifier'
106
+ ? all([
107
+ value.referencedMemberName === null,
108
+ nonEmpty(value.importedName),
109
+ value.requestedPublicName === value.importedName,
110
+ ])
111
+ : memberReferenceValid(value, shape);
112
+ }
113
+
114
+ function importReferenceBaseValid(
115
+ item: Record<string, unknown>,
116
+ ): boolean {
117
+ return all([
118
+ item.version === 1,
119
+ bindingShapeValid(item),
120
+ referenceShapeValid(item),
121
+ nonEmpty(item.rawModuleSpecifier),
122
+ nonNegativeInteger(item.bindingSiteStartOffset),
123
+ nonNegativeInteger(item.bindingSiteEndOffset),
124
+ Number(item.bindingSiteEndOffset)
125
+ > Number(item.bindingSiteStartOffset),
126
+ ]);
127
+ }
128
+
129
+ export function parsePackageImportReference(
130
+ value: unknown,
131
+ ): SymbolImportReference | undefined {
132
+ const item = record(value);
133
+ if (!item || !importReferenceBaseValid(item)
134
+ || item.moduleKind !== 'package'
135
+ || !nonEmpty(item.requestedPackageName)
136
+ || !nonEmpty(item.requestedModuleSubpath)) return undefined;
137
+ const specifier = item.rawModuleSpecifier;
138
+ if (!nonEmpty(specifier)) return undefined;
139
+ const request = packageModuleRequest(specifier);
140
+ if (!request || request.packageName !== item.requestedPackageName
141
+ || request.moduleSubpath !== item.requestedModuleSubpath) return undefined;
142
+ return item as unknown as SymbolImportReference;
143
+ }
144
+
145
+ export function parseRelativeImportReference(
146
+ value: unknown,
147
+ ): SymbolImportReference | undefined {
148
+ const item = record(value);
149
+ if (!item || !importReferenceBaseValid(item)) return undefined;
150
+ if (!all([
151
+ item.moduleKind === 'relative',
152
+ item.requestedPackageName === null,
153
+ item.requestedModuleSubpath === null,
154
+ String(item.rawModuleSpecifier).startsWith('.'),
155
+ ])) return undefined;
156
+ return item as unknown as SymbolImportReference;
157
+ }
158
+
159
+ function validBody(value: unknown): value is ExecutableBodyEligibility {
160
+ const item = record(value);
161
+ if (!item || typeof item.eligible !== 'boolean'
162
+ || typeof item.reason !== 'string' || !bodyReasons.has(item.reason))
163
+ return false;
164
+ return item.eligible === (item.reason === 'body_present');
165
+ }
166
+
167
+ function validTarget(value: unknown): value is PublicSurfaceTarget {
168
+ const item = record(value);
169
+ return Boolean(item && nonEmpty(item.sourceFile)
170
+ && ['function', 'method', 'object_method'].includes(String(item.kind))
171
+ && nonEmpty(item.localName) && nonEmpty(item.qualifiedName)
172
+ && nonNegativeInteger(item.startOffset)
173
+ && nonNegativeInteger(item.endOffset)
174
+ && Number(item.endOffset) > Number(item.startOffset)
175
+ && validBody(item.bodyEligibility));
176
+ }
177
+
178
+ function targetIdentity(value: PublicSurfaceTarget): string {
179
+ return [
180
+ value.sourceFile, value.kind, value.qualifiedName,
181
+ value.startOffset, value.endOffset,
182
+ ].join('\0');
183
+ }
184
+
185
+ function validEntry(value: unknown): value is PackagePublicEntry {
186
+ const item = record(value);
187
+ return Boolean(item && nonEmpty(item.entry) && nonEmpty(item.modulePath)
188
+ && (item.entry === '.' || String(item.entry).startsWith('./')));
189
+ }
190
+
191
+ function validScope(value: unknown): value is PackagePublicScope {
192
+ const item = record(value);
193
+ if (!item || !all([
194
+ validEntry(item),
195
+ nonEmpty(item.publicName),
196
+ nonNegativeInteger(item.candidateCount),
197
+ nonNegativeInteger(item.eligibleCandidateCount),
198
+ [0, 1].includes(Number(item.selectedCandidateCount)),
199
+ item.candidateSetComplete === true,
200
+ Array.isArray(item.targets),
201
+ ])) return false;
202
+ if (!Array.isArray(item.targets) || !item.targets.every(validTarget))
203
+ return false;
204
+ const targetKeys = new Set(item.targets.map(targetIdentity));
205
+ if (targetKeys.size !== item.targets.length) return false;
206
+ const eligible = item.targets.filter((target) =>
207
+ target.bodyEligibility.eligible).length;
208
+ return all([
209
+ Number(item.candidateCount) >= item.targets.length,
210
+ item.eligibleCandidateCount === eligible,
211
+ item.selectedCandidateCount === (eligible === 1 ? 1 : 0),
212
+ ]);
213
+ }
214
+
215
+ function statusMatrixValid(
216
+ value: Record<string, unknown>,
217
+ expectedPackageName: string | null,
218
+ ): boolean {
219
+ const status = value.status;
220
+ const reason = value.reason;
221
+ if (!['complete', 'incomplete', 'unsupported', 'not_applicable']
222
+ .includes(String(status))) return false;
223
+ if (status === 'complete')
224
+ return expectedPackageName !== null && reason === null;
225
+ if (status === 'not_applicable') return all([
226
+ expectedPackageName === null,
227
+ reason === null,
228
+ value.total === 0,
229
+ value.shown === 0,
230
+ value.omitted === 0,
231
+ ]);
232
+ return expectedPackageName !== null
233
+ && typeof reason === 'string' && surfaceReasons.has(reason);
234
+ }
235
+
236
+ function surfaceCountsValid(value: Record<string, unknown>): boolean {
237
+ if (!nonNegativeInteger(value.total) || !nonNegativeInteger(value.shown)
238
+ || !nonNegativeInteger(value.omitted)
239
+ || Number(value.shown) + Number(value.omitted) !== Number(value.total))
240
+ return false;
241
+ if (!Array.isArray(value.entries) || !Array.isArray(value.scopes)
242
+ || !value.scopes.every((scope) => {
243
+ const item = record(scope);
244
+ return item && Array.isArray(item.targets);
245
+ })) return false;
246
+ const entries = value.entries;
247
+ const scopes = value.scopes as Array<Record<string, unknown>>;
248
+ const represented = entries.length + scopes.reduce(
249
+ (sum, scope) => sum + 1 + (scope.targets as unknown[]).length, 0,
250
+ );
251
+ return represented === value.shown
252
+ && Number(value.total) >= represented
253
+ && Number(value.shown) <= PACKAGE_PUBLIC_SURFACE_RECORD_CAP;
254
+ }
255
+
256
+ function entriesAndScopesValid(value: Record<string, unknown>): boolean {
257
+ if (!Array.isArray(value.entries) || !Array.isArray(value.scopes))
258
+ return false;
259
+ if (!value.entries.every(validEntry) || !value.scopes.every(validScope))
260
+ return false;
261
+ const entries = new Map((value.entries as PackagePublicEntry[])
262
+ .map((entry) => [entry.entry, entry.modulePath]));
263
+ const entryKeys = new Set((value.entries as PackagePublicEntry[])
264
+ .map((entry) => `${entry.entry}\0${entry.modulePath}`));
265
+ if (entryKeys.size !== value.entries.length) return false;
266
+ const scopeKeys = new Set<string>();
267
+ for (const scope of value.scopes as PackagePublicScope[]) {
268
+ if (entries.get(scope.entry) !== scope.modulePath) return false;
269
+ scopeKeys.add(`${scope.entry}\0${scope.publicName}`);
270
+ }
271
+ return scopeKeys.size === value.scopes.length;
272
+ }
273
+
274
+ function surfaceBaseValid(
275
+ item: Record<string, unknown>,
276
+ expectedPackageName: string | null,
277
+ ): boolean {
278
+ return all([
279
+ item.schema === PACKAGE_PUBLIC_SURFACE_SCHEMA,
280
+ item.recordCap === PACKAGE_PUBLIC_SURFACE_RECORD_CAP,
281
+ item.packageName === expectedPackageName,
282
+ typeof item.exportsPresent === 'boolean',
283
+ item.exportsAuthoritative === item.exportsPresent,
284
+ nullableString(item.main),
285
+ nullableString(item.module),
286
+ entriesAndScopesValid(item),
287
+ surfaceCountsValid(item),
288
+ statusMatrixValid(item, expectedPackageName),
289
+ ]);
290
+ }
291
+
292
+ function nonCompleteSurfaceEmpty(item: Record<string, unknown>): boolean {
293
+ if (item.status === 'complete') return true;
294
+ return Array.isArray(item.entries) && item.entries.length === 0
295
+ && Array.isArray(item.scopes) && item.scopes.length === 0;
296
+ }
297
+
298
+ export function parsePackagePublicSurfaceFact(
299
+ value: unknown,
300
+ expectedPackageName: string | null,
301
+ ): PackagePublicSurfaceFact | undefined {
302
+ const item = record(value);
303
+ if (!item || !surfaceBaseValid(item, expectedPackageName)
304
+ || !nonCompleteSurfaceEmpty(item)) return undefined;
305
+ return item as unknown as PackagePublicSurfaceFact;
306
+ }
@@ -0,0 +1,35 @@
1
+ import ts from 'typescript';
2
+
3
+ export type ExecutableBodyEligibilityReason =
4
+ | 'body_present'
5
+ | 'declaration_only'
6
+ | 'ambient_declaration'
7
+ | 'abstract_bodyless'
8
+ | 'overload_signature';
9
+
10
+ export interface ExecutableBodyEligibilityEvidence {
11
+ eligible: boolean;
12
+ reason: ExecutableBodyEligibilityReason;
13
+ }
14
+
15
+ function hasModifier(node: ts.Node, kind: ts.SyntaxKind): boolean {
16
+ return ts.canHaveModifiers(node)
17
+ && Boolean(ts.getModifiers(node)?.some((item) => item.kind === kind));
18
+ }
19
+
20
+ export function executableBodyEligibility(
21
+ node: ts.FunctionLikeDeclaration,
22
+ source: ts.SourceFile,
23
+ ): ExecutableBodyEligibilityEvidence {
24
+ if (node.body) return { eligible: true, reason: 'body_present' };
25
+ if (hasModifier(node, ts.SyntaxKind.AbstractKeyword))
26
+ return { eligible: false, reason: 'abstract_bodyless' };
27
+ if (hasModifier(node, ts.SyntaxKind.DeclareKeyword))
28
+ return { eligible: false, reason: 'ambient_declaration' };
29
+ return {
30
+ eligible: false,
31
+ reason: source.isDeclarationFile
32
+ ? 'declaration_only'
33
+ : 'overload_signature',
34
+ };
35
+ }
@@ -0,0 +1,306 @@
1
+ import ts from 'typescript';
2
+ import {
3
+ connectFactFromCall,
4
+ transactionReceiverName,
5
+ unwrapCall,
6
+ unwrapIdentityExpression,
7
+ type HelperBinding,
8
+ } from './service-binding-parser-helpers.js';
9
+ import {
10
+ createBindingLexicalIndex,
11
+ type BindingLexicalIndex,
12
+ type BindingSiteCandidate,
13
+ type VisibleBinding,
14
+ } from './011-binding-lexical-scope.js';
15
+ import { selectVisibleBinding } from './021-binding-visibility.js';
16
+
17
+ export type LocalBindingFact = Omit<
18
+ HelperBinding,
19
+ 'exportedName' | 'sourceFile' | 'sourceLine'
20
+ >;
21
+
22
+ export function directConnectFact(
23
+ expression: ts.Expression,
24
+ ): LocalBindingFact | undefined {
25
+ const call = unwrapCall(expression);
26
+ return call ? connectFactFromCall(call) : undefined;
27
+ }
28
+
29
+ export function bindingValueAtSite<T>(
30
+ variableName: string,
31
+ node: ts.Node,
32
+ source: ts.SourceFile,
33
+ value: T,
34
+ ): BindingSiteCandidate<T> {
35
+ return {
36
+ variableName,
37
+ bindingSiteStartOffset: node.getStart(source),
38
+ bindingSiteEndOffset: node.getEnd(),
39
+ value,
40
+ };
41
+ }
42
+
43
+ export function arrayBindingName(
44
+ element: ts.ArrayBindingElement | undefined,
45
+ ): string | undefined {
46
+ return element && ts.isBindingElement(element)
47
+ && !element.dotDotDotToken && ts.isIdentifier(element.name)
48
+ ? element.name.text
49
+ : undefined;
50
+ }
51
+
52
+ export function arrayAssignmentName(
53
+ element: ts.Expression | undefined,
54
+ ): string | undefined {
55
+ return element && !ts.isOmittedExpression(element)
56
+ && !ts.isSpreadElement(element) && ts.isIdentifier(element)
57
+ ? element.text
58
+ : undefined;
59
+ }
60
+
61
+ function deterministicLocalSource(
62
+ selected: VisibleBinding<LocalBindingFact>,
63
+ ): LocalBindingFact | undefined {
64
+ if (selected.status !== 'resolved') return undefined;
65
+ if (!selected.site?.deterministic) return undefined;
66
+ if (selected.declarationSite?.declarationKind === 'var') return undefined;
67
+ return selected.candidate?.value;
68
+ }
69
+
70
+ function aliasLocalFact(
71
+ sourceName: string,
72
+ targetName: string,
73
+ node: ts.Node,
74
+ index: BindingLexicalIndex,
75
+ bindings: readonly BindingSiteCandidate<LocalBindingFact>[],
76
+ transaction: boolean,
77
+ ): BindingSiteCandidate<LocalBindingFact> | undefined {
78
+ const source = deterministicLocalSource(
79
+ selectVisibleBinding(index, bindings, sourceName, node),
80
+ );
81
+ if (!source) return undefined;
82
+ const helperChain = [...(source.helperChain ?? []), {
83
+ aliasOf: sourceName,
84
+ callerVariable: targetName,
85
+ aliasKind: transaction ? 'transaction' : 'identity',
86
+ scopeRule: 'exact_lexical_scope',
87
+ ...(transaction ? { transactionAliasSource: sourceName } : {}),
88
+ }];
89
+ return bindingValueAtSite(
90
+ targetName, node, index.source, { ...source, helperChain },
91
+ );
92
+ }
93
+
94
+ function localAliasSource(initializer: ts.Expression): {
95
+ sourceName?: string;
96
+ transaction: boolean;
97
+ } {
98
+ const transaction = transactionReceiverName(initializer);
99
+ if (transaction) return { sourceName: transaction, transaction: true };
100
+ const unwrapped = unwrapIdentityExpression(initializer);
101
+ return {
102
+ sourceName: ts.isIdentifier(unwrapped) ? unwrapped.text : undefined,
103
+ transaction: false,
104
+ };
105
+ }
106
+
107
+ function localFactCandidate(
108
+ node: ts.Node,
109
+ source: ts.SourceFile,
110
+ index: BindingLexicalIndex,
111
+ bindings: readonly BindingSiteCandidate<LocalBindingFact>[],
112
+ ): BindingSiteCandidate<LocalBindingFact> | undefined {
113
+ if (!ts.isVariableDeclaration(node)
114
+ || !ts.isIdentifier(node.name) || !node.initializer) return undefined;
115
+ const fact = directConnectFact(node.initializer);
116
+ if (fact) return bindingValueAtSite(node.name.text, node, source, fact);
117
+ const alias = localAliasSource(node.initializer);
118
+ return alias.sourceName
119
+ ? aliasLocalFact(
120
+ alias.sourceName, node.name.text, node, index,
121
+ bindings, alias.transaction,
122
+ )
123
+ : undefined;
124
+ }
125
+
126
+ function collectLocalBindingFacts(
127
+ fn: ts.FunctionLikeDeclaration,
128
+ ): BindingSiteCandidate<LocalBindingFact>[] {
129
+ const source = fn.getSourceFile();
130
+ const index = createBindingLexicalIndex(source);
131
+ const bindings: BindingSiteCandidate<LocalBindingFact>[] = [];
132
+ function visit(node: ts.Node): void {
133
+ if (node !== fn && (ts.isFunctionDeclaration(node)
134
+ || ts.isArrowFunction(node) || ts.isFunctionExpression(node))) return;
135
+ const row = localFactCandidate(node, source, index, bindings);
136
+ if (row) bindings.push(row);
137
+ ts.forEachChild(node, visit);
138
+ }
139
+ visit(fn);
140
+ return bindings;
141
+ }
142
+
143
+ function returnedProperty(
144
+ property: ts.ObjectLiteralElementLike,
145
+ ): { propertyName?: string; variableName?: string } {
146
+ if (ts.isShorthandPropertyAssignment(property))
147
+ return {
148
+ propertyName: property.name.text,
149
+ variableName: property.name.text,
150
+ };
151
+ if (!ts.isPropertyAssignment(property)
152
+ || !ts.isIdentifier(property.initializer)) return {};
153
+ const propertyName = ts.isIdentifier(property.name)
154
+ || ts.isStringLiteralLike(property.name)
155
+ ? property.name.text
156
+ : undefined;
157
+ return { propertyName, variableName: property.initializer.text };
158
+ }
159
+
160
+ function hazardousTryClause(
161
+ node: ts.Node | undefined,
162
+ fn: ts.FunctionLikeDeclaration,
163
+ anyReturn: boolean,
164
+ ): boolean {
165
+ if (!node) return false;
166
+ let found = false;
167
+ const visit = (current: ts.Node): void => {
168
+ if (found || current !== fn && ts.isFunctionLike(current)) return;
169
+ if (ts.isReturnStatement(current)
170
+ && (anyReturn || current.expression !== undefined)
171
+ || ts.isCallExpression(current) && connectFactFromCall(current)) {
172
+ found = true;
173
+ return;
174
+ }
175
+ ts.forEachChild(current, visit);
176
+ };
177
+ visit(node);
178
+ return found;
179
+ }
180
+
181
+ function hasAncestor(node: ts.Node, ancestor: ts.Node): boolean {
182
+ let current: ts.Node | undefined = node.parent;
183
+ while (current) {
184
+ if (current === ancestor) return true;
185
+ current = current.parent;
186
+ }
187
+ return false;
188
+ }
189
+
190
+ function unsupportedTryAncestor(
191
+ statement: ts.TryStatement,
192
+ returned: ts.Node,
193
+ fn: ts.FunctionLikeDeclaration,
194
+ ): boolean {
195
+ if (!hasAncestor(returned, statement.tryBlock)) return true;
196
+ if (hazardousTryClause(statement.catchClause?.block, fn, false))
197
+ return true;
198
+ return hazardousTryClause(statement.finallyBlock, fn, true);
199
+ }
200
+
201
+ function branchDependent(
202
+ node: ts.Node,
203
+ fn: ts.FunctionLikeDeclaration,
204
+ ): boolean {
205
+ let current: ts.Node | undefined = node.parent;
206
+ while (current && current !== fn) {
207
+ if (ts.isIfStatement(current) || ts.isConditionalExpression(current)
208
+ || ts.isSwitchStatement(current)
209
+ || ts.isIterationStatement(current, false)) return true;
210
+ if (ts.isTryStatement(current)
211
+ && unsupportedTryAncestor(current, node, fn)) return true;
212
+ current = current.parent;
213
+ }
214
+ return false;
215
+ }
216
+
217
+ function singleReturn(
218
+ fn: ts.FunctionLikeDeclaration,
219
+ ): ts.ReturnStatement | undefined {
220
+ const returns: ts.ReturnStatement[] = [];
221
+ function visit(node: ts.Node): void {
222
+ if (node !== fn && (ts.isFunctionDeclaration(node)
223
+ || ts.isArrowFunction(node) || ts.isFunctionExpression(node))) return;
224
+ if (ts.isReturnStatement(node)) returns.push(node);
225
+ else ts.forEachChild(node, visit);
226
+ }
227
+ visit(fn);
228
+ const returned = returns.length === 1 ? returns[0] : undefined;
229
+ return returned && !branchDependent(returned, fn) ? returned : undefined;
230
+ }
231
+
232
+ export function collectReturnedObjectBindings(
233
+ fn: ts.FunctionLikeDeclaration,
234
+ ): Map<string, LocalBindingFact> {
235
+ const index = createBindingLexicalIndex(fn.getSourceFile());
236
+ const bindings = collectLocalBindingFacts(fn);
237
+ const out = new Map<string, LocalBindingFact>();
238
+ const returned = singleReturn(fn)?.expression;
239
+ if (!returned || !ts.isObjectLiteralExpression(returned)) return out;
240
+ for (const property of returned.properties) {
241
+ const names = returnedProperty(property);
242
+ if (!names.propertyName || !names.variableName) continue;
243
+ const selected = selectVisibleBinding(
244
+ index, bindings, names.variableName, property,
245
+ );
246
+ const fact = deterministicLocalSource(selected);
247
+ if (fact) out.set(names.propertyName, fact);
248
+ }
249
+ return out;
250
+ }
251
+
252
+ export function functionLikeInitializer(
253
+ expression: ts.Expression | undefined,
254
+ ): ts.FunctionLikeDeclaration | undefined {
255
+ return expression && (ts.isArrowFunction(expression)
256
+ || ts.isFunctionExpression(expression))
257
+ ? expression
258
+ : undefined;
259
+ }
260
+
261
+ function directReturnConnectFact(
262
+ fn: ts.FunctionLikeDeclaration,
263
+ ): LocalBindingFact | undefined {
264
+ const index = createBindingLexicalIndex(fn.getSourceFile());
265
+ const bindings = collectLocalBindingFacts(fn);
266
+ const returned = singleReturn(fn)?.expression;
267
+ if (!returned) return undefined;
268
+ if (ts.isIdentifier(returned)) {
269
+ const selected = selectVisibleBinding(
270
+ index, bindings, returned.text, returned,
271
+ );
272
+ return selected.site?.deterministic
273
+ && selected.declarationSite?.declarationKind !== 'var'
274
+ ? selected.candidate?.value
275
+ : undefined;
276
+ }
277
+ return directConnectFact(returned);
278
+ }
279
+
280
+ function hasTryAncestor(
281
+ returned: ts.ReturnStatement,
282
+ fn: ts.FunctionLikeDeclaration,
283
+ ): boolean {
284
+ let current: ts.Node | undefined = returned.parent;
285
+ while (current && current !== fn) {
286
+ if (ts.isTryStatement(current)) return true;
287
+ current = current.parent;
288
+ }
289
+ return false;
290
+ }
291
+
292
+ export function directConnectFactFromFunctionLike(
293
+ fn: ts.FunctionLikeDeclaration,
294
+ ): LocalBindingFact | undefined {
295
+ if (ts.isArrowFunction(fn) && fn.body && !ts.isBlock(fn.body))
296
+ return directConnectFact(fn.body);
297
+ const returned = singleReturn(fn);
298
+ const fact = directReturnConnectFact(fn);
299
+ if (!fact || !returned || !hasTryAncestor(returned, fn)) return fact;
300
+ return {
301
+ ...fact,
302
+ helperChain: [...(fact.helperChain ?? []), {
303
+ bindingOrigin: 'single_hop_helper_return',
304
+ }],
305
+ };
306
+ }