@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
|
@@ -1,17 +1,38 @@
|
|
|
1
|
-
import { posix } from 'node:path';
|
|
2
1
|
import type { OutboundCallFact, SymbolCallFact } from '../types.js';
|
|
3
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
selectCallOwner,
|
|
4
|
+
type OwnerCandidate,
|
|
5
|
+
type OwnerSelection,
|
|
6
|
+
} from '../parsers/004-fact-identity.js';
|
|
4
7
|
import type { Db, Statement } from './connection.js';
|
|
8
|
+
import {
|
|
9
|
+
resolveRelativeSymbolCall,
|
|
10
|
+
} from './006-relative-symbol-resolution.js';
|
|
11
|
+
import {
|
|
12
|
+
parsePackageImportReference,
|
|
13
|
+
} from '../parsers/012-package-fact-contract.js';
|
|
14
|
+
import {
|
|
15
|
+
resolvedBindingReferenceProofValid,
|
|
16
|
+
type BindingProofCall,
|
|
17
|
+
type BindingProofTarget,
|
|
18
|
+
} from './012-binding-reference-proof.js';
|
|
19
|
+
import {
|
|
20
|
+
preparedCallSnapshotError,
|
|
21
|
+
} from './013-index-publication-failure.js';
|
|
22
|
+
import {
|
|
23
|
+
hasSingleHopHelperReturn,
|
|
24
|
+
} from './014-binding-helper-provenance.js';
|
|
5
25
|
|
|
6
26
|
export function insertSymbolCalls(db: Db, repoId: number, rows: SymbolCallFact[]): void {
|
|
7
|
-
const callerStmt = db.prepare('SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1');
|
|
8
27
|
const insertStmt = db.prepare('INSERT INTO symbol_calls(repo_id,caller_symbol_id,callee_symbol_id,callee_expression,import_source,source_file,source_line,call_site_start_offset,call_site_end_offset,call_role,status,confidence,evidence_json,unresolved_reason) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
|
|
9
28
|
for (const r of rows) {
|
|
10
|
-
|
|
29
|
+
assertImportProvenance(r);
|
|
30
|
+
const callerId = requiredSymbolCallOwnerId(db, repoId, r);
|
|
11
31
|
const target = resolveSymbolCallTarget(db, repoId, r);
|
|
32
|
+
const cardinality = resolutionCardinality(target);
|
|
12
33
|
insertStmt.run(
|
|
13
34
|
repoId,
|
|
14
|
-
|
|
35
|
+
callerId,
|
|
15
36
|
target.id,
|
|
16
37
|
r.calleeExpression,
|
|
17
38
|
r.importSource,
|
|
@@ -26,6 +47,10 @@ export function insertSymbolCalls(db: Db, repoId: number, rows: SymbolCallFact[]
|
|
|
26
47
|
...r.evidence,
|
|
27
48
|
candidateStrategy: target.strategy,
|
|
28
49
|
candidateCount: target.candidateCount,
|
|
50
|
+
eligibleCandidateCount: cardinality.eligibleCandidateCount,
|
|
51
|
+
selectedCandidateCount: cardinality.selectedCandidateCount,
|
|
52
|
+
candidateSetComplete: cardinality.candidateSetComplete,
|
|
53
|
+
unresolvedReason: target.reason,
|
|
29
54
|
resolvedModulePath: target.resolvedModulePath,
|
|
30
55
|
}),
|
|
31
56
|
target.reason,
|
|
@@ -33,6 +58,40 @@ export function insertSymbolCalls(db: Db, repoId: number, rows: SymbolCallFact[]
|
|
|
33
58
|
}
|
|
34
59
|
}
|
|
35
60
|
|
|
61
|
+
function assertImportProvenance(call: SymbolCallFact): void {
|
|
62
|
+
if (call.importSource === undefined || call.importSource.startsWith('.'))
|
|
63
|
+
return;
|
|
64
|
+
const direct = directPackageProvenanceValid(call);
|
|
65
|
+
const derived = derivedPackageProvenanceValid(call);
|
|
66
|
+
if (!direct && !derived)
|
|
67
|
+
throw preparedCallSnapshotError(
|
|
68
|
+
'package_import_provenance_missing', 'symbol_call', call,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function directPackageProvenanceValid(call: SymbolCallFact): boolean {
|
|
73
|
+
const binding = parsePackageImportReference(call.evidence.importBinding);
|
|
74
|
+
return Boolean(binding
|
|
75
|
+
&& call.evidence.relation === 'package_import'
|
|
76
|
+
&& call.evidence.derivedImportBinding === undefined
|
|
77
|
+
&& call.importSource === binding.rawModuleSpecifier
|
|
78
|
+
&& call.evidence.targetName === binding.requestedPublicName);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function derivedPackageProvenanceValid(call: SymbolCallFact): boolean {
|
|
82
|
+
const binding = parsePackageImportReference(
|
|
83
|
+
call.evidence.derivedImportBinding,
|
|
84
|
+
);
|
|
85
|
+
if (!binding || typeof binding.referencedMemberName !== 'string')
|
|
86
|
+
return false;
|
|
87
|
+
const expected = typeof call.evidence.proxyVariableName === 'string'
|
|
88
|
+
? binding.referencedMemberName : binding.requestedPublicName;
|
|
89
|
+
return call.evidence.relation === 'package_import_derived_member'
|
|
90
|
+
&& call.evidence.importBinding === undefined
|
|
91
|
+
&& call.importSource === binding.rawModuleSpecifier
|
|
92
|
+
&& call.evidence.targetName === expected;
|
|
93
|
+
}
|
|
94
|
+
|
|
36
95
|
interface SymbolTargetRow {
|
|
37
96
|
id: number;
|
|
38
97
|
kind?: string;
|
|
@@ -46,38 +105,116 @@ interface SymbolCallResolution {
|
|
|
46
105
|
reason: string | null;
|
|
47
106
|
strategy: string;
|
|
48
107
|
candidateCount: number;
|
|
108
|
+
eligibleCandidateCount: number;
|
|
109
|
+
candidateSetComplete: boolean;
|
|
49
110
|
resolvedModulePath?: string;
|
|
50
111
|
}
|
|
51
112
|
|
|
52
|
-
|
|
113
|
+
interface PersistedOwnerCandidate extends OwnerCandidate {
|
|
114
|
+
id: number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function persistedCallOwners(
|
|
118
|
+
db: Db,
|
|
119
|
+
repoId: number,
|
|
120
|
+
sourceFile: string,
|
|
121
|
+
start: number | undefined,
|
|
122
|
+
end: number | undefined,
|
|
123
|
+
): PersistedOwnerCandidate[] {
|
|
124
|
+
if (start === undefined || end === undefined) return [];
|
|
125
|
+
const rows = db.prepare(`SELECT id,kind,qualified_name qualifiedName,
|
|
126
|
+
start_offset startOffset,end_offset endOffset FROM symbols
|
|
127
|
+
WHERE repo_id=? AND source_file=? AND start_offset<=? AND end_offset>=?`)
|
|
128
|
+
.all(repoId, sourceFile, start, end);
|
|
129
|
+
return rows.flatMap(persistedOwnerCandidate);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function persistedOwnerCandidate(
|
|
133
|
+
row: Record<string, unknown>,
|
|
134
|
+
): PersistedOwnerCandidate[] {
|
|
135
|
+
if (typeof row.id !== 'number' || typeof row.kind !== 'string'
|
|
136
|
+
|| typeof row.qualifiedName !== 'string'
|
|
137
|
+
|| typeof row.startOffset !== 'number'
|
|
138
|
+
|| typeof row.endOffset !== 'number') return [];
|
|
139
|
+
return [{
|
|
140
|
+
id: row.id,
|
|
141
|
+
kind: row.kind,
|
|
142
|
+
qualifiedName: row.qualifiedName,
|
|
143
|
+
startOffset: row.startOffset,
|
|
144
|
+
endOffset: row.endOffset,
|
|
145
|
+
}];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function requiredSymbolCallOwnerId(
|
|
149
|
+
db: Db,
|
|
150
|
+
repoId: number,
|
|
151
|
+
call: SymbolCallFact,
|
|
152
|
+
): number {
|
|
153
|
+
const candidates = persistedCallOwners(
|
|
154
|
+
db, repoId, call.sourceFile,
|
|
155
|
+
call.callSiteStartOffset, call.callSiteEndOffset,
|
|
156
|
+
);
|
|
157
|
+
const selected = selectCallOwner(
|
|
158
|
+
candidates,
|
|
159
|
+
call.callSiteStartOffset ?? -1,
|
|
160
|
+
call.callSiteEndOffset ?? -1,
|
|
161
|
+
call.callRole === 'event_subscribe_handler',
|
|
162
|
+
);
|
|
163
|
+
if (selected.status !== 'resolved'
|
|
164
|
+
|| selected.owner?.qualifiedName !== call.callerQualifiedName)
|
|
165
|
+
throw preparedCallSnapshotError(
|
|
166
|
+
'symbol_call_owner_mismatch', 'symbol_call', call,
|
|
167
|
+
);
|
|
168
|
+
return selected.owner.id;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function resolutionCardinality(
|
|
172
|
+
target: SymbolCallResolution,
|
|
173
|
+
): {
|
|
174
|
+
eligibleCandidateCount: number;
|
|
175
|
+
selectedCandidateCount: 0 | 1;
|
|
176
|
+
candidateSetComplete: boolean;
|
|
177
|
+
} {
|
|
178
|
+
return {
|
|
179
|
+
eligibleCandidateCount: target.eligibleCandidateCount,
|
|
180
|
+
selectedCandidateCount: target.status === 'resolved' ? 1 : 0,
|
|
181
|
+
candidateSetComplete: target.candidateSetComplete,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
53
184
|
|
|
54
185
|
function symbolTargetRows(rows: Array<Record<string, unknown>>): SymbolTargetRow[] {
|
|
55
186
|
return rows.flatMap((row) => typeof row.id === 'number' ? [{
|
|
56
187
|
id: row.id,
|
|
57
188
|
kind: typeof row.kind === 'string' ? row.kind : undefined,
|
|
58
|
-
sourceFile:
|
|
59
|
-
evidenceJson:
|
|
189
|
+
sourceFile: typeof row.sourceFile === 'string' ? row.sourceFile : null,
|
|
190
|
+
evidenceJson: typeof row.evidenceJson === 'string'
|
|
191
|
+
? row.evidenceJson : null,
|
|
60
192
|
}] : []);
|
|
61
193
|
}
|
|
62
194
|
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return new Set([joined, `${joined}/index`]);
|
|
195
|
+
function record(value: unknown): Record<string, unknown> | undefined {
|
|
196
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
197
|
+
? value as Record<string, unknown> : undefined;
|
|
67
198
|
}
|
|
68
199
|
|
|
69
|
-
function
|
|
70
|
-
if (!
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
200
|
+
function executableTarget(row: SymbolTargetRow): boolean {
|
|
201
|
+
if (!row.evidenceJson || !row.kind
|
|
202
|
+
|| !['function', 'method', 'object_method', 'callback'].includes(row.kind))
|
|
203
|
+
return false;
|
|
204
|
+
try {
|
|
205
|
+
const evidence = record(JSON.parse(row.evidenceJson) as unknown);
|
|
206
|
+
const body = record(evidence?.executableBodyEligibility);
|
|
207
|
+
return body?.eligible === true && body.reason === 'body_present';
|
|
208
|
+
} catch {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
74
211
|
}
|
|
75
212
|
|
|
76
213
|
function resolvedSymbol(
|
|
77
214
|
row: SymbolTargetRow,
|
|
78
215
|
strategy: string,
|
|
79
216
|
candidateCount: number,
|
|
80
|
-
|
|
217
|
+
eligibleCandidateCount = 1,
|
|
81
218
|
): SymbolCallResolution {
|
|
82
219
|
return {
|
|
83
220
|
id: row.id,
|
|
@@ -85,211 +222,170 @@ function resolvedSymbol(
|
|
|
85
222
|
reason: null,
|
|
86
223
|
strategy,
|
|
87
224
|
candidateCount,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
: undefined,
|
|
225
|
+
eligibleCandidateCount,
|
|
226
|
+
candidateSetComplete: true,
|
|
91
227
|
};
|
|
92
228
|
}
|
|
93
229
|
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
function sameFileResolution(
|
|
103
|
-
db: Db,
|
|
104
|
-
repoId: number,
|
|
105
|
-
r: SymbolCallFact,
|
|
106
|
-
relation: unknown,
|
|
107
|
-
): SymbolCallResolution | undefined {
|
|
108
|
-
const bareImport = relation === 'relative_import' && isRelativeImportedSymbolCall(r)
|
|
109
|
-
&& !String(r.calleeLocalName).includes('.');
|
|
110
|
-
if (bareImport || relation === 'relative_import_namespace_member'
|
|
111
|
-
|| relation === 'package_import') return undefined;
|
|
112
|
-
const rows = symbolTargetRows(db.prepare('SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND (name=? OR qualified_name=?) ORDER BY id').all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName));
|
|
113
|
-
if (rows.length === 1 && rows[0]) return resolvedSymbol(rows[0], 'same_file_exact', 1);
|
|
114
|
-
return rows.length > 1
|
|
115
|
-
? {
|
|
116
|
-
id: null,
|
|
117
|
-
status: 'ambiguous',
|
|
118
|
-
reason: 'Multiple same-file symbol targets matched exactly',
|
|
119
|
-
strategy: 'same_file_exact',
|
|
120
|
-
candidateCount: rows.length,
|
|
121
|
-
}
|
|
122
|
-
: undefined;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function classInstanceResolution(
|
|
126
|
-
db: Db,
|
|
127
|
-
repoId: number,
|
|
128
|
-
r: SymbolCallFact,
|
|
129
|
-
relation: unknown,
|
|
130
|
-
): SymbolCallResolution | undefined {
|
|
131
|
-
if (relation !== 'class_instance_method' || !isRelativeImportedSymbolCall(r))
|
|
132
|
-
return undefined;
|
|
133
|
-
const rows = symbolTargetRows(db.prepare('SELECT id FROM symbols WHERE repo_id=? AND source_file<>? AND qualified_name=? ORDER BY id').all(repoId, r.sourceFile, r.calleeLocalName));
|
|
134
|
-
if (rows.length === 1 && rows[0])
|
|
135
|
-
return resolvedSymbol(rows[0], 'relative_import_class_instance_method', 1);
|
|
136
|
-
return rows.length > 1
|
|
137
|
-
? {
|
|
138
|
-
id: null,
|
|
139
|
-
status: 'ambiguous',
|
|
140
|
-
reason: 'Multiple relative class instance method targets matched exactly',
|
|
141
|
-
strategy: 'relative_import_class_instance_method',
|
|
142
|
-
candidateCount: rows.length,
|
|
143
|
-
}
|
|
144
|
-
: undefined;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function namespaceResolution(
|
|
148
|
-
db: Db,
|
|
149
|
-
repoId: number,
|
|
150
|
-
r: SymbolCallFact,
|
|
151
|
-
relation: unknown,
|
|
152
|
-
): SymbolCallResolution | undefined {
|
|
153
|
-
if (relation !== 'relative_import_namespace_member'
|
|
154
|
-
|| !isRelativeImportedSymbolCall(r)) return undefined;
|
|
155
|
-
const rows = moduleRows(exportedSymbolRows(db, repoId, r), r);
|
|
156
|
-
if (rows.length === 1 && rows[0])
|
|
157
|
-
return resolvedSymbol(rows[0], 'relative_import_namespace_member', 1, true);
|
|
158
|
-
if (rows.length > 1) return {
|
|
159
|
-
id: null,
|
|
160
|
-
status: 'ambiguous',
|
|
161
|
-
reason: 'Multiple namespace member targets matched the imported module',
|
|
162
|
-
strategy: 'relative_import_namespace_member',
|
|
163
|
-
candidateCount: rows.length,
|
|
164
|
-
};
|
|
230
|
+
function unresolvedSymbol(
|
|
231
|
+
strategy: string,
|
|
232
|
+
reason: string,
|
|
233
|
+
candidateCount: number,
|
|
234
|
+
eligibleCandidateCount = 0,
|
|
235
|
+
candidateSetComplete = true,
|
|
236
|
+
): SymbolCallResolution {
|
|
165
237
|
return {
|
|
166
238
|
id: null,
|
|
167
239
|
status: 'unresolved',
|
|
168
|
-
reason
|
|
169
|
-
strategy
|
|
170
|
-
candidateCount
|
|
240
|
+
reason,
|
|
241
|
+
strategy,
|
|
242
|
+
candidateCount,
|
|
243
|
+
eligibleCandidateCount,
|
|
244
|
+
candidateSetComplete,
|
|
171
245
|
};
|
|
172
246
|
}
|
|
173
247
|
|
|
174
|
-
function
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const mapped = rows.filter(isExportedObjectMapping);
|
|
181
|
-
if (mapped.length > 0) {
|
|
182
|
-
const concrete = rows.find((row) => row.kind !== 'object_alias') ?? mapped[0];
|
|
183
|
-
return {
|
|
184
|
-
id: concrete?.id ?? null,
|
|
185
|
-
status: 'resolved',
|
|
186
|
-
reason: null,
|
|
187
|
-
strategy: 'proxy_member_exported_object_map',
|
|
188
|
-
candidateCount: rows.length,
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
const scoped = moduleRows(rows, r);
|
|
192
|
-
if (scoped.length === 1 && scoped[0])
|
|
193
|
-
return resolvedSymbol(scoped[0], 'relative_import_path_disambiguated', rows.length, true);
|
|
248
|
+
function ambiguousSymbol(
|
|
249
|
+
strategy: string,
|
|
250
|
+
reason: string,
|
|
251
|
+
candidateCount: number,
|
|
252
|
+
eligibleCandidateCount: number,
|
|
253
|
+
): SymbolCallResolution {
|
|
194
254
|
return {
|
|
195
255
|
id: null,
|
|
196
256
|
status: 'ambiguous',
|
|
197
|
-
reason
|
|
198
|
-
strategy
|
|
199
|
-
candidateCount
|
|
257
|
+
reason,
|
|
258
|
+
strategy,
|
|
259
|
+
candidateCount,
|
|
260
|
+
eligibleCandidateCount,
|
|
261
|
+
candidateSetComplete: true,
|
|
200
262
|
};
|
|
201
263
|
}
|
|
202
264
|
|
|
203
|
-
function
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
265
|
+
function exportedSymbolRows(db: Db, repoId: number, r: SymbolCallFact): SymbolTargetRow[] {
|
|
266
|
+
return symbolTargetRows(db.prepare(`SELECT id,kind,
|
|
267
|
+
source_file sourceFile,evidence_json evidenceJson FROM symbols
|
|
268
|
+
WHERE repo_id=? AND source_file<>? AND exported=1
|
|
269
|
+
AND (exported_name=? OR name=? OR qualified_name=?)
|
|
270
|
+
ORDER BY id`).all(
|
|
271
|
+
repoId, r.sourceFile,
|
|
272
|
+
r.calleeLocalName, r.calleeLocalName, r.calleeLocalName,
|
|
273
|
+
));
|
|
207
274
|
}
|
|
208
275
|
|
|
209
|
-
|
|
276
|
+
const sameFileEligibleRelations = new Set([
|
|
277
|
+
'indexed_local_symbol',
|
|
278
|
+
'indexed_local_symbol_unproven',
|
|
279
|
+
'indexed_this_method',
|
|
280
|
+
]);
|
|
281
|
+
|
|
282
|
+
function eligibleSymbolResolution(
|
|
210
283
|
rows: SymbolTargetRow[],
|
|
211
|
-
|
|
212
|
-
|
|
284
|
+
strategy: string,
|
|
285
|
+
ambiguousReason: string,
|
|
213
286
|
): SymbolCallResolution | undefined {
|
|
214
|
-
if (rows.length ===
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
287
|
+
if (rows.length === 0) return undefined;
|
|
288
|
+
const eligible = rows.filter(executableTarget);
|
|
289
|
+
if (eligible.length === 1 && eligible[0])
|
|
290
|
+
return resolvedSymbol(eligible[0], strategy, rows.length, 1);
|
|
291
|
+
if (eligible.length > 1) return ambiguousSymbol(
|
|
292
|
+
strategy, ambiguousReason, rows.length, eligible.length,
|
|
293
|
+
);
|
|
294
|
+
return unresolvedSymbol(
|
|
295
|
+
strategy, 'symbol_target_has_no_executable_body', rows.length,
|
|
221
296
|
);
|
|
222
|
-
if (rows.length <= 1) return undefined;
|
|
223
|
-
const scoped = isRelativeImportedSymbolCall(r) ? moduleRows(rows, r) : [];
|
|
224
|
-
if (scoped.length === 1 && scoped[0])
|
|
225
|
-
return resolvedSymbol(scoped[0], 'relative_import_path_disambiguated', rows.length, true);
|
|
226
|
-
return {
|
|
227
|
-
id: null,
|
|
228
|
-
status: 'ambiguous',
|
|
229
|
-
reason: 'Multiple exported symbol targets matched exactly',
|
|
230
|
-
strategy: 'exported_exact',
|
|
231
|
-
candidateCount: rows.length,
|
|
232
|
-
};
|
|
233
297
|
}
|
|
234
298
|
|
|
235
|
-
function
|
|
299
|
+
function sameFileResolution(
|
|
236
300
|
db: Db,
|
|
237
301
|
repoId: number,
|
|
238
302
|
r: SymbolCallFact,
|
|
239
303
|
relation: unknown,
|
|
240
304
|
): SymbolCallResolution | undefined {
|
|
241
|
-
if (relation
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
305
|
+
if (!sameFileEligibleRelations.has(String(relation))) return undefined;
|
|
306
|
+
if (relation === 'indexed_local_symbol_unproven')
|
|
307
|
+
return unresolvedSymbol(
|
|
308
|
+
'exact_symbol_match', 'no_local_symbol_target', 0,
|
|
309
|
+
);
|
|
310
|
+
if (relation === 'indexed_local_symbol'
|
|
311
|
+
&& r.evidence.localTargetIdentity !== undefined)
|
|
312
|
+
return exactLocalSymbolResolution(db, repoId, r);
|
|
313
|
+
const rows = symbolTargetRows(db.prepare(`SELECT id,kind,
|
|
314
|
+
source_file sourceFile,evidence_json evidenceJson FROM symbols
|
|
315
|
+
WHERE repo_id=? AND source_file=?
|
|
316
|
+
AND (name=? OR qualified_name=?) ORDER BY id`).all(
|
|
317
|
+
repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName,
|
|
318
|
+
));
|
|
319
|
+
return eligibleSymbolResolution(
|
|
320
|
+
rows, 'same_file_exact', 'multiple_same_file_symbol_targets',
|
|
250
321
|
);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function exactLocalSymbolResolution(
|
|
325
|
+
db: Db,
|
|
326
|
+
repoId: number,
|
|
327
|
+
call: SymbolCallFact,
|
|
328
|
+
): SymbolCallResolution {
|
|
329
|
+
const target = record(call.evidence.localTargetIdentity);
|
|
330
|
+
if (!target
|
|
331
|
+
|| typeof target.sourceFile !== 'string'
|
|
332
|
+
|| typeof target.qualifiedName !== 'string'
|
|
333
|
+
|| typeof target.startOffset !== 'number'
|
|
334
|
+
|| typeof target.endOffset !== 'number')
|
|
335
|
+
return unresolvedSymbol(
|
|
336
|
+
'exact_symbol_match', 'no_local_symbol_target', 0,
|
|
337
|
+
);
|
|
338
|
+
const rows = symbolTargetRows(db.prepare(`SELECT id,kind,
|
|
339
|
+
source_file sourceFile,evidence_json evidenceJson FROM symbols
|
|
340
|
+
WHERE repo_id=? AND source_file=? AND qualified_name=?
|
|
341
|
+
AND start_offset=? AND end_offset=? ORDER BY id`).all(
|
|
342
|
+
repoId, target.sourceFile, target.qualifiedName,
|
|
343
|
+
target.startOffset, target.endOffset,
|
|
344
|
+
));
|
|
345
|
+
return eligibleSymbolResolution(
|
|
346
|
+
rows, 'same_file_exact', 'multiple_same_file_symbol_targets',
|
|
347
|
+
) ?? unresolvedSymbol(
|
|
348
|
+
'exact_symbol_match', 'no_local_symbol_target', 0,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function exportedResolution(
|
|
353
|
+
rows: SymbolTargetRow[],
|
|
354
|
+
): SymbolCallResolution | undefined {
|
|
355
|
+
return eligibleSymbolResolution(
|
|
356
|
+
rows, 'exported_exact', 'multiple_exported_symbol_targets',
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function resolveSymbolCallTarget(
|
|
263
361
|
db: Db,
|
|
264
362
|
repoId: number,
|
|
265
363
|
r: SymbolCallFact,
|
|
266
364
|
): SymbolCallResolution {
|
|
267
365
|
const relation = r.evidence.relation;
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
366
|
+
const relative = resolveRelativeSymbolCall(db, repoId, r, relation);
|
|
367
|
+
if (relative) return relative;
|
|
368
|
+
const early = sameFileResolution(db, repoId, r, relation);
|
|
271
369
|
if (early) return early;
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
370
|
+
if (relation === 'package_import_derived_member') return unresolvedSymbol(
|
|
371
|
+
'package_import_derived_member_unsupported',
|
|
372
|
+
'package_derived_member_provenance_insufficient',
|
|
373
|
+
0,
|
|
374
|
+
);
|
|
375
|
+
if (relation === 'package_import') return unresolvedSymbol(
|
|
376
|
+
'package_import_pending',
|
|
377
|
+
'package_resolution_pending',
|
|
378
|
+
0,
|
|
379
|
+
0,
|
|
380
|
+
false,
|
|
381
|
+
);
|
|
382
|
+
const matched = exportedResolution(exportedSymbolRows(db, repoId, r));
|
|
276
383
|
if (matched) return matched;
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
candidateCount: 0,
|
|
283
|
-
};
|
|
284
|
-
return {
|
|
285
|
-
id: null,
|
|
286
|
-
status: 'unresolved',
|
|
287
|
-
reason: 'No local symbol target matched exactly',
|
|
288
|
-
strategy: relation === 'relative_import_proxy_member'
|
|
289
|
-
? 'proxy_member_no_global_name_fallback'
|
|
290
|
-
: 'exact_symbol_match',
|
|
291
|
-
candidateCount: 0,
|
|
292
|
-
};
|
|
384
|
+
return unresolvedSymbol(
|
|
385
|
+
'exact_symbol_match',
|
|
386
|
+
'no_local_symbol_target',
|
|
387
|
+
0,
|
|
388
|
+
);
|
|
293
389
|
}
|
|
294
390
|
|
|
295
391
|
export function insertCalls(
|
|
@@ -308,12 +404,7 @@ function outboundCallInsertStatement(db: Db): Statement {
|
|
|
308
404
|
call_site_end_offset,confidence,unresolved_reason,local_service_name,
|
|
309
405
|
local_service_lookup,alias_chain_json,evidence_json,external_target_kind,
|
|
310
406
|
external_target_id,external_target_label,external_target_dynamic,service_binding_id
|
|
311
|
-
) VALUES(
|
|
312
|
-
?,COALESCE(
|
|
313
|
-
(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1),
|
|
314
|
-
(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1)
|
|
315
|
-
),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
|
|
316
|
-
)`);
|
|
407
|
+
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`);
|
|
317
408
|
}
|
|
318
409
|
|
|
319
410
|
function insertOutboundCall(
|
|
@@ -322,19 +413,20 @@ function insertOutboundCall(
|
|
|
322
413
|
repoId: number,
|
|
323
414
|
call: OutboundCallFact,
|
|
324
415
|
): void {
|
|
416
|
+
const sourceSymbolId = outboundOwnerId(db, repoId, call);
|
|
325
417
|
const binding = resolvePersistedBinding(db, repoId, call);
|
|
326
418
|
const external = externalTargetValues(call.externalTarget);
|
|
327
419
|
const evidence = {
|
|
328
420
|
...(call.evidence ?? {}),
|
|
329
|
-
|
|
421
|
+
serviceBindingReference: binding.reference,
|
|
422
|
+
serviceBindingResolution: binding.resolution,
|
|
330
423
|
};
|
|
331
424
|
stmt.run(
|
|
332
|
-
repoId,
|
|
333
|
-
repoId, call.sourceFile, call.sourceLine, call.sourceLine,
|
|
425
|
+
repoId, sourceSymbolId,
|
|
334
426
|
call.callType, call.method, call.operationPathExpr, call.queryEntity,
|
|
335
427
|
call.eventNameExpr, call.payloadSummary, call.sourceFile, call.sourceLine,
|
|
336
428
|
call.callSiteStartOffset, call.callSiteEndOffset, call.confidence,
|
|
337
|
-
call.unresolvedReason
|
|
429
|
+
call.unresolvedReason,
|
|
338
430
|
call.localServiceName, call.localServiceLookup,
|
|
339
431
|
serializedAliasChain(call.aliasChain),
|
|
340
432
|
JSON.stringify(evidence), external.kind, external.stableId, external.label,
|
|
@@ -359,17 +451,67 @@ function externalTargetValues(
|
|
|
359
451
|
};
|
|
360
452
|
}
|
|
361
453
|
|
|
362
|
-
interface
|
|
454
|
+
interface PersistedBinding {
|
|
363
455
|
id: number;
|
|
364
456
|
symbolId?: number | null;
|
|
365
457
|
variableName: string;
|
|
366
|
-
alias?: string | null;
|
|
367
|
-
aliasExpr?: string | null;
|
|
368
|
-
destinationExpr?: string | null;
|
|
369
|
-
servicePathExpr?: string | null;
|
|
370
458
|
sourceFile: string;
|
|
371
|
-
|
|
372
|
-
|
|
459
|
+
siteStart: number;
|
|
460
|
+
siteEnd: number;
|
|
461
|
+
ownerResolution: string;
|
|
462
|
+
ownerStartOffset: number | null;
|
|
463
|
+
ownerEndOffset: number | null;
|
|
464
|
+
singleHopHelperReturn: boolean;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function outboundOwnerId(
|
|
468
|
+
db: Db,
|
|
469
|
+
repoId: number,
|
|
470
|
+
call: OutboundCallFact,
|
|
471
|
+
): number | null {
|
|
472
|
+
const candidates = persistedCallOwners(
|
|
473
|
+
db, repoId, call.sourceFile,
|
|
474
|
+
call.callSiteStartOffset, call.callSiteEndOffset,
|
|
475
|
+
);
|
|
476
|
+
const selected = selectCallOwner(
|
|
477
|
+
candidates,
|
|
478
|
+
call.callSiteStartOffset ?? -1,
|
|
479
|
+
call.callSiteEndOffset ?? -1,
|
|
480
|
+
call.callType === 'async_subscribe',
|
|
481
|
+
);
|
|
482
|
+
const resolution = call.evidence?.sourceOwnerResolution;
|
|
483
|
+
if (resolution === 'ownerless_file_scope')
|
|
484
|
+
return ownerlessOutboundOwner(call, selected.status);
|
|
485
|
+
return ownedOutboundOwner(call, resolution, selected);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function ownerlessOutboundOwner(
|
|
489
|
+
call: OutboundCallFact,
|
|
490
|
+
status: string,
|
|
491
|
+
): null {
|
|
492
|
+
if (status !== 'none')
|
|
493
|
+
throw preparedCallSnapshotError(
|
|
494
|
+
'outbound_owner_mismatch', 'outbound_call', call,
|
|
495
|
+
);
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function ownedOutboundOwner(
|
|
500
|
+
call: OutboundCallFact,
|
|
501
|
+
resolution: unknown,
|
|
502
|
+
selected: OwnerSelection<PersistedOwnerCandidate>,
|
|
503
|
+
): number {
|
|
504
|
+
if (resolution !== 'owned_exact' || selected.status !== 'resolved'
|
|
505
|
+
|| selected.owner?.qualifiedName !== call.sourceSymbolQualifiedName)
|
|
506
|
+
throw preparedCallSnapshotError(
|
|
507
|
+
'outbound_owner_mismatch', 'outbound_call', call,
|
|
508
|
+
);
|
|
509
|
+
const owner = selected.owner;
|
|
510
|
+
if (!owner)
|
|
511
|
+
throw preparedCallSnapshotError(
|
|
512
|
+
'outbound_owner_mismatch', 'outbound_call', call,
|
|
513
|
+
);
|
|
514
|
+
return owner.id;
|
|
373
515
|
}
|
|
374
516
|
|
|
375
517
|
function resolvePersistedBinding(
|
|
@@ -378,160 +520,177 @@ function resolvePersistedBinding(
|
|
|
378
520
|
call: OutboundCallFact,
|
|
379
521
|
): {
|
|
380
522
|
bindingId: number | null;
|
|
381
|
-
|
|
382
|
-
|
|
523
|
+
reference: NonNullable<OutboundCallFact['serviceBindingReference']>;
|
|
524
|
+
resolution: Record<string, unknown>;
|
|
383
525
|
} {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
unresolvedReason: 'ambiguous_service_binding_candidates',
|
|
400
|
-
evidence: bindingEvidence('ambiguous', prior),
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
if (candidates.length > 0) {
|
|
404
|
-
return {
|
|
405
|
-
bindingId: null,
|
|
406
|
-
unresolvedReason: 'service_binding_declared_after_call',
|
|
407
|
-
evidence: bindingEvidence('rejected_future_binding', candidates),
|
|
408
|
-
};
|
|
409
|
-
}
|
|
526
|
+
const reference = call.serviceBindingReference;
|
|
527
|
+
if (!reference)
|
|
528
|
+
throw preparedCallSnapshotError(
|
|
529
|
+
'binding_reference_missing', 'outbound_call', call,
|
|
530
|
+
);
|
|
531
|
+
if (reference.status !== 'resolved_exact')
|
|
532
|
+
return unresolvedBinding(reference);
|
|
533
|
+
const candidates = exactBindingRows(db, repoId, reference);
|
|
534
|
+
const selected = candidates[0];
|
|
535
|
+
if (candidates.length !== 1 || !selected
|
|
536
|
+
|| selected.variableName !== call.serviceVariableName)
|
|
537
|
+
throw preparedCallSnapshotError(
|
|
538
|
+
'binding_reference_mismatch', 'outbound_call', call,
|
|
539
|
+
);
|
|
540
|
+
assertResolvedBindingProof(repoId, call, selected, reference);
|
|
410
541
|
return {
|
|
411
|
-
bindingId:
|
|
412
|
-
|
|
542
|
+
bindingId: selected.id,
|
|
543
|
+
reference,
|
|
544
|
+
resolution: {
|
|
545
|
+
status: 'selected_exact',
|
|
546
|
+
selectedBindingId: selected.id,
|
|
547
|
+
candidateCount: 1,
|
|
548
|
+
},
|
|
413
549
|
};
|
|
414
550
|
}
|
|
415
551
|
|
|
416
|
-
function
|
|
417
|
-
db: Db,
|
|
552
|
+
function bindingProofCall(
|
|
418
553
|
repoId: number,
|
|
419
554
|
call: OutboundCallFact,
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
AND (? IS NULL OR symbol_id IS NULL OR symbol_id=?)
|
|
429
|
-
ORDER BY source_line,id
|
|
430
|
-
`).all(
|
|
555
|
+
bindingId: number,
|
|
556
|
+
): BindingProofCall {
|
|
557
|
+
if (call.callSiteStartOffset === undefined
|
|
558
|
+
|| call.callSiteEndOffset === undefined)
|
|
559
|
+
throw preparedCallSnapshotError(
|
|
560
|
+
'binding_lexical_proof_invalid', 'outbound_call', call,
|
|
561
|
+
);
|
|
562
|
+
return {
|
|
431
563
|
repoId,
|
|
432
|
-
|
|
433
|
-
call.
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (typeof row.id !== 'number' || typeof row.variableName !== 'string'
|
|
439
|
-
|| typeof row.sourceFile !== 'string' || typeof row.sourceLine !== 'number')
|
|
440
|
-
return [];
|
|
441
|
-
return [{
|
|
442
|
-
id: row.id,
|
|
443
|
-
symbolId: nullableNumber(row.symbolId),
|
|
444
|
-
variableName: row.variableName,
|
|
445
|
-
alias: nullableString(row.alias),
|
|
446
|
-
aliasExpr: nullableString(row.aliasExpr),
|
|
447
|
-
destinationExpr: nullableString(row.destinationExpr),
|
|
448
|
-
servicePathExpr: nullableString(row.servicePathExpr),
|
|
449
|
-
sourceFile: row.sourceFile,
|
|
450
|
-
sourceLine: row.sourceLine,
|
|
451
|
-
helperChainJson: nullableString(row.helperChainJson),
|
|
452
|
-
}];
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
function nullableString(value: unknown): string | null | undefined {
|
|
457
|
-
return value === null || typeof value === 'string' ? value : undefined;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function nullableNumber(value: unknown): number | null | undefined {
|
|
461
|
-
return value === null || typeof value === 'number' ? value : undefined;
|
|
564
|
+
bindingId,
|
|
565
|
+
variableName: call.serviceVariableName,
|
|
566
|
+
sourceFile: call.sourceFile,
|
|
567
|
+
startOffset: call.callSiteStartOffset,
|
|
568
|
+
endOffset: call.callSiteEndOffset,
|
|
569
|
+
};
|
|
462
570
|
}
|
|
463
571
|
|
|
464
|
-
function
|
|
465
|
-
db: Db,
|
|
572
|
+
function bindingProofTarget(
|
|
466
573
|
repoId: number,
|
|
467
|
-
|
|
468
|
-
):
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
WHERE repo_id=? AND source_file=?
|
|
472
|
-
AND ((? IS NOT NULL AND qualified_name=?)
|
|
473
|
-
OR (start_line<=? AND end_line>=?))
|
|
474
|
-
ORDER BY CASE WHEN qualified_name=? THEN 0 ELSE 1 END,
|
|
475
|
-
(end_line-start_line),id
|
|
476
|
-
LIMIT 1
|
|
477
|
-
`).get(
|
|
574
|
+
binding: PersistedBinding,
|
|
575
|
+
): BindingProofTarget {
|
|
576
|
+
return {
|
|
577
|
+
id: binding.id,
|
|
478
578
|
repoId,
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
579
|
+
symbolId: binding.symbolId ?? null,
|
|
580
|
+
variableName: binding.variableName,
|
|
581
|
+
sourceFile: binding.sourceFile,
|
|
582
|
+
startOffset: binding.siteStart,
|
|
583
|
+
endOffset: binding.siteEnd,
|
|
584
|
+
ownerResolution: binding.ownerResolution,
|
|
585
|
+
ownerStartOffset: binding.ownerStartOffset,
|
|
586
|
+
ownerEndOffset: binding.ownerEndOffset,
|
|
587
|
+
singleHopHelperReturn: binding.singleHopHelperReturn,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function assertResolvedBindingProof(
|
|
592
|
+
repoId: number,
|
|
593
|
+
call: OutboundCallFact,
|
|
594
|
+
binding: PersistedBinding,
|
|
595
|
+
reference: NonNullable<OutboundCallFact['serviceBindingReference']>,
|
|
596
|
+
): void {
|
|
597
|
+
const valid = resolvedBindingReferenceProofValid(
|
|
598
|
+
bindingProofCall(repoId, call, binding.id),
|
|
599
|
+
reference,
|
|
600
|
+
bindingProofTarget(repoId, binding),
|
|
485
601
|
);
|
|
486
|
-
|
|
602
|
+
if (!valid)
|
|
603
|
+
throw preparedCallSnapshotError(
|
|
604
|
+
'binding_lexical_proof_invalid', 'outbound_call', call,
|
|
605
|
+
);
|
|
487
606
|
}
|
|
488
607
|
|
|
489
|
-
function
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|| left.sourceFile.localeCompare(right.sourceFile)
|
|
497
|
-
|| left.sourceLine - right.sourceLine
|
|
498
|
-
|| left.id - right.id);
|
|
608
|
+
function unresolvedBinding(
|
|
609
|
+
reference: NonNullable<OutboundCallFact['serviceBindingReference']>,
|
|
610
|
+
): {
|
|
611
|
+
bindingId: null;
|
|
612
|
+
reference: NonNullable<OutboundCallFact['serviceBindingReference']>;
|
|
613
|
+
resolution: Record<string, unknown>;
|
|
614
|
+
} {
|
|
499
615
|
return {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
candidates: projection.items.map((candidate) => ({
|
|
507
|
-
bindingId: candidate.id,
|
|
508
|
-
symbolId: candidate.symbolId,
|
|
509
|
-
variableName: candidate.variableName,
|
|
510
|
-
alias: candidate.alias,
|
|
511
|
-
aliasExpr: candidate.aliasExpr,
|
|
512
|
-
destinationExpr: candidate.destinationExpr,
|
|
513
|
-
servicePathExpr: candidate.servicePathExpr,
|
|
514
|
-
sourceFile: candidate.sourceFile,
|
|
515
|
-
sourceLine: candidate.sourceLine,
|
|
516
|
-
helperChain: parseBindingChain(candidate.helperChainJson),
|
|
517
|
-
})),
|
|
616
|
+
bindingId: null,
|
|
617
|
+
reference,
|
|
618
|
+
resolution: {
|
|
619
|
+
status: reference.status,
|
|
620
|
+
candidateCount: 0,
|
|
621
|
+
},
|
|
518
622
|
};
|
|
519
623
|
}
|
|
520
624
|
|
|
521
|
-
function
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
625
|
+
function exactBindingRows(
|
|
626
|
+
db: Db,
|
|
627
|
+
repoId: number,
|
|
628
|
+
reference: NonNullable<OutboundCallFact['serviceBindingReference']>,
|
|
629
|
+
): PersistedBinding[] {
|
|
630
|
+
const rows = db.prepare(`SELECT binding.id,binding.symbol_id symbolId,
|
|
631
|
+
binding.variable_name variableName,binding.source_file sourceFile,
|
|
632
|
+
binding.binding_site_start_offset siteStart,
|
|
633
|
+
binding.binding_site_end_offset siteEnd,
|
|
634
|
+
binding.owner_resolution ownerResolution,
|
|
635
|
+
binding.helper_chain_json helperChainJson,
|
|
636
|
+
owner.start_offset ownerStartOffset,owner.end_offset ownerEndOffset
|
|
637
|
+
FROM service_bindings binding
|
|
638
|
+
LEFT JOIN symbols owner ON owner.id=binding.symbol_id
|
|
639
|
+
WHERE binding.repo_id=? AND binding.source_file=?
|
|
640
|
+
AND binding.variable_name=?
|
|
641
|
+
AND binding.binding_site_start_offset=?
|
|
642
|
+
AND binding.binding_site_end_offset=?`).all(
|
|
643
|
+
repoId,
|
|
644
|
+
reference.bindingSourceFile,
|
|
645
|
+
reference.variableName,
|
|
646
|
+
reference.bindingSiteStartOffset,
|
|
647
|
+
reference.bindingSiteEndOffset,
|
|
648
|
+
);
|
|
649
|
+
return rows.flatMap(persistedBindingRow);
|
|
528
650
|
}
|
|
529
651
|
|
|
530
|
-
function
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
652
|
+
function persistedBindingRow(
|
|
653
|
+
row: Record<string, unknown>,
|
|
654
|
+
): PersistedBinding[] {
|
|
655
|
+
const ownerStartOffset = nullableOffset(row.ownerStartOffset);
|
|
656
|
+
const ownerEndOffset = nullableOffset(row.ownerEndOffset);
|
|
657
|
+
if (!persistedBindingRequiredValid(row)
|
|
658
|
+
|| ownerStartOffset === undefined || ownerEndOffset === undefined) return [];
|
|
659
|
+
return [{
|
|
660
|
+
id: row.id,
|
|
661
|
+
symbolId: row.symbolId === null || typeof row.symbolId === 'number'
|
|
662
|
+
? row.symbolId : undefined,
|
|
663
|
+
variableName: row.variableName,
|
|
664
|
+
sourceFile: row.sourceFile,
|
|
665
|
+
siteStart: row.siteStart,
|
|
666
|
+
siteEnd: row.siteEnd,
|
|
667
|
+
ownerResolution: row.ownerResolution,
|
|
668
|
+
ownerStartOffset,
|
|
669
|
+
ownerEndOffset,
|
|
670
|
+
singleHopHelperReturn: hasSingleHopHelperReturn(row.helperChainJson),
|
|
671
|
+
}];
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function nullableOffset(value: unknown): number | null | undefined {
|
|
675
|
+
return value === null || typeof value === 'number' ? value : undefined;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function persistedBindingRequiredValid(
|
|
679
|
+
row: Record<string, unknown>,
|
|
680
|
+
): row is Record<string, unknown> & {
|
|
681
|
+
id: number;
|
|
682
|
+
variableName: string;
|
|
683
|
+
sourceFile: string;
|
|
684
|
+
siteStart: number;
|
|
685
|
+
siteEnd: number;
|
|
686
|
+
ownerResolution: string;
|
|
687
|
+
} {
|
|
688
|
+
return [
|
|
689
|
+
typeof row.id === 'number',
|
|
690
|
+
typeof row.variableName === 'string',
|
|
691
|
+
typeof row.sourceFile === 'string',
|
|
692
|
+
typeof row.siteStart === 'number',
|
|
693
|
+
typeof row.siteEnd === 'number',
|
|
694
|
+
typeof row.ownerResolution === 'string',
|
|
695
|
+
].every(Boolean);
|
|
537
696
|
}
|