@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.
@@ -723,16 +723,353 @@ import * as path3 from "node:path";
723
723
  import { Project as Project2 } from "ts-morph";
724
724
 
725
725
  // vendor/analysis/src/extract/handlers.ts
726
- import { Project, SyntaxKind, Node } from "ts-morph";
726
+ import { Project, SyntaxKind as SyntaxKind2, Node as Node2 } from "ts-morph";
727
727
 
728
+ // vendor/analysis/src/extract/relationships.ts
729
+ import { Node } from "ts-morph";
730
+
731
+ class RelationshipExtractor {
732
+ extractFromHandler(handlerNode, sourceFile, handlerName) {
733
+ const relationships = [];
734
+ const visited = new Set;
735
+ this.extractFromNode(handlerNode, sourceFile, handlerName, relationships, visited);
736
+ return this.deduplicateRelationships(relationships);
737
+ }
738
+ extractFromNode(node, sourceFile, handlerName, relationships, visited) {
739
+ node.forEachDescendant((descendant) => {
740
+ if (Node.isCallExpression(descendant)) {
741
+ const expr = descendant.getExpression();
742
+ if (Node.isIdentifier(expr)) {
743
+ const functionName = expr.getText();
744
+ let functionDecl = sourceFile.getFunction(functionName);
745
+ let targetSourceFile = sourceFile;
746
+ if (!functionDecl) {
747
+ const resolved = this.resolveImportedFunction(functionName, sourceFile);
748
+ if (resolved) {
749
+ functionDecl = resolved.functionDecl;
750
+ targetSourceFile = resolved.sourceFile;
751
+ }
752
+ }
753
+ if (functionDecl && !visited.has(functionName)) {
754
+ visited.add(functionName);
755
+ const body = functionDecl.getBody();
756
+ if (body) {
757
+ this.extractFromNode(body, targetSourceFile, handlerName, relationships, visited);
758
+ }
759
+ return;
760
+ }
761
+ if (!functionDecl) {
762
+ const componentFromName = this.inferComponentFromFunctionName(functionName);
763
+ if (componentFromName) {
764
+ relationships.push({
765
+ from: this.toComponentId(handlerName),
766
+ to: componentFromName,
767
+ description: `Calls ${functionName}()`,
768
+ technology: "Function Call",
769
+ confidence: "medium",
770
+ evidence: [`Function call: ${functionName}`]
771
+ });
772
+ return;
773
+ }
774
+ }
775
+ }
776
+ if (Node.isPropertyAccessExpression(expr)) {
777
+ const rel2 = this.extractFromPropertyAccess(expr, handlerName);
778
+ if (rel2) {
779
+ relationships.push(rel2);
780
+ return;
781
+ }
782
+ }
783
+ const rel = this.extractFromFunctionCall(descendant, handlerName, sourceFile);
784
+ if (rel) {
785
+ relationships.push(rel);
786
+ }
787
+ }
788
+ if (Node.isAwaitExpression(descendant)) {
789
+ const rel = this.extractFromDatabaseCall(descendant, handlerName);
790
+ if (rel) {
791
+ relationships.push(rel);
792
+ }
793
+ }
794
+ if (Node.isCallExpression(descendant) && descendant.getExpression().getText() === "fetch") {
795
+ const rel = this.extractFromFetchCall(descendant, handlerName);
796
+ if (rel) {
797
+ relationships.push(rel);
798
+ }
799
+ }
800
+ });
801
+ }
802
+ extractFromFunctionCall(callExpr, handlerName, sourceFile) {
803
+ const expr = callExpr.getExpression();
804
+ const exprText = expr.getText();
805
+ const skipList = [
806
+ "console.",
807
+ "JSON.",
808
+ "Math.",
809
+ "Object.",
810
+ "Array.",
811
+ "String.",
812
+ "Number.",
813
+ "Date.",
814
+ "Promise.",
815
+ "setTimeout",
816
+ "setInterval",
817
+ "clearTimeout",
818
+ "clearInterval"
819
+ ];
820
+ if (skipList.some((skip) => exprText.startsWith(skip))) {
821
+ return null;
822
+ }
823
+ let functionName = exprText;
824
+ let targetComponent = null;
825
+ if (Node.isPropertyAccessExpression(expr)) {
826
+ const objectExpr = expr.getExpression();
827
+ const objectName = objectExpr.getText();
828
+ const methodName = expr.getName();
829
+ targetComponent = this.inferComponentFromCall(objectName, methodName);
830
+ if (!targetComponent) {
831
+ return null;
832
+ }
833
+ functionName = methodName;
834
+ } else {
835
+ targetComponent = this.resolveComponentFromImport(exprText, sourceFile);
836
+ if (!targetComponent) {
837
+ return null;
838
+ }
839
+ }
840
+ return {
841
+ from: this.toComponentId(handlerName),
842
+ to: targetComponent,
843
+ description: `Calls ${functionName}()`,
844
+ technology: "Function Call",
845
+ confidence: "high",
846
+ evidence: [`Function call: ${exprText}`]
847
+ };
848
+ }
849
+ extractFromPropertyAccess(propAccess, handlerName) {
850
+ if (!Node.isPropertyAccessExpression(propAccess)) {
851
+ return null;
852
+ }
853
+ const fullChain = propAccess.getText();
854
+ const methodName = propAccess.getName();
855
+ let rootObject = propAccess.getExpression();
856
+ while (Node.isPropertyAccessExpression(rootObject)) {
857
+ rootObject = rootObject.getExpression();
858
+ }
859
+ const objectName = rootObject.getText();
860
+ const targetComponent = this.inferComponentFromCall(objectName, methodName);
861
+ if (!targetComponent) {
862
+ return null;
863
+ }
864
+ return {
865
+ from: this.toComponentId(handlerName),
866
+ to: targetComponent,
867
+ description: `Calls ${methodName}()`,
868
+ technology: "Function Call",
869
+ confidence: "high",
870
+ evidence: [`Property access: ${fullChain}`]
871
+ };
872
+ }
873
+ extractFromDatabaseCall(awaitExpr, handlerName) {
874
+ if (!Node.isAwaitExpression(awaitExpr)) {
875
+ return null;
876
+ }
877
+ const innerExpr = awaitExpr.getExpression();
878
+ if (!Node.isCallExpression(innerExpr)) {
879
+ return null;
880
+ }
881
+ const callExpr = innerExpr.getExpression().getText();
882
+ 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")) {
883
+ const operation = this.inferDatabaseOperation(callExpr);
884
+ return {
885
+ from: this.toComponentId(handlerName),
886
+ to: "database",
887
+ description: operation,
888
+ technology: "SQL",
889
+ confidence: "high",
890
+ evidence: [`Database call: ${callExpr}`]
891
+ };
892
+ }
893
+ return null;
894
+ }
895
+ extractFromFetchCall(callExpr, handlerName) {
896
+ const args = callExpr.getArguments();
897
+ if (args.length === 0) {
898
+ return null;
899
+ }
900
+ const urlArg = args[0].getText();
901
+ let apiName = "external_api";
902
+ if (urlArg.includes("openai")) {
903
+ apiName = "openai_api";
904
+ } else if (urlArg.includes("anthropic")) {
905
+ apiName = "anthropic_api";
906
+ }
907
+ return {
908
+ from: this.toComponentId(handlerName),
909
+ to: apiName,
910
+ description: "Calls external API",
911
+ technology: "HTTP/REST",
912
+ confidence: "high",
913
+ evidence: [`fetch() call to: ${urlArg}`]
914
+ };
915
+ }
916
+ inferComponentFromCall(objectName, methodName) {
917
+ const mappings = {
918
+ db: "db_client",
919
+ database: "database",
920
+ repos: "repositories",
921
+ repository: "repositories",
922
+ repositories: "repositories",
923
+ cache: "cache",
924
+ storage: "storage",
925
+ ai: "ai_service",
926
+ auth: "auth_service",
927
+ authservice: "auth_service",
928
+ user: "user_service",
929
+ userservice: "user_service",
930
+ logger: "logger",
931
+ queue: "queue_service"
932
+ };
933
+ const normalized = objectName.toLowerCase();
934
+ return mappings[normalized] || null;
935
+ }
936
+ inferComponentFromFunctionName(functionName) {
937
+ const normalized = functionName.toLowerCase();
938
+ if (normalized.startsWith("get") || normalized.startsWith("create")) {
939
+ const suffix = functionName.substring(normalized.startsWith("get") ? 3 : 6);
940
+ const suffixLower = suffix.toLowerCase();
941
+ if (suffixLower.includes("database") || suffixLower === "db" || suffixLower.includes("dbconnection") || suffixLower.includes("connection")) {
942
+ return "db_client";
943
+ }
944
+ if (suffixLower.includes("repositories") || suffixLower.includes("repos") || suffixLower.includes("repository")) {
945
+ return "repositories";
946
+ }
947
+ if (suffixLower.includes("service")) {
948
+ const serviceMatch = suffix.match(/^(.+?)Service/i);
949
+ if (serviceMatch) {
950
+ return this.toComponentId(`${serviceMatch[1]}_service`);
951
+ }
952
+ return "service";
953
+ }
954
+ if (suffixLower.includes("cache")) {
955
+ return "cache";
956
+ }
957
+ if (suffixLower.includes("storage")) {
958
+ return "storage";
959
+ }
960
+ if (suffixLower.includes("ai") || suffixLower.includes("llm")) {
961
+ return "ai_service";
962
+ }
963
+ if (suffixLower.includes("logger")) {
964
+ return "logger";
965
+ }
966
+ }
967
+ if (normalized.startsWith("init") || normalized.startsWith("setup")) {
968
+ const suffix = functionName.substring(normalized.startsWith("init") ? 4 : 5);
969
+ const suffixLower = suffix.toLowerCase();
970
+ if (suffixLower.includes("database") || suffixLower === "db") {
971
+ return "db_client";
972
+ }
973
+ if (suffixLower.includes("cache")) {
974
+ return "cache";
975
+ }
976
+ }
977
+ return null;
978
+ }
979
+ resolveComponentFromImport(functionName, sourceFile) {
980
+ for (const importDecl of sourceFile.getImportDeclarations()) {
981
+ const namedImports = importDecl.getNamedImports();
982
+ for (const namedImport of namedImports) {
983
+ if (namedImport.getName() === functionName) {
984
+ const modulePath = importDecl.getModuleSpecifierValue();
985
+ if (modulePath.includes("/db/") || modulePath.includes("/database/")) {
986
+ return "db_client";
987
+ }
988
+ if (modulePath.includes("/repos") || modulePath.includes("/repositories")) {
989
+ return "repositories";
990
+ }
991
+ if (modulePath.includes("/service") || modulePath.includes("/services")) {
992
+ const match = modulePath.match(/\/([^/]+)\.ts$/);
993
+ if (match) {
994
+ return this.toComponentId(match[1]);
995
+ }
996
+ }
997
+ }
998
+ }
999
+ }
1000
+ return null;
1001
+ }
1002
+ resolveImportedFunction(functionName, sourceFile) {
1003
+ try {
1004
+ for (const importDecl of sourceFile.getImportDeclarations()) {
1005
+ const namedImports = importDecl.getNamedImports();
1006
+ for (const namedImport of namedImports) {
1007
+ if (namedImport.getName() === functionName) {
1008
+ const moduleSpecifier = importDecl.getModuleSpecifierSourceFile();
1009
+ if (!moduleSpecifier)
1010
+ continue;
1011
+ const functionDecl = moduleSpecifier.getFunction(functionName);
1012
+ if (functionDecl) {
1013
+ return {
1014
+ functionDecl,
1015
+ sourceFile: moduleSpecifier
1016
+ };
1017
+ }
1018
+ const variableDecl = moduleSpecifier.getVariableDeclaration(functionName);
1019
+ if (variableDecl) {
1020
+ const initializer = variableDecl.getInitializer();
1021
+ if (initializer && (Node.isArrowFunction(initializer) || Node.isFunctionExpression(initializer))) {
1022
+ return {
1023
+ functionDecl: initializer,
1024
+ sourceFile: moduleSpecifier
1025
+ };
1026
+ }
1027
+ }
1028
+ }
1029
+ }
1030
+ }
1031
+ } catch (error) {
1032
+ return null;
1033
+ }
1034
+ return null;
1035
+ }
1036
+ inferDatabaseOperation(callExpr) {
1037
+ if (callExpr.includes("query") || callExpr.includes("select")) {
1038
+ return "Reads from database";
1039
+ }
1040
+ if (callExpr.includes("execute") || callExpr.includes("insert") || callExpr.includes("update") || callExpr.includes("delete")) {
1041
+ return "Writes to database";
1042
+ }
1043
+ return "Accesses database";
1044
+ }
1045
+ toComponentId(name) {
1046
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
1047
+ }
1048
+ deduplicateRelationships(relationships) {
1049
+ const seen = new Set;
1050
+ const unique = [];
1051
+ for (const rel of relationships) {
1052
+ const key = `${rel.from}->${rel.to}`;
1053
+ if (!seen.has(key)) {
1054
+ seen.add(key);
1055
+ unique.push(rel);
1056
+ }
1057
+ }
1058
+ return unique;
1059
+ }
1060
+ }
1061
+
1062
+ // vendor/analysis/src/extract/handlers.ts
728
1063
  class HandlerExtractor {
729
1064
  project;
730
1065
  typeGuardCache;
1066
+ relationshipExtractor;
731
1067
  constructor(tsConfigPath) {
732
1068
  this.project = new Project({
733
1069
  tsConfigFilePath: tsConfigPath
734
1070
  });
735
1071
  this.typeGuardCache = new WeakMap;
1072
+ this.relationshipExtractor = new RelationshipExtractor;
736
1073
  }
737
1074
  extractHandlers() {
738
1075
  const handlers = [];
@@ -766,9 +1103,9 @@ class HandlerExtractor {
766
1103
  const filePath = sourceFile.getFilePath();
767
1104
  const context = this.inferContext(filePath);
768
1105
  sourceFile.forEachDescendant((node) => {
769
- if (Node.isCallExpression(node)) {
1106
+ if (Node2.isCallExpression(node)) {
770
1107
  const expression = node.getExpression();
771
- if (Node.isPropertyAccessExpression(expression)) {
1108
+ if (Node2.isPropertyAccessExpression(expression)) {
772
1109
  const methodName = expression.getName();
773
1110
  if (methodName === "on" || methodName === "addEventListener") {
774
1111
  const handler = this.extractHandler(node, context, filePath);
@@ -778,17 +1115,17 @@ class HandlerExtractor {
778
1115
  }
779
1116
  }
780
1117
  }
781
- if (Node.isSwitchStatement(node)) {
1118
+ if (Node2.isSwitchStatement(node)) {
782
1119
  const switchHandlers = this.extractSwitchCaseHandlers(node, context, filePath);
783
1120
  handlers.push(...switchHandlers);
784
1121
  }
785
- if (Node.isVariableDeclaration(node)) {
1122
+ if (Node2.isVariableDeclaration(node)) {
786
1123
  const mapHandlers = this.extractHandlerMapPattern(node, context, filePath);
787
1124
  handlers.push(...mapHandlers);
788
1125
  }
789
- if (Node.isIfStatement(node)) {
1126
+ if (Node2.isIfStatement(node)) {
790
1127
  const parent = node.getParent();
791
- const isElseIf = parent && Node.isIfStatement(parent);
1128
+ const isElseIf = parent && Node2.isIfStatement(parent);
792
1129
  if (!isElseIf) {
793
1130
  const typeGuardHandlers = this.extractTypeGuardHandlers(node, context, filePath);
794
1131
  handlers.push(...typeGuardHandlers);
@@ -804,9 +1141,9 @@ class HandlerExtractor {
804
1141
  }
805
1142
  const messageTypeArg = args[0];
806
1143
  let messageType = null;
807
- if (Node.isStringLiteral(messageTypeArg)) {
1144
+ if (Node2.isStringLiteral(messageTypeArg)) {
808
1145
  messageType = messageTypeArg.getLiteralValue();
809
- } else if (Node.isTemplateExpression(messageTypeArg)) {
1146
+ } else if (Node2.isTemplateExpression(messageTypeArg)) {
810
1147
  messageType = messageTypeArg.getText().replace(/[`'"]/g, "");
811
1148
  }
812
1149
  if (!messageType) {
@@ -816,11 +1153,20 @@ class HandlerExtractor {
816
1153
  const assignments = [];
817
1154
  const preconditions = [];
818
1155
  const postconditions = [];
819
- if (Node.isArrowFunction(handlerArg) || Node.isFunctionExpression(handlerArg)) {
1156
+ if (Node2.isArrowFunction(handlerArg) || Node2.isFunctionExpression(handlerArg)) {
820
1157
  this.extractAssignments(handlerArg, assignments);
821
1158
  this.extractVerificationConditions(handlerArg, preconditions, postconditions);
822
1159
  }
823
1160
  const line = callExpr.getStartLineNumber();
1161
+ const sourceFile = callExpr.getSourceFile();
1162
+ const handlerName = `${messageType}_handler`;
1163
+ let relationships = undefined;
1164
+ if (Node2.isArrowFunction(handlerArg) || Node2.isFunctionExpression(handlerArg)) {
1165
+ const detectedRelationships = this.relationshipExtractor.extractFromHandler(handlerArg, sourceFile, handlerName);
1166
+ if (detectedRelationships.length > 0) {
1167
+ relationships = detectedRelationships;
1168
+ }
1169
+ }
824
1170
  return {
825
1171
  messageType,
826
1172
  node: context,
@@ -830,17 +1176,18 @@ class HandlerExtractor {
830
1176
  location: {
831
1177
  file: filePath,
832
1178
  line
833
- }
1179
+ },
1180
+ relationships
834
1181
  };
835
1182
  }
836
1183
  extractAssignments(funcNode, assignments) {
837
1184
  funcNode.forEachDescendant((node) => {
838
- if (Node.isBinaryExpression(node)) {
1185
+ if (Node2.isBinaryExpression(node)) {
839
1186
  const operator = node.getOperatorToken().getText();
840
1187
  if (operator === "=") {
841
1188
  const left = node.getLeft();
842
1189
  const right = node.getRight();
843
- if (Node.isPropertyAccessExpression(left)) {
1190
+ if (Node2.isPropertyAccessExpression(left)) {
844
1191
  const fieldPath = this.getPropertyPath(left);
845
1192
  if (fieldPath.startsWith("state.")) {
846
1193
  const field = fieldPath.substring(6);
@@ -859,13 +1206,13 @@ class HandlerExtractor {
859
1206
  }
860
1207
  extractVerificationConditions(funcNode, preconditions, postconditions) {
861
1208
  const body = funcNode.getBody();
862
- const statements = Node.isBlock(body) ? body.getStatements() : [body];
1209
+ const statements = Node2.isBlock(body) ? body.getStatements() : [body];
863
1210
  statements.forEach((statement, index) => {
864
- if (Node.isExpressionStatement(statement)) {
1211
+ if (Node2.isExpressionStatement(statement)) {
865
1212
  const expr = statement.getExpression();
866
- if (Node.isCallExpression(expr)) {
1213
+ if (Node2.isCallExpression(expr)) {
867
1214
  const callee = expr.getExpression();
868
- if (Node.isIdentifier(callee)) {
1215
+ if (Node2.isIdentifier(callee)) {
869
1216
  const functionName = callee.getText();
870
1217
  if (functionName === "requires") {
871
1218
  const condition = this.extractCondition(expr);
@@ -891,7 +1238,7 @@ class HandlerExtractor {
891
1238
  const conditionArg = args[0];
892
1239
  const expression = conditionArg.getText();
893
1240
  let message;
894
- if (args.length >= 2 && Node.isStringLiteral(args[1])) {
1241
+ if (args.length >= 2 && Node2.isStringLiteral(args[1])) {
895
1242
  message = args[1].getLiteralValue();
896
1243
  }
897
1244
  const line = callExpr.getStartLineNumber();
@@ -908,29 +1255,29 @@ class HandlerExtractor {
908
1255
  getPropertyPath(node) {
909
1256
  const parts = [];
910
1257
  let current = node;
911
- while (Node.isPropertyAccessExpression(current)) {
1258
+ while (Node2.isPropertyAccessExpression(current)) {
912
1259
  parts.unshift(current.getName());
913
1260
  current = current.getExpression();
914
1261
  }
915
- if (Node.isIdentifier(current)) {
1262
+ if (Node2.isIdentifier(current)) {
916
1263
  parts.unshift(current.getText());
917
1264
  }
918
1265
  return parts.join(".");
919
1266
  }
920
1267
  extractValue(node) {
921
- if (Node.isStringLiteral(node)) {
1268
+ if (Node2.isStringLiteral(node)) {
922
1269
  return node.getLiteralValue();
923
1270
  }
924
- if (Node.isNumericLiteral(node)) {
1271
+ if (Node2.isNumericLiteral(node)) {
925
1272
  return node.getLiteralValue();
926
1273
  }
927
- if (node.getKind() === SyntaxKind.TrueKeyword) {
1274
+ if (node.getKind() === SyntaxKind2.TrueKeyword) {
928
1275
  return true;
929
1276
  }
930
- if (node.getKind() === SyntaxKind.FalseKeyword) {
1277
+ if (node.getKind() === SyntaxKind2.FalseKeyword) {
931
1278
  return false;
932
1279
  }
933
- if (node.getKind() === SyntaxKind.NullKeyword) {
1280
+ if (node.getKind() === SyntaxKind2.NullKeyword) {
934
1281
  return null;
935
1282
  }
936
1283
  return;
@@ -945,10 +1292,10 @@ class HandlerExtractor {
945
1292
  }
946
1293
  const caseClauses = switchNode.getClauses();
947
1294
  for (const clause of caseClauses) {
948
- if (Node.isCaseClause(clause)) {
1295
+ if (Node2.isCaseClause(clause)) {
949
1296
  const caseExpr = clause.getExpression();
950
1297
  let messageType = null;
951
- if (Node.isStringLiteral(caseExpr)) {
1298
+ if (Node2.isStringLiteral(caseExpr)) {
952
1299
  messageType = caseExpr.getLiteralValue();
953
1300
  }
954
1301
  if (messageType) {
@@ -971,7 +1318,7 @@ class HandlerExtractor {
971
1318
  const handlers = [];
972
1319
  try {
973
1320
  const initializer = varDecl.getInitializer();
974
- if (!initializer || !Node.isObjectLiteralExpression(initializer)) {
1321
+ if (!initializer || !Node2.isObjectLiteralExpression(initializer)) {
975
1322
  return handlers;
976
1323
  }
977
1324
  const varName = varDecl.getName().toLowerCase();
@@ -980,12 +1327,12 @@ class HandlerExtractor {
980
1327
  }
981
1328
  const properties = initializer.getProperties();
982
1329
  for (const prop of properties) {
983
- if (Node.isPropertyAssignment(prop)) {
1330
+ if (Node2.isPropertyAssignment(prop)) {
984
1331
  const nameNode = prop.getNameNode();
985
1332
  let messageType = null;
986
- if (Node.isStringLiteral(nameNode)) {
1333
+ if (Node2.isStringLiteral(nameNode)) {
987
1334
  messageType = nameNode.getLiteralValue();
988
- } else if (Node.isIdentifier(nameNode)) {
1335
+ } else if (Node2.isIdentifier(nameNode)) {
989
1336
  messageType = nameNode.getText();
990
1337
  }
991
1338
  if (messageType) {
@@ -1032,7 +1379,7 @@ class HandlerExtractor {
1032
1379
  }
1033
1380
  }
1034
1381
  const elseStatement = currentIf.getElseStatement();
1035
- if (elseStatement && Node.isIfStatement(elseStatement)) {
1382
+ if (elseStatement && Node2.isIfStatement(elseStatement)) {
1036
1383
  currentIf = elseStatement;
1037
1384
  } else {
1038
1385
  break;
@@ -1048,12 +1395,12 @@ class HandlerExtractor {
1048
1395
  extractHandlerFromIfClause(ifNode, typeGuards, context, filePath) {
1049
1396
  try {
1050
1397
  const condition = ifNode.getExpression();
1051
- if (!Node.isCallExpression(condition)) {
1398
+ if (!Node2.isCallExpression(condition)) {
1052
1399
  return null;
1053
1400
  }
1054
1401
  const funcExpr = condition.getExpression();
1055
1402
  let funcName;
1056
- if (Node.isIdentifier(funcExpr)) {
1403
+ if (Node2.isIdentifier(funcExpr)) {
1057
1404
  funcName = funcExpr.getText();
1058
1405
  }
1059
1406
  if (process.env.POLLY_DEBUG && funcName) {
@@ -1065,7 +1412,7 @@ class HandlerExtractor {
1065
1412
  if (process.env.POLLY_DEBUG) {
1066
1413
  console.log(`[DEBUG] Found in local type guards: ${funcName} → ${messageType}`);
1067
1414
  }
1068
- } else if (Node.isIdentifier(funcExpr)) {
1415
+ } else if (Node2.isIdentifier(funcExpr)) {
1069
1416
  if (process.env.POLLY_DEBUG) {
1070
1417
  console.log(`[DEBUG] Not found locally, trying import resolution for: ${funcName}`);
1071
1418
  }
@@ -1078,13 +1425,24 @@ class HandlerExtractor {
1078
1425
  return null;
1079
1426
  }
1080
1427
  const line = ifNode.getStartLineNumber();
1428
+ const sourceFile = ifNode.getSourceFile();
1429
+ const handlerName = `${messageType}_handler`;
1430
+ let relationships = undefined;
1431
+ const thenStatement = ifNode.getThenStatement();
1432
+ if (thenStatement) {
1433
+ const detectedRelationships = this.relationshipExtractor.extractFromHandler(thenStatement, sourceFile, handlerName);
1434
+ if (detectedRelationships.length > 0) {
1435
+ relationships = detectedRelationships;
1436
+ }
1437
+ }
1081
1438
  return {
1082
1439
  messageType,
1083
1440
  node: context,
1084
1441
  assignments: [],
1085
1442
  preconditions: [],
1086
1443
  postconditions: [],
1087
- location: { file: filePath, line }
1444
+ location: { file: filePath, line },
1445
+ relationships
1088
1446
  };
1089
1447
  } catch (error) {
1090
1448
  return null;
@@ -1093,20 +1451,20 @@ class HandlerExtractor {
1093
1451
  findTypePredicateFunctions(sourceFile) {
1094
1452
  const typeGuards = new Map;
1095
1453
  sourceFile.forEachDescendant((node) => {
1096
- if (Node.isFunctionDeclaration(node) || Node.isFunctionExpression(node) || Node.isArrowFunction(node)) {
1454
+ if (Node2.isFunctionDeclaration(node) || Node2.isFunctionExpression(node) || Node2.isArrowFunction(node)) {
1097
1455
  const returnTypeNode = node.getReturnTypeNode();
1098
- if (returnTypeNode && Node.isTypePredicate(returnTypeNode)) {
1456
+ if (returnTypeNode && Node2.isTypePredicate(returnTypeNode)) {
1099
1457
  let functionName;
1100
- if (Node.isFunctionDeclaration(node)) {
1458
+ if (Node2.isFunctionDeclaration(node)) {
1101
1459
  functionName = node.getName();
1102
- } else if (Node.isFunctionExpression(node)) {
1460
+ } else if (Node2.isFunctionExpression(node)) {
1103
1461
  const parent = node.getParent();
1104
- if (Node.isVariableDeclaration(parent)) {
1462
+ if (Node2.isVariableDeclaration(parent)) {
1105
1463
  functionName = parent.getName();
1106
1464
  }
1107
- } else if (Node.isArrowFunction(node)) {
1465
+ } else if (Node2.isArrowFunction(node)) {
1108
1466
  const parent = node.getParent();
1109
- if (Node.isVariableDeclaration(parent)) {
1467
+ if (Node2.isVariableDeclaration(parent)) {
1110
1468
  functionName = parent.getName();
1111
1469
  }
1112
1470
  }
@@ -1147,15 +1505,15 @@ class HandlerExtractor {
1147
1505
  return null;
1148
1506
  }
1149
1507
  for (const def of definitions) {
1150
- if (Node.isFunctionDeclaration(def) || Node.isFunctionExpression(def) || Node.isArrowFunction(def)) {
1508
+ if (Node2.isFunctionDeclaration(def) || Node2.isFunctionExpression(def) || Node2.isArrowFunction(def)) {
1151
1509
  const returnTypeNode = def.getReturnTypeNode();
1152
1510
  if (process.env.POLLY_DEBUG) {
1153
1511
  const returnType = def.getReturnType().getText();
1154
1512
  console.log(`[DEBUG] Function ${funcName} return type (resolved): ${returnType}`);
1155
1513
  console.log(`[DEBUG] Has return type node: ${!!returnTypeNode}`);
1156
- console.log(`[DEBUG] Is type predicate node: ${returnTypeNode && Node.isTypePredicate(returnTypeNode)}`);
1514
+ console.log(`[DEBUG] Is type predicate node: ${returnTypeNode && Node2.isTypePredicate(returnTypeNode)}`);
1157
1515
  }
1158
- if (returnTypeNode && Node.isTypePredicate(returnTypeNode)) {
1516
+ if (returnTypeNode && Node2.isTypePredicate(returnTypeNode)) {
1159
1517
  const typeNode = returnTypeNode.getTypeNode();
1160
1518
  if (typeNode) {
1161
1519
  const typeName = typeNode.getText();
@@ -2385,4 +2743,4 @@ Stack trace:`, COLORS.gray));
2385
2743
  process.exit(1);
2386
2744
  });
2387
2745
 
2388
- //# debugId=EE34F5E282168E7C64756E2164756E21
2746
+ //# debugId=C40ED4F55177D0FC64756E2164756E21