@saptools/service-flow 0.1.67 → 0.1.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +27 -9
- package/TECHNICAL-NOTE.md +36 -0
- package/dist/chunk-3N3B5KHV.js +19596 -0
- package/dist/chunk-3N3B5KHV.js.map +1 -0
- package/dist/cli.js +2645 -521
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +67 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/001-index-summary.ts +22 -0
- package/src/cli/003-doctor-package-resolution.ts +68 -0
- package/src/cli/doctor.ts +25 -14
- package/src/cli.ts +151 -87
- package/src/db/000-call-fact-repository.ts +499 -340
- package/src/db/001-fact-lifecycle.ts +60 -30
- package/src/db/002-fact-json-inventory.ts +169 -0
- package/src/db/003-current-fact-semantics.ts +699 -0
- package/src/db/004-package-target-invalidation.ts +183 -0
- package/src/db/005-schema-structure.ts +201 -0
- package/src/db/006-relative-symbol-resolution.ts +464 -0
- package/src/db/007-package-fact-semantics.ts +573 -0
- package/src/db/008-relative-fact-semantics.ts +210 -0
- package/src/db/009-binding-fact-semantics.ts +352 -0
- package/src/db/010-package-symbol-surface-semantics.ts +320 -0
- package/src/db/011-symbol-call-semantics.ts +144 -0
- package/src/db/012-binding-reference-proof.ts +268 -0
- package/src/db/013-index-publication-failure.ts +91 -0
- package/src/db/014-binding-helper-provenance.ts +17 -0
- package/src/db/migrations.ts +16 -3
- package/src/db/repositories.ts +130 -6
- package/src/db/schema.ts +4 -2
- package/src/index.ts +12 -0
- package/src/indexer/cds-extension-resolver.ts +27 -3
- package/src/indexer/repository-indexer.ts +135 -13
- package/src/indexer/workspace-indexer.ts +237 -34
- package/src/linker/003-package-import-symbol-resolver.ts +363 -131
- package/src/linker/004-event-subscription-handler-linker.ts +34 -11
- package/src/linker/005-odata-path-structure.ts +371 -0
- package/src/linker/006-event-template-link.ts +72 -0
- package/src/linker/007-call-edge-insertion.ts +568 -0
- package/src/linker/cross-repo-linker.ts +4 -166
- package/src/linker/odata-path-normalizer.ts +273 -180
- package/src/linker/service-resolver.ts +197 -77
- package/src/parsers/000-direct-query-execution.ts +11 -0
- package/src/parsers/002-symbol-import-bindings.ts +516 -0
- package/src/parsers/003-package-public-surface.ts +661 -0
- package/src/parsers/004-fact-identity.ts +108 -0
- package/src/parsers/005-event-subscription-facts.ts +281 -0
- package/src/parsers/006-binding-identity.ts +348 -0
- package/src/parsers/007-source-fact-reconciliation.ts +105 -0
- package/src/parsers/008-package-surface-publication.ts +82 -0
- package/src/parsers/009-symbol-call-facts.ts +528 -0
- package/src/parsers/010-package-public-surface-analysis.ts +352 -0
- package/src/parsers/011-binding-lexical-scope.ts +583 -0
- package/src/parsers/012-package-fact-contract.ts +306 -0
- package/src/parsers/013-executable-body-eligibility.ts +35 -0
- package/src/parsers/014-service-binding-helper-flow.ts +306 -0
- package/src/parsers/015-service-binding-collector.ts +693 -0
- package/src/parsers/016-local-symbol-reference.ts +261 -0
- package/src/parsers/017-symbol-derived-contexts.ts +268 -0
- package/src/parsers/018-package-commonjs-syntax.ts +142 -0
- package/src/parsers/019-binding-assignment-targets.ts +76 -0
- package/src/parsers/020-stable-local-value.ts +217 -0
- package/src/parsers/021-binding-visibility.ts +168 -0
- package/src/parsers/022-outbound-expression-analysis.ts +700 -0
- package/src/parsers/023-outbound-call-classifier.ts +692 -0
- package/src/parsers/operation-path-analysis.ts +6 -1
- package/src/parsers/outbound-call-parser.ts +162 -512
- package/src/parsers/package-json-parser.ts +45 -3
- package/src/parsers/service-binding-parser-helpers.ts +86 -15
- package/src/parsers/service-binding-parser.ts +147 -597
- package/src/parsers/symbol-parser.ts +513 -352
- package/src/trace/002-trace-diagnostics.ts +36 -1
- package/src/trace/007-implementation-start-diagnostic.ts +1 -0
- package/src/trace/011-event-subscriber-traversal.ts +100 -8
- package/src/trace/013-trace-root-scopes.ts +2 -3
- package/src/trace/014-compact-contract.ts +6 -0
- package/src/trace/015-trace-edge-recorder.ts +61 -4
- package/src/trace/016-compact-projector.ts +15 -17
- package/src/trace/019-trace-edge-semantics.ts +6 -10
- package/src/trace/020-compact-field-projection.ts +122 -38
- package/src/trace/021-compact-decision-normalization.ts +171 -0
- package/src/trace/022-trace-fact-preflight.ts +21 -0
- package/src/trace/023-nested-event-scopes.ts +23 -0
- package/src/trace/024-compact-observation-decision.ts +81 -0
- package/src/trace/025-trace-implementation-scope.ts +123 -0
- package/src/trace/026-trace-start-scope.ts +336 -0
- package/src/trace/027-trace-scope-execution.ts +566 -0
- package/src/trace/028-trace-operation-execution.ts +336 -0
- package/src/trace/029-trace-start-implementation.ts +172 -0
- package/src/trace/030-event-runtime-resolution.ts +151 -0
- package/src/trace/031-local-call-expansion.ts +37 -0
- package/src/trace/implementation-hints.ts +9 -6
- package/src/trace/selectors.ts +1 -0
- package/src/trace/trace-engine.ts +122 -624
- package/src/types.ts +57 -0
- package/src/utils/001-placeholders.ts +188 -10
- package/src/version.ts +1 -1
- package/dist/chunk-ZQABU7MR.js +0 -12151
- package/dist/chunk-ZQABU7MR.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ANALYZER_VERSION,
|
|
4
|
+
PreparedRepositorySnapshotError,
|
|
4
5
|
VERSION,
|
|
6
|
+
analyzePackagePublicSurface,
|
|
7
|
+
binaryCompare,
|
|
8
|
+
bindingSite,
|
|
5
9
|
classifyODataPathIntent,
|
|
6
10
|
classifyOutboundCallsInSource,
|
|
7
|
-
|
|
11
|
+
collectSymbolImportBindings,
|
|
8
12
|
compactTrace,
|
|
9
13
|
containsSupportedOutboundCall,
|
|
14
|
+
createBindingLexicalIndex,
|
|
15
|
+
derivedMemberImportReference,
|
|
10
16
|
discoverRepositories,
|
|
17
|
+
executableSymbolCandidates,
|
|
11
18
|
factLifecycleDiagnostic,
|
|
12
|
-
|
|
13
|
-
handlerMethodIsExecutable,
|
|
19
|
+
identifierMatchesDeclaration,
|
|
14
20
|
implementationHintSuggestions,
|
|
15
|
-
insertBindings,
|
|
16
21
|
insertCalls,
|
|
17
|
-
insertExecutableSymbols,
|
|
18
|
-
insertHandler,
|
|
19
|
-
insertRegistrations,
|
|
20
|
-
insertRequires,
|
|
21
|
-
insertService,
|
|
22
22
|
insertSymbolCalls,
|
|
23
|
+
isPreparedRepositorySnapshotError,
|
|
24
|
+
lexicalIdentifierDeclaration,
|
|
25
|
+
lexicalIdentifierDeclarations,
|
|
26
|
+
lexicalScopeChain,
|
|
23
27
|
linkWorkspace,
|
|
24
|
-
listRepositories,
|
|
25
28
|
loadPackageJsonSnapshot,
|
|
26
29
|
loadRepositorySourceContext,
|
|
27
30
|
migrate,
|
|
@@ -32,17 +35,20 @@ import {
|
|
|
32
35
|
parseHandlerRegistrations,
|
|
33
36
|
parseImplementationHint,
|
|
34
37
|
parseOutboundCalls,
|
|
38
|
+
parsePackageImportReference,
|
|
35
39
|
parsePackageJson,
|
|
36
40
|
parseServiceBindings,
|
|
37
41
|
parseVars,
|
|
38
42
|
projectBoundedInOrder,
|
|
43
|
+
recordPreparedSnapshotFailure,
|
|
39
44
|
redactValue,
|
|
40
|
-
|
|
45
|
+
selectCallOwner,
|
|
46
|
+
selectVisibleBinding,
|
|
41
47
|
selectorRepoAmbiguousDiagnostic,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
} from "./chunk-
|
|
48
|
+
stableLocalValueReference,
|
|
49
|
+
symbolImportReference,
|
|
50
|
+
trace
|
|
51
|
+
} from "./chunk-3N3B5KHV.js";
|
|
46
52
|
|
|
47
53
|
// src/cli.ts
|
|
48
54
|
import { Command, Option } from "commander";
|
|
@@ -209,6 +215,354 @@ function openReadOnlyDatabase(dbPath) {
|
|
|
209
215
|
return openDatabase(dbPath, { readonly: true, migrate: false });
|
|
210
216
|
}
|
|
211
217
|
|
|
218
|
+
// src/db/repositories.ts
|
|
219
|
+
function initialPackagePublicSurface(packageName2) {
|
|
220
|
+
return JSON.stringify({
|
|
221
|
+
schema: "service-flow/package-public-surface@1",
|
|
222
|
+
status: packageName2 ? "incomplete" : "not_applicable",
|
|
223
|
+
reason: packageName2 ? "package_surface_pending_index" : null,
|
|
224
|
+
recordCap: 256,
|
|
225
|
+
total: 0,
|
|
226
|
+
shown: 0,
|
|
227
|
+
omitted: 0,
|
|
228
|
+
packageName: packageName2 ?? null,
|
|
229
|
+
exportsPresent: false,
|
|
230
|
+
exportsAuthoritative: false,
|
|
231
|
+
main: null,
|
|
232
|
+
module: null,
|
|
233
|
+
entries: [],
|
|
234
|
+
scopes: []
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function upsertWorkspace(db, rootPath, dbPath) {
|
|
238
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
239
|
+
db.prepare(
|
|
240
|
+
"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"
|
|
241
|
+
).run(rootPath, dbPath, now, now);
|
|
242
|
+
return Number(
|
|
243
|
+
db.prepare("SELECT id FROM workspaces WHERE root_path=?").get(rootPath)?.id
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
function getWorkspace(db, rootPath) {
|
|
247
|
+
return db.prepare("SELECT * FROM workspaces WHERE root_path=?").get(rootPath);
|
|
248
|
+
}
|
|
249
|
+
function upsertRepository(db, workspaceId, r) {
|
|
250
|
+
db.prepare(
|
|
251
|
+
`INSERT INTO repositories(workspace_id,name,absolute_path,relative_path,
|
|
252
|
+
package_name,package_version,dependencies_json,
|
|
253
|
+
package_public_surface_json,kind,is_git_repo)
|
|
254
|
+
VALUES(?,?,?,?,?,?,?,?,?,?)
|
|
255
|
+
ON CONFLICT(workspace_id,absolute_path) DO UPDATE SET
|
|
256
|
+
name=excluded.name,relative_path=excluded.relative_path,
|
|
257
|
+
package_name=excluded.package_name,
|
|
258
|
+
package_version=excluded.package_version,
|
|
259
|
+
dependencies_json=excluded.dependencies_json,kind=excluded.kind`
|
|
260
|
+
).run(
|
|
261
|
+
workspaceId,
|
|
262
|
+
r.name,
|
|
263
|
+
r.absolutePath,
|
|
264
|
+
r.relativePath,
|
|
265
|
+
r.packageName,
|
|
266
|
+
r.packageVersion,
|
|
267
|
+
JSON.stringify(r.dependencies ?? {}),
|
|
268
|
+
initialPackagePublicSurface(r.packageName),
|
|
269
|
+
r.kind ?? "unknown",
|
|
270
|
+
r.isGitRepo ? 1 : 0
|
|
271
|
+
);
|
|
272
|
+
return Number(
|
|
273
|
+
db.prepare(
|
|
274
|
+
"SELECT id FROM repositories WHERE workspace_id=? AND absolute_path=?"
|
|
275
|
+
).get(workspaceId, r.absolutePath)?.id
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
function listRepositories(db, workspaceId) {
|
|
279
|
+
return db.prepare("SELECT * FROM repositories WHERE (? IS NULL OR workspace_id=?) ORDER BY name,absolute_path,id").all(workspaceId, workspaceId);
|
|
280
|
+
}
|
|
281
|
+
function reposByName(db, name, workspaceId) {
|
|
282
|
+
return db.prepare(`SELECT * FROM repositories
|
|
283
|
+
WHERE (? IS NULL OR workspace_id=?) AND (name=? OR package_name=?)
|
|
284
|
+
ORDER BY name,absolute_path,id`).all(workspaceId, workspaceId, name, name);
|
|
285
|
+
}
|
|
286
|
+
function clearRepoFacts(db, repoId) {
|
|
287
|
+
for (const t of [
|
|
288
|
+
"cds_requires",
|
|
289
|
+
"cds_services",
|
|
290
|
+
"handler_classes",
|
|
291
|
+
"outbound_calls",
|
|
292
|
+
"symbol_calls",
|
|
293
|
+
"handler_registrations",
|
|
294
|
+
"service_bindings",
|
|
295
|
+
"symbols",
|
|
296
|
+
"diagnostics",
|
|
297
|
+
"files"
|
|
298
|
+
])
|
|
299
|
+
db.prepare(`DELETE FROM ${t} WHERE repo_id=?`).run(repoId);
|
|
300
|
+
db.prepare("DELETE FROM search_index WHERE repo=?").run(String(repoId));
|
|
301
|
+
}
|
|
302
|
+
function insertRequires(db, repoId, rows) {
|
|
303
|
+
const stmt = db.prepare(
|
|
304
|
+
"INSERT INTO cds_requires(repo_id,alias,kind,model,destination,service_path,request_timeout,raw_json) VALUES(?,?,?,?,?,?,?,?)"
|
|
305
|
+
);
|
|
306
|
+
for (const r of rows)
|
|
307
|
+
stmt.run(
|
|
308
|
+
repoId,
|
|
309
|
+
r.alias,
|
|
310
|
+
r.kind,
|
|
311
|
+
r.model,
|
|
312
|
+
r.destination,
|
|
313
|
+
r.servicePath,
|
|
314
|
+
r.requestTimeout,
|
|
315
|
+
r.rawJson
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
function insertService(db, repoId, s) {
|
|
319
|
+
const id = Number(
|
|
320
|
+
db.prepare(
|
|
321
|
+
"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"
|
|
322
|
+
).get(
|
|
323
|
+
repoId,
|
|
324
|
+
s.namespace,
|
|
325
|
+
s.serviceName,
|
|
326
|
+
s.qualifiedName,
|
|
327
|
+
s.servicePath,
|
|
328
|
+
s.isExtend ? 1 : 0,
|
|
329
|
+
s.sourceFile,
|
|
330
|
+
s.sourceLine,
|
|
331
|
+
s.extension?.localReference,
|
|
332
|
+
s.extension?.importedSymbol,
|
|
333
|
+
s.extension?.localAlias,
|
|
334
|
+
s.extension?.moduleSpecifier,
|
|
335
|
+
s.extension?.importKind
|
|
336
|
+
)?.id
|
|
337
|
+
);
|
|
338
|
+
const stmt = db.prepare(
|
|
339
|
+
"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(?,?,?,?,?,?,?,?,?,?)"
|
|
340
|
+
);
|
|
341
|
+
db.prepare(
|
|
342
|
+
"INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
|
|
343
|
+
).run("service", s.qualifiedName, s.servicePath, String(repoId));
|
|
344
|
+
for (const o of s.operations)
|
|
345
|
+
stmt.run(
|
|
346
|
+
id,
|
|
347
|
+
o.operationType,
|
|
348
|
+
o.operationName,
|
|
349
|
+
o.operationPath,
|
|
350
|
+
o.paramsJson,
|
|
351
|
+
o.returnType,
|
|
352
|
+
o.sourceFile,
|
|
353
|
+
o.sourceLine,
|
|
354
|
+
o.provenance ?? "direct",
|
|
355
|
+
o.baseOperationId ?? null
|
|
356
|
+
);
|
|
357
|
+
const search = db.prepare(
|
|
358
|
+
"INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
|
|
359
|
+
);
|
|
360
|
+
for (const o of s.operations)
|
|
361
|
+
search.run("operation", o.operationName, o.operationPath, String(repoId));
|
|
362
|
+
return id;
|
|
363
|
+
}
|
|
364
|
+
function insertHandler(db, repoId, h) {
|
|
365
|
+
const sid = insertHandlerClassSymbol(db, repoId, h);
|
|
366
|
+
const hid = Number(
|
|
367
|
+
db.prepare(
|
|
368
|
+
"INSERT INTO handler_classes(repo_id,symbol_id,class_name,source_file,source_line) VALUES(?,?,?,?,?) RETURNING id"
|
|
369
|
+
).get(repoId, sid, h.className, h.sourceFile, h.sourceLine)?.id
|
|
370
|
+
);
|
|
371
|
+
const stmt = db.prepare(
|
|
372
|
+
"INSERT INTO handler_methods(handler_class_id,method_name,decorator_kind,decorator_value,decorator_raw_expression,decorator_resolution_json,source_file,source_line) VALUES(?,?,?,?,?,?,?,?)"
|
|
373
|
+
);
|
|
374
|
+
for (const m of h.methods)
|
|
375
|
+
stmt.run(
|
|
376
|
+
hid,
|
|
377
|
+
m.methodName,
|
|
378
|
+
m.decoratorKind,
|
|
379
|
+
m.decoratorValue,
|
|
380
|
+
m.decoratorRawExpression,
|
|
381
|
+
JSON.stringify(canonicalHandlerMethodResolution(m)),
|
|
382
|
+
m.sourceFile,
|
|
383
|
+
m.sourceLine
|
|
384
|
+
);
|
|
385
|
+
insertHandlerIndexDiagnostic(db, repoId, h);
|
|
386
|
+
return hid;
|
|
387
|
+
}
|
|
388
|
+
function insertHandlerClassSymbol(db, repoId, h) {
|
|
389
|
+
const classEvidence = {
|
|
390
|
+
hasHandlerDecorator: h.hasHandlerDecorator ?? false,
|
|
391
|
+
classDecoratorNames: h.classDecoratorNames ?? [],
|
|
392
|
+
observedDecoratorNames: h.observedDecoratorNames ?? [],
|
|
393
|
+
unsupportedDecoratorNames: h.unsupportedDecoratorNames ?? [],
|
|
394
|
+
unsupportedMethods: h.methods.filter((method) => !handlerMethodIsExecutable(method)).map((method) => ({
|
|
395
|
+
methodName: method.methodName,
|
|
396
|
+
decoratorKind: method.decoratorKind,
|
|
397
|
+
sourceFile: method.sourceFile,
|
|
398
|
+
sourceLine: method.sourceLine,
|
|
399
|
+
reason: method.decoratorResolution.unresolvedReason
|
|
400
|
+
}))
|
|
401
|
+
};
|
|
402
|
+
return Number(
|
|
403
|
+
db.prepare(
|
|
404
|
+
"INSERT INTO symbols(repo_id,kind,name,qualified_name,exported,start_line,end_line,source_file,evidence_json) VALUES(?,?,?,?,?,?,?,?,?) RETURNING id"
|
|
405
|
+
).get(
|
|
406
|
+
repoId,
|
|
407
|
+
"class",
|
|
408
|
+
h.className,
|
|
409
|
+
h.className,
|
|
410
|
+
1,
|
|
411
|
+
h.sourceLine,
|
|
412
|
+
h.sourceLine,
|
|
413
|
+
h.sourceFile,
|
|
414
|
+
JSON.stringify(classEvidence)
|
|
415
|
+
)?.id
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
function insertHandlerIndexDiagnostic(db, repoId, h) {
|
|
419
|
+
if (!h.hasHandlerDecorator) return;
|
|
420
|
+
const hasExecutable = h.methods.some(handlerMethodIsExecutable);
|
|
421
|
+
const unsupported = h.methods.filter((method) => !handlerMethodIsExecutable(method));
|
|
422
|
+
if (hasExecutable && unsupported.length === 0) return;
|
|
423
|
+
const code = hasExecutable ? "handler_decorators_not_indexed" : "handler_methods_not_indexed";
|
|
424
|
+
const names = unsupported.map((method) => method.decoratorKind).sort();
|
|
425
|
+
const detail = names.length > 0 ? ` Unsupported decorators: ${[...new Set(names)].join(", ")}.` : "";
|
|
426
|
+
db.prepare(
|
|
427
|
+
"INSERT INTO diagnostics(repo_id,severity,code,message,source_file,source_line) VALUES(?,?,?,?,?,?)"
|
|
428
|
+
).run(
|
|
429
|
+
repoId,
|
|
430
|
+
"warning",
|
|
431
|
+
code,
|
|
432
|
+
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}`,
|
|
433
|
+
h.sourceFile,
|
|
434
|
+
h.sourceLine
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
function canonicalHandlerMethodResolution(method) {
|
|
438
|
+
const handlerKind = method.handlerKind ?? method.decoratorResolution.handlerKind ?? legacyHandlerKind(method.decoratorKind);
|
|
439
|
+
const executable = method.executable ?? method.decoratorResolution.executable ?? (handlerKind === "operation" || handlerKind === "event" || handlerKind === "entity_lifecycle");
|
|
440
|
+
return {
|
|
441
|
+
...method.decoratorResolution,
|
|
442
|
+
handlerKind,
|
|
443
|
+
executable,
|
|
444
|
+
lifecyclePhase: method.lifecyclePhase ?? method.decoratorResolution.lifecyclePhase,
|
|
445
|
+
lifecycleEvent: method.lifecycleEvent ?? method.decoratorResolution.lifecycleEvent
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function handlerMethodIsExecutable(method) {
|
|
449
|
+
return canonicalHandlerMethodResolution(method).executable === true;
|
|
450
|
+
}
|
|
451
|
+
function legacyHandlerKind(kind) {
|
|
452
|
+
if (kind === "Event") return "event";
|
|
453
|
+
if (["Action", "Func", "On"].includes(kind)) return "operation";
|
|
454
|
+
return "unsupported_decorator";
|
|
455
|
+
}
|
|
456
|
+
function insertRegistrations(db, repoId, rows) {
|
|
457
|
+
const stmt = db.prepare(
|
|
458
|
+
"INSERT INTO handler_registrations(repo_id,handler_class_id,class_name,import_source,registration_file,registration_line,registration_kind,confidence) VALUES(?,?,?,?,?,?,?,?)"
|
|
459
|
+
);
|
|
460
|
+
for (const r of rows) {
|
|
461
|
+
const handlerClass = r.className ? db.prepare(
|
|
462
|
+
"SELECT id FROM handler_classes WHERE repo_id=? AND class_name=? ORDER BY id"
|
|
463
|
+
).all(repoId, r.className) : [];
|
|
464
|
+
stmt.run(
|
|
465
|
+
repoId,
|
|
466
|
+
handlerClass.length === 1 ? handlerClass[0]?.id : null,
|
|
467
|
+
r.className,
|
|
468
|
+
r.importSource,
|
|
469
|
+
r.registrationFile,
|
|
470
|
+
r.registrationLine,
|
|
471
|
+
r.registrationKind,
|
|
472
|
+
r.confidence
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function insertBindings(db, repoId, rows) {
|
|
477
|
+
assertUniquePreparedBindingSites(rows);
|
|
478
|
+
const stmt = db.prepare(
|
|
479
|
+
"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(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
|
|
480
|
+
);
|
|
481
|
+
for (const r of rows)
|
|
482
|
+
stmt.run(
|
|
483
|
+
repoId,
|
|
484
|
+
bindingOwnerId(db, repoId, r),
|
|
485
|
+
r.variableName,
|
|
486
|
+
r.alias,
|
|
487
|
+
r.aliasExpr,
|
|
488
|
+
r.destinationExpr,
|
|
489
|
+
r.servicePathExpr,
|
|
490
|
+
r.isDynamic ? 1 : 0,
|
|
491
|
+
JSON.stringify(r.placeholders),
|
|
492
|
+
r.sourceFile,
|
|
493
|
+
r.sourceLine,
|
|
494
|
+
r.bindingSiteStartOffset,
|
|
495
|
+
r.bindingSiteEndOffset,
|
|
496
|
+
r.ownerResolution,
|
|
497
|
+
r.helperChain ? JSON.stringify(r.helperChain) : null
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
function bindingSnapshotError(failureCode, fact) {
|
|
501
|
+
return new PreparedRepositorySnapshotError(failureCode, {
|
|
502
|
+
factKind: "service_binding",
|
|
503
|
+
sourceFile: fact.sourceFile,
|
|
504
|
+
sourceLine: fact.sourceLine,
|
|
505
|
+
callSiteStartOffset: fact.bindingSiteStartOffset,
|
|
506
|
+
callSiteEndOffset: fact.bindingSiteEndOffset
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
function persistedOwnerCandidates(db, repoId, fact) {
|
|
510
|
+
const rows = db.prepare(`SELECT id,kind,qualified_name qualifiedName,
|
|
511
|
+
start_offset startOffset,end_offset endOffset FROM symbols
|
|
512
|
+
WHERE repo_id=? AND source_file=? AND start_offset<=? AND end_offset>=?`).all(
|
|
513
|
+
repoId,
|
|
514
|
+
fact.sourceFile,
|
|
515
|
+
fact.bindingSiteStartOffset,
|
|
516
|
+
fact.bindingSiteEndOffset
|
|
517
|
+
);
|
|
518
|
+
return rows.flatMap((row) => typeof row.id === "number" && typeof row.kind === "string" && typeof row.qualifiedName === "string" && typeof row.startOffset === "number" && typeof row.endOffset === "number" ? [{
|
|
519
|
+
id: row.id,
|
|
520
|
+
kind: row.kind,
|
|
521
|
+
qualifiedName: row.qualifiedName,
|
|
522
|
+
startOffset: row.startOffset,
|
|
523
|
+
endOffset: row.endOffset
|
|
524
|
+
}] : []);
|
|
525
|
+
}
|
|
526
|
+
function bindingOwnerId(db, repoId, fact) {
|
|
527
|
+
const candidates = persistedOwnerCandidates(db, repoId, fact);
|
|
528
|
+
const selected = selectCallOwner(
|
|
529
|
+
candidates,
|
|
530
|
+
fact.bindingSiteStartOffset ?? -1,
|
|
531
|
+
fact.bindingSiteEndOffset ?? -1
|
|
532
|
+
);
|
|
533
|
+
if (fact.ownerResolution === "ownerless_file_scope") {
|
|
534
|
+
if (selected.status !== "none")
|
|
535
|
+
throw bindingSnapshotError("binding_owner_mismatch", fact);
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
if (fact.ownerResolution !== "owned_exact" || selected.status !== "resolved" || selected.owner?.qualifiedName !== fact.sourceSymbolQualifiedName)
|
|
539
|
+
throw bindingSnapshotError("binding_owner_mismatch", fact);
|
|
540
|
+
const owner = selected.owner;
|
|
541
|
+
if (!owner)
|
|
542
|
+
throw bindingSnapshotError("binding_owner_mismatch", fact);
|
|
543
|
+
return owner.id;
|
|
544
|
+
}
|
|
545
|
+
function assertUniquePreparedBindingSites(rows) {
|
|
546
|
+
const seen = /* @__PURE__ */ new Set();
|
|
547
|
+
for (const row of rows) {
|
|
548
|
+
if (row.bindingSiteStartOffset === void 0 || row.bindingSiteEndOffset === void 0)
|
|
549
|
+
throw bindingSnapshotError("binding_site_missing", row);
|
|
550
|
+
const key = [
|
|
551
|
+
row.sourceFile,
|
|
552
|
+
row.variableName,
|
|
553
|
+
row.bindingSiteStartOffset,
|
|
554
|
+
row.bindingSiteEndOffset
|
|
555
|
+
].join("\0");
|
|
556
|
+
if (seen.has(key))
|
|
557
|
+
throw bindingSnapshotError("duplicate_service_binding_site", row);
|
|
558
|
+
seen.add(key);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function insertExecutableSymbols(db, repoId, rows) {
|
|
562
|
+
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=?),?,?,?,?,?,?,?,?,?,?,?)");
|
|
563
|
+
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);
|
|
564
|
+
}
|
|
565
|
+
|
|
212
566
|
// src/discovery/classify-repository.ts
|
|
213
567
|
import fs3 from "fs/promises";
|
|
214
568
|
import path3 from "path";
|
|
@@ -258,91 +612,1042 @@ import path5 from "path";
|
|
|
258
612
|
// src/parsers/symbol-parser.ts
|
|
259
613
|
import fs4 from "fs/promises";
|
|
260
614
|
import path4 from "path";
|
|
615
|
+
import ts8 from "typescript";
|
|
616
|
+
|
|
617
|
+
// src/parsers/005-event-subscription-facts.ts
|
|
618
|
+
import ts2 from "typescript";
|
|
619
|
+
|
|
620
|
+
// src/parsers/016-local-symbol-reference.ts
|
|
261
621
|
import ts from "typescript";
|
|
262
|
-
function
|
|
263
|
-
|
|
622
|
+
function functionTargetNode(declaration) {
|
|
623
|
+
const parent = declaration.parent;
|
|
624
|
+
if (ts.isFunctionDeclaration(parent)) return parent;
|
|
625
|
+
if (!ts.isVariableDeclaration(parent)) return void 0;
|
|
626
|
+
const initializer = parent.initializer;
|
|
627
|
+
return initializer && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) ? initializer : void 0;
|
|
628
|
+
}
|
|
629
|
+
function declarationKey(identifier) {
|
|
630
|
+
return `${identifier.getStart(identifier.getSourceFile())}:${identifier.getEnd()}`;
|
|
631
|
+
}
|
|
632
|
+
function unwrapAssignmentTarget(expression) {
|
|
633
|
+
if (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression) || ts.isSatisfiesExpression(expression) || ts.isTypeAssertionExpression(expression) || ts.isNonNullExpression(expression))
|
|
634
|
+
return unwrapAssignmentTarget(expression.expression);
|
|
635
|
+
return expression;
|
|
636
|
+
}
|
|
637
|
+
function objectAssignmentTargets(expression) {
|
|
638
|
+
return expression.properties.flatMap((property) => {
|
|
639
|
+
if (ts.isShorthandPropertyAssignment(property)) return [property.name];
|
|
640
|
+
if (ts.isPropertyAssignment(property))
|
|
641
|
+
return assignmentTargets(property.initializer);
|
|
642
|
+
return ts.isSpreadAssignment(property) ? assignmentTargets(property.expression) : [];
|
|
643
|
+
});
|
|
264
644
|
}
|
|
265
|
-
function
|
|
266
|
-
|
|
267
|
-
if (ts.isIdentifier(
|
|
268
|
-
|
|
645
|
+
function assignmentTargets(expression) {
|
|
646
|
+
const target = unwrapAssignmentTarget(expression);
|
|
647
|
+
if (ts.isIdentifier(target)) return [target];
|
|
648
|
+
if (ts.isSpreadElement(target))
|
|
649
|
+
return assignmentTargets(target.expression);
|
|
650
|
+
if (ts.isBinaryExpression(target) && target.operatorToken.kind === ts.SyntaxKind.EqualsToken)
|
|
651
|
+
return assignmentTargets(target.left);
|
|
652
|
+
if (ts.isArrayLiteralExpression(target))
|
|
653
|
+
return target.elements.flatMap((item) => ts.isExpression(item) ? assignmentTargets(item) : []);
|
|
654
|
+
return ts.isObjectLiteralExpression(target) ? objectAssignmentTargets(target) : [];
|
|
655
|
+
}
|
|
656
|
+
function assignmentOperator(kind) {
|
|
657
|
+
return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment;
|
|
658
|
+
}
|
|
659
|
+
function mutationUnary(node) {
|
|
660
|
+
return node.operator === ts.SyntaxKind.PlusPlusToken || node.operator === ts.SyntaxKind.MinusMinusToken;
|
|
661
|
+
}
|
|
662
|
+
function binaryWritesDeclaration(node, matches) {
|
|
663
|
+
if (!ts.isBinaryExpression(node) || !assignmentOperator(node.operatorToken.kind)) return false;
|
|
664
|
+
return assignmentTargets(node.left).some(matches);
|
|
665
|
+
}
|
|
666
|
+
function unaryWritesDeclaration(node, matches) {
|
|
667
|
+
if (!ts.isPrefixUnaryExpression(node) && !ts.isPostfixUnaryExpression(node)) return false;
|
|
668
|
+
return mutationUnary(node) && ts.isIdentifier(node.operand) && matches(node.operand);
|
|
669
|
+
}
|
|
670
|
+
function loopWritesDeclaration(node, matches) {
|
|
671
|
+
if (!ts.isForInStatement(node) && !ts.isForOfStatement(node)) return false;
|
|
672
|
+
return !ts.isVariableDeclarationList(node.initializer) && assignmentTargets(node.initializer).some(matches);
|
|
673
|
+
}
|
|
674
|
+
function declarationWritten(source, declarations) {
|
|
675
|
+
const keys = new Set(declarations.map(declarationKey));
|
|
676
|
+
let written = false;
|
|
677
|
+
const matches = (identifier) => lexicalIdentifierDeclarations(identifier).some((item) => keys.has(declarationKey(item)));
|
|
678
|
+
const visit = (node) => {
|
|
679
|
+
if (written) return;
|
|
680
|
+
written = binaryWritesDeclaration(node, matches) || unaryWritesDeclaration(node, matches) || loopWritesDeclaration(node, matches);
|
|
681
|
+
if (!written) ts.forEachChild(node, visit);
|
|
682
|
+
};
|
|
683
|
+
visit(source);
|
|
684
|
+
return written;
|
|
269
685
|
}
|
|
270
|
-
function
|
|
271
|
-
|
|
686
|
+
function singleImmutableDeclaration(declaration) {
|
|
687
|
+
const parent = declaration?.parent;
|
|
688
|
+
if (parent && ts.isClassDeclaration(parent)) return true;
|
|
689
|
+
return Boolean(parent && ts.isVariableDeclaration(parent) && ts.isVariableDeclarationList(parent.parent) && (parent.parent.flags & ts.NodeFlags.Const) !== 0);
|
|
272
690
|
}
|
|
273
|
-
function
|
|
274
|
-
|
|
691
|
+
function immutableDeclarationSet(declarations, source) {
|
|
692
|
+
if (declarations.length === 0 || declarationWritten(source, declarations)) return false;
|
|
693
|
+
if (declarations.every((item) => ts.isFunctionDeclaration(item.parent)))
|
|
694
|
+
return true;
|
|
695
|
+
if (declarations.length !== 1) return false;
|
|
696
|
+
return singleImmutableDeclaration(declarations[0]);
|
|
275
697
|
}
|
|
276
|
-
function
|
|
277
|
-
const
|
|
278
|
-
|
|
698
|
+
function propertyContainer(declaration) {
|
|
699
|
+
const parent = declaration.parent;
|
|
700
|
+
if (ts.isClassDeclaration(parent)) return parent;
|
|
701
|
+
if (!ts.isVariableDeclaration(parent)) return void 0;
|
|
702
|
+
const initializer = parent.initializer;
|
|
703
|
+
return initializer && (ts.isObjectLiteralExpression(initializer) || ts.isClassExpression(initializer)) ? initializer : void 0;
|
|
279
704
|
}
|
|
280
|
-
function
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
ts.forEachChild(node, visit);
|
|
705
|
+
function identity(symbol) {
|
|
706
|
+
return {
|
|
707
|
+
sourceFile: symbol.sourceFile,
|
|
708
|
+
qualifiedName: symbol.qualifiedName,
|
|
709
|
+
startOffset: symbol.startOffset,
|
|
710
|
+
endOffset: symbol.endOffset
|
|
287
711
|
};
|
|
288
|
-
visit(source);
|
|
289
|
-
return exports;
|
|
290
712
|
}
|
|
291
|
-
function
|
|
292
|
-
|
|
713
|
+
function executableBody(symbol) {
|
|
714
|
+
const value = symbol.importExportEvidence?.executableBodyEligibility;
|
|
715
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && "eligible" in value && value.eligible === true);
|
|
716
|
+
}
|
|
717
|
+
function exactFunctionTarget(declarations, symbols, source) {
|
|
718
|
+
if (!immutableDeclarationSet(declarations, source)) return void 0;
|
|
719
|
+
const spans = declarations.flatMap((declaration) => {
|
|
720
|
+
const target = functionTargetNode(declaration);
|
|
721
|
+
return target ? [{ start: target.getStart(source), end: target.getEnd() }] : [];
|
|
722
|
+
});
|
|
723
|
+
const matches = symbols.filter((symbol) => localFunctionMatches(symbol, spans, source.fileName));
|
|
724
|
+
return matches.length === 1 && matches[0] ? identity(matches[0]) : void 0;
|
|
725
|
+
}
|
|
726
|
+
function localFunctionMatches(symbol, spans, sourceFile2) {
|
|
727
|
+
if (symbol.sourceFile !== sourceFile2 || !executableBody(symbol))
|
|
728
|
+
return false;
|
|
729
|
+
return spans.some((span) => symbol.startOffset === span.start && symbol.endOffset === span.end);
|
|
293
730
|
}
|
|
294
|
-
function
|
|
731
|
+
function exactPropertyTarget(expression, declaration, symbols, source) {
|
|
732
|
+
if (!immutableDeclarationSet([declaration], source) || !stableLocalValueReference(source, declaration))
|
|
733
|
+
return void 0;
|
|
734
|
+
const container = propertyContainer(declaration);
|
|
735
|
+
if (!container) return void 0;
|
|
736
|
+
const qualifiedName = `${declaration.text}.${expression.name.text}`;
|
|
737
|
+
const start = container.getStart(source);
|
|
738
|
+
const end = container.getEnd();
|
|
739
|
+
const matches = symbols.filter((symbol) => symbol.sourceFile === source.fileName && symbol.qualifiedName === qualifiedName && symbol.startOffset >= start && symbol.endOffset <= end && executableBody(symbol));
|
|
740
|
+
return matches.length === 1 && matches[0] ? identity(matches[0]) : void 0;
|
|
741
|
+
}
|
|
742
|
+
function localSymbolTarget(expression, source, symbols) {
|
|
295
743
|
if (ts.isIdentifier(expression)) {
|
|
296
|
-
|
|
744
|
+
return exactFunctionTarget(
|
|
745
|
+
lexicalIdentifierDeclarations(expression),
|
|
746
|
+
symbols,
|
|
747
|
+
source
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
if (!ts.isPropertyAccessExpression(expression) || expression.questionDotToken || !ts.isIdentifier(expression.expression)) return void 0;
|
|
751
|
+
const declaration = lexicalIdentifierDeclaration(expression.expression);
|
|
752
|
+
return declaration ? exactPropertyTarget(expression, declaration, symbols, source) : void 0;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// src/parsers/005-event-subscription-facts.ts
|
|
756
|
+
function lineOf(source, node) {
|
|
757
|
+
return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
|
|
758
|
+
}
|
|
759
|
+
function lineAt(source, position) {
|
|
760
|
+
return source.getLineAndCharacterOfPosition(position).line + 1;
|
|
761
|
+
}
|
|
762
|
+
function importRelation(binding) {
|
|
763
|
+
if (binding.moduleKind === "package") return "package_import";
|
|
764
|
+
return binding.referenceShape === "namespace_member" ? "relative_import_namespace_member" : "relative_import";
|
|
765
|
+
}
|
|
766
|
+
function directTarget(expression, source, imports, symbols) {
|
|
767
|
+
const imported = symbolImportReference(expression, imports);
|
|
768
|
+
if (imported) return importedTarget(expression, source, imported);
|
|
769
|
+
if (ts2.isIdentifier(expression)) {
|
|
770
|
+
const exact = localSymbolTarget(expression, source, symbols);
|
|
297
771
|
return {
|
|
298
772
|
calleeExpression: expression.text,
|
|
299
773
|
calleeLocalName: expression.text,
|
|
300
|
-
|
|
301
|
-
|
|
774
|
+
relation: exact ? "indexed_local_symbol" : "indexed_local_symbol_unproven",
|
|
775
|
+
referenceShape: "identifier",
|
|
776
|
+
localTargetIdentity: exact
|
|
302
777
|
};
|
|
303
778
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const importSource = imports.get(objectName);
|
|
309
|
-
if (namespaceImports.has(objectName)) return {
|
|
779
|
+
return propertyTarget(expression, source, symbols);
|
|
780
|
+
}
|
|
781
|
+
function importedTarget(expression, source, binding) {
|
|
782
|
+
return {
|
|
310
783
|
calleeExpression: expression.getText(source),
|
|
311
|
-
calleeLocalName:
|
|
312
|
-
importSource,
|
|
313
|
-
relation:
|
|
784
|
+
calleeLocalName: binding.requestedPublicName,
|
|
785
|
+
importSource: binding.rawModuleSpecifier,
|
|
786
|
+
relation: importRelation(binding),
|
|
787
|
+
referenceShape: binding.referenceShape,
|
|
788
|
+
importBinding: binding
|
|
314
789
|
};
|
|
315
|
-
|
|
790
|
+
}
|
|
791
|
+
function propertyTarget(expression, source, symbols) {
|
|
792
|
+
if (!ts2.isPropertyAccessExpression(expression) || expression.questionDotToken || !ts2.isIdentifier(expression.expression)) return void 0;
|
|
793
|
+
const exact = localSymbolTarget(expression, source, symbols);
|
|
316
794
|
return {
|
|
317
|
-
calleeExpression:
|
|
318
|
-
calleeLocalName:
|
|
319
|
-
|
|
320
|
-
|
|
795
|
+
calleeExpression: expression.getText(source),
|
|
796
|
+
calleeLocalName: `${expression.expression.text}.${expression.name.text}`,
|
|
797
|
+
relation: exact ? "indexed_local_symbol" : "indexed_local_symbol_unproven",
|
|
798
|
+
referenceShape: "static_member",
|
|
799
|
+
localTargetIdentity: exact
|
|
321
800
|
};
|
|
322
801
|
}
|
|
323
|
-
function
|
|
324
|
-
|
|
325
|
-
|
|
802
|
+
function unsupportedClassification(status, reason, referenceShape) {
|
|
803
|
+
return { status, reason, referenceShape };
|
|
804
|
+
}
|
|
805
|
+
function classifyHandlerReference(expression, source, imports = collectSymbolImportBindings(source), symbols = []) {
|
|
806
|
+
if (!expression)
|
|
807
|
+
return unsupportedClassification(
|
|
808
|
+
"missing_argument",
|
|
809
|
+
"handler_argument_missing",
|
|
810
|
+
"missing"
|
|
811
|
+
);
|
|
812
|
+
if (ts2.isArrowFunction(expression) || ts2.isFunctionExpression(expression))
|
|
813
|
+
return unsupportedClassification(
|
|
814
|
+
"unsupported_inline",
|
|
815
|
+
"inline_handler_body_not_indexed",
|
|
816
|
+
"inline_callback"
|
|
817
|
+
);
|
|
818
|
+
const direct = directTarget(expression, source, imports, symbols);
|
|
819
|
+
if (direct)
|
|
820
|
+
return { status: "role_required", referenceShape: direct.referenceShape, target: direct };
|
|
821
|
+
if (!ts2.isCallExpression(expression))
|
|
822
|
+
return unsupportedClassification(
|
|
823
|
+
"unsupported_reference_shape",
|
|
824
|
+
"handler_reference_shape_unsupported",
|
|
825
|
+
"unsupported_expression"
|
|
826
|
+
);
|
|
827
|
+
return classifyWrapperReference(expression, source, imports, symbols);
|
|
828
|
+
}
|
|
829
|
+
function classifyWrapperReference(expression, source, imports, symbols) {
|
|
830
|
+
if (expression.questionDotToken || expression.arguments.length !== 1)
|
|
831
|
+
return unsupportedClassification(
|
|
832
|
+
"unsupported_wrapper",
|
|
833
|
+
"wrapper_requires_one_reference",
|
|
834
|
+
"wrapper_call"
|
|
835
|
+
);
|
|
836
|
+
const inner = expression.arguments[0];
|
|
837
|
+
const target = inner ? directTarget(inner, source, imports, symbols) : void 0;
|
|
838
|
+
if (!target)
|
|
839
|
+
return unsupportedClassification(
|
|
840
|
+
"unsupported_wrapper",
|
|
841
|
+
"wrapper_reference_shape_unsupported",
|
|
842
|
+
"wrapper_call"
|
|
843
|
+
);
|
|
844
|
+
return {
|
|
845
|
+
status: "role_required",
|
|
846
|
+
referenceShape: `wrapped_${target.referenceShape}`,
|
|
847
|
+
target: { ...target, wrapperFunction: expression.expression.getText(source) }
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
function eventSymbol(source, classified) {
|
|
851
|
+
const node = classified.node;
|
|
852
|
+
const eventName = classified.fact.eventNameExpr ?? "";
|
|
853
|
+
const line = lineOf(source, node);
|
|
854
|
+
const safeName = eventName.replace(/[^A-Za-z0-9_$-]/g, "_");
|
|
855
|
+
const name = `event:${safeName}:${line}`;
|
|
856
|
+
const receiver = classified.fact.evidence?.receiver;
|
|
857
|
+
return {
|
|
858
|
+
kind: "event_registration",
|
|
859
|
+
localName: name,
|
|
860
|
+
qualifiedName: `module:${classified.fact.sourceFile}#${name}`,
|
|
861
|
+
sourceFile: classified.fact.sourceFile,
|
|
862
|
+
startLine: line,
|
|
863
|
+
endLine: lineAt(source, node.getEnd() - 1),
|
|
864
|
+
startOffset: node.getStart(source),
|
|
865
|
+
endOffset: node.getEnd(),
|
|
866
|
+
exported: false,
|
|
867
|
+
importExportEvidence: {
|
|
868
|
+
source: "synthetic_event_registration",
|
|
869
|
+
eventName,
|
|
870
|
+
registrationLine: line,
|
|
871
|
+
receiver: typeof receiver === "string" ? receiver : void 0
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function eventCall(classified, owner, classification) {
|
|
876
|
+
const target = classification.target;
|
|
877
|
+
if (!target) return void 0;
|
|
878
|
+
return {
|
|
879
|
+
callerQualifiedName: owner.qualifiedName,
|
|
880
|
+
calleeExpression: target.calleeExpression,
|
|
881
|
+
calleeLocalName: target.calleeLocalName,
|
|
882
|
+
importSource: target.importSource,
|
|
883
|
+
sourceFile: classified.fact.sourceFile,
|
|
884
|
+
sourceLine: classified.fact.sourceLine,
|
|
885
|
+
callSiteStartOffset: classified.fact.callSiteStartOffset,
|
|
886
|
+
callSiteEndOffset: classified.fact.callSiteEndOffset,
|
|
887
|
+
callRole: "event_subscribe_handler",
|
|
888
|
+
evidence: {
|
|
889
|
+
relation: target.relation,
|
|
890
|
+
caller: owner.qualifiedName,
|
|
891
|
+
targetName: target.calleeLocalName,
|
|
892
|
+
referenceShape: classification.referenceShape,
|
|
893
|
+
...target.importBinding ? { importBinding: target.importBinding } : {},
|
|
894
|
+
...target.localTargetIdentity ? { localTargetIdentity: target.localTargetIdentity } : {},
|
|
895
|
+
...target.wrapperFunction ? { wrapperFunction: target.wrapperFunction } : {},
|
|
896
|
+
factOrigin: "event_subscribe_handler_reference"
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
function enrichSubscription(source, classified, symbols) {
|
|
901
|
+
const classification = classifyHandlerReference(
|
|
902
|
+
classified.node.arguments[1],
|
|
326
903
|
source,
|
|
327
|
-
|
|
328
|
-
|
|
904
|
+
void 0,
|
|
905
|
+
symbols
|
|
906
|
+
);
|
|
907
|
+
const evidence = {
|
|
908
|
+
...classified.fact.evidence ?? {},
|
|
909
|
+
handlerReferenceStatus: classification.status,
|
|
910
|
+
...classification.reason ? { handlerReferenceReason: classification.reason } : {},
|
|
911
|
+
handlerReferenceShape: classification.referenceShape
|
|
912
|
+
};
|
|
913
|
+
return {
|
|
914
|
+
classified: { ...classified, fact: { ...classified.fact, evidence } },
|
|
915
|
+
classification
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
function reconcileEventSubscriptions(source, classifications, symbols, calls) {
|
|
919
|
+
const subscriptions = classifications.filter(
|
|
920
|
+
(item) => item.fact.callType === "async_subscribe"
|
|
921
|
+
).map((item) => enrichSubscription(source, item, symbols));
|
|
922
|
+
const eventSymbols = subscriptions.map((item) => eventSymbol(source, item.classified));
|
|
923
|
+
const eventCalls = subscriptions.flatMap((item, index) => {
|
|
924
|
+
const owner = eventSymbols[index];
|
|
925
|
+
const call = owner ? eventCall(item.classified, owner, item.classification) : void 0;
|
|
926
|
+
return call ? [call] : [];
|
|
927
|
+
});
|
|
928
|
+
return {
|
|
929
|
+
classifications: classifications.map((item) => subscriptions.find((candidate) => candidate.classified.node === item.node)?.classified ?? item),
|
|
930
|
+
symbols: [...symbols.filter((item) => item.kind !== "event_registration"), ...eventSymbols],
|
|
931
|
+
calls: [
|
|
932
|
+
...calls.filter((item) => item.callRole !== "event_subscribe_handler"),
|
|
933
|
+
...eventCalls
|
|
934
|
+
]
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// src/parsers/007-source-fact-reconciliation.ts
|
|
939
|
+
import "typescript";
|
|
940
|
+
|
|
941
|
+
// src/parsers/006-binding-identity.ts
|
|
942
|
+
import ts3 from "typescript";
|
|
943
|
+
var scopeChainCap = 16;
|
|
944
|
+
function matchFactsToSites(index, facts) {
|
|
945
|
+
return facts.map((fact) => {
|
|
946
|
+
if (fact.bindingSiteStartOffset === void 0 || fact.bindingSiteEndOffset === void 0)
|
|
947
|
+
throw new Error("invalid_prepared_repository_snapshot:binding_site_missing");
|
|
948
|
+
const site = bindingSite(
|
|
949
|
+
index,
|
|
950
|
+
fact.variableName,
|
|
951
|
+
fact.bindingSiteStartOffset,
|
|
952
|
+
fact.bindingSiteEndOffset
|
|
953
|
+
);
|
|
954
|
+
if (!site)
|
|
955
|
+
throw new Error("invalid_prepared_repository_snapshot:binding_site_invalid");
|
|
956
|
+
return { fact, site };
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
function exactOwner(fact, site, symbols) {
|
|
960
|
+
const candidates = executableSymbolCandidates(symbols, fact.sourceFile);
|
|
961
|
+
const owner = selectCallOwner(
|
|
962
|
+
candidates,
|
|
963
|
+
site.startOffset,
|
|
964
|
+
site.endOffset
|
|
329
965
|
);
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
966
|
+
if (owner.status === "ambiguous")
|
|
967
|
+
throw new Error("invalid_prepared_repository_snapshot:binding_owner_ambiguous");
|
|
968
|
+
return {
|
|
969
|
+
...fact,
|
|
970
|
+
bindingSiteStartOffset: site.startOffset,
|
|
971
|
+
bindingSiteEndOffset: site.endOffset,
|
|
972
|
+
sourceSymbolQualifiedName: owner.owner?.qualifiedName,
|
|
973
|
+
ownerResolution: owner.status === "resolved" ? "owned_exact" : "ownerless_file_scope"
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
function prepareBindings(index, facts, symbols) {
|
|
977
|
+
return matchFactsToSites(index, facts).map(({ fact, site }) => ({
|
|
978
|
+
fact: exactOwner(fact, site, symbols),
|
|
979
|
+
site,
|
|
980
|
+
aliasProven: site.aliasSource === void 0
|
|
981
|
+
}));
|
|
982
|
+
}
|
|
983
|
+
function aliasStep(binding, source) {
|
|
984
|
+
const aliasKind = binding.site.aliasKind ?? (binding.site.flow === "assignment" ? "identity-assignment" : "identity");
|
|
985
|
+
return {
|
|
986
|
+
callerVariable: binding.fact.variableName,
|
|
987
|
+
aliasOf: source.fact.variableName,
|
|
988
|
+
aliasKind,
|
|
989
|
+
scopeRule: "exact_lexical_scope",
|
|
990
|
+
...aliasKind === "transaction" ? { transactionAliasSource: source.fact.variableName } : {},
|
|
991
|
+
...binding.site.aliasArrayIndex === void 0 ? {} : {
|
|
992
|
+
sourceVariable: source.fact.variableName,
|
|
993
|
+
arrayIndex: binding.site.aliasArrayIndex,
|
|
994
|
+
promiseAll: binding.site.aliasPromiseAll ?? false,
|
|
995
|
+
arrayContainer: binding.site.aliasPromiseAll ? "Promise.all" : "array_literal"
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
function copyAliasProvenance(binding, source) {
|
|
1000
|
+
binding.fact = {
|
|
1001
|
+
...binding.fact,
|
|
1002
|
+
alias: source.fact.alias,
|
|
1003
|
+
aliasExpr: source.fact.aliasExpr,
|
|
1004
|
+
destinationExpr: source.fact.destinationExpr,
|
|
1005
|
+
servicePathExpr: source.fact.servicePathExpr,
|
|
1006
|
+
isDynamic: source.fact.isDynamic,
|
|
1007
|
+
placeholders: source.fact.placeholders,
|
|
1008
|
+
helperChain: [
|
|
1009
|
+
...source.fact.helperChain ?? [],
|
|
1010
|
+
aliasStep(binding, source)
|
|
1011
|
+
]
|
|
1012
|
+
};
|
|
1013
|
+
binding.aliasProven = true;
|
|
1014
|
+
}
|
|
1015
|
+
function bindingCandidates(bindings) {
|
|
1016
|
+
return bindings.map((binding) => ({
|
|
1017
|
+
variableName: binding.fact.variableName,
|
|
1018
|
+
bindingSiteStartOffset: binding.site.startOffset,
|
|
1019
|
+
bindingSiteEndOffset: binding.site.endOffset,
|
|
1020
|
+
value: binding
|
|
1021
|
+
}));
|
|
1022
|
+
}
|
|
1023
|
+
function resolvedAliasSource(selected) {
|
|
1024
|
+
if (selected.status !== "resolved") return void 0;
|
|
1025
|
+
if (!selected.site?.deterministic) return void 0;
|
|
1026
|
+
if (selected.declarationSite?.declarationKind === "var") return void 0;
|
|
1027
|
+
const source = selected.candidate?.value;
|
|
1028
|
+
return source?.aliasProven ? source : void 0;
|
|
1029
|
+
}
|
|
1030
|
+
function reconcileAliases(index, bindings) {
|
|
1031
|
+
const candidates = bindingCandidates(bindings);
|
|
1032
|
+
for (const binding of bindings) {
|
|
1033
|
+
const sourceName = binding.site.aliasSource;
|
|
1034
|
+
if (!sourceName) continue;
|
|
1035
|
+
const selected = selectVisibleBinding(
|
|
1036
|
+
index,
|
|
1037
|
+
candidates,
|
|
1038
|
+
sourceName,
|
|
1039
|
+
binding.site.node
|
|
1040
|
+
);
|
|
1041
|
+
const source = resolvedAliasSource(selected);
|
|
1042
|
+
if (source) copyAliasProvenance(binding, source);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
function assertUniqueSites(bindings) {
|
|
1046
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1047
|
+
for (const binding of bindings) {
|
|
1048
|
+
const key = [
|
|
1049
|
+
binding.fact.sourceFile,
|
|
1050
|
+
binding.fact.variableName,
|
|
1051
|
+
binding.site.startOffset,
|
|
1052
|
+
binding.site.endOffset
|
|
1053
|
+
].join("\0");
|
|
1054
|
+
if (seen.has(key))
|
|
1055
|
+
throw new Error("invalid_prepared_repository_snapshot:duplicate_service_binding_site");
|
|
1056
|
+
seen.add(key);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
function callNodeMap(source) {
|
|
1060
|
+
const calls = /* @__PURE__ */ new Map();
|
|
1061
|
+
const visit = (node) => {
|
|
1062
|
+
if (ts3.isCallExpression(node))
|
|
1063
|
+
calls.set(`${node.getStart(source)}:${node.getEnd()}`, node);
|
|
1064
|
+
ts3.forEachChild(node, visit);
|
|
1065
|
+
};
|
|
1066
|
+
visit(source);
|
|
1067
|
+
return calls;
|
|
1068
|
+
}
|
|
1069
|
+
function emptyReference(call, reason) {
|
|
1070
|
+
return {
|
|
1071
|
+
status: call.serviceVariableName ? "unresolved" : "not_applicable",
|
|
1072
|
+
variableName: call.serviceVariableName,
|
|
1073
|
+
scopeChainTotal: 0,
|
|
1074
|
+
scopeChainShown: 0,
|
|
1075
|
+
scopeChainOmitted: 0,
|
|
1076
|
+
reason
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
function rejectedReference(call, chain, reason, status = "unresolved") {
|
|
1080
|
+
return {
|
|
1081
|
+
status,
|
|
1082
|
+
variableName: call.serviceVariableName,
|
|
1083
|
+
scopeChainTotal: chain.length,
|
|
1084
|
+
scopeChainShown: Math.min(chain.length, scopeChainCap),
|
|
1085
|
+
scopeChainOmitted: Math.max(0, chain.length - scopeChainCap),
|
|
1086
|
+
reason
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
function resolvedReference(call, selected, chain, scopeIndex) {
|
|
1090
|
+
const helperReturn = selected.fact.helperChain?.some(
|
|
1091
|
+
(step) => step.bindingOrigin === "single_hop_helper_return"
|
|
1092
|
+
) ?? false;
|
|
1093
|
+
return {
|
|
1094
|
+
status: "resolved_exact",
|
|
1095
|
+
variableName: call.serviceVariableName,
|
|
1096
|
+
bindingSourceFile: selected.fact.sourceFile,
|
|
1097
|
+
bindingSiteStartOffset: selected.site.startOffset,
|
|
1098
|
+
bindingSiteEndOffset: selected.site.endOffset,
|
|
1099
|
+
resolutionStrategy: selected.site.flow === "assignment" ? "deterministic_reaching_assignment" : selected.site.aliasSource ? "lexical_alias_declaration" : helperReturn ? "single_hop_helper_return" : "lexical_declaration",
|
|
1100
|
+
lexicalScopeChain: chain,
|
|
1101
|
+
bindingScopeIndex: scopeIndex,
|
|
1102
|
+
scopeChainTotal: chain.length,
|
|
1103
|
+
scopeChainShown: chain.length,
|
|
1104
|
+
scopeChainOmitted: 0
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
function selectedBindingReference(call, chain, chosen) {
|
|
1108
|
+
const selected = chosen.candidate?.value;
|
|
1109
|
+
if (!selected) {
|
|
1110
|
+
return rejectedReference(
|
|
1111
|
+
call,
|
|
1112
|
+
chain,
|
|
1113
|
+
chosen.reason ?? "binding_not_found"
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
if (chosen.declarationSite?.declarationKind === "var")
|
|
1117
|
+
return rejectedReference(call, chain, "unsupported_var_binding");
|
|
1118
|
+
if (!selected.site.deterministic || !selected.aliasProven)
|
|
1119
|
+
return rejectedReference(call, chain, "unsupported_reaching_assignment");
|
|
1120
|
+
return resolvedReference(call, selected, chain, chosen.scopeIndex ?? 0);
|
|
1121
|
+
}
|
|
1122
|
+
function bindingReference(call, node, index, bindings) {
|
|
1123
|
+
if (!call.serviceVariableName) return emptyReference(call);
|
|
1124
|
+
if (!node) return emptyReference(call, "binding_flow_unsupported");
|
|
1125
|
+
const chain = lexicalScopeChain(node, index.source);
|
|
1126
|
+
if (chain.length > scopeChainCap)
|
|
1127
|
+
return rejectedReference(call, chain, "scope_chain_limit_exceeded");
|
|
1128
|
+
const chosen = selectVisibleBinding(
|
|
1129
|
+
index,
|
|
1130
|
+
bindingCandidates(bindings),
|
|
1131
|
+
call.serviceVariableName,
|
|
1132
|
+
node
|
|
1133
|
+
);
|
|
1134
|
+
if (chosen.status === "ambiguous")
|
|
1135
|
+
return rejectedReference(
|
|
1136
|
+
call,
|
|
1137
|
+
chain,
|
|
1138
|
+
"binding_scope_ambiguous",
|
|
1139
|
+
"ambiguous"
|
|
1140
|
+
);
|
|
1141
|
+
return selectedBindingReference(call, chain, chosen);
|
|
1142
|
+
}
|
|
1143
|
+
function exactCallOwner(call, symbols) {
|
|
1144
|
+
const start = call.callSiteStartOffset;
|
|
1145
|
+
const end = call.callSiteEndOffset;
|
|
1146
|
+
if (start === void 0 || end === void 0)
|
|
1147
|
+
return { resolution: "ownerless_file_scope" };
|
|
1148
|
+
const selected = selectCallOwner(
|
|
1149
|
+
executableSymbolCandidates(symbols, call.sourceFile),
|
|
1150
|
+
start,
|
|
1151
|
+
end,
|
|
1152
|
+
call.callType === "async_subscribe"
|
|
1153
|
+
);
|
|
1154
|
+
if (selected.status === "ambiguous")
|
|
1155
|
+
throw new Error("invalid_prepared_repository_snapshot:outbound_owner_ambiguous");
|
|
1156
|
+
if (call.callType === "async_subscribe" && selected.status !== "resolved")
|
|
1157
|
+
throw new Error("invalid_prepared_repository_snapshot:subscription_owner_missing");
|
|
1158
|
+
return selected.owner ? { qualifiedName: selected.owner.qualifiedName, resolution: "owned_exact" } : { resolution: "ownerless_file_scope" };
|
|
1159
|
+
}
|
|
1160
|
+
function reconcileBindingAndCallIdentity(source, bindingFacts, callFacts, symbols) {
|
|
1161
|
+
const lexicalIndex = createBindingLexicalIndex(source);
|
|
1162
|
+
const bindings = prepareBindings(lexicalIndex, bindingFacts, symbols);
|
|
1163
|
+
reconcileAliases(lexicalIndex, bindings);
|
|
1164
|
+
assertUniqueSites(bindings);
|
|
1165
|
+
const nodes = callNodeMap(source);
|
|
1166
|
+
const calls = callFacts.map((call) => {
|
|
1167
|
+
const key = `${call.callSiteStartOffset}:${call.callSiteEndOffset}`;
|
|
1168
|
+
const reference = bindingReference(
|
|
1169
|
+
call,
|
|
1170
|
+
nodes.get(key),
|
|
1171
|
+
lexicalIndex,
|
|
1172
|
+
bindings
|
|
1173
|
+
);
|
|
1174
|
+
const owner = exactCallOwner(call, symbols);
|
|
1175
|
+
return {
|
|
1176
|
+
...call,
|
|
1177
|
+
sourceSymbolQualifiedName: owner.qualifiedName,
|
|
1178
|
+
serviceBindingReference: reference,
|
|
1179
|
+
evidence: {
|
|
1180
|
+
...call.evidence ?? {},
|
|
1181
|
+
sourceOwnerResolution: owner.resolution,
|
|
1182
|
+
serviceBindingReference: reference
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
});
|
|
1186
|
+
return { bindings: bindings.map((binding) => binding.fact), calls };
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// src/parsers/007-source-fact-reconciliation.ts
|
|
1190
|
+
function callKey(call) {
|
|
1191
|
+
return `${call.callType}:${call.callSiteStartOffset}:${call.callSiteEndOffset}`;
|
|
1192
|
+
}
|
|
1193
|
+
function mergedOutboundCalls(existing, classifications) {
|
|
1194
|
+
const classifiedKeys = new Set(
|
|
1195
|
+
classifications.map((item) => callKey(item.fact))
|
|
1196
|
+
);
|
|
1197
|
+
return [
|
|
1198
|
+
...classifications.map((item) => item.fact),
|
|
1199
|
+
...existing.filter((call) => !classifiedKeys.has(callKey(call)))
|
|
1200
|
+
];
|
|
1201
|
+
}
|
|
1202
|
+
function exactSymbolCallOwner(call, symbols) {
|
|
1203
|
+
const start = call.callSiteStartOffset;
|
|
1204
|
+
const end = call.callSiteEndOffset;
|
|
1205
|
+
if (start === void 0 || end === void 0) return void 0;
|
|
1206
|
+
const selected = selectCallOwner(
|
|
1207
|
+
executableSymbolCandidates(symbols, call.sourceFile),
|
|
1208
|
+
start,
|
|
1209
|
+
end,
|
|
1210
|
+
call.callRole === "event_subscribe_handler"
|
|
1211
|
+
);
|
|
1212
|
+
if (selected.status === "ambiguous")
|
|
1213
|
+
throw new Error("invalid_prepared_repository_snapshot:symbol_call_owner_ambiguous");
|
|
1214
|
+
if (call.callRole === "event_subscribe_handler" && !selected.owner)
|
|
1215
|
+
throw new Error("invalid_prepared_repository_snapshot:handler_owner_missing");
|
|
1216
|
+
return selected.owner;
|
|
1217
|
+
}
|
|
1218
|
+
function reconcileSymbolCallOwners(calls, symbols) {
|
|
1219
|
+
return calls.flatMap((call) => {
|
|
1220
|
+
const owner = exactSymbolCallOwner(call, symbols);
|
|
1221
|
+
if (!owner) return [];
|
|
1222
|
+
return [{
|
|
1223
|
+
...call,
|
|
1224
|
+
callerQualifiedName: owner.qualifiedName,
|
|
1225
|
+
evidence: {
|
|
1226
|
+
...call.evidence,
|
|
1227
|
+
caller: owner.qualifiedName,
|
|
1228
|
+
callerResolution: "full_span_containment"
|
|
1229
|
+
}
|
|
1230
|
+
}];
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
function reconcileSourceFacts(source, classifications, bindings, outboundCalls, symbols, symbolCalls) {
|
|
1234
|
+
const events = reconcileEventSubscriptions(
|
|
1235
|
+
source,
|
|
1236
|
+
classifications,
|
|
1237
|
+
symbols,
|
|
1238
|
+
symbolCalls
|
|
1239
|
+
);
|
|
1240
|
+
const allCalls = mergedOutboundCalls(outboundCalls, events.classifications);
|
|
1241
|
+
const identities = reconcileBindingAndCallIdentity(
|
|
334
1242
|
source,
|
|
335
|
-
|
|
336
|
-
|
|
1243
|
+
bindings,
|
|
1244
|
+
allCalls,
|
|
1245
|
+
events.symbols
|
|
337
1246
|
);
|
|
338
|
-
return
|
|
1247
|
+
return {
|
|
1248
|
+
bindings: identities.bindings,
|
|
1249
|
+
outboundCalls: identities.calls,
|
|
1250
|
+
symbols: events.symbols,
|
|
1251
|
+
symbolCalls: reconcileSymbolCallOwners(events.calls, events.symbols),
|
|
1252
|
+
classifications: events.classifications
|
|
1253
|
+
};
|
|
339
1254
|
}
|
|
340
|
-
|
|
341
|
-
|
|
1255
|
+
|
|
1256
|
+
// src/parsers/009-symbol-call-facts.ts
|
|
1257
|
+
import ts5 from "typescript";
|
|
1258
|
+
var commonTerminalMembers = /* @__PURE__ */ new Set([
|
|
1259
|
+
"push",
|
|
1260
|
+
"includes",
|
|
1261
|
+
"find",
|
|
1262
|
+
"findIndex",
|
|
1263
|
+
"map",
|
|
1264
|
+
"filter",
|
|
1265
|
+
"reduce",
|
|
1266
|
+
"forEach",
|
|
1267
|
+
"some",
|
|
1268
|
+
"every",
|
|
1269
|
+
"toUpperCase",
|
|
1270
|
+
"toLowerCase",
|
|
1271
|
+
"trim",
|
|
1272
|
+
"split",
|
|
1273
|
+
"join",
|
|
1274
|
+
"get",
|
|
1275
|
+
"set",
|
|
1276
|
+
"has"
|
|
1277
|
+
]);
|
|
1278
|
+
var loggerMembers = /* @__PURE__ */ new Set([
|
|
1279
|
+
"trace",
|
|
1280
|
+
"debug",
|
|
1281
|
+
"info",
|
|
1282
|
+
"warn",
|
|
1283
|
+
"error",
|
|
1284
|
+
"fatal",
|
|
1285
|
+
"log"
|
|
1286
|
+
]);
|
|
1287
|
+
var globalObjects = /* @__PURE__ */ new Set([
|
|
1288
|
+
"JSON",
|
|
1289
|
+
"Object",
|
|
1290
|
+
"Array",
|
|
1291
|
+
"String",
|
|
1292
|
+
"Number",
|
|
1293
|
+
"Boolean",
|
|
1294
|
+
"Math",
|
|
1295
|
+
"Date",
|
|
1296
|
+
"Promise",
|
|
1297
|
+
"Reflect"
|
|
1298
|
+
]);
|
|
1299
|
+
var capDslRoots = /* @__PURE__ */ new Set([
|
|
1300
|
+
"SELECT",
|
|
1301
|
+
"INSERT",
|
|
1302
|
+
"UPSERT",
|
|
1303
|
+
"UPDATE",
|
|
1304
|
+
"DELETE"
|
|
1305
|
+
]);
|
|
1306
|
+
var requestHelpers = /* @__PURE__ */ new Set([
|
|
1307
|
+
"reject",
|
|
1308
|
+
"error",
|
|
1309
|
+
"info",
|
|
1310
|
+
"warn",
|
|
1311
|
+
"notify"
|
|
1312
|
+
]);
|
|
1313
|
+
var transportMembers = /* @__PURE__ */ new Set([
|
|
1314
|
+
"emit",
|
|
1315
|
+
"publish",
|
|
1316
|
+
"send",
|
|
1317
|
+
"on"
|
|
1318
|
+
]);
|
|
1319
|
+
var cdsFrameworkPrefixes = [
|
|
1320
|
+
"cds.connect.",
|
|
1321
|
+
"cds.services.",
|
|
1322
|
+
"cds.parse."
|
|
1323
|
+
];
|
|
1324
|
+
function symbolCallName(expr) {
|
|
1325
|
+
if (ts5.isIdentifier(expr)) return { expression: expr.text, local: expr.text };
|
|
1326
|
+
if (!ts5.isPropertyAccessExpression(expr))
|
|
1327
|
+
return { expression: expr.getText() };
|
|
1328
|
+
const left = expr.expression.getText();
|
|
1329
|
+
return {
|
|
1330
|
+
expression: expr.getText(),
|
|
1331
|
+
local: left === "this" ? void 0 : left.split(".")[0],
|
|
1332
|
+
member: expr.name.text,
|
|
1333
|
+
receiver: left
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
function hasSetMember(value, values) {
|
|
1337
|
+
return value ? values.has(value) : false;
|
|
1338
|
+
}
|
|
1339
|
+
function cdsFrameworkCall(callee) {
|
|
1340
|
+
return callee.expression === "cds.run" || cdsFrameworkPrefixes.some((prefix) => callee.expression.startsWith(prefix));
|
|
1341
|
+
}
|
|
1342
|
+
function requestHelperCall(callee) {
|
|
1343
|
+
return callee.local === "req" && hasSetMember(callee.member, requestHelpers);
|
|
1344
|
+
}
|
|
1345
|
+
function ignoredFrameworkCall(callee) {
|
|
1346
|
+
const checks = [
|
|
1347
|
+
hasSetMember(callee.local, capDslRoots),
|
|
1348
|
+
cdsFrameworkCall(callee),
|
|
1349
|
+
requestHelperCall(callee),
|
|
1350
|
+
hasSetMember(callee.member, transportMembers),
|
|
1351
|
+
hasSetMember(callee.local, globalObjects),
|
|
1352
|
+
callee.expression.startsWith("new Date().")
|
|
1353
|
+
];
|
|
1354
|
+
return checks.some(Boolean);
|
|
1355
|
+
}
|
|
1356
|
+
function argumentEvidence(args) {
|
|
1357
|
+
return args.map((arg) => {
|
|
1358
|
+
if (ts5.isIdentifier(arg)) return { kind: "identifier", name: arg.text };
|
|
1359
|
+
if (ts5.isArrayLiteralExpression(arg)) return {
|
|
1360
|
+
kind: "array_literal",
|
|
1361
|
+
elements: arg.elements.flatMap((item, index) => ts5.isIdentifier(item) ? [{ index, kind: "identifier", name: item.text }] : [])
|
|
1362
|
+
};
|
|
1363
|
+
if (ts5.isObjectLiteralExpression(arg))
|
|
1364
|
+
return objectArgumentEvidence(arg);
|
|
1365
|
+
return { kind: "unsupported", expression: arg.getText() };
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
function objectArgumentEvidence(argument) {
|
|
1369
|
+
const properties = argument.properties.flatMap((property) => {
|
|
1370
|
+
if (ts5.isShorthandPropertyAssignment(property))
|
|
1371
|
+
return [{
|
|
1372
|
+
kind: "shorthand",
|
|
1373
|
+
property: property.name.text,
|
|
1374
|
+
argument: property.name.text
|
|
1375
|
+
}];
|
|
1376
|
+
if (!ts5.isPropertyAssignment(property) || !ts5.isIdentifier(property.initializer)) return [];
|
|
1377
|
+
const name = propertyName(property.name);
|
|
1378
|
+
return name ? [{
|
|
1379
|
+
kind: "property_assignment",
|
|
1380
|
+
property: name,
|
|
1381
|
+
argument: property.initializer.text
|
|
1382
|
+
}] : [];
|
|
1383
|
+
});
|
|
1384
|
+
return { kind: "object_literal", properties };
|
|
1385
|
+
}
|
|
1386
|
+
function propertyName(name) {
|
|
1387
|
+
return ts5.isIdentifier(name) || ts5.isStringLiteralLike(name) || ts5.isNumericLiteral(name) ? name.text : void 0;
|
|
1388
|
+
}
|
|
1389
|
+
function exactCaller(collection, node) {
|
|
1390
|
+
const selected = selectCallOwner(
|
|
1391
|
+
executableSymbolCandidates(collection.symbols, collection.sourceFile),
|
|
1392
|
+
node.getStart(collection.source),
|
|
1393
|
+
node.getEnd()
|
|
1394
|
+
);
|
|
1395
|
+
if (selected.status === "ambiguous")
|
|
1396
|
+
throw new Error("invalid_prepared_repository_snapshot:symbol_call_owner_ambiguous");
|
|
1397
|
+
return selected.owner;
|
|
1398
|
+
}
|
|
1399
|
+
function relation(reference, instance, proxy, localTarget, provenThisMethod) {
|
|
1400
|
+
const derived = derivedRelation(instance, proxy);
|
|
1401
|
+
if (derived) return derived;
|
|
1402
|
+
if (reference?.moduleKind === "package") return "package_import";
|
|
1403
|
+
if (reference?.referenceShape === "namespace_member")
|
|
1404
|
+
return "relative_import_namespace_member";
|
|
1405
|
+
if (reference) return "relative_import";
|
|
1406
|
+
if (localTarget) return "indexed_local_symbol";
|
|
1407
|
+
return provenThisMethod ? "indexed_this_method" : "indexed_local_symbol";
|
|
1408
|
+
}
|
|
1409
|
+
function derivedRelation(instance, proxy) {
|
|
1410
|
+
if (instance?.importBinding?.moduleKind === "package")
|
|
1411
|
+
return "package_import_derived_member";
|
|
1412
|
+
if (instance) return "class_instance_method";
|
|
1413
|
+
if (proxy?.importBinding.moduleKind === "package")
|
|
1414
|
+
return "package_import_derived_member";
|
|
1415
|
+
return proxy ? "relative_import_proxy_member" : void 0;
|
|
1416
|
+
}
|
|
1417
|
+
function targetName(callee, reference, instance, proxy) {
|
|
1418
|
+
if (instance && callee.member)
|
|
1419
|
+
return `${instance.className}.${callee.member}`;
|
|
1420
|
+
if (proxy && callee.member) return callee.member;
|
|
1421
|
+
if (callee.receiver === "this") return callee.member;
|
|
1422
|
+
if (reference) return reference.requestedPublicName;
|
|
1423
|
+
return callee.member && callee.local ? `${callee.local}.${callee.member}` : callee.local;
|
|
1424
|
+
}
|
|
1425
|
+
function callFact(collection, node) {
|
|
1426
|
+
const caller = exactCaller(collection, node);
|
|
1427
|
+
if (!caller) return void 0;
|
|
1428
|
+
const callee = symbolCallName(node.expression);
|
|
1429
|
+
const proxy = callProxy(collection, node);
|
|
1430
|
+
const instance = callInstance(collection, node, callee);
|
|
1431
|
+
const reference = callImportReference(
|
|
1432
|
+
collection,
|
|
1433
|
+
node.expression,
|
|
1434
|
+
callee,
|
|
1435
|
+
instance,
|
|
1436
|
+
proxy
|
|
1437
|
+
);
|
|
1438
|
+
const localTarget = reference || instance || proxy ? void 0 : localSymbolTarget(node.expression, collection.source, collection.symbols);
|
|
1439
|
+
return retainedCallFact(collection, node, caller, callee, {
|
|
1440
|
+
proxy,
|
|
1441
|
+
instance,
|
|
1442
|
+
reference,
|
|
1443
|
+
localTarget
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
function expressionRoot(expression) {
|
|
1447
|
+
let current = expression;
|
|
1448
|
+
while (ts5.isPropertyAccessExpression(current))
|
|
1449
|
+
current = current.expression;
|
|
1450
|
+
return ts5.isIdentifier(current) ? current : void 0;
|
|
1451
|
+
}
|
|
1452
|
+
function exactDeclaredContext(identifier, candidates) {
|
|
1453
|
+
const matches = candidates.filter((candidate) => identifierMatchesDeclaration(
|
|
1454
|
+
identifier,
|
|
1455
|
+
candidate.declarationStartOffset,
|
|
1456
|
+
candidate.declarationEndOffset
|
|
1457
|
+
));
|
|
1458
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
1459
|
+
}
|
|
1460
|
+
function callProxy(collection, node) {
|
|
1461
|
+
const root = expressionRoot(node.expression);
|
|
1462
|
+
if (!root) return void 0;
|
|
1463
|
+
return exactDeclaredContext(
|
|
1464
|
+
root,
|
|
1465
|
+
collection.proxies.get(root.text) ?? []
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
function callInstance(collection, node, callee) {
|
|
1469
|
+
const root = expressionRoot(node.expression);
|
|
1470
|
+
if (root && root.text !== "this") {
|
|
1471
|
+
const exact = exactDeclaredContext(
|
|
1472
|
+
root,
|
|
1473
|
+
collection.instances.get(root.text) ?? []
|
|
1474
|
+
);
|
|
1475
|
+
if (exact) return exact;
|
|
1476
|
+
}
|
|
1477
|
+
return callee.receiver ? thisPropertyInstance(collection, node, callee.receiver) : void 0;
|
|
1478
|
+
}
|
|
1479
|
+
function enclosingClass(node) {
|
|
1480
|
+
let current = node.parent;
|
|
1481
|
+
while (current && !ts5.isSourceFile(current)) {
|
|
1482
|
+
if (ts5.isClassLike(current)) return current;
|
|
1483
|
+
current = current.parent;
|
|
1484
|
+
}
|
|
1485
|
+
return void 0;
|
|
1486
|
+
}
|
|
1487
|
+
function thisPropertyInstance(collection, node, receiver) {
|
|
1488
|
+
if (!receiver.startsWith("this.")) return void 0;
|
|
1489
|
+
const container = enclosingClass(node);
|
|
1490
|
+
if (!container) return void 0;
|
|
1491
|
+
const start = container.getStart(collection.source);
|
|
1492
|
+
const end = container.getEnd();
|
|
1493
|
+
const matches = (collection.instances.get(receiver) ?? []).filter(
|
|
1494
|
+
(candidate) => candidate.containerStartOffset === start && candidate.containerEndOffset === end
|
|
1495
|
+
);
|
|
1496
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
1497
|
+
}
|
|
1498
|
+
function callImportReference(collection, expression, callee, instance, proxy) {
|
|
1499
|
+
const direct = symbolImportReference(expression, collection.importBindings);
|
|
1500
|
+
if (direct || !callee.member) return direct;
|
|
1501
|
+
const inherited = instance?.importBinding ?? proxy?.importBinding;
|
|
1502
|
+
return inherited ? derivedMemberImportReference(inherited, callee.member) : void 0;
|
|
1503
|
+
}
|
|
1504
|
+
function retainedCallFact(collection, node, caller, callee, context) {
|
|
1505
|
+
const target = targetName(
|
|
1506
|
+
callee,
|
|
1507
|
+
context.reference,
|
|
1508
|
+
context.instance,
|
|
1509
|
+
context.proxy
|
|
1510
|
+
);
|
|
1511
|
+
const className = caller.qualifiedName.includes(".") ? caller.qualifiedName.split(".")[0] : void 0;
|
|
1512
|
+
const thisTarget = callee.receiver === "this" && className && callee.member ? `${className}.${callee.member}` : void 0;
|
|
1513
|
+
const resolvedTarget = thisTarget ?? target;
|
|
1514
|
+
if (!resolvedTarget || !shouldRetainCall(
|
|
1515
|
+
collection,
|
|
1516
|
+
callee,
|
|
1517
|
+
resolvedTarget,
|
|
1518
|
+
context,
|
|
1519
|
+
Boolean(thisTarget)
|
|
1520
|
+
))
|
|
1521
|
+
return void 0;
|
|
1522
|
+
return createCallFact(
|
|
1523
|
+
collection,
|
|
1524
|
+
node,
|
|
1525
|
+
caller,
|
|
1526
|
+
callee,
|
|
1527
|
+
resolvedTarget,
|
|
1528
|
+
thisTarget,
|
|
1529
|
+
context
|
|
1530
|
+
);
|
|
1531
|
+
}
|
|
1532
|
+
function shouldRetainCall(collection, callee, target, context, provenThisMethod) {
|
|
1533
|
+
if (!target || ignoredCall(callee)) return false;
|
|
1534
|
+
const callables = new Set(collection.symbols.flatMap((symbol) => [symbol.localName, symbol.qualifiedName]));
|
|
1535
|
+
return provenThisMethod || Boolean(context.localTarget) || callables.has(target) && Boolean(context.instance) || hasImportedContext(context);
|
|
1536
|
+
}
|
|
1537
|
+
function ignoredCall(callee) {
|
|
1538
|
+
return loggerCall(callee) || terminalCall(callee) || ignoredFrameworkCall(callee);
|
|
1539
|
+
}
|
|
1540
|
+
function loggerCall(callee) {
|
|
1541
|
+
if (callee.local === "logger") return true;
|
|
1542
|
+
if (callee.receiver?.endsWith(".logger")) return true;
|
|
1543
|
+
return callee.expression.startsWith("this.logger.") && hasSetMember(callee.member, loggerMembers);
|
|
1544
|
+
}
|
|
1545
|
+
function terminalCall(callee) {
|
|
1546
|
+
return hasSetMember(callee.member, commonTerminalMembers) || hasSetMember(callee.member, loggerMembers);
|
|
1547
|
+
}
|
|
1548
|
+
function hasImportedContext(context) {
|
|
1549
|
+
return Boolean(context.reference ?? context.proxy ?? context.instance);
|
|
1550
|
+
}
|
|
1551
|
+
function createCallFact(collection, node, caller, callee, target, thisTarget, context) {
|
|
1552
|
+
const importSource = context.instance?.importSource ?? context.proxy?.importSource ?? context.reference?.rawModuleSpecifier;
|
|
1553
|
+
return {
|
|
1554
|
+
callerQualifiedName: caller.qualifiedName,
|
|
1555
|
+
calleeExpression: callee.expression,
|
|
1556
|
+
calleeLocalName: target,
|
|
1557
|
+
receiverLocalName: callee.member ? callee.local ?? callee.receiver : void 0,
|
|
1558
|
+
importSource,
|
|
1559
|
+
sourceFile: collection.sourceFile,
|
|
1560
|
+
sourceLine: collection.source.getLineAndCharacterOfPosition(
|
|
1561
|
+
node.getStart(collection.source)
|
|
1562
|
+
).line + 1,
|
|
1563
|
+
callSiteStartOffset: node.getStart(collection.source),
|
|
1564
|
+
callSiteEndOffset: node.getEnd(),
|
|
1565
|
+
callRole: "ordinary_call",
|
|
1566
|
+
evidence: callEvidence(caller, callee, target, thisTarget, context, node)
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
function callEvidence(caller, callee, target, thisTarget, context, node) {
|
|
1570
|
+
return {
|
|
1571
|
+
relation: relation(
|
|
1572
|
+
context.reference,
|
|
1573
|
+
context.instance,
|
|
1574
|
+
context.proxy,
|
|
1575
|
+
context.localTarget,
|
|
1576
|
+
Boolean(thisTarget)
|
|
1577
|
+
),
|
|
1578
|
+
caller: caller.qualifiedName,
|
|
1579
|
+
targetName: target,
|
|
1580
|
+
...importReferenceEvidence(context),
|
|
1581
|
+
...instanceEvidence(context.instance, callee),
|
|
1582
|
+
callArguments: argumentEvidence(node.arguments),
|
|
1583
|
+
...proxyEvidence(context.proxy),
|
|
1584
|
+
...context.localTarget ? { localTargetIdentity: context.localTarget } : {},
|
|
1585
|
+
candidateStrategy: parserCandidateStrategy(context)
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
function importReferenceEvidence(context) {
|
|
1589
|
+
if (!context.reference) return {};
|
|
1590
|
+
const derived = context.instance ?? context.proxy;
|
|
1591
|
+
return derived && context.reference.moduleKind === "package" ? { derivedImportBinding: context.reference } : { importBinding: context.reference };
|
|
1592
|
+
}
|
|
1593
|
+
function instanceEvidence(instance, callee) {
|
|
1594
|
+
if (!instance) return {};
|
|
1595
|
+
return {
|
|
1596
|
+
instanceVariable: instance.propertyName ?? callee.local,
|
|
1597
|
+
className: instance.className,
|
|
1598
|
+
methodName: callee.member,
|
|
1599
|
+
classImportSource: instance.importSource,
|
|
1600
|
+
classImportBinding: instance.importBinding
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
function proxyEvidence(proxy) {
|
|
1604
|
+
if (!proxy) return {};
|
|
1605
|
+
return {
|
|
1606
|
+
proxyVariableName: proxy.variableName,
|
|
1607
|
+
factory: proxy.factory,
|
|
1608
|
+
factoryExpression: proxy.factory,
|
|
1609
|
+
factoryImportSource: proxy.importSource,
|
|
1610
|
+
factoryImportBinding: proxy.importBinding
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
function parserCandidateStrategy(context) {
|
|
1614
|
+
if (context.instance?.importSource)
|
|
1615
|
+
return "relative_import_class_instance_method";
|
|
1616
|
+
if (context.instance) return "same_file_class_instance_method";
|
|
1617
|
+
return context.proxy ? "proxy_member_exact_export_or_unique_member" : void 0;
|
|
342
1618
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
1619
|
+
function collectSymbolCallFacts(collection) {
|
|
1620
|
+
const calls = [];
|
|
1621
|
+
const visit = (node) => {
|
|
1622
|
+
if (ts5.isCallExpression(node)) {
|
|
1623
|
+
const call = callFact(collection, node);
|
|
1624
|
+
if (call) calls.push(call);
|
|
1625
|
+
}
|
|
1626
|
+
ts5.forEachChild(node, visit);
|
|
1627
|
+
};
|
|
1628
|
+
visit(collection.source);
|
|
1629
|
+
return calls;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// src/parsers/013-executable-body-eligibility.ts
|
|
1633
|
+
import ts6 from "typescript";
|
|
1634
|
+
function hasModifier(node, kind) {
|
|
1635
|
+
return ts6.canHaveModifiers(node) && Boolean(ts6.getModifiers(node)?.some((item) => item.kind === kind));
|
|
1636
|
+
}
|
|
1637
|
+
function executableBodyEligibility(node, source) {
|
|
1638
|
+
if (node.body) return { eligible: true, reason: "body_present" };
|
|
1639
|
+
if (hasModifier(node, ts6.SyntaxKind.AbstractKeyword))
|
|
1640
|
+
return { eligible: false, reason: "abstract_bodyless" };
|
|
1641
|
+
if (hasModifier(node, ts6.SyntaxKind.DeclareKeyword))
|
|
1642
|
+
return { eligible: false, reason: "ambient_declaration" };
|
|
1643
|
+
return {
|
|
1644
|
+
eligible: false,
|
|
1645
|
+
reason: source.isDeclarationFile ? "declaration_only" : "overload_signature"
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// src/parsers/017-symbol-derived-contexts.ts
|
|
1650
|
+
import ts7 from "typescript";
|
|
346
1651
|
var builtInConstructors = /* @__PURE__ */ new Set([
|
|
347
1652
|
"Set",
|
|
348
1653
|
"Map",
|
|
@@ -377,340 +1682,688 @@ var builtInConstructors = /* @__PURE__ */ new Set([
|
|
|
377
1682
|
"Promise",
|
|
378
1683
|
"AbortController"
|
|
379
1684
|
]);
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
1685
|
+
function appendNamed(values, name, value) {
|
|
1686
|
+
const matches = values.get(name) ?? [];
|
|
1687
|
+
matches.push(value);
|
|
1688
|
+
values.set(name, matches);
|
|
1689
|
+
}
|
|
1690
|
+
function assignmentOperator2(kind) {
|
|
1691
|
+
return kind >= ts7.SyntaxKind.FirstAssignment && kind <= ts7.SyntaxKind.LastAssignment;
|
|
1692
|
+
}
|
|
1693
|
+
function mutationUnary2(node) {
|
|
1694
|
+
return node.operator === ts7.SyntaxKind.PlusPlusToken || node.operator === ts7.SyntaxKind.MinusMinusToken;
|
|
1695
|
+
}
|
|
1696
|
+
function receiverIdentifier(expression) {
|
|
1697
|
+
let current = expression;
|
|
1698
|
+
while (ts7.isPropertyAccessExpression(current) || ts7.isElementAccessExpression(current))
|
|
1699
|
+
current = current.expression;
|
|
1700
|
+
return ts7.isIdentifier(current) ? current : void 0;
|
|
1701
|
+
}
|
|
1702
|
+
function nodeWritesMember(node, matches) {
|
|
1703
|
+
if (ts7.isBinaryExpression(node))
|
|
1704
|
+
return assignmentOperator2(node.operatorToken.kind) && matches(node.left);
|
|
1705
|
+
if (ts7.isPrefixUnaryExpression(node) || ts7.isPostfixUnaryExpression(node))
|
|
1706
|
+
return mutationUnary2(node) && matches(node.operand);
|
|
1707
|
+
return ts7.isDeleteExpression(node) && matches(node.expression);
|
|
1708
|
+
}
|
|
1709
|
+
function memberWrite(source, declaration) {
|
|
1710
|
+
const start = declaration.getStart(source);
|
|
1711
|
+
const end = declaration.getEnd();
|
|
1712
|
+
const matches = (expression) => {
|
|
1713
|
+
const receiver = receiverIdentifier(expression);
|
|
1714
|
+
return Boolean(receiver && receiver !== expression && identifierMatchesDeclaration(receiver, start, end));
|
|
1715
|
+
};
|
|
1716
|
+
let written = false;
|
|
1717
|
+
const visit = (node) => {
|
|
1718
|
+
if (written) return;
|
|
1719
|
+
written = nodeWritesMember(node, matches);
|
|
1720
|
+
if (!written) ts7.forEachChild(node, visit);
|
|
1721
|
+
};
|
|
1722
|
+
visit(source);
|
|
1723
|
+
return written;
|
|
391
1724
|
}
|
|
392
|
-
function
|
|
393
|
-
|
|
394
|
-
const first = expr.arguments[0];
|
|
395
|
-
return first && ts.isStringLiteral(first) ? first.text : void 0;
|
|
1725
|
+
function stableVariable(collection, node) {
|
|
1726
|
+
return ts7.isIdentifier(node.name) && ts7.isVariableDeclarationList(node.parent) && (node.parent.flags & ts7.NodeFlags.Const) !== 0 && !memberWrite(collection.source, node.name);
|
|
396
1727
|
}
|
|
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) };
|
|
1728
|
+
function collectProxy(collection, node) {
|
|
1729
|
+
if (!ts7.isVariableDeclaration(node) || !stableVariable(collection, node) || !node.initializer || !ts7.isCallExpression(node.initializer) || !ts7.isPropertyAccessExpression(node.initializer.expression)) return;
|
|
1730
|
+
const callee = symbolCallName(node.initializer.expression);
|
|
1731
|
+
const binding = symbolImportReference(
|
|
1732
|
+
node.initializer.expression,
|
|
1733
|
+
collection.importBindings
|
|
1734
|
+
);
|
|
1735
|
+
if (!callee.member || !binding) return;
|
|
1736
|
+
appendNamed(collection.proxies, node.name.text, {
|
|
1737
|
+
importSource: binding.rawModuleSpecifier,
|
|
1738
|
+
importBinding: binding,
|
|
1739
|
+
factory: callee.expression,
|
|
1740
|
+
variableName: node.name.text,
|
|
1741
|
+
declarationStartOffset: node.name.getStart(collection.source),
|
|
1742
|
+
declarationEndOffset: node.name.getEnd()
|
|
428
1743
|
});
|
|
429
1744
|
}
|
|
1745
|
+
function propertyContainer2(declaration, propertyName3) {
|
|
1746
|
+
const property = propertyName3 && ts7.isPropertyDeclaration(declaration.parent) ? declaration.parent : void 0;
|
|
1747
|
+
return property && ts7.isClassLike(property.parent) ? property.parent : void 0;
|
|
1748
|
+
}
|
|
1749
|
+
function eligibleClassName(collection, className, imported) {
|
|
1750
|
+
if (builtInConstructors.has(className)) return false;
|
|
1751
|
+
return Boolean(imported || collection.declaredClasses.has(className));
|
|
1752
|
+
}
|
|
1753
|
+
function resolvedClassName(className, imported) {
|
|
1754
|
+
const importedName = imported?.importedName;
|
|
1755
|
+
return importedName && importedName !== "default" ? importedName : className;
|
|
1756
|
+
}
|
|
1757
|
+
function rememberInstance(collection, declaration, classExpression, propertyName3) {
|
|
1758
|
+
const className = classExpression.text;
|
|
1759
|
+
const imported = symbolImportReference(
|
|
1760
|
+
classExpression,
|
|
1761
|
+
collection.importBindings
|
|
1762
|
+
);
|
|
1763
|
+
if (!eligibleClassName(collection, className, imported)) return;
|
|
1764
|
+
const container = propertyContainer2(declaration, propertyName3);
|
|
1765
|
+
const variableName = propertyName3 ? `this.${propertyName3}` : declaration.getText();
|
|
1766
|
+
appendNamed(collection.instances, variableName, {
|
|
1767
|
+
className: resolvedClassName(className, imported),
|
|
1768
|
+
importSource: imported?.rawModuleSpecifier,
|
|
1769
|
+
importBinding: imported,
|
|
1770
|
+
propertyName: propertyName3,
|
|
1771
|
+
declarationStartOffset: declaration.getStart(collection.source),
|
|
1772
|
+
declarationEndOffset: declaration.getEnd(),
|
|
1773
|
+
containerStartOffset: container?.getStart(collection.source),
|
|
1774
|
+
containerEndOffset: container?.getEnd()
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
function collectVariableInstance(collection, node) {
|
|
1778
|
+
if (!ts7.isVariableDeclaration(node) || !stableVariable(collection, node))
|
|
1779
|
+
return;
|
|
1780
|
+
const initializer = node.initializer;
|
|
1781
|
+
if (initializer && ts7.isNewExpression(initializer) && ts7.isIdentifier(initializer.expression))
|
|
1782
|
+
rememberInstance(collection, node.name, initializer.expression);
|
|
1783
|
+
}
|
|
1784
|
+
function propertyName2(name) {
|
|
1785
|
+
return ts7.isIdentifier(name) || ts7.isStringLiteralLike(name) || ts7.isNumericLiteral(name) ? name.text : void 0;
|
|
1786
|
+
}
|
|
1787
|
+
function thisPropertyWrite(expression, name) {
|
|
1788
|
+
if (ts7.isPropertyAccessExpression(expression))
|
|
1789
|
+
return expression.expression.kind === ts7.SyntaxKind.ThisKeyword && expression.name.text === name;
|
|
1790
|
+
return ts7.isElementAccessExpression(expression) && expression.expression.kind === ts7.SyntaxKind.ThisKeyword && Boolean(expression.argumentExpression && ts7.isStringLiteralLike(expression.argumentExpression) && expression.argumentExpression.text === name);
|
|
1791
|
+
}
|
|
1792
|
+
function nodeWritesThisProperty(node, name) {
|
|
1793
|
+
if (ts7.isBinaryExpression(node))
|
|
1794
|
+
return assignmentOperator2(node.operatorToken.kind) && thisPropertyWrite(node.left, name);
|
|
1795
|
+
if (ts7.isPrefixUnaryExpression(node) || ts7.isPostfixUnaryExpression(node))
|
|
1796
|
+
return mutationUnary2(node) && thisPropertyWrite(node.operand, name);
|
|
1797
|
+
return ts7.isDeleteExpression(node) && thisPropertyWrite(node.expression, name);
|
|
1798
|
+
}
|
|
1799
|
+
function stableProperty(node, name) {
|
|
1800
|
+
const flags = ts7.getCombinedModifierFlags(node);
|
|
1801
|
+
if ((flags & (ts7.ModifierFlags.Private | ts7.ModifierFlags.Readonly)) === 0 || !ts7.isClassLike(node.parent)) return false;
|
|
1802
|
+
let written = false;
|
|
1803
|
+
const visit = (child) => {
|
|
1804
|
+
if (written || child === node) return;
|
|
1805
|
+
written = nodeWritesThisProperty(child, name);
|
|
1806
|
+
if (!written) ts7.forEachChild(child, visit);
|
|
1807
|
+
};
|
|
1808
|
+
visit(node.parent);
|
|
1809
|
+
return !written;
|
|
1810
|
+
}
|
|
1811
|
+
function collectPropertyInstance(collection, node) {
|
|
1812
|
+
if (!ts7.isPropertyDeclaration(node)) return;
|
|
1813
|
+
const initializer = node.initializer;
|
|
1814
|
+
if (!initializer || !ts7.isNewExpression(initializer) || !ts7.isIdentifier(initializer.expression)) return;
|
|
1815
|
+
const name = propertyName2(node.name);
|
|
1816
|
+
if (name && stableProperty(node, name)) rememberInstance(
|
|
1817
|
+
collection,
|
|
1818
|
+
node.name,
|
|
1819
|
+
initializer.expression,
|
|
1820
|
+
name
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1823
|
+
function collectDerivedSymbolContexts(collection) {
|
|
1824
|
+
const visit = (node) => {
|
|
1825
|
+
collectProxy(collection, node);
|
|
1826
|
+
collectVariableInstance(collection, node);
|
|
1827
|
+
collectPropertyInstance(collection, node);
|
|
1828
|
+
ts7.forEachChild(node, visit);
|
|
1829
|
+
};
|
|
1830
|
+
visit(collection.source);
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
// src/parsers/symbol-parser.ts
|
|
1834
|
+
function lineOf2(source, pos) {
|
|
1835
|
+
return source.getLineAndCharacterOfPosition(pos).line + 1;
|
|
1836
|
+
}
|
|
1837
|
+
function nameOf(node) {
|
|
1838
|
+
if (!node) return void 0;
|
|
1839
|
+
if (ts8.isIdentifier(node) || ts8.isStringLiteral(node) || ts8.isNumericLiteral(node)) return node.text;
|
|
1840
|
+
return void 0;
|
|
1841
|
+
}
|
|
1842
|
+
function isFunctionLike(node) {
|
|
1843
|
+
return ts8.isFunctionDeclaration(node) || ts8.isMethodDeclaration(node) || ts8.isFunctionExpression(node) || ts8.isArrowFunction(node);
|
|
1844
|
+
}
|
|
1845
|
+
function exported(node) {
|
|
1846
|
+
return Boolean(ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Export);
|
|
1847
|
+
}
|
|
1848
|
+
function defaultExported(node) {
|
|
1849
|
+
return Boolean(
|
|
1850
|
+
ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Default
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
function isPublicClassMethod(node) {
|
|
1854
|
+
const flags = ts8.getCombinedModifierFlags(node);
|
|
1855
|
+
return (flags & ts8.ModifierFlags.Private) === 0 && (flags & ts8.ModifierFlags.Protected) === 0;
|
|
1856
|
+
}
|
|
1857
|
+
function exportDeclarations(source) {
|
|
1858
|
+
const exports = /* @__PURE__ */ new Map();
|
|
1859
|
+
const visit = (node) => {
|
|
1860
|
+
if (ts8.isExportDeclaration(node) && node.exportClause && ts8.isNamedExports(node.exportClause)) {
|
|
1861
|
+
for (const el of node.exportClause.elements) exports.set((el.propertyName ?? el.name).text, el.name.text);
|
|
1862
|
+
}
|
|
1863
|
+
ts8.forEachChild(node, visit);
|
|
1864
|
+
};
|
|
1865
|
+
visit(source);
|
|
1866
|
+
return exports;
|
|
1867
|
+
}
|
|
1868
|
+
function isObjectFunction(node) {
|
|
1869
|
+
return ts8.isFunctionExpression(node) || ts8.isArrowFunction(node) || ts8.isMethodDeclaration(node);
|
|
1870
|
+
}
|
|
1871
|
+
function requireSource(expr) {
|
|
1872
|
+
if (!ts8.isCallExpression(expr) || !ts8.isIdentifier(expr.expression) || expr.expression.text !== "require") return void 0;
|
|
1873
|
+
const first = expr.arguments[0];
|
|
1874
|
+
return first && ts8.isStringLiteral(first) ? first.text : void 0;
|
|
1875
|
+
}
|
|
430
1876
|
function bindingLocalName(name, initializer) {
|
|
431
|
-
if (
|
|
432
|
-
if (initializer &&
|
|
1877
|
+
if (ts8.isIdentifier(name)) return name.text;
|
|
1878
|
+
if (initializer && ts8.isIdentifier(initializer)) return initializer.text;
|
|
433
1879
|
return void 0;
|
|
434
1880
|
}
|
|
435
1881
|
function objectPatternAliases(pattern, parameter, source, lineNode) {
|
|
436
1882
|
return pattern.elements.flatMap((element) => {
|
|
437
|
-
if (element.dotDotDotToken ||
|
|
1883
|
+
if (element.dotDotDotToken || ts8.isObjectBindingPattern(element.name) || ts8.isArrayBindingPattern(element.name)) return [];
|
|
438
1884
|
const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
|
|
439
1885
|
if (!property) return [];
|
|
440
1886
|
const local = bindingLocalName(element.name, element.initializer);
|
|
441
|
-
return local ? [{ parameter, property, local, kind: "object_parameter_destructure", line:
|
|
1887
|
+
return local ? [{ parameter, property, local, kind: "object_parameter_destructure", line: lineOf2(source, lineNode.getStart(source)) }] : [];
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
function parameterPropertyAliases(fn, source) {
|
|
1891
|
+
const parameterNames = new Set(fn.parameters.flatMap((param) => ts8.isIdentifier(param.name) ? [param.name.text] : []));
|
|
1892
|
+
if (!fn.body || parameterNames.size === 0) return [];
|
|
1893
|
+
const aliases = [];
|
|
1894
|
+
const addFromAssignment = (left, right, node) => {
|
|
1895
|
+
if (!ts8.isObjectLiteralExpression(left) || !ts8.isIdentifier(right) || !parameterNames.has(right.text)) return;
|
|
1896
|
+
for (const prop of left.properties) {
|
|
1897
|
+
if (!ts8.isPropertyAssignment(prop)) continue;
|
|
1898
|
+
const property = nameOf(prop.name);
|
|
1899
|
+
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)) });
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1902
|
+
const visit = (node) => {
|
|
1903
|
+
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));
|
|
1904
|
+
if (ts8.isBinaryExpression(node) && node.operatorToken.kind === ts8.SyntaxKind.EqualsToken) addFromAssignment(ts8.isParenthesizedExpression(node.left) ? node.left.expression : node.left, node.right, node);
|
|
1905
|
+
ts8.forEachChild(node, visit);
|
|
1906
|
+
};
|
|
1907
|
+
visit(fn.body);
|
|
1908
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1909
|
+
return aliases.filter((alias) => {
|
|
1910
|
+
const key = `${alias.parameter}.${alias.property}:${alias.local}`;
|
|
1911
|
+
if (seen.has(key)) return false;
|
|
1912
|
+
seen.add(key);
|
|
1913
|
+
return true;
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
function parameterBindings(params) {
|
|
1917
|
+
return params.flatMap((param, index) => {
|
|
1918
|
+
if (ts8.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
|
|
1919
|
+
if (ts8.isObjectBindingPattern(param.name)) {
|
|
1920
|
+
const properties = param.name.elements.flatMap((element) => {
|
|
1921
|
+
if (element.dotDotDotToken || ts8.isObjectBindingPattern(element.name) || ts8.isArrayBindingPattern(element.name)) return [];
|
|
1922
|
+
const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
|
|
1923
|
+
if (!property) return [];
|
|
1924
|
+
const local = bindingLocalName(element.name, element.initializer);
|
|
1925
|
+
return local ? [{ property, local }] : [];
|
|
1926
|
+
});
|
|
1927
|
+
return properties.length > 0 ? [{ index, kind: "object_pattern", properties }] : [];
|
|
1928
|
+
}
|
|
1929
|
+
if (ts8.isArrayBindingPattern(param.name)) {
|
|
1930
|
+
const elements = param.name.elements.flatMap((element, elementIndex) => ts8.isBindingElement(element) && !element.dotDotDotToken && ts8.isIdentifier(element.name) ? [{ index: elementIndex, local: element.name.text }] : []);
|
|
1931
|
+
return elements.length > 0 ? [{ index, kind: "array_pattern", elements }] : [];
|
|
1932
|
+
}
|
|
1933
|
+
return [];
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
function symbolSourceEvidence(collection, node, options) {
|
|
1937
|
+
if (options.evidence) return options.evidence;
|
|
1938
|
+
if (options.classMemberExported) return {
|
|
1939
|
+
source: "exported_class_member",
|
|
1940
|
+
exportedClass: options.parentRoot,
|
|
1941
|
+
memberKind: ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Static ? "static_method" : "class_method"
|
|
1942
|
+
};
|
|
1943
|
+
if (options.classContainerExported && ts8.isMethodDeclaration(node) && isPublicClassMethod(node)) return {
|
|
1944
|
+
source: "exported_class_instance_member",
|
|
1945
|
+
exportedClass: options.parentRoot,
|
|
1946
|
+
exportedClassExportKind: options.classContainerDefaultExported ? "default" : "named",
|
|
1947
|
+
memberKind: "class_method"
|
|
1948
|
+
};
|
|
1949
|
+
if (options.declaredExportName) return {
|
|
1950
|
+
exportedName: options.declaredExportName,
|
|
1951
|
+
source: "export_declaration"
|
|
1952
|
+
};
|
|
1953
|
+
return options.objectExported ? { exportedName: options.qualifiedName, source: "exported_object_literal" } : {};
|
|
1954
|
+
}
|
|
1955
|
+
function executableEvidence(node, source) {
|
|
1956
|
+
if (!isFunctionLike(node)) return {};
|
|
1957
|
+
const bindings = parameterBindings(node.parameters);
|
|
1958
|
+
const parameters = bindings.flatMap((binding) => binding.kind === "identifier" ? [binding.name] : []);
|
|
1959
|
+
const aliases = parameterPropertyAliases(node, source);
|
|
1960
|
+
return {
|
|
1961
|
+
executableBodyEligibility: executableBodyEligibility(node, source),
|
|
1962
|
+
...bindings.length > 0 ? { parameters, parameterBindings: bindings } : {},
|
|
1963
|
+
...aliases.length > 0 ? { parameterPropertyAliases: aliases } : {}
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
function exportedClassMember(collection, kind, parentName, parentRoot, node) {
|
|
1967
|
+
if (kind !== "method" || !parentName || !collection.exportedClasses.has(parentRoot) || !ts8.isMethodDeclaration(node)) return false;
|
|
1968
|
+
return Boolean(ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Static) && isPublicClassMethod(node);
|
|
1969
|
+
}
|
|
1970
|
+
function classExportState(collection, parentName, parentRoot) {
|
|
1971
|
+
return {
|
|
1972
|
+
classContainerExported: Boolean(
|
|
1973
|
+
parentName && collection.exportedClasses.has(parentRoot)
|
|
1974
|
+
),
|
|
1975
|
+
classContainerDefaultExported: Boolean(
|
|
1976
|
+
parentName && collection.defaultExportedClasses.has(parentRoot)
|
|
1977
|
+
)
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
function symbolNames(collection, kind, localName, node, parentName, exportedName) {
|
|
1981
|
+
const parentRoot = parentName?.split(".")[0] ?? "";
|
|
1982
|
+
const declaredExportName = exportedName ?? collection.exportNames.get(
|
|
1983
|
+
parentName ? parentRoot : localName
|
|
1984
|
+
);
|
|
1985
|
+
const qualifiedName = parentName ? `${parentName}.${localName}` : localName;
|
|
1986
|
+
const objectExported = Boolean(
|
|
1987
|
+
parentName && collection.objectExports.has(parentRoot)
|
|
1988
|
+
);
|
|
1989
|
+
const classMemberExported = exportedClassMember(
|
|
1990
|
+
collection,
|
|
1991
|
+
kind,
|
|
1992
|
+
parentName,
|
|
1993
|
+
parentRoot,
|
|
1994
|
+
node
|
|
1995
|
+
);
|
|
1996
|
+
const classState = classExportState(collection, parentName, parentRoot);
|
|
1997
|
+
return {
|
|
1998
|
+
parentRoot,
|
|
1999
|
+
declaredExportName,
|
|
2000
|
+
qualifiedName,
|
|
2001
|
+
objectExported,
|
|
2002
|
+
...classState,
|
|
2003
|
+
classMemberExported,
|
|
2004
|
+
effectiveName: classMemberExported || objectExported ? qualifiedName : declaredExportName
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
function addExecutableSymbol(collection, kind, localName, node, parentName, exportedName, evidence) {
|
|
2008
|
+
const names = symbolNames(
|
|
2009
|
+
collection,
|
|
2010
|
+
kind,
|
|
2011
|
+
localName,
|
|
2012
|
+
node,
|
|
2013
|
+
parentName,
|
|
2014
|
+
exportedName
|
|
2015
|
+
);
|
|
2016
|
+
const sourceEvidence = symbolSourceEvidence(collection, node, {
|
|
2017
|
+
parentRoot: names.parentRoot,
|
|
2018
|
+
qualifiedName: names.qualifiedName,
|
|
2019
|
+
declaredExportName: names.declaredExportName,
|
|
2020
|
+
classContainerExported: names.classContainerExported,
|
|
2021
|
+
classContainerDefaultExported: names.classContainerDefaultExported,
|
|
2022
|
+
classMemberExported: names.classMemberExported,
|
|
2023
|
+
objectExported: names.objectExported,
|
|
2024
|
+
evidence
|
|
2025
|
+
});
|
|
2026
|
+
collection.symbols.push({
|
|
2027
|
+
kind,
|
|
2028
|
+
localName: kind === "object_method" ? names.qualifiedName : localName,
|
|
2029
|
+
exportedName: names.effectiveName,
|
|
2030
|
+
qualifiedName: names.qualifiedName,
|
|
2031
|
+
sourceFile: collection.sourceFile,
|
|
2032
|
+
startLine: lineOf2(collection.source, node.getStart(collection.source)),
|
|
2033
|
+
endLine: lineOf2(collection.source, node.getEnd()),
|
|
2034
|
+
startOffset: node.getStart(collection.source),
|
|
2035
|
+
endOffset: node.getEnd(),
|
|
2036
|
+
exported: exported(node) || Boolean(names.effectiveName),
|
|
2037
|
+
importExportEvidence: {
|
|
2038
|
+
...sourceEvidence,
|
|
2039
|
+
...executableEvidence(node, collection.source)
|
|
2040
|
+
}
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
function addAliasSymbol(collection, objectName, propertyName3, node) {
|
|
2044
|
+
collection.symbols.push({
|
|
2045
|
+
kind: "object_alias",
|
|
2046
|
+
localName: propertyName3,
|
|
2047
|
+
exportedName: propertyName3,
|
|
2048
|
+
qualifiedName: `${objectName}.${propertyName3}`,
|
|
2049
|
+
sourceFile: collection.sourceFile,
|
|
2050
|
+
startLine: lineOf2(collection.source, node.getStart(collection.source)),
|
|
2051
|
+
endLine: lineOf2(collection.source, node.getEnd()),
|
|
2052
|
+
startOffset: node.getStart(collection.source),
|
|
2053
|
+
endOffset: node.getEnd(),
|
|
2054
|
+
exported: true,
|
|
2055
|
+
importExportEvidence: {
|
|
2056
|
+
source: "exported_object_shorthand",
|
|
2057
|
+
objectName,
|
|
2058
|
+
propertyName: propertyName3,
|
|
2059
|
+
targetImportSource: collection.imports.get(propertyName3)
|
|
2060
|
+
}
|
|
442
2061
|
});
|
|
443
2062
|
}
|
|
444
|
-
function
|
|
445
|
-
const parameterNames = new Set(fn.parameters.flatMap((param) => ts.isIdentifier(param.name) ? [param.name.text] : []));
|
|
446
|
-
if (!fn.body || parameterNames.size === 0) return [];
|
|
447
|
-
const aliases = [];
|
|
448
|
-
const addFromAssignment = (left, right, node) => {
|
|
449
|
-
if (!ts.isObjectLiteralExpression(left) || !ts.isIdentifier(right) || !parameterNames.has(right.text)) return;
|
|
450
|
-
for (const prop of left.properties) {
|
|
451
|
-
if (!ts.isPropertyAssignment(prop)) continue;
|
|
452
|
-
const property = nameOf(prop.name);
|
|
453
|
-
if (property && ts.isIdentifier(prop.initializer)) aliases.push({ parameter: right.text, property, local: prop.initializer.text, kind: "object_parameter_destructure", line: lineOf(source, node.getStart(source)) });
|
|
454
|
-
}
|
|
455
|
-
};
|
|
2063
|
+
function collectImportSources(collection) {
|
|
456
2064
|
const visit = (node) => {
|
|
457
|
-
if (
|
|
458
|
-
|
|
459
|
-
|
|
2065
|
+
if (ts8.isImportDeclaration(node) && ts8.isStringLiteral(node.moduleSpecifier))
|
|
2066
|
+
collectEsmImportSources(collection.imports, node);
|
|
2067
|
+
if (ts8.isVariableStatement(node))
|
|
2068
|
+
collectCjsImportSources(collection.imports, node);
|
|
2069
|
+
ts8.forEachChild(node, visit);
|
|
460
2070
|
};
|
|
461
|
-
visit(
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
2071
|
+
visit(collection.source);
|
|
2072
|
+
}
|
|
2073
|
+
function collectEsmImportSources(imports, node) {
|
|
2074
|
+
if (!ts8.isStringLiteral(node.moduleSpecifier)) return;
|
|
2075
|
+
const source = node.moduleSpecifier.text;
|
|
2076
|
+
const clause = node.importClause;
|
|
2077
|
+
if (clause?.name) imports.set(clause.name.text, source);
|
|
2078
|
+
const named = clause?.namedBindings;
|
|
2079
|
+
if (named && ts8.isNamedImports(named))
|
|
2080
|
+
for (const item of named.elements) imports.set(item.name.text, source);
|
|
2081
|
+
if (named && ts8.isNamespaceImport(named))
|
|
2082
|
+
imports.set(named.name.text, source);
|
|
2083
|
+
}
|
|
2084
|
+
function collectCjsImportSources(imports, node) {
|
|
2085
|
+
for (const declaration of node.declarationList.declarations) {
|
|
2086
|
+
const source = declaration.initializer ? requireSource(declaration.initializer) : void 0;
|
|
2087
|
+
if (!source) continue;
|
|
2088
|
+
if (ts8.isIdentifier(declaration.name))
|
|
2089
|
+
imports.set(declaration.name.text, source);
|
|
2090
|
+
if (ts8.isObjectBindingPattern(declaration.name)) {
|
|
2091
|
+
for (const item of declaration.name.elements)
|
|
2092
|
+
if (ts8.isIdentifier(item.name)) imports.set(item.name.text, source);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
469
2095
|
}
|
|
470
|
-
function
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
2096
|
+
function classPropertySymbol(collection, node, parentClass) {
|
|
2097
|
+
const initializer = node.initializer;
|
|
2098
|
+
const localName = nameOf(node.name);
|
|
2099
|
+
if (!localName || !initializer || !ts8.isArrowFunction(initializer) && !ts8.isFunctionExpression(initializer)) return;
|
|
2100
|
+
const staticPublic = publicStaticProperty(collection, node, parentClass);
|
|
2101
|
+
const memberKind = propertyMemberKind(initializer, staticPublic);
|
|
2102
|
+
addExecutableSymbol(
|
|
2103
|
+
collection,
|
|
2104
|
+
"method",
|
|
2105
|
+
localName,
|
|
2106
|
+
initializer,
|
|
2107
|
+
parentClass,
|
|
2108
|
+
staticPublic ? `${parentClass}.${localName}` : void 0,
|
|
2109
|
+
staticPublic ? { source: "exported_class_member", exportedClass: parentClass, memberKind } : { source: "class_property_function", memberKind }
|
|
2110
|
+
);
|
|
2111
|
+
}
|
|
2112
|
+
function publicStaticProperty(collection, node, parentClass) {
|
|
2113
|
+
const flags = ts8.getCombinedModifierFlags(node);
|
|
2114
|
+
return collection.exportedClasses.has(parentClass) && Boolean(flags & ts8.ModifierFlags.Static) && (flags & ts8.ModifierFlags.Private) === 0 && (flags & ts8.ModifierFlags.Protected) === 0;
|
|
2115
|
+
}
|
|
2116
|
+
function propertyMemberKind(initializer, staticPublic) {
|
|
2117
|
+
if (ts8.isArrowFunction(initializer))
|
|
2118
|
+
return staticPublic ? "static_arrow_function" : "arrow_function_property";
|
|
2119
|
+
return staticPublic ? "static_function_expression" : "function_expression_property";
|
|
2120
|
+
}
|
|
2121
|
+
function objectCallable(property) {
|
|
2122
|
+
if (ts8.isMethodDeclaration(property)) return property;
|
|
2123
|
+
return ts8.isPropertyAssignment(property) && isObjectFunction(property.initializer) ? property.initializer : void 0;
|
|
2124
|
+
}
|
|
2125
|
+
function objectLiteralSymbols(collection, objectName, object, objectIsExported) {
|
|
2126
|
+
if (objectIsExported) collection.objectExports.add(objectName);
|
|
2127
|
+
for (const property of object.properties) {
|
|
2128
|
+
if (objectIsExported && ts8.isShorthandPropertyAssignment(property))
|
|
2129
|
+
addAliasSymbol(collection, objectName, property.name.text, property.name);
|
|
2130
|
+
const callable = objectCallable(property);
|
|
2131
|
+
const propertyName3 = callable ? nameOf(property.name) : void 0;
|
|
2132
|
+
if (callable && propertyName3)
|
|
2133
|
+
addExecutableSymbol(
|
|
2134
|
+
collection,
|
|
2135
|
+
"object_method",
|
|
2136
|
+
propertyName3,
|
|
2137
|
+
callable,
|
|
2138
|
+
objectName
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
function variableSymbols(collection, node) {
|
|
2143
|
+
for (const declaration of node.declarationList.declarations) {
|
|
2144
|
+
const localName = nameOf(declaration.name);
|
|
2145
|
+
const initializer = declaration.initializer;
|
|
2146
|
+
if (!localName || !initializer) continue;
|
|
2147
|
+
if (isFunctionLike(initializer)) addExecutableSymbol(
|
|
2148
|
+
collection,
|
|
2149
|
+
"function",
|
|
2150
|
+
localName,
|
|
2151
|
+
initializer,
|
|
2152
|
+
void 0,
|
|
2153
|
+
exported(node) ? localName : collection.exportNames.get(localName)
|
|
2154
|
+
);
|
|
2155
|
+
if (ts8.isObjectLiteralExpression(initializer))
|
|
2156
|
+
objectLiteralSymbols(
|
|
2157
|
+
collection,
|
|
2158
|
+
localName,
|
|
2159
|
+
initializer,
|
|
2160
|
+
exported(node) || collection.exportNames.has(localName)
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
function collectClassDeclaration(collection, node) {
|
|
2165
|
+
if (!ts8.isClassDeclaration(node) || !node.name) return false;
|
|
2166
|
+
collection.declaredClasses.add(node.name.text);
|
|
2167
|
+
if (exported(node) || collection.exportNames.has(node.name.text))
|
|
2168
|
+
collection.exportedClasses.add(node.name.text);
|
|
2169
|
+
if (defaultExported(node))
|
|
2170
|
+
collection.defaultExportedClasses.add(node.name.text);
|
|
2171
|
+
for (const member of node.members)
|
|
2172
|
+
visitDeclaredSymbol(collection, member, node.name.text);
|
|
2173
|
+
return true;
|
|
2174
|
+
}
|
|
2175
|
+
function collectMethodDeclaration(collection, node, parentClass) {
|
|
2176
|
+
if (!ts8.isMethodDeclaration(node)) return false;
|
|
2177
|
+
const localName = nameOf(node.name);
|
|
2178
|
+
if (localName)
|
|
2179
|
+
addExecutableSymbol(collection, "method", localName, node, parentClass);
|
|
2180
|
+
return true;
|
|
2181
|
+
}
|
|
2182
|
+
function visitDeclaredSymbol(collection, node, parentClass) {
|
|
2183
|
+
if (collectClassDeclaration(collection, node)) return;
|
|
2184
|
+
if (collectMethodDeclaration(collection, node, parentClass)) return;
|
|
2185
|
+
if (ts8.isPropertyDeclaration(node)) {
|
|
2186
|
+
if (parentClass) classPropertySymbol(collection, node, parentClass);
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
if (ts8.isFunctionDeclaration(node) && node.name) {
|
|
2190
|
+
addExecutableSymbol(
|
|
2191
|
+
collection,
|
|
2192
|
+
"function",
|
|
2193
|
+
node.name.text,
|
|
2194
|
+
node,
|
|
2195
|
+
void 0,
|
|
2196
|
+
exported(node) ? node.name.text : void 0
|
|
2197
|
+
);
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
if (ts8.isVariableStatement(node)) {
|
|
2201
|
+
variableSymbols(collection, node);
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
ts8.forEachChild(node, (child) => visitDeclaredSymbol(collection, child, parentClass));
|
|
2205
|
+
}
|
|
2206
|
+
function collectDeclaredSymbols(collection) {
|
|
2207
|
+
visitDeclaredSymbol(collection, collection.source);
|
|
2208
|
+
}
|
|
2209
|
+
function isTopLevelCallback(node) {
|
|
2210
|
+
if (!ts8.isArrowFunction(node) && !ts8.isFunctionExpression(node) || !ts8.isCallExpression(node.parent)) return false;
|
|
2211
|
+
const callee = symbolCallName(node.parent.expression);
|
|
2212
|
+
const member = callee.member ?? callee.local;
|
|
2213
|
+
return Boolean(member && [
|
|
2214
|
+
"bootstrap",
|
|
2215
|
+
"served",
|
|
2216
|
+
"connect",
|
|
2217
|
+
"on",
|
|
2218
|
+
"once",
|
|
2219
|
+
"use",
|
|
2220
|
+
"get",
|
|
2221
|
+
"post",
|
|
2222
|
+
"put",
|
|
2223
|
+
"patch",
|
|
2224
|
+
"delete",
|
|
2225
|
+
"subscribe"
|
|
2226
|
+
].includes(member));
|
|
2227
|
+
}
|
|
2228
|
+
function collectCallbackSymbols(collection) {
|
|
2229
|
+
const visit = (node) => {
|
|
2230
|
+
if (isTopLevelCallback(node) && containsSupportedOutboundCall(node, collection.classifiedCalls)) {
|
|
2231
|
+
const startLine = lineOf2(
|
|
2232
|
+
collection.source,
|
|
2233
|
+
node.getStart(collection.source)
|
|
2234
|
+
);
|
|
2235
|
+
const name = `callback:${startLine}`;
|
|
2236
|
+
collection.symbols.push({
|
|
2237
|
+
kind: "callback",
|
|
2238
|
+
localName: name,
|
|
2239
|
+
qualifiedName: `module:${collection.sourceFile}#${name}`,
|
|
2240
|
+
sourceFile: collection.sourceFile,
|
|
2241
|
+
startLine,
|
|
2242
|
+
endLine: lineOf2(collection.source, node.getEnd()),
|
|
2243
|
+
startOffset: node.getStart(collection.source),
|
|
2244
|
+
endOffset: node.getEnd(),
|
|
2245
|
+
exported: false,
|
|
2246
|
+
importExportEvidence: {
|
|
2247
|
+
source: "synthetic_outbound_callback",
|
|
2248
|
+
callbackLine: startLine
|
|
2249
|
+
}
|
|
480
2250
|
});
|
|
481
|
-
return properties.length > 0 ? [{ index, kind: "object_pattern", properties }] : [];
|
|
482
|
-
}
|
|
483
|
-
if (ts.isArrayBindingPattern(param.name)) {
|
|
484
|
-
const elements = param.name.elements.flatMap((element, elementIndex) => ts.isBindingElement(element) && !element.dotDotDotToken && ts.isIdentifier(element.name) ? [{ index: elementIndex, local: element.name.text }] : []);
|
|
485
|
-
return elements.length > 0 ? [{ index, kind: "array_pattern", elements }] : [];
|
|
486
2251
|
}
|
|
487
|
-
|
|
488
|
-
}
|
|
2252
|
+
ts8.forEachChild(node, visit);
|
|
2253
|
+
};
|
|
2254
|
+
visit(collection.source);
|
|
2255
|
+
}
|
|
2256
|
+
function createCollection(source, sourceFile2, classifiedCalls) {
|
|
2257
|
+
return {
|
|
2258
|
+
source,
|
|
2259
|
+
sourceFile: sourceFile2,
|
|
2260
|
+
classifiedCalls,
|
|
2261
|
+
symbols: [],
|
|
2262
|
+
imports: /* @__PURE__ */ new Map(),
|
|
2263
|
+
importBindings: collectSymbolImportBindings(source),
|
|
2264
|
+
exportNames: exportDeclarations(source),
|
|
2265
|
+
objectExports: /* @__PURE__ */ new Set(),
|
|
2266
|
+
exportedClasses: /* @__PURE__ */ new Set(),
|
|
2267
|
+
defaultExportedClasses: /* @__PURE__ */ new Set(),
|
|
2268
|
+
declaredClasses: /* @__PURE__ */ new Set(),
|
|
2269
|
+
proxies: /* @__PURE__ */ new Map(),
|
|
2270
|
+
instances: /* @__PURE__ */ new Map()
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2273
|
+
function populateCollection(collection) {
|
|
2274
|
+
collectImportSources(collection);
|
|
2275
|
+
collectDeclaredSymbols(collection);
|
|
2276
|
+
collectCallbackSymbols(collection);
|
|
2277
|
+
collectDerivedSymbolContexts(collection);
|
|
489
2278
|
}
|
|
490
|
-
async function
|
|
2279
|
+
async function sourceFile(repoPath, filePath, context) {
|
|
491
2280
|
const snapshot = context?.get(filePath);
|
|
492
2281
|
const text = snapshot?.text ?? await fs4.readFile(path4.join(repoPath, filePath), "utf8");
|
|
493
|
-
|
|
2282
|
+
return snapshot?.sourceFile() ?? ts8.createSourceFile(
|
|
494
2283
|
filePath,
|
|
495
2284
|
text,
|
|
496
|
-
|
|
2285
|
+
ts8.ScriptTarget.Latest,
|
|
497
2286
|
true,
|
|
498
|
-
filePath.endsWith(".ts") ?
|
|
2287
|
+
filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS
|
|
499
2288
|
);
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const
|
|
503
|
-
const
|
|
504
|
-
const
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
const parameterEvidence = { ...bindings && bindings.length > 0 ? { parameters: params, parameterBindings: bindings } : {}, ...aliases.length > 0 ? { parameterPropertyAliases: aliases } : {} };
|
|
526
|
-
symbols.push({ kind, localName: kind === "object_method" ? qualifiedName : localName, exportedName: effectiveExportedName, qualifiedName, sourceFile, startLine: lineOf(source, node.getStart(source)), endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: exported(node) || Boolean(effectiveExportedName), importExportEvidence: sourceEvidence ? { ...sourceEvidence, ...parameterEvidence } : bindings && bindings.length > 0 ? parameterEvidence : void 0 });
|
|
527
|
-
};
|
|
528
|
-
const addAliasSymbol = (objectName, propertyName, node, targetImportSource) => {
|
|
529
|
-
symbols.push({ kind: "object_alias", localName: propertyName, exportedName: propertyName, qualifiedName: `${objectName}.${propertyName}`, sourceFile, startLine: lineOf(source, node.getStart(source)), endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: true, importExportEvidence: { source: "exported_object_shorthand", objectName, propertyName, targetImportSource } });
|
|
530
|
-
};
|
|
531
|
-
const visitImports = (node) => {
|
|
532
|
-
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
533
|
-
const sourceText = node.moduleSpecifier.text;
|
|
534
|
-
const clause = node.importClause;
|
|
535
|
-
if (clause?.name) imports.set(clause.name.text, sourceText);
|
|
536
|
-
const named = clause?.namedBindings;
|
|
537
|
-
if (named && ts.isNamedImports(named)) for (const el of named.elements) imports.set(el.name.text, sourceText);
|
|
538
|
-
if (named && ts.isNamespaceImport(named)) {
|
|
539
|
-
imports.set(named.name.text, sourceText);
|
|
540
|
-
namespaceImports.add(named.name.text);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
if (ts.isVariableStatement(node)) {
|
|
544
|
-
for (const declaration of node.declarationList.declarations) {
|
|
545
|
-
const requiredSource = declaration.initializer ? requireSource(declaration.initializer) : void 0;
|
|
546
|
-
if (ts.isIdentifier(declaration.name) && requiredSource) imports.set(declaration.name.text, requiredSource);
|
|
547
|
-
if (ts.isObjectBindingPattern(declaration.name) && requiredSource) {
|
|
548
|
-
for (const element of declaration.name.elements) if (ts.isIdentifier(element.name)) imports.set(element.name.text, requiredSource);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
ts.forEachChild(node, visitImports);
|
|
553
|
-
};
|
|
554
|
-
visitImports(source);
|
|
555
|
-
const visitSymbols = (node, parentClass) => {
|
|
556
|
-
if (ts.isClassDeclaration(node) && node.name) {
|
|
557
|
-
declaredClasses.add(node.name.text);
|
|
558
|
-
if (exported(node) || exportNames.has(node.name.text)) exportedClasses.add(node.name.text);
|
|
559
|
-
for (const member of node.members) visitSymbols(member, node.name.text);
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
if (ts.isMethodDeclaration(node)) {
|
|
563
|
-
const localName = nameOf(node.name);
|
|
564
|
-
if (localName) addSymbol("method", localName, node, parentClass);
|
|
565
|
-
} else if (ts.isPropertyDeclaration(node) && parentClass && node.initializer && (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) {
|
|
566
|
-
const localName = nameOf(node.name);
|
|
567
|
-
if (localName) {
|
|
568
|
-
const flags = ts.getCombinedModifierFlags(node);
|
|
569
|
-
const staticPublicExported = exportedClasses.has(parentClass) && (flags & ts.ModifierFlags.Static) !== 0 && (flags & ts.ModifierFlags.Private) === 0 && (flags & ts.ModifierFlags.Protected) === 0;
|
|
570
|
-
const isArrow = ts.isArrowFunction(node.initializer);
|
|
571
|
-
const memberKind = isArrow ? staticPublicExported ? "static_arrow_function" : "arrow_function_property" : staticPublicExported ? "static_function_expression" : "function_expression_property";
|
|
572
|
-
addSymbol("method", localName, node.initializer, parentClass, staticPublicExported ? `${parentClass}.${localName}` : void 0, staticPublicExported ? { source: "exported_class_member", exportedClass: parentClass, memberKind } : { source: "class_property_function", memberKind });
|
|
573
|
-
}
|
|
574
|
-
} else if (ts.isFunctionDeclaration(node) && node.name) addSymbol("function", node.name.text, node, void 0, exported(node) ? node.name.text : void 0);
|
|
575
|
-
else if (ts.isVariableStatement(node)) {
|
|
576
|
-
for (const d of node.declarationList.declarations) {
|
|
577
|
-
const localName = nameOf(d.name);
|
|
578
|
-
if (!localName || !d.initializer) continue;
|
|
579
|
-
if (isFunctionLike(d.initializer)) addSymbol("function", localName, d.initializer, void 0, exported(node) ? localName : exportNames.get(localName));
|
|
580
|
-
if (ts.isObjectLiteralExpression(d.initializer)) {
|
|
581
|
-
const objectIsExported = exported(node) || exportNames.has(localName);
|
|
582
|
-
if (objectIsExported) objectExports.add(localName);
|
|
583
|
-
for (const prop of d.initializer.properties) {
|
|
584
|
-
if (objectIsExported && ts.isShorthandPropertyAssignment(prop)) addAliasSymbol(localName, prop.name.text, prop.name, imports.get(prop.name.text));
|
|
585
|
-
if (ts.isPropertyAssignment(prop) && isObjectFunction(prop.initializer)) {
|
|
586
|
-
const propName = nameOf(prop.name);
|
|
587
|
-
if (propName) addSymbol("object_method", propName, prop.initializer, localName);
|
|
588
|
-
} else if (ts.isMethodDeclaration(prop)) {
|
|
589
|
-
const propName = nameOf(prop.name);
|
|
590
|
-
if (propName) addSymbol("object_method", propName, prop, localName);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
} else ts.forEachChild(node, (child) => visitSymbols(child, parentClass));
|
|
596
|
-
};
|
|
597
|
-
visitSymbols(source);
|
|
598
|
-
const isTopLevelCallback = (node) => {
|
|
599
|
-
if (!ts.isArrowFunction(node) && !ts.isFunctionExpression(node)) return false;
|
|
600
|
-
if (!ts.isCallExpression(node.parent)) return false;
|
|
601
|
-
const callee = callName(node.parent.expression);
|
|
602
|
-
const member = callee.member ?? callee.local;
|
|
603
|
-
return Boolean(member && ["bootstrap", "served", "connect", "on", "once", "use", "get", "post", "put", "patch", "delete", "subscribe"].includes(member));
|
|
604
|
-
};
|
|
605
|
-
const visitCallbackSymbols = (node) => {
|
|
606
|
-
if (isTopLevelCallback(node) && containsSupportedOutboundCall(node)) {
|
|
607
|
-
const startLine = lineOf(source, node.getStart(source));
|
|
608
|
-
const name = `callback:${startLine}`;
|
|
609
|
-
symbols.push({ kind: "callback", localName: name, qualifiedName: `module:${sourceFile}#${name}`, sourceFile, startLine, endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: false, importExportEvidence: { source: "synthetic_outbound_callback", callbackLine: startLine } });
|
|
610
|
-
}
|
|
611
|
-
ts.forEachChild(node, visitCallbackSymbols);
|
|
612
|
-
};
|
|
613
|
-
visitCallbackSymbols(source);
|
|
614
|
-
const visitEventRegistrationSymbols = (node) => {
|
|
615
|
-
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "on") {
|
|
616
|
-
const receiver = node.expression.expression.getText(source);
|
|
617
|
-
const eventArg = node.arguments[0];
|
|
618
|
-
if ((receiver === "cds" || /^(srv|service|serviceClient|messaging|messageClient|eventClient|.*Client)$/.test(receiver)) && eventArg && (ts.isStringLiteral(eventArg) || ts.isNoSubstitutionTemplateLiteral(eventArg))) {
|
|
619
|
-
const startLine = lineOf(source, node.getStart(source));
|
|
620
|
-
const eventName = eventArg.text.replace(/[^A-Za-z0-9_$-]/g, "_");
|
|
621
|
-
const name = `event:${eventName}:${startLine}`;
|
|
622
|
-
symbols.push({ kind: "event_registration", localName: name, qualifiedName: `module:${sourceFile}#${name}`, sourceFile, startLine, endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: false, importExportEvidence: { source: "synthetic_event_registration", eventName: eventArg.text, registrationLine: startLine, receiver } });
|
|
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 });
|
|
662
|
-
}
|
|
663
|
-
ts.forEachChild(node, visitProxyVariables);
|
|
664
|
-
};
|
|
665
|
-
visitProxyVariables(source);
|
|
666
|
-
const rememberClassInstance = (variableName, className, propertyName) => {
|
|
667
|
-
const importSource = imports.get(className);
|
|
668
|
-
if (!builtInConstructors.has(className) && (importSource && isRelativeImport(importSource) || declaredClasses.has(className))) classInstances.set(variableName, { className, importSource, propertyName });
|
|
2289
|
+
}
|
|
2290
|
+
async function parseExecutableSymbols(repoPath, filePath, context, preparedOutboundCalls) {
|
|
2291
|
+
const source = await sourceFile(repoPath, filePath, context);
|
|
2292
|
+
const normalizedFile = normalizePath(filePath);
|
|
2293
|
+
const classified = preparedOutboundCalls ?? classifyOutboundCallsInSource(source, normalizedFile);
|
|
2294
|
+
const collection = createCollection(source, normalizedFile, classified);
|
|
2295
|
+
populateCollection(collection);
|
|
2296
|
+
const calls = collectSymbolCallFacts({
|
|
2297
|
+
source,
|
|
2298
|
+
sourceFile: normalizedFile,
|
|
2299
|
+
symbols: collection.symbols,
|
|
2300
|
+
imports: collection.imports,
|
|
2301
|
+
importBindings: collection.importBindings,
|
|
2302
|
+
proxies: collection.proxies,
|
|
2303
|
+
instances: collection.instances
|
|
2304
|
+
});
|
|
2305
|
+
const events = reconcileEventSubscriptions(
|
|
2306
|
+
source,
|
|
2307
|
+
classified,
|
|
2308
|
+
collection.symbols,
|
|
2309
|
+
calls
|
|
2310
|
+
);
|
|
2311
|
+
return {
|
|
2312
|
+
symbols: events.symbols,
|
|
2313
|
+
calls: reconcileSymbolCallOwners(events.calls, events.symbols)
|
|
669
2314
|
};
|
|
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
|
-
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
// src/parsers/008-package-surface-publication.ts
|
|
2318
|
+
function symbolIdentity(symbol) {
|
|
2319
|
+
return [
|
|
2320
|
+
symbol.sourceFile,
|
|
2321
|
+
symbol.kind,
|
|
2322
|
+
symbol.qualifiedName,
|
|
2323
|
+
symbol.startOffset,
|
|
2324
|
+
symbol.endOffset
|
|
2325
|
+
].join("\0");
|
|
2326
|
+
}
|
|
2327
|
+
function exposureBySymbol(analysis) {
|
|
2328
|
+
return new Map(analysis.symbols.map((item) => [
|
|
2329
|
+
symbolIdentity({
|
|
2330
|
+
sourceFile: item.target.sourceFile,
|
|
2331
|
+
kind: item.target.kind,
|
|
2332
|
+
qualifiedName: item.target.qualifiedName,
|
|
2333
|
+
startOffset: item.target.startOffset,
|
|
2334
|
+
endOffset: item.target.endOffset
|
|
2335
|
+
}),
|
|
2336
|
+
item
|
|
2337
|
+
]));
|
|
2338
|
+
}
|
|
2339
|
+
function analyzeRepositoryPackageSurface(facts, manifest, sources) {
|
|
2340
|
+
const modules = sources.entries().filter((snapshot) => /\.[jt]s$/.test(snapshot.filePath)).map((snapshot) => ({
|
|
2341
|
+
sourceFile: normalizePath(snapshot.filePath),
|
|
2342
|
+
source: snapshot.sourceFile()
|
|
2343
|
+
}));
|
|
2344
|
+
return analyzePackagePublicSurface(facts.packageName, manifest, modules);
|
|
2345
|
+
}
|
|
2346
|
+
function mergePackageSymbolEvidence(symbols, analysis) {
|
|
2347
|
+
const exposures = exposureBySymbol(analysis);
|
|
2348
|
+
return symbols.map((symbol) => {
|
|
2349
|
+
const exposure = exposures.get(symbolIdentity(symbol));
|
|
2350
|
+
if (!exposure) return symbol;
|
|
2351
|
+
return {
|
|
2352
|
+
...symbol,
|
|
2353
|
+
importExportEvidence: {
|
|
2354
|
+
...symbol.importExportEvidence ?? {},
|
|
2355
|
+
packagePublicSurface: {
|
|
2356
|
+
schema: analysis.surface.schema,
|
|
2357
|
+
recordCap: analysis.surface.recordCap,
|
|
2358
|
+
bodyEligibility: exposure.target.bodyEligibility,
|
|
2359
|
+
exposures: exposure.exposures,
|
|
2360
|
+
exposureTotal: exposure.exposureTotal,
|
|
2361
|
+
shownExposureCount: exposure.shownExposureCount,
|
|
2362
|
+
omittedExposureCount: exposure.omittedExposureCount
|
|
2363
|
+
}
|
|
708
2364
|
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
};
|
|
712
|
-
visitCalls(source);
|
|
713
|
-
return { symbols, calls };
|
|
2365
|
+
};
|
|
2366
|
+
});
|
|
714
2367
|
}
|
|
715
2368
|
|
|
716
2369
|
// src/utils/hashing.ts
|
|
@@ -720,6 +2373,136 @@ function sha256Text(text) {
|
|
|
720
2373
|
return createHash("sha256").update(text).digest("hex");
|
|
721
2374
|
}
|
|
722
2375
|
|
|
2376
|
+
// src/db/004-package-target-invalidation.ts
|
|
2377
|
+
var resolverKeys = /* @__PURE__ */ new Set([
|
|
2378
|
+
"candidateStrategy",
|
|
2379
|
+
"candidateCount",
|
|
2380
|
+
"eligibleCandidateCount",
|
|
2381
|
+
"selectedCandidateCount",
|
|
2382
|
+
"candidateSetComplete",
|
|
2383
|
+
"resolvedModulePath",
|
|
2384
|
+
"resolvedTargetRepositoryId",
|
|
2385
|
+
"unresolvedReason",
|
|
2386
|
+
"targetRepositoryCandidateCount",
|
|
2387
|
+
"targetRepositoryCandidates",
|
|
2388
|
+
"shownTargetRepositoryCandidateCount",
|
|
2389
|
+
"omittedTargetRepositoryCandidateCount",
|
|
2390
|
+
"publicSurface"
|
|
2391
|
+
]);
|
|
2392
|
+
function record(value) {
|
|
2393
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2394
|
+
}
|
|
2395
|
+
function parsedEvidence(value) {
|
|
2396
|
+
if (typeof value !== "string") return void 0;
|
|
2397
|
+
try {
|
|
2398
|
+
return record(JSON.parse(value));
|
|
2399
|
+
} catch {
|
|
2400
|
+
return void 0;
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
function packageName(evidence) {
|
|
2404
|
+
return parsePackageImportReference(evidence.importBinding)?.requestedPackageName ?? void 0;
|
|
2405
|
+
}
|
|
2406
|
+
function packageCallEvidenceValid(evidence) {
|
|
2407
|
+
const binding = record(evidence.importBinding);
|
|
2408
|
+
const classified = evidence.relation === "package_import" || binding?.moduleKind === "package";
|
|
2409
|
+
return !classified || packageName(evidence) !== void 0;
|
|
2410
|
+
}
|
|
2411
|
+
function currentCalls(db, workspaceId, targetRepoId) {
|
|
2412
|
+
const rows = db.prepare(`SELECT sc.id,sc.repo_id repoId,
|
|
2413
|
+
sc.evidence_json evidenceJson FROM symbol_calls sc
|
|
2414
|
+
JOIN repositories r ON r.id=sc.repo_id
|
|
2415
|
+
WHERE r.workspace_id=? AND r.id<>? AND r.fact_analyzer_version=?
|
|
2416
|
+
ORDER BY sc.id`).all(workspaceId, targetRepoId, ANALYZER_VERSION);
|
|
2417
|
+
return rows.flatMap((row) => {
|
|
2418
|
+
const evidence = parsedEvidence(row.evidenceJson);
|
|
2419
|
+
if (!evidence || typeof row.id !== "number" || typeof row.repoId !== "number")
|
|
2420
|
+
throw new Error("invalid_current_package_import_evidence");
|
|
2421
|
+
if (!packageCallEvidenceValid(evidence))
|
|
2422
|
+
throw new Error("invalid_current_package_import_evidence");
|
|
2423
|
+
return evidence.relation === "package_import" || record(evidence.importBinding)?.moduleKind === "package" ? [{ id: row.id, repoId: row.repoId, evidence }] : [];
|
|
2424
|
+
});
|
|
2425
|
+
}
|
|
2426
|
+
function pendingEvidence(evidence) {
|
|
2427
|
+
const parser = Object.fromEntries(Object.entries(evidence).filter(
|
|
2428
|
+
([key]) => !resolverKeys.has(key)
|
|
2429
|
+
));
|
|
2430
|
+
if (!packageName(parser))
|
|
2431
|
+
throw new Error("invalid_current_package_import_evidence");
|
|
2432
|
+
return JSON.stringify({
|
|
2433
|
+
...parser,
|
|
2434
|
+
candidateStrategy: "package_import_pending",
|
|
2435
|
+
candidateCount: 0,
|
|
2436
|
+
eligibleCandidateCount: 0,
|
|
2437
|
+
selectedCandidateCount: 0,
|
|
2438
|
+
candidateSetComplete: false,
|
|
2439
|
+
unresolvedReason: "package_resolution_pending"
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
function targetWorkspace(db, repoId) {
|
|
2443
|
+
const row = db.prepare(`SELECT workspace_id workspaceId,
|
|
2444
|
+
package_name packageName FROM repositories WHERE id=?`).get(repoId);
|
|
2445
|
+
if (typeof row?.workspaceId !== "number")
|
|
2446
|
+
throw new Error("Repository target is missing its workspace");
|
|
2447
|
+
return {
|
|
2448
|
+
workspaceId: row.workspaceId,
|
|
2449
|
+
packageName: typeof row.packageName === "string" || row.packageName === null ? row.packageName : void 0
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
function packageIdentityChanged(previous, next) {
|
|
2453
|
+
const previousName = typeof previous === "string" ? previous : null;
|
|
2454
|
+
const nextName = typeof next === "string" ? next : null;
|
|
2455
|
+
return previousName !== nextName;
|
|
2456
|
+
}
|
|
2457
|
+
function invalidatePackageTargetFacts(db, targetRepoId, newPackageName, batch) {
|
|
2458
|
+
const target = targetWorkspace(db, targetRepoId);
|
|
2459
|
+
const names = new Set(
|
|
2460
|
+
[target.packageName, newPackageName].filter(
|
|
2461
|
+
(value) => typeof value === "string" && value.length > 0
|
|
2462
|
+
)
|
|
2463
|
+
);
|
|
2464
|
+
if (names.size === 0) return;
|
|
2465
|
+
const update = db.prepare(`UPDATE symbol_calls SET callee_symbol_id=NULL,
|
|
2466
|
+
status='unresolved',unresolved_reason='package_resolution_pending',
|
|
2467
|
+
evidence_json=? WHERE id=?`);
|
|
2468
|
+
let matched = false;
|
|
2469
|
+
for (const call of currentCalls(db, target.workspaceId, targetRepoId)) {
|
|
2470
|
+
const requested = packageName(call.evidence);
|
|
2471
|
+
if (!requested || !names.has(requested)) continue;
|
|
2472
|
+
update.run(pendingEvidence(call.evidence), call.id);
|
|
2473
|
+
batch.affectedCallerRepoIds.add(call.repoId);
|
|
2474
|
+
matched = true;
|
|
2475
|
+
}
|
|
2476
|
+
if (matched || packageIdentityChanged(
|
|
2477
|
+
target.packageName,
|
|
2478
|
+
newPackageName
|
|
2479
|
+
)) batch.affectedWorkspaceIds.add(target.workspaceId);
|
|
2480
|
+
}
|
|
2481
|
+
function createPackageInvalidationBatch(publishingRepoIds) {
|
|
2482
|
+
return {
|
|
2483
|
+
publishingRepoIds: new Set(publishingRepoIds),
|
|
2484
|
+
affectedCallerRepoIds: /* @__PURE__ */ new Set(),
|
|
2485
|
+
affectedWorkspaceIds: /* @__PURE__ */ new Set()
|
|
2486
|
+
};
|
|
2487
|
+
}
|
|
2488
|
+
function mergePackageInvalidationEffects(target, source) {
|
|
2489
|
+
for (const repoId of source.affectedCallerRepoIds)
|
|
2490
|
+
target.affectedCallerRepoIds.add(repoId);
|
|
2491
|
+
for (const workspaceId of source.affectedWorkspaceIds)
|
|
2492
|
+
target.affectedWorkspaceIds.add(workspaceId);
|
|
2493
|
+
}
|
|
2494
|
+
function finalizePackageTargetInvalidations(db, batch) {
|
|
2495
|
+
const increment = db.prepare(`UPDATE repositories
|
|
2496
|
+
SET fact_generation=fact_generation+1 WHERE id=?`);
|
|
2497
|
+
for (const repoId of batch.affectedCallerRepoIds)
|
|
2498
|
+
if (!batch.publishingRepoIds.has(repoId)) increment.run(repoId);
|
|
2499
|
+
const stale = db.prepare(`UPDATE repositories
|
|
2500
|
+
SET graph_stale_reason='package_target_facts_changed',
|
|
2501
|
+
graph_stale_at=datetime('now') WHERE workspace_id=?`);
|
|
2502
|
+
for (const workspaceId of batch.affectedWorkspaceIds)
|
|
2503
|
+
stale.run(workspaceId);
|
|
2504
|
+
}
|
|
2505
|
+
|
|
723
2506
|
// src/indexer/repository-indexer.ts
|
|
724
2507
|
async function prepareRepositoryIndex(repo, force, instrumentation) {
|
|
725
2508
|
const sourceFiles = await findSourceFiles(repo.absolute_path);
|
|
@@ -739,24 +2522,51 @@ async function prepareRepositoryIndex(repo, force, instrumentation) {
|
|
|
739
2522
|
packageSnapshot.rawText
|
|
740
2523
|
);
|
|
741
2524
|
if (!force && repo.fingerprint === fingerprint) return { repo, fileCount: 0, diagnosticCount: 0, skipped: true };
|
|
742
|
-
const
|
|
2525
|
+
const parsedFacts = await parseAllSourceFacts(repo.absolute_path, sources);
|
|
2526
|
+
const packageSurface = analyzeRepositoryPackageSurface(
|
|
2527
|
+
packageFacts,
|
|
2528
|
+
packageSnapshot.manifest,
|
|
2529
|
+
sources
|
|
2530
|
+
);
|
|
2531
|
+
const parsed = {
|
|
2532
|
+
...parsedFacts,
|
|
2533
|
+
symbols: mergePackageSymbolEvidence(parsedFacts.symbols, packageSurface)
|
|
2534
|
+
};
|
|
743
2535
|
return {
|
|
744
2536
|
repo,
|
|
745
2537
|
packageFacts,
|
|
746
2538
|
fingerprint,
|
|
747
2539
|
kind: await classifyRepository(repo.absolute_path, packageFacts),
|
|
748
2540
|
parsed,
|
|
2541
|
+
packagePublicSurface: packageSurface.surface,
|
|
749
2542
|
fileCount: sourceFiles.length,
|
|
750
2543
|
diagnosticCount: parsed.handlers.filter((handler) => handler.hasHandlerDecorator && (handler.methods.length === 0 || handler.methods.some((method) => !handlerMethodIsExecutable(method)))).length,
|
|
751
2544
|
skipped: false
|
|
752
2545
|
};
|
|
753
2546
|
}
|
|
754
|
-
function publishPreparedRepositoryIndex(db, prepared) {
|
|
2547
|
+
function publishPreparedRepositoryIndex(db, prepared, invalidations) {
|
|
755
2548
|
if (prepared.skipped) return;
|
|
756
|
-
if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint || !prepared.kind
|
|
2549
|
+
if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint || !prepared.kind || !prepared.packagePublicSurface)
|
|
2550
|
+
throw new Error("Prepared repository index is missing publication facts");
|
|
757
2551
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
758
2552
|
const repoId = prepared.repo.id;
|
|
759
|
-
|
|
2553
|
+
invalidatePackageTargetFacts(
|
|
2554
|
+
db,
|
|
2555
|
+
repoId,
|
|
2556
|
+
prepared.packageFacts.packageName,
|
|
2557
|
+
invalidations
|
|
2558
|
+
);
|
|
2559
|
+
db.prepare(`UPDATE repositories SET package_name=?, package_version=?,
|
|
2560
|
+
dependencies_json=?,package_public_surface_json=?,kind=?,index_status=?
|
|
2561
|
+
WHERE id=?`).run(
|
|
2562
|
+
prepared.packageFacts.packageName,
|
|
2563
|
+
prepared.packageFacts.packageVersion,
|
|
2564
|
+
JSON.stringify(prepared.packageFacts.dependencies),
|
|
2565
|
+
JSON.stringify(prepared.packagePublicSurface),
|
|
2566
|
+
prepared.kind,
|
|
2567
|
+
"indexing",
|
|
2568
|
+
repoId
|
|
2569
|
+
);
|
|
760
2570
|
clearRepoFacts(db, repoId);
|
|
761
2571
|
insertRequires(db, repoId, prepared.packageFacts.cdsRequires);
|
|
762
2572
|
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");
|
|
@@ -770,10 +2580,43 @@ function publishPreparedRepositoryIndex(db, prepared) {
|
|
|
770
2580
|
insertCalls(db, repoId, prepared.parsed.calls);
|
|
771
2581
|
db.prepare("UPDATE repositories SET last_indexed_at=?, index_status='indexed', error_count=0, fingerprint=?, fact_generation=COALESCE(fact_generation,0)+1, graph_stale_reason='facts_changed', graph_stale_at=?, fact_analyzer_version=? WHERE id=?").run(now, prepared.fingerprint, now, ANALYZER_VERSION, repoId);
|
|
772
2582
|
}
|
|
2583
|
+
function publishOneRepository(db, prepared, invalidations) {
|
|
2584
|
+
try {
|
|
2585
|
+
db.transaction(() => withPublicationSavepoint(
|
|
2586
|
+
db,
|
|
2587
|
+
prepared.repo.id,
|
|
2588
|
+
() => publishPreparedRepositoryIndex(db, prepared, invalidations)
|
|
2589
|
+
));
|
|
2590
|
+
return { ok: true };
|
|
2591
|
+
} catch (error) {
|
|
2592
|
+
recordIndexFailure(db, prepared.repo.id, error);
|
|
2593
|
+
return { ok: false, error };
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
function withPublicationSavepoint(db, repoId, publish) {
|
|
2597
|
+
const name = `service_flow_repository_${repoId}`;
|
|
2598
|
+
db.exec(`SAVEPOINT ${name}`);
|
|
2599
|
+
try {
|
|
2600
|
+
const result = publish();
|
|
2601
|
+
db.exec(`RELEASE SAVEPOINT ${name}`);
|
|
2602
|
+
return result;
|
|
2603
|
+
} catch (error) {
|
|
2604
|
+
db.exec(`ROLLBACK TO SAVEPOINT ${name}`);
|
|
2605
|
+
db.exec(`RELEASE SAVEPOINT ${name}`);
|
|
2606
|
+
throw error;
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
773
2609
|
function recordIndexFailure(db, repoId, error) {
|
|
2610
|
+
if (isPreparedRepositorySnapshotError(error)) {
|
|
2611
|
+
recordPreparedSnapshotFailure(db, repoId, error);
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
774
2614
|
const message = errorMessage(error);
|
|
775
2615
|
db.prepare("UPDATE repositories SET index_status='failed', error_count=1 WHERE id=?").run(repoId);
|
|
776
|
-
db.prepare(
|
|
2616
|
+
db.prepare(`DELETE FROM diagnostics WHERE repo_id=? AND (
|
|
2617
|
+
code IN ('index_failed_snapshot_preserved','source_read_failed')
|
|
2618
|
+
OR code GLOB 'invalid_prepared_repository_snapshot:*'
|
|
2619
|
+
)`).run(repoId);
|
|
777
2620
|
db.prepare("INSERT INTO diagnostics(repo_id,severity,code,message) VALUES(?,?,?,?)").run(repoId, "error", "source_read_failed", `Index failed before publication; previous facts and fingerprint were preserved. ${message}`);
|
|
778
2621
|
}
|
|
779
2622
|
async function parseAllSourceFacts(root, sources) {
|
|
@@ -783,13 +2626,36 @@ async function parseAllSourceFacts(root, sources) {
|
|
|
783
2626
|
facts.fileRecords.push({ relativePath: normalizePath(file), extension: path5.extname(file), sha256: sha256Text(snapshot.text), sizeBytes: snapshot.sizeBytes });
|
|
784
2627
|
if (file.endsWith(".cds")) facts.services.push(...await parseCdsFile(root, file, sources));
|
|
785
2628
|
if (/\.[jt]s$/.test(file)) {
|
|
2629
|
+
const source = snapshot.sourceFile();
|
|
2630
|
+
const classified = classifyOutboundCallsInSource(source, file);
|
|
786
2631
|
facts.handlers.push(...await parseDecorators(root, file, sources));
|
|
787
2632
|
facts.registrations.push(...await parseHandlerRegistrations(root, file, sources));
|
|
788
|
-
|
|
789
|
-
const symbolFacts = await parseExecutableSymbols(
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
2633
|
+
const bindings = await parseServiceBindings(root, file, sources);
|
|
2634
|
+
const symbolFacts = await parseExecutableSymbols(
|
|
2635
|
+
root,
|
|
2636
|
+
file,
|
|
2637
|
+
sources,
|
|
2638
|
+
classified
|
|
2639
|
+
);
|
|
2640
|
+
const outboundCalls = await parseOutboundCalls(
|
|
2641
|
+
root,
|
|
2642
|
+
file,
|
|
2643
|
+
sources,
|
|
2644
|
+
classified,
|
|
2645
|
+
bindings
|
|
2646
|
+
);
|
|
2647
|
+
const reconciled = reconcileSourceFacts(
|
|
2648
|
+
source,
|
|
2649
|
+
classified,
|
|
2650
|
+
bindings,
|
|
2651
|
+
outboundCalls,
|
|
2652
|
+
symbolFacts.symbols,
|
|
2653
|
+
symbolFacts.calls
|
|
2654
|
+
);
|
|
2655
|
+
facts.bindings.push(...reconciled.bindings);
|
|
2656
|
+
facts.symbols.push(...reconciled.symbols);
|
|
2657
|
+
facts.symbolCalls.push(...reconciled.symbolCalls);
|
|
2658
|
+
facts.calls.push(...reconciled.outboundCalls);
|
|
793
2659
|
}
|
|
794
2660
|
}
|
|
795
2661
|
return facts;
|
|
@@ -831,9 +2697,8 @@ function repositoryFingerprint(sources, facts, packageJsonText) {
|
|
|
831
2697
|
}
|
|
832
2698
|
|
|
833
2699
|
// src/indexer/cds-extension-resolver.ts
|
|
834
|
-
function materializeCdsExtensionOperations(db, workspaceId) {
|
|
835
|
-
const extensions = db
|
|
836
|
-
FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND s.is_extend=1`).all(workspaceId);
|
|
2700
|
+
function materializeCdsExtensionOperations(db, workspaceId, excludedRepoIds = /* @__PURE__ */ new Set()) {
|
|
2701
|
+
const extensions = extensionRows(db, workspaceId, excludedRepoIds);
|
|
837
2702
|
db.transaction(() => {
|
|
838
2703
|
const changedRepos = /* @__PURE__ */ new Set();
|
|
839
2704
|
for (const extension of extensions) {
|
|
@@ -842,6 +2707,21 @@ function materializeCdsExtensionOperations(db, workspaceId) {
|
|
|
842
2707
|
for (const repoId of changedRepos) markRepositoryDerivedFactsChanged(db, repoId);
|
|
843
2708
|
});
|
|
844
2709
|
}
|
|
2710
|
+
function extensionRows(db, workspaceId, excludedRepoIds) {
|
|
2711
|
+
const excluded = [...excludedRepoIds].sort((left, right) => left - right);
|
|
2712
|
+
const exclusion = excluded.length > 0 ? ` AND r.id NOT IN (${excluded.map(() => "?").join(",")})` : "";
|
|
2713
|
+
return db.prepare(`SELECT s.id,r.id repoId,s.service_name serviceName,
|
|
2714
|
+
s.qualified_name qualifiedName,s.source_file sourceFile,
|
|
2715
|
+
s.extension_module_specifier moduleSpecifier,
|
|
2716
|
+
s.extension_imported_symbol importedSymbol,
|
|
2717
|
+
s.extension_import_kind importKind
|
|
2718
|
+
FROM cds_services s JOIN repositories r ON r.id=s.repo_id
|
|
2719
|
+
WHERE r.workspace_id=? AND s.is_extend=1${exclusion}
|
|
2720
|
+
ORDER BY r.id,s.id`).all(
|
|
2721
|
+
workspaceId,
|
|
2722
|
+
...excluded
|
|
2723
|
+
);
|
|
2724
|
+
}
|
|
845
2725
|
function reconcileExtension(db, workspaceId, extension) {
|
|
846
2726
|
const bases = resolveBase(db, workspaceId, extension);
|
|
847
2727
|
const status = bases.length === 1 ? "resolved" : bases.length > 1 ? "ambiguous" : "unresolved";
|
|
@@ -906,15 +2786,15 @@ function relativeBase(db, extension, symbol) {
|
|
|
906
2786
|
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
2787
|
}
|
|
908
2788
|
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,
|
|
2789
|
+
const packageName2 = packageNameFromSpecifier(specifier);
|
|
2790
|
+
const moduleSuffix = specifier.slice(packageName2.length).replace(/^\//, "");
|
|
2791
|
+
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
2792
|
}
|
|
913
2793
|
function sameRepoBase(db, extension, symbol) {
|
|
914
2794
|
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
2795
|
}
|
|
916
|
-
function normalizeModulePath(
|
|
917
|
-
const base =
|
|
2796
|
+
function normalizeModulePath(sourceFile2, specifier) {
|
|
2797
|
+
const base = sourceFile2.split("/").slice(0, -1).join("/");
|
|
918
2798
|
const parts = `${base}/${specifier}`.split("/");
|
|
919
2799
|
const out = [];
|
|
920
2800
|
for (const part of parts) {
|
|
@@ -985,42 +2865,157 @@ function claimIndexRun(db, workspaceId, repoCount) {
|
|
|
985
2865
|
}
|
|
986
2866
|
}
|
|
987
2867
|
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.`);
|
|
2868
|
+
const repos = selectedRepositories(db, workspaceId, options.repo);
|
|
993
2869
|
const runId = claimIndexRun(db, workspaceId, repos.length);
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
2870
|
+
const state = {
|
|
2871
|
+
fileCount: 0,
|
|
2872
|
+
diagnosticCount: 0,
|
|
2873
|
+
skippedCount: 0,
|
|
2874
|
+
rows: []
|
|
2875
|
+
};
|
|
999
2876
|
try {
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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 };
|
|
2877
|
+
await prepareRepositories(repos, options.force, state);
|
|
2878
|
+
return publishPreparedWorkspaceRows(
|
|
2879
|
+
db,
|
|
2880
|
+
workspaceId,
|
|
2881
|
+
runId,
|
|
2882
|
+
state.rows,
|
|
2883
|
+
options
|
|
2884
|
+
);
|
|
1018
2885
|
} catch (error) {
|
|
1019
|
-
db
|
|
1020
|
-
if (activeRepoId &&
|
|
2886
|
+
finishFailedRun(db, runId, state, error);
|
|
2887
|
+
if (state.activeRepoId && state.rows.length < repos.length)
|
|
2888
|
+
recordIndexFailure(db, state.activeRepoId, error);
|
|
1021
2889
|
throw error;
|
|
1022
2890
|
}
|
|
1023
2891
|
}
|
|
2892
|
+
function selectedRepositories(db, workspaceId, repoName) {
|
|
2893
|
+
const repos = repoName ? reposByName(db, repoName, workspaceId) : listRepositories(db, workspaceId);
|
|
2894
|
+
if (repoName && repos.length === 0)
|
|
2895
|
+
throw new Error(
|
|
2896
|
+
`selector_repo_not_found: no indexed repository matched ${repoName}.`
|
|
2897
|
+
);
|
|
2898
|
+
if (repoName && repos.length > 1)
|
|
2899
|
+
throw new Error(
|
|
2900
|
+
`selector_repo_ambiguous: repository selector ${repoName} matched ${repos.length} repositories; use a unique repository name.`
|
|
2901
|
+
);
|
|
2902
|
+
return repos;
|
|
2903
|
+
}
|
|
2904
|
+
async function prepareRepositories(repos, force, state) {
|
|
2905
|
+
for (const repo of repos) {
|
|
2906
|
+
state.activeRepoId = repo.id;
|
|
2907
|
+
const result = await prepareRepositoryIndex(repo, force);
|
|
2908
|
+
state.rows.push(result);
|
|
2909
|
+
state.fileCount += result.fileCount;
|
|
2910
|
+
state.diagnosticCount += result.diagnosticCount;
|
|
2911
|
+
state.skippedCount += result.skipped ? 1 : 0;
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
function publishPreparedWorkspaceRows(db, workspaceId, runId, rows, options = {}) {
|
|
2915
|
+
const state = publicationState(rows);
|
|
2916
|
+
db.transaction(() => {
|
|
2917
|
+
const effects = createPackageInvalidationBatch([]);
|
|
2918
|
+
const publishedRepoIds = [];
|
|
2919
|
+
for (const row of state.rows) {
|
|
2920
|
+
state.activeRepoId = row.repo.id;
|
|
2921
|
+
if (row.skipped) continue;
|
|
2922
|
+
const result = publishPreparedRow(db, row);
|
|
2923
|
+
if (result.status === "failed") {
|
|
2924
|
+
recordPublicationFailure(db, state, row, result.error);
|
|
2925
|
+
continue;
|
|
2926
|
+
}
|
|
2927
|
+
state.indexedCount += 1;
|
|
2928
|
+
publishedRepoIds.push(row.repo.id);
|
|
2929
|
+
mergePackageInvalidationEffects(effects, result.effects);
|
|
2930
|
+
}
|
|
2931
|
+
if (options.injectDerivedMaterializationFailure)
|
|
2932
|
+
throw new Error("Injected derived materialization failure");
|
|
2933
|
+
materializeCdsExtensionOperations(
|
|
2934
|
+
db,
|
|
2935
|
+
workspaceId,
|
|
2936
|
+
state.failedRepoIds
|
|
2937
|
+
);
|
|
2938
|
+
const invalidations = createPackageInvalidationBatch(publishedRepoIds);
|
|
2939
|
+
mergePackageInvalidationEffects(invalidations, effects);
|
|
2940
|
+
finalizePackageTargetInvalidations(db, invalidations);
|
|
2941
|
+
finishCompletedRun(db, runId, state);
|
|
2942
|
+
});
|
|
2943
|
+
return indexSummary(rows.length, state);
|
|
2944
|
+
}
|
|
2945
|
+
function publicationState(rows) {
|
|
2946
|
+
return {
|
|
2947
|
+
rows,
|
|
2948
|
+
fileCount: rows.reduce((total, row) => total + row.fileCount, 0),
|
|
2949
|
+
diagnosticCount: rows.reduce(
|
|
2950
|
+
(total, row) => total + row.diagnosticCount,
|
|
2951
|
+
0
|
|
2952
|
+
),
|
|
2953
|
+
skippedCount: rows.filter((row) => row.skipped).length,
|
|
2954
|
+
indexedCount: 0,
|
|
2955
|
+
publicationFailureCount: 0,
|
|
2956
|
+
failedRepoIds: /* @__PURE__ */ new Set(),
|
|
2957
|
+
failedRepos: []
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
function publishPreparedRow(db, row) {
|
|
2961
|
+
const effects = createPackageInvalidationBatch([row.repo.id]);
|
|
2962
|
+
const outcome = publishOneRepository(db, row, effects);
|
|
2963
|
+
if (!outcome.ok && !isPreparedRepositorySnapshotError(outcome.error))
|
|
2964
|
+
throw outcome.error;
|
|
2965
|
+
return outcome.ok ? { status: "published", effects } : { status: "failed", error: outcome.error };
|
|
2966
|
+
}
|
|
2967
|
+
function recordPublicationFailure(db, state, row, error) {
|
|
2968
|
+
state.failedRepoIds.add(row.repo.id);
|
|
2969
|
+
state.failedRepos.push({
|
|
2970
|
+
name: row.repo.name,
|
|
2971
|
+
code: isPreparedRepositorySnapshotError(error) ? error.message : "source_read_failed"
|
|
2972
|
+
});
|
|
2973
|
+
state.publicationFailureCount += 1;
|
|
2974
|
+
}
|
|
2975
|
+
function finishCompletedRun(db, runId, state) {
|
|
2976
|
+
const status = completedRunStatus(
|
|
2977
|
+
state.rows.length,
|
|
2978
|
+
state.publicationFailureCount
|
|
2979
|
+
);
|
|
2980
|
+
const error = status === "success" ? null : `${state.publicationFailureCount} repositories failed index publication.`;
|
|
2981
|
+
db.prepare(`UPDATE index_runs SET finished_at=?,status=?,
|
|
2982
|
+
file_count=?,diagnostic_count=?,error_message=? WHERE id=?`).run(
|
|
2983
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
2984
|
+
status,
|
|
2985
|
+
state.fileCount,
|
|
2986
|
+
completedDiagnosticCount(state),
|
|
2987
|
+
error,
|
|
2988
|
+
runId
|
|
2989
|
+
);
|
|
2990
|
+
}
|
|
2991
|
+
function completedRunStatus(repoCount, failedCount) {
|
|
2992
|
+
if (failedCount === 0) return "success";
|
|
2993
|
+
return failedCount === repoCount ? "failed" : "partial_failure";
|
|
2994
|
+
}
|
|
2995
|
+
function completedDiagnosticCount(state) {
|
|
2996
|
+
return state.diagnosticCount + state.publicationFailureCount;
|
|
2997
|
+
}
|
|
2998
|
+
function finishFailedRun(db, runId, state, error) {
|
|
2999
|
+
db.prepare(`UPDATE index_runs SET finished_at=?,status='failed',
|
|
3000
|
+
file_count=?,diagnostic_count=?,error_message=? WHERE id=?`).run(
|
|
3001
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
3002
|
+
state.fileCount,
|
|
3003
|
+
state.diagnosticCount + 1,
|
|
3004
|
+
errorMessage(error),
|
|
3005
|
+
runId
|
|
3006
|
+
);
|
|
3007
|
+
}
|
|
3008
|
+
function indexSummary(repoCount, state) {
|
|
3009
|
+
return {
|
|
3010
|
+
repoCount,
|
|
3011
|
+
indexedCount: state.indexedCount,
|
|
3012
|
+
skippedCount: state.skippedCount,
|
|
3013
|
+
failedCount: state.publicationFailureCount,
|
|
3014
|
+
failedRepos: [...state.failedRepos].sort((left, right) => binaryCompare(left.name, right.name) || binaryCompare(left.code, right.code)),
|
|
3015
|
+
fileCount: state.fileCount,
|
|
3016
|
+
diagnosticCount: completedDiagnosticCount(state)
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
1024
3019
|
|
|
1025
3020
|
// src/cli/001-doctor-projection.ts
|
|
1026
3021
|
var boundedDoctorArrayKeys = /* @__PURE__ */ new Set([
|
|
@@ -1191,15 +3186,88 @@ function analyzerVersionDiagnostics(db, strict, workspaceId) {
|
|
|
1191
3186
|
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
3187
|
}
|
|
1193
3188
|
|
|
3189
|
+
// src/cli/003-doctor-package-resolution.ts
|
|
3190
|
+
function pendingPredicate(alias) {
|
|
3191
|
+
return `${alias}.status='unresolved'
|
|
3192
|
+
AND ${alias}.callee_symbol_id IS NULL
|
|
3193
|
+
AND ${alias}.unresolved_reason='package_resolution_pending'
|
|
3194
|
+
AND json_extract(${alias}.evidence_json,'$.relation')='package_import'
|
|
3195
|
+
AND json_extract(${alias}.evidence_json,
|
|
3196
|
+
'$.importBinding.moduleKind')='package'
|
|
3197
|
+
AND json_extract(${alias}.evidence_json,
|
|
3198
|
+
'$.candidateStrategy')='package_import_pending'
|
|
3199
|
+
AND json_extract(${alias}.evidence_json,'$.candidateCount')=0
|
|
3200
|
+
AND json_extract(${alias}.evidence_json,'$.eligibleCandidateCount')=0
|
|
3201
|
+
AND json_extract(${alias}.evidence_json,'$.selectedCandidateCount')=0
|
|
3202
|
+
AND json_extract(${alias}.evidence_json,'$.candidateSetComplete')=0`;
|
|
3203
|
+
}
|
|
3204
|
+
function count(db, sql) {
|
|
3205
|
+
return Number(db.prepare(sql).get()?.count ?? 0);
|
|
3206
|
+
}
|
|
3207
|
+
function packagePendingDiagnostics(db) {
|
|
3208
|
+
const pending = count(db, `SELECT COUNT(*) count FROM symbol_calls sc
|
|
3209
|
+
WHERE ${pendingPredicate("sc")}`);
|
|
3210
|
+
if (pending === 0) return [];
|
|
3211
|
+
const stale = count(db, `SELECT COUNT(*) count FROM repositories
|
|
3212
|
+
WHERE graph_stale_reason IS NOT NULL`);
|
|
3213
|
+
return [{
|
|
3214
|
+
severity: "warning",
|
|
3215
|
+
code: "package_import_resolution_pending",
|
|
3216
|
+
message: "Package-import facts await workspace linking; terminal package-resolution quality is deferred.",
|
|
3217
|
+
packageResolutionState: "pre_link_pending",
|
|
3218
|
+
pendingPackageImportCount: pending,
|
|
3219
|
+
graphState: "stale",
|
|
3220
|
+
staleRepositoryCount: stale,
|
|
3221
|
+
requiredAction: "relink",
|
|
3222
|
+
remediation: "service-flow link --workspace /workspace --force"
|
|
3223
|
+
}];
|
|
3224
|
+
}
|
|
3225
|
+
function symbolCallQuality(db) {
|
|
3226
|
+
const terminal = `NOT (${pendingPredicate("sc")})`;
|
|
3227
|
+
const row = db.prepare(`SELECT COUNT(*) total,
|
|
3228
|
+
SUM(CASE WHEN sc.status='resolved' THEN 1 ELSE 0 END) resolved,
|
|
3229
|
+
SUM(CASE WHEN sc.status='unresolved' THEN 1 ELSE 0 END) unresolved
|
|
3230
|
+
FROM symbol_calls sc WHERE ${terminal}`).get();
|
|
3231
|
+
const top = db.prepare(`SELECT sc.callee_expression calleeExpression,
|
|
3232
|
+
COUNT(*) count FROM symbol_calls sc
|
|
3233
|
+
WHERE sc.status='unresolved' AND ${terminal}
|
|
3234
|
+
GROUP BY sc.callee_expression
|
|
3235
|
+
ORDER BY count DESC,sc.callee_expression COLLATE BINARY LIMIT 5`).all();
|
|
3236
|
+
const total = Number(row?.total ?? 0);
|
|
3237
|
+
const unresolved = Number(row?.unresolved ?? 0);
|
|
3238
|
+
const ratio = total === 0 ? 0 : Number((unresolved / total).toFixed(4));
|
|
3239
|
+
return {
|
|
3240
|
+
severity: ratio > 0.05 ? "warning" : "info",
|
|
3241
|
+
code: "strict_symbol_call_quality",
|
|
3242
|
+
message: "Terminal symbol-call quality aggregate",
|
|
3243
|
+
total,
|
|
3244
|
+
resolved: Number(row?.resolved ?? 0),
|
|
3245
|
+
unresolved,
|
|
3246
|
+
unresolvedRatio: ratio,
|
|
3247
|
+
unresolvedRatioThreshold: 0.05,
|
|
3248
|
+
topUnresolvedCallees: top
|
|
3249
|
+
};
|
|
3250
|
+
}
|
|
3251
|
+
|
|
1194
3252
|
// src/cli/doctor.ts
|
|
1195
3253
|
function doctorDiagnostics(db, strict, options = {}) {
|
|
1196
3254
|
const lifecycle = factLifecycleDiagnostic(db, options.workspaceId);
|
|
1197
|
-
if (lifecycle
|
|
1198
|
-
|
|
3255
|
+
if (lifecycle) return boundDoctorDiagnostics([lifecycle]);
|
|
3256
|
+
const globalLifecycle = options.workspaceId === void 0 ? void 0 : factLifecycleDiagnostic(db);
|
|
3257
|
+
if (globalLifecycle) return boundDoctorDiagnostics([
|
|
3258
|
+
...schemaDriftDiagnostics(db, strict, options.workspaceId),
|
|
3259
|
+
...analyzerVersionDiagnostics(db, strict, options.workspaceId),
|
|
3260
|
+
{
|
|
3261
|
+
severity: "warning",
|
|
3262
|
+
code: "workspace_detail_checks_deferred",
|
|
3263
|
+
message: "Selected-workspace lifecycle is valid, but JSON-dependent global detail checks were deferred because another workspace requires reindexing.",
|
|
3264
|
+
remediation: "Run doctor for the affected workspace after force index and link."
|
|
3265
|
+
}
|
|
3266
|
+
]);
|
|
1199
3267
|
const diagnostics = db.prepare("SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics ORDER BY id").all();
|
|
1200
3268
|
return boundDoctorDiagnostics([
|
|
1201
|
-
...lifecycle ? [lifecycle] : [],
|
|
1202
3269
|
...diagnostics,
|
|
3270
|
+
...packagePendingDiagnostics(db),
|
|
1203
3271
|
...healthDiagnostics(db, strict),
|
|
1204
3272
|
...remoteTargetWithoutImplementationDiagnostics(db, strict, Boolean(options.detail)),
|
|
1205
3273
|
...localServiceDiagnostics(db, strict),
|
|
@@ -1338,14 +3406,6 @@ function jsonEvidenceQuality(db) {
|
|
|
1338
3406
|
{ 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
3407
|
];
|
|
1340
3408
|
}
|
|
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
3409
|
function dbQueryQuality(db) {
|
|
1350
3410
|
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
3411
|
const total = Number(row.total ?? 0);
|
|
@@ -1561,8 +3621,12 @@ function serviceBindingQuality(db, detail) {
|
|
|
1561
3621
|
function bindingCategory(row) {
|
|
1562
3622
|
const evidence = parseObject(row.evidenceJson);
|
|
1563
3623
|
const resolution = parseObject(evidence.serviceBindingResolution);
|
|
3624
|
+
const reference = parseObject(evidence.serviceBindingReference);
|
|
1564
3625
|
if (resolution.status === "rejected_future_binding") return "direct_binding_missing";
|
|
1565
|
-
if (
|
|
3626
|
+
if (reference.reason === "binding_declared_after_call")
|
|
3627
|
+
return "direct_binding_missing";
|
|
3628
|
+
if (resolution.status === "ambiguous" || reference.status === "ambiguous" || reference.reason === "unsupported_reaching_assignment")
|
|
3629
|
+
return "ambiguous_binding_candidates";
|
|
1566
3630
|
const receiver = evidence.receiver;
|
|
1567
3631
|
const symbolEvidence = parseObject(row.symbolEvidenceJson);
|
|
1568
3632
|
if (symbolHasReceiverParameter(symbolEvidence, receiver))
|
|
@@ -1588,7 +3652,8 @@ function bindingExample(row) {
|
|
|
1588
3652
|
sourceLine: row.sourceLine,
|
|
1589
3653
|
receiver: evidence.receiver,
|
|
1590
3654
|
unresolvedReason: row.unresolvedReason,
|
|
1591
|
-
bindingResolution: evidence.serviceBindingResolution
|
|
3655
|
+
bindingResolution: evidence.serviceBindingResolution,
|
|
3656
|
+
bindingReference: evidence.serviceBindingReference
|
|
1592
3657
|
};
|
|
1593
3658
|
}
|
|
1594
3659
|
function bindingCategoryAction(category) {
|
|
@@ -1841,8 +3906,8 @@ function hintLines(evidence) {
|
|
|
1841
3906
|
}
|
|
1842
3907
|
function dynamicHintLines(evidence) {
|
|
1843
3908
|
const exploration = isRecord(evidence.dynamicTargetExploration) ? evidence.dynamicTargetExploration : evidence;
|
|
1844
|
-
const
|
|
1845
|
-
if (
|
|
3909
|
+
const count2 = numberValue(exploration.candidateCount);
|
|
3910
|
+
if (count2 === 0) return [];
|
|
1846
3911
|
const shown = numberValue(exploration.shownCandidateCount);
|
|
1847
3912
|
const omitted = numberValue(exploration.omittedCandidateCount);
|
|
1848
3913
|
const rejected = numberValue(exploration.rejectedCandidateCount);
|
|
@@ -1850,7 +3915,7 @@ function dynamicHintLines(evidence) {
|
|
|
1850
3915
|
`viable candidates: ${shown} shown, ${omitted} omitted; rejected: ${rejected}`
|
|
1851
3916
|
];
|
|
1852
3917
|
lines.push(...varSetHints(exploration.suggestedVarSets));
|
|
1853
|
-
if (omitted > 0 || rejected > 0 || shown <
|
|
3918
|
+
if (omitted > 0 || rejected > 0 || shown < count2)
|
|
1854
3919
|
lines.push("use --dynamic-mode candidates --max-dynamic-candidates 20 to explore candidate branches");
|
|
1855
3920
|
return lines;
|
|
1856
3921
|
}
|
|
@@ -1920,9 +3985,9 @@ function diagnosticLocation(diagnostic) {
|
|
|
1920
3985
|
}
|
|
1921
3986
|
function compactMessage(diagnostic) {
|
|
1922
3987
|
const message = String(diagnostic.message ?? "");
|
|
1923
|
-
const
|
|
3988
|
+
const count2 = typeof diagnostic.count === "number" ? ` count=${diagnostic.count}` : "";
|
|
1924
3989
|
const total = typeof diagnostic.total === "number" ? ` total=${diagnostic.total}` : "";
|
|
1925
|
-
return `${message}${
|
|
3990
|
+
return `${message}${count2}${total}`.trim();
|
|
1926
3991
|
}
|
|
1927
3992
|
function suggestedHintLines(diagnostic) {
|
|
1928
3993
|
const direct = cliHints(diagnostic.suggestedHints);
|
|
@@ -2096,6 +4161,16 @@ async function markCleanClaimFailed(dbPath, runId, error) {
|
|
|
2096
4161
|
}
|
|
2097
4162
|
}
|
|
2098
4163
|
|
|
4164
|
+
// src/cli/001-index-summary.ts
|
|
4165
|
+
function indexCommandOutcome(summary) {
|
|
4166
|
+
const failed = summary.failedCount > 0 ? `, failed ${summary.failedCount} (${summary.failedRepos.map(({ name, code }) => `${name}: ${code}`).join(", ")})` : "";
|
|
4167
|
+
return {
|
|
4168
|
+
stdout: `Indexed ${summary.indexedCount} repositories, skipped ${summary.skippedCount}${failed}, ${summary.fileCount} files, ${summary.diagnosticCount} diagnostics
|
|
4169
|
+
`,
|
|
4170
|
+
exitCode: summary.failedCount > 0 ? 1 : 0
|
|
4171
|
+
};
|
|
4172
|
+
}
|
|
4173
|
+
|
|
2099
4174
|
// src/cli.ts
|
|
2100
4175
|
var stdout = createStdoutWriter(process.stdout, fail);
|
|
2101
4176
|
var TRACE_FORMATS = ["table", "json", "mermaid", "compact-json"];
|
|
@@ -2243,26 +4318,30 @@ function runGraphCommand(opts) {
|
|
|
2243
4318
|
writeTraceOutput(db, start, options, opts.format);
|
|
2244
4319
|
});
|
|
2245
4320
|
}
|
|
2246
|
-
function
|
|
2247
|
-
|
|
2248
|
-
program.name("service-flow").description(
|
|
4321
|
+
function configuredProgram() {
|
|
4322
|
+
return new Command().name("service-flow").description(
|
|
2249
4323
|
"Trace SAP CAP service-to-service flows across multi-repository workspaces"
|
|
2250
4324
|
).version(VERSION);
|
|
4325
|
+
}
|
|
4326
|
+
function registerInitCommand(program) {
|
|
2251
4327
|
program.command("init").argument("<workspace>").option("--db <path>").option("--ignore <pattern...>").action(
|
|
2252
4328
|
(workspace, opts) => void init(workspace, opts).catch(fail)
|
|
2253
4329
|
);
|
|
4330
|
+
}
|
|
4331
|
+
function registerIndexCommand(program) {
|
|
2254
4332
|
program.command("index").option("--workspace <path>").option("--repo <name>").option("--force").action(
|
|
2255
4333
|
(opts) => void withWorkspace(opts.workspace, async (db, workspaceId) => {
|
|
2256
4334
|
const r = await indexWorkspace(db, workspaceId, {
|
|
2257
4335
|
repo: opts.repo,
|
|
2258
4336
|
force: Boolean(opts.force)
|
|
2259
4337
|
});
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
);
|
|
4338
|
+
const outcome = indexCommandOutcome(r);
|
|
4339
|
+
writeStdout(outcome.stdout);
|
|
4340
|
+
if (outcome.exitCode !== 0) process.exitCode = outcome.exitCode;
|
|
2264
4341
|
}).catch(fail)
|
|
2265
4342
|
);
|
|
4343
|
+
}
|
|
4344
|
+
function registerLinkCommand(program) {
|
|
2266
4345
|
program.command("link").option("--workspace <path>").option("--force").action(
|
|
2267
4346
|
(opts) => void withWorkspace(opts.workspace, (db, workspaceId) => {
|
|
2268
4347
|
const r = linkWorkspace(db, workspaceId);
|
|
@@ -2274,90 +4353,121 @@ function createProgram() {
|
|
|
2274
4353
|
);
|
|
2275
4354
|
}).catch(fail)
|
|
2276
4355
|
);
|
|
4356
|
+
}
|
|
4357
|
+
function registerTraceCommand(program) {
|
|
2277
4358
|
program.command("trace").option("--workspace <path>").option("--repo <name>").option("--operation <name>").option("--service <path>").option("--path <operationPath>").option("--handler <name>").option("--depth <n>", "trace depth", "25").addOption(traceFormatOption()).option("--include-external").option("--include-db").option("--include-async").option("--implementation-repo <name>").option("--implementation-hint <scope>", "scoped implementation hint", collect, []).option("--var <key=value>", "dynamic variable", collect, []).option("--dynamic-mode <mode>", "strict|candidates|infer", "strict").option("--max-dynamic-candidates <n>", "maximum dynamic candidates to show", "5").action((opts) => void runTraceCommand(opts).catch(fail));
|
|
4359
|
+
}
|
|
4360
|
+
function listRepositoriesCommand(opts) {
|
|
4361
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => writeStdout(
|
|
4362
|
+
renderJson(
|
|
4363
|
+
listRepositories(db, workspaceId).map((repo) => ({
|
|
4364
|
+
name: repo.name,
|
|
4365
|
+
kind: repo.kind,
|
|
4366
|
+
packageName: repo.package_name
|
|
4367
|
+
}))
|
|
4368
|
+
)
|
|
4369
|
+
));
|
|
4370
|
+
}
|
|
4371
|
+
function listServicesCommand(opts) {
|
|
4372
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
4373
|
+
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
4374
|
+
if (selection.diagnostic) {
|
|
4375
|
+
writeStdout(renderJson([selection.diagnostic]));
|
|
4376
|
+
return;
|
|
4377
|
+
}
|
|
4378
|
+
const repo = selection.repo;
|
|
4379
|
+
const rows = db.prepare(
|
|
4380
|
+
"SELECT r.name repo,s.service_path servicePath,s.qualified_name qualifiedName FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (? IS NULL OR s.repo_id=?) ORDER BY r.name,s.service_path"
|
|
4381
|
+
).all(workspaceId, repo?.id, repo?.id);
|
|
4382
|
+
writeStdout(renderJson(rows));
|
|
4383
|
+
});
|
|
4384
|
+
}
|
|
4385
|
+
function listOperationsCommand(opts) {
|
|
4386
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
4387
|
+
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
4388
|
+
if (selection.diagnostic) {
|
|
4389
|
+
writeStdout(renderJson([selection.diagnostic]));
|
|
4390
|
+
return;
|
|
4391
|
+
}
|
|
4392
|
+
const repo = selection.repo;
|
|
4393
|
+
const rows = db.prepare(
|
|
4394
|
+
"SELECT r.name repo,s.service_path servicePath,o.operation_name operation,o.operation_path path FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (? IS NULL OR s.repo_id=?) AND (? IS NULL OR s.service_path=?)"
|
|
4395
|
+
).all(
|
|
4396
|
+
workspaceId,
|
|
4397
|
+
repo?.id,
|
|
4398
|
+
repo?.id,
|
|
4399
|
+
opts.service,
|
|
4400
|
+
opts.service
|
|
4401
|
+
);
|
|
4402
|
+
writeStdout(renderJson(rows));
|
|
4403
|
+
});
|
|
4404
|
+
}
|
|
4405
|
+
function listCallsCommand(opts) {
|
|
4406
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
4407
|
+
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
4408
|
+
if (selection.diagnostic) {
|
|
4409
|
+
writeStdout(renderJson([selection.diagnostic]));
|
|
4410
|
+
return;
|
|
4411
|
+
}
|
|
4412
|
+
const repo = selection.repo;
|
|
4413
|
+
const rows = db.prepare(
|
|
4414
|
+
"SELECT r.name repo,c.call_type type,c.operation_path_expr path,c.source_file file,c.source_line line FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE r.workspace_id=? AND (? IS NULL OR c.repo_id=?) AND (? IS NULL OR c.operation_path_expr=? OR c.operation_path_expr=? OR c.payload_summary LIKE ?)"
|
|
4415
|
+
).all(
|
|
4416
|
+
workspaceId,
|
|
4417
|
+
repo?.id,
|
|
4418
|
+
repo?.id,
|
|
4419
|
+
opts.operation,
|
|
4420
|
+
opts.operation,
|
|
4421
|
+
opts.operation ? `/${opts.operation}` : void 0,
|
|
4422
|
+
opts.operation ? `%${opts.operation}%` : void 0
|
|
4423
|
+
);
|
|
4424
|
+
writeStdout(renderJson(rows));
|
|
4425
|
+
});
|
|
4426
|
+
}
|
|
4427
|
+
function registerListCommands(program) {
|
|
2278
4428
|
const list = program.command("list");
|
|
2279
4429
|
list.command("repos").option("--workspace <path>").action(
|
|
2280
|
-
(opts) => void
|
|
2281
|
-
opts.workspace,
|
|
2282
|
-
(db, workspaceId) => writeStdout(
|
|
2283
|
-
renderJson(
|
|
2284
|
-
listRepositories(db, workspaceId).map((r) => ({
|
|
2285
|
-
name: r.name,
|
|
2286
|
-
kind: r.kind,
|
|
2287
|
-
packageName: r.package_name
|
|
2288
|
-
}))
|
|
2289
|
-
)
|
|
2290
|
-
)
|
|
2291
|
-
).catch(fail)
|
|
4430
|
+
(opts) => void listRepositoriesCommand(opts).catch(fail)
|
|
2292
4431
|
);
|
|
2293
4432
|
list.command("services").option("--workspace <path>").option("--repo <name>").action(
|
|
2294
|
-
(opts) => void
|
|
2295
|
-
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
2296
|
-
if (selection.diagnostic) {
|
|
2297
|
-
writeStdout(renderJson([selection.diagnostic]));
|
|
2298
|
-
return;
|
|
2299
|
-
}
|
|
2300
|
-
const repo = selection.repo;
|
|
2301
|
-
const rows = db.prepare(
|
|
2302
|
-
"SELECT r.name repo,s.service_path servicePath,s.qualified_name qualifiedName FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (? IS NULL OR s.repo_id=?) ORDER BY r.name,s.service_path"
|
|
2303
|
-
).all(workspaceId, repo?.id, repo?.id);
|
|
2304
|
-
writeStdout(renderJson(rows));
|
|
2305
|
-
}).catch(fail)
|
|
4433
|
+
(opts) => void listServicesCommand(opts).catch(fail)
|
|
2306
4434
|
);
|
|
2307
4435
|
list.command("operations").option("--workspace <path>").option("--repo <name>").option("--service <path>").action(
|
|
2308
|
-
(opts) => void
|
|
2309
|
-
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
2310
|
-
if (selection.diagnostic) {
|
|
2311
|
-
writeStdout(renderJson([selection.diagnostic]));
|
|
2312
|
-
return;
|
|
2313
|
-
}
|
|
2314
|
-
const repo = selection.repo;
|
|
2315
|
-
const rows = db.prepare(
|
|
2316
|
-
"SELECT r.name repo,s.service_path servicePath,o.operation_name operation,o.operation_path path FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (? IS NULL OR s.repo_id=?) AND (? IS NULL OR s.service_path=?)"
|
|
2317
|
-
).all(workspaceId, repo?.id, repo?.id, opts.service, opts.service);
|
|
2318
|
-
writeStdout(renderJson(rows));
|
|
2319
|
-
}).catch(fail)
|
|
4436
|
+
(opts) => void listOperationsCommand(opts).catch(fail)
|
|
2320
4437
|
);
|
|
2321
4438
|
list.command("calls").option("--workspace <path>").option("--repo <name>").option("--operation <name>").action(
|
|
2322
|
-
(opts) => void
|
|
2323
|
-
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
2324
|
-
if (selection.diagnostic) {
|
|
2325
|
-
writeStdout(renderJson([selection.diagnostic]));
|
|
2326
|
-
return;
|
|
2327
|
-
}
|
|
2328
|
-
const repo = selection.repo;
|
|
2329
|
-
const rows = db.prepare(
|
|
2330
|
-
"SELECT r.name repo,c.call_type type,c.operation_path_expr path,c.source_file file,c.source_line line FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE r.workspace_id=? AND (? IS NULL OR c.repo_id=?) AND (? IS NULL OR c.operation_path_expr=? OR c.operation_path_expr=? OR c.payload_summary LIKE ?)"
|
|
2331
|
-
).all(
|
|
2332
|
-
workspaceId,
|
|
2333
|
-
repo?.id,
|
|
2334
|
-
repo?.id,
|
|
2335
|
-
opts.operation,
|
|
2336
|
-
opts.operation,
|
|
2337
|
-
opts.operation ? `/${opts.operation}` : void 0,
|
|
2338
|
-
opts.operation ? `%${opts.operation}%` : void 0
|
|
2339
|
-
);
|
|
2340
|
-
writeStdout(renderJson(rows));
|
|
2341
|
-
}).catch(fail)
|
|
4439
|
+
(opts) => void listCallsCommand(opts).catch(fail)
|
|
2342
4440
|
);
|
|
4441
|
+
}
|
|
4442
|
+
function registerGraphCommand(program) {
|
|
2343
4443
|
program.command("graph").option("--workspace <path>").option("--repo <name>").option("--operation <name>").option("--service <path>").option("--path <operationPath>").addOption(graphFormatOption()).option("--implementation-repo <name>").option("--implementation-hint <scope>", "scoped implementation hint", collect, []).option("--var <key=value>", "dynamic variable", collect, []).option("--dynamic-mode <mode>", "strict|candidates|infer", "strict").option("--max-dynamic-candidates <n>", "maximum dynamic candidates to show", "5").action((opts) => void runGraphCommand(opts).catch(fail));
|
|
4444
|
+
}
|
|
4445
|
+
function inspectRepositoryCommand(name, opts) {
|
|
4446
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
4447
|
+
const selection = selectRepository(db, name, workspaceId);
|
|
4448
|
+
writeStdout(renderJson(
|
|
4449
|
+
selection.repo ?? selection.diagnostic ?? { error: "repo not found" }
|
|
4450
|
+
));
|
|
4451
|
+
});
|
|
4452
|
+
}
|
|
4453
|
+
function inspectOperationCommand(selector, opts) {
|
|
4454
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
4455
|
+
const rows = db.prepare(
|
|
4456
|
+
"SELECT o.* FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (o.operation_name=? OR o.operation_path=?)"
|
|
4457
|
+
).all(workspaceId, selector, selector);
|
|
4458
|
+
writeStdout(renderJson(rows));
|
|
4459
|
+
});
|
|
4460
|
+
}
|
|
4461
|
+
function registerInspectCommands(program) {
|
|
2344
4462
|
const inspect = program.command("inspect");
|
|
2345
4463
|
inspect.command("repo").argument("<name>").option("--workspace <path>").action(
|
|
2346
|
-
(name, opts) => void
|
|
2347
|
-
const selection = selectRepository(db, name, workspaceId);
|
|
2348
|
-
writeStdout(renderJson(
|
|
2349
|
-
selection.repo ?? selection.diagnostic ?? { error: "repo not found" }
|
|
2350
|
-
));
|
|
2351
|
-
}).catch(fail)
|
|
4464
|
+
(name, opts) => void inspectRepositoryCommand(name, opts).catch(fail)
|
|
2352
4465
|
);
|
|
2353
4466
|
inspect.command("operation").argument("<selector>").option("--workspace <path>").action(
|
|
2354
|
-
(selector, opts) => void
|
|
2355
|
-
const rows = db.prepare(
|
|
2356
|
-
"SELECT o.* FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (o.operation_name=? OR o.operation_path=?)"
|
|
2357
|
-
).all(workspaceId, selector, selector);
|
|
2358
|
-
writeStdout(renderJson(rows));
|
|
2359
|
-
}).catch(fail)
|
|
4467
|
+
(selector, opts) => void inspectOperationCommand(selector, opts).catch(fail)
|
|
2360
4468
|
);
|
|
4469
|
+
}
|
|
4470
|
+
function registerDoctorCommand(program) {
|
|
2361
4471
|
program.command("doctor").option("--workspace <path>").option("--strict").option("--detail").option("--format <format>", "json|table").action(
|
|
2362
4472
|
(opts) => void withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
2363
4473
|
const allDiagnostics = doctorDiagnostics(db, Boolean(opts.strict), {
|
|
@@ -2367,6 +4477,8 @@ function createProgram() {
|
|
|
2367
4477
|
writeStdout(renderDoctorDiagnostics(allDiagnostics, opts.format));
|
|
2368
4478
|
}).catch(fail)
|
|
2369
4479
|
);
|
|
4480
|
+
}
|
|
4481
|
+
function registerCleanCommand(program) {
|
|
2370
4482
|
program.command("clean").option("--workspace <path>").option("--db-only").action(
|
|
2371
4483
|
(opts) => void (async () => {
|
|
2372
4484
|
const config = await loadWorkspaceConfig(opts.workspace);
|
|
@@ -2374,6 +4486,18 @@ function createProgram() {
|
|
|
2374
4486
|
writeStdout("Cleaned service-flow state\n");
|
|
2375
4487
|
})().catch(fail)
|
|
2376
4488
|
);
|
|
4489
|
+
}
|
|
4490
|
+
function createProgram() {
|
|
4491
|
+
const program = configuredProgram();
|
|
4492
|
+
registerInitCommand(program);
|
|
4493
|
+
registerIndexCommand(program);
|
|
4494
|
+
registerLinkCommand(program);
|
|
4495
|
+
registerTraceCommand(program);
|
|
4496
|
+
registerListCommands(program);
|
|
4497
|
+
registerGraphCommand(program);
|
|
4498
|
+
registerInspectCommands(program);
|
|
4499
|
+
registerDoctorCommand(program);
|
|
4500
|
+
registerCleanCommand(program);
|
|
2377
4501
|
return program;
|
|
2378
4502
|
}
|
|
2379
4503
|
function collect(value, previous) {
|