From fb15293550431fd2a697d474ddc177254722147c Mon Sep 17 00:00:00 2001 From: GantaRoja Date: Wed, 6 Aug 2025 14:46:11 +0000 Subject: [PATCH 01/11] Added a new function and test case for mismatched title and heading --- scripts/js/lib/markdownTitles.test.ts | 39 ++++++++++++++++++++++ scripts/js/lib/markdownTitles.ts | 47 +++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 scripts/js/lib/markdownTitles.test.ts create mode 100644 scripts/js/lib/markdownTitles.ts diff --git a/scripts/js/lib/markdownTitles.test.ts b/scripts/js/lib/markdownTitles.test.ts new file mode 100644 index 00000000000..ad10fd296cb --- /dev/null +++ b/scripts/js/lib/markdownTitles.test.ts @@ -0,0 +1,39 @@ +// This code is a Qiskit project. +// +// (C) Copyright IBM 2023. +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +import { expect, test } from "@playwright/test"; + +import { collectHeadingTitleMismatch } from "./markdownTitles"; + +test("Test to match mismatched titles and headings", async () => { + const markdown1 = ` +--- +title: My Awesome Guide +--- + +# My Awesome Guide + `; +const markdown2 = `--- +title: Qiskit Doc +author: John +--- + +# Introduction + +This guide will walk you through everything.`; + const mismatched = await collectHeadingTitleMismatch(markdown1); + const mismatched2 = await collectHeadingTitleMismatch(markdown2) + const result: string[] = []; + const result2: string[] = [`Mismatch: frontmatter title "Qiskit Doc" does not match heading "Introduction"`]; +expect(mismatched).toEqual(result); +expect(mismatched2).toEqual(result2); +}); diff --git a/scripts/js/lib/markdownTitles.ts b/scripts/js/lib/markdownTitles.ts new file mode 100644 index 00000000000..c0f9005f2fa --- /dev/null +++ b/scripts/js/lib/markdownTitles.ts @@ -0,0 +1,47 @@ +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkGfm from "remark-gfm"; +import remarkFrontmatter from "remark-frontmatter"; +import { visit } from "unist-util-visit"; +import { Root } from "mdast"; +import yaml from "js-yaml"; + +export async function collectHeadingTitleMismatch(markdown: string): Promise { + const mismatches: string[] = []; + let frontmatterTitle: string | undefined; + let headingText: string | undefined; + + const processor = unified() + .use(remarkParse) + .use(remarkGfm) + .use(remarkFrontmatter, ["yaml"]); + + const tree = processor.parse(markdown); + + // Run the transformer manually + visit(tree, "yaml", (node: any) => { + const data = yaml.load(node.value); + if (typeof data === "object" && data !== null && "title" in data) { + frontmatterTitle = (data as any).title; + console.log("Frontmatter title:", frontmatterTitle); + } + }); + + visit(tree, "heading", (node: any) => { + if (node.depth === 1 && !headingText) { + headingText = node.children + .filter((child: any) => child.type === "text") + .map((child: any) => child.value) + .join(" "); + console.log("Heading text:", headingText); + } + }); + + if (frontmatterTitle && headingText && frontmatterTitle !== headingText) { + mismatches.push( + `Mismatch: frontmatter title "${frontmatterTitle}" does not match heading "${headingText}"` + ); + } + + return mismatches; +} From 176bc954169bfd1361e9cb8b9ff51fc11d06d33a Mon Sep 17 00:00:00 2001 From: GantaRoja Date: Wed, 6 Aug 2025 16:13:44 +0000 Subject: [PATCH 02/11] lint errors fixed --- scripts/js/lib/markdownTitles.test.ts | 80 +++++++++++----------- scripts/js/lib/markdownTitles.ts | 96 ++++++++++++++------------- 2 files changed, 90 insertions(+), 86 deletions(-) diff --git a/scripts/js/lib/markdownTitles.test.ts b/scripts/js/lib/markdownTitles.test.ts index ad10fd296cb..7cb5932defd 100644 --- a/scripts/js/lib/markdownTitles.test.ts +++ b/scripts/js/lib/markdownTitles.test.ts @@ -1,39 +1,41 @@ -// This code is a Qiskit project. -// -// (C) Copyright IBM 2023. -// -// This code is licensed under the Apache License, Version 2.0. You may -// obtain a copy of this license in the LICENSE file in the root directory -// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -// -// Any modifications or derivative works of this code must retain this -// copyright notice, and modified files need to carry a notice indicating -// that they have been altered from the originals. - -import { expect, test } from "@playwright/test"; - -import { collectHeadingTitleMismatch } from "./markdownTitles"; - -test("Test to match mismatched titles and headings", async () => { - const markdown1 = ` ---- -title: My Awesome Guide ---- - -# My Awesome Guide - `; -const markdown2 = `--- -title: Qiskit Doc -author: John ---- - -# Introduction - -This guide will walk you through everything.`; - const mismatched = await collectHeadingTitleMismatch(markdown1); - const mismatched2 = await collectHeadingTitleMismatch(markdown2) - const result: string[] = []; - const result2: string[] = [`Mismatch: frontmatter title "Qiskit Doc" does not match heading "Introduction"`]; -expect(mismatched).toEqual(result); -expect(mismatched2).toEqual(result2); -}); +// This code is a Qiskit project. +// +// (C) Copyright IBM 2023. +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +import { expect, test } from "@playwright/test"; + +import { collectHeadingTitleMismatch } from "./markdownTitles"; + +test("Test to match mismatched titles and headings", async () => { + const markdown1 = ` +--- +title: My Awesome Guide +--- + +# My Awesome Guide + `; + const markdown2 = `--- +title: Qiskit Doc +author: John +--- + +# Introduction + +This guide will walk you through everything.`; + const mismatched = await collectHeadingTitleMismatch(markdown1); + const mismatched2 = await collectHeadingTitleMismatch(markdown2); + const result: string[] = []; + const result2: string[] = [ + `Mismatch: frontmatter title "Qiskit Doc" does not match heading "Introduction"`, + ]; + expect(mismatched).toEqual(result); + expect(mismatched2).toEqual(result2); +}); diff --git a/scripts/js/lib/markdownTitles.ts b/scripts/js/lib/markdownTitles.ts index c0f9005f2fa..ff6980d5fec 100644 --- a/scripts/js/lib/markdownTitles.ts +++ b/scripts/js/lib/markdownTitles.ts @@ -1,47 +1,49 @@ -import { unified } from "unified"; -import remarkParse from "remark-parse"; -import remarkGfm from "remark-gfm"; -import remarkFrontmatter from "remark-frontmatter"; -import { visit } from "unist-util-visit"; -import { Root } from "mdast"; -import yaml from "js-yaml"; - -export async function collectHeadingTitleMismatch(markdown: string): Promise { - const mismatches: string[] = []; - let frontmatterTitle: string | undefined; - let headingText: string | undefined; - - const processor = unified() - .use(remarkParse) - .use(remarkGfm) - .use(remarkFrontmatter, ["yaml"]); - - const tree = processor.parse(markdown); - - // Run the transformer manually - visit(tree, "yaml", (node: any) => { - const data = yaml.load(node.value); - if (typeof data === "object" && data !== null && "title" in data) { - frontmatterTitle = (data as any).title; - console.log("Frontmatter title:", frontmatterTitle); - } - }); - - visit(tree, "heading", (node: any) => { - if (node.depth === 1 && !headingText) { - headingText = node.children - .filter((child: any) => child.type === "text") - .map((child: any) => child.value) - .join(" "); - console.log("Heading text:", headingText); - } - }); - - if (frontmatterTitle && headingText && frontmatterTitle !== headingText) { - mismatches.push( - `Mismatch: frontmatter title "${frontmatterTitle}" does not match heading "${headingText}"` - ); - } - - return mismatches; -} +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkGfm from "remark-gfm"; +import remarkFrontmatter from "remark-frontmatter"; +import { visit } from "unist-util-visit"; +import { Root } from "mdast"; +import yaml from "js-yaml"; + +export async function collectHeadingTitleMismatch( + markdown: string, +): Promise { + const mismatches: string[] = []; + let frontmatterTitle: string | undefined; + let headingText: string | undefined; + + const processor = unified() + .use(remarkParse) + .use(remarkGfm) + .use(remarkFrontmatter, ["yaml"]); + + const tree = processor.parse(markdown); + + // Run the transformer manually + visit(tree, "yaml", (node: any) => { + const data = yaml.load(node.value); + if (typeof data === "object" && data !== null && "title" in data) { + frontmatterTitle = (data as any).title; + console.log("Frontmatter title:", frontmatterTitle); + } + }); + + visit(tree, "heading", (node: any) => { + if (node.depth === 1 && !headingText) { + headingText = node.children + .filter((child: any) => child.type === "text") + .map((child: any) => child.value) + .join(" "); + console.log("Heading text:", headingText); + } + }); + + if (frontmatterTitle && headingText && frontmatterTitle !== headingText) { + mismatches.push( + `Mismatch: frontmatter title "${frontmatterTitle}" does not match heading "${headingText}"`, + ); + } + + return mismatches; +} From 9ab781cef50d90d7a6496dfa130f5bc6c3b3707b Mon Sep 17 00:00:00 2001 From: GantaRoja Date: Thu, 7 Aug 2025 05:53:39 +0000 Subject: [PATCH 03/11] separaeted tests removed consoles --- scripts/js/lib/markdownTitles.test.ts | 37 ++++++++++++++++++++++----- scripts/js/lib/markdownTitles.ts | 36 ++++++++++++++++++-------- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/scripts/js/lib/markdownTitles.test.ts b/scripts/js/lib/markdownTitles.test.ts index 7cb5932defd..66af6e24797 100644 --- a/scripts/js/lib/markdownTitles.test.ts +++ b/scripts/js/lib/markdownTitles.test.ts @@ -14,15 +14,20 @@ import { expect, test } from "@playwright/test"; import { collectHeadingTitleMismatch } from "./markdownTitles"; -test("Test to match mismatched titles and headings", async () => { - const markdown1 = ` ---- +test("Test for matching titles and headings", async () => { + const markdown1 = `--- title: My Awesome Guide --- # My Awesome Guide `; - const markdown2 = `--- +const mismatched = await collectHeadingTitleMismatch(markdown1); +const result: string[] = []; + expect(mismatched).toEqual(result); +}); + +test("Test to find mismatched titles and headings", async () => { +const markdown2 = `--- title: Qiskit Doc author: John --- @@ -30,12 +35,30 @@ author: John # Introduction This guide will walk you through everything.`; - const mismatched = await collectHeadingTitleMismatch(markdown1); + const mismatched2 = await collectHeadingTitleMismatch(markdown2); - const result: string[] = []; + const result2: string[] = [ `Mismatch: frontmatter title "Qiskit Doc" does not match heading "Introduction"`, ]; - expect(mismatched).toEqual(result); + + expect(mismatched2).toEqual(result2); +}); + +test("Test to mismatched and complex titles and headings", async () => { +const markdown2 = `--- +title: My Awesome Guide +--- + +# This is a *Heading* + +This guide will walk you through everything.`; + + const mismatched2 = await collectHeadingTitleMismatch(markdown2); + + const result2: string[] = [ + `Mismatch: frontmatter title "My Awesome Guide" does not match heading "This is a Heading"`, + ]; + expect(mismatched2).toEqual(result2); }); diff --git a/scripts/js/lib/markdownTitles.ts b/scripts/js/lib/markdownTitles.ts index ff6980d5fec..61867be2a78 100644 --- a/scripts/js/lib/markdownTitles.ts +++ b/scripts/js/lib/markdownTitles.ts @@ -6,10 +6,28 @@ import { visit } from "unist-util-visit"; import { Root } from "mdast"; import yaml from "js-yaml"; +// Helper to recursively extract visible text from heading node +function extractText(node: any): string { + if (node.type === "text" || node.type === "inlineCode") { + return node.value; + } + + if (node.type === "link" && node.children) { + return node.children.map(extractText).join(" "); + } + + if (node.children && Array.isArray(node.children)) { + return node.children.map(extractText).join(" "); + } + + return ""; +} + export async function collectHeadingTitleMismatch( markdown: string, ): Promise { - const mismatches: string[] = []; + const mismatches = new Set(); + let frontmatterTitle: string | undefined; let headingText: string | undefined; @@ -20,30 +38,28 @@ export async function collectHeadingTitleMismatch( const tree = processor.parse(markdown); - // Run the transformer manually + // Extract frontmatter title visit(tree, "yaml", (node: any) => { const data = yaml.load(node.value); if (typeof data === "object" && data !== null && "title" in data) { frontmatterTitle = (data as any).title; - console.log("Frontmatter title:", frontmatterTitle); } }); + // Extract first level-1 heading with full formatting visit(tree, "heading", (node: any) => { if (node.depth === 1 && !headingText) { - headingText = node.children - .filter((child: any) => child.type === "text") - .map((child: any) => child.value) - .join(" "); - console.log("Heading text:", headingText); + headingText = extractText(node).trim(); } }); + // Compare and collect mismatch if (frontmatterTitle && headingText && frontmatterTitle !== headingText) { - mismatches.push( + mismatches.add( `Mismatch: frontmatter title "${frontmatterTitle}" does not match heading "${headingText}"`, ); } - return mismatches; + return Array.from(mismatches); } + From 32cfd9d7141132c80a28fb3cf7d28313020f3d63 Mon Sep 17 00:00:00 2001 From: GantaRoja Date: Thu, 7 Aug 2025 06:58:02 +0000 Subject: [PATCH 04/11] lint error fixed --- scripts/js/lib/markdownTitles.test.ts | 20 ++++++++++---------- scripts/js/lib/markdownTitles.ts | 1 - 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/scripts/js/lib/markdownTitles.test.ts b/scripts/js/lib/markdownTitles.test.ts index 66af6e24797..4d50b9f895e 100644 --- a/scripts/js/lib/markdownTitles.test.ts +++ b/scripts/js/lib/markdownTitles.test.ts @@ -21,13 +21,13 @@ title: My Awesome Guide # My Awesome Guide `; -const mismatched = await collectHeadingTitleMismatch(markdown1); -const result: string[] = []; + const mismatched = await collectHeadingTitleMismatch(markdown1); + const result: string[] = []; expect(mismatched).toEqual(result); }); test("Test to find mismatched titles and headings", async () => { -const markdown2 = `--- + const markdown2 = `--- title: Qiskit Doc author: John --- @@ -35,30 +35,30 @@ author: John # Introduction This guide will walk you through everything.`; - + const mismatched2 = await collectHeadingTitleMismatch(markdown2); - + const result2: string[] = [ `Mismatch: frontmatter title "Qiskit Doc" does not match heading "Introduction"`, ]; - + expect(mismatched2).toEqual(result2); }); test("Test to mismatched and complex titles and headings", async () => { -const markdown2 = `--- + const markdown2 = `--- title: My Awesome Guide --- # This is a *Heading* This guide will walk you through everything.`; - + const mismatched2 = await collectHeadingTitleMismatch(markdown2); - + const result2: string[] = [ `Mismatch: frontmatter title "My Awesome Guide" does not match heading "This is a Heading"`, ]; - + expect(mismatched2).toEqual(result2); }); diff --git a/scripts/js/lib/markdownTitles.ts b/scripts/js/lib/markdownTitles.ts index 61867be2a78..21b0e7ec21f 100644 --- a/scripts/js/lib/markdownTitles.ts +++ b/scripts/js/lib/markdownTitles.ts @@ -62,4 +62,3 @@ export async function collectHeadingTitleMismatch( return Array.from(mismatches); } - From 5b5fd21e17309ece2483898dee88a92d7a8df538 Mon Sep 17 00:00:00 2001 From: GantaRoja Date: Thu, 7 Aug 2025 13:17:14 +0000 Subject: [PATCH 05/11] called the function and rename of checkimages to checkMarkdoen --- package.json | 2 +- .../{checkImages.ts => checkMarkdown.ts} | 34 ++++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) rename scripts/js/commands/{checkImages.ts => checkMarkdown.ts} (70%) diff --git a/package.json b/package.json index df2ffe2bc5b..20874f6e543 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test": "playwright test", "typecheck": "tsc", "check": "npm run check:patterns-index && npm run check:qiskit-bot && npm run check:metadata && npm run check:images && npm run check:orphan-pages && npm run check:spelling && npm run check:internal-links", - "check:images": "tsx scripts/js/commands/checkImages.ts", + "check:images": "tsx scripts/js/commands/checkMarkdown.ts", "check:metadata": "tsx scripts/js/commands/checkMetadata.ts", "check:spelling": "tsx scripts/js/commands/checkSpelling.ts", "check:fmt": "prettier --check .", diff --git a/scripts/js/commands/checkImages.ts b/scripts/js/commands/checkMarkdown.ts similarity index 70% rename from scripts/js/commands/checkImages.ts rename to scripts/js/commands/checkMarkdown.ts index 1085838860e..e6c8ff3bf6b 100644 --- a/scripts/js/commands/checkImages.ts +++ b/scripts/js/commands/checkMarkdown.ts @@ -13,8 +13,10 @@ import { globby } from "globby"; import yargs from "yargs/yargs"; import { hideBin } from "yargs/helpers"; + import { collectInvalidImageErrors } from "../lib/markdownImages.js"; import { readMarkdown } from "../lib/markdownReader.js"; +import { collectHeadingTitleMismatch } from "../lib/markdownTitles.js"; interface Arguments { [x: string]: unknown; @@ -35,29 +37,53 @@ const readArgs = (): Arguments => { async function main() { const args = readArgs(); - const files = await determineContentFiles(args); const fileErrors: string[] = []; for (const file of files) { const markdown = await readMarkdown(file); const imageErrors = await collectInvalidImageErrors(markdown); + const mismatchedTitleHeadingErrors = + await collectHeadingTitleMismatch(markdown); + + const errorsInFile: string[] = []; + // Handle image errors if (imageErrors.size) { + errorsInFile.push( + ...[...imageErrors].map((err) => `Image error: ${err}`), + ); + } + + //Handle title/heading mismatch errors + if (mismatchedTitleHeadingErrors.length) { + errorsInFile.push( + ...mismatchedTitleHeadingErrors.map( + (err) => `Title/Heading mismatch: ${err}`, + ), + ); + } + + //Collect all errors for this file + if (errorsInFile.length) { fileErrors.push( - `Error in file '${file}':\n\t- ${[...imageErrors].join("\n\t- ")}\n`, + `Error in file '${file}':\n\t- ${errorsInFile.join("\n\t- ")}\n`, ); } } + // Final error report if (fileErrors.length) { fileErrors.forEach((error) => console.log(error)); console.error( - "💔 Some images have problems. See https://github.com/Qiskit/documentation#images for instructions.\n", + "Some issues were found in your Markdown files. Please fix them before proceeding.\n" + + "Image help: https://github.com/Qiskit/documentation#images\n" + + "Title/Heading help: https://github.com/Qiskit/documentation#titles-and-headings\n", ); process.exit(1); } - console.log("✅ All images are valid.\n"); + + console.log("All files passed validation.\n"); } async function determineContentFiles(args: Arguments): Promise { From 86b5296b0918e2df26cd61042b9488e259b77a38 Mon Sep 17 00:00:00 2001 From: GantaRoja Date: Fri, 8 Aug 2025 11:46:13 +0000 Subject: [PATCH 06/11] checkpoint --- .github/workflows/api-checks.yml | 4 +-- .github/workflows/main.yml | 4 +-- package.json | 4 +-- scripts/js/commands/checkMarkdown.ts | 40 +++++++++++---------------- scripts/js/lib/markdownTitles.test.ts | 10 +++---- scripts/js/lib/markdownTitles.ts | 8 ++++-- 6 files changed, 32 insertions(+), 38 deletions(-) diff --git a/.github/workflows/api-checks.yml b/.github/workflows/api-checks.yml index 093d6f0f2b2..ce5c77fc16a 100644 --- a/.github/workflows/api-checks.yml +++ b/.github/workflows/api-checks.yml @@ -44,5 +44,5 @@ jobs: run: npm run check:orphan-pages -- --apis - name: Check metadata run: npm run check:metadata -- --apis - - name: Check images - run: npm run check:images -- --apis + - name: Check markdown + run: npm run check:markdown -- --apis diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5ef39d6fb1d..3e08e9f6a82 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,8 +35,8 @@ jobs: - name: File metadata run: npm run check:metadata - - name: Check images - run: npm run check:images + - name: Check markdown + run: npm run check:markdown - name: Spellcheck run: npm run check:spelling - name: Check Qiskit bot config diff --git a/package.json b/package.json index 20874f6e543..c9811e37387 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test": "playwright test", "typecheck": "tsc", "check": "npm run check:patterns-index && npm run check:qiskit-bot && npm run check:metadata && npm run check:images && npm run check:orphan-pages && npm run check:spelling && npm run check:internal-links", - "check:images": "tsx scripts/js/commands/checkMarkdown.ts", + "check:markdown": "tsx scripts/js/commands/checkMarkdown.ts", "check:metadata": "tsx scripts/js/commands/checkMetadata.ts", "check:spelling": "tsx scripts/js/commands/checkSpelling.ts", "check:fmt": "prettier --check .", @@ -57,7 +57,7 @@ "remark-mdx": "^2.3.0", "remark-parse": "^10.0.1", "remark-stringify": "^10.0.3", - "transform-markdown-links": "^2.1.0", + "transform-images-links": "^2.1.0", "unified": "^10.0.0", "unist-util-visit": "^4.0.0", "yargs": "^17.7.2", diff --git a/scripts/js/commands/checkMarkdown.ts b/scripts/js/commands/checkMarkdown.ts index e6c8ff3bf6b..e498987f059 100644 --- a/scripts/js/commands/checkMarkdown.ts +++ b/scripts/js/commands/checkMarkdown.ts @@ -18,6 +18,10 @@ import { collectInvalidImageErrors } from "../lib/markdownImages.js"; import { readMarkdown } from "../lib/markdownReader.js"; import { collectHeadingTitleMismatch } from "../lib/markdownTitles.js"; +const IGNORE_TITLE_MISMATCHES: string[] = []; + +const allErrors: string[] = []; + interface Arguments { [x: string]: unknown; apis: boolean; @@ -38,43 +42,31 @@ const readArgs = (): Arguments => { async function main() { const args = readArgs(); const files = await determineContentFiles(args); - const fileErrors: string[] = []; for (const file of files) { const markdown = await readMarkdown(file); const imageErrors = await collectInvalidImageErrors(markdown); - const mismatchedTitleHeadingErrors = - await collectHeadingTitleMismatch(markdown); - - const errorsInFile: string[] = []; - - // Handle image errors - if (imageErrors.size) { - errorsInFile.push( - ...[...imageErrors].map((err) => `Image error: ${err}`), - ); - } - - //Handle title/heading mismatch errors - if (mismatchedTitleHeadingErrors.length) { - errorsInFile.push( - ...mismatchedTitleHeadingErrors.map( - (err) => `Title/Heading mismatch: ${err}`, - ), - ); - } + const mismatchedTitleHeadingErrors = IGNORE_TITLE_MISMATCHES.includes(file) + ? new Set() + : await collectHeadingTitleMismatch(markdown); //Collect all errors for this file + const errorsInFile: string[] = [ + ...imageErrors, + ...mismatchedTitleHeadingErrors, + ]; + if (errorsInFile.length) { - fileErrors.push( + allErrors.push( + // `Error in file '${file}':\n\t- ${[...imageErrors].join("\n\t- ")}\n`, `Error in file '${file}':\n\t- ${errorsInFile.join("\n\t- ")}\n`, ); } } // Final error report - if (fileErrors.length) { - fileErrors.forEach((error) => console.log(error)); + if (allErrors.length) { + allErrors.forEach((error) => console.log(error)); console.error( "Some issues were found in your Markdown files. Please fix them before proceeding.\n" + "Image help: https://github.com/Qiskit/documentation#images\n" + diff --git a/scripts/js/lib/markdownTitles.test.ts b/scripts/js/lib/markdownTitles.test.ts index 4d50b9f895e..53ebbac4d3b 100644 --- a/scripts/js/lib/markdownTitles.test.ts +++ b/scripts/js/lib/markdownTitles.test.ts @@ -22,7 +22,7 @@ title: My Awesome Guide # My Awesome Guide `; const mismatched = await collectHeadingTitleMismatch(markdown1); - const result: string[] = []; + const result: Set = new Set(); expect(mismatched).toEqual(result); }); @@ -38,9 +38,9 @@ This guide will walk you through everything.`; const mismatched2 = await collectHeadingTitleMismatch(markdown2); - const result2: string[] = [ + const result2: Set = new Set([ `Mismatch: frontmatter title "Qiskit Doc" does not match heading "Introduction"`, - ]; + ]); expect(mismatched2).toEqual(result2); }); @@ -56,9 +56,9 @@ This guide will walk you through everything.`; const mismatched2 = await collectHeadingTitleMismatch(markdown2); - const result2: string[] = [ + const result2: Set = new Set([ `Mismatch: frontmatter title "My Awesome Guide" does not match heading "This is a Heading"`, - ]; + ]); expect(mismatched2).toEqual(result2); }); diff --git a/scripts/js/lib/markdownTitles.ts b/scripts/js/lib/markdownTitles.ts index 21b0e7ec21f..6cf145d943d 100644 --- a/scripts/js/lib/markdownTitles.ts +++ b/scripts/js/lib/markdownTitles.ts @@ -2,7 +2,7 @@ import { unified } from "unified"; import remarkParse from "remark-parse"; import remarkGfm from "remark-gfm"; import remarkFrontmatter from "remark-frontmatter"; -import { visit } from "unist-util-visit"; +import { visit, EXIT } from "unist-util-visit"; import { Root } from "mdast"; import yaml from "js-yaml"; @@ -25,7 +25,7 @@ function extractText(node: any): string { export async function collectHeadingTitleMismatch( markdown: string, -): Promise { +): Promise> { const mismatches = new Set(); let frontmatterTitle: string | undefined; @@ -43,6 +43,7 @@ export async function collectHeadingTitleMismatch( const data = yaml.load(node.value); if (typeof data === "object" && data !== null && "title" in data) { frontmatterTitle = (data as any).title; + //return EXIT; } }); @@ -50,6 +51,7 @@ export async function collectHeadingTitleMismatch( visit(tree, "heading", (node: any) => { if (node.depth === 1 && !headingText) { headingText = extractText(node).trim(); + //return EXIT; } }); @@ -60,5 +62,5 @@ export async function collectHeadingTitleMismatch( ); } - return Array.from(mismatches); + return mismatches; } From 4e6ac5f37f2db347a420c3e7b7b9df23b160133d Mon Sep 17 00:00:00 2001 From: GantaRoja Date: Fri, 8 Aug 2025 15:23:29 +0000 Subject: [PATCH 07/11] modified check file --- check | 2 +- scripts/js/commands/checkMarkdown.ts | 47 +++++++++++++++++++++++++++- scripts/js/lib/markdownTitles.ts | 4 +-- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/check b/check index c71b9c9f40d..b1ce76f2297 100755 --- a/check +++ b/check @@ -25,7 +25,7 @@ CHECKS = { "metadata": ["npm", "run", "check:metadata"], "patterns index pages": ["npm", "run", "check:patterns-index"], "tutorials index page": ["python3", "scripts/ci/check-tutorials-index.py"], - "images": ["npm", "run", "check:images"], + "images": ["npm", "run", "check:markdown"], "orphan pages": ["npm", "run", "check:orphan-pages"], "spelling": ["npm", "run", "check:spelling"], "internal links": ["npm", "run", "check:internal-links"], diff --git a/scripts/js/commands/checkMarkdown.ts b/scripts/js/commands/checkMarkdown.ts index e498987f059..98366254d2f 100644 --- a/scripts/js/commands/checkMarkdown.ts +++ b/scripts/js/commands/checkMarkdown.ts @@ -18,7 +18,52 @@ import { collectInvalidImageErrors } from "../lib/markdownImages.js"; import { readMarkdown } from "../lib/markdownReader.js"; import { collectHeadingTitleMismatch } from "../lib/markdownTitles.js"; -const IGNORE_TITLE_MISMATCHES: string[] = []; +const IGNORE_TITLE_MISMATCHES: string[] = [ + "docs/migration-guides/external-providers-primitives-v2.mdx", + "docs/migration-guides/local-simulators.mdx", + "docs/migration-guides/metapackage-migration.mdx", + "docs/migration-guides/qiskit-1.0-features.mdx", + "docs/migration-guides/qiskit-1.0-installation.mdx", + "docs/migration-guides/qiskit-algorithms-module.mdx", + "docs/migration-guides/qiskit-backend-primitives.mdx", + "docs/migration-guides/qiskit-backendv1-to-v2.mdx", + "docs/migration-guides/qiskit-opflow-module.mdx", + "docs/migration-guides/qiskit-runtime-from-ibm-provider.mdx", + "docs/migration-guides/qiskit-runtime-from-ibmq-provider.mdx", + "docs/migration-guides/qiskit-runtime-options.mdx", + "docs/guides/access-groups.mdx", + "docs/migration-guides/v2-primitives.mdx", + "docs/guides/execution-modes.mdx", + "docs/guides/install-qiskit-source.mdx", + "docs/guides/manage-cost.mdx", + "docs/guides/plans-overview.mdx", + "docs/guides/qiskit-addons-aqc.mdx", + "docs/guides/qiskit-addons-sqd.mdx", + "docs/guides/qiskit-code-assistant-vscode.mdx", + "docs/guides/qiskit-function-templates.mdx", + "docs/guides/serverless.mdx", + "docs/open-source/code-of-conduct.mdx", + "docs/open-source/create-a-provider.mdx", + "docs/support/execution-modes-faq.mdx", + "docs/support/faq.mdx", + "learning/index.mdx", + "learning/courses/basics-of-quantum-information/exam.mdx", + "learning/courses/basics-of-quantum-information/index.mdx", + "learning/courses/foundations-of-quantum-error-correction/index.mdx", + "learning/courses/fundamentals-of-quantum-algorithms/exam.mdx", + "learning/courses/fundamentals-of-quantum-algorithms/index.mdx", + "learning/courses/quantum-business-foundations/business-impacts.mdx", + "learning/courses/quantum-business-foundations/exam.mdx", + "learning/courses/quantum-business-foundations/quantum-computing-fundamentals.mdx", + "learning/courses/quantum-business-foundations/quantum-technology.mdx", + "learning/courses/quantum-business-foundations/start-your-quantum-journey.mdx", + "learning/courses/quantum-chem-with-vqe/exam.mdx", + "learning/courses/quantum-diagonalization-algorithms/exam.mdx", + "learning/courses/quantum-machine-learning/exam.mdx", + "learning/courses/quantum-safe-cryptography/exam.mdx", + "learning/courses/utility-scale-quantum-computing/classical-simulation.mdx", + "learning/courses/variational-algorithm-design/exam.mdx", +]; const allErrors: string[] = []; diff --git a/scripts/js/lib/markdownTitles.ts b/scripts/js/lib/markdownTitles.ts index 6cf145d943d..53028a79b82 100644 --- a/scripts/js/lib/markdownTitles.ts +++ b/scripts/js/lib/markdownTitles.ts @@ -43,7 +43,7 @@ export async function collectHeadingTitleMismatch( const data = yaml.load(node.value); if (typeof data === "object" && data !== null && "title" in data) { frontmatterTitle = (data as any).title; - //return EXIT; + return EXIT; } }); @@ -51,7 +51,7 @@ export async function collectHeadingTitleMismatch( visit(tree, "heading", (node: any) => { if (node.depth === 1 && !headingText) { headingText = extractText(node).trim(); - //return EXIT; + return EXIT; } }); From 94d1e010f3a80c23000b4e257742c588e482ff8a Mon Sep 17 00:00:00 2001 From: GantaRoja Date: Fri, 8 Aug 2025 16:13:00 +0000 Subject: [PATCH 08/11] corrected packagejson file --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c9811e37387..ddda2c0d18e 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "remark-mdx": "^2.3.0", "remark-parse": "^10.0.1", "remark-stringify": "^10.0.3", - "transform-images-links": "^2.1.0", + "transform-markdown-links": "^2.1.0", "unified": "^10.0.0", "unist-util-visit": "^4.0.0", "yargs": "^17.7.2", From 6a9af5b0493a4cf039bf0a1d3b0662db1dd930bd Mon Sep 17 00:00:00 2001 From: GantaRoja Date: Fri, 8 Aug 2025 17:37:45 +0000 Subject: [PATCH 09/11] ignored files starts with doc/spi --- scripts/js/commands/checkMarkdown.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/js/commands/checkMarkdown.ts b/scripts/js/commands/checkMarkdown.ts index 98366254d2f..155d75cf79e 100644 --- a/scripts/js/commands/checkMarkdown.ts +++ b/scripts/js/commands/checkMarkdown.ts @@ -91,9 +91,10 @@ async function main() { for (const file of files) { const markdown = await readMarkdown(file); const imageErrors = await collectInvalidImageErrors(markdown); - const mismatchedTitleHeadingErrors = IGNORE_TITLE_MISMATCHES.includes(file) - ? new Set() - : await collectHeadingTitleMismatch(markdown); + const mismatchedTitleHeadingErrors = + IGNORE_TITLE_MISMATCHES.includes(file) || file.startsWith("docs/api") + ? new Set() + : await collectHeadingTitleMismatch(markdown); //Collect all errors for this file const errorsInFile: string[] = [ From 8012257664f49e335d90b417d6efc84deac4d264 Mon Sep 17 00:00:00 2001 From: GantaRoja Date: Mon, 11 Aug 2025 17:51:24 +0000 Subject: [PATCH 10/11] moved parsing step out of the function --- scripts/js/commands/checkMarkdown.ts | 19 ++++++-- scripts/js/lib/markdownImages.test.ts | 19 +++++++- scripts/js/lib/markdownImages.ts | 68 ++++++++++----------------- scripts/js/lib/markdownTitles.test.ts | 38 ++++++++++----- scripts/js/lib/markdownTitles.ts | 25 +++++----- 5 files changed, 97 insertions(+), 72 deletions(-) diff --git a/scripts/js/commands/checkMarkdown.ts b/scripts/js/commands/checkMarkdown.ts index 155d75cf79e..7a112642bc6 100644 --- a/scripts/js/commands/checkMarkdown.ts +++ b/scripts/js/commands/checkMarkdown.ts @@ -13,6 +13,10 @@ import { globby } from "globby"; import yargs from "yargs/yargs"; import { hideBin } from "yargs/helpers"; +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkGfm from "remark-gfm"; +import remarkFrontmatter from "remark-frontmatter"; import { collectInvalidImageErrors } from "../lib/markdownImages.js"; import { readMarkdown } from "../lib/markdownReader.js"; @@ -90,11 +94,18 @@ async function main() { for (const file of files) { const markdown = await readMarkdown(file); - const imageErrors = await collectInvalidImageErrors(markdown); + const processor = unified() + .use(remarkParse) + .use(remarkGfm) + .use(remarkFrontmatter, ["yaml"]); + + const tree = processor.parse(markdown); + + const imageErrors = await collectInvalidImageErrors(tree); const mismatchedTitleHeadingErrors = IGNORE_TITLE_MISMATCHES.includes(file) || file.startsWith("docs/api") ? new Set() - : await collectHeadingTitleMismatch(markdown); + : await collectHeadingTitleMismatch(tree); //Collect all errors for this file const errorsInFile: string[] = [ @@ -114,14 +125,14 @@ async function main() { if (allErrors.length) { allErrors.forEach((error) => console.log(error)); console.error( - "Some issues were found in your Markdown files. Please fix them before proceeding.\n" + + "💔 Some issues were found in your Markdown files. Please fix them before proceeding.\n" + "Image help: https://github.com/Qiskit/documentation#images\n" + "Title/Heading help: https://github.com/Qiskit/documentation#titles-and-headings\n", ); process.exit(1); } - console.log("All files passed validation.\n"); + console.log("✅ All files passed validation.\n"); } async function determineContentFiles(args: Arguments): Promise { diff --git a/scripts/js/lib/markdownImages.test.ts b/scripts/js/lib/markdownImages.test.ts index 70e27a6affc..e519840ae5d 100644 --- a/scripts/js/lib/markdownImages.test.ts +++ b/scripts/js/lib/markdownImages.test.ts @@ -11,6 +11,19 @@ // that they have been altered from the originals. import { expect, test } from "@playwright/test"; +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkGfm from "remark-gfm"; +import remarkFrontmatter from "remark-frontmatter"; +import { Root } from "mdast"; + +function parseMarkdown(markdown: string): Root { + return unified() + .use(remarkParse) + .use(remarkGfm) + .use(remarkFrontmatter, ["yaml"]) + .parse(markdown); +} import { collectInvalidImageErrors } from "./markdownImages.js"; @@ -45,8 +58,12 @@ test("Test the finding of invalid images", async () => { ![And a valid SVG](/learning/images/valid.svg) + `; - const images = await collectInvalidImageErrors(markdown); + + const tree = parseMarkdown(markdown); + const images = await collectInvalidImageErrors(tree); + const correct_images = new Set([ "Convert 'img1.png' to AVIF. You can use the command `magick .png .avif`. If ImageMagick isn't preinstalled, you can get it from https://imagemagick.org/script/download.php. Then delete the old file and update the markdown to point to the new file.", "Convert 'img2.png' to AVIF. You can use the command `magick .png .avif`. If ImageMagick isn't preinstalled, you can get it from https://imagemagick.org/script/download.php. Then delete the old file and update the markdown to point to the new file.", diff --git a/scripts/js/lib/markdownImages.ts b/scripts/js/lib/markdownImages.ts index ea2c3b180c0..af2054c9c50 100644 --- a/scripts/js/lib/markdownImages.ts +++ b/scripts/js/lib/markdownImages.ts @@ -11,55 +11,37 @@ // that they have been altered from the originals. import { load } from "cheerio"; -import { unified } from "unified"; -import { Root } from "remark-mdx"; +import { Root } from "mdast"; import { visit } from "unist-util-visit"; -import remarkParse from "remark-parse"; -import remarkGfm from "remark-gfm"; -import remarkStringify from "remark-stringify"; import { last, split } from "lodash-es"; -export async function collectInvalidImageErrors( - markdown: string, -): Promise> { +export function collectInvalidImageErrors(tree: Root): Set { const imagesErrors = new Set(); - await unified() - .use(remarkParse) - .use(remarkGfm) - .use(() => (tree: Root) => { - visit(tree, "image", (node) => { - // Sphinx uses the image path as alt text if it wasn't defined using the - // :alt: option. - const imageName = last(split(node.url, "/")); - if (!node.alt || node.alt.endsWith(imageName!)) { - imagesErrors.add(`The image '${node.url}' does not have alt text.`); - } + visit(tree, "image", (node) => { + const imageName = last(split(node.url, "/")); + if (!node.alt || node.alt.endsWith(imageName!)) { + imagesErrors.add(`The image '${node.url}' does not have alt text.`); + } - // Ask to convert PNG and JPEG to AVIF - if (node.url.match(/\.(png|jpe?g)$/)) { - const urlWithAvifExtension = node.url.replace( - /\.(png|jpe?g)$/, - ".avif", - ); - imagesErrors.add( - `Convert '${imageName}' to AVIF. You can use the command \`magick .png .avif\`. ` + - `If ImageMagick isn't preinstalled, you can get it from https://imagemagick.org/script/download.php. ` + - `Then delete the old file and update the markdown to point to the new file.`, - ); - } - }); - visit(tree, "html", (node) => { - const $ = load(node.value); - if ($("img").length) { - imagesErrors.add( - `The image '${$("img").attr("src")}' uses an HTML tag instead of markdown syntax.`, - ); - } - }); - }) - .use(remarkStringify) - .process(markdown); + if (node.url.match(/\.(png|jpe?g)$/)) { + const urlWithAvifExtension = node.url.replace(/\.(png|jpe?g)$/, ".avif"); + imagesErrors.add( + `Convert '${imageName}' to AVIF. You can use the command \`magick .png .avif\`. ` + + `If ImageMagick isn't preinstalled, you can get it from https://imagemagick.org/script/download.php. ` + + `Then delete the old file and update the markdown to point to the new file.`, + ); + } + }); + + visit(tree, "html", (node) => { + const $ = load(node.value); + if ($("img").length) { + imagesErrors.add( + `The image '${$("img").attr("src")}' uses an HTML tag instead of markdown syntax.`, + ); + } + }); return imagesErrors; } diff --git a/scripts/js/lib/markdownTitles.test.ts b/scripts/js/lib/markdownTitles.test.ts index 53ebbac4d3b..c1f6b8d2b96 100644 --- a/scripts/js/lib/markdownTitles.test.ts +++ b/scripts/js/lib/markdownTitles.test.ts @@ -1,6 +1,6 @@ // This code is a Qiskit project. // -// (C) Copyright IBM 2023. +// (C) Copyright IBM 2025. // // This code is licensed under the Apache License, Version 2.0. You may // obtain a copy of this license in the LICENSE file in the root directory @@ -11,8 +11,20 @@ // that they have been altered from the originals. import { expect, test } from "@playwright/test"; - +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkGfm from "remark-gfm"; +import remarkFrontmatter from "remark-frontmatter"; import { collectHeadingTitleMismatch } from "./markdownTitles"; +import { Root } from "mdast"; + +function parseMarkdown(markdown: string): Root { + return unified() + .use(remarkParse) + .use(remarkGfm) + .use(remarkFrontmatter, ["yaml"]) + .parse(markdown); +} test("Test for matching titles and headings", async () => { const markdown1 = `--- @@ -20,10 +32,10 @@ title: My Awesome Guide --- # My Awesome Guide - `; - const mismatched = await collectHeadingTitleMismatch(markdown1); - const result: Set = new Set(); - expect(mismatched).toEqual(result); + `; + const tree = parseMarkdown(markdown1); + const mismatched = await collectHeadingTitleMismatch(tree); + expect(mismatched).toEqual(new Set()); }); test("Test to find mismatched titles and headings", async () => { @@ -36,9 +48,10 @@ author: John This guide will walk you through everything.`; - const mismatched2 = await collectHeadingTitleMismatch(markdown2); + const tree = parseMarkdown(markdown2); + const mismatched2 = await collectHeadingTitleMismatch(tree); - const result2: Set = new Set([ + const result2 = new Set([ `Mismatch: frontmatter title "Qiskit Doc" does not match heading "Introduction"`, ]); @@ -46,7 +59,7 @@ This guide will walk you through everything.`; }); test("Test to mismatched and complex titles and headings", async () => { - const markdown2 = `--- + const markdown3 = `--- title: My Awesome Guide --- @@ -54,11 +67,12 @@ title: My Awesome Guide This guide will walk you through everything.`; - const mismatched2 = await collectHeadingTitleMismatch(markdown2); + const tree = parseMarkdown(markdown3); + const mismatched3 = await collectHeadingTitleMismatch(tree); - const result2: Set = new Set([ + const result3 = new Set([ `Mismatch: frontmatter title "My Awesome Guide" does not match heading "This is a Heading"`, ]); - expect(mismatched2).toEqual(result2); + expect(mismatched3).toEqual(result3); }); diff --git a/scripts/js/lib/markdownTitles.ts b/scripts/js/lib/markdownTitles.ts index 53028a79b82..cb9776c1e4b 100644 --- a/scripts/js/lib/markdownTitles.ts +++ b/scripts/js/lib/markdownTitles.ts @@ -1,7 +1,15 @@ -import { unified } from "unified"; -import remarkParse from "remark-parse"; -import remarkGfm from "remark-gfm"; -import remarkFrontmatter from "remark-frontmatter"; +// This code is a Qiskit project. +// +// (C) Copyright IBM 2025. +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + import { visit, EXIT } from "unist-util-visit"; import { Root } from "mdast"; import yaml from "js-yaml"; @@ -24,20 +32,13 @@ function extractText(node: any): string { } export async function collectHeadingTitleMismatch( - markdown: string, + tree: Root, ): Promise> { const mismatches = new Set(); let frontmatterTitle: string | undefined; let headingText: string | undefined; - const processor = unified() - .use(remarkParse) - .use(remarkGfm) - .use(remarkFrontmatter, ["yaml"]); - - const tree = processor.parse(markdown); - // Extract frontmatter title visit(tree, "yaml", (node: any) => { const data = yaml.load(node.value); From ee770328563ad332b522975ff4f8eed875beed24 Mon Sep 17 00:00:00 2001 From: GantaRoja Date: Tue, 12 Aug 2025 11:50:33 +0000 Subject: [PATCH 11/11] moved helper function to a new file --- scripts/js/commands/checkMarkdown.ts | 16 +++------------- scripts/js/lib/markdownTitles.test.ts | 15 ++------------- scripts/js/lib/markdownUtils.ts | 25 +++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 26 deletions(-) create mode 100644 scripts/js/lib/markdownUtils.ts diff --git a/scripts/js/commands/checkMarkdown.ts b/scripts/js/commands/checkMarkdown.ts index 7a112642bc6..24c0f3af3d5 100644 --- a/scripts/js/commands/checkMarkdown.ts +++ b/scripts/js/commands/checkMarkdown.ts @@ -13,14 +13,11 @@ import { globby } from "globby"; import yargs from "yargs/yargs"; import { hideBin } from "yargs/helpers"; -import { unified } from "unified"; -import remarkParse from "remark-parse"; -import remarkGfm from "remark-gfm"; -import remarkFrontmatter from "remark-frontmatter"; import { collectInvalidImageErrors } from "../lib/markdownImages.js"; import { readMarkdown } from "../lib/markdownReader.js"; import { collectHeadingTitleMismatch } from "../lib/markdownTitles.js"; +import { parseMarkdown } from "../lib/markdownUtils"; const IGNORE_TITLE_MISMATCHES: string[] = [ "docs/migration-guides/external-providers-primitives-v2.mdx", @@ -83,7 +80,7 @@ const readArgs = (): Arguments => { type: "boolean", default: false, description: - "Check the images in the current and dev versions of the API docs have alt text.", + "Check files in the current and dev versions of the API docs have matching titles and metadata.", }) .parseSync(); }; @@ -94,13 +91,7 @@ async function main() { for (const file of files) { const markdown = await readMarkdown(file); - const processor = unified() - .use(remarkParse) - .use(remarkGfm) - .use(remarkFrontmatter, ["yaml"]); - - const tree = processor.parse(markdown); - + const tree = parseMarkdown(markdown); const imageErrors = await collectInvalidImageErrors(tree); const mismatchedTitleHeadingErrors = IGNORE_TITLE_MISMATCHES.includes(file) || file.startsWith("docs/api") @@ -115,7 +106,6 @@ async function main() { if (errorsInFile.length) { allErrors.push( - // `Error in file '${file}':\n\t- ${[...imageErrors].join("\n\t- ")}\n`, `Error in file '${file}':\n\t- ${errorsInFile.join("\n\t- ")}\n`, ); } diff --git a/scripts/js/lib/markdownTitles.test.ts b/scripts/js/lib/markdownTitles.test.ts index c1f6b8d2b96..ba983d71167 100644 --- a/scripts/js/lib/markdownTitles.test.ts +++ b/scripts/js/lib/markdownTitles.test.ts @@ -11,20 +11,9 @@ // that they have been altered from the originals. import { expect, test } from "@playwright/test"; -import { unified } from "unified"; -import remarkParse from "remark-parse"; -import remarkGfm from "remark-gfm"; -import remarkFrontmatter from "remark-frontmatter"; + import { collectHeadingTitleMismatch } from "./markdownTitles"; -import { Root } from "mdast"; - -function parseMarkdown(markdown: string): Root { - return unified() - .use(remarkParse) - .use(remarkGfm) - .use(remarkFrontmatter, ["yaml"]) - .parse(markdown); -} +import { parseMarkdown } from "./markdownUtils"; test("Test for matching titles and headings", async () => { const markdown1 = `--- diff --git a/scripts/js/lib/markdownUtils.ts b/scripts/js/lib/markdownUtils.ts new file mode 100644 index 00000000000..bd0c975e449 --- /dev/null +++ b/scripts/js/lib/markdownUtils.ts @@ -0,0 +1,25 @@ +// This code is a Qiskit project. +// +// (C) Copyright IBM 2025. +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkGfm from "remark-gfm"; +import remarkFrontmatter from "remark-frontmatter"; +import { Root } from "mdast"; + +export function parseMarkdown(markdown: string): Root { + return unified() + .use(remarkParse) + .use(remarkGfm) + .use(remarkFrontmatter, ["yaml"]) + .parse(markdown); +}