@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
package/dist/cli.js
CHANGED
|
@@ -2,26 +2,26 @@
|
|
|
2
2
|
import {
|
|
3
3
|
ANALYZER_VERSION,
|
|
4
4
|
VERSION,
|
|
5
|
+
analyzePackagePublicSurface,
|
|
6
|
+
bindingSite,
|
|
5
7
|
classifyODataPathIntent,
|
|
6
8
|
classifyOutboundCallsInSource,
|
|
7
|
-
|
|
9
|
+
collectSymbolImportBindings,
|
|
8
10
|
compactTrace,
|
|
9
11
|
containsSupportedOutboundCall,
|
|
12
|
+
createBindingLexicalIndex,
|
|
13
|
+
derivedMemberImportReference,
|
|
10
14
|
discoverRepositories,
|
|
15
|
+
executableSymbolCandidates,
|
|
11
16
|
factLifecycleDiagnostic,
|
|
12
|
-
|
|
13
|
-
handlerMethodIsExecutable,
|
|
17
|
+
identifierMatchesDeclaration,
|
|
14
18
|
implementationHintSuggestions,
|
|
15
|
-
insertBindings,
|
|
16
19
|
insertCalls,
|
|
17
|
-
insertExecutableSymbols,
|
|
18
|
-
insertHandler,
|
|
19
|
-
insertRegistrations,
|
|
20
|
-
insertRequires,
|
|
21
|
-
insertService,
|
|
22
20
|
insertSymbolCalls,
|
|
21
|
+
lexicalIdentifierDeclaration,
|
|
22
|
+
lexicalIdentifierDeclarations,
|
|
23
|
+
lexicalScopeChain,
|
|
23
24
|
linkWorkspace,
|
|
24
|
-
listRepositories,
|
|
25
25
|
loadPackageJsonSnapshot,
|
|
26
26
|
loadRepositorySourceContext,
|
|
27
27
|
migrate,
|
|
@@ -32,17 +32,19 @@ import {
|
|
|
32
32
|
parseHandlerRegistrations,
|
|
33
33
|
parseImplementationHint,
|
|
34
34
|
parseOutboundCalls,
|
|
35
|
+
parsePackageImportReference,
|
|
35
36
|
parsePackageJson,
|
|
36
37
|
parseServiceBindings,
|
|
37
38
|
parseVars,
|
|
38
39
|
projectBoundedInOrder,
|
|
39
40
|
redactValue,
|
|
40
|
-
|
|
41
|
+
selectCallOwner,
|
|
42
|
+
selectVisibleBinding,
|
|
41
43
|
selectorRepoAmbiguousDiagnostic,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
} from "./chunk-
|
|
44
|
+
stableLocalValueReference,
|
|
45
|
+
symbolImportReference,
|
|
46
|
+
trace
|
|
47
|
+
} from "./chunk-AEM4JY22.js";
|
|
46
48
|
|
|
47
49
|
// src/cli.ts
|
|
48
50
|
import { Command, Option } from "commander";
|
|
@@ -209,6 +211,345 @@ function openReadOnlyDatabase(dbPath) {
|
|
|
209
211
|
return openDatabase(dbPath, { readonly: true, migrate: false });
|
|
210
212
|
}
|
|
211
213
|
|
|
214
|
+
// src/db/repositories.ts
|
|
215
|
+
function initialPackagePublicSurface(packageName2) {
|
|
216
|
+
return JSON.stringify({
|
|
217
|
+
schema: "service-flow/package-public-surface@1",
|
|
218
|
+
status: packageName2 ? "incomplete" : "not_applicable",
|
|
219
|
+
reason: packageName2 ? "package_surface_pending_index" : null,
|
|
220
|
+
recordCap: 256,
|
|
221
|
+
total: 0,
|
|
222
|
+
shown: 0,
|
|
223
|
+
omitted: 0,
|
|
224
|
+
packageName: packageName2 ?? null,
|
|
225
|
+
exportsPresent: false,
|
|
226
|
+
exportsAuthoritative: false,
|
|
227
|
+
main: null,
|
|
228
|
+
module: null,
|
|
229
|
+
entries: [],
|
|
230
|
+
scopes: []
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function upsertWorkspace(db, rootPath, dbPath) {
|
|
234
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
235
|
+
db.prepare(
|
|
236
|
+
"INSERT INTO workspaces(root_path,db_path,created_at,updated_at) VALUES(?,?,?,?) ON CONFLICT(root_path) DO UPDATE SET db_path=excluded.db_path,updated_at=excluded.updated_at"
|
|
237
|
+
).run(rootPath, dbPath, now, now);
|
|
238
|
+
return Number(
|
|
239
|
+
db.prepare("SELECT id FROM workspaces WHERE root_path=?").get(rootPath)?.id
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
function getWorkspace(db, rootPath) {
|
|
243
|
+
return db.prepare("SELECT * FROM workspaces WHERE root_path=?").get(rootPath);
|
|
244
|
+
}
|
|
245
|
+
function upsertRepository(db, workspaceId, r) {
|
|
246
|
+
db.prepare(
|
|
247
|
+
`INSERT INTO repositories(workspace_id,name,absolute_path,relative_path,
|
|
248
|
+
package_name,package_version,dependencies_json,
|
|
249
|
+
package_public_surface_json,kind,is_git_repo)
|
|
250
|
+
VALUES(?,?,?,?,?,?,?,?,?,?)
|
|
251
|
+
ON CONFLICT(workspace_id,absolute_path) DO UPDATE SET
|
|
252
|
+
name=excluded.name,relative_path=excluded.relative_path,
|
|
253
|
+
package_name=excluded.package_name,
|
|
254
|
+
package_version=excluded.package_version,
|
|
255
|
+
dependencies_json=excluded.dependencies_json,kind=excluded.kind`
|
|
256
|
+
).run(
|
|
257
|
+
workspaceId,
|
|
258
|
+
r.name,
|
|
259
|
+
r.absolutePath,
|
|
260
|
+
r.relativePath,
|
|
261
|
+
r.packageName,
|
|
262
|
+
r.packageVersion,
|
|
263
|
+
JSON.stringify(r.dependencies ?? {}),
|
|
264
|
+
initialPackagePublicSurface(r.packageName),
|
|
265
|
+
r.kind ?? "unknown",
|
|
266
|
+
r.isGitRepo ? 1 : 0
|
|
267
|
+
);
|
|
268
|
+
return Number(
|
|
269
|
+
db.prepare(
|
|
270
|
+
"SELECT id FROM repositories WHERE workspace_id=? AND absolute_path=?"
|
|
271
|
+
).get(workspaceId, r.absolutePath)?.id
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
function listRepositories(db, workspaceId) {
|
|
275
|
+
return db.prepare("SELECT * FROM repositories WHERE (? IS NULL OR workspace_id=?) ORDER BY name,absolute_path,id").all(workspaceId, workspaceId);
|
|
276
|
+
}
|
|
277
|
+
function reposByName(db, name, workspaceId) {
|
|
278
|
+
return db.prepare(`SELECT * FROM repositories
|
|
279
|
+
WHERE (? IS NULL OR workspace_id=?) AND (name=? OR package_name=?)
|
|
280
|
+
ORDER BY name,absolute_path,id`).all(workspaceId, workspaceId, name, name);
|
|
281
|
+
}
|
|
282
|
+
function clearRepoFacts(db, repoId) {
|
|
283
|
+
for (const t of [
|
|
284
|
+
"cds_requires",
|
|
285
|
+
"cds_services",
|
|
286
|
+
"handler_classes",
|
|
287
|
+
"outbound_calls",
|
|
288
|
+
"symbol_calls",
|
|
289
|
+
"handler_registrations",
|
|
290
|
+
"service_bindings",
|
|
291
|
+
"symbols",
|
|
292
|
+
"diagnostics",
|
|
293
|
+
"files"
|
|
294
|
+
])
|
|
295
|
+
db.prepare(`DELETE FROM ${t} WHERE repo_id=?`).run(repoId);
|
|
296
|
+
db.prepare("DELETE FROM search_index WHERE repo=?").run(String(repoId));
|
|
297
|
+
}
|
|
298
|
+
function insertRequires(db, repoId, rows) {
|
|
299
|
+
const stmt = db.prepare(
|
|
300
|
+
"INSERT INTO cds_requires(repo_id,alias,kind,model,destination,service_path,request_timeout,raw_json) VALUES(?,?,?,?,?,?,?,?)"
|
|
301
|
+
);
|
|
302
|
+
for (const r of rows)
|
|
303
|
+
stmt.run(
|
|
304
|
+
repoId,
|
|
305
|
+
r.alias,
|
|
306
|
+
r.kind,
|
|
307
|
+
r.model,
|
|
308
|
+
r.destination,
|
|
309
|
+
r.servicePath,
|
|
310
|
+
r.requestTimeout,
|
|
311
|
+
r.rawJson
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
function insertService(db, repoId, s) {
|
|
315
|
+
const id = Number(
|
|
316
|
+
db.prepare(
|
|
317
|
+
"INSERT INTO cds_services(repo_id,namespace,service_name,qualified_name,service_path,is_extend,source_file,source_line,extension_local_ref,extension_imported_symbol,extension_local_alias,extension_module_specifier,extension_import_kind) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?) RETURNING id"
|
|
318
|
+
).get(
|
|
319
|
+
repoId,
|
|
320
|
+
s.namespace,
|
|
321
|
+
s.serviceName,
|
|
322
|
+
s.qualifiedName,
|
|
323
|
+
s.servicePath,
|
|
324
|
+
s.isExtend ? 1 : 0,
|
|
325
|
+
s.sourceFile,
|
|
326
|
+
s.sourceLine,
|
|
327
|
+
s.extension?.localReference,
|
|
328
|
+
s.extension?.importedSymbol,
|
|
329
|
+
s.extension?.localAlias,
|
|
330
|
+
s.extension?.moduleSpecifier,
|
|
331
|
+
s.extension?.importKind
|
|
332
|
+
)?.id
|
|
333
|
+
);
|
|
334
|
+
const stmt = db.prepare(
|
|
335
|
+
"INSERT INTO cds_operations(service_id,operation_type,operation_name,operation_path,params_json,return_type,source_file,source_line,provenance,base_operation_id) VALUES(?,?,?,?,?,?,?,?,?,?)"
|
|
336
|
+
);
|
|
337
|
+
db.prepare(
|
|
338
|
+
"INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
|
|
339
|
+
).run("service", s.qualifiedName, s.servicePath, String(repoId));
|
|
340
|
+
for (const o of s.operations)
|
|
341
|
+
stmt.run(
|
|
342
|
+
id,
|
|
343
|
+
o.operationType,
|
|
344
|
+
o.operationName,
|
|
345
|
+
o.operationPath,
|
|
346
|
+
o.paramsJson,
|
|
347
|
+
o.returnType,
|
|
348
|
+
o.sourceFile,
|
|
349
|
+
o.sourceLine,
|
|
350
|
+
o.provenance ?? "direct",
|
|
351
|
+
o.baseOperationId ?? null
|
|
352
|
+
);
|
|
353
|
+
const search = db.prepare(
|
|
354
|
+
"INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
|
|
355
|
+
);
|
|
356
|
+
for (const o of s.operations)
|
|
357
|
+
search.run("operation", o.operationName, o.operationPath, String(repoId));
|
|
358
|
+
return id;
|
|
359
|
+
}
|
|
360
|
+
function insertHandler(db, repoId, h) {
|
|
361
|
+
const sid = insertHandlerClassSymbol(db, repoId, h);
|
|
362
|
+
const hid = Number(
|
|
363
|
+
db.prepare(
|
|
364
|
+
"INSERT INTO handler_classes(repo_id,symbol_id,class_name,source_file,source_line) VALUES(?,?,?,?,?) RETURNING id"
|
|
365
|
+
).get(repoId, sid, h.className, h.sourceFile, h.sourceLine)?.id
|
|
366
|
+
);
|
|
367
|
+
const stmt = db.prepare(
|
|
368
|
+
"INSERT INTO handler_methods(handler_class_id,method_name,decorator_kind,decorator_value,decorator_raw_expression,decorator_resolution_json,source_file,source_line) VALUES(?,?,?,?,?,?,?,?)"
|
|
369
|
+
);
|
|
370
|
+
for (const m of h.methods)
|
|
371
|
+
stmt.run(
|
|
372
|
+
hid,
|
|
373
|
+
m.methodName,
|
|
374
|
+
m.decoratorKind,
|
|
375
|
+
m.decoratorValue,
|
|
376
|
+
m.decoratorRawExpression,
|
|
377
|
+
JSON.stringify(canonicalHandlerMethodResolution(m)),
|
|
378
|
+
m.sourceFile,
|
|
379
|
+
m.sourceLine
|
|
380
|
+
);
|
|
381
|
+
insertHandlerIndexDiagnostic(db, repoId, h);
|
|
382
|
+
return hid;
|
|
383
|
+
}
|
|
384
|
+
function insertHandlerClassSymbol(db, repoId, h) {
|
|
385
|
+
const classEvidence = {
|
|
386
|
+
hasHandlerDecorator: h.hasHandlerDecorator ?? false,
|
|
387
|
+
classDecoratorNames: h.classDecoratorNames ?? [],
|
|
388
|
+
observedDecoratorNames: h.observedDecoratorNames ?? [],
|
|
389
|
+
unsupportedDecoratorNames: h.unsupportedDecoratorNames ?? [],
|
|
390
|
+
unsupportedMethods: h.methods.filter((method) => !handlerMethodIsExecutable(method)).map((method) => ({
|
|
391
|
+
methodName: method.methodName,
|
|
392
|
+
decoratorKind: method.decoratorKind,
|
|
393
|
+
sourceFile: method.sourceFile,
|
|
394
|
+
sourceLine: method.sourceLine,
|
|
395
|
+
reason: method.decoratorResolution.unresolvedReason
|
|
396
|
+
}))
|
|
397
|
+
};
|
|
398
|
+
return Number(
|
|
399
|
+
db.prepare(
|
|
400
|
+
"INSERT INTO symbols(repo_id,kind,name,qualified_name,exported,start_line,end_line,source_file,evidence_json) VALUES(?,?,?,?,?,?,?,?,?) RETURNING id"
|
|
401
|
+
).get(
|
|
402
|
+
repoId,
|
|
403
|
+
"class",
|
|
404
|
+
h.className,
|
|
405
|
+
h.className,
|
|
406
|
+
1,
|
|
407
|
+
h.sourceLine,
|
|
408
|
+
h.sourceLine,
|
|
409
|
+
h.sourceFile,
|
|
410
|
+
JSON.stringify(classEvidence)
|
|
411
|
+
)?.id
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
function insertHandlerIndexDiagnostic(db, repoId, h) {
|
|
415
|
+
if (!h.hasHandlerDecorator) return;
|
|
416
|
+
const hasExecutable = h.methods.some(handlerMethodIsExecutable);
|
|
417
|
+
const unsupported = h.methods.filter((method) => !handlerMethodIsExecutable(method));
|
|
418
|
+
if (hasExecutable && unsupported.length === 0) return;
|
|
419
|
+
const code = hasExecutable ? "handler_decorators_not_indexed" : "handler_methods_not_indexed";
|
|
420
|
+
const names = unsupported.map((method) => method.decoratorKind).sort();
|
|
421
|
+
const detail = names.length > 0 ? ` Unsupported decorators: ${[...new Set(names)].join(", ")}.` : "";
|
|
422
|
+
db.prepare(
|
|
423
|
+
"INSERT INTO diagnostics(repo_id,severity,code,message,source_file,source_line) VALUES(?,?,?,?,?,?)"
|
|
424
|
+
).run(
|
|
425
|
+
repoId,
|
|
426
|
+
"warning",
|
|
427
|
+
code,
|
|
428
|
+
hasExecutable ? `Handler class ${h.className} contains methods that were not indexed.${detail}` : `Handler class ${h.className} has no indexed executable methods; use a supported CAP handler decorator and re-index.${detail}`,
|
|
429
|
+
h.sourceFile,
|
|
430
|
+
h.sourceLine
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
function canonicalHandlerMethodResolution(method) {
|
|
434
|
+
const handlerKind = method.handlerKind ?? method.decoratorResolution.handlerKind ?? legacyHandlerKind(method.decoratorKind);
|
|
435
|
+
const executable = method.executable ?? method.decoratorResolution.executable ?? (handlerKind === "operation" || handlerKind === "event" || handlerKind === "entity_lifecycle");
|
|
436
|
+
return {
|
|
437
|
+
...method.decoratorResolution,
|
|
438
|
+
handlerKind,
|
|
439
|
+
executable,
|
|
440
|
+
lifecyclePhase: method.lifecyclePhase ?? method.decoratorResolution.lifecyclePhase,
|
|
441
|
+
lifecycleEvent: method.lifecycleEvent ?? method.decoratorResolution.lifecycleEvent
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function handlerMethodIsExecutable(method) {
|
|
445
|
+
return canonicalHandlerMethodResolution(method).executable === true;
|
|
446
|
+
}
|
|
447
|
+
function legacyHandlerKind(kind) {
|
|
448
|
+
if (kind === "Event") return "event";
|
|
449
|
+
if (["Action", "Func", "On"].includes(kind)) return "operation";
|
|
450
|
+
return "unsupported_decorator";
|
|
451
|
+
}
|
|
452
|
+
function insertRegistrations(db, repoId, rows) {
|
|
453
|
+
const stmt = db.prepare(
|
|
454
|
+
"INSERT INTO handler_registrations(repo_id,handler_class_id,class_name,import_source,registration_file,registration_line,registration_kind,confidence) VALUES(?,?,?,?,?,?,?,?)"
|
|
455
|
+
);
|
|
456
|
+
for (const r of rows) {
|
|
457
|
+
const handlerClass = r.className ? db.prepare(
|
|
458
|
+
"SELECT id FROM handler_classes WHERE repo_id=? AND class_name=? ORDER BY id"
|
|
459
|
+
).all(repoId, r.className) : [];
|
|
460
|
+
stmt.run(
|
|
461
|
+
repoId,
|
|
462
|
+
handlerClass.length === 1 ? handlerClass[0]?.id : null,
|
|
463
|
+
r.className,
|
|
464
|
+
r.importSource,
|
|
465
|
+
r.registrationFile,
|
|
466
|
+
r.registrationLine,
|
|
467
|
+
r.registrationKind,
|
|
468
|
+
r.confidence
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function insertBindings(db, repoId, rows) {
|
|
473
|
+
assertUniquePreparedBindingSites(rows);
|
|
474
|
+
const stmt = db.prepare(
|
|
475
|
+
"INSERT INTO service_bindings(repo_id,symbol_id,variable_name,alias,alias_expr,destination_expr,service_path_expr,is_dynamic,placeholders_json,source_file,source_line,binding_site_start_offset,binding_site_end_offset,owner_resolution,helper_chain_json) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
|
|
476
|
+
);
|
|
477
|
+
for (const r of rows)
|
|
478
|
+
stmt.run(
|
|
479
|
+
repoId,
|
|
480
|
+
bindingOwnerId(db, repoId, r),
|
|
481
|
+
r.variableName,
|
|
482
|
+
r.alias,
|
|
483
|
+
r.aliasExpr,
|
|
484
|
+
r.destinationExpr,
|
|
485
|
+
r.servicePathExpr,
|
|
486
|
+
r.isDynamic ? 1 : 0,
|
|
487
|
+
JSON.stringify(r.placeholders),
|
|
488
|
+
r.sourceFile,
|
|
489
|
+
r.sourceLine,
|
|
490
|
+
r.bindingSiteStartOffset,
|
|
491
|
+
r.bindingSiteEndOffset,
|
|
492
|
+
r.ownerResolution,
|
|
493
|
+
r.helperChain ? JSON.stringify(r.helperChain) : null
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
function persistedOwnerCandidates(db, repoId, fact) {
|
|
497
|
+
const rows = db.prepare(`SELECT id,kind,qualified_name qualifiedName,
|
|
498
|
+
start_offset startOffset,end_offset endOffset FROM symbols
|
|
499
|
+
WHERE repo_id=? AND source_file=? AND start_offset<=? AND end_offset>=?`).all(
|
|
500
|
+
repoId,
|
|
501
|
+
fact.sourceFile,
|
|
502
|
+
fact.bindingSiteStartOffset,
|
|
503
|
+
fact.bindingSiteEndOffset
|
|
504
|
+
);
|
|
505
|
+
return rows.flatMap((row) => typeof row.id === "number" && typeof row.kind === "string" && typeof row.qualifiedName === "string" && typeof row.startOffset === "number" && typeof row.endOffset === "number" ? [{
|
|
506
|
+
id: row.id,
|
|
507
|
+
kind: row.kind,
|
|
508
|
+
qualifiedName: row.qualifiedName,
|
|
509
|
+
startOffset: row.startOffset,
|
|
510
|
+
endOffset: row.endOffset
|
|
511
|
+
}] : []);
|
|
512
|
+
}
|
|
513
|
+
function bindingOwnerId(db, repoId, fact) {
|
|
514
|
+
const candidates = persistedOwnerCandidates(db, repoId, fact);
|
|
515
|
+
const selected = selectCallOwner(
|
|
516
|
+
candidates,
|
|
517
|
+
fact.bindingSiteStartOffset ?? -1,
|
|
518
|
+
fact.bindingSiteEndOffset ?? -1
|
|
519
|
+
);
|
|
520
|
+
if (fact.ownerResolution === "ownerless_file_scope") {
|
|
521
|
+
if (selected.status !== "none")
|
|
522
|
+
throw new Error("invalid_prepared_repository_snapshot:binding_owner_mismatch");
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
if (fact.ownerResolution !== "owned_exact" || selected.status !== "resolved" || selected.owner?.qualifiedName !== fact.sourceSymbolQualifiedName)
|
|
526
|
+
throw new Error("invalid_prepared_repository_snapshot:binding_owner_mismatch");
|
|
527
|
+
const owner = selected.owner;
|
|
528
|
+
if (!owner)
|
|
529
|
+
throw new Error("invalid_prepared_repository_snapshot:binding_owner_mismatch");
|
|
530
|
+
return owner.id;
|
|
531
|
+
}
|
|
532
|
+
function assertUniquePreparedBindingSites(rows) {
|
|
533
|
+
const seen = /* @__PURE__ */ new Set();
|
|
534
|
+
for (const row of rows) {
|
|
535
|
+
if (row.bindingSiteStartOffset === void 0 || row.bindingSiteEndOffset === void 0)
|
|
536
|
+
throw new Error("invalid_prepared_repository_snapshot:binding_site_missing");
|
|
537
|
+
const key = [
|
|
538
|
+
row.sourceFile,
|
|
539
|
+
row.variableName,
|
|
540
|
+
row.bindingSiteStartOffset,
|
|
541
|
+
row.bindingSiteEndOffset
|
|
542
|
+
].join("\0");
|
|
543
|
+
if (seen.has(key))
|
|
544
|
+
throw new Error("invalid_prepared_repository_snapshot:duplicate_service_binding_site");
|
|
545
|
+
seen.add(key);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function insertExecutableSymbols(db, repoId, rows) {
|
|
549
|
+
const stmt = db.prepare("INSERT INTO symbols(repo_id,file_id,kind,name,qualified_name,exported,start_line,end_line,start_offset,end_offset,source_file,exported_name,evidence_json) VALUES(?,(SELECT id FROM files WHERE repo_id=? AND relative_path=?),?,?,?,?,?,?,?,?,?,?,?)");
|
|
550
|
+
for (const r of rows) stmt.run(repoId, repoId, r.sourceFile, r.kind, r.localName, r.qualifiedName, r.exported ? 1 : 0, r.startLine, r.endLine, r.startOffset, r.endOffset, r.sourceFile, r.exportedName, r.importExportEvidence ? JSON.stringify(r.importExportEvidence) : null);
|
|
551
|
+
}
|
|
552
|
+
|
|
212
553
|
// src/discovery/classify-repository.ts
|
|
213
554
|
import fs3 from "fs/promises";
|
|
214
555
|
import path3 from "path";
|
|
@@ -258,91 +599,1039 @@ import path5 from "path";
|
|
|
258
599
|
// src/parsers/symbol-parser.ts
|
|
259
600
|
import fs4 from "fs/promises";
|
|
260
601
|
import path4 from "path";
|
|
602
|
+
import ts8 from "typescript";
|
|
603
|
+
|
|
604
|
+
// src/parsers/005-event-subscription-facts.ts
|
|
605
|
+
import ts2 from "typescript";
|
|
606
|
+
|
|
607
|
+
// src/parsers/016-local-symbol-reference.ts
|
|
261
608
|
import ts from "typescript";
|
|
262
|
-
function
|
|
263
|
-
|
|
609
|
+
function functionTargetNode(declaration) {
|
|
610
|
+
const parent = declaration.parent;
|
|
611
|
+
if (ts.isFunctionDeclaration(parent)) return parent;
|
|
612
|
+
if (!ts.isVariableDeclaration(parent)) return void 0;
|
|
613
|
+
const initializer = parent.initializer;
|
|
614
|
+
return initializer && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) ? initializer : void 0;
|
|
615
|
+
}
|
|
616
|
+
function declarationKey(identifier) {
|
|
617
|
+
return `${identifier.getStart(identifier.getSourceFile())}:${identifier.getEnd()}`;
|
|
618
|
+
}
|
|
619
|
+
function unwrapAssignmentTarget(expression) {
|
|
620
|
+
if (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression) || ts.isSatisfiesExpression(expression) || ts.isTypeAssertionExpression(expression) || ts.isNonNullExpression(expression))
|
|
621
|
+
return unwrapAssignmentTarget(expression.expression);
|
|
622
|
+
return expression;
|
|
623
|
+
}
|
|
624
|
+
function objectAssignmentTargets(expression) {
|
|
625
|
+
return expression.properties.flatMap((property) => {
|
|
626
|
+
if (ts.isShorthandPropertyAssignment(property)) return [property.name];
|
|
627
|
+
if (ts.isPropertyAssignment(property))
|
|
628
|
+
return assignmentTargets(property.initializer);
|
|
629
|
+
return ts.isSpreadAssignment(property) ? assignmentTargets(property.expression) : [];
|
|
630
|
+
});
|
|
264
631
|
}
|
|
265
|
-
function
|
|
266
|
-
|
|
267
|
-
if (ts.isIdentifier(
|
|
268
|
-
|
|
632
|
+
function assignmentTargets(expression) {
|
|
633
|
+
const target = unwrapAssignmentTarget(expression);
|
|
634
|
+
if (ts.isIdentifier(target)) return [target];
|
|
635
|
+
if (ts.isSpreadElement(target))
|
|
636
|
+
return assignmentTargets(target.expression);
|
|
637
|
+
if (ts.isBinaryExpression(target) && target.operatorToken.kind === ts.SyntaxKind.EqualsToken)
|
|
638
|
+
return assignmentTargets(target.left);
|
|
639
|
+
if (ts.isArrayLiteralExpression(target))
|
|
640
|
+
return target.elements.flatMap((item) => ts.isExpression(item) ? assignmentTargets(item) : []);
|
|
641
|
+
return ts.isObjectLiteralExpression(target) ? objectAssignmentTargets(target) : [];
|
|
642
|
+
}
|
|
643
|
+
function assignmentOperator(kind) {
|
|
644
|
+
return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment;
|
|
645
|
+
}
|
|
646
|
+
function mutationUnary(node) {
|
|
647
|
+
return node.operator === ts.SyntaxKind.PlusPlusToken || node.operator === ts.SyntaxKind.MinusMinusToken;
|
|
648
|
+
}
|
|
649
|
+
function binaryWritesDeclaration(node, matches) {
|
|
650
|
+
if (!ts.isBinaryExpression(node) || !assignmentOperator(node.operatorToken.kind)) return false;
|
|
651
|
+
return assignmentTargets(node.left).some(matches);
|
|
652
|
+
}
|
|
653
|
+
function unaryWritesDeclaration(node, matches) {
|
|
654
|
+
if (!ts.isPrefixUnaryExpression(node) && !ts.isPostfixUnaryExpression(node)) return false;
|
|
655
|
+
return mutationUnary(node) && ts.isIdentifier(node.operand) && matches(node.operand);
|
|
656
|
+
}
|
|
657
|
+
function loopWritesDeclaration(node, matches) {
|
|
658
|
+
if (!ts.isForInStatement(node) && !ts.isForOfStatement(node)) return false;
|
|
659
|
+
return !ts.isVariableDeclarationList(node.initializer) && assignmentTargets(node.initializer).some(matches);
|
|
660
|
+
}
|
|
661
|
+
function declarationWritten(source, declarations) {
|
|
662
|
+
const keys = new Set(declarations.map(declarationKey));
|
|
663
|
+
let written = false;
|
|
664
|
+
const matches = (identifier) => lexicalIdentifierDeclarations(identifier).some((item) => keys.has(declarationKey(item)));
|
|
665
|
+
const visit = (node) => {
|
|
666
|
+
if (written) return;
|
|
667
|
+
written = binaryWritesDeclaration(node, matches) || unaryWritesDeclaration(node, matches) || loopWritesDeclaration(node, matches);
|
|
668
|
+
if (!written) ts.forEachChild(node, visit);
|
|
669
|
+
};
|
|
670
|
+
visit(source);
|
|
671
|
+
return written;
|
|
269
672
|
}
|
|
270
|
-
function
|
|
271
|
-
|
|
673
|
+
function singleImmutableDeclaration(declaration) {
|
|
674
|
+
const parent = declaration?.parent;
|
|
675
|
+
if (parent && ts.isClassDeclaration(parent)) return true;
|
|
676
|
+
return Boolean(parent && ts.isVariableDeclaration(parent) && ts.isVariableDeclarationList(parent.parent) && (parent.parent.flags & ts.NodeFlags.Const) !== 0);
|
|
272
677
|
}
|
|
273
|
-
function
|
|
274
|
-
|
|
678
|
+
function immutableDeclarationSet(declarations, source) {
|
|
679
|
+
if (declarations.length === 0 || declarationWritten(source, declarations)) return false;
|
|
680
|
+
if (declarations.every((item) => ts.isFunctionDeclaration(item.parent)))
|
|
681
|
+
return true;
|
|
682
|
+
if (declarations.length !== 1) return false;
|
|
683
|
+
return singleImmutableDeclaration(declarations[0]);
|
|
275
684
|
}
|
|
276
|
-
function
|
|
277
|
-
const
|
|
278
|
-
|
|
685
|
+
function propertyContainer(declaration) {
|
|
686
|
+
const parent = declaration.parent;
|
|
687
|
+
if (ts.isClassDeclaration(parent)) return parent;
|
|
688
|
+
if (!ts.isVariableDeclaration(parent)) return void 0;
|
|
689
|
+
const initializer = parent.initializer;
|
|
690
|
+
return initializer && (ts.isObjectLiteralExpression(initializer) || ts.isClassExpression(initializer)) ? initializer : void 0;
|
|
279
691
|
}
|
|
280
|
-
function
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
ts.forEachChild(node, visit);
|
|
692
|
+
function identity(symbol) {
|
|
693
|
+
return {
|
|
694
|
+
sourceFile: symbol.sourceFile,
|
|
695
|
+
qualifiedName: symbol.qualifiedName,
|
|
696
|
+
startOffset: symbol.startOffset,
|
|
697
|
+
endOffset: symbol.endOffset
|
|
287
698
|
};
|
|
288
|
-
visit(source);
|
|
289
|
-
return exports;
|
|
290
699
|
}
|
|
291
|
-
function
|
|
292
|
-
|
|
700
|
+
function executableBody(symbol) {
|
|
701
|
+
const value = symbol.importExportEvidence?.executableBodyEligibility;
|
|
702
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && "eligible" in value && value.eligible === true);
|
|
703
|
+
}
|
|
704
|
+
function exactFunctionTarget(declarations, symbols, source) {
|
|
705
|
+
if (!immutableDeclarationSet(declarations, source)) return void 0;
|
|
706
|
+
const spans = declarations.flatMap((declaration) => {
|
|
707
|
+
const target = functionTargetNode(declaration);
|
|
708
|
+
return target ? [{ start: target.getStart(source), end: target.getEnd() }] : [];
|
|
709
|
+
});
|
|
710
|
+
const matches = symbols.filter((symbol) => localFunctionMatches(symbol, spans, source.fileName));
|
|
711
|
+
return matches.length === 1 && matches[0] ? identity(matches[0]) : void 0;
|
|
293
712
|
}
|
|
294
|
-
function
|
|
713
|
+
function localFunctionMatches(symbol, spans, sourceFile2) {
|
|
714
|
+
if (symbol.sourceFile !== sourceFile2 || !executableBody(symbol))
|
|
715
|
+
return false;
|
|
716
|
+
return spans.some((span) => symbol.startOffset === span.start && symbol.endOffset === span.end);
|
|
717
|
+
}
|
|
718
|
+
function exactPropertyTarget(expression, declaration, symbols, source) {
|
|
719
|
+
if (!immutableDeclarationSet([declaration], source) || !stableLocalValueReference(source, declaration))
|
|
720
|
+
return void 0;
|
|
721
|
+
const container = propertyContainer(declaration);
|
|
722
|
+
if (!container) return void 0;
|
|
723
|
+
const qualifiedName = `${declaration.text}.${expression.name.text}`;
|
|
724
|
+
const start = container.getStart(source);
|
|
725
|
+
const end = container.getEnd();
|
|
726
|
+
const matches = symbols.filter((symbol) => symbol.sourceFile === source.fileName && symbol.qualifiedName === qualifiedName && symbol.startOffset >= start && symbol.endOffset <= end && executableBody(symbol));
|
|
727
|
+
return matches.length === 1 && matches[0] ? identity(matches[0]) : void 0;
|
|
728
|
+
}
|
|
729
|
+
function localSymbolTarget(expression, source, symbols) {
|
|
295
730
|
if (ts.isIdentifier(expression)) {
|
|
296
|
-
|
|
731
|
+
return exactFunctionTarget(
|
|
732
|
+
lexicalIdentifierDeclarations(expression),
|
|
733
|
+
symbols,
|
|
734
|
+
source
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
if (!ts.isPropertyAccessExpression(expression) || expression.questionDotToken || !ts.isIdentifier(expression.expression)) return void 0;
|
|
738
|
+
const declaration = lexicalIdentifierDeclaration(expression.expression);
|
|
739
|
+
return declaration ? exactPropertyTarget(expression, declaration, symbols, source) : void 0;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// src/parsers/005-event-subscription-facts.ts
|
|
743
|
+
function lineOf(source, node) {
|
|
744
|
+
return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
|
|
745
|
+
}
|
|
746
|
+
function lineAt(source, position) {
|
|
747
|
+
return source.getLineAndCharacterOfPosition(position).line + 1;
|
|
748
|
+
}
|
|
749
|
+
function importRelation(binding) {
|
|
750
|
+
if (binding.moduleKind === "package") return "package_import";
|
|
751
|
+
return binding.referenceShape === "namespace_member" ? "relative_import_namespace_member" : "relative_import";
|
|
752
|
+
}
|
|
753
|
+
function directTarget(expression, source, imports, symbols) {
|
|
754
|
+
const imported = symbolImportReference(expression, imports);
|
|
755
|
+
if (imported) return importedTarget(expression, source, imported);
|
|
756
|
+
if (ts2.isIdentifier(expression)) {
|
|
757
|
+
const exact = localSymbolTarget(expression, source, symbols);
|
|
297
758
|
return {
|
|
298
759
|
calleeExpression: expression.text,
|
|
299
760
|
calleeLocalName: expression.text,
|
|
300
|
-
|
|
301
|
-
|
|
761
|
+
relation: exact ? "indexed_local_symbol" : "indexed_local_symbol_unproven",
|
|
762
|
+
referenceShape: "identifier",
|
|
763
|
+
localTargetIdentity: exact
|
|
302
764
|
};
|
|
303
765
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const importSource = imports.get(objectName);
|
|
309
|
-
if (namespaceImports.has(objectName)) return {
|
|
766
|
+
return propertyTarget(expression, source, symbols);
|
|
767
|
+
}
|
|
768
|
+
function importedTarget(expression, source, binding) {
|
|
769
|
+
return {
|
|
310
770
|
calleeExpression: expression.getText(source),
|
|
311
|
-
calleeLocalName:
|
|
312
|
-
importSource,
|
|
313
|
-
relation:
|
|
771
|
+
calleeLocalName: binding.requestedPublicName,
|
|
772
|
+
importSource: binding.rawModuleSpecifier,
|
|
773
|
+
relation: importRelation(binding),
|
|
774
|
+
referenceShape: binding.referenceShape,
|
|
775
|
+
importBinding: binding
|
|
314
776
|
};
|
|
315
|
-
|
|
777
|
+
}
|
|
778
|
+
function propertyTarget(expression, source, symbols) {
|
|
779
|
+
if (!ts2.isPropertyAccessExpression(expression) || expression.questionDotToken || !ts2.isIdentifier(expression.expression)) return void 0;
|
|
780
|
+
const exact = localSymbolTarget(expression, source, symbols);
|
|
316
781
|
return {
|
|
317
|
-
calleeExpression:
|
|
318
|
-
calleeLocalName:
|
|
319
|
-
|
|
320
|
-
|
|
782
|
+
calleeExpression: expression.getText(source),
|
|
783
|
+
calleeLocalName: `${expression.expression.text}.${expression.name.text}`,
|
|
784
|
+
relation: exact ? "indexed_local_symbol" : "indexed_local_symbol_unproven",
|
|
785
|
+
referenceShape: "static_member",
|
|
786
|
+
localTargetIdentity: exact
|
|
321
787
|
};
|
|
322
788
|
}
|
|
323
|
-
function
|
|
324
|
-
|
|
325
|
-
|
|
789
|
+
function unsupportedClassification(status, reason, referenceShape) {
|
|
790
|
+
return { status, reason, referenceShape };
|
|
791
|
+
}
|
|
792
|
+
function classifyHandlerReference(expression, source, imports = collectSymbolImportBindings(source), symbols = []) {
|
|
793
|
+
if (!expression)
|
|
794
|
+
return unsupportedClassification(
|
|
795
|
+
"missing_argument",
|
|
796
|
+
"handler_argument_missing",
|
|
797
|
+
"missing"
|
|
798
|
+
);
|
|
799
|
+
if (ts2.isArrowFunction(expression) || ts2.isFunctionExpression(expression))
|
|
800
|
+
return unsupportedClassification(
|
|
801
|
+
"unsupported_inline",
|
|
802
|
+
"inline_handler_body_not_indexed",
|
|
803
|
+
"inline_callback"
|
|
804
|
+
);
|
|
805
|
+
const direct = directTarget(expression, source, imports, symbols);
|
|
806
|
+
if (direct)
|
|
807
|
+
return { status: "role_required", referenceShape: direct.referenceShape, target: direct };
|
|
808
|
+
if (!ts2.isCallExpression(expression))
|
|
809
|
+
return unsupportedClassification(
|
|
810
|
+
"unsupported_reference_shape",
|
|
811
|
+
"handler_reference_shape_unsupported",
|
|
812
|
+
"unsupported_expression"
|
|
813
|
+
);
|
|
814
|
+
return classifyWrapperReference(expression, source, imports, symbols);
|
|
815
|
+
}
|
|
816
|
+
function classifyWrapperReference(expression, source, imports, symbols) {
|
|
817
|
+
if (expression.questionDotToken || expression.arguments.length !== 1)
|
|
818
|
+
return unsupportedClassification(
|
|
819
|
+
"unsupported_wrapper",
|
|
820
|
+
"wrapper_requires_one_reference",
|
|
821
|
+
"wrapper_call"
|
|
822
|
+
);
|
|
823
|
+
const inner = expression.arguments[0];
|
|
824
|
+
const target = inner ? directTarget(inner, source, imports, symbols) : void 0;
|
|
825
|
+
if (!target)
|
|
826
|
+
return unsupportedClassification(
|
|
827
|
+
"unsupported_wrapper",
|
|
828
|
+
"wrapper_reference_shape_unsupported",
|
|
829
|
+
"wrapper_call"
|
|
830
|
+
);
|
|
831
|
+
return {
|
|
832
|
+
status: "role_required",
|
|
833
|
+
referenceShape: `wrapped_${target.referenceShape}`,
|
|
834
|
+
target: { ...target, wrapperFunction: expression.expression.getText(source) }
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
function eventSymbol(source, classified) {
|
|
838
|
+
const node = classified.node;
|
|
839
|
+
const eventName = classified.fact.eventNameExpr ?? "";
|
|
840
|
+
const line = lineOf(source, node);
|
|
841
|
+
const safeName = eventName.replace(/[^A-Za-z0-9_$-]/g, "_");
|
|
842
|
+
const name = `event:${safeName}:${line}`;
|
|
843
|
+
const receiver = classified.fact.evidence?.receiver;
|
|
844
|
+
return {
|
|
845
|
+
kind: "event_registration",
|
|
846
|
+
localName: name,
|
|
847
|
+
qualifiedName: `module:${classified.fact.sourceFile}#${name}`,
|
|
848
|
+
sourceFile: classified.fact.sourceFile,
|
|
849
|
+
startLine: line,
|
|
850
|
+
endLine: lineAt(source, node.getEnd() - 1),
|
|
851
|
+
startOffset: node.getStart(source),
|
|
852
|
+
endOffset: node.getEnd(),
|
|
853
|
+
exported: false,
|
|
854
|
+
importExportEvidence: {
|
|
855
|
+
source: "synthetic_event_registration",
|
|
856
|
+
eventName,
|
|
857
|
+
registrationLine: line,
|
|
858
|
+
receiver: typeof receiver === "string" ? receiver : void 0
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function eventCall(classified, owner, classification) {
|
|
863
|
+
const target = classification.target;
|
|
864
|
+
if (!target) return void 0;
|
|
865
|
+
return {
|
|
866
|
+
callerQualifiedName: owner.qualifiedName,
|
|
867
|
+
calleeExpression: target.calleeExpression,
|
|
868
|
+
calleeLocalName: target.calleeLocalName,
|
|
869
|
+
importSource: target.importSource,
|
|
870
|
+
sourceFile: classified.fact.sourceFile,
|
|
871
|
+
sourceLine: classified.fact.sourceLine,
|
|
872
|
+
callSiteStartOffset: classified.fact.callSiteStartOffset,
|
|
873
|
+
callSiteEndOffset: classified.fact.callSiteEndOffset,
|
|
874
|
+
callRole: "event_subscribe_handler",
|
|
875
|
+
evidence: {
|
|
876
|
+
relation: target.relation,
|
|
877
|
+
caller: owner.qualifiedName,
|
|
878
|
+
targetName: target.calleeLocalName,
|
|
879
|
+
referenceShape: classification.referenceShape,
|
|
880
|
+
...target.importBinding ? { importBinding: target.importBinding } : {},
|
|
881
|
+
...target.localTargetIdentity ? { localTargetIdentity: target.localTargetIdentity } : {},
|
|
882
|
+
...target.wrapperFunction ? { wrapperFunction: target.wrapperFunction } : {},
|
|
883
|
+
factOrigin: "event_subscribe_handler_reference"
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
function enrichSubscription(source, classified, symbols) {
|
|
888
|
+
const classification = classifyHandlerReference(
|
|
889
|
+
classified.node.arguments[1],
|
|
326
890
|
source,
|
|
327
|
-
|
|
328
|
-
|
|
891
|
+
void 0,
|
|
892
|
+
symbols
|
|
329
893
|
);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
894
|
+
const evidence = {
|
|
895
|
+
...classified.fact.evidence ?? {},
|
|
896
|
+
handlerReferenceStatus: classification.status,
|
|
897
|
+
...classification.reason ? { handlerReferenceReason: classification.reason } : {},
|
|
898
|
+
handlerReferenceShape: classification.referenceShape
|
|
899
|
+
};
|
|
900
|
+
return {
|
|
901
|
+
classified: { ...classified, fact: { ...classified.fact, evidence } },
|
|
902
|
+
classification
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
function reconcileEventSubscriptions(source, classifications, symbols, calls) {
|
|
906
|
+
const subscriptions = classifications.filter(
|
|
907
|
+
(item) => item.fact.callType === "async_subscribe"
|
|
908
|
+
).map((item) => enrichSubscription(source, item, symbols));
|
|
909
|
+
const eventSymbols = subscriptions.map((item) => eventSymbol(source, item.classified));
|
|
910
|
+
const eventCalls = subscriptions.flatMap((item, index) => {
|
|
911
|
+
const owner = eventSymbols[index];
|
|
912
|
+
const call = owner ? eventCall(item.classified, owner, item.classification) : void 0;
|
|
913
|
+
return call ? [call] : [];
|
|
914
|
+
});
|
|
915
|
+
return {
|
|
916
|
+
classifications: classifications.map((item) => subscriptions.find((candidate) => candidate.classified.node === item.node)?.classified ?? item),
|
|
917
|
+
symbols: [...symbols.filter((item) => item.kind !== "event_registration"), ...eventSymbols],
|
|
918
|
+
calls: [
|
|
919
|
+
...calls.filter((item) => item.callRole !== "event_subscribe_handler"),
|
|
920
|
+
...eventCalls
|
|
921
|
+
]
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// src/parsers/007-source-fact-reconciliation.ts
|
|
926
|
+
import "typescript";
|
|
927
|
+
|
|
928
|
+
// src/parsers/006-binding-identity.ts
|
|
929
|
+
import ts3 from "typescript";
|
|
930
|
+
var scopeChainCap = 16;
|
|
931
|
+
function matchFactsToSites(index, facts) {
|
|
932
|
+
return facts.map((fact) => {
|
|
933
|
+
if (fact.bindingSiteStartOffset === void 0 || fact.bindingSiteEndOffset === void 0)
|
|
934
|
+
throw new Error("invalid_prepared_repository_snapshot:binding_site_missing");
|
|
935
|
+
const site = bindingSite(
|
|
936
|
+
index,
|
|
937
|
+
fact.variableName,
|
|
938
|
+
fact.bindingSiteStartOffset,
|
|
939
|
+
fact.bindingSiteEndOffset
|
|
940
|
+
);
|
|
941
|
+
if (!site)
|
|
942
|
+
throw new Error("invalid_prepared_repository_snapshot:binding_site_invalid");
|
|
943
|
+
return { fact, site };
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
function exactOwner(fact, site, symbols) {
|
|
947
|
+
const candidates = executableSymbolCandidates(symbols, fact.sourceFile);
|
|
948
|
+
const owner = selectCallOwner(
|
|
949
|
+
candidates,
|
|
950
|
+
site.startOffset,
|
|
951
|
+
site.endOffset
|
|
952
|
+
);
|
|
953
|
+
if (owner.status === "ambiguous")
|
|
954
|
+
throw new Error("invalid_prepared_repository_snapshot:binding_owner_ambiguous");
|
|
955
|
+
return {
|
|
956
|
+
...fact,
|
|
957
|
+
bindingSiteStartOffset: site.startOffset,
|
|
958
|
+
bindingSiteEndOffset: site.endOffset,
|
|
959
|
+
sourceSymbolQualifiedName: owner.owner?.qualifiedName,
|
|
960
|
+
ownerResolution: owner.status === "resolved" ? "owned_exact" : "ownerless_file_scope"
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
function prepareBindings(index, facts, symbols) {
|
|
964
|
+
return matchFactsToSites(index, facts).map(({ fact, site }) => ({
|
|
965
|
+
fact: exactOwner(fact, site, symbols),
|
|
966
|
+
site,
|
|
967
|
+
aliasProven: site.aliasSource === void 0
|
|
968
|
+
}));
|
|
969
|
+
}
|
|
970
|
+
function aliasStep(binding, source) {
|
|
971
|
+
const aliasKind = binding.site.aliasKind ?? (binding.site.flow === "assignment" ? "identity-assignment" : "identity");
|
|
972
|
+
return {
|
|
973
|
+
callerVariable: binding.fact.variableName,
|
|
974
|
+
aliasOf: source.fact.variableName,
|
|
975
|
+
aliasKind,
|
|
976
|
+
scopeRule: "exact_lexical_scope",
|
|
977
|
+
...aliasKind === "transaction" ? { transactionAliasSource: source.fact.variableName } : {},
|
|
978
|
+
...binding.site.aliasArrayIndex === void 0 ? {} : {
|
|
979
|
+
sourceVariable: source.fact.variableName,
|
|
980
|
+
arrayIndex: binding.site.aliasArrayIndex,
|
|
981
|
+
promiseAll: binding.site.aliasPromiseAll ?? false,
|
|
982
|
+
arrayContainer: binding.site.aliasPromiseAll ? "Promise.all" : "array_literal"
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
function copyAliasProvenance(binding, source) {
|
|
987
|
+
binding.fact = {
|
|
988
|
+
...binding.fact,
|
|
989
|
+
alias: source.fact.alias,
|
|
990
|
+
aliasExpr: source.fact.aliasExpr,
|
|
991
|
+
destinationExpr: source.fact.destinationExpr,
|
|
992
|
+
servicePathExpr: source.fact.servicePathExpr,
|
|
993
|
+
isDynamic: source.fact.isDynamic,
|
|
994
|
+
placeholders: source.fact.placeholders,
|
|
995
|
+
helperChain: [
|
|
996
|
+
...source.fact.helperChain ?? [],
|
|
997
|
+
aliasStep(binding, source)
|
|
998
|
+
]
|
|
999
|
+
};
|
|
1000
|
+
binding.aliasProven = true;
|
|
1001
|
+
}
|
|
1002
|
+
function bindingCandidates(bindings) {
|
|
1003
|
+
return bindings.map((binding) => ({
|
|
1004
|
+
variableName: binding.fact.variableName,
|
|
1005
|
+
bindingSiteStartOffset: binding.site.startOffset,
|
|
1006
|
+
bindingSiteEndOffset: binding.site.endOffset,
|
|
1007
|
+
value: binding
|
|
1008
|
+
}));
|
|
1009
|
+
}
|
|
1010
|
+
function resolvedAliasSource(selected) {
|
|
1011
|
+
if (selected.status !== "resolved") return void 0;
|
|
1012
|
+
if (!selected.site?.deterministic) return void 0;
|
|
1013
|
+
if (selected.declarationSite?.declarationKind === "var") return void 0;
|
|
1014
|
+
const source = selected.candidate?.value;
|
|
1015
|
+
return source?.aliasProven ? source : void 0;
|
|
1016
|
+
}
|
|
1017
|
+
function reconcileAliases(index, bindings) {
|
|
1018
|
+
const candidates = bindingCandidates(bindings);
|
|
1019
|
+
for (const binding of bindings) {
|
|
1020
|
+
const sourceName = binding.site.aliasSource;
|
|
1021
|
+
if (!sourceName) continue;
|
|
1022
|
+
const selected = selectVisibleBinding(
|
|
1023
|
+
index,
|
|
1024
|
+
candidates,
|
|
1025
|
+
sourceName,
|
|
1026
|
+
binding.site.node
|
|
1027
|
+
);
|
|
1028
|
+
const source = resolvedAliasSource(selected);
|
|
1029
|
+
if (source) copyAliasProvenance(binding, source);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
function assertUniqueSites(bindings) {
|
|
1033
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1034
|
+
for (const binding of bindings) {
|
|
1035
|
+
const key = [
|
|
1036
|
+
binding.fact.sourceFile,
|
|
1037
|
+
binding.fact.variableName,
|
|
1038
|
+
binding.site.startOffset,
|
|
1039
|
+
binding.site.endOffset
|
|
1040
|
+
].join("\0");
|
|
1041
|
+
if (seen.has(key))
|
|
1042
|
+
throw new Error("invalid_prepared_repository_snapshot:duplicate_service_binding_site");
|
|
1043
|
+
seen.add(key);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
function callNodeMap(source) {
|
|
1047
|
+
const calls = /* @__PURE__ */ new Map();
|
|
1048
|
+
const visit = (node) => {
|
|
1049
|
+
if (ts3.isCallExpression(node))
|
|
1050
|
+
calls.set(`${node.getStart(source)}:${node.getEnd()}`, node);
|
|
1051
|
+
ts3.forEachChild(node, visit);
|
|
1052
|
+
};
|
|
1053
|
+
visit(source);
|
|
1054
|
+
return calls;
|
|
1055
|
+
}
|
|
1056
|
+
function emptyReference(call, reason) {
|
|
1057
|
+
return {
|
|
1058
|
+
status: call.serviceVariableName ? "unresolved" : "not_applicable",
|
|
1059
|
+
variableName: call.serviceVariableName,
|
|
1060
|
+
scopeChainTotal: 0,
|
|
1061
|
+
scopeChainShown: 0,
|
|
1062
|
+
scopeChainOmitted: 0,
|
|
1063
|
+
reason
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
function rejectedReference(call, chain, reason, status = "unresolved") {
|
|
1067
|
+
return {
|
|
1068
|
+
status,
|
|
1069
|
+
variableName: call.serviceVariableName,
|
|
1070
|
+
scopeChainTotal: chain.length,
|
|
1071
|
+
scopeChainShown: Math.min(chain.length, scopeChainCap),
|
|
1072
|
+
scopeChainOmitted: Math.max(0, chain.length - scopeChainCap),
|
|
1073
|
+
reason
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
function resolvedReference(call, selected, chain, scopeIndex) {
|
|
1077
|
+
return {
|
|
1078
|
+
status: "resolved_exact",
|
|
1079
|
+
variableName: call.serviceVariableName,
|
|
1080
|
+
bindingSourceFile: selected.fact.sourceFile,
|
|
1081
|
+
bindingSiteStartOffset: selected.site.startOffset,
|
|
1082
|
+
bindingSiteEndOffset: selected.site.endOffset,
|
|
1083
|
+
resolutionStrategy: selected.site.flow === "assignment" ? "deterministic_reaching_assignment" : selected.site.aliasSource ? "lexical_alias_declaration" : "lexical_declaration",
|
|
1084
|
+
lexicalScopeChain: chain,
|
|
1085
|
+
bindingScopeIndex: scopeIndex,
|
|
1086
|
+
scopeChainTotal: chain.length,
|
|
1087
|
+
scopeChainShown: chain.length,
|
|
1088
|
+
scopeChainOmitted: 0
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function selectedBindingReference(call, chain, chosen) {
|
|
1092
|
+
const selected = chosen.candidate?.value;
|
|
1093
|
+
if (!selected) {
|
|
1094
|
+
return rejectedReference(
|
|
1095
|
+
call,
|
|
1096
|
+
chain,
|
|
1097
|
+
chosen.reason ?? "binding_not_found"
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
if (chosen.declarationSite?.declarationKind === "var")
|
|
1101
|
+
return rejectedReference(call, chain, "unsupported_var_binding");
|
|
1102
|
+
if (!selected.site.deterministic || !selected.aliasProven)
|
|
1103
|
+
return rejectedReference(call, chain, "unsupported_reaching_assignment");
|
|
1104
|
+
return resolvedReference(call, selected, chain, chosen.scopeIndex ?? 0);
|
|
1105
|
+
}
|
|
1106
|
+
function bindingReference(call, node, index, bindings) {
|
|
1107
|
+
if (!call.serviceVariableName) return emptyReference(call);
|
|
1108
|
+
if (!node) return emptyReference(call, "binding_flow_unsupported");
|
|
1109
|
+
const chain = lexicalScopeChain(node, index.source);
|
|
1110
|
+
if (chain.length > scopeChainCap)
|
|
1111
|
+
return rejectedReference(call, chain, "scope_chain_limit_exceeded");
|
|
1112
|
+
const chosen = selectVisibleBinding(
|
|
1113
|
+
index,
|
|
1114
|
+
bindingCandidates(bindings),
|
|
1115
|
+
call.serviceVariableName,
|
|
1116
|
+
node
|
|
1117
|
+
);
|
|
1118
|
+
if (chosen.status === "ambiguous")
|
|
1119
|
+
return rejectedReference(
|
|
1120
|
+
call,
|
|
1121
|
+
chain,
|
|
1122
|
+
"binding_scope_ambiguous",
|
|
1123
|
+
"ambiguous"
|
|
1124
|
+
);
|
|
1125
|
+
return selectedBindingReference(call, chain, chosen);
|
|
1126
|
+
}
|
|
1127
|
+
function exactCallOwner(call, symbols) {
|
|
1128
|
+
const start = call.callSiteStartOffset;
|
|
1129
|
+
const end = call.callSiteEndOffset;
|
|
1130
|
+
if (start === void 0 || end === void 0)
|
|
1131
|
+
return { resolution: "ownerless_file_scope" };
|
|
1132
|
+
const selected = selectCallOwner(
|
|
1133
|
+
executableSymbolCandidates(symbols, call.sourceFile),
|
|
1134
|
+
start,
|
|
1135
|
+
end,
|
|
1136
|
+
call.callType === "async_subscribe"
|
|
1137
|
+
);
|
|
1138
|
+
if (selected.status === "ambiguous")
|
|
1139
|
+
throw new Error("invalid_prepared_repository_snapshot:outbound_owner_ambiguous");
|
|
1140
|
+
if (call.callType === "async_subscribe" && selected.status !== "resolved")
|
|
1141
|
+
throw new Error("invalid_prepared_repository_snapshot:subscription_owner_missing");
|
|
1142
|
+
return selected.owner ? { qualifiedName: selected.owner.qualifiedName, resolution: "owned_exact" } : { resolution: "ownerless_file_scope" };
|
|
1143
|
+
}
|
|
1144
|
+
function reconcileBindingAndCallIdentity(source, bindingFacts, callFacts, symbols) {
|
|
1145
|
+
const lexicalIndex = createBindingLexicalIndex(source);
|
|
1146
|
+
const bindings = prepareBindings(lexicalIndex, bindingFacts, symbols);
|
|
1147
|
+
reconcileAliases(lexicalIndex, bindings);
|
|
1148
|
+
assertUniqueSites(bindings);
|
|
1149
|
+
const nodes = callNodeMap(source);
|
|
1150
|
+
const calls = callFacts.map((call) => {
|
|
1151
|
+
const key = `${call.callSiteStartOffset}:${call.callSiteEndOffset}`;
|
|
1152
|
+
const reference = bindingReference(
|
|
1153
|
+
call,
|
|
1154
|
+
nodes.get(key),
|
|
1155
|
+
lexicalIndex,
|
|
1156
|
+
bindings
|
|
1157
|
+
);
|
|
1158
|
+
const owner = exactCallOwner(call, symbols);
|
|
1159
|
+
return {
|
|
1160
|
+
...call,
|
|
1161
|
+
sourceSymbolQualifiedName: owner.qualifiedName,
|
|
1162
|
+
serviceBindingReference: reference,
|
|
1163
|
+
evidence: {
|
|
1164
|
+
...call.evidence ?? {},
|
|
1165
|
+
sourceOwnerResolution: owner.resolution,
|
|
1166
|
+
serviceBindingReference: reference
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
});
|
|
1170
|
+
return { bindings: bindings.map((binding) => binding.fact), calls };
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// src/parsers/007-source-fact-reconciliation.ts
|
|
1174
|
+
function callKey(call) {
|
|
1175
|
+
return `${call.callType}:${call.callSiteStartOffset}:${call.callSiteEndOffset}`;
|
|
1176
|
+
}
|
|
1177
|
+
function mergedOutboundCalls(existing, classifications) {
|
|
1178
|
+
const classifiedKeys = new Set(
|
|
1179
|
+
classifications.map((item) => callKey(item.fact))
|
|
1180
|
+
);
|
|
1181
|
+
return [
|
|
1182
|
+
...classifications.map((item) => item.fact),
|
|
1183
|
+
...existing.filter((call) => !classifiedKeys.has(callKey(call)))
|
|
1184
|
+
];
|
|
1185
|
+
}
|
|
1186
|
+
function exactSymbolCallOwner(call, symbols) {
|
|
1187
|
+
const start = call.callSiteStartOffset;
|
|
1188
|
+
const end = call.callSiteEndOffset;
|
|
1189
|
+
if (start === void 0 || end === void 0) return void 0;
|
|
1190
|
+
const selected = selectCallOwner(
|
|
1191
|
+
executableSymbolCandidates(symbols, call.sourceFile),
|
|
1192
|
+
start,
|
|
1193
|
+
end,
|
|
1194
|
+
call.callRole === "event_subscribe_handler"
|
|
1195
|
+
);
|
|
1196
|
+
if (selected.status === "ambiguous")
|
|
1197
|
+
throw new Error("invalid_prepared_repository_snapshot:symbol_call_owner_ambiguous");
|
|
1198
|
+
if (call.callRole === "event_subscribe_handler" && !selected.owner)
|
|
1199
|
+
throw new Error("invalid_prepared_repository_snapshot:handler_owner_missing");
|
|
1200
|
+
return selected.owner;
|
|
1201
|
+
}
|
|
1202
|
+
function reconcileSymbolCallOwners(calls, symbols) {
|
|
1203
|
+
return calls.flatMap((call) => {
|
|
1204
|
+
const owner = exactSymbolCallOwner(call, symbols);
|
|
1205
|
+
if (!owner) return [];
|
|
1206
|
+
return [{
|
|
1207
|
+
...call,
|
|
1208
|
+
callerQualifiedName: owner.qualifiedName,
|
|
1209
|
+
evidence: {
|
|
1210
|
+
...call.evidence,
|
|
1211
|
+
caller: owner.qualifiedName,
|
|
1212
|
+
callerResolution: "full_span_containment"
|
|
1213
|
+
}
|
|
1214
|
+
}];
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
function reconcileSourceFacts(source, classifications, bindings, outboundCalls, symbols, symbolCalls) {
|
|
1218
|
+
const events = reconcileEventSubscriptions(
|
|
1219
|
+
source,
|
|
1220
|
+
classifications,
|
|
1221
|
+
symbols,
|
|
1222
|
+
symbolCalls
|
|
1223
|
+
);
|
|
1224
|
+
const allCalls = mergedOutboundCalls(outboundCalls, events.classifications);
|
|
1225
|
+
const identities = reconcileBindingAndCallIdentity(
|
|
334
1226
|
source,
|
|
335
|
-
|
|
336
|
-
|
|
1227
|
+
bindings,
|
|
1228
|
+
allCalls,
|
|
1229
|
+
events.symbols
|
|
337
1230
|
);
|
|
338
|
-
return
|
|
1231
|
+
return {
|
|
1232
|
+
bindings: identities.bindings,
|
|
1233
|
+
outboundCalls: identities.calls,
|
|
1234
|
+
symbols: events.symbols,
|
|
1235
|
+
symbolCalls: reconcileSymbolCallOwners(events.calls, events.symbols),
|
|
1236
|
+
classifications: events.classifications
|
|
1237
|
+
};
|
|
339
1238
|
}
|
|
340
|
-
|
|
341
|
-
|
|
1239
|
+
|
|
1240
|
+
// src/parsers/009-symbol-call-facts.ts
|
|
1241
|
+
import ts5 from "typescript";
|
|
1242
|
+
var commonTerminalMembers = /* @__PURE__ */ new Set([
|
|
1243
|
+
"push",
|
|
1244
|
+
"includes",
|
|
1245
|
+
"find",
|
|
1246
|
+
"findIndex",
|
|
1247
|
+
"map",
|
|
1248
|
+
"filter",
|
|
1249
|
+
"reduce",
|
|
1250
|
+
"forEach",
|
|
1251
|
+
"some",
|
|
1252
|
+
"every",
|
|
1253
|
+
"toUpperCase",
|
|
1254
|
+
"toLowerCase",
|
|
1255
|
+
"trim",
|
|
1256
|
+
"split",
|
|
1257
|
+
"join",
|
|
1258
|
+
"get",
|
|
1259
|
+
"set",
|
|
1260
|
+
"has"
|
|
1261
|
+
]);
|
|
1262
|
+
var loggerMembers = /* @__PURE__ */ new Set([
|
|
1263
|
+
"trace",
|
|
1264
|
+
"debug",
|
|
1265
|
+
"info",
|
|
1266
|
+
"warn",
|
|
1267
|
+
"error",
|
|
1268
|
+
"fatal",
|
|
1269
|
+
"log"
|
|
1270
|
+
]);
|
|
1271
|
+
var globalObjects = /* @__PURE__ */ new Set([
|
|
1272
|
+
"JSON",
|
|
1273
|
+
"Object",
|
|
1274
|
+
"Array",
|
|
1275
|
+
"String",
|
|
1276
|
+
"Number",
|
|
1277
|
+
"Boolean",
|
|
1278
|
+
"Math",
|
|
1279
|
+
"Date",
|
|
1280
|
+
"Promise",
|
|
1281
|
+
"Reflect"
|
|
1282
|
+
]);
|
|
1283
|
+
var capDslRoots = /* @__PURE__ */ new Set([
|
|
1284
|
+
"SELECT",
|
|
1285
|
+
"INSERT",
|
|
1286
|
+
"UPSERT",
|
|
1287
|
+
"UPDATE",
|
|
1288
|
+
"DELETE"
|
|
1289
|
+
]);
|
|
1290
|
+
var requestHelpers = /* @__PURE__ */ new Set([
|
|
1291
|
+
"reject",
|
|
1292
|
+
"error",
|
|
1293
|
+
"info",
|
|
1294
|
+
"warn",
|
|
1295
|
+
"notify"
|
|
1296
|
+
]);
|
|
1297
|
+
var transportMembers = /* @__PURE__ */ new Set([
|
|
1298
|
+
"emit",
|
|
1299
|
+
"publish",
|
|
1300
|
+
"send",
|
|
1301
|
+
"on"
|
|
1302
|
+
]);
|
|
1303
|
+
var cdsFrameworkPrefixes = [
|
|
1304
|
+
"cds.connect.",
|
|
1305
|
+
"cds.services.",
|
|
1306
|
+
"cds.parse."
|
|
1307
|
+
];
|
|
1308
|
+
function symbolCallName(expr) {
|
|
1309
|
+
if (ts5.isIdentifier(expr)) return { expression: expr.text, local: expr.text };
|
|
1310
|
+
if (!ts5.isPropertyAccessExpression(expr))
|
|
1311
|
+
return { expression: expr.getText() };
|
|
1312
|
+
const left = expr.expression.getText();
|
|
1313
|
+
return {
|
|
1314
|
+
expression: expr.getText(),
|
|
1315
|
+
local: left === "this" ? void 0 : left.split(".")[0],
|
|
1316
|
+
member: expr.name.text,
|
|
1317
|
+
receiver: left
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
function hasSetMember(value, values) {
|
|
1321
|
+
return value ? values.has(value) : false;
|
|
1322
|
+
}
|
|
1323
|
+
function cdsFrameworkCall(callee) {
|
|
1324
|
+
return callee.expression === "cds.run" || cdsFrameworkPrefixes.some((prefix) => callee.expression.startsWith(prefix));
|
|
1325
|
+
}
|
|
1326
|
+
function requestHelperCall(callee) {
|
|
1327
|
+
return callee.local === "req" && hasSetMember(callee.member, requestHelpers);
|
|
1328
|
+
}
|
|
1329
|
+
function ignoredFrameworkCall(callee) {
|
|
1330
|
+
const checks = [
|
|
1331
|
+
hasSetMember(callee.local, capDslRoots),
|
|
1332
|
+
cdsFrameworkCall(callee),
|
|
1333
|
+
requestHelperCall(callee),
|
|
1334
|
+
hasSetMember(callee.member, transportMembers),
|
|
1335
|
+
hasSetMember(callee.local, globalObjects),
|
|
1336
|
+
callee.expression.startsWith("new Date().")
|
|
1337
|
+
];
|
|
1338
|
+
return checks.some(Boolean);
|
|
1339
|
+
}
|
|
1340
|
+
function argumentEvidence(args) {
|
|
1341
|
+
return args.map((arg) => {
|
|
1342
|
+
if (ts5.isIdentifier(arg)) return { kind: "identifier", name: arg.text };
|
|
1343
|
+
if (ts5.isArrayLiteralExpression(arg)) return {
|
|
1344
|
+
kind: "array_literal",
|
|
1345
|
+
elements: arg.elements.flatMap((item, index) => ts5.isIdentifier(item) ? [{ index, kind: "identifier", name: item.text }] : [])
|
|
1346
|
+
};
|
|
1347
|
+
if (ts5.isObjectLiteralExpression(arg))
|
|
1348
|
+
return objectArgumentEvidence(arg);
|
|
1349
|
+
return { kind: "unsupported", expression: arg.getText() };
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
function objectArgumentEvidence(argument) {
|
|
1353
|
+
const properties = argument.properties.flatMap((property) => {
|
|
1354
|
+
if (ts5.isShorthandPropertyAssignment(property))
|
|
1355
|
+
return [{
|
|
1356
|
+
kind: "shorthand",
|
|
1357
|
+
property: property.name.text,
|
|
1358
|
+
argument: property.name.text
|
|
1359
|
+
}];
|
|
1360
|
+
if (!ts5.isPropertyAssignment(property) || !ts5.isIdentifier(property.initializer)) return [];
|
|
1361
|
+
const name = propertyName(property.name);
|
|
1362
|
+
return name ? [{
|
|
1363
|
+
kind: "property_assignment",
|
|
1364
|
+
property: name,
|
|
1365
|
+
argument: property.initializer.text
|
|
1366
|
+
}] : [];
|
|
1367
|
+
});
|
|
1368
|
+
return { kind: "object_literal", properties };
|
|
1369
|
+
}
|
|
1370
|
+
function propertyName(name) {
|
|
1371
|
+
return ts5.isIdentifier(name) || ts5.isStringLiteralLike(name) || ts5.isNumericLiteral(name) ? name.text : void 0;
|
|
1372
|
+
}
|
|
1373
|
+
function exactCaller(collection, node) {
|
|
1374
|
+
const selected = selectCallOwner(
|
|
1375
|
+
executableSymbolCandidates(collection.symbols, collection.sourceFile),
|
|
1376
|
+
node.getStart(collection.source),
|
|
1377
|
+
node.getEnd()
|
|
1378
|
+
);
|
|
1379
|
+
if (selected.status === "ambiguous")
|
|
1380
|
+
throw new Error("invalid_prepared_repository_snapshot:symbol_call_owner_ambiguous");
|
|
1381
|
+
return selected.owner;
|
|
1382
|
+
}
|
|
1383
|
+
function relation(reference, instance, proxy, localTarget, provenThisMethod) {
|
|
1384
|
+
const derived = derivedRelation(instance, proxy);
|
|
1385
|
+
if (derived) return derived;
|
|
1386
|
+
if (reference?.moduleKind === "package") return "package_import";
|
|
1387
|
+
if (reference?.referenceShape === "namespace_member")
|
|
1388
|
+
return "relative_import_namespace_member";
|
|
1389
|
+
if (reference) return "relative_import";
|
|
1390
|
+
if (localTarget) return "indexed_local_symbol";
|
|
1391
|
+
return provenThisMethod ? "indexed_this_method" : "indexed_local_symbol";
|
|
1392
|
+
}
|
|
1393
|
+
function derivedRelation(instance, proxy) {
|
|
1394
|
+
if (instance?.importBinding?.moduleKind === "package")
|
|
1395
|
+
return "package_import_derived_member";
|
|
1396
|
+
if (instance) return "class_instance_method";
|
|
1397
|
+
if (proxy?.importBinding.moduleKind === "package")
|
|
1398
|
+
return "package_import_derived_member";
|
|
1399
|
+
return proxy ? "relative_import_proxy_member" : void 0;
|
|
1400
|
+
}
|
|
1401
|
+
function targetName(callee, reference, instance, proxy) {
|
|
1402
|
+
if (instance && callee.member)
|
|
1403
|
+
return `${instance.className}.${callee.member}`;
|
|
1404
|
+
if (proxy && callee.member) return callee.member;
|
|
1405
|
+
if (callee.receiver === "this") return callee.member;
|
|
1406
|
+
if (reference) return reference.requestedPublicName;
|
|
1407
|
+
return callee.member && callee.local ? `${callee.local}.${callee.member}` : callee.local;
|
|
1408
|
+
}
|
|
1409
|
+
function callFact(collection, node) {
|
|
1410
|
+
const caller = exactCaller(collection, node);
|
|
1411
|
+
if (!caller) return void 0;
|
|
1412
|
+
const callee = symbolCallName(node.expression);
|
|
1413
|
+
const proxy = callProxy(collection, node);
|
|
1414
|
+
const instance = callInstance(collection, node, callee);
|
|
1415
|
+
const reference = callImportReference(
|
|
1416
|
+
collection,
|
|
1417
|
+
node.expression,
|
|
1418
|
+
callee,
|
|
1419
|
+
instance,
|
|
1420
|
+
proxy
|
|
1421
|
+
);
|
|
1422
|
+
const localTarget = reference || instance || proxy ? void 0 : localSymbolTarget(node.expression, collection.source, collection.symbols);
|
|
1423
|
+
return retainedCallFact(collection, node, caller, callee, {
|
|
1424
|
+
proxy,
|
|
1425
|
+
instance,
|
|
1426
|
+
reference,
|
|
1427
|
+
localTarget
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
function expressionRoot(expression) {
|
|
1431
|
+
let current = expression;
|
|
1432
|
+
while (ts5.isPropertyAccessExpression(current))
|
|
1433
|
+
current = current.expression;
|
|
1434
|
+
return ts5.isIdentifier(current) ? current : void 0;
|
|
1435
|
+
}
|
|
1436
|
+
function exactDeclaredContext(identifier, candidates) {
|
|
1437
|
+
const matches = candidates.filter((candidate) => identifierMatchesDeclaration(
|
|
1438
|
+
identifier,
|
|
1439
|
+
candidate.declarationStartOffset,
|
|
1440
|
+
candidate.declarationEndOffset
|
|
1441
|
+
));
|
|
1442
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
1443
|
+
}
|
|
1444
|
+
function callProxy(collection, node) {
|
|
1445
|
+
const root = expressionRoot(node.expression);
|
|
1446
|
+
if (!root) return void 0;
|
|
1447
|
+
return exactDeclaredContext(
|
|
1448
|
+
root,
|
|
1449
|
+
collection.proxies.get(root.text) ?? []
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
function callInstance(collection, node, callee) {
|
|
1453
|
+
const root = expressionRoot(node.expression);
|
|
1454
|
+
if (root && root.text !== "this") {
|
|
1455
|
+
const exact = exactDeclaredContext(
|
|
1456
|
+
root,
|
|
1457
|
+
collection.instances.get(root.text) ?? []
|
|
1458
|
+
);
|
|
1459
|
+
if (exact) return exact;
|
|
1460
|
+
}
|
|
1461
|
+
return callee.receiver ? thisPropertyInstance(collection, node, callee.receiver) : void 0;
|
|
1462
|
+
}
|
|
1463
|
+
function enclosingClass(node) {
|
|
1464
|
+
let current = node.parent;
|
|
1465
|
+
while (current && !ts5.isSourceFile(current)) {
|
|
1466
|
+
if (ts5.isClassLike(current)) return current;
|
|
1467
|
+
current = current.parent;
|
|
1468
|
+
}
|
|
1469
|
+
return void 0;
|
|
1470
|
+
}
|
|
1471
|
+
function thisPropertyInstance(collection, node, receiver) {
|
|
1472
|
+
if (!receiver.startsWith("this.")) return void 0;
|
|
1473
|
+
const container = enclosingClass(node);
|
|
1474
|
+
if (!container) return void 0;
|
|
1475
|
+
const start = container.getStart(collection.source);
|
|
1476
|
+
const end = container.getEnd();
|
|
1477
|
+
const matches = (collection.instances.get(receiver) ?? []).filter(
|
|
1478
|
+
(candidate) => candidate.containerStartOffset === start && candidate.containerEndOffset === end
|
|
1479
|
+
);
|
|
1480
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
1481
|
+
}
|
|
1482
|
+
function callImportReference(collection, expression, callee, instance, proxy) {
|
|
1483
|
+
const direct = symbolImportReference(expression, collection.importBindings);
|
|
1484
|
+
if (direct || !callee.member) return direct;
|
|
1485
|
+
const inherited = instance?.importBinding ?? proxy?.importBinding;
|
|
1486
|
+
return inherited ? derivedMemberImportReference(inherited, callee.member) : void 0;
|
|
1487
|
+
}
|
|
1488
|
+
function retainedCallFact(collection, node, caller, callee, context) {
|
|
1489
|
+
const target = targetName(
|
|
1490
|
+
callee,
|
|
1491
|
+
context.reference,
|
|
1492
|
+
context.instance,
|
|
1493
|
+
context.proxy
|
|
1494
|
+
);
|
|
1495
|
+
const className = caller.qualifiedName.includes(".") ? caller.qualifiedName.split(".")[0] : void 0;
|
|
1496
|
+
const thisTarget = callee.receiver === "this" && className && callee.member ? `${className}.${callee.member}` : void 0;
|
|
1497
|
+
const resolvedTarget = thisTarget ?? target;
|
|
1498
|
+
if (!resolvedTarget || !shouldRetainCall(
|
|
1499
|
+
collection,
|
|
1500
|
+
callee,
|
|
1501
|
+
resolvedTarget,
|
|
1502
|
+
context,
|
|
1503
|
+
Boolean(thisTarget)
|
|
1504
|
+
))
|
|
1505
|
+
return void 0;
|
|
1506
|
+
return createCallFact(
|
|
1507
|
+
collection,
|
|
1508
|
+
node,
|
|
1509
|
+
caller,
|
|
1510
|
+
callee,
|
|
1511
|
+
resolvedTarget,
|
|
1512
|
+
thisTarget,
|
|
1513
|
+
context
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
function shouldRetainCall(collection, callee, target, context, provenThisMethod) {
|
|
1517
|
+
if (!target || ignoredCall(callee)) return false;
|
|
1518
|
+
const callables = new Set(collection.symbols.flatMap((symbol) => [symbol.localName, symbol.qualifiedName]));
|
|
1519
|
+
return provenThisMethod || Boolean(context.localTarget) || callables.has(target) && Boolean(context.instance) || hasImportedContext(context);
|
|
1520
|
+
}
|
|
1521
|
+
function ignoredCall(callee) {
|
|
1522
|
+
return loggerCall(callee) || terminalCall(callee) || ignoredFrameworkCall(callee);
|
|
1523
|
+
}
|
|
1524
|
+
function loggerCall(callee) {
|
|
1525
|
+
if (callee.local === "logger") return true;
|
|
1526
|
+
if (callee.receiver?.endsWith(".logger")) return true;
|
|
1527
|
+
return callee.expression.startsWith("this.logger.") && hasSetMember(callee.member, loggerMembers);
|
|
1528
|
+
}
|
|
1529
|
+
function terminalCall(callee) {
|
|
1530
|
+
return hasSetMember(callee.member, commonTerminalMembers) || hasSetMember(callee.member, loggerMembers);
|
|
1531
|
+
}
|
|
1532
|
+
function hasImportedContext(context) {
|
|
1533
|
+
return Boolean(context.reference ?? context.proxy ?? context.instance);
|
|
1534
|
+
}
|
|
1535
|
+
function createCallFact(collection, node, caller, callee, target, thisTarget, context) {
|
|
1536
|
+
const importSource = context.instance?.importSource ?? context.proxy?.importSource ?? context.reference?.rawModuleSpecifier;
|
|
1537
|
+
return {
|
|
1538
|
+
callerQualifiedName: caller.qualifiedName,
|
|
1539
|
+
calleeExpression: callee.expression,
|
|
1540
|
+
calleeLocalName: target,
|
|
1541
|
+
receiverLocalName: callee.member ? callee.local ?? callee.receiver : void 0,
|
|
1542
|
+
importSource,
|
|
1543
|
+
sourceFile: collection.sourceFile,
|
|
1544
|
+
sourceLine: collection.source.getLineAndCharacterOfPosition(
|
|
1545
|
+
node.getStart(collection.source)
|
|
1546
|
+
).line + 1,
|
|
1547
|
+
callSiteStartOffset: node.getStart(collection.source),
|
|
1548
|
+
callSiteEndOffset: node.getEnd(),
|
|
1549
|
+
callRole: "ordinary_call",
|
|
1550
|
+
evidence: callEvidence(caller, callee, target, thisTarget, context, node)
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
function callEvidence(caller, callee, target, thisTarget, context, node) {
|
|
1554
|
+
return {
|
|
1555
|
+
relation: relation(
|
|
1556
|
+
context.reference,
|
|
1557
|
+
context.instance,
|
|
1558
|
+
context.proxy,
|
|
1559
|
+
context.localTarget,
|
|
1560
|
+
Boolean(thisTarget)
|
|
1561
|
+
),
|
|
1562
|
+
caller: caller.qualifiedName,
|
|
1563
|
+
targetName: target,
|
|
1564
|
+
...importReferenceEvidence(context),
|
|
1565
|
+
...instanceEvidence(context.instance, callee),
|
|
1566
|
+
callArguments: argumentEvidence(node.arguments),
|
|
1567
|
+
...proxyEvidence(context.proxy),
|
|
1568
|
+
...context.localTarget ? { localTargetIdentity: context.localTarget } : {},
|
|
1569
|
+
candidateStrategy: parserCandidateStrategy(context)
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
function importReferenceEvidence(context) {
|
|
1573
|
+
if (!context.reference) return {};
|
|
1574
|
+
const derived = context.instance ?? context.proxy;
|
|
1575
|
+
return derived && context.reference.moduleKind === "package" ? { derivedImportBinding: context.reference } : { importBinding: context.reference };
|
|
1576
|
+
}
|
|
1577
|
+
function instanceEvidence(instance, callee) {
|
|
1578
|
+
if (!instance) return {};
|
|
1579
|
+
return {
|
|
1580
|
+
instanceVariable: instance.propertyName ?? callee.local,
|
|
1581
|
+
className: instance.className,
|
|
1582
|
+
methodName: callee.member,
|
|
1583
|
+
classImportSource: instance.importSource,
|
|
1584
|
+
classImportBinding: instance.importBinding
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
function proxyEvidence(proxy) {
|
|
1588
|
+
if (!proxy) return {};
|
|
1589
|
+
return {
|
|
1590
|
+
proxyVariableName: proxy.variableName,
|
|
1591
|
+
factory: proxy.factory,
|
|
1592
|
+
factoryExpression: proxy.factory,
|
|
1593
|
+
factoryImportSource: proxy.importSource,
|
|
1594
|
+
factoryImportBinding: proxy.importBinding
|
|
1595
|
+
};
|
|
342
1596
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
1597
|
+
function parserCandidateStrategy(context) {
|
|
1598
|
+
if (context.instance?.importSource)
|
|
1599
|
+
return "relative_import_class_instance_method";
|
|
1600
|
+
if (context.instance) return "same_file_class_instance_method";
|
|
1601
|
+
return context.proxy ? "proxy_member_exact_export_or_unique_member" : void 0;
|
|
1602
|
+
}
|
|
1603
|
+
function collectSymbolCallFacts(collection) {
|
|
1604
|
+
const calls = [];
|
|
1605
|
+
const visit = (node) => {
|
|
1606
|
+
if (ts5.isCallExpression(node)) {
|
|
1607
|
+
const call = callFact(collection, node);
|
|
1608
|
+
if (call) calls.push(call);
|
|
1609
|
+
}
|
|
1610
|
+
ts5.forEachChild(node, visit);
|
|
1611
|
+
};
|
|
1612
|
+
visit(collection.source);
|
|
1613
|
+
return calls;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// src/parsers/013-executable-body-eligibility.ts
|
|
1617
|
+
import ts6 from "typescript";
|
|
1618
|
+
function hasModifier(node, kind) {
|
|
1619
|
+
return ts6.canHaveModifiers(node) && Boolean(ts6.getModifiers(node)?.some((item) => item.kind === kind));
|
|
1620
|
+
}
|
|
1621
|
+
function executableBodyEligibility(node, source) {
|
|
1622
|
+
if (node.body) return { eligible: true, reason: "body_present" };
|
|
1623
|
+
if (hasModifier(node, ts6.SyntaxKind.AbstractKeyword))
|
|
1624
|
+
return { eligible: false, reason: "abstract_bodyless" };
|
|
1625
|
+
if (hasModifier(node, ts6.SyntaxKind.DeclareKeyword))
|
|
1626
|
+
return { eligible: false, reason: "ambient_declaration" };
|
|
1627
|
+
return {
|
|
1628
|
+
eligible: false,
|
|
1629
|
+
reason: source.isDeclarationFile ? "declaration_only" : "overload_signature"
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// src/parsers/017-symbol-derived-contexts.ts
|
|
1634
|
+
import ts7 from "typescript";
|
|
346
1635
|
var builtInConstructors = /* @__PURE__ */ new Set([
|
|
347
1636
|
"Set",
|
|
348
1637
|
"Map",
|
|
@@ -377,86 +1666,222 @@ var builtInConstructors = /* @__PURE__ */ new Set([
|
|
|
377
1666
|
"Promise",
|
|
378
1667
|
"AbortController"
|
|
379
1668
|
]);
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
1669
|
+
function appendNamed(values, name, value) {
|
|
1670
|
+
const matches = values.get(name) ?? [];
|
|
1671
|
+
matches.push(value);
|
|
1672
|
+
values.set(name, matches);
|
|
1673
|
+
}
|
|
1674
|
+
function assignmentOperator2(kind) {
|
|
1675
|
+
return kind >= ts7.SyntaxKind.FirstAssignment && kind <= ts7.SyntaxKind.LastAssignment;
|
|
1676
|
+
}
|
|
1677
|
+
function mutationUnary2(node) {
|
|
1678
|
+
return node.operator === ts7.SyntaxKind.PlusPlusToken || node.operator === ts7.SyntaxKind.MinusMinusToken;
|
|
1679
|
+
}
|
|
1680
|
+
function receiverIdentifier(expression) {
|
|
1681
|
+
let current = expression;
|
|
1682
|
+
while (ts7.isPropertyAccessExpression(current) || ts7.isElementAccessExpression(current))
|
|
1683
|
+
current = current.expression;
|
|
1684
|
+
return ts7.isIdentifier(current) ? current : void 0;
|
|
1685
|
+
}
|
|
1686
|
+
function nodeWritesMember(node, matches) {
|
|
1687
|
+
if (ts7.isBinaryExpression(node))
|
|
1688
|
+
return assignmentOperator2(node.operatorToken.kind) && matches(node.left);
|
|
1689
|
+
if (ts7.isPrefixUnaryExpression(node) || ts7.isPostfixUnaryExpression(node))
|
|
1690
|
+
return mutationUnary2(node) && matches(node.operand);
|
|
1691
|
+
return ts7.isDeleteExpression(node) && matches(node.expression);
|
|
1692
|
+
}
|
|
1693
|
+
function memberWrite(source, declaration) {
|
|
1694
|
+
const start = declaration.getStart(source);
|
|
1695
|
+
const end = declaration.getEnd();
|
|
1696
|
+
const matches = (expression) => {
|
|
1697
|
+
const receiver = receiverIdentifier(expression);
|
|
1698
|
+
return Boolean(receiver && receiver !== expression && identifierMatchesDeclaration(receiver, start, end));
|
|
1699
|
+
};
|
|
1700
|
+
let written = false;
|
|
1701
|
+
const visit = (node) => {
|
|
1702
|
+
if (written) return;
|
|
1703
|
+
written = nodeWritesMember(node, matches);
|
|
1704
|
+
if (!written) ts7.forEachChild(node, visit);
|
|
1705
|
+
};
|
|
1706
|
+
visit(source);
|
|
1707
|
+
return written;
|
|
391
1708
|
}
|
|
392
|
-
function
|
|
393
|
-
|
|
394
|
-
const first = expr.arguments[0];
|
|
395
|
-
return first && ts.isStringLiteral(first) ? first.text : void 0;
|
|
1709
|
+
function stableVariable(collection, node) {
|
|
1710
|
+
return ts7.isIdentifier(node.name) && ts7.isVariableDeclarationList(node.parent) && (node.parent.flags & ts7.NodeFlags.Const) !== 0 && !memberWrite(collection.source, node.name);
|
|
396
1711
|
}
|
|
397
|
-
function
|
|
398
|
-
if (
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
return
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (ts.isObjectLiteralExpression(arg)) {
|
|
413
|
-
const properties = [];
|
|
414
|
-
for (const prop of arg.properties) {
|
|
415
|
-
if (ts.isShorthandPropertyAssignment(prop)) properties.push({ kind: "shorthand", property: prop.name.text, argument: prop.name.text });
|
|
416
|
-
if (ts.isPropertyAssignment(prop)) {
|
|
417
|
-
const propName = nameOf(prop.name);
|
|
418
|
-
if (propName && ts.isIdentifier(prop.initializer)) properties.push({ kind: "property_assignment", property: propName, argument: prop.initializer.text });
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
return { kind: "object_literal", properties };
|
|
422
|
-
}
|
|
423
|
-
if (ts.isArrayLiteralExpression(arg)) {
|
|
424
|
-
const elements = arg.elements.flatMap((element, index) => ts.isIdentifier(element) ? [{ index, kind: "identifier", name: element.text }] : []);
|
|
425
|
-
return { kind: "array_literal", elements };
|
|
426
|
-
}
|
|
427
|
-
return { kind: "unsupported", expression: arg.getText(source) };
|
|
1712
|
+
function collectProxy(collection, node) {
|
|
1713
|
+
if (!ts7.isVariableDeclaration(node) || !stableVariable(collection, node) || !node.initializer || !ts7.isCallExpression(node.initializer) || !ts7.isPropertyAccessExpression(node.initializer.expression)) return;
|
|
1714
|
+
const callee = symbolCallName(node.initializer.expression);
|
|
1715
|
+
const binding = symbolImportReference(
|
|
1716
|
+
node.initializer.expression,
|
|
1717
|
+
collection.importBindings
|
|
1718
|
+
);
|
|
1719
|
+
if (!callee.member || !binding) return;
|
|
1720
|
+
appendNamed(collection.proxies, node.name.text, {
|
|
1721
|
+
importSource: binding.rawModuleSpecifier,
|
|
1722
|
+
importBinding: binding,
|
|
1723
|
+
factory: callee.expression,
|
|
1724
|
+
variableName: node.name.text,
|
|
1725
|
+
declarationStartOffset: node.name.getStart(collection.source),
|
|
1726
|
+
declarationEndOffset: node.name.getEnd()
|
|
428
1727
|
});
|
|
429
1728
|
}
|
|
1729
|
+
function propertyContainer2(declaration, propertyName3) {
|
|
1730
|
+
const property = propertyName3 && ts7.isPropertyDeclaration(declaration.parent) ? declaration.parent : void 0;
|
|
1731
|
+
return property && ts7.isClassLike(property.parent) ? property.parent : void 0;
|
|
1732
|
+
}
|
|
1733
|
+
function eligibleClassName(collection, className, imported) {
|
|
1734
|
+
if (builtInConstructors.has(className)) return false;
|
|
1735
|
+
return Boolean(imported || collection.declaredClasses.has(className));
|
|
1736
|
+
}
|
|
1737
|
+
function resolvedClassName(className, imported) {
|
|
1738
|
+
const importedName = imported?.importedName;
|
|
1739
|
+
return importedName && importedName !== "default" ? importedName : className;
|
|
1740
|
+
}
|
|
1741
|
+
function rememberInstance(collection, declaration, classExpression, propertyName3) {
|
|
1742
|
+
const className = classExpression.text;
|
|
1743
|
+
const imported = symbolImportReference(
|
|
1744
|
+
classExpression,
|
|
1745
|
+
collection.importBindings
|
|
1746
|
+
);
|
|
1747
|
+
if (!eligibleClassName(collection, className, imported)) return;
|
|
1748
|
+
const container = propertyContainer2(declaration, propertyName3);
|
|
1749
|
+
const variableName = propertyName3 ? `this.${propertyName3}` : declaration.getText();
|
|
1750
|
+
appendNamed(collection.instances, variableName, {
|
|
1751
|
+
className: resolvedClassName(className, imported),
|
|
1752
|
+
importSource: imported?.rawModuleSpecifier,
|
|
1753
|
+
importBinding: imported,
|
|
1754
|
+
propertyName: propertyName3,
|
|
1755
|
+
declarationStartOffset: declaration.getStart(collection.source),
|
|
1756
|
+
declarationEndOffset: declaration.getEnd(),
|
|
1757
|
+
containerStartOffset: container?.getStart(collection.source),
|
|
1758
|
+
containerEndOffset: container?.getEnd()
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
function collectVariableInstance(collection, node) {
|
|
1762
|
+
if (!ts7.isVariableDeclaration(node) || !stableVariable(collection, node))
|
|
1763
|
+
return;
|
|
1764
|
+
const initializer = node.initializer;
|
|
1765
|
+
if (initializer && ts7.isNewExpression(initializer) && ts7.isIdentifier(initializer.expression))
|
|
1766
|
+
rememberInstance(collection, node.name, initializer.expression);
|
|
1767
|
+
}
|
|
1768
|
+
function propertyName2(name) {
|
|
1769
|
+
return ts7.isIdentifier(name) || ts7.isStringLiteralLike(name) || ts7.isNumericLiteral(name) ? name.text : void 0;
|
|
1770
|
+
}
|
|
1771
|
+
function thisPropertyWrite(expression, name) {
|
|
1772
|
+
if (ts7.isPropertyAccessExpression(expression))
|
|
1773
|
+
return expression.expression.kind === ts7.SyntaxKind.ThisKeyword && expression.name.text === name;
|
|
1774
|
+
return ts7.isElementAccessExpression(expression) && expression.expression.kind === ts7.SyntaxKind.ThisKeyword && Boolean(expression.argumentExpression && ts7.isStringLiteralLike(expression.argumentExpression) && expression.argumentExpression.text === name);
|
|
1775
|
+
}
|
|
1776
|
+
function nodeWritesThisProperty(node, name) {
|
|
1777
|
+
if (ts7.isBinaryExpression(node))
|
|
1778
|
+
return assignmentOperator2(node.operatorToken.kind) && thisPropertyWrite(node.left, name);
|
|
1779
|
+
if (ts7.isPrefixUnaryExpression(node) || ts7.isPostfixUnaryExpression(node))
|
|
1780
|
+
return mutationUnary2(node) && thisPropertyWrite(node.operand, name);
|
|
1781
|
+
return ts7.isDeleteExpression(node) && thisPropertyWrite(node.expression, name);
|
|
1782
|
+
}
|
|
1783
|
+
function stableProperty(node, name) {
|
|
1784
|
+
const flags = ts7.getCombinedModifierFlags(node);
|
|
1785
|
+
if ((flags & (ts7.ModifierFlags.Private | ts7.ModifierFlags.Readonly)) === 0 || !ts7.isClassLike(node.parent)) return false;
|
|
1786
|
+
let written = false;
|
|
1787
|
+
const visit = (child) => {
|
|
1788
|
+
if (written || child === node) return;
|
|
1789
|
+
written = nodeWritesThisProperty(child, name);
|
|
1790
|
+
if (!written) ts7.forEachChild(child, visit);
|
|
1791
|
+
};
|
|
1792
|
+
visit(node.parent);
|
|
1793
|
+
return !written;
|
|
1794
|
+
}
|
|
1795
|
+
function collectPropertyInstance(collection, node) {
|
|
1796
|
+
if (!ts7.isPropertyDeclaration(node)) return;
|
|
1797
|
+
const initializer = node.initializer;
|
|
1798
|
+
if (!initializer || !ts7.isNewExpression(initializer) || !ts7.isIdentifier(initializer.expression)) return;
|
|
1799
|
+
const name = propertyName2(node.name);
|
|
1800
|
+
if (name && stableProperty(node, name)) rememberInstance(
|
|
1801
|
+
collection,
|
|
1802
|
+
node.name,
|
|
1803
|
+
initializer.expression,
|
|
1804
|
+
name
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1807
|
+
function collectDerivedSymbolContexts(collection) {
|
|
1808
|
+
const visit = (node) => {
|
|
1809
|
+
collectProxy(collection, node);
|
|
1810
|
+
collectVariableInstance(collection, node);
|
|
1811
|
+
collectPropertyInstance(collection, node);
|
|
1812
|
+
ts7.forEachChild(node, visit);
|
|
1813
|
+
};
|
|
1814
|
+
visit(collection.source);
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
// src/parsers/symbol-parser.ts
|
|
1818
|
+
function lineOf2(source, pos) {
|
|
1819
|
+
return source.getLineAndCharacterOfPosition(pos).line + 1;
|
|
1820
|
+
}
|
|
1821
|
+
function nameOf(node) {
|
|
1822
|
+
if (!node) return void 0;
|
|
1823
|
+
if (ts8.isIdentifier(node) || ts8.isStringLiteral(node) || ts8.isNumericLiteral(node)) return node.text;
|
|
1824
|
+
return void 0;
|
|
1825
|
+
}
|
|
1826
|
+
function isFunctionLike(node) {
|
|
1827
|
+
return ts8.isFunctionDeclaration(node) || ts8.isMethodDeclaration(node) || ts8.isFunctionExpression(node) || ts8.isArrowFunction(node);
|
|
1828
|
+
}
|
|
1829
|
+
function exported(node) {
|
|
1830
|
+
return Boolean(ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Export);
|
|
1831
|
+
}
|
|
1832
|
+
function isPublicClassMethod(node) {
|
|
1833
|
+
const flags = ts8.getCombinedModifierFlags(node);
|
|
1834
|
+
return (flags & ts8.ModifierFlags.Private) === 0 && (flags & ts8.ModifierFlags.Protected) === 0;
|
|
1835
|
+
}
|
|
1836
|
+
function exportDeclarations(source) {
|
|
1837
|
+
const exports = /* @__PURE__ */ new Map();
|
|
1838
|
+
const visit = (node) => {
|
|
1839
|
+
if (ts8.isExportDeclaration(node) && node.exportClause && ts8.isNamedExports(node.exportClause)) {
|
|
1840
|
+
for (const el of node.exportClause.elements) exports.set((el.propertyName ?? el.name).text, el.name.text);
|
|
1841
|
+
}
|
|
1842
|
+
ts8.forEachChild(node, visit);
|
|
1843
|
+
};
|
|
1844
|
+
visit(source);
|
|
1845
|
+
return exports;
|
|
1846
|
+
}
|
|
1847
|
+
function isObjectFunction(node) {
|
|
1848
|
+
return ts8.isFunctionExpression(node) || ts8.isArrowFunction(node) || ts8.isMethodDeclaration(node);
|
|
1849
|
+
}
|
|
1850
|
+
function requireSource(expr) {
|
|
1851
|
+
if (!ts8.isCallExpression(expr) || !ts8.isIdentifier(expr.expression) || expr.expression.text !== "require") return void 0;
|
|
1852
|
+
const first = expr.arguments[0];
|
|
1853
|
+
return first && ts8.isStringLiteral(first) ? first.text : void 0;
|
|
1854
|
+
}
|
|
430
1855
|
function bindingLocalName(name, initializer) {
|
|
431
|
-
if (
|
|
432
|
-
if (initializer &&
|
|
1856
|
+
if (ts8.isIdentifier(name)) return name.text;
|
|
1857
|
+
if (initializer && ts8.isIdentifier(initializer)) return initializer.text;
|
|
433
1858
|
return void 0;
|
|
434
1859
|
}
|
|
435
1860
|
function objectPatternAliases(pattern, parameter, source, lineNode) {
|
|
436
1861
|
return pattern.elements.flatMap((element) => {
|
|
437
|
-
if (element.dotDotDotToken ||
|
|
1862
|
+
if (element.dotDotDotToken || ts8.isObjectBindingPattern(element.name) || ts8.isArrayBindingPattern(element.name)) return [];
|
|
438
1863
|
const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
|
|
439
1864
|
if (!property) return [];
|
|
440
1865
|
const local = bindingLocalName(element.name, element.initializer);
|
|
441
|
-
return local ? [{ parameter, property, local, kind: "object_parameter_destructure", line:
|
|
1866
|
+
return local ? [{ parameter, property, local, kind: "object_parameter_destructure", line: lineOf2(source, lineNode.getStart(source)) }] : [];
|
|
442
1867
|
});
|
|
443
1868
|
}
|
|
444
1869
|
function parameterPropertyAliases(fn, source) {
|
|
445
|
-
const parameterNames = new Set(fn.parameters.flatMap((param) =>
|
|
1870
|
+
const parameterNames = new Set(fn.parameters.flatMap((param) => ts8.isIdentifier(param.name) ? [param.name.text] : []));
|
|
446
1871
|
if (!fn.body || parameterNames.size === 0) return [];
|
|
447
1872
|
const aliases = [];
|
|
448
1873
|
const addFromAssignment = (left, right, node) => {
|
|
449
|
-
if (!
|
|
1874
|
+
if (!ts8.isObjectLiteralExpression(left) || !ts8.isIdentifier(right) || !parameterNames.has(right.text)) return;
|
|
450
1875
|
for (const prop of left.properties) {
|
|
451
|
-
if (!
|
|
1876
|
+
if (!ts8.isPropertyAssignment(prop)) continue;
|
|
452
1877
|
const property = nameOf(prop.name);
|
|
453
|
-
if (property &&
|
|
1878
|
+
if (property && ts8.isIdentifier(prop.initializer)) aliases.push({ parameter: right.text, property, local: prop.initializer.text, kind: "object_parameter_destructure", line: lineOf2(source, node.getStart(source)) });
|
|
454
1879
|
}
|
|
455
1880
|
};
|
|
456
1881
|
const visit = (node) => {
|
|
457
|
-
if (
|
|
458
|
-
if (
|
|
459
|
-
|
|
1882
|
+
if (ts8.isVariableDeclaration(node) && ts8.isObjectBindingPattern(node.name) && node.initializer && ts8.isIdentifier(node.initializer) && parameterNames.has(node.initializer.text)) aliases.push(...objectPatternAliases(node.name, node.initializer.text, source, node));
|
|
1883
|
+
if (ts8.isBinaryExpression(node) && node.operatorToken.kind === ts8.SyntaxKind.EqualsToken) addFromAssignment(ts8.isParenthesizedExpression(node.left) ? node.left.expression : node.left, node.right, node);
|
|
1884
|
+
ts8.forEachChild(node, visit);
|
|
460
1885
|
};
|
|
461
1886
|
visit(fn.body);
|
|
462
1887
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -469,10 +1894,10 @@ function parameterPropertyAliases(fn, source) {
|
|
|
469
1894
|
}
|
|
470
1895
|
function parameterBindings(params) {
|
|
471
1896
|
return params.flatMap((param, index) => {
|
|
472
|
-
if (
|
|
473
|
-
if (
|
|
1897
|
+
if (ts8.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
|
|
1898
|
+
if (ts8.isObjectBindingPattern(param.name)) {
|
|
474
1899
|
const properties = param.name.elements.flatMap((element) => {
|
|
475
|
-
if (element.dotDotDotToken ||
|
|
1900
|
+
if (element.dotDotDotToken || ts8.isObjectBindingPattern(element.name) || ts8.isArrayBindingPattern(element.name)) return [];
|
|
476
1901
|
const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
|
|
477
1902
|
if (!property) return [];
|
|
478
1903
|
const local = bindingLocalName(element.name, element.initializer);
|
|
@@ -480,237 +1905,431 @@ function parameterBindings(params) {
|
|
|
480
1905
|
});
|
|
481
1906
|
return properties.length > 0 ? [{ index, kind: "object_pattern", properties }] : [];
|
|
482
1907
|
}
|
|
483
|
-
if (
|
|
484
|
-
const elements = param.name.elements.flatMap((element, elementIndex) =>
|
|
1908
|
+
if (ts8.isArrayBindingPattern(param.name)) {
|
|
1909
|
+
const elements = param.name.elements.flatMap((element, elementIndex) => ts8.isBindingElement(element) && !element.dotDotDotToken && ts8.isIdentifier(element.name) ? [{ index: elementIndex, local: element.name.text }] : []);
|
|
485
1910
|
return elements.length > 0 ? [{ index, kind: "array_pattern", elements }] : [];
|
|
486
1911
|
}
|
|
487
1912
|
return [];
|
|
488
1913
|
});
|
|
489
1914
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
1915
|
+
function symbolSourceEvidence(collection, node, options) {
|
|
1916
|
+
if (options.evidence) return options.evidence;
|
|
1917
|
+
if (options.classMemberExported) return {
|
|
1918
|
+
source: "exported_class_member",
|
|
1919
|
+
exportedClass: options.parentRoot,
|
|
1920
|
+
memberKind: ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Static ? "static_method" : "class_method"
|
|
1921
|
+
};
|
|
1922
|
+
if (options.classContainerExported && ts8.isMethodDeclaration(node) && isPublicClassMethod(node)) return {
|
|
1923
|
+
source: "exported_class_instance_member",
|
|
1924
|
+
exportedClass: options.parentRoot,
|
|
1925
|
+
memberKind: "class_method"
|
|
1926
|
+
};
|
|
1927
|
+
if (options.declaredExportName) return {
|
|
1928
|
+
exportedName: options.declaredExportName,
|
|
1929
|
+
source: "export_declaration"
|
|
1930
|
+
};
|
|
1931
|
+
return options.objectExported ? { exportedName: options.qualifiedName, source: "exported_object_literal" } : {};
|
|
1932
|
+
}
|
|
1933
|
+
function executableEvidence(node, source) {
|
|
1934
|
+
if (!isFunctionLike(node)) return {};
|
|
1935
|
+
const bindings = parameterBindings(node.parameters);
|
|
1936
|
+
const parameters = bindings.flatMap((binding) => binding.kind === "identifier" ? [binding.name] : []);
|
|
1937
|
+
const aliases = parameterPropertyAliases(node, source);
|
|
1938
|
+
return {
|
|
1939
|
+
executableBodyEligibility: executableBodyEligibility(node, source),
|
|
1940
|
+
...bindings.length > 0 ? { parameters, parameterBindings: bindings } : {},
|
|
1941
|
+
...aliases.length > 0 ? { parameterPropertyAliases: aliases } : {}
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
function exportedClassMember(collection, kind, parentName, parentRoot, node) {
|
|
1945
|
+
if (kind !== "method" || !parentName || !collection.exportedClasses.has(parentRoot) || !ts8.isMethodDeclaration(node)) return false;
|
|
1946
|
+
return Boolean(ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Static) && isPublicClassMethod(node);
|
|
1947
|
+
}
|
|
1948
|
+
function symbolNames(collection, kind, localName, node, parentName, exportedName) {
|
|
1949
|
+
const parentRoot = parentName?.split(".")[0] ?? "";
|
|
1950
|
+
const declaredExportName = exportedName ?? collection.exportNames.get(
|
|
1951
|
+
parentName ? parentRoot : localName
|
|
499
1952
|
);
|
|
500
|
-
const
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
1953
|
+
const qualifiedName = parentName ? `${parentName}.${localName}` : localName;
|
|
1954
|
+
const objectExported = Boolean(
|
|
1955
|
+
parentName && collection.objectExports.has(parentRoot)
|
|
1956
|
+
);
|
|
1957
|
+
const classMemberExported = exportedClassMember(
|
|
1958
|
+
collection,
|
|
1959
|
+
kind,
|
|
1960
|
+
parentName,
|
|
1961
|
+
parentRoot,
|
|
1962
|
+
node
|
|
1963
|
+
);
|
|
1964
|
+
const classContainerExported = Boolean(
|
|
1965
|
+
parentName && collection.exportedClasses.has(parentRoot)
|
|
1966
|
+
);
|
|
1967
|
+
return {
|
|
1968
|
+
parentRoot,
|
|
1969
|
+
declaredExportName,
|
|
1970
|
+
qualifiedName,
|
|
1971
|
+
objectExported,
|
|
1972
|
+
classContainerExported,
|
|
1973
|
+
classMemberExported,
|
|
1974
|
+
effectiveName: classMemberExported || objectExported ? qualifiedName : declaredExportName
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
function addExecutableSymbol(collection, kind, localName, node, parentName, exportedName, evidence) {
|
|
1978
|
+
const names = symbolNames(
|
|
1979
|
+
collection,
|
|
1980
|
+
kind,
|
|
1981
|
+
localName,
|
|
1982
|
+
node,
|
|
1983
|
+
parentName,
|
|
1984
|
+
exportedName
|
|
1985
|
+
);
|
|
1986
|
+
const sourceEvidence = symbolSourceEvidence(collection, node, {
|
|
1987
|
+
parentRoot: names.parentRoot,
|
|
1988
|
+
qualifiedName: names.qualifiedName,
|
|
1989
|
+
declaredExportName: names.declaredExportName,
|
|
1990
|
+
classContainerExported: names.classContainerExported,
|
|
1991
|
+
classMemberExported: names.classMemberExported,
|
|
1992
|
+
objectExported: names.objectExported,
|
|
1993
|
+
evidence
|
|
1994
|
+
});
|
|
1995
|
+
collection.symbols.push({
|
|
1996
|
+
kind,
|
|
1997
|
+
localName: kind === "object_method" ? names.qualifiedName : localName,
|
|
1998
|
+
exportedName: names.effectiveName,
|
|
1999
|
+
qualifiedName: names.qualifiedName,
|
|
2000
|
+
sourceFile: collection.sourceFile,
|
|
2001
|
+
startLine: lineOf2(collection.source, node.getStart(collection.source)),
|
|
2002
|
+
endLine: lineOf2(collection.source, node.getEnd()),
|
|
2003
|
+
startOffset: node.getStart(collection.source),
|
|
2004
|
+
endOffset: node.getEnd(),
|
|
2005
|
+
exported: exported(node) || Boolean(names.effectiveName),
|
|
2006
|
+
importExportEvidence: {
|
|
2007
|
+
...sourceEvidence,
|
|
2008
|
+
...executableEvidence(node, collection.source)
|
|
542
2009
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
function addAliasSymbol(collection, objectName, propertyName3, node) {
|
|
2013
|
+
collection.symbols.push({
|
|
2014
|
+
kind: "object_alias",
|
|
2015
|
+
localName: propertyName3,
|
|
2016
|
+
exportedName: propertyName3,
|
|
2017
|
+
qualifiedName: `${objectName}.${propertyName3}`,
|
|
2018
|
+
sourceFile: collection.sourceFile,
|
|
2019
|
+
startLine: lineOf2(collection.source, node.getStart(collection.source)),
|
|
2020
|
+
endLine: lineOf2(collection.source, node.getEnd()),
|
|
2021
|
+
startOffset: node.getStart(collection.source),
|
|
2022
|
+
endOffset: node.getEnd(),
|
|
2023
|
+
exported: true,
|
|
2024
|
+
importExportEvidence: {
|
|
2025
|
+
source: "exported_object_shorthand",
|
|
2026
|
+
objectName,
|
|
2027
|
+
propertyName: propertyName3,
|
|
2028
|
+
targetImportSource: collection.imports.get(propertyName3)
|
|
551
2029
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const
|
|
556
|
-
if (
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
function collectImportSources(collection) {
|
|
2033
|
+
const visit = (node) => {
|
|
2034
|
+
if (ts8.isImportDeclaration(node) && ts8.isStringLiteral(node.moduleSpecifier))
|
|
2035
|
+
collectEsmImportSources(collection.imports, node);
|
|
2036
|
+
if (ts8.isVariableStatement(node))
|
|
2037
|
+
collectCjsImportSources(collection.imports, node);
|
|
2038
|
+
ts8.forEachChild(node, visit);
|
|
2039
|
+
};
|
|
2040
|
+
visit(collection.source);
|
|
2041
|
+
}
|
|
2042
|
+
function collectEsmImportSources(imports, node) {
|
|
2043
|
+
if (!ts8.isStringLiteral(node.moduleSpecifier)) return;
|
|
2044
|
+
const source = node.moduleSpecifier.text;
|
|
2045
|
+
const clause = node.importClause;
|
|
2046
|
+
if (clause?.name) imports.set(clause.name.text, source);
|
|
2047
|
+
const named = clause?.namedBindings;
|
|
2048
|
+
if (named && ts8.isNamedImports(named))
|
|
2049
|
+
for (const item of named.elements) imports.set(item.name.text, source);
|
|
2050
|
+
if (named && ts8.isNamespaceImport(named))
|
|
2051
|
+
imports.set(named.name.text, source);
|
|
2052
|
+
}
|
|
2053
|
+
function collectCjsImportSources(imports, node) {
|
|
2054
|
+
for (const declaration of node.declarationList.declarations) {
|
|
2055
|
+
const source = declaration.initializer ? requireSource(declaration.initializer) : void 0;
|
|
2056
|
+
if (!source) continue;
|
|
2057
|
+
if (ts8.isIdentifier(declaration.name))
|
|
2058
|
+
imports.set(declaration.name.text, source);
|
|
2059
|
+
if (ts8.isObjectBindingPattern(declaration.name)) {
|
|
2060
|
+
for (const item of declaration.name.elements)
|
|
2061
|
+
if (ts8.isIdentifier(item.name)) imports.set(item.name.text, source);
|
|
561
2062
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
if (
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
function classPropertySymbol(collection, node, parentClass) {
|
|
2066
|
+
const initializer = node.initializer;
|
|
2067
|
+
const localName = nameOf(node.name);
|
|
2068
|
+
if (!localName || !initializer || !ts8.isArrowFunction(initializer) && !ts8.isFunctionExpression(initializer)) return;
|
|
2069
|
+
const staticPublic = publicStaticProperty(collection, node, parentClass);
|
|
2070
|
+
const memberKind = propertyMemberKind(initializer, staticPublic);
|
|
2071
|
+
addExecutableSymbol(
|
|
2072
|
+
collection,
|
|
2073
|
+
"method",
|
|
2074
|
+
localName,
|
|
2075
|
+
initializer,
|
|
2076
|
+
parentClass,
|
|
2077
|
+
staticPublic ? `${parentClass}.${localName}` : void 0,
|
|
2078
|
+
staticPublic ? { source: "exported_class_member", exportedClass: parentClass, memberKind } : { source: "class_property_function", memberKind }
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
function publicStaticProperty(collection, node, parentClass) {
|
|
2082
|
+
const flags = ts8.getCombinedModifierFlags(node);
|
|
2083
|
+
return collection.exportedClasses.has(parentClass) && Boolean(flags & ts8.ModifierFlags.Static) && (flags & ts8.ModifierFlags.Private) === 0 && (flags & ts8.ModifierFlags.Protected) === 0;
|
|
2084
|
+
}
|
|
2085
|
+
function propertyMemberKind(initializer, staticPublic) {
|
|
2086
|
+
if (ts8.isArrowFunction(initializer))
|
|
2087
|
+
return staticPublic ? "static_arrow_function" : "arrow_function_property";
|
|
2088
|
+
return staticPublic ? "static_function_expression" : "function_expression_property";
|
|
2089
|
+
}
|
|
2090
|
+
function objectCallable(property) {
|
|
2091
|
+
if (ts8.isMethodDeclaration(property)) return property;
|
|
2092
|
+
return ts8.isPropertyAssignment(property) && isObjectFunction(property.initializer) ? property.initializer : void 0;
|
|
2093
|
+
}
|
|
2094
|
+
function objectLiteralSymbols(collection, objectName, object, objectIsExported) {
|
|
2095
|
+
if (objectIsExported) collection.objectExports.add(objectName);
|
|
2096
|
+
for (const property of object.properties) {
|
|
2097
|
+
if (objectIsExported && ts8.isShorthandPropertyAssignment(property))
|
|
2098
|
+
addAliasSymbol(collection, objectName, property.name.text, property.name);
|
|
2099
|
+
const callable = objectCallable(property);
|
|
2100
|
+
const propertyName3 = callable ? nameOf(property.name) : void 0;
|
|
2101
|
+
if (callable && propertyName3)
|
|
2102
|
+
addExecutableSymbol(
|
|
2103
|
+
collection,
|
|
2104
|
+
"object_method",
|
|
2105
|
+
propertyName3,
|
|
2106
|
+
callable,
|
|
2107
|
+
objectName
|
|
2108
|
+
);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
function variableSymbols(collection, node) {
|
|
2112
|
+
for (const declaration of node.declarationList.declarations) {
|
|
2113
|
+
const localName = nameOf(declaration.name);
|
|
2114
|
+
const initializer = declaration.initializer;
|
|
2115
|
+
if (!localName || !initializer) continue;
|
|
2116
|
+
if (isFunctionLike(initializer)) addExecutableSymbol(
|
|
2117
|
+
collection,
|
|
2118
|
+
"function",
|
|
2119
|
+
localName,
|
|
2120
|
+
initializer,
|
|
2121
|
+
void 0,
|
|
2122
|
+
exported(node) ? localName : collection.exportNames.get(localName)
|
|
2123
|
+
);
|
|
2124
|
+
if (ts8.isObjectLiteralExpression(initializer))
|
|
2125
|
+
objectLiteralSymbols(
|
|
2126
|
+
collection,
|
|
2127
|
+
localName,
|
|
2128
|
+
initializer,
|
|
2129
|
+
exported(node) || collection.exportNames.has(localName)
|
|
2130
|
+
);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
function collectClassDeclaration(collection, node) {
|
|
2134
|
+
if (!ts8.isClassDeclaration(node) || !node.name) return false;
|
|
2135
|
+
collection.declaredClasses.add(node.name.text);
|
|
2136
|
+
if (exported(node) || collection.exportNames.has(node.name.text))
|
|
2137
|
+
collection.exportedClasses.add(node.name.text);
|
|
2138
|
+
for (const member of node.members)
|
|
2139
|
+
visitDeclaredSymbol(collection, member, node.name.text);
|
|
2140
|
+
return true;
|
|
2141
|
+
}
|
|
2142
|
+
function collectMethodDeclaration(collection, node, parentClass) {
|
|
2143
|
+
if (!ts8.isMethodDeclaration(node)) return false;
|
|
2144
|
+
const localName = nameOf(node.name);
|
|
2145
|
+
if (localName)
|
|
2146
|
+
addExecutableSymbol(collection, "method", localName, node, parentClass);
|
|
2147
|
+
return true;
|
|
2148
|
+
}
|
|
2149
|
+
function visitDeclaredSymbol(collection, node, parentClass) {
|
|
2150
|
+
if (collectClassDeclaration(collection, node)) return;
|
|
2151
|
+
if (collectMethodDeclaration(collection, node, parentClass)) return;
|
|
2152
|
+
if (ts8.isPropertyDeclaration(node)) {
|
|
2153
|
+
if (parentClass) classPropertySymbol(collection, node, parentClass);
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
if (ts8.isFunctionDeclaration(node) && node.name) {
|
|
2157
|
+
addExecutableSymbol(
|
|
2158
|
+
collection,
|
|
2159
|
+
"function",
|
|
2160
|
+
node.name.text,
|
|
2161
|
+
node,
|
|
2162
|
+
void 0,
|
|
2163
|
+
exported(node) ? node.name.text : void 0
|
|
2164
|
+
);
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
if (ts8.isVariableStatement(node)) {
|
|
2168
|
+
variableSymbols(collection, node);
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
ts8.forEachChild(node, (child) => visitDeclaredSymbol(collection, child, parentClass));
|
|
2172
|
+
}
|
|
2173
|
+
function collectDeclaredSymbols(collection) {
|
|
2174
|
+
visitDeclaredSymbol(collection, collection.source);
|
|
2175
|
+
}
|
|
2176
|
+
function isTopLevelCallback(node) {
|
|
2177
|
+
if (!ts8.isArrowFunction(node) && !ts8.isFunctionExpression(node) || !ts8.isCallExpression(node.parent)) return false;
|
|
2178
|
+
const callee = symbolCallName(node.parent.expression);
|
|
2179
|
+
const member = callee.member ?? callee.local;
|
|
2180
|
+
return Boolean(member && [
|
|
2181
|
+
"bootstrap",
|
|
2182
|
+
"served",
|
|
2183
|
+
"connect",
|
|
2184
|
+
"on",
|
|
2185
|
+
"once",
|
|
2186
|
+
"use",
|
|
2187
|
+
"get",
|
|
2188
|
+
"post",
|
|
2189
|
+
"put",
|
|
2190
|
+
"patch",
|
|
2191
|
+
"delete",
|
|
2192
|
+
"subscribe"
|
|
2193
|
+
].includes(member));
|
|
2194
|
+
}
|
|
2195
|
+
function collectCallbackSymbols(collection) {
|
|
2196
|
+
const visit = (node) => {
|
|
2197
|
+
if (isTopLevelCallback(node) && containsSupportedOutboundCall(node, collection.classifiedCalls)) {
|
|
2198
|
+
const startLine = lineOf2(
|
|
2199
|
+
collection.source,
|
|
2200
|
+
node.getStart(collection.source)
|
|
2201
|
+
);
|
|
608
2202
|
const name = `callback:${startLine}`;
|
|
609
|
-
symbols.push({
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
}
|
|
624
|
-
if (eventSubscriptionOffsets.has(`${node.getStart(source)}:${node.getEnd()}`)) {
|
|
625
|
-
const startLine = lineOf(source, node.getStart(source));
|
|
626
|
-
const handlerArgument = node.arguments[1];
|
|
627
|
-
const target = handlerArgument ? handlerReferenceTarget(
|
|
628
|
-
handlerArgument,
|
|
629
|
-
source,
|
|
630
|
-
imports,
|
|
631
|
-
namespaceImports
|
|
632
|
-
) : void 0;
|
|
633
|
-
const anchor = nearest(symbols, startLine);
|
|
634
|
-
if (target && anchor) calls.push({
|
|
635
|
-
callerQualifiedName: anchor.qualifiedName,
|
|
636
|
-
calleeExpression: target.calleeExpression,
|
|
637
|
-
calleeLocalName: target.calleeLocalName,
|
|
638
|
-
importSource: target.importSource,
|
|
639
|
-
sourceFile,
|
|
640
|
-
sourceLine: startLine,
|
|
641
|
-
callSiteStartOffset: node.getStart(source),
|
|
642
|
-
callSiteEndOffset: node.getEnd(),
|
|
643
|
-
callRole: "event_subscribe_handler",
|
|
644
|
-
evidence: {
|
|
645
|
-
relation: target.relation,
|
|
646
|
-
caller: anchor.qualifiedName,
|
|
647
|
-
targetName: target.calleeLocalName,
|
|
648
|
-
...target.wrapperFunction ? { wrapperFunction: target.wrapperFunction } : {},
|
|
649
|
-
factOrigin: "event_subscribe_handler_reference"
|
|
650
|
-
}
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
ts.forEachChild(node, visitEventRegistrationSymbols);
|
|
655
|
-
};
|
|
656
|
-
visitEventRegistrationSymbols(source);
|
|
657
|
-
const visitProxyVariables = (node) => {
|
|
658
|
-
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isPropertyAccessExpression(node.initializer.expression)) {
|
|
659
|
-
const callee = callName(node.initializer.expression);
|
|
660
|
-
const importSource = callee.local ? imports.get(callee.local) : void 0;
|
|
661
|
-
if (callee.member && importSource && isRelativeImport(importSource)) proxyVariables.set(node.name.text, { importSource, factory: callee.expression, variableName: node.name.text });
|
|
2203
|
+
collection.symbols.push({
|
|
2204
|
+
kind: "callback",
|
|
2205
|
+
localName: name,
|
|
2206
|
+
qualifiedName: `module:${collection.sourceFile}#${name}`,
|
|
2207
|
+
sourceFile: collection.sourceFile,
|
|
2208
|
+
startLine,
|
|
2209
|
+
endLine: lineOf2(collection.source, node.getEnd()),
|
|
2210
|
+
startOffset: node.getStart(collection.source),
|
|
2211
|
+
endOffset: node.getEnd(),
|
|
2212
|
+
exported: false,
|
|
2213
|
+
importExportEvidence: {
|
|
2214
|
+
source: "synthetic_outbound_callback",
|
|
2215
|
+
callbackLine: startLine
|
|
2216
|
+
}
|
|
2217
|
+
});
|
|
662
2218
|
}
|
|
663
|
-
|
|
2219
|
+
ts8.forEachChild(node, visit);
|
|
664
2220
|
};
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
2221
|
+
visit(collection.source);
|
|
2222
|
+
}
|
|
2223
|
+
function createCollection(source, sourceFile2, classifiedCalls) {
|
|
2224
|
+
return {
|
|
2225
|
+
source,
|
|
2226
|
+
sourceFile: sourceFile2,
|
|
2227
|
+
classifiedCalls,
|
|
2228
|
+
symbols: [],
|
|
2229
|
+
imports: /* @__PURE__ */ new Map(),
|
|
2230
|
+
importBindings: collectSymbolImportBindings(source),
|
|
2231
|
+
exportNames: exportDeclarations(source),
|
|
2232
|
+
objectExports: /* @__PURE__ */ new Set(),
|
|
2233
|
+
exportedClasses: /* @__PURE__ */ new Set(),
|
|
2234
|
+
declaredClasses: /* @__PURE__ */ new Set(),
|
|
2235
|
+
proxies: /* @__PURE__ */ new Map(),
|
|
2236
|
+
instances: /* @__PURE__ */ new Map()
|
|
669
2237
|
};
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
2238
|
+
}
|
|
2239
|
+
function populateCollection(collection) {
|
|
2240
|
+
collectImportSources(collection);
|
|
2241
|
+
collectDeclaredSymbols(collection);
|
|
2242
|
+
collectCallbackSymbols(collection);
|
|
2243
|
+
collectDerivedSymbolContexts(collection);
|
|
2244
|
+
}
|
|
2245
|
+
async function sourceFile(repoPath, filePath, context) {
|
|
2246
|
+
const snapshot = context?.get(filePath);
|
|
2247
|
+
const text = snapshot?.text ?? await fs4.readFile(path4.join(repoPath, filePath), "utf8");
|
|
2248
|
+
return snapshot?.sourceFile() ?? ts8.createSourceFile(
|
|
2249
|
+
filePath,
|
|
2250
|
+
text,
|
|
2251
|
+
ts8.ScriptTarget.Latest,
|
|
2252
|
+
true,
|
|
2253
|
+
filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS
|
|
2254
|
+
);
|
|
2255
|
+
}
|
|
2256
|
+
async function parseExecutableSymbols(repoPath, filePath, context, preparedOutboundCalls) {
|
|
2257
|
+
const source = await sourceFile(repoPath, filePath, context);
|
|
2258
|
+
const normalizedFile = normalizePath(filePath);
|
|
2259
|
+
const classified = preparedOutboundCalls ?? classifyOutboundCallsInSource(source, normalizedFile);
|
|
2260
|
+
const collection = createCollection(source, normalizedFile, classified);
|
|
2261
|
+
populateCollection(collection);
|
|
2262
|
+
const calls = collectSymbolCallFacts({
|
|
2263
|
+
source,
|
|
2264
|
+
sourceFile: normalizedFile,
|
|
2265
|
+
symbols: collection.symbols,
|
|
2266
|
+
imports: collection.imports,
|
|
2267
|
+
importBindings: collection.importBindings,
|
|
2268
|
+
proxies: collection.proxies,
|
|
2269
|
+
instances: collection.instances
|
|
2270
|
+
});
|
|
2271
|
+
const events = reconcileEventSubscriptions(
|
|
2272
|
+
source,
|
|
2273
|
+
classified,
|
|
2274
|
+
collection.symbols,
|
|
2275
|
+
calls
|
|
2276
|
+
);
|
|
2277
|
+
return {
|
|
2278
|
+
symbols: events.symbols,
|
|
2279
|
+
calls: reconcileSymbolCallOwners(events.calls, events.symbols)
|
|
711
2280
|
};
|
|
712
|
-
|
|
713
|
-
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
// src/parsers/008-package-surface-publication.ts
|
|
2284
|
+
function symbolIdentity(symbol) {
|
|
2285
|
+
return [
|
|
2286
|
+
symbol.sourceFile,
|
|
2287
|
+
symbol.kind,
|
|
2288
|
+
symbol.qualifiedName,
|
|
2289
|
+
symbol.startOffset,
|
|
2290
|
+
symbol.endOffset
|
|
2291
|
+
].join("\0");
|
|
2292
|
+
}
|
|
2293
|
+
function exposureBySymbol(analysis) {
|
|
2294
|
+
return new Map(analysis.symbols.map((item) => [
|
|
2295
|
+
symbolIdentity({
|
|
2296
|
+
sourceFile: item.target.sourceFile,
|
|
2297
|
+
kind: item.target.kind,
|
|
2298
|
+
qualifiedName: item.target.qualifiedName,
|
|
2299
|
+
startOffset: item.target.startOffset,
|
|
2300
|
+
endOffset: item.target.endOffset
|
|
2301
|
+
}),
|
|
2302
|
+
item
|
|
2303
|
+
]));
|
|
2304
|
+
}
|
|
2305
|
+
function analyzeRepositoryPackageSurface(facts, manifest, sources) {
|
|
2306
|
+
const modules = sources.entries().filter((snapshot) => /\.[jt]s$/.test(snapshot.filePath)).map((snapshot) => ({
|
|
2307
|
+
sourceFile: normalizePath(snapshot.filePath),
|
|
2308
|
+
source: snapshot.sourceFile()
|
|
2309
|
+
}));
|
|
2310
|
+
return analyzePackagePublicSurface(facts.packageName, manifest, modules);
|
|
2311
|
+
}
|
|
2312
|
+
function mergePackageSymbolEvidence(symbols, analysis) {
|
|
2313
|
+
const exposures = exposureBySymbol(analysis);
|
|
2314
|
+
return symbols.map((symbol) => {
|
|
2315
|
+
const exposure = exposures.get(symbolIdentity(symbol));
|
|
2316
|
+
if (!exposure) return symbol;
|
|
2317
|
+
return {
|
|
2318
|
+
...symbol,
|
|
2319
|
+
importExportEvidence: {
|
|
2320
|
+
...symbol.importExportEvidence ?? {},
|
|
2321
|
+
packagePublicSurface: {
|
|
2322
|
+
schema: analysis.surface.schema,
|
|
2323
|
+
recordCap: analysis.surface.recordCap,
|
|
2324
|
+
bodyEligibility: exposure.target.bodyEligibility,
|
|
2325
|
+
exposures: exposure.exposures,
|
|
2326
|
+
exposureTotal: exposure.exposureTotal,
|
|
2327
|
+
shownExposureCount: exposure.shownExposureCount,
|
|
2328
|
+
omittedExposureCount: exposure.omittedExposureCount
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
};
|
|
2332
|
+
});
|
|
714
2333
|
}
|
|
715
2334
|
|
|
716
2335
|
// src/utils/hashing.ts
|
|
@@ -720,6 +2339,130 @@ function sha256Text(text) {
|
|
|
720
2339
|
return createHash("sha256").update(text).digest("hex");
|
|
721
2340
|
}
|
|
722
2341
|
|
|
2342
|
+
// src/db/004-package-target-invalidation.ts
|
|
2343
|
+
var resolverKeys = /* @__PURE__ */ new Set([
|
|
2344
|
+
"candidateStrategy",
|
|
2345
|
+
"candidateCount",
|
|
2346
|
+
"eligibleCandidateCount",
|
|
2347
|
+
"selectedCandidateCount",
|
|
2348
|
+
"candidateSetComplete",
|
|
2349
|
+
"resolvedModulePath",
|
|
2350
|
+
"resolvedTargetRepositoryId",
|
|
2351
|
+
"unresolvedReason",
|
|
2352
|
+
"targetRepositoryCandidateCount",
|
|
2353
|
+
"targetRepositoryCandidates",
|
|
2354
|
+
"shownTargetRepositoryCandidateCount",
|
|
2355
|
+
"omittedTargetRepositoryCandidateCount",
|
|
2356
|
+
"publicSurface"
|
|
2357
|
+
]);
|
|
2358
|
+
function record(value) {
|
|
2359
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2360
|
+
}
|
|
2361
|
+
function parsedEvidence(value) {
|
|
2362
|
+
if (typeof value !== "string") return void 0;
|
|
2363
|
+
try {
|
|
2364
|
+
return record(JSON.parse(value));
|
|
2365
|
+
} catch {
|
|
2366
|
+
return void 0;
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
function packageName(evidence) {
|
|
2370
|
+
return parsePackageImportReference(evidence.importBinding)?.requestedPackageName ?? void 0;
|
|
2371
|
+
}
|
|
2372
|
+
function packageCallEvidenceValid(evidence) {
|
|
2373
|
+
const binding = record(evidence.importBinding);
|
|
2374
|
+
const classified = evidence.relation === "package_import" || binding?.moduleKind === "package";
|
|
2375
|
+
return !classified || packageName(evidence) !== void 0;
|
|
2376
|
+
}
|
|
2377
|
+
function currentCalls(db, workspaceId, targetRepoId) {
|
|
2378
|
+
const rows = db.prepare(`SELECT sc.id,sc.repo_id repoId,
|
|
2379
|
+
sc.evidence_json evidenceJson FROM symbol_calls sc
|
|
2380
|
+
JOIN repositories r ON r.id=sc.repo_id
|
|
2381
|
+
WHERE r.workspace_id=? AND r.id<>? AND r.fact_analyzer_version=?
|
|
2382
|
+
ORDER BY sc.id`).all(workspaceId, targetRepoId, ANALYZER_VERSION);
|
|
2383
|
+
return rows.flatMap((row) => {
|
|
2384
|
+
const evidence = parsedEvidence(row.evidenceJson);
|
|
2385
|
+
if (!evidence || typeof row.id !== "number" || typeof row.repoId !== "number")
|
|
2386
|
+
throw new Error("invalid_current_package_import_evidence");
|
|
2387
|
+
if (!packageCallEvidenceValid(evidence))
|
|
2388
|
+
throw new Error("invalid_current_package_import_evidence");
|
|
2389
|
+
return evidence.relation === "package_import" || record(evidence.importBinding)?.moduleKind === "package" ? [{ id: row.id, repoId: row.repoId, evidence }] : [];
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2392
|
+
function pendingEvidence(evidence) {
|
|
2393
|
+
const parser = Object.fromEntries(Object.entries(evidence).filter(
|
|
2394
|
+
([key]) => !resolverKeys.has(key)
|
|
2395
|
+
));
|
|
2396
|
+
if (!packageName(parser))
|
|
2397
|
+
throw new Error("invalid_current_package_import_evidence");
|
|
2398
|
+
return JSON.stringify({
|
|
2399
|
+
...parser,
|
|
2400
|
+
candidateStrategy: "package_import_pending",
|
|
2401
|
+
candidateCount: 0,
|
|
2402
|
+
eligibleCandidateCount: 0,
|
|
2403
|
+
selectedCandidateCount: 0,
|
|
2404
|
+
candidateSetComplete: false,
|
|
2405
|
+
unresolvedReason: "package_resolution_pending"
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
function targetWorkspace(db, repoId) {
|
|
2409
|
+
const row = db.prepare(`SELECT workspace_id workspaceId,
|
|
2410
|
+
package_name packageName FROM repositories WHERE id=?`).get(repoId);
|
|
2411
|
+
if (typeof row?.workspaceId !== "number")
|
|
2412
|
+
throw new Error("Repository target is missing its workspace");
|
|
2413
|
+
return {
|
|
2414
|
+
workspaceId: row.workspaceId,
|
|
2415
|
+
packageName: typeof row.packageName === "string" || row.packageName === null ? row.packageName : void 0
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
function packageIdentityChanged(previous, next) {
|
|
2419
|
+
const previousName = typeof previous === "string" ? previous : null;
|
|
2420
|
+
const nextName = typeof next === "string" ? next : null;
|
|
2421
|
+
return previousName !== nextName;
|
|
2422
|
+
}
|
|
2423
|
+
function invalidatePackageTargetFacts(db, targetRepoId, newPackageName, batch) {
|
|
2424
|
+
const target = targetWorkspace(db, targetRepoId);
|
|
2425
|
+
const names = new Set(
|
|
2426
|
+
[target.packageName, newPackageName].filter(
|
|
2427
|
+
(value) => typeof value === "string" && value.length > 0
|
|
2428
|
+
)
|
|
2429
|
+
);
|
|
2430
|
+
if (names.size === 0) return;
|
|
2431
|
+
const update = db.prepare(`UPDATE symbol_calls SET callee_symbol_id=NULL,
|
|
2432
|
+
status='unresolved',unresolved_reason='package_resolution_pending',
|
|
2433
|
+
evidence_json=? WHERE id=?`);
|
|
2434
|
+
let matched = false;
|
|
2435
|
+
for (const call of currentCalls(db, target.workspaceId, targetRepoId)) {
|
|
2436
|
+
const requested = packageName(call.evidence);
|
|
2437
|
+
if (!requested || !names.has(requested)) continue;
|
|
2438
|
+
update.run(pendingEvidence(call.evidence), call.id);
|
|
2439
|
+
batch.affectedCallerRepoIds.add(call.repoId);
|
|
2440
|
+
matched = true;
|
|
2441
|
+
}
|
|
2442
|
+
if (matched || packageIdentityChanged(
|
|
2443
|
+
target.packageName,
|
|
2444
|
+
newPackageName
|
|
2445
|
+
)) batch.affectedWorkspaceIds.add(target.workspaceId);
|
|
2446
|
+
}
|
|
2447
|
+
function createPackageInvalidationBatch(publishingRepoIds) {
|
|
2448
|
+
return {
|
|
2449
|
+
publishingRepoIds: new Set(publishingRepoIds),
|
|
2450
|
+
affectedCallerRepoIds: /* @__PURE__ */ new Set(),
|
|
2451
|
+
affectedWorkspaceIds: /* @__PURE__ */ new Set()
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
function finalizePackageTargetInvalidations(db, batch) {
|
|
2455
|
+
const increment = db.prepare(`UPDATE repositories
|
|
2456
|
+
SET fact_generation=fact_generation+1 WHERE id=?`);
|
|
2457
|
+
for (const repoId of batch.affectedCallerRepoIds)
|
|
2458
|
+
if (!batch.publishingRepoIds.has(repoId)) increment.run(repoId);
|
|
2459
|
+
const stale = db.prepare(`UPDATE repositories
|
|
2460
|
+
SET graph_stale_reason='package_target_facts_changed',
|
|
2461
|
+
graph_stale_at=datetime('now') WHERE workspace_id=?`);
|
|
2462
|
+
for (const workspaceId of batch.affectedWorkspaceIds)
|
|
2463
|
+
stale.run(workspaceId);
|
|
2464
|
+
}
|
|
2465
|
+
|
|
723
2466
|
// src/indexer/repository-indexer.ts
|
|
724
2467
|
async function prepareRepositoryIndex(repo, force, instrumentation) {
|
|
725
2468
|
const sourceFiles = await findSourceFiles(repo.absolute_path);
|
|
@@ -739,24 +2482,51 @@ async function prepareRepositoryIndex(repo, force, instrumentation) {
|
|
|
739
2482
|
packageSnapshot.rawText
|
|
740
2483
|
);
|
|
741
2484
|
if (!force && repo.fingerprint === fingerprint) return { repo, fileCount: 0, diagnosticCount: 0, skipped: true };
|
|
742
|
-
const
|
|
2485
|
+
const parsedFacts = await parseAllSourceFacts(repo.absolute_path, sources);
|
|
2486
|
+
const packageSurface = analyzeRepositoryPackageSurface(
|
|
2487
|
+
packageFacts,
|
|
2488
|
+
packageSnapshot.manifest,
|
|
2489
|
+
sources
|
|
2490
|
+
);
|
|
2491
|
+
const parsed = {
|
|
2492
|
+
...parsedFacts,
|
|
2493
|
+
symbols: mergePackageSymbolEvidence(parsedFacts.symbols, packageSurface)
|
|
2494
|
+
};
|
|
743
2495
|
return {
|
|
744
2496
|
repo,
|
|
745
2497
|
packageFacts,
|
|
746
2498
|
fingerprint,
|
|
747
2499
|
kind: await classifyRepository(repo.absolute_path, packageFacts),
|
|
748
2500
|
parsed,
|
|
2501
|
+
packagePublicSurface: packageSurface.surface,
|
|
749
2502
|
fileCount: sourceFiles.length,
|
|
750
2503
|
diagnosticCount: parsed.handlers.filter((handler) => handler.hasHandlerDecorator && (handler.methods.length === 0 || handler.methods.some((method) => !handlerMethodIsExecutable(method)))).length,
|
|
751
2504
|
skipped: false
|
|
752
2505
|
};
|
|
753
2506
|
}
|
|
754
|
-
function publishPreparedRepositoryIndex(db, prepared) {
|
|
2507
|
+
function publishPreparedRepositoryIndex(db, prepared, invalidations) {
|
|
755
2508
|
if (prepared.skipped) return;
|
|
756
|
-
if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint || !prepared.kind
|
|
2509
|
+
if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint || !prepared.kind || !prepared.packagePublicSurface)
|
|
2510
|
+
throw new Error("Prepared repository index is missing publication facts");
|
|
757
2511
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
758
2512
|
const repoId = prepared.repo.id;
|
|
759
|
-
|
|
2513
|
+
invalidatePackageTargetFacts(
|
|
2514
|
+
db,
|
|
2515
|
+
repoId,
|
|
2516
|
+
prepared.packageFacts.packageName,
|
|
2517
|
+
invalidations
|
|
2518
|
+
);
|
|
2519
|
+
db.prepare(`UPDATE repositories SET package_name=?, package_version=?,
|
|
2520
|
+
dependencies_json=?,package_public_surface_json=?,kind=?,index_status=?
|
|
2521
|
+
WHERE id=?`).run(
|
|
2522
|
+
prepared.packageFacts.packageName,
|
|
2523
|
+
prepared.packageFacts.packageVersion,
|
|
2524
|
+
JSON.stringify(prepared.packageFacts.dependencies),
|
|
2525
|
+
JSON.stringify(prepared.packagePublicSurface),
|
|
2526
|
+
prepared.kind,
|
|
2527
|
+
"indexing",
|
|
2528
|
+
repoId
|
|
2529
|
+
);
|
|
760
2530
|
clearRepoFacts(db, repoId);
|
|
761
2531
|
insertRequires(db, repoId, prepared.packageFacts.cdsRequires);
|
|
762
2532
|
const fileStmt = db.prepare("INSERT INTO files(repo_id,relative_path,extension,sha256,size_bytes,last_indexed_at) VALUES(?,?,?,?,?,?) ON CONFLICT(repo_id,relative_path) DO UPDATE SET sha256=excluded.sha256,size_bytes=excluded.size_bytes,last_indexed_at=excluded.last_indexed_at");
|
|
@@ -783,13 +2553,36 @@ async function parseAllSourceFacts(root, sources) {
|
|
|
783
2553
|
facts.fileRecords.push({ relativePath: normalizePath(file), extension: path5.extname(file), sha256: sha256Text(snapshot.text), sizeBytes: snapshot.sizeBytes });
|
|
784
2554
|
if (file.endsWith(".cds")) facts.services.push(...await parseCdsFile(root, file, sources));
|
|
785
2555
|
if (/\.[jt]s$/.test(file)) {
|
|
2556
|
+
const source = snapshot.sourceFile();
|
|
2557
|
+
const classified = classifyOutboundCallsInSource(source, file);
|
|
786
2558
|
facts.handlers.push(...await parseDecorators(root, file, sources));
|
|
787
2559
|
facts.registrations.push(...await parseHandlerRegistrations(root, file, sources));
|
|
788
|
-
|
|
789
|
-
const symbolFacts = await parseExecutableSymbols(
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
2560
|
+
const bindings = await parseServiceBindings(root, file, sources);
|
|
2561
|
+
const symbolFacts = await parseExecutableSymbols(
|
|
2562
|
+
root,
|
|
2563
|
+
file,
|
|
2564
|
+
sources,
|
|
2565
|
+
classified
|
|
2566
|
+
);
|
|
2567
|
+
const outboundCalls = await parseOutboundCalls(
|
|
2568
|
+
root,
|
|
2569
|
+
file,
|
|
2570
|
+
sources,
|
|
2571
|
+
classified,
|
|
2572
|
+
bindings
|
|
2573
|
+
);
|
|
2574
|
+
const reconciled = reconcileSourceFacts(
|
|
2575
|
+
source,
|
|
2576
|
+
classified,
|
|
2577
|
+
bindings,
|
|
2578
|
+
outboundCalls,
|
|
2579
|
+
symbolFacts.symbols,
|
|
2580
|
+
symbolFacts.calls
|
|
2581
|
+
);
|
|
2582
|
+
facts.bindings.push(...reconciled.bindings);
|
|
2583
|
+
facts.symbols.push(...reconciled.symbols);
|
|
2584
|
+
facts.symbolCalls.push(...reconciled.symbolCalls);
|
|
2585
|
+
facts.calls.push(...reconciled.outboundCalls);
|
|
793
2586
|
}
|
|
794
2587
|
}
|
|
795
2588
|
return facts;
|
|
@@ -906,15 +2699,15 @@ function relativeBase(db, extension, symbol) {
|
|
|
906
2699
|
return db.prepare(`SELECT s.id,s.repo_id repoId FROM cds_services s WHERE s.repo_id=? AND s.is_extend=0 AND (s.qualified_name=? OR s.service_name=?) AND (s.source_file=? OR s.source_file=?) ORDER BY s.id`).all(extension.repoId, symbol, symbol, modulePath, `${modulePath}.cds`);
|
|
907
2700
|
}
|
|
908
2701
|
function packageBase(db, workspaceId, specifier, symbol) {
|
|
909
|
-
const
|
|
910
|
-
const moduleSuffix = specifier.slice(
|
|
911
|
-
return db.prepare(`SELECT s.id,s.repo_id repoId FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND s.is_extend=0 AND r.package_name=? AND (s.qualified_name=? OR s.service_name=?) AND (?='' OR s.source_file=? OR s.source_file=?) ORDER BY s.id`).all(workspaceId,
|
|
2702
|
+
const packageName2 = packageNameFromSpecifier(specifier);
|
|
2703
|
+
const moduleSuffix = specifier.slice(packageName2.length).replace(/^\//, "");
|
|
2704
|
+
return db.prepare(`SELECT s.id,s.repo_id repoId FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND s.is_extend=0 AND r.package_name=? AND (s.qualified_name=? OR s.service_name=?) AND (?='' OR s.source_file=? OR s.source_file=?) ORDER BY s.id`).all(workspaceId, packageName2, symbol, symbol, moduleSuffix, moduleSuffix, `${moduleSuffix}.cds`);
|
|
912
2705
|
}
|
|
913
2706
|
function sameRepoBase(db, extension, symbol) {
|
|
914
2707
|
return db.prepare("SELECT id,repo_id repoId FROM cds_services WHERE repo_id=? AND is_extend=0 AND (qualified_name=? OR service_name=?) ORDER BY id").all(extension.repoId, symbol, symbol);
|
|
915
2708
|
}
|
|
916
|
-
function normalizeModulePath(
|
|
917
|
-
const base =
|
|
2709
|
+
function normalizeModulePath(sourceFile2, specifier) {
|
|
2710
|
+
const base = sourceFile2.split("/").slice(0, -1).join("/");
|
|
918
2711
|
const parts = `${base}/${specifier}`.split("/");
|
|
919
2712
|
const out = [];
|
|
920
2713
|
for (const part of parts) {
|
|
@@ -985,42 +2778,91 @@ function claimIndexRun(db, workspaceId, repoCount) {
|
|
|
985
2778
|
}
|
|
986
2779
|
}
|
|
987
2780
|
async function indexWorkspace(db, workspaceId, options) {
|
|
988
|
-
const repos =
|
|
989
|
-
if (options.repo && repos.length === 0)
|
|
990
|
-
throw new Error(`selector_repo_not_found: no indexed repository matched ${options.repo}.`);
|
|
991
|
-
if (options.repo && repos.length > 1)
|
|
992
|
-
throw new Error(`selector_repo_ambiguous: repository selector ${options.repo} matched ${repos.length} repositories; use a unique repository name.`);
|
|
2781
|
+
const repos = selectedRepositories(db, workspaceId, options.repo);
|
|
993
2782
|
const runId = claimIndexRun(db, workspaceId, repos.length);
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
2783
|
+
const state = {
|
|
2784
|
+
fileCount: 0,
|
|
2785
|
+
diagnosticCount: 0,
|
|
2786
|
+
skippedCount: 0,
|
|
2787
|
+
rows: []
|
|
2788
|
+
};
|
|
999
2789
|
try {
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
preparedRows.push(result);
|
|
1004
|
-
fileCount += result.fileCount;
|
|
1005
|
-
diagnosticCount += result.diagnosticCount;
|
|
1006
|
-
skippedCount += result.skipped ? 1 : 0;
|
|
1007
|
-
}
|
|
1008
|
-
db.transaction(() => {
|
|
1009
|
-
for (const row of preparedRows) {
|
|
1010
|
-
activeRepoId = row.repo.id;
|
|
1011
|
-
publishPreparedRepositoryIndex(db, row);
|
|
1012
|
-
}
|
|
1013
|
-
if (options.injectDerivedMaterializationFailure) throw new Error("Injected derived materialization failure");
|
|
1014
|
-
materializeCdsExtensionOperations(db, workspaceId);
|
|
1015
|
-
db.prepare("UPDATE index_runs SET finished_at=?, status='success', file_count=?, diagnostic_count=? WHERE id=?").run((/* @__PURE__ */ new Date()).toISOString(), fileCount, diagnosticCount, runId);
|
|
1016
|
-
});
|
|
1017
|
-
return { repoCount: repos.length, indexedCount: repos.length - skippedCount, skippedCount, fileCount, diagnosticCount };
|
|
2790
|
+
await prepareRepositories(repos, options.force, state);
|
|
2791
|
+
publishPreparedRows(db, workspaceId, options, runId, state);
|
|
2792
|
+
return indexSummary(repos.length, state);
|
|
1018
2793
|
} catch (error) {
|
|
1019
|
-
db
|
|
1020
|
-
if (activeRepoId &&
|
|
2794
|
+
finishFailedRun(db, runId, state, error);
|
|
2795
|
+
if (state.activeRepoId && state.rows.length < repos.length)
|
|
2796
|
+
recordIndexFailure(db, state.activeRepoId, error);
|
|
1021
2797
|
throw error;
|
|
1022
2798
|
}
|
|
1023
2799
|
}
|
|
2800
|
+
function selectedRepositories(db, workspaceId, repoName) {
|
|
2801
|
+
const repos = repoName ? reposByName(db, repoName, workspaceId) : listRepositories(db, workspaceId);
|
|
2802
|
+
if (repoName && repos.length === 0)
|
|
2803
|
+
throw new Error(
|
|
2804
|
+
`selector_repo_not_found: no indexed repository matched ${repoName}.`
|
|
2805
|
+
);
|
|
2806
|
+
if (repoName && repos.length > 1)
|
|
2807
|
+
throw new Error(
|
|
2808
|
+
`selector_repo_ambiguous: repository selector ${repoName} matched ${repos.length} repositories; use a unique repository name.`
|
|
2809
|
+
);
|
|
2810
|
+
return repos;
|
|
2811
|
+
}
|
|
2812
|
+
async function prepareRepositories(repos, force, state) {
|
|
2813
|
+
for (const repo of repos) {
|
|
2814
|
+
state.activeRepoId = repo.id;
|
|
2815
|
+
const result = await prepareRepositoryIndex(repo, force);
|
|
2816
|
+
state.rows.push(result);
|
|
2817
|
+
state.fileCount += result.fileCount;
|
|
2818
|
+
state.diagnosticCount += result.diagnosticCount;
|
|
2819
|
+
state.skippedCount += result.skipped ? 1 : 0;
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
function publishPreparedRows(db, workspaceId, options, runId, state) {
|
|
2823
|
+
db.transaction(() => {
|
|
2824
|
+
const invalidations = createPackageInvalidationBatch(
|
|
2825
|
+
state.rows.filter((row) => !row.skipped).map((row) => row.repo.id)
|
|
2826
|
+
);
|
|
2827
|
+
for (const row of state.rows) {
|
|
2828
|
+
state.activeRepoId = row.repo.id;
|
|
2829
|
+
publishPreparedRepositoryIndex(db, row, invalidations);
|
|
2830
|
+
}
|
|
2831
|
+
if (options.injectDerivedMaterializationFailure)
|
|
2832
|
+
throw new Error("Injected derived materialization failure");
|
|
2833
|
+
materializeCdsExtensionOperations(db, workspaceId);
|
|
2834
|
+
finalizePackageTargetInvalidations(db, invalidations);
|
|
2835
|
+
finishSuccessfulRun(db, runId, state);
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2838
|
+
function finishSuccessfulRun(db, runId, state) {
|
|
2839
|
+
db.prepare(`UPDATE index_runs SET finished_at=?,status='success',
|
|
2840
|
+
file_count=?,diagnostic_count=? WHERE id=?`).run(
|
|
2841
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
2842
|
+
state.fileCount,
|
|
2843
|
+
state.diagnosticCount,
|
|
2844
|
+
runId
|
|
2845
|
+
);
|
|
2846
|
+
}
|
|
2847
|
+
function finishFailedRun(db, runId, state, error) {
|
|
2848
|
+
db.prepare(`UPDATE index_runs SET finished_at=?,status='failed',
|
|
2849
|
+
file_count=?,diagnostic_count=?,error_message=? WHERE id=?`).run(
|
|
2850
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
2851
|
+
state.fileCount,
|
|
2852
|
+
state.diagnosticCount + 1,
|
|
2853
|
+
errorMessage(error),
|
|
2854
|
+
runId
|
|
2855
|
+
);
|
|
2856
|
+
}
|
|
2857
|
+
function indexSummary(repoCount, state) {
|
|
2858
|
+
return {
|
|
2859
|
+
repoCount,
|
|
2860
|
+
indexedCount: repoCount - state.skippedCount,
|
|
2861
|
+
skippedCount: state.skippedCount,
|
|
2862
|
+
fileCount: state.fileCount,
|
|
2863
|
+
diagnosticCount: state.diagnosticCount
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
1024
2866
|
|
|
1025
2867
|
// src/cli/001-doctor-projection.ts
|
|
1026
2868
|
var boundedDoctorArrayKeys = /* @__PURE__ */ new Set([
|
|
@@ -1191,15 +3033,88 @@ function analyzerVersionDiagnostics(db, strict, workspaceId) {
|
|
|
1191
3033
|
return [{ severity: "warning", code: "reindex_required_after_analyzer_upgrade", message: "Repository facts were produced by an older or unknown analyzer; run service-flow index --force before relink to apply current parser semantics.", scope: "workspace", affectedRepositoryCount: rows.length, currentAnalyzerVersion: ANALYZER_VERSION, repositories: rows, remediation: "service-flow index --force && service-flow link" }];
|
|
1192
3034
|
}
|
|
1193
3035
|
|
|
3036
|
+
// src/cli/003-doctor-package-resolution.ts
|
|
3037
|
+
function pendingPredicate(alias) {
|
|
3038
|
+
return `${alias}.status='unresolved'
|
|
3039
|
+
AND ${alias}.callee_symbol_id IS NULL
|
|
3040
|
+
AND ${alias}.unresolved_reason='package_resolution_pending'
|
|
3041
|
+
AND json_extract(${alias}.evidence_json,'$.relation')='package_import'
|
|
3042
|
+
AND json_extract(${alias}.evidence_json,
|
|
3043
|
+
'$.importBinding.moduleKind')='package'
|
|
3044
|
+
AND json_extract(${alias}.evidence_json,
|
|
3045
|
+
'$.candidateStrategy')='package_import_pending'
|
|
3046
|
+
AND json_extract(${alias}.evidence_json,'$.candidateCount')=0
|
|
3047
|
+
AND json_extract(${alias}.evidence_json,'$.eligibleCandidateCount')=0
|
|
3048
|
+
AND json_extract(${alias}.evidence_json,'$.selectedCandidateCount')=0
|
|
3049
|
+
AND json_extract(${alias}.evidence_json,'$.candidateSetComplete')=0`;
|
|
3050
|
+
}
|
|
3051
|
+
function count(db, sql) {
|
|
3052
|
+
return Number(db.prepare(sql).get()?.count ?? 0);
|
|
3053
|
+
}
|
|
3054
|
+
function packagePendingDiagnostics(db) {
|
|
3055
|
+
const pending = count(db, `SELECT COUNT(*) count FROM symbol_calls sc
|
|
3056
|
+
WHERE ${pendingPredicate("sc")}`);
|
|
3057
|
+
if (pending === 0) return [];
|
|
3058
|
+
const stale = count(db, `SELECT COUNT(*) count FROM repositories
|
|
3059
|
+
WHERE graph_stale_reason IS NOT NULL`);
|
|
3060
|
+
return [{
|
|
3061
|
+
severity: "warning",
|
|
3062
|
+
code: "package_import_resolution_pending",
|
|
3063
|
+
message: "Package-import facts await workspace linking; terminal package-resolution quality is deferred.",
|
|
3064
|
+
packageResolutionState: "pre_link_pending",
|
|
3065
|
+
pendingPackageImportCount: pending,
|
|
3066
|
+
graphState: "stale",
|
|
3067
|
+
staleRepositoryCount: stale,
|
|
3068
|
+
requiredAction: "relink",
|
|
3069
|
+
remediation: "service-flow link --workspace /workspace --force"
|
|
3070
|
+
}];
|
|
3071
|
+
}
|
|
3072
|
+
function symbolCallQuality(db) {
|
|
3073
|
+
const terminal = `NOT (${pendingPredicate("sc")})`;
|
|
3074
|
+
const row = db.prepare(`SELECT COUNT(*) total,
|
|
3075
|
+
SUM(CASE WHEN sc.status='resolved' THEN 1 ELSE 0 END) resolved,
|
|
3076
|
+
SUM(CASE WHEN sc.status='unresolved' THEN 1 ELSE 0 END) unresolved
|
|
3077
|
+
FROM symbol_calls sc WHERE ${terminal}`).get();
|
|
3078
|
+
const top = db.prepare(`SELECT sc.callee_expression calleeExpression,
|
|
3079
|
+
COUNT(*) count FROM symbol_calls sc
|
|
3080
|
+
WHERE sc.status='unresolved' AND ${terminal}
|
|
3081
|
+
GROUP BY sc.callee_expression
|
|
3082
|
+
ORDER BY count DESC,sc.callee_expression COLLATE BINARY LIMIT 5`).all();
|
|
3083
|
+
const total = Number(row?.total ?? 0);
|
|
3084
|
+
const unresolved = Number(row?.unresolved ?? 0);
|
|
3085
|
+
const ratio = total === 0 ? 0 : Number((unresolved / total).toFixed(4));
|
|
3086
|
+
return {
|
|
3087
|
+
severity: ratio > 0.05 ? "warning" : "info",
|
|
3088
|
+
code: "strict_symbol_call_quality",
|
|
3089
|
+
message: "Terminal symbol-call quality aggregate",
|
|
3090
|
+
total,
|
|
3091
|
+
resolved: Number(row?.resolved ?? 0),
|
|
3092
|
+
unresolved,
|
|
3093
|
+
unresolvedRatio: ratio,
|
|
3094
|
+
unresolvedRatioThreshold: 0.05,
|
|
3095
|
+
topUnresolvedCallees: top
|
|
3096
|
+
};
|
|
3097
|
+
}
|
|
3098
|
+
|
|
1194
3099
|
// src/cli/doctor.ts
|
|
1195
3100
|
function doctorDiagnostics(db, strict, options = {}) {
|
|
1196
3101
|
const lifecycle = factLifecycleDiagnostic(db, options.workspaceId);
|
|
1197
|
-
if (lifecycle
|
|
1198
|
-
|
|
3102
|
+
if (lifecycle) return boundDoctorDiagnostics([lifecycle]);
|
|
3103
|
+
const globalLifecycle = options.workspaceId === void 0 ? void 0 : factLifecycleDiagnostic(db);
|
|
3104
|
+
if (globalLifecycle) return boundDoctorDiagnostics([
|
|
3105
|
+
...schemaDriftDiagnostics(db, strict, options.workspaceId),
|
|
3106
|
+
...analyzerVersionDiagnostics(db, strict, options.workspaceId),
|
|
3107
|
+
{
|
|
3108
|
+
severity: "warning",
|
|
3109
|
+
code: "workspace_detail_checks_deferred",
|
|
3110
|
+
message: "Selected-workspace lifecycle is valid, but JSON-dependent global detail checks were deferred because another workspace requires reindexing.",
|
|
3111
|
+
remediation: "Run doctor for the affected workspace after force index and link."
|
|
3112
|
+
}
|
|
3113
|
+
]);
|
|
1199
3114
|
const diagnostics = db.prepare("SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics ORDER BY id").all();
|
|
1200
3115
|
return boundDoctorDiagnostics([
|
|
1201
|
-
...lifecycle ? [lifecycle] : [],
|
|
1202
3116
|
...diagnostics,
|
|
3117
|
+
...packagePendingDiagnostics(db),
|
|
1203
3118
|
...healthDiagnostics(db, strict),
|
|
1204
3119
|
...remoteTargetWithoutImplementationDiagnostics(db, strict, Boolean(options.detail)),
|
|
1205
3120
|
...localServiceDiagnostics(db, strict),
|
|
@@ -1338,14 +3253,6 @@ function jsonEvidenceQuality(db) {
|
|
|
1338
3253
|
{ severity: Number(graph.nonObject ?? 0) > 0 || Number(graph.withOutboundEvidence ?? 0) < Number(graph.total ?? 0) ? "warning" : "info", code: "strict_graph_evidence_quality", message: "Call-derived graph evidence and parser-evidence propagation aggregate", total: Number(graph.total ?? 0), nonObject: Number(graph.nonObject ?? 0), withOutboundEvidence: Number(graph.withOutboundEvidence ?? 0), examples: graphExamples }
|
|
1339
3254
|
];
|
|
1340
3255
|
}
|
|
1341
|
-
function symbolCallQuality(db) {
|
|
1342
|
-
const row = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN status='resolved' THEN 1 ELSE 0 END) resolved, SUM(CASE WHEN status='unresolved' THEN 1 ELSE 0 END) unresolved FROM symbol_calls").get();
|
|
1343
|
-
const top = db.prepare("SELECT callee_expression calleeExpression,COUNT(*) count FROM symbol_calls WHERE status='unresolved' GROUP BY callee_expression ORDER BY count DESC,callee_expression LIMIT 5").all();
|
|
1344
|
-
const total = Number(row.total ?? 0);
|
|
1345
|
-
const unresolved = Number(row.unresolved ?? 0);
|
|
1346
|
-
const ratio = total === 0 ? 0 : Number((unresolved / total).toFixed(4));
|
|
1347
|
-
return { severity: ratio > 0.05 ? "warning" : "info", code: "strict_symbol_call_quality", message: "Symbol-call quality aggregate", total, resolved: Number(row.resolved ?? 0), unresolved, unresolvedRatio: ratio, unresolvedRatioThreshold: 0.05, topUnresolvedCallees: top };
|
|
1348
|
-
}
|
|
1349
3256
|
function dbQueryQuality(db) {
|
|
1350
3257
|
const row = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN query_entity IS NOT NULL THEN 1 ELSE 0 END) known, SUM(CASE WHEN query_entity IS NULL THEN 1 ELSE 0 END) unknown FROM outbound_calls WHERE call_type='local_db_query'").get();
|
|
1351
3258
|
const total = Number(row.total ?? 0);
|
|
@@ -1561,8 +3468,12 @@ function serviceBindingQuality(db, detail) {
|
|
|
1561
3468
|
function bindingCategory(row) {
|
|
1562
3469
|
const evidence = parseObject(row.evidenceJson);
|
|
1563
3470
|
const resolution = parseObject(evidence.serviceBindingResolution);
|
|
3471
|
+
const reference = parseObject(evidence.serviceBindingReference);
|
|
1564
3472
|
if (resolution.status === "rejected_future_binding") return "direct_binding_missing";
|
|
1565
|
-
if (
|
|
3473
|
+
if (reference.reason === "binding_declared_after_call")
|
|
3474
|
+
return "direct_binding_missing";
|
|
3475
|
+
if (resolution.status === "ambiguous" || reference.status === "ambiguous" || reference.reason === "unsupported_reaching_assignment")
|
|
3476
|
+
return "ambiguous_binding_candidates";
|
|
1566
3477
|
const receiver = evidence.receiver;
|
|
1567
3478
|
const symbolEvidence = parseObject(row.symbolEvidenceJson);
|
|
1568
3479
|
if (symbolHasReceiverParameter(symbolEvidence, receiver))
|
|
@@ -1588,7 +3499,8 @@ function bindingExample(row) {
|
|
|
1588
3499
|
sourceLine: row.sourceLine,
|
|
1589
3500
|
receiver: evidence.receiver,
|
|
1590
3501
|
unresolvedReason: row.unresolvedReason,
|
|
1591
|
-
bindingResolution: evidence.serviceBindingResolution
|
|
3502
|
+
bindingResolution: evidence.serviceBindingResolution,
|
|
3503
|
+
bindingReference: evidence.serviceBindingReference
|
|
1592
3504
|
};
|
|
1593
3505
|
}
|
|
1594
3506
|
function bindingCategoryAction(category) {
|
|
@@ -1841,8 +3753,8 @@ function hintLines(evidence) {
|
|
|
1841
3753
|
}
|
|
1842
3754
|
function dynamicHintLines(evidence) {
|
|
1843
3755
|
const exploration = isRecord(evidence.dynamicTargetExploration) ? evidence.dynamicTargetExploration : evidence;
|
|
1844
|
-
const
|
|
1845
|
-
if (
|
|
3756
|
+
const count2 = numberValue(exploration.candidateCount);
|
|
3757
|
+
if (count2 === 0) return [];
|
|
1846
3758
|
const shown = numberValue(exploration.shownCandidateCount);
|
|
1847
3759
|
const omitted = numberValue(exploration.omittedCandidateCount);
|
|
1848
3760
|
const rejected = numberValue(exploration.rejectedCandidateCount);
|
|
@@ -1850,7 +3762,7 @@ function dynamicHintLines(evidence) {
|
|
|
1850
3762
|
`viable candidates: ${shown} shown, ${omitted} omitted; rejected: ${rejected}`
|
|
1851
3763
|
];
|
|
1852
3764
|
lines.push(...varSetHints(exploration.suggestedVarSets));
|
|
1853
|
-
if (omitted > 0 || rejected > 0 || shown <
|
|
3765
|
+
if (omitted > 0 || rejected > 0 || shown < count2)
|
|
1854
3766
|
lines.push("use --dynamic-mode candidates --max-dynamic-candidates 20 to explore candidate branches");
|
|
1855
3767
|
return lines;
|
|
1856
3768
|
}
|
|
@@ -1920,9 +3832,9 @@ function diagnosticLocation(diagnostic) {
|
|
|
1920
3832
|
}
|
|
1921
3833
|
function compactMessage(diagnostic) {
|
|
1922
3834
|
const message = String(diagnostic.message ?? "");
|
|
1923
|
-
const
|
|
3835
|
+
const count2 = typeof diagnostic.count === "number" ? ` count=${diagnostic.count}` : "";
|
|
1924
3836
|
const total = typeof diagnostic.total === "number" ? ` total=${diagnostic.total}` : "";
|
|
1925
|
-
return `${message}${
|
|
3837
|
+
return `${message}${count2}${total}`.trim();
|
|
1926
3838
|
}
|
|
1927
3839
|
function suggestedHintLines(diagnostic) {
|
|
1928
3840
|
const direct = cliHints(diagnostic.suggestedHints);
|