eslint-doc-generator
Advanced tools
Comparing version 0.8.1 to 0.9.0
@@ -25,6 +25,8 @@ import { Command, Argument, Option } from 'commander'; | ||
.addArgument(new Argument('[path]', 'path to ESLint plugin root').default('.')) | ||
.option('--check', '(optional) Whether to check for and fail if there is a diff. No output will be written. Typically used during CI.', false) | ||
.option('--config-emoji <config-emoji>', '(optional) Custom emoji to use for a config. Defaults to `recommended,✅`. Configs for which no emoji is specified will expect a corresponding badge to be specified in `README.md` instead. Option can be repeated.', collect, []) | ||
.option('--ignore-config <config>', '(optional) Config to ignore from being displayed (often used for an `all` config) (option can be repeated).', collect, []) | ||
.option('--ignore-deprecated-rules', '(optional) Whether to ignore deprecated rules from being checked, displayed, or updated.', false) | ||
.option('--rule-doc-section-exclude <section>', '(optional) Disallowed section in each rule doc (option can be repeated).', collect, []) | ||
.option('--rule-doc-section-include <section>', '(optional) Required section in each rule doc (option can be repeated).', collect, []) | ||
.option('--rule-doc-section-exclude <section>', '(optional) Disallowed section in each rule doc (option can be repeated).', collect, []) | ||
.addOption(new Option('--rule-doc-title-format <format>', '(optional) The format to use for rule doc titles.') | ||
@@ -36,6 +38,8 @@ .choices(RULE_DOC_TITLE_FORMATS) | ||
await generate(path, { | ||
check: options.check, | ||
configEmoji: options.configEmoji, | ||
ignoreConfig: options.ignoreConfig, | ||
ignoreDeprecatedRules: options.ignoreDeprecatedRules, | ||
ruleDocSectionExclude: options.ruleDocSectionExclude, | ||
ruleDocSectionInclude: options.ruleDocSectionInclude, | ||
ruleDocSectionExclude: options.ruleDocSectionExclude, | ||
ruleDocTitleFormat: options.ruleDocTitleFormat, | ||
@@ -42,0 +46,0 @@ urlConfigs: options.urlConfigs, |
@@ -1,3 +0,2 @@ | ||
import type { Plugin, ConfigsToRules } from './types.js'; | ||
export declare function hasCustomConfigs(plugin: Plugin, ignoreConfig?: string[]): boolean; | ||
import type { Plugin, ConfigsToRules, ConfigEmojis } from './types.js'; | ||
export declare function hasAnyConfigs(configsToRules: ConfigsToRules): boolean; | ||
@@ -9,4 +8,4 @@ /** | ||
/** | ||
* Convert list of configs to string list of formatted names. | ||
* Parse the options, check for errors, and set defaults. | ||
*/ | ||
export declare function configNamesToList(configNames: readonly string[]): string; | ||
export declare function parseConfigEmojiOptions(plugin: Plugin, configEmoji?: string[]): ConfigEmojis; |
@@ -1,6 +0,2 @@ | ||
export function hasCustomConfigs(plugin, ignoreConfig) { | ||
return Object.keys(plugin.configs || {}).some( | ||
// Consider a config custom if not-recommended and not ignored. | ||
(configName) => configName !== 'recommended' && !ignoreConfig?.includes(configName)); | ||
} | ||
import { EMOJI_CONFIG_RECOMMENDED } from './emojis.js'; | ||
export function hasAnyConfigs(configsToRules) { | ||
@@ -31,6 +27,23 @@ return Object.keys(configsToRules).length > 0; | ||
/** | ||
* Convert list of configs to string list of formatted names. | ||
* Parse the options, check for errors, and set defaults. | ||
*/ | ||
export function configNamesToList(configNames) { | ||
return `\`${configNames.join('`, `')}\``; | ||
export function parseConfigEmojiOptions(plugin, configEmoji) { | ||
const configEmojis = configEmoji?.map((configEmojiItem) => { | ||
const [config, emoji] = configEmojiItem.split(','); | ||
if (!config || !emoji) { | ||
throw new Error(`Invalid configEmoji option: ${configEmojiItem}. Expected format: config,emoji`); | ||
} | ||
if (plugin.configs?.[config] === undefined) { | ||
throw new Error(`Invalid configEmoji option: ${config} config not found.`); | ||
} | ||
return { config, emoji }; | ||
}) || []; | ||
// Add default emoji for the common `recommended` config. | ||
if (!configEmojis.some((configEmoji) => configEmoji.config === 'recommended')) { | ||
configEmojis.push({ | ||
config: 'recommended', | ||
emoji: EMOJI_CONFIG_RECOMMENDED, | ||
}); | ||
} | ||
return configEmojis; | ||
} |
import { RuleDocTitleFormat } from './rule-doc-title-format.js'; | ||
export declare function generate(path: string, options?: { | ||
check?: boolean; | ||
configEmoji?: string[]; | ||
ignoreConfig?: string[]; | ||
ignoreDeprecatedRules?: boolean; | ||
ruleDocSectionExclude?: string[]; | ||
ruleDocSectionInclude?: string[]; | ||
ruleDocSectionExclude?: string[]; | ||
ruleDocTitleFormat?: RuleDocTitleFormat; | ||
urlConfigs?: string; | ||
}): Promise<void>; |
@@ -10,2 +10,3 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs'; | ||
import { resolveConfigsToRules } from './config-resolution.js'; | ||
import { parseConfigEmojiOptions } from './configs.js'; | ||
/** | ||
@@ -76,2 +77,5 @@ * Ensure a rule doc contains (or doesn't contain) some particular content. | ||
(details) => !options?.ignoreDeprecatedRules || !details.deprecated); | ||
// Options. | ||
const configEmojis = parseConfigEmojiOptions(plugin, options?.configEmoji); | ||
const ignoreConfig = options?.ignoreConfig ?? []; | ||
// Update rule doc for each rule. | ||
@@ -84,6 +88,14 @@ for (const { name, description, schema } of details) { | ||
// Regenerate the header (title/notices) of each rule doc. | ||
const newHeaderLines = generateRuleHeaderLines(description, name, plugin, configsToRules, pluginPrefix, options?.ignoreConfig, options?.ruleDocTitleFormat, options?.urlConfigs); | ||
const newHeaderLines = generateRuleHeaderLines(description, name, plugin, configsToRules, pluginPrefix, configEmojis, ignoreConfig, options?.ruleDocTitleFormat, options?.urlConfigs); | ||
const contents = readFileSync(pathToDoc).toString(); | ||
const contentsNew = await replaceOrCreateHeader(contents, newHeaderLines, END_RULE_HEADER_MARKER, pathToDoc); | ||
writeFileSync(pathToDoc, contentsNew); | ||
if (options?.check) { | ||
if (contentsNew !== contents) { | ||
console.error(`Please run eslint-doc-generator. A rule doc is out-of-date: ${relative(getPluginRoot(path), pathToDoc)}`); | ||
process.exitCode = 1; | ||
} | ||
} | ||
else { | ||
writeFileSync(pathToDoc, contentsNew); | ||
} | ||
// Check for potential issues with the rule doc. | ||
@@ -110,4 +122,13 @@ // Check for required sections. | ||
// Update the rules list in the README. | ||
const readme = await updateRulesList(details, readFileSync(pathToReadme, 'utf8'), plugin, configsToRules, pluginPrefix, pathToReadme, path, options?.ignoreConfig, options?.urlConfigs); | ||
writeFileSync(pathToReadme, readme, 'utf8'); | ||
const readmeContents = readFileSync(pathToReadme, 'utf8'); | ||
const readmeContentsNew = await updateRulesList(details, readmeContents, plugin, configsToRules, pluginPrefix, pathToReadme, path, configEmojis, ignoreConfig, options?.urlConfigs); | ||
if (options?.check) { | ||
if (readmeContentsNew !== readmeContents) { | ||
console.error(`Please run eslint-doc-generator. ${relative(getPluginRoot(path), pathToReadme)} is out-of-date.`); | ||
process.exitCode = 1; | ||
} | ||
} | ||
else { | ||
writeFileSync(pathToReadme, readmeContentsNew, 'utf8'); | ||
} | ||
} |
import { COLUMN_TYPE } from './rule-list-columns.js'; | ||
export declare function generateLegend(columns: Record<COLUMN_TYPE, boolean>, urlConfigs?: string): string; | ||
import { ConfigEmojis, Plugin } from './types.js'; | ||
export declare function generateLegend(columns: Record<COLUMN_TYPE, boolean>, plugin: Plugin, configEmojis: ConfigEmojis, ignoreConfig: string[], urlConfigs?: string): string; |
@@ -1,24 +0,44 @@ | ||
import { EMOJI_DEPRECATED, EMOJI_FIXABLE, EMOJI_HAS_SUGGESTIONS, EMOJI_CONFIGS, EMOJI_CONFIG_RECOMMENDED, EMOJI_REQUIRES_TYPE_CHECKING, } from './emojis.js'; | ||
import { EMOJI_DEPRECATED, EMOJI_FIXABLE, EMOJI_HAS_SUGGESTIONS, EMOJI_CONFIGS, EMOJI_REQUIRES_TYPE_CHECKING, } from './emojis.js'; | ||
import { COLUMN_TYPE } from './rule-list-columns.js'; | ||
function getMessages(urlConfigs) { | ||
const configsLinkOrWord = urlConfigs | ||
? `[Configurations](${urlConfigs})` | ||
: 'Configurations'; | ||
const configLinkOrWord = urlConfigs | ||
? `[configuration](${urlConfigs})` | ||
: 'configuration'; | ||
const MESSAGES = { | ||
[COLUMN_TYPE.NAME]: undefined, | ||
[COLUMN_TYPE.DESCRIPTION]: undefined, | ||
[COLUMN_TYPE.CONFIGS]: `${EMOJI_CONFIGS} ${configsLinkOrWord} enabled in.`, | ||
[COLUMN_TYPE.CONFIG_RECOMMENDED]: `${EMOJI_CONFIG_RECOMMENDED} Enabled in the \`recommended\` ${configLinkOrWord}.`, | ||
[COLUMN_TYPE.DEPRECATED]: `${EMOJI_DEPRECATED} Deprecated.`, | ||
[COLUMN_TYPE.FIXABLE]: `${EMOJI_FIXABLE} Automatically fixable by the \`--fix\` [CLI option](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems).`, | ||
[COLUMN_TYPE.HAS_SUGGESTIONS]: `${EMOJI_HAS_SUGGESTIONS} Manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).`, | ||
[COLUMN_TYPE.REQUIRES_TYPE_CHECKING]: `${EMOJI_REQUIRES_TYPE_CHECKING} Requires type information.`, | ||
}; | ||
return MESSAGES; | ||
} | ||
export function generateLegend(columns, urlConfigs) { | ||
const MESSAGES = getMessages(urlConfigs); | ||
/** | ||
* An object containing the legends for each column (as a string or function to generate the string). | ||
*/ | ||
const LEGENDS = { | ||
// Legends are included for each config. A generic config legend is also included if there are multiple configs. | ||
[COLUMN_TYPE.CONFIGS]: ({ configNames, configEmojis, urlConfigs, ignoreConfig, }) => { | ||
// Add link to configs documentation if provided. | ||
const configsLinkOrWord = urlConfigs | ||
? `[Configurations](${urlConfigs})` | ||
: 'Configurations'; | ||
const configLinkOrWord = urlConfigs | ||
? `[configuration](${urlConfigs})` | ||
: 'configuration'; | ||
const configNamesWithoutIgnored = configNames.filter((configName) => !ignoreConfig?.includes(configName)); | ||
const legends = []; | ||
if (configNamesWithoutIgnored.length > 1 || | ||
!configEmojis?.find((configEmoji) => configNamesWithoutIgnored?.includes(configEmoji.config))?.emoji) { | ||
// Generic config emoji will be used if the plugin has multiple configs or the sole config has no emoji. | ||
legends.push(`${EMOJI_CONFIGS} ${configsLinkOrWord} enabled in.`); | ||
} | ||
legends.push(...configNames.flatMap((configName) => { | ||
const emoji = configEmojis?.find((configEmoji) => configEmoji.config === configName)?.emoji; | ||
if (!emoji) { | ||
// No legend for this config as it has no emoji. | ||
return []; | ||
} | ||
return [ | ||
`${emoji} Enabled in the \`${configName}\` ${configLinkOrWord}.`, | ||
]; | ||
})); | ||
return legends; | ||
}, | ||
// Simple strings. | ||
[COLUMN_TYPE.DEPRECATED]: `${EMOJI_DEPRECATED} Deprecated.`, | ||
[COLUMN_TYPE.DESCRIPTION]: undefined, | ||
[COLUMN_TYPE.FIXABLE]: `${EMOJI_FIXABLE} Automatically fixable by the \`--fix\` [CLI option](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems).`, | ||
[COLUMN_TYPE.HAS_SUGGESTIONS]: `${EMOJI_HAS_SUGGESTIONS} Manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).`, | ||
[COLUMN_TYPE.NAME]: undefined, | ||
[COLUMN_TYPE.REQUIRES_TYPE_CHECKING]: `${EMOJI_REQUIRES_TYPE_CHECKING} Requires type information.`, | ||
}; | ||
export function generateLegend(columns, plugin, configEmojis, ignoreConfig, urlConfigs) { | ||
return Object.entries(columns) | ||
@@ -30,9 +50,18 @@ .flatMap(([columnType, enabled]) => { | ||
} | ||
if (!MESSAGES[columnType]) { | ||
const legendStrOrFn = LEGENDS[columnType]; | ||
if (!legendStrOrFn) { | ||
// No legend specified for this column. | ||
return []; | ||
} | ||
return [MESSAGES[columnType]]; | ||
const configNames = Object.keys(plugin.configs || {}); | ||
return typeof legendStrOrFn === 'function' | ||
? legendStrOrFn({ | ||
configNames, | ||
configEmojis, | ||
urlConfigs, | ||
ignoreConfig, | ||
}) | ||
: [legendStrOrFn]; | ||
}) | ||
.join('\\\n'); // Back slash ensures these end up displayed on separate lines. | ||
} |
@@ -1,5 +0,4 @@ | ||
import type { Plugin, RuleDetails, ConfigsToRules } from './types.js'; | ||
import type { RuleDetails, ConfigsToRules, ConfigEmojis } from './types.js'; | ||
export declare enum COLUMN_TYPE { | ||
CONFIGS = "configs", | ||
CONFIG_RECOMMENDED = "configRecommended", | ||
DEPRECATED = "deprecated", | ||
@@ -12,4 +11,11 @@ DESCRIPTION = "description", | ||
} | ||
/** | ||
* An object containing the column header for each column (as a string or function to generate the string). | ||
*/ | ||
export declare const COLUMN_HEADER: { | ||
[key in COLUMN_TYPE]: string; | ||
[key in COLUMN_TYPE]: string | ((data: { | ||
configNames: string[]; | ||
configEmojis: ConfigEmojis; | ||
ignoreConfig: string[]; | ||
}) => string); | ||
}; | ||
@@ -20,5 +26,4 @@ /** | ||
*/ | ||
export declare function getColumns(details: RuleDetails[], plugin: Plugin, configsToRules: ConfigsToRules, ignoreConfig?: string[]): { | ||
export declare function getColumns(details: RuleDetails[], configsToRules: ConfigsToRules, ignoreConfig: string[]): { | ||
configs: boolean; | ||
configRecommended: boolean; | ||
deprecated: boolean; | ||
@@ -25,0 +30,0 @@ description: boolean; |
@@ -1,7 +0,5 @@ | ||
import { hasCustomConfigs, hasAnyConfigs } from './configs.js'; | ||
import { EMOJI_CONFIG_RECOMMENDED, EMOJI_DEPRECATED, EMOJI_FIXABLE, EMOJI_HAS_SUGGESTIONS, EMOJI_REQUIRES_TYPE_CHECKING, EMOJI_CONFIGS, } from './emojis.js'; | ||
import { EMOJI_CONFIGS, EMOJI_DEPRECATED, EMOJI_FIXABLE, EMOJI_HAS_SUGGESTIONS, EMOJI_REQUIRES_TYPE_CHECKING, } from './emojis.js'; | ||
export var COLUMN_TYPE; | ||
(function (COLUMN_TYPE) { | ||
COLUMN_TYPE["CONFIGS"] = "configs"; | ||
COLUMN_TYPE["CONFIG_RECOMMENDED"] = "configRecommended"; | ||
COLUMN_TYPE["DEPRECATED"] = "deprecated"; | ||
@@ -14,5 +12,14 @@ COLUMN_TYPE["DESCRIPTION"] = "description"; | ||
})(COLUMN_TYPE || (COLUMN_TYPE = {})); | ||
/** | ||
* An object containing the column header for each column (as a string or function to generate the string). | ||
*/ | ||
export const COLUMN_HEADER = { | ||
[COLUMN_TYPE.CONFIGS]: EMOJI_CONFIGS, | ||
[COLUMN_TYPE.CONFIG_RECOMMENDED]: EMOJI_CONFIG_RECOMMENDED, | ||
// Use the general config emoji if there are multiple configs or the sole config doesn't have an emoji. | ||
[COLUMN_TYPE.CONFIGS]: ({ configNames, configEmojis, ignoreConfig, }) => { | ||
const configNamesWithoutIgnored = configNames.filter((configName) => !ignoreConfig?.includes(configName)); | ||
return configNamesWithoutIgnored.length > 1 | ||
? EMOJI_CONFIGS | ||
: configEmojis?.find((configEmoji) => configNamesWithoutIgnored.includes(configEmoji.config))?.emoji ?? EMOJI_CONFIGS; | ||
}, | ||
// Simple strings. | ||
[COLUMN_TYPE.DEPRECATED]: EMOJI_DEPRECATED, | ||
@@ -29,3 +36,3 @@ [COLUMN_TYPE.DESCRIPTION]: 'Description', | ||
*/ | ||
export function getColumns(details, plugin, configsToRules, ignoreConfig) { | ||
export function getColumns(details, configsToRules, ignoreConfig) { | ||
const columns = { | ||
@@ -35,4 +42,4 @@ // Object keys in display order. | ||
[COLUMN_TYPE.DESCRIPTION]: details.some((detail) => detail.description), | ||
[COLUMN_TYPE.CONFIGS]: hasCustomConfigs(plugin, ignoreConfig), | ||
[COLUMN_TYPE.CONFIG_RECOMMENDED]: !hasCustomConfigs(plugin, ignoreConfig) && hasAnyConfigs(configsToRules), | ||
// Show the configs column if there exists a non-ignored config. | ||
[COLUMN_TYPE.CONFIGS]: Object.keys(configsToRules).some((config) => !ignoreConfig?.includes(config)), | ||
[COLUMN_TYPE.FIXABLE]: details.some((detail) => detail.fixable), | ||
@@ -39,0 +46,0 @@ [COLUMN_TYPE.HAS_SUGGESTIONS]: details.some((detail) => detail.hasSuggestions), |
@@ -1,2 +0,2 @@ | ||
import type { Plugin, RuleDetails, ConfigsToRules } from './types.js'; | ||
export declare function updateRulesList(details: RuleDetails[], markdown: string, plugin: Plugin, configsToRules: ConfigsToRules, pluginPrefix: string, pathToReadme: string, pathToPlugin: string, ignoreConfig?: string[], urlConfigs?: string): Promise<string>; | ||
import type { Plugin, RuleDetails, ConfigsToRules, ConfigEmojis } from './types.js'; | ||
export declare function updateRulesList(details: RuleDetails[], markdown: string, plugin: Plugin, configsToRules: ConfigsToRules, pluginPrefix: string, pathToReadme: string, pathToPlugin: string, configEmojis: ConfigEmojis, ignoreConfig: string[], urlConfigs?: string): Promise<string>; |
import { BEGIN_RULE_LIST_MARKER, END_RULE_LIST_MARKER } from './markers.js'; | ||
import { EMOJI_CONFIG_RECOMMENDED, EMOJI_DEPRECATED, EMOJI_FIXABLE, EMOJI_HAS_SUGGESTIONS, EMOJI_REQUIRES_TYPE_CHECKING, } from './emojis.js'; | ||
import { EMOJI_DEPRECATED, EMOJI_FIXABLE, EMOJI_HAS_SUGGESTIONS, EMOJI_REQUIRES_TYPE_CHECKING, } from './emojis.js'; | ||
import { getConfigsForRule, hasAnyConfigs } from './configs.js'; | ||
@@ -9,3 +9,3 @@ import { COLUMN_TYPE, getColumns, COLUMN_HEADER } from './rule-list-columns.js'; | ||
import { relative } from 'node:path'; | ||
function getConfigurationColumnValueForRule(rule, configsToRules, pluginPrefix, ignoreConfig) { | ||
function getConfigurationColumnValueForRule(rule, configsToRules, pluginPrefix, configEmojis, ignoreConfig) { | ||
const badges = []; | ||
@@ -18,11 +18,9 @@ const configs = getConfigsForRule(rule.name, configsToRules, pluginPrefix); | ||
} | ||
// Use the standard `recommended` emoji for that config. | ||
// For other config names, the user can manually define a badge image. | ||
badges.push(configName === 'recommended' | ||
? EMOJI_CONFIG_RECOMMENDED | ||
: `![${configName}][]`); | ||
// Find the emoji for the config or otherwise use a badge that can be defined in markdown. | ||
const emoji = configEmojis?.find((configEmoji) => configEmoji.config === configName)?.emoji; | ||
badges.push(emoji ?? `![${configName}][]`); | ||
} | ||
return badges.join(' '); | ||
} | ||
function buildRuleRow(columnsEnabled, rule, configsToRules, pluginPrefix, includeTypesColumn, ignoreConfig) { | ||
function buildRuleRow(columnsEnabled, rule, configsToRules, pluginPrefix, includeTypesColumn, configEmojis, ignoreConfig) { | ||
const columns = []; | ||
@@ -35,6 +33,4 @@ if (columnsEnabled[COLUMN_TYPE.NAME]) { | ||
} | ||
if ((columnsEnabled[COLUMN_TYPE.CONFIGS] || | ||
columnsEnabled[COLUMN_TYPE.CONFIG_RECOMMENDED]) && | ||
hasAnyConfigs(configsToRules)) { | ||
columns.push(getConfigurationColumnValueForRule(rule, configsToRules, pluginPrefix, ignoreConfig)); | ||
if (columnsEnabled[COLUMN_TYPE.CONFIGS] && hasAnyConfigs(configsToRules)) { | ||
columns.push(getConfigurationColumnValueForRule(rule, configsToRules, pluginPrefix, configEmojis, ignoreConfig)); | ||
} | ||
@@ -56,3 +52,3 @@ if (columnsEnabled[COLUMN_TYPE.FIXABLE]) { | ||
} | ||
function generateRulesListMarkdown(columns, details, configsToRules, pluginPrefix, ignoreConfig) { | ||
function generateRulesListMarkdown(columns, details, configsToRules, pluginPrefix, configEmojis, ignoreConfig) { | ||
// Since such rules are rare, we'll only include the types column if at least one rule requires type checking. | ||
@@ -64,3 +60,12 @@ const includeTypesColumn = details.some((detail) => detail.requiresTypeChecking); | ||
} | ||
return [COLUMN_HEADER[columnType]]; | ||
const headerStrOrFn = COLUMN_HEADER[columnType]; | ||
return [ | ||
typeof headerStrOrFn === 'function' | ||
? headerStrOrFn({ | ||
configNames: Object.keys(configsToRules), | ||
configEmojis, | ||
ignoreConfig, | ||
}) | ||
: headerStrOrFn, | ||
]; | ||
}); | ||
@@ -73,3 +78,3 @@ const listSpacerRow = Array.from({ length: listHeaderRow.length }).fill(':--'); // Left-align header with colon. | ||
.sort(({ name: a }, { name: b }) => a.localeCompare(b)) | ||
.map((rule) => buildRuleRow(columns, rule, configsToRules, pluginPrefix, includeTypesColumn, ignoreConfig)), | ||
.map((rule) => buildRuleRow(columns, rule, configsToRules, pluginPrefix, includeTypesColumn, configEmojis, ignoreConfig)), | ||
] | ||
@@ -79,3 +84,3 @@ .map((column) => [...column, ' '].join('|')) | ||
} | ||
export async function updateRulesList(details, markdown, plugin, configsToRules, pluginPrefix, pathToReadme, pathToPlugin, ignoreConfig, urlConfigs) { | ||
export async function updateRulesList(details, markdown, plugin, configsToRules, pluginPrefix, pathToReadme, pathToPlugin, configEmojis, ignoreConfig, urlConfigs) { | ||
let listStartIndex = markdown.indexOf(BEGIN_RULE_LIST_MARKER); | ||
@@ -106,9 +111,9 @@ let listEndIndex = markdown.indexOf(END_RULE_LIST_MARKER); | ||
// Determine columns to include in the rules list. | ||
const columns = getColumns(details, plugin, configsToRules, ignoreConfig); | ||
const columns = getColumns(details, configsToRules, ignoreConfig); | ||
// New legend. | ||
const legend = generateLegend(columns, urlConfigs); | ||
const legend = generateLegend(columns, plugin, configEmojis, ignoreConfig, urlConfigs); | ||
// New rule list. | ||
const list = generateRulesListMarkdown(columns, details, configsToRules, pluginPrefix, ignoreConfig); | ||
const list = generateRulesListMarkdown(columns, details, configsToRules, pluginPrefix, configEmojis, ignoreConfig); | ||
const newContent = await format(`${legend}\n\n${list}`, pathToReadme); | ||
return `${preList}${BEGIN_RULE_LIST_MARKER}\n\n${newContent}\n\n${END_RULE_LIST_MARKER}${postList}`; | ||
} |
@@ -1,2 +0,2 @@ | ||
import type { Plugin, ConfigsToRules } from './types.js'; | ||
import type { Plugin, ConfigsToRules, ConfigEmojis } from './types.js'; | ||
import { RuleDocTitleFormat } from './rule-doc-title-format.js'; | ||
@@ -7,2 +7,2 @@ /** | ||
*/ | ||
export declare function generateRuleHeaderLines(description: string | undefined, name: string, plugin: Plugin, configsToRules: ConfigsToRules, pluginPrefix: string, ignoreConfig?: string[], ruleDocTitleFormat?: RuleDocTitleFormat, urlConfigs?: string): string; | ||
export declare function generateRuleHeaderLines(description: string | undefined, name: string, plugin: Plugin, configsToRules: ConfigsToRules, pluginPrefix: string, configEmojis: ConfigEmojis, ignoreConfig: string[], ruleDocTitleFormat?: RuleDocTitleFormat, urlConfigs?: string): string; |
import { END_RULE_HEADER_MARKER } from './markers.js'; | ||
import { EMOJI_DEPRECATED, EMOJI_FIXABLE, EMOJI_HAS_SUGGESTIONS, EMOJI_CONFIGS, EMOJI_CONFIG_RECOMMENDED, } from './emojis.js'; | ||
import { getConfigsForRule, configNamesToList } from './configs.js'; | ||
import { EMOJI_DEPRECATED, EMOJI_FIXABLE, EMOJI_HAS_SUGGESTIONS, EMOJI_CONFIGS, } from './emojis.js'; | ||
import { getConfigsForRule } from './configs.js'; | ||
import { RULE_DOC_TITLE_FORMAT_DEFAULT, } from './rule-doc-title-format.js'; | ||
@@ -8,3 +8,2 @@ var MESSAGE_TYPE; | ||
MESSAGE_TYPE["CONFIGS"] = "configs"; | ||
MESSAGE_TYPE["CONFIG_RECOMMENDED"] = "configRecommended"; | ||
MESSAGE_TYPE["DEPRECATED"] = "deprecated"; | ||
@@ -14,15 +13,42 @@ MESSAGE_TYPE["FIXABLE"] = "fixable"; | ||
})(MESSAGE_TYPE || (MESSAGE_TYPE = {})); | ||
function getMessages(urlConfigs) { | ||
const configsLinkOrWord = urlConfigs ? `[configs](${urlConfigs})` : 'configs'; | ||
const configLinkOrWord = urlConfigs ? `[config](${urlConfigs})` : 'config'; | ||
const MESSAGES = { | ||
[MESSAGE_TYPE.CONFIGS]: `${EMOJI_CONFIGS} This rule is enabled in the following ${configsLinkOrWord}:`, | ||
[MESSAGE_TYPE.CONFIG_RECOMMENDED]: `${EMOJI_CONFIG_RECOMMENDED} This rule is enabled in the \`recommended\` ${configLinkOrWord}.`, | ||
[MESSAGE_TYPE.DEPRECATED]: `${EMOJI_DEPRECATED} This rule is deprecated.`, | ||
[MESSAGE_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).`, | ||
[MESSAGE_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).`, | ||
}; | ||
return MESSAGES; | ||
} | ||
/** | ||
* An object containing the text for each notice type (as a string or function to generate the string). | ||
*/ | ||
const RULE_NOTICES = { | ||
// Configs notice varies based on whether the rule is enabled in one or more configs. | ||
[MESSAGE_TYPE.CONFIGS]: ({ configsEnabled, configEmojis, urlConfigs, }) => { | ||
// Add link to configs documentation if provided. | ||
const configsLinkOrWord = urlConfigs | ||
? `[configs](${urlConfigs})` | ||
: 'configs'; | ||
const configLinkOrWord = urlConfigs ? `[config](${urlConfigs})` : 'config'; | ||
/* istanbul ignore next -- this shouldn't happen */ | ||
if (!configsEnabled || configsEnabled.length === 0) { | ||
throw new Error('Should not be trying to display config notice for rule not enabled in any configs.'); | ||
} | ||
if (configsEnabled.length > 1) { | ||
// Rule is enabled in multiple configs. | ||
const configs = configsEnabled | ||
.map((configEnabled) => { | ||
const emoji = configEmojis?.find((configEmoji) => configEmoji.config === configEnabled)?.emoji; | ||
return `${emoji ? `${emoji} ` : ''}\`${configEnabled}\``; | ||
}) | ||
.join(', '); | ||
return `${EMOJI_CONFIGS} This rule is enabled in the following ${configsLinkOrWord}: ${configs}.`; | ||
} | ||
else { | ||
// Rule only enabled in one config. | ||
const emoji = configEmojis?.find((configEmoji) => configEmoji.config === configsEnabled?.[0])?.emoji ?? EMOJI_CONFIGS; | ||
return `${emoji} This rule is enabled in the \`${configsEnabled?.[0]}\` ${configLinkOrWord}.`; | ||
} | ||
}, | ||
// Deprecated notice has optional "replaced by" rules list. | ||
[MESSAGE_TYPE.DEPRECATED]: ({ replacedBy, }) => `${EMOJI_DEPRECATED} This rule is deprecated.${replacedBy && replacedBy.length > 0 | ||
? ` It was replaced by ${ruleNamesToList(replacedBy)}.` | ||
: ''}`, | ||
// Simple strings. | ||
[MESSAGE_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).`, | ||
[MESSAGE_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).`, | ||
}; | ||
/** | ||
* Convert list of rule names to string list of links. | ||
@@ -40,5 +66,3 @@ */ | ||
const notices = { | ||
[MESSAGE_TYPE.CONFIGS]: configsEnabled.length > 1 || | ||
(configsEnabled.length === 1 && configsEnabled[0] !== 'recommended'), | ||
[MESSAGE_TYPE.CONFIG_RECOMMENDED]: configsEnabled.length === 1 && configsEnabled[0] === 'recommended', | ||
[MESSAGE_TYPE.CONFIGS]: configsEnabled.length > 0, | ||
[MESSAGE_TYPE.DEPRECATED]: rule.meta.deprecated || false, | ||
@@ -53,3 +77,3 @@ [MESSAGE_TYPE.FIXABLE]: Boolean(rule.meta.fixable), | ||
*/ | ||
function getRuleNoticeLines(ruleName, plugin, configsToRules, pluginPrefix, ignoreConfig, urlConfigs) { | ||
function getRuleNoticeLines(ruleName, plugin, configsToRules, pluginPrefix, configEmojis, ignoreConfig, urlConfigs) { | ||
const lines = []; | ||
@@ -77,20 +101,11 @@ const rule = plugin.rules?.[ruleName]; | ||
lines.push(''); // Blank line first. | ||
const MESSAGES = getMessages(urlConfigs); | ||
if (messageType === MESSAGE_TYPE.CONFIGS) { | ||
// This notice should have a list of the rule's configs. | ||
const message = `${MESSAGES[MESSAGE_TYPE.CONFIGS]} ${configNamesToList(configsEnabled)}.`; | ||
lines.push(message); | ||
} | ||
else if (messageType === MESSAGE_TYPE.DEPRECATED) { | ||
// This notice should include links to the replacement rule(s) if available. | ||
const message = typeof rule === 'object' && | ||
Array.isArray(rule.meta.replacedBy) && | ||
rule.meta.replacedBy.length > 0 | ||
? `${MESSAGES[messageType]} It was replaced by ${ruleNamesToList(rule.meta.replacedBy)}.` | ||
: MESSAGES[messageType]; | ||
lines.push(message); | ||
} | ||
else { | ||
lines.push(MESSAGES[messageType]); | ||
} | ||
const ruleNoticeStrOrFn = RULE_NOTICES[messageType]; | ||
lines.push(typeof ruleNoticeStrOrFn === 'function' | ||
? ruleNoticeStrOrFn({ | ||
configsEnabled, | ||
configEmojis, | ||
urlConfigs, | ||
replacedBy: rule.meta.replacedBy, | ||
}) | ||
: ruleNoticeStrOrFn); | ||
} | ||
@@ -135,6 +150,6 @@ return lines; | ||
*/ | ||
export function generateRuleHeaderLines(description, name, plugin, configsToRules, pluginPrefix, ignoreConfig, ruleDocTitleFormat, urlConfigs) { | ||
export function generateRuleHeaderLines(description, name, plugin, configsToRules, pluginPrefix, configEmojis, ignoreConfig, ruleDocTitleFormat, urlConfigs) { | ||
return [ | ||
makeTitle(name, description, pluginPrefix, ruleDocTitleFormat), | ||
...getRuleNoticeLines(name, plugin, configsToRules, pluginPrefix, ignoreConfig, urlConfigs), | ||
...getRuleNoticeLines(name, plugin, configsToRules, pluginPrefix, configEmojis, ignoreConfig, urlConfigs), | ||
'', | ||
@@ -141,0 +156,0 @@ END_RULE_HEADER_MARKER, |
@@ -16,1 +16,8 @@ import type { TSESLint, JSONSchema } from '@typescript-eslint/utils'; | ||
} | ||
/** | ||
* Some configs may have an emoji defined. | ||
*/ | ||
export declare type ConfigEmojis = { | ||
config: string; | ||
emoji: string; | ||
}[]; |
{ | ||
"name": "eslint-doc-generator", | ||
"version": "0.8.1", | ||
"version": "0.9.0", | ||
"description": "Automatic documentation generator for ESLint plugins and rules.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -24,3 +24,3 @@ # eslint-doc-generator | ||
Add it as as script in `package.json` (included as a lint script to demonstrate how we can ensure it passes and is up-to-date in CI): | ||
Add scripts to `package.json` (both a lint script to ensure everything is up-to-date in CI and an update script) (add any config options in the `update:eslint-docs` script): | ||
@@ -32,3 +32,3 @@ ```json | ||
"lint:docs": "markdownlint \"**/*.md\"", | ||
"lint:eslint-docs": "npm-run-all update:eslint-docs && git diff --exit-code", | ||
"lint:eslint-docs": "npm-run-all \"update:eslint-docs --check\"", | ||
"lint:js": "eslint .", | ||
@@ -51,3 +51,3 @@ "update:eslint-docs": "eslint-doc-generator" | ||
Run the script from `package.json`: | ||
Run the script from `package.json` to start out or any time you add a rule or update rule metadata in your plugin: | ||
@@ -65,3 +65,3 @@ ```sh | ||
💼 This rule is enabled in the following configs: `all`, `recommended`. | ||
💼 This rule is enabled in the following configs: `all`, ✅ `recommended`. | ||
@@ -101,3 +101,3 @@ 🔧 This rule is automatically fixable by the `--fix` [CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). | ||
| Rule | Description | ✅ | 🔧 | 💡 | 💭 | | ||
| Name | Description | ✅ | 🔧 | 💡 | 💭 | | ||
| :------------------------------------------------------------- | :------------------------------------------------ | :-- | :-- | :-- | :-- | | ||
@@ -115,4 +115,8 @@ | [max-nested-describe](docs/rules/max-nested-describe.md) | Enforces a maximum depth to nested describe calls | | | | | | ||
If you have any custom configs (besides `all`, `recommended`), you'll need to define a badge for them at the bottom of your `README.md`. Here's a badge for a custom `style` config that displays in blue: | ||
## Badge | ||
If you have any custom configs (besides `recommended`), you'll need to either define emojis for them with `--config-emoji`, or define badges for them at the bottom of your `README.md`. | ||
Here's a badge for a custom `style` config that displays in blue: | ||
```md | ||
@@ -122,5 +126,8 @@ [style]: https://img.shields.io/badge/-style-blue.svg | ||
[npm-image]: https://badge.fury.io/js/eslint-doc-generator.svg | ||
[npm-url]: https://www.npmjs.com/package/eslint-doc-generator | ||
And how it looks: | ||
![style][] | ||
[style]: https://img.shields.io/badge/-style-blue.svg | ||
## Configuration options | ||
@@ -130,13 +137,25 @@ | ||
| :-- | :-- | | ||
| `--ignore-config` | (optional) Config to ignore from being displayed (often used for an `all` config) (option can be repeated). | | ||
| `--ignore-deprecated-rules` | (optional) Whether to ignore deprecated rules from being checked, displayed, or updated (default: `false`). | | ||
| `--rule-doc-section-include` | (optional) Required section in each rule doc (option can be repeated). | | ||
| `--rule-doc-section-exclude` | (optional) Disallowed section in each rule doc (option can be repeated). | | ||
| `--rule-doc-title-format` | (optional) The format to use for rule doc titles. Choices: `desc-parens-prefix-name` (default), `desc`, `desc-parens-name`, `name`, `prefix-name`. | | ||
| `--url-configs` | (optional) Link to documentation about the ESLint configurations exported by the plugin. | | ||
| `--check` | Whether to check for and fail if there is a diff. No output will be written. Typically used during CI. | | ||
| `--config-emoji` | Custom emoji to use for a config. Defaults to `recommended,✅`. Configs for which no emoji is specified will expect a corresponding [badge](#badge) to be specified in `README.md` instead. Option can be repeated. | | ||
| `--ignore-config` | Config to ignore from being displayed. Often used for an `all` config. Option can be repeated. | | ||
| `--ignore-deprecated-rules` | Whether to ignore deprecated rules from being checked, displayed, or updated (default: `false`). | | ||
| `--rule-doc-section-exclude` | Disallowed section in each rule doc. Exit with failure if present. Option can be repeated. | | ||
| `--rule-doc-section-include` | Required section in each rule doc. Exit with failure if missing. Option can be repeated. | | ||
| `--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). | | ||
| `--url-configs` | Link to documentation about the ESLint configurations exported by the plugin. | | ||
## Upcoming features | ||
All options are optional. | ||
- Custom config emojis ([#34](https://github.com/bmish/eslint-doc-generator/issues/34)) | ||
### `--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. | ||
| 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)` | | ||
| `name` | `# no-foo` | | ||
`prefix-name` | `# test/no-foo` | | ||
## Related | ||
@@ -146,1 +165,4 @@ | ||
- [generator-eslint](https://github.com/eslint/generator-eslint) - Generates initial ESLint plugin and rule files but without the sophisticated documentation provided by eslint-doc-generator | ||
[npm-image]: https://badge.fury.io/js/eslint-doc-generator.svg | ||
[npm-url]: https://www.npmjs.com/package/eslint-doc-generator |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
54272
972
160