@saptools/service-flow 0.1.7 → 0.1.9
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 +15 -7
- package/README.md +9 -2
- package/TECHNICAL-NOTE.md +17 -2
- package/dist/{chunk-YFT57U54.js → chunk-HQMAFSRR.js} +371 -87
- package/dist/chunk-HQMAFSRR.js.map +1 -0
- package/dist/cli.js +28 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -1
- package/dist/chunk-YFT57U54.js.map +0 -1
|
@@ -328,45 +328,176 @@ async function parseDecorators(repoPath, filePath) {
|
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
// src/parsers/handler-registration-parser.ts
|
|
331
|
+
import fsSync from "fs";
|
|
331
332
|
import fs5 from "fs/promises";
|
|
332
333
|
import path6 from "path";
|
|
333
|
-
|
|
334
|
-
|
|
334
|
+
import ts3 from "typescript";
|
|
335
|
+
var MAX_EXPORT_DEPTH = 5;
|
|
336
|
+
function lineOf2(sourceFile, node) {
|
|
337
|
+
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
338
|
+
}
|
|
339
|
+
function isRelative(source) {
|
|
340
|
+
return source.startsWith("./") || source.startsWith("../");
|
|
341
|
+
}
|
|
342
|
+
function sourceText(node, sourceFile) {
|
|
343
|
+
if (ts3.isIdentifier(node) || ts3.isStringLiteral(node) || ts3.isNumericLiteral(node)) return node.text;
|
|
344
|
+
return node.getText(sourceFile);
|
|
345
|
+
}
|
|
346
|
+
function importSourceFor(identifier, imports) {
|
|
347
|
+
const evidence = imports.get(identifier);
|
|
348
|
+
return evidence ? `${evidence.source}#${evidence.importedName}` : void 0;
|
|
335
349
|
}
|
|
336
350
|
async function parseHandlerRegistrations(repoPath, filePath) {
|
|
337
|
-
const
|
|
351
|
+
const absolutePath = path6.join(repoPath, filePath);
|
|
352
|
+
const text = await fs5.readFile(absolutePath, "utf8");
|
|
353
|
+
const sourceFile = ts3.createSourceFile(filePath, text, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
|
|
354
|
+
const imports = collectImports(sourceFile);
|
|
355
|
+
const localArrays = collectLocalArrays(sourceFile, imports, /* @__PURE__ */ new Map(), repoPath, filePath);
|
|
338
356
|
const out = [];
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
357
|
+
function emitFromExpression(expression, call) {
|
|
358
|
+
const classes = resolveArrayExpression(expression, localArrays, imports, repoPath, filePath, /* @__PURE__ */ new Set());
|
|
359
|
+
for (const cls of classes) {
|
|
360
|
+
out.push({
|
|
361
|
+
className: cls.className,
|
|
362
|
+
importSource: cls.importSource,
|
|
363
|
+
registrationFile: normalizePath(filePath),
|
|
364
|
+
registrationLine: lineOf2(sourceFile, call),
|
|
365
|
+
registrationKind: "combined-handler-class",
|
|
366
|
+
confidence: 0.95
|
|
367
|
+
});
|
|
345
368
|
}
|
|
346
|
-
|
|
347
|
-
for (const m of text.matchAll(
|
|
348
|
-
/createCombinedHandler\s*\(|srv\.prepend\s*\(|cds\.serve\s*\(/g
|
|
349
|
-
))
|
|
350
|
-
out.push({
|
|
351
|
-
registrationFile: normalizePath(filePath),
|
|
352
|
-
registrationLine: lineOf2(text, m.index ?? 0),
|
|
353
|
-
registrationKind: m[0].startsWith("cds") ? "cds.serve" : "combined-handler",
|
|
354
|
-
confidence: 0.8
|
|
355
|
-
});
|
|
356
|
-
for (const m of text.matchAll(
|
|
357
|
-
/(?:const|export\s+const)\s+handlers\s*=\s*\[([\s\S]*?)\]/g
|
|
358
|
-
))
|
|
359
|
-
for (const c of (m[1] ?? "").matchAll(/\b(\w+Handler)\b/g))
|
|
369
|
+
if (classes.length === 0) {
|
|
360
370
|
out.push({
|
|
361
|
-
className: c[1],
|
|
362
|
-
importSource: imports.get(c[1]),
|
|
363
371
|
registrationFile: normalizePath(filePath),
|
|
364
|
-
registrationLine: lineOf2(
|
|
365
|
-
registrationKind: "handler
|
|
366
|
-
confidence: 0.
|
|
372
|
+
registrationLine: lineOf2(sourceFile, call),
|
|
373
|
+
registrationKind: "combined-handler",
|
|
374
|
+
confidence: 0.75
|
|
367
375
|
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
function visit(node) {
|
|
379
|
+
if (ts3.isCallExpression(node) && isRegistrationCall(node)) {
|
|
380
|
+
const handlerExpr = handlerExpression(node, sourceFile);
|
|
381
|
+
if (handlerExpr) emitFromExpression(handlerExpr, node);
|
|
382
|
+
else out.push({ registrationFile: normalizePath(filePath), registrationLine: lineOf2(sourceFile, node), registrationKind: "combined-handler", confidence: 0.75 });
|
|
383
|
+
}
|
|
384
|
+
ts3.forEachChild(node, visit);
|
|
385
|
+
}
|
|
386
|
+
visit(sourceFile);
|
|
368
387
|
return out;
|
|
369
388
|
}
|
|
389
|
+
function isRegistrationCall(call) {
|
|
390
|
+
const text = call.expression.getText();
|
|
391
|
+
return text.endsWith("createCombinedHandler") || text.endsWith("srv.prepend") || text.endsWith("cds.serve");
|
|
392
|
+
}
|
|
393
|
+
function handlerExpression(call, sourceFile) {
|
|
394
|
+
for (const arg of call.arguments) {
|
|
395
|
+
if (!ts3.isObjectLiteralExpression(arg)) continue;
|
|
396
|
+
for (const prop of arg.properties) {
|
|
397
|
+
if (!ts3.isPropertyAssignment(prop)) continue;
|
|
398
|
+
if (sourceText(prop.name, sourceFile) === "handler") return prop.initializer;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return void 0;
|
|
402
|
+
}
|
|
403
|
+
function collectImports(sourceFile) {
|
|
404
|
+
const imports = /* @__PURE__ */ new Map();
|
|
405
|
+
for (const statement of sourceFile.statements) {
|
|
406
|
+
if (!ts3.isImportDeclaration(statement) || !ts3.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
407
|
+
const source = statement.moduleSpecifier.text;
|
|
408
|
+
const clause = statement.importClause;
|
|
409
|
+
if (!clause) continue;
|
|
410
|
+
if (clause.name) imports.set(clause.name.text, { importedName: "default", source });
|
|
411
|
+
const named = clause.namedBindings;
|
|
412
|
+
if (named && ts3.isNamedImports(named)) {
|
|
413
|
+
for (const element of named.elements) imports.set(element.name.text, { importedName: element.propertyName?.text ?? element.name.text, source });
|
|
414
|
+
}
|
|
415
|
+
if (named && ts3.isNamespaceImport(named)) imports.set(named.name.text, { importedName: "*", source });
|
|
416
|
+
}
|
|
417
|
+
return imports;
|
|
418
|
+
}
|
|
419
|
+
function collectLocalArrays(sourceFile, imports, seed, repoPath = "", fromFile = "") {
|
|
420
|
+
const arrays = new Map(seed);
|
|
421
|
+
for (const statement of sourceFile.statements) {
|
|
422
|
+
if (ts3.isVariableStatement(statement)) {
|
|
423
|
+
for (const decl of statement.declarationList.declarations) {
|
|
424
|
+
if (ts3.isIdentifier(decl.name) && decl.initializer && ts3.isArrayLiteralExpression(decl.initializer)) {
|
|
425
|
+
arrays.set(decl.name.text, resolveArrayLiteral(decl.initializer, arrays, imports, repoPath, fromFile, /* @__PURE__ */ new Set()));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return arrays;
|
|
431
|
+
}
|
|
432
|
+
function resolveArrayExpression(expr, arrays, imports, repoPath, fromFile, seen) {
|
|
433
|
+
if (ts3.isArrayLiteralExpression(expr)) return resolveArrayLiteral(expr, arrays, imports, repoPath, fromFile, seen);
|
|
434
|
+
if (ts3.isIdentifier(expr)) {
|
|
435
|
+
const local = arrays.get(expr.text);
|
|
436
|
+
if (local) return local;
|
|
437
|
+
const evidence = imports.get(expr.text);
|
|
438
|
+
if (evidence && isRelative(evidence.source)) return resolveImportedArray(repoPath, fromFile, evidence, seen);
|
|
439
|
+
if (evidence) return [{ className: evidence.importedName === "default" ? expr.text : evidence.importedName, importSource: `${evidence.source}#${evidence.importedName}` }];
|
|
440
|
+
}
|
|
441
|
+
return [];
|
|
442
|
+
}
|
|
443
|
+
function resolveArrayLiteral(array, arrays, imports, repoPath, fromFile, seen) {
|
|
444
|
+
const out = [];
|
|
445
|
+
for (const element of array.elements) {
|
|
446
|
+
if (ts3.isSpreadElement(element)) out.push(...resolveArrayExpression(element.expression, arrays, imports, repoPath, fromFile, seen));
|
|
447
|
+
else if (ts3.isIdentifier(element)) out.push({ className: element.text, importSource: importSourceFor(element.text, imports) });
|
|
448
|
+
}
|
|
449
|
+
return out;
|
|
450
|
+
}
|
|
451
|
+
function resolveImportedArray(repoPath, fromFile, evidence, seen) {
|
|
452
|
+
const moduleFile = resolveRelativeModule(repoPath, fromFile, evidence.source);
|
|
453
|
+
if (!moduleFile) return [];
|
|
454
|
+
const key = `${moduleFile}:${evidence.importedName}`;
|
|
455
|
+
if (seen.has(key) || seen.size > MAX_EXPORT_DEPTH) return [];
|
|
456
|
+
seen.add(key);
|
|
457
|
+
const exports = readExports(repoPath, moduleFile, seen);
|
|
458
|
+
if (evidence.importedName === "default") return exports.defaultArray ?? [];
|
|
459
|
+
return exports.arrays.get(evidence.importedName) ?? exports.arrays.get(exports.aliases.get(evidence.importedName) ?? evidence.importedName) ?? [];
|
|
460
|
+
}
|
|
461
|
+
function resolveRelativeModule(repoPath, fromFile, specifier) {
|
|
462
|
+
const base = path6.resolve(repoPath, path6.dirname(fromFile), specifier);
|
|
463
|
+
for (const candidate of [base, `${base}.ts`, `${base}.js`, path6.join(base, "index.ts"), path6.join(base, "index.js")]) {
|
|
464
|
+
try {
|
|
465
|
+
const stat = fsSync.statSync(candidate);
|
|
466
|
+
if (stat.isFile()) return normalizePath(path6.relative(repoPath, candidate));
|
|
467
|
+
} catch {
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return void 0;
|
|
471
|
+
}
|
|
472
|
+
function readExports(repoPath, filePath, seen) {
|
|
473
|
+
const absolute = path6.join(repoPath, filePath);
|
|
474
|
+
let text;
|
|
475
|
+
try {
|
|
476
|
+
text = fsSync.readFileSync(absolute, "utf8");
|
|
477
|
+
} catch {
|
|
478
|
+
return { arrays: /* @__PURE__ */ new Map(), aliases: /* @__PURE__ */ new Map() };
|
|
479
|
+
}
|
|
480
|
+
const sourceFile = ts3.createSourceFile(filePath, text, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
|
|
481
|
+
const imports = collectImports(sourceFile);
|
|
482
|
+
const arrays = collectLocalArrays(sourceFile, imports, /* @__PURE__ */ new Map(), repoPath, filePath);
|
|
483
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
484
|
+
let defaultArray;
|
|
485
|
+
for (const statement of sourceFile.statements) {
|
|
486
|
+
if (ts3.isExportAssignment(statement) && ts3.isIdentifier(statement.expression)) defaultArray = arrays.get(statement.expression.text);
|
|
487
|
+
if (ts3.isExportDeclaration(statement) && statement.exportClause && ts3.isNamedExports(statement.exportClause)) {
|
|
488
|
+
const module = statement.moduleSpecifier && ts3.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : void 0;
|
|
489
|
+
for (const element of statement.exportClause.elements) {
|
|
490
|
+
const local = element.propertyName?.text ?? element.name.text;
|
|
491
|
+
aliases.set(element.name.text, local);
|
|
492
|
+
if (module && isRelative(module)) {
|
|
493
|
+
const imported = resolveImportedArray(repoPath, filePath, { source: module, importedName: local }, seen);
|
|
494
|
+
if (imported.length > 0) arrays.set(element.name.text, imported);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return { arrays, defaultArray, aliases };
|
|
500
|
+
}
|
|
370
501
|
|
|
371
502
|
// src/utils/redaction.ts
|
|
372
503
|
var SENSITIVE = /authorization|cookie|token|secret|password|key|credential/i;
|
|
@@ -513,15 +644,15 @@ async function parseOutboundCalls(repoPath, filePath) {
|
|
|
513
644
|
// src/parsers/service-binding-parser.ts
|
|
514
645
|
import fs7 from "fs/promises";
|
|
515
646
|
import path8 from "path";
|
|
516
|
-
import
|
|
647
|
+
import ts4 from "typescript";
|
|
517
648
|
function lineOf4(sf, node) {
|
|
518
649
|
return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
519
650
|
}
|
|
520
651
|
function stringValue(node) {
|
|
521
652
|
if (!node) return void 0;
|
|
522
|
-
if (
|
|
653
|
+
if (ts4.isStringLiteralLike(node) || ts4.isNoSubstitutionTemplateLiteral(node))
|
|
523
654
|
return node.text;
|
|
524
|
-
if (
|
|
655
|
+
if (ts4.isTemplateExpression(node))
|
|
525
656
|
return node.getText().replace(/^`|`$/g, "");
|
|
526
657
|
return node.getText();
|
|
527
658
|
}
|
|
@@ -530,22 +661,22 @@ function placeholders(value) {
|
|
|
530
661
|
}
|
|
531
662
|
function connectFactFromCall(call) {
|
|
532
663
|
const expr = call.expression;
|
|
533
|
-
if (!
|
|
664
|
+
if (!ts4.isPropertyAccessExpression(expr) || expr.name.text !== "to")
|
|
534
665
|
return void 0;
|
|
535
666
|
const inner = expr.expression;
|
|
536
|
-
if (!
|
|
667
|
+
if (!ts4.isPropertyAccessExpression(inner) || inner.name.text !== "connect" || inner.expression.getText() !== "cds")
|
|
537
668
|
return void 0;
|
|
538
669
|
const first = call.arguments[0];
|
|
539
670
|
if (!first) return void 0;
|
|
540
671
|
const second = call.arguments[1];
|
|
541
|
-
const objectArg =
|
|
672
|
+
const objectArg = ts4.isObjectLiteralExpression(first) ? first : second && ts4.isObjectLiteralExpression(second) ? second : void 0;
|
|
542
673
|
let alias;
|
|
543
674
|
let aliasExpr;
|
|
544
|
-
if (
|
|
675
|
+
if (ts4.isStringLiteralLike(first) || ts4.isNoSubstitutionTemplateLiteral(first))
|
|
545
676
|
alias = first.text;
|
|
546
|
-
else if (!
|
|
677
|
+
else if (!ts4.isObjectLiteralExpression(first))
|
|
547
678
|
aliasExpr = stringValue(first);
|
|
548
|
-
if ((
|
|
679
|
+
if ((ts4.isStringLiteralLike(first) || ts4.isNoSubstitutionTemplateLiteral(first)) && !objectArg)
|
|
549
680
|
return { alias: first.text, isDynamic: false, placeholders: [] };
|
|
550
681
|
if (!objectArg && aliasExpr)
|
|
551
682
|
return {
|
|
@@ -557,13 +688,13 @@ function connectFactFromCall(call) {
|
|
|
557
688
|
let servicePathExpr;
|
|
558
689
|
function visitObject(obj) {
|
|
559
690
|
for (const prop of obj.properties) {
|
|
560
|
-
if (!
|
|
561
|
-
const name =
|
|
691
|
+
if (!ts4.isPropertyAssignment(prop)) continue;
|
|
692
|
+
const name = ts4.isIdentifier(prop.name) || ts4.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
|
|
562
693
|
if (name === "destination")
|
|
563
694
|
destinationExpr = stringValue(prop.initializer);
|
|
564
695
|
if (name === "path" || name === "servicePath")
|
|
565
696
|
servicePathExpr = stringValue(prop.initializer);
|
|
566
|
-
if (
|
|
697
|
+
if (ts4.isObjectLiteralExpression(prop.initializer))
|
|
567
698
|
visitObject(prop.initializer);
|
|
568
699
|
}
|
|
569
700
|
}
|
|
@@ -583,8 +714,8 @@ function connectFactFromCall(call) {
|
|
|
583
714
|
};
|
|
584
715
|
}
|
|
585
716
|
function unwrapCall(expr) {
|
|
586
|
-
if (
|
|
587
|
-
if (
|
|
717
|
+
if (ts4.isAwaitExpression(expr)) return unwrapCall(expr.expression);
|
|
718
|
+
if (ts4.isCallExpression(expr)) return expr;
|
|
588
719
|
return void 0;
|
|
589
720
|
}
|
|
590
721
|
function findConnectInExpression(expr) {
|
|
@@ -596,8 +727,8 @@ function findConnectInExpression(expr) {
|
|
|
596
727
|
let found;
|
|
597
728
|
function visit(node) {
|
|
598
729
|
if (found) return;
|
|
599
|
-
if (
|
|
600
|
-
if (!found)
|
|
730
|
+
if (ts4.isCallExpression(node)) found = connectFactFromCall(node);
|
|
731
|
+
if (!found) ts4.forEachChild(node, visit);
|
|
601
732
|
}
|
|
602
733
|
visit(expr);
|
|
603
734
|
return found;
|
|
@@ -605,12 +736,12 @@ function findConnectInExpression(expr) {
|
|
|
605
736
|
async function readSource(abs) {
|
|
606
737
|
try {
|
|
607
738
|
const text = await fs7.readFile(abs, "utf8");
|
|
608
|
-
return
|
|
739
|
+
return ts4.createSourceFile(
|
|
609
740
|
abs,
|
|
610
741
|
text,
|
|
611
|
-
|
|
742
|
+
ts4.ScriptTarget.Latest,
|
|
612
743
|
true,
|
|
613
|
-
|
|
744
|
+
ts4.ScriptKind.TS
|
|
614
745
|
);
|
|
615
746
|
} catch {
|
|
616
747
|
return void 0;
|
|
@@ -639,7 +770,7 @@ async function resolveImport(repoPath, fromFile, spec) {
|
|
|
639
770
|
async function importsFor(repoPath, filePath, sf) {
|
|
640
771
|
const imports = [];
|
|
641
772
|
for (const stmt of sf.statements) {
|
|
642
|
-
if (!
|
|
773
|
+
if (!ts4.isImportDeclaration(stmt) || !ts4.isStringLiteralLike(stmt.moduleSpecifier))
|
|
643
774
|
continue;
|
|
644
775
|
const sourceFile = await resolveImport(
|
|
645
776
|
repoPath,
|
|
@@ -655,7 +786,7 @@ async function importsFor(repoPath, filePath, sf) {
|
|
|
655
786
|
sourceFile
|
|
656
787
|
});
|
|
657
788
|
const bindings = clause.namedBindings;
|
|
658
|
-
if (bindings &&
|
|
789
|
+
if (bindings && ts4.isNamedImports(bindings))
|
|
659
790
|
for (const el of bindings.elements)
|
|
660
791
|
imports.push({
|
|
661
792
|
localName: el.name.text,
|
|
@@ -668,17 +799,17 @@ async function importsFor(repoPath, filePath, sf) {
|
|
|
668
799
|
function exportedLocalNames(sf) {
|
|
669
800
|
const exports = /* @__PURE__ */ new Map();
|
|
670
801
|
for (const stmt of sf.statements) {
|
|
671
|
-
const direct =
|
|
672
|
-
(m) => m.kind ===
|
|
802
|
+
const direct = ts4.canHaveModifiers(stmt) ? ts4.getModifiers(stmt)?.some(
|
|
803
|
+
(m) => m.kind === ts4.SyntaxKind.ExportKeyword
|
|
673
804
|
) ?? false : false;
|
|
674
|
-
if (direct &&
|
|
805
|
+
if (direct && ts4.isFunctionDeclaration(stmt) && stmt.name)
|
|
675
806
|
exports.set(stmt.name.text, stmt.name.text);
|
|
676
|
-
if (direct &&
|
|
807
|
+
if (direct && ts4.isVariableStatement(stmt)) {
|
|
677
808
|
for (const decl of stmt.declarationList.declarations)
|
|
678
|
-
if (
|
|
809
|
+
if (ts4.isIdentifier(decl.name)) exports.set(decl.name.text, decl.name.text);
|
|
679
810
|
}
|
|
680
|
-
if (!
|
|
681
|
-
if (!
|
|
811
|
+
if (!ts4.isExportDeclaration(stmt) || !stmt.exportClause) continue;
|
|
812
|
+
if (!ts4.isNamedExports(stmt.exportClause)) continue;
|
|
682
813
|
for (const el of stmt.exportClause.elements)
|
|
683
814
|
exports.set(el.name.text, el.propertyName?.text ?? el.name.text);
|
|
684
815
|
}
|
|
@@ -692,18 +823,18 @@ async function helperBindings(repoPath, filePath) {
|
|
|
692
823
|
const exportedLocals = exportedLocalNames(sf);
|
|
693
824
|
const factsByLocal = /* @__PURE__ */ new Map();
|
|
694
825
|
for (const stmt of sf.statements) {
|
|
695
|
-
if (
|
|
826
|
+
if (ts4.isFunctionDeclaration(stmt) && stmt.name) {
|
|
696
827
|
let fact;
|
|
697
828
|
stmt.forEachChild(function visit(node) {
|
|
698
|
-
if (!fact &&
|
|
829
|
+
if (!fact && ts4.isReturnStatement(node) && node.expression)
|
|
699
830
|
fact = findConnectInExpression(node.expression);
|
|
700
|
-
if (!fact)
|
|
831
|
+
if (!fact) ts4.forEachChild(node, visit);
|
|
701
832
|
});
|
|
702
833
|
if (fact) factsByLocal.set(stmt.name.text, { ...fact, sourceLine: lineOf4(sf, stmt) });
|
|
703
834
|
}
|
|
704
|
-
if (
|
|
835
|
+
if (ts4.isVariableStatement(stmt))
|
|
705
836
|
for (const decl of stmt.declarationList.declarations) {
|
|
706
|
-
if (!
|
|
837
|
+
if (!ts4.isIdentifier(decl.name) || !decl.initializer) continue;
|
|
707
838
|
const fact = findConnectInExpression(decl.initializer);
|
|
708
839
|
if (fact)
|
|
709
840
|
factsByLocal.set(decl.name.text, {
|
|
@@ -744,7 +875,7 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
744
875
|
return helper ? { imp, helper } : void 0;
|
|
745
876
|
}
|
|
746
877
|
async function recordVariable(decl) {
|
|
747
|
-
if (!
|
|
878
|
+
if (!ts4.isIdentifier(decl.name) || !decl.initializer) return;
|
|
748
879
|
const call = unwrapCall(decl.initializer);
|
|
749
880
|
if (!call) return;
|
|
750
881
|
const direct = connectFactFromCall(call);
|
|
@@ -755,7 +886,7 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
755
886
|
sourceFile: normalizePath(filePath),
|
|
756
887
|
sourceLine: lineOf4(sourceFileAst, decl)
|
|
757
888
|
});
|
|
758
|
-
else if (
|
|
889
|
+
else if (ts4.isIdentifier(call.expression)) {
|
|
759
890
|
const resolved = await importedHelper(call.expression.text);
|
|
760
891
|
if (resolved)
|
|
761
892
|
out.push({
|
|
@@ -782,14 +913,14 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
782
913
|
}
|
|
783
914
|
}
|
|
784
915
|
function recordDestructuredClassHelper(decl) {
|
|
785
|
-
if (!
|
|
916
|
+
if (!ts4.isObjectBindingPattern(decl.name) || !decl.initializer) return;
|
|
786
917
|
const call = unwrapCall(decl.initializer);
|
|
787
|
-
if (!call || !
|
|
918
|
+
if (!call || !ts4.isPropertyAccessExpression(call.expression)) return;
|
|
788
919
|
const target = call.expression;
|
|
789
|
-
if (target.expression.kind !==
|
|
920
|
+
if (target.expression.kind !== ts4.SyntaxKind.ThisKeyword) return;
|
|
790
921
|
for (const el of decl.name.elements) {
|
|
791
|
-
if (!
|
|
792
|
-
const propertyName = el.propertyName &&
|
|
922
|
+
if (!ts4.isIdentifier(el.name)) continue;
|
|
923
|
+
const propertyName = el.propertyName && ts4.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
|
|
793
924
|
const helper = classHelpers.find(
|
|
794
925
|
(h) => h.helperName === target.name.text && h.propertyName === propertyName
|
|
795
926
|
);
|
|
@@ -815,8 +946,8 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
815
946
|
}
|
|
816
947
|
const declarations = [];
|
|
817
948
|
function collectDeclarations(node) {
|
|
818
|
-
if (
|
|
819
|
-
|
|
949
|
+
if (ts4.isVariableDeclaration(node)) declarations.push(node);
|
|
950
|
+
ts4.forEachChild(node, collectDeclarations);
|
|
820
951
|
}
|
|
821
952
|
collectDeclarations(sourceFileAst);
|
|
822
953
|
for (const decl of declarations) {
|
|
@@ -828,16 +959,16 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
828
959
|
function collectClassHelpers(sf) {
|
|
829
960
|
const helpers = [];
|
|
830
961
|
for (const stmt of sf.statements) {
|
|
831
|
-
if (!
|
|
962
|
+
if (!ts4.isClassDeclaration(stmt) || !stmt.name) continue;
|
|
832
963
|
for (const member of stmt.members) {
|
|
833
964
|
let visit2 = function(node) {
|
|
834
|
-
if (
|
|
965
|
+
if (ts4.isVariableDeclaration(node) && ts4.isIdentifier(node.name) && node.initializer) {
|
|
835
966
|
const fact = findConnectInExpression(node.initializer);
|
|
836
967
|
if (fact) bindings.set(node.name.text, fact);
|
|
837
968
|
}
|
|
838
|
-
if (
|
|
969
|
+
if (ts4.isReturnStatement(node) && node.expression && ts4.isObjectLiteralExpression(node.expression)) {
|
|
839
970
|
for (const prop of node.expression.properties) {
|
|
840
|
-
if (
|
|
971
|
+
if (ts4.isShorthandPropertyAssignment(prop)) {
|
|
841
972
|
const fact = bindings.get(prop.name.text);
|
|
842
973
|
if (fact)
|
|
843
974
|
helpers.push({
|
|
@@ -849,8 +980,8 @@ function collectClassHelpers(sf) {
|
|
|
849
980
|
sourceLine: lineOf4(sf, prop)
|
|
850
981
|
});
|
|
851
982
|
}
|
|
852
|
-
if (
|
|
853
|
-
const propertyName =
|
|
983
|
+
if (ts4.isPropertyAssignment(prop) && ts4.isIdentifier(prop.initializer)) {
|
|
984
|
+
const propertyName = ts4.isIdentifier(prop.name) || ts4.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
|
|
854
985
|
const fact = propertyName ? bindings.get(prop.initializer.text) : void 0;
|
|
855
986
|
if (propertyName && fact)
|
|
856
987
|
helpers.push({
|
|
@@ -864,15 +995,15 @@ function collectClassHelpers(sf) {
|
|
|
864
995
|
}
|
|
865
996
|
}
|
|
866
997
|
}
|
|
867
|
-
|
|
998
|
+
ts4.forEachChild(node, visit2);
|
|
868
999
|
};
|
|
869
1000
|
var visit = visit2;
|
|
870
|
-
if (!
|
|
871
|
-
if (!
|
|
1001
|
+
if (!ts4.isPropertyDeclaration(member) || !member.initializer) continue;
|
|
1002
|
+
if (!ts4.isIdentifier(member.name)) continue;
|
|
872
1003
|
const className = stmt.name.text;
|
|
873
1004
|
const helperName = member.name.text;
|
|
874
1005
|
const initializer = member.initializer;
|
|
875
|
-
if (!
|
|
1006
|
+
if (!ts4.isArrowFunction(initializer) && !ts4.isFunctionExpression(initializer))
|
|
876
1007
|
continue;
|
|
877
1008
|
const bindings = /* @__PURE__ */ new Map();
|
|
878
1009
|
visit2(initializer);
|
|
@@ -1094,23 +1225,176 @@ function callEvidence(call, resolution, servicePath, op, destination) {
|
|
|
1094
1225
|
return { sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, repo: call.repoName, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination, servicePath, operationPath: op, targetRepo: resolution.target?.repoName, targetOperation: resolution.target?.operationName, helperChain: call.helperChainJson ? JSON.parse(String(call.helperChainJson)) : void 0, candidates: resolution.candidates, candidateCount: resolution.candidates.length, resolutionStatus: resolution.status, resolutionReasons: resolution.reasons, analysisCompleteness: call.unresolved_reason ? "partial" : "complete", parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0 };
|
|
1095
1226
|
}
|
|
1096
1227
|
function linkImplementations(db, workspaceId, generation) {
|
|
1097
|
-
const operations = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,s.service_path servicePath,s.repo_id modelRepoId,r.package_name modelPackage 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=?`).all(workspaceId);
|
|
1228
|
+
const operations = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,r.package_name modelPackage 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=?`).all(workspaceId);
|
|
1098
1229
|
let edgeCount = 0;
|
|
1099
1230
|
let resolvedCount = 0;
|
|
1100
1231
|
let ambiguousCount = 0;
|
|
1101
1232
|
for (const operation of operations) {
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1233
|
+
const candidates = rankedImplementationCandidates(db, workspaceId, operation);
|
|
1234
|
+
const accepted = candidates.filter((candidate) => candidate.accepted);
|
|
1235
|
+
if (accepted.length === 0) continue;
|
|
1236
|
+
const topScore = accepted[0]?.score ?? 0;
|
|
1237
|
+
const winners = accepted.filter((candidate) => candidate.score === topScore);
|
|
1238
|
+
const unique = winners.length === 1 ? winners[0] : void 0;
|
|
1239
|
+
const evidence = {
|
|
1240
|
+
servicePath: operation.servicePath,
|
|
1241
|
+
operationPath: operation.operationPath,
|
|
1242
|
+
operationName: operation.operationName,
|
|
1243
|
+
modelPackage: { id: operation.modelRepoId, name: operation.modelRepo, packageName: operation.modelPackage },
|
|
1244
|
+
candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
|
|
1245
|
+
};
|
|
1246
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", unique ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique ? "handler_method" : "handler_method_candidates", unique ? graphId(unique.methodId) : winners.map((row) => graphId(row.methodId)).join(","), unique ? 0.95 : 0.5, JSON.stringify(evidence), 0, unique ? null : "Ambiguous registered handler implementation candidates", generation);
|
|
1106
1247
|
edgeCount += 1;
|
|
1107
1248
|
if (unique) resolvedCount += 1;
|
|
1108
1249
|
else ambiguousCount += 1;
|
|
1109
1250
|
}
|
|
1110
1251
|
return { edgeCount, resolvedCount, ambiguousCount };
|
|
1111
1252
|
}
|
|
1253
|
+
function rankedImplementationCandidates(db, workspaceId, operation) {
|
|
1254
|
+
const rows2 = implementationCandidates(db, workspaceId, operation);
|
|
1255
|
+
return rows2.map((row) => scoreImplementationCandidate(row, operation)).sort((a, b) => b.score - a.score || String(a.className).localeCompare(String(b.className)) || a.methodId - b.methodId);
|
|
1256
|
+
}
|
|
1112
1257
|
function implementationCandidates(db, workspaceId, operation) {
|
|
1113
|
-
|
|
1258
|
+
const modelRepoGraphId = graphId(operation.modelRepoId);
|
|
1259
|
+
return db.prepare(`SELECT DISTINCT
|
|
1260
|
+
hm.id methodId,
|
|
1261
|
+
hc.id classId,
|
|
1262
|
+
hc.class_name className,
|
|
1263
|
+
hc.source_file sourceFile,
|
|
1264
|
+
hc.source_line sourceLine,
|
|
1265
|
+
hr.repo_id applicationRepoId,
|
|
1266
|
+
hr.registration_file registrationFile,
|
|
1267
|
+
hr.registration_line registrationLine,
|
|
1268
|
+
hr.registration_kind registrationKind,
|
|
1269
|
+
hr.import_source importSource,
|
|
1270
|
+
handlerRepo.id handlerRepoId,
|
|
1271
|
+
handlerRepo.name handlerRepo,
|
|
1272
|
+
handlerRepo.package_name handlerPackage,
|
|
1273
|
+
appRepo.name applicationRepo,
|
|
1274
|
+
appRepo.package_name applicationPackage,
|
|
1275
|
+
? modelRepoId,
|
|
1276
|
+
? modelRepo,
|
|
1277
|
+
? modelPackage,
|
|
1278
|
+
? servicePath,
|
|
1279
|
+
? operationPath,
|
|
1280
|
+
? operationName,
|
|
1281
|
+
CASE WHEN appRepo.id=? THEN 1 ELSE 0 END modelIsApplicationRepo,
|
|
1282
|
+
CASE WHEN handlerRepo.id=? THEN 1 ELSE 0 END modelIsHandlerRepo,
|
|
1283
|
+
CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
|
|
1284
|
+
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id AND localService.service_path=?) THEN 1 ELSE 0 END localServicePathMatch,
|
|
1285
|
+
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
|
|
1286
|
+
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END appDependsOnModel,
|
|
1287
|
+
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=CAST(handlerRepo.id AS TEXT)) THEN 1 ELSE 0 END appDependsOnHandler,
|
|
1288
|
+
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(handlerRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END handlerDependsOnModel
|
|
1289
|
+
FROM handler_methods hm
|
|
1290
|
+
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
1291
|
+
JOIN repositories handlerRepo ON handlerRepo.id=hc.repo_id
|
|
1292
|
+
JOIN handler_registrations hr ON (hr.handler_class_id=hc.id OR (hr.class_name=hc.class_name AND (hr.repo_id=hc.repo_id OR hr.import_source IS NOT NULL)))
|
|
1293
|
+
JOIN repositories appRepo ON appRepo.id=hr.repo_id
|
|
1294
|
+
WHERE appRepo.workspace_id=?
|
|
1295
|
+
AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=?)`).all(
|
|
1296
|
+
operation.modelRepoId,
|
|
1297
|
+
operation.modelRepo,
|
|
1298
|
+
operation.modelPackage,
|
|
1299
|
+
operation.servicePath,
|
|
1300
|
+
operation.operationPath,
|
|
1301
|
+
operation.operationName,
|
|
1302
|
+
operation.modelRepoId,
|
|
1303
|
+
operation.modelRepoId,
|
|
1304
|
+
operation.servicePath,
|
|
1305
|
+
modelRepoGraphId,
|
|
1306
|
+
modelRepoGraphId,
|
|
1307
|
+
workspaceId,
|
|
1308
|
+
normalizedOperation(String(operation.operationPath ?? "")),
|
|
1309
|
+
operation.operationName,
|
|
1310
|
+
operation.operationName
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
function scoreImplementationCandidate(row, operation) {
|
|
1314
|
+
const acceptedReasons = [];
|
|
1315
|
+
const rejectedReasons = [];
|
|
1316
|
+
let score = 0;
|
|
1317
|
+
const modelIsApplicationRepo = flag(row.modelIsApplicationRepo);
|
|
1318
|
+
const modelIsHandlerRepo = flag(row.modelIsHandlerRepo);
|
|
1319
|
+
const localServicePathMatch = flag(row.localServicePathMatch);
|
|
1320
|
+
const applicationHasLocalServices = flag(row.applicationHasLocalServices);
|
|
1321
|
+
const appDependsOnModel = flag(row.appDependsOnModel);
|
|
1322
|
+
const appDependsOnHandler = flag(row.appDependsOnHandler);
|
|
1323
|
+
const handlerDependsOnModel = flag(row.handlerDependsOnModel);
|
|
1324
|
+
const importSource = typeof row.importSource === "string" && row.importSource.length > 0;
|
|
1325
|
+
if (modelIsApplicationRepo) {
|
|
1326
|
+
score += 100;
|
|
1327
|
+
acceptedReasons.push("model package equals registration package");
|
|
1328
|
+
}
|
|
1329
|
+
if (modelIsHandlerRepo) {
|
|
1330
|
+
score += 100;
|
|
1331
|
+
acceptedReasons.push("model package equals handler package");
|
|
1332
|
+
}
|
|
1333
|
+
if (localServicePathMatch) {
|
|
1334
|
+
score += 80;
|
|
1335
|
+
acceptedReasons.push("registration package contains exact local service path");
|
|
1336
|
+
} else if (applicationHasLocalServices && !appDependsOnModel && !modelIsApplicationRepo) {
|
|
1337
|
+
rejectedReasons.push(`registration package has local services but none match ${String(operation.servicePath ?? "")}`);
|
|
1338
|
+
}
|
|
1339
|
+
if (appDependsOnModel) {
|
|
1340
|
+
score += 70;
|
|
1341
|
+
acceptedReasons.push("registration package depends on model package");
|
|
1342
|
+
}
|
|
1343
|
+
if (appDependsOnHandler) {
|
|
1344
|
+
score += 30;
|
|
1345
|
+
acceptedReasons.push("registration package depends on handler package");
|
|
1346
|
+
}
|
|
1347
|
+
if (handlerDependsOnModel) {
|
|
1348
|
+
score += 20;
|
|
1349
|
+
acceptedReasons.push("handler package depends on model package");
|
|
1350
|
+
}
|
|
1351
|
+
if (importSource) {
|
|
1352
|
+
score += 10;
|
|
1353
|
+
acceptedReasons.push("registration imports handler class");
|
|
1354
|
+
}
|
|
1355
|
+
const hasOwnership = modelIsApplicationRepo || modelIsHandlerRepo;
|
|
1356
|
+
const hasCrossPackage = appDependsOnModel && (modelIsHandlerRepo || appDependsOnHandler || !importSource);
|
|
1357
|
+
const contradicted = applicationHasLocalServices && !localServicePathMatch && !appDependsOnModel && !hasOwnership;
|
|
1358
|
+
if (!hasOwnership && !localServicePathMatch && !hasCrossPackage) rejectedReasons.push("missing direct ownership, exact local service path, or validated cross-package dependency evidence");
|
|
1359
|
+
const accepted = !contradicted && (hasOwnership || localServicePathMatch || hasCrossPackage || handlerDependsOnModel);
|
|
1360
|
+
if (!accepted && rejectedReasons.length === 0) rejectedReasons.push("candidate did not meet implementation ownership policy");
|
|
1361
|
+
return { ...row, methodId: Number(row.methodId), score, accepted, acceptedReasons, rejectedReasons };
|
|
1362
|
+
}
|
|
1363
|
+
function candidateEvidence(candidate, rank) {
|
|
1364
|
+
return {
|
|
1365
|
+
rank,
|
|
1366
|
+
score: candidate.score,
|
|
1367
|
+
accepted: candidate.accepted,
|
|
1368
|
+
acceptedReasons: candidate.acceptedReasons,
|
|
1369
|
+
rejectedReasons: candidate.rejectedReasons,
|
|
1370
|
+
methodId: candidate.methodId,
|
|
1371
|
+
classId: candidate.classId,
|
|
1372
|
+
className: candidate.className,
|
|
1373
|
+
sourceFile: candidate.sourceFile,
|
|
1374
|
+
sourceLine: candidate.sourceLine,
|
|
1375
|
+
registration: { file: candidate.registrationFile, line: candidate.registrationLine, kind: candidate.registrationKind, importSource: candidate.importSource },
|
|
1376
|
+
applicationPackage: { id: candidate.applicationRepoId, name: candidate.applicationRepo, packageName: candidate.applicationPackage },
|
|
1377
|
+
handlerPackage: { id: candidate.handlerRepoId, name: candidate.handlerRepo, packageName: candidate.handlerPackage },
|
|
1378
|
+
modelPackage: { id: candidate.modelRepoId, name: candidate.modelRepo, packageName: candidate.modelPackage },
|
|
1379
|
+
servicePath: candidate.servicePath,
|
|
1380
|
+
operationPath: candidate.operationPath,
|
|
1381
|
+
operationName: candidate.operationName,
|
|
1382
|
+
signals: {
|
|
1383
|
+
directOwnership: { modelIsApplicationRepo: flag(candidate.modelIsApplicationRepo), modelIsHandlerRepo: flag(candidate.modelIsHandlerRepo) },
|
|
1384
|
+
localServicePathMatch: flag(candidate.localServicePathMatch),
|
|
1385
|
+
applicationHasLocalServices: flag(candidate.applicationHasLocalServices),
|
|
1386
|
+
appDependsOnModel: flag(candidate.appDependsOnModel),
|
|
1387
|
+
appDependsOnHandler: flag(candidate.appDependsOnHandler),
|
|
1388
|
+
handlerDependsOnModel: flag(candidate.handlerDependsOnModel),
|
|
1389
|
+
sameRepoRegistration: flag(candidate.sameRepoRegistration)
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
function flag(value) {
|
|
1394
|
+
return Boolean(Number(value ?? 0));
|
|
1395
|
+
}
|
|
1396
|
+
function graphId(value) {
|
|
1397
|
+
return String(value);
|
|
1114
1398
|
}
|
|
1115
1399
|
function normalizedOperation(value) {
|
|
1116
1400
|
return value.startsWith("/") ? value.slice(1) : value;
|
|
@@ -1394,4 +1678,4 @@ export {
|
|
|
1394
1678
|
linkWorkspace,
|
|
1395
1679
|
trace
|
|
1396
1680
|
};
|
|
1397
|
-
//# sourceMappingURL=chunk-
|
|
1681
|
+
//# sourceMappingURL=chunk-HQMAFSRR.js.map
|