diff --git a/.circleci/config.yml b/.circleci/config.yml index 7b3aa42218..78b66949da 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -61,13 +61,14 @@ jobs: paths: - node_modules - # Build and cache axe.js + # Build and cache built files build_unix: <<: *defaults <<: *unix_box steps: - checkout - <<: *restore_dependency_cache_unix + - run: npm run prepare - run: npm run build - save_cache: key: v9-cache-build-<< pipeline.git.revision >> diff --git a/.eslintignore b/.eslintignore index ca1d987e96..df541335e0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -7,5 +7,6 @@ doc/api/* doc/examples/jest_react/*.js lib/core/imports/*.js +lib/core/utils/uuid.js axe.js -axe.min.js \ No newline at end of file +axe.min.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 37592b6609..0000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,181 +0,0 @@ -module.exports = { - root: true, - extends: ['prettier'], - parserOptions: { - ecmaVersion: 2023 - }, - env: { - node: true, - es6: true - }, - globals: { - axe: true, - Promise: true - }, - rules: { - 'no-bitwise': 2, - camelcase: 2, - curly: 2, - eqeqeq: 2, - 'guard-for-in': 2, - 'wrap-iife': [2, 'any'], - 'no-use-before-define': [ - 2, - { - functions: false - } - ], - 'new-cap': 2, - 'no-caller': 2, - 'no-empty': 2, - 'no-new': 2, - 'no-plusplus': 0, - 'no-undef': 2, - 'no-unused-vars': 2, - strict: 0, - 'max-params': [2, 6], - 'max-depth': [2, 5], - 'max-len': 0, - semi: 0, - 'no-cond-assign': 0, - 'no-debugger': 2, - 'no-eq-null': 0, - 'no-eval': 2, - 'no-unused-expressions': 0, - 'block-scoped-var': 0, - 'no-iterator': 0, - 'linebreak-style': 0, - 'no-loop-func': 0, - 'no-multi-str': 0, - 'no-proto': 0, - 'no-script-url': 0, - 'dot-notation': 2, - 'no-new-func': 0, - 'no-new-wrappers': 0, - 'no-shadow': 2, - 'no-restricted-syntax': [ - 'error', - { - selector: 'MemberExpression[property.name=tagName]', - message: "Don't use node.tagName, use node.nodeName instead." - }, - // node.attributes can be clobbered so is unsafe to use - // @see https://github.com/dequelabs/axe-core/pull/1432 - { - // node.attributes - selector: - 'MemberExpression[object.name=node][property.name=attributes]', - message: - "Don't use node.attributes, use node.hasAttributes() or axe.utils.getNodeAttributes(node) instead." - }, - { - // vNode.actualNode.attributes - selector: - 'MemberExpression[object.property.name=actualNode][property.name=attributes]', - message: - "Don't use node.attributes, use node.hasAttributes() or axe.utils.getNodeAttributes(node) instead." - }, - // node.contains doesn't work with shadow dom - // @see https://github.com/dequelabs/axe-core/issues/4194 - { - // node.contains() - selector: - 'CallExpression[callee.object.name=node][callee.property.name=contains]', - message: - "Don't use node.contains(node2) as it doesn't work across shadow DOM. Use axe.utils.contains(node, node2) instead." - }, - { - // vNode.actualNode.contains() - selector: - 'CallExpression[callee.object.property.name=actualNode][callee.property.name=contains]', - message: - "Don't use node.contains(node2) as it doesn't work across shadow DOM. Use axe.utils.contains(node, node2) instead." - } - ] - }, - overrides: [ - { - files: ['lib/**/*.js'], - excludedFiles: ['lib/core/reporters/**/*.js', 'lib/**/*-after.js'], - parserOptions: { - sourceType: 'module' - }, - env: { - // do not access global window properties without going through window - browser: false, - es6: true - }, - globals: { - window: true, - document: true - }, - rules: { - 'func-names': [2, 'as-needed'], - 'prefer-const': 2, - 'no-use-before-define': 'off' - } - }, - { - // after functions and reporters will not be run inside the same context as axe.run so should not access browser globals that require context specific information (window.location, window.getComputedStyles, etc.) - files: ['lib/**/*-after.js', 'lib/core/reporters/**/*.js'], - parserOptions: { - sourceType: 'module' - }, - env: { - browser: false - }, - globals: {}, - rules: { - 'func-names': [2, 'as-needed'], - 'prefer-const': 2, - 'no-use-before-define': 'off' - } - }, - { - // polyfills are mostly copy-pasted from sources so we don't control their styling - files: [ - 'lib/core/imports/polyfills.js', - 'lib/core/utils/pollyfill-elements-from-point.js' - ], - env: { - browser: false - }, - rules: { - 'func-names': 0, - 'no-bitwise': 0, - curly: 0, - eqeqeq: 0 - } - }, - { - files: ['test/act-rules/**/*.js', 'test/aria-practices/**/*.js'], - env: { - mocha: true - } - }, - { - files: ['test/**/*.js'], - excludedFiles: ['test/act-rules/**/*.js', 'test/aria-practices/**/*.js'], - parserOptions: { - ecmaVersion: 2021 - }, - env: { - browser: true, - es6: false, - mocha: true - }, - globals: { - assert: true, - helpers: true, - checks: true, - sinon: true - }, - plugins: ['mocha-no-only'], - rules: { - 'new-cap': 0, - 'no-use-before-define': 0, - 'mocha-no-only/mocha-no-only': ['error'] - } - } - ] -}; diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b7ba1c567c..dfe93aaf63 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -32,10 +32,16 @@ updates: versions: ['>=9.0.0'] - dependency-name: 'chai' versions: ['>=5.0.0'] + - dependency-name: 'conventional-commits-parser' + versions: ['>=6.0.0'] # Prevent Webpack error caused by v0.11+ of esbuild # @see https://github.com/dequelabs/axe-core/issues/3771 - dependency-name: 'esbuild' versions: ['>=0.11.0'] + # Prevent colorjs.io issue caused by >v0.4.3 + # @see https://github.com/dequelabs/axe-core/issues/4428 + - dependency-name: 'colorjs.io' + versions: ['>0.4.3'] groups: # Any updates not caught by the group config will get individual PRs npm-low-risk: diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 74898ee2d4..722093522b 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -21,6 +21,6 @@ jobs: - run: npm run fmt # Prevent the prettierignore change from being committed. - run: git checkout .prettierignore - - uses: stefanzweifel/git-auto-commit-action@8756aa072ef5b4a080af5dc8fef36c5d586e521d # tag=v5 + - uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # tag=v5 with: commit_message: ':robot: Automated formatting fixes' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5c5e1cdb25..9b6e4cc28e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,10 +18,9 @@ jobs: node-version: 20 cache: 'npm' - run: npm ci + - run: npm run prepare - run: npm run build - # v4 download seems to have some flakiness with the download of artifacts so pinning to v3 for now - # @see https://github.com/actions/download-artifact/issues/249 - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: axe-core path: axe.js @@ -39,7 +38,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node}} - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: axe-core - run: npm run test:node diff --git a/.gitignore b/.gitignore index fe79e6d264..3331c6fdbb 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,3 @@ typings/axe-core/axe-core-tests.js # doc doc/rule-descriptions.*.md - diff --git a/CHANGELOG.md b/CHANGELOG.md index f464270605..86eef014c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [4.10.0](https://github.com/dequelabs/axe-core/compare/v4.9.1...v4.10.0) (2024-07-29) + +### Features + +- **new-rule:** summary elements must have an accessible name ([#4511](https://github.com/dequelabs/axe-core/issues/4511)) ([0d8a99e](https://github.com/dequelabs/axe-core/commit/0d8a99eadd8d49e5d3ea0f11ad77be732148431e)), closes [#4510](https://github.com/dequelabs/axe-core/issues/4510) + +### Bug Fixes + +- **aria-allowed-attr:** allow aria-multiline=false for element with contenteditable ([#4537](https://github.com/dequelabs/axe-core/issues/4537)) ([f019068](https://github.com/dequelabs/axe-core/commit/f0190685722495d00be644cabb1c9741d74acdea)) +- **aria-allowed-attr:** allow aria-required=false when normally not allowed ([#4532](https://github.com/dequelabs/axe-core/issues/4532)) ([2e242e1](https://github.com/dequelabs/axe-core/commit/2e242e146929902c97e181e41fa45e656cf3eb51)) +- **aria-prohibited-attr:** allow aria-label/ledby on descendants of widget ([#4541](https://github.com/dequelabs/axe-core/issues/4541)) ([07c5d91](https://github.com/dequelabs/axe-core/commit/07c5d91c658bda6bcd2743950bf70f25abd1f9ae)) +- **aria-roledescription:** keep disabled with { runOnly: 'wcag2a' } ([#4526](https://github.com/dequelabs/axe-core/issues/4526)) ([5b4cb9d](https://github.com/dequelabs/axe-core/commit/5b4cb9d7992a4c07745e64708040777de64874bd)), closes [#4523](https://github.com/dequelabs/axe-core/issues/4523) +- **autocomplete-valid:** incomplete for invalid but safe values ([#4500](https://github.com/dequelabs/axe-core/issues/4500)) ([e31a974](https://github.com/dequelabs/axe-core/commit/e31a974de395845c08af345f9458a8091e2b1c4b)), closes [#4492](https://github.com/dequelabs/axe-core/issues/4492) +- **build:** limit locales to valid files when using the --all-lang option ([#4486](https://github.com/dequelabs/axe-core/issues/4486)) ([d3db593](https://github.com/dequelabs/axe-core/commit/d3db593991261ad44eef1c142d8a4646edde93fa)), closes [#4485](https://github.com/dequelabs/axe-core/issues/4485) +- Prevent errors when loading axe in Angular + Jest ([#4456](https://github.com/dequelabs/axe-core/issues/4456)) ([3ef9353](https://github.com/dequelabs/axe-core/commit/3ef93531a574c2be76a92d59599d978714cca9d0)), closes [#4400](https://github.com/dequelabs/axe-core/issues/4400) +- Minor grammatical fixes for some rules and checks ([#4499](https://github.com/dequelabs/axe-core/issues/4499)) ([11fad59](https://github.com/dequelabs/axe-core/commit/11fad598c25eadd29f35ef6be382d907057d4537)) +- **landmark-unique:** follow spec, aside -> landmark ([#4469](https://github.com/dequelabs/axe-core/issues/4469)) ([e32f803](https://github.com/dequelabs/axe-core/commit/e32f8034246a92e4132dc04f6310e2b414d6d43f)), closes [#4460](https://github.com/dequelabs/axe-core/issues/4460) +- **required-attr:** allow aria-valuetext on slider instead of valuenow ([#4518](https://github.com/dequelabs/axe-core/issues/4518)) ([135898b](https://github.com/dequelabs/axe-core/commit/135898b38d5eb46c42170527a0ac9add425c5c3d)), closes [#4515](https://github.com/dequelabs/axe-core/issues/4515) + ### [4.9.1](https://github.com/dequelabs/axe-core/compare/v4.9.0...v4.9.1) (2024-05-06) ### Bug Fixes diff --git a/Gruntfile.js b/Gruntfile.js index 0a888fe1c5..64cfb9ab1d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -22,9 +22,13 @@ module.exports = function (grunt) { }); } else if (grunt.option('all-lang')) { var localeFiles = require('fs').readdirSync('./locales'); - langs = localeFiles.map(function (file) { - return '.' + file.replace('.json', ''); - }); + langs = localeFiles + .filter(function (file) { + return !file.startsWith('_') && file.endsWith('.json'); + }) + .map(function (file) { + return '.' + file.replace('.json', ''); + }); langs.unshift(''); // Add default } else { langs = ['']; diff --git a/axe.d.ts b/axe.d.ts index 8ecd9de75a..5c4197a872 100644 --- a/axe.d.ts +++ b/axe.d.ts @@ -70,16 +70,19 @@ declare namespace axe { | LabelledShadowDomSelector | LabelledFramesSelector; type SelectorList = Array | NodeList; + type ContextProp = Selector | SelectorList; type ContextObject = | { - include: Selector | SelectorList; - exclude?: Selector | SelectorList; + include: ContextProp; + exclude?: ContextProp; } | { - exclude: Selector | SelectorList; - include?: Selector | SelectorList; + exclude: ContextProp; + include?: ContextProp; }; - type ElementContext = Selector | SelectorList | ContextObject; + type ContextSpec = ContextProp | ContextObject; + /** Synonym to ContextSpec */ + type ElementContext = ContextSpec; type SerialSelector = | BaseSelector @@ -406,6 +409,16 @@ declare namespace axe { shadowSelect: (selector: CrossTreeSelector) => Element | null; shadowSelectAll: (selector: CrossTreeSelector) => Element[]; getStandards(): Required; + isContextSpec: (context: unknown) => context is ContextSpec; + isContextObject: (context: unknown) => context is ContextObject; + isContextProp: (context: unknown) => context is ContextProp; + isLabelledFramesSelector: ( + selector: unknown + ) => selector is LabelledFramesSelector; + isLabelledShadowDomSelector: ( + selector: unknown + ) => selector is LabelledShadowDomSelector; + DqElement: new ( elm: Element, options?: { absolutePaths?: boolean } diff --git a/bower.json b/bower.json index 0019b4a63a..87018d7ffa 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "4.9.1", + "version": "4.10.0", "deprecated": true, "contributors": [ { diff --git a/build/cherry-pick.js b/build/cherry-pick.js old mode 100644 new mode 100755 index 2b3dc2afe3..f8040807b2 --- a/build/cherry-pick.js +++ b/build/cherry-pick.js @@ -1,3 +1,5 @@ +#!/usr/bin/env node + const { execSync } = require('child_process'); const conventionalCommitsParser = require('conventional-commits-parser'); const chalk = require('chalk'); @@ -129,7 +131,7 @@ commitsToCherryPick.forEach(({ hash, type, scope, subject }) => { try { execSync(`git cherry-pick ${hash} -X theirs`); - } catch (e) { + } catch { console.error( chalk.red.bold('\nAborting cherry-pick and reseting to master') ); diff --git a/build/tasks/validate.js b/build/tasks/validate.js index 9af5edb28a..8eb5a2eba5 100644 --- a/build/tasks/validate.js +++ b/build/tasks/validate.js @@ -14,7 +14,7 @@ function fileExists(v, o) { var exists; try { exists = fs.existsSync(file); - } catch (e) { + } catch { return false; } return exists; diff --git a/doc/API.md b/doc/API.md index ea1f04a447..828fd52778 100644 --- a/doc/API.md +++ b/doc/API.md @@ -146,7 +146,7 @@ In this example, we pass in the WCAG 2 A and AA tags into `axe.getRules` to retr ```js [ { - description: "Ensures elements of image maps have alternate text", + description: "Ensure elements of image maps have alternate text", help: "Active elements must have alternate text", helpUrl: "https://dequeuniversity.com/rules/axe/3.5/area-alt?application=axeAPI", ruleId: "area-alt", @@ -162,7 +162,7 @@ In this example, we pass in the WCAG 2 A and AA tags into `axe.getRules` to retr actIds: ['c487ae'] }, { - description: "Ensures ARIA attributes are allowed for an element's role", + description: "Ensure ARIA attributes are allowed for an element's role", help: "Elements must only use allowed ARIA attributes", helpUrl: "https://dequeuniversity.com/rules/axe/3.5/aria-allowed-attr?application=axeAPI", ruleId: "aria-allowed-attr", diff --git a/doc/check-options.md b/doc/check-options.md index 0ca3b53814..d35e9575a2 100644 --- a/doc/check-options.md +++ b/doc/check-options.md @@ -10,6 +10,7 @@ - [aria-required-children](#aria-required-children) - [aria-required-parent](#aria-required-parent) - [aria-roledescription](#aria-roledescription) + - [autocomplete-valid](#autocomplete-valid) - [color-contrast](#color-contrast) - [page-has-heading-one](#page-has-heading-one) - [page-has-main](#page-has-main) @@ -237,6 +238,52 @@ Previously supported properties `validTreeRowAttrs` is no longer available. `inv +### autocomplete-valid + + + + + + + + + + + + + + + + + + + + + +
OptionDefaultDescription
+ stateTerms + +
[
+  'none',
+  'false',
+  'true',
+  'disabled',
+  'enabled',
+  'undefined',
+  'null',
+]
+
List of allowed autocomplete state terms other than "on" and "off."
+ ignoredValues + +
[
+  'text',
+  'pronouns',
+  'gender',
+  'message',
+  'content'
+]
+
List of autocomplete values that are technically invalid but will be ignored as they may not necessarily cause accessibility problems
+ ### color-contrast | Option | Default | Description | diff --git a/doc/projects.md b/doc/projects.md index 5dcd800666..c30d8098db 100644 --- a/doc/projects.md +++ b/doc/projects.md @@ -59,3 +59,4 @@ Add your project/integration to this file and submit a pull request. 1. [mitm-play](https://github.com/mitmplay/mitm-play) 1. [Sitebulb](https://sitebulb.com/hints/accessibility/) 1. [Webatool](https://github.com/balajihambeere/webatool) +1. [A11y Audit Elixir](https://github.com/angelikatyborska/a11y-audit-elixir) diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index 75787363d5..cd10e730b6 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -14,148 +14,149 @@ ## WCAG 2.0 Level A & AA Rules -| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | -| :------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------- | :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [area-alt](https://dequeuniversity.com/rules/axe/4.9/area-alt?application=RuleDescription) | Ensures <area> elements of image maps have alternate text | Critical | cat.text-alternatives, wcag2a, wcag244, wcag412, section508, section508.22.a, TTv5, TT6.a, EN-301-549, EN-9.2.4.4, EN-9.4.1.2, ACT | failure, needs review | [c487ae](https://act-rules.github.io/rules/c487ae) | -| [aria-allowed-attr](https://dequeuniversity.com/rules/axe/4.9/aria-allowed-attr?application=RuleDescription) | Ensures an element's role supports its ARIA attributes | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | [5c01ea](https://act-rules.github.io/rules/5c01ea) | -| [aria-braille-equivalent](https://dequeuniversity.com/rules/axe/4.9/aria-braille-equivalent?application=RuleDescription) | Ensure aria-braillelabel and aria-brailleroledescription have a non-braille equivalent | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | needs review | | -| [aria-command-name](https://dequeuniversity.com/rules/axe/4.9/aria-command-name?application=RuleDescription) | Ensures every ARIA button, link and menuitem has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [97a4e1](https://act-rules.github.io/rules/97a4e1) | -| [aria-conditional-attr](https://dequeuniversity.com/rules/axe/4.9/aria-conditional-attr?application=RuleDescription) | Ensures ARIA attributes are used as described in the specification of the element's role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [5c01ea](https://act-rules.github.io/rules/5c01ea) | -| [aria-deprecated-role](https://dequeuniversity.com/rules/axe/4.9/aria-deprecated-role?application=RuleDescription) | Ensures elements do not use deprecated roles | Minor | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [674b10](https://act-rules.github.io/rules/674b10) | -| [aria-hidden-body](https://dequeuniversity.com/rules/axe/4.9/aria-hidden-body?application=RuleDescription) | Ensures aria-hidden="true" is not present on the document body. | Critical | cat.aria, wcag2a, wcag131, wcag412, EN-301-549, EN-9.1.3.1, EN-9.4.1.2 | failure | | -| [aria-hidden-focus](https://dequeuniversity.com/rules/axe/4.9/aria-hidden-focus?application=RuleDescription) | Ensures aria-hidden elements are not focusable nor contain focusable elements | Serious | cat.name-role-value, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2 | failure, needs review | [6cfa84](https://act-rules.github.io/rules/6cfa84) | -| [aria-input-field-name](https://dequeuniversity.com/rules/axe/4.9/aria-input-field-name?application=RuleDescription) | Ensures every ARIA input field has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | -| [aria-meter-name](https://dequeuniversity.com/rules/axe/4.9/aria-meter-name?application=RuleDescription) | Ensures every ARIA meter node has an accessible name | Serious | cat.aria, wcag2a, wcag111, EN-301-549, EN-9.1.1.1 | failure, needs review | | -| [aria-progressbar-name](https://dequeuniversity.com/rules/axe/4.9/aria-progressbar-name?application=RuleDescription) | Ensures every ARIA progressbar node has an accessible name | Serious | cat.aria, wcag2a, wcag111, EN-301-549, EN-9.1.1.1 | failure, needs review | | -| [aria-prohibited-attr](https://dequeuniversity.com/rules/axe/4.9/aria-prohibited-attr?application=RuleDescription) | Ensures ARIA attributes are not prohibited for an element's role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | [5c01ea](https://act-rules.github.io/rules/5c01ea) | -| [aria-required-attr](https://dequeuniversity.com/rules/axe/4.9/aria-required-attr?application=RuleDescription) | Ensures elements with ARIA roles have all required ARIA attributes | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [4e8ab6](https://act-rules.github.io/rules/4e8ab6) | -| [aria-required-children](https://dequeuniversity.com/rules/axe/4.9/aria-required-children?application=RuleDescription) | Ensures elements with an ARIA role that require child roles contain them | Critical | cat.aria, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure, needs review | [bc4a75](https://act-rules.github.io/rules/bc4a75), [ff89c9](https://act-rules.github.io/rules/ff89c9) | -| [aria-required-parent](https://dequeuniversity.com/rules/axe/4.9/aria-required-parent?application=RuleDescription) | Ensures elements with an ARIA role that require parent roles are contained by them | Critical | cat.aria, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | [ff89c9](https://act-rules.github.io/rules/ff89c9) | -| [aria-roles](https://dequeuniversity.com/rules/axe/4.9/aria-roles?application=RuleDescription) | Ensures all elements with a role attribute use a valid value | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [674b10](https://act-rules.github.io/rules/674b10) | -| [aria-toggle-field-name](https://dequeuniversity.com/rules/axe/4.9/aria-toggle-field-name?application=RuleDescription) | Ensures every ARIA toggle field has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | -| [aria-tooltip-name](https://dequeuniversity.com/rules/axe/4.9/aria-tooltip-name?application=RuleDescription) | Ensures every ARIA tooltip node has an accessible name | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | | -| [aria-valid-attr-value](https://dequeuniversity.com/rules/axe/4.9/aria-valid-attr-value?application=RuleDescription) | Ensures all ARIA attributes have valid values | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | [6a7281](https://act-rules.github.io/rules/6a7281) | -| [aria-valid-attr](https://dequeuniversity.com/rules/axe/4.9/aria-valid-attr?application=RuleDescription) | Ensures attributes that begin with aria- are valid ARIA attributes | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [5f99a7](https://act-rules.github.io/rules/5f99a7) | -| [blink](https://dequeuniversity.com/rules/axe/4.9/blink?application=RuleDescription) | Ensures <blink> elements are not used | Serious | cat.time-and-media, wcag2a, wcag222, section508, section508.22.j, TTv5, TT2.b, EN-301-549, EN-9.2.2.2 | failure | | -| [button-name](https://dequeuniversity.com/rules/axe/4.9/button-name?application=RuleDescription) | Ensures buttons have discernible text | Critical | cat.name-role-value, wcag2a, wcag412, section508, section508.22.a, TTv5, TT6.a, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [97a4e1](https://act-rules.github.io/rules/97a4e1), [m6b1q3](https://act-rules.github.io/rules/m6b1q3) | -| [bypass](https://dequeuniversity.com/rules/axe/4.9/bypass?application=RuleDescription) | Ensures each page has at least one mechanism for a user to bypass navigation and jump straight to the content | Serious | cat.keyboard, wcag2a, wcag241, section508, section508.22.o, TTv5, TT9.a, EN-301-549, EN-9.2.4.1 | needs review | [cf77f2](https://act-rules.github.io/rules/cf77f2), [047fe0](https://act-rules.github.io/rules/047fe0), [b40fd1](https://act-rules.github.io/rules/b40fd1), [3e12e1](https://act-rules.github.io/rules/3e12e1), [ye5d6e](https://act-rules.github.io/rules/ye5d6e) | -| [color-contrast](https://dequeuniversity.com/rules/axe/4.9/color-contrast?application=RuleDescription) | Ensures the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds | Serious | cat.color, wcag2aa, wcag143, TTv5, TT13.c, EN-301-549, EN-9.1.4.3, ACT | failure, needs review | [afw4f7](https://act-rules.github.io/rules/afw4f7), [09o5cg](https://act-rules.github.io/rules/09o5cg) | -| [definition-list](https://dequeuniversity.com/rules/axe/4.9/definition-list?application=RuleDescription) | Ensures <dl> elements are structured correctly | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | -| [dlitem](https://dequeuniversity.com/rules/axe/4.9/dlitem?application=RuleDescription) | Ensures <dt> and <dd> elements are contained by a <dl> | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | -| [document-title](https://dequeuniversity.com/rules/axe/4.9/document-title?application=RuleDescription) | Ensures each HTML document contains a non-empty <title> element | Serious | cat.text-alternatives, wcag2a, wcag242, TTv5, TT12.a, EN-301-549, EN-9.2.4.2, ACT | failure | [2779a5](https://act-rules.github.io/rules/2779a5) | -| [duplicate-id-aria](https://dequeuniversity.com/rules/axe/4.9/duplicate-id-aria?application=RuleDescription) | Ensures every id attribute value used in ARIA and in labels is unique | Critical | cat.parsing, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | needs review | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | -| [form-field-multiple-labels](https://dequeuniversity.com/rules/axe/4.9/form-field-multiple-labels?application=RuleDescription) | Ensures form field does not have multiple label elements | Moderate | cat.forms, wcag2a, wcag332, TTv5, TT5.c, EN-301-549, EN-9.3.3.2 | needs review | | -| [frame-focusable-content](https://dequeuniversity.com/rules/axe/4.9/frame-focusable-content?application=RuleDescription) | Ensures <frame> and <iframe> elements with focusable content do not have tabindex=-1 | Serious | cat.keyboard, wcag2a, wcag211, TTv5, TT4.a, EN-301-549, EN-9.2.1.1 | failure, needs review | [akn7bn](https://act-rules.github.io/rules/akn7bn) | -| [frame-title-unique](https://dequeuniversity.com/rules/axe/4.9/frame-title-unique?application=RuleDescription) | Ensures <iframe> and <frame> elements contain a unique title attribute | Serious | cat.text-alternatives, wcag2a, wcag412, TTv5, TT12.d, EN-301-549, EN-9.4.1.2 | needs review | [4b1c6c](https://act-rules.github.io/rules/4b1c6c) | -| [frame-title](https://dequeuniversity.com/rules/axe/4.9/frame-title?application=RuleDescription) | Ensures <iframe> and <frame> elements have an accessible name | Serious | cat.text-alternatives, wcag2a, wcag412, section508, section508.22.i, TTv5, TT12.d, EN-301-549, EN-9.4.1.2 | failure, needs review | [cae760](https://act-rules.github.io/rules/cae760) | -| [html-has-lang](https://dequeuniversity.com/rules/axe/4.9/html-has-lang?application=RuleDescription) | Ensures every HTML document has a lang attribute | Serious | cat.language, wcag2a, wcag311, TTv5, TT11.a, EN-301-549, EN-9.3.1.1, ACT | failure | [b5c3f8](https://act-rules.github.io/rules/b5c3f8) | -| [html-lang-valid](https://dequeuniversity.com/rules/axe/4.9/html-lang-valid?application=RuleDescription) | Ensures the lang attribute of the <html> element has a valid value | Serious | cat.language, wcag2a, wcag311, TTv5, TT11.a, EN-301-549, EN-9.3.1.1, ACT | failure | [bf051a](https://act-rules.github.io/rules/bf051a) | -| [html-xml-lang-mismatch](https://dequeuniversity.com/rules/axe/4.9/html-xml-lang-mismatch?application=RuleDescription) | Ensure that HTML elements with both valid lang and xml:lang attributes agree on the base language of the page | Moderate | cat.language, wcag2a, wcag311, EN-301-549, EN-9.3.1.1, ACT | failure | [5b7ae0](https://act-rules.github.io/rules/5b7ae0) | -| [image-alt](https://dequeuniversity.com/rules/axe/4.9/image-alt?application=RuleDescription) | Ensures <img> elements have alternate text or a role of none or presentation | Critical | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, TTv5, TT7.a, TT7.b, EN-301-549, EN-9.1.1.1, ACT | failure, needs review | [23a2a8](https://act-rules.github.io/rules/23a2a8) | -| [input-button-name](https://dequeuniversity.com/rules/axe/4.9/input-button-name?application=RuleDescription) | Ensures input buttons have discernible text | Critical | cat.name-role-value, wcag2a, wcag412, section508, section508.22.a, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [97a4e1](https://act-rules.github.io/rules/97a4e1) | -| [input-image-alt](https://dequeuniversity.com/rules/axe/4.9/input-image-alt?application=RuleDescription) | Ensures <input type="image"> elements have alternate text | Critical | cat.text-alternatives, wcag2a, wcag111, wcag412, section508, section508.22.a, TTv5, TT7.a, EN-301-549, EN-9.1.1.1, EN-9.4.1.2, ACT | failure, needs review | [59796f](https://act-rules.github.io/rules/59796f) | -| [label](https://dequeuniversity.com/rules/axe/4.9/label?application=RuleDescription) | Ensures every form element has a label | Critical | cat.forms, wcag2a, wcag412, section508, section508.22.n, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | -| [link-in-text-block](https://dequeuniversity.com/rules/axe/4.9/link-in-text-block?application=RuleDescription) | Ensure links are distinguished from surrounding text in a way that does not rely on color | Serious | cat.color, wcag2a, wcag141, TTv5, TT13.a, EN-301-549, EN-9.1.4.1 | failure, needs review | | -| [link-name](https://dequeuniversity.com/rules/axe/4.9/link-name?application=RuleDescription) | Ensures links have discernible text | Serious | cat.name-role-value, wcag2a, wcag244, wcag412, section508, section508.22.a, TTv5, TT6.a, EN-301-549, EN-9.2.4.4, EN-9.4.1.2, ACT | failure, needs review | [c487ae](https://act-rules.github.io/rules/c487ae) | -| [list](https://dequeuniversity.com/rules/axe/4.9/list?application=RuleDescription) | Ensures that lists are structured correctly | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | -| [listitem](https://dequeuniversity.com/rules/axe/4.9/listitem?application=RuleDescription) | Ensures <li> elements are used semantically | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | -| [marquee](https://dequeuniversity.com/rules/axe/4.9/marquee?application=RuleDescription) | Ensures <marquee> elements are not used | Serious | cat.parsing, wcag2a, wcag222, TTv5, TT2.b, EN-301-549, EN-9.2.2.2 | failure | | -| [meta-refresh](https://dequeuniversity.com/rules/axe/4.9/meta-refresh?application=RuleDescription) | Ensures <meta http-equiv="refresh"> is not used for delayed refresh | Critical | cat.time-and-media, wcag2a, wcag221, TTv5, TT8.a, EN-301-549, EN-9.2.2.1 | failure | [bc659a](https://act-rules.github.io/rules/bc659a), [bisz58](https://act-rules.github.io/rules/bisz58) | -| [meta-viewport](https://dequeuniversity.com/rules/axe/4.9/meta-viewport?application=RuleDescription) | Ensures <meta name="viewport"> does not disable text scaling and zooming | Critical | cat.sensory-and-visual-cues, wcag2aa, wcag144, EN-301-549, EN-9.1.4.4, ACT | failure | [b4f0c3](https://act-rules.github.io/rules/b4f0c3) | -| [nested-interactive](https://dequeuniversity.com/rules/axe/4.9/nested-interactive?application=RuleDescription) | Ensures interactive controls are not nested as they are not always announced by screen readers or can cause focus problems for assistive technologies | Serious | cat.keyboard, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2 | failure, needs review | [307n5z](https://act-rules.github.io/rules/307n5z) | -| [no-autoplay-audio](https://dequeuniversity.com/rules/axe/4.9/no-autoplay-audio?application=RuleDescription) | Ensures <video> or <audio> elements do not autoplay audio for more than 3 seconds without a control mechanism to stop or mute the audio | Moderate | cat.time-and-media, wcag2a, wcag142, TTv5, TT2.a, EN-301-549, EN-9.1.4.2, ACT | needs review | [80f0bf](https://act-rules.github.io/rules/80f0bf) | -| [object-alt](https://dequeuniversity.com/rules/axe/4.9/object-alt?application=RuleDescription) | Ensures <object> elements have alternate text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, EN-301-549, EN-9.1.1.1 | failure, needs review | [8fc3b6](https://act-rules.github.io/rules/8fc3b6) | -| [role-img-alt](https://dequeuniversity.com/rules/axe/4.9/role-img-alt?application=RuleDescription) | Ensures [role="img"] elements have alternate text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, TTv5, TT7.a, EN-301-549, EN-9.1.1.1, ACT | failure, needs review | [23a2a8](https://act-rules.github.io/rules/23a2a8) | -| [scrollable-region-focusable](https://dequeuniversity.com/rules/axe/4.9/scrollable-region-focusable?application=RuleDescription) | Ensure elements that have scrollable content are accessible by keyboard | Serious | cat.keyboard, wcag2a, wcag211, wcag213, TTv5, TT4.a, EN-301-549, EN-9.2.1.1, EN-9.2.1.3 | failure | [0ssw9k](https://act-rules.github.io/rules/0ssw9k) | -| [select-name](https://dequeuniversity.com/rules/axe/4.9/select-name?application=RuleDescription) | Ensures select element has an accessible name | Critical | cat.forms, wcag2a, wcag412, section508, section508.22.n, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | -| [server-side-image-map](https://dequeuniversity.com/rules/axe/4.9/server-side-image-map?application=RuleDescription) | Ensures that server-side image maps are not used | Minor | cat.text-alternatives, wcag2a, wcag211, section508, section508.22.f, TTv5, TT4.a, EN-301-549, EN-9.2.1.1 | needs review | | -| [svg-img-alt](https://dequeuniversity.com/rules/axe/4.9/svg-img-alt?application=RuleDescription) | Ensures <svg> elements with an img, graphics-document or graphics-symbol role have an accessible text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, TTv5, TT7.a, EN-301-549, EN-9.1.1.1, ACT | failure, needs review | [7d6734](https://act-rules.github.io/rules/7d6734) | -| [td-headers-attr](https://dequeuniversity.com/rules/axe/4.9/td-headers-attr?application=RuleDescription) | Ensure that each cell in a table that uses the headers attribute refers only to other cells in that table | Serious | cat.tables, wcag2a, wcag131, section508, section508.22.g, TTv5, TT14.b, EN-301-549, EN-9.1.3.1 | failure, needs review | [a25f45](https://act-rules.github.io/rules/a25f45) | -| [th-has-data-cells](https://dequeuniversity.com/rules/axe/4.9/th-has-data-cells?application=RuleDescription) | Ensure that <th> elements and elements with role=columnheader/rowheader have data cells they describe | Serious | cat.tables, wcag2a, wcag131, section508, section508.22.g, TTv5, TT14.b, EN-301-549, EN-9.1.3.1 | failure, needs review | [d0f69e](https://act-rules.github.io/rules/d0f69e) | -| [valid-lang](https://dequeuniversity.com/rules/axe/4.9/valid-lang?application=RuleDescription) | Ensures lang attributes have valid values | Serious | cat.language, wcag2aa, wcag312, TTv5, TT11.b, EN-301-549, EN-9.3.1.2, ACT | failure | [de46e4](https://act-rules.github.io/rules/de46e4) | -| [video-caption](https://dequeuniversity.com/rules/axe/4.9/video-caption?application=RuleDescription) | Ensures <video> elements have captions | Critical | cat.text-alternatives, wcag2a, wcag122, section508, section508.22.a, TTv5, TT17.a, EN-301-549, EN-9.1.2.2 | needs review | [eac66b](https://act-rules.github.io/rules/eac66b) | +| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | +| :-------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------- | :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [area-alt](https://dequeuniversity.com/rules/axe/4.10/area-alt?application=RuleDescription) | Ensure <area> elements of image maps have alternate text | Critical | cat.text-alternatives, wcag2a, wcag244, wcag412, section508, section508.22.a, TTv5, TT6.a, EN-301-549, EN-9.2.4.4, EN-9.4.1.2, ACT | failure, needs review | [c487ae](https://act-rules.github.io/rules/c487ae) | +| [aria-allowed-attr](https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr?application=RuleDescription) | Ensure an element's role supports its ARIA attributes | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | [5c01ea](https://act-rules.github.io/rules/5c01ea) | +| [aria-braille-equivalent](https://dequeuniversity.com/rules/axe/4.10/aria-braille-equivalent?application=RuleDescription) | Ensure aria-braillelabel and aria-brailleroledescription have a non-braille equivalent | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | needs review | | +| [aria-command-name](https://dequeuniversity.com/rules/axe/4.10/aria-command-name?application=RuleDescription) | Ensure every ARIA button, link and menuitem has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [97a4e1](https://act-rules.github.io/rules/97a4e1) | +| [aria-conditional-attr](https://dequeuniversity.com/rules/axe/4.10/aria-conditional-attr?application=RuleDescription) | Ensure ARIA attributes are used as described in the specification of the element's role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [5c01ea](https://act-rules.github.io/rules/5c01ea) | +| [aria-deprecated-role](https://dequeuniversity.com/rules/axe/4.10/aria-deprecated-role?application=RuleDescription) | Ensure elements do not use deprecated roles | Minor | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [674b10](https://act-rules.github.io/rules/674b10) | +| [aria-hidden-body](https://dequeuniversity.com/rules/axe/4.10/aria-hidden-body?application=RuleDescription) | Ensure aria-hidden="true" is not present on the document body. | Critical | cat.aria, wcag2a, wcag131, wcag412, EN-301-549, EN-9.1.3.1, EN-9.4.1.2 | failure | | +| [aria-hidden-focus](https://dequeuniversity.com/rules/axe/4.10/aria-hidden-focus?application=RuleDescription) | Ensure aria-hidden elements are not focusable nor contain focusable elements | Serious | cat.name-role-value, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2 | failure, needs review | [6cfa84](https://act-rules.github.io/rules/6cfa84) | +| [aria-input-field-name](https://dequeuniversity.com/rules/axe/4.10/aria-input-field-name?application=RuleDescription) | Ensure every ARIA input field has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | +| [aria-meter-name](https://dequeuniversity.com/rules/axe/4.10/aria-meter-name?application=RuleDescription) | Ensure every ARIA meter node has an accessible name | Serious | cat.aria, wcag2a, wcag111, EN-301-549, EN-9.1.1.1 | failure, needs review | | +| [aria-progressbar-name](https://dequeuniversity.com/rules/axe/4.10/aria-progressbar-name?application=RuleDescription) | Ensure every ARIA progressbar node has an accessible name | Serious | cat.aria, wcag2a, wcag111, EN-301-549, EN-9.1.1.1 | failure, needs review | | +| [aria-prohibited-attr](https://dequeuniversity.com/rules/axe/4.10/aria-prohibited-attr?application=RuleDescription) | Ensure ARIA attributes are not prohibited for an element's role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | [5c01ea](https://act-rules.github.io/rules/5c01ea) | +| [aria-required-attr](https://dequeuniversity.com/rules/axe/4.10/aria-required-attr?application=RuleDescription) | Ensure elements with ARIA roles have all required ARIA attributes | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [4e8ab6](https://act-rules.github.io/rules/4e8ab6) | +| [aria-required-children](https://dequeuniversity.com/rules/axe/4.10/aria-required-children?application=RuleDescription) | Ensure elements with an ARIA role that require child roles contain them | Critical | cat.aria, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure, needs review | [bc4a75](https://act-rules.github.io/rules/bc4a75), [ff89c9](https://act-rules.github.io/rules/ff89c9) | +| [aria-required-parent](https://dequeuniversity.com/rules/axe/4.10/aria-required-parent?application=RuleDescription) | Ensure elements with an ARIA role that require parent roles are contained by them | Critical | cat.aria, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | [ff89c9](https://act-rules.github.io/rules/ff89c9) | +| [aria-roles](https://dequeuniversity.com/rules/axe/4.10/aria-roles?application=RuleDescription) | Ensure all elements with a role attribute use a valid value | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [674b10](https://act-rules.github.io/rules/674b10) | +| [aria-toggle-field-name](https://dequeuniversity.com/rules/axe/4.10/aria-toggle-field-name?application=RuleDescription) | Ensure every ARIA toggle field has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | +| [aria-tooltip-name](https://dequeuniversity.com/rules/axe/4.10/aria-tooltip-name?application=RuleDescription) | Ensure every ARIA tooltip node has an accessible name | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | | +| [aria-valid-attr-value](https://dequeuniversity.com/rules/axe/4.10/aria-valid-attr-value?application=RuleDescription) | Ensure all ARIA attributes have valid values | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | [6a7281](https://act-rules.github.io/rules/6a7281) | +| [aria-valid-attr](https://dequeuniversity.com/rules/axe/4.10/aria-valid-attr?application=RuleDescription) | Ensure attributes that begin with aria- are valid ARIA attributes | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [5f99a7](https://act-rules.github.io/rules/5f99a7) | +| [blink](https://dequeuniversity.com/rules/axe/4.10/blink?application=RuleDescription) | Ensure <blink> elements are not used | Serious | cat.time-and-media, wcag2a, wcag222, section508, section508.22.j, TTv5, TT2.b, EN-301-549, EN-9.2.2.2 | failure | | +| [button-name](https://dequeuniversity.com/rules/axe/4.10/button-name?application=RuleDescription) | Ensure buttons have discernible text | Critical | cat.name-role-value, wcag2a, wcag412, section508, section508.22.a, TTv5, TT6.a, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [97a4e1](https://act-rules.github.io/rules/97a4e1), [m6b1q3](https://act-rules.github.io/rules/m6b1q3) | +| [bypass](https://dequeuniversity.com/rules/axe/4.10/bypass?application=RuleDescription) | Ensure each page has at least one mechanism for a user to bypass navigation and jump straight to the content | Serious | cat.keyboard, wcag2a, wcag241, section508, section508.22.o, TTv5, TT9.a, EN-301-549, EN-9.2.4.1 | needs review | [cf77f2](https://act-rules.github.io/rules/cf77f2), [047fe0](https://act-rules.github.io/rules/047fe0), [b40fd1](https://act-rules.github.io/rules/b40fd1), [3e12e1](https://act-rules.github.io/rules/3e12e1), [ye5d6e](https://act-rules.github.io/rules/ye5d6e) | +| [color-contrast](https://dequeuniversity.com/rules/axe/4.10/color-contrast?application=RuleDescription) | Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds | Serious | cat.color, wcag2aa, wcag143, TTv5, TT13.c, EN-301-549, EN-9.1.4.3, ACT | failure, needs review | [afw4f7](https://act-rules.github.io/rules/afw4f7), [09o5cg](https://act-rules.github.io/rules/09o5cg) | +| [definition-list](https://dequeuniversity.com/rules/axe/4.10/definition-list?application=RuleDescription) | Ensure <dl> elements are structured correctly | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | +| [dlitem](https://dequeuniversity.com/rules/axe/4.10/dlitem?application=RuleDescription) | Ensure <dt> and <dd> elements are contained by a <dl> | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | +| [document-title](https://dequeuniversity.com/rules/axe/4.10/document-title?application=RuleDescription) | Ensure each HTML document contains a non-empty <title> element | Serious | cat.text-alternatives, wcag2a, wcag242, TTv5, TT12.a, EN-301-549, EN-9.2.4.2, ACT | failure | [2779a5](https://act-rules.github.io/rules/2779a5) | +| [duplicate-id-aria](https://dequeuniversity.com/rules/axe/4.10/duplicate-id-aria?application=RuleDescription) | Ensure every id attribute value used in ARIA and in labels is unique | Critical | cat.parsing, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | needs review | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | +| [form-field-multiple-labels](https://dequeuniversity.com/rules/axe/4.10/form-field-multiple-labels?application=RuleDescription) | Ensure form field does not have multiple label elements | Moderate | cat.forms, wcag2a, wcag332, TTv5, TT5.c, EN-301-549, EN-9.3.3.2 | needs review | | +| [frame-focusable-content](https://dequeuniversity.com/rules/axe/4.10/frame-focusable-content?application=RuleDescription) | Ensure <frame> and <iframe> elements with focusable content do not have tabindex=-1 | Serious | cat.keyboard, wcag2a, wcag211, TTv5, TT4.a, EN-301-549, EN-9.2.1.1 | failure, needs review | [akn7bn](https://act-rules.github.io/rules/akn7bn) | +| [frame-title-unique](https://dequeuniversity.com/rules/axe/4.10/frame-title-unique?application=RuleDescription) | Ensure <iframe> and <frame> elements contain a unique title attribute | Serious | cat.text-alternatives, wcag2a, wcag412, TTv5, TT12.d, EN-301-549, EN-9.4.1.2 | needs review | [4b1c6c](https://act-rules.github.io/rules/4b1c6c) | +| [frame-title](https://dequeuniversity.com/rules/axe/4.10/frame-title?application=RuleDescription) | Ensure <iframe> and <frame> elements have an accessible name | Serious | cat.text-alternatives, wcag2a, wcag412, section508, section508.22.i, TTv5, TT12.d, EN-301-549, EN-9.4.1.2 | failure, needs review | [cae760](https://act-rules.github.io/rules/cae760) | +| [html-has-lang](https://dequeuniversity.com/rules/axe/4.10/html-has-lang?application=RuleDescription) | Ensure every HTML document has a lang attribute | Serious | cat.language, wcag2a, wcag311, TTv5, TT11.a, EN-301-549, EN-9.3.1.1, ACT | failure | [b5c3f8](https://act-rules.github.io/rules/b5c3f8) | +| [html-lang-valid](https://dequeuniversity.com/rules/axe/4.10/html-lang-valid?application=RuleDescription) | Ensure the lang attribute of the <html> element has a valid value | Serious | cat.language, wcag2a, wcag311, TTv5, TT11.a, EN-301-549, EN-9.3.1.1, ACT | failure | [bf051a](https://act-rules.github.io/rules/bf051a) | +| [html-xml-lang-mismatch](https://dequeuniversity.com/rules/axe/4.10/html-xml-lang-mismatch?application=RuleDescription) | Ensure that HTML elements with both valid lang and xml:lang attributes agree on the base language of the page | Moderate | cat.language, wcag2a, wcag311, EN-301-549, EN-9.3.1.1, ACT | failure | [5b7ae0](https://act-rules.github.io/rules/5b7ae0) | +| [image-alt](https://dequeuniversity.com/rules/axe/4.10/image-alt?application=RuleDescription) | Ensure <img> elements have alternate text or a role of none or presentation | Critical | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, TTv5, TT7.a, TT7.b, EN-301-549, EN-9.1.1.1, ACT | failure, needs review | [23a2a8](https://act-rules.github.io/rules/23a2a8) | +| [input-button-name](https://dequeuniversity.com/rules/axe/4.10/input-button-name?application=RuleDescription) | Ensure input buttons have discernible text | Critical | cat.name-role-value, wcag2a, wcag412, section508, section508.22.a, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [97a4e1](https://act-rules.github.io/rules/97a4e1) | +| [input-image-alt](https://dequeuniversity.com/rules/axe/4.10/input-image-alt?application=RuleDescription) | Ensure <input type="image"> elements have alternate text | Critical | cat.text-alternatives, wcag2a, wcag111, wcag412, section508, section508.22.a, TTv5, TT7.a, EN-301-549, EN-9.1.1.1, EN-9.4.1.2, ACT | failure, needs review | [59796f](https://act-rules.github.io/rules/59796f) | +| [label](https://dequeuniversity.com/rules/axe/4.10/label?application=RuleDescription) | Ensure every form element has a label | Critical | cat.forms, wcag2a, wcag412, section508, section508.22.n, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | +| [link-in-text-block](https://dequeuniversity.com/rules/axe/4.10/link-in-text-block?application=RuleDescription) | Ensure links are distinguished from surrounding text in a way that does not rely on color | Serious | cat.color, wcag2a, wcag141, TTv5, TT13.a, EN-301-549, EN-9.1.4.1 | failure, needs review | | +| [link-name](https://dequeuniversity.com/rules/axe/4.10/link-name?application=RuleDescription) | Ensure links have discernible text | Serious | cat.name-role-value, wcag2a, wcag244, wcag412, section508, section508.22.a, TTv5, TT6.a, EN-301-549, EN-9.2.4.4, EN-9.4.1.2, ACT | failure, needs review | [c487ae](https://act-rules.github.io/rules/c487ae) | +| [list](https://dequeuniversity.com/rules/axe/4.10/list?application=RuleDescription) | Ensure that lists are structured correctly | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | +| [listitem](https://dequeuniversity.com/rules/axe/4.10/listitem?application=RuleDescription) | Ensure <li> elements are used semantically | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | +| [marquee](https://dequeuniversity.com/rules/axe/4.10/marquee?application=RuleDescription) | Ensure <marquee> elements are not used | Serious | cat.parsing, wcag2a, wcag222, TTv5, TT2.b, EN-301-549, EN-9.2.2.2 | failure | | +| [meta-refresh](https://dequeuniversity.com/rules/axe/4.10/meta-refresh?application=RuleDescription) | Ensure <meta http-equiv="refresh"> is not used for delayed refresh | Critical | cat.time-and-media, wcag2a, wcag221, TTv5, TT8.a, EN-301-549, EN-9.2.2.1 | failure | [bc659a](https://act-rules.github.io/rules/bc659a), [bisz58](https://act-rules.github.io/rules/bisz58) | +| [meta-viewport](https://dequeuniversity.com/rules/axe/4.10/meta-viewport?application=RuleDescription) | Ensure <meta name="viewport"> does not disable text scaling and zooming | Critical | cat.sensory-and-visual-cues, wcag2aa, wcag144, EN-301-549, EN-9.1.4.4, ACT | failure | [b4f0c3](https://act-rules.github.io/rules/b4f0c3) | +| [nested-interactive](https://dequeuniversity.com/rules/axe/4.10/nested-interactive?application=RuleDescription) | Ensure interactive controls are not nested as they are not always announced by screen readers or can cause focus problems for assistive technologies | Serious | cat.keyboard, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2 | failure, needs review | [307n5z](https://act-rules.github.io/rules/307n5z) | +| [no-autoplay-audio](https://dequeuniversity.com/rules/axe/4.10/no-autoplay-audio?application=RuleDescription) | Ensure <video> or <audio> elements do not autoplay audio for more than 3 seconds without a control mechanism to stop or mute the audio | Moderate | cat.time-and-media, wcag2a, wcag142, TTv5, TT2.a, EN-301-549, EN-9.1.4.2, ACT | needs review | [80f0bf](https://act-rules.github.io/rules/80f0bf) | +| [object-alt](https://dequeuniversity.com/rules/axe/4.10/object-alt?application=RuleDescription) | Ensure <object> elements have alternate text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, EN-301-549, EN-9.1.1.1 | failure, needs review | [8fc3b6](https://act-rules.github.io/rules/8fc3b6) | +| [role-img-alt](https://dequeuniversity.com/rules/axe/4.10/role-img-alt?application=RuleDescription) | Ensure [role="img"] elements have alternate text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, TTv5, TT7.a, EN-301-549, EN-9.1.1.1, ACT | failure, needs review | [23a2a8](https://act-rules.github.io/rules/23a2a8) | +| [scrollable-region-focusable](https://dequeuniversity.com/rules/axe/4.10/scrollable-region-focusable?application=RuleDescription) | Ensure elements that have scrollable content are accessible by keyboard | Serious | cat.keyboard, wcag2a, wcag211, wcag213, TTv5, TT4.a, EN-301-549, EN-9.2.1.1, EN-9.2.1.3 | failure | [0ssw9k](https://act-rules.github.io/rules/0ssw9k) | +| [select-name](https://dequeuniversity.com/rules/axe/4.10/select-name?application=RuleDescription) | Ensure select element has an accessible name | Critical | cat.forms, wcag2a, wcag412, section508, section508.22.n, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | +| [server-side-image-map](https://dequeuniversity.com/rules/axe/4.10/server-side-image-map?application=RuleDescription) | Ensure that server-side image maps are not used | Minor | cat.text-alternatives, wcag2a, wcag211, section508, section508.22.f, TTv5, TT4.a, EN-301-549, EN-9.2.1.1 | needs review | | +| [summary-name](https://dequeuniversity.com/rules/axe/4.10/summary-name?application=RuleDescription) | Ensure summary elements have discernible text | Serious | cat.name-role-value, wcag2a, wcag412, section508, section508.22.a, TTv5, TT6.a, EN-301-549, EN-9.4.1.2 | failure, needs review | | +| [svg-img-alt](https://dequeuniversity.com/rules/axe/4.10/svg-img-alt?application=RuleDescription) | Ensure <svg> elements with an img, graphics-document or graphics-symbol role have an accessible text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, TTv5, TT7.a, EN-301-549, EN-9.1.1.1, ACT | failure, needs review | [7d6734](https://act-rules.github.io/rules/7d6734) | +| [td-headers-attr](https://dequeuniversity.com/rules/axe/4.10/td-headers-attr?application=RuleDescription) | Ensure that each cell in a table that uses the headers attribute refers only to other cells in that table | Serious | cat.tables, wcag2a, wcag131, section508, section508.22.g, TTv5, TT14.b, EN-301-549, EN-9.1.3.1 | failure, needs review | [a25f45](https://act-rules.github.io/rules/a25f45) | +| [th-has-data-cells](https://dequeuniversity.com/rules/axe/4.10/th-has-data-cells?application=RuleDescription) | Ensure that <th> elements and elements with role=columnheader/rowheader have data cells they describe | Serious | cat.tables, wcag2a, wcag131, section508, section508.22.g, TTv5, TT14.b, EN-301-549, EN-9.1.3.1 | failure, needs review | [d0f69e](https://act-rules.github.io/rules/d0f69e) | +| [valid-lang](https://dequeuniversity.com/rules/axe/4.10/valid-lang?application=RuleDescription) | Ensure lang attributes have valid values | Serious | cat.language, wcag2aa, wcag312, TTv5, TT11.b, EN-301-549, EN-9.3.1.2, ACT | failure | [de46e4](https://act-rules.github.io/rules/de46e4) | +| [video-caption](https://dequeuniversity.com/rules/axe/4.10/video-caption?application=RuleDescription) | Ensure <video> elements have captions | Critical | cat.text-alternatives, wcag2a, wcag122, section508, section508.22.a, TTv5, TT17.a, EN-301-549, EN-9.1.2.2 | needs review | [eac66b](https://act-rules.github.io/rules/eac66b) | ## WCAG 2.1 Level A & AA Rules -| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | -| :----------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | :------ | :-------------------------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [autocomplete-valid](https://dequeuniversity.com/rules/axe/4.9/autocomplete-valid?application=RuleDescription) | Ensure the autocomplete attribute is correct and suitable for the form field | Serious | cat.forms, wcag21aa, wcag135, EN-301-549, EN-9.1.3.5, ACT | failure | [73f2c2](https://act-rules.github.io/rules/73f2c2) | -| [avoid-inline-spacing](https://dequeuniversity.com/rules/axe/4.9/avoid-inline-spacing?application=RuleDescription) | Ensure that text spacing set through style attributes can be adjusted with custom stylesheets | Serious | cat.structure, wcag21aa, wcag1412, EN-301-549, EN-9.1.4.12, ACT | failure | [24afc2](https://act-rules.github.io/rules/24afc2), [9e45ec](https://act-rules.github.io/rules/9e45ec), [78fd32](https://act-rules.github.io/rules/78fd32) | +| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | +| :------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------- | :------ | :-------------------------------------------------------------- | :------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [autocomplete-valid](https://dequeuniversity.com/rules/axe/4.10/autocomplete-valid?application=RuleDescription) | Ensure the autocomplete attribute is correct and suitable for the form field | Serious | cat.forms, wcag21aa, wcag135, EN-301-549, EN-9.1.3.5, ACT | failure, needs review | [73f2c2](https://act-rules.github.io/rules/73f2c2) | +| [avoid-inline-spacing](https://dequeuniversity.com/rules/axe/4.10/avoid-inline-spacing?application=RuleDescription) | Ensure that text spacing set through style attributes can be adjusted with custom stylesheets | Serious | cat.structure, wcag21aa, wcag1412, EN-301-549, EN-9.1.4.12, ACT | failure | [24afc2](https://act-rules.github.io/rules/24afc2), [9e45ec](https://act-rules.github.io/rules/9e45ec), [78fd32](https://act-rules.github.io/rules/78fd32) | ## WCAG 2.2 Level A & AA Rules These rules are disabled by default, until WCAG 2.2 is more widely adopted and required. -| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | -| :----------------------------------------------------------------------------------------------- | :------------------------------------------------- | :------ | :--------------------------------------------- | :------------------------- | :-------- | -| [target-size](https://dequeuniversity.com/rules/axe/4.9/target-size?application=RuleDescription) | Ensure touch target have sufficient size and space | Serious | cat.sensory-and-visual-cues, wcag22aa, wcag258 | failure, needs review | | +| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | +| :------------------------------------------------------------------------------------------------ | :-------------------------------------------------- | :------ | :--------------------------------------------- | :------------------------- | :-------- | +| [target-size](https://dequeuniversity.com/rules/axe/4.10/target-size?application=RuleDescription) | Ensure touch targets have sufficient size and space | Serious | cat.sensory-and-visual-cues, wcag22aa, wcag258 | failure, needs review | | ## Best Practices Rules Rules that do not necessarily conform to WCAG success criterion but are industry accepted practices that improve the user experience. -| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | -| :----------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :----------------------------------------- | :------------------------- | :------------------------------------------------- | -| [accesskeys](https://dequeuniversity.com/rules/axe/4.9/accesskeys?application=RuleDescription) | Ensures every accesskey attribute value is unique | Serious | cat.keyboard, best-practice | failure | | -| [aria-allowed-role](https://dequeuniversity.com/rules/axe/4.9/aria-allowed-role?application=RuleDescription) | Ensures role attribute has an appropriate value for the element | Minor | cat.aria, best-practice | failure, needs review | | -| [aria-dialog-name](https://dequeuniversity.com/rules/axe/4.9/aria-dialog-name?application=RuleDescription) | Ensures every ARIA dialog and alertdialog node has an accessible name | Serious | cat.aria, best-practice | failure, needs review | | -| [aria-text](https://dequeuniversity.com/rules/axe/4.9/aria-text?application=RuleDescription) | Ensures role="text" is used on elements with no focusable descendants | Serious | cat.aria, best-practice | failure, needs review | | -| [aria-treeitem-name](https://dequeuniversity.com/rules/axe/4.9/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.9/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.9/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.9/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.9/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.9/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.9/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 | | -| [landmark-banner-is-top-level](https://dequeuniversity.com/rules/axe/4.9/landmark-banner-is-top-level?application=RuleDescription) | Ensures the banner landmark is at top level | Moderate | cat.semantics, best-practice | failure | | -| [landmark-complementary-is-top-level](https://dequeuniversity.com/rules/axe/4.9/landmark-complementary-is-top-level?application=RuleDescription) | Ensures the complementary landmark or aside is at top level | Moderate | cat.semantics, best-practice | failure | | -| [landmark-contentinfo-is-top-level](https://dequeuniversity.com/rules/axe/4.9/landmark-contentinfo-is-top-level?application=RuleDescription) | Ensures the contentinfo landmark is at top level | Moderate | cat.semantics, best-practice | failure | | -| [landmark-main-is-top-level](https://dequeuniversity.com/rules/axe/4.9/landmark-main-is-top-level?application=RuleDescription) | Ensures the main landmark is at top level | Moderate | cat.semantics, best-practice | failure | | -| [landmark-no-duplicate-banner](https://dequeuniversity.com/rules/axe/4.9/landmark-no-duplicate-banner?application=RuleDescription) | Ensures the document has at most one banner landmark | Moderate | cat.semantics, best-practice | failure | | -| [landmark-no-duplicate-contentinfo](https://dequeuniversity.com/rules/axe/4.9/landmark-no-duplicate-contentinfo?application=RuleDescription) | Ensures the document has at most one contentinfo landmark | Moderate | cat.semantics, best-practice | failure | | -| [landmark-no-duplicate-main](https://dequeuniversity.com/rules/axe/4.9/landmark-no-duplicate-main?application=RuleDescription) | Ensures the document has at most one main landmark | Moderate | cat.semantics, best-practice | failure | | -| [landmark-one-main](https://dequeuniversity.com/rules/axe/4.9/landmark-one-main?application=RuleDescription) | Ensures the document has a main landmark | Moderate | cat.semantics, best-practice | failure | | -| [landmark-unique](https://dequeuniversity.com/rules/axe/4.9/landmark-unique?application=RuleDescription) | Landmarks should have a unique role or role/label/title (i.e. accessible name) combination | Moderate | cat.semantics, best-practice | failure | | -| [meta-viewport-large](https://dequeuniversity.com/rules/axe/4.9/meta-viewport-large?application=RuleDescription) | Ensures <meta name="viewport"> can scale a significant amount | Minor | cat.sensory-and-visual-cues, best-practice | failure | | -| [page-has-heading-one](https://dequeuniversity.com/rules/axe/4.9/page-has-heading-one?application=RuleDescription) | Ensure that the page, or at least one of its frames contains a level-one heading | Moderate | cat.semantics, best-practice | failure | | -| [presentation-role-conflict](https://dequeuniversity.com/rules/axe/4.9/presentation-role-conflict?application=RuleDescription) | Elements marked as presentational should not have global ARIA or tabindex to ensure all screen readers ignore them | Minor | cat.aria, best-practice, ACT | failure | [46ca7f](https://act-rules.github.io/rules/46ca7f) | -| [region](https://dequeuniversity.com/rules/axe/4.9/region?application=RuleDescription) | Ensures all page content is contained by landmarks | Moderate | cat.keyboard, best-practice | failure | | -| [scope-attr-valid](https://dequeuniversity.com/rules/axe/4.9/scope-attr-valid?application=RuleDescription) | Ensures the scope attribute is used correctly on tables | Moderate | cat.tables, best-practice | failure | | -| [skip-link](https://dequeuniversity.com/rules/axe/4.9/skip-link?application=RuleDescription) | Ensure all skip links have a focusable target | Moderate | cat.keyboard, best-practice | failure, needs review | | -| [tabindex](https://dequeuniversity.com/rules/axe/4.9/tabindex?application=RuleDescription) | Ensures tabindex attribute values are not greater than 0 | Serious | cat.keyboard, best-practice | failure | | -| [table-duplicate-name](https://dequeuniversity.com/rules/axe/4.9/table-duplicate-name?application=RuleDescription) | Ensure the <caption> element does not contain the same text as the summary attribute | Minor | cat.tables, best-practice | failure, needs review | | +| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | +| :------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :----------------------------------------- | :------------------------- | :------------------------------------------------- | +| [accesskeys](https://dequeuniversity.com/rules/axe/4.10/accesskeys?application=RuleDescription) | Ensure every accesskey attribute value is unique | Serious | cat.keyboard, best-practice | failure | | +| [aria-allowed-role](https://dequeuniversity.com/rules/axe/4.10/aria-allowed-role?application=RuleDescription) | Ensure role attribute has an appropriate value for the element | Minor | cat.aria, best-practice | failure, needs review | | +| [aria-dialog-name](https://dequeuniversity.com/rules/axe/4.10/aria-dialog-name?application=RuleDescription) | Ensure every ARIA dialog and alertdialog node has an accessible name | Serious | cat.aria, best-practice | failure, needs review | | +| [aria-text](https://dequeuniversity.com/rules/axe/4.10/aria-text?application=RuleDescription) | Ensure role="text" is used on elements with no focusable descendants | Serious | cat.aria, best-practice | failure, needs review | | +| [aria-treeitem-name](https://dequeuniversity.com/rules/axe/4.10/aria-treeitem-name?application=RuleDescription) | Ensure every ARIA treeitem node has an accessible name | Serious | cat.aria, best-practice | failure, needs review | | +| [empty-heading](https://dequeuniversity.com/rules/axe/4.10/empty-heading?application=RuleDescription) | Ensure 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.10/empty-table-header?application=RuleDescription) | Ensure table headers have discernible text | Minor | cat.name-role-value, best-practice | failure, needs review | | +| [frame-tested](https://dequeuniversity.com/rules/axe/4.10/frame-tested?application=RuleDescription) | Ensure <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.10/heading-order?application=RuleDescription) | Ensure the order of headings is semantically correct | Moderate | cat.semantics, best-practice | failure, needs review | | +| [image-redundant-alt](https://dequeuniversity.com/rules/axe/4.10/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.10/label-title-only?application=RuleDescription) | Ensure 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 | | +| [landmark-banner-is-top-level](https://dequeuniversity.com/rules/axe/4.10/landmark-banner-is-top-level?application=RuleDescription) | Ensure the banner landmark is at top level | Moderate | cat.semantics, best-practice | failure | | +| [landmark-complementary-is-top-level](https://dequeuniversity.com/rules/axe/4.10/landmark-complementary-is-top-level?application=RuleDescription) | Ensure the complementary landmark or aside is at top level | Moderate | cat.semantics, best-practice | failure | | +| [landmark-contentinfo-is-top-level](https://dequeuniversity.com/rules/axe/4.10/landmark-contentinfo-is-top-level?application=RuleDescription) | Ensure the contentinfo landmark is at top level | Moderate | cat.semantics, best-practice | failure | | +| [landmark-main-is-top-level](https://dequeuniversity.com/rules/axe/4.10/landmark-main-is-top-level?application=RuleDescription) | Ensure the main landmark is at top level | Moderate | cat.semantics, best-practice | failure | | +| [landmark-no-duplicate-banner](https://dequeuniversity.com/rules/axe/4.10/landmark-no-duplicate-banner?application=RuleDescription) | Ensure the document has at most one banner landmark | Moderate | cat.semantics, best-practice | failure | | +| [landmark-no-duplicate-contentinfo](https://dequeuniversity.com/rules/axe/4.10/landmark-no-duplicate-contentinfo?application=RuleDescription) | Ensure the document has at most one contentinfo landmark | Moderate | cat.semantics, best-practice | failure | | +| [landmark-no-duplicate-main](https://dequeuniversity.com/rules/axe/4.10/landmark-no-duplicate-main?application=RuleDescription) | Ensure the document has at most one main landmark | Moderate | cat.semantics, best-practice | failure | | +| [landmark-one-main](https://dequeuniversity.com/rules/axe/4.10/landmark-one-main?application=RuleDescription) | Ensure the document has a main landmark | Moderate | cat.semantics, best-practice | failure | | +| [landmark-unique](https://dequeuniversity.com/rules/axe/4.10/landmark-unique?application=RuleDescription) | Ensure landmarks are unique | Moderate | cat.semantics, best-practice | failure | | +| [meta-viewport-large](https://dequeuniversity.com/rules/axe/4.10/meta-viewport-large?application=RuleDescription) | Ensure <meta name="viewport"> can scale a significant amount | Minor | cat.sensory-and-visual-cues, best-practice | failure | | +| [page-has-heading-one](https://dequeuniversity.com/rules/axe/4.10/page-has-heading-one?application=RuleDescription) | Ensure that the page, or at least one of its frames contains a level-one heading | Moderate | cat.semantics, best-practice | failure | | +| [presentation-role-conflict](https://dequeuniversity.com/rules/axe/4.10/presentation-role-conflict?application=RuleDescription) | Elements marked as presentational should not have global ARIA or tabindex to ensure all screen readers ignore them | Minor | cat.aria, best-practice, ACT | failure | [46ca7f](https://act-rules.github.io/rules/46ca7f) | +| [region](https://dequeuniversity.com/rules/axe/4.10/region?application=RuleDescription) | Ensure all page content is contained by landmarks | Moderate | cat.keyboard, best-practice | failure | | +| [scope-attr-valid](https://dequeuniversity.com/rules/axe/4.10/scope-attr-valid?application=RuleDescription) | Ensure the scope attribute is used correctly on tables | Moderate | cat.tables, best-practice | failure | | +| [skip-link](https://dequeuniversity.com/rules/axe/4.10/skip-link?application=RuleDescription) | Ensure all skip links have a focusable target | Moderate | cat.keyboard, best-practice | failure, needs review | | +| [tabindex](https://dequeuniversity.com/rules/axe/4.10/tabindex?application=RuleDescription) | Ensure tabindex attribute values are not greater than 0 | Serious | cat.keyboard, best-practice | failure | | +| [table-duplicate-name](https://dequeuniversity.com/rules/axe/4.10/table-duplicate-name?application=RuleDescription) | Ensure the <caption> element does not contain the same text as the summary attribute | Minor | cat.tables, best-practice | failure, needs review | | ## WCAG 2.x level AAA rules Rules that check for conformance to WCAG AAA success criteria that can be fully automated. These are disabled by default in axe-core. -| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | -| :--------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------- | :------ | :--------------------------------------------- | :------------------------- | :------------------------------------------------- | -| [color-contrast-enhanced](https://dequeuniversity.com/rules/axe/4.9/color-contrast-enhanced?application=RuleDescription) | Ensures the contrast between foreground and background colors meets WCAG 2 AAA enhanced contrast ratio thresholds | Serious | cat.color, wcag2aaa, wcag146, ACT | failure, needs review | [09o5cg](https://act-rules.github.io/rules/09o5cg) | -| [identical-links-same-purpose](https://dequeuniversity.com/rules/axe/4.9/identical-links-same-purpose?application=RuleDescription) | Ensure that links with the same accessible name serve a similar purpose | Minor | cat.semantics, wcag2aaa, wcag249 | needs review | [b20e66](https://act-rules.github.io/rules/b20e66) | -| [meta-refresh-no-exceptions](https://dequeuniversity.com/rules/axe/4.9/meta-refresh-no-exceptions?application=RuleDescription) | Ensures <meta http-equiv="refresh"> is not used for delayed refresh | Minor | cat.time-and-media, wcag2aaa, wcag224, wcag325 | failure | [bisz58](https://act-rules.github.io/rules/bisz58) | +| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | +| :---------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | :------ | :--------------------------------------------- | :------------------------- | :------------------------------------------------- | +| [color-contrast-enhanced](https://dequeuniversity.com/rules/axe/4.10/color-contrast-enhanced?application=RuleDescription) | Ensure the contrast between foreground and background colors meets WCAG 2 AAA enhanced contrast ratio thresholds | Serious | cat.color, wcag2aaa, wcag146, ACT | failure, needs review | [09o5cg](https://act-rules.github.io/rules/09o5cg) | +| [identical-links-same-purpose](https://dequeuniversity.com/rules/axe/4.10/identical-links-same-purpose?application=RuleDescription) | Ensure that links with the same accessible name serve a similar purpose | Minor | cat.semantics, wcag2aaa, wcag249 | needs review | [b20e66](https://act-rules.github.io/rules/b20e66) | +| [meta-refresh-no-exceptions](https://dequeuniversity.com/rules/axe/4.10/meta-refresh-no-exceptions?application=RuleDescription) | Ensure <meta http-equiv="refresh"> is not used for delayed refresh | Minor | cat.time-and-media, wcag2aaa, wcag224, wcag325 | failure | [bisz58](https://act-rules.github.io/rules/bisz58) | ## Experimental Rules Rules we are still testing and developing. They are disabled by default in axe-core, but are enabled for the axe browser extensions. -| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | -| :------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- | :------- | :----------------------------------------------------------------------------------------------------------- | :------------------------- | :------------------------------------------------- | -| [css-orientation-lock](https://dequeuniversity.com/rules/axe/4.9/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.9/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.9/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.9/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.9/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.9/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 | | -| [td-has-header](https://dequeuniversity.com/rules/axe/4.9/td-has-header?application=RuleDescription) | Ensure that each non-empty data cell in a <table> larger than 3 by 3 has one or more table headers | Critical | cat.tables, experimental, wcag2a, wcag131, section508, section508.22.g, TTv5, TT14.b, EN-301-549, EN-9.1.3.1 | failure | | +| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | +| :-------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ | :------- | :----------------------------------------------------------------------------------------------------------- | :------------------------- | :------------------------------------------------- | +| [css-orientation-lock](https://dequeuniversity.com/rules/axe/4.10/css-orientation-lock?application=RuleDescription) | Ensure 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.10/focus-order-semantics?application=RuleDescription) | Ensure 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.10/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.10/label-content-name-mismatch?application=RuleDescription) | Ensure 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.10/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.10/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 | | +| [td-has-header](https://dequeuniversity.com/rules/axe/4.10/td-has-header?application=RuleDescription) | Ensure that each non-empty data cell in a <table> larger than 3 by 3 has one or more table headers | Critical | cat.tables, experimental, wcag2a, wcag131, section508, section508.22.g, TTv5, TT14.b, EN-301-549, EN-9.1.3.1 | failure | | ## Deprecated Rules Deprecated rules are disabled by default and will be removed in the next major release. -| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | -| :----------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- | :------- | :--------------------------------------------------------------------------------------------------- | :------------------------- | :----------------------------------------------------------------------------------------------------- | -| [aria-roledescription](https://dequeuniversity.com/rules/axe/4.9/aria-roledescription?application=RuleDescription) | Ensure aria-roledescription is only used on elements with an implicit or explicit role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2, deprecated | failure, needs review | | -| [audio-caption](https://dequeuniversity.com/rules/axe/4.9/audio-caption?application=RuleDescription) | Ensures <audio> elements have captions | Critical | cat.time-and-media, wcag2a, wcag121, EN-301-549, EN-9.1.2.1, section508, section508.22.a, deprecated | needs review | [2eb176](https://act-rules.github.io/rules/2eb176), [afb423](https://act-rules.github.io/rules/afb423) | -| [duplicate-id-active](https://dequeuniversity.com/rules/axe/4.9/duplicate-id-active?application=RuleDescription) | Ensures every id attribute value of active elements is unique | Serious | cat.parsing, wcag2a-obsolete, wcag411, deprecated | failure | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | -| [duplicate-id](https://dequeuniversity.com/rules/axe/4.9/duplicate-id?application=RuleDescription) | Ensures every id attribute value is unique | Minor | cat.parsing, wcag2a-obsolete, wcag411, deprecated | failure | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | +| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | +| :------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------- | :------- | :--------------------------------------------------------------------------------------------------- | :------------------------- | :----------------------------------------------------------------------------------------------------- | +| [aria-roledescription](https://dequeuniversity.com/rules/axe/4.10/aria-roledescription?application=RuleDescription) | Ensure aria-roledescription is only used on elements with an implicit or explicit role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2, deprecated | failure, needs review | | +| [audio-caption](https://dequeuniversity.com/rules/axe/4.10/audio-caption?application=RuleDescription) | Ensure <audio> elements have captions | Critical | cat.time-and-media, wcag2a, wcag121, EN-301-549, EN-9.1.2.1, section508, section508.22.a, deprecated | needs review | [2eb176](https://act-rules.github.io/rules/2eb176), [afb423](https://act-rules.github.io/rules/afb423) | +| [duplicate-id-active](https://dequeuniversity.com/rules/axe/4.10/duplicate-id-active?application=RuleDescription) | Ensure every id attribute value of active elements is unique | Serious | cat.parsing, wcag2a-obsolete, wcag411, deprecated | failure | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | +| [duplicate-id](https://dequeuniversity.com/rules/axe/4.10/duplicate-id?application=RuleDescription) | Ensure every id attribute value is unique | Minor | cat.parsing, wcag2a-obsolete, wcag411, deprecated | failure | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | diff --git a/doc/rule-proposal.md b/doc/rule-proposal.md index f49395c327..6a43e86f9c 100644 --- a/doc/rule-proposal.md +++ b/doc/rule-proposal.md @@ -18,7 +18,7 @@ All Github issues that propose a rule must be tagged as _rule_, and must use the In one sentence, describe what the rule does. -Example: "Ensures ARIA attributes are allowed for an element's role"\ +Example: "Ensure ARIA attributes are allowed for an element's role" #### Rule help diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000000..db39951cb6 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,198 @@ +const prettier = require('eslint-config-prettier'); +const globals = require('globals'); +const mochaNoOnly = require('eslint-plugin-mocha-no-only'); + +module.exports = [ + prettier, + { + rules: { + 'mocha-no-only/mocha-no-only': ['error'], + 'no-bitwise': 2, + camelcase: 2, + curly: 2, + eqeqeq: 2, + 'guard-for-in': 2, + 'wrap-iife': [2, 'any'], + 'no-use-before-define': [ + 2, + { + functions: false + } + ], + 'new-cap': 2, + 'no-caller': 2, + 'no-empty': 2, + 'no-new': 2, + 'no-plusplus': 0, + 'no-undef': 2, + 'no-unused-vars': 2, + strict: 0, + 'max-params': [2, 6], + 'max-depth': [2, 5], + 'max-len': 0, + semi: 0, + 'no-cond-assign': 0, + 'no-debugger': 2, + 'no-eq-null': 0, + 'no-eval': 2, + 'no-unused-expressions': 0, + 'block-scoped-var': 0, + 'no-iterator': 0, + 'linebreak-style': 0, + 'no-loop-func': 0, + 'no-multi-str': 0, + 'no-proto': 0, + 'no-script-url': 0, + 'dot-notation': 2, + 'no-new-func': 0, + 'no-new-wrappers': 0, + 'no-shadow': 2, + 'no-restricted-syntax': [ + 'error', + { + selector: 'MemberExpression[property.name=tagName]', + message: "Don't use node.tagName, use node.nodeName instead." + }, + // node.attributes can be clobbered so is unsafe to use + // @see https://github.com/dequelabs/axe-core/pull/1432 + { + // node.attributes + selector: + 'MemberExpression[object.name=node][property.name=attributes]', + message: + "Don't use node.attributes, use node.hasAttributes() or axe.utils.getNodeAttributes(node) instead." + }, + { + // vNode.actualNode.attributes + selector: + 'MemberExpression[object.property.name=actualNode][property.name=attributes]', + message: + "Don't use node.attributes, use node.hasAttributes() or axe.utils.getNodeAttributes(node) instead." + }, + // node.contains doesn't work with shadow dom + // @see https://github.com/dequelabs/axe-core/issues/4194 + { + // node.contains() + selector: + 'CallExpression[callee.object.name=node][callee.property.name=contains]', + message: + "Don't use node.contains(node2) as it doesn't work across shadow DOM. Use axe.utils.contains(node, node2) instead." + }, + { + // vNode.actualNode.contains() + selector: + 'CallExpression[callee.object.property.name=actualNode][callee.property.name=contains]', + message: + "Don't use node.contains(node2) as it doesn't work across shadow DOM. Use axe.utils.contains(node, node2) instead." + } + ] + }, + plugins: { + 'mocha-no-only': mochaNoOnly + } + }, + { + languageOptions: { + ecmaVersion: 2023, + globals: { + axe: true, + Promise: true, + ...globals.node, + ...globals.es2015 + } + } + }, + { + files: ['lib/**/*.js'], + // reporters and check after methods should not have access to window or document + ignores: ['lib/core/reporters/**/*.js', 'lib/**/*-after.js'], + languageOptions: { + sourceType: 'module', + // lib files should not access global window properties without going through window (i.e. do not add globals.browser) + globals: { + window: true, + document: true, + ...globals.node, + ...globals.es2015 + } + }, + rules: { + 'func-names': [2, 'as-needed'], + 'prefer-const': 2, + 'no-use-before-define': 'off' + } + }, + { + files: ['doc/examples/chrome-debugging-protocol/axe-cdp.js'], + languageOptions: { + globals: { + window: true + } + } + }, + { + // after functions and reporters will not be run inside the same context as axe.run so should not access browser globals that require context specific information (window.location, window.getComputedStyles, etc.) + files: ['lib/**/*-after.js', 'lib/core/reporters/**/*.js'], + languageOptions: { + sourceType: 'module' + } + }, + { + // polyfills are mostly copy-pasted from sources so we don't control their styling + files: [ + 'lib/core/imports/polyfills.js', + 'lib/core/utils/pollyfill-elements-from-point.js' + ], + rules: { + 'func-names': 0, + 'no-bitwise': 0, + curly: 0, + eqeqeq: 0 + } + }, + { + files: ['test/act-rules/**/*.js', 'test/aria-practices/**/*.js'], + languageOptions: { + globals: { + ...globals.mocha + } + }, + rules: { + 'new-cap': 0, + 'no-use-before-define': 0 + } + }, + { + files: ['test/**/*.js'], + languageOptions: { + globals: { + ...globals.mocha, + ...globals.browser, + ...globals.es2015, + ...globals.node, + assert: true, + helpers: true, + checks: true, + sinon: true + } + }, + rules: { + 'new-cap': 0, + 'no-use-before-define': 0 + } + }, + { + ignores: [ + '**/node_modules/*', + '**/tmp/*', + 'patches/*', + 'build/tasks/aria-supported.js', + 'doc/api/*', + 'doc/examples/jest_react/*.js', + 'lib/core/imports/*.js', + 'lib/core/utils/uuid.js', + 'axe.js', + 'axe.min.js' + ] + } +]; diff --git a/lib/checks/aria/aria-allowed-attr-evaluate.js b/lib/checks/aria/aria-allowed-attr-evaluate.js index a1049ef38d..59d09e7e6e 100644 --- a/lib/checks/aria/aria-allowed-attr-evaluate.js +++ b/lib/checks/aria/aria-allowed-attr-evaluate.js @@ -39,7 +39,11 @@ export default function ariaAllowedAttrEvaluate(node, options, virtualNode) { // Unknown ARIA attributes are tested in aria-valid-attr for (const attrName of virtualNode.attrNames) { - if (validateAttr(attrName) && !allowed.includes(attrName)) { + if ( + validateAttr(attrName) && + !allowed.includes(attrName) && + !ignoredAttrs(attrName, virtualNode.attr(attrName), virtualNode) + ) { invalid.push(attrName); } } @@ -57,3 +61,23 @@ export default function ariaAllowedAttrEvaluate(node, options, virtualNode) { } return false; } + +function ignoredAttrs(attrName, attrValue, vNode) { + // allow aria-required=false as screen readers consistently ignore it + // @see https://github.com/dequelabs/axe-core/issues/3756 + if (attrName === 'aria-required' && attrValue === 'false') { + return true; + } + + // allow aria-multiline=false when contenteditable is set + // @see https://github.com/dequelabs/axe-core/issues/4463 + if ( + attrName === 'aria-multiline' && + attrValue === 'false' && + vNode.hasAttr('contenteditable') + ) { + return true; + } + + return false; +} diff --git a/lib/checks/aria/aria-errormessage-evaluate.js b/lib/checks/aria/aria-errormessage-evaluate.js index b7b971d4e2..26367c4b72 100644 --- a/lib/checks/aria/aria-errormessage-evaluate.js +++ b/lib/checks/aria/aria-errormessage-evaluate.js @@ -46,7 +46,7 @@ export default function ariaErrormessageEvaluate(node, options, virtualNode) { try { idref = attr && idrefs(virtualNode, 'aria-errormessage')[0]; - } catch (e) { + } catch { this.data({ messageKey: 'idrefs', values: tokenList(attr) diff --git a/lib/checks/aria/aria-errormessage.json b/lib/checks/aria/aria-errormessage.json index 84901729af..e238d7f654 100644 --- a/lib/checks/aria/aria-errormessage.json +++ b/lib/checks/aria/aria-errormessage.json @@ -11,9 +11,9 @@ "hidden": "aria-errormessage value `${data.values}` cannot reference a hidden element" }, "incomplete": { - "singular": "ensure aria-errormessage value `${data.values}` references an existing element", - "plural": "ensure aria-errormessage values `${data.values}` reference existing elements", - "idrefs": "unable to determine if aria-errormessage element exists on the page: ${data.values}" + "singular": "Ensure aria-errormessage value `${data.values}` references an existing element", + "plural": "Ensure aria-errormessage values `${data.values}` reference existing elements", + "idrefs": "Unable to determine if aria-errormessage element exists on the page: ${data.values}" } } } diff --git a/lib/checks/aria/aria-prohibited-attr-evaluate.js b/lib/checks/aria/aria-prohibited-attr-evaluate.js index e6e2f9da7d..cc9cefd5f8 100644 --- a/lib/checks/aria/aria-prohibited-attr-evaluate.js +++ b/lib/checks/aria/aria-prohibited-attr-evaluate.js @@ -1,6 +1,7 @@ -import { getRole } from '../../commons/aria'; +import { getRole, getRoleType } from '../../commons/aria'; import { sanitize, subtreeText } from '../../commons/text'; import standards from '../../standards'; +import memoize from '../../core/utils/memoize'; /** * Check that an element does not use any prohibited ARIA attributes. @@ -36,6 +37,7 @@ export default function ariaProhibitedAttrEvaluate( const role = getRole(virtualNode, { chromium: true }); const prohibitedList = listProhibitedAttrs( + virtualNode, role, nodeName, elementsAllowedAriaLabel @@ -64,13 +66,32 @@ export default function ariaProhibitedAttrEvaluate( return true; } -function listProhibitedAttrs(role, nodeName, elementsAllowedAriaLabel) { +function listProhibitedAttrs(vNode, role, nodeName, elementsAllowedAriaLabel) { const roleSpec = standards.ariaRoles[role]; if (roleSpec) { return roleSpec.prohibitedAttrs || []; } - if (!!role || elementsAllowedAriaLabel.includes(nodeName)) { + if ( + !!role || + elementsAllowedAriaLabel.includes(nodeName) || + getClosestAncestorRoleType(vNode) === 'widget' + ) { return []; } return ['aria-label', 'aria-labelledby']; } + +const getClosestAncestorRoleType = memoize( + function getClosestAncestorRoleTypeMemoized(vNode) { + if (!vNode) { + return; + } + + const role = getRole(vNode, { noPresentational: true, chromium: true }); + if (role) { + return getRoleType(role); + } + + return getClosestAncestorRoleType(vNode.parent); + } +); diff --git a/lib/checks/aria/aria-required-attr-evaluate.js b/lib/checks/aria/aria-required-attr-evaluate.js index f10642c4c4..f623ba4be7 100644 --- a/lib/checks/aria/aria-required-attr-evaluate.js +++ b/lib/checks/aria/aria-required-attr-evaluate.js @@ -54,6 +54,11 @@ export default function ariaRequiredAttrEvaluate( ) { return true; } + // Non-normative exception for things like media player seek slider. + // Tested to work in various screen readers. + if (role === 'slider' && virtualNode.attr('aria-valuetext')?.trim()) { + return true; + } const elmSpec = getElementSpec(virtualNode); const missingAttrs = requiredAttrs.filter( diff --git a/lib/checks/aria/aria-required-parent-evaluate.js b/lib/checks/aria/aria-required-parent-evaluate.js index c985cedc27..a374f0aea0 100644 --- a/lib/checks/aria/aria-required-parent-evaluate.js +++ b/lib/checks/aria/aria-required-parent-evaluate.js @@ -48,8 +48,8 @@ function getMissingContext( } function getAriaOwners(element) { - var owners = [], - o = null; + const owners = []; + let o = null; while (element) { if (element.getAttribute('id')) { diff --git a/lib/checks/aria/aria-valid-attr-value-evaluate.js b/lib/checks/aria/aria-valid-attr-value-evaluate.js index 79ea53fefb..ad43d13936 100644 --- a/lib/checks/aria/aria-valid-attr-value-evaluate.js +++ b/lib/checks/aria/aria-valid-attr-value-evaluate.js @@ -115,7 +115,7 @@ export default function ariaValidAttrValueEvaluate(node, options, virtualNode) { try { validValue = validateAttrValue(virtualNode, attrName); - } catch (e) { + } catch { needsReview = `${attrName}="${attrValue}"`; messageKey = 'idrefs'; return; diff --git a/lib/checks/aria/no-implicit-explicit-label-evaluate.js b/lib/checks/aria/no-implicit-explicit-label-evaluate.js index 0fb7270770..5fbe5d99a1 100644 --- a/lib/checks/aria/no-implicit-explicit-label-evaluate.js +++ b/lib/checks/aria/no-implicit-explicit-label-evaluate.js @@ -32,7 +32,7 @@ function noImplicitExplicitLabelEvaluate(node, options, virtualNode) { try { label = sanitize(labelText(virtualNode)).toLowerCase(); accText = sanitize(accessibleTextVirtual(virtualNode)).toLowerCase(); - } catch (e) { + } catch { return undefined; } diff --git a/lib/checks/aria/valid-scrollable-semantics-evaluate.js b/lib/checks/aria/valid-scrollable-semantics-evaluate.js index fef446f35e..0241f6cf83 100644 --- a/lib/checks/aria/valid-scrollable-semantics-evaluate.js +++ b/lib/checks/aria/valid-scrollable-semantics-evaluate.js @@ -51,7 +51,7 @@ function validScrollableTagName(node) { * region. */ function validScrollableRole(node, options) { - var role = getExplicitRole(node); + const role = getExplicitRole(node); if (!role) { return false; } diff --git a/lib/checks/color/link-in-text-block-evaluate.js b/lib/checks/color/link-in-text-block-evaluate.js index acadb1aec5..1cda344739 100644 --- a/lib/checks/color/link-in-text-block-evaluate.js +++ b/lib/checks/color/link-in-text-block-evaluate.js @@ -6,8 +6,8 @@ import { } from '../../commons/color'; function getContrast(color1, color2) { - var c1lum = color1.getRelativeLuminance(); - var c2lum = color2.getRelativeLuminance(); + const c1lum = color1.getRelativeLuminance(); + const c2lum = color2.getRelativeLuminance(); return (Math.max(c1lum, c2lum) + 0.05) / (Math.min(c1lum, c2lum) + 0.05); } @@ -20,7 +20,7 @@ const blockLike = [ 'inline-block' ]; function isBlock(elm) { - var display = window.getComputedStyle(elm).getPropertyValue('display'); + const display = window.getComputedStyle(elm).getPropertyValue('display'); return blockLike.indexOf(display) !== -1 || display.substr(0, 6) === 'table-'; } @@ -31,7 +31,7 @@ function linkInTextBlockEvaluate(node, options) { return false; } - var parentBlock = getComposedParent(node); + let parentBlock = getComposedParent(node); while (parentBlock && parentBlock.nodeType === 1 && !isBlock(parentBlock)) { parentBlock = getComposedParent(parentBlock); } @@ -43,13 +43,13 @@ function linkInTextBlockEvaluate(node, options) { this.relatedNodes([parentBlock]); // Capture colors - var nodeColor = getForegroundColor(node); - var parentColor = getForegroundColor(parentBlock); - var nodeBackgroundColor = getBackgroundColor(node); - var parentBackgroundColor = getBackgroundColor(parentBlock); + const nodeColor = getForegroundColor(node); + const parentColor = getForegroundColor(parentBlock); + const nodeBackgroundColor = getBackgroundColor(node); + const parentBackgroundColor = getBackgroundColor(parentBlock); // Compute contrasts, giving preference to foreground color and doing as little work as possible - var textContrast = + let textContrast = nodeColor && parentColor ? getContrast(nodeColor, parentColor) : undefined; if (textContrast) { textContrast = Math.floor(textContrast * 100) / 100; @@ -59,7 +59,7 @@ function linkInTextBlockEvaluate(node, options) { return true; } - var backgroundContrast = + let backgroundContrast = nodeBackgroundColor && parentBackgroundColor ? getContrast(nodeBackgroundColor, parentBackgroundColor) : undefined; @@ -74,7 +74,7 @@ function linkInTextBlockEvaluate(node, options) { // Report incomplete instead of failure if we're not sure if (!backgroundContrast) { - var reason = incompleteData.get('bgColor') ?? 'bgContrast'; + const reason = incompleteData.get('bgColor') ?? 'bgContrast'; this.data({ messageKey: reason }); diff --git a/lib/checks/color/link-in-text-block-style-evaluate.js b/lib/checks/color/link-in-text-block-style-evaluate.js index e07929ea41..e1e0f53739 100644 --- a/lib/checks/color/link-in-text-block-style-evaluate.js +++ b/lib/checks/color/link-in-text-block-style-evaluate.js @@ -15,7 +15,7 @@ export default function linkInTextBlockStyleEvaluate(node) { return false; } - var parentBlock = getComposedParent(node); + let parentBlock = getComposedParent(node); while (parentBlock && parentBlock.nodeType === 1 && !isBlock(parentBlock)) { parentBlock = getComposedParent(parentBlock); } @@ -37,7 +37,7 @@ export default function linkInTextBlockStyleEvaluate(node) { } function isBlock(elm) { - var display = window.getComputedStyle(elm).getPropertyValue('display'); + const display = window.getComputedStyle(elm).getPropertyValue('display'); return blockLike.indexOf(display) !== -1 || display.substr(0, 6) === 'table-'; } diff --git a/lib/checks/forms/autocomplete-appropriate.json b/lib/checks/forms/autocomplete-appropriate.json index dda69e3b06..0e9940de8a 100644 --- a/lib/checks/forms/autocomplete-appropriate.json +++ b/lib/checks/forms/autocomplete-appropriate.json @@ -5,8 +5,8 @@ "metadata": { "impact": "serious", "messages": { - "pass": "the autocomplete value is on an appropriate element", - "fail": "the autocomplete value is inappropriate for this type of input" + "pass": "The autocomplete value is on an appropriate element", + "fail": "The autocomplete value is inappropriate for this type of input" } } } diff --git a/lib/checks/forms/autocomplete-valid-evaluate.js b/lib/checks/forms/autocomplete-valid-evaluate.js index 8139dc64dd..8e80aa06fb 100644 --- a/lib/checks/forms/autocomplete-valid-evaluate.js +++ b/lib/checks/forms/autocomplete-valid-evaluate.js @@ -1,6 +1,6 @@ import { isValidAutocomplete } from '../../commons/text'; -function autocompleteValidEvaluate(node, options, virtualNode) { +function autocompleteValidEvaluate(_node, options, virtualNode) { const autocomplete = virtualNode.attr('autocomplete') || ''; return isValidAutocomplete(autocomplete, options); } diff --git a/lib/checks/forms/autocomplete-valid.json b/lib/checks/forms/autocomplete-valid.json index 0906e4b89a..3081143edc 100644 --- a/lib/checks/forms/autocomplete-valid.json +++ b/lib/checks/forms/autocomplete-valid.json @@ -5,7 +5,8 @@ "impact": "serious", "messages": { "pass": "the autocomplete attribute is correctly formatted", - "fail": "the autocomplete attribute is incorrectly formatted" + "fail": "the autocomplete attribute is incorrectly formatted", + "incomplete": "the autocomplete attribute has a non-standard value. Check whether any standard value could be used instead." } }, "options": { @@ -17,6 +18,7 @@ "enabled", "undefined", "null" - ] + ], + "ignoredValues": ["text", "pronouns", "gender", "message", "content"] } } diff --git a/lib/checks/generic/has-text-content-evaluate.js b/lib/checks/generic/has-text-content-evaluate.js index a3d28d5a7d..e487f7254e 100644 --- a/lib/checks/generic/has-text-content-evaluate.js +++ b/lib/checks/generic/has-text-content-evaluate.js @@ -3,7 +3,7 @@ import { sanitize, subtreeText } from '../../commons/text'; export default function hasTextContentEvaluate(node, options, virtualNode) { try { return sanitize(subtreeText(virtualNode)) !== ''; - } catch (e) { + } catch { return undefined; } } diff --git a/lib/checks/keyboard/accesskeys-after.js b/lib/checks/keyboard/accesskeys-after.js index ec65c48f49..13a0ff489f 100644 --- a/lib/checks/keyboard/accesskeys-after.js +++ b/lib/checks/keyboard/accesskeys-after.js @@ -1,11 +1,11 @@ function accesskeysAfter(results) { - var seen = {}; + const seen = {}; return results .filter(r => { if (!r.data) { return false; } - var key = r.data.toUpperCase(); + const key = r.data.toUpperCase(); if (!seen[key]) { seen[key] = r; r.relatedNodes = []; diff --git a/lib/checks/keyboard/focusable-no-name-evaluate.js b/lib/checks/keyboard/focusable-no-name-evaluate.js index 3d84a37550..490470a67c 100644 --- a/lib/checks/keyboard/focusable-no-name-evaluate.js +++ b/lib/checks/keyboard/focusable-no-name-evaluate.js @@ -10,7 +10,7 @@ function focusableNoNameEvaluate(node, options, virtualNode) { try { return !accessibleTextVirtual(virtualNode); - } catch (e) { + } catch { return undefined; } } diff --git a/lib/checks/keyboard/frame-focusable-content-evaluate.js b/lib/checks/keyboard/frame-focusable-content-evaluate.js index 0b48657c46..60c47a531c 100644 --- a/lib/checks/keyboard/frame-focusable-content-evaluate.js +++ b/lib/checks/keyboard/frame-focusable-content-evaluate.js @@ -13,7 +13,7 @@ export default function frameFocusableContentEvaluate( return !virtualNode.children.some(child => { return focusableDescendants(child); }); - } catch (e) { + } catch { return undefined; } } diff --git a/lib/checks/keyboard/landmark-is-top-level-evaluate.js b/lib/checks/keyboard/landmark-is-top-level-evaluate.js index 15ba47200d..4fbd93d73f 100644 --- a/lib/checks/keyboard/landmark-is-top-level-evaluate.js +++ b/lib/checks/keyboard/landmark-is-top-level-evaluate.js @@ -3,14 +3,14 @@ import { getAriaRolesByType } from '../../commons/standards'; import { getComposedParent } from '../../commons/dom'; function landmarkIsTopLevelEvaluate(node) { - var landmarks = getAriaRolesByType('landmark'); - var parent = getComposedParent(node); - var nodeRole = getRole(node); + const landmarks = getAriaRolesByType('landmark'); + let parent = getComposedParent(node); + const nodeRole = getRole(node); this.data({ role: nodeRole }); while (parent) { - var role = parent.getAttribute('role'); + let role = parent.getAttribute('role'); if (!role && parent.nodeName.toUpperCase() !== 'FORM') { role = implicitRole(parent); } diff --git a/lib/checks/keyboard/no-focusable-content-evaluate.js b/lib/checks/keyboard/no-focusable-content-evaluate.js index cb0b38cf98..dd338a1dec 100644 --- a/lib/checks/keyboard/no-focusable-content-evaluate.js +++ b/lib/checks/keyboard/no-focusable-content-evaluate.js @@ -25,7 +25,7 @@ export default function noFocusableContentEvaluate(node, options, virtualNode) { } return false; - } catch (e) { + } catch { return undefined; } } diff --git a/lib/checks/label/explicit-evaluate.js b/lib/checks/label/explicit-evaluate.js index 585371989b..59b0c123c2 100644 --- a/lib/checks/label/explicit-evaluate.js +++ b/lib/checks/label/explicit-evaluate.js @@ -35,7 +35,7 @@ function explicitEvaluate(node, options, virtualNode) { return !!explicitLabel; } }); - } catch (e) { + } catch { return undefined; } } diff --git a/lib/checks/label/help-same-as-label-evaluate.js b/lib/checks/label/help-same-as-label-evaluate.js index 61262e0faa..47b15769f6 100644 --- a/lib/checks/label/help-same-as-label-evaluate.js +++ b/lib/checks/label/help-same-as-label-evaluate.js @@ -2,8 +2,8 @@ import { labelVirtual, accessibleText, sanitize } from '../../commons/text'; import { idrefs } from '../../commons/dom'; function helpSameAsLabelEvaluate(node, options, virtualNode) { - var labelText = labelVirtual(virtualNode), - check = node.getAttribute('title'); + const labelText = labelVirtual(virtualNode); + let check = node.getAttribute('title'); if (!labelText) { return false; @@ -13,7 +13,7 @@ function helpSameAsLabelEvaluate(node, options, virtualNode) { check = ''; if (node.getAttribute('aria-describedby')) { - var ref = idrefs(node, 'aria-describedby'); + const ref = idrefs(node, 'aria-describedby'); check = ref .map(thing => { return thing ? accessibleText(thing) : ''; diff --git a/lib/checks/label/hidden-explicit-label-evaluate.js b/lib/checks/label/hidden-explicit-label-evaluate.js index eafcc14095..f74f285f91 100644 --- a/lib/checks/label/hidden-explicit-label-evaluate.js +++ b/lib/checks/label/hidden-explicit-label-evaluate.js @@ -16,7 +16,7 @@ function hiddenExplicitLabelEvaluate(node, options, virtualNode) { let name; try { name = accessibleTextVirtual(virtualNode).trim(); - } catch (e) { + } catch { return undefined; } diff --git a/lib/checks/label/implicit-evaluate.js b/lib/checks/label/implicit-evaluate.js index 4d43d9f5ef..094095e2d7 100644 --- a/lib/checks/label/implicit-evaluate.js +++ b/lib/checks/label/implicit-evaluate.js @@ -18,7 +18,7 @@ function implicitEvaluate(node, options, virtualNode) { return !!implicitLabel; } return false; - } catch (e) { + } catch { return undefined; } } diff --git a/lib/checks/landmarks/landmark-is-unique-after.js b/lib/checks/landmarks/landmark-is-unique-after.js index eb57c4da3c..fd4eb2ec1f 100644 --- a/lib/checks/landmarks/landmark-is-unique-after.js +++ b/lib/checks/landmarks/landmark-is-unique-after.js @@ -1,17 +1,17 @@ function landmarkIsUniqueAfter(results) { - var uniqueLandmarks = []; + const uniqueLandmarks = []; // filter out landmark elements that share the same role and accessible text // so every non-unique landmark isn't reported as a failure (just the first) return results.filter(currentResult => { - var findMatch = someResult => { + const findMatch = someResult => { return ( currentResult.data.role === someResult.data.role && currentResult.data.accessibleText === someResult.data.accessibleText ); }; - var matchedResult = uniqueLandmarks.find(findMatch); + const matchedResult = uniqueLandmarks.find(findMatch); if (matchedResult) { matchedResult.result = false; matchedResult.relatedNodes.push(currentResult.relatedNodes[0]); diff --git a/lib/checks/landmarks/landmark-is-unique-evaluate.js b/lib/checks/landmarks/landmark-is-unique-evaluate.js index f8f073d19c..f8375fed5f 100644 --- a/lib/checks/landmarks/landmark-is-unique-evaluate.js +++ b/lib/checks/landmarks/landmark-is-unique-evaluate.js @@ -2,8 +2,8 @@ import { getRole } from '../../commons/aria'; import { accessibleTextVirtual } from '../../commons/text'; function landmarkIsUniqueEvaluate(node, options, virtualNode) { - var role = getRole(node); - var accessibleText = accessibleTextVirtual(virtualNode); + const role = getRole(node); + let accessibleText = accessibleTextVirtual(virtualNode); accessibleText = accessibleText ? accessibleText.toLowerCase() : null; this.data({ role: role, accessibleText: accessibleText }); this.relatedNodes([node]); diff --git a/lib/checks/lists/structured-dlitems-evaluate.js b/lib/checks/lists/structured-dlitems-evaluate.js index b13fbc9800..ad2d7f837d 100644 --- a/lib/checks/lists/structured-dlitems-evaluate.js +++ b/lib/checks/lists/structured-dlitems-evaluate.js @@ -7,7 +7,7 @@ function structuredDlitemsEvaluate(node, options, virtualNode) { let hasDt = false, hasDd = false, nodeName; - for (var i = 0; i < children.length; i++) { + for (let i = 0; i < children.length; i++) { nodeName = children[i].props.nodeName.toUpperCase(); if (nodeName === 'DT') { hasDt = true; diff --git a/lib/checks/mobile/target-size-evaluate.js b/lib/checks/mobile/target-size-evaluate.js index 88c029f34e..1fb76e775c 100644 --- a/lib/checks/mobile/target-size-evaluate.js +++ b/lib/checks/mobile/target-size-evaluate.js @@ -135,7 +135,7 @@ function getLargestUnobscuredArea(vNode, obscuredNodes) { let unobscuredRects; try { unobscuredRects = splitRects(nodeRect, obscuringRects); - } catch (err) { + } catch { return null; } diff --git a/lib/checks/navigation/unique-frame-title-after.js b/lib/checks/navigation/unique-frame-title-after.js index a0f2bb4be3..e4a017219b 100644 --- a/lib/checks/navigation/unique-frame-title-after.js +++ b/lib/checks/navigation/unique-frame-title-after.js @@ -1,5 +1,5 @@ function uniqueFrameTitleAfter(results) { - var titles = {}; + const titles = {}; results.forEach(r => { titles[r.data] = titles[r.data] !== undefined ? ++titles[r.data] : 0; }); diff --git a/lib/checks/navigation/unique-frame-title-evaluate.js b/lib/checks/navigation/unique-frame-title-evaluate.js index 85e2eed58a..e00595a6b1 100644 --- a/lib/checks/navigation/unique-frame-title-evaluate.js +++ b/lib/checks/navigation/unique-frame-title-evaluate.js @@ -1,7 +1,7 @@ import { sanitize } from '../../commons/text'; function uniqueFrameTitleEvaluate(node, options, vNode) { - var title = sanitize(vNode.attr('title')).toLowerCase(); + const title = sanitize(vNode.attr('title')).toLowerCase(); this.data(title); return true; } diff --git a/lib/checks/parsing/duplicate-id-after.js b/lib/checks/parsing/duplicate-id-after.js index 7e170feb9a..e6a587185c 100644 --- a/lib/checks/parsing/duplicate-id-after.js +++ b/lib/checks/parsing/duplicate-id-after.js @@ -1,5 +1,5 @@ function duplicateIdAfter(results) { - var uniqueIds = []; + const uniqueIds = []; return results.filter(r => { if (uniqueIds.indexOf(r.data) === -1) { uniqueIds.push(r.data); diff --git a/lib/checks/shared/aria-labelledby-evaluate.js b/lib/checks/shared/aria-labelledby-evaluate.js index e9910ef52a..f01fa7bc94 100644 --- a/lib/checks/shared/aria-labelledby-evaluate.js +++ b/lib/checks/shared/aria-labelledby-evaluate.js @@ -4,7 +4,7 @@ import { arialabelledbyText } from '../../commons/aria'; function ariaLabelledbyEvaluate(node, options, virtualNode) { try { return !!sanitize(arialabelledbyText(virtualNode)); - } catch (e) { + } catch { return undefined; } } diff --git a/lib/checks/shared/aria-labelledby.json b/lib/checks/shared/aria-labelledby.json index f09d75fdad..0207baf864 100644 --- a/lib/checks/shared/aria-labelledby.json +++ b/lib/checks/shared/aria-labelledby.json @@ -6,7 +6,7 @@ "messages": { "pass": "aria-labelledby attribute exists and references elements that are visible to screen readers", "fail": "aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty", - "incomplete": "ensure aria-labelledby references an existing element" + "incomplete": "Ensure aria-labelledby references an existing element" } } } diff --git a/lib/checks/shared/doc-has-title-evaluate.js b/lib/checks/shared/doc-has-title-evaluate.js index 9a1de035ce..928154796f 100644 --- a/lib/checks/shared/doc-has-title-evaluate.js +++ b/lib/checks/shared/doc-has-title-evaluate.js @@ -1,7 +1,7 @@ import { sanitize } from '../../commons/text'; function docHasTitleEvaluate() { - var title = document.title; + const title = document.title; return !!sanitize(title); } diff --git a/lib/checks/shared/presentational-role.json b/lib/checks/shared/presentational-role.json index 7b8dfb3b08..152477696b 100644 --- a/lib/checks/shared/presentational-role.json +++ b/lib/checks/shared/presentational-role.json @@ -4,7 +4,7 @@ "metadata": { "impact": "minor", "messages": { - "pass": "Element's default semantics were overriden with role=\"${data.role}\"", + "pass": "Element's default semantics were overridden with role=\"${data.role}\"", "fail": { "default": "Element's default semantics were not overridden with role=\"none\" or role=\"presentation\"", "globalAria": "Element's role is not presentational because it has a global ARIA attribute", diff --git a/lib/checks/shared/role-none.json b/lib/checks/shared/role-none.json index dfae40a5bf..dd46b9148e 100644 --- a/lib/checks/shared/role-none.json +++ b/lib/checks/shared/role-none.json @@ -12,7 +12,7 @@ "metadata": { "impact": "minor", "messages": { - "pass": "Element's default semantics were overriden with role=\"none\"", + "pass": "Element's default semantics were overridden with role=\"none\"", "fail": "Element's default semantics were not overridden with role=\"none\"" } } diff --git a/lib/checks/shared/role-presentation.json b/lib/checks/shared/role-presentation.json index 8615e6c356..d083000af4 100644 --- a/lib/checks/shared/role-presentation.json +++ b/lib/checks/shared/role-presentation.json @@ -12,7 +12,7 @@ "metadata": { "impact": "minor", "messages": { - "pass": "Element's default semantics were overriden with role=\"presentation\"", + "pass": "Element's default semantics were overridden with role=\"presentation\"", "fail": "Element's default semantics were not overridden with role=\"presentation\"" } } diff --git a/lib/checks/shared/svg-non-empty-title-evaluate.js b/lib/checks/shared/svg-non-empty-title-evaluate.js index b614de2470..348f0f00b9 100644 --- a/lib/checks/shared/svg-non-empty-title-evaluate.js +++ b/lib/checks/shared/svg-non-empty-title-evaluate.js @@ -24,7 +24,7 @@ function svgNonEmptyTitleEvaluate(node, options, virtualNode) { }); return false; } - } catch (e) { + } catch { return undefined; } diff --git a/lib/checks/tables/caption-faked-evaluate.js b/lib/checks/tables/caption-faked-evaluate.js index 18a1bf2e01..2e99849712 100644 --- a/lib/checks/tables/caption-faked-evaluate.js +++ b/lib/checks/tables/caption-faked-evaluate.js @@ -1,8 +1,8 @@ import { toGrid } from '../../commons/table'; function captionFakedEvaluate(node) { - var table = toGrid(node); - var firstRow = table[0]; + const table = toGrid(node); + const firstRow = table[0]; if (table.length <= 1 || firstRow.length <= 1 || node.rows.length <= 1) { return true; diff --git a/lib/checks/tables/same-caption-summary-evaluate.js b/lib/checks/tables/same-caption-summary-evaluate.js index 6aafd3f087..2a2a9552ee 100644 --- a/lib/checks/tables/same-caption-summary-evaluate.js +++ b/lib/checks/tables/same-caption-summary-evaluate.js @@ -7,9 +7,9 @@ function sameCaptionSummaryEvaluate(node, options, virtualNode) { return undefined; } - var summary = virtualNode.attr('summary'); - var captionNode = virtualNode.children.find(isCaptionNode); - var caption = captionNode ? sanitize(subtreeText(captionNode)) : false; + const summary = virtualNode.attr('summary'); + const captionNode = virtualNode.children.find(isCaptionNode); + const caption = captionNode ? sanitize(subtreeText(captionNode)) : false; if (!caption || !summary) { return false; diff --git a/lib/checks/tables/scope-value-evaluate.js b/lib/checks/tables/scope-value-evaluate.js index 91e6b87c0f..170e8d7fcc 100644 --- a/lib/checks/tables/scope-value-evaluate.js +++ b/lib/checks/tables/scope-value-evaluate.js @@ -1,5 +1,5 @@ function scopeValueEvaluate(node, options) { - var value = node.getAttribute('scope').toLowerCase(); + const value = node.getAttribute('scope').toLowerCase(); return options.values.indexOf(value) !== -1; } diff --git a/lib/commons/aria/validate-attr-value.js b/lib/commons/aria/validate-attr-value.js index 862d8affae..d6f96b65b5 100644 --- a/lib/commons/aria/validate-attr-value.js +++ b/lib/commons/aria/validate-attr-value.js @@ -50,7 +50,7 @@ function validateAttrValue(vNode, attr) { try { const doc = getRootNode(vNode.actualNode); return !!(value && doc.getElementById(value)); - } catch (e) { + } catch { throw new TypeError('Cannot resolve id references for partial DOM'); } diff --git a/lib/commons/color/color.js b/lib/commons/color/color.js index c6cb6496a8..7a5eaa3af6 100644 --- a/lib/commons/color/color.js +++ b/lib/commons/color/color.js @@ -166,7 +166,7 @@ export default class Color { this.b = color.b; // color.alpha is a Number object so convert it to a number this.alpha = +color.alpha; - } catch (err) { + } catch { throw new Error(`Unable to parse color "${colorString}"`); } diff --git a/lib/commons/color/element-is-distinct.js b/lib/commons/color/element-is-distinct.js index a3ff6a6fae..ddb6c91cd8 100644 --- a/lib/commons/color/element-is-distinct.js +++ b/lib/commons/color/element-is-distinct.js @@ -25,7 +25,7 @@ function _getFonts(style) { * @return {Boolean} */ function elementIsDistinct(node, ancestorNode) { - var nodeStyle = window.getComputedStyle(node); + const nodeStyle = window.getComputedStyle(node); // Check if the link has a background if (nodeStyle.getPropertyValue('background-image') !== 'none') { @@ -33,9 +33,9 @@ function elementIsDistinct(node, ancestorNode) { } // Check if the link has a border or outline - var hasBorder = ['border-bottom', 'border-top', 'outline'].reduce( + const hasBorder = ['border-bottom', 'border-top', 'outline'].reduce( (result, edge) => { - var borderClr = new Color(); + const borderClr = new Color(); borderClr.parseString(nodeStyle.getPropertyValue(edge + '-color')); // Check if a border/outline was specified @@ -54,13 +54,13 @@ function elementIsDistinct(node, ancestorNode) { return true; } - var parentStyle = window.getComputedStyle(ancestorNode); + const parentStyle = window.getComputedStyle(ancestorNode); // Compare fonts if (_getFonts(nodeStyle)[0] !== _getFonts(parentStyle)[0]) { return true; } - var hasStyle = [ + let hasStyle = [ 'text-decoration-line', 'text-decoration-style', 'font-weight', @@ -74,7 +74,7 @@ function elementIsDistinct(node, ancestorNode) { ); }, false); - var tDec = nodeStyle.getPropertyValue('text-decoration'); + const tDec = nodeStyle.getPropertyValue('text-decoration'); if (tDec.split(' ').length < 3) { // old style CSS text decoration hasStyle = diff --git a/lib/commons/color/flatten-shadow-colors.js b/lib/commons/color/flatten-shadow-colors.js index 448462367a..c4cfcba87a 100644 --- a/lib/commons/color/flatten-shadow-colors.js +++ b/lib/commons/color/flatten-shadow-colors.js @@ -11,11 +11,11 @@ import Color from './color'; * @return {Color} Blended color */ export default function flattenShadowColors(fgColor, bgColor) { - var alpha = fgColor.alpha; - var r = (1 - alpha) * bgColor.red + alpha * fgColor.red; - var g = (1 - alpha) * bgColor.green + alpha * fgColor.green; - var b = (1 - alpha) * bgColor.blue + alpha * fgColor.blue; - var a = fgColor.alpha + bgColor.alpha * (1 - fgColor.alpha); + const alpha = fgColor.alpha; + const r = (1 - alpha) * bgColor.red + alpha * fgColor.red; + const g = (1 - alpha) * bgColor.green + alpha * fgColor.green; + const b = (1 - alpha) * bgColor.blue + alpha * fgColor.blue; + const a = fgColor.alpha + bgColor.alpha * (1 - fgColor.alpha); return new Color(r, g, b, a); } diff --git a/lib/commons/color/get-background-stack.js b/lib/commons/color/get-background-stack.js index cf417fcea4..652c1596de 100644 --- a/lib/commons/color/get-background-stack.js +++ b/lib/commons/color/get-background-stack.js @@ -98,7 +98,7 @@ function shallowArraysEqual(a, b) { return false; } - for (var i = 0; i < a.length; ++i) { + for (let i = 0; i < a.length; ++i) { if (a[i] !== b[i]) { return false; } diff --git a/lib/commons/color/get-contrast.js b/lib/commons/color/get-contrast.js index 1ece583cc0..f70af85afe 100644 --- a/lib/commons/color/get-contrast.js +++ b/lib/commons/color/get-contrast.js @@ -18,8 +18,8 @@ function getContrast(bgColor, fgColor) { fgColor = flattenColors(fgColor, bgColor); } - var bL = bgColor.getRelativeLuminance(); - var fL = fgColor.getRelativeLuminance(); + const bL = bgColor.getRelativeLuminance(); + const fL = fgColor.getRelativeLuminance(); return (Math.max(fL, bL) + 0.05) / (Math.min(fL, bL) + 0.05); } diff --git a/lib/commons/color/has-valid-contrast-ratio.js b/lib/commons/color/has-valid-contrast-ratio.js index 3259ed3fe1..e68024eb7d 100644 --- a/lib/commons/color/has-valid-contrast-ratio.js +++ b/lib/commons/color/has-valid-contrast-ratio.js @@ -14,11 +14,11 @@ import getContrast from './get-contrast'; * @deprecated */ function hasValidContrastRatio(bg, fg, fontSize, isBold) { - var contrast = getContrast(bg, fg); - var isSmallFont = + const contrast = getContrast(bg, fg); + const isSmallFont = (isBold && Math.ceil(fontSize * 72) / 96 < 14) || (!isBold && Math.ceil(fontSize * 72) / 96 < 18); - var expectedContrastRatio = isSmallFont ? 4.5 : 3; + const expectedContrastRatio = isSmallFont ? 4.5 : 3; return { isValid: contrast > expectedContrastRatio, diff --git a/lib/commons/dom/get-composed-parent.js b/lib/commons/dom/get-composed-parent.js index 3857a0bd43..ddb28945a9 100644 --- a/lib/commons/dom/get-composed-parent.js +++ b/lib/commons/dom/get-composed-parent.js @@ -13,7 +13,7 @@ function getComposedParent(element) { // we'll skip this part for now. return getComposedParent(element.assignedSlot); // parent of a shadow DOM slot } else if (element.parentNode) { - var parentNode = element.parentNode; + const parentNode = element.parentNode; if (parentNode.nodeType === 1) { return parentNode; // Regular node } else if (parentNode.host) { diff --git a/lib/commons/dom/get-element-coordinates.js b/lib/commons/dom/get-element-coordinates.js index b3dfecd79b..1bbf26e7ba 100644 --- a/lib/commons/dom/get-element-coordinates.js +++ b/lib/commons/dom/get-element-coordinates.js @@ -20,7 +20,7 @@ import getScrollOffset from './get-scroll-offset'; * @property {Number} height The height of the element */ function getElementCoordinates(element) { - var scrollOffset = getScrollOffset(document), + const scrollOffset = getScrollOffset(document), xOffset = scrollOffset.left, yOffset = scrollOffset.top, coords = element.getBoundingClientRect(); diff --git a/lib/commons/dom/get-scroll-offset.js b/lib/commons/dom/get-scroll-offset.js index f1f4aec905..e5ca0d184d 100644 --- a/lib/commons/dom/get-scroll-offset.js +++ b/lib/commons/dom/get-scroll-offset.js @@ -13,7 +13,7 @@ function getScrollOffset(element) { // 9 === Node.DOCUMENT_NODE if (element.nodeType === 9) { - var docElement = element.documentElement, + const docElement = element.documentElement, body = element.body; return { diff --git a/lib/commons/dom/has-content-virtual.js b/lib/commons/dom/has-content-virtual.js index ae71c1f7d0..691555262d 100644 --- a/lib/commons/dom/has-content-virtual.js +++ b/lib/commons/dom/has-content-virtual.js @@ -42,12 +42,12 @@ export function hasChildTextNodes(elm) { function hasContentVirtual(elm, noRecursion, ignoreAria) { return ( // It has text + // or one of it's descendants does hasChildTextNodes(elm) || // It is a graphical element isVisualContent(elm.actualNode) || // It has an ARIA label (!ignoreAria && !!labelVirtual(elm)) || - // or one of it's descendants does (!noRecursion && elm.children.some( child => child.actualNode.nodeType === 1 && hasContentVirtual(child) diff --git a/lib/commons/dom/idrefs.js b/lib/commons/dom/idrefs.js index 2d044f95c8..be3dc56db0 100644 --- a/lib/commons/dom/idrefs.js +++ b/lib/commons/dom/idrefs.js @@ -33,7 +33,7 @@ function idrefs(node, attr) { } return result; - } catch (e) { + } catch { throw new TypeError('Cannot resolve id references for non-DOM nodes'); } } diff --git a/lib/commons/dom/is-focusable.js b/lib/commons/dom/is-focusable.js index 27b6dd133b..b9411c9c0d 100644 --- a/lib/commons/dom/is-focusable.js +++ b/lib/commons/dom/is-focusable.js @@ -23,7 +23,7 @@ export default function isFocusable(el) { return true; } // check if the tabindex is specified and a parseable number - var tabindex = vNode.attr('tabindex'); + const tabindex = vNode.attr('tabindex'); if (tabindex && !isNaN(parseInt(tabindex, 10))) { return true; } diff --git a/lib/commons/dom/is-in-text-block.js b/lib/commons/dom/is-in-text-block.js index fe390ce4d0..3f4a4f39bb 100644 --- a/lib/commons/dom/is-in-text-block.js +++ b/lib/commons/dom/is-in-text-block.js @@ -19,7 +19,7 @@ const blockLike = [ ]; function isBlock(elm) { - var display = window.getComputedStyle(elm).getPropertyValue('display'); + const display = window.getComputedStyle(elm).getPropertyValue('display'); return blockLike.includes(display) || display.substr(0, 6) === 'table-'; } @@ -72,7 +72,7 @@ function isInTextBlock(node, options) { return; } - var nodeName = (currNode.nodeName || '').toUpperCase(); + const nodeName = (currNode.nodeName || '').toUpperCase(); if (currNode === node) { inBrBlock = 1; } diff --git a/lib/commons/dom/visually-overlaps.js b/lib/commons/dom/visually-overlaps.js index ecfbcba9a2..887fd6794d 100644 --- a/lib/commons/dom/visually-overlaps.js +++ b/lib/commons/dom/visually-overlaps.js @@ -8,10 +8,10 @@ * @return {boolean} True if rect is visually contained within parent */ function visuallyOverlaps(rect, parent) { - var parentRect = parent.getBoundingClientRect(); - var parentTop = parentRect.top; - var parentLeft = parentRect.left; - var parentScrollArea = { + const parentRect = parent.getBoundingClientRect(); + const parentTop = parentRect.top; + const parentLeft = parentRect.left; + const parentScrollArea = { top: parentTop - parent.scrollTop, bottom: parentTop - parent.scrollTop + parent.scrollHeight, left: parentLeft - parent.scrollLeft, @@ -29,7 +29,7 @@ function visuallyOverlaps(rect, parent) { return false; } - var style = window.getComputedStyle(parent); + const style = window.getComputedStyle(parent); if (rect.left > parentRect.right || rect.top > parentRect.bottom) { return ( diff --git a/lib/commons/index.js b/lib/commons/index.js index cbbf9d7e4e..9ab285a827 100644 --- a/lib/commons/index.js +++ b/lib/commons/index.js @@ -17,7 +17,7 @@ import * as table from './table'; import * as text from './text'; import * as utils from '../core/utils'; -var commons = { +const commons = { aria, color, dom, diff --git a/lib/commons/standards/implicit-html-roles.js b/lib/commons/standards/implicit-html-roles.js index b302f0319b..579c166954 100644 --- a/lib/commons/standards/implicit-html-roles.js +++ b/lib/commons/standards/implicit-html-roles.js @@ -13,17 +13,32 @@ import { closest } from '../../core/utils'; import cache from '../../core/base/cache'; import getExplicitRole from '../aria/get-explicit-role'; -const getSectioningElementSelector = () => { - return cache.get('sectioningElementSelector', () => { +// Sectioning content elements: article, aside, nav, section +// https://html.spec.whatwg.org/multipage/dom.html#sectioning-content +const getSectioningContentSelector = () => { + return cache.get('sectioningContentSelector', () => { return ( getElementsByContentType('sectioning') .map(nodeName => `${nodeName}:not([role])`) .join(', ') + - ' , main:not([role]), [role=article], [role=complementary], [role=main], [role=navigation], [role=region]' + ' , [role=article], [role=complementary], [role=navigation], [role=region]' ); }); }; +const getSectioningContentPlusMainSelector = () => { + // Why is there this similar but slightly different selector? + // -> + // Asides can be scoped to body or main, but headers and footers must be + // scoped **only** to body (for landmark role mapping). + // - Header: https://w3c.github.io/html-aam/#el-header-ancestorbody + // - Footer: https://w3c.github.io/html-aam/#el-footer-ancestorbody + // - Aside: https://w3c.github.io/html-aam/#el-aside-ancestorbodymain + return cache.get('sectioningContentPlusMainSelector', () => { + return getSectioningContentSelector() + ' , main:not([role]), [role=main]'; + }); +}; + // sectioning elements only have an accessible name if the // aria-label, aria-labelledby, or title attribute has valid // content. @@ -36,18 +51,22 @@ const getSectioningElementSelector = () => { // specifically called out in the spec like section elements // (per Scott O'Hara) // Source: https://web-a11y.slack.com/archives/C042TSFGN/p1590607895241100?thread_ts=1590602189.217800&cid=C042TSFGN -function hasAccessibleName(vNode) { +// +// `checkTitle` means - also check the title attribute and +// return true if the node has a non-empty title +function hasAccessibleName(vNode, { checkTitle = false } = {}) { // testing for when browsers give a
a region role: // chrome - always a region role // firefox - if non-empty aria-labelledby, aria-label, or title - // safari - if non-empty aria-lablledby or aria-label + // safari - if non-empty aria-labelledby or aria-label // - // we will go with safaris implantation as it is the least common + // we will go with safaris implementation as it is the least common // denominator - const ariaLabelledby = sanitize(arialabelledbyText(vNode)); - const ariaLabel = sanitize(arialabelText(vNode)); - - return !!(ariaLabelledby || ariaLabel); + return !!( + sanitize(arialabelledbyText(vNode)) || + sanitize(arialabelText(vNode)) || + (checkTitle && vNode?.props.nodeType === 1 && sanitize(vNode.attr('title'))) + ); } const implicitHtmlRoles = { @@ -58,7 +77,18 @@ const implicitHtmlRoles = { return vNode.hasAttr('href') ? 'link' : null; }, article: 'article', - aside: 'complementary', + aside: vNode => { + if ( + closest(vNode.parent, getSectioningContentSelector()) && + // An aside within sectioning content can still be mapped to + // role=complementary if it has an accessible name + !hasAccessibleName(vNode, { checkTitle: true }) + ) { + return null; + } + + return 'complementary'; + }, body: 'document', button: 'button', datalist: 'listbox', @@ -70,7 +100,10 @@ const implicitHtmlRoles = { fieldset: 'group', figure: 'figure', footer: vNode => { - const sectioningElement = closest(vNode, getSectioningElementSelector()); + const sectioningElement = closest( + vNode, + getSectioningContentPlusMainSelector() + ); return !sectioningElement ? 'contentinfo' : null; }, @@ -84,7 +117,10 @@ const implicitHtmlRoles = { h5: 'heading', h6: 'heading', header: vNode => { - const sectioningElement = closest(vNode, getSectioningElementSelector()); + const sectioningElement = closest( + vNode, + getSectioningContentPlusMainSelector() + ); return !sectioningElement ? 'banner' : null; }, diff --git a/lib/commons/table/get-all-cells.js b/lib/commons/table/get-all-cells.js index a85480cd82..6f8beb5a42 100644 --- a/lib/commons/table/get-all-cells.js +++ b/lib/commons/table/get-all-cells.js @@ -7,8 +7,8 @@ * @return {Array} */ function getAllCells(tableElm) { - var rowIndex, cellIndex, rowLength, cellLength; - var cells = []; + let rowIndex, cellIndex, rowLength, cellLength; + const cells = []; for ( rowIndex = 0, rowLength = tableElm.rows.length; rowIndex < rowLength; diff --git a/lib/commons/table/get-cell-position.js b/lib/commons/table/get-cell-position.js index 8d7f6faf6e..d251c4cab9 100644 --- a/lib/commons/table/get-cell-position.js +++ b/lib/commons/table/get-cell-position.js @@ -11,7 +11,7 @@ import { memoize } from '../../core/utils'; * @return {Object} Object with `x` and `y` properties of the coordinates */ function getCellPosition(cell, tableGrid) { - var rowIndex, index; + let rowIndex, index; if (!tableGrid) { tableGrid = toGrid(findUp(cell, 'table')); } diff --git a/lib/commons/table/is-data-table.js b/lib/commons/table/is-data-table.js index 852c98c020..663f65e2cb 100644 --- a/lib/commons/table/is-data-table.js +++ b/lib/commons/table/is-data-table.js @@ -14,7 +14,7 @@ import getViewportSize from '../dom/get-viewport-size'; * @see http://asurkov.blogspot.co.uk/2011/10/data-vs-layout-table.html */ function isDataTable(node) { - var role = (node.getAttribute('role') || '').toLowerCase(); + const role = (node.getAttribute('role') || '').toLowerCase(); // The element is not focusable and has role=presentation if ((role === 'presentation' || role === 'none') && !isFocusable(node)) { @@ -55,7 +55,7 @@ function isDataTable(node) { } // colgroup / col - colgroup is magically generated for ( - var childIndex = 0, childLength = node.children.length; + let childIndex = 0, childLength = node.children.length; childIndex < childLength; childIndex++ ) { @@ -64,14 +64,14 @@ function isDataTable(node) { } } - var cells = 0; - var rowLength = node.rows.length; - var row, cell; - var hasBorder = false; - for (var rowIndex = 0; rowIndex < rowLength; rowIndex++) { + let cells = 0; + const rowLength = node.rows.length; + let row, cell; + let hasBorder = false; + for (let rowIndex = 0; rowIndex < rowLength; rowIndex++) { row = node.rows[rowIndex]; for ( - var cellIndex = 0, cellLength = row.cells.length; + let cellIndex = 0, cellLength = row.cells.length; cellIndex < cellLength; cellIndex++ ) { @@ -122,7 +122,7 @@ function isDataTable(node) { } // Table having only one row or column is layout table (column) - var sampleRow = node.rows[Math.ceil(rowLength / 2)]; + const sampleRow = node.rows[Math.ceil(rowLength / 2)]; if (sampleRow.cells.length === 1 && sampleRow.cells[0].colSpan === 1) { return false; } @@ -138,8 +138,8 @@ function isDataTable(node) { } // Table having differently colored rows is data table - var bgColor, bgImage; - for (rowIndex = 0; rowIndex < rowLength; rowIndex++) { + let bgColor, bgImage; + for (let rowIndex = 0; rowIndex < rowLength; rowIndex++) { row = node.rows[rowIndex]; if ( bgColor && diff --git a/lib/commons/table/to-grid.js b/lib/commons/table/to-grid.js index 0463c491cf..89a369c440 100644 --- a/lib/commons/table/to-grid.js +++ b/lib/commons/table/to-grid.js @@ -9,16 +9,16 @@ import { memoize } from '../../core/utils'; * @return {Array} Array of HTMLTableCellElements */ function toGrid(node) { - var table = []; - var rows = node.rows; - for (var i = 0, rowLength = rows.length; i < rowLength; i++) { - var cells = rows[i].cells; + const table = []; + const rows = node.rows; + for (let i = 0, rowLength = rows.length; i < rowLength; i++) { + const cells = rows[i].cells; table[i] = table[i] || []; - var columnIndex = 0; + let columnIndex = 0; - for (var j = 0, cellLength = cells.length; j < cellLength; j++) { - for (var colSpan = 0; colSpan < cells[j].colSpan; colSpan++) { + for (let j = 0, cellLength = cells.length; j < cellLength; j++) { + for (let colSpan = 0; colSpan < cells[j].colSpan; colSpan++) { // if [the rowSpan] value is set to 0, it extends until the // end of the table section // @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td#attr-rowspan @@ -30,7 +30,7 @@ function toGrid(node) { ? rows.length : cells[j].rowSpan; - for (var rowSpan = 0; rowSpan < rowspanValue; rowSpan++) { + for (let rowSpan = 0; rowSpan < rowspanValue; rowSpan++) { table[i + rowSpan] = table[i + rowSpan] || []; while (table[i + rowSpan][columnIndex]) { columnIndex++; diff --git a/lib/commons/text/is-valid-autocomplete.js b/lib/commons/text/is-valid-autocomplete.js index 8ca49035a2..91b4928ca7 100644 --- a/lib/commons/text/is-valid-autocomplete.js +++ b/lib/commons/text/is-valid-autocomplete.js @@ -70,7 +70,8 @@ function isValidAutocomplete( locations = [], qualifiers = [], standaloneTerms = [], - qualifiedTerms = [] + qualifiedTerms = [], + ignoredValues = [] } = {} ) { autocompleteValue = autocompleteValue.toLowerCase().trim(); @@ -117,6 +118,11 @@ function isValidAutocomplete( } const purposeTerm = autocompleteTerms[autocompleteTerms.length - 1]; + + if (ignoredValues.includes(purposeTerm)) { + return undefined; + } + return ( standaloneTerms.includes(purposeTerm) || qualifiedTerms.includes(purposeTerm) diff --git a/lib/commons/text/label-virtual.js b/lib/commons/text/label-virtual.js index acc82ec6a0..0473a2e5e0 100644 --- a/lib/commons/text/label-virtual.js +++ b/lib/commons/text/label-virtual.js @@ -14,7 +14,7 @@ import { closest, escapeSelector } from '../../core/utils'; * @return {Mixed} String of visible text, or `null` if no label is found */ function labelVirtual(virtualNode) { - var ref, candidate, doc; + let ref, candidate, doc; candidate = ariaLabelVirtual(virtualNode); if (candidate) { diff --git a/lib/core/base/audit.js b/lib/core/base/audit.js index 47b57f66d9..36bcb325ff 100644 --- a/lib/core/base/audit.js +++ b/lib/core/base/audit.js @@ -183,7 +183,7 @@ export default class Audit { * Initializes the rules and checks */ _init() { - var audit = getDefaultConfiguration(this.defaultConfig); + const audit = getDefaultConfiguration(this.defaultConfig); this.lang = audit.lang || 'en'; this.reporter = audit.reporter; this.commands = {}; @@ -191,7 +191,7 @@ export default class Audit { this.checks = {}; this.brand = 'axe'; this.application = 'axeAPI'; - this.tagExclude = ['experimental']; + this.tagExclude = ['experimental', 'deprecated']; this.noHtml = audit.noHtml; this.allowedOrigins = audit.allowedOrigins; unpackToObject(audit.rules, this, 'addRule'); @@ -366,9 +366,9 @@ export default class Audit { * @param {Mixed} options Options object to pass into rules and/or disable rules or checks */ after(results, options) { - var rules = this.rules; + const rules = this.rules; return results.map(ruleResult => { - var rule = findBy(rules, 'id', ruleResult.id); + const rule = findBy(rules, 'id', ruleResult.id); if (!rule) { // If you see this, you're probably running the Mocha tests with the axe extension installed throw new Error( @@ -393,7 +393,7 @@ export default class Audit { * @return {Object} Validated options object */ normalizeOptions(options) { - var audit = this; + const audit = this; const tags = []; const ruleIds = []; audit.rules.forEach(rule => { @@ -498,7 +498,7 @@ export default class Audit { } _constructHelpUrls(previous = null) { // TODO: es-modules-version - var version = (axe.version.match(/^[1-9][0-9]*\.[0-9]+/) || ['x.y'])[0]; + const version = (axe.version.match(/^[1-9][0-9]*\.[0-9]+/) || ['x.y'])[0]; this.rules.forEach(rule => { if (!this.data.rules[rule.id]) { this.data.rules[rule.id] = {}; diff --git a/lib/core/base/context/create-frame-context.js b/lib/core/base/context/create-frame-context.js index cbe52eb3aa..abb396ecf1 100644 --- a/lib/core/base/context/create-frame-context.js +++ b/lib/core/base/context/create-frame-context.js @@ -11,7 +11,7 @@ export function createFrameContext(frame, { focusable, page }) { } function frameFocusable(frame) { - var tabIndex = frame.getAttribute('tabindex'); + const tabIndex = frame.getAttribute('tabindex'); if (!tabIndex) { return true; } diff --git a/lib/core/base/context/normalize-context.js b/lib/core/base/context/normalize-context.js index 8187b3d64e..a3dbb28ef2 100644 --- a/lib/core/base/context/normalize-context.js +++ b/lib/core/base/context/normalize-context.js @@ -1,4 +1,12 @@ -import { assert as utilsAssert } from '../../utils'; +import { + assert as utilsAssert, + objectHasOwn, + isArrayLike, + isContextObject, + isContextProp, + isLabelledFramesSelector, + isLabelledShadowDomSelector +} from '../../utils'; /** * Normalize the input of "context" so that many different methods of input are accepted @@ -29,16 +37,6 @@ export function normalizeContext(contextSpec) { return { include, exclude }; } -/** - * Determine if some value can be parsed as a context - * @private - * @param {Mixed} contextSpec The configuration object passed to `Context` - * @return {boolea} - */ -export function isContextSpec(contextSpec) { - return isContextObject(contextSpec) || isContextProp(contextSpec); -} - function normalizeContextList(selectorList = []) { const normalizedList = []; if (!isArrayLike(selectorList)) { @@ -89,30 +87,6 @@ function normalizeFrameSelectors(frameSelectors) { return normalizedSelectors; } -function isContextObject(contextSpec) { - return ['include', 'exclude'].some( - prop => objectHasOwn(contextSpec, prop) && isContextProp(contextSpec[prop]) - ); -} - -function isContextProp(contextList) { - return ( - typeof contextList === 'string' || - contextList instanceof window.Node || - isLabelledFramesSelector(contextList) || - isLabelledShadowDomSelector(contextList) || - isArrayLike(contextList) - ); -} - -function isLabelledFramesSelector(selector) { - return objectHasOwn(selector, 'fromFrames'); -} - -function isLabelledShadowDomSelector(selector) { - return objectHasOwn(selector, 'fromShadowDom'); -} - function assertLabelledFrameSelector(selector) { assert( Array.isArray(selector.fromFrames), @@ -157,15 +131,6 @@ function isShadowSelector(selector) { ); } -function isArrayLike(arr) { - return ( - arr && - typeof arr === 'object' && - typeof arr.length === 'number' && - arr instanceof window.Node === false // Avoid DOM weirdness - ); -} - // Wrapper to ensure the correct message function assert(bool, str) { utilsAssert( @@ -173,11 +138,3 @@ function assert(bool, str) { `Invalid context; ${str}\nSee: https://github.com/dequelabs/axe-core/blob/master/doc/context.md` ); } - -// Wrapper to prevent throwing for non-objects & null -function objectHasOwn(obj, prop) { - if (!obj || typeof obj !== 'object') { - return false; - } - return Object.prototype.hasOwnProperty.call(obj, prop); -} diff --git a/lib/core/base/rule.js b/lib/core/base/rule.js index a5995adfe4..37b59904fe 100644 --- a/lib/core/base/rule.js +++ b/lib/core/base/rule.js @@ -137,7 +137,7 @@ Rule.prototype.gather = function gather(context, options = {}) { performanceTimer.mark(markStart); } - var elements = select(this.selector, context); + let elements = select(this.selector, context); if (this.excludeHidden) { if (options.performanceTimer) { performanceTimer.mark(markHiddenStart); @@ -173,13 +173,13 @@ Rule.prototype.runChecks = function runChecks( resolve, reject ) { - var self = this; + const self = this; - var checkQueue = queue(); + const checkQueue = queue(); this[type].forEach(c => { - var check = self._audit.checks[c.id || c]; - var option = getCheckOption(check, self.id, options); + const check = self._audit.checks[c.id || c]; + const option = getCheckOption(check, self.id, options); checkQueue.defer((res, rej) => { check.run(node, option, context, res, rej); }); @@ -246,7 +246,7 @@ Rule.prototype.run = function run(context, options = {}, resolve, reject) { nodes.forEach(node => { q.defer((resolveNode, rejectNode) => { - var checkQueue = queue(); + const checkQueue = queue(); ['any', 'all', 'none'].forEach(type => { checkQueue.defer((res, rej) => { @@ -466,7 +466,7 @@ Rule.prototype.gatherAndMatchNodes = function gatherAndMatchNodes( function findAfterChecks(rule) { return getAllChecks(rule) .map(c => { - var check = rule._audit.checks[c.id || c]; + const check = rule._audit.checks[c.id || c]; return check && typeof check.after === 'function' ? check : null; }) .filter(Boolean); @@ -480,9 +480,9 @@ function findAfterChecks(rule) { * @return {Array} Matching CheckResults */ function findCheckResults(nodes, checkID) { - var checkResults = []; + const checkResults = []; nodes.forEach(nodeResult => { - var checks = getAllChecks(nodeResult); + const checks = getAllChecks(nodeResult); checks.forEach(checkResult => { if (checkResult.id === checkID) { checkResult.node = nodeResult.node; @@ -500,10 +500,10 @@ function filterChecks(checks) { } function sanitizeNodes(result) { - var checkTypes = ['any', 'all', 'none']; + const checkTypes = ['any', 'all', 'none']; - var nodes = result.nodes.filter(detail => { - var length = 0; + let nodes = result.nodes.filter(detail => { + let length = 0; checkTypes.forEach(type => { detail[type] = filterChecks(detail[type]); length += detail[type].length; @@ -533,13 +533,13 @@ function sanitizeNodes(result) { * @return {RuleResult} The RuleResult as filtered by after functions */ Rule.prototype.after = function after(result, options) { - var afterChecks = findAfterChecks(this); - var ruleID = this.id; + const afterChecks = findAfterChecks(this); + const ruleID = this.id; afterChecks.forEach(check => { - var beforeResults = findCheckResults(result.nodes, check.id); - var checkOption = getCheckOption(check, ruleID, options); + const beforeResults = findCheckResults(result.nodes, check.id); + const checkOption = getCheckOption(check, ruleID, options); - var afterResults = check.after(beforeResults, checkOption.options); + const afterResults = check.after(beforeResults, checkOption.options); if (this.reviewOnFail) { afterResults.forEach(checkResult => { diff --git a/lib/core/imports/polyfills.js b/lib/core/imports/polyfills.js index 7ad8adb4a1..de47772a5b 100644 --- a/lib/core/imports/polyfills.js +++ b/lib/core/imports/polyfills.js @@ -49,11 +49,11 @@ if (typeof Object.assign !== 'function') { throw new TypeError('Cannot convert undefined or null to object'); } - var output = Object(target); - for (var index = 1; index < arguments.length; index++) { - var source = arguments[index]; + let output = Object(target); + for (let index = 1; index < arguments.length; index++) { + let source = arguments[index]; if (source !== undefined && source !== null) { - for (var nextKey in source) { + for (let nextKey in source) { if (source.hasOwnProperty(nextKey)) { output[nextKey] = source[nextKey]; } @@ -74,12 +74,12 @@ if (!Array.prototype.find) { if (typeof predicate !== 'function') { throw new TypeError('predicate must be a function'); } - var list = Object(this); - var length = list.length >>> 0; - var thisArg = arguments[1]; - var value; + let list = Object(this); + let length = list.length >>> 0; + let thisArg = arguments[1]; + let value; - for (var i = 0; i < length; i++) { + for (let i = 0; i < length; i++) { value = list[i]; if (predicate.call(thisArg, value, i, list)) { return value; @@ -99,11 +99,11 @@ if (!Array.prototype.findIndex) { if (typeof predicate !== 'function') { throw new TypeError('predicate must be a function'); } - var list = Object(this); - var length = list.length >>> 0; - var value; + let list = Object(this); + let length = list.length >>> 0; + let value; - for (var i = 0; i < length; i++) { + for (let i = 0; i < length; i++) { value = list[i]; if (predicate.call(thisArg, value, i, list)) { return i; @@ -117,13 +117,13 @@ if (!Array.prototype.findIndex) { if (!Array.prototype.includes) { Object.defineProperty(Array.prototype, 'includes', { value: function (searchElement) { - var O = Object(this); - var len = parseInt(O.length, 10) || 0; + let O = Object(this); + let len = parseInt(O.length, 10) || 0; if (len === 0) { return false; } - var n = parseInt(arguments[1], 10) || 0; - var k; + let n = parseInt(arguments[1], 10) || 0; + let k; if (n >= 0) { k = n; } else { @@ -132,7 +132,7 @@ if (!Array.prototype.includes) { k = 0; } } - var currentElement; + let currentElement; while (k < len) { currentElement = O[k]; if ( @@ -162,11 +162,11 @@ if (!Array.prototype.some) { throw new TypeError(); } - var t = Object(this); - var len = t.length >>> 0; + let t = Object(this); + let len = t.length >>> 0; - var thisArg = arguments.length >= 2 ? arguments[1] : void 0; - for (var i = 0; i < len; i++) { + let thisArg = arguments.length >= 2 ? arguments[1] : void 0; + for (let i = 0; i < len; i++) { if (i in t && fun.call(thisArg, t[i], i, t)) { return true; } @@ -199,7 +199,7 @@ if (!Array.prototype.flat) { Object.defineProperty(Array.prototype, 'flat', { configurable: true, value: function flat() { - var depth = isNaN(arguments[0]) ? 1 : Number(arguments[0]); + let depth = isNaN(arguments[0]) ? 1 : Number(arguments[0]); return depth ? Array.prototype.reduce.call( diff --git a/lib/core/index.js b/lib/core/index.js index 6aa45e4880..2d15d52824 100644 --- a/lib/core/index.js +++ b/lib/core/index.js @@ -2,7 +2,7 @@ /*global axeFunction, module, define */ // exported namespace for axe /*eslint no-use-before-define: 0, no-unused-vars: 0*/ -var axe = axe || {}; +const axe = axe || {}; axe.version = '<%= pkg.version %>'; if (typeof define === 'function' && define.amd) { @@ -24,7 +24,7 @@ if (typeof window.getComputedStyle === 'function') { window.axe = axe; } // local namespace for common functions -var commons; +let commons; function SupportError(error) { this.name = 'SupportError'; diff --git a/lib/core/public/cleanup.js b/lib/core/public/cleanup.js index 76492c592a..6b5de2555e 100644 --- a/lib/core/public/cleanup.js +++ b/lib/core/public/cleanup.js @@ -6,13 +6,13 @@ function cleanup(resolve, reject) { throw new Error('No audit configured'); } - var q = axe.utils.queue(); + const q = axe.utils.queue(); // If a plugin fails it's cleanup, we still want the others to run - var cleanupErrors = []; + const cleanupErrors = []; Object.keys(axe.plugins).forEach(key => { q.defer(res => { - var rej = function rej(err) { + const rej = function rej(err) { cleanupErrors.push(err); res(); }; @@ -24,7 +24,7 @@ function cleanup(resolve, reject) { }); }); - var flattenedTree = axe.utils.getFlattenedTree(document.body); + const flattenedTree = axe.utils.getFlattenedTree(document.body); axe.utils.querySelectorAll(flattenedTree, 'iframe, frame').forEach(node => { q.defer((res, rej) => { diff --git a/lib/core/public/configure.js b/lib/core/public/configure.js index cbd6424837..afe73582b1 100644 --- a/lib/core/public/configure.js +++ b/lib/core/public/configure.js @@ -3,9 +3,8 @@ import { configureStandards } from '../../standards'; import constants from '../constants'; function configure(spec) { - var audit; + const audit = axe._audit; - audit = axe._audit; if (!audit) { throw new Error('No audit configured'); } diff --git a/lib/core/public/get-rules.js b/lib/core/public/get-rules.js index d52216f776..fa23a3b5b2 100644 --- a/lib/core/public/get-rules.js +++ b/lib/core/public/get-rules.js @@ -5,7 +5,7 @@ */ function getRules(tags) { tags = tags || []; - var matchingRules = !tags.length + const matchingRules = !tags.length ? axe._audit.rules : axe._audit.rules.filter(item => { return !!tags.filter(tag => { @@ -13,9 +13,9 @@ function getRules(tags) { }).length; }); - var ruleData = axe._audit.data.rules || {}; + const ruleData = axe._audit.data.rules || {}; return matchingRules.map(matchingRule => { - var rd = ruleData[matchingRule.id] || {}; + const rd = ruleData[matchingRule.id] || {}; return { ruleId: matchingRule.id, description: rd.description, diff --git a/lib/core/public/load.js b/lib/core/public/load.js index 124c849b12..a6dbe6972e 100644 --- a/lib/core/public/load.js +++ b/lib/core/public/load.js @@ -14,19 +14,19 @@ export default function load(audit) { } function runCommand(data, keepalive, callback) { - var resolve = callback; - var reject = function reject(err) { + const resolve = callback; + const reject = function reject(err) { if (err instanceof Error === false) { err = new Error(err); } callback(err); }; - var context = (data && data.context) || {}; + const context = (data && data.context) || {}; if (context.hasOwnProperty('include') && !context.include.length) { context.include = [document]; } - var options = (data && data.options) || {}; + const options = (data && data.options) || {}; switch (data.command) { case 'rules': diff --git a/lib/core/public/plugins.js b/lib/core/public/plugins.js index 5b20b4f8cb..4da9100d61 100644 --- a/lib/core/public/plugins.js +++ b/lib/core/public/plugins.js @@ -18,8 +18,8 @@ Plugin.prototype.collect = function collect() { }; Plugin.prototype.cleanup = function cleanup(done) { - var q = axe.utils.queue(); - var that = this; + const q = axe.utils.queue(); + const that = this; Object.keys(this._registry).forEach(key => { q.defer(_done => { that._registry[key].cleanup(_done); diff --git a/lib/core/public/reset.js b/lib/core/public/reset.js index 586224bb8e..ae33123f0d 100644 --- a/lib/core/public/reset.js +++ b/lib/core/public/reset.js @@ -1,7 +1,7 @@ import { resetStandards } from '../../standards'; function reset() { - var audit = axe._audit; + const audit = axe._audit; if (!audit) { throw new Error('No audit configured'); diff --git a/lib/core/public/run-rules.js b/lib/core/public/run-rules.js index 5eb26d7c9f..7824080821 100644 --- a/lib/core/public/run-rules.js +++ b/lib/core/public/run-rules.js @@ -29,8 +29,8 @@ export default function runRules(context, options, resolve, reject) { return reject(e); } - var q = queue(); - var audit = axe._audit; + const q = queue(); + const audit = axe._audit; if (options.performanceTimer) { performanceTimer.auditStart(); @@ -51,7 +51,7 @@ export default function runRules(context, options, resolve, reject) { } // Add wrapper object so that we may use the same "merge" function for results from inside and outside frames - var results = mergeResults( + let results = mergeResults( data.map(res => { return { results: res }; }) diff --git a/lib/core/public/run/normalize-run-params.js b/lib/core/public/run/normalize-run-params.js index 96c4b18f4e..c952684b92 100644 --- a/lib/core/public/run/normalize-run-params.js +++ b/lib/core/public/run/normalize-run-params.js @@ -1,5 +1,4 @@ -import { clone } from '../../utils'; -import { isContextSpec } from '../../base/context/normalize-context'; +import { clone, isContextSpec } from '../../utils'; /** * Normalize the optional params of axe.run() diff --git a/lib/core/reporters/helpers/failure-summary.js b/lib/core/reporters/helpers/failure-summary.js index a35c8554bc..19f69e8d70 100644 --- a/lib/core/reporters/helpers/failure-summary.js +++ b/lib/core/reporters/helpers/failure-summary.js @@ -4,7 +4,7 @@ * @return {String} failure messages */ function failureSummary(nodeData) { - var failingChecks = {}; + const failingChecks = {}; // combine "all" and "none" as messaging is the same failingChecks.none = nodeData.none.concat(nodeData.all); failingChecks.any = nodeData.any; @@ -15,7 +15,7 @@ function failureSummary(nodeData) { return; } - var sum = axe._audit.data.failureSummaries[key]; + const sum = axe._audit.data.failureSummaries[key]; if (sum && typeof sum.failureMessage === 'function') { return sum.failureMessage( failingChecks[key].map(check => { diff --git a/lib/core/reporters/no-passes.js b/lib/core/reporters/no-passes.js index 3cc3cbe451..8c9c41c66c 100644 --- a/lib/core/reporters/no-passes.js +++ b/lib/core/reporters/no-passes.js @@ -10,7 +10,7 @@ const noPassesReporter = (results, options, callback) => { // limit result processing to types we want to include in the output options.resultTypes = ['violations']; - var { violations } = processAggregate(results, options); + const { violations } = processAggregate(results, options); callback({ ...getEnvironmentData(environmentData), diff --git a/lib/core/reporters/v2.js b/lib/core/reporters/v2.js index f4a63333ee..4597ca109b 100644 --- a/lib/core/reporters/v2.js +++ b/lib/core/reporters/v2.js @@ -7,7 +7,7 @@ const v2Reporter = (results, options, callback) => { options = {}; } const { environmentData, ...toolOptions } = options; - var out = processAggregate(results, options); + const out = processAggregate(results, options); callback({ ...getEnvironmentData(environmentData), toolOptions, diff --git a/lib/core/utils/aggregate-node-results.js b/lib/core/utils/aggregate-node-results.js index 151880e576..d7ce70675d 100644 --- a/lib/core/utils/aggregate-node-results.js +++ b/lib/core/utils/aggregate-node-results.js @@ -40,12 +40,12 @@ function aggregateNodeResults(nodeResults) { // Fill the array with nodes nodeResults.forEach(nodeResult => { - var groupName = constants.resultGroupMap[nodeResult.result]; + const groupName = constants.resultGroupMap[nodeResult.result]; ruleResult[groupName].push(nodeResult); }); // Take the highest impact of failed or canttell rules - var impactGroup = constants.FAIL_GROUP; + let impactGroup = constants.FAIL_GROUP; if (ruleResult[impactGroup].length === 0) { impactGroup = constants.CANTTELL_GROUP; } diff --git a/lib/core/utils/aggregate-result.js b/lib/core/utils/aggregate-result.js index d60122a060..ba7356d064 100644 --- a/lib/core/utils/aggregate-result.js +++ b/lib/core/utils/aggregate-result.js @@ -1,7 +1,7 @@ import constants from '../constants'; function copyToGroup(resultObject, subResult, group) { - var resultCopy = Object.assign({}, subResult); + const resultCopy = Object.assign({}, subResult); resultCopy.nodes = (resultCopy[group] || []).concat(); constants.resultGroups.forEach(resultGroup => { delete resultCopy[resultGroup]; diff --git a/lib/core/utils/aggregate.js b/lib/core/utils/aggregate.js index bdc186e017..16fcc0c013 100644 --- a/lib/core/utils/aggregate.js +++ b/lib/core/utils/aggregate.js @@ -13,7 +13,7 @@ function aggregate(map, values, initial) { values.push(initial); } - var sorting = values.map(val => map.indexOf(val)).sort(); // Stupid NodeJS array.sort functor doesn't work!! + const sorting = values.map(val => map.indexOf(val)).sort(); // Stupid NodeJS array.sort functor doesn't work!! return map[sorting.pop()]; } diff --git a/lib/core/utils/are-styles-set.js b/lib/core/utils/are-styles-set.js index 7192a83c97..c6332041fc 100644 --- a/lib/core/utils/are-styles-set.js +++ b/lib/core/utils/are-styles-set.js @@ -1,10 +1,10 @@ function areStylesSet(el, styles, stopAt) { - var styl = window.getComputedStyle(el, null); + const styl = window.getComputedStyle(el, null); if (!styl) { return false; } - for (var i = 0; i < styles.length; ++i) { - var att = styles[i]; + for (let i = 0; i < styles.length; ++i) { + const att = styles[i]; if (styl.getPropertyValue(att.property) === att.value) { return true; } diff --git a/lib/core/utils/collect-results-from-frames.js b/lib/core/utils/collect-results-from-frames.js index be17f8ccfd..7ca5de6e17 100644 --- a/lib/core/utils/collect-results-from-frames.js +++ b/lib/core/utils/collect-results-from-frames.js @@ -22,8 +22,8 @@ export default function collectResultsFromFrames( // elementRefs can't be passed across frame boundaries options = { ...options, elementRef: false }; - var q = queue(); - var frames = parentContent.frames; + const q = queue(); + const frames = parentContent.frames; // Tell each axe running in each frame to collect results frames.forEach(({ node: frameElement, ...context }) => { diff --git a/lib/core/utils/dq-element.js b/lib/core/utils/dq-element.js index 3cf790fe1a..ab259a0c47 100644 --- a/lib/core/utils/dq-element.js +++ b/lib/core/utils/dq-element.js @@ -4,6 +4,7 @@ import getXpath from './get-xpath'; import getNodeFromTree from './get-node-from-tree'; import AbstractVirtualNode from '../base/virtual-node/abstract-virtual-node'; import cache from '../base/cache'; +import memoize from './memoize'; const CACHE_KEY = 'DqElm.RunOptions'; @@ -11,7 +12,7 @@ function truncate(str, maxLength) { maxLength = maxLength || 300; if (str.length > maxLength) { - var index = str.indexOf('>'); + const index = str.indexOf('>'); str = str.substring(0, index + 1); } @@ -22,7 +23,7 @@ function getSource(element) { if (!element?.outerHTML) { return ''; } - var source = element.outerHTML; + let source = element.outerHTML; if (!source && typeof window.XMLSerializer === 'function') { source = new window.XMLSerializer().serializeToString(element); } @@ -36,7 +37,10 @@ function getSource(element) { * @param {Object} options Propagated from axe.run/etc * @param {Object} spec Properties to use in place of the element when instantiated on Elements from other frames */ -function DqElement(elm, options = null, spec = {}) { +const DqElement = memoize(function DqElement(elm, options, spec) { + options ??= null; + spec ??= {}; + if (!options) { options = cache.get(CACHE_KEY) ?? {}; } @@ -82,7 +86,9 @@ function DqElement(elm, options = null, spec = {}) { if (!axe._audit.noHtml) { this.source = this.spec.source ?? getSource(this._element); } -} + + return this; +}); DqElement.prototype = { /** diff --git a/lib/core/utils/element-matches.js b/lib/core/utils/element-matches.js index bad802d423..7399e59ff5 100644 --- a/lib/core/utils/element-matches.js +++ b/lib/core/utils/element-matches.js @@ -5,19 +5,18 @@ * @return {Boolean} */ const matchesSelector = (() => { - var method; + let method; function getMethod(node) { - var index, - candidate, - candidates = [ - 'matches', - 'matchesSelector', - 'mozMatchesSelector', - 'webkitMatchesSelector', - 'msMatchesSelector' - ], - length = candidates.length; + const candidates = [ + 'matches', + 'matchesSelector', + 'mozMatchesSelector', + 'webkitMatchesSelector', + 'msMatchesSelector' + ]; + const length = candidates.length; + let index, candidate; for (index = 0; index < length; index++) { candidate = candidates[index]; diff --git a/lib/core/utils/escape-selector.js b/lib/core/utils/escape-selector.js index 7add6e89b5..dc4bf0d68b 100644 --- a/lib/core/utils/escape-selector.js +++ b/lib/core/utils/escape-selector.js @@ -7,12 +7,12 @@ */ function escapeSelector(value) { /*eslint no-bitwise: 0, eqeqeq: 0, one-var: 0 */ - var string = String(value); - var length = string.length; - var index = -1; - var codeUnit; - var result = ''; - var firstCodeUnit = string.charCodeAt(0); + const string = String(value); + const length = string.length; + let index = -1; + let codeUnit; + let result = ''; + const firstCodeUnit = string.charCodeAt(0); while (++index < length) { codeUnit = string.charCodeAt(index); // Note: there’s no need to special-case astral symbols, surrogate diff --git a/lib/core/utils/extend-meta-data.js b/lib/core/utils/extend-meta-data.js index 3634915b79..c97d895ce8 100644 --- a/lib/core/utils/extend-meta-data.js +++ b/lib/core/utils/extend-meta-data.js @@ -11,7 +11,7 @@ function extendMetaData(to, from) { to[prop] = null; try { to[prop] = from[prop](to); - } catch (e) { + } catch { // Ignore } }); diff --git a/lib/core/utils/frame-messenger/message-parser.js b/lib/core/utils/frame-messenger/message-parser.js index c7154f8e38..881ac07c83 100644 --- a/lib/core/utils/frame-messenger/message-parser.js +++ b/lib/core/utils/frame-messenger/message-parser.js @@ -43,7 +43,7 @@ export function parseMessage(dataString) { let data; try { data = JSON.parse(dataString); - } catch (e) { + } catch { return; // Wasn't meant for us. } if (!isRespondableMessage(data)) { diff --git a/lib/core/utils/get-all-checks.js b/lib/core/utils/get-all-checks.js index 4c79e8368d..78c203489a 100644 --- a/lib/core/utils/get-all-checks.js +++ b/lib/core/utils/get-all-checks.js @@ -3,7 +3,7 @@ * @param {RuleResult|Rule} rule */ function getAllChecks(object) { - var result = []; + const result = []; return result .concat(object.any || []) .concat(object.all || []) diff --git a/lib/core/utils/get-check-option.js b/lib/core/utils/get-check-option.js index e159965e87..f438d8d418 100644 --- a/lib/core/utils/get-check-option.js +++ b/lib/core/utils/get-check-option.js @@ -6,12 +6,12 @@ * @return {Object} The resolved object with `options` and `enabled` keys */ function getCheckOption(check, ruleID, options) { - var ruleCheckOption = (((options.rules && options.rules[ruleID]) || {}) + const ruleCheckOption = (((options.rules && options.rules[ruleID]) || {}) .checks || {})[check.id]; - var checkOption = (options.checks || {})[check.id]; + const checkOption = (options.checks || {})[check.id]; - var enabled = check.enabled; - var opts = check.options; + let enabled = check.enabled; + let opts = check.options; if (checkOption) { if (checkOption.hasOwnProperty('enabled')) { diff --git a/lib/core/utils/get-flattened-tree.js b/lib/core/utils/get-flattened-tree.js index 2fb869e78a..f95a6a929b 100644 --- a/lib/core/utils/get-flattened-tree.js +++ b/lib/core/utils/get-flattened-tree.js @@ -61,7 +61,7 @@ export default function getFlattenedTree( * @return Array{Nodes} */ function getSlotChildren(node) { - var retVal = []; + const retVal = []; node = node.firstChild; while (node) { @@ -96,7 +96,7 @@ function createNode(node, parent, shadowId) { */ function flattenTree(node, shadowId, parent) { // using a closure here and therefore cannot easily refactor toreduce the statements - var retVal, realArray, nodeName; + let retVal, realArray; function reduceShadowDOM(res, child, parentVNode) { const replacements = flattenTree(child, shadowId, parentVNode); if (replacements) { @@ -109,7 +109,7 @@ function flattenTree(node, shadowId, parent) { // document node = node.documentElement; } - nodeName = node.nodeName.toLowerCase(); + const nodeName = node.nodeName.toLowerCase(); if (isShadowRoot(node)) { hasShadowRoot = true; @@ -142,7 +142,7 @@ function flattenTree(node, shadowId, parent) { // fallback content realArray = getSlotChildren(node); } - var styl = window.getComputedStyle(node); + const styl = window.getComputedStyle(node); // check the display property if (false && styl.display !== 'contents') { // intentionally commented out diff --git a/lib/core/utils/get-root-node.js b/lib/core/utils/get-root-node.js index 6eaf28d08f..aaab8944a7 100644 --- a/lib/core/utils/get-root-node.js +++ b/lib/core/utils/get-root-node.js @@ -6,7 +6,7 @@ * @returns {DocumentFragment|Document} */ function getRootNode(node) { - var doc = (node.getRootNode && node.getRootNode()) || document; // this is for backwards compatibility + let doc = (node.getRootNode && node.getRootNode()) || document; // this is for backwards compatibility if (doc === node) { // disconnected node doc = document; diff --git a/lib/core/utils/get-selector.js b/lib/core/utils/get-selector.js index 470946705b..d37354df55 100644 --- a/lib/core/utils/get-selector.js +++ b/lib/core/utils/get-selector.js @@ -97,8 +97,6 @@ function filterAttributes(at) { * the counts for how many elements with that feature exist */ export function getSelectorData(domTree) { - /* eslint no-loop-func:0*/ - // Initialize the return structure with the three maps const data = { classes: {}, diff --git a/lib/core/utils/get-xpath.js b/lib/core/utils/get-xpath.js index b0b66ba9c7..14298daab0 100644 --- a/lib/core/utils/get-xpath.js +++ b/lib/core/utils/get-xpath.js @@ -1,7 +1,7 @@ import escapeSelector from './escape-selector'; function getXPathArray(node, path) { - var sibling, count; + let sibling, count; // Gets an XPath for an element which describes its hierarchical location. if (!node) { return []; @@ -46,10 +46,10 @@ function getXPathArray(node, path) { } if (node.nodeType === 1) { - var element = {}; + const element = {}; element.str = node.nodeName.toLowerCase(); // add the id and the count so we can construct robust versions of the xpath - var id = node.getAttribute && escapeSelector(node.getAttribute('id')); + const id = node.getAttribute && escapeSelector(node.getAttribute('id')); if (id && node.ownerDocument.querySelectorAll('#' + id).length === 1) { element.id = node.getAttribute('id'); } @@ -77,7 +77,7 @@ function xpathToString(xpathArray) { } function getXpath(node) { - var xpathArray = getXPathArray(node); + const xpathArray = getXPathArray(node); return xpathToString(xpathArray); } diff --git a/lib/core/utils/index.js b/lib/core/utils/index.js index 360c1d7283..ac1286d6cf 100644 --- a/lib/core/utils/index.js +++ b/lib/core/utils/index.js @@ -43,6 +43,14 @@ export { default as getStyleSheetFactory } from './get-stylesheet-factory'; export { default as getXpath } from './get-xpath'; export { default as getAncestry } from './get-ancestry'; export { default as injectStyle } from './inject-style'; +export { default as isArrayLike } from './is-array-like'; +export { + isContextSpec, + isContextObject, + isContextProp, + isLabelledShadowDomSelector, + isLabelledFramesSelector +} from './is-context'; export { default as isHidden } from './is-hidden'; export { default as isHtmlElement } from './is-html-element'; export { default as isNodeInContext } from './is-node-in-context'; @@ -59,6 +67,7 @@ export { default as mergeResults } from './merge-results'; export { default as nodeSerializer } from './node-serializer'; export { default as nodeSorter } from './node-sorter'; export { default as nodeLookup } from './node-lookup'; +export { default as objectHasOwn } from './object-has-own'; export { default as parseCrossOriginStylesheet } from './parse-crossorigin-stylesheet'; export { default as parseSameOriginStylesheet } from './parse-sameorigin-stylesheet'; export { default as parseStylesheet } from './parse-stylesheet'; diff --git a/lib/core/utils/inject-style.js b/lib/core/utils/inject-style.js index 503300648f..86ce8b03ac 100644 --- a/lib/core/utils/inject-style.js +++ b/lib/core/utils/inject-style.js @@ -1,4 +1,4 @@ -var styleSheet; +let styleSheet; function injectStyle(style) { if (styleSheet && styleSheet.parentNode) { // append the style to the existing sheet @@ -14,7 +14,7 @@ function injectStyle(style) { return; } - var head = document.head || document.getElementsByTagName('head')[0]; + const head = document.head || document.getElementsByTagName('head')[0]; styleSheet = document.createElement('style'); styleSheet.type = 'text/css'; diff --git a/lib/core/utils/is-array-like.js b/lib/core/utils/is-array-like.js new file mode 100644 index 0000000000..97e7b4f910 --- /dev/null +++ b/lib/core/utils/is-array-like.js @@ -0,0 +1,15 @@ +/** + * Checks if a value is array-like. + * + * @param {any} arr - The value to check. + * @returns {boolean} - Returns true if the value is array-like, false otherwise. + */ +export default function isArrayLike(arr) { + return ( + !!arr && + typeof arr === 'object' && + typeof arr.length === 'number' && + // Avoid DOM weirdness + arr instanceof window.Node === false + ); +} diff --git a/lib/core/utils/is-context.js b/lib/core/utils/is-context.js new file mode 100644 index 0000000000..fcee309ac2 --- /dev/null +++ b/lib/core/utils/is-context.js @@ -0,0 +1,53 @@ +import objectHasOwn from './object-has-own'; +import isArrayLike from './is-array-like'; + +/** + * Determine if some value can be parsed as a context + * @private + * @param {Mixed} contextSpec The configuration object passed to `Context` + * @return {boolea} + */ +export function isContextSpec(contextSpec) { + return isContextObject(contextSpec) || isContextProp(contextSpec); +} + +/** + * Checks if the given context specification is a valid context object. + * + * @param {Object} contextSpec - The context specification object to check. + * @returns {boolean} - Returns true if the context specification is a valid context object, otherwise returns false. + */ +export function isContextObject(contextSpec) { + return ['include', 'exclude'].some( + prop => objectHasOwn(contextSpec, prop) && isContextProp(contextSpec[prop]) + ); +} + +/** + * Checks if the given contextList is a valid context property. + * @param {string|Node|Array} contextList - The contextList to check. + * @returns {boolean} - Returns true if the contextList is a valid context property, otherwise false. + */ +export function isContextProp(contextList) { + return ( + typeof contextList === 'string' || + contextList instanceof window.Node || + isLabelledFramesSelector(contextList) || + isLabelledShadowDomSelector(contextList) || + isArrayLike(contextList) + ); +} + +export function isLabelledFramesSelector(selector) { + // This doesn't guarantee the selector is valid. + // Just that this isn't a runOptions object + // Normalization will ignore invalid selectors + return objectHasOwn(selector, 'fromFrames'); +} + +export function isLabelledShadowDomSelector(selector) { + // This doesn't guarantee the selector is valid. + // Just that this isn't a runOptions object + // Normalization will ignore invalid selectors + return objectHasOwn(selector, 'fromShadowDom'); +} diff --git a/lib/core/utils/matches.js b/lib/core/utils/matches.js index f49bace8f3..7bd28c6501 100644 --- a/lib/core/utils/matches.js +++ b/lib/core/utils/matches.js @@ -29,7 +29,7 @@ function matchesAttributes(vNode, exp) { return ( !exp.attributes || exp.attributes.every(att => { - var nodeAtt = vNode.attr(att.key); + const nodeAtt = vNode.attr(att.key); return nodeAtt !== null && att.test(nodeAtt); }) ); @@ -72,10 +72,10 @@ function matchExpression(vNode, expression) { ); } -var escapeRegExp = (() => { +const escapeRegExp = (() => { /*! Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan MIT License */ - var from = /(?=[\-\[\]{}()*+?.\\\^$|,#\s])/g; - var to = '\\'; + const from = /(?=[\-\[\]{}()*+?.\\\^$|,#\s])/g; + const to = '\\'; return string => { return string.replace(from, to); }; @@ -168,7 +168,7 @@ function convertPseudos(pseudos) { return; } return pseudos.map(p => { - var expressions; + let expressions; if (['is', 'not'].includes(p.name)) { expressions = p.value; @@ -194,8 +194,8 @@ function convertPseudos(pseudos) { */ function convertExpressions(expressions) { return expressions.map(exp => { - var newExp = []; - var rule = exp.rule; + const newExp = []; + let rule = exp.rule; while (rule) { /* eslint no-restricted-syntax: 0 */ // `.tagName` is a property coming from the `CSSSelectorParser` library @@ -221,7 +221,7 @@ function convertExpressions(expressions) { * @returns {Object[]} Array of Slick format expressions */ export function convertSelector(selector) { - var expressions = cssParser.parse(selector); + let expressions = cssParser.parse(selector); expressions = expressions.selectors ? expressions.selectors : [expressions]; return convertExpressions(expressions); } diff --git a/lib/core/utils/merge-results.js b/lib/core/utils/merge-results.js index e5c9547adf..717eeb198a 100644 --- a/lib/core/utils/merge-results.js +++ b/lib/core/utils/merge-results.js @@ -31,8 +31,9 @@ function pushFrame(resultSet, options, frameSpec) { */ function spliceNodes(target, to) { const firstFromFrame = to[0].node; + let node; for (let i = 0; i < target.length; i++) { - const node = target[i].node; + node = target[i].node; const resultSort = nodeIndexSort( node.nodeIndexes, firstFromFrame.nodeIndexes @@ -87,7 +88,7 @@ function mergeResults(frameResults, options) { pushFrame(ruleResult.nodes, options, frameSpec); } - var res = findBy(mergedResult, 'id', ruleResult.id); + const res = findBy(mergedResult, 'id', ruleResult.id); if (!res) { mergedResult.push(ruleResult); } else { diff --git a/lib/core/utils/object-has-own.js b/lib/core/utils/object-has-own.js new file mode 100644 index 0000000000..4ac189bd7a --- /dev/null +++ b/lib/core/utils/object-has-own.js @@ -0,0 +1,7 @@ +// Wrapper to prevent throwing for non-objects & null +export default function objectHasOwn(obj, prop) { + if (!obj || typeof obj !== 'object') { + return false; + } + return Object.prototype.hasOwnProperty.call(obj, prop); +} diff --git a/lib/core/utils/performance-timer.js b/lib/core/utils/performance-timer.js index 78c25f4047..a66d6348be 100644 --- a/lib/core/utils/performance-timer.js +++ b/lib/core/utils/performance-timer.js @@ -17,8 +17,8 @@ const performanceTimer = (() => { return window.performance.now(); } } - var originalTime = null; - var lastRecordedTime = now(); + let originalTime = null; + let lastRecordedTime = now(); /** * @typedef {utils.performanceTimer} Public API Methods @@ -90,12 +90,13 @@ const performanceTimer = (() => { ) { // only output measures that were started after axe started, otherwise // we get measures made by the page before axe ran (which is confusing) - var axeStart = window.performance.getEntriesByName('mark_axe_start')[0]; - var measures = window.performance + const axeStart = + window.performance.getEntriesByName('mark_axe_start')[0]; + const measures = window.performance .getEntriesByType('measure') .filter(measure => measure.startTime >= axeStart.startTime); - for (var i = 0; i < measures.length; ++i) { - var req = measures[i]; + for (let i = 0; i < measures.length; ++i) { + const req = measures[i]; if (req.name === measureName) { logMeasure(req); return; diff --git a/lib/core/utils/pollyfill-elements-from-point.js b/lib/core/utils/pollyfill-elements-from-point.js index 1783f12946..b306f74ad7 100644 --- a/lib/core/utils/pollyfill-elements-from-point.js +++ b/lib/core/utils/pollyfill-elements-from-point.js @@ -3,24 +3,24 @@ export function pollyfillElementsFromPoint() { if (document.elementsFromPoint) return document.elementsFromPoint; if (document.msElementsFromPoint) return document.msElementsFromPoint; - var usePointer = (function () { - var element = document.createElement('x'); + const usePointer = (function () { + const element = document.createElement('x'); element.style.cssText = 'pointer-events:auto'; return element.style.pointerEvents === 'auto'; })(); - var cssProp = usePointer ? 'pointer-events' : 'visibility'; - var cssDisableVal = usePointer ? 'none' : 'hidden'; + const cssProp = usePointer ? 'pointer-events' : 'visibility'; + const cssDisableVal = usePointer ? 'none' : 'hidden'; - var style = document.createElement('style'); + const style = document.createElement('style'); style.innerHTML = usePointer ? '* { pointer-events: all }' : '* { visibility: visible }'; return function (x, y) { - var current, i, d; - var elements = []; - var previousPointerEvents = []; + let current, i, d; + const elements = []; + const previousPointerEvents = []; // startup document.head.appendChild(style); diff --git a/lib/core/utils/publish-metadata.js b/lib/core/utils/publish-metadata.js index 8354ceab01..3ab459ccb5 100644 --- a/lib/core/utils/publish-metadata.js +++ b/lib/core/utils/publish-metadata.js @@ -50,7 +50,7 @@ function getIncompleteReason(checkData, messages) { throw new Error(); } return msg; - } catch (e) { + } catch { if (typeof checkData.missingData === 'string') { // return a string with the appropriate reason return messages.incomplete[checkData.missingData]; diff --git a/lib/core/utils/queue.js b/lib/core/utils/queue.js index 5d5e82053b..fd27e4a5d9 100644 --- a/lib/core/utils/queue.js +++ b/lib/core/utils/queue.js @@ -12,16 +12,16 @@ function funcGuard(f) { * @return {Queue} The newly generated "queue" */ function queue() { - var tasks = []; - var started = 0; - var remaining = 0; // number of tasks not yet finished - var completeQueue = noop; - var complete = false; - var err; + const tasks = []; + let started = 0; + let remaining = 0; // number of tasks not yet finished + let completeQueue = noop; + let complete = false; + let err; // By default, wait until the next tick, // if no catch was set, throw to console. - var defaultFail = e => { + const defaultFail = e => { err = e; setTimeout(() => { if (err !== undefined && err !== null) { @@ -29,7 +29,7 @@ function queue() { } }, 1); }; - var failed = defaultFail; + let failed = defaultFail; function createResolve(i) { return r => { @@ -53,9 +53,9 @@ function queue() { } function pop() { - var length = tasks.length; + const length = tasks.length; for (; started < length; started++) { - var task = tasks[started]; + const task = tasks[started]; try { task.call(null, createResolve(started), abort); @@ -65,7 +65,7 @@ function queue() { } } - var q = { + const q = { /** * Defer a function that may or may not run asynchronously. * @@ -74,7 +74,7 @@ function queue() { */ defer(fn) { if (typeof fn === 'object' && fn.then && fn.catch) { - var defer = fn; + const defer = fn; fn = (resolve, reject) => { defer.then(resolve).catch(reject); }; diff --git a/lib/core/utils/respondable.js b/lib/core/utils/respondable.js index a43a8332ba..7a1c5c4da8 100644 --- a/lib/core/utils/respondable.js +++ b/lib/core/utils/respondable.js @@ -64,7 +64,7 @@ respondable.updateMessenger = function updateMessenger({ open, post }) { closeHandler(); } - var close = open(messageListener); + const close = open(messageListener); if (close) { assert( diff --git a/lib/core/utils/rule-should-run.js b/lib/core/utils/rule-should-run.js index 70c1fef55d..38fde1fcbd 100644 --- a/lib/core/utils/rule-should-run.js +++ b/lib/core/utils/rule-should-run.js @@ -6,8 +6,8 @@ * @return {bool} */ function matchTags(rule, runOnly) { - var include, exclude, matching; - var defaultExclude = + let include, exclude; + const defaultExclude = // TODO: es-modules_audit axe._audit && axe._audit.tagExclude ? axe._audit.tagExclude : []; @@ -35,7 +35,7 @@ function matchTags(rule, runOnly) { }); } - matching = include.some(tag => { + const matching = include.some(tag => { return rule.tags.indexOf(tag) !== -1; }); if (matching || (include.length === 0 && rule.enabled !== false)) { @@ -55,8 +55,8 @@ function matchTags(rule, runOnly) { * @return {Boolean} */ function ruleShouldRun(rule, context, options) { - var runOnly = options.runOnly || {}; - var ruleOptions = (options.rules || {})[rule.id]; + const runOnly = options.runOnly || {}; + const ruleOptions = (options.rules || {})[rule.id]; // Never run page level rules if the context is not on the page if (rule.pageLevel && !context.page) { diff --git a/lib/core/utils/send-command-to-frame.js b/lib/core/utils/send-command-to-frame.js index 7d7f5b57ea..f6b13fac3f 100644 --- a/lib/core/utils/send-command-to-frame.js +++ b/lib/core/utils/send-command-to-frame.js @@ -63,7 +63,7 @@ function callAxeStart(node, parameters, resolve, reject) { } function err(message, node) { - var selector; + let selector; // TODO: es-modules_tree if (axe._tree) { selector = getSelector(node); diff --git a/lib/core/utils/uuid.js b/lib/core/utils/uuid.js index de8a9eb66e..c10369a1f0 100644 --- a/lib/core/utils/uuid.js +++ b/lib/core/utils/uuid.js @@ -3,20 +3,20 @@ // // Copyright (c) 2010-2012 Robert Kieffer // MIT License - http://opensource.org/licenses/mit-license.php -var uuid; +let uuid; // Unique ID creation requires a high quality random # generator. We feature // detect to determine the best RNG source, normalizing to a function that // returns 128-bits of randomness, since that's what's usually required -var _rng; +let _rng; // Allow for MSIE11 msCrypto -var _crypto = window.crypto || window.msCrypto; +let _crypto = window.crypto || window.msCrypto; if (!_rng && _crypto && _crypto.getRandomValues) { // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto // // Moderately fast, high quality - var _rnds8 = new Uint8Array(16); + let _rnds8 = new Uint8Array(16); _rng = function whatwgRNG() { _crypto.getRandomValues(_rnds8); return _rnds8; @@ -28,9 +28,9 @@ if (!_rng) { // // If all else fails, use Math.random(). It's fast, but is of unspecified // quality. - var _rnds = new Array(16); + let _rnds = new Array(16); _rng = () => { - for (var i = 0, r; i < 16; i++) { + for (let i = 0, r; i < 16; i++) { if ((i & 0x03) === 0) r = Math.random() * 0x100000000; _rnds[i] = (r >>> ((i & 0x03) << 3)) & 0xff; } @@ -40,19 +40,19 @@ if (!_rng) { } // Buffer class to use -var BufferClass = typeof window.Buffer == 'function' ? window.Buffer : Array; +let BufferClass = typeof window.Buffer == 'function' ? window.Buffer : Array; // Maps for number <-> hex string conversion -var _byteToHex = []; -var _hexToByte = {}; -for (var i = 0; i < 256; i++) { +let _byteToHex = []; +let _hexToByte = {}; +for (let i = 0; i < 256; i++) { _byteToHex[i] = (i + 0x100).toString(16).substr(1); _hexToByte[_byteToHex[i]] = i; } // **`parse()` - Parse a UUID into it's component bytes** function parse(s, buf, offset) { - var i = (buf && offset) || 0, + let i = (buf && offset) || 0, ii = 0; buf = buf || []; @@ -73,7 +73,7 @@ function parse(s, buf, offset) { // **`unparse()` - Convert UUID byte array (ala parse()) into a string** function unparse(buf, offset) { - var i = offset || 0, + let i = offset || 0, bth = _byteToHex; return ( bth[buf[i++]] + @@ -105,10 +105,10 @@ function unparse(buf, offset) { // and http://docs.python.org/library/uuid.html // random #'s we need to init node and clockseq -var _seedBytes = _rng(); +let _seedBytes = _rng(); // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1) -var _nodeId = [ +let _nodeId = [ _seedBytes[0] | 0x01, _seedBytes[1], _seedBytes[2], @@ -118,33 +118,33 @@ var _nodeId = [ ]; // Per 4.2.2, randomize (14 bit) clockseq -var _clockseq = ((_seedBytes[6] << 8) | _seedBytes[7]) & 0x3fff; +let _clockseq = ((_seedBytes[6] << 8) | _seedBytes[7]) & 0x3fff; // Previous uuid creation time -var _lastMSecs = 0, +let _lastMSecs = 0, _lastNSecs = 0; // See https://github.com/broofa/node-uuid for API details function v1(options, buf, offset) { - var i = (buf && offset) || 0; - var b = buf || []; + let i = (buf && offset) || 0; + let b = buf || []; options = options || {}; - var clockseq = options.clockseq != null ? options.clockseq : _clockseq; + let clockseq = options.clockseq != null ? options.clockseq : _clockseq; // UUID timestamps are 100 nano-second units since the Gregorian epoch, // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs' // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00. - var msecs = options.msecs != null ? options.msecs : new Date().getTime(); + let msecs = options.msecs != null ? options.msecs : new Date().getTime(); // Per 4.2.1.2, use count of uuid's generated during the current clock // cycle to simulate higher resolution clock - var nsecs = options.nsecs != null ? options.nsecs : _lastNSecs + 1; + let nsecs = options.nsecs != null ? options.nsecs : _lastNSecs + 1; // Time since last uuid creation (in msecs) - var dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000; + let dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000; // Per 4.2.1.2, Bump clockseq on clock regression if (dt < 0 && options.clockseq == null) { @@ -170,14 +170,14 @@ function v1(options, buf, offset) { msecs += 12219292800000; // `time_low` - var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000; + let tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000; b[i++] = (tl >>> 24) & 0xff; b[i++] = (tl >>> 16) & 0xff; b[i++] = (tl >>> 8) & 0xff; b[i++] = tl & 0xff; // `time_mid` - var tmh = ((msecs / 0x100000000) * 10000) & 0xfffffff; + let tmh = ((msecs / 0x100000000) * 10000) & 0xfffffff; b[i++] = (tmh >>> 8) & 0xff; b[i++] = tmh & 0xff; @@ -192,8 +192,8 @@ function v1(options, buf, offset) { b[i++] = clockseq & 0xff; // `node` - var node = options.node || _nodeId; - for (var n = 0; n < 6; n++) { + let node = options.node || _nodeId; + for (let n = 0; n < 6; n++) { b[i + n] = node[n]; } @@ -205,7 +205,7 @@ function v1(options, buf, offset) { // See https://github.com/broofa/node-uuid for API details function v4(options, buf, offset) { // Deprecated - 'format' argument, as supported in v1.2 - var i = (buf && offset) || 0; + let i = (buf && offset) || 0; if (typeof options == 'string') { buf = options == 'binary' ? new BufferClass(16) : null; @@ -213,7 +213,7 @@ function v4(options, buf, offset) { } options = options || {}; - var rnds = options.random || (options.rng || _rng)(); + let rnds = options.random || (options.rng || _rng)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` rnds[6] = (rnds[6] & 0x0f) | 0x40; @@ -221,7 +221,7 @@ function v4(options, buf, offset) { // Copy bytes to buffer, if provided if (buf) { - for (var ii = 0; ii < 16; ii++) { + for (let ii = 0; ii < 16; ii++) { buf[i + ii] = rnds[ii]; } } diff --git a/lib/rules/accesskeys.json b/lib/rules/accesskeys.json index d5eb681cd7..4b3cdfbcf9 100644 --- a/lib/rules/accesskeys.json +++ b/lib/rules/accesskeys.json @@ -5,7 +5,7 @@ "excludeHidden": false, "tags": ["cat.keyboard", "best-practice"], "metadata": { - "description": "Ensures every accesskey attribute value is unique", + "description": "Ensure every accesskey attribute value is unique", "help": "accesskey attribute value should be unique" }, "all": [], diff --git a/lib/rules/area-alt.json b/lib/rules/area-alt.json index 06218e354b..3939818e25 100644 --- a/lib/rules/area-alt.json +++ b/lib/rules/area-alt.json @@ -19,7 +19,7 @@ ], "actIds": ["c487ae"], "metadata": { - "description": "Ensures elements of image maps have alternate text", + "description": "Ensure elements of image maps have alternate text", "help": "Active elements must have alternate text" }, "all": [], diff --git a/lib/rules/aria-allowed-attr.json b/lib/rules/aria-allowed-attr.json index b7073c38cf..48e95ed50b 100644 --- a/lib/rules/aria-allowed-attr.json +++ b/lib/rules/aria-allowed-attr.json @@ -5,7 +5,7 @@ "tags": ["cat.aria", "wcag2a", "wcag412", "EN-301-549", "EN-9.4.1.2"], "actIds": ["5c01ea"], "metadata": { - "description": "Ensures an element's role supports its ARIA attributes", + "description": "Ensure an element's role supports its ARIA attributes", "help": "Elements must only use supported ARIA attributes" }, "all": ["aria-allowed-attr"], diff --git a/lib/rules/aria-allowed-role.json b/lib/rules/aria-allowed-role.json index 43035835ca..62c14398dd 100644 --- a/lib/rules/aria-allowed-role.json +++ b/lib/rules/aria-allowed-role.json @@ -6,7 +6,7 @@ "matches": "aria-allowed-role-matches", "tags": ["cat.aria", "best-practice"], "metadata": { - "description": "Ensures role attribute has an appropriate value for the element", + "description": "Ensure role attribute has an appropriate value for the element", "help": "ARIA role should be appropriate for the element" }, "all": [], diff --git a/lib/rules/aria-command-name.json b/lib/rules/aria-command-name.json index bd7803d14d..5b5e357ada 100644 --- a/lib/rules/aria-command-name.json +++ b/lib/rules/aria-command-name.json @@ -15,7 +15,7 @@ ], "actIds": ["97a4e1"], "metadata": { - "description": "Ensures every ARIA button, link and menuitem has an accessible name", + "description": "Ensure every ARIA button, link and menuitem has an accessible name", "help": "ARIA commands must have an accessible name" }, "all": [], diff --git a/lib/rules/aria-conditional-attr.json b/lib/rules/aria-conditional-attr.json index ac1f57ae4b..866ec95c37 100644 --- a/lib/rules/aria-conditional-attr.json +++ b/lib/rules/aria-conditional-attr.json @@ -5,7 +5,7 @@ "tags": ["cat.aria", "wcag2a", "wcag412", "EN-301-549", "EN-9.4.1.2"], "actIds": ["5c01ea"], "metadata": { - "description": "Ensures ARIA attributes are used as described in the specification of the element's role", + "description": "Ensure ARIA attributes are used as described in the specification of the element's role", "help": "ARIA attributes must be used as specified for the element's role" }, "all": ["aria-conditional-attr"], diff --git a/lib/rules/aria-deprecated-role.json b/lib/rules/aria-deprecated-role.json index 3e181f366a..33f45e4587 100644 --- a/lib/rules/aria-deprecated-role.json +++ b/lib/rules/aria-deprecated-role.json @@ -6,7 +6,7 @@ "tags": ["cat.aria", "wcag2a", "wcag412", "EN-301-549", "EN-9.4.1.2"], "actIds": ["674b10"], "metadata": { - "description": "Ensures elements do not use deprecated roles", + "description": "Ensure elements do not use deprecated roles", "help": "Deprecated ARIA roles must not be used" }, "all": [], diff --git a/lib/rules/aria-dialog-name.json b/lib/rules/aria-dialog-name.json index fe30cd09d7..60bf60aaf1 100644 --- a/lib/rules/aria-dialog-name.json +++ b/lib/rules/aria-dialog-name.json @@ -5,7 +5,7 @@ "matches": "no-naming-method-matches", "tags": ["cat.aria", "best-practice"], "metadata": { - "description": "Ensures every ARIA dialog and alertdialog node has an accessible name", + "description": "Ensure every ARIA dialog and alertdialog node has an accessible name", "help": "ARIA dialog and alertdialog nodes should have an accessible name" }, "all": [], diff --git a/lib/rules/aria-hidden-body.json b/lib/rules/aria-hidden-body.json index a1b74c2c34..a35cc90277 100644 --- a/lib/rules/aria-hidden-body.json +++ b/lib/rules/aria-hidden-body.json @@ -14,7 +14,7 @@ "EN-9.4.1.2" ], "metadata": { - "description": "Ensures aria-hidden=\"true\" is not present on the document body.", + "description": "Ensure aria-hidden=\"true\" is not present on the document body.", "help": "aria-hidden=\"true\" must not be present on the document body" }, "all": [], diff --git a/lib/rules/aria-hidden-focus.json b/lib/rules/aria-hidden-focus.json index b49c75ae86..53158518a8 100755 --- a/lib/rules/aria-hidden-focus.json +++ b/lib/rules/aria-hidden-focus.json @@ -15,7 +15,7 @@ ], "actIds": ["6cfa84"], "metadata": { - "description": "Ensures aria-hidden elements are not focusable nor contain focusable elements", + "description": "Ensure aria-hidden elements are not focusable nor contain focusable elements", "help": "ARIA hidden element must not be focusable or contain focusable elements" }, "all": [ diff --git a/lib/rules/aria-input-field-name.json b/lib/rules/aria-input-field-name.json index 9aff44648d..2e8802f699 100644 --- a/lib/rules/aria-input-field-name.json +++ b/lib/rules/aria-input-field-name.json @@ -15,7 +15,7 @@ ], "actIds": ["e086e5"], "metadata": { - "description": "Ensures every ARIA input field has an accessible name", + "description": "Ensure every ARIA input field has an accessible name", "help": "ARIA input fields must have an accessible name" }, "all": [], diff --git a/lib/rules/aria-meter-name.json b/lib/rules/aria-meter-name.json index f62d5c975e..20c841d711 100644 --- a/lib/rules/aria-meter-name.json +++ b/lib/rules/aria-meter-name.json @@ -5,7 +5,7 @@ "matches": "no-naming-method-matches", "tags": ["cat.aria", "wcag2a", "wcag111", "EN-301-549", "EN-9.1.1.1"], "metadata": { - "description": "Ensures every ARIA meter node has an accessible name", + "description": "Ensure every ARIA meter node has an accessible name", "help": "ARIA meter nodes must have an accessible name" }, "all": [], diff --git a/lib/rules/aria-progressbar-name.json b/lib/rules/aria-progressbar-name.json index 10650ff1e6..47ef514a68 100644 --- a/lib/rules/aria-progressbar-name.json +++ b/lib/rules/aria-progressbar-name.json @@ -5,7 +5,7 @@ "matches": "no-naming-method-matches", "tags": ["cat.aria", "wcag2a", "wcag111", "EN-301-549", "EN-9.1.1.1"], "metadata": { - "description": "Ensures every ARIA progressbar node has an accessible name", + "description": "Ensure every ARIA progressbar node has an accessible name", "help": "ARIA progressbar nodes must have an accessible name" }, "all": [], diff --git a/lib/rules/aria-prohibited-attr.json b/lib/rules/aria-prohibited-attr.json index f97a04774b..0db152613e 100644 --- a/lib/rules/aria-prohibited-attr.json +++ b/lib/rules/aria-prohibited-attr.json @@ -5,7 +5,7 @@ "tags": ["cat.aria", "wcag2a", "wcag412", "EN-301-549", "EN-9.4.1.2"], "actIds": ["5c01ea"], "metadata": { - "description": "Ensures ARIA attributes are not prohibited for an element's role", + "description": "Ensure ARIA attributes are not prohibited for an element's role", "help": "Elements must only use permitted ARIA attributes" }, "all": [], diff --git a/lib/rules/aria-required-attr.json b/lib/rules/aria-required-attr.json index cfe0323bc8..1cf72512ce 100644 --- a/lib/rules/aria-required-attr.json +++ b/lib/rules/aria-required-attr.json @@ -5,7 +5,7 @@ "tags": ["cat.aria", "wcag2a", "wcag412", "EN-301-549", "EN-9.4.1.2"], "actIds": ["4e8ab6"], "metadata": { - "description": "Ensures elements with ARIA roles have all required ARIA attributes", + "description": "Ensure elements with ARIA roles have all required ARIA attributes", "help": "Required ARIA attributes must be provided" }, "all": [], diff --git a/lib/rules/aria-required-children.json b/lib/rules/aria-required-children.json index e1b22fb129..f3b95ea458 100644 --- a/lib/rules/aria-required-children.json +++ b/lib/rules/aria-required-children.json @@ -6,7 +6,7 @@ "tags": ["cat.aria", "wcag2a", "wcag131", "EN-301-549", "EN-9.1.3.1"], "actIds": ["bc4a75", "ff89c9"], "metadata": { - "description": "Ensures elements with an ARIA role that require child roles contain them", + "description": "Ensure elements with an ARIA role that require child roles contain them", "help": "Certain ARIA roles must contain particular children" }, "all": [], diff --git a/lib/rules/aria-required-parent.json b/lib/rules/aria-required-parent.json index 370a0ed674..72436ed7d8 100644 --- a/lib/rules/aria-required-parent.json +++ b/lib/rules/aria-required-parent.json @@ -6,7 +6,7 @@ "tags": ["cat.aria", "wcag2a", "wcag131", "EN-301-549", "EN-9.1.3.1"], "actIds": ["ff89c9"], "metadata": { - "description": "Ensures elements with an ARIA role that require parent roles are contained by them", + "description": "Ensure elements with an ARIA role that require parent roles are contained by them", "help": "Certain ARIA roles must be contained by particular parents" }, "all": [], diff --git a/lib/rules/aria-roles.json b/lib/rules/aria-roles.json index 3cc591cb3a..79f55319fa 100644 --- a/lib/rules/aria-roles.json +++ b/lib/rules/aria-roles.json @@ -6,7 +6,7 @@ "tags": ["cat.aria", "wcag2a", "wcag412", "EN-301-549", "EN-9.4.1.2"], "actIds": ["674b10"], "metadata": { - "description": "Ensures all elements with a role attribute use a valid value", + "description": "Ensure all elements with a role attribute use a valid value", "help": "ARIA roles used must conform to valid values" }, "all": [], diff --git a/lib/rules/aria-text.json b/lib/rules/aria-text.json index 30db550134..2ef28fda5e 100644 --- a/lib/rules/aria-text.json +++ b/lib/rules/aria-text.json @@ -4,7 +4,7 @@ "selector": "[role=text]", "tags": ["cat.aria", "best-practice"], "metadata": { - "description": "Ensures role=\"text\" is used on elements with no focusable descendants", + "description": "Ensure role=\"text\" is used on elements with no focusable descendants", "help": "\"role=text\" should have no focusable descendants" }, "all": [], diff --git a/lib/rules/aria-toggle-field-name.json b/lib/rules/aria-toggle-field-name.json index e673facfbb..505ceb2cbc 100644 --- a/lib/rules/aria-toggle-field-name.json +++ b/lib/rules/aria-toggle-field-name.json @@ -15,7 +15,7 @@ ], "actIds": ["e086e5"], "metadata": { - "description": "Ensures every ARIA toggle field has an accessible name", + "description": "Ensure every ARIA toggle field has an accessible name", "help": "ARIA toggle fields must have an accessible name" }, "all": [], diff --git a/lib/rules/aria-tooltip-name.json b/lib/rules/aria-tooltip-name.json index c90b7ffde8..be4d4628d3 100644 --- a/lib/rules/aria-tooltip-name.json +++ b/lib/rules/aria-tooltip-name.json @@ -5,7 +5,7 @@ "matches": "no-naming-method-matches", "tags": ["cat.aria", "wcag2a", "wcag412", "EN-301-549", "EN-9.4.1.2"], "metadata": { - "description": "Ensures every ARIA tooltip node has an accessible name", + "description": "Ensure every ARIA tooltip node has an accessible name", "help": "ARIA tooltip nodes must have an accessible name" }, "all": [], diff --git a/lib/rules/aria-treeitem-name.json b/lib/rules/aria-treeitem-name.json index 091cd7ee21..15e6b80d37 100644 --- a/lib/rules/aria-treeitem-name.json +++ b/lib/rules/aria-treeitem-name.json @@ -5,7 +5,7 @@ "matches": "no-naming-method-matches", "tags": ["cat.aria", "best-practice"], "metadata": { - "description": "Ensures every ARIA treeitem node has an accessible name", + "description": "Ensure every ARIA treeitem node has an accessible name", "help": "ARIA treeitem nodes should have an accessible name" }, "all": [], diff --git a/lib/rules/aria-valid-attr-value.json b/lib/rules/aria-valid-attr-value.json index 924380388a..186b023dbd 100644 --- a/lib/rules/aria-valid-attr-value.json +++ b/lib/rules/aria-valid-attr-value.json @@ -5,7 +5,7 @@ "tags": ["cat.aria", "wcag2a", "wcag412", "EN-301-549", "EN-9.4.1.2"], "actIds": ["6a7281"], "metadata": { - "description": "Ensures all ARIA attributes have valid values", + "description": "Ensure all ARIA attributes have valid values", "help": "ARIA attributes must conform to valid values" }, "all": ["aria-valid-attr-value", "aria-errormessage", "aria-level"], diff --git a/lib/rules/aria-valid-attr.json b/lib/rules/aria-valid-attr.json index 22476a16c3..c227a84228 100644 --- a/lib/rules/aria-valid-attr.json +++ b/lib/rules/aria-valid-attr.json @@ -5,7 +5,7 @@ "tags": ["cat.aria", "wcag2a", "wcag412", "EN-301-549", "EN-9.4.1.2"], "actIds": ["5f99a7"], "metadata": { - "description": "Ensures attributes that begin with aria- are valid ARIA attributes", + "description": "Ensure attributes that begin with aria- are valid ARIA attributes", "help": "ARIA attributes must conform to valid names" }, "all": [], diff --git a/lib/rules/audio-caption.json b/lib/rules/audio-caption.json index 3974aade12..5776d2b894 100644 --- a/lib/rules/audio-caption.json +++ b/lib/rules/audio-caption.json @@ -16,7 +16,7 @@ ], "actIds": ["2eb176", "afb423"], "metadata": { - "description": "Ensures