Skip to content

Commit

Permalink
feat(plugin-eslint): create groups from rules' meta.type (problem/sug…
Browse files Browse the repository at this point in the history
…gestion/layout)
  • Loading branch information
matejchalk committed Nov 6, 2023
1 parent 346596d commit 0350e49
Show file tree
Hide file tree
Showing 9 changed files with 541 additions and 14 deletions.

Large diffs are not rendered by default.

9 changes: 3 additions & 6 deletions packages/plugin-eslint/src/lib/eslint-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { fileURLToPath } from 'url';
import { PluginConfig } from '@code-pushup/models';
import { name, version } from '../../package.json';
import { ESLintPluginConfig, eslintPluginConfigSchema } from './config';
import { listAudits } from './meta';
import { listAuditsAndGroups } from './meta';
import { createRunnerConfig } from './runner';

/**
Expand Down Expand Up @@ -37,7 +37,7 @@ export async function eslintPlugin(
baseConfig: { extends: eslintrc },
});

const audits = await listAudits(eslint, patterns);
const { audits, groups } = await listAuditsAndGroups(eslint, patterns);

const runnerScriptPath = join(
fileURLToPath(dirname(import.meta.url)),
Expand All @@ -54,10 +54,7 @@ export async function eslintPlugin(
version,

audits,

// TODO: groups?
// - could be `problem`/`suggestion`/`layout` if based on `meta.type`
// - `meta.category` (deprecated, but still used by some) could also be a source of groups
groups,

runner: createRunnerConfig(runnerScriptPath, audits, eslintrc, patterns),
};
Expand Down
52 changes: 52 additions & 0 deletions packages/plugin-eslint/src/lib/meta/groups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { Rule } from 'eslint';
import type { AuditGroup, AuditGroupRef } from '@code-pushup/models';
import { objectToKeys } from '@code-pushup/utils';
import { ruleIdToSlug } from './hash';
import type { RuleData } from './rules';

type RuleType = NonNullable<Rule.RuleMetaData['type']>;

// docs on meta.type: https://eslint.org/docs/latest/extend/custom-rules#rule-structure
const typeGroups: Record<RuleType, Omit<AuditGroup, 'refs'>> = {
problem: {
slug: 'problems',
title: 'Problems',
description:
'Code that either will cause an error or may cause confusing behavior. Developers should consider this a high priority to resolve.',
},
suggestion: {
slug: 'suggestions',
title: 'Suggestions',
description:
"Something that could be done in a better way but no errors will occur if the code isn't changed.",
},
layout: {
slug: 'formatting',
title: 'Formatting',
description:
'Primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes.',
},
};

export function groupsFromRuleTypes(rules: RuleData[]): AuditGroup[] {
const allTypes = objectToKeys(typeGroups);

const auditSlugsMap = rules.reduce<Partial<Record<RuleType, string[]>>>(
(acc, { meta: { type }, ruleId, options }) =>
type == null
? acc
: {
...acc,
[type]: [...(acc[type] ?? []), ruleIdToSlug(ruleId, options)],
},
{},
);

return allTypes.map(type => ({
...typeGroups[type],
refs:
auditSlugsMap[type]?.map(
(slug): AuditGroupRef => ({ slug, weight: 1 }),
) ?? [],
}));
}
14 changes: 10 additions & 4 deletions packages/plugin-eslint/src/lib/meta/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import type { ESLint } from 'eslint';
import type { Audit } from '@code-pushup/models';
import type { Audit, AuditGroup } from '@code-pushup/models';
import { groupsFromRuleTypes } from './groups';
import { listRules } from './rules';
import { ruleToAudit } from './transform';

export async function listAudits(
export async function listAuditsAndGroups(
eslint: ESLint,
patterns: string | string[],
): Promise<Audit[]> {
): Promise<{ audits: Audit[]; groups: AuditGroup[] }> {
const rules = await listRules(eslint, patterns);
return rules.map(ruleToAudit);

const audits = rules.map(ruleToAudit);

const groups = groupsFromRuleTypes(rules);

return { audits, groups };
}
33 changes: 32 additions & 1 deletion packages/plugin-eslint/src/lib/meta/rules.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ESLint } from 'eslint';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import type { SpyInstance } from 'vitest';
import { RuleData, listRules } from './rules';
import { RuleData, listRules, parseRuleId } from './rules';

describe('listRules', () => {
const fixturesDir = join(
Expand Down Expand Up @@ -244,3 +244,34 @@ describe('listRules', () => {
});
});
});

describe('parseRuleId', () => {
it.each([
{
ruleId: 'prefer-const',
name: 'prefer-const',
},
{
ruleId: 'sonarjs/no-identical-functions',
plugin: 'sonarjs',
name: 'no-identical-functions',
},
{
ruleId: '@typescript-eslint/no-non-null-assertion',
plugin: '@typescript-eslint',
name: 'no-non-null-assertion',
},
{
ruleId: 'no-secrets/no-secrets',
plugin: 'no-secrets',
name: 'no-secrets',
},
{
ruleId: '@angular-eslint/template/no-negated-async',
plugin: '@angular-eslint/template',
name: 'no-negated-async',
},
])('$ruleId => name: $name, plugin: $plugin', ({ ruleId, name, plugin }) => {
expect(parseRuleId(ruleId)).toEqual({ name, plugin });
});
});
11 changes: 11 additions & 0 deletions packages/plugin-eslint/src/lib/meta/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,14 @@ function isRuleOff(entry: Linter.RuleEntry<unknown[]>): boolean {
return false;
}
}

export function parseRuleId(ruleId: string): { plugin?: string; name: string } {
const i = ruleId.lastIndexOf('/');
if (i < 0) {
return { name: ruleId };
}
return {
plugin: ruleId.slice(0, i),
name: ruleId.slice(i + 1),
};
}
4 changes: 2 additions & 2 deletions packages/plugin-eslint/src/lib/runner.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import type { SpyInstance } from 'vitest';
import { readJsonFile } from '@code-pushup/utils';
import { listAudits } from './meta';
import { listAuditsAndGroups } from './meta';
import {
RUNNER_OUTPUT_PATH,
createRunnerConfig,
Expand Down Expand Up @@ -36,7 +36,7 @@ describe('executeRunner', () => {
useEslintrc: false,
baseConfig: { extends: eslintrc },
});
const audits = await listAudits(eslint, patterns);
const { audits } = await listAuditsAndGroups(eslint, patterns);

const runnerConfig = createRunnerConfig(
'bin.js',
Expand Down
3 changes: 2 additions & 1 deletion packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ export {
executeProcess,
objectToCliArgs,
} from './lib/execute-process';
export { getProgressBar, ProgressBar } from './lib/progress';
export { git, latestHash } from './lib/git';
export { importEsmModule } from './lib/load-file';
export { ProgressBar, getProgressBar } from './lib/progress';
export {
CODE_PUSHUP_DOMAIN,
FOOTER_PREFIX,
Expand All @@ -27,6 +27,7 @@ export {
countOccurrences,
distinct,
objectToEntries,
objectToKeys,
pluralize,
readJsonFile,
readTextFile,
Expand Down
4 changes: 4 additions & 0 deletions packages/utils/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export function toArray<T>(val: T | T[]): T[] {
return Array.isArray(val) ? val : [val];
}

export function objectToKeys<T extends object>(obj: T) {
return Object.keys(obj) as (keyof T)[];
}

export function objectToEntries<T extends object>(obj: T) {
return Object.entries(obj) as [keyof T, T[keyof T]][];
}
Expand Down

0 comments on commit 0350e49

Please sign in to comment.