@markuplint/rules 2.0.0-rc.0 → 2.0.0-rc.1
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 +52 -454
- 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.js +2 -4
- 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 +1 -4
- package/lib/deprecated-element/index.d.ts +1 -1
- package/lib/deprecated-element/index.js +0 -2
- package/lib/disallowed-element/index.d.ts +2 -0
- package/lib/disallowed-element/index.js +34 -0
- package/lib/helpers.d.ts +9 -3
- package/lib/helpers.js +37 -41
- package/lib/id-duplication/index.d.ts +1 -1
- package/lib/id-duplication/index.js +0 -2
- package/lib/index.d.ts +27 -10
- 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 +16 -7
- package/lib/landmark-roles/index.js +0 -1
- 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 +11 -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 +0 -1
- package/lib/wai-aria/index.d.ts +1 -1
- package/lib/wai-aria/index.js +2 -3
- 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
package/lib/index.js
CHANGED
|
@@ -12,13 +12,21 @@ const character_reference_1 = (0, tslib_1.__importDefault)(require("./character-
|
|
|
12
12
|
const class_naming_1 = (0, tslib_1.__importDefault)(require("./class-naming"));
|
|
13
13
|
const deprecated_attr_1 = (0, tslib_1.__importDefault)(require("./deprecated-attr"));
|
|
14
14
|
const deprecated_element_1 = (0, tslib_1.__importDefault)(require("./deprecated-element"));
|
|
15
|
+
const disallowed_element_1 = (0, tslib_1.__importDefault)(require("./disallowed-element"));
|
|
15
16
|
const doctype_1 = (0, tslib_1.__importDefault)(require("./doctype"));
|
|
16
17
|
const id_duplication_1 = (0, tslib_1.__importDefault)(require("./id-duplication"));
|
|
17
18
|
const indentation_1 = (0, tslib_1.__importDefault)(require("./indentation"));
|
|
19
|
+
const ineffective_attr_1 = (0, tslib_1.__importDefault)(require("./ineffective-attr"));
|
|
18
20
|
const invalid_attr_1 = (0, tslib_1.__importDefault)(require("./invalid-attr"));
|
|
19
21
|
const landmark_roles_1 = (0, tslib_1.__importDefault)(require("./landmark-roles"));
|
|
22
|
+
const no_boolean_attr_value_1 = (0, tslib_1.__importDefault)(require("./no-boolean-attr-value"));
|
|
23
|
+
const no_default_value_1 = (0, tslib_1.__importDefault)(require("./no-default-value"));
|
|
24
|
+
const no_hard_code_id_1 = (0, tslib_1.__importDefault)(require("./no-hard-code-id"));
|
|
25
|
+
const no_refer_to_non_existent_id_1 = (0, tslib_1.__importDefault)(require("./no-refer-to-non-existent-id"));
|
|
26
|
+
const no_use_event_handler_attr_1 = (0, tslib_1.__importDefault)(require("./no-use-event-handler-attr"));
|
|
20
27
|
const permitted_contents_1 = (0, tslib_1.__importDefault)(require("./permitted-contents"));
|
|
21
28
|
const required_attr_1 = (0, tslib_1.__importDefault)(require("./required-attr"));
|
|
29
|
+
const required_element_1 = (0, tslib_1.__importDefault)(require("./required-element"));
|
|
22
30
|
const required_h1_1 = (0, tslib_1.__importDefault)(require("./required-h1"));
|
|
23
31
|
const wai_aria_1 = (0, tslib_1.__importDefault)(require("./wai-aria"));
|
|
24
32
|
exports.default = {
|
|
@@ -33,13 +41,21 @@ exports.default = {
|
|
|
33
41
|
'class-naming': class_naming_1.default,
|
|
34
42
|
'deprecated-attr': deprecated_attr_1.default,
|
|
35
43
|
'deprecated-element': deprecated_element_1.default,
|
|
44
|
+
'disallowed-element': disallowed_element_1.default,
|
|
36
45
|
doctype: doctype_1.default,
|
|
37
46
|
'id-duplication': id_duplication_1.default,
|
|
38
47
|
indentation: indentation_1.default,
|
|
48
|
+
'ineffective-attr': ineffective_attr_1.default,
|
|
39
49
|
'invalid-attr': invalid_attr_1.default,
|
|
40
50
|
'landmark-roles': landmark_roles_1.default,
|
|
51
|
+
'no-boolean-attr-value': no_boolean_attr_value_1.default,
|
|
52
|
+
'no-default-value': no_default_value_1.default,
|
|
53
|
+
'no-hard-code-id': no_hard_code_id_1.default,
|
|
54
|
+
'no-refer-to-non-existent-id': no_refer_to_non_existent_id_1.default,
|
|
55
|
+
'no-use-event-handler-attr': no_use_event_handler_attr_1.default,
|
|
41
56
|
'permitted-contents': permitted_contents_1.default,
|
|
42
57
|
'required-attr': required_attr_1.default,
|
|
58
|
+
'required-element': required_element_1.default,
|
|
43
59
|
'required-h1': required_h1_1.default,
|
|
44
60
|
'wai-aria': wai_aria_1.default,
|
|
45
61
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
document.walkOn('Element', el => {
|
|
8
|
+
const attrSpec = (0, ml_core_1.getAttrSpecs)(el.nameWithNS, document.specs);
|
|
9
|
+
if (!attrSpec) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
for (const attr of el.attributes) {
|
|
13
|
+
if (attr.attrType === 'ps-attr') {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const name = attr.getName().potential;
|
|
17
|
+
const spec = attrSpec.find(s => s.name === name);
|
|
18
|
+
if (!spec) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const ineffectiveConditions = spec.ineffective
|
|
22
|
+
? Array.isArray(spec.ineffective)
|
|
23
|
+
? spec.ineffective
|
|
24
|
+
: [spec.ineffective]
|
|
25
|
+
: [];
|
|
26
|
+
if (ineffectiveConditions.some(selector => el.matches(selector))) {
|
|
27
|
+
report({
|
|
28
|
+
scope: el,
|
|
29
|
+
raw: attr.raw,
|
|
30
|
+
line: attr.startLine,
|
|
31
|
+
col: attr.startCol,
|
|
32
|
+
message: t('{0} is {1:c}', t('the "{0}" {1}', name, 'attribute'), 'ineffective') +
|
|
33
|
+
t('. ') +
|
|
34
|
+
t("It doesn't need {0}", t('the {0}', 'attribute')),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
});
|
|
@@ -4,11 +4,13 @@ declare type Option = {
|
|
|
4
4
|
ignoreAttrNamePrefix?: string | string[];
|
|
5
5
|
};
|
|
6
6
|
declare type Rule = {
|
|
7
|
-
enum: string[];
|
|
7
|
+
enum: [string, ...string[]];
|
|
8
8
|
} | {
|
|
9
9
|
pattern: string;
|
|
10
10
|
} | {
|
|
11
11
|
type: AttributeType;
|
|
12
|
+
} | {
|
|
13
|
+
disallowed: true;
|
|
12
14
|
};
|
|
13
|
-
declare const _default: import("@markuplint/ml-core").RuleSeed<
|
|
15
|
+
declare const _default: import("@markuplint/ml-core").RuleSeed<boolean, Option>;
|
|
14
16
|
export default _default;
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const ml_core_1 = require("@markuplint/ml-core");
|
|
4
4
|
const attr_check_1 = require("../attr-check");
|
|
5
|
+
const debug_1 = require("../debug");
|
|
5
6
|
const helpers_1 = require("../helpers");
|
|
7
|
+
const log = debug_1.log.extend('invalid-attr');
|
|
6
8
|
exports.default = (0, ml_core_1.createRule)({
|
|
7
|
-
defaultValue: true,
|
|
8
9
|
defaultOptions: {},
|
|
9
10
|
async verify({ document, report, t }) {
|
|
10
11
|
await document.walkOn('Element', async (node) => {
|
|
11
|
-
|
|
12
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
13
|
+
const attrSpecs = (0, ml_core_1.getAttrSpecs)(node.nameWithNS, document.specs);
|
|
12
14
|
for (const attr of node.attributes) {
|
|
13
15
|
if (attr.attrType === 'html-attr' && attr.isDirective) {
|
|
14
16
|
continue;
|
|
@@ -29,7 +31,7 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
29
31
|
continue;
|
|
30
32
|
}
|
|
31
33
|
const attrValue = attr.getValue();
|
|
32
|
-
const value = attrValue.
|
|
34
|
+
const value = attrValue.potential;
|
|
33
35
|
if (node.rule.option.ignoreAttrNamePrefix) {
|
|
34
36
|
const ignoreAttrNamePrefixes = Array.isArray(node.rule.option.ignoreAttrNamePrefix)
|
|
35
37
|
? node.rule.option.ignoreAttrNamePrefix
|
|
@@ -61,9 +63,16 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
61
63
|
else if ('type' in customRule) {
|
|
62
64
|
invalid = (0, attr_check_1.attrCheck)(t, name, value, true, { name, type: customRule.type, description: '' });
|
|
63
65
|
}
|
|
66
|
+
else if ('disallowed' in customRule && customRule.disallowed) {
|
|
67
|
+
invalid = {
|
|
68
|
+
invalidType: 'non-existent',
|
|
69
|
+
message: t('{0} is disallowed', t('the "{0}" {1}', name, 'attribute')),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
64
72
|
}
|
|
65
73
|
else if (!node.isCustomElement && attrSpecs) {
|
|
66
|
-
|
|
74
|
+
log('Checking %s[%s="%s"]', node.nodeName, name, value);
|
|
75
|
+
invalid = (0, helpers_1.isValidAttr)(t, name, value, (attr.attrType === 'html-attr' && attr.isDynamicValue) || false, node, attrSpecs, log);
|
|
67
76
|
}
|
|
68
77
|
if (invalid) {
|
|
69
78
|
switch (invalid.invalidType) {
|
|
@@ -71,9 +80,9 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
71
80
|
report({
|
|
72
81
|
scope: node,
|
|
73
82
|
message: invalid.message,
|
|
74
|
-
line: attrValue.line,
|
|
75
|
-
col: attrValue.col,
|
|
76
|
-
raw: value,
|
|
83
|
+
line: attrValue.line + ((_b = (_a = invalid.loc) === null || _a === void 0 ? void 0 : _a.line) !== null && _b !== void 0 ? _b : 0),
|
|
84
|
+
col: ((_c = invalid.loc) === null || _c === void 0 ? void 0 : _c.line) ? ((_d = invalid.loc) === null || _d === void 0 ? void 0 : _d.col) + 1 : attrValue.col + ((_f = (_e = invalid.loc) === null || _e === void 0 ? void 0 : _e.col) !== null && _f !== void 0 ? _f : 0),
|
|
85
|
+
raw: (_h = (_g = invalid.loc) === null || _g === void 0 ? void 0 : _g.raw) !== null && _h !== void 0 ? _h : value,
|
|
77
86
|
});
|
|
78
87
|
break;
|
|
79
88
|
}
|
|
@@ -13,7 +13,6 @@ const selectors = {
|
|
|
13
13
|
const topLevelRoles = ['banner', 'main', 'complementary', 'contentinfo'];
|
|
14
14
|
exports.default = (0, ml_core_1.createRule)({
|
|
15
15
|
defaultServerity: 'warning',
|
|
16
|
-
defaultValue: true,
|
|
17
16
|
defaultOptions: {
|
|
18
17
|
ignoreRoles: [],
|
|
19
18
|
labelEachArea: true,
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
await document.walkOn('Element', el => {
|
|
8
|
+
const attrSpec = (0, ml_core_1.getAttrSpecs)(el.nameWithNS, document.specs);
|
|
9
|
+
if (!attrSpec) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
for (const attr of el.attributes) {
|
|
13
|
+
if (attr.attrType === 'ps-attr') {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const name = attr.getName().potential;
|
|
17
|
+
const spec = attrSpec.find(s => s.name === name);
|
|
18
|
+
if (!spec) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (spec.type !== 'Boolean') {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const extraTokens = [
|
|
25
|
+
attr.spacesBeforeEqual,
|
|
26
|
+
attr.equal,
|
|
27
|
+
attr.spacesAfterEqual,
|
|
28
|
+
attr.startQuote,
|
|
29
|
+
attr.value,
|
|
30
|
+
attr.endQuote,
|
|
31
|
+
];
|
|
32
|
+
const extraRaw = extraTokens.reduce((raw, t) => raw + t.raw, '');
|
|
33
|
+
if (extraRaw) {
|
|
34
|
+
report({
|
|
35
|
+
scope: el,
|
|
36
|
+
raw: extraRaw,
|
|
37
|
+
line: attr.spacesBeforeEqual.startLine,
|
|
38
|
+
col: attr.spacesBeforeEqual.startCol,
|
|
39
|
+
message: t('{0} is {1}', t('the "{0}" {1}', name, 'attribute'), t('a {0}', 'boolean attribute')) +
|
|
40
|
+
t('. ') +
|
|
41
|
+
t("It doesn't need {0}", t('the {0}', 'value')),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
async verify({ document, report, t }) {
|
|
8
|
+
await document.walkOn('Element', el => {
|
|
9
|
+
const attrSpec = (0, ml_core_1.getAttrSpecs)(el.nameWithNS, document.specs);
|
|
10
|
+
if (!attrSpec) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
for (const attr of el.attributes) {
|
|
14
|
+
if (attr.attrType !== 'html-attr') {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const spec = attrSpec.find(s => s.name === attr.getName().potential);
|
|
18
|
+
if (!spec) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (!spec.defaultValue) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const normalizedValue = (0, helpers_1.toNormalizedValue)(attr.getValue().potential, spec);
|
|
25
|
+
const defaultValue = (0, helpers_1.toNormalizedValue)(spec.defaultValue, spec);
|
|
26
|
+
if (defaultValue === normalizedValue) {
|
|
27
|
+
report({
|
|
28
|
+
scope: el,
|
|
29
|
+
line: attr.value.startLine,
|
|
30
|
+
col: attr.value.startCol,
|
|
31
|
+
raw: attr.value.raw,
|
|
32
|
+
message: t('It is {0}', t('the {0}', 'default value')),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -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)();
|
|
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') {
|
|
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
|
+
});
|
|
@@ -166,8 +166,19 @@ function getRegExpFromParentNode(node, expGen) {
|
|
|
166
166
|
const parentExp = node.parentNode ? getRegExpFromNode(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
|
},
|