Skip to content

Commit

Permalink
Merge pull request #84 from bmish/include-exclude-headers
Browse files Browse the repository at this point in the history
  • Loading branch information
bmish authored Oct 9, 2022
2 parents 55a843b + 43e3a5d commit b7884d6
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 8 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ If you have any custom configs (besides `all`, `recommended`), you'll need to de

| Name | Description |
| --- | --- |
| `--rule-doc-section-include` | (optional) Required section in each rule doc (option can be repeated). |
| `--rule-doc-section-exclude` | (optional) Disallowed section in each rule doc (option can be repeated). |
| `--rule-doc-title-format` | (optional) The format to use for rule doc titles. Choices: `desc-parens-prefix-name` (default), `desc-parens-name`, `prefix-name`, `name`. |
| `--url-configs` | (optional) Link to documentation about the ESLint configurations exported by the plugin. |

Expand Down
26 changes: 25 additions & 1 deletion lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ function getCurrentPackageVersion(): string {
return packageJson.version;
}

// Used for collecting repeated CLI options into an array.
function collect(value: string, previous: string[]) {
return [...previous, value];
}

export function run() {
const program = new Command();

Expand All @@ -30,6 +35,18 @@ export function run() {
.addArgument(
new Argument('[path]', 'path to ESLint plugin root').default('.')
)
.option(
'--rule-doc-section-include <section>',
'(optional) Required section in each rule doc (option can be repeated).',
collect,
[]
)
.option(
'--rule-doc-section-exclude <section>',
'(optional) Disallowed section in each rule doc (option can be repeated).',
collect,
[]
)
.addOption(
new Option(
'--rule-doc-title-format <format>',
Expand All @@ -44,9 +61,16 @@ export function run() {
)
.action(async function (
path,
options: { ruleDocTitleFormat: RuleDocTitleFormat; urlConfigs?: string }
options: {
ruleDocSectionInclude: string[];
ruleDocSectionExclude: string[];
ruleDocTitleFormat: RuleDocTitleFormat;
urlConfigs?: string;
}
) {
await generate(path, {
ruleDocSectionInclude: options.ruleDocSectionInclude,
ruleDocSectionExclude: options.ruleDocSectionExclude,
ruleDocTitleFormat: options.ruleDocTitleFormat,
urlConfigs: options.urlConfigs,
});
Expand Down
38 changes: 32 additions & 6 deletions lib/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,34 @@ function expectSectionHeader(
findSectionHeader(contents, header)
);
if (found !== expected) {
console.error(
`\`${ruleName}\` rule doc should ${expected ? '' : 'not '}have included ${
expected ? 'one' : 'any'
} of these headers: ${possibleHeaders.join(', ')}`
);
if (possibleHeaders.length > 1) {
console.error(
`\`${ruleName}\` rule doc should ${
expected ? '' : 'not '
}have included ${
expected ? 'one' : 'any'
} of these headers: ${possibleHeaders.join(', ')}`
);
} else {
console.error(
`\`${ruleName}\` rule doc should ${
expected ? '' : 'not '
}have included the header: ${possibleHeaders.join(', ')}`
);
}

process.exitCode = 1;
}
}

export async function generate(
path: string,
options?: { ruleDocTitleFormat?: RuleDocTitleFormat; urlConfigs?: string }
options?: {
ruleDocSectionInclude?: string[];
ruleDocSectionExclude?: string[];
ruleDocTitleFormat?: RuleDocTitleFormat;
urlConfigs?: string;
}
) {
const plugin = await loadPlugin(path);
const pluginPrefix = getPluginPrefix(path);
Expand Down Expand Up @@ -138,6 +154,16 @@ export async function generate(

// Check for potential issues with the rule doc.

// Check for required sections.
for (const section of options?.ruleDocSectionInclude || []) {
expectSectionHeader(name, contents, [section], true);
}

// Check for disallowed sections.
for (const section of options?.ruleDocSectionExclude || []) {
expectSectionHeader(name, contents, [section], false);
}

// Options section.
expectSectionHeader(
name,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"lint:types": "tsc",
"prepublishOnly": "tsc",
"release": "release-it",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage"
"test": "node --max-old-space-size=4096 --experimental-vm-modules node_modules/jest/bin/jest.js --coverage"
},
"dependencies": {
"@typescript-eslint/utils": "^5.38.1",
Expand Down
91 changes: 91 additions & 0 deletions test/lib/generator-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2071,5 +2071,96 @@ describe('generator', function () {
expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot();
});
});

describe('with `--rule-doc-section-include` and `--rule-doc-section-exclude` and no problems', function () {
beforeEach(function () {
mockFs({
'package.json': JSON.stringify({
name: 'eslint-plugin-test',
main: 'index.js',
type: 'module',
}),

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

'README.md': '## Rules\n',

'docs/rules/no-foo.md': '## Examples\n',

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

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

it('has no issues', async function () {
await expect(
generate('.', {
ruleDocSectionInclude: ['Examples'],
ruleDocSectionExclude: ['Unwanted Section'],
})
).resolves.toBeUndefined();
});
});

describe('with `--rule-doc-section-include` and `--rule-doc-section-exclude` and problems', function () {
beforeEach(function () {
mockFs({
'package.json': JSON.stringify({
name: 'eslint-plugin-test',
main: 'index.js',
type: 'module',
}),

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

'README.md': '## Rules\n',

'docs/rules/no-foo.md': '## Unwanted Section\n',

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

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

it('prints errors', async function () {
const consoleErrorStub = sinon.stub(console, 'error');
await generate('.', {
ruleDocSectionInclude: ['Examples'],
ruleDocSectionExclude: ['Unwanted Section'],
});
expect(consoleErrorStub.callCount).toBe(2);
expect(consoleErrorStub.firstCall.args).toStrictEqual([
'`no-foo` rule doc should have included the header: Examples',
]);
expect(consoleErrorStub.secondCall.args).toStrictEqual([
'`no-foo` rule doc should not have included the header: Unwanted Section',
]);
consoleErrorStub.restore();
});
});
});
});

0 comments on commit b7884d6

Please sign in to comment.