@kodalabs-io/eqo 1.0.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/LICENSE +21 -0
- package/README.md +162 -0
- package/action.yml +77 -0
- package/dist/chunk-WKI3N5NX.js +3258 -0
- package/dist/cli/index.js +209 -0
- package/dist/en-US-JQN64XYI.js +300 -0
- package/dist/fr-FR-NZKLCQE5.js +291 -0
- package/dist/index.d.ts +445 -0
- package/dist/index.js +941 -0
- package/dist/worker.js +9 -0
- package/package.json +115 -0
|
@@ -0,0 +1,3258 @@
|
|
|
1
|
+
// src/criteria/rgaa-4.1.2.ts
|
|
2
|
+
var theme1 = [
|
|
3
|
+
{
|
|
4
|
+
id: "1.1",
|
|
5
|
+
theme: 1,
|
|
6
|
+
level: "A",
|
|
7
|
+
wcag: ["1.1.1"],
|
|
8
|
+
automationLevel: "full",
|
|
9
|
+
tests: [
|
|
10
|
+
{ id: "1.1.1", automated: true },
|
|
11
|
+
{ id: "1.1.2", automated: true },
|
|
12
|
+
{ id: "1.1.3", automated: true },
|
|
13
|
+
{ id: "1.1.4", automated: false },
|
|
14
|
+
{ id: "1.1.5", automated: true },
|
|
15
|
+
{ id: "1.1.6", automated: true },
|
|
16
|
+
{ id: "1.1.7", automated: true },
|
|
17
|
+
{ id: "1.1.8", automated: true }
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "1.2",
|
|
22
|
+
theme: 1,
|
|
23
|
+
level: "A",
|
|
24
|
+
wcag: ["1.1.1", "4.1.2"],
|
|
25
|
+
automationLevel: "full",
|
|
26
|
+
tests: [
|
|
27
|
+
{ id: "1.2.1", automated: true },
|
|
28
|
+
{ id: "1.2.2", automated: true },
|
|
29
|
+
{ id: "1.2.3", automated: true },
|
|
30
|
+
{ id: "1.2.4", automated: true },
|
|
31
|
+
{ id: "1.2.5", automated: true },
|
|
32
|
+
{ id: "1.2.6", automated: true }
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "1.3",
|
|
37
|
+
theme: 1,
|
|
38
|
+
level: "A",
|
|
39
|
+
wcag: ["1.1.1"],
|
|
40
|
+
automationLevel: "manual",
|
|
41
|
+
tests: [
|
|
42
|
+
{ id: "1.3.1", automated: false },
|
|
43
|
+
{ id: "1.3.2", automated: false },
|
|
44
|
+
{ id: "1.3.3", automated: false },
|
|
45
|
+
{ id: "1.3.4", automated: false },
|
|
46
|
+
{ id: "1.3.5", automated: false },
|
|
47
|
+
{ id: "1.3.6", automated: false },
|
|
48
|
+
{ id: "1.3.7", automated: false }
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "1.4",
|
|
53
|
+
theme: 1,
|
|
54
|
+
level: "A",
|
|
55
|
+
wcag: ["1.1.1"],
|
|
56
|
+
automationLevel: "manual",
|
|
57
|
+
tests: [
|
|
58
|
+
{ id: "1.4.1", automated: false },
|
|
59
|
+
{ id: "1.4.2", automated: false },
|
|
60
|
+
{ id: "1.4.3", automated: false },
|
|
61
|
+
{ id: "1.4.4", automated: false },
|
|
62
|
+
{ id: "1.4.5", automated: false },
|
|
63
|
+
{ id: "1.4.6", automated: false },
|
|
64
|
+
{ id: "1.4.7", automated: false },
|
|
65
|
+
{ id: "1.4.8", automated: false }
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "1.5",
|
|
70
|
+
theme: 1,
|
|
71
|
+
level: "A",
|
|
72
|
+
wcag: ["1.1.1"],
|
|
73
|
+
automationLevel: "manual",
|
|
74
|
+
tests: [{ id: "1.5.1", automated: false }]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "1.6",
|
|
78
|
+
theme: 1,
|
|
79
|
+
level: "A",
|
|
80
|
+
wcag: ["1.1.1"],
|
|
81
|
+
automationLevel: "manual",
|
|
82
|
+
tests: [
|
|
83
|
+
{ id: "1.6.1", automated: false },
|
|
84
|
+
{ id: "1.6.2", automated: false },
|
|
85
|
+
{ id: "1.6.3", automated: false },
|
|
86
|
+
{ id: "1.6.4", automated: false },
|
|
87
|
+
{ id: "1.6.5", automated: false },
|
|
88
|
+
{ id: "1.6.6", automated: false },
|
|
89
|
+
{ id: "1.6.7", automated: false },
|
|
90
|
+
{ id: "1.6.8", automated: false }
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "1.7",
|
|
95
|
+
theme: 1,
|
|
96
|
+
level: "A",
|
|
97
|
+
wcag: ["1.1.1"],
|
|
98
|
+
automationLevel: "manual",
|
|
99
|
+
tests: [
|
|
100
|
+
{ id: "1.7.1", automated: false },
|
|
101
|
+
{ id: "1.7.2", automated: false },
|
|
102
|
+
{ id: "1.7.3", automated: false }
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: "1.8",
|
|
107
|
+
theme: 1,
|
|
108
|
+
level: "AA",
|
|
109
|
+
wcag: ["1.4.5"],
|
|
110
|
+
automationLevel: "partial",
|
|
111
|
+
tests: [
|
|
112
|
+
{ id: "1.8.1", automated: false },
|
|
113
|
+
{ id: "1.8.2", automated: false },
|
|
114
|
+
{ id: "1.8.3", automated: false },
|
|
115
|
+
{ id: "1.8.4", automated: false },
|
|
116
|
+
{ id: "1.8.5", automated: false }
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "1.9",
|
|
121
|
+
theme: 1,
|
|
122
|
+
level: "A",
|
|
123
|
+
wcag: ["1.1.1"],
|
|
124
|
+
automationLevel: "partial",
|
|
125
|
+
tests: [{ id: "1.9.1", automated: true }]
|
|
126
|
+
}
|
|
127
|
+
];
|
|
128
|
+
var theme2 = [
|
|
129
|
+
{
|
|
130
|
+
id: "2.1",
|
|
131
|
+
theme: 2,
|
|
132
|
+
level: "A",
|
|
133
|
+
wcag: ["4.1.2"],
|
|
134
|
+
automationLevel: "full",
|
|
135
|
+
tests: [{ id: "2.1.1", automated: true }]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "2.2",
|
|
139
|
+
theme: 2,
|
|
140
|
+
level: "A",
|
|
141
|
+
wcag: ["4.1.2"],
|
|
142
|
+
automationLevel: "partial",
|
|
143
|
+
tests: [{ id: "2.2.1", automated: true }]
|
|
144
|
+
}
|
|
145
|
+
];
|
|
146
|
+
var theme3 = [
|
|
147
|
+
{
|
|
148
|
+
id: "3.1",
|
|
149
|
+
theme: 3,
|
|
150
|
+
level: "A",
|
|
151
|
+
wcag: ["1.4.1"],
|
|
152
|
+
automationLevel: "manual",
|
|
153
|
+
tests: [{ id: "3.1.1", automated: false }]
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: "3.2",
|
|
157
|
+
theme: 3,
|
|
158
|
+
level: "AA",
|
|
159
|
+
wcag: ["1.4.3"],
|
|
160
|
+
automationLevel: "full",
|
|
161
|
+
tests: [
|
|
162
|
+
{ id: "3.2.1", automated: true },
|
|
163
|
+
{ id: "3.2.2", automated: true },
|
|
164
|
+
{ id: "3.2.3", automated: true },
|
|
165
|
+
{ id: "3.2.4", automated: true },
|
|
166
|
+
{ id: "3.2.5", automated: true }
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: "3.3",
|
|
171
|
+
theme: 3,
|
|
172
|
+
level: "AA",
|
|
173
|
+
wcag: ["1.4.11"],
|
|
174
|
+
automationLevel: "full",
|
|
175
|
+
tests: [{ id: "3.3.1", automated: true }]
|
|
176
|
+
}
|
|
177
|
+
];
|
|
178
|
+
var theme4 = [
|
|
179
|
+
{
|
|
180
|
+
id: "4.1",
|
|
181
|
+
theme: 4,
|
|
182
|
+
level: "A",
|
|
183
|
+
wcag: ["1.2.1"],
|
|
184
|
+
automationLevel: "manual",
|
|
185
|
+
tests: [
|
|
186
|
+
{ id: "4.1.1", automated: false },
|
|
187
|
+
{ id: "4.1.2", automated: false },
|
|
188
|
+
{ id: "4.1.3", automated: false }
|
|
189
|
+
]
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: "4.2",
|
|
193
|
+
theme: 4,
|
|
194
|
+
level: "A",
|
|
195
|
+
wcag: ["1.2.1"],
|
|
196
|
+
automationLevel: "manual",
|
|
197
|
+
tests: [
|
|
198
|
+
{ id: "4.2.1", automated: false },
|
|
199
|
+
{ id: "4.2.2", automated: false }
|
|
200
|
+
]
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: "4.3",
|
|
204
|
+
theme: 4,
|
|
205
|
+
level: "A",
|
|
206
|
+
wcag: ["1.2.2"],
|
|
207
|
+
automationLevel: "partial",
|
|
208
|
+
tests: [
|
|
209
|
+
{ id: "4.3.1", automated: true },
|
|
210
|
+
{ id: "4.3.2", automated: false }
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: "4.4",
|
|
215
|
+
theme: 4,
|
|
216
|
+
level: "AA",
|
|
217
|
+
wcag: ["1.2.9"],
|
|
218
|
+
automationLevel: "manual",
|
|
219
|
+
tests: [{ id: "4.4.1", automated: false }]
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: "4.5",
|
|
223
|
+
theme: 4,
|
|
224
|
+
level: "A",
|
|
225
|
+
wcag: ["1.2.3", "1.2.5"],
|
|
226
|
+
automationLevel: "manual",
|
|
227
|
+
tests: [
|
|
228
|
+
{ id: "4.5.1", automated: false },
|
|
229
|
+
{ id: "4.5.2", automated: false }
|
|
230
|
+
]
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: "4.6",
|
|
234
|
+
theme: 4,
|
|
235
|
+
level: "AA",
|
|
236
|
+
wcag: ["1.2.5"],
|
|
237
|
+
automationLevel: "manual",
|
|
238
|
+
tests: [{ id: "4.6.1", automated: false }]
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: "4.7",
|
|
242
|
+
theme: 4,
|
|
243
|
+
level: "A",
|
|
244
|
+
wcag: ["1.1.1"],
|
|
245
|
+
automationLevel: "partial",
|
|
246
|
+
tests: [{ id: "4.7.1", automated: true }]
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: "4.8",
|
|
250
|
+
theme: 4,
|
|
251
|
+
level: "A",
|
|
252
|
+
wcag: ["1.1.1"],
|
|
253
|
+
automationLevel: "manual",
|
|
254
|
+
tests: [
|
|
255
|
+
{ id: "4.8.1", automated: false },
|
|
256
|
+
{ id: "4.8.2", automated: false }
|
|
257
|
+
]
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
id: "4.9",
|
|
261
|
+
theme: 4,
|
|
262
|
+
level: "A",
|
|
263
|
+
wcag: ["1.1.1"],
|
|
264
|
+
automationLevel: "manual",
|
|
265
|
+
tests: [{ id: "4.9.1", automated: false }]
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: "4.10",
|
|
269
|
+
theme: 4,
|
|
270
|
+
level: "A",
|
|
271
|
+
wcag: ["1.4.2"],
|
|
272
|
+
automationLevel: "partial",
|
|
273
|
+
tests: [{ id: "4.10.1", automated: true }]
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: "4.11",
|
|
277
|
+
theme: 4,
|
|
278
|
+
level: "A",
|
|
279
|
+
wcag: ["2.1.1"],
|
|
280
|
+
automationLevel: "partial",
|
|
281
|
+
tests: [
|
|
282
|
+
{ id: "4.11.1", automated: false },
|
|
283
|
+
{ id: "4.11.2", automated: false }
|
|
284
|
+
]
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
id: "4.12",
|
|
288
|
+
theme: 4,
|
|
289
|
+
level: "A",
|
|
290
|
+
wcag: ["2.1.1"],
|
|
291
|
+
automationLevel: "partial",
|
|
292
|
+
tests: [
|
|
293
|
+
{ id: "4.12.1", automated: false },
|
|
294
|
+
{ id: "4.12.2", automated: false }
|
|
295
|
+
]
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
id: "4.13",
|
|
299
|
+
theme: 4,
|
|
300
|
+
level: "A",
|
|
301
|
+
wcag: ["4.1.2"],
|
|
302
|
+
automationLevel: "manual",
|
|
303
|
+
tests: [
|
|
304
|
+
{ id: "4.13.1", automated: false },
|
|
305
|
+
{ id: "4.13.2", automated: false }
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
];
|
|
309
|
+
var theme5 = [
|
|
310
|
+
{
|
|
311
|
+
id: "5.1",
|
|
312
|
+
theme: 5,
|
|
313
|
+
level: "A",
|
|
314
|
+
wcag: ["1.3.1"],
|
|
315
|
+
automationLevel: "partial",
|
|
316
|
+
tests: [{ id: "5.1.1", automated: true }]
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: "5.2",
|
|
320
|
+
theme: 5,
|
|
321
|
+
level: "A",
|
|
322
|
+
wcag: ["1.3.1"],
|
|
323
|
+
automationLevel: "manual",
|
|
324
|
+
tests: [{ id: "5.2.1", automated: false }]
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: "5.3",
|
|
328
|
+
theme: 5,
|
|
329
|
+
level: "A",
|
|
330
|
+
wcag: ["1.3.1", "1.3.2"],
|
|
331
|
+
automationLevel: "partial",
|
|
332
|
+
tests: [{ id: "5.3.1", automated: false }]
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
id: "5.4",
|
|
336
|
+
theme: 5,
|
|
337
|
+
level: "A",
|
|
338
|
+
wcag: ["1.3.1"],
|
|
339
|
+
automationLevel: "full",
|
|
340
|
+
tests: [{ id: "5.4.1", automated: true }]
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
id: "5.5",
|
|
344
|
+
theme: 5,
|
|
345
|
+
level: "A",
|
|
346
|
+
wcag: ["1.3.1"],
|
|
347
|
+
automationLevel: "manual",
|
|
348
|
+
tests: [{ id: "5.5.1", automated: false }]
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
id: "5.6",
|
|
352
|
+
theme: 5,
|
|
353
|
+
level: "A",
|
|
354
|
+
wcag: ["1.3.1"],
|
|
355
|
+
automationLevel: "full",
|
|
356
|
+
tests: [
|
|
357
|
+
{ id: "5.6.1", automated: true },
|
|
358
|
+
{ id: "5.6.2", automated: true }
|
|
359
|
+
]
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
id: "5.7",
|
|
363
|
+
theme: 5,
|
|
364
|
+
level: "A",
|
|
365
|
+
wcag: ["1.3.1"],
|
|
366
|
+
automationLevel: "full",
|
|
367
|
+
tests: [
|
|
368
|
+
{ id: "5.7.1", automated: true },
|
|
369
|
+
{ id: "5.7.2", automated: true },
|
|
370
|
+
{ id: "5.7.3", automated: true }
|
|
371
|
+
]
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
id: "5.8",
|
|
375
|
+
theme: 5,
|
|
376
|
+
level: "A",
|
|
377
|
+
wcag: ["1.3.1"],
|
|
378
|
+
automationLevel: "full",
|
|
379
|
+
tests: [{ id: "5.8.1", automated: true }]
|
|
380
|
+
}
|
|
381
|
+
];
|
|
382
|
+
var theme6 = [
|
|
383
|
+
{
|
|
384
|
+
id: "6.1",
|
|
385
|
+
theme: 6,
|
|
386
|
+
level: "A",
|
|
387
|
+
wcag: ["2.4.4", "2.4.9"],
|
|
388
|
+
automationLevel: "partial",
|
|
389
|
+
tests: [
|
|
390
|
+
{ id: "6.1.1", automated: true },
|
|
391
|
+
{ id: "6.1.2", automated: true },
|
|
392
|
+
{ id: "6.1.3", automated: true },
|
|
393
|
+
{ id: "6.1.4", automated: true },
|
|
394
|
+
{ id: "6.1.5", automated: false }
|
|
395
|
+
]
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
id: "6.2",
|
|
399
|
+
theme: 6,
|
|
400
|
+
level: "A",
|
|
401
|
+
wcag: ["4.1.2"],
|
|
402
|
+
automationLevel: "full",
|
|
403
|
+
tests: [{ id: "6.2.1", automated: true }]
|
|
404
|
+
}
|
|
405
|
+
];
|
|
406
|
+
var theme7 = [
|
|
407
|
+
{
|
|
408
|
+
id: "7.1",
|
|
409
|
+
theme: 7,
|
|
410
|
+
level: "A",
|
|
411
|
+
wcag: ["4.1.2"],
|
|
412
|
+
automationLevel: "partial",
|
|
413
|
+
tests: [
|
|
414
|
+
{ id: "7.1.1", automated: false },
|
|
415
|
+
{ id: "7.1.2", automated: false },
|
|
416
|
+
{ id: "7.1.3", automated: false }
|
|
417
|
+
]
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
id: "7.2",
|
|
421
|
+
theme: 7,
|
|
422
|
+
level: "A",
|
|
423
|
+
wcag: ["4.1.2"],
|
|
424
|
+
automationLevel: "manual",
|
|
425
|
+
tests: [{ id: "7.2.1", automated: false }]
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
id: "7.3",
|
|
429
|
+
theme: 7,
|
|
430
|
+
level: "A",
|
|
431
|
+
wcag: ["2.1.1"],
|
|
432
|
+
automationLevel: "partial",
|
|
433
|
+
tests: [
|
|
434
|
+
{ id: "7.3.1", automated: false },
|
|
435
|
+
{ id: "7.3.2", automated: false }
|
|
436
|
+
]
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
id: "7.4",
|
|
440
|
+
theme: 7,
|
|
441
|
+
level: "A",
|
|
442
|
+
wcag: ["3.2.1", "3.2.2"],
|
|
443
|
+
automationLevel: "manual",
|
|
444
|
+
tests: [{ id: "7.4.1", automated: false }]
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
id: "7.5",
|
|
448
|
+
theme: 7,
|
|
449
|
+
level: "AA",
|
|
450
|
+
wcag: ["4.1.3"],
|
|
451
|
+
automationLevel: "full",
|
|
452
|
+
tests: [
|
|
453
|
+
{ id: "7.5.1", automated: true },
|
|
454
|
+
{ id: "7.5.2", automated: true },
|
|
455
|
+
{ id: "7.5.3", automated: true }
|
|
456
|
+
]
|
|
457
|
+
}
|
|
458
|
+
];
|
|
459
|
+
var theme8 = [
|
|
460
|
+
{
|
|
461
|
+
id: "8.1",
|
|
462
|
+
theme: 8,
|
|
463
|
+
level: "A",
|
|
464
|
+
wcag: ["4.1.1"],
|
|
465
|
+
automationLevel: "full",
|
|
466
|
+
tests: [
|
|
467
|
+
{ id: "8.1.1", automated: true },
|
|
468
|
+
{ id: "8.1.2", automated: true },
|
|
469
|
+
{ id: "8.1.3", automated: true }
|
|
470
|
+
]
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
id: "8.2",
|
|
474
|
+
theme: 8,
|
|
475
|
+
level: "A",
|
|
476
|
+
wcag: ["4.1.1", "4.1.2"],
|
|
477
|
+
automationLevel: "full",
|
|
478
|
+
tests: [{ id: "8.2.1", automated: true }]
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
id: "8.3",
|
|
482
|
+
theme: 8,
|
|
483
|
+
level: "A",
|
|
484
|
+
wcag: ["3.1.1"],
|
|
485
|
+
automationLevel: "full",
|
|
486
|
+
tests: [{ id: "8.3.1", automated: true }]
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
id: "8.4",
|
|
490
|
+
theme: 8,
|
|
491
|
+
level: "A",
|
|
492
|
+
wcag: ["3.1.1"],
|
|
493
|
+
automationLevel: "full",
|
|
494
|
+
tests: [{ id: "8.4.1", automated: true }]
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
id: "8.5",
|
|
498
|
+
theme: 8,
|
|
499
|
+
level: "A",
|
|
500
|
+
wcag: ["2.4.2"],
|
|
501
|
+
automationLevel: "full",
|
|
502
|
+
tests: [{ id: "8.5.1", automated: true }]
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
id: "8.6",
|
|
506
|
+
theme: 8,
|
|
507
|
+
level: "A",
|
|
508
|
+
wcag: ["2.4.2"],
|
|
509
|
+
automationLevel: "partial",
|
|
510
|
+
tests: [{ id: "8.6.1", automated: true }]
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
id: "8.7",
|
|
514
|
+
theme: 8,
|
|
515
|
+
level: "AA",
|
|
516
|
+
wcag: ["3.1.2"],
|
|
517
|
+
automationLevel: "partial",
|
|
518
|
+
tests: [{ id: "8.7.1", automated: true }]
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
id: "8.8",
|
|
522
|
+
theme: 8,
|
|
523
|
+
level: "AA",
|
|
524
|
+
wcag: ["3.1.2"],
|
|
525
|
+
automationLevel: "partial",
|
|
526
|
+
tests: [{ id: "8.8.1", automated: true }]
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
id: "8.9",
|
|
530
|
+
theme: 8,
|
|
531
|
+
level: "A",
|
|
532
|
+
wcag: ["1.3.1"],
|
|
533
|
+
automationLevel: "partial",
|
|
534
|
+
tests: [{ id: "8.9.1", automated: true }]
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
id: "8.10",
|
|
538
|
+
theme: 8,
|
|
539
|
+
level: "A",
|
|
540
|
+
wcag: ["1.3.2"],
|
|
541
|
+
automationLevel: "partial",
|
|
542
|
+
tests: [{ id: "8.10.1", automated: true }]
|
|
543
|
+
}
|
|
544
|
+
];
|
|
545
|
+
var theme9 = [
|
|
546
|
+
{
|
|
547
|
+
id: "9.1",
|
|
548
|
+
theme: 9,
|
|
549
|
+
level: "A",
|
|
550
|
+
wcag: ["1.3.1", "2.4.6"],
|
|
551
|
+
automationLevel: "full",
|
|
552
|
+
tests: [
|
|
553
|
+
{ id: "9.1.1", automated: true },
|
|
554
|
+
{ id: "9.1.2", automated: true },
|
|
555
|
+
{ id: "9.1.3", automated: true }
|
|
556
|
+
]
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
id: "9.2",
|
|
560
|
+
theme: 9,
|
|
561
|
+
level: "A",
|
|
562
|
+
wcag: ["1.3.1"],
|
|
563
|
+
automationLevel: "partial",
|
|
564
|
+
tests: [
|
|
565
|
+
{ id: "9.2.1", automated: true },
|
|
566
|
+
{ id: "9.2.2", automated: true }
|
|
567
|
+
]
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
id: "9.3",
|
|
571
|
+
theme: 9,
|
|
572
|
+
level: "A",
|
|
573
|
+
wcag: ["1.3.1"],
|
|
574
|
+
automationLevel: "full",
|
|
575
|
+
tests: [{ id: "9.3.1", automated: true }]
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
id: "9.4",
|
|
579
|
+
theme: 9,
|
|
580
|
+
level: "A",
|
|
581
|
+
wcag: ["1.3.1"],
|
|
582
|
+
automationLevel: "partial",
|
|
583
|
+
tests: [{ id: "9.4.1", automated: true }]
|
|
584
|
+
}
|
|
585
|
+
];
|
|
586
|
+
var theme10 = [
|
|
587
|
+
{
|
|
588
|
+
id: "10.1",
|
|
589
|
+
theme: 10,
|
|
590
|
+
level: "A",
|
|
591
|
+
wcag: ["1.3.3"],
|
|
592
|
+
automationLevel: "partial",
|
|
593
|
+
tests: [
|
|
594
|
+
{ id: "10.1.1", automated: false },
|
|
595
|
+
{ id: "10.1.2", automated: false },
|
|
596
|
+
{ id: "10.1.3", automated: false }
|
|
597
|
+
]
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
id: "10.2",
|
|
601
|
+
theme: 10,
|
|
602
|
+
level: "A",
|
|
603
|
+
wcag: ["1.3.3"],
|
|
604
|
+
automationLevel: "manual",
|
|
605
|
+
tests: [{ id: "10.2.1", automated: false }]
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
id: "10.3",
|
|
609
|
+
theme: 10,
|
|
610
|
+
level: "A",
|
|
611
|
+
wcag: ["1.3.2"],
|
|
612
|
+
automationLevel: "manual",
|
|
613
|
+
tests: [{ id: "10.3.1", automated: false }]
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
id: "10.4",
|
|
617
|
+
theme: 10,
|
|
618
|
+
level: "AA",
|
|
619
|
+
wcag: ["1.4.4"],
|
|
620
|
+
automationLevel: "full",
|
|
621
|
+
tests: [
|
|
622
|
+
{ id: "10.4.1", automated: true },
|
|
623
|
+
{ id: "10.4.2", automated: true },
|
|
624
|
+
{ id: "10.4.3", automated: true }
|
|
625
|
+
]
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
id: "10.5",
|
|
629
|
+
theme: 10,
|
|
630
|
+
level: "A",
|
|
631
|
+
wcag: ["1.4.8"],
|
|
632
|
+
automationLevel: "partial",
|
|
633
|
+
tests: [{ id: "10.5.1", automated: true }]
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
id: "10.6",
|
|
637
|
+
theme: 10,
|
|
638
|
+
level: "A",
|
|
639
|
+
wcag: ["1.4.1"],
|
|
640
|
+
automationLevel: "partial",
|
|
641
|
+
tests: [{ id: "10.6.1", automated: true }]
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
id: "10.7",
|
|
645
|
+
theme: 10,
|
|
646
|
+
level: "AA",
|
|
647
|
+
wcag: ["2.4.7"],
|
|
648
|
+
automationLevel: "full",
|
|
649
|
+
tests: [{ id: "10.7.1", automated: true }]
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
id: "10.8",
|
|
653
|
+
theme: 10,
|
|
654
|
+
level: "A",
|
|
655
|
+
wcag: ["1.3.2", "4.1.2"],
|
|
656
|
+
automationLevel: "partial",
|
|
657
|
+
tests: [
|
|
658
|
+
{ id: "10.8.1", automated: true },
|
|
659
|
+
{ id: "10.8.2", automated: true }
|
|
660
|
+
]
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
id: "10.9",
|
|
664
|
+
theme: 10,
|
|
665
|
+
level: "A",
|
|
666
|
+
wcag: ["1.3.3"],
|
|
667
|
+
automationLevel: "manual",
|
|
668
|
+
tests: [{ id: "10.9.1", automated: false }]
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
id: "10.10",
|
|
672
|
+
theme: 10,
|
|
673
|
+
level: "A",
|
|
674
|
+
wcag: ["1.3.3"],
|
|
675
|
+
automationLevel: "manual",
|
|
676
|
+
tests: [{ id: "10.10.1", automated: false }]
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
id: "10.11",
|
|
680
|
+
theme: 10,
|
|
681
|
+
level: "AA",
|
|
682
|
+
wcag: ["1.4.10"],
|
|
683
|
+
automationLevel: "partial",
|
|
684
|
+
tests: [
|
|
685
|
+
{ id: "10.11.1", automated: true },
|
|
686
|
+
{ id: "10.11.2", automated: true }
|
|
687
|
+
]
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
id: "10.12",
|
|
691
|
+
theme: 10,
|
|
692
|
+
level: "AA",
|
|
693
|
+
wcag: ["1.4.12"],
|
|
694
|
+
automationLevel: "full",
|
|
695
|
+
tests: [{ id: "10.12.1", automated: true }]
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
id: "10.13",
|
|
699
|
+
theme: 10,
|
|
700
|
+
level: "AA",
|
|
701
|
+
wcag: ["1.4.13"],
|
|
702
|
+
automationLevel: "partial",
|
|
703
|
+
tests: [
|
|
704
|
+
{ id: "10.13.1", automated: false },
|
|
705
|
+
{ id: "10.13.2", automated: false },
|
|
706
|
+
{ id: "10.13.3", automated: false }
|
|
707
|
+
]
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
id: "10.14",
|
|
711
|
+
theme: 10,
|
|
712
|
+
level: "A",
|
|
713
|
+
wcag: ["1.4.13"],
|
|
714
|
+
automationLevel: "partial",
|
|
715
|
+
tests: [
|
|
716
|
+
{ id: "10.14.1", automated: false },
|
|
717
|
+
{ id: "10.14.2", automated: false }
|
|
718
|
+
]
|
|
719
|
+
}
|
|
720
|
+
];
|
|
721
|
+
var theme11 = [
|
|
722
|
+
{
|
|
723
|
+
id: "11.1",
|
|
724
|
+
theme: 11,
|
|
725
|
+
level: "A",
|
|
726
|
+
wcag: ["1.3.1", "3.3.2"],
|
|
727
|
+
automationLevel: "full",
|
|
728
|
+
tests: [
|
|
729
|
+
{ id: "11.1.1", automated: true },
|
|
730
|
+
{ id: "11.1.2", automated: true },
|
|
731
|
+
{ id: "11.1.3", automated: true }
|
|
732
|
+
]
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
id: "11.2",
|
|
736
|
+
theme: 11,
|
|
737
|
+
level: "A",
|
|
738
|
+
wcag: ["2.4.6", "3.3.2"],
|
|
739
|
+
automationLevel: "manual",
|
|
740
|
+
tests: [
|
|
741
|
+
{ id: "11.2.1", automated: false },
|
|
742
|
+
{ id: "11.2.2", automated: false },
|
|
743
|
+
{ id: "11.2.3", automated: false },
|
|
744
|
+
{ id: "11.2.4", automated: false },
|
|
745
|
+
{ id: "11.2.5", automated: false }
|
|
746
|
+
]
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
id: "11.3",
|
|
750
|
+
theme: 11,
|
|
751
|
+
level: "A",
|
|
752
|
+
wcag: ["1.3.1"],
|
|
753
|
+
automationLevel: "partial",
|
|
754
|
+
tests: [{ id: "11.3.1", automated: false }]
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
id: "11.4",
|
|
758
|
+
theme: 11,
|
|
759
|
+
level: "A",
|
|
760
|
+
wcag: ["1.3.1", "3.3.2"],
|
|
761
|
+
automationLevel: "partial",
|
|
762
|
+
tests: [
|
|
763
|
+
{ id: "11.4.1", automated: false },
|
|
764
|
+
{ id: "11.4.2", automated: false },
|
|
765
|
+
{ id: "11.4.3", automated: false }
|
|
766
|
+
]
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
id: "11.5",
|
|
770
|
+
theme: 11,
|
|
771
|
+
level: "A",
|
|
772
|
+
wcag: ["1.3.1"],
|
|
773
|
+
automationLevel: "partial",
|
|
774
|
+
tests: [{ id: "11.5.1", automated: true }]
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
id: "11.6",
|
|
778
|
+
theme: 11,
|
|
779
|
+
level: "A",
|
|
780
|
+
wcag: ["1.3.1"],
|
|
781
|
+
automationLevel: "full",
|
|
782
|
+
tests: [
|
|
783
|
+
{ id: "11.6.1", automated: true },
|
|
784
|
+
{ id: "11.6.2", automated: true }
|
|
785
|
+
]
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
id: "11.7",
|
|
789
|
+
theme: 11,
|
|
790
|
+
level: "A",
|
|
791
|
+
wcag: ["1.3.1"],
|
|
792
|
+
automationLevel: "manual",
|
|
793
|
+
tests: [{ id: "11.7.1", automated: false }]
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
id: "11.8",
|
|
797
|
+
theme: 11,
|
|
798
|
+
level: "A",
|
|
799
|
+
wcag: ["1.3.1"],
|
|
800
|
+
automationLevel: "partial",
|
|
801
|
+
tests: [
|
|
802
|
+
{ id: "11.8.1", automated: true },
|
|
803
|
+
{ id: "11.8.2", automated: true }
|
|
804
|
+
]
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
id: "11.9",
|
|
808
|
+
theme: 11,
|
|
809
|
+
level: "A",
|
|
810
|
+
wcag: ["1.3.1", "2.4.6"],
|
|
811
|
+
automationLevel: "partial",
|
|
812
|
+
tests: [
|
|
813
|
+
{ id: "11.9.1", automated: true },
|
|
814
|
+
{ id: "11.9.2", automated: true }
|
|
815
|
+
]
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
id: "11.10",
|
|
819
|
+
theme: 11,
|
|
820
|
+
level: "A",
|
|
821
|
+
wcag: ["3.3.1", "3.3.3"],
|
|
822
|
+
automationLevel: "partial",
|
|
823
|
+
tests: [
|
|
824
|
+
{ id: "11.10.1", automated: false },
|
|
825
|
+
{ id: "11.10.2", automated: false },
|
|
826
|
+
{ id: "11.10.3", automated: false },
|
|
827
|
+
{ id: "11.10.4", automated: false }
|
|
828
|
+
]
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
id: "11.11",
|
|
832
|
+
theme: 11,
|
|
833
|
+
level: "A",
|
|
834
|
+
wcag: ["3.3.3"],
|
|
835
|
+
automationLevel: "partial",
|
|
836
|
+
tests: [
|
|
837
|
+
{ id: "11.11.1", automated: false },
|
|
838
|
+
{ id: "11.11.2", automated: false }
|
|
839
|
+
]
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
id: "11.12",
|
|
843
|
+
theme: 11,
|
|
844
|
+
level: "AA",
|
|
845
|
+
wcag: ["3.3.4"],
|
|
846
|
+
automationLevel: "manual",
|
|
847
|
+
tests: [
|
|
848
|
+
{ id: "11.12.1", automated: false },
|
|
849
|
+
{ id: "11.12.2", automated: false }
|
|
850
|
+
]
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
id: "11.13",
|
|
854
|
+
theme: 11,
|
|
855
|
+
level: "AA",
|
|
856
|
+
wcag: ["1.3.5"],
|
|
857
|
+
automationLevel: "full",
|
|
858
|
+
tests: [{ id: "11.13.1", automated: true }]
|
|
859
|
+
}
|
|
860
|
+
];
|
|
861
|
+
var theme12 = [
|
|
862
|
+
{
|
|
863
|
+
id: "12.1",
|
|
864
|
+
theme: 12,
|
|
865
|
+
level: "AA",
|
|
866
|
+
wcag: ["2.4.5"],
|
|
867
|
+
automationLevel: "partial",
|
|
868
|
+
tests: [{ id: "12.1.1", automated: false }]
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
id: "12.2",
|
|
872
|
+
theme: 12,
|
|
873
|
+
level: "AA",
|
|
874
|
+
wcag: ["3.2.3"],
|
|
875
|
+
automationLevel: "partial",
|
|
876
|
+
tests: [
|
|
877
|
+
{ id: "12.2.1", automated: false },
|
|
878
|
+
{ id: "12.2.2", automated: false }
|
|
879
|
+
]
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
id: "12.3",
|
|
883
|
+
theme: 12,
|
|
884
|
+
level: "AA",
|
|
885
|
+
wcag: ["2.4.5"],
|
|
886
|
+
automationLevel: "manual",
|
|
887
|
+
tests: [{ id: "12.3.1", automated: false }]
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
id: "12.4",
|
|
891
|
+
theme: 12,
|
|
892
|
+
level: "AA",
|
|
893
|
+
wcag: ["2.4.5"],
|
|
894
|
+
automationLevel: "partial",
|
|
895
|
+
tests: [{ id: "12.4.1", automated: false }]
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
id: "12.5",
|
|
899
|
+
theme: 12,
|
|
900
|
+
level: "AA",
|
|
901
|
+
wcag: ["2.4.5"],
|
|
902
|
+
automationLevel: "partial",
|
|
903
|
+
tests: [{ id: "12.5.1", automated: false }]
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
id: "12.6",
|
|
907
|
+
theme: 12,
|
|
908
|
+
level: "A",
|
|
909
|
+
wcag: ["1.3.1", "2.4.1"],
|
|
910
|
+
automationLevel: "full",
|
|
911
|
+
tests: [
|
|
912
|
+
{ id: "12.6.1", automated: true },
|
|
913
|
+
{ id: "12.6.2", automated: true }
|
|
914
|
+
]
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
id: "12.7",
|
|
918
|
+
theme: 12,
|
|
919
|
+
level: "A",
|
|
920
|
+
wcag: ["2.4.1"],
|
|
921
|
+
automationLevel: "full",
|
|
922
|
+
tests: [{ id: "12.7.1", automated: true }]
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
id: "12.8",
|
|
926
|
+
theme: 12,
|
|
927
|
+
level: "A",
|
|
928
|
+
wcag: ["2.4.3"],
|
|
929
|
+
automationLevel: "partial",
|
|
930
|
+
tests: [{ id: "12.8.1", automated: true }]
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
id: "12.9",
|
|
934
|
+
theme: 12,
|
|
935
|
+
level: "A",
|
|
936
|
+
wcag: ["2.1.2"],
|
|
937
|
+
automationLevel: "full",
|
|
938
|
+
tests: [{ id: "12.9.1", automated: true }]
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
id: "12.10",
|
|
942
|
+
theme: 12,
|
|
943
|
+
level: "A",
|
|
944
|
+
wcag: ["2.1.4"],
|
|
945
|
+
automationLevel: "manual",
|
|
946
|
+
tests: [
|
|
947
|
+
{ id: "12.10.1", automated: false },
|
|
948
|
+
{ id: "12.10.2", automated: false },
|
|
949
|
+
{ id: "12.10.3", automated: false }
|
|
950
|
+
]
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
id: "12.11",
|
|
954
|
+
theme: 12,
|
|
955
|
+
level: "AA",
|
|
956
|
+
wcag: ["1.4.13"],
|
|
957
|
+
automationLevel: "partial",
|
|
958
|
+
tests: [
|
|
959
|
+
{ id: "12.11.1", automated: false },
|
|
960
|
+
{ id: "12.11.2", automated: false },
|
|
961
|
+
{ id: "12.11.3", automated: false }
|
|
962
|
+
]
|
|
963
|
+
}
|
|
964
|
+
];
|
|
965
|
+
var theme13 = [
|
|
966
|
+
{
|
|
967
|
+
id: "13.1",
|
|
968
|
+
theme: 13,
|
|
969
|
+
level: "A",
|
|
970
|
+
wcag: ["2.2.1"],
|
|
971
|
+
automationLevel: "manual",
|
|
972
|
+
tests: [
|
|
973
|
+
{ id: "13.1.1", automated: false },
|
|
974
|
+
{ id: "13.1.2", automated: false },
|
|
975
|
+
{ id: "13.1.3", automated: false },
|
|
976
|
+
{ id: "13.1.4", automated: false }
|
|
977
|
+
]
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
id: "13.2",
|
|
981
|
+
theme: 13,
|
|
982
|
+
level: "A",
|
|
983
|
+
wcag: ["3.2.2"],
|
|
984
|
+
automationLevel: "partial",
|
|
985
|
+
tests: [{ id: "13.2.1", automated: true }]
|
|
986
|
+
},
|
|
987
|
+
{
|
|
988
|
+
id: "13.3",
|
|
989
|
+
theme: 13,
|
|
990
|
+
level: "A",
|
|
991
|
+
wcag: ["1.1.1"],
|
|
992
|
+
automationLevel: "manual",
|
|
993
|
+
tests: [{ id: "13.3.1", automated: false }]
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
id: "13.4",
|
|
997
|
+
theme: 13,
|
|
998
|
+
level: "A",
|
|
999
|
+
wcag: ["1.1.1"],
|
|
1000
|
+
automationLevel: "partial",
|
|
1001
|
+
tests: [{ id: "13.4.1", automated: false }]
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
id: "13.5",
|
|
1005
|
+
theme: 13,
|
|
1006
|
+
level: "A",
|
|
1007
|
+
wcag: ["1.1.1"],
|
|
1008
|
+
automationLevel: "partial",
|
|
1009
|
+
tests: [{ id: "13.5.1", automated: true }]
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
id: "13.6",
|
|
1013
|
+
theme: 13,
|
|
1014
|
+
level: "A",
|
|
1015
|
+
wcag: ["1.1.1"],
|
|
1016
|
+
automationLevel: "partial",
|
|
1017
|
+
tests: [{ id: "13.6.1", automated: false }]
|
|
1018
|
+
},
|
|
1019
|
+
{
|
|
1020
|
+
id: "13.7",
|
|
1021
|
+
theme: 13,
|
|
1022
|
+
level: "A",
|
|
1023
|
+
wcag: ["2.3.1"],
|
|
1024
|
+
automationLevel: "manual",
|
|
1025
|
+
tests: [{ id: "13.7.1", automated: false }]
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
id: "13.8",
|
|
1029
|
+
theme: 13,
|
|
1030
|
+
level: "A",
|
|
1031
|
+
wcag: ["2.2.2"],
|
|
1032
|
+
automationLevel: "partial",
|
|
1033
|
+
tests: [
|
|
1034
|
+
{ id: "13.8.1", automated: true },
|
|
1035
|
+
{ id: "13.8.2", automated: true }
|
|
1036
|
+
]
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
id: "13.9",
|
|
1040
|
+
theme: 13,
|
|
1041
|
+
level: "AA",
|
|
1042
|
+
wcag: ["1.3.4"],
|
|
1043
|
+
automationLevel: "partial",
|
|
1044
|
+
tests: [{ id: "13.9.1", automated: true }]
|
|
1045
|
+
},
|
|
1046
|
+
{
|
|
1047
|
+
id: "13.10",
|
|
1048
|
+
theme: 13,
|
|
1049
|
+
level: "A",
|
|
1050
|
+
wcag: ["2.5.1"],
|
|
1051
|
+
automationLevel: "manual",
|
|
1052
|
+
tests: [
|
|
1053
|
+
{ id: "13.10.1", automated: false },
|
|
1054
|
+
{ id: "13.10.2", automated: false }
|
|
1055
|
+
]
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
id: "13.11",
|
|
1059
|
+
theme: 13,
|
|
1060
|
+
level: "A",
|
|
1061
|
+
wcag: ["2.5.2"],
|
|
1062
|
+
automationLevel: "partial",
|
|
1063
|
+
tests: [{ id: "13.11.1", automated: true }]
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
id: "13.12",
|
|
1067
|
+
theme: 13,
|
|
1068
|
+
level: "A",
|
|
1069
|
+
wcag: ["2.5.4"],
|
|
1070
|
+
automationLevel: "manual",
|
|
1071
|
+
tests: [
|
|
1072
|
+
{ id: "13.12.1", automated: false },
|
|
1073
|
+
{ id: "13.12.2", automated: false },
|
|
1074
|
+
{ id: "13.12.3", automated: false }
|
|
1075
|
+
]
|
|
1076
|
+
}
|
|
1077
|
+
];
|
|
1078
|
+
var THEMES = [
|
|
1079
|
+
{ id: 1, criteria: theme1 },
|
|
1080
|
+
{ id: 2, criteria: theme2 },
|
|
1081
|
+
{ id: 3, criteria: theme3 },
|
|
1082
|
+
{ id: 4, criteria: theme4 },
|
|
1083
|
+
{ id: 5, criteria: theme5 },
|
|
1084
|
+
{ id: 6, criteria: theme6 },
|
|
1085
|
+
{ id: 7, criteria: theme7 },
|
|
1086
|
+
{ id: 8, criteria: theme8 },
|
|
1087
|
+
{ id: 9, criteria: theme9 },
|
|
1088
|
+
{ id: 10, criteria: theme10 },
|
|
1089
|
+
{ id: 11, criteria: theme11 },
|
|
1090
|
+
{ id: 12, criteria: theme12 },
|
|
1091
|
+
{ id: 13, criteria: theme13 }
|
|
1092
|
+
];
|
|
1093
|
+
var ALL_CRITERIA = THEMES.flatMap((t) => t.criteria);
|
|
1094
|
+
var CRITERIA_BY_ID = Object.fromEntries(
|
|
1095
|
+
ALL_CRITERIA.map((c) => [c.id, c])
|
|
1096
|
+
);
|
|
1097
|
+
var TOTAL_CRITERIA = ALL_CRITERIA.length;
|
|
1098
|
+
|
|
1099
|
+
// src/utils/log.ts
|
|
1100
|
+
function warn(module, message) {
|
|
1101
|
+
console.warn(`[eqo:${module}] ${message}`);
|
|
1102
|
+
}
|
|
1103
|
+
function error(module, message) {
|
|
1104
|
+
console.error(`[eqo:${module}] ${message}`);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// src/analyzer/mapper/axe-to-rgaa.ts
|
|
1108
|
+
var AXE_TEST_ID_PREFIX = "axe/";
|
|
1109
|
+
var AXE_TO_RGAA = {
|
|
1110
|
+
// ── Theme 1: Images ──────────────────────────────────────────────────────
|
|
1111
|
+
"image-alt": ["1.1"],
|
|
1112
|
+
"area-alt": ["1.1"],
|
|
1113
|
+
"input-image-alt": ["1.1"],
|
|
1114
|
+
"role-img-alt": ["1.1"],
|
|
1115
|
+
"image-redundant-alt": ["1.3"],
|
|
1116
|
+
"object-alt": ["1.1"],
|
|
1117
|
+
// ── Theme 2: Frames ───────────────────────────────────────────────────────
|
|
1118
|
+
"frame-title": ["2.1", "2.2"],
|
|
1119
|
+
"frame-focusable-content": ["2.1"],
|
|
1120
|
+
// ── Theme 3: Colors ───────────────────────────────────────────────────────
|
|
1121
|
+
"color-contrast": ["3.2"],
|
|
1122
|
+
"color-contrast-enhanced": ["3.2"],
|
|
1123
|
+
"link-in-text-block": ["3.2", "10.6"],
|
|
1124
|
+
// ── Theme 4: Multimedia ───────────────────────────────────────────────────
|
|
1125
|
+
"video-caption": ["4.3"],
|
|
1126
|
+
"audio-caption": ["4.3"],
|
|
1127
|
+
"video-description": ["4.5"],
|
|
1128
|
+
blink: ["13.8"],
|
|
1129
|
+
marquee: ["13.8"],
|
|
1130
|
+
// ── Theme 5: Tables ───────────────────────────────────────────────────────
|
|
1131
|
+
"td-headers-attr": ["5.6", "5.7"],
|
|
1132
|
+
"th-has-data-cells": ["5.6"],
|
|
1133
|
+
"table-duplicate-name": ["5.4", "5.5"],
|
|
1134
|
+
"table-fake-caption": ["5.1"],
|
|
1135
|
+
"layout-table": ["5.8"],
|
|
1136
|
+
"scope-attr-valid": ["5.6"],
|
|
1137
|
+
// ── Theme 6: Links ────────────────────────────────────────────────────────
|
|
1138
|
+
"link-name": ["6.1", "6.2"],
|
|
1139
|
+
// ── Theme 7: Scripts ──────────────────────────────────────────────────────
|
|
1140
|
+
"aria-allowed-attr": ["7.1"],
|
|
1141
|
+
"aria-required-attr": ["7.1"],
|
|
1142
|
+
"aria-required-children": ["7.1"],
|
|
1143
|
+
"aria-required-parent": ["7.1"],
|
|
1144
|
+
"aria-roledescription": ["7.1"],
|
|
1145
|
+
"aria-roles": ["7.1"],
|
|
1146
|
+
"aria-valid-attr": ["7.1"],
|
|
1147
|
+
"aria-valid-attr-value": ["7.1"],
|
|
1148
|
+
"aria-hidden-body": ["7.1"],
|
|
1149
|
+
"aria-hidden-focus": ["7.1"],
|
|
1150
|
+
"status-messages-role": ["7.5"],
|
|
1151
|
+
// ── Theme 8: Mandatory Elements ───────────────────────────────────────────
|
|
1152
|
+
"duplicate-id": ["8.2"],
|
|
1153
|
+
"duplicate-id-aria": ["8.2"],
|
|
1154
|
+
"duplicate-id-active": ["8.2"],
|
|
1155
|
+
"html-has-lang": ["8.3"],
|
|
1156
|
+
"html-lang-valid": ["8.4"],
|
|
1157
|
+
"html-xml-lang-mismatch": ["8.3"],
|
|
1158
|
+
"valid-lang": ["8.7", "8.8"],
|
|
1159
|
+
"document-title": ["8.5", "8.6"],
|
|
1160
|
+
// ── Theme 9: Structure ────────────────────────────────────────────────────
|
|
1161
|
+
"heading-order": ["9.1"],
|
|
1162
|
+
"page-has-heading-one": ["9.1"],
|
|
1163
|
+
listitem: ["9.3"],
|
|
1164
|
+
list: ["9.3"],
|
|
1165
|
+
"definition-list": ["9.3"],
|
|
1166
|
+
// ── Theme 10: Presentation ────────────────────────────────────────────────
|
|
1167
|
+
"meta-viewport": ["10.4"],
|
|
1168
|
+
"meta-viewport-large": ["10.4"],
|
|
1169
|
+
"focus-order-semantics": ["10.7", "12.8"],
|
|
1170
|
+
"scrollable-region-focusable": ["10.7"],
|
|
1171
|
+
"css-orientation-lock": ["13.9"],
|
|
1172
|
+
// ── Theme 11: Forms ───────────────────────────────────────────────────────
|
|
1173
|
+
label: ["11.1"],
|
|
1174
|
+
"label-content-name-mismatch": ["11.2"],
|
|
1175
|
+
"label-title-only": ["11.2"],
|
|
1176
|
+
"select-name": ["11.1"],
|
|
1177
|
+
"button-name": ["11.9"],
|
|
1178
|
+
"form-field-multiple-labels": ["11.1"],
|
|
1179
|
+
radiogroup: ["11.5", "11.6"],
|
|
1180
|
+
"autocomplete-valid": ["11.13"],
|
|
1181
|
+
// ── Theme 12: Navigation ──────────────────────────────────────────────────
|
|
1182
|
+
"landmark-one-main": ["12.6"],
|
|
1183
|
+
region: ["12.6"],
|
|
1184
|
+
"landmark-banner-is-top-level": ["12.6"],
|
|
1185
|
+
"landmark-complementary-is-top-level": ["12.6"],
|
|
1186
|
+
"landmark-contentinfo-is-top-level": ["12.6"],
|
|
1187
|
+
"landmark-main-is-top-level": ["12.6"],
|
|
1188
|
+
"landmark-no-duplicate-banner": ["12.6"],
|
|
1189
|
+
"landmark-no-duplicate-contentinfo": ["12.6"],
|
|
1190
|
+
"landmark-no-duplicate-main": ["12.6"],
|
|
1191
|
+
"landmark-unique": ["12.6"],
|
|
1192
|
+
bypass: ["12.7"],
|
|
1193
|
+
"skip-link": ["12.7"],
|
|
1194
|
+
tabindex: ["12.8"],
|
|
1195
|
+
"focus-trap": ["12.9"],
|
|
1196
|
+
// ── Theme 13: Consultation ────────────────────────────────────────────────
|
|
1197
|
+
"meta-refresh": ["13.1"],
|
|
1198
|
+
"meta-refresh-no-exceptions": ["13.1"],
|
|
1199
|
+
"animation-from-interactions": ["13.8"]
|
|
1200
|
+
};
|
|
1201
|
+
function getRGAACriteria(axeRuleId) {
|
|
1202
|
+
return AXE_TO_RGAA[axeRuleId] ?? [];
|
|
1203
|
+
}
|
|
1204
|
+
var RGAA_TO_AXE = (() => {
|
|
1205
|
+
const map = {};
|
|
1206
|
+
for (const [axeId, criteria] of Object.entries(AXE_TO_RGAA)) {
|
|
1207
|
+
for (const c of criteria) {
|
|
1208
|
+
if (!map[c]) map[c] = [];
|
|
1209
|
+
map[c].push(axeId);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
return map;
|
|
1213
|
+
})();
|
|
1214
|
+
function getAxeRulesForCriterion(criterionId) {
|
|
1215
|
+
return RGAA_TO_AXE[criterionId] ?? [];
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// src/errors.ts
|
|
1219
|
+
var ConfigError = class extends Error {
|
|
1220
|
+
code = "CONFIG_ERROR";
|
|
1221
|
+
constructor(message) {
|
|
1222
|
+
super(message);
|
|
1223
|
+
this.name = "ConfigError";
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1226
|
+
var AnalysisError = class extends Error {
|
|
1227
|
+
code = "ANALYSIS_ERROR";
|
|
1228
|
+
constructor(message) {
|
|
1229
|
+
super(message);
|
|
1230
|
+
this.name = "AnalysisError";
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
var TimeoutError = class extends Error {
|
|
1234
|
+
code = "TIMEOUT_ERROR";
|
|
1235
|
+
constructor(message) {
|
|
1236
|
+
super(message);
|
|
1237
|
+
this.name = "TimeoutError";
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
|
|
1241
|
+
// src/utils/race-with-timeout.ts
|
|
1242
|
+
async function raceWithTimeout(promise, timeoutMs, errorMsg) {
|
|
1243
|
+
let rejectTimer;
|
|
1244
|
+
const timer = setTimeout(() => rejectTimer(new TimeoutError(errorMsg)), timeoutMs);
|
|
1245
|
+
try {
|
|
1246
|
+
return await Promise.race([
|
|
1247
|
+
promise,
|
|
1248
|
+
new Promise((_, reject) => {
|
|
1249
|
+
rejectTimer = reject;
|
|
1250
|
+
})
|
|
1251
|
+
]);
|
|
1252
|
+
} finally {
|
|
1253
|
+
clearTimeout(timer);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// src/reporter/escaping.ts
|
|
1258
|
+
var HTML_ESCAPE = {
|
|
1259
|
+
"&": "&",
|
|
1260
|
+
"<": "<",
|
|
1261
|
+
">": ">",
|
|
1262
|
+
'"': """,
|
|
1263
|
+
"'": "'"
|
|
1264
|
+
};
|
|
1265
|
+
var HTML_RE = /[&<>"']/g;
|
|
1266
|
+
function escapeHtml(str) {
|
|
1267
|
+
return str.replace(HTML_RE, (ch) => HTML_ESCAPE[ch] ?? ch);
|
|
1268
|
+
}
|
|
1269
|
+
var XML_ESCAPE = {
|
|
1270
|
+
"&": "&",
|
|
1271
|
+
"<": "<",
|
|
1272
|
+
">": ">",
|
|
1273
|
+
'"': """,
|
|
1274
|
+
"'": "'"
|
|
1275
|
+
};
|
|
1276
|
+
var XML_RE = /[&<>"']/g;
|
|
1277
|
+
function escapeXml(str) {
|
|
1278
|
+
return str.replace(XML_RE, (ch) => XML_ESCAPE[ch] ?? ch);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// src/analyzer/static/parser.ts
|
|
1282
|
+
import { parse } from "@babel/parser";
|
|
1283
|
+
var VISITOR_KEYS = {
|
|
1284
|
+
ArrayExpression: ["elements"],
|
|
1285
|
+
AssignmentExpression: ["left", "right"],
|
|
1286
|
+
BinaryExpression: ["left", "right"],
|
|
1287
|
+
InterpreterDirective: [],
|
|
1288
|
+
Directive: ["value"],
|
|
1289
|
+
DirectiveLiteral: [],
|
|
1290
|
+
BlockStatement: ["directives", "body"],
|
|
1291
|
+
BreakStatement: ["label"],
|
|
1292
|
+
CallExpression: ["callee", "typeParameters", "typeArguments", "arguments"],
|
|
1293
|
+
CatchClause: ["param", "body"],
|
|
1294
|
+
ConditionalExpression: ["test", "consequent", "alternate"],
|
|
1295
|
+
ContinueStatement: ["label"],
|
|
1296
|
+
DebuggerStatement: [],
|
|
1297
|
+
DoWhileStatement: ["body", "test"],
|
|
1298
|
+
EmptyStatement: [],
|
|
1299
|
+
ExpressionStatement: ["expression"],
|
|
1300
|
+
File: ["program"],
|
|
1301
|
+
ForInStatement: ["left", "right", "body"],
|
|
1302
|
+
ForStatement: ["init", "test", "update", "body"],
|
|
1303
|
+
FunctionDeclaration: ["id", "typeParameters", "params", "predicate", "returnType", "body"],
|
|
1304
|
+
FunctionExpression: ["id", "typeParameters", "params", "predicate", "returnType", "body"],
|
|
1305
|
+
Identifier: ["typeAnnotation", "decorators"],
|
|
1306
|
+
IfStatement: ["test", "consequent", "alternate"],
|
|
1307
|
+
LabeledStatement: ["label", "body"],
|
|
1308
|
+
StringLiteral: [],
|
|
1309
|
+
NumericLiteral: [],
|
|
1310
|
+
NullLiteral: [],
|
|
1311
|
+
BooleanLiteral: [],
|
|
1312
|
+
RegExpLiteral: [],
|
|
1313
|
+
LogicalExpression: ["left", "right"],
|
|
1314
|
+
MemberExpression: ["object", "property"],
|
|
1315
|
+
NewExpression: ["callee", "typeParameters", "typeArguments", "arguments"],
|
|
1316
|
+
Program: ["directives", "body"],
|
|
1317
|
+
ObjectExpression: ["properties"],
|
|
1318
|
+
ObjectMethod: ["decorators", "key", "typeParameters", "params", "returnType", "body"],
|
|
1319
|
+
ObjectProperty: ["decorators", "key", "value"],
|
|
1320
|
+
RestElement: ["argument", "typeAnnotation"],
|
|
1321
|
+
ReturnStatement: ["argument"],
|
|
1322
|
+
SequenceExpression: ["expressions"],
|
|
1323
|
+
ParenthesizedExpression: ["expression"],
|
|
1324
|
+
SwitchCase: ["test", "consequent"],
|
|
1325
|
+
SwitchStatement: ["discriminant", "cases"],
|
|
1326
|
+
ThisExpression: [],
|
|
1327
|
+
ThrowStatement: ["argument"],
|
|
1328
|
+
TryStatement: ["block", "handler", "finalizer"],
|
|
1329
|
+
UnaryExpression: ["argument"],
|
|
1330
|
+
UpdateExpression: ["argument"],
|
|
1331
|
+
VariableDeclaration: ["declarations"],
|
|
1332
|
+
VariableDeclarator: ["id", "init"],
|
|
1333
|
+
WhileStatement: ["test", "body"],
|
|
1334
|
+
WithStatement: ["object", "body"],
|
|
1335
|
+
AssignmentPattern: ["left", "right", "decorators"],
|
|
1336
|
+
ArrayPattern: ["elements", "typeAnnotation"],
|
|
1337
|
+
ArrowFunctionExpression: ["typeParameters", "params", "predicate", "returnType", "body"],
|
|
1338
|
+
ClassBody: ["body"],
|
|
1339
|
+
ClassExpression: [
|
|
1340
|
+
"decorators",
|
|
1341
|
+
"id",
|
|
1342
|
+
"typeParameters",
|
|
1343
|
+
"superClass",
|
|
1344
|
+
"superTypeParameters",
|
|
1345
|
+
"mixins",
|
|
1346
|
+
"implements",
|
|
1347
|
+
"body"
|
|
1348
|
+
],
|
|
1349
|
+
ClassDeclaration: [
|
|
1350
|
+
"decorators",
|
|
1351
|
+
"id",
|
|
1352
|
+
"typeParameters",
|
|
1353
|
+
"superClass",
|
|
1354
|
+
"superTypeParameters",
|
|
1355
|
+
"mixins",
|
|
1356
|
+
"implements",
|
|
1357
|
+
"body"
|
|
1358
|
+
],
|
|
1359
|
+
ExportAllDeclaration: ["source", "attributes", "assertions"],
|
|
1360
|
+
ExportDefaultDeclaration: ["declaration"],
|
|
1361
|
+
ExportNamedDeclaration: ["declaration", "specifiers", "source", "attributes", "assertions"],
|
|
1362
|
+
ExportSpecifier: ["local", "exported"],
|
|
1363
|
+
ForOfStatement: ["left", "right", "body"],
|
|
1364
|
+
ImportDeclaration: ["specifiers", "source", "attributes", "assertions"],
|
|
1365
|
+
ImportDefaultSpecifier: ["local"],
|
|
1366
|
+
ImportNamespaceSpecifier: ["local"],
|
|
1367
|
+
ImportSpecifier: ["imported", "local"],
|
|
1368
|
+
ImportExpression: ["source", "options"],
|
|
1369
|
+
MetaProperty: ["meta", "property"],
|
|
1370
|
+
ClassMethod: ["decorators", "key", "typeParameters", "params", "returnType", "body"],
|
|
1371
|
+
ObjectPattern: ["decorators", "properties", "typeAnnotation"],
|
|
1372
|
+
SpreadElement: ["argument"],
|
|
1373
|
+
Super: [],
|
|
1374
|
+
TaggedTemplateExpression: ["tag", "typeParameters", "quasi"],
|
|
1375
|
+
TemplateElement: [],
|
|
1376
|
+
TemplateLiteral: ["quasis", "expressions"],
|
|
1377
|
+
YieldExpression: ["argument"],
|
|
1378
|
+
AwaitExpression: ["argument"],
|
|
1379
|
+
Import: [],
|
|
1380
|
+
BigIntLiteral: [],
|
|
1381
|
+
ExportNamespaceSpecifier: ["exported"],
|
|
1382
|
+
OptionalMemberExpression: ["object", "property"],
|
|
1383
|
+
OptionalCallExpression: ["callee", "typeParameters", "typeArguments", "arguments"],
|
|
1384
|
+
ClassProperty: ["decorators", "variance", "key", "typeAnnotation", "value"],
|
|
1385
|
+
ClassAccessorProperty: ["decorators", "key", "typeAnnotation", "value"],
|
|
1386
|
+
ClassPrivateProperty: ["decorators", "variance", "key", "typeAnnotation", "value"],
|
|
1387
|
+
ClassPrivateMethod: ["decorators", "key", "typeParameters", "params", "returnType", "body"],
|
|
1388
|
+
PrivateName: ["id"],
|
|
1389
|
+
StaticBlock: ["body"],
|
|
1390
|
+
ImportAttribute: ["key", "value"],
|
|
1391
|
+
AnyTypeAnnotation: [],
|
|
1392
|
+
ArrayTypeAnnotation: ["elementType"],
|
|
1393
|
+
BooleanTypeAnnotation: [],
|
|
1394
|
+
BooleanLiteralTypeAnnotation: [],
|
|
1395
|
+
NullLiteralTypeAnnotation: [],
|
|
1396
|
+
ClassImplements: ["id", "typeParameters"],
|
|
1397
|
+
DeclareClass: ["id", "typeParameters", "extends", "mixins", "implements", "body"],
|
|
1398
|
+
DeclareFunction: ["id", "predicate"],
|
|
1399
|
+
DeclareInterface: ["id", "typeParameters", "extends", "body"],
|
|
1400
|
+
DeclareModule: ["id", "body"],
|
|
1401
|
+
DeclareModuleExports: ["typeAnnotation"],
|
|
1402
|
+
DeclareTypeAlias: ["id", "typeParameters", "right"],
|
|
1403
|
+
DeclareOpaqueType: ["id", "typeParameters", "supertype"],
|
|
1404
|
+
DeclareVariable: ["id"],
|
|
1405
|
+
DeclareExportDeclaration: ["declaration", "specifiers", "source", "attributes"],
|
|
1406
|
+
DeclareExportAllDeclaration: ["source", "attributes"],
|
|
1407
|
+
DeclaredPredicate: ["value"],
|
|
1408
|
+
ExistsTypeAnnotation: [],
|
|
1409
|
+
FunctionTypeAnnotation: ["typeParameters", "this", "params", "rest", "returnType"],
|
|
1410
|
+
FunctionTypeParam: ["name", "typeAnnotation"],
|
|
1411
|
+
GenericTypeAnnotation: ["id", "typeParameters"],
|
|
1412
|
+
InferredPredicate: [],
|
|
1413
|
+
InterfaceExtends: ["id", "typeParameters"],
|
|
1414
|
+
InterfaceDeclaration: ["id", "typeParameters", "extends", "body"],
|
|
1415
|
+
InterfaceTypeAnnotation: ["extends", "body"],
|
|
1416
|
+
IntersectionTypeAnnotation: ["types"],
|
|
1417
|
+
MixedTypeAnnotation: [],
|
|
1418
|
+
EmptyTypeAnnotation: [],
|
|
1419
|
+
NullableTypeAnnotation: ["typeAnnotation"],
|
|
1420
|
+
NumberLiteralTypeAnnotation: [],
|
|
1421
|
+
NumberTypeAnnotation: [],
|
|
1422
|
+
ObjectTypeAnnotation: ["properties", "indexers", "callProperties", "internalSlots"],
|
|
1423
|
+
ObjectTypeInternalSlot: ["id", "value"],
|
|
1424
|
+
ObjectTypeCallProperty: ["value"],
|
|
1425
|
+
ObjectTypeIndexer: ["variance", "id", "key", "value"],
|
|
1426
|
+
ObjectTypeProperty: ["key", "value", "variance"],
|
|
1427
|
+
ObjectTypeSpreadProperty: ["argument"],
|
|
1428
|
+
OpaqueType: ["id", "typeParameters", "supertype", "impltype"],
|
|
1429
|
+
QualifiedTypeIdentifier: ["qualification", "id"],
|
|
1430
|
+
StringLiteralTypeAnnotation: [],
|
|
1431
|
+
StringTypeAnnotation: [],
|
|
1432
|
+
SymbolTypeAnnotation: [],
|
|
1433
|
+
ThisTypeAnnotation: [],
|
|
1434
|
+
TupleTypeAnnotation: ["types"],
|
|
1435
|
+
TypeofTypeAnnotation: ["argument"],
|
|
1436
|
+
TypeAlias: ["id", "typeParameters", "right"],
|
|
1437
|
+
TypeAnnotation: ["typeAnnotation"],
|
|
1438
|
+
TypeCastExpression: ["expression", "typeAnnotation"],
|
|
1439
|
+
TypeParameter: ["bound", "default", "variance"],
|
|
1440
|
+
TypeParameterDeclaration: ["params"],
|
|
1441
|
+
TypeParameterInstantiation: ["params"],
|
|
1442
|
+
UnionTypeAnnotation: ["types"],
|
|
1443
|
+
Variance: [],
|
|
1444
|
+
VoidTypeAnnotation: [],
|
|
1445
|
+
EnumDeclaration: ["id", "body"],
|
|
1446
|
+
EnumBooleanBody: ["members"],
|
|
1447
|
+
EnumNumberBody: ["members"],
|
|
1448
|
+
EnumStringBody: ["members"],
|
|
1449
|
+
EnumSymbolBody: ["members"],
|
|
1450
|
+
EnumBooleanMember: ["id", "init"],
|
|
1451
|
+
EnumNumberMember: ["id", "init"],
|
|
1452
|
+
EnumStringMember: ["id", "init"],
|
|
1453
|
+
EnumDefaultedMember: ["id"],
|
|
1454
|
+
IndexedAccessType: ["objectType", "indexType"],
|
|
1455
|
+
OptionalIndexedAccessType: ["objectType", "indexType"],
|
|
1456
|
+
JSXAttribute: ["name", "value"],
|
|
1457
|
+
JSXClosingElement: ["name"],
|
|
1458
|
+
JSXElement: ["openingElement", "children", "closingElement"],
|
|
1459
|
+
JSXEmptyExpression: [],
|
|
1460
|
+
JSXExpressionContainer: ["expression"],
|
|
1461
|
+
JSXSpreadChild: ["expression"],
|
|
1462
|
+
JSXIdentifier: [],
|
|
1463
|
+
JSXMemberExpression: ["object", "property"],
|
|
1464
|
+
JSXNamespacedName: ["namespace", "name"],
|
|
1465
|
+
JSXOpeningElement: ["name", "typeParameters", "typeArguments", "attributes"],
|
|
1466
|
+
JSXSpreadAttribute: ["argument"],
|
|
1467
|
+
JSXText: [],
|
|
1468
|
+
JSXFragment: ["openingFragment", "children", "closingFragment"],
|
|
1469
|
+
JSXOpeningFragment: [],
|
|
1470
|
+
JSXClosingFragment: [],
|
|
1471
|
+
Noop: [],
|
|
1472
|
+
Placeholder: [],
|
|
1473
|
+
V8IntrinsicIdentifier: [],
|
|
1474
|
+
ArgumentPlaceholder: [],
|
|
1475
|
+
BindExpression: ["object", "callee"],
|
|
1476
|
+
Decorator: ["expression"],
|
|
1477
|
+
DoExpression: ["body"],
|
|
1478
|
+
ExportDefaultSpecifier: ["exported"],
|
|
1479
|
+
RecordExpression: ["properties"],
|
|
1480
|
+
TupleExpression: ["elements"],
|
|
1481
|
+
DecimalLiteral: [],
|
|
1482
|
+
ModuleExpression: ["body"],
|
|
1483
|
+
TopicReference: [],
|
|
1484
|
+
PipelineTopicExpression: ["expression"],
|
|
1485
|
+
PipelineBareFunction: ["callee"],
|
|
1486
|
+
PipelinePrimaryTopicReference: [],
|
|
1487
|
+
VoidPattern: [],
|
|
1488
|
+
TSParameterProperty: ["parameter"],
|
|
1489
|
+
TSDeclareFunction: ["id", "typeParameters", "params", "returnType"],
|
|
1490
|
+
TSDeclareMethod: ["decorators", "key", "typeParameters", "params", "returnType"],
|
|
1491
|
+
TSQualifiedName: ["left", "right"],
|
|
1492
|
+
TSCallSignatureDeclaration: ["typeParameters", "parameters", "typeAnnotation"],
|
|
1493
|
+
TSConstructSignatureDeclaration: ["typeParameters", "parameters", "typeAnnotation"],
|
|
1494
|
+
TSPropertySignature: ["key", "typeAnnotation"],
|
|
1495
|
+
TSMethodSignature: ["key", "typeParameters", "parameters", "typeAnnotation"],
|
|
1496
|
+
TSIndexSignature: ["parameters", "typeAnnotation"],
|
|
1497
|
+
TSAnyKeyword: [],
|
|
1498
|
+
TSBooleanKeyword: [],
|
|
1499
|
+
TSBigIntKeyword: [],
|
|
1500
|
+
TSIntrinsicKeyword: [],
|
|
1501
|
+
TSNeverKeyword: [],
|
|
1502
|
+
TSNullKeyword: [],
|
|
1503
|
+
TSNumberKeyword: [],
|
|
1504
|
+
TSObjectKeyword: [],
|
|
1505
|
+
TSStringKeyword: [],
|
|
1506
|
+
TSSymbolKeyword: [],
|
|
1507
|
+
TSUndefinedKeyword: [],
|
|
1508
|
+
TSUnknownKeyword: [],
|
|
1509
|
+
TSVoidKeyword: [],
|
|
1510
|
+
TSThisType: [],
|
|
1511
|
+
TSFunctionType: ["typeParameters", "parameters", "typeAnnotation"],
|
|
1512
|
+
TSConstructorType: ["typeParameters", "parameters", "typeAnnotation"],
|
|
1513
|
+
TSTypeReference: ["typeName", "typeParameters"],
|
|
1514
|
+
TSTypePredicate: ["parameterName", "typeAnnotation"],
|
|
1515
|
+
TSTypeQuery: ["exprName", "typeParameters"],
|
|
1516
|
+
TSTypeLiteral: ["members"],
|
|
1517
|
+
TSArrayType: ["elementType"],
|
|
1518
|
+
TSTupleType: ["elementTypes"],
|
|
1519
|
+
TSOptionalType: ["typeAnnotation"],
|
|
1520
|
+
TSRestType: ["typeAnnotation"],
|
|
1521
|
+
TSNamedTupleMember: ["label", "elementType"],
|
|
1522
|
+
TSUnionType: ["types"],
|
|
1523
|
+
TSIntersectionType: ["types"],
|
|
1524
|
+
TSConditionalType: ["checkType", "extendsType", "trueType", "falseType"],
|
|
1525
|
+
TSInferType: ["typeParameter"],
|
|
1526
|
+
TSParenthesizedType: ["typeAnnotation"],
|
|
1527
|
+
TSTypeOperator: ["typeAnnotation"],
|
|
1528
|
+
TSIndexedAccessType: ["objectType", "indexType"],
|
|
1529
|
+
TSMappedType: ["typeParameter", "nameType", "typeAnnotation"],
|
|
1530
|
+
TSTemplateLiteralType: ["quasis", "types"],
|
|
1531
|
+
TSLiteralType: ["literal"],
|
|
1532
|
+
TSExpressionWithTypeArguments: ["expression", "typeParameters"],
|
|
1533
|
+
TSInterfaceDeclaration: ["id", "typeParameters", "extends", "body"],
|
|
1534
|
+
TSInterfaceBody: ["body"],
|
|
1535
|
+
TSTypeAliasDeclaration: ["id", "typeParameters", "typeAnnotation"],
|
|
1536
|
+
TSInstantiationExpression: ["expression", "typeParameters"],
|
|
1537
|
+
TSAsExpression: ["expression", "typeAnnotation"],
|
|
1538
|
+
TSSatisfiesExpression: ["expression", "typeAnnotation"],
|
|
1539
|
+
TSTypeAssertion: ["typeAnnotation", "expression"],
|
|
1540
|
+
TSEnumBody: ["members"],
|
|
1541
|
+
TSEnumDeclaration: ["id", "members"],
|
|
1542
|
+
TSEnumMember: ["id", "initializer"],
|
|
1543
|
+
TSModuleDeclaration: ["id", "body"],
|
|
1544
|
+
TSModuleBlock: ["body"],
|
|
1545
|
+
TSImportType: ["argument", "options", "qualifier", "typeParameters"],
|
|
1546
|
+
TSImportEqualsDeclaration: ["id", "moduleReference"],
|
|
1547
|
+
TSExternalModuleReference: ["expression"],
|
|
1548
|
+
TSNonNullExpression: ["expression"],
|
|
1549
|
+
TSExportAssignment: ["expression"],
|
|
1550
|
+
TSNamespaceExportDeclaration: ["id"],
|
|
1551
|
+
TSTypeAnnotation: ["typeAnnotation"],
|
|
1552
|
+
TSTypeParameterInstantiation: ["params"],
|
|
1553
|
+
TSTypeParameterDeclaration: ["params"],
|
|
1554
|
+
TSTypeParameter: ["constraint", "default"]
|
|
1555
|
+
};
|
|
1556
|
+
function walk(node, visitors) {
|
|
1557
|
+
if (!node || typeof node !== "object") return;
|
|
1558
|
+
const visitor = visitors[node.type];
|
|
1559
|
+
if (visitor) {
|
|
1560
|
+
visitor(node);
|
|
1561
|
+
}
|
|
1562
|
+
const keys = VISITOR_KEYS[node.type];
|
|
1563
|
+
if (!keys) return;
|
|
1564
|
+
for (const key of keys) {
|
|
1565
|
+
const child = node[key];
|
|
1566
|
+
if (Array.isArray(child)) {
|
|
1567
|
+
for (const item of child) {
|
|
1568
|
+
if (item && typeof item === "object" && "type" in item) {
|
|
1569
|
+
walk(item, visitors);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
} else if (child && typeof child === "object" && "type" in child) {
|
|
1573
|
+
walk(child, visitors);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
function getTagName(node) {
|
|
1578
|
+
const { name } = node;
|
|
1579
|
+
if (name.type === "JSXIdentifier") {
|
|
1580
|
+
const raw = name.name;
|
|
1581
|
+
return /^[a-z]/.test(raw) ? raw.toLowerCase() : raw;
|
|
1582
|
+
}
|
|
1583
|
+
if (name.type === "JSXMemberExpression") return null;
|
|
1584
|
+
return null;
|
|
1585
|
+
}
|
|
1586
|
+
function getAttr(node, attrName) {
|
|
1587
|
+
const lower = attrName.toLowerCase();
|
|
1588
|
+
for (const attr of node.attributes) {
|
|
1589
|
+
if (attr.type === "JSXAttribute") {
|
|
1590
|
+
const name = attr.name.type === "JSXIdentifier" ? attr.name.name.toLowerCase() : attr.name.name.name.toLowerCase();
|
|
1591
|
+
if (name === lower) return attr;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
function getAttrStringValue(attr) {
|
|
1597
|
+
if (!attr?.value) return null;
|
|
1598
|
+
if (attr.value.type === "StringLiteral") return attr.value.value;
|
|
1599
|
+
return null;
|
|
1600
|
+
}
|
|
1601
|
+
function isAttrDynamic(attr) {
|
|
1602
|
+
if (!attr) return false;
|
|
1603
|
+
return attr.value?.type === "JSXExpressionContainer";
|
|
1604
|
+
}
|
|
1605
|
+
function getTextContent(node) {
|
|
1606
|
+
let text = "";
|
|
1607
|
+
let hasDynamic = false;
|
|
1608
|
+
for (const child of node.children) {
|
|
1609
|
+
if (child.type === "JSXText") {
|
|
1610
|
+
text += child.value;
|
|
1611
|
+
} else if (child.type === "JSXExpressionContainer") {
|
|
1612
|
+
if (child.expression.type === "StringLiteral") {
|
|
1613
|
+
text += child.expression.value;
|
|
1614
|
+
} else if (child.expression.type !== "JSXEmptyExpression") {
|
|
1615
|
+
hasDynamic = true;
|
|
1616
|
+
}
|
|
1617
|
+
} else if (child.type === "JSXElement") {
|
|
1618
|
+
const childText = getTextContent(child);
|
|
1619
|
+
if (childText === null) {
|
|
1620
|
+
hasDynamic = true;
|
|
1621
|
+
} else {
|
|
1622
|
+
text += childText;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
if (hasDynamic && text.trim() === "") return null;
|
|
1627
|
+
return text.trim();
|
|
1628
|
+
}
|
|
1629
|
+
function nodeLoc(node) {
|
|
1630
|
+
return node.loc ? { line: node.loc.start.line, column: node.loc.start.column } : {};
|
|
1631
|
+
}
|
|
1632
|
+
function getAttrMap(node) {
|
|
1633
|
+
const map = /* @__PURE__ */ new Map();
|
|
1634
|
+
for (const attr of node.attributes) {
|
|
1635
|
+
if (attr.type === "JSXAttribute") {
|
|
1636
|
+
const name = attr.name.type === "JSXIdentifier" ? attr.name.name.toLowerCase() : attr.name.name.name.toLowerCase();
|
|
1637
|
+
map.set(name, attr);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
return map;
|
|
1641
|
+
}
|
|
1642
|
+
var MAX_ELEMENT_SERIALIZATION_LENGTH = 500;
|
|
1643
|
+
function serializeElement(node, maxLen = MAX_ELEMENT_SERIALIZATION_LENGTH) {
|
|
1644
|
+
const tag = getTagName(node) ?? "unknown";
|
|
1645
|
+
const attrs = node.attributes.filter((a) => a.type === "JSXAttribute").slice(0, 20).map((a) => {
|
|
1646
|
+
const attr = a;
|
|
1647
|
+
const name = attr.name.type === "JSXIdentifier" ? attr.name.name : attr.name.name.name;
|
|
1648
|
+
if (!attr.value) return name;
|
|
1649
|
+
if (attr.value.type === "StringLiteral") {
|
|
1650
|
+
const escaped = attr.value.value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
1651
|
+
return `${name}="${escaped}"`;
|
|
1652
|
+
}
|
|
1653
|
+
return `${name}={\u2026}`;
|
|
1654
|
+
}).join(" ");
|
|
1655
|
+
const result = attrs ? `<${tag} ${attrs}>` : `<${tag}>`;
|
|
1656
|
+
return result.length > maxLen ? `${result.slice(0, maxLen)}\u2026` : result;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
// src/analyzer/static/rules/helpers.ts
|
|
1660
|
+
var MAX_CONTEXT_VALUE_LENGTH = 200;
|
|
1661
|
+
var _prefix = Math.random().toString(36).slice(2, 8);
|
|
1662
|
+
var _seq = 0;
|
|
1663
|
+
function issueId() {
|
|
1664
|
+
return `${_prefix}-${(++_seq).toString(36)}`;
|
|
1665
|
+
}
|
|
1666
|
+
function sanitizeContextValue(value) {
|
|
1667
|
+
return escapeHtml(value).slice(0, MAX_CONTEXT_VALUE_LENGTH);
|
|
1668
|
+
}
|
|
1669
|
+
function defineRule(meta, check) {
|
|
1670
|
+
return {
|
|
1671
|
+
...meta,
|
|
1672
|
+
check(context) {
|
|
1673
|
+
if (!context.ast) return [];
|
|
1674
|
+
return check(context);
|
|
1675
|
+
}
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
function createStaticIssue(opts) {
|
|
1679
|
+
const { node, filePath, remediationKey, severity = "error", ...rest } = opts;
|
|
1680
|
+
const sanitizedContext = rest.messageContext ? Object.fromEntries(
|
|
1681
|
+
Object.entries(rest.messageContext).map(([k, v]) => [k, sanitizeContextValue(v)])
|
|
1682
|
+
) : void 0;
|
|
1683
|
+
return {
|
|
1684
|
+
id: issueId(),
|
|
1685
|
+
phase: "static",
|
|
1686
|
+
element: serializeElement(node),
|
|
1687
|
+
file: filePath,
|
|
1688
|
+
...nodeLoc(node),
|
|
1689
|
+
severity,
|
|
1690
|
+
remediationKey: remediationKey ?? rest.messageKey,
|
|
1691
|
+
...rest,
|
|
1692
|
+
...sanitizedContext ? { messageContext: sanitizedContext } : {}
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// src/analyzer/runtime/axe-runner.ts
|
|
1697
|
+
import AxeBuilder from "@axe-core/playwright";
|
|
1698
|
+
var IMPACT_TO_SEVERITY = {
|
|
1699
|
+
critical: "error",
|
|
1700
|
+
serious: "error",
|
|
1701
|
+
moderate: "warning",
|
|
1702
|
+
minor: "notice"
|
|
1703
|
+
};
|
|
1704
|
+
async function runAxe(page, pagePath, exemptedCriteria = /* @__PURE__ */ new Set()) {
|
|
1705
|
+
const builder = new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "best-practice"]).disableRules([
|
|
1706
|
+
// Handled statically
|
|
1707
|
+
"image-alt",
|
|
1708
|
+
"area-alt",
|
|
1709
|
+
"input-image-alt",
|
|
1710
|
+
"frame-title",
|
|
1711
|
+
"label",
|
|
1712
|
+
"heading-order"
|
|
1713
|
+
]);
|
|
1714
|
+
const results = await raceWithTimeout(builder.analyze(), 3e4, "axe-core timed out after 30s");
|
|
1715
|
+
const issues = [];
|
|
1716
|
+
for (const violation of results.violations) {
|
|
1717
|
+
const rgaaCriteria = getRGAACriteria(violation.id);
|
|
1718
|
+
if (rgaaCriteria.length > 0 && rgaaCriteria.every((c) => exemptedCriteria.has(c))) {
|
|
1719
|
+
continue;
|
|
1720
|
+
}
|
|
1721
|
+
const primaryCriterion = rgaaCriteria[0];
|
|
1722
|
+
if (!primaryCriterion) continue;
|
|
1723
|
+
const severity = IMPACT_TO_SEVERITY[violation.impact ?? "minor"] ?? "warning";
|
|
1724
|
+
for (const node of violation.nodes) {
|
|
1725
|
+
issues.push({
|
|
1726
|
+
id: issueId(),
|
|
1727
|
+
criterionId: primaryCriterion,
|
|
1728
|
+
testId: `${AXE_TEST_ID_PREFIX}${violation.id}`,
|
|
1729
|
+
phase: "runtime",
|
|
1730
|
+
severity,
|
|
1731
|
+
element: node.html,
|
|
1732
|
+
page: pagePath,
|
|
1733
|
+
messageKey: violation.id,
|
|
1734
|
+
remediationKey: violation.id,
|
|
1735
|
+
messageContext: {
|
|
1736
|
+
axeRuleId: violation.id,
|
|
1737
|
+
description: violation.description,
|
|
1738
|
+
help: violation.help,
|
|
1739
|
+
helpUrl: violation.helpUrl
|
|
1740
|
+
},
|
|
1741
|
+
...violation.tags.some((t) => t.startsWith("wcag")) ? {
|
|
1742
|
+
wcag: violation.tags.filter((t) => t.startsWith("wcag")).join(", ")
|
|
1743
|
+
} : {}
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
return issues;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// src/analyzer/runtime/engine.ts
|
|
1751
|
+
async function getChromium() {
|
|
1752
|
+
try {
|
|
1753
|
+
const pw = await import("playwright");
|
|
1754
|
+
return pw.chromium;
|
|
1755
|
+
} catch (err) {
|
|
1756
|
+
if (err instanceof Error && (err.message.includes("Cannot find module") || err.message.includes("ERR_MODULE_NOT_FOUND") || err.message.includes("Cannot find package"))) {
|
|
1757
|
+
throw new Error(
|
|
1758
|
+
"[eqo] Runtime analysis requires playwright.\nInstall: npm install playwright @axe-core/playwright && npx playwright install chromium"
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
throw err;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
var BROWSER_POOL_SIZE = 3;
|
|
1765
|
+
var PAGE_TIMEOUT = 15e3;
|
|
1766
|
+
var NETWORK_IDLE_TIMEOUT = 5e3;
|
|
1767
|
+
var URL_PROTOCOL_RE = /^https?:\/\//;
|
|
1768
|
+
function normalizePagePath(p) {
|
|
1769
|
+
const decoded = decodeURIComponent(p);
|
|
1770
|
+
if (decoded.includes("..") || decoded.includes("\\")) {
|
|
1771
|
+
throw new Error(`[eqo:runtime] Page path contains traversal: "${p}"`);
|
|
1772
|
+
}
|
|
1773
|
+
const normalized = p.startsWith("/") ? p : `/${p}`;
|
|
1774
|
+
return normalized.split(/[?#]/)[0] ?? normalized;
|
|
1775
|
+
}
|
|
1776
|
+
async function closeWithTimeout(closeCall, label, timeoutMs = 5e3) {
|
|
1777
|
+
if (!closeCall) return;
|
|
1778
|
+
await raceWithTimeout(closeCall, timeoutMs, `${label} timed out after ${timeoutMs}ms`).catch(
|
|
1779
|
+
(err) => {
|
|
1780
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1781
|
+
if (msg.includes("timed out")) {
|
|
1782
|
+
warn("runtime", `${msg} (usually harmless)`);
|
|
1783
|
+
} else {
|
|
1784
|
+
error("runtime", `Unexpected error during ${label}: ${msg}`);
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
async function analyzePage(browser, normalizedBase, pageConfig, exemptedCriteria, signal) {
|
|
1790
|
+
const pagePath = normalizePagePath(pageConfig.path);
|
|
1791
|
+
const url = `${normalizedBase}${pagePath}`;
|
|
1792
|
+
const context = await browser.newContext({
|
|
1793
|
+
// Force reduced motion to ensure animations don't interfere with checks
|
|
1794
|
+
reducedMotion: "reduce"
|
|
1795
|
+
});
|
|
1796
|
+
let page = null;
|
|
1797
|
+
try {
|
|
1798
|
+
page = await context.newPage();
|
|
1799
|
+
await page.goto(url, {
|
|
1800
|
+
waitUntil: "domcontentloaded",
|
|
1801
|
+
timeout: PAGE_TIMEOUT
|
|
1802
|
+
});
|
|
1803
|
+
if (signal?.aborted) throw new Error("Aborted");
|
|
1804
|
+
await page.waitForLoadState("networkidle", { timeout: NETWORK_IDLE_TIMEOUT }).catch((err) => {
|
|
1805
|
+
if (!(err instanceof Error && err.message.includes("Timeout"))) {
|
|
1806
|
+
warn(
|
|
1807
|
+
"runtime",
|
|
1808
|
+
`networkidle wait failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
});
|
|
1812
|
+
const title = await page.title().catch(() => void 0);
|
|
1813
|
+
const axeIssues = await runAxe(page, pageConfig.path, exemptedCriteria);
|
|
1814
|
+
const customIssues = await runCustomChecks(page, pageConfig.path);
|
|
1815
|
+
const allIssues = [...axeIssues, ...customIssues];
|
|
1816
|
+
const pageResult = {
|
|
1817
|
+
url,
|
|
1818
|
+
path: pageConfig.path,
|
|
1819
|
+
...title !== void 0 ? { title } : {},
|
|
1820
|
+
issueCount: allIssues.length
|
|
1821
|
+
};
|
|
1822
|
+
return { issues: allIssues, page: pageResult };
|
|
1823
|
+
} finally {
|
|
1824
|
+
await Promise.all([
|
|
1825
|
+
closeWithTimeout(page?.close(), "page.close"),
|
|
1826
|
+
closeWithTimeout(context.close(), "context.close")
|
|
1827
|
+
]).catch(() => {
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
var SELECTOR_CHECKS = [
|
|
1832
|
+
{
|
|
1833
|
+
criterionId: "12.7",
|
|
1834
|
+
testId: "12.7.1",
|
|
1835
|
+
wcag: "2.4.1",
|
|
1836
|
+
messageKey: "a11y.missing-skip-link",
|
|
1837
|
+
remediationKey: "a11y.missing-skip-link",
|
|
1838
|
+
selector: 'a[href^="#"]:first-of-type'
|
|
1839
|
+
},
|
|
1840
|
+
{
|
|
1841
|
+
criterionId: "12.6",
|
|
1842
|
+
testId: "12.6.1",
|
|
1843
|
+
wcag: "1.3.1",
|
|
1844
|
+
messageKey: "a11y.missing-landmark",
|
|
1845
|
+
remediationKey: "a11y.missing-landmark",
|
|
1846
|
+
selector: 'main, [role="main"]'
|
|
1847
|
+
}
|
|
1848
|
+
];
|
|
1849
|
+
async function runCustomChecks(page, pagePath) {
|
|
1850
|
+
const issues = [];
|
|
1851
|
+
for (const check of SELECTOR_CHECKS) {
|
|
1852
|
+
try {
|
|
1853
|
+
const el = await page.$(check.selector);
|
|
1854
|
+
if (!el) {
|
|
1855
|
+
issues.push({
|
|
1856
|
+
id: issueId(),
|
|
1857
|
+
criterionId: check.criterionId,
|
|
1858
|
+
testId: check.testId,
|
|
1859
|
+
phase: "runtime",
|
|
1860
|
+
severity: "error",
|
|
1861
|
+
page: pagePath,
|
|
1862
|
+
messageKey: check.messageKey,
|
|
1863
|
+
remediationKey: check.remediationKey,
|
|
1864
|
+
wcag: check.wcag
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
} catch (err) {
|
|
1868
|
+
warn(
|
|
1869
|
+
"runtime",
|
|
1870
|
+
`check ${check.criterionId} skipped for ${pagePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
try {
|
|
1875
|
+
const duplicateIds = await raceWithTimeout(
|
|
1876
|
+
page.evaluate(() => {
|
|
1877
|
+
const doc = globalThis.document;
|
|
1878
|
+
const ids = Array.from(doc.querySelectorAll("[id]")).map((el) => el.id);
|
|
1879
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1880
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
1881
|
+
for (const id of ids) {
|
|
1882
|
+
if (seen.has(id)) duplicates.add(id);
|
|
1883
|
+
seen.add(id);
|
|
1884
|
+
}
|
|
1885
|
+
return Array.from(duplicates);
|
|
1886
|
+
}),
|
|
1887
|
+
1e4,
|
|
1888
|
+
"page.evaluate timed out after 10s"
|
|
1889
|
+
);
|
|
1890
|
+
for (const dupId of duplicateIds) {
|
|
1891
|
+
issues.push({
|
|
1892
|
+
id: issueId(),
|
|
1893
|
+
criterionId: "8.2",
|
|
1894
|
+
testId: "8.2.1",
|
|
1895
|
+
phase: "runtime",
|
|
1896
|
+
severity: "error",
|
|
1897
|
+
page: pagePath,
|
|
1898
|
+
messageKey: "html.duplicate-id",
|
|
1899
|
+
remediationKey: "html.duplicate-id",
|
|
1900
|
+
messageContext: { id: dupId },
|
|
1901
|
+
wcag: "4.1.1"
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
} catch (err) {
|
|
1905
|
+
warn(
|
|
1906
|
+
"runtime",
|
|
1907
|
+
`check 8.2 skipped for ${pagePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
return issues;
|
|
1911
|
+
}
|
|
1912
|
+
async function runRuntimeAnalysis(baseUrl, pages, exemptions = [], signal) {
|
|
1913
|
+
const start = performance.now();
|
|
1914
|
+
if (!URL_PROTOCOL_RE.test(baseUrl)) {
|
|
1915
|
+
throw new Error(
|
|
1916
|
+
`[eqo:runtime] baseUrl must start with http:// or https://, got: "${baseUrl.slice(0, 50)}"`
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
for (const page of pages) {
|
|
1920
|
+
if (page.path.includes("://") || page.path.startsWith("//")) {
|
|
1921
|
+
throw new Error(`[eqo:runtime] Page path must be relative, got: "${page.path}"`);
|
|
1922
|
+
}
|
|
1923
|
+
normalizePagePath(page.path);
|
|
1924
|
+
}
|
|
1925
|
+
const normalizedBase = baseUrl.replace(/\/$/, "");
|
|
1926
|
+
const exemptedCriteria = new Set(exemptions.map((e) => e.criterion));
|
|
1927
|
+
const chromium = await getChromium();
|
|
1928
|
+
let browser = await chromium.launch({ headless: true });
|
|
1929
|
+
try {
|
|
1930
|
+
const allIssues = [];
|
|
1931
|
+
const allPageResults = [];
|
|
1932
|
+
for (let i = 0; i < pages.length; i += BROWSER_POOL_SIZE) {
|
|
1933
|
+
if (signal?.aborted) break;
|
|
1934
|
+
if (!browser.isConnected()) {
|
|
1935
|
+
warn("runtime", "Browser disconnected, re-launching...");
|
|
1936
|
+
browser = await chromium.launch({ headless: true });
|
|
1937
|
+
}
|
|
1938
|
+
const batch = pages.slice(i, i + BROWSER_POOL_SIZE);
|
|
1939
|
+
const batchResults = await Promise.allSettled(
|
|
1940
|
+
batch.map(
|
|
1941
|
+
(pageConfig) => analyzePage(browser, normalizedBase, pageConfig, exemptedCriteria, signal)
|
|
1942
|
+
)
|
|
1943
|
+
);
|
|
1944
|
+
for (let j = 0; j < batchResults.length; j++) {
|
|
1945
|
+
const result = batchResults[j];
|
|
1946
|
+
const pageConfig = batch[j];
|
|
1947
|
+
if (!result || !pageConfig) continue;
|
|
1948
|
+
const pagePath = pageConfig.path.startsWith("/") ? pageConfig.path : `/${pageConfig.path}`;
|
|
1949
|
+
const url = `${normalizedBase}${pagePath}`;
|
|
1950
|
+
if (result.status === "fulfilled") {
|
|
1951
|
+
allIssues.push(...result.value.issues);
|
|
1952
|
+
allPageResults.push(result.value.page);
|
|
1953
|
+
} else {
|
|
1954
|
+
const message = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
1955
|
+
warn("runtime", `could not analyze ${url} \u2014 ${message}`);
|
|
1956
|
+
allPageResults.push({
|
|
1957
|
+
url,
|
|
1958
|
+
path: pageConfig.path,
|
|
1959
|
+
issueCount: 0,
|
|
1960
|
+
error: message
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
return {
|
|
1966
|
+
issues: allIssues,
|
|
1967
|
+
pages: allPageResults,
|
|
1968
|
+
durationMs: performance.now() - start
|
|
1969
|
+
};
|
|
1970
|
+
} finally {
|
|
1971
|
+
await closeWithTimeout(browser.close(), "browser.close");
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
// src/analyzer/static/engine.ts
|
|
1976
|
+
import { existsSync } from "fs";
|
|
1977
|
+
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
1978
|
+
import os from "os";
|
|
1979
|
+
import path2 from "path";
|
|
1980
|
+
import { fileURLToPath } from "url";
|
|
1981
|
+
import picomatch from "picomatch";
|
|
1982
|
+
import Piscina from "piscina";
|
|
1983
|
+
|
|
1984
|
+
// src/analyzer/static/cache.ts
|
|
1985
|
+
import { createHash } from "crypto";
|
|
1986
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
1987
|
+
import path from "path";
|
|
1988
|
+
var CACHE_DIR = "node_modules/.cache/@kodalabs-io/eqo";
|
|
1989
|
+
var CACHE_FILE = "analysis-cache.json";
|
|
1990
|
+
var TOOL_VERSION = (true ? "1.0.0" : null) ?? "0.1.0";
|
|
1991
|
+
function fileHash(source) {
|
|
1992
|
+
return createHash("sha256").update(source).digest("hex").slice(0, 16);
|
|
1993
|
+
}
|
|
1994
|
+
async function loadCache(projectRoot) {
|
|
1995
|
+
try {
|
|
1996
|
+
const data = await readFile(path.join(projectRoot, CACHE_DIR, CACHE_FILE), "utf-8");
|
|
1997
|
+
const cache2 = JSON.parse(data);
|
|
1998
|
+
if (cache2.version !== TOOL_VERSION) return null;
|
|
1999
|
+
return cache2;
|
|
2000
|
+
} catch {
|
|
2001
|
+
return null;
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
async function saveCache(projectRoot, cache2) {
|
|
2005
|
+
const dir = path.join(projectRoot, CACHE_DIR);
|
|
2006
|
+
await mkdir(dir, { recursive: true });
|
|
2007
|
+
await writeFile(path.join(dir, CACHE_FILE), JSON.stringify(cache2), "utf-8");
|
|
2008
|
+
}
|
|
2009
|
+
function createEmptyCache() {
|
|
2010
|
+
return { version: TOOL_VERSION, entries: {} };
|
|
2011
|
+
}
|
|
2012
|
+
function getCachedResult(cache2, filePath, sourceHash) {
|
|
2013
|
+
const entry = cache2.entries[filePath];
|
|
2014
|
+
if (!entry) return null;
|
|
2015
|
+
if (entry.hash !== sourceHash) return null;
|
|
2016
|
+
return entry.issues;
|
|
2017
|
+
}
|
|
2018
|
+
function setCacheEntry(cache2, filePath, sourceHash, issues) {
|
|
2019
|
+
cache2.entries[filePath] = { hash: sourceHash, issues };
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
// src/analyzer/static/engine.ts
|
|
2023
|
+
var __dirname = path2.dirname(fileURLToPath(import.meta.url));
|
|
2024
|
+
var _workerInSameDir = path2.join(__dirname, "worker.js");
|
|
2025
|
+
var _workerPath = existsSync(_workerInSameDir) ? _workerInSameDir : path2.join(__dirname, "../worker.js");
|
|
2026
|
+
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js", ".mjs"]);
|
|
2027
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next"]);
|
|
2028
|
+
var WORKER_TIMEOUT_MS = 3e4;
|
|
2029
|
+
function compileGlob(pattern) {
|
|
2030
|
+
return picomatch(pattern, { dot: false });
|
|
2031
|
+
}
|
|
2032
|
+
function shouldInclude(relativePath, includeFns, excludeFns) {
|
|
2033
|
+
return includeFns.some((fn) => fn(relativePath)) && !excludeFns.some((fn) => fn(relativePath));
|
|
2034
|
+
}
|
|
2035
|
+
async function* collectFiles(dir, projectRoot, includeFns, excludeFns) {
|
|
2036
|
+
async function* walkDir(current) {
|
|
2037
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
2038
|
+
for (const entry of entries) {
|
|
2039
|
+
const full = path2.join(current, entry.name);
|
|
2040
|
+
if (entry.isDirectory()) {
|
|
2041
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
2042
|
+
yield* walkDir(full);
|
|
2043
|
+
} else if (entry.isFile()) {
|
|
2044
|
+
const ext = path2.extname(entry.name);
|
|
2045
|
+
if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
|
|
2046
|
+
const rel = path2.relative(projectRoot, full);
|
|
2047
|
+
if (shouldInclude(rel, includeFns, excludeFns)) {
|
|
2048
|
+
yield full;
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
yield* walkDir(dir);
|
|
2054
|
+
}
|
|
2055
|
+
async function runStaticAnalysis(projectRoot, config = {}, options = {}) {
|
|
2056
|
+
const start = performance.now();
|
|
2057
|
+
const useCache = !options.noCache;
|
|
2058
|
+
const include = config.include ?? ["src/**/*.{tsx,jsx,ts,js}"];
|
|
2059
|
+
const exclude = config.exclude ?? [
|
|
2060
|
+
"**/*.test.*",
|
|
2061
|
+
"**/*.spec.*",
|
|
2062
|
+
"**/node_modules/**",
|
|
2063
|
+
"**/.next/**",
|
|
2064
|
+
"**/dist/**"
|
|
2065
|
+
];
|
|
2066
|
+
const includeFns = include.map(compileGlob);
|
|
2067
|
+
const excludeFns = exclude.map(compileGlob);
|
|
2068
|
+
const cache2 = useCache ? await loadCache(projectRoot) ?? createEmptyCache() : null;
|
|
2069
|
+
const pool = new Piscina({
|
|
2070
|
+
filename: _workerPath,
|
|
2071
|
+
maxThreads: Math.max(2, os.cpus().length - 1),
|
|
2072
|
+
idleTimeout: 5e3
|
|
2073
|
+
});
|
|
2074
|
+
const BATCH_SIZE = 500;
|
|
2075
|
+
const allResults = [];
|
|
2076
|
+
const allFilePaths = [];
|
|
2077
|
+
const cachedIssues = [];
|
|
2078
|
+
const analyzedRelPaths = /* @__PURE__ */ new Set();
|
|
2079
|
+
let totalFiles = 0;
|
|
2080
|
+
let cacheHits = 0;
|
|
2081
|
+
let batch = [];
|
|
2082
|
+
try {
|
|
2083
|
+
for await (const filePath of collectFiles(projectRoot, projectRoot, includeFns, excludeFns)) {
|
|
2084
|
+
totalFiles++;
|
|
2085
|
+
if (cache2) {
|
|
2086
|
+
const relPath = path2.relative(projectRoot, filePath);
|
|
2087
|
+
analyzedRelPaths.add(relPath);
|
|
2088
|
+
try {
|
|
2089
|
+
const source = await readFile2(filePath, "utf-8");
|
|
2090
|
+
const hash = fileHash(source);
|
|
2091
|
+
const cached = getCachedResult(cache2, relPath, hash);
|
|
2092
|
+
if (cached) {
|
|
2093
|
+
cacheHits++;
|
|
2094
|
+
cachedIssues.push(...cached);
|
|
2095
|
+
continue;
|
|
2096
|
+
}
|
|
2097
|
+
batch.push({ filePath, source, projectRoot });
|
|
2098
|
+
} catch {
|
|
2099
|
+
batch.push({ filePath, projectRoot });
|
|
2100
|
+
}
|
|
2101
|
+
} else {
|
|
2102
|
+
batch.push({ filePath, projectRoot });
|
|
2103
|
+
}
|
|
2104
|
+
if (batch.length >= BATCH_SIZE) {
|
|
2105
|
+
const batchResults = await Promise.allSettled(
|
|
2106
|
+
batch.map(
|
|
2107
|
+
(workItem) => raceWithTimeout(
|
|
2108
|
+
pool.run(workItem),
|
|
2109
|
+
WORKER_TIMEOUT_MS,
|
|
2110
|
+
`Worker timed out after ${WORKER_TIMEOUT_MS}ms`
|
|
2111
|
+
)
|
|
2112
|
+
)
|
|
2113
|
+
);
|
|
2114
|
+
allResults.push(...batchResults);
|
|
2115
|
+
allFilePaths.push(...batch.map((w) => w.filePath));
|
|
2116
|
+
batch = [];
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
if (batch.length > 0) {
|
|
2120
|
+
const batchResults = await Promise.allSettled(
|
|
2121
|
+
batch.map(
|
|
2122
|
+
(workItem) => raceWithTimeout(
|
|
2123
|
+
pool.run(workItem),
|
|
2124
|
+
WORKER_TIMEOUT_MS,
|
|
2125
|
+
`Worker timed out after ${WORKER_TIMEOUT_MS}ms`
|
|
2126
|
+
)
|
|
2127
|
+
)
|
|
2128
|
+
);
|
|
2129
|
+
allResults.push(...batchResults);
|
|
2130
|
+
allFilePaths.push(...batch.map((w) => w.filePath));
|
|
2131
|
+
}
|
|
2132
|
+
} finally {
|
|
2133
|
+
await raceWithTimeout(pool.destroy(), 1e4, "Worker pool destroy timed out after 10s").catch(
|
|
2134
|
+
(err) => {
|
|
2135
|
+
warn(
|
|
2136
|
+
"core",
|
|
2137
|
+
`Failed to destroy worker pool: ${err instanceof Error ? err.message : String(err)}`
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
);
|
|
2141
|
+
}
|
|
2142
|
+
if (totalFiles === 0) {
|
|
2143
|
+
return {
|
|
2144
|
+
issues: [],
|
|
2145
|
+
filesAnalyzed: 0,
|
|
2146
|
+
durationMs: performance.now() - start,
|
|
2147
|
+
cacheHits: 0
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
const issues = [...cachedIssues];
|
|
2151
|
+
let failedCount = 0;
|
|
2152
|
+
let parseErrorCount = 0;
|
|
2153
|
+
for (const [i, result] of allResults.entries()) {
|
|
2154
|
+
if (result.status === "fulfilled") {
|
|
2155
|
+
const output = result.value;
|
|
2156
|
+
issues.push(...output.issues);
|
|
2157
|
+
if (output.parseError) parseErrorCount++;
|
|
2158
|
+
if (cache2 && output.sourceHash) {
|
|
2159
|
+
setCacheEntry(cache2, output.filePath, output.sourceHash, output.issues);
|
|
2160
|
+
}
|
|
2161
|
+
} else {
|
|
2162
|
+
failedCount++;
|
|
2163
|
+
const fp = allFilePaths[i] ?? "unknown";
|
|
2164
|
+
const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
2165
|
+
warn("static", `Failed: ${path2.relative(projectRoot, fp)} \u2014 ${reason}`);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
if (failedCount > 0) {
|
|
2169
|
+
warn("static", `${failedCount}/${allResults.length} file(s) failed to analyze (see above)`);
|
|
2170
|
+
}
|
|
2171
|
+
if (parseErrorCount > 0) {
|
|
2172
|
+
warn(
|
|
2173
|
+
"static",
|
|
2174
|
+
`${parseErrorCount}/${allResults.length} file(s) could not be parsed (syntax errors or binary files)`
|
|
2175
|
+
);
|
|
2176
|
+
}
|
|
2177
|
+
if (cache2) {
|
|
2178
|
+
for (const key of Object.keys(cache2.entries)) {
|
|
2179
|
+
if (!analyzedRelPaths.has(key)) delete cache2.entries[key];
|
|
2180
|
+
}
|
|
2181
|
+
try {
|
|
2182
|
+
await saveCache(projectRoot, cache2);
|
|
2183
|
+
} catch (err) {
|
|
2184
|
+
warn(
|
|
2185
|
+
"static",
|
|
2186
|
+
`Failed to save analysis cache: ${err instanceof Error ? err.message : String(err)}`
|
|
2187
|
+
);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
return {
|
|
2191
|
+
issues,
|
|
2192
|
+
filesAnalyzed: totalFiles,
|
|
2193
|
+
durationMs: performance.now() - start,
|
|
2194
|
+
cacheHits
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
// src/analyzer/index.ts
|
|
2199
|
+
var TOOL_VERSION2 = (true ? "1.0.0" : null) ?? "0.1.0";
|
|
2200
|
+
function resolveCriterionStatus(criterionId, issuesByCriterion, exemptedCriteria) {
|
|
2201
|
+
const criterion = CRITERIA_BY_ID[criterionId];
|
|
2202
|
+
if (!criterion) {
|
|
2203
|
+
warn("core", `Unknown criterion ID: ${criterionId}`);
|
|
2204
|
+
return {
|
|
2205
|
+
id: criterionId,
|
|
2206
|
+
status: "not-applicable",
|
|
2207
|
+
issueCount: 0,
|
|
2208
|
+
testResults: []
|
|
2209
|
+
};
|
|
2210
|
+
}
|
|
2211
|
+
if (exemptedCriteria.has(criterionId)) {
|
|
2212
|
+
return {
|
|
2213
|
+
id: criterionId,
|
|
2214
|
+
status: "not-applicable",
|
|
2215
|
+
issueCount: 0,
|
|
2216
|
+
testResults: []
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
const criterionIssues = issuesByCriterion.get(criterionId) ?? [];
|
|
2220
|
+
if (criterion.automationLevel === "manual") {
|
|
2221
|
+
const manualStatus = "needs-review";
|
|
2222
|
+
return {
|
|
2223
|
+
id: criterionId,
|
|
2224
|
+
status: "needs-review",
|
|
2225
|
+
issueCount: 0,
|
|
2226
|
+
testResults: criterion.tests.map((t) => ({
|
|
2227
|
+
id: t.id,
|
|
2228
|
+
status: manualStatus
|
|
2229
|
+
}))
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
const hasErrors = criterionIssues.some((i) => i.severity === "error");
|
|
2233
|
+
const status = hasErrors ? "invalidated" : "validated";
|
|
2234
|
+
const axeIssues = [];
|
|
2235
|
+
const issuesByTest = /* @__PURE__ */ new Map();
|
|
2236
|
+
for (const issue of criterionIssues) {
|
|
2237
|
+
if (issue.testId.startsWith(AXE_TEST_ID_PREFIX)) {
|
|
2238
|
+
axeIssues.push(issue);
|
|
2239
|
+
} else {
|
|
2240
|
+
const arr = issuesByTest.get(issue.testId);
|
|
2241
|
+
if (arr) arr.push(issue);
|
|
2242
|
+
else issuesByTest.set(issue.testId, [issue]);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
const hasAxeError = axeIssues.some((i) => i.severity === "error");
|
|
2246
|
+
const testResults = criterion.tests.map((t) => {
|
|
2247
|
+
const reviewStatus = "needs-review";
|
|
2248
|
+
if (!t.automated) return { id: t.id, status: reviewStatus };
|
|
2249
|
+
const testIssues = issuesByTest.get(t.id);
|
|
2250
|
+
const hasFail = (testIssues?.some((i) => i.severity === "error") ?? false) || hasAxeError;
|
|
2251
|
+
const testStatus = hasFail ? "fail" : "pass";
|
|
2252
|
+
return { id: t.id, status: testStatus };
|
|
2253
|
+
});
|
|
2254
|
+
return {
|
|
2255
|
+
id: criterionId,
|
|
2256
|
+
status,
|
|
2257
|
+
issueCount: criterionIssues.length,
|
|
2258
|
+
testResults
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
function buildReport(issues, pages, config) {
|
|
2262
|
+
const exemptedCriteria = new Set((config.exemptions ?? []).map((e) => e.criterion));
|
|
2263
|
+
const issuesByCriterion = /* @__PURE__ */ new Map();
|
|
2264
|
+
for (const issue of issues) {
|
|
2265
|
+
const arr = issuesByCriterion.get(issue.criterionId);
|
|
2266
|
+
if (arr) arr.push(issue);
|
|
2267
|
+
else issuesByCriterion.set(issue.criterionId, [issue]);
|
|
2268
|
+
}
|
|
2269
|
+
const themeResults = THEMES.map((theme) => {
|
|
2270
|
+
const criteriaResults = theme.criteria.map(
|
|
2271
|
+
(c) => resolveCriterionStatus(c.id, issuesByCriterion, exemptedCriteria)
|
|
2272
|
+
);
|
|
2273
|
+
const applicableCriteria = criteriaResults.filter(
|
|
2274
|
+
(c) => c.status !== "not-applicable" && c.status !== "needs-review"
|
|
2275
|
+
);
|
|
2276
|
+
const validatedCount = applicableCriteria.filter((c) => c.status === "validated").length;
|
|
2277
|
+
const complianceRate2 = applicableCriteria.length > 0 ? Math.max(0, Math.min(1, validatedCount / applicableCriteria.length)) : 0;
|
|
2278
|
+
return { id: theme.id, complianceRate: complianceRate2, criteriaResults };
|
|
2279
|
+
});
|
|
2280
|
+
let applicable = 0;
|
|
2281
|
+
let validated = 0;
|
|
2282
|
+
let invalidated = 0;
|
|
2283
|
+
let notApplicable = 0;
|
|
2284
|
+
let needsReview = 0;
|
|
2285
|
+
for (const theme of themeResults) {
|
|
2286
|
+
for (const criterion of theme.criteriaResults) {
|
|
2287
|
+
switch (criterion.status) {
|
|
2288
|
+
case "validated":
|
|
2289
|
+
applicable++;
|
|
2290
|
+
validated++;
|
|
2291
|
+
break;
|
|
2292
|
+
case "invalidated":
|
|
2293
|
+
applicable++;
|
|
2294
|
+
invalidated++;
|
|
2295
|
+
break;
|
|
2296
|
+
case "not-applicable":
|
|
2297
|
+
notApplicable++;
|
|
2298
|
+
break;
|
|
2299
|
+
case "needs-review":
|
|
2300
|
+
needsReview++;
|
|
2301
|
+
break;
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
const complianceRate = applicable > 0 ? Math.max(0, Math.min(1, validated / applicable)) : 0;
|
|
2306
|
+
return {
|
|
2307
|
+
meta: {
|
|
2308
|
+
rgaaVersion: "4.1.2",
|
|
2309
|
+
toolVersion: TOOL_VERSION2,
|
|
2310
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2311
|
+
...config.projectName !== void 0 ? { projectName: config.projectName } : {},
|
|
2312
|
+
analyzedPages: config.pages.map((p) => p.path),
|
|
2313
|
+
locale: config.locale ?? "en-US"
|
|
2314
|
+
},
|
|
2315
|
+
summary: {
|
|
2316
|
+
totalCriteria: TOTAL_CRITERIA,
|
|
2317
|
+
applicable,
|
|
2318
|
+
validated,
|
|
2319
|
+
invalidated,
|
|
2320
|
+
notApplicable,
|
|
2321
|
+
needsReview,
|
|
2322
|
+
complianceRate
|
|
2323
|
+
},
|
|
2324
|
+
themes: themeResults,
|
|
2325
|
+
pages,
|
|
2326
|
+
issues
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
async function analyze(config, options = {}) {
|
|
2330
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
2331
|
+
const issueArrays = [];
|
|
2332
|
+
let pageResults = [];
|
|
2333
|
+
if (!options.runtimeOnly) {
|
|
2334
|
+
const staticResult = await runStaticAnalysis(projectRoot, config.static);
|
|
2335
|
+
issueArrays.push(staticResult.issues);
|
|
2336
|
+
}
|
|
2337
|
+
if (!options.staticOnly) {
|
|
2338
|
+
try {
|
|
2339
|
+
const runtimeResult = await runRuntimeAnalysis(
|
|
2340
|
+
config.baseUrl,
|
|
2341
|
+
config.pages,
|
|
2342
|
+
config.exemptions,
|
|
2343
|
+
options.signal
|
|
2344
|
+
);
|
|
2345
|
+
issueArrays.push(runtimeResult.issues);
|
|
2346
|
+
pageResults = runtimeResult.pages;
|
|
2347
|
+
} catch (err) {
|
|
2348
|
+
warn(
|
|
2349
|
+
"core",
|
|
2350
|
+
`Runtime analysis failed: ${err instanceof Error ? err.message : String(err)}. Continuing with static-only results.`
|
|
2351
|
+
);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
const allIssues = [].concat(...issueArrays);
|
|
2355
|
+
return buildReport(allIssues, pageResults, config);
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
// src/config/schema.ts
|
|
2359
|
+
import { z } from "zod";
|
|
2360
|
+
var PageConfigSchema = z.object({
|
|
2361
|
+
path: z.string().startsWith("/", { message: 'Page path must start with "/"' }),
|
|
2362
|
+
name: z.string().optional()
|
|
2363
|
+
});
|
|
2364
|
+
var OutputConfigSchema = z.object({
|
|
2365
|
+
format: z.enum(["json", "html", "sarif", "markdown", "junit"]),
|
|
2366
|
+
path: z.string().min(1, { message: "Output path must not be empty" }),
|
|
2367
|
+
minify: z.boolean().optional().default(false)
|
|
2368
|
+
});
|
|
2369
|
+
var ThresholdConfigSchema = z.object({
|
|
2370
|
+
/**
|
|
2371
|
+
* Minimum compliance rate from 0 to 100.
|
|
2372
|
+
* 0 means CI/CD is never blocked — the report is still generated.
|
|
2373
|
+
*/
|
|
2374
|
+
complianceRate: z.number().min(0, { message: "complianceRate must be >= 0" }).max(100, { message: "complianceRate must be <= 100" }).optional().default(0),
|
|
2375
|
+
failOn: z.enum(["error", "threshold", "none"]).optional().default("threshold")
|
|
2376
|
+
}).optional();
|
|
2377
|
+
var DEFAULT_STATIC_INCLUDE = ["src/**/*.{tsx,jsx,ts,js}"];
|
|
2378
|
+
var DEFAULT_STATIC_EXCLUDE = ["**/*.test.*", "**/*.spec.*", "**/node_modules/**"];
|
|
2379
|
+
var StaticConfigSchema = z.object({
|
|
2380
|
+
include: z.array(z.string()).optional().default(DEFAULT_STATIC_INCLUDE),
|
|
2381
|
+
exclude: z.array(z.string()).optional().default(DEFAULT_STATIC_EXCLUDE)
|
|
2382
|
+
}).optional();
|
|
2383
|
+
var CRITERION_FORMAT_RE = /^\d+\.\d+$/;
|
|
2384
|
+
var ExemptionConfigSchema = z.object({
|
|
2385
|
+
criterion: z.string().regex(CRITERION_FORMAT_RE, { message: 'criterion must be in the format "X.Y" (e.g., "4.1")' }),
|
|
2386
|
+
reason: z.string().min(10, { message: "Exemption reason must be at least 10 characters" })
|
|
2387
|
+
});
|
|
2388
|
+
var KodaRGAAConfigSchema = z.object({
|
|
2389
|
+
baseUrl: z.string().url({ message: "baseUrl must be a valid URL (e.g., http://localhost:3000)" }).refine((url) => /^https?:\/\//.test(url), {
|
|
2390
|
+
message: "baseUrl must use http:// or https:// protocol"
|
|
2391
|
+
}),
|
|
2392
|
+
pages: z.array(PageConfigSchema).min(1, { message: "At least one page must be configured" }),
|
|
2393
|
+
output: z.array(OutputConfigSchema).min(1, { message: "At least one output format must be configured" }),
|
|
2394
|
+
thresholds: ThresholdConfigSchema,
|
|
2395
|
+
exemptions: z.array(ExemptionConfigSchema).optional().default([]),
|
|
2396
|
+
locale: z.string().optional().default("en-US"),
|
|
2397
|
+
projectName: z.string().optional(),
|
|
2398
|
+
static: StaticConfigSchema
|
|
2399
|
+
});
|
|
2400
|
+
|
|
2401
|
+
// src/config/loader.ts
|
|
2402
|
+
import { existsSync as existsSync2, realpathSync } from "fs";
|
|
2403
|
+
import path3 from "path";
|
|
2404
|
+
import { pathToFileURL } from "url";
|
|
2405
|
+
var CONFIG_FILE_NAMES = [
|
|
2406
|
+
"rgaa.config.ts",
|
|
2407
|
+
"rgaa.config.js",
|
|
2408
|
+
"rgaa.config.mjs",
|
|
2409
|
+
"rgaa.config.json"
|
|
2410
|
+
];
|
|
2411
|
+
function resolveConfigPath(cwd, explicitPath) {
|
|
2412
|
+
if (explicitPath) {
|
|
2413
|
+
const resolved = path3.resolve(cwd, explicitPath);
|
|
2414
|
+
if (!existsSync2(resolved)) {
|
|
2415
|
+
throw new ConfigError(`Configuration file not found: ${resolved}`);
|
|
2416
|
+
}
|
|
2417
|
+
const real = realpathSync(resolved);
|
|
2418
|
+
const realCwd = realpathSync(cwd);
|
|
2419
|
+
if (!real.startsWith(realCwd + path3.sep) && real !== realCwd) {
|
|
2420
|
+
throw new ConfigError(`Config path must be within project directory: ${explicitPath}`);
|
|
2421
|
+
}
|
|
2422
|
+
return real;
|
|
2423
|
+
}
|
|
2424
|
+
for (const name of CONFIG_FILE_NAMES) {
|
|
2425
|
+
const candidate = path3.join(cwd, name);
|
|
2426
|
+
if (existsSync2(candidate)) {
|
|
2427
|
+
return candidate;
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
return null;
|
|
2431
|
+
}
|
|
2432
|
+
async function loadConfig(cwd = process.cwd(), explicitPath) {
|
|
2433
|
+
const configPath = resolveConfigPath(cwd, explicitPath);
|
|
2434
|
+
if (!configPath) {
|
|
2435
|
+
throw new ConfigError(
|
|
2436
|
+
"No configuration file found. Run `eqo init` to create one, or specify the path with --config."
|
|
2437
|
+
);
|
|
2438
|
+
}
|
|
2439
|
+
let raw;
|
|
2440
|
+
if (configPath.endsWith(".json")) {
|
|
2441
|
+
const { readFileSync } = await import("fs");
|
|
2442
|
+
raw = JSON.parse(readFileSync(configPath, "utf-8"), (key, value) => {
|
|
2443
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
2444
|
+
return void 0;
|
|
2445
|
+
}
|
|
2446
|
+
return value;
|
|
2447
|
+
});
|
|
2448
|
+
} else if (configPath.endsWith(".ts")) {
|
|
2449
|
+
let createJiti;
|
|
2450
|
+
try {
|
|
2451
|
+
({ createJiti } = await import("jiti"));
|
|
2452
|
+
} catch {
|
|
2453
|
+
throw new ConfigError(
|
|
2454
|
+
"Loading .ts config files requires jiti.\nInstall it: npm install jiti\nOr use a .js / .mjs config file instead."
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
const jiti = createJiti(import.meta.url);
|
|
2458
|
+
const mod = await jiti.import(configPath);
|
|
2459
|
+
raw = mod.default ?? mod;
|
|
2460
|
+
} else {
|
|
2461
|
+
const fileUrl = pathToFileURL(configPath).href;
|
|
2462
|
+
const mod = await import(fileUrl);
|
|
2463
|
+
raw = mod.default ?? mod;
|
|
2464
|
+
}
|
|
2465
|
+
const result = KodaRGAAConfigSchema.safeParse(raw);
|
|
2466
|
+
if (!result.success) {
|
|
2467
|
+
const messages = result.error.issues.map((issue) => ` \u2022 ${issue.path.join(".")}: ${issue.message}`).join("\n");
|
|
2468
|
+
throw new ConfigError(`Invalid configuration:
|
|
2469
|
+
${messages}`);
|
|
2470
|
+
}
|
|
2471
|
+
return result.data;
|
|
2472
|
+
}
|
|
2473
|
+
function generateDefaultConfig(options) {
|
|
2474
|
+
const baseUrl = options?.baseUrl ?? "http://localhost:3000";
|
|
2475
|
+
const projectName = options?.projectName ?? "my-project";
|
|
2476
|
+
const locale = options?.locale ?? "en-US";
|
|
2477
|
+
return `import { defineConfig } from "@kodalabs-io/eqo";
|
|
2478
|
+
|
|
2479
|
+
export default defineConfig({
|
|
2480
|
+
baseUrl: ${JSON.stringify(baseUrl)},
|
|
2481
|
+
projectName: ${JSON.stringify(projectName)},
|
|
2482
|
+
locale: ${JSON.stringify(locale)},
|
|
2483
|
+
|
|
2484
|
+
pages: [
|
|
2485
|
+
{ path: "/", name: "Home" },
|
|
2486
|
+
],
|
|
2487
|
+
|
|
2488
|
+
output: [
|
|
2489
|
+
// JSON report \u2014 consumed by your /accessibility Next.js page
|
|
2490
|
+
{ format: "json", path: "./public/rgaa-report.json", minify: false },
|
|
2491
|
+
// HTML report \u2014 open in a browser for a visual overview
|
|
2492
|
+
{ format: "html", path: "./reports/rgaa.html" },
|
|
2493
|
+
// SARIF \u2014 integrates with GitHub Code Scanning to annotate PRs
|
|
2494
|
+
{ format: "sarif", path: "./reports/rgaa.sarif" },
|
|
2495
|
+
// Markdown \u2014 suitable for PR comments
|
|
2496
|
+
// { format: "markdown", path: "./reports/rgaa.md" },
|
|
2497
|
+
],
|
|
2498
|
+
|
|
2499
|
+
thresholds: {
|
|
2500
|
+
// Set complianceRate to 0 to never block CI (report is still generated).
|
|
2501
|
+
// Set to 80 to fail CI if fewer than 80% of auto-checkable criteria pass.
|
|
2502
|
+
complianceRate: 0,
|
|
2503
|
+
failOn: "threshold",
|
|
2504
|
+
},
|
|
2505
|
+
|
|
2506
|
+
// Mark criteria that do not apply to your project (with justification).
|
|
2507
|
+
exemptions: [
|
|
2508
|
+
// { criterion: "4.1", reason: "No video content on this site." },
|
|
2509
|
+
],
|
|
2510
|
+
|
|
2511
|
+
static: {
|
|
2512
|
+
include: ["src/**/*.{tsx,jsx,ts,js}"],
|
|
2513
|
+
exclude: ["**/*.test.*", "**/*.spec.*"],
|
|
2514
|
+
},
|
|
2515
|
+
});
|
|
2516
|
+
`;
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
// src/i18n/index.ts
|
|
2520
|
+
var localeLoaders = {
|
|
2521
|
+
"en-US": () => import("./en-US-JQN64XYI.js"),
|
|
2522
|
+
"fr-FR": () => import("./fr-FR-NZKLCQE5.js")
|
|
2523
|
+
};
|
|
2524
|
+
var cache = /* @__PURE__ */ new Map();
|
|
2525
|
+
async function loadTranslations(locale) {
|
|
2526
|
+
if (cache.has(locale)) {
|
|
2527
|
+
return cache.get(locale);
|
|
2528
|
+
}
|
|
2529
|
+
const loader = localeLoaders[locale] ?? localeLoaders["en-US"];
|
|
2530
|
+
if (!loader) {
|
|
2531
|
+
throw new Error(`[eqo] No translation loader found for locale "${locale}"`);
|
|
2532
|
+
}
|
|
2533
|
+
const mod = await loader();
|
|
2534
|
+
cache.set(locale, mod.default);
|
|
2535
|
+
return mod.default;
|
|
2536
|
+
}
|
|
2537
|
+
function getTranslations(locale) {
|
|
2538
|
+
const t = cache.get(locale) ?? cache.get("en-US");
|
|
2539
|
+
if (!t) {
|
|
2540
|
+
throw new Error(`[eqo] Translations not loaded. Call loadTranslations("${locale}") first.`);
|
|
2541
|
+
}
|
|
2542
|
+
return t;
|
|
2543
|
+
}
|
|
2544
|
+
function interpolate(template, context) {
|
|
2545
|
+
if (!context) return template;
|
|
2546
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => context[key] ?? `{${key}}`);
|
|
2547
|
+
}
|
|
2548
|
+
function getSupportedLocales() {
|
|
2549
|
+
return Object.keys(localeLoaders);
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
// src/reporter/issue-format.ts
|
|
2553
|
+
var MAX_URL_LENGTH = 2048;
|
|
2554
|
+
var SAFE_URL = /^https?:\/\/[\w][\w.-]*\.[a-z]{2,}[^\s"'<>]*$/;
|
|
2555
|
+
function resolveIssueMessage(issue, t) {
|
|
2556
|
+
return interpolate(
|
|
2557
|
+
t.issues[issue.messageKey] ?? issue.messageContext?.help ?? issue.messageKey,
|
|
2558
|
+
issue.messageContext
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
function resolveRemediation(issue, t) {
|
|
2562
|
+
const text = t.remediation[issue.remediationKey] ?? issue.messageContext?.helpUrl ?? issue.remediationKey;
|
|
2563
|
+
return { text, isUrl: text.length <= MAX_URL_LENGTH && SAFE_URL.test(text) };
|
|
2564
|
+
}
|
|
2565
|
+
function getRemediationLinkText(url) {
|
|
2566
|
+
return url.includes("dequeuniversity") ? "Documentation axe-core" : "Documentation";
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
// src/reporter/thresholds.ts
|
|
2570
|
+
function getComplianceLevel(pct) {
|
|
2571
|
+
if (pct >= 80) return "green";
|
|
2572
|
+
if (pct >= 60) return "yellow";
|
|
2573
|
+
return "red";
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
// src/reporter/write-output.ts
|
|
2577
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
2578
|
+
import path4 from "path";
|
|
2579
|
+
async function writeOutputFile(filePath, content) {
|
|
2580
|
+
await mkdir2(path4.dirname(filePath), { recursive: true });
|
|
2581
|
+
await writeFile2(filePath, content, "utf-8");
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
// src/reporter/html.ts
|
|
2585
|
+
function statusClass(status) {
|
|
2586
|
+
switch (status) {
|
|
2587
|
+
case "validated":
|
|
2588
|
+
return "status-pass";
|
|
2589
|
+
case "invalidated":
|
|
2590
|
+
return "status-fail";
|
|
2591
|
+
case "not-applicable":
|
|
2592
|
+
return "status-na";
|
|
2593
|
+
default:
|
|
2594
|
+
return "status-review";
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
function statusLabel(status, t) {
|
|
2598
|
+
return t.criterionStatus[status] ?? status;
|
|
2599
|
+
}
|
|
2600
|
+
function complianceColor(pctValue) {
|
|
2601
|
+
const level = getComplianceLevel(pctValue);
|
|
2602
|
+
return level === "green" ? "#22c55e" : level === "yellow" ? "#f59e0b" : "#ef4444";
|
|
2603
|
+
}
|
|
2604
|
+
async function writeHtmlReport(report, outputConfig) {
|
|
2605
|
+
const t = getTranslations(report.meta.locale);
|
|
2606
|
+
const compliancePct = Math.round(report.summary.complianceRate * 100);
|
|
2607
|
+
const rateColor = complianceColor(compliancePct);
|
|
2608
|
+
const themeRows = report.themes.map((theme) => {
|
|
2609
|
+
const themePct = Math.round(theme.complianceRate * 100);
|
|
2610
|
+
const themeName = t.themes[theme.id] ?? `Theme ${theme.id}`;
|
|
2611
|
+
const criteriaRows = theme.criteriaResults.map((c) => {
|
|
2612
|
+
const title = escapeHtml(t.criteria[c.id] ?? c.id);
|
|
2613
|
+
const st = statusClass(c.status);
|
|
2614
|
+
const label = statusLabel(c.status, t);
|
|
2615
|
+
const issuesBadge = c.issueCount > 0 ? `<span class="badge badge-error">${c.issueCount}</span>` : "";
|
|
2616
|
+
return `<tr><th scope="row"><code>${c.id}</code></th><td>${title}</td><td><span class="${st}">${label}</span>${issuesBadge}</td></tr>`;
|
|
2617
|
+
});
|
|
2618
|
+
return `
|
|
2619
|
+
<details class="theme-block">
|
|
2620
|
+
<summary>
|
|
2621
|
+
<span class="theme-name">${theme.id}. ${escapeHtml(themeName)}</span>
|
|
2622
|
+
<span class="theme-rate" style="color:${complianceColor(themePct)}">${themePct}%</span>
|
|
2623
|
+
</summary>
|
|
2624
|
+
<table class="criteria-table">
|
|
2625
|
+
<thead><tr><th scope="col">ID</th><th scope="col">${escapeHtml(
|
|
2626
|
+
t.report.criterion
|
|
2627
|
+
)}</th><th scope="col">Status</th></tr></thead>
|
|
2628
|
+
<tbody>${criteriaRows.join("")}</tbody>
|
|
2629
|
+
</table>
|
|
2630
|
+
</details>`;
|
|
2631
|
+
}).join("");
|
|
2632
|
+
const MAX_HTML_ISSUES = 500;
|
|
2633
|
+
const displayedIssues = report.issues.slice(0, MAX_HTML_ISSUES);
|
|
2634
|
+
const truncatedNotice = report.issues.length > MAX_HTML_ISSUES ? `<p class="truncated" style="padding:1rem;text-align:center;color:#64748b;font-style:italic">${report.issues.length - MAX_HTML_ISSUES} more issues omitted. See JSON report for the full list.</p>` : "";
|
|
2635
|
+
const issuesSection = report.issues.length === 0 ? `<p class="no-issues">\u2705 ${escapeHtml(t.report.noIssues)}</p>` : displayedIssues.map((issue) => {
|
|
2636
|
+
const msg = escapeHtml(resolveIssueMessage(issue, t));
|
|
2637
|
+
const { text: rawFix, isUrl } = resolveRemediation(issue, t);
|
|
2638
|
+
const linkText = getRemediationLinkText(rawFix);
|
|
2639
|
+
const fix = isUrl ? `<a href="${escapeHtml(
|
|
2640
|
+
rawFix
|
|
2641
|
+
)}" target="_blank" rel="noopener noreferrer">${linkText} \u2197</a>` : escapeHtml(interpolate(rawFix, issue.messageContext));
|
|
2642
|
+
const location = [
|
|
2643
|
+
issue.file && `<code>${escapeHtml(issue.file)}${issue.line ? `:${issue.line}` : ""}</code>`,
|
|
2644
|
+
issue.page && `Page: <code>${escapeHtml(issue.page)}</code>`
|
|
2645
|
+
].filter(Boolean).join(" ");
|
|
2646
|
+
const element = issue.element ? `<pre class="element-preview">${escapeHtml(issue.element)}</pre>` : "";
|
|
2647
|
+
return `
|
|
2648
|
+
<div class="issue issue-${issue.severity}">
|
|
2649
|
+
<div class="issue-header">
|
|
2650
|
+
<span class="issue-criterion">RGAA ${escapeHtml(issue.criterionId)}</span>
|
|
2651
|
+
<span class="issue-severity severity-${issue.severity}">${escapeHtml(
|
|
2652
|
+
t.severity[issue.severity] ?? issue.severity
|
|
2653
|
+
)}</span>
|
|
2654
|
+
<span class="issue-phase">${escapeHtml(issue.phase)}</span>
|
|
2655
|
+
</div>
|
|
2656
|
+
<p class="issue-message">${msg}</p>
|
|
2657
|
+
${element}
|
|
2658
|
+
<p class="issue-location">${location}</p>
|
|
2659
|
+
<p class="issue-fix">\u{1F4A1} ${fix}</p>
|
|
2660
|
+
</div>`;
|
|
2661
|
+
}).join("") + truncatedNotice;
|
|
2662
|
+
const html = `<!DOCTYPE html>
|
|
2663
|
+
<html lang="${escapeHtml(report.meta.locale.split("-")[0] ?? "en")}">
|
|
2664
|
+
<head>
|
|
2665
|
+
<meta charset="UTF-8">
|
|
2666
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2667
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data: https:">
|
|
2668
|
+
<title>${escapeHtml(t.report.title)}${report.meta.projectName ? ` \u2014 ${escapeHtml(report.meta.projectName)}` : ""}</title>
|
|
2669
|
+
<style>
|
|
2670
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
2671
|
+
body { font-family: system-ui, -apple-system, sans-serif; background: #f8fafc; color: #1e293b; line-height: 1.6; }
|
|
2672
|
+
.container { max-width: 1100px; margin: 0 auto; padding: 2rem 1rem; }
|
|
2673
|
+
header { background: #1e293b; color: #f8fafc; padding: 2rem; border-radius: 12px; margin-bottom: 2rem; }
|
|
2674
|
+
header h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
|
|
2675
|
+
header .meta { font-size: 0.875rem; opacity: 0.8; }
|
|
2676
|
+
.score-card { background: #fff; border-radius: 12px; padding: 2rem; margin-bottom: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,.1); display: grid; grid-template-columns: auto 1fr; gap: 2rem; align-items: center; }
|
|
2677
|
+
.score-ring { width: 120px; height: 120px; border-radius: 50%; background: conic-gradient(${rateColor} ${compliancePct}%, #e2e8f0 0); display: flex; align-items: center; justify-content: center; font-size: 1.5rem; font-weight: 700; color: ${rateColor}; }
|
|
2678
|
+
.score-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 1rem; }
|
|
2679
|
+
.stat { text-align: center; }
|
|
2680
|
+
.stat .val { font-size: 1.5rem; font-weight: 700; }
|
|
2681
|
+
.stat .lbl { font-size: 0.75rem; color: #64748b; text-transform: uppercase; }
|
|
2682
|
+
.section-title { font-size: 1.25rem; font-weight: 600; margin: 2rem 0 1rem; }
|
|
2683
|
+
.theme-block { background: #fff; border-radius: 8px; margin-bottom: 0.5rem; box-shadow: 0 1px 2px rgba(0,0,0,.05); overflow: hidden; }
|
|
2684
|
+
.theme-block summary { cursor: pointer; padding: 1rem 1.25rem; display: flex; justify-content: space-between; align-items: center; user-select: none; }
|
|
2685
|
+
.theme-block summary:hover { background: #f1f5f9; }
|
|
2686
|
+
.theme-name { font-weight: 600; }
|
|
2687
|
+
.theme-rate { font-weight: 700; font-size: 1rem; }
|
|
2688
|
+
.criteria-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
|
|
2689
|
+
.criteria-table th { background: #f8fafc; padding: 0.5rem 1rem; text-align: left; font-weight: 600; border-bottom: 1px solid #e2e8f0; }
|
|
2690
|
+
.criteria-table td { padding: 0.5rem 1rem; border-bottom: 1px solid #f1f5f9; vertical-align: top; }
|
|
2691
|
+
:focus { outline: 3px solid #3b82f6; outline-offset: 2px; }
|
|
2692
|
+
:focus:not(:focus-visible) { outline: none; }
|
|
2693
|
+
:focus-visible { outline: 3px solid #3b82f6; outline-offset: 2px; }
|
|
2694
|
+
.skip-link { position: absolute; left: -9999px; top: 0; z-index: 999; padding: 0.5rem 1rem; background: #1e293b; color: #f8fafc; text-decoration: none; border-radius: 0 0 4px 0; }
|
|
2695
|
+
.skip-link:focus { left: 0; }
|
|
2696
|
+
.status-pass { color: #16a34a; font-weight: 500; }
|
|
2697
|
+
.status-pass::before { content: "\u2713 "; }
|
|
2698
|
+
.status-fail { color: #dc2626; font-weight: 500; }
|
|
2699
|
+
.status-fail::before { content: "\u2717 "; }
|
|
2700
|
+
.status-na { color: #94a3b8; }
|
|
2701
|
+
.status-na::before { content: "\u2014 "; }
|
|
2702
|
+
.status-review { color: #d97706; font-weight: 500; }
|
|
2703
|
+
.status-review::before { content: "? "; }
|
|
2704
|
+
.badge { display: inline-block; padding: 0.125rem 0.5rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 700; margin-left: 0.5rem; }
|
|
2705
|
+
.badge-error { background: #fee2e2; color: #dc2626; }
|
|
2706
|
+
.issue { background: #fff; border-radius: 8px; margin-bottom: 1rem; border-left: 4px solid #e2e8f0; box-shadow: 0 1px 2px rgba(0,0,0,.05); overflow: hidden; }
|
|
2707
|
+
.issue-error { border-left-color: #dc2626; }
|
|
2708
|
+
.issue-warning { border-left-color: #f59e0b; }
|
|
2709
|
+
.issue-notice { border-left-color: #3b82f6; }
|
|
2710
|
+
.issue-header { display: flex; gap: 0.75rem; align-items: center; padding: 0.75rem 1rem; background: #f8fafc; border-bottom: 1px solid #f1f5f9; }
|
|
2711
|
+
.issue-criterion { font-weight: 700; font-size: 0.875rem; }
|
|
2712
|
+
.issue-severity { font-size: 0.75rem; padding: 0.125rem 0.5rem; border-radius: 9999px; font-weight: 600; }
|
|
2713
|
+
.severity-error { background: #fee2e2; color: #dc2626; }
|
|
2714
|
+
.severity-warning { background: #fef3c7; color: #d97706; }
|
|
2715
|
+
.severity-notice { background: #dbeafe; color: #2563eb; }
|
|
2716
|
+
.issue-phase { font-size: 0.75rem; color: #94a3b8; margin-left: auto; }
|
|
2717
|
+
.issue-message { padding: 0.75rem 1rem; font-weight: 500; }
|
|
2718
|
+
.element-preview { padding: 0.5rem 1rem; background: #f1f5f9; font-size: 0.8rem; overflow-x: auto; }
|
|
2719
|
+
.issue-location { padding: 0.25rem 1rem; font-size: 0.8rem; color: #64748b; }
|
|
2720
|
+
.issue-fix { padding: 0.5rem 1rem 0.75rem; font-size: 0.875rem; color: #166534; background: #f0fdf4; }
|
|
2721
|
+
.no-issues { padding: 2rem; text-align: center; color: #16a34a; font-size: 1.125rem; background: #fff; border-radius: 8px; }
|
|
2722
|
+
.disclaimer { font-size: 0.8rem; color: #64748b; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 1rem; margin-top: 2rem; }
|
|
2723
|
+
footer { text-align: center; padding: 2rem; color: #94a3b8; font-size: 0.8rem; }
|
|
2724
|
+
@media print {
|
|
2725
|
+
.skip-link { display: none; }
|
|
2726
|
+
body { background: #fff; color: #000; }
|
|
2727
|
+
.score-ring { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
|
|
2728
|
+
.issue { break-inside: avoid; }
|
|
2729
|
+
.theme-block { break-inside: avoid; }
|
|
2730
|
+
header { background: #1e293b; print-color-adjust: exact; -webkit-print-color-adjust: exact; }
|
|
2731
|
+
}
|
|
2732
|
+
</style>
|
|
2733
|
+
</head>
|
|
2734
|
+
<body>
|
|
2735
|
+
<a href="#main-content" class="skip-link">Skip to main content</a>
|
|
2736
|
+
<main class="container" id="main-content">
|
|
2737
|
+
<header>
|
|
2738
|
+
<h1>${escapeHtml(t.report.title)}</h1>
|
|
2739
|
+
<div class="meta">
|
|
2740
|
+
${report.meta.projectName ? `<strong>${escapeHtml(report.meta.projectName)}</strong> \u2014 ` : ""}
|
|
2741
|
+
${escapeHtml(t.report.generated)}: ${new Date(report.meta.generatedAt).toLocaleString()} \u2014
|
|
2742
|
+
@kodalabs-io/eqo v${escapeHtml(report.meta.toolVersion)}
|
|
2743
|
+
</div>
|
|
2744
|
+
</header>
|
|
2745
|
+
|
|
2746
|
+
<div class="score-card">
|
|
2747
|
+
<div class="score-ring" role="img" aria-label="Compliance: ${compliancePct}%">${compliancePct}%</div>
|
|
2748
|
+
<!-- <dl> pairs each value with its label for correct screen reader announcement -->
|
|
2749
|
+
<dl class="score-stats">
|
|
2750
|
+
<div class="stat"><dd class="val">${report.summary.totalCriteria}</dd><dt class="lbl">${escapeHtml(t.report.totalCriteria)}</dt></div>
|
|
2751
|
+
<div class="stat"><dd class="val" style="color:#16a34a">${report.summary.validated}</dd><dt class="lbl">${escapeHtml(t.report.validated)}</dt></div>
|
|
2752
|
+
<div class="stat"><dd class="val" style="color:#dc2626">${report.summary.invalidated}</dd><dt class="lbl">${escapeHtml(t.report.invalidated)}</dt></div>
|
|
2753
|
+
<div class="stat"><dd class="val" style="color:#94a3b8">${report.summary.notApplicable}</dd><dt class="lbl">${escapeHtml(t.report.notApplicable)}</dt></div>
|
|
2754
|
+
<div class="stat"><dd class="val" style="color:#d97706">${report.summary.needsReview}</dd><dt class="lbl">${escapeHtml(t.report.needsReview)}</dt></div>
|
|
2755
|
+
</dl>
|
|
2756
|
+
</div>
|
|
2757
|
+
|
|
2758
|
+
<h2 class="section-title" id="themes">${escapeHtml(t.report.themes)}</h2>
|
|
2759
|
+
${themeRows}
|
|
2760
|
+
|
|
2761
|
+
<h2 class="section-title" id="issues">${escapeHtml(t.report.issues)} (${report.issues.length})</h2>
|
|
2762
|
+
${issuesSection}
|
|
2763
|
+
|
|
2764
|
+
<div class="disclaimer">${escapeHtml(t.report.automationDisclaimer)}</div>
|
|
2765
|
+
|
|
2766
|
+
<footer>
|
|
2767
|
+
Generated by <a href="https://github.com/kodalabs-io/eqo">@kodalabs-io/eqo</a> v${escapeHtml(
|
|
2768
|
+
report.meta.toolVersion
|
|
2769
|
+
)}
|
|
2770
|
+
</footer>
|
|
2771
|
+
</main>
|
|
2772
|
+
</body>
|
|
2773
|
+
</html>`;
|
|
2774
|
+
await writeOutputFile(outputConfig.path, html);
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
// src/reporter/json.ts
|
|
2778
|
+
async function writeJsonReport(report, outputConfig) {
|
|
2779
|
+
let json;
|
|
2780
|
+
try {
|
|
2781
|
+
json = outputConfig.minify ? JSON.stringify(report) : JSON.stringify(report, null, 2);
|
|
2782
|
+
} catch (err) {
|
|
2783
|
+
throw new Error(
|
|
2784
|
+
`[eqo] Failed to serialize report to JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
2785
|
+
);
|
|
2786
|
+
}
|
|
2787
|
+
await writeOutputFile(outputConfig.path, json);
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
// src/reporter/junit.ts
|
|
2791
|
+
async function writeJunitReport(report, outputConfig) {
|
|
2792
|
+
const t = getTranslations(report.meta.locale);
|
|
2793
|
+
const totalTests = report.summary.applicable;
|
|
2794
|
+
const failures = report.summary.invalidated;
|
|
2795
|
+
const errors = report.issues.filter((i) => i.severity === "error").length;
|
|
2796
|
+
const timestamp = report.meta.generatedAt;
|
|
2797
|
+
const issuesByCriterion = /* @__PURE__ */ new Map();
|
|
2798
|
+
for (const issue of report.issues) {
|
|
2799
|
+
const arr = issuesByCriterion.get(issue.criterionId);
|
|
2800
|
+
if (arr) arr.push(issue);
|
|
2801
|
+
else issuesByCriterion.set(issue.criterionId, [issue]);
|
|
2802
|
+
}
|
|
2803
|
+
const testCases = [];
|
|
2804
|
+
for (const theme of report.themes) {
|
|
2805
|
+
for (const criterion of theme.criteriaResults) {
|
|
2806
|
+
const name = escapeXml(`RGAA ${criterion.id}`);
|
|
2807
|
+
const classname = escapeXml(`rgaa.theme${theme.id}`);
|
|
2808
|
+
if (criterion.status === "not-applicable") continue;
|
|
2809
|
+
if (criterion.status === "validated") {
|
|
2810
|
+
testCases.push(` <testcase name="${name}" classname="${classname}" />`);
|
|
2811
|
+
} else if (criterion.status === "needs-review") {
|
|
2812
|
+
testCases.push(
|
|
2813
|
+
` <testcase name="${name}" classname="${classname}">
|
|
2814
|
+
<skipped message="Requires manual review" />
|
|
2815
|
+
</testcase>`
|
|
2816
|
+
);
|
|
2817
|
+
} else {
|
|
2818
|
+
const criterionIssues = issuesByCriterion.get(criterion.id) ?? [];
|
|
2819
|
+
const messageParts = [];
|
|
2820
|
+
for (const i of criterionIssues) {
|
|
2821
|
+
const msg = resolveIssueMessage(i, t);
|
|
2822
|
+
const loc = i.file ? ` in ${i.file}${i.line ? `:${i.line}` : ""}` : "";
|
|
2823
|
+
messageParts.push(escapeXml(`[${i.severity.toUpperCase()}] ${msg}${loc}`));
|
|
2824
|
+
}
|
|
2825
|
+
testCases.push(
|
|
2826
|
+
` <testcase name="${name}" classname="${classname}">
|
|
2827
|
+
<failure message="${escapeXml(criterion.id)} violations" type="AccessibilityViolation">
|
|
2828
|
+
${messageParts.join("\n")}
|
|
2829
|
+
</failure>
|
|
2830
|
+
</testcase>`
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
const xml = [
|
|
2836
|
+
`<?xml version="1.0" encoding="UTF-8"?>`,
|
|
2837
|
+
`<testsuites name="RGAA 4.1.2" tests="${totalTests}" failures="${failures}" errors="${errors}" timestamp="${timestamp}">`,
|
|
2838
|
+
` <testsuite name="RGAA Accessibility" tests="${totalTests}" failures="${failures}" errors="${errors}" timestamp="${timestamp}">`,
|
|
2839
|
+
...testCases,
|
|
2840
|
+
" </testsuite>",
|
|
2841
|
+
"</testsuites>"
|
|
2842
|
+
].join("\n");
|
|
2843
|
+
await writeOutputFile(outputConfig.path, xml);
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
// src/reporter/markdown.ts
|
|
2847
|
+
function badge(rate) {
|
|
2848
|
+
const pct = Math.round(rate * 100);
|
|
2849
|
+
const level = getComplianceLevel(pct);
|
|
2850
|
+
const color = level === "green" ? "brightgreen" : level === "yellow" ? "yellow" : "red";
|
|
2851
|
+
return ``;
|
|
2852
|
+
}
|
|
2853
|
+
function statusIcon(status) {
|
|
2854
|
+
switch (status) {
|
|
2855
|
+
case "validated":
|
|
2856
|
+
return "\u2705 Validated";
|
|
2857
|
+
case "invalidated":
|
|
2858
|
+
return "\u274C Failed";
|
|
2859
|
+
case "not-applicable":
|
|
2860
|
+
return "\u2796 N/A";
|
|
2861
|
+
case "needs-review":
|
|
2862
|
+
return "\u26A0\uFE0F Review";
|
|
2863
|
+
default:
|
|
2864
|
+
return "\u2753 Unknown";
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
function severityIcon(severity) {
|
|
2868
|
+
switch (severity) {
|
|
2869
|
+
case "error":
|
|
2870
|
+
return "\u{1F534}";
|
|
2871
|
+
case "warning":
|
|
2872
|
+
return "\u{1F7E1}";
|
|
2873
|
+
default:
|
|
2874
|
+
return "\u{1F535}";
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
async function writeMarkdownReport(report, outputConfig) {
|
|
2878
|
+
const t = getTranslations(report.meta.locale);
|
|
2879
|
+
const pct = Math.round(report.summary.complianceRate * 100);
|
|
2880
|
+
const lines = [
|
|
2881
|
+
`# ${t.report.title}`,
|
|
2882
|
+
"",
|
|
2883
|
+
badge(report.summary.complianceRate),
|
|
2884
|
+
"",
|
|
2885
|
+
`> ${t.report.automationDisclaimer}`,
|
|
2886
|
+
"",
|
|
2887
|
+
`**${t.report.generated}:** ${new Date(report.meta.generatedAt).toLocaleString()}`,
|
|
2888
|
+
report.meta.projectName ? `**${t.report.project}:** ${report.meta.projectName}` : "",
|
|
2889
|
+
`**${t.report.pages}:** ${report.meta.analyzedPages.join(", ")}`,
|
|
2890
|
+
"",
|
|
2891
|
+
`## ${t.report.summary}`,
|
|
2892
|
+
"",
|
|
2893
|
+
"| | |",
|
|
2894
|
+
"|---|---|",
|
|
2895
|
+
`| ${t.report.complianceRate} | **${pct}%** |`,
|
|
2896
|
+
`| ${t.report.totalCriteria} | ${report.summary.totalCriteria} |`,
|
|
2897
|
+
`| ${t.report.applicable} | ${report.summary.applicable} |`,
|
|
2898
|
+
`| ${t.report.validated} | ${report.summary.validated} |`,
|
|
2899
|
+
`| ${t.report.invalidated} | ${report.summary.invalidated} |`,
|
|
2900
|
+
`| ${t.report.notApplicable} | ${report.summary.notApplicable} |`,
|
|
2901
|
+
`| ${t.report.needsReview} | ${report.summary.needsReview} |`,
|
|
2902
|
+
"",
|
|
2903
|
+
`## ${t.report.themes}`,
|
|
2904
|
+
""
|
|
2905
|
+
];
|
|
2906
|
+
for (const theme of report.themes) {
|
|
2907
|
+
const themeName = t.themes[theme.id] ?? `Theme ${theme.id}`;
|
|
2908
|
+
const themePct = Math.round(theme.complianceRate * 100);
|
|
2909
|
+
lines.push(`### ${theme.id}. ${themeName} \u2014 ${themePct}%`);
|
|
2910
|
+
lines.push("");
|
|
2911
|
+
lines.push(`| ${t.report.criterion} | Status |`);
|
|
2912
|
+
lines.push("|---|---|");
|
|
2913
|
+
for (const criterion of theme.criteriaResults) {
|
|
2914
|
+
const title = t.criteria[criterion.id] ?? criterion.id;
|
|
2915
|
+
const icon = statusIcon(criterion.status);
|
|
2916
|
+
const status = t.criterionStatus[criterion.status] ?? criterion.status;
|
|
2917
|
+
lines.push(`| **${criterion.id}** ${title} | ${icon} ${status} |`);
|
|
2918
|
+
}
|
|
2919
|
+
lines.push("");
|
|
2920
|
+
}
|
|
2921
|
+
if (report.issues.length > 0) {
|
|
2922
|
+
lines.push(`## ${t.report.issues}`);
|
|
2923
|
+
lines.push("");
|
|
2924
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2925
|
+
for (const issue of report.issues) {
|
|
2926
|
+
const key = issue.file ? `file:${issue.file}` : issue.page ? `page:${issue.page}` : "unknown";
|
|
2927
|
+
const existing = grouped.get(key);
|
|
2928
|
+
if (existing) {
|
|
2929
|
+
existing.push(issue);
|
|
2930
|
+
} else {
|
|
2931
|
+
grouped.set(key, [issue]);
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
for (const [groupKey, locationIssues] of grouped) {
|
|
2935
|
+
const location = groupKey.replace(/^(file|page):/, "");
|
|
2936
|
+
lines.push(`### \`${location}\``);
|
|
2937
|
+
lines.push("");
|
|
2938
|
+
for (const issue of locationIssues) {
|
|
2939
|
+
const msg = resolveIssueMessage(issue, t);
|
|
2940
|
+
const { text: rawFix, isUrl } = resolveRemediation(issue, t);
|
|
2941
|
+
const linkText = getRemediationLinkText(rawFix);
|
|
2942
|
+
const fix = isUrl ? `[${linkText} \u2197](${encodeURI(rawFix)})` : interpolate(rawFix, issue.messageContext);
|
|
2943
|
+
const loc = issue.line ? ` (line ${issue.line})` : "";
|
|
2944
|
+
lines.push(`- ${severityIcon(issue.severity)} **[${issue.criterionId}]** ${msg}${loc}`);
|
|
2945
|
+
if (issue.element) {
|
|
2946
|
+
const indented = issue.element.replace(/^/gm, " ");
|
|
2947
|
+
lines.push(indented);
|
|
2948
|
+
}
|
|
2949
|
+
lines.push(` > \u{1F4A1} ${fix}`);
|
|
2950
|
+
lines.push("");
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
} else {
|
|
2954
|
+
lines.push(`## ${t.report.issues}`);
|
|
2955
|
+
lines.push("");
|
|
2956
|
+
lines.push(`\u2705 ${t.report.noIssues}`);
|
|
2957
|
+
}
|
|
2958
|
+
lines.push("---");
|
|
2959
|
+
lines.push(
|
|
2960
|
+
`*Generated by [@kodalabs-io/eqo](https://github.com/kodalabs-io/eqo) v${report.meta.toolVersion}*`
|
|
2961
|
+
);
|
|
2962
|
+
await writeOutputFile(outputConfig.path, lines.filter((l) => l !== void 0).join("\n"));
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
// src/reporter/sarif.ts
|
|
2966
|
+
function sanitizeElement(el, maxLen = 200) {
|
|
2967
|
+
const stripped = el.replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim();
|
|
2968
|
+
return stripped.length > maxLen ? `${stripped.slice(0, maxLen)}\u2026` : stripped;
|
|
2969
|
+
}
|
|
2970
|
+
function issueToLevel(issue) {
|
|
2971
|
+
switch (issue.severity) {
|
|
2972
|
+
case "error":
|
|
2973
|
+
return "error";
|
|
2974
|
+
case "warning":
|
|
2975
|
+
return "warning";
|
|
2976
|
+
default:
|
|
2977
|
+
return "note";
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
async function writeSarifReport(report, outputConfig) {
|
|
2981
|
+
const rulesMap = /* @__PURE__ */ new Map();
|
|
2982
|
+
for (const theme of report.themes) {
|
|
2983
|
+
for (const criterion of theme.criteriaResults) {
|
|
2984
|
+
const ruleId = `RGAA-${criterion.id}`;
|
|
2985
|
+
if (!rulesMap.has(ruleId)) {
|
|
2986
|
+
rulesMap.set(ruleId, {
|
|
2987
|
+
id: ruleId,
|
|
2988
|
+
name: `Criterion${criterion.id.replace(".", "_")}`,
|
|
2989
|
+
shortDescription: { text: `RGAA 4.1.2 \u2014 Criterion ${criterion.id}` },
|
|
2990
|
+
helpUri: `https://accessibilite.numerique.gouv.fr/methode/criteres-et-tests/#${criterion.id}`,
|
|
2991
|
+
properties: { tags: ["accessibility", "rgaa", "wcag"] }
|
|
2992
|
+
});
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
const t = getTranslations(report.meta.locale);
|
|
2997
|
+
const artifactsMap = /* @__PURE__ */ new Map();
|
|
2998
|
+
const results = report.issues.filter((issue) => issue.criterionId !== "unknown").map((issue) => {
|
|
2999
|
+
const locations = [];
|
|
3000
|
+
if (issue.file) {
|
|
3001
|
+
const uri = issue.file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3002
|
+
if (!uri.startsWith("../")) {
|
|
3003
|
+
if (!artifactsMap.has(uri)) {
|
|
3004
|
+
artifactsMap.set(uri, {
|
|
3005
|
+
location: { uri, uriBaseId: "%SRCROOT%" }
|
|
3006
|
+
});
|
|
3007
|
+
}
|
|
3008
|
+
locations.push({
|
|
3009
|
+
physicalLocation: {
|
|
3010
|
+
artifactLocation: { uri, uriBaseId: "%SRCROOT%" },
|
|
3011
|
+
...issue.line ? {
|
|
3012
|
+
region: {
|
|
3013
|
+
startLine: issue.line,
|
|
3014
|
+
...issue.column !== void 0 ? { startColumn: issue.column } : {}
|
|
3015
|
+
}
|
|
3016
|
+
} : {}
|
|
3017
|
+
}
|
|
3018
|
+
});
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
if (issue.page) {
|
|
3022
|
+
locations.push({
|
|
3023
|
+
logicalLocations: [{ name: issue.page, kind: "page" }]
|
|
3024
|
+
});
|
|
3025
|
+
}
|
|
3026
|
+
const messageText = [
|
|
3027
|
+
resolveIssueMessage(issue, t),
|
|
3028
|
+
issue.element ? `Element: ${sanitizeElement(issue.element)}` : null
|
|
3029
|
+
].filter(Boolean).join(" \u2014 ");
|
|
3030
|
+
return {
|
|
3031
|
+
ruleId: `RGAA-${issue.criterionId}`,
|
|
3032
|
+
level: issueToLevel(issue),
|
|
3033
|
+
message: { text: messageText },
|
|
3034
|
+
...locations.length > 0 ? { locations } : {}
|
|
3035
|
+
};
|
|
3036
|
+
});
|
|
3037
|
+
const sarifLog = {
|
|
3038
|
+
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/Schemata/sarif-schema-2.1.0.json",
|
|
3039
|
+
version: "2.1.0",
|
|
3040
|
+
runs: [
|
|
3041
|
+
{
|
|
3042
|
+
tool: {
|
|
3043
|
+
driver: {
|
|
3044
|
+
name: "@kodalabs-io/eqo",
|
|
3045
|
+
version: report.meta.toolVersion,
|
|
3046
|
+
informationUri: "https://github.com/kodalabs-io/eqo",
|
|
3047
|
+
rules: Array.from(rulesMap.values())
|
|
3048
|
+
}
|
|
3049
|
+
},
|
|
3050
|
+
results,
|
|
3051
|
+
artifacts: Array.from(artifactsMap.values())
|
|
3052
|
+
}
|
|
3053
|
+
]
|
|
3054
|
+
};
|
|
3055
|
+
await writeOutputFile(outputConfig.path, JSON.stringify(sarifLog, null, 2));
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
// src/reporter/console.ts
|
|
3059
|
+
import pc from "picocolors";
|
|
3060
|
+
var MAX_DISPLAYED_ERRORS = 10;
|
|
3061
|
+
var MAX_DISPLAYED_WARNINGS = 5;
|
|
3062
|
+
var COMPLIANCE_BAR_WIDTH = 30;
|
|
3063
|
+
function complianceBar(rate, width = COMPLIANCE_BAR_WIDTH) {
|
|
3064
|
+
const filled = Math.round(rate * width);
|
|
3065
|
+
const empty = width - filled;
|
|
3066
|
+
const level = getComplianceLevel(Math.round(rate * 100));
|
|
3067
|
+
const color = level === "green" ? pc.green : level === "yellow" ? pc.yellow : pc.red;
|
|
3068
|
+
return color("\u2588".repeat(filled)) + pc.dim("\u2591".repeat(empty));
|
|
3069
|
+
}
|
|
3070
|
+
function printThemeSummary(report, t) {
|
|
3071
|
+
console.log(pc.bold(` ${t.report.themes}`));
|
|
3072
|
+
for (const theme of report.themes) {
|
|
3073
|
+
const themeName = t.themes[theme.id] ?? `Theme ${theme.id}`;
|
|
3074
|
+
const themePct = Math.round(theme.complianceRate * 100);
|
|
3075
|
+
const themeLevel = getComplianceLevel(themePct);
|
|
3076
|
+
const themeColor = themeLevel === "green" ? pc.green : themeLevel === "yellow" ? pc.yellow : pc.red;
|
|
3077
|
+
const invalidated = theme.criteriaResults.filter((c) => c.status === "invalidated").length;
|
|
3078
|
+
const badge2 = invalidated > 0 ? pc.red(` (${invalidated} failed)`) : "";
|
|
3079
|
+
console.log(
|
|
3080
|
+
` ${pc.dim(`${theme.id}.`)} ${themeName.padEnd(35)} ${themeColor(`${themePct}%`)}${badge2}`
|
|
3081
|
+
);
|
|
3082
|
+
}
|
|
3083
|
+
console.log("");
|
|
3084
|
+
}
|
|
3085
|
+
function printIssues(report, t) {
|
|
3086
|
+
const errors = [];
|
|
3087
|
+
const warnings = [];
|
|
3088
|
+
for (const issue of report.issues) {
|
|
3089
|
+
if (issue.severity === "error") errors.push(issue);
|
|
3090
|
+
else if (issue.severity === "warning") warnings.push(issue);
|
|
3091
|
+
}
|
|
3092
|
+
if (errors.length > 0) {
|
|
3093
|
+
console.log(pc.bold(pc.red(` \u2717 ${errors.length} errors`)));
|
|
3094
|
+
for (const issue of errors.slice(0, MAX_DISPLAYED_ERRORS)) {
|
|
3095
|
+
const msg = resolveIssueMessage(issue, t);
|
|
3096
|
+
const loc = issue.file ? pc.dim(` ${issue.file}${issue.line ? `:${issue.line}` : ""}`) : issue.page ? pc.dim(` page: ${issue.page}`) : "";
|
|
3097
|
+
console.log(` ${pc.red("\u25CF")} ${pc.bold(`[${issue.criterionId}]`)} ${msg}${loc}`);
|
|
3098
|
+
}
|
|
3099
|
+
if (errors.length > MAX_DISPLAYED_ERRORS) {
|
|
3100
|
+
console.log(pc.dim(` \u2026 and ${errors.length - MAX_DISPLAYED_ERRORS} more errors`));
|
|
3101
|
+
}
|
|
3102
|
+
console.log("");
|
|
3103
|
+
}
|
|
3104
|
+
if (warnings.length > 0) {
|
|
3105
|
+
console.log(pc.bold(pc.yellow(` \u26A0 ${warnings.length} warnings`)));
|
|
3106
|
+
for (const issue of warnings.slice(0, MAX_DISPLAYED_WARNINGS)) {
|
|
3107
|
+
const msg = resolveIssueMessage(issue, t);
|
|
3108
|
+
const loc = issue.file ? pc.dim(` ${issue.file}${issue.line ? `:${issue.line}` : ""}`) : "";
|
|
3109
|
+
console.log(` ${pc.yellow("\u25CF")} ${pc.bold(`[${issue.criterionId}]`)} ${msg}${loc}`);
|
|
3110
|
+
}
|
|
3111
|
+
if (warnings.length > MAX_DISPLAYED_WARNINGS) {
|
|
3112
|
+
console.log(pc.dim(` \u2026 and ${warnings.length - MAX_DISPLAYED_WARNINGS} more warnings`));
|
|
3113
|
+
}
|
|
3114
|
+
console.log("");
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
function printPages(report, t) {
|
|
3118
|
+
if (report.pages.length > 0) {
|
|
3119
|
+
console.log(pc.bold(` ${t.report.pages}`));
|
|
3120
|
+
for (const page of report.pages) {
|
|
3121
|
+
const badge2 = page.error ? pc.yellow(` \u26A0 skipped (${page.error.split("\n")[0]})`) : page.issueCount > 0 ? pc.red(` ${page.issueCount} issues`) : pc.green(" \u2713");
|
|
3122
|
+
console.log(` ${pc.dim("\u2192")} ${page.path}${badge2}`);
|
|
3123
|
+
}
|
|
3124
|
+
console.log("");
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
function printReport(report) {
|
|
3128
|
+
const t = getTranslations(report.meta.locale);
|
|
3129
|
+
const pct = Math.round(report.summary.complianceRate * 100);
|
|
3130
|
+
const level = getComplianceLevel(pct);
|
|
3131
|
+
const rateColor = level === "green" ? pc.green : level === "yellow" ? pc.yellow : pc.red;
|
|
3132
|
+
console.log("");
|
|
3133
|
+
console.log(pc.bold(pc.cyan(" RGAA v4.1.2 \u2014 Accessibility Report")));
|
|
3134
|
+
if (report.meta.projectName) {
|
|
3135
|
+
console.log(pc.dim(` ${report.meta.projectName}`));
|
|
3136
|
+
}
|
|
3137
|
+
console.log("");
|
|
3138
|
+
console.log(
|
|
3139
|
+
` ${complianceBar(report.summary.complianceRate)} ${rateColor(pc.bold(`${pct}%`))} ${pc.dim(t.report.complianceRate)}`
|
|
3140
|
+
);
|
|
3141
|
+
console.log("");
|
|
3142
|
+
const statsLine = [
|
|
3143
|
+
`${pc.green("\u2713")} ${report.summary.validated} ${pc.dim(t.report.validated)}`,
|
|
3144
|
+
`${pc.red("\u2717")} ${report.summary.invalidated} ${pc.dim(t.report.invalidated)}`,
|
|
3145
|
+
`${pc.dim("\u2500")} ${report.summary.notApplicable} ${pc.dim(t.report.notApplicable)}`,
|
|
3146
|
+
`${pc.yellow("?")} ${report.summary.needsReview} ${pc.dim(t.report.needsReview)}`
|
|
3147
|
+
].join(" ");
|
|
3148
|
+
console.log(` ${statsLine}`);
|
|
3149
|
+
console.log("");
|
|
3150
|
+
printThemeSummary(report, t);
|
|
3151
|
+
printIssues(report, t);
|
|
3152
|
+
printPages(report, t);
|
|
3153
|
+
console.log(pc.dim(` ${t.report.automationDisclaimer}`));
|
|
3154
|
+
console.log("");
|
|
3155
|
+
}
|
|
3156
|
+
function printProgress(message) {
|
|
3157
|
+
if (process.stdout.isTTY) {
|
|
3158
|
+
process.stdout.write(` ${pc.cyan("\u283F")} ${message}\u2026\r`);
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
function printSuccess(message) {
|
|
3162
|
+
process.stdout.write("\x1B[2K\r");
|
|
3163
|
+
console.log(` ${pc.green("\u2713")} ${message}`);
|
|
3164
|
+
}
|
|
3165
|
+
function printError(message) {
|
|
3166
|
+
console.error(` ${pc.red("\u2717")} ${message}`);
|
|
3167
|
+
}
|
|
3168
|
+
function printInfo(message) {
|
|
3169
|
+
console.log(` ${pc.blue("\u2139")} ${message}`);
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
// src/reporter/index.ts
|
|
3173
|
+
async function writeReports(report, outputs) {
|
|
3174
|
+
await loadTranslations(report.meta.locale);
|
|
3175
|
+
const results = await Promise.allSettled(
|
|
3176
|
+
outputs.map(async (output) => {
|
|
3177
|
+
switch (output.format) {
|
|
3178
|
+
case "json":
|
|
3179
|
+
await writeJsonReport(report, output);
|
|
3180
|
+
break;
|
|
3181
|
+
case "html":
|
|
3182
|
+
await writeHtmlReport(report, output);
|
|
3183
|
+
break;
|
|
3184
|
+
case "sarif":
|
|
3185
|
+
await writeSarifReport(report, output);
|
|
3186
|
+
break;
|
|
3187
|
+
case "markdown":
|
|
3188
|
+
await writeMarkdownReport(report, output);
|
|
3189
|
+
break;
|
|
3190
|
+
case "junit":
|
|
3191
|
+
await writeJunitReport(report, output);
|
|
3192
|
+
break;
|
|
3193
|
+
}
|
|
3194
|
+
return output.path;
|
|
3195
|
+
})
|
|
3196
|
+
);
|
|
3197
|
+
const written = [];
|
|
3198
|
+
for (const result of results) {
|
|
3199
|
+
if (result.status === "fulfilled") {
|
|
3200
|
+
written.push(result.value);
|
|
3201
|
+
} else {
|
|
3202
|
+
warn(
|
|
3203
|
+
"core",
|
|
3204
|
+
`Failed to write report: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
|
|
3205
|
+
);
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
return written;
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
export {
|
|
3212
|
+
THEMES,
|
|
3213
|
+
ALL_CRITERIA,
|
|
3214
|
+
CRITERIA_BY_ID,
|
|
3215
|
+
TOTAL_CRITERIA,
|
|
3216
|
+
error,
|
|
3217
|
+
AXE_TEST_ID_PREFIX,
|
|
3218
|
+
AXE_TO_RGAA,
|
|
3219
|
+
getRGAACriteria,
|
|
3220
|
+
getAxeRulesForCriterion,
|
|
3221
|
+
ConfigError,
|
|
3222
|
+
AnalysisError,
|
|
3223
|
+
TimeoutError,
|
|
3224
|
+
walk,
|
|
3225
|
+
getTagName,
|
|
3226
|
+
getAttr,
|
|
3227
|
+
getAttrStringValue,
|
|
3228
|
+
isAttrDynamic,
|
|
3229
|
+
getTextContent,
|
|
3230
|
+
getAttrMap,
|
|
3231
|
+
issueId,
|
|
3232
|
+
defineRule,
|
|
3233
|
+
createStaticIssue,
|
|
3234
|
+
runRuntimeAnalysis,
|
|
3235
|
+
runStaticAnalysis,
|
|
3236
|
+
buildReport,
|
|
3237
|
+
analyze,
|
|
3238
|
+
KodaRGAAConfigSchema,
|
|
3239
|
+
resolveConfigPath,
|
|
3240
|
+
loadConfig,
|
|
3241
|
+
generateDefaultConfig,
|
|
3242
|
+
loadTranslations,
|
|
3243
|
+
getTranslations,
|
|
3244
|
+
interpolate,
|
|
3245
|
+
getSupportedLocales,
|
|
3246
|
+
writeHtmlReport,
|
|
3247
|
+
writeJsonReport,
|
|
3248
|
+
writeJunitReport,
|
|
3249
|
+
writeMarkdownReport,
|
|
3250
|
+
writeSarifReport,
|
|
3251
|
+
printReport,
|
|
3252
|
+
printProgress,
|
|
3253
|
+
printSuccess,
|
|
3254
|
+
printError,
|
|
3255
|
+
printInfo,
|
|
3256
|
+
writeReports
|
|
3257
|
+
};
|
|
3258
|
+
//# sourceMappingURL=chunk-WKI3N5NX.js.map
|