Skip to content

refactor: use virtual node attr/hasAttr in rules and checks#5198

Open
chutchins25 wants to merge 2 commits into
developfrom
chut/5043-rules-matches-vnode-attr
Open

refactor: use virtual node attr/hasAttr in rules and checks#5198
chutchins25 wants to merge 2 commits into
developfrom
chut/5043-rules-matches-vnode-attr

Conversation

@chutchins25

Copy link
Copy Markdown
Contributor

Converts getAttribute/hasAttribute calls to the VirtualNode .attr()/.hasAttr() equivalents where it's a safe, behavior-preserving drop-in. .attr()/.hasAttr() return the raw attribute value (or null) exactly like getAttribute/hasAttribute — no trimming or normalization — so these swaps preserve behavior.

Scope

A full sweep of lib/ found 71 raw getAttribute/hasAttribute calls (74 grep hits − 3 JSDoc comments). Of these, 13 are converted here; the remaining 58 are intentionally left as-is (52 must-stay + 6 judgment). #5043 is satisfied by converting where it makes sense, not by driving the count to zero.

✅ Converted (13)

lib/rules/*-matches (6):

Site Change
xml-lang-mismatch-matches getAttribute('lang'), getAttribute('xml:lang').attr()
duplicate-id-active-matches getAttribute('id').trim().attr('id').trim()
duplicate-id-misc-matches getAttribute('id').trim().attr('id').trim()
frame-title-has-text-matches getAttribute('title').attr('title')
landmark-has-body-context-matches hasAttribute('role').hasAttr('role')

lib/checks/ (7):

Site Change
label/help-same-as-label getAttribute('title'), getAttribute('aria-describedby').attr()
label/hidden-explicit-label getAttribute('id').attr('id')
label/multiple-label getAttribute('id').attr('id')
parsing/duplicate-id getAttribute('id').trim().attr('id').trim()
tables/scope-value getAttribute('scope').toLowerCase().attr('scope').toLowerCase()
generic/page-no-duplicate elm.actualNode.hasAttribute('role')elm.hasAttr('role')

matches functions receive virtualNode as the 2nd argument and checks as the 3rd, so the virtual node is always available (a few functions had the parameter added). Affected matches/check tests were updated to pass a virtual node argument.

⏸️ Left as-is — must-stay (52)

These operate on real DOM by design; converting them would be incorrect or pointless.

Area Sites Reason
core/utils/is-hidden, preload-media, get-xpath, get-selector, preload-cssom 14 core/utils works on real DOM by design
core/base/context/create-frame-context 3 Runs before the tree is built (raw frame element)
core/base/virtual-node 3 This is the .attr()/.hasAttr() implementation + constructor
commons/table (to-grid, is-header, is-data-table, get-headers) 11 Raw cells accessed via .rows/.cells; is-data-table takes a real DOM table
commons/aria/get-accessible-refs 3 Raw childNodes recursion
commons/dom (idrefs, is-current-page-link, url-props-from-attribute, is-visible) 6 idrefs left per #5138; live URL props; is-visible:147 is an intentional vNode-null fallback
checks/tables/th-has-data-cells, td-headers-attr 8 Raw cells; table cells can't be custom elements
checks/aria/aria-required-parent 2 parentElement walk on the raw element
rules/aria-hidden-focus-matches 1 Real-DOM composed-parent walk (no vNode in the recursion)
rules/color-contrast-matches 1 control comes from getAccessibleRefs (raw refs); color-contrast is #5150 scope

⏸️ Left as-is — judgment (6)

These mix attribute reads with live DOM/property reads, so converting only the attribute reads would be inconsistent.

Site Sites Reason
commons/aria/lookup-table 3 evaluateRoleForElement handlers read live props (node.type, etc.) alongside the attribute
checks/media/no-autoplay-audio-evaluate, rules/no-autoplay-audio-matches 3 Mixed with live media props (node.duration, node.currentSrc)

Testing

  • matches/check tests updated to pass a virtual node argument (some previously passed only the DOM node and would throw on the new .attr() reads).
  • npm run eslint, npm run test:tsc, npm run build, and the affected rule-matches + check suites all pass.

Closes #5043

Convert getAttribute/hasAttribute reads to the virtual node
.attr()/.hasAttr() equivalents in the rules/*-matches cluster
(xml-lang-mismatch, duplicate-id-active, duplicate-id-misc,
frame-title-has-text, landmark-has-body-context). These are
behavior-preserving drop-ins — .attr() returns the raw value or
null with no trimming, matching getAttribute.

Part of #5043
…ic checks

Convert getAttribute/hasAttribute reads to the virtual node
.attr()/.hasAttr() equivalents in help-same-as-label,
hidden-explicit-label, multiple-label, parsing/duplicate-id,
tables/scope-value, and generic/page-no-duplicate. Behavior-preserving
drop-ins — .attr() returns the raw value or null, matching getAttribute.

Update the affected check tests to pass a virtual node argument.

Part of #5043

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors a targeted set of rule matchers and check evaluators to use VirtualNode.attr() / VirtualNode.hasAttr() in places where those calls are intended to be behavior-preserving drop-in replacements for getAttribute() / hasAttribute(), and updates impacted unit tests to pass the required virtualNode argument.

Changes:

  • Updated selected lib/rules/*-matches.js matchers to read attributes via virtualNode.attr() / virtualNode.hasAttr() and adjusted matcher signatures where needed.
  • Updated selected lib/checks/**/*-evaluate.js checks to read attributes via virtualNode.attr() and adjusted evaluate signatures where needed.
  • Updated affected rule-matcher and check unit tests to pass a VirtualNode argument (or axe.utils.getNodeFromTree(...) where appropriate).

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test/rule-matches/html-xml-lang-mismatch.js Passes a VirtualNode into the matcher to support .attr() reads.
test/rule-matches/frame-title-has-text-matches.js Passes a VirtualNode into the matcher to support .attr('title').
test/checks/tables/scope-value.js Updates check invocation to provide virtualNode for .attr('scope').
test/checks/parser/duplicate-id.js Updates check invocation to provide virtualNode for .attr('id').
test/checks/label/multiple-label.js Updates check invocation to provide a tree VirtualNode for .attr('id').
lib/rules/xml-lang-mismatch-matches.js Replaces getAttribute reads with virtualNode.attr(...).
lib/rules/landmark-has-body-context-matches.js Replaces hasAttribute('role') with virtualNode.hasAttr('role').
lib/rules/frame-title-has-text-matches.js Replaces getAttribute('title') with virtualNode.attr('title').
lib/rules/duplicate-id-misc-matches.js Replaces getAttribute('id') with virtualNode.attr('id').
lib/rules/duplicate-id-active-matches.js Replaces getAttribute('id') with virtualNode.attr('id').
lib/checks/tables/scope-value-evaluate.js Replaces getAttribute('scope') with virtualNode.attr('scope').
lib/checks/parsing/duplicate-id-evaluate.js Replaces getAttribute('id') with virtualNode.attr('id') and updates signature.
lib/checks/label/multiple-label-evaluate.js Replaces getAttribute('id') with virtualNode.attr('id') and updates signature.
lib/checks/label/hidden-explicit-label-evaluate.js Replaces getAttribute('id') with virtualNode.attr('id').
lib/checks/label/help-same-as-label-evaluate.js Replaces getAttribute(...) reads with virtualNode.attr(...).
lib/checks/generic/page-no-duplicate-evaluate.js Replaces actualNode.hasAttribute('role') with hasAttr('role') on vNodes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chutchins25 chutchins25 requested review from WilcoFiers and straker July 1, 2026 14:55
@chutchins25 chutchins25 marked this pull request as ready for review July 1, 2026 14:56
@chutchins25 chutchins25 requested a review from a team as a code owner July 1, 2026 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ElementInternals: Convert all uses of Node getAttribute and hasAttribute to their virtual node functions where possible

2 participants