@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 (
|
|
1106
|
+
if (Node2.isCallExpression(node)) {
|
|
770
1107
|
const expression = node.getExpression();
|
|
771
|
-
if (
|
|
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 (
|
|
1118
|
+
if (Node2.isSwitchStatement(node)) {
|
|
782
1119
|
const switchHandlers = this.extractSwitchCaseHandlers(node, context, filePath);
|
|
783
1120
|
handlers.push(...switchHandlers);
|
|
784
1121
|
}
|
|
785
|
-
if (
|
|
1122
|
+
if (Node2.isVariableDeclaration(node)) {
|
|
786
1123
|
const mapHandlers = this.extractHandlerMapPattern(node, context, filePath);
|
|
787
1124
|
handlers.push(...mapHandlers);
|
|
788
1125
|
}
|
|
789
|
-
if (
|
|
1126
|
+
if (Node2.isIfStatement(node)) {
|
|
790
1127
|
const parent = node.getParent();
|
|
791
|
-
const isElseIf = 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 (
|
|
1144
|
+
if (Node2.isStringLiteral(messageTypeArg)) {
|
|
808
1145
|
messageType = messageTypeArg.getLiteralValue();
|
|
809
|
-
} else if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 =
|
|
1209
|
+
const statements = Node2.isBlock(body) ? body.getStatements() : [body];
|
|
863
1210
|
statements.forEach((statement, index) => {
|
|
864
|
-
if (
|
|
1211
|
+
if (Node2.isExpressionStatement(statement)) {
|
|
865
1212
|
const expr = statement.getExpression();
|
|
866
|
-
if (
|
|
1213
|
+
if (Node2.isCallExpression(expr)) {
|
|
867
1214
|
const callee = expr.getExpression();
|
|
868
|
-
if (
|
|
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 &&
|
|
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 (
|
|
1258
|
+
while (Node2.isPropertyAccessExpression(current)) {
|
|
912
1259
|
parts.unshift(current.getName());
|
|
913
1260
|
current = current.getExpression();
|
|
914
1261
|
}
|
|
915
|
-
if (
|
|
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 (
|
|
1268
|
+
if (Node2.isStringLiteral(node)) {
|
|
922
1269
|
return node.getLiteralValue();
|
|
923
1270
|
}
|
|
924
|
-
if (
|
|
1271
|
+
if (Node2.isNumericLiteral(node)) {
|
|
925
1272
|
return node.getLiteralValue();
|
|
926
1273
|
}
|
|
927
|
-
if (node.getKind() ===
|
|
1274
|
+
if (node.getKind() === SyntaxKind2.TrueKeyword) {
|
|
928
1275
|
return true;
|
|
929
1276
|
}
|
|
930
|
-
if (node.getKind() ===
|
|
1277
|
+
if (node.getKind() === SyntaxKind2.FalseKeyword) {
|
|
931
1278
|
return false;
|
|
932
1279
|
}
|
|
933
|
-
if (node.getKind() ===
|
|
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 (
|
|
1295
|
+
if (Node2.isCaseClause(clause)) {
|
|
949
1296
|
const caseExpr = clause.getExpression();
|
|
950
1297
|
let messageType = null;
|
|
951
|
-
if (
|
|
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 || !
|
|
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 (
|
|
1330
|
+
if (Node2.isPropertyAssignment(prop)) {
|
|
984
1331
|
const nameNode = prop.getNameNode();
|
|
985
1332
|
let messageType = null;
|
|
986
|
-
if (
|
|
1333
|
+
if (Node2.isStringLiteral(nameNode)) {
|
|
987
1334
|
messageType = nameNode.getLiteralValue();
|
|
988
|
-
} else if (
|
|
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 &&
|
|
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 (!
|
|
1398
|
+
if (!Node2.isCallExpression(condition)) {
|
|
1052
1399
|
return null;
|
|
1053
1400
|
}
|
|
1054
1401
|
const funcExpr = condition.getExpression();
|
|
1055
1402
|
let funcName;
|
|
1056
|
-
if (
|
|
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 (
|
|
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 (
|
|
1454
|
+
if (Node2.isFunctionDeclaration(node) || Node2.isFunctionExpression(node) || Node2.isArrowFunction(node)) {
|
|
1097
1455
|
const returnTypeNode = node.getReturnTypeNode();
|
|
1098
|
-
if (returnTypeNode &&
|
|
1456
|
+
if (returnTypeNode && Node2.isTypePredicate(returnTypeNode)) {
|
|
1099
1457
|
let functionName;
|
|
1100
|
-
if (
|
|
1458
|
+
if (Node2.isFunctionDeclaration(node)) {
|
|
1101
1459
|
functionName = node.getName();
|
|
1102
|
-
} else if (
|
|
1460
|
+
} else if (Node2.isFunctionExpression(node)) {
|
|
1103
1461
|
const parent = node.getParent();
|
|
1104
|
-
if (
|
|
1462
|
+
if (Node2.isVariableDeclaration(parent)) {
|
|
1105
1463
|
functionName = parent.getName();
|
|
1106
1464
|
}
|
|
1107
|
-
} else if (
|
|
1465
|
+
} else if (Node2.isArrowFunction(node)) {
|
|
1108
1466
|
const parent = node.getParent();
|
|
1109
|
-
if (
|
|
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 (
|
|
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 &&
|
|
1514
|
+
console.log(`[DEBUG] Is type predicate node: ${returnTypeNode && Node2.isTypePredicate(returnTypeNode)}`);
|
|
1157
1515
|
}
|
|
1158
|
-
if (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=
|
|
2746
|
+
//# debugId=C40ED4F55177D0FC64756E2164756E21
|