@surea11y/core 1.1.2 → 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.
- package/CHANGELOG.md +48 -4
- package/LICENSE +373 -21
- package/README.md +70 -1
- package/bin/core.js +240 -11
- package/docs/API_STABILITY.md +61 -0
- package/docs/BASELINE.md +66 -0
- package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
- package/docs/CI_INTEGRATIONS.md +103 -0
- package/docs/CLI.md +80 -1
- package/docs/ENGINE_OPTIONS.md +4 -0
- package/docs/INTEGRATION.md +19 -1
- package/docs/OUTPUT_SCHEMA.md +2 -2
- package/docs/REPORT.md +33 -0
- package/docs/RULE_AUTHORING.md +31 -0
- package/docs/RULE_CATALOG.md +1 -1
- package/docs/SARIF.md +59 -0
- package/package.json +15 -4
- package/src/baseline.js +0 -0
- package/src/catalogs/composites.wcag.js +414 -450
- package/src/checks/automatic/area-alt-present.js +59 -25
- package/src/checks/automatic/aria-allowed-attr.js +193 -33
- package/src/checks/automatic/aria-allowed-role.js +21 -7
- package/src/checks/automatic/aria-braille-equivalent.js +32 -10
- package/src/checks/automatic/aria-conditional-attr.js +24 -7
- package/src/checks/automatic/aria-deprecated-role.js +22 -8
- package/src/checks/automatic/aria-hidden-body.js +50 -19
- package/src/checks/automatic/aria-hidden-focus.js +408 -53
- package/src/checks/automatic/aria-prohibited-attr.js +296 -22
- package/src/checks/automatic/aria-prohibited-children.js +125 -29
- package/src/checks/automatic/aria-required-attr.js +23 -8
- package/src/checks/automatic/aria-required-children.js +37 -14
- package/src/checks/automatic/aria-required-parent.js +48 -14
- package/src/checks/automatic/aria-role-name-present.js +47 -21
- package/src/checks/automatic/aria-roles-valid.js +22 -12
- package/src/checks/automatic/aria-valid-attr-value.js +28 -7
- package/src/checks/automatic/aria-valid-attr.js +17 -5
- package/src/checks/automatic/autocomplete-valid.js +74 -16
- package/src/checks/automatic/avoid-inline-spacing.js +20 -6
- package/src/checks/automatic/binary-control-name-present.js +60 -50
- package/src/checks/automatic/button-name-present.js +48 -18
- package/src/checks/automatic/bypass-blocks-present.js +50 -25
- package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
- package/src/checks/automatic/combobox-name-present.js +38 -45
- package/src/checks/automatic/contrast-computable.js +361 -341
- package/src/checks/automatic/contrast-enhanced.js +487 -466
- package/src/checks/automatic/contrast-minimum.js +486 -465
- package/src/checks/automatic/css-orientation-lock.js +39 -9
- package/src/checks/automatic/definition-list-children-valid.js +40 -19
- package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
- package/src/checks/automatic/dialog-name-present.js +37 -75
- package/src/checks/automatic/dlitem-parent-valid.js +23 -8
- package/src/checks/automatic/duplicate-id-aria.js +24 -6
- package/src/checks/automatic/embed-text-alternative-present.js +86 -35
- package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
- package/src/checks/automatic/form-control-single-label.js +47 -10
- package/src/checks/automatic/html-xml-lang-mismatch.js +43 -19
- package/src/checks/automatic/iframe-focusable-content.js +26 -11
- package/src/checks/automatic/iframe-name-present.js +31 -9
- package/src/checks/automatic/iframe-title-unique.js +29 -8
- package/src/checks/automatic/img-alt-present.js +47 -43
- package/src/checks/automatic/input-image-alt-present.js +143 -112
- package/src/checks/automatic/label-in-name.js +50 -22
- package/src/checks/automatic/language-page-present.js +117 -109
- package/src/checks/automatic/link-in-text-block.js +59 -19
- package/src/checks/automatic/link-name-present.js +45 -14
- package/src/checks/automatic/list-children-valid.js +26 -9
- package/src/checks/automatic/listbox-name-present.js +39 -19
- package/src/checks/automatic/listitem-parent-valid.js +18 -6
- package/src/checks/automatic/menuitem-name-present.js +39 -61
- package/src/checks/automatic/meta-refresh-no-exceptions.js +37 -8
- package/src/checks/automatic/meta-refresh-timing-absent.js +28 -6
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +32 -7
- package/src/checks/automatic/meter-name-present.js +36 -33
- package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
- package/src/checks/automatic/object-text-alternative-present.js +91 -39
- package/src/checks/automatic/option-name-present.js +38 -21
- package/src/checks/automatic/page-title-present.js +26 -7
- package/src/checks/automatic/progressbar-name-present.js +41 -34
- package/src/checks/automatic/role-img-alt-present.js +209 -157
- package/src/checks/automatic/searchbox-name-present.js +39 -19
- package/src/checks/automatic/server-side-image-map-absent.js +23 -8
- package/src/checks/automatic/slider-name-present.js +40 -47
- package/src/checks/automatic/spinbutton-name-present.js +39 -19
- package/src/checks/automatic/summary-name-present.js +37 -17
- package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
- package/src/checks/automatic/svg-text-alternative-present.js +246 -226
- package/src/checks/automatic/tab-name-present.js +37 -60
- package/src/checks/automatic/table-headers-attr-valid.js +24 -8
- package/src/checks/automatic/table-th-has-data-cells.js +22 -8
- package/src/checks/automatic/target-size-minimum.js +118 -48
- package/src/checks/automatic/td-has-header.js +29 -11
- package/src/checks/automatic/textbox-name-present.js +39 -19
- package/src/checks/automatic/tooltip-name-present.js +37 -18
- package/src/checks/automatic/treeitem-name-present.js +38 -21
- package/src/checks/automatic/valid-lang.js +20 -6
- package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
- package/src/checks/manual/accesskeys-manual.js +14 -5
- package/src/checks/manual/area-alt-decorative-manual.js +192 -193
- package/src/checks/manual/area-alt-quality-manual.js +182 -141
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
- package/src/checks/manual/aria-text-manual.js +14 -6
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
- package/src/checks/manual/css-hidden-focus.js +196 -165
- package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
- package/src/checks/manual/empty-heading-manual.js +24 -12
- package/src/checks/manual/empty-table-header-manual.js +21 -14
- package/src/checks/manual/focus-order-semantics-manual.js +45 -10
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
- package/src/checks/manual/heading-order-manual.js +22 -12
- package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
- package/src/checks/manual/image-redundant-alt-manual.js +17 -7
- package/src/checks/manual/img-alt-decorative-manual.js +131 -96
- package/src/checks/manual/img-alt-quality-manual.js +176 -127
- package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
- package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
- package/src/checks/manual/label-title-only-manual.js +14 -5
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -29
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +82 -29
- package/src/checks/manual/landmark-main-is-top-level-manual.js +64 -23
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +54 -23
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +54 -23
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
- package/src/checks/manual/landmark-one-main-manual.js +35 -21
- package/src/checks/manual/landmark-unique-manual.js +73 -45
- package/src/checks/manual/link-name-quality-manual.js +43 -12
- package/src/checks/manual/media-transcript-present-manual.js +35 -22
- package/src/checks/manual/meta-viewport-large-manual.js +25 -6
- package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
- package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
- package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
- package/src/checks/manual/p-as-heading-manual.js +22 -7
- package/src/checks/manual/page-has-heading-one-manual.js +40 -23
- package/src/checks/manual/page-title-patterns-manual.js +87 -51
- package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
- package/src/checks/manual/region-manual.js +275 -62
- package/src/checks/manual/scope-attr-valid-manual.js +11 -9
- package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
- package/src/checks/manual/skip-link-manual.js +35 -12
- package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
- package/src/checks/manual/tabindex-manual.js +11 -9
- package/src/checks/manual/table-duplicate-name-manual.js +17 -7
- package/src/checks/manual/table-fake-caption-manual.js +24 -7
- package/src/checks/manual/video-caption-manual.js +15 -4
- package/src/checks/manual-review.js +56 -12
- package/src/core/aria-helpers.js +1128 -823
- package/src/core/contrast-helpers.js +1217 -1062
- package/src/core/dom-helpers.js +4176 -3886
- package/src/core/dom-runner.js +720 -596
- package/src/core/frame-messaging.js +189 -138
- package/src/core/frame-scan.js +94 -82
- package/src/core/rollup-composites.js +94 -102
- package/src/core/rule-meta.js +71 -35
- package/src/core.js +37939 -29121
- package/src/i18n/en.js +1194 -887
- package/src/i18n/fr.js +1136 -793
- package/src/policy/contracts.js +13 -13
- package/src/policy/resolvePolicy.js +48 -44
- package/src/report.js +502 -0
- package/src/sarif.js +175 -0
- package/surea11y.browser.js +36042 -0
package/src/sarif.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Renders one scan result as a SARIF 2.1.0 log (docs/SARIF.md) for GitHub
|
|
5
|
+
* Code Scanning / other SARIF-consuming dashboards -- a different output
|
|
6
|
+
* shape from --json's raw result, purpose-built for that ecosystem (see
|
|
7
|
+
* docs/CLI.md's --sarif flag).
|
|
8
|
+
*
|
|
9
|
+
* Only fail/cantTell occurrences become SARIF results (a pass/notApplicable
|
|
10
|
+
* result has no occurrences at all -- see docs/OUTPUT_SCHEMA.md), the same
|
|
11
|
+
* "violations only" framing docs/REPORT.md's HTML report already uses.
|
|
12
|
+
* fail -> SARIF level "error" (the CI-gating case); cantTell -> "warning"
|
|
13
|
+
* (surfaced, non-blocking -- this engine's own cantTell/manual-review mental
|
|
14
|
+
* model, docs/TROUBLESHOOTING.md).
|
|
15
|
+
*
|
|
16
|
+
* If `baselineEntries` is supplied (mirrors --baseline, docs/BASELINE.md),
|
|
17
|
+
* fail occurrences already recorded there are omitted entirely rather than
|
|
18
|
+
* downgraded -- a generic SARIF consumer has no "known, don't re-gate"
|
|
19
|
+
* concept of its own, so the only faithful way to honor a baseline here is
|
|
20
|
+
* to not report the occurrence at all. cantTell occurrences are never
|
|
21
|
+
* baseline-filtered (the baseline mechanism only ever tracks fail
|
|
22
|
+
* occurrences, matching --write-baseline).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const path = require('path');
|
|
26
|
+
const { computeBaselineKey, getReasonCode } = require('./baseline.js');
|
|
27
|
+
|
|
28
|
+
const SARIF_SCHEMA_URI =
|
|
29
|
+
'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/Schemata/sarif-schema-2.1.0.json';
|
|
30
|
+
const SARIF_VERSION = '2.1.0';
|
|
31
|
+
|
|
32
|
+
function artifactUriFromResult(result) {
|
|
33
|
+
const url = result && result.url;
|
|
34
|
+
if (!url) return 'about:blank';
|
|
35
|
+
if (url.startsWith('file://')) {
|
|
36
|
+
const filePath = url.slice('file://'.length);
|
|
37
|
+
const rel = path.relative(process.cwd(), filePath).split(path.sep).join('/');
|
|
38
|
+
// A file outside the cwd (e.g. an absolute path elsewhere on disk)
|
|
39
|
+
// produces a "../"-leading relative path -- still valid as a URI, just
|
|
40
|
+
// not resolvable as a repo-relative one by a SARIF consumer like GitHub
|
|
41
|
+
// Code Scanning (see docs/SARIF.md's known limitations).
|
|
42
|
+
return rel || filePath;
|
|
43
|
+
}
|
|
44
|
+
return url;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildRemainingBaselineMap(baselineEntries) {
|
|
48
|
+
const remaining = new Map();
|
|
49
|
+
for (const entry of Array.isArray(baselineEntries) ? baselineEntries : []) {
|
|
50
|
+
if (!entry) continue;
|
|
51
|
+
const key = computeBaselineKey(
|
|
52
|
+
entry.ruleId,
|
|
53
|
+
entry.reasonCode || 'DEFAULT',
|
|
54
|
+
typeof entry.html === 'string' ? entry.html : ''
|
|
55
|
+
);
|
|
56
|
+
remaining.set(key, (remaining.get(key) || 0) + 1);
|
|
57
|
+
}
|
|
58
|
+
return remaining;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function wcagTags(check) {
|
|
62
|
+
const mappings = (check.meta && check.meta.normativeMappings) || [];
|
|
63
|
+
const tags = new Set(['accessibility', check.type === 'automatic' ? 'automatic' : 'manual']);
|
|
64
|
+
for (const m of mappings) {
|
|
65
|
+
if (m && m.requirement) tags.add(`wcag-${m.requirement}`);
|
|
66
|
+
}
|
|
67
|
+
return Array.from(tags);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function buildRule(check) {
|
|
71
|
+
return {
|
|
72
|
+
id: check.ruleId,
|
|
73
|
+
name: check.ruleId,
|
|
74
|
+
shortDescription: { text: check.title || check.ruleId },
|
|
75
|
+
fullDescription: { text: check.description || check.title || check.ruleId },
|
|
76
|
+
// type: "manual" rules are capped at cantTell (never fail), so their
|
|
77
|
+
// worst-case, rule-level default is "warning"; automatic rules can
|
|
78
|
+
// reach "error" -- see docs/OUTPUT_SCHEMA.md's outcome/type table.
|
|
79
|
+
defaultConfiguration: { level: check.type === 'automatic' ? 'error' : 'warning' },
|
|
80
|
+
properties: { tags: wcagTags(check) }
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function buildResult(check, occurrence, level, artifactUri) {
|
|
85
|
+
const reasonCode = getReasonCode(occurrence);
|
|
86
|
+
const html = typeof occurrence.html === 'string' ? occurrence.html : '';
|
|
87
|
+
const message = occurrence.hint ? `${occurrence.summary} ${occurrence.hint}` : occurrence.summary;
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
ruleId: check.ruleId,
|
|
91
|
+
level,
|
|
92
|
+
message: { text: message },
|
|
93
|
+
locations: [
|
|
94
|
+
{
|
|
95
|
+
physicalLocation: { artifactLocation: { uri: artifactUri } },
|
|
96
|
+
...(occurrence.selector
|
|
97
|
+
? { logicalLocations: [{ fullyQualifiedName: occurrence.selector, kind: 'element' }] }
|
|
98
|
+
: {})
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
partialFingerprints: {
|
|
102
|
+
'surea11y/violation/v1': computeBaselineKey(check.ruleId, reasonCode, html)
|
|
103
|
+
},
|
|
104
|
+
properties: {
|
|
105
|
+
severity: check.severity,
|
|
106
|
+
confidence: check.confidence,
|
|
107
|
+
reasonCode,
|
|
108
|
+
html
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function renderSarifReport(result, options = {}) {
|
|
114
|
+
const { toolVersion, informationUri, baselineEntries } = options;
|
|
115
|
+
const artifactUri = artifactUriFromResult(result);
|
|
116
|
+
const remaining = buildRemainingBaselineMap(baselineEntries);
|
|
117
|
+
|
|
118
|
+
const rules = [];
|
|
119
|
+
const seenRuleIds = new Set();
|
|
120
|
+
const failResults = [];
|
|
121
|
+
const cantTellResults = [];
|
|
122
|
+
|
|
123
|
+
for (const check of (result && result.checksResults) || []) {
|
|
124
|
+
if (!check || !Array.isArray(check.occurrences)) continue;
|
|
125
|
+
|
|
126
|
+
if (!seenRuleIds.has(check.ruleId)) {
|
|
127
|
+
seenRuleIds.add(check.ruleId);
|
|
128
|
+
rules.push(buildRule(check));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (check.outcome !== 'fail' && check.outcome !== 'cantTell') continue;
|
|
132
|
+
|
|
133
|
+
for (const occurrence of check.occurrences) {
|
|
134
|
+
if (!occurrence) continue;
|
|
135
|
+
|
|
136
|
+
if (check.outcome === 'fail') {
|
|
137
|
+
const reasonCode = getReasonCode(occurrence);
|
|
138
|
+
const html = typeof occurrence.html === 'string' ? occurrence.html : '';
|
|
139
|
+
const key = computeBaselineKey(check.ruleId, reasonCode, html);
|
|
140
|
+
const left = remaining.get(key) || 0;
|
|
141
|
+
if (left > 0) {
|
|
142
|
+
remaining.set(key, left - 1);
|
|
143
|
+
continue; // already known via the baseline -- omit, don't re-gate
|
|
144
|
+
}
|
|
145
|
+
failResults.push(buildResult(check, occurrence, 'error', artifactUri));
|
|
146
|
+
} else {
|
|
147
|
+
cantTellResults.push(buildResult(check, occurrence, 'warning', artifactUri));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const sarifLog = {
|
|
153
|
+
$schema: SARIF_SCHEMA_URI,
|
|
154
|
+
version: SARIF_VERSION,
|
|
155
|
+
runs: [
|
|
156
|
+
{
|
|
157
|
+
tool: {
|
|
158
|
+
driver: {
|
|
159
|
+
name: 'surea11y',
|
|
160
|
+
informationUri: informationUri || 'https://github.com/rumoroso/surea11y-core',
|
|
161
|
+
version: toolVersion || '0.0.0',
|
|
162
|
+
rules
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
// fail first: matches docs/REPORT.md's own "violations before advisory
|
|
166
|
+
// findings" ordering.
|
|
167
|
+
results: [...failResults, ...cantTellResults]
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return JSON.stringify(sarifLog, null, 2) + '\n';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = { renderSarifReport };
|