Skip to content

Commit d63fbe2

Browse files
committed
Updated validate to allow multiple wcag rules
1 parent c1602ab commit d63fbe2

File tree

1 file changed

+172
-161
lines changed

1 file changed

+172
-161
lines changed

build/tasks/validate.js

Lines changed: 172 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -303,167 +303,178 @@ function validateRule({ tags, metadata }) {
303303
issues.push(`metadata.help can not contain the word '${prohibitedWord}'.`);
304304
}
305305

306-
// issues.push(...findTagIssues(tags));
306+
issues.push(...findTagIssues(tags));
307307
return issues;
308308
}
309309

310-
// const miscTags = ['ACT', 'experimental', 'review-item', 'deprecated'];
311-
312-
// const categories = [
313-
// 'aria',
314-
// 'color',
315-
// 'forms',
316-
// 'keyboard',
317-
// 'language',
318-
// 'name-role-value',
319-
// 'parsing',
320-
// 'semantics',
321-
// 'sensory-and-visual-cues',
322-
// 'structure',
323-
// 'tables',
324-
// 'text-alternatives',
325-
// 'time-and-media'
326-
// ];
327-
328-
// const standardsTags = [
329-
// {
330-
// // Has to be first, as others rely on the WCAG level getting picked up first
331-
// name: 'WCAG',
332-
// standardRegex: /^wcag2(1|2)?a{1,3}(-obsolete)?$/,
333-
// criterionRegex: /^wcag\d{3,4}$/
334-
// },
335-
// {
336-
// name: 'Section 508',
337-
// standardRegex: /^section508$/,
338-
// criterionRegex: /^section508\.\d{1,2}\.[a-z]$/,
339-
// wcagLevelRegex: /^wcag2aa?$/
340-
// },
341-
// {
342-
// name: 'Trusted Tester',
343-
// standardRegex: /^TTv5$/,
344-
// criterionRegex: /^TT\d{1,3}\.[a-z]$/,
345-
// wcagLevelRegex: /^wcag2aa?$/
346-
// },
347-
// {
348-
// name: 'EN 301 549',
349-
// standardRegex: /^EN-301-549$/,
350-
// criterionRegex: /^EN-9\.[1-4]\.[1-9]\.\d{1,2}$/,
351-
// wcagLevelRegex: /^wcag21?aa?$/
352-
// }
353-
// ];
354-
355-
// function findTagIssues(tags) {
356-
// const issues = [];
357-
// const catTags = tags.filter(tag => tag.startsWith('cat.'));
358-
// const bestPracticeTags = tags.filter(tag => tag === 'best-practice');
359-
360-
// // Category
361-
// if (catTags.length !== 1) {
362-
// issues.push(`Must have exactly one cat. tag, got ${catTags.length}`);
363-
// }
364-
// if (catTags.length && !categories.includes(catTags[0].slice(4))) {
365-
// issues.push(`Invalid category tag: ${catTags[0]}`);
366-
// }
367-
// if (!startsWith(tags, catTags)) {
368-
// issues.push(`Tag ${catTags[0]} must be before ${tags[0]}`);
369-
// }
370-
// tags = removeTags(tags, catTags);
371-
372-
// // Best practice
373-
// if (bestPracticeTags.length > 1) {
374-
// issues.push(
375-
// `Only one best-practice tag is allowed, got ${bestPracticeTags.length}`
376-
// );
377-
// }
378-
// if (!startsWith(tags, bestPracticeTags)) {
379-
// issues.push(`Tag ${bestPracticeTags[0]} must be before ${tags[0]}`);
380-
// }
381-
// tags = removeTags(tags, bestPracticeTags);
382-
383-
// const standards = {};
384-
// // WCAG, Section 508, Trusted Tester, EN 301 549
385-
// for (const {
386-
// name,
387-
// standardRegex,
388-
// criterionRegex,
389-
// wcagLevelRegex
390-
// } of standardsTags) {
391-
// const standardTags = tags.filter(tag => tag.match(standardRegex));
392-
// const criterionTags = tags.filter(tag => tag.match(criterionRegex));
393-
// if (!standardTags.length && !criterionTags.length) {
394-
// continue;
395-
// }
396-
397-
// standards[name] = {
398-
// name,
399-
// standardTag: standardTags[0] ?? null,
400-
// criterionTags
401-
// };
402-
// if (bestPracticeTags.length !== 0) {
403-
// issues.push(`${name} tags cannot be used along side best-practice tag`);
404-
// }
405-
// if (standardTags.length === 0) {
406-
// issues.push(`Expected one ${name} tag, got 0`);
407-
// } else if (standardTags.length > 1) {
408-
// issues.push(`Expected one ${name} tag, got: ${standardTags.join(', ')}`);
409-
// }
410-
// if (criterionTags.length === 0) {
411-
// issues.push(`Expected at least one ${name} criterion tag, got 0`);
412-
// }
413-
414-
// if (wcagLevelRegex) {
415-
// const wcagLevel = standards.WCAG.standardTag;
416-
// if (!wcagLevel.match(wcagLevelRegex)) {
417-
// issues.push(`${name} rules not allowed on ${wcagLevel}`);
418-
// }
419-
// }
420-
421-
// // Must have the same criteria listed
422-
// if (name === 'EN 301 549') {
423-
// const wcagCriteria = standards.WCAG.criterionTags.map(tag =>
424-
// tag.slice(4)
425-
// );
426-
// const enCriteria = criterionTags.map(tag =>
427-
// tag.slice(5).replaceAll('.', '')
428-
// );
429-
// if (
430-
// wcagCriteria.length !== enCriteria.length ||
431-
// !startsWith(wcagCriteria, enCriteria)
432-
// ) {
433-
// issues.push(
434-
// `Expect WCAG and EN criteria numbers to match: ${wcagCriteria.join(
435-
// ', '
436-
// )} vs ${enCriteria.join(', ')}}`
437-
// );
438-
// }
439-
// }
440-
// tags = removeTags(tags, [...standardTags, ...criterionTags]);
441-
// }
442-
443-
// // Other tags
444-
// const usedMiscTags = miscTags.filter(tag => tags.includes(tag));
445-
// const unknownTags = removeTags(tags, usedMiscTags);
446-
// if (unknownTags.length) {
447-
// issues.push(`Invalid tags: ${unknownTags.join(', ')}`);
448-
// }
449-
450-
// // At this point only misc tags are left:
451-
// tags = removeTags(tags, unknownTags);
452-
// if (!startsWith(tags, usedMiscTags)) {
453-
// issues.push(
454-
// `Tags [${tags.join(', ')}] should be sorted like [${usedMiscTags.join(
455-
// ', '
456-
// )}]`
457-
// );
458-
// }
459-
460-
// return issues;
461-
// }
462-
463-
// function startsWith(arr1, arr2) {
464-
// return arr2.every((item, i) => item === arr1[i]);
465-
// }
466-
467-
// function removeTags(tags, tagsToRemove) {
468-
// return tags.filter(tag => !tagsToRemove.includes(tag));
469-
// }
310+
const miscTags = [
311+
'ACT',
312+
'experimental',
313+
'review-item',
314+
'deprecated',
315+
'a11y-engine',
316+
'a11y-engine-experimental'
317+
];
318+
319+
const categories = [
320+
'aria',
321+
'color',
322+
'forms',
323+
'keyboard',
324+
'language',
325+
'name-role-value',
326+
'parsing',
327+
'semantics',
328+
'sensory-and-visual-cues',
329+
'structure',
330+
'tables',
331+
'text-alternatives',
332+
'time-and-media'
333+
];
334+
335+
const standardsTags = [
336+
{
337+
// Has to be first, as others rely on the WCAG level getting picked up first
338+
name: 'WCAG',
339+
standardRegex: /^wcag2(1|2)?a{1,3}(-obsolete)?$/,
340+
criterionRegex: /^wcag\d{3,4}$/
341+
},
342+
{
343+
name: 'Section 508',
344+
standardRegex: /^section508$/,
345+
criterionRegex: /^section508\.\d{1,2}\.[a-z]$/,
346+
wcagLevelRegex: /^wcag2aa?$/
347+
},
348+
{
349+
name: 'Trusted Tester',
350+
standardRegex: /^TTv5$/,
351+
criterionRegex: /^TT\d{1,3}\.[a-z]$/,
352+
wcagLevelRegex: /^wcag2aa?$/
353+
},
354+
{
355+
name: 'EN 301 549',
356+
standardRegex: /^EN-301-549$/,
357+
criterionRegex: /^EN-9\.[1-4]\.[1-9]\.\d{1,2}$/,
358+
wcagLevelRegex: /^wcag21?aa?$/
359+
}
360+
];
361+
362+
function findTagIssues(tags) {
363+
const issues = [];
364+
const catTags = tags.filter(tag => tag.startsWith('cat.'));
365+
const bestPracticeTags = tags.filter(tag => tag === 'best-practice');
366+
367+
// Category
368+
if (catTags.length !== 1) {
369+
issues.push(`Must have exactly one cat. tag, got ${catTags.length}`);
370+
}
371+
if (catTags.length && !categories.includes(catTags[0].slice(4))) {
372+
issues.push(`Invalid category tag: ${catTags[0]}`);
373+
}
374+
if (!startsWith(tags, catTags)) {
375+
issues.push(`Tag ${catTags[0]} must be before ${tags[0]}`);
376+
}
377+
tags = removeTags(tags, catTags);
378+
379+
// Best practice
380+
if (bestPracticeTags.length > 1) {
381+
issues.push(
382+
`Only one best-practice tag is allowed, got ${bestPracticeTags.length}`
383+
);
384+
}
385+
if (!startsWith(tags, bestPracticeTags)) {
386+
issues.push(`Tag ${bestPracticeTags[0]} must be before ${tags[0]}`);
387+
}
388+
tags = removeTags(tags, bestPracticeTags);
389+
390+
const standards = {};
391+
// WCAG, Section 508, Trusted Tester, EN 301 549
392+
for (const {
393+
name,
394+
standardRegex,
395+
criterionRegex,
396+
wcagLevelRegex
397+
} of standardsTags) {
398+
const standardTags = tags.filter(tag => tag.match(standardRegex));
399+
const criterionTags = tags.filter(tag => tag.match(criterionRegex));
400+
if (!standardTags.length && !criterionTags.length) {
401+
continue;
402+
}
403+
404+
standards[name] = {
405+
name,
406+
standardTag: standardTags[0] ?? null,
407+
criterionTags
408+
};
409+
if (bestPracticeTags.length !== 0) {
410+
issues.push(`${name} tags cannot be used along side best-practice tag`);
411+
}
412+
if (standardTags.length === 0) {
413+
issues.push(`Expected one ${name} tag, got 0`);
414+
}
415+
// Commented out this part allowing multiple wcag rules.
416+
// This is because we have multiple WCAG rules for different levels.
417+
418+
// else if (standardTags.length > 1) {
419+
// issues.push(`Expected one ${name} tag, got: ${standardTags.join(', ')}`);
420+
// }
421+
if (criterionTags.length === 0) {
422+
issues.push(`Expected at least one ${name} criterion tag, got 0`);
423+
}
424+
425+
if (wcagLevelRegex) {
426+
const wcagLevel = standards.WCAG.standardTag;
427+
if (!wcagLevel.match(wcagLevelRegex)) {
428+
issues.push(`${name} rules not allowed on ${wcagLevel}`);
429+
}
430+
}
431+
432+
// Must have the same criteria listed
433+
if (name === 'EN 301 549') {
434+
const wcagCriteria = standards.WCAG.criterionTags.map(tag =>
435+
tag.slice(4)
436+
);
437+
const enCriteria = criterionTags.map(tag =>
438+
tag.slice(5).replaceAll('.', '')
439+
);
440+
if (
441+
wcagCriteria.length !== enCriteria.length ||
442+
!startsWith(wcagCriteria, enCriteria)
443+
) {
444+
issues.push(
445+
`Expect WCAG and EN criteria numbers to match: ${wcagCriteria.join(
446+
', '
447+
)} vs ${enCriteria.join(', ')}}`
448+
);
449+
}
450+
}
451+
tags = removeTags(tags, [...standardTags, ...criterionTags]);
452+
}
453+
454+
// Other tags
455+
const usedMiscTags = miscTags.filter(tag => tags.includes(tag));
456+
const unknownTags = removeTags(tags, usedMiscTags);
457+
if (unknownTags.length) {
458+
issues.push(`Invalid tags: ${unknownTags.join(', ')}`);
459+
}
460+
461+
// At this point only misc tags are left:
462+
tags = removeTags(tags, unknownTags);
463+
if (!startsWith(tags, usedMiscTags)) {
464+
issues.push(
465+
`Tags [${tags.join(', ')}] should be sorted like [${usedMiscTags.join(
466+
', '
467+
)}]`
468+
);
469+
}
470+
471+
return issues;
472+
}
473+
474+
function startsWith(arr1, arr2) {
475+
return arr2.every((item, i) => item === arr1[i]);
476+
}
477+
478+
function removeTags(tags, tagsToRemove) {
479+
return tags.filter(tag => !tagsToRemove.includes(tag));
480+
}

0 commit comments

Comments
 (0)