UNPKG

@notjustcoders/ioc-arise

Version:

Arise type-safe IoC containers from your code. Zero overhead, zero coupling.

159 lines 8.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProjectAnalyzer = void 0; const class_analyzer_1 = require("./class-analyzer"); const factory_analyzer_1 = require("./factory-analyzer"); const value_analyzer_1 = require("./value-analyzer"); const file_discovery_1 = require("./file-discovery"); const errorFactory_1 = require("../errors/errorFactory"); const IoCError_1 = require("../errors/IoCError"); class ProjectAnalyzer { constructor(options) { this.fileDiscovery = new file_discovery_1.FileDiscovery(options.sourceDir, options.excludePatterns); this.classAnalyzer = new class_analyzer_1.ClassAnalyzer(options.sourceDir, options.interfacePattern); this.factoryAnalyzer = new factory_analyzer_1.FactoryAnalyzer(options.sourceDir, options.factoryPattern); this.valueAnalyzer = new value_analyzer_1.ValueAnalyzer(options.sourceDir, options.valuePattern); } async analyzeProject() { const tsFiles = await this.fileDiscovery.findTypeScriptFiles(); // First pass: Collect all tokens from all files const classTokens = await this.classAnalyzer.collectTokens(tsFiles); const factoryTokens = await this.factoryAnalyzer.collectTokens(tsFiles); const valueTokens = await this.valueAnalyzer.collectTokens(tsFiles); // Merge all interfaces from all analyzers const allInterfaces = new Set([ ...classTokens.interfaces, ...factoryTokens.interfaces, ...valueTokens.interfaces ]); const tokens = { interfaces: allInterfaces, classNames: classTokens.classNames, abstractClasses: classTokens.abstractClasses, factoryNames: factoryTokens.factoryNames, valueNames: valueTokens.valueNames }; // Second pass: Perform full analysis using collected tokens const allClasses = await this.classAnalyzer.analyzeFiles(tsFiles, tokens); const allFactories = await this.factoryAnalyzer.analyzeFiles(tsFiles, tokens); const allValues = await this.valueAnalyzer.analyzeFiles(tsFiles, { interfaces: allInterfaces }); // Validate that no interface is implemented by multiple classes this.validateUniqueInterfaceImplementations(allClasses); // Validate that no abstract class is extended by multiple classes this.validateUniqueAbstractClassExtensions(allClasses); // Validate that no interface has both a class implementation and an instance factory, // or multiple instance factories this.validateInstanceFactoryConflicts(allClasses, allFactories); return { classes: allClasses, factories: allFactories, values: allValues }; } validateUniqueInterfaceImplementations(classes) { const interfaceToClassMap = new Map(); // Group classes by their interface names for (const classInfo of classes) { if (classInfo.interfaceName) { if (!interfaceToClassMap.has(classInfo.interfaceName)) { interfaceToClassMap.set(classInfo.interfaceName, []); } interfaceToClassMap.get(classInfo.interfaceName).push(classInfo); } } // Check for duplicate implementations const duplicateInterfaces = []; for (const [interfaceName, implementingClasses] of interfaceToClassMap) { if (implementingClasses.length > 1) { duplicateInterfaces.push(interfaceName); const error = errorFactory_1.ErrorFactory.duplicateInterfaceImplementation(interfaceName, implementingClasses.map(c => c.name)); const { Logger } = require('../utils/logger'); Logger.error(IoCError_1.ErrorUtils.formatForConsole(error)); for (const classInfo of implementingClasses) { Logger.log(` • ${classInfo.name} (${classInfo.filePath})`); } } } if (duplicateInterfaces.length > 0) { const allClassNames = []; for (const [interfaceName, implementingClasses] of interfaceToClassMap) { if (implementingClasses.length > 1) { allClassNames.push(...implementingClasses.map(c => c.name)); } } throw errorFactory_1.ErrorFactory.duplicateInterfaceImplementation(duplicateInterfaces.join(', '), allClassNames); } } validateInstanceFactoryConflicts(classes, factories) { const interfaceToImplementors = new Map(); // Collect class implementations for (const cls of classes) { if (cls.interfaceName) { if (!interfaceToImplementors.has(cls.interfaceName)) { interfaceToImplementors.set(cls.interfaceName, []); } interfaceToImplementors.get(cls.interfaceName).push(`class:${cls.name}`); } } // Collect instance factory implementations for (const factory of factories) { if (factory.instanceFactoryFor) { if (!interfaceToImplementors.has(factory.instanceFactoryFor)) { interfaceToImplementors.set(factory.instanceFactoryFor, []); } interfaceToImplementors.get(factory.instanceFactoryFor).push(`factory:${factory.name}`); } } // Check for conflicts: more than one implementation provider for an interface const conflicts = []; for (const [interfaceName, implementors] of interfaceToImplementors.entries()) { if (implementors.length > 1) { conflicts.push(interfaceName); const { Logger } = require('../utils/logger'); Logger.error(`Multiple implementation providers found for interface '${interfaceName}':`); for (const impl of implementors) { Logger.log(` • ${impl}`); } } } if (conflicts.length > 0) { const allNames = []; for (const interfaceName of conflicts) { allNames.push(...(interfaceToImplementors.get(interfaceName) ?? [])); } throw errorFactory_1.ErrorFactory.duplicateInterfaceImplementation(conflicts.join(', '), allNames); } } validateUniqueAbstractClassExtensions(classes) { const abstractClassToClassMap = new Map(); // Group classes by their abstract class names for (const classInfo of classes) { if (classInfo.abstractClassName) { if (!abstractClassToClassMap.has(classInfo.abstractClassName)) { abstractClassToClassMap.set(classInfo.abstractClassName, []); } abstractClassToClassMap.get(classInfo.abstractClassName).push(classInfo); } } // Check for duplicate extensions const duplicateAbstractClasses = []; for (const [abstractClassName, extendingClasses] of abstractClassToClassMap) { if (extendingClasses.length > 1) { duplicateAbstractClasses.push(abstractClassName); const error = errorFactory_1.ErrorFactory.duplicateAbstractClassExtension(abstractClassName, extendingClasses.map(c => c.name)); const { Logger } = require('../utils/logger'); Logger.error(`Multiple classes extending abstract class: ${abstractClassName}`); for (const classInfo of extendingClasses) { Logger.log(` • ${classInfo.name} (${classInfo.filePath})`); } } } if (duplicateAbstractClasses.length > 0) { const allClassNames = []; for (const [abstractClassName, extendingClasses] of abstractClassToClassMap) { if (extendingClasses.length > 1) { allClassNames.push(...extendingClasses.map(c => c.name)); } } throw errorFactory_1.ErrorFactory.duplicateAbstractClassExtension(duplicateAbstractClasses.join(', '), allClassNames); } } } exports.ProjectAnalyzer = ProjectAnalyzer; //# sourceMappingURL=project-analyzer.js.map