@html-eslint/eslint-plugin 0.13.1 → 0.14.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/rules/element-newline.js +21 -56
- package/lib/rules/id-naming-convention.js +5 -2
- package/lib/rules/indent.js +171 -168
- package/lib/rules/no-abstract-roles.js +2 -2
- package/lib/rules/no-accesskey-attrs.js +1 -1
- package/lib/rules/no-aria-hidden-body.js +14 -6
- package/lib/rules/no-duplicate-attrs.js +7 -7
- package/lib/rules/no-duplicate-id.js +8 -5
- package/lib/rules/no-extra-spacing-attrs.js +21 -40
- package/lib/rules/no-inline-styles.js +4 -10
- package/lib/rules/no-multiple-h1.js +4 -2
- package/lib/rules/no-non-scalable-viewport.js +8 -3
- package/lib/rules/no-obsolete-tags.js +4 -15
- package/lib/rules/no-positive-tabindex.js +6 -2
- package/lib/rules/no-restricted-attrs.js +12 -18
- package/lib/rules/no-skip-heading-levels.js +5 -2
- package/lib/rules/no-target-blank.js +8 -5
- package/lib/rules/quotes.js +31 -68
- package/lib/rules/require-button-type.js +8 -5
- package/lib/rules/require-closing-tags.js +26 -41
- package/lib/rules/require-doctype.js +1 -1
- package/lib/rules/require-frame-title.js +7 -11
- package/lib/rules/require-img-alt.js +15 -17
- package/lib/rules/require-lang.js +13 -7
- package/lib/rules/require-li-container.js +7 -11
- package/lib/rules/require-meta-charset.js +23 -19
- package/lib/rules/require-meta-description.js +30 -16
- package/lib/rules/require-meta-viewport.js +36 -23
- package/lib/rules/require-title.js +38 -16
- package/lib/rules/utils/node-utils.js +58 -20
- package/lib/types.d.ts +193 -62
- package/package.json +4 -3
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
* @typedef {import("../types").AttrNode} AttrNode
|
|
4
|
-
* @typedef {import("../types").TagNode} TagNode
|
|
5
|
-
* @typedef {import("../types").ElementNode} ElementNode
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
const { RULE_CATEGORY } = require("../constants");
|
|
9
2
|
|
|
10
3
|
const MESSAGE_IDS = {
|
|
@@ -13,9 +6,6 @@ const MESSAGE_IDS = {
|
|
|
13
6
|
EXTRA_BEFORE: "unexpectedBefore",
|
|
14
7
|
};
|
|
15
8
|
|
|
16
|
-
/**
|
|
17
|
-
* @type {Rule}
|
|
18
|
-
*/
|
|
19
9
|
module.exports = {
|
|
20
10
|
meta: {
|
|
21
11
|
type: "code",
|
|
@@ -35,9 +25,6 @@ module.exports = {
|
|
|
35
25
|
},
|
|
36
26
|
},
|
|
37
27
|
create(context) {
|
|
38
|
-
/**
|
|
39
|
-
* @param {AttrNode[]} attrs
|
|
40
|
-
*/
|
|
41
28
|
function checkExtraSpacesBetweenAttrs(attrs) {
|
|
42
29
|
attrs.forEach((current, index, attrs) => {
|
|
43
30
|
if (index >= attrs.length - 1) {
|
|
@@ -63,17 +50,13 @@ module.exports = {
|
|
|
63
50
|
}
|
|
64
51
|
});
|
|
65
52
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
* @param {boolean} isSelfClosed
|
|
70
|
-
*/
|
|
71
|
-
function checkExtraSpaceAfter(startTag, lastAttr, isSelfClosed) {
|
|
72
|
-
if (startTag.loc.end.line !== lastAttr.loc.end.line) {
|
|
53
|
+
|
|
54
|
+
function checkExtraSpaceAfter(openEnd, lastAttr, isSelfClosed) {
|
|
55
|
+
if (openEnd.loc.end.line !== lastAttr.loc.end.line) {
|
|
73
56
|
// skip the attribute on the different line with the start tag
|
|
74
57
|
return;
|
|
75
58
|
}
|
|
76
|
-
let spacesBetween =
|
|
59
|
+
let spacesBetween = openEnd.loc.end.column - lastAttr.loc.end.column;
|
|
77
60
|
if (isSelfClosed) {
|
|
78
61
|
spacesBetween--;
|
|
79
62
|
}
|
|
@@ -82,7 +65,7 @@ module.exports = {
|
|
|
82
65
|
context.report({
|
|
83
66
|
loc: {
|
|
84
67
|
start: lastAttr.loc.end,
|
|
85
|
-
end:
|
|
68
|
+
end: openEnd.loc.end,
|
|
86
69
|
},
|
|
87
70
|
messageId: MESSAGE_IDS.EXTRA_AFTER,
|
|
88
71
|
fix(fixer) {
|
|
@@ -95,19 +78,14 @@ module.exports = {
|
|
|
95
78
|
}
|
|
96
79
|
}
|
|
97
80
|
|
|
98
|
-
/**
|
|
99
|
-
* @param {ElementNode} node
|
|
100
|
-
* @param {AttrNode} firstAttr
|
|
101
|
-
*/
|
|
102
81
|
function checkExtraSpaceBefore(node, firstAttr) {
|
|
103
82
|
if (node.loc.start.line !== firstAttr.loc.start.line) {
|
|
104
83
|
// skip the attribute on the different line with the start tag
|
|
105
84
|
return;
|
|
106
85
|
}
|
|
107
|
-
|
|
108
|
-
const spacesBetween =
|
|
109
|
-
|
|
110
|
-
if (spacesBetween > 2) {
|
|
86
|
+
|
|
87
|
+
const spacesBetween = firstAttr.loc.start.column - node.loc.end.column;
|
|
88
|
+
if (spacesBetween >= 2) {
|
|
111
89
|
context.report({
|
|
112
90
|
loc: {
|
|
113
91
|
start: node.loc.start,
|
|
@@ -116,7 +94,7 @@ module.exports = {
|
|
|
116
94
|
messageId: MESSAGE_IDS.EXTRA_BEFORE,
|
|
117
95
|
fix(fixer) {
|
|
118
96
|
return fixer.removeRange([
|
|
119
|
-
|
|
97
|
+
firstAttr.range[0] - spacesBetween + 1,
|
|
120
98
|
firstAttr.range[0],
|
|
121
99
|
]);
|
|
122
100
|
},
|
|
@@ -125,19 +103,22 @@ module.exports = {
|
|
|
125
103
|
}
|
|
126
104
|
|
|
127
105
|
return {
|
|
128
|
-
"
|
|
129
|
-
if (node.
|
|
130
|
-
|
|
106
|
+
[["Tag", "StyleTag", "ScriptTag"].join(",")](node) {
|
|
107
|
+
if (!node.attributes || node.attributes.length <= 0) {
|
|
108
|
+
return;
|
|
131
109
|
}
|
|
132
|
-
|
|
133
|
-
|
|
110
|
+
|
|
111
|
+
checkExtraSpaceBefore(node.openStart, node.attributes[0]);
|
|
112
|
+
|
|
113
|
+
if (node.openEnd && node.attributes && node.attributes.length > 0) {
|
|
114
|
+
const selfClosing = node.openEnd.value === "/>";
|
|
134
115
|
checkExtraSpaceAfter(
|
|
135
|
-
node.
|
|
136
|
-
node.
|
|
137
|
-
|
|
116
|
+
node.openEnd,
|
|
117
|
+
node.attributes[node.attributes.length - 1],
|
|
118
|
+
selfClosing
|
|
138
119
|
);
|
|
139
120
|
}
|
|
140
|
-
checkExtraSpacesBetweenAttrs(node.
|
|
121
|
+
checkExtraSpacesBetweenAttrs(node.attributes);
|
|
141
122
|
},
|
|
142
123
|
};
|
|
143
124
|
},
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
const { RULE_CATEGORY } = require("../constants");
|
|
6
2
|
const { NodeUtils } = require("./utils");
|
|
7
3
|
|
|
@@ -9,9 +5,6 @@ const MESSAGE_IDS = {
|
|
|
9
5
|
INLINE_STYLE: "unexpectedInlineStyle",
|
|
10
6
|
};
|
|
11
7
|
|
|
12
|
-
/**
|
|
13
|
-
* @type {Rule}
|
|
14
|
-
*/
|
|
15
8
|
module.exports = {
|
|
16
9
|
meta: {
|
|
17
10
|
type: "code",
|
|
@@ -31,10 +24,11 @@ module.exports = {
|
|
|
31
24
|
|
|
32
25
|
create(context) {
|
|
33
26
|
return {
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
Tag(node) {
|
|
28
|
+
const styleAttr = NodeUtils.findAttr(node, "style");
|
|
29
|
+
if (styleAttr) {
|
|
36
30
|
context.report({
|
|
37
|
-
node:
|
|
31
|
+
node: styleAttr,
|
|
38
32
|
messageId: MESSAGE_IDS.INLINE_STYLE,
|
|
39
33
|
});
|
|
40
34
|
}
|
|
@@ -32,14 +32,19 @@ module.exports = {
|
|
|
32
32
|
|
|
33
33
|
create(context) {
|
|
34
34
|
return {
|
|
35
|
-
|
|
35
|
+
Tag(node) {
|
|
36
|
+
if (node.name !== "meta") {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
36
39
|
const nameAttr = NodeUtils.findAttr(node, "name");
|
|
37
40
|
const contentAttr = NodeUtils.findAttr(node, "content");
|
|
38
41
|
if (
|
|
39
42
|
nameAttr &&
|
|
43
|
+
nameAttr.value &&
|
|
40
44
|
contentAttr &&
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
contentAttr.value &&
|
|
46
|
+
nameAttr.value.value.toLowerCase() === "viewport" &&
|
|
47
|
+
contentAttr.value.value.match(/user-scalable\s*=\s*no/i)
|
|
43
48
|
) {
|
|
44
49
|
context.report({
|
|
45
50
|
node: contentAttr,
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const { RULE_CATEGORY, NODE_TYPES } = require("../constants");
|
|
1
|
+
const { RULE_CATEGORY } = require("../constants");
|
|
6
2
|
const { OBSOLETE_TAGS } = require("../constants");
|
|
7
3
|
|
|
8
4
|
const OBSOLETE_TAGS_SET = new Set(OBSOLETE_TAGS);
|
|
@@ -11,9 +7,6 @@ const MESSAGE_IDS = {
|
|
|
11
7
|
UNEXPECTED: "unexpected",
|
|
12
8
|
};
|
|
13
9
|
|
|
14
|
-
/**
|
|
15
|
-
* @type {Rule}
|
|
16
|
-
*/
|
|
17
10
|
module.exports = {
|
|
18
11
|
meta: {
|
|
19
12
|
type: "code",
|
|
@@ -33,16 +26,12 @@ module.exports = {
|
|
|
33
26
|
|
|
34
27
|
create(context) {
|
|
35
28
|
return {
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
node.type !== NODE_TYPES.PROGRAM &&
|
|
39
|
-
node.tagName &&
|
|
40
|
-
OBSOLETE_TAGS_SET.has(node.tagName)
|
|
41
|
-
) {
|
|
29
|
+
Tag(node) {
|
|
30
|
+
if (OBSOLETE_TAGS_SET.has(node.name)) {
|
|
42
31
|
context.report({
|
|
43
32
|
node,
|
|
44
33
|
data: {
|
|
45
|
-
tag: node.
|
|
34
|
+
tag: node.name,
|
|
46
35
|
},
|
|
47
36
|
messageId: MESSAGE_IDS.UNEXPECTED,
|
|
48
37
|
});
|
|
@@ -31,9 +31,13 @@ module.exports = {
|
|
|
31
31
|
|
|
32
32
|
create(context) {
|
|
33
33
|
return {
|
|
34
|
-
"
|
|
34
|
+
[["Tag", "StyleTag", "ScriptTag"].join(",")](node) {
|
|
35
35
|
const tabIndexAttr = NodeUtils.findAttr(node, "tabindex");
|
|
36
|
-
if (
|
|
36
|
+
if (
|
|
37
|
+
tabIndexAttr &&
|
|
38
|
+
tabIndexAttr.value &&
|
|
39
|
+
parseInt(tabIndexAttr.value.value, 10) > 0
|
|
40
|
+
) {
|
|
37
41
|
context.report({
|
|
38
42
|
node: tabIndexAttr,
|
|
39
43
|
messageId: MESSAGE_IDS.UNEXPECTED,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
* @typedef {import("../types").AnyNode} AnyNode
|
|
4
3
|
* @typedef {{tagPatterns: string[], attrPatterns: string[], message?: string}[]} Options
|
|
5
4
|
*/
|
|
6
5
|
|
|
@@ -62,27 +61,22 @@ module.exports = {
|
|
|
62
61
|
const checkers = options.map((option) => new PatternChecker(option));
|
|
63
62
|
|
|
64
63
|
return {
|
|
65
|
-
"
|
|
66
|
-
const tagName = node.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
node.attrs.forEach((attr) => {
|
|
72
|
-
if (!attr.name) return;
|
|
73
|
-
|
|
64
|
+
[["Tag", "StyleTag", "ScriptTag"].join(",")](node) {
|
|
65
|
+
const tagName = node.name;
|
|
66
|
+
node.attributes.forEach((attr) => {
|
|
67
|
+
if (!attr.key || !attr.key.value) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
74
70
|
const matched = checkers.find((checker) =>
|
|
75
|
-
checker.test(
|
|
71
|
+
checker.test(tagName, attr.key.value)
|
|
76
72
|
);
|
|
77
73
|
|
|
78
|
-
if (!matched)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
* @type {{node: AnyNode, message: string, messageId?: string}}
|
|
82
|
-
*/
|
|
74
|
+
if (!matched) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
83
77
|
|
|
84
78
|
const result = {
|
|
85
|
-
node:
|
|
79
|
+
node: attr,
|
|
86
80
|
message: "",
|
|
87
81
|
};
|
|
88
82
|
|
|
@@ -96,7 +90,7 @@ module.exports = {
|
|
|
96
90
|
|
|
97
91
|
context.report({
|
|
98
92
|
...result,
|
|
99
|
-
data: { attr: attr.
|
|
93
|
+
data: { attr: attr.key.value },
|
|
100
94
|
});
|
|
101
95
|
});
|
|
102
96
|
},
|
|
@@ -33,10 +33,13 @@ module.exports = {
|
|
|
33
33
|
const headings = [];
|
|
34
34
|
|
|
35
35
|
return {
|
|
36
|
-
|
|
36
|
+
Tag(node) {
|
|
37
|
+
if (!["h1", "h2", "h3", "h5", "h6"].includes(node.name)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
37
40
|
headings.push({
|
|
38
41
|
node,
|
|
39
|
-
level: parseInt(node.
|
|
42
|
+
level: parseInt(node.name.replace("h", ""), 10),
|
|
40
43
|
});
|
|
41
44
|
},
|
|
42
45
|
"Program:exit"() {
|
|
@@ -39,16 +39,19 @@ module.exports = {
|
|
|
39
39
|
return /^(?:\w+:|\/\/)/.test(link);
|
|
40
40
|
}
|
|
41
41
|
return {
|
|
42
|
-
|
|
42
|
+
Tag(node) {
|
|
43
|
+
if (node.name !== "a") {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
43
46
|
/* eslint-disable */
|
|
44
47
|
const target = NodeUtils.findAttr(node, "target");
|
|
45
|
-
if (target && target.value === "_blank") {
|
|
48
|
+
if (target && target.value && target.value.value === "_blank") {
|
|
46
49
|
const href = NodeUtils.findAttr(node, "href");
|
|
47
|
-
if (href && isExternalLink(href.value)) {
|
|
50
|
+
if (href && href.value && isExternalLink(href.value.value)) {
|
|
48
51
|
const rel = NodeUtils.findAttr(node, "rel");
|
|
49
|
-
if (!rel || !rel.value.includes("noreferrer")) {
|
|
52
|
+
if (!rel || !rel.value || !rel.value.value.includes("noreferrer")) {
|
|
50
53
|
context.report({
|
|
51
|
-
node,
|
|
54
|
+
node: target,
|
|
52
55
|
messageId: MESSAGE_IDS.MISSING,
|
|
53
56
|
});
|
|
54
57
|
}
|
package/lib/rules/quotes.js
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
* @typedef {import("../types").Range} Range
|
|
4
|
-
* @typedef {import("../types").AttrNode} AttrNode
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
const { RULE_CATEGORY } = require("../constants");
|
|
8
2
|
|
|
9
3
|
const MESSAGE_IDS = {
|
|
@@ -18,9 +12,6 @@ const QUOTES_STYLES = {
|
|
|
18
12
|
|
|
19
13
|
const QUOTES_CODES = [`"`, `'`];
|
|
20
14
|
|
|
21
|
-
/**
|
|
22
|
-
* @type {Rule}
|
|
23
|
-
*/
|
|
24
15
|
module.exports = {
|
|
25
16
|
meta: {
|
|
26
17
|
type: "code",
|
|
@@ -62,66 +53,39 @@ module.exports = {
|
|
|
62
53
|
return sourceCode.text.slice(range[0], range[1]);
|
|
63
54
|
}
|
|
64
55
|
|
|
65
|
-
/**
|
|
66
|
-
* @param {AttrNode} attr
|
|
67
|
-
* @returns {Range}
|
|
68
|
-
*/
|
|
69
|
-
function getValueRange(attr) {
|
|
70
|
-
const attrCode = getCodeIn(attr.range);
|
|
71
|
-
const [matched = ""] = attrCode.match(/\S*?\s*=\s*/) || [];
|
|
72
|
-
return [attr.range[0] + matched.length, attr.range[1]];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* @param {AttrNode} attr
|
|
77
|
-
* @returns {[string, string]}
|
|
78
|
-
*/
|
|
79
56
|
function getQuotes(attr) {
|
|
80
|
-
|
|
81
|
-
const opening = getCodeIn([valueStart, valueStart + 1]);
|
|
82
|
-
const closing = getCodeIn([valueEnd - 1, valueEnd]);
|
|
83
|
-
return [opening, closing];
|
|
57
|
+
return [attr.startWrapper.value, attr.endWrapper.value];
|
|
84
58
|
}
|
|
85
59
|
|
|
86
|
-
/**
|
|
87
|
-
* @param {AttrNode} attr
|
|
88
|
-
* @returns {boolean}
|
|
89
|
-
*/
|
|
90
|
-
function hasEqualSign(attr) {
|
|
91
|
-
const keyEnd = attr.range[0] + attr.name.length;
|
|
92
|
-
return getCodeIn([keyEnd, attr.range[1]]).trimStart().startsWith("=");
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* @param {AttrNode} attr
|
|
97
|
-
*/
|
|
98
60
|
function checkQuotes(attr) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
context.report({
|
|
103
|
-
node: attr,
|
|
104
|
-
messageId: MESSAGE_IDS.UNEXPECTED,
|
|
105
|
-
data: {
|
|
106
|
-
expected: `${SELECTED_STYLE}(${expectedQuote})`,
|
|
107
|
-
actual:
|
|
108
|
-
SELECTED_STYLE === QUOTES_STYLES.SINGLE
|
|
109
|
-
? `${QUOTES_STYLES.DOUBLE}(")`
|
|
110
|
-
: `${QUOTES_STYLES.SINGLE}(')`,
|
|
111
|
-
},
|
|
112
|
-
fix(fixer) {
|
|
113
|
-
const range = getValueRange(attr);
|
|
114
|
-
const originCode = getCodeIn(range);
|
|
115
|
-
const onlyValue = originCode.slice(1, originCode.length - 1);
|
|
61
|
+
if (!attr.value || attr.value.value.includes(expectedQuote)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
116
64
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
65
|
+
if (attr.startWrapper && attr.endWrapper) {
|
|
66
|
+
const [opening, closing] = getQuotes(attr);
|
|
67
|
+
if (QUOTES_CODES.includes(opening)) {
|
|
68
|
+
if (opening === closing && opening !== expectedQuote) {
|
|
69
|
+
context.report({
|
|
70
|
+
node: attr,
|
|
71
|
+
messageId: MESSAGE_IDS.UNEXPECTED,
|
|
72
|
+
data: {
|
|
73
|
+
expected: `${SELECTED_STYLE}(${expectedQuote})`,
|
|
74
|
+
actual:
|
|
75
|
+
SELECTED_STYLE === QUOTES_STYLES.SINGLE
|
|
76
|
+
? `${QUOTES_STYLES.DOUBLE}(")`
|
|
77
|
+
: `${QUOTES_STYLES.SINGLE}(')`,
|
|
78
|
+
},
|
|
79
|
+
fix(fixer) {
|
|
80
|
+
return fixer.replaceTextRange(
|
|
81
|
+
[attr.startWrapper.range[0], attr.endWrapper.range[1]],
|
|
82
|
+
`${expectedQuote}${attr.value.value}${expectedQuote}`
|
|
83
|
+
);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
123
87
|
}
|
|
124
|
-
} else
|
|
88
|
+
} else {
|
|
125
89
|
context.report({
|
|
126
90
|
node: attr,
|
|
127
91
|
messageId: MESSAGE_IDS.MISSING,
|
|
@@ -129,10 +93,9 @@ module.exports = {
|
|
|
129
93
|
expected: `${SELECTED_STYLE}(${expectedQuote})`,
|
|
130
94
|
},
|
|
131
95
|
fix(fixer) {
|
|
132
|
-
const
|
|
133
|
-
const originCode = getCodeIn(range);
|
|
96
|
+
const originCode = getCodeIn(attr.value.range);
|
|
134
97
|
return fixer.replaceTextRange(
|
|
135
|
-
range,
|
|
98
|
+
attr.value.range,
|
|
136
99
|
`${expectedQuote}${originCode}${expectedQuote}`
|
|
137
100
|
);
|
|
138
101
|
},
|
|
@@ -141,8 +104,8 @@ module.exports = {
|
|
|
141
104
|
}
|
|
142
105
|
|
|
143
106
|
return {
|
|
144
|
-
"
|
|
145
|
-
|
|
107
|
+
[["Tag", "ScriptTag", "StyleTag"].join(",")](node) {
|
|
108
|
+
node.attributes.forEach(checkQuotes);
|
|
146
109
|
},
|
|
147
110
|
};
|
|
148
111
|
},
|
|
@@ -36,19 +36,22 @@ module.exports = {
|
|
|
36
36
|
|
|
37
37
|
create(context) {
|
|
38
38
|
return {
|
|
39
|
-
|
|
39
|
+
Tag(node) {
|
|
40
|
+
if (node.name !== "button") {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
40
43
|
const typeAttr = NodeUtils.findAttr(node, "type");
|
|
41
|
-
if (!typeAttr) {
|
|
44
|
+
if (!typeAttr || !typeAttr.value) {
|
|
42
45
|
context.report({
|
|
43
|
-
node: node.
|
|
46
|
+
node: node.openStart,
|
|
44
47
|
messageId: MESSAGE_IDS.MISSING,
|
|
45
48
|
});
|
|
46
|
-
} else if (!VALID_BUTTON_TYPES_SET.has(typeAttr.value)) {
|
|
49
|
+
} else if (!VALID_BUTTON_TYPES_SET.has(typeAttr.value.value)) {
|
|
47
50
|
context.report({
|
|
48
51
|
node: typeAttr,
|
|
49
52
|
messageId: MESSAGE_IDS.INVALID,
|
|
50
53
|
data: {
|
|
51
|
-
type: typeAttr.value,
|
|
54
|
+
type: typeAttr.value.value,
|
|
52
55
|
},
|
|
53
56
|
});
|
|
54
57
|
}
|
|
@@ -41,36 +41,24 @@ module.exports = {
|
|
|
41
41
|
[MESSAGE_IDS.MISSING]: "Missing closing tag for {{tag}}.",
|
|
42
42
|
[MESSAGE_IDS.MISSING_SELF]: "Missing self closing tag for {{tag}}",
|
|
43
43
|
[MESSAGE_IDS.UNEXPECTED]: "Unexpected self closing tag for {{tag}}.",
|
|
44
|
+
[MESSAGE_IDS.HUCKS]: "HUCKS.",
|
|
44
45
|
},
|
|
45
46
|
},
|
|
46
47
|
|
|
47
48
|
create(context) {
|
|
49
|
+
let svgStacks = [];
|
|
50
|
+
|
|
48
51
|
const shouldSelfClose =
|
|
49
52
|
context.options && context.options.length
|
|
50
53
|
? context.options[0].selfClosing === "always"
|
|
51
54
|
: false;
|
|
52
55
|
|
|
53
|
-
const sourceCode = context.getSourceCode();
|
|
54
|
-
function getCodeIn(range) {
|
|
55
|
-
return sourceCode.text.slice(range[0], range[1]);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
56
|
function checkClosingTag(node) {
|
|
59
|
-
if (
|
|
60
|
-
if (
|
|
61
|
-
node.namespaceURI === "http://www.w3.org/2000/svg" ||
|
|
62
|
-
node.namespaceURI === "http://www.w3.org/1998/Math/MathML"
|
|
63
|
-
) {
|
|
64
|
-
const code = getCodeIn(node.startTag.range);
|
|
65
|
-
const hasSelfClose = code.endsWith("/>");
|
|
66
|
-
if (hasSelfClose) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
57
|
+
if (!node.close) {
|
|
70
58
|
context.report({
|
|
71
|
-
node: node
|
|
59
|
+
node: node,
|
|
72
60
|
data: {
|
|
73
|
-
tag: node.
|
|
61
|
+
tag: node.name,
|
|
74
62
|
},
|
|
75
63
|
messageId: MESSAGE_IDS.MISSING,
|
|
76
64
|
});
|
|
@@ -78,50 +66,47 @@ module.exports = {
|
|
|
78
66
|
}
|
|
79
67
|
|
|
80
68
|
function checkVoidElement(node) {
|
|
81
|
-
const
|
|
82
|
-
const code = getCodeIn(startTag.range);
|
|
83
|
-
const hasSelfClose = code.endsWith("/>");
|
|
84
|
-
|
|
69
|
+
const hasSelfClose = node.openEnd.value === "/>";
|
|
85
70
|
if (shouldSelfClose && !hasSelfClose) {
|
|
86
71
|
context.report({
|
|
87
|
-
node:
|
|
72
|
+
node: node.openEnd,
|
|
88
73
|
data: {
|
|
89
|
-
tag: node.
|
|
74
|
+
tag: node.name,
|
|
90
75
|
},
|
|
91
76
|
messageId: MESSAGE_IDS.MISSING_SELF,
|
|
92
77
|
fix(fixer) {
|
|
93
|
-
return fixer.
|
|
94
|
-
[startTag.range[1] - 1, startTag.range[1]],
|
|
95
|
-
" />"
|
|
96
|
-
);
|
|
78
|
+
return fixer.replaceText(node.openEnd, " />");
|
|
97
79
|
},
|
|
98
80
|
});
|
|
99
81
|
}
|
|
100
82
|
if (!shouldSelfClose && hasSelfClose) {
|
|
101
83
|
context.report({
|
|
102
|
-
node:
|
|
84
|
+
node: node.openEnd,
|
|
103
85
|
data: {
|
|
104
|
-
tag: node.
|
|
86
|
+
tag: node.name,
|
|
105
87
|
},
|
|
106
88
|
messageId: MESSAGE_IDS.UNEXPECTED,
|
|
107
89
|
fix(fixer) {
|
|
108
|
-
return fixer.
|
|
109
|
-
[startTag.range[1] - 2, startTag.range[1]],
|
|
110
|
-
">"
|
|
111
|
-
);
|
|
90
|
+
return fixer.replaceText(node.openEnd, ">");
|
|
112
91
|
},
|
|
113
92
|
});
|
|
114
93
|
}
|
|
115
94
|
}
|
|
116
95
|
|
|
117
96
|
return {
|
|
118
|
-
|
|
119
|
-
if (node.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
97
|
+
Tag(node) {
|
|
98
|
+
if (node.name === "svg") {
|
|
99
|
+
svgStacks.push(node);
|
|
100
|
+
}
|
|
101
|
+
if (node.selfClosing || VOID_ELEMENTS_SET.has(node.name)) {
|
|
102
|
+
checkVoidElement(node);
|
|
103
|
+
} else if (node.openEnd.value !== "/>") {
|
|
104
|
+
checkClosingTag(node);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"Tag:exit"(node) {
|
|
108
|
+
if (node.name === "svg") {
|
|
109
|
+
svgStacks.push(node);
|
|
125
110
|
}
|
|
126
111
|
},
|
|
127
112
|
};
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
const { RULE_CATEGORY } = require("../constants");
|
|
6
2
|
const { NodeUtils } = require("./utils");
|
|
7
3
|
|
|
@@ -10,9 +6,6 @@ const MESSAGE_IDS = {
|
|
|
10
6
|
UNEXPECTED: "unexpected",
|
|
11
7
|
};
|
|
12
8
|
|
|
13
|
-
/**
|
|
14
|
-
* @type {Rule}
|
|
15
|
-
*/
|
|
16
9
|
module.exports = {
|
|
17
10
|
meta: {
|
|
18
11
|
type: "code",
|
|
@@ -33,15 +26,18 @@ module.exports = {
|
|
|
33
26
|
|
|
34
27
|
create(context) {
|
|
35
28
|
return {
|
|
36
|
-
|
|
29
|
+
Tag(node) {
|
|
30
|
+
if (node.name !== "frame" && node.name !== "iframe") {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
37
33
|
const title = NodeUtils.findAttr(node, "title");
|
|
38
34
|
if (!title) {
|
|
39
35
|
context.report({
|
|
40
|
-
node: node.
|
|
41
|
-
data: { frame: `<${node.
|
|
36
|
+
node: node.openStart,
|
|
37
|
+
data: { frame: `<${node.name}>` },
|
|
42
38
|
messageId: MESSAGE_IDS.MISSING,
|
|
43
39
|
});
|
|
44
|
-
} else if (title.value.trim().length === 0) {
|
|
40
|
+
} else if (!title.value || title.value.value.trim().length === 0) {
|
|
45
41
|
context.report({
|
|
46
42
|
node: title,
|
|
47
43
|
messageId: MESSAGE_IDS.UNEXPECTED,
|