Skip to content

Commit

Permalink
Add proper tag validation
Browse files Browse the repository at this point in the history
  • Loading branch information
WilcoFiers committed Jun 26, 2023
1 parent c5e6523 commit d6febf9
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 14 deletions.
171 changes: 165 additions & 6 deletions build/tasks/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,6 @@ function createSchemas() {
type: 'array',
items: {
type: 'string'
},
conform: function hasCategoryTag(tags) {
return tags.some(tag => tag.includes('cat.'));
},
messages: {
conform: 'must include a category tag'
}
},
actIds: {
Expand Down Expand Up @@ -307,5 +301,170 @@ function validateRule({ tags, metadata }) {
if (help.toLowerCase().includes(prohibitedWord)) {
issues.push(`metadata.help can not contain the word '${prohibitedWord}'.`);
}

issues.push(...findTagIssues(tags));
return issues;
}

const uniqueTags = ['ACT', 'experimental', 'review-item', 'deprecated'];

const categories = [
'aria',
'color',
'forms',
'keyboard',
'language',
'name-role-value',
'parsing',
'semantics',
'sensory-and-visual-cues',
'structure',
'tables',
'text-alternatives',
'time-and-media'
];

const standardsTags = [
{
name: 'WCAG',
standardRegex: /^wcag2(1|2)?a{1,3}$/,
criterionRegex: /^wcag\d{3,4}$/
},
{
name: 'Section 508',
standardRegex: /^section508$/,
criterionRegex: /^section508\.\d{1,2}\.[a-z]$/,
wcagLevelRegex: /wcag2aa?/
},
{
name: 'Trusted Tester',
standardRegex: /^TTv5$/,
criterionRegex: /^TT\d{1,3}\.[a-z]$/,
wcagLevelRegex: /wcag2aa?/
},
{
name: 'EN 301 549',
standardRegex: /^EN.301.549$/,
criterionRegex: /^EN-9\.[1-4]\.[1-9]\.\d{1,2}$/,
wcagLevelRegex: /wcag21?aa?/
}
];

function findTagIssues(tags) {
const issues = [];
const catTags = tags.filter(tag => tag.startsWith('cat.'));
const bestPracticeTags = tags.filter(tag => tag === 'best-practice');
const miscTags = tags.filter(tag => uniqueTags.includes(tag));

// Category
if (catTags.length !== 1) {
issues.push(`Must have exactly one cat. tag, got ${catTags.length}`);
}
if (catTags.length && !categories.includes(catTags[0].slice(4))) {
issues.push(`Invalid category tag: ${catTags[0]}`);
}
if (!startsWith(tags, catTags)) {
issues.push(`Tag ${catTags[0]} must be before ${tags[0]}`);
}
tags = removeTags(tags, catTags);

// Best practice
if (bestPracticeTags.length > 1) {
issues.push(
`Only one best-practice tag is allowed, got ${bestPracticeTags.length}`
);
}
if (!startsWith(tags, bestPracticeTags)) {
issues.push(`Tag ${bestPracticeTags[0]} must be before ${tags[0]}`);
}
tags = removeTags(tags, bestPracticeTags);

const standards = {};
// WCAG, Section 508, Trusted Tester, EN 301 549
for (const {
name,
standardRegex,
criterionRegex,
wcagLevelRegex
} of standardsTags) {
const standardTags = tags.filter(tag => tag.match(standardRegex));
const criterionTags = tags.filter(tag => tag.match(criterionRegex));
if (!standardTags.length && !criterionTags.length) {
continue;
}

standards[name] = {
name,
standardTag: standardTags[0] ?? null,
criterionTags
};
if (bestPracticeTags.length) {
issues.push(`${name} tags cannot be used along side best-practice tag`);
}
if (standardTags === 0) {
issues.push(`Expected one ${name} tag, got 0`);
} else if (standardTags.length > 1) {
issues.push(`Expected one ${name} tag, got: ${standardTags.join(', ')}`);
}
if (!criterionTags.length) {
issues.push(
`Expected at least one ${name} criterion tag, got ${criterionTags.length}`
);
}

if (wcagLevelRegex) {
const wcagLevel = standards.WCAG.standardTag;
if (!wcagLevel.match(wcagLevelRegex)) {
issues.push(`${name} rules not allowed on ${wcagLevel}`);
}
}

// Must have the same criteria listed
if (name === 'EN 301 549') {
const wcagCriteria = standards.WCAG.criterionTags.map(tag =>
tag.slice(4)
);
const enCriteria = criterionTags.map(tag =>
tag.slice(5).replaceAll('.', '')
);
if (
wcagCriteria.length !== enCriteria.length ||
!startsWith(wcagCriteria, enCriteria)
) {
issues.push(
`Expect WCAG and EN criteria numbers to match: ${wcagCriteria.join(
', '
)} vs ${enCriteria.join(', ')}}`
);
}
}

if (!startsWith(tags, bestPracticeTags)) {
issues.push(`Tag ${bestPracticeTags[0]} must be before ${tags[0]}`);
}
tags = removeTags(tags, [...standardTags, ...criterionTags]);
}

// Other tags
const usedMiscTags = miscTags.filter(tag => tags.includes(tag));
if (!startsWith(tags, usedMiscTags)) {
issues.push(
`Tag [${usedMiscTags.join(',')}] must be before [${tags.join(',')}]`
);
}
const unknownTags = removeTags(tags, usedMiscTags);

// At this point all known tags are filtered
if (unknownTags.length) {
issues.push(`Invalid tags: ${unknownTags.join(', ')}`);
}
return issues;
}

function startsWith(arr1, arr2) {
return arr2.every((item, i) => item === arr1[i]);
}

function removeTags(tags, tagsToRemove) {
return tags.filter(tag => !tagsToRemove.includes(tag));
}
8 changes: 4 additions & 4 deletions doc/rule-descriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ These rules are disabled by default, until WCAG 2.2 is more widely adopted and r

| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules |
| :----------------------------------------------------------------------------------------------- | :------------------------------------------------- | :------ | :--------------------------------------------- | :------------------------- | :-------- |
| [target-size](https://dequeuniversity.com/rules/axe/4.7/target-size?application=RuleDescription) | Ensure touch target have sufficient size and space | Serious | wcag22aa, wcag258, cat.sensory-and-visual-cues | failure, needs review | |
| [target-size](https://dequeuniversity.com/rules/axe/4.7/target-size?application=RuleDescription) | Ensure touch target have sufficient size and space | Serious | cat.sensory-and-visual-cues, wcag22aa, wcag258 | failure, needs review | |

## Best Practices Rules

Expand All @@ -99,7 +99,7 @@ Rules that do not necessarily conform to WCAG success criterion but are industry
| [aria-treeitem-name](https://dequeuniversity.com/rules/axe/4.7/aria-treeitem-name?application=RuleDescription) | Ensures every ARIA treeitem node has an accessible name | Serious | cat.aria, best-practice | failure, needs review | |
| [empty-heading](https://dequeuniversity.com/rules/axe/4.7/empty-heading?application=RuleDescription) | Ensures headings have discernible text | Minor | cat.name-role-value, best-practice | failure, needs review | [ffd0e9](https://act-rules.github.io/rules/ffd0e9) |
| [empty-table-header](https://dequeuniversity.com/rules/axe/4.7/empty-table-header?application=RuleDescription) | Ensures table headers have discernible text | Minor | cat.name-role-value, best-practice | failure, needs review | |
| [frame-tested](https://dequeuniversity.com/rules/axe/4.7/frame-tested?application=RuleDescription) | Ensures <iframe> and <frame> elements contain the axe-core script | Critical | cat.structure, review-item, best-practice | failure, needs review | |
| [frame-tested](https://dequeuniversity.com/rules/axe/4.7/frame-tested?application=RuleDescription) | Ensures <iframe> and <frame> elements contain the axe-core script | Critical | cat.structure, best-practice, review-item | failure, needs review | |
| [heading-order](https://dequeuniversity.com/rules/axe/4.7/heading-order?application=RuleDescription) | Ensures the order of headings is semantically correct | Moderate | cat.semantics, best-practice | failure, needs review | |
| [image-redundant-alt](https://dequeuniversity.com/rules/axe/4.7/image-redundant-alt?application=RuleDescription) | Ensure image alternative is not repeated as text | Minor | cat.text-alternatives, best-practice | failure | |
| [label-title-only](https://dequeuniversity.com/rules/axe/4.7/label-title-only?application=RuleDescription) | Ensures that every form element has a visible label and is not solely labeled using hidden labels, or the title or aria-describedby attributes | Serious | cat.forms, best-practice | failure | |
Expand Down Expand Up @@ -137,9 +137,9 @@ Rules we are still testing and developing. They are disabled by default in axe-c

| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules |
| :------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- | :------- | :----------------------------------------------------------------------------------------------------------- | :------------------------- | :------------------------------------------------- |
| [css-orientation-lock](https://dequeuniversity.com/rules/axe/4.7/css-orientation-lock?application=RuleDescription) | Ensures content is not locked to any specific display orientation, and the content is operable in all display orientations | Serious | cat.structure, wcag134, wcag21aa, EN.301.549, EN-9.1.4.3, experimental | failure, needs review | [b33eff](https://act-rules.github.io/rules/b33eff) |
| [css-orientation-lock](https://dequeuniversity.com/rules/axe/4.7/css-orientation-lock?application=RuleDescription) | Ensures content is not locked to any specific display orientation, and the content is operable in all display orientations | Serious | cat.structure, wcag134, wcag21aa, EN.301.549, EN-9.1.3.4, experimental | failure, needs review | [b33eff](https://act-rules.github.io/rules/b33eff) |
| [focus-order-semantics](https://dequeuniversity.com/rules/axe/4.7/focus-order-semantics?application=RuleDescription) | Ensures elements in the focus order have a role appropriate for interactive content | Minor | cat.keyboard, best-practice, experimental | failure | |
| [hidden-content](https://dequeuniversity.com/rules/axe/4.7/hidden-content?application=RuleDescription) | Informs users about hidden content. | Minor | cat.structure, experimental, review-item, best-practice | failure, needs review | |
| [hidden-content](https://dequeuniversity.com/rules/axe/4.7/hidden-content?application=RuleDescription) | Informs users about hidden content. | Minor | cat.structure, best-practice, experimental, review-item | failure, needs review | |
| [label-content-name-mismatch](https://dequeuniversity.com/rules/axe/4.7/label-content-name-mismatch?application=RuleDescription) | Ensures that elements labelled through their content must have their visible text as part of their accessible name | Serious | cat.semantics, wcag21a, wcag253, EN.301.549, EN-9.2.5.3, experimental | failure | [2ee8b8](https://act-rules.github.io/rules/2ee8b8) |
| [p-as-heading](https://dequeuniversity.com/rules/axe/4.7/p-as-heading?application=RuleDescription) | Ensure bold, italic text and font-size is not used to style <p> elements as a heading | Serious | cat.semantics, wcag2a, wcag131, EN.301.549, EN-9.1.3.1, experimental | failure, needs review | |
| [table-fake-caption](https://dequeuniversity.com/rules/axe/4.7/table-fake-caption?application=RuleDescription) | Ensure that tables with a caption use the <caption> element. | Serious | cat.tables, experimental, wcag2a, wcag131, section508, section508.22.g, EN.301.549, EN-9.1.3.1 | failure | |
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/css-orientation-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"wcag134",
"wcag21aa",
"EN.301.549",
"EN-9.1.4.3",
"EN-9.1.3.4",
"experimental"
],
"actIds": ["b33eff"],
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/frame-tested.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "frame-tested",
"selector": "html, frame, iframe",
"tags": ["cat.structure", "review-item", "best-practice"],
"tags": ["cat.structure", "best-practice", "review-item"],
"metadata": {
"description": "Ensures <iframe> and <frame> elements contain the axe-core script",
"help": "Frames should be tested with axe-core"
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/hidden-content.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"id": "hidden-content",
"selector": "*",
"excludeHidden": false,
"tags": ["cat.structure", "experimental", "review-item", "best-practice"],
"tags": ["cat.structure", "best-practice", "experimental", "review-item"],
"metadata": {
"description": "Informs users about hidden content.",
"help": "Hidden content on the page should be analyzed"
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/target-size.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"selector": "*",
"enabled": false,
"matches": "widget-not-inline-matches",
"tags": ["wcag22aa", "wcag258", "cat.sensory-and-visual-cues"],
"tags": ["cat.sensory-and-visual-cues", "wcag22aa", "wcag258"],
"metadata": {
"description": "Ensure touch target have sufficient size and space",
"help": "All touch targets must be 24px large, or leave sufficient space"
Expand Down

0 comments on commit d6febf9

Please sign in to comment.