Match jsx-eslint implementation of no-noninteractive-to-interactive rule

pull/8167/head
Nguyen Tran 4 years ago
parent 3af087d50f
commit 6d0fab0b34

@ -24,7 +24,7 @@ import { Literal } from 'estree';
import compiler_warnings from '../compiler_warnings'; import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import { ARIARoleDefintionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query'; import { ARIARoleDefintionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query';
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'; 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';
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(' '); 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(' ');
const aria_attribute_set = new Set(aria_attributes); const aria_attribute_set = new Set(aria_attributes);
@ -539,7 +539,7 @@ export default class Element extends Node {
} }
// no-noninteractive-element-to-interactive-role // no-noninteractive-element-to-interactive-role
if (!is_interactive_element(this.name, attribute_map) && is_interactive_roles(current_role)) { if (is_non_interactive_element(this.name, attribute_map) && is_interactive_roles(current_role)) {
component.warn(this, compiler_warnings.a11y_no_noninteractive_element_to_interactive_role(current_role, this.name)); component.warn(this, compiler_warnings.a11y_no_noninteractive_element_to_interactive_role(current_role, this.name));
} }
}); });

@ -7,7 +7,7 @@ import {
import { AXObjects, AXObjectRoles, elementAXObjects } from 'axobject-query'; import { AXObjects, AXObjectRoles, elementAXObjects } from 'axobject-query';
import Attribute from '../nodes/Attribute'; import Attribute from '../nodes/Attribute';
const non_abstract_roles = [...roles_map.keys()].filter((name) => !roles_map.get(name).abstract); const non_abstract_roles = [...roles_map.keys()].filter((name) => !roles_map.get(name).abstract && name !== 'generic');
const non_interactive_roles = new Set( const non_interactive_roles = new Set(
non_abstract_roles non_abstract_roles
@ -82,6 +82,10 @@ const interactive_ax_objects = new Set(
[...AXObjects.keys()].filter((name) => AXObjects.get(name).type === 'widget') [...AXObjects.keys()].filter((name) => AXObjects.get(name).type === 'widget')
); );
const non_interactive_ax_objects = new Set(
[...AXObjects.keys()].filter((name) => ['windows', 'structure'].includes(AXObjects.get(name).type))
);
const interactive_element_ax_object_schemas: ARIARoleRelationConcept[] = []; const interactive_element_ax_object_schemas: ARIARoleRelationConcept[] = [];
elementAXObjects.entries().forEach(([schema, ax_object]) => { elementAXObjects.entries().forEach(([schema, ax_object]) => {
@ -90,6 +94,15 @@ elementAXObjects.entries().forEach(([schema, ax_object]) => {
} }
}); });
const non_interactive_element_ax_object_schemas: ARIARoleRelationConcept[] = [];
elementAXObjects.entries().forEach(([schema, ax_object]) => {
if ([...ax_object].every((role) => non_interactive_ax_objects.has(role))) {
non_interactive_element_ax_object_schemas.push(schema);
}
});
function match_schema( function match_schema(
schema: ARIARoleRelationConcept, schema: ARIARoleRelationConcept,
tag_name: string, tag_name: string,
@ -141,6 +154,41 @@ export function is_interactive_element(
return false; return false;
} }
export function is_non_interactive_element(
tag_name: string,
attribute_map: Map<string, Attribute>
): boolean {
if (tag_name === 'header') {
return false;
}
if (
non_interactive_element_role_schemas.some((schema) =>
match_schema(schema, tag_name, attribute_map)
)
) {
return true;
}
if (
interactive_element_role_schemas.some((schema) =>
match_schema(schema, tag_name, attribute_map)
)
) {
return false;
}
if (
non_interactive_element_ax_object_schemas.some((schema) =>
match_schema(schema, tag_name, attribute_map)
)
) {
return true;
}
return false;
}
export function is_semantic_role_element(role: ARIARoleDefintionKey, tag_name: string, attribute_map: Map<string, Attribute>) { export function is_semantic_role_element(role: ARIARoleDefintionKey, tag_name: string, attribute_map: Map<string, Attribute>) {
for (const [schema, ax_object] of elementAXObjects.entries()) { for (const [schema, ax_object] of elementAXObjects.entries()) {
if (schema.name === tag_name && (!schema.attributes || schema.attributes.every( if (schema.name === tag_name && (!schema.attributes || schema.attributes.every(

Loading…
Cancel
Save