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 content above rule doc title #269

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
15 changes: 7 additions & 8 deletions lib/generator.ts
Expand Up @@ -162,9 +162,8 @@ export async function generate(path: string, options?: GenerateOptions) {
(details) => !ignoreDeprecatedRules || !details.deprecated
);

let initializedRuleDoc = false;

// Update rule doc for each rule.
let initializedRuleDoc = false;
for (const { name, description, schema } of details) {
const pathToDoc = join(path, pathRuleDoc).replace(/{name}/g, name);

Expand Down Expand Up @@ -243,6 +242,12 @@ export async function generate(path: string, options?: GenerateOptions) {
}
}

if (initRuleDocs && !initializedRuleDoc) {
throw new Error(
'--init-rule-docs was enabled, but no rule doc file needed to be created.'
);
}

// Find the README.
const pathToReadme = getPathWithExactFileNameCasing(join(path, pathRuleList));
if (!pathToReadme || !existsSync(pathToReadme)) {
Expand Down Expand Up @@ -282,10 +287,4 @@ export async function generate(path: string, options?: GenerateOptions) {
} else {
writeFileSync(pathToReadme, readmeContentsNew, 'utf8');
}

if (initRuleDocs && !initializedRuleDoc) {
throw new Error(
'--init-rule-docs was enabled, but no rule doc file needed to be created.'
);
}
}
32 changes: 27 additions & 5 deletions lib/markdown.ts
Expand Up @@ -14,14 +14,36 @@ export function replaceOrCreateHeader(
) {
const lines = markdown.split('\n');

const titleLineIndex = lines.findIndex((line) => line.startsWith('# '));
const markerLineIndex = lines.indexOf(marker);
const dashesLineIndex1 = lines.indexOf('---');
const dashesLineIndex2 = lines.indexOf('---', dashesLineIndex1 + 1);
const hasTitle = titleLineIndex !== -1;
const hasMarker = markerLineIndex !== -1;
const hasYamlFrontMatter = dashesLineIndex1 === 0 && dashesLineIndex2 !== -1;

if (markerLineIndex === -1 && lines.length > 0 && lines[0].startsWith('# ')) {
// No marker present so delete any existing title before we add the new one.
lines.splice(0, 1);
}
// Any YAML front matter or anything else above the title should be kept as-is ahead of the new header.
const preHeader = lines
.slice(
0,
hasTitle ? titleLineIndex : hasYamlFrontMatter ? dashesLineIndex2 + 1 : 0
)
.join('\n');

// Anything after the marker comment, title, or YAML front matter should be kept as-is after the new header.
const postHeader = lines
.slice(
hasMarker
? markerLineIndex + 1
: hasTitle
? titleLineIndex + 1
: hasYamlFrontMatter
? dashesLineIndex2 + 1
: 0
)
.join('\n');

return `${newHeader}\n${lines.slice(markerLineIndex + 1).join('\n')}`;
return `${preHeader ? `${preHeader}\n` : ''}${newHeader}\n${postHeader}`;
}

