Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 64 additions & 46 deletions src/features/boundaries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,30 +235,23 @@ interface EvaluateBoundariesOpts {
noTests?: boolean;
}

export function evaluateBoundaries(
db: BetterSqlite3Database,
boundaryConfig: BoundaryConfig | undefined,
opts: EvaluateBoundariesOpts = {},
): { violations: BoundaryViolation[]; violationCount: number } {
if (!boundaryConfig) return { violations: [], violationCount: 0 };

const { valid, errors } = validateBoundaryConfig(boundaryConfig);
if (!valid) {
throw new BoundaryError(`Invalid boundary configuration: ${errors.join('; ')}`);
}

const modules = resolveModules(boundaryConfig);
if (modules.size === 0) return { violations: [], violationCount: 0 };

let allRules: BoundaryRule[] = [];
if (boundaryConfig.preset) {
allRules = generatePresetRules(modules, boundaryConfig.preset);
}
function collectAllRules(
boundaryConfig: BoundaryConfig,
modules: Map<string, ResolvedModule>,
): BoundaryRule[] {
const rules: BoundaryRule[] = boundaryConfig.preset
? generatePresetRules(modules, boundaryConfig.preset)
: [];
if (boundaryConfig.rules && Array.isArray(boundaryConfig.rules)) {
allRules = allRules.concat(boundaryConfig.rules);
return rules.concat(boundaryConfig.rules);
}
if (allRules.length === 0) return { violations: [], violationCount: 0 };
return rules;
}

function loadImportEdges(
db: BetterSqlite3Database,
opts: EvaluateBoundariesOpts,
): Array<{ source: string; target: string }> {
let edges: Array<{ source: string; target: string }>;
try {
edges = db
Expand All @@ -281,38 +274,63 @@ export function evaluateBoundaries(
const scope = new Set(opts.scopeFiles);
edges = edges.filter((e) => scope.has(e.source));
}
return edges;
}

const violations: BoundaryViolation[] = [];
function ruleViolated(rule: BoundaryRule, toModule: string): boolean {
if (rule.notTo?.includes(toModule)) return true;
if (rule.onlyTo && !rule.onlyTo.includes(toModule)) return true;
return false;
}

for (const edge of edges) {
const fromModule = classifyFile(edge.source, modules);
const toModule = classifyFile(edge.target, modules);
function emitEdgeViolations(
edge: { source: string; target: string },
fromModule: string,
toModule: string,
allRules: BoundaryRule[],
violations: BoundaryViolation[],
): void {
for (const rule of allRules) {
if (rule.from !== fromModule) continue;
if (!ruleViolated(rule, toModule)) continue;
violations.push({
rule: 'boundaries',
name: `${fromModule} -> ${toModule}`,
file: edge.source,
targetFile: edge.target,
message: rule.message || `${fromModule} must not depend on ${toModule}`,
value: 1,
threshold: 0,
});
}
}

if (!fromModule || !toModule) continue;
export function evaluateBoundaries(
db: BetterSqlite3Database,
boundaryConfig: BoundaryConfig | undefined,
opts: EvaluateBoundariesOpts = {},
): { violations: BoundaryViolation[]; violationCount: number } {
if (!boundaryConfig) return { violations: [], violationCount: 0 };

for (const rule of allRules) {
if (rule.from !== fromModule) continue;
const { valid, errors } = validateBoundaryConfig(boundaryConfig);
if (!valid) {
throw new BoundaryError(`Invalid boundary configuration: ${errors.join('; ')}`);
}

let isViolation = false;
const modules = resolveModules(boundaryConfig);
if (modules.size === 0) return { violations: [], violationCount: 0 };

if (rule.notTo?.includes(toModule)) {
isViolation = true;
} else if (rule.onlyTo && !rule.onlyTo.includes(toModule)) {
isViolation = true;
}
const allRules = collectAllRules(boundaryConfig, modules);
if (allRules.length === 0) return { violations: [], violationCount: 0 };

if (isViolation) {
violations.push({
rule: 'boundaries',
name: `${fromModule} -> ${toModule}`,
file: edge.source,
targetFile: edge.target,
message: rule.message || `${fromModule} must not depend on ${toModule}`,
value: 1,
threshold: 0,
});
}
}
const edges = loadImportEdges(db, opts);
const violations: BoundaryViolation[] = [];

for (const edge of edges) {
const fromModule = classifyFile(edge.source, modules);
const toModule = classifyFile(edge.target, modules);
if (!fromModule || !toModule) continue;
emitEdgeViolations(edge, fromModule, toModule, allRules, violations);
}

return { violations, violationCount: violations.length };
Expand Down
Loading
Loading