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