Skip to content

Commit

Permalink
Support a user-defined function for --path-rule-doc option (#502)
Browse files Browse the repository at this point in the history
  • Loading branch information
hyoban authored Nov 14, 2023
1 parent e133737 commit f2fd013
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 17 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ There's also a `postprocess` option that's only available via a [config file](#c
| `--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. | `false` |
| `--init-rule-docs` | Whether to create rule doc files if they don't yet exist. | `false` |
| `--path-rule-doc` | Path to markdown file for each rule doc. Use `{name}` placeholder for the rule name. | `docs/rules/{name}.md` |
| `--path-rule-doc` | Path to markdown file for each rule doc. Use `{name}` placeholder for the rule name. A function can also be provided for this option via a [config file](#configuration-file). | `docs/rules/{name}.md` |
| `--path-rule-list` | Path to markdown file where the rules table list should live. Option can be repeated. | `README.md` |
| `--rule-doc-notices` | Ordered, comma-separated list of notices to display in rule doc. Non-applicable notices will be hidden. See choices in below [table](#column-and-notice-types). | `deprecated`, `configs`, `fixableAndHasSuggestions`, `requiresTypeChecking` |
| `--rule-doc-section-exclude` | Disallowed section in each rule doc. Exit with failure if present. Option can be repeated. |
Expand Down Expand Up @@ -261,6 +261,21 @@ const config = {
module.exports = config;
```

Example `.eslint-doc-generatorrc.js` with `pathRuleDoc` function:

```js
/** @type {import('eslint-doc-generator').GenerateOptions} */
const config = {
pathRuleDoc(name) {
// e.g. rule name format is `some-plugin/some-rule`, and rule is in a monorepo under different package.
const [plugin, rule] = name.split("/");
return `packages/eslint-plugin-${plugin}/src/rules/${rule}.md`;
},
};

module.exports = config;
```

Example `.eslint-doc-generatorrc.js` with `ruleListSplit` function:

```js
Expand Down
11 changes: 9 additions & 2 deletions lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,14 @@ async function loadConfigFileOptions(): Promise<GenerateOptions> {
ignoreConfig: schemaStringArray,
ignoreDeprecatedRules: { type: 'boolean' },
initRuleDocs: { type: 'boolean' },
pathRuleDoc: { type: 'string' },
pathRuleDoc:
/* istanbul ignore next -- TODO: haven't tested JavaScript config files yet https://github.com/bmish/eslint-doc-generator/issues/366 */
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
typeof explorerResults.config.pathRuleDoc === 'function'
? {
/* Functions are allowed but JSON Schema can't validate them so no-op in this case. */
}
: { type: 'string' },
pathRuleList: { anyOf: [{ type: 'string' }, schemaStringArray] },
postprocess: {
/* JSON Schema can't validate functions so check this later */
Expand Down Expand Up @@ -226,7 +233,7 @@ export async function run(
)
.option(
'--path-rule-doc <path>',
`(optional) Path to markdown file for each rule doc. Use \`{name}\` placeholder for the rule name. (default: ${
`(optional) Path to markdown file for each rule doc. Use \`{name}\` placeholder for the rule name. To specify a function, use a JavaScript-based config file. (default: ${
OPTION_DEFAULTS[OPTION_TYPE.PATH_RULE_DOC]
})`
)
Expand Down
2 changes: 1 addition & 1 deletion lib/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export async function generate(path: string, options?: GenerateOptions) {
for (const [name, rule] of ruleNamesAndRules) {
const schema = rule.meta?.schema;
const description = rule.meta?.docs?.description;
const pathToDoc = replaceRulePlaceholder(join(path, pathRuleDoc), name);
const pathToDoc = join(path, replaceRulePlaceholder(pathRuleDoc, name));
const ruleHasOptions = hasOptions(schema);

if (!existsSync(pathToDoc)) {
Expand Down
7 changes: 4 additions & 3 deletions lib/rule-doc-notices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
SEVERITY_TYPE,
NOTICE_TYPE,
UrlRuleDocFunction,
PathRuleDocFunction,
} from './types.js';
import { RULE_TYPE, RULE_TYPE_MESSAGES_NOTICES } from './rule-type.js';
import { RuleDocTitleFormat } from './rule-doc-title-format.js';
Expand Down Expand Up @@ -104,7 +105,7 @@ const RULE_NOTICES: {
plugin: Plugin;
pluginPrefix: string;
pathPlugin: string;
pathRuleDoc: string;
pathRuleDoc: string | PathRuleDocFunction;
type?: `${RULE_TYPE}`;
urlRuleDoc?: string | UrlRuleDocFunction;
}) => string);
Expand Down Expand Up @@ -303,7 +304,7 @@ function getRuleNoticeLines(
configsToRules: ConfigsToRules,
pluginPrefix: string,
pathPlugin: string,
pathRuleDoc: string,
pathRuleDoc: string | PathRuleDocFunction,
configEmojis: ConfigEmojis,
configFormat: ConfigFormat,
ignoreConfig: readonly string[],
Expand Down Expand Up @@ -495,7 +496,7 @@ export function generateRuleHeaderLines(
configsToRules: ConfigsToRules,
pluginPrefix: string,
pathPlugin: string,
pathRuleDoc: string,
pathRuleDoc: string | PathRuleDocFunction,
configEmojis: ConfigEmojis,
configFormat: ConfigFormat,
ignoreConfig: readonly string[],
Expand Down
19 changes: 15 additions & 4 deletions lib/rule-link.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { join, sep, relative, dirname } from 'node:path';
import { Plugin, RULE_SOURCE, UrlRuleDocFunction } from './types.js';
import {
PathRuleDocFunction,
Plugin,
RULE_SOURCE,
UrlRuleDocFunction,
} from './types.js';
import { getPluginRoot } from './package-json.js';

export function replaceRulePlaceholder(pathOrUrl: string, ruleName: string) {
export function replaceRulePlaceholder(
pathOrUrl: string | PathRuleDocFunction,
ruleName: string
) {
if (typeof pathOrUrl === 'function') {
return pathOrUrl(ruleName);
}
return pathOrUrl.replace(/\{name\}/gu, ruleName);
}

Expand All @@ -22,7 +33,7 @@ export function getUrlToRule(
ruleSource: RULE_SOURCE,
pluginPrefix: string,
pathPlugin: string,
pathRuleDoc: string,
pathRuleDoc: string | PathRuleDocFunction,
pathCurrentPage: string,
urlRuleDoc?: string | UrlRuleDocFunction
) {
Expand Down Expand Up @@ -76,7 +87,7 @@ export function getLinkToRule(
plugin: Plugin,
pluginPrefix: string,
pathPlugin: string,
pathRuleDoc: string,
pathRuleDoc: string | PathRuleDocFunction,
pathCurrentPage: string,
includeBackticks: boolean,
includePrefix: boolean,
Expand Down
9 changes: 5 additions & 4 deletions lib/rule-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type {
ConfigsToRules,
ConfigEmojis,
RuleNamesAndRules,
PathRuleDocFunction,
} from './types.js';
import { EMOJIS_TYPE } from './rule-type.js';
import { hasOptions } from './rule-options.js';
Expand Down Expand Up @@ -118,7 +119,7 @@ function buildRuleRow(
plugin: Plugin,
pluginPrefix: string,
pathPlugin: string,
pathRuleDoc: string,
pathRuleDoc: string | PathRuleDocFunction,
pathRuleList: string,
configEmojis: ConfigEmojis,
ignoreConfig: readonly string[],
Expand Down Expand Up @@ -203,7 +204,7 @@ function generateRulesListMarkdown(
plugin: Plugin,
pluginPrefix: string,
pathPlugin: string,
pathRuleDoc: string,
pathRuleDoc: string | PathRuleDocFunction,
pathRuleList: string,
configEmojis: ConfigEmojis,
ignoreConfig: readonly string[],
Expand Down Expand Up @@ -258,7 +259,7 @@ function generateRuleListMarkdownForRulesAndHeaders(
plugin: Plugin,
pluginPrefix: string,
pathPlugin: string,
pathRuleDoc: string,
pathRuleDoc: string | PathRuleDocFunction,
pathRuleList: string,
configEmojis: ConfigEmojis,
ignoreConfig: readonly string[],
Expand Down Expand Up @@ -396,7 +397,7 @@ export function updateRulesList(
plugin: Plugin,
configsToRules: ConfigsToRules,
pluginPrefix: string,
pathRuleDoc: string,
pathRuleDoc: string | PathRuleDocFunction,
pathRuleList: string,
pathPlugin: string,
configEmojis: ConfigEmojis,
Expand Down
16 changes: 14 additions & 2 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ export type UrlRuleDocFunction = (
path: string
) => string | undefined;

/**
* Function for generating the path to markdown file for each rule doc.
* Can be provided via a JavaScript-based config file using the `pathRuleDoc` option.
* @param name - the name of the rule
* @returns the path to the rule doc
*/
export type PathRuleDocFunction = (name: string) => string;

// JSDocs for options should be kept in sync with README.md and the CLI runner in cli.ts.
/** The type for the config file (e.g. `.eslint-doc-generatorrc.js`) and internal `generate()` function. */
export type GenerateOptions = {
Expand Down Expand Up @@ -166,8 +174,12 @@ export type GenerateOptions = {
readonly ignoreDeprecatedRules?: boolean;
/** Whether to create rule doc files if they don't yet exist. Default: `false`. */
readonly initRuleDocs?: boolean;
/** Path to markdown file for each rule doc. Use `{name}` placeholder for the rule name. Default: `docs/rules/{name}.md`. */
readonly pathRuleDoc?: string;
/**
* Path (or function to generate a path) to to markdown file for each rule doc.
* For the string version, use `{name}` placeholder for the rule name.
* Default: `docs/rules/{name}.md`.
*/
readonly pathRuleDoc?: string | PathRuleDocFunction;
/** Path to markdown file(s) where the rules table list should live. Default: `README.md`. */
readonly pathRuleList?: string | readonly string[];
/**
Expand Down
17 changes: 17 additions & 0 deletions test/lib/generate/__snapshots__/file-paths-test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@ exports[`generate (file paths) custom path to rule docs and rules list generates
"
`;

exports[`generate (file paths) custom path to rule docs and rules list generates the documentation using a function for pathRuleDoc 1`] = `
"<!-- begin auto-generated rules list -->
| Name |
| :------------------------------- |
| [no-foo](rules/no-foo/no-foo.md) |
<!-- end auto-generated rules list -->"
`;

exports[`generate (file paths) custom path to rule docs and rules list generates the documentation using a function for pathRuleDoc 2`] = `
"# test/no-foo
<!-- end auto-generated rule header -->
"
`;

exports[`generate (file paths) empty array of rule lists (happens when CLI option is not passed) falls back to default rules list 1`] = `
"<!-- begin auto-generated rules list -->
Expand Down
10 changes: 10 additions & 0 deletions test/lib/generate/file-paths-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ describe('generate (file paths)', function () {
},
};`,

'README.md':
'<!-- begin auto-generated rules list --><!-- end auto-generated rules list -->',
'rules/list.md':
'<!-- begin auto-generated rules list --><!-- end auto-generated rules list -->',
'rules/no-foo/no-foo.md': '',
Expand All @@ -267,6 +269,14 @@ describe('generate (file paths)', function () {
expect(readFileSync('rules/list.md', 'utf8')).toMatchSnapshot();
expect(readFileSync('rules/no-foo/no-foo.md', 'utf8')).toMatchSnapshot();
});

it('generates the documentation using a function for pathRuleDoc', async function () {
await generate('.', {
pathRuleDoc: (ruleName) => join('rules', ruleName, `${ruleName}.md`),
});
expect(readFileSync('README.md', 'utf8')).toMatchSnapshot();
expect(readFileSync('rules/no-foo/no-foo.md', 'utf8')).toMatchSnapshot();
});
});

describe('multiple rules lists', function () {
Expand Down

0 comments on commit f2fd013

Please sign in to comment.