Skip to content

Commit 6d0fab0

Browse files
committed
Match jsx-eslint implementation of no-noninteractive-to-interactive rule
1 parent 3af087d commit 6d0fab0

File tree

2 files changed

+51
-3
lines changed

2 files changed

+51
-3
lines changed

src/compiler/compile/nodes/Element.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { Literal } from 'estree';
2424
import compiler_warnings from '../compiler_warnings';
2525
import compiler_errors from '../compiler_errors';
2626
import { ARIARoleDefintionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query';
27-
import { is_interactive_element, is_non_interactive_roles, is_presentation_role, is_interactive_roles, is_hidden_from_screen_reader, is_semantic_role_element } from '../utils/a11y';
27+
import { is_interactive_element, is_non_interactive_element, is_non_interactive_roles, is_presentation_role, is_interactive_roles, is_hidden_from_screen_reader, is_semantic_role_element } from '../utils/a11y';
2828

2929
const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby description details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(' ');
3030
const aria_attribute_set = new Set(aria_attributes);
@@ -539,7 +539,7 @@ export default class Element extends Node {
539539
}
540540

541541
// no-noninteractive-element-to-interactive-role
542-
if (!is_interactive_element(this.name, attribute_map) && is_interactive_roles(current_role)) {
542+
if (is_non_interactive_element(this.name, attribute_map) && is_interactive_roles(current_role)) {
543543
component.warn(this, compiler_warnings.a11y_no_noninteractive_element_to_interactive_role(current_role, this.name));
544544
}
545545
});

src/compiler/compile/utils/a11y.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
import { AXObjects, AXObjectRoles, elementAXObjects } from 'axobject-query';
88
import Attribute from '../nodes/Attribute';
99

10-
const non_abstract_roles = [...roles_map.keys()].filter((name) => !roles_map.get(name).abstract);
10+
const non_abstract_roles = [...roles_map.keys()].filter((name) => !roles_map.get(name).abstract && name !== 'generic');
1111

1212
const non_interactive_roles = new Set(
1313
non_abstract_roles
@@ -82,6 +82,10 @@ const interactive_ax_objects = new Set(
8282
[...AXObjects.keys()].filter((name) => AXObjects.get(name).type === 'widget')
8383
);
8484

85+
const non_interactive_ax_objects = new Set(
86+
[...AXObjects.keys()].filter((name) => ['windows', 'structure'].includes(AXObjects.get(name).type))
87+
);
88+
8589
const interactive_element_ax_object_schemas: ARIARoleRelationConcept[] = [];
8690

8791
elementAXObjects.entries().forEach(([schema, ax_object]) => {
@@ -90,6 +94,15 @@ elementAXObjects.entries().forEach(([schema, ax_object]) => {
9094
}
9195
});
9296

97+
const non_interactive_element_ax_object_schemas: ARIARoleRelationConcept[] = [];
98+
99+
elementAXObjects.entries().forEach(([schema, ax_object]) => {
100+
if ([...ax_object].every((role) => non_interactive_ax_objects.has(role))) {
101+
non_interactive_element_ax_object_schemas.push(schema);
102+
}
103+
});
104+
105+
93106
function match_schema(
94107
schema: ARIARoleRelationConcept,
95108
tag_name: string,
@@ -141,6 +154,41 @@ export function is_interactive_element(
141154
return false;
142155
}
143156

157+
export function is_non_interactive_element(
158+
tag_name: string,
159+
attribute_map: Map<string, Attribute>
160+
): boolean {
161+
if (tag_name === 'header') {
162+
return false;
163+
}
164+
165+
if (
166+
non_interactive_element_role_schemas.some((schema) =>
167+
match_schema(schema, tag_name, attribute_map)
168+
)
169+
) {
170+
return true;
171+
}
172+
173+
if (
174+
interactive_element_role_schemas.some((schema) =>
175+
match_schema(schema, tag_name, attribute_map)
176+
)
177+
) {
178+
return false;
179+
}
180+
181+
if (
182+
non_interactive_element_ax_object_schemas.some((schema) =>
183+
match_schema(schema, tag_name, attribute_map)
184+
)
185+
) {
186+
return true;
187+
}
188+
189+
return false;
190+
}
191+
144192
export function is_semantic_role_element(role: ARIARoleDefintionKey, tag_name: string, attribute_map: Map<string, Attribute>) {
145193
for (const [schema, ax_object] of elementAXObjects.entries()) {
146194
if (schema.name === tag_name && (!schema.attributes || schema.attributes.every(

0 commit comments

Comments
 (0)