a11ylens 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +21 -0
  3. package/README.md +135 -0
  4. package/dist/cli/index.d.ts +2 -0
  5. package/dist/cli/index.js +162 -0
  6. package/dist/config/defaults.d.ts +2 -0
  7. package/dist/config/defaults.js +9 -0
  8. package/dist/config/loader.d.ts +7 -0
  9. package/dist/config/loader.js +104 -0
  10. package/dist/engine/runA11yLens.d.ts +9 -0
  11. package/dist/engine/runA11yLens.js +158 -0
  12. package/dist/engine/styleResolver.d.ts +2 -0
  13. package/dist/engine/styleResolver.js +63 -0
  14. package/dist/index.d.ts +5 -0
  15. package/dist/index.js +8 -0
  16. package/dist/parser/ast.d.ts +25 -0
  17. package/dist/parser/ast.js +169 -0
  18. package/dist/parser/css.d.ts +7 -0
  19. package/dist/parser/css.js +48 -0
  20. package/dist/reporters/consoleReporter.d.ts +2 -0
  21. package/dist/reporters/consoleReporter.js +67 -0
  22. package/dist/reporters/jsonReporter.d.ts +2 -0
  23. package/dist/reporters/jsonReporter.js +7 -0
  24. package/dist/reporters/sarifReporter.d.ts +2 -0
  25. package/dist/reporters/sarifReporter.js +95 -0
  26. package/dist/rules/clickableNonsemantic.d.ts +2 -0
  27. package/dist/rules/clickableNonsemantic.js +43 -0
  28. package/dist/rules/contrastRatio.d.ts +2 -0
  29. package/dist/rules/contrastRatio.js +149 -0
  30. package/dist/rules/engine.d.ts +4 -0
  31. package/dist/rules/engine.js +22 -0
  32. package/dist/rules/iconOnlyControl.d.ts +2 -0
  33. package/dist/rules/iconOnlyControl.js +50 -0
  34. package/dist/rules/imgAlt.d.ts +2 -0
  35. package/dist/rules/imgAlt.js +48 -0
  36. package/dist/rules/registry.d.ts +3 -0
  37. package/dist/rules/registry.js +19 -0
  38. package/dist/rules/routerLinkText.d.ts +2 -0
  39. package/dist/rules/routerLinkText.js +32 -0
  40. package/dist/rules/utils.d.ts +6 -0
  41. package/dist/rules/utils.js +51 -0
  42. package/dist/scanner/fileAnalyzer.d.ts +1 -0
  43. package/dist/scanner/fileAnalyzer.js +46 -0
  44. package/dist/scanner/fileSystemWalker.js +122 -0
  45. package/dist/scanner/fileWalker.d.ts +8 -0
  46. package/dist/scanner/fileWalker.js +126 -0
  47. package/dist/scanner/styleExtractor.d.ts +13 -0
  48. package/dist/scanner/styleExtractor.js +95 -0
  49. package/dist/scanner/templateExtractor.d.ts +13 -0
  50. package/dist/scanner/templateExtractor.js +101 -0
  51. package/dist/types/config.d.ts +9 -0
  52. package/dist/types/config.js +2 -0
  53. package/dist/types/results.d.ts +22 -0
  54. package/dist/types/results.js +2 -0
  55. package/dist/types/rules.d.ts +14 -0
  56. package/dist/types/rules.js +2 -0
  57. package/dist/types/style.d.ts +9 -0
  58. package/dist/types/style.js +2 -0
  59. package/package.json +65 -0
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildStyleLookup = void 0;
4
+ const css_1 = require("../parser/css");
5
+ const invalidSelectorChars = /[\\s>#:[\\]+~*]/;
6
+ const parseSelector = (selector) => {
7
+ if (invalidSelectorChars.test(selector))
8
+ return undefined;
9
+ const parts = selector.split(".");
10
+ const tag = parts[0] ? parts[0].toLowerCase() : undefined;
11
+ const classes = parts.slice(1).filter((item) => item.length > 0);
12
+ if (!tag && classes.length === 0)
13
+ return undefined;
14
+ return { tag, classes };
15
+ };
16
+ const matchSelector = (node, selector, classList) => {
17
+ if (selector.tag && selector.tag !== node.tagName)
18
+ return false;
19
+ if (selector.classes.length === 0)
20
+ return true;
21
+ for (const cls of selector.classes) {
22
+ if (!classList.includes(cls))
23
+ return false;
24
+ }
25
+ return true;
26
+ };
27
+ const mergeStyles = (base, next) => {
28
+ var _a, _b, _c, _d;
29
+ return {
30
+ color: (_a = next.color) !== null && _a !== void 0 ? _a : base.color,
31
+ backgroundColor: (_b = next.backgroundColor) !== null && _b !== void 0 ? _b : base.backgroundColor,
32
+ fontSize: (_c = next.fontSize) !== null && _c !== void 0 ? _c : base.fontSize,
33
+ fontWeight: (_d = next.fontWeight) !== null && _d !== void 0 ? _d : base.fontWeight
34
+ };
35
+ };
36
+ const buildStyleLookup = (styleSheets) => {
37
+ const rules = styleSheets.flatMap((sheet) => (0, css_1.parseCss)(sheet));
38
+ const ruleSelectors = rules.map((rule) => ({
39
+ selectors: rule.selectors.map(parseSelector),
40
+ declarations: rule.declarations
41
+ }));
42
+ return {
43
+ getStyleFor: (node) => {
44
+ const classAttr = node.attrs["class"];
45
+ const classList = typeof classAttr === "string"
46
+ ? classAttr.split(/\s+/).map((item) => item.trim()).filter(Boolean)
47
+ : [];
48
+ let resolved = {};
49
+ for (const rule of ruleSelectors) {
50
+ for (const selector of rule.selectors) {
51
+ if (!selector)
52
+ continue;
53
+ if (matchSelector(node, selector, classList)) {
54
+ resolved = mergeStyles(resolved, rule.declarations);
55
+ break;
56
+ }
57
+ }
58
+ }
59
+ return resolved;
60
+ }
61
+ };
62
+ };
63
+ exports.buildStyleLookup = buildStyleLookup;
@@ -0,0 +1,5 @@
1
+ export { runA11yLens, resolveConfig } from "./engine/runA11yLens";
2
+ export { parseTemplate } from "./parser/ast";
3
+ export type { A11yLensConfig, A11yLensConfigOverrides, OutputFormat } from "./types/config";
4
+ export type { Issue, FileScanResult, ProjectScanResult, Severity } from "./types/results";
5
+ export type { Rule, RuleContext } from "./types/rules";
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseTemplate = exports.resolveConfig = exports.runA11yLens = void 0;
4
+ var runA11yLens_1 = require("./engine/runA11yLens");
5
+ Object.defineProperty(exports, "runA11yLens", { enumerable: true, get: function () { return runA11yLens_1.runA11yLens; } });
6
+ Object.defineProperty(exports, "resolveConfig", { enumerable: true, get: function () { return runA11yLens_1.resolveConfig; } });
7
+ var ast_1 = require("./parser/ast");
8
+ Object.defineProperty(exports, "parseTemplate", { enumerable: true, get: function () { return ast_1.parseTemplate; } });
@@ -0,0 +1,25 @@
1
+ export interface SourceLocation {
2
+ line: number;
3
+ column: number;
4
+ }
5
+ export type Node = ElementNode | TextNode;
6
+ export interface RootNode {
7
+ type: "root";
8
+ children: Node[];
9
+ }
10
+ export interface ElementNode {
11
+ type: "element";
12
+ tagName: string;
13
+ attrs: Record<string, string | true>;
14
+ children: Node[];
15
+ loc?: SourceLocation;
16
+ }
17
+ export interface TextNode {
18
+ type: "text";
19
+ value: string;
20
+ loc?: SourceLocation;
21
+ }
22
+ export declare const parseTemplate: (input: string) => RootNode;
23
+ export declare const traverse: (node: RootNode | Node, visit: (node: Node) => void) => void;
24
+ export declare const getTextContent: (node: RootNode | Node) => string;
25
+ export declare const findElementsByTag: (root: RootNode, tagName: string) => ElementNode[];
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findElementsByTag = exports.getTextContent = exports.traverse = exports.parseTemplate = void 0;
4
+ const voidTags = new Set([
5
+ "area",
6
+ "base",
7
+ "br",
8
+ "col",
9
+ "embed",
10
+ "hr",
11
+ "img",
12
+ "input",
13
+ "link",
14
+ "meta",
15
+ "param",
16
+ "source",
17
+ "track",
18
+ "wbr"
19
+ ]);
20
+ const buildLineMap = (input) => {
21
+ const lineStarts = [0];
22
+ for (let i = 0; i < input.length; i += 1) {
23
+ if (input[i] === "\n") {
24
+ lineStarts.push(i + 1);
25
+ }
26
+ }
27
+ return lineStarts;
28
+ };
29
+ const getLocation = (lineStarts, index) => {
30
+ let low = 0;
31
+ let high = lineStarts.length - 1;
32
+ while (low <= high) {
33
+ const mid = Math.floor((low + high) / 2);
34
+ const start = lineStarts[mid];
35
+ const nextStart = mid + 1 < lineStarts.length ? lineStarts[mid + 1] : Number.MAX_SAFE_INTEGER;
36
+ if (index >= start && index < nextStart) {
37
+ return { line: mid + 1, column: index - start + 1 };
38
+ }
39
+ if (index < start) {
40
+ high = mid - 1;
41
+ }
42
+ else {
43
+ low = mid + 1;
44
+ }
45
+ }
46
+ return { line: 1, column: 1 };
47
+ };
48
+ const parseAttributes = (attrText) => {
49
+ var _a, _b, _c;
50
+ const attrs = {};
51
+ const attrRegex = /([^\s=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|`([^`]*)`|([^\s"'=<>`]+)))?/g;
52
+ let match = null;
53
+ while ((match = attrRegex.exec(attrText)) !== null) {
54
+ const name = match[1];
55
+ const value = (_c = (_b = (_a = match[2]) !== null && _a !== void 0 ? _a : match[3]) !== null && _b !== void 0 ? _b : match[4]) !== null && _c !== void 0 ? _c : match[5];
56
+ attrs[name] = value === undefined ? true : value;
57
+ }
58
+ return attrs;
59
+ };
60
+ const parseTemplate = (input) => {
61
+ const root = { type: "root", children: [] };
62
+ const stack = [];
63
+ const tagRegex = /<!--[\s\S]*?-->|<\/?[a-zA-Z][^>]*?>/g;
64
+ const lineStarts = buildLineMap(input);
65
+ let lastIndex = 0;
66
+ let match = null;
67
+ const pushNode = (node) => {
68
+ const parent = stack[stack.length - 1];
69
+ if (parent) {
70
+ parent.children.push(node);
71
+ }
72
+ else {
73
+ root.children.push(node);
74
+ }
75
+ };
76
+ while ((match = tagRegex.exec(input)) !== null) {
77
+ const token = match[0];
78
+ const index = match.index;
79
+ if (index > lastIndex) {
80
+ const textValue = input.slice(lastIndex, index);
81
+ if (textValue.length > 0) {
82
+ pushNode({
83
+ type: "text",
84
+ value: textValue,
85
+ loc: getLocation(lineStarts, lastIndex)
86
+ });
87
+ }
88
+ }
89
+ lastIndex = index + token.length;
90
+ if (token.startsWith("<!--")) {
91
+ continue;
92
+ }
93
+ const isEndTag = token.startsWith("</");
94
+ const isSelfClosing = token.endsWith("/>");
95
+ if (isEndTag) {
96
+ const name = token.slice(2, -1).trim().toLowerCase();
97
+ while (stack.length > 0) {
98
+ const current = stack.pop();
99
+ if (current && current.tagName === name)
100
+ break;
101
+ }
102
+ continue;
103
+ }
104
+ const content = token.slice(1, isSelfClosing ? -2 : -1).trim();
105
+ if (content.length === 0)
106
+ continue;
107
+ const firstSpace = content.indexOf(" ");
108
+ const tagName = (firstSpace === -1 ? content : content.slice(0, firstSpace)).toLowerCase();
109
+ const attrText = firstSpace === -1 ? "" : content.slice(firstSpace + 1);
110
+ const element = {
111
+ type: "element",
112
+ tagName,
113
+ attrs: parseAttributes(attrText),
114
+ children: [],
115
+ loc: getLocation(lineStarts, index)
116
+ };
117
+ pushNode(element);
118
+ if (!isSelfClosing && !voidTags.has(tagName)) {
119
+ stack.push(element);
120
+ }
121
+ }
122
+ if (lastIndex < input.length) {
123
+ const textValue = input.slice(lastIndex);
124
+ if (textValue.length > 0) {
125
+ pushNode({
126
+ type: "text",
127
+ value: textValue,
128
+ loc: getLocation(lineStarts, lastIndex)
129
+ });
130
+ }
131
+ }
132
+ return root;
133
+ };
134
+ exports.parseTemplate = parseTemplate;
135
+ const traverse = (node, visit) => {
136
+ if (node.type === "root") {
137
+ for (const child of node.children) {
138
+ (0, exports.traverse)(child, visit);
139
+ }
140
+ return;
141
+ }
142
+ visit(node);
143
+ if (node.type === "element") {
144
+ for (const child of node.children) {
145
+ (0, exports.traverse)(child, visit);
146
+ }
147
+ }
148
+ };
149
+ exports.traverse = traverse;
150
+ const getTextContent = (node) => {
151
+ if (node.type === "root") {
152
+ return node.children.map(exports.getTextContent).join("");
153
+ }
154
+ if (node.type === "text") {
155
+ return node.value;
156
+ }
157
+ return node.children.map(exports.getTextContent).join("");
158
+ };
159
+ exports.getTextContent = getTextContent;
160
+ const findElementsByTag = (root, tagName) => {
161
+ const matches = [];
162
+ (0, exports.traverse)(root, (node) => {
163
+ if (node.type === "element" && node.tagName === tagName) {
164
+ matches.push(node);
165
+ }
166
+ });
167
+ return matches;
168
+ };
169
+ exports.findElementsByTag = findElementsByTag;
@@ -0,0 +1,7 @@
1
+ import type { StyleDeclaration } from "../types/style";
2
+ export interface CssRule {
3
+ selectors: string[];
4
+ declarations: StyleDeclaration;
5
+ }
6
+ export declare const parseCss: (input: string) => CssRule[];
7
+ export declare const parseInlineStyle: (styleValue: string) => StyleDeclaration;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseInlineStyle = exports.parseCss = void 0;
4
+ const parseCss = (input) => {
5
+ const rules = [];
6
+ const blockRegex = /([^{}]+)\{([^{}]+)\}/g;
7
+ let match = null;
8
+ while ((match = blockRegex.exec(input)) !== null) {
9
+ const selectorText = match[1].trim();
10
+ const body = match[2].trim();
11
+ if (!selectorText || !body)
12
+ continue;
13
+ const selectors = selectorText
14
+ .split(",")
15
+ .map((selector) => selector.trim())
16
+ .filter((selector) => selector.length > 0);
17
+ const declarations = parseDeclarations(body);
18
+ if (selectors.length === 0 || Object.keys(declarations).length === 0)
19
+ continue;
20
+ rules.push({ selectors, declarations });
21
+ }
22
+ return rules;
23
+ };
24
+ exports.parseCss = parseCss;
25
+ const parseInlineStyle = (styleValue) => {
26
+ const declarations = parseDeclarations(styleValue);
27
+ return declarations;
28
+ };
29
+ exports.parseInlineStyle = parseInlineStyle;
30
+ const parseDeclarations = (body) => {
31
+ const entries = body.split(";");
32
+ const declarations = {};
33
+ for (const entry of entries) {
34
+ const [rawKey, rawValue] = entry.split(":").map((part) => part.trim());
35
+ if (!rawKey || !rawValue)
36
+ continue;
37
+ const key = rawKey.toLowerCase();
38
+ if (key === "color")
39
+ declarations.color = rawValue;
40
+ if (key === "background-color")
41
+ declarations.backgroundColor = rawValue;
42
+ if (key === "font-size")
43
+ declarations.fontSize = rawValue;
44
+ if (key === "font-weight")
45
+ declarations.fontWeight = rawValue;
46
+ }
47
+ return declarations;
48
+ };
@@ -0,0 +1,2 @@
1
+ import type { ProjectScanResult } from "../types/results";
2
+ export declare const reportConsole: (result: ProjectScanResult, cwd: string) => void;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.reportConsole = void 0;
37
+ const path = __importStar(require("path"));
38
+ const reportConsole = (result, cwd) => {
39
+ var _a;
40
+ console.log("A11yLens");
41
+ console.log("=======");
42
+ console.log("");
43
+ if (result.files.length === 0) {
44
+ console.log("No matching files found.");
45
+ return;
46
+ }
47
+ for (const file of result.files) {
48
+ const relative = path.relative(cwd, file.filePath);
49
+ console.log(relative || file.filePath);
50
+ if (file.issues.length === 0) {
51
+ console.log(" - No issues\n");
52
+ continue;
53
+ }
54
+ for (const issue of file.issues) {
55
+ const location = issue.line ? `:${issue.line}:${(_a = issue.column) !== null && _a !== void 0 ? _a : 1}` : "";
56
+ console.log(` - [${issue.severity}] ${issue.ruleId}${location} ${issue.message}`);
57
+ }
58
+ console.log("");
59
+ }
60
+ console.log("Summary");
61
+ console.log("-------");
62
+ console.log(`Errors: ${result.totals.error}`);
63
+ console.log(`Warnings: ${result.totals.warn}`);
64
+ console.log(`Info: ${result.totals.info}`);
65
+ console.log(`Score: ${result.score} / 100`);
66
+ };
67
+ exports.reportConsole = reportConsole;
@@ -0,0 +1,2 @@
1
+ import type { ProjectScanResult } from "../types/results";
2
+ export declare const reportJson: (result: ProjectScanResult) => string;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reportJson = void 0;
4
+ const reportJson = (result) => {
5
+ return JSON.stringify(result, null, 2);
6
+ };
7
+ exports.reportJson = reportJson;
@@ -0,0 +1,2 @@
1
+ import type { ProjectScanResult } from "../types/results";
2
+ export declare const reportSarif: (result: ProjectScanResult) => string;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.reportSarif = void 0;
37
+ const path = __importStar(require("path"));
38
+ const registry_1 = require("../rules/registry");
39
+ const severityToLevel = {
40
+ error: "error",
41
+ warn: "warning",
42
+ info: "note"
43
+ };
44
+ const reportSarif = (result) => {
45
+ const rules = registry_1.builtInRules.map((rule) => ({
46
+ id: rule.id,
47
+ name: rule.id,
48
+ shortDescription: { text: rule.description },
49
+ fullDescription: { text: rule.description }
50
+ }));
51
+ const sarif = {
52
+ version: "2.1.0",
53
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
54
+ runs: [
55
+ {
56
+ tool: {
57
+ driver: {
58
+ name: "a11ylens",
59
+ informationUri: "https://github.com/madhanmonishj/a11ylens",
60
+ rules
61
+ }
62
+ },
63
+ results: result.files.flatMap((file) => file.issues.map((issue) => {
64
+ var _a, _b;
65
+ return ({
66
+ ruleId: issue.ruleId,
67
+ level: (_a = severityToLevel[issue.severity]) !== null && _a !== void 0 ? _a : "warning",
68
+ message: { text: issue.message },
69
+ locations: [
70
+ {
71
+ physicalLocation: {
72
+ artifactLocation: {
73
+ uri: normalizeFileUri(file.filePath)
74
+ },
75
+ region: issue.line
76
+ ? {
77
+ startLine: issue.line,
78
+ startColumn: (_b = issue.column) !== null && _b !== void 0 ? _b : 1
79
+ }
80
+ : undefined
81
+ }
82
+ }
83
+ ]
84
+ });
85
+ }))
86
+ }
87
+ ]
88
+ };
89
+ return JSON.stringify(sarif, null, 2);
90
+ };
91
+ exports.reportSarif = reportSarif;
92
+ const normalizeFileUri = (filePath) => {
93
+ const resolved = path.resolve(filePath);
94
+ return resolved.replace(/\\/g, "/");
95
+ };
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "../types/rules";
2
+ export declare const clickableNonSemanticRule: Rule;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clickableNonSemanticRule = void 0;
4
+ const utils_1 = require("./utils");
5
+ const isInteractive = (node) => {
6
+ if (node.tagName === "button")
7
+ return true;
8
+ if (node.tagName === "a") {
9
+ return (0, utils_1.hasAttr)(node, "href") || (0, utils_1.hasAttr)(node, "routerLink") || (0, utils_1.hasAttr)(node, "[routerLink]");
10
+ }
11
+ const role = (0, utils_1.getAttr)(node, "role");
12
+ if (typeof role === "string" && role.toLowerCase() === "button")
13
+ return true;
14
+ return (0, utils_1.hasAttr)(node, "tabindex");
15
+ };
16
+ exports.clickableNonSemanticRule = {
17
+ id: "clickable-nonsemantic",
18
+ description: "Non-semantic elements with click handlers should be accessible.",
19
+ severity: "warn",
20
+ check: (ast, context) => {
21
+ const issues = [];
22
+ (0, utils_1.walkWithAncestors)(ast, (node) => {
23
+ var _a, _b;
24
+ if (node.type !== "element")
25
+ return;
26
+ if (node.tagName !== "div" && node.tagName !== "span")
27
+ return;
28
+ if (!(0, utils_1.hasAttr)(node, "(click)"))
29
+ return;
30
+ if (isInteractive(node))
31
+ return;
32
+ issues.push({
33
+ ruleId: "clickable-nonsemantic",
34
+ message: "Clickable div/span needs semantic role or keyboard support.",
35
+ filePath: context.filePath,
36
+ line: (_a = node.loc) === null || _a === void 0 ? void 0 : _a.line,
37
+ column: (_b = node.loc) === null || _b === void 0 ? void 0 : _b.column,
38
+ severity: "warn"
39
+ });
40
+ });
41
+ return issues;
42
+ }
43
+ };
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "../types/rules";
2
+ export declare const contrastRatioRule: Rule;