Skip to content

Commit

Permalink
and for match-module-rule as well
Browse files Browse the repository at this point in the history
  • Loading branch information
sverweij committed Sep 20, 2024
1 parent bee3e9f commit b63a136
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 257 deletions.
132 changes: 132 additions & 0 deletions src/validate/match-module-rule-helpers.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import {
matchToModulePath,
matchToModulePathNot,
matchesFromPath,
matchesFromPathNot,
matchesModulePath,
matchesModulePathNot,
} from "./matchers.mjs";
import { extractGroups } from "#utl/regex-util.mjs";
/**
* Returns true if pRule is an orphan rule and pModule is an orphan.
* Returns false in all other cases
*
* @param {import("../../types/rule-set.mjs").IAnyRuleType} pRule
* @param {import("../../types/cruise-result.mjs").IModule} pModule
* @returns {boolean}
*/
export function matchesOrphanRule(pRule, pModule) {
return (
Object.hasOwn(pRule?.from ?? {}, "orphan") &&
// @ts-expect-error the 'hasOwn' above guarantees there's a 'from.orphan' attribute
pModule.orphan === pRule.from.orphan &&
matchesFromPath(pRule, pModule) &&
matchesFromPathNot(pRule, pModule)
);
}

/**
* Returns true if pRule is a 'reachable' rule and pModule matches the reachability
* criteria.
* Returns false in all other cases
*
* @param {import("../../types/rule-set.mjs").IAnyRuleType} pRule
* @param {import("../../types/cruise-result.mjs").IModule} pModule
* @returns {boolean}
*/
export function matchesReachableRule(pRule, pModule) {
if (
Object.hasOwn(pRule?.to ?? {}, "reachable") &&
Object.hasOwn(pModule, "reachable")
) {
// @ts-expect-error the 'hasOwn' above ensures the 'reachable' exists
const lReachableRecord = pModule.reachable.find(
(pReachable) =>
pReachable.asDefinedInRule === pRule.name &&
// @ts-expect-error the 'hasOwn' above ensures the 'to.reachable' exists
pReachable.value === pRule.to.reachable,
);
if (lReachableRecord) {
const lGroups = extractGroups(pRule.from, lReachableRecord.matchedFrom);

return (
matchToModulePath(pRule, pModule, lGroups) &&
matchToModulePathNot(pRule, pModule, lGroups)
);
}
}
return false;
}

/**
* Returns true if pRule is a 'reaches' rule and pModule matches the reachability
* criteria.
* Returns false in all other cases
*
* @param {import("../../types/rule-set.mjs").IAnyRuleType} pRule
* @param {import("../../types/cruise-result.mjs").IModule} pModule
* @returns {boolean}
*/
export function matchesReachesRule(pRule, pModule) {
return (
Object.hasOwn(pRule?.to ?? {}, "reachable") &&
Object.hasOwn(pModule, "reaches") &&
// @ts-expect-error the 'hasOwn' above guarantees the .reaches exists
pModule.reaches.some(
(pReaches) =>
pReaches.asDefinedInRule === pRule.name &&
pReaches.modules.some(
(pReachesModule) =>
matchToModulePath(pRule, pReachesModule) &&
matchToModulePathNot(pRule, pReachesModule),
),
)
);
}
/**
*
* @param {import("../../types/rule-set.mjs").IAnyRuleType} pRule
* @param {string[]} pDependents
* @returns {boolean}
*/
function dependentsCountsMatch(pRule, pDependents) {
const lMatchingDependentsCount = pDependents.filter(
(pDependent) =>
Boolean(!pRule.from.path || pDependent.match(pRule.from.path)) &&
Boolean(!pRule.from.pathNot || !pDependent.match(pRule.from.pathNot)),
).length;
return (
(!pRule.module.numberOfDependentsLessThan ||
lMatchingDependentsCount < pRule.module.numberOfDependentsLessThan) &&
(!pRule.module.numberOfDependentsMoreThan ||
lMatchingDependentsCount > pRule.module.numberOfDependentsMoreThan)
);
}

