react-native-accessibility-scanner 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +501 -0
  3. package/dist/cli/index.d.ts +3 -0
  4. package/dist/cli/index.d.ts.map +1 -0
  5. package/dist/cli/index.js +131 -0
  6. package/dist/cli/index.js.map +1 -0
  7. package/dist/eslint/index.d.ts +59 -0
  8. package/dist/eslint/index.d.ts.map +1 -0
  9. package/dist/eslint/index.js +181 -0
  10. package/dist/eslint/index.js.map +1 -0
  11. package/dist/index.d.ts +12 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +36 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/reporters/console-reporter.d.ts +5 -0
  16. package/dist/reporters/console-reporter.d.ts.map +1 -0
  17. package/dist/reporters/console-reporter.js +67 -0
  18. package/dist/reporters/console-reporter.js.map +1 -0
  19. package/dist/reporters/json-reporter.d.ts +5 -0
  20. package/dist/reporters/json-reporter.d.ts.map +1 -0
  21. package/dist/reporters/json-reporter.js +13 -0
  22. package/dist/reporters/json-reporter.js.map +1 -0
  23. package/dist/reporters/markdown-reporter.d.ts +5 -0
  24. package/dist/reporters/markdown-reporter.d.ts.map +1 -0
  25. package/dist/reporters/markdown-reporter.js +66 -0
  26. package/dist/reporters/markdown-reporter.js.map +1 -0
  27. package/dist/rules/duplicate-labels.d.ts +25 -0
  28. package/dist/rules/duplicate-labels.d.ts.map +1 -0
  29. package/dist/rules/duplicate-labels.js +107 -0
  30. package/dist/rules/duplicate-labels.js.map +1 -0
  31. package/dist/rules/index.d.ts +13 -0
  32. package/dist/rules/index.d.ts.map +1 -0
  33. package/dist/rules/index.js +38 -0
  34. package/dist/rules/index.js.map +1 -0
  35. package/dist/rules/missing-hint.d.ts +14 -0
  36. package/dist/rules/missing-hint.d.ts.map +1 -0
  37. package/dist/rules/missing-hint.js +94 -0
  38. package/dist/rules/missing-hint.js.map +1 -0
  39. package/dist/rules/missing-label.d.ts +10 -0
  40. package/dist/rules/missing-label.d.ts.map +1 -0
  41. package/dist/rules/missing-label.js +75 -0
  42. package/dist/rules/missing-label.js.map +1 -0
  43. package/dist/rules/missing-role.d.ts +10 -0
  44. package/dist/rules/missing-role.d.ts.map +1 -0
  45. package/dist/rules/missing-role.js +82 -0
  46. package/dist/rules/missing-role.js.map +1 -0
  47. package/dist/rules/small-touch-target.d.ts +10 -0
  48. package/dist/rules/small-touch-target.d.ts.map +1 -0
  49. package/dist/rules/small-touch-target.js +90 -0
  50. package/dist/rules/small-touch-target.js.map +1 -0
  51. package/dist/rules/touchable-without-label.d.ts +17 -0
  52. package/dist/rules/touchable-without-label.d.ts.map +1 -0
  53. package/dist/rules/touchable-without-label.js +81 -0
  54. package/dist/rules/touchable-without-label.js.map +1 -0
  55. package/dist/scanner/config-loader.d.ts +3 -0
  56. package/dist/scanner/config-loader.d.ts.map +1 -0
  57. package/dist/scanner/config-loader.js +46 -0
  58. package/dist/scanner/config-loader.js.map +1 -0
  59. package/dist/scanner/file-scanner.d.ts +6 -0
  60. package/dist/scanner/file-scanner.d.ts.map +1 -0
  61. package/dist/scanner/file-scanner.js +82 -0
  62. package/dist/scanner/file-scanner.js.map +1 -0
  63. package/dist/scanner/index.d.ts +12 -0
  64. package/dist/scanner/index.d.ts.map +1 -0
  65. package/dist/scanner/index.js +64 -0
  66. package/dist/scanner/index.js.map +1 -0
  67. package/dist/scanner/scorer.d.ts +26 -0
  68. package/dist/scanner/scorer.d.ts.map +1 -0
  69. package/dist/scanner/scorer.js +65 -0
  70. package/dist/scanner/scorer.js.map +1 -0
  71. package/dist/types/index.d.ts +88 -0
  72. package/dist/types/index.d.ts.map +1 -0
  73. package/dist/types/index.js +13 -0
  74. package/dist/types/index.js.map +1 -0
  75. package/dist/utils/ast.d.ts +24 -0
  76. package/dist/utils/ast.d.ts.map +1 -0
  77. package/dist/utils/ast.js +170 -0
  78. package/dist/utils/ast.js.map +1 -0
  79. package/package.json +67 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,501 @@
