@markuplint/rules 2.0.0-dev.23 → 2.0.0-rc.5
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.d.ts +8 -1
- package/lib/attr-check.js +53 -455
- package/lib/attr-duplication/index.d.ts +1 -1
- package/lib/attr-duplication/index.js +0 -2
- package/lib/attr-equal-space-after/index.js +0 -1
- package/lib/attr-equal-space-before/index.js +0 -1
- package/lib/attr-spacing/index.js +0 -1
- package/lib/attr-value-quotes/index.js +0 -1
- package/lib/case-sensitive-attr-name/index.d.ts +6 -2
- package/lib/case-sensitive-attr-name/index.js +30 -8
- package/lib/case-sensitive-tag-name/index.js +0 -1
- package/lib/character-reference/index.d.ts +1 -2
- package/lib/character-reference/index.js +0 -2
- package/lib/class-naming/index.js +23 -23
- package/lib/create-message.d.ts +5 -0
- package/lib/create-message.js +270 -0
- package/lib/debug.d.ts +3 -0
- package/lib/debug.js +6 -0
- package/lib/deprecated-attr/index.d.ts +1 -1
- package/lib/deprecated-attr/index.js +2 -5
- package/lib/deprecated-element/index.d.ts +1 -1
- package/lib/deprecated-element/index.js +1 -3
- package/lib/disallowed-element/index.d.ts +2 -0
- package/lib/disallowed-element/index.js +34 -0
- package/lib/helpers.d.ts +15 -9
- package/lib/helpers.js +63 -68
- package/lib/id-duplication/index.d.ts +1 -1
- package/lib/id-duplication/index.js +1 -3
- package/lib/index.d.ts +28 -11
- package/lib/index.js +16 -0
- package/lib/ineffective-attr/index.d.ts +2 -0
- package/lib/ineffective-attr/index.js +40 -0
- package/lib/invalid-attr/index.d.ts +4 -2
- package/lib/invalid-attr/index.js +25 -9
- package/lib/landmark-roles/index.js +1 -2
- package/lib/no-boolean-attr-value/index.d.ts +2 -0
- package/lib/no-boolean-attr-value/index.js +47 -0
- package/lib/no-default-value/index.d.ts +2 -0
- package/lib/no-default-value/index.js +38 -0
- package/lib/no-hard-code-id/index.d.ts +2 -0
- package/lib/no-hard-code-id/index.js +27 -0
- package/lib/no-refer-to-non-existent-id/index.d.ts +2 -0
- package/lib/no-refer-to-non-existent-id/index.js +110 -0
- package/lib/no-use-event-handler-attr/index.d.ts +5 -0
- package/lib/no-use-event-handler-attr/index.js +37 -0
- package/lib/permitted-contents/index.js +24 -13
- package/lib/permitted-contents/types.d.ts +8 -0
- package/lib/permitted-contents/types.js +2 -0
- package/lib/require-element/index.d.ts +2 -0
- package/lib/require-element/index.js +38 -0
- package/lib/required-attr/index.d.ts +5 -1
- package/lib/required-attr/index.js +60 -19
- package/lib/required-element/index.d.ts +5 -0
- package/lib/required-element/index.js +44 -0
- package/lib/required-h1/index.d.ts +2 -3
- package/lib/required-h1/index.js +2 -3
- package/lib/wai-aria/index.d.ts +1 -1
- package/lib/wai-aria/index.js +23 -24
- package/package.json +10 -5
- package/schema.json +24 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/primitive-check.d.ts +0 -48
- package/lib/primitive-check.js +0 -109
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ml_core_1 = require("@markuplint/ml-core");
|
|
4
|
+
exports.default = (0, ml_core_1.createRule)({
|
|
5
|
+
defaultServerity: 'warning',
|
|
6
|
+
async verify({ document, report, t }) {
|
|
7
|
+
if (!document.isFragment) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
await document.walkOn('Element', el => {
|
|
11
|
+
for (const attr of el.attributes) {
|
|
12
|
+
if ((attr.attrType === 'html-attr' && !attr.isDynamicValue) ||
|
|
13
|
+
(attr.attrType === 'ps-attr' &&
|
|
14
|
+
attr.potentialName.toLowerCase() === 'id' &&
|
|
15
|
+
attr.valueType !== 'code')) {
|
|
16
|
+
report({
|
|
17
|
+
scope: el,
|
|
18
|
+
line: attr.startLine,
|
|
19
|
+
col: attr.startCol,
|
|
20
|
+
raw: attr.raw,
|
|
21
|
+
message: t('It is {0:c}', 'hard-coded'),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ml_core_1 = require("@markuplint/ml-core");
|
|
4
|
+
const helpers_1 = require("../helpers");
|
|
5
|
+
exports.default = (0, ml_core_1.createRule)({
|
|
6
|
+
async verify({ document, report, t }) {
|
|
7
|
+
const { ariaAttrs } = (0, helpers_1.ariaSpec)(document.specs);
|
|
8
|
+
const idList = new Set();
|
|
9
|
+
let hasDynamicId = false;
|
|
10
|
+
await document.walkOn('Element', el => {
|
|
11
|
+
for (const attr of el.attributes) {
|
|
12
|
+
if (attr.getName().potential.toLowerCase() !== 'id') {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (attr.attrType === 'html-attr') {
|
|
16
|
+
if (attr.isDynamicValue) {
|
|
17
|
+
hasDynamicId = true;
|
|
18
|
+
}
|
|
19
|
+
idList.add(attr.getValue().potential);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
if (attr.valueType !== 'code') {
|
|
23
|
+
idList.add(attr.getValue().potential);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
if (hasDynamicId) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
document.walkOn('Element', el => {
|
|
32
|
+
const attrSpec = (0, ml_core_1.getAttrSpecs)(el.nameWithNS, document.specs);
|
|
33
|
+
if (!attrSpec) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
for (const attr of el.attributes) {
|
|
37
|
+
if (attr.attrType !== 'html-attr' || attr.isDynamicValue) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const name = attr.getName().potential;
|
|
41
|
+
if (name.toLowerCase() === 'id') {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const value = attr.getValue().potential;
|
|
45
|
+
const spec = attrSpec.find(s => s.name === name);
|
|
46
|
+
if (spec) {
|
|
47
|
+
// DOMID and DOMID List do not become in the array type.
|
|
48
|
+
if (Array.isArray(spec.type)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (spec.type === 'DOMID' && !idList.has(value)) {
|
|
52
|
+
report({
|
|
53
|
+
scope: el,
|
|
54
|
+
line: attr.value.startLine,
|
|
55
|
+
col: attr.value.startCol,
|
|
56
|
+
raw: attr.value.raw,
|
|
57
|
+
message: t('Missing {0}', t('"{0*}" ID', value)),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (typeof spec.type !== 'string' && 'token' in spec.type && spec.type.token === 'DOMID') {
|
|
61
|
+
const refs = value
|
|
62
|
+
.split(spec.type.separator === 'space' ? /\s/ : ',')
|
|
63
|
+
.map(id => id.trim())
|
|
64
|
+
.filter(_ => _);
|
|
65
|
+
for (const ref of refs) {
|
|
66
|
+
if (!idList.has(ref)) {
|
|
67
|
+
report({
|
|
68
|
+
scope: el,
|
|
69
|
+
line: attr.value.startLine,
|
|
70
|
+
col: attr.value.startCol,
|
|
71
|
+
raw: attr.value.raw,
|
|
72
|
+
message: t('Missing {0}', t('"{0*}" ID', ref)),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const aria = ariaAttrs.find(aria => aria.name === name);
|
|
79
|
+
if (aria) {
|
|
80
|
+
if (aria.value === 'ID reference' && !idList.has(value)) {
|
|
81
|
+
report({
|
|
82
|
+
scope: el,
|
|
83
|
+
line: attr.value.startLine,
|
|
84
|
+
col: attr.value.startCol,
|
|
85
|
+
raw: attr.value.raw,
|
|
86
|
+
message: t('Missing {0}', t('"{0*}" ID', value)),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else if (aria.value === 'ID reference list') {
|
|
90
|
+
const refs = value
|
|
91
|
+
.split(/\s/)
|
|
92
|
+
.map(id => id.trim())
|
|
93
|
+
.filter(_ => _);
|
|
94
|
+
for (const ref of refs) {
|
|
95
|
+
if (!idList.has(ref)) {
|
|
96
|
+
report({
|
|
97
|
+
scope: el,
|
|
98
|
+
line: attr.value.startLine,
|
|
99
|
+
col: attr.value.startCol,
|
|
100
|
+
raw: attr.value.raw,
|
|
101
|
+
message: t('Missing {0}', t('"{0*}" ID', ref)),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ml_core_1 = require("@markuplint/ml-core");
|
|
4
|
+
const helpers_1 = require("../helpers");
|
|
5
|
+
exports.default = (0, ml_core_1.createRule)({
|
|
6
|
+
defaultServerity: 'warning',
|
|
7
|
+
defaultOptions: {},
|
|
8
|
+
async verify({ document, report, t }) {
|
|
9
|
+
await document.walkOn('Element', el => {
|
|
10
|
+
if (el.isCustomElement) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const ignoreList = Array.isArray(el.rule.option.ignore)
|
|
14
|
+
? el.rule.option.ignore
|
|
15
|
+
: el.rule.option.ignore
|
|
16
|
+
? [el.rule.option.ignore]
|
|
17
|
+
: [];
|
|
18
|
+
el.attributes.forEach(attr => {
|
|
19
|
+
const name = attr.getName().potential;
|
|
20
|
+
for (const ignore of ignoreList) {
|
|
21
|
+
if ((0, helpers_1.match)(name, ignore)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (/^on/i.test(name)) {
|
|
26
|
+
report({
|
|
27
|
+
scope: el,
|
|
28
|
+
raw: attr.raw,
|
|
29
|
+
line: attr.startLine,
|
|
30
|
+
col: attr.startCol,
|
|
31
|
+
message: t('{0} is disallowed', t('the "{0*}" {1}', name, 'attribute')),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -23,13 +23,13 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
23
23
|
}
|
|
24
24
|
const specType = node.ns === 'html' ? 'HTML' : node.ns === 'svg' ? 'SVG' : 'Any Language';
|
|
25
25
|
const childNodes = node.getChildElementsAndTextNodeWithoutWhitespaces();
|
|
26
|
-
const spec = !node.isCustomElement && ((_a = (0, helpers_1.htmlSpec)(node.nameWithNS)) === null || _a === void 0 ? void 0 : _a.permittedStructures);
|
|
26
|
+
const spec = !node.isCustomElement && ((_a = (0, helpers_1.htmlSpec)(document.specs, node.nameWithNS)) === null || _a === void 0 ? void 0 : _a.permittedStructures);
|
|
27
27
|
const expGen = new permitted_content_spec_to_regexp_1.default(idCounter++);
|
|
28
28
|
if (spec) {
|
|
29
29
|
if (spec.ancestor && !node.closest(spec.ancestor)) {
|
|
30
30
|
report({
|
|
31
31
|
scope: node,
|
|
32
|
-
message: t('{0} must be {1}', t('the "{0}" {1}', node.nodeName, 'element'), t('{0} of {1}', 'descendant', t('the "{0}" {1}', spec.ancestor, 'element'))),
|
|
32
|
+
message: t('{0} must be {1}', t('the "{0*}" {1}', node.nodeName, 'element'), t('{0} of {1}', 'descendant', t('the "{0*}" {1}', spec.ancestor, 'element'))),
|
|
33
33
|
});
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
@@ -45,11 +45,11 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
45
45
|
// console.log({ ...conditional, matched });
|
|
46
46
|
if (matched) {
|
|
47
47
|
try {
|
|
48
|
-
const parentExp = getRegExpFromParentNode(node, expGen);
|
|
48
|
+
const parentExp = getRegExpFromParentNode(document.specs, node, expGen);
|
|
49
49
|
const exp = expGen.specToRegExp(conditional.contents, parentExp, node.ns);
|
|
50
50
|
const conditionalResult = match(exp, childNodes, node.ns, false);
|
|
51
51
|
if (!conditionalResult) {
|
|
52
|
-
const message = t('{0} is {1:c}', t('{0} of {1}', t('the {0}', 'content'), t('the "{0}" {1}', node.nodeName, 'element')), 'invalid');
|
|
52
|
+
const message = t('{0} is {1:c}', t('{0} of {1}', t('the {0}', 'content'), t('the "{0*}" {1}', node.nodeName, 'element')), 'invalid');
|
|
53
53
|
report({
|
|
54
54
|
scope: node,
|
|
55
55
|
message: specType !== 'Any Language'
|
|
@@ -76,10 +76,10 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
76
76
|
}
|
|
77
77
|
if (!matched) {
|
|
78
78
|
try {
|
|
79
|
-
const exp = getRegExpFromNode(node, expGen);
|
|
79
|
+
const exp = getRegExpFromNode(document.specs, node, expGen);
|
|
80
80
|
const specResult = match(exp, childNodes, node.ns, false);
|
|
81
81
|
if (!specResult) {
|
|
82
|
-
const message = t('{0} is {1:c}', t('{0} of {1}', t('the {0}', 'content'), t('the "{0}" {1}', node.nodeName, 'element')), 'invalid');
|
|
82
|
+
const message = t('{0} is {1:c}', t('{0} of {1}', t('the {0}', 'content'), t('the "{0*}" {1}', node.nodeName, 'element')), 'invalid');
|
|
83
83
|
report({
|
|
84
84
|
scope: node,
|
|
85
85
|
message: specType !== 'Any Language'
|
|
@@ -103,7 +103,7 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
103
103
|
if (rule.tag.toLowerCase() !== node.nodeName.toLowerCase()) {
|
|
104
104
|
continue;
|
|
105
105
|
}
|
|
106
|
-
const parentExp = getRegExpFromParentNode(node, expGen);
|
|
106
|
+
const parentExp = getRegExpFromParentNode(document.specs, node, expGen);
|
|
107
107
|
try {
|
|
108
108
|
const exp = expGen.specToRegExp(rule.contents, parentExp, node.ns);
|
|
109
109
|
// Evaluate the custom element if the optional schema.
|
|
@@ -111,7 +111,7 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
111
111
|
if (!r) {
|
|
112
112
|
report({
|
|
113
113
|
scope: node,
|
|
114
|
-
message: t('{0} is {1:c}', t('{0} of {1}', t('the {0}', 'content'), t('the "{0}" {1}', node.nodeName, 'element')), 'invalid'),
|
|
114
|
+
message: t('{0} is {1:c}', t('{0} of {1}', t('the {0}', 'content'), t('the "{0*}" {1}', node.nodeName, 'element')), 'invalid'),
|
|
115
115
|
line: node.startLine,
|
|
116
116
|
col: node.startCol,
|
|
117
117
|
raw: node.raw,
|
|
@@ -148,26 +148,37 @@ function normalization(nodes, ownNS, evalCustomElement) {
|
|
|
148
148
|
})
|
|
149
149
|
.join('');
|
|
150
150
|
}
|
|
151
|
-
function getRegExpFromNode(node, expGen) {
|
|
151
|
+
function getRegExpFromNode(specs, node, expGen) {
|
|
152
152
|
var _a;
|
|
153
153
|
// console.log({ n: node.nodeName });
|
|
154
154
|
if (expMapOnNodeId.has(node.uuid)) {
|
|
155
155
|
return expMapOnNodeId.get(node.uuid);
|
|
156
156
|
}
|
|
157
|
-
const parentExp = node.parentNode ? getRegExpFromNode(node.parentNode, expGen) : null;
|
|
158
|
-
const spec = !node.isCustomElement && ((_a = (0, helpers_1.htmlSpec)(node.nameWithNS || node.nodeName.toLowerCase())) === null || _a === void 0 ? void 0 : _a.permittedStructures);
|
|
157
|
+
const parentExp = node.parentNode ? getRegExpFromNode(specs, node.parentNode, expGen) : null;
|
|
158
|
+
const spec = !node.isCustomElement && ((_a = (0, helpers_1.htmlSpec)(specs, node.nameWithNS || node.nodeName.toLowerCase())) === null || _a === void 0 ? void 0 : _a.permittedStructures);
|
|
159
159
|
const contentRule = spec ? spec.contents : true;
|
|
160
160
|
const exp = expGen.specToRegExp(contentRule, parentExp, node.ns || null);
|
|
161
161
|
expMapOnNodeId.set(node.uuid, exp);
|
|
162
162
|
return exp;
|
|
163
163
|
}
|
|
164
|
-
function getRegExpFromParentNode(node, expGen) {
|
|
164
|
+
function getRegExpFromParentNode(specs, node, expGen) {
|
|
165
165
|
// console.log({ p: node.nodeName });
|
|
166
|
-
const parentExp = node.parentNode ? getRegExpFromNode(node.parentNode, expGen) : null;
|
|
166
|
+
const parentExp = node.parentNode ? getRegExpFromNode(specs, node.parentNode, expGen) : null;
|
|
167
167
|
return parentExp;
|
|
168
168
|
}
|
|
169
|
+
const matchingCacheMap = new Map();
|
|
169
170
|
function match(exp, childNodes, ownNS, evalCustomElement) {
|
|
170
171
|
const target = normalization(childNodes, ownNS, evalCustomElement);
|
|
172
|
+
const cacheKey = target + exp.source + childNodes.map(n => (n.type == 'Element' ? n.toNormalizeString() : n.originRaw)).join('');
|
|
173
|
+
const res = matchingCacheMap.get(cacheKey);
|
|
174
|
+
if (res != null) {
|
|
175
|
+
return res;
|
|
176
|
+
}
|
|
177
|
+
const matched = _match(target, exp, childNodes);
|
|
178
|
+
matchingCacheMap.set(cacheKey, matched);
|
|
179
|
+
return matched;
|
|
180
|
+
}
|
|
181
|
+
function _match(target, exp, childNodes) {
|
|
171
182
|
const result = exp.exec(target);
|
|
172
183
|
if (!result) {
|
|
173
184
|
return false;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ml_core_1 = require("@markuplint/ml-core");
|
|
4
|
+
exports.default = (0, ml_core_1.createRule)({
|
|
5
|
+
defaultValue: [],
|
|
6
|
+
defaultOptions: null,
|
|
7
|
+
async verify({ document, report, t, globalRule }) {
|
|
8
|
+
for (const query of globalRule.value) {
|
|
9
|
+
const exists = document.matchNodes(query);
|
|
10
|
+
if (exists.length === 0) {
|
|
11
|
+
const message = t('Require {0}', t('the "{0*}" {1}', query, 'element'));
|
|
12
|
+
report({
|
|
13
|
+
message,
|
|
14
|
+
line: 1,
|
|
15
|
+
col: 1,
|
|
16
|
+
raw: document.nodeList[0].raw.slice(0, 1),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
await document.walkOn('Element', el => {
|
|
21
|
+
if (el.rule.value === globalRule.value) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
for (const query of el.rule.value) {
|
|
25
|
+
const exists = el.children.find(child => child.matches(query));
|
|
26
|
+
if (!exists) {
|
|
27
|
+
const message = t('Require {0}', t('the "{0*}" {1}', query, 'element'));
|
|
28
|
+
report({
|
|
29
|
+
message,
|
|
30
|
+
line: 1,
|
|
31
|
+
col: 1,
|
|
32
|
+
raw: document.nodeList[0].raw.slice(0, 1),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
declare type RequiredAttributes = string | string[];
|
|
1
|
+
declare type RequiredAttributes = string | (string | Attr)[];
|
|
2
|
+
declare type Attr = {
|
|
3
|
+
name: string;
|
|
4
|
+
value: string | string[];
|
|
5
|
+
};
|
|
2
6
|
declare const _default: import("@markuplint/ml-core").RuleSeed<RequiredAttributes, null>;
|
|
3
7
|
export default _default;
|
|
@@ -4,32 +4,47 @@ const ml_core_1 = require("@markuplint/ml-core");
|
|
|
4
4
|
const helpers_1 = require("../helpers");
|
|
5
5
|
exports.default = (0, ml_core_1.createRule)({
|
|
6
6
|
defaultValue: [],
|
|
7
|
-
defaultOptions: null,
|
|
8
7
|
async verify({ document, report, t }) {
|
|
9
8
|
await document.walkOn('Element', async (node) => {
|
|
9
|
+
var _a;
|
|
10
10
|
if (node.hasSpreadAttr) {
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
const customRequiredAttrs = typeof node.rule.value === 'string' ? [node.rule.value] : node.rule.value;
|
|
14
|
-
const attrSpec = (0,
|
|
15
|
-
const attributeSpecs =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (required) {
|
|
19
|
-
|
|
20
|
-
...
|
|
21
|
-
|
|
14
|
+
const attrSpec = (0, ml_core_1.getAttrSpecs)(node.nameWithNS, document.specs);
|
|
15
|
+
const attributeSpecs = {};
|
|
16
|
+
if (attrSpec && !node.isCustomElement) {
|
|
17
|
+
for (const spec of attrSpec) {
|
|
18
|
+
if (spec.required || spec.requiredEither || spec.condition) {
|
|
19
|
+
attributeSpecs[spec.name] = {
|
|
20
|
+
...spec,
|
|
21
|
+
values: [],
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (const req of customRequiredAttrs) {
|
|
27
|
+
let name;
|
|
28
|
+
const values = [];
|
|
29
|
+
if (typeof req === 'string') {
|
|
30
|
+
name = req;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
name = req.name;
|
|
34
|
+
if (Array.isArray(req.value)) {
|
|
35
|
+
values.push(...req.value);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
values.push(req.value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
attributeSpecs[name] = {
|
|
42
|
+
name,
|
|
43
|
+
values,
|
|
28
44
|
required: true,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
for (const spec of attributeSpecs) {
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
for (const spec of Object.values(attributeSpecs)) {
|
|
33
48
|
const didntHave = !node.hasAttribute(spec.name);
|
|
34
49
|
let invalid = false;
|
|
35
50
|
if (spec.requiredEither) {
|
|
@@ -41,17 +56,43 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
41
56
|
}
|
|
42
57
|
else if (spec.required) {
|
|
43
58
|
if ('ancestor' in spec.required && spec.required.ancestor) {
|
|
44
|
-
const
|
|
59
|
+
const ancestorList = Array.isArray(spec.required.ancestor)
|
|
60
|
+
? spec.required.ancestor
|
|
61
|
+
: [spec.required.ancestor];
|
|
62
|
+
const ancestors = ancestorList
|
|
63
|
+
.join(',')
|
|
64
|
+
.split(',')
|
|
65
|
+
.map(a => a.trim());
|
|
45
66
|
invalid = ancestors.some(a => node.closest(a)) && didntHave;
|
|
46
67
|
}
|
|
47
68
|
}
|
|
48
69
|
if (invalid) {
|
|
49
|
-
const message = t('{0} expects {1}', t('the "{0}" {1}',
|
|
70
|
+
const message = t('{0} expects {1}', t('the "{0*}" {1}', node.nodeName, 'element'), t('the "{0*}" {1}', spec.name, 'attribute'));
|
|
50
71
|
report({
|
|
51
72
|
scope: node,
|
|
52
73
|
message,
|
|
53
74
|
});
|
|
54
75
|
}
|
|
76
|
+
const value = node.getAttribute(spec.name);
|
|
77
|
+
if (spec.values.length && value) {
|
|
78
|
+
const matched = spec.values.some(pattern => (0, helpers_1.match)(value, pattern));
|
|
79
|
+
if (!matched) {
|
|
80
|
+
const expects = spec.values.length === 1 ? t(spec.values) : t('either {0}', t(spec.values));
|
|
81
|
+
const message = t('{0} expects {1}', t('the "{0*}" {1}', spec.name, 'attribute'), expects);
|
|
82
|
+
const attrToken = node.getAttributeToken(spec.name);
|
|
83
|
+
const valueToken = ((_a = attrToken[0]) === null || _a === void 0 ? void 0 : _a.attrType) === 'html-attr' ? attrToken[0].value : null;
|
|
84
|
+
const token = valueToken || attrToken[0];
|
|
85
|
+
report({
|
|
86
|
+
scope: {
|
|
87
|
+
rule: node.rule,
|
|
88
|
+
raw: token.raw,
|
|
89
|
+
startCol: token.startCol,
|
|
90
|
+
startLine: token.startLine,
|
|
91
|
+
},
|
|
92
|
+
message,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
55
96
|
}
|
|
56
97
|
});
|
|
57
98
|
},
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ml_core_1 = require("@markuplint/ml-core");
|
|
4
|
+
exports.default = (0, ml_core_1.createRule)({
|
|
5
|
+
defaultValue: [],
|
|
6
|
+
defaultOptions: {
|
|
7
|
+
ignoreHasMutableContents: true,
|
|
8
|
+
},
|
|
9
|
+
async verify({ document, report, t, globalRule }) {
|
|
10
|
+
const hasMutableContent = document.nodeList.some(n => (n.type === 'Element' || n.type === 'OmittedElement') && n.hasMutableChildren());
|
|
11
|
+
if (!hasMutableContent) {
|
|
12
|
+
for (const query of globalRule.value) {
|
|
13
|
+
const exists = document.matchNodes(query);
|
|
14
|
+
if (exists.length === 0) {
|
|
15
|
+
const message = t('Require {0}', t('the "{0*}" {1}', query, 'element'));
|
|
16
|
+
report({
|
|
17
|
+
message,
|
|
18
|
+
line: 1,
|
|
19
|
+
col: 1,
|
|
20
|
+
raw: document.nodeList[0].raw.slice(0, 1),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
await document.walkOn('Element', el => {
|
|
26
|
+
if (el.rule.value === globalRule.value) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (el.rule.option.ignoreHasMutableContents && el.hasMutableChildren()) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
for (const query of el.rule.value) {
|
|
33
|
+
const exists = el.children.find(child => child.matches(query));
|
|
34
|
+
if (!exists) {
|
|
35
|
+
const message = t('Require {0}', t('the "{0*}" {1}', query, 'element'));
|
|
36
|
+
report({
|
|
37
|
+
scope: el,
|
|
38
|
+
message,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
export
|
|
2
|
-
export interface RequiredH1Options {
|
|
1
|
+
export interface Options {
|
|
3
2
|
'expected-once'?: boolean;
|
|
4
3
|
'in-document-fragment'?: boolean;
|
|
5
4
|
}
|
|
6
|
-
declare const _default: import("@markuplint/ml-core").RuleSeed<boolean,
|
|
5
|
+
declare const _default: import("@markuplint/ml-core").RuleSeed<boolean, Options>;
|
|
7
6
|
export default _default;
|
package/lib/required-h1/index.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const ml_core_1 = require("@markuplint/ml-core");
|
|
4
4
|
exports.default = (0, ml_core_1.createRule)({
|
|
5
|
-
defaultValue: true,
|
|
6
5
|
defaultOptions: {
|
|
7
6
|
'expected-once': true,
|
|
8
7
|
'in-document-fragment': false,
|
|
@@ -21,7 +20,7 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
21
20
|
}
|
|
22
21
|
});
|
|
23
22
|
if (h1Stack.length === 0) {
|
|
24
|
-
const message = t('Require {0}', t('the "{0}" {1}', 'h1', 'element'));
|
|
23
|
+
const message = t('Require {0}', t('the "{0*}" {1}', 'h1', 'element'));
|
|
25
24
|
report({
|
|
26
25
|
message,
|
|
27
26
|
line: 1,
|
|
@@ -30,7 +29,7 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
30
29
|
});
|
|
31
30
|
}
|
|
32
31
|
else if (globalRule.option['expected-once'] && h1Stack.length > 1) {
|
|
33
|
-
const message = t('{0} is {1:c}', t('the "{0}" {1}', 'h1', 'element'), 'duplicated');
|
|
32
|
+
const message = t('{0} is {1:c}', t('the "{0*}" {1}', 'h1', 'element'), 'duplicated');
|
|
34
33
|
report({
|
|
35
34
|
message,
|
|
36
35
|
line: h1Stack[1].startLine,
|
package/lib/wai-aria/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ declare type Options = {
|
|
|
6
6
|
disallowSetImplicitProps?: boolean;
|
|
7
7
|
disallowDefaultValue?: boolean;
|
|
8
8
|
};
|
|
9
|
-
declare const _default: import("@markuplint/ml-core").RuleSeed<
|
|
9
|
+
declare const _default: import("@markuplint/ml-core").RuleSeed<boolean, Options>;
|
|
10
10
|
export default _default;
|