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

Allow specifying multiple rules lists with --path-rule-list #270

Merged
merged 1 commit into from
Nov 23, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ There's also an optional path argument if you need to point the CLI to an ESLint
| `--ignore-deprecated-rules` | Whether to ignore deprecated rules from being checked, displayed, or updated (default: `false`). |
| `--init-rule-docs` | Whether to create rule doc files if they don't yet exist (default: `false`). |
| `--path-rule-doc` | Path to markdown file for each rule doc. Use `{name}` placeholder for the rule name (default: `docs/rules/{name}.md`). |
| `--path-rule-list` | Path to markdown file with a rules section where the rules table list should live (default: `README.md`). |
| `--path-rule-list` | Path to markdown file where the rules table list should live. Default: `README.md`. Option can be repeated. |
| `--rule-doc-notices` | Ordered, comma-separated list of notices to display in rule doc. Non-applicable notices will be hidden. Choices: `configs`, `deprecated`, `fixable` (off by default), `fixableAndHasSuggestions`, `hasSuggestions` (off by default), `options` (off by default), `requiresTypeChecking`, `type` (off by default). Default: `deprecated,configs,fixableAndHasSuggestions,requiresTypeChecking`. |
| `--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. |
Expand Down
14 changes: 11 additions & 3 deletions lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ async function loadConfigFileOptions(): Promise<GenerateOptions> {
ignoreDeprecatedRules: { type: 'boolean' },
initRuleDocs: { type: 'boolean' },
pathRuleDoc: { type: 'string' },
pathRuleList: { type: 'string' },
pathRuleList: { anyOf: [{ type: 'string' }, schemaStringArray] },
ruleDocNotices: { type: 'string' },
ruleDocSectionExclude: schemaStringArray,
ruleDocSectionInclude: schemaStringArray,
Expand Down Expand Up @@ -138,9 +138,11 @@ export async function run(
)
.option(
'--path-rule-list <path>',
`(optional) Path to markdown file with a rules section where the rules table list should live. (default: ${
`(optional) Path to markdown file where the rules table list should live. Option can be repeated. Defaults to ${
OPTION_DEFAULTS[OPTION_TYPE.PATH_RULE_LIST]
})`
} if not provided.`,
collect,
[]
)
.option(
'--rule-doc-notices <notices>',
Expand Down Expand Up @@ -203,6 +205,12 @@ export async function run(
// For this to work, we can't have any default values from the CLI options that will override the config file options (except empty arrays, as arrays will be merged).
// Default values should be handled in the callback function.
const configFileOptions = await loadConfigFileOptions();

// Perform any normalization needed ahead of merging.
if (typeof configFileOptions.pathRuleList === 'string') {
configFileOptions.pathRuleList = [configFileOptions.pathRuleList];
}

const generateOptions = merge(configFileOptions, options); // Recursive merge.

// Invoke callback.
Expand Down
2 changes: 1 addition & 1 deletion lib/comment-markers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Markers so that the README rules list can be automatically updated.
// Markers so that the rules table list can be automatically updated.
export const BEGIN_RULE_LIST_MARKER =
'<!-- begin auto-generated rules list -->';
export const END_RULE_LIST_MARKER = '<!-- end auto-generated rules list -->';
Expand Down
76 changes: 41 additions & 35 deletions lib/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,43 +248,49 @@ export async function generate(path: string, options?: GenerateOptions) {
);
}

// Find the README.
const pathToReadme = getPathWithExactFileNameCasing(join(path, pathRuleList));
if (!pathToReadme || !existsSync(pathToReadme)) {
throw new Error(`Could not find ${pathRuleList} in ESLint plugin.`);
}
for (const pathRuleListItem of Array.isArray(pathRuleList)
? pathRuleList
: [pathRuleList]) {
// Find the exact filename.
const pathToFile = getPathWithExactFileNameCasing(
join(path, pathRuleListItem)
);
if (!pathToFile || !existsSync(pathToFile)) {
throw new Error(`Could not find ${pathRuleList} in ESLint plugin.`);
}

// Update the rules list in the README.
const readmeContents = readFileSync(pathToReadme, 'utf8');
const readmeContentsNew = updateRulesList(
details,
readmeContents,
plugin,
configsToRules,
pluginPrefix,
pathRuleDoc,
pathToReadme,
path,
configEmojis,
ignoreConfig,
ruleListColumns,
urlConfigs,
urlRuleDoc,
splitBy
);
// Update the rules list in this file.
const fileContents = readFileSync(pathToFile, 'utf8');
const fileContentsNew = updateRulesList(
details,
fileContents,
plugin,
configsToRules,
pluginPrefix,
pathRuleDoc,
pathToFile,
path,
configEmojis,
ignoreConfig,
ruleListColumns,
urlConfigs,
urlRuleDoc,
splitBy
);

if (check) {
if (readmeContentsNew !== readmeContents) {
console.error(
`Please run eslint-doc-generator. ${relative(
getPluginRoot(path),
pathToReadme
)} is out-of-date.`
);
console.error(diff(readmeContentsNew, readmeContents, { expand: false }));
process.exitCode = 1;
if (check) {
if (fileContentsNew !== fileContents) {
console.error(
`Please run eslint-doc-generator. The rules table in ${relative(
getPluginRoot(path),
pathToFile
)} is out-of-date.`
);
console.error(diff(fileContentsNew, fileContents, { expand: false }));
process.exitCode = 1;
}
} else {
writeFileSync(pathToFile, fileContentsNew, 'utf8');
}
} else {
writeFileSync(pathToReadme, readmeContentsNew, 'utf8');
}
}
4 changes: 2 additions & 2 deletions lib/rule-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ export function updateRulesList(
configsToRules: ConfigsToRules,
pluginPrefix: string,
pathRuleDoc: string,
pathToReadme: string,
pathRuleList: string,
pathToPlugin: string,
configEmojis: ConfigEmojis,
ignoreConfig: string[],
Expand Down Expand Up @@ -336,7 +336,7 @@ export function updateRulesList(
throw new Error(
`${relative(
getPluginRoot(pathToPlugin),
pathToReadme
pathRuleList
)} is missing rules list markers: ${BEGIN_RULE_LIST_MARKER}${END_RULE_LIST_MARKER}`
);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export type GenerateOptions = {
ignoreDeprecatedRules?: boolean;
initRuleDocs?: boolean;
pathRuleDoc?: string;
pathRuleList?: string;
pathRuleList?: string | string[];
ruleDocNotices?: string;
ruleDocSectionExclude?: string[];
ruleDocSectionInclude?: string[];
Expand Down
35 changes: 32 additions & 3 deletions test/lib/__snapshots__/cli-test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ exports[`cli all CLI options and all config files options merges correctly, with
"ignoreDeprecatedRules": true,
"initRuleDocs": false,
"pathRuleDoc": "www.example.com/rule-doc-from-cli",
"pathRuleList": "www.example.com/rule-list-from-cli",
"pathRuleList": [
"www.example.com/rule-list-from-config-file",
"www.example.com/rule-list-from-cli",
],
"ruleDocNotices": "type",
"ruleDocSectionExclude": [
"excludedSectionFromConfigFile1",
Expand Down Expand Up @@ -57,7 +60,9 @@ exports[`cli all CLI options, no config file options is called correctly 1`] = `
"ignoreDeprecatedRules": true,
"initRuleDocs": false,
"pathRuleDoc": "www.example.com/rule-doc-from-cli",
"pathRuleList": "www.example.com/rule-list-from-cli",
"pathRuleList": [
"www.example.com/rule-list-from-cli",
],
"ruleDocNotices": "type",
"ruleDocSectionExclude": [
"excludedSectionFromCli1",
Expand Down Expand Up @@ -92,7 +97,9 @@ exports[`cli all config files options, no CLI options is called correctly 1`] =
"ignoreDeprecatedRules": true,
"initRuleDocs": true,
"pathRuleDoc": "www.example.com/rule-doc-from-config-file",
"pathRuleList": "www.example.com/rule-list-from-config-file",
"pathRuleList": [
"www.example.com/rule-list-from-config-file",
],
"ruleDocNotices": "type",
"ruleDocSectionExclude": [
"excludedSectionFromConfigFile1",
Expand All @@ -119,6 +126,7 @@ exports[`cli boolean option - false (explicit) is called correctly 1`] = `
"configEmoji": [],
"ignoreConfig": [],
"ignoreDeprecatedRules": false,
"pathRuleList": [],
"ruleDocSectionExclude": [],
"ruleDocSectionInclude": [],
},
Expand All @@ -132,6 +140,7 @@ exports[`cli boolean option - true (explicit) is called correctly 1`] = `
"configEmoji": [],
"ignoreConfig": [],
"ignoreDeprecatedRules": true,
"pathRuleList": [],
"ruleDocSectionExclude": [],
"ruleDocSectionInclude": [],
},
Expand All @@ -145,6 +154,7 @@ exports[`cli boolean option - true (implicit) is called correctly 1`] = `
"configEmoji": [],
"ignoreConfig": [],
"ignoreDeprecatedRules": true,
"pathRuleList": [],
"ruleDocSectionExclude": [],
"ruleDocSectionInclude": [],
},
Expand All @@ -157,6 +167,25 @@ exports[`cli no options is called correctly 1`] = `
{
"configEmoji": [],
"ignoreConfig": [],
"pathRuleList": [],
"ruleDocSectionExclude": [],
"ruleDocSectionInclude": [],
},
]
`;

