Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Loads the plugin's per-repo config file and extracts per-rule enable/disable
// overrides. Missing, unreadable, or malformed config falls back to defaults.

import {readFile} from 'node:fs/promises'

export type PluginConfig = {
Expand Down Expand Up @@ -29,6 +32,8 @@ export async function loadConfig(configPath: string, knownRuleIds: ReadonlySet<s
return {ruleOverrides: collectRuleOverrides(parsed, knownRuleIds)}
}

// Pulls the boolean `rules` map out of parsed config, dropping unknown ids and
// non-boolean values so a bad config can't disable an unrelated rule.
function collectRuleOverrides(parsed: unknown, knownRuleIds: ReadonlySet<string>): Map<string, boolean> {
const result = new Map<string, boolean>()
if (!parsed || typeof parsed !== 'object') return result
Expand Down
2 changes: 2 additions & 0 deletions src/rules/filename-alt-text.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// filename-alt-text — flags alt text that is just an image filename (e.g. "hero.png").

import type {Rule, RuleResult, RuleContext} from '../types.js'

const FILENAME_PATTERN = /\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/i
Expand Down
3 changes: 3 additions & 0 deletions src/rules/missing-alt-text.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// missing-alt-text — flags images whose alt attribute is missing or whitespace-only.
// alt="" is treated as an intentional decorative marker and left alone.

import type {Rule, RuleResult, RuleContext} from '../types.js'

export const missingAltText: Rule = {
Expand Down
27 changes: 25 additions & 2 deletions src/rules/placeholder-alt-text.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
// placeholder-alt-text — flags alt text left as placeholder/boilerplate (e.g. "TODO", "alt text").

import type {Rule, RuleResult, RuleContext} from '../types.js'
import {normalizeAltText} from '../utils/normalize-alt-text.js'

// Known placeholder/boilerplate strings that signal the alt text was never written.
const PLACEHOLDER_ALT_TEXT = new Set([
// Author-facing stubs
'todo',
'tbd',
'fixme',
'placeholder',
'alt text',
'insert alt text',
'image alt',
'fixme',

// "Fill me in" template prompts
'insert alt text',
'insert image',
'insert photo',
'image goes here',
'photo goes here',
'picture goes here',
'your image here',
'your photo here',

// Dummy / default values
'sample',
'example',
'test',
'demo',
'default',
'untitled',
'null',
'undefined',
'none',
])

export const placeholderAltText: Rule = {
Expand Down
3 changes: 3 additions & 0 deletions src/rules/repeated-alt-text.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// repeated-alt-text — flags runs of adjacent images that share identical alt text,
// which usually means one caption was copied across a group of distinct images.

import type {ImageRecord, Rule, RuleResult} from '../types.js'
import {normalizeAltText} from '../utils/normalize-alt-text.js'

Expand Down
22 changes: 3 additions & 19 deletions src/rules/vague-alt-text.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// vague-alt-text — flags alt text that is a single generic word or phrase
// (e.g. "image", "logo", "photo of") carrying no real description of the image.

import type {Rule, RuleResult} from '../types.js'
import {normalizeAltText} from '../utils/normalize-alt-text.js'

Expand Down Expand Up @@ -58,18 +61,6 @@ const VAGUE_WORDS = new Set([
'map',
'maps',

// Placeholders
// Note: todo, tbd, fixme, and placeholder live in placeholder-alt-text.ts.
'sample',
'example',
'test',
'demo',
'default',
'untitled',
'null',
'undefined',
'none',

// File format / extension names
'jpg',
'jpeg',
Expand Down Expand Up @@ -111,13 +102,6 @@ const VAGUE_PHRASES = new Set([
'picture of',
'graphic of',
'screenshot of',
'image goes here',
'photo goes here',
'picture goes here',
'your image here',
'your photo here',
'insert image',
'insert photo',
])

export const vagueAltText: Rule = {
Expand Down
8 changes: 3 additions & 5 deletions src/utils/normalize-alt-text.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
/**
* Normalizes alt text for comparison by trimming, lowercasing, collapsing internal
* whitespace, and stripping leading and trailing punctuation. Used by rules that
* need to compare alt text against a set.
*/
// Normalizes alt text for comparison by trimming, lowercasing, collapsing internal
// whitespace, and stripping leading and trailing punctuation. Used by rules that
// compare alt text against a fixed set of words or phrases.
export function normalizeAltText(alt: string): string {
return alt
.trim()
Expand Down
26 changes: 25 additions & 1 deletion tests/unit/placeholder-alt-text.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,31 @@ import {describe, it, expect} from 'vitest'
import {placeholderAltText} from '../../src/rules/placeholder-alt-text.js'
import {evaluateAlts} from '../utils/helpers.js'

const placeholders = ['todo', 'tbd', 'placeholder', 'alt text', 'insert alt text', 'image alt', 'fixme']
const placeholders = [
'todo',
'tbd',
'fixme',
'placeholder',
'alt text',
'image alt',
'insert alt text',
'insert image',
'insert photo',
'image goes here',
'photo goes here',
'picture goes here',
'your image here',
'your photo here',
'sample',
'example',
'test',
'demo',
'default',
'untitled',
'null',
'undefined',
'none',
]

describe('placeholder-alt-text', () => {
for (const placeholder of placeholders) {
Expand Down
20 changes: 11 additions & 9 deletions tests/unit/vague-alt-text.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@ import {evaluateAlts, makeImage} from '../utils/helpers.js'

describe('vagueAltText', () => {
describe('flags vague single-word alt text', () => {
it.each(['image', 'photo', 'picture', 'icon', 'logo', 'chart', 'screenshot', 'untitled', 'below'])(
'flags alt="%s"',
alt => {
const results = evaluateAlts([alt], vagueAltText)
expect(results).toHaveLength(1)
expect(results[0]).toBeDefined()
expect(results[0]!.image.alt).toBe(alt)
},
)
it.each(['image', 'photo', 'picture', 'icon', 'logo', 'chart', 'screenshot', 'below'])('flags alt="%s"', alt => {
const results = evaluateAlts([alt], vagueAltText)
expect(results).toHaveLength(1)
expect(results[0]).toBeDefined()
expect(results[0]!.image.alt).toBe(alt)
})
})

describe('flags vague multi-word phrases', () => {
Expand Down Expand Up @@ -77,6 +74,11 @@ describe('vagueAltText', () => {
expect(evaluateAlts(['tbd'], vagueAltText)).toHaveLength(0)
expect(evaluateAlts(['fixme'], vagueAltText)).toHaveLength(0)
expect(evaluateAlts(['placeholder'], vagueAltText)).toHaveLength(0)
expect(evaluateAlts(['untitled'], vagueAltText)).toHaveLength(0)
expect(evaluateAlts(['sample'], vagueAltText)).toHaveLength(0)
expect(evaluateAlts(['test'], vagueAltText)).toHaveLength(0)
expect(evaluateAlts(['insert image'], vagueAltText)).toHaveLength(0)
expect(evaluateAlts(['image goes here'], vagueAltText)).toHaveLength(0)
})
})

Expand Down