UNPKG

@samiyev/guardian

Version:

Research-backed code quality guardian for AI-assisted development. Detects hardcodes, secrets, circular deps, framework leaks, entity exposure, and 9 architecture violations. Enforces Clean Architecture/DDD principles. Works with GitHub Copilot, Cursor, W

312 lines 14.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExecuteDetection = void 0; const HardcodedValue_1 = require("../../../domain/value-objects/HardcodedValue"); const constants_1 = require("../../../shared/constants"); /** * Pipeline step responsible for running all detectors */ class ExecuteDetection { hardcodeDetector; namingConventionDetector; frameworkLeakDetector; entityExposureDetector; dependencyDirectionDetector; repositoryPatternDetector; aggregateBoundaryDetector; secretDetector; anemicModelDetector; duplicateValueTracker; // eslint-disable-next-line max-params constructor(hardcodeDetector, namingConventionDetector, frameworkLeakDetector, entityExposureDetector, dependencyDirectionDetector, repositoryPatternDetector, aggregateBoundaryDetector, secretDetector, anemicModelDetector, duplicateValueTracker) { this.hardcodeDetector = hardcodeDetector; this.namingConventionDetector = namingConventionDetector; this.frameworkLeakDetector = frameworkLeakDetector; this.entityExposureDetector = entityExposureDetector; this.dependencyDirectionDetector = dependencyDirectionDetector; this.repositoryPatternDetector = repositoryPatternDetector; this.aggregateBoundaryDetector = aggregateBoundaryDetector; this.secretDetector = secretDetector; this.anemicModelDetector = anemicModelDetector; this.duplicateValueTracker = duplicateValueTracker; } async execute(request) { const secretViolations = await this.detectSecrets(request.sourceFiles); return { violations: this.sortBySeverity(this.detectViolations(request.sourceFiles)), hardcodeViolations: this.sortBySeverity(this.detectHardcode(request.sourceFiles)), circularDependencyViolations: this.sortBySeverity(this.detectCircularDependencies(request.dependencyGraph)), namingViolations: this.sortBySeverity(this.detectNamingConventions(request.sourceFiles)), frameworkLeakViolations: this.sortBySeverity(this.detectFrameworkLeaks(request.sourceFiles)), entityExposureViolations: this.sortBySeverity(this.detectEntityExposures(request.sourceFiles)), dependencyDirectionViolations: this.sortBySeverity(this.detectDependencyDirections(request.sourceFiles)), repositoryPatternViolations: this.sortBySeverity(this.detectRepositoryPatternViolations(request.sourceFiles)), aggregateBoundaryViolations: this.sortBySeverity(this.detectAggregateBoundaryViolations(request.sourceFiles)), secretViolations: this.sortBySeverity(secretViolations), anemicModelViolations: this.sortBySeverity(this.detectAnemicModels(request.sourceFiles)), }; } detectViolations(sourceFiles) { const violations = []; const layerRules = { [constants_1.LAYERS.DOMAIN]: [constants_1.LAYERS.SHARED], [constants_1.LAYERS.APPLICATION]: [constants_1.LAYERS.DOMAIN, constants_1.LAYERS.SHARED], [constants_1.LAYERS.INFRASTRUCTURE]: [constants_1.LAYERS.DOMAIN, constants_1.LAYERS.APPLICATION, constants_1.LAYERS.SHARED], [constants_1.LAYERS.SHARED]: [], }; for (const file of sourceFiles) { if (!file.layer) { continue; } const allowedLayers = layerRules[file.layer]; for (const imp of file.imports) { const importedLayer = this.detectLayerFromImport(imp); if (importedLayer && importedLayer !== file.layer && !allowedLayers.includes(importedLayer)) { violations.push({ rule: constants_1.RULES.CLEAN_ARCHITECTURE, message: `Layer "${file.layer}" cannot import from "${importedLayer}"`, file: file.path.relative, severity: constants_1.VIOLATION_SEVERITY_MAP.ARCHITECTURE, }); } } } return violations; } detectLayerFromImport(importPath) { const layers = Object.values(constants_1.LAYERS); for (const layer of layers) { if (importPath.toLowerCase().includes(layer)) { return layer; } } return undefined; } detectHardcode(sourceFiles) { const allHardcodedValues = []; for (const file of sourceFiles) { const hardcodedValues = this.hardcodeDetector.detectAll(file.content, file.path.relative); for (const hardcoded of hardcodedValues) { allHardcodedValues.push({ value: hardcoded, file }); } } this.duplicateValueTracker.clear(); for (const { value, file } of allHardcodedValues) { this.duplicateValueTracker.track(value, file.path.relative); } const violations = []; for (const { value, file } of allHardcodedValues) { const duplicateLocations = this.duplicateValueTracker.getDuplicateLocations(value.value, value.type); const enrichedValue = duplicateLocations ? HardcodedValue_1.HardcodedValue.create(value.value, value.type, value.line, value.column, value.context, value.valueType, duplicateLocations.filter((loc) => loc.file !== file.path.relative)) : value; if (enrichedValue.shouldSkip(file.layer)) { continue; } violations.push({ rule: constants_1.RULES.HARDCODED_VALUE, type: enrichedValue.type, value: enrichedValue.value, file: file.path.relative, line: enrichedValue.line, column: enrichedValue.column, context: enrichedValue.context, suggestion: { constantName: enrichedValue.suggestConstantName(), location: enrichedValue.suggestLocation(file.layer), }, severity: constants_1.VIOLATION_SEVERITY_MAP.HARDCODE, }); } return violations; } detectCircularDependencies(dependencyGraph) { const violations = []; const cycles = dependencyGraph.findCycles(); for (const cycle of cycles) { const cycleChain = [...cycle, cycle[0]].join(" → "); violations.push({ rule: constants_1.RULES.CIRCULAR_DEPENDENCY, message: `Circular dependency detected: ${cycleChain}`, cycle, severity: constants_1.VIOLATION_SEVERITY_MAP.CIRCULAR_DEPENDENCY, }); } return violations; } detectNamingConventions(sourceFiles) { const violations = []; for (const file of sourceFiles) { const namingViolations = this.namingConventionDetector.detectViolations(file.content, file.path.filename, file.layer, file.path.relative); for (const violation of namingViolations) { violations.push({ rule: constants_1.RULES.NAMING_CONVENTION, type: violation.violationType, fileName: violation.fileName, layer: violation.layer, file: violation.filePath, expected: violation.expected, actual: violation.actual, message: violation.getMessage(), suggestion: violation.suggestion, severity: constants_1.VIOLATION_SEVERITY_MAP.NAMING_CONVENTION, }); } } return violations; } detectFrameworkLeaks(sourceFiles) { const violations = []; for (const file of sourceFiles) { const leaks = this.frameworkLeakDetector.detectLeaks(file.imports, file.path.relative, file.layer); for (const leak of leaks) { violations.push({ rule: constants_1.RULES.FRAMEWORK_LEAK, packageName: leak.packageName, category: leak.category, categoryDescription: leak.getCategoryDescription(), file: file.path.relative, layer: leak.layer, line: leak.line, message: leak.getMessage(), suggestion: leak.getSuggestion(), severity: constants_1.VIOLATION_SEVERITY_MAP.FRAMEWORK_LEAK, }); } } return violations; } detectEntityExposures(sourceFiles) { const violations = []; for (const file of sourceFiles) { const exposures = this.entityExposureDetector.detectExposures(file.content, file.path.relative, file.layer); for (const exposure of exposures) { violations.push({ rule: constants_1.RULES.ENTITY_EXPOSURE, entityName: exposure.entityName, returnType: exposure.returnType, file: file.path.relative, layer: exposure.layer, line: exposure.line, methodName: exposure.methodName, message: exposure.getMessage(), suggestion: exposure.getSuggestion(), severity: constants_1.VIOLATION_SEVERITY_MAP.ENTITY_EXPOSURE, }); } } return violations; } detectDependencyDirections(sourceFiles) { const violations = []; for (const file of sourceFiles) { const directionViolations = this.dependencyDirectionDetector.detectViolations(file.content, file.path.relative, file.layer); for (const violation of directionViolations) { violations.push({ rule: constants_1.RULES.DEPENDENCY_DIRECTION, fromLayer: violation.fromLayer, toLayer: violation.toLayer, importPath: violation.importPath, file: file.path.relative, line: violation.line, message: violation.getMessage(), suggestion: violation.getSuggestion(), severity: constants_1.VIOLATION_SEVERITY_MAP.DEPENDENCY_DIRECTION, }); } } return violations; } detectRepositoryPatternViolations(sourceFiles) { const violations = []; for (const file of sourceFiles) { const patternViolations = this.repositoryPatternDetector.detectViolations(file.content, file.path.relative, file.layer); for (const violation of patternViolations) { violations.push({ rule: constants_1.RULES.REPOSITORY_PATTERN, violationType: violation.violationType, file: file.path.relative, layer: violation.layer, line: violation.line, details: violation.details, message: violation.getMessage(), suggestion: violation.getSuggestion(), severity: constants_1.VIOLATION_SEVERITY_MAP.REPOSITORY_PATTERN, }); } } return violations; } detectAggregateBoundaryViolations(sourceFiles) { const violations = []; for (const file of sourceFiles) { const boundaryViolations = this.aggregateBoundaryDetector.detectViolations(file.content, file.path.relative, file.layer); for (const violation of boundaryViolations) { violations.push({ rule: constants_1.RULES.AGGREGATE_BOUNDARY, fromAggregate: violation.fromAggregate, toAggregate: violation.toAggregate, entityName: violation.entityName, importPath: violation.importPath, file: file.path.relative, line: violation.line, message: violation.getMessage(), suggestion: violation.getSuggestion(), severity: constants_1.VIOLATION_SEVERITY_MAP.AGGREGATE_BOUNDARY, }); } } return violations; } async detectSecrets(sourceFiles) { const violations = []; for (const file of sourceFiles) { const secretViolations = await this.secretDetector.detectAll(file.content, file.path.relative); for (const secret of secretViolations) { violations.push({ rule: constants_1.RULES.SECRET_EXPOSURE, secretType: secret.secretType, file: file.path.relative, line: secret.line, column: secret.column, message: secret.getMessage(), suggestion: secret.getSuggestion(), severity: "critical", }); } } return violations; } detectAnemicModels(sourceFiles) { const violations = []; for (const file of sourceFiles) { const anemicModels = this.anemicModelDetector.detectAnemicModels(file.content, file.path.relative, file.layer); for (const anemicModel of anemicModels) { violations.push({ rule: constants_1.RULES.ANEMIC_MODEL, className: anemicModel.className, file: file.path.relative, layer: anemicModel.layer, line: anemicModel.line, methodCount: anemicModel.methodCount, propertyCount: anemicModel.propertyCount, hasOnlyGettersSetters: anemicModel.hasOnlyGettersSetters, hasPublicSetters: anemicModel.hasPublicSetters, message: anemicModel.getMessage(), suggestion: anemicModel.getSuggestion(), severity: constants_1.VIOLATION_SEVERITY_MAP.ANEMIC_MODEL, }); } } return violations; } sortBySeverity(violations) { return violations.sort((a, b) => { return constants_1.SEVERITY_ORDER[a.severity] - constants_1.SEVERITY_ORDER[b.severity]; }); } } exports.ExecuteDetection = ExecuteDetection; //# sourceMappingURL=ExecuteDetection.js.map