Skip to content

Commit 22a0754

Browse files
authored
Support auto-generated config lists (#480)
1 parent 9534838 commit 22a0754

File tree

7 files changed

+599
-12
lines changed

7 files changed

+599
-12
lines changed

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Automatic documentation generator for [ESLint](https://eslint.org/) plugins and
77
Generates the following documentation covering a [wide variety](#column-and-notice-types) of rule metadata:
88

99
- `README.md` rules table
10+
- `README.md` configs table
1011
- Rule doc titles and notices
1112

1213
Also performs [configurable](#configuration-options) section consistency checks on rule docs:
@@ -20,6 +21,7 @@ Also performs [configurable](#configuration-options) section consistency checks
2021
- [Usage](#usage)
2122
- [Examples](#examples)
2223
- [Rules list table](#rules-list-table)
24+
- [Configs list table](#configs-list-table)
2325
- [Rule doc notices](#rule-doc-notices)
2426
- [Users](#users)
2527
- [Configuration options](#configuration-options)
@@ -75,6 +77,13 @@ Delete any old rules list from your `README.md`. A new one will be automatically
7577
<!-- end auto-generated rules list -->
7678
```
7779

80+
Optionally, add these marker comments to your `README.md` where you would like the configs list to go (uses the `description` property exported by each config if available):
81+
82+
```md
83+
<!-- begin auto-generated configs list -->
84+
<!-- end auto-generated configs list -->
85+
```
86+
7887
Delete any old recommended/fixable/etc. notices from your rule docs. A new title and notices will be automatically added to the top of each rule doc (along with a marker comment if it doesn't already exist).
7988

8089
```md
@@ -102,6 +111,10 @@ For examples, see our [users](#users) or the in-house examples below. Note that
102111

103112
See the generated rules table and legend in our example [`README.md`](./docs/examples/eslint-plugin-test/README.md#rules).
104113

114+
### Configs list table
115+
116+
See the generated configs table in our example [`README.md`](./docs/examples/eslint-plugin-test/README.md#configs).
117+
105118
### Rule doc notices
106119

107120
See the generated rule doc title and notices in our example rule docs [`no-foo.md`](./docs/examples/eslint-plugin-test/docs/rules/no-foo.md), [`prefer-bar.md`](./docs/examples/eslint-plugin-test/docs/rules/prefer-bar.md), [`require-baz.md`](./docs/examples/eslint-plugin-test/docs/rules/require-baz.md).

docs/examples/eslint-plugin-test/README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@ This plugin is for x purpose.
44

55
## Configs
66

7-
Configs section would normally go here.
7+
<!-- begin auto-generated configs list -->
8+
9+
| | Name | Description |
10+
| :- | :------------ | :----------------------------------------------- |
11+
|| `recommended` | These rules are recommended for everyone. |
12+
| 🎨 | `stylistic` | These rules are more about code style than bugs. |
13+
| ⌨️ | `typescript` | These are good rules to use with TypeScript. |
14+
15+
<!-- end auto-generated configs list -->
816

917
## Rules
1018

lib/comment-markers.ts

+6
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,9 @@ export const END_RULE_LIST_MARKER = '<!-- end auto-generated rules list -->';
55

66
// Marker so that rule doc header (title/notices) can be automatically updated.
77
export const END_RULE_HEADER_MARKER = '<!-- end auto-generated rule header -->';
8+
9+
// Markers so that the configs table list can be automatically updated.
10+
export const BEGIN_CONFIG_LIST_MARKER =
11+
'<!-- begin auto-generated configs list -->';
12+
export const END_CONFIG_LIST_MARKER =
13+
'<!-- end auto-generated configs list -->';

lib/config-list.ts

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import {
2+
BEGIN_CONFIG_LIST_MARKER,
3+
END_CONFIG_LIST_MARKER,
4+
} from './comment-markers.js';
5+
import { markdownTable } from 'markdown-table';
6+
import type { ConfigsToRules, ConfigEmojis, Plugin } from './types.js';
7+
import { ConfigFormat, configNameToDisplay } from './config-format.js';
8+
9+
function generateConfigListMarkdown(
10+
plugin: Plugin,
11+
configsToRules: ConfigsToRules,
12+
pluginPrefix: string,
13+
configEmojis: ConfigEmojis,
14+
configFormat: ConfigFormat,
15+
ignoreConfig: readonly string[]
16+
): string {
17+
/* istanbul ignore next -- configs are sure to exist at this point */
18+
const configs = Object.values(plugin.configs || {});
19+
const hasDescription = configs.some(
20+
// @ts-expect-error -- description is not an official config property.
21+
(config) => config.description
22+
);
23+
const listHeaderRow = ['', 'Name'];
24+
if (hasDescription) {
25+
listHeaderRow.push('Description');
26+
}
27+
28+
return markdownTable(
29+
[
30+
listHeaderRow,
31+
...Object.keys(configsToRules)
32+
.filter((configName) => !ignoreConfig.includes(configName))
33+
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
34+
.map((configName) => {
35+
return [
36+
configEmojis.find((obj) => obj.config === configName)?.emoji || '',
37+
`\`${configNameToDisplay(
38+
configName,
39+
configFormat,
40+
pluginPrefix
41+
)}\``,
42+
hasDescription
43+
? // @ts-expect-error -- description is not an official config property.
44+
(plugin.configs?.[configName]?.description as
45+
| string
46+
| undefined) || ''
47+
: undefined,
48+
].filter((col) => col !== undefined);
49+
}),
50+
],
51+
{ align: 'l' } // Left-align headers.
52+
);
53+
}
54+
55+
export function updateConfigsList(
56+
markdown: string,
57+
plugin: Plugin,
58+
configsToRules: ConfigsToRules,
59+
pluginPrefix: string,
60+
configEmojis: ConfigEmojis,
61+
configFormat: ConfigFormat,
62+
ignoreConfig: readonly string[]
63+
): string {
64+
const listStartIndex = markdown.indexOf(BEGIN_CONFIG_LIST_MARKER);
65+
let listEndIndex = markdown.indexOf(END_CONFIG_LIST_MARKER);
66+
67+
if (listStartIndex === -1 || listEndIndex === -1) {
68+
// No config list found.
69+
return markdown;
70+
}
71+
72+
if (
73+
Object.keys(configsToRules).filter(
74+
(configName) => !ignoreConfig.includes(configName)
75+
).length === 0
76+
) {
77+
// No non-ignored configs found.
78+
return markdown;
79+
}
80+
81+
// Account for length of pre-existing marker.
82+
listEndIndex += END_CONFIG_LIST_MARKER.length;
83+
84+
const preList = markdown.slice(0, Math.max(0, listStartIndex));
85+
const postList = markdown.slice(Math.max(0, listEndIndex));
86+
87+
// New config list.
88+
const list = generateConfigListMarkdown(
89+
plugin,
90+
configsToRules,
91+
pluginPrefix,
92+
configEmojis,
93+
configFormat,
94+
ignoreConfig
95+
);
96+
97+
return `${preList}${BEGIN_CONFIG_LIST_MARKER}\n\n${list}\n\n${END_CONFIG_LIST_MARKER}${postList}`;
98+
}

lib/generator.ts

+20-11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
getPathWithExactFileNameCasing,
99
} from './package-json.js';
1010
import { updateRulesList } from './rule-list.js';
11+
import { updateConfigsList } from './config-list.js';
1112
import { generateRuleHeaderLines } from './rule-doc-notices.js';
1213
import {
1314
parseRuleDocNoticesOption,
@@ -260,22 +261,30 @@ export async function generate(path: string, options?: GenerateOptions) {
260261
// Update the rules list in this file.
261262
const fileContents = readFileSync(pathToFile, 'utf8');
262263
const fileContentsNew = await postprocess(
263-
updateRulesList(
264-
ruleNamesAndRules,
265-
fileContents,
264+
updateConfigsList(
265+
updateRulesList(
266+
ruleNamesAndRules,
267+
fileContents,
268+
plugin,
269+
configsToRules,
270+
pluginPrefix,
271+
pathRuleDoc,
272+
pathToFile,
273+
path,
274+
configEmojis,
275+
configFormat,
276+
ignoreConfig,
277+
ruleListColumns,
278+
ruleListSplit,
279+
urlConfigs,
280+
urlRuleDoc
281+
),
266282
plugin,
267283
configsToRules,
268284
pluginPrefix,
269-
pathRuleDoc,
270-
pathToFile,
271-
path,
272285
configEmojis,
273286
configFormat,
274-
ignoreConfig,
275-
ruleListColumns,
276-
ruleListSplit,
277-
urlConfigs,
278-
urlRuleDoc
287+
ignoreConfig
279288
),
280289
resolve(pathToFile)
281290
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`generate (configs list) basic generates the documentation 1`] = `
4+
"## Rules
5+
<!-- begin auto-generated rules list -->
6+
7+
| Name | Description |
8+
| :----------------------------- | :--------------------- |
9+
| [no-foo](docs/rules/no-foo.md) | Description of no-foo. |
10+
11+
<!-- end auto-generated rules list -->
12+
## Configs
13+
<!-- begin auto-generated configs list -->
14+
15+
| | Name |
16+
| :- | :------------ |
17+
| ✅ | \`recommended\` |
18+
19+
<!-- end auto-generated configs list -->"
20+
`;
21+
22+
exports[`generate (configs list) when a config exports a description generates the documentation 1`] = `
23+
"## Rules
24+
<!-- begin auto-generated rules list -->
25+
26+
| Name | Description |
27+
| :----------------------------- | :--------------------- |
28+
| [no-foo](docs/rules/no-foo.md) | Description of no-foo. |
29+
30+
<!-- end auto-generated rules list -->
31+
## Configs
32+
<!-- begin auto-generated configs list -->
33+
34+
| | Name | Description |
35+
| :- | :------------ | :--------------------------------------- |
36+
| | \`foo\` | |
37+
| ✅ | \`recommended\` | This config has the recommended rules... |
38+
39+
<!-- end auto-generated configs list -->"
40+
`;
41+
42+
exports[`generate (configs list) when all configs are ignored generates the documentation 1`] = `
43+
"## Rules
44+
<!-- begin auto-generated rules list -->
45+
46+
| Name | Description |
47+
| :----------------------------- | :--------------------- |
48+
| [no-foo](docs/rules/no-foo.md) | Description of no-foo. |
49+
50+
<!-- end auto-generated rules list -->
51+
## Configs
52+
<!-- begin auto-generated configs list -->
53+
<!-- end auto-generated configs list -->"
54+
`;
55+
56+
exports[`generate (configs list) when there are no configs generates the documentation 1`] = `
57+
"## Rules
58+
<!-- begin auto-generated rules list -->
59+
60+
| Name | Description |
61+
| :----------------------------- | :--------------------- |
62+
| [no-foo](docs/rules/no-foo.md) | Description of no-foo. |
63+
64+
<!-- end auto-generated rules list -->
65+
## Configs
66+
<!-- begin auto-generated configs list -->
67+
<!-- end auto-generated configs list -->"
68+
`;
69+
70+
exports[`generate (configs list) with --config-format generates the documentation 1`] = `
71+
"## Rules
72+
<!-- begin auto-generated rules list -->
73+
74+
| Name | Description |
75+
| :----------------------------- | :--------------------- |
76+
| [no-foo](docs/rules/no-foo.md) | Description of no-foo. |
77+
78+
<!-- end auto-generated rules list -->
79+
## Configs
80+
<!-- begin auto-generated configs list -->
81+
82+
| | Name |
83+
| :- | :----------------- |
84+
| ✅ | \`test/recommended\` |
85+
86+
<!-- end auto-generated configs list -->"
87+
`;
88+
89+
exports[`generate (configs list) with --ignore-config generates the documentation 1`] = `
90+
"## Rules
91+
<!-- begin auto-generated rules list -->
92+
93+
| Name | Description |
94+
| :----------------------------- | :--------------------- |
95+
| [no-foo](docs/rules/no-foo.md) | Description of no-foo. |
96+
97+
<!-- end auto-generated rules list -->
98+
## Configs
99+
<!-- begin auto-generated configs list -->
100+
101+
| | Name |
102+
| :- | :------------ |
103+
| ✅ | \`recommended\` |
104+
105+
<!-- end auto-generated configs list -->"
106+
`;
107+
108+
exports[`generate (configs list) with configs not defined in alphabetical order generates the documentation 1`] = `
109+
"## Rules
110+
<!-- begin auto-generated rules list -->
111+
112+
| Name | Description |
113+
| :----------------------------- | :--------------------- |
114+
| [no-foo](docs/rules/no-foo.md) | Description of no-foo. |
115+
116+
<!-- end auto-generated rules list -->
117+
## Configs
118+
<!-- begin auto-generated configs list -->
119+
120+
| | Name |
121+
| :- | :------------ |
122+
| | \`foo\` |
123+
| ✅ | \`recommended\` |
124+
125+
<!-- end auto-generated configs list -->"
126+
`;

0 commit comments

Comments
 (0)