diff --git a/helpers/micromark-helpers.cjs b/helpers/micromark-helpers.cjs index aa35b96d..e487ef79 100644 --- a/helpers/micromark-helpers.cjs +++ b/helpers/micromark-helpers.cjs @@ -270,6 +270,26 @@ function getParentOfType(token, types) { return current; } +const docfxTabSyntaxRe = /^#tab\//; + +/** + * Returns whether the specified Micromark token looks like a Docfx tab. + * + * @param {Token | null} heading Micromark token. + * @returns {boolean} True iff the token looks like a Docfx tab. + */ +function isDocfxTab(heading) { + // See https://dotnet.github.io/docfx/docs/markdown.html?tabs=linux%2Cdotnet#tabs + if (heading?.type === "atxHeading") { + const headingTexts = getDescendantsByType(heading, [ "atxHeadingText" ]); + if ((headingTexts.length === 1) && (headingTexts[0].children.length === 1) && (headingTexts[0].children[0].type === "link")) { + const resourceDestinationStrings = filterByTypes(headingTexts[0].children[0].children, [ "resourceDestinationString" ]); + return (resourceDestinationStrings.length === 1) && docfxTabSyntaxRe.test(resourceDestinationStrings[0].text); + } + } + return false; +} + /** * Set containing token types that do not contain content. * @@ -301,6 +321,7 @@ module.exports = { getHtmlTagInfo, getParentOfType, inHtmlFlow, + isDocfxTab, isHtmlFlowComment, nonContentTokens }; diff --git a/lib/md025.mjs b/lib/md025.mjs index aa1b88e4..7b239c2f 100644 --- a/lib/md025.mjs +++ b/lib/md025.mjs @@ -1,7 +1,7 @@ // @ts-check import { addErrorContext, frontMatterHasTitle } from "../helpers/helpers.cjs"; -import { getHeadingLevel, getHeadingText, isHtmlFlowComment, nonContentTokens } from "../helpers/micromark-helpers.cjs"; +import { getHeadingLevel, getHeadingText, isDocfxTab, isHtmlFlowComment, nonContentTokens } from "../helpers/micromark-helpers.cjs"; import { filterByTypesCached } from "./cache.mjs"; /** @type {import("markdownlint").Rule} */ @@ -14,7 +14,7 @@ export default { const level = Number(params.config.level || 1); const { tokens } = params.parsers.micromark; const matchingHeadings = filterByTypesCached([ "atxHeading", "setextHeading" ]) - .filter((heading) => level === getHeadingLevel(heading)); + .filter((heading) => (level === getHeadingLevel(heading)) && !isDocfxTab(heading)); if (matchingHeadings.length > 0) { const foundFrontMatterTitle = frontMatterHasTitle( diff --git a/lib/md051.mjs b/lib/md051.mjs index 5156878f..bcae33b8 100644 --- a/lib/md051.mjs +++ b/lib/md051.mjs @@ -1,7 +1,7 @@ // @ts-check import { addError, getHtmlAttributeRe } from "../helpers/helpers.cjs"; -import { filterByPredicate, filterByTypes, getHtmlTagInfo } from "../helpers/micromark-helpers.cjs"; +import { filterByPredicate, filterByTypes, getHtmlTagInfo, isDocfxTab } from "../helpers/micromark-helpers.cjs"; import { filterByTypesCached } from "./cache.mjs"; // Regular expression for identifying HTML anchor names @@ -110,7 +110,10 @@ export default { [ "definition", "definitionDestinationString" ] ]; for (const [ parentType, definitionType ] of parentChilds) { - const links = filterByTypesCached([ parentType ]); + const links = filterByTypesCached([ parentType ]) + .filter( + (link) => !((link.parent?.type === "atxHeadingText") && isDocfxTab(link.parent.parent)) + ); for (const link of links) { const definitions = filterByTypes(link.children, [ definitionType ]); for (const definition of definitions) { diff --git a/test/docfx-tab-syntax.md b/test/docfx-tab-syntax.md new file mode 100644 index 00000000..82342b43 --- /dev/null +++ b/test/docfx-tab-syntax.md @@ -0,0 +1,39 @@ +# Docfx Tab Syntax + +Examples copied from: + +--- + +# [Tab Display Name](#tab/tab-id) + +--- + +# [Linux](#tab/linux) + +Content for Linux... + +# [Windows](#tab/windows) + +Content for Windows... + +--- + +# [.NET](#tab/dotnet/linux) + +.NET content for Linux... + +# [.NET](#tab/dotnet/windows) + +.NET content for Windows... + +# [TypeScript](#tab/typescript/linux) + +TypeScript content for Linux... + +# [TypeScript](#tab/typescript/windows) + +TypeScript content for Windows... + +# [REST API](#tab/rest) + +REST API content, independent of platform... diff --git a/test/snapshots/markdownlint-test-scenarios.mjs.md b/test/snapshots/markdownlint-test-scenarios.mjs.md index f28f91c4..52ecf928 100644 --- a/test/snapshots/markdownlint-test-scenarios.mjs.md +++ b/test/snapshots/markdownlint-test-scenarios.mjs.md @@ -11348,6 +11348,54 @@ Generated by [AVA](https://avajs.dev). `, } +## docfx-tab-syntax.md + +> Snapshot 1 + + { + errors: [], + fixed: `# Docfx Tab Syntax␊ + ␊ + Examples copied from: ␊ + ␊ + ---␊ + ␊ + # [Tab Display Name](#tab/tab-id)␊ + ␊ + ---␊ + ␊ + # [Linux](#tab/linux)␊ + ␊ + Content for Linux...␊ + ␊ + # [Windows](#tab/windows)␊ + ␊ + Content for Windows...␊ + ␊ + ---␊ + ␊ + # [.NET](#tab/dotnet/linux)␊ + ␊ + .NET content for Linux...␊ + ␊ + # [.NET](#tab/dotnet/windows)␊ + ␊ + .NET content for Windows...␊ + ␊ + # [TypeScript](#tab/typescript/linux)␊ + ␊ + TypeScript content for Linux...␊ + ␊ + # [TypeScript](#tab/typescript/windows)␊ + ␊ + TypeScript content for Windows...␊ + ␊ + # [REST API](#tab/rest)␊ + ␊ + REST API content, independent of platform...␊ + `, + } + ## emoji-headings.md > Snapshot 1 diff --git a/test/snapshots/markdownlint-test-scenarios.mjs.snap b/test/snapshots/markdownlint-test-scenarios.mjs.snap index f10223c2..abe9a035 100644 Binary files a/test/snapshots/markdownlint-test-scenarios.mjs.snap and b/test/snapshots/markdownlint-test-scenarios.mjs.snap differ