Utils/JS_Code_Analyzer/calculate-complexity.js (115 lines of code) (raw):
import fs from "fs";
import { fileURLToPath } from 'url';
import path, { dirname } from "path";
import madge from 'madge';
import { ESLint } from "eslint";
import calculateDepths from "./common/calculate-depth.js";
const MADGE_CONFIG = {
"fileExtensions": ["js", "jsx", "ts", "tsx"]
}
const ESLINT_CONFIG = {
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
extends: ["plugin:@typescript-eslint/recommended"],
"rules": {
"complexity": ["error", 0], // Example: Report error if complexity is greater than 5
"max-lines": ["error", 0]
}
}
const findJavaScriptFiles = (dir, excludedDirs = []) => {
let filesToAnalyze = [];
const walkDir = (currentPath) => {
const files = fs.readdirSync(currentPath, { withFileTypes: true });
for (const file of files) {
const absolutePath = path.join(currentPath, file.name);
if (file.isDirectory()) {
// Skip excluded directories
if (!excludedDirs.includes(file.name)) {
walkDir(absolutePath);
}
} else if (/^(?!.*\.test\.(js|jsx|ts|tsx)$).*\.(js|jsx|ts|tsx|html)$/.test(file.name)) {
filesToAnalyze.push({ absolutePath });
}
}
};
walkDir(dir);
return filesToAnalyze;
};
function createReportItem() {
return {
reponame: "",
filepath: "",
filename: "",
linesOfCode: 0,
complexities: [],
avrComplexity: 0,
maxDepth: 0,
};
}
(async function main() {
const [directory, exclude] = process.argv.slice(2);
const excludedDirs = exclude ? exclude.split(',').map(dir => dir.trim()) : [];
if (!directory) {
console.log("Usage: node script.js <directory> [<excludedDirs>]");
process.exit(1);
}
const calculatedDepth = await madge(directory, MADGE_CONFIG).then((res) => calculateDepths(res.obj()))
const files = findJavaScriptFiles(directory, excludedDirs);
const repoName = path.basename(directory)
if (files.length === 0) {
console.log("No JavaScript files found.");
return;
}
console.log(`Analyzing ${files.length} files...`);
const eslint = new ESLint({
useEslintrc: false,
overrideConfig: ESLINT_CONFIG
});
const results = await eslint.lintFiles(files.map(file => file.absolutePath));
let report = [];
const complexityRegex = /complexity of (\d+)/;
const locRegex = /File has too many lines \((\d+)\)/;
results.forEach((result) => {
let newReport = createReportItem();
newReport.filepath = result.filePath;
result.messages.forEach((message) => {
if (message.ruleId === 'max-lines') {
const match = message.message.match(locRegex);
newReport.linesOfCode = match[1];
}
if (message.ruleId === 'complexity') {
const match = message.message.match(complexityRegex);
newReport.complexities.push(Number(match[1]));
}
});
// add calculatedDepth to report
const fileName = newReport.filepath.replace(path.join(directory, '/'), "");
newReport.maxDepth = calculatedDepth[fileName] || 0
newReport.filename = fileName
newReport.reponame = repoName
report.push(newReport);
});
report.forEach(item => {
if (item.complexities.length !== 0) {
item.complexities.forEach(complexity => {
item.avrComplexity += Number(complexity)
});
// item.avrComplexity = Number(Math.ceil(item.avrComplexity / item.complexities.length));
}
});
addToCsv(report)
console.log(report);
})();
function addToCsv(data) {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const filePath = path.join(__dirname, 'data.csv');
const exists = fs.existsSync(filePath);
let csvContent = '';
if (!exists) {
csvContent += 'Repo,File,Complexity,LoC,Depth\n';
}
data.forEach(fileReport => {
const dataToAdd = ["reponame", "filename", "avrComplexity", "linesOfCode", "maxDepth"].map(metric => fileReport[metric]).join(',')
csvContent += dataToAdd + '\n'
})
csvContent += '\n'
fs.appendFileSync(filePath, csvContent);
}