Skip to content

Commit 455878b

Browse files
authored
Merge pull request #139 from bmish/rule-doc-notices
2 parents a45808b + 11d013b commit 455878b

File tree

11 files changed

+320
-82
lines changed

11 files changed

+320
-82
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ And how it looks:
153153
| `--config-emoji` | Custom emoji to use for a config. Format is `config-name,emoji`. Default emojis are provided for [common configs](./lib/emojis.ts). To remove a default emoji and rely on a [badge](#badge) instead, provide the config name without an emoji. Option can be repeated. |
154154
| `--ignore-config` | Config to ignore from being displayed. Often used for an `all` config. Option can be repeated. |
155155
| `--ignore-deprecated-rules` | Whether to ignore deprecated rules from being checked, displayed, or updated (default: `false`). |
156+
| `--rule-doc-notices` | Ordered, comma-separated list of notices to display in rule doc. Non-applicable notices will be hidden. Choices: `configs`, `deprecated`, `fixable`, `hasSuggestions`, `requiresTypeChecking`, `type` (off by default). Default: `deprecated,configs,fixable,hasSuggestions,requiresTypeChecking`. |
156157
| `--rule-doc-section-exclude` | Disallowed section in each rule doc. Exit with failure if present. Option can be repeated. |
157158
| `--rule-doc-section-include` | Required section in each rule doc. Exit with failure if missing. Option can be repeated. |
158159
| `--rule-doc-title-format` | The format to use for rule doc titles. Defaults to `desc-parens-prefix-name`. See choices in below [table](#--rule-doc-title-format). |

lib/cli.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ import {
88
RULE_DOC_TITLE_FORMAT_DEFAULT,
99
RULE_DOC_TITLE_FORMATS,
1010
} from './rule-doc-title-format.js';
11-
import {
12-
COLUMN_TYPE,
13-
COLUMN_TYPE_DEFAULT_PRESENCE_AND_ORDERING,
14-
} from './rule-list-columns.js';
11+
import { COLUMN_TYPE_DEFAULT_PRESENCE_AND_ORDERING } from './rule-list-columns.js';
12+
import { NOTICE_TYPE_DEFAULT_PRESENCE_AND_ORDERING } from './rule-notices.js';
13+
import { COLUMN_TYPE, NOTICE_TYPE } from './types.js';
1514
import type { PackageJson } from 'type-fest';
1615

1716
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -61,6 +60,17 @@ export function run() {
6160
'(optional) Whether to ignore deprecated rules from being checked, displayed, or updated.',
6261
false
6362
)
63+
.option(
64+
'--rule-doc-notices <notices>',
65+
`(optional) Ordered, comma-separated list of notices to display in rule doc. Non-applicable notices will be hidden. (choices: "${Object.values(
66+
NOTICE_TYPE
67+
).join('", "')}")`,
68+
// List of default enabled notices.
69+
Object.entries(NOTICE_TYPE_DEFAULT_PRESENCE_AND_ORDERING)
70+
.filter(([_col, enabled]) => enabled)
71+
.map(([col]) => col)
72+
.join(',')
73+
)
6474
.option(
6575
'--rule-doc-section-exclude <section>',
6676
'(optional) Disallowed section in each rule doc (option can be repeated).',
@@ -103,6 +113,7 @@ export function run() {
103113
configEmoji?: string[];
104114
ignoreConfig: string[];
105115
ignoreDeprecatedRules?: boolean;
116+
ruleDocNotices: string;
106117
ruleDocSectionExclude: string[];
107118
ruleDocSectionInclude: string[];
108119
ruleDocTitleFormat: RuleDocTitleFormat;
@@ -115,6 +126,7 @@ export function run() {
115126
configEmoji: options.configEmoji,
116127
ignoreConfig: options.ignoreConfig,
117128
ignoreDeprecatedRules: options.ignoreDeprecatedRules,
129+
ruleDocNotices: options.ruleDocNotices,
118130
ruleDocSectionExclude: options.ruleDocSectionExclude,
119131
ruleDocSectionInclude: options.ruleDocSectionInclude,
120132
ruleDocTitleFormat: options.ruleDocTitleFormat,

lib/generator.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ import {
99
} from './package-json.js';
1010
import { updateRulesList } from './rule-list.js';
1111
import { generateRuleHeaderLines } from './rule-notices.js';
12+
import {
13+
parseRuleDocNoticesOption,
14+
parseRuleListColumnsOption,
15+
} from './options.js';
1216
import { END_RULE_HEADER_MARKER } from './markers.js';
1317
import { findSectionHeader, replaceOrCreateHeader } from './markdown.js';
1418
import { resolveConfigsToRules } from './config-resolution.js';
1519
import { RuleDocTitleFormat } from './rule-doc-title-format.js';
1620
import { parseConfigEmojiOptions } from './configs.js';
17-
import { parseRuleListColumnsOption } from './rule-list-columns.js';
1821
import type { RuleDetails } from './types.js';
1922

2023
/**
@@ -79,6 +82,7 @@ export async function generate(
7982
configEmoji?: string[];
8083
ignoreConfig?: string[];
8184
ignoreDeprecatedRules?: boolean;
85+
ruleDocNotices?: string;
8286
ruleDocSectionExclude?: string[];
8387
ruleDocSectionInclude?: string[];
8488
ruleDocTitleFormat?: RuleDocTitleFormat;
@@ -131,6 +135,7 @@ export async function generate(
131135
// Options.
132136
const configEmojis = parseConfigEmojiOptions(plugin, options?.configEmoji);
133137
const ignoreConfig = options?.ignoreConfig ?? [];
138+
const ruleDocNotices = parseRuleDocNoticesOption(options?.ruleDocNotices);
134139
const ruleListColumns = parseRuleListColumnsOption(options?.ruleListColumns);
135140

136141
// Update rule doc for each rule.
@@ -152,6 +157,7 @@ export async function generate(
152157
pluginPrefix,
153158
configEmojis,
154159
ignoreConfig,
160+
ruleDocNotices,
155161
options?.ruleDocTitleFormat,
156162
options?.urlConfigs
157163
);

lib/legend.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ import {
66
EMOJI_REQUIRES_TYPE_CHECKING,
77
EMOJI_TYPE,
88
} from './emojis.js';
9-
import { COLUMN_TYPE } from './rule-list-columns.js';
9+
import { COLUMN_TYPE, ConfigEmojis, Plugin } from './types.js';
1010
import { RULE_TYPE_MESSAGES_LEGEND, RULE_TYPES } from './rule-type.js';
11-
import { ConfigEmojis, Plugin } from './types.js';
1211

1312
/**
1413
* An object containing the legends for each column (as a string or function to generate the string).

lib/options.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { COLUMN_TYPE_DEFAULT_PRESENCE_AND_ORDERING } from './rule-list-columns.js';
2+
import { NOTICE_TYPE_DEFAULT_PRESENCE_AND_ORDERING } from './rule-notices.js';
3+
import { COLUMN_TYPE, NOTICE_TYPE } from './types.js';
4+
5+
/**
6+
* Parse the option, check for errors, and set defaults.
7+
*/
8+
export function parseRuleListColumnsOption(
9+
ruleListColumns: string | undefined
10+
): COLUMN_TYPE[] {
11+
const values = ruleListColumns ? ruleListColumns.split(',') : [];
12+
const VALUES_OF_TYPE = new Set(Object.values(COLUMN_TYPE).map(String));
13+
14+
// Check for invalid.
15+
const invalid = values.find((val) => !VALUES_OF_TYPE.has(val));
16+
if (invalid) {
17+
throw new Error(`Invalid ruleListColumns option: ${invalid}`);
18+
}
19+
if (values.length !== new Set(values).size) {
20+
throw new Error('Duplicate value detected in ruleListColumns option.');
21+
}
22+
23+
if (values.length === 0) {
24+
// Use default presence and ordering.
25+
values.push(
26+
...Object.entries(COLUMN_TYPE_DEFAULT_PRESENCE_AND_ORDERING)
27+
.filter(([_type, enabled]) => enabled)
28+
.map(([type]) => type)
29+
);
30+
}
31+
32+
return values as COLUMN_TYPE[];
33+
}
34+
35+
/**
36+
* Parse the option, check for errors, and set defaults.
37+
*/
38+
export function parseRuleDocNoticesOption(
39+
ruleDocNotices: string | undefined
40+
): NOTICE_TYPE[] {
41+
const values = ruleDocNotices ? ruleDocNotices.split(',') : [];
42+
const VALUES_OF_TYPE = new Set(Object.values(NOTICE_TYPE).map(String));
43+
44+
// Check for invalid.
45+
const invalid = values.find((val) => !VALUES_OF_TYPE.has(val));
46+
if (invalid) {
47+
throw new Error(`Invalid ruleDocNotices option: ${invalid}`);
48+
}
49+
if (values.length !== new Set(values).size) {
50+
throw new Error('Duplicate value detected in ruleDocNotices option.');
51+
}
52+
53+
if (values.length === 0) {
54+
// Use default presence and ordering.
55+
values.push(
56+
...Object.entries(NOTICE_TYPE_DEFAULT_PRESENCE_AND_ORDERING)
57+
.filter(([_type, enabled]) => enabled)
58+
.map(([type]) => type)
59+
);
60+
}
61+
62+
return values as NOTICE_TYPE[];
63+
}

lib/rule-list-columns.ts

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,9 @@ import {
77
EMOJI_TYPE,
88
} from './emojis.js';
99
import { RULE_TYPES } from './rule-type.js';
10+
import { COLUMN_TYPE } from './types.js';
1011
import type { RuleDetails, ConfigsToRules, ConfigEmojis } from './types.js';
1112

12-
export enum COLUMN_TYPE {
13-
CONFIGS = 'configs',
14-
DEPRECATED = 'deprecated',
15-
DESCRIPTION = 'description',
16-
FIXABLE = 'fixable',
17-
HAS_SUGGESTIONS = 'hasSuggestions',
18-
NAME = 'name',
19-
REQUIRES_TYPE_CHECKING = 'requiresTypeChecking',
20-
TYPE = 'type',
21-
}
22-
2313
export const COLUMN_TYPE_DEFAULT_PRESENCE_AND_ORDERING: {
2414
[key in COLUMN_TYPE]: boolean;
2515
} = {
@@ -114,36 +104,6 @@ export function getColumns(
114104

115105
// Recreate object using the ordering and presence of columns specified in ruleListColumns.
116106
return Object.fromEntries(
117-
ruleListColumns.map((column) => [column, columns[column]])
107+
ruleListColumns.map((type) => [type, columns[type]])
118108
) as Record<COLUMN_TYPE, boolean>;
119109
}
120-
121-
/**
122-
* Parse the option, check for errors, and set defaults.
123-
*/
124-
export function parseRuleListColumnsOption(
125-
ruleListColumns: string | undefined
126-
): COLUMN_TYPE[] {
127-
const values = ruleListColumns ? ruleListColumns.split(',') : [];
128-
const COLUMN_TYPE_VALUES = new Set(Object.values(COLUMN_TYPE).map(String));
129-
130-
// Check for invalid.
131-
const invalid = values.find((val) => !COLUMN_TYPE_VALUES.has(val));
132-
if (invalid) {
133-
throw new Error(`Invalid ruleListColumns option: ${invalid}`);
134-
}
135-
if (values.length !== new Set(values).size) {
136-
throw new Error('Duplicate value detected in ruleListColumns option.');
137-
}
138-
139-
if (values.length === 0) {
140-
// Use default columns and ordering.
141-
values.push(
142-
...Object.entries(COLUMN_TYPE_DEFAULT_PRESENCE_AND_ORDERING)
143-
.filter(([_col, enabled]) => enabled)
144-
.map(([col]) => col)
145-
);
146-
}
147-
148-
return values as COLUMN_TYPE[];
149-
}

lib/rule-list.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import {
66
EMOJI_REQUIRES_TYPE_CHECKING,
77
} from './emojis.js';
88
import { getConfigsForRule } from './configs.js';
9-
import { COLUMN_TYPE, getColumns, COLUMN_HEADER } from './rule-list-columns.js';
9+
import { getColumns, COLUMN_HEADER } from './rule-list-columns.js';
1010
import { findSectionHeader, format } from './markdown.js';
1111
import { getPluginRoot } from './package-json.js';
1212
import { generateLegend } from './legend.js';
1313
import { relative } from 'node:path';
14+
import { COLUMN_TYPE } from './types.js';
1415
import type {
1516
Plugin,
1617
RuleDetails,

lib/rule-notices.ts

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,26 @@ import {
1818
RuleDocTitleFormat,
1919
RULE_DOC_TITLE_FORMAT_DEFAULT,
2020
} from './rule-doc-title-format.js';
21-
import { COLUMN_TYPE } from './rule-list-columns.js';
21+
import { NOTICE_TYPE } from './types.js';
22+
23+
export const NOTICE_TYPE_DEFAULT_PRESENCE_AND_ORDERING: {
24+
[key in NOTICE_TYPE]: boolean;
25+
} = {
26+
// Object keys ordered in display order.
27+
// Object values indicate whether the column is displayed by default.
28+
[NOTICE_TYPE.DEPRECATED]: true, // Most important.
29+
[NOTICE_TYPE.CONFIGS]: true,
30+
[NOTICE_TYPE.FIXABLE]: true,
31+
[NOTICE_TYPE.HAS_SUGGESTIONS]: true,
32+
[NOTICE_TYPE.REQUIRES_TYPE_CHECKING]: true,
33+
[NOTICE_TYPE.TYPE]: false,
34+
};
2235

2336
/**
2437
* An object containing the text for each notice type (as a string or function to generate the string).
2538
*/
2639
const RULE_NOTICES: {
27-
[key in COLUMN_TYPE]:
40+
[key in NOTICE_TYPE]:
2841
| string
2942
| undefined
3043
| ((data: {
@@ -36,7 +49,7 @@ const RULE_NOTICES: {
3649
}) => string);
3750
} = {
3851
// Configs notice varies based on whether the rule is enabled in one or more configs.
39-
[COLUMN_TYPE.CONFIGS]: ({
52+
[NOTICE_TYPE.CONFIGS]: ({
4053
configsEnabled,
4154
configEmojis,
4255
urlConfigs,
@@ -80,7 +93,7 @@ const RULE_NOTICES: {
8093
},
8194

8295
// Deprecated notice has optional "replaced by" rules list.
83-
[COLUMN_TYPE.DEPRECATED]: ({
96+
[NOTICE_TYPE.DEPRECATED]: ({
8497
replacedBy,
8598
}: {
8699
replacedBy?: readonly string[] | undefined;
@@ -91,7 +104,7 @@ const RULE_NOTICES: {
91104
: ''
92105
}`,
93106

94-
[COLUMN_TYPE.TYPE]: ({ type }: { type?: RULE_TYPE }) => {
107+
[NOTICE_TYPE.TYPE]: ({ type }: { type?: RULE_TYPE }) => {
95108
/* istanbul ignore next -- this shouldn't happen */
96109
if (!type) {
97110
throw new Error(
@@ -102,13 +115,9 @@ const RULE_NOTICES: {
102115
},
103116

104117
// Simple strings.
105-
[COLUMN_TYPE.FIXABLE]: `${EMOJI_FIXABLE} This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).`,
106-
[COLUMN_TYPE.HAS_SUGGESTIONS]: `${EMOJI_HAS_SUGGESTIONS} This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).`,
107-
[COLUMN_TYPE.REQUIRES_TYPE_CHECKING]: `${EMOJI_REQUIRES_TYPE_CHECKING} This rule requires type information.`,
108-
109-
// No notice for these.
110-
[COLUMN_TYPE.DESCRIPTION]: undefined,
111-
[COLUMN_TYPE.NAME]: undefined,
118+
[NOTICE_TYPE.FIXABLE]: `${EMOJI_FIXABLE} This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).`,
119+
[NOTICE_TYPE.HAS_SUGGESTIONS]: `${EMOJI_HAS_SUGGESTIONS} This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).`,
120+
[NOTICE_TYPE.REQUIRES_TYPE_CHECKING]: `${EMOJI_REQUIRES_TYPE_CHECKING} This rule requires type information.`,
112121
};
113122

114123
/**
@@ -123,22 +132,28 @@ function ruleNamesToList(ruleNames: readonly string[]) {
123132
/**
124133
* Determine which notices should and should not be included at the top of a rule doc.
125134
*/
126-
function getNoticesForRule(rule: RuleModule, configsEnabled: string[]) {
135+
function getNoticesForRule(
136+
rule: RuleModule,
137+
configsEnabled: string[],
138+
ruleDocNotices: NOTICE_TYPE[]
139+
) {
127140
const notices: {
128-
[key in COLUMN_TYPE]: boolean;
141+
[key in NOTICE_TYPE]: boolean;
129142
} = {
130-
[COLUMN_TYPE.CONFIGS]: configsEnabled.length > 0,
131-
[COLUMN_TYPE.DEPRECATED]: rule.meta.deprecated || false,
132-
[COLUMN_TYPE.DESCRIPTION]: false, // No notice for this column.
133-
[COLUMN_TYPE.FIXABLE]: Boolean(rule.meta.fixable),
134-
[COLUMN_TYPE.HAS_SUGGESTIONS]: rule.meta.hasSuggestions || false,
135-
[COLUMN_TYPE.NAME]: false, // No notice for this column.
136-
[COLUMN_TYPE.REQUIRES_TYPE_CHECKING]:
143+
// Alphabetical order.
144+
[NOTICE_TYPE.CONFIGS]: configsEnabled.length > 0,
145+
[NOTICE_TYPE.DEPRECATED]: rule.meta.deprecated || false,
146+
[NOTICE_TYPE.FIXABLE]: Boolean(rule.meta.fixable),
147+
[NOTICE_TYPE.HAS_SUGGESTIONS]: rule.meta.hasSuggestions || false,
148+
[NOTICE_TYPE.REQUIRES_TYPE_CHECKING]:
137149
rule.meta.docs?.requiresTypeChecking || false,
138-
[COLUMN_TYPE.TYPE]: Boolean(rule.meta.type),
150+
[NOTICE_TYPE.TYPE]: Boolean(rule.meta.type),
139151
};
140152

141-
return notices;
153+
// Recreate object using the ordering and presence of columns specified in ruleDocNotices.
154+
return Object.fromEntries(
155+
ruleDocNotices.map((type) => [type, notices[type]])
156+
) as Record<NOTICE_TYPE, boolean>;
142157
}
143158

144159
/**
@@ -151,6 +166,7 @@ function getRuleNoticeLines(
151166
pluginPrefix: string,
152167
configEmojis: ConfigEmojis,
153168
ignoreConfig: string[],
169+
ruleDocNotices: NOTICE_TYPE[],
154170
urlConfigs?: string
155171
) {
156172
const lines: string[] = [];
@@ -173,7 +189,7 @@ function getRuleNoticeLines(
173189
configsToRules,
174190
pluginPrefix
175191
).filter((config) => !ignoreConfig?.includes(config));
176-
const notices = getNoticesForRule(rule, configsEnabled);
192+
const notices = getNoticesForRule(rule, configsEnabled, ruleDocNotices);
177193
let noticeType: keyof typeof notices;
178194

179195
for (noticeType in notices) {
@@ -267,6 +283,7 @@ export function generateRuleHeaderLines(
267283
pluginPrefix: string,
268284
configEmojis: ConfigEmojis,
269285
ignoreConfig: string[],
286+
ruleDocNotices: NOTICE_TYPE[],
270287
ruleDocTitleFormat?: RuleDocTitleFormat,
271288
urlConfigs?: string
272289
): string {
@@ -279,6 +296,7 @@ export function generateRuleHeaderLines(
279296
pluginPrefix,
280297
configEmojis,
281298
ignoreConfig,
299+
ruleDocNotices,
282300
urlConfigs
283301
),
284302
'',

0 commit comments

Comments
 (0)