@markuplint/rules 4.0.0-alpha.1 → 4.0.0-alpha.11
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
|
@@ -12,23 +12,33 @@ elements, specs, options, depth) {
|
|
|
12
12
|
for (const some of pattern.choice) {
|
|
13
13
|
choiceLog('Patterns[%s]: %s', i, modelLog(some, ''));
|
|
14
14
|
const result = order(some, collection.unmatched, specs, options, depth + 1);
|
|
15
|
-
if (result.type === '
|
|
15
|
+
if (result.type === 'MATCHED' ||
|
|
16
|
+
result.type === 'MATCHED_ZERO' ||
|
|
17
|
+
(result.type === 'UNEXPECTED_EXTRA_NODE' && result.matched.length > 0)) {
|
|
16
18
|
choiceLog('Results[%s]: %s', i, choiceLogString(pattern.choice, i));
|
|
17
|
-
collection.addMatched(result.matched);
|
|
18
19
|
return {
|
|
19
20
|
type: result.type,
|
|
20
|
-
matched:
|
|
21
|
-
unmatched:
|
|
21
|
+
matched: result.matched,
|
|
22
|
+
unmatched: result.unmatched,
|
|
22
23
|
zeroMatch: result.zeroMatch,
|
|
23
24
|
query: result.query,
|
|
24
25
|
hint: result.hint,
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
unmatchedResults.push(result);
|
|
29
|
+
collection.addMatched(result.matched);
|
|
28
30
|
indexes.set(result, i);
|
|
29
31
|
i++;
|
|
30
32
|
}
|
|
31
33
|
const barelyMatchedResult = unmatchedResults.sort((a, b) => {
|
|
34
|
+
if (a.type !== b.type) {
|
|
35
|
+
if (a.type === 'UNEXPECTED_EXTRA_NODE') {
|
|
36
|
+
return -1;
|
|
37
|
+
}
|
|
38
|
+
if (b.type === 'UNEXPECTED_EXTRA_NODE') {
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
32
42
|
const computed1 = b.matched.length - a.matched.length;
|
|
33
43
|
if (computed1 !== 0) {
|
|
34
44
|
return computed1;
|
|
@@ -3,7 +3,7 @@ import { start } from './start.js';
|
|
|
3
3
|
export function contentModel(
|
|
4
4
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
5
5
|
el, rules, options) {
|
|
6
|
-
const model = createModel(el, rules);
|
|
6
|
+
const { model, specs } = createModel(el, rules);
|
|
7
7
|
if (model == null) {
|
|
8
8
|
return [
|
|
9
9
|
{
|
|
@@ -14,7 +14,7 @@ el, rules, options) {
|
|
|
14
14
|
},
|
|
15
15
|
];
|
|
16
16
|
}
|
|
17
|
-
const result = start(model, el,
|
|
17
|
+
const result = start(model, el, specs, options);
|
|
18
18
|
return result;
|
|
19
19
|
}
|
|
20
20
|
function createModel(
|
|
@@ -22,7 +22,10 @@ function createModel(
|
|
|
22
22
|
el, rules) {
|
|
23
23
|
const specs = cachedSpecs(el.ownerMLDocument.specs, rules);
|
|
24
24
|
const model = getContentModel(el, specs.specs);
|
|
25
|
-
return
|
|
25
|
+
return {
|
|
26
|
+
model,
|
|
27
|
+
specs,
|
|
28
|
+
};
|
|
26
29
|
}
|
|
27
30
|
const caches = new Map();
|
|
28
31
|
function cachedSpecs(specs, rules) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cmLog } from './debug.js';
|
|
2
2
|
import { recursiveBranch } from './recursive-branch.js';
|
|
3
3
|
import { Collection, mergeHints, modelLog, normalizeModel } from './utils.js';
|
|
4
|
+
const cLog = cmLog.extend('countCompereResult');
|
|
4
5
|
/**
|
|
5
6
|
* Check count
|
|
6
7
|
*
|
|
@@ -139,7 +140,6 @@ elements, specs, options, depth) {
|
|
|
139
140
|
return compereResult(matchedResult, barelyResult);
|
|
140
141
|
}
|
|
141
142
|
}
|
|
142
|
-
const cLog = cmLog.extend('countCompereResult');
|
|
143
143
|
function compereResult(
|
|
144
144
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
145
145
|
a,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Log } from '../debug.js';
|
|
2
2
|
import color from 'ansi-colors';
|
|
3
|
-
export declare const cmLog:
|
|
3
|
+
export declare const cmLog: Log;
|
|
4
4
|
export declare const bgGreen: color.StyleFunction;
|
|
5
5
|
export declare const green: color.StyleFunction;
|
|
6
6
|
export declare const bgRed: color.StyleFunction;
|
|
@@ -2,6 +2,9 @@ import { createRule } from '@markuplint/ml-core';
|
|
|
2
2
|
import { contentModel } from './content-model.js';
|
|
3
3
|
import { transparentMode } from './represent-transparent-nodes.js';
|
|
4
4
|
export default createRule({
|
|
5
|
+
meta: {
|
|
6
|
+
category: 'validation',
|
|
7
|
+
},
|
|
5
8
|
defaultValue: [],
|
|
6
9
|
defaultOptions: {
|
|
7
10
|
ignoreHasMutableChildren: true,
|
|
@@ -152,7 +152,7 @@ function optCondition(query, specs) {
|
|
|
152
152
|
}
|
|
153
153
|
let hasCustom = false;
|
|
154
154
|
let hasText = false;
|
|
155
|
-
const selector = query.replace(
|
|
155
|
+
const selector = query.replace(/^:model\(([^)]+)\)|^#([a-z-]+)/, (_, $model, _model) => {
|
|
156
156
|
const _selectors = contentModelCategoryToTagNames(`#${$model ?? _model}`, specs.def);
|
|
157
157
|
if (_selectors.length === 0) {
|
|
158
158
|
throw new Error(`${$model ?? _model} is empty`);
|
|
@@ -169,7 +169,7 @@ function optCondition(query, specs) {
|
|
|
169
169
|
}
|
|
170
170
|
selectors.push(selector);
|
|
171
171
|
}
|
|
172
|
-
return `:is(${selectors.join(',')})
|
|
172
|
+
return `:is(${selectors.join(',')})`;
|
|
173
173
|
});
|
|
174
174
|
const result = {
|
|
175
175
|
selector,
|
|
@@ -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,21 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
export default createRule({
|
|
3
|
+
meta: {
|
|
4
|
+
category: 'validation',
|
|
5
|
+
},
|
|
3
6
|
verify({ document, report, t }) {
|
|
4
|
-
document.querySelectorAll('select')
|
|
7
|
+
for (const select of document.querySelectorAll('select')) {
|
|
5
8
|
if (hasPlaceholderLabelOption(select)) {
|
|
6
|
-
|
|
9
|
+
continue;
|
|
7
10
|
}
|
|
8
11
|
if (!needPlaceholderLabelOption(select)) {
|
|
9
|
-
|
|
12
|
+
continue;
|
|
10
13
|
}
|
|
11
14
|
report({
|
|
12
15
|
scope: select,
|
|
13
16
|
message: t('need {0}', t('the {0}', 'placeholder label option')),
|
|
14
17
|
});
|
|
15
|
-
}
|
|
18
|
+
}
|
|
16
19
|
},
|
|
17
20
|
});
|
|
18
21
|
/**
|
|
@@ -2,14 +2,14 @@ import { createRule, getRoleSpec, getComputedRole } from '@markuplint/ml-core';
|
|
|
2
2
|
import { ARIA_RECOMMENDED_VERSION, isExposed } from '@markuplint/ml-spec';
|
|
3
3
|
import { accnameMayBeMutable } from '../helpers.js';
|
|
4
4
|
export default createRule({
|
|
5
|
+
meta: {
|
|
6
|
+
category: 'a11y',
|
|
7
|
+
},
|
|
5
8
|
defaultOptions: {
|
|
6
9
|
ariaVersion: ARIA_RECOMMENDED_VERSION,
|
|
7
10
|
},
|
|
8
11
|
async verify({ document, report, t }) {
|
|
9
12
|
await document.walkOn('Element', el => {
|
|
10
|
-
if (el.pretenderContext?.type === 'pretender') {
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
13
|
if (accnameMayBeMutable(el, document)) {
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
@@ -2,18 +2,21 @@ import { createRule } from '@markuplint/ml-core';
|
|
|
2
2
|
import { check } from '@markuplint/types';
|
|
3
3
|
import { getCandidateDatetimeString } from './utils.js';
|
|
4
4
|
export default createRule({
|
|
5
|
+
meta: {
|
|
6
|
+
category: 'validation',
|
|
7
|
+
},
|
|
5
8
|
defaultOptions: {
|
|
6
9
|
langs: undefined,
|
|
7
10
|
},
|
|
8
11
|
verify({ document, report, t }) {
|
|
9
|
-
document.querySelectorAll('time:not([datetime])')
|
|
12
|
+
for (const time of document.querySelectorAll('time:not([datetime])')) {
|
|
10
13
|
if (time.hasMutableChildren()) {
|
|
11
|
-
|
|
14
|
+
continue;
|
|
12
15
|
}
|
|
13
16
|
const content = time.textContent.trim();
|
|
14
17
|
const result = check(content, 'DateTime');
|
|
15
18
|
if (result.matched) {
|
|
16
|
-
|
|
19
|
+
continue;
|
|
17
20
|
}
|
|
18
21
|
const candidate = getCandidateDatetimeString(content, time.rule.options.langs);
|
|
19
22
|
if (candidate) {
|
|
@@ -21,12 +24,12 @@ export default createRule({
|
|
|
21
24
|
scope: time,
|
|
22
25
|
message: t('need {0*}', `datetime="${candidate}"`),
|
|
23
26
|
});
|
|
24
|
-
|
|
27
|
+
continue;
|
|
25
28
|
}
|
|
26
29
|
report({
|
|
27
30
|
scope: time,
|
|
28
31
|
message: t('need {0}', t('the "{0*}" {1}', 'datetime', 'attribute')),
|
|
29
32
|
});
|
|
30
|
-
}
|
|
33
|
+
}
|
|
31
34
|
},
|
|
32
35
|
});
|
|
@@ -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];
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createRule, getAttrSpecs } from '@markuplint/ml-core';
|
|
2
2
|
import { attrMatches, match } from '../helpers.js';
|
|
3
3
|
export default createRule({
|
|
4
|
+
meta: {
|
|
5
|
+
category: 'validation',
|
|
6
|
+
},
|
|
4
7
|
defaultValue: [],
|
|
5
8
|
async verify({ document, report, t }) {
|
|
6
9
|
await document.walkOn('Element', el => {
|
|
@@ -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
|
defaultValue: [],
|
|
4
7
|
defaultOptions: {
|
|
5
8
|
ignoreHasMutableContents: true,
|
|
@@ -28,7 +31,7 @@ export default createRule({
|
|
|
28
31
|
return;
|
|
29
32
|
}
|
|
30
33
|
for (const query of el.rule.value) {
|
|
31
|
-
const exists =
|
|
34
|
+
const exists = [...el.children].find(child => child.matches(query));
|
|
32
35
|
if (!exists) {
|
|
33
36
|
const message = t('Require {0}', t('the "{0*}" {1}', query, 'element'));
|
|
34
37
|
report({
|
package/lib/required-h1/index.js
CHANGED
package/lib/use-list/index.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
type Bullets = readonly string[];
|
|
2
2
|
type Options = {
|
|
3
3
|
spaceNeededBullets?: string[];
|
|
4
|
+
noPrev?: boolean;
|
|
5
|
+
prevElement?: boolean;
|
|
6
|
+
prevComment?: boolean;
|
|
7
|
+
prevCodeBlock?: boolean;
|
|
4
8
|
};
|
|
5
9
|
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<Bullets, Options>>;
|
|
6
10
|
export default _default;
|
package/lib/use-list/index.js
CHANGED
|
@@ -1,36 +1,39 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
import { decodeEntities } from '@markuplint/shared';
|
|
3
3
|
export default createRule({
|
|
4
|
+
meta: {
|
|
5
|
+
category: 'a11y',
|
|
6
|
+
},
|
|
4
7
|
defaultValue: [
|
|
5
8
|
/**
|
|
6
9
|
* @see https://en.wikipedia.org/wiki/Bullet_(typography)#In_Unicode
|
|
7
10
|
*/
|
|
8
|
-
'\u2022',
|
|
9
|
-
'\u2023',
|
|
10
|
-
'\u2043',
|
|
11
|
-
'\u204C',
|
|
12
|
-
'\u204D',
|
|
13
|
-
'\u2219',
|
|
14
|
-
'\u25CB',
|
|
15
|
-
'\u25CF',
|
|
16
|
-
'\u25D8',
|
|
17
|
-
'\u25E6',
|
|
18
|
-
'\u2619',
|
|
19
|
-
'\u2765',
|
|
20
|
-
'\u2767',
|
|
21
|
-
'\u29BE',
|
|
22
|
-
'\u29BF',
|
|
11
|
+
'\u2022', // • BULLET (HTML • · •, •)
|
|
12
|
+
'\u2023', // ‣ TRIANGULAR BULLET (HTML ‣)
|
|
13
|
+
'\u2043', // ⁃ HYPHEN BULLET (HTML ⁃ · ⁃)
|
|
14
|
+
'\u204C', // ⁌ BLACK LEFTWARDS BULLET (HTML ⁌)
|
|
15
|
+
'\u204D', // ⁍ BLACK RIGHTWARDS BULLET (HTML ⁍)
|
|
16
|
+
'\u2219', // ∙ BULLET OPERATOR (HTML ∙) for use in mathematical notation primarily as a dot product instead of interupt.
|
|
17
|
+
'\u25CB', // ○ WHITE CIRCLE (HTML ○ · ○)
|
|
18
|
+
'\u25CF', // ● BLACK CIRCLE (HTML ●)
|
|
19
|
+
'\u25D8', // ◘ INVERSE BULLET (HTML ◘)
|
|
20
|
+
'\u25E6', // ◦ WHITE BULLET (HTML ◦)
|
|
21
|
+
'\u2619', // ☙ REVERSED ROTATED FLORAL HEART BULLET (HTML ☙); see Fleuron (typography)
|
|
22
|
+
'\u2765', // ❥ ROTATED HEAVY BLACK HEART BULLET (HTML ❥)
|
|
23
|
+
'\u2767', // ❧ ROTATED FLORAL HEART BULLET (HTML ❧); see Fleuron (typography)
|
|
24
|
+
'\u29BE', // ⦾ CIRCLED WHITE BULLET (HTML ⦾ · ⦾)
|
|
25
|
+
'\u29BF', // ⦿ CIRCLED BULLET (HTML ⦿ · ⦿)
|
|
23
26
|
/**
|
|
24
27
|
* In Japanese
|
|
25
28
|
* @see https://ja.wikipedia.org/wiki/中黒#符号位置
|
|
26
29
|
*/
|
|
27
|
-
'\u00B7',
|
|
28
|
-
'\u0387',
|
|
29
|
-
'\u2022',
|
|
30
|
-
'\u2219',
|
|
31
|
-
'\u22C5',
|
|
32
|
-
'\u30FB',
|
|
33
|
-
'\uFF65',
|
|
30
|
+
'\u00B7', // MIDDLE DOT
|
|
31
|
+
'\u0387', // GREEK ANO TELIA
|
|
32
|
+
'\u2022', // BULLET
|
|
33
|
+
'\u2219', // BULLET OPERATOR
|
|
34
|
+
'\u22C5', // DOT OPERATOR
|
|
35
|
+
'\u30FB', // KATAKANA MIDDLE DOT
|
|
36
|
+
'\uFF65', // HALFWIDTH KATAKANA MIDDLE DOT
|
|
34
37
|
/**
|
|
35
38
|
* In Other Languages
|
|
36
39
|
*/
|
|
@@ -38,8 +41,8 @@ export default createRule({
|
|
|
38
41
|
/**
|
|
39
42
|
* From Markdown
|
|
40
43
|
*/
|
|
41
|
-
'-',
|
|
42
|
-
'*',
|
|
44
|
+
'-', // dashes
|
|
45
|
+
'*', // asterisks
|
|
43
46
|
'+', // plus signs
|
|
44
47
|
],
|
|
45
48
|
defaultOptions: {
|
|
@@ -47,10 +50,14 @@ export default createRule({
|
|
|
47
50
|
/**
|
|
48
51
|
* From Markdown
|
|
49
52
|
*/
|
|
50
|
-
'-',
|
|
51
|
-
'*',
|
|
53
|
+
'-', // dashes
|
|
54
|
+
'*', // asterisks
|
|
52
55
|
'+', // plus signs
|
|
53
56
|
],
|
|
57
|
+
noPrev: true,
|
|
58
|
+
prevElement: true,
|
|
59
|
+
prevComment: true,
|
|
60
|
+
prevCodeBlock: false,
|
|
54
61
|
},
|
|
55
62
|
defaultSeverity: 'warning',
|
|
56
63
|
async verify({ document, report, t }) {
|
|
@@ -64,6 +71,19 @@ export default createRule({
|
|
|
64
71
|
// character only
|
|
65
72
|
return;
|
|
66
73
|
}
|
|
74
|
+
if (textNode.rule.options.noPrev === false && !textNode.prevNode) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (textNode.rule.options.prevElement === false && textNode.prevNode?.is(textNode.ELEMENT_NODE)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (textNode.rule.options.prevComment === false && textNode.prevNode?.is(textNode.COMMENT_NODE)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (textNode.rule.options.prevCodeBlock === false &&
|
|
84
|
+
textNode.prevNode?.is(textNode.MARKUPLINT_PREPROCESSOR_BLOCK)) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
67
87
|
const bullets = textNode.rule.value;
|
|
68
88
|
const spaceNeededBullets = textNode.rule.options.spaceNeededBullets ?? [];
|
|
69
89
|
if (isMayListItem(text, bullets, spaceNeededBullets)) {
|
|
@@ -76,7 +96,7 @@ export default createRule({
|
|
|
76
96
|
},
|
|
77
97
|
});
|
|
78
98
|
function isMayListItem(text, bullets, spaceNeededBullets) {
|
|
79
|
-
const textArray =
|
|
99
|
+
const textArray = [...text];
|
|
80
100
|
const firstLetter = textArray[0] ?? '';
|
|
81
101
|
const isBullet = bullets.includes(firstLetter);
|
|
82
102
|
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'];
|
|
@@ -75,7 +75,7 @@ export const checkingRequiredOwnedElements = ({ el, role }) => t => {
|
|
|
75
75
|
if (mayBeBeforeCreated(el)) {
|
|
76
76
|
return {
|
|
77
77
|
scope: el,
|
|
78
|
-
message: t('{0}. Or, {1}', t('
|
|
78
|
+
message: t('{0}. Or, {1}', t('{0} requires {1}', t('the {0}', 'child element'), role.requiredOwnedElements.length === 1 && role.requiredOwnedElements[0]
|
|
79
79
|
? t('the "{0*}" {1}', role.requiredOwnedElements[0], 'role')
|
|
80
80
|
: t('the {0}', 'roles') + `: ${t(role.requiredOwnedElements)}`), t('require {0}', 'aria-busy="true"')),
|
|
81
81
|
};
|
|
@@ -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
|
}
|
|
@@ -49,7 +49,7 @@ export function checkAriaValue(type, value, tokenEnum, booleanish) {
|
|
|
49
49
|
return tokenEnum.includes(value);
|
|
50
50
|
}
|
|
51
51
|
case 'token list': {
|
|
52
|
-
const list = value.split(/\s+/
|
|
52
|
+
const list = value.split(/\s+/).map(s => s.trim());
|
|
53
53
|
return list.every(token => tokenEnum.includes(token));
|
|
54
54
|
}
|
|
55
55
|
case 'string':
|
|
@@ -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
|