sicua
Version:
A tool for analyzing project structure and dependencies
336 lines (335 loc) ⢠19.2 kB
JavaScript
"use strict";
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;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProjectAnalyzer = void 0;
// General Imports
const configManager_1 = require("./configManager");
const progressTracker_1 = require("./progressTracker");
const fs = __importStar(require("fs/promises"));
const typescript_1 = __importDefault(require("typescript"));
// Parsers Imports
const directoryScanner_1 = require("../parsers/directoryScanner");
const translationSourceScanner_1 = require("../parsers/translationSourceScanner");
const fileParser_1 = require("../parsers/fileParser");
// Utils & Types Imports
const analysisUtils_1 = require("../utils/common/analysisUtils");
const contentProcessor_1 = require("../utils/common/contentProcessor");
// Analyzers imports
const ComponentAnalyzer_1 = require("../analyzers/component/ComponentAnalyzer");
const FunctionAnalyzer_1 = require("../analyzers/function/FunctionAnalyzer");
const TypeAnalyzer_1 = require("../analyzers/type/TypeAnalyzer");
const ComplexityAnalyzer_1 = require("../analyzers/complexity/ComplexityAnalyzer");
const DeduplicationAnalyzer_1 = require("../analyzers/deduplication/DeduplicationAnalyzer");
const ErrorHandlingAnalyzer_1 = require("../analyzers/errorHandling/ErrorHandlingAnalyzer");
const SeoAnalyzer_1 = require("../analyzers/seo/SeoAnalyzer");
const TranslationAnalyzer_1 = require("../analyzers/translation/TranslationAnalyzer");
const SecurityAnalyzer_1 = require("../analyzers/security/SecurityAnalyzer");
// Graph generators
const graphGenerator_1 = require("../generators/graphGenerator");
const structureGraphGenerator_1 = require("../generators/structureGraphGenerator");
const ComponentFlowAnalyzer_1 = require("../analyzers/componentFlow/ComponentFlowAnalyzer");
const GeneralAnalyzer_1 = require("../analyzers/general/GeneralAnalyzer");
const ComponentScoringAnalyzer_1 = require("../analyzers/scoring/ComponentScoringAnalyzer");
const AccessibilityAnalyzer_1 = require("../analyzers/accessibility/AccessibilityAnalyzer");
const path_1 = __importDefault(require("path"));
const componentLookupService_1 = require("./componentLookupService");
const pathResolver_1 = require("../parsers/pathResolver");
class ProjectAnalyzer {
constructor(projectPath) {
this.scanResult = null;
this.typeChecker = null;
this.program = null;
this.config = new configManager_1.ConfigManager(projectPath);
this.contentProcessor = new contentProcessor_1.ContentProcessor();
this.progressTracker = new progressTracker_1.ProgressTracker([
"Loading configuration",
"š Detecting project structure...",
"š Scanning project directory...",
"š Parsing component files...",
"š Analyzing general metrics...",
"š Analyzing component dependencies...",
"ā” Analyzing functions...",
"šÆ Analyzing TypeScript types...",
"š§® Calculating complexity metrics...",
"šøļø Generating component dependency graph...",
"šļø Generating file structure graph...",
"š Detecting component duplications...",
"ā ļø Analyzing error handling patterns...",
"š Analyzing SEO implementation...",
"š Analyzing translation coverage...",
"š Analyzing component flow patterns...",
"š Analyzing accessibility compliance...",
"š Analyzing security vulnerabilities...",
"š Calculating component scores...",
"š¾ Writing analysis results...",
]);
}
getProjectPath() {
return this.config.projectPath;
}
async analyze() {
try {
// Load configuration with dynamic project structure detection
await this.config.loadConfig();
this.progressTracker.start();
this.progressTracker.incrementProgress(); // Loading configuration
// Validate configuration and show warnings if any
const warnings = this.config.validateConfig();
if (warnings.length > 0) {
console.warn("ā ļø Configuration warnings:");
warnings.forEach((warning) => console.warn(` ${warning}`));
}
this.progressTracker.incrementProgress(); // Detecting project structure
// Perform unified scanning of the project with enhanced structure detection
this.scanResult = await (0, directoryScanner_1.scanDirectory)(this.config.projectPath, this.config);
this.progressTracker.incrementProgress(); // Scanning project directory
// Initialize TypeScript program and type checker with dynamic paths
this.initializeTypeScriptProgram();
// Filter parseable files but keep all for security analysis
const parseableScanResult = this.createParseableScanResult();
// Parse components using the enhanced scan result
const components = await (0, fileParser_1.parseFiles)(parseableScanResult, this.config.srcDir, this.config);
this.progressTracker.incrementProgress(); // Parsing component files
// Initialize optimized services once for all analyzers
const componentLookupService = new componentLookupService_1.ComponentLookupService(components);
const pathResolver = new pathResolver_1.PathResolver(this.config, this.scanResult);
// General Analysis
const generalAnalyzer = new GeneralAnalyzer_1.GeneralAnalyzer(this.scanResult);
const generalAnalysis = await generalAnalyzer.analyze();
this.progressTracker.incrementProgress(); // Analyzing general metrics
// Dependency analysis with optimized services
const componentAnalyzer = new ComponentAnalyzer_1.ComponentAnalyzer(components, this.config, componentLookupService, pathResolver);
const advancedAnalysis = await componentAnalyzer.analyze();
this.progressTracker.incrementProgress(); // Analyzing component dependencies
// Function analysis
const functionAnalyzer = new FunctionAnalyzer_1.FunctionAnalyzer(components);
const functionAnalysis = await functionAnalyzer.analyze();
this.progressTracker.incrementProgress(); // Analyzing functions
// Type analysis using the enhanced type analyzer
const typeAnalyzer = new TypeAnalyzer_1.TypeAnalyzer(this.scanResult, this.typeChecker);
const typeAnalysis = await typeAnalyzer.analyze();
this.progressTracker.incrementProgress(); // Analyzing TypeScript types
// Complexity analysis
const complexityAnalyzer = new ComplexityAnalyzer_1.ComplexityAnalyzer(components, componentLookupService, pathResolver, this.scanResult);
const complexityAnalysis = await complexityAnalyzer.analyze();
this.progressTracker.incrementProgress(); // Calculating complexity metrics
// Graph generation for components
const componentGraphGenerator = (0, graphGenerator_1.generateGraphData)(components, this.config);
this.progressTracker.incrementProgress(); // Generating component dependency graph
// Graph generation for file structure with enhanced metadata
const structureGraphGenerator = (0, structureGraphGenerator_1.generateStructureGraphData)(this.scanResult, this.config);
this.progressTracker.incrementProgress(); // Generating file structure graph
// Ensure all component details are loaded
components.forEach((component) => componentGraphGenerator.loadComponentDetails(component.name));
// Deduplication analysis
const analyzer = new DeduplicationAnalyzer_1.DeduplicationAnalyzer(components, this.scanResult);
const similarities = await analyzer.analyzeComponents(components);
// Filter significant matches
const significantMatches = similarities.filter((s) => s.similarityScore > 0.7);
this.progressTracker.incrementProgress(); // Detecting component duplications
// Error handling analysis
const errorHandlingAnalyzer = new ErrorHandlingAnalyzer_1.ErrorHandlingAnalyzer(this.scanResult.filePaths, components);
const errorHandlingAnalysis = errorHandlingAnalyzer.analyze();
this.progressTracker.incrementProgress(); // Analyzing error handling patterns
// SEO analysis
const seoAnalyzer = new SeoAnalyzer_1.SEOAnalyzer(components);
const seoAnalysis = await seoAnalyzer.analyze();
this.progressTracker.incrementProgress(); // Analyzing SEO implementation
// Translation analysis
const translationSourceFiles = (0, translationSourceScanner_1.getTranslationSourceFiles)(this.scanResult);
const translationAnalyzer = new TranslationAnalyzer_1.TranslationAnalyzer(this.config.projectPath, translationSourceFiles);
const translationAnalysis = await translationAnalyzer.analyze();
this.progressTracker.incrementProgress(); // Analyzing translation coverage
// Component flow analysis with enhanced path handling
const componentFlowAnalyzer = new ComponentFlowAnalyzer_1.ComponentFlowAnalyzer(this.config.projectPath, this.config.srcDir, components, componentLookupService, pathResolver, this.scanResult);
const componentFlowAnalysis = await componentFlowAnalyzer.analyze();
this.progressTracker.incrementProgress(); // Analyzing component flow patterns
const accessibilityAnalyzer = new AccessibilityAnalyzer_1.AccessibilityAnalyzer(this.scanResult, components);
const accessibilityAnalysis = await accessibilityAnalyzer.analyze();
this.progressTracker.incrementProgress(); // Analyzing accessibility compliance
// Security analysis with comprehensive scan result
const securityAnalyzer = new SecurityAnalyzer_1.SecurityAnalyzer();
const securityAnalysis = await securityAnalyzer.analyze(this.scanResult, {
projectPath: this.config.projectPath,
sourcePath: this.config.srcDir,
filesToAnalyze: this.scanResult.filePaths,
});
this.progressTracker.incrementProgress(); // Analyzing security vulnerabilities
// Component scoring
const componentScoringAnalyzer = new ComponentScoringAnalyzer_1.ComponentScoringAnalyzer();
const topComponents = await componentScoringAnalyzer.calculateTopScoringComponents(components, {
generalAnalysis,
dependencyAnalysis: advancedAnalysis,
errorHandlingAnalysis,
complexityAnalysis,
typeAnalysis,
seoAnalysis,
translationAnalysis,
componentFlowAnalysis,
deduplicationAnalysis: significantMatches,
}, 20 // Top 20 components
);
this.progressTracker.incrementProgress(); // Calculating component scores
// Build the final analysis result
const analysisResult = {
generalAnalysis,
componentDependencyGraph: componentGraphGenerator.getSigmaData(),
fileStructureGraph: structureGraphGenerator.getStructureData(),
advancedAnalysis,
functionAnalysis,
typeAnalysis,
complexityAnalysis,
deduplicationAnalysis: significantMatches,
errorHandlingAnalysis,
seoAnalysis,
translationAnalysis,
componentFlowAnalysis,
accessibilityAnalysis,
securityAnalysis,
topScoringComponents: topComponents,
};
// Write output file
await fs.writeFile(this.config.outputFileName, JSON.stringify(analysisResult, analysisUtils_1.replacer, 2));
this.progressTracker.incrementProgress(); // Processing and writing output
this.progressTracker.complete();
// Show any configuration warnings again at the end
if (warnings.length > 0) {
console.log(`\nā ļø ${warnings.length} configuration warning(s) - see above for details`);
}
}
catch (error) {
console.error("\nā An error occurred during analysis:", error);
// Provide helpful error context
if (error instanceof Error) {
if (error.message.includes("ENOENT") ||
error.message.includes("does not exist")) {
console.error("š” This might be a path or file access issue. Please check:");
console.error(" - The project path exists and is accessible");
console.error(" - You have read permissions for the directory");
console.error(" - The detected source directory is correct");
}
if (error.message.includes("package.json")) {
console.error("š” Package.json issue detected. Please ensure:");
console.error(" - package.json exists in the project root");
console.error(" - The file contains valid JSON");
console.error(" - You're running the analyzer from the correct directory");
}
}
throw error;
}
}
/**
* Create a filtered scan result for parseable files while keeping security data intact
*/
createParseableScanResult() {
const parseableExtensions = [".ts", ".tsx", ".js", ".jsx"];
const parseableFiles = this.scanResult.filePaths.filter((filePath) => {
const ext = path_1.default.extname(filePath).toLowerCase();
return parseableExtensions.includes(ext);
});
return {
...this.scanResult,
filePaths: parseableFiles,
// Filter the sourceFiles and fileContents maps to parseable files only
sourceFiles: new Map(Array.from(this.scanResult.sourceFiles.entries()).filter(([filePath]) => {
const ext = path_1.default.extname(filePath).toLowerCase();
return parseableExtensions.includes(ext);
})),
fileContents: new Map(Array.from(this.scanResult.fileContents.entries()).filter(([filePath]) => {
const ext = path_1.default.extname(filePath).toLowerCase();
return parseableExtensions.includes(ext);
})),
fileMetadata: new Map(Array.from(this.scanResult.fileMetadata.entries()).filter(([filePath]) => {
const ext = path_1.default.extname(filePath).toLowerCase();
return parseableExtensions.includes(ext);
})),
};
}
/**
* Initialize TypeScript program and type checker for static analysis with enhanced path handling
*/
initializeTypeScriptProgram() {
if (!this.scanResult) {
throw new Error("Scan result is not available");
}
try {
// Look for tsconfig.json in project root or source directory
const possibleTsConfigPaths = [
path_1.default.join(this.config.projectPath, "tsconfig.json"),
path_1.default.join(this.config.srcDir, "tsconfig.json"),
];
let tsConfigPath;
let compilerOptions = {
target: typescript_1.default.ScriptTarget.Latest,
module: typescript_1.default.ModuleKind.ESNext,
jsx: typescript_1.default.JsxEmit.React,
allowJs: true,
esModuleInterop: true,
skipLibCheck: true,
noEmit: true,
incremental: false,
};
// Try to find and parse tsconfig.json
for (const configPath of possibleTsConfigPaths) {
try {
const configFile = typescript_1.default.readConfigFile(configPath, typescript_1.default.sys.readFile);
if (!configFile.error) {
tsConfigPath = configPath;
const parsedConfig = typescript_1.default.parseJsonConfigFileContent(configFile.config, typescript_1.default.sys, path_1.default.dirname(configPath));
if (!parsedConfig.errors.length) {
compilerOptions = { ...compilerOptions, ...parsedConfig.options };
break;
}
}
}
catch (error) {
// Continue to next possible config path
}
}
if (!tsConfigPath) {
console.log("š No tsconfig.json found, using default TypeScript configuration");
}
// Create program with all source files
this.program = typescript_1.default.createProgram(Array.from(this.scanResult.sourceFiles.keys()), compilerOptions);
this.typeChecker = this.program.getTypeChecker();
}
catch (error) {
console.warn("ā ļø Failed to initialize TypeScript program:", error);
console.log("š§ Continuing with basic analysis (some type features may be limited)");
// Create a minimal program as fallback
this.program = typescript_1.default.createProgram(Array.from(this.scanResult.sourceFiles.keys()), {
target: typescript_1.default.ScriptTarget.Latest,
module: typescript_1.default.ModuleKind.ESNext,
allowJs: true,
noEmit: true,
});
this.typeChecker = this.program.getTypeChecker();
}
}
}
exports.ProjectAnalyzer = ProjectAnalyzer;