Skip to content

Commit

Permalink
Merge pull request #129 from bmish/configurable-columns
Browse files Browse the repository at this point in the history
  • Loading branch information
bmish committed Oct 13, 2022
2 parents 9756bff + f48dd08 commit d6e2fab
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 53 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ And how it looks:
| `--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). |
| `--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`. Default: `name,description,configs,fixable,hasSuggestions,requiresTypeChecking,deprecated`. |
| `--url-configs` | Link to documentation about the ESLint configurations exported by the plugin. |

All options are optional.
Expand Down
13 changes: 13 additions & 0 deletions lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import {
RULE_DOC_TITLE_FORMAT_DEFAULT,
RULE_DOC_TITLE_FORMATS,
} from './rule-doc-title-format.js';
import {
COLUMN_TYPE,
COLUMN_TYPE_DEFAULT_ORDERING,
} from './rule-list-columns.js';
import type { PackageJson } from 'type-fest';

const __dirname = dirname(fileURLToPath(import.meta.url));
Expand Down Expand Up @@ -77,6 +81,13 @@ export function run() {
.choices(RULE_DOC_TITLE_FORMATS)
.default(RULE_DOC_TITLE_FORMAT_DEFAULT)
)
.option(
'--rule-list-columns <columns>',
`(optional) Ordered, comma-separated list of columns to display in rule list. Empty columns will be hidden. (choices: "${Object.values(
COLUMN_TYPE
).join('", "')}")`,
COLUMN_TYPE_DEFAULT_ORDERING.join(',')
)
.option(
'--url-configs <url>',
'(optional) Link to documentation about the ESLint configurations exported by the plugin.'
Expand All @@ -91,6 +102,7 @@ export function run() {
ruleDocSectionExclude: string[];
ruleDocSectionInclude: string[];
ruleDocTitleFormat: RuleDocTitleFormat;
ruleListColumns: string;
urlConfigs?: string;
}
) {
Expand All @@ -102,6 +114,7 @@ export function run() {
ruleDocSectionExclude: options.ruleDocSectionExclude,
ruleDocSectionInclude: options.ruleDocSectionInclude,
ruleDocTitleFormat: options.ruleDocTitleFormat,
ruleListColumns: options.ruleListColumns,
urlConfigs: options.urlConfigs,
});
})
Expand Down
4 changes: 0 additions & 4 deletions lib/configs.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { EMOJI_CONFIG_RECOMMENDED } from './emojis.js';
import type { Plugin, ConfigsToRules, ConfigEmojis } from './types.js';

export function hasAnyConfigs(configsToRules: ConfigsToRules) {
return Object.keys(configsToRules).length > 0;
}

const SEVERITY_ENABLED = new Set([2, 'error']);

