Skip to content

Commit 1544b4b

Browse files
authored
Merge pull request #156 from bmish/rule-disabled-in-config
2 parents 58f2269 + 4e34b9e commit 1544b4b

File tree

7 files changed

+207
-44
lines changed

7 files changed

+207
-44
lines changed

lib/configs.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import { EMOJI_CONFIGS } from './emojis.js';
2-
import type { Plugin, ConfigsToRules, ConfigEmojis } from './types.js';
3-
4-
const SEVERITY_ENABLED = new Set([2, 'error']);
2+
import type {
3+
Plugin,
4+
ConfigsToRules,
5+
ConfigEmojis,
6+
RuleSeverity,
7+
} from './types.js';
58

69
/**
710
* Get config names that a given rule belongs to.
811
*/
912
export function getConfigsForRule(
1013
ruleName: string,
1114
configsToRules: ConfigsToRules,
12-
pluginPrefix: string
15+
pluginPrefix: string,
16+
severity: Set<RuleSeverity>
1317
) {
1418
const configNames: Array<keyof typeof configsToRules> = [];
1519

@@ -18,11 +22,11 @@ export function getConfigsForRule(
1822
const value = rules[`${pluginPrefix}/${ruleName}`];
1923
const isEnabled =
2024
((typeof value === 'string' || typeof value === 'number') &&
21-
SEVERITY_ENABLED.has(value)) ||
25+
severity.has(value)) ||
2226
(typeof value === 'object' &&
2327
Array.isArray(value) &&
2428
value.length > 0 &&
25-
SEVERITY_ENABLED.has(value[0]));
29+
severity.has(value[0]));
2630

