Skip to content
Draft
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
16 changes: 16 additions & 0 deletions lib/core/public/configure.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ function configure(spec) {
throw new Error('No audit configured');
}

// [a11y-critical]: configure() reports success by not throwing and returns
// nothing, so a caller cannot tell a completed registration from one that was
// later discarded (see load.js — a second axe evaluation replaces _audit).
// Record what this call registered so that is answerable after the fact.
const configureLog = {
ruleCountBefore: audit.rules.length,
rulesIn: Array.isArray(spec.rules) ? spec.rules.length : 0,
checksIn: Array.isArray(spec.checks) ? spec.checks.length : 0
};

if (spec.axeVersion || spec.ver) {
const specVersion = spec.axeVersion || spec.ver;
if (!/^\d+\.\d+\.\d+(-canary)?/.test(specVersion)) {
Expand Down Expand Up @@ -139,6 +149,12 @@ function configure(spec) {
if (spec.crossOriginDenylist) {
audit.setCrossOriginDenylist(spec.crossOriginDenylist);
}

// [a11y-critical]: completes the record started above. audit.generation lets a
// caller detect that the audit it configured is no longer the live one.
configureLog.ruleCountAfter = audit.rules.length;
configureLog.generation = audit.generation;
audit.lastConfigure = configureLog;
}

export default configure;
12 changes: 12 additions & 0 deletions lib/core/public/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,19 @@ import mergeErrors from '../utils/merge-errors';
* @private
*/
export default function load(audit) {
// [a11y-critical]: this discards every prior axe.configure() with no error —
// the built axe.js calls _load on evaluation, so evaluating axe twice in one
// context silently resets the registry to stock. Carry a generation counter so
// a caller can tell its configured audit was replaced out from under it.
const previous = axe._audit;
axe._audit = new Audit(audit);
axe._audit.generation = previous ? (previous.generation || 0) + 1 : 0;
if (previous && typeof axe.log === 'function') {
axe.log(
'axe._load replaced an existing audit; previously configured rules and checks are gone',
{ generation: axe._audit.generation }
);
}
}

function runCommand(data, keepalive, callback) {
Expand Down
14 changes: 8 additions & 6 deletions lib/core/utils/normalize-run-options.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Ensure all rules that are expected to run exist
* @throws {Error} If any tag or rule specified in options is unknown
* @throws {Error} If any rule specified in options.runOnly is unknown
* @param {Object} options Options object
* @return {Object} Validated options object
*/
Expand Down Expand Up @@ -69,11 +69,13 @@ export default function normalizeRunOptions(options = {}) {
}
}
if (typeof options.rules === 'object') {
Object.keys(options.rules).forEach(ruleId => {
if (!ruleIds.includes(ruleId)) {
throw new Error('unknown rule `' + ruleId + '` in options.rules');
}
});
// [a11y-critical]: log unknown ids in options.rules instead of throwing
const unmatchedRules = Object.keys(options.rules).filter(
ruleId => !ruleIds.includes(ruleId)
);
if (unmatchedRules.length !== 0) {
axe.log('Could not find rules `' + unmatchedRules.join('`, `') + '`');
}
}
return options;
}
27 changes: 25 additions & 2 deletions test/core/utils/normalize-run-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,21 @@ describe('axe.utils.normalizeRunOptions', () => {
});
});

it('throws an error when option.rules has an unknown rule', () => {
assert.throws(() => {
it('logs an issue when option.rules has an unknown rule', () => {
let message = '';
axe._setLogger(m => {
message = m;
});
axe.utils.normalizeRunOptions({
rules: {
fakeRule: { enabled: false }
}
});
assert.include(message, 'Could not find rules');
});

it('does not throw when option.rules has an unknown rule', () => {
assert.doesNotThrow(() => {
axe.utils.normalizeRunOptions({
rules: {
fakeRule: { enabled: false }
Expand All @@ -223,6 +236,16 @@ describe('axe.utils.normalizeRunOptions', () => {
});
});

it('keeps known rules in options.rules when another id is unknown', () => {
const options = axe.utils.normalizeRunOptions({
rules: {
'color-contrast': { enabled: false },
fakeRule: { enabled: false }
}
});
assert.deepEqual(options.rules['color-contrast'], { enabled: false });
});

it('logs an issue when a tag is unknown', () => {
let message = '';
axe._setLogger(m => {
Expand Down
Loading