exports[`cli pathRuleList as array in config file and CLI merges correctly 1`] = `
[
".",
{
"configEmoji": [],
"ignoreConfig": [],
"pathRuleList": [
"listFromConfigFile1.md",
"listFromConfigFile2.md",
"listFromCli1.md",
"listFromCli2.md",
],
"ruleDocSectionExclude": [],
"ruleDocSectionInclude": [],
},
Expand Down
39 changes: 39 additions & 0 deletions test/lib/cli-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,45 @@ describe('cli', function () {
});
});

describe('pathRuleList as array in config file and CLI', function () {
beforeEach(function () {
mockFs({
'package.json': JSON.stringify({
name: 'eslint-plugin-test',
main: 'index.js',
type: 'module',
version: '1.0.0',
}),

'.eslint-doc-generatorrc.json': JSON.stringify({
pathRuleList: ['listFromConfigFile1.md', 'listFromConfigFile2.md'],
}),
});
});

afterEach(function () {
mockFs.restore();
});

it('merges correctly', async function () {
const stub = sinon.stub().resolves();
await run(
[
'node', // Path to node.
'eslint-doc-generator.js', // Path to this binary.

'--path-rule-list',
'listFromCli1.md',
'--path-rule-list',
'listFromCli2.md',
],
stub
);
expect(stub.callCount).toBe(1);
expect(stub.firstCall.args).toMatchSnapshot();
});
});

describe('boolean option - false (explicit)', function () {
it('is called correctly', async function () {
const stub = sinon.stub().resolves();
Expand Down
20 changes: 20 additions & 0 deletions test/lib/generate/__snapshots__/file-paths-test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,23 @@ exports[`generate (file paths) missing rule doc when initRuleDocs is true create
<!-- end auto-generated rule header -->
"
`;

exports[`generate (file paths) multiple rules lists generates the documentation 1`] = `
"<!-- begin auto-generated rules list -->

