@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
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* > reprocess the token.
|
|
8
8
|
*
|
|
9
9
|
* Whitespace characters (U+0009/000A/000C/000D/0020) have their own case one
|
|
10
|
-
* step above (
|
|
11
|
-
* without the parse error), so a trailing newline after `</body>` is normal
|
|
10
|
+
* step above (they are processed with the rules of the 'in body' insertion
|
|
11
|
+
* mode, without the parse error), so a trailing newline after `</body>` is normal
|
|
12
12
|
* and out of scope here — only a start tag or non-whitespace text reaching
|
|
13
13
|
* this insertion mode is the "Anything else" parse error. Because the
|
|
14
14
|
* reprocessing switches back to "in body", the offending content still ends
|
|
@@ -9,8 +9,8 @@ import meta from './meta.js';
|
|
|
9
9
|
* > reprocess the token.
|
|
10
10
|
*
|
|
11
11
|
* Whitespace characters (U+0009/000A/000C/000D/0020) have their own case one
|
|
12
|
-
* step above (
|
|
13
|
-
* without the parse error), so a trailing newline after `</body>` is normal
|
|
12
|
+
* step above (they are processed with the rules of the 'in body' insertion
|
|
13
|
+
* mode, without the parse error), so a trailing newline after `</body>` is normal
|
|
14
14
|
* and out of scope here — only a start tag or non-whitespace text reaching
|
|
15
15
|
* this insertion mode is the "Anything else" parse error. Because the
|
|
16
16
|
* reprocessing switches back to "in body", the offending content still ends
|
|
@@ -2,6 +2,7 @@ import { createRule, ariaSpecs, getAttrSpecs, getSpec } from '@markuplint/ml-cor
|
|
|
2
2
|
import { ARIA_RECOMMENDED_VERSION } from '@markuplint/ml-spec';
|
|
3
3
|
import { checkingContradictoryAriaProp } from '../wai-aria/checkings/contradictory-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-implicit-props` rule (#3989): the
|
|
@@ -20,7 +21,7 @@ export default createRule({
|
|
|
20
21
|
return;
|
|
21
22
|
if (!elSpec.globalAttrs['#ARIAAttrs'])
|
|
22
23
|
return;
|
|
23
|
-
const propAttrs = el.attributes.filter(attr =>
|
|
24
|
+
const propAttrs = el.attributes.filter(attr => isAriaAttr(attr));
|
|
24
25
|
if (propAttrs.length === 0)
|
|
25
26
|
return;
|
|
26
27
|
const ariaVersion = el.rule.options?.version ?? document.ruleCommonSettings?.ariaVersion ?? ARIA_RECOMMENDED_VERSION;
|
|
@@ -2,6 +2,7 @@ import { createRule, ariaSpecs, getSpec } from '@markuplint/ml-core';
|
|
|
2
2
|
import { ARIA_RECOMMENDED_VERSION } from '@markuplint/ml-spec';
|
|
3
3
|
import { checkingDefaultValue } from '../wai-aria/checkings/default-value.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,
|
|
@@ -14,7 +15,7 @@ export default createRule({
|
|
|
14
15
|
return;
|
|
15
16
|
if (!elSpec.globalAttrs['#ARIAAttrs'])
|
|
16
17
|
return;
|
|
17
|
-
const propAttrs = el.attributes.filter(attr =>
|
|
18
|
+
const propAttrs = el.attributes.filter(attr => isAriaAttr(attr));
|
|
18
19
|
if (propAttrs.length === 0)
|
|
19
20
|
return;
|
|
20
21
|
const ariaVersion = el.rule.options?.version ?? document.ruleCommonSettings?.ariaVersion ?? ARIA_RECOMMENDED_VERSION;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRule, getAttrSpecs } from '@markuplint/ml-core';
|
|
2
|
-
import { removeAttr, toNormalizedValue } from '../helpers.js';
|
|
2
|
+
import { findAttrSpec, removeAttr, toNormalizedValue } from '../helpers.js';
|
|
3
3
|
import meta from './meta.js';
|
|
4
4
|
export default createRule({
|
|
5
5
|
meta: meta,
|
|
@@ -10,7 +10,7 @@ export default createRule({
|
|
|
10
10
|
if (!attrSpec) {
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
|
-
const spec = attrSpec
|
|
13
|
+
const spec = findAttrSpec(attrSpec, attr);
|
|
14
14
|
if (!spec) {
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
@@ -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 { checkingDeprecatedProps } from '../wai-aria/checkings/deprecated-props.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,
|
|
@@ -14,7 +15,7 @@ export default createRule({
|
|
|
14
15
|
return;
|
|
15
16
|
if (!elSpec.globalAttrs['#ARIAAttrs'])
|
|
16
17
|
return;
|
|
17
|
-
const propAttrs = el.attributes.filter(attr =>
|
|
18
|
+
const propAttrs = el.attributes.filter(attr => isAriaAttr(attr));
|
|
18
19
|
if (propAttrs.length === 0)
|
|
19
20
|
return;
|
|
20
21
|
const ariaVersion = el.rule.options?.version ?? document.ruleCommonSettings?.ariaVersion ?? ARIA_RECOMMENDED_VERSION;
|
|
@@ -1,4 +1,5 @@
|
|
|
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,
|
|
@@ -12,7 +13,7 @@ export default createRule({
|
|
|
12
13
|
return;
|
|
13
14
|
}
|
|
14
15
|
const name = attr.name;
|
|
15
|
-
const attrSpec = attrSpecs
|
|
16
|
+
const attrSpec = findAttrSpec(attrSpecs, attr);
|
|
16
17
|
if (!attrSpec) {
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
@@ -9,10 +9,16 @@ type Option = {
|
|
|
9
9
|
* defines. Shares its shape with `no-unknown-attr`'s option of the same
|
|
10
10
|
* name — see that rule for the full description.
|
|
11
11
|
*
|
|
12
|
+
* A name is compared as the DOM compares attribute names.
|
|
13
|
+
*
|
|
14
|
+
* @see https://markuplint.dev/docs/rules/no-unknown-attr#attribute-name-case
|
|
12
15
|
* @since 3.7.0
|
|
13
16
|
*/
|
|
14
17
|
allowAttrs?: (string | Attr)[] | Record<string, AttributeType | Pattern>;
|
|
15
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Attribute name prefix(es) to ignore during validation, compared as the DOM compares attribute
|
|
20
|
+
* names, like `allowAttrs`.
|
|
21
|
+
*/
|
|
16
22
|
ignoreAttrNamePrefix?: string | string[];
|
|
17
23
|
};
|
|
18
24
|
/**
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
import { createRule, getAttrSpecs, getSpec } from '@markuplint/ml-core';
|
|
2
2
|
import { resolveAttrEligibility } from '../attr-eligibility.js';
|
|
3
|
+
import { createNameOptionLookup, createPrefixMatcher } from '../helpers.js';
|
|
3
4
|
import meta from './meta.js';
|
|
4
|
-
function allowedNames(allowAttrs) {
|
|
5
|
-
if (!allowAttrs) {
|
|
6
|
-
return new Set();
|
|
7
|
-
}
|
|
8
|
-
if (Array.isArray(allowAttrs)) {
|
|
9
|
-
return new Set(allowAttrs.map(a => (typeof a === 'string' ? a : a.name)));
|
|
10
|
-
}
|
|
11
|
-
return new Set(Object.keys(allowAttrs));
|
|
12
|
-
}
|
|
13
5
|
export default createRule({
|
|
14
6
|
meta: meta,
|
|
15
7
|
defaultOptions: {},
|
|
16
8
|
async verify({ document, report, t }) {
|
|
9
|
+
// Whether an entry of the options is the attribute is up to the attribute: the DOM decides
|
|
10
|
+
// whether two spellings are one name (`MLAttr#isNamedBy`, `MLAttr#isPrefixedBy`).
|
|
11
|
+
const lookupAllowed = createNameOptionLookup('Any');
|
|
12
|
+
const hasIgnoredPrefix = createPrefixMatcher();
|
|
17
13
|
await document.walkOn('Attr', attr => {
|
|
18
14
|
if (attr.isDirective) {
|
|
19
15
|
return;
|
|
@@ -21,8 +17,9 @@ export default createRule({
|
|
|
21
17
|
const name = attr.name;
|
|
22
18
|
const attrName = attr.nameNode;
|
|
23
19
|
// https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is
|
|
24
|
-
//
|
|
25
|
-
|
|
20
|
+
// > The exception is the is attribute, which must not be specified on an autonomous custom element (and which will
|
|
21
|
+
// > have no effect if it is).
|
|
22
|
+
if (attr.nameKey === 'is' && attr.ownerElement.elementType === 'web-component') {
|
|
26
23
|
report({
|
|
27
24
|
scope: attr,
|
|
28
25
|
line: attrName?.startLine,
|
|
@@ -32,15 +29,11 @@ export default createRule({
|
|
|
32
29
|
});
|
|
33
30
|
return;
|
|
34
31
|
}
|
|
35
|
-
if (attr.rule.options.ignoreAttrNamePrefix
|
|
36
|
-
|
|
37
|
-
? attr.rule.options.ignoreAttrNamePrefix
|
|
38
|
-
: [attr.rule.options.ignoreAttrNamePrefix];
|
|
39
|
-
if (prefixes.some(prefix => name.indexOf(prefix) === 0)) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
32
|
+
if (hasIgnoredPrefix(attr.rule.options.ignoreAttrNamePrefix, attr)) {
|
|
33
|
+
return;
|
|
42
34
|
}
|
|
43
|
-
|
|
35
|
+
// A name listed in `allowAttrs` is allowed whatever its value
|
|
36
|
+
if (lookupAllowed(attr.rule.options.allowAttrs, attr) !== undefined) {
|
|
44
37
|
return;
|
|
45
38
|
}
|
|
46
39
|
if (attr.ownerElement.elementType !== 'html') {
|
|
@@ -50,7 +43,7 @@ export default createRule({
|
|
|
50
43
|
if (!attrSpecs) {
|
|
51
44
|
return;
|
|
52
45
|
}
|
|
53
|
-
const eligibility = resolveAttrEligibility(name, attr.ownerElement, attrSpecs);
|
|
46
|
+
const eligibility = resolveAttrEligibility(name, attr.ownerElement, attrSpecs, attr);
|
|
54
47
|
if (eligibility.status === 'no-use') {
|
|
55
48
|
report({
|
|
56
49
|
scope: attr,
|
|
@@ -11,8 +11,7 @@ export default createRule({
|
|
|
11
11
|
if (attr.isDuplicatable) {
|
|
12
12
|
continue;
|
|
13
13
|
}
|
|
14
|
-
const
|
|
15
|
-
const name = node.elementType === 'html' ? attrName.toLowerCase() : attrName;
|
|
14
|
+
const name = attr.nameKey;
|
|
16
15
|
if (attrNameStack.includes(name)) {
|
|
17
16
|
const scope = attr.nameNode ?? attr;
|
|
18
17
|
report({
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
import meta from './meta.js';
|
|
3
3
|
/**
|
|
4
|
-
* The HTML Living Standard scopes autofocus uniqueness to the
|
|
5
|
-
* autofocus scoping root element
|
|
4
|
+
* The HTML Living Standard scopes autofocus uniqueness to the nearest ancestor
|
|
5
|
+
* autofocus scoping root element — the element itself if it is a `dialog` or
|
|
6
6
|
* has a `popover` attribute, otherwise the nearest such ancestor, otherwise the
|
|
7
7
|
* document as a whole. Two `autofocus` elements only conflict when they share
|
|
8
8
|
* the same scoping root; two `dialog`/`popover` elements can each carry their
|
|
@@ -12,7 +12,7 @@ export interface Options {
|
|
|
12
12
|
* severity is `warning`. The former rule's `expected-once` option gated this
|
|
13
13
|
* check; disabling this rule entirely is how to opt out now.
|
|
14
14
|
*
|
|
15
|
-
* @see https://www.w3.org/WAI/
|
|
15
|
+
* @see https://www.w3.org/WAI/WCAG22/Techniques/html/H42
|
|
16
16
|
*/
|
|
17
17
|
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, Options>>;
|
|
18
18
|
export default _default;
|
|
@@ -7,7 +7,7 @@ import meta from './meta.js';
|
|
|
7
7
|
* severity is `warning`. The former rule's `expected-once` option gated this
|
|
8
8
|
* check; disabling this rule entirely is how to opt out now.
|
|
9
9
|
*
|
|
10
|
-
* @see https://www.w3.org/WAI/
|
|
10
|
+
* @see https://www.w3.org/WAI/WCAG22/Techniques/html/H42
|
|
11
11
|
*/
|
|
12
12
|
export default createRule({
|
|
13
13
|
meta: meta,
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTML LS [§4.9.12.1 *Forming a table*](https://html.spec.whatwg.org/multipage/tables.html#forming-a-table)
|
|
3
|
-
* Step 20:
|
|
4
|
-
*
|
|
3
|
+
* Step 20:
|
|
4
|
+
*
|
|
5
|
+
* > If there exists a row or column in the table containing only slots that do not have a cell anchored to them, then
|
|
6
|
+
* > this is a table model error.
|
|
7
|
+
*
|
|
8
|
+
* Covers both a `<tr>` with no cell anchored in it and a
|
|
5
9
|
* column that no cell (nor a `<col>` / `<colgroup>` it belongs to) ever anchors a cell to.
|
|
6
10
|
*
|
|
7
11
|
* Split from the original `table-row-column-alignment` rule. Skips a table that
|
|
@@ -3,8 +3,12 @@ import { Grid } from '../table-row-column-alignment/grid.js';
|
|
|
3
3
|
import meta from './meta.js';
|
|
4
4
|
/**
|
|
5
5
|
* HTML LS [§4.9.12.1 *Forming a table*](https://html.spec.whatwg.org/multipage/tables.html#forming-a-table)
|
|
6
|
-
* Step 20:
|
|
7
|
-
*
|
|
6
|
+
* Step 20:
|
|
7
|
+
*
|
|
8
|
+
* > If there exists a row or column in the table containing only slots that do not have a cell anchored to them, then
|
|
9
|
+
* > this is a table model error.
|
|
10
|
+
*
|
|
11
|
+
* Covers both a `<tr>` with no cell anchored in it and a
|
|
8
12
|
* column that no cell (nor a `<col>` / `<colgroup>` it belongs to) ever anchors a cell to.
|
|
9
13
|
*
|
|
10
14
|
* Split from the original `table-row-column-alignment` rule. Skips a table that
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
* Options for the `no-experimental-features` rule.
|
|
3
3
|
*/
|
|
4
4
|
type Options = {
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Features to ignore: an element name ("dialog") or an attribute of an element ("input[list]"). The
|
|
7
|
+
* element name is compared as the document compares tag names, the attribute name as the DOM
|
|
8
|
+
* compares it with the attribute that is checked.
|
|
9
|
+
*/
|
|
6
10
|
readonly ignoreFeatures?: readonly string[];
|
|
7
11
|
};
|
|
8
12
|
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, Options>>;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createRule, getAttrSpecs, getSpec } from '@markuplint/ml-core';
|
|
2
|
+
import { createIgnoreFeaturesLookup, 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
|
defaultOptions: {},
|
|
7
8
|
async verify({ document, report, t }) {
|
|
8
|
-
const
|
|
9
|
-
const ignoreFeatures = new Set(options.ignoreFeatures);
|
|
9
|
+
const ignored = createIgnoreFeaturesLookup(document.tagNameCaseSensitive, document.rule.options.ignoreFeatures);
|
|
10
10
|
await document.walkOn('Element', el => {
|
|
11
11
|
// Web components and authored elements (JSX/Vue/Svelte) reach this rule
|
|
12
12
|
// via `pretenders`: they masquerade as a known HTML element on `el.localName`,
|
|
@@ -16,9 +16,8 @@ export default createRule({
|
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
18
|
const elName = el.localName;
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
if (!elIgnoreFeatures.has(elName)) {
|
|
19
|
+
const elIgnoreFeatures = el.rule.options.ignoreFeatures;
|
|
20
|
+
if (!ignored.isElementIgnored(elIgnoreFeatures, elName)) {
|
|
22
21
|
const spec = getSpec(el, document.specs.specs);
|
|
23
22
|
if (spec?.experimental) {
|
|
24
23
|
report({
|
|
@@ -33,11 +32,10 @@ export default createRule({
|
|
|
33
32
|
continue;
|
|
34
33
|
}
|
|
35
34
|
const attrName = attr.name;
|
|
36
|
-
|
|
37
|
-
if (elIgnoreFeatures.has(ignoreKey)) {
|
|
35
|
+
if (ignored.isAttrIgnored(elIgnoreFeatures, elName, attr)) {
|
|
38
36
|
continue;
|
|
39
37
|
}
|
|
40
|
-
const attrSpec = attrSpecs
|
|
38
|
+
const attrSpec = attrSpecs && findAttrSpec(attrSpecs, attr);
|
|
41
39
|
if (attrSpec?.experimental) {
|
|
42
40
|
report({
|
|
43
41
|
scope: attr,
|
|
@@ -8,7 +8,7 @@ export default createRule({
|
|
|
8
8
|
return;
|
|
9
9
|
}
|
|
10
10
|
await document.walkOn('Attr', attr => {
|
|
11
|
-
if (attr.
|
|
11
|
+
if (attr.nameKey === 'id' && !attr.isDynamicValue && attr.valueType !== 'code') {
|
|
12
12
|
report({
|
|
13
13
|
scope: attr,
|
|
14
14
|
line: attr.startLine,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRule, getAttrSpecs } from '@markuplint/ml-core';
|
|
2
2
|
import { toNoEmptyStringArrayFromStringOrArray } from '@markuplint/shared';
|
|
3
|
-
import { removeAttr } from '../helpers.js';
|
|
3
|
+
import { findAttrSpec, removeAttr } from '../helpers.js';
|
|
4
4
|
import meta from './meta.js';
|
|
5
5
|
export default createRule({
|
|
6
6
|
meta: meta,
|
|
@@ -12,7 +12,7 @@ export default createRule({
|
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
const name = attr.name;
|
|
15
|
-
const spec = attrSpec
|
|
15
|
+
const spec = findAttrSpec(attrSpec, attr);
|
|
16
16
|
if (!spec) {
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Reports a non-empty `value` attribute on `<input type="file">`.
|
|
3
|
+
*
|
|
4
|
+
* Superseded by `no-disallowed-attr`: HTML LS §4.10.5.1.17 (File Upload
|
|
5
|
+
* state) does not allow the attribute at all, an empty one included.
|
|
6
|
+
* This rule was written against a sentence that is not in the spec
|
|
7
|
+
* (that the attribute, if specified, must have a value that is the empty
|
|
8
|
+
* string) and so lets `value=""` through. Its behavior is kept for configurations that
|
|
9
|
+
* name it; it is removed in v6.
|
|
10
|
+
*
|
|
11
|
+
* > The element's value attribute must be omitted.
|
|
6
12
|
*
|
|
7
13
|
* @see https://html.spec.whatwg.org/multipage/input.html#file-upload-state-(type=file)
|
|
8
14
|
*/
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
import meta from './meta.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Reports a non-empty `value` attribute on `<input type="file">`.
|
|
5
|
+
*
|
|
6
|
+
* Superseded by `no-disallowed-attr`: HTML LS §4.10.5.1.17 (File Upload
|
|
7
|
+
* state) does not allow the attribute at all, an empty one included.
|
|
8
|
+
* This rule was written against a sentence that is not in the spec
|
|
9
|
+
* (that the attribute, if specified, must have a value that is the empty
|
|
10
|
+
* string) and so lets `value=""` through. Its behavior is kept for configurations that
|
|
11
|
+
* name it; it is removed in v6.
|
|
12
|
+
*
|
|
13
|
+
* > The element's value attribute must be omitted.
|
|
8
14
|
*
|
|
9
15
|
* @see https://html.spec.whatwg.org/multipage/input.html#file-upload-state-(type=file)
|
|
10
16
|
*/
|
|
@@ -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 { checkingValue } from '../wai-aria/checkings/value.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;
|
|
@@ -10,6 +10,9 @@ type Option = {
|
|
|
10
10
|
* `no-unknown-attr`'s option of the same name — the name-eligibility half
|
|
11
11
|
* lives there, this rule only consumes the value constraint.
|
|
12
12
|
*
|
|
13
|
+
* A name is compared as the DOM compares attribute names.
|
|
14
|
+
*
|
|
15
|
+
* @see https://markuplint.dev/docs/rules/no-unknown-attr#attribute-name-case
|
|
13
16
|
* @since 3.7.0
|
|
14
17
|
*/
|
|
15
18
|
allowAttrs?: (string | Attr)[] | Record<string, AttributeType | Pattern>;
|
|
@@ -2,33 +2,16 @@ import { createRule, getAttrSpecs } from '@markuplint/ml-core';
|
|
|
2
2
|
import { attrCheck } from '../attr-check.js';
|
|
3
3
|
import { resolveAttrEligibility } from '../attr-eligibility.js';
|
|
4
4
|
import { log as ruleLog } from '../debug.js';
|
|
5
|
+
import { createNameOptionLookup } from '../helpers.js';
|
|
5
6
|
import meta from './meta.js';
|
|
6
7
|
const log = ruleLog.extend('no-invalid-attr-value');
|
|
7
|
-
function allowedTypes(allowAttrs) {
|
|
8
|
-
const map = new Map();
|
|
9
|
-
if (!allowAttrs) {
|
|
10
|
-
return map;
|
|
11
|
-
}
|
|
12
|
-
if (Array.isArray(allowAttrs)) {
|
|
13
|
-
for (const allowAttr of allowAttrs) {
|
|
14
|
-
if (typeof allowAttr === 'string') {
|
|
15
|
-
map.set(allowAttr, 'Any');
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
map.set(allowAttr.name, allowAttr.value);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
for (const [name, type] of Object.entries(allowAttrs)) {
|
|
23
|
-
map.set(name, type);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return map;
|
|
27
|
-
}
|
|
28
8
|
export default createRule({
|
|
29
9
|
meta: meta,
|
|
30
10
|
defaultOptions: {},
|
|
31
11
|
async verify({ document, report, t }) {
|
|
12
|
+
// Whether an entry of `allowAttrs` is the attribute is up to the attribute: the DOM decides
|
|
13
|
+
// whether two spellings are one name (`MLAttr#isNamedBy`).
|
|
14
|
+
const lookupAllowed = createNameOptionLookup('Any');
|
|
32
15
|
await document.walkOn('Attr', attr => {
|
|
33
16
|
if (attr.isDirective) {
|
|
34
17
|
return;
|
|
@@ -36,7 +19,7 @@ export default createRule({
|
|
|
36
19
|
const name = attr.name;
|
|
37
20
|
const value = attr.value;
|
|
38
21
|
const valueNode = attr.valueNode;
|
|
39
|
-
const allowType =
|
|
22
|
+
const allowType = lookupAllowed(attr.rule.options.allowAttrs, attr);
|
|
40
23
|
let invalid = false;
|
|
41
24
|
if (allowType === undefined) {
|
|
42
25
|
if (attr.ownerElement.elementType !== 'html') {
|
|
@@ -46,8 +29,10 @@ export default createRule({
|
|
|
46
29
|
if (!attrSpecs) {
|
|
47
30
|
return;
|
|
48
31
|
}
|
|
49
|
-
log
|
|
50
|
-
|
|
32
|
+
if (log.enabled) {
|
|
33
|
+
log('Checking %s[%s="%s"]', attr.nodeName, name, value);
|
|
34
|
+
}
|
|
35
|
+
const eligibility = resolveAttrEligibility(name, attr.ownerElement, attrSpecs, attr);
|
|
51
36
|
if (eligibility.status !== 'ok') {
|
|
52
37
|
return;
|
|
53
38
|
}
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
* Options for the `no-nonstandard-features` rule.
|
|
3
3
|
*/
|
|
4
4
|
type Options = {
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Features to ignore: an element name ("dialog") or an attribute of an element ("input[list]"). The
|
|
7
|
+
* element name is compared as the document compares tag names, the attribute name as the DOM
|
|
8
|
+
* compares it with the attribute that is checked.
|
|
9
|
+
*/
|
|
6
10
|
readonly ignoreFeatures?: readonly string[];
|
|
7
11
|
};
|
|
8
12
|
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, Options>>;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createRule, getAttrSpecs, getSpec } from '@markuplint/ml-core';
|
|
2
|
+
import { createIgnoreFeaturesLookup, 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
|
defaultOptions: {},
|
|
7
8
|
async verify({ document, report, t }) {
|
|
8
|
-
const
|
|
9
|
-
const ignoreFeatures = new Set(options.ignoreFeatures);
|
|
9
|
+
const ignored = createIgnoreFeaturesLookup(document.tagNameCaseSensitive, document.rule.options.ignoreFeatures);
|
|
10
10
|
await document.walkOn('Element', el => {
|
|
11
11
|
// Web components and authored elements (JSX/Vue/Svelte) reach this rule
|
|
12
12
|
// via `pretenders`: they masquerade as a known HTML element on `el.localName`,
|
|
@@ -16,9 +16,8 @@ export default createRule({
|
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
18
|
const elName = el.localName;
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
if (!elIgnoreFeatures.has(elName)) {
|
|
19
|
+
const elIgnoreFeatures = el.rule.options.ignoreFeatures;
|
|
20
|
+
if (!ignored.isElementIgnored(elIgnoreFeatures, elName)) {
|
|
22
21
|
const spec = getSpec(el, document.specs.specs);
|
|
23
22
|
if (spec?.nonStandard) {
|
|
24
23
|
report({
|
|
@@ -33,11 +32,10 @@ export default createRule({
|
|
|
33
32
|
continue;
|
|
34
33
|
}
|
|
35
34
|
const attrName = attr.name;
|
|
36
|
-
|
|
37
|
-
if (elIgnoreFeatures.has(ignoreKey)) {
|
|
35
|
+
if (ignored.isAttrIgnored(elIgnoreFeatures, elName, attr)) {
|
|
38
36
|
continue;
|
|
39
37
|
}
|
|
40
|
-
const attrSpec = attrSpecs
|
|
38
|
+
const attrSpec = attrSpecs && findAttrSpec(attrSpecs, attr);
|
|
41
39
|
if (attrSpec?.nonStandard) {
|
|
42
40
|
report({
|
|
43
41
|
scope: attr,
|
|
@@ -1,4 +1,5 @@
|
|
|
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,
|
|
@@ -8,8 +9,7 @@ export default createRule({
|
|
|
8
9
|
if (!attrSpecs) {
|
|
9
10
|
return;
|
|
10
11
|
}
|
|
11
|
-
const
|
|
12
|
-
const attrSpec = attrSpecs.find(item => item.name === name);
|
|
12
|
+
const attrSpec = findAttrSpec(attrSpecs, attr);
|
|
13
13
|
if (!attrSpec) {
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
@@ -19,7 +19,7 @@ export default createRule({
|
|
|
19
19
|
line: attr.nameNode?.startLine,
|
|
20
20
|
col: attr.nameNode?.startCol,
|
|
21
21
|
raw: attr.nameNode?.raw,
|
|
22
|
-
message: t('{0} is {1:c}', t('the "{0*}" {1}', name, 'attribute'), 'obsolete'),
|
|
22
|
+
message: t('{0} is {1:c}', t('the "{0*}" {1}', attrSpec.name, 'attribute'), 'obsolete'),
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
});
|
|
@@ -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 { checkingProhibitedNaming } from '../wai-aria/checkings/prohibited-naming.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 naming-
|
|
@@ -22,7 +23,7 @@ export default createRule({
|
|
|
22
23
|
return;
|
|
23
24
|
if (elSpec && !elSpec.globalAttrs['#ARIAAttrs'])
|
|
24
25
|
return;
|
|
25
|
-
const propAttrs = el.attributes.filter(attr =>
|
|
26
|
+
const propAttrs = el.attributes.filter(attr => isAriaAttr(attr));
|
|
26
27
|
if (propAttrs.length === 0)
|
|
27
28
|
return;
|
|
28
29
|
const ariaVersion = el.rule.options?.version ?? document.ruleCommonSettings?.ariaVersion ?? ARIA_RECOMMENDED_VERSION;
|
|
@@ -48,11 +48,27 @@ document, options,
|
|
|
48
48
|
computed) {
|
|
49
49
|
const sources = [];
|
|
50
50
|
// 1. aria-labelledby
|
|
51
|
+
//
|
|
52
|
+
// A node that references itself is not overridden by its own aria-labelledby: when the
|
|
53
|
+
// traversal reaches the node again, accname step 2B is skipped and the name falls through
|
|
54
|
+
// to 2D–2F (aria-label, native sources, content). aria-labelledby then only composes the
|
|
55
|
+
// node's own name with the other referenced elements, so it is not a competing source.
|
|
56
|
+
// https://w3c.github.io/accname/#comp_labelledby (see Example 5 under 2D)
|
|
57
|
+
//
|
|
58
|
+
// The remaining sources are still ranked as usual: inside the self-referencing traversal,
|
|
59
|
+
// aria-label (2D) still wins over a label (2E) or content (2F), so those are reported exactly
|
|
60
|
+
// as on an element without aria-labelledby. A self-reference is not an exemption from that.
|
|
61
|
+
//
|
|
62
|
+
// Known limitation: step 2C (Embedded Control) is not modeled, so a self-referencing
|
|
63
|
+
// textbox, combobox, listbox or range is treated like any other element.
|
|
51
64
|
const ariaLabelledby = el.getAttribute('aria-labelledby');
|
|
52
65
|
if (ariaLabelledby && ariaLabelledby.trim()) {
|
|
53
66
|
const ids = ariaLabelledby.trim().split(/\s+/);
|
|
67
|
+
// An IDREF resolves to the first element with that id, so with duplicate ids a
|
|
68
|
+
// reference only counts as self-referencing when this element is that first match.
|
|
69
|
+
const isSelfReferencing = ids.some(id => id === el.id && document.querySelector(`[id="${id}"]`) === el);
|
|
54
70
|
const hasResolvableId = ids.some(id => document.querySelector(`[id="${id}"]`));
|
|
55
|
-
if (hasResolvableId) {
|
|
71
|
+
if (hasResolvableId && !isSelfReferencing) {
|
|
56
72
|
sources.push({ label: 'aria-labelledby' });
|
|
57
73
|
}
|
|
58
74
|
}
|
|
@@ -2,6 +2,7 @@ import { createRule, ariaSpecs, getAttrSpecs, getSpec } from '@markuplint/ml-cor
|
|
|
2
2
|
import { ARIA_RECOMMENDED_VERSION } from '@markuplint/ml-spec';
|
|
3
3
|
import { checkingRedundantAriaProp } from '../wai-aria/checkings/redundant-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-implicit-props` rule (#3989): the
|
|
@@ -21,7 +22,7 @@ export default createRule({
|
|
|
21
22
|
return;
|
|
22
23
|
if (!elSpec.globalAttrs['#ARIAAttrs'])
|
|
23
24
|
return;
|
|
24
|
-
const propAttrs = el.attributes.filter(attr =>
|
|
25
|
+
const propAttrs = el.attributes.filter(attr => isAriaAttr(attr));
|
|
25
26
|
if (propAttrs.length === 0)
|
|
26
27
|
return;
|
|
27
28
|
const ariaVersion = el.rule.options?.version ?? document.ruleCommonSettings?.ariaVersion ?? ARIA_RECOMMENDED_VERSION;
|