Skip to content

Commit

Permalink
feat(cli): enhance category filtering and validation logic
Browse files Browse the repository at this point in the history
  • Loading branch information
hanna-skryl committed Jan 7, 2025
1 parent 884f53c commit 1e29603
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 155 deletions.
69 changes: 33 additions & 36 deletions packages/cli/src/lib/implementation/filter.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,33 @@ export function filterMiddleware<T extends FilterOptions>(
verbose = false,
} = originalProcessArgs;

const plugins = processPlugins(rcPlugins);
const categories = filterSkippedCategories(rcCategories, plugins);

if (rcCategories && categories) {
validateFilteredCategories(rcCategories, categories, {
onlyCategories,
skipCategories,
verbose,
});
}

if (
skipCategories.length === 0 &&
onlyCategories.length === 0 &&
skipPlugins.length === 0 &&
onlyPlugins.length === 0
) {
return originalProcessArgs;
return {
...originalProcessArgs,
...(categories && { categories }),
plugins,
};
}

handleConflictingOptions('categories', onlyCategories, skipCategories);
handleConflictingOptions('plugins', onlyPlugins, skipPlugins);

const plugins = processPlugins(rcPlugins);
const categories = filterSkippedCategories(rcCategories, plugins);

if (rcCategories && categories && verbose) {
validateFilteredCategories(rcCategories, categories);
}

