UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

183 lines (182 loc) 7.76 kB
"use strict"; /** * 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;