/**
*
* @param {import("../../types/rule-set.mjs").IAnyRuleType} pRule
* @param {import("../../types/cruise-result.mjs").IModule} pModule
* @returns {boolean}
*/
// eslint-disable-next-line complexity
export function matchesDependentsRule(pRule, pModule) {
if (
(Object.hasOwn(pModule, "dependents") &&
Object.hasOwn(pRule?.module ?? {}, "numberOfDependentsLessThan")) ||
Object.hasOwn(pRule?.module ?? {}, "numberOfDependentsMoreThan")
) {
return (
// group matching seems like a nice idea, however, the 'from' part of the
// rule is going to match not one module (as with regular dependency rules)
// but a whole bunch of them, being the 'dependents'. So that match is going
// to produce not one result, but one per matching dependent. To get meaningful
// results we'd probably have to loop over these and or the
// matchToModulePath together.
matchesModulePath(pRule, pModule) &&
matchesModulePathNot(pRule, pModule) &&
dependentsCountsMatch(pRule, pModule.dependents)
);
}
return false;
}
141 changes: 5 additions & 136 deletions src/validate/match-module-rule.mjs
Original file line number Diff line number Diff line change
@@ -1,137 +1,10 @@
import { isModuleOnlyRule, isFolderScope } from "./rule-classifiers.mjs";
import {
matchToModulePath,
matchToModulePathNot,
matchesFromPath,
matchesFromPathNot,
matchesModulePath,
matchesModulePathNot,
} from "./matchers.mjs";
import { extractGroups } from "#utl/regex-util.mjs";

/**
* Returns true if pRule is an orphan rule and pModule is an orphan.
* Returns false in all other cases
*
* @param {import("../../types/rule-set.mjs").IAnyRuleType} pRule
* @param {import("../../types/cruise-result.mjs").IModule} pModule
* @returns {boolean}
*/
function matchesOrphanRule(pRule, pModule) {
return (
Object.hasOwn(pRule?.from ?? {}, "orphan") &&
// @ts-expect-error the 'hasOwn' above guarantees there's a 'from.orphan' attribute
pModule.orphan === pRule.from.orphan &&
matchesFromPath(pRule, pModule) &&
matchesFromPathNot(pRule, pModule)
);
}

/**
* Returns true if pRule is a 'reachable' rule and pModule matches the reachability
* criteria.
* Returns false in all other cases
*
* @param {import("../../types/rule-set.mjs").IAnyRuleType} pRule
* @param {import("../../types/cruise-result.mjs").IModule} pModule
* @returns {boolean}
*/
function matchesReachableRule(pRule, pModule) {
if (
Object.hasOwn(pRule?.to ?? {}, "reachable") &&
Object.hasOwn(pModule, "reachable")
) {
// @ts-expect-error the 'hasOwn' above ensures the 'reachable' exists
const lReachableRecord = pModule.reachable.find(
(pReachable) =>
pReachable.asDefinedInRule === pRule.name &&
// @ts-expect-error the 'hasOwn' above ensures the 'to.reachable' exists
pReachable.value === pRule.to.reachable,
);
if (lReachableRecord) {
const lGroups = extractGroups(pRule.from, lReachableRecord.matchedFrom);

return (
matchToModulePath(pRule, pModule, lGroups) &&
matchToModulePathNot(pRule, pModule, lGroups)
);
}
}
return false;
}

/**
* Returns true if pRule is a 'reaches' rule and pModule matches the reachability
* criteria.
* Returns false in all other cases
*
* @param {import("../../types/rule-set.mjs").IAnyRuleType} pRule
* @param {import("../../types/cruise-result.mjs").IModule} pModule
* @returns {boolean}
*/
function matchesReachesRule(pRule, pModule) {
return (
Object.hasOwn(pRule?.to ?? {}, "reachable") &&
Object.hasOwn(pModule, "reaches") &&
// @ts-expect-error the 'hasOwn' above guarantees the .reaches exists
pModule.reaches.some(
(pReaches) =>
pReaches.asDefinedInRule === pRule.name &&
pReaches.modules.some(
(pReachesModule) =>
matchToModulePath(pRule, pReachesModule) &&
matchToModulePathNot(pRule, pReachesModule),
),
)
);
}
/**
*
* @param {import("../../types/rule-set.mjs").IAnyRuleType} pRule
* @param {string[]} pDependents
* @returns {boolean}
*/
function dependentsCountsMatch(pRule, pDependents) {
const lMatchingDependentsCount = pDependents.filter(
(pDependent) =>
Boolean(!pRule.from.path || pDependent.match(pRule.from.path)) &&
Boolean(!pRule.from.pathNot || !pDependent.match(pRule.from.pathNot)),
).length;
return (
(!pRule.module.numberOfDependentsLessThan ||
lMatchingDependentsCount < pRule.module.numberOfDependentsLessThan) &&
(!pRule.module.numberOfDependentsMoreThan ||
lMatchingDependentsCount > pRule.module.numberOfDependentsMoreThan)
);
}