const filteredCategories = applyCategoryFilters(
{ categories, plugins },
skipCategories,
Expand Down Expand Up @@ -158,44 +166,33 @@ function filterPluginsFromCategories({

function filterSkippedItems<T extends { isSkipped?: boolean }>(
items: T[] | undefined,
): T[] {
return (items ?? []).filter(({ isSkipped }) => isSkipped !== true);
}

export function filterSkippedGroups(
groups: PluginConfig['groups'],
audits: PluginConfig['audits'],
): PluginConfig['groups'] {
if (!groups) {
return groups;
}
return filterItemRefsBy(groups, ref =>
audits.some(audit => audit.slug === ref.slug && audit.isSkipped !== true),
);
): Omit<T, 'isSkipped'>[] {
return (items ?? [])
.filter(({ isSkipped }) => isSkipped !== true)
.map(({ isSkipped, ...props }) => props);
}

export function processPlugins(plugins: PluginConfig[]): PluginConfig[] {
return plugins.map((plugin: PluginConfig) => ({
...plugin,
...(plugin.groups && {
groups: filterSkippedGroups(
filterSkippedItems(plugin.groups),
filterSkippedItems(plugin.audits),
),
}),
audits: filterSkippedItems(plugin.audits),
}));
return plugins.map((plugin: PluginConfig) => {
const filteredAudits = filterSkippedItems(plugin.audits);
return {
...plugin,
...(plugin.groups && {
groups: filterItemRefsBy(filterSkippedItems(plugin.groups), ref =>
filteredAudits.some(({ slug }) => slug === ref.slug),
),
}),
audits: filteredAudits,
};
});
}

export function filterSkippedCategories(
categories: CoreConfig['categories'],
plugins: CoreConfig['plugins'],
): CoreConfig['categories'] {
if (!categories || categories.length === 0) {
return categories;
}
return categories
.map(category => {
?.map(category => {
const validRefs = category.refs.filter(ref =>
isValidCategoryRef(ref, plugins),
);
Expand Down
108 changes: 10 additions & 98 deletions packages/cli/src/lib/implementation/filter.middleware.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { ui } from '@code-pushup/utils';
import {
filterMiddleware,
filterSkippedCategories,
filterSkippedGroups,
processPlugins,
} from './filter.middleware.js';
import { OptionValidationError } from './validate-filter-options.utils.js';
Expand Down Expand Up @@ -39,8 +38,8 @@ describe('filterMiddleware', () => {
{
slug: 'c1',
refs: [
{ plugin: 'p1', slug: 'a1-p1' },
{ plugin: 'p2', slug: 'a1-p2' },
{ type: 'audit', plugin: 'p1', slug: 'a1-p1' },
{ type: 'audit', plugin: 'p2', slug: 'a1-p2' },
],
},
] as CategoryConfig[];
Expand All @@ -63,8 +62,8 @@ describe('filterMiddleware', () => {
{
slug: 'c1',
refs: [
{ plugin: 'p1', slug: 'a1-p1' },
{ plugin: 'p2', slug: 'a1-p2' },
{ type: 'audit', plugin: 'p1', slug: 'a1-p1' },
{ type: 'audit', plugin: 'p2', slug: 'a1-p2' },
],
},
] as CategoryConfig[];
Expand Down Expand Up @@ -367,63 +366,6 @@ describe('filterMiddleware', () => {
});
});

describe('filterSkippedGroups', () => {
it('should return original input when groups are undefined', () => {
expect(
filterSkippedGroups(undefined, [
{ slug: 'a1', isSkipped: false },
{ slug: 'a2', isSkipped: true },
] as PluginConfig['audits']),
).toBeUndefined();
});

it('should filter out refs for skipped audits', () => {
expect(
filterSkippedGroups(
[
{
slug: 'g1',
refs: [
{ slug: 'a1', weight: 1 },
{ slug: 'a2', weight: 2 },
],
},
] as PluginConfig['groups'],
[
{ slug: 'a1', isSkipped: false },
{ slug: 'a2', isSkipped: true },
] as PluginConfig['audits'],
),
).toEqual([{ slug: 'g1', refs: [{ slug: 'a1', weight: 1 }] }]);
});

it('should return empty groups when all refs are skipped', () => {
expect(
filterSkippedGroups(
[
{
slug: 'g1',
refs: [{ slug: 'a1', weight: 1 }],
},
] as PluginConfig['groups'],
[{ slug: 'a1', isSkipped: true }] as PluginConfig['audits'],
),
).toEqual([]);
});

it('should return the same groups when no refs are skipped', () => {
const groups = [
{
slug: 'g1',
refs: [{ slug: 'a1', weight: 1 }],
},
] as PluginConfig['groups'];
const audits = [{ slug: 'a1', isSkipped: false }] as PluginConfig['audits'];

expect(filterSkippedGroups(groups, audits)).toEqual(groups);
});
});

describe('processPlugins', () => {
it('should filter out skipped audits and groups', () => {
expect(
Expand All @@ -449,12 +391,11 @@ describe('processPlugins', () => {
).toEqual([
{
slug: 'p1',
audits: [{ slug: 'a1', isSkipped: false }],
audits: [{ slug: 'a1' }],
groups: [
{
slug: 'g1',
refs: [{ slug: 'a1', weight: 1 }],
isSkipped: false,
},
],
},
Expand All @@ -479,7 +420,7 @@ describe('processPlugins', () => {
).toEqual([
{
slug: 'p1',
audits: [{ slug: 'a1', isSkipped: false }],
audits: [{ slug: 'a1' }],
groups: [],
},
]);
Expand All @@ -499,20 +440,7 @@ describe('filterSkippedCategories', () => {
[
{
slug: 'p1',
audits: [
{ slug: 'a1', isSkipped: false },
{ slug: 'a2', isSkipped: false },
],
groups: [
{
slug: 'g1',
refs: [
{ slug: 'a1', weight: 1 },
{ slug: 'a2', weight: 1 },
],
isSkipped: true,
},
],
audits: [{ slug: 'a1' }, { slug: 'a2' }],
},
] as PluginConfig[],
),
Expand All @@ -535,35 +463,19 @@ describe('filterSkippedCategories', () => {
[
{
slug: 'p1',
audits: [
{ slug: 'a1-p1', isSkipped: false },
{ slug: 'a2-p1', isSkipped: false },
],
groups: [
{
slug: 'g1-p1',
refs: [
{ slug: 'a1-p1', weight: 1 },
{ slug: 'a2-p1', weight: 1 },
],
isSkipped: true,
},
],
audits: [{ slug: 'a1-p1' }, { slug: 'a2-p1' }],
groups: [],
},
{
slug: 'p2',
audits: [
{ slug: 'a1-p2', isSkipped: false },
{ slug: 'a2-p2', isSkipped: false },
],
audits: [{ slug: 'a1-p2' }, { slug: 'a2-p2' }],
groups: [
{
slug: 'g1-p2',
refs: [
{ slug: 'a1-p2', weight: 1 },
{ slug: 'a2-p2', weight: 1 },
],
isSkipped: false,
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import {
pluralize,
ui,
} from '@code-pushup/utils';
import type { FilterOptionType, Filterables } from './filter.model.js';
import type {
FilterOptionType,
FilterOptions,
Filterables,
} from './filter.model.js';

export class OptionValidationError extends Error {}

Expand Down Expand Up @@ -62,40 +66,62 @@ export function validateFilterOption(
export function validateFilteredCategories(
originalCategories: NonNullable<Filterables['categories']>,
filteredCategories: NonNullable<Filterables['categories']>,
{
onlyCategories,
skipCategories,
verbose,
}: Pick<FilterOptions, 'onlyCategories' | 'skipCategories' | 'verbose'>,
): void {
originalCategories
.filter(
original =>
!filteredCategories.some(filtered => filtered.slug === original.slug),
)
.forEach(category => {
const skippedCategories = originalCategories.filter(
original => !filteredCategories.some(({ slug }) => slug === original.slug),
);
if (verbose) {
skippedCategories.forEach(category => {
ui().logger.info(
`Category ${category.slug} was removed because all its refs were skipped. Affected refs: ${category.refs
.map(ref => `${ref.slug} (${ref.type})`)
.join(', ')}`,
);
});
}
const invalidArgs = [
{ option: 'onlyCategories', args: onlyCategories ?? [] },
{ option: 'skipCategories', args: skipCategories ?? [] },
].filter(({ args }) =>
args.some(arg => skippedCategories.some(({ slug }) => slug === arg)),
);
if (invalidArgs.length > 0) {
throw new OptionValidationError(
invalidArgs
.map(
({ option, args }) =>
`The --${option} argument references skipped categories: ${args.join(', ')}`,
)
.join('. '),
);
}
if (filteredCategories.length === 0) {
throw new OptionValidationError(
`No categories remain after filtering. Removed categories: ${skippedCategories
.map(({ slug }) => slug)
.join(', ')}`,
);
}
}

export function isValidCategoryRef(
ref: CategoryRef,
plugins: Filterables['plugins'],
): boolean {
const plugin = plugins.find(p => p.slug === ref.plugin);
const plugin = plugins.find(({ slug }) => slug === ref.plugin);
if (!plugin) {
return false;
}
switch (ref.type) {
case 'audit':
return plugin.audits.some(
audit => audit.slug === ref.slug && audit.isSkipped !== true,
);
return plugin.audits.some(({ slug }) => slug === ref.slug);
case 'group':
return (
plugin.groups?.some(
group => group.slug === ref.slug && group.isSkipped !== true,
) ?? false
);
return plugin.groups?.some(({ slug }) => slug === ref.slug) ?? false;
}
}

Expand Down
Loading

0 comments on commit 1e29603

Please sign in to comment.