@html-eslint/eslint-plugin 0.20.0 → 0.22.0
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/constants/index.js +0 -2
- package/lib/rules/element-newline.js +30 -11
- package/lib/rules/id-naming-convention.js +30 -12
- package/lib/rules/indent.js +7 -10
- package/lib/rules/index.js +8 -0
- package/lib/rules/lowercase.js +93 -0
- package/lib/rules/no-abstract-roles.js +15 -13
- package/lib/rules/no-accesskey-attrs.js +5 -6
- package/lib/rules/no-aria-hidden-body.js +2 -6
- package/lib/rules/no-duplicate-attrs.js +3 -4
- package/lib/rules/no-duplicate-id.js +7 -7
- package/lib/rules/no-extra-spacing-attrs.js +29 -9
- package/lib/rules/no-inline-styles.js +5 -2
- package/lib/rules/no-multiple-empty-lines.js +3 -4
- package/lib/rules/no-multiple-h1.js +3 -4
- package/lib/rules/no-non-scalable-viewport.js +3 -7
- package/lib/rules/no-obsolete-tags.js +4 -2
- package/lib/rules/no-positive-tabindex.js +5 -6
- package/lib/rules/no-restricted-attr-values.js +9 -3
- package/lib/rules/no-restricted-attrs.js +13 -2
- package/lib/rules/no-script-style-type.js +69 -0
- package/lib/rules/no-skip-heading-levels.js +4 -5
- package/lib/rules/no-target-blank.js +5 -9
- package/lib/rules/no-trailing-spaces.js +0 -4
- package/lib/rules/quotes.js +24 -1
- package/lib/rules/require-attrs.js +18 -7
- package/lib/rules/require-button-type.js +2 -6
- package/lib/rules/require-closing-tags.js +8 -5
- package/lib/rules/require-doctype.js +4 -5
- package/lib/rules/require-frame-title.js +5 -2
- package/lib/rules/require-img-alt.js +10 -0
- package/lib/rules/require-lang.js +5 -6
- package/lib/rules/require-li-container.js +9 -2
- package/lib/rules/require-meta-charset.js +19 -13
- package/lib/rules/require-meta-description.js +9 -12
- package/lib/rules/require-meta-viewport.js +19 -20
- package/lib/rules/require-open-graph-protocol.js +135 -0
- package/lib/rules/require-title.js +19 -25
- package/lib/rules/sort-attrs.js +158 -0
- package/lib/rules/utils/array.js +26 -0
- package/lib/rules/utils/node.js +70 -0
- package/lib/types.d.ts +262 -246
- package/package.json +4 -4
- package/lib/constants/node-types.js +0 -13
- package/lib/rules/utils/index.js +0 -7
- package/lib/rules/utils/node-utils.js +0 -112
- /package/lib/rules/utils/{naming-utils.js → naming.js} +0 -0
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
const { RULE_CATEGORY } = require("../constants");
|
|
6
|
-
const {
|
|
2
|
+
const { findAttr } = require("./utils/node");
|
|
7
3
|
|
|
8
4
|
const MESSAGE_IDS = {
|
|
9
5
|
UNEXPECTED: "unexpected",
|
|
@@ -31,8 +27,11 @@ module.exports = {
|
|
|
31
27
|
|
|
32
28
|
create(context) {
|
|
33
29
|
return {
|
|
30
|
+
/**
|
|
31
|
+
* @param {TagNode | StyleTagNode | ScriptTagNode} node
|
|
32
|
+
*/
|
|
34
33
|
[["Tag", "StyleTag", "ScriptTag"].join(",")](node) {
|
|
35
|
-
const tabIndexAttr =
|
|
34
|
+
const tabIndexAttr = findAttr(node, "tabindex");
|
|
36
35
|
if (
|
|
37
36
|
tabIndexAttr &&
|
|
38
37
|
tabIndexAttr.value &&
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
2
|
* @typedef {{attrPatterns: string[], attrValuePatterns: string[], message?: string}[]} Options
|
|
4
3
|
*/
|
|
5
4
|
|
|
@@ -62,6 +61,9 @@ module.exports = {
|
|
|
62
61
|
const checkers = options.map((option) => new PatternChecker(option));
|
|
63
62
|
|
|
64
63
|
return {
|
|
64
|
+
/**
|
|
65
|
+
* @param {TagNode | StyleTagNode | ScriptTagNode} node
|
|
66
|
+
*/
|
|
65
67
|
[["Tag", "StyleTag", "ScriptTag"].join(",")](node) {
|
|
66
68
|
node.attributes.forEach((attr) => {
|
|
67
69
|
if (
|
|
@@ -73,14 +75,18 @@ module.exports = {
|
|
|
73
75
|
return;
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
const matched = checkers.find(
|
|
77
|
-
checker
|
|
78
|
+
const matched = checkers.find(
|
|
79
|
+
(checker) =>
|
|
80
|
+
attr.value && checker.test(attr.key.value, attr.value.value)
|
|
78
81
|
);
|
|
79
82
|
|
|
80
83
|
if (!matched) {
|
|
81
84
|
return;
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
/**
|
|
88
|
+
* @type {{node: AttributeNode, message: string, messageId?: string}}
|
|
89
|
+
*/
|
|
84
90
|
const result = {
|
|
85
91
|
node: attr,
|
|
86
92
|
message: "",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
2
|
* @typedef {{tagPatterns: string[], attrPatterns: string[], message?: string}[]} Options
|
|
4
3
|
*/
|
|
5
4
|
|
|
5
|
+
const { NODE_TYPES } = require("@html-eslint/parser");
|
|
6
6
|
const { RULE_CATEGORY } = require("../constants");
|
|
7
7
|
|
|
8
8
|
const MESSAGE_IDS = {
|
|
@@ -61,8 +61,16 @@ module.exports = {
|
|
|
61
61
|
const checkers = options.map((option) => new PatternChecker(option));
|
|
62
62
|
|
|
63
63
|
return {
|
|
64
|
+
/**
|
|
65
|
+
* @param {TagNode | StyleTagNode | ScriptTagNode} node
|
|
66
|
+
*/
|
|
64
67
|
[["Tag", "StyleTag", "ScriptTag"].join(",")](node) {
|
|
65
|
-
const tagName =
|
|
68
|
+
const tagName =
|
|
69
|
+
node.type === NODE_TYPES.Tag
|
|
70
|
+
? node.name
|
|
71
|
+
: node.type === NODE_TYPES.ScriptTag
|
|
72
|
+
? "script"
|
|
73
|
+
: "style";
|
|
66
74
|
node.attributes.forEach((attr) => {
|
|
67
75
|
if (!attr.key || !attr.key.value) {
|
|
68
76
|
return;
|
|
@@ -75,6 +83,9 @@ module.exports = {
|
|
|
75
83
|
return;
|
|
76
84
|
}
|
|
77
85
|
|
|
86
|
+
/**
|
|
87
|
+
* @type {{node: AttributeNode, message: string, messageId?: string}}
|
|
88
|
+
*/
|
|
78
89
|
const result = {
|
|
79
90
|
node: attr,
|
|
80
91
|
message: "",
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const { RULE_CATEGORY } = require("../constants");
|
|
2
|
+
const { findAttr } = require("./utils/node");
|
|
3
|
+
|
|
4
|
+
const MESSAGE_IDS = {
|
|
5
|
+
UNNECESSARY: "unnecessary",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @type {Rule}
|
|
10
|
+
*/
|
|
11
|
+
module.exports = {
|
|
12
|
+
meta: {
|
|
13
|
+
type: "code",
|
|
14
|
+
|
|
15
|
+
docs: {
|
|
16
|
+
description:
|
|
17
|
+
"Enforce to omit type attributes for style sheets and scripts",
|
|
18
|
+
category: RULE_CATEGORY.BEST_PRACTICE,
|
|
19
|
+
recommended: false,
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
fixable: "code",
|
|
23
|
+
schema: [],
|
|
24
|
+
messages: {
|
|
25
|
+
[MESSAGE_IDS.UNNECESSARY]: "Unnecessary type attributes",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
create(context) {
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
* @param {ScriptTagNode | TagNode | StyleTagNode} node
|
|
33
|
+
* @param {string} unnecessaryValue
|
|
34
|
+
*/
|
|
35
|
+
function check(node, unnecessaryValue) {
|
|
36
|
+
const type = findAttr(node, "type");
|
|
37
|
+
if (
|
|
38
|
+
type &&
|
|
39
|
+
type.value &&
|
|
40
|
+
type.value.value &&
|
|
41
|
+
type.value.value.trim().toLocaleLowerCase() === unnecessaryValue
|
|
42
|
+
) {
|
|
43
|
+
context.report({
|
|
44
|
+
node: type,
|
|
45
|
+
messageId: MESSAGE_IDS.UNNECESSARY,
|
|
46
|
+
fix(fixer) {
|
|
47
|
+
return fixer.remove(type);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
ScriptTag(node) {
|
|
54
|
+
check(node, "text/javascript");
|
|
55
|
+
},
|
|
56
|
+
StyleTag(node) {
|
|
57
|
+
check(node, "text/css");
|
|
58
|
+
},
|
|
59
|
+
Tag(node) {
|
|
60
|
+
if (node.name === "link") {
|
|
61
|
+
const rel = findAttr(node, "rel");
|
|
62
|
+
if (rel && rel.value && rel.value.value === "stylesheet") {
|
|
63
|
+
check(node, "text/css");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
const { RULE_CATEGORY } = require("../constants");
|
|
6
2
|
|
|
7
3
|
const MESSAGE_IDS = {
|
|
@@ -30,6 +26,9 @@ module.exports = {
|
|
|
30
26
|
},
|
|
31
27
|
|
|
32
28
|
create(context) {
|
|
29
|
+
/**
|
|
30
|
+
* @type {{node: TagNode; level: number}[]}
|
|
31
|
+
*/
|
|
33
32
|
const headings = [];
|
|
34
33
|
|
|
35
34
|
return {
|
|
@@ -53,7 +52,7 @@ module.exports = {
|
|
|
53
52
|
if (next.level - current.level > 1) {
|
|
54
53
|
context.report({
|
|
55
54
|
node: next.node,
|
|
56
|
-
data: { expected: current.level + 1 },
|
|
55
|
+
data: { expected: String(current.level + 1) },
|
|
57
56
|
messageId: MESSAGE_IDS.UNEXPECTED,
|
|
58
57
|
});
|
|
59
58
|
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
const { RULE_CATEGORY } = require("../constants");
|
|
6
|
-
const {
|
|
2
|
+
const { findAttr } = require("./utils/node");
|
|
7
3
|
|
|
8
4
|
const MESSAGE_IDS = {
|
|
9
5
|
MISSING: "missing",
|
|
@@ -43,12 +39,12 @@ module.exports = {
|
|
|
43
39
|
if (node.name !== "a") {
|
|
44
40
|
return;
|
|
45
41
|
}
|
|
46
|
-
|
|
47
|
-
const target =
|
|
42
|
+
|
|
43
|
+
const target = findAttr(node, "target");
|
|
48
44
|
if (target && target.value && target.value.value === "_blank") {
|
|
49
|
-
const href =
|
|
45
|
+
const href = findAttr(node, "href");
|
|
50
46
|
if (href && href.value && isExternalLink(href.value.value)) {
|
|
51
|
-
const rel =
|
|
47
|
+
const rel = findAttr(node, "rel");
|
|
52
48
|
if (!rel || !rel.value || !rel.value.value.includes("noreferrer")) {
|
|
53
49
|
context.report({
|
|
54
50
|
node: target,
|
package/lib/rules/quotes.js
CHANGED
|
@@ -12,6 +12,9 @@ const QUOTES_STYLES = {
|
|
|
12
12
|
|
|
13
13
|
const QUOTES_CODES = [`"`, `'`];
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @type {Rule}
|
|
17
|
+
*/
|
|
15
18
|
module.exports = {
|
|
16
19
|
meta: {
|
|
17
20
|
type: "code",
|
|
@@ -53,10 +56,20 @@ module.exports = {
|
|
|
53
56
|
return sourceCode.text.slice(range[0], range[1]);
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
/**
|
|
60
|
+
*
|
|
61
|
+
* @param {AttributeNode} attr
|
|
62
|
+
* @returns {[string, string]}
|
|
63
|
+
*/
|
|
56
64
|
function getQuotes(attr) {
|
|
65
|
+
// @ts-ignore
|
|
57
66
|
return [attr.startWrapper.value, attr.endWrapper.value];
|
|
58
67
|
}
|
|
59
68
|
|
|
69
|
+
/**
|
|
70
|
+
* @param {AttributeNode} attr
|
|
71
|
+
* @returns {void}
|
|
72
|
+
*/
|
|
60
73
|
function checkQuotes(attr) {
|
|
61
74
|
if (!attr.value || attr.value.value.includes(expectedQuote)) {
|
|
62
75
|
return;
|
|
@@ -77,6 +90,12 @@ module.exports = {
|
|
|
77
90
|
: `${QUOTES_STYLES.SINGLE}(')`,
|
|
78
91
|
},
|
|
79
92
|
fix(fixer) {
|
|
93
|
+
if (
|
|
94
|
+
!attr.startWrapper ||
|
|
95
|
+
!attr.endWrapper ||
|
|
96
|
+
attr.value === undefined
|
|
97
|
+
)
|
|
98
|
+
return null;
|
|
80
99
|
return fixer.replaceTextRange(
|
|
81
100
|
[attr.startWrapper.range[0], attr.endWrapper.range[1]],
|
|
82
101
|
`${expectedQuote}${attr.value.value}${expectedQuote}`
|
|
@@ -93,6 +112,7 @@ module.exports = {
|
|
|
93
112
|
expected: `${SELECTED_STYLE}(${expectedQuote})`,
|
|
94
113
|
},
|
|
95
114
|
fix(fixer) {
|
|
115
|
+
if (attr.value === undefined) return null;
|
|
96
116
|
const originCode = getCodeIn(attr.value.range);
|
|
97
117
|
return fixer.replaceTextRange(
|
|
98
118
|
attr.value.range,
|
|
@@ -104,8 +124,11 @@ module.exports = {
|
|
|
104
124
|
}
|
|
105
125
|
|
|
106
126
|
return {
|
|
127
|
+
/**
|
|
128
|
+
* @param {TagNode | ScriptTagNode | StyleTagNode} node
|
|
129
|
+
*/
|
|
107
130
|
[["Tag", "ScriptTag", "StyleTag"].join(",")](node) {
|
|
108
|
-
node.attributes.forEach(checkQuotes);
|
|
131
|
+
node.attributes.forEach((attr) => checkQuotes(attr));
|
|
109
132
|
},
|
|
110
133
|
};
|
|
111
134
|
},
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
*/
|
|
4
|
-
|
|
1
|
+
const { NODE_TYPES } = require("@html-eslint/parser");
|
|
5
2
|
const { RULE_CATEGORY } = require("../constants");
|
|
6
3
|
|
|
7
4
|
const MESSAGE_IDS = {
|
|
@@ -44,19 +41,29 @@ module.exports = {
|
|
|
44
41
|
|
|
45
42
|
create(context) {
|
|
46
43
|
const options = context.options || [];
|
|
44
|
+
/**
|
|
45
|
+
* @type {Map<string, { tag: string, attr: string, value?: string}[]>}
|
|
46
|
+
*/
|
|
47
47
|
const tagOptionsMap = new Map();
|
|
48
48
|
|
|
49
49
|
options.forEach((option) => {
|
|
50
50
|
const tagName = option.tag.toLowerCase();
|
|
51
51
|
if (tagOptionsMap.has(tagName)) {
|
|
52
|
-
tagOptionsMap.set(tagName, [
|
|
52
|
+
tagOptionsMap.set(tagName, [
|
|
53
|
+
...(tagOptionsMap.get(tagName) || []),
|
|
54
|
+
option,
|
|
55
|
+
]);
|
|
53
56
|
} else {
|
|
54
57
|
tagOptionsMap.set(tagName, [option]);
|
|
55
58
|
}
|
|
56
59
|
});
|
|
57
60
|
|
|
61
|
+
/**
|
|
62
|
+
* @param {StyleTagNode | ScriptTagNode | TagNode} node
|
|
63
|
+
* @param {string} tagName
|
|
64
|
+
*/
|
|
58
65
|
function check(node, tagName) {
|
|
59
|
-
const tagOptions = tagOptionsMap.get(tagName);
|
|
66
|
+
const tagOptions = tagOptionsMap.get(tagName) || [];
|
|
60
67
|
const attributes = node.attributes || [];
|
|
61
68
|
|
|
62
69
|
tagOptions.forEach((option) => {
|
|
@@ -90,8 +97,12 @@ module.exports = {
|
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
return {
|
|
100
|
+
/**
|
|
101
|
+
* @param {StyleTagNode | ScriptTagNode} node
|
|
102
|
+
* @returns
|
|
103
|
+
*/
|
|
93
104
|
[["StyleTag", "ScriptTag"].join(",")](node) {
|
|
94
|
-
const tagName = node.type ===
|
|
105
|
+
const tagName = node.type === NODE_TYPES.StyleTag ? "style" : "script";
|
|
95
106
|
if (!tagOptionsMap.has(tagName)) {
|
|
96
107
|
return;
|
|
97
108
|
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
const { RULE_CATEGORY } = require("../constants");
|
|
6
|
-
const {
|
|
2
|
+
const { findAttr } = require("./utils/node");
|
|
7
3
|
|
|
8
4
|
const MESSAGE_IDS = {
|
|
9
5
|
MISSING: "missing",
|
|
@@ -40,7 +36,7 @@ module.exports = {
|
|
|
40
36
|
if (node.name !== "button") {
|
|
41
37
|
return;
|
|
42
38
|
}
|
|
43
|
-
const typeAttr =
|
|
39
|
+
const typeAttr = findAttr(node, "type");
|
|
44
40
|
if (!typeAttr || !typeAttr.value) {
|
|
45
41
|
context.report({
|
|
46
42
|
node: node.openStart,
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
const { RULE_CATEGORY, VOID_ELEMENTS } = require("../constants");
|
|
6
2
|
|
|
7
3
|
const VOID_ELEMENTS_SET = new Set(VOID_ELEMENTS);
|
|
@@ -44,7 +40,6 @@ module.exports = {
|
|
|
44
40
|
[MESSAGE_IDS.MISSING]: "Missing closing tag for {{tag}}.",
|
|
45
41
|
[MESSAGE_IDS.MISSING_SELF]: "Missing self closing tag for {{tag}}",
|
|
46
42
|
[MESSAGE_IDS.UNEXPECTED]: "Unexpected self closing tag for {{tag}}.",
|
|
47
|
-
[MESSAGE_IDS.HUCKS]: "HUCKS.",
|
|
48
43
|
},
|
|
49
44
|
},
|
|
50
45
|
|
|
@@ -58,6 +53,9 @@ module.exports = {
|
|
|
58
53
|
? context.options[0].allowSelfClosingCustom === true
|
|
59
54
|
: false;
|
|
60
55
|
|
|
56
|
+
/**
|
|
57
|
+
* @param {TagNode} node
|
|
58
|
+
*/
|
|
61
59
|
function checkClosingTag(node) {
|
|
62
60
|
if (!node.close) {
|
|
63
61
|
context.report({
|
|
@@ -70,6 +68,11 @@ module.exports = {
|
|
|
70
68
|
}
|
|
71
69
|
}
|
|
72
70
|
|
|
71
|
+
/**
|
|
72
|
+
* @param {TagNode} node
|
|
73
|
+
* @param {boolean} shouldSelfClose
|
|
74
|
+
* @param {boolean} fixable
|
|
75
|
+
*/
|
|
73
76
|
function checkVoidElement(node, shouldSelfClose, fixable) {
|
|
74
77
|
const hasSelfClose = node.openEnd.value === "/>";
|
|
75
78
|
if (shouldSelfClose && !hasSelfClose) {
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
const { RULE_CATEGORY } = require("../constants");
|
|
6
2
|
|
|
7
3
|
const MESSAGE_IDS = {
|
|
@@ -34,7 +30,10 @@ module.exports = {
|
|
|
34
30
|
Doctype() {
|
|
35
31
|
hasDocType = true;
|
|
36
32
|
},
|
|
37
|
-
|
|
33
|
+
Tag(node) {
|
|
34
|
+
if (node.name !== "html") {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
38
37
|
if (!hasDocType) {
|
|
39
38
|
context.report({
|
|
40
39
|
node,
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
|
-
const {
|
|
2
|
+
const { findAttr } = require("./utils/node");
|
|
3
3
|
|
|
4
4
|
const MESSAGE_IDS = {
|
|
5
5
|
MISSING: "missing",
|
|
6
6
|
UNEXPECTED: "unexpected",
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @type {Rule}
|
|
11
|
+
*/
|
|
9
12
|
module.exports = {
|
|
10
13
|
meta: {
|
|
11
14
|
type: "code",
|
|
@@ -30,7 +33,7 @@ module.exports = {
|
|
|
30
33
|
if (node.name !== "frame" && node.name !== "iframe") {
|
|
31
34
|
return;
|
|
32
35
|
}
|
|
33
|
-
const title =
|
|
36
|
+
const title = findAttr(node, "title");
|
|
34
37
|
if (!title) {
|
|
35
38
|
context.report({
|
|
36
39
|
node: node.openStart,
|
|
@@ -4,6 +4,9 @@ const MESSAGE_IDS = {
|
|
|
4
4
|
MISSING_ALT: "missingAlt",
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @type {Rule}
|
|
9
|
+
*/
|
|
7
10
|
module.exports = {
|
|
8
11
|
meta: {
|
|
9
12
|
type: "code",
|
|
@@ -62,6 +65,12 @@ module.exports = {
|
|
|
62
65
|
},
|
|
63
66
|
};
|
|
64
67
|
|
|
68
|
+
/**
|
|
69
|
+
*
|
|
70
|
+
* @param {TagNode} node
|
|
71
|
+
* @param {string[]} substitute
|
|
72
|
+
* @returns
|
|
73
|
+
*/
|
|
65
74
|
function hasAltAttrAndValue(node, substitute = []) {
|
|
66
75
|
return node.attributes.some((attr) => {
|
|
67
76
|
if (attr.key && attr.value) {
|
|
@@ -70,5 +79,6 @@ function hasAltAttrAndValue(node, substitute = []) {
|
|
|
70
79
|
typeof attr.value.value === "string"
|
|
71
80
|
);
|
|
72
81
|
}
|
|
82
|
+
return false;
|
|
73
83
|
});
|
|
74
84
|
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
const { RULE_CATEGORY } = require("../constants");
|
|
6
|
-
const {
|
|
2
|
+
const { findAttr } = require("./utils/node");
|
|
7
3
|
|
|
8
4
|
const MESSAGE_IDS = {
|
|
9
5
|
MISSING: "missing",
|
|
10
6
|
EMPTY: "empty",
|
|
11
7
|
};
|
|
12
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @type {Rule}
|
|
11
|
+
*/
|
|
13
12
|
module.exports = {
|
|
14
13
|
meta: {
|
|
15
14
|
type: "code",
|
|
@@ -34,7 +33,7 @@ module.exports = {
|
|
|
34
33
|
if (node.name !== "html") {
|
|
35
34
|
return;
|
|
36
35
|
}
|
|
37
|
-
const langAttr =
|
|
36
|
+
const langAttr = findAttr(node, "lang");
|
|
38
37
|
if (!langAttr) {
|
|
39
38
|
context.report({
|
|
40
39
|
node: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { NODE_TYPES } = require("@html-eslint/parser");
|
|
1
2
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
3
|
|
|
3
4
|
const MESSAGE_IDS = {
|
|
@@ -6,6 +7,9 @@ const MESSAGE_IDS = {
|
|
|
6
7
|
|
|
7
8
|
const VALID_CONTAINERS = ["ul", "ol", "menu"];
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @type {Rule}
|
|
12
|
+
*/
|
|
9
13
|
module.exports = {
|
|
10
14
|
meta: {
|
|
11
15
|
type: "code",
|
|
@@ -30,12 +34,15 @@ module.exports = {
|
|
|
30
34
|
if (node.name !== "li") {
|
|
31
35
|
return;
|
|
32
36
|
}
|
|
33
|
-
if (!node.parent) {
|
|
37
|
+
if (!node.parent || node.parent.type === NODE_TYPES.Program) {
|
|
34
38
|
context.report({
|
|
35
39
|
node,
|
|
36
40
|
messageId: MESSAGE_IDS.INVALID,
|
|
37
41
|
});
|
|
38
|
-
} else if (
|
|
42
|
+
} else if (
|
|
43
|
+
node.parent.type === NODE_TYPES.Tag &&
|
|
44
|
+
!VALID_CONTAINERS.includes(node.parent.name || "")
|
|
45
|
+
) {
|
|
39
46
|
context.report({
|
|
40
47
|
node,
|
|
41
48
|
messageId: MESSAGE_IDS.INVALID,
|
|
@@ -1,15 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
*/
|
|
4
|
-
|
|
1
|
+
const { NODE_TYPES } = require("@html-eslint/parser");
|
|
5
2
|
const { RULE_CATEGORY } = require("../constants");
|
|
6
|
-
const {
|
|
3
|
+
const { find } = require("./utils/array");
|
|
4
|
+
const { findAttr } = require("./utils/node");
|
|
7
5
|
|
|
8
6
|
const MESSAGE_IDS = {
|
|
9
7
|
MISSING: "missing",
|
|
10
8
|
EMPTY: "empty",
|
|
11
9
|
};
|
|
12
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @param {ChildType<TagNode>} node
|
|
13
|
+
* @returns {node is TagNode}
|
|
14
|
+
*/
|
|
15
|
+
function isMetaCharset(node) {
|
|
16
|
+
return (
|
|
17
|
+
node.type === NODE_TYPES.Tag &&
|
|
18
|
+
node.name === "meta" &&
|
|
19
|
+
!!findAttr(node, "charset")
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
/**
|
|
14
24
|
* @type {Rule}
|
|
15
25
|
*/
|
|
@@ -38,13 +48,7 @@ module.exports = {
|
|
|
38
48
|
return;
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
const metaCharset = node.children
|
|
42
|
-
return (
|
|
43
|
-
child.type === "Tag" &&
|
|
44
|
-
child.name === "meta" &&
|
|
45
|
-
!!NodeUtils.findAttr(child, "charset")
|
|
46
|
-
);
|
|
47
|
-
});
|
|
51
|
+
const metaCharset = find(node.children, isMetaCharset);
|
|
48
52
|
|
|
49
53
|
if (!metaCharset) {
|
|
50
54
|
context.report({
|
|
@@ -53,7 +57,9 @@ module.exports = {
|
|
|
53
57
|
});
|
|
54
58
|
return;
|
|
55
59
|
}
|
|
56
|
-
|
|
60
|
+
|
|
61
|
+
const charsetAttr = findAttr(metaCharset, "charset");
|
|
62
|
+
|
|
57
63
|
if (charsetAttr) {
|
|
58
64
|
if (!charsetAttr.value || !charsetAttr.value.value.length) {
|
|
59
65
|
context.report({
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
* @typedef {import("es-html-parser").TagNode} TagNode
|
|
4
|
-
*/
|
|
5
|
-
|
|
1
|
+
const { NODE_TYPES } = require("@html-eslint/parser");
|
|
6
2
|
const { RULE_CATEGORY } = require("../constants");
|
|
7
|
-
const {
|
|
3
|
+
const { filter } = require("./utils/array");
|
|
4
|
+
const { findAttr } = require("./utils/node");
|
|
8
5
|
|
|
9
6
|
const MESSAGE_IDS = {
|
|
10
7
|
MISSING: "missing",
|
|
@@ -12,11 +9,11 @@ const MESSAGE_IDS = {
|
|
|
12
9
|
};
|
|
13
10
|
|
|
14
11
|
/**
|
|
15
|
-
* @param {TagNode
|
|
16
|
-
* @returns {
|
|
12
|
+
* @param {ChildType<TagNode>} node
|
|
13
|
+
* @returns {node is TagNode}
|
|
17
14
|
*/
|
|
18
15
|
function isMetaTagNode(node) {
|
|
19
|
-
return node.type ===
|
|
16
|
+
return node.type === NODE_TYPES.Tag && node.name === "meta";
|
|
20
17
|
}
|
|
21
18
|
|
|
22
19
|
/**
|
|
@@ -46,10 +43,10 @@ module.exports = {
|
|
|
46
43
|
if (node.name !== "head") {
|
|
47
44
|
return;
|
|
48
45
|
}
|
|
49
|
-
const metaTags = node.children
|
|
46
|
+
const metaTags = filter(node.children, isMetaTagNode);
|
|
50
47
|
|
|
51
48
|
const descriptionMetaTags = metaTags.filter((meta) => {
|
|
52
|
-
const nameAttr =
|
|
49
|
+
const nameAttr = findAttr(meta, "name");
|
|
53
50
|
return (
|
|
54
51
|
!!nameAttr &&
|
|
55
52
|
nameAttr.value &&
|
|
@@ -64,7 +61,7 @@ module.exports = {
|
|
|
64
61
|
});
|
|
65
62
|
} else {
|
|
66
63
|
descriptionMetaTags.forEach((meta) => {
|
|
67
|
-
const content =
|
|
64
|
+
const content = findAttr(meta, "content");
|
|
68
65
|
if (
|
|
69
66
|
!content ||
|
|
70
67
|
!content.value ||
|