Skip to content

feat: add no-heading-like-paragraph rule - #716

Open
Gaic4o wants to merge 2 commits into
eslint:mainfrom
Gaic4o:feat/no-heading-like-paragraph
Open

feat: add no-heading-like-paragraph rule#716
Gaic4o wants to merge 2 commits into
eslint:mainfrom
Gaic4o:feat/no-heading-like-paragraph

Conversation

@Gaic4o

@Gaic4o Gaic4o commented Aug 16, 2026

Copy link
Copy Markdown

Prerequisites checklist

AI acknowledgment

  • I did not use AI to generate this PR.
  • (If the above is not checked) I have reviewed the AI-generated content before submitting.

What is the purpose of this pull request?

This PR implements the no-heading-like-paragraph rule proposed and accepted in #700.

CommonMark ATX headings support at most six # characters. As a result, content such as ####### Installation is parsed as a paragraph rather than a heading, even though it can easily look like an intended heading in the source.

The rule reports these heading-like paragraphs so that likely heading mistakes can be identified.

What changes did you make? (Give an overview)

  • Added the no-heading-like-paragraph rule for paragraphs that look like ATX headings with seven or more leading # characters.
  • The rule checks the raw source to avoid false positives for escaped or character-referenced input.
  • Added suggestions to either convert the paragraph to a valid level-6 heading or escape the first # to keep it as a paragraph.
  • No automatic fix is provided because the author's intent is ambiguous.

Related Issues

fixes #700

Disclosure: I'm a participant of open source contribution program OSSCA

@eslintbot eslintbot added this to Triage Aug 16, 2026
@github-project-automation github-project-automation Bot moved this to Needs Triage in Triage Aug 16, 2026
@lumirlumir lumirlumir moved this from Needs Triage to Triaging in Triage Aug 17, 2026

@lumirlumir lumirlumir left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Disclosure: I'm a participant of open source contribution program OSSCA: confirmed.

Can you take a look at the CI failure? Running npm run fmt should resolve the problem.

Comment on lines +459 to +464
const gfmRuleTester = new RuleTester({
plugins: {
markdown,
},
language: "markdown/gfm",
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we can use language: "markdown/gfm" where necessary to test GFM mode, as shown below. Consolidating these RuleTester test cases with the ones above also seems helpful.

code: dedent`
- [x] Checked
- [-] In progress
`,
language: "markdown/gfm",
options: [{ allowLabels: ["-"] }],

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Integrated the GFM cases into the existing RuleTester as suggested.

Comment thread src/rules/no-heading-like-paragraph.js Outdated
type: "problem",

docs: {
recommended: false,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
recommended: false,

Non-blocking stylistic choice: The repository usually omits the recommended field when it is false.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed recommended: false to match the existing repository style. Thanks!

endColumn: 8,
suggestions: [
{
messageId: "useMaxDepthHashes",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you add the missing data properties to the invalid test cases? This applies to all other invalid test cases as well.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added the required data properties to all invalid test cases.

@lumirlumir lumirlumir moved this from Triaging to Implementing in Triage Aug 17, 2026
Seven ####### characters in the middle of a paragraph.
```

This rule only checks the beginning of a paragraph, so it ignores hash characters on a continuation line:

@DMartens DMartens Aug 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This case should still be handled by the rule as the writer expects to create a heading (which would be created if it would use valid heading syntax).
This could be accomplished by setting the m(ultiline) flag for headingLikeParagraphPattern.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I agree. I’ll update the rule to handle continuation lines as well. I think this will also keep it consistent with no-missing-atx-heading-space.

However, since the m flag alone can miss continuation lines inside blockquotes or list items, would it be okay to handle those container cases as well as the top-level case and add tests for them?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I do not think we need to check for continuation lines inside a container (like blockquotes).
What do you think @lumirlumir?

* the author escaped the leading hash on purpose.
*/
const match = headingLikeParagraphPattern.exec(
sourceCode.getText(node),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
sourceCode.getText(node),
sourceCode.getText(node.children[0]),

Why not use just the first child. This would make the text potentially smaller which is always good when using regular expressions.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Using only the first child could introduce false positives when inline markup immediately follows the hashes. For example, in the current valid test case #######*Installation*, the source of the first child ends with #######, so the $ condition in the regular expression could match. However, in the full paragraph source, the hashes are followed by *, so it does not satisfy the ATX heading delimiter condition.

Also, checking only the first child would not be sufficient for handling continuation lines discussed in the other thread. For example, in Install **first**.\n####### Config, the heading-like text can be contained in a later child rather than the first one, so this case would be missed.

For correctness, I think matching against the whole paragraph source is safer than checking only the first child.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For this edge case you can pass afterCount to get also the following characters, so
sourceCode.getText(node.children[0], 0, 1).

],
},
{
code: "#######",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I do not think this should be an invalid test case as the text after the hashes are missing.
Some may use thisas decoration.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

That makes sense. If there’s no text after the hashes, I agree that it’s much less clear whether the author actually intended to create a heading. For reference, remark-lint-no-heading-like-paragraph does report a bare #######, but I agree that this case could reasonably be treated as decoration.

I’d just like to clarify the intended scope. Should cases like ####### , where the hashes are followed only by trailing whitespace, and #######\nText, where the first line of a multi-line paragraph contains only the hashes, also be ignored? Or should only the single-line bare ####### case be excluded?

Comment thread src/rules/no-heading-like-paragraph.js Outdated
messageId: "headingLikeParagraph",
data: { count: hashes.length },

/*

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This comment is unnecessary as two suggestions are provided, it is clear that there can be no autofix.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I removed the unnecessary comment as you suggested. Thank you!

@Gaic4o
Gaic4o requested review from DMartens and lumirlumir August 19, 2026 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Implementing

Development

Successfully merging this pull request may close these issues.

New Rule: no-heading-like-paragraph

4 participants