Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support a user-defined function for --path-rule-doc option #502

Merged
merged 5 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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` |
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add an example of the function to the "Configuration file" section in the readme, similar to how we have examples of the other option functions in that section?

| `--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
Loading