diff --git a/accessibility-checker-engine/README-RULES.md b/accessibility-checker-engine/README-RULES.md index 3d05b669b..d5842685a 100644 --- a/accessibility-checker-engine/README-RULES.md +++ b/accessibility-checker-engine/README-RULES.md @@ -13,7 +13,7 @@ Multiple objects are needed for a rule to fire and show up in the tool results: ### Rule object -The basic rule format is defined by the Rule type in [src/v2/api/IEngine.ts](src/v2/api/IEngine.ts). Rule implementation is located in [src/v2/checker/accessibility/rules](src/v2/checker/accessibility/rules). The rule context, including DOM object hierarchies, attributes, explicit/implicit CSS and ARIA attributes, that may trigger a rule, are defined in [src/v2/common/Context.ts](src/v2/common/Context.ts). The rule results can be one of: +The basic rule format is defined by the Rule type in [src/v4/api/IRule.ts](src/v4/api/IRule.ts). Rule implementation is located in [src/v4/rules](src/v4/rules). The rule context, including DOM object hierarchies, attributes, explicit/implicit CSS and ARIA attributes, that may trigger a rule, are defined in [src/v2/common/Context.ts](src/v2/common/Context.ts). The rule results can be one of: * RulePass("MSG_ID") * RuleFail("MSG_ID") * RulePotential("MSG_ID") @@ -26,8 +26,26 @@ An example rule might look like: { id: "TRIGGER_ALL_BODY", context: "dom:body", - run: (context: RuleContext, - options?: {}): RuleResult | RuleResult[] => { + help: { + "en-US": { + 0: `TRIGGER_ALL_BODY.html`, + "Pass_0": `TRIGGER_ALL_BODY.html` + } + }, + messages: { + "en-US": { + "group": "Grouping label for the rule", + "Pass_0": "Check the body element for something" + } + }, + rulesets: [{ + id: [ "IBM_Accessibility", "WCAG_2_0", "WCAG_2_1"], + num: "2.1.1", // num: [ "2.4.4", "x.y.z" ] also allowed + level: eRulePolicy.RECOMMENDATION, + toolkitLevel: eToolkitLevel.LEVEL_FOUR + }], + act: {}, + run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => { const ruleContext = context["dom"].node as Element; const domAttrs = context["dom"].attributes; @@ -36,39 +54,7 @@ An example rule might look like: } ``` -### Ruleset mapping - -Rules are mapped to rulesets based on checkpoints. A rule may be mapped to one or more rulesets, and a ruleset may include one or more rules. The ruleset mappings are defined in [src/v2/checker/accessibility/rulesets/index.ts](src/v2/checker/accessibility/rulesets/index.ts). Rules are added to an appropriate checkpoint section with a mapping such as: -``` -{ - id: "TRIGGER_ALL_BODY", - level: eRulePolicy.VIOLATION, - toolkitLevel: eToolkitLevel.LEVEL_ONE -} -``` - -### Messages - -Each rule message is a short description of the result of a rule execution. Message mappings are defined in [src/v2/checker/accessibility/nls/index.ts](src/v2/checker/accessibility/nls/index.ts). Mappings are defined as: -``` -"TRIGGER_ALL_BODY": { - 0: "Passive message used for rule groupings", - "Pass_0": "Message with message code PASS_0 and arguments {0}, {1}, etc", - "Fail_1": "Another message with message code Fail_1." -}, -``` - -### Help file - -Each rule has its own help file in .mdx format. A help file contains rule description and examples. The rule help files are located in [help](help). The mapping between a rule and its help file is defined in [src/v2/checker/accessibility/help/index.ts](src/v2/checker/accessibility/help/index.ts): - -``` -"TRIGGER_ALL_BODY": { - 0: `${Config.helpRoot}/Rpt_Aria_MultipleApplicationLandmarks`, - "Pass_0": `${Config.helpRoot}/Rpt_Aria_MultipleApplicationLandmarks`, - "Fail_1": `${Config.helpRoot}/Rpt_Aria_MultipleApplicationLandmarks` -} -``` +Help files are found in [help-v4](help-v4). ## Test cases @@ -123,7 +109,7 @@ Then, run `npm test` again. You can run test cases to verify a rule implementation, or you can deploy the rules to a local rule server, and then build the browser extension to access the rules deployed in the local server to test. The steps to use a local server are: -* Build and start rule server. In `rule-server` run `npm run start` or without help `npm run start:nohelp`. +* Build and start rule server. In `rule-server` run `npm run start`. * Load `https://localhost:9445/` in the browser and type `thisisunsafe` to bypass cert warnings. * Build extension. In `accessibility-checker-extension` run `npm run build:watch:local`. * Add the extension in the `accessibility-checker-extension/dist` directory to Chrome. It will have the `(local)` label on the DevTools tab. @@ -134,7 +120,7 @@ Note: Rule changes are not automatically rebuilt. You will have to kill the rule * Create a rule id for a new rule. * Create the rule and ruleset mapping to [src/v2/checker/accessibility/rulesets/index.ts](src/v2/checker/accessibility/rulesets/index.ts). -* Create the .mdx help file in [help](help), and add the rule and the help file mapping to [src/v2/checker/accessibility/help/index.ts](src/v2/checker/accessibility/help/index.ts). -* Create the rule implementation in [src/v2/checker/accessibility/rules](src/v2/checker/accessibility/rules). The rule implementation includes the rule context, logic and outcome (Pass or Fail). +* Create the help file in [help-v4](help-v4). +* Create the rule implementation in [src/v4/rules](src/v4/rules). The rule implementation includes the rule context, logic and outcome (Pass or Fail). * Create test cases for the rule in [test/v2/checker/accessibility/rules](test/v2/checker/accessibility/rules). * Test the rules with the test cases. You may run the test cases locally, or run with the local rule server. \ No newline at end of file diff --git a/accessibility-checker-engine/src/v2/checker/accessibility/util/legacy.ts b/accessibility-checker-engine/src/v2/checker/accessibility/util/legacy.ts index 8b49a313e..3bb38b239 100644 --- a/accessibility-checker-engine/src/v2/checker/accessibility/util/legacy.ts +++ b/accessibility-checker-engine/src/v2/checker/accessibility/util/legacy.ts @@ -1374,7 +1374,7 @@ export class RPTUtil { while (nw.nextNode()) { if (formElements.includes(nw.node.nodeName.toLowerCase())) { if (RPTUtil.isNodeDisabled(nw.node)) - return true; + return true; return false; } } @@ -2425,14 +2425,14 @@ export class RPTUtil { // ignore aria-level, aria-setsize or aria-posinset if "row" is not in treegrid if (permittedRoles.includes("row") && RPTUtil.getAncestorWithRole(ruleContext, "treegrid", true) == null ) { - let index = -1; - if ((index = allowedAttributes.indexOf("aria-level")) > -1) + let index = -1; + if ((index = allowedAttributes.indexOf("aria-level")) > -1) allowedAttributes.splice(index, 1); - if ((index = allowedAttributes.indexOf("aria-setsize")) > -1) + if ((index = allowedAttributes.indexOf("aria-setsize")) > -1) allowedAttributes.splice(index, 1); - if ((index = allowedAttributes.indexOf("aria-posinset")) > -1) + if ((index = allowedAttributes.indexOf("aria-posinset")) > -1) allowedAttributes.splice(index, 1); } @@ -3455,21 +3455,22 @@ export class NodeWalker { { let ownerElement = this.node; this.node = iframeNode.contentDocument.documentElement; - (this.node as any).ownerElement = ownerElement; + (this.node as any).nwOwnerElement = ownerElement; } else if (this.node.nodeType === 1 /* Node.ELEMENT_NODE */ && elementNode.shadowRoot && elementNode.shadowRoot.firstChild) { let ownerElement = this.node; this.node = elementNode.shadowRoot; - (this.node as any).ownerElement = ownerElement; + (this.node as any).nwOwnerElement = ownerElement; } else if (this.node.nodeType === 1 && elementNode.nodeName.toLowerCase() === "slot" && slotElement.assignedNodes().length > 0) { let slotOwner = this.node; this.node = slotElement.assignedNodes()[0]; - (this.node as any).slotOwner = slotOwner; + (this.node as any).nwSlotOwner = slotOwner; + (this.node as any).nwSlotIndex = 0; } else if (this.node.firstChild) { this.node = this.node.firstChild; } else { @@ -3477,33 +3478,26 @@ export class NodeWalker { return this.nextNode(); } } else { - if (this.node.nextSibling) { - this.node = this.node.nextSibling; - this.bEndTag = false; - } else if ((this.node as any).ownerElement) { - this.node = (this.node as any).ownerElement; - this.bEndTag = true; - } else if ((this.node as any).slotOwner) { - if (this.node.nodeType !== 1 || !(this.node as HTMLElement).hasAttribute("slot")) { - // If this wasn't a named slot, look for the next unnamed node to put in the slot - let n = this.node.nextSibling; - while (n && this.node.nodeType === 1 && (this.node as HTMLElement).hasAttribute("slot")) { - n = this.node.nextSibling; - } - if (n) { - // We found another unnamed slot - let slotOwner = (this.node as any).slotOwner; - this.node = n; - (this.node as any).slotOwner = slotOwner; - this.bEndTag = false; - } else { - this.node = (this.node as any).slotOwner; - this.bEndTag = true; - } + if ((this.node as any).nwSlotOwner) { + let slotOwner = (this.node as any).nwSlotOwner; + let nextSlotIndex = (this.node as any).nwSlotIndex+1; + delete (this.node as any).nwSlotOwner; + delete (this.node as any).nwSlotIndex; + if (nextSlotIndex < slotOwner.assignedNodes().length) { + this.node = slotOwner.assignedNodes()[nextSlotIndex]; + (this.node as any).nwSlotOwner = slotOwner; + (this.node as any).nwSlotIndex = nextSlotIndex; + this.bEndTag = false; } else { - this.node = (this.node as any).slotOwner; + this.node = slotOwner; this.bEndTag = true; } + } else if ((this.node as any).nwOwnerElement) { + this.node = (this.node as any).nwOwnerElement; + this.bEndTag = true; + } else if (this.node.nextSibling) { + this.node = this.node.nextSibling; + this.bEndTag = false; } else if (this.node.parentNode) { this.node = this.node.parentNode; this.bEndTag = true; diff --git a/accessibility-checker-engine/src/v2/dom/DOMWalker.ts b/accessibility-checker-engine/src/v2/dom/DOMWalker.ts index 6e89fa0fa..b93ff30cc 100644 --- a/accessibility-checker-engine/src/v2/dom/DOMWalker.ts +++ b/accessibility-checker-engine/src/v2/dom/DOMWalker.ts @@ -84,6 +84,8 @@ export class DOMWalker { } else if ((this.node as any).slotOwner) { let slotOwner = (this.node as any).slotOwner; let nextSlotIndex = (this.node as any).slotIndex+1; + delete (this.node as any).slotOwner; + delete (this.node as any).slotIndex; if (nextSlotIndex < slotOwner.assignedNodes().length) { this.node = slotOwner.assignedNodes()[nextSlotIndex]; (this.node as any).slotOwner = slotOwner; diff --git a/rule-server/gulp/gulpfile.js b/rule-server/gulp/gulpfile.js index 4b711d5ea..37e7a511e 100644 --- a/rule-server/gulp/gulpfile.js +++ b/rule-server/gulp/gulpfile.js @@ -109,6 +109,7 @@ const archivePolicies = () => { latestArchive.policies = latestPol; latestArchive.rulesets = latestRS; latestArchive.version = latestVersion; + latestArchive.latest = true; } if (latestVersion !== releaseTag) { previewArchive.version = releaseTag;