@surea11y/core 1.2.0 → 1.4.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 +81 -7
- package/LICENSE +373 -21
- package/README.md +175 -35
- package/bin/surea11y-core.js +20 -0
- package/docs/API_STABILITY.md +27 -1
- package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
- package/docs/CI_INTEGRATIONS.md +103 -0
- package/docs/ENGINE_OPTIONS.md +2 -0
- package/docs/I18N.md +12 -9
- package/docs/INTEGRATION.md +19 -1
- package/docs/LIMITATIONS.md +1 -1
- package/docs/OUTPUT_SCHEMA.md +1 -1
- package/docs/REPORT.md +1 -1
- package/docs/RULE_CATALOG.md +1 -1
- package/docs/SARIF.md +59 -0
- package/package.json +63 -18
- package/src/baseline.js +0 -0
- package/src/checks/automatic/area-alt-present.js +63 -31
- package/src/checks/automatic/aria-allowed-attr.js +204 -80
- package/src/checks/automatic/aria-allowed-role.js +23 -7
- package/src/checks/automatic/aria-braille-equivalent.js +34 -10
- package/src/checks/automatic/aria-conditional-attr.js +32 -14
- package/src/checks/automatic/aria-deprecated-role.js +26 -11
- package/src/checks/automatic/aria-hidden-body.js +48 -23
- package/src/checks/automatic/aria-hidden-focus.js +420 -66
- package/src/checks/automatic/aria-prohibited-attr.js +327 -60
- package/src/checks/automatic/aria-prohibited-children.js +111 -103
- package/src/checks/automatic/aria-required-attr.js +29 -15
- package/src/checks/automatic/aria-required-children.js +44 -24
- package/src/checks/automatic/aria-required-parent.js +64 -35
- package/src/checks/automatic/aria-role-name-present.js +49 -21
- package/src/checks/automatic/aria-roles-valid.js +24 -12
- package/src/checks/automatic/aria-valid-attr-value.js +46 -22
- package/src/checks/automatic/aria-valid-attr.js +19 -5
- package/src/checks/automatic/autocomplete-valid.js +76 -16
- package/src/checks/automatic/avoid-inline-spacing.js +23 -8
- package/src/checks/automatic/binary-control-name-present.js +62 -50
- package/src/checks/automatic/button-name-present.js +54 -24
- package/src/checks/automatic/bypass-blocks-present.js +51 -32
- package/src/checks/automatic/canvas-text-alternative-present.js +59 -26
- package/src/checks/automatic/combobox-name-present.js +40 -45
- package/src/checks/automatic/contrast-computable.js +363 -341
- package/src/checks/automatic/contrast-enhanced.js +489 -466
- package/src/checks/automatic/contrast-minimum.js +488 -465
- package/src/checks/automatic/css-orientation-lock.js +51 -35
- package/src/checks/automatic/definition-list-children-valid.js +46 -25
- package/src/checks/automatic/deprecated-elements-not-used.js +25 -9
- package/src/checks/automatic/dialog-name-present.js +47 -85
- package/src/checks/automatic/dlitem-parent-valid.js +25 -8
- package/src/checks/automatic/duplicate-id-aria.js +28 -9
- package/src/checks/automatic/embed-text-alternative-present.js +88 -35
- package/src/checks/automatic/form-control-programmatic-label-present.js +81 -196
- package/src/checks/automatic/form-control-single-label.js +50 -14
- package/src/checks/automatic/html-xml-lang-mismatch.js +36 -18
- package/src/checks/automatic/iframe-focusable-content.js +265 -22
- package/src/checks/automatic/iframe-name-present.js +33 -9
- package/src/checks/automatic/iframe-title-unique.js +32 -9
- package/src/checks/automatic/img-alt-present.js +54 -52
- package/src/checks/automatic/input-image-alt-present.js +141 -112
- package/src/checks/automatic/label-in-name.js +65 -41
- package/src/checks/automatic/language-page-present.js +111 -109
- package/src/checks/automatic/link-in-text-block.js +61 -19
- package/src/checks/automatic/link-name-present.js +47 -14
- package/src/checks/automatic/list-children-valid.js +40 -33
- package/src/checks/automatic/listbox-name-present.js +41 -19
- package/src/checks/automatic/listitem-parent-valid.js +48 -13
- package/src/checks/automatic/menuitem-name-present.js +41 -61
- package/src/checks/automatic/meta-refresh-no-exceptions.js +32 -11
- package/src/checks/automatic/meta-refresh-timing-absent.js +22 -6
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +26 -7
- package/src/checks/automatic/meter-name-present.js +40 -36
- package/src/checks/automatic/nested-interactive-controls-absent.js +58 -15
- package/src/checks/automatic/object-text-alternative-present.js +93 -39
- package/src/checks/automatic/option-name-present.js +40 -21
- package/src/checks/automatic/page-title-present.js +19 -6
- package/src/checks/automatic/progressbar-name-present.js +49 -44
- package/src/checks/automatic/role-img-alt-present.js +211 -159
- package/src/checks/automatic/searchbox-name-present.js +41 -19
- package/src/checks/automatic/server-side-image-map-absent.js +27 -11
- package/src/checks/automatic/slider-name-present.js +42 -47
- package/src/checks/automatic/spinbutton-name-present.js +41 -19
- package/src/checks/automatic/summary-name-present.js +39 -17
- package/src/checks/automatic/svg-image-text-alternative-present.js +116 -47
- package/src/checks/automatic/svg-text-alternative-present.js +262 -230
- package/src/checks/automatic/tab-name-present.js +39 -60
- package/src/checks/automatic/table-headers-attr-valid.js +27 -10
- package/src/checks/automatic/table-th-has-data-cells.js +24 -8
- package/src/checks/automatic/target-size-minimum.js +123 -48
- package/src/checks/automatic/td-has-header.js +53 -12
- package/src/checks/automatic/textbox-name-present.js +41 -19
- package/src/checks/automatic/tooltip-name-present.js +39 -18
- package/src/checks/automatic/treeitem-name-present.js +40 -21
- package/src/checks/automatic/valid-lang.js +22 -6
- package/src/checks/automatic/video-poster-text-alternative-present.js +81 -36
- package/src/checks/manual/accesskeys-manual.js +17 -6
- package/src/checks/manual/area-alt-decorative-manual.js +194 -193
- package/src/checks/manual/area-alt-quality-manual.js +184 -141
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +48 -34
- package/src/checks/manual/aria-text-manual.js +20 -11
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +151 -114
- package/src/checks/manual/css-hidden-focus.js +375 -169
- package/src/checks/manual/embed-text-alternative-quality-manual.js +178 -162
- package/src/checks/manual/empty-heading-manual.js +41 -24
- package/src/checks/manual/empty-table-header-manual.js +69 -31
- package/src/checks/manual/focus-order-semantics-manual.js +60 -13
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +209 -246
- package/src/checks/manual/heading-order-manual.js +50 -8
- package/src/checks/manual/identical-links-same-purpose-manual.js +36 -12
- package/src/checks/manual/image-redundant-alt-manual.js +38 -8
- package/src/checks/manual/img-alt-decorative-manual.js +133 -96
- package/src/checks/manual/img-alt-quality-manual.js +178 -127
- package/src/checks/manual/input-image-alt-decorative-manual.js +127 -92
- package/src/checks/manual/input-image-alt-quality-manual.js +127 -92
- package/src/checks/manual/label-title-only-manual.js +44 -28
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -38
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +85 -32
- package/src/checks/manual/landmark-main-is-top-level-manual.js +69 -27
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +45 -33
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +43 -31
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +27 -21
- package/src/checks/manual/landmark-one-main-manual.js +38 -43
- package/src/checks/manual/landmark-unique-manual.js +78 -67
- package/src/checks/manual/link-name-quality-manual.js +45 -12
- package/src/checks/manual/media-transcript-present-manual.js +37 -22
- package/src/checks/manual/meta-viewport-large-manual.js +19 -6
- package/src/checks/manual/mouse-only-event-handlers-manual.js +40 -11
- package/src/checks/manual/no-autoplay-audio-manual.js +22 -6
- package/src/checks/manual/object-text-alternative-quality-manual.js +177 -154
- package/src/checks/manual/p-as-heading-manual.js +24 -7
- package/src/checks/manual/page-has-heading-one-manual.js +42 -32
- package/src/checks/manual/page-title-patterns-manual.js +80 -50
- package/src/checks/manual/presentation-role-conflict-manual.js +101 -47
- package/src/checks/manual/region-manual.js +244 -60
- package/src/checks/manual/scope-attr-valid-manual.js +13 -4
- package/src/checks/manual/scrollable-region-focusable-manual.js +39 -11
- package/src/checks/manual/skip-link-manual.js +42 -18
- package/src/checks/manual/svg-text-alternative-quality-manual.js +208 -165
- package/src/checks/manual/tabindex-manual.js +13 -4
- package/src/checks/manual/table-duplicate-name-manual.js +22 -11
- package/src/checks/manual/table-fake-caption-manual.js +48 -10
- package/src/checks/manual/video-caption-manual.js +17 -4
- package/src/checks/manual-review.js +58 -12
- package/src/core.js +41705 -29650
- package/src/index.js +2 -0
- package/src/report.js +109 -47
- package/src/sarif.js +190 -0
- package/surea11y.browser.js +37774 -0
- package/bin/core.js +0 -348
- package/docs/CLI.md +0 -75
- package/src/catalogs/composites.wcag.js +0 -490
- package/src/checks/rules-and-tags.full.csv +0 -19
- package/src/checks/rules-and-tags.full.json +0 -259
- package/src/core/aria-helpers.js +0 -970
- package/src/core/contrast-helpers.js +0 -1147
- package/src/core/dom-helpers.js +0 -4235
- package/src/core/dom-runner.js +0 -671
- package/src/core/frame-messaging.js +0 -210
- package/src/core/frame-scan.js +0 -178
- package/src/core/rollup-composites.js +0 -135
- package/src/core/rule-meta.js +0 -159
- package/src/coverage/wcag-facets.js +0 -1079
- package/src/coverage/wcag-version-map.js +0 -84
- package/src/i18n/en.js +0 -923
- package/src/i18n/fr.js +0 -844
- package/src/policy/contracts.js +0 -18
- package/src/policy/resolvePolicy.js +0 -55
- package/src/policy/schemas/engine-options.schema.json +0 -103
- package/src/policy/schemas/policy-contract.schema.json +0 -40
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* postMessage-based RPC used to reach into cooperating child frames (same-
|
|
5
|
-
* or cross-origin -- treated identically, exactly like a widely-used
|
|
6
|
-
* reference engine's own runPartial/finishRun/frameMessenger protocol,
|
|
7
|
-
* confirmed by reading that engine's _sendCommandToFrame/_collectResultsFromFrames
|
|
8
|
-
* source directly rather than assuming). Used by frame-scan.js.
|
|
9
|
-
*
|
|
10
|
-
* Only matters for the "plain script injection" consumption mode (surea11y
|
|
11
|
-
* loaded directly into a page with no automation driver -- see
|
|
12
|
-
* docs/INTEGRATION.md's "Browser extension context" section). A Playwright-
|
|
13
|
-
* driven scan doesn't need any of this: Playwright reaches cross-origin
|
|
14
|
-
* frames unconditionally via CDP (see surea11y-playwright's ROADMAP.md gap
|
|
15
|
-
* #1), which is strictly better than what a cooperative postMessage protocol
|
|
16
|
-
* can achieve. This exists for when there IS no automation driver.
|
|
17
|
-
*
|
|
18
|
-
* Zero free vars in each exported piece -- inlined into generated core.js
|
|
19
|
-
* via inlineConstFunction (scripts/build-core.js), same as dom-helpers.js/
|
|
20
|
-
* dom-runner.js/rule-meta.js. All functions here become sibling `const`
|
|
21
|
-
* declarations in the same generated scope, so they may freely reference
|
|
22
|
-
* each other (matching how resolveEffectiveRunOnly/ruleMatchesRunOnly/etc.
|
|
23
|
-
* already do) -- but each is independently a zero-free-var function with
|
|
24
|
-
* respect to anything OUTSIDE that shared generated scope.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
const FRAME_RPC_CHANNEL = '__frame_rpc_v1__';
|
|
28
|
-
|
|
29
|
-
function getFrameRpcRegistry(win) {
|
|
30
|
-
if (!win.__a11yCoreFrameRpc__) {
|
|
31
|
-
win.__a11yCoreFrameRpc__ = {
|
|
32
|
-
pending: new Map(),
|
|
33
|
-
seq: 0,
|
|
34
|
-
responder: null,
|
|
35
|
-
listening: false
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
return win.__a11yCoreFrameRpc__;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function installFrameRpcListener(win, channel) {
|
|
42
|
-
const registry = getFrameRpcRegistry(win);
|
|
43
|
-
if (registry.listening) return registry;
|
|
44
|
-
|
|
45
|
-
win.addEventListener('message', function a11yCoreFrameRpcListener(event) {
|
|
46
|
-
const data = event && event.data;
|
|
47
|
-
if (!data || data.__a11ycore !== true || data.channel !== channel) return;
|
|
48
|
-
|
|
49
|
-
if (data.type === 'ping') {
|
|
50
|
-
try {
|
|
51
|
-
event.source && event.source.postMessage(
|
|
52
|
-
{ __a11ycore: true, channel: channel, requestId: data.requestId, type: 'pong' },
|
|
53
|
-
'*'
|
|
54
|
-
);
|
|
55
|
-
} catch (e) { /* target gone/closed -- nothing to do */ }
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (data.type === 'run') {
|
|
60
|
-
const responder = registry.responder;
|
|
61
|
-
if (typeof responder !== 'function') return; // no responder enabled here: unreachable, same as a widely-used reference engine's own limitation
|
|
62
|
-
Promise.resolve()
|
|
63
|
-
.then(function () { return responder(data.payload); })
|
|
64
|
-
.then(function (result) {
|
|
65
|
-
try {
|
|
66
|
-
event.source && event.source.postMessage(
|
|
67
|
-
{ __a11ycore: true, channel: channel, requestId: data.requestId, type: 'result', payload: result },
|
|
68
|
-
'*'
|
|
69
|
-
);
|
|
70
|
-
} catch (e) { /* target gone/closed */ }
|
|
71
|
-
})
|
|
72
|
-
.catch(function (err) {
|
|
73
|
-
try {
|
|
74
|
-
event.source && event.source.postMessage(
|
|
75
|
-
{ __a11ycore: true, channel: channel, requestId: data.requestId, type: 'error', payload: String(err && err.message ? err.message : err) },
|
|
76
|
-
'*'
|
|
77
|
-
);
|
|
78
|
-
} catch (e) { /* target gone/closed */ }
|
|
79
|
-
});
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// 'pong' | 'result' | 'error' -- resolve whichever pending request this replies to.
|
|
84
|
-
const pending = registry.pending.get(data.requestId);
|
|
85
|
-
if (!pending) return;
|
|
86
|
-
if (data.type === 'pong') {
|
|
87
|
-
pending.onPong();
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
registry.pending.delete(data.requestId);
|
|
91
|
-
if (data.type === 'result') pending.resolve(data.payload);
|
|
92
|
-
else pending.reject(new Error(typeof data.payload === 'string' ? data.payload : 'surea11y frame RPC error'));
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
registry.listening = true;
|
|
96
|
-
return registry;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function nextFrameRpcRequestId(win) {
|
|
100
|
-
const registry = getFrameRpcRegistry(win);
|
|
101
|
-
registry.seq += 1;
|
|
102
|
-
return 'req_' + Date.now().toString(36) + '_' + registry.seq + '_' + Math.random().toString(36).slice(2, 8);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Pings a target frame's window; resolves true if a cooperating surea11y
|
|
107
|
-
* responder answers within pingWaitTime (default 500ms, matching a widely-used
|
|
108
|
-
* reference engine's own default), false otherwise. Never rejects -- "not reachable" is a normal,
|
|
109
|
-
* expected outcome (most iframes on the web have no surea11y loaded at
|
|
110
|
-
* all), not an error.
|
|
111
|
-
*/
|
|
112
|
-
function pingFrame(win, targetWindow, pingWaitTime) {
|
|
113
|
-
installFrameRpcListener(win, FRAME_RPC_CHANNEL);
|
|
114
|
-
const registry = getFrameRpcRegistry(win);
|
|
115
|
-
const requestId = nextFrameRpcRequestId(win);
|
|
116
|
-
const waitMs = typeof pingWaitTime === 'number' ? pingWaitTime : 500;
|
|
117
|
-
|
|
118
|
-
return new Promise(function (resolve) {
|
|
119
|
-
let settled = false;
|
|
120
|
-
const timeout = setTimeout(function () {
|
|
121
|
-
if (settled) return;
|
|
122
|
-
settled = true;
|
|
123
|
-
registry.pending.delete(requestId);
|
|
124
|
-
resolve(false);
|
|
125
|
-
}, waitMs);
|
|
126
|
-
|
|
127
|
-
registry.pending.set(requestId, {
|
|
128
|
-
onPong: function () {
|
|
129
|
-
if (settled) return;
|
|
130
|
-
settled = true;
|
|
131
|
-
clearTimeout(timeout);
|
|
132
|
-
registry.pending.delete(requestId);
|
|
133
|
-
resolve(true);
|
|
134
|
-
},
|
|
135
|
-
resolve: function () {},
|
|
136
|
-
reject: function () {}
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
targetWindow.postMessage({ __a11ycore: true, channel: FRAME_RPC_CHANNEL, requestId: requestId, type: 'ping' }, '*');
|
|
141
|
-
} catch (e) {
|
|
142
|
-
if (!settled) {
|
|
143
|
-
settled = true;
|
|
144
|
-
clearTimeout(timeout);
|
|
145
|
-
registry.pending.delete(requestId);
|
|
146
|
-
resolve(false);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Sends a 'run' command to a target frame's window (already confirmed
|
|
154
|
-
* reachable via pingFrame) and resolves with its reply payload, or rejects
|
|
155
|
-
* on timeout (default 60s, matching a widely-used reference engine's own frameWaitTime default) or an
|
|
156
|
-
* explicit error reply.
|
|
157
|
-
*/
|
|
158
|
-
function sendFrameRunCommand(win, targetWindow, payload, frameWaitTime) {
|
|
159
|
-
installFrameRpcListener(win, FRAME_RPC_CHANNEL);
|
|
160
|
-
const registry = getFrameRpcRegistry(win);
|
|
161
|
-
const requestId = nextFrameRpcRequestId(win);
|
|
162
|
-
const waitMs = typeof frameWaitTime === 'number' ? frameWaitTime : 60000;
|
|
163
|
-
|
|
164
|
-
return new Promise(function (resolve, reject) {
|
|
165
|
-
const timeout = setTimeout(function () {
|
|
166
|
-
registry.pending.delete(requestId);
|
|
167
|
-
reject(new Error('surea11y frame RPC timed out waiting for a run result'));
|
|
168
|
-
}, waitMs);
|
|
169
|
-
|
|
170
|
-
registry.pending.set(requestId, {
|
|
171
|
-
onPong: function () {},
|
|
172
|
-
resolve: function (result) { clearTimeout(timeout); resolve(result); },
|
|
173
|
-
reject: function (err) { clearTimeout(timeout); reject(err); }
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
targetWindow.postMessage({ __a11ycore: true, channel: FRAME_RPC_CHANNEL, requestId: requestId, type: 'run', payload: payload }, '*');
|
|
178
|
-
} catch (e) {
|
|
179
|
-
clearTimeout(timeout);
|
|
180
|
-
registry.pending.delete(requestId);
|
|
181
|
-
reject(e);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Registers this window as reachable by a parent frame's scan: `handler`
|
|
188
|
-
* receives the run command's payload and must return a result (or a
|
|
189
|
-
* Promise of one). Returns a disable() function that removes the responder
|
|
190
|
-
* (the message listener itself stays installed -- harmless/idle -- so a
|
|
191
|
-
* later re-enable doesn't need to re-attach it).
|
|
192
|
-
*/
|
|
193
|
-
function enableFrameRpcResponder(win, handler) {
|
|
194
|
-
installFrameRpcListener(win, FRAME_RPC_CHANNEL);
|
|
195
|
-
const registry = getFrameRpcRegistry(win);
|
|
196
|
-
registry.responder = handler;
|
|
197
|
-
return function disable() {
|
|
198
|
-
if (registry.responder === handler) registry.responder = null;
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
module.exports = {
|
|
203
|
-
FRAME_RPC_CHANNEL,
|
|
204
|
-
getFrameRpcRegistry,
|
|
205
|
-
installFrameRpcListener,
|
|
206
|
-
nextFrameRpcRequestId,
|
|
207
|
-
pingFrame,
|
|
208
|
-
sendFrameRunCommand,
|
|
209
|
-
enableFrameRpcResponder
|
|
210
|
-
};
|
package/src/core/frame-scan.js
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Cross-frame orchestration for the "plain script injection" consumption
|
|
5
|
-
* mode (surea11y loaded directly into a page with no automation driver --
|
|
6
|
-
* see docs/INTEGRATION.md's "Browser extension context" section). A
|
|
7
|
-
* Playwright-driven scan doesn't need any of this (see
|
|
8
|
-
* surea11y-playwright's ROADMAP.md gap #1 -- CDP-level frame access is
|
|
9
|
-
* unconditional, strictly better than what a cooperative protocol like this
|
|
10
|
-
* one can achieve). This exists for when there is no automation driver, the
|
|
11
|
-
* same situation a widely-used reference engine itself is built around.
|
|
12
|
-
*
|
|
13
|
-
* Inlined into generated core.js (via scripts/build-core.js), wrapped in its
|
|
14
|
-
* OWN private IIFE together with its own local copies of CHECK_DEFS/
|
|
15
|
-
* RULE_IMPLS/ENGINE_TAG/SCHEMA_VERSION/COMPOSITE_RULES and the shared
|
|
16
|
-
* runnersSharedSource block (runCore, resolveEffectiveRunOnly,
|
|
17
|
-
* resolveContextRoots, pingFrame, sendFrameRunCommand,
|
|
18
|
-
* enableFrameRpcResponder, etc.) -- mirroring exactly how runa11yCoreInPage
|
|
19
|
-
* itself achieves self-containment, and deliberately calling runCore(...)
|
|
20
|
-
* directly here (not the sibling runa11yCoreInPage) so this stays
|
|
21
|
-
* independent of the outer, Node-require-based RULE_IMPLS section. That
|
|
22
|
-
* independence matters concretely: it's what lets these two functions be
|
|
23
|
-
* used the exact same bundler-free way runa11yCoreInPage already is (raw
|
|
24
|
-
* source injected into a page, e.g. a bookmarklet or a content script with
|
|
25
|
-
* no build step) rather than requiring a real bundler to resolve `require()`
|
|
26
|
-
* calls first. References runCore/resolveContextRoots/resolveEffectiveRunOnly/
|
|
27
|
-
* pingFrame/sendFrameRunCommand/enableFrameRpcResponder/CHECK_DEFS/
|
|
28
|
-
* RULE_IMPLS/ENGINE_TAG/SCHEMA_VERSION/COMPOSITE_RULES as free vars,
|
|
29
|
-
* satisfied by that wrapping. Not requireable/testable in isolation for
|
|
30
|
-
* that reason (same as dom-runner.js) -- test via the generated core.js
|
|
31
|
-
* bundle instead.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
function findChildFrameElements(roots) {
|
|
35
|
-
const seen = new Set();
|
|
36
|
-
const out = [];
|
|
37
|
-
for (const root of roots) {
|
|
38
|
-
if (!root || typeof root.querySelectorAll !== 'function') continue;
|
|
39
|
-
let matches = [];
|
|
40
|
-
try {
|
|
41
|
-
matches = root.querySelectorAll('iframe, frame');
|
|
42
|
-
} catch (e) {
|
|
43
|
-
matches = [];
|
|
44
|
-
}
|
|
45
|
-
for (const el of matches) {
|
|
46
|
-
if (el && !seen.has(el)) {
|
|
47
|
-
seen.add(el);
|
|
48
|
-
out.push(el);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return out;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function getFrameElementUrl(el) {
|
|
56
|
-
try {
|
|
57
|
-
if (el.contentWindow && el.contentWindow.location && el.contentWindow.location.href) {
|
|
58
|
-
return el.contentWindow.location.href;
|
|
59
|
-
}
|
|
60
|
-
} catch (e) {
|
|
61
|
-
// Cross-origin: reading contentWindow.location.href itself throws. Fall
|
|
62
|
-
// back to the authored src attribute (always readable, any origin).
|
|
63
|
-
}
|
|
64
|
-
return el.getAttribute ? (el.getAttribute('src') || null) : null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Scans the current frame, then attempts to reach every direct child
|
|
69
|
-
* <iframe>/<frame> within the same scan scope via the frame RPC protocol
|
|
70
|
-
* (src/core/frame-messaging.js). A child that doesn't respond (no
|
|
71
|
-
* cooperating surea11y loaded and enabled there via
|
|
72
|
-
* a11yCoreEnableFrameResponder() -- the common case for most third-party
|
|
73
|
-
* embeds, and the same real limitation a widely-used reference engine itself has for
|
|
74
|
-
* non-cooperating frames) is reported as { url, error } rather than
|
|
75
|
-
* aborting the scan, matching the non-fatal-per-frame philosophy already
|
|
76
|
-
* established for the Playwright binding's .frames(true). A child that
|
|
77
|
-
* does respond replies with its OWN complete { topFrame, frames } result,
|
|
78
|
-
* recursively including ITS OWN nested frames -- a tree, not a flat list
|
|
79
|
-
* (unlike the Playwright binding: that binding can flatten because
|
|
80
|
-
* Playwright's page.frames() already gives a flat list regardless of
|
|
81
|
-
* nesting depth; a postMessage relay can't know about a grandchild without
|
|
82
|
-
* asking through the child first).
|
|
83
|
-
*
|
|
84
|
-
* @returns {Promise<{ topFrame: object, frames: Array<{url:string|null, topFrame?:object, frames?:Array, error?:string}> }>}
|
|
85
|
-
*/
|
|
86
|
-
function runa11yCoreAcrossFrames(pageUrl, contextSelector, engineOptions, runOnly) {
|
|
87
|
-
const topFrame = runCore(
|
|
88
|
-
pageUrl,
|
|
89
|
-
contextSelector,
|
|
90
|
-
engineOptions,
|
|
91
|
-
resolveEffectiveRunOnly(engineOptions, runOnly),
|
|
92
|
-
CHECK_DEFS,
|
|
93
|
-
RULE_IMPLS,
|
|
94
|
-
ENGINE_TAG,
|
|
95
|
-
SCHEMA_VERSION,
|
|
96
|
-
COMPOSITE_RULES
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
const eo = (engineOptions && typeof engineOptions === 'object') ? engineOptions : {};
|
|
100
|
-
const pingWaitTime = typeof eo.pingWaitTime === 'number' ? eo.pingWaitTime : undefined;
|
|
101
|
-
const frameWaitTime = typeof eo.frameWaitTime === 'number' ? eo.frameWaitTime : undefined;
|
|
102
|
-
|
|
103
|
-
const { roots } = resolveContextRoots(document, contextSelector);
|
|
104
|
-
const frameElements = findChildFrameElements(roots);
|
|
105
|
-
|
|
106
|
-
const framePromises = frameElements.map(function (el) {
|
|
107
|
-
const url = getFrameElementUrl(el);
|
|
108
|
-
let targetWindow = null;
|
|
109
|
-
try {
|
|
110
|
-
targetWindow = el.contentWindow || null;
|
|
111
|
-
} catch (e) {
|
|
112
|
-
targetWindow = null;
|
|
113
|
-
}
|
|
114
|
-
if (!targetWindow) {
|
|
115
|
-
return Promise.resolve({ url: url, error: 'frame has no accessible contentWindow' });
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return pingFrame(window, targetWindow, pingWaitTime).then(function (reachable) {
|
|
119
|
-
if (!reachable) {
|
|
120
|
-
return {
|
|
121
|
-
url: url,
|
|
122
|
-
error: 'no surea11y frame responder detected (that frame never called a11yCoreEnableFrameResponder(), or has not finished loading yet)'
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
return sendFrameRunCommand(
|
|
126
|
-
window,
|
|
127
|
-
targetWindow,
|
|
128
|
-
{ pageUrl: url, contextSelector: null, engineOptions: eo, runOnly: runOnly },
|
|
129
|
-
frameWaitTime
|
|
130
|
-
).then(function (result) {
|
|
131
|
-
return { url: url, topFrame: result.topFrame, frames: result.frames };
|
|
132
|
-
}).catch(function (err) {
|
|
133
|
-
return { url: url, error: String(err && err.message ? err.message : err) };
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
return Promise.all(framePromises).then(function (frames) {
|
|
139
|
-
return { topFrame: topFrame, frames: frames };
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Opt-in: makes the CURRENT window reachable by a parent frame's
|
|
145
|
-
* runa11yCoreAcrossFrames() call. A page calls this once (e.g. right after
|
|
146
|
-
* loading surea11y) to become scannable from above. Deliberately a
|
|
147
|
-
* separate, explicit call rather than an automatic side effect of loading
|
|
148
|
-
* surea11y's code -- unlike a widely-used reference engine, whose mere
|
|
149
|
-
* presence as a loaded <script> makes it listen automatically. surea11y's distribution model
|
|
150
|
-
* (a function you call, not a script tag with load-time side effects)
|
|
151
|
-
* doesn't have an equivalent "just including it" moment, and an explicit
|
|
152
|
-
* opt-in is a clearer consent point besides -- it means every Node/jsdom
|
|
153
|
-
* consumer that merely requires the module never gets a phantom
|
|
154
|
-
* `window.addEventListener` they didn't ask for.
|
|
155
|
-
*
|
|
156
|
-
* The incoming run command's own engineOptions/runOnly are always used
|
|
157
|
-
* as-is (matching a widely-used reference engine's own behavior: the parent's
|
|
158
|
-
* request carries the options, the child just executes with them, no local
|
|
159
|
-
* override) -- there's no origin/identity check on the sender beyond the
|
|
160
|
-
* namespaced message envelope itself, matching that reference engine's own permissiveness here
|
|
161
|
-
* (running a read-only scan and replying with DOM-derived results isn't a
|
|
162
|
-
* privileged operation; the DOM content involved is no more sensitive than
|
|
163
|
-
* what's already rendered on the page).
|
|
164
|
-
*
|
|
165
|
-
* @returns {function(): void} disable() -- stops responding to future scans
|
|
166
|
-
*/
|
|
167
|
-
function a11yCoreEnableFrameResponder() {
|
|
168
|
-
return enableFrameRpcResponder(window, function (payload) {
|
|
169
|
-
return runa11yCoreAcrossFrames(
|
|
170
|
-
payload ? payload.pageUrl : null,
|
|
171
|
-
payload ? payload.contextSelector : null,
|
|
172
|
-
payload ? payload.engineOptions : {},
|
|
173
|
-
payload ? payload.runOnly : null
|
|
174
|
-
);
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
module.exports = { findChildFrameElements, getFrameElementUrl, runa11yCoreAcrossFrames, a11yCoreEnableFrameResponder };
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const LEVEL_RANK = Object.freeze({ A: 1, AA: 2, AAA: 3 });
|
|
4
|
-
|
|
5
|
-
function normalizeScanLevel(scanLevel) {
|
|
6
|
-
if (scanLevel === 'A' || scanLevel === 'AA' || scanLevel === 'AAA') return scanLevel;
|
|
7
|
-
return null;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Try to infer scan level deterministically from runOnly.tags if provided by the caller.
|
|
12
|
-
* (If you already pass scanLevel explicitly, you can ignore this.)
|
|
13
|
-
*/
|
|
14
|
-
function inferScanLevelFromRunOnly(runOnly) {
|
|
15
|
-
const tags = runOnly && Array.isArray(runOnly.tags) ? runOnly.tags : null;
|
|
16
|
-
if (!tags || tags.length === 0) return null;
|
|
17
|
-
|
|
18
|
-
// Common reference-engine-style tags; adjust if your app uses different ones.
|
|
19
|
-
if (tags.includes('wcag2aaa') || tags.includes('wcag22aaa')) return 'AAA';
|
|
20
|
-
if (tags.includes('wcag2aa') || tags.includes('wcag22aa')) return 'AA';
|
|
21
|
-
if (tags.includes('wcag2a') || tags.includes('wcag22a')) return 'A';
|
|
22
|
-
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function isWithinTargetLevel(compositeLevel, scanLevel) {
|
|
27
|
-
const c = LEVEL_RANK[compositeLevel];
|
|
28
|
-
const s = LEVEL_RANK[scanLevel];
|
|
29
|
-
if (!c || !s) return false;
|
|
30
|
-
return c <= s;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function aggregateOutcome(childOutcomes) {
|
|
34
|
-
// Deterministic priority: fail > cantTell > pass > notApplicable
|
|
35
|
-
let hasPass = false;
|
|
36
|
-
let hasCantTell = false;
|
|
37
|
-
|
|
38
|
-
for (const o of childOutcomes) {
|
|
39
|
-
if (o === 'fail') return 'fail';
|
|
40
|
-
if (o === 'cantTell') hasCantTell = true;
|
|
41
|
-
else if (o === 'pass') hasPass = true;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (hasCantTell) return 'cantTell';
|
|
45
|
-
if (hasPass) return 'pass';
|
|
46
|
-
return 'notApplicable';
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Roll up atomic rule results into composite (SC) results, filtered by target level.
|
|
51
|
-
*
|
|
52
|
-
* Inputs are plain JS objects/arrays; function never throws and is deterministic.
|
|
53
|
-
*/
|
|
54
|
-
function rollupComposites({
|
|
55
|
-
atomicResults,
|
|
56
|
-
composites,
|
|
57
|
-
scanLevel,
|
|
58
|
-
runOnly
|
|
59
|
-
}) {
|
|
60
|
-
try {
|
|
61
|
-
const target =
|
|
62
|
-
normalizeScanLevel(scanLevel) ||
|
|
63
|
-
inferScanLevelFromRunOnly(runOnly) ||
|
|
64
|
-
'AAA'; // safest default: if unspecified, do not hide anything
|
|
65
|
-
|
|
66
|
-
const atomicByRuleId = Object.create(null);
|
|
67
|
-
if (Array.isArray(atomicResults)) {
|
|
68
|
-
for (const r of atomicResults) {
|
|
69
|
-
if (!r || typeof r.ruleId !== 'string') continue;
|
|
70
|
-
atomicByRuleId[r.ruleId] = r;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const out = [];
|
|
75
|
-
const list = Array.isArray(composites) ? composites : [];
|
|
76
|
-
for (const c of list) {
|
|
77
|
-
const level = c && c.meta && c.meta.level;
|
|
78
|
-
if (!isWithinTargetLevel(level, target)) continue;
|
|
79
|
-
|
|
80
|
-
const checksIds = Array.isArray(c.checksIds) ? c.checksIds : [];
|
|
81
|
-
const childOutcomes = checksIds.map((id) => {
|
|
82
|
-
const rr = atomicByRuleId[id];
|
|
83
|
-
return rr && typeof rr.outcome === 'string' ? rr.outcome : 'notApplicable';
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const outcome = aggregateOutcome(childOutcomes);
|
|
87
|
-
|
|
88
|
-
// Deterministic i18n keys + structured details (matches your reporting schema expectations)
|
|
89
|
-
const i18nKey =
|
|
90
|
-
outcome === 'fail'
|
|
91
|
-
? 'composite.outcome.fail'
|
|
92
|
-
: outcome === 'cantTell'
|
|
93
|
-
? 'composite.outcome.cantTell'
|
|
94
|
-
: outcome === 'pass'
|
|
95
|
-
? 'composite.outcome.pass'
|
|
96
|
-
: 'composite.outcome.notApplicable';
|
|
97
|
-
|
|
98
|
-
out.push({
|
|
99
|
-
ruleId: c.id,
|
|
100
|
-
outcome,
|
|
101
|
-
confidence: outcome === 'cantTell' ? 'low' : 'high',
|
|
102
|
-
summaryKey: i18nKey, // resolved at build-time by your i18n layer
|
|
103
|
-
i18nKey,
|
|
104
|
-
i18nParams: {
|
|
105
|
-
wcagSc: (c.meta && c.meta.wcagSc && c.meta.wcagSc[0]) || null,
|
|
106
|
-
level: level || null,
|
|
107
|
-
scanLevel: target
|
|
108
|
-
},
|
|
109
|
-
data: {
|
|
110
|
-
details: {
|
|
111
|
-
reasonCode:
|
|
112
|
-
outcome === 'cantTell'
|
|
113
|
-
? 'oneOrMoreChecksCantTell'
|
|
114
|
-
: outcome === 'fail'
|
|
115
|
-
? 'oneOrMoreChecksFailed'
|
|
116
|
-
: outcome === 'pass'
|
|
117
|
-
? 'allApplicableChecksPassed'
|
|
118
|
-
: 'allChecksNotApplicable',
|
|
119
|
-
scanLevel: target,
|
|
120
|
-
compositeLevel: level || null,
|
|
121
|
-
checksIds,
|
|
122
|
-
childOutcomes
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return out;
|
|
129
|
-
} catch (e) {
|
|
130
|
-
// no-throws guarantee
|
|
131
|
-
return [];
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
module.exports = { rollupComposites };
|
package/src/core/rule-meta.js
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Normalizes a rule module's `meta` export into the stable shape CHECK_DEFS
|
|
5
|
-
* entries use everywhere else (build-time rules and runtime-registered
|
|
6
|
-
* custom rules alike).
|
|
7
|
-
*
|
|
8
|
-
* Zero free vars (besides its own params) -- this gets inlined into the
|
|
9
|
-
* generated core.js runtime via inlineConstFunction, the same mechanism
|
|
10
|
-
* dom-helpers.js/dom-runner.js use, so it must stay self-contained/embeddable
|
|
11
|
-
* via .toString(). engineTag is a param (not a closed-over module constant)
|
|
12
|
-
* for exactly that reason.
|
|
13
|
-
*/
|
|
14
|
-
function normalizeRuleMeta(ruleId, id, meta, engineTag) {
|
|
15
|
-
function normalizeStringArray(value) {
|
|
16
|
-
if (!Array.isArray(value)) return [];
|
|
17
|
-
return value.map(String).map((s) => s.trim()).filter(Boolean);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function normalizeObjectArray(value) {
|
|
21
|
-
if (!Array.isArray(value)) return [];
|
|
22
|
-
return value
|
|
23
|
-
.filter((v) => v && typeof v === 'object' && !Array.isArray(v))
|
|
24
|
-
.map((v) => ({ ...v }));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function deriveWcagScFromNormativeMappings(normativeMappings) {
|
|
28
|
-
const nm = Array.isArray(normativeMappings) ? normativeMappings : [];
|
|
29
|
-
const out = new Set();
|
|
30
|
-
for (const m of nm) {
|
|
31
|
-
if (!m || typeof m !== 'object') continue;
|
|
32
|
-
if (String(m.standard || '').toUpperCase() !== 'WCAG') continue;
|
|
33
|
-
const req = String(m.requirement || '').trim();
|
|
34
|
-
if (req) out.add(req);
|
|
35
|
-
}
|
|
36
|
-
return Array.from(out).sort();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const m = (meta && typeof meta === 'object') ? meta : {};
|
|
40
|
-
|
|
41
|
-
const title = (typeof m.title === 'string' && m.title.trim()) ? m.title.trim() : id;
|
|
42
|
-
const description = (typeof m.description === 'string') ? m.description : '';
|
|
43
|
-
const helpUrl = (typeof m.helpUrl === 'string') ? m.helpUrl : '';
|
|
44
|
-
|
|
45
|
-
const i18n = (m.i18n && typeof m.i18n === 'object' && !Array.isArray(m.i18n))
|
|
46
|
-
? { ...m.i18n }
|
|
47
|
-
: null;
|
|
48
|
-
|
|
49
|
-
const tags = normalizeStringArray(m.tags).map((t) => t.toLowerCase());
|
|
50
|
-
if (!tags.includes(engineTag)) tags.push(engineTag);
|
|
51
|
-
|
|
52
|
-
const normativeMappings = normalizeObjectArray(m.normativeMappings);
|
|
53
|
-
const wcagSc = deriveWcagScFromNormativeMappings(normativeMappings);
|
|
54
|
-
const informativeReferences = normalizeObjectArray(m.informativeReferences);
|
|
55
|
-
|
|
56
|
-
const defaultSeverity = (typeof m.defaultSeverity === 'string' && m.defaultSeverity.trim())
|
|
57
|
-
? m.defaultSeverity.trim()
|
|
58
|
-
: 'moderate';
|
|
59
|
-
|
|
60
|
-
const defaultConfidence = (typeof m.defaultConfidence === 'string' && m.defaultConfidence.trim())
|
|
61
|
-
? m.defaultConfidence.trim()
|
|
62
|
-
: 'medium';
|
|
63
|
-
|
|
64
|
-
const type = (m.type === 'manual' || m.type === 'automatic')
|
|
65
|
-
? m.type
|
|
66
|
-
: 'automatic';
|
|
67
|
-
|
|
68
|
-
const coverage = (m.coverage === null || typeof m.coverage === 'string' || typeof m.coverage === 'object')
|
|
69
|
-
? m.coverage
|
|
70
|
-
: null;
|
|
71
|
-
|
|
72
|
-
const ruleInterfaceVersion = (typeof m.ruleInterfaceVersion === 'string' && m.ruleInterfaceVersion.trim())
|
|
73
|
-
? m.ruleInterfaceVersion.trim()
|
|
74
|
-
: '1.0.0';
|
|
75
|
-
|
|
76
|
-
const ruleVersion = (typeof m.ruleVersion === 'string' && m.ruleVersion.trim())
|
|
77
|
-
? m.ruleVersion.trim()
|
|
78
|
-
: '0.0.0';
|
|
79
|
-
|
|
80
|
-
const normative = (typeof m.normative === 'boolean') ? m.normative : true;
|
|
81
|
-
const atomic = (typeof m.atomic === 'boolean') ? m.atomic : true;
|
|
82
|
-
|
|
83
|
-
// Deprecation signal for the rule catalog (see docs/API_STABILITY.md).
|
|
84
|
-
// Purely informational -- a deprecated rule still runs and produces
|
|
85
|
-
// results completely normally; this is a catalog-level migration signal
|
|
86
|
-
// for integrators, not an automatic exclusion.
|
|
87
|
-
const deprecated = (typeof m.deprecated === 'boolean') ? m.deprecated : false;
|
|
88
|
-
const deprecation = (deprecated && m.deprecation && typeof m.deprecation === 'object' && !Array.isArray(m.deprecation))
|
|
89
|
-
? {
|
|
90
|
-
replacedBy: (typeof m.deprecation.replacedBy === 'string' && m.deprecation.replacedBy.trim()) ? m.deprecation.replacedBy.trim() : null,
|
|
91
|
-
reason: (typeof m.deprecation.reason === 'string') ? m.deprecation.reason.trim() : '',
|
|
92
|
-
sinceVersion: (typeof m.deprecation.sinceVersion === 'string') ? m.deprecation.sinceVersion.trim() : ''
|
|
93
|
-
}
|
|
94
|
-
: null;
|
|
95
|
-
|
|
96
|
-
if (deprecated && (!deprecation || !deprecation.reason || !deprecation.sinceVersion)) {
|
|
97
|
-
throw new Error(`Rule ${ruleId}: meta.deprecated:true requires meta.deprecation.reason and meta.deprecation.sinceVersion`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const category = (typeof m.category === 'string' && m.category.trim()) ? m.category.trim() : null;
|
|
101
|
-
const standard = (typeof m.standard === 'string' && m.standard.trim()) ? m.standard.trim() : null;
|
|
102
|
-
|
|
103
|
-
const applicability = (typeof m.applicability === 'string') ? m.applicability : '';
|
|
104
|
-
const expectation = (typeof m.expectation === 'string') ? m.expectation : '';
|
|
105
|
-
|
|
106
|
-
const references = Array.isArray(m.references) ? m.references.slice() : [];
|
|
107
|
-
const requirements = (m.requirements === null || typeof m.requirements === 'string' || typeof m.requirements === 'object')
|
|
108
|
-
? m.requirements
|
|
109
|
-
: null;
|
|
110
|
-
|
|
111
|
-
const mappings = (m.mappings === null || typeof m.mappings === 'string' || typeof m.mappings === 'object')
|
|
112
|
-
? m.mappings
|
|
113
|
-
: null;
|
|
114
|
-
|
|
115
|
-
if (!Array.isArray(tags)) throw new Error(`Rule ${ruleId}: meta.tags must be an array`);
|
|
116
|
-
if (!Array.isArray(normativeMappings)) throw new Error(`Rule ${ruleId}: meta.normativeMappings must be an array`);
|
|
117
|
-
if (!Array.isArray(informativeReferences)) throw new Error(`Rule ${ruleId}: meta.informativeReferences must be an array`);
|
|
118
|
-
if (type !== 'automatic' && type !== 'manual') throw new Error(`Rule ${ruleId}: meta.type must be "automatic" or "manual"`);
|
|
119
|
-
|
|
120
|
-
if (i18n) {
|
|
121
|
-
if (typeof i18n.titleKey !== 'string' || !i18n.titleKey.trim()) {
|
|
122
|
-
throw new Error(`Rule ${ruleId}: meta.i18n.titleKey must be a non-empty string`);
|
|
123
|
-
}
|
|
124
|
-
if (i18n.descriptionKey != null && (typeof i18n.descriptionKey !== 'string' || !i18n.descriptionKey.trim())) {
|
|
125
|
-
throw new Error(`Rule ${ruleId}: meta.i18n.descriptionKey must be a non-empty string when provided`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
title,
|
|
131
|
-
description,
|
|
132
|
-
i18n,
|
|
133
|
-
helpUrl,
|
|
134
|
-
tags,
|
|
135
|
-
wcagSc,
|
|
136
|
-
normativeMappings,
|
|
137
|
-
informativeReferences,
|
|
138
|
-
defaultSeverity,
|
|
139
|
-
defaultConfidence,
|
|
140
|
-
type,
|
|
141
|
-
coverage,
|
|
142
|
-
|
|
143
|
-
ruleInterfaceVersion,
|
|
144
|
-
ruleVersion,
|
|
145
|
-
normative,
|
|
146
|
-
atomic,
|
|
147
|
-
deprecated,
|
|
148
|
-
deprecation,
|
|
149
|
-
category,
|
|
150
|
-
standard,
|
|
151
|
-
applicability,
|
|
152
|
-
expectation,
|
|
153
|
-
references,
|
|
154
|
-
requirements,
|
|
155
|
-
mappings
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
module.exports = { normalizeRuleMeta };
|