| Name |
| :----------------------------- |
| [no-foo](docs/rules/no-foo.md) |

<!-- end auto-generated rules list -->"
`;

exports[`generate (file paths) multiple rules lists generates the documentation 2`] = `
"<!-- begin auto-generated rules list -->

| Name |
| :----------------------------- |
| [no-foo](docs/rules/no-foo.md) |

<!-- end auto-generated rules list -->"
`;
41 changes: 41 additions & 0 deletions test/lib/generate/file-paths-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,45 @@ describe('generate (file paths)', function () {
expect(readFileSync('rules/no-foo/no-foo.md', 'utf8')).toMatchSnapshot();
});
});

describe('multiple rules lists', function () {
beforeEach(function () {
mockFs({
'package.json': JSON.stringify({
name: 'eslint-plugin-test',
exports: 'index.js',
type: 'module',
}),

'index.js': `
export default {
rules: {
'no-foo': { meta: { }, create(context) {} },
},
};`,

'rules/list1.md':
'<!-- begin auto-generated rules list --><!-- end auto-generated rules list -->',
'rules/list2.md':
'<!-- begin auto-generated rules list --><!-- end auto-generated rules list -->',
'docs/rules/no-foo.md': '',

// Needed for some of the test infrastructure to work.
node_modules: mockFs.load(PATH_NODE_MODULES),
});
});

afterEach(function () {
mockFs.restore();
jest.resetModules();
});

it('generates the documentation', async function () {
await generate('.', {
pathRuleList: ['rules/list1.md', 'rules/list2.md'],
});
expect(readFileSync('rules/list1.md', 'utf8')).toMatchSnapshot();
expect(readFileSync('rules/list2.md', 'utf8')).toMatchSnapshot();
});
});
});
2 changes: 1 addition & 1 deletion test/lib/generate/option-check-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('generate (--check)', function () {
]);
expect(consoleErrorStub.secondCall.args).toMatchSnapshot(); // Diff
expect(consoleErrorStub.thirdCall.args).toStrictEqual([
'Please run eslint-doc-generator. README.md is out-of-date.',
'Please run eslint-doc-generator. The rules table in README.md is out-of-date.',
]);
expect(consoleErrorStub.getCall(3).args).toMatchSnapshot(); // Diff
consoleErrorStub.restore();
Expand Down