1
+ # ♿ react-native-accessibility-scanner
2
+
3
+ > **Accessibility auditing for React Native apps.**
4
+
5
+ [![npm version](https://badge.fury.io/js/react-native-accessibility-scanner.svg)](https://www.npmjs.com/package/react-native-accessibility-scanner)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D16-brightgreen)](https://nodejs.org)
8
+
9
+ A single tool that scans your React Native codebase for accessibility issues and generates actionable reports — before your users encounter them.
10
+
11
+ ```
12
+ npx react-native-accessibility-scan ./src
13
+ ```
14
+
15
+ ```
16
+ ═══════════════════════════════════════════════════════
17
+ React Native Accessibility Scanner
18
+ ═══════════════════════════════════════════════════════
19
+
20
+ 📊 Summary
21
+ Scanned : 12 files
22
+ Issues : 7 total
23
+ ■ HIGH 2 ■ MEDIUM 3 ■ LOW 2
24
+
25
+ HIGH 2 issues
26
+ ────────────────────────────────────────────────────────────
27
+
28
+ Missing accessibilityLabel
29
+ src/screens/HomeScreen.tsx:43:5
30
+ <TouchableOpacity> is interactive but has no accessibilityLabel
31
+ Fix: Add accessibilityLabel="Describe the action"
32
+
33
+ ♿ Accessibility Readiness Score
34
+
35
+ Overall 🟡 72/100 ████████████████░░░░
36
+ ─────────────────────────────────────────────────
37
+ Labels 🔴 40/100
38
+ Roles 🟡 75/100
39
+ Touch Targets 🟢 100/100
40
+ Hints 🟢 94/100
41
+ ```
42
+
43
+ ---
44
+
45
+ ## Why This Exists
46
+
47
+ React Native has no single dedicated accessibility linting tool. Existing solutions are either:
48
+
49
+ - **Web-focused** (not aware of RN-specific components like `Pressable`, `FlatList`, `Modal`)
50
+ - **Runtime-only** (can't catch issues at build time)
51
+ - **Fragmented** (separate tools for linting, reporting, CI)
52
+
53
+ <!-- This package gives you one unified tool with a CLI, ESLint plugin, GitHub Action, and programmatic API. -->
54
+
55
+ ---
56
+
57
+ ## Table of Contents
58
+
59
+ - [Installation](#installation)
60
+ - [Quick Start](#quick-start)
61
+ - [CLI Reference](#cli-reference)
62
+ - [Rules](#rules)
63
+ - [Accessibility Score](#accessibility-score)
64
+ <!-- - [ESLint Plugin](#eslint-plugin) -->
65
+ <!-- - [GitHub Action](#github-action) -->
66
+ <!-- - [Programmatic API](#programmatic-api) -->
67
+ <!-- - [Config File](#config-file) -->
68
+ <!-- - [Writing Custom Rules](#writing-custom-rules) -->
69
+ - [Roadmap](#roadmap)
70
+
71
+ ---
72
+
73
+ ## Installation
74
+
75
+ ```bash
76
+ # npm
77
+ npm install --save-dev react-native-accessibility-scanner
78
+
79
+ # yarn
80
+ yarn add --dev react-native-accessibility-scanner
81
+
82
+ # No install needed for one-off scans:
83
+ npx react-native-accessibility-scan ./src
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Quick Start
89
+
90
+ ```bash
91
+ # Scan your src directory
92
+ npx react-native-accessibility-scan ./src
93
+
94
+ # Fail CI on high severity issues
95
+ npx react-native-accessibility-scan ./src --fail-on-high
96
+
97
+ # Output JSON report
98
+ npx react-native-accessibility-scan ./src --json > report.json
99
+
100
+ # Save Markdown report
101
+ npx react-native-accessibility-scan ./src --output accessibility-report.md
102
+ ```
103
+
104
+ ---
105
+
106
+ ## CLI Reference
107
+
108
+ ```
109
+ Usage: react-native-accessibility-scan [path] [options]
110
+
111
+ Arguments:
112
+ path Path to scan (default: ./src)
113
+
114
+ Options:
115
+ --json Output results as JSON to stdout
116
+ --markdown Output results as Markdown to stdout
117
+ --output <file> Write report to file (.json or .md)
118
+ --fail-on-high Exit 1 if HIGH severity issues found
119
+ --fail-on-medium Exit 1 if MEDIUM or higher issues found
120
+ --ignore <patterns...> Glob patterns to ignore
121
+ --disable-rules <ids> Rule IDs to disable
122
+ --min-width <n> Minimum touch target width (default: 44)
123
+ --min-height <n> Minimum touch target height (default: 44)
124
+ --score Show accessibility score breakdown
125
+ -V, --version Output version number
126
+ -h, --help Display help
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Rules
132
+
133
+ | Rule ID | Severity | Description |
134
+ | ------------------------- | --------- | ----------------------------------------------------------------- |
135
+ | `missing-label` | 🔴 HIGH | Interactive element missing `accessibilityLabel` |
136
+ | `touchable-without-label` | 🔴 HIGH | Touchable with no label and no `Text` child |
137
+ | `missing-role` | 🟡 MEDIUM | Interactive element missing `accessibilityRole` |
138
+ | `small-touch-target` | 🟡 MEDIUM | Touch target below 44×44pt recommendation |
139
+ | `missing-hint` | 🔵 LOW | Critical action (delete/checkout/pay) missing `accessibilityHint` |
140
+ | `duplicate-labels` | 🔵 LOW | Multiple elements share the same `accessibilityLabel` |
141
+
142
+ ### Rule Details
143
+
144
+ #### `missing-label` 🔴
145
+
146
+ Detects interactive elements (`TouchableOpacity`, `Pressable`, `Button`, etc.) that have no `accessibilityLabel` and no `Text` child. Screen readers will announce these as "button" with no context.
147
+
148
+ ```tsx
149
+ // ❌ Bad
150
+ <TouchableOpacity onPress={handleSearch} />
151
+
152
+ // ✅ Good
153
+ <TouchableOpacity
154
+ accessibilityLabel="Search"
155
+ onPress={handleSearch}
156
+ />
157
+
158
+ // ✅ Also good — Text child acts as label
159
+ <TouchableOpacity onPress={handleSearch}>
160
+ <Text>Search</Text>
161
+ </TouchableOpacity>
162
+ ```
163
+
164
+ #### `missing-role` 🟡
165
+
166
+ Interactive elements without `accessibilityRole` may be announced incorrectly by VoiceOver/TalkBack.
167
+
168
+ ```tsx
169
+ // ❌ Bad
170
+ <TouchableOpacity accessibilityLabel="Search" onPress={...} />
171
+
172
+ // ✅ Good
173
+ <TouchableOpacity
174
+ accessibilityLabel="Search"
175
+ accessibilityRole="button"
176
+ onPress={...}
177
+ />
178
+ ```
179
+
180
+ #### `small-touch-target` 🟡
181
+
182
+ Targets smaller than 44×44pt (iOS) or 48×48dp (Android) are hard to activate for users with motor impairments.
183
+
184
+ ```tsx
185
+ // ❌ Bad
186
+ <TouchableOpacity style={{ width: 20, height: 20 }} />
187
+
188
+ // ✅ Good
189
+ <TouchableOpacity style={{ width: 44, height: 44 }} />
190
+ // Or use padding:
191
+ <TouchableOpacity style={{ padding: 12 }} />
192
+ ```
193
+
194
+ #### `missing-hint` 🔵
195
+
196
+ Critical actions (containing words like "delete", "checkout", "purchase", "pay") should explain what will happen.
197
+
198
+ ```tsx
199
+ // ❌ Bad — user doesn't know what "Delete" will do
200
+ <TouchableOpacity accessibilityLabel="Delete account" />
201
+
202
+ // ✅ Good
203
+ <TouchableOpacity
204
+ accessibilityLabel="Delete account"
205
+ accessibilityHint="Permanently removes your account and all data"
206
+ />
207
+ ```
208
+
209
+ #### `duplicate-labels` 🔵
210
+
211
+ When two interactive elements on the same screen share a label, screen reader users cannot distinguish them.
212
+
213
+ ```tsx
214
+ // ❌ Bad — which "Edit" is which?
215
+ <TouchableOpacity accessibilityLabel="Edit" />
216
+ <TouchableOpacity accessibilityLabel="Edit" />
217
+
218
+ // ✅ Good
219
+ <TouchableOpacity accessibilityLabel="Edit profile" />
220
+ <TouchableOpacity accessibilityLabel="Edit address" />
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Accessibility Score
226
+
227
+ The scanner computes an **Accessibility Readiness Score** — a 0–100 rating for each file and overall.
228
+
229
+ ```bash
230
+ npx react-native-accessibility-scan ./src --score
231
+ ```
232
+
233
+ ```
234
+ ♿ Accessibility Readiness Score
235
+
236
+ Overall 🟡 72/100 ████████████████░░░░
237
+ ─────────────────────────────────────────────────
238
+ Labels 🔴 40/100
239
+ Roles 🟡 75/100
240
+ Touch Targets 🟢 100/100
241
+ Hints 🟢 94/100
242
+
243
+ 📱 Screen Scores
244
+
245
+ 🔴 CheckoutScreen.tsx 42/100 (5 issues)
246
+ 🟡 HomeScreen.tsx 68/100 (3 issues)
247
+ 🟢 ProfileScreen.tsx 96/100 (1 issue)
248
+ 🟢 SettingsScreen.tsx 100/100 (0 issues)
249
+ ```
250
+
251
+ **Scoring model:**
252
+
253
+ - HIGH issue: −20 points per issue
254
+ - MEDIUM issue: −8 points per issue
255
+ - LOW issue: −3 points per issue
256
+ - Score is clamped to [0, 100]
257
+
258
+ Use this in CI to enforce a minimum score:
259
+
260
+ ```ts
261
+ const report = await AccessibilityScanner.scan({ path: "./src" });
262
+ const score = computeScore(report);
263
+
264
+ if (score.overall < 80) {
265
+ console.error(`Score ${score.overall} is below required 80`);
266
+ process.exit(1);
267
+ }
268
+ ```
269
+
270
+ ---
271
+
272
+ <!-- ## ESLint Plugin
273
+
274
+ Get inline warnings while you write code. -->
275
+
276
+ ### Setup
277
+
278
+ ```bash
279
+ npm install --save-dev react-native-accessibility-scanner
280
+ ```
281
+
282
+ ```js
283
+ // .eslintrc.js
284
+ module.exports = {
285
+ plugins: ["react-native-accessibility-scanner"],
286
+ rules: {
287
+ "react-native-accessibility-scanner/missing-label": "error",
288
+ "react-native-accessibility-scanner/missing-role": "warn",
289
+ "react-native-accessibility-scanner/small-touch-target": "warn",
290
+ },
291
+ };
292
+ ```
293
+
294
+ Or use the recommended preset:
295
+
296
+ ```js
297
+ // .eslintrc.js
298
+ module.exports = {
299
+ extends: ["plugin:react-native-accessibility-scanner/recommended"],
300
+ };
301
+ ```
302
+
303
+ <!-- ### Import path
304
+
305
+ ```js
306
+ // The ESLint plugin is a separate export
307
+ const plugin = require("react-native-accessibility-scanner/eslint");
308
+ ``` -->
309
+
310
+ ---
311
+
312
+ <!-- ## GitHub Action
313
+
314
+ Add to `.github/workflows/accessibility.yml` (copy from `github-action/accessibility.yml` in this repo):
315
+
316
+ ```yaml
317
+ name: Accessibility Scan
318
+
319
+ on:
320
+ push:
321
+ branches: [main]
322
+ pull_request:
323
+ branches: [main]
324
+
325
+ jobs:
326
+ accessibility:
327
+ runs-on: ubuntu-latest
328
+ steps:
329
+ - uses: actions/checkout@v4
330
+ - uses: actions/setup-node@v4
331
+ with:
332
+ node-version: "20"
333
+
334
+ - run: npm ci
335
+
336
+ - name: Run Accessibility Scanner
337
+ run: npx react-native-accessibility-scan ./src --fail-on-high --output accessibility-report.json
338
+
339
+ - name: Upload Report
340
+ if: always()
341
+ uses: actions/upload-artifact@v4
342
+ with:
343
+ name: accessibility-report
344
+ path: accessibility-report.json
345
+ ```
346
+
347
+ The Action will:
348
+
349
+ - Run on every push and pull request
350
+ - Post a Markdown summary as a PR comment
351
+ - Upload the JSON report as a build artifact
352
+ - Fail the build if HIGH severity issues are found
353
+
354
+ --- -->
355
+
356
+ <!-- ## Programmatic API
357
+
358
+ ```ts
359
+ import {
360
+ AccessibilityScanner,
361
+ computeScore,
362
+ ConsoleReporter,
363
+ } from "react-native-accessibility-scanner";
364
+
365
+ const report = await AccessibilityScanner.scan({
366
+ path: "./src",
367
+ ignore: ["**/*.stories.tsx"],
368
+ failOnHigh: false,
369
+ touchTarget: { minWidth: 44, minHeight: 44 },
370
+ });
371
+
372
+ // Print to console
373
+ new ConsoleReporter().report(report);
374
+
375
+ // Get score
376
+ const score = computeScore(report);
377
+ console.log(score.overall); // 72
378
+
379
+ // Access issues directly
380
+ report.issues.forEach((issue) => {
381
+ console.log(
382
+ `${issue.severity}: ${issue.message} (${issue.file}:${issue.line})`,
383
+ );
384
+ });
385
+ ```
386
+
387
+ ### `ScanOptions`
388
+
389
+ ```ts
390
+ interface ScanOptions {
391
+ path?: string | string[]; // default: "./src"
392
+ ignore?: string[]; // glob patterns
393
+ failOnHigh?: boolean; // default: false
394
+ failOnMedium?: boolean; // default: false
395
+ touchTarget?: {
396
+ minWidth?: number; // default: 44
397
+ minHeight?: number; // default: 44
398
+ };
399
+ disabledRules?: string[]; // rule IDs to skip
400
+ }
401
+ ```
402
+
403
+ --- -->
404
+
405
+ <!-- ## Config File
406
+
407
+ Create `accessibility-scanner.config.js` in your project root:
408
+
409
+ ```js
410
+ module.exports = {
411
+ path: "./src",
412
+ ignore: ["**/*.stories.tsx", "**/*.test.tsx"],
413
+ failOnHigh: true,
414
+ failOnMedium: false,
415
+ touchTarget: {
416
+ minWidth: 44,
417
+ minHeight: 44,
418
+ },
419
+ disabledRules: [],
420
+ };
421
+ ```
422
+
423
+ The scanner automatically detects this file. You can also use:
424
+
425
+ - `.accessibility-scannerrc`
426
+ - `.accessibility-scannerrc.json`
427
+ - `"accessibility-scanner"` key in `package.json`
428
+
429
+ --- -->
430
+
431
+ <!-- ## Writing Custom Rules
432
+
433
+ ```ts
434
+ import { registerRule } from "react-native-accessibility-scanner";
435
+ import type {
436
+ AccessibilityRule,
437
+ AccessibilityIssue,
438
+ RuleContext,
439
+ } from "react-native-accessibility-scanner";
440
+ import * as t from "@babel/types";
441
+
442
+ class NoHideDescendantsRule implements AccessibilityRule {
443
+ id = "no-hide-descendants";
444
+ name = "No importantForAccessibility no-hide-descendants";
445
+ description = "Avoid hiding subtrees from screen readers.";
446
+ severity = "medium" as const;
447
+
448
+ run(node: t.Node, context: RuleContext): AccessibilityIssue[] {
449
+ // Your AST logic here
450
+ return [];
451
+ }
452
+ }
453
+
454
+ // Register before scanning
455
+ registerRule(new NoHideDescendantsRule());
456
+
457
+ const report = await AccessibilityScanner.scan({ path: "./src" });
458
+ ```
459
+
460
+ --- -->
461
+
462
+ ## Roadmap
463
+
464
+ <!-- | Version | Feature |
465
+ |---------|---------|
466
+ | **v0.1** | ✅ CLI, 6 rules, JSON/Markdown reporters |
467
+ | **v0.2** | Touch target detection, duplicate labels, score |
468
+ | **v0.3** | ESLint plugin, config file support |
469
+ | **v0.4** | GitHub Action, PR comments |
470
+ | **v1.0** | Stable API, full docs, test coverage |
471
+ | v1.1 | Accessibility score per screen in CI |
472
+ | v1.2 | Auto-fix mode (`--fix`) |
473
+ | v1.3 | React Navigation screen title checks |
474
+ | v1.4 | Modal accessibility checks |
475
+ | v1.5 | FlatList / SectionList rules |
476
+ | v2.0 | VS Code extension |
477
+ | v3.0 | HTML dashboard |
478
+ | v4.0 | AI-powered label suggestions | -->
479
+
480
+ | Version | Feature |
481
+ | -------- | ---------------------------------------- |
482
+ | **v0.1** | ✅ CLI, 6 rules, JSON/Markdown reporters |
483
+
484
+ ---
485
+
486
+ ## Contributing
487
+
488
+ 1. Fork the repository
489
+ 2. Create a branch: `git checkout -b feat/my-rule`
490
+ 3. Make your changes
491
+ 4. Add tests in `tests/unit/`
492
+ 5. Run `npm test` to verify
493
+ 6. Submit a pull request
494
+
495
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for details.
496
+
497
+ ---
498
+
499
+ ## License
500
+
501
+ MIT © [Arthnex](https://github.com/arthnex-admin)
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const fs_1 = require("fs");
9
+ const path_1 = __importDefault(require("path"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const scanner_1 = require("../scanner");
12
+ const console_reporter_1 = require("../reporters/console-reporter");
13
+ const json_reporter_1 = require("../reporters/json-reporter");
14
+ const markdown_reporter_1 = require("../reporters/markdown-reporter");
15
+ const scorer_1 = require("../scanner/scorer");
16
+ const program = new commander_1.Command();
17
+ program
18
+ .name("react-native-accessibility-scan")
19
+ .description("Accessibility auditing for React Native apps.")
20
+ .version("0.1.0")
21
+ .argument("[path]", "Path to scan (default: ./src)", "./src")
22
+ .option("--json", "Output results as JSON to stdout")
23
+ .option("--markdown", "Output results as Markdown to stdout")
24
+ .option("--output <file>", "Write report to a file (auto-detects format from extension)")
25
+ .option("--fail-on-high", "Exit with code 1 if any HIGH severity issues are found")
26
+ .option("--fail-on-medium", "Exit with code 1 if any MEDIUM or HIGH issues are found")
27
+ .option("--ignore <patterns...>", "Glob patterns to ignore (space-separated)")
28
+ .option("--disable-rules <rules...>", "Rule IDs to disable")
29
+ .option("--min-width <n>", "Minimum touch target width (default: 44)", "44")
30
+ .option("--min-height <n>", "Minimum touch target height (default: 44)", "44")
31
+ .option("--score", "Show accessibility score breakdown")
32
+ .action(async (scanPath, opts) => {
33
+ const options = {
34
+ path: scanPath,
35
+ failOnHigh: opts.failOnHigh ?? false,
36
+ failOnMedium: opts.failOnMedium ?? false,
37
+ ignore: opts.ignore,
38
+ disabledRules: opts.disableRules,
39
+ touchTarget: {
40
+ minWidth: parseInt(opts.minWidth, 10),
41
+ minHeight: parseInt(opts.minHeight, 10),
42
+ },
43
+ };
44
+ let report;
45
+ try {
46
+ report = await scanner_1.AccessibilityScanner.scan(options);
47
+ }
48
+ catch (err) {
49
+ const message = err instanceof Error ? err.message : String(err);
50
+ console.error(chalk_1.default.red(`\n✖ Scanner error: ${message}\n`));
51
+ process.exit(2);
52
+ }
53
+ // ── Output format ──────────────────────────────────────────────────────────
54
+ if (opts.json) {
55
+ const output = new json_reporter_1.JsonReporter().report(report);
56
+ console.log(output);
57
+ }
58
+ else if (opts.markdown) {
59
+ const output = new markdown_reporter_1.MarkdownReporter().report(report);
60
+ console.log(output);
61
+ }
62
+ else {
63
+ new console_reporter_1.ConsoleReporter().report(report);
64
+ }
65
+ // ── Score display ──────────────────────────────────────────────────────────
66
+ if (opts.score || (!opts.json && !opts.markdown)) {
67
+ const score = (0, scorer_1.computeScore)(report);
68
+ if (!opts.json && !opts.markdown) {
69
+ printScore(score);
70
+ }
71
+ }
72
+ // ── Write to file ──────────────────────────────────────────────────────────
73
+ if (opts.output) {
74
+ const outPath = path_1.default.resolve(opts.output);
75
+ const ext = path_1.default.extname(outPath).toLowerCase();
76
+ let content;
77
+ if (ext === ".json") {
78
+ content = new json_reporter_1.JsonReporter().report(report);
79
+ }
80
+ else if (ext === ".md") {
81
+ content = new markdown_reporter_1.MarkdownReporter().report(report);
82
+ }
83
+ else {
84
+ content = new json_reporter_1.JsonReporter().report(report);
85
+ }
86
+ (0, fs_1.writeFileSync)(outPath, content, "utf-8");
87
+ console.log(chalk_1.default.dim(`\n📄 Report saved to ${outPath}`));
88
+ }
89
+ // ── Exit code ──────────────────────────────────────────────────────────────
90
+ const { summary } = report;
91
+ if (options.failOnHigh && summary.highIssues > 0) {
92
+ process.exit(1);
93
+ }
94
+ if (options.failOnMedium && (summary.highIssues > 0 || summary.mediumIssues > 0)) {
95
+ process.exit(1);
96
+ }
97
+ });
98
+ function printScore(score) {
99
+ console.log(chalk_1.default.bold("\n♿ Accessibility Readiness Score\n"));
100
+ const bar = makeBar(score.overall);
101
+ console.log(` Overall ${(0, scorer_1.scoreEmoji)(score.overall)} ${chalk_1.default.bold(score.overall)}/100 ${bar}`);
102
+ console.log(chalk_1.default.dim(" ─────────────────────────────────────────────────"));
103
+ console.log(` Labels ${(0, scorer_1.scoreEmoji)(score.breakdown.labels)} ${score.breakdown.labels}/100`);
104
+ console.log(` Roles ${(0, scorer_1.scoreEmoji)(score.breakdown.roles)} ${score.breakdown.roles}/100`);
105
+ console.log(` Touch Targets ${(0, scorer_1.scoreEmoji)(score.breakdown.touchTargets)} ${score.breakdown.touchTargets}/100`);
106
+ console.log(` Hints ${(0, scorer_1.scoreEmoji)(score.breakdown.hints)} ${score.breakdown.hints}/100`);
107
+ if (score.screens.length > 1) {
108
+ console.log(chalk_1.default.bold("\n📱 Screen Scores\n"));
109
+ const sorted = [...score.screens].sort((a, b) => a.score - b.score);
110
+ for (const s of sorted) {
111
+ const icon = (0, scorer_1.scoreEmoji)(s.score);
112
+ const name = s.displayName.padEnd(32, " ");
113
+ console.log(` ${icon} ${chalk_1.default.white(name)} ${chalk_1.default.bold(s.score)}/100 (${s.issueCount} issues)`);
114
+ }
115
+ }
116
+ console.log();
117
+ }
118
+ function makeBar(score) {
119
+ const filled = Math.round(score / 5);
120
+ const empty = 20 - filled;
121
+ const bar = "█".repeat(filled) + "░".repeat(empty);
122
+ if (score >= 90)
123
+ return chalk_1.default.green(bar);
124
+ if (score >= 70)
125
+ return chalk_1.default.yellow(bar);
126
+ if (score >= 50)
127
+ return chalk_1.default.hex("#FFA500")(bar);
128
+ return chalk_1.default.red(bar);
129
+ }
130
+ program.parse(process.argv);
131
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}