2731
if (isEnabled) {
2832
configNames.push(configName);

lib/legend.ts

+36-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ import {
66
EMOJI_REQUIRES_TYPE_CHECKING,
77
EMOJI_TYPE,
88
} from './emojis.js';
9-
import { COLUMN_TYPE, ConfigEmojis, Plugin } from './types.js';
9+
import { getConfigsForRule } from './configs.js';
10+
import {
11+
COLUMN_TYPE,
12+
ConfigEmojis,
13+
Plugin,
14+
ConfigsToRules,
15+
SEVERITY_ERROR,
16+
} from './types.js';
1017
import { RULE_TYPE_MESSAGES_LEGEND, RULE_TYPES } from './rule-type.js';
1118

1219
/**
@@ -18,25 +25,28 @@ const LEGENDS: {
1825
| undefined // For no legend.
1926
| ((data: {
2027
plugin: Plugin;
28+
configsToRules: ConfigsToRules;
2129
configEmojis: ConfigEmojis;
30+
pluginPrefix: string;
2231
ignoreConfig: string[];
2332
urlConfigs?: string;
2433
}) => string[]);
2534
} = {
2635
// Legends are included for each config. A generic config legend is also included if there are multiple configs.
2736
[COLUMN_TYPE.CONFIGS]: ({
2837
plugin,
38+
configsToRules,
2939
configEmojis,
40+
pluginPrefix,
3041
urlConfigs,
3142
ignoreConfig,
3243
}) => {
3344
/* istanbul ignore next -- this shouldn't happen */
34-
if (!plugin.configs) {
45+
if (!plugin.configs || !plugin.rules) {
3546
throw new Error(
36-
'Should not be attempting to display configs column when there are no configs.'
47+
'Should not be attempting to display configs column when there are no configs/rules.'
3748
);
3849
}
39-
const configNames = Object.keys(plugin.configs);
4050

4151
// Add link to configs documentation if provided.
4252
const configsLinkOrWord = urlConfigs
@@ -46,7 +56,21 @@ const LEGENDS: {
4656
? `[configuration](${urlConfigs})`
4757
: 'configuration';
4858

49-
const configNamesWithoutIgnored = configNames.filter(
59+
const ruleNames = Object.keys(plugin.rules);
60+
const configsThatEnableAnyRule = Object.entries(configsToRules)
61+
.filter(([configName, _config]) =>
62+
ruleNames.some((ruleName) =>
63+
getConfigsForRule(
64+
ruleName,
65+
configsToRules,
66+
pluginPrefix,
67+
SEVERITY_ERROR
68+
).includes(configName)
69+
)
70+
)
71+
.map(([configName, _config]) => configName);
72+
73+
const configNamesWithoutIgnored = configsThatEnableAnyRule.filter(
5074
(configName) => !ignoreConfig?.includes(configName)
5175
);
5276

@@ -57,7 +81,9 @@ const LEGENDS: {
5781
configNamesWithoutIgnored?.includes(configEmoji.config)
5882
)?.emoji) &&
5983
// If any configs are using the generic config emoji, then don't display the generic config legend.
60-
!configEmojis.some((configEmoji) => configEmoji.emoji === EMOJI_CONFIG)
84+
!configEmojis
85+
.filter(({ config }) => !ignoreConfig?.includes(config))
86+
.some((configEmoji) => configEmoji.emoji === EMOJI_CONFIG)
6187
) {
6288
// Generic config emoji will be used if the plugin has multiple configs or the sole config has no emoji.
6389
legends.push(`${EMOJI_CONFIG} ${configsLinkOrWord} enabled in.`);
@@ -121,7 +147,9 @@ const LEGENDS: {
121147
export function generateLegend(
122148
columns: Record<COLUMN_TYPE, boolean>,
123149
plugin: Plugin,
150+
configsToRules: ConfigsToRules,
124151
configEmojis: ConfigEmojis,
152+
pluginPrefix: string,
125153
ignoreConfig: string[],
126154
urlConfigs?: string
127155
) {
@@ -139,7 +167,9 @@ export function generateLegend(
139167
return typeof legendStrOrFn === 'function'
140168
? legendStrOrFn({
141169
plugin,
170+
configsToRules,
142171
configEmojis,
172+
pluginPrefix,
143173
urlConfigs,
144174
ignoreConfig,
145175
})

lib/rule-list.ts

+26-8
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ 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';
14+
import { COLUMN_TYPE, SEVERITY_ERROR } from './types.js';
1515
import { markdownTable } from 'markdown-table';
1616
import type {
1717
Plugin,
@@ -29,12 +29,15 @@ function getConfigurationColumnValueForRule(
2929
ignoreConfig: string[]
3030
): string {
3131
const badges: string[] = [];
32-
const configs = getConfigsForRule(rule.name, configsToRules, pluginPrefix);
33-
for (const configName of configs) {
34-
if (ignoreConfig?.includes(configName)) {
35-
// Ignore config.
36-
continue;
37-
}
32+
33+
const configsEnabled = getConfigsForRule(
34+
rule.name,
35+
configsToRules,
36+
pluginPrefix,
37+
SEVERITY_ERROR
38+
).filter((configName) => !ignoreConfig?.includes(configName));
39+
40+
for (const configName of configsEnabled) {
3841
// Find the emoji for the config or otherwise use a badge that can be defined in markdown.
3942
const emoji = configEmojis.find(
4043
(configEmoji) => configEmoji.config === configName
@@ -99,10 +102,23 @@ function generateRulesListMarkdown(
99102
return [];
100103
}
101104
const headerStrOrFn = COLUMN_HEADER[columnType];
105+
const ruleNames = details.map((rule) => rule.name);
106+
const configsThatEnableAnyRule = Object.entries(configsToRules)
107+
.filter(([configName, _config]) =>
108+
ruleNames.some((ruleName) =>
109+
getConfigsForRule(
110+
ruleName,
111+
configsToRules,
112+
pluginPrefix,
113+
SEVERITY_ERROR
114+
).includes(configName)
115+
)
116+
)
117+
.map(([configName, _config]) => configName);
102118
return [
103119
typeof headerStrOrFn === 'function'
104120
? headerStrOrFn({
105-
configNames: Object.keys(configsToRules),
121+
configNames: configsThatEnableAnyRule,
106122
configEmojis,
107123
ignoreConfig,
108124
details,
@@ -191,7 +207,9 @@ export async function updateRulesList(
191207
const legend = generateLegend(
192208
columns,
193209
plugin,
210+
configsToRules,
194211
configEmojis,
212+
pluginPrefix,
195213
ignoreConfig,
196214
urlConfigs
197215
);

lib/rule-notices.ts

+86-24
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
RuleDocTitleFormat,
1919
RULE_DOC_TITLE_FORMAT_DEFAULT,
2020
} from './rule-doc-title-format.js';
21-
import { NOTICE_TYPE } from './types.js';
21+
import { NOTICE_TYPE, SEVERITY_ERROR, SEVERITY_OFF } from './types.js';
2222

2323
export const NOTICE_TYPE_DEFAULT_PRESENCE_AND_ORDERING: {
2424
[key in NOTICE_TYPE]: boolean;
@@ -43,46 +43,91 @@ const RULE_NOTICES: {
4343
| undefined
4444
| ((data: {
4545
configsEnabled: string[];
46+
configsDisabled: string[];
4647
configEmojis: ConfigEmojis;
4748
urlConfigs?: string;
4849
replacedBy: readonly string[] | undefined;
4950
type?: RULE_TYPE;
5051
}) => string);
5152
} = {
5253
// Configs notice varies based on whether the rule is enabled in one or more configs.
53-
[NOTICE_TYPE.CONFIGS]: ({ configsEnabled, configEmojis, urlConfigs }) => {
54+
[NOTICE_TYPE.CONFIGS]: ({
55+
configsEnabled,
56+
configsDisabled,
57+
configEmojis,
58+
urlConfigs,
59+
}) => {
5460
// Add link to configs documentation if provided.
5561
const configsLinkOrWord = urlConfigs
5662
? `[configs](${urlConfigs})`
5763
: 'configs';
5864
const configLinkOrWord = urlConfigs ? `[config](${urlConfigs})` : 'config';
5965

6066
/* istanbul ignore next -- this shouldn't happen */
61-
if (!configsEnabled || configsEnabled.length === 0) {
67+
if (
68+
(!configsEnabled || configsEnabled.length === 0) &&
69+
(!configsDisabled || configsDisabled.length === 0)
70+
) {
6271
throw new Error(
63-
'Should not be trying to display config notice for rule not enabled in any configs.'
72+
'Should not be trying to display config notice for rule not enabled/disabled in any configs.'
6473
);
6574
}
6675

67-
if (configsEnabled.length > 1) {
68-
// Rule is enabled in multiple configs.
69-
const configs = configsEnabled
70-
.map((configEnabled) => {
71-
const emoji = configEmojis.find(
72-
(configEmoji) => configEmoji.config === configEnabled
73-
)?.emoji;
74-
return `${emoji ? `${emoji} ` : ''}\`${configEnabled}\``;
75-
})
76-
.join(', ');
77-
return `${EMOJI_CONFIG} This rule is enabled in the following ${configsLinkOrWord}: ${configs}.`;
78-
} else {
79-
// Rule only enabled in one config.
80-
const emoji =
76+
// If one applicable config with an emoji, use the emoji for that config, otherwise use the general config emoji.
77+
let emoji = '';
78+
if (configsEnabled.length + configsDisabled.length > 1) {
79+
emoji = EMOJI_CONFIG;
80+
} else if (configsEnabled.length > 0) {
81+
emoji =
8182
configEmojis.find(
82-
(configEmoji) => configEmoji.config === configsEnabled?.[0]
83+
(configEmoji) => configEmoji.config === configsEnabled[0]
84+
)?.emoji ?? EMOJI_CONFIG;
85+
} else if (configsDisabled.length > 0) {
86+
emoji =
87+
configEmojis.find(
88+
(configEmoji) => configEmoji.config === configsDisabled[0]
8389
)?.emoji ?? EMOJI_CONFIG;
84-
return `${emoji} This rule is enabled in the \`${configsEnabled?.[0]}\` ${configLinkOrWord}.`;
8590
}
91+
92+
// List of configs that enable the rule.
93+
const configsEnabledCSV = configsEnabled
94+
.map((configEnabled) => {
95+
const emoji = configEmojis.find(
96+
(configEmoji) => configEmoji.config === configEnabled
97+
)?.emoji;
98+
return `${emoji ? `${emoji} ` : ''}\`${configEnabled}\``;
99+
})
100+
.join(', ');
101+
102+
// List of configs that disable the rule.
103+
const configsDisabledCSV = configsDisabled
104+
.map((configDisabled) => {
105+
const emoji = configEmojis.find(
106+
(configEmoji) => configEmoji.config === configDisabled
107+
)?.emoji;
108+
return `${emoji ? `${emoji} ` : ''}\`${configDisabled}\``;
109+
})
110+
.join(', ');
111+
112+
// Complete sentence for configs that enable the rule.
113+
const SENTENCE_ENABLED =
114+
configsEnabled.length > 1
115+
? `This rule is enabled in the following ${configsLinkOrWord}: ${configsEnabledCSV}.`
116+
: configsEnabled.length === 1
117+
? `This rule is enabled in the \`${configsEnabled?.[0]}\` ${configLinkOrWord}.`
118+
: '';
119+
120+
// Complete sentence for configs that disable the rule.
121+
const SENTENCE_DISABLED =
122+
configsDisabled.length > 1
123+
? `This rule is disabled in the following ${configsLinkOrWord}: ${configsDisabledCSV}.`
124+
: configsDisabled.length === 1
125+
? `This rule is disabled in the \`${configsDisabled?.[0]}\` ${configLinkOrWord}.`
126+
: '';
127+
128+
return `${emoji} ${SENTENCE_ENABLED}${
129+
SENTENCE_ENABLED && SENTENCE_DISABLED ? ' ' : '' // Space if two sentences.
130+
}${SENTENCE_DISABLED}`;
86131
},
87132

88133
// Deprecated notice has optional "replaced by" rules list.
@@ -125,13 +170,15 @@ function ruleNamesToList(ruleNames: readonly string[]) {
125170
function getNoticesForRule(
126171
rule: RuleModule,
127172
configsEnabled: string[],
173+
configsDisabled: string[],
128174
ruleDocNotices: NOTICE_TYPE[]
129175
) {
130176
const notices: {
131177
[key in NOTICE_TYPE]: boolean;
132178
} = {
133179
// Alphabetical order.
134-
[NOTICE_TYPE.CONFIGS]: configsEnabled.length > 0,
180+
[NOTICE_TYPE.CONFIGS]:
181+
configsEnabled.length > 0 || configsDisabled.length > 0,
135182
[NOTICE_TYPE.DEPRECATED]: rule.meta.deprecated || false,
136183

137184
// FIXABLE_AND_HAS_SUGGESTIONS potentially replaces FIXABLE and HAS_SUGGESTIONS.
@@ -188,9 +235,23 @@ function getRuleNoticeLines(
188235
const configsEnabled = getConfigsForRule(
189236
ruleName,
190237
configsToRules,
191-
pluginPrefix
192-
).filter((config) => !ignoreConfig?.includes(config));
193-
const notices = getNoticesForRule(rule, configsEnabled, ruleDocNotices);
238+
pluginPrefix,
239+
SEVERITY_ERROR
240+
).filter((configName) => !ignoreConfig?.includes(configName));
241+
242+
const configsDisabled = getConfigsForRule(
243+
ruleName,
244+
configsToRules,
245+
pluginPrefix,
246+
SEVERITY_OFF
247+
).filter((configName) => !ignoreConfig?.includes(configName));
248+
249+
const notices = getNoticesForRule(
250+
rule,
251+
configsEnabled,
252+
configsDisabled,
253+
ruleDocNotices
254+
);
194255
let noticeType: keyof typeof notices;
195256

196257
for (noticeType in notices) {
@@ -215,6 +276,7 @@ function getRuleNoticeLines(
215276
typeof ruleNoticeStrOrFn === 'function'
216277
? ruleNoticeStrOrFn({
217278
configsEnabled,
279+
configsDisabled,
218280
configEmojis,
219281
urlConfigs,
220282
replacedBy: rule.meta.replacedBy,

lib/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@ export type RuleModule = TSESLint.RuleModule<string, unknown[]>;
66

77
export type Rules = TSESLint.Linter.RulesRecord;
88

9+
export type RuleSeverity = TSESLint.Linter.RuleLevel;
10+
911
export type Config = TSESLint.Linter.Config;
1012

1113
export type Plugin = TSESLint.Linter.Plugin;
1214

1315
// Custom types.
1416

17+
export const SEVERITY_ERROR = new Set<RuleSeverity>([2, 'error']);
18+
export const SEVERITY_OFF = new Set<RuleSeverity>([0, 'off']);
19+
1520
export type ConfigsToRules = Record<string, Rules>;
1621

1722
export interface RuleDetails {

0 commit comments

Comments
 (0)