diff --git a/README.md b/README.md index a2236073..2077e7b3 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ export default defineConfig([ | [`no-bare-urls`](./docs/rules/no-bare-urls.md) | Disallow bare URLs | no | | [`no-duplicate-definitions`](./docs/rules/no-duplicate-definitions.md) | Disallow duplicate definitions | yes | | [`no-duplicate-headings`](./docs/rules/no-duplicate-headings.md) | Disallow duplicate headings in the same document | no | +| [`no-emphasis-as-heading`](./docs/rules/no-emphasis-as-heading.md) | Disallow using fully bolded paragraphs as headings | yes | | [`no-empty-definitions`](./docs/rules/no-empty-definitions.md) | Disallow empty definitions | yes | | [`no-empty-images`](./docs/rules/no-empty-images.md) | Disallow empty images | yes | | [`no-empty-links`](./docs/rules/no-empty-links.md) | Disallow empty links | yes | diff --git a/docs/rules/no-emphasis-as-heading.md b/docs/rules/no-emphasis-as-heading.md new file mode 100644 index 00000000..5471154d --- /dev/null +++ b/docs/rules/no-emphasis-as-heading.md @@ -0,0 +1,79 @@ +# no-emphasis-as-heading + +TODO + +## Rule Details + +This rule disallows using fully bolded paragraphs as headings. The use of fully bolded paragraphs as headings is a common anti-pattern that reduces document semantics and accessibility. Instead, proper heading elements (`#`, `##`, etc.) should be used. + +### Why This Is Important + +Using proper headings instead of bolded paragraphs: + +- Improves document structure and semantics +- Enhances accessibility for screen readers +- Creates proper document outline +- Makes navigation of the document easier + +### What This Rule Checks + +This rule identifies paragraphs that: + +- Consist entirely of a bold element (`**text**` or `__text__`) +- Are contained on a single line +- Are not within list items +- Have bold markup that spans the entire paragraph content + +## Examples + +### :x: Incorrect + +Examples of **incorrect** code for this rule: + +```md /**First Chapter**/ /__Second Chapter__/ + + +# Book + +**First Chapter** + +Content of the first chapter + +__Second Chapter__ + +Content of the second chapter +``` + +### :white_check_mark: Correct + +Examples of **correct** code for this rule: + +```md + + +# Book + +## First Chapter + +Content of the first chapter + +## Second Chapter + +Content of the second chapter + +--- + +**Bold text** with normal text in the paragraph. + +Text with **bold parts** is fine. + +- **Bold text in a list item** is allowed. +``` + +## Options + +No options are available for this rule. + +## Prior Art + +- [textlint-rule-no-bold-paragraph](https://github.com/aborazmeh/textlint-rule-no-bold-paragraph) diff --git a/src/rules/no-emphasis-as-heading.js b/src/rules/no-emphasis-as-heading.js new file mode 100644 index 00000000..6aa15aed --- /dev/null +++ b/src/rules/no-emphasis-as-heading.js @@ -0,0 +1,67 @@ +/** + * @fileoverview Rule to disallow using fully bolded paragraphs as headings. + * @author lumir(lumirlumir) + */ + +// @ts-nocheck -- TODO + +//----------------------------------------------------------------------------- +// Type Definitions +//----------------------------------------------------------------------------- + +/** + * @import { MarkdownRuleDefinition } from "../types.js"; + * @typedef {"no-emphasis-as-heading"} NoEmphasisAsHeadingMessageIds + * @typedef {[]} NoEmphasisAsHeadingOptions + * @typedef {MarkdownRuleDefinition<{ RuleOptions: NoEmphasisAsHeadingOptions, MessageIds: NoEmphasisAsHeadingMessageIds }>} NoEmphasisAsHeadingRuleDefinition + */ + +// -------------------------------------------------------------------------------- +// Rule Definition +// -------------------------------------------------------------------------------- + +export default /** @satisfies {NoEmphasisAsHeadingRuleDefinition} */ ({ + meta: { + type: "problem", + + docs: { + recommended: true, + description: "Disallow using fully bolded paragraphs as headings", + url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-emphasis-as-heading.md", + }, + + messages: { + noEmphasisAsHeading: + "Fully bolded paragraphs should not be used as headings. Please use a heading instead.", + }, + + // language: "markdown", + + // dialects: ["commonmark", "gfm"], + }, + + create(context) { + return { + strong(node) { + const parentNode = context.sourceCode.getParent(node); + const ancestorNode = context.sourceCode.getParent(parentNode); + + if ( + parentNode.type === "paragraph" && + ancestorNode.type !== "listItem" && + parentNode.position.start.line === + parentNode.position.end.line && // Should be a single line. + parentNode.position.start.offset === + node.position.start.offset && // Should have the same start offset. + parentNode.position.end.offset === node.position.end.offset // Should have the same end offset. + ) { + context.report({ + node, + + messageId: "noEmphasisAsHeading", + }); + } + }, + }; + }, +}); diff --git a/tests/rules/no-emphasis-as-heading.test.js b/tests/rules/no-emphasis-as-heading.test.js new file mode 100644 index 00000000..64849f36 --- /dev/null +++ b/tests/rules/no-emphasis-as-heading.test.js @@ -0,0 +1,115 @@ +/** + * @fileoverview Test for `no-emphasis-as-heading.ts`. + * @author lumir(lumirlumir) + */ + +//------------------------------------------------------------------------------ +// Imports +//------------------------------------------------------------------------------ + +import rule from "../../src/rules/no-emphasis-as-heading.js"; +import markdown from "../../src/index.js"; +import { RuleTester } from "eslint"; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + plugins: { + markdown, + }, + language: "markdown/commonmark", +}); + +ruleTester.run("no-emphasis-as-heading", rule, { + valid: [ + // Basic + { + name: "Empty", + code: "", + }, + { + name: "Empty string", + code: " ", + }, + { + name: "Paragraph without bolded text", + code: "#Book\n\nFirst Chapter\n\nContent\n\nSecond Chapter\n\nContent", + }, + { + name: "Paragraph with bolded text and other text", + code: "**Hello** World.\n\nHello **World**.", + }, + { + name: "Paragraph with bolded text and other multiline text", + code: "**Hello**\nWorld.\n\nHello\n**World**.", + }, + { + name: "Bold with whitespace inside", + code: "** Not fully bolded paragraph **", + }, + { + name: "Multiple bold elements comprising entire paragraph", + code: "**First part** **second part**", + }, + + // ListItem + { + name: "Bold text in list item", + code: "- **List item**\n- __List item__", + }, + ], + + invalid: [ + // Basic + { + name: "Paragraph with fully bolded text", + code: "#Book\n\n**First Chapter**\n\nContent\n\n__Second Chapter__\n\nContent", + errors: [ + { + messageId: "noEmphasisAsHeading", + line: 3, + column: 1, + endLine: 3, + endColumn: 18, + }, + { + messageId: "noEmphasisAsHeading", + line: 7, + column: 1, + endLine: 7, + endColumn: 19, + }, + ], + }, + { + name: "Paragraph with fully bolded single character", + code: "**X**", + errors: [ + { + messageId: "noEmphasisAsHeading", + line: 1, + column: 1, + endLine: 1, + endColumn: 6, + }, + ], + }, + + // Blockquote + { + name: "Bold text in blockquote", + code: "> **Blockquote**", + errors: [ + { + messageId: "noEmphasisAsHeading", + line: 1, + column: 3, + endLine: 1, + endColumn: 17, + }, + ], + }, + ], +});