/**
*
* @param {import("../../types/rule-set.mjs").IAnyRuleType} pRule
* @param {import("../../types/cruise-result.mjs").IModule} pModule
* @returns {boolean}
*/
// eslint-disable-next-line complexity
function matchesDependentsRule(pRule, pModule) {
if (
(Object.hasOwn(pModule, "dependents") &&
Object.hasOwn(pRule?.module ?? {}, "numberOfDependentsLessThan")) ||
Object.hasOwn(pRule?.module ?? {}, "numberOfDependentsMoreThan")
) {
return (
// group matching seems like a nice idea, however, the 'from' part of the
// rule is going to match not one module (as with regular dependency rules)
// but a whole bunch of them, being the 'dependents'. So that match is going
// to produce not one result, but one per matching dependent. To get meaningful
// results we'd probably have to loop over these and or the
// matchToModulePath together.
matchesModulePath(pRule, pModule) &&
matchesModulePathNot(pRule, pModule) &&
dependentsCountsMatch(pRule, pModule.dependents)
);
}
return false;
}
matchesOrphanRule,
matchesReachableRule,
matchesReachesRule,
matchesDependentsRule,
} from "./match-module-rule-helpers.mjs";

/**
*
Expand All @@ -155,10 +28,6 @@ const isInteresting = (pRule) =>
isModuleOnlyRule(pRule) && !isFolderScope(pRule);

export default {
matchesOrphanRule,
matchesReachableRule,
matchesReachesRule,
matchesDependentsRule,
match,
isInteresting,
};
20 changes: 10 additions & 10 deletions test/configs/no-orphans.spec.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { equal } from "node:assert/strict";
import noOrphansRule from "../../configs/rules/no-orphans.cjs";
import matchModuleRule from "#validate/match-module-rule.mjs";
import { matchesOrphanRule } from "#validate/match-module-rule-helpers.mjs";

describe("[I] configs/rules/no-orphans", () => {
it("flags non-excepted orphans as orphan rule transgression", () => {
equal(
matchModuleRule.matchesOrphanRule(noOrphansRule, {
matchesOrphanRule(noOrphansRule, {
source: "Rémi.js",
orphan: true,
}),
Expand All @@ -15,7 +15,7 @@ describe("[I] configs/rules/no-orphans", () => {

it("flags files ending on a dotfile as orphan rule transgression", () => {
equal(
matchModuleRule.matchesOrphanRule(noOrphansRule, {
matchesOrphanRule(noOrphansRule, {
source: "looks-like-a-dot-sorta.Rémi.js",
orphan: true,
}),
Expand All @@ -25,7 +25,7 @@ describe("[I] configs/rules/no-orphans", () => {

it("does not flag dot files as orphan rule transgressions", () => {
equal(
matchModuleRule.matchesOrphanRule(noOrphansRule, {
matchesOrphanRule(noOrphansRule, {
source: ".Rémi.js",
orphan: true,
}),
Expand All @@ -35,7 +35,7 @@ describe("[I] configs/rules/no-orphans", () => {

it("does not flag dot files in the tree as orphan rule transgressions", () => {
equal(
matchModuleRule.matchesOrphanRule(noOrphansRule, {
matchesOrphanRule(noOrphansRule, {
source: "packages/thing/.Rémi.js",
orphan: true,
}),
Expand All @@ -45,7 +45,7 @@ describe("[I] configs/rules/no-orphans", () => {

it("does not flag dot files in the tree as orphan rule transgressions, regardless extension", () => {
equal(
matchModuleRule.matchesOrphanRule(noOrphansRule, {
matchesOrphanRule(noOrphansRule, {
source: "packages/thing/.Rémi.ts",
orphan: true,
}),
Expand All @@ -55,14 +55,14 @@ describe("[I] configs/rules/no-orphans", () => {

it("does not flag any .d.ts not as orphan rule transgressions", () => {
equal(
matchModuleRule.matchesOrphanRule(noOrphansRule, {
matchesOrphanRule(noOrphansRule, {
source: "packages/thing/types/lalalal.d.ts",
orphan: true,
}),
false,
);
equal(
matchModuleRule.matchesOrphanRule(noOrphansRule, {
matchesOrphanRule(noOrphansRule, {
source: "lalalal.d.ts",
orphan: true,
}),
Expand All @@ -72,7 +72,7 @@ describe("[I] configs/rules/no-orphans", () => {

it("does not flag babel config files in the tree not as orphan rule transgressions", () => {
equal(
matchModuleRule.matchesOrphanRule(noOrphansRule, {
matchesOrphanRule(noOrphansRule, {
source: "packages/thing/babel.config.mjs",
orphan: true,
}),
Expand All @@ -82,7 +82,7 @@ describe("[I] configs/rules/no-orphans", () => {

it("does not flag babel config files as orphan rule transgressions", () => {
equal(
matchModuleRule.matchesOrphanRule(noOrphansRule, {
matchesOrphanRule(noOrphansRule, {
source: "babel.config.mjs",
orphan: true,
}),
Expand Down
Loading

0 comments on commit b63a136

Please sign in to comment.