@dallask/a11y-mcp-srv 1.1.13 → 2.2.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.
- package/README.md +17 -0
- package/dist/core/accessibility-runner.d.ts +22 -14
- package/dist/core/accessibility-runner.d.ts.map +1 -1
- package/dist/core/accessibility-runner.js +110 -64
- package/dist/core/accessibility-runner.js.map +1 -1
- package/dist/core/basic-auth.d.ts +35 -0
- package/dist/core/basic-auth.d.ts.map +1 -0
- package/dist/core/basic-auth.js +52 -0
- package/dist/core/basic-auth.js.map +1 -0
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +4 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/normalize-audit-result.d.ts +19 -0
- package/dist/core/normalize-audit-result.d.ts.map +1 -0
- package/dist/core/normalize-audit-result.js +173 -0
- package/dist/core/normalize-audit-result.js.map +1 -0
- package/dist/core/result-processor.d.ts +22 -5
- package/dist/core/result-processor.d.ts.map +1 -1
- package/dist/core/result-processor.js +115 -101
- package/dist/core/result-processor.js.map +1 -1
- package/dist/server.d.ts +5 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +133 -6
- package/dist/server.js.map +1 -1
- package/dist/tools/aggregate.d.ts.map +1 -1
- package/dist/tools/aggregate.js +23 -22
- package/dist/tools/aggregate.js.map +1 -1
- package/dist/tools/analysis.d.ts.map +1 -1
- package/dist/tools/analysis.js +87 -134
- package/dist/tools/analysis.js.map +1 -1
- package/dist/tools/audit.d.ts.map +1 -1
- package/dist/tools/audit.js +73 -23
- package/dist/tools/audit.js.map +1 -1
- package/dist/tools/comparison.d.ts.map +1 -1
- package/dist/tools/comparison.js +28 -17
- package/dist/tools/comparison.js.map +1 -1
- package/dist/tools/export.d.ts.map +1 -1
- package/dist/tools/export.js +55 -38
- package/dist/tools/export.js.map +1 -1
- package/dist/tools/filter.d.ts.map +1 -1
- package/dist/tools/filter.js +16 -9
- package/dist/tools/filter.js.map +1 -1
- package/dist/tools/session.d.ts.map +1 -1
- package/dist/tools/session.js +3 -2
- package/dist/tools/session.js.map +1 -1
- package/dist/tools/visualize.d.ts.map +1 -1
- package/dist/tools/visualize.js +47 -51
- package/dist/tools/visualize.js.map +1 -1
- package/dist/types/index.d.ts +53 -13
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +19 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +14 -2
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize audit result input so tools that accept "result or URL" receive the same
|
|
3
|
+
* canonical AuditResult shape that audit_url returns. Handles JSON strings, MCP
|
|
4
|
+
* response wrappers, batch results, and alternate metadata shapes (e.g. ACE vs axe).
|
|
5
|
+
*/
|
|
6
|
+
import type { AuditResult } from '../types/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* Normalize audit result input from a client.
|
|
9
|
+
* - URL string: returns null (caller should run audit).
|
|
10
|
+
* - JSON string of single result: parses and normalizes.
|
|
11
|
+
* - MCP wrapper { content: [{ text: "..." }] }: extracts and normalizes.
|
|
12
|
+
* - Batch { results: [...] }: normalizes first element.
|
|
13
|
+
* - Plain object: fills missing fields with defaults.
|
|
14
|
+
*
|
|
15
|
+
* @param value - Raw value (string, or object from previous tool output).
|
|
16
|
+
* @returns Normalized AuditResult, or null when value is a URL (run audit) or parsing fails.
|
|
17
|
+
*/
|
|
18
|
+
export declare function normalizeAuditResult(value: unknown): AuditResult | null;
|
|
19
|
+
//# sourceMappingURL=normalize-audit-result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-audit-result.d.ts","sourceRoot":"","sources":["../../src/core/normalize-audit-result.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,WAAW,EAIZ,MAAM,mBAAmB,CAAA;AA+G1B;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAkEvE"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize audit result input so tools that accept "result or URL" receive the same
|
|
3
|
+
* canonical AuditResult shape that audit_url returns. Handles JSON strings, MCP
|
|
4
|
+
* response wrappers, batch results, and alternate metadata shapes (e.g. ACE vs axe).
|
|
5
|
+
*/
|
|
6
|
+
const DEFAULT_SUMMARY = {
|
|
7
|
+
totalIssues: 0,
|
|
8
|
+
score: 0,
|
|
9
|
+
wcagCompliance: { A: 0, AA: 0, AAA: 0 },
|
|
10
|
+
byCategory: {},
|
|
11
|
+
byImpact: {},
|
|
12
|
+
};
|
|
13
|
+
function isUrlString(value) {
|
|
14
|
+
const t = value.trim();
|
|
15
|
+
return t.startsWith('http://') || t.startsWith('https://');
|
|
16
|
+
}
|
|
17
|
+
function isJsonLikeString(value) {
|
|
18
|
+
return value.trim().startsWith('{');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build canonical TestMetadata from any raw object (top-level or metadata in various shapes).
|
|
22
|
+
* Ensures all tools receive the same metadata shape regardless of engine (axe, ACE) or API.
|
|
23
|
+
*/
|
|
24
|
+
function buildCanonicalMetadata(obj) {
|
|
25
|
+
const meta = obj.metadata != null && typeof obj.metadata === 'object' ? obj.metadata : {};
|
|
26
|
+
const url = (typeof obj.url === 'string' ? obj.url : undefined) ??
|
|
27
|
+
(typeof meta.url === 'string' ? meta.url : undefined);
|
|
28
|
+
const timestamp = (typeof obj.timestamp === 'string' ? obj.timestamp : undefined) ??
|
|
29
|
+
(typeof meta.timestamp === 'string' ? meta.timestamp : undefined) ??
|
|
30
|
+
(typeof meta.testDate === 'string' ? meta.testDate : undefined);
|
|
31
|
+
if (!url && !timestamp && meta.testEngine == null && meta.testRunner == null) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const testEngine = meta.testEngine != null && typeof meta.testEngine === 'object' && !Array.isArray(meta.testEngine)
|
|
35
|
+
? {
|
|
36
|
+
name: String(meta.testEngine.name ?? 'Unknown'),
|
|
37
|
+
version: String(meta.testEngine.version ?? ''),
|
|
38
|
+
}
|
|
39
|
+
: { name: String(meta.testEngine ?? 'Unknown'), version: '' };
|
|
40
|
+
const testRunner = meta.testRunner != null && typeof meta.testRunner === 'object' && !Array.isArray(meta.testRunner)
|
|
41
|
+
? { name: String(meta.testRunner.name ?? 'Unknown') }
|
|
42
|
+
: { name: String(meta.testRunner ?? 'Unknown') };
|
|
43
|
+
const env = meta.testEnvironment != null && typeof meta.testEnvironment === 'object' && !Array.isArray(meta.testEnvironment)
|
|
44
|
+
? meta.testEnvironment
|
|
45
|
+
: {};
|
|
46
|
+
const viewport = typeof meta.viewport === 'string' ? meta.viewport : '';
|
|
47
|
+
const [w, h] = viewport ? viewport.split('x').map((n) => parseInt(n, 10)) : [env.windowWidth, env.windowHeight];
|
|
48
|
+
const testEnvironment = {
|
|
49
|
+
userAgent: String(env.userAgent ?? 'unknown'),
|
|
50
|
+
windowWidth: typeof w === 'number' && !Number.isNaN(w) ? w : 1280,
|
|
51
|
+
windowHeight: typeof h === 'number' && !Number.isNaN(h) ? h : 720,
|
|
52
|
+
orientationType: env.orientationType,
|
|
53
|
+
orientationAngle: env.orientationAngle,
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
testEngine,
|
|
57
|
+
testRunner,
|
|
58
|
+
testEnvironment,
|
|
59
|
+
timestamp: timestamp ?? new Date().toISOString(),
|
|
60
|
+
url: url ?? '',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Normalize a raw object into the canonical AuditResult shape (same as audit_url returns).
|
|
65
|
+
* Metadata is always normalized to TestMetadata when present.
|
|
66
|
+
*/
|
|
67
|
+
function normalizeObject(obj) {
|
|
68
|
+
const prioritizedIssues = Array.isArray(obj.prioritizedIssues) ? obj.prioritizedIssues : [];
|
|
69
|
+
const quickWins = Array.isArray(obj.quickWins) ? obj.quickWins : [];
|
|
70
|
+
const criticalBlockers = Array.isArray(obj.criticalBlockers) ? obj.criticalBlockers : [];
|
|
71
|
+
let summary = DEFAULT_SUMMARY;
|
|
72
|
+
if (obj.summary != null && typeof obj.summary === 'object') {
|
|
73
|
+
const s = obj.summary;
|
|
74
|
+
const wcag = s.wcagCompliance != null && typeof s.wcagCompliance === 'object'
|
|
75
|
+
? s.wcagCompliance
|
|
76
|
+
: { A: 0, AA: 0, AAA: 0 };
|
|
77
|
+
summary = {
|
|
78
|
+
totalIssues: typeof s.totalIssues === 'number' ? s.totalIssues : 0,
|
|
79
|
+
score: typeof s.score === 'number' ? s.score : 0,
|
|
80
|
+
wcagCompliance: wcag,
|
|
81
|
+
byCategory: s.byCategory != null && typeof s.byCategory === 'object' && !Array.isArray(s.byCategory)
|
|
82
|
+
? s.byCategory
|
|
83
|
+
: {},
|
|
84
|
+
byImpact: s.byImpact != null && typeof s.byImpact === 'object' && !Array.isArray(s.byImpact)
|
|
85
|
+
? s.byImpact
|
|
86
|
+
: {},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const metadata = buildCanonicalMetadata(obj);
|
|
90
|
+
return {
|
|
91
|
+
summary,
|
|
92
|
+
prioritizedIssues,
|
|
93
|
+
quickWins,
|
|
94
|
+
criticalBlockers,
|
|
95
|
+
conversationalSummary: typeof obj.conversationalSummary === 'string' ? obj.conversationalSummary : '',
|
|
96
|
+
issuesTable: typeof obj.issuesTable === 'string' ? obj.issuesTable : '',
|
|
97
|
+
appliedFilters: obj.appliedFilters != null && typeof obj.appliedFilters === 'object' ? obj.appliedFilters : undefined,
|
|
98
|
+
metadata: metadata ?? (obj.metadata != null && typeof obj.metadata === 'object' ? obj.metadata : undefined),
|
|
99
|
+
rawResults: obj.rawResults,
|
|
100
|
+
responseStatus: typeof obj.responseStatus === 'number' ? obj.responseStatus : undefined,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Normalize audit result input from a client.
|
|
105
|
+
* - URL string: returns null (caller should run audit).
|
|
106
|
+
* - JSON string of single result: parses and normalizes.
|
|
107
|
+
* - MCP wrapper { content: [{ text: "..." }] }: extracts and normalizes.
|
|
108
|
+
* - Batch { results: [...] }: normalizes first element.
|
|
109
|
+
* - Plain object: fills missing fields with defaults.
|
|
110
|
+
*
|
|
111
|
+
* @param value - Raw value (string, or object from previous tool output).
|
|
112
|
+
* @returns Normalized AuditResult, or null when value is a URL (run audit) or parsing fails.
|
|
113
|
+
*/
|
|
114
|
+
export function normalizeAuditResult(value) {
|
|
115
|
+
if (value == null) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
if (typeof value === 'string') {
|
|
119
|
+
const trimmed = value.trim();
|
|
120
|
+
if (isUrlString(trimmed)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
if (isJsonLikeString(trimmed)) {
|
|
124
|
+
try {
|
|
125
|
+
const parsed = JSON.parse(trimmed);
|
|
126
|
+
return normalizeAuditResult(parsed);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
if (typeof value !== 'object') {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const obj = value;
|
|
138
|
+
// MCP response wrapper: { content: [ { type, text } ] }
|
|
139
|
+
const content = obj.content;
|
|
140
|
+
if (Array.isArray(content) && content.length > 0) {
|
|
141
|
+
const first = content[0];
|
|
142
|
+
if (first != null && typeof first === 'object' && 'text' in first && typeof first.text === 'string') {
|
|
143
|
+
try {
|
|
144
|
+
const parsed = JSON.parse(first.text);
|
|
145
|
+
return normalizeAuditResult(parsed);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Batch result from audit_multiple_urls: { results: AuditResult[], ... }
|
|
153
|
+
const resultsArray = obj.results;
|
|
154
|
+
if (Array.isArray(resultsArray) && resultsArray.length > 0) {
|
|
155
|
+
return normalizeAuditResult(resultsArray[0]);
|
|
156
|
+
}
|
|
157
|
+
// Wrapped single result: { results: <audit object>, format?: string, ... } (e.g. full tool args passed as results)
|
|
158
|
+
const singleResult = obj.results;
|
|
159
|
+
if (singleResult != null &&
|
|
160
|
+
typeof singleResult === 'object' &&
|
|
161
|
+
!Array.isArray(singleResult) &&
|
|
162
|
+
(('summary' in singleResult && singleResult.summary != null) ||
|
|
163
|
+
('prioritizedIssues' in singleResult && Array.isArray(singleResult.prioritizedIssues)))) {
|
|
164
|
+
return normalizeAuditResult(singleResult);
|
|
165
|
+
}
|
|
166
|
+
// Plain audit result (single): must look like one (has summary or prioritizedIssues)
|
|
167
|
+
if ('summary' in obj || 'prioritizedIssues' in obj) {
|
|
168
|
+
return normalizeObject(obj);
|
|
169
|
+
}
|
|
170
|
+
// Empty or unrecognized object: return minimal valid result so callers don't throw
|
|
171
|
+
return normalizeObject(obj);
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=normalize-audit-result.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-audit-result.js","sourceRoot":"","sources":["../../src/core/normalize-audit-result.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,MAAM,eAAe,GAAiB;IACpC,WAAW,EAAE,CAAC;IACd,KAAK,EAAE,CAAC;IACR,cAAc,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;IACvC,UAAU,EAAE,EAAE;IACd,QAAQ,EAAE,EAAE;CACb,CAAA;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;IACtB,OAAO,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAA;AAC5D,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa;IACrC,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;AACrC,CAAC;AAED;;;GAGG;AACH,SAAS,sBAAsB,CAAC,GAA4B;IAC1D,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,IAAI,IAAI,IAAI,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAE,GAAG,CAAC,QAAoC,CAAC,CAAC,CAAC,EAAE,CAAA;IACtH,MAAM,GAAG,GACP,CAAC,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;QACnD,CAAC,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IACvD,MAAM,SAAS,GACb,CAAC,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QAC/D,CAAC,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QACjE,CAAC,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IACjE,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;QAC7E,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,UAAU,GACd,IAAI,CAAC,UAAU,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC;QAC/F,CAAC,CAAC;YACE,IAAI,EAAE,MAAM,CAAE,IAAI,CAAC,UAAsC,CAAC,IAAI,IAAI,SAAS,CAAC;YAC5E,OAAO,EAAE,MAAM,CAAE,IAAI,CAAC,UAAsC,CAAC,OAAO,IAAI,EAAE,CAAC;SAC5E;QACH,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,IAAI,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;IACjE,MAAM,UAAU,GACd,IAAI,CAAC,UAAU,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC;QAC/F,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAE,IAAI,CAAC,UAAsC,CAAC,IAAI,IAAI,SAAS,CAAC,EAAE;QAClF,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,IAAI,SAAS,CAAC,EAAE,CAAA;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,eAAe,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC;QAC1H,CAAC,CAAE,IAAI,CAAC,eAA2C;QACnD,CAAC,CAAC,EAAE,CAAA;IACN,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;IACvE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,WAAiC,EAAE,GAAG,CAAC,YAAkC,CAAC,CAAA;IAC3J,MAAM,eAAe,GAAG;QACtB,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,SAAS,IAAI,SAAS,CAAC;QAC7C,WAAW,EAAE,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;QACjE,YAAY,EAAE,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG;QACjE,eAAe,EAAE,GAAG,CAAC,eAAqC;QAC1D,gBAAgB,EAAE,GAAG,CAAC,gBAAsC;KAC7D,CAAA;IACD,OAAO;QACL,UAAU;QACV,UAAU;QACV,eAAe;QACf,SAAS,EAAE,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAChD,GAAG,EAAE,GAAG,IAAI,EAAE;KACf,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CAAC,GAA4B;IACnD,MAAM,iBAAiB,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAA;IAC3F,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IACnE,MAAM,gBAAgB,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAA;IAExF,IAAI,OAAO,GAAiB,eAAe,CAAA;IAC3C,IAAI,GAAG,CAAC,OAAO,IAAI,IAAI,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC3D,MAAM,CAAC,GAAG,GAAG,CAAC,OAAkC,CAAA;QAChD,MAAM,IAAI,GAAG,CAAC,CAAC,cAAc,IAAI,IAAI,IAAI,OAAO,CAAC,CAAC,cAAc,KAAK,QAAQ;YAC3E,CAAC,CAAE,CAAC,CAAC,cAAiC;YACtC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAA;QAC3B,OAAO,GAAG;YACR,WAAW,EAAE,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YAClE,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAChD,cAAc,EAAE,IAAI;YACpB,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,IAAI,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;gBAClG,CAAC,CAAE,CAAC,CAAC,UAAqC;gBAC1C,CAAC,CAAC,EAAE;YACN,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI,IAAI,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;gBAC1F,CAAC,CAAE,CAAC,CAAC,QAAmC;gBACxC,CAAC,CAAC,EAAE;SACP,CAAA;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAA;IAE5C,OAAO;QACL,OAAO;QACP,iBAAiB;QACjB,SAAS;QACT,gBAAgB;QAChB,qBAAqB,EAAE,OAAO,GAAG,CAAC,qBAAqB,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE;QACrG,WAAW,EAAE,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE;QACvE,cAAc,EAAE,GAAG,CAAC,cAAc,IAAI,IAAI,IAAI,OAAO,GAAG,CAAC,cAAc,KAAK,QAAQ,CAAC,CAAC,CAAE,GAAG,CAAC,cAAgD,CAAC,CAAC,CAAC,SAAS;QACxJ,QAAQ,EAAE,QAAQ,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,IAAI,IAAI,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAE,GAAG,CAAC,QAAoC,CAAC,CAAC,CAAC,SAAS,CAAC;QACxI,UAAU,EAAE,GAAG,CAAC,UAAuC;QACvD,cAAc,EAAE,OAAO,GAAG,CAAC,cAAc,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS;KACxF,CAAA;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;QAClB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;QAC5B,IAAI,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;YACzB,OAAO,IAAI,CAAA;QACb,CAAC;QACD,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAY,CAAA;gBAC7C,OAAO,oBAAoB,CAAC,MAAM,CAAC,CAAA;YACrC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAA;YACb,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,GAAG,GAAG,KAAgC,CAAA;IAE5C,wDAAwD;IACxD,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAA;IAC3B,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;QACxB,IAAI,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,IAAI,KAAK,IAAI,OAAQ,KAA2B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3H,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAE,KAA0B,CAAC,IAAI,CAAY,CAAA;gBACtE,OAAO,oBAAoB,CAAC,MAAM,CAAC,CAAA;YACrC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAA;YACb,CAAC;QACH,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAA;IAChC,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3D,OAAO,oBAAoB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAA;IAC9C,CAAC;IAED,mHAAmH;IACnH,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAA;IAChC,IACE,YAAY,IAAI,IAAI;QACpB,OAAO,YAAY,KAAK,QAAQ;QAChC,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC;QAC5B,CAAC,CAAC,SAAS,IAAI,YAAY,IAAI,YAAY,CAAC,OAAO,IAAI,IAAI,CAAC;YAC1D,CAAC,mBAAmB,IAAI,YAAY,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC,CAAC,EACzF,CAAC;QACD,OAAO,oBAAoB,CAAC,YAAY,CAAC,CAAA;IAC3C,CAAC;IAED,qFAAqF;IACrF,IAAI,SAAS,IAAI,GAAG,IAAI,mBAAmB,IAAI,GAAG,EAAE,CAAC;QACnD,OAAO,eAAe,CAAC,GAAG,CAAC,CAAA;IAC7B,CAAC;IAED,mFAAmF;IACnF,OAAO,eAAe,CAAC,GAAG,CAAC,CAAA;AAC7B,CAAC"}
|
|
@@ -3,19 +3,36 @@
|
|
|
3
3
|
* Converts raw accessibility results into structured, prioritized, and human-readable format
|
|
4
4
|
*/
|
|
5
5
|
import type { AccessibilityResults, AuditResult, AppliedFilters } from '../types/index.js';
|
|
6
|
+
/** Options passed when processing audit results (e.g. WCAG label used for the audit) */
|
|
7
|
+
export interface ProcessOptions {
|
|
8
|
+
/** WCAG version and level used for the audit (e.g. "WCAG 2.2 AA"). Used when rule tags are empty. */
|
|
9
|
+
auditWcagLabel?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Derive full WCAG label from engine tags (e.g. wcag22aa -> "WCAG 2.2 AA").
|
|
13
|
+
* Used for display and for audit fallback.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getWcagLabelFromTags(tags: string[]): string;
|
|
16
|
+
/** True if wcagLevel string matches the given level (A, AA, AAA); supports full label e.g. "WCAG 2.2 AA". */
|
|
17
|
+
export declare function wcagLevelMatches(level: string, target: 'A' | 'AA' | 'AAA'): boolean;
|
|
18
|
+
/** Numeric order for sorting by WCAG level (A=3, AA=2, AAA=1). Supports full label e.g. "WCAG 2.2 AA". */
|
|
19
|
+
export declare function wcagLevelOrder(level: string): number;
|
|
6
20
|
/**
|
|
7
21
|
* ResultProcessor class - Formats and processes accessibility results
|
|
8
22
|
*/
|
|
9
23
|
export declare class ResultProcessor {
|
|
10
24
|
/**
|
|
11
|
-
* Get WCAG level
|
|
25
|
+
* Get WCAG level for display (full label e.g. "WCAG 2.2 AA"). Uses tags when present, else audit fallback.
|
|
12
26
|
*/
|
|
13
27
|
private getWCAGLevel;
|
|
14
28
|
/**
|
|
15
|
-
* Determine impact level from
|
|
16
|
-
* When ruleData.impact is set (e.g. from ACE), use it so output matches the browser tool.
|
|
29
|
+
* Determine impact level from engine-reported impact (axe or ACE native levels).
|
|
17
30
|
*/
|
|
18
31
|
private getImpactLevel;
|
|
32
|
+
/**
|
|
33
|
+
* Get impact rank for ordering (higher = worse). Uses IMPACT_ORDER.
|
|
34
|
+
*/
|
|
35
|
+
private impactRank;
|
|
19
36
|
/**
|
|
20
37
|
* Calculate priority score for an issue (higher = more important)
|
|
21
38
|
*/
|
|
@@ -25,7 +42,7 @@ export declare class ResultProcessor {
|
|
|
25
42
|
*/
|
|
26
43
|
private generateFixSuggestion;
|
|
27
44
|
/**
|
|
28
|
-
* Generate user impact description
|
|
45
|
+
* Generate user impact description from engine-native impact level.
|
|
29
46
|
*/
|
|
30
47
|
private generateUserImpact;
|
|
31
48
|
/**
|
|
@@ -64,6 +81,6 @@ export declare class ResultProcessor {
|
|
|
64
81
|
/**
|
|
65
82
|
* Process accessibility results into structured audit result
|
|
66
83
|
*/
|
|
67
|
-
process(accessibilityResults: AccessibilityResults, appliedFilters?: AppliedFilters): AuditResult;
|
|
84
|
+
process(accessibilityResults: AccessibilityResults, appliedFilters?: AppliedFilters, options?: ProcessOptions): AuditResult;
|
|
68
85
|
}
|
|
69
86
|
//# sourceMappingURL=result-processor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"result-processor.d.ts","sourceRoot":"","sources":["../../src/core/result-processor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,oBAAoB,EAEpB,WAAW,EAOX,cAAc,EAEf,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"result-processor.d.ts","sourceRoot":"","sources":["../../src/core/result-processor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,oBAAoB,EAEpB,WAAW,EAOX,cAAc,EAEf,MAAM,mBAAmB,CAAA;AAG1B,wFAAwF;AACxF,MAAM,WAAW,cAAc;IAC7B,qGAAqG;IACrG,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAW3D;AAED,6GAA6G;AAC7G,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,IAAI,GAAG,KAAK,GAAG,OAAO,CAInF;AAED,0GAA0G;AAC1G,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKpD;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B;;OAEG;IACH,OAAO,CAAC,YAAY;IAKpB;;OAEG;IACH,OAAO,CAAC,cAAc;IAQtB;;OAEG;IACH,OAAO,CAAC,UAAU;IAIlB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAezB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAwC7B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAc1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAiFzB;;OAEG;IACH,OAAO,CAAC,cAAc;IAqBtB;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAmB/B;;OAEG;IACH,OAAO,CAAC,eAAe;IA0BvB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA0CzB;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAwChC;;OAEG;IACH,OAAO,CAAC,6BAA6B;IAsErC;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAoC3B;;OAEG;IACH,OAAO,CACL,oBAAoB,EAAE,oBAAoB,EAC1C,cAAc,CAAC,EAAE,cAAc,EAC/B,OAAO,CAAC,EAAE,cAAc,GACvB,WAAW;CAiDf"}
|
|
@@ -2,74 +2,85 @@
|
|
|
2
2
|
* Result Processor - Formats accessibility results into conversational, actionable format
|
|
3
3
|
* Converts raw accessibility results into structured, prioritized, and human-readable format
|
|
4
4
|
*/
|
|
5
|
+
import { IMPACT_ORDER } from '../types/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Derive full WCAG label from engine tags (e.g. wcag22aa -> "WCAG 2.2 AA").
|
|
8
|
+
* Used for display and for audit fallback.
|
|
9
|
+
*/
|
|
10
|
+
export function getWcagLabelFromTags(tags) {
|
|
11
|
+
if (!tags || tags.length === 0)
|
|
12
|
+
return 'N/A';
|
|
13
|
+
const level = tags.some((t) => t.endsWith('aaa')) ? 'AAA'
|
|
14
|
+
: tags.some((t) => t.endsWith('aa')) ? 'AA'
|
|
15
|
+
: tags.some((t) => t.endsWith('a')) ? 'A'
|
|
16
|
+
: null;
|
|
17
|
+
if (!level)
|
|
18
|
+
return 'N/A';
|
|
19
|
+
if (tags.some((t) => t.startsWith('wcag22')))
|
|
20
|
+
return `WCAG 2.2 ${level}`;
|
|
21
|
+
if (tags.some((t) => t.startsWith('wcag21')))
|
|
22
|
+
return `WCAG 2.1 ${level}`;
|
|
23
|
+
if (tags.some((t) => t.startsWith('wcag2')))
|
|
24
|
+
return `WCAG 2.0 ${level}`;
|
|
25
|
+
return `WCAG ${level}`;
|
|
26
|
+
}
|
|
27
|
+
/** True if wcagLevel string matches the given level (A, AA, AAA); supports full label e.g. "WCAG 2.2 AA". */
|
|
28
|
+
export function wcagLevelMatches(level, target) {
|
|
29
|
+
if (target === 'AAA')
|
|
30
|
+
return level === 'AAA' || level.endsWith(' AAA');
|
|
31
|
+
if (target === 'AA')
|
|
32
|
+
return level === 'AA' || (level.endsWith(' AA') && !level.endsWith(' AAA'));
|
|
33
|
+
return level === 'A' || (level.endsWith(' A') && !level.endsWith(' AA'));
|
|
34
|
+
}
|
|
35
|
+
/** Numeric order for sorting by WCAG level (A=3, AA=2, AAA=1). Supports full label e.g. "WCAG 2.2 AA". */
|
|
36
|
+
export function wcagLevelOrder(level) {
|
|
37
|
+
if (wcagLevelMatches(level, 'A'))
|
|
38
|
+
return 3;
|
|
39
|
+
if (wcagLevelMatches(level, 'AA'))
|
|
40
|
+
return 2;
|
|
41
|
+
if (wcagLevelMatches(level, 'AAA'))
|
|
42
|
+
return 1;
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
5
45
|
/**
|
|
6
46
|
* ResultProcessor class - Formats and processes accessibility results
|
|
7
47
|
*/
|
|
8
48
|
export class ResultProcessor {
|
|
9
49
|
/**
|
|
10
|
-
* Get WCAG level
|
|
50
|
+
* Get WCAG level for display (full label e.g. "WCAG 2.2 AA"). Uses tags when present, else audit fallback.
|
|
11
51
|
*/
|
|
12
|
-
getWCAGLevel(tags) {
|
|
13
|
-
if (tags
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
-
return 'AA';
|
|
17
|
-
if (tags.includes('wcag2a') || tags.includes('wcag21a'))
|
|
18
|
-
return 'A';
|
|
19
|
-
return 'N/A';
|
|
52
|
+
getWCAGLevel(tags, auditWcagLabel) {
|
|
53
|
+
if (tags && tags.length > 0)
|
|
54
|
+
return getWcagLabelFromTags(tags);
|
|
55
|
+
return auditWcagLabel ?? 'N/A';
|
|
20
56
|
}
|
|
21
57
|
/**
|
|
22
|
-
* Determine impact level from
|
|
23
|
-
* When ruleData.impact is set (e.g. from ACE), use it so output matches the browser tool.
|
|
58
|
+
* Determine impact level from engine-reported impact (axe or ACE native levels).
|
|
24
59
|
*/
|
|
25
|
-
getImpactLevel(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (category === 'contrast')
|
|
34
|
-
return 'needs-review';
|
|
35
|
-
if (ruleId.includes('label') || ruleId.includes('form'))
|
|
36
|
-
return 'needs-review';
|
|
37
|
-
if (ruleId.includes('alt'))
|
|
38
|
-
return 'violation';
|
|
39
|
-
return 'recommendation';
|
|
60
|
+
getImpactLevel(_category, _ruleId, ruleData) {
|
|
61
|
+
return ruleData?.impact ?? 'moderate';
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get impact rank for ordering (higher = worse). Uses IMPACT_ORDER.
|
|
65
|
+
*/
|
|
66
|
+
impactRank(impact) {
|
|
67
|
+
return IMPACT_ORDER[impact.toLowerCase()] ?? 2;
|
|
40
68
|
}
|
|
41
69
|
/**
|
|
42
70
|
* Calculate priority score for an issue (higher = more important)
|
|
43
71
|
*/
|
|
44
72
|
calculatePriority(issue) {
|
|
45
73
|
let priority = 0;
|
|
46
|
-
// Impact weighting (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
break;
|
|
57
|
-
case 'minor':
|
|
58
|
-
priority += 10;
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
// WCAG level weighting (AAA > AA > A)
|
|
62
|
-
switch (issue.wcagLevel) {
|
|
63
|
-
case 'AAA':
|
|
64
|
-
priority += 30;
|
|
65
|
-
break;
|
|
66
|
-
case 'AA':
|
|
67
|
-
priority += 20;
|
|
68
|
-
break;
|
|
69
|
-
case 'A':
|
|
70
|
-
priority += 10;
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
74
|
+
// Impact weighting (axe: critical/serious/moderate/minor; ACE: violation/potentialviolation/...)
|
|
75
|
+
priority += this.impactRank(issue.impact) * 25;
|
|
76
|
+
// WCAG level weighting (AAA > AA > A); support full label e.g. "WCAG 2.2 AA"
|
|
77
|
+
const level = issue.wcagLevel;
|
|
78
|
+
if (level.endsWith('AAA'))
|
|
79
|
+
priority += 30;
|
|
80
|
+
else if (level.endsWith('AA'))
|
|
81
|
+
priority += 20;
|
|
82
|
+
else if (level.endsWith('A'))
|
|
83
|
+
priority += 10;
|
|
73
84
|
return priority;
|
|
74
85
|
}
|
|
75
86
|
/**
|
|
@@ -109,16 +120,17 @@ export class ResultProcessor {
|
|
|
109
120
|
};
|
|
110
121
|
}
|
|
111
122
|
/**
|
|
112
|
-
* Generate user impact description
|
|
123
|
+
* Generate user impact description from engine-native impact level.
|
|
113
124
|
*/
|
|
114
125
|
generateUserImpact(_ruleId, impact) {
|
|
115
|
-
|
|
116
|
-
|
|
126
|
+
const rank = this.impactRank(impact);
|
|
127
|
+
if (rank >= 5) {
|
|
128
|
+
return 'This is a definite or likely accessibility failure that prevents users with disabilities from accessing content or functionality.';
|
|
117
129
|
}
|
|
118
|
-
if (
|
|
130
|
+
if (rank >= 4) {
|
|
119
131
|
return 'This potential issue needs manual review — it may significantly impact users with disabilities.';
|
|
120
132
|
}
|
|
121
|
-
if (
|
|
133
|
+
if (rank >= 3) {
|
|
122
134
|
return 'This is a best-practice recommendation that improves accessibility for users with disabilities.';
|
|
123
135
|
}
|
|
124
136
|
return 'This may cause minor inconveniences for some users.';
|
|
@@ -126,8 +138,9 @@ export class ResultProcessor {
|
|
|
126
138
|
/**
|
|
127
139
|
* Process accessibility violations into prioritized issues
|
|
128
140
|
*/
|
|
129
|
-
processViolations(violations, _appliedFilters) {
|
|
141
|
+
processViolations(violations, _appliedFilters, options) {
|
|
130
142
|
const issues = [];
|
|
143
|
+
const auditWcagLabel = options?.auditWcagLabel;
|
|
131
144
|
Object.entries(violations).forEach(([categoryKey, category]) => {
|
|
132
145
|
if (!category || !category.items) {
|
|
133
146
|
return;
|
|
@@ -144,8 +157,16 @@ export class ResultProcessor {
|
|
|
144
157
|
const element = domInfo
|
|
145
158
|
? `${domInfo.tagName || 'Unknown'}${domInfo.id ? `#${domInfo.id}` : ''}${domInfo.className ? `.${domInfo.className.replace(/\s+/g, '.')}` : ''}`
|
|
146
159
|
: xpath;
|
|
160
|
+
// Build a selector that identifies the exact element: tag + optional id + optional classes.
|
|
161
|
+
// Never leave empty: use tag when no class/id so the column always has a value.
|
|
162
|
+
const tag = domInfo?.tagName || 'element';
|
|
163
|
+
const idPart = domInfo?.id ? `#${domInfo.id}` : '';
|
|
164
|
+
const classPart = domInfo?.className != null && domInfo.className !== ''
|
|
165
|
+
? '.' + String(domInfo.className).trim().replace(/\s+/g, '.')
|
|
166
|
+
: '';
|
|
167
|
+
const classSelector = tag + idPart + classPart;
|
|
147
168
|
const tags = ruleData.tags || [];
|
|
148
|
-
const wcagLevel = this.getWCAGLevel(tags);
|
|
169
|
+
const wcagLevel = this.getWCAGLevel(tags, auditWcagLabel);
|
|
149
170
|
const impact = this.getImpactLevel(categoryKey, ruleId, ruleData);
|
|
150
171
|
const fix = this.generateFixSuggestion(ruleId, categoryKey, element, xpath, domInfo);
|
|
151
172
|
const userImpact = this.generateUserImpact(ruleId, impact);
|
|
@@ -157,6 +178,7 @@ export class ResultProcessor {
|
|
|
157
178
|
tags,
|
|
158
179
|
element,
|
|
159
180
|
xpath,
|
|
181
|
+
classSelector,
|
|
160
182
|
fix,
|
|
161
183
|
userImpact,
|
|
162
184
|
priority: 0, // Will be calculated after all issues are collected
|
|
@@ -184,22 +206,17 @@ export class ResultProcessor {
|
|
|
184
206
|
}
|
|
185
207
|
// Base score starts at 100
|
|
186
208
|
let score = 100;
|
|
187
|
-
// Deduct points based on
|
|
209
|
+
// Deduct points based on impact (axe/ACE native levels via IMPACT_ORDER)
|
|
188
210
|
issues.forEach((issue) => {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
break;
|
|
199
|
-
case 'minor':
|
|
200
|
-
score -= 0.5;
|
|
201
|
-
break;
|
|
202
|
-
}
|
|
211
|
+
const rank = this.impactRank(issue.impact);
|
|
212
|
+
if (rank >= 5)
|
|
213
|
+
score -= 5;
|
|
214
|
+
else if (rank >= 4)
|
|
215
|
+
score -= 3;
|
|
216
|
+
else if (rank >= 3)
|
|
217
|
+
score -= 1;
|
|
218
|
+
else
|
|
219
|
+
score -= 0.5;
|
|
203
220
|
});
|
|
204
221
|
// Ensure score doesn't go below 0
|
|
205
222
|
return Math.max(0, Math.round(score));
|
|
@@ -208,9 +225,9 @@ export class ResultProcessor {
|
|
|
208
225
|
* Calculate WCAG compliance percentages
|
|
209
226
|
*/
|
|
210
227
|
calculateWCAGCompliance(issues) {
|
|
211
|
-
const levelA = issues.filter((i) => i.wcagLevel
|
|
212
|
-
const levelAA = issues.filter((i) => i.wcagLevel
|
|
213
|
-
const levelAAA = issues.filter((i) => i.wcagLevel
|
|
228
|
+
const levelA = issues.filter((i) => wcagLevelMatches(i.wcagLevel, 'A'));
|
|
229
|
+
const levelAA = issues.filter((i) => wcagLevelMatches(i.wcagLevel, 'AA'));
|
|
230
|
+
const levelAAA = issues.filter((i) => wcagLevelMatches(i.wcagLevel, 'AAA'));
|
|
214
231
|
// Calculate compliance as percentage (100% - violation percentage)
|
|
215
232
|
// This is a simplified calculation - in reality, we'd need to know total criteria
|
|
216
233
|
const totalIssues = issues.length;
|
|
@@ -259,10 +276,10 @@ export class ResultProcessor {
|
|
|
259
276
|
});
|
|
260
277
|
ruleGroups.forEach((groupIssues, ruleId) => {
|
|
261
278
|
// Quick wins are issues that:
|
|
262
|
-
// 1. Have high impact (critical
|
|
279
|
+
// 1. Have high impact (axe: critical/serious; ACE: violation/potentialviolation)
|
|
263
280
|
// 2. Are easy to fix (have clear fix suggestions)
|
|
264
281
|
// 3. Affect multiple elements (batch fix opportunity)
|
|
265
|
-
const highImpactIssues = groupIssues.filter((i) =>
|
|
282
|
+
const highImpactIssues = groupIssues.filter((i) => this.impactRank(i.impact) >= 5);
|
|
266
283
|
if (highImpactIssues.length > 0 && groupIssues.length > 1) {
|
|
267
284
|
const firstIssue = groupIssues[0];
|
|
268
285
|
quickWins.push({
|
|
@@ -277,8 +294,7 @@ export class ResultProcessor {
|
|
|
277
294
|
});
|
|
278
295
|
// Sort by impact and number of affected elements
|
|
279
296
|
quickWins.sort((a, b) => {
|
|
280
|
-
const
|
|
281
|
-
const impactDiff = (impactOrder[b.impact] ?? 0) - (impactOrder[a.impact] ?? 0);
|
|
297
|
+
const impactDiff = this.impactRank(b.impact) - this.impactRank(a.impact);
|
|
282
298
|
if (impactDiff !== 0)
|
|
283
299
|
return impactDiff;
|
|
284
300
|
return b.affectedElements - a.affectedElements;
|
|
@@ -299,12 +315,10 @@ export class ResultProcessor {
|
|
|
299
315
|
ruleGroups.get(issue.ruleId).push(issue);
|
|
300
316
|
});
|
|
301
317
|
ruleGroups.forEach((groupIssues, ruleId) => {
|
|
302
|
-
// Critical blockers
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const levelAIssues = groupIssues.filter((i) => i.wcagLevel === 'A');
|
|
307
|
-
if (violationIssues.length > 0 || levelAIssues.length > 0) {
|
|
318
|
+
// Critical blockers: high impact (axe critical/serious; ACE violation/potentialviolation) or WCAG Level A
|
|
319
|
+
const highImpactIssues = groupIssues.filter((i) => this.impactRank(i.impact) >= 5);
|
|
320
|
+
const levelAIssues = groupIssues.filter((i) => wcagLevelMatches(i.wcagLevel, 'A'));
|
|
321
|
+
if (highImpactIssues.length > 0 || levelAIssues.length > 0) {
|
|
308
322
|
const firstIssue = groupIssues[0];
|
|
309
323
|
blockers.push({
|
|
310
324
|
ruleId,
|
|
@@ -316,14 +330,12 @@ export class ResultProcessor {
|
|
|
316
330
|
});
|
|
317
331
|
}
|
|
318
332
|
});
|
|
319
|
-
// Sort by WCAG level (A first) then
|
|
333
|
+
// Sort by WCAG level (A first) then impact rank
|
|
320
334
|
blockers.sort((a, b) => {
|
|
321
|
-
const
|
|
322
|
-
const levelDiff = (levelOrder[b.wcagLevel] ?? 0) - (levelOrder[a.wcagLevel] ?? 0);
|
|
335
|
+
const levelDiff = wcagLevelOrder(b.wcagLevel) - wcagLevelOrder(a.wcagLevel);
|
|
323
336
|
if (levelDiff !== 0)
|
|
324
337
|
return levelDiff;
|
|
325
|
-
|
|
326
|
-
return (impactOrder[b.impact] ?? 0) - (impactOrder[a.impact] ?? 0);
|
|
338
|
+
return this.impactRank(b.impact) - this.impactRank(a.impact);
|
|
327
339
|
});
|
|
328
340
|
return blockers;
|
|
329
341
|
}
|
|
@@ -395,12 +407,14 @@ export class ResultProcessor {
|
|
|
395
407
|
return '| Severity | Rule | Description | WCAG | Element |\n|---|---|---|---|---|\n| — | — | No issues found | — | — |';
|
|
396
408
|
}
|
|
397
409
|
const severityIcon = (impact) => {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
410
|
+
const rank = IMPACT_ORDER[impact.toLowerCase()] ?? 2;
|
|
411
|
+
if (rank >= 5)
|
|
412
|
+
return `🚫 ${impact}`;
|
|
413
|
+
if (rank >= 4)
|
|
414
|
+
return `⚠️ ${impact}`;
|
|
415
|
+
if (rank >= 3)
|
|
416
|
+
return `ℹ️ ${impact}`;
|
|
417
|
+
return impact;
|
|
404
418
|
};
|
|
405
419
|
const truncate = (s, max = 80) => s.length > max ? s.substring(0, max - 1) + '…' : s;
|
|
406
420
|
const escape = (s) => s.replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
@@ -421,11 +435,11 @@ export class ResultProcessor {
|
|
|
421
435
|
/**
|
|
422
436
|
* Process accessibility results into structured audit result
|
|
423
437
|
*/
|
|
424
|
-
process(accessibilityResults, appliedFilters) {
|
|
438
|
+
process(accessibilityResults, appliedFilters, options) {
|
|
425
439
|
// Use filtered results if available, otherwise use original
|
|
426
440
|
const violations = accessibilityResults.violations;
|
|
427
441
|
// Process violations into prioritized issues
|
|
428
|
-
const issues = this.processViolations(violations, appliedFilters);
|
|
442
|
+
const issues = this.processViolations(violations, appliedFilters, options);
|
|
429
443
|
// Calculate metrics
|
|
430
444
|
const score = this.calculateScore(issues);
|
|
431
445
|
const wcagCompliance = this.calculateWCAGCompliance(issues);
|