@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,583 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import type { LexicalScopeFact } from '../types.js';
|
|
3
|
+
import {
|
|
4
|
+
bindingAssignmentEntries,
|
|
5
|
+
type BindingAssignmentEntry,
|
|
6
|
+
} from './019-binding-assignment-targets.js';
|
|
7
|
+
|
|
8
|
+
export type BindingDeclarationKind =
|
|
9
|
+
| 'const'
|
|
10
|
+
| 'let'
|
|
11
|
+
| 'var'
|
|
12
|
+
| 'parameter'
|
|
13
|
+
| 'catch'
|
|
14
|
+
| 'value_shadow';
|
|
15
|
+
|
|
16
|
+
export interface BindingLexicalSite {
|
|
17
|
+
variableName: string;
|
|
18
|
+
node: ts.Node;
|
|
19
|
+
startOffset: number;
|
|
20
|
+
endOffset: number;
|
|
21
|
+
flow: 'declaration' | 'assignment' | 'shadow';
|
|
22
|
+
declarationKind?: BindingDeclarationKind;
|
|
23
|
+
aliasSource?: string;
|
|
24
|
+
aliasKind?: 'identity'
|
|
25
|
+
| 'identity-assignment'
|
|
26
|
+
| 'transaction'
|
|
27
|
+
| 'array-destructuring';
|
|
28
|
+
aliasArrayIndex?: number;
|
|
29
|
+
aliasPromiseAll?: boolean;
|
|
30
|
+
deterministic: boolean;
|
|
31
|
+
scopeChain: LexicalScopeFact[];
|
|
32
|
+
executionScope: LexicalScopeFact;
|
|
33
|
+
caseClauseStartOffset?: number;
|
|
34
|
+
caseClauseEndOffset?: number;
|
|
35
|
+
declarationKey?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface BindingSiteCandidate<T> {
|
|
39
|
+
variableName: string;
|
|
40
|
+
bindingSiteStartOffset?: number;
|
|
41
|
+
bindingSiteEndOffset?: number;
|
|
42
|
+
value: T;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BindingLexicalIndex {
|
|
46
|
+
source: ts.SourceFile;
|
|
47
|
+
sites: BindingLexicalSite[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface VisibleBinding<T> {
|
|
51
|
+
status: 'resolved' | 'ambiguous' | 'unresolved';
|
|
52
|
+
reason?: 'binding_not_found'
|
|
53
|
+
| 'binding_declared_after_call'
|
|
54
|
+
| 'unsupported_reaching_assignment'
|
|
55
|
+
| 'unsupported_var_binding'
|
|
56
|
+
| 'binding_flow_unsupported';
|
|
57
|
+
candidate?: BindingSiteCandidate<T>;
|
|
58
|
+
site?: BindingLexicalSite;
|
|
59
|
+
declarationSite?: BindingLexicalSite;
|
|
60
|
+
scopeIndex?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function unwrapIdentity(expression: ts.Expression): ts.Expression {
|
|
64
|
+
if (ts.isParenthesizedExpression(expression)
|
|
65
|
+
|| ts.isAwaitExpression(expression)
|
|
66
|
+
|| ts.isAsExpression(expression)
|
|
67
|
+
|| ts.isSatisfiesExpression(expression)
|
|
68
|
+
|| ts.isTypeAssertionExpression(expression))
|
|
69
|
+
return unwrapIdentity(expression.expression);
|
|
70
|
+
return expression;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function transactionSource(expression: ts.Expression | undefined): string | undefined {
|
|
74
|
+
if (!expression) return undefined;
|
|
75
|
+
const unwrapped = unwrapIdentity(expression);
|
|
76
|
+
if (ts.isConditionalExpression(unwrapped)) {
|
|
77
|
+
const left = transactionSource(unwrapped.whenTrue);
|
|
78
|
+
const right = transactionSource(unwrapped.whenFalse);
|
|
79
|
+
return left && left === right ? left : undefined;
|
|
80
|
+
}
|
|
81
|
+
if (!ts.isCallExpression(unwrapped)
|
|
82
|
+
|| !ts.isPropertyAccessExpression(unwrapped.expression)) return undefined;
|
|
83
|
+
const receiver = unwrapped.expression;
|
|
84
|
+
return ['tx', 'transaction'].includes(receiver.name.text)
|
|
85
|
+
&& ts.isIdentifier(receiver.expression)
|
|
86
|
+
? receiver.expression.text
|
|
87
|
+
: undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function directAlias(expression: ts.Expression | undefined): string | undefined {
|
|
91
|
+
if (!expression) return undefined;
|
|
92
|
+
const unwrapped = unwrapIdentity(expression);
|
|
93
|
+
return ts.isIdentifier(unwrapped)
|
|
94
|
+
? unwrapped.text
|
|
95
|
+
: transactionSource(unwrapped);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function scopeKind(node: ts.Node): LexicalScopeFact['kind'] | undefined {
|
|
99
|
+
if (ts.isSourceFile(node)) return 'source_file';
|
|
100
|
+
if (ts.isModuleBlock(node)) return 'module_block';
|
|
101
|
+
if (ts.isFunctionLike(node)) return 'function';
|
|
102
|
+
if (ts.isClassLike(node)) return 'class';
|
|
103
|
+
if (ts.isIterationStatement(node, false)) return 'loop';
|
|
104
|
+
if (ts.isCaseBlock(node)) return 'case_block';
|
|
105
|
+
if (ts.isBlock(node)) return 'block';
|
|
106
|
+
return ts.isCatchClause(node) ? 'catch' : undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function lexicalScope(node: ts.Node, source: ts.SourceFile): LexicalScopeFact | undefined {
|
|
110
|
+
const kind = scopeKind(node);
|
|
111
|
+
if (!kind) return undefined;
|
|
112
|
+
return {
|
|
113
|
+
kind,
|
|
114
|
+
startOffset: ts.isSourceFile(node) ? 0 : node.getStart(source),
|
|
115
|
+
endOffset: node.getEnd(),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function lexicalScopeChain(
|
|
120
|
+
node: ts.Node,
|
|
121
|
+
source: ts.SourceFile,
|
|
122
|
+
): LexicalScopeFact[] {
|
|
123
|
+
const scopes: LexicalScopeFact[] = [];
|
|
124
|
+
let current: ts.Node | undefined = node;
|
|
125
|
+
while (current) {
|
|
126
|
+
const scope = lexicalScope(current, source);
|
|
127
|
+
if (scope) scopes.push(scope);
|
|
128
|
+
current = current.parent;
|
|
129
|
+
}
|
|
130
|
+
return scopes.reverse();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function sameScope(
|
|
134
|
+
left: LexicalScopeFact,
|
|
135
|
+
right: LexicalScopeFact,
|
|
136
|
+
): boolean {
|
|
137
|
+
return left.kind === right.kind
|
|
138
|
+
&& left.startOffset === right.startOffset
|
|
139
|
+
&& left.endOffset === right.endOffset;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function executionScope(
|
|
143
|
+
chain: readonly LexicalScopeFact[],
|
|
144
|
+
): LexicalScopeFact {
|
|
145
|
+
return [...chain].reverse().find((scope) =>
|
|
146
|
+
scope.kind === 'function' || scope.kind === 'source_file')
|
|
147
|
+
?? { kind: 'source_file', startOffset: 0, endOffset: 0 };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function declarationKind(node: ts.VariableDeclaration): BindingDeclarationKind {
|
|
151
|
+
const flags = node.parent.flags;
|
|
152
|
+
if ((flags & ts.NodeFlags.Const) !== 0) return 'const';
|
|
153
|
+
return (flags & ts.NodeFlags.Let) !== 0 ? 'let' : 'var';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const shortCircuitOperators = new Set<ts.SyntaxKind>([
|
|
157
|
+
ts.SyntaxKind.AmpersandAmpersandToken,
|
|
158
|
+
ts.SyntaxKind.BarBarToken,
|
|
159
|
+
ts.SyntaxKind.QuestionQuestionToken,
|
|
160
|
+
ts.SyntaxKind.AmpersandAmpersandEqualsToken,
|
|
161
|
+
ts.SyntaxKind.BarBarEqualsToken,
|
|
162
|
+
ts.SyntaxKind.QuestionQuestionEqualsToken,
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
function shortCircuitRightBranch(
|
|
166
|
+
descendant: ts.Node,
|
|
167
|
+
node: ts.Node,
|
|
168
|
+
): boolean {
|
|
169
|
+
return ts.isBinaryExpression(node)
|
|
170
|
+
&& shortCircuitOperators.has(node.operatorToken.kind)
|
|
171
|
+
&& descendant.pos >= node.right.pos
|
|
172
|
+
&& descendant.end <= node.right.end;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function optionalChainAncestor(node: ts.Node): boolean {
|
|
176
|
+
return (ts.isCallExpression(node)
|
|
177
|
+
|| ts.isPropertyAccessExpression(node)
|
|
178
|
+
|| ts.isElementAccessExpression(node))
|
|
179
|
+
&& Boolean(node.questionDotToken);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function deferredClassInitializer(node: ts.Node): boolean {
|
|
183
|
+
return ts.isPropertyDeclaration(node)
|
|
184
|
+
&& (ts.getCombinedModifierFlags(node)
|
|
185
|
+
& ts.ModifierFlags.Static) === 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function controlFlowAncestor(node: ts.Node): boolean {
|
|
189
|
+
return ts.isIfStatement(node)
|
|
190
|
+
|| ts.isConditionalExpression(node)
|
|
191
|
+
|| ts.isSwitchStatement(node)
|
|
192
|
+
|| ts.isIterationStatement(node, false)
|
|
193
|
+
|| ts.isTryStatement(node)
|
|
194
|
+
|| deferredClassInitializer(node);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function branchDependent(node: ts.Node): boolean {
|
|
198
|
+
let descendant = node;
|
|
199
|
+
let current: ts.Node | undefined = node.parent;
|
|
200
|
+
while (current && !ts.isSourceFile(current) && !ts.isFunctionLike(current)) {
|
|
201
|
+
if (controlFlowAncestor(current)
|
|
202
|
+
|| shortCircuitRightBranch(descendant, current)
|
|
203
|
+
|| optionalChainAncestor(current)) return true;
|
|
204
|
+
descendant = current;
|
|
205
|
+
current = current.parent;
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function bindingEntries(name: ts.BindingName): Array<{
|
|
211
|
+
variableName: string;
|
|
212
|
+
element?: ts.BindingElement;
|
|
213
|
+
arrayIndex?: number;
|
|
214
|
+
}> {
|
|
215
|
+
if (ts.isIdentifier(name)) return [{ variableName: name.text }];
|
|
216
|
+
return name.elements.flatMap((element, arrayIndex) =>
|
|
217
|
+
ts.isBindingElement(element)
|
|
218
|
+
? bindingEntries(element.name).map((entry) => ({
|
|
219
|
+
...entry,
|
|
220
|
+
element,
|
|
221
|
+
...(ts.isArrayBindingPattern(name) ? { arrayIndex } : {}),
|
|
222
|
+
}))
|
|
223
|
+
: []);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function arrayContainer(
|
|
227
|
+
expression: ts.Expression | undefined,
|
|
228
|
+
): { array: ts.ArrayLiteralExpression; promiseAll: boolean } | undefined {
|
|
229
|
+
if (!expression) return undefined;
|
|
230
|
+
const unwrapped = unwrapIdentity(expression);
|
|
231
|
+
if (ts.isArrayLiteralExpression(unwrapped))
|
|
232
|
+
return { array: unwrapped, promiseAll: false };
|
|
233
|
+
if (!ts.isCallExpression(unwrapped)
|
|
234
|
+
|| !ts.isPropertyAccessExpression(unwrapped.expression)
|
|
235
|
+
|| unwrapped.expression.name.text !== 'all'
|
|
236
|
+
|| unwrapped.expression.expression.getText() !== 'Promise') return undefined;
|
|
237
|
+
const first = unwrapped.arguments[0];
|
|
238
|
+
if (!first) return undefined;
|
|
239
|
+
const container = unwrapIdentity(first);
|
|
240
|
+
return ts.isArrayLiteralExpression(container)
|
|
241
|
+
? { array: container, promiseAll: true }
|
|
242
|
+
: undefined;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function entryAlias(
|
|
246
|
+
node: ts.VariableDeclaration,
|
|
247
|
+
entry: {
|
|
248
|
+
variableName: string;
|
|
249
|
+
element?: ts.BindingElement;
|
|
250
|
+
arrayIndex?: number;
|
|
251
|
+
},
|
|
252
|
+
): string | undefined {
|
|
253
|
+
if (!entry.element) return directAlias(node.initializer);
|
|
254
|
+
if (!ts.isArrayBindingPattern(node.name)) return undefined;
|
|
255
|
+
const expression = entry.arrayIndex === undefined
|
|
256
|
+
? undefined
|
|
257
|
+
: arrayContainer(node.initializer)?.array.elements[entry.arrayIndex];
|
|
258
|
+
return expression && !ts.isOmittedExpression(expression)
|
|
259
|
+
? directAlias(expression)
|
|
260
|
+
: undefined;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function siteBase(
|
|
264
|
+
source: ts.SourceFile,
|
|
265
|
+
node: ts.Node,
|
|
266
|
+
): Pick<BindingLexicalSite, 'node' | 'startOffset' | 'endOffset'
|
|
267
|
+
| 'scopeChain' | 'executionScope'
|
|
268
|
+
| 'caseClauseStartOffset' | 'caseClauseEndOffset'> {
|
|
269
|
+
const scopeChain = lexicalScopeChain(node, source);
|
|
270
|
+
const clause = enclosingCaseClause(node);
|
|
271
|
+
return {
|
|
272
|
+
node,
|
|
273
|
+
startOffset: node.getStart(source),
|
|
274
|
+
endOffset: node.getEnd(),
|
|
275
|
+
scopeChain,
|
|
276
|
+
executionScope: executionScope(scopeChain),
|
|
277
|
+
caseClauseStartOffset: clause?.getStart(source),
|
|
278
|
+
caseClauseEndOffset: clause?.getEnd(),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function enclosingCaseClause(
|
|
283
|
+
node: ts.Node,
|
|
284
|
+
): ts.CaseClause | ts.DefaultClause | undefined {
|
|
285
|
+
let current: ts.Node | undefined = node.parent;
|
|
286
|
+
while (current && !ts.isSourceFile(current)
|
|
287
|
+
&& !ts.isFunctionLike(current)) {
|
|
288
|
+
if (ts.isCaseClause(current) || ts.isDefaultClause(current))
|
|
289
|
+
return current;
|
|
290
|
+
current = current.parent;
|
|
291
|
+
}
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function hoistedVarSiteBase(
|
|
296
|
+
source: ts.SourceFile,
|
|
297
|
+
node: ts.Node,
|
|
298
|
+
): ReturnType<typeof siteBase> {
|
|
299
|
+
const full = lexicalScopeChain(node, source);
|
|
300
|
+
const scope = executionScope(full);
|
|
301
|
+
const scopeIndex = full.findIndex((item) => sameScope(item, scope));
|
|
302
|
+
return {
|
|
303
|
+
node,
|
|
304
|
+
startOffset: node.getStart(source),
|
|
305
|
+
endOffset: node.getEnd(),
|
|
306
|
+
scopeChain: full.slice(0, scopeIndex + 1),
|
|
307
|
+
executionScope: scope,
|
|
308
|
+
caseClauseStartOffset: undefined,
|
|
309
|
+
caseClauseEndOffset: undefined,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function declarationSites(
|
|
314
|
+
source: ts.SourceFile,
|
|
315
|
+
node: ts.VariableDeclaration,
|
|
316
|
+
): BindingLexicalSite[] {
|
|
317
|
+
const kind = declarationKind(node);
|
|
318
|
+
const base = kind === 'var'
|
|
319
|
+
? hoistedVarSiteBase(source, node)
|
|
320
|
+
: siteBase(source, node);
|
|
321
|
+
return bindingEntries(node.name).map((entry) => {
|
|
322
|
+
const aliasSource = entryAlias(node, entry);
|
|
323
|
+
const transaction = !entry.element
|
|
324
|
+
&& transactionSource(node.initializer) === aliasSource;
|
|
325
|
+
return {
|
|
326
|
+
...base,
|
|
327
|
+
variableName: entry.variableName,
|
|
328
|
+
flow: 'declaration',
|
|
329
|
+
declarationKind: kind,
|
|
330
|
+
aliasSource,
|
|
331
|
+
aliasKind: aliasSource
|
|
332
|
+
? entry.arrayIndex === undefined
|
|
333
|
+
? transaction ? 'transaction' : 'identity'
|
|
334
|
+
: 'array-destructuring'
|
|
335
|
+
: undefined,
|
|
336
|
+
aliasArrayIndex: entry.arrayIndex,
|
|
337
|
+
aliasPromiseAll: entry.arrayIndex === undefined
|
|
338
|
+
? undefined
|
|
339
|
+
: arrayContainer(node.initializer)?.promiseAll,
|
|
340
|
+
deterministic: declarationKind(node) !== 'var',
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function assignmentAlias(
|
|
346
|
+
node: ts.BinaryExpression,
|
|
347
|
+
entry: BindingAssignmentEntry,
|
|
348
|
+
): string | undefined {
|
|
349
|
+
if (entry.unsupported) return undefined;
|
|
350
|
+
if (entry.arrayIndex === undefined) return directAlias(node.right);
|
|
351
|
+
const expression = arrayContainer(node.right)?.array.elements[entry.arrayIndex];
|
|
352
|
+
return expression && !ts.isOmittedExpression(expression)
|
|
353
|
+
? directAlias(expression)
|
|
354
|
+
: undefined;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function assignmentSites(
|
|
358
|
+
source: ts.SourceFile,
|
|
359
|
+
node: ts.BinaryExpression,
|
|
360
|
+
): BindingLexicalSite[] {
|
|
361
|
+
const base = siteBase(source, node);
|
|
362
|
+
return bindingAssignmentEntries(node.left).map((entry) => {
|
|
363
|
+
const aliasSource = assignmentAlias(node, entry);
|
|
364
|
+
const transaction = entry.arrayIndex === undefined
|
|
365
|
+
&& transactionSource(node.right) === aliasSource;
|
|
366
|
+
return {
|
|
367
|
+
...base,
|
|
368
|
+
variableName: entry.variableName,
|
|
369
|
+
flow: 'assignment',
|
|
370
|
+
aliasSource,
|
|
371
|
+
aliasKind: aliasSource
|
|
372
|
+
? entry.arrayIndex === undefined
|
|
373
|
+
? transaction ? 'transaction' : 'identity-assignment'
|
|
374
|
+
: 'array-destructuring'
|
|
375
|
+
: undefined,
|
|
376
|
+
aliasArrayIndex: entry.arrayIndex,
|
|
377
|
+
aliasPromiseAll: entry.arrayIndex === undefined
|
|
378
|
+
? undefined
|
|
379
|
+
: arrayContainer(node.right)?.promiseAll,
|
|
380
|
+
deterministic: !entry.unsupported
|
|
381
|
+
&& !shortCircuitOperators.has(node.operatorToken.kind)
|
|
382
|
+
&& !branchDependent(node),
|
|
383
|
+
};
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function isAssignmentOperator(kind: ts.SyntaxKind): boolean {
|
|
388
|
+
return kind >= ts.SyntaxKind.FirstAssignment
|
|
389
|
+
&& kind <= ts.SyntaxKind.LastAssignment;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function isMutationUnary(
|
|
393
|
+
node: ts.PrefixUnaryExpression | ts.PostfixUnaryExpression,
|
|
394
|
+
): boolean {
|
|
395
|
+
return node.operator === ts.SyntaxKind.PlusPlusToken
|
|
396
|
+
|| node.operator === ts.SyntaxKind.MinusMinusToken;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function parameterSites(
|
|
400
|
+
source: ts.SourceFile,
|
|
401
|
+
node: ts.ParameterDeclaration,
|
|
402
|
+
): BindingLexicalSite[] {
|
|
403
|
+
const base = siteBase(source, node.parent);
|
|
404
|
+
return bindingEntries(node.name).map(({ variableName }) => ({
|
|
405
|
+
...base,
|
|
406
|
+
node,
|
|
407
|
+
startOffset: node.getStart(source),
|
|
408
|
+
endOffset: node.getEnd(),
|
|
409
|
+
variableName,
|
|
410
|
+
flow: 'declaration',
|
|
411
|
+
declarationKind: 'parameter',
|
|
412
|
+
deterministic: true,
|
|
413
|
+
}));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function shadowSite(
|
|
417
|
+
source: ts.SourceFile,
|
|
418
|
+
name: ts.Identifier,
|
|
419
|
+
scopeNode: ts.Node,
|
|
420
|
+
): BindingLexicalSite {
|
|
421
|
+
const base = siteBase(source, scopeNode);
|
|
422
|
+
return {
|
|
423
|
+
...base,
|
|
424
|
+
node: name,
|
|
425
|
+
startOffset: name.getStart(source),
|
|
426
|
+
endOffset: name.getEnd(),
|
|
427
|
+
variableName: name.text,
|
|
428
|
+
flow: 'shadow',
|
|
429
|
+
declarationKind: 'value_shadow',
|
|
430
|
+
deterministic: false,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function declarationShadowName(node: ts.Node): ts.Identifier | undefined {
|
|
435
|
+
if (ts.isFunctionDeclaration(node)) return node.name;
|
|
436
|
+
if (ts.isClassDeclaration(node)) return node.name;
|
|
437
|
+
return ts.isEnumDeclaration(node) ? node.name : undefined;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function expressionShadowName(node: ts.Node): ts.Identifier | undefined {
|
|
441
|
+
if (ts.isFunctionExpression(node)) return node.name;
|
|
442
|
+
return ts.isClassExpression(node) ? node.name : undefined;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function valueShadowSites(
|
|
446
|
+
source: ts.SourceFile,
|
|
447
|
+
node: ts.Node,
|
|
448
|
+
): BindingLexicalSite[] {
|
|
449
|
+
const declaration = declarationShadowName(node);
|
|
450
|
+
if (declaration)
|
|
451
|
+
return [shadowSite(source, declaration, node.parent)];
|
|
452
|
+
if (ts.isModuleDeclaration(node) && ts.isIdentifier(node.name))
|
|
453
|
+
return [shadowSite(source, node.name, node.parent)];
|
|
454
|
+
if (ts.isImportEqualsDeclaration(node))
|
|
455
|
+
return [shadowSite(source, node.name, node.parent)];
|
|
456
|
+
const expression = expressionShadowName(node);
|
|
457
|
+
if (expression) return [shadowSite(source, expression, node)];
|
|
458
|
+
return [];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function mutationSite(
|
|
462
|
+
source: ts.SourceFile,
|
|
463
|
+
expression: ts.Expression,
|
|
464
|
+
): BindingLexicalSite[] {
|
|
465
|
+
if (!ts.isIdentifier(expression)) return [];
|
|
466
|
+
return [{
|
|
467
|
+
...siteBase(source, expression),
|
|
468
|
+
variableName: expression.text,
|
|
469
|
+
flow: 'assignment',
|
|
470
|
+
deterministic: false,
|
|
471
|
+
}];
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function loopWriteSites(
|
|
475
|
+
source: ts.SourceFile,
|
|
476
|
+
node: ts.ForInOrOfStatement,
|
|
477
|
+
): BindingLexicalSite[] {
|
|
478
|
+
return ts.isVariableDeclarationList(node.initializer)
|
|
479
|
+
? []
|
|
480
|
+
: bindingAssignmentEntries(node.initializer).map((entry) => ({
|
|
481
|
+
...siteBase(source, node.initializer),
|
|
482
|
+
variableName: entry.variableName,
|
|
483
|
+
flow: 'assignment',
|
|
484
|
+
deterministic: false,
|
|
485
|
+
}));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function collectSites(source: ts.SourceFile): BindingLexicalSite[] {
|
|
489
|
+
const sites: BindingLexicalSite[] = [];
|
|
490
|
+
const visit = (node: ts.Node): void => {
|
|
491
|
+
sites.push(...valueShadowSites(source, node));
|
|
492
|
+
if (ts.isVariableDeclaration(node))
|
|
493
|
+
sites.push(...declarationSites(source, node));
|
|
494
|
+
if (ts.isParameter(node))
|
|
495
|
+
sites.push(...parameterSites(source, node));
|
|
496
|
+
if (ts.isBinaryExpression(node)
|
|
497
|
+
&& isAssignmentOperator(node.operatorToken.kind))
|
|
498
|
+
sites.push(...assignmentSites(source, node));
|
|
499
|
+
if (ts.isForInStatement(node) || ts.isForOfStatement(node))
|
|
500
|
+
sites.push(...loopWriteSites(source, node));
|
|
501
|
+
if ((ts.isPrefixUnaryExpression(node)
|
|
502
|
+
|| ts.isPostfixUnaryExpression(node)) && isMutationUnary(node))
|
|
503
|
+
sites.push(...mutationSite(source, node.operand));
|
|
504
|
+
ts.forEachChild(node, visit);
|
|
505
|
+
};
|
|
506
|
+
visit(source);
|
|
507
|
+
return sites.sort((left, right) =>
|
|
508
|
+
left.startOffset - right.startOffset || left.endOffset - right.endOffset);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function declarationKey(site: BindingLexicalSite): string {
|
|
512
|
+
return [
|
|
513
|
+
site.variableName,
|
|
514
|
+
site.startOffset,
|
|
515
|
+
site.endOffset,
|
|
516
|
+
site.declarationKind,
|
|
517
|
+
].join('\u0000');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export function declarationAt(
|
|
521
|
+
sites: readonly BindingLexicalSite[],
|
|
522
|
+
variableName: string,
|
|
523
|
+
useStart: number,
|
|
524
|
+
useChain: readonly LexicalScopeFact[],
|
|
525
|
+
): { site?: BindingLexicalSite; scopeIndex?: number; after: boolean; ambiguous: boolean } {
|
|
526
|
+
const declarations = sites.flatMap((site) => {
|
|
527
|
+
if (!['declaration', 'shadow'].includes(site.flow)
|
|
528
|
+
|| site.variableName !== variableName) return [];
|
|
529
|
+
const scope = site.scopeChain.at(-1);
|
|
530
|
+
const scopeIndex = scope
|
|
531
|
+
? useChain.findIndex((candidate) => sameScope(candidate, scope))
|
|
532
|
+
: -1;
|
|
533
|
+
return scopeIndex >= 0 ? [{ site, scopeIndex }] : [];
|
|
534
|
+
});
|
|
535
|
+
const deepest = Math.max(-1, ...declarations.map((item) => item.scopeIndex));
|
|
536
|
+
const nearest = declarations.filter((item) => item.scopeIndex === deepest);
|
|
537
|
+
if (nearest.length === 0) return { after: false, ambiguous: false };
|
|
538
|
+
if (nearest.length > 1)
|
|
539
|
+
return { after: false, ambiguous: true, scopeIndex: deepest };
|
|
540
|
+
const site = nearest[0]?.site;
|
|
541
|
+
return {
|
|
542
|
+
site,
|
|
543
|
+
scopeIndex: deepest,
|
|
544
|
+
after: Boolean(site && site.declarationKind !== 'var'
|
|
545
|
+
&& site.startOffset >= useStart),
|
|
546
|
+
ambiguous: false,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function attachDeclarationKeys(sites: BindingLexicalSite[]): void {
|
|
551
|
+
for (const site of sites) {
|
|
552
|
+
if (site.flow === 'declaration' || site.flow === 'shadow') {
|
|
553
|
+
site.declarationKey = declarationKey(site);
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
const selected = declarationAt(
|
|
557
|
+
sites, site.variableName, site.startOffset, site.scopeChain,
|
|
558
|
+
);
|
|
559
|
+
if (selected.site && !selected.after && !selected.ambiguous)
|
|
560
|
+
site.declarationKey = declarationKey(selected.site);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export function createBindingLexicalIndex(
|
|
565
|
+
source: ts.SourceFile,
|
|
566
|
+
): BindingLexicalIndex {
|
|
567
|
+
const sites = collectSites(source);
|
|
568
|
+
attachDeclarationKeys(sites);
|
|
569
|
+
return { source, sites };
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export function bindingSite(
|
|
573
|
+
index: BindingLexicalIndex,
|
|
574
|
+
variableName: string,
|
|
575
|
+
startOffset: number,
|
|
576
|
+
endOffset: number,
|
|
577
|
+
): BindingLexicalSite | undefined {
|
|
578
|
+
const matches = index.sites.filter((site) =>
|
|
579
|
+
site.variableName === variableName
|
|
580
|
+
&& site.startOffset === startOffset
|
|
581
|
+
&& site.endOffset === endOffset);
|
|
582
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
583
|
+
}
|