@markuplint/rules 4.0.0-alpha.1 → 4.0.0-alpha.10
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/.eslintrc +5 -0
- package/LICENSE +1 -1
- package/lib/attr-check.js +2 -2
- package/lib/attr-duplication/index.js +3 -0
- package/lib/attr-value-quotes/index.js +3 -1
- package/lib/case-sensitive-attr-name/index.js +3 -0
- package/lib/case-sensitive-tag-name/index.js +3 -0
- package/lib/character-reference/index.js +8 -5
- package/lib/class-naming/index.js +5 -2
- package/lib/create-message.d.ts +1 -1
- package/lib/create-message.js +21 -10
- package/lib/deprecated-attr/index.js +3 -0
- package/lib/deprecated-element/index.js +4 -1
- package/lib/disallowed-element/index.js +7 -4
- package/lib/doctype/index.js +3 -0
- package/lib/end-tag/index.js +3 -0
- package/lib/helpers.js +10 -6
- package/lib/id-duplication/index.js +3 -0
- package/lib/index.d.ts +6 -2
- package/lib/ineffective-attr/index.js +3 -0
- package/lib/invalid-attr/index.js +3 -0
- package/lib/label-has-control/index.js +3 -0
- package/lib/landmark-roles/index.js +13 -12
- package/lib/no-boolean-attr-value/index.js +3 -0
- package/lib/no-default-value/index.js +3 -0
- package/lib/no-empty-palpable-content/index.js +4 -1
- package/lib/no-hard-code-id/index.js +3 -0
- package/lib/no-refer-to-non-existent-id/index.d.ts +1 -1
- package/lib/no-refer-to-non-existent-id/index.js +11 -8
- package/lib/no-use-event-handler-attr/index.js +3 -0
- package/lib/permitted-contents/choice.js +14 -4
- package/lib/permitted-contents/content-model.js +6 -3
- package/lib/permitted-contents/count-pattern.js +1 -1
- package/lib/permitted-contents/debug.browser.d.ts +2 -2
- package/lib/permitted-contents/debug.d.ts +2 -2
- package/lib/permitted-contents/index.js +3 -0
- package/lib/permitted-contents/matches-selector.js +2 -2
- package/lib/permitted-contents/order.js +3 -3
- package/lib/permitted-contents/represent-transparent-nodes.js +1 -1
- package/lib/permitted-contents/transparent.js +2 -2
- package/lib/permitted-contents/utils.js +27 -18
- package/lib/placeholder-label-option/index.js +7 -4
- package/lib/require-accessible-name/index.js +3 -3
- package/lib/require-datetime/index.js +8 -5
- package/lib/require-datetime/utils.js +1 -1
- package/lib/required-attr/index.js +3 -0
- package/lib/required-element/index.js +4 -1
- package/lib/required-h1/index.js +3 -0
- package/lib/use-list/index.d.ts +4 -0
- package/lib/use-list/index.js +47 -27
- package/lib/wai-aria/checkings/implicit-props.js +7 -7
- package/lib/wai-aria/checkings/presentational-children.js +1 -1
- package/lib/wai-aria/checkings/required-owned-elements.js +3 -3
- package/lib/wai-aria/checkings/value.js +3 -3
- package/lib/wai-aria/index.js +3 -0
- package/package.json +11 -12
package/.eslintrc
ADDED
package/LICENSE
CHANGED
package/lib/attr-check.js
CHANGED
|
@@ -71,10 +71,10 @@ export function attrCheck(t, name, value, isCustomRule, spec) {
|
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
|
-
if (invalidList.
|
|
74
|
+
if (invalidList.includes(false)) {
|
|
75
75
|
return false;
|
|
76
76
|
}
|
|
77
|
-
const invalid = invalidList.find(
|
|
77
|
+
const invalid = invalidList.find(Boolean);
|
|
78
78
|
return invalid ?? false;
|
|
79
79
|
}
|
|
80
80
|
export function valueCheck(t, name, value, type) {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
export default createRule({
|
|
3
|
+
meta: {
|
|
4
|
+
category: 'validation',
|
|
5
|
+
},
|
|
3
6
|
async verify({ document, report, t }) {
|
|
4
7
|
const message = t('{0} is {1:c}', t('the {0}', 'attribute name'), 'duplicated');
|
|
5
8
|
await document.walkOn('Element', node => {
|
|
@@ -4,6 +4,9 @@ const quoteList = {
|
|
|
4
4
|
single: "'",
|
|
5
5
|
};
|
|
6
6
|
export default createRule({
|
|
7
|
+
meta: {
|
|
8
|
+
category: 'style',
|
|
9
|
+
},
|
|
7
10
|
defaultSeverity: 'warning',
|
|
8
11
|
defaultValue: 'double',
|
|
9
12
|
async verify({ document, report, t }) {
|
|
@@ -26,7 +29,6 @@ export default createRule({
|
|
|
26
29
|
const quote = quoteList[attr.rule.value];
|
|
27
30
|
if (quote && attr.startQuote && attr.startQuote.raw !== quote) {
|
|
28
31
|
attr.startQuote.fix(quote);
|
|
29
|
-
// TODO: attr.endQuote = new MLToken(quote);
|
|
30
32
|
attr.endQuote?.fix(quote);
|
|
31
33
|
}
|
|
32
34
|
});
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { createRule, getLocationFromChars } from '@markuplint/ml-core';
|
|
2
2
|
const defaultChars = ['"', '&', '<', '>'];
|
|
3
|
-
const ignoreParentElement = ['script', 'style'];
|
|
3
|
+
const ignoreParentElement = new Set(['script', 'style']);
|
|
4
4
|
export default createRule({
|
|
5
|
+
meta: {
|
|
6
|
+
category: 'style',
|
|
7
|
+
},
|
|
5
8
|
async verify({ document, report, t }) {
|
|
6
9
|
const targetNodes = [];
|
|
7
10
|
await document.walkOn('Text', node => {
|
|
8
|
-
if (node.parentNode && ignoreParentElement.
|
|
11
|
+
if (node.parentNode && ignoreParentElement.has(node.parentNode.nodeName.toLowerCase())) {
|
|
9
12
|
return;
|
|
10
13
|
}
|
|
11
14
|
const severity = node.rule.severity;
|
|
@@ -41,14 +44,14 @@ export default createRule({
|
|
|
41
44
|
if (!('scope' in targetNode && 'line' in targetNode && targetNode.line != null)) {
|
|
42
45
|
continue;
|
|
43
46
|
}
|
|
44
|
-
const escapedText = targetNode.raw.
|
|
45
|
-
getLocationFromChars(defaultChars, escapedText, targetNode.line, targetNode.col)
|
|
47
|
+
const escapedText = targetNode.raw.replaceAll(/&(?:[a-z]+|#\d+|#x[\da-f]+);/gi, $0 => '*'.repeat($0.length));
|
|
48
|
+
for (const location of getLocationFromChars(defaultChars, escapedText, targetNode.line, targetNode.col)) {
|
|
46
49
|
report({
|
|
47
50
|
scope: targetNode.scope,
|
|
48
51
|
message: targetNode.message,
|
|
49
52
|
...location,
|
|
50
53
|
});
|
|
51
|
-
}
|
|
54
|
+
}
|
|
52
55
|
}
|
|
53
56
|
},
|
|
54
57
|
});
|
|
@@ -2,6 +2,9 @@ import { createRule } from '@markuplint/ml-core';
|
|
|
2
2
|
import { toNoEmptyStringArrayFromStringOrArray } from '@markuplint/shared';
|
|
3
3
|
import { match } from '../helpers.js';
|
|
4
4
|
export default createRule({
|
|
5
|
+
meta: {
|
|
6
|
+
category: 'naming-convention',
|
|
7
|
+
},
|
|
5
8
|
defaultSeverity: 'warning',
|
|
6
9
|
defaultValue: null,
|
|
7
10
|
async verify({ document, report, t }) {
|
|
@@ -14,9 +17,9 @@ export default createRule({
|
|
|
14
17
|
}
|
|
15
18
|
const classAttr = attr.valueNode;
|
|
16
19
|
const classList = attr.value
|
|
17
|
-
.split(/\s+/
|
|
20
|
+
.split(/\s+/)
|
|
18
21
|
.map(c => c.trim())
|
|
19
|
-
.filter(
|
|
22
|
+
.filter(Boolean);
|
|
20
23
|
for (const className of classList) {
|
|
21
24
|
if (!classPatterns.some(pattern => match(className, pattern))) {
|
|
22
25
|
report({
|
package/lib/create-message.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ import type { AttributeType } from '@markuplint/ml-spec';
|
|
|
3
3
|
import type { UnmatchedResult } from '@markuplint/types';
|
|
4
4
|
import type { ReadonlyDeep } from 'type-fest';
|
|
5
5
|
export declare function createMessageValueExpected(t: Translator, baseTarget: string, type: ReadonlyDeep<AttributeType>, matches: UnmatchedResult): string;
|
|
6
|
-
export declare function __createMessageValueExpected(t: Translator, baseTarget: string, expected: string | null, matches: Pick<UnmatchedResult, 'partName' | 'reason' | 'raw' | 'candidate' | 'ref' | 'extra'>): string;
|
|
6
|
+
export declare function __createMessageValueExpected(t: Translator, baseTarget: string, expected: string | null, matches: Pick<UnmatchedResult, 'partName' | 'reason' | 'raw' | 'candidate' | 'ref' | 'extra' | 'fallbackTo'>): string;
|
package/lib/create-message.js
CHANGED
|
@@ -13,7 +13,7 @@ export function createMessageValueExpected(t, baseTarget, type, matches) {
|
|
|
13
13
|
}
|
|
14
14
|
const expected = createExpectedObject(tokenType, matches, t);
|
|
15
15
|
const message = [listDescriptionPart, __createMessageValueExpected(t, target, expected, matches)]
|
|
16
|
-
.filter(
|
|
16
|
+
.filter(Boolean)
|
|
17
17
|
.join(t('. '));
|
|
18
18
|
return message;
|
|
19
19
|
}
|
|
@@ -23,6 +23,7 @@ export function __createMessageValueExpected(t, baseTarget, expected, matches) {
|
|
|
23
23
|
let expectPart;
|
|
24
24
|
let unnecessaryPart;
|
|
25
25
|
let candidatePart;
|
|
26
|
+
let fallbackToPart;
|
|
26
27
|
let expectOrNeed = 'expects';
|
|
27
28
|
if (matches.partName) {
|
|
28
29
|
if (matches.partName === 'the content of the list') {
|
|
@@ -135,11 +136,11 @@ export function __createMessageValueExpected(t, baseTarget, expected, matches) {
|
|
|
135
136
|
if (lte != null && gte === lte) {
|
|
136
137
|
expectedDigits = t('{0} digits', gte);
|
|
137
138
|
}
|
|
138
|
-
else if (lte
|
|
139
|
-
expectedDigits = t('{0}
|
|
139
|
+
else if (lte == null) {
|
|
140
|
+
expectedDigits = t('{0} or more digits', gte);
|
|
140
141
|
}
|
|
141
142
|
else {
|
|
142
|
-
expectedDigits = t('{0}
|
|
143
|
+
expectedDigits = t('{0} to {1} digits', gte, lte);
|
|
143
144
|
}
|
|
144
145
|
if (!expected) {
|
|
145
146
|
expected = expectedDigits;
|
|
@@ -187,7 +188,12 @@ export function __createMessageValueExpected(t, baseTarget, expected, matches) {
|
|
|
187
188
|
if (matches.candidate) {
|
|
188
189
|
candidatePart = t('Did you mean "{0*}"?', matches.candidate);
|
|
189
190
|
}
|
|
190
|
-
|
|
191
|
+
if (matches.fallbackTo) {
|
|
192
|
+
fallbackToPart = t('The user agent will automatically use "{0*}" instead', matches.fallbackTo);
|
|
193
|
+
}
|
|
194
|
+
let message = [reasonPart, unnecessaryPart, expectPart, candidatePart, fallbackToPart]
|
|
195
|
+
.filter(Boolean)
|
|
196
|
+
.join(t('. '));
|
|
191
197
|
if (matches.ref) {
|
|
192
198
|
message += ` (${matches.ref})`;
|
|
193
199
|
}
|
|
@@ -220,19 +226,24 @@ function createExpectedObject(type, matches, t) {
|
|
|
220
226
|
}
|
|
221
227
|
function expectValueToWord(t, expect, type) {
|
|
222
228
|
switch (expect.type) {
|
|
223
|
-
case 'common':
|
|
229
|
+
case 'common': {
|
|
224
230
|
return expect.value;
|
|
225
|
-
|
|
231
|
+
}
|
|
232
|
+
case 'const': {
|
|
226
233
|
return expect.value ? `%${expect.value}%` : '';
|
|
227
|
-
|
|
234
|
+
}
|
|
235
|
+
case 'format': {
|
|
228
236
|
return t('the {0} format', expect.value);
|
|
229
|
-
|
|
237
|
+
}
|
|
238
|
+
case 'regexp': {
|
|
230
239
|
return t('{0} ({1})', 'regular expression', expect.value);
|
|
231
|
-
|
|
240
|
+
}
|
|
241
|
+
case 'syntax': {
|
|
232
242
|
if (isKeyword(type) && type[0] === '<') {
|
|
233
243
|
return t('the CSS Syntax "{0}"', expect.value);
|
|
234
244
|
}
|
|
235
245
|
return t('{0} syntax', expect.value);
|
|
246
|
+
}
|
|
236
247
|
}
|
|
237
248
|
}
|
|
238
249
|
function createExpectedNumber(t, type) {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { createRule, getAttrSpecs } from '@markuplint/ml-core';
|
|
2
2
|
export default createRule({
|
|
3
|
+
meta: {
|
|
4
|
+
category: 'validation',
|
|
5
|
+
},
|
|
3
6
|
async verify({ document, report, t }) {
|
|
4
7
|
await document.walkOn('Attr', attr => {
|
|
5
8
|
const attrSpecs = getAttrSpecs(attr.ownerElement, document.specs);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { createRule, getSpec } from '@markuplint/ml-core';
|
|
2
2
|
export default createRule({
|
|
3
|
+
meta: {
|
|
4
|
+
category: 'validation',
|
|
5
|
+
},
|
|
3
6
|
async verify({ document, report, t }) {
|
|
4
7
|
await document.walkOn('Element', el => {
|
|
5
8
|
if (!(el.namespaceURI === 'http://www.w3.org/1999/xhtml' ||
|
|
@@ -9,7 +12,7 @@ export default createRule({
|
|
|
9
12
|
}
|
|
10
13
|
const spec = getSpec(el, document.specs.specs);
|
|
11
14
|
if (spec && (spec.obsolete != null || spec.deprecated || spec.nonStandard)) {
|
|
12
|
-
const message = t('{0} is {1:c}', t('the "{0*}" {1}', el.localName, 'element'), spec.deprecated ? 'deprecated' : spec.obsolete
|
|
15
|
+
const message = t('{0} is {1:c}', t('the "{0*}" {1}', el.localName, 'element'), spec.deprecated ? 'deprecated' : spec.obsolete == null ? 'non-standard' : 'obsolete');
|
|
13
16
|
report({
|
|
14
17
|
scope: el,
|
|
15
18
|
message,
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
export default createRule({
|
|
3
|
+
meta: {
|
|
4
|
+
category: 'validation',
|
|
5
|
+
},
|
|
3
6
|
defaultValue: [],
|
|
4
7
|
async verify({ document, report, t }) {
|
|
5
8
|
for (const query of document.rule.value) {
|
|
6
9
|
const elements = document.querySelectorAll(query);
|
|
7
|
-
|
|
10
|
+
for (const el of elements) {
|
|
8
11
|
const message = t('{0} is disallowed', t('the "{0*}" {1}', query, 'element'));
|
|
9
12
|
report({
|
|
10
13
|
scope: el,
|
|
11
14
|
message,
|
|
12
15
|
});
|
|
13
|
-
}
|
|
16
|
+
}
|
|
14
17
|
}
|
|
15
18
|
await document.walkOn('Element', el => {
|
|
16
19
|
if (el.rule.value === document.rule.value) {
|
|
@@ -18,13 +21,13 @@ export default createRule({
|
|
|
18
21
|
}
|
|
19
22
|
for (const query of el.rule.value) {
|
|
20
23
|
const elements = el.querySelectorAll(query);
|
|
21
|
-
|
|
24
|
+
for (const el of elements) {
|
|
22
25
|
const message = t('{0} is disallowed', t('the "{0*}" {1}', query, 'element'));
|
|
23
26
|
report({
|
|
24
27
|
scope: el,
|
|
25
28
|
message,
|
|
26
29
|
});
|
|
27
|
-
}
|
|
30
|
+
}
|
|
28
31
|
}
|
|
29
32
|
});
|
|
30
33
|
},
|
package/lib/doctype/index.js
CHANGED
package/lib/end-tag/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
import { isVoidElement } from '@markuplint/ml-spec';
|
|
3
3
|
export default createRule({
|
|
4
|
+
meta: {
|
|
5
|
+
category: 'style',
|
|
6
|
+
},
|
|
4
7
|
defaultSeverity: 'warning',
|
|
5
8
|
async verify({ document, report, t }) {
|
|
6
9
|
if (document.endTag === 'never') {
|
package/lib/helpers.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
1
6
|
var _Collection_items;
|
|
2
|
-
import { __classPrivateFieldGet } from "tslib";
|
|
3
7
|
// @ts-ignore
|
|
4
8
|
import structuredClone from '@ungap/structured-clone';
|
|
5
9
|
import { attrCheck } from './attr-check.js';
|
|
@@ -13,7 +17,7 @@ node, condition) {
|
|
|
13
17
|
return node.matches(condSelector);
|
|
14
18
|
}
|
|
15
19
|
export function match(needle, pattern) {
|
|
16
|
-
const matches = pattern.match(/^\/(.*)\/(
|
|
20
|
+
const matches = pattern.match(/^\/(.*)\/([gim])*$/);
|
|
17
21
|
if (matches && matches[1]) {
|
|
18
22
|
const re = matches[1];
|
|
19
23
|
const flag = matches[2];
|
|
@@ -87,7 +91,7 @@ export function toNormalizedValue(value, spec) {
|
|
|
87
91
|
}
|
|
88
92
|
if (typeof spec.type === 'string') {
|
|
89
93
|
if (spec.type[0] === '<') {
|
|
90
|
-
normalized = normalized.toLowerCase().trim().
|
|
94
|
+
normalized = normalized.toLowerCase().trim().replaceAll(/\s+/g, ' ');
|
|
91
95
|
}
|
|
92
96
|
}
|
|
93
97
|
else {
|
|
@@ -99,10 +103,10 @@ export function toNormalizedValue(value, spec) {
|
|
|
99
103
|
normalized = normalized.trim();
|
|
100
104
|
}
|
|
101
105
|
if (spec.type.separator === 'space') {
|
|
102
|
-
normalized = normalized.
|
|
106
|
+
normalized = normalized.replaceAll(/\s+/g, ' ');
|
|
103
107
|
}
|
|
104
108
|
if (spec.type.separator === 'comma') {
|
|
105
|
-
normalized = normalized.
|
|
109
|
+
normalized = normalized.replaceAll(/\s*,\s*/g, ',');
|
|
106
110
|
}
|
|
107
111
|
}
|
|
108
112
|
}
|
|
@@ -157,7 +161,7 @@ export class Collection {
|
|
|
157
161
|
}
|
|
158
162
|
}
|
|
159
163
|
toArray() {
|
|
160
|
-
return Object.freeze(
|
|
164
|
+
return Object.freeze([...__classPrivateFieldGet(this, _Collection_items, "f")]);
|
|
161
165
|
}
|
|
162
166
|
}
|
|
163
167
|
export function deepCopy(value) {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
export default createRule({
|
|
3
|
+
meta: {
|
|
4
|
+
category: 'validation',
|
|
5
|
+
},
|
|
3
6
|
async verify({ document, report, t }) {
|
|
4
7
|
const message = t('{0} is {1:c}', t('{0} of {1}', t('the {0}', 'value'), t('the "{0*}" {1}', 'id', 'attribute')), 'duplicated');
|
|
5
8
|
const idStack = [];
|
package/lib/index.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ declare const rules: {
|
|
|
72
72
|
}>>;
|
|
73
73
|
readonly 'no-hard-code-id': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
74
74
|
readonly 'no-refer-to-non-existent-id': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, {
|
|
75
|
-
ariaVersion: "1.
|
|
75
|
+
ariaVersion: "1.2" | "1.1" | "1.3";
|
|
76
76
|
fragmentRefersNameAttr: boolean;
|
|
77
77
|
}>>;
|
|
78
78
|
readonly 'no-use-event-handler-attr': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, {
|
|
@@ -81,7 +81,7 @@ declare const rules: {
|
|
|
81
81
|
readonly 'permitted-contents': Readonly<import("@markuplint/ml-core").RuleSeed<import("./permitted-contents/types.js").TagRule[], import("./permitted-contents/types.js").Options>>;
|
|
82
82
|
readonly 'placeholder-label-option': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, undefined>>;
|
|
83
83
|
readonly 'require-accessible-name': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, {
|
|
84
|
-
ariaVersion: "1.
|
|
84
|
+
ariaVersion: "1.2" | "1.1" | "1.3";
|
|
85
85
|
}>>;
|
|
86
86
|
readonly 'require-datetime': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, {
|
|
87
87
|
langs?: import("./require-datetime/types.js").Lang[] | undefined;
|
|
@@ -96,6 +96,10 @@ declare const rules: {
|
|
|
96
96
|
readonly 'required-h1': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./required-h1/index.js").Options>>;
|
|
97
97
|
readonly 'use-list': Readonly<import("@markuplint/ml-core").RuleSeed<readonly string[], {
|
|
98
98
|
spaceNeededBullets?: string[] | undefined;
|
|
99
|
+
noPrev?: boolean | undefined;
|
|
100
|
+
prevElement?: boolean | undefined;
|
|
101
|
+
prevComment?: boolean | undefined;
|
|
102
|
+
prevCodeBlock?: boolean | undefined;
|
|
99
103
|
}>>;
|
|
100
104
|
readonly 'wai-aria': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
101
105
|
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createRule, getAttrSpecs } from '@markuplint/ml-core';
|
|
2
2
|
import { toNoEmptyStringArrayFromStringOrArray } from '@markuplint/shared';
|
|
3
3
|
export default createRule({
|
|
4
|
+
meta: {
|
|
5
|
+
category: 'style',
|
|
6
|
+
},
|
|
4
7
|
defaultSeverity: 'warning',
|
|
5
8
|
async verify({ document, report, t }) {
|
|
6
9
|
await document.walkOn('Attr', attr => {
|
|
@@ -4,6 +4,9 @@ import { log as ruleLog } from '../debug.js';
|
|
|
4
4
|
import { isValidAttr, match } from '../helpers.js';
|
|
5
5
|
const log = ruleLog.extend('invalid-attr');
|
|
6
6
|
export default createRule({
|
|
7
|
+
meta: {
|
|
8
|
+
category: 'validation',
|
|
9
|
+
},
|
|
7
10
|
defaultOptions: {},
|
|
8
11
|
async verify({ document, report, t }) {
|
|
9
12
|
await document.walkOn('Attr', attr => {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
const controlSelector = ["input:not([type='hidden' i])", 'select', 'textarea'].join(',');
|
|
3
3
|
export default createRule({
|
|
4
|
+
meta: {
|
|
5
|
+
category: 'a11y',
|
|
6
|
+
},
|
|
4
7
|
defaultSeverity: 'warning',
|
|
5
8
|
async verify({ document, report, t }) {
|
|
6
9
|
await document.walkOn('Element', el => {
|
|
@@ -10,6 +10,9 @@ const selectors = {
|
|
|
10
10
|
};
|
|
11
11
|
const topLevelRoles = ['banner', 'main', 'complementary', 'contentinfo'];
|
|
12
12
|
export default createRule({
|
|
13
|
+
meta: {
|
|
14
|
+
category: 'a11y',
|
|
15
|
+
},
|
|
13
16
|
defaultSeverity: 'warning',
|
|
14
17
|
defaultOptions: {
|
|
15
18
|
ignoreRoles: [],
|
|
@@ -20,13 +23,13 @@ export default createRule({
|
|
|
20
23
|
return;
|
|
21
24
|
}
|
|
22
25
|
const roles = {
|
|
23
|
-
complementary:
|
|
24
|
-
contentinfo:
|
|
25
|
-
form:
|
|
26
|
-
banner:
|
|
27
|
-
main:
|
|
28
|
-
navigation:
|
|
29
|
-
region:
|
|
26
|
+
complementary: [...document.querySelectorAll(selectors.complementary.join(','))],
|
|
27
|
+
contentinfo: [...document.querySelectorAll(selectors.contentinfo.join(','))],
|
|
28
|
+
form: [...document.querySelectorAll(selectors.form.join(','))],
|
|
29
|
+
banner: [...document.querySelectorAll(selectors.banner.join(','))],
|
|
30
|
+
main: [...document.querySelectorAll(selectors.main.join(','))],
|
|
31
|
+
navigation: [...document.querySelectorAll(selectors.navigation.join(','))],
|
|
32
|
+
region: [...document.querySelectorAll(selectors.region.join(','))],
|
|
30
33
|
};
|
|
31
34
|
/**
|
|
32
35
|
* `<header>`
|
|
@@ -40,7 +43,7 @@ export default createRule({
|
|
|
40
43
|
* > - nav
|
|
41
44
|
* > - section
|
|
42
45
|
*/
|
|
43
|
-
const headers =
|
|
46
|
+
const headers = [...document.querySelectorAll('header')].filter(header => {
|
|
44
47
|
return !header.closest('article, aside, main, nav, section');
|
|
45
48
|
});
|
|
46
49
|
roles.banner.push(...headers);
|
|
@@ -56,7 +59,7 @@ export default createRule({
|
|
|
56
59
|
* > - nav
|
|
57
60
|
* > - section
|
|
58
61
|
*/
|
|
59
|
-
const footers =
|
|
62
|
+
const footers = [...document.querySelectorAll('footer')].filter(footer => {
|
|
60
63
|
return !footer.closest('article, aside, main, nav, section');
|
|
61
64
|
});
|
|
62
65
|
roles.contentinfo.push(...footers);
|
|
@@ -106,9 +109,7 @@ export default createRule({
|
|
|
106
109
|
function landmarkRoleElementUUIDList(
|
|
107
110
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
108
111
|
roleset) {
|
|
109
|
-
return Object.values(roleset)
|
|
110
|
-
.map(elements => elements.map(element => element.uuid))
|
|
111
|
-
.flat();
|
|
112
|
+
return Object.values(roleset).flatMap(elements => elements.map(element => element.uuid));
|
|
112
113
|
}
|
|
113
114
|
function hasLabel(
|
|
114
115
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createRule, getAttrSpecs } from '@markuplint/ml-core';
|
|
2
2
|
import { toNormalizedValue } from '../helpers.js';
|
|
3
3
|
export default createRule({
|
|
4
|
+
meta: {
|
|
5
|
+
category: 'style',
|
|
6
|
+
},
|
|
4
7
|
defaultSeverity: 'warning',
|
|
5
8
|
async verify({ document, report, t }) {
|
|
6
9
|
await document.walkOn('Attr', attr => {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
import { isNothingContentModel, isPalpableElement } from '@markuplint/ml-spec';
|
|
3
3
|
export default createRule({
|
|
4
|
+
meta: {
|
|
5
|
+
category: 'validation',
|
|
6
|
+
},
|
|
4
7
|
defaultSeverity: 'warning',
|
|
5
8
|
defaultOptions: {
|
|
6
9
|
extendsExposableElements: true,
|
|
@@ -28,7 +31,7 @@ export default createRule({
|
|
|
28
31
|
if (el.rule.options.ignoreIfAriaBusy && el.getAttribute('aria-busy') === 'true') {
|
|
29
32
|
return;
|
|
30
33
|
}
|
|
31
|
-
const isEmpty =
|
|
34
|
+
const isEmpty = [...el.childNodes].every(node => node.is(node.TEXT_NODE) && node.isWhitespace());
|
|
32
35
|
if (isEmpty) {
|
|
33
36
|
report({
|
|
34
37
|
scope: el,
|
|
@@ -3,6 +3,9 @@ import { ARIA_RECOMMENDED_VERSION } from '@markuplint/ml-spec';
|
|
|
3
3
|
import { decodeEntities, decodeHref } from '@markuplint/shared';
|
|
4
4
|
const HYPERLINK_SELECTOR = 'a[href], area[href]';
|
|
5
5
|
export default createRule({
|
|
6
|
+
meta: {
|
|
7
|
+
category: 'a11y',
|
|
8
|
+
},
|
|
6
9
|
defaultOptions: {
|
|
7
10
|
ariaVersion: ARIA_RECOMMENDED_VERSION,
|
|
8
11
|
fragmentRefersNameAttr: false,
|
|
@@ -16,10 +19,10 @@ export default createRule({
|
|
|
16
19
|
if (isMutable) {
|
|
17
20
|
return;
|
|
18
21
|
}
|
|
19
|
-
document.querySelectorAll('[id]')
|
|
22
|
+
for (const el of document.querySelectorAll('[id]')) {
|
|
20
23
|
const attr = el.getAttributeNode('id');
|
|
21
24
|
if (!attr) {
|
|
22
|
-
|
|
25
|
+
continue;
|
|
23
26
|
}
|
|
24
27
|
if (attr.isDynamicValue) {
|
|
25
28
|
hasDynamicId = true;
|
|
@@ -27,14 +30,14 @@ export default createRule({
|
|
|
27
30
|
if (attr.valueType !== 'code') {
|
|
28
31
|
idList.add(decodeEntities(attr.value));
|
|
29
32
|
}
|
|
30
|
-
}
|
|
33
|
+
}
|
|
31
34
|
if (hasDynamicId) {
|
|
32
35
|
return;
|
|
33
36
|
}
|
|
34
|
-
document.querySelectorAll('[name]')
|
|
37
|
+
for (const el of document.querySelectorAll('[name]')) {
|
|
35
38
|
const attr = el.getAttributeNode('name');
|
|
36
39
|
if (!attr) {
|
|
37
|
-
|
|
40
|
+
continue;
|
|
38
41
|
}
|
|
39
42
|
if (attr.isDynamicValue) {
|
|
40
43
|
hasDynamicName = true;
|
|
@@ -42,7 +45,7 @@ export default createRule({
|
|
|
42
45
|
if (attr.valueType !== 'code') {
|
|
43
46
|
nameList.add(decodeEntities(attr.value));
|
|
44
47
|
}
|
|
45
|
-
}
|
|
48
|
+
}
|
|
46
49
|
await document.walkOn('Attr', attr => {
|
|
47
50
|
const attrSpec = getAttrSpecs(attr.ownerElement, document.specs);
|
|
48
51
|
if (!attrSpec) {
|
|
@@ -75,7 +78,7 @@ export default createRule({
|
|
|
75
78
|
const refs = value
|
|
76
79
|
.split(spec.type.separator === 'space' ? /\s/ : ',')
|
|
77
80
|
.map(id => id.trim())
|
|
78
|
-
.filter(
|
|
81
|
+
.filter(Boolean);
|
|
79
82
|
for (const ref of refs) {
|
|
80
83
|
if (!idList.has(ref)) {
|
|
81
84
|
report({
|
|
@@ -105,7 +108,7 @@ export default createRule({
|
|
|
105
108
|
const refs = value
|
|
106
109
|
.split(/\s/)
|
|
107
110
|
.map(id => id.trim())
|
|
108
|
-
.filter(
|
|
111
|
+
.filter(Boolean);
|
|
109
112
|
for (const ref of refs) {
|
|
110
113
|
if (!idList.has(ref)) {
|
|
111
114
|
report({
|