@fairfox/polly 0.4.2 → 0.5.0
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,250 @@ 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
|
+
const functionDecl = sourceFile.getFunction(functionName);
|
|
745
|
+
if (functionDecl && !visited.has(functionName)) {
|
|
746
|
+
visited.add(functionName);
|
|
747
|
+
const body = functionDecl.getBody();
|
|
748
|
+
if (body) {
|
|
749
|
+
this.extractFromNode(body, sourceFile, handlerName, relationships, visited);
|
|
750
|
+
}
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (Node.isPropertyAccessExpression(expr)) {
|
|
755
|
+
const rel2 = this.extractFromPropertyAccess(expr, handlerName);
|
|
756
|
+
if (rel2) {
|
|
757
|
+
relationships.push(rel2);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
const rel = this.extractFromFunctionCall(descendant, handlerName, sourceFile);
|
|
762
|
+
if (rel) {
|
|
763
|
+
relationships.push(rel);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (Node.isAwaitExpression(descendant)) {
|
|
767
|
+
const rel = this.extractFromDatabaseCall(descendant, handlerName);
|
|
768
|
+
if (rel) {
|
|
769
|
+
relationships.push(rel);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (Node.isCallExpression(descendant) && descendant.getExpression().getText() === "fetch") {
|
|
773
|
+
const rel = this.extractFromFetchCall(descendant, handlerName);
|
|
774
|
+
if (rel) {
|
|
775
|
+
relationships.push(rel);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
extractFromFunctionCall(callExpr, handlerName, sourceFile) {
|
|
781
|
+
const expr = callExpr.getExpression();
|
|
782
|
+
const exprText = expr.getText();
|
|
783
|
+
const skipList = [
|
|
784
|
+
"console.",
|
|
785
|
+
"JSON.",
|
|
786
|
+
"Math.",
|
|
787
|
+
"Object.",
|
|
788
|
+
"Array.",
|
|
789
|
+
"String.",
|
|
790
|
+
"Number.",
|
|
791
|
+
"Date.",
|
|
792
|
+
"Promise.",
|
|
793
|
+
"setTimeout",
|
|
794
|
+
"setInterval",
|
|
795
|
+
"clearTimeout",
|
|
796
|
+
"clearInterval"
|
|
797
|
+
];
|
|
798
|
+
if (skipList.some((skip) => exprText.startsWith(skip))) {
|
|
799
|
+
return null;
|
|
800
|
+
}
|
|
801
|
+
let functionName = exprText;
|
|
802
|
+
let targetComponent = null;
|
|
803
|
+
if (Node.isPropertyAccessExpression(expr)) {
|
|
804
|
+
const objectExpr = expr.getExpression();
|
|
805
|
+
const objectName = objectExpr.getText();
|
|
806
|
+
const methodName = expr.getName();
|
|
807
|
+
targetComponent = this.inferComponentFromCall(objectName, methodName);
|
|
808
|
+
if (!targetComponent) {
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
functionName = methodName;
|
|
812
|
+
} else {
|
|
813
|
+
targetComponent = this.resolveComponentFromImport(exprText, sourceFile);
|
|
814
|
+
if (!targetComponent) {
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
from: this.toComponentId(handlerName),
|
|
820
|
+
to: targetComponent,
|
|
821
|
+
description: `Calls ${functionName}()`,
|
|
822
|
+
technology: "Function Call",
|
|
823
|
+
confidence: "high",
|
|
824
|
+
evidence: [`Function call: ${exprText}`]
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
extractFromPropertyAccess(propAccess, handlerName) {
|
|
828
|
+
if (!Node.isPropertyAccessExpression(propAccess)) {
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
const fullChain = propAccess.getText();
|
|
832
|
+
const objectExpr = propAccess.getExpression();
|
|
833
|
+
const objectName = objectExpr.getText();
|
|
834
|
+
const methodName = propAccess.getName();
|
|
835
|
+
const targetComponent = this.inferComponentFromCall(objectName, methodName);
|
|
836
|
+
if (!targetComponent) {
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
return {
|
|
840
|
+
from: this.toComponentId(handlerName),
|
|
841
|
+
to: targetComponent,
|
|
842
|
+
description: `Calls ${methodName}()`,
|
|
843
|
+
technology: "Function Call",
|
|
844
|
+
confidence: "high",
|
|
845
|
+
evidence: [`Property access: ${fullChain}`]
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
extractFromDatabaseCall(awaitExpr, handlerName) {
|
|
849
|
+
if (!Node.isAwaitExpression(awaitExpr)) {
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
852
|
+
const innerExpr = awaitExpr.getExpression();
|
|
853
|
+
if (!Node.isCallExpression(innerExpr)) {
|
|
854
|
+
return null;
|
|
855
|
+
}
|
|
856
|
+
const callExpr = innerExpr.getExpression().getText();
|
|
857
|
+
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")) {
|
|
858
|
+
const operation = this.inferDatabaseOperation(callExpr);
|
|
859
|
+
return {
|
|
860
|
+
from: this.toComponentId(handlerName),
|
|
861
|
+
to: "database",
|
|
862
|
+
description: operation,
|
|
863
|
+
technology: "SQL",
|
|
864
|
+
confidence: "high",
|
|
865
|
+
evidence: [`Database call: ${callExpr}`]
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
extractFromFetchCall(callExpr, handlerName) {
|
|
871
|
+
const args = callExpr.getArguments();
|
|
872
|
+
if (args.length === 0) {
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
const urlArg = args[0].getText();
|
|
876
|
+
let apiName = "external_api";
|
|
877
|
+
if (urlArg.includes("openai")) {
|
|
878
|
+
apiName = "openai_api";
|
|
879
|
+
} else if (urlArg.includes("anthropic")) {
|
|
880
|
+
apiName = "anthropic_api";
|
|
881
|
+
}
|
|
882
|
+
return {
|
|
883
|
+
from: this.toComponentId(handlerName),
|
|
884
|
+
to: apiName,
|
|
885
|
+
description: "Calls external API",
|
|
886
|
+
technology: "HTTP/REST",
|
|
887
|
+
confidence: "high",
|
|
888
|
+
evidence: [`fetch() call to: ${urlArg}`]
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
inferComponentFromCall(objectName, methodName) {
|
|
892
|
+
const mappings = {
|
|
893
|
+
db: "db_client",
|
|
894
|
+
database: "database",
|
|
895
|
+
repos: "repositories",
|
|
896
|
+
repository: "repositories",
|
|
897
|
+
cache: "cache",
|
|
898
|
+
storage: "storage",
|
|
899
|
+
ai: "ai_service",
|
|
900
|
+
auth: "auth_service",
|
|
901
|
+
authservice: "auth_service",
|
|
902
|
+
user: "user_service",
|
|
903
|
+
userservice: "user_service",
|
|
904
|
+
logger: "logger",
|
|
905
|
+
queue: "queue_service"
|
|
906
|
+
};
|
|
907
|
+
const normalized = objectName.toLowerCase();
|
|
908
|
+
return mappings[normalized] || null;
|
|
909
|
+
}
|
|
910
|
+
resolveComponentFromImport(functionName, sourceFile) {
|
|
911
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
912
|
+
const namedImports = importDecl.getNamedImports();
|
|
913
|
+
for (const namedImport of namedImports) {
|
|
914
|
+
if (namedImport.getName() === functionName) {
|
|
915
|
+
const modulePath = importDecl.getModuleSpecifierValue();
|
|
916
|
+
if (modulePath.includes("/db/") || modulePath.includes("/database/")) {
|
|
917
|
+
return "db_client";
|
|
918
|
+
}
|
|
919
|
+
if (modulePath.includes("/repos") || modulePath.includes("/repositories")) {
|
|
920
|
+
return "repositories";
|
|
921
|
+
}
|
|
922
|
+
if (modulePath.includes("/service") || modulePath.includes("/services")) {
|
|
923
|
+
const match = modulePath.match(/\/([^/]+)\.ts$/);
|
|
924
|
+
if (match) {
|
|
925
|
+
return this.toComponentId(match[1]);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
inferDatabaseOperation(callExpr) {
|
|
934
|
+
if (callExpr.includes("query") || callExpr.includes("select")) {
|
|
935
|
+
return "Reads from database";
|
|
936
|
+
}
|
|
937
|
+
if (callExpr.includes("execute") || callExpr.includes("insert") || callExpr.includes("update") || callExpr.includes("delete")) {
|
|
938
|
+
return "Writes to database";
|
|
939
|
+
}
|
|
940
|
+
return "Accesses database";
|
|
941
|
+
}
|
|
942
|
+
toComponentId(name) {
|
|
943
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
944
|
+
}
|
|
945
|
+
deduplicateRelationships(relationships) {
|
|
946
|
+
const seen = new Set;
|
|
947
|
+
const unique = [];
|
|
948
|
+
for (const rel of relationships) {
|
|
949
|
+
const key = `${rel.from}->${rel.to}`;
|
|
950
|
+
if (!seen.has(key)) {
|
|
951
|
+
seen.add(key);
|
|
952
|
+
unique.push(rel);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return unique;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// vendor/analysis/src/extract/handlers.ts
|
|
728
960
|
class HandlerExtractor {
|
|
729
961
|
project;
|
|
730
962
|
typeGuardCache;
|
|
963
|
+
relationshipExtractor;
|
|
731
964
|
constructor(tsConfigPath) {
|
|
732
965
|
this.project = new Project({
|
|
733
966
|
tsConfigFilePath: tsConfigPath
|
|
734
967
|
});
|
|
735
968
|
this.typeGuardCache = new WeakMap;
|
|
969
|
+
this.relationshipExtractor = new RelationshipExtractor;
|
|
736
970
|
}
|
|
737
971
|
extractHandlers() {
|
|
738
972
|
const handlers = [];
|
|
@@ -766,9 +1000,9 @@ class HandlerExtractor {
|
|
|
766
1000
|
const filePath = sourceFile.getFilePath();
|
|
767
1001
|
const context = this.inferContext(filePath);
|
|
768
1002
|
sourceFile.forEachDescendant((node) => {
|
|
769
|
-
if (
|
|
1003
|
+
if (Node2.isCallExpression(node)) {
|
|
770
1004
|
const expression = node.getExpression();
|
|
771
|
-
if (
|
|
1005
|
+
if (Node2.isPropertyAccessExpression(expression)) {
|
|
772
1006
|
const methodName = expression.getName();
|
|
773
1007
|
if (methodName === "on" || methodName === "addEventListener") {
|
|
774
1008
|
const handler = this.extractHandler(node, context, filePath);
|
|
@@ -778,17 +1012,17 @@ class HandlerExtractor {
|
|
|
778
1012
|
}
|
|
779
1013
|
}
|
|
780
1014
|
}
|
|
781
|
-
if (
|
|
1015
|
+
if (Node2.isSwitchStatement(node)) {
|
|
782
1016
|
const switchHandlers = this.extractSwitchCaseHandlers(node, context, filePath);
|
|
783
1017
|
handlers.push(...switchHandlers);
|
|
784
1018
|
}
|
|
785
|
-
if (
|
|
1019
|
+
if (Node2.isVariableDeclaration(node)) {
|
|
786
1020
|
const mapHandlers = this.extractHandlerMapPattern(node, context, filePath);
|
|
787
1021
|
handlers.push(...mapHandlers);
|
|
788
1022
|
}
|
|
789
|
-
if (
|
|
1023
|
+
if (Node2.isIfStatement(node)) {
|
|
790
1024
|
const parent = node.getParent();
|
|
791
|
-
const isElseIf = parent &&
|
|
1025
|
+
const isElseIf = parent && Node2.isIfStatement(parent);
|
|
792
1026
|
if (!isElseIf) {
|
|
793
1027
|
const typeGuardHandlers = this.extractTypeGuardHandlers(node, context, filePath);
|
|
794
1028
|
handlers.push(...typeGuardHandlers);
|
|
@@ -804,9 +1038,9 @@ class HandlerExtractor {
|
|
|
804
1038
|
}
|
|
805
1039
|
const messageTypeArg = args[0];
|
|
806
1040
|
let messageType = null;
|
|
807
|
-
if (
|
|
1041
|
+
if (Node2.isStringLiteral(messageTypeArg)) {
|
|
808
1042
|
messageType = messageTypeArg.getLiteralValue();
|
|
809
|
-
} else if (
|
|
1043
|
+
} else if (Node2.isTemplateExpression(messageTypeArg)) {
|
|
810
1044
|
messageType = messageTypeArg.getText().replace(/[`'"]/g, "");
|
|
811
1045
|
}
|
|
812
1046
|
if (!messageType) {
|
|
@@ -816,11 +1050,20 @@ class HandlerExtractor {
|
|
|
816
1050
|
const assignments = [];
|
|
817
1051
|
const preconditions = [];
|
|
818
1052
|
const postconditions = [];
|
|
819
|
-
if (
|
|
1053
|
+
if (Node2.isArrowFunction(handlerArg) || Node2.isFunctionExpression(handlerArg)) {
|
|
820
1054
|
this.extractAssignments(handlerArg, assignments);
|
|
821
1055
|
this.extractVerificationConditions(handlerArg, preconditions, postconditions);
|
|
822
1056
|
}
|
|
823
1057
|
const line = callExpr.getStartLineNumber();
|
|
1058
|
+
const sourceFile = callExpr.getSourceFile();
|
|
1059
|
+
const handlerName = `${messageType}_handler`;
|
|
1060
|
+
let relationships = undefined;
|
|
1061
|
+
if (Node2.isArrowFunction(handlerArg) || Node2.isFunctionExpression(handlerArg)) {
|
|
1062
|
+
const detectedRelationships = this.relationshipExtractor.extractFromHandler(handlerArg, sourceFile, handlerName);
|
|
1063
|
+
if (detectedRelationships.length > 0) {
|
|
1064
|
+
relationships = detectedRelationships;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
824
1067
|
return {
|
|
825
1068
|
messageType,
|
|
826
1069
|
node: context,
|
|
@@ -830,17 +1073,18 @@ class HandlerExtractor {
|
|
|
830
1073
|
location: {
|
|
831
1074
|
file: filePath,
|
|
832
1075
|
line
|
|
833
|
-
}
|
|
1076
|
+
},
|
|
1077
|
+
relationships
|
|
834
1078
|
};
|
|
835
1079
|
}
|
|
836
1080
|
extractAssignments(funcNode, assignments) {
|
|
837
1081
|
funcNode.forEachDescendant((node) => {
|
|
838
|
-
if (
|
|
1082
|
+
if (Node2.isBinaryExpression(node)) {
|
|
839
1083
|
const operator = node.getOperatorToken().getText();
|
|
840
1084
|
if (operator === "=") {
|
|
841
1085
|
const left = node.getLeft();
|
|
842
1086
|
const right = node.getRight();
|
|
843
|
-
if (
|
|
1087
|
+
if (Node2.isPropertyAccessExpression(left)) {
|
|
844
1088
|
const fieldPath = this.getPropertyPath(left);
|
|
845
1089
|
if (fieldPath.startsWith("state.")) {
|
|
846
1090
|
const field = fieldPath.substring(6);
|
|
@@ -859,13 +1103,13 @@ class HandlerExtractor {
|
|
|
859
1103
|
}
|
|
860
1104
|
extractVerificationConditions(funcNode, preconditions, postconditions) {
|
|
861
1105
|
const body = funcNode.getBody();
|
|
862
|
-
const statements =
|
|
1106
|
+
const statements = Node2.isBlock(body) ? body.getStatements() : [body];
|
|
863
1107
|
statements.forEach((statement, index) => {
|
|
864
|
-
if (
|
|
1108
|
+
if (Node2.isExpressionStatement(statement)) {
|
|
865
1109
|
const expr = statement.getExpression();
|
|
866
|
-
if (
|
|
1110
|
+
if (Node2.isCallExpression(expr)) {
|
|
867
1111
|
const callee = expr.getExpression();
|
|
868
|
-
if (
|
|
1112
|
+
if (Node2.isIdentifier(callee)) {
|
|
869
1113
|
const functionName = callee.getText();
|
|
870
1114
|
if (functionName === "requires") {
|
|
871
1115
|
const condition = this.extractCondition(expr);
|
|
@@ -891,7 +1135,7 @@ class HandlerExtractor {
|
|
|
891
1135
|
const conditionArg = args[0];
|
|
892
1136
|
const expression = conditionArg.getText();
|
|
893
1137
|
let message;
|
|
894
|
-
if (args.length >= 2 &&
|
|
1138
|
+
if (args.length >= 2 && Node2.isStringLiteral(args[1])) {
|
|
895
1139
|
message = args[1].getLiteralValue();
|
|
896
1140
|
}
|
|
897
1141
|
const line = callExpr.getStartLineNumber();
|
|
@@ -908,29 +1152,29 @@ class HandlerExtractor {
|
|
|
908
1152
|
getPropertyPath(node) {
|
|
909
1153
|
const parts = [];
|
|
910
1154
|
let current = node;
|
|
911
|
-
while (
|
|
1155
|
+
while (Node2.isPropertyAccessExpression(current)) {
|
|
912
1156
|
parts.unshift(current.getName());
|
|
913
1157
|
current = current.getExpression();
|
|
914
1158
|
}
|
|
915
|
-
if (
|
|
1159
|
+
if (Node2.isIdentifier(current)) {
|
|
916
1160
|
parts.unshift(current.getText());
|
|
917
1161
|
}
|
|
918
1162
|
return parts.join(".");
|
|
919
1163
|
}
|
|
920
1164
|
extractValue(node) {
|
|
921
|
-
if (
|
|
1165
|
+
if (Node2.isStringLiteral(node)) {
|
|
922
1166
|
return node.getLiteralValue();
|
|
923
1167
|
}
|
|
924
|
-
if (
|
|
1168
|
+
if (Node2.isNumericLiteral(node)) {
|
|
925
1169
|
return node.getLiteralValue();
|
|
926
1170
|
}
|
|
927
|
-
if (node.getKind() ===
|
|
1171
|
+
if (node.getKind() === SyntaxKind2.TrueKeyword) {
|
|
928
1172
|
return true;
|
|
929
1173
|
}
|
|
930
|
-
if (node.getKind() ===
|
|
1174
|
+
if (node.getKind() === SyntaxKind2.FalseKeyword) {
|
|
931
1175
|
return false;
|
|
932
1176
|
}
|
|
933
|
-
if (node.getKind() ===
|
|
1177
|
+
if (node.getKind() === SyntaxKind2.NullKeyword) {
|
|
934
1178
|
return null;
|
|
935
1179
|
}
|
|
936
1180
|
return;
|
|
@@ -945,10 +1189,10 @@ class HandlerExtractor {
|
|
|
945
1189
|
}
|
|
946
1190
|
const caseClauses = switchNode.getClauses();
|
|
947
1191
|
for (const clause of caseClauses) {
|
|
948
|
-
if (
|
|
1192
|
+
if (Node2.isCaseClause(clause)) {
|
|
949
1193
|
const caseExpr = clause.getExpression();
|
|
950
1194
|
let messageType = null;
|
|
951
|
-
if (
|
|
1195
|
+
if (Node2.isStringLiteral(caseExpr)) {
|
|
952
1196
|
messageType = caseExpr.getLiteralValue();
|
|
953
1197
|
}
|
|
954
1198
|
if (messageType) {
|
|
@@ -971,7 +1215,7 @@ class HandlerExtractor {
|
|
|
971
1215
|
const handlers = [];
|
|
972
1216
|
try {
|
|
973
1217
|
const initializer = varDecl.getInitializer();
|
|
974
|
-
if (!initializer || !
|
|
1218
|
+
if (!initializer || !Node2.isObjectLiteralExpression(initializer)) {
|
|
975
1219
|
return handlers;
|
|
976
1220
|
}
|
|
977
1221
|
const varName = varDecl.getName().toLowerCase();
|
|
@@ -980,12 +1224,12 @@ class HandlerExtractor {
|
|
|
980
1224
|
}
|
|
981
1225
|
const properties = initializer.getProperties();
|
|
982
1226
|
for (const prop of properties) {
|
|
983
|
-
if (
|
|
1227
|
+
if (Node2.isPropertyAssignment(prop)) {
|
|
984
1228
|
const nameNode = prop.getNameNode();
|
|
985
1229
|
let messageType = null;
|
|
986
|
-
if (
|
|
1230
|
+
if (Node2.isStringLiteral(nameNode)) {
|
|
987
1231
|
messageType = nameNode.getLiteralValue();
|
|
988
|
-
} else if (
|
|
1232
|
+
} else if (Node2.isIdentifier(nameNode)) {
|
|
989
1233
|
messageType = nameNode.getText();
|
|
990
1234
|
}
|
|
991
1235
|
if (messageType) {
|
|
@@ -1032,7 +1276,7 @@ class HandlerExtractor {
|
|
|
1032
1276
|
}
|
|
1033
1277
|
}
|
|
1034
1278
|
const elseStatement = currentIf.getElseStatement();
|
|
1035
|
-
if (elseStatement &&
|
|
1279
|
+
if (elseStatement && Node2.isIfStatement(elseStatement)) {
|
|
1036
1280
|
currentIf = elseStatement;
|
|
1037
1281
|
} else {
|
|
1038
1282
|
break;
|
|
@@ -1048,12 +1292,12 @@ class HandlerExtractor {
|
|
|
1048
1292
|
extractHandlerFromIfClause(ifNode, typeGuards, context, filePath) {
|
|
1049
1293
|
try {
|
|
1050
1294
|
const condition = ifNode.getExpression();
|
|
1051
|
-
if (!
|
|
1295
|
+
if (!Node2.isCallExpression(condition)) {
|
|
1052
1296
|
return null;
|
|
1053
1297
|
}
|
|
1054
1298
|
const funcExpr = condition.getExpression();
|
|
1055
1299
|
let funcName;
|
|
1056
|
-
if (
|
|
1300
|
+
if (Node2.isIdentifier(funcExpr)) {
|
|
1057
1301
|
funcName = funcExpr.getText();
|
|
1058
1302
|
}
|
|
1059
1303
|
if (process.env.POLLY_DEBUG && funcName) {
|
|
@@ -1065,7 +1309,7 @@ class HandlerExtractor {
|
|
|
1065
1309
|
if (process.env.POLLY_DEBUG) {
|
|
1066
1310
|
console.log(`[DEBUG] Found in local type guards: ${funcName} → ${messageType}`);
|
|
1067
1311
|
}
|
|
1068
|
-
} else if (
|
|
1312
|
+
} else if (Node2.isIdentifier(funcExpr)) {
|
|
1069
1313
|
if (process.env.POLLY_DEBUG) {
|
|
1070
1314
|
console.log(`[DEBUG] Not found locally, trying import resolution for: ${funcName}`);
|
|
1071
1315
|
}
|
|
@@ -1078,13 +1322,24 @@ class HandlerExtractor {
|
|
|
1078
1322
|
return null;
|
|
1079
1323
|
}
|
|
1080
1324
|
const line = ifNode.getStartLineNumber();
|
|
1325
|
+
const sourceFile = ifNode.getSourceFile();
|
|
1326
|
+
const handlerName = `${messageType}_handler`;
|
|
1327
|
+
let relationships = undefined;
|
|
1328
|
+
const thenStatement = ifNode.getThenStatement();
|
|
1329
|
+
if (thenStatement) {
|
|
1330
|
+
const detectedRelationships = this.relationshipExtractor.extractFromHandler(thenStatement, sourceFile, handlerName);
|
|
1331
|
+
if (detectedRelationships.length > 0) {
|
|
1332
|
+
relationships = detectedRelationships;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1081
1335
|
return {
|
|
1082
1336
|
messageType,
|
|
1083
1337
|
node: context,
|
|
1084
1338
|
assignments: [],
|
|
1085
1339
|
preconditions: [],
|
|
1086
1340
|
postconditions: [],
|
|
1087
|
-
location: { file: filePath, line }
|
|
1341
|
+
location: { file: filePath, line },
|
|
1342
|
+
relationships
|
|
1088
1343
|
};
|
|
1089
1344
|
} catch (error) {
|
|
1090
1345
|
return null;
|
|
@@ -1093,20 +1348,20 @@ class HandlerExtractor {
|
|
|
1093
1348
|
findTypePredicateFunctions(sourceFile) {
|
|
1094
1349
|
const typeGuards = new Map;
|
|
1095
1350
|
sourceFile.forEachDescendant((node) => {
|
|
1096
|
-
if (
|
|
1351
|
+
if (Node2.isFunctionDeclaration(node) || Node2.isFunctionExpression(node) || Node2.isArrowFunction(node)) {
|
|
1097
1352
|
const returnTypeNode = node.getReturnTypeNode();
|
|
1098
|
-
if (returnTypeNode &&
|
|
1353
|
+
if (returnTypeNode && Node2.isTypePredicate(returnTypeNode)) {
|
|
1099
1354
|
let functionName;
|
|
1100
|
-
if (
|
|
1355
|
+
if (Node2.isFunctionDeclaration(node)) {
|
|
1101
1356
|
functionName = node.getName();
|
|
1102
|
-
} else if (
|
|
1357
|
+
} else if (Node2.isFunctionExpression(node)) {
|
|
1103
1358
|
const parent = node.getParent();
|
|
1104
|
-
if (
|
|
1359
|
+
if (Node2.isVariableDeclaration(parent)) {
|
|
1105
1360
|
functionName = parent.getName();
|
|
1106
1361
|
}
|
|
1107
|
-
} else if (
|
|
1362
|
+
} else if (Node2.isArrowFunction(node)) {
|
|
1108
1363
|
const parent = node.getParent();
|
|
1109
|
-
if (
|
|
1364
|
+
if (Node2.isVariableDeclaration(parent)) {
|
|
1110
1365
|
functionName = parent.getName();
|
|
1111
1366
|
}
|
|
1112
1367
|
}
|
|
@@ -1147,15 +1402,15 @@ class HandlerExtractor {
|
|
|
1147
1402
|
return null;
|
|
1148
1403
|
}
|
|
1149
1404
|
for (const def of definitions) {
|
|
1150
|
-
if (
|
|
1405
|
+
if (Node2.isFunctionDeclaration(def) || Node2.isFunctionExpression(def) || Node2.isArrowFunction(def)) {
|
|
1151
1406
|
const returnTypeNode = def.getReturnTypeNode();
|
|
1152
1407
|
if (process.env.POLLY_DEBUG) {
|
|
1153
1408
|
const returnType = def.getReturnType().getText();
|
|
1154
1409
|
console.log(`[DEBUG] Function ${funcName} return type (resolved): ${returnType}`);
|
|
1155
1410
|
console.log(`[DEBUG] Has return type node: ${!!returnTypeNode}`);
|
|
1156
|
-
console.log(`[DEBUG] Is type predicate node: ${returnTypeNode &&
|
|
1411
|
+
console.log(`[DEBUG] Is type predicate node: ${returnTypeNode && Node2.isTypePredicate(returnTypeNode)}`);
|
|
1157
1412
|
}
|
|
1158
|
-
if (returnTypeNode &&
|
|
1413
|
+
if (returnTypeNode && Node2.isTypePredicate(returnTypeNode)) {
|
|
1159
1414
|
const typeNode = returnTypeNode.getTypeNode();
|
|
1160
1415
|
if (typeNode) {
|
|
1161
1416
|
const typeName = typeNode.getText();
|
|
@@ -2385,4 +2640,4 @@ Stack trace:`, COLORS.gray));
|
|
|
2385
2640
|
process.exit(1);
|
|
2386
2641
|
});
|
|
2387
2642
|
|
|
2388
|
-
//# debugId=
|
|
2643
|
+
//# debugId=F2D5F1DF2FB1D52164756E2164756E21
|