@saptools/service-flow 0.1.67 → 0.1.68
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 +10 -0
- package/README.md +20 -8
- package/TECHNICAL-NOTE.md +27 -0
- package/dist/{chunk-ZQABU7MR.js → chunk-AEM4JY22.js} +12676 -6714
- package/dist/chunk-AEM4JY22.js.map +1 -0
- package/dist/cli.js +2325 -413
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +61 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/003-doctor-package-resolution.ts +68 -0
- package/src/cli/doctor.ts +25 -14
- package/src/db/000-call-fact-repository.ts +475 -342
- 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 +698 -0
- package/src/db/004-package-target-invalidation.ts +173 -0
- package/src/db/005-schema-structure.ts +201 -0
- package/src/db/006-relative-symbol-resolution.ts +443 -0
- package/src/db/007-package-fact-semantics.ts +573 -0
- package/src/db/008-relative-fact-semantics.ts +207 -0
- package/src/db/009-binding-fact-semantics.ts +347 -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 +264 -0
- package/src/db/migrations.ts +16 -3
- package/src/db/repositories.ts +113 -6
- package/src/db/schema.ts +4 -2
- package/src/index.ts +12 -0
- package/src/indexer/repository-indexer.ts +72 -11
- package/src/indexer/workspace-indexer.ts +123 -32
- package/src/linker/003-package-import-symbol-resolver.ts +363 -131
- package/src/linker/004-event-subscription-handler-linker.ts +2 -0
- package/src/linker/005-odata-path-structure.ts +371 -0
- package/src/linker/cross-repo-linker.ts +2 -1
- package/src/linker/odata-path-normalizer.ts +273 -180
- package/src/linker/service-resolver.ts +197 -77
- 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 +343 -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 +240 -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 +152 -0
- package/src/parsers/operation-path-analysis.ts +6 -1
- package/src/parsers/outbound-call-parser.ts +19 -6
- 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 +482 -353
- package/src/trace/002-trace-diagnostics.ts +36 -1
- package/src/trace/016-compact-projector.ts +16 -17
- package/src/trace/020-compact-field-projection.ts +48 -37
- package/src/trace/021-compact-decision-normalization.ts +105 -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 +76 -0
- package/src/trace/025-trace-implementation-scope.ts +123 -0
- package/src/trace/026-trace-start-scope.ts +335 -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/trace-engine.ts +122 -624
- package/src/types.ts +56 -0
- package/src/utils/001-placeholders.ts +188 -10
- package/src/version.ts +1 -1
- package/dist/chunk-ZQABU7MR.js.map +0 -1
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import type {
|
|
3
|
+
ExecutableSymbolFact,
|
|
4
|
+
SymbolCallFact,
|
|
5
|
+
} from '../types.js';
|
|
6
|
+
import {
|
|
7
|
+
executableSymbolCandidates,
|
|
8
|
+
selectCallOwner,
|
|
9
|
+
} from './004-fact-identity.js';
|
|
10
|
+
import {
|
|
11
|
+
derivedMemberImportReference,
|
|
12
|
+
identifierMatchesDeclaration,
|
|
13
|
+
symbolImportReference,
|
|
14
|
+
type SymbolImportBinding,
|
|
15
|
+
type SymbolImportReference,
|
|
16
|
+
} from './002-symbol-import-bindings.js';
|
|
17
|
+
import {
|
|
18
|
+
localSymbolTarget,
|
|
19
|
+
type LocalSymbolTargetIdentity,
|
|
20
|
+
} from './016-local-symbol-reference.js';
|
|
21
|
+
|
|
22
|
+
export interface SymbolCallProxy {
|
|
23
|
+
importSource: string;
|
|
24
|
+
importBinding: SymbolImportReference;
|
|
25
|
+
factory: string;
|
|
26
|
+
variableName: string;
|
|
27
|
+
declarationStartOffset: number;
|
|
28
|
+
declarationEndOffset: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SymbolClassInstance {
|
|
32
|
+
className: string;
|
|
33
|
+
importSource?: string;
|
|
34
|
+
importBinding?: SymbolImportReference;
|
|
35
|
+
propertyName?: string;
|
|
36
|
+
declarationStartOffset: number;
|
|
37
|
+
declarationEndOffset: number;
|
|
38
|
+
containerStartOffset?: number;
|
|
39
|
+
containerEndOffset?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface CalleeName {
|
|
43
|
+
expression: string;
|
|
44
|
+
local?: string;
|
|
45
|
+
member?: string;
|
|
46
|
+
receiver?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface CallCollection {
|
|
50
|
+
source: ts.SourceFile;
|
|
51
|
+
sourceFile: string;
|
|
52
|
+
symbols: readonly ExecutableSymbolFact[];
|
|
53
|
+
imports: ReadonlyMap<string, string>;
|
|
54
|
+
importBindings: readonly SymbolImportBinding[];
|
|
55
|
+
proxies: ReadonlyMap<string, readonly SymbolCallProxy[]>;
|
|
56
|
+
instances: ReadonlyMap<string, readonly SymbolClassInstance[]>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface CallContext {
|
|
60
|
+
proxy?: SymbolCallProxy;
|
|
61
|
+
instance?: SymbolClassInstance;
|
|
62
|
+
reference?: SymbolImportReference;
|
|
63
|
+
localTarget?: LocalSymbolTargetIdentity;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const commonTerminalMembers = new Set([
|
|
67
|
+
'push', 'includes', 'find', 'findIndex', 'map', 'filter', 'reduce',
|
|
68
|
+
'forEach', 'some', 'every', 'toUpperCase', 'toLowerCase', 'trim',
|
|
69
|
+
'split', 'join', 'get', 'set', 'has',
|
|
70
|
+
]);
|
|
71
|
+
const loggerMembers = new Set([
|
|
72
|
+
'trace', 'debug', 'info', 'warn', 'error', 'fatal', 'log',
|
|
73
|
+
]);
|
|
74
|
+
const globalObjects = new Set([
|
|
75
|
+
'JSON', 'Object', 'Array', 'String', 'Number', 'Boolean', 'Math', 'Date',
|
|
76
|
+
'Promise', 'Reflect',
|
|
77
|
+
]);
|
|
78
|
+
const capDslRoots = new Set([
|
|
79
|
+
'SELECT', 'INSERT', 'UPSERT', 'UPDATE', 'DELETE',
|
|
80
|
+
]);
|
|
81
|
+
const requestHelpers = new Set([
|
|
82
|
+
'reject', 'error', 'info', 'warn', 'notify',
|
|
83
|
+
]);
|
|
84
|
+
const transportMembers = new Set([
|
|
85
|
+
'emit', 'publish', 'send', 'on',
|
|
86
|
+
]);
|
|
87
|
+
const cdsFrameworkPrefixes = [
|
|
88
|
+
'cds.connect.', 'cds.services.', 'cds.parse.',
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
export function symbolCallName(expr: ts.Expression): CalleeName {
|
|
92
|
+
if (ts.isIdentifier(expr)) return { expression: expr.text, local: expr.text };
|
|
93
|
+
if (!ts.isPropertyAccessExpression(expr))
|
|
94
|
+
return { expression: expr.getText() };
|
|
95
|
+
const left = expr.expression.getText();
|
|
96
|
+
return {
|
|
97
|
+
expression: expr.getText(),
|
|
98
|
+
local: left === 'this' ? undefined : left.split('.')[0],
|
|
99
|
+
member: expr.name.text,
|
|
100
|
+
receiver: left,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function hasSetMember(
|
|
105
|
+
value: string | undefined,
|
|
106
|
+
values: ReadonlySet<string>,
|
|
107
|
+
): boolean {
|
|
108
|
+
return value ? values.has(value) : false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function cdsFrameworkCall(callee: CalleeName): boolean {
|
|
112
|
+
return callee.expression === 'cds.run'
|
|
113
|
+
|| cdsFrameworkPrefixes.some((prefix) =>
|
|
114
|
+
callee.expression.startsWith(prefix));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function requestHelperCall(callee: CalleeName): boolean {
|
|
118
|
+
return callee.local === 'req'
|
|
119
|
+
&& hasSetMember(callee.member, requestHelpers);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function ignoredFrameworkCall(callee: CalleeName): boolean {
|
|
123
|
+
const checks = [
|
|
124
|
+
hasSetMember(callee.local, capDslRoots),
|
|
125
|
+
cdsFrameworkCall(callee),
|
|
126
|
+
requestHelperCall(callee),
|
|
127
|
+
hasSetMember(callee.member, transportMembers),
|
|
128
|
+
hasSetMember(callee.local, globalObjects),
|
|
129
|
+
callee.expression.startsWith('new Date().'),
|
|
130
|
+
];
|
|
131
|
+
return checks.some(Boolean);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function argumentEvidence(
|
|
135
|
+
args: ts.NodeArray<ts.Expression>,
|
|
136
|
+
): Array<Record<string, unknown>> {
|
|
137
|
+
return args.map((arg) => {
|
|
138
|
+
if (ts.isIdentifier(arg)) return { kind: 'identifier', name: arg.text };
|
|
139
|
+
if (ts.isArrayLiteralExpression(arg)) return {
|
|
140
|
+
kind: 'array_literal',
|
|
141
|
+
elements: arg.elements.flatMap((item, index) =>
|
|
142
|
+
ts.isIdentifier(item)
|
|
143
|
+
? [{ index, kind: 'identifier', name: item.text }]
|
|
144
|
+
: []),
|
|
145
|
+
};
|
|
146
|
+
if (ts.isObjectLiteralExpression(arg))
|
|
147
|
+
return objectArgumentEvidence(arg);
|
|
148
|
+
return { kind: 'unsupported', expression: arg.getText() };
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function objectArgumentEvidence(
|
|
153
|
+
argument: ts.ObjectLiteralExpression,
|
|
154
|
+
): Record<string, unknown> {
|
|
155
|
+
const properties = argument.properties.flatMap((property) => {
|
|
156
|
+
if (ts.isShorthandPropertyAssignment(property))
|
|
157
|
+
return [{
|
|
158
|
+
kind: 'shorthand',
|
|
159
|
+
property: property.name.text,
|
|
160
|
+
argument: property.name.text,
|
|
161
|
+
}];
|
|
162
|
+
if (!ts.isPropertyAssignment(property)
|
|
163
|
+
|| !ts.isIdentifier(property.initializer)) return [];
|
|
164
|
+
const name = propertyName(property.name);
|
|
165
|
+
return name ? [{
|
|
166
|
+
kind: 'property_assignment',
|
|
167
|
+
property: name,
|
|
168
|
+
argument: property.initializer.text,
|
|
169
|
+
}] : [];
|
|
170
|
+
});
|
|
171
|
+
return { kind: 'object_literal', properties };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function propertyName(name: ts.PropertyName): string | undefined {
|
|
175
|
+
return ts.isIdentifier(name) || ts.isStringLiteralLike(name)
|
|
176
|
+
|| ts.isNumericLiteral(name) ? name.text : undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function exactCaller(
|
|
180
|
+
collection: CallCollection,
|
|
181
|
+
node: ts.CallExpression,
|
|
182
|
+
): ExecutableSymbolFact | undefined {
|
|
183
|
+
const selected = selectCallOwner(
|
|
184
|
+
executableSymbolCandidates(collection.symbols, collection.sourceFile),
|
|
185
|
+
node.getStart(collection.source),
|
|
186
|
+
node.getEnd(),
|
|
187
|
+
);
|
|
188
|
+
if (selected.status === 'ambiguous')
|
|
189
|
+
throw new Error('invalid_prepared_repository_snapshot:symbol_call_owner_ambiguous');
|
|
190
|
+
return selected.owner;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function relation(
|
|
194
|
+
reference: SymbolImportReference | undefined,
|
|
195
|
+
instance: SymbolClassInstance | undefined,
|
|
196
|
+
proxy: SymbolCallProxy | undefined,
|
|
197
|
+
localTarget: LocalSymbolTargetIdentity | undefined,
|
|
198
|
+
provenThisMethod: boolean,
|
|
199
|
+
): string {
|
|
200
|
+
const derived = derivedRelation(instance, proxy);
|
|
201
|
+
if (derived) return derived;
|
|
202
|
+
if (reference?.moduleKind === 'package') return 'package_import';
|
|
203
|
+
if (reference?.referenceShape === 'namespace_member')
|
|
204
|
+
return 'relative_import_namespace_member';
|
|
205
|
+
if (reference) return 'relative_import';
|
|
206
|
+
if (localTarget) return 'indexed_local_symbol';
|
|
207
|
+
return provenThisMethod ? 'indexed_this_method' : 'indexed_local_symbol';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function derivedRelation(
|
|
211
|
+
instance: SymbolClassInstance | undefined,
|
|
212
|
+
proxy: SymbolCallProxy | undefined,
|
|
213
|
+
): string | undefined {
|
|
214
|
+
if (instance?.importBinding?.moduleKind === 'package')
|
|
215
|
+
return 'package_import_derived_member';
|
|
216
|
+
if (instance) return 'class_instance_method';
|
|
217
|
+
if (proxy?.importBinding.moduleKind === 'package')
|
|
218
|
+
return 'package_import_derived_member';
|
|
219
|
+
return proxy ? 'relative_import_proxy_member' : undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function targetName(
|
|
223
|
+
callee: CalleeName,
|
|
224
|
+
reference: SymbolImportReference | undefined,
|
|
225
|
+
instance: SymbolClassInstance | undefined,
|
|
226
|
+
proxy: SymbolCallProxy | undefined,
|
|
227
|
+
): string | undefined {
|
|
228
|
+
if (instance && callee.member)
|
|
229
|
+
return `${instance.className}.${callee.member}`;
|
|
230
|
+
if (proxy && callee.member) return callee.member;
|
|
231
|
+
if (callee.receiver === 'this') return callee.member;
|
|
232
|
+
if (reference) return reference.requestedPublicName;
|
|
233
|
+
return callee.member && callee.local
|
|
234
|
+
? `${callee.local}.${callee.member}`
|
|
235
|
+
: callee.local;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function callFact(
|
|
239
|
+
collection: CallCollection,
|
|
240
|
+
node: ts.CallExpression,
|
|
241
|
+
): SymbolCallFact | undefined {
|
|
242
|
+
const caller = exactCaller(collection, node);
|
|
243
|
+
if (!caller) return undefined;
|
|
244
|
+
const callee = symbolCallName(node.expression);
|
|
245
|
+
const proxy = callProxy(collection, node);
|
|
246
|
+
const instance = callInstance(collection, node, callee);
|
|
247
|
+
const reference = callImportReference(
|
|
248
|
+
collection, node.expression, callee, instance, proxy,
|
|
249
|
+
);
|
|
250
|
+
const localTarget = reference || instance || proxy
|
|
251
|
+
? undefined
|
|
252
|
+
: localSymbolTarget(node.expression, collection.source, collection.symbols);
|
|
253
|
+
return retainedCallFact(collection, node, caller, callee, {
|
|
254
|
+
proxy, instance, reference, localTarget,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function expressionRoot(
|
|
259
|
+
expression: ts.Expression,
|
|
260
|
+
): ts.Identifier | undefined {
|
|
261
|
+
let current = expression;
|
|
262
|
+
while (ts.isPropertyAccessExpression(current))
|
|
263
|
+
current = current.expression;
|
|
264
|
+
return ts.isIdentifier(current) ? current : undefined;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function exactDeclaredContext<T extends {
|
|
268
|
+
declarationStartOffset: number;
|
|
269
|
+
declarationEndOffset: number;
|
|
270
|
+
}>(
|
|
271
|
+
identifier: ts.Identifier,
|
|
272
|
+
candidates: readonly T[],
|
|
273
|
+
): T | undefined {
|
|
274
|
+
const matches = candidates.filter((candidate) =>
|
|
275
|
+
identifierMatchesDeclaration(
|
|
276
|
+
identifier,
|
|
277
|
+
candidate.declarationStartOffset,
|
|
278
|
+
candidate.declarationEndOffset,
|
|
279
|
+
));
|
|
280
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function callProxy(
|
|
284
|
+
collection: CallCollection,
|
|
285
|
+
node: ts.CallExpression,
|
|
286
|
+
): SymbolCallProxy | undefined {
|
|
287
|
+
const root = expressionRoot(node.expression);
|
|
288
|
+
if (!root) return undefined;
|
|
289
|
+
return exactDeclaredContext(
|
|
290
|
+
root, collection.proxies.get(root.text) ?? [],
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function callInstance(
|
|
295
|
+
collection: CallCollection,
|
|
296
|
+
node: ts.CallExpression,
|
|
297
|
+
callee: CalleeName,
|
|
298
|
+
): SymbolClassInstance | undefined {
|
|
299
|
+
const root = expressionRoot(node.expression);
|
|
300
|
+
if (root && root.text !== 'this') {
|
|
301
|
+
const exact = exactDeclaredContext(
|
|
302
|
+
root, collection.instances.get(root.text) ?? [],
|
|
303
|
+
);
|
|
304
|
+
if (exact) return exact;
|
|
305
|
+
}
|
|
306
|
+
return callee.receiver
|
|
307
|
+
? thisPropertyInstance(collection, node, callee.receiver)
|
|
308
|
+
: undefined;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function enclosingClass(node: ts.Node): ts.ClassLikeDeclaration | undefined {
|
|
312
|
+
let current: ts.Node | undefined = node.parent;
|
|
313
|
+
while (current && !ts.isSourceFile(current)) {
|
|
314
|
+
if (ts.isClassLike(current)) return current;
|
|
315
|
+
current = current.parent;
|
|
316
|
+
}
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function thisPropertyInstance(
|
|
321
|
+
collection: CallCollection,
|
|
322
|
+
node: ts.CallExpression,
|
|
323
|
+
receiver: string,
|
|
324
|
+
): SymbolClassInstance | undefined {
|
|
325
|
+
if (!receiver.startsWith('this.')) return undefined;
|
|
326
|
+
const container = enclosingClass(node);
|
|
327
|
+
if (!container) return undefined;
|
|
328
|
+
const start = container.getStart(collection.source);
|
|
329
|
+
const end = container.getEnd();
|
|
330
|
+
const matches = (collection.instances.get(receiver) ?? []).filter(
|
|
331
|
+
(candidate) => candidate.containerStartOffset === start
|
|
332
|
+
&& candidate.containerEndOffset === end,
|
|
333
|
+
);
|
|
334
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function callImportReference(
|
|
338
|
+
collection: CallCollection,
|
|
339
|
+
expression: ts.Expression,
|
|
340
|
+
callee: CalleeName,
|
|
341
|
+
instance: SymbolClassInstance | undefined,
|
|
342
|
+
proxy: SymbolCallProxy | undefined,
|
|
343
|
+
): SymbolImportReference | undefined {
|
|
344
|
+
const direct = symbolImportReference(expression, collection.importBindings);
|
|
345
|
+
if (direct || !callee.member) return direct;
|
|
346
|
+
const inherited = instance?.importBinding ?? proxy?.importBinding;
|
|
347
|
+
return inherited
|
|
348
|
+
? derivedMemberImportReference(inherited, callee.member)
|
|
349
|
+
: undefined;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function retainedCallFact(
|
|
353
|
+
collection: CallCollection,
|
|
354
|
+
node: ts.CallExpression,
|
|
355
|
+
caller: ExecutableSymbolFact,
|
|
356
|
+
callee: CalleeName,
|
|
357
|
+
context: CallContext,
|
|
358
|
+
): SymbolCallFact | undefined {
|
|
359
|
+
const target = targetName(
|
|
360
|
+
callee, context.reference, context.instance, context.proxy,
|
|
361
|
+
);
|
|
362
|
+
const className = caller.qualifiedName.includes('.')
|
|
363
|
+
? caller.qualifiedName.split('.')[0] : undefined;
|
|
364
|
+
const thisTarget = callee.receiver === 'this' && className && callee.member
|
|
365
|
+
? `${className}.${callee.member}` : undefined;
|
|
366
|
+
const resolvedTarget = thisTarget ?? target;
|
|
367
|
+
if (!resolvedTarget
|
|
368
|
+
|| !shouldRetainCall(
|
|
369
|
+
collection, callee, resolvedTarget, context, Boolean(thisTarget),
|
|
370
|
+
))
|
|
371
|
+
return undefined;
|
|
372
|
+
return createCallFact(
|
|
373
|
+
collection, node, caller, callee, resolvedTarget, thisTarget, context,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function shouldRetainCall(
|
|
378
|
+
collection: CallCollection,
|
|
379
|
+
callee: CalleeName,
|
|
380
|
+
target: string | undefined,
|
|
381
|
+
context: CallContext,
|
|
382
|
+
provenThisMethod: boolean,
|
|
383
|
+
): boolean {
|
|
384
|
+
if (!target || ignoredCall(callee)) return false;
|
|
385
|
+
const callables = new Set(collection.symbols.flatMap((symbol) =>
|
|
386
|
+
[symbol.localName, symbol.qualifiedName]));
|
|
387
|
+
return provenThisMethod
|
|
388
|
+
|| Boolean(context.localTarget)
|
|
389
|
+
|| callables.has(target) && Boolean(context.instance)
|
|
390
|
+
|| hasImportedContext(context);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function ignoredCall(callee: CalleeName): boolean {
|
|
394
|
+
return loggerCall(callee) || terminalCall(callee)
|
|
395
|
+
|| ignoredFrameworkCall(callee);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function loggerCall(callee: CalleeName): boolean {
|
|
399
|
+
if (callee.local === 'logger') return true;
|
|
400
|
+
if (callee.receiver?.endsWith('.logger')) return true;
|
|
401
|
+
return callee.expression.startsWith('this.logger.')
|
|
402
|
+
&& hasSetMember(callee.member, loggerMembers);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function terminalCall(callee: CalleeName): boolean {
|
|
406
|
+
return hasSetMember(callee.member, commonTerminalMembers)
|
|
407
|
+
|| hasSetMember(callee.member, loggerMembers);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function hasImportedContext(context: CallContext): boolean {
|
|
411
|
+
return Boolean(context.reference ?? context.proxy ?? context.instance);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function createCallFact(
|
|
415
|
+
collection: CallCollection,
|
|
416
|
+
node: ts.CallExpression,
|
|
417
|
+
caller: ExecutableSymbolFact,
|
|
418
|
+
callee: CalleeName,
|
|
419
|
+
target: string,
|
|
420
|
+
thisTarget: string | undefined,
|
|
421
|
+
context: CallContext,
|
|
422
|
+
): SymbolCallFact {
|
|
423
|
+
const importSource = context.instance?.importSource
|
|
424
|
+
?? context.proxy?.importSource
|
|
425
|
+
?? context.reference?.rawModuleSpecifier;
|
|
426
|
+
return {
|
|
427
|
+
callerQualifiedName: caller.qualifiedName,
|
|
428
|
+
calleeExpression: callee.expression,
|
|
429
|
+
calleeLocalName: target,
|
|
430
|
+
receiverLocalName: callee.member ? callee.local ?? callee.receiver : undefined,
|
|
431
|
+
importSource,
|
|
432
|
+
sourceFile: collection.sourceFile,
|
|
433
|
+
sourceLine: collection.source.getLineAndCharacterOfPosition(
|
|
434
|
+
node.getStart(collection.source),
|
|
435
|
+
).line + 1,
|
|
436
|
+
callSiteStartOffset: node.getStart(collection.source),
|
|
437
|
+
callSiteEndOffset: node.getEnd(),
|
|
438
|
+
callRole: 'ordinary_call',
|
|
439
|
+
evidence: callEvidence(caller, callee, target, thisTarget, context, node),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function callEvidence(
|
|
444
|
+
caller: ExecutableSymbolFact,
|
|
445
|
+
callee: CalleeName,
|
|
446
|
+
target: string,
|
|
447
|
+
thisTarget: string | undefined,
|
|
448
|
+
context: CallContext,
|
|
449
|
+
node: ts.CallExpression,
|
|
450
|
+
): Record<string, unknown> {
|
|
451
|
+
return {
|
|
452
|
+
relation: relation(
|
|
453
|
+
context.reference, context.instance, context.proxy,
|
|
454
|
+
context.localTarget, Boolean(thisTarget),
|
|
455
|
+
),
|
|
456
|
+
caller: caller.qualifiedName,
|
|
457
|
+
targetName: target,
|
|
458
|
+
...importReferenceEvidence(context),
|
|
459
|
+
...instanceEvidence(context.instance, callee),
|
|
460
|
+
callArguments: argumentEvidence(node.arguments),
|
|
461
|
+
...proxyEvidence(context.proxy),
|
|
462
|
+
...(context.localTarget
|
|
463
|
+
? { localTargetIdentity: context.localTarget }
|
|
464
|
+
: {}),
|
|
465
|
+
candidateStrategy: parserCandidateStrategy(context),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function importReferenceEvidence(
|
|
470
|
+
context: CallContext,
|
|
471
|
+
): Record<string, unknown> {
|
|
472
|
+
if (!context.reference) return {};
|
|
473
|
+
const derived = context.instance ?? context.proxy;
|
|
474
|
+
return derived && context.reference.moduleKind === 'package'
|
|
475
|
+
? { derivedImportBinding: context.reference }
|
|
476
|
+
: { importBinding: context.reference };
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function instanceEvidence(
|
|
480
|
+
instance: SymbolClassInstance | undefined,
|
|
481
|
+
callee: CalleeName,
|
|
482
|
+
): Record<string, unknown> {
|
|
483
|
+
if (!instance) return {};
|
|
484
|
+
return {
|
|
485
|
+
instanceVariable: instance.propertyName ?? callee.local,
|
|
486
|
+
className: instance.className,
|
|
487
|
+
methodName: callee.member,
|
|
488
|
+
classImportSource: instance.importSource,
|
|
489
|
+
classImportBinding: instance.importBinding,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function proxyEvidence(
|
|
494
|
+
proxy: SymbolCallProxy | undefined,
|
|
495
|
+
): Record<string, unknown> {
|
|
496
|
+
if (!proxy) return {};
|
|
497
|
+
return {
|
|
498
|
+
proxyVariableName: proxy.variableName,
|
|
499
|
+
factory: proxy.factory,
|
|
500
|
+
factoryExpression: proxy.factory,
|
|
501
|
+
factoryImportSource: proxy.importSource,
|
|
502
|
+
factoryImportBinding: proxy.importBinding,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function parserCandidateStrategy(context: CallContext): string | undefined {
|
|
507
|
+
if (context.instance?.importSource)
|
|
508
|
+
return 'relative_import_class_instance_method';
|
|
509
|
+
if (context.instance) return 'same_file_class_instance_method';
|
|
510
|
+
return context.proxy
|
|
511
|
+
? 'proxy_member_exact_export_or_unique_member'
|
|
512
|
+
: undefined;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export function collectSymbolCallFacts(
|
|
516
|
+
collection: CallCollection,
|
|
517
|
+
): SymbolCallFact[] {
|
|
518
|
+
const calls: SymbolCallFact[] = [];
|
|
519
|
+
const visit = (node: ts.Node): void => {
|
|
520
|
+
if (ts.isCallExpression(node)) {
|
|
521
|
+
const call = callFact(collection, node);
|
|
522
|
+
if (call) calls.push(call);
|
|
523
|
+
}
|
|
524
|
+
ts.forEachChild(node, visit);
|
|
525
|
+
};
|
|
526
|
+
visit(collection.source);
|
|
527
|
+
return calls;
|
|
528
|
+
}
|