@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,76 @@
1
+ import ts from 'typescript';
2
+
3
+ export interface BindingAssignmentEntry {
4
+ variableName: string;
5
+ arrayIndex?: number;
6
+ unsupported?: boolean;
7
+ }
8
+
9
+ function unsupported(
10
+ entries: readonly BindingAssignmentEntry[],
11
+ ): BindingAssignmentEntry[] {
12
+ return entries.map((entry) => ({
13
+ ...entry,
14
+ arrayIndex: undefined,
15
+ unsupported: true,
16
+ }));
17
+ }
18
+
19
+ function objectEntries(
20
+ property: ts.ObjectLiteralElementLike,
21
+ ): BindingAssignmentEntry[] {
22
+ if (ts.isShorthandPropertyAssignment(property))
23
+ return [{ variableName: property.name.text }];
24
+ if (ts.isSpreadAssignment(property))
25
+ return unsupported(bindingAssignmentEntries(property.expression));
26
+ if (!ts.isPropertyAssignment(property)) return [];
27
+ if (ts.isIdentifier(property.initializer))
28
+ return [{ variableName: property.initializer.text }];
29
+ return unsupported(bindingAssignmentEntries(property.initializer));
30
+ }
31
+
32
+ function arrayEntries(
33
+ element: ts.Expression | ts.OmittedExpression,
34
+ arrayIndex: number,
35
+ ): BindingAssignmentEntry[] {
36
+ if (ts.isOmittedExpression(element)) return [];
37
+ if (ts.isIdentifier(element))
38
+ return [{ variableName: element.text, arrayIndex }];
39
+ return unsupported(bindingAssignmentEntries(element));
40
+ }
41
+
42
+ function assignmentTarget(
43
+ expression: ts.Expression,
44
+ ): { expression: ts.Expression; unsupported: boolean } {
45
+ if (ts.isParenthesizedExpression(expression))
46
+ return assignmentTarget(expression.expression);
47
+ if (ts.isAsExpression(expression)
48
+ || ts.isSatisfiesExpression(expression)
49
+ || ts.isTypeAssertionExpression(expression)
50
+ || ts.isNonNullExpression(expression)) {
51
+ const target = assignmentTarget(expression.expression);
52
+ return { expression: target.expression, unsupported: true };
53
+ }
54
+ if (ts.isBinaryExpression(expression)
55
+ && expression.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
56
+ const target = assignmentTarget(expression.left);
57
+ return { expression: target.expression, unsupported: true };
58
+ }
59
+ return { expression, unsupported: false };
60
+ }
61
+
62
+ export function bindingAssignmentEntries(
63
+ expression: ts.Expression,
64
+ ): BindingAssignmentEntry[] {
65
+ const target = assignmentTarget(expression);
66
+ let entries: BindingAssignmentEntry[] = [];
67
+ if (ts.isIdentifier(target.expression))
68
+ entries = [{ variableName: target.expression.text }];
69
+ if (ts.isObjectLiteralExpression(target.expression))
70
+ entries = target.expression.properties.flatMap(objectEntries);
71
+ if (ts.isArrayLiteralExpression(target.expression))
72
+ entries = target.expression.elements.flatMap(arrayEntries);
73
+ if (ts.isSpreadElement(target.expression))
74
+ entries = unsupported(bindingAssignmentEntries(target.expression.expression));
75
+ return target.unsupported ? unsupported(entries) : entries;
76
+ }
@@ -0,0 +1,217 @@
1
+ import ts from 'typescript';
2
+ import {
3
+ identifierMatchesDeclaration,
4
+ } from './002-symbol-import-bindings.js';
5
+ import {
6
+ isUnshadowedCommonJsExportExpression,
7
+ } from './018-package-commonjs-syntax.js';
8
+
9
+ function transparentUse(node: ts.Node): ts.Node {
10
+ const parent = node.parent;
11
+ if (parent && (ts.isParenthesizedExpression(parent)
12
+ || ts.isAsExpression(parent)
13
+ || ts.isSatisfiesExpression(parent)
14
+ || ts.isTypeAssertionExpression(parent)
15
+ || ts.isNonNullExpression(parent)))
16
+ return transparentUse(parent);
17
+ return node;
18
+ }
19
+
20
+ function assignmentOperator(kind: ts.SyntaxKind): boolean {
21
+ return kind >= ts.SyntaxKind.FirstAssignment
22
+ && kind <= ts.SyntaxKind.LastAssignment;
23
+ }
24
+
25
+ function directWriteTarget(node: ts.Node): boolean {
26
+ const use = transparentUse(node);
27
+ const parent = use.parent;
28
+ if (!parent) return false;
29
+ if (ts.isBinaryExpression(parent))
30
+ return parent.left === use && assignmentOperator(parent.operatorToken.kind);
31
+ if (ts.isDeleteExpression(parent))
32
+ return parent.expression === use;
33
+ if (ts.isPrefixUnaryExpression(parent)
34
+ || ts.isPostfixUnaryExpression(parent))
35
+ return parent.operand === use;
36
+ return (ts.isForInStatement(parent) || ts.isForOfStatement(parent))
37
+ && parent.initializer === use;
38
+ }
39
+
40
+ type MemberContainer = ts.ObjectLiteralExpression | ts.ClassLikeDeclaration;
41
+ type StableMember = ts.ObjectLiteralElementLike | ts.ClassElement;
42
+
43
+ function memberContainer(
44
+ declaration: ts.Identifier,
45
+ ): MemberContainer | undefined {
46
+ const parent = declaration.parent;
47
+ if (ts.isClassDeclaration(parent)) return parent;
48
+ if (!ts.isVariableDeclaration(parent) || parent.name !== declaration
49
+ || !parent.initializer) return undefined;
50
+ return ts.isObjectLiteralExpression(parent.initializer)
51
+ || ts.isClassExpression(parent.initializer)
52
+ ? parent.initializer
53
+ : undefined;
54
+ }
55
+
56
+ function declaredMemberName(node: ts.Node): string | undefined {
57
+ if (ts.isIdentifier(node) || ts.isPrivateIdentifier(node))
58
+ return node.text;
59
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)
60
+ || ts.isNumericLiteral(node)) return node.text;
61
+ return undefined;
62
+ }
63
+
64
+ function accessedMemberName(node: ts.Node): string | undefined {
65
+ if (ts.isPropertyAccessExpression(node)) return node.name.text;
66
+ if (!ts.isElementAccessExpression(node)) return undefined;
67
+ const argument = node.argumentExpression;
68
+ return argument && (ts.isStringLiteral(argument)
69
+ || ts.isNoSubstitutionTemplateLiteral(argument))
70
+ ? argument.text
71
+ : undefined;
72
+ }
73
+
74
+ function memberName(node: ts.Node): string | undefined {
75
+ return declaredMemberName(node) ?? accessedMemberName(node);
76
+ }
77
+
78
+ function matchingMember(
79
+ value: MemberContainer,
80
+ name: string,
81
+ ): StableMember | undefined {
82
+ const members = ts.isObjectLiteralExpression(value)
83
+ ? value.properties
84
+ : value.members.filter((member) =>
85
+ ts.canHaveModifiers(member)
86
+ && ts.getModifiers(member)?.some((modifier) =>
87
+ modifier.kind === ts.SyntaxKind.StaticKeyword));
88
+ const matches = members.filter((member) =>
89
+ 'name' in member && member.name && memberName(member.name) === name);
90
+ if (matches.length === 1) return matches[0];
91
+ const implemented = matches.filter((member) => callableMember(member)?.body);
92
+ return implemented.length === 1 ? implemented[0] : undefined;
93
+ }
94
+
95
+ function callableMember(
96
+ member: StableMember,
97
+ ): ts.FunctionLikeDeclaration | undefined {
98
+ if (ts.isMethodDeclaration(member)) return member;
99
+ if (ts.isPropertyAssignment(member)
100
+ && (ts.isArrowFunction(member.initializer)
101
+ || ts.isFunctionExpression(member.initializer)))
102
+ return member.initializer;
103
+ if (ts.isPropertyDeclaration(member) && member.initializer
104
+ && (ts.isArrowFunction(member.initializer)
105
+ || ts.isFunctionExpression(member.initializer)))
106
+ return member.initializer;
107
+ return undefined;
108
+ }
109
+
110
+ function containsThis(node: ts.Node): boolean {
111
+ let found = false;
112
+ const visit = (child: ts.Node): void => {
113
+ if (found) return;
114
+ if (child.kind === ts.SyntaxKind.ThisKeyword) {
115
+ found = true;
116
+ return;
117
+ }
118
+ ts.forEachChild(child, visit);
119
+ };
120
+ visit(node);
121
+ return found;
122
+ }
123
+
124
+ function invokedMember(node: ts.Node): boolean {
125
+ const use = transparentUse(node);
126
+ const parent = use.parent;
127
+ return Boolean(parent && ts.isCallExpression(parent)
128
+ && parent.expression === use);
129
+ }
130
+
131
+ type MemberAccess = ts.PropertyAccessExpression | ts.ElementAccessExpression;
132
+
133
+ function receiverAccess(
134
+ node: ts.Node,
135
+ ): MemberAccess | undefined {
136
+ const parent = node.parent;
137
+ if (!parent || (!ts.isPropertyAccessExpression(parent)
138
+ && !ts.isElementAccessExpression(parent))
139
+ || parent.expression !== node || directWriteTarget(parent)) return undefined;
140
+ return parent;
141
+ }
142
+
143
+ function stableMemberUse(
144
+ access: MemberAccess,
145
+ member: StableMember,
146
+ ): boolean {
147
+ if (!invokedMember(access)) return !ts.isGetAccessor(member)
148
+ && !ts.isSetAccessor(member);
149
+ const callable = callableMember(member);
150
+ return Boolean(callable && !containsThis(callable));
151
+ }
152
+
153
+ function memberReceiverUse(
154
+ node: ts.Node,
155
+ declaration: ts.Identifier,
156
+ ): boolean {
157
+ const access = receiverAccess(node);
158
+ if (!access) return false;
159
+ const name = memberName(access);
160
+ const value = memberContainer(declaration);
161
+ const member = name && value ? matchingMember(value, name) : undefined;
162
+ return member ? stableMemberUse(access, member) : false;
163
+ }
164
+
165
+ function commonJsExportUse(node: ts.Node): boolean {
166
+ const parent = node.parent;
167
+ return Boolean(parent && ts.isBinaryExpression(parent)
168
+ && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken
169
+ && parent.right === node
170
+ && isUnshadowedCommonJsExportExpression(parent.left)
171
+ && ts.isExpressionStatement(parent.parent)
172
+ && ts.isSourceFile(parent.parent.parent));
173
+ }
174
+
175
+ function esmExportUse(node: ts.Node): boolean {
176
+ const parent = node.parent;
177
+ return Boolean(parent && (ts.isExportSpecifier(parent)
178
+ || (ts.isExportAssignment(parent) && parent.expression === node)));
179
+ }
180
+
181
+ function inertUnaryUse(node: ts.Node): boolean {
182
+ const parent = node.parent;
183
+ return Boolean(parent && ((ts.isVoidExpression(parent)
184
+ || ts.isTypeOfExpression(parent)) && parent.expression === node));
185
+ }
186
+
187
+ function safeReferenceUse(
188
+ node: ts.Identifier,
189
+ declaration: ts.Identifier,
190
+ ): boolean {
191
+ const use = transparentUse(node);
192
+ return memberReceiverUse(use, declaration)
193
+ || commonJsExportUse(use)
194
+ || esmExportUse(use)
195
+ || inertUnaryUse(use);
196
+ }
197
+
198
+ export function stableLocalValueReference(
199
+ source: ts.SourceFile,
200
+ declaration: ts.Identifier,
201
+ ): boolean {
202
+ const start = declaration.getStart(source);
203
+ const end = declaration.getEnd();
204
+ let stable = true;
205
+ const visit = (node: ts.Node): void => {
206
+ if (!stable) return;
207
+ if (ts.isIdentifier(node) && node !== declaration
208
+ && identifierMatchesDeclaration(node, start, end)
209
+ && !safeReferenceUse(node, declaration)) {
210
+ stable = false;
211
+ return;
212
+ }
213
+ ts.forEachChild(node, visit);
214
+ };
215
+ visit(source);
216
+ return stable;
217
+ }
@@ -0,0 +1,168 @@
1
+ import ts from 'typescript';
2
+ import type { LexicalScopeFact } from '../types.js';
3
+ import {
4
+ declarationAt,
5
+ enclosingCaseClause,
6
+ executionScope,
7
+ lexicalScopeChain,
8
+ sameScope,
9
+ type BindingLexicalIndex,
10
+ type BindingLexicalSite,
11
+ type BindingSiteCandidate,
12
+ type VisibleBinding,
13
+ } from './011-binding-lexical-scope.js';
14
+
15
+ function reachingSites(
16
+ index: BindingLexicalIndex,
17
+ variableName: string,
18
+ useStart: number,
19
+ useChain: readonly LexicalScopeFact[],
20
+ declaration: BindingLexicalSite,
21
+ ): BindingLexicalSite[] {
22
+ const useExecution = executionScope(useChain);
23
+ return index.sites.filter((site) =>
24
+ site.variableName === variableName
25
+ && site.declarationKey === declaration.declarationKey
26
+ && site.startOffset < useStart
27
+ && (site.flow === 'declaration'
28
+ || sameScope(site.executionScope, useExecution)))
29
+ .sort((left, right) =>
30
+ right.startOffset - left.startOffset
31
+ || right.endOffset - left.endOffset);
32
+ }
33
+
34
+ function enclosingLoops(node: ts.Node): ts.IterationStatement[] {
35
+ const loops: ts.IterationStatement[] = [];
36
+ let current: ts.Node | undefined = node.parent;
37
+ while (current && !ts.isSourceFile(current)
38
+ && !ts.isFunctionLike(current)) {
39
+ if (ts.isIterationStatement(current, false)) loops.push(current);
40
+ current = current.parent;
41
+ }
42
+ return loops;
43
+ }
44
+
45
+ function unsupportedAssignmentFlow(
46
+ index: BindingLexicalIndex,
47
+ variableName: string,
48
+ useNode: ts.Node,
49
+ useStart: number,
50
+ useChain: readonly LexicalScopeFact[],
51
+ declaration: BindingLexicalSite,
52
+ ): boolean {
53
+ const useExecution = executionScope(useChain);
54
+ const loops = enclosingLoops(useNode);
55
+ return index.sites.some((site) => {
56
+ if (site.flow !== 'assignment'
57
+ || site.variableName !== variableName
58
+ || site.declarationKey !== declaration.declarationKey) return false;
59
+ if (!sameScope(site.executionScope, useExecution)) return true;
60
+ return site.startOffset > useStart && loops.some((loop) =>
61
+ site.node.pos >= loop.pos && site.node.end <= loop.end);
62
+ });
63
+ }
64
+
65
+ function candidatesAtSite<T>(
66
+ candidates: readonly BindingSiteCandidate<T>[],
67
+ site: BindingLexicalSite,
68
+ ): BindingSiteCandidate<T>[] {
69
+ return candidates.filter((candidate) =>
70
+ candidate.variableName === site.variableName
71
+ && candidate.bindingSiteStartOffset === site.startOffset
72
+ && candidate.bindingSiteEndOffset === site.endOffset);
73
+ }
74
+
75
+ function helperCallDeclaration(site: BindingLexicalSite): boolean {
76
+ if (!ts.isVariableDeclaration(site.node) || !site.node.initializer)
77
+ return false;
78
+ let expression = site.node.initializer;
79
+ while (ts.isAwaitExpression(expression)
80
+ || ts.isParenthesizedExpression(expression)
81
+ || ts.isAsExpression(expression)
82
+ || ts.isSatisfiesExpression(expression)
83
+ || ts.isTypeAssertionExpression(expression))
84
+ expression = expression.expression;
85
+ return ts.isCallExpression(expression)
86
+ && ts.isIdentifier(expression.expression);
87
+ }
88
+
89
+ function compatibleCaseClause(
90
+ site: BindingLexicalSite,
91
+ useNode: ts.Node,
92
+ source: ts.SourceFile,
93
+ ): boolean {
94
+ if (site.caseClauseStartOffset === undefined
95
+ || site.caseClauseEndOffset === undefined) return true;
96
+ const clause = enclosingCaseClause(useNode);
97
+ return clause?.getStart(source) === site.caseClauseStartOffset
98
+ && clause.getEnd() === site.caseClauseEndOffset;
99
+ }
100
+
101
+ type VisibleBindingReason = NonNullable<VisibleBinding<unknown>['reason']>;
102
+
103
+ function declarationFailure(
104
+ declaration: BindingLexicalSite,
105
+ after: boolean,
106
+ useNode: ts.Node,
107
+ source: ts.SourceFile,
108
+ ): VisibleBindingReason | undefined {
109
+ if (declaration.flow === 'shadow') return 'binding_flow_unsupported';
110
+ if (declaration.declarationKind === 'var') return 'unsupported_var_binding';
111
+ if (!compatibleCaseClause(declaration, useNode, source))
112
+ return 'binding_flow_unsupported';
113
+ return after ? 'binding_declared_after_call' : undefined;
114
+ }
115
+
116
+ function matchedBinding<T>(
117
+ candidates: readonly BindingSiteCandidate<T>[],
118
+ site: BindingLexicalSite,
119
+ ): VisibleBinding<T> | undefined {
120
+ const matches = candidatesAtSite(candidates, site);
121
+ if (matches.length === 0) return {
122
+ status: 'unresolved',
123
+ reason: site.flow === 'assignment'
124
+ ? 'unsupported_reaching_assignment'
125
+ : helperCallDeclaration(site)
126
+ ? 'binding_flow_unsupported'
127
+ : 'binding_not_found',
128
+ };
129
+ if (matches.length > 1) return { status: 'ambiguous' };
130
+ return matches[0] ? { status: 'resolved', candidate: matches[0] } : undefined;
131
+ }
132
+
133
+ export function selectVisibleBinding<T>(
134
+ index: BindingLexicalIndex,
135
+ candidates: readonly BindingSiteCandidate<T>[],
136
+ variableName: string,
137
+ useNode: ts.Node,
138
+ ): VisibleBinding<T> {
139
+ const useStart = useNode.getStart(index.source);
140
+ const useChain = lexicalScopeChain(useNode, index.source);
141
+ const declaration = declarationAt(index.sites, variableName, useStart, useChain);
142
+ if (declaration.ambiguous) return { status: 'ambiguous' };
143
+ if (!declaration.site) return { status: 'unresolved', reason: 'binding_not_found' };
144
+ const failure = declarationFailure(
145
+ declaration.site, declaration.after, useNode, index.source,
146
+ );
147
+ if (failure) return { status: 'unresolved', reason: failure };
148
+ if (unsupportedAssignmentFlow(
149
+ index, variableName, useNode, useStart, useChain, declaration.site,
150
+ )) return {
151
+ status: 'unresolved',
152
+ reason: 'unsupported_reaching_assignment',
153
+ };
154
+ const site = reachingSites(
155
+ index, variableName, useStart, useChain, declaration.site,
156
+ )[0];
157
+ if (!site) return { status: 'unresolved', reason: 'binding_not_found' };
158
+ const matched = matchedBinding(candidates, site);
159
+ if (!matched || matched.status !== 'resolved') return matched ?? {
160
+ status: 'unresolved', reason: 'binding_not_found',
161
+ };
162
+ return {
163
+ ...matched,
164
+ site,
165
+ declarationSite: declaration.site,
166
+ scopeIndex: declaration.scopeIndex,
167
+ };
168
+ }