@markuplint/rules 4.0.0-alpha.3 → 4.0.0-dev.28
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/lib/attr-check.js +2 -2
- package/lib/character-reference/index.js +5 -5
- package/lib/class-naming/index.js +1 -1
- package/lib/create-message.js +17 -10
- package/lib/deprecated-element/index.js +1 -1
- package/lib/disallowed-element/index.js +4 -4
- package/lib/helpers.js +10 -6
- package/lib/index.d.ts +17 -17
- package/lib/landmark-roles/index.js +10 -12
- package/lib/no-empty-palpable-content/index.js +1 -1
- package/lib/no-refer-to-non-existent-id/index.d.ts +1 -1
- package/lib/no-refer-to-non-existent-id/index.js +8 -8
- package/lib/permitted-contents/choice.js +1 -1
- 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 +4 -4
- package/lib/require-datetime/index.js +5 -5
- package/lib/require-datetime/utils.js +1 -1
- package/lib/required-element/index.js +1 -1
- package/lib/use-list/index.js +1 -1
- 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 +2 -2
- package/lib/wai-aria/checkings/value.js +2 -2
- package/package.json +10 -11
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,11 +1,11 @@
|
|
|
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
5
|
async verify({ document, report, t }) {
|
|
6
6
|
const targetNodes = [];
|
|
7
7
|
await document.walkOn('Text', node => {
|
|
8
|
-
if (node.parentNode && ignoreParentElement.
|
|
8
|
+
if (node.parentNode && ignoreParentElement.has(node.parentNode.nodeName.toLowerCase())) {
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
11
|
const severity = node.rule.severity;
|
|
@@ -41,14 +41,14 @@ export default createRule({
|
|
|
41
41
|
if (!('scope' in targetNode && 'line' in targetNode && targetNode.line != null)) {
|
|
42
42
|
continue;
|
|
43
43
|
}
|
|
44
|
-
const escapedText = targetNode.raw.
|
|
45
|
-
getLocationFromChars(defaultChars, escapedText, targetNode.line, targetNode.col)
|
|
44
|
+
const escapedText = targetNode.raw.replaceAll(/&(?:[a-z]+|#\d+|#x[\da-f]+);/gi, $0 => '*'.repeat($0.length));
|
|
45
|
+
for (const location of getLocationFromChars(defaultChars, escapedText, targetNode.line, targetNode.col)) {
|
|
46
46
|
report({
|
|
47
47
|
scope: targetNode.scope,
|
|
48
48
|
message: targetNode.message,
|
|
49
49
|
...location,
|
|
50
50
|
});
|
|
51
|
-
}
|
|
51
|
+
}
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
});
|
|
@@ -16,7 +16,7 @@ export default createRule({
|
|
|
16
16
|
const classList = attr.value
|
|
17
17
|
.split(/\s+/g)
|
|
18
18
|
.map(c => c.trim())
|
|
19
|
-
.filter(
|
|
19
|
+
.filter(Boolean);
|
|
20
20
|
for (const className of classList) {
|
|
21
21
|
if (!classPatterns.some(pattern => match(className, pattern))) {
|
|
22
22
|
report({
|
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
|
}
|
|
@@ -136,11 +136,11 @@ export function __createMessageValueExpected(t, baseTarget, expected, matches) {
|
|
|
136
136
|
if (lte != null && gte === lte) {
|
|
137
137
|
expectedDigits = t('{0} digits', gte);
|
|
138
138
|
}
|
|
139
|
-
else if (lte
|
|
140
|
-
expectedDigits = t('{0}
|
|
139
|
+
else if (lte == null) {
|
|
140
|
+
expectedDigits = t('{0} or more digits', gte);
|
|
141
141
|
}
|
|
142
142
|
else {
|
|
143
|
-
expectedDigits = t('{0}
|
|
143
|
+
expectedDigits = t('{0} to {1} digits', gte, lte);
|
|
144
144
|
}
|
|
145
145
|
if (!expected) {
|
|
146
146
|
expected = expectedDigits;
|
|
@@ -191,7 +191,9 @@ export function __createMessageValueExpected(t, baseTarget, expected, matches) {
|
|
|
191
191
|
if (matches.fallbackTo) {
|
|
192
192
|
fallbackToPart = t('The user agent will automatically use "{0*}" instead', matches.fallbackTo);
|
|
193
193
|
}
|
|
194
|
-
let message = [reasonPart, unnecessaryPart, expectPart, candidatePart, fallbackToPart]
|
|
194
|
+
let message = [reasonPart, unnecessaryPart, expectPart, candidatePart, fallbackToPart]
|
|
195
|
+
.filter(Boolean)
|
|
196
|
+
.join(t('. '));
|
|
195
197
|
if (matches.ref) {
|
|
196
198
|
message += ` (${matches.ref})`;
|
|
197
199
|
}
|
|
@@ -224,19 +226,24 @@ function createExpectedObject(type, matches, t) {
|
|
|
224
226
|
}
|
|
225
227
|
function expectValueToWord(t, expect, type) {
|
|
226
228
|
switch (expect.type) {
|
|
227
|
-
case 'common':
|
|
229
|
+
case 'common': {
|
|
228
230
|
return expect.value;
|
|
229
|
-
|
|
231
|
+
}
|
|
232
|
+
case 'const': {
|
|
230
233
|
return expect.value ? `%${expect.value}%` : '';
|
|
231
|
-
|
|
234
|
+
}
|
|
235
|
+
case 'format': {
|
|
232
236
|
return t('the {0} format', expect.value);
|
|
233
|
-
|
|
237
|
+
}
|
|
238
|
+
case 'regexp': {
|
|
234
239
|
return t('{0} ({1})', 'regular expression', expect.value);
|
|
235
|
-
|
|
240
|
+
}
|
|
241
|
+
case 'syntax': {
|
|
236
242
|
if (isKeyword(type) && type[0] === '<') {
|
|
237
243
|
return t('the CSS Syntax "{0}"', expect.value);
|
|
238
244
|
}
|
|
239
245
|
return t('{0} syntax', expect.value);
|
|
246
|
+
}
|
|
240
247
|
}
|
|
241
248
|
}
|
|
242
249
|
function createExpectedNumber(t, type) {
|
|
@@ -9,7 +9,7 @@ export default createRule({
|
|
|
9
9
|
}
|
|
10
10
|
const spec = getSpec(el, document.specs.specs);
|
|
11
11
|
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
|
|
12
|
+
const message = t('{0} is {1:c}', t('the "{0*}" {1}', el.localName, 'element'), spec.deprecated ? 'deprecated' : spec.obsolete == null ? 'non-standard' : 'obsolete');
|
|
13
13
|
report({
|
|
14
14
|
scope: el,
|
|
15
15
|
message,
|
|
@@ -4,13 +4,13 @@ export default createRule({
|
|
|
4
4
|
async verify({ document, report, t }) {
|
|
5
5
|
for (const query of document.rule.value) {
|
|
6
6
|
const elements = document.querySelectorAll(query);
|
|
7
|
-
|
|
7
|
+
for (const el of elements) {
|
|
8
8
|
const message = t('{0} is disallowed', t('the "{0*}" {1}', query, 'element'));
|
|
9
9
|
report({
|
|
10
10
|
scope: el,
|
|
11
11
|
message,
|
|
12
12
|
});
|
|
13
|
-
}
|
|
13
|
+
}
|
|
14
14
|
}
|
|
15
15
|
await document.walkOn('Element', el => {
|
|
16
16
|
if (el.rule.value === document.rule.value) {
|
|
@@ -18,13 +18,13 @@ export default createRule({
|
|
|
18
18
|
}
|
|
19
19
|
for (const query of el.rule.value) {
|
|
20
20
|
const elements = el.querySelectorAll(query);
|
|
21
|
-
|
|
21
|
+
for (const el of elements) {
|
|
22
22
|
const message = t('{0} is disallowed', t('the "{0*}" {1}', query, 'element'));
|
|
23
23
|
report({
|
|
24
24
|
scope: el,
|
|
25
25
|
message,
|
|
26
26
|
});
|
|
27
|
-
}
|
|
27
|
+
}
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
},
|
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) {
|
package/lib/index.d.ts
CHANGED
|
@@ -15,38 +15,38 @@ declare const rules: {
|
|
|
15
15
|
readonly 'id-duplication': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
16
16
|
readonly 'ineffective-attr': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
17
17
|
readonly 'invalid-attr': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, {
|
|
18
|
-
allowAttrs?:
|
|
18
|
+
allowAttrs?: Record<string, {
|
|
19
|
+
enum: [string, ...string[]];
|
|
20
|
+
} | {
|
|
21
|
+
pattern: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: import("packages/@markuplint/ml-spec/lib/index.js").AttributeType;
|
|
24
|
+
}> | (string | {
|
|
19
25
|
name: string;
|
|
20
|
-
value: ({
|
|
26
|
+
value: import("packages/@markuplint/ml-spec/lib/index.js").AttributeType | ({
|
|
21
27
|
enum: [string, ...string[]];
|
|
22
28
|
} | {
|
|
23
29
|
pattern: string;
|
|
24
30
|
} | {
|
|
25
31
|
type: import("packages/@markuplint/ml-spec/lib/index.js").AttributeType;
|
|
26
|
-
})
|
|
27
|
-
})[] |
|
|
32
|
+
});
|
|
33
|
+
})[] | undefined;
|
|
34
|
+
disallowAttrs?: Record<string, {
|
|
28
35
|
enum: [string, ...string[]];
|
|
29
36
|
} | {
|
|
30
37
|
pattern: string;
|
|
31
38
|
} | {
|
|
32
39
|
type: import("packages/@markuplint/ml-spec/lib/index.js").AttributeType;
|
|
33
|
-
}> |
|
|
34
|
-
disallowAttrs?: (string | {
|
|
40
|
+
}> | (string | {
|
|
35
41
|
name: string;
|
|
36
|
-
value: ({
|
|
42
|
+
value: import("packages/@markuplint/ml-spec/lib/index.js").AttributeType | ({
|
|
37
43
|
enum: [string, ...string[]];
|
|
38
44
|
} | {
|
|
39
45
|
pattern: string;
|
|
40
46
|
} | {
|
|
41
47
|
type: import("packages/@markuplint/ml-spec/lib/index.js").AttributeType;
|
|
42
|
-
})
|
|
43
|
-
})[] |
|
|
44
|
-
enum: [string, ...string[]];
|
|
45
|
-
} | {
|
|
46
|
-
pattern: string;
|
|
47
|
-
} | {
|
|
48
|
-
type: import("packages/@markuplint/ml-spec/lib/index.js").AttributeType;
|
|
49
|
-
}> | undefined;
|
|
48
|
+
});
|
|
49
|
+
})[] | undefined;
|
|
50
50
|
ignoreAttrNamePrefix?: string | string[] | undefined;
|
|
51
51
|
allowToAddPropertiesForPretender?: boolean | undefined;
|
|
52
52
|
attrs?: Record<string, ({
|
|
@@ -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;
|
|
@@ -20,13 +20,13 @@ export default createRule({
|
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
22
|
const roles = {
|
|
23
|
-
complementary:
|
|
24
|
-
contentinfo:
|
|
25
|
-
form:
|
|
26
|
-
banner:
|
|
27
|
-
main:
|
|
28
|
-
navigation:
|
|
29
|
-
region:
|
|
23
|
+
complementary: [...document.querySelectorAll(selectors.complementary.join(','))],
|
|
24
|
+
contentinfo: [...document.querySelectorAll(selectors.contentinfo.join(','))],
|
|
25
|
+
form: [...document.querySelectorAll(selectors.form.join(','))],
|
|
26
|
+
banner: [...document.querySelectorAll(selectors.banner.join(','))],
|
|
27
|
+
main: [...document.querySelectorAll(selectors.main.join(','))],
|
|
28
|
+
navigation: [...document.querySelectorAll(selectors.navigation.join(','))],
|
|
29
|
+
region: [...document.querySelectorAll(selectors.region.join(','))],
|
|
30
30
|
};
|
|
31
31
|
/**
|
|
32
32
|
* `<header>`
|
|
@@ -40,7 +40,7 @@ export default createRule({
|
|
|
40
40
|
* > - nav
|
|
41
41
|
* > - section
|
|
42
42
|
*/
|
|
43
|
-
const headers =
|
|
43
|
+
const headers = [...document.querySelectorAll('header')].filter(header => {
|
|
44
44
|
return !header.closest('article, aside, main, nav, section');
|
|
45
45
|
});
|
|
46
46
|
roles.banner.push(...headers);
|
|
@@ -56,7 +56,7 @@ export default createRule({
|
|
|
56
56
|
* > - nav
|
|
57
57
|
* > - section
|
|
58
58
|
*/
|
|
59
|
-
const footers =
|
|
59
|
+
const footers = [...document.querySelectorAll('footer')].filter(footer => {
|
|
60
60
|
return !footer.closest('article, aside, main, nav, section');
|
|
61
61
|
});
|
|
62
62
|
roles.contentinfo.push(...footers);
|
|
@@ -106,9 +106,7 @@ export default createRule({
|
|
|
106
106
|
function landmarkRoleElementUUIDList(
|
|
107
107
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
108
108
|
roleset) {
|
|
109
|
-
return Object.values(roleset)
|
|
110
|
-
.map(elements => elements.map(element => element.uuid))
|
|
111
|
-
.flat();
|
|
109
|
+
return Object.values(roleset).flatMap(elements => elements.map(element => element.uuid));
|
|
112
110
|
}
|
|
113
111
|
function hasLabel(
|
|
114
112
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -28,7 +28,7 @@ export default createRule({
|
|
|
28
28
|
if (el.rule.options.ignoreIfAriaBusy && el.getAttribute('aria-busy') === 'true') {
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
|
-
const isEmpty =
|
|
31
|
+
const isEmpty = [...el.childNodes].every(node => node.is(node.TEXT_NODE) && node.isWhitespace());
|
|
32
32
|
if (isEmpty) {
|
|
33
33
|
report({
|
|
34
34
|
scope: el,
|
|
@@ -16,10 +16,10 @@ export default createRule({
|
|
|
16
16
|
if (isMutable) {
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
|
-
document.querySelectorAll('[id]')
|
|
19
|
+
for (const el of document.querySelectorAll('[id]')) {
|
|
20
20
|
const attr = el.getAttributeNode('id');
|
|
21
21
|
if (!attr) {
|
|
22
|
-
|
|
22
|
+
continue;
|
|
23
23
|
}
|
|
24
24
|
if (attr.isDynamicValue) {
|
|
25
25
|
hasDynamicId = true;
|
|
@@ -27,14 +27,14 @@ export default createRule({
|
|
|
27
27
|
if (attr.valueType !== 'code') {
|
|
28
28
|
idList.add(decodeEntities(attr.value));
|
|
29
29
|
}
|
|
30
|
-
}
|
|
30
|
+
}
|
|
31
31
|
if (hasDynamicId) {
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
-
document.querySelectorAll('[name]')
|
|
34
|
+
for (const el of document.querySelectorAll('[name]')) {
|
|
35
35
|
const attr = el.getAttributeNode('name');
|
|
36
36
|
if (!attr) {
|
|
37
|
-
|
|
37
|
+
continue;
|
|
38
38
|
}
|
|
39
39
|
if (attr.isDynamicValue) {
|
|
40
40
|
hasDynamicName = true;
|
|
@@ -42,7 +42,7 @@ export default createRule({
|
|
|
42
42
|
if (attr.valueType !== 'code') {
|
|
43
43
|
nameList.add(decodeEntities(attr.value));
|
|
44
44
|
}
|
|
45
|
-
}
|
|
45
|
+
}
|
|
46
46
|
await document.walkOn('Attr', attr => {
|
|
47
47
|
const attrSpec = getAttrSpecs(attr.ownerElement, document.specs);
|
|
48
48
|
if (!attrSpec) {
|
|
@@ -75,7 +75,7 @@ export default createRule({
|
|
|
75
75
|
const refs = value
|
|
76
76
|
.split(spec.type.separator === 'space' ? /\s/ : ',')
|
|
77
77
|
.map(id => id.trim())
|
|
78
|
-
.filter(
|
|
78
|
+
.filter(Boolean);
|
|
79
79
|
for (const ref of refs) {
|
|
80
80
|
if (!idList.has(ref)) {
|
|
81
81
|
report({
|
|
@@ -105,7 +105,7 @@ export default createRule({
|
|
|
105
105
|
const refs = value
|
|
106
106
|
.split(/\s/)
|
|
107
107
|
.map(id => id.trim())
|
|
108
|
-
.filter(
|
|
108
|
+
.filter(Boolean);
|
|
109
109
|
for (const ref of refs) {
|
|
110
110
|
if (!idList.has(ref)) {
|
|
111
111
|
report({
|
|
@@ -14,7 +14,7 @@ elements, specs, options, depth) {
|
|
|
14
14
|
const result = order(some, collection.unmatched, specs, options, depth + 1);
|
|
15
15
|
if (result.type === 'MATCHED' ||
|
|
16
16
|
result.type === 'MATCHED_ZERO' ||
|
|
17
|
-
(result.type === 'UNEXPECTED_EXTRA_NODE' && result.matched.length
|
|
17
|
+
(result.type === 'UNEXPECTED_EXTRA_NODE' && result.matched.length > 0)) {
|
|
18
18
|
choiceLog('Results[%s]: %s', i, choiceLogString(pattern.choice, i));
|
|
19
19
|
return {
|
|
20
20
|
type: result.type,
|
|
@@ -41,9 +41,9 @@ nodes, specs, options, depth) {
|
|
|
41
41
|
if (!barelyMatchedResult) {
|
|
42
42
|
throw new Error('Unreachable code');
|
|
43
43
|
}
|
|
44
|
-
orderLog('Result (%s): %s%s', result.type, collection.toString(true), barelyMatchedResult.hint.missing?.barelyMatchedElements
|
|
45
|
-
?
|
|
46
|
-
:
|
|
44
|
+
orderLog('Result (%s): %s%s', result.type, collection.toString(true), barelyMatchedResult.hint.missing?.barelyMatchedElements == null
|
|
45
|
+
? ''
|
|
46
|
+
: `; But ${barelyMatchedResult.hint.missing.barelyMatchedElements} elements hit out of pattern`);
|
|
47
47
|
return {
|
|
48
48
|
type: barelyMatchedResult.type,
|
|
49
49
|
matched: collection.matched,
|
|
@@ -30,7 +30,7 @@ nodes, specs, options) {
|
|
|
30
30
|
const collection = new Collection(getChildNodesWithoutWhitespaces(node));
|
|
31
31
|
let unmatched;
|
|
32
32
|
if (noTransparentModels.length > 0) {
|
|
33
|
-
const result = order(noTransparentModels, collection.unmatched, specs, options,
|
|
33
|
+
const result = order(noTransparentModels, collection.unmatched, specs, options, Number.POSITIVE_INFINITY);
|
|
34
34
|
unmatched = result.unmatched;
|
|
35
35
|
}
|
|
36
36
|
else {
|
|
@@ -7,7 +7,7 @@ nodes) {
|
|
|
7
7
|
transparentLog('Skipped');
|
|
8
8
|
return {
|
|
9
9
|
type: nodes.length === 0 ? 'MATCHED_ZERO' : 'MATCHED',
|
|
10
|
-
matched: nodes
|
|
10
|
+
matched: [...nodes],
|
|
11
11
|
unmatched: [],
|
|
12
12
|
zeroMatch: nodes.length === 0,
|
|
13
13
|
query: 'transparent',
|
|
@@ -17,7 +17,7 @@ nodes) {
|
|
|
17
17
|
transparentLog('Transparent model element is component root');
|
|
18
18
|
return {
|
|
19
19
|
type: 'MATCHED',
|
|
20
|
-
matched: nodes
|
|
20
|
+
matched: [...nodes],
|
|
21
21
|
unmatched: [],
|
|
22
22
|
zeroMatch: false,
|
|
23
23
|
query: 'transparent',
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
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");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
1
12
|
var _Collection_locked, _Collection_matched, _Collection_nodes, _Collection_origin;
|
|
2
|
-
import { __classPrivateFieldGet, __classPrivateFieldSet } from "tslib";
|
|
3
13
|
import { createSelector } from '@markuplint/selector';
|
|
4
14
|
import { bgGreen, green, bgRed, bgBlue, blue, bgMagenta, cyan } from './debug.js';
|
|
5
15
|
import { transparentMode } from './represent-transparent-nodes.js';
|
|
@@ -11,7 +21,7 @@ el) {
|
|
|
11
21
|
if (nodes) {
|
|
12
22
|
return nodes;
|
|
13
23
|
}
|
|
14
|
-
nodes =
|
|
24
|
+
nodes = [...el.childNodes].filter(node => {
|
|
15
25
|
return !(node.is(node.TEXT_NODE) && node.isWhitespace());
|
|
16
26
|
});
|
|
17
27
|
getChildNodesWithoutWhitespacesCaches.set(el, nodes);
|
|
@@ -44,10 +54,8 @@ node, specs) {
|
|
|
44
54
|
};
|
|
45
55
|
}
|
|
46
56
|
const not = selectorResult
|
|
47
|
-
.
|
|
48
|
-
.
|
|
49
|
-
.map(descendants)
|
|
50
|
-
.flat()
|
|
57
|
+
.flatMap(r => (r.matched ? [] : r.not ?? []))
|
|
58
|
+
.flatMap(descendants)
|
|
51
59
|
.shift();
|
|
52
60
|
return {
|
|
53
61
|
matched: false,
|
|
@@ -57,7 +65,7 @@ node, specs) {
|
|
|
57
65
|
function descendants(
|
|
58
66
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
59
67
|
selectorResult) {
|
|
60
|
-
let nodes = selectorResult.nodes
|
|
68
|
+
let nodes = [...selectorResult.nodes];
|
|
61
69
|
while (selectorResult.has.length > 0) {
|
|
62
70
|
for (const dep of selectorResult.has) {
|
|
63
71
|
if (dep.has.length === 0) {
|
|
@@ -109,13 +117,13 @@ export function normalizeModel(pattern) {
|
|
|
109
117
|
else if (isOneOrMore(pattern)) {
|
|
110
118
|
model = pattern.oneOrMore;
|
|
111
119
|
min = 1;
|
|
112
|
-
max = Math.max(pattern.max ??
|
|
120
|
+
max = Math.max(pattern.max ?? Number.POSITIVE_INFINITY, 1);
|
|
113
121
|
missingType = 'MISSING_NODE_ONE_OR_MORE';
|
|
114
122
|
}
|
|
115
123
|
else if (isZeroOrMore(pattern)) {
|
|
116
124
|
model = pattern.zeroOrMore;
|
|
117
125
|
min = 0;
|
|
118
|
-
max = Math.max(pattern.max ??
|
|
126
|
+
max = Math.max(pattern.max ?? Number.POSITIVE_INFINITY, 1);
|
|
119
127
|
}
|
|
120
128
|
else {
|
|
121
129
|
throw new Error('Unreachable code');
|
|
@@ -157,11 +165,11 @@ b) {
|
|
|
157
165
|
}
|
|
158
166
|
export function cleanObject(object) {
|
|
159
167
|
const newObject = {};
|
|
160
|
-
|
|
168
|
+
for (const [key, value] of Object.entries(object)) {
|
|
161
169
|
if (value !== undefined) {
|
|
162
170
|
newObject[key] = value;
|
|
163
171
|
}
|
|
164
|
-
}
|
|
172
|
+
}
|
|
165
173
|
return newObject;
|
|
166
174
|
}
|
|
167
175
|
export class Collection {
|
|
@@ -172,20 +180,20 @@ export class Collection {
|
|
|
172
180
|
_Collection_matched.set(this, new Set());
|
|
173
181
|
_Collection_nodes.set(this, void 0);
|
|
174
182
|
_Collection_origin.set(this, void 0);
|
|
175
|
-
__classPrivateFieldSet(this, _Collection_origin, origin
|
|
183
|
+
__classPrivateFieldSet(this, _Collection_origin, [...origin], "f");
|
|
176
184
|
__classPrivateFieldSet(this, _Collection_nodes, new Set(__classPrivateFieldGet(this, _Collection_origin, "f")), "f");
|
|
177
185
|
}
|
|
178
186
|
get matched() {
|
|
179
|
-
return
|
|
187
|
+
return [...__classPrivateFieldGet(this, _Collection_matched, "f")];
|
|
180
188
|
}
|
|
181
189
|
get matchedCount() {
|
|
182
190
|
return __classPrivateFieldGet(this, _Collection_matched, "f").size;
|
|
183
191
|
}
|
|
184
192
|
get nodes() {
|
|
185
|
-
return __classPrivateFieldGet(this, _Collection_origin, "f")
|
|
193
|
+
return [...__classPrivateFieldGet(this, _Collection_origin, "f")];
|
|
186
194
|
}
|
|
187
195
|
get unmatched() {
|
|
188
|
-
return
|
|
196
|
+
return [...__classPrivateFieldGet(this, _Collection_nodes, "f")].filter(n => !__classPrivateFieldGet(this, _Collection_matched, "f").has(n));
|
|
189
197
|
}
|
|
190
198
|
addMatched(
|
|
191
199
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -206,13 +214,14 @@ export class Collection {
|
|
|
206
214
|
__classPrivateFieldSet(this, _Collection_locked, new Set(__classPrivateFieldGet(this, _Collection_matched, "f")), "f");
|
|
207
215
|
}
|
|
208
216
|
max(max) {
|
|
209
|
-
const sliced =
|
|
210
|
-
|
|
217
|
+
const sliced = [...__classPrivateFieldGet(this, _Collection_matched, "f")].slice(max);
|
|
218
|
+
for (const n of sliced)
|
|
219
|
+
__classPrivateFieldGet(this, _Collection_matched, "f").delete(n);
|
|
211
220
|
}
|
|
212
221
|
toString(highlightExtraNodes = false) {
|
|
213
222
|
const out = [];
|
|
214
223
|
for (const n of __classPrivateFieldGet(this, _Collection_origin, "f")) {
|
|
215
|
-
const raw = n.is(n.TEXT_NODE) ? `:text(${n.raw.
|
|
224
|
+
const raw = n.is(n.TEXT_NODE) ? `:text(${n.raw.replaceAll('\n', '\\n')})` : n.raw;
|
|
216
225
|
if (__classPrivateFieldGet(this, _Collection_locked, "f").has(n)) {
|
|
217
226
|
if (transparentMode.has(n)) {
|
|
218
227
|
out.push(bgBlue.bold(raw));
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
export default createRule({
|
|
3
3
|
verify({ document, report, t }) {
|
|
4
|
-
document.querySelectorAll('select')
|
|
4
|
+
for (const select of document.querySelectorAll('select')) {
|
|
5
5
|
if (hasPlaceholderLabelOption(select)) {
|
|
6
|
-
|
|
6
|
+
continue;
|
|
7
7
|
}
|
|
8
8
|
if (!needPlaceholderLabelOption(select)) {
|
|
9
|
-
|
|
9
|
+
continue;
|
|
10
10
|
}
|
|
11
11
|
report({
|
|
12
12
|
scope: select,
|
|
13
13
|
message: t('need {0}', t('the {0}', 'placeholder label option')),
|
|
14
14
|
});
|
|
15
|
-
}
|
|
15
|
+
}
|
|
16
16
|
},
|
|
17
17
|
});
|
|
18
18
|
/**
|
|
@@ -6,14 +6,14 @@ export default createRule({
|
|
|
6
6
|
langs: undefined,
|
|
7
7
|
},
|
|
8
8
|
verify({ document, report, t }) {
|
|
9
|
-
document.querySelectorAll('time:not([datetime])')
|
|
9
|
+
for (const time of document.querySelectorAll('time:not([datetime])')) {
|
|
10
10
|
if (time.hasMutableChildren()) {
|
|
11
|
-
|
|
11
|
+
continue;
|
|
12
12
|
}
|
|
13
13
|
const content = time.textContent.trim();
|
|
14
14
|
const result = check(content, 'DateTime');
|
|
15
15
|
if (result.matched) {
|
|
16
|
-
|
|
16
|
+
continue;
|
|
17
17
|
}
|
|
18
18
|
const candidate = getCandidateDatetimeString(content, time.rule.options.langs);
|
|
19
19
|
if (candidate) {
|
|
@@ -21,12 +21,12 @@ export default createRule({
|
|
|
21
21
|
scope: time,
|
|
22
22
|
message: t('need {0*}', `datetime="${candidate}"`),
|
|
23
23
|
});
|
|
24
|
-
|
|
24
|
+
continue;
|
|
25
25
|
}
|
|
26
26
|
report({
|
|
27
27
|
scope: time,
|
|
28
28
|
message: t('need {0}', t('the "{0*}" {1}', 'datetime', 'attribute')),
|
|
29
29
|
});
|
|
30
|
-
}
|
|
30
|
+
}
|
|
31
31
|
},
|
|
32
32
|
});
|
|
@@ -94,7 +94,7 @@ function parseTryMultipleLangs(content, langs, base) {
|
|
|
94
94
|
for (const lang of langs) {
|
|
95
95
|
const results = chrono[lang].casual.parse(content, base);
|
|
96
96
|
// Is not multiple datetime contents
|
|
97
|
-
if (results.length
|
|
97
|
+
if (results.length === 0) {
|
|
98
98
|
continue;
|
|
99
99
|
}
|
|
100
100
|
const result = results[0];
|
|
@@ -28,7 +28,7 @@ export default createRule({
|
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
30
|
for (const query of el.rule.value) {
|
|
31
|
-
const exists =
|
|
31
|
+
const exists = [...el.children].find(child => child.matches(query));
|
|
32
32
|
if (!exists) {
|
|
33
33
|
const message = t('Require {0}', t('the "{0*}" {1}', query, 'element'));
|
|
34
34
|
report({
|
package/lib/use-list/index.js
CHANGED
|
@@ -76,7 +76,7 @@ export default createRule({
|
|
|
76
76
|
},
|
|
77
77
|
});
|
|
78
78
|
function isMayListItem(text, bullets, spaceNeededBullets) {
|
|
79
|
-
const textArray =
|
|
79
|
+
const textArray = [...text];
|
|
80
80
|
const firstLetter = textArray[0] ?? '';
|
|
81
81
|
const isBullet = bullets.includes(firstLetter);
|
|
82
82
|
const needSpace = spaceNeededBullets.includes(firstLetter);
|
|
@@ -34,13 +34,13 @@ export const checkingImplicitProps = ({ attr, propSpecs, attrSpecs }) => t => {
|
|
|
34
34
|
message: t('{0} contradicts {1}', t('the "{0*}" {1}', attr.name, `ARIA ${propSpec.type}`), t('the current "{0}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')),
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
-
else if (value === 'true'
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
}
|
|
37
|
+
else if (value === 'true' &&
|
|
38
|
+
!equivalentHtmlAttr.isNotStrictEquivalent &&
|
|
39
|
+
htmlAttrSpec?.type === 'Boolean') {
|
|
40
|
+
return {
|
|
41
|
+
scope: attr,
|
|
42
|
+
message: t('{0} contradicts {1}', t('the "{0*}" {1}', attr.name, `ARIA ${propSpec.type}`), t('the implicit "{0}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')),
|
|
43
|
+
};
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
};
|
|
@@ -33,7 +33,7 @@ export const checkingPresentationalChildren = ({ el }) => t => {
|
|
|
33
33
|
if (!ancestor.role) {
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
-
const hasAriaAttr =
|
|
36
|
+
const hasAriaAttr = [...el.attributes].some(attr => /^aria-|^role$/i.test(attr.name));
|
|
37
37
|
if (!hasAriaAttr) {
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
@@ -25,7 +25,7 @@ export const checkingRequiredOwnedElements = ({ el, role }) => t => {
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
// TODO: Needs to resolve `aria-own`
|
|
28
|
-
const children =
|
|
28
|
+
const children = [...el.childNodes].map(child => {
|
|
29
29
|
if (child.is(child.ELEMENT_NODE)) {
|
|
30
30
|
if (child.matches('[aria-busy="true" i]')) {
|
|
31
31
|
return [child, 'BUSY'];
|
|
@@ -93,7 +93,7 @@ el) {
|
|
|
93
93
|
if (el.isEmpty()) {
|
|
94
94
|
return true;
|
|
95
95
|
}
|
|
96
|
-
return
|
|
96
|
+
return [...el.children].every(child => {
|
|
97
97
|
return ['script', 'template'].includes(child.localName);
|
|
98
98
|
});
|
|
99
99
|
}
|
|
@@ -76,10 +76,10 @@ export function checkAriaValue(type, value, tokenEnum, booleanish) {
|
|
|
76
76
|
return ['true', 'false', 'undefined'].includes(value);
|
|
77
77
|
}
|
|
78
78
|
case 'integer': {
|
|
79
|
-
return parseInt(value).toString() === value;
|
|
79
|
+
return Number.parseInt(value).toString() === value;
|
|
80
80
|
}
|
|
81
81
|
case 'number': {
|
|
82
|
-
return parseFloat(value).toString() === value;
|
|
82
|
+
return Number.parseFloat(value).toString() === value;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
// For skipping checking
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/rules",
|
|
3
|
-
"version": "4.0.0-
|
|
3
|
+
"version": "4.0.0-dev.28+0131de5e",
|
|
4
4
|
"description": "Built-in rules of markuplint",
|
|
5
5
|
"repository": "git@github.com:markuplint/markuplint.git",
|
|
6
6
|
"author": "Yusuke Hirao <yusukehirao@me.com>",
|
|
@@ -25,19 +25,18 @@
|
|
|
25
25
|
"./lib/permitted-contents/debug.js": "./lib/permitted-contents/debug.browser.js"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@markuplint/html-spec": "4.0.0-
|
|
29
|
-
"@markuplint/ml-core": "4.0.0-
|
|
30
|
-
"@markuplint/ml-spec": "4.0.0-
|
|
31
|
-
"@markuplint/selector": "4.0.0-
|
|
32
|
-
"@markuplint/shared": "4.0.0-
|
|
33
|
-
"@markuplint/types": "4.0.0-
|
|
34
|
-
"@types/debug": "^4.1.
|
|
28
|
+
"@markuplint/html-spec": "4.0.0-dev.28+0131de5e",
|
|
29
|
+
"@markuplint/ml-core": "4.0.0-dev.28+0131de5e",
|
|
30
|
+
"@markuplint/ml-spec": "4.0.0-dev.28+0131de5e",
|
|
31
|
+
"@markuplint/selector": "4.0.0-dev.28+0131de5e",
|
|
32
|
+
"@markuplint/shared": "4.0.0-dev.28+0131de5e",
|
|
33
|
+
"@markuplint/types": "4.0.0-dev.28+0131de5e",
|
|
34
|
+
"@types/debug": "^4.1.10",
|
|
35
35
|
"@ungap/structured-clone": "^1.2.0",
|
|
36
36
|
"ansi-colors": "^4.1.3",
|
|
37
37
|
"chrono-node": "^2.7.0",
|
|
38
38
|
"debug": "^4.3.4",
|
|
39
|
-
"
|
|
40
|
-
"type-fest": "^4.3.1"
|
|
39
|
+
"type-fest": "^4.5.0"
|
|
41
40
|
},
|
|
42
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "0131de5ea9dd6d3fd5472d7b414b66644c758881"
|
|
43
42
|
}
|