sicua
Version:
A tool for analyzing project structure and dependencies
183 lines (182 loc) • 7.76 kB
JavaScript
;
/**
* General Analyzer - Extracts general code metrics from the codebase
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeneralAnalyzer = void 0;
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
const metricsCalculator_1 = require("./calculators/metricsCalculator");
const magicNumberDetector_1 = require("./detectors/magicNumberDetector");
const projectMetadataDetector_1 = require("./detectors/projectMetadataDetector");
const lineCounter_1 = require("./utils/lineCounter");
class GeneralAnalyzer {
constructor(scanResult, config = {}) {
this.scanResult = scanResult;
this.projectRoot = this.extractProjectRoot();
this.config = {
excludeTestFiles: true,
excludeNodeModules: true,
fileExtensions: [".ts", ".tsx", ".js", ".jsx"],
...config,
};
}
/**
* Extracts the project root directory from the scanned file paths
* @returns The project root directory path
*/
extractProjectRoot() {
if (this.scanResult.filePaths.length === 0) {
throw new Error("No files found in scan result");
}
// Find the common root directory of all scanned files
const firstPath = this.scanResult.filePaths[0];
let commonRoot = path.dirname(firstPath);
// Find the deepest common directory
for (const filePath of this.scanResult.filePaths.slice(1)) {
while (!filePath.startsWith(commonRoot + path.sep) &&
!filePath.startsWith(commonRoot)) {
commonRoot = path.dirname(commonRoot);
if (commonRoot === path.dirname(commonRoot)) {
// Reached filesystem root
break;
}
}
}
// Look for package.json in the common root and parent directories
let projectRoot = commonRoot;
const maxLevelsUp = 3; // Prevent infinite loops
for (let i = 0; i < maxLevelsUp; i++) {
const packageJsonPath = path.join(projectRoot, "package.json");
if (fs.existsSync(packageJsonPath)) {
return projectRoot;
}
const parentDir = path.dirname(projectRoot);
if (parentDir === projectRoot) {
// Reached filesystem root
break;
}
projectRoot = parentDir;
}
return commonRoot;
}
/**
* Performs the general analysis and returns metrics
* @returns Promise containing the analysis result
*/
async analyze() {
const relevantFiles = this.getRelevantFiles();
const totalFiles = this.scanResult.filePaths.length;
const analyzedFiles = relevantFiles.length;
const fileLineMetrics = [];
const fileMagicNumbers = [];
// Process each relevant file
for (const filePath of relevantFiles) {
try {
// Get file content and source file from scan result
const content = this.scanResult.fileContents.get(filePath);
const sourceFile = this.scanResult.sourceFiles.get(filePath);
if (!content || !sourceFile) {
console.warn(`Missing content or source file for: ${filePath}`);
continue;
}
// Count lines for this file
const lineMetrics = (0, lineCounter_1.countLines)(content);
// Validate line metrics
if (!(0, metricsCalculator_1.validateLineMetrics)(lineMetrics)) {
console.warn(`Invalid line metrics for file: ${filePath}`);
}
fileLineMetrics.push(lineMetrics);
// Detect magic numbers for this file
const magicNumbers = (0, magicNumberDetector_1.detectMagicNumbers)(sourceFile, filePath, this.getContextCode);
fileMagicNumbers.push(magicNumbers);
}
catch (error) {
console.error(`Error analyzing file ${filePath}:`, error);
// Continue processing other files
continue;
}
}
// Aggregate all metrics
const aggregatedLineMetrics = (0, metricsCalculator_1.aggregateLineMetrics)(fileLineMetrics);
const allMagicNumbers = (0, metricsCalculator_1.aggregateMagicNumbers)(fileMagicNumbers);
const codeMetrics = (0, metricsCalculator_1.buildCodeMetrics)(aggregatedLineMetrics, allMagicNumbers);
// Detect project metadata
const projectMetadata = (0, projectMetadataDetector_1.detectProjectMetadata)(this.projectRoot);
return {
codeMetrics,
projectMetadata,
analyzedFiles,
totalFiles,
};
}
/**
* Filters files based on configuration
* @returns Array of file paths that should be analyzed
*/
getRelevantFiles() {
return this.scanResult.filePaths.filter((filePath) => {
// Check file extension
if (this.config.fileExtensions) {
const hasValidExtension = this.config.fileExtensions.some((ext) => filePath.endsWith(ext));
if (!hasValidExtension)
return false;
}
// Exclude test files if configured
if (this.config.excludeTestFiles) {
const metadata = this.scanResult.fileMetadata.get(filePath);
if (metadata?.isTest)
return false;
}
// Exclude node_modules if configured
if (this.config.excludeNodeModules && filePath.includes("node_modules")) {
return false;
}
return true;
});
}
/**
* Gets context code for a node (line before, current line, line after)
* This is the same function signature as used in translations
* @param node The AST node
* @param sourceFile The source file
* @returns Object with before, line, and after text
*/
getContextCode(node, sourceFile) {
// Get the line number (0-based from TS API)
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
const fileLines = sourceFile.text.split("\n");
const beforeLine = line > 0 ? fileLines[line - 1].trim() : "";
const currentLine = fileLines[line].trim();
const afterLine = line + 1 < fileLines.length ? fileLines[line + 1].trim() : "";
return {
before: beforeLine,
line: currentLine,
after: afterLine,
};
}
}
exports.GeneralAnalyzer = GeneralAnalyzer;