Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

eslint-doc-generator

Package Overview
Dependencies
Maintainers
1
Versions
62
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-doc-generator - npm Package Compare versions

Comparing version 0.8.1 to 0.9.0

8

dist/lib/cli.js

@@ -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
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc