@saptools/service-flow 0.1.66 → 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 +14 -0
- package/README.md +20 -8
- package/TECHNICAL-NOTE.md +27 -0
- package/dist/{chunk-TOVX4WYH.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-TOVX4WYH.js.map +0 -1
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PACKAGE_PUBLIC_SURFACE_RECORD_CAP,
|
|
3
|
+
PACKAGE_PUBLIC_SURFACE_SCHEMA,
|
|
4
|
+
} from './003-package-public-surface.js';
|
|
5
|
+
import type {
|
|
6
|
+
ExecutableBodyEligibility,
|
|
7
|
+
PackagePublicEntry,
|
|
8
|
+
PackagePublicScope,
|
|
9
|
+
PackagePublicSurfaceFact,
|
|
10
|
+
PublicSurfaceTarget,
|
|
11
|
+
} from './003-package-public-surface.js';
|
|
12
|
+
import {
|
|
13
|
+
packageModuleRequest,
|
|
14
|
+
type SymbolImportBindingKind,
|
|
15
|
+
type SymbolImportReference,
|
|
16
|
+
type SymbolImportReferenceShape,
|
|
17
|
+
} from './002-symbol-import-bindings.js';
|
|
18
|
+
|
|
19
|
+
const bindingKinds = new Set<SymbolImportBindingKind>([
|
|
20
|
+
'esm_named', 'esm_default', 'esm_namespace',
|
|
21
|
+
'cjs_destructured', 'cjs_namespace',
|
|
22
|
+
]);
|
|
23
|
+
const referenceShapes = new Set<SymbolImportReferenceShape>([
|
|
24
|
+
'identifier', 'namespace_member', 'static_member', 'default_member',
|
|
25
|
+
]);
|
|
26
|
+
const bodyReasons = new Set([
|
|
27
|
+
'body_present', 'declaration_only', 'ambient_declaration',
|
|
28
|
+
'abstract_bodyless', 'overload_signature',
|
|
29
|
+
]);
|
|
30
|
+
const surfaceReasons = new Set([
|
|
31
|
+
'anonymous_default_export_without_symbol_identity',
|
|
32
|
+
'unsupported_namespace_reexport',
|
|
33
|
+
'unsupported_export_declaration',
|
|
34
|
+
'unsupported_export_assignment',
|
|
35
|
+
'unsupported_commonjs_export_shape',
|
|
36
|
+
'unsupported_mutable_export_binding',
|
|
37
|
+
'unsupported_external_reexport',
|
|
38
|
+
'unsupported_exports_map_shape',
|
|
39
|
+
'unsupported_package_entrypoint_shape',
|
|
40
|
+
'public_surface_entry_target_not_indexed',
|
|
41
|
+
'public_surface_main_module_conflict',
|
|
42
|
+
'public_surface_module_resolution_ambiguous',
|
|
43
|
+
'public_surface_module_not_indexed',
|
|
44
|
+
'public_surface_reexport_cycle',
|
|
45
|
+
'public_surface_reexport_depth_exceeded',
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
function record(value: unknown): Record<string, unknown> | undefined {
|
|
49
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
50
|
+
? value as Record<string, unknown> : undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function nonEmpty(value: unknown): value is string {
|
|
54
|
+
return typeof value === 'string' && value.length > 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function nullableString(value: unknown): value is string | null {
|
|
58
|
+
return value === null || typeof value === 'string';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function nonNegativeInteger(value: unknown): value is number {
|
|
62
|
+
return Number.isInteger(value) && Number(value) >= 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function all(values: readonly boolean[]): boolean {
|
|
66
|
+
return values.every(Boolean);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function bindingShapeValid(value: Record<string, unknown>): boolean {
|
|
70
|
+
const kind = value.bindingKind as SymbolImportBindingKind;
|
|
71
|
+
const imported = value.importedName;
|
|
72
|
+
if (!all([
|
|
73
|
+
bindingKinds.has(kind),
|
|
74
|
+
nonEmpty(value.localName),
|
|
75
|
+
typeof value.typeOnly === 'boolean',
|
|
76
|
+
])) return false;
|
|
77
|
+
if (kind === 'esm_namespace' || kind === 'cjs_namespace')
|
|
78
|
+
return imported === null;
|
|
79
|
+
if (!nonEmpty(imported)) return false;
|
|
80
|
+
return kind !== 'esm_default' || imported === 'default';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function memberReferenceValid(
|
|
84
|
+
value: Record<string, unknown>,
|
|
85
|
+
shape: SymbolImportReferenceShape,
|
|
86
|
+
): boolean {
|
|
87
|
+
const kind = value.bindingKind as SymbolImportBindingKind;
|
|
88
|
+
const member = value.referencedMemberName;
|
|
89
|
+
if (!nonEmpty(member)) return false;
|
|
90
|
+
if (shape === 'namespace_member') return all([
|
|
91
|
+
kind === 'esm_namespace' || kind === 'cjs_namespace',
|
|
92
|
+
value.requestedPublicName === member,
|
|
93
|
+
]);
|
|
94
|
+
if (!nonEmpty(value.importedName)) return false;
|
|
95
|
+
return all([
|
|
96
|
+
value.requestedPublicName === `${value.importedName}.${member}`,
|
|
97
|
+
shape !== 'default_member' || kind === 'esm_default',
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function referenceShapeValid(value: Record<string, unknown>): boolean {
|
|
102
|
+
const shape = value.referenceShape as SymbolImportReferenceShape;
|
|
103
|
+
if (!referenceShapes.has(shape)
|
|
104
|
+
|| !nonEmpty(value.requestedPublicName)) return false;
|
|
105
|
+
return shape === 'identifier'
|
|
106
|
+
? all([
|
|
107
|
+
value.referencedMemberName === null,
|
|
108
|
+
nonEmpty(value.importedName),
|
|
109
|
+
value.requestedPublicName === value.importedName,
|
|
110
|
+
])
|
|
111
|
+
: memberReferenceValid(value, shape);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function importReferenceBaseValid(
|
|
115
|
+
item: Record<string, unknown>,
|
|
116
|
+
): boolean {
|
|
117
|
+
return all([
|
|
118
|
+
item.version === 1,
|
|
119
|
+
bindingShapeValid(item),
|
|
120
|
+
referenceShapeValid(item),
|
|
121
|
+
nonEmpty(item.rawModuleSpecifier),
|
|
122
|
+
nonNegativeInteger(item.bindingSiteStartOffset),
|
|
123
|
+
nonNegativeInteger(item.bindingSiteEndOffset),
|
|
124
|
+
Number(item.bindingSiteEndOffset)
|
|
125
|
+
> Number(item.bindingSiteStartOffset),
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function parsePackageImportReference(
|
|
130
|
+
value: unknown,
|
|
131
|
+
): SymbolImportReference | undefined {
|
|
132
|
+
const item = record(value);
|
|
133
|
+
if (!item || !importReferenceBaseValid(item)
|
|
134
|
+
|| item.moduleKind !== 'package'
|
|
135
|
+
|| !nonEmpty(item.requestedPackageName)
|
|
136
|
+
|| !nonEmpty(item.requestedModuleSubpath)) return undefined;
|
|
137
|
+
const specifier = item.rawModuleSpecifier;
|
|
138
|
+
if (!nonEmpty(specifier)) return undefined;
|
|
139
|
+
const request = packageModuleRequest(specifier);
|
|
140
|
+
if (!request || request.packageName !== item.requestedPackageName
|
|
141
|
+
|| request.moduleSubpath !== item.requestedModuleSubpath) return undefined;
|
|
142
|
+
return item as unknown as SymbolImportReference;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function parseRelativeImportReference(
|
|
146
|
+
value: unknown,
|
|
147
|
+
): SymbolImportReference | undefined {
|
|
148
|
+
const item = record(value);
|
|
149
|
+
if (!item || !importReferenceBaseValid(item)) return undefined;
|
|
150
|
+
if (!all([
|
|
151
|
+
item.moduleKind === 'relative',
|
|
152
|
+
item.requestedPackageName === null,
|
|
153
|
+
item.requestedModuleSubpath === null,
|
|
154
|
+
String(item.rawModuleSpecifier).startsWith('.'),
|
|
155
|
+
])) return undefined;
|
|
156
|
+
return item as unknown as SymbolImportReference;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function validBody(value: unknown): value is ExecutableBodyEligibility {
|
|
160
|
+
const item = record(value);
|
|
161
|
+
if (!item || typeof item.eligible !== 'boolean'
|
|
162
|
+
|| typeof item.reason !== 'string' || !bodyReasons.has(item.reason))
|
|
163
|
+
return false;
|
|
164
|
+
return item.eligible === (item.reason === 'body_present');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function validTarget(value: unknown): value is PublicSurfaceTarget {
|
|
168
|
+
const item = record(value);
|
|
169
|
+
return Boolean(item && nonEmpty(item.sourceFile)
|
|
170
|
+
&& ['function', 'method', 'object_method'].includes(String(item.kind))
|
|
171
|
+
&& nonEmpty(item.localName) && nonEmpty(item.qualifiedName)
|
|
172
|
+
&& nonNegativeInteger(item.startOffset)
|
|
173
|
+
&& nonNegativeInteger(item.endOffset)
|
|
174
|
+
&& Number(item.endOffset) > Number(item.startOffset)
|
|
175
|
+
&& validBody(item.bodyEligibility));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function targetIdentity(value: PublicSurfaceTarget): string {
|
|
179
|
+
return [
|
|
180
|
+
value.sourceFile, value.kind, value.qualifiedName,
|
|
181
|
+
value.startOffset, value.endOffset,
|
|
182
|
+
].join('\0');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function validEntry(value: unknown): value is PackagePublicEntry {
|
|
186
|
+
const item = record(value);
|
|
187
|
+
return Boolean(item && nonEmpty(item.entry) && nonEmpty(item.modulePath)
|
|
188
|
+
&& (item.entry === '.' || String(item.entry).startsWith('./')));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function validScope(value: unknown): value is PackagePublicScope {
|
|
192
|
+
const item = record(value);
|
|
193
|
+
if (!item || !all([
|
|
194
|
+
validEntry(item),
|
|
195
|
+
nonEmpty(item.publicName),
|
|
196
|
+
nonNegativeInteger(item.candidateCount),
|
|
197
|
+
nonNegativeInteger(item.eligibleCandidateCount),
|
|
198
|
+
[0, 1].includes(Number(item.selectedCandidateCount)),
|
|
199
|
+
item.candidateSetComplete === true,
|
|
200
|
+
Array.isArray(item.targets),
|
|
201
|
+
])) return false;
|
|
202
|
+
if (!Array.isArray(item.targets) || !item.targets.every(validTarget))
|
|
203
|
+
return false;
|
|
204
|
+
const targetKeys = new Set(item.targets.map(targetIdentity));
|
|
205
|
+
if (targetKeys.size !== item.targets.length) return false;
|
|
206
|
+
const eligible = item.targets.filter((target) =>
|
|
207
|
+
target.bodyEligibility.eligible).length;
|
|
208
|
+
return all([
|
|
209
|
+
Number(item.candidateCount) >= item.targets.length,
|
|
210
|
+
item.eligibleCandidateCount === eligible,
|
|
211
|
+
item.selectedCandidateCount === (eligible === 1 ? 1 : 0),
|
|
212
|
+
]);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function statusMatrixValid(
|
|
216
|
+
value: Record<string, unknown>,
|
|
217
|
+
expectedPackageName: string | null,
|
|
218
|
+
): boolean {
|
|
219
|
+
const status = value.status;
|
|
220
|
+
const reason = value.reason;
|
|
221
|
+
if (!['complete', 'incomplete', 'unsupported', 'not_applicable']
|
|
222
|
+
.includes(String(status))) return false;
|
|
223
|
+
if (status === 'complete')
|
|
224
|
+
return expectedPackageName !== null && reason === null;
|
|
225
|
+
if (status === 'not_applicable') return all([
|
|
226
|
+
expectedPackageName === null,
|
|
227
|
+
reason === null,
|
|
228
|
+
value.total === 0,
|
|
229
|
+
value.shown === 0,
|
|
230
|
+
value.omitted === 0,
|
|
231
|
+
]);
|
|
232
|
+
return expectedPackageName !== null
|
|
233
|
+
&& typeof reason === 'string' && surfaceReasons.has(reason);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function surfaceCountsValid(value: Record<string, unknown>): boolean {
|
|
237
|
+
if (!nonNegativeInteger(value.total) || !nonNegativeInteger(value.shown)
|
|
238
|
+
|| !nonNegativeInteger(value.omitted)
|
|
239
|
+
|| Number(value.shown) + Number(value.omitted) !== Number(value.total))
|
|
240
|
+
return false;
|
|
241
|
+
if (!Array.isArray(value.entries) || !Array.isArray(value.scopes)
|
|
242
|
+
|| !value.scopes.every((scope) => {
|
|
243
|
+
const item = record(scope);
|
|
244
|
+
return item && Array.isArray(item.targets);
|
|
245
|
+
})) return false;
|
|
246
|
+
const entries = value.entries;
|
|
247
|
+
const scopes = value.scopes as Array<Record<string, unknown>>;
|
|
248
|
+
const represented = entries.length + scopes.reduce(
|
|
249
|
+
(sum, scope) => sum + 1 + (scope.targets as unknown[]).length, 0,
|
|
250
|
+
);
|
|
251
|
+
return represented === value.shown
|
|
252
|
+
&& Number(value.total) >= represented
|
|
253
|
+
&& Number(value.shown) <= PACKAGE_PUBLIC_SURFACE_RECORD_CAP;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function entriesAndScopesValid(value: Record<string, unknown>): boolean {
|
|
257
|
+
if (!Array.isArray(value.entries) || !Array.isArray(value.scopes))
|
|
258
|
+
return false;
|
|
259
|
+
if (!value.entries.every(validEntry) || !value.scopes.every(validScope))
|
|
260
|
+
return false;
|
|
261
|
+
const entries = new Map((value.entries as PackagePublicEntry[])
|
|
262
|
+
.map((entry) => [entry.entry, entry.modulePath]));
|
|
263
|
+
const entryKeys = new Set((value.entries as PackagePublicEntry[])
|
|
264
|
+
.map((entry) => `${entry.entry}\0${entry.modulePath}`));
|
|
265
|
+
if (entryKeys.size !== value.entries.length) return false;
|
|
266
|
+
const scopeKeys = new Set<string>();
|
|
267
|
+
for (const scope of value.scopes as PackagePublicScope[]) {
|
|
268
|
+
if (entries.get(scope.entry) !== scope.modulePath) return false;
|
|
269
|
+
scopeKeys.add(`${scope.entry}\0${scope.publicName}`);
|
|
270
|
+
}
|
|
271
|
+
return scopeKeys.size === value.scopes.length;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function surfaceBaseValid(
|
|
275
|
+
item: Record<string, unknown>,
|
|
276
|
+
expectedPackageName: string | null,
|
|
277
|
+
): boolean {
|
|
278
|
+
return all([
|
|
279
|
+
item.schema === PACKAGE_PUBLIC_SURFACE_SCHEMA,
|
|
280
|
+
item.recordCap === PACKAGE_PUBLIC_SURFACE_RECORD_CAP,
|
|
281
|
+
item.packageName === expectedPackageName,
|
|
282
|
+
typeof item.exportsPresent === 'boolean',
|
|
283
|
+
item.exportsAuthoritative === item.exportsPresent,
|
|
284
|
+
nullableString(item.main),
|
|
285
|
+
nullableString(item.module),
|
|
286
|
+
entriesAndScopesValid(item),
|
|
287
|
+
surfaceCountsValid(item),
|
|
288
|
+
statusMatrixValid(item, expectedPackageName),
|
|
289
|
+
]);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function nonCompleteSurfaceEmpty(item: Record<string, unknown>): boolean {
|
|
293
|
+
if (item.status === 'complete') return true;
|
|
294
|
+
return Array.isArray(item.entries) && item.entries.length === 0
|
|
295
|
+
&& Array.isArray(item.scopes) && item.scopes.length === 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function parsePackagePublicSurfaceFact(
|
|
299
|
+
value: unknown,
|
|
300
|
+
expectedPackageName: string | null,
|
|
301
|
+
): PackagePublicSurfaceFact | undefined {
|
|
302
|
+
const item = record(value);
|
|
303
|
+
if (!item || !surfaceBaseValid(item, expectedPackageName)
|
|
304
|
+
|| !nonCompleteSurfaceEmpty(item)) return undefined;
|
|
305
|
+
return item as unknown as PackagePublicSurfaceFact;
|
|
306
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
|
|
3
|
+
export type ExecutableBodyEligibilityReason =
|
|
4
|
+
| 'body_present'
|
|
5
|
+
| 'declaration_only'
|
|
6
|
+
| 'ambient_declaration'
|
|
7
|
+
| 'abstract_bodyless'
|
|
8
|
+
| 'overload_signature';
|
|
9
|
+
|
|
10
|
+
export interface ExecutableBodyEligibilityEvidence {
|
|
11
|
+
eligible: boolean;
|
|
12
|
+
reason: ExecutableBodyEligibilityReason;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function hasModifier(node: ts.Node, kind: ts.SyntaxKind): boolean {
|
|
16
|
+
return ts.canHaveModifiers(node)
|
|
17
|
+
&& Boolean(ts.getModifiers(node)?.some((item) => item.kind === kind));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function executableBodyEligibility(
|
|
21
|
+
node: ts.FunctionLikeDeclaration,
|
|
22
|
+
source: ts.SourceFile,
|
|
23
|
+
): ExecutableBodyEligibilityEvidence {
|
|
24
|
+
if (node.body) return { eligible: true, reason: 'body_present' };
|
|
25
|
+
if (hasModifier(node, ts.SyntaxKind.AbstractKeyword))
|
|
26
|
+
return { eligible: false, reason: 'abstract_bodyless' };
|
|
27
|
+
if (hasModifier(node, ts.SyntaxKind.DeclareKeyword))
|
|
28
|
+
return { eligible: false, reason: 'ambient_declaration' };
|
|
29
|
+
return {
|
|
30
|
+
eligible: false,
|
|
31
|
+
reason: source.isDeclarationFile
|
|
32
|
+
? 'declaration_only'
|
|
33
|
+
: 'overload_signature',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import {
|
|
3
|
+
connectFactFromCall,
|
|
4
|
+
transactionReceiverName,
|
|
5
|
+
unwrapCall,
|
|
6
|
+
unwrapIdentityExpression,
|
|
7
|
+
type HelperBinding,
|
|
8
|
+
} from './service-binding-parser-helpers.js';
|
|
9
|
+
import {
|
|
10
|
+
createBindingLexicalIndex,
|
|
11
|
+
type BindingLexicalIndex,
|
|
12
|
+
type BindingSiteCandidate,
|
|
13
|
+
type VisibleBinding,
|
|
14
|
+
} from './011-binding-lexical-scope.js';
|
|
15
|
+
import { selectVisibleBinding } from './021-binding-visibility.js';
|
|
16
|
+
|
|
17
|
+
export type LocalBindingFact = Omit<
|
|
18
|
+
HelperBinding,
|
|
19
|
+
'exportedName' | 'sourceFile' | 'sourceLine'
|
|
20
|
+
>;
|
|
21
|
+
|
|
22
|
+
export function directConnectFact(
|
|
23
|
+
expression: ts.Expression,
|
|
24
|
+
): LocalBindingFact | undefined {
|
|
25
|
+
const call = unwrapCall(expression);
|
|
26
|
+
return call ? connectFactFromCall(call) : undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function bindingValueAtSite<T>(
|
|
30
|
+
variableName: string,
|
|
31
|
+
node: ts.Node,
|
|
32
|
+
source: ts.SourceFile,
|
|
33
|
+
value: T,
|
|
34
|
+
): BindingSiteCandidate<T> {
|
|
35
|
+
return {
|
|
36
|
+
variableName,
|
|
37
|
+
bindingSiteStartOffset: node.getStart(source),
|
|
38
|
+
bindingSiteEndOffset: node.getEnd(),
|
|
39
|
+
value,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function arrayBindingName(
|
|
44
|
+
element: ts.ArrayBindingElement | undefined,
|
|
45
|
+
): string | undefined {
|
|
46
|
+
return element && ts.isBindingElement(element)
|
|
47
|
+
&& !element.dotDotDotToken && ts.isIdentifier(element.name)
|
|
48
|
+
? element.name.text
|
|
49
|
+
: undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function arrayAssignmentName(
|
|
53
|
+
element: ts.Expression | undefined,
|
|
54
|
+
): string | undefined {
|
|
55
|
+
return element && !ts.isOmittedExpression(element)
|
|
56
|
+
&& !ts.isSpreadElement(element) && ts.isIdentifier(element)
|
|
57
|
+
? element.text
|
|
58
|
+
: undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function deterministicLocalSource(
|
|
62
|
+
selected: VisibleBinding<LocalBindingFact>,
|
|
63
|
+
): LocalBindingFact | undefined {
|
|
64
|
+
if (selected.status !== 'resolved') return undefined;
|
|
65
|
+
if (!selected.site?.deterministic) return undefined;
|
|
66
|
+
if (selected.declarationSite?.declarationKind === 'var') return undefined;
|
|
67
|
+
return selected.candidate?.value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function aliasLocalFact(
|
|
71
|
+
sourceName: string,
|
|
72
|
+
targetName: string,
|
|
73
|
+
node: ts.Node,
|
|
74
|
+
index: BindingLexicalIndex,
|
|
75
|
+
bindings: readonly BindingSiteCandidate<LocalBindingFact>[],
|
|
76
|
+
transaction: boolean,
|
|
77
|
+
): BindingSiteCandidate<LocalBindingFact> | undefined {
|
|
78
|
+
const source = deterministicLocalSource(
|
|
79
|
+
selectVisibleBinding(index, bindings, sourceName, node),
|
|
80
|
+
);
|
|
81
|
+
if (!source) return undefined;
|
|
82
|
+
const helperChain = [...(source.helperChain ?? []), {
|
|
83
|
+
aliasOf: sourceName,
|
|
84
|
+
callerVariable: targetName,
|
|
85
|
+
aliasKind: transaction ? 'transaction' : 'identity',
|
|
86
|
+
scopeRule: 'exact_lexical_scope',
|
|
87
|
+
...(transaction ? { transactionAliasSource: sourceName } : {}),
|
|
88
|
+
}];
|
|
89
|
+
return bindingValueAtSite(
|
|
90
|
+
targetName, node, index.source, { ...source, helperChain },
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function localAliasSource(initializer: ts.Expression): {
|
|
95
|
+
sourceName?: string;
|
|
96
|
+
transaction: boolean;
|
|
97
|
+
} {
|
|
98
|
+
const transaction = transactionReceiverName(initializer);
|
|
99
|
+
if (transaction) return { sourceName: transaction, transaction: true };
|
|
100
|
+
const unwrapped = unwrapIdentityExpression(initializer);
|
|
101
|
+
return {
|
|
102
|
+
sourceName: ts.isIdentifier(unwrapped) ? unwrapped.text : undefined,
|
|
103
|
+
transaction: false,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function localFactCandidate(
|
|
108
|
+
node: ts.Node,
|
|
109
|
+
source: ts.SourceFile,
|
|
110
|
+
index: BindingLexicalIndex,
|
|
111
|
+
bindings: readonly BindingSiteCandidate<LocalBindingFact>[],
|
|
112
|
+
): BindingSiteCandidate<LocalBindingFact> | undefined {
|
|
113
|
+
if (!ts.isVariableDeclaration(node)
|
|
114
|
+
|| !ts.isIdentifier(node.name) || !node.initializer) return undefined;
|
|
115
|
+
const fact = directConnectFact(node.initializer);
|
|
116
|
+
if (fact) return bindingValueAtSite(node.name.text, node, source, fact);
|
|
117
|
+
const alias = localAliasSource(node.initializer);
|
|
118
|
+
return alias.sourceName
|
|
119
|
+
? aliasLocalFact(
|
|
120
|
+
alias.sourceName, node.name.text, node, index,
|
|
121
|
+
bindings, alias.transaction,
|
|
122
|
+
)
|
|
123
|
+
: undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function collectLocalBindingFacts(
|
|
127
|
+
fn: ts.FunctionLikeDeclaration,
|
|
128
|
+
): BindingSiteCandidate<LocalBindingFact>[] {
|
|
129
|
+
const source = fn.getSourceFile();
|
|
130
|
+
const index = createBindingLexicalIndex(source);
|
|
131
|
+
const bindings: BindingSiteCandidate<LocalBindingFact>[] = [];
|
|
132
|
+
function visit(node: ts.Node): void {
|
|
133
|
+
if (node !== fn && (ts.isFunctionDeclaration(node)
|
|
134
|
+
|| ts.isArrowFunction(node) || ts.isFunctionExpression(node))) return;
|
|
135
|
+
const row = localFactCandidate(node, source, index, bindings);
|
|
136
|
+
if (row) bindings.push(row);
|
|
137
|
+
ts.forEachChild(node, visit);
|
|
138
|
+
}
|
|
139
|
+
visit(fn);
|
|
140
|
+
return bindings;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function returnedProperty(
|
|
144
|
+
property: ts.ObjectLiteralElementLike,
|
|
145
|
+
): { propertyName?: string; variableName?: string } {
|
|
146
|
+
if (ts.isShorthandPropertyAssignment(property))
|
|
147
|
+
return {
|
|
148
|
+
propertyName: property.name.text,
|
|
149
|
+
variableName: property.name.text,
|
|
150
|
+
};
|
|
151
|
+
if (!ts.isPropertyAssignment(property)
|
|
152
|
+
|| !ts.isIdentifier(property.initializer)) return {};
|
|
153
|
+
const propertyName = ts.isIdentifier(property.name)
|
|
154
|
+
|| ts.isStringLiteralLike(property.name)
|
|
155
|
+
? property.name.text
|
|
156
|
+
: undefined;
|
|
157
|
+
return { propertyName, variableName: property.initializer.text };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function branchDependent(node: ts.Node, fn: ts.FunctionLikeDeclaration): boolean {
|
|
161
|
+
let current: ts.Node | undefined = node.parent;
|
|
162
|
+
while (current && current !== fn) {
|
|
163
|
+
if (ts.isIfStatement(current) || ts.isConditionalExpression(current)
|
|
164
|
+
|| ts.isSwitchStatement(current) || ts.isIterationStatement(current, false)
|
|
165
|
+
|| ts.isTryStatement(current)) return true;
|
|
166
|
+
current = current.parent;
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function singleReturn(
|
|
172
|
+
fn: ts.FunctionLikeDeclaration,
|
|
173
|
+
): ts.ReturnStatement | undefined {
|
|
174
|
+
const returns: ts.ReturnStatement[] = [];
|
|
175
|
+
function visit(node: ts.Node): void {
|
|
176
|
+
if (node !== fn && (ts.isFunctionDeclaration(node)
|
|
177
|
+
|| ts.isArrowFunction(node) || ts.isFunctionExpression(node))) return;
|
|
178
|
+
if (ts.isReturnStatement(node)) returns.push(node);
|
|
179
|
+
else ts.forEachChild(node, visit);
|
|
180
|
+
}
|
|
181
|
+
visit(fn);
|
|
182
|
+
const returned = returns.length === 1 ? returns[0] : undefined;
|
|
183
|
+
return returned && !branchDependent(returned, fn) ? returned : undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function collectReturnedObjectBindings(
|
|
187
|
+
fn: ts.FunctionLikeDeclaration,
|
|
188
|
+
): Map<string, LocalBindingFact> {
|
|
189
|
+
const index = createBindingLexicalIndex(fn.getSourceFile());
|
|
190
|
+
const bindings = collectLocalBindingFacts(fn);
|
|
191
|
+
const out = new Map<string, LocalBindingFact>();
|
|
192
|
+
const returned = singleReturn(fn)?.expression;
|
|
193
|
+
if (!returned || !ts.isObjectLiteralExpression(returned)) return out;
|
|
194
|
+
for (const property of returned.properties) {
|
|
195
|
+
const names = returnedProperty(property);
|
|
196
|
+
if (!names.propertyName || !names.variableName) continue;
|
|
197
|
+
const selected = selectVisibleBinding(
|
|
198
|
+
index, bindings, names.variableName, property,
|
|
199
|
+
);
|
|
200
|
+
const fact = deterministicLocalSource(selected);
|
|
201
|
+
if (fact) out.set(names.propertyName, fact);
|
|
202
|
+
}
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function functionLikeInitializer(
|
|
207
|
+
expression: ts.Expression | undefined,
|
|
208
|
+
): ts.FunctionLikeDeclaration | undefined {
|
|
209
|
+
return expression && (ts.isArrowFunction(expression)
|
|
210
|
+
|| ts.isFunctionExpression(expression))
|
|
211
|
+
? expression
|
|
212
|
+
: undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function directReturnConnectFact(
|
|
216
|
+
fn: ts.FunctionLikeDeclaration,
|
|
217
|
+
): LocalBindingFact | undefined {
|
|
218
|
+
const index = createBindingLexicalIndex(fn.getSourceFile());
|
|
219
|
+
const bindings = collectLocalBindingFacts(fn);
|
|
220
|
+
const returned = singleReturn(fn)?.expression;
|
|
221
|
+
if (!returned) return undefined;
|
|
222
|
+
if (ts.isIdentifier(returned)) {
|
|
223
|
+
const selected = selectVisibleBinding(
|
|
224
|
+
index, bindings, returned.text, returned,
|
|
225
|
+
);
|
|
226
|
+
return selected.site?.deterministic
|
|
227
|
+
&& selected.declarationSite?.declarationKind !== 'var'
|
|
228
|
+
? selected.candidate?.value
|
|
229
|
+
: undefined;
|
|
230
|
+
}
|
|
231
|
+
return directConnectFact(returned);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function directConnectFactFromFunctionLike(
|
|
235
|
+
fn: ts.FunctionLikeDeclaration,
|
|
236
|
+
): LocalBindingFact | undefined {
|
|
237
|
+
if (ts.isArrowFunction(fn) && fn.body && !ts.isBlock(fn.body))
|
|
238
|
+
return directConnectFact(fn.body);
|
|
239
|
+
return directReturnConnectFact(fn);
|
|
240
|
+
}
|