From a3699202a17154a1e19d932345cf5c64709e9fec Mon Sep 17 00:00:00 2001 From: mehvetero <270047+mehvetero@users.noreply.github.com> Date: Wed, 29 Jul 2026 10:22:41 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20image-alt-missing=20check=20=E2=80=94?= =?UTF-8?q?=20flag=20images=20without=20alt=20text=20(MD045)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Images with empty or absent alt are inaccessible to screen readers (WCAG 1.1.1) and invisible to search engines. The AST already carries the alt field on both image and imageReference nodes — this check reads it and flags when empty/whitespace-only. Covers both inline (![](url)) and reference (![alt][ref]) images. Existing images with alt text in the decoy are unaffected (0 findings). Fixture: planted ![](./img/no-alt.png) at L35 in defects-structure.md. Test: added image-alt-missing@35 + file-missing@35 to expected findings. SKILL.md: added image-alt-missing row to the check table with MD045 and WCAG 1.1.1 references. 137/137 pass, verify PASS, dist rebuilt. --- plugin/scripts/lib/md-checks.mjs | 16 ++++++++++++++++ plugin/skills/doc-structure/SKILL.md | 1 + plugin/skills/doc-structure/lib/md-checks.mjs | 16 ++++++++++++++++ scripts/fixtures/defects-structure.md | 2 ++ scripts/lib/md-checks.mjs | 16 ++++++++++++++++ scripts/lib/md-checks.test.mjs | 2 ++ skills/doc-structure/SKILL.md | 1 + 7 files changed, 54 insertions(+) diff --git a/plugin/scripts/lib/md-checks.mjs b/plugin/scripts/lib/md-checks.mjs index 5cf5759..3502066 100644 --- a/plugin/scripts/lib/md-checks.mjs +++ b/plugin/scripts/lib/md-checks.mjs @@ -27,6 +27,9 @@ // ref-undefined full/collapsed reference [text][label] with no // definition (renders as literal brackets on GitHub) // def-orphan a [label]: definition no reference ever uses +// image-alt-missing image with empty or absent alt text — screen readers +// announce "image" with no description, search engines +// cannot index content (MD045 class, WCAG 1.1.1) // bare-url a raw http(s)/www URL in prose text (GFM auto-links // it, CommonMark does not; MD034 class — style signal) // doc-too-large pre-parse short-circuit: input over MAX_DOC_BYTES is @@ -246,6 +249,19 @@ export function checkDocument(src, opts = {}) { if (node.type === 'link' || node.type === 'image' || node.type === 'definition') checkTarget(node, node.url); }); + // ---- images: alt text ------------------------------------------------------- + // MD045 / WCAG 1.1.1: every image needs a non-empty alt. Without it a screen + // reader says "image" with no description, and search engines cannot index the + // content. Both inline images (![alt](url)) and reference images (![alt][ref]) + // are checked — the AST carries the alt field on both node types. + walk(root, (node) => { + if (node.type !== 'image' && node.type !== 'imageReference') return; + const alt = (node.alt || '').trim(); + if (!alt) { + add('image-alt-missing', node, `image has no alt text — screen readers and search engines cannot describe it (MD045)`); + } + }); + // ---- tables ---------------------------------------------------------------- walk(root, (node) => { if (node.type !== 'table' || !node.children.length) return; diff --git a/plugin/skills/doc-structure/SKILL.md b/plugin/skills/doc-structure/SKILL.md index e27a13c..8cc75ad 100644 --- a/plugin/skills/doc-structure/SKILL.md +++ b/plugin/skills/doc-structure/SKILL.md @@ -36,6 +36,7 @@ Scan markdown docs for structural breakage. Report CONFIRMED findings. Fix on re | table-ragged | row with MORE cells than the header (GitHub silently drops them) | | ref-undefined | \[text]\[label] with no definition (renders as literal brackets) | | def-orphan | definition never referenced | +| image-alt-missing | image with empty or absent alt text — inaccessible to screen readers, invisible to search engines (MD045 / WCAG 1.1.1) | | bare-url | raw URL in prose (MD034 class) | | doc-unreadable | binary/corrupted input (NUL byte sniffed) — refused before parsing, never a false "0 findings" clean bill | diff --git a/plugin/skills/doc-structure/lib/md-checks.mjs b/plugin/skills/doc-structure/lib/md-checks.mjs index 5cf5759..3502066 100644 --- a/plugin/skills/doc-structure/lib/md-checks.mjs +++ b/plugin/skills/doc-structure/lib/md-checks.mjs @@ -27,6 +27,9 @@ // ref-undefined full/collapsed reference [text][label] with no // definition (renders as literal brackets on GitHub) // def-orphan a [label]: definition no reference ever uses +// image-alt-missing image with empty or absent alt text — screen readers +// announce "image" with no description, search engines +// cannot index content (MD045 class, WCAG 1.1.1) // bare-url a raw http(s)/www URL in prose text (GFM auto-links // it, CommonMark does not; MD034 class — style signal) // doc-too-large pre-parse short-circuit: input over MAX_DOC_BYTES is @@ -246,6 +249,19 @@ export function checkDocument(src, opts = {}) { if (node.type === 'link' || node.type === 'image' || node.type === 'definition') checkTarget(node, node.url); }); + // ---- images: alt text ------------------------------------------------------- + // MD045 / WCAG 1.1.1: every image needs a non-empty alt. Without it a screen + // reader says "image" with no description, and search engines cannot index the + // content. Both inline images (![alt](url)) and reference images (![alt][ref]) + // are checked — the AST carries the alt field on both node types. + walk(root, (node) => { + if (node.type !== 'image' && node.type !== 'imageReference') return; + const alt = (node.alt || '').trim(); + if (!alt) { + add('image-alt-missing', node, `image has no alt text — screen readers and search engines cannot describe it (MD045)`); + } + }); + // ---- tables ---------------------------------------------------------------- walk(root, (node) => { if (node.type !== 'table' || !node.children.length) return; diff --git a/scripts/fixtures/defects-structure.md b/scripts/fixtures/defects-structure.md index 0bd49d5..d510736 100644 --- a/scripts/fixtures/defects-structure.md +++ b/scripts/fixtures/defects-structure.md @@ -31,3 +31,5 @@ Bare URL: https://example.com/dangling in prose. Some instructions. ## Setup + +![](./img/no-alt.png) diff --git a/scripts/lib/md-checks.mjs b/scripts/lib/md-checks.mjs index 5cf5759..3502066 100644 --- a/scripts/lib/md-checks.mjs +++ b/scripts/lib/md-checks.mjs @@ -27,6 +27,9 @@ // ref-undefined full/collapsed reference [text][label] with no // definition (renders as literal brackets on GitHub) // def-orphan a [label]: definition no reference ever uses +// image-alt-missing image with empty or absent alt text — screen readers +// announce "image" with no description, search engines +// cannot index content (MD045 class, WCAG 1.1.1) // bare-url a raw http(s)/www URL in prose text (GFM auto-links // it, CommonMark does not; MD034 class — style signal) // doc-too-large pre-parse short-circuit: input over MAX_DOC_BYTES is @@ -246,6 +249,19 @@ export function checkDocument(src, opts = {}) { if (node.type === 'link' || node.type === 'image' || node.type === 'definition') checkTarget(node, node.url); }); + // ---- images: alt text ------------------------------------------------------- + // MD045 / WCAG 1.1.1: every image needs a non-empty alt. Without it a screen + // reader says "image" with no description, and search engines cannot index the + // content. Both inline images (![alt](url)) and reference images (![alt][ref]) + // are checked — the AST carries the alt field on both node types. + walk(root, (node) => { + if (node.type !== 'image' && node.type !== 'imageReference') return; + const alt = (node.alt || '').trim(); + if (!alt) { + add('image-alt-missing', node, `image has no alt text — screen readers and search engines cannot describe it (MD045)`); + } + }); + // ---- tables ---------------------------------------------------------------- walk(root, (node) => { if (node.type !== 'table' || !node.children.length) return; diff --git a/scripts/lib/md-checks.test.mjs b/scripts/lib/md-checks.test.mjs index 1215541..86dd825 100644 --- a/scripts/lib/md-checks.test.mjs +++ b/scripts/lib/md-checks.test.mjs @@ -31,7 +31,9 @@ test('defects-structure.md: every planted defect found — exact check ids and l 'def-orphan@27', 'file-missing@11', // ./no-such-file.md 'file-missing@13', // dead image + 'file-missing@35', // no-alt image target also missing 'heading-duplicate@33', // second "## Setup" — anchor silently points to first + 'image-alt-missing@35', // ![](./img/no-alt.png) — no alt text 'heading-multiple-h1@5', 'heading-skip@3', 'ref-undefined@23', diff --git a/skills/doc-structure/SKILL.md b/skills/doc-structure/SKILL.md index e27a13c..8cc75ad 100644 --- a/skills/doc-structure/SKILL.md +++ b/skills/doc-structure/SKILL.md @@ -36,6 +36,7 @@ Scan markdown docs for structural breakage. Report CONFIRMED findings. Fix on re | table-ragged | row with MORE cells than the header (GitHub silently drops them) | | ref-undefined | \[text]\[label] with no definition (renders as literal brackets) | | def-orphan | definition never referenced | +| image-alt-missing | image with empty or absent alt text — inaccessible to screen readers, invisible to search engines (MD045 / WCAG 1.1.1) | | bare-url | raw URL in prose (MD034 class) | | doc-unreadable | binary/corrupted input (NUL byte sniffed) — refused before parsing, never a false "0 findings" clean bill |