@surea11y/core 1.8.0 → 1.8.1

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 CHANGED
@@ -4,6 +4,15 @@ All notable changes to this project are documented here, in [Keep a Changelog](h
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.8.1] - 2026-10-02
8
+
9
+ ### Added
10
+ - CI runs ACT's test-case corpus on every push and pull request, and the release workflow runs it against the live corpus before publishing; either fails on a false positive, or when the corpus cannot be fetched in full. The false positive fixed below reached 1.8.0 because the corpus only ran after the release was tagged.
11
+ - ACT rule 4b1c6c is mapped to `identical-iframes-same-purpose`, so the implementation report covers it. The rule reports `cantTell`, never `fail`, for frames sharing a name but embedding different resources, so the mapping is partial.
12
+
13
+ ### Fixed
14
+ - `autocomplete-valid` no longer fails a well-formed field name that does not suit its control, such as `bday-day` on `<input type="tel">`, which 1.8.0 began failing. The name still identifies the input's purpose, which is all WCAG 1.3.5 asks, and ACT rule 73f2c2 passes this exact case (Passed Example 8), so 1.8.0 reported a false positive in the ACT implementation report. Its reason code `AUTOCOMPLETE_FIELD_CONTROL_MISMATCH` is retired with the failure it named, so a stored baseline entry or Code Scanning alert for it closes. The rule's other 1.8.0 fixes stay: `impp` and the `tel-local-prefix`/`tel-local-suffix` field names, and controls disabled by a disabled `<fieldset>`.
15
+
7
16
  ## [1.8.0] - 2026-10-02
8
17
 
9
18
  ### Added
@@ -3,11 +3,11 @@
3
3
  Cross-reference between the [W3C ACT Rules](https://act-rules.github.io/rules/) (as published at act-rules.github.io) and this repo's rule catalog (`docs/RULE_CATALOG.md`). Built by matching rule names/descriptions and WCAG SC, not machine-generated, so treat close calls as a starting point for review rather than ground truth.
4
4
 
5
5
  **Summary (117 active ACT rules, excludes 3 deprecated):**
6
- - **58 confirmed direct, family, or partial matches** in our automatic/manual rules (`scripts/data/act-rule-map.json` is the machine-readable version of the table below)
6
+ - **59 confirmed direct, family, or partial matches** in our automatic/manual rules (`scripts/data/act-rule-map.json` is the machine-readable version of the table below)
7
7
  - **~2** are covered structurally by our composite/rollup layer, not a named rule
8
8
  - **~46 are gaps**, no corresponding rule in this repo, listed in [Gaps](#gaps-no-corresponding-rule) below
9
9
 
10
- **Every matched rule has now been run through ACT's own official test-case corpus** (`scripts/act-testcase-check.js`, 713 test cases across the 51-rule matched set of the time). Started at 86 mismatches; real bugs were fixed, mapping errors corrected, and every remaining mismatch triaged into a scope difference, a jsdom/environment limit, or a genuine open design question (tracked in [`docs/DESIGN_CHALLENGES.md`](./DESIGN_CHALLENGES.md)). A second pass then re-ran the whole corpus from a local checkout (see "Second pass" below) and repeated the exercise on what it turned up. Current state: **798 examples across the 58-rule matched set, 31 mismatches, all explained** below or in that file, and — the figure that matters for an implementation report — **zero false positives**: no example ACT declares `passed` or `inapplicable` is failed by this engine, so every remaining mismatch is a case it does not catch rather than one it gets wrong; see "Progress" further down for the full per-rule breakdown. (The exact example count drifts slightly over time as ACT's own published corpus gains or loses cases; re-run `scripts/act-testcase-check.js` for the live figure rather than trusting this number indefinitely.)
10
+ **Every matched rule has now been run through ACT's own official test-case corpus** (`scripts/act-testcase-check.js`, 713 test cases across the 51-rule matched set of the time). Started at 86 mismatches; real bugs were fixed, mapping errors corrected, and every remaining mismatch triaged into a scope difference, a jsdom/environment limit, or a genuine open design question (tracked in [`docs/DESIGN_CHALLENGES.md`](./DESIGN_CHALLENGES.md)). A second pass then re-ran the whole corpus from a local checkout (see "Second pass" below) and repeated the exercise on what it turned up. Current state: **821 examples across the 59-rule matched set, 30 mismatches, all explained** below or in that file, and — the figure that matters for an implementation report — **zero false positives**: no example ACT declares `passed` or `inapplicable` is failed by this engine, so every remaining mismatch is a case it does not catch rather than one it gets wrong; see "Progress" further down for the full per-rule breakdown. (The exact example count drifts slightly over time as ACT's own published corpus gains or loses cases; re-run `scripts/act-testcase-check.js` for the live figure rather than trusting this number indefinitely.)
11
11
 
12
12
  Real rule bugs found and fixed this way, in rough chronological order:
13
13
  - `button-name-present` wasn't crediting the UA-default label on `input[type=submit]`/`input[type=reset]` with no `value`, and wasn't honoring `role="none"`/`role="presentation"` conflict-resolution.
