eslint-doc-generator
Advanced tools
Comparing version 0.15.0 to 0.16.0
@@ -40,2 +40,3 @@ import { Command, Argument, Option } from 'commander'; | ||
.option('--rule-doc-section-include <section>', '(optional) Required section in each rule doc (option can be repeated).', collect, []) | ||
.option('--rule-doc-section-options [required]', '(optional) Whether to require an "Options" or "Config" rule doc section and mention of any named options for rules with options.', true) | ||
.addOption(new Option('--rule-doc-title-format <format>', '(optional) The format to use for rule doc titles.') | ||
@@ -61,2 +62,3 @@ .choices(RULE_DOC_TITLE_FORMATS) | ||
ruleDocSectionInclude: options.ruleDocSectionInclude, | ||
ruleDocSectionOptions: ['true', true, undefined].includes(options.ruleDocSectionOptions), | ||
ruleDocTitleFormat: options.ruleDocTitleFormat, | ||
@@ -63,0 +65,0 @@ ruleListColumns: options.ruleListColumns, |
@@ -1,2 +0,2 @@ | ||
import { EMOJI_CONFIGS } from './emojis.js'; | ||
import { EMOJI_CONFIGS, EMOJI_CONFIG } from './emojis.js'; | ||
/** | ||
@@ -40,2 +40,5 @@ * Get config names that a given rule belongs to. | ||
} | ||
if (Object.keys(plugin.configs)?.length > 1 && emoji === EMOJI_CONFIG) { | ||
throw new Error(`Cannot use the general configs emoji ${EMOJI_CONFIG} for an individual config when multiple configs are present.`); | ||
} | ||
return [{ config, emoji }]; | ||
@@ -42,0 +45,0 @@ }) || []; |
@@ -10,2 +10,3 @@ import { RuleDocTitleFormat } from './rule-doc-title-format.js'; | ||
ruleDocSectionInclude?: string[]; | ||
ruleDocSectionOptions?: boolean; | ||
ruleDocTitleFormat?: RuleDocTitleFormat; | ||
@@ -12,0 +13,0 @@ ruleListColumns?: string; |
@@ -21,3 +21,8 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs'; | ||
function expectContent(ruleName, contents, content, expected) { | ||
if (contents.includes(content) !== expected) { | ||
// Check for the content and also the versions of the content with escaped quotes | ||
// in case escaping is needed where the content is referenced. | ||
const hasContent = contents.includes(content) || | ||
contents.includes(content.replace(/"/g, '\\"')) || | ||
contents.includes(content.replace(/'/g, "\\'")); | ||
if (hasContent !== expected) { | ||
console.error(`\`${ruleName}\` rule doc should ${ | ||
@@ -55,11 +60,11 @@ /* istanbul ignore next -- TODO: test !expected or remove parameter */ | ||
name, | ||
description: rule.meta.docs?.description, | ||
fixable: rule.meta.fixable | ||
description: rule.meta?.docs?.description, | ||
fixable: rule.meta?.fixable | ||
? ['code', 'whitespace'].includes(rule.meta.fixable) | ||
: false, | ||
hasSuggestions: rule.meta.hasSuggestions ?? false, | ||
requiresTypeChecking: rule.meta.docs?.requiresTypeChecking ?? false, | ||
deprecated: rule.meta.deprecated ?? false, | ||
schema: rule.meta.schema, | ||
type: rule.meta.type, | ||
hasSuggestions: rule.meta?.hasSuggestions ?? false, | ||
requiresTypeChecking: rule.meta?.docs?.requiresTypeChecking ?? false, | ||
deprecated: rule.meta?.deprecated ?? false, | ||
schema: rule.meta?.schema, | ||
type: rule.meta?.type, | ||
} | ||
@@ -85,2 +90,3 @@ : // Deprecated function-style rule (does not support most of these features). | ||
const ruleDocNotices = parseRuleDocNoticesOption(options?.ruleDocNotices); | ||
const ruleDocSectionOptions = options?.ruleDocSectionOptions ?? true; | ||
const ruleListColumns = parseRuleListColumnsOption(options?.ruleListColumns); | ||
@@ -115,6 +121,8 @@ // Update rule doc for each rule. | ||
} | ||
// Options section. | ||
expectSectionHeader(name, contents, ['Options', 'Config'], hasOptions(schema)); | ||
for (const namedOption of getAllNamedOptions(schema)) { | ||
expectContent(name, contents, namedOption, true); // Each rule option is mentioned. | ||
if (ruleDocSectionOptions) { | ||
// Options section. | ||
expectSectionHeader(name, contents, ['Options', 'Config'], hasOptions(schema)); | ||
for (const namedOption of getAllNamedOptions(schema)) { | ||
expectContent(name, contents, namedOption, true); // Each rule option is mentioned. | ||
} | ||
} | ||
@@ -121,0 +129,0 @@ } |
@@ -38,3 +38,3 @@ import { EMOJI_CONFIG, EMOJI_DEPRECATED, EMOJI_FIXABLE, EMOJI_HAS_SUGGESTIONS, EMOJI_REQUIRES_TYPE_CHECKING, EMOJI_TYPE, } from './emojis.js'; | ||
longestRuleNameLength > title.length | ||
? ' '.repeat(longestRuleNameLength - title.length) | ||
? ' '.repeat(longestRuleNameLength - title.length) // U+00A0 nbsp character. | ||
: ''; | ||
@@ -41,0 +41,0 @@ return `${title}${spaces}`; |
@@ -6,3 +6,3 @@ import { END_RULE_HEADER_MARKER } from './markers.js'; | ||
import { RULE_DOC_TITLE_FORMAT_DEFAULT, } from './rule-doc-title-format.js'; | ||
import { NOTICE_TYPE, SEVERITY_ERROR, SEVERITY_OFF } from './types.js'; | ||
import { NOTICE_TYPE, SEVERITY_ERROR, SEVERITY_OFF, SEVERITY_WARN, } from './types.js'; | ||
export const NOTICE_TYPE_DEFAULT_PRESENCE_AND_ORDERING = { | ||
@@ -23,4 +23,5 @@ // Object keys ordered in display order. | ||
const RULE_NOTICES = { | ||
// Configs notice varies based on whether the rule is enabled in one or more configs. | ||
[NOTICE_TYPE.CONFIGS]: ({ configsEnabled, configsDisabled, configEmojis, urlConfigs, }) => { | ||
// Configs notice varies based on whether the rule is configured in one or more configs. | ||
// eslint-disable-next-line complexity | ||
[NOTICE_TYPE.CONFIGS]: ({ configsEnabled, configsWarn, configsDisabled, configEmojis, urlConfigs, }) => { | ||
// Add link to configs documentation if provided. | ||
@@ -33,8 +34,10 @@ const configsLinkOrWord = urlConfigs | ||
if ((!configsEnabled || configsEnabled.length === 0) && | ||
(!configsWarn || configsWarn.length === 0) && | ||
(!configsDisabled || configsDisabled.length === 0)) { | ||
throw new Error('Should not be trying to display config notice for rule not enabled/disabled in any configs.'); | ||
throw new Error('Should not be trying to display config notice for rule not configured in any configs.'); | ||
} | ||
// If one applicable config with an emoji, use the emoji for that config, otherwise use the general config emoji. | ||
let emoji = ''; | ||
if (configsEnabled.length + configsDisabled.length > 1) { | ||
if (configsEnabled.length + configsWarn.length + configsDisabled.length > | ||
1) { | ||
emoji = EMOJI_CONFIG; | ||
@@ -46,2 +49,6 @@ } | ||
} | ||
else if (configsWarn.length > 0) { | ||
emoji = | ||
configEmojis.find((configEmoji) => configEmoji.config === configsWarn[0])?.emoji ?? EMOJI_CONFIG; | ||
} | ||
else if (configsDisabled.length > 0) { | ||
@@ -58,2 +65,9 @@ emoji = | ||
.join(', '); | ||
// List of configs that warn for the rule. | ||
const configsWarnCSV = configsWarn | ||
.map((configWarn) => { | ||
const emoji = configEmojis.find((configEmoji) => configEmoji.config === configWarn)?.emoji; | ||
return `${emoji ? `${emoji} ` : ''}\`${configWarn}\``; | ||
}) | ||
.join(', '); | ||
// List of configs that disable the rule. | ||
@@ -71,3 +85,9 @@ const configsDisabledCSV = configsDisabled | ||
? `This rule is enabled in the \`${configsEnabled?.[0]}\` ${configLinkOrWord}.` | ||
: ''; | ||
: undefined; | ||
// Complete sentence for configs that warn for the rule. | ||
const SENTENCE_WARN = configsWarn.length > 1 | ||
? `This rule will _warn_ in the following ${configsLinkOrWord}: ${configsWarnCSV}.` | ||
: configsWarn.length === 1 | ||
? `This rule will _warn_ in the \`${configsWarn?.[0]}\` ${configLinkOrWord}.` | ||
: undefined; | ||
// Complete sentence for configs that disable the rule. | ||
@@ -78,5 +98,6 @@ const SENTENCE_DISABLED = configsDisabled.length > 1 | ||
? `This rule is _disabled_ in the \`${configsDisabled?.[0]}\` ${configLinkOrWord}.` | ||
: ''; | ||
return `${emoji} ${SENTENCE_ENABLED}${SENTENCE_ENABLED && SENTENCE_DISABLED ? ' ' : '' // Space if two sentences. | ||
}${SENTENCE_DISABLED}`; | ||
: undefined; | ||
return `${emoji} ${[SENTENCE_ENABLED, SENTENCE_WARN, SENTENCE_DISABLED] | ||
.filter((sentence) => sentence !== undefined) | ||
.join(' ')}`; | ||
}, | ||
@@ -111,17 +132,19 @@ // Deprecated notice has optional "replaced by" rules list. | ||
*/ | ||
function getNoticesForRule(rule, configsEnabled, configsDisabled, ruleDocNotices) { | ||
function getNoticesForRule(rule, configsEnabled, configsWarn, configsDisabled, ruleDocNotices) { | ||
const notices = { | ||
// Alphabetical order. | ||
[NOTICE_TYPE.CONFIGS]: configsEnabled.length > 0 || configsDisabled.length > 0, | ||
[NOTICE_TYPE.DEPRECATED]: rule.meta.deprecated || false, | ||
[NOTICE_TYPE.CONFIGS]: configsEnabled.length > 0 || | ||
configsWarn.length > 0 || | ||
configsDisabled.length > 0, | ||
[NOTICE_TYPE.DEPRECATED]: rule.meta?.deprecated || false, | ||
// FIXABLE_AND_HAS_SUGGESTIONS potentially replaces FIXABLE and HAS_SUGGESTIONS. | ||
[NOTICE_TYPE.FIXABLE]: Boolean(rule.meta.fixable) && | ||
[NOTICE_TYPE.FIXABLE]: Boolean(rule.meta?.fixable) && | ||
(!rule.meta.hasSuggestions || | ||
!ruleDocNotices.includes(NOTICE_TYPE.FIXABLE_AND_HAS_SUGGESTIONS)), | ||
[NOTICE_TYPE.FIXABLE_AND_HAS_SUGGESTIONS]: Boolean(rule.meta.fixable) && Boolean(rule.meta.hasSuggestions), | ||
[NOTICE_TYPE.HAS_SUGGESTIONS]: Boolean(rule.meta.hasSuggestions) && | ||
[NOTICE_TYPE.FIXABLE_AND_HAS_SUGGESTIONS]: Boolean(rule.meta?.fixable) && Boolean(rule.meta?.hasSuggestions), | ||
[NOTICE_TYPE.HAS_SUGGESTIONS]: Boolean(rule.meta?.hasSuggestions) && | ||
(!rule.meta.fixable || | ||
!ruleDocNotices.includes(NOTICE_TYPE.FIXABLE_AND_HAS_SUGGESTIONS)), | ||
[NOTICE_TYPE.REQUIRES_TYPE_CHECKING]: rule.meta.docs?.requiresTypeChecking || false, | ||
[NOTICE_TYPE.TYPE]: Boolean(rule.meta.type), | ||
[NOTICE_TYPE.REQUIRES_TYPE_CHECKING]: rule.meta?.docs?.requiresTypeChecking || false, | ||
[NOTICE_TYPE.TYPE]: Boolean(rule.meta?.type), | ||
}; | ||
@@ -148,4 +171,5 @@ // Recreate object using the ordering and presence of columns specified in ruleDocNotices. | ||
const configsEnabled = getConfigsForRule(ruleName, configsToRules, pluginPrefix, SEVERITY_ERROR).filter((configName) => !ignoreConfig?.includes(configName)); | ||
const configsWarn = getConfigsForRule(ruleName, configsToRules, pluginPrefix, SEVERITY_WARN).filter((configName) => !ignoreConfig?.includes(configName)); | ||
const configsDisabled = getConfigsForRule(ruleName, configsToRules, pluginPrefix, SEVERITY_OFF).filter((configName) => !ignoreConfig?.includes(configName)); | ||
const notices = getNoticesForRule(rule, configsEnabled, configsDisabled, ruleDocNotices); | ||
const notices = getNoticesForRule(rule, configsEnabled, configsWarn, configsDisabled, ruleDocNotices); | ||
let noticeType; | ||
@@ -168,7 +192,8 @@ for (noticeType in notices) { | ||
configsEnabled, | ||
configsWarn, | ||
configsDisabled, | ||
configEmojis, | ||
urlConfigs, | ||
replacedBy: rule.meta.replacedBy, | ||
type: rule.meta.type, // Convert union type to enum. | ||
replacedBy: rule.meta?.replacedBy, | ||
type: rule.meta?.type, // Convert union type to enum. | ||
}) | ||
@@ -193,3 +218,16 @@ : ruleNoticeStrOrFn); | ||
if (ruleDocTitleFormatWithFallback.includes('desc') && !description) { | ||
ruleDocTitleFormatWithFallback = 'prefix-name'; // Fallback if rule missing description. | ||
// If format includes the description but the rule is missing a description, | ||
// fallback to the corresponding format without the description. | ||
switch (ruleDocTitleFormatWithFallback) { | ||
case 'desc': | ||
case 'desc-parens-prefix-name': | ||
ruleDocTitleFormatWithFallback = 'prefix-name'; | ||
break; | ||
case 'desc-parens-name': | ||
ruleDocTitleFormatWithFallback = 'name'; | ||
break; | ||
/* istanbul ignore next -- this shouldn't happen */ | ||
default: | ||
throw new Error(`Unhandled rule doc title format fallback: ${ruleDocTitleFormatWithFallback}`); | ||
} | ||
} | ||
@@ -196,0 +234,0 @@ switch (ruleDocTitleFormatWithFallback) { |
@@ -8,2 +8,3 @@ import type { TSESLint, JSONSchema } from '@typescript-eslint/utils'; | ||
export declare const SEVERITY_ERROR: Set<TSESLint.Linter.RuleLevel>; | ||
export declare const SEVERITY_WARN: Set<TSESLint.Linter.RuleLevel>; | ||
export declare const SEVERITY_OFF: Set<TSESLint.Linter.RuleLevel>; | ||
@@ -10,0 +11,0 @@ export declare type ConfigsToRules = Record<string, Rules>; |
// Custom types. | ||
export const SEVERITY_ERROR = new Set([2, 'error']); | ||
export const SEVERITY_WARN = new Set([1, 'warn']); | ||
export const SEVERITY_OFF = new Set([0, 'off']); | ||
@@ -4,0 +5,0 @@ /** |
{ | ||
"name": "eslint-doc-generator", | ||
"version": "0.15.0", | ||
"version": "0.16.0", | ||
"description": "Automatic documentation generator for ESLint plugins and rules.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -73,2 +73,4 @@ # eslint-doc-generator | ||
🎨 This rule will _warn_ in the `stylistic` config. | ||
🎨 This rule is _disabled_ in the `stylistic` config. | ||
@@ -164,2 +166,3 @@ | ||
| `--rule-doc-section-include` | Required section in each rule doc. Exit with failure if missing. Option can be repeated. | | ||
| `--rule-doc-section-options` | Whether to require an "Options" or "Config" rule doc section and mention of any named options for rules with options (default: `true`). | | ||
| `--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). | | ||
@@ -166,0 +169,0 @@ | `--rule-list-columns` | Ordered, comma-separated list of columns to display in rule list. Empty columns will be hidden. Choices: `configs`, `deprecated`, `description`, `fixable`, `hasSuggestions`, `name`, `requiresTypeChecking`, `type` (off by default). Default: `name,description,configs,fixable,hasSuggestions,requiresTypeChecking,deprecated`. | |
82018
1465
192