Skip to content

Commit a6e243a

Browse files
refactor: automatically generate the rules-list in README (#397)
* refactor: automatically generate the rules-list in README * chore: update rule descriptions Co-authored-by: Mario Beltrán Alarcón <[email protected]>
1 parent 0c11511 commit a6e243a

15 files changed

+177
-54
lines changed

README.md

+37-30
Large diffs are not rendered by default.

lib/rules/await-async-utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
1818
meta: {
1919
type: 'problem',
2020
docs: {
21-
description: 'Enforce promises from async utils to be handled',
21+
description: 'Enforce promises from async utils to be awaited properly',
2222
category: 'Best Practices',
2323
recommendedConfig: {
2424
dom: 'error',

lib/rules/await-fire-event.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
1818
meta: {
1919
type: 'problem',
2020
docs: {
21-
description: 'Enforce promises from fire event methods to be handled',
21+
description: 'Enforce promises from `fireEvent` methods to be handled',
2222
category: 'Best Practices',
2323
recommendedConfig: {
2424
dom: false,

lib/rules/no-container.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
1919
meta: {
2020
type: 'problem',
2121
docs: {
22-
description: 'Disallow the use of container methods',
22+
description: 'Disallow the use of `container` methods',
2323
category: 'Best Practices',
2424
recommendedConfig: {
2525
dom: false,

lib/rules/no-wait-for-empty-callback.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
1717
type: 'suggestion',
1818
docs: {
1919
description:
20-
"It's preferred to avoid empty callbacks in `waitFor` and `waitForElementToBeRemoved`",
20+
'Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved`',
2121
category: 'Best Practices',
2222
recommendedConfig: {
2323
dom: 'error',

lib/rules/no-wait-for-multiple-assertions.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ export default createTestingLibraryRule<Options, MessageIds>({
1515
meta: {
1616
type: 'suggestion',
1717
docs: {
18-
description: "It's preferred to avoid multiple assertions in `waitFor`",
18+
description:
19+
'Disallow the use of multiple `expect` calls inside `waitFor`',
1920
category: 'Best Practices',
2021
recommendedConfig: {
2122
dom: false,

lib/rules/no-wait-for-side-effects.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
1919
meta: {
2020
type: 'suggestion',
2121
docs: {
22-
description: "It's preferred to avoid side effects in `waitFor`",
22+
description: 'Disallow the us of side effects in `waitFor`',
2323
category: 'Best Practices',
2424
recommendedConfig: {
2525
dom: false,

lib/rules/prefer-find-by.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
5555
type: 'suggestion',
5656
docs: {
5757
description:
58-
'Suggest using `find*` query instead of `waitFor` + `get*` to wait for elements',
58+
'Suggest using `find(All)By*` query instead of `waitFor` + `get(All)By*` to wait for elements',
5959
category: 'Best Practices',
6060
recommendedConfig: {
6161
dom: 'error',

lib/rules/prefer-presence-queries.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
1313
docs: {
1414
category: 'Best Practices',
1515
description:
16-
'Ensure appropriate get*/query* queries are used with their respective matchers',
16+
'Ensure appropriate `get*`/`query*` queries are used with their respective matchers',
1717
recommendedConfig: {
1818
dom: false,
1919
angular: false,

lib/rules/prefer-screen-queries.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
3939
meta: {
4040
type: 'suggestion',
4141
docs: {
42-
description: 'Suggest using screen while querying',
42+
description: 'Suggest using `screen` while querying',
4343
category: 'Best Practices',
4444
recommendedConfig: {
4545
dom: 'error',

lib/rules/prefer-user-event.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ export default createTestingLibraryRule<Options, MessageIds>({
6969
meta: {
7070
type: 'suggestion',
7171
docs: {
72-
description: 'Suggest using userEvent over fireEvent',
72+
description:
73+
'Suggest using `userEvent` over `fireEvent` for simulating user interactions',
7374
category: 'Best Practices',
7475
recommendedConfig: {
7576
dom: false,

lib/utils/types.ts

+12-14
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,18 @@ type RecommendedConfig<TOptions extends readonly unknown[]> =
66

77
// These 2 types are copied from @typescript-eslint/experimental-utils' CreateRuleMeta
88
// and modified to our needs
9-
type TestingLibraryRuleMetaDocs<TOptions extends readonly unknown[]> = Omit<
10-
TSESLint.RuleMetaDataDocs,
11-
'recommended' | 'url'
12-
> & {
13-
/**
14-
* The recommendation level for the rule on a framework basis.
15-
* Used by the build tools to generate the framework config.
16-
* Set to false to not include it the config
17-
*/
18-
recommendedConfig: Record<
19-
SupportedTestingFramework,
20-
RecommendedConfig<TOptions>
21-
>;
22-
};
9+
export type TestingLibraryRuleMetaDocs<TOptions extends readonly unknown[]> =
10+
Omit<TSESLint.RuleMetaDataDocs, 'recommended' | 'url'> & {
11+
/**
12+
* The recommendation level for the rule on a framework basis.
13+
* Used by the build tools to generate the framework config.
14+
* Set to false to not include it the config
15+
*/
16+
recommendedConfig: Record<
17+
SupportedTestingFramework,
18+
RecommendedConfig<TOptions>
19+
>;
20+
};
2321
export type TestingLibraryRuleMeta<
2422
TMessageIds extends string,
2523
TOptions extends readonly unknown[]

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"format": "prettier --write .",
3131
"format:check": "prettier --check .",
3232
"generate:configs": "ts-node tools/generate-configs",
33+
"generate:rules-list": "ts-node tools/generate-rules-list",
3334
"lint": "eslint . --max-warnings 0 --ext .js,.ts",
3435
"lint:fix": "npm run lint -- --fix",
3536
"test": "jest",
@@ -46,6 +47,7 @@
4647
"@commitlint/cli": "^12.1.4",
4748
"@commitlint/config-conventional": "^12.1.4",
4849
"@types/jest": "^26.0.23",
50+
"@types/node": "^10.17.60",
4951
"@typescript-eslint/eslint-plugin": "^4.26.1",
5052
"@typescript-eslint/parser": "^4.26.1",
5153
"cpy-cli": "^3.1.1",
@@ -64,6 +66,7 @@
6466
"prettier": "2.3.2",
6567
"semantic-release": "^17.4.3",
6668
"ts-jest": "^27.0.3",
69+
"ts-node": "^10.0.0",
6770
"typescript": "^4.3.2"
6871
},
6972
"peerDependencies": {

tools/generate-rules-list/index.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import rules from '../../lib/rules';
2+
import type { TestingLibraryRuleMetaDocs } from '../../lib/utils';
3+
4+
import { configBadges, emojiKey, RulesList, writeRulesList } from './utils';
5+
6+
export const createRuleLink = (ruleName: string): string =>
7+
`[\`testing-library/${ruleName}\`](./docs/rules/${ruleName}.md)`;
8+
9+
export const generateConfigBadges = (
10+
recommendedConfig: TestingLibraryRuleMetaDocs<unknown[]>['recommendedConfig']
11+
): string =>
12+
Object.entries(recommendedConfig)
13+
.filter(([_, config]) => Boolean(config))
14+
.map(([framework]) => configBadges[framework])
15+
.join(' ');
16+
17+
const rulesList: RulesList = Object.entries(rules)
18+
.sort(([ruleNameA], [ruleNameB]) => ruleNameA.localeCompare(ruleNameB))
19+
.map(([name, rule]) => [
20+
createRuleLink(name),
21+
rule.meta.docs.description,
22+
Boolean(rule.meta.fixable) ? emojiKey.fixable : '',
23+
generateConfigBadges(rule.meta.docs.recommendedConfig),
24+
]);
25+
26+
writeRulesList(rulesList);

tools/generate-rules-list/utils.ts

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { readFileSync, writeFileSync } from 'fs';
2+
import { resolve } from 'path';
3+
4+
import { format, resolveConfig } from 'prettier';
5+
6+
import {
7+
SUPPORTED_TESTING_FRAMEWORKS,
8+
SupportedTestingFramework,
9+
} from '../../lib/utils';
10+
11+
const prettierConfig = resolveConfig.sync(__dirname);
12+
const readmePath = resolve(__dirname, `../../README.md`);
13+
14+
export type RulesList = string[][];
15+
16+
export const configBadges = SUPPORTED_TESTING_FRAMEWORKS.reduce(
17+
(badges, framework) => ({
18+
...badges,
19+
[framework]: `![${framework}-badge][]`,
20+
}),
21+
{}
22+
) as Record<SupportedTestingFramework, string>;
23+
export const emojiKey = {
24+
fixable: '🔧',
25+
} as const;
26+
const staticElements = {
27+
listHeaderRow: [
28+
'Name',
29+
'Description',
30+
emojiKey.fixable,
31+
'Included in configurations',
32+
],
33+
listSpacerRow: Array(4).fill('-'),
34+
rulesListKey: [
35+
`**Key**: ${emojiKey.fixable} = fixable`,
36+
'',
37+
[
38+
`**Configurations**:`,
39+
Object.entries(configBadges)
40+
.map(([template, badge]) => `${badge} = ${template}`)
41+
.join(', '),
42+
].join(' '),
43+
].join('\n'),
44+
};
45+
46+
const generateRulesListTable = (rulesList: RulesList) =>
47+
[staticElements.listHeaderRow, staticElements.listSpacerRow, ...rulesList]
48+
.map((column) => `|${column.join('|')}|`)
49+
.join('\n');
50+
51+
const generateRulesListMarkdown = (rulesList: RulesList) =>
52+
[
53+
'',
54+
staticElements.rulesListKey,
55+
'',
56+
generateRulesListTable(rulesList),
57+
'',
58+
].join('\n');
59+
60+
const listBeginMarker = '<!-- RULES-LIST:START -->';
61+
const listEndMarker = '<!-- RULES-LIST:END -->';
62+
const overWriteRulesList = (rulesList: RulesList, readme: string) => {
63+
const listStartIndex = readme.indexOf(listBeginMarker);
64+
const listEndIndex = readme.indexOf(listEndMarker);
65+
66+
if ([listStartIndex, listEndIndex].includes(-1)) {
67+
throw new Error(`cannot find start or end rules-list`);
68+
}
69+
70+
return [
71+
readme.substring(0, listStartIndex - 1),
72+
listBeginMarker,
73+
'',
74+
generateRulesListMarkdown(rulesList),
75+
readme.substring(listEndIndex),
76+
].join('\n');
77+
};
78+
79+
export const writeRulesList = (rulesList: RulesList): void => {
80+
const readme = readFileSync(readmePath, 'utf8');
81+
const newReadme = format(overWriteRulesList(rulesList, readme), {
82+
parser: 'markdown',
83+
...prettierConfig,
84+
});
85+
86+
writeFileSync(readmePath, newReadme);
87+
};

0 commit comments

Comments
 (0)