@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
JavaScript
;
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