├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── cli.js ├── package.json ├── src ├── index.ts └── lib │ ├── act-rules.options.json │ ├── actParser.ts │ ├── bpParser.ts │ ├── fileUtils.ts │ ├── options.ts │ ├── parser.ts │ ├── parserUtils.ts │ └── wcagParser.ts ├── test └── cli.spec.mjs └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bin 3 | dist 4 | test -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint", "sonarjs", "prettier"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:sonarjs/recommended", 10 | "prettier" 11 | ], 12 | "rules": { 13 | "prettier/prettier": 2, 14 | "sonarjs/no-duplicate-string": 0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules 3 | /.yalc 4 | yalc.lock 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 120 6 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.6.4] - 23/07/2021 4 | 5 | ### Updated 6 | 7 | - dependencies 8 | 9 | ## [0.6.3] - 22/07/2021 10 | 11 | ### Updated 12 | 13 | - dependencies 14 | 15 | ## [0.6.2] - 21/07/2021 16 | 17 | ### Updated 18 | 19 | - dependencies 20 | 21 | ## [0.6.1] - 31/04/2021 22 | 23 | ### Updated 24 | 25 | - dependencies 26 | - README.md 27 | 28 | ### Fixed 29 | 30 | - error when using option "-m wcag" 31 | 32 | ## [0.5.2] - 23/04/2021 33 | 34 | ### Fixed 35 | 36 | - save-name option not working 37 | 38 | ### Updated 39 | 40 | - dependencies 41 | 42 | ## [0.5.1] - 31/03/2021 43 | 44 | ### Updated 45 | 46 | - dependencies 47 | - code optimized 48 | 49 | ## [0.5.0] - 23/03/2021 50 | 51 | ### Updated 52 | 53 | - @qualweb/core to version 0.5.1 54 | 55 | ## [0.4.16] - 23/03/2021 56 | 57 | ### Updated 58 | 59 | - dependencies 60 | 61 | ## [0.4.15] - 22/03/2021 62 | 63 | ### Updated 64 | 65 | - dependencies 66 | 67 | ## [0.4.14] - 11/03/2021 68 | 69 | ### Updated 70 | 71 | - dependencies 72 | - README.md 73 | 74 | ## [0.4.13] - 10/03/2021 75 | 76 | ### Updated 77 | 78 | - dependencies 79 | 80 | ## [0.4.12] - 10/03/2021 81 | 82 | ### Added 83 | 84 | - support for counting elements and roles 85 | 86 | ## [0.4.11] - 08/03/2021 87 | 88 | ### Updated 89 | 90 | - dependencies 91 | 92 | ## [0.4.10] - 01/03/2021 93 | 94 | ### Updated 95 | 96 | - dependencies 97 | 98 | ## [0.4.9] - 27/02/2021 99 | 100 | ### Updated 101 | 102 | - dependencies 103 | 104 | ## [0.4.8] - 27/02/2021 105 | 106 | ### Added 107 | 108 | - new options to exclude rules/techniques/best-practices from executing 109 | 110 | ### Updated 111 | 112 | - README.md 113 | 114 | ## [0.4.7] - 25/02/2021 115 | 116 | ### Added 117 | 118 | - support for new rules 119 | 120 | ## [0.4.6] - 25/02/2021 121 | 122 | ### Updated 123 | 124 | - dependencies 125 | - README.md 126 | 127 | ## [0.4.5] - 20/02/2021 128 | 129 | ### Updated 130 | 131 | - dependencies 132 | - README.md 133 | - list of available act rules 134 | 135 | ## [0.4.4] - 25/01/2021 136 | 137 | ### Updated 138 | 139 | - dependencies 140 | 141 | ## [0.4.3] - 25/01/2021 142 | 143 | ### Updated 144 | 145 | - dependencies 146 | 147 | ## [0.4.2] - 25/01/2021 148 | 149 | ### Updated 150 | 151 | - dependencies 152 | 153 | ## [0.4.1] - 06/01/2021 154 | 155 | ### Added 156 | 157 | - eslint 158 | 159 | ### Updated 160 | 161 | - dependencies 162 | 163 | ## [0.4.0] - 16/12/2020 164 | 165 | ### Updated 166 | 167 | - compatibility with wcag-techniques module 168 | - dependencies 169 | 170 | ## [0.3.39] - 12/10/2020 171 | 172 | ### Updated 173 | 174 | - dependencies 175 | 176 | ## [0.3.38] - 12/10/2020 177 | 178 | ### Updated 179 | 180 | - dependencies 181 | 182 | ## [0.3.37] - 09/10/2020 183 | 184 | ### Updated 185 | 186 | - dependencies 187 | - code refactor 188 | 189 | ## [0.3.36] - 23/09/2020 190 | 191 | ### Updated 192 | 193 | - dependencies 194 | 195 | ## [0.3.35] - 16/09/2020 196 | 197 | ### Updated 198 | 199 | - dependencies 200 | 201 | ## [0.3.34] - 25/08/2020 202 | 203 | ### Updated 204 | 205 | - dependencies 206 | 207 | ## [0.3.33] - 29/07/2020 208 | 209 | ### Updated 210 | 211 | - dependencies 212 | 213 | ## [0.3.32] - 28/07/2020 214 | 215 | ### Fixed 216 | 217 | - known bugs 218 | 219 | ### Updated 220 | 221 | - dependencies 222 | 223 | ## [0.3.31] - 27/07/2020 224 | 225 | ### Updated 226 | 227 | - dependencies 228 | 229 | ## [0.3.30] - 13/07/2020 230 | 231 | ### Updated 232 | 233 | - dependencies 234 | 235 | ## [0.3.29] - 03/07/2020 236 | 237 | ### Updated 238 | 239 | - dependencies 240 | 241 | ## [0.3.28] - 03/07/2020 242 | 243 | ### Updated 244 | 245 | - README.md 246 | 247 | ## [0.3.27] - 03/07/2020 248 | 249 | ### Updated 250 | 251 | - dependencies 252 | 253 | ## [0.3.26] - 23/06/2020 254 | 255 | ### Fixed 256 | 257 | - Updated core 258 | 259 | ## [0.3.25] - 15/06/2020 260 | 261 | ### Fixed 262 | 263 | - Updated core 264 | 265 | ## [0.3.23] - 15/06/2020 266 | 267 | ### Fixed 268 | 269 | - a bug where act rule ids were invalid 270 | 271 | ## [0.3.22] - 08/06/2020 272 | 273 | ### Updated 274 | 275 | - Updated core 276 | 277 | ## [0.3.21] - 08/06/2020 278 | 279 | ### Updated 280 | 281 | - Updated core 282 | 283 | ## [0.3.20] - 06/05/2020 284 | 285 | ### Updated 286 | 287 | - dependencies 288 | 289 | ## [0.3.19] - 06/05/2020 290 | 291 | ### Updated 292 | 293 | - dependencies 294 | 295 | ## [0.3.18] - 05/05/2020 296 | 297 | ### Added 298 | 299 | - viewport options 300 | - possibility of using config files 301 | 302 | ### Updated 303 | 304 | - parser 305 | - README.md 306 | 307 | ## [0.3.17] - 23/04/2020 308 | 309 | ### Fixed 310 | 311 | - some bugs 312 | 313 | ## [0.3.16] - 22/04/2020 314 | 315 | ### Fixed 316 | 317 | - a bug 318 | 319 | ## [0.3.15] - 22/04/2020 320 | 321 | ### Updated 322 | 323 | - @qualweb/core 324 | - CLI interface 325 | - @qualweb/types 326 | 327 | ## [0.3.14] - 19/03/2020 328 | 329 | ### Updated 330 | 331 | - @qualweb/core 332 | 333 | ## [0.3.13] - 18/03/2020 334 | 335 | ### Updated 336 | 337 | - @qualweb/core 338 | 339 | ## [0.3.12] - 21/02/2020 340 | 341 | ### Updated 342 | 343 | - @qualweb/core 344 | 345 | ## [0.3.12] - 17/02/2020 346 | 347 | ### Updated 348 | 349 | - @qualweb/core 350 | 351 | ## [0.3.11] - 22/01/2020 352 | 353 | ### Updated 354 | 355 | - @qualweb/core 356 | 357 | ## [0.3.10] - 21/01/2020 358 | 359 | ### Updated 360 | 361 | - @qualweb/core 362 | 363 | ## [0.3.9] - 17/01/2020 364 | 365 | ### Updated 366 | 367 | - @qualweb/core 368 | 369 | ## [0.3.8] - 17/01/2020 370 | 371 | ### Fixed 372 | 373 | - a bug where the rules were not correctly parsed 374 | 375 | ## [0.3.7] - 15/01/2020 376 | 377 | ### Added 378 | 379 | - option to change the name of th file of the aggregated earl reports 380 | 381 | ### Updated 382 | 383 | - @qualweb/core 384 | - README.md 385 | 386 | ## [0.3.6] - 14/01/2020 387 | 388 | ### Updated 389 | 390 | - @qualweb/core 391 | - README.md 392 | 393 | ## [0.3.5] - 13/01/2020 394 | 395 | ### Updated 396 | 397 | - @qualweb/core 398 | 399 | ## [0.3.4] - 09/01/2020 400 | 401 | ### Updated 402 | 403 | - @qualweb/core 404 | 405 | ### Fixed 406 | 407 | - some bugs 408 | 409 | ## [0.3.3] - 08/01/2020 410 | 411 | ### Updated 412 | 413 | - @qualweb/core 414 | 415 | ## [0.3.2] - 08/01/2020 416 | 417 | ### Fixed 418 | 419 | - dependencies 420 | 421 | ## [0.3.1] - 08/01/2020 422 | 423 | ### Updated 424 | 425 | - @qualweb/core 426 | 427 | ## [0.3.0] - 08/01/2020 428 | 429 | ### Updated 430 | 431 | - @qualweb/core 432 | - README.md 433 | 434 | ## [0.2.5] - 03/12/2019 435 | 436 | ### Removed 437 | 438 | - unnecessary files 439 | 440 | ## [0.2.4] - 03/12/2019 441 | 442 | ### Update 443 | 444 | - core engine 445 | 446 | ## [0.2.3] - 02/12/2019 447 | 448 | ### Update 449 | 450 | - core engine 451 | 452 | ## [0.2.2] - 02/12/2019 453 | 454 | ### Fixed 455 | 456 | - a bug when trying to save the reports 457 | 458 | ## [0.2.1] - 02/12/2019 459 | 460 | ### Fixed 461 | 462 | - a bug when trying to run specific modules 463 | 464 | ## [0.2.0] - 02/12/2019 465 | 466 | ### Added 467 | 468 | - best-practices modules configuration options 469 | 470 | ### Updated 471 | 472 | - core engine 473 | 474 | ## [0.1.0] - 04/11/2019 475 | 476 | ### Updated 477 | 478 | - package @qualweb/core to version 0.1.6 479 | 480 | ## [0.0.3] - 12/08/2019 481 | 482 | ### Added 483 | 484 | - "mocha" framework 485 | 486 | ### Updated 487 | 488 | - package @qualweb/types to version 0.0.32 489 | - package @qualweb/core to version 0.0.3 490 | - package @types/node to version 12.7.1 491 | - package commander to version 3.0.0 492 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2021, FCUL 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository has been archived. QualWeb is now being maintained in a monorepo at https://github.com/qualweb/qualweb 2 | 3 | # QualWeb CLI 4 | 5 | QualWeb command line interface. It allows you to perform accessibility evaluations from the terminal. It uses the [@qualweb/core](https://github.com/qualweb/core) that contains 3 evaluation modules: 6 | 7 | - [@qualweb/act-rules](https://github.com/qualweb/act-rules) 8 | - [@qualweb/wcag-techniques](https://github.com/qualweb/wcag-techniques) 9 | - [@qualweb/best-practices](https://github.com/qualweb/best-practices) 10 | 11 | You can also perform evaluations at [http://qualweb.di.fc.ul.pt/evaluator/](http://qualweb.di.fc.ul.pt/evaluator/), or by installing the [chrome extension](https://chrome.google.com/webstore/detail/qualweb-extension/ljgilomdnehokancdcbkmbndkkiggioc). 12 | 13 | ## How to install 14 | 15 | ```shell 16 | $ npm i -g @qualweb/cli 17 | ``` 18 | 19 | ## How to run 20 | 21 | ### Url input 22 | 23 | #### Simple evaluation 24 | 25 | ```shell 26 | $ qw -u https://act-rules.github.io/pages/about/ 27 | ``` 28 | 29 | #### Evaluation with EARL report 30 | 31 | ```shell 32 | $ qw -u https://act-rules.github.io/pages/about/ -r earl 33 | ``` 34 | 35 | ### File input 36 | 37 | If you want to evaluate multiple url's at once, you can input a file with each url separated by a newline **\n**. 38 | 39 | #### Example 40 | 41 | ```shell 42 | $ qw -f urls.txt 43 | ``` 44 | 45 | #### Evaluation with EARL report 46 | 47 | This method will create an EARL report for each url. 48 | 49 | ```shell 50 | $ qw -f urls.txt -r earl 51 | ``` 52 | 53 | This method will create an aggregated EARL report from all urls. 54 | 55 | ```shell 56 | $ qw -f urls.txt -r earl-a # add `-s ` to rename the report file 57 | ``` 58 | 59 | ## Options 60 | 61 | ### Usage options 62 | 63 | | Alias | Command | Value | Information | 64 | | ----- | ------------------------ | ------------------------------------------------- | --------------------------------------------------------------------- | 65 | | -u | --url | `` | Url to evaluate | 66 | | -f | --file | `` | File with urls to evaluate | 67 | | -c | --crawl | `` | Domain to crawl | 68 | | -m | --module | `act wcag bp` | Choose which modules to execute | 69 | | -r | --report-type | `"earl" or "earl-a"` | Convert the evaluation to `earl` or `earl-a` (_earl-aggregated_) | 70 | | -s | --save-name | `` | The name to save the aggregated earl reports (_earl-a_) | 71 | | -t | --timeout | `` | Timeout for page to load | 72 | | -w | --waitUntil | `load doncontentloaded networkidle0 networkidle2` | Events to wait before starting evaluation | 73 | | -p | --maxParallelEvaluations | `` | Evaluates multiples urls ate the same time (_experimental_) | 74 | | -j | --json | `` | Loads a json file with the configs to execute. Check an example below | 75 | | -h | --help | | Print the help menu | 76 | 77 | ### -j, --json config file example 78 | 79 | This command replaces all other commands. 80 | 81 | ```json 82 | { 83 | "url": "https://act-rules.github.io/pages/about/", 84 | "file": "test_url.txt", 85 | "crawl": "https://act-rules.github.io", 86 | "viewport": { 87 | "mobile": false, 88 | "orientation": "landscape", 89 | "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:22.0) Gecko/20100101 Firefox/22.0', default value for mobile = 'Mozilla/5.0 (Linux; U; Android 2.2; en-us; DROID2 GLOBAL Build/S273) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", 90 | "width": 1920, 91 | "height": 1080 92 | }, 93 | "maxParallelEvaluations": "5", 94 | "modules": { 95 | "act": true, 96 | "wcag": true, 97 | "bp": true, 98 | "counter": false, 99 | }, 100 | "act-rules": { 101 | "rules": ["QW-ACT-R1"], 102 | "exclude": ["QW-ACT-R2"], 103 | "levels": ["A", "AA", "AAA"], 104 | "principles": ["Perceivable", "Operable", "Understandable", "Robust"] 105 | }, 106 | "wcag-techniques": { 107 | "rules": ["QW-WCAG-T1"], 108 | "exclude": ["QW-WCAG-T2"], 109 | "levels": ["A", "AA", "AAA"], 110 | "principles": ["Perceivable", "Operable", "Understandable", "Robust"] 111 | }, 112 | "best-practices": { 113 | "bestPractices": ["QW-BP1"], 114 | "exclude": ["QW-BP2"] 115 | } 116 | } 117 | ``` 118 | 119 | ### Viewport Options 120 | 121 | | Alias | Command | Value | Information | 122 | | ----- | ------------- | --------------------------- | ------------------------------------------------------------------------------- | 123 | | -v | --viewport | | Use custom viewport | 124 | | | --mobile | | Use a mobile context (_default: desktop_) | 125 | | | --orientation | `"portrait" or "landscape"` | Set window orientation (_default desktop: landscape, default mobile: portrait_) | 126 | | | --user-agent | `` | Set custom user agent | 127 | | | --width | `` | Set custom viewport width (_default desktop: 1366, default mobile: 1080_) | 128 | | | --height | `` | Set custom viewport height (_default desktop: 768, default mobile: 1920_) | 129 | 130 | ### Modules options 131 | 132 | | Command | Value | Information | 133 | | ----------------- | --------------------------------------------------------------- | --------------------------------------------------------------------- | 134 | | --act-rules | `"ruleId1 ruleId2 ... ruleIdx" or ` | Choose which act rules to execute. For config file check below | 135 | | --exclude-act | `"ruleId1 ruleId2 ... ruleIdx" or ` | Choose which act rules to exclude. For config file check below | 136 | | --act-levels | `A AA AAA` | Choose which conform levels to evaluate regarding the act rules | 137 | | --act-principles | `Perceivable Operable Understandable Robust` | Choose which principles to evaluate regarding the act rules | 138 | | --wcag-techniques | `"techniqueId1 techniqueId2 ... techniqueIdx" or ` | Choose which wcag techniques to execute. For config file check below | 139 | | --exclude-wcag | `"techniqueId1 techniqueId2 ... techniqueIdx" or ` | Choose which wcag techniques to exclude. For config file check below | 140 | | --wcag-levels | `A AA AAA` | Choose which conform levels to evaluate regarding the wcag techniques | 141 | | --wcag-principles | `Perceivable Operable Understandable Robust` | Choose which principles to evaluate regarding the wcag techniques | 142 | | --best-practices | `bestpracticeId1 bestpracticeId2 ... bestpracticeIdx` | Choose which best practices to execute. For config file check below | 143 | | --exclude-bp | `bestpracticeId1 bestpracticeId2 ... bestpracticeIdx` | Choose which best practices to exclude. For config file check below | 144 | 145 | **Note:** The module options above are only used if the correspondent module was set to be executed (command _-m_). 146 | 147 | #### --act-rules config file example 148 | 149 | This config file can replace commands **--act-rules**, **--act-levels** and **--act-principles**. 150 | 151 | ```json 152 | { 153 | "act-rules": { 154 | "rules": ["QW-ACT-R1"], 155 | "exclude": ["QW-ACT-R2"], 156 | "levels": ["A", "AA", "AAA"], 157 | "principles": ["Perceivable", "Operable", "Understandable", "Robust"] 158 | } 159 | } 160 | ``` 161 | 162 | #### --wcag-techniques config file example 163 | 164 | This config file can replace commands **--wcag-techniques**, **--wcag-levels** and **--wcag-principles**. 165 | 166 | ```json 167 | { 168 | "wcag-techniques": { 169 | "techniques": ["QW-WCAG-T1"], 170 | "exclude": ["QW-WCAG-T2"], 171 | "levels": ["A", "AA", "AAA"], 172 | "principles": ["Perceivable", "Operable", "Understandable", "Robust"] 173 | } 174 | } 175 | ``` 176 | 177 | #### --best-practices config file example 178 | 179 | ```json 180 | { 181 | "best-practices": { 182 | "bestPractices": ["QW-BP1"], 183 | "exclude": ["QW-BP2"] 184 | } 185 | } 186 | ``` 187 | 188 | ## Implemented ACT Rules 189 | 190 | | QualWeb Rule ID | ACT Rule ID | ACT Rule Name | 191 | | --------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------- | 192 | | QW-ACT-R1 | [2779a5](https://act-rules.github.io/rules/2779a5) | HTML Page has a title | 193 | | QW-ACT-R2 | [b5c3f8](https://act-rules.github.io/rules/b5c3f8) | HTML has lang attribute | 194 | | QW-ACT-R3 | [5b7ae0](https://act-rules.github.io/rules/5b7ae0) | HTML lang and xml:lang match | 195 | | QW-ACT-R4 | [bc659a](https://act-rules.github.io/rules/bc659a) | Meta-refresh no delay | 196 | | QW-ACT-R5 | [bf051a](https://act-rules.github.io/rules/bf051a) | Validity of HTML Lang attribute | 197 | | QW-ACT-R6 | [59796f](https://act-rules.github.io/rules/59796f) | Image button has accessible name | 198 | | QW-ACT-R7 | [b33eff](https://act-rules.github.io/rules/b33eff) | Orientation of the page is not restricted using CSS transform property | 199 | | QW-ACT-R9 | [b20e66](https://act-rules.github.io/rules/b20e66) | Links with identical accessible names have equivalent purpose | 200 | | QW-ACT-R10 | [4b1c6c](https://act-rules.github.io/rules/4b1c6c) | `iframe` elements with identical accessible names have equivalent purpose | 201 | | QW-ACT-R11 | [97a4e1](https://act-rules.github.io/rules/97a4e1) | Button has accessible name | 202 | | QW-ACT-R12 | [c487ae](https://act-rules.github.io/rules/c487ae) | Link has accessible name | 203 | | QW-ACT-R13 | [6cfa84](https://act-rules.github.io/rules/6cfa84) | Element with `aria-hidden` has no focusable content | 204 | | QW-ACT-R14 | [b4f0c3](https://act-rules.github.io/rules/b4f0c3) | meta viewport does not prevent zoom | 205 | | QW-ACT-R15 | [80f0bf](https://act-rules.github.io/rules/80f0bf) | audio or video has no audio that plays automatically | 206 | | QW-ACT-R16 | [e086e5](https://act-rules.github.io/rules/e086e5) | Form control has accessible name | 207 | | QW-ACT-R17 | [23a2a8](https://act-rules.github.io/rules/23a2a8) | Image has accessible name | 208 | | QW-ACT-R18 | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | `id` attribute value is unique | 209 | | QW-ACT-R19 | [cae760](https://act-rules.github.io/rules/cae760) | iframe element has accessible name | 210 | | QW-ACT-R20 | [674b10](https://act-rules.github.io/rules/674b10) | role attribute has valid value | 211 | | QW-ACT-R21 | [7d6734](https://act-rules.github.io/rules/7d6734) | svg element with explicit role has accessible name | 212 | | QW-ACT-R22 | [de46e4](https://act-rules.github.io/rules/de46e4) | Element within body has valid lang attribute | 213 | | QW-ACT-R23 | [c5a4ea](https://act-rules.github.io/rules/c5a4ea) | video element visual content has accessible alternative | 214 | | QW-ACT-R24 | [73f2c2](https://act-rules.github.io/rules/73f2c2) | autocomplete attribute has valid value | 215 | | QW-ACT-R25 | [5c01ea](https://act-rules.github.io/rules/5c01ea) | ARIA state or property is permitted | 216 | | QW-ACT-R26 | [eac66b](https://act-rules.github.io/rules/eac66b) | video element auditory content has accessible alternative | 217 | | QW-ACT-R27 | [5f99a7](https://act-rules.github.io/rules/5f99a7) | This rule checks that each aria- attribute specified is defined in ARIA 1.1. | 218 | | QW-ACT-R28 | [4e8ab6](https://act-rules.github.io/rules/4e8ab6) | Element with role attribute has required states and properties | 219 | | QW-ACT-R29 | [e7aa44](https://act-rules.github.io/rules/e7aa44) | Audio element content has text alternative | 220 | | QW-ACT-R30 | [2ee8b8](https://act-rules.github.io/rules/2ee8b8) | Visible label is part of accessible name | 221 | | QW-ACT-R31 | [c3232f](https://act-rules.github.io/rules/c3232f) | Video element visual-only content has accessible alternative | 222 | | QW-ACT-R32 | [1ec09b](https://act-rules.github.io/rules/1ec09b) | video element visual content has strict accessible alternative | 223 | | QW-ACT-R33 | [ff89c9](https://act-rules.github.io/rules/ff89c9) | ARIA required context role | 224 | | QW-ACT-R34 | [6a7281](https://act-rules.github.io/rules/6a7281) | ARIA state or property has valid value | 225 | | QW-ACT-R35 | [ffd0e9](https://act-rules.github.io/rules/ffd0e9) | Heading has accessible name | 226 | | QW-ACT-R36 | [a25f45](https://act-rules.github.io/rules/a25f45) | Headers attribute specified on a cell refers to cells in the same table element | 227 | | QW-ACT-R37 | [afw4f7](https://act-rules.github.io/rules/afw4f7) | Text has minimum contrast | 228 | | QW-ACT-R38 | [bc4a75](https://act-rules.github.io/rules/bc4a75) | ARIA required owned elements | 229 | | QW-ACT-R39 | [d0f69e](https://act-rules.github.io/rules/d0f69e) | All table header cells have assigned data cells | 230 | | QW-ACT-R40 | [59br37](https://act-rules.github.io/rules/59br37) | Zoomed text node is not clipped with CSS overflow | 231 | | QW-ACT-R41 | [36b590](https://act-rules.github.io/rules/36b590) | Error message describes invalid form field value | 232 | | QW-ACT-R42 | [8fc3b6](https://act-rules.github.io/rules/8fc3b6) | Object element has non-empty accessible name | 233 | | QW-ACT-R43 | [0ssw9k](https://act-rules.github.io/rules/0ssw9k) | Scrollable element is keyboard accessible | 234 | | QW-ACT-R44 | [fd3a94](https://act-rules.github.io/rules/fd3a94) | Links with identical accessible names and context serve equivalent purpose | 235 | | QW-ACT-R48 | [46ca7f](https://act-rules.github.io/rules/46ca7f) | Element marked as decorative is not exposed | 236 | | QW-ACT-R49 | [aaa1bf](https://act-rules.github.io/rules/aaa1bf) | Audio or video that plays automatically has no audio that lasts more than 3 seconds | 237 | | QW-ACT-R50 | [4c31df](https://act-rules.github.io/rules/4c31df) | Audio or video that plays automatically has a control mechanism | 238 | | QW-ACT-R51 | [fd26cf](https://act-rules.github.io/rules/fd26cf) | video element visual-only content is media alternative for text | 239 | | QW-ACT-R52 | [ac7dc6](https://act-rules.github.io/rules/ac7dc6) | video element visual-only content has description track | 240 | | QW-ACT-R53 | [ee13b5](https://act-rules.github.io/rules/ee13b5) | video element visual-only content has transcript | 241 | | QW-ACT-R54 | [d7ba54](https://act-rules.github.io/rules/d7ba54) | video element visual-only content has audio track alternative | 242 | | QW-ACT-R55 | [1ea59c](https://act-rules.github.io/rules/1ea59c) | video element visual content has audio description | 243 | | QW-ACT-R56 | [ab4d13](https://act-rules.github.io/rules/ab4d13) | video element content is media alternative for text | 244 | | QW-ACT-R57 | [f196ce](https://act-rules.github.io/rules/f196ce) | video element visual content has description track | 245 | | QW-ACT-R58 | [2eb176](https://act-rules.github.io/rules/2eb176) | audio element content has transcript | 246 | | QW-ACT-R59 | [afb423](https://act-rules.github.io/rules/afb423) | audio element content is media alternative for text | 247 | | QW-ACT-R60 | [f51b46](https://act-rules.github.io/rules/f51b46) | video element auditory content has captions | 248 | | QW-ACT-R61 | [1a02b0](https://act-rules.github.io/rules/1a02b0) | video element visual content has transcript | 249 | | QW-ACT-R62 | [oj04fd](https://act-rules.github.io/rules/oj04fd) | Element in sequential focus order has visible focus | 250 | | QW-ACT-R63 | [b40fd1](https://act-rules.github.io/rules/b40fd1) | Document has a landmark with non-repeated content | 251 | | QW-ACT-R64 | [047fe0](https://act-rules.github.io/rules/047fe0) | Document has heading for non-repeated content | 252 | | QW-ACT-R65 | [307n5z](https://act-rules.github.io/rules/307n5z) | Element with presentational children has no focusable content | 253 | | QW-ACT-R66 | [m6b1q3](https://act-rules.github.io/rules/m6b1q3) | Menuitem has non-empty accessible name | 254 | | QW-ACT-R67 | [24afc2](https://act-rules.github.io/rules/24afc2) | Letter spacing in style attributes is not !important | 255 | | QW-ACT-R68 | [78fd32](https://act-rules.github.io/rules/78fd32) | Line height in style attributes is not !important | 256 | | QW-ACT-R69 | [9e45ec](https://act-rules.github.io/rules/9e45ec) | Word spacing in style attributes is not !important | 257 | | QW-ACT-R70 | [akn7bn](https://act-rules.github.io/rules/akn7bn) | frame with negative tabindex has no interactive elements | 258 | | QW-ACT-R71 | [bisz58](https://act-rules.github.io/rules/bisz58) | `meta` element has no refresh delay (no exception) | 259 | | QW-ACT-R72 | [8a213c](https://act-rules.github.io/rules/8a213c) | First focusable element is link to non-repeated content | 260 | | QW-ACT-R73 | [3e12e1](https://act-rules.github.io/rules/3e12e1) | Block of repeated content is collapsible | 261 | | QW-ACT-R74 | [ye5d6e](https://act-rules.github.io/rules/ye5d6e) | Document has an instrument to move focus to non-repeated content | 262 | | QW-ACT-R75 | [cf77f2](https://act-rules.github.io/rules/cf77f2) | Bypass Blocks of Repeated Content | 263 | | QW-ACT-R76 | [09o5cg](https://act-rules.github.io/rules/09o5cg) | Text has enhanced contrast | 264 | 265 | ## Implemented WCAG 2.1 Techniques 266 | 267 | | QualWeb Technique ID | WCAG Technique ID | WCAG Technique Name | 268 | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | 269 | | QW-WCAG-T1 | [H24](https://www.w3.org/WAI/WCAG21/Techniques/html/H24) | Providing text alternatives for the area elements of image maps | 270 | | QW-WCAG-T2 | [H39](https://www.w3.org/WAI/WCAG21/Techniques/html/H39) | Using caption elements to associate data table captions with data tables | 271 | | QW-WCAG-T3 | [H71](https://www.w3.org/WAI/WCAG21/Techniques/html/H71) | Providing a description for groups of form controls using fieldset and legend elements | 272 | | QW-WCAG-T4 | [H73](https://www.w3.org/WAI/WCAG21/Techniques/html/H73) | Using the summary attribute of the table element to give an overview of data tables | 273 | | QW-WCAG-T5 | [H36](https://www.w3.org/WAI/WCAG21/Techniques/html/H36) | Using alt attributes on images used as submit buttons | 274 | | QW-WCAG-T6 | [SCR20](https://www.w3.org/WAI/WCAG21/Techniques/client-side-script/SCR20) | Using both keyboard and other device-specific functions | 275 | | QW-WCAG-T7 | [H28](https://www.w3.org/WAI/WCAG21/Techniques/html/H28) | Providing definitions for abbreviations by using the abbr element | 276 | | QW-WCAG-T8 | [F30](https://www.w3.org/WAI/WCAG21/Techniques/failures/F30) | Failure of Success Criterion 1.1.1 and 1.2.1 due to using text alternatives that are not alternatives | 277 | | QW-WCAG-T9 | [G141](https://www.w3.org/WAI/WCAG21/Techniques/general/G141) | Organizing a page using headings | 278 | | QW-WCAG-T10 | [H2](https://www.w3.org/WAI/WCAG21/Techniques/html/H2) | Combining adjacent image and text links for the same resource | 279 | | QW-WCAG-T11 | [H35](https://www.w3.org/WAI/WCAG21/Techniques/html/H35) | Providing text alternatives on applet elements | 280 | | QW-WCAG-T12 | [F46](https://www.w3.org/WAI/WCAG21/Techniques/failures/F46) | Failure of Success Criterion 1.3.1 due to using th elements, caption elements, or non-empty summary attributes in layout tables | 281 | | QW-WCAG-T13 | [F47](https://www.w3.org/WAI/WCAG21/Techniques/failures/F47) | Failure of Success Criterion 2.2.2 due to using the blink element | 282 | | QW-WCAG-T14 | [H43](https://www.w3.org/WAI/WCAG21/Techniques/html/H43) | Using id and headers attributes to associate data cells with header cells in data tables | 283 | | QW-WCAG-T15 | [H59](https://www.w3.org/WAI/WCAG21/Techniques/html/H59) | Using the link element and navigation tools | 284 | | QW-WCAG-T16 | [H88](https://www.w3.org/WAI/WCAG21/Techniques/html/H88) | Using HTML according to spec | 285 | | QW-WCAG-T17 | [G162](https://www.w3.org/WAI/WCAG21/Techniques/general/G162) | Positioning labels to maximize predictability of relationships | 286 | | QW-WCAG-T18 | [H51](https://www.w3.org/WAI/WCAG21/Techniques/html/H51) | Using table markup to present tabular information | 287 | | QW-WCAG-T19 | [H32](https://www.w3.org/WAI/WCAG21/Techniques/html/H32) | Providing submit buttons | 288 | | QW-WCAG-T20 | [H33](https://www.w3.org/WAI/WCAG21/Techniques/html/H33) | Supplementing link text with the title attribute | 289 | | QW-WCAG-T21 | [F89](https://www.w3.org/WAI/WCAG21/Techniques/failures/F89) | Failure of Success Criteria 2.4.4, 2.4.9 and 4.1.2 due to not providing an accessible name for an image which is the only content in a link | 290 | | QW-WCAG-T22 | [F52](https://www.w3.org/WAI/WCAG21/Techniques/failures/F52) | Failure of Success Criterion 3.2.1 and 3.2.5 due to opening a new window as soon as a new page is loaded | 291 | | QW-WCAG-T23 | [G1](https://www.w3.org/WAI/WCAG21/Techniques/general/G1) | Adding a link at the top of each page that goes directly to the main content area | 292 | | QW-WCAG-T24 | [F55](https://www.w3.org/WAI/WCAG21/Techniques/failures/F55) | Failure of Success Criteria 2.1.1, 2.4.7, and 3.2.1 due to using script to remove focus when focus is received | 293 | | QW-WCAG-T25 | [H63](https://www.w3.org/WAI/WCAG21/Techniques/html/H63) | Using the scope attribute to associate header cells and data cells in data tables | 294 | | QW-WCAG-T26 | [F59](https://www.w3.org/WAI/WCAG21/Techniques/failures/F59) | Failure of Success Criterion 4.1.2 due to using script to make div or span a user interface control in HTML without providing a role for the control | 295 | | QW-WCAG-T27 | [F88](https://www.w3.org/WAI/WCAG21/Techniques/failures/F88) | Failure of Success Criterion 1.4.8 due to using text that is justified (aligned to both the left and the right margins) | 296 | | QW-WCAG-T28 | [C12](https://www.w3.org/WAI/WCAG21/Techniques/css/C12) [C13](https://www.w3.org/WAI/WCAG21/Techniques/css/C13) [C14](https://www.w3.org/WAI/WCAG21/Techniques/css/C14) | Using `percent, em, names` for font sizes | 297 | | QW-WCAG-T29 | [C19](https://www.w3.org/WAI/WCAG21/Techniques/css/C19) | Specifying alignment either to the left or right in CSS | 298 | | QW-WCAG-T30 | [F4](https://www.w3.org/WAI/WCAG21/Techniques/failures/F4) | Failure of Success Criterion 2.2.2 due to using text-decoration:blink without a mechanism to stop it in less than five seconds | 299 | | QW-WCAG-T31 | [F24](https://www.w3.org/WAI/WCAG21/Techniques/failures/F24) | Failure of Success Criterion 1.4.3, 1.4.6 and 1.4.8 due to specifying foreground colors without specifying background colors or vice versa | 300 | | QW-WCAG-T32 | [H48](https://www.w3.org/WAI/WCAG21/Techniques/html/H48) | Using ol, ul and dl for lists or groups of links | 301 | 302 | # License 303 | 304 | ISC 305 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const cli = require('../dist/index'); 5 | 6 | (async () => { 7 | const args = Array.from(process.argv).slice(2); 8 | 9 | try { 10 | process.title = 'qw ' + args.join(' '); 11 | } catch(err) { 12 | process.title = 'qw'; 13 | } 14 | 15 | await cli(args); 16 | })(); 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qualweb/cli", 3 | "version": "0.6.4", 4 | "description": "QualWeb evaluator command line interface", 5 | "main": "dist/index.js", 6 | "bin": { 7 | "qw": "./bin/cli.js" 8 | }, 9 | "files": [ 10 | "dist/*" 11 | ], 12 | "scripts": { 13 | "tsc": "tsc", 14 | "test": "mocha", 15 | "prebuild": "rimraf dist", 16 | "lint": "eslint src --ext .ts", 17 | "lint:fix": "eslint src --ext .ts --fix", 18 | "format": "prettier --config .prettierrc 'src/**/*.ts' --write", 19 | "build": "npm run prebuild && npm run format && npm run lint && tsc --build", 20 | "typedoc": "typedoc --out docs src/index.ts", 21 | "prepare": "npm run build" 22 | }, 23 | "keywords": [ 24 | "qualweb", 25 | "cli", 26 | "a11y", 27 | "accessibility" 28 | ], 29 | "homepage": "https://github.com/qualweb/cli#readme", 30 | "bugs": { 31 | "url": "https://github.com/qualweb/cli/issues", 32 | "email": "qualweb@fc.ul.pt" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "https://github.com/qualweb/cli.git" 37 | }, 38 | "engines": { 39 | "node": ">=12.0.0" 40 | }, 41 | "author": "João Vicente", 42 | "license": "ISC", 43 | "dependencies": { 44 | "@qualweb/core": "0.6.15", 45 | "command-line-args": "^5.1.1", 46 | "command-line-usage": "^6.1.1", 47 | "log-update": "^4.0.0", 48 | "set-value": "^4.0.0" 49 | }, 50 | "devDependencies": { 51 | "@qualweb/types": "0.6.13", 52 | "@tsconfig/recommended": "^1.0.1", 53 | "@types/command-line-args": "^5.0.0", 54 | "@types/command-line-usage": "^5.0.1", 55 | "@types/node": "^16.4.1", 56 | "@types/set-value": "^4.0.0", 57 | "@typescript-eslint/eslint-plugin": "^4.28.4", 58 | "@typescript-eslint/parser": "^4.28.4", 59 | "chai": "^4.3.4", 60 | "eslint": "^7.31.0", 61 | "eslint-config-prettier": "^8.3.0", 62 | "eslint-plugin-prettier": "^3.4.0", 63 | "eslint-plugin-sonarjs": "^0.9.1", 64 | "mocha": "^9.0.2", 65 | "prettier": "^2.3.2", 66 | "rimraf": "^3.0.2", 67 | "typedoc": "^0.21.4", 68 | "typescript": "^4.3.5" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { QualWeb, generateEARLReport, EvaluationReport, QualwebOptions } from '@qualweb/core'; 2 | import { EarlOptions } from '@qualweb/earl-reporter'; 3 | import parse from './lib/parser'; 4 | import { saveReport } from './lib/fileUtils'; 5 | import { printHelp } from './lib/parserUtils'; 6 | 7 | async function cli(): Promise { 8 | try { 9 | const options = await parse(); 10 | 11 | const qualweb = new QualWeb({ adBlock: true, stealth: true }); 12 | 13 | await qualweb.start( 14 | { maxConcurrency: options.maxParallelEvaluations, timeout: options.timeout }, 15 | { args: ['--no-sandbox', '--ignore-certificate-errors'] } 16 | ); 17 | 18 | if (!options['wcag-techniques']) { 19 | options['wcag-techniques'] = {}; 20 | } 21 | 22 | options['wcag-techniques'].exclude = ['QW-WCAG-T16']; 23 | 24 | const reports = await qualweb.evaluate(options); 25 | await qualweb.stop(); 26 | 27 | await handleReporting(reports, options); 28 | } catch (err) { 29 | if (err?.message === 'Invalid input method') { 30 | printHelp(); 31 | } else { 32 | console.error(err); 33 | } 34 | } 35 | 36 | process.exit(0); 37 | } 38 | 39 | async function handleReporting(reports: { [url: string]: EvaluationReport }, options: QualwebOptions): Promise { 40 | const reportType = options.report; 41 | const saveName = options['save-name']; 42 | delete options.report; 43 | delete options['save-name']; 44 | 45 | if (reportType) { 46 | if (reportType === 'earl') { 47 | const earlReports = generateEARLReport(reports); 48 | for (const url in earlReports || {}) { 49 | await saveReport(url, earlReports[url]); 50 | } 51 | } else if (reportType === 'earl-a') { 52 | const earlOptions = checkEarlOptions(options, saveName); 53 | const earlReport = generateEARLReport(reports, earlOptions); 54 | const name = Object.keys(earlReport)[0]; 55 | await saveReport(name, earlReport[name], !!saveName); 56 | } else { 57 | throw new Error('Invalid reporter format'); 58 | } 59 | } else { 60 | for (const url in reports ?? {}) { 61 | const report = reports[url]; 62 | await saveReport(url, report); 63 | } 64 | } 65 | } 66 | 67 | function checkEarlOptions(options: QualwebOptions, saveName?: string): EarlOptions { 68 | const earlOptions: EarlOptions = { aggregated: true, aggregatedName: saveName }; 69 | if (options.execute) { 70 | earlOptions.modules = {}; 71 | earlOptions.modules.act = !!options?.execute?.act; 72 | earlOptions.modules.wcag = !!options?.execute?.wcag; 73 | earlOptions.modules['best-practices'] = !!options?.execute?.bp; 74 | } 75 | 76 | return earlOptions; 77 | } 78 | 79 | export = cli; 80 | -------------------------------------------------------------------------------- /src/lib/act-rules.options.json: -------------------------------------------------------------------------------- 1 | { 2 | "qualweb_id": [ 3 | "QW-ACT-R1", 4 | "QW-ACT-R2", 5 | "QW-ACT-R3", 6 | "QW-ACT-R4", 7 | "QW-ACT-R5", 8 | "QW-ACT-R6", 9 | "QW-ACT-R7", 10 | "QW-ACT-R9", 11 | "QW-ACT-R10", 12 | "QW-ACT-R11", 13 | "QW-ACT-R12", 14 | "QW-ACT-R13", 15 | "QW-ACT-R14", 16 | "QW-ACT-R15", 17 | "QW-ACT-R16", 18 | "QW-ACT-R17", 19 | "QW-ACT-R18", 20 | "QW-ACT-R19", 21 | "QW-ACT-R20", 22 | "QW-ACT-R21", 23 | "QW-ACT-R22", 24 | "QW-ACT-R23", 25 | "QW-ACT-R24", 26 | "QW-ACT-R25", 27 | "QW-ACT-R26", 28 | "QW-ACT-R27", 29 | "QW-ACT-R28", 30 | "QW-ACT-R29", 31 | "QW-ACT-R30", 32 | "QW-ACT-R31", 33 | "QW-ACT-R32", 34 | "QW-ACT-R33", 35 | "QW-ACT-R34", 36 | "QW-ACT-R35", 37 | "QW-ACT-R36", 38 | "QW-ACT-R37", 39 | "QW-ACT-R38", 40 | "QW-ACT-R39", 41 | "QW-ACT-R40", 42 | "QW-ACT-R41", 43 | "QW-ACT-R42", 44 | "QW-ACT-R43", 45 | "QW-ACT-R44", 46 | "QW-ACT-R48", 47 | "QW-ACT-R49", 48 | "QW-ACT-R50", 49 | "QW-ACT-R51", 50 | "QW-ACT-R52", 51 | "QW-ACT-R53", 52 | "QW-ACT-R54", 53 | "QW-ACT-R56", 54 | "QW-ACT-R57", 55 | "QW-ACT-R58", 56 | "QW-ACT-R59", 57 | "QW-ACT-R60", 58 | "QW-ACT-R61", 59 | "QW-ACT-R62", 60 | "QW-ACT-R63", 61 | "QW-ACT-R64", 62 | "QW-ACT-R65", 63 | "QW-ACT-R66", 64 | "QW-ACT-R67", 65 | "QW-ACT-R68", 66 | "QW-ACT-R69", 67 | "QW-ACT-R70", 68 | "QW-ACT-R71", 69 | "QW-ACT-R72", 70 | "QW-ACT-R73", 71 | "QW-ACT-R74", 72 | "QW-ACT-R75", 73 | "QW-ACT-R76" 74 | ], 75 | "act_id": [ 76 | "2779a5", 77 | "b5c3f8", 78 | "5b7ae0", 79 | "bc659a", 80 | "bf051a", 81 | "59796f", 82 | "b33eff", 83 | "b20e66", 84 | "4b1c6c", 85 | "97a4e1", 86 | "c487ae", 87 | "6cfa84", 88 | "b4f0c3", 89 | "80f0bf", 90 | "e086e5", 91 | "23a2a8", 92 | "3ea0c8", 93 | "cae760", 94 | "674b10", 95 | "7d6734", 96 | "de46e4", 97 | "c5a4ea", 98 | "73f2c2", 99 | "5c01ea", 100 | "eac66b", 101 | "5f99a7", 102 | "4e8ab6", 103 | "e7aa44", 104 | "2ee8b8", 105 | "c3232f", 106 | "1ec09b", 107 | "ff89c9", 108 | "6a7281", 109 | "ffd0e9", 110 | "a25f45", 111 | "afw4f7", 112 | "bc4a75", 113 | "d0f69e", 114 | "59br37", 115 | "36b590", 116 | "8fc3b6", 117 | "0ssw9k", 118 | "fd3a94", 119 | "46ca7f", 120 | "aaa1bf", 121 | "4c31df", 122 | "fd26cf", 123 | "ac7dc6", 124 | "ee13b5", 125 | "d7ba54", 126 | "1ea59c", 127 | "ab4d13", 128 | "f196ce", 129 | "2eb176", 130 | "afb423", 131 | "f51b46", 132 | "1a02b0", 133 | "oj04fd", 134 | "b40fd1", 135 | "047fe0", 136 | "307n5z", 137 | "m6b1q3", 138 | "24afc2", 139 | "78fd32", 140 | "9e45ec", 141 | "akn7bn", 142 | "bisz58", 143 | "8a213c", 144 | "3e12e1", 145 | "ye5d6e", 146 | "cf77f2", 147 | "09o5cg" 148 | ] 149 | } 150 | -------------------------------------------------------------------------------- /src/lib/actParser.ts: -------------------------------------------------------------------------------- 1 | import { validateACT, validatePrinciples, validateLevels, printError } from './parserUtils'; 2 | import { ACTRJsonFile, readJsonFile, fileExists } from './fileUtils'; 3 | import { CommandLineOptions } from 'command-line-args'; 4 | import { QualwebOptions } from '@qualweb/core'; 5 | import setValue from 'set-value'; 6 | 7 | async function parseACT(mainOptions: CommandLineOptions, options: QualwebOptions): Promise { 8 | options['act-rules'] = {}; 9 | 10 | await validateACTRules(mainOptions, options); 11 | validateACTExclusions(mainOptions, options); 12 | validateACTLevels(mainOptions, options); 13 | validateACTPrinciples(mainOptions, options); 14 | 15 | if (Object.keys(options['act-rules']).length === 0) { 16 | delete options['act-rules']; 17 | } 18 | } 19 | 20 | function validateModule(mainOptions: CommandLineOptions, options: QualwebOptions): void { 21 | if (mainOptions.module && options?.execute?.act === undefined) { 22 | printError('The "--act-rules" option doesn\'t match any of the modules selected.'); 23 | } else { 24 | console.warn('Warning: Module act has options but is not select. Will be select automatically.'); 25 | setValue(options, 'execute.act', true); 26 | } 27 | } 28 | 29 | async function validateACTRules(mainOptions: CommandLineOptions, options: QualwebOptions): Promise { 30 | if (mainOptions['act-rules'] && options['act-rules']) { 31 | validateModule(mainOptions, options); 32 | 33 | if (mainOptions['act-rules'].length === 1) { 34 | if (await fileExists(mainOptions['act-rules'][0])) { 35 | const rules = await readJsonFile(mainOptions['act-rules'][0]); 36 | options['act-rules'].rules = [...(rules['act-rules'].rules ?? [])]; 37 | } else { 38 | options['act-rules'].rules = [...mainOptions['act-rules']]; 39 | } 40 | } else { 41 | options['act-rules'].rules = [...mainOptions['act-rules']]; 42 | } 43 | 44 | validateACT(options['act-rules'].rules); 45 | } 46 | } 47 | 48 | function validateACTExclusions(mainOptions: CommandLineOptions, options: QualwebOptions): void { 49 | if (mainOptions['exclude-act'] && options['act-rules']) { 50 | validateModule(mainOptions, options); 51 | options['act-rules'].exclude = [...mainOptions['exclude-act']]; 52 | validateACT(options['act-rules'].exclude); 53 | } 54 | } 55 | 56 | function validateACTLevels(mainOptions: CommandLineOptions, options: QualwebOptions): void { 57 | if (mainOptions['act-levels'] && options['act-rules']) { 58 | validateModule(mainOptions, options); 59 | options['act-rules'].levels = [...mainOptions['act-levels']]; 60 | validateLevels(options['act-rules'].levels); 61 | } 62 | } 63 | 64 | function validateACTPrinciples(mainOptions: CommandLineOptions, options: QualwebOptions): void { 65 | if (mainOptions['act-principles'] && options['act-rules']) { 66 | validateModule(mainOptions, options); 67 | options['act-rules'].principles = [...mainOptions['act-principles']]; 68 | validatePrinciples(options['act-rules'].principles); 69 | } 70 | } 71 | 72 | export = parseACT; 73 | -------------------------------------------------------------------------------- /src/lib/bpParser.ts: -------------------------------------------------------------------------------- 1 | import { validateBP, printError } from './parserUtils'; 2 | import { BPJsonFile, readJsonFile, fileExists } from './fileUtils'; 3 | import { CommandLineOptions } from 'command-line-args'; 4 | import { QualwebOptions } from '@qualweb/core'; 5 | import setValue from 'set-value'; 6 | 7 | async function parseBP(mainOptions: CommandLineOptions, options: QualwebOptions): Promise { 8 | options['best-practices'] = {}; 9 | 10 | await validateBestPractices(mainOptions, options); 11 | validateBPExclusions(mainOptions, options); 12 | 13 | if (Object.keys(options['best-practices']).length === 0) { 14 | delete options['best-practices']; 15 | } 16 | } 17 | 18 | function validateModule(mainOptions: CommandLineOptions, options: QualwebOptions): void { 19 | if (mainOptions.module && options?.execute?.act === undefined) { 20 | printError('The "--best-practices" option doesn\'t match any of the modules selected.'); 21 | } else { 22 | console.warn('Warning: Module bp has options but is not select. Will be select automatically.'); 23 | setValue(options, 'execute.bp', true); 24 | } 25 | } 26 | 27 | async function validateBestPractices(mainOptions: CommandLineOptions, options: QualwebOptions): Promise { 28 | if (mainOptions['best-practices'] && options['best-practices']) { 29 | validateModule(mainOptions, options); 30 | 31 | if (mainOptions['best-practices'].length === 1) { 32 | if (await fileExists(mainOptions['best-practices'][0])) { 33 | const bps = await readJsonFile(mainOptions['best-practices'][0]); 34 | options['best-practices'].bestPractices = [...(bps['best-practices'].bestPractices ?? [])]; 35 | } else { 36 | options['best-practices'].bestPractices = [...mainOptions['best-practices']]; 37 | } 38 | } else { 39 | options['best-practices'].bestPractices = [...mainOptions['best-practices']]; 40 | } 41 | 42 | validateBP(options['best-practices'].bestPractices); 43 | } 44 | } 45 | 46 | function validateBPExclusions(mainOptions: CommandLineOptions, options: QualwebOptions): void { 47 | if (mainOptions['exclude-bp'] && options['best-practices']) { 48 | validateModule(mainOptions, options); 49 | options['best-practices'].exclude = [...mainOptions['exclude-bp']]; 50 | validateBP(options['best-practices'].exclude); 51 | } 52 | } 53 | 54 | export = parseBP; 55 | -------------------------------------------------------------------------------- /src/lib/fileUtils.ts: -------------------------------------------------------------------------------- 1 | import { ACTROptions } from '@qualweb/act-rules'; 2 | import { BPOptions } from '@qualweb/best-practices'; 3 | import { EvaluationReport } from '@qualweb/core'; 4 | import { EarlReport } from '@qualweb/earl-reporter'; 5 | import { WCAGOptions } from '@qualweb/wcag-techniques'; 6 | import fs from 'fs'; 7 | 8 | interface ACTRJsonFile { 9 | 'act-rules': ACTROptions; 10 | } 11 | 12 | interface WCAGTJsonFile { 13 | 'wcag-techniques': WCAGOptions; 14 | } 15 | 16 | interface BPJsonFile { 17 | 'best-practices': BPOptions; 18 | } 19 | 20 | function writeFile(file: string, data: string): Promise { 21 | return new Promise((resolve, reject) => { 22 | fs.writeFile(file, data, (err) => { 23 | if (err) reject(err); 24 | else resolve(); 25 | }); 26 | }); 27 | } 28 | 29 | async function saveReport(name: string, report: EvaluationReport | EarlReport, overrideName = false): Promise { 30 | const path = process.cwd(); 31 | const filename = overrideName ? name : `${encodeURIComponent(name)}_${new Date().getTime()}.json`; 32 | 33 | await writeFile(`${path}/${filename}`, JSON.stringify(report, null, 2)); 34 | } 35 | 36 | function readJsonFile(filePath: string): Promise { 37 | return new Promise((resolve, reject) => { 38 | fs.readFile(filePath, (err, data) => { 39 | if (err) reject(err); 40 | else resolve(JSON.parse(data.toString())); 41 | }); 42 | }); 43 | } 44 | 45 | function fileExists(filePath: string): Promise { 46 | return new Promise((resolve) => { 47 | try { 48 | fs.access(filePath, () => { 49 | resolve(true); 50 | }); 51 | } catch (err) { 52 | resolve(false); 53 | } 54 | }); 55 | } 56 | 57 | export { ACTRJsonFile, WCAGTJsonFile, BPJsonFile, readJsonFile, saveReport, fileExists }; 58 | -------------------------------------------------------------------------------- /src/lib/options.ts: -------------------------------------------------------------------------------- 1 | import actRulesJson from './act-rules.options.json'; 2 | 3 | const header = ` _____ _____ _____ __ _ _ _ _____ _____ _____ __ _____ 4 | | | | | _ | | | | | | __| __ | | | | | | 5 | | | | | | | |__| | | | __| __ -| | --| |__|- -| 6 | |__ _|_____|__|__|_____|_____|_____|_____| |_____|_____|_____| 7 | |__| `; 8 | 9 | const strings = {}; 10 | const modules = ['act', 'html', 'css', 'bp', /*'wappalyzer',*/ 'counter']; 11 | const reports = ['earl', 'earl-a']; 12 | const actRules = [...actRulesJson.qualweb_id, ...actRulesJson.act_id]; 13 | const wcagTechniques = [ 14 | 'QW-WCAG-T1', 15 | 'QW-WCAG-T2', 16 | 'QW-WCAG-T3', 17 | 'QW-WCAG-T4', 18 | 'QW-WCAG-T5', 19 | 'QW-WCAG-T6', 20 | 'QW-WCAG-T7', 21 | 'QW-WCAG-T8', 22 | 'QW-WCAG-T9', 23 | 'QW-WCAG-T10', 24 | 'QW-WCAG-T11', 25 | 'QW-WCAG-T12', 26 | 'QW-WCAG-T13', 27 | 'QW-WCAG-T14', 28 | 'QW-WCAG-T15', 29 | 'QW-WCAG-T16', 30 | 'QW-WCAG-T17', 31 | 'QW-WCAG-T18', 32 | 'QW-WCAG-T19', 33 | 'QW-WCAG-T20', 34 | 'QW-WCAG-T21', 35 | 'QW-WCAG-T22', 36 | 'QW-WCAG-T23', 37 | 'QW-WCAG-T24', 38 | 'QW-WCAG-T25', 39 | 'QW-WCAG-T26', 40 | 'QW-WCAG-T27', 41 | 'QW-WCAG-T28', 42 | 'QW-WCAG-T29', 43 | 'QW-WCAG-T30', 44 | 'QW-WCAG-T31', 45 | 'QW-WCAG-T32' 46 | ]; 47 | const bps = [ 48 | 'QW-BP1', 49 | 'QW-BP2', 50 | 'QW-BP4', 51 | 'QW-BP5', 52 | 'QW-BP6', 53 | 'QW-BP7', 54 | 'QW-BP8', 55 | 'QW-BP9', 56 | 'QW-BP10', 57 | 'QW-BP11', 58 | 'QW-BP12', 59 | 'QW-BP13', 60 | 'QW-BP15', 61 | 'QW-BP17', 62 | 'QW-BP18' 63 | ]; 64 | const levels = ['A', 'AA', 'AAA']; 65 | const principles = ['Perceivable', 'Operable', 'Understandable', 'Robust']; 66 | const viewport = [ 67 | { 68 | name: 'viewport', 69 | alias: 'v', 70 | description: 'Use custom viewport.', 71 | type: Boolean 72 | }, 73 | { 74 | name: 'mobile', 75 | description: 'Use mobile mode.', 76 | type: Boolean 77 | }, 78 | { 79 | name: 'orientation', 80 | description: 'Orientation of the screen.', 81 | typeLabel: '{underline portrait or landscape}', 82 | type: String 83 | }, 84 | { 85 | name: 'user-agent', 86 | description: 'User agent for the execution.', 87 | typeLabel: '{underline user agent string}', 88 | type: Boolean 89 | }, 90 | { 91 | name: 'width', 92 | description: 'Width of the viewport.', 93 | type: Number 94 | }, 95 | { 96 | name: 'height', 97 | description: 'Height of the viewport.', 98 | type: Number 99 | } 100 | ]; 101 | const moduleFilters = [ 102 | { 103 | name: 'act-rules', 104 | typeLabel: 105 | '{underline file-path} or [ QW-ACT-R1 ... QW-ACT-R' + actRulesJson.qualweb_id.length + ' ] or [ ACT Rule ID ]', 106 | type: String, 107 | multiple: true, 108 | description: 'Choose which ACT rules to execute. Can be multiple.' 109 | }, 110 | { 111 | name: 'exclude-act', 112 | typeLabel: 113 | '{underline file-path} or [ QW-ACT-R1 ... QW-ACT-R' + actRulesJson.qualweb_id.length + ' ] or [ ACT Rule ID ]', 114 | type: String, 115 | multiple: true, 116 | description: 'Choose which ACT rules to exclude. Can be multiple.' 117 | }, 118 | { 119 | name: 'act-levels', 120 | typeLabel: '[ ' + levels.join(' | ') + ' ]', 121 | type: String, 122 | multiple: true, 123 | description: 'Choose which conform levels to evaluate for the act rules only. Can be multiple.' 124 | }, 125 | { 126 | name: 'act-principles', 127 | typeLabel: '[ ' + principles.join(' | ') + ' ]', 128 | type: String, 129 | multiple: true, 130 | description: 'Choose which principles to evaluate for the act rules only. Can be multiple.' 131 | }, 132 | { 133 | name: 'wcag-techniques', 134 | typeLabel: '{underline file-path} or [ QW-WCAG-T1 ... QW-WCAG-T' + wcagTechniques.length + ' ]', 135 | type: String, 136 | multiple: true, 137 | description: 'Choose which wcag techniques to execute. Can be multiple.' 138 | }, 139 | { 140 | name: 'exclude-wcag', 141 | typeLabel: '{underline file-path} or [ QW-WCAG-T1 ... QW-WCAG-T' + wcagTechniques.length + ' ]', 142 | type: String, 143 | multiple: true, 144 | description: 'Choose which wcag techniques to exclude. Can be multiple.' 145 | }, 146 | { 147 | name: 'wcag-levels', 148 | typeLabel: '[ ' + levels.join(' | ') + ' ]', 149 | type: String, 150 | multiple: true, 151 | description: 'Choose which conform levels to evaluate for the wcag techniques only. Can be multiple.' 152 | }, 153 | { 154 | name: 'wcag-principles', 155 | typeLabel: '[ ' + principles.join(' | ') + ' ]', 156 | type: String, 157 | multiple: true, 158 | description: 'Choose which principles to evaluate for the wcag techniques only. Can be multiple.' 159 | }, 160 | { 161 | name: 'best-practices', 162 | typeLabel: '{underline file-path} or [ QW-BP1 ... QW-BP' + bps.length + ' ]', 163 | type: String, 164 | multiple: true, 165 | description: 'Choose which best-practices to execute. Can be multiple.' 166 | }, 167 | { 168 | name: 'exclude-bp', 169 | typeLabel: '{underline file-path} or [ QW-BP1 ... QW-BP' + bps.length + ' ]', 170 | type: String, 171 | multiple: true, 172 | description: 'Choose which best-practices to exclude. Can be multiple.' 173 | } 174 | ]; 175 | const options = [ 176 | { 177 | name: 'url', 178 | alias: 'u', 179 | type: String, 180 | typeLabel: '{underline url}', 181 | description: 'Url to evaluate.' 182 | }, 183 | { 184 | name: 'file', 185 | alias: 'f', 186 | type: String, 187 | typeLabel: '{underline path-to-file}', 188 | description: 'File with urls to evaluate.' 189 | }, 190 | { 191 | name: 'crawl', 192 | alias: 'c', 193 | type: String, 194 | typeLabel: '{underline domain}', 195 | description: 'Domain to crawl.' 196 | }, 197 | { 198 | name: 'module', 199 | alias: 'm', 200 | type: String, 201 | multiple: true, 202 | typeLabel: '[ ' + modules.join(' | ') + ' ]', 203 | description: 'Choose which modules to execute. Can be multiple' 204 | }, 205 | { 206 | name: 'report-type', 207 | alias: 'r', 208 | type: String, 209 | typeLabel: '[ ' + reports.join(' | ') + ' ]', 210 | description: 'Convert the evaluation to `earl` or `earl-a` (earl-aggregated).' 211 | }, 212 | { 213 | name: 'save-name', 214 | alias: 's', 215 | type: String, 216 | typeLabel: '{underline name}', 217 | description: 'The name to save the aggregated earl reports (earl-a).' 218 | }, 219 | { 220 | name: 'timeout', 221 | alias: 't', 222 | type: Number, 223 | typeLabel: '{underline number}', 224 | description: 'Timeout for loading page.' 225 | }, 226 | { 227 | name: 'waitUntil', 228 | alias: 'w', 229 | type: String, 230 | typeLabel: '[ load | domcontentloaded | networkidle0 | networkidle2 ]', 231 | description: 'Specify which events to wait before starting the evaluation. Can be multiple.' 232 | }, 233 | { 234 | name: 'maxParallelEvaluations', 235 | alias: 'p', 236 | type: Number, 237 | typeLabel: '{underline number}', 238 | description: 'Evaluates multiples urls at the same time.' 239 | }, 240 | { 241 | name: 'json', 242 | alias: 'j', 243 | type: String, 244 | typeLabel: '{underline path-to-json}', 245 | description: 'Loads a json file with the configs to execute.' 246 | }, 247 | { 248 | name: 'help', 249 | alias: 'h', 250 | description: 'Print this usage guide.', 251 | type: Boolean 252 | } 253 | ]; 254 | const sections = [ 255 | { 256 | content: header, 257 | raw: true 258 | }, 259 | { 260 | header: 'QualWeb CLI', 261 | content: 'QualWeb command line interface.' 262 | }, 263 | { 264 | header: 'Usage', 265 | content: [ 266 | '$ qw [OPTION] ...', 267 | '$ qw [OPTION] ... [-r] ...', 268 | '$ qw [-m] act [-act-rules | -act-levels | -act-principles] ...', 269 | '$ qw [-m] wcag [-wcag-techniques | -wcag-levels | -wcag-principles] ...', 270 | '$ qw [-m] bp [-best-practices] ...' 271 | ] 272 | }, 273 | { 274 | header: 'Options', 275 | optionList: options 276 | }, 277 | { 278 | header: 'Viewport Options', 279 | optionList: viewport 280 | }, 281 | { 282 | header: 'Module Filters', 283 | optionList: moduleFilters 284 | } 285 | ]; 286 | 287 | const optionList = [...options, ...viewport, ...moduleFilters]; 288 | 289 | export { optionList, sections, strings, actRules, wcagTechniques, bps, reports, levels, principles, modules }; 290 | -------------------------------------------------------------------------------- /src/lib/parser.ts: -------------------------------------------------------------------------------- 1 | import { optionList, reports, modules } from './options'; 2 | import { readJsonFile } from './fileUtils'; 3 | import { printHelp, printError } from './parserUtils'; 4 | import parseACT from './actParser'; 5 | import parseWCAG from './wcagParser'; 6 | import parseBP from './bpParser'; 7 | 8 | import commandLineArgs, { CommandLineOptions } from 'command-line-args'; 9 | import { QualwebOptions } from '@qualweb/core'; 10 | import setValue from 'set-value'; 11 | 12 | function parseInputMethods(mainOptions: CommandLineOptions, options: QualwebOptions): void { 13 | if (mainOptions.url) { 14 | options.url = mainOptions.url; 15 | } 16 | 17 | if (mainOptions.file) { 18 | options.file = mainOptions.file; 19 | } 20 | 21 | if (mainOptions.crawl) { 22 | options.crawl = mainOptions.crawl; 23 | } 24 | } 25 | 26 | function parseModules(mainOptions: CommandLineOptions, options: QualwebOptions): void { 27 | if (mainOptions.module) { 28 | options.execute = {}; 29 | const modulesToExecute = mainOptions.module; 30 | 31 | for (const module of modulesToExecute ?? []) { 32 | if (!modules.includes(module.replace(',', '').trim())) { 33 | printError('Module ' + module.replace(',', '').trim() + ' does not exist.'); 34 | } else { 35 | const mod = module.replace(',', '').trim(); 36 | switch (mod) { 37 | case 'act': 38 | options.execute.act = true; 39 | break; 40 | case 'wcag': 41 | options.execute.wcag = true; 42 | break; 43 | case 'bp': 44 | options.execute.bp = true; 45 | break; 46 | // case 'wappalyzer': 47 | // options.execute.wappalyzer = true; 48 | break; 49 | case 'counter': 50 | options.execute.counter = true; 51 | break; 52 | default: 53 | printError('Module ' + mod + ' does not exist.'); 54 | break; 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | function parseViewport(mainOptions: CommandLineOptions, options: QualwebOptions): void { 62 | if (mainOptions.viewport) { 63 | options.viewport = { 64 | mobile: false, 65 | landscape: true, 66 | userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:22.0) Gecko/20100101 Firefox/22.0', 67 | resolution: { 68 | width: 1366, 69 | height: 768 70 | } 71 | }; 72 | 73 | if (mainOptions.mobile) { 74 | options.viewport.mobile = mainOptions.mobile; 75 | } 76 | 77 | if (mainOptions.orientation) { 78 | options.viewport.landscape = mainOptions.orientation !== 'portrait'; 79 | } 80 | 81 | if (mainOptions['user-agent']) { 82 | options.viewport.userAgent = mainOptions['user-agent']; 83 | } 84 | 85 | if (mainOptions.width) { 86 | setValue(options, 'viewport.resolution.width', mainOptions.width); 87 | } 88 | 89 | if (mainOptions.height) { 90 | setValue(options, 'viewport.resolution.height', mainOptions.height); 91 | } 92 | } 93 | } 94 | 95 | function parseSystemOptions(mainOptions: CommandLineOptions, options: QualwebOptions): void { 96 | if (mainOptions.timeout) { 97 | options.timeout = mainOptions.timeout; 98 | } 99 | 100 | if (mainOptions.waitUntil) { 101 | options.waitUntil = mainOptions.waitUntil.split(' '); 102 | } 103 | 104 | if (mainOptions.maxParallelEvaluations) { 105 | options.maxParallelEvaluations = mainOptions.maxParallelEvaluations; 106 | } 107 | } 108 | 109 | function parseReportType(mainOptions: CommandLineOptions, options: QualwebOptions): void { 110 | const reportType = 'report-type'; 111 | 112 | if (mainOptions[reportType]) { 113 | options.report = mainOptions[reportType]; 114 | 115 | if (!reports.includes(mainOptions[reportType])) { 116 | printError('Wrong report type selected.'); 117 | } 118 | } 119 | } 120 | 121 | function parseSaveName(mainOptions: CommandLineOptions, options: QualwebOptions): void { 122 | if (mainOptions['save-name']) { 123 | options['save-name'] = mainOptions['save-name']; 124 | } 125 | } 126 | 127 | async function parse(): Promise { 128 | let mainOptions = commandLineArgs(optionList, { stopAtFirstUnknown: true }); 129 | const options: QualwebOptions = {}; 130 | 131 | if (mainOptions._unknown) { 132 | printHelp(); 133 | } 134 | 135 | if (mainOptions.json) { 136 | mainOptions = await readJsonFile(mainOptions['json']); 137 | } 138 | 139 | parseInputMethods(mainOptions, options); 140 | 141 | parseModules(mainOptions, options); 142 | 143 | parseViewport(mainOptions, options); 144 | 145 | parseSystemOptions(mainOptions, options); 146 | 147 | parseReportType(mainOptions, options); 148 | 149 | parseSaveName(mainOptions, options); 150 | 151 | ////////////////////////////////////////////////////////////////////////////////// 152 | // ACT /////////////////////////////////////////////////////////////////////////// 153 | ////////////////////////////////////////////////////////////////////////////////// 154 | 155 | await parseACT(mainOptions, options); 156 | 157 | ////////////////////////////////////////////////////////////////////////////////// 158 | // WCAG ////////////////////////////////////////////////////////////////////////// 159 | ////////////////////////////////////////////////////////////////////////////////// 160 | 161 | await parseWCAG(mainOptions, options); 162 | 163 | ////////////////////////////////////////////////////////////////////////////////// 164 | // BP //////////////////////////////////////////////////////////////////////////// 165 | ////////////////////////////////////////////////////////////////////////////////// 166 | 167 | await parseBP(mainOptions, options); 168 | 169 | ////////////////////////////////////////////////////////////////////////////////// 170 | // HELP ////////////////////////////////////////////////////////////////////////// 171 | ////////////////////////////////////////////////////////////////////////////////// 172 | 173 | if (mainOptions.help) { 174 | printHelp(); 175 | } 176 | 177 | return options; 178 | } 179 | 180 | export = parse; 181 | -------------------------------------------------------------------------------- /src/lib/parserUtils.ts: -------------------------------------------------------------------------------- 1 | import { sections, actRules, wcagTechniques, bps, levels, principles } from './options'; 2 | import commandLineUsage from 'command-line-usage'; 3 | 4 | function printHelp(): void { 5 | console.log(commandLineUsage(sections)); 6 | process.exit(0); 7 | } 8 | 9 | function printError(err: string): void { 10 | console.error(err); 11 | console.log('To get help please run'); 12 | console.log(' $ qw --help'); 13 | process.exit(0); 14 | } 15 | 16 | function validatePrinciples(arrayPrinciples: string[] | undefined): void { 17 | if (arrayPrinciples) { 18 | const valid = arrayContainsArray(arrayPrinciples, principles); 19 | if (!valid) { 20 | printError('Invalid principle(s) selected.'); 21 | } 22 | } 23 | } 24 | 25 | function validateLevels(arrayLevels: string[] | undefined): void { 26 | if (arrayLevels) { 27 | const valid = arrayContainsArray(arrayLevels, levels); 28 | if (!valid) { 29 | printError('Invalid level(s) selected.'); 30 | } 31 | } 32 | } 33 | 34 | function validateACT(rules: string[] | undefined): void { 35 | if (rules) { 36 | const valid = arrayContainsArray(rules, actRules); 37 | if (!valid) { 38 | printError('Invalid rule(s) selected.'); 39 | } 40 | } 41 | } 42 | 43 | function validateWCAG(techniques: string[] | undefined): void { 44 | if (techniques) { 45 | const valid = arrayContainsArray(techniques, wcagTechniques); 46 | if (!valid) { 47 | printError('Invalid techniques(s) selected.'); 48 | } 49 | } 50 | } 51 | 52 | function validateBP(bestPractices: string[] | undefined): void { 53 | if (bestPractices) { 54 | const valid = arrayContainsArray(bestPractices, bps); 55 | if (!valid) { 56 | printError('Invalid best-practice(s) selected.'); 57 | } 58 | } 59 | } 60 | 61 | /** 62 | * true if arr2 contains arr1 63 | */ 64 | function arrayContainsArray(arr1: string[], arr2: string[]): boolean { 65 | return arr1.some((r) => arr2.includes(r)); 66 | } 67 | 68 | export { printHelp, printError, validatePrinciples, validateLevels, validateACT, validateWCAG, validateBP }; 69 | -------------------------------------------------------------------------------- /src/lib/wcagParser.ts: -------------------------------------------------------------------------------- 1 | import { validateWCAG, validatePrinciples, validateLevels, printError } from './parserUtils'; 2 | import { readJsonFile, fileExists, WCAGTJsonFile } from './fileUtils'; 3 | import { CommandLineOptions } from 'command-line-args'; 4 | import { QualwebOptions } from '@qualweb/core'; 5 | import setValue from 'set-value'; 6 | 7 | async function parseWCAG(mainOptions: CommandLineOptions, options: QualwebOptions): Promise { 8 | options['wcag-techniques'] = {}; 9 | 10 | await validateWCAGTechniques(mainOptions, options); 11 | validateWCAGExclusions(mainOptions, options); 12 | validateWCAGLevels(mainOptions, options); 13 | validateWCAGPrinciples(mainOptions, options); 14 | 15 | if (Object.keys(options['wcag-techniques']).length === 0) { 16 | delete options['wcag-techniques']; 17 | } 18 | } 19 | 20 | function validateModule(mainOptions: CommandLineOptions, options: QualwebOptions): void { 21 | if (mainOptions.module && options?.execute?.wcag === undefined) { 22 | printError('The "--wcag-techniques" option doesn\'t match any of the modules selected.'); 23 | } else { 24 | console.warn('Warning: Module wcag has options but is not select. Will be select automatically.'); 25 | setValue(options, 'execute.wcag', true); 26 | } 27 | } 28 | 29 | async function validateWCAGTechniques(mainOptions: CommandLineOptions, options: QualwebOptions): Promise { 30 | if (mainOptions['wcag-techniques'] && options['wcag-techniques']) { 31 | validateModule(mainOptions, options); 32 | 33 | if (mainOptions['wcag-techniques'].length === 1) { 34 | if (await fileExists(mainOptions['wcag-techniques'][0])) { 35 | const techniques = await readJsonFile(mainOptions['wcag-techniques'][0]); 36 | options['wcag-techniques'].techniques = [...(techniques['wcag-techniques'].techniques ?? [])]; 37 | } else { 38 | options['wcag-techniques'].techniques = [...mainOptions['wcag-techniques']]; 39 | } 40 | } else { 41 | options['wcag-techniques'].techniques = [...mainOptions['wcag-techniques']]; 42 | } 43 | 44 | validateWCAG(options['wcag-techniques'].techniques); 45 | } 46 | } 47 | 48 | function validateWCAGExclusions(mainOptions: CommandLineOptions, options: QualwebOptions): void { 49 | if (mainOptions['exclude-wcag'] && options['wcag-techniques']) { 50 | validateModule(mainOptions, options); 51 | options['wcag-techniques'].exclude = [...mainOptions['exclude-wcag']]; 52 | validateWCAG(options['wcag-techniques'].exclude); 53 | } 54 | } 55 | 56 | function validateWCAGLevels(mainOptions: CommandLineOptions, options: QualwebOptions): void { 57 | if (mainOptions['wcag-levels'] && options['wcag-techniques']) { 58 | validateModule(mainOptions, options); 59 | options['wcag-techniques']['levels'] = [...mainOptions['wcag-levels']]; 60 | validateLevels(options['wcag-techniques'].levels); 61 | } 62 | } 63 | 64 | function validateWCAGPrinciples(mainOptions: CommandLineOptions, options: QualwebOptions): void { 65 | if (mainOptions['wcag-principles'] && options['wcag-techniques']) { 66 | validateModule(mainOptions, options); 67 | options['wcag-techniques'].principles = [...mainOptions['wcag-principles']]; 68 | validatePrinciples(options['wcag-techniques'].principles); 69 | } 70 | } 71 | 72 | export = parseWCAG; 73 | -------------------------------------------------------------------------------- /test/cli.spec.mjs: -------------------------------------------------------------------------------- 1 | import cli from '../dist/index.js'; 2 | 3 | describe('CLI', function () { 4 | it('Print options', async function () { 5 | await cli(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/recommended/tsconfig.json", 3 | "compilerOptions": { 4 | /* Basic Options */ 5 | "resolveJsonModule": true, 6 | "target": "ES2019", 7 | "lib": ["ES2019"], 8 | "declaration": true /* Generates corresponding '.d.ts' file. */, 9 | "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */, 10 | "sourceMap": true /* Generates corresponding '.map' file. */, 11 | "outDir": "./dist" /* Redirect output structure to the directory. */, 12 | "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 13 | "removeComments": true /* Do not emit comments to output. */, 14 | 15 | /* Strict Type-Checking Options */ 16 | "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 17 | "strictNullChecks": true /* Enable strict null checks. */, 18 | "strictFunctionTypes": true /* Enable strict checking of function types. */, 19 | "strictBindCallApply": true /* Enable strict 'bind', 'call', and 'apply' methods on functions. */, 20 | "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 21 | "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 22 | "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 23 | 24 | /* Additional Checks */ 25 | "noUnusedLocals": true /* Report errors on unused locals. */, 26 | "noUnusedParameters": true /* Report errors on unused parameters. */, 27 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 28 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 29 | 30 | /* Module Resolution Options */ 31 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 32 | "typeRoots": ["@qualweb/types"] /* List of folders to include type definitions from. */, 33 | "types": ["@qualweb/types", "node"] /* Type declaration files to be included in compilation. */ 34 | } 35 | } 36 | --------------------------------------------------------------------------------