@notjustcoders/ioc-arise
Version:
Arise type-safe IoC containers from your code. Zero overhead, zero coupling.
159 lines • 8.3 kB
JavaScript
;
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