/**
Expand Down
51 changes: 51 additions & 0 deletions test/lib/generate/__snapshots__/comment-markers-test.ts.snap
Expand Up @@ -46,6 +46,24 @@ exports[`generate (comment markers) no existing comment markers - minimal doc co
"
`;

exports[`generate (comment markers) no existing comment markers - rule doc with YAML-formatted metadata (front matter) above title updates the documentation 1`] = `
"---
pageClass: "rule-details"
sidebarDepth: 0
title: "plugin/rule-name"
description: "disallow foo"
since: "v0.12.0"
---
# Description (\`test/no-foo\`)

💼 This rule is enabled in the ✅ \`recommended\` config.

<!-- end auto-generated rule header -->
Pre-existing notice about the rule being recommended.
## Rule details
Details."
`;

exports[`generate (comment markers) no existing comment markers - with no blank lines in existing content generates the documentation 1`] = `
"## Rules
<!-- begin auto-generated rules list -->
Expand Down Expand Up @@ -94,6 +112,39 @@ exports[`generate (comment markers) no existing comment markers - with one blank
Existing rule doc content."
`;

exports[`generate (comment markers) rule doc with YAML-formatted metadata (front matter) above title and comment marker updates the documentation 1`] = `
"---
pageClass: "rule-details"
sidebarDepth: 0
title: "plugin/rule-name"
description: "disallow foo"
since: "v0.12.0"
---
# Description (\`test/no-foo\`)

💼 This rule is enabled in the ✅ \`recommended\` config.

<!-- end auto-generated rule header -->
## Rule details
Details."
`;

exports[`generate (comment markers) rule doc with YAML-formatted metadata (front matter) and nothing else updates the documentation 1`] = `
"---
pageClass: "rule-details"
sidebarDepth: 0
title: "plugin/rule-name"
description: "disallow foo"
since: "v0.12.0"
---
# Description (\`test/no-foo\`)

💼 This rule is enabled in the ✅ \`recommended\` config.

<!-- end auto-generated rule header -->
"
`;

exports[`generate (comment markers) rule doc without header marker but pre-existing header updates the documentation 1`] = `
"# Description (\`test/no-foo\`)

Expand Down
159 changes: 146 additions & 13 deletions test/lib/generate/comment-markers-test.ts
Expand Up @@ -260,6 +260,55 @@ describe('generate (comment markers)', function () {
});
});

describe('no existing comment markers - rule doc with YAML-formatted metadata (front matter) above title', 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: { docs: { description: 'Description.' }, }, create(context) {} }, },
configs: { recommended: { rules: { 'test/no-foo': 'error', } } }
};`,

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

// YAML-formatted metadata (front matter) content above title.
'docs/rules/no-foo.md': outdent`
---
pageClass: "rule-details"
sidebarDepth: 0
title: "plugin/rule-name"
description: "disallow foo"
since: "v0.12.0"
---
# Some pre-existing title.
Pre-existing notice about the rule being recommended.
## Rule details
Details.
`,

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

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

it('updates the documentation', async function () {
await generate('.');

expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot();
});
});

describe('README missing rule list markers but with rules section', function () {
beforeEach(function () {
mockFs({
Expand Down Expand Up @@ -357,19 +406,8 @@ describe('generate (comment markers)', function () {

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

'README.md':
Expand Down Expand Up @@ -398,4 +436,99 @@ describe('generate (comment markers)', function () {
expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot();
});
});

describe('rule doc with YAML-formatted metadata (front matter) above title and comment marker', 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: { docs: { description: 'Description.' }, }, create(context) {} }, },
configs: { recommended: { rules: { 'test/no-foo': 'error', } } }
};`,

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

// YAML-formatted metadata (front matter) above title.
'docs/rules/no-foo.md': outdent`
---
pageClass: "rule-details"
sidebarDepth: 0
title: "plugin/rule-name"
description: "disallow foo"
since: "v0.12.0"
---
# Outdated title.
Outdated content.
<!-- end auto-generated rule header -->
## Rule details
Details.
`,

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

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

it('updates the documentation', async function () {
await generate('.');

expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot();
});
});

describe('rule doc with YAML-formatted metadata (front matter) and nothing else', 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: { docs: { description: 'Description.' }, }, create(context) {} }, },
configs: { recommended: { rules: { 'test/no-foo': 'error', } } }
};`,

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

// YAML-formatted metadata (front matter) only.
'docs/rules/no-foo.md': outdent`
---
pageClass: "rule-details"
sidebarDepth: 0
title: "plugin/rule-name"
description: "disallow foo"
since: "v0.12.0"
---
`,

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

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

it('updates the documentation', async function () {
await generate('.');

expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot();
});
});
});
7 changes: 1 addition & 6 deletions test/lib/generate/configs-test.ts
Expand Up @@ -407,12 +407,7 @@ describe('generate (configs)', function () {

'index.js': `
export default {
rules: {
'no-foo': {
meta: { docs: { description: 'Description.' }, },
create(context) {}
},
},
rules: { 'no-foo': { meta: { docs: { description: 'Description.' }, }, create(context) {} }, },
configs: {
recommended: {
rules: {
Expand Down