Skip to content

Commit

Permalink
feat: indicate when a rule is set to off/warn in a config in readme r…
Browse files Browse the repository at this point in the history
…ules list
  • Loading branch information
bmish committed Nov 1, 2022
1 parent 7c35d27 commit 2278e0e
Show file tree
Hide file tree
Showing 9 changed files with 519 additions and 86 deletions.
28 changes: 21 additions & 7 deletions README.md
Expand Up @@ -7,7 +7,7 @@ Automatic documentation generator for [ESLint](https://eslint.org/) plugins and
Generates the following documentation based on ESLint and top [ESLint plugin](https://eslint.org/docs/latest/developer-guide/working-with-plugins) conventions:

- `README.md` rules table
- Rule doc titles and notices
- Rule doc titles and config/fixable/etc. notices

Also performs some basic section consistency checks on rule docs:

Expand Down Expand Up @@ -89,9 +89,9 @@ Generated content in a rule doc (everything above the marker comment) (intention

💼 This rule is enabled in the following configs: ✅ `recommended`, 🎨 `stylistic`.

🎨 This rule will _warn_ in the `stylistic` config.
🎨<sup>⚠️</sup> This rule _warns_ in the `stylistic` config.

🎨 This rule is _disabled_ in the `stylistic` config.
🎨<sup>🚫</sup> This rule is _disabled_ in the `stylistic` config.

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).

Expand Down Expand Up @@ -131,6 +131,8 @@ Generated rules table in `README.md` (everything between the marker comments) (i

💼 Configurations enabled in.\
✅ Enabled in the `recommended` configuration.\
✅<sup>⚠️</sup> Warns in the `recommended` configuration.\
✅<sup>🚫</sup> Disabled in the `recommended` configuration.\
🎨 Enabled in the `stylistic` configuration.\
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).\
Expand Down Expand Up @@ -191,16 +193,28 @@ All options are optional.

### `--rule-doc-title-format`

Where `no-foo` is the rule name, `Do not use foo` is the rule description, and `eslint-plugin-test` is the plugin name.
Where `no-foo` is the rule name, `Disallow use of foo` is the rule description, and `eslint-plugin-test` is the plugin name.

| Value | Example |
| :-- | :-- |
| `desc` | `# Do not use foo` |
| `desc-parens-name` | `# Do not use foo (no-foo)` |
| `desc-parens-prefix-name` (default) | `# Do not use foo (test/no-foo)` |
| `desc` | `# Disallow use of foo` |
| `desc-parens-name` | `# Disallow use of foo (no-foo)` |
| `desc-parens-prefix-name` (default) | `# Disallow use of foo (test/no-foo)` |
| `name` | `# no-foo` |
`prefix-name` | `# test/no-foo` |

## Compatibility

### markdownlint

The output of this tool should be compatible with [markdownlint](https://github.com/DavidAnson/markdownlint) which you might use to lint your markdown. However, if any of your ESLint configs disable your rules or set them to warn, you'll need to exempt the [`<sup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup) (superscript) element from [no-inline-html](https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md033---inline-html):

```json
{
"no-inline-html": { "allowed_elements": ["sup"] }
}
```

## Related

- [eslint-plugin-eslint-plugin](https://github.com/eslint-community/eslint-plugin-eslint-plugin) - Linter for ESLint plugins ([related list](https://eslint.org/docs/latest/developer-guide/working-with-plugins#linting))
Expand Down
65 changes: 59 additions & 6 deletions lib/configs.ts
@@ -1,34 +1,41 @@
import { EMOJI_CONFIGS, EMOJI_CONFIG } from './emojis.js';
import {
EMOJI_CONFIGS,
EMOJI_CONFIG,
EMOJI_CONFIG_WARN,
EMOJI_CONFIG_OFF,
} from './emojis.js';
import type {
Plugin,
ConfigsToRules,
ConfigEmojis,
RuleSeverity,
SEVERITY_TYPE,
} from './types.js';

/**
* Get config names that a given rule belongs to.
* @param severity - Include configs that set the rule to this severity. Omit to allow any severity.
*/
export function getConfigsForRule(
ruleName: string,
configsToRules: ConfigsToRules,
pluginPrefix: string,
severity: Set<RuleSeverity>
severity?: Set<RuleSeverity>
) {
const configNames: Array<keyof typeof configsToRules> = [];

for (const configName in configsToRules) {
const rules = configsToRules[configName];
const value = rules[`${pluginPrefix}/${ruleName}`];
const isEnabled =
const isSet =
((typeof value === 'string' || typeof value === 'number') &&
severity.has(value)) ||
(!severity || severity.has(value))) ||
(typeof value === 'object' &&
Array.isArray(value) &&
value.length > 0 &&
severity.has(value[0]));
(!severity || severity.has(value[0])));

if (isEnabled) {
if (isSet) {
configNames.push(configName);
}
}
Expand Down Expand Up @@ -90,3 +97,49 @@ export function parseConfigEmojiOptions(

return configEmojis;
}

/**
*
* @param configEmojis - known list of configs and corresponding emojis
* @param configName - name of the config to find an emoji for
* @param options
* @param options.severity - if present, decorate the config's emoji for the given severity level
* @param options.fallback - if true and no emoji is found, choose whether to fallback to a generic config emoji or a badge
* @returns the string to display for the config
*/
export function findConfigEmoji(
configEmojis: ConfigEmojis,
configName: string,
options?: {
severity?: SEVERITY_TYPE;
fallback?: 'badge' | 'emoji';
}
) {
let emoji = configEmojis.find(
(configEmoji) => configEmoji.config === configName
)?.emoji;
if (!emoji) {
if (options?.fallback === 'badge') {
emoji = `![${configName}][]`;
} else if (options?.fallback === 'emoji') {
emoji = EMOJI_CONFIG;
} else {
// No fallback.
return undefined; // eslint-disable-line unicorn/no-useless-undefined
}
}
switch (options?.severity) {
case 'warn':
return `${emoji}${
// Conditional is to avoid double emoji.
emoji === EMOJI_CONFIG_WARN ? '' : `<sup>${EMOJI_CONFIG_WARN}</sup>`
}`;
case 'off':
// Conditional is to avoid double emoji.
return `${emoji}${
emoji === EMOJI_CONFIG_OFF ? '' : `<sup>${EMOJI_CONFIG_OFF}</sup>`
}`;
default:
return emoji;
}
}
2 changes: 2 additions & 0 deletions lib/emojis.ts
Expand Up @@ -15,6 +15,8 @@ export const EMOJI_CONFIGS = {

// General configs.
export const EMOJI_CONFIG = '💼';
export const EMOJI_CONFIG_WARN = '⚠️';
export const EMOJI_CONFIG_OFF = '🚫';

// Fixers.
export const EMOJI_FIXABLE = '🔧';
Expand Down
95 changes: 74 additions & 21 deletions lib/legend.ts
Expand Up @@ -6,13 +6,16 @@ import {
EMOJI_REQUIRES_TYPE_CHECKING,
EMOJI_TYPE,
} from './emojis.js';
import { getConfigsForRule } from './configs.js';
import { getConfigsForRule, findConfigEmoji } from './configs.js';
import {
COLUMN_TYPE,
ConfigEmojis,
Plugin,
ConfigsToRules,
SEVERITY_ERROR,
SEVERITY_WARN,
SEVERITY_OFF,
SEVERITY_TYPE,
} from './types.js';
import { RULE_TYPE_MESSAGES_LEGEND, RULE_TYPES } from './rule-type.js';

Expand Down Expand Up @@ -57,28 +60,24 @@ const LEGENDS: {
: 'configuration';

const ruleNames = Object.keys(plugin.rules);
const configsThatEnableAnyRule = Object.entries(configsToRules)
const configNamesWithoutIgnored = Object.entries(configsToRules)
.filter(([configName, _config]) =>
// Only consider configs that configure at least one of the plugin's rules.
ruleNames.some((ruleName) =>
getConfigsForRule(
ruleName,
configsToRules,
pluginPrefix,
SEVERITY_ERROR
).includes(configName)
getConfigsForRule(ruleName, configsToRules, pluginPrefix).includes(
configName
)
)
)
.map(([configName, _config]) => configName);

const configNamesWithoutIgnored = configsThatEnableAnyRule.filter(
(configName) => !ignoreConfig?.includes(configName)
);
// Filter out ignored configs.
.filter(([configName]) => !ignoreConfig?.includes(configName))
.map(([configName]) => configName);

const legends = [];
if (
(configNamesWithoutIgnored.length > 1 ||
!configEmojis.find((configEmoji) =>
configNamesWithoutIgnored?.includes(configEmoji.config)
configNamesWithoutIgnored.includes(configEmoji.config)
)?.emoji) &&
// If any configs are using the generic config emoji, then don't display the generic config legend.
!configEmojis
Expand All @@ -90,16 +89,70 @@ const LEGENDS: {
}
legends.push(
...configNamesWithoutIgnored.flatMap((configName) => {
const emoji = configEmojis.find(
(configEmoji) => configEmoji.config === configName
)?.emoji;
if (!emoji) {
if (!findConfigEmoji(configEmojis, configName)) {
// No legend for this config as it has no emoji.
return [];
}
return [
`${emoji} Enabled in the \`${configName}\` ${configLinkOrWord}.`,
];

let hasErrorRule = false;
let hasWarnRule = false;
let hasOffRule = false;
for (const ruleName of ruleNames) {
if (
getConfigsForRule(
ruleName,
configsToRules,
pluginPrefix,
SEVERITY_ERROR
).includes(configName)
) {
hasErrorRule = true;
}
if (
getConfigsForRule(
ruleName,
configsToRules,
pluginPrefix,
SEVERITY_WARN
).includes(configName)
) {
hasWarnRule = true;
}
if (
getConfigsForRule(
ruleName,
configsToRules,
pluginPrefix,
SEVERITY_OFF
).includes(configName)
) {
hasOffRule = true;
}
}

const legendsForThisConfig = [];
if (hasErrorRule) {
legendsForThisConfig.push(
`${findConfigEmoji(configEmojis, configName, {
severity: SEVERITY_TYPE.error,
})} Enabled in the \`${configName}\` ${configLinkOrWord}.`
);
}
if (hasWarnRule) {
legendsForThisConfig.push(
`${findConfigEmoji(configEmojis, configName, {
severity: SEVERITY_TYPE.warn,
})} Warns in the \`${configName}\` ${configLinkOrWord}.`
);
}
if (hasOffRule) {
legendsForThisConfig.push(
`${findConfigEmoji(configEmojis, configName, {
severity: SEVERITY_TYPE.off,
})} Disabled in the \`${configName}\` ${configLinkOrWord}.`
);
}
return legendsForThisConfig;
})
);
return legends;
Expand Down

0 comments on commit 2278e0e

Please sign in to comment.