/**
Expand Down
4 changes: 4 additions & 0 deletions lib/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { findSectionHeader, replaceOrCreateHeader } from './markdown.js';
import { resolveConfigsToRules } from './config-resolution.js';
import { RuleDocTitleFormat } from './rule-doc-title-format.js';
import { parseConfigEmojiOptions } from './configs.js';
import { parseRuleListColumnsOption } from './rule-list-columns.js';
import type { RuleDetails } from './types.js';

/**
Expand Down Expand Up @@ -81,6 +82,7 @@ export async function generate(
ruleDocSectionExclude?: string[];
ruleDocSectionInclude?: string[];
ruleDocTitleFormat?: RuleDocTitleFormat;
ruleListColumns?: string;
urlConfigs?: string;
}
) {
Expand Down Expand Up @@ -127,6 +129,7 @@ export async function generate(
// Options.
const configEmojis = parseConfigEmojiOptions(plugin, options?.configEmoji);
const ignoreConfig = options?.ignoreConfig ?? [];
const ruleListColumns = parseRuleListColumnsOption(options?.ruleListColumns);

// Update rule doc for each rule.
for (const { name, description, schema } of details) {
Expand Down Expand Up @@ -215,6 +218,7 @@ export async function generate(
path,
configEmojis,
ignoreConfig,
ruleListColumns,
options?.urlConfigs
);

Expand Down
54 changes: 47 additions & 7 deletions lib/rule-list-columns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,20 @@ export enum COLUMN_TYPE {
DESCRIPTION = 'description',
FIXABLE = 'fixable',
HAS_SUGGESTIONS = 'hasSuggestions',
NAME = 'rules',
NAME = 'name',
REQUIRES_TYPE_CHECKING = 'requiresTypeChecking',
}

export const COLUMN_TYPE_DEFAULT_ORDERING = [
COLUMN_TYPE.NAME,
COLUMN_TYPE.DESCRIPTION,
COLUMN_TYPE.CONFIGS,
COLUMN_TYPE.FIXABLE,
COLUMN_TYPE.HAS_SUGGESTIONS,
COLUMN_TYPE.REQUIRES_TYPE_CHECKING,
COLUMN_TYPE.DEPRECATED,
];

/**
* An object containing the column header for each column (as a string or function to generate the string).
*/
Expand Down Expand Up @@ -66,27 +76,57 @@ export const COLUMN_HEADER: {
export function getColumns(
details: RuleDetails[],
configsToRules: ConfigsToRules,
ruleListColumns: COLUMN_TYPE[],
ignoreConfig: string[]
) {
): Record<COLUMN_TYPE, boolean> {
const columns: {
[key in COLUMN_TYPE]: boolean;
} = {
// Object keys in display order.
[COLUMN_TYPE.NAME]: true,
[COLUMN_TYPE.DESCRIPTION]: details.some((detail) => detail.description),
// Alphabetical order.
// Show the configs column if there exists a non-ignored config.
[COLUMN_TYPE.CONFIGS]: Object.keys(configsToRules).some(
(config) => !ignoreConfig?.includes(config)
),
[COLUMN_TYPE.DEPRECATED]: details.some((detail) => detail.deprecated),
[COLUMN_TYPE.DESCRIPTION]: details.some((detail) => detail.description),
[COLUMN_TYPE.FIXABLE]: details.some((detail) => detail.fixable),
[COLUMN_TYPE.HAS_SUGGESTIONS]: details.some(
(detail) => detail.hasSuggestions
),
[COLUMN_TYPE.NAME]: true,
[COLUMN_TYPE.REQUIRES_TYPE_CHECKING]: details.some(
(detail) => detail.requiresTypeChecking
),
[COLUMN_TYPE.DEPRECATED]: details.some((detail) => detail.deprecated),
};

return columns;
// Recreate object using the ordering and presence of columns specified in ruleListColumns.
return Object.fromEntries(
ruleListColumns.map((column) => [column, columns[column]])
) as Record<COLUMN_TYPE, boolean>;
}

/**
* Parse the option, check for errors, and set defaults.
*/
export function parseRuleListColumnsOption(
ruleListColumns: string | undefined
): COLUMN_TYPE[] {
const values = ruleListColumns ? ruleListColumns.split(',') : [];
const COLUMN_TYPE_VALUES = new Set(Object.values(COLUMN_TYPE).map(String));

// Check for invalid.
const invalid = values.find((val) => !COLUMN_TYPE_VALUES.has(val));
if (invalid) {
throw new Error(`Invalid ruleListColumns option: ${invalid}`);
}
if (values.length !== new Set(values).size) {
throw new Error('Duplicate value detected in ruleListColumns option.');
}

if (values.length === 0) {
// Use default columns and ordering.
values.push(...COLUMN_TYPE_DEFAULT_ORDERING);
}

return values as COLUMN_TYPE[];
}
79 changes: 37 additions & 42 deletions lib/rule-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
EMOJI_HAS_SUGGESTIONS,
EMOJI_REQUIRES_TYPE_CHECKING,
} from './emojis.js';
import { getConfigsForRule, hasAnyConfigs } from './configs.js';
import { getConfigsForRule } from './configs.js';
import { COLUMN_TYPE, getColumns, COLUMN_HEADER } from './rule-list-columns.js';
import { findSectionHeader, format } from './markdown.js';
import { getPluginRoot } from './package-json.js';
Expand Down Expand Up @@ -46,44 +46,38 @@ function buildRuleRow(
rule: RuleDetails,
configsToRules: ConfigsToRules,
pluginPrefix: string,
includeTypesColumn: boolean,
configEmojis: ConfigEmojis,
ignoreConfig: string[]
): string[] {
const columns: string[] = [];
if (columnsEnabled[COLUMN_TYPE.NAME]) {
columns.push(`[${rule.name}](docs/rules/${rule.name}.md)`);
}
if (columnsEnabled[COLUMN_TYPE.DESCRIPTION]) {
columns.push(rule.description || '');
}
if (columnsEnabled[COLUMN_TYPE.CONFIGS] && hasAnyConfigs(configsToRules)) {
columns.push(
getConfigurationColumnValueForRule(
rule,
configsToRules,
pluginPrefix,
configEmojis,
ignoreConfig
)
);
}
if (columnsEnabled[COLUMN_TYPE.FIXABLE]) {
columns.push(rule.fixable ? EMOJI_FIXABLE : '');
}
if (columnsEnabled[COLUMN_TYPE.HAS_SUGGESTIONS]) {
columns.push(rule.hasSuggestions ? EMOJI_HAS_SUGGESTIONS : '');
}
if (
columnsEnabled[COLUMN_TYPE.REQUIRES_TYPE_CHECKING] &&
includeTypesColumn
) {
columns.push(rule.requiresTypeChecking ? EMOJI_REQUIRES_TYPE_CHECKING : '');
}
if (columnsEnabled[COLUMN_TYPE.DEPRECATED] && rule.deprecated) {
columns.push(EMOJI_DEPRECATED);
}
return columns;
const columns: {
[key in COLUMN_TYPE]: string;
} = {
// Alphabetical order.
[COLUMN_TYPE.CONFIGS]: getConfigurationColumnValueForRule(
rule,
configsToRules,
pluginPrefix,
configEmojis,
ignoreConfig
),
[COLUMN_TYPE.DEPRECATED]: rule.deprecated ? EMOJI_DEPRECATED : '',
[COLUMN_TYPE.DESCRIPTION]: rule.description || '',
[COLUMN_TYPE.FIXABLE]: rule.fixable ? EMOJI_FIXABLE : '',
[COLUMN_TYPE.HAS_SUGGESTIONS]: rule.hasSuggestions
? EMOJI_HAS_SUGGESTIONS
: '',
[COLUMN_TYPE.NAME]: `[${rule.name}](docs/rules/${rule.name}.md)`,
[COLUMN_TYPE.REQUIRES_TYPE_CHECKING]: rule.requiresTypeChecking
? EMOJI_REQUIRES_TYPE_CHECKING
: '',
};

// List columns using the ordering and presence of columns specified in columnsEnabled.
return Object.keys(columnsEnabled).flatMap((column) =>
columnsEnabled[column as COLUMN_TYPE]
? [columns[column as COLUMN_TYPE]]
: []
);
}

