Skip to content

Commit f2fd013

Browse files
authored
Support a user-defined function for --path-rule-doc option (#502)
1 parent e133737 commit f2fd013

File tree

9 files changed

+91
-17
lines changed

9 files changed

+91
-17
lines changed

README.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ There's also a `postprocess` option that's only available via a [config file](#c
182182
| `--ignore-config` | Config to ignore from being displayed. Often used for an `all` config. Option can be repeated. | |
183183
| `--ignore-deprecated-rules` | Whether to ignore deprecated rules from being checked, displayed, or updated. | `false` |
184184
| `--init-rule-docs` | Whether to create rule doc files if they don't yet exist. | `false` |
185-
| `--path-rule-doc` | Path to markdown file for each rule doc. Use `{name}` placeholder for the rule name. | `docs/rules/{name}.md` |
185+
| `--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` |
186186
| `--path-rule-list` | Path to markdown file where the rules table list should live. Option can be repeated. | `README.md` |
187187
| `--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` |
188188
| `--rule-doc-section-exclude` | Disallowed section in each rule doc. Exit with failure if present. Option can be repeated. |
@@ -261,6 +261,21 @@ const config = {
261261
module.exports = config;
262262
```
263263

264+
Example `.eslint-doc-generatorrc.js` with `pathRuleDoc` function:
265+
266+
```js
267+
/** @type {import('eslint-doc-generator').GenerateOptions} */
268+
const config = {
269+
pathRuleDoc(name) {
270+
// e.g. rule name format is `some-plugin/some-rule`, and rule is in a monorepo under different package.
271+
const [plugin, rule] = name.split("/");
272+
return `packages/eslint-plugin-${plugin}/src/rules/${rule}.md`;
273+
},
274+
};
275+
276+
module.exports = config;
277+
```
278+
264279
Example `.eslint-doc-generatorrc.js` with `ruleListSplit` function:
265280

266281
```js

lib/cli.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,14 @@ async function loadConfigFileOptions(): Promise<GenerateOptions> {
9696
ignoreConfig: schemaStringArray,
9797
ignoreDeprecatedRules: { type: 'boolean' },
9898
initRuleDocs: { type: 'boolean' },
99-
pathRuleDoc: { type: 'string' },
99+
pathRuleDoc:
100+
/* istanbul ignore next -- TODO: haven't tested JavaScript config files yet https://github.com/bmish/eslint-doc-generator/issues/366 */
101+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
102+
typeof explorerResults.config.pathRuleDoc === 'function'
103+
? {
104+
/* Functions are allowed but JSON Schema can't validate them so no-op in this case. */
105+
}
106+
: { type: 'string' },
100107
pathRuleList: { anyOf: [{ type: 'string' }, schemaStringArray] },
101108
postprocess: {
102109
/* JSON Schema can't validate functions so check this later */
@@ -226,7 +233,7 @@ export async function run(
226233
)
227234
.option(
228235
'--path-rule-doc <path>',
229-
`(optional) Path to markdown file for each rule doc. Use \`{name}\` placeholder for the rule name. (default: ${
236+
`(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: ${
230237
OPTION_DEFAULTS[OPTION_TYPE.PATH_RULE_DOC]
231238
})`
232239
)

lib/generator.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export async function generate(path: string, options?: GenerateOptions) {
151151
for (const [name, rule] of ruleNamesAndRules) {
152152
const schema = rule.meta?.schema;
153153
const description = rule.meta?.docs?.description;
154-
const pathToDoc = replaceRulePlaceholder(join(path, pathRuleDoc), name);
154+
const pathToDoc = join(path, replaceRulePlaceholder(pathRuleDoc, name));
155155
const ruleHasOptions = hasOptions(schema);
156156

157157
if (!existsSync(pathToDoc)) {

lib/rule-doc-notices.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
SEVERITY_TYPE,
1717
NOTICE_TYPE,
1818
UrlRuleDocFunction,
19+
PathRuleDocFunction,
1920
} from './types.js';
2021
import { RULE_TYPE, RULE_TYPE_MESSAGES_NOTICES } from './rule-type.js';
2122
import { RuleDocTitleFormat } from './rule-doc-title-format.js';
@@ -104,7 +105,7 @@ const RULE_NOTICES: {
104105
plugin: Plugin;
105106
pluginPrefix: string;
106107
pathPlugin: string;
107-
pathRuleDoc: string;
108+
pathRuleDoc: string | PathRuleDocFunction;
108109
type?: `${RULE_TYPE}`;
109110
urlRuleDoc?: string | UrlRuleDocFunction;
110111
}) => string);
@@ -303,7 +304,7 @@ function getRuleNoticeLines(
303304
configsToRules: ConfigsToRules,
304305
pluginPrefix: string,
305306
pathPlugin: string,
306-
pathRuleDoc: string,
307+
pathRuleDoc: string | PathRuleDocFunction,
307308
configEmojis: ConfigEmojis,
308309
configFormat: ConfigFormat,
309310
ignoreConfig: readonly string[],
@@ -495,7 +496,7 @@ export function generateRuleHeaderLines(
495496
configsToRules: ConfigsToRules,
496497
pluginPrefix: string,
497498
pathPlugin: string,
498-
pathRuleDoc: string,
499+
pathRuleDoc: string | PathRuleDocFunction,
499500
configEmojis: ConfigEmojis,
500501
configFormat: ConfigFormat,
501502
ignoreConfig: readonly string[],

lib/rule-link.ts

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
import { join, sep, relative, dirname } from 'node:path';
2-
import { Plugin, RULE_SOURCE, UrlRuleDocFunction } from './types.js';
2+
import {
3+
PathRuleDocFunction,
4+
Plugin,
5+
RULE_SOURCE,
6+
UrlRuleDocFunction,
7+
} from './types.js';
38
import { getPluginRoot } from './package-json.js';
49

5-
export function replaceRulePlaceholder(pathOrUrl: string, ruleName: string) {
10+
export function replaceRulePlaceholder(
11+
pathOrUrl: string | PathRuleDocFunction,
12+
ruleName: string
13+
) {
14+
if (typeof pathOrUrl === 'function') {
15+
return pathOrUrl(ruleName);
16+
}
617
return pathOrUrl.replace(/\{name\}/gu, ruleName);
718
}
819

@@ -22,7 +33,7 @@ export function getUrlToRule(
2233
ruleSource: RULE_SOURCE,
2334
pluginPrefix: string,
2435
pathPlugin: string,
25-
pathRuleDoc: string,
36+
pathRuleDoc: string | PathRuleDocFunction,
2637
pathCurrentPage: string,
2738
urlRuleDoc?: string | UrlRuleDocFunction
2839
) {
@@ -76,7 +87,7 @@ export function getLinkToRule(
7687
plugin: Plugin,
7788
pluginPrefix: string,
7889
pathPlugin: string,
79-
pathRuleDoc: string,
90+
pathRuleDoc: string | PathRuleDocFunction,
8091
pathCurrentPage: string,
8192
includeBackticks: boolean,
8293
includePrefix: boolean,

lib/rule-list.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type {
2828
ConfigsToRules,
2929
ConfigEmojis,
3030
RuleNamesAndRules,
31+
PathRuleDocFunction,
3132
} from './types.js';
3233
import { EMOJIS_TYPE } from './rule-type.js';
3334
import { hasOptions } from './rule-options.js';
@@ -118,7 +119,7 @@ function buildRuleRow(
118119
plugin: Plugin,
119120
pluginPrefix: string,
120121
pathPlugin: string,
121-
pathRuleDoc: string,
122+
pathRuleDoc: string | PathRuleDocFunction,
122123
pathRuleList: string,
123124
configEmojis: ConfigEmojis,
124125
ignoreConfig: readonly string[],
@@ -203,7 +204,7 @@ function generateRulesListMarkdown(
203204
plugin: Plugin,
204205
pluginPrefix: string,
205206
pathPlugin: string,
206-
pathRuleDoc: string,
207+
pathRuleDoc: string | PathRuleDocFunction,
207208
pathRuleList: string,
208209
configEmojis: ConfigEmojis,
209210
ignoreConfig: readonly string[],
@@ -258,7 +259,7 @@ function generateRuleListMarkdownForRulesAndHeaders(
258259
plugin: Plugin,
259260
pluginPrefix: string,
260261
pathPlugin: string,
261-
pathRuleDoc: string,
262+
pathRuleDoc: string | PathRuleDocFunction,
262263
pathRuleList: string,
263264
configEmojis: ConfigEmojis,
264265
ignoreConfig: readonly string[],
@@ -396,7 +397,7 @@ export function updateRulesList(
396397
plugin: Plugin,
397398
configsToRules: ConfigsToRules,
398399
pluginPrefix: string,
399-
pathRuleDoc: string,
400+
pathRuleDoc: string | PathRuleDocFunction,
400401
pathRuleList: string,
401402
pathPlugin: string,
402403
configEmojis: ConfigEmojis,

lib/types.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@ export type UrlRuleDocFunction = (
138138
path: string
139139
) => string | undefined;
140140

141+
/**
142+
* Function for generating the path to markdown file for each rule doc.
143+
* Can be provided via a JavaScript-based config file using the `pathRuleDoc` option.
144+
* @param name - the name of the rule
145+
* @returns the path to the rule doc
146+
*/
147+
export type PathRuleDocFunction = (name: string) => string;
148+
141149
// JSDocs for options should be kept in sync with README.md and the CLI runner in cli.ts.
142150
/** The type for the config file (e.g. `.eslint-doc-generatorrc.js`) and internal `generate()` function. */
143151
export type GenerateOptions = {
@@ -166,8 +174,12 @@ export type GenerateOptions = {
166174
readonly ignoreDeprecatedRules?: boolean;
167175
/** Whether to create rule doc files if they don't yet exist. Default: `false`. */
168176
readonly initRuleDocs?: boolean;
169-
/** Path to markdown file for each rule doc. Use `{name}` placeholder for the rule name. Default: `docs/rules/{name}.md`. */
170-
readonly pathRuleDoc?: string;
177+
/**
178+
* Path (or function to generate a path) to to markdown file for each rule doc.
179+
* For the string version, use `{name}` placeholder for the rule name.
180+
* Default: `docs/rules/{name}.md`.
181+
*/
182+
readonly pathRuleDoc?: string | PathRuleDocFunction;
171183
/** Path to markdown file(s) where the rules table list should live. Default: `README.md`. */
172184
readonly pathRuleList?: string | readonly string[];
173185
/**

test/lib/generate/__snapshots__/file-paths-test.ts.snap

+17
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,23 @@ exports[`generate (file paths) custom path to rule docs and rules list generates
1717
"
1818
`;
1919

20+
exports[`generate (file paths) custom path to rule docs and rules list generates the documentation using a function for pathRuleDoc 1`] = `
21+
"<!-- begin auto-generated rules list -->
22+
23+
| Name |
24+
| :------------------------------- |
25+
| [no-foo](rules/no-foo/no-foo.md) |
26+
27+
<!-- end auto-generated rules list -->"
28+
`;
29+
30+
exports[`generate (file paths) custom path to rule docs and rules list generates the documentation using a function for pathRuleDoc 2`] = `
31+
"# test/no-foo
32+
33+
<!-- end auto-generated rule header -->
34+
"
35+
`;
36+
2037
exports[`generate (file paths) empty array of rule lists (happens when CLI option is not passed) falls back to default rules list 1`] = `
2138
"<!-- begin auto-generated rules list -->
2239

test/lib/generate/file-paths-test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ describe('generate (file paths)', function () {
245245
},
246246
};`,
247247

248+
'README.md':
249+
'<!-- begin auto-generated rules list --><!-- end auto-generated rules list -->',
248250
'rules/list.md':
249251
'<!-- begin auto-generated rules list --><!-- end auto-generated rules list -->',
250252
'rules/no-foo/no-foo.md': '',
@@ -267,6 +269,14 @@ describe('generate (file paths)', function () {
267269
expect(readFileSync('rules/list.md', 'utf8')).toMatchSnapshot();
268270
expect(readFileSync('rules/no-foo/no-foo.md', 'utf8')).toMatchSnapshot();
269271
});
272+
273+
it('generates the documentation using a function for pathRuleDoc', async function () {
274+
await generate('.', {
275+
pathRuleDoc: (ruleName) => join('rules', ruleName, `${ruleName}.md`),
276+
});
277+
expect(readFileSync('README.md', 'utf8')).toMatchSnapshot();
278+
expect(readFileSync('rules/no-foo/no-foo.md', 'utf8')).toMatchSnapshot();
279+
});
270280
});
271281

272282
describe('multiple rules lists', function () {

0 commit comments

Comments
 (0)