@surea11y/core 1.2.0 → 1.3.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 (157) hide show
  1. package/CHANGELOG.md +37 -7
  2. package/LICENSE +373 -21
  3. package/README.md +67 -1
  4. package/bin/core.js +144 -19
  5. package/docs/API_STABILITY.md +1 -1
  6. package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
  7. package/docs/CI_INTEGRATIONS.md +103 -0
  8. package/docs/CLI.md +54 -1
  9. package/docs/ENGINE_OPTIONS.md +2 -0
  10. package/docs/INTEGRATION.md +18 -0
  11. package/docs/OUTPUT_SCHEMA.md +1 -1
  12. package/docs/RULE_CATALOG.md +1 -1
  13. package/docs/SARIF.md +59 -0
  14. package/package.json +15 -4
  15. package/src/baseline.js +0 -0
  16. package/src/catalogs/composites.wcag.js +414 -450
  17. package/src/checks/automatic/area-alt-present.js +59 -25
  18. package/src/checks/automatic/aria-allowed-attr.js +193 -33
  19. package/src/checks/automatic/aria-allowed-role.js +21 -7
  20. package/src/checks/automatic/aria-braille-equivalent.js +32 -10
  21. package/src/checks/automatic/aria-conditional-attr.js +24 -7
  22. package/src/checks/automatic/aria-deprecated-role.js +22 -8
  23. package/src/checks/automatic/aria-hidden-body.js +42 -19
  24. package/src/checks/automatic/aria-hidden-focus.js +408 -53
  25. package/src/checks/automatic/aria-prohibited-attr.js +296 -22
  26. package/src/checks/automatic/aria-prohibited-children.js +55 -16
  27. package/src/checks/automatic/aria-required-attr.js +23 -8
  28. package/src/checks/automatic/aria-required-children.js +37 -14
  29. package/src/checks/automatic/aria-required-parent.js +48 -14
  30. package/src/checks/automatic/aria-role-name-present.js +47 -21
  31. package/src/checks/automatic/aria-roles-valid.js +22 -12
  32. package/src/checks/automatic/aria-valid-attr-value.js +28 -7
  33. package/src/checks/automatic/aria-valid-attr.js +17 -5
  34. package/src/checks/automatic/autocomplete-valid.js +74 -16
  35. package/src/checks/automatic/avoid-inline-spacing.js +20 -6
  36. package/src/checks/automatic/binary-control-name-present.js +60 -50
  37. package/src/checks/automatic/button-name-present.js +48 -18
  38. package/src/checks/automatic/bypass-blocks-present.js +42 -25
  39. package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
  40. package/src/checks/automatic/combobox-name-present.js +38 -45
  41. package/src/checks/automatic/contrast-computable.js +361 -341
  42. package/src/checks/automatic/contrast-enhanced.js +487 -466
  43. package/src/checks/automatic/contrast-minimum.js +486 -465
  44. package/src/checks/automatic/css-orientation-lock.js +30 -8
  45. package/src/checks/automatic/definition-list-children-valid.js +40 -19
  46. package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
  47. package/src/checks/automatic/dialog-name-present.js +37 -75
  48. package/src/checks/automatic/dlitem-parent-valid.js +23 -8
  49. package/src/checks/automatic/duplicate-id-aria.js +24 -6
  50. package/src/checks/automatic/embed-text-alternative-present.js +86 -35
  51. package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
  52. package/src/checks/automatic/form-control-single-label.js +47 -10
  53. package/src/checks/automatic/html-xml-lang-mismatch.js +34 -18
  54. package/src/checks/automatic/iframe-focusable-content.js +26 -11
  55. package/src/checks/automatic/iframe-name-present.js +31 -9
  56. package/src/checks/automatic/iframe-title-unique.js +29 -8
  57. package/src/checks/automatic/img-alt-present.js +47 -43
  58. package/src/checks/automatic/input-image-alt-present.js +143 -112
  59. package/src/checks/automatic/label-in-name.js +50 -22
  60. package/src/checks/automatic/language-page-present.js +109 -109
  61. package/src/checks/automatic/link-in-text-block.js +59 -19
  62. package/src/checks/automatic/link-name-present.js +45 -14
  63. package/src/checks/automatic/list-children-valid.js +26 -9
  64. package/src/checks/automatic/listbox-name-present.js +39 -19
  65. package/src/checks/automatic/listitem-parent-valid.js +18 -6
  66. package/src/checks/automatic/menuitem-name-present.js +39 -61
  67. package/src/checks/automatic/meta-refresh-no-exceptions.js +28 -7
  68. package/src/checks/automatic/meta-refresh-timing-absent.js +20 -6
  69. package/src/checks/automatic/meta-viewport-zoom-enabled.js +24 -7
  70. package/src/checks/automatic/meter-name-present.js +36 -33
  71. package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
  72. package/src/checks/automatic/object-text-alternative-present.js +91 -39
  73. package/src/checks/automatic/option-name-present.js +38 -21
  74. package/src/checks/automatic/page-title-present.js +17 -6
  75. package/src/checks/automatic/progressbar-name-present.js +41 -34
  76. package/src/checks/automatic/role-img-alt-present.js +209 -157
  77. package/src/checks/automatic/searchbox-name-present.js +39 -19
  78. package/src/checks/automatic/server-side-image-map-absent.js +23 -8
  79. package/src/checks/automatic/slider-name-present.js +40 -47
  80. package/src/checks/automatic/spinbutton-name-present.js +39 -19
  81. package/src/checks/automatic/summary-name-present.js +37 -17
  82. package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
  83. package/src/checks/automatic/svg-text-alternative-present.js +246 -226
  84. package/src/checks/automatic/tab-name-present.js +37 -60
  85. package/src/checks/automatic/table-headers-attr-valid.js +24 -8
  86. package/src/checks/automatic/table-th-has-data-cells.js +22 -8
  87. package/src/checks/automatic/target-size-minimum.js +118 -48
  88. package/src/checks/automatic/td-has-header.js +29 -11
  89. package/src/checks/automatic/textbox-name-present.js +39 -19
  90. package/src/checks/automatic/tooltip-name-present.js +37 -18
  91. package/src/checks/automatic/treeitem-name-present.js +38 -21
  92. package/src/checks/automatic/valid-lang.js +20 -6
  93. package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
  94. package/src/checks/manual/accesskeys-manual.js +14 -5
  95. package/src/checks/manual/area-alt-decorative-manual.js +192 -193
  96. package/src/checks/manual/area-alt-quality-manual.js +182 -141
  97. package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
  98. package/src/checks/manual/aria-text-manual.js +14 -6
  99. package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
  100. package/src/checks/manual/css-hidden-focus.js +196 -165
  101. package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
  102. package/src/checks/manual/empty-heading-manual.js +24 -7
  103. package/src/checks/manual/empty-table-header-manual.js +17 -6
  104. package/src/checks/manual/focus-order-semantics-manual.js +45 -10
  105. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
  106. package/src/checks/manual/heading-order-manual.js +22 -7
  107. package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
  108. package/src/checks/manual/image-redundant-alt-manual.js +17 -7
  109. package/src/checks/manual/img-alt-decorative-manual.js +131 -96
  110. package/src/checks/manual/img-alt-quality-manual.js +176 -127
  111. package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
  112. package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
  113. package/src/checks/manual/label-title-only-manual.js +14 -5
  114. package/src/checks/manual/landmark-banner-is-top-level-manual.js +66 -16
  115. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +53 -16
  116. package/src/checks/manual/landmark-main-is-top-level-manual.js +35 -10
  117. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +29 -10
  118. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +29 -10
  119. package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
  120. package/src/checks/manual/landmark-one-main-manual.js +26 -20
  121. package/src/checks/manual/landmark-unique-manual.js +41 -15
  122. package/src/checks/manual/link-name-quality-manual.js +43 -12
  123. package/src/checks/manual/media-transcript-present-manual.js +35 -22
  124. package/src/checks/manual/meta-viewport-large-manual.js +16 -5
  125. package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
  126. package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
  127. package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
  128. package/src/checks/manual/p-as-heading-manual.js +22 -7
  129. package/src/checks/manual/page-has-heading-one-manual.js +31 -22
  130. package/src/checks/manual/page-title-patterns-manual.js +78 -50
  131. package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
  132. package/src/checks/manual/region-manual.js +241 -48
  133. package/src/checks/manual/scope-attr-valid-manual.js +10 -3
  134. package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
  135. package/src/checks/manual/skip-link-manual.js +35 -12
  136. package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
  137. package/src/checks/manual/tabindex-manual.js +10 -3
  138. package/src/checks/manual/table-duplicate-name-manual.js +17 -7
  139. package/src/checks/manual/table-fake-caption-manual.js +24 -7
  140. package/src/checks/manual/video-caption-manual.js +15 -4
  141. package/src/checks/manual-review.js +56 -12
  142. package/src/core/aria-helpers.js +1127 -886
  143. package/src/core/contrast-helpers.js +1217 -1062
  144. package/src/core/dom-helpers.js +4175 -3917
  145. package/src/core/dom-runner.js +720 -604
  146. package/src/core/frame-messaging.js +189 -138
  147. package/src/core/frame-scan.js +94 -82
  148. package/src/core/rollup-composites.js +94 -102
  149. package/src/core/rule-meta.js +58 -41
  150. package/src/core.js +36552 -29111
  151. package/src/i18n/en.js +1194 -889
  152. package/src/i18n/fr.js +1136 -795
  153. package/src/policy/contracts.js +13 -13
  154. package/src/policy/resolvePolicy.js +48 -44
  155. package/src/report.js +63 -43
  156. package/src/sarif.js +175 -0
  157. package/surea11y.browser.js +36042 -0
