Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

⚠️ Add warnings for missing alt-text and auto-generated alt-text #1814

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions .changeset/big-cooks-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"myst-transforms": patch
"myst-common": patch
---

Add warnings for missing alt-text and auto-generated alt-text.
2 changes: 2 additions & 0 deletions packages/myst-common/src/ruleids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export enum RuleId {
imageFormatConverts = 'image-format-converts',
imageCopied = 'image-copied',
imageFormatOptimizes = 'image-format-optimizes',
imageHasAltText = 'image-has-alt-text',
imageAltTextGenerated = 'image-alt-text-generated',
// Math rules
mathLabelLifted = 'math-label-lifted',
mathEquationEnvRemoved = 'math-equation-env-removed',
Expand Down
3 changes: 2 additions & 1 deletion packages/myst-transforms/src/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { captionParagraphTransform } from './caption.js';
import { admonitionBlockquoteTransform, admonitionHeadersTransform } from './admonitions.js';
import { blockMetadataTransform, blockNestingTransform, blockToFigureTransform } from './blocks.js';
import { htmlIdsTransform } from './htmlIds.js';
import { imageAltTextTransform } from './images.js';
import { imageAltTextTransform, imageNoAltTextTransform } from './images.js';
import { mathLabelTransform, mathNestingTransform, subequationTransform } from './math.js';
import { blockquoteTransform } from './blockquote.js';
import { codeBlockToDirectiveTransform, inlineCodeFlattenTransform } from './code.js';
Expand Down Expand Up @@ -40,6 +40,7 @@ export function basicTransformations(tree: GenericParent, file: VFile, opts?: Re
containerChildrenTransform(tree, file);
htmlIdsTransform(tree);
imageAltTextTransform(tree);
imageNoAltTextTransform(tree, file);
blockquoteTransform(tree);
removeUnicodeTransform(tree);
headingDepthTransform(tree, file, opts);
Expand Down
163 changes: 163 additions & 0 deletions packages/myst-transforms/src/images.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { describe, expect, test } from 'vitest';
import { VFile } from 'vfile';

import { imageNoAltTextTransform } from './images.js';

describe('Test imageNoAltTextTransform', () => {
test('image without alt text generates warning', () => {
const mdast = {
type: 'root',
children: [
{
type: 'image',
url: 'https://images.com/cats',
align: 'center',
},
],
};
const file = new VFile();
imageNoAltTextTransform(mdast, file);
expect(mdast).toEqual({
type: 'root',
children: [
{
type: 'image',
url: 'https://images.com/cats',
align: 'center',
},
],
});
// A warning was created
expect(file.messages.length).toBe(1);
expect(file.messages[0].message.includes('missing alt text')).toBe(true);
});
test('image without alt text does not generate a warning', () => {
const mdast = {
type: 'root',
children: [
{
type: 'image',
url: 'https://images.com/cats',
alt: 'I have alt text',
align: 'center',
},
],
};
const file = new VFile();
imageNoAltTextTransform(mdast, file);
expect(mdast).toEqual({
type: 'root',
children: [
{
type: 'image',
url: 'https://images.com/cats',
alt: 'I have alt text',
align: 'center',
},
],
});
// A warning was created
expect(file.messages.length).toBe(0);
});
test('image inside captioned figure warns about generated alt text', () => {
const mdast = {
type: 'container',
kind: 'figure',
children: [
{
type: 'image',
url: 'https://images.com/cats',
alt: 'I don’t have alt text, but I do have a caption',
data: {
altTextIsAutoGenerated: true,
},
},
{
type: 'caption',
children: [
{
type: 'paragraph',
children: [
{
type: 'text',
value: 'I don’t have alt text, but I do have a caption',
},
],
},
],
},
],
enumerator: '1',
};
const file = new VFile();
imageNoAltTextTransform(mdast, file);
expect(mdast).toEqual({
type: 'container',
kind: 'figure',
children: [
{
type: 'image',
url: 'https://images.com/cats',
alt: 'I don’t have alt text, but I do have a caption',
data: {
altTextIsAutoGenerated: true,
},
},
{
type: 'caption',
children: [
{
type: 'paragraph',
children: [
{
type: 'text',
value: 'I don’t have alt text, but I do have a caption',
},
],
},
],
},
],
enumerator: '1',
});
// A warning was created
expect(file.messages.length).toBe(1);
expect(file.messages[0].message.includes('was auto-generated')).toBe(true);
});
test('image inside output does not generate warning', () => {
const mdast = {
type: 'root',
children: [
{
type: 'output',
children: [
{
type: 'image',
url: 'https://images.com/cats',
align: 'center',
},
],
},
],
};
const file = new VFile();
imageNoAltTextTransform(mdast, file);
expect(mdast).toEqual({
type: 'root',
children: [
{
type: 'output',
children: [
{
type: 'image',
url: 'https://images.com/cats',
align: 'center',
},
],
},
],
});
// A warning was created
expect(file.messages.length).toBe(0);
});
});
38 changes: 36 additions & 2 deletions packages/myst-transforms/src/images.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import type { Plugin } from 'unified';
import type { Container, Paragraph, PhrasingContent, Image } from 'myst-spec';
import type { VFile } from 'vfile';
import { select, selectAll } from 'unist-util-select';
import { visit, SKIP } from 'unist-util-visit';
import type { GenericParent } from 'myst-common';
import { toText } from 'myst-common';
import { fileWarn, toText, RuleId } from 'myst-common';

const TRANSFORM_SOURCE = 'myst-transforms:images';

/**
* Generate image alt text from figure caption
Expand All @@ -29,6 +33,36 @@ export function imageAltTextTransform(tree: GenericParent) {
});
}

export const imageAltTextPlugin: Plugin<[], GenericParent, GenericParent> = () => (tree) => {
export function imageNoAltTextTransform(tree: GenericParent, file: VFile) {
visit(tree, ['output', 'image'], (node) => {
switch (node.type) {
// Do not recurse into outputs, as they rarely have alt-texts and are usually embedded
// into a figure that does
case 'output': {
return SKIP;
}
case 'image': {
if (node.alt == null) {
fileWarn(file, `missing alt text for ${node.url}`, {
ruleId: RuleId.imageHasAltText,
node: node,
source: TRANSFORM_SOURCE,
});
}
if (node.data?.altTextIsAutoGenerated) {
fileWarn(file, `alt text for ${node.url} was auto-generated`, {
ruleId: RuleId.imageAltTextGenerated,
node: node,
source: TRANSFORM_SOURCE,
note: 'You can remove this warning by writing your own alt text',
});
}
}
}
});
}

export const imageAltTextPlugin: Plugin<[], GenericParent, GenericParent> = () => (tree, file) => {
imageAltTextTransform(tree);
imageNoAltTextTransform(tree, file);
};
Loading