diff --git a/accessibility-checker-engine/src/genHelp.ts b/accessibility-checker-engine/src/genHelp.ts index b9ade7d64..fff847fac 100644 --- a/accessibility-checker-engine/src/genHelp.ts +++ b/accessibility-checker-engine/src/genHelp.ts @@ -14,11 +14,12 @@ import { exec } from "child_process"; import { existsSync, readFileSync, writeFileSync } from "fs"; import { Rule as RuleV4 } from "./v4/api/IRule"; +import { Guideline } from "./v4/api/IGuideline"; //requiring path and fs modules const path = require('path'); const rulesV4 = require("./v4/rules"); -const rulesets = require("./v4/rulesets"); +const { a11yRulesets } = require("./v4/rulesets"); function myExec(cmd: string) : Promise { return new Promise((resolve, reject) => { @@ -136,7 +137,7 @@ width="16px" height="16px" viewBox="0 0 16 16" style="enable-background:new 0 0 function processRules() { let retVal = []; - for (const ruleset of rulesets.a11yRulesets) { + for (const ruleset of a11yRulesets as Guideline[]) { if (ruleset.type === "extension") continue; let rsInfo = { id: ruleset.id, @@ -153,27 +154,33 @@ function processRules() { rules: [] } for (const ruleId in rulesV4) { - let includeRule = false; - let rule = rulesV4[ruleId]; + let includeRuleCodes = new Set(); + let rule: RuleV4 = rulesV4[ruleId]; let rsLevel = ""; let toolkitLevel; for (const rsInfo of rule.rulesets) { if ((rsInfo.id === ruleset.id || rsInfo.id.includes(ruleset.id)) && (rsInfo.num === cp.num || rsInfo.num.includes(cp.num))) { - includeRule = true; + if (rsInfo.reasonCodes) { + rsInfo.reasonCodes.forEach(code => includeRuleCodes.add(code)); + } else { + for (const code in rule.messages["en-US"]) { + includeRuleCodes.add(code); + } + } rsLevel = rsInfo.level; toolkitLevel = rsInfo.toolkitLevel; break; } } - if (includeRule) { + if (includeRuleCodes.size > 0) { let ruleInfo = { level: rsLevel, toolkitLevel: toolkitLevel, rule: rule, reasons: [] } - for (const msgCode in rule.messages["en-US"]) { - if (msgCode === "group") continue; + includeRuleCodes.forEach((msgCode) => { + if (msgCode === "group") return; let re = new RegExp(`\\.Rule([^()) ]+)[ ()]+["']${msgCode}["']`); let reMatch = re.exec(rule.run.toString()); if (reMatch && reMatch[1] !== "Pass") { @@ -184,7 +191,7 @@ function processRules() { type: reMatch[1] }) } - } + }) ruleInfo.reasons.sort((a,b) => { if (a.type === b.type) return 0; if (a.level === "Fail") return -1; diff --git a/accessibility-checker-engine/src/index.ts b/accessibility-checker-engine/src/index.ts index 8589f479a..a731b388b 100644 --- a/accessibility-checker-engine/src/index.ts +++ b/accessibility-checker-engine/src/index.ts @@ -14,12 +14,13 @@ limitations under the License. *****************************************************************************/ -import { Context } from "./v2/common/Context" +export { Context } from "./v2/common/Context" // import { Simulator } from "./v2/simulator" import { Checker } from "./v4/checker/Checker" -import { ARIAMapper } from "./v2/aria/ARIAMapper"; -import { Config } from "./v2/config/Config"; -import { DOMWalker } from "./v2/dom/DOMWalker"; +export { Checker } +export { ARIAMapper } from "./v2/aria/ARIAMapper"; +export { Config } from "./v2/config/Config"; +export { DOMWalker } from "./v2/dom/DOMWalker"; String.prototype.startsWith = String.prototype.startsWith || function (str) { return this.indexOf(str) === 0; @@ -30,23 +31,8 @@ String.prototype.includes = String.prototype.includes || function (str) { Array.prototype.includes = Array.prototype.includes || function (str) { return this.indexOf(str) !== -1; } -/* -function simDemo(timeout?: number) { - if (!timeout) timeout = 0; - setTimeout(function() { - let sim = new Simulator(); - let s = sim.renderItem(document.documentElement); - console.group("--- Item View ---"); - console.log(s); - console.groupEnd(); - console.group("--- Link View ---"); - s = sim.renderLink(document.documentElement); - console.log(s); - console.groupEnd(); - }, timeout); -} -*/ -function checkDemo(timeout?: number) { + +export function checkDemo(timeout?: number) { if (!timeout) timeout = 0; let checker = new Checker(); setTimeout(function() { @@ -103,5 +89,3 @@ function checkDemo(timeout?: number) { }); }, timeout); } - -export { Checker, Context, ARIAMapper, checkDemo, Config/*, simDemo*/, DOMWalker }; \ No newline at end of file diff --git a/accessibility-checker-engine/src/v2/api/IEngine.ts b/accessibility-checker-engine/src/v2/api/IEngine.ts index 661371537..ffef698c6 100644 --- a/accessibility-checker-engine/src/v2/api/IEngine.ts +++ b/accessibility-checker-engine/src/v2/api/IEngine.ts @@ -14,182 +14,120 @@ limitations under the License. *****************************************************************************/ -import { IMapResult } from "./IMapper"; - -export enum eRuleConfidence { - PASS = "PASS", - FAIL = "FAIL", - POTENTIAL = "POTENTIAL", - MANUAL = "MANUAL" -} - -export enum eRulePolicy { - VIOLATION = "VIOLATION", - RECOMMENDATION = "RECOMMENDATION", - INFORMATION = "INFORMATION" -} - -export enum eToolkitLevel { - LEVEL_ONE = "1", - LEVEL_TWO = "2", - LEVEL_THREE = "3", - LEVEL_FOUR = "4" -} - -export enum eRuleCategory { - ACCESSIBILITY = "Accessibility", - DESIGN = "Design", - OTHER = "Other" -} - -export enum eRulesetType { - DEFAULT = "default", - EXTENSION = "extension" -} - -export function RulePass(reasonId: number | string, messageArgs? : string[], apiArgs? : any[]) : RuleResult { - if (typeof reasonId === "undefined" || reasonId === null) throw new Error("Reason ID must be defined"); - return { - value: [eRulePolicy.INFORMATION, eRuleConfidence.PASS], - reasonId: reasonId, - messageArgs: messageArgs || [], - apiArgs: apiArgs || [] - } -} - -export function RuleRender(reasonId: number | string, messageArgs? : string[], apiArgs? : any[]) : RuleResult { - if (typeof reasonId === "undefined" || reasonId === null) throw new Error("Reason ID must be defined"); - return { - value: [eRulePolicy.INFORMATION, eRuleConfidence.PASS], - reasonId: 0, - messageArgs: messageArgs || [], - apiArgs: apiArgs || [] - } -} -export function RuleFail(reasonId: number | string, messageArgs? : string[], apiArgs? : any[]) : RuleResult { - if (typeof reasonId === "undefined" || reasonId === null) throw new Error("Reason ID must be defined"); - return { - value: [eRulePolicy.INFORMATION, eRuleConfidence.FAIL], - reasonId: reasonId, - messageArgs: messageArgs || [], - apiArgs: apiArgs || [] - } -} - -export function RulePotential(reasonId: number | string, messageArgs? : string[], apiArgs? : any[]) : RuleResult { - if (typeof reasonId === "undefined" || reasonId === null) throw new Error("Reason ID must be defined"); - return { - value: [eRulePolicy.INFORMATION, eRuleConfidence.POTENTIAL], - reasonId: reasonId, - messageArgs: messageArgs || [], - apiArgs: apiArgs || [] - } -} - -export function RuleManual(reasonId: number | string, messageArgs? : string[], apiArgs? : any[]) : RuleResult { - if (typeof reasonId === "undefined" || reasonId === null) throw new Error("Reason ID must be defined"); - return { - value: [eRulePolicy.INFORMATION, eRuleConfidence.MANUAL], - reasonId: reasonId, - messageArgs: messageArgs || [], - apiArgs: apiArgs || [] - } -} - -export type RuleResult = { - value: [eRulePolicy, eRuleConfidence], - reasonId?: number | string, - messageArgs?: string[], - apiArgs?: any[] -} - -export type RuleDetails = RuleResult & { - ruleId: string, - - node: Node, - // namespace: string, - category?: eRuleCategory, - path: { [ns: string] : string }, - - ruleTime: number, - message: string, - bounds?: { - top: number, - left: number, - width: number, - height: number - }, - snippet: string -} - -export type RuleContextHierarchy = { [namespace: string] : IMapResult[] }; - -export type RuleContext = { - [namespace: string] : IMapResult -} - -export type Rule = { - // Unique string identifier for this rule (should be human understandable) - // NLS codes and help sources will be based off of this id - id: string; - - // See src/v2/common/Context.ts for valid contexts - context: string; - - // Array of rules that must pass to allow this validate to run - they must have the same context property - dependencies?: string[] - prereqs?: string[] - - run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy) => RuleResult | RuleResult[] - - enabled?: boolean -} - - -export type Report = { - results: RuleDetails[], - numExecuted: number, - ruleTime: number, - // This may be undefined for a filtered report - totalTime?: number, - nls?: { - [ruleId: string]: { - [reasonId: string]: string - } - } -} - -export type NlsMap = { - [key: string]: string[] -} - -export type HelpMap = { - [key: string]: string[] -} - -export interface IEngine { - /** - * Perform a scan on a document or subtree - * @param rulesetIds Array of ruleset ids of rulesets to use for this scan - * @param root Document or subtree to scan - */ - run(root: Document | Node, options?: {}) : Promise; - - enableRules(ruleIds: string[]); - - getRule(ruleId: string): Rule; - - getRulesIds() : string[]; - - getMessage(ruleId: string, ruleIdx: number | string, msgArgs?: string[]): string; - - getHelp(ruleId: string, ruleIdx: number | string): string; - - addRules(rule: Rule[]); - - addRule(rule: Rule); - - addNlsMap(map: NlsMap); - - addHelpMap(map: NlsMap); -} \ No newline at end of file +import { + Issue, + RulePass as RulePassNew, + RuleFail as RuleFailNew, + RuleRender as RuleRenderNew, + RulePotential as RulePotentialNew, + RuleManual as RuleManualNew, + RuleResult as RuleResultNew, + RuleContextHierarchy as RuleContextHierarchyNew, + RuleContext as RuleContextNew, + Rule as RuleNew +} from "../../v4/api/IRule"; + +import { + NlsMap as NlsMapNew, + HelpMap as HelpMapNew, + IEngine as IEngineNew +} from "../../v4/api/IEngine"; + +import { + Report as ReportNew +} from "../../v4/api/IReport"; + +/** + * @deprecated See ../../v4/api/IRule + */ +export { eRuleConfidence } from "../../v4/api/IRule"; + +/** + * @deprecated See ../../v4/api/IRule + */ +export { eRulePolicy } from "../../v4/api/IRule"; + +/** + * @deprecated See ../../v4/api/IGuideline + */ +export { eToolkitLevel } from "../../v4/api/IGuideline"; + +/** + * @deprecated See ../../v4/api/IGuideline::eGuidelineCategory + */ +export { eGuidelineCategory as eRuleCategory } from "../../v4/api/IGuideline"; + +/** + * @deprecated See ../../v4/api/IGuideline::eGuidelineType + */ +export { eGuidelineType as eRulesetType } from "../../v4/api/IGuideline"; + +/** + * @deprecated See ../../v4/api/IRule + */ +export let RulePass = RulePassNew; + +/** + * @deprecated See ../../v4/api/IRule + */ +export let RuleRender = RuleRenderNew; + +/** + * @deprecated See ../../v4/api/IRule + */ +export let RuleFail = RuleFailNew; + +/** + * @deprecated See ../../v4/api/IRule + */ +export let RulePotential = RulePotentialNew; + +/** + * @deprecated See ../../v4/api/IRule + */ +export let RuleManual = RuleManualNew; + +/** + * @deprecated See ../../v4/api/IRule + */ +export type RuleResult = RuleResultNew; + +/** + * @deprecated See ../../v4/api/IRule + */ +export type RuleDetails = Issue + +/** + * @deprecated See ../../v4/api/IRule + */ +export type RuleContextHierarchy = RuleContextHierarchyNew + +/** + * @deprecated See ../../v4/api/IRule + */ +export type RuleContext = RuleContextNew + +/** + * @deprecated See ../../v4/api/IRule + */ +export type Rule = RuleNew + +/** + * @deprecated See ../../v4/api/IEngine + */ +export type Report = ReportNew; + +/** + * @deprecated See ../../v4/api/IEngine + */ +export type NlsMap = NlsMapNew + +/** + * @deprecated See ../../v4/api/IEngine + */ +export type HelpMap = HelpMapNew + +/** + * @deprecated See ../../v4/api/IEngine + */ +export type IEngine = IEngineNew \ No newline at end of file diff --git a/accessibility-checker-engine/src/v2/api/IMapper.ts b/accessibility-checker-engine/src/v2/api/IMapper.ts index c960dd53e..bc4ddb79d 100644 --- a/accessibility-checker-engine/src/v2/api/IMapper.ts +++ b/accessibility-checker-engine/src/v2/api/IMapper.ts @@ -14,27 +14,23 @@ limitations under the License. *****************************************************************************/ -export type Bounds = { - left: number, - top: number, - width: number, - height: number -} +import { + Bounds as BoundsNew, + IMapResult as IMapResultNew, + IMapper as IMapperNew +} from "../../v4/api/IMapper"; -export interface IMapResult { - node: Node, - namespace: string, - role: string, - rolePath: string, - attributes: { - [key: string]: string - }, - bounds?: Bounds -} +/** + * @deprecated See ../../v4/api/IMapper + */ +export type Bounds = BoundsNew; -export interface IMapper { - reset(node: Node): void; - openScope(node: Node) : IMapResult[]; - closeScope(node: Node) : IMapResult[]; - getNamespace() : string; -} \ No newline at end of file +/** + * @deprecated See ../../v4/api/IMapper + */ +export type IMapResult = IMapResultNew; + +/** + * @deprecated See ../../v4/api/IMapper + */ +export type IMapper = IMapperNew; \ No newline at end of file diff --git a/accessibility-checker-engine/src/v2/aria/ARIADefinitions.ts b/accessibility-checker-engine/src/v2/aria/ARIADefinitions.ts index 3e0dc2dc4..6d622f8c9 100644 --- a/accessibility-checker-engine/src/v2/aria/ARIADefinitions.ts +++ b/accessibility-checker-engine/src/v2/aria/ARIADefinitions.ts @@ -18,7 +18,7 @@ // all references to WAI-ARIA specification is the WAI-ARIA 1.2 // https://www.w3.org/TR/wai-aria-1.2/ -export interface IDocumentConformanceRequirement { +export type IDocumentConformanceRequirement = { implicitRole: string[], validRoles: string[], globalAriaAttributesValid: boolean, diff --git a/accessibility-checker-engine/src/v2/common/Engine.ts b/accessibility-checker-engine/src/v2/common/Engine.ts index 06adf0aae..2cdd35996 100644 --- a/accessibility-checker-engine/src/v2/common/Engine.ts +++ b/accessibility-checker-engine/src/v2/common/Engine.ts @@ -14,14 +14,16 @@ limitations under the License. *****************************************************************************/ -import { IEngine, Report, Rule, RuleDetails, RuleResult, eRuleConfidence, RuleContext, NlsMap, HelpMap, RuleContextHierarchy } from "../api/IEngine"; import { DOMWalker } from "../dom/DOMWalker"; import { Context, PartInfo, AttrInfo } from "./Context"; import { Config } from "../config/Config"; -import { IMapResult, IMapper } from "../api/IMapper"; import { DOMMapper } from "../dom/DOMMapper"; import { DOMUtil } from "../dom/DOMUtil"; import { clearCaches } from "../../v4/util/CacheUtil"; +import { Issue, Rule, RuleContext, RuleContextHierarchy, RuleResult, eRuleConfidence } from "../../v4/api/IRule"; +import { HelpMap, IEngine, NlsMap } from "../../v4/api/IEngine"; +import { IMapper } from "../../v4/api/IMapper"; +import { Report } from "../../v4/api/IReport"; class WrappedRule { ns: string; @@ -74,7 +76,7 @@ class WrappedRule { return nodeSnippet; } - run(engine: Engine, context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy) : RuleDetails[] { + run(engine: Engine, context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy) : Issue[] { const startTime = new Date().getTime(); let results: RuleResult | RuleResult[]; try { @@ -90,7 +92,7 @@ class WrappedRule { if (!(results instanceof Array)) { results = [results]; } - let retVal : RuleDetails[] = []; + let retVal : Issue[] = []; for (const result of results) { const message = engine.getMessage(this.rule.id, result.reasonId, result.messageArgs); const path = {}; @@ -219,7 +221,7 @@ export class Engine implements IEngine { if (!depMatch[dep]) fulfillsDependencies = false; } if (fulfillsDependencies) { - let results : RuleDetails[] = []; + let results : Issue[] = []; try { results = matchingRule.run(this, context, options, contextHierarchies); } catch (err) { @@ -525,13 +527,13 @@ export class Engine implements IEngine { } } } - if (depRule.rule.prereqs && depRule.rule.prereqs.length > 0) { - for (const depId of depRule.rule.prereqs) { - if (!(depId in idToRule)) { - allMatch = false; - } - } - } + // if (depRule.rule.prereqs && depRule.rule.prereqs.length > 0) { + // for (const depId of depRule.rule.prereqs) { + // if (!(depId in idToRule)) { + // allMatch = false; + // } + // } + // } if (allMatch) { change = true; retVal.push(depRule); diff --git a/accessibility-checker-engine/src/v2/simulator/Simulator.ts b/accessibility-checker-engine/src/v2/simulator/Simulator.ts deleted file mode 100644 index c05c4ef32..000000000 --- a/accessibility-checker-engine/src/v2/simulator/Simulator.ts +++ /dev/null @@ -1,63 +0,0 @@ -/****************************************************************************** - Copyright:: 2020- IBM, Inc - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - *****************************************************************************/ - -import { IEngine } from "../api/IEngine"; -import { Engine } from "../common/Engine"; -import { ARIAMapper } from "../aria/ARIAMapper"; -import { StyleMapper } from "../style/StyleMapper"; -import { simRules } from "./rules"; -import { simNls } from "./nls"; - -export class Simulator { - engine: IEngine; - - constructor() { - let engine = this.engine = new Engine(); - engine.addMapper(new ARIAMapper()); - engine.addMapper(new StyleMapper()); - engine.addRules(simRules); - engine.addNlsMap(simNls); - engine.enableRules(null); - } - - async renderItem(node: Node, bEndTag?: boolean) : Promise { - bEndTag = !!bEndTag; - let report = await this.engine.run(node, { "mode": "ITEM" }); - let s = ""; - for (const result of report.results) { - s += result.message - .replace(/^\s+(\S)/g,"$1") // remove leading whitespace if there's a character - .replace(/ +/g, " ") // replace multiple spaces with a single - .replace(/^ +$/g, "") // replace all spaces with nothing - .replace(/[\n]{2,}/g,"\n"); // replace two or more newlines with a single - } - return s; - } - - async renderLink(node: Node, bEndTag?: boolean) :Promise { - bEndTag = !!bEndTag; - let report = await this.engine.run(node, { "mode": "LINK" }); - let s = ""; - for (const result of report.results) { - s += result.message; - } - return s.replace(/ +/g, " "); - } - - getEngine() : IEngine { - return this.engine; - } -} \ No newline at end of file diff --git a/accessibility-checker-engine/src/v2/simulator/rules/index.ts b/accessibility-checker-engine/src/v2/simulator/rules/index.ts deleted file mode 100644 index eb8e9fb63..000000000 --- a/accessibility-checker-engine/src/v2/simulator/rules/index.ts +++ /dev/null @@ -1,106 +0,0 @@ -/****************************************************************************** - Copyright:: 2020- IBM, Inc - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - *****************************************************************************/ - -import { Rule, RuleResult, RuleRender, RuleContext } from "../../api/IEngine"; -import { DOMUtil } from "../../dom/DOMUtil"; - -let simRules: Rule[] = [ - { - id: "LinkRule", - context: "aria:/link", - run: (context: RuleContext, options?: { [key: string]: any }): RuleResult | RuleResult[] => { - if (options.mode === "ITEM" || options.mode === "LINK") { - return RuleRender(0); - } - } - }, - { - id: "Button", - context: "aria:button", - run: (context: RuleContext, options?: { [key: string]: any }): RuleResult | RuleResult[] => { - if (options.mode === "ITEM" || options.mode === "LINK") { - return RuleRender(0,[context["dom"].node.textContent]); - } - } - }, - { - id: "ButtonEnd", - context: "aria:/button", - run: (context: RuleContext, options?: { [key: string]: any }): RuleResult | RuleResult[] => { - if (options.mode === "ITEM" || options.mode === "LINK") { - return RuleRender(0); - } - } - }, - { - id: "Headings", - context: "aria:heading", - run: (context: RuleContext, options?: { [key: string]: any }): RuleResult | RuleResult[] => { - if (options.mode === "ITEM") { - return RuleRender(0,[context["dom"].node.nodeName.substring(1)]); - } - } - }, - { - id: "Textbox", - context: "aria:textbox", - run: (context: RuleContext, options?: { [key: string]: any }): RuleResult | RuleResult[] => { - if (options.mode === "ITEM" || options.mode === "LINK") { - let elem = context["dom"].node as Element; - return RuleRender(0,[ - options.mode === "ITEM"?"":context["aria"].attributes.name, - (elem.nodeValue || elem.getAttribute("placeholder") || "")]); - } - } - }, - { - id: "TextRule", - context: "aria:document !dom:script aria:text", - run: (context: RuleContext, options?: { [key: string]: any }): RuleResult | RuleResult[] => { - if (options.mode === "ITEM") { - return RuleRender(0,[DOMUtil.cleanWhitespace(context["dom"].node.nodeValue)]); - } - } - }, - { - id: "ImgRule", - context: "aria:document dom:img", - run: (context: RuleContext, options?: { [key: string]: any }): RuleResult | RuleResult[] => { - if (options.mode === "ITEM") { - const elem = context["dom"].node as Element; - return RuleRender(0,[elem.getAttribute("alt") || "NOALT"]); - } - } - }, - { - id: "EndBlock", - context: "css:/computed[display~block]", - run: (context: RuleContext, options?: { [key: string]: any }): RuleResult | RuleResult[] => { - if (options.mode === "ITEM") { - return RuleRender(0); - } - } - }, - { - id: "EndLink", - context: "aria:/link,aria:/button,aria:/textbox", - run: (context: RuleContext, options?: { [key: string]: any }): RuleResult | RuleResult[] => { - if (options.mode === "LINK") { - return RuleRender(0); - } - } - }] -export { simRules } \ No newline at end of file diff --git a/accessibility-checker-engine/src/v2/simulator/index.ts b/accessibility-checker-engine/src/v4/api/IBounds.ts similarity index 88% rename from accessibility-checker-engine/src/v2/simulator/index.ts rename to accessibility-checker-engine/src/v4/api/IBounds.ts index 683e843a4..224d65b34 100644 --- a/accessibility-checker-engine/src/v2/simulator/index.ts +++ b/accessibility-checker-engine/src/v4/api/IBounds.ts @@ -14,5 +14,9 @@ limitations under the License. *****************************************************************************/ -import { Simulator } from "./Simulator"; -export { Simulator } \ No newline at end of file +export type Bounds = { + left: number, + top: number, + width: number, + height: number +} diff --git a/accessibility-checker-engine/src/v4/api/IChecker.ts b/accessibility-checker-engine/src/v4/api/IChecker.ts new file mode 100644 index 000000000..81cf5a684 --- /dev/null +++ b/accessibility-checker-engine/src/v4/api/IChecker.ts @@ -0,0 +1,29 @@ +/****************************************************************************** + Copyright:: 2020- IBM, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *****************************************************************************/ + +import { Checkpoint, Guideline } from "./IGuideline"; +import { Issue } from "./IRule"; +import { Report } from "./IReport"; + +export interface IChecker { + addGuideline(guideline: Guideline); + + getGuidelines() : Guideline[] + + getGuidelineIds() : string[] + + check(node: Node | Document, guidelineIds?: string | string[]) : Promise; +} \ No newline at end of file diff --git a/accessibility-checker-engine/src/v4/api/IEngine.ts b/accessibility-checker-engine/src/v4/api/IEngine.ts new file mode 100644 index 000000000..9cd63deea --- /dev/null +++ b/accessibility-checker-engine/src/v4/api/IEngine.ts @@ -0,0 +1,58 @@ +/****************************************************************************** + Copyright:: 2020- IBM, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *****************************************************************************/ + +import { Rule } from "./IRule"; +import { Report as ReportNew } from "./IReport"; + +export interface IEngine { + /** + * Perform a scan on a document or subtree + * @param rulesetIds Array of ruleset ids of rulesets to use for this scan + * @param root Document or subtree to scan + */ + run(root: Document | Node, options?: {}) : Promise; + + enableRules(ruleIds: string[]); + + getRule(ruleId: string): Rule; + + getRulesIds() : string[]; + + getMessage(ruleId: string, ruleIdx: number | string, msgArgs?: string[]): string; + + getHelp(ruleId: string, ruleIdx: number | string): string; + + addRules(rule: Rule[]); + + addRule(rule: Rule); + + addNlsMap(map: NlsMap); + + addHelpMap(map: NlsMap); +} + +export type NlsMap = { + [key: string]: string[] +} + +export type HelpMap = { + [key: string]: string[] +} + +/** + * @deprecated See ./Report + */ +export type Report = ReportNew; diff --git a/accessibility-checker-engine/src/v4/api/IGuideline.ts b/accessibility-checker-engine/src/v4/api/IGuideline.ts new file mode 100644 index 000000000..9fe96ee7d --- /dev/null +++ b/accessibility-checker-engine/src/v4/api/IGuideline.ts @@ -0,0 +1,62 @@ +/****************************************************************************** + Copyright:: 2020- IBM, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *****************************************************************************/ + +import { eRulePolicy } from "./IRule" + +export enum eToolkitLevel { + LEVEL_ONE = "1", + LEVEL_TWO = "2", + LEVEL_THREE = "3", + LEVEL_FOUR = "4" +} + +export enum eGuidelineCategory { + ACCESSIBILITY = "Accessibility", + DESIGN = "Design", + OTHER = "Other" +} + +export enum eGuidelineType { + DEFAULT = "default", + EXTENSION = "extension" +} + +export type Checkpoint = { + num: string, + // See https://github.com/act-rules/act-tools/blob/main/src/data/sc-urls.json + scId?: string, + // JCH: add name of checkpoint and summary description + name: string, + wcagLevel: string, + summary: string, + rules?: Array<{ + id: string, + // (optional) Reason codes that this ruleset mapping applies to, + // or all if not specified + reasonCodes?: string[], + level: eRulePolicy, + toolkitLevel: eToolkitLevel + }> +} + +export type Guideline = { + id: string, + name: string, + category: eGuidelineCategory, + description: string, + type?: eGuidelineType, + checkpoints: Array +} diff --git a/accessibility-checker-engine/src/v4/api/IMapper.ts b/accessibility-checker-engine/src/v4/api/IMapper.ts index c960dd53e..a8a378a6e 100644 --- a/accessibility-checker-engine/src/v4/api/IMapper.ts +++ b/accessibility-checker-engine/src/v4/api/IMapper.ts @@ -14,14 +14,14 @@ limitations under the License. *****************************************************************************/ -export type Bounds = { - left: number, - top: number, - width: number, - height: number -} +import { Bounds as BoundsNew } from "./IBounds"; + +/** + * @deprecated See ./IBounds + */ +export type Bounds = BoundsNew; -export interface IMapResult { +export type IMapResult = { node: Node, namespace: string, role: string, @@ -29,7 +29,7 @@ export interface IMapResult { attributes: { [key: string]: string }, - bounds?: Bounds + bounds?: BoundsNew } export interface IMapper { diff --git a/accessibility-checker-engine/src/v4/api/IReport.ts b/accessibility-checker-engine/src/v4/api/IReport.ts new file mode 100644 index 000000000..305bb395c --- /dev/null +++ b/accessibility-checker-engine/src/v4/api/IReport.ts @@ -0,0 +1,30 @@ +/****************************************************************************** + Copyright:: 2020- IBM, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *****************************************************************************/ + +import { Issue } from "./IRule" + +export type Report = { + results: Issue[], + numExecuted: number, + ruleTime: number, + // This may be undefined for a filtered report + totalTime?: number, + nls?: { + [ruleId: string]: { + [reasonId: string]: string + } + } +} \ No newline at end of file diff --git a/accessibility-checker-engine/src/v4/api/IRule.ts b/accessibility-checker-engine/src/v4/api/IRule.ts index 272c58800..19c1bdae8 100644 --- a/accessibility-checker-engine/src/v4/api/IRule.ts +++ b/accessibility-checker-engine/src/v4/api/IRule.ts @@ -14,7 +14,12 @@ limitations under the License. *****************************************************************************/ +import { eGuidelineCategory, eGuidelineType } from "./IGuideline"; import { IMapResult } from "./IMapper"; +import { NlsMap as NlsMapNew, HelpMap as HelpMapNew, IEngine as IEngineNew } from "./IEngine"; +import { Report as ReportNew } from "./IReport"; +import { eToolkitLevel as eToolkitLevelNew } from "./IGuideline" +import { Bounds } from "./IBounds"; export enum eRuleConfidence { PASS = "PASS", @@ -29,24 +34,6 @@ export enum eRulePolicy { INFORMATION = "INFORMATION" } -export enum eToolkitLevel { - LEVEL_ONE = "1", - LEVEL_TWO = "2", - LEVEL_THREE = "3", - LEVEL_FOUR = "4" -} - -export enum eRuleCategory { - ACCESSIBILITY = "Accessibility", - DESIGN = "Design", - OTHER = "Other" -} - -export enum eRulesetType { - DEFAULT = "default", - EXTENSION = "extension" -} - export function RulePass(reasonId: number | string, messageArgs? : string[], apiArgs? : any[]) : RuleResult { if (typeof reasonId === "undefined" || reasonId === null) throw new Error("Reason ID must be defined"); return { @@ -103,22 +90,17 @@ export type RuleResult = { apiArgs?: any[] } -export type RuleDetails = RuleResult & { +export type Issue = RuleResult & { ruleId: string, node: Node, // namespace: string, - category?: eRuleCategory, + category?: eGuidelineCategory, path: { [ns: string] : string }, ruleTime: number, message: string, - bounds?: { - top: number, - left: number, - width: number, - height: number - }, + bounds?: Bounds, snippet: string } @@ -137,7 +119,10 @@ export type Rule = { id: string | string[] num: string | string[] level: eRulePolicy, - toolkitLevel: eToolkitLevel + toolkitLevel: eToolkitLevelNew, + // (optional) Reason codes that this ruleset mapping applies to, + // or all if not specified + reasonCodes?: string[] }> refactor?: { @@ -181,51 +166,42 @@ export type Rule = { enabled?: boolean } - -export type Report = { - results: RuleDetails[], - numExecuted: number, - ruleTime: number, - // This may be undefined for a filtered report - totalTime?: number, - nls?: { - [ruleId: string]: { - [reasonId: string]: string - } - } -} - -export type NlsMap = { - [key: string]: string[] -} - -export type HelpMap = { - [key: string]: string[] -} - -export interface IEngine { - /** - * Perform a scan on a document or subtree - * @param rulesetIds Array of ruleset ids of rulesets to use for this scan - * @param root Document or subtree to scan - */ - run(root: Document | Node, options?: {}) : Promise; - - enableRules(ruleIds: string[]); - - getRule(ruleId: string): Rule; - - getRulesIds() : string[]; - - getMessage(ruleId: string, ruleIdx: number | string, msgArgs?: string[]): string; - - getHelp(ruleId: string, ruleIdx: number | string): string; - - addRules(rule: Rule[]); - - addRule(rule: Rule); - - addNlsMap(map: NlsMap); - - addHelpMap(map: NlsMap); -} \ No newline at end of file +/** + * @deprecated + */ +export type RuleDetails = Issue; + +/** + * @deprecated See IEngine + */ +export type Report = ReportNew; + +/** + * @deprecated See IEngine + */ +export type NlsMap = NlsMapNew; + +/** + * @deprecated See IEngine + */ +export type HelpMap = HelpMapNew; + +/** + * @deprecated See ./IEngine + */ +export type IEngine = IEngineNew; + +/** + * @deprecated See ./IGuideline + */ +export { eToolkitLevel } from "./IGuideline"; + +/** + * @deprecated See ./IGuideline:eGuidelineCategory + */ +export { eGuidelineCategory as eRuleCategory } from "./IGuideline"; + +/** + * @deprecated See ./IGuideline:eGuidelineType + */ +export { eGuidelineType as eRulesetType } from "./IGuideline"; diff --git a/accessibility-checker-engine/src/v4/checker/Checker.ts b/accessibility-checker-engine/src/v4/checker/Checker.ts index db034dc08..1911d814d 100644 --- a/accessibility-checker-engine/src/v4/checker/Checker.ts +++ b/accessibility-checker-engine/src/v4/checker/Checker.ts @@ -14,13 +14,16 @@ limitations under the License. *****************************************************************************/ -import { IEngine, eRulePolicy, Report, eRuleCategory, eToolkitLevel, eRulesetType, Rule as RuleV2 } from "../../v2/api/IEngine"; -import { Rule as RuleV4 } from "../api/IRule"; +import { Issue, Rule as RuleV4, eRulePolicy } from "../api/IRule"; import { Engine } from "../../v2/common/Engine"; import { ARIAMapper } from "../../v2/aria/ARIAMapper"; import { StyleMapper } from "../../v2/style/StyleMapper"; import { a11yRulesets } from "../rulesets"; import * as checkRulesV4 from "../rules"; +import { Checkpoint, Guideline, eGuidelineCategory } from "../api/IGuideline"; +import { IEngine } from "../api/IEngine"; +import { Report } from "../api/IReport"; +import { IChecker } from "../api/IChecker"; let checkRules = []; let checkNls = {}; @@ -53,7 +56,7 @@ function _initialize() { } // Convert RS for (const rsSection of v4Rule.rulesets) { - for (const rs of a11yRulesets as Ruleset[]) { + for (const rs of a11yRulesets as Guideline[]) { let checkRsIds : string[] = typeof rsSection.id === "string" ? [rsSection.id] : rsSection.id; if (checkRsIds.includes(rs.id)) { for (const cp of rs.checkpoints) { @@ -62,6 +65,7 @@ function _initialize() { cp.rules = cp.rules || [] cp.rules.push({ id: v4Rule.id, + reasonCodes: rsSection.reasonCodes, level: rsSection.level, toolkitLevel: rsSection.toolkitLevel }) @@ -74,31 +78,18 @@ function _initialize() { } _initialize(); -export type Ruleset = { - id: string, - name: string, - category: eRuleCategory, - description: string, - type?: eRulesetType, - checkpoints: Array<{ - num: string, - // See https://github.com/act-rules/act-tools/blob/main/src/data/sc-urls.json - scId?: string, - // JCH: add name of checkpoint and summary description - name: string, - wcagLevel: string, - summary: string, - rules?: Array<{ id: string, level: eRulePolicy, toolkitLevel: eToolkitLevel }> - }> -} +/** + * @deprecated See ../api/IGuideline + */ +export type Ruleset = Guideline; -export class Checker { +export class Checker implements IChecker { engine: IEngine; - rulesets: Ruleset[] = []; + rulesets: Guideline[] = []; rulesetIds: string[] = []; rulesetRules: { [rsId: string]: string[] } = {}; ruleLevels : { [ruleId: string]: { [rsId: string] : eRulePolicy }} = {}; - ruleCategory : { [ruleId: string]: { [rsId: string] : eRuleCategory }} = {}; + ruleCategory : { [ruleId: string]: { [rsId: string] : eGuidelineCategory }} = {}; constructor() { let engine = this.engine = new Engine(); @@ -115,21 +106,37 @@ export class Checker { } } - addRuleset(rs: Ruleset) { - this.rulesets.push(rs); - this.rulesetIds.push(rs.id); + addGuideline(guideline: Guideline) { + this.rulesets.push(guideline); + this.rulesetIds.push(guideline.id); const ruleIds = []; - for (const cp of rs.checkpoints) { + for (const cp of guideline.checkpoints) { cp.rules = cp.rules || []; for (const rule of cp.rules) { ruleIds.push(rule.id); this.ruleLevels[rule.id] = this.ruleLevels[rule.id] || {}; - this.ruleLevels[rule.id][rs.id] = rule.level; + this.ruleLevels[rule.id][guideline.id] = rule.level; this.ruleCategory[rule.id] = this.ruleCategory[rule.id] || {}; - this.ruleCategory[rule.id][rs.id] = rs.category; + this.ruleCategory[rule.id][guideline.id] = guideline.category; } } - this.rulesetRules[rs.id] = ruleIds; + this.rulesetRules[guideline.id] = ruleIds; + } + + getGuidelines() { + return this.rulesets; + } + + getGuidelineIds() { + return this.rulesetIds; + } + + /** + * + * @deprecated See addGuideline + */ + addRuleset(rs: Ruleset) { + this.addGuideline(rs); } check(node: Node | Document, rsIds?: string | string[]) : Promise { @@ -176,7 +183,7 @@ export class Checker { }); } - getLevel(rsIds: string[], ruleId: string) : eRulePolicy { + private getLevel(rsIds: string[], ruleId: string) : eRulePolicy { if (!rsIds) return eRulePolicy.INFORMATION; let rsInfo = this.ruleLevels[ruleId]; let retVal = null; @@ -202,12 +209,12 @@ export class Checker { return retVal; } - getCategory(rsIds: string[], ruleId: string) : eRuleCategory { + private getCategory(rsIds: string[], ruleId?: string) : eGuidelineCategory { let rsInfo = this.ruleCategory[ruleId]; let retVal = ""; if (!(ruleId in this.ruleCategory)) { - return eRuleCategory.OTHER; + return eGuidelineCategory.OTHER; } if (!rsIds) { rsIds = this.rulesetIds; @@ -217,6 +224,6 @@ export class Checker { return rsInfo[rsId]; } } - return eRuleCategory.OTHER; + return eGuidelineCategory.OTHER; } } diff --git a/accessibility-checker-engine/src/v4/rulesets.ts b/accessibility-checker-engine/src/v4/rulesets.ts index 6eb8b6ecc..341239e67 100644 --- a/accessibility-checker-engine/src/v4/rulesets.ts +++ b/accessibility-checker-engine/src/v4/rulesets.ts @@ -14,10 +14,9 @@ limitations under the License. *****************************************************************************/ -import { Ruleset } from "./checker/Checker"; -import { eRuleCategory, eRulesetType } from "../v2/api/IEngine"; // This file comes from https://raw.githubusercontent.com/act-rules/act-tools/main/src/data/sc-urls.json import * as SCURLs from "./sc-urls.json" +import { Guideline, eGuidelineCategory, eGuidelineType } from "./api/IGuideline"; const SCs = []; for (const key in SCURLs) { SCs.push(SCURLs[key]); @@ -76,7 +75,7 @@ const summaries = { "4.1.3": "In content implemented using markup languages, status messages can be programmatically determined through role or properties such that they can be presented to the user by assistive technologies without receiving focus.", } -export let a11yRulesets: Ruleset[] = [ +export let a11yRulesets: Guideline[] = [ // { // id: "DEBUG", // name: "DEBUG Rules", @@ -92,9 +91,9 @@ export let a11yRulesets: Ruleset[] = [ { id: "EXTENSIONS", name: "Extension Rules", - category: eRuleCategory.ACCESSIBILITY, + category: eGuidelineCategory.ACCESSIBILITY, description: "Rules for enabling the browser extensions", - type: eRulesetType.EXTENSION, + type: eGuidelineType.EXTENSION, checkpoints: [{ num: "1", name: "Extension CP 1", @@ -105,7 +104,7 @@ export let a11yRulesets: Ruleset[] = [ { id: "IBM_Accessibility", name: "IBM Accessibility", - category: eRuleCategory.ACCESSIBILITY, + category: eGuidelineCategory.ACCESSIBILITY, description: "Rules for WCAG 2.1 AA plus additional IBM checklist supplemental requirements.", // This ruleset has all 2.0 and 2.1 checkpoints that are A or AA checkpoints: SCs @@ -121,7 +120,7 @@ export let a11yRulesets: Ruleset[] = [ { id: "WCAG_2_1", name: "WCAG 2.1 (A, AA)", - category: eRuleCategory.ACCESSIBILITY, + category: eGuidelineCategory.ACCESSIBILITY, description: "Rules for WCAG 2.1 AA. This is the current W3C recommendation. Content that conforms to WCAG 2.1 also conforms to WCAG 2.0.", // This ruleset has all 2.0 and 2.1 checkpoints that are A or AA checkpoints: SCs @@ -137,7 +136,7 @@ export let a11yRulesets: Ruleset[] = [ { id: "WCAG_2_0", name: "WCAG 2.0 (A, AA)", - category: eRuleCategory.ACCESSIBILITY, + category: eGuidelineCategory.ACCESSIBILITY, description: "Rules for WCAG 2.0 AA. Referenced by US Section 508, but not the latest W3C recommendation.", // This ruleset has all 2.0 checkpoints that are A or AA checkpoints: SCs diff --git a/accessibility-checker-extension/src/ts/devtools/components/reports/reportReqts.tsx b/accessibility-checker-extension/src/ts/devtools/components/reports/reportReqts.tsx index 853a33801..ed56bee74 100644 --- a/accessibility-checker-extension/src/ts/devtools/components/reports/reportReqts.tsx +++ b/accessibility-checker-extension/src/ts/devtools/components/reports/reportReqts.tsx @@ -71,7 +71,10 @@ export class ReportReqts extends React.Component { children: [] }; for (const result of this.props.issues) { - if (checkpoint.rules?.find(rule => rule.id === result.ruleId)) { + if (checkpoint.rules?.find(rule => ( + rule.id === result.ruleId + && (!rule.reasonCodes || rule.reasonCodes.includes(result.reasonId)) + ))) { curGroup.children.push(result); } } diff --git a/accessibility-checker-extension/src/ts/devtools/devtoolsController.ts b/accessibility-checker-extension/src/ts/devtools/devtoolsController.ts index 0c3b7a19d..6dc264f1f 100644 --- a/accessibility-checker-extension/src/ts/devtools/devtoolsController.ts +++ b/accessibility-checker-extension/src/ts/devtools/devtoolsController.ts @@ -558,6 +558,7 @@ export class DevtoolsController extends Controller { for (const result of devtoolsState?.lastReport.results) { reportObj.report.results.push({ ruleId: result.ruleId, + reasonId: result.reasonId, path: result.path, value: result.value, message: result.message, diff --git a/accessibility-checker-extension/src/ts/interfaces/interfaces.ts b/accessibility-checker-extension/src/ts/interfaces/interfaces.ts index 74882b85d..6c6aa38fb 100644 --- a/accessibility-checker-extension/src/ts/interfaces/interfaces.ts +++ b/accessibility-checker-extension/src/ts/interfaces/interfaces.ts @@ -204,7 +204,12 @@ export interface IRuleset { name: string, wcagLevel: string, summary: string, - rules?: Array<{ id: string, level: eRulePolicy, toolkitLevel: eToolkitLevel }> + rules?: Array<{ + id: string, + level: eRulePolicy, + toolkitLevel: eToolkitLevel, + reasonCodes?: string[] + }> }> } diff --git a/accessibility-checker-extension/src/ts/util/xlsxReport/multiScanReport/xlsx/MultiScanData.tsx b/accessibility-checker-extension/src/ts/util/xlsxReport/multiScanReport/xlsx/MultiScanData.tsx index 21fcaa1b9..68ed03ffd 100644 --- a/accessibility-checker-extension/src/ts/util/xlsxReport/multiScanReport/xlsx/MultiScanData.tsx +++ b/accessibility-checker-extension/src/ts/util/xlsxReport/multiScanReport/xlsx/MultiScanData.tsx @@ -88,7 +88,7 @@ export default class MultiScanData { stringHash(item.ruleId + item.path.dom), valueMap[item.value[0]][item.value[1]], parseInt(rule_map.get(item.ruleId).toolkitLevel), // make number instead of text for spreadsheet - this.checkpoints_string(rule_checkpoints_map, item.ruleId), + this.checkpoints_string(rule_checkpoints_map, item.ruleId, item.reasonId), this.wcag_string(rule_checkpoints_map, item.ruleId), item.ruleId, item.message.substring(0, 32767), //max ength for MS Excel 32767 characters @@ -104,13 +104,17 @@ export default class MultiScanData { return ret; } - public static checkpoints_string(rule_checkpoints_map: any, rule_id: string) { + public static checkpoints_string(rule_checkpoints_map: any, rule_id: string, reasonId: string) { let checkpoint_string = ''; let checkpoint_array = rule_checkpoints_map.get(rule_id); for (let checkpoint of checkpoint_array) { + console.log(checkpoint); + let ruleInfo = checkpoint.rules.find((rule: any) => rule.id === rule_id); + console.log(ruleInfo); + if (ruleInfo.reasonCodes && !ruleInfo.reasonCodes.includes(reasonId)) continue; if (checkpoint_string.length > 1) { checkpoint_string = checkpoint_string + '; '; } @@ -175,14 +179,6 @@ export default class MultiScanData { let checkpoint_map = new Map(); for (let checkpoint of checkpoints) { - - let temp_checkpoint = { - name: checkpoint.name, - num: checkpoint.num, - summary: checkpoint.summary, - wcagLevel: checkpoint.wcagLevel - } - let rules = checkpoint.rules; if (rules && rules.length > 0) { @@ -190,10 +186,10 @@ export default class MultiScanData { if (!checkpoint_map.get(rule.id)) { - checkpoint_map.set(rule.id, [temp_checkpoint]); + checkpoint_map.set(rule.id, [checkpoint]); } else { let checkpoint_array = checkpoint_map.get(rule.id); - checkpoint_array.push(temp_checkpoint); + checkpoint_array.push(checkpoint); checkpoint_map.set(rule.id, checkpoint_array); } } diff --git a/accessibility-checker/src-ts/index.ts b/accessibility-checker/src-ts/index.ts index b2daaf6b1..996e94046 100644 --- a/accessibility-checker/src-ts/index.ts +++ b/accessibility-checker/src-ts/index.ts @@ -20,7 +20,9 @@ import { getComplianceHelper } from "./lib/ACHelper"; import { eAssertResult, ICheckerReport, ICheckerResult, ReportResult } from "./lib/api/IChecker"; import { ACConfigManager } from "./lib/common/config/ACConfigManager"; import { IConfig, IConfigInternal } from "./lib/common/config/IConfig"; +import { Checkpoint } from "./lib/common/engine/IGuideline"; import { IBaselineReport } from "./lib/common/engine/IReport"; +import { Issue } from "./lib/common/engine/IRule"; import { BaselineManager } from "./lib/common/report/BaselineManager"; import { ReporterManager } from "./lib/common/report/ReporterManager"; diff --git a/accessibility-checker/src-ts/lib/ACEngineManager.ts b/accessibility-checker/src-ts/lib/ACEngineManager.ts index ac030056d..250007207 100644 --- a/accessibility-checker/src-ts/lib/ACEngineManager.ts +++ b/accessibility-checker/src-ts/lib/ACEngineManager.ts @@ -2,10 +2,11 @@ import * as path from "path"; import * as fs from "fs"; import { ACConfigManager } from "./common/config/ACConfigManager"; import { fetch_get_text } from "./common/api-ext/Fetch"; +import { IChecker } from "./common/engine/IChecker"; let ace; -let checker; +let checker: IChecker; export class ACEngineManager { static customRulesets = [] @@ -311,27 +312,34 @@ export class ACEngineManager { if (!checker) { await ACEngineManager.loadEngineLocal(); } - return ACEngineManager.customRulesets.concat(checker.rulesets).filter((function (rs) { return rs.id === rsId }))[0]; + return ACEngineManager.customRulesets.concat(checker.getGuidelines()).filter((function (rs) { return rs.id === rsId }))[0]; }; static getRulesets = async function () { if (!checker) { await ACEngineManager.loadEngineLocal(); } - return ACEngineManager.customRulesets.concat(checker.rulesets); + return ACEngineManager.customRulesets.concat(checker.getGuidelines()); }; static getChecker() { return checker; } + static async loadChecker() { + if (!checker) { + await ACEngineManager.loadEngineLocal(); + } + return checker; + } + static getRules = async function() { if (!checker) { await ACEngineManager.loadEngineLocal(); } let retVal = []; - for (const ruleId in checker.engine.ruleMap) { - retVal.push(checker.engine.ruleMap[ruleId]); + for (const ruleId in (checker as any).engine.ruleMap) { + retVal.push((checker as any).engine.ruleMap[ruleId]); } return retVal; } @@ -339,8 +347,8 @@ export class ACEngineManager { static getRulesSync = function() { if (!checker) return null; let retVal = []; - for (const ruleId in checker.engine.ruleMap) { - retVal.push(checker.engine.ruleMap[ruleId]); + for (const ruleId in (checker as any).engine.ruleMap) { + retVal.push((checker as any).engine.ruleMap[ruleId]); } return retVal; } diff --git a/accessibility-checker/src-ts/lib/ACHelper.ts b/accessibility-checker/src-ts/lib/ACHelper.ts index a4b8ec406..ed57977be 100644 --- a/accessibility-checker/src-ts/lib/ACHelper.ts +++ b/accessibility-checker/src-ts/lib/ACHelper.ts @@ -401,7 +401,7 @@ async function getComplianceHelperLocal(label, parsed, curPol) : Promise checker.addRuleset(rs)); + ACEngineManager.customRulesets.forEach((rs) => checker.addGuideline(rs)); let report : IEngineReport = await checker.check(parsed, Config.policies) .then(function (report) { for (const result of report.results) { @@ -411,7 +411,7 @@ async function getComplianceHelperLocal(label, parsed, curPol) : Promise cp.rules && cp.rules.filter(rule => rule.id === ruleId).length > 0) + .filter(cp => ( + cp.rules + && cp.rules.filter(rule => ( + // Checkpoint refers to a rule mapping that has the ruleid + rule.id === ruleId + // and either maps to all reasons, or one the these reasons is selected + && (!rule.reasonCodes || rule.reasonCodes.filter(code => aceRule.reasonIds.includes(code))) + ).length > 0) // Replace with the scId - .map(cp => cp.scId) + )).map(cp => cp.scId) }, "result": { "outcome": result } } diff --git a/accessibility-checker-engine/src/v2/simulator/nls/index.ts b/common/module/src/engine/IBounds.ts similarity index 72% rename from accessibility-checker-engine/src/v2/simulator/nls/index.ts rename to common/module/src/engine/IBounds.ts index a17029783..224d65b34 100644 --- a/accessibility-checker-engine/src/v2/simulator/nls/index.ts +++ b/common/module/src/engine/IBounds.ts @@ -14,15 +14,9 @@ limitations under the License. *****************************************************************************/ -let simNls = { - "LinkRule": ["[link]"], - "TextRule": ["{0}"], - "ImgRule": ["{0}"], - "Button": ["{0}"], - "ButtonEnd": ["[button]"], - "Headings": ["[Heading level {0}]"], - "Textbox": ["{0} {1}[edit]"], - "EndBlock": ["\n"], - "EndLink": ["\n"] +export type Bounds = { + left: number, + top: number, + width: number, + height: number } -export { simNls } \ No newline at end of file diff --git a/common/module/src/engine/IChecker.ts b/common/module/src/engine/IChecker.ts new file mode 100644 index 000000000..bc0ea3617 --- /dev/null +++ b/common/module/src/engine/IChecker.ts @@ -0,0 +1,29 @@ +/****************************************************************************** + Copyright:: 2020- IBM, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *****************************************************************************/ + +import { Checkpoint, Guideline } from "./IGuideline"; +import { IEngineReport } from "./IReport"; +import { Issue } from "./IRule"; + +export interface IChecker { + addGuideline(guideline: Guideline); + + getGuidelines() : Guideline[] + + getGuidelineIds() : string[] + + check(node: Node | Document, guidelineIds?: string | string[]) : Promise; +} \ No newline at end of file diff --git a/common/module/src/engine/IEngine.ts b/common/module/src/engine/IEngine.ts new file mode 100644 index 000000000..b98f2c8d5 --- /dev/null +++ b/common/module/src/engine/IEngine.ts @@ -0,0 +1,52 @@ +/****************************************************************************** + Copyright:: 2020- IBM, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *****************************************************************************/ + +import { Rule } from "./IRule"; + +export interface IEngine { + /** + * Perform a scan on a document or subtree + * @param rulesetIds Array of ruleset ids of rulesets to use for this scan + * @param root Document or subtree to scan + */ + run(root: Document | Node, options?: {}) : Promise; + + enableRules(ruleIds: string[]); + + getRule(ruleId: string): Rule; + + getRulesIds() : string[]; + + getMessage(ruleId: string, ruleIdx: number | string, msgArgs?: string[]): string; + + getHelp(ruleId: string, ruleIdx: number | string): string; + + addRules(rule: Rule[]); + + addRule(rule: Rule); + + addNlsMap(map: NlsMap); + + addHelpMap(map: NlsMap); +} + +export type NlsMap = { + [key: string]: string[] +} + +export type HelpMap = { + [key: string]: string[] +} \ No newline at end of file diff --git a/common/module/src/engine/IGuideline.ts b/common/module/src/engine/IGuideline.ts new file mode 100644 index 000000000..9fe96ee7d --- /dev/null +++ b/common/module/src/engine/IGuideline.ts @@ -0,0 +1,62 @@ +/****************************************************************************** + Copyright:: 2020- IBM, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *****************************************************************************/ + +import { eRulePolicy } from "./IRule" + +export enum eToolkitLevel { + LEVEL_ONE = "1", + LEVEL_TWO = "2", + LEVEL_THREE = "3", + LEVEL_FOUR = "4" +} + +export enum eGuidelineCategory { + ACCESSIBILITY = "Accessibility", + DESIGN = "Design", + OTHER = "Other" +} + +export enum eGuidelineType { + DEFAULT = "default", + EXTENSION = "extension" +} + +export type Checkpoint = { + num: string, + // See https://github.com/act-rules/act-tools/blob/main/src/data/sc-urls.json + scId?: string, + // JCH: add name of checkpoint and summary description + name: string, + wcagLevel: string, + summary: string, + rules?: Array<{ + id: string, + // (optional) Reason codes that this ruleset mapping applies to, + // or all if not specified + reasonCodes?: string[], + level: eRulePolicy, + toolkitLevel: eToolkitLevel + }> +} + +export type Guideline = { + id: string, + name: string, + category: eGuidelineCategory, + description: string, + type?: eGuidelineType, + checkpoints: Array +} diff --git a/common/module/src/engine/IMapper.ts b/common/module/src/engine/IMapper.ts new file mode 100644 index 000000000..36c0e95fb --- /dev/null +++ b/common/module/src/engine/IMapper.ts @@ -0,0 +1,35 @@ +/****************************************************************************** + Copyright:: 2020- IBM, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *****************************************************************************/ + +import { Bounds } from "./IBounds"; + +export type IMapResult = { + node: Node, + namespace: string, + role: string, + rolePath: string, + attributes: { + [key: string]: string + }, + bounds?: Bounds +} + +export interface IMapper { + reset(node: Node): void; + openScope(node: Node) : IMapResult[]; + closeScope(node: Node) : IMapResult[]; + getNamespace() : string; +} \ No newline at end of file diff --git a/common/module/src/engine/IReport.ts b/common/module/src/engine/IReport.ts index 473e73571..5c42187e4 100644 --- a/common/module/src/engine/IReport.ts +++ b/common/module/src/engine/IReport.ts @@ -15,66 +15,47 @@ *****************************************************************************/ import { eRuleLevel } from "../config/IConfig" - - -export enum eRuleConfidence { - PASS = "PASS", - FAIL = "FAIL", - POTENTIAL = "POTENTIAL", - MANUAL = "MANUAL" -} - -export enum eRulePolicy { - VIOLATION = "VIOLATION", - RECOMMENDATION = "RECOMMENDATION", - INFORMATION = "INFORMATION" -} - -export enum eToolkitLevel { - LEVEL_ONE = "1", - LEVEL_TWO = "2", - LEVEL_THREE = "3", - LEVEL_FOUR = "4" -} - -export enum eRuleCategory { - ACCESSIBILITY = "Accessibility", - DESIGN = "Design", - OTHER = "Other" -} - -export enum eRulesetType { - DEFAULT = "default", - EXTENSION = "extension" -} - -export interface Bounds { - left: number - top: number - width: number - height: number -} - -export interface IRuleset { - id: string, - name: string, - category: eRuleCategory, - description: string, - type?: eRulesetType, - checkpoints: Array<{ - num: string, - // See https://github.com/act-rules/act-tools/blob/main/src/data/sc-urls.json - scId?: string, - // JCH: add name of checkpoint and summary description - name: string, - wcagLevel: string, - summary: string, - rules?: Array<{ id: string, level: eRulePolicy, toolkitLevel: eToolkitLevel }> - }> -} - -export interface IEngineReport { - results: IEngineResult[], +import { Guideline, eGuidelineCategory } from "./IGuideline"; +import { Issue, eRulePolicy as eRulePolicyNew, eRuleConfidence as eRuleConfidenceNew } from "./IRule" +import { Bounds as BoundsNew } from "./IBounds"; + +/** + * @deprecated See ./IRule + */ +export { eRuleConfidence } from "./IRule"; + +/** + * @deprecated See ./IRule + */ +export { eRulePolicy } from "./IRule"; + +/** + * @deprecated See ./IGuideline + */ +export { eToolkitLevel } from "./IGuideline"; + +/** + * @deprecated See ./IGuideline::eGuidelineCategory + */ +export { eGuidelineCategory as eRuleCategory } from "./IGuideline"; + +/** + * @deprecated See ./IGuideline::eGuidelineType + */ +export { eGuidelineType as eRuleType } from "./IGuideline"; + +/** + * @deprecated See ./IBounds + */ +export type Bounds = BoundsNew; + +/** + * @deprecated See ./IGuidline + */ +export type IRuleset = Guideline; + +export type IEngineReport = { + results: Issue[], numExecuted: number, ruleTime: number, // This may be undefined for a filtered report @@ -87,29 +68,15 @@ export interface IEngineReport { } } -export interface IEngineResult { - category?: eRuleCategory, - ruleId: string, - value: [eRulePolicy, eRuleConfidence], - reasonId?: number | string, - messageArgs?: string[], - apiArgs?: any[] - node?: Node, - path: { [ns: string] : string }, - ruleTime: number, - message: string, - bounds?: Bounds, - snippet: string, - help?: string -} +export type IEngineResult = Issue; -export interface IBaselineResult extends IEngineResult { +export type IBaselineResult = IEngineResult & { ignored: boolean help: string level: eRuleLevel } -export interface IBaselineReport { +export type IBaselineReport = { results: IBaselineResult[] numExecuted: number, nls: { @@ -157,9 +124,9 @@ export type CompressedReport = [ ] export type CompressedIssue = [ // results - eRuleCategory | undefined, //category? + eGuidelineCategory | undefined, //category? string, // ruleId - [eRulePolicy, eRuleConfidence], // value + [eRulePolicyNew, eRuleConfidenceNew], // value number | string | undefined, // reasonId string[], // messageArgs { [ns: string] : string }, // path diff --git a/common/module/src/engine/IRule.ts b/common/module/src/engine/IRule.ts new file mode 100644 index 000000000..f10a64d8c --- /dev/null +++ b/common/module/src/engine/IRule.ts @@ -0,0 +1,165 @@ +/****************************************************************************** + Copyright:: 2020- IBM, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *****************************************************************************/ + +import { eGuidelineCategory } from "./IGuideline"; +import { IMapResult } from "./IMapper"; +import { eToolkitLevel as eToolkitLevelNew } from "./IGuideline" +import { Bounds } from "./IBounds"; + +export enum eRuleConfidence { + PASS = "PASS", + FAIL = "FAIL", + POTENTIAL = "POTENTIAL", + MANUAL = "MANUAL" +} + +export enum eRulePolicy { + VIOLATION = "VIOLATION", + RECOMMENDATION = "RECOMMENDATION", + INFORMATION = "INFORMATION" +} + +export function RulePass(reasonId: number | string, messageArgs? : string[], apiArgs? : any[]) : RuleResult { + if (typeof reasonId === "undefined" || reasonId === null) throw new Error("Reason ID must be defined"); + return { + value: [eRulePolicy.INFORMATION, eRuleConfidence.PASS], + reasonId: reasonId, + messageArgs: messageArgs || [], + apiArgs: apiArgs || [] + } +} + +export function RuleRender(reasonId: number | string, messageArgs? : string[], apiArgs? : any[]) : RuleResult { + if (typeof reasonId === "undefined" || reasonId === null) throw new Error("Reason ID must be defined"); + return { + value: [eRulePolicy.INFORMATION, eRuleConfidence.PASS], + reasonId: 0, + messageArgs: messageArgs || [], + apiArgs: apiArgs || [] + } +} +export function RuleFail(reasonId: number | string, messageArgs? : string[], apiArgs? : any[]) : RuleResult { + if (typeof reasonId === "undefined" || reasonId === null) throw new Error("Reason ID must be defined"); + return { + value: [eRulePolicy.INFORMATION, eRuleConfidence.FAIL], + reasonId: reasonId, + messageArgs: messageArgs || [], + apiArgs: apiArgs || [] + } +} + +export function RulePotential(reasonId: number | string, messageArgs? : string[], apiArgs? : any[]) : RuleResult { + if (typeof reasonId === "undefined" || reasonId === null) throw new Error("Reason ID must be defined"); + return { + value: [eRulePolicy.INFORMATION, eRuleConfidence.POTENTIAL], + reasonId: reasonId, + messageArgs: messageArgs || [], + apiArgs: apiArgs || [] + } +} + +export function RuleManual(reasonId: number | string, messageArgs? : string[], apiArgs? : any[]) : RuleResult { + if (typeof reasonId === "undefined" || reasonId === null) throw new Error("Reason ID must be defined"); + return { + value: [eRulePolicy.INFORMATION, eRuleConfidence.MANUAL], + reasonId: reasonId, + messageArgs: messageArgs || [], + apiArgs: apiArgs || [] + } +} + +export type RuleResult = { + value: [eRulePolicy, eRuleConfidence], + reasonId?: number | string, + messageArgs?: string[], + apiArgs?: any[] +} + +export type Issue = RuleResult & { + ruleId: string, + + node: Node, + // namespace: string, + category?: eGuidelineCategory, + path: { [ns: string] : string }, + + ruleTime: number, + message: string, + bounds?: Bounds, + snippet: string +} + +export type RuleContextHierarchy = { [namespace: string] : IMapResult[] }; + +export type RuleContext = { + [namespace: string] : IMapResult +} + +export type Rule = { + // Unique string identifier for this rule (should be human understandable) + // NLS codes and help sources will be based off of this id + id: string; + + rulesets: Array<{ + id: string | string[] + num: string | string[] + level: eRulePolicy, + toolkitLevel: eToolkitLevelNew, + // (optional) Reason codes that this ruleset mapping applies to, + // or all if not specified + reasonCodes?: string[] + }> + + refactor?: { + [oldRuleId: string]: { + [oldReasonCode: string]: string + } + }; + + messages: { + [locale: string]: { + [reasonId: string]: string + } + }; + + help: { + [locale: string]: { + [reasonId: string]: string + } + }; + + /** + * How this rule maps to ACT rules, if any (https://act-rules.github.io/rules/) + * + * string: For a single rule mapping that matches exactly to the rule (Pass -> pass, Potential -> cantTell, Fail -> fail, unlisted => inapplicable) + * Array<>: Custom mapping of rule to ACT results + */ + act?: string | string[] | Array + + // See src/v2/common/Context.ts for valid contexts + context: string; + + // Array of rules that must pass to allow this validate to run - they must have the same context property + dependencies?: string[] + + run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy) => RuleResult | RuleResult[] | null + + enabled?: boolean +} \ No newline at end of file diff --git a/common/module/src/engine/IRuleset.ts b/common/module/src/engine/IRuleset.ts index b04320436..aa8f1a9ca 100644 --- a/common/module/src/engine/IRuleset.ts +++ b/common/module/src/engine/IRuleset.ts @@ -14,22 +14,9 @@ limitations under the License. *****************************************************************************/ -import { eRuleCategory, eRulePolicy, eRulesetType, eToolkitLevel } from "./IReport" +import { Guideline } from "./IGuideline" -export interface IRuleset { - id: string, - name: string, - category: eRuleCategory, - description: string, - type?: eRulesetType, - checkpoints: Array<{ - num: string, - // See https://github.com/act-rules/act-tools/blob/main/src/data/sc-urls.json - scId?: string, - // JCH: add name of checkpoint and summary description - name: string, - wcagLevel: string, - summary: string, - rules?: Array<{ id: string, level: eRulePolicy, toolkitLevel: eToolkitLevel }> - }> -} +/** + * @deprecated + */ +export type IRuleset = Guideline; \ No newline at end of file diff --git a/common/module/src/report/ACReporterCSV.ts b/common/module/src/report/ACReporterCSV.ts index ab43fa157..329d9a779 100644 --- a/common/module/src/report/ACReporterCSV.ts +++ b/common/module/src/report/ACReporterCSV.ts @@ -15,7 +15,8 @@ *****************************************************************************/ import { IConfigInternal } from "../config/IConfig"; -import { CompressedReport, IRuleset } from "../engine/IReport"; +import { Guideline } from "../engine/IGuideline"; +import { CompressedReport } from "../engine/IReport"; import { GenSummReturn, IReporter, ReporterManager } from "./ReporterManager"; export class ACReporterCSV implements IReporter { @@ -35,7 +36,7 @@ export class ACReporterCSV implements IReporter { public generateReport(_reportData) : { reportPath: string, report: string } | void { } - public async generateSummary(config: IConfigInternal, _rulesets: IRuleset[], endReport: number, compressedReports: CompressedReport[]): Promise { + public async generateSummary(config: IConfigInternal, _rulesets: Guideline[], endReport: number, compressedReports: CompressedReport[]): Promise { let toCSV = ACReporterCSV.toCSV; let resultStr = `Label,Level,RuleId,Message,Xpath,Help\n`; let startScan = 0; diff --git a/common/module/src/report/ACReporterHTML.ts b/common/module/src/report/ACReporterHTML.ts index f68fca2da..7c6fa3b27 100644 --- a/common/module/src/report/ACReporterHTML.ts +++ b/common/module/src/report/ACReporterHTML.ts @@ -15,7 +15,8 @@ *****************************************************************************/ import { IConfigInternal } from "../config/IConfig"; -import { CompressedReport, IBaselineReport, IRuleset } from "../engine/IReport"; +import { Guideline } from "../engine/IGuideline"; +import { CompressedReport, IBaselineReport } from "../engine/IReport"; import { GenSummReturn, IReporter, IReporterStored, ReporterManager } from "./ReporterManager"; import { genReport } from "./genReport"; @@ -24,7 +25,7 @@ export class ACReporterHTML implements IReporter { return "html"; } - public generateReport(config: IConfigInternal, rulesets: IRuleset[], storedReport: IReporterStored): { reportPath: string, report: string } | void { + public generateReport(config: IConfigInternal, rulesets: Guideline[], storedReport: IReporterStored): { reportPath: string, report: string } | void { let cloneReport : IBaselineReport= JSON.parse(JSON.stringify(storedReport.engineReport)); let outReport = { @@ -48,6 +49,6 @@ export class ACReporterHTML implements IReporter { report: genReport(outReport) }; } - public async generateSummary(_config: IConfigInternal, rulesets: IRuleset[], _endReport: number, _summaryData: CompressedReport[]): Promise { + public async generateSummary(_config: IConfigInternal, rulesets: Guideline[], _endReport: number, _summaryData: CompressedReport[]): Promise { } } diff --git a/common/module/src/report/ACReporterJSON.ts b/common/module/src/report/ACReporterJSON.ts index 3d0f117e3..1fc78acc7 100644 --- a/common/module/src/report/ACReporterJSON.ts +++ b/common/module/src/report/ACReporterJSON.ts @@ -15,14 +15,15 @@ *****************************************************************************/ import { IConfigInternal } from "../config/IConfig"; -import { CompressedReport, IBaselineReport, IEngineReport, IRuleset } from "../engine/IReport"; +import { Guideline } from "../engine/IGuideline"; +import { CompressedReport, IBaselineReport, IEngineReport } from "../engine/IReport"; import { GenSummReturn, IReporter, IReporterStored, ReporterManager } from "./ReporterManager"; export class ACReporterJSON implements IReporter { public name(): string { return "json"; } - public generateReport(config: IConfigInternal, rulesets: IRuleset[], storedReport: IReporterStored): { reportPath: string, report: string } | void { + public generateReport(config: IConfigInternal, rulesets: Guideline[], storedReport: IReporterStored): { reportPath: string, report: string } | void { let outReport : IBaselineReport= JSON.parse(JSON.stringify(storedReport.engineReport)); outReport.summary = ACReporterJSON.generateReportSummary(config, rulesets, storedReport); delete (outReport as any).totalTime; @@ -36,7 +37,7 @@ export class ACReporterJSON implements IReporter { }; } - public async generateSummary(config: IConfigInternal, _rulesets: IRuleset[], endReport: number, compressedReports: CompressedReport[]): Promise { + public async generateSummary(config: IConfigInternal, _rulesets: Guideline[], endReport: number, compressedReports: CompressedReport[]): Promise { if (compressedReports && compressedReports.length > 0) { let storedScan = ReporterManager.uncompressReport(compressedReports[0]); let retVal = { @@ -85,7 +86,7 @@ export class ACReporterJSON implements IReporter { } } - public static generateReportSummary(config: IConfigInternal, rulesets: IRuleset[], storedReport: IReporterStored) { + public static generateReportSummary(config: IConfigInternal, rulesets: Guideline[], storedReport: IReporterStored) { let { engineReport, startScan, url } = storedReport; return { diff --git a/common/module/src/report/ACReporterMetrics.ts b/common/module/src/report/ACReporterMetrics.ts index 06d8005f8..6c6a980a6 100644 --- a/common/module/src/report/ACReporterMetrics.ts +++ b/common/module/src/report/ACReporterMetrics.ts @@ -16,7 +16,8 @@ import { fetch_get } from "../api-ext/Fetch"; import { IConfigInternal } from "../config/IConfig"; -import { CompressedReport, IRuleset } from "../engine/IReport"; +import { Guideline } from "../engine/IGuideline"; +import { CompressedReport } from "../engine/IReport"; import { GenSummReturn, IReporter, IReporterStored } from "./ReporterManager"; /******************************************************************************* @@ -72,7 +73,7 @@ export class ACReporterMetrics implements IReporter { * * @memberOf this */ - public generateReport(config: IConfigInternal, rulesets: IRuleset[], storedReport: IReporterStored): { reportPath: string, report: string } | void { + public generateReport(config: IConfigInternal, rulesets: Guideline[], storedReport: IReporterStored): { reportPath: string, report: string } | void { if (!config.label || !config.label.includes("IBMa-Node-TeSt")) { // URI encode the profile text provided let profile = encodeURIComponent(storedReport.scanProfile); @@ -93,7 +94,7 @@ export class ACReporterMetrics implements IReporter { * * @memberOf this */ - public async generateSummary(config: IConfigInternal, rulesets: IRuleset[], endReport: number, summaryData: CompressedReport[]): Promise { + public async generateSummary(config: IConfigInternal, rulesets: Guideline[], endReport: number, summaryData: CompressedReport[]): Promise { try { // Variable Decleration let numProfiles = 0; diff --git a/common/module/src/report/ACReporterXLSX.ts b/common/module/src/report/ACReporterXLSX.ts index a06d26caa..060f5b8c2 100644 --- a/common/module/src/report/ACReporterXLSX.ts +++ b/common/module/src/report/ACReporterXLSX.ts @@ -15,14 +15,15 @@ *****************************************************************************/ import { IConfigInternal, eRuleLevel } from "../config/IConfig"; -import { CompressedReport, IRuleset, eRuleConfidence, eToolkitLevel } from "../engine/IReport"; +import { Checkpoint, Guideline, eToolkitLevel } from "../engine/IGuideline"; +import { CompressedReport } from "../engine/IReport"; +import { eRuleConfidence } from "../engine/IRule"; import { GenSummReturn, IReporter, ReporterManager } from "./ReporterManager"; import * as ExcelJS from "exceljs"; type PolicyInfo = { tkLevels: eToolkitLevel[] - wcagLevels: string[] - cps: string[] + cps: Checkpoint[] }; function dropDupes(arr: T[]): T[] { @@ -43,7 +44,7 @@ export class ACReporterXLSX implements IReporter { public generateReport(_reportData): { reportPath: string, report: string } | void { } - public async generateSummary(config: IConfigInternal, rulesets: IRuleset[], endReport: number, summaryData: CompressedReport[]): Promise { + public async generateSummary(config: IConfigInternal, rulesets: Guideline[], endReport: number, summaryData: CompressedReport[]): Promise { let storedReport = ReporterManager.uncompressReport(summaryData[0]) let cfgRulesets = rulesets.filter(rs => config.policies.includes(rs.id)); let policyInfo: { @@ -54,22 +55,16 @@ export class ACReporterXLSX implements IReporter { for (const rule of cp.rules) { policyInfo[rule.id] = policyInfo[rule.id] || { tkLevels: [], - wcagLevels: [], cps: [] } policyInfo[rule.id].tkLevels.push(rule.toolkitLevel); - policyInfo[rule.id].wcagLevels.push(cp.wcagLevel); - policyInfo[rule.id].cps.push(`${cp.num}`); + policyInfo[rule.id].cps.push(cp); } } } for (const ruleId in policyInfo) { policyInfo[ruleId].tkLevels = dropDupes(policyInfo[ruleId].tkLevels); - policyInfo[ruleId].cps = dropDupes(policyInfo[ruleId].cps); - policyInfo[ruleId].wcagLevels = dropDupes(policyInfo[ruleId].wcagLevels); policyInfo[ruleId].tkLevels.sort(); - policyInfo[ruleId].cps.sort(); - policyInfo[ruleId].wcagLevels.sort(); } // const buffer: any = await workbook.xlsx.writeBuffer(); @@ -484,7 +479,6 @@ export class ACReporterXLSX implements IReporter { if (!(issue.ruleId in policyInfo)) { policyInfo[issue.ruleId] = { tkLevels: [], - wcagLevels: [], cps: [] } } @@ -700,19 +694,27 @@ export class ACReporterXLSX implements IReporter { if (!(item.ruleId in policyInfo)) { policyInfo[item.ruleId] = { tkLevels: [], - wcagLevels: [], cps: [] } } + let polInfo = policyInfo[item.ruleId]; + let cps = polInfo.cps.filter(cp => { + let ruleInfo = cp.rules.find(ruleInfo => ruleInfo.id === item.ruleId && (!ruleInfo.reasonCodes || ruleInfo.reasonCodes.includes(""+item.reasonId))); + return !!ruleInfo; + }) + let wcagLevels = dropDupes(cps.map(cp => cp.wcagLevel)); + wcagLevels.sort(); + let cpStrs = dropDupes(cps.map(cp => `${cp.num} ${cp.name}`)); + cpStrs.sort(); let row = [ storedScan.pageTitle, storedScan.engineReport.summary.URL, storedScan.label, this.stringHash(item.ruleId + item.path.dom), `${valueMap[item.value[0]][item.value[1]]}${item.ignored ? ` (Archived)` : ``}`, - policyInfo[item.ruleId].tkLevels.join(", "), - policyInfo[item.ruleId].cps.join(", "), - policyInfo[item.ruleId].wcagLevels.join(", "), + polInfo.tkLevels.join(", "), + cpStrs.join("; "), + wcagLevels.join(", "), item.ruleId, item.message.substring(0, 32767), //max ength for MS Excel 32767 characters this.get_element(item.snippet), diff --git a/common/module/src/report/ReporterManager.ts b/common/module/src/report/ReporterManager.ts index 344faaac5..605fc8a5c 100644 --- a/common/module/src/report/ReporterManager.ts +++ b/common/module/src/report/ReporterManager.ts @@ -16,12 +16,14 @@ import { IAbstractAPI } from "../api-ext/IAbstractAPI"; import { IConfigInternal, eRuleLevel } from "../config/IConfig"; -import { CompressedIssue, CompressedReport, IBaselineReport, IBaselineResult, IEngineReport, IRuleset, eRuleConfidence, eRulePolicy } from "../engine/IReport"; +import { CompressedIssue, CompressedReport, IBaselineReport, IBaselineResult, IEngineReport } from "../engine/IReport"; import { ACReporterMetrics } from "./ACReporterMetrics"; import { ACReporterCSV } from "./ACReporterCSV"; import { ACReporterHTML } from "./ACReporterHTML"; import { ACReporterJSON } from "./ACReporterJSON"; import { ACReporterXLSX } from "./ACReporterXLSX"; +import { Guideline } from "../engine/IGuideline"; +import { eRuleConfidence, eRulePolicy } from "../engine/IRule"; export interface IReporterStored { startScan: number, @@ -38,8 +40,8 @@ export type GenSummReturn = { } | void export interface IReporter { name(): string - generateReport(config: IConfigInternal, rulesets: IRuleset[], reportData: IReporterStored): { reportPath: string, report: string } | void; - generateSummary(config: IConfigInternal, rulesets: IRuleset[], endReport: number, summaryData: CompressedReport[]): Promise; + generateReport(config: IConfigInternal, rulesets: Guideline[], reportData: IReporterStored): { reportPath: string, report: string } | void; + generateSummary(config: IConfigInternal, rulesets: Guideline[], endReport: number, summaryData: CompressedReport[]): Promise; }; /** @@ -48,7 +50,7 @@ export interface IReporter { */ export class ReporterManager { private static config: IConfigInternal; - private static rulesets: IRuleset[]; + private static rulesets: Guideline[]; private static absAPI: IAbstractAPI; private static reporters: IReporter[] = []; private static reports: CompressedReport[] = [] @@ -137,7 +139,8 @@ export class ReporterManager { snippet: issue[7], help: issue[8], ignored: issue[9], - level: ReporterManager.valueToLevel(issue[2]) + level: ReporterManager.valueToLevel(issue[2]), + node: null } results.push(result); nls[result.ruleId] = nls[result.ruleId] || {}; @@ -182,7 +185,7 @@ export class ReporterManager { } } - public static initialize(config: IConfigInternal, absAPI: IAbstractAPI, rulesets: IRuleset[]) { + public static initialize(config: IConfigInternal, absAPI: IAbstractAPI, rulesets: Guideline[]) { ReporterManager.config = config; ReporterManager.absAPI = absAPI; ReporterManager.rulesets = rulesets; diff --git a/report-react/src/IReport.tsx b/report-react/src/IReport.tsx index 18f51c453..6e234f2aa 100644 --- a/report-react/src/IReport.tsx +++ b/report-react/src/IReport.tsx @@ -37,6 +37,7 @@ export interface IReport { export interface IReportItem { ruleId: string, + reasonId?: number | string, path: { aria: string, dom: string @@ -51,7 +52,14 @@ export interface ICheckpoint { num: string, name: string, summary: string, - rules: { id: string, level: string }[] + rules: Array<{ + id: string, + // (optional) Reason codes that this ruleset mapping applies to, + // or all if not specified + reasonCodes?: string[], + level: string, + toolkitLevel: string + }> } export interface IRuleset { diff --git a/report-react/src/report/ReportChecklist.tsx b/report-react/src/report/ReportChecklist.tsx index 5b36b9ed6..7d55f3139 100644 --- a/report-react/src/report/ReportChecklist.tsx +++ b/report-react/src/report/ReportChecklist.tsx @@ -17,7 +17,7 @@ import React from "react"; import "./report.scss"; -import { IReport, IReportItem, valueMap, IRuleset } from "../IReport"; +import { IReport, IReportItem, valueMap, IRuleset, ICheckpoint } from "../IReport"; import ReportRow from "./ReportRow"; import { Grid, Column } from "@carbon/react"; @@ -37,6 +37,7 @@ export default class ReportChecklist extends React.Component rule.id === item.ruleId); + if (ruleInfo?.reasonCodes) { + console.log(ruleInfo, item); + } + if (!ruleInfo?.reasonCodes || ruleInfo.reasonCodes.includes(""+item.reasonId!)) { + group.items.push(item); + group.counts[val] = (group.counts[val] || 0) + 1; + } } } }