UNPKG

stack-performance

Version:

A comprehensive application stack analyzer that evaluates MEAN, MERN, and other Node.js-based applications across 15 performance criteria

182 lines (159 loc) 5.62 kB
const fs = require('fs-extra'); const path = require('path'); const glob = require('glob'); /** * Base class for all criterion analyzers * Provides common functionality and enforces interface */ class BaseAnalyzer { constructor(stackInfo, projectPath, criterionName) { this.stackInfo = stackInfo; this.projectPath = projectPath; this.criterionName = criterionName; } /** * Main analysis method - must be implemented by subclasses * @returns {Object} Analysis result with score, remark, and details */ async analyze() { throw new Error('analyze() method must be implemented by subclass'); } /** * Convert numerical score to performance remark * @param {number} score - Score out of 100 * @returns {string} Performance remark */ getPerformanceRemark(score) { if (score >= 95) return 'Excellent'; if (score >= 85) return 'Best'; if (score >= 75) return 'Good'; if (score >= 60) return 'Average'; return 'Poor'; } /** * Check if a dependency exists in the project * @param {string} dependency - Dependency name * @returns {boolean} Whether dependency exists */ hasDependency(dependency) { const packageJson = this.stackInfo.packageJson; if (!packageJson) return false; const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies, ...packageJson.peerDependencies, ...packageJson.optionalDependencies }; return !!allDeps[dependency]; } /** * Get dependency version if it exists * @param {string} dependency - Dependency name * @returns {string|null} Version string or null */ getDependencyVersion(dependency) { const packageJson = this.stackInfo.packageJson; if (!packageJson) return null; const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies, ...packageJson.peerDependencies, ...packageJson.optionalDependencies }; return allDeps[dependency] || null; } /** * Check if file exists in project * @param {string} relativePath - Path relative to project root * @returns {Promise<boolean>} Whether file exists */ async fileExists(relativePath) { return await fs.pathExists(path.join(this.projectPath, relativePath)); } /** * Find files matching pattern * @param {string} pattern - Glob pattern * @returns {Array<string>} Matching file paths */ findFiles(pattern) { return glob.sync(pattern, { cwd: this.projectPath }); } /** * Read file content safely * @param {string} relativePath - Path relative to project root * @returns {Promise<string|null>} File content or null */ async readFile(relativePath) { try { return await fs.readFile(path.join(this.projectPath, relativePath), 'utf8'); } catch (error) { return null; } } /** * Calculate weighted score based on multiple factors * @param {Array<Object>} factors - Array of {score, weight} objects * @returns {number} Weighted average score */ calculateWeightedScore(factors) { const totalWeight = factors.reduce((sum, factor) => sum + factor.weight, 0); if (totalWeight === 0) return 0; const weightedSum = factors.reduce((sum, factor) => sum + (factor.score * factor.weight), 0); return Math.round(weightedSum / totalWeight); } /** * Analyze code complexity based on file patterns * @param {Array<string>} files - Array of file paths * @returns {Object} Complexity metrics */ async analyzeCodeComplexity(files) { const complexity = { totalFiles: files.length, totalLines: 0, avgLinesPerFile: 0, complexityScore: 0 }; for (const file of files) { const content = await this.readFile(file); if (content) { const lines = content.split('\n').length; complexity.totalLines += lines; // Basic complexity indicators const complexityIndicators = [ (content.match(/if\s*\(/g) || []).length, (content.match(/for\s*\(/g) || []).length, (content.match(/while\s*\(/g) || []).length, (content.match(/function\s+\w+/g) || []).length, (content.match(/=>\s*{/g) || []).length, (content.match(/try\s*{/g) || []).length, (content.match(/catch\s*\(/g) || []).length ]; const fileComplexity = complexityIndicators.reduce((sum, count) => sum + count, 0); complexity.complexityScore += fileComplexity / lines * 100; // Normalize by file size } } if (complexity.totalFiles > 0) { complexity.avgLinesPerFile = Math.round(complexity.totalLines / complexity.totalFiles); complexity.complexityScore = Math.round(complexity.complexityScore / complexity.totalFiles); } return complexity; } /** * Create standardized result object * @param {number} score - Score out of 100 * @param {Object} details - Detailed analysis results * @param {Array<string>} recommendations - Improvement recommendations * @returns {Object} Standardized result */ createResult(score, details = {}, recommendations = []) { return { name: this.criterionName, score: Math.max(0, Math.min(100, Math.round(score))), // Ensure score is 0-100 remark: this.getPerformanceRemark(score), details, recommendations, timestamp: new Date().toISOString() }; } } module.exports = BaseAnalyzer;