@@ -74,11 +74,11 @@ We also have automatic rules with **no ACT counterpart at all** (see [Extra cove
74
74
 
75
75
  ### Progress: full validation results, by ACT rule
76
76
 
77
- **Clean (0 mismatches):** `5f99a7`, `80f0bf`, `4c31df`, `73f2c2`, `97a4e1`, `cf77f2`, `b40fd1`, `46ca7f`, `6cfa84`, `307n5z`, `4e8ab6`, `a25f45`, `ffd0e9`, `b5c3f8`, `2779a5`, `5b7ae0`, `bf051a`, `qt1vmo`, `59796f`, `23a2a8`, `24afc2`, `9e45ec`, `c487ae`, `m6b1q3`, `bc659a`, `bisz58`, `b4f0c3`, `674b10`, `0ssw9k`, `3ea0c8`, `5c01ea`, `bc4a75`, `2ee8b8`, `e88epe`, `7d6734`, `de46e4`, `6a7281`, `8fc3b6`, `akn7bn`, `fd3a94`, `b20e66`, `cae760`, `78fd32` (43 of 58 matched rules).
77
+ **Clean (0 mismatches):** `5f99a7`, `80f0bf`, `4c31df`, `73f2c2`, `97a4e1`, `cf77f2`, `b40fd1`, `46ca7f`, `6cfa84`, `307n5z`, `4e8ab6`, `a25f45`, `ffd0e9`, `b5c3f8`, `2779a5`, `5b7ae0`, `bf051a`, `qt1vmo`, `59796f`, `23a2a8`, `24afc2`, `9e45ec`, `c487ae`, `m6b1q3`, `bc659a`, `bisz58`, `b4f0c3`, `674b10`, `0ssw9k`, `3ea0c8`, `5c01ea`, `bc4a75`, `2ee8b8`, `e88epe`, `7d6734`, `de46e4`, `6a7281`, `8fc3b6`, `akn7bn`, `fd3a94`, `b20e66`, `cae760`, `78fd32`, `b33eff`, `e086e5` (45 of 59 matched rules).
78
78
 
79
79
  Two rules changed what they report after this table was last regenerated, both from `fail` to `cantTell`: `3ea0c8`/`duplicate-id` under the default WCAG 2.2 target, and `6a7281`/`aria-valid-attr-value` for an unresolved `aria-controls` target (see `docs/DESIGN_CHALLENGES.md`). The verdicts above should still hold, since `evaluate()` in `scripts/act-testcase-check.js` counts a `cantTell` carrying occurrences as satisfying an ACT "failed" expectation and a `cantTell` never breaks a "passed" one, but neither was re-run against the live corpus at the time (the site was unreachable from that environment). Re-run both when convenient.
80
80
 
81
- **Remaining mismatches (31 total), all triaged.** Every one is an ACT `failed` example this engine does not flag — a coverage gap, which a partially consistent implementation is allowed — not an example it fails wrongly:
81
+ **Remaining mismatches (30 total), all triaged.** Every one is an ACT `failed` example this engine does not flag — a coverage gap, which a partially consistent implementation is allowed — not an example it fails wrongly:
82
82
 
83
83
  | ACT ID | Mismatches | Category |
84
84
  |---|---|---|
@@ -86,10 +86,10 @@ Two rules changed what they report after this table was last regenerated, both f
86
86
  | `aaa1bf` | 1 | different question, not a gap in ours: `aaa1bf`'s own applicability/expectation is purely about clip *duration* ("does the audio stay under 3s," explicitly not exempted by a `controls` mechanism); `no-autoplay-audio` answers WCAG 1.4.2's other disjunct instead (does a pause/stop/volume mechanism exist). Duration isn't knowable from static markup regardless, no browser decodes media at scan time, so this mismatch can't close even in principle, not because our rule falls short of it |
87
87
  | `ye5d6e` | 1 | scoped leniency: whether repeated-boilerplate content wraps the skip target is a cross-page judgment undecidable from one document; the rule's own header comment already reasons through this trade-off |
88
88
  | `047fe0` | 2 | one of each: scoped leniency (a heading sitting inside the repeated `<nav>` block itself, same cross-page judgment as `ye5d6e` above), and an env/harness limit (a heading positioned off-screen via a `<link>`ed external stylesheet the test fetcher doesn't load, same class as `oj04fd` below; `tests/engine-checks/manual/bypass-blocks-present.test.js` pins the real, fixed behavior with the same CSS inlined) |
89
- | `e086e5` | 2 | accepted divergence, decided 2026-08-19, see `docs/DESIGN_CHALLENGES.md`'s "Decided" section: `<label for>`/wrapping association stays honoured on non-natively-labelable ARIA widgets, because a screen reader that announces such a label makes "no accessible name" a false positive. Real AT behaviour wins over the spec reading here |
90
89
  | `oj04fd` | 1 | env/harness limit: ACT's one failed example keeps its `outline: none` in a linked stylesheet, which the example runner does not fetch, so no focus rule is visible to parse at all. Inlining that same CSS reports the element (`tests/engine-checks/manual/css-focus-indicator-suppressed.test.js` pins it); a real page hands the engine its stylesheets through the CSSOM |
91
90
  | `cc0f0a` | 3 | inherent limitation: `form-control-label-quality` catches the three deterministic shapes (a placeholder label, a label repeated with no visible heading/legend/row telling the fields apart, a label split between visible and hidden parts, which covers ACT's failed examples 4 and 5). The remaining three fail on the meaning of a well-formed word, `<label>Menu</label>` over a first-name field, which no markup-level check reaches |
92
91
  | `b49b2e` | 5 | inherent limitation: `heading-quality` catches placeholder heading text (a generic word, a numbered template slot, a filename, a URL), which is the deterministic half of this rule; whether a well-formed heading actually describes the content after it is a reading-comprehension judgment, and all 5 of ACT's failed examples are exactly that shape ("Weather" over opening hours, across five different heading-naming mechanisms) |
92
+ | `4b1c6c` | 1 | env/harness limit: Failed Example 4 nests the second frame inside another frame's `srcdoc`, which jsdom does not load, so only one frame carries the shared name. `identical-iframes-same-purpose` never fails by design: frames embedding different resources may still be equivalent, so it reports `cantTell`, which counts as flagged |
93
93
  | `aizyf1` | 2 | inherent tension with `5effbb`'s own examples, not a bug: both remaining cases (`<a>this product</a>` after "See the description of", and a format-name list under an "Ulysses" heading) are ACT's own *passed* examples for `5effbb` (context-aware) but *failed* examples for `aizyf1` (context-blind: the accessible name alone, ignoring what makes it clear, must already be descriptive). One shared rule can credit context or not, not both on the same markup; `link-name-quality` sides with `5effbb`'s reading, which is what its context-detection is for |
94
94
  | `5effbb` | 1 | genuine judgment gap: the one remaining case (`<a>Workshop</a>` after an unrelated paragraph) needs to judge whether an ordinary word actually relates to nearby prose, not a phrase-list or context-structure question `link-name-quality` can answer |
95
95
  | `d0f69e` | 3 | documented false-negative policy: `table-th-has-data-cells`'s own header comment explains it only catches the unambiguous "zero data cells anywhere" case, not real positional header-association (the new ARIA-grid coverage added during this pass is real but doesn't happen to close these 3 specific positional-mismatch cases) |
@@ -130,6 +130,7 @@ Two rules changed what they report after this table was last regenerated, both f
130
130
  | `5b7ae0` | HTML page lang/xml:lang attributes match | `html-xml-lang-mismatch` | exact |
131
131
  | `bf051a` | HTML page lang attribute has valid language tag | `html-lang-attr-present` | exact |
132
132
  | `3ea0c8` | Id attribute value is unique | `duplicate-id` | exact |
133
+ | `4b1c6c` | Iframe elements with identical accessible names have equivalent purpose | `identical-iframes-same-purpose` | partial |
133
134
  | `cae760` | Iframe element has non-empty accessible name | `iframe-name-present` | exact |
134
135
  | `akn7bn` | Iframe with negative tabindex has no interactive content | `iframe-focusable-content` | exact |
135
136
  | `qt1vmo` | Image accessible name is descriptive | `img-alt-quality`, `canvas-text-alternative-quality`, `svg-text-alternative-quality` (all manual) | family |
package/docs/EARL.md CHANGED
@@ -72,7 +72,7 @@ Criterion ids are derived from the criterion's own title (`Non-text Content` →
72
72
 
73
73
  `earl:untested` has no counterpart: a rule that did not run produces no result to assert on, so it contributes no assertion rather than an untested one.
74
74
 
75
- **`cantTell` does not cost conformance credit.** ACT's own consistency rules allow an automated implementation to report "cannot tell" on some — though not all — examples and still count as consistent. What a partially consistent implementation may *not* do is produce a false positive: failing an example the rule says should pass, or that is inapplicable. That is the gate worth watching, and it is a property of the rules rather than of this reporter. As of the last full run the engine produces **zero false positives** across the 798 ACT examples covering its 58 matched rules — see [`ACT_RULE_MAPPING.md`](./ACT_RULE_MAPPING.md), and re-run `scripts/act-testcase-check.js` for the live figure rather than trusting this one indefinitely.
75
+ **`cantTell` does not cost conformance credit.** ACT's own consistency rules allow an automated implementation to report "cannot tell" on some — though not all — examples and still count as consistent. What a partially consistent implementation may *not* do is produce a false positive: failing an example the rule says should pass, or that is inapplicable. That is the gate worth watching, and it is a property of the rules rather than of this reporter. As of the last full run the engine produces **zero false positives** across the 821 ACT examples covering its 59 matched rules — see [`ACT_RULE_MAPPING.md`](./ACT_RULE_MAPPING.md), and re-run `scripts/act-testcase-check.js` for the live figure rather than trusting this one indefinitely.
76
76
 
77
77
  ## Several results, one report
78
78
 
@@ -490,7 +490,7 @@ Checks that a non-empty autocomplete attribute is "on"/"off" or a well-formed au
490
490
 
491
491
  **Applies to.** Applies to form controls (input, select, textarea) with a non-empty autocomplete attribute. Disabled controls (the disabled attribute, including a control disabled by a disabled fieldset ancestor, or aria-disabled="true") and input types with a fixed value are exempt, as in ACT 73f2c2.
492
492
 
493
- **Expectation.** The value is "on"/"off" alone, or a well-formed autofill detail token list: an optional "section-*" token, then an optional "shipping"/"billing" token, then an optional contact-modality token (home/work/mobile/fax/pager), then exactly one recognized field-name token (name, email, street-address, cc-number, tel, ...), optionally followed by "webauthn". The field name must also suit the control: the HTML Standard gives each field name a control group, and each group is allowed only on some input types (street-address only on textarea or select; email only on text, search or email inputs; and so on). A malformed or unsuitable value means the field is not reliably identified for assistive technology that relies on autocomplete to describe the expected input purpose.
493
+ **Expectation.** The value is "on"/"off" alone, or a well-formed autofill detail token list: an optional "section-*" token, then an optional "shipping"/"billing" token, then an optional contact-modality token (home/work/mobile/fax/pager), then exactly one recognized field-name token (name, email, street-address, cc-number, tel, ...), optionally followed by "webauthn". A malformed value means the field is not reliably identified for assistive technology that relies on autocomplete to describe the expected input purpose.
494
494
 
495
495
  ### `avoid-inline-spacing`
496
496
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@surea11y/core",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "description": "Deterministic WCAG 2.2 accessibility engine that tells you what it can't tell you. Zero dependencies.",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -20,13 +20,9 @@
20
20
  * "shipping"/"billing" token, then an optional contact-modality token
21
21
  * (home/work/mobile/fax/pager), then exactly one recognized
22
22
  * field-name token (name, email, street-address, cc-number, tel, ...),
23
- * optionally followed by "webauthn". The field name must also suit the
24
- * control: the HTML Standard gives each field name a control group, and
25
- * each group is allowed only on some input types (street-address only on
26
- * textarea or select; email only on text, search or email inputs; and so
27
- * on). A malformed or unsuitable value means the field is not reliably
28
- * identified for assistive technology that relies on autocomplete to
29
- * describe the expected input purpose.
23
+ * optionally followed by "webauthn". A malformed value means the field
24
+ * is not reliably identified for assistive technology that relies on
25
+ * autocomplete to describe the expected input purpose.
30
26
  * @implementation-notes
31
27
  * - Implements the structural shape of the WHATWG autofill grammar
32
28
  * (section/mode/contact-modality prefixes + one field-name token, in
@@ -36,14 +32,9 @@
36
32
  * this engine's established "scoped" precedent (see aria-helpers.js)
37
33
  * for keeping high-confidence fail without reimplementing the entire
38
34
  * spec.
39
- * - Control groups: textarea, select and input type=hidden accept every
40
- * group. input types text and search (and a missing or unknown type)
41
- * accept every group except Multiline (street-address). password, email,
42
- * url, tel, number, month and date inputs accept only their own group
43
- * (email also accepts username). Other input types (time, week,
44
- * datetime-local, range, color) are not checked for the group, as the
45
- * W3C validator does not check them either. A mismatch is reported with
46
- * reasonCode AUTOCOMPLETE_FIELD_CONTROL_MISMATCH.
35
+ * - A field name that does not suit its control (bday-day on type=tel)
36
+ * passes: browsers still derive the purpose from it, and ACT 73f2c2
37
+ * leaves contextual appropriateness out of scope.
47
38
  */
48
39
 
49
40
  const id = 'autocomplete-valid';
@@ -138,68 +129,10 @@ function runInPage(ctx) {
138
129
  ]);
139
130
  const CONTACT_MODALITY = new Set(['home', 'work', 'mobile', 'fax', 'pager']);
140
131
 
141
- // Control group of each field name that is not in the Text group (HTML
142
- // Standard, autofill field table).
143
- const FIELD_GROUP = {
144
- username: 'username',
145
- 'new-password': 'password',
146
- 'current-password': 'password',
147
- 'one-time-code': 'password',
148
- 'street-address': 'multiline',
149
- 'cc-exp': 'month',
150
- 'cc-exp-month': 'numeric',
151
- 'cc-exp-year': 'numeric',
152
- 'transaction-amount': 'numeric',
153
- bday: 'date',
154
- 'bday-day': 'numeric',
155
- 'bday-month': 'numeric',
156
- 'bday-year': 'numeric',
157
- url: 'url',
158
- photo: 'url',
159
- impp: 'url',
160
- tel: 'tel',
161
- email: 'email'
162
- };
163
- // Groups accepted by input types other than text and search. text and
164
- // search accept every group except multiline.
165
- const GROUPS_BY_INPUT_TYPE = {
166
- password: ['password'],
167
- email: ['email', 'username'],
168
- url: ['url'],
169
- tel: ['tel'],
170
- number: ['numeric'],
171
- month: ['month'],
172
- date: ['date']
173
- };
174
- const KNOWN_INPUT_TYPES = new Set([
175
- 'hidden',
176
- 'text',
177
- 'search',
178
- 'tel',
179
- 'url',
180
- 'email',
181
- 'password',
182
- 'date',
183
- 'month',
184
- 'week',
185
- 'time',
186
- 'datetime-local',
187
- 'number',
188
- 'range',
189
- 'color',
190
- 'checkbox',
191
- 'radio',
192
- 'file',
193
- 'submit',
194
- 'image',
195
- 'reset',
196
- 'button'
197
- ]);
198
-
199
- // Returns the field-name token of a well-formed value, or null.
200
- function getFieldName(raw) {
132
+ // True when the value is a well-formed autofill detail token list.
133
+ function isValidAutocomplete(raw) {
201
134
  const tokens = raw.trim().toLowerCase().split(/\s+/).filter(Boolean);
202
- if (!tokens.length) return null;
135
+ if (!tokens.length) return false;
203
136
 
204
137
  let i = 0;
205
138
  if (tokens[i] && tokens[i].startsWith('section-') && tokens[i].length > 'section-'.length)
@@ -211,7 +144,7 @@ function runInPage(ctx) {
211
144
  const next = tokens[i + 1];
212
145
  const isContactField =
213
146
  next === 'email' || next === 'impp' || next === 'tel' || (next || '').startsWith('tel-');
214
- if (!isContactField) return null;
147
+ if (!isContactField) return false;
215
148
  i += 1;
216
149
  }
217
150
 
@@ -219,24 +152,8 @@ function runInPage(ctx) {
219
152
  if (tokens[end - 1] === 'webauthn') end -= 1;
220
153
 
221
154
  const remaining = tokens.slice(i, end);
222
- if (remaining.length !== 1) return null;
223
- return FIELD_NAMES.has(remaining[0]) ? remaining[0] : null;
224
- }
225
-
226
- // True when the field name's control group is allowed on this control.
227
- function fieldSuitsControl(el, fieldName) {
228
- const tag = String(el.tagName || '').toLowerCase();
229
- if (tag !== 'input') return true;
230
- let type = String(el.getAttribute('type') || 'text')
231
- .trim()
232
- .toLowerCase();
233
- if (!KNOWN_INPUT_TYPES.has(type)) type = 'text';
234
- if (type === 'hidden') return true;
235
- const group = FIELD_GROUP[fieldName] || 'text';
236
- if (type === 'text' || type === 'search') return group !== 'multiline';
237
- const allowed = GROUPS_BY_INPUT_TYPE[type];
238
- if (!allowed) return true;
239
- return allowed.includes(group);
155
+ if (remaining.length !== 1) return false;
156
+ return FIELD_NAMES.has(remaining[0]);
240
157
  }
241
158
 
242
159
  const nodes = helpers.queryAllSmart
@@ -288,49 +205,21 @@ function runInPage(ctx) {
288
205
 
289
206
  applicableCount += 1;
290
207
 
291
- const fieldName = getFieldName(raw);
292
- const tag = el.tagName.toLowerCase();
293
-
294
- if (!fieldName) {
295
- occurrences.push(
296
- helpers.reportOccurrence(el, {
297
- summary: 'This autocomplete attribute value is not a valid autofill value.',
298
- hint: 'Use "on"/"off", or a valid autofill token list (e.g. "shipping postal-code", "cc-number").',
299
- i18n: {
300
- summaryKey: 'autocompleteValid_summary_fail',
301
- hintKey: 'autocompleteValid_hint_fail',
302
- params: { element: tag, value: raw }
303
- },
304
- data: {
305
- details: { reasonCode: 'AUTOCOMPLETE_VALUE_INVALID', element: tag, value: raw }
306
- }
307
- })
308
- );
309
- continue;
310
- }
208
+ if (isValidAutocomplete(raw)) continue;
311
209
 
312
- if (fieldSuitsControl(el, fieldName)) continue;
210
+ const tag = el.tagName.toLowerCase();
313
211
 
314
- const inputType = String(el.getAttribute('type') || 'text')
315
- .trim()
316
- .toLowerCase();
317
212
  occurrences.push(
318
213
  helpers.reportOccurrence(el, {
319
- summary: `The autofill field name "${fieldName}" is not allowed on an input of type "${inputType}".`,
320
- hint: 'Use a field name that suits this type of control, or change the control (street-address needs a textarea; email needs a text, search or email input; bday-day needs a text, search or number input).',
214
+ summary: 'This autocomplete attribute value is not a valid autofill value.',
215
+ hint: 'Use "on"/"off", or a valid autofill token list (e.g. "shipping postal-code", "cc-number").',
321
216
  i18n: {
322
- summaryKey: 'autocompleteValid_summary_mismatch',
323
- hintKey: 'autocompleteValid_hint_mismatch',
324
- params: { element: tag, value: raw, fieldName, inputType }
217
+ summaryKey: 'autocompleteValid_summary_fail',
218
+ hintKey: 'autocompleteValid_hint_fail',
219
+ params: { element: tag, value: raw }
325
220
  },
326
221
  data: {
327
- details: {
328
- reasonCode: 'AUTOCOMPLETE_FIELD_CONTROL_MISMATCH',
329
- element: tag,
330
- value: raw,
331
- fieldName,
332
- inputType
333
- }
222
+ details: { reasonCode: 'AUTOCOMPLETE_VALUE_INVALID', element: tag, value: raw }
334
223
  }
335
224
  })
336
225
  );
package/src/core.js CHANGED
@@ -11014,8 +11014,6 @@ const I18N = {
11014
11014
  "autocompleteValid_description": "Prüft, ob ein nicht leeres autocomplete-Attribut „on“/„off“ oder eine wohlgeformte Liste von Autofill-Detail-Tokens ist.",
11015
11015
  "autocompleteValid_summary_fail": "Dieser autocomplete-Attributwert ist kein gültiger Autofill-Wert.",
11016
11016
  "autocompleteValid_hint_fail": "Verwenden Sie „on“/„off“ oder eine gültige Liste von Autofill-Tokens (z. B. „shipping postal-code“, „cc-number“).",
11017
- "autocompleteValid_summary_mismatch": "Der Autofill-Feldname „{{fieldName}}“ ist auf einem input vom Typ „{{inputType}}“ nicht zulässig.",
11018
- "autocompleteValid_hint_mismatch": "Verwenden Sie einen Feldnamen, der zu diesem Steuerelementtyp passt, oder ändern Sie das Steuerelement (street-address erfordert ein textarea; email erfordert ein input vom Typ text, search oder email; bday-day erfordert ein input vom Typ text, search oder number).",
11019
11017
  "passwordPasteEnabled_title": "Authentifizierungsfelder dürfen das Einfügen nicht blockieren",
11020
11018
  "passwordPasteEnabled_description": "Prüft, dass ein Passwort- oder Einmalcode-Feld keinen Inline-Einfügehandler trägt, der den Vorgang abbricht und damit den Passwortmanager oder die Zwischenablage entfernt, auf die sich WCAG 3.3.8 als unterstützenden Mechanismus stützt.",
11021
11019
  "passwordPasteEnabled_summary_fail": "Dieses Authentifizierungsfeld hat einen Einfügehandler, dessen einzige Wirkung das Abbrechen des Vorgangs ist.",
@@ -11857,8 +11855,6 @@ const I18N = {
11857
11855
  "autocompleteValid_description": "Checks that a non-empty autocomplete attribute is \"on\"/\"off\" or a well-formed autofill detail token list.",
11858
11856
  "autocompleteValid_summary_fail": "This autocomplete attribute value is not a valid autofill value.",
11859
11857
  "autocompleteValid_hint_fail": "Use \"on\"/\"off\", or a valid autofill token list (e.g. \"shipping postal-code\", \"cc-number\").",
11860
- "autocompleteValid_summary_mismatch": "The autofill field name \"{{fieldName}}\" is not allowed on an input of type \"{{inputType}}\".",
11861
- "autocompleteValid_hint_mismatch": "Use a field name that suits this type of control, or change the control (street-address needs a textarea; email needs a text, search or email input; bday-day needs a text, search or number input).",
11862
11858
  "passwordPasteEnabled_title": "Authentication fields must not block pasting",
11863
11859
  "passwordPasteEnabled_description": "Checks that a password or one-time-code field carries no inline paste handler that cancels the paste, which would remove the password manager or clipboard that WCAG 3.3.8 relies on as the assisting mechanism.",
11864
11860
  "passwordPasteEnabled_summary_fail": "This authentication field has a paste handler whose only effect is to cancel the paste.",
@@ -12700,8 +12696,6 @@ const I18N = {
12700
12696
  "autocompleteValid_description": "Comprueba que un atributo autocomplete no vacío sea \"on\"/\"off\" o una lista de tokens de detalle de autocompletado bien formada.",
12701
12697
  "autocompleteValid_summary_fail": "Este valor del atributo autocomplete no es un valor de autocompletado válido.",
12702
12698
  "autocompleteValid_hint_fail": "Usar \"on\"/\"off\", o una lista de tokens de autocompletado válida (por ejemplo, \"shipping postal-code\", \"cc-number\").",
12703
- "autocompleteValid_summary_mismatch": "El nombre de campo de autocompletado \"{{fieldName}}\" no está permitido en un input de tipo \"{{inputType}}\".",
12704
- "autocompleteValid_hint_mismatch": "Usar un nombre de campo adecuado para este tipo de control, o cambiar el control (street-address requiere un textarea; email requiere un input de tipo text, search o email; bday-day requiere un input de tipo text, search o number).",
12705
12699
  "passwordPasteEnabled_title": "Los campos de autenticación no deben impedir pegar",
12706
12700
  "passwordPasteEnabled_description": "Comprueba que un campo de contraseña o de código de un solo uso no lleve un controlador de pegado en línea que cancele la acción, lo que eliminaría el gestor de contraseñas o el portapapeles en los que WCAG 3.3.8 se apoya como mecanismo de ayuda.",
12707
12701
  "passwordPasteEnabled_summary_fail": "Este campo de autenticación tiene un controlador de pegado cuyo único efecto es cancelar la acción.",
@@ -13543,8 +13537,6 @@ const I18N = {
13543
13537
  "autocompleteValid_description": "Vérifie qu’un attribut autocomplete non vide vaut « on »/« off » ou une liste de jetons d’auto-remplissage bien formée.",
13544
13538
  "autocompleteValid_summary_fail": "Cette valeur d’attribut autocomplete n’est pas une valeur d’auto-remplissage valide.",
13545
13539
  "autocompleteValid_hint_fail": "Utilisez « on »/« off », ou une liste de jetons d’auto-remplissage valide (ex. « shipping postal-code », « cc-number »).",
13546
- "autocompleteValid_summary_mismatch": "Le nom de champ d’auto-remplissage « {{fieldName}} » n’est pas autorisé sur un champ input de type « {{inputType}} ».",
13547
- "autocompleteValid_hint_mismatch": "Utilisez un nom de champ adapté à ce type de champ, ou changez le champ (street-address demande un textarea ; email demande un input de type text, search ou email ; bday-day demande un input de type text, search ou number).",
13548
13540
  "passwordPasteEnabled_title": "Les champs d'authentification ne doivent pas empêcher le collage",
13549
13541
  "passwordPasteEnabled_description": "Vérifie qu'un champ de mot de passe ou de code à usage unique ne porte pas de gestionnaire de collage en ligne qui annule l'action, ce qui supprimerait le gestionnaire de mots de passe ou le presse-papiers sur lesquels WCAG 3.3.8 s'appuie comme mécanisme d'aide.",
13550
13542
  "passwordPasteEnabled_summary_fail": "Ce champ d'authentification comporte un gestionnaire de collage dont le seul effet est d'annuler l'action.",
@@ -14386,8 +14378,6 @@ const I18N = {
14386
14378
  "autocompleteValid_description": "空でない autocomplete 属性の値が \"on\"/\"off\"、または正しい形式の自動入力の詳細トークンのリストであるかを確認します。",
14387
14379
  "autocompleteValid_summary_fail": "この autocomplete 属性の値は、有効な自動入力の値ではありません。",
14388
14380
  "autocompleteValid_hint_fail": "\"on\"/\"off\"、または有効な自動入力トークンのリスト (例: \"shipping postal-code\"、\"cc-number\") を使用してください。",
14389
- "autocompleteValid_summary_mismatch": "自動入力のフィールド名 \"{{fieldName}}\" は、type が \"{{inputType}}\" の input では使用できません。",
14390
- "autocompleteValid_hint_mismatch": "このコントロールの種類に合うフィールド名を使用するか、コントロールを変更してください (street-address には textarea、email には type が text、search、email の input、bday-day には type が text、search、number の input が必要です)。",
14391
14381
  "passwordPasteEnabled_title": "認証用のフィールドで貼り付けを禁止してはならない",
14392
14382
  "passwordPasteEnabled_description": "パスワードやワンタイムコードのフィールドに、貼り付けを取り消すインラインの paste ハンドラーがないかを確認します。貼り付けが禁止されると、WCAG 3.3.8 が支援の仕組みとして想定しているパスワードマネージャーやクリップボードが使えなくなります。",
14393
14383
  "passwordPasteEnabled_summary_fail": "この認証用フィールドには、貼り付けを取り消すことだけを行う paste ハンドラーがあります。",
@@ -40305,68 +40295,10 @@ function runa11yCoreInPage(pageUrl, contextSelector, engineOptions, runOnly) {
40305
40295
  ]);
40306
40296
  const CONTACT_MODALITY = new Set(['home', 'work', 'mobile', 'fax', 'pager']);
40307
40297
 
40308
- // Control group of each field name that is not in the Text group (HTML
40309
- // Standard, autofill field table).
40310
- const FIELD_GROUP = {
40311
- username: 'username',
40312
- 'new-password': 'password',
40313
- 'current-password': 'password',
40314
- 'one-time-code': 'password',
40315
- 'street-address': 'multiline',
40316
- 'cc-exp': 'month',
40317
- 'cc-exp-month': 'numeric',
40318
- 'cc-exp-year': 'numeric',
40319
- 'transaction-amount': 'numeric',
40320
- bday: 'date',
40321
- 'bday-day': 'numeric',
40322
- 'bday-month': 'numeric',
40323
- 'bday-year': 'numeric',
40324
- url: 'url',
40325
- photo: 'url',
40326
- impp: 'url',
40327
- tel: 'tel',
40328
- email: 'email'
40329
- };
40330
- // Groups accepted by input types other than text and search. text and
40331
- // search accept every group except multiline.
40332
- const GROUPS_BY_INPUT_TYPE = {
40333
- password: ['password'],
40334
- email: ['email', 'username'],
40335
- url: ['url'],
40336
- tel: ['tel'],
40337
- number: ['numeric'],
40338
- month: ['month'],
40339
- date: ['date']
40340
- };
40341
- const KNOWN_INPUT_TYPES = new Set([
40342
- 'hidden',
40343
- 'text',
40344
- 'search',
40345
- 'tel',
40346
- 'url',
40347
- 'email',
40348
- 'password',
40349
- 'date',
40350
- 'month',
40351
- 'week',
40352
- 'time',
40353
- 'datetime-local',
40354
- 'number',
40355
- 'range',
40356
- 'color',
40357
- 'checkbox',
40358
- 'radio',
40359
- 'file',
40360
- 'submit',
40361
- 'image',
40362
- 'reset',
40363
- 'button'
40364
- ]);
40365
-
40366
- // Returns the field-name token of a well-formed value, or null.
40367
- function getFieldName(raw) {
40298
+ // True when the value is a well-formed autofill detail token list.
40299
+ function isValidAutocomplete(raw) {
40368
40300
  const tokens = raw.trim().toLowerCase().split(/\s+/).filter(Boolean);
40369
- if (!tokens.length) return null;
40301
+ if (!tokens.length) return false;
40370
40302
 
40371
40303
  let i = 0;
40372
40304
  if (tokens[i] && tokens[i].startsWith('section-') && tokens[i].length > 'section-'.length)
@@ -40378,7 +40310,7 @@ function runa11yCoreInPage(pageUrl, contextSelector, engineOptions, runOnly) {
40378
40310
  const next = tokens[i + 1];
40379
40311
  const isContactField =
40380
40312
  next === 'email' || next === 'impp' || next === 'tel' || (next || '').startsWith('tel-');
40381
- if (!isContactField) return null;
40313
+ if (!isContactField) return false;
40382
40314
  i += 1;
40383
40315
  }
40384
40316
 
@@ -40386,24 +40318,8 @@ function runa11yCoreInPage(pageUrl, contextSelector, engineOptions, runOnly) {
40386
40318
  if (tokens[end - 1] === 'webauthn') end -= 1;
40387
40319
 
40388
40320
  const remaining = tokens.slice(i, end);
40389
- if (remaining.length !== 1) return null;
40390
- return FIELD_NAMES.has(remaining[0]) ? remaining[0] : null;
40391
- }
40392
-
40393
- // True when the field name's control group is allowed on this control.
40394
- function fieldSuitsControl(el, fieldName) {
40395
- const tag = String(el.tagName || '').toLowerCase();
40396
- if (tag !== 'input') return true;
40397
- let type = String(el.getAttribute('type') || 'text')
40398
- .trim()
40399
- .toLowerCase();
40400
- if (!KNOWN_INPUT_TYPES.has(type)) type = 'text';
40401
- if (type === 'hidden') return true;
40402
- const group = FIELD_GROUP[fieldName] || 'text';
40403
- if (type === 'text' || type === 'search') return group !== 'multiline';
40404
- const allowed = GROUPS_BY_INPUT_TYPE[type];
40405
- if (!allowed) return true;
40406
- return allowed.includes(group);
40321
+ if (remaining.length !== 1) return false;
40322
+ return FIELD_NAMES.has(remaining[0]);
40407
40323
  }
40408
40324
 
40409
40325
  const nodes = helpers.queryAllSmart
@@ -40455,49 +40371,21 @@ function runa11yCoreInPage(pageUrl, contextSelector, engineOptions, runOnly) {
40455
40371
 
40456
40372
  applicableCount += 1;
40457
40373
 
40458
- const fieldName = getFieldName(raw);
40459
- const tag = el.tagName.toLowerCase();
40374
+ if (isValidAutocomplete(raw)) continue;
40460
40375
 
40461
- if (!fieldName) {
40462
- occurrences.push(
40463
- helpers.reportOccurrence(el, {
40464
- summary: 'This autocomplete attribute value is not a valid autofill value.',
40465
- hint: 'Use "on"/"off", or a valid autofill token list (e.g. "shipping postal-code", "cc-number").',
40466
- i18n: {
40467
- summaryKey: 'autocompleteValid_summary_fail',
40468
- hintKey: 'autocompleteValid_hint_fail',
40469
- params: { element: tag, value: raw }
40470
- },
40471
- data: {
40472
- details: { reasonCode: 'AUTOCOMPLETE_VALUE_INVALID', element: tag, value: raw }
40473
- }
40474
- })
40475
- );
40476
- continue;
40477
- }
40478
-
40479
- if (fieldSuitsControl(el, fieldName)) continue;
40376
+ const tag = el.tagName.toLowerCase();
40480
40377
 
40481
- const inputType = String(el.getAttribute('type') || 'text')
40482
- .trim()
40483
- .toLowerCase();
40484
40378
  occurrences.push(
40485
40379
  helpers.reportOccurrence(el, {
40486
- summary: `The autofill field name "${fieldName}" is not allowed on an input of type "${inputType}".`,
40487
- hint: 'Use a field name that suits this type of control, or change the control (street-address needs a textarea; email needs a text, search or email input; bday-day needs a text, search or number input).',
40380
+ summary: 'This autocomplete attribute value is not a valid autofill value.',
40381
+ hint: 'Use "on"/"off", or a valid autofill token list (e.g. "shipping postal-code", "cc-number").',
40488
40382
  i18n: {
40489
- summaryKey: 'autocompleteValid_summary_mismatch',
40490
- hintKey: 'autocompleteValid_hint_mismatch',
40491
- params: { element: tag, value: raw, fieldName, inputType }
40383
+ summaryKey: 'autocompleteValid_summary_fail',
40384
+ hintKey: 'autocompleteValid_hint_fail',
40385
+ params: { element: tag, value: raw }
40492
40386
  },
40493
40387
  data: {
40494
- details: {
40495
- reasonCode: 'AUTOCOMPLETE_FIELD_CONTROL_MISMATCH',
40496
- element: tag,
40497
- value: raw,
40498
- fieldName,
40499
- inputType
40500
- }
40388
+ details: { reasonCode: 'AUTOCOMPLETE_VALUE_INVALID', element: tag, value: raw }
40501
40389
  }
40502
40390
  })
40503
40391
  );
@@ -63059,8 +62947,6 @@ const I18N = {
63059
62947
  "autocompleteValid_description": "Prüft, ob ein nicht leeres autocomplete-Attribut „on“/„off“ oder eine wohlgeformte Liste von Autofill-Detail-Tokens ist.",
63060
62948
  "autocompleteValid_summary_fail": "Dieser autocomplete-Attributwert ist kein gültiger Autofill-Wert.",
63061
62949
  "autocompleteValid_hint_fail": "Verwenden Sie „on“/„off“ oder eine gültige Liste von Autofill-Tokens (z. B. „shipping postal-code“, „cc-number“).",
63062
- "autocompleteValid_summary_mismatch": "Der Autofill-Feldname „{{fieldName}}“ ist auf einem input vom Typ „{{inputType}}“ nicht zulässig.",
63063
- "autocompleteValid_hint_mismatch": "Verwenden Sie einen Feldnamen, der zu diesem Steuerelementtyp passt, oder ändern Sie das Steuerelement (street-address erfordert ein textarea; email erfordert ein input vom Typ text, search oder email; bday-day erfordert ein input vom Typ text, search oder number).",
63064
62950
  "passwordPasteEnabled_title": "Authentifizierungsfelder dürfen das Einfügen nicht blockieren",
63065
62951
  "passwordPasteEnabled_description": "Prüft, dass ein Passwort- oder Einmalcode-Feld keinen Inline-Einfügehandler trägt, der den Vorgang abbricht und damit den Passwortmanager oder die Zwischenablage entfernt, auf die sich WCAG 3.3.8 als unterstützenden Mechanismus stützt.",
63066
62952
  "passwordPasteEnabled_summary_fail": "Dieses Authentifizierungsfeld hat einen Einfügehandler, dessen einzige Wirkung das Abbrechen des Vorgangs ist.",
@@ -63902,8 +63788,6 @@ const I18N = {
63902
63788
  "autocompleteValid_description": "Checks that a non-empty autocomplete attribute is \"on\"/\"off\" or a well-formed autofill detail token list.",
63903
63789
  "autocompleteValid_summary_fail": "This autocomplete attribute value is not a valid autofill value.",
63904
63790
  "autocompleteValid_hint_fail": "Use \"on\"/\"off\", or a valid autofill token list (e.g. \"shipping postal-code\", \"cc-number\").",
63905
- "autocompleteValid_summary_mismatch": "The autofill field name \"{{fieldName}}\" is not allowed on an input of type \"{{inputType}}\".",
63906
- "autocompleteValid_hint_mismatch": "Use a field name that suits this type of control, or change the control (street-address needs a textarea; email needs a text, search or email input; bday-day needs a text, search or number input).",
63907
63791
  "passwordPasteEnabled_title": "Authentication fields must not block pasting",
63908
63792
  "passwordPasteEnabled_description": "Checks that a password or one-time-code field carries no inline paste handler that cancels the paste, which would remove the password manager or clipboard that WCAG 3.3.8 relies on as the assisting mechanism.",
63909
63793
  "passwordPasteEnabled_summary_fail": "This authentication field has a paste handler whose only effect is to cancel the paste.",
@@ -64745,8 +64629,6 @@ const I18N = {
64745
64629
  "autocompleteValid_description": "Comprueba que un atributo autocomplete no vacío sea \"on\"/\"off\" o una lista de tokens de detalle de autocompletado bien formada.",
64746
64630
  "autocompleteValid_summary_fail": "Este valor del atributo autocomplete no es un valor de autocompletado válido.",
64747
64631
  "autocompleteValid_hint_fail": "Usar \"on\"/\"off\", o una lista de tokens de autocompletado válida (por ejemplo, \"shipping postal-code\", \"cc-number\").",
64748
- "autocompleteValid_summary_mismatch": "El nombre de campo de autocompletado \"{{fieldName}}\" no está permitido en un input de tipo \"{{inputType}}\".",
64749
- "autocompleteValid_hint_mismatch": "Usar un nombre de campo adecuado para este tipo de control, o cambiar el control (street-address requiere un textarea; email requiere un input de tipo text, search o email; bday-day requiere un input de tipo text, search o number).",
64750
64632
  "passwordPasteEnabled_title": "Los campos de autenticación no deben impedir pegar",
64751
64633
  "passwordPasteEnabled_description": "Comprueba que un campo de contraseña o de código de un solo uso no lleve un controlador de pegado en línea que cancele la acción, lo que eliminaría el gestor de contraseñas o el portapapeles en los que WCAG 3.3.8 se apoya como mecanismo de ayuda.",
64752
64634
  "passwordPasteEnabled_summary_fail": "Este campo de autenticación tiene un controlador de pegado cuyo único efecto es cancelar la acción.",
@@ -65588,8 +65470,6 @@ const I18N = {
65588
65470
  "autocompleteValid_description": "Vérifie qu’un attribut autocomplete non vide vaut « on »/« off » ou une liste de jetons d’auto-remplissage bien formée.",
65589
65471
  "autocompleteValid_summary_fail": "Cette valeur d’attribut autocomplete n’est pas une valeur d’auto-remplissage valide.",
65590
65472
  "autocompleteValid_hint_fail": "Utilisez « on »/« off », ou une liste de jetons d’auto-remplissage valide (ex. « shipping postal-code », « cc-number »).",
65591
- "autocompleteValid_summary_mismatch": "Le nom de champ d’auto-remplissage « {{fieldName}} » n’est pas autorisé sur un champ input de type « {{inputType}} ».",
65592
- "autocompleteValid_hint_mismatch": "Utilisez un nom de champ adapté à ce type de champ, ou changez le champ (street-address demande un textarea ; email demande un input de type text, search ou email ; bday-day demande un input de type text, search ou number).",
65593
65473
  "passwordPasteEnabled_title": "Les champs d'authentification ne doivent pas empêcher le collage",
65594
65474
  "passwordPasteEnabled_description": "Vérifie qu'un champ de mot de passe ou de code à usage unique ne porte pas de gestionnaire de collage en ligne qui annule l'action, ce qui supprimerait le gestionnaire de mots de passe ou le presse-papiers sur lesquels WCAG 3.3.8 s'appuie comme mécanisme d'aide.",
65595
65475
  "passwordPasteEnabled_summary_fail": "Ce champ d'authentification comporte un gestionnaire de collage dont le seul effet est d'annuler l'action.",
@@ -66431,8 +66311,6 @@ const I18N = {
66431
66311
  "autocompleteValid_description": "空でない autocomplete 属性の値が \"on\"/\"off\"、または正しい形式の自動入力の詳細トークンのリストであるかを確認します。",
66432
66312
  "autocompleteValid_summary_fail": "この autocomplete 属性の値は、有効な自動入力の値ではありません。",
66433
66313
  "autocompleteValid_hint_fail": "\"on\"/\"off\"、または有効な自動入力トークンのリスト (例: \"shipping postal-code\"、\"cc-number\") を使用してください。",
66434
- "autocompleteValid_summary_mismatch": "自動入力のフィールド名 \"{{fieldName}}\" は、type が \"{{inputType}}\" の input では使用できません。",
66435
- "autocompleteValid_hint_mismatch": "このコントロールの種類に合うフィールド名を使用するか、コントロールを変更してください (street-address には textarea、email には type が text、search、email の input、bday-day には type が text、search、number の input が必要です)。",
66436
66314
  "passwordPasteEnabled_title": "認証用のフィールドで貼り付けを禁止してはならない",
66437
66315
  "passwordPasteEnabled_description": "パスワードやワンタイムコードのフィールドに、貼り付けを取り消すインラインの paste ハンドラーがないかを確認します。貼り付けが禁止されると、WCAG 3.3.8 が支援の仕組みとして想定しているパスワードマネージャーやクリップボードが使えなくなります。",
66438
66316
  "passwordPasteEnabled_summary_fail": "この認証用フィールドには、貼り付けを取り消すことだけを行う paste ハンドラーがあります。",