adam-java-analytics
Version:
Java code analyzer for detecting package dependencies and circular references
127 lines (103 loc) • 3.25 kB
text/typescript
"use server"
import fs from "fs"
import path from "path"
import { promisify } from "util"
const readFile = promisify(fs.readFile)
export interface JavaFile {
class: string
package: string
path: string
fullName: string
imports: string[]
}
export interface JavaProjectData {
files: JavaFile[]
circularDependencies: string[][]
}
export async function analyzeProject(projectPath: string): Promise<JavaProjectData> {
try {
const javaFiles: string[] = []
const sourceRoots = [
path.join(projectPath, "src", "main", "java"),
path.join(projectPath, "src"),
projectPath,
]
function collectJavaFiles(dir: string) {
if (!fs.existsSync(dir)) return
const entries = fs.readdirSync(dir, { withFileTypes: true })
for (const entry of entries) {
const fullPath = path.join(dir, entry.name)
if (entry.isDirectory()) {
collectJavaFiles(fullPath)
} else if (entry.isFile() && entry.name.endsWith(".java")) {
javaFiles.push(fullPath)
}
}
}
sourceRoots.forEach(collectJavaFiles)
console.log("✅ Java files found:", javaFiles.length)
const files: JavaFile[] = []
const dependencyEdges = new Set<string>()
const dependencyGraph: Record<string, string[]> = {}
for (const filePath of javaFiles) {
const content = await readFile(filePath, "utf8")
const packageMatch = content.match(/package\s+([\w\.]+);/)
const classMatch = filePath.match(/([^\/\\]+)\.java$/)
const importMatches = [...content.matchAll(/import\s+([\w\.]+);/g)]
if (!packageMatch || !classMatch) continue
const packageName = packageMatch[1]
const className = classMatch[1]
const fullName = `${packageName}.${className}`
const imports = importMatches.map((m) => m[1])
files.push({
class: className,
package: packageName,
path: filePath,
fullName,
imports,
})
dependencyGraph[fullName] = imports
}
const circularDependencies = detectCircularDependencies(dependencyGraph)
console.log(files);
return {
files,
circularDependencies,
}
} catch (error) {
console.error("❌ Error analyzing project:", error)
throw new Error("Failed to analyze project")
}
}
function detectCircularDependencies(graph: Record<string, string[]>): string[][] {
const cycles: string[][] = []
const visited = new Set<string>()
const recursionStack = new Set<string>()
function dfs(node: string, path: string[] = []) {
if (recursionStack.has(node)) {
const cycleStart = path.indexOf(node)
if (cycleStart !== -1) {
const cycle = path.slice(cycleStart).concat(node)
const cycleKey = cycle.join("->")
if (!cycles.some((c) => c.join("->") === cycleKey)) {
cycles.push(cycle)
}
}
return
}
if (visited.has(node)) return
visited.add(node)
recursionStack.add(node)
path.push(node)
for (const neighbor of graph[node] || []) {
dfs(neighbor, [...path])
}
recursionStack.delete(node)
}
for (const node of Object.keys(graph)) {
if (!visited.has(node)) {
dfs(node)
}
}
return cycles
}