@markuplint/rules 5.2.0 → 5.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 +48 -0
- package/CLAUDE.md +19 -19
- package/lib/aria-prop-requires-role/index.js +2 -1
- package/lib/attr-check.js +6 -12
- package/lib/attr-eligibility.d.ts +15 -8
- package/lib/attr-eligibility.js +19 -10
- package/lib/attr-order/index.js +24 -18
- package/lib/case-sensitive-attr-name/index.js +24 -5
- package/lib/class-naming/index.js +6 -0
- package/lib/debug.d.ts +8 -1
- package/lib/debug.js +8 -1
- package/lib/element-supports-aria-prop/index.js +2 -1
- package/lib/form-attr-references-form/index.d.ts +6 -3
- package/lib/form-attr-references-form/index.js +6 -3
- package/lib/helpers.d.ts +111 -1
- package/lib/helpers.js +212 -2
- package/lib/input-list-references-datalist/index.d.ts +8 -6
- package/lib/input-list-references-datalist/index.js +8 -6
- package/lib/label-for-references-labelable/index.d.ts +8 -5
- package/lib/label-for-references-labelable/index.js +8 -5
- package/lib/landmark-roles/roles.js +14 -2
- package/lib/map-id-name-match/index.d.ts +3 -3
- package/lib/map-id-name-match/index.js +3 -3
- package/lib/no-ambiguous-navigable-target-names/index.js +2 -1
- package/lib/no-aria-hidden-on-hidden-until-found/index.d.ts +9 -6
- package/lib/no-aria-hidden-on-hidden-until-found/index.js +9 -6
- package/lib/no-aria-hidden-on-hidden-until-found/meta.d.ts +1 -1
- package/lib/no-aria-hidden-on-hidden-until-found/meta.js +1 -1
- package/lib/no-aria-on-presentational-children/index.js +3 -1
- package/lib/no-aria-on-unsupported-element/index.js +2 -2
- package/lib/no-aria-on-unsupported-element/meta.d.ts +1 -1
- package/lib/no-aria-on-unsupported-element/meta.js +1 -1
- package/lib/no-boolean-attr-value/index.js +2 -2
- package/lib/no-content-after-body/index.d.ts +2 -2
- package/lib/no-content-after-body/index.js +2 -2
- package/lib/no-contradictory-aria-prop/index.js +2 -1
- package/lib/no-default-aria-value/index.js +2 -1
- package/lib/no-default-value/index.js +2 -2
- package/lib/no-deprecated-aria-prop/index.js +2 -1
- package/lib/no-deprecated-attr/index.js +2 -1
- package/lib/no-disallowed-attr/index.d.ts +7 -1
- package/lib/no-disallowed-attr/index.js +13 -20
- package/lib/no-duplicate-attr/index.js +1 -2
- package/lib/no-duplicate-autofocus/index.js +2 -2
- package/lib/no-duplicate-h1/index.d.ts +1 -1
- package/lib/no-duplicate-h1/index.js +1 -1
- package/lib/no-empty-table-track/index.d.ts +6 -2
- package/lib/no-empty-table-track/index.js +6 -2
- package/lib/no-experimental-features/index.d.ts +5 -1
- package/lib/no-experimental-features/index.js +6 -8
- package/lib/no-hardcoded-id/index.js +1 -1
- package/lib/no-ineffective-attr/index.js +2 -2
- package/lib/no-input-file-value/index.d.ts +10 -4
- package/lib/no-input-file-value/index.js +10 -4
- package/lib/no-invalid-aria-prop-value/index.js +2 -1
- package/lib/no-invalid-attr-value/index.d.ts +3 -0
- package/lib/no-invalid-attr-value/index.js +9 -24
- package/lib/no-nonstandard-features/index.d.ts +5 -1
- package/lib/no-nonstandard-features/index.js +6 -8
- package/lib/no-obsolete-attr/index.js +3 -3
- package/lib/no-prohibited-naming/index.js +2 -1
- package/lib/no-redundant-accessible-name/index.js +17 -1
- package/lib/no-redundant-aria-prop/index.js +2 -1
- package/lib/no-refer-to-non-existent-id/index.js +4 -4
- package/lib/no-restricted-attr/index.d.ts +4 -0
- package/lib/no-restricted-attr/index.js +5 -22
- package/lib/no-stray-head-or-body-tag/index.d.ts +6 -4
- package/lib/no-stray-head-or-body-tag/index.js +11 -6
- package/lib/no-unknown-attr/index.d.ts +7 -1
- package/lib/no-unknown-attr/index.js +11 -25
- package/lib/no-unpaired-srcset-sizes/index.d.ts +3 -2
- package/lib/no-unpaired-srcset-sizes/index.js +23 -17
- package/lib/no-unsupported-browser-features/compat-data.d.ts +15 -2
- package/lib/no-unsupported-browser-features/compat-data.js +48 -3
- package/lib/no-unsupported-browser-features/index.d.ts +5 -1
- package/lib/no-unsupported-browser-features/index.js +6 -7
- package/lib/permitted-contents/choice.js +19 -8
- package/lib/permitted-contents/content-model.d.ts +24 -1
- package/lib/permitted-contents/content-model.js +56 -9
- package/lib/permitted-contents/count-pattern.js +36 -13
- package/lib/permitted-contents/debug.browser.d.ts +18 -0
- package/lib/permitted-contents/debug.browser.js +27 -0
- package/lib/permitted-contents/debug.d.ts +18 -0
- package/lib/permitted-contents/debug.js +27 -0
- package/lib/permitted-contents/index.js +2 -2
- package/lib/permitted-contents/matches-selector.js +40 -18
- package/lib/permitted-contents/order.js +23 -11
- package/lib/permitted-contents/represent-transparent-nodes.js +3 -1
- package/lib/permitted-contents/transparent.js +6 -2
- package/lib/placeholder-label-option/index.d.ts +3 -3
- package/lib/placeholder-label-option/index.js +163 -37
- package/lib/progress-value-bounds/index.d.ts +1 -1
- package/lib/progress-value-bounds/index.js +1 -1
- package/lib/require-attr/index.d.ts +6 -1
- package/lib/require-attr/index.js +13 -5
- package/lib/require-h1/index.d.ts +1 -1
- package/lib/require-h1/index.js +1 -1
- package/lib/role-supports-aria-prop/index.js +2 -1
- package/lib/sizes-auto-requires-lazy-loading/index.d.ts +3 -1
- package/lib/sizes-auto-requires-lazy-loading/index.js +30 -16
- package/lib/srcset-sizes-constraint/parse-srcset.d.ts +6 -5
- package/lib/srcset-sizes-constraint/parse-srcset.js +8 -7
- package/lib/srcset-sizes-constraint/sibling-helpers.d.ts +31 -0
- package/lib/srcset-sizes-constraint/sibling-helpers.js +58 -0
- package/lib/valid-speculation-rules/index.js +1 -1
- package/lib/wai-aria/checkings/contradictory-aria-prop.js +3 -5
- package/lib/wai-aria/checkings/default-value.js +1 -1
- package/lib/wai-aria/checkings/deprecated-props.js +2 -2
- package/lib/wai-aria/checkings/element-supports-aria-prop.js +5 -4
- package/lib/wai-aria/checkings/interaction-in-hidden.d.ts +1 -1
- package/lib/wai-aria/checkings/interaction-in-hidden.js +1 -1
- package/lib/wai-aria/checkings/no-global-prop.js +1 -1
- package/lib/wai-aria/checkings/presentational-children.d.ts +21 -10
- package/lib/wai-aria/checkings/presentational-children.js +240 -26
- package/lib/wai-aria/checkings/prohibited-naming.js +5 -4
- package/lib/wai-aria/checkings/redundant-aria-prop.js +3 -5
- package/lib/wai-aria/checkings/required-prop.js +1 -4
- package/lib/wai-aria/checkings/role-supports-aria-prop.js +4 -3
- package/lib/wai-aria/checkings/tab-requires-tabpanel.d.ts +3 -2
- package/lib/wai-aria/checkings/tab-requires-tabpanel.js +3 -2
- package/lib/wai-aria/checkings/value.d.ts +1 -1
- package/lib/wai-aria/checkings/value.js +2 -2
- package/package.json +15 -15
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,54 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [5.4.0](https://github.com/markuplint/markuplint/compare/v5.3.0...v5.4.0) (2026-10-29)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **rules:** do not report descendant attributes that take part in a text alternative ([46002a7](https://github.com/markuplint/markuplint/commit/46002a75e06f877938d134ce0958a625e5d989d3)), closes [#4408](https://github.com/markuplint/markuplint/issues/4408)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# [5.3.0](https://github.com/markuplint/markuplint/compare/v5.2.0...v5.3.0) (2026-10-21)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **github:** retry unreachable spec pages before failing the check ([02c7d35](https://github.com/markuplint/markuplint/commit/02c7d353037f9cf0c04bb11aa3c5827c129a7231))
|
|
18
|
+
* make published packages loadable from require() and put types first ([723106f](https://github.com/markuplint/markuplint/commit/723106f51aac4e60dc02db2c1a894617368b0253)), closes [#4198](https://github.com/markuplint/markuplint/issues/4198)
|
|
19
|
+
* **rules:** accept a pretender that stands for a component in permitted-contents ([0efc2c9](https://github.com/markuplint/markuplint/commit/0efc2c9b2ef423a9b9e72128f3d6e2eb4ba4dd22))
|
|
20
|
+
* **rules:** apply a tag rule of permitted-contents to every spelling of a Vue component ([f3f96dc](https://github.com/markuplint/markuplint/commit/f3f96dcff5cbd514b3aeb55fc387297fa7af6f5a))
|
|
21
|
+
* **rules:** apply the conditional contents of a tag rule to the component of a pretender ([df96f58](https://github.com/markuplint/markuplint/commit/df96f58af9be0ee20ca04f3cc22d17a681ba674e)), closes [#4352](https://github.com/markuplint/markuplint/issues/4352)
|
|
22
|
+
* **rules:** check upper-case ARIA attributes as the DOM names them ([29eaf3b](https://github.com/markuplint/markuplint/commit/29eaf3bfbfd40dc74977f0940a83e6f90eb7ddc6))
|
|
23
|
+
* **rules:** compare attribute names as the DOM does in the rules that list attributes ([51faee6](https://github.com/markuplint/markuplint/commit/51faee6f13a8f09f136438873991f0581071012a))
|
|
24
|
+
* **rules:** do not count a self-referencing aria-labelledby as a naming source ([84efeb1](https://github.com/markuplint/markuplint/commit/84efeb136260bcf0dd7161b38d69a9fadccb7834)), closes [#4217](https://github.com/markuplint/markuplint/issues/4217)
|
|
25
|
+
* **rules:** do not report a placeholder option whose value is not known ([edfc526](https://github.com/markuplint/markuplint/commit/edfc5262a9e784ed80ec0d1a5000f7ed55b94583)), closes [#32](https://github.com/markuplint/markuplint/issues/32)
|
|
26
|
+
* **rules:** do not translate the suggested property name in no-unknown-attr ([bb22ae2](https://github.com/markuplint/markuplint/commit/bb22ae2e4a1ac5ef9b36e922892e8f608e363940))
|
|
27
|
+
* **rules:** find a compat key by the name the DOM gives it, in any spelling ([a5b286b](https://github.com/markuplint/markuplint/commit/a5b286b57afba6ef7a33073e650b983df1d8cc46))
|
|
28
|
+
* **rules:** fix case-sensitive attribute lookup, duplicate landmarks and raw text scan ([a98b850](https://github.com/markuplint/markuplint/commit/a98b850cd64f4a277e4fd65ff5b7bb20c170d38b)), closes [#4180](https://github.com/markuplint/markuplint/issues/4180)
|
|
29
|
+
* **rules:** follow the spec's definition of the placeholder label option ([4797eec](https://github.com/markuplint/markuplint/commit/4797eec2f2b7a45d3ffdb251f2401f977327725a))
|
|
30
|
+
* **rules:** judge loading and sizes of the img the same way in both directions ([9cdab16](https://github.com/markuplint/markuplint/commit/9cdab1618e923f6271dea95998c42fbe260848b5))
|
|
31
|
+
* **rules:** keep ranking the other sources of a self-referencing aria-labelledby element ([33563fb](https://github.com/markuplint/markuplint/commit/33563fbc76e46d26562e230bec9d378a8d564e1b)), closes [#4251](https://github.com/markuplint/markuplint/issues/4251) [#4251](https://github.com/markuplint/markuplint/issues/4251) [#4250](https://github.com/markuplint/markuplint/issues/4250) [#4217](https://github.com/markuplint/markuplint/issues/4217)
|
|
32
|
+
* **rules:** look attributes up in the spec as the DOM names them ([149d3fd](https://github.com/markuplint/markuplint/commit/149d3fd63b762ff334307977ec3059dc839ccbc3))
|
|
33
|
+
* **rules:** name the spelling in the case-sensitive-attr-name report on foreign elements ([b63ebf4](https://github.com/markuplint/markuplint/commit/b63ebf4c0de648428dbf72220dda321e594b8728))
|
|
34
|
+
* **rules:** order attributes and match ignoreFeatures keys as the DOM names them ([f955369](https://github.com/markuplint/markuplint/commit/f955369505bb3a7fc739c99cf8b32827a4e10e46))
|
|
35
|
+
* **rules:** recognize sizes="auto" the way the spec defines it ([91cfae3](https://github.com/markuplint/markuplint/commit/91cfae3ef8fccc7d7139a3c523d9f7f1bc99c386))
|
|
36
|
+
* **rules:** require the img after a source to allow auto-sizes, not only to load lazily ([9774217](https://github.com/markuplint/markuplint/commit/977421797466a13028f59228c8b40a521f3a6e82))
|
|
37
|
+
* **rules:** skip case-sensitive-attr-name for case-sensitive languages ([238f011](https://github.com/markuplint/markuplint/commit/238f011b8393644057a6258cc5444101f3d18deb)), closes [#4229](https://github.com/markuplint/markuplint/issues/4229)
|
|
38
|
+
* **rules:** skip class-naming when no pattern is configured ([7387c16](https://github.com/markuplint/markuplint/commit/7387c167dbb0a22e480924b556e1fae62635ffc4)), closes [#4202](https://github.com/markuplint/markuplint/issues/4202)
|
|
39
|
+
* **rules:** skip no-redundant-accessible-name for a self-referencing aria-labelledby with aria-label ([a46715f](https://github.com/markuplint/markuplint/commit/a46715f0c46283709274cbf16d5484089236dec1)), closes [#4217](https://github.com/markuplint/markuplint/issues/4217) [#4250](https://github.com/markuplint/markuplint/issues/4250) [#4217](https://github.com/markuplint/markuplint/issues/4217)
|
|
40
|
+
* **rules:** sort the props of a component in attr-order by the name as written ([a2d3b48](https://github.com/markuplint/markuplint/commit/a2d3b48c810b5800dbf718975d4758461656b588))
|
|
41
|
+
* **rules:** tell the namespace of a component's prop by the name as written ([157f434](https://github.com/markuplint/markuplint/commit/157f434b9dc4bb0f51c3fb77b219f38ef4035dd8))
|
|
42
|
+
* **selector:** match a prefixed attribute by its local name as well ([a9beb11](https://github.com/markuplint/markuplint/commit/a9beb112b87bf9ade372ede0fe0d7958d05027fc))
|
|
43
|
+
* supersede no-input-file-value with no-disallowed-attr ([2450fa1](https://github.com/markuplint/markuplint/commit/2450fa11c6cd0c32271ccfaed237fbd3b4b31953))
|
|
44
|
+
|
|
45
|
+
### Features
|
|
46
|
+
|
|
47
|
+
* **github:** watch the spec passages that rules cite ([c0203d1](https://github.com/markuplint/markuplint/commit/c0203d14d9db3168d9720375c08edaeed2691aa0))
|
|
48
|
+
|
|
49
|
+
### Performance Improvements
|
|
50
|
+
|
|
51
|
+
* **rules:** guard debug logging in permitted-contents and attribute checks ([db3281e](https://github.com/markuplint/markuplint/commit/db3281e048cdecfae6a51cb50b3bfb2f19a73e5c))
|
|
52
|
+
|
|
53
|
+
|
|
6
54
|
# [5.2.0](https://github.com/markuplint/markuplint/compare/v5.1.1...v5.2.0) (2026-10-13)
|
|
7
55
|
|
|
8
56
|
### Bug Fixes
|
package/CLAUDE.md
CHANGED
|
@@ -21,34 +21,25 @@ Repo-wide meta specs directly under `src/` (e.g. `mirrors-parse-error-codes.spec
|
|
|
21
21
|
|
|
22
22
|
### Categories
|
|
23
23
|
|
|
24
|
-
| Category | When to use
|
|
25
|
-
| ------------ |
|
|
26
|
-
| `valid` | Test expects 0 violations
|
|
27
|
-
| `invalid` | Test expects 1+ violations
|
|
28
|
-
| `fix` | Test checks `fixedCode` or auto-fix behavior
|
|
29
|
-
| `parser` | Test uses a non-default parser (Vue, React, Pug, etc.)
|
|
30
|
-
| `
|
|
24
|
+
| Category | When to use |
|
|
25
|
+
| ------------ | ----------------------------------------------------------------- |
|
|
26
|
+
| `valid` | Test expects 0 violations |
|
|
27
|
+
| `invalid` | Test expects 1+ violations |
|
|
28
|
+
| `fix` | Test checks `fixedCode` or auto-fix behavior |
|
|
29
|
+
| `parser` | Test uses a non-default parser (Vue, React, Pug, etc.) |
|
|
30
|
+
| `debug` | Test checks only debug-logging behavior (not `valid` / `invalid`) |
|
|
31
|
+
| `issue-NNNN` | Regression test for a GitHub issue (use the issue number) |
|
|
31
32
|
|
|
32
33
|
### Rules
|
|
33
34
|
|
|
34
35
|
- IDs go on `test()` blocks only (not `describe()`)
|
|
35
36
|
- Numbers are sequential per category per file, starting at `001`
|
|
36
37
|
- Multiple tests for the same issue: `[rule-name-issue-NNNN-001]`, `[rule-name-issue-NNNN-002]`
|
|
38
|
+
- The number is that of the issue whose behavior the test guards. A test added later for that behavior — by an issue that only asks for tests, or with a fix found while writing them — continues its numbers, next to its earlier tests
|
|
37
39
|
- When adding a new test, assign the next available number in the appropriate category
|
|
40
|
+
- An existing test whose assertion was flipped keeps its ID, category included (`.claude/rules/spec-files.md`)
|
|
38
41
|
- Run `/list-rule-test` (or `node .claude/skills/list-rule-test/scripts/list-rule-test.mjs --no-id`) to check for missing IDs — the only expected hits are the exempt meta specs noted above
|
|
39
42
|
|
|
40
|
-
## Sample identifiers in specs
|
|
41
|
-
|
|
42
|
-
<!-- cspell:ignore myform mymap -->
|
|
43
|
-
|
|
44
|
-
Sample `id` / `name` values in test markup must be kebab-case (`my-form`), suffixed with a digit
|
|
45
|
-
(`form1`), or a plain dictionary word (`target`). Never a camelCase coinage (`myform`, `mymap`,
|
|
46
|
-
`targetEl`): CSpell decomposes compound words only when every part is at least three characters,
|
|
47
|
-
so a two-letter prefix glued to a word is looked up whole, misses the dictionary, and fails the
|
|
48
|
-
lint — on the `dev` line the same one-line fix has been made in four separate PRs (#3826, #3831,
|
|
49
|
-
#3833, #3932). The v6 line has no CSpell, so a coinage introduced there surfaces only once the
|
|
50
|
-
branch merges somewhere that does.
|
|
51
|
-
|
|
52
43
|
## Writing a rule's README and `schema.json` description
|
|
53
44
|
|
|
54
45
|
Both files feed the website build (`website/scripts/prebuild/rule-content.mts` injects the
|
|
@@ -72,6 +63,15 @@ turning the incorrect example into a copy of the correct one. Apply it to `READM
|
|
|
72
63
|
The CI website check only runs when a PR already touches `website/`, so a new rule can land with a
|
|
73
64
|
broken description and detonate in someone else's PR. Run `yarn site:build` before pushing.
|
|
74
65
|
|
|
66
|
+
## Citing a specification
|
|
67
|
+
|
|
68
|
+
A README, a source comment, `meta.ts` and `schema.json` cite the passage that justifies a rule. `yarn spec-citations` checks every citation against the live specification (what it checks and why: the module comment of `scripts/spec-citations/cli.ts`), and `scripts/spec-citations/citations-policy.spec.ts` holds the conventions below offline. After editing a citation, run `yarn spec-citations` (read-only) and fix what it reports. Its lock file is refreshed by the scheduled workflow's PR, never by hand.
|
|
69
|
+
|
|
70
|
+
- Quote a spec sentence verbatim, as a blockquote in a README or as `>` lines in a source comment, and name its source: in a README the `Cite: [..](url)` line after the quote (`[..](url)より引用` in Japanese), in a comment an `@see` URL in the same comment. Never quote inline in double quotes: the monitor cannot check it. `[…]` marks what the quote leaves out.
|
|
71
|
+
- Cite a section by its fragment. A `§4.10.7` beside the link is compared with the section the fragment is in; the number of an ancestor section is accepted.
|
|
72
|
+
- `README.ja.md` cites the HTML Living Standard through its translation (`https://momdo.github.io/html/…`, the same page and anchor as the English README) and quotes the translation's own text. That text stays English where the translation is behind the original, and the monitor reports it once the translation catches up. For WCAG, the translation is waic.jp's.
|
|
73
|
+
- Cite W3C specs at an edition-less URL (`https://w3c.github.io/accname/`, `https://w3c.github.io/aria/`, `https://www.w3.org/TR/WCAG/`, `https://www.w3.org/TR/html-aria/`). WCAG's Techniques and Understanding pages have no edition-less URL; cite the latest edition (`https://www.w3.org/WAI/WCAG22/…`). Not `w3c.github.io/html-aria/`: that is unrendered ReSpec source whose anchors exist only after a browser builds them.
|
|
74
|
+
|
|
75
75
|
## Assertion Convention
|
|
76
76
|
|
|
77
77
|
When asserting reported violations in rule specs, use `toStrictEqual` with the exact violation object `{ severity, line, col, message, raw }`. Never loose or partial matchers for violation objects. (Assertions on other values — counts, fixed code strings, registry invariants — use whatever matcher fits.)
|
|
@@ -2,6 +2,7 @@ import { createRule, getComputedRole, ariaSpecs, getSpec } from '@markuplint/ml-
|
|
|
2
2
|
import { ARIA_RECOMMENDED_VERSION } from '@markuplint/ml-spec';
|
|
3
3
|
import { checkingNoGlobalProp } from '../wai-aria/checkings/no-global-prop.js';
|
|
4
4
|
import { defaultOptions } from '../wai-aria/default-options.js';
|
|
5
|
+
import { isAriaAttr } from '../helpers.js';
|
|
5
6
|
import meta from './meta.js';
|
|
6
7
|
export default createRule({
|
|
7
8
|
meta,
|
|
@@ -13,7 +14,7 @@ export default createRule({
|
|
|
13
14
|
return;
|
|
14
15
|
if (!elSpec.globalAttrs['#ARIAAttrs'])
|
|
15
16
|
return;
|
|
16
|
-
const propAttrs = el.attributes.filter(attr =>
|
|
17
|
+
const propAttrs = el.attributes.filter(attr => isAriaAttr(attr));
|
|
17
18
|
if (propAttrs.length === 0)
|
|
18
19
|
return;
|
|
19
20
|
const ariaVersion = el.rule.options?.version ?? document.ruleCommonSettings?.ariaVersion ?? ARIA_RECOMMENDED_VERSION;
|
package/lib/attr-check.js
CHANGED
|
@@ -22,7 +22,9 @@ export function attrCheck(t, name, value, isCustomRule, spec, allAttrNames) {
|
|
|
22
22
|
}
|
|
23
23
|
// Existence
|
|
24
24
|
if (!spec) {
|
|
25
|
-
log
|
|
25
|
+
if (log.enabled) {
|
|
26
|
+
log('The "%s" attribute DOES\'NT EXIST in the spec', name);
|
|
27
|
+
}
|
|
26
28
|
const baseMessage = t('{0} is {1:c}', t('the "{0*}" {1}', name, 'attribute'), 'disallowed');
|
|
27
29
|
const candidate = allAttrNames ? getCandidate(name, allAttrNames) : undefined;
|
|
28
30
|
if (candidate) {
|
|
@@ -36,16 +38,6 @@ export function attrCheck(t, name, value, isCustomRule, spec, allAttrNames) {
|
|
|
36
38
|
message: baseMessage,
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
|
-
const nameCaseSensitive = /[A-Z]/.test(spec.name);
|
|
40
|
-
if (nameCaseSensitive && name !== spec.name) {
|
|
41
|
-
log('The "%s" attribute name is unmatched in case-sensitive', name);
|
|
42
|
-
return {
|
|
43
|
-
invalidType: 'non-existent',
|
|
44
|
-
message: t('{0} is {1:c}', t('the "{0*}" {1}', name, 'attribute'), 'disallowed') +
|
|
45
|
-
t('. ') +
|
|
46
|
-
t('Did you mean "{0*}"?', spec.name),
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
41
|
if (spec.noUse) {
|
|
50
42
|
return {
|
|
51
43
|
invalidType: 'disallowed-attr',
|
|
@@ -55,7 +47,9 @@ export function attrCheck(t, name, value, isCustomRule, spec, allAttrNames) {
|
|
|
55
47
|
// Safety net: In normal flow, `isValidAttr()` resolves ConditionalAttributeType[]
|
|
56
48
|
// before calling this function (#3598). This guard handles direct calls.
|
|
57
49
|
if (isConditionalAttributeTypeArray(spec.type)) {
|
|
58
|
-
log
|
|
50
|
+
if (log.enabled) {
|
|
51
|
+
log('The "%s" attribute uses ConditionalAttributeType[] — unresolved, treating as valid', name);
|
|
52
|
+
}
|
|
59
53
|
return false;
|
|
60
54
|
}
|
|
61
55
|
const types = toNonNullableArrayFromItemOrArray(spec.type);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Translator } from '@markuplint/i18n';
|
|
2
2
|
import type { PlainData } from '@markuplint/ml-config';
|
|
3
|
-
import type { Element, RuleConfigValue } from '@markuplint/ml-core';
|
|
3
|
+
import type { Attr, Element, RuleConfigValue } from '@markuplint/ml-core';
|
|
4
4
|
import type { Attribute } from '@markuplint/ml-spec';
|
|
5
5
|
/**
|
|
6
6
|
* Result of resolving whether an attribute name is known to the spec and,
|
|
7
7
|
* if so, whether it currently applies. Shared by `no-unknown-attr` (acts on
|
|
8
|
-
* `unknown
|
|
8
|
+
* `unknown`), `no-disallowed-attr` (acts on `no-use`/
|
|
9
9
|
* `condition-not-met`), and `no-invalid-attr-value` (acts on `ok`, using the
|
|
10
10
|
* resolved `spec.type` for value-checking). Splitting this into one function
|
|
11
11
|
* keeps the three rules' notion of "what the spec says about this name" in
|
|
@@ -16,9 +16,6 @@ export type AttrEligibility = {
|
|
|
16
16
|
} | {
|
|
17
17
|
readonly status: 'unknown';
|
|
18
18
|
readonly candidate: string | undefined;
|
|
19
|
-
} | {
|
|
20
|
-
readonly status: 'case-mismatch';
|
|
21
|
-
readonly expectedName: string;
|
|
22
19
|
} | {
|
|
23
20
|
readonly status: 'no-use';
|
|
24
21
|
} | {
|
|
@@ -30,10 +27,20 @@ export type AttrEligibility = {
|
|
|
30
27
|
/**
|
|
31
28
|
* Resolves an attribute name against the element's `attrSpecs`, following
|
|
32
29
|
* the same order the pre-split `invalid-attr` rule used: existence, then
|
|
33
|
-
*
|
|
34
|
-
*
|
|
30
|
+
* `noUse`, then `condition` (with `ConditionalAttributeType[]` resolved to a
|
|
31
|
+
* concrete type first).
|
|
32
|
+
*
|
|
33
|
+
* Whether a spelling is the name of a spec is up to the attribute, and the DOM decides it
|
|
34
|
+
* (`MLAttr#isNamedBy`): `viewbox` is `viewBox` where the HTML parser reads it, and is not where
|
|
35
|
+
* code sets the name. Ask with the attribute (`attr`); without it, `name` is a name of the spec
|
|
36
|
+
* and is looked up as written, which is what a rule does for the native attribute of an ARIA one.
|
|
37
|
+
*
|
|
38
|
+
* @param name - The attribute name (a name of the spec when `attr` is not given)
|
|
39
|
+
* @param node - The element
|
|
40
|
+
* @param attrSpecs - The specs of the attributes of the element
|
|
41
|
+
* @param attr - The attribute that is being resolved
|
|
35
42
|
*/
|
|
36
|
-
export declare function resolveAttrEligibility<T extends RuleConfigValue, O extends PlainData>(name: string, node: Element<T, O>, attrSpecs: readonly Attribute[]): AttrEligibility;
|
|
43
|
+
export declare function resolveAttrEligibility<T extends RuleConfigValue, O extends PlainData>(name: string, node: Element<T, O>, attrSpecs: readonly Attribute[], attr?: Attr<T, O>): AttrEligibility;
|
|
37
44
|
/**
|
|
38
45
|
* Formats the "did you mean" suffix shared by `no-unknown-attr`'s two typo
|
|
39
46
|
* paths (spec-driven candidate, case-sensitivity mismatch).
|
package/lib/attr-eligibility.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isConditionalAttributeTypeArray } from '@markuplint/ml-spec';
|
|
2
2
|
import { getCandidate } from '@markuplint/types';
|
|
3
|
-
import { attrMatches, conditionDependsOnDynamicAttr } from './helpers.js';
|
|
3
|
+
import { attrMatches, conditionDependsOnDynamicAttr, findAttrSpec, namespaceKeyOf } from './helpers.js';
|
|
4
4
|
/**
|
|
5
5
|
* Attribute namespaces that are exempt from spec-based name/eligibility
|
|
6
6
|
* checking regardless of what `attrSpecs` says: `data-*` and `adapt-*` are
|
|
@@ -11,24 +11,33 @@ const EXEMPT_NAME_PATTERN = /^(?:data-.+|adapt-.+|aria-.+|role)$/;
|
|
|
11
11
|
/**
|
|
12
12
|
* Resolves an attribute name against the element's `attrSpecs`, following
|
|
13
13
|
* the same order the pre-split `invalid-attr` rule used: existence, then
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* `noUse`, then `condition` (with `ConditionalAttributeType[]` resolved to a
|
|
15
|
+
* concrete type first).
|
|
16
|
+
*
|
|
17
|
+
* Whether a spelling is the name of a spec is up to the attribute, and the DOM decides it
|
|
18
|
+
* (`MLAttr#isNamedBy`): `viewbox` is `viewBox` where the HTML parser reads it, and is not where
|
|
19
|
+
* code sets the name. Ask with the attribute (`attr`); without it, `name` is a name of the spec
|
|
20
|
+
* and is looked up as written, which is what a rule does for the native attribute of an ARIA one.
|
|
21
|
+
*
|
|
22
|
+
* @param name - The attribute name (a name of the spec when `attr` is not given)
|
|
23
|
+
* @param node - The element
|
|
24
|
+
* @param attrSpecs - The specs of the attributes of the element
|
|
25
|
+
* @param attr - The attribute that is being resolved
|
|
16
26
|
*/
|
|
17
27
|
export function resolveAttrEligibility(name,
|
|
18
28
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
19
|
-
node, attrSpecs
|
|
20
|
-
|
|
29
|
+
node, attrSpecs,
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
31
|
+
attr) {
|
|
32
|
+
// The name that the DOM gives the attribute is what a pattern of a namespace is tested against
|
|
33
|
+
if (EXEMPT_NAME_PATTERN.test(attr ? namespaceKeyOf(attr) : name)) {
|
|
21
34
|
return { status: 'exempt' };
|
|
22
35
|
}
|
|
23
|
-
let spec = attrSpecs.find(s => s.name
|
|
36
|
+
let spec = attr ? findAttrSpec(attrSpecs, attr) : attrSpecs.find(s => s.name === name);
|
|
24
37
|
if (!spec) {
|
|
25
38
|
const candidate = getCandidate(name, attrSpecs.map(s => s.name));
|
|
26
39
|
return { status: 'unknown', candidate };
|
|
27
40
|
}
|
|
28
|
-
const nameCaseSensitive = /[A-Z]/.test(spec.name);
|
|
29
|
-
if (nameCaseSensitive && name !== spec.name) {
|
|
30
|
-
return { status: 'case-mismatch', expectedName: spec.name };
|
|
31
|
-
}
|
|
32
41
|
if (spec.noUse) {
|
|
33
42
|
return { status: 'no-use' };
|
|
34
43
|
}
|
package/lib/attr-order/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
|
+
import { namespaceKeyOf } from '../helpers.js';
|
|
2
3
|
import meta from './meta.js';
|
|
3
4
|
function compareStrings(a, b) {
|
|
4
5
|
if (a < b) {
|
|
@@ -57,15 +58,16 @@ eventAttrs) {
|
|
|
57
58
|
return {
|
|
58
59
|
entryIndex,
|
|
59
60
|
order: 'alphabetical',
|
|
60
|
-
match: (
|
|
61
|
+
match: (attr) => attr.isNamedBy(entry),
|
|
61
62
|
};
|
|
62
63
|
}
|
|
63
64
|
const order = entry.order ?? 'alphabetical';
|
|
64
65
|
if (entry.name != null) {
|
|
66
|
+
const entryName = entry.name;
|
|
65
67
|
return {
|
|
66
68
|
entryIndex,
|
|
67
69
|
order,
|
|
68
|
-
match: (
|
|
70
|
+
match: (attr) => attr.isNamedBy(entryName),
|
|
69
71
|
};
|
|
70
72
|
}
|
|
71
73
|
if (entry.pattern != null) {
|
|
@@ -84,7 +86,8 @@ eventAttrs) {
|
|
|
84
86
|
return {
|
|
85
87
|
entryIndex,
|
|
86
88
|
order,
|
|
87
|
-
|
|
89
|
+
// The name as written: a pattern takes no flags, so it is not folded
|
|
90
|
+
match: (attr) => re.test(attr.name),
|
|
88
91
|
};
|
|
89
92
|
}
|
|
90
93
|
if (entry.group != null) {
|
|
@@ -95,42 +98,42 @@ eventAttrs) {
|
|
|
95
98
|
return {
|
|
96
99
|
entryIndex,
|
|
97
100
|
order: groupOrder,
|
|
98
|
-
match: (
|
|
101
|
+
match: (attr) => globalAttrs.has(namespaceKeyOf(attr)),
|
|
99
102
|
};
|
|
100
103
|
}
|
|
101
104
|
case 'event': {
|
|
102
105
|
return {
|
|
103
106
|
entryIndex,
|
|
104
107
|
order: groupOrder,
|
|
105
|
-
match: (
|
|
108
|
+
match: (attr) => eventAttrs.has(namespaceKeyOf(attr)),
|
|
106
109
|
};
|
|
107
110
|
}
|
|
108
111
|
case 'aria': {
|
|
109
112
|
return {
|
|
110
113
|
entryIndex,
|
|
111
114
|
order: groupOrder,
|
|
112
|
-
match: (
|
|
115
|
+
match: (attr) => /^aria-.+$/.test(namespaceKeyOf(attr)),
|
|
113
116
|
};
|
|
114
117
|
}
|
|
115
118
|
case 'role': {
|
|
116
119
|
return {
|
|
117
120
|
entryIndex,
|
|
118
121
|
order: groupOrder,
|
|
119
|
-
match: (
|
|
122
|
+
match: (attr) => namespaceKeyOf(attr) === 'role',
|
|
120
123
|
};
|
|
121
124
|
}
|
|
122
125
|
case 'data': {
|
|
123
126
|
return {
|
|
124
127
|
entryIndex,
|
|
125
128
|
order: groupOrder,
|
|
126
|
-
match: (
|
|
129
|
+
match: (attr) => /^data-.+$/.test(namespaceKeyOf(attr)),
|
|
127
130
|
};
|
|
128
131
|
}
|
|
129
132
|
case 'spread': {
|
|
130
133
|
return {
|
|
131
134
|
entryIndex,
|
|
132
135
|
order: groupOrder,
|
|
133
|
-
match: (
|
|
136
|
+
match: (_attr, isSpread) => isSpread,
|
|
134
137
|
};
|
|
135
138
|
}
|
|
136
139
|
}
|
|
@@ -144,9 +147,9 @@ eventAttrs) {
|
|
|
144
147
|
}
|
|
145
148
|
function matchEntry(
|
|
146
149
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
147
|
-
compiledEntries,
|
|
150
|
+
compiledEntries, attr, isSpread, fallbackIndex) {
|
|
148
151
|
for (const entry of compiledEntries) {
|
|
149
|
-
if (entry.match(
|
|
152
|
+
if (entry.match(attr, isSpread)) {
|
|
150
153
|
return { entryIndex: entry.entryIndex, order: entry.order };
|
|
151
154
|
}
|
|
152
155
|
}
|
|
@@ -155,21 +158,21 @@ compiledEntries, name, isSpread, fallbackIndex) {
|
|
|
155
158
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
156
159
|
function compareWithinGroup(a, b, order) {
|
|
157
160
|
if (order === 'alphabetical') {
|
|
158
|
-
return compareStrings(a.
|
|
161
|
+
return compareStrings(namespaceKeyOf(a.attr), namespaceKeyOf(b.attr));
|
|
159
162
|
}
|
|
160
163
|
if (order === 'source-order') {
|
|
161
164
|
return a.index - b.index;
|
|
162
165
|
}
|
|
163
166
|
if (Array.isArray(order)) {
|
|
164
|
-
const ai = order.
|
|
165
|
-
const bi = order.
|
|
167
|
+
const ai = order.findIndex(name => a.attr.isNamedBy(name));
|
|
168
|
+
const bi = order.findIndex(name => b.attr.isNamedBy(name));
|
|
166
169
|
const aIdx = ai === -1 ? order.length : ai;
|
|
167
170
|
const bIdx = bi === -1 ? order.length : bi;
|
|
168
171
|
if (aIdx !== bIdx) {
|
|
169
172
|
return aIdx - bIdx;
|
|
170
173
|
}
|
|
171
174
|
if (ai === -1 && bi === -1) {
|
|
172
|
-
return compareStrings(a.
|
|
175
|
+
return compareStrings(namespaceKeyOf(a.attr), namespaceKeyOf(b.attr));
|
|
173
176
|
}
|
|
174
177
|
return 0;
|
|
175
178
|
}
|
|
@@ -180,7 +183,7 @@ function sortAttrs(attrInfos,
|
|
|
180
183
|
compiledEntries, alphabetical) {
|
|
181
184
|
const fallbackIndex = compiledEntries.length;
|
|
182
185
|
const tagged = attrInfos.map(info => {
|
|
183
|
-
const { entryIndex, order } = matchEntry(compiledEntries, info.
|
|
186
|
+
const { entryIndex, order } = matchEntry(compiledEntries, info.attr, info.isSpread, fallbackIndex);
|
|
184
187
|
return { info, entryIndex, order };
|
|
185
188
|
});
|
|
186
189
|
return tagged
|
|
@@ -190,7 +193,9 @@ compiledEntries, alphabetical) {
|
|
|
190
193
|
}
|
|
191
194
|
const isFallback = a.entryIndex === fallbackIndex;
|
|
192
195
|
if (isFallback) {
|
|
193
|
-
return alphabetical
|
|
196
|
+
return alphabetical
|
|
197
|
+
? compareStrings(namespaceKeyOf(a.info.attr), namespaceKeyOf(b.info.attr))
|
|
198
|
+
: a.info.index - b.info.index;
|
|
194
199
|
}
|
|
195
200
|
return compareWithinGroup(a.info, b.info, a.order);
|
|
196
201
|
})
|
|
@@ -246,6 +251,7 @@ export default createRule({
|
|
|
246
251
|
const attrInfos = attrs.map((attr, i) => {
|
|
247
252
|
const nameNode = attr.nameNode;
|
|
248
253
|
return {
|
|
254
|
+
attr,
|
|
249
255
|
name: attr.name,
|
|
250
256
|
index: i,
|
|
251
257
|
isSpread: nameNode == null,
|
|
@@ -257,7 +263,7 @@ export default createRule({
|
|
|
257
263
|
});
|
|
258
264
|
const sorted = isAlphabeticalOnly
|
|
259
265
|
? alphabetical
|
|
260
|
-
? [...attrInfos].toSorted((a, b) => compareStrings(a.
|
|
266
|
+
? [...attrInfos].toSorted((a, b) => compareStrings(namespaceKeyOf(a.attr), namespaceKeyOf(b.attr)))
|
|
261
267
|
: attrInfos
|
|
262
268
|
: sortAttrs(attrInfos, compiledEntries, alphabetical);
|
|
263
269
|
// Find first violation
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { createRule, getAttrSpecs } from '@markuplint/ml-core';
|
|
2
|
+
import { findAttrSpec } from '../helpers.js';
|
|
2
3
|
import meta from './meta.js';
|
|
3
4
|
export default createRule({
|
|
4
5
|
meta: meta,
|
|
5
6
|
defaultSeverity: 'warning',
|
|
6
7
|
defaultValue: 'lower',
|
|
7
8
|
async verify({ document, report, t }) {
|
|
9
|
+
// JSX props (`onClick`, `suppressHydrationWarning`) are JavaScript identifiers,
|
|
10
|
+
// not case-insensitive HTML attribute names; lowercasing them breaks the app.
|
|
11
|
+
if (document.attrNameCaseSensitive) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
8
14
|
await document.walkOn('Attr', attr => {
|
|
9
15
|
const el = attr.ownerElement;
|
|
10
|
-
if (el.
|
|
16
|
+
if (el.elementType !== 'html') {
|
|
11
17
|
return;
|
|
12
18
|
}
|
|
13
19
|
const value = attr.rule.value;
|
|
@@ -24,11 +30,24 @@ export default createRule({
|
|
|
24
30
|
return;
|
|
25
31
|
}
|
|
26
32
|
const name = attr.name;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
const spec = attrSpecs ? findAttrSpec(attrSpecs, attr) : undefined;
|
|
34
|
+
if (el.isForeignElement) {
|
|
35
|
+
// What the browser has for the name is the spelling of the spec, whatever the option says.
|
|
36
|
+
if (spec && spec.name !== name) {
|
|
37
|
+
const nameNode = attr.nameNode;
|
|
38
|
+
report({
|
|
39
|
+
scope: attr,
|
|
40
|
+
line: nameNode?.startLine,
|
|
41
|
+
col: nameNode?.startCol,
|
|
42
|
+
raw: nameNode?.raw,
|
|
43
|
+
message: t(`{0} ${ms} be {1}`, t('the "{0*}" {1}', name, 'attribute'), `"${spec.name}"`),
|
|
44
|
+
fix: nameNode ? fixer => fixer.replaceText(nameNode, spec.name) : undefined, // eslint-disable-line @typescript-eslint/strict-boolean-expressions
|
|
45
|
+
});
|
|
31
46
|
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (spec?.caseSensitive) {
|
|
50
|
+
return;
|
|
32
51
|
}
|
|
33
52
|
if (deny.test(name)) {
|
|
34
53
|
const nameNode = attr.nameNode;
|
|
@@ -9,6 +9,12 @@ export default createRule({
|
|
|
9
9
|
async verify({ document, report, t }) {
|
|
10
10
|
await document.walkOn('Element', el => {
|
|
11
11
|
const classPatterns = toNoEmptyStringArrayFromStringOrArray(el.rule.value).filter(className => !!className && typeof className === 'string');
|
|
12
|
+
// No pattern means the rule is disabled for this element (`null` is
|
|
13
|
+
// documented as "disable"); without this every class name would be
|
|
14
|
+
// reported against an empty pattern list. See #4202.
|
|
15
|
+
if (classPatterns.length === 0) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
12
18
|
const attrs = el.getAttributeToken('class');
|
|
13
19
|
for (const attr of attrs) {
|
|
14
20
|
if (attr.isDynamicValue) {
|
package/lib/debug.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import debug from 'debug';
|
|
2
2
|
export type Log = debug.Debugger;
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Enable by setting `DEBUG` to include `ml-rules` (e.g. `DEBUG=ml-rules`).
|
|
5
|
+
*
|
|
6
|
+
* Read `.enabled` at call time; never cache it at module load. `debug.enable()` (including via `verbosely()` /
|
|
7
|
+
* `enableDebug()`) can switch a namespace on after import. The `permitted-contents` functions read it once on entry to
|
|
8
|
+
* decide whether to build log arguments. Enabling debug while one runs is not seen until the next call; disabling
|
|
9
|
+
* takes effect at once because the logger itself re-checks.
|
|
10
|
+
*/
|
|
4
11
|
export declare const log: debug.Debugger;
|
package/lib/debug.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
import debug from 'debug';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Enable by setting `DEBUG` to include `ml-rules` (e.g. `DEBUG=ml-rules`).
|
|
4
|
+
*
|
|
5
|
+
* Read `.enabled` at call time; never cache it at module load. `debug.enable()` (including via `verbosely()` /
|
|
6
|
+
* `enableDebug()`) can switch a namespace on after import. The `permitted-contents` functions read it once on entry to
|
|
7
|
+
* decide whether to build log arguments. Enabling debug while one runs is not seen until the next call; disabling
|
|
8
|
+
* takes effect at once because the logger itself re-checks.
|
|
9
|
+
*/
|
|
3
10
|
export const log = debug('ml-rules');
|
|
@@ -2,6 +2,7 @@ import { createRule, getComputedRole, ariaSpecs, getSpec } from '@markuplint/ml-
|
|
|
2
2
|
import { ARIA_RECOMMENDED_VERSION } from '@markuplint/ml-spec';
|
|
3
3
|
import { checkingElementSupportsAriaProp } from '../wai-aria/checkings/element-supports-aria-prop.js';
|
|
4
4
|
import { defaultOptions } from '../wai-aria/default-options.js';
|
|
5
|
+
import { isAriaAttr } from '../helpers.js';
|
|
5
6
|
import meta from './meta.js';
|
|
6
7
|
/**
|
|
7
8
|
* Split from the former `wai-aria-disallowed-props` rule (#3989): the ARIA in
|
|
@@ -24,7 +25,7 @@ export default createRule({
|
|
|
24
25
|
return;
|
|
25
26
|
if (!elSpec.globalAttrs['#ARIAAttrs'])
|
|
26
27
|
return;
|
|
27
|
-
const propAttrs = el.attributes.filter(attr =>
|
|
28
|
+
const propAttrs = el.attributes.filter(attr => isAriaAttr(attr));
|
|
28
29
|
if (propAttrs.length === 0)
|
|
29
30
|
return;
|
|
30
31
|
const ariaVersion = el.rule.options?.version ?? document.ruleCommonSettings?.ariaVersion ?? ARIA_RECOMMENDED_VERSION;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Require the `form` attribute on a form-associated element to reference
|
|
3
|
-
* an actual `<form>` element by ID. Per HTML LS §4.10.18.
|
|
4
|
-
*
|
|
5
|
-
* element
|
|
3
|
+
* an actual `<form>` element by ID. Per HTML LS §4.10.18.3:
|
|
4
|
+
*
|
|
5
|
+
* > If a listed form-associated element has a form attribute specified, then that attribute's value must be the ID of a
|
|
6
|
+
* > form element in the element's tree.
|
|
7
|
+
*
|
|
8
|
+
* Existence of the ID itself is the responsibility of
|
|
6
9
|
* `no-refer-to-non-existent-id`; this rule only checks the element type
|
|
7
10
|
* when the ID resolves.
|
|
8
11
|
*
|
|
@@ -21,9 +21,12 @@ const FORM_ASSOCIATED_SELECTOR = [
|
|
|
21
21
|
].join(',');
|
|
22
22
|
/**
|
|
23
23
|
* Require the `form` attribute on a form-associated element to reference
|
|
24
|
-
* an actual `<form>` element by ID. Per HTML LS §4.10.18.
|
|
25
|
-
*
|
|
26
|
-
* element
|
|
24
|
+
* an actual `<form>` element by ID. Per HTML LS §4.10.18.3:
|
|
25
|
+
*
|
|
26
|
+
* > If a listed form-associated element has a form attribute specified, then that attribute's value must be the ID of a
|
|
27
|
+
* > form element in the element's tree.
|
|
28
|
+
*
|
|
29
|
+
* Existence of the ID itself is the responsibility of
|
|
27
30
|
* `no-refer-to-non-existent-id`; this rule only checks the element type
|
|
28
31
|
* when the ID resolves.
|
|
29
32
|
*
|