Skip to content

Commit f553e16

Browse files
committed
fix(template-no-aria-label-misuse): specificity tiers, pre-indexed elementRoles, valueless-label, fixable meta (Copilot review)
- scoreMatch now uses three tiers: exact value (3) > set (2) > undefined (1). Previously set and undefined both added +1, so a stricter 'set' constraint could lose to a looser 'undefined' when both matched. Exact-value entries still win over 'set' (e.g. <img alt=''> → presentation still beats the generic alt-set entry). - Pre-index elementRoles by tag name at module load (same pattern as PR #52's template-no-unsupported-role-attributes). Turns a ~80-key scan per call into a 1–5-key lookup. - hasNonEmptyLabelAttr returns false when attr.value == null — a valueless aria-label / aria-labelledby carries no accessible name, so it shouldn't be treated as a non-empty (author-declared) label. - Add meta.fixable: null — rule is not autofixable, be explicit.
1 parent 404850e commit f553e16

1 file changed

Lines changed: 37 additions & 9 deletions

File tree

lib/rules/template-no-aria-label-misuse.js

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,24 @@ function scoreMatch(entry, node) {
2929
const isPresent = Boolean(nodeAttr);
3030
const staticValue = getStaticAttrString(node, spec.name);
3131

32+
// Specificity tiers: exact value match is strictest (3), "set" presence
33+
// is stricter than "undefined" absence (2 vs 1). Previously `set` and
34+
// `undefined` both scored 1, so a constraints-match on either tied; the
35+
// tier split ensures a more specific "set" entry beats a looser
36+
// "undefined" entry when both match — and an exact-value entry (e.g.
37+
// <img alt=""> → presentation) still beats a plain "set" entry.
3238
if (spec.value !== undefined) {
3339
if (staticValue === null || staticValue.toLowerCase() !== spec.value) {
3440
return null;
3541
}
36-
score += 2;
42+
score += 3;
3743
continue;
3844
}
3945
if (spec.constraints?.includes('set')) {
4046
if (!isPresent) {
4147
return null;
4248
}
43-
score += 1;
49+
score += 2;
4450
continue;
4551
}
4652
if (spec.constraints?.includes('undefined')) {
@@ -55,20 +61,38 @@ function scoreMatch(entry, node) {
5561
return score;
5662
}
5763

64+
// Pre-index elementRoles by tag name at module load. aria-query's Map is
65+
// static data; bucketing by tag turns the per-call scan (~80 keys) into a
66+
// 1–5 key lookup per tag. Mirrors the optimization landed on PR #52's
67+
// template-no-unsupported-role-attributes rule.
68+
const ELEMENT_ROLES_KEYS_BY_TAG = buildElementRolesIndex();
69+
70+
function buildElementRolesIndex() {
71+
const index = new Map();
72+
for (const key of elementRoles.keys()) {
73+
if (!index.has(key.name)) {
74+
index.set(key.name, []);
75+
}
76+
index.get(key.name).push(key);
77+
}
78+
return index;
79+
}
80+
5881
function getImplicitRole(node) {
82+
const keys = ELEMENT_ROLES_KEYS_BY_TAG.get(node.tag);
83+
if (!keys) {
84+
return null;
85+
}
5986
let best = null;
6087
let bestScore = -1;
61-
for (const [key, roleList] of elementRoles.entries()) {
62-
if (key.name !== node.tag) {
63-
continue;
64-
}
88+
for (const key of keys) {
6589
const score = scoreMatch(key, node);
6690
if (score === null) {
6791
continue;
6892
}
6993
if (score > bestScore) {
7094
bestScore = score;
71-
best = roleList[0];
95+
best = elementRoles.get(key)[0];
7296
}
7397
}
7498
return best;
@@ -106,8 +130,11 @@ function hasNonEmptyLabelAttr(node, name) {
106130
if (!attr) {
107131
return false;
108132
}
109-
if (!attr.value) {
110-
return true;
133+
// A valueless attribute (e.g. `<div aria-label>`) carries no accessible
134+
// name. Treat it as empty — not as non-empty — so downstream checks don't
135+
// mistake it for an author-declared label.
136+
if (attr.value == null) {
137+
return false;
111138
}
112139
if (attr.value.type === 'GlimmerTextNode') {
113140
return attr.value.chars !== '';
@@ -145,6 +172,7 @@ module.exports = {
145172
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-aria-label-misuse.md',
146173
templateMode: 'both',
147174
},
175+
fixable: null,
148176
schema: [
149177
{
150178
type: 'object',

0 commit comments

Comments
 (0)