Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(engine): Fix ruleID dependencies, and context dependency, and create a cache function computation #1357

Merged
merged 10 commits into from
Mar 28, 2023
Prev Previous commit
Next Next commit
update invalid aria role dependencies #1330
  • Loading branch information
shunguoy committed Mar 21, 2023
commit b7b9dbca31a6f7b6df8aaa7f1cfd4a232a633230
11 changes: 8 additions & 3 deletions accessibility-checker-engine/src/v4/rules/aria_role_redundant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Rule, RuleResult, RuleFail, RuleContext, RulePass, RuleContextHierarchy
import { eRulePolicy, eToolkitLevel } from "../api/IRule";
import { RPTUtil } from "../../v2/checker/accessibility/util/legacy";
import { getCache } from "../util/CacheUtil";
import { isTableDescendant } from "../util/CommonUtil";

export let aria_role_redundant: Rule = {
id: "aria_role_redundant",
Expand Down Expand Up @@ -46,10 +47,14 @@ export let aria_role_redundant: Rule = {

// dependency check: if the ARIA attribute is completely invalid, skip this check
if (getCache(ruleContext, "aria_semantics_role", "") === "Fail_1") return null;

// dependency check: if it's already failed in the parent relation, then skip this check
if (["td", "th", "tr"].includes(elemName) && getCache(ruleContext, "table_aria_descendants", "") === "explicit_role")
return null;

if (["td", "th", "tr"].includes(elemName)) {
let parentRole = isTableDescendant(contextHierarchies);
if (parentRole !== null && parentRole.length > 0)
return null;
}

let ariaRoles = RPTUtil.getRoles(ruleContext, false);
if (!ariaRoles || ariaRoles.length === 0) return;

Expand Down
59 changes: 33 additions & 26 deletions accessibility-checker-engine/src/v4/rules/aria_semantics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Rule, RuleResult, RuleFail, RuleContext, RulePass, RuleContextHierarchy
import { eRulePolicy, eToolkitLevel } from "../api/IRule";
import { RPTUtil } from "../../v2/checker/accessibility/util/legacy";
import { getCache, setCache } from "../util/CacheUtil";
import { getInvalidAriaAttributes, areRolesDefined } from "../util/CommonUtil";
import { getInvalidAriaAttributes, areRolesDefined, isTableDescendant } from "../util/CommonUtil";

export let aria_semantics_role: Rule = {
id: "aria_semantics_role",
Expand Down Expand Up @@ -52,9 +52,12 @@ export let aria_semantics_role: Rule = {
return null;

// dependency check: if it's already failed, then skip
if (["td", "th", "tr"].includes(tagName) && getCache(ruleContext, "table_aria_descendants", "") === "explicit_role")
return null;

if (["td", "th", "tr"].includes(tagName)) {
let parentRole = isTableDescendant(contextHierarchies);
if (parentRole !== null && parentRole.length > 0)
return null;
}

let domRoles: string[] = RPTUtil.getUserDefinedRoles(ruleContext);

// check the 'generic' role first
Expand All @@ -78,29 +81,33 @@ export let aria_semantics_role: Rule = {

let tagProperty = RPTUtil.getElementAriaProperty(ruleContext);
allowedRoles = RPTUtil.getAllowedAriaRoles(ruleContext, tagProperty);
// Testing restrictions for each role and adding the corresponding attributes to the allowed attribute list
for (let i = 0; i < domRoles.length; i++) {
if (allowedRoles.length === 0) {
if (!failRoleTokens.includes(domRoles[i])) {
failRoleTokens.push(domRoles[i]);
}
} else if (!allowedRoles.includes("any")) { // any role is valid so no checking here. the validity of the aria role is checked by Rpt_Aria_ValidRole
if (!allowedRoles.includes(domRoles[i])) {
if (!failRoleTokens.includes(domRoles[i])) {
failRoleTokens.push(domRoles[i]);
}
} else if (!passRoleTokens.includes(domRoles[i])) {
passRoleTokens.push(domRoles[i])
}
} else if (allowedRoles.includes("any")) {
if (domRoles[i] === 'generic' && failRoleTokens.indexOf(domRoles[i]) === -1) {
failRoleTokens.push(domRoles[i]);
} else {
if (passRoleTokens.indexOf(domRoles[i]) === -1)
passRoleTokens.push(domRoles[i]);
}

if (!allowedRoles || allowedRoles.length === 0) {
RPTUtil.concatUniqueArrayItemList(domRoles, failRoleTokens);
} else {
if (allowedRoles && allowedRoles.includes('any')) {
RPTUtil.concatUniqueArrayItemList(domRoles, passRoleTokens);
} else {
// Testing restrictions for each role and adding the corresponding attributes to the allowed attribute list
for (let i = 0; i < domRoles.length; i++) {
if (!allowedRoles.includes(domRoles[i])) {
if (!failRoleTokens.includes(domRoles[i])) {
failRoleTokens.push(domRoles[i]);
}
} else if (!passRoleTokens.includes(domRoles[i])) {
passRoleTokens.push(domRoles[i])
}
/**else if (allowedRoles.includes("any")) {
//if (domRoles[i] === 'generic' && failRoleTokens.indexOf(domRoles[i]) === -1) {
// failRoleTokens.push(domRoles[i]);
//} else {
if (passRoleTokens.indexOf(domRoles[i]) === -1)
passRoleTokens.push(domRoles[i]);
//}
}*/
} // for loop
}
} // for loop
}
if (failRoleTokens.includes("presentation") || failRoleTokens.includes("none") && RPTUtil.isTabbable(ruleContext)) {
return RuleFail("Fail_2", [failRoleTokens.join(", "), tagName]);
} else if (failRoleTokens.length > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
limitations under the License.
*****************************************************************************/

import { Rule, RuleResult, RuleFail, RuleContext, RulePotential, RuleManual, RulePass, RuleContextHierarchy } from "../api/IRule";
import { Rule, RuleResult, RuleFail, RuleContext, RuleContextHierarchy } from "../api/IRule";
import { eRulePolicy, eToolkitLevel } from "../api/IRule";
import { setCache } from "../util/CacheUtil";
import { isTableDescendant } from "../util/CommonUtil";

export let table_aria_descendants: Rule = {
id: "table_aria_descendants",
Expand All @@ -38,10 +39,12 @@ export let table_aria_descendants: Rule = {
}],
act: [],
run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => {
const ruleContext = context["dom"].node as Element;
let parentRole = contextHierarchies["aria"].filter(hier => ["table", "grid", "treegrid"].includes(hier.role));
const ruleContext = context["dom"].node as Element;
let parentRole = isTableDescendant(contextHierarchies);
// cache the result
setCache(ruleContext, "table_aria_descendants", "explicit_role");
if (parentRole === null || parentRole.length === 0)
return;

return RuleFail("explicit_role", [context["dom"].node.nodeName.toLowerCase(), parentRole[0].role]);
}
}
55 changes: 51 additions & 4 deletions accessibility-checker-engine/src/v4/util/CommonUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
limitations under the License.
*****************************************************************************/

import {RuleContextHierarchy } from "../api/IRule";
import { RPTUtil } from "../../v2/checker/accessibility/util/legacy";
import { ARIADefinitions } from "../../v2/aria/ARIADefinitions";

Expand All @@ -31,6 +32,44 @@ export function areRolesDefined(roles: string[]) {
return true;
}

/*
* check if any explicit role specified for the element is a valid ARIA role
* return: null if no explicit role is defined,
* true if the role(s) are defined in ARIA
* false if any role is not defined in ARIA
*/
export function getInvalidRoles(ruleContext: Element) {
let domRoles: string[] = RPTUtil.getUserDefinedRoles(ruleContext);

if (!domRoles || domRoles.length === 0)
return null;

// check the 'generic' role first
if (domRoles && domRoles.includes('generic'))
return ["generic"];

// Failing roles
let failRoleTokens = [];
// Passing roles
let passRoleTokens = [];

let tagProperty = RPTUtil.getElementAriaProperty(ruleContext);
let allowedRoles = RPTUtil.getAllowedAriaRoles(ruleContext, tagProperty);
if (!allowedRoles && allowedRoles.length === 0)
return domRoles;

let invalidRoles = [];

if (allowedRoles && allowedRoles.includes('any'))
return [];

for (let i = 0; i < domRoles.length; i++)
if (!allowedRoles.includes(domRoles[i]) && !invalidRoles.includes(domRoles[i]))
invalidRoles.push(domRoles[i]);

return invalidRoles;
}

/*
* this method first checks explicit roles, if no explicit role, it will check the implicit role
* return: null if any explicit role is invalid,
Expand Down Expand Up @@ -67,10 +106,8 @@ export function getInvalidAriaAttributes(ruleContext: Element): string[] {
}

/*
* check if any explicit role specified for the element is a valid ARIA role
* return: null if no explicit role is defined,
* true if the role(s) are defined in ARIA
* false if any role is not defined in ARIA
* get conflict Aria and Html attributes
* return: a list of Aria and Html attribute pairs that are conflict
*/
export function getConflictAriaAndHtmlAttributes(elem: Element) {

Expand All @@ -90,3 +127,13 @@ export function getConflictAriaAndHtmlAttributes(elem: Element) {
}
return ret;
}

/*
* get conflict Aria and Html attributes
* return: a list of Aria and Html attribute pairs that are conflict
*/
export function isTableDescendant(contextHierarchies?: RuleContextHierarchy) {
if (!contextHierarchies) return null;

return contextHierarchies["aria"].filter(hier => ["table", "grid", "treegrid"].includes(hier.role));
}