@fairfox/polly 0.4.2 → 0.5.1
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.
|
@@ -1314,16 +1314,353 @@ class IntegrationAnalyzer {
|
|
|
1314
1314
|
}
|
|
1315
1315
|
|
|
1316
1316
|
// vendor/analysis/src/extract/handlers.ts
|
|
1317
|
-
import { Project as Project4, SyntaxKind as
|
|
1317
|
+
import { Project as Project4, SyntaxKind as SyntaxKind3, Node as Node5 } from "ts-morph";
|
|
1318
1318
|
|
|
1319
|
+
// vendor/analysis/src/extract/relationships.ts
|
|
1320
|
+
import { Node as Node4 } from "ts-morph";
|
|
1321
|
+
|
|
1322
|
+
class RelationshipExtractor {
|
|
1323
|
+
extractFromHandler(handlerNode, sourceFile, handlerName) {
|
|
1324
|
+
const relationships = [];
|
|
1325
|
+
const visited = new Set;
|
|
1326
|
+
this.extractFromNode(handlerNode, sourceFile, handlerName, relationships, visited);
|
|
1327
|
+
return this.deduplicateRelationships(relationships);
|
|
1328
|
+
}
|
|
1329
|
+
extractFromNode(node, sourceFile, handlerName, relationships, visited) {
|
|
1330
|
+
node.forEachDescendant((descendant) => {
|
|
1331
|
+
if (Node4.isCallExpression(descendant)) {
|
|
1332
|
+
const expr = descendant.getExpression();
|
|
1333
|
+
if (Node4.isIdentifier(expr)) {
|
|
1334
|
+
const functionName = expr.getText();
|
|
1335
|
+
let functionDecl = sourceFile.getFunction(functionName);
|
|
1336
|
+
let targetSourceFile = sourceFile;
|
|
1337
|
+
if (!functionDecl) {
|
|
1338
|
+
const resolved = this.resolveImportedFunction(functionName, sourceFile);
|
|
1339
|
+
if (resolved) {
|
|
1340
|
+
functionDecl = resolved.functionDecl;
|
|
1341
|
+
targetSourceFile = resolved.sourceFile;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
if (functionDecl && !visited.has(functionName)) {
|
|
1345
|
+
visited.add(functionName);
|
|
1346
|
+
const body = functionDecl.getBody();
|
|
1347
|
+
if (body) {
|
|
1348
|
+
this.extractFromNode(body, targetSourceFile, handlerName, relationships, visited);
|
|
1349
|
+
}
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
if (!functionDecl) {
|
|
1353
|
+
const componentFromName = this.inferComponentFromFunctionName(functionName);
|
|
1354
|
+
if (componentFromName) {
|
|
1355
|
+
relationships.push({
|
|
1356
|
+
from: this.toComponentId(handlerName),
|
|
1357
|
+
to: componentFromName,
|
|
1358
|
+
description: `Calls ${functionName}()`,
|
|
1359
|
+
technology: "Function Call",
|
|
1360
|
+
confidence: "medium",
|
|
1361
|
+
evidence: [`Function call: ${functionName}`]
|
|
1362
|
+
});
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
if (Node4.isPropertyAccessExpression(expr)) {
|
|
1368
|
+
const rel2 = this.extractFromPropertyAccess(expr, handlerName);
|
|
1369
|
+
if (rel2) {
|
|
1370
|
+
relationships.push(rel2);
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
const rel = this.extractFromFunctionCall(descendant, handlerName, sourceFile);
|
|
1375
|
+
if (rel) {
|
|
1376
|
+
relationships.push(rel);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
if (Node4.isAwaitExpression(descendant)) {
|
|
1380
|
+
const rel = this.extractFromDatabaseCall(descendant, handlerName);
|
|
1381
|
+
if (rel) {
|
|
1382
|
+
relationships.push(rel);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
if (Node4.isCallExpression(descendant) && descendant.getExpression().getText() === "fetch") {
|
|
1386
|
+
const rel = this.extractFromFetchCall(descendant, handlerName);
|
|
1387
|
+
if (rel) {
|
|
1388
|
+
relationships.push(rel);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
extractFromFunctionCall(callExpr, handlerName, sourceFile) {
|
|
1394
|
+
const expr = callExpr.getExpression();
|
|
1395
|
+
const exprText = expr.getText();
|
|
1396
|
+
const skipList = [
|
|
1397
|
+
"console.",
|
|
1398
|
+
"JSON.",
|
|
1399
|
+
"Math.",
|
|
1400
|
+
"Object.",
|
|
1401
|
+
"Array.",
|
|
1402
|
+
"String.",
|
|
1403
|
+
"Number.",
|
|
1404
|
+
"Date.",
|
|
1405
|
+
"Promise.",
|
|
1406
|
+
"setTimeout",
|
|
1407
|
+
"setInterval",
|
|
1408
|
+
"clearTimeout",
|
|
1409
|
+
"clearInterval"
|
|
1410
|
+
];
|
|
1411
|
+
if (skipList.some((skip) => exprText.startsWith(skip))) {
|
|
1412
|
+
return null;
|
|
1413
|
+
}
|
|
1414
|
+
let functionName = exprText;
|
|
1415
|
+
let targetComponent = null;
|
|
1416
|
+
if (Node4.isPropertyAccessExpression(expr)) {
|
|
1417
|
+
const objectExpr = expr.getExpression();
|
|
1418
|
+
const objectName = objectExpr.getText();
|
|
1419
|
+
const methodName = expr.getName();
|
|
1420
|
+
targetComponent = this.inferComponentFromCall(objectName, methodName);
|
|
1421
|
+
if (!targetComponent) {
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
functionName = methodName;
|
|
1425
|
+
} else {
|
|
1426
|
+
targetComponent = this.resolveComponentFromImport(exprText, sourceFile);
|
|
1427
|
+
if (!targetComponent) {
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
return {
|
|
1432
|
+
from: this.toComponentId(handlerName),
|
|
1433
|
+
to: targetComponent,
|
|
1434
|
+
description: `Calls ${functionName}()`,
|
|
1435
|
+
technology: "Function Call",
|
|
1436
|
+
confidence: "high",
|
|
1437
|
+
evidence: [`Function call: ${exprText}`]
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
extractFromPropertyAccess(propAccess, handlerName) {
|
|
1441
|
+
if (!Node4.isPropertyAccessExpression(propAccess)) {
|
|
1442
|
+
return null;
|
|
1443
|
+
}
|
|
1444
|
+
const fullChain = propAccess.getText();
|
|
1445
|
+
const methodName = propAccess.getName();
|
|
1446
|
+
let rootObject = propAccess.getExpression();
|
|
1447
|
+
while (Node4.isPropertyAccessExpression(rootObject)) {
|
|
1448
|
+
rootObject = rootObject.getExpression();
|
|
1449
|
+
}
|
|
1450
|
+
const objectName = rootObject.getText();
|
|
1451
|
+
const targetComponent = this.inferComponentFromCall(objectName, methodName);
|
|
1452
|
+
if (!targetComponent) {
|
|
1453
|
+
return null;
|
|
1454
|
+
}
|
|
1455
|
+
return {
|
|
1456
|
+
from: this.toComponentId(handlerName),
|
|
1457
|
+
to: targetComponent,
|
|
1458
|
+
description: `Calls ${methodName}()`,
|
|
1459
|
+
technology: "Function Call",
|
|
1460
|
+
confidence: "high",
|
|
1461
|
+
evidence: [`Property access: ${fullChain}`]
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
extractFromDatabaseCall(awaitExpr, handlerName) {
|
|
1465
|
+
if (!Node4.isAwaitExpression(awaitExpr)) {
|
|
1466
|
+
return null;
|
|
1467
|
+
}
|
|
1468
|
+
const innerExpr = awaitExpr.getExpression();
|
|
1469
|
+
if (!Node4.isCallExpression(innerExpr)) {
|
|
1470
|
+
return null;
|
|
1471
|
+
}
|
|
1472
|
+
const callExpr = innerExpr.getExpression().getText();
|
|
1473
|
+
if (callExpr.includes("db.query") || callExpr.includes("db.execute") || callExpr.includes("db.select") || callExpr.includes("db.insert") || callExpr.includes("db.update") || callExpr.includes("db.delete")) {
|
|
1474
|
+
const operation = this.inferDatabaseOperation(callExpr);
|
|
1475
|
+
return {
|
|
1476
|
+
from: this.toComponentId(handlerName),
|
|
1477
|
+
to: "database",
|
|
1478
|
+
description: operation,
|
|
1479
|
+
technology: "SQL",
|
|
1480
|
+
confidence: "high",
|
|
1481
|
+
evidence: [`Database call: ${callExpr}`]
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
return null;
|
|
1485
|
+
}
|
|
1486
|
+
extractFromFetchCall(callExpr, handlerName) {
|
|
1487
|
+
const args = callExpr.getArguments();
|
|
1488
|
+
if (args.length === 0) {
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
const urlArg = args[0].getText();
|
|
1492
|
+
let apiName = "external_api";
|
|
1493
|
+
if (urlArg.includes("openai")) {
|
|
1494
|
+
apiName = "openai_api";
|
|
1495
|
+
} else if (urlArg.includes("anthropic")) {
|
|
1496
|
+
apiName = "anthropic_api";
|
|
1497
|
+
}
|
|
1498
|
+
return {
|
|
1499
|
+
from: this.toComponentId(handlerName),
|
|
1500
|
+
to: apiName,
|
|
1501
|
+
description: "Calls external API",
|
|
1502
|
+
technology: "HTTP/REST",
|
|
1503
|
+
confidence: "high",
|
|
1504
|
+
evidence: [`fetch() call to: ${urlArg}`]
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
inferComponentFromCall(objectName, methodName) {
|
|
1508
|
+
const mappings = {
|
|
1509
|
+
db: "db_client",
|
|
1510
|
+
database: "database",
|
|
1511
|
+
repos: "repositories",
|
|
1512
|
+
repository: "repositories",
|
|
1513
|
+
repositories: "repositories",
|
|
1514
|
+
cache: "cache",
|
|
1515
|
+
storage: "storage",
|
|
1516
|
+
ai: "ai_service",
|
|
1517
|
+
auth: "auth_service",
|
|
1518
|
+
authservice: "auth_service",
|
|
1519
|
+
user: "user_service",
|
|
1520
|
+
userservice: "user_service",
|
|
1521
|
+
logger: "logger",
|
|
1522
|
+
queue: "queue_service"
|
|
1523
|
+
};
|
|
1524
|
+
const normalized = objectName.toLowerCase();
|
|
1525
|
+
return mappings[normalized] || null;
|
|
1526
|
+
}
|
|
1527
|
+
inferComponentFromFunctionName(functionName) {
|
|
1528
|
+
const normalized = functionName.toLowerCase();
|
|
1529
|
+
if (normalized.startsWith("get") || normalized.startsWith("create")) {
|
|
1530
|
+
const suffix = functionName.substring(normalized.startsWith("get") ? 3 : 6);
|
|
1531
|
+
const suffixLower = suffix.toLowerCase();
|
|
1532
|
+
if (suffixLower.includes("database") || suffixLower === "db" || suffixLower.includes("dbconnection") || suffixLower.includes("connection")) {
|
|
1533
|
+
return "db_client";
|
|
1534
|
+
}
|
|
1535
|
+
if (suffixLower.includes("repositories") || suffixLower.includes("repos") || suffixLower.includes("repository")) {
|
|
1536
|
+
return "repositories";
|
|
1537
|
+
}
|
|
1538
|
+
if (suffixLower.includes("service")) {
|
|
1539
|
+
const serviceMatch = suffix.match(/^(.+?)Service/i);
|
|
1540
|
+
if (serviceMatch) {
|
|
1541
|
+
return this.toComponentId(`${serviceMatch[1]}_service`);
|
|
1542
|
+
}
|
|
1543
|
+
return "service";
|
|
1544
|
+
}
|
|
1545
|
+
if (suffixLower.includes("cache")) {
|
|
1546
|
+
return "cache";
|
|
1547
|
+
}
|
|
1548
|
+
if (suffixLower.includes("storage")) {
|
|
1549
|
+
return "storage";
|
|
1550
|
+
}
|
|
1551
|
+
if (suffixLower.includes("ai") || suffixLower.includes("llm")) {
|
|
1552
|
+
return "ai_service";
|
|
1553
|
+
}
|
|
1554
|
+
if (suffixLower.includes("logger")) {
|
|
1555
|
+
return "logger";
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
if (normalized.startsWith("init") || normalized.startsWith("setup")) {
|
|
1559
|
+
const suffix = functionName.substring(normalized.startsWith("init") ? 4 : 5);
|
|
1560
|
+
const suffixLower = suffix.toLowerCase();
|
|
1561
|
+
if (suffixLower.includes("database") || suffixLower === "db") {
|
|
1562
|
+
return "db_client";
|
|
1563
|
+
}
|
|
1564
|
+
if (suffixLower.includes("cache")) {
|
|
1565
|
+
return "cache";
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
return null;
|
|
1569
|
+
}
|
|
1570
|
+
resolveComponentFromImport(functionName, sourceFile) {
|
|
1571
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
1572
|
+
const namedImports = importDecl.getNamedImports();
|
|
1573
|
+
for (const namedImport of namedImports) {
|
|
1574
|
+
if (namedImport.getName() === functionName) {
|
|
1575
|
+
const modulePath = importDecl.getModuleSpecifierValue();
|
|
1576
|
+
if (modulePath.includes("/db/") || modulePath.includes("/database/")) {
|
|
1577
|
+
return "db_client";
|
|
1578
|
+
}
|
|
1579
|
+
if (modulePath.includes("/repos") || modulePath.includes("/repositories")) {
|
|
1580
|
+
return "repositories";
|
|
1581
|
+
}
|
|
1582
|
+
if (modulePath.includes("/service") || modulePath.includes("/services")) {
|
|
1583
|
+
const match = modulePath.match(/\/([^/]+)\.ts$/);
|
|
1584
|
+
if (match) {
|
|
1585
|
+
return this.toComponentId(match[1]);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
return null;
|
|
1592
|
+
}
|
|
1593
|
+
resolveImportedFunction(functionName, sourceFile) {
|
|
1594
|
+
try {
|
|
1595
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
1596
|
+
const namedImports = importDecl.getNamedImports();
|
|
1597
|
+
for (const namedImport of namedImports) {
|
|
1598
|
+
if (namedImport.getName() === functionName) {
|
|
1599
|
+
const moduleSpecifier = importDecl.getModuleSpecifierSourceFile();
|
|
1600
|
+
if (!moduleSpecifier)
|
|
1601
|
+
continue;
|
|
1602
|
+
const functionDecl = moduleSpecifier.getFunction(functionName);
|
|
1603
|
+
if (functionDecl) {
|
|
1604
|
+
return {
|
|
1605
|
+
functionDecl,
|
|
1606
|
+
sourceFile: moduleSpecifier
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
const variableDecl = moduleSpecifier.getVariableDeclaration(functionName);
|
|
1610
|
+
if (variableDecl) {
|
|
1611
|
+
const initializer = variableDecl.getInitializer();
|
|
1612
|
+
if (initializer && (Node4.isArrowFunction(initializer) || Node4.isFunctionExpression(initializer))) {
|
|
1613
|
+
return {
|
|
1614
|
+
functionDecl: initializer,
|
|
1615
|
+
sourceFile: moduleSpecifier
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
} catch (error) {
|
|
1623
|
+
return null;
|
|
1624
|
+
}
|
|
1625
|
+
return null;
|
|
1626
|
+
}
|
|
1627
|
+
inferDatabaseOperation(callExpr) {
|
|
1628
|
+
if (callExpr.includes("query") || callExpr.includes("select")) {
|
|
1629
|
+
return "Reads from database";
|
|
1630
|
+
}
|
|
1631
|
+
if (callExpr.includes("execute") || callExpr.includes("insert") || callExpr.includes("update") || callExpr.includes("delete")) {
|
|
1632
|
+
return "Writes to database";
|
|
1633
|
+
}
|
|
1634
|
+
return "Accesses database";
|
|
1635
|
+
}
|
|
1636
|
+
toComponentId(name) {
|
|
1637
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
1638
|
+
}
|
|
1639
|
+
deduplicateRelationships(relationships) {
|
|
1640
|
+
const seen = new Set;
|
|
1641
|
+
const unique = [];
|
|
1642
|
+
for (const rel of relationships) {
|
|
1643
|
+
const key = `${rel.from}->${rel.to}`;
|
|
1644
|
+
if (!seen.has(key)) {
|
|
1645
|
+
seen.add(key);
|
|
1646
|
+
unique.push(rel);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
return unique;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
// vendor/analysis/src/extract/handlers.ts
|
|
1319
1654
|
class HandlerExtractor {
|
|
1320
1655
|
project;
|
|
1321
1656
|
typeGuardCache;
|
|
1657
|
+
relationshipExtractor;
|
|
1322
1658
|
constructor(tsConfigPath) {
|
|
1323
1659
|
this.project = new Project4({
|
|
1324
1660
|
tsConfigFilePath: tsConfigPath
|
|
1325
1661
|
});
|
|
1326
1662
|
this.typeGuardCache = new WeakMap;
|
|
1663
|
+
this.relationshipExtractor = new RelationshipExtractor;
|
|
1327
1664
|
}
|
|
1328
1665
|
extractHandlers() {
|
|
1329
1666
|
const handlers = [];
|
|
@@ -1357,9 +1694,9 @@ class HandlerExtractor {
|
|
|
1357
1694
|
const filePath = sourceFile.getFilePath();
|
|
1358
1695
|
const context = this.inferContext(filePath);
|
|
1359
1696
|
sourceFile.forEachDescendant((node) => {
|
|
1360
|
-
if (
|
|
1697
|
+
if (Node5.isCallExpression(node)) {
|
|
1361
1698
|
const expression = node.getExpression();
|
|
1362
|
-
if (
|
|
1699
|
+
if (Node5.isPropertyAccessExpression(expression)) {
|
|
1363
1700
|
const methodName = expression.getName();
|
|
1364
1701
|
if (methodName === "on" || methodName === "addEventListener") {
|
|
1365
1702
|
const handler = this.extractHandler(node, context, filePath);
|
|
@@ -1369,17 +1706,17 @@ class HandlerExtractor {
|
|
|
1369
1706
|
}
|
|
1370
1707
|
}
|
|
1371
1708
|
}
|
|
1372
|
-
if (
|
|
1709
|
+
if (Node5.isSwitchStatement(node)) {
|
|
1373
1710
|
const switchHandlers = this.extractSwitchCaseHandlers(node, context, filePath);
|
|
1374
1711
|
handlers.push(...switchHandlers);
|
|
1375
1712
|
}
|
|
1376
|
-
if (
|
|
1713
|
+
if (Node5.isVariableDeclaration(node)) {
|
|
1377
1714
|
const mapHandlers = this.extractHandlerMapPattern(node, context, filePath);
|
|
1378
1715
|
handlers.push(...mapHandlers);
|
|
1379
1716
|
}
|
|
1380
|
-
if (
|
|
1717
|
+
if (Node5.isIfStatement(node)) {
|
|
1381
1718
|
const parent = node.getParent();
|
|
1382
|
-
const isElseIf = parent &&
|
|
1719
|
+
const isElseIf = parent && Node5.isIfStatement(parent);
|
|
1383
1720
|
if (!isElseIf) {
|
|
1384
1721
|
const typeGuardHandlers = this.extractTypeGuardHandlers(node, context, filePath);
|
|
1385
1722
|
handlers.push(...typeGuardHandlers);
|
|
@@ -1395,9 +1732,9 @@ class HandlerExtractor {
|
|
|
1395
1732
|
}
|
|
1396
1733
|
const messageTypeArg = args[0];
|
|
1397
1734
|
let messageType = null;
|
|
1398
|
-
if (
|
|
1735
|
+
if (Node5.isStringLiteral(messageTypeArg)) {
|
|
1399
1736
|
messageType = messageTypeArg.getLiteralValue();
|
|
1400
|
-
} else if (
|
|
1737
|
+
} else if (Node5.isTemplateExpression(messageTypeArg)) {
|
|
1401
1738
|
messageType = messageTypeArg.getText().replace(/[`'"]/g, "");
|
|
1402
1739
|
}
|
|
1403
1740
|
if (!messageType) {
|
|
@@ -1407,11 +1744,20 @@ class HandlerExtractor {
|
|
|
1407
1744
|
const assignments = [];
|
|
1408
1745
|
const preconditions = [];
|
|
1409
1746
|
const postconditions = [];
|
|
1410
|
-
if (
|
|
1747
|
+
if (Node5.isArrowFunction(handlerArg) || Node5.isFunctionExpression(handlerArg)) {
|
|
1411
1748
|
this.extractAssignments(handlerArg, assignments);
|
|
1412
1749
|
this.extractVerificationConditions(handlerArg, preconditions, postconditions);
|
|
1413
1750
|
}
|
|
1414
1751
|
const line = callExpr.getStartLineNumber();
|
|
1752
|
+
const sourceFile = callExpr.getSourceFile();
|
|
1753
|
+
const handlerName = `${messageType}_handler`;
|
|
1754
|
+
let relationships = undefined;
|
|
1755
|
+
if (Node5.isArrowFunction(handlerArg) || Node5.isFunctionExpression(handlerArg)) {
|
|
1756
|
+
const detectedRelationships = this.relationshipExtractor.extractFromHandler(handlerArg, sourceFile, handlerName);
|
|
1757
|
+
if (detectedRelationships.length > 0) {
|
|
1758
|
+
relationships = detectedRelationships;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1415
1761
|
return {
|
|
1416
1762
|
messageType,
|
|
1417
1763
|
node: context,
|
|
@@ -1421,17 +1767,18 @@ class HandlerExtractor {
|
|
|
1421
1767
|
location: {
|
|
1422
1768
|
file: filePath,
|
|
1423
1769
|
line
|
|
1424
|
-
}
|
|
1770
|
+
},
|
|
1771
|
+
relationships
|
|
1425
1772
|
};
|
|
1426
1773
|
}
|
|
1427
1774
|
extractAssignments(funcNode, assignments) {
|
|
1428
1775
|
funcNode.forEachDescendant((node) => {
|
|
1429
|
-
if (
|
|
1776
|
+
if (Node5.isBinaryExpression(node)) {
|
|
1430
1777
|
const operator = node.getOperatorToken().getText();
|
|
1431
1778
|
if (operator === "=") {
|
|
1432
1779
|
const left = node.getLeft();
|
|
1433
1780
|
const right = node.getRight();
|
|
1434
|
-
if (
|
|
1781
|
+
if (Node5.isPropertyAccessExpression(left)) {
|
|
1435
1782
|
const fieldPath = this.getPropertyPath(left);
|
|
1436
1783
|
if (fieldPath.startsWith("state.")) {
|
|
1437
1784
|
const field = fieldPath.substring(6);
|
|
@@ -1450,13 +1797,13 @@ class HandlerExtractor {
|
|
|
1450
1797
|
}
|
|
1451
1798
|
extractVerificationConditions(funcNode, preconditions, postconditions) {
|
|
1452
1799
|
const body = funcNode.getBody();
|
|
1453
|
-
const statements =
|
|
1800
|
+
const statements = Node5.isBlock(body) ? body.getStatements() : [body];
|
|
1454
1801
|
statements.forEach((statement, index) => {
|
|
1455
|
-
if (
|
|
1802
|
+
if (Node5.isExpressionStatement(statement)) {
|
|
1456
1803
|
const expr = statement.getExpression();
|
|
1457
|
-
if (
|
|
1804
|
+
if (Node5.isCallExpression(expr)) {
|
|
1458
1805
|
const callee = expr.getExpression();
|
|
1459
|
-
if (
|
|
1806
|
+
if (Node5.isIdentifier(callee)) {
|
|
1460
1807
|
const functionName = callee.getText();
|
|
1461
1808
|
if (functionName === "requires") {
|
|
1462
1809
|
const condition = this.extractCondition(expr);
|
|
@@ -1482,7 +1829,7 @@ class HandlerExtractor {
|
|
|
1482
1829
|
const conditionArg = args[0];
|
|
1483
1830
|
const expression = conditionArg.getText();
|
|
1484
1831
|
let message;
|
|
1485
|
-
if (args.length >= 2 &&
|
|
1832
|
+
if (args.length >= 2 && Node5.isStringLiteral(args[1])) {
|
|
1486
1833
|
message = args[1].getLiteralValue();
|
|
1487
1834
|
}
|
|
1488
1835
|
const line = callExpr.getStartLineNumber();
|
|
@@ -1499,29 +1846,29 @@ class HandlerExtractor {
|
|
|
1499
1846
|
getPropertyPath(node) {
|
|
1500
1847
|
const parts = [];
|
|
1501
1848
|
let current = node;
|
|
1502
|
-
while (
|
|
1849
|
+
while (Node5.isPropertyAccessExpression(current)) {
|
|
1503
1850
|
parts.unshift(current.getName());
|
|
1504
1851
|
current = current.getExpression();
|
|
1505
1852
|
}
|
|
1506
|
-
if (
|
|
1853
|
+
if (Node5.isIdentifier(current)) {
|
|
1507
1854
|
parts.unshift(current.getText());
|
|
1508
1855
|
}
|
|
1509
1856
|
return parts.join(".");
|
|
1510
1857
|
}
|
|
1511
1858
|
extractValue(node) {
|
|
1512
|
-
if (
|
|
1859
|
+
if (Node5.isStringLiteral(node)) {
|
|
1513
1860
|
return node.getLiteralValue();
|
|
1514
1861
|
}
|
|
1515
|
-
if (
|
|
1862
|
+
if (Node5.isNumericLiteral(node)) {
|
|
1516
1863
|
return node.getLiteralValue();
|
|
1517
1864
|
}
|
|
1518
|
-
if (node.getKind() ===
|
|
1865
|
+
if (node.getKind() === SyntaxKind3.TrueKeyword) {
|
|
1519
1866
|
return true;
|
|
1520
1867
|
}
|
|
1521
|
-
if (node.getKind() ===
|
|
1868
|
+
if (node.getKind() === SyntaxKind3.FalseKeyword) {
|
|
1522
1869
|
return false;
|
|
1523
1870
|
}
|
|
1524
|
-
if (node.getKind() ===
|
|
1871
|
+
if (node.getKind() === SyntaxKind3.NullKeyword) {
|
|
1525
1872
|
return null;
|
|
1526
1873
|
}
|
|
1527
1874
|
return;
|
|
@@ -1536,10 +1883,10 @@ class HandlerExtractor {
|
|
|
1536
1883
|
}
|
|
1537
1884
|
const caseClauses = switchNode.getClauses();
|
|
1538
1885
|
for (const clause of caseClauses) {
|
|
1539
|
-
if (
|
|
1886
|
+
if (Node5.isCaseClause(clause)) {
|
|
1540
1887
|
const caseExpr = clause.getExpression();
|
|
1541
1888
|
let messageType = null;
|
|
1542
|
-
if (
|
|
1889
|
+
if (Node5.isStringLiteral(caseExpr)) {
|
|
1543
1890
|
messageType = caseExpr.getLiteralValue();
|
|
1544
1891
|
}
|
|
1545
1892
|
if (messageType) {
|
|
@@ -1562,7 +1909,7 @@ class HandlerExtractor {
|
|
|
1562
1909
|
const handlers = [];
|
|
1563
1910
|
try {
|
|
1564
1911
|
const initializer = varDecl.getInitializer();
|
|
1565
|
-
if (!initializer || !
|
|
1912
|
+
if (!initializer || !Node5.isObjectLiteralExpression(initializer)) {
|
|
1566
1913
|
return handlers;
|
|
1567
1914
|
}
|
|
1568
1915
|
const varName = varDecl.getName().toLowerCase();
|
|
@@ -1571,12 +1918,12 @@ class HandlerExtractor {
|
|
|
1571
1918
|
}
|
|
1572
1919
|
const properties = initializer.getProperties();
|
|
1573
1920
|
for (const prop of properties) {
|
|
1574
|
-
if (
|
|
1921
|
+
if (Node5.isPropertyAssignment(prop)) {
|
|
1575
1922
|
const nameNode = prop.getNameNode();
|
|
1576
1923
|
let messageType = null;
|
|
1577
|
-
if (
|
|
1924
|
+
if (Node5.isStringLiteral(nameNode)) {
|
|
1578
1925
|
messageType = nameNode.getLiteralValue();
|
|
1579
|
-
} else if (
|
|
1926
|
+
} else if (Node5.isIdentifier(nameNode)) {
|
|
1580
1927
|
messageType = nameNode.getText();
|
|
1581
1928
|
}
|
|
1582
1929
|
if (messageType) {
|
|
@@ -1623,7 +1970,7 @@ class HandlerExtractor {
|
|
|
1623
1970
|
}
|
|
1624
1971
|
}
|
|
1625
1972
|
const elseStatement = currentIf.getElseStatement();
|
|
1626
|
-
if (elseStatement &&
|
|
1973
|
+
if (elseStatement && Node5.isIfStatement(elseStatement)) {
|
|
1627
1974
|
currentIf = elseStatement;
|
|
1628
1975
|
} else {
|
|
1629
1976
|
break;
|
|
@@ -1639,12 +1986,12 @@ class HandlerExtractor {
|
|
|
1639
1986
|
extractHandlerFromIfClause(ifNode, typeGuards, context, filePath) {
|
|
1640
1987
|
try {
|
|
1641
1988
|
const condition = ifNode.getExpression();
|
|
1642
|
-
if (!
|
|
1989
|
+
if (!Node5.isCallExpression(condition)) {
|
|
1643
1990
|
return null;
|
|
1644
1991
|
}
|
|
1645
1992
|
const funcExpr = condition.getExpression();
|
|
1646
1993
|
let funcName;
|
|
1647
|
-
if (
|
|
1994
|
+
if (Node5.isIdentifier(funcExpr)) {
|
|
1648
1995
|
funcName = funcExpr.getText();
|
|
1649
1996
|
}
|
|
1650
1997
|
if (process.env.POLLY_DEBUG && funcName) {
|
|
@@ -1656,7 +2003,7 @@ class HandlerExtractor {
|
|
|
1656
2003
|
if (process.env.POLLY_DEBUG) {
|
|
1657
2004
|
console.log(`[DEBUG] Found in local type guards: ${funcName} → ${messageType}`);
|
|
1658
2005
|
}
|
|
1659
|
-
} else if (
|
|
2006
|
+
} else if (Node5.isIdentifier(funcExpr)) {
|
|
1660
2007
|
if (process.env.POLLY_DEBUG) {
|
|
1661
2008
|
console.log(`[DEBUG] Not found locally, trying import resolution for: ${funcName}`);
|
|
1662
2009
|
}
|
|
@@ -1669,13 +2016,24 @@ class HandlerExtractor {
|
|
|
1669
2016
|
return null;
|
|
1670
2017
|
}
|
|
1671
2018
|
const line = ifNode.getStartLineNumber();
|
|
2019
|
+
const sourceFile = ifNode.getSourceFile();
|
|
2020
|
+
const handlerName = `${messageType}_handler`;
|
|
2021
|
+
let relationships = undefined;
|
|
2022
|
+
const thenStatement = ifNode.getThenStatement();
|
|
2023
|
+
if (thenStatement) {
|
|
2024
|
+
const detectedRelationships = this.relationshipExtractor.extractFromHandler(thenStatement, sourceFile, handlerName);
|
|
2025
|
+
if (detectedRelationships.length > 0) {
|
|
2026
|
+
relationships = detectedRelationships;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
1672
2029
|
return {
|
|
1673
2030
|
messageType,
|
|
1674
2031
|
node: context,
|
|
1675
2032
|
assignments: [],
|
|
1676
2033
|
preconditions: [],
|
|
1677
2034
|
postconditions: [],
|
|
1678
|
-
location: { file: filePath, line }
|
|
2035
|
+
location: { file: filePath, line },
|
|
2036
|
+
relationships
|
|
1679
2037
|
};
|
|
1680
2038
|
} catch (error) {
|
|
1681
2039
|
return null;
|
|
@@ -1684,20 +2042,20 @@ class HandlerExtractor {
|
|
|
1684
2042
|
findTypePredicateFunctions(sourceFile) {
|
|
1685
2043
|
const typeGuards = new Map;
|
|
1686
2044
|
sourceFile.forEachDescendant((node) => {
|
|
1687
|
-
if (
|
|
2045
|
+
if (Node5.isFunctionDeclaration(node) || Node5.isFunctionExpression(node) || Node5.isArrowFunction(node)) {
|
|
1688
2046
|
const returnTypeNode = node.getReturnTypeNode();
|
|
1689
|
-
if (returnTypeNode &&
|
|
2047
|
+
if (returnTypeNode && Node5.isTypePredicate(returnTypeNode)) {
|
|
1690
2048
|
let functionName;
|
|
1691
|
-
if (
|
|
2049
|
+
if (Node5.isFunctionDeclaration(node)) {
|
|
1692
2050
|
functionName = node.getName();
|
|
1693
|
-
} else if (
|
|
2051
|
+
} else if (Node5.isFunctionExpression(node)) {
|
|
1694
2052
|
const parent = node.getParent();
|
|
1695
|
-
if (
|
|
2053
|
+
if (Node5.isVariableDeclaration(parent)) {
|
|
1696
2054
|
functionName = parent.getName();
|
|
1697
2055
|
}
|
|
1698
|
-
} else if (
|
|
2056
|
+
} else if (Node5.isArrowFunction(node)) {
|
|
1699
2057
|
const parent = node.getParent();
|
|
1700
|
-
if (
|
|
2058
|
+
if (Node5.isVariableDeclaration(parent)) {
|
|
1701
2059
|
functionName = parent.getName();
|
|
1702
2060
|
}
|
|
1703
2061
|
}
|
|
@@ -1738,15 +2096,15 @@ class HandlerExtractor {
|
|
|
1738
2096
|
return null;
|
|
1739
2097
|
}
|
|
1740
2098
|
for (const def of definitions) {
|
|
1741
|
-
if (
|
|
2099
|
+
if (Node5.isFunctionDeclaration(def) || Node5.isFunctionExpression(def) || Node5.isArrowFunction(def)) {
|
|
1742
2100
|
const returnTypeNode = def.getReturnTypeNode();
|
|
1743
2101
|
if (process.env.POLLY_DEBUG) {
|
|
1744
2102
|
const returnType = def.getReturnType().getText();
|
|
1745
2103
|
console.log(`[DEBUG] Function ${funcName} return type (resolved): ${returnType}`);
|
|
1746
2104
|
console.log(`[DEBUG] Has return type node: ${!!returnTypeNode}`);
|
|
1747
|
-
console.log(`[DEBUG] Is type predicate node: ${returnTypeNode &&
|
|
2105
|
+
console.log(`[DEBUG] Is type predicate node: ${returnTypeNode && Node5.isTypePredicate(returnTypeNode)}`);
|
|
1748
2106
|
}
|
|
1749
|
-
if (returnTypeNode &&
|
|
2107
|
+
if (returnTypeNode && Node5.isTypePredicate(returnTypeNode)) {
|
|
1750
2108
|
const typeNode = returnTypeNode.getTypeNode();
|
|
1751
2109
|
if (typeNode) {
|
|
1752
2110
|
const typeName = typeNode.getText();
|
|
@@ -2288,10 +2646,33 @@ class StructurizrDSLGenerator {
|
|
|
2288
2646
|
if (this.options.groups && this.options.groups.length > 0) {
|
|
2289
2647
|
parts.push(...this.generateGroupedComponents(componentDefs, this.options.groups));
|
|
2290
2648
|
} else {
|
|
2291
|
-
|
|
2292
|
-
|
|
2649
|
+
const autoGroups = this.generateAutomaticGroups(componentDefs);
|
|
2650
|
+
if (autoGroups.length > 0) {
|
|
2651
|
+
parts.push(...this.generateGroupedComponents(componentDefs, autoGroups));
|
|
2652
|
+
} else {
|
|
2653
|
+
for (const comp of componentDefs) {
|
|
2654
|
+
parts.push(this.generateComponentDefinition(comp, " "));
|
|
2655
|
+
}
|
|
2293
2656
|
}
|
|
2294
2657
|
}
|
|
2658
|
+
const serviceComponents = new Set;
|
|
2659
|
+
for (const handler of contextInfo.handlers) {
|
|
2660
|
+
if (handler.relationships) {
|
|
2661
|
+
for (const rel of handler.relationships) {
|
|
2662
|
+
const targetId = this.toId(rel.to);
|
|
2663
|
+
const isHandler = componentDefs.some((c) => c.id === targetId);
|
|
2664
|
+
if (!isHandler) {
|
|
2665
|
+
serviceComponents.add(rel.to);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
for (const serviceId of serviceComponents) {
|
|
2671
|
+
const serviceName = serviceId.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
2672
|
+
parts.push(` ${this.toId(serviceId)} = component "${serviceName}" "Business logic service" {`);
|
|
2673
|
+
parts.push(` tags "Service" "Auto-detected"`);
|
|
2674
|
+
parts.push(` }`);
|
|
2675
|
+
}
|
|
2295
2676
|
if (contextInfo.components) {
|
|
2296
2677
|
for (const comp of contextInfo.components) {
|
|
2297
2678
|
parts.push(` ${this.toId(comp.name)} = component "${comp.name}" "${this.escape(comp.description || "UI component")}" {`);
|
|
@@ -2444,6 +2825,21 @@ class StructurizrDSLGenerator {
|
|
|
2444
2825
|
parts.push(` }`);
|
|
2445
2826
|
}
|
|
2446
2827
|
}
|
|
2828
|
+
for (const handler of contextInfo.handlers) {
|
|
2829
|
+
if (handler.relationships && handler.relationships.length > 0) {
|
|
2830
|
+
for (const rel of handler.relationships) {
|
|
2831
|
+
const fromId = this.toId(rel.from);
|
|
2832
|
+
const toId = this.toId(rel.to);
|
|
2833
|
+
const description = this.escape(rel.description);
|
|
2834
|
+
parts.push(` ${fromId} -> ${toId} "${description}" {`);
|
|
2835
|
+
if (rel.technology) {
|
|
2836
|
+
parts.push(` technology "${this.escape(rel.technology)}"`);
|
|
2837
|
+
}
|
|
2838
|
+
parts.push(` tags "Auto-detected"`);
|
|
2839
|
+
parts.push(` }`);
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2447
2843
|
const stateHandlers = [];
|
|
2448
2844
|
const queryHandlers = [];
|
|
2449
2845
|
for (const [messageType, handlers] of handlersByType) {
|
|
@@ -2568,6 +2964,10 @@ class StructurizrDSLGenerator {
|
|
|
2568
2964
|
parts.push(this.generateUserDynamicDiagram(diagram));
|
|
2569
2965
|
}
|
|
2570
2966
|
}
|
|
2967
|
+
const autoDiagrams = this.generateAutomaticDynamicDiagrams();
|
|
2968
|
+
if (autoDiagrams.length > 0) {
|
|
2969
|
+
parts.push(...autoDiagrams);
|
|
2970
|
+
}
|
|
2571
2971
|
const flowsByDomain = new Map;
|
|
2572
2972
|
for (const flow of this.analysis.messageFlows) {
|
|
2573
2973
|
const messageType = flow.messageType.toLowerCase();
|
|
@@ -2596,6 +2996,111 @@ class StructurizrDSLGenerator {
|
|
|
2596
2996
|
|
|
2597
2997
|
`);
|
|
2598
2998
|
}
|
|
2999
|
+
generateAutomaticDynamicDiagrams() {
|
|
3000
|
+
const diagrams = [];
|
|
3001
|
+
const processedHandlers = new Set;
|
|
3002
|
+
const handlersWithRelationships = [];
|
|
3003
|
+
for (const [contextType, contextInfo] of Object.entries(this.analysis.contexts)) {
|
|
3004
|
+
for (const handler of contextInfo.handlers) {
|
|
3005
|
+
if (handler.relationships && handler.relationships.length > 0) {
|
|
3006
|
+
handlersWithRelationships.push({
|
|
3007
|
+
handler,
|
|
3008
|
+
contextType,
|
|
3009
|
+
contextName: this.toId(contextType)
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
const handlerGroups = new Map;
|
|
3015
|
+
for (const hwc of handlersWithRelationships) {
|
|
3016
|
+
const messageType = hwc.handler.messageType.toLowerCase();
|
|
3017
|
+
let category = "general";
|
|
3018
|
+
if (messageType.includes("auth") || messageType.includes("login") || messageType.includes("logout") || messageType.includes("verify") || messageType.includes("register")) {
|
|
3019
|
+
category = "authentication";
|
|
3020
|
+
} else if (messageType.includes("user")) {
|
|
3021
|
+
category = "user";
|
|
3022
|
+
} else if (messageType.includes("todo")) {
|
|
3023
|
+
category = "todo";
|
|
3024
|
+
} else if (messageType.includes("query") || messageType.includes("get") || messageType.includes("fetch") || messageType.includes("list")) {
|
|
3025
|
+
category = "query";
|
|
3026
|
+
} else if (messageType.includes("command") || messageType.includes("create") || messageType.includes("update") || messageType.includes("delete") || messageType.includes("add") || messageType.includes("remove")) {
|
|
3027
|
+
category = "command";
|
|
3028
|
+
}
|
|
3029
|
+
if (!handlerGroups.has(category)) {
|
|
3030
|
+
handlerGroups.set(category, []);
|
|
3031
|
+
}
|
|
3032
|
+
handlerGroups.get(category).push(hwc);
|
|
3033
|
+
}
|
|
3034
|
+
let diagramCount = 0;
|
|
3035
|
+
const maxDiagrams = 5;
|
|
3036
|
+
const totalHandlers = handlersWithRelationships.length;
|
|
3037
|
+
const categoriesWithHandlers = handlerGroups.size;
|
|
3038
|
+
if (totalHandlers <= 5 && categoriesWithHandlers <= 3) {
|
|
3039
|
+
const allHandlers = Array.from(handlerGroups.values()).flat();
|
|
3040
|
+
const diagram = this.generateHandlerFlowDiagram("general", allHandlers);
|
|
3041
|
+
if (diagram) {
|
|
3042
|
+
diagrams.push(diagram);
|
|
3043
|
+
}
|
|
3044
|
+
} else {
|
|
3045
|
+
for (const [category, handlers] of handlerGroups) {
|
|
3046
|
+
if (diagramCount >= maxDiagrams)
|
|
3047
|
+
break;
|
|
3048
|
+
if (handlers.length === 0)
|
|
3049
|
+
continue;
|
|
3050
|
+
const diagram = this.generateHandlerFlowDiagram(category, handlers);
|
|
3051
|
+
if (diagram) {
|
|
3052
|
+
diagrams.push(diagram);
|
|
3053
|
+
diagramCount++;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
return diagrams;
|
|
3058
|
+
}
|
|
3059
|
+
generateHandlerFlowDiagram(category, handlers) {
|
|
3060
|
+
const parts = [];
|
|
3061
|
+
const title = this.getCategoryTitle(category);
|
|
3062
|
+
const description = this.getCategoryDescription(category);
|
|
3063
|
+
const scope = handlers[0]?.contextName ? `extension.${handlers[0].contextName}` : "extension";
|
|
3064
|
+
parts.push(` dynamic ${scope} "${title}" "${description}" {`);
|
|
3065
|
+
let stepCount = 0;
|
|
3066
|
+
for (const { handler, contextName } of handlers) {
|
|
3067
|
+
const handlerComponentId = this.toId(`${handler.messageType}_handler`);
|
|
3068
|
+
for (const rel of handler.relationships) {
|
|
3069
|
+
const toComponent = this.toId(rel.to);
|
|
3070
|
+
parts.push(` ${handlerComponentId} -> ${toComponent} "${rel.description}"`);
|
|
3071
|
+
stepCount++;
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
if (stepCount === 0) {
|
|
3075
|
+
return null;
|
|
3076
|
+
}
|
|
3077
|
+
parts.push(" autoLayout lr");
|
|
3078
|
+
parts.push(" }");
|
|
3079
|
+
return parts.join(`
|
|
3080
|
+
`);
|
|
3081
|
+
}
|
|
3082
|
+
getCategoryTitle(category) {
|
|
3083
|
+
const titles = {
|
|
3084
|
+
authentication: "Authentication Flow",
|
|
3085
|
+
user: "User Management Flow",
|
|
3086
|
+
todo: "Todo Management Flow",
|
|
3087
|
+
query: "Query Processing Flow",
|
|
3088
|
+
command: "Command Processing Flow",
|
|
3089
|
+
general: "Message Processing Flow"
|
|
3090
|
+
};
|
|
3091
|
+
return titles[category] || `${this.capitalize(category)} Flow`;
|
|
3092
|
+
}
|
|
3093
|
+
getCategoryDescription(category) {
|
|
3094
|
+
const descriptions = {
|
|
3095
|
+
authentication: "Shows authentication request processing and service interactions",
|
|
3096
|
+
user: "Shows user management operations and service dependencies",
|
|
3097
|
+
todo: "Shows todo item operations and service dependencies",
|
|
3098
|
+
query: "Shows read operations flow through query handlers and services",
|
|
3099
|
+
command: "Shows write operations flow through command handlers and services",
|
|
3100
|
+
general: "Shows message processing flow through handlers and services"
|
|
3101
|
+
};
|
|
3102
|
+
return descriptions[category] || "Shows message flow through system components";
|
|
3103
|
+
}
|
|
2599
3104
|
generateUserDynamicDiagram(diagram) {
|
|
2600
3105
|
const parts = [];
|
|
2601
3106
|
parts.push(` dynamic ${diagram.scope || "extension"} "${this.escape(diagram.title)}" "${this.escape(diagram.description || "")}" {`);
|
|
@@ -2892,6 +3397,105 @@ class StructurizrDSLGenerator {
|
|
|
2892
3397
|
return parts.join(`
|
|
2893
3398
|
`);
|
|
2894
3399
|
}
|
|
3400
|
+
generateAutomaticGroups(componentDefs) {
|
|
3401
|
+
const groups = [];
|
|
3402
|
+
const assigned = new Set;
|
|
3403
|
+
const authHandlers = componentDefs.filter((comp) => {
|
|
3404
|
+
const type = comp.messageType.toLowerCase();
|
|
3405
|
+
return type.includes("login") || type.includes("logout") || type.includes("auth") || type.includes("verify") || type.includes("register") || type.includes("signup");
|
|
3406
|
+
});
|
|
3407
|
+
if (authHandlers.length > 0) {
|
|
3408
|
+
groups.push({
|
|
3409
|
+
name: "Authentication",
|
|
3410
|
+
components: authHandlers.map((c) => c.id)
|
|
3411
|
+
});
|
|
3412
|
+
authHandlers.forEach((h) => assigned.add(h.id));
|
|
3413
|
+
}
|
|
3414
|
+
const subscriptionHandlers = componentDefs.filter((comp) => {
|
|
3415
|
+
const type = comp.messageType.toLowerCase();
|
|
3416
|
+
return type.includes("subscribe") || type.includes("unsubscribe");
|
|
3417
|
+
});
|
|
3418
|
+
if (subscriptionHandlers.length > 0) {
|
|
3419
|
+
groups.push({
|
|
3420
|
+
name: "Subscriptions",
|
|
3421
|
+
components: subscriptionHandlers.map((c) => c.id)
|
|
3422
|
+
});
|
|
3423
|
+
subscriptionHandlers.forEach((h) => assigned.add(h.id));
|
|
3424
|
+
}
|
|
3425
|
+
const entityGroups = new Map;
|
|
3426
|
+
for (const comp of componentDefs) {
|
|
3427
|
+
if (assigned.has(comp.id))
|
|
3428
|
+
continue;
|
|
3429
|
+
const type = comp.messageType.toLowerCase();
|
|
3430
|
+
if (type === "connection" || type === "message" || type === "close" || type === "error") {
|
|
3431
|
+
continue;
|
|
3432
|
+
}
|
|
3433
|
+
let entity = null;
|
|
3434
|
+
const underscoreMatch = type.match(/^([a-z]+)_(add|create|update|delete|remove|get|fetch|load|list|query)/);
|
|
3435
|
+
if (underscoreMatch) {
|
|
3436
|
+
entity = underscoreMatch[1];
|
|
3437
|
+
}
|
|
3438
|
+
if (!entity) {
|
|
3439
|
+
const camelMatch = type.match(/(add|create|update|delete|remove|get|fetch|load|list|query)([a-z]+)/i);
|
|
3440
|
+
if (camelMatch) {
|
|
3441
|
+
entity = camelMatch[2].toLowerCase();
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
if (!entity && type.match(/^[a-z]+$/)) {
|
|
3445
|
+
entity = type;
|
|
3446
|
+
}
|
|
3447
|
+
if (entity) {
|
|
3448
|
+
if (!entityGroups.has(entity)) {
|
|
3449
|
+
entityGroups.set(entity, []);
|
|
3450
|
+
}
|
|
3451
|
+
entityGroups.get(entity).push(comp.id);
|
|
3452
|
+
assigned.add(comp.id);
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
for (const [entity, componentIds] of entityGroups) {
|
|
3456
|
+
if (componentIds.length >= 2) {
|
|
3457
|
+
const entityName = entity.charAt(0).toUpperCase() + entity.slice(1);
|
|
3458
|
+
groups.push({
|
|
3459
|
+
name: `${entityName} Management`,
|
|
3460
|
+
components: componentIds
|
|
3461
|
+
});
|
|
3462
|
+
} else {
|
|
3463
|
+
componentIds.forEach((id) => assigned.delete(id));
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
const queryHandlers = componentDefs.filter((comp) => {
|
|
3467
|
+
if (assigned.has(comp.id))
|
|
3468
|
+
return false;
|
|
3469
|
+
const type = comp.messageType.toLowerCase();
|
|
3470
|
+
return type.includes("query") || type.includes("get") || type.includes("fetch") || type.includes("load") || type.includes("list") || type.includes("read");
|
|
3471
|
+
});
|
|
3472
|
+
const commandHandlers = componentDefs.filter((comp) => {
|
|
3473
|
+
if (assigned.has(comp.id))
|
|
3474
|
+
return false;
|
|
3475
|
+
const type = comp.messageType.toLowerCase();
|
|
3476
|
+
return type.includes("command") || type.includes("add") || type.includes("create") || type.includes("update") || type.includes("delete") || type.includes("remove") || type.includes("set") || type.includes("clear");
|
|
3477
|
+
});
|
|
3478
|
+
if (queryHandlers.length >= 2) {
|
|
3479
|
+
groups.push({
|
|
3480
|
+
name: "Query Handlers",
|
|
3481
|
+
components: queryHandlers.map((c) => c.id)
|
|
3482
|
+
});
|
|
3483
|
+
queryHandlers.forEach((h) => assigned.add(h.id));
|
|
3484
|
+
}
|
|
3485
|
+
if (commandHandlers.length >= 2) {
|
|
3486
|
+
groups.push({
|
|
3487
|
+
name: "Command Handlers",
|
|
3488
|
+
components: commandHandlers.map((c) => c.id)
|
|
3489
|
+
});
|
|
3490
|
+
commandHandlers.forEach((h) => assigned.add(h.id));
|
|
3491
|
+
}
|
|
3492
|
+
const groupedCount = assigned.size;
|
|
3493
|
+
const totalCount = componentDefs.length;
|
|
3494
|
+
if (groupedCount >= totalCount * 0.5 || groups.length >= 2) {
|
|
3495
|
+
return groups;
|
|
3496
|
+
}
|
|
3497
|
+
return [];
|
|
3498
|
+
}
|
|
2895
3499
|
generateGroupedComponents(componentDefs, groups) {
|
|
2896
3500
|
const parts = [];
|
|
2897
3501
|
const assignedComponents = new Set;
|
|
@@ -3399,4 +4003,4 @@ Stack trace:`, COLORS.gray));
|
|
|
3399
4003
|
process.exit(1);
|
|
3400
4004
|
});
|
|
3401
4005
|
|
|
3402
|
-
//# debugId=
|
|
4006
|
+
//# debugId=5BFF854178F00CEB64756E2164756E21
|