@hint/hint-axe
Version:
hint that that checks using axe for accessibility related best practices
183 lines (182 loc) • 6.86 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.register = void 0;
const axe_core_1 = require("axe-core");
const utils_types_1 = require("@hint/utils-types");
const utils_network_1 = require("@hint/utils-network");
const i18n_import_1 = require("../i18n.import");
const registrationMap = new Map();
const getElement = (context, node) => {
var _a;
let selector = node.target[0];
if (Array.isArray(selector)) {
selector = selector[0];
}
return (_a = context.pageDOM) === null || _a === void 0 ? void 0 : _a.querySelector(selector);
};
const hasCheckData = (result) => {
return !!result.data;
};
const toCheckMessage = (result) => {
return result.message;
};
const getSummary = (node) => {
const summary = [...node.all, ...node.any, ...node.none]
.filter(hasCheckData)
.map(toCheckMessage)
.join(' ');
return summary;
};
const queueRegistration = (registration, map) => {
const engineKey = registration.context.engineKey;
const resource = registration.event.resource;
const registrationsByResource = map.get(engineKey) || new Map();
const registrations = registrationsByResource.get(resource) || [];
registrations.push(registration);
registrationsByResource.set(resource, registrations);
map.set(engineKey, registrationsByResource);
};
const useRegistrations = (engineKey, resource, map) => {
const registrationsByResource = map.get(engineKey);
if (!registrationsByResource) {
return null;
}
const registrations = registrationsByResource.get(resource);
if (!registrations) {
return null;
}
registrationsByResource.delete(resource);
if (!registrationsByResource.size) {
map.delete(engineKey);
}
return registrations;
};
const toSeverity = (impact) => {
if (impact === 'minor') {
return utils_types_1.Severity.hint;
}
if (impact === 'moderate' || impact === 'serious') {
return utils_types_1.Severity.warning;
}
if (impact === 'critical') {
return utils_types_1.Severity.error;
}
return utils_types_1.Severity.warning;
};
const withQuotes = (ruleId) => {
return `'${ruleId}'`;
};
const evaluateAxe = async (context, event, rules) => {
const { document, resource } = event;
const uri = (0, utils_network_1.getAsUri)(resource);
const shouldScanIframes = !(uri && uri.protocol.includes('file'));
try {
const target = document.isFragment ?
'document.body' :
'document';
return await context.evaluate(`(function(module) {
${axe_core_1.source}
var target = ${target};
return window.axe.run(target, {
iframes: ${shouldScanIframes},
runOnly: {
type: 'rule',
values: [${rules.map(withQuotes).join(',')}]
}
});
})()`);
}
catch (e) {
const err = e;
let message;
console.error(`Running axe-core failed: ${err.message}\n${err.stack}`);
if (err.message.includes('evaluation exceeded')) {
message = (0, i18n_import_1.getMessage)('notFastEnough', context.language);
}
else {
message = (0, i18n_import_1.getMessage)('errorExecuting', context.language, err.message);
}
message = (0, i18n_import_1.getMessage)('tryAgainLater', context.language, message);
context.report(resource, message, { severity: utils_types_1.Severity.warning });
return null;
}
};
const normalizeOptions = (options) => {
if (Array.isArray(options)) {
const normalizedOptions = options.reduce((newOptions, axeRuleId) => {
newOptions[axeRuleId] = 'default';
return newOptions;
}, {});
return normalizedOptions;
}
return options || {};
};
const register = (context, rules, disabled) => {
const options = normalizeOptions(context.hintOptions);
const { engineKey } = context;
const enabledRules = rules.filter((rule) => {
if (options[rule]) {
return options[rule] !== 'off';
}
return !disabled.includes(rule);
});
context.on('traverse::end', (event) => {
queueRegistration({ context, enabledRules, event, options }, registrationMap);
});
context.on('scan::end', async ({ resource }) => {
var _a;
const registrations = useRegistrations(engineKey, resource, registrationMap);
if (!registrations) {
return;
}
const ruleToRegistration = new Map();
for (const registration of registrations) {
for (const rule of registration.enabledRules) {
ruleToRegistration.set(rule, registration);
}
}
const document = registrations[0].event.document;
const rules = Array.from(ruleToRegistration.keys());
let result = null;
if (document.defaultView) {
const target = document.isFragment ? document.body : document.documentElement;
result = await (0, axe_core_1.run)(target, {
runOnly: {
type: 'rule',
values: rules
}
});
}
else {
result = await evaluateAxe(context, { document, resource }, rules);
}
if (!result || !Array.isArray(result.violations)) {
throw new Error(`Unable to parse axe results ${result}`);
}
for (const violation of result.violations) {
for (const node of violation.nodes) {
const summary = getSummary(node);
const message = summary ? `${violation.help}: ${summary}` : violation.help;
const registration = ruleToRegistration.get(violation.id);
const element = getElement(context, node);
const ruleSeverity = (_a = utils_types_1.Severity[registration.options[violation.id]]) !== null && _a !== void 0 ? _a : utils_types_1.Severity.default;
const forceSeverity = ruleSeverity !== utils_types_1.Severity.default;
const severity = !forceSeverity ?
toSeverity(violation.impact) :
ruleSeverity;
registration.context.report(resource, message, {
codeLanguage: 'html',
codeSnippet: node.html,
documentation: [{
link: violation.helpUrl,
text: (0, i18n_import_1.getMessage)('learnMore', context.language)
}],
element,
forceSeverity,
severity
});
}
}
});
};
exports.register = register;