Update MD025/single-title to ignore preceding blank lines and comments when looking for a title (top-level heading) (fixes #1420).

This commit is contained in:
David Anson 2025-03-15 17:52:09 -07:00
parent cb39972d9b
commit 5fc91f6977
6 changed files with 123 additions and 15 deletions

View file

@ -1,7 +1,7 @@
// @ts-check // @ts-check
import { addErrorContext, frontMatterHasTitle } from "../helpers/helpers.cjs"; import { addErrorContext, frontMatterHasTitle } from "../helpers/helpers.cjs";
import { getHeadingLevel, getHeadingText } from "../helpers/micromark-helpers.cjs"; import { getHeadingLevel, getHeadingText, isHtmlFlowComment, nonContentTokens } from "../helpers/micromark-helpers.cjs";
import { filterByTypesCached } from "./cache.mjs"; import { filterByTypesCached } from "./cache.mjs";
/** @type {import("markdownlint").Rule} */ /** @type {import("markdownlint").Rule} */
@ -12,24 +12,32 @@ export default {
"parser": "micromark", "parser": "micromark",
"function": function MD025(params, onError) { "function": function MD025(params, onError) {
const level = Number(params.config.level || 1); const level = Number(params.config.level || 1);
const { tokens } = params.parsers.micromark;
const matchingHeadings = filterByTypesCached([ "atxHeading", "setextHeading" ])
.filter((heading) => level === getHeadingLevel(heading));
if (matchingHeadings.length > 0) {
const foundFrontMatterTitle = const foundFrontMatterTitle =
frontMatterHasTitle( frontMatterHasTitle(
params.frontMatterLines, params.frontMatterLines,
params.config.front_matter_title params.config.front_matter_title
); );
let hasTopLevelHeading = false; // Front matter title counts as a top-level heading if present
for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) { let hasTopLevelHeading = foundFrontMatterTitle;
const headingLevel = getHeadingLevel(heading); if (!hasTopLevelHeading) {
if (headingLevel === level) { // Check if the first matching heading is a top-level heading
if (hasTopLevelHeading || foundFrontMatterTitle) { const previousTokens = tokens.slice(0, tokens.indexOf(matchingHeadings[0]));
const headingText = getHeadingText(heading); hasTopLevelHeading = previousTokens.every(
(token) => nonContentTokens.has(token.type) || isHtmlFlowComment(token)
);
}
if (hasTopLevelHeading) {
// All other matching headings are violations
for (const heading of matchingHeadings.slice(foundFrontMatterTitle ? 0 : 1)) {
addErrorContext( addErrorContext(
onError, onError,
heading.startLine, heading.startLine,
headingText getHeadingText(heading)
); );
} else if (heading.startLine === 1) {
hasTopLevelHeading = true;
} }
} }
} }

View file

@ -0,0 +1,6 @@
<!-- Comment -->
# Heading 1
# Heading 2 {MD025}

View file

@ -0,0 +1,4 @@
# Heading 1
# Heading 2 {MD025}

View file

@ -0,0 +1,4 @@
<!-- Comment -->
# Heading 1
# Heading 2 {MD025}

View file

@ -15750,6 +15750,92 @@ Generated by [AVA](https://avajs.dev).
`, `,
} }
## heading-multiple-top-level-preceding-blank-and-comment.md
> Snapshot 1
{
errors: [
{
errorContext: 'Heading 2 {MD025}',
errorDetail: null,
errorRange: null,
fixInfo: null,
lineNumber: 6,
ruleDescription: 'Multiple top-level headings in the same document',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md025.md',
ruleNames: [
'MD025',
'single-title',
'single-h1',
],
},
],
fixed: `␊
<!-- Comment -->
# Heading 1␊
# Heading 2 {MD025}␊
`,
}
## heading-multiple-top-level-preceding-blank.md
> Snapshot 1
{
errors: [
{
errorContext: 'Heading 2 {MD025}',
errorDetail: null,
errorRange: null,
fixInfo: null,
lineNumber: 4,
ruleDescription: 'Multiple top-level headings in the same document',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md025.md',
ruleNames: [
'MD025',
'single-title',
'single-h1',
],
},
],
fixed: `␊
# Heading 1␊
# Heading 2 {MD025}␊
`,
}
## heading-multiple-top-level-preceding-comment.md
> Snapshot 1
{
errors: [
{
errorContext: 'Heading 2 {MD025}',
errorDetail: null,
errorRange: null,
fixInfo: null,
lineNumber: 4,
ruleDescription: 'Multiple top-level headings in the same document',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md025.md',
ruleNames: [
'MD025',
'single-title',
'single-h1',
],
},
],
fixed: `<!-- Comment -->
# Heading 1␊
# Heading 2 {MD025}␊
`,
}
## heading_duplicate_content.md ## heading_duplicate_content.md
> Snapshot 1 > Snapshot 1