Skip to content

Commit

Permalink
chore(rules): add ast building htmlcs rules
Browse files Browse the repository at this point in the history
  • Loading branch information
j-mendez committed Mar 9, 2024
1 parent ce3c373 commit 3b0f0ef
Show file tree
Hide file tree
Showing 14 changed files with 2,140 additions and 130 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ kayle/screenshot.png
chrome-extension

# build custom extension
build-extension.js
build-rules.js
kayle/builder/*.js

# data dir
_data
Original file line number Diff line number Diff line change
Expand Up @@ -972,7 +972,7 @@ _global.HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_3_1_3_1 = {
}
}
},
testHeadingOrder: function (top, level) {
testHeadingOrder: function (top) {
let lastHeading = 0;

for (const heading of HTMLCS.util.getAllElements(
Expand All @@ -988,7 +988,7 @@ _global.HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_3_1_3_1 = {
// If last heading is empty, we are at document top and we are
// expecting a H1, generally speaking.
HTMLCS.addMessage(
level,
HTMLCS.ERROR,
heading,
_global.HTMLCS.getTranslation("1_3_1_G141_a").replace(
/\{\{headingNum\}\}/g,
Expand All @@ -999,7 +999,7 @@ _global.HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_3_1_3_1 = {
}

HTMLCS.addMessage(
level,
HTMLCS.ERROR,
heading,
_global.HTMLCS.getTranslation("1_3_1_G141_b")
.replace(/\{\{headingNum\}\}/g, headingNumStr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ _global.HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_3_1_3_1_A = {
process: (element, top) => {
if (element === top) {
HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_3_1_3_1.testHeadingOrder(
top,
HTMLCS.ERROR
top
);
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ _global.HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_3_1_3_1_AAA = {
process: (element, top) => {
if (element === top) {
HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_3_1_3_1.testHeadingOrder(
top,
HTMLCS.ERROR
top
);
}
},
Expand Down
4 changes: 2 additions & 2 deletions fast_htmlcs/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ type TestContrastRatio = (
type Snif = null | {
isValidLanguageTag?(element): boolean;
testSemanticPresentationRole(element: Element): boolean;
testHeadingOrder?(element: Element, typeCode: string): boolean;
testHeadingOrder?(element: Element): boolean;
testLabelsOnInputs?(element: Element, top: Element, t: boolean): boolean;
testContrastRatio?: TestContrastRatio;
};
Expand Down Expand Up @@ -163,7 +163,7 @@ type GuideLine = {
testOptgroup?(element: Element): void;
testRequiredFieldsets?(element: Element): void;
testListsWithBreaks?(element: Element): void;
testHeadingOrder?(element: Element, level: string): void;
testHeadingOrder?(element: Element): void;
testEmptyHeading?(element: Element): void;
testUnstructuredNavLinks?(element: Element): void;
testIframeTitle?(element: Element): void;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// compiler the runners into a valid chrome extension
import { writeFileSync, mkdirSync, existsSync } from "fs";
import { runnersJavascript } from "./build/runner-js";
import { runnersJavascript } from "../build/runner-js";
import { cwd } from "process";
import { join } from "path";

Expand Down
60 changes: 60 additions & 0 deletions kayle/builder/build-htmlcs-params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { lstatSync, readdirSync, readFileSync } from "fs";
import { join, parse as pathParse } from "path";
import { parse } from "acorn";
import { simple } from "acorn-walk";
import type { ParamList } from "./build-types";

const paramList: ParamList[] = [];

const processDirectory = (directory) => {
readdirSync(directory).forEach((file) => {
const fullPath = join(directory, file);
if (lstatSync(fullPath).isDirectory()) {
processDirectory(fullPath);
} else if (fullPath.endsWith(".js")) {
processFile(fullPath);
}
});
};

const processFile = (filePath) => {
const code = readFileSync(filePath, "utf8");
const ast = parse(code, { sourceType: "script", ecmaVersion: 2020 });

simple(ast, {
CallExpression(node) {
if (
node.callee.type === "MemberExpression" &&
// @ts-ignore
node.callee.object.name === "HTMLCS" &&
// @ts-ignore
node.callee.property.name === "addMessage"
) {
const params = node.arguments.map((arg) => {
if (arg.type === "Literal") {
return arg.value;
}
if (arg.type === "Identifier") {
return arg.name;
}
if (arg.type === "MemberExpression") {
// @ts-ignore
return arg.property.name?.toLowerCase();
}

return null;
});

params.push(pathParse(filePath).name);

paramList.push(params as ParamList);
}
},
});
};

// process the params to a list
export const processParams = () => {
processDirectory("../fast_htmlcs/dist/Standards");
return paramList;
};
42 changes: 20 additions & 22 deletions kayle/build-rules.ts → kayle/builder/build-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,14 @@ import { chromium } from "playwright";
import { Standard, kayle } from "kayle";
import { writeFile } from "fs/promises";
import { format } from "prettier";

type Rule = {
ruleId: string;
description?: string;
help?: string;
helpUrl?: string;
tags?: string[];
actIds?: string[];
};
import { htmlcsRuleMap } from "./htmlcs-rule-map";
import { processParams } from "./build-htmlcs-params";
import type { Rule } from "./build-types";

(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
page.on("console", (msg) => console.log("PAGE LOG:", msg.text()));

const fast_htmlcs_rules: Rule[] = [];
const fast_axe_rules: Rule[] = [];
Expand All @@ -32,26 +27,29 @@ type Rule = {
true,
);

await page.exposeFunction("pushHtmlcsRule", (t: Rule) => {
fast_htmlcs_rules.push(t);
});
const paramList = await processParams();

await page.evaluate((o) => {
window.paramList = o;
}, paramList);

await page.exposeFunction("pushAxeRule", (t: Rule) => {
fast_axe_rules.push(t);
await page.addScriptTag({
content: `window.htmlcsRuleMap = ${htmlcsRuleMap.toString()};`,
});

await page.exposeFunction("pushHtmlcsRule", (t: Rule) =>
fast_htmlcs_rules.push(t),
);

await page.exposeFunction("pushAxeRule", (t: Rule) => fast_axe_rules.push(t));

await page.evaluate(() => {
// @ts-ignore
for (const r of window.axe.getRules()) {
// @ts-ignore we need to get the rules description and urls.
window.pushAxeRule(r);
}
for (const k of Object.keys(window)) {
if (k.startsWith("HTMLCS_WCAG2AAA_Sniffs_Principle")) {
// @ts-ignore
window.pushHtmlcsRule({
ruleId: k,
});
for (const k of window.paramList) {
if (k && k.length >= 4) {
window.pushHtmlcsRule(window.htmlcsRuleMap(k));
}
}
});
Expand Down
24 changes: 24 additions & 0 deletions kayle/builder/build-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export type Rule = {
ruleId: string;
description?: string;
help?: string;
helpUrl?: string | string[];
tags?: string[];
actIds?: string[];
ruleType?: "error" | "warning" | "notice";
};

export type ParamList = string[];

declare global {
interface Window {
HTMLCS: any;
axe: any;
pushHtmlcsRule: (r: Rule) => void;
pushAxeRule: (r: Rule) => void;
htmlcsRuleMap: (t: ParamList) => Rule;
paramList: ParamList[];
}
}

window.HTMLCS = window.HTMLCS || {};
44 changes: 44 additions & 0 deletions kayle/builder/htmlcs-rule-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { Rule, ParamList } from "./build-types";

// Hand stich the rules to map from htmlcs.
// We need to map the ruleId to the translation file key.

// map the rules to a detailed object
// the descriptions handling is not perfect and needs to handle split cases.
export const htmlcsRuleMap = (rule: ParamList) => {
const concatWcagLink = (l: string) =>
`https://www.w3.org/TR/WCAG20-TECHS/${l}`;

const helpLinks = rule[3] ? rule[3].split(",") : [];
const section508 = !!rule[2];

// translations that do not use _ to concat
const directRuleAssignments = [
"Check",
"SinglePointer_Check",
"AccessibleName",
"Mousedown_Check",
"Touchstart_Check",
"Devicemotion",
];

// TODO: PATCH RULES FROM INLINE TRANSLATIONS

const description =
rule[2] ||
window.HTMLCS.getTranslation(`${rule[4]}_${rule[3]}`) ||
window.HTMLCS.getTranslation(`${rule[4]}${rule[3]}`) ||
"";

return {
ruleId:
section508 || !description
? rule[3]
: `${rule[4]}${directRuleAssignments.includes(rule[3]) ? "." : "_"}${rule[3]}`,
description: description,
helpUrl: section508
? []
: helpLinks.map((r) => concatWcagLink(r.split(".")[0])),
ruleType: rule[0],
};
};
Loading

0 comments on commit 3b0f0ef

Please sign in to comment.