depwire-cli 1.0.5 → 1.0.8
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 +8 -2
- package/dist/{chunk-67KDN6H4.js → chunk-W3ZVSDFL.js} +8 -8
- package/dist/{chunk-SPZNB7BS.js → chunk-WLKW7X7G.js} +2032 -190
- package/dist/index.js +88 -2
- package/dist/mcpb-entry.js +2 -2
- package/dist/parser/grammars/tree-sitter-kotlin.wasm +0 -0
- package/dist/parser/grammars/tree-sitter-php.wasm +0 -0
- package/dist/sdk.js +1 -1
- package/package.json +5 -2
|
@@ -35,8 +35,10 @@ function scanDirectory(rootDir, baseDir = rootDir) {
|
|
|
35
35
|
const isCpp = entry.endsWith(".cpp") || entry.endsWith(".cc") || entry.endsWith(".cxx") || entry.endsWith(".c++") || entry.endsWith(".hpp") || entry.endsWith(".hh") || entry.endsWith(".hxx") || entry.endsWith(".h++") || entry.endsWith(".h") || entry.endsWith(".inl") || entry.endsWith(".ipp");
|
|
36
36
|
const isCSharp = entry.endsWith(".cs") || entry.endsWith(".csx") || entry.endsWith(".csproj");
|
|
37
37
|
const isJava = entry.endsWith(".java") || entry === "pom.xml" || entry === "build.gradle" || entry === "build.gradle.kts";
|
|
38
|
+
const isKotlin = entry.endsWith(".kt") || entry.endsWith(".kts") || entry === "settings.gradle.kts" || entry === "settings.gradle";
|
|
39
|
+
const isPhp = entry.endsWith(".php");
|
|
38
40
|
const isCppBuild = entry === "CMakeLists.txt" || entry === "conanfile.txt" || entry === "vcpkg.json";
|
|
39
|
-
if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCpp || isCSharp || isJava || isCppBuild) {
|
|
41
|
+
if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCpp || isCSharp || isJava || isKotlin || isPhp || isCppBuild) {
|
|
40
42
|
files.push(relative(rootDir, fullPath));
|
|
41
43
|
}
|
|
42
44
|
}
|
|
@@ -78,6 +80,10 @@ function findProjectRoot(startDir = process.cwd()) {
|
|
|
78
80
|
// Java (Maven)
|
|
79
81
|
"build.gradle",
|
|
80
82
|
// Java (Gradle)
|
|
83
|
+
"build.gradle.kts",
|
|
84
|
+
// Kotlin (Gradle KTS)
|
|
85
|
+
"composer.json",
|
|
86
|
+
// PHP
|
|
81
87
|
".git"
|
|
82
88
|
// Any git repo
|
|
83
89
|
];
|
|
@@ -111,11 +117,11 @@ function findProjectRoot(startDir = process.cwd()) {
|
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
// src/parser/index.ts
|
|
114
|
-
import { readFileSync as
|
|
115
|
-
import { join as
|
|
120
|
+
import { readFileSync as readFileSync10, statSync as statSync7 } from "fs";
|
|
121
|
+
import { join as join13, resolve as resolve7 } from "path";
|
|
116
122
|
|
|
117
123
|
// src/parser/detect.ts
|
|
118
|
-
import { extname as
|
|
124
|
+
import { extname as extname8, basename as basename6 } from "path";
|
|
119
125
|
|
|
120
126
|
// src/parser/wasm-init.ts
|
|
121
127
|
import { Parser, Language } from "web-tree-sitter";
|
|
@@ -145,7 +151,9 @@ async function initParser() {
|
|
|
145
151
|
"c": "tree-sitter-c.wasm",
|
|
146
152
|
"c_sharp": "tree-sitter-c_sharp.wasm",
|
|
147
153
|
"java": "tree-sitter-java.wasm",
|
|
148
|
-
"cpp": "tree-sitter-cpp.wasm"
|
|
154
|
+
"cpp": "tree-sitter-cpp.wasm",
|
|
155
|
+
"kotlin": "tree-sitter-kotlin.wasm",
|
|
156
|
+
"php": "tree-sitter-php.wasm"
|
|
149
157
|
};
|
|
150
158
|
for (const [name, file] of Object.entries(grammarFiles)) {
|
|
151
159
|
const wasmPath = path.join(grammarsDir, file);
|
|
@@ -4927,6 +4935,1302 @@ var cppParser = {
|
|
|
4927
4935
|
parseFile: parseCppFile
|
|
4928
4936
|
};
|
|
4929
4937
|
|
|
4938
|
+
// src/parser/kotlin.ts
|
|
4939
|
+
import { dirname as dirname10, join as join11, basename as basename4 } from "path";
|
|
4940
|
+
import { existsSync as existsSync11, readdirSync as readdirSync8, statSync as statSync5 } from "fs";
|
|
4941
|
+
function parseKotlinFile(filePath, sourceCode, projectRoot) {
|
|
4942
|
+
if (filePath.endsWith("build.gradle.kts")) {
|
|
4943
|
+
return parseGradleBuild2(filePath, sourceCode, projectRoot);
|
|
4944
|
+
}
|
|
4945
|
+
if (filePath.endsWith("build.gradle")) {
|
|
4946
|
+
return parseGradleBuild2(filePath, sourceCode, projectRoot);
|
|
4947
|
+
}
|
|
4948
|
+
if (filePath.endsWith("settings.gradle.kts") || filePath.endsWith("settings.gradle")) {
|
|
4949
|
+
return parseSettingsGradle(filePath, sourceCode, projectRoot);
|
|
4950
|
+
}
|
|
4951
|
+
const parser = getParser("kotlin");
|
|
4952
|
+
const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
|
|
4953
|
+
const context = {
|
|
4954
|
+
filePath,
|
|
4955
|
+
projectRoot,
|
|
4956
|
+
sourceCode,
|
|
4957
|
+
symbols: [],
|
|
4958
|
+
edges: [],
|
|
4959
|
+
currentScope: [],
|
|
4960
|
+
currentClass: null,
|
|
4961
|
+
currentPackage: null,
|
|
4962
|
+
imports: /* @__PURE__ */ new Map(),
|
|
4963
|
+
isBuildFile: false,
|
|
4964
|
+
isScriptFile: filePath.endsWith(".kts")
|
|
4965
|
+
};
|
|
4966
|
+
walkNode10(tree.rootNode, context);
|
|
4967
|
+
return {
|
|
4968
|
+
filePath,
|
|
4969
|
+
symbols: context.symbols,
|
|
4970
|
+
edges: context.edges
|
|
4971
|
+
};
|
|
4972
|
+
}
|
|
4973
|
+
function walkNode10(node, context) {
|
|
4974
|
+
processNode10(node, context);
|
|
4975
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
4976
|
+
const child = node.child(i);
|
|
4977
|
+
if (child) {
|
|
4978
|
+
walkNode10(child, context);
|
|
4979
|
+
}
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
function processNode10(node, context) {
|
|
4983
|
+
switch (node.type) {
|
|
4984
|
+
case "package_header":
|
|
4985
|
+
processPackageHeader(node, context);
|
|
4986
|
+
break;
|
|
4987
|
+
case "import_header":
|
|
4988
|
+
processImportHeader(node, context);
|
|
4989
|
+
break;
|
|
4990
|
+
case "class_declaration":
|
|
4991
|
+
processClassDeclaration5(node, context);
|
|
4992
|
+
break;
|
|
4993
|
+
case "object_declaration":
|
|
4994
|
+
processObjectDeclaration(node, context);
|
|
4995
|
+
break;
|
|
4996
|
+
case "companion_object":
|
|
4997
|
+
processCompanionObject(node, context);
|
|
4998
|
+
break;
|
|
4999
|
+
case "function_declaration":
|
|
5000
|
+
processFunctionDeclaration4(node, context);
|
|
5001
|
+
break;
|
|
5002
|
+
case "property_declaration":
|
|
5003
|
+
processPropertyDeclaration2(node, context);
|
|
5004
|
+
break;
|
|
5005
|
+
case "secondary_constructor":
|
|
5006
|
+
processSecondaryConstructor(node, context);
|
|
5007
|
+
break;
|
|
5008
|
+
case "type_alias":
|
|
5009
|
+
processTypeAlias(node, context);
|
|
5010
|
+
break;
|
|
5011
|
+
case "call_expression":
|
|
5012
|
+
processCallExpression10(node, context);
|
|
5013
|
+
break;
|
|
5014
|
+
case "navigation_expression":
|
|
5015
|
+
processNavigationExpression(node, context);
|
|
5016
|
+
break;
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
function processPackageHeader(node, context) {
|
|
5020
|
+
const ident = findDescendantByTypes2(node, ["identifier"]);
|
|
5021
|
+
if (!ident) return;
|
|
5022
|
+
const name = nodeText9(ident, context);
|
|
5023
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
5024
|
+
context.symbols.push({
|
|
5025
|
+
id: symbolId,
|
|
5026
|
+
name,
|
|
5027
|
+
kind: "module",
|
|
5028
|
+
filePath: context.filePath,
|
|
5029
|
+
startLine: node.startPosition.row + 1,
|
|
5030
|
+
endLine: node.endPosition.row + 1,
|
|
5031
|
+
exported: true
|
|
5032
|
+
});
|
|
5033
|
+
context.currentPackage = name;
|
|
5034
|
+
}
|
|
5035
|
+
function processImportHeader(node, context) {
|
|
5036
|
+
const ident = findDescendantByTypes2(node, ["identifier"]);
|
|
5037
|
+
if (!ident) return;
|
|
5038
|
+
let importPath = nodeText9(ident, context);
|
|
5039
|
+
const text = nodeText9(node, context).trim();
|
|
5040
|
+
const isWildcard = text.endsWith(".*");
|
|
5041
|
+
if (isWildcard && !importPath.endsWith(".*")) {
|
|
5042
|
+
importPath = importPath + ".*";
|
|
5043
|
+
}
|
|
5044
|
+
const aliasMatch = text.match(/\bas\s+(\w+)/);
|
|
5045
|
+
const alias = aliasMatch ? aliasMatch[1] : null;
|
|
5046
|
+
const resolvedPath = resolveKotlinImport(importPath, context.filePath, context.projectRoot);
|
|
5047
|
+
if (resolvedPath) {
|
|
5048
|
+
const sourceId = `${context.filePath}::__file__`;
|
|
5049
|
+
const targetId = `${resolvedPath}::__file__`;
|
|
5050
|
+
context.edges.push({
|
|
5051
|
+
source: sourceId,
|
|
5052
|
+
target: targetId,
|
|
5053
|
+
kind: "imports",
|
|
5054
|
+
filePath: context.filePath,
|
|
5055
|
+
line: node.startPosition.row + 1
|
|
5056
|
+
});
|
|
5057
|
+
const parts = importPath.replace(/\.\*$/, "").split(".");
|
|
5058
|
+
if (!isWildcard) {
|
|
5059
|
+
const simpleName = alias || parts[parts.length - 1];
|
|
5060
|
+
context.imports.set(simpleName, `${resolvedPath}::${parts[parts.length - 1]}`);
|
|
5061
|
+
}
|
|
5062
|
+
}
|
|
5063
|
+
const symbolId = `${context.filePath}::import:${importPath}`;
|
|
5064
|
+
context.symbols.push({
|
|
5065
|
+
id: symbolId,
|
|
5066
|
+
name: importPath,
|
|
5067
|
+
kind: "import",
|
|
5068
|
+
filePath: context.filePath,
|
|
5069
|
+
startLine: node.startPosition.row + 1,
|
|
5070
|
+
endLine: node.endPosition.row + 1,
|
|
5071
|
+
exported: false
|
|
5072
|
+
});
|
|
5073
|
+
}
|
|
5074
|
+
function processClassDeclaration5(node, context) {
|
|
5075
|
+
const nameNode = findChildByType10(node, "type_identifier");
|
|
5076
|
+
if (!nameNode) return;
|
|
5077
|
+
let name = nodeText9(nameNode, context);
|
|
5078
|
+
const angleBracketIdx = name.indexOf("<");
|
|
5079
|
+
if (angleBracketIdx > 0) {
|
|
5080
|
+
name = name.substring(0, angleBracketIdx);
|
|
5081
|
+
}
|
|
5082
|
+
const text = nodeText9(node, context);
|
|
5083
|
+
const modifiers = getModifiers(node, context);
|
|
5084
|
+
let kind = "class";
|
|
5085
|
+
if (text.match(/\binterface\b/) && !text.match(/\bfun\s+interface\b/)) {
|
|
5086
|
+
kind = "interface";
|
|
5087
|
+
} else if (text.match(/\benum\s+class\b/)) {
|
|
5088
|
+
kind = "enum";
|
|
5089
|
+
} else if (text.match(/\bannotation\s+class\b/)) {
|
|
5090
|
+
kind = "interface";
|
|
5091
|
+
}
|
|
5092
|
+
const exported = modifiers.includes("public") || modifiers.includes("internal") || !modifiers.includes("private") && !modifiers.includes("protected");
|
|
5093
|
+
const scope = context.currentClass || void 0;
|
|
5094
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
5095
|
+
context.symbols.push({
|
|
5096
|
+
id: symbolId,
|
|
5097
|
+
name,
|
|
5098
|
+
kind,
|
|
5099
|
+
filePath: context.filePath,
|
|
5100
|
+
startLine: node.startPosition.row + 1,
|
|
5101
|
+
endLine: node.endPosition.row + 1,
|
|
5102
|
+
exported,
|
|
5103
|
+
scope
|
|
5104
|
+
});
|
|
5105
|
+
const delegationSpecifiers = findChildByType10(node, "delegation_specifiers");
|
|
5106
|
+
if (delegationSpecifiers) {
|
|
5107
|
+
processDelegationSpecifiers(delegationSpecifiers, symbolId, context);
|
|
5108
|
+
}
|
|
5109
|
+
if (kind === "enum") {
|
|
5110
|
+
processEnumEntries(node, name, context);
|
|
5111
|
+
}
|
|
5112
|
+
const oldClass = context.currentClass;
|
|
5113
|
+
context.currentClass = name;
|
|
5114
|
+
context.currentScope.push(name);
|
|
5115
|
+
const body = findChildByType10(node, "class_body") || findChildByType10(node, "enum_class_body");
|
|
5116
|
+
if (body) {
|
|
5117
|
+
walkNode10(body, context);
|
|
5118
|
+
}
|
|
5119
|
+
context.currentScope.pop();
|
|
5120
|
+
context.currentClass = oldClass;
|
|
5121
|
+
}
|
|
5122
|
+
function processObjectDeclaration(node, context) {
|
|
5123
|
+
const nameNode = findChildByType10(node, "type_identifier");
|
|
5124
|
+
if (!nameNode) return;
|
|
5125
|
+
const name = nodeText9(nameNode, context);
|
|
5126
|
+
const modifiers = getModifiers(node, context);
|
|
5127
|
+
const exported = !modifiers.includes("private");
|
|
5128
|
+
const scope = context.currentClass || void 0;
|
|
5129
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
5130
|
+
context.symbols.push({
|
|
5131
|
+
id: symbolId,
|
|
5132
|
+
name,
|
|
5133
|
+
kind: "class",
|
|
5134
|
+
// Objects are singletons, map to class
|
|
5135
|
+
filePath: context.filePath,
|
|
5136
|
+
startLine: node.startPosition.row + 1,
|
|
5137
|
+
endLine: node.endPosition.row + 1,
|
|
5138
|
+
exported,
|
|
5139
|
+
scope
|
|
5140
|
+
});
|
|
5141
|
+
const delegationSpecifiers = findChildByType10(node, "delegation_specifiers");
|
|
5142
|
+
if (delegationSpecifiers) {
|
|
5143
|
+
processDelegationSpecifiers(delegationSpecifiers, symbolId, context);
|
|
5144
|
+
}
|
|
5145
|
+
const oldClass = context.currentClass;
|
|
5146
|
+
context.currentClass = name;
|
|
5147
|
+
context.currentScope.push(name);
|
|
5148
|
+
const body = findChildByType10(node, "class_body");
|
|
5149
|
+
if (body) {
|
|
5150
|
+
walkNode10(body, context);
|
|
5151
|
+
}
|
|
5152
|
+
context.currentScope.pop();
|
|
5153
|
+
context.currentClass = oldClass;
|
|
5154
|
+
}
|
|
5155
|
+
function processCompanionObject(node, context) {
|
|
5156
|
+
const nameNode = findChildByType10(node, "type_identifier");
|
|
5157
|
+
const name = nameNode ? nodeText9(nameNode, context) : "Companion";
|
|
5158
|
+
const scope = context.currentClass || void 0;
|
|
5159
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
5160
|
+
context.symbols.push({
|
|
5161
|
+
id: symbolId,
|
|
5162
|
+
name,
|
|
5163
|
+
kind: "class",
|
|
5164
|
+
filePath: context.filePath,
|
|
5165
|
+
startLine: node.startPosition.row + 1,
|
|
5166
|
+
endLine: node.endPosition.row + 1,
|
|
5167
|
+
exported: true,
|
|
5168
|
+
scope
|
|
5169
|
+
});
|
|
5170
|
+
const oldClass = context.currentClass;
|
|
5171
|
+
context.currentClass = scope ? `${scope}.${name}` : name;
|
|
5172
|
+
context.currentScope.push(context.currentClass);
|
|
5173
|
+
const body = findChildByType10(node, "class_body");
|
|
5174
|
+
if (body) {
|
|
5175
|
+
walkNode10(body, context);
|
|
5176
|
+
}
|
|
5177
|
+
context.currentScope.pop();
|
|
5178
|
+
context.currentClass = oldClass;
|
|
5179
|
+
}
|
|
5180
|
+
function processDelegationSpecifiers(node, sourceId, context) {
|
|
5181
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
5182
|
+
const child = node.child(i);
|
|
5183
|
+
if (!child) continue;
|
|
5184
|
+
const typeName = extractTypeName5(child, context);
|
|
5185
|
+
if (typeName) {
|
|
5186
|
+
const baseId = resolveSymbol9(typeName, context);
|
|
5187
|
+
if (baseId) {
|
|
5188
|
+
const edgeKind = typeName.startsWith("I") && typeName.length > 1 && typeName[1] === typeName[1].toUpperCase() ? "implements" : "inherits";
|
|
5189
|
+
context.edges.push({
|
|
5190
|
+
source: sourceId,
|
|
5191
|
+
target: baseId,
|
|
5192
|
+
kind: edgeKind,
|
|
5193
|
+
filePath: context.filePath,
|
|
5194
|
+
line: child.startPosition.row + 1
|
|
5195
|
+
});
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
}
|
|
5199
|
+
}
|
|
5200
|
+
function processEnumEntries(node, enumName, context) {
|
|
5201
|
+
const body = findChildByType10(node, "enum_class_body");
|
|
5202
|
+
if (!body) return;
|
|
5203
|
+
const entries = findChildrenByType4(body, "enum_entry");
|
|
5204
|
+
for (const entry of entries) {
|
|
5205
|
+
const nameNode = findChildByType10(entry, "simple_identifier");
|
|
5206
|
+
if (!nameNode) continue;
|
|
5207
|
+
const constName = nodeText9(nameNode, context);
|
|
5208
|
+
const constId = `${context.filePath}::${enumName}.${constName}`;
|
|
5209
|
+
context.symbols.push({
|
|
5210
|
+
id: constId,
|
|
5211
|
+
name: constName,
|
|
5212
|
+
kind: "constant",
|
|
5213
|
+
filePath: context.filePath,
|
|
5214
|
+
startLine: entry.startPosition.row + 1,
|
|
5215
|
+
endLine: entry.endPosition.row + 1,
|
|
5216
|
+
exported: true,
|
|
5217
|
+
scope: enumName
|
|
5218
|
+
});
|
|
5219
|
+
}
|
|
5220
|
+
}
|
|
5221
|
+
function processFunctionDeclaration4(node, context) {
|
|
5222
|
+
const nameNode = findChildByType10(node, "simple_identifier");
|
|
5223
|
+
if (!nameNode) return;
|
|
5224
|
+
const name = nodeText9(nameNode, context);
|
|
5225
|
+
const modifiers = getModifiers(node, context);
|
|
5226
|
+
const exported = !modifiers.includes("private");
|
|
5227
|
+
const scope = context.currentClass || void 0;
|
|
5228
|
+
const text = nodeText9(node, context);
|
|
5229
|
+
const isExtension = text.match(/fun\s+[\w.<>, ]+\./) !== null;
|
|
5230
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
5231
|
+
context.symbols.push({
|
|
5232
|
+
id: symbolId,
|
|
5233
|
+
name,
|
|
5234
|
+
kind: context.currentClass ? "method" : "function",
|
|
5235
|
+
filePath: context.filePath,
|
|
5236
|
+
startLine: node.startPosition.row + 1,
|
|
5237
|
+
endLine: node.endPosition.row + 1,
|
|
5238
|
+
exported,
|
|
5239
|
+
scope
|
|
5240
|
+
});
|
|
5241
|
+
const scopeName = scope ? `${scope}.${name}` : name;
|
|
5242
|
+
context.currentScope.push(scopeName);
|
|
5243
|
+
const body = findChildByType10(node, "function_body");
|
|
5244
|
+
if (body) {
|
|
5245
|
+
walkNode10(body, context);
|
|
5246
|
+
}
|
|
5247
|
+
context.currentScope.pop();
|
|
5248
|
+
}
|
|
5249
|
+
function processPropertyDeclaration2(node, context) {
|
|
5250
|
+
const varDecl = findChildByType10(node, "variable_declaration");
|
|
5251
|
+
if (!varDecl) return;
|
|
5252
|
+
const nameNode = findChildByType10(varDecl, "simple_identifier");
|
|
5253
|
+
if (!nameNode) return;
|
|
5254
|
+
const name = nodeText9(nameNode, context);
|
|
5255
|
+
const modifiers = getModifiers(node, context);
|
|
5256
|
+
const exported = !modifiers.includes("private");
|
|
5257
|
+
const scope = context.currentClass || void 0;
|
|
5258
|
+
const text = nodeText9(node, context);
|
|
5259
|
+
const isConst = modifiers.includes("const") || text.match(/\bval\b/) !== null && text.match(/\bconst\b/) !== null;
|
|
5260
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
5261
|
+
context.symbols.push({
|
|
5262
|
+
id: symbolId,
|
|
5263
|
+
name,
|
|
5264
|
+
kind: isConst ? "constant" : "property",
|
|
5265
|
+
filePath: context.filePath,
|
|
5266
|
+
startLine: node.startPosition.row + 1,
|
|
5267
|
+
endLine: node.endPosition.row + 1,
|
|
5268
|
+
exported,
|
|
5269
|
+
scope
|
|
5270
|
+
});
|
|
5271
|
+
}
|
|
5272
|
+
function processSecondaryConstructor(node, context) {
|
|
5273
|
+
const scope = context.currentClass || void 0;
|
|
5274
|
+
if (!scope) return;
|
|
5275
|
+
const name = "constructor";
|
|
5276
|
+
const symbolId = `${context.filePath}::${scope}.${name}:${node.startPosition.row + 1}`;
|
|
5277
|
+
context.symbols.push({
|
|
5278
|
+
id: symbolId,
|
|
5279
|
+
name,
|
|
5280
|
+
kind: "method",
|
|
5281
|
+
filePath: context.filePath,
|
|
5282
|
+
startLine: node.startPosition.row + 1,
|
|
5283
|
+
endLine: node.endPosition.row + 1,
|
|
5284
|
+
exported: true,
|
|
5285
|
+
scope
|
|
5286
|
+
});
|
|
5287
|
+
const scopeName = `${scope}.${name}`;
|
|
5288
|
+
context.currentScope.push(scopeName);
|
|
5289
|
+
const body = findChildByType10(node, "function_body");
|
|
5290
|
+
if (body) {
|
|
5291
|
+
walkNode10(body, context);
|
|
5292
|
+
}
|
|
5293
|
+
context.currentScope.pop();
|
|
5294
|
+
}
|
|
5295
|
+
function processTypeAlias(node, context) {
|
|
5296
|
+
const nameNode = findChildByType10(node, "type_identifier");
|
|
5297
|
+
if (!nameNode) return;
|
|
5298
|
+
const name = nodeText9(nameNode, context);
|
|
5299
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
5300
|
+
context.symbols.push({
|
|
5301
|
+
id: symbolId,
|
|
5302
|
+
name,
|
|
5303
|
+
kind: "type_alias",
|
|
5304
|
+
filePath: context.filePath,
|
|
5305
|
+
startLine: node.startPosition.row + 1,
|
|
5306
|
+
endLine: node.endPosition.row + 1,
|
|
5307
|
+
exported: true
|
|
5308
|
+
});
|
|
5309
|
+
}
|
|
5310
|
+
function processCallExpression10(node, context) {
|
|
5311
|
+
if (context.currentScope.length === 0) return;
|
|
5312
|
+
const firstChild = node.child(0);
|
|
5313
|
+
if (!firstChild) return;
|
|
5314
|
+
let calleeName = null;
|
|
5315
|
+
if (firstChild.type === "simple_identifier") {
|
|
5316
|
+
calleeName = nodeText9(firstChild, context);
|
|
5317
|
+
} else if (firstChild.type === "navigation_expression") {
|
|
5318
|
+
const suffix = findChildByType10(firstChild, "navigation_suffix");
|
|
5319
|
+
if (suffix) {
|
|
5320
|
+
const ident = findChildByType10(suffix, "simple_identifier");
|
|
5321
|
+
if (ident) calleeName = nodeText9(ident, context);
|
|
5322
|
+
}
|
|
5323
|
+
if (!calleeName) {
|
|
5324
|
+
for (let i = firstChild.childCount - 1; i >= 0; i--) {
|
|
5325
|
+
const child = firstChild.child(i);
|
|
5326
|
+
if (child && child.type === "simple_identifier") {
|
|
5327
|
+
calleeName = nodeText9(child, context);
|
|
5328
|
+
break;
|
|
5329
|
+
}
|
|
5330
|
+
}
|
|
5331
|
+
}
|
|
5332
|
+
}
|
|
5333
|
+
if (!calleeName) return;
|
|
5334
|
+
const builtins = /* @__PURE__ */ new Set([
|
|
5335
|
+
"println",
|
|
5336
|
+
"print",
|
|
5337
|
+
"toString",
|
|
5338
|
+
"equals",
|
|
5339
|
+
"hashCode",
|
|
5340
|
+
"let",
|
|
5341
|
+
"apply",
|
|
5342
|
+
"also",
|
|
5343
|
+
"run",
|
|
5344
|
+
"with",
|
|
5345
|
+
"takeIf",
|
|
5346
|
+
"takeUnless",
|
|
5347
|
+
"repeat",
|
|
5348
|
+
"require",
|
|
5349
|
+
"check",
|
|
5350
|
+
"error",
|
|
5351
|
+
"TODO",
|
|
5352
|
+
"listOf",
|
|
5353
|
+
"mapOf",
|
|
5354
|
+
"setOf",
|
|
5355
|
+
"arrayOf",
|
|
5356
|
+
"mutableListOf",
|
|
5357
|
+
"mutableMapOf",
|
|
5358
|
+
"mutableSetOf",
|
|
5359
|
+
"emptyList",
|
|
5360
|
+
"emptyMap",
|
|
5361
|
+
"emptySet",
|
|
5362
|
+
"to",
|
|
5363
|
+
"Pair",
|
|
5364
|
+
"Triple",
|
|
5365
|
+
"lazy",
|
|
5366
|
+
"synchronized",
|
|
5367
|
+
"map",
|
|
5368
|
+
"filter",
|
|
5369
|
+
"forEach",
|
|
5370
|
+
"flatMap",
|
|
5371
|
+
"fold",
|
|
5372
|
+
"reduce",
|
|
5373
|
+
"any",
|
|
5374
|
+
"all",
|
|
5375
|
+
"none",
|
|
5376
|
+
"find",
|
|
5377
|
+
"first",
|
|
5378
|
+
"last",
|
|
5379
|
+
"count",
|
|
5380
|
+
"sum",
|
|
5381
|
+
"average",
|
|
5382
|
+
"sortedBy",
|
|
5383
|
+
"groupBy",
|
|
5384
|
+
"associate",
|
|
5385
|
+
"zip",
|
|
5386
|
+
"joinToString",
|
|
5387
|
+
"getOrDefault",
|
|
5388
|
+
"getOrElse",
|
|
5389
|
+
"getOrPut",
|
|
5390
|
+
"contains",
|
|
5391
|
+
"containsKey",
|
|
5392
|
+
"add",
|
|
5393
|
+
"remove",
|
|
5394
|
+
"clear",
|
|
5395
|
+
"size",
|
|
5396
|
+
"isEmpty",
|
|
5397
|
+
"isNotEmpty"
|
|
5398
|
+
]);
|
|
5399
|
+
if (builtins.has(calleeName)) return;
|
|
5400
|
+
const callerId = getCurrentSymbolId10(context);
|
|
5401
|
+
if (!callerId) return;
|
|
5402
|
+
const calleeId = resolveSymbol9(calleeName, context);
|
|
5403
|
+
if (calleeId) {
|
|
5404
|
+
context.edges.push({
|
|
5405
|
+
source: callerId,
|
|
5406
|
+
target: calleeId,
|
|
5407
|
+
kind: "calls",
|
|
5408
|
+
filePath: context.filePath,
|
|
5409
|
+
line: node.startPosition.row + 1
|
|
5410
|
+
});
|
|
5411
|
+
}
|
|
5412
|
+
}
|
|
5413
|
+
function processNavigationExpression(node, context) {
|
|
5414
|
+
}
|
|
5415
|
+
function parseGradleBuild2(filePath, sourceCode, projectRoot) {
|
|
5416
|
+
const symbols = [];
|
|
5417
|
+
const edges = [];
|
|
5418
|
+
const lines = sourceCode.split("\n");
|
|
5419
|
+
const projectName = basename4(dirname10(join11(projectRoot, filePath)));
|
|
5420
|
+
symbols.push({
|
|
5421
|
+
id: `${filePath}::${projectName}`,
|
|
5422
|
+
name: projectName,
|
|
5423
|
+
kind: "module",
|
|
5424
|
+
filePath,
|
|
5425
|
+
startLine: 1,
|
|
5426
|
+
endLine: lines.length,
|
|
5427
|
+
exported: true
|
|
5428
|
+
});
|
|
5429
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5430
|
+
const line = lines[i].trim();
|
|
5431
|
+
const lineNum = i + 1;
|
|
5432
|
+
const depMatch = line.match(
|
|
5433
|
+
/(?:implementation|api|compileOnly|runtimeOnly|testImplementation|testRuntimeOnly|kapt|ksp|annotationProcessor)\s*[\(]?\s*['"]([^'"]+)['"]\s*[\)]?/
|
|
5434
|
+
);
|
|
5435
|
+
if (depMatch) {
|
|
5436
|
+
const depCoord = depMatch[1];
|
|
5437
|
+
if (!depCoord.startsWith(":")) {
|
|
5438
|
+
symbols.push({
|
|
5439
|
+
id: `${filePath}::dep:${depCoord}`,
|
|
5440
|
+
name: depCoord,
|
|
5441
|
+
kind: "import",
|
|
5442
|
+
filePath,
|
|
5443
|
+
startLine: lineNum,
|
|
5444
|
+
endLine: lineNum,
|
|
5445
|
+
exported: false
|
|
5446
|
+
});
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
const projectMatch = line.match(/project\s*\(\s*['":]+([^'")\s]+)['"]*\s*\)/);
|
|
5450
|
+
if (projectMatch) {
|
|
5451
|
+
const moduleName = projectMatch[1].replace(/^:/, "");
|
|
5452
|
+
const candidates = [
|
|
5453
|
+
join11(moduleName, "build.gradle.kts"),
|
|
5454
|
+
join11(moduleName, "build.gradle")
|
|
5455
|
+
];
|
|
5456
|
+
for (const candidate of candidates) {
|
|
5457
|
+
if (existsSync11(join11(projectRoot, candidate))) {
|
|
5458
|
+
edges.push({
|
|
5459
|
+
source: `${filePath}::__file__`,
|
|
5460
|
+
target: `${candidate}::__file__`,
|
|
5461
|
+
kind: "imports",
|
|
5462
|
+
filePath,
|
|
5463
|
+
line: lineNum
|
|
5464
|
+
});
|
|
5465
|
+
break;
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
return { filePath, symbols, edges };
|
|
5471
|
+
}
|
|
5472
|
+
function parseSettingsGradle(filePath, sourceCode, projectRoot) {
|
|
5473
|
+
const symbols = [];
|
|
5474
|
+
const edges = [];
|
|
5475
|
+
const lines = sourceCode.split("\n");
|
|
5476
|
+
symbols.push({
|
|
5477
|
+
id: `${filePath}::settings`,
|
|
5478
|
+
name: "settings",
|
|
5479
|
+
kind: "module",
|
|
5480
|
+
filePath,
|
|
5481
|
+
startLine: 1,
|
|
5482
|
+
endLine: lines.length,
|
|
5483
|
+
exported: true
|
|
5484
|
+
});
|
|
5485
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5486
|
+
const line = lines[i].trim();
|
|
5487
|
+
const lineNum = i + 1;
|
|
5488
|
+
const includeMatches = line.matchAll(/['"]:([^'"]+)['"]/g);
|
|
5489
|
+
for (const match of includeMatches) {
|
|
5490
|
+
const moduleName = match[1];
|
|
5491
|
+
const candidates = [
|
|
5492
|
+
join11(moduleName, "build.gradle.kts"),
|
|
5493
|
+
join11(moduleName, "build.gradle")
|
|
5494
|
+
];
|
|
5495
|
+
for (const candidate of candidates) {
|
|
5496
|
+
if (existsSync11(join11(projectRoot, candidate))) {
|
|
5497
|
+
edges.push({
|
|
5498
|
+
source: `${filePath}::__file__`,
|
|
5499
|
+
target: `${candidate}::__file__`,
|
|
5500
|
+
kind: "imports",
|
|
5501
|
+
filePath,
|
|
5502
|
+
line: lineNum
|
|
5503
|
+
});
|
|
5504
|
+
break;
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
5507
|
+
}
|
|
5508
|
+
}
|
|
5509
|
+
return { filePath, symbols, edges };
|
|
5510
|
+
}
|
|
5511
|
+
function resolveKotlinImport(importPath, currentFile, projectRoot) {
|
|
5512
|
+
const cleanPath2 = importPath.replace(/\.\*$/, "");
|
|
5513
|
+
const parts = cleanPath2.split(".");
|
|
5514
|
+
const className = parts[parts.length - 1];
|
|
5515
|
+
const packagePath = parts.slice(0, -1).join("/");
|
|
5516
|
+
const sourceRoots = [
|
|
5517
|
+
"",
|
|
5518
|
+
"src/main/kotlin",
|
|
5519
|
+
"src/main/java",
|
|
5520
|
+
// Kotlin can live in java source dirs
|
|
5521
|
+
"src",
|
|
5522
|
+
"app/src/main/kotlin",
|
|
5523
|
+
"app/src/main/java"
|
|
5524
|
+
];
|
|
5525
|
+
for (const root of sourceRoots) {
|
|
5526
|
+
for (const ext of [".kt", ".java"]) {
|
|
5527
|
+
const filePath = packagePath ? join11(packagePath, className + ext) : className + ext;
|
|
5528
|
+
const candidate = root ? join11(root, filePath) : filePath;
|
|
5529
|
+
const fullPath = join11(projectRoot, candidate);
|
|
5530
|
+
if (existsSync11(fullPath)) {
|
|
5531
|
+
return candidate;
|
|
5532
|
+
}
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5535
|
+
if (importPath.endsWith(".*")) {
|
|
5536
|
+
const dirPath = cleanPath2.replace(/\./g, "/");
|
|
5537
|
+
for (const root of sourceRoots) {
|
|
5538
|
+
const candidate = root ? join11(root, dirPath) : dirPath;
|
|
5539
|
+
const fullPath = join11(projectRoot, candidate);
|
|
5540
|
+
if (existsSync11(fullPath)) {
|
|
5541
|
+
try {
|
|
5542
|
+
const stats = statSync5(fullPath);
|
|
5543
|
+
if (stats.isDirectory()) {
|
|
5544
|
+
const ktFiles = readdirSync8(fullPath).filter((f) => f.endsWith(".kt"));
|
|
5545
|
+
if (ktFiles.length > 0) {
|
|
5546
|
+
return join11(candidate, ktFiles[0]);
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5549
|
+
} catch {
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
return null;
|
|
5555
|
+
}
|
|
5556
|
+
function resolveSymbol9(name, context) {
|
|
5557
|
+
if (context.imports.has(name)) {
|
|
5558
|
+
return context.imports.get(name) || null;
|
|
5559
|
+
}
|
|
5560
|
+
const currentFileId = `${context.filePath}::${name}`;
|
|
5561
|
+
if (context.symbols.find((s) => s.id === currentFileId)) {
|
|
5562
|
+
return currentFileId;
|
|
5563
|
+
}
|
|
5564
|
+
if (context.currentClass) {
|
|
5565
|
+
const classMethodId = `${context.filePath}::${context.currentClass}.${name}`;
|
|
5566
|
+
if (context.symbols.find((s) => s.id === classMethodId)) {
|
|
5567
|
+
return classMethodId;
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
return null;
|
|
5571
|
+
}
|
|
5572
|
+
function getModifiers(node, context) {
|
|
5573
|
+
const modifiers = [];
|
|
5574
|
+
const modList = findChildByType10(node, "modifiers");
|
|
5575
|
+
if (modList) {
|
|
5576
|
+
for (let i = 0; i < modList.childCount; i++) {
|
|
5577
|
+
const child = modList.child(i);
|
|
5578
|
+
if (child) {
|
|
5579
|
+
const text = nodeText9(child, context).trim();
|
|
5580
|
+
if (text) modifiers.push(text);
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5583
|
+
}
|
|
5584
|
+
return modifiers;
|
|
5585
|
+
}
|
|
5586
|
+
function extractTypeName5(node, context) {
|
|
5587
|
+
const text = nodeText9(node, context).trim();
|
|
5588
|
+
if (!text || text === "," || text === ":") return null;
|
|
5589
|
+
let name = text;
|
|
5590
|
+
const angleBracketIdx = name.indexOf("<");
|
|
5591
|
+
if (angleBracketIdx > 0) name = name.substring(0, angleBracketIdx);
|
|
5592
|
+
const parenIdx = name.indexOf("(");
|
|
5593
|
+
if (parenIdx > 0) name = name.substring(0, parenIdx);
|
|
5594
|
+
const dotIdx = name.lastIndexOf(".");
|
|
5595
|
+
name = dotIdx >= 0 ? name.substring(dotIdx + 1) : name;
|
|
5596
|
+
return name.trim() || null;
|
|
5597
|
+
}
|
|
5598
|
+
function findChildByType10(node, type) {
|
|
5599
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
5600
|
+
const child = node.child(i);
|
|
5601
|
+
if (child && child.type === type) return child;
|
|
5602
|
+
}
|
|
5603
|
+
return null;
|
|
5604
|
+
}
|
|
5605
|
+
function findChildrenByType4(node, type) {
|
|
5606
|
+
const results = [];
|
|
5607
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
5608
|
+
const child = node.child(i);
|
|
5609
|
+
if (child && child.type === type) results.push(child);
|
|
5610
|
+
}
|
|
5611
|
+
return results;
|
|
5612
|
+
}
|
|
5613
|
+
function findDescendantByTypes2(node, types) {
|
|
5614
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
5615
|
+
const child = node.child(i);
|
|
5616
|
+
if (!child) continue;
|
|
5617
|
+
if (types.includes(child.type)) return child;
|
|
5618
|
+
const found = findDescendantByTypes2(child, types);
|
|
5619
|
+
if (found) return found;
|
|
5620
|
+
}
|
|
5621
|
+
return null;
|
|
5622
|
+
}
|
|
5623
|
+
function nodeText9(node, context) {
|
|
5624
|
+
return context.sourceCode.substring(node.startIndex, node.endIndex);
|
|
5625
|
+
}
|
|
5626
|
+
function getCurrentSymbolId10(context) {
|
|
5627
|
+
if (context.currentScope.length === 0) return null;
|
|
5628
|
+
return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
|
|
5629
|
+
}
|
|
5630
|
+
var kotlinParser = {
|
|
5631
|
+
name: "kotlin",
|
|
5632
|
+
extensions: [".kt", ".kts", "build.gradle.kts", "settings.gradle.kts", "settings.gradle"],
|
|
5633
|
+
parseFile: parseKotlinFile
|
|
5634
|
+
};
|
|
5635
|
+
|
|
5636
|
+
// src/parser/php.ts
|
|
5637
|
+
import { dirname as dirname11, join as join12 } from "path";
|
|
5638
|
+
import { existsSync as existsSync12 } from "fs";
|
|
5639
|
+
function parsePhpFile(filePath, sourceCode, projectRoot) {
|
|
5640
|
+
const parser = getParser("php");
|
|
5641
|
+
const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
|
|
5642
|
+
const context = {
|
|
5643
|
+
filePath,
|
|
5644
|
+
projectRoot,
|
|
5645
|
+
sourceCode,
|
|
5646
|
+
symbols: [],
|
|
5647
|
+
edges: [],
|
|
5648
|
+
currentScope: [],
|
|
5649
|
+
currentClass: null,
|
|
5650
|
+
currentNamespace: null,
|
|
5651
|
+
imports: /* @__PURE__ */ new Map()
|
|
5652
|
+
};
|
|
5653
|
+
walkNode11(tree.rootNode, context);
|
|
5654
|
+
return {
|
|
5655
|
+
filePath,
|
|
5656
|
+
symbols: context.symbols,
|
|
5657
|
+
edges: context.edges
|
|
5658
|
+
};
|
|
5659
|
+
}
|
|
5660
|
+
var SCOPE_TYPES = /* @__PURE__ */ new Set([
|
|
5661
|
+
"class_declaration",
|
|
5662
|
+
"interface_declaration",
|
|
5663
|
+
"trait_declaration",
|
|
5664
|
+
"enum_declaration",
|
|
5665
|
+
"function_definition",
|
|
5666
|
+
"method_declaration"
|
|
5667
|
+
]);
|
|
5668
|
+
function walkNode11(node, context) {
|
|
5669
|
+
processNode11(node, context);
|
|
5670
|
+
if (SCOPE_TYPES.has(node.type)) return;
|
|
5671
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
5672
|
+
const child = node.child(i);
|
|
5673
|
+
if (child) {
|
|
5674
|
+
walkNode11(child, context);
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
}
|
|
5678
|
+
function processNode11(node, context) {
|
|
5679
|
+
switch (node.type) {
|
|
5680
|
+
case "namespace_definition":
|
|
5681
|
+
processNamespaceDefinition2(node, context);
|
|
5682
|
+
break;
|
|
5683
|
+
case "namespace_use_declaration":
|
|
5684
|
+
processUseDeclaration2(node, context);
|
|
5685
|
+
break;
|
|
5686
|
+
case "class_declaration":
|
|
5687
|
+
processClassDeclaration6(node, context);
|
|
5688
|
+
break;
|
|
5689
|
+
case "interface_declaration":
|
|
5690
|
+
processInterfaceDeclaration4(node, context);
|
|
5691
|
+
break;
|
|
5692
|
+
case "trait_declaration":
|
|
5693
|
+
processTraitDeclaration(node, context);
|
|
5694
|
+
break;
|
|
5695
|
+
case "enum_declaration":
|
|
5696
|
+
processEnumDeclaration4(node, context);
|
|
5697
|
+
break;
|
|
5698
|
+
case "function_definition":
|
|
5699
|
+
processFunctionDefinition4(node, context);
|
|
5700
|
+
break;
|
|
5701
|
+
case "method_declaration":
|
|
5702
|
+
processMethodDeclaration4(node, context);
|
|
5703
|
+
break;
|
|
5704
|
+
case "property_declaration":
|
|
5705
|
+
processPropertyDeclaration3(node, context);
|
|
5706
|
+
break;
|
|
5707
|
+
case "const_declaration":
|
|
5708
|
+
processConstDeclaration2(node, context);
|
|
5709
|
+
break;
|
|
5710
|
+
case "function_call_expression":
|
|
5711
|
+
processCallExpression11(node, context);
|
|
5712
|
+
break;
|
|
5713
|
+
case "member_call_expression":
|
|
5714
|
+
processMemberCallExpression(node, context);
|
|
5715
|
+
break;
|
|
5716
|
+
case "scoped_call_expression":
|
|
5717
|
+
processScopedCallExpression(node, context);
|
|
5718
|
+
break;
|
|
5719
|
+
case "include_expression":
|
|
5720
|
+
case "include_once_expression":
|
|
5721
|
+
case "require_expression":
|
|
5722
|
+
case "require_once_expression":
|
|
5723
|
+
processIncludeRequire(node, context);
|
|
5724
|
+
break;
|
|
5725
|
+
}
|
|
5726
|
+
}
|
|
5727
|
+
function processNamespaceDefinition2(node, context) {
|
|
5728
|
+
const nameNode = findChildByType11(node, "namespace_name");
|
|
5729
|
+
if (!nameNode) return;
|
|
5730
|
+
const name = nodeText10(nameNode, context);
|
|
5731
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
5732
|
+
context.symbols.push({
|
|
5733
|
+
id: symbolId,
|
|
5734
|
+
name,
|
|
5735
|
+
kind: "module",
|
|
5736
|
+
filePath: context.filePath,
|
|
5737
|
+
startLine: node.startPosition.row + 1,
|
|
5738
|
+
endLine: node.endPosition.row + 1,
|
|
5739
|
+
exported: true
|
|
5740
|
+
});
|
|
5741
|
+
context.currentNamespace = name;
|
|
5742
|
+
}
|
|
5743
|
+
function processUseDeclaration2(node, context) {
|
|
5744
|
+
const clauses = findChildrenByType5(node, "namespace_use_clause");
|
|
5745
|
+
for (const clause of clauses) {
|
|
5746
|
+
const nameNode = findChildByType11(clause, "namespace_name") || findChildByType11(clause, "qualified_name");
|
|
5747
|
+
if (!nameNode) continue;
|
|
5748
|
+
const importPath = nodeText10(nameNode, context);
|
|
5749
|
+
const aliasNode = findChildByType11(clause, "namespace_aliasing_clause");
|
|
5750
|
+
const alias = aliasNode ? nodeText10(findChildByType11(aliasNode, "name") || aliasNode, context).trim() : null;
|
|
5751
|
+
const parts = importPath.split("\\");
|
|
5752
|
+
const simpleName = alias || parts[parts.length - 1];
|
|
5753
|
+
const resolvedPath = resolvePhpImport(importPath, context.filePath, context.projectRoot);
|
|
5754
|
+
if (resolvedPath) {
|
|
5755
|
+
const sourceId = `${context.filePath}::__file__`;
|
|
5756
|
+
const targetId = `${resolvedPath}::__file__`;
|
|
5757
|
+
context.edges.push({
|
|
5758
|
+
source: sourceId,
|
|
5759
|
+
target: targetId,
|
|
5760
|
+
kind: "imports",
|
|
5761
|
+
filePath: context.filePath,
|
|
5762
|
+
line: node.startPosition.row + 1
|
|
5763
|
+
});
|
|
5764
|
+
context.imports.set(simpleName, `${resolvedPath}::${parts[parts.length - 1]}`);
|
|
5765
|
+
}
|
|
5766
|
+
const symbolId = `${context.filePath}::import:${importPath}`;
|
|
5767
|
+
context.symbols.push({
|
|
5768
|
+
id: symbolId,
|
|
5769
|
+
name: importPath,
|
|
5770
|
+
kind: "import",
|
|
5771
|
+
filePath: context.filePath,
|
|
5772
|
+
startLine: node.startPosition.row + 1,
|
|
5773
|
+
endLine: node.endPosition.row + 1,
|
|
5774
|
+
exported: false
|
|
5775
|
+
});
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
function processClassDeclaration6(node, context) {
|
|
5779
|
+
const nameNode = findChildByType11(node, "name");
|
|
5780
|
+
if (!nameNode) return;
|
|
5781
|
+
const name = nodeText10(nameNode, context);
|
|
5782
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
5783
|
+
const text = nodeText10(node, context);
|
|
5784
|
+
const isAbstract = text.trimStart().startsWith("abstract");
|
|
5785
|
+
context.symbols.push({
|
|
5786
|
+
id: symbolId,
|
|
5787
|
+
name,
|
|
5788
|
+
kind: "class",
|
|
5789
|
+
filePath: context.filePath,
|
|
5790
|
+
startLine: node.startPosition.row + 1,
|
|
5791
|
+
endLine: node.endPosition.row + 1,
|
|
5792
|
+
exported: true,
|
|
5793
|
+
scope: context.currentClass || void 0
|
|
5794
|
+
});
|
|
5795
|
+
const baseClause = findChildByType11(node, "base_clause");
|
|
5796
|
+
if (baseClause) {
|
|
5797
|
+
const baseName = extractQualifiedName(baseClause, context);
|
|
5798
|
+
if (baseName) {
|
|
5799
|
+
const baseId = resolveSymbol10(baseName, context);
|
|
5800
|
+
if (baseId) {
|
|
5801
|
+
context.edges.push({
|
|
5802
|
+
source: symbolId,
|
|
5803
|
+
target: baseId,
|
|
5804
|
+
kind: "inherits",
|
|
5805
|
+
filePath: context.filePath,
|
|
5806
|
+
line: node.startPosition.row + 1
|
|
5807
|
+
});
|
|
5808
|
+
}
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5811
|
+
const interfaceClause = findChildByType11(node, "class_interface_clause");
|
|
5812
|
+
if (interfaceClause) {
|
|
5813
|
+
const names = findChildrenByType5(interfaceClause, "name");
|
|
5814
|
+
const qualifiedNames = findChildrenByType5(interfaceClause, "qualified_name");
|
|
5815
|
+
for (const n of [...names, ...qualifiedNames]) {
|
|
5816
|
+
const ifaceName = nodeText10(n, context).trim();
|
|
5817
|
+
if (ifaceName && ifaceName !== ",") {
|
|
5818
|
+
const ifaceId = resolveSymbol10(ifaceName, context);
|
|
5819
|
+
if (ifaceId) {
|
|
5820
|
+
context.edges.push({
|
|
5821
|
+
source: symbolId,
|
|
5822
|
+
target: ifaceId,
|
|
5823
|
+
kind: "implements",
|
|
5824
|
+
filePath: context.filePath,
|
|
5825
|
+
line: node.startPosition.row + 1
|
|
5826
|
+
});
|
|
5827
|
+
}
|
|
5828
|
+
}
|
|
5829
|
+
}
|
|
5830
|
+
}
|
|
5831
|
+
const oldClass = context.currentClass;
|
|
5832
|
+
context.currentClass = name;
|
|
5833
|
+
context.currentScope.push(name);
|
|
5834
|
+
const body = findChildByType11(node, "declaration_list");
|
|
5835
|
+
if (body) {
|
|
5836
|
+
walkNode11(body, context);
|
|
5837
|
+
}
|
|
5838
|
+
context.currentScope.pop();
|
|
5839
|
+
context.currentClass = oldClass;
|
|
5840
|
+
}
|
|
5841
|
+
function processInterfaceDeclaration4(node, context) {
|
|
5842
|
+
const nameNode = findChildByType11(node, "name");
|
|
5843
|
+
if (!nameNode) return;
|
|
5844
|
+
const name = nodeText10(nameNode, context);
|
|
5845
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
5846
|
+
context.symbols.push({
|
|
5847
|
+
id: symbolId,
|
|
5848
|
+
name,
|
|
5849
|
+
kind: "interface",
|
|
5850
|
+
filePath: context.filePath,
|
|
5851
|
+
startLine: node.startPosition.row + 1,
|
|
5852
|
+
endLine: node.endPosition.row + 1,
|
|
5853
|
+
exported: true,
|
|
5854
|
+
scope: context.currentClass || void 0
|
|
5855
|
+
});
|
|
5856
|
+
const oldClass = context.currentClass;
|
|
5857
|
+
context.currentClass = name;
|
|
5858
|
+
context.currentScope.push(name);
|
|
5859
|
+
const body = findChildByType11(node, "declaration_list");
|
|
5860
|
+
if (body) {
|
|
5861
|
+
walkNode11(body, context);
|
|
5862
|
+
}
|
|
5863
|
+
context.currentScope.pop();
|
|
5864
|
+
context.currentClass = oldClass;
|
|
5865
|
+
}
|
|
5866
|
+
function processTraitDeclaration(node, context) {
|
|
5867
|
+
const nameNode = findChildByType11(node, "name");
|
|
5868
|
+
if (!nameNode) return;
|
|
5869
|
+
const name = nodeText10(nameNode, context);
|
|
5870
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
5871
|
+
context.symbols.push({
|
|
5872
|
+
id: symbolId,
|
|
5873
|
+
name,
|
|
5874
|
+
kind: "class",
|
|
5875
|
+
// Traits map to class kind
|
|
5876
|
+
filePath: context.filePath,
|
|
5877
|
+
startLine: node.startPosition.row + 1,
|
|
5878
|
+
endLine: node.endPosition.row + 1,
|
|
5879
|
+
exported: true,
|
|
5880
|
+
scope: context.currentClass || void 0
|
|
5881
|
+
});
|
|
5882
|
+
const oldClass = context.currentClass;
|
|
5883
|
+
context.currentClass = name;
|
|
5884
|
+
context.currentScope.push(name);
|
|
5885
|
+
const body = findChildByType11(node, "declaration_list");
|
|
5886
|
+
if (body) {
|
|
5887
|
+
walkNode11(body, context);
|
|
5888
|
+
}
|
|
5889
|
+
context.currentScope.pop();
|
|
5890
|
+
context.currentClass = oldClass;
|
|
5891
|
+
}
|
|
5892
|
+
function processEnumDeclaration4(node, context) {
|
|
5893
|
+
const nameNode = findChildByType11(node, "name");
|
|
5894
|
+
if (!nameNode) return;
|
|
5895
|
+
const name = nodeText10(nameNode, context);
|
|
5896
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
5897
|
+
context.symbols.push({
|
|
5898
|
+
id: symbolId,
|
|
5899
|
+
name,
|
|
5900
|
+
kind: "enum",
|
|
5901
|
+
filePath: context.filePath,
|
|
5902
|
+
startLine: node.startPosition.row + 1,
|
|
5903
|
+
endLine: node.endPosition.row + 1,
|
|
5904
|
+
exported: true
|
|
5905
|
+
});
|
|
5906
|
+
const oldClass = context.currentClass;
|
|
5907
|
+
context.currentClass = name;
|
|
5908
|
+
context.currentScope.push(name);
|
|
5909
|
+
const body = findChildByType11(node, "declaration_list");
|
|
5910
|
+
if (body) {
|
|
5911
|
+
walkNode11(body, context);
|
|
5912
|
+
}
|
|
5913
|
+
context.currentScope.pop();
|
|
5914
|
+
context.currentClass = oldClass;
|
|
5915
|
+
}
|
|
5916
|
+
function processFunctionDefinition4(node, context) {
|
|
5917
|
+
const nameNode = findChildByType11(node, "name");
|
|
5918
|
+
if (!nameNode) return;
|
|
5919
|
+
const name = nodeText10(nameNode, context);
|
|
5920
|
+
const scope = context.currentClass || void 0;
|
|
5921
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
5922
|
+
context.symbols.push({
|
|
5923
|
+
id: symbolId,
|
|
5924
|
+
name,
|
|
5925
|
+
kind: "function",
|
|
5926
|
+
filePath: context.filePath,
|
|
5927
|
+
startLine: node.startPosition.row + 1,
|
|
5928
|
+
endLine: node.endPosition.row + 1,
|
|
5929
|
+
exported: true,
|
|
5930
|
+
scope
|
|
5931
|
+
});
|
|
5932
|
+
const scopeName = scope ? `${scope}.${name}` : name;
|
|
5933
|
+
context.currentScope.push(scopeName);
|
|
5934
|
+
const body = findChildByType11(node, "compound_statement");
|
|
5935
|
+
if (body) {
|
|
5936
|
+
walkNode11(body, context);
|
|
5937
|
+
}
|
|
5938
|
+
context.currentScope.pop();
|
|
5939
|
+
}
|
|
5940
|
+
function processMethodDeclaration4(node, context) {
|
|
5941
|
+
const nameNode = findChildByType11(node, "name");
|
|
5942
|
+
if (!nameNode) return;
|
|
5943
|
+
const name = nodeText10(nameNode, context);
|
|
5944
|
+
const modifiers = getModifiers2(node, context);
|
|
5945
|
+
const exported = !modifiers.includes("private");
|
|
5946
|
+
const scope = context.currentClass || void 0;
|
|
5947
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
5948
|
+
context.symbols.push({
|
|
5949
|
+
id: symbolId,
|
|
5950
|
+
name,
|
|
5951
|
+
kind: "method",
|
|
5952
|
+
filePath: context.filePath,
|
|
5953
|
+
startLine: node.startPosition.row + 1,
|
|
5954
|
+
endLine: node.endPosition.row + 1,
|
|
5955
|
+
exported,
|
|
5956
|
+
scope
|
|
5957
|
+
});
|
|
5958
|
+
const scopeName = scope ? `${scope}.${name}` : name;
|
|
5959
|
+
context.currentScope.push(scopeName);
|
|
5960
|
+
const body = findChildByType11(node, "compound_statement");
|
|
5961
|
+
if (body) {
|
|
5962
|
+
walkNode11(body, context);
|
|
5963
|
+
}
|
|
5964
|
+
context.currentScope.pop();
|
|
5965
|
+
}
|
|
5966
|
+
function processPropertyDeclaration3(node, context) {
|
|
5967
|
+
const varNode = findDescendantByTypes3(node, ["variable_name"]);
|
|
5968
|
+
if (!varNode) return;
|
|
5969
|
+
const name = nodeText10(varNode, context).replace(/^\$/, "");
|
|
5970
|
+
const modifiers = getModifiers2(node, context);
|
|
5971
|
+
const exported = !modifiers.includes("private");
|
|
5972
|
+
const scope = context.currentClass || void 0;
|
|
5973
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
5974
|
+
context.symbols.push({
|
|
5975
|
+
id: symbolId,
|
|
5976
|
+
name,
|
|
5977
|
+
kind: "property",
|
|
5978
|
+
filePath: context.filePath,
|
|
5979
|
+
startLine: node.startPosition.row + 1,
|
|
5980
|
+
endLine: node.endPosition.row + 1,
|
|
5981
|
+
exported,
|
|
5982
|
+
scope
|
|
5983
|
+
});
|
|
5984
|
+
}
|
|
5985
|
+
function processConstDeclaration2(node, context) {
|
|
5986
|
+
const elements = findChildrenByType5(node, "const_element");
|
|
5987
|
+
for (const elem of elements) {
|
|
5988
|
+
const nameNode = findChildByType11(elem, "name");
|
|
5989
|
+
if (!nameNode) continue;
|
|
5990
|
+
const name = nodeText10(nameNode, context);
|
|
5991
|
+
const scope = context.currentClass || void 0;
|
|
5992
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
5993
|
+
context.symbols.push({
|
|
5994
|
+
id: symbolId,
|
|
5995
|
+
name,
|
|
5996
|
+
kind: "constant",
|
|
5997
|
+
filePath: context.filePath,
|
|
5998
|
+
startLine: elem.startPosition.row + 1,
|
|
5999
|
+
endLine: elem.endPosition.row + 1,
|
|
6000
|
+
exported: true,
|
|
6001
|
+
scope
|
|
6002
|
+
});
|
|
6003
|
+
}
|
|
6004
|
+
}
|
|
6005
|
+
function processCallExpression11(node, context) {
|
|
6006
|
+
if (context.currentScope.length === 0) return;
|
|
6007
|
+
const firstChild = node.child(0);
|
|
6008
|
+
if (!firstChild) return;
|
|
6009
|
+
let calleeName = null;
|
|
6010
|
+
if (firstChild.type === "name") {
|
|
6011
|
+
calleeName = nodeText10(firstChild, context);
|
|
6012
|
+
} else if (firstChild.type === "qualified_name") {
|
|
6013
|
+
const parts = nodeText10(firstChild, context).split("\\");
|
|
6014
|
+
calleeName = parts[parts.length - 1];
|
|
6015
|
+
}
|
|
6016
|
+
if (!calleeName) return;
|
|
6017
|
+
const builtins = /* @__PURE__ */ new Set([
|
|
6018
|
+
"echo",
|
|
6019
|
+
"print",
|
|
6020
|
+
"var_dump",
|
|
6021
|
+
"print_r",
|
|
6022
|
+
"isset",
|
|
6023
|
+
"unset",
|
|
6024
|
+
"empty",
|
|
6025
|
+
"array",
|
|
6026
|
+
"list",
|
|
6027
|
+
"count",
|
|
6028
|
+
"strlen",
|
|
6029
|
+
"strpos",
|
|
6030
|
+
"substr",
|
|
6031
|
+
"explode",
|
|
6032
|
+
"implode",
|
|
6033
|
+
"array_map",
|
|
6034
|
+
"array_filter",
|
|
6035
|
+
"array_merge",
|
|
6036
|
+
"array_push",
|
|
6037
|
+
"array_pop",
|
|
6038
|
+
"array_shift",
|
|
6039
|
+
"array_unshift",
|
|
6040
|
+
"array_keys",
|
|
6041
|
+
"array_values",
|
|
6042
|
+
"in_array",
|
|
6043
|
+
"json_encode",
|
|
6044
|
+
"json_decode",
|
|
6045
|
+
"sprintf",
|
|
6046
|
+
"printf",
|
|
6047
|
+
"is_array",
|
|
6048
|
+
"is_string",
|
|
6049
|
+
"is_int",
|
|
6050
|
+
"is_null",
|
|
6051
|
+
"is_bool",
|
|
6052
|
+
"intval",
|
|
6053
|
+
"floatval",
|
|
6054
|
+
"strval",
|
|
6055
|
+
"boolval",
|
|
6056
|
+
"trim",
|
|
6057
|
+
"ltrim",
|
|
6058
|
+
"rtrim",
|
|
6059
|
+
"strtolower",
|
|
6060
|
+
"strtoupper",
|
|
6061
|
+
"str_replace",
|
|
6062
|
+
"preg_match",
|
|
6063
|
+
"preg_replace",
|
|
6064
|
+
"file_exists",
|
|
6065
|
+
"is_file",
|
|
6066
|
+
"is_dir",
|
|
6067
|
+
"dirname",
|
|
6068
|
+
"basename",
|
|
6069
|
+
"date",
|
|
6070
|
+
"time",
|
|
6071
|
+
"strtotime",
|
|
6072
|
+
"compact",
|
|
6073
|
+
"extract",
|
|
6074
|
+
"defined",
|
|
6075
|
+
"define",
|
|
6076
|
+
"class_exists",
|
|
6077
|
+
"function_exists",
|
|
6078
|
+
"throw",
|
|
6079
|
+
"die",
|
|
6080
|
+
"exit"
|
|
6081
|
+
]);
|
|
6082
|
+
if (builtins.has(calleeName)) return;
|
|
6083
|
+
const callerId = getCurrentSymbolId11(context);
|
|
6084
|
+
if (!callerId) return;
|
|
6085
|
+
const calleeId = resolveSymbol10(calleeName, context);
|
|
6086
|
+
if (calleeId) {
|
|
6087
|
+
context.edges.push({
|
|
6088
|
+
source: callerId,
|
|
6089
|
+
target: calleeId,
|
|
6090
|
+
kind: "calls",
|
|
6091
|
+
filePath: context.filePath,
|
|
6092
|
+
line: node.startPosition.row + 1
|
|
6093
|
+
});
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
function processMemberCallExpression(node, context) {
|
|
6097
|
+
}
|
|
6098
|
+
function processScopedCallExpression(node, context) {
|
|
6099
|
+
}
|
|
6100
|
+
function processIncludeRequire(node, context) {
|
|
6101
|
+
const text = nodeText10(node, context);
|
|
6102
|
+
const pathMatch = text.match(/['"]([^'"]+)['"]/);
|
|
6103
|
+
if (!pathMatch) return;
|
|
6104
|
+
const includePath = pathMatch[1];
|
|
6105
|
+
const resolvedPath = resolvePhpInclude(includePath, context.filePath, context.projectRoot);
|
|
6106
|
+
if (resolvedPath) {
|
|
6107
|
+
const sourceId = `${context.filePath}::__file__`;
|
|
6108
|
+
const targetId = `${resolvedPath}::__file__`;
|
|
6109
|
+
context.edges.push({
|
|
6110
|
+
source: sourceId,
|
|
6111
|
+
target: targetId,
|
|
6112
|
+
kind: "imports",
|
|
6113
|
+
filePath: context.filePath,
|
|
6114
|
+
line: node.startPosition.row + 1
|
|
6115
|
+
});
|
|
6116
|
+
}
|
|
6117
|
+
}
|
|
6118
|
+
function resolvePhpImport(importPath, currentFile, projectRoot) {
|
|
6119
|
+
const parts = importPath.split("\\");
|
|
6120
|
+
const filePath = parts.join("/") + ".php";
|
|
6121
|
+
const sourceRoots = [
|
|
6122
|
+
"",
|
|
6123
|
+
"src",
|
|
6124
|
+
"app",
|
|
6125
|
+
"lib",
|
|
6126
|
+
"includes",
|
|
6127
|
+
"wp-content/plugins",
|
|
6128
|
+
"wp-content/themes"
|
|
6129
|
+
];
|
|
6130
|
+
for (const root of sourceRoots) {
|
|
6131
|
+
const candidate = root ? join12(root, filePath) : filePath;
|
|
6132
|
+
const fullPath = join12(projectRoot, candidate);
|
|
6133
|
+
if (existsSync12(fullPath)) {
|
|
6134
|
+
return candidate;
|
|
6135
|
+
}
|
|
6136
|
+
const loweredParts = [...parts];
|
|
6137
|
+
loweredParts[0] = loweredParts[0].toLowerCase();
|
|
6138
|
+
const loweredFilePath = loweredParts.join("/") + ".php";
|
|
6139
|
+
const loweredCandidate = root ? join12(root, loweredFilePath) : loweredFilePath;
|
|
6140
|
+
const loweredFullPath = join12(projectRoot, loweredCandidate);
|
|
6141
|
+
if (existsSync12(loweredFullPath)) {
|
|
6142
|
+
return loweredCandidate;
|
|
6143
|
+
}
|
|
6144
|
+
}
|
|
6145
|
+
return null;
|
|
6146
|
+
}
|
|
6147
|
+
function resolvePhpInclude(includePath, currentFile, projectRoot) {
|
|
6148
|
+
const currentDir = dirname11(join12(projectRoot, currentFile));
|
|
6149
|
+
const relativePath = join12(currentDir, includePath);
|
|
6150
|
+
const relativeToRoot = relativePath.replace(projectRoot + "/", "");
|
|
6151
|
+
if (existsSync12(relativePath)) {
|
|
6152
|
+
return relativeToRoot;
|
|
6153
|
+
}
|
|
6154
|
+
const fromRoot = join12(projectRoot, includePath);
|
|
6155
|
+
if (existsSync12(fromRoot)) {
|
|
6156
|
+
return includePath;
|
|
6157
|
+
}
|
|
6158
|
+
return null;
|
|
6159
|
+
}
|
|
6160
|
+
function resolveSymbol10(name, context) {
|
|
6161
|
+
if (context.imports.has(name)) {
|
|
6162
|
+
return context.imports.get(name) || null;
|
|
6163
|
+
}
|
|
6164
|
+
const currentFileId = `${context.filePath}::${name}`;
|
|
6165
|
+
if (context.symbols.find((s) => s.id === currentFileId)) {
|
|
6166
|
+
return currentFileId;
|
|
6167
|
+
}
|
|
6168
|
+
if (context.currentClass) {
|
|
6169
|
+
const classMethodId = `${context.filePath}::${context.currentClass}.${name}`;
|
|
6170
|
+
if (context.symbols.find((s) => s.id === classMethodId)) {
|
|
6171
|
+
return classMethodId;
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
return null;
|
|
6175
|
+
}
|
|
6176
|
+
function getModifiers2(node, context) {
|
|
6177
|
+
const modifiers = [];
|
|
6178
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
6179
|
+
const child = node.child(i);
|
|
6180
|
+
if (!child) continue;
|
|
6181
|
+
const type = child.type;
|
|
6182
|
+
if (type === "visibility_modifier" || type === "static_modifier" || type === "abstract_modifier" || type === "final_modifier" || type === "readonly_modifier") {
|
|
6183
|
+
modifiers.push(nodeText10(child, context).trim());
|
|
6184
|
+
}
|
|
6185
|
+
}
|
|
6186
|
+
return modifiers;
|
|
6187
|
+
}
|
|
6188
|
+
function extractQualifiedName(node, context) {
|
|
6189
|
+
const nameNode = findDescendantByTypes3(node, ["name", "qualified_name", "namespace_name"]);
|
|
6190
|
+
if (!nameNode) return null;
|
|
6191
|
+
const text = nodeText10(nameNode, context).trim();
|
|
6192
|
+
if (!text) return null;
|
|
6193
|
+
const parts = text.split("\\");
|
|
6194
|
+
return parts[parts.length - 1];
|
|
6195
|
+
}
|
|
6196
|
+
function findChildByType11(node, type) {
|
|
6197
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
6198
|
+
const child = node.child(i);
|
|
6199
|
+
if (child && child.type === type) return child;
|
|
6200
|
+
}
|
|
6201
|
+
return null;
|
|
6202
|
+
}
|
|
6203
|
+
function findChildrenByType5(node, type) {
|
|
6204
|
+
const results = [];
|
|
6205
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
6206
|
+
const child = node.child(i);
|
|
6207
|
+
if (child && child.type === type) results.push(child);
|
|
6208
|
+
}
|
|
6209
|
+
return results;
|
|
6210
|
+
}
|
|
6211
|
+
function findDescendantByTypes3(node, types) {
|
|
6212
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
6213
|
+
const child = node.child(i);
|
|
6214
|
+
if (!child) continue;
|
|
6215
|
+
if (types.includes(child.type)) return child;
|
|
6216
|
+
const found = findDescendantByTypes3(child, types);
|
|
6217
|
+
if (found) return found;
|
|
6218
|
+
}
|
|
6219
|
+
return null;
|
|
6220
|
+
}
|
|
6221
|
+
function nodeText10(node, context) {
|
|
6222
|
+
return context.sourceCode.substring(node.startIndex, node.endIndex);
|
|
6223
|
+
}
|
|
6224
|
+
function getCurrentSymbolId11(context) {
|
|
6225
|
+
if (context.currentScope.length === 0) return null;
|
|
6226
|
+
return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
|
|
6227
|
+
}
|
|
6228
|
+
var phpParser = {
|
|
6229
|
+
name: "php",
|
|
6230
|
+
extensions: [".php"],
|
|
6231
|
+
parseFile: parsePhpFile
|
|
6232
|
+
};
|
|
6233
|
+
|
|
4930
6234
|
// src/parser/detect.ts
|
|
4931
6235
|
var parsers = [
|
|
4932
6236
|
typescriptParser,
|
|
@@ -4937,12 +6241,14 @@ var parsers = [
|
|
|
4937
6241
|
cParser,
|
|
4938
6242
|
csharpParser,
|
|
4939
6243
|
javaParser,
|
|
4940
|
-
cppParser
|
|
6244
|
+
cppParser,
|
|
6245
|
+
kotlinParser,
|
|
6246
|
+
phpParser
|
|
4941
6247
|
];
|
|
4942
6248
|
var CPP_KEYWORDS = /\b(?:class|namespace|template|public:|private:|protected:|virtual|nullptr|constexpr|auto\s+\w+\s*=|using\s+\w+\s*=|static_cast|dynamic_cast|reinterpret_cast|const_cast|noexcept|override|final|decltype|concept|requires|co_await|co_yield|co_return|std::)\b/;
|
|
4943
6249
|
function getParserForFile(filePath, content) {
|
|
4944
|
-
const ext =
|
|
4945
|
-
const fileName =
|
|
6250
|
+
const ext = extname8(filePath).toLowerCase();
|
|
6251
|
+
const fileName = basename6(filePath);
|
|
4946
6252
|
if (ext === ".h" && content) {
|
|
4947
6253
|
if (CPP_KEYWORDS.test(content)) {
|
|
4948
6254
|
return cppParser;
|
|
@@ -4960,7 +6266,7 @@ import { minimatch } from "minimatch";
|
|
|
4960
6266
|
var MAX_FILE_SIZE = 1e6;
|
|
4961
6267
|
function shouldParseFile(fullPath) {
|
|
4962
6268
|
try {
|
|
4963
|
-
const stats =
|
|
6269
|
+
const stats = statSync7(fullPath);
|
|
4964
6270
|
if (stats.size > MAX_FILE_SIZE) {
|
|
4965
6271
|
console.error(`[Parser] Skipping ${fullPath} \u2014 file too large (${(stats.size / 1024).toFixed(0)}KB)`);
|
|
4966
6272
|
return false;
|
|
@@ -4978,8 +6284,8 @@ async function parseProject(projectRoot, options) {
|
|
|
4978
6284
|
let errorFiles = 0;
|
|
4979
6285
|
for (const file of files) {
|
|
4980
6286
|
try {
|
|
4981
|
-
const fullPath =
|
|
4982
|
-
if (!
|
|
6287
|
+
const fullPath = join13(projectRoot, file);
|
|
6288
|
+
if (!resolve7(fullPath).startsWith(resolve7(projectRoot))) {
|
|
4983
6289
|
skippedFiles++;
|
|
4984
6290
|
continue;
|
|
4985
6291
|
}
|
|
@@ -5002,7 +6308,7 @@ async function parseProject(projectRoot, options) {
|
|
|
5002
6308
|
if (options?.verbose) {
|
|
5003
6309
|
console.error(`[Parser] Parsing: ${file}`);
|
|
5004
6310
|
}
|
|
5005
|
-
const sourceCode =
|
|
6311
|
+
const sourceCode = readFileSync10(fullPath, "utf-8");
|
|
5006
6312
|
const parser = getParserForFile(file, sourceCode);
|
|
5007
6313
|
if (!parser) {
|
|
5008
6314
|
console.error(`No parser found for file: ${file}`);
|
|
@@ -5031,8 +6337,8 @@ async function parseProject(projectRoot, options) {
|
|
|
5031
6337
|
}
|
|
5032
6338
|
|
|
5033
6339
|
// src/cross-language/detectors/rest-api.ts
|
|
5034
|
-
import { readFileSync as
|
|
5035
|
-
import { join as
|
|
6340
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
6341
|
+
import { join as join14, resolve as resolve8 } from "path";
|
|
5036
6342
|
function getLanguage(filePath) {
|
|
5037
6343
|
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
|
|
5038
6344
|
if (filePath.endsWith(".js") || filePath.endsWith(".jsx") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs")) return "javascript";
|
|
@@ -5040,6 +6346,8 @@ function getLanguage(filePath) {
|
|
|
5040
6346
|
if (filePath.endsWith(".go")) return "go";
|
|
5041
6347
|
if (filePath.endsWith(".cs") || filePath.endsWith(".csx")) return "csharp";
|
|
5042
6348
|
if (filePath.endsWith(".java")) return "java";
|
|
6349
|
+
if (filePath.endsWith(".kt") || filePath.endsWith(".kts")) return "kotlin";
|
|
6350
|
+
if (filePath.endsWith(".php")) return "php";
|
|
5043
6351
|
if (filePath.endsWith(".cpp") || filePath.endsWith(".cc") || filePath.endsWith(".cxx") || filePath.endsWith(".c++") || filePath.endsWith(".hpp") || filePath.endsWith(".hh") || filePath.endsWith(".hxx") || filePath.endsWith(".h++") || filePath.endsWith(".h") || filePath.endsWith(".inl") || filePath.endsWith(".ipp")) return "cpp";
|
|
5044
6352
|
return "unknown";
|
|
5045
6353
|
}
|
|
@@ -5252,43 +6560,208 @@ function extractRouteDefinitions(source, filePath) {
|
|
|
5252
6560
|
}
|
|
5253
6561
|
}
|
|
5254
6562
|
}
|
|
5255
|
-
const requestMappingMatch = line.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
|
|
5256
|
-
if (requestMappingMatch) {
|
|
5257
|
-
let path6 = requestMappingMatch[1];
|
|
6563
|
+
const requestMappingMatch = line.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
|
|
6564
|
+
if (requestMappingMatch) {
|
|
6565
|
+
let path6 = requestMappingMatch[1];
|
|
6566
|
+
if (!path6.startsWith("/")) path6 = "/" + path6;
|
|
6567
|
+
const methodMatch = line.match(/method\s*=\s*RequestMethod\.(\w+)/);
|
|
6568
|
+
const method = methodMatch ? methodMatch[1].toUpperCase() : "ANY";
|
|
6569
|
+
routes.push({
|
|
6570
|
+
method,
|
|
6571
|
+
path: path6,
|
|
6572
|
+
normalizedPath: normalizePath(path6),
|
|
6573
|
+
file: filePath,
|
|
6574
|
+
line: i + 1
|
|
6575
|
+
});
|
|
6576
|
+
}
|
|
6577
|
+
const jaxPathMatch = line.match(/@Path\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
6578
|
+
if (jaxPathMatch) {
|
|
6579
|
+
let path6 = jaxPathMatch[1];
|
|
6580
|
+
if (!path6.startsWith("/")) path6 = "/" + path6;
|
|
6581
|
+
const nextLine = i + 1 < lines.length ? lines[i + 1] : "";
|
|
6582
|
+
const prevLine = i > 0 ? lines[i - 1] : "";
|
|
6583
|
+
const jaxMethodMatch = (nextLine + prevLine).match(/@(GET|POST|PUT|DELETE|PATCH)/);
|
|
6584
|
+
const method = jaxMethodMatch ? jaxMethodMatch[1] : "ANY";
|
|
6585
|
+
routes.push({
|
|
6586
|
+
method,
|
|
6587
|
+
path: path6,
|
|
6588
|
+
normalizedPath: normalizePath(path6),
|
|
6589
|
+
file: filePath,
|
|
6590
|
+
line: i + 1
|
|
6591
|
+
});
|
|
6592
|
+
}
|
|
6593
|
+
const webFluxMatch = line.match(/(?:route|andRoute)\s*\(\s*(GET|POST|PUT|DELETE|PATCH)\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
6594
|
+
if (webFluxMatch) {
|
|
6595
|
+
const path6 = webFluxMatch[2].startsWith("/") ? webFluxMatch[2] : "/" + webFluxMatch[2];
|
|
6596
|
+
routes.push({
|
|
6597
|
+
method: webFluxMatch[1].toUpperCase(),
|
|
6598
|
+
path: path6,
|
|
6599
|
+
normalizedPath: normalizePath(path6),
|
|
6600
|
+
file: filePath,
|
|
6601
|
+
line: i + 1
|
|
6602
|
+
});
|
|
6603
|
+
}
|
|
6604
|
+
}
|
|
6605
|
+
if (lang === "kotlin") {
|
|
6606
|
+
const springMethodMatch = line.match(/@(Get|Post|Put|Delete|Patch)Mapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
|
|
6607
|
+
if (springMethodMatch) {
|
|
6608
|
+
const method = springMethodMatch[1].toUpperCase();
|
|
6609
|
+
let path6 = springMethodMatch[2];
|
|
6610
|
+
const classPrefix = findClassLevelPrefix(source);
|
|
6611
|
+
if (classPrefix) path6 = classPrefix + path6;
|
|
6612
|
+
if (!path6.startsWith("/")) path6 = "/" + path6;
|
|
6613
|
+
routes.push({
|
|
6614
|
+
method,
|
|
6615
|
+
path: path6,
|
|
6616
|
+
normalizedPath: normalizePath(path6),
|
|
6617
|
+
file: filePath,
|
|
6618
|
+
line: i + 1
|
|
6619
|
+
});
|
|
6620
|
+
}
|
|
6621
|
+
if (!springMethodMatch) {
|
|
6622
|
+
const springNoPathMatch = line.match(/@(Get|Post|Put|Delete|Patch)Mapping\s*$/);
|
|
6623
|
+
if (springNoPathMatch) {
|
|
6624
|
+
const method = springNoPathMatch[1].toUpperCase();
|
|
6625
|
+
const classPrefix = findClassLevelPrefix(source);
|
|
6626
|
+
if (classPrefix) {
|
|
6627
|
+
routes.push({
|
|
6628
|
+
method,
|
|
6629
|
+
path: classPrefix,
|
|
6630
|
+
normalizedPath: normalizePath(classPrefix),
|
|
6631
|
+
file: filePath,
|
|
6632
|
+
line: i + 1
|
|
6633
|
+
});
|
|
6634
|
+
}
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
const requestMappingMatch = line.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*(?:\[)?\s*)?["']([^"']+)["']/);
|
|
6638
|
+
if (requestMappingMatch) {
|
|
6639
|
+
let path6 = requestMappingMatch[1];
|
|
6640
|
+
if (!path6.startsWith("/")) path6 = "/" + path6;
|
|
6641
|
+
const methodMatch = line.match(/method\s*=\s*\[?\s*RequestMethod\.(\w+)/);
|
|
6642
|
+
const method = methodMatch ? methodMatch[1].toUpperCase() : "ANY";
|
|
6643
|
+
routes.push({
|
|
6644
|
+
method,
|
|
6645
|
+
path: path6,
|
|
6646
|
+
normalizedPath: normalizePath(path6),
|
|
6647
|
+
file: filePath,
|
|
6648
|
+
line: i + 1
|
|
6649
|
+
});
|
|
6650
|
+
}
|
|
6651
|
+
const ktorMatch = line.match(/\b(get|post|put|delete|patch|head|options)\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
6652
|
+
if (ktorMatch) {
|
|
6653
|
+
const path6 = ktorMatch[2];
|
|
6654
|
+
if (path6.startsWith("/")) {
|
|
6655
|
+
routes.push({
|
|
6656
|
+
method: ktorMatch[1].toUpperCase(),
|
|
6657
|
+
path: path6,
|
|
6658
|
+
normalizedPath: normalizePath(path6),
|
|
6659
|
+
file: filePath,
|
|
6660
|
+
line: i + 1
|
|
6661
|
+
});
|
|
6662
|
+
}
|
|
6663
|
+
}
|
|
6664
|
+
const ktorRouteMatch = line.match(/\broute\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
6665
|
+
if (ktorRouteMatch) {
|
|
6666
|
+
const path6 = ktorRouteMatch[1];
|
|
6667
|
+
if (path6.startsWith("/")) {
|
|
6668
|
+
routes.push({
|
|
6669
|
+
method: "ANY",
|
|
6670
|
+
path: path6,
|
|
6671
|
+
normalizedPath: normalizePath(path6),
|
|
6672
|
+
file: filePath,
|
|
6673
|
+
line: i + 1
|
|
6674
|
+
});
|
|
6675
|
+
}
|
|
6676
|
+
}
|
|
6677
|
+
const resourceMatch = line.match(/@Resource\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
6678
|
+
if (resourceMatch) {
|
|
6679
|
+
const path6 = resourceMatch[1];
|
|
6680
|
+
if (path6.startsWith("/")) {
|
|
6681
|
+
routes.push({
|
|
6682
|
+
method: "ANY",
|
|
6683
|
+
path: path6,
|
|
6684
|
+
normalizedPath: normalizePath(path6),
|
|
6685
|
+
file: filePath,
|
|
6686
|
+
line: i + 1
|
|
6687
|
+
});
|
|
6688
|
+
}
|
|
6689
|
+
}
|
|
6690
|
+
const http4kMatch = line.match(/["']([^"']+)["']\s*bind\s*(GET|POST|PUT|DELETE|PATCH)/);
|
|
6691
|
+
if (http4kMatch) {
|
|
6692
|
+
const path6 = http4kMatch[1];
|
|
6693
|
+
if (path6.startsWith("/")) {
|
|
6694
|
+
routes.push({
|
|
6695
|
+
method: http4kMatch[2].toUpperCase(),
|
|
6696
|
+
path: path6,
|
|
6697
|
+
normalizedPath: normalizePath(path6),
|
|
6698
|
+
file: filePath,
|
|
6699
|
+
line: i + 1
|
|
6700
|
+
});
|
|
6701
|
+
}
|
|
6702
|
+
}
|
|
6703
|
+
const retrofitMatch = line.match(/@(GET|POST|PUT|DELETE|PATCH|HEAD)\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
6704
|
+
if (retrofitMatch) {
|
|
6705
|
+
let path6 = retrofitMatch[2];
|
|
6706
|
+
if (!path6.startsWith("/")) path6 = "/" + path6;
|
|
6707
|
+
}
|
|
6708
|
+
}
|
|
6709
|
+
if (lang === "php") {
|
|
6710
|
+
const laravelRouteMatch = line.match(/Route\s*::\s*(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/i);
|
|
6711
|
+
if (laravelRouteMatch) {
|
|
6712
|
+
const path6 = laravelRouteMatch[2];
|
|
6713
|
+
if (path6.startsWith("/")) {
|
|
6714
|
+
routes.push({
|
|
6715
|
+
method: laravelRouteMatch[1].toUpperCase(),
|
|
6716
|
+
path: path6,
|
|
6717
|
+
normalizedPath: normalizePath(path6),
|
|
6718
|
+
file: filePath,
|
|
6719
|
+
line: i + 1
|
|
6720
|
+
});
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
const symfonyRouteMatch = line.match(/#\[Route\s*\(\s*['"]([^'"]+)['"]/);
|
|
6724
|
+
if (symfonyRouteMatch) {
|
|
6725
|
+
const path6 = symfonyRouteMatch[1];
|
|
6726
|
+
if (path6.startsWith("/")) {
|
|
6727
|
+
const methodsMatch = line.match(/methods\s*:\s*\[([^\]]+)\]/);
|
|
6728
|
+
const methods = methodsMatch ? methodsMatch[1].match(/['"](\w+)['"]/g)?.map((m) => m.replace(/['"]/g, "").toUpperCase()) || ["ANY"] : ["ANY"];
|
|
6729
|
+
for (const method of methods) {
|
|
6730
|
+
routes.push({
|
|
6731
|
+
method,
|
|
6732
|
+
path: path6,
|
|
6733
|
+
normalizedPath: normalizePath(path6),
|
|
6734
|
+
file: filePath,
|
|
6735
|
+
line: i + 1
|
|
6736
|
+
});
|
|
6737
|
+
}
|
|
6738
|
+
}
|
|
6739
|
+
}
|
|
6740
|
+
const slimMatch = line.match(/\$(?:app|group)\s*->\s*(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/i);
|
|
6741
|
+
if (slimMatch) {
|
|
6742
|
+
const path6 = slimMatch[2];
|
|
6743
|
+
if (path6.startsWith("/")) {
|
|
6744
|
+
routes.push({
|
|
6745
|
+
method: slimMatch[1].toUpperCase(),
|
|
6746
|
+
path: path6,
|
|
6747
|
+
normalizedPath: normalizePath(path6),
|
|
6748
|
+
file: filePath,
|
|
6749
|
+
line: i + 1
|
|
6750
|
+
});
|
|
6751
|
+
}
|
|
6752
|
+
}
|
|
6753
|
+
const wpRestMatch = line.match(/register_rest_route\s*\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]/);
|
|
6754
|
+
if (wpRestMatch) {
|
|
6755
|
+
const namespace = wpRestMatch[1];
|
|
6756
|
+
let path6 = wpRestMatch[2];
|
|
5258
6757
|
if (!path6.startsWith("/")) path6 = "/" + path6;
|
|
5259
|
-
const
|
|
6758
|
+
const fullPath = `/wp-json/${namespace}${path6}`;
|
|
6759
|
+
const methodMatch = line.match(/methods\s*['"=>\s]+['"](\w+)['"]/i);
|
|
5260
6760
|
const method = methodMatch ? methodMatch[1].toUpperCase() : "ANY";
|
|
5261
6761
|
routes.push({
|
|
5262
6762
|
method,
|
|
5263
|
-
path:
|
|
5264
|
-
normalizedPath: normalizePath(
|
|
5265
|
-
file: filePath,
|
|
5266
|
-
line: i + 1
|
|
5267
|
-
});
|
|
5268
|
-
}
|
|
5269
|
-
const jaxPathMatch = line.match(/@Path\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
5270
|
-
if (jaxPathMatch) {
|
|
5271
|
-
let path6 = jaxPathMatch[1];
|
|
5272
|
-
if (!path6.startsWith("/")) path6 = "/" + path6;
|
|
5273
|
-
const nextLine = i + 1 < lines.length ? lines[i + 1] : "";
|
|
5274
|
-
const prevLine = i > 0 ? lines[i - 1] : "";
|
|
5275
|
-
const jaxMethodMatch = (nextLine + prevLine).match(/@(GET|POST|PUT|DELETE|PATCH)/);
|
|
5276
|
-
const method = jaxMethodMatch ? jaxMethodMatch[1] : "ANY";
|
|
5277
|
-
routes.push({
|
|
5278
|
-
method,
|
|
5279
|
-
path: path6,
|
|
5280
|
-
normalizedPath: normalizePath(path6),
|
|
5281
|
-
file: filePath,
|
|
5282
|
-
line: i + 1
|
|
5283
|
-
});
|
|
5284
|
-
}
|
|
5285
|
-
const webFluxMatch = line.match(/(?:route|andRoute)\s*\(\s*(GET|POST|PUT|DELETE|PATCH)\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
5286
|
-
if (webFluxMatch) {
|
|
5287
|
-
const path6 = webFluxMatch[2].startsWith("/") ? webFluxMatch[2] : "/" + webFluxMatch[2];
|
|
5288
|
-
routes.push({
|
|
5289
|
-
method: webFluxMatch[1].toUpperCase(),
|
|
5290
|
-
path: path6,
|
|
5291
|
-
normalizedPath: normalizePath(path6),
|
|
6763
|
+
path: fullPath,
|
|
6764
|
+
normalizedPath: normalizePath(fullPath),
|
|
5292
6765
|
file: filePath,
|
|
5293
6766
|
line: i + 1
|
|
5294
6767
|
});
|
|
@@ -5408,11 +6881,11 @@ function detectRestApiEdges(files, projectRoot) {
|
|
|
5408
6881
|
const allCalls = [];
|
|
5409
6882
|
const allRoutes = [];
|
|
5410
6883
|
for (const file of files) {
|
|
5411
|
-
const fullPath =
|
|
5412
|
-
if (!
|
|
6884
|
+
const fullPath = join14(projectRoot, file.filePath);
|
|
6885
|
+
if (!resolve8(fullPath).startsWith(resolve8(projectRoot))) continue;
|
|
5413
6886
|
let source;
|
|
5414
6887
|
try {
|
|
5415
|
-
source =
|
|
6888
|
+
source = readFileSync11(fullPath, "utf-8");
|
|
5416
6889
|
} catch {
|
|
5417
6890
|
continue;
|
|
5418
6891
|
}
|
|
@@ -5420,6 +6893,46 @@ function detectRestApiEdges(files, projectRoot) {
|
|
|
5420
6893
|
if (lang === "typescript" || lang === "javascript") {
|
|
5421
6894
|
allCalls.push(...extractHttpCalls(source, file.filePath));
|
|
5422
6895
|
}
|
|
6896
|
+
if (lang === "kotlin") {
|
|
6897
|
+
const kotlinLines = source.split("\n");
|
|
6898
|
+
for (let i = 0; i < kotlinLines.length; i++) {
|
|
6899
|
+
const line = kotlinLines[i];
|
|
6900
|
+
const retrofitMatch = line.match(/@(GET|POST|PUT|DELETE|PATCH|HEAD)\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
6901
|
+
if (retrofitMatch) {
|
|
6902
|
+
let path6 = retrofitMatch[2];
|
|
6903
|
+
if (!path6.startsWith("/")) path6 = "/" + path6;
|
|
6904
|
+
allCalls.push({ method: retrofitMatch[1].toUpperCase(), path: path6, file: file.filePath, line: i + 1 });
|
|
6905
|
+
}
|
|
6906
|
+
}
|
|
6907
|
+
}
|
|
6908
|
+
if (lang === "php") {
|
|
6909
|
+
const phpLines = source.split("\n");
|
|
6910
|
+
for (let i = 0; i < phpLines.length; i++) {
|
|
6911
|
+
const line = phpLines[i];
|
|
6912
|
+
const guzzleMatch = line.match(/\$\w+\s*->\s*(get|post|put|delete|patch|request)\s*\(\s*['"]([^'"]+)['"]/i);
|
|
6913
|
+
if (guzzleMatch) {
|
|
6914
|
+
let method = guzzleMatch[1].toUpperCase();
|
|
6915
|
+
let path6 = guzzleMatch[2];
|
|
6916
|
+
if (method === "REQUEST") {
|
|
6917
|
+
const reqMethodMatch = line.match(/request\s*\(\s*['"](\w+)['"]\s*,\s*['"]([^'"]+)['"]/i);
|
|
6918
|
+
if (reqMethodMatch) {
|
|
6919
|
+
method = reqMethodMatch[1].toUpperCase();
|
|
6920
|
+
path6 = reqMethodMatch[2];
|
|
6921
|
+
}
|
|
6922
|
+
}
|
|
6923
|
+
if (isLocalApiPath(path6)) {
|
|
6924
|
+
allCalls.push({ method, path: cleanPath(path6), file: file.filePath, line: i + 1 });
|
|
6925
|
+
}
|
|
6926
|
+
}
|
|
6927
|
+
const fgcMatch = line.match(/file_get_contents\s*\(\s*['"]([^'"]+)['"]/);
|
|
6928
|
+
if (fgcMatch) {
|
|
6929
|
+
const path6 = fgcMatch[1];
|
|
6930
|
+
if (isLocalApiPath(path6)) {
|
|
6931
|
+
allCalls.push({ method: "GET", path: cleanPath(path6), file: file.filePath, line: i + 1 });
|
|
6932
|
+
}
|
|
6933
|
+
}
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
5423
6936
|
allRoutes.push(...extractRouteDefinitions(source, file.filePath));
|
|
5424
6937
|
}
|
|
5425
6938
|
for (const call of allCalls) {
|
|
@@ -5448,8 +6961,8 @@ function detectRestApiEdges(files, projectRoot) {
|
|
|
5448
6961
|
}
|
|
5449
6962
|
|
|
5450
6963
|
// src/cross-language/detectors/subprocess.ts
|
|
5451
|
-
import { readFileSync as
|
|
5452
|
-
import { join as
|
|
6964
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
6965
|
+
import { join as join15, resolve as resolve9, basename as basename7 } from "path";
|
|
5453
6966
|
var SCRIPT_EXTENSIONS = [".py", ".js", ".ts", ".go", ".rs"];
|
|
5454
6967
|
function getLanguage2(filePath) {
|
|
5455
6968
|
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
|
|
@@ -5548,16 +7061,16 @@ function detectSubprocessEdges(files, projectRoot) {
|
|
|
5548
7061
|
const knownFiles = new Set(files.map((f) => f.filePath));
|
|
5549
7062
|
const basenameMap = /* @__PURE__ */ new Map();
|
|
5550
7063
|
for (const f of files) {
|
|
5551
|
-
const base =
|
|
7064
|
+
const base = basename7(f.filePath);
|
|
5552
7065
|
if (!basenameMap.has(base)) basenameMap.set(base, []);
|
|
5553
7066
|
basenameMap.get(base).push(f.filePath);
|
|
5554
7067
|
}
|
|
5555
7068
|
for (const file of files) {
|
|
5556
|
-
const fullPath =
|
|
5557
|
-
if (!
|
|
7069
|
+
const fullPath = join15(projectRoot, file.filePath);
|
|
7070
|
+
if (!resolve9(fullPath).startsWith(resolve9(projectRoot))) continue;
|
|
5558
7071
|
let source;
|
|
5559
7072
|
try {
|
|
5560
|
-
source =
|
|
7073
|
+
source = readFileSync12(fullPath, "utf-8");
|
|
5561
7074
|
} catch {
|
|
5562
7075
|
continue;
|
|
5563
7076
|
}
|
|
@@ -5569,7 +7082,7 @@ function detectSubprocessEdges(files, projectRoot) {
|
|
|
5569
7082
|
targetFile = call.calledFile;
|
|
5570
7083
|
confidence = "high";
|
|
5571
7084
|
} else {
|
|
5572
|
-
const base =
|
|
7085
|
+
const base = basename7(call.calledFile);
|
|
5573
7086
|
const candidates = basenameMap.get(base);
|
|
5574
7087
|
if (candidates && candidates.length > 0) {
|
|
5575
7088
|
const exactCandidate = candidates.find((c) => c.endsWith(call.calledFile));
|
|
@@ -5948,7 +7461,7 @@ function getArchitectureSummary(graph) {
|
|
|
5948
7461
|
}
|
|
5949
7462
|
|
|
5950
7463
|
// src/health/metrics.ts
|
|
5951
|
-
import { dirname as
|
|
7464
|
+
import { dirname as dirname12 } from "path";
|
|
5952
7465
|
function scoreToGrade(score) {
|
|
5953
7466
|
if (score >= 90) return "A";
|
|
5954
7467
|
if (score >= 80) return "B";
|
|
@@ -5981,8 +7494,8 @@ function calculateCouplingScore(graph) {
|
|
|
5981
7494
|
totalEdges++;
|
|
5982
7495
|
fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
|
|
5983
7496
|
fileConnections.set(targetAttrs.filePath, (fileConnections.get(targetAttrs.filePath) || 0) + 1);
|
|
5984
|
-
const sourceDir =
|
|
5985
|
-
const targetDir =
|
|
7497
|
+
const sourceDir = dirname12(sourceAttrs.filePath).split("/")[0];
|
|
7498
|
+
const targetDir = dirname12(targetAttrs.filePath).split("/")[0];
|
|
5986
7499
|
if (sourceDir !== targetDir) {
|
|
5987
7500
|
crossDirEdges++;
|
|
5988
7501
|
}
|
|
@@ -6029,8 +7542,8 @@ function calculateCohesionScore(graph) {
|
|
|
6029
7542
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
6030
7543
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
6031
7544
|
if (sourceAttrs.filePath !== targetAttrs.filePath) {
|
|
6032
|
-
const sourceDir =
|
|
6033
|
-
const targetDir =
|
|
7545
|
+
const sourceDir = dirname12(sourceAttrs.filePath);
|
|
7546
|
+
const targetDir = dirname12(targetAttrs.filePath);
|
|
6034
7547
|
if (!dirEdges.has(sourceDir)) {
|
|
6035
7548
|
dirEdges.set(sourceDir, { internal: 0, total: 0 });
|
|
6036
7549
|
}
|
|
@@ -6312,8 +7825,8 @@ function calculateDepthScore(graph) {
|
|
|
6312
7825
|
}
|
|
6313
7826
|
|
|
6314
7827
|
// src/health/index.ts
|
|
6315
|
-
import { readFileSync as
|
|
6316
|
-
import { dirname as
|
|
7828
|
+
import { readFileSync as readFileSync13, writeFileSync, existsSync as existsSync13, mkdirSync } from "fs";
|
|
7829
|
+
import { dirname as dirname13, resolve as resolve10 } from "path";
|
|
6317
7830
|
function calculateHealthScore(graph, projectRoot) {
|
|
6318
7831
|
const coupling = calculateCouplingScore(graph);
|
|
6319
7832
|
const cohesion = calculateCohesionScore(graph);
|
|
@@ -6412,8 +7925,8 @@ function getHealthTrend(projectRoot, currentScore) {
|
|
|
6412
7925
|
}
|
|
6413
7926
|
}
|
|
6414
7927
|
function saveHealthHistory(projectRoot, report) {
|
|
6415
|
-
const resolvedRoot =
|
|
6416
|
-
const historyFile =
|
|
7928
|
+
const resolvedRoot = resolve10(projectRoot);
|
|
7929
|
+
const historyFile = resolve10(resolvedRoot, ".depwire", "health-history.json");
|
|
6417
7930
|
if (!historyFile.startsWith(resolvedRoot)) {
|
|
6418
7931
|
return;
|
|
6419
7932
|
}
|
|
@@ -6428,10 +7941,10 @@ function saveHealthHistory(projectRoot, report) {
|
|
|
6428
7941
|
}))
|
|
6429
7942
|
};
|
|
6430
7943
|
let history = [];
|
|
6431
|
-
if (
|
|
7944
|
+
if (existsSync13(historyFile)) {
|
|
6432
7945
|
try {
|
|
6433
7946
|
if (!historyFile.startsWith(resolvedRoot)) return;
|
|
6434
|
-
const content =
|
|
7947
|
+
const content = readFileSync13(historyFile, "utf-8");
|
|
6435
7948
|
history = JSON.parse(content);
|
|
6436
7949
|
} catch {
|
|
6437
7950
|
}
|
|
@@ -6440,19 +7953,19 @@ function saveHealthHistory(projectRoot, report) {
|
|
|
6440
7953
|
if (history.length > 50) {
|
|
6441
7954
|
history = history.slice(-50);
|
|
6442
7955
|
}
|
|
6443
|
-
mkdirSync(
|
|
7956
|
+
mkdirSync(dirname13(historyFile), { recursive: true });
|
|
6444
7957
|
if (!historyFile.startsWith(resolvedRoot)) return;
|
|
6445
7958
|
writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
|
|
6446
7959
|
}
|
|
6447
7960
|
function loadHealthHistory(projectRoot) {
|
|
6448
|
-
const resolvedRoot =
|
|
6449
|
-
const historyFile =
|
|
6450
|
-
if (!historyFile.startsWith(resolvedRoot) || !
|
|
7961
|
+
const resolvedRoot = resolve10(projectRoot);
|
|
7962
|
+
const historyFile = resolve10(resolvedRoot, ".depwire", "health-history.json");
|
|
7963
|
+
if (!historyFile.startsWith(resolvedRoot) || !existsSync13(historyFile)) {
|
|
6451
7964
|
return [];
|
|
6452
7965
|
}
|
|
6453
7966
|
try {
|
|
6454
7967
|
if (!historyFile.startsWith(resolvedRoot)) return [];
|
|
6455
|
-
const content =
|
|
7968
|
+
const content = readFileSync13(historyFile, "utf-8");
|
|
6456
7969
|
return JSON.parse(content);
|
|
6457
7970
|
} catch {
|
|
6458
7971
|
return [];
|
|
@@ -6461,7 +7974,7 @@ function loadHealthHistory(projectRoot) {
|
|
|
6461
7974
|
|
|
6462
7975
|
// src/dead-code/detector.ts
|
|
6463
7976
|
import path2 from "path";
|
|
6464
|
-
import { readFileSync as
|
|
7977
|
+
import { readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
|
|
6465
7978
|
function findDeadSymbols(graph, projectRoot, includeTests = false, debug = false) {
|
|
6466
7979
|
const deadSymbols = [];
|
|
6467
7980
|
const context = { graph, projectRoot };
|
|
@@ -6594,11 +8107,11 @@ function getPackageEntryPoints(projectRoot) {
|
|
|
6594
8107
|
const entryPoints = /* @__PURE__ */ new Set();
|
|
6595
8108
|
const resolvedRoot = path2.resolve(projectRoot);
|
|
6596
8109
|
const packageJsonPath = path2.resolve(resolvedRoot, "package.json");
|
|
6597
|
-
if (!packageJsonPath.startsWith(resolvedRoot) || !
|
|
8110
|
+
if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync14(packageJsonPath)) {
|
|
6598
8111
|
return entryPoints;
|
|
6599
8112
|
}
|
|
6600
8113
|
try {
|
|
6601
|
-
const packageJson = JSON.parse(
|
|
8114
|
+
const packageJson = JSON.parse(readFileSync14(packageJsonPath, "utf-8"));
|
|
6602
8115
|
if (packageJson.main) {
|
|
6603
8116
|
entryPoints.add(path2.resolve(projectRoot, packageJson.main));
|
|
6604
8117
|
}
|
|
@@ -6652,6 +8165,12 @@ function shouldExclude(attrs, context, includeTests, packageEntryPoints) {
|
|
|
6652
8165
|
if (isCppExcluded(attrs)) {
|
|
6653
8166
|
return "framework";
|
|
6654
8167
|
}
|
|
8168
|
+
if (isKotlinExcluded(attrs)) {
|
|
8169
|
+
return "framework";
|
|
8170
|
+
}
|
|
8171
|
+
if (isPhpExcluded(attrs)) {
|
|
8172
|
+
return "framework";
|
|
8173
|
+
}
|
|
6655
8174
|
return null;
|
|
6656
8175
|
}
|
|
6657
8176
|
function isRealPackageEntryPoint(filePath, packageEntryPoints) {
|
|
@@ -6690,6 +8209,98 @@ function isCppExcluded(attrs) {
|
|
|
6690
8209
|
if (kind === "constant" && /\.(?:h|hpp)$/.test(filePath)) return true;
|
|
6691
8210
|
return false;
|
|
6692
8211
|
}
|
|
8212
|
+
function isKotlinExcluded(attrs) {
|
|
8213
|
+
const filePath = attrs.file || attrs.filePath || "";
|
|
8214
|
+
const name = attrs.name || "";
|
|
8215
|
+
if (!filePath.endsWith(".kt") && !filePath.endsWith(".kts")) return false;
|
|
8216
|
+
if (name === "main") return true;
|
|
8217
|
+
const androidLifecycle = [
|
|
8218
|
+
"onCreate",
|
|
8219
|
+
"onStart",
|
|
8220
|
+
"onResume",
|
|
8221
|
+
"onPause",
|
|
8222
|
+
"onStop",
|
|
8223
|
+
"onDestroy",
|
|
8224
|
+
"onCreateView",
|
|
8225
|
+
"onViewCreated",
|
|
8226
|
+
"onDestroyView",
|
|
8227
|
+
"onSaveInstanceState",
|
|
8228
|
+
"onRestoreInstanceState",
|
|
8229
|
+
"onActivityResult",
|
|
8230
|
+
"onRequestPermissionsResult",
|
|
8231
|
+
"onConfigurationChanged",
|
|
8232
|
+
"onNewIntent"
|
|
8233
|
+
];
|
|
8234
|
+
if (androidLifecycle.includes(name)) return true;
|
|
8235
|
+
if (["readObject", "writeObject", "readResolve", "writeReplace"].includes(name)) return true;
|
|
8236
|
+
if (name.startsWith("operator")) return true;
|
|
8237
|
+
return false;
|
|
8238
|
+
}
|
|
8239
|
+
function isPhpExcluded(attrs) {
|
|
8240
|
+
const filePath = attrs.file || attrs.filePath || "";
|
|
8241
|
+
const name = attrs.name || "";
|
|
8242
|
+
if (!filePath.endsWith(".php")) return false;
|
|
8243
|
+
const magicMethods = [
|
|
8244
|
+
"__construct",
|
|
8245
|
+
"__destruct",
|
|
8246
|
+
"__call",
|
|
8247
|
+
"__callStatic",
|
|
8248
|
+
"__get",
|
|
8249
|
+
"__set",
|
|
8250
|
+
"__isset",
|
|
8251
|
+
"__unset",
|
|
8252
|
+
"__sleep",
|
|
8253
|
+
"__wakeup",
|
|
8254
|
+
"__serialize",
|
|
8255
|
+
"__unserialize",
|
|
8256
|
+
"__toString",
|
|
8257
|
+
"__invoke",
|
|
8258
|
+
"__set_state",
|
|
8259
|
+
"__clone",
|
|
8260
|
+
"__debugInfo"
|
|
8261
|
+
];
|
|
8262
|
+
if (magicMethods.includes(name)) return true;
|
|
8263
|
+
const wpHooks = [
|
|
8264
|
+
"init",
|
|
8265
|
+
"admin_init",
|
|
8266
|
+
"wp_enqueue_scripts",
|
|
8267
|
+
"admin_enqueue_scripts",
|
|
8268
|
+
"widgets_init",
|
|
8269
|
+
"register_activation_hook",
|
|
8270
|
+
"register_deactivation_hook",
|
|
8271
|
+
"add_action",
|
|
8272
|
+
"add_filter",
|
|
8273
|
+
"activate",
|
|
8274
|
+
"deactivate"
|
|
8275
|
+
];
|
|
8276
|
+
if (wpHooks.includes(name)) return true;
|
|
8277
|
+
const laravelMethods = [
|
|
8278
|
+
"register",
|
|
8279
|
+
"boot",
|
|
8280
|
+
"handle",
|
|
8281
|
+
"authorize",
|
|
8282
|
+
"rules",
|
|
8283
|
+
"messages",
|
|
8284
|
+
"prepareForValidation",
|
|
8285
|
+
"failed",
|
|
8286
|
+
"broadcastOn",
|
|
8287
|
+
"broadcastAs",
|
|
8288
|
+
"broadcastWith"
|
|
8289
|
+
];
|
|
8290
|
+
if (laravelMethods.includes(name)) return true;
|
|
8291
|
+
const symfonyMethods = [
|
|
8292
|
+
"__invoke",
|
|
8293
|
+
"getSubscribedEvents",
|
|
8294
|
+
"getSubscribedServices",
|
|
8295
|
+
"configureOptions",
|
|
8296
|
+
"buildForm",
|
|
8297
|
+
"load",
|
|
8298
|
+
"getConfigTreeBuilder"
|
|
8299
|
+
];
|
|
8300
|
+
if (symfonyMethods.includes(name)) return true;
|
|
8301
|
+
if (name.startsWith("test") || name === "setUp" || name === "tearDown" || name === "setUpBeforeClass" || name === "tearDownAfterClass") return true;
|
|
8302
|
+
return false;
|
|
8303
|
+
}
|
|
6693
8304
|
|
|
6694
8305
|
// src/dead-code/classifier.ts
|
|
6695
8306
|
import path3 from "path";
|
|
@@ -6756,8 +8367,8 @@ function generateReason(symbol, confidence) {
|
|
|
6756
8367
|
return "Potentially unused";
|
|
6757
8368
|
}
|
|
6758
8369
|
function isBarrelFile(filePath) {
|
|
6759
|
-
const
|
|
6760
|
-
return
|
|
8370
|
+
const basename11 = path3.basename(filePath);
|
|
8371
|
+
return basename11 === "index.ts" || basename11 === "index.js";
|
|
6761
8372
|
}
|
|
6762
8373
|
function isTestFile2(filePath) {
|
|
6763
8374
|
return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
|
|
@@ -6936,11 +8547,11 @@ function filterByConfidence(symbols, minConfidence) {
|
|
|
6936
8547
|
}
|
|
6937
8548
|
|
|
6938
8549
|
// src/docs/generator.ts
|
|
6939
|
-
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as
|
|
6940
|
-
import { join as
|
|
8550
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync17 } from "fs";
|
|
8551
|
+
import { join as join19 } from "path";
|
|
6941
8552
|
|
|
6942
8553
|
// src/docs/architecture.ts
|
|
6943
|
-
import { dirname as
|
|
8554
|
+
import { dirname as dirname14 } from "path";
|
|
6944
8555
|
|
|
6945
8556
|
// src/docs/templates.ts
|
|
6946
8557
|
function header(text, level = 1) {
|
|
@@ -7091,7 +8702,7 @@ function generateModuleStructure(graph) {
|
|
|
7091
8702
|
function getDirectoryStats(graph) {
|
|
7092
8703
|
const dirMap = /* @__PURE__ */ new Map();
|
|
7093
8704
|
graph.forEachNode((node, attrs) => {
|
|
7094
|
-
const dir =
|
|
8705
|
+
const dir = dirname14(attrs.filePath);
|
|
7095
8706
|
if (dir === ".") return;
|
|
7096
8707
|
if (!dirMap.has(dir)) {
|
|
7097
8708
|
dirMap.set(dir, {
|
|
@@ -7116,7 +8727,7 @@ function getDirectoryStats(graph) {
|
|
|
7116
8727
|
});
|
|
7117
8728
|
const filesPerDir = /* @__PURE__ */ new Map();
|
|
7118
8729
|
graph.forEachNode((node, attrs) => {
|
|
7119
|
-
const dir =
|
|
8730
|
+
const dir = dirname14(attrs.filePath);
|
|
7120
8731
|
if (!filesPerDir.has(dir)) {
|
|
7121
8732
|
filesPerDir.set(dir, /* @__PURE__ */ new Set());
|
|
7122
8733
|
}
|
|
@@ -7131,8 +8742,8 @@ function getDirectoryStats(graph) {
|
|
|
7131
8742
|
graph.forEachEdge((edge, attrs, source, target) => {
|
|
7132
8743
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
7133
8744
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
7134
|
-
const sourceDir =
|
|
7135
|
-
const targetDir =
|
|
8745
|
+
const sourceDir = dirname14(sourceAttrs.filePath);
|
|
8746
|
+
const targetDir = dirname14(targetAttrs.filePath);
|
|
7136
8747
|
if (sourceDir !== targetDir) {
|
|
7137
8748
|
if (!dirEdges.has(sourceDir)) {
|
|
7138
8749
|
dirEdges.set(sourceDir, { in: 0, out: 0 });
|
|
@@ -7339,7 +8950,7 @@ function detectCycles(graph) {
|
|
|
7339
8950
|
}
|
|
7340
8951
|
|
|
7341
8952
|
// src/docs/conventions.ts
|
|
7342
|
-
import { basename as
|
|
8953
|
+
import { basename as basename8, extname as extname9 } from "path";
|
|
7343
8954
|
function generateConventions(graph, projectRoot, version) {
|
|
7344
8955
|
let output = "";
|
|
7345
8956
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -7377,7 +8988,7 @@ function generateFileOrganization(graph) {
|
|
|
7377
8988
|
graph.forEachNode((node, attrs) => {
|
|
7378
8989
|
if (!files.has(attrs.filePath)) {
|
|
7379
8990
|
files.add(attrs.filePath);
|
|
7380
|
-
const fileName =
|
|
8991
|
+
const fileName = basename8(attrs.filePath);
|
|
7381
8992
|
if (fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx") {
|
|
7382
8993
|
barrelFileCount++;
|
|
7383
8994
|
}
|
|
@@ -7436,7 +9047,7 @@ function generateNamingPatterns(graph) {
|
|
|
7436
9047
|
graph.forEachNode((node, attrs) => {
|
|
7437
9048
|
if (!files.has(attrs.filePath)) {
|
|
7438
9049
|
files.add(attrs.filePath);
|
|
7439
|
-
const fileName =
|
|
9050
|
+
const fileName = basename8(attrs.filePath, extname9(attrs.filePath));
|
|
7440
9051
|
if (isCamelCase(fileName)) patterns.files.camelCase++;
|
|
7441
9052
|
else if (isPascalCase(fileName)) patterns.files.PascalCase++;
|
|
7442
9053
|
else if (isKebabCase(fileName)) patterns.files.kebabCase++;
|
|
@@ -8113,7 +9724,7 @@ function detectCyclesDetailed(graph) {
|
|
|
8113
9724
|
}
|
|
8114
9725
|
|
|
8115
9726
|
// src/docs/onboarding.ts
|
|
8116
|
-
import { dirname as
|
|
9727
|
+
import { dirname as dirname15 } from "path";
|
|
8117
9728
|
function generateOnboarding(graph, projectRoot, version) {
|
|
8118
9729
|
let output = "";
|
|
8119
9730
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -8172,7 +9783,7 @@ function generateQuickOrientation(graph) {
|
|
|
8172
9783
|
const primaryLang = Object.entries(languages2).sort((a, b) => b[1] - a[1])[0];
|
|
8173
9784
|
const dirs = /* @__PURE__ */ new Set();
|
|
8174
9785
|
graph.forEachNode((node, attrs) => {
|
|
8175
|
-
const dir =
|
|
9786
|
+
const dir = dirname15(attrs.filePath);
|
|
8176
9787
|
if (dir !== ".") {
|
|
8177
9788
|
const topLevel = dir.split("/")[0];
|
|
8178
9789
|
dirs.add(topLevel);
|
|
@@ -8276,7 +9887,7 @@ function generateModuleMap(graph) {
|
|
|
8276
9887
|
function getDirectoryStats2(graph) {
|
|
8277
9888
|
const dirMap = /* @__PURE__ */ new Map();
|
|
8278
9889
|
graph.forEachNode((node, attrs) => {
|
|
8279
|
-
const dir =
|
|
9890
|
+
const dir = dirname15(attrs.filePath);
|
|
8280
9891
|
if (dir === ".") return;
|
|
8281
9892
|
if (!dirMap.has(dir)) {
|
|
8282
9893
|
dirMap.set(dir, {
|
|
@@ -8291,7 +9902,7 @@ function getDirectoryStats2(graph) {
|
|
|
8291
9902
|
});
|
|
8292
9903
|
const filesPerDir = /* @__PURE__ */ new Map();
|
|
8293
9904
|
graph.forEachNode((node, attrs) => {
|
|
8294
|
-
const dir =
|
|
9905
|
+
const dir = dirname15(attrs.filePath);
|
|
8295
9906
|
if (!filesPerDir.has(dir)) {
|
|
8296
9907
|
filesPerDir.set(dir, /* @__PURE__ */ new Set());
|
|
8297
9908
|
}
|
|
@@ -8306,8 +9917,8 @@ function getDirectoryStats2(graph) {
|
|
|
8306
9917
|
graph.forEachEdge((edge, attrs, source, target) => {
|
|
8307
9918
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
8308
9919
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
8309
|
-
const sourceDir =
|
|
8310
|
-
const targetDir =
|
|
9920
|
+
const sourceDir = dirname15(sourceAttrs.filePath);
|
|
9921
|
+
const targetDir = dirname15(targetAttrs.filePath);
|
|
8311
9922
|
if (sourceDir !== targetDir) {
|
|
8312
9923
|
if (!dirEdges.has(sourceDir)) {
|
|
8313
9924
|
dirEdges.set(sourceDir, { in: 0, out: 0 });
|
|
@@ -8388,7 +9999,7 @@ function detectClusters(graph) {
|
|
|
8388
9999
|
const dirFiles = /* @__PURE__ */ new Map();
|
|
8389
10000
|
const fileEdges = /* @__PURE__ */ new Map();
|
|
8390
10001
|
graph.forEachNode((node, attrs) => {
|
|
8391
|
-
const dir =
|
|
10002
|
+
const dir = dirname15(attrs.filePath);
|
|
8392
10003
|
if (!dirFiles.has(dir)) {
|
|
8393
10004
|
dirFiles.set(dir, /* @__PURE__ */ new Set());
|
|
8394
10005
|
}
|
|
@@ -8442,8 +10053,8 @@ function inferClusterName(files) {
|
|
|
8442
10053
|
if (sortedWords.length > 0 && sortedWords[0][1] > 1) {
|
|
8443
10054
|
return capitalizeFirst2(sortedWords[0][0]);
|
|
8444
10055
|
}
|
|
8445
|
-
const commonDir =
|
|
8446
|
-
if (files.every((f) =>
|
|
10056
|
+
const commonDir = dirname15(files[0]);
|
|
10057
|
+
if (files.every((f) => dirname15(f) === commonDir)) {
|
|
8447
10058
|
return capitalizeFirst2(commonDir.split("/").pop() || "Core");
|
|
8448
10059
|
}
|
|
8449
10060
|
return "Core";
|
|
@@ -8501,7 +10112,7 @@ function generateDepwireUsage(projectRoot) {
|
|
|
8501
10112
|
}
|
|
8502
10113
|
|
|
8503
10114
|
// src/docs/files.ts
|
|
8504
|
-
import { dirname as
|
|
10115
|
+
import { dirname as dirname16, basename as basename9 } from "path";
|
|
8505
10116
|
function generateFiles(graph, projectRoot, version) {
|
|
8506
10117
|
let output = "";
|
|
8507
10118
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -8605,7 +10216,7 @@ function generateDirectoryBreakdown(graph) {
|
|
|
8605
10216
|
const fileStats = getFileStats2(graph);
|
|
8606
10217
|
const dirMap = /* @__PURE__ */ new Map();
|
|
8607
10218
|
for (const file of fileStats) {
|
|
8608
|
-
const dir =
|
|
10219
|
+
const dir = dirname16(file.filePath);
|
|
8609
10220
|
const topDir = dir === "." ? "." : dir.split("/")[0];
|
|
8610
10221
|
if (!dirMap.has(topDir)) {
|
|
8611
10222
|
dirMap.set(topDir, {
|
|
@@ -8620,7 +10231,7 @@ function generateDirectoryBreakdown(graph) {
|
|
|
8620
10231
|
dirStats.symbolCount += file.symbolCount;
|
|
8621
10232
|
if (file.totalConnections > dirStats.maxConnections) {
|
|
8622
10233
|
dirStats.maxConnections = file.totalConnections;
|
|
8623
|
-
dirStats.mostConnectedFile =
|
|
10234
|
+
dirStats.mostConnectedFile = basename9(file.filePath);
|
|
8624
10235
|
}
|
|
8625
10236
|
}
|
|
8626
10237
|
if (dirMap.size === 0) {
|
|
@@ -9248,7 +10859,7 @@ function generateRecommendations(graph) {
|
|
|
9248
10859
|
}
|
|
9249
10860
|
|
|
9250
10861
|
// src/docs/tests.ts
|
|
9251
|
-
import { basename as
|
|
10862
|
+
import { basename as basename10, dirname as dirname17 } from "path";
|
|
9252
10863
|
function generateTests(graph, projectRoot, version) {
|
|
9253
10864
|
let output = "";
|
|
9254
10865
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -9276,8 +10887,8 @@ function getFileCount8(graph) {
|
|
|
9276
10887
|
return files.size;
|
|
9277
10888
|
}
|
|
9278
10889
|
function isTestFile3(filePath) {
|
|
9279
|
-
const fileName =
|
|
9280
|
-
const dirPath =
|
|
10890
|
+
const fileName = basename10(filePath).toLowerCase();
|
|
10891
|
+
const dirPath = dirname17(filePath).toLowerCase();
|
|
9281
10892
|
if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
|
|
9282
10893
|
return true;
|
|
9283
10894
|
}
|
|
@@ -9335,13 +10946,13 @@ function generateTestFileInventory(graph) {
|
|
|
9335
10946
|
return output;
|
|
9336
10947
|
}
|
|
9337
10948
|
function matchTestToSource(testFile) {
|
|
9338
|
-
const testFileName =
|
|
9339
|
-
const testDir =
|
|
10949
|
+
const testFileName = basename10(testFile);
|
|
10950
|
+
const testDir = dirname17(testFile);
|
|
9340
10951
|
let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
|
|
9341
10952
|
const possiblePaths = [];
|
|
9342
10953
|
possiblePaths.push(testDir + "/" + sourceFileName);
|
|
9343
10954
|
if (testDir.endsWith("/test") || testDir.endsWith("/tests") || testDir.endsWith("/__tests__")) {
|
|
9344
|
-
const parentDir =
|
|
10955
|
+
const parentDir = dirname17(testDir);
|
|
9345
10956
|
possiblePaths.push(parentDir + "/" + sourceFileName);
|
|
9346
10957
|
}
|
|
9347
10958
|
if (testDir.includes("test")) {
|
|
@@ -9497,7 +11108,7 @@ function generateTestCoverageMap(graph) {
|
|
|
9497
11108
|
const rows = mappings.slice(0, 30).map((m) => [
|
|
9498
11109
|
`\`${m.sourceFile}\``,
|
|
9499
11110
|
m.hasTest ? "\u2705" : "\u274C",
|
|
9500
|
-
m.testFile ? `\`${
|
|
11111
|
+
m.testFile ? `\`${basename10(m.testFile)}\`` : "-",
|
|
9501
11112
|
formatNumber(m.symbolCount)
|
|
9502
11113
|
]);
|
|
9503
11114
|
let output = table(headers, rows);
|
|
@@ -9538,7 +11149,7 @@ function generateTestStatistics(graph) {
|
|
|
9538
11149
|
`;
|
|
9539
11150
|
const dirTestCoverage = /* @__PURE__ */ new Map();
|
|
9540
11151
|
for (const sourceFile of sourceFiles) {
|
|
9541
|
-
const dir =
|
|
11152
|
+
const dir = dirname17(sourceFile).split("/")[0];
|
|
9542
11153
|
if (!dirTestCoverage.has(dir)) {
|
|
9543
11154
|
dirTestCoverage.set(dir, { total: 0, tested: 0 });
|
|
9544
11155
|
}
|
|
@@ -9561,7 +11172,7 @@ function generateTestStatistics(graph) {
|
|
|
9561
11172
|
}
|
|
9562
11173
|
|
|
9563
11174
|
// src/docs/history.ts
|
|
9564
|
-
import { dirname as
|
|
11175
|
+
import { dirname as dirname18 } from "path";
|
|
9565
11176
|
import { execSync } from "child_process";
|
|
9566
11177
|
function generateHistory(graph, projectRoot, version) {
|
|
9567
11178
|
let output = "";
|
|
@@ -9842,7 +11453,7 @@ function generateFeatureClusters(graph) {
|
|
|
9842
11453
|
const dirFiles = /* @__PURE__ */ new Map();
|
|
9843
11454
|
const fileEdges = /* @__PURE__ */ new Map();
|
|
9844
11455
|
graph.forEachNode((node, attrs) => {
|
|
9845
|
-
const dir =
|
|
11456
|
+
const dir = dirname18(attrs.filePath);
|
|
9846
11457
|
if (!dirFiles.has(dir)) {
|
|
9847
11458
|
dirFiles.set(dir, /* @__PURE__ */ new Set());
|
|
9848
11459
|
}
|
|
@@ -9924,7 +11535,7 @@ function capitalizeFirst3(str) {
|
|
|
9924
11535
|
}
|
|
9925
11536
|
|
|
9926
11537
|
// src/docs/current.ts
|
|
9927
|
-
import { dirname as
|
|
11538
|
+
import { dirname as dirname19 } from "path";
|
|
9928
11539
|
function generateCurrent(graph, projectRoot, version) {
|
|
9929
11540
|
let output = "";
|
|
9930
11541
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -10062,7 +11673,7 @@ function generateCompleteFileIndex(graph) {
|
|
|
10062
11673
|
fileInfos.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
10063
11674
|
const dirGroups = /* @__PURE__ */ new Map();
|
|
10064
11675
|
for (const info of fileInfos) {
|
|
10065
|
-
const dir =
|
|
11676
|
+
const dir = dirname19(info.filePath);
|
|
10066
11677
|
const topDir = dir === "." ? "root" : dir.split("/")[0];
|
|
10067
11678
|
if (!dirGroups.has(topDir)) {
|
|
10068
11679
|
dirGroups.set(topDir, []);
|
|
@@ -10273,8 +11884,8 @@ function getTopLevelDir2(filePath) {
|
|
|
10273
11884
|
}
|
|
10274
11885
|
|
|
10275
11886
|
// src/docs/status.ts
|
|
10276
|
-
import { readFileSync as
|
|
10277
|
-
import { resolve as
|
|
11887
|
+
import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
|
|
11888
|
+
import { resolve as resolve11 } from "path";
|
|
10278
11889
|
function generateStatus(graph, projectRoot, version) {
|
|
10279
11890
|
let output = "";
|
|
10280
11891
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -10307,16 +11918,16 @@ function getFileCount11(graph) {
|
|
|
10307
11918
|
}
|
|
10308
11919
|
function extractComments(projectRoot, filePath) {
|
|
10309
11920
|
const comments = [];
|
|
10310
|
-
const resolvedRoot =
|
|
10311
|
-
const fullPath =
|
|
11921
|
+
const resolvedRoot = resolve11(projectRoot);
|
|
11922
|
+
const fullPath = resolve11(resolvedRoot, filePath);
|
|
10312
11923
|
if (!fullPath.startsWith(resolvedRoot)) {
|
|
10313
11924
|
return comments;
|
|
10314
11925
|
}
|
|
10315
|
-
if (!
|
|
11926
|
+
if (!existsSync15(fullPath)) {
|
|
10316
11927
|
return comments;
|
|
10317
11928
|
}
|
|
10318
11929
|
try {
|
|
10319
|
-
const content =
|
|
11930
|
+
const content = readFileSync15(fullPath, "utf-8");
|
|
10320
11931
|
const lines = content.split("\n");
|
|
10321
11932
|
const patterns = [
|
|
10322
11933
|
{ type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
|
|
@@ -10895,16 +12506,16 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
|
|
|
10895
12506
|
}
|
|
10896
12507
|
|
|
10897
12508
|
// src/docs/metadata.ts
|
|
10898
|
-
import { existsSync as
|
|
10899
|
-
import { resolve as
|
|
12509
|
+
import { existsSync as existsSync16, readFileSync as readFileSync16, writeFileSync as writeFileSync2 } from "fs";
|
|
12510
|
+
import { resolve as resolve12 } from "path";
|
|
10900
12511
|
function loadMetadata(outputDir) {
|
|
10901
|
-
const resolvedDir =
|
|
10902
|
-
const metadataPath =
|
|
10903
|
-
if (!metadataPath.startsWith(resolvedDir) || !
|
|
12512
|
+
const resolvedDir = resolve12(outputDir);
|
|
12513
|
+
const metadataPath = resolve12(resolvedDir, "metadata.json");
|
|
12514
|
+
if (!metadataPath.startsWith(resolvedDir) || !existsSync16(metadataPath)) {
|
|
10904
12515
|
return null;
|
|
10905
12516
|
}
|
|
10906
12517
|
try {
|
|
10907
|
-
const content =
|
|
12518
|
+
const content = readFileSync16(metadataPath, "utf-8");
|
|
10908
12519
|
return JSON.parse(content);
|
|
10909
12520
|
} catch (err) {
|
|
10910
12521
|
console.error("Failed to load metadata:", err);
|
|
@@ -10912,8 +12523,8 @@ function loadMetadata(outputDir) {
|
|
|
10912
12523
|
}
|
|
10913
12524
|
}
|
|
10914
12525
|
function saveMetadata(outputDir, metadata) {
|
|
10915
|
-
const resolvedDir =
|
|
10916
|
-
const metadataPath =
|
|
12526
|
+
const resolvedDir = resolve12(outputDir);
|
|
12527
|
+
const metadataPath = resolve12(resolvedDir, "metadata.json");
|
|
10917
12528
|
if (!metadataPath.startsWith(resolvedDir)) {
|
|
10918
12529
|
throw new Error(`Path traversal attempt blocked: ${metadataPath}`);
|
|
10919
12530
|
}
|
|
@@ -10959,7 +12570,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
10959
12570
|
const generated = [];
|
|
10960
12571
|
const errors = [];
|
|
10961
12572
|
try {
|
|
10962
|
-
if (!
|
|
12573
|
+
if (!existsSync17(options.outputDir)) {
|
|
10963
12574
|
mkdirSync2(options.outputDir, { recursive: true });
|
|
10964
12575
|
if (options.verbose) {
|
|
10965
12576
|
console.log(`Created output directory: ${options.outputDir}`);
|
|
@@ -10998,7 +12609,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
10998
12609
|
try {
|
|
10999
12610
|
if (options.verbose) console.log("Generating ARCHITECTURE.md...");
|
|
11000
12611
|
const content = generateArchitecture(graph, projectRoot, version, parseTime);
|
|
11001
|
-
const filePath =
|
|
12612
|
+
const filePath = join19(options.outputDir, "ARCHITECTURE.md");
|
|
11002
12613
|
writeFileSync3(filePath, content, "utf-8");
|
|
11003
12614
|
generated.push("ARCHITECTURE.md");
|
|
11004
12615
|
} catch (err) {
|
|
@@ -11009,7 +12620,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11009
12620
|
try {
|
|
11010
12621
|
if (options.verbose) console.log("Generating CONVENTIONS.md...");
|
|
11011
12622
|
const content = generateConventions(graph, projectRoot, version);
|
|
11012
|
-
const filePath =
|
|
12623
|
+
const filePath = join19(options.outputDir, "CONVENTIONS.md");
|
|
11013
12624
|
writeFileSync3(filePath, content, "utf-8");
|
|
11014
12625
|
generated.push("CONVENTIONS.md");
|
|
11015
12626
|
} catch (err) {
|
|
@@ -11020,7 +12631,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11020
12631
|
try {
|
|
11021
12632
|
if (options.verbose) console.log("Generating DEPENDENCIES.md...");
|
|
11022
12633
|
const content = generateDependencies(graph, projectRoot, version);
|
|
11023
|
-
const filePath =
|
|
12634
|
+
const filePath = join19(options.outputDir, "DEPENDENCIES.md");
|
|
11024
12635
|
writeFileSync3(filePath, content, "utf-8");
|
|
11025
12636
|
generated.push("DEPENDENCIES.md");
|
|
11026
12637
|
} catch (err) {
|
|
@@ -11031,7 +12642,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11031
12642
|
try {
|
|
11032
12643
|
if (options.verbose) console.log("Generating ONBOARDING.md...");
|
|
11033
12644
|
const content = generateOnboarding(graph, projectRoot, version);
|
|
11034
|
-
const filePath =
|
|
12645
|
+
const filePath = join19(options.outputDir, "ONBOARDING.md");
|
|
11035
12646
|
writeFileSync3(filePath, content, "utf-8");
|
|
11036
12647
|
generated.push("ONBOARDING.md");
|
|
11037
12648
|
} catch (err) {
|
|
@@ -11042,7 +12653,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11042
12653
|
try {
|
|
11043
12654
|
if (options.verbose) console.log("Generating FILES.md...");
|
|
11044
12655
|
const content = generateFiles(graph, projectRoot, version);
|
|
11045
|
-
const filePath =
|
|
12656
|
+
const filePath = join19(options.outputDir, "FILES.md");
|
|
11046
12657
|
writeFileSync3(filePath, content, "utf-8");
|
|
11047
12658
|
generated.push("FILES.md");
|
|
11048
12659
|
} catch (err) {
|
|
@@ -11053,7 +12664,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11053
12664
|
try {
|
|
11054
12665
|
if (options.verbose) console.log("Generating API_SURFACE.md...");
|
|
11055
12666
|
const content = generateApiSurface(graph, projectRoot, version);
|
|
11056
|
-
const filePath =
|
|
12667
|
+
const filePath = join19(options.outputDir, "API_SURFACE.md");
|
|
11057
12668
|
writeFileSync3(filePath, content, "utf-8");
|
|
11058
12669
|
generated.push("API_SURFACE.md");
|
|
11059
12670
|
} catch (err) {
|
|
@@ -11064,7 +12675,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11064
12675
|
try {
|
|
11065
12676
|
if (options.verbose) console.log("Generating ERRORS.md...");
|
|
11066
12677
|
const content = generateErrors(graph, projectRoot, version);
|
|
11067
|
-
const filePath =
|
|
12678
|
+
const filePath = join19(options.outputDir, "ERRORS.md");
|
|
11068
12679
|
writeFileSync3(filePath, content, "utf-8");
|
|
11069
12680
|
generated.push("ERRORS.md");
|
|
11070
12681
|
} catch (err) {
|
|
@@ -11075,7 +12686,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11075
12686
|
try {
|
|
11076
12687
|
if (options.verbose) console.log("Generating TESTS.md...");
|
|
11077
12688
|
const content = generateTests(graph, projectRoot, version);
|
|
11078
|
-
const filePath =
|
|
12689
|
+
const filePath = join19(options.outputDir, "TESTS.md");
|
|
11079
12690
|
writeFileSync3(filePath, content, "utf-8");
|
|
11080
12691
|
generated.push("TESTS.md");
|
|
11081
12692
|
} catch (err) {
|
|
@@ -11086,7 +12697,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11086
12697
|
try {
|
|
11087
12698
|
if (options.verbose) console.log("Generating HISTORY.md...");
|
|
11088
12699
|
const content = generateHistory(graph, projectRoot, version);
|
|
11089
|
-
const filePath =
|
|
12700
|
+
const filePath = join19(options.outputDir, "HISTORY.md");
|
|
11090
12701
|
writeFileSync3(filePath, content, "utf-8");
|
|
11091
12702
|
generated.push("HISTORY.md");
|
|
11092
12703
|
} catch (err) {
|
|
@@ -11097,7 +12708,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11097
12708
|
try {
|
|
11098
12709
|
if (options.verbose) console.log("Generating CURRENT.md...");
|
|
11099
12710
|
const content = generateCurrent(graph, projectRoot, version);
|
|
11100
|
-
const filePath =
|
|
12711
|
+
const filePath = join19(options.outputDir, "CURRENT.md");
|
|
11101
12712
|
writeFileSync3(filePath, content, "utf-8");
|
|
11102
12713
|
generated.push("CURRENT.md");
|
|
11103
12714
|
} catch (err) {
|
|
@@ -11108,7 +12719,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11108
12719
|
try {
|
|
11109
12720
|
if (options.verbose) console.log("Generating STATUS.md...");
|
|
11110
12721
|
const content = generateStatus(graph, projectRoot, version);
|
|
11111
|
-
const filePath =
|
|
12722
|
+
const filePath = join19(options.outputDir, "STATUS.md");
|
|
11112
12723
|
writeFileSync3(filePath, content, "utf-8");
|
|
11113
12724
|
generated.push("STATUS.md");
|
|
11114
12725
|
} catch (err) {
|
|
@@ -11119,7 +12730,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11119
12730
|
try {
|
|
11120
12731
|
if (options.verbose) console.log("Generating HEALTH.md...");
|
|
11121
12732
|
const content = generateHealth(graph, projectRoot, version);
|
|
11122
|
-
const filePath =
|
|
12733
|
+
const filePath = join19(options.outputDir, "HEALTH.md");
|
|
11123
12734
|
writeFileSync3(filePath, content, "utf-8");
|
|
11124
12735
|
generated.push("HEALTH.md");
|
|
11125
12736
|
} catch (err) {
|
|
@@ -11130,7 +12741,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11130
12741
|
try {
|
|
11131
12742
|
if (options.verbose) console.log("Generating DEAD_CODE.md...");
|
|
11132
12743
|
const content = generateDeadCode(graph, projectRoot, version);
|
|
11133
|
-
const filePath =
|
|
12744
|
+
const filePath = join19(options.outputDir, "DEAD_CODE.md");
|
|
11134
12745
|
writeFileSync3(filePath, content, "utf-8");
|
|
11135
12746
|
generated.push("DEAD_CODE.md");
|
|
11136
12747
|
} catch (err) {
|
|
@@ -11174,7 +12785,7 @@ function getFileCount13(graph) {
|
|
|
11174
12785
|
}
|
|
11175
12786
|
|
|
11176
12787
|
// src/simulation/engine.ts
|
|
11177
|
-
import { dirname as
|
|
12788
|
+
import { dirname as dirname20, join as join20 } from "path";
|
|
11178
12789
|
function normalizePath2(p) {
|
|
11179
12790
|
return p.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
11180
12791
|
}
|
|
@@ -11306,7 +12917,7 @@ var SimulationEngine = class {
|
|
|
11306
12917
|
}
|
|
11307
12918
|
}
|
|
11308
12919
|
applyRename(clone, target, newName, brokenImports) {
|
|
11309
|
-
const destination =
|
|
12920
|
+
const destination = join20(dirname20(target), newName);
|
|
11310
12921
|
this.applyMove(clone, target, destination, brokenImports);
|
|
11311
12922
|
}
|
|
11312
12923
|
applySplit(clone, target, newFile, symbols, brokenImports) {
|
|
@@ -11506,13 +13117,13 @@ var SimulationEngine = class {
|
|
|
11506
13117
|
};
|
|
11507
13118
|
|
|
11508
13119
|
// src/security/scanner.ts
|
|
11509
|
-
import { existsSync as
|
|
11510
|
-
import { join as
|
|
13120
|
+
import { existsSync as existsSync19 } from "fs";
|
|
13121
|
+
import { join as join30 } from "path";
|
|
11511
13122
|
|
|
11512
13123
|
// src/security/checks/dependencies.ts
|
|
11513
13124
|
import { execSync as execSync2 } from "child_process";
|
|
11514
|
-
import { existsSync as
|
|
11515
|
-
import { join as
|
|
13125
|
+
import { existsSync as existsSync18, readFileSync as readFileSync17, readdirSync as readdirSync10 } from "fs";
|
|
13126
|
+
import { join as join21 } from "path";
|
|
11516
13127
|
function cvssToSeverity(score) {
|
|
11517
13128
|
if (score >= 9) return "critical";
|
|
11518
13129
|
if (score >= 7) return "high";
|
|
@@ -11522,18 +13133,18 @@ function cvssToSeverity(score) {
|
|
|
11522
13133
|
async function checkDependencies(_files, projectRoot) {
|
|
11523
13134
|
const findings = [];
|
|
11524
13135
|
try {
|
|
11525
|
-
if (
|
|
13136
|
+
if (existsSync18(join21(projectRoot, "package.json"))) {
|
|
11526
13137
|
findings.push(...checkNpmAudit(projectRoot));
|
|
11527
13138
|
findings.push(...checkPackageJsonPatterns(projectRoot));
|
|
11528
13139
|
findings.push(...checkPostinstallScripts(projectRoot));
|
|
11529
13140
|
}
|
|
11530
|
-
if (
|
|
13141
|
+
if (existsSync18(join21(projectRoot, "requirements.txt")) || existsSync18(join21(projectRoot, "pyproject.toml"))) {
|
|
11531
13142
|
findings.push(...checkPipAudit(projectRoot));
|
|
11532
13143
|
}
|
|
11533
|
-
if (
|
|
13144
|
+
if (existsSync18(join21(projectRoot, "Cargo.toml"))) {
|
|
11534
13145
|
findings.push(...checkCargoAudit(projectRoot));
|
|
11535
13146
|
}
|
|
11536
|
-
if (
|
|
13147
|
+
if (existsSync18(join21(projectRoot, "go.mod"))) {
|
|
11537
13148
|
findings.push(...checkGoVerify(projectRoot));
|
|
11538
13149
|
}
|
|
11539
13150
|
} catch (err) {
|
|
@@ -11622,8 +13233,8 @@ function checkNpmAudit(projectRoot) {
|
|
|
11622
13233
|
function checkPackageJsonPatterns(projectRoot) {
|
|
11623
13234
|
const findings = [];
|
|
11624
13235
|
try {
|
|
11625
|
-
const pkgPath =
|
|
11626
|
-
const pkg = JSON.parse(
|
|
13236
|
+
const pkgPath = join21(projectRoot, "package.json");
|
|
13237
|
+
const pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
|
|
11627
13238
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
11628
13239
|
for (const [name, version] of Object.entries(allDeps)) {
|
|
11629
13240
|
if (version.startsWith("^") || version.startsWith("~")) {
|
|
@@ -11645,15 +13256,15 @@ function checkPackageJsonPatterns(projectRoot) {
|
|
|
11645
13256
|
}
|
|
11646
13257
|
function checkPostinstallScripts(projectRoot) {
|
|
11647
13258
|
const findings = [];
|
|
11648
|
-
const nodeModules =
|
|
11649
|
-
if (!
|
|
13259
|
+
const nodeModules = join21(projectRoot, "node_modules");
|
|
13260
|
+
if (!existsSync18(nodeModules)) return findings;
|
|
11650
13261
|
try {
|
|
11651
|
-
const topLevelDeps =
|
|
13262
|
+
const topLevelDeps = readdirSync10(nodeModules).filter((d) => !d.startsWith("."));
|
|
11652
13263
|
for (const dep of topLevelDeps) {
|
|
11653
|
-
const depPkgPath =
|
|
11654
|
-
if (!
|
|
13264
|
+
const depPkgPath = join21(nodeModules, dep, "package.json");
|
|
13265
|
+
if (!existsSync18(depPkgPath)) continue;
|
|
11655
13266
|
try {
|
|
11656
|
-
const depPkg = JSON.parse(
|
|
13267
|
+
const depPkg = JSON.parse(readFileSync17(depPkgPath, "utf-8"));
|
|
11657
13268
|
const scripts = depPkg.scripts || {};
|
|
11658
13269
|
if (scripts.postinstall || scripts.preinstall || scripts.install) {
|
|
11659
13270
|
const scriptName = scripts.postinstall ? "postinstall" : scripts.preinstall ? "preinstall" : "install";
|
|
@@ -11691,7 +13302,7 @@ function checkPipAudit(projectRoot) {
|
|
|
11691
13302
|
id: "",
|
|
11692
13303
|
severity: cvssToSeverity(vuln.cvss?.score || 5),
|
|
11693
13304
|
vulnerabilityClass: "dependency-cve",
|
|
11694
|
-
file:
|
|
13305
|
+
file: existsSync18(join21(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
|
|
11695
13306
|
title: `Vulnerable Python dependency: ${vuln.name}`,
|
|
11696
13307
|
description: `${vuln.name}@${vuln.version} \u2014 ${vuln.id}: ${vuln.description || "Known vulnerability"}`,
|
|
11697
13308
|
attackScenario: `An attacker could exploit the vulnerability in ${vuln.name}.`,
|
|
@@ -11788,8 +13399,8 @@ function checkGoVerify(projectRoot) {
|
|
|
11788
13399
|
}
|
|
11789
13400
|
|
|
11790
13401
|
// src/security/checks/injection.ts
|
|
11791
|
-
import { readFileSync as
|
|
11792
|
-
import { join as
|
|
13402
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
13403
|
+
import { join as join22 } from "path";
|
|
11793
13404
|
var SKIP_DIRS = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
11794
13405
|
var TEST_PATTERNS = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__"];
|
|
11795
13406
|
var USER_INPUT_NAMES = /(?:input|user|name|path|query|branch|hash|cmd|command|req\.|params|body|args|url|dir|file|subdirectory)/i;
|
|
@@ -11975,6 +13586,107 @@ var PATTERNS = [
|
|
|
11975
13586
|
description: "popen() called with a variable argument \u2014 potential command injection.",
|
|
11976
13587
|
attackScenario: "An attacker could inject shell metacharacters to execute arbitrary commands.",
|
|
11977
13588
|
suggestedFix: "Avoid popen(). Use pipe/fork/exec with argument arrays instead."
|
|
13589
|
+
},
|
|
13590
|
+
// Kotlin injection patterns
|
|
13591
|
+
{
|
|
13592
|
+
regex: /["']SELECT\b[^"']*\$(?:\{|\w)/,
|
|
13593
|
+
title: "Kotlin SQL injection via string template",
|
|
13594
|
+
vulnClass: "code-injection",
|
|
13595
|
+
baseSeverity: "high",
|
|
13596
|
+
description: "SQL query built using Kotlin string templates \u2014 vulnerable to SQL injection.",
|
|
13597
|
+
attackScenario: "An attacker could inject SQL through interpolated variables to read, modify, or delete database data.",
|
|
13598
|
+
suggestedFix: "Use parameterized queries with PreparedStatement or your ORM's query builder."
|
|
13599
|
+
},
|
|
13600
|
+
{
|
|
13601
|
+
regex: /["'](?:INSERT|UPDATE|DELETE)\b[^"']*\$(?:\{|\w)/,
|
|
13602
|
+
title: "Kotlin SQL injection via string template",
|
|
13603
|
+
vulnClass: "code-injection",
|
|
13604
|
+
baseSeverity: "high",
|
|
13605
|
+
description: "SQL mutation query built using Kotlin string templates \u2014 vulnerable to SQL injection.",
|
|
13606
|
+
attackScenario: "An attacker could inject SQL through interpolated variables.",
|
|
13607
|
+
suggestedFix: "Use parameterized queries with PreparedStatement or your ORM's query builder."
|
|
13608
|
+
},
|
|
13609
|
+
{
|
|
13610
|
+
regex: /Runtime\.getRuntime\(\)\.exec\s*\(/,
|
|
13611
|
+
title: "Kotlin/Java command injection via Runtime.exec",
|
|
13612
|
+
vulnClass: "shell-injection",
|
|
13613
|
+
baseSeverity: "high",
|
|
13614
|
+
description: "Runtime.exec() executes a system command \u2014 vulnerable if user input reaches the argument.",
|
|
13615
|
+
attackScenario: "An attacker could inject shell metacharacters to execute arbitrary commands on the server.",
|
|
13616
|
+
suggestedFix: "Use ProcessBuilder with an argument array. Validate all input against a strict allowlist."
|
|
13617
|
+
},
|
|
13618
|
+
{
|
|
13619
|
+
regex: /\.csrf\(\)\s*\.?\s*disable\(\)/,
|
|
13620
|
+
title: "Spring Security CSRF protection disabled",
|
|
13621
|
+
vulnClass: "code-injection",
|
|
13622
|
+
baseSeverity: "medium",
|
|
13623
|
+
description: "CSRF protection has been explicitly disabled in Spring Security configuration.",
|
|
13624
|
+
attackScenario: "An attacker could forge cross-site requests to perform actions on behalf of authenticated users.",
|
|
13625
|
+
suggestedFix: "Only disable CSRF for stateless APIs using JWT. Keep CSRF enabled for session-based authentication."
|
|
13626
|
+
},
|
|
13627
|
+
{
|
|
13628
|
+
regex: /\.permitAll\(\).*(?:admin|manage|delete|config|setting)/i,
|
|
13629
|
+
title: "Spring Security permitAll on sensitive path",
|
|
13630
|
+
vulnClass: "code-injection",
|
|
13631
|
+
baseSeverity: "high",
|
|
13632
|
+
description: "permitAll() applied to a path that appears security-sensitive.",
|
|
13633
|
+
attackScenario: "An attacker could access administrative or destructive endpoints without authentication.",
|
|
13634
|
+
suggestedFix: 'Use .hasRole("ADMIN") or .authenticated() for sensitive endpoints.'
|
|
13635
|
+
},
|
|
13636
|
+
// PHP injection patterns
|
|
13637
|
+
{
|
|
13638
|
+
regex: /\$wpdb\s*->\s*query\s*\(\s*["'][^"']*\$|.*\$wpdb\s*->\s*query\s*\(\s*[^"']*\.\s*\$/,
|
|
13639
|
+
title: "PHP SQL injection via $wpdb->query with string concatenation",
|
|
13640
|
+
vulnClass: "code-injection",
|
|
13641
|
+
baseSeverity: "high",
|
|
13642
|
+
description: "WordPress $wpdb->query() called with direct variable interpolation \u2014 vulnerable to SQL injection.",
|
|
13643
|
+
attackScenario: "An attacker could inject SQL through unescaped user input to read, modify, or delete database data.",
|
|
13644
|
+
suggestedFix: 'Use $wpdb->prepare() with placeholders: $wpdb->query($wpdb->prepare("SELECT * FROM table WHERE id = %d", $id))'
|
|
13645
|
+
},
|
|
13646
|
+
{
|
|
13647
|
+
regex: /\beval\s*\(\s*\$/,
|
|
13648
|
+
title: "PHP eval() with variable input",
|
|
13649
|
+
vulnClass: "code-injection",
|
|
13650
|
+
baseSeverity: "high",
|
|
13651
|
+
description: "eval() executes arbitrary PHP code from a variable \u2014 potential RCE.",
|
|
13652
|
+
attackScenario: "An attacker could inject malicious PHP code if user input reaches eval().",
|
|
13653
|
+
suggestedFix: "Remove eval() entirely. Use safe alternatives like json_decode() for data or specific parsers."
|
|
13654
|
+
},
|
|
13655
|
+
{
|
|
13656
|
+
regex: /\b(?:system|exec|shell_exec|passthru)\s*\(\s*\$/,
|
|
13657
|
+
title: "PHP command injection via system/exec/shell_exec/passthru",
|
|
13658
|
+
vulnClass: "shell-injection",
|
|
13659
|
+
baseSeverity: "high",
|
|
13660
|
+
description: "Shell command execution with a variable argument \u2014 potential command injection.",
|
|
13661
|
+
attackScenario: "An attacker could inject shell metacharacters to execute arbitrary commands on the server.",
|
|
13662
|
+
suggestedFix: "Use escapeshellarg() and escapeshellcmd() to sanitize input, or avoid shell commands entirely."
|
|
13663
|
+
},
|
|
13664
|
+
{
|
|
13665
|
+
regex: /preg_replace\s*\(\s*['"]\/[^'"]*\/e['"]/,
|
|
13666
|
+
title: "PHP preg_replace with /e modifier (code execution)",
|
|
13667
|
+
vulnClass: "code-injection",
|
|
13668
|
+
baseSeverity: "high",
|
|
13669
|
+
description: "preg_replace() with the /e modifier evaluates the replacement as PHP code \u2014 deprecated and dangerous.",
|
|
13670
|
+
attackScenario: "An attacker could inject PHP code through the matched string to achieve remote code execution.",
|
|
13671
|
+
suggestedFix: "Use preg_replace_callback() instead of the /e modifier."
|
|
13672
|
+
},
|
|
13673
|
+
{
|
|
13674
|
+
regex: /\bunserialize\s*\(\s*\$(?:_GET|_POST|_REQUEST|_COOKIE)/,
|
|
13675
|
+
title: "PHP insecure deserialization of user input",
|
|
13676
|
+
vulnClass: "code-injection",
|
|
13677
|
+
baseSeverity: "high",
|
|
13678
|
+
description: "unserialize() called on user-controlled superglobal input \u2014 potential RCE via PHP object injection.",
|
|
13679
|
+
attackScenario: "An attacker could craft a malicious serialized PHP object to achieve remote code execution.",
|
|
13680
|
+
suggestedFix: "Use json_decode() instead of unserialize() for user input. If unserialize is necessary, use the allowed_classes option."
|
|
13681
|
+
},
|
|
13682
|
+
{
|
|
13683
|
+
regex: /\bextract\s*\(\s*\$(?:_GET|_POST|_REQUEST)/,
|
|
13684
|
+
title: "PHP extract() on superglobal input",
|
|
13685
|
+
vulnClass: "code-injection",
|
|
13686
|
+
baseSeverity: "high",
|
|
13687
|
+
description: "extract() on $_GET/$_POST/$_REQUEST overwrites local variables \u2014 can bypass security checks.",
|
|
13688
|
+
attackScenario: "An attacker could set arbitrary variables by crafting request parameters, potentially overwriting auth flags or config values.",
|
|
13689
|
+
suggestedFix: "Avoid extract() on user input. Access superglobals directly or use a whitelist of expected keys."
|
|
11978
13690
|
}
|
|
11979
13691
|
];
|
|
11980
13692
|
function shouldSkip(filePath) {
|
|
@@ -11991,7 +13703,7 @@ async function checkInjection(files, projectRoot) {
|
|
|
11991
13703
|
if (shouldSkip(file.filePath) || isTestFile4(file.filePath)) continue;
|
|
11992
13704
|
let content;
|
|
11993
13705
|
try {
|
|
11994
|
-
content =
|
|
13706
|
+
content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
|
|
11995
13707
|
} catch {
|
|
11996
13708
|
continue;
|
|
11997
13709
|
}
|
|
@@ -12029,8 +13741,8 @@ async function checkInjection(files, projectRoot) {
|
|
|
12029
13741
|
}
|
|
12030
13742
|
|
|
12031
13743
|
// src/security/checks/secrets.ts
|
|
12032
|
-
import { readFileSync as
|
|
12033
|
-
import { join as
|
|
13744
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
13745
|
+
import { join as join23 } from "path";
|
|
12034
13746
|
var SKIP_DIRS2 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
12035
13747
|
var TEST_PATTERNS2 = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__", ".example", ".sample"];
|
|
12036
13748
|
var SECRET_PATTERNS = [
|
|
@@ -12063,7 +13775,7 @@ async function checkSecrets(files, projectRoot) {
|
|
|
12063
13775
|
if (shouldSkip2(file.filePath) || isTestFile5(file.filePath)) continue;
|
|
12064
13776
|
let content;
|
|
12065
13777
|
try {
|
|
12066
|
-
content =
|
|
13778
|
+
content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
|
|
12067
13779
|
} catch {
|
|
12068
13780
|
continue;
|
|
12069
13781
|
}
|
|
@@ -12096,8 +13808,8 @@ async function checkSecrets(files, projectRoot) {
|
|
|
12096
13808
|
}
|
|
12097
13809
|
|
|
12098
13810
|
// src/security/checks/path-traversal.ts
|
|
12099
|
-
import { readFileSync as
|
|
12100
|
-
import { join as
|
|
13811
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
13812
|
+
import { join as join24 } from "path";
|
|
12101
13813
|
var SKIP_DIRS3 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
12102
13814
|
var USER_INPUT_VARS = /(?:req\.|params|query|body|input|path|dir|subdirectory|file|userInput|fileName|filePath)/i;
|
|
12103
13815
|
var PATTERNS2 = [
|
|
@@ -12144,7 +13856,7 @@ async function checkPathTraversal(files, projectRoot) {
|
|
|
12144
13856
|
if (shouldSkip3(file.filePath)) continue;
|
|
12145
13857
|
let content;
|
|
12146
13858
|
try {
|
|
12147
|
-
content =
|
|
13859
|
+
content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
|
|
12148
13860
|
} catch {
|
|
12149
13861
|
continue;
|
|
12150
13862
|
}
|
|
@@ -12186,8 +13898,8 @@ async function checkPathTraversal(files, projectRoot) {
|
|
|
12186
13898
|
}
|
|
12187
13899
|
|
|
12188
13900
|
// src/security/checks/auth.ts
|
|
12189
|
-
import { readFileSync as
|
|
12190
|
-
import { join as
|
|
13901
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
13902
|
+
import { join as join25 } from "path";
|
|
12191
13903
|
var SKIP_DIRS4 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
12192
13904
|
function shouldSkip4(filePath) {
|
|
12193
13905
|
return SKIP_DIRS4.some((d) => filePath.includes(d));
|
|
@@ -12203,7 +13915,7 @@ async function checkAuth(files, projectRoot) {
|
|
|
12203
13915
|
if (shouldSkip4(file.filePath)) continue;
|
|
12204
13916
|
let content;
|
|
12205
13917
|
try {
|
|
12206
|
-
content =
|
|
13918
|
+
content = readFileSync21(join25(projectRoot, file.filePath), "utf-8");
|
|
12207
13919
|
} catch {
|
|
12208
13920
|
continue;
|
|
12209
13921
|
}
|
|
@@ -12294,8 +14006,8 @@ async function checkAuth(files, projectRoot) {
|
|
|
12294
14006
|
}
|
|
12295
14007
|
|
|
12296
14008
|
// src/security/checks/input-validation.ts
|
|
12297
|
-
import { readFileSync as
|
|
12298
|
-
import { join as
|
|
14009
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
14010
|
+
import { join as join26 } from "path";
|
|
12299
14011
|
var SKIP_DIRS5 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
12300
14012
|
function shouldSkip5(filePath) {
|
|
12301
14013
|
return SKIP_DIRS5.some((d) => filePath.includes(d));
|
|
@@ -12307,7 +14019,7 @@ async function checkInputValidation(files, projectRoot) {
|
|
|
12307
14019
|
if (shouldSkip5(file.filePath)) continue;
|
|
12308
14020
|
let content;
|
|
12309
14021
|
try {
|
|
12310
|
-
content =
|
|
14022
|
+
content = readFileSync22(join26(projectRoot, file.filePath), "utf-8");
|
|
12311
14023
|
} catch {
|
|
12312
14024
|
continue;
|
|
12313
14025
|
}
|
|
@@ -12384,8 +14096,8 @@ async function checkInputValidation(files, projectRoot) {
|
|
|
12384
14096
|
}
|
|
12385
14097
|
|
|
12386
14098
|
// src/security/checks/information-disclosure.ts
|
|
12387
|
-
import { readFileSync as
|
|
12388
|
-
import { join as
|
|
14099
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
14100
|
+
import { join as join27 } from "path";
|
|
12389
14101
|
var SKIP_DIRS6 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
12390
14102
|
function shouldSkip6(filePath) {
|
|
12391
14103
|
return SKIP_DIRS6.some((d) => filePath.includes(d));
|
|
@@ -12397,7 +14109,7 @@ async function checkInformationDisclosure(files, projectRoot) {
|
|
|
12397
14109
|
if (shouldSkip6(file.filePath)) continue;
|
|
12398
14110
|
let content;
|
|
12399
14111
|
try {
|
|
12400
|
-
content =
|
|
14112
|
+
content = readFileSync23(join27(projectRoot, file.filePath), "utf-8");
|
|
12401
14113
|
} catch {
|
|
12402
14114
|
continue;
|
|
12403
14115
|
}
|
|
@@ -12467,8 +14179,8 @@ async function checkInformationDisclosure(files, projectRoot) {
|
|
|
12467
14179
|
}
|
|
12468
14180
|
|
|
12469
14181
|
// src/security/checks/cryptography.ts
|
|
12470
|
-
import { readFileSync as
|
|
12471
|
-
import { join as
|
|
14182
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
14183
|
+
import { join as join28 } from "path";
|
|
12472
14184
|
var SKIP_DIRS7 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
12473
14185
|
var USER_INPUT_NAMES2 = /(?:input|user|name|path|query|param|request|body|args|url)/i;
|
|
12474
14186
|
function shouldSkip7(filePath) {
|
|
@@ -12485,7 +14197,7 @@ async function checkCryptography(files, projectRoot) {
|
|
|
12485
14197
|
if (shouldSkip7(file.filePath)) continue;
|
|
12486
14198
|
let content;
|
|
12487
14199
|
try {
|
|
12488
|
-
content =
|
|
14200
|
+
content = readFileSync24(join28(projectRoot, file.filePath), "utf-8");
|
|
12489
14201
|
} catch {
|
|
12490
14202
|
continue;
|
|
12491
14203
|
}
|
|
@@ -12613,6 +14325,136 @@ async function checkCryptography(files, projectRoot) {
|
|
|
12613
14325
|
suggestedFix: "Load credentials from environment variables or a secure vault at runtime."
|
|
12614
14326
|
});
|
|
12615
14327
|
}
|
|
14328
|
+
if (/(?:val|var)\s+(?:password|secret|apiKey|api_key|token)\s*=\s*["']/i.test(line)) {
|
|
14329
|
+
findings.push({
|
|
14330
|
+
id: "",
|
|
14331
|
+
severity: "high",
|
|
14332
|
+
vulnerabilityClass: "cryptography",
|
|
14333
|
+
file: file.filePath,
|
|
14334
|
+
line: i + 1,
|
|
14335
|
+
title: "Hardcoded credentials in Kotlin source",
|
|
14336
|
+
description: "A password, secret, or API key is hardcoded as a string literal.",
|
|
14337
|
+
attackScenario: "An attacker with access to the binary or source could extract the credential.",
|
|
14338
|
+
suggestedFix: "Load credentials from environment variables or a secure vault at runtime."
|
|
14339
|
+
});
|
|
14340
|
+
}
|
|
14341
|
+
if (/\bRandom\s*\(\s*\)/.test(line) && isCryptoFile) {
|
|
14342
|
+
findings.push({
|
|
14343
|
+
id: "",
|
|
14344
|
+
severity: "medium",
|
|
14345
|
+
vulnerabilityClass: "cryptography",
|
|
14346
|
+
file: file.filePath,
|
|
14347
|
+
line: i + 1,
|
|
14348
|
+
title: "Insecure random in Kotlin security context",
|
|
14349
|
+
description: "kotlin.random.Random() is not cryptographically secure \u2014 its output can be predicted.",
|
|
14350
|
+
attackScenario: "An attacker could predict random values to forge tokens or bypass security checks.",
|
|
14351
|
+
suggestedFix: "Use java.security.SecureRandom for cryptographic purposes."
|
|
14352
|
+
});
|
|
14353
|
+
}
|
|
14354
|
+
if (/!!\s*\./.test(line) && isCryptoFile) {
|
|
14355
|
+
findings.push({
|
|
14356
|
+
id: "",
|
|
14357
|
+
severity: "medium",
|
|
14358
|
+
vulnerabilityClass: "cryptography",
|
|
14359
|
+
file: file.filePath,
|
|
14360
|
+
line: i + 1,
|
|
14361
|
+
title: "Not-null assertion (!!) in security-sensitive Kotlin code",
|
|
14362
|
+
description: "The !! operator can throw NullPointerException, potentially bypassing security checks.",
|
|
14363
|
+
attackScenario: "An attacker could trigger a null value to cause an exception that bypasses validation logic.",
|
|
14364
|
+
suggestedFix: "Use safe calls (?.) with proper null handling instead of !! assertions."
|
|
14365
|
+
});
|
|
14366
|
+
}
|
|
14367
|
+
if (/(?:val|var)\s+\w*[Uu]rl\w*\s*=\s*["']http:\/\/(?!(?:localhost|127\.))/.test(line)) {
|
|
14368
|
+
findings.push({
|
|
14369
|
+
id: "",
|
|
14370
|
+
severity: "medium",
|
|
14371
|
+
vulnerabilityClass: "cryptography",
|
|
14372
|
+
file: file.filePath,
|
|
14373
|
+
line: i + 1,
|
|
14374
|
+
title: "Hardcoded HTTP URL in Kotlin source",
|
|
14375
|
+
description: "An HTTP (not HTTPS) URL is hardcoded \u2014 data is transmitted unencrypted.",
|
|
14376
|
+
attackScenario: "An attacker on the network path could intercept, read, or modify data in transit.",
|
|
14377
|
+
suggestedFix: "Use HTTPS for all external URLs to ensure data confidentiality and integrity."
|
|
14378
|
+
});
|
|
14379
|
+
}
|
|
14380
|
+
if (/\bmd5\s*\(/.test(line) && /password|passwd|pass|pwd/i.test(line)) {
|
|
14381
|
+
findings.push({
|
|
14382
|
+
id: "",
|
|
14383
|
+
severity: "high",
|
|
14384
|
+
vulnerabilityClass: "cryptography",
|
|
14385
|
+
file: file.filePath,
|
|
14386
|
+
line: i + 1,
|
|
14387
|
+
title: "PHP md5() used for password hashing",
|
|
14388
|
+
description: "md5() is cryptographically broken and should never be used for password hashing.",
|
|
14389
|
+
attackScenario: "An attacker could crack MD5 password hashes in seconds using rainbow tables or GPU brute force.",
|
|
14390
|
+
suggestedFix: "Use password_hash() with PASSWORD_BCRYPT or PASSWORD_ARGON2ID."
|
|
14391
|
+
});
|
|
14392
|
+
}
|
|
14393
|
+
if (/\bsha1\s*\(/.test(line) && /password|passwd|pass|pwd/i.test(line)) {
|
|
14394
|
+
findings.push({
|
|
14395
|
+
id: "",
|
|
14396
|
+
severity: "high",
|
|
14397
|
+
vulnerabilityClass: "cryptography",
|
|
14398
|
+
file: file.filePath,
|
|
14399
|
+
line: i + 1,
|
|
14400
|
+
title: "PHP sha1() used for password hashing",
|
|
14401
|
+
description: "SHA-1 has known collision attacks and should not be used for password hashing.",
|
|
14402
|
+
attackScenario: "An attacker could crack SHA-1 password hashes using precomputed tables.",
|
|
14403
|
+
suggestedFix: "Use password_hash() with PASSWORD_BCRYPT or PASSWORD_ARGON2ID."
|
|
14404
|
+
});
|
|
14405
|
+
}
|
|
14406
|
+
if (/\bcrypt\s*\(\s*[^,]+,\s*['"][\$]?[12a-zA-Z]{0,3}['"]/.test(line)) {
|
|
14407
|
+
findings.push({
|
|
14408
|
+
id: "",
|
|
14409
|
+
severity: "medium",
|
|
14410
|
+
vulnerabilityClass: "cryptography",
|
|
14411
|
+
file: file.filePath,
|
|
14412
|
+
line: i + 1,
|
|
14413
|
+
title: "PHP crypt() with potentially weak salt",
|
|
14414
|
+
description: "crypt() with a short or weak salt may use DES or MD5 algorithm.",
|
|
14415
|
+
attackScenario: "An attacker could crack weakly-salted crypt() hashes using brute force.",
|
|
14416
|
+
suggestedFix: "Use password_hash() instead of crypt(). It automatically uses a strong algorithm and salt."
|
|
14417
|
+
});
|
|
14418
|
+
}
|
|
14419
|
+
if (/\bmcrypt_/.test(line)) {
|
|
14420
|
+
findings.push({
|
|
14421
|
+
id: "",
|
|
14422
|
+
severity: "high",
|
|
14423
|
+
vulnerabilityClass: "cryptography",
|
|
14424
|
+
file: file.filePath,
|
|
14425
|
+
line: i + 1,
|
|
14426
|
+
title: "PHP deprecated mcrypt_* function",
|
|
14427
|
+
description: "mcrypt extension was deprecated in PHP 7.1 and removed in PHP 7.2. It has known vulnerabilities.",
|
|
14428
|
+
attackScenario: "An attacker could exploit known weaknesses in mcrypt implementations.",
|
|
14429
|
+
suggestedFix: "Use openssl_encrypt()/openssl_decrypt() or the sodium extension (sodium_crypto_*)."
|
|
14430
|
+
});
|
|
14431
|
+
}
|
|
14432
|
+
if (/\b(?:rand|mt_rand)\s*\(/.test(line) && isCryptoFile) {
|
|
14433
|
+
findings.push({
|
|
14434
|
+
id: "",
|
|
14435
|
+
severity: "medium",
|
|
14436
|
+
vulnerabilityClass: "cryptography",
|
|
14437
|
+
file: file.filePath,
|
|
14438
|
+
line: i + 1,
|
|
14439
|
+
title: "PHP rand()/mt_rand() in security context",
|
|
14440
|
+
description: "rand() and mt_rand() are not cryptographically secure \u2014 their output can be predicted.",
|
|
14441
|
+
attackScenario: "An attacker could predict random values to forge tokens or bypass security checks.",
|
|
14442
|
+
suggestedFix: "Use random_bytes() or random_int() for cryptographic purposes."
|
|
14443
|
+
});
|
|
14444
|
+
}
|
|
14445
|
+
if (/\$(?:password|secret|api_?key|token)\s*=\s*['"][^'"]{4,}['"]/i.test(line)) {
|
|
14446
|
+
findings.push({
|
|
14447
|
+
id: "",
|
|
14448
|
+
severity: "high",
|
|
14449
|
+
vulnerabilityClass: "cryptography",
|
|
14450
|
+
file: file.filePath,
|
|
14451
|
+
line: i + 1,
|
|
14452
|
+
title: "Hardcoded credentials in PHP source",
|
|
14453
|
+
description: "A password, secret, or API key is hardcoded as a string literal.",
|
|
14454
|
+
attackScenario: "An attacker with access to the source could extract the credential.",
|
|
14455
|
+
suggestedFix: "Load credentials from environment variables using getenv() or $_ENV."
|
|
14456
|
+
});
|
|
14457
|
+
}
|
|
12616
14458
|
}
|
|
12617
14459
|
}
|
|
12618
14460
|
} catch {
|
|
@@ -12621,8 +14463,8 @@ async function checkCryptography(files, projectRoot) {
|
|
|
12621
14463
|
}
|
|
12622
14464
|
|
|
12623
14465
|
// src/security/checks/frontend.ts
|
|
12624
|
-
import { readFileSync as
|
|
12625
|
-
import { join as
|
|
14466
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
14467
|
+
import { join as join29 } from "path";
|
|
12626
14468
|
var SKIP_DIRS8 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
12627
14469
|
function shouldSkip8(filePath) {
|
|
12628
14470
|
return SKIP_DIRS8.some((d) => filePath.includes(d));
|
|
@@ -12638,7 +14480,7 @@ async function checkFrontend(files, projectRoot) {
|
|
|
12638
14480
|
if (!isFrontendFile(file.filePath)) continue;
|
|
12639
14481
|
let content;
|
|
12640
14482
|
try {
|
|
12641
|
-
content =
|
|
14483
|
+
content = readFileSync25(join29(projectRoot, file.filePath), "utf-8");
|
|
12642
14484
|
} catch {
|
|
12643
14485
|
continue;
|
|
12644
14486
|
}
|
|
@@ -13098,13 +14940,13 @@ async function scanSecurity(projectRoot, graph, options = {}) {
|
|
|
13098
14940
|
};
|
|
13099
14941
|
}
|
|
13100
14942
|
function detectPackageManager(projectRoot) {
|
|
13101
|
-
if (
|
|
13102
|
-
if (
|
|
13103
|
-
if (
|
|
13104
|
-
if (
|
|
13105
|
-
if (
|
|
13106
|
-
if (
|
|
13107
|
-
if (
|
|
14943
|
+
if (existsSync19(join30(projectRoot, "package.json"))) return "npm";
|
|
14944
|
+
if (existsSync19(join30(projectRoot, "requirements.txt"))) return "pip";
|
|
14945
|
+
if (existsSync19(join30(projectRoot, "pyproject.toml"))) return "pip";
|
|
14946
|
+
if (existsSync19(join30(projectRoot, "Cargo.toml"))) return "cargo";
|
|
14947
|
+
if (existsSync19(join30(projectRoot, "go.mod"))) return "go";
|
|
14948
|
+
if (existsSync19(join30(projectRoot, "pom.xml"))) return "maven";
|
|
14949
|
+
if (existsSync19(join30(projectRoot, "build.gradle")) || existsSync19(join30(projectRoot, "build.gradle.kts"))) return "gradle";
|
|
13108
14950
|
return "unknown";
|
|
13109
14951
|
}
|
|
13110
14952
|
|