diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ed9fc9b2c45..e47d7bb617f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,6 +28,8 @@ jobs: - name: File metadata run: npm run check:metadata + - name: Check image alt text + run: npm run check:alt-text - name: Spellcheck run: npm run check:spelling - name: Check Qiskit bot config diff --git a/README.md b/README.md index a1b5a5dfed6..3d616bf52ee 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,20 @@ npm run check:metadata -- --apis npm run check ``` +## Check image alt text + +Every image needs to have alt text for accessibility. The `lint` job in CI will fail if images do not have alt text defined. + +You can check it locally by running: + +```bash +# Only check alt text +npm run check:alt-text + +# Or, run all the checks +npm run check +``` + ## Spellcheck We use [cSpell](https://cspell.org) to check for spelling. The `lint` job in CI will fail if there are spelling issues. diff --git a/docs/guides/custom-transpiler-pass.ipynb b/docs/guides/custom-transpiler-pass.ipynb index abfa3536099..ed4fb3fb5ee 100644 --- a/docs/guides/custom-transpiler-pass.ipynb +++ b/docs/guides/custom-transpiler-pass.ipynb @@ -72,7 +72,7 @@ " qc.draw(output='mpl')\n", "\n", "```\n", - "![The circuit's DAG consists of nodes that are connected by directional edges. It is a visual way to represent qubits or classical bits, the operations, and the way that data flows. ](/images/guides/custom-transpiler-pass/DAG_circ.png \"DAG\")\n", + "![Circuit preparing a Bell state and applying an $R_Z$ rotation depending on the measurement outcome.](/images/guides/custom-transpiler-pass/DAG_circ.png \"Circuit\")\n", "\n", "Use the `qiskit.tools.visualization.dag_drawer()` function to view this circuit's DAG. There are three kinds of graph nodes: qubit/clbit nodes (green), operation nodes (blue), and output nodes (red). Each edge indicates data flow (or dependency) between two nodes.\n", "\n", @@ -83,7 +83,7 @@ "dag = circuit_to_dag(qc)\n", "dag_drawer(dag)\n", "```\n", - "![](/images/guides/custom-transpiler-pass/DAG.png)\n", + "![The circuit's DAG consists of nodes that are connected by directional edges. It is a visual way to represent qubits or classical bits, the operations, and the way that data flows.](/images/guides/custom-transpiler-pass/DAG.png \"DAG\")\n", "" ] }, diff --git a/package.json b/package.json index f67d649c9f7..a4dec020db7 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "fmt": "prettier --write .", "test": "playwright test", "typecheck": "tsc", - "check": "npm run check:qiskit-bot && npm run check:patterns-index && npm run check:metadata && npm run check:spelling && npm run check:internal-links && npm run check:orphan-pages && npm run check:fmt", + "check": "npm run check:qiskit-bot && npm run check:patterns-index && npm run check:alt-text && npm run check:metadata && npm run check:spelling && npm run check:internal-links && npm run check:orphan-pages && npm run check:fmt", + "check:alt-text": "tsx scripts/js/commands/checkAltText.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/checkAltText.ts b/scripts/js/commands/checkAltText.ts new file mode 100644 index 00000000000..c1ddff96650 --- /dev/null +++ b/scripts/js/commands/checkAltText.ts @@ -0,0 +1,43 @@ +// This code is a Qiskit project. +// +// (C) Copyright IBM 2024. +// +// 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 { globby } from "globby"; +import { findImagesWithoutAltText } from "../lib/extractMarkdownImages.js"; +import { readMarkdown } from "../lib/markdownReader.js"; + +async function main() { + const files = await globby(["docs/**/*.{ipynb,mdx}", "!docs/api/**/*.mdx"]); + const fileErrors: string[] = []; + + for (const file of files) { + const markdown = await readMarkdown(file); + const images = await findImagesWithoutAltText(markdown); + const imageErrors = [...images].map( + (image) => `The image '${image}' does not have an alt text defined.`, + ); + + if (imageErrors.length) { + fileErrors.push( + `Error in file '${file}':\n\t- ${imageErrors.join("\n\t- ")}`, + ); + } + } + + if (fileErrors.length) { + fileErrors.forEach((error) => console.log(error)); + console.error("\nSome images are missing alt text 💔\n"); + process.exit(1); + } + console.log("\nAll images have alt text ✅\n"); +} + +main().then(() => process.exit()); diff --git a/scripts/js/lib/extractMarkdownImages.test.ts b/scripts/js/lib/extractMarkdownImages.test.ts new file mode 100644 index 00000000000..86c094ce044 --- /dev/null +++ b/scripts/js/lib/extractMarkdownImages.test.ts @@ -0,0 +1,40 @@ +// 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 { findImagesWithoutAltText } from "./extractMarkdownImages.js"; + +test("Test the extraction of the images", async () => { + const markdown = ` +# A header + +![Our first image with alt text](/images/img1.png) + +![](/images/invalid_img1.png) + +![Here's another valid image](/images/img2.png) + +![](/images/invalid_img2.png) + +![](/images/invalid_img2.png) + +![And now, our last link](https://ibm.com) + `; + const images = await findImagesWithoutAltText(markdown); + const correct_images = new Set([ + "/images/invalid_img1.png", + "/images/invalid_img2.png", + ]); + + expect(images).toEqual(correct_images); +}); diff --git a/scripts/js/lib/extractMarkdownImages.ts b/scripts/js/lib/extractMarkdownImages.ts new file mode 100644 index 00000000000..766714823b0 --- /dev/null +++ b/scripts/js/lib/extractMarkdownImages.ts @@ -0,0 +1,39 @@ +// This code is a Qiskit project. +// +// (C) Copyright IBM 2024. +// +// 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 { Root } from "remark-mdx"; +import { visit } from "unist-util-visit"; +import remarkParse from "remark-parse"; +import remarkGfm from "remark-gfm"; +import remarkStringify from "remark-stringify"; + +export async function findImagesWithoutAltText( + markdown: string, +): Promise> { + const images = new Set(); + + await unified() + .use(remarkParse) + .use(remarkGfm) + .use(() => (tree: Root) => { + visit(tree, "image", (node) => { + if (!node.alt) { + images.add(node.url); + } + }); + }) + .use(remarkStringify) + .process(markdown); + + return images; +}