@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.
- package/CHANGELOG.md +20 -0
- package/README.md +27 -9
- package/TECHNICAL-NOTE.md +36 -0
- package/dist/chunk-3N3B5KHV.js +19596 -0
- package/dist/chunk-3N3B5KHV.js.map +1 -0
- package/dist/cli.js +2645 -521
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +67 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/001-index-summary.ts +22 -0
- package/src/cli/003-doctor-package-resolution.ts +68 -0
- package/src/cli/doctor.ts +25 -14
- package/src/cli.ts +151 -87
- package/src/db/000-call-fact-repository.ts +499 -340
- package/src/db/001-fact-lifecycle.ts +60 -30
- package/src/db/002-fact-json-inventory.ts +169 -0
- package/src/db/003-current-fact-semantics.ts +699 -0
- package/src/db/004-package-target-invalidation.ts +183 -0
- package/src/db/005-schema-structure.ts +201 -0
- package/src/db/006-relative-symbol-resolution.ts +464 -0
- package/src/db/007-package-fact-semantics.ts +573 -0
- package/src/db/008-relative-fact-semantics.ts +210 -0
- package/src/db/009-binding-fact-semantics.ts +352 -0
- package/src/db/010-package-symbol-surface-semantics.ts +320 -0
- package/src/db/011-symbol-call-semantics.ts +144 -0
- package/src/db/012-binding-reference-proof.ts +268 -0
- package/src/db/013-index-publication-failure.ts +91 -0
- package/src/db/014-binding-helper-provenance.ts +17 -0
- package/src/db/migrations.ts +16 -3
- package/src/db/repositories.ts +130 -6
- package/src/db/schema.ts +4 -2
- package/src/index.ts +12 -0
- package/src/indexer/cds-extension-resolver.ts +27 -3
- package/src/indexer/repository-indexer.ts +135 -13
- package/src/indexer/workspace-indexer.ts +237 -34
- package/src/linker/003-package-import-symbol-resolver.ts +363 -131
- package/src/linker/004-event-subscription-handler-linker.ts +34 -11
- package/src/linker/005-odata-path-structure.ts +371 -0
- package/src/linker/006-event-template-link.ts +72 -0
- package/src/linker/007-call-edge-insertion.ts +568 -0
- package/src/linker/cross-repo-linker.ts +4 -166
- package/src/linker/odata-path-normalizer.ts +273 -180
- package/src/linker/service-resolver.ts +197 -77
- package/src/parsers/000-direct-query-execution.ts +11 -0
- package/src/parsers/002-symbol-import-bindings.ts +516 -0
- package/src/parsers/003-package-public-surface.ts +661 -0
- package/src/parsers/004-fact-identity.ts +108 -0
- package/src/parsers/005-event-subscription-facts.ts +281 -0
- package/src/parsers/006-binding-identity.ts +348 -0
- package/src/parsers/007-source-fact-reconciliation.ts +105 -0
- package/src/parsers/008-package-surface-publication.ts +82 -0
- package/src/parsers/009-symbol-call-facts.ts +528 -0
- package/src/parsers/010-package-public-surface-analysis.ts +352 -0
- package/src/parsers/011-binding-lexical-scope.ts +583 -0
- package/src/parsers/012-package-fact-contract.ts +306 -0
- package/src/parsers/013-executable-body-eligibility.ts +35 -0
- package/src/parsers/014-service-binding-helper-flow.ts +306 -0
- package/src/parsers/015-service-binding-collector.ts +693 -0
- package/src/parsers/016-local-symbol-reference.ts +261 -0
- package/src/parsers/017-symbol-derived-contexts.ts +268 -0
- package/src/parsers/018-package-commonjs-syntax.ts +142 -0
- package/src/parsers/019-binding-assignment-targets.ts +76 -0
- package/src/parsers/020-stable-local-value.ts +217 -0
- package/src/parsers/021-binding-visibility.ts +168 -0
- package/src/parsers/022-outbound-expression-analysis.ts +700 -0
- package/src/parsers/023-outbound-call-classifier.ts +692 -0
- package/src/parsers/operation-path-analysis.ts +6 -1
- package/src/parsers/outbound-call-parser.ts +162 -512
- package/src/parsers/package-json-parser.ts +45 -3
- package/src/parsers/service-binding-parser-helpers.ts +86 -15
- package/src/parsers/service-binding-parser.ts +147 -597
- package/src/parsers/symbol-parser.ts +513 -352
- package/src/trace/002-trace-diagnostics.ts +36 -1
- package/src/trace/007-implementation-start-diagnostic.ts +1 -0
- package/src/trace/011-event-subscriber-traversal.ts +100 -8
- package/src/trace/013-trace-root-scopes.ts +2 -3
- package/src/trace/014-compact-contract.ts +6 -0
- package/src/trace/015-trace-edge-recorder.ts +61 -4
- package/src/trace/016-compact-projector.ts +15 -17
- package/src/trace/019-trace-edge-semantics.ts +6 -10
- package/src/trace/020-compact-field-projection.ts +122 -38
- package/src/trace/021-compact-decision-normalization.ts +171 -0
- package/src/trace/022-trace-fact-preflight.ts +21 -0
- package/src/trace/023-nested-event-scopes.ts +23 -0
- package/src/trace/024-compact-observation-decision.ts +81 -0
- package/src/trace/025-trace-implementation-scope.ts +123 -0
- package/src/trace/026-trace-start-scope.ts +336 -0
- package/src/trace/027-trace-scope-execution.ts +566 -0
- package/src/trace/028-trace-operation-execution.ts +336 -0
- package/src/trace/029-trace-start-implementation.ts +172 -0
- package/src/trace/030-event-runtime-resolution.ts +151 -0
- package/src/trace/031-local-call-expansion.ts +37 -0
- package/src/trace/implementation-hints.ts +9 -6
- package/src/trace/selectors.ts +1 -0
- package/src/trace/trace-engine.ts +122 -624
- package/src/types.ts +57 -0
- package/src/utils/001-placeholders.ts +188 -10
- package/src/version.ts +1 -1
- package/dist/chunk-ZQABU7MR.js +0 -12151
- 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
|
+
}
|