function generateRulesListMarkdown(
Expand All @@ -94,10 +88,6 @@ function generateRulesListMarkdown(
configEmojis: ConfigEmojis,
ignoreConfig: string[]
): string {
// Since such rules are rare, we'll only include the types column if at least one rule requires type checking.
const includeTypesColumn = details.some(
(detail: RuleDetails) => detail.requiresTypeChecking
);
const listHeaderRow = (
Object.entries(columns) as [COLUMN_TYPE, boolean][]
).flatMap(([columnType, enabled]) => {
Expand Down Expand Up @@ -129,7 +119,6 @@ function generateRulesListMarkdown(
rule,
configsToRules,
pluginPrefix,
includeTypesColumn,
configEmojis,
ignoreConfig
)
Expand All @@ -149,6 +138,7 @@ export async function updateRulesList(
pathToPlugin: string,
configEmojis: ConfigEmojis,
ignoreConfig: string[],
ruleListColumns: COLUMN_TYPE[],
urlConfigs?: string
): Promise<string> {
let listStartIndex = markdown.indexOf(BEGIN_RULE_LIST_MARKER);
Expand Down Expand Up @@ -187,7 +177,12 @@ export async function updateRulesList(
const postList = markdown.slice(Math.max(0, listEndIndex));

// Determine columns to include in the rules list.
const columns = getColumns(details, configsToRules, ignoreConfig);
const columns = getColumns(
details,
configsToRules,
ruleListColumns,
ignoreConfig
);

// New legend.
const legend = generateLegend(
Expand Down
36 changes: 36 additions & 0 deletions test/lib/__snapshots__/generator-test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ exports[`generator #generate deprecated rules updates the documentation 1`] = `
| :----------------------------- | :----------- | :-- |
| [no-bar](docs/rules/no-bar.md) | Description. | ❌ |
| [no-baz](docs/rules/no-baz.md) | Description. | ❌ |
| [no-biz](docs/rules/no-biz.md) | Description. | |
| [no-foo](docs/rules/no-foo.md) | Description. | ❌ |
<!-- end rules list -->"
Expand Down Expand Up @@ -226,6 +227,13 @@ exports[`generator #generate deprecated rules updates the documentation 4`] = `
"
`;

exports[`generator #generate deprecated rules updates the documentation 5`] = `
"# Description (\`test/no-biz\`)
<!-- end rule header -->
"
`;

exports[`generator #generate lowercase README file generates the documentation 1`] = `
"<!-- begin rules list -->
Expand Down Expand Up @@ -750,6 +758,34 @@ exports[`generator #generate with --ignore-config hides the ignored config 2`] =
"
`;

exports[`generator #generate with --rule-list-columns shows the right columns and legend 1`] = `
"## Rules
<!-- begin rules list -->
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).\\
🔧 Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).
| 💡 | 🔧 | Name |
| :-- | :-- | :----------------------------- |
| 💡 | 🔧 | [no-foo](docs/rules/no-foo.md) |
<!-- end rules list -->
"
`;

exports[`generator #generate with --rule-list-columns shows the right columns and legend 2`] = `
"# Description for no-foo (\`test/no-foo\`)
❌ This rule is deprecated.
🔧 This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
<!-- end rule header -->
"
`;

exports[`generator #generate with no blank lines around comment markers generates the documentation 1`] = `
"# Rules
Expand Down
Loading

0 comments on commit d6e2fab

Please sign in to comment.