package/docs/CLI.md CHANGED
@@ -21,9 +21,11 @@ The CLI reads **static HTML only** — a local file, or the raw response of an H
21
21
  | `--exclude-rules <ids>` | Comma-separated rule IDs — never run these. |
22
22
  | `--tags <tags>` | Comma-separated tags — e.g. `--tags wcag2a,wcag2aa` to target a conformance level (see [`WCAG_CONFORMANCE.md`](./WCAG_CONFORMANCE.md)). |
23
23
  | `--context <selector>` | Scope the scan to one CSS-selected subtree. |
24
+ | `--custom-rules <path>` | Load runtime custom rules from a local JS file. Repeatable. See [Custom rules](#custom-rules) below. |
24
25
  | `--write-baseline <path>` | Write every current `fail` occurrence to `<path>`; never fails the build. See [`BASELINE.md`](./BASELINE.md). |
25
26
  | `--baseline <path>` | Gate only on occurrences not already recorded in `<path>`. See [`BASELINE.md`](./BASELINE.md). |
26
27
  | `--html <path>` | Write a self-contained, browsable HTML report to `<path>`. See [`REPORT.md`](./REPORT.md). |
28
+ | `--sarif <path>` | Write a SARIF 2.1.0 report to `<path>` (e.g. for GitHub Code Scanning). See [`SARIF.md`](./SARIF.md). |
27
29
  | `-h`, `--help` | Show usage. |
28
30
  | `-v`, `--version` | Show the installed version. |
29
31
 
@@ -52,6 +54,47 @@ surea11y scan ./dist/index.html --baseline baseline.json # in CI, from t
52
54
 
53
55
  See [`BASELINE.md`](./BASELINE.md) for the matching semantics, file format, and known limitations.
54
56
 
57
+ ## Custom rules
58
+
59
+ For an org-specific check that isn't (and shouldn't be) one of the built-in rules — an internal design-system convention, a company style-guide requirement — `--custom-rules <path>` registers your own rule(s) for that one scan, on top of every built-in rule:
60
+
61
+ ```sh
62
+ surea11y scan ./dist/index.html --custom-rules ./a11y-rules.js
63
+ ```
64
+
65
+ `a11y-rules.js` exports either a single rule descriptor or an array of them, using the same shape as a built-in rule module:
66
+
67
+ ```js
68
+ // a11y-rules.js
69
+ module.exports = [
70
+ {
71
+ id: 'org-no-inline-onclick',
72
+ meta: { title: 'No inline onclick handlers', defaultSeverity: 'moderate' },
73
+ runInPage(ctx) {
74
+ const els = ctx.helpers.queryAll('[onclick]');
75
+ const occurrences = els.map((el) => ({
76
+ selector: ctx.helpers.buildSelector(el),
77
+ html: el.outerHTML,
78
+ summary: 'Inline onclick handler found.',
79
+ hint: 'Move event handling into an external script.'
80
+ }));
81
+ return {
82
+ ruleId: ctx.rule.ruleId,
83
+ outcome: occurrences.length ? 'fail' : 'pass',
84
+ occurrences
85
+ };
86
+ }
87
+ }
88
+ ];
89
+ ```
90
+
91
+ - `<path>` is a **local file**, `require()`d directly by the CLI — never a URL. (Unlike the scan target, which does accept a URL: fetching and executing remote code as a rule would be a very different, much riskier trust model than running a file you already have on disk.)
92
+ - Because the CLI runs your rule in the same Node process as the scan, `runInPage`/`applicability` can be plain functions — no `fn.toString()` string-source workaround needed (that's only required for callers, like a browser-automation binding, whose `engineOptions` crosses a serialization boundary). See [`ENGINE_OPTIONS.md`](./ENGINE_OPTIONS.md) for the full descriptor contract (`meta` defaulting, the `ctx` shape, etc.) — it's identical here.
93
+ - Repeat the flag to load rules from more than one file: `--custom-rules ./a.js --custom-rules ./b.js`.
94
+ - A custom rule's `id` colliding with a built-in one **overrides** that built-in for the scan, surfaced via a `console.warn` and the result's top-level `overriddenBuiltinIds` array — see [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md).
95
+ - The file itself is validated at load time (must export a descriptor, or array of descriptors, each with a string `id` and a function-or-source-string `runInPage`) — a malformed export exits `2` with a clear error rather than silently scanning with one fewer rule than expected.
96
+ - Works alongside every other flag, including `--rules`/`--exclude-rules`/`--tags` (which can target your custom rule's `id` exactly like a built-in one) and `--baseline`/`--html`/`--sarif`.
97
+
55
98
  ## HTML report
56
99
 
57
100
  For a browsable view of a scan's results — hero summary, WCAG rollup grouped by conformance level, and a searchable/filterable occurrence table — rather than raw JSON or a terminal summary:
@@ -62,13 +105,23 @@ surea11y scan ./dist/index.html --html report.html
62
105
 
63
106
  Open `report.html` directly from disk; no server, no external assets. Works alongside any other output mode. See [`REPORT.md`](./REPORT.md).
64
107
 
108
+ ## SARIF report
109
+
110
+ For GitHub Code Scanning or another SARIF-consuming dashboard:
111
+
112
+ ```sh
113
+ surea11y scan ./dist/index.html --sarif results.sarif
114
+ ```
115
+
116
+ Works alongside any other output mode, and alongside `--baseline` (already-known `fail` occurrences are omitted from the SARIF output rather than re-reported). See [`SARIF.md`](./SARIF.md).
117
+
65
118
  ## In CI
66
119
 
67
120
  ```sh
68
121
  npx @surea11y/core scan ./dist/index.html || exit 1
69
122
  ```
70
123
 
71
- Or, since the exit code already reflects pass/fail, just let the command's own exit code propagate — most CI systems fail the step automatically on a non-zero exit.
124
+ Or, since the exit code already reflects pass/fail, just let the command's own exit code propagate — most CI systems fail the step automatically on a non-zero exit. See [`CI_INTEGRATIONS.md`](./CI_INTEGRATIONS.md) for ready-to-paste GitHub Actions and Bitbucket Pipelines templates, including a SARIF-upload example.
72
125
 
73
126
  ## A note on dependencies
74
127
 
@@ -224,6 +224,8 @@ See the option-by-option table above for anything not shown here, and the `custo
224
224
 
225
225
  Every shipped rule is baked into `src/core.js` at build time. `engineOptions.customRules` is the runtime escape hatch: an array of rule descriptors registered for that one call only — nothing is added to the static catalog (`getRulesCatalog()`/`getChecksCatalog()`), and nothing persists between calls. This is deliberate, not a limitation to work around: surea11y already takes fresh `engineOptions` per call with no mutable global config (unlike some other engines, which need a `configure()`/`reset()` step against a shared runtime), and custom rules follow that same per-call model.
226
226
 
227
+ Calling the library directly is one way in; the CLI also exposes this via `--custom-rules <path>` (a local file, loaded once per scan) — see [`CLI.md`](./CLI.md#custom-rules).
228
+
227
229
  A descriptor has the *same shape as an internal rule module's own export* — if you already know how to write a rule file for this engine, you already know this API:
228
230
 
229
231
  ```js
@@ -91,6 +91,24 @@ const result = await page.evaluate(wrapperFn, {
91
91
 
92
92
  This is the only pattern that gives every rule real computed layout, so it's the one to reach for if you need `target-size-minimum` or any other geometry-dependent check to actually run instead of reporting `notApplicable`.
93
93
 
94
+ ## Pattern 3 — a standalone `<script>` tag (no driver, no bundler)
95
+
96
+ Good for: a manual check against a page open in a real browser, a bookmarklet, or any other context where there's no automation driver and no build step to reach for.
97
+
98
+ `@surea11y/core` ships `surea11y.browser.js` at the package root, a bundle generated from the same rule sources as `src/core.js`. Loading it directly defines one global, `a11ycore`:
99
+
100
+ ```html
101
+ <script src="node_modules/@surea11y/core/surea11y.browser.js"></script>
102
+ <script>
103
+ const result = a11ycore.runa11yCoreInPage(location.href, null, {}, null);
104
+ console.log(result.checksResults.filter((r) => r.outcome === 'fail'));
105
+ </script>
106
+ ```
107
+
108
+ `a11ycore.runa11yCoreInPage` is the exact same function described in Pattern 2 above — the bundle exists only to solve *loading* it without `require`/a module system, not to add a separate API surface. It carries the same self-containment property (own inlined rule catalog, no free variables), which is what makes a plain `<script>` tag sufficient.
109
+
110
+ Deliberately excluded from this bundle: `runa11yCoreAcrossFrames`/`a11yCoreEnableFrameResponder`. Cross-frame scanning needs the embedded frame to load the engine and opt in too (see "Cross-frame scanning" below) — not a fit for a single dropped-in script tag. Use the npm package directly if you need it.
111
+
94
112
  ## Scoping a scan to part of the page
95
113
 
96
114
  Pass a CSS selector as the 2nd argument (`contextSelector`) to scan one subtree instead of the whole document — e.g. `runDomRulesInPage(url, '#app', {}, null)` to skip a surrounding CMS chrome you don't control. Pass an array of selectors (or a single comma-separated selector string) to scan multiple, possibly disjoint regions in one run — e.g. `runDomRulesInPage(url, ['#header', '#main'], {}, null)`. See [`ENGINE_OPTIONS.md`](./ENGINE_OPTIONS.md) for the full `contextSelector` reference and for `excludeSelectors`, the complementary "skip specific elements anywhere" option.
@@ -56,7 +56,7 @@ This is the exact shape of the object returned by `runDomRulesInPage(...)` / `ru
56
56
 
57
57
  - `topFrame` is exactly the [top-level result](#top-level-result) shape, for the frame the function was called in.
58
58
  - `frames` has one entry per direct child `<iframe>`/`<frame>` in the scanned scope. A reachable child (one that called `a11yCoreEnableFrameResponder()`) contributes its own complete `{ url, topFrame, frames }` — including *its own* nested `frames`, recursively, since a further-nested grandchild is only reachable through its immediate parent. An unreachable child (the common case for most third-party embeds — no cooperating responder, or it timed out) contributes `{ url, error }` instead, and does not abort the rest of the scan.
59
- - This is a **tree, not a flat list** — a deliberate difference from the `surea11y-playwright` binding's `.frames(true)`, which *can* flatten because Playwright's `page.frames()` already gives every frame regardless of nesting depth; a `postMessage` relay has no such global view, so nesting is expressed structurally instead.
59
+ - This is a **tree, not a flat list** — a deliberate difference from the `@surea11y/playwright` binding's `.frames(true)`, which *can* flatten because Playwright's `page.frames()` already gives every frame regardless of nesting depth; a `postMessage` relay has no such global view, so nesting is expressed structurally instead.
60
60
 
61
61
  ## A check result (`checksResults[i]`)
62
62
 
@@ -134,7 +134,7 @@ See [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md) for what `type`/`confidence`/`sever
134
134
  | `region` | Page content should be inside a landmark region | — | — | medium | minor |
135
135
  | `scope-attr-valid` | scope attribute must have a valid value | — | — | medium | minor |
136
136
  | `scrollable-region-focusable` | Scrollable regions with no focusable content should be keyboard-focusable | 2.1.1, 2.1.3 | AAA | low | moderate |
137
- | `skip-link` | Skip link must have a resolvable target | — | — | medium | minor |
137
+ | `skip-link` | Skip link must have a resolvable, usable target | — | — | medium | minor |
138
138
  | `svg-text-alternative-quality` | &lt;svg&gt; text alternative must be appropriate (manual review) | 1.1.1 | A | medium | minor |
139
139
  | `tabindex` | tabindex should not be greater than 0 | — | — | medium | minor |
140
140
  | `table-duplicate-name` | Table caption must not duplicate its summary attribute | — | — | medium | minor |
package/docs/SARIF.md ADDED
@@ -0,0 +1,59 @@
1
+ # SARIF report
2
+
3
+ `--sarif <path>` writes a [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html) log — the standard format GitHub Code Scanning (and other SARIF-consuming dashboards) expect — instead of, or alongside, `--json`'s raw engine result.
4
+
5
+ ```sh
6
+ surea11y scan ./dist/index.html --sarif results.sarif
7
+ ```
8
+
9
+ See [`CI_INTEGRATIONS.md`](./CI_INTEGRATIONS.md) for a ready-to-paste GitHub Actions workflow that runs a scan and uploads `results.sarif` to the "Security" tab.
10
+
11
+ ## Why a separate format from `--json`
12
+
13
+ `--json`'s raw result (see [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md)) is this engine's own contract, versioned and stable per [`API_STABILITY.md`](./API_STABILITY.md). SARIF is a different, externally-defined contract purpose-built for code-scanning dashboards — a `checksResults[]` entry and a SARIF `result` don't map 1:1, so this is a real conversion, not a re-serialization.
14
+
15
+ ## What becomes a SARIF result
16
+
17
+ Only `fail`/`cantTell` occurrences produce SARIF results — a `pass`/`notApplicable` check has no occurrences to report at all (same "violations only" framing as [`REPORT.md`](./REPORT.md)'s HTML report).
18
+
19
+ | Engine outcome | SARIF `level` | Meaning |
20
+ |---|---|---|
21
+ | `fail` | `error` | Deterministic, high-confidence violation — the CI-gating case. |
22
+ | `cantTell` | `warning` | Needs human review — surfaced, but shouldn't block a build on its own. |
23
+
24
+ Every rule that ran (regardless of whether it produced a result) is listed once in `runs[0].tool.driver.rules`, with `defaultConfiguration.level` set from the rule's `type`: `automatic` (fail-capable) → `error`, `manual` (capped at `cantTell`) → `warning`.
25
+
26
+ ## Field mapping
27
+
28
+ | SARIF field | Source |
29
+ |---|---|
30
+ | `results[].ruleId` | `checksResults[i].ruleId` |
31
+ | `results[].message.text` | `occurrence.summary` + `occurrence.hint` |
32
+ | `results[].locations[].physicalLocation.artifactLocation.uri` | The scanned target — see "Locations" below. |
33
+ | `results[].locations[].logicalLocations[].fullyQualifiedName` | `occurrence.selector`, when present. |
34
+ | `results[].partialFingerprints["surea11y/violation/v1"]` | The same `ruleId + reasonCode + html` identity key used by [`BASELINE.md`](./BASELINE.md) (`computeBaselineKey`) — a stable, content-based fingerprint rather than a position-based one. |
35
+ | `results[].properties.severity` / `.confidence` | `checksResults[i].severity` / `.confidence` — informational, not part of SARIF's own schema. |
36
+ | `tool.driver.rules[].properties.tags` | `accessibility`, `automatic`/`manual`, and a `wcag-<SC>` tag per `meta.normativeMappings[].requirement`. |
37
+
38
+ ## Locations
39
+
40
+ DOM-based scanning has no line/column to report, so `physicalLocation.artifactLocation.uri` is the scanned target itself, not a source-file position:
41
+
42
+ - **Local file scans**: a path relative to the current working directory (forward-slashed). If this matches a real file in your repository, GitHub Code Scanning can render the finding as an inline annotation.
43
+ - **URL scans**: the scanned URL itself. GitHub Code Scanning will still list the finding, but can't attach an inline annotation to a URL that isn't a file in the repository — this is inherent to how SARIF/Code Scanning associate findings with source, not a surea11y limitation. If you need inline annotations, scan the rendered HTML file (e.g. a build output artifact) rather than a live URL.
44
+
45
+ `occurrence.selector` is additionally carried as a `logicalLocations[].fullyQualifiedName`, so a consumer that reads logical locations still gets the "which element" signal even without a usable physical location.
46
+
47
+ ## Combining with `--baseline`
48
+
49
+ A generic SARIF consumer has no "known, don't gate on this" concept of its own — the only faithful way to honor a baseline in SARIF output is to omit already-known `fail` occurrences entirely, rather than downgrade them to `warning`:
50
+
51
+ ```sh
52
+ surea11y scan ./dist/index.html --baseline baseline.json --sarif results.sarif
53
+ ```
54
+
55
+ `cantTell` occurrences are never filtered by a baseline — the baseline mechanism only ever tracks `fail` occurrences (matching `--write-baseline`, see [`BASELINE.md`](./BASELINE.md)).
56
+
57
+ ## Combining with `--html`/`--json`
58
+
59
+ `--sarif`, `--html`, and `--json` are independent output flags — pass any combination in one run; each writes/prints its own report from the same single scan.
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@surea11y/core",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Lightweight DOM rules accessibility core with modular rules.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
7
  "surea11y": "bin/core.js"
8
8
  },
9
9
  "author": "Jorge Rumoroso",
10
- "license": "MIT",
10
+ "license": "MPL-2.0",
11
11
  "repository": {
12
12
  "type": "git",
13
13
  "url": "git+https://github.com/rumoroso/surea11y-core.git"
@@ -25,6 +25,7 @@
25
25
  "files": [
26
26
  "src",
27
27
  "bin",
28
+ "surea11y.browser.js",
28
29
  "docs/**/*.md",
29
30
  "README.md",
30
31
  "LICENSE",
@@ -36,9 +37,14 @@
36
37
  "!src/explain"
37
38
  ],
38
39
  "scripts": {
39
- "build": "node scripts/build-core.js",
40
+ "lint": "eslint .",
41
+ "lint:fix": "eslint . --fix",
42
+ "format": "prettier --write \"src/**/*.js\" \"bin/**/*.js\" \"scripts/**/*.js\" \"tests/**/*.js\" \"eslint.config.js\"",
43
+ "format:check": "prettier --check \"src/**/*.js\" \"bin/**/*.js\" \"scripts/**/*.js\" \"tests/**/*.js\" \"eslint.config.js\"",
44
+ "build": "node scripts/build-core.js && node scripts/build-browser.js",
40
45
  "pretest": "playwright install chromium",
41
46
  "test": "npm run build && node scripts/run-tests.js",
47
+ "test:coverage": "npm run build && node scripts/run-tests.js --experimental-test-coverage --test-coverage-include=src/**/*.js --test-coverage-include=bin/**/*.js",
42
48
  "test:contrast-helpers": "node tests/contrast-helpers.test.js",
43
49
  "helpers-perf-bench": "node --expose-gc scripts/dom-helpers-perf-bench.js",
44
50
  "engine-perf-bench": "node --expose-gc scripts/engine-perf-bench.js --profileRules=true --iters=25 --top=15",
@@ -56,6 +62,11 @@
56
62
  "jsdom": "^29.1.1"
57
63
  },
58
64
  "devDependencies": {
59
- "playwright": "^1.61.1"
65
+ "@eslint/js": "^10.0.1",
66
+ "eslint": "^10.8.0",
67
+ "eslint-config-prettier": "^10.1.8",
68
+ "globals": "^17.8.0",
69
+ "playwright": "^1.62.1",
70
+ "prettier": "^3.9.6"
60
71
  }
61
72
  }
package/src/baseline.js CHANGED
Binary file