depwire-cli 0.9.30 → 1.0.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.
- package/README.md +57 -8
- package/dist/{chunk-FUIZQCYB.js → chunk-5D36PY3Q.js} +10 -7
- package/dist/{chunk-WUSXCZXA.js → chunk-LV32EDYQ.js} +1703 -159
- package/dist/index.js +4 -4
- package/dist/mcpb-entry.js +2 -2
- package/dist/parser/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/dist/parser/grammars/tree-sitter-java.wasm +0 -0
- package/dist/sdk.js +1 -1
- package/package.json +3 -1
|
@@ -32,7 +32,9 @@ function scanDirectory(rootDir, baseDir = rootDir) {
|
|
|
32
32
|
const isGo = entry.endsWith(".go") && !entry.endsWith("_test.go");
|
|
33
33
|
const isRust = entry.endsWith(".rs");
|
|
34
34
|
const isC = entry.endsWith(".c") || entry.endsWith(".h");
|
|
35
|
-
|
|
35
|
+
const isCSharp = entry.endsWith(".cs") || entry.endsWith(".csx") || entry.endsWith(".csproj");
|
|
36
|
+
const isJava = entry.endsWith(".java") || entry === "pom.xml" || entry === "build.gradle" || entry === "build.gradle.kts";
|
|
37
|
+
if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCSharp || isJava) {
|
|
36
38
|
files.push(relative(rootDir, fullPath));
|
|
37
39
|
}
|
|
38
40
|
}
|
|
@@ -70,6 +72,10 @@ function findProjectRoot(startDir = process.cwd()) {
|
|
|
70
72
|
// C/C++ (cmake-based)
|
|
71
73
|
"configure.ac",
|
|
72
74
|
// C/C++ (autotools)
|
|
75
|
+
"pom.xml",
|
|
76
|
+
// Java (Maven)
|
|
77
|
+
"build.gradle",
|
|
78
|
+
// Java (Gradle)
|
|
73
79
|
".git"
|
|
74
80
|
// Any git repo
|
|
75
81
|
];
|
|
@@ -103,11 +109,11 @@ function findProjectRoot(startDir = process.cwd()) {
|
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
// src/parser/index.ts
|
|
106
|
-
import { readFileSync as
|
|
107
|
-
import { join as
|
|
112
|
+
import { readFileSync as readFileSync7, statSync as statSync4 } from "fs";
|
|
113
|
+
import { join as join10, resolve as resolve5 } from "path";
|
|
108
114
|
|
|
109
115
|
// src/parser/detect.ts
|
|
110
|
-
import { extname as
|
|
116
|
+
import { extname as extname5, basename as basename3 } from "path";
|
|
111
117
|
|
|
112
118
|
// src/parser/wasm-init.ts
|
|
113
119
|
import { Parser, Language } from "web-tree-sitter";
|
|
@@ -134,7 +140,9 @@ async function initParser() {
|
|
|
134
140
|
"python": "tree-sitter-python.wasm",
|
|
135
141
|
"go": "tree-sitter-go.wasm",
|
|
136
142
|
"rust": "tree-sitter-rust.wasm",
|
|
137
|
-
"c": "tree-sitter-c.wasm"
|
|
143
|
+
"c": "tree-sitter-c.wasm",
|
|
144
|
+
"c_sharp": "tree-sitter-c_sharp.wasm",
|
|
145
|
+
"java": "tree-sitter-java.wasm"
|
|
138
146
|
};
|
|
139
147
|
for (const [name, file] of Object.entries(grammarFiles)) {
|
|
140
148
|
const wasmPath = path.join(grammarsDir, file);
|
|
@@ -2782,6 +2790,1312 @@ var cParser = {
|
|
|
2782
2790
|
parseFile: parseCFile
|
|
2783
2791
|
};
|
|
2784
2792
|
|
|
2793
|
+
// src/parser/csharp.ts
|
|
2794
|
+
import { dirname as dirname7, join as join8, resolve as resolve3, basename } from "path";
|
|
2795
|
+
import { existsSync as existsSync8, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
|
|
2796
|
+
function parseCSharpFile(filePath, sourceCode, projectRoot) {
|
|
2797
|
+
if (filePath.endsWith(".csproj")) {
|
|
2798
|
+
return parseCsprojFile(filePath, sourceCode, projectRoot);
|
|
2799
|
+
}
|
|
2800
|
+
const parser = getParser("c_sharp");
|
|
2801
|
+
const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
|
|
2802
|
+
const context = {
|
|
2803
|
+
filePath,
|
|
2804
|
+
projectRoot,
|
|
2805
|
+
sourceCode,
|
|
2806
|
+
symbols: [],
|
|
2807
|
+
edges: [],
|
|
2808
|
+
currentScope: [],
|
|
2809
|
+
currentClass: null,
|
|
2810
|
+
currentNamespace: null,
|
|
2811
|
+
imports: /* @__PURE__ */ new Map(),
|
|
2812
|
+
isCsproj: false
|
|
2813
|
+
};
|
|
2814
|
+
walkNode7(tree.rootNode, context);
|
|
2815
|
+
return {
|
|
2816
|
+
filePath,
|
|
2817
|
+
symbols: context.symbols,
|
|
2818
|
+
edges: context.edges
|
|
2819
|
+
};
|
|
2820
|
+
}
|
|
2821
|
+
function walkNode7(node, context) {
|
|
2822
|
+
processNode7(node, context);
|
|
2823
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2824
|
+
const child = node.child(i);
|
|
2825
|
+
if (child) {
|
|
2826
|
+
walkNode7(child, context);
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
function processNode7(node, context) {
|
|
2831
|
+
switch (node.type) {
|
|
2832
|
+
case "namespace_declaration":
|
|
2833
|
+
processNamespaceDeclaration(node, context);
|
|
2834
|
+
break;
|
|
2835
|
+
case "file_scoped_namespace_declaration":
|
|
2836
|
+
processFileScopedNamespace(node, context);
|
|
2837
|
+
break;
|
|
2838
|
+
case "class_declaration":
|
|
2839
|
+
processClassDeclaration3(node, context);
|
|
2840
|
+
break;
|
|
2841
|
+
case "interface_declaration":
|
|
2842
|
+
processInterfaceDeclaration2(node, context);
|
|
2843
|
+
break;
|
|
2844
|
+
case "struct_declaration":
|
|
2845
|
+
processStructDeclaration(node, context);
|
|
2846
|
+
break;
|
|
2847
|
+
case "enum_declaration":
|
|
2848
|
+
processEnumDeclaration2(node, context);
|
|
2849
|
+
break;
|
|
2850
|
+
case "record_declaration":
|
|
2851
|
+
processRecordDeclaration(node, context);
|
|
2852
|
+
break;
|
|
2853
|
+
case "delegate_declaration":
|
|
2854
|
+
processDelegateDeclaration(node, context);
|
|
2855
|
+
break;
|
|
2856
|
+
case "method_declaration":
|
|
2857
|
+
processMethodDeclaration2(node, context);
|
|
2858
|
+
break;
|
|
2859
|
+
case "constructor_declaration":
|
|
2860
|
+
processConstructorDeclaration(node, context);
|
|
2861
|
+
break;
|
|
2862
|
+
case "property_declaration":
|
|
2863
|
+
processPropertyDeclaration(node, context);
|
|
2864
|
+
break;
|
|
2865
|
+
case "event_field_declaration":
|
|
2866
|
+
processEventFieldDeclaration(node, context);
|
|
2867
|
+
break;
|
|
2868
|
+
case "indexer_declaration":
|
|
2869
|
+
processIndexerDeclaration(node, context);
|
|
2870
|
+
break;
|
|
2871
|
+
case "using_directive":
|
|
2872
|
+
processUsingDirective(node, context);
|
|
2873
|
+
break;
|
|
2874
|
+
case "global_statement":
|
|
2875
|
+
processGlobalStatement(node, context);
|
|
2876
|
+
break;
|
|
2877
|
+
case "invocation_expression":
|
|
2878
|
+
processCallExpression7(node, context);
|
|
2879
|
+
break;
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
function processNamespaceDeclaration(node, context) {
|
|
2883
|
+
const nameNode = node.childForFieldName("name");
|
|
2884
|
+
if (!nameNode) return;
|
|
2885
|
+
const name = nodeText6(nameNode, context);
|
|
2886
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2887
|
+
context.symbols.push({
|
|
2888
|
+
id: symbolId,
|
|
2889
|
+
name,
|
|
2890
|
+
kind: "module",
|
|
2891
|
+
filePath: context.filePath,
|
|
2892
|
+
startLine: node.startPosition.row + 1,
|
|
2893
|
+
endLine: node.endPosition.row + 1,
|
|
2894
|
+
exported: true
|
|
2895
|
+
});
|
|
2896
|
+
const oldNamespace = context.currentNamespace;
|
|
2897
|
+
context.currentNamespace = name;
|
|
2898
|
+
context.currentScope.push(name);
|
|
2899
|
+
const body = findChildByType7(node, "declaration_list");
|
|
2900
|
+
if (body) {
|
|
2901
|
+
walkNode7(body, context);
|
|
2902
|
+
}
|
|
2903
|
+
context.currentScope.pop();
|
|
2904
|
+
context.currentNamespace = oldNamespace;
|
|
2905
|
+
}
|
|
2906
|
+
function processFileScopedNamespace(node, context) {
|
|
2907
|
+
const nameNode = node.childForFieldName("name");
|
|
2908
|
+
if (!nameNode) return;
|
|
2909
|
+
const name = nodeText6(nameNode, context);
|
|
2910
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2911
|
+
context.symbols.push({
|
|
2912
|
+
id: symbolId,
|
|
2913
|
+
name,
|
|
2914
|
+
kind: "module",
|
|
2915
|
+
filePath: context.filePath,
|
|
2916
|
+
startLine: node.startPosition.row + 1,
|
|
2917
|
+
endLine: node.endPosition.row + 1,
|
|
2918
|
+
exported: true
|
|
2919
|
+
});
|
|
2920
|
+
context.currentNamespace = name;
|
|
2921
|
+
}
|
|
2922
|
+
function processClassDeclaration3(node, context) {
|
|
2923
|
+
processTypeDeclaration2(node, context, "class");
|
|
2924
|
+
}
|
|
2925
|
+
function processInterfaceDeclaration2(node, context) {
|
|
2926
|
+
processTypeDeclaration2(node, context, "interface");
|
|
2927
|
+
}
|
|
2928
|
+
function processStructDeclaration(node, context) {
|
|
2929
|
+
processTypeDeclaration2(node, context, "class");
|
|
2930
|
+
}
|
|
2931
|
+
function processRecordDeclaration(node, context) {
|
|
2932
|
+
processTypeDeclaration2(node, context, "class");
|
|
2933
|
+
}
|
|
2934
|
+
function processTypeDeclaration2(node, context, kind) {
|
|
2935
|
+
const nameNode = node.childForFieldName("name");
|
|
2936
|
+
if (!nameNode) return;
|
|
2937
|
+
let name = nodeText6(nameNode, context);
|
|
2938
|
+
const angleBracketIdx = name.indexOf("<");
|
|
2939
|
+
if (angleBracketIdx > 0) {
|
|
2940
|
+
name = name.substring(0, angleBracketIdx);
|
|
2941
|
+
}
|
|
2942
|
+
const exported = hasModifier(node, context, "public") || hasModifier(node, context, "internal");
|
|
2943
|
+
const scope = context.currentClass || void 0;
|
|
2944
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2945
|
+
context.symbols.push({
|
|
2946
|
+
id: symbolId,
|
|
2947
|
+
name,
|
|
2948
|
+
kind,
|
|
2949
|
+
filePath: context.filePath,
|
|
2950
|
+
startLine: node.startPosition.row + 1,
|
|
2951
|
+
endLine: node.endPosition.row + 1,
|
|
2952
|
+
exported,
|
|
2953
|
+
scope
|
|
2954
|
+
});
|
|
2955
|
+
const baseList = findChildByType7(node, "base_list");
|
|
2956
|
+
if (baseList) {
|
|
2957
|
+
for (let i = 0; i < baseList.childCount; i++) {
|
|
2958
|
+
const child = baseList.child(i);
|
|
2959
|
+
if (!child) continue;
|
|
2960
|
+
if (child.type === "simple_base_type" || child.type === "identifier" || child.type === "generic_name" || child.type === "qualified_name") {
|
|
2961
|
+
let baseName = extractBaseTypeName(child, context);
|
|
2962
|
+
if (baseName) {
|
|
2963
|
+
const baseId = resolveSymbol6(baseName, context);
|
|
2964
|
+
if (baseId) {
|
|
2965
|
+
const edgeKind = baseName.startsWith("I") && baseName.length > 1 && baseName[1] === baseName[1].toUpperCase() ? "implements" : "inherits";
|
|
2966
|
+
context.edges.push({
|
|
2967
|
+
source: symbolId,
|
|
2968
|
+
target: baseId,
|
|
2969
|
+
kind: edgeKind,
|
|
2970
|
+
filePath: context.filePath,
|
|
2971
|
+
line: child.startPosition.row + 1
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
const oldClass = context.currentClass;
|
|
2979
|
+
context.currentClass = name;
|
|
2980
|
+
context.currentScope.push(name);
|
|
2981
|
+
const body = findChildByType7(node, "declaration_list");
|
|
2982
|
+
if (body) {
|
|
2983
|
+
walkNode7(body, context);
|
|
2984
|
+
}
|
|
2985
|
+
context.currentScope.pop();
|
|
2986
|
+
context.currentClass = oldClass;
|
|
2987
|
+
}
|
|
2988
|
+
function processEnumDeclaration2(node, context) {
|
|
2989
|
+
const nameNode = node.childForFieldName("name");
|
|
2990
|
+
if (!nameNode) return;
|
|
2991
|
+
const name = nodeText6(nameNode, context);
|
|
2992
|
+
const exported = hasModifier(node, context, "public") || hasModifier(node, context, "internal");
|
|
2993
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2994
|
+
context.symbols.push({
|
|
2995
|
+
id: symbolId,
|
|
2996
|
+
name,
|
|
2997
|
+
kind: "enum",
|
|
2998
|
+
filePath: context.filePath,
|
|
2999
|
+
startLine: node.startPosition.row + 1,
|
|
3000
|
+
endLine: node.endPosition.row + 1,
|
|
3001
|
+
exported
|
|
3002
|
+
});
|
|
3003
|
+
const body = findChildByType7(node, "enum_member_declaration_list");
|
|
3004
|
+
if (body) {
|
|
3005
|
+
const members = findChildrenByType2(body, "enum_member_declaration");
|
|
3006
|
+
for (const member of members) {
|
|
3007
|
+
const memberNameNode = member.childForFieldName("name");
|
|
3008
|
+
if (!memberNameNode) continue;
|
|
3009
|
+
const memberName = nodeText6(memberNameNode, context);
|
|
3010
|
+
const memberId = `${context.filePath}::${name}.${memberName}`;
|
|
3011
|
+
context.symbols.push({
|
|
3012
|
+
id: memberId,
|
|
3013
|
+
name: memberName,
|
|
3014
|
+
kind: "constant",
|
|
3015
|
+
filePath: context.filePath,
|
|
3016
|
+
startLine: member.startPosition.row + 1,
|
|
3017
|
+
endLine: member.endPosition.row + 1,
|
|
3018
|
+
exported,
|
|
3019
|
+
scope: name
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
function processDelegateDeclaration(node, context) {
|
|
3025
|
+
const nameNode = node.childForFieldName("name");
|
|
3026
|
+
if (!nameNode) return;
|
|
3027
|
+
let name = nodeText6(nameNode, context);
|
|
3028
|
+
const angleBracketIdx = name.indexOf("<");
|
|
3029
|
+
if (angleBracketIdx > 0) name = name.substring(0, angleBracketIdx);
|
|
3030
|
+
const exported = hasModifier(node, context, "public") || hasModifier(node, context, "internal");
|
|
3031
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
3032
|
+
context.symbols.push({
|
|
3033
|
+
id: symbolId,
|
|
3034
|
+
name,
|
|
3035
|
+
kind: "type_alias",
|
|
3036
|
+
filePath: context.filePath,
|
|
3037
|
+
startLine: node.startPosition.row + 1,
|
|
3038
|
+
endLine: node.endPosition.row + 1,
|
|
3039
|
+
exported
|
|
3040
|
+
});
|
|
3041
|
+
}
|
|
3042
|
+
function processMethodDeclaration2(node, context) {
|
|
3043
|
+
const nameNode = node.childForFieldName("name");
|
|
3044
|
+
if (!nameNode) return;
|
|
3045
|
+
const name = nodeText6(nameNode, context);
|
|
3046
|
+
const exported = hasModifier(node, context, "public");
|
|
3047
|
+
const scope = context.currentClass || void 0;
|
|
3048
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
3049
|
+
context.symbols.push({
|
|
3050
|
+
id: symbolId,
|
|
3051
|
+
name,
|
|
3052
|
+
kind: context.currentClass ? "method" : "function",
|
|
3053
|
+
filePath: context.filePath,
|
|
3054
|
+
startLine: node.startPosition.row + 1,
|
|
3055
|
+
endLine: node.endPosition.row + 1,
|
|
3056
|
+
exported,
|
|
3057
|
+
scope
|
|
3058
|
+
});
|
|
3059
|
+
const scopeName = scope ? `${scope}.${name}` : name;
|
|
3060
|
+
context.currentScope.push(scopeName);
|
|
3061
|
+
const body = node.childForFieldName("body");
|
|
3062
|
+
if (body) {
|
|
3063
|
+
walkNode7(body, context);
|
|
3064
|
+
}
|
|
3065
|
+
context.currentScope.pop();
|
|
3066
|
+
}
|
|
3067
|
+
function processConstructorDeclaration(node, context) {
|
|
3068
|
+
const nameNode = node.childForFieldName("name");
|
|
3069
|
+
if (!nameNode) return;
|
|
3070
|
+
const name = nodeText6(nameNode, context);
|
|
3071
|
+
const exported = hasModifier(node, context, "public");
|
|
3072
|
+
const scope = context.currentClass || void 0;
|
|
3073
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
3074
|
+
context.symbols.push({
|
|
3075
|
+
id: symbolId,
|
|
3076
|
+
name,
|
|
3077
|
+
kind: "method",
|
|
3078
|
+
filePath: context.filePath,
|
|
3079
|
+
startLine: node.startPosition.row + 1,
|
|
3080
|
+
endLine: node.endPosition.row + 1,
|
|
3081
|
+
exported,
|
|
3082
|
+
scope
|
|
3083
|
+
});
|
|
3084
|
+
const scopeName = scope ? `${scope}.${name}` : name;
|
|
3085
|
+
context.currentScope.push(scopeName);
|
|
3086
|
+
const body = node.childForFieldName("body");
|
|
3087
|
+
if (body) {
|
|
3088
|
+
walkNode7(body, context);
|
|
3089
|
+
}
|
|
3090
|
+
context.currentScope.pop();
|
|
3091
|
+
}
|
|
3092
|
+
function processPropertyDeclaration(node, context) {
|
|
3093
|
+
const nameNode = node.childForFieldName("name");
|
|
3094
|
+
if (!nameNode) return;
|
|
3095
|
+
const name = nodeText6(nameNode, context);
|
|
3096
|
+
const exported = hasModifier(node, context, "public");
|
|
3097
|
+
const scope = context.currentClass || void 0;
|
|
3098
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
3099
|
+
context.symbols.push({
|
|
3100
|
+
id: symbolId,
|
|
3101
|
+
name,
|
|
3102
|
+
kind: "property",
|
|
3103
|
+
filePath: context.filePath,
|
|
3104
|
+
startLine: node.startPosition.row + 1,
|
|
3105
|
+
endLine: node.endPosition.row + 1,
|
|
3106
|
+
exported,
|
|
3107
|
+
scope
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3110
|
+
function processEventFieldDeclaration(node, context) {
|
|
3111
|
+
const varDecl = findChildByType7(node, "variable_declaration");
|
|
3112
|
+
if (!varDecl) return;
|
|
3113
|
+
const declarator = findChildByType7(varDecl, "variable_declarator");
|
|
3114
|
+
if (!declarator) return;
|
|
3115
|
+
const nameNode = declarator.childForFieldName("name") || findChildByType7(declarator, "identifier");
|
|
3116
|
+
if (!nameNode) return;
|
|
3117
|
+
const name = nodeText6(nameNode, context);
|
|
3118
|
+
const exported = hasModifier(node, context, "public");
|
|
3119
|
+
const scope = context.currentClass || void 0;
|
|
3120
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
3121
|
+
context.symbols.push({
|
|
3122
|
+
id: symbolId,
|
|
3123
|
+
name,
|
|
3124
|
+
kind: "property",
|
|
3125
|
+
filePath: context.filePath,
|
|
3126
|
+
startLine: node.startPosition.row + 1,
|
|
3127
|
+
endLine: node.endPosition.row + 1,
|
|
3128
|
+
exported,
|
|
3129
|
+
scope
|
|
3130
|
+
});
|
|
3131
|
+
}
|
|
3132
|
+
function processIndexerDeclaration(node, context) {
|
|
3133
|
+
const exported = hasModifier(node, context, "public");
|
|
3134
|
+
const scope = context.currentClass || void 0;
|
|
3135
|
+
const name = "this[]";
|
|
3136
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
3137
|
+
context.symbols.push({
|
|
3138
|
+
id: symbolId,
|
|
3139
|
+
name,
|
|
3140
|
+
kind: "property",
|
|
3141
|
+
filePath: context.filePath,
|
|
3142
|
+
startLine: node.startPosition.row + 1,
|
|
3143
|
+
endLine: node.endPosition.row + 1,
|
|
3144
|
+
exported,
|
|
3145
|
+
scope
|
|
3146
|
+
});
|
|
3147
|
+
}
|
|
3148
|
+
function processUsingDirective(node, context) {
|
|
3149
|
+
const nameNode = node.childForFieldName("name");
|
|
3150
|
+
if (!nameNode) return;
|
|
3151
|
+
const name = nodeText6(nameNode, context);
|
|
3152
|
+
const resolvedPath = resolveCSharpNamespace(name, context.filePath, context.projectRoot);
|
|
3153
|
+
if (resolvedPath) {
|
|
3154
|
+
const sourceId = `${context.filePath}::__file__`;
|
|
3155
|
+
const targetId = `${resolvedPath}::__file__`;
|
|
3156
|
+
context.edges.push({
|
|
3157
|
+
source: sourceId,
|
|
3158
|
+
target: targetId,
|
|
3159
|
+
kind: "imports",
|
|
3160
|
+
filePath: context.filePath,
|
|
3161
|
+
line: node.startPosition.row + 1
|
|
3162
|
+
});
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
function processGlobalStatement(node, context) {
|
|
3166
|
+
const symbolId = `${context.filePath}::__toplevel__`;
|
|
3167
|
+
if (context.symbols.find((s) => s.id === symbolId)) return;
|
|
3168
|
+
context.symbols.push({
|
|
3169
|
+
id: symbolId,
|
|
3170
|
+
name: "__toplevel__",
|
|
3171
|
+
kind: "function",
|
|
3172
|
+
filePath: context.filePath,
|
|
3173
|
+
startLine: node.startPosition.row + 1,
|
|
3174
|
+
endLine: node.endPosition.row + 1,
|
|
3175
|
+
exported: true
|
|
3176
|
+
});
|
|
3177
|
+
}
|
|
3178
|
+
function processCallExpression7(node, context) {
|
|
3179
|
+
const functionNode = node.childForFieldName("function");
|
|
3180
|
+
if (!functionNode) return;
|
|
3181
|
+
let calleeName = null;
|
|
3182
|
+
if (functionNode.type === "identifier") {
|
|
3183
|
+
calleeName = nodeText6(functionNode, context);
|
|
3184
|
+
} else if (functionNode.type === "member_access_expression") {
|
|
3185
|
+
const nameNode = functionNode.childForFieldName("name");
|
|
3186
|
+
if (nameNode) {
|
|
3187
|
+
calleeName = nodeText6(nameNode, context);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
if (!calleeName) return;
|
|
3191
|
+
const builtins = ["ToString", "Equals", "GetHashCode", "GetType", "Console", "Write", "WriteLine", "Format", "Parse", "TryParse"];
|
|
3192
|
+
if (builtins.includes(calleeName)) return;
|
|
3193
|
+
const callerId = getCurrentSymbolId7(context);
|
|
3194
|
+
if (!callerId) return;
|
|
3195
|
+
const calleeId = resolveSymbol6(calleeName, context);
|
|
3196
|
+
if (calleeId) {
|
|
3197
|
+
context.edges.push({
|
|
3198
|
+
source: callerId,
|
|
3199
|
+
target: calleeId,
|
|
3200
|
+
kind: "calls",
|
|
3201
|
+
filePath: context.filePath,
|
|
3202
|
+
line: node.startPosition.row + 1
|
|
3203
|
+
});
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
function parseCsprojFile(filePath, sourceCode, projectRoot) {
|
|
3207
|
+
const symbols = [];
|
|
3208
|
+
const edges = [];
|
|
3209
|
+
const lines = sourceCode.split("\n");
|
|
3210
|
+
const projectName = basename(filePath, ".csproj");
|
|
3211
|
+
symbols.push({
|
|
3212
|
+
id: `${filePath}::${projectName}`,
|
|
3213
|
+
name: projectName,
|
|
3214
|
+
kind: "module",
|
|
3215
|
+
filePath,
|
|
3216
|
+
startLine: 1,
|
|
3217
|
+
endLine: lines.length,
|
|
3218
|
+
exported: true
|
|
3219
|
+
});
|
|
3220
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3221
|
+
const line = lines[i];
|
|
3222
|
+
const lineNum = i + 1;
|
|
3223
|
+
const projectRefMatch = line.match(/<ProjectReference\s+Include\s*=\s*"([^"]+)"/);
|
|
3224
|
+
if (projectRefMatch) {
|
|
3225
|
+
const refPath = projectRefMatch[1];
|
|
3226
|
+
const csprojDir = dirname7(join8(projectRoot, filePath));
|
|
3227
|
+
const resolvedRef = resolve3(csprojDir, refPath);
|
|
3228
|
+
const relativeRef = resolvedRef.startsWith(projectRoot + "/") ? resolvedRef.substring(projectRoot.length + 1) : null;
|
|
3229
|
+
if (relativeRef && existsSync8(resolvedRef)) {
|
|
3230
|
+
edges.push({
|
|
3231
|
+
source: `${filePath}::__file__`,
|
|
3232
|
+
target: `${relativeRef}::__file__`,
|
|
3233
|
+
kind: "imports",
|
|
3234
|
+
filePath,
|
|
3235
|
+
line: lineNum
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
const packageRefMatch = line.match(/<PackageReference\s+Include\s*=\s*"([^"]+)"/);
|
|
3240
|
+
if (packageRefMatch) {
|
|
3241
|
+
const packageName = packageRefMatch[1];
|
|
3242
|
+
const versionMatch = line.match(/Version\s*=\s*"([^"]+)"/);
|
|
3243
|
+
const version = versionMatch ? versionMatch[1] : "unknown";
|
|
3244
|
+
symbols.push({
|
|
3245
|
+
id: `${filePath}::pkg:${packageName}`,
|
|
3246
|
+
name: `${packageName}@${version}`,
|
|
3247
|
+
kind: "import",
|
|
3248
|
+
filePath,
|
|
3249
|
+
startLine: lineNum,
|
|
3250
|
+
endLine: lineNum,
|
|
3251
|
+
exported: false
|
|
3252
|
+
});
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
return { filePath, symbols, edges };
|
|
3256
|
+
}
|
|
3257
|
+
function resolveCSharpNamespace(namespace, currentFile, projectRoot) {
|
|
3258
|
+
const namespacePath = namespace.replace(/\./g, "/");
|
|
3259
|
+
const candidates = [
|
|
3260
|
+
join8(projectRoot, namespacePath),
|
|
3261
|
+
join8(projectRoot, "src", namespacePath)
|
|
3262
|
+
];
|
|
3263
|
+
for (const candidate of candidates) {
|
|
3264
|
+
if (existsSync8(candidate)) {
|
|
3265
|
+
try {
|
|
3266
|
+
const stats = statSync2(candidate);
|
|
3267
|
+
if (stats.isDirectory()) {
|
|
3268
|
+
const csFiles = readdirSync5(candidate).filter((f) => f.endsWith(".cs"));
|
|
3269
|
+
if (csFiles.length > 0) {
|
|
3270
|
+
const fullPath = join8(candidate, csFiles[0]);
|
|
3271
|
+
return fullPath.substring(projectRoot.length + 1);
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
} catch {
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
return null;
|
|
3279
|
+
}
|
|
3280
|
+
function resolveSymbol6(name, context) {
|
|
3281
|
+
if (context.imports.has(name)) {
|
|
3282
|
+
return context.imports.get(name) || null;
|
|
3283
|
+
}
|
|
3284
|
+
const currentFileId = `${context.filePath}::${name}`;
|
|
3285
|
+
if (context.symbols.find((s) => s.id === currentFileId)) {
|
|
3286
|
+
return currentFileId;
|
|
3287
|
+
}
|
|
3288
|
+
if (context.currentClass) {
|
|
3289
|
+
const classMethodId = `${context.filePath}::${context.currentClass}.${name}`;
|
|
3290
|
+
if (context.symbols.find((s) => s.id === classMethodId)) {
|
|
3291
|
+
return classMethodId;
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
return null;
|
|
3295
|
+
}
|
|
3296
|
+
function hasModifier(node, context, modifier) {
|
|
3297
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3298
|
+
const child = node.child(i);
|
|
3299
|
+
if (child && child.type === "modifier" && nodeText6(child, context) === modifier) {
|
|
3300
|
+
return true;
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
if (modifier === "internal") {
|
|
3304
|
+
const hasExplicitAccess = ["public", "private", "protected", "internal"].some((m) => {
|
|
3305
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3306
|
+
const child = node.child(i);
|
|
3307
|
+
if (child && child.type === "modifier" && nodeText6(child, context) === m) return true;
|
|
3308
|
+
}
|
|
3309
|
+
return false;
|
|
3310
|
+
});
|
|
3311
|
+
return !hasExplicitAccess;
|
|
3312
|
+
}
|
|
3313
|
+
return false;
|
|
3314
|
+
}
|
|
3315
|
+
function extractBaseTypeName(node, context) {
|
|
3316
|
+
const text = nodeText6(node, context).trim();
|
|
3317
|
+
if (!text || text === ":" || text === ",") return null;
|
|
3318
|
+
const angleBracketIdx = text.indexOf("<");
|
|
3319
|
+
const name = angleBracketIdx > 0 ? text.substring(0, angleBracketIdx) : text;
|
|
3320
|
+
const dotIdx = name.lastIndexOf(".");
|
|
3321
|
+
return dotIdx >= 0 ? name.substring(dotIdx + 1) : name;
|
|
3322
|
+
}
|
|
3323
|
+
function findChildByType7(node, type) {
|
|
3324
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3325
|
+
const child = node.child(i);
|
|
3326
|
+
if (child && child.type === type) return child;
|
|
3327
|
+
}
|
|
3328
|
+
return null;
|
|
3329
|
+
}
|
|
3330
|
+
function findChildrenByType2(node, type) {
|
|
3331
|
+
const results = [];
|
|
3332
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3333
|
+
const child = node.child(i);
|
|
3334
|
+
if (child && child.type === type) results.push(child);
|
|
3335
|
+
}
|
|
3336
|
+
return results;
|
|
3337
|
+
}
|
|
3338
|
+
function nodeText6(node, context) {
|
|
3339
|
+
return context.sourceCode.substring(node.startIndex, node.endIndex);
|
|
3340
|
+
}
|
|
3341
|
+
function getCurrentSymbolId7(context) {
|
|
3342
|
+
if (context.currentScope.length === 0) return null;
|
|
3343
|
+
return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
|
|
3344
|
+
}
|
|
3345
|
+
var csharpParser = {
|
|
3346
|
+
name: "csharp",
|
|
3347
|
+
extensions: [".cs", ".csx", ".csproj"],
|
|
3348
|
+
parseFile: parseCSharpFile
|
|
3349
|
+
};
|
|
3350
|
+
|
|
3351
|
+
// src/parser/java.ts
|
|
3352
|
+
import { dirname as dirname8, join as join9, resolve as resolve4, basename as basename2 } from "path";
|
|
3353
|
+
import { existsSync as existsSync9, readdirSync as readdirSync6, statSync as statSync3 } from "fs";
|
|
3354
|
+
function parseJavaFile(filePath, sourceCode, projectRoot) {
|
|
3355
|
+
if (filePath.endsWith("pom.xml")) {
|
|
3356
|
+
return parsePomXml(filePath, sourceCode, projectRoot);
|
|
3357
|
+
}
|
|
3358
|
+
if (filePath.endsWith("build.gradle") || filePath.endsWith("build.gradle.kts")) {
|
|
3359
|
+
return parseGradleBuild(filePath, sourceCode, projectRoot);
|
|
3360
|
+
}
|
|
3361
|
+
const parser = getParser("java");
|
|
3362
|
+
const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
|
|
3363
|
+
const context = {
|
|
3364
|
+
filePath,
|
|
3365
|
+
projectRoot,
|
|
3366
|
+
sourceCode,
|
|
3367
|
+
symbols: [],
|
|
3368
|
+
edges: [],
|
|
3369
|
+
currentScope: [],
|
|
3370
|
+
currentClass: null,
|
|
3371
|
+
currentPackage: null,
|
|
3372
|
+
imports: /* @__PURE__ */ new Map(),
|
|
3373
|
+
isBuildFile: false
|
|
3374
|
+
};
|
|
3375
|
+
walkNode8(tree.rootNode, context);
|
|
3376
|
+
return {
|
|
3377
|
+
filePath,
|
|
3378
|
+
symbols: context.symbols,
|
|
3379
|
+
edges: context.edges
|
|
3380
|
+
};
|
|
3381
|
+
}
|
|
3382
|
+
function walkNode8(node, context) {
|
|
3383
|
+
processNode8(node, context);
|
|
3384
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3385
|
+
const child = node.child(i);
|
|
3386
|
+
if (child) {
|
|
3387
|
+
walkNode8(child, context);
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
function processNode8(node, context) {
|
|
3392
|
+
switch (node.type) {
|
|
3393
|
+
case "package_declaration":
|
|
3394
|
+
processPackageDeclaration(node, context);
|
|
3395
|
+
break;
|
|
3396
|
+
case "import_declaration":
|
|
3397
|
+
processImportDeclaration2(node, context);
|
|
3398
|
+
break;
|
|
3399
|
+
case "class_declaration":
|
|
3400
|
+
processClassDeclaration4(node, context);
|
|
3401
|
+
break;
|
|
3402
|
+
case "interface_declaration":
|
|
3403
|
+
processInterfaceDeclaration3(node, context);
|
|
3404
|
+
break;
|
|
3405
|
+
case "enum_declaration":
|
|
3406
|
+
processEnumDeclaration3(node, context);
|
|
3407
|
+
break;
|
|
3408
|
+
case "annotation_type_declaration":
|
|
3409
|
+
processAnnotationTypeDeclaration(node, context);
|
|
3410
|
+
break;
|
|
3411
|
+
case "record_declaration":
|
|
3412
|
+
processRecordDeclaration2(node, context);
|
|
3413
|
+
break;
|
|
3414
|
+
case "method_declaration":
|
|
3415
|
+
processMethodDeclaration3(node, context);
|
|
3416
|
+
break;
|
|
3417
|
+
case "constructor_declaration":
|
|
3418
|
+
processConstructorDeclaration2(node, context);
|
|
3419
|
+
break;
|
|
3420
|
+
case "field_declaration":
|
|
3421
|
+
processFieldDeclaration(node, context);
|
|
3422
|
+
break;
|
|
3423
|
+
case "constant_declaration":
|
|
3424
|
+
processConstantDeclaration(node, context);
|
|
3425
|
+
break;
|
|
3426
|
+
case "annotation_type_element_declaration":
|
|
3427
|
+
processAnnotationElement(node, context);
|
|
3428
|
+
break;
|
|
3429
|
+
case "method_invocation":
|
|
3430
|
+
processCallExpression8(node, context);
|
|
3431
|
+
break;
|
|
3432
|
+
case "object_creation_expression":
|
|
3433
|
+
processObjectCreation(node, context);
|
|
3434
|
+
break;
|
|
3435
|
+
case "lambda_expression":
|
|
3436
|
+
processLambdaExpression(node, context);
|
|
3437
|
+
break;
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
function processPackageDeclaration(node, context) {
|
|
3441
|
+
const scopedIdent = findDescendantByTypes(node, ["scoped_identifier", "identifier"]);
|
|
3442
|
+
if (!scopedIdent) return;
|
|
3443
|
+
const name = nodeText7(scopedIdent, context);
|
|
3444
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
3445
|
+
context.symbols.push({
|
|
3446
|
+
id: symbolId,
|
|
3447
|
+
name,
|
|
3448
|
+
kind: "module",
|
|
3449
|
+
filePath: context.filePath,
|
|
3450
|
+
startLine: node.startPosition.row + 1,
|
|
3451
|
+
endLine: node.endPosition.row + 1,
|
|
3452
|
+
exported: true
|
|
3453
|
+
});
|
|
3454
|
+
context.currentPackage = name;
|
|
3455
|
+
}
|
|
3456
|
+
function processImportDeclaration2(node, context) {
|
|
3457
|
+
const text = nodeText7(node, context).trim();
|
|
3458
|
+
const isStatic = text.includes("import static");
|
|
3459
|
+
const isWildcard = text.includes(".*");
|
|
3460
|
+
const scopedIdent = findDescendantByTypes(node, ["scoped_identifier", "identifier"]);
|
|
3461
|
+
if (!scopedIdent) return;
|
|
3462
|
+
let importPath = nodeText7(scopedIdent, context);
|
|
3463
|
+
const asterisk = findChildByType8(node, "asterisk");
|
|
3464
|
+
if (asterisk) {
|
|
3465
|
+
importPath = importPath + ".*";
|
|
3466
|
+
}
|
|
3467
|
+
const resolvedPath = resolveJavaImport(importPath, context.filePath, context.projectRoot);
|
|
3468
|
+
if (resolvedPath) {
|
|
3469
|
+
const sourceId = `${context.filePath}::__file__`;
|
|
3470
|
+
const targetId = `${resolvedPath}::__file__`;
|
|
3471
|
+
context.edges.push({
|
|
3472
|
+
source: sourceId,
|
|
3473
|
+
target: targetId,
|
|
3474
|
+
kind: "imports",
|
|
3475
|
+
filePath: context.filePath,
|
|
3476
|
+
line: node.startPosition.row + 1
|
|
3477
|
+
});
|
|
3478
|
+
const parts = importPath.split(".");
|
|
3479
|
+
if (!isWildcard) {
|
|
3480
|
+
const simpleName = parts[parts.length - 1];
|
|
3481
|
+
context.imports.set(simpleName, `${resolvedPath}::${simpleName}`);
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
const symbolId = `${context.filePath}::import:${importPath}`;
|
|
3485
|
+
context.symbols.push({
|
|
3486
|
+
id: symbolId,
|
|
3487
|
+
name: importPath,
|
|
3488
|
+
kind: "import",
|
|
3489
|
+
filePath: context.filePath,
|
|
3490
|
+
startLine: node.startPosition.row + 1,
|
|
3491
|
+
endLine: node.endPosition.row + 1,
|
|
3492
|
+
exported: false
|
|
3493
|
+
});
|
|
3494
|
+
}
|
|
3495
|
+
function processClassDeclaration4(node, context) {
|
|
3496
|
+
processTypeDeclaration3(node, context, "class");
|
|
3497
|
+
}
|
|
3498
|
+
function processInterfaceDeclaration3(node, context) {
|
|
3499
|
+
processTypeDeclaration3(node, context, "interface");
|
|
3500
|
+
}
|
|
3501
|
+
function processRecordDeclaration2(node, context) {
|
|
3502
|
+
processTypeDeclaration3(node, context, "class");
|
|
3503
|
+
}
|
|
3504
|
+
function processAnnotationTypeDeclaration(node, context) {
|
|
3505
|
+
processTypeDeclaration3(node, context, "interface");
|
|
3506
|
+
}
|
|
3507
|
+
function processTypeDeclaration3(node, context, kind) {
|
|
3508
|
+
const nameNode = node.childForFieldName("name");
|
|
3509
|
+
if (!nameNode) return;
|
|
3510
|
+
let name = nodeText7(nameNode, context);
|
|
3511
|
+
const angleBracketIdx = name.indexOf("<");
|
|
3512
|
+
if (angleBracketIdx > 0) {
|
|
3513
|
+
name = name.substring(0, angleBracketIdx);
|
|
3514
|
+
}
|
|
3515
|
+
const exported = hasModifier2(node, context, "public");
|
|
3516
|
+
const scope = context.currentClass || void 0;
|
|
3517
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
3518
|
+
context.symbols.push({
|
|
3519
|
+
id: symbolId,
|
|
3520
|
+
name,
|
|
3521
|
+
kind,
|
|
3522
|
+
filePath: context.filePath,
|
|
3523
|
+
startLine: node.startPosition.row + 1,
|
|
3524
|
+
endLine: node.endPosition.row + 1,
|
|
3525
|
+
exported,
|
|
3526
|
+
scope
|
|
3527
|
+
});
|
|
3528
|
+
const superclass = node.childForFieldName("superclass");
|
|
3529
|
+
if (superclass) {
|
|
3530
|
+
let baseName = extractTypeName3(superclass, context);
|
|
3531
|
+
if (baseName) {
|
|
3532
|
+
const baseId = resolveSymbol7(baseName, context);
|
|
3533
|
+
if (baseId) {
|
|
3534
|
+
context.edges.push({
|
|
3535
|
+
source: symbolId,
|
|
3536
|
+
target: baseId,
|
|
3537
|
+
kind: "inherits",
|
|
3538
|
+
filePath: context.filePath,
|
|
3539
|
+
line: superclass.startPosition.row + 1
|
|
3540
|
+
});
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
const interfaces = node.childForFieldName("interfaces");
|
|
3545
|
+
if (interfaces) {
|
|
3546
|
+
processInterfaceList(interfaces, symbolId, context);
|
|
3547
|
+
}
|
|
3548
|
+
const extendsInterfaces = node.childForFieldName("extends_interfaces") || findChildByType8(node, "extends_interfaces");
|
|
3549
|
+
if (extendsInterfaces) {
|
|
3550
|
+
processInterfaceList(extendsInterfaces, symbolId, context);
|
|
3551
|
+
}
|
|
3552
|
+
const oldClass = context.currentClass;
|
|
3553
|
+
context.currentClass = name;
|
|
3554
|
+
context.currentScope.push(name);
|
|
3555
|
+
const body = node.childForFieldName("body") || findChildByType8(node, "class_body") || findChildByType8(node, "interface_body") || findChildByType8(node, "enum_body") || findChildByType8(node, "annotation_type_body") || findChildByType8(node, "record_declaration_body");
|
|
3556
|
+
if (body) {
|
|
3557
|
+
walkNode8(body, context);
|
|
3558
|
+
}
|
|
3559
|
+
context.currentScope.pop();
|
|
3560
|
+
context.currentClass = oldClass;
|
|
3561
|
+
}
|
|
3562
|
+
function processInterfaceList(node, sourceId, context) {
|
|
3563
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3564
|
+
const child = node.child(i);
|
|
3565
|
+
if (!child) continue;
|
|
3566
|
+
if (child.type === "type_identifier" || child.type === "generic_type" || child.type === "scoped_type_identifier") {
|
|
3567
|
+
const baseName = extractTypeName3(child, context);
|
|
3568
|
+
if (baseName) {
|
|
3569
|
+
const baseId = resolveSymbol7(baseName, context);
|
|
3570
|
+
if (baseId) {
|
|
3571
|
+
context.edges.push({
|
|
3572
|
+
source: sourceId,
|
|
3573
|
+
target: baseId,
|
|
3574
|
+
kind: "implements",
|
|
3575
|
+
filePath: context.filePath,
|
|
3576
|
+
line: child.startPosition.row + 1
|
|
3577
|
+
});
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
function processEnumDeclaration3(node, context) {
|
|
3584
|
+
const nameNode = node.childForFieldName("name");
|
|
3585
|
+
if (!nameNode) return;
|
|
3586
|
+
const name = nodeText7(nameNode, context);
|
|
3587
|
+
const exported = hasModifier2(node, context, "public");
|
|
3588
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
3589
|
+
context.symbols.push({
|
|
3590
|
+
id: symbolId,
|
|
3591
|
+
name,
|
|
3592
|
+
kind: "enum",
|
|
3593
|
+
filePath: context.filePath,
|
|
3594
|
+
startLine: node.startPosition.row + 1,
|
|
3595
|
+
endLine: node.endPosition.row + 1,
|
|
3596
|
+
exported
|
|
3597
|
+
});
|
|
3598
|
+
const body = node.childForFieldName("body") || findChildByType8(node, "enum_body");
|
|
3599
|
+
if (body) {
|
|
3600
|
+
const constants = findChildrenByType3(body, "enum_constant");
|
|
3601
|
+
for (const constant of constants) {
|
|
3602
|
+
const constNameNode = constant.childForFieldName("name");
|
|
3603
|
+
if (!constNameNode) continue;
|
|
3604
|
+
const constName = nodeText7(constNameNode, context);
|
|
3605
|
+
const constId = `${context.filePath}::${name}.${constName}`;
|
|
3606
|
+
context.symbols.push({
|
|
3607
|
+
id: constId,
|
|
3608
|
+
name: constName,
|
|
3609
|
+
kind: "constant",
|
|
3610
|
+
filePath: context.filePath,
|
|
3611
|
+
startLine: constant.startPosition.row + 1,
|
|
3612
|
+
endLine: constant.endPosition.row + 1,
|
|
3613
|
+
exported,
|
|
3614
|
+
scope: name
|
|
3615
|
+
});
|
|
3616
|
+
}
|
|
3617
|
+
const oldClass = context.currentClass;
|
|
3618
|
+
context.currentClass = name;
|
|
3619
|
+
context.currentScope.push(name);
|
|
3620
|
+
for (let i = 0; i < body.childCount; i++) {
|
|
3621
|
+
const child = body.child(i);
|
|
3622
|
+
if (child && child.type !== "enum_constant") {
|
|
3623
|
+
walkNode8(child, context);
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
context.currentScope.pop();
|
|
3627
|
+
context.currentClass = oldClass;
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
function processMethodDeclaration3(node, context) {
|
|
3631
|
+
const nameNode = node.childForFieldName("name");
|
|
3632
|
+
if (!nameNode) return;
|
|
3633
|
+
const name = nodeText7(nameNode, context);
|
|
3634
|
+
const exported = hasModifier2(node, context, "public");
|
|
3635
|
+
const scope = context.currentClass || void 0;
|
|
3636
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
3637
|
+
context.symbols.push({
|
|
3638
|
+
id: symbolId,
|
|
3639
|
+
name,
|
|
3640
|
+
kind: context.currentClass ? "method" : "function",
|
|
3641
|
+
filePath: context.filePath,
|
|
3642
|
+
startLine: node.startPosition.row + 1,
|
|
3643
|
+
endLine: node.endPosition.row + 1,
|
|
3644
|
+
exported,
|
|
3645
|
+
scope
|
|
3646
|
+
});
|
|
3647
|
+
const scopeName = scope ? `${scope}.${name}` : name;
|
|
3648
|
+
context.currentScope.push(scopeName);
|
|
3649
|
+
const body = node.childForFieldName("body");
|
|
3650
|
+
if (body) {
|
|
3651
|
+
walkNode8(body, context);
|
|
3652
|
+
}
|
|
3653
|
+
context.currentScope.pop();
|
|
3654
|
+
}
|
|
3655
|
+
function processConstructorDeclaration2(node, context) {
|
|
3656
|
+
const nameNode = node.childForFieldName("name");
|
|
3657
|
+
if (!nameNode) return;
|
|
3658
|
+
const name = nodeText7(nameNode, context);
|
|
3659
|
+
const exported = hasModifier2(node, context, "public");
|
|
3660
|
+
const scope = context.currentClass || void 0;
|
|
3661
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
3662
|
+
context.symbols.push({
|
|
3663
|
+
id: symbolId,
|
|
3664
|
+
name,
|
|
3665
|
+
kind: "method",
|
|
3666
|
+
filePath: context.filePath,
|
|
3667
|
+
startLine: node.startPosition.row + 1,
|
|
3668
|
+
endLine: node.endPosition.row + 1,
|
|
3669
|
+
exported,
|
|
3670
|
+
scope
|
|
3671
|
+
});
|
|
3672
|
+
const scopeName = scope ? `${scope}.${name}` : name;
|
|
3673
|
+
context.currentScope.push(scopeName);
|
|
3674
|
+
const body = node.childForFieldName("body");
|
|
3675
|
+
if (body) {
|
|
3676
|
+
walkNode8(body, context);
|
|
3677
|
+
}
|
|
3678
|
+
context.currentScope.pop();
|
|
3679
|
+
}
|
|
3680
|
+
function processFieldDeclaration(node, context) {
|
|
3681
|
+
const declarator = findDescendantByTypes(node, ["variable_declarator"]);
|
|
3682
|
+
if (!declarator) return;
|
|
3683
|
+
const nameNode = declarator.childForFieldName("name");
|
|
3684
|
+
if (!nameNode) return;
|
|
3685
|
+
const name = nodeText7(nameNode, context);
|
|
3686
|
+
const exported = hasModifier2(node, context, "public");
|
|
3687
|
+
const scope = context.currentClass || void 0;
|
|
3688
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
3689
|
+
const isConstant = hasModifier2(node, context, "static") && hasModifier2(node, context, "final");
|
|
3690
|
+
context.symbols.push({
|
|
3691
|
+
id: symbolId,
|
|
3692
|
+
name,
|
|
3693
|
+
kind: isConstant ? "constant" : "property",
|
|
3694
|
+
filePath: context.filePath,
|
|
3695
|
+
startLine: node.startPosition.row + 1,
|
|
3696
|
+
endLine: node.endPosition.row + 1,
|
|
3697
|
+
exported,
|
|
3698
|
+
scope
|
|
3699
|
+
});
|
|
3700
|
+
}
|
|
3701
|
+
function processConstantDeclaration(node, context) {
|
|
3702
|
+
const declarator = findDescendantByTypes(node, ["variable_declarator"]);
|
|
3703
|
+
if (!declarator) return;
|
|
3704
|
+
const nameNode = declarator.childForFieldName("name");
|
|
3705
|
+
if (!nameNode) return;
|
|
3706
|
+
const name = nodeText7(nameNode, context);
|
|
3707
|
+
const scope = context.currentClass || void 0;
|
|
3708
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
3709
|
+
context.symbols.push({
|
|
3710
|
+
id: symbolId,
|
|
3711
|
+
name,
|
|
3712
|
+
kind: "constant",
|
|
3713
|
+
filePath: context.filePath,
|
|
3714
|
+
startLine: node.startPosition.row + 1,
|
|
3715
|
+
endLine: node.endPosition.row + 1,
|
|
3716
|
+
exported: true,
|
|
3717
|
+
// Interface constants are always public
|
|
3718
|
+
scope
|
|
3719
|
+
});
|
|
3720
|
+
}
|
|
3721
|
+
function processAnnotationElement(node, context) {
|
|
3722
|
+
const nameNode = node.childForFieldName("name");
|
|
3723
|
+
if (!nameNode) return;
|
|
3724
|
+
const name = nodeText7(nameNode, context);
|
|
3725
|
+
const scope = context.currentClass || void 0;
|
|
3726
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
3727
|
+
context.symbols.push({
|
|
3728
|
+
id: symbolId,
|
|
3729
|
+
name,
|
|
3730
|
+
kind: "method",
|
|
3731
|
+
filePath: context.filePath,
|
|
3732
|
+
startLine: node.startPosition.row + 1,
|
|
3733
|
+
endLine: node.endPosition.row + 1,
|
|
3734
|
+
exported: true,
|
|
3735
|
+
scope
|
|
3736
|
+
});
|
|
3737
|
+
}
|
|
3738
|
+
function processCallExpression8(node, context) {
|
|
3739
|
+
const nameNode = node.childForFieldName("name");
|
|
3740
|
+
if (!nameNode) return;
|
|
3741
|
+
const calleeName = nodeText7(nameNode, context);
|
|
3742
|
+
const builtins = [
|
|
3743
|
+
"toString",
|
|
3744
|
+
"equals",
|
|
3745
|
+
"hashCode",
|
|
3746
|
+
"getClass",
|
|
3747
|
+
"println",
|
|
3748
|
+
"printf",
|
|
3749
|
+
"format",
|
|
3750
|
+
"parseInt",
|
|
3751
|
+
"valueOf",
|
|
3752
|
+
"length",
|
|
3753
|
+
"size",
|
|
3754
|
+
"get",
|
|
3755
|
+
"set",
|
|
3756
|
+
"add",
|
|
3757
|
+
"remove",
|
|
3758
|
+
"contains",
|
|
3759
|
+
"isEmpty",
|
|
3760
|
+
"stream",
|
|
3761
|
+
"collect",
|
|
3762
|
+
"map",
|
|
3763
|
+
"filter",
|
|
3764
|
+
"forEach",
|
|
3765
|
+
"of",
|
|
3766
|
+
"orElse",
|
|
3767
|
+
"orElseThrow",
|
|
3768
|
+
"isPresent",
|
|
3769
|
+
"ifPresent",
|
|
3770
|
+
"close",
|
|
3771
|
+
"flush",
|
|
3772
|
+
"write",
|
|
3773
|
+
"read"
|
|
3774
|
+
];
|
|
3775
|
+
if (builtins.includes(calleeName)) return;
|
|
3776
|
+
const callerId = getCurrentSymbolId8(context);
|
|
3777
|
+
if (!callerId) return;
|
|
3778
|
+
const calleeId = resolveSymbol7(calleeName, context);
|
|
3779
|
+
if (calleeId) {
|
|
3780
|
+
context.edges.push({
|
|
3781
|
+
source: callerId,
|
|
3782
|
+
target: calleeId,
|
|
3783
|
+
kind: "calls",
|
|
3784
|
+
filePath: context.filePath,
|
|
3785
|
+
line: node.startPosition.row + 1
|
|
3786
|
+
});
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
function processObjectCreation(node, context) {
|
|
3790
|
+
const typeNode = node.childForFieldName("type");
|
|
3791
|
+
if (!typeNode) return;
|
|
3792
|
+
const typeName = extractTypeName3(typeNode, context);
|
|
3793
|
+
if (!typeName) return;
|
|
3794
|
+
const callerId = getCurrentSymbolId8(context);
|
|
3795
|
+
if (!callerId) return;
|
|
3796
|
+
const targetId = resolveSymbol7(typeName, context);
|
|
3797
|
+
if (targetId) {
|
|
3798
|
+
context.edges.push({
|
|
3799
|
+
source: callerId,
|
|
3800
|
+
target: targetId,
|
|
3801
|
+
kind: "references",
|
|
3802
|
+
filePath: context.filePath,
|
|
3803
|
+
line: node.startPosition.row + 1
|
|
3804
|
+
});
|
|
3805
|
+
}
|
|
3806
|
+
const classBody = findChildByType8(node, "class_body");
|
|
3807
|
+
if (classBody) {
|
|
3808
|
+
const anonName = `<anonymous:${typeName}>`;
|
|
3809
|
+
const anonId = `${context.filePath}::${anonName}:${node.startPosition.row + 1}`;
|
|
3810
|
+
context.symbols.push({
|
|
3811
|
+
id: anonId,
|
|
3812
|
+
name: anonName,
|
|
3813
|
+
kind: "class",
|
|
3814
|
+
filePath: context.filePath,
|
|
3815
|
+
startLine: node.startPosition.row + 1,
|
|
3816
|
+
endLine: node.endPosition.row + 1,
|
|
3817
|
+
exported: false,
|
|
3818
|
+
scope: context.currentClass || void 0
|
|
3819
|
+
});
|
|
3820
|
+
const oldClass = context.currentClass;
|
|
3821
|
+
context.currentClass = anonName;
|
|
3822
|
+
context.currentScope.push(anonName);
|
|
3823
|
+
walkNode8(classBody, context);
|
|
3824
|
+
context.currentScope.pop();
|
|
3825
|
+
context.currentClass = oldClass;
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
function processLambdaExpression(node, context) {
|
|
3829
|
+
const parent = node.parent;
|
|
3830
|
+
if (!parent) return;
|
|
3831
|
+
if (parent.type === "variable_declarator") {
|
|
3832
|
+
const nameNode = parent.childForFieldName("name");
|
|
3833
|
+
if (nameNode) {
|
|
3834
|
+
const name = nodeText7(nameNode, context);
|
|
3835
|
+
const scope = context.currentClass || void 0;
|
|
3836
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
3837
|
+
context.symbols.push({
|
|
3838
|
+
id: symbolId,
|
|
3839
|
+
name,
|
|
3840
|
+
kind: "function",
|
|
3841
|
+
filePath: context.filePath,
|
|
3842
|
+
startLine: node.startPosition.row + 1,
|
|
3843
|
+
endLine: node.endPosition.row + 1,
|
|
3844
|
+
exported: false,
|
|
3845
|
+
scope
|
|
3846
|
+
});
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
function parsePomXml(filePath, sourceCode, projectRoot) {
|
|
3851
|
+
const symbols = [];
|
|
3852
|
+
const edges = [];
|
|
3853
|
+
const lines = sourceCode.split("\n");
|
|
3854
|
+
const projectName = basename2(dirname8(join9(projectRoot, filePath)));
|
|
3855
|
+
symbols.push({
|
|
3856
|
+
id: `${filePath}::${projectName}`,
|
|
3857
|
+
name: projectName,
|
|
3858
|
+
kind: "module",
|
|
3859
|
+
filePath,
|
|
3860
|
+
startLine: 1,
|
|
3861
|
+
endLine: lines.length,
|
|
3862
|
+
exported: true
|
|
3863
|
+
});
|
|
3864
|
+
let inDependency = false;
|
|
3865
|
+
let groupId = "";
|
|
3866
|
+
let artifactId = "";
|
|
3867
|
+
let version = "";
|
|
3868
|
+
let depStartLine = 0;
|
|
3869
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3870
|
+
const line = lines[i];
|
|
3871
|
+
const lineNum = i + 1;
|
|
3872
|
+
if (/<dependency>/.test(line)) {
|
|
3873
|
+
inDependency = true;
|
|
3874
|
+
groupId = "";
|
|
3875
|
+
artifactId = "";
|
|
3876
|
+
version = "";
|
|
3877
|
+
depStartLine = lineNum;
|
|
3878
|
+
}
|
|
3879
|
+
if (inDependency) {
|
|
3880
|
+
const gMatch = line.match(/<groupId>([^<]+)<\/groupId>/);
|
|
3881
|
+
if (gMatch) groupId = gMatch[1];
|
|
3882
|
+
const aMatch = line.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
3883
|
+
if (aMatch) artifactId = aMatch[1];
|
|
3884
|
+
const vMatch = line.match(/<version>([^<]+)<\/version>/);
|
|
3885
|
+
if (vMatch) version = vMatch[1];
|
|
3886
|
+
}
|
|
3887
|
+
if (/<\/dependency>/.test(line) && inDependency) {
|
|
3888
|
+
inDependency = false;
|
|
3889
|
+
if (groupId && artifactId) {
|
|
3890
|
+
const depName = `${groupId}:${artifactId}`;
|
|
3891
|
+
const displayVersion = version || "managed";
|
|
3892
|
+
symbols.push({
|
|
3893
|
+
id: `${filePath}::dep:${depName}`,
|
|
3894
|
+
name: `${depName}@${displayVersion}`,
|
|
3895
|
+
kind: "import",
|
|
3896
|
+
filePath,
|
|
3897
|
+
startLine: depStartLine,
|
|
3898
|
+
endLine: lineNum,
|
|
3899
|
+
exported: false
|
|
3900
|
+
});
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
const moduleMatch = line.match(/<module>([^<]+)<\/module>/);
|
|
3904
|
+
if (moduleMatch) {
|
|
3905
|
+
const modulePath = moduleMatch[1];
|
|
3906
|
+
const pomDir = dirname8(join9(projectRoot, filePath));
|
|
3907
|
+
const resolvedModule = resolve4(pomDir, modulePath);
|
|
3908
|
+
const relativeModule = resolvedModule.startsWith(projectRoot + "/") ? resolvedModule.substring(projectRoot.length + 1) : null;
|
|
3909
|
+
if (relativeModule) {
|
|
3910
|
+
const modulePom = join9(relativeModule, "pom.xml");
|
|
3911
|
+
if (existsSync9(join9(projectRoot, modulePom))) {
|
|
3912
|
+
edges.push({
|
|
3913
|
+
source: `${filePath}::__file__`,
|
|
3914
|
+
target: `${modulePom}::__file__`,
|
|
3915
|
+
kind: "imports",
|
|
3916
|
+
filePath,
|
|
3917
|
+
line: lineNum
|
|
3918
|
+
});
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
return { filePath, symbols, edges };
|
|
3924
|
+
}
|
|
3925
|
+
function parseGradleBuild(filePath, sourceCode, projectRoot) {
|
|
3926
|
+
const symbols = [];
|
|
3927
|
+
const edges = [];
|
|
3928
|
+
const lines = sourceCode.split("\n");
|
|
3929
|
+
const projectName = basename2(dirname8(join9(projectRoot, filePath)));
|
|
3930
|
+
symbols.push({
|
|
3931
|
+
id: `${filePath}::${projectName}`,
|
|
3932
|
+
name: projectName,
|
|
3933
|
+
kind: "module",
|
|
3934
|
+
filePath,
|
|
3935
|
+
startLine: 1,
|
|
3936
|
+
endLine: lines.length,
|
|
3937
|
+
exported: true
|
|
3938
|
+
});
|
|
3939
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3940
|
+
const line = lines[i].trim();
|
|
3941
|
+
const lineNum = i + 1;
|
|
3942
|
+
const depMatch = line.match(
|
|
3943
|
+
/(?:implementation|api|compileOnly|runtimeOnly|testImplementation|testRuntimeOnly|annotationProcessor)\s*[\(]?\s*['"]([^'"]+)['"]\s*[\)]?/
|
|
3944
|
+
);
|
|
3945
|
+
if (depMatch) {
|
|
3946
|
+
const depCoord = depMatch[1];
|
|
3947
|
+
if (!depCoord.startsWith(":")) {
|
|
3948
|
+
symbols.push({
|
|
3949
|
+
id: `${filePath}::dep:${depCoord}`,
|
|
3950
|
+
name: depCoord,
|
|
3951
|
+
kind: "import",
|
|
3952
|
+
filePath,
|
|
3953
|
+
startLine: lineNum,
|
|
3954
|
+
endLine: lineNum,
|
|
3955
|
+
exported: false
|
|
3956
|
+
});
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
const projectMatch = line.match(/project\s*\(\s*['":]+([^'")\s]+)['"]*\s*\)/);
|
|
3960
|
+
if (projectMatch) {
|
|
3961
|
+
const moduleName = projectMatch[1].replace(/^:/, "");
|
|
3962
|
+
const candidates = [
|
|
3963
|
+
join9(moduleName, "build.gradle"),
|
|
3964
|
+
join9(moduleName, "build.gradle.kts")
|
|
3965
|
+
];
|
|
3966
|
+
for (const candidate of candidates) {
|
|
3967
|
+
if (existsSync9(join9(projectRoot, candidate))) {
|
|
3968
|
+
edges.push({
|
|
3969
|
+
source: `${filePath}::__file__`,
|
|
3970
|
+
target: `${candidate}::__file__`,
|
|
3971
|
+
kind: "imports",
|
|
3972
|
+
filePath,
|
|
3973
|
+
line: lineNum
|
|
3974
|
+
});
|
|
3975
|
+
break;
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
return { filePath, symbols, edges };
|
|
3981
|
+
}
|
|
3982
|
+
function resolveJavaImport(importPath, currentFile, projectRoot) {
|
|
3983
|
+
const cleanPath2 = importPath.replace(/\.\*$/, "");
|
|
3984
|
+
const javaPath = cleanPath2.replace(/\./g, "/") + ".java";
|
|
3985
|
+
const sourceRoots = [
|
|
3986
|
+
"",
|
|
3987
|
+
"src/main/java",
|
|
3988
|
+
"src",
|
|
3989
|
+
"app/src/main/java"
|
|
3990
|
+
];
|
|
3991
|
+
for (const root of sourceRoots) {
|
|
3992
|
+
const candidate = root ? join9(root, javaPath) : javaPath;
|
|
3993
|
+
const fullPath = join9(projectRoot, candidate);
|
|
3994
|
+
if (existsSync9(fullPath)) {
|
|
3995
|
+
return candidate;
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
if (importPath.endsWith(".*")) {
|
|
3999
|
+
const packagePath = cleanPath2.replace(/\./g, "/");
|
|
4000
|
+
for (const root of sourceRoots) {
|
|
4001
|
+
const candidate = root ? join9(root, packagePath) : packagePath;
|
|
4002
|
+
const fullPath = join9(projectRoot, candidate);
|
|
4003
|
+
if (existsSync9(fullPath)) {
|
|
4004
|
+
try {
|
|
4005
|
+
const stats = statSync3(fullPath);
|
|
4006
|
+
if (stats.isDirectory()) {
|
|
4007
|
+
const javaFiles = readdirSync6(fullPath).filter((f) => f.endsWith(".java"));
|
|
4008
|
+
if (javaFiles.length > 0) {
|
|
4009
|
+
return join9(candidate, javaFiles[0]);
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
} catch {
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
return null;
|
|
4018
|
+
}
|
|
4019
|
+
function resolveSymbol7(name, context) {
|
|
4020
|
+
if (context.imports.has(name)) {
|
|
4021
|
+
return context.imports.get(name) || null;
|
|
4022
|
+
}
|
|
4023
|
+
const currentFileId = `${context.filePath}::${name}`;
|
|
4024
|
+
if (context.symbols.find((s) => s.id === currentFileId)) {
|
|
4025
|
+
return currentFileId;
|
|
4026
|
+
}
|
|
4027
|
+
if (context.currentClass) {
|
|
4028
|
+
const classMethodId = `${context.filePath}::${context.currentClass}.${name}`;
|
|
4029
|
+
if (context.symbols.find((s) => s.id === classMethodId)) {
|
|
4030
|
+
return classMethodId;
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
return null;
|
|
4034
|
+
}
|
|
4035
|
+
function hasModifier2(node, context, modifier) {
|
|
4036
|
+
const modifiers = node.childForFieldName("modifiers") || findChildByType8(node, "modifiers");
|
|
4037
|
+
if (modifiers) {
|
|
4038
|
+
for (let i = 0; i < modifiers.childCount; i++) {
|
|
4039
|
+
const child = modifiers.child(i);
|
|
4040
|
+
if (child && nodeText7(child, context) === modifier) {
|
|
4041
|
+
return true;
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
4046
|
+
const child = node.child(i);
|
|
4047
|
+
if (child && child.type === modifier) {
|
|
4048
|
+
return true;
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4051
|
+
return false;
|
|
4052
|
+
}
|
|
4053
|
+
function extractTypeName3(node, context) {
|
|
4054
|
+
const text = nodeText7(node, context).trim();
|
|
4055
|
+
if (!text) return null;
|
|
4056
|
+
const angleBracketIdx = text.indexOf("<");
|
|
4057
|
+
const name = angleBracketIdx > 0 ? text.substring(0, angleBracketIdx) : text;
|
|
4058
|
+
const dotIdx = name.lastIndexOf(".");
|
|
4059
|
+
return dotIdx >= 0 ? name.substring(dotIdx + 1) : name;
|
|
4060
|
+
}
|
|
4061
|
+
function findChildByType8(node, type) {
|
|
4062
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
4063
|
+
const child = node.child(i);
|
|
4064
|
+
if (child && child.type === type) return child;
|
|
4065
|
+
}
|
|
4066
|
+
return null;
|
|
4067
|
+
}
|
|
4068
|
+
function findChildrenByType3(node, type) {
|
|
4069
|
+
const results = [];
|
|
4070
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
4071
|
+
const child = node.child(i);
|
|
4072
|
+
if (child && child.type === type) results.push(child);
|
|
4073
|
+
}
|
|
4074
|
+
return results;
|
|
4075
|
+
}
|
|
4076
|
+
function findDescendantByTypes(node, types) {
|
|
4077
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
4078
|
+
const child = node.child(i);
|
|
4079
|
+
if (!child) continue;
|
|
4080
|
+
if (types.includes(child.type)) return child;
|
|
4081
|
+
const found = findDescendantByTypes(child, types);
|
|
4082
|
+
if (found) return found;
|
|
4083
|
+
}
|
|
4084
|
+
return null;
|
|
4085
|
+
}
|
|
4086
|
+
function nodeText7(node, context) {
|
|
4087
|
+
return context.sourceCode.substring(node.startIndex, node.endIndex);
|
|
4088
|
+
}
|
|
4089
|
+
function getCurrentSymbolId8(context) {
|
|
4090
|
+
if (context.currentScope.length === 0) return null;
|
|
4091
|
+
return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
|
|
4092
|
+
}
|
|
4093
|
+
var javaParser = {
|
|
4094
|
+
name: "java",
|
|
4095
|
+
extensions: [".java", "pom.xml", "build.gradle", "build.gradle.kts"],
|
|
4096
|
+
parseFile: parseJavaFile
|
|
4097
|
+
};
|
|
4098
|
+
|
|
2785
4099
|
// src/parser/detect.ts
|
|
2786
4100
|
var parsers = [
|
|
2787
4101
|
typescriptParser,
|
|
@@ -2789,11 +4103,14 @@ var parsers = [
|
|
|
2789
4103
|
javascriptParser,
|
|
2790
4104
|
goParser,
|
|
2791
4105
|
rustParser,
|
|
2792
|
-
cParser
|
|
4106
|
+
cParser,
|
|
4107
|
+
csharpParser,
|
|
4108
|
+
javaParser
|
|
2793
4109
|
];
|
|
2794
4110
|
function getParserForFile(filePath) {
|
|
2795
|
-
const ext =
|
|
2796
|
-
|
|
4111
|
+
const ext = extname5(filePath).toLowerCase();
|
|
4112
|
+
const fileName = basename3(filePath);
|
|
4113
|
+
return parsers.find((p) => p.extensions.includes(ext) || p.extensions.includes(fileName)) || null;
|
|
2797
4114
|
}
|
|
2798
4115
|
|
|
2799
4116
|
// src/parser/index.ts
|
|
@@ -2801,7 +4118,7 @@ import { minimatch } from "minimatch";
|
|
|
2801
4118
|
var MAX_FILE_SIZE = 1e6;
|
|
2802
4119
|
function shouldParseFile(fullPath) {
|
|
2803
4120
|
try {
|
|
2804
|
-
const stats =
|
|
4121
|
+
const stats = statSync4(fullPath);
|
|
2805
4122
|
if (stats.size > MAX_FILE_SIZE) {
|
|
2806
4123
|
console.error(`[Parser] Skipping ${fullPath} \u2014 file too large (${(stats.size / 1024).toFixed(0)}KB)`);
|
|
2807
4124
|
return false;
|
|
@@ -2819,8 +4136,8 @@ async function parseProject(projectRoot, options) {
|
|
|
2819
4136
|
let errorFiles = 0;
|
|
2820
4137
|
for (const file of files) {
|
|
2821
4138
|
try {
|
|
2822
|
-
const fullPath =
|
|
2823
|
-
if (!
|
|
4139
|
+
const fullPath = join10(projectRoot, file);
|
|
4140
|
+
if (!resolve5(fullPath).startsWith(resolve5(projectRoot))) {
|
|
2824
4141
|
skippedFiles++;
|
|
2825
4142
|
continue;
|
|
2826
4143
|
}
|
|
@@ -2849,7 +4166,7 @@ async function parseProject(projectRoot, options) {
|
|
|
2849
4166
|
skippedFiles++;
|
|
2850
4167
|
continue;
|
|
2851
4168
|
}
|
|
2852
|
-
const sourceCode =
|
|
4169
|
+
const sourceCode = readFileSync7(fullPath, "utf-8");
|
|
2853
4170
|
const parsed = parser.parseFile(file, sourceCode, projectRoot);
|
|
2854
4171
|
parsedFiles.push(parsed);
|
|
2855
4172
|
} catch (err) {
|
|
@@ -2872,13 +4189,15 @@ async function parseProject(projectRoot, options) {
|
|
|
2872
4189
|
}
|
|
2873
4190
|
|
|
2874
4191
|
// src/cross-language/detectors/rest-api.ts
|
|
2875
|
-
import { readFileSync as
|
|
2876
|
-
import { join as
|
|
4192
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
4193
|
+
import { join as join11, resolve as resolve6 } from "path";
|
|
2877
4194
|
function getLanguage(filePath) {
|
|
2878
4195
|
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
|
|
2879
4196
|
if (filePath.endsWith(".js") || filePath.endsWith(".jsx") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs")) return "javascript";
|
|
2880
4197
|
if (filePath.endsWith(".py")) return "python";
|
|
2881
4198
|
if (filePath.endsWith(".go")) return "go";
|
|
4199
|
+
if (filePath.endsWith(".cs") || filePath.endsWith(".csx")) return "csharp";
|
|
4200
|
+
if (filePath.endsWith(".java")) return "java";
|
|
2882
4201
|
return "unknown";
|
|
2883
4202
|
}
|
|
2884
4203
|
function normalizePath(routePath) {
|
|
@@ -3015,9 +4334,136 @@ function extractRouteDefinitions(source, filePath) {
|
|
|
3015
4334
|
}
|
|
3016
4335
|
}
|
|
3017
4336
|
}
|
|
4337
|
+
if (lang === "csharp") {
|
|
4338
|
+
const attrMatch = line.match(/\[\s*Http(Get|Post|Put|Delete|Patch)\s*\(\s*"([^"]+)"\s*\)\s*\]/);
|
|
4339
|
+
if (attrMatch) {
|
|
4340
|
+
routes.push({
|
|
4341
|
+
method: attrMatch[1].toUpperCase(),
|
|
4342
|
+
path: attrMatch[2],
|
|
4343
|
+
normalizedPath: normalizePath(attrMatch[2]),
|
|
4344
|
+
file: filePath,
|
|
4345
|
+
line: i + 1
|
|
4346
|
+
});
|
|
4347
|
+
}
|
|
4348
|
+
const routeAttrMatch = line.match(/\[\s*Route\s*\(\s*"([^"]+)"\s*\)\s*\]/);
|
|
4349
|
+
if (routeAttrMatch) {
|
|
4350
|
+
let routePath = routeAttrMatch[1];
|
|
4351
|
+
if (routePath.includes("[controller]")) {
|
|
4352
|
+
const classMatch = source.match(/class\s+(\w+?)Controller\s/);
|
|
4353
|
+
if (classMatch) {
|
|
4354
|
+
routePath = routePath.replace("[controller]", classMatch[1].toLowerCase());
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
if (!routePath.startsWith("/")) routePath = "/" + routePath;
|
|
4358
|
+
routes.push({
|
|
4359
|
+
method: "ANY",
|
|
4360
|
+
path: routePath,
|
|
4361
|
+
normalizedPath: normalizePath(routePath),
|
|
4362
|
+
file: filePath,
|
|
4363
|
+
line: i + 1
|
|
4364
|
+
});
|
|
4365
|
+
}
|
|
4366
|
+
const minimalMatch = line.match(/app\s*\.\s*Map(Get|Post|Put|Delete|Patch)\s*\(\s*"([^"]+)"/);
|
|
4367
|
+
if (minimalMatch) {
|
|
4368
|
+
const path6 = minimalMatch[2];
|
|
4369
|
+
if (path6.startsWith("/")) {
|
|
4370
|
+
routes.push({
|
|
4371
|
+
method: minimalMatch[1].toUpperCase(),
|
|
4372
|
+
path: path6,
|
|
4373
|
+
normalizedPath: normalizePath(path6),
|
|
4374
|
+
file: filePath,
|
|
4375
|
+
line: i + 1
|
|
4376
|
+
});
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
if (lang === "java") {
|
|
4381
|
+
const springMethodMatch = line.match(/@(Get|Post|Put|Delete|Patch)Mapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
|
|
4382
|
+
if (springMethodMatch) {
|
|
4383
|
+
const method = springMethodMatch[1].toUpperCase();
|
|
4384
|
+
let path6 = springMethodMatch[2];
|
|
4385
|
+
const classPrefix = findClassLevelPrefix(source);
|
|
4386
|
+
if (classPrefix) path6 = classPrefix + path6;
|
|
4387
|
+
if (!path6.startsWith("/")) path6 = "/" + path6;
|
|
4388
|
+
routes.push({
|
|
4389
|
+
method,
|
|
4390
|
+
path: path6,
|
|
4391
|
+
normalizedPath: normalizePath(path6),
|
|
4392
|
+
file: filePath,
|
|
4393
|
+
line: i + 1
|
|
4394
|
+
});
|
|
4395
|
+
}
|
|
4396
|
+
if (!springMethodMatch) {
|
|
4397
|
+
const springNoPathMatch = line.match(/@(Get|Post|Put|Delete|Patch)Mapping\s*$/);
|
|
4398
|
+
if (springNoPathMatch) {
|
|
4399
|
+
const method = springNoPathMatch[1].toUpperCase();
|
|
4400
|
+
const classPrefix = findClassLevelPrefix(source);
|
|
4401
|
+
if (classPrefix) {
|
|
4402
|
+
routes.push({
|
|
4403
|
+
method,
|
|
4404
|
+
path: classPrefix,
|
|
4405
|
+
normalizedPath: normalizePath(classPrefix),
|
|
4406
|
+
file: filePath,
|
|
4407
|
+
line: i + 1
|
|
4408
|
+
});
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4412
|
+
const requestMappingMatch = line.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
|
|
4413
|
+
if (requestMappingMatch) {
|
|
4414
|
+
let path6 = requestMappingMatch[1];
|
|
4415
|
+
if (!path6.startsWith("/")) path6 = "/" + path6;
|
|
4416
|
+
const methodMatch = line.match(/method\s*=\s*RequestMethod\.(\w+)/);
|
|
4417
|
+
const method = methodMatch ? methodMatch[1].toUpperCase() : "ANY";
|
|
4418
|
+
routes.push({
|
|
4419
|
+
method,
|
|
4420
|
+
path: path6,
|
|
4421
|
+
normalizedPath: normalizePath(path6),
|
|
4422
|
+
file: filePath,
|
|
4423
|
+
line: i + 1
|
|
4424
|
+
});
|
|
4425
|
+
}
|
|
4426
|
+
const jaxPathMatch = line.match(/@Path\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
4427
|
+
if (jaxPathMatch) {
|
|
4428
|
+
let path6 = jaxPathMatch[1];
|
|
4429
|
+
if (!path6.startsWith("/")) path6 = "/" + path6;
|
|
4430
|
+
const nextLine = i + 1 < lines.length ? lines[i + 1] : "";
|
|
4431
|
+
const prevLine = i > 0 ? lines[i - 1] : "";
|
|
4432
|
+
const jaxMethodMatch = (nextLine + prevLine).match(/@(GET|POST|PUT|DELETE|PATCH)/);
|
|
4433
|
+
const method = jaxMethodMatch ? jaxMethodMatch[1] : "ANY";
|
|
4434
|
+
routes.push({
|
|
4435
|
+
method,
|
|
4436
|
+
path: path6,
|
|
4437
|
+
normalizedPath: normalizePath(path6),
|
|
4438
|
+
file: filePath,
|
|
4439
|
+
line: i + 1
|
|
4440
|
+
});
|
|
4441
|
+
}
|
|
4442
|
+
const webFluxMatch = line.match(/(?:route|andRoute)\s*\(\s*(GET|POST|PUT|DELETE|PATCH)\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
4443
|
+
if (webFluxMatch) {
|
|
4444
|
+
const path6 = webFluxMatch[2].startsWith("/") ? webFluxMatch[2] : "/" + webFluxMatch[2];
|
|
4445
|
+
routes.push({
|
|
4446
|
+
method: webFluxMatch[1].toUpperCase(),
|
|
4447
|
+
path: path6,
|
|
4448
|
+
normalizedPath: normalizePath(path6),
|
|
4449
|
+
file: filePath,
|
|
4450
|
+
line: i + 1
|
|
4451
|
+
});
|
|
4452
|
+
}
|
|
4453
|
+
}
|
|
3018
4454
|
}
|
|
3019
4455
|
return routes;
|
|
3020
4456
|
}
|
|
4457
|
+
function findClassLevelPrefix(source) {
|
|
4458
|
+
const match = source.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
|
|
4459
|
+
if (match) {
|
|
4460
|
+
let path6 = match[1];
|
|
4461
|
+
if (!path6.startsWith("/")) path6 = "/" + path6;
|
|
4462
|
+
if (path6.endsWith("/") && path6.length > 1) path6 = path6.slice(0, -1);
|
|
4463
|
+
return path6;
|
|
4464
|
+
}
|
|
4465
|
+
return null;
|
|
4466
|
+
}
|
|
3021
4467
|
function matchPaths(callPath, routeNormalized) {
|
|
3022
4468
|
const normalizedCall = normalizePath(stripTrailingSlash(callPath));
|
|
3023
4469
|
const normalizedRoute = stripTrailingSlash(routeNormalized);
|
|
@@ -3042,7 +4488,7 @@ function getConfidence(callPath, callMethod, routePath, routeMethod) {
|
|
|
3042
4488
|
const normalizedCall = normalizePath(stripTrailingSlash(callPath));
|
|
3043
4489
|
const normalizedRoute = normalizePath(stripTrailingSlash(routePath));
|
|
3044
4490
|
const exactPath = normalizedCall === normalizedRoute;
|
|
3045
|
-
const methodMatch = callMethod === routeMethod;
|
|
4491
|
+
const methodMatch = callMethod === routeMethod || routeMethod === "ANY";
|
|
3046
4492
|
if (exactPath && methodMatch) return "high";
|
|
3047
4493
|
if (exactPath) return "medium";
|
|
3048
4494
|
if (methodMatch) return "medium";
|
|
@@ -3053,11 +4499,11 @@ function detectRestApiEdges(files, projectRoot) {
|
|
|
3053
4499
|
const allCalls = [];
|
|
3054
4500
|
const allRoutes = [];
|
|
3055
4501
|
for (const file of files) {
|
|
3056
|
-
const fullPath =
|
|
3057
|
-
if (!
|
|
4502
|
+
const fullPath = join11(projectRoot, file.filePath);
|
|
4503
|
+
if (!resolve6(fullPath).startsWith(resolve6(projectRoot))) continue;
|
|
3058
4504
|
let source;
|
|
3059
4505
|
try {
|
|
3060
|
-
source =
|
|
4506
|
+
source = readFileSync8(fullPath, "utf-8");
|
|
3061
4507
|
} catch {
|
|
3062
4508
|
continue;
|
|
3063
4509
|
}
|
|
@@ -3093,8 +4539,8 @@ function detectRestApiEdges(files, projectRoot) {
|
|
|
3093
4539
|
}
|
|
3094
4540
|
|
|
3095
4541
|
// src/cross-language/detectors/subprocess.ts
|
|
3096
|
-
import { readFileSync as
|
|
3097
|
-
import { join as
|
|
4542
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
4543
|
+
import { join as join12, resolve as resolve7, basename as basename4 } from "path";
|
|
3098
4544
|
var SCRIPT_EXTENSIONS = [".py", ".js", ".ts", ".go", ".rs"];
|
|
3099
4545
|
function getLanguage2(filePath) {
|
|
3100
4546
|
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
|
|
@@ -3193,16 +4639,16 @@ function detectSubprocessEdges(files, projectRoot) {
|
|
|
3193
4639
|
const knownFiles = new Set(files.map((f) => f.filePath));
|
|
3194
4640
|
const basenameMap = /* @__PURE__ */ new Map();
|
|
3195
4641
|
for (const f of files) {
|
|
3196
|
-
const base =
|
|
4642
|
+
const base = basename4(f.filePath);
|
|
3197
4643
|
if (!basenameMap.has(base)) basenameMap.set(base, []);
|
|
3198
4644
|
basenameMap.get(base).push(f.filePath);
|
|
3199
4645
|
}
|
|
3200
4646
|
for (const file of files) {
|
|
3201
|
-
const fullPath =
|
|
3202
|
-
if (!
|
|
4647
|
+
const fullPath = join12(projectRoot, file.filePath);
|
|
4648
|
+
if (!resolve7(fullPath).startsWith(resolve7(projectRoot))) continue;
|
|
3203
4649
|
let source;
|
|
3204
4650
|
try {
|
|
3205
|
-
source =
|
|
4651
|
+
source = readFileSync9(fullPath, "utf-8");
|
|
3206
4652
|
} catch {
|
|
3207
4653
|
continue;
|
|
3208
4654
|
}
|
|
@@ -3214,7 +4660,7 @@ function detectSubprocessEdges(files, projectRoot) {
|
|
|
3214
4660
|
targetFile = call.calledFile;
|
|
3215
4661
|
confidence = "high";
|
|
3216
4662
|
} else {
|
|
3217
|
-
const base =
|
|
4663
|
+
const base = basename4(call.calledFile);
|
|
3218
4664
|
const candidates = basenameMap.get(base);
|
|
3219
4665
|
if (candidates && candidates.length > 0) {
|
|
3220
4666
|
const exactCandidate = candidates.find((c) => c.endsWith(call.calledFile));
|
|
@@ -3593,7 +5039,7 @@ function getArchitectureSummary(graph) {
|
|
|
3593
5039
|
}
|
|
3594
5040
|
|
|
3595
5041
|
// src/health/metrics.ts
|
|
3596
|
-
import { dirname as
|
|
5042
|
+
import { dirname as dirname9 } from "path";
|
|
3597
5043
|
function scoreToGrade(score) {
|
|
3598
5044
|
if (score >= 90) return "A";
|
|
3599
5045
|
if (score >= 80) return "B";
|
|
@@ -3626,8 +5072,8 @@ function calculateCouplingScore(graph) {
|
|
|
3626
5072
|
totalEdges++;
|
|
3627
5073
|
fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
|
|
3628
5074
|
fileConnections.set(targetAttrs.filePath, (fileConnections.get(targetAttrs.filePath) || 0) + 1);
|
|
3629
|
-
const sourceDir =
|
|
3630
|
-
const targetDir =
|
|
5075
|
+
const sourceDir = dirname9(sourceAttrs.filePath).split("/")[0];
|
|
5076
|
+
const targetDir = dirname9(targetAttrs.filePath).split("/")[0];
|
|
3631
5077
|
if (sourceDir !== targetDir) {
|
|
3632
5078
|
crossDirEdges++;
|
|
3633
5079
|
}
|
|
@@ -3674,8 +5120,8 @@ function calculateCohesionScore(graph) {
|
|
|
3674
5120
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
3675
5121
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
3676
5122
|
if (sourceAttrs.filePath !== targetAttrs.filePath) {
|
|
3677
|
-
const sourceDir =
|
|
3678
|
-
const targetDir =
|
|
5123
|
+
const sourceDir = dirname9(sourceAttrs.filePath);
|
|
5124
|
+
const targetDir = dirname9(targetAttrs.filePath);
|
|
3679
5125
|
if (!dirEdges.has(sourceDir)) {
|
|
3680
5126
|
dirEdges.set(sourceDir, { internal: 0, total: 0 });
|
|
3681
5127
|
}
|
|
@@ -3957,8 +5403,8 @@ function calculateDepthScore(graph) {
|
|
|
3957
5403
|
}
|
|
3958
5404
|
|
|
3959
5405
|
// src/health/index.ts
|
|
3960
|
-
import { readFileSync as
|
|
3961
|
-
import { dirname as
|
|
5406
|
+
import { readFileSync as readFileSync10, writeFileSync, existsSync as existsSync10, mkdirSync } from "fs";
|
|
5407
|
+
import { dirname as dirname10, resolve as resolve8 } from "path";
|
|
3962
5408
|
function calculateHealthScore(graph, projectRoot) {
|
|
3963
5409
|
const coupling = calculateCouplingScore(graph);
|
|
3964
5410
|
const cohesion = calculateCohesionScore(graph);
|
|
@@ -4057,8 +5503,8 @@ function getHealthTrend(projectRoot, currentScore) {
|
|
|
4057
5503
|
}
|
|
4058
5504
|
}
|
|
4059
5505
|
function saveHealthHistory(projectRoot, report) {
|
|
4060
|
-
const resolvedRoot =
|
|
4061
|
-
const historyFile =
|
|
5506
|
+
const resolvedRoot = resolve8(projectRoot);
|
|
5507
|
+
const historyFile = resolve8(resolvedRoot, ".depwire", "health-history.json");
|
|
4062
5508
|
if (!historyFile.startsWith(resolvedRoot)) {
|
|
4063
5509
|
return;
|
|
4064
5510
|
}
|
|
@@ -4073,10 +5519,10 @@ function saveHealthHistory(projectRoot, report) {
|
|
|
4073
5519
|
}))
|
|
4074
5520
|
};
|
|
4075
5521
|
let history = [];
|
|
4076
|
-
if (
|
|
5522
|
+
if (existsSync10(historyFile)) {
|
|
4077
5523
|
try {
|
|
4078
5524
|
if (!historyFile.startsWith(resolvedRoot)) return;
|
|
4079
|
-
const content =
|
|
5525
|
+
const content = readFileSync10(historyFile, "utf-8");
|
|
4080
5526
|
history = JSON.parse(content);
|
|
4081
5527
|
} catch {
|
|
4082
5528
|
}
|
|
@@ -4085,19 +5531,19 @@ function saveHealthHistory(projectRoot, report) {
|
|
|
4085
5531
|
if (history.length > 50) {
|
|
4086
5532
|
history = history.slice(-50);
|
|
4087
5533
|
}
|
|
4088
|
-
mkdirSync(
|
|
5534
|
+
mkdirSync(dirname10(historyFile), { recursive: true });
|
|
4089
5535
|
if (!historyFile.startsWith(resolvedRoot)) return;
|
|
4090
5536
|
writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
|
|
4091
5537
|
}
|
|
4092
5538
|
function loadHealthHistory(projectRoot) {
|
|
4093
|
-
const resolvedRoot =
|
|
4094
|
-
const historyFile =
|
|
4095
|
-
if (!historyFile.startsWith(resolvedRoot) || !
|
|
5539
|
+
const resolvedRoot = resolve8(projectRoot);
|
|
5540
|
+
const historyFile = resolve8(resolvedRoot, ".depwire", "health-history.json");
|
|
5541
|
+
if (!historyFile.startsWith(resolvedRoot) || !existsSync10(historyFile)) {
|
|
4096
5542
|
return [];
|
|
4097
5543
|
}
|
|
4098
5544
|
try {
|
|
4099
5545
|
if (!historyFile.startsWith(resolvedRoot)) return [];
|
|
4100
|
-
const content =
|
|
5546
|
+
const content = readFileSync10(historyFile, "utf-8");
|
|
4101
5547
|
return JSON.parse(content);
|
|
4102
5548
|
} catch {
|
|
4103
5549
|
return [];
|
|
@@ -4106,7 +5552,7 @@ function loadHealthHistory(projectRoot) {
|
|
|
4106
5552
|
|
|
4107
5553
|
// src/dead-code/detector.ts
|
|
4108
5554
|
import path2 from "path";
|
|
4109
|
-
import { readFileSync as
|
|
5555
|
+
import { readFileSync as readFileSync11, existsSync as existsSync11 } from "fs";
|
|
4110
5556
|
function findDeadSymbols(graph, projectRoot, includeTests = false, debug = false) {
|
|
4111
5557
|
const deadSymbols = [];
|
|
4112
5558
|
const context = { graph, projectRoot };
|
|
@@ -4218,8 +5664,10 @@ function isRelevantForDeadCodeDetection(attrs) {
|
|
|
4218
5664
|
"class",
|
|
4219
5665
|
"interface",
|
|
4220
5666
|
"type",
|
|
5667
|
+
"type_alias",
|
|
4221
5668
|
"enum",
|
|
4222
5669
|
"const",
|
|
5670
|
+
"constant",
|
|
4223
5671
|
"let",
|
|
4224
5672
|
"var",
|
|
4225
5673
|
"method",
|
|
@@ -4237,11 +5685,11 @@ function getPackageEntryPoints(projectRoot) {
|
|
|
4237
5685
|
const entryPoints = /* @__PURE__ */ new Set();
|
|
4238
5686
|
const resolvedRoot = path2.resolve(projectRoot);
|
|
4239
5687
|
const packageJsonPath = path2.resolve(resolvedRoot, "package.json");
|
|
4240
|
-
if (!packageJsonPath.startsWith(resolvedRoot) || !
|
|
5688
|
+
if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync11(packageJsonPath)) {
|
|
4241
5689
|
return entryPoints;
|
|
4242
5690
|
}
|
|
4243
5691
|
try {
|
|
4244
|
-
const packageJson = JSON.parse(
|
|
5692
|
+
const packageJson = JSON.parse(readFileSync11(packageJsonPath, "utf-8"));
|
|
4245
5693
|
if (packageJson.main) {
|
|
4246
5694
|
entryPoints.add(path2.resolve(projectRoot, packageJson.main));
|
|
4247
5695
|
}
|
|
@@ -4314,7 +5762,8 @@ function isTypeDeclarationFile(filePath) {
|
|
|
4314
5762
|
return filePath.endsWith(".d.ts");
|
|
4315
5763
|
}
|
|
4316
5764
|
function isFrameworkAutoLoadedFile(filePath) {
|
|
4317
|
-
return filePath.includes("/pages/") || filePath.includes("/routes/") || filePath.includes("/middleware/") || filePath.includes("/commands/") || filePath.includes("/api/") || filePath.includes("/app/")
|
|
5765
|
+
return filePath.includes("/pages/") || filePath.includes("/routes/") || filePath.includes("/middleware/") || filePath.includes("/commands/") || filePath.includes("/api/") || filePath.includes("/app/") || filePath.includes("/Controllers/") || filePath.includes("/Hubs/") || filePath.includes("/Migrations/") || // Java / Spring / Jakarta
|
|
5766
|
+
filePath.includes("/controller/") || filePath.includes("/controllers/") || filePath.includes("/service/") || filePath.includes("/repository/") || filePath.includes("/config/") || filePath.includes("/configuration/");
|
|
4318
5767
|
}
|
|
4319
5768
|
|
|
4320
5769
|
// src/dead-code/classifier.ts
|
|
@@ -4382,8 +5831,8 @@ function generateReason(symbol, confidence) {
|
|
|
4382
5831
|
return "Potentially unused";
|
|
4383
5832
|
}
|
|
4384
5833
|
function isBarrelFile(filePath) {
|
|
4385
|
-
const
|
|
4386
|
-
return
|
|
5834
|
+
const basename8 = path3.basename(filePath);
|
|
5835
|
+
return basename8 === "index.ts" || basename8 === "index.js";
|
|
4387
5836
|
}
|
|
4388
5837
|
function isTestFile2(filePath) {
|
|
4389
5838
|
return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
|
|
@@ -4562,11 +6011,11 @@ function filterByConfidence(symbols, minConfidence) {
|
|
|
4562
6011
|
}
|
|
4563
6012
|
|
|
4564
6013
|
// src/docs/generator.ts
|
|
4565
|
-
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as
|
|
4566
|
-
import { join as
|
|
6014
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync14 } from "fs";
|
|
6015
|
+
import { join as join16 } from "path";
|
|
4567
6016
|
|
|
4568
6017
|
// src/docs/architecture.ts
|
|
4569
|
-
import { dirname as
|
|
6018
|
+
import { dirname as dirname11 } from "path";
|
|
4570
6019
|
|
|
4571
6020
|
// src/docs/templates.ts
|
|
4572
6021
|
function header(text, level = 1) {
|
|
@@ -4717,7 +6166,7 @@ function generateModuleStructure(graph) {
|
|
|
4717
6166
|
function getDirectoryStats(graph) {
|
|
4718
6167
|
const dirMap = /* @__PURE__ */ new Map();
|
|
4719
6168
|
graph.forEachNode((node, attrs) => {
|
|
4720
|
-
const dir =
|
|
6169
|
+
const dir = dirname11(attrs.filePath);
|
|
4721
6170
|
if (dir === ".") return;
|
|
4722
6171
|
if (!dirMap.has(dir)) {
|
|
4723
6172
|
dirMap.set(dir, {
|
|
@@ -4742,7 +6191,7 @@ function getDirectoryStats(graph) {
|
|
|
4742
6191
|
});
|
|
4743
6192
|
const filesPerDir = /* @__PURE__ */ new Map();
|
|
4744
6193
|
graph.forEachNode((node, attrs) => {
|
|
4745
|
-
const dir =
|
|
6194
|
+
const dir = dirname11(attrs.filePath);
|
|
4746
6195
|
if (!filesPerDir.has(dir)) {
|
|
4747
6196
|
filesPerDir.set(dir, /* @__PURE__ */ new Set());
|
|
4748
6197
|
}
|
|
@@ -4757,8 +6206,8 @@ function getDirectoryStats(graph) {
|
|
|
4757
6206
|
graph.forEachEdge((edge, attrs, source, target) => {
|
|
4758
6207
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
4759
6208
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
4760
|
-
const sourceDir =
|
|
4761
|
-
const targetDir =
|
|
6209
|
+
const sourceDir = dirname11(sourceAttrs.filePath);
|
|
6210
|
+
const targetDir = dirname11(targetAttrs.filePath);
|
|
4762
6211
|
if (sourceDir !== targetDir) {
|
|
4763
6212
|
if (!dirEdges.has(sourceDir)) {
|
|
4764
6213
|
dirEdges.set(sourceDir, { in: 0, out: 0 });
|
|
@@ -4965,7 +6414,7 @@ function detectCycles(graph) {
|
|
|
4965
6414
|
}
|
|
4966
6415
|
|
|
4967
6416
|
// src/docs/conventions.ts
|
|
4968
|
-
import { basename as
|
|
6417
|
+
import { basename as basename5, extname as extname6 } from "path";
|
|
4969
6418
|
function generateConventions(graph, projectRoot, version) {
|
|
4970
6419
|
let output = "";
|
|
4971
6420
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -5003,7 +6452,7 @@ function generateFileOrganization(graph) {
|
|
|
5003
6452
|
graph.forEachNode((node, attrs) => {
|
|
5004
6453
|
if (!files.has(attrs.filePath)) {
|
|
5005
6454
|
files.add(attrs.filePath);
|
|
5006
|
-
const fileName =
|
|
6455
|
+
const fileName = basename5(attrs.filePath);
|
|
5007
6456
|
if (fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx") {
|
|
5008
6457
|
barrelFileCount++;
|
|
5009
6458
|
}
|
|
@@ -5062,7 +6511,7 @@ function generateNamingPatterns(graph) {
|
|
|
5062
6511
|
graph.forEachNode((node, attrs) => {
|
|
5063
6512
|
if (!files.has(attrs.filePath)) {
|
|
5064
6513
|
files.add(attrs.filePath);
|
|
5065
|
-
const fileName =
|
|
6514
|
+
const fileName = basename5(attrs.filePath, extname6(attrs.filePath));
|
|
5066
6515
|
if (isCamelCase(fileName)) patterns.files.camelCase++;
|
|
5067
6516
|
else if (isPascalCase(fileName)) patterns.files.PascalCase++;
|
|
5068
6517
|
else if (isKebabCase(fileName)) patterns.files.kebabCase++;
|
|
@@ -5739,7 +7188,7 @@ function detectCyclesDetailed(graph) {
|
|
|
5739
7188
|
}
|
|
5740
7189
|
|
|
5741
7190
|
// src/docs/onboarding.ts
|
|
5742
|
-
import { dirname as
|
|
7191
|
+
import { dirname as dirname12 } from "path";
|
|
5743
7192
|
function generateOnboarding(graph, projectRoot, version) {
|
|
5744
7193
|
let output = "";
|
|
5745
7194
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -5798,7 +7247,7 @@ function generateQuickOrientation(graph) {
|
|
|
5798
7247
|
const primaryLang = Object.entries(languages2).sort((a, b) => b[1] - a[1])[0];
|
|
5799
7248
|
const dirs = /* @__PURE__ */ new Set();
|
|
5800
7249
|
graph.forEachNode((node, attrs) => {
|
|
5801
|
-
const dir =
|
|
7250
|
+
const dir = dirname12(attrs.filePath);
|
|
5802
7251
|
if (dir !== ".") {
|
|
5803
7252
|
const topLevel = dir.split("/")[0];
|
|
5804
7253
|
dirs.add(topLevel);
|
|
@@ -5902,7 +7351,7 @@ function generateModuleMap(graph) {
|
|
|
5902
7351
|
function getDirectoryStats2(graph) {
|
|
5903
7352
|
const dirMap = /* @__PURE__ */ new Map();
|
|
5904
7353
|
graph.forEachNode((node, attrs) => {
|
|
5905
|
-
const dir =
|
|
7354
|
+
const dir = dirname12(attrs.filePath);
|
|
5906
7355
|
if (dir === ".") return;
|
|
5907
7356
|
if (!dirMap.has(dir)) {
|
|
5908
7357
|
dirMap.set(dir, {
|
|
@@ -5917,7 +7366,7 @@ function getDirectoryStats2(graph) {
|
|
|
5917
7366
|
});
|
|
5918
7367
|
const filesPerDir = /* @__PURE__ */ new Map();
|
|
5919
7368
|
graph.forEachNode((node, attrs) => {
|
|
5920
|
-
const dir =
|
|
7369
|
+
const dir = dirname12(attrs.filePath);
|
|
5921
7370
|
if (!filesPerDir.has(dir)) {
|
|
5922
7371
|
filesPerDir.set(dir, /* @__PURE__ */ new Set());
|
|
5923
7372
|
}
|
|
@@ -5932,8 +7381,8 @@ function getDirectoryStats2(graph) {
|
|
|
5932
7381
|
graph.forEachEdge((edge, attrs, source, target) => {
|
|
5933
7382
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
5934
7383
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
5935
|
-
const sourceDir =
|
|
5936
|
-
const targetDir =
|
|
7384
|
+
const sourceDir = dirname12(sourceAttrs.filePath);
|
|
7385
|
+
const targetDir = dirname12(targetAttrs.filePath);
|
|
5937
7386
|
if (sourceDir !== targetDir) {
|
|
5938
7387
|
if (!dirEdges.has(sourceDir)) {
|
|
5939
7388
|
dirEdges.set(sourceDir, { in: 0, out: 0 });
|
|
@@ -6014,7 +7463,7 @@ function detectClusters(graph) {
|
|
|
6014
7463
|
const dirFiles = /* @__PURE__ */ new Map();
|
|
6015
7464
|
const fileEdges = /* @__PURE__ */ new Map();
|
|
6016
7465
|
graph.forEachNode((node, attrs) => {
|
|
6017
|
-
const dir =
|
|
7466
|
+
const dir = dirname12(attrs.filePath);
|
|
6018
7467
|
if (!dirFiles.has(dir)) {
|
|
6019
7468
|
dirFiles.set(dir, /* @__PURE__ */ new Set());
|
|
6020
7469
|
}
|
|
@@ -6068,8 +7517,8 @@ function inferClusterName(files) {
|
|
|
6068
7517
|
if (sortedWords.length > 0 && sortedWords[0][1] > 1) {
|
|
6069
7518
|
return capitalizeFirst2(sortedWords[0][0]);
|
|
6070
7519
|
}
|
|
6071
|
-
const commonDir =
|
|
6072
|
-
if (files.every((f) =>
|
|
7520
|
+
const commonDir = dirname12(files[0]);
|
|
7521
|
+
if (files.every((f) => dirname12(f) === commonDir)) {
|
|
6073
7522
|
return capitalizeFirst2(commonDir.split("/").pop() || "Core");
|
|
6074
7523
|
}
|
|
6075
7524
|
return "Core";
|
|
@@ -6127,7 +7576,7 @@ function generateDepwireUsage(projectRoot) {
|
|
|
6127
7576
|
}
|
|
6128
7577
|
|
|
6129
7578
|
// src/docs/files.ts
|
|
6130
|
-
import { dirname as
|
|
7579
|
+
import { dirname as dirname13, basename as basename6 } from "path";
|
|
6131
7580
|
function generateFiles(graph, projectRoot, version) {
|
|
6132
7581
|
let output = "";
|
|
6133
7582
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -6231,7 +7680,7 @@ function generateDirectoryBreakdown(graph) {
|
|
|
6231
7680
|
const fileStats = getFileStats2(graph);
|
|
6232
7681
|
const dirMap = /* @__PURE__ */ new Map();
|
|
6233
7682
|
for (const file of fileStats) {
|
|
6234
|
-
const dir =
|
|
7683
|
+
const dir = dirname13(file.filePath);
|
|
6235
7684
|
const topDir = dir === "." ? "." : dir.split("/")[0];
|
|
6236
7685
|
if (!dirMap.has(topDir)) {
|
|
6237
7686
|
dirMap.set(topDir, {
|
|
@@ -6246,7 +7695,7 @@ function generateDirectoryBreakdown(graph) {
|
|
|
6246
7695
|
dirStats.symbolCount += file.symbolCount;
|
|
6247
7696
|
if (file.totalConnections > dirStats.maxConnections) {
|
|
6248
7697
|
dirStats.maxConnections = file.totalConnections;
|
|
6249
|
-
dirStats.mostConnectedFile =
|
|
7698
|
+
dirStats.mostConnectedFile = basename6(file.filePath);
|
|
6250
7699
|
}
|
|
6251
7700
|
}
|
|
6252
7701
|
if (dirMap.size === 0) {
|
|
@@ -6874,7 +8323,7 @@ function generateRecommendations(graph) {
|
|
|
6874
8323
|
}
|
|
6875
8324
|
|
|
6876
8325
|
// src/docs/tests.ts
|
|
6877
|
-
import { basename as
|
|
8326
|
+
import { basename as basename7, dirname as dirname14 } from "path";
|
|
6878
8327
|
function generateTests(graph, projectRoot, version) {
|
|
6879
8328
|
let output = "";
|
|
6880
8329
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -6902,8 +8351,8 @@ function getFileCount8(graph) {
|
|
|
6902
8351
|
return files.size;
|
|
6903
8352
|
}
|
|
6904
8353
|
function isTestFile3(filePath) {
|
|
6905
|
-
const fileName =
|
|
6906
|
-
const dirPath =
|
|
8354
|
+
const fileName = basename7(filePath).toLowerCase();
|
|
8355
|
+
const dirPath = dirname14(filePath).toLowerCase();
|
|
6907
8356
|
if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
|
|
6908
8357
|
return true;
|
|
6909
8358
|
}
|
|
@@ -6961,13 +8410,13 @@ function generateTestFileInventory(graph) {
|
|
|
6961
8410
|
return output;
|
|
6962
8411
|
}
|
|
6963
8412
|
function matchTestToSource(testFile) {
|
|
6964
|
-
const testFileName =
|
|
6965
|
-
const testDir =
|
|
8413
|
+
const testFileName = basename7(testFile);
|
|
8414
|
+
const testDir = dirname14(testFile);
|
|
6966
8415
|
let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
|
|
6967
8416
|
const possiblePaths = [];
|
|
6968
8417
|
possiblePaths.push(testDir + "/" + sourceFileName);
|
|
6969
8418
|
if (testDir.endsWith("/test") || testDir.endsWith("/tests") || testDir.endsWith("/__tests__")) {
|
|
6970
|
-
const parentDir =
|
|
8419
|
+
const parentDir = dirname14(testDir);
|
|
6971
8420
|
possiblePaths.push(parentDir + "/" + sourceFileName);
|
|
6972
8421
|
}
|
|
6973
8422
|
if (testDir.includes("test")) {
|
|
@@ -7123,7 +8572,7 @@ function generateTestCoverageMap(graph) {
|
|
|
7123
8572
|
const rows = mappings.slice(0, 30).map((m) => [
|
|
7124
8573
|
`\`${m.sourceFile}\``,
|
|
7125
8574
|
m.hasTest ? "\u2705" : "\u274C",
|
|
7126
|
-
m.testFile ? `\`${
|
|
8575
|
+
m.testFile ? `\`${basename7(m.testFile)}\`` : "-",
|
|
7127
8576
|
formatNumber(m.symbolCount)
|
|
7128
8577
|
]);
|
|
7129
8578
|
let output = table(headers, rows);
|
|
@@ -7164,7 +8613,7 @@ function generateTestStatistics(graph) {
|
|
|
7164
8613
|
`;
|
|
7165
8614
|
const dirTestCoverage = /* @__PURE__ */ new Map();
|
|
7166
8615
|
for (const sourceFile of sourceFiles) {
|
|
7167
|
-
const dir =
|
|
8616
|
+
const dir = dirname14(sourceFile).split("/")[0];
|
|
7168
8617
|
if (!dirTestCoverage.has(dir)) {
|
|
7169
8618
|
dirTestCoverage.set(dir, { total: 0, tested: 0 });
|
|
7170
8619
|
}
|
|
@@ -7187,7 +8636,7 @@ function generateTestStatistics(graph) {
|
|
|
7187
8636
|
}
|
|
7188
8637
|
|
|
7189
8638
|
// src/docs/history.ts
|
|
7190
|
-
import { dirname as
|
|
8639
|
+
import { dirname as dirname15 } from "path";
|
|
7191
8640
|
import { execSync } from "child_process";
|
|
7192
8641
|
function generateHistory(graph, projectRoot, version) {
|
|
7193
8642
|
let output = "";
|
|
@@ -7468,7 +8917,7 @@ function generateFeatureClusters(graph) {
|
|
|
7468
8917
|
const dirFiles = /* @__PURE__ */ new Map();
|
|
7469
8918
|
const fileEdges = /* @__PURE__ */ new Map();
|
|
7470
8919
|
graph.forEachNode((node, attrs) => {
|
|
7471
|
-
const dir =
|
|
8920
|
+
const dir = dirname15(attrs.filePath);
|
|
7472
8921
|
if (!dirFiles.has(dir)) {
|
|
7473
8922
|
dirFiles.set(dir, /* @__PURE__ */ new Set());
|
|
7474
8923
|
}
|
|
@@ -7550,7 +8999,7 @@ function capitalizeFirst3(str) {
|
|
|
7550
8999
|
}
|
|
7551
9000
|
|
|
7552
9001
|
// src/docs/current.ts
|
|
7553
|
-
import { dirname as
|
|
9002
|
+
import { dirname as dirname16 } from "path";
|
|
7554
9003
|
function generateCurrent(graph, projectRoot, version) {
|
|
7555
9004
|
let output = "";
|
|
7556
9005
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -7688,7 +9137,7 @@ function generateCompleteFileIndex(graph) {
|
|
|
7688
9137
|
fileInfos.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
7689
9138
|
const dirGroups = /* @__PURE__ */ new Map();
|
|
7690
9139
|
for (const info of fileInfos) {
|
|
7691
|
-
const dir =
|
|
9140
|
+
const dir = dirname16(info.filePath);
|
|
7692
9141
|
const topDir = dir === "." ? "root" : dir.split("/")[0];
|
|
7693
9142
|
if (!dirGroups.has(topDir)) {
|
|
7694
9143
|
dirGroups.set(topDir, []);
|
|
@@ -7899,8 +9348,8 @@ function getTopLevelDir2(filePath) {
|
|
|
7899
9348
|
}
|
|
7900
9349
|
|
|
7901
9350
|
// src/docs/status.ts
|
|
7902
|
-
import { readFileSync as
|
|
7903
|
-
import { resolve as
|
|
9351
|
+
import { readFileSync as readFileSync12, existsSync as existsSync12 } from "fs";
|
|
9352
|
+
import { resolve as resolve9 } from "path";
|
|
7904
9353
|
function generateStatus(graph, projectRoot, version) {
|
|
7905
9354
|
let output = "";
|
|
7906
9355
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -7933,16 +9382,16 @@ function getFileCount11(graph) {
|
|
|
7933
9382
|
}
|
|
7934
9383
|
function extractComments(projectRoot, filePath) {
|
|
7935
9384
|
const comments = [];
|
|
7936
|
-
const resolvedRoot =
|
|
7937
|
-
const fullPath =
|
|
9385
|
+
const resolvedRoot = resolve9(projectRoot);
|
|
9386
|
+
const fullPath = resolve9(resolvedRoot, filePath);
|
|
7938
9387
|
if (!fullPath.startsWith(resolvedRoot)) {
|
|
7939
9388
|
return comments;
|
|
7940
9389
|
}
|
|
7941
|
-
if (!
|
|
9390
|
+
if (!existsSync12(fullPath)) {
|
|
7942
9391
|
return comments;
|
|
7943
9392
|
}
|
|
7944
9393
|
try {
|
|
7945
|
-
const content =
|
|
9394
|
+
const content = readFileSync12(fullPath, "utf-8");
|
|
7946
9395
|
const lines = content.split("\n");
|
|
7947
9396
|
const patterns = [
|
|
7948
9397
|
{ type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
|
|
@@ -8521,16 +9970,16 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
|
|
|
8521
9970
|
}
|
|
8522
9971
|
|
|
8523
9972
|
// src/docs/metadata.ts
|
|
8524
|
-
import { existsSync as
|
|
8525
|
-
import { resolve as
|
|
9973
|
+
import { existsSync as existsSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync2 } from "fs";
|
|
9974
|
+
import { resolve as resolve10 } from "path";
|
|
8526
9975
|
function loadMetadata(outputDir) {
|
|
8527
|
-
const resolvedDir =
|
|
8528
|
-
const metadataPath =
|
|
8529
|
-
if (!metadataPath.startsWith(resolvedDir) || !
|
|
9976
|
+
const resolvedDir = resolve10(outputDir);
|
|
9977
|
+
const metadataPath = resolve10(resolvedDir, "metadata.json");
|
|
9978
|
+
if (!metadataPath.startsWith(resolvedDir) || !existsSync13(metadataPath)) {
|
|
8530
9979
|
return null;
|
|
8531
9980
|
}
|
|
8532
9981
|
try {
|
|
8533
|
-
const content =
|
|
9982
|
+
const content = readFileSync13(metadataPath, "utf-8");
|
|
8534
9983
|
return JSON.parse(content);
|
|
8535
9984
|
} catch (err) {
|
|
8536
9985
|
console.error("Failed to load metadata:", err);
|
|
@@ -8538,8 +9987,8 @@ function loadMetadata(outputDir) {
|
|
|
8538
9987
|
}
|
|
8539
9988
|
}
|
|
8540
9989
|
function saveMetadata(outputDir, metadata) {
|
|
8541
|
-
const resolvedDir =
|
|
8542
|
-
const metadataPath =
|
|
9990
|
+
const resolvedDir = resolve10(outputDir);
|
|
9991
|
+
const metadataPath = resolve10(resolvedDir, "metadata.json");
|
|
8543
9992
|
if (!metadataPath.startsWith(resolvedDir)) {
|
|
8544
9993
|
throw new Error(`Path traversal attempt blocked: ${metadataPath}`);
|
|
8545
9994
|
}
|
|
@@ -8585,7 +10034,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8585
10034
|
const generated = [];
|
|
8586
10035
|
const errors = [];
|
|
8587
10036
|
try {
|
|
8588
|
-
if (!
|
|
10037
|
+
if (!existsSync14(options.outputDir)) {
|
|
8589
10038
|
mkdirSync2(options.outputDir, { recursive: true });
|
|
8590
10039
|
if (options.verbose) {
|
|
8591
10040
|
console.log(`Created output directory: ${options.outputDir}`);
|
|
@@ -8624,7 +10073,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8624
10073
|
try {
|
|
8625
10074
|
if (options.verbose) console.log("Generating ARCHITECTURE.md...");
|
|
8626
10075
|
const content = generateArchitecture(graph, projectRoot, version, parseTime);
|
|
8627
|
-
const filePath =
|
|
10076
|
+
const filePath = join16(options.outputDir, "ARCHITECTURE.md");
|
|
8628
10077
|
writeFileSync3(filePath, content, "utf-8");
|
|
8629
10078
|
generated.push("ARCHITECTURE.md");
|
|
8630
10079
|
} catch (err) {
|
|
@@ -8635,7 +10084,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8635
10084
|
try {
|
|
8636
10085
|
if (options.verbose) console.log("Generating CONVENTIONS.md...");
|
|
8637
10086
|
const content = generateConventions(graph, projectRoot, version);
|
|
8638
|
-
const filePath =
|
|
10087
|
+
const filePath = join16(options.outputDir, "CONVENTIONS.md");
|
|
8639
10088
|
writeFileSync3(filePath, content, "utf-8");
|
|
8640
10089
|
generated.push("CONVENTIONS.md");
|
|
8641
10090
|
} catch (err) {
|
|
@@ -8646,7 +10095,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8646
10095
|
try {
|
|
8647
10096
|
if (options.verbose) console.log("Generating DEPENDENCIES.md...");
|
|
8648
10097
|
const content = generateDependencies(graph, projectRoot, version);
|
|
8649
|
-
const filePath =
|
|
10098
|
+
const filePath = join16(options.outputDir, "DEPENDENCIES.md");
|
|
8650
10099
|
writeFileSync3(filePath, content, "utf-8");
|
|
8651
10100
|
generated.push("DEPENDENCIES.md");
|
|
8652
10101
|
} catch (err) {
|
|
@@ -8657,7 +10106,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8657
10106
|
try {
|
|
8658
10107
|
if (options.verbose) console.log("Generating ONBOARDING.md...");
|
|
8659
10108
|
const content = generateOnboarding(graph, projectRoot, version);
|
|
8660
|
-
const filePath =
|
|
10109
|
+
const filePath = join16(options.outputDir, "ONBOARDING.md");
|
|
8661
10110
|
writeFileSync3(filePath, content, "utf-8");
|
|
8662
10111
|
generated.push("ONBOARDING.md");
|
|
8663
10112
|
} catch (err) {
|
|
@@ -8668,7 +10117,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8668
10117
|
try {
|
|
8669
10118
|
if (options.verbose) console.log("Generating FILES.md...");
|
|
8670
10119
|
const content = generateFiles(graph, projectRoot, version);
|
|
8671
|
-
const filePath =
|
|
10120
|
+
const filePath = join16(options.outputDir, "FILES.md");
|
|
8672
10121
|
writeFileSync3(filePath, content, "utf-8");
|
|
8673
10122
|
generated.push("FILES.md");
|
|
8674
10123
|
} catch (err) {
|
|
@@ -8679,7 +10128,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8679
10128
|
try {
|
|
8680
10129
|
if (options.verbose) console.log("Generating API_SURFACE.md...");
|
|
8681
10130
|
const content = generateApiSurface(graph, projectRoot, version);
|
|
8682
|
-
const filePath =
|
|
10131
|
+
const filePath = join16(options.outputDir, "API_SURFACE.md");
|
|
8683
10132
|
writeFileSync3(filePath, content, "utf-8");
|
|
8684
10133
|
generated.push("API_SURFACE.md");
|
|
8685
10134
|
} catch (err) {
|
|
@@ -8690,7 +10139,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8690
10139
|
try {
|
|
8691
10140
|
if (options.verbose) console.log("Generating ERRORS.md...");
|
|
8692
10141
|
const content = generateErrors(graph, projectRoot, version);
|
|
8693
|
-
const filePath =
|
|
10142
|
+
const filePath = join16(options.outputDir, "ERRORS.md");
|
|
8694
10143
|
writeFileSync3(filePath, content, "utf-8");
|
|
8695
10144
|
generated.push("ERRORS.md");
|
|
8696
10145
|
} catch (err) {
|
|
@@ -8701,7 +10150,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8701
10150
|
try {
|
|
8702
10151
|
if (options.verbose) console.log("Generating TESTS.md...");
|
|
8703
10152
|
const content = generateTests(graph, projectRoot, version);
|
|
8704
|
-
const filePath =
|
|
10153
|
+
const filePath = join16(options.outputDir, "TESTS.md");
|
|
8705
10154
|
writeFileSync3(filePath, content, "utf-8");
|
|
8706
10155
|
generated.push("TESTS.md");
|
|
8707
10156
|
} catch (err) {
|
|
@@ -8712,7 +10161,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8712
10161
|
try {
|
|
8713
10162
|
if (options.verbose) console.log("Generating HISTORY.md...");
|
|
8714
10163
|
const content = generateHistory(graph, projectRoot, version);
|
|
8715
|
-
const filePath =
|
|
10164
|
+
const filePath = join16(options.outputDir, "HISTORY.md");
|
|
8716
10165
|
writeFileSync3(filePath, content, "utf-8");
|
|
8717
10166
|
generated.push("HISTORY.md");
|
|
8718
10167
|
} catch (err) {
|
|
@@ -8723,7 +10172,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8723
10172
|
try {
|
|
8724
10173
|
if (options.verbose) console.log("Generating CURRENT.md...");
|
|
8725
10174
|
const content = generateCurrent(graph, projectRoot, version);
|
|
8726
|
-
const filePath =
|
|
10175
|
+
const filePath = join16(options.outputDir, "CURRENT.md");
|
|
8727
10176
|
writeFileSync3(filePath, content, "utf-8");
|
|
8728
10177
|
generated.push("CURRENT.md");
|
|
8729
10178
|
} catch (err) {
|
|
@@ -8734,7 +10183,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8734
10183
|
try {
|
|
8735
10184
|
if (options.verbose) console.log("Generating STATUS.md...");
|
|
8736
10185
|
const content = generateStatus(graph, projectRoot, version);
|
|
8737
|
-
const filePath =
|
|
10186
|
+
const filePath = join16(options.outputDir, "STATUS.md");
|
|
8738
10187
|
writeFileSync3(filePath, content, "utf-8");
|
|
8739
10188
|
generated.push("STATUS.md");
|
|
8740
10189
|
} catch (err) {
|
|
@@ -8745,7 +10194,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8745
10194
|
try {
|
|
8746
10195
|
if (options.verbose) console.log("Generating HEALTH.md...");
|
|
8747
10196
|
const content = generateHealth(graph, projectRoot, version);
|
|
8748
|
-
const filePath =
|
|
10197
|
+
const filePath = join16(options.outputDir, "HEALTH.md");
|
|
8749
10198
|
writeFileSync3(filePath, content, "utf-8");
|
|
8750
10199
|
generated.push("HEALTH.md");
|
|
8751
10200
|
} catch (err) {
|
|
@@ -8756,7 +10205,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8756
10205
|
try {
|
|
8757
10206
|
if (options.verbose) console.log("Generating DEAD_CODE.md...");
|
|
8758
10207
|
const content = generateDeadCode(graph, projectRoot, version);
|
|
8759
|
-
const filePath =
|
|
10208
|
+
const filePath = join16(options.outputDir, "DEAD_CODE.md");
|
|
8760
10209
|
writeFileSync3(filePath, content, "utf-8");
|
|
8761
10210
|
generated.push("DEAD_CODE.md");
|
|
8762
10211
|
} catch (err) {
|
|
@@ -8800,7 +10249,7 @@ function getFileCount13(graph) {
|
|
|
8800
10249
|
}
|
|
8801
10250
|
|
|
8802
10251
|
// src/simulation/engine.ts
|
|
8803
|
-
import { dirname as
|
|
10252
|
+
import { dirname as dirname17, join as join17 } from "path";
|
|
8804
10253
|
function normalizePath2(p) {
|
|
8805
10254
|
return p.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
8806
10255
|
}
|
|
@@ -8931,7 +10380,7 @@ var SimulationEngine = class {
|
|
|
8931
10380
|
}
|
|
8932
10381
|
}
|
|
8933
10382
|
applyRename(clone, target, newName, brokenImports) {
|
|
8934
|
-
const destination =
|
|
10383
|
+
const destination = join17(dirname17(target), newName);
|
|
8935
10384
|
this.applyMove(clone, target, destination, brokenImports);
|
|
8936
10385
|
}
|
|
8937
10386
|
applySplit(clone, target, newFile, symbols, brokenImports) {
|
|
@@ -9131,13 +10580,13 @@ var SimulationEngine = class {
|
|
|
9131
10580
|
};
|
|
9132
10581
|
|
|
9133
10582
|
// src/security/scanner.ts
|
|
9134
|
-
import { existsSync as
|
|
9135
|
-
import { join as
|
|
10583
|
+
import { existsSync as existsSync16 } from "fs";
|
|
10584
|
+
import { join as join27 } from "path";
|
|
9136
10585
|
|
|
9137
10586
|
// src/security/checks/dependencies.ts
|
|
9138
10587
|
import { execSync as execSync2 } from "child_process";
|
|
9139
|
-
import { existsSync as
|
|
9140
|
-
import { join as
|
|
10588
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14, readdirSync as readdirSync7 } from "fs";
|
|
10589
|
+
import { join as join18 } from "path";
|
|
9141
10590
|
function cvssToSeverity(score) {
|
|
9142
10591
|
if (score >= 9) return "critical";
|
|
9143
10592
|
if (score >= 7) return "high";
|
|
@@ -9147,18 +10596,18 @@ function cvssToSeverity(score) {
|
|
|
9147
10596
|
async function checkDependencies(_files, projectRoot) {
|
|
9148
10597
|
const findings = [];
|
|
9149
10598
|
try {
|
|
9150
|
-
if (
|
|
10599
|
+
if (existsSync15(join18(projectRoot, "package.json"))) {
|
|
9151
10600
|
findings.push(...checkNpmAudit(projectRoot));
|
|
9152
10601
|
findings.push(...checkPackageJsonPatterns(projectRoot));
|
|
9153
10602
|
findings.push(...checkPostinstallScripts(projectRoot));
|
|
9154
10603
|
}
|
|
9155
|
-
if (
|
|
10604
|
+
if (existsSync15(join18(projectRoot, "requirements.txt")) || existsSync15(join18(projectRoot, "pyproject.toml"))) {
|
|
9156
10605
|
findings.push(...checkPipAudit(projectRoot));
|
|
9157
10606
|
}
|
|
9158
|
-
if (
|
|
10607
|
+
if (existsSync15(join18(projectRoot, "Cargo.toml"))) {
|
|
9159
10608
|
findings.push(...checkCargoAudit(projectRoot));
|
|
9160
10609
|
}
|
|
9161
|
-
if (
|
|
10610
|
+
if (existsSync15(join18(projectRoot, "go.mod"))) {
|
|
9162
10611
|
findings.push(...checkGoVerify(projectRoot));
|
|
9163
10612
|
}
|
|
9164
10613
|
} catch (err) {
|
|
@@ -9247,8 +10696,8 @@ function checkNpmAudit(projectRoot) {
|
|
|
9247
10696
|
function checkPackageJsonPatterns(projectRoot) {
|
|
9248
10697
|
const findings = [];
|
|
9249
10698
|
try {
|
|
9250
|
-
const pkgPath =
|
|
9251
|
-
const pkg = JSON.parse(
|
|
10699
|
+
const pkgPath = join18(projectRoot, "package.json");
|
|
10700
|
+
const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
|
|
9252
10701
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
9253
10702
|
for (const [name, version] of Object.entries(allDeps)) {
|
|
9254
10703
|
if (version.startsWith("^") || version.startsWith("~")) {
|
|
@@ -9270,15 +10719,15 @@ function checkPackageJsonPatterns(projectRoot) {
|
|
|
9270
10719
|
}
|
|
9271
10720
|
function checkPostinstallScripts(projectRoot) {
|
|
9272
10721
|
const findings = [];
|
|
9273
|
-
const nodeModules =
|
|
9274
|
-
if (!
|
|
10722
|
+
const nodeModules = join18(projectRoot, "node_modules");
|
|
10723
|
+
if (!existsSync15(nodeModules)) return findings;
|
|
9275
10724
|
try {
|
|
9276
|
-
const topLevelDeps =
|
|
10725
|
+
const topLevelDeps = readdirSync7(nodeModules).filter((d) => !d.startsWith("."));
|
|
9277
10726
|
for (const dep of topLevelDeps) {
|
|
9278
|
-
const depPkgPath =
|
|
9279
|
-
if (!
|
|
10727
|
+
const depPkgPath = join18(nodeModules, dep, "package.json");
|
|
10728
|
+
if (!existsSync15(depPkgPath)) continue;
|
|
9280
10729
|
try {
|
|
9281
|
-
const depPkg = JSON.parse(
|
|
10730
|
+
const depPkg = JSON.parse(readFileSync14(depPkgPath, "utf-8"));
|
|
9282
10731
|
const scripts = depPkg.scripts || {};
|
|
9283
10732
|
if (scripts.postinstall || scripts.preinstall || scripts.install) {
|
|
9284
10733
|
const scriptName = scripts.postinstall ? "postinstall" : scripts.preinstall ? "preinstall" : "install";
|
|
@@ -9316,7 +10765,7 @@ function checkPipAudit(projectRoot) {
|
|
|
9316
10765
|
id: "",
|
|
9317
10766
|
severity: cvssToSeverity(vuln.cvss?.score || 5),
|
|
9318
10767
|
vulnerabilityClass: "dependency-cve",
|
|
9319
|
-
file:
|
|
10768
|
+
file: existsSync15(join18(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
|
|
9320
10769
|
title: `Vulnerable Python dependency: ${vuln.name}`,
|
|
9321
10770
|
description: `${vuln.name}@${vuln.version} \u2014 ${vuln.id}: ${vuln.description || "Known vulnerability"}`,
|
|
9322
10771
|
attackScenario: `An attacker could exploit the vulnerability in ${vuln.name}.`,
|
|
@@ -9413,8 +10862,8 @@ function checkGoVerify(projectRoot) {
|
|
|
9413
10862
|
}
|
|
9414
10863
|
|
|
9415
10864
|
// src/security/checks/injection.ts
|
|
9416
|
-
import { readFileSync as
|
|
9417
|
-
import { join as
|
|
10865
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
10866
|
+
import { join as join19 } from "path";
|
|
9418
10867
|
var SKIP_DIRS = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
9419
10868
|
var TEST_PATTERNS = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__"];
|
|
9420
10869
|
var USER_INPUT_NAMES = /(?:input|user|name|path|query|branch|hash|cmd|command|req\.|params|body|args|url|dir|file|subdirectory)/i;
|
|
@@ -9499,6 +10948,70 @@ var PATTERNS = [
|
|
|
9499
10948
|
description: "Database query built using fmt.Sprintf directly passed to db.Query.",
|
|
9500
10949
|
attackScenario: "An attacker could inject SQL through interpolated values.",
|
|
9501
10950
|
suggestedFix: 'Use parameterized queries: db.Query("SELECT ... WHERE id = ?", id)'
|
|
10951
|
+
},
|
|
10952
|
+
// Java-specific injection patterns
|
|
10953
|
+
{
|
|
10954
|
+
regex: /(?:executeQuery|executeUpdate|execute)\s*\(\s*["']?\s*(?:SELECT|INSERT|UPDATE|DELETE)\b[^"']*["']?\s*\+/i,
|
|
10955
|
+
title: "Java SQL injection via string concatenation",
|
|
10956
|
+
vulnClass: "code-injection",
|
|
10957
|
+
baseSeverity: "high",
|
|
10958
|
+
description: "SQL query built using string concatenation \u2014 vulnerable to SQL injection.",
|
|
10959
|
+
attackScenario: "An attacker could inject SQL through concatenated user input to read, modify, or delete database data.",
|
|
10960
|
+
suggestedFix: "Use PreparedStatement with parameterized queries: preparedStatement.setString(1, userInput)"
|
|
10961
|
+
},
|
|
10962
|
+
{
|
|
10963
|
+
regex: /Runtime\.getRuntime\(\)\.exec\s*\(/,
|
|
10964
|
+
title: "Java command injection via Runtime.exec",
|
|
10965
|
+
vulnClass: "shell-injection",
|
|
10966
|
+
baseSeverity: "high",
|
|
10967
|
+
description: "Runtime.exec() executes a system command \u2014 vulnerable if user input reaches the argument.",
|
|
10968
|
+
attackScenario: "An attacker could inject shell metacharacters to execute arbitrary commands on the server.",
|
|
10969
|
+
suggestedFix: "Use ProcessBuilder with an argument array. Validate all input against a strict allowlist."
|
|
10970
|
+
},
|
|
10971
|
+
{
|
|
10972
|
+
regex: /new\s+ProcessBuilder\s*\([^)]*(?:input|user|param|query|request|body|arg)/i,
|
|
10973
|
+
title: "Java command injection via ProcessBuilder with user input",
|
|
10974
|
+
vulnClass: "shell-injection",
|
|
10975
|
+
baseSeverity: "medium",
|
|
10976
|
+
description: "ProcessBuilder called with arguments that may originate from user input.",
|
|
10977
|
+
attackScenario: "An attacker could inject malicious arguments to the spawned process.",
|
|
10978
|
+
suggestedFix: "Validate all arguments against a strict allowlist before passing to ProcessBuilder."
|
|
10979
|
+
},
|
|
10980
|
+
{
|
|
10981
|
+
regex: /new\s+ObjectInputStream\s*\(/,
|
|
10982
|
+
title: "Java insecure deserialization via ObjectInputStream",
|
|
10983
|
+
vulnClass: "code-injection",
|
|
10984
|
+
baseSeverity: "high",
|
|
10985
|
+
description: "ObjectInputStream.readObject() deserializes arbitrary Java objects \u2014 potential RCE.",
|
|
10986
|
+
attackScenario: "An attacker could craft a malicious serialized object to achieve remote code execution.",
|
|
10987
|
+
suggestedFix: "Use a whitelist-based ObjectInputFilter, or switch to JSON/Protobuf for data exchange."
|
|
10988
|
+
},
|
|
10989
|
+
{
|
|
10990
|
+
regex: /DocumentBuilderFactory\.newInstance\(\)/,
|
|
10991
|
+
title: "Java XML External Entity (XXE) risk",
|
|
10992
|
+
vulnClass: "code-injection",
|
|
10993
|
+
baseSeverity: "medium",
|
|
10994
|
+
description: "DocumentBuilderFactory without FEATURE_SECURE_PROCESSING may allow XXE attacks.",
|
|
10995
|
+
attackScenario: "An attacker could inject external entity references in XML to read server files or perform SSRF.",
|
|
10996
|
+
suggestedFix: "Set FEATURE_SECURE_PROCESSING and disable external DTDs/entities: factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)"
|
|
10997
|
+
},
|
|
10998
|
+
{
|
|
10999
|
+
regex: /\.csrf\(\)\s*\.\s*disable\(\)/,
|
|
11000
|
+
title: "Spring Security CSRF protection disabled",
|
|
11001
|
+
vulnClass: "code-injection",
|
|
11002
|
+
baseSeverity: "medium",
|
|
11003
|
+
description: "CSRF protection has been explicitly disabled in Spring Security configuration.",
|
|
11004
|
+
attackScenario: "An attacker could forge cross-site requests to perform actions on behalf of authenticated users.",
|
|
11005
|
+
suggestedFix: "Only disable CSRF for stateless APIs using JWT. Keep CSRF enabled for session-based authentication."
|
|
11006
|
+
},
|
|
11007
|
+
{
|
|
11008
|
+
regex: /\.permitAll\(\).*(?:admin|manage|delete|config|setting)/i,
|
|
11009
|
+
title: "Spring Security permitAll on sensitive path",
|
|
11010
|
+
vulnClass: "code-injection",
|
|
11011
|
+
baseSeverity: "high",
|
|
11012
|
+
description: "permitAll() applied to a path that appears security-sensitive.",
|
|
11013
|
+
attackScenario: "An attacker could access administrative or destructive endpoints without authentication.",
|
|
11014
|
+
suggestedFix: 'Use .hasRole("ADMIN") or .authenticated() for sensitive endpoints.'
|
|
9502
11015
|
}
|
|
9503
11016
|
];
|
|
9504
11017
|
function shouldSkip(filePath) {
|
|
@@ -9515,7 +11028,7 @@ async function checkInjection(files, projectRoot) {
|
|
|
9515
11028
|
if (shouldSkip(file.filePath) || isTestFile4(file.filePath)) continue;
|
|
9516
11029
|
let content;
|
|
9517
11030
|
try {
|
|
9518
|
-
content =
|
|
11031
|
+
content = readFileSync15(join19(projectRoot, file.filePath), "utf-8");
|
|
9519
11032
|
} catch {
|
|
9520
11033
|
continue;
|
|
9521
11034
|
}
|
|
@@ -9553,8 +11066,8 @@ async function checkInjection(files, projectRoot) {
|
|
|
9553
11066
|
}
|
|
9554
11067
|
|
|
9555
11068
|
// src/security/checks/secrets.ts
|
|
9556
|
-
import { readFileSync as
|
|
9557
|
-
import { join as
|
|
11069
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
11070
|
+
import { join as join20 } from "path";
|
|
9558
11071
|
var SKIP_DIRS2 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
9559
11072
|
var TEST_PATTERNS2 = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__", ".example", ".sample"];
|
|
9560
11073
|
var SECRET_PATTERNS = [
|
|
@@ -9587,7 +11100,7 @@ async function checkSecrets(files, projectRoot) {
|
|
|
9587
11100
|
if (shouldSkip2(file.filePath) || isTestFile5(file.filePath)) continue;
|
|
9588
11101
|
let content;
|
|
9589
11102
|
try {
|
|
9590
|
-
content =
|
|
11103
|
+
content = readFileSync16(join20(projectRoot, file.filePath), "utf-8");
|
|
9591
11104
|
} catch {
|
|
9592
11105
|
continue;
|
|
9593
11106
|
}
|
|
@@ -9620,8 +11133,8 @@ async function checkSecrets(files, projectRoot) {
|
|
|
9620
11133
|
}
|
|
9621
11134
|
|
|
9622
11135
|
// src/security/checks/path-traversal.ts
|
|
9623
|
-
import { readFileSync as
|
|
9624
|
-
import { join as
|
|
11136
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
11137
|
+
import { join as join21 } from "path";
|
|
9625
11138
|
var SKIP_DIRS3 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
9626
11139
|
var USER_INPUT_VARS = /(?:req\.|params|query|body|input|path|dir|subdirectory|file|userInput|fileName|filePath)/i;
|
|
9627
11140
|
var PATTERNS2 = [
|
|
@@ -9668,7 +11181,7 @@ async function checkPathTraversal(files, projectRoot) {
|
|
|
9668
11181
|
if (shouldSkip3(file.filePath)) continue;
|
|
9669
11182
|
let content;
|
|
9670
11183
|
try {
|
|
9671
|
-
content =
|
|
11184
|
+
content = readFileSync17(join21(projectRoot, file.filePath), "utf-8");
|
|
9672
11185
|
} catch {
|
|
9673
11186
|
continue;
|
|
9674
11187
|
}
|
|
@@ -9710,8 +11223,8 @@ async function checkPathTraversal(files, projectRoot) {
|
|
|
9710
11223
|
}
|
|
9711
11224
|
|
|
9712
11225
|
// src/security/checks/auth.ts
|
|
9713
|
-
import { readFileSync as
|
|
9714
|
-
import { join as
|
|
11226
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
11227
|
+
import { join as join22 } from "path";
|
|
9715
11228
|
var SKIP_DIRS4 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
9716
11229
|
function shouldSkip4(filePath) {
|
|
9717
11230
|
return SKIP_DIRS4.some((d) => filePath.includes(d));
|
|
@@ -9727,7 +11240,7 @@ async function checkAuth(files, projectRoot) {
|
|
|
9727
11240
|
if (shouldSkip4(file.filePath)) continue;
|
|
9728
11241
|
let content;
|
|
9729
11242
|
try {
|
|
9730
|
-
content =
|
|
11243
|
+
content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
|
|
9731
11244
|
} catch {
|
|
9732
11245
|
continue;
|
|
9733
11246
|
}
|
|
@@ -9818,8 +11331,8 @@ async function checkAuth(files, projectRoot) {
|
|
|
9818
11331
|
}
|
|
9819
11332
|
|
|
9820
11333
|
// src/security/checks/input-validation.ts
|
|
9821
|
-
import { readFileSync as
|
|
9822
|
-
import { join as
|
|
11334
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
11335
|
+
import { join as join23 } from "path";
|
|
9823
11336
|
var SKIP_DIRS5 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
9824
11337
|
function shouldSkip5(filePath) {
|
|
9825
11338
|
return SKIP_DIRS5.some((d) => filePath.includes(d));
|
|
@@ -9831,7 +11344,7 @@ async function checkInputValidation(files, projectRoot) {
|
|
|
9831
11344
|
if (shouldSkip5(file.filePath)) continue;
|
|
9832
11345
|
let content;
|
|
9833
11346
|
try {
|
|
9834
|
-
content =
|
|
11347
|
+
content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
|
|
9835
11348
|
} catch {
|
|
9836
11349
|
continue;
|
|
9837
11350
|
}
|
|
@@ -9908,8 +11421,8 @@ async function checkInputValidation(files, projectRoot) {
|
|
|
9908
11421
|
}
|
|
9909
11422
|
|
|
9910
11423
|
// src/security/checks/information-disclosure.ts
|
|
9911
|
-
import { readFileSync as
|
|
9912
|
-
import { join as
|
|
11424
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
11425
|
+
import { join as join24 } from "path";
|
|
9913
11426
|
var SKIP_DIRS6 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
9914
11427
|
function shouldSkip6(filePath) {
|
|
9915
11428
|
return SKIP_DIRS6.some((d) => filePath.includes(d));
|
|
@@ -9921,7 +11434,7 @@ async function checkInformationDisclosure(files, projectRoot) {
|
|
|
9921
11434
|
if (shouldSkip6(file.filePath)) continue;
|
|
9922
11435
|
let content;
|
|
9923
11436
|
try {
|
|
9924
|
-
content =
|
|
11437
|
+
content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
|
|
9925
11438
|
} catch {
|
|
9926
11439
|
continue;
|
|
9927
11440
|
}
|
|
@@ -9991,9 +11504,10 @@ async function checkInformationDisclosure(files, projectRoot) {
|
|
|
9991
11504
|
}
|
|
9992
11505
|
|
|
9993
11506
|
// src/security/checks/cryptography.ts
|
|
9994
|
-
import { readFileSync as
|
|
9995
|
-
import { join as
|
|
11507
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
11508
|
+
import { join as join25 } from "path";
|
|
9996
11509
|
var SKIP_DIRS7 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
11510
|
+
var USER_INPUT_NAMES2 = /(?:input|user|name|path|query|param|request|body|args|url)/i;
|
|
9997
11511
|
function shouldSkip7(filePath) {
|
|
9998
11512
|
return SKIP_DIRS7.some((d) => filePath.includes(d));
|
|
9999
11513
|
}
|
|
@@ -10008,7 +11522,7 @@ async function checkCryptography(files, projectRoot) {
|
|
|
10008
11522
|
if (shouldSkip7(file.filePath)) continue;
|
|
10009
11523
|
let content;
|
|
10010
11524
|
try {
|
|
10011
|
-
content =
|
|
11525
|
+
content = readFileSync21(join25(projectRoot, file.filePath), "utf-8");
|
|
10012
11526
|
} catch {
|
|
10013
11527
|
continue;
|
|
10014
11528
|
}
|
|
@@ -10017,7 +11531,7 @@ async function checkCryptography(files, projectRoot) {
|
|
|
10017
11531
|
for (let i = 0; i < lines.length; i++) {
|
|
10018
11532
|
const line = lines[i];
|
|
10019
11533
|
if (line.trimStart().startsWith("//") || line.trimStart().startsWith("#")) continue;
|
|
10020
|
-
if (/createHash\s*\(\s*['"]md5['"]\s*\)/.test(line) || /hashlib\.md5\s*\(/.test(line)) {
|
|
11534
|
+
if (/createHash\s*\(\s*['"]md5['"]\s*\)/.test(line) || /hashlib\.md5\s*\(/.test(line) || /MessageDigest\.getInstance\s*\(\s*["']MD5["']\s*\)/.test(line)) {
|
|
10021
11535
|
findings.push({
|
|
10022
11536
|
id: "",
|
|
10023
11537
|
severity: isCryptoFile ? "high" : "medium",
|
|
@@ -10030,7 +11544,7 @@ async function checkCryptography(files, projectRoot) {
|
|
|
10030
11544
|
suggestedFix: "Use SHA-256 or SHA-3 for integrity checks. Use bcrypt, scrypt, or argon2 for password hashing."
|
|
10031
11545
|
});
|
|
10032
11546
|
}
|
|
10033
|
-
if (/createHash\s*\(\s*['"]sha1['"]\s*\)/.test(line) || /hashlib\.sha1\s*\(/.test(line)) {
|
|
11547
|
+
if (/createHash\s*\(\s*['"]sha1['"]\s*\)/.test(line) || /hashlib\.sha1\s*\(/.test(line) || /MessageDigest\.getInstance\s*\(\s*["']SHA-?1["']\s*\)/.test(line)) {
|
|
10034
11548
|
findings.push({
|
|
10035
11549
|
id: "",
|
|
10036
11550
|
severity: isCryptoFile ? "high" : "medium",
|
|
@@ -10043,6 +11557,34 @@ async function checkCryptography(files, projectRoot) {
|
|
|
10043
11557
|
suggestedFix: "Use SHA-256 or SHA-3 for integrity checks. Use bcrypt, scrypt, or argon2 for password hashing."
|
|
10044
11558
|
});
|
|
10045
11559
|
}
|
|
11560
|
+
if (/Cipher\.getInstance\s*\(\s*["']DES/.test(line)) {
|
|
11561
|
+
findings.push({
|
|
11562
|
+
id: "",
|
|
11563
|
+
severity: "high",
|
|
11564
|
+
vulnerabilityClass: "cryptography",
|
|
11565
|
+
file: file.filePath,
|
|
11566
|
+
line: i + 1,
|
|
11567
|
+
title: "Weak cipher algorithm: DES",
|
|
11568
|
+
description: "DES uses a 56-bit key and can be brute-forced in hours.",
|
|
11569
|
+
attackScenario: "An attacker could brute-force DES-encrypted data to reveal plaintext.",
|
|
11570
|
+
suggestedFix: 'Use AES-256 with GCM mode: Cipher.getInstance("AES/GCM/NoPadding")'
|
|
11571
|
+
});
|
|
11572
|
+
}
|
|
11573
|
+
if (/(?:log|logger|LOG)\s*\.\s*(?:info|debug|warn|error|trace)\s*\([^)]*\+/.test(line)) {
|
|
11574
|
+
if (USER_INPUT_NAMES2.test(line)) {
|
|
11575
|
+
findings.push({
|
|
11576
|
+
id: "",
|
|
11577
|
+
severity: "medium",
|
|
11578
|
+
vulnerabilityClass: "cryptography",
|
|
11579
|
+
file: file.filePath,
|
|
11580
|
+
line: i + 1,
|
|
11581
|
+
title: "Potential log injection",
|
|
11582
|
+
description: "User-controlled input concatenated directly into log output.",
|
|
11583
|
+
attackScenario: "An attacker could inject newlines or control characters to forge log entries or hide malicious activity.",
|
|
11584
|
+
suggestedFix: 'Use parameterized logging: log.info("User: {}", userInput) instead of string concatenation.'
|
|
11585
|
+
});
|
|
11586
|
+
}
|
|
11587
|
+
}
|
|
10046
11588
|
if (/Math\.random\(\)/.test(line) && isCryptoFile) {
|
|
10047
11589
|
findings.push({
|
|
10048
11590
|
id: "",
|
|
@@ -10090,8 +11632,8 @@ async function checkCryptography(files, projectRoot) {
|
|
|
10090
11632
|
}
|
|
10091
11633
|
|
|
10092
11634
|
// src/security/checks/frontend.ts
|
|
10093
|
-
import { readFileSync as
|
|
10094
|
-
import { join as
|
|
11635
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
11636
|
+
import { join as join26 } from "path";
|
|
10095
11637
|
var SKIP_DIRS8 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
10096
11638
|
function shouldSkip8(filePath) {
|
|
10097
11639
|
return SKIP_DIRS8.some((d) => filePath.includes(d));
|
|
@@ -10107,7 +11649,7 @@ async function checkFrontend(files, projectRoot) {
|
|
|
10107
11649
|
if (!isFrontendFile(file.filePath)) continue;
|
|
10108
11650
|
let content;
|
|
10109
11651
|
try {
|
|
10110
|
-
content =
|
|
11652
|
+
content = readFileSync22(join26(projectRoot, file.filePath), "utf-8");
|
|
10111
11653
|
} catch {
|
|
10112
11654
|
continue;
|
|
10113
11655
|
}
|
|
@@ -10567,11 +12109,13 @@ async function scanSecurity(projectRoot, graph, options = {}) {
|
|
|
10567
12109
|
};
|
|
10568
12110
|
}
|
|
10569
12111
|
function detectPackageManager(projectRoot) {
|
|
10570
|
-
if (
|
|
10571
|
-
if (
|
|
10572
|
-
if (
|
|
10573
|
-
if (
|
|
10574
|
-
if (
|
|
12112
|
+
if (existsSync16(join27(projectRoot, "package.json"))) return "npm";
|
|
12113
|
+
if (existsSync16(join27(projectRoot, "requirements.txt"))) return "pip";
|
|
12114
|
+
if (existsSync16(join27(projectRoot, "pyproject.toml"))) return "pip";
|
|
12115
|
+
if (existsSync16(join27(projectRoot, "Cargo.toml"))) return "cargo";
|
|
12116
|
+
if (existsSync16(join27(projectRoot, "go.mod"))) return "go";
|
|
12117
|
+
if (existsSync16(join27(projectRoot, "pom.xml"))) return "maven";
|
|
12118
|
+
if (existsSync16(join27(projectRoot, "build.gradle")) || existsSync16(join27(projectRoot, "build.gradle.kts"))) return "gradle";
|
|
10575
12119
|
return "unknown";
|
|
10576
12120
|
}
|
|
10577
12121
|
|