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

151 lines 6.03 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DependencyDirectionDetector = void 0; const DependencyViolation_1 = require("../../domain/value-objects/DependencyViolation"); const rules_1 = require("../../shared/constants/rules"); const paths_1 = require("../constants/paths"); /** * Detects dependency direction violations between architectural layers * * This detector enforces Clean Architecture dependency rules: * - Domain → should not import from Application or Infrastructure * - Application → should not import from Infrastructure * - Infrastructure → can import from Application and Domain (allowed) * - Shared → can be imported by all layers (allowed) * * @example * ```typescript * const detector = new DependencyDirectionDetector() * * // Detect violations in domain file * const code = ` * import { PrismaClient } from '@prisma/client' * import { UserDto } from '../application/dtos/UserDto' * ` * const violations = detector.detectViolations(code, 'src/domain/entities/User.ts', 'domain') * * // violations will contain 1 violation for domain importing from application * console.log(violations.length) // 1 * console.log(violations[0].getMessage()) * // "Domain layer should not import from Application layer" * ``` */ class DependencyDirectionDetector { dependencyRules; constructor() { this.dependencyRules = new Map([ [rules_1.LAYERS.DOMAIN, new Set([rules_1.LAYERS.DOMAIN, rules_1.LAYERS.SHARED])], [rules_1.LAYERS.APPLICATION, new Set([rules_1.LAYERS.DOMAIN, rules_1.LAYERS.APPLICATION, rules_1.LAYERS.SHARED])], [ rules_1.LAYERS.INFRASTRUCTURE, new Set([rules_1.LAYERS.DOMAIN, rules_1.LAYERS.APPLICATION, rules_1.LAYERS.INFRASTRUCTURE, rules_1.LAYERS.SHARED]), ], [ rules_1.LAYERS.SHARED, new Set([rules_1.LAYERS.DOMAIN, rules_1.LAYERS.APPLICATION, rules_1.LAYERS.INFRASTRUCTURE, rules_1.LAYERS.SHARED]), ], ]); } /** * Detects dependency direction violations in the given code * * Analyzes import statements to identify violations of dependency rules * between architectural layers. * * @param code - Source code to analyze * @param filePath - Path to the file being analyzed * @param layer - The architectural layer of the file (domain, application, infrastructure, shared) * @returns Array of detected dependency direction violations */ detectViolations(code, filePath, layer) { if (!layer || layer === rules_1.LAYERS.SHARED) { return []; } const violations = []; const lines = code.split("\n"); for (let i = 0; i < lines.length; i++) { const line = lines[i]; const lineNumber = i + 1; const imports = this.extractImports(line); for (const importPath of imports) { const targetLayer = this.extractLayerFromImport(importPath); if (targetLayer && this.isViolation(layer, targetLayer)) { violations.push(DependencyViolation_1.DependencyViolation.create(layer, targetLayer, importPath, filePath, lineNumber)); } } } return violations; } /** * Checks if an import violates dependency direction rules * * @param fromLayer - The layer that is importing * @param toLayer - The layer being imported * @returns True if the import violates dependency rules */ isViolation(fromLayer, toLayer) { const allowedDependencies = this.dependencyRules.get(fromLayer); if (!allowedDependencies) { return false; } return !allowedDependencies.has(toLayer); } /** * Extracts the layer from an import path * * @param importPath - The import path to analyze * @returns The layer name if detected, undefined otherwise */ extractLayerFromImport(importPath) { const normalizedPath = importPath.replace(paths_1.IMPORT_PATTERNS.QUOTE, "").toLowerCase(); const layerPatterns = [ [rules_1.LAYERS.DOMAIN, paths_1.LAYER_PATHS.DOMAIN], [rules_1.LAYERS.APPLICATION, paths_1.LAYER_PATHS.APPLICATION], [rules_1.LAYERS.INFRASTRUCTURE, paths_1.LAYER_PATHS.INFRASTRUCTURE], [rules_1.LAYERS.SHARED, paths_1.LAYER_PATHS.SHARED], ]; for (const [layer, pattern] of layerPatterns) { if (this.containsLayerPattern(normalizedPath, pattern)) { return layer; } } return undefined; } /** * Checks if the normalized path contains the layer pattern */ containsLayerPattern(normalizedPath, pattern) { return (normalizedPath.includes(pattern) || normalizedPath.includes(`.${pattern}`) || normalizedPath.includes(`..${pattern}`) || normalizedPath.includes(`...${pattern}`)); } /** * Extracts import paths from a line of code * * Handles various import statement formats: * - import { X } from 'path' * - import X from 'path' * - import * as X from 'path' * - const X = require('path') * * @param line - A line of code to analyze * @returns Array of import paths found in the line */ extractImports(line) { const imports = []; let match = paths_1.IMPORT_PATTERNS.ES_IMPORT.exec(line); while (match) { imports.push(match[1]); match = paths_1.IMPORT_PATTERNS.ES_IMPORT.exec(line); } match = paths_1.IMPORT_PATTERNS.REQUIRE.exec(line); while (match) { imports.push(match[1]); match = paths_1.IMPORT_PATTERNS.REQUIRE.exec(line); } return imports; } } exports.DependencyDirectionDetector = DependencyDirectionDetector; //# sourceMappingURL=DependencyDirectionDetector.js.map