@html-eslint/eslint-plugin 0.10.1 → 0.11.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/node-types.js +0 -5
- package/lib/constants/obsolete-tags.js +1 -1
- package/lib/constants/rule-category.js +0 -5
- package/lib/constants/void-elements.js +1 -1
- package/lib/rules/element-newline.js +29 -9
- package/lib/rules/id-naming-convention.js +14 -7
- package/lib/rules/indent.js +19 -7
- package/lib/rules/index.js +4 -0
- package/lib/rules/no-abstract-roles.js +7 -0
- package/lib/rules/no-accesskey-attrs.js +45 -0
- package/lib/rules/no-aria-hidden-body.js +7 -0
- package/lib/rules/no-duplicate-attrs.js +7 -0
- package/lib/rules/no-duplicate-id.js +7 -0
- package/lib/rules/no-extra-spacing-attrs.js +24 -2
- package/lib/rules/no-inline-styles.js +7 -0
- package/lib/rules/no-multiple-empty-lines.js +90 -0
- package/lib/rules/no-multiple-h1.js +7 -0
- package/lib/rules/no-non-scalable-viewport.js +7 -0
- package/lib/rules/no-obsolete-tags.js +7 -0
- package/lib/rules/no-positive-tabindex.js +7 -0
- package/lib/rules/no-skip-heading-levels.js +7 -0
- package/lib/rules/no-target-blank.js +7 -0
- package/lib/rules/quotes.js +28 -0
- package/lib/rules/require-button-type.js +7 -0
- package/lib/rules/require-closing-tags.js +7 -0
- package/lib/rules/require-doctype.js +7 -0
- package/lib/rules/require-frame-title.js +7 -0
- package/lib/rules/require-img-alt.js +13 -0
- package/lib/rules/require-lang.js +7 -1
- package/lib/rules/require-li-container.js +13 -1
- package/lib/rules/require-meta-charset.js +12 -1
- package/lib/rules/require-meta-description.js +11 -0
- package/lib/rules/require-meta-viewport.js +12 -8
- package/lib/rules/require-title.js +9 -3
- package/lib/rules/utils/node-utils.js +27 -5
- package/lib/types.d.ts +104 -33
- package/package.json +8 -4
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import("../types").
|
|
2
|
+
* @typedef {import("../types").ElementNode} ElementNode
|
|
3
|
+
* @typedef {import("../types").AnyNode} AnyNode
|
|
4
|
+
* @typedef {import("../types").Context} Context
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
const { RULE_CATEGORY, NODE_TYPES } = require("../constants");
|
|
@@ -29,9 +31,12 @@ module.exports = {
|
|
|
29
31
|
},
|
|
30
32
|
},
|
|
31
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @param {Context} context
|
|
36
|
+
*/
|
|
32
37
|
create(context) {
|
|
33
|
-
function checkSiblings(
|
|
34
|
-
|
|
38
|
+
function checkSiblings(siblings) {
|
|
39
|
+
siblings
|
|
35
40
|
.filter((node) => node.type !== NODE_TYPES.TEXT && node.range[0])
|
|
36
41
|
.forEach((current, index, arr) => {
|
|
37
42
|
const after = arr[index + 1];
|
|
@@ -50,6 +55,10 @@ module.exports = {
|
|
|
50
55
|
});
|
|
51
56
|
}
|
|
52
57
|
|
|
58
|
+
/**
|
|
59
|
+
*
|
|
60
|
+
* @param {ElementNode['childNodes'][number]} node
|
|
61
|
+
*/
|
|
53
62
|
function checkChild(node) {
|
|
54
63
|
const children = (node.childNodes || []).filter(
|
|
55
64
|
(n) => !!n.range[0] && n.type !== NODE_TYPES.TEXT
|
|
@@ -57,31 +66,40 @@ module.exports = {
|
|
|
57
66
|
const first = children[0];
|
|
58
67
|
const last = children[children.length - 1];
|
|
59
68
|
if (first) {
|
|
60
|
-
if (isOnTheSameLine(node.startTag, first)) {
|
|
69
|
+
if (node.startTag && isOnTheSameLine(node.startTag, first)) {
|
|
61
70
|
context.report({
|
|
62
71
|
node: node.startTag,
|
|
63
72
|
messageId: MESSAGE_IDS.EXPECT_NEW_LINE_AFTER,
|
|
64
73
|
data: { tag: `<${node.tagName}>` },
|
|
65
74
|
fix(fixer) {
|
|
66
|
-
|
|
75
|
+
if (node.startTag) {
|
|
76
|
+
return fixer.insertTextAfter(node.startTag, "\n");
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
67
79
|
},
|
|
68
80
|
});
|
|
69
81
|
}
|
|
70
82
|
}
|
|
71
83
|
if (last) {
|
|
72
|
-
if (isOnTheSameLine(node.endTag, last)) {
|
|
84
|
+
if (node.endTag && isOnTheSameLine(node.endTag, last)) {
|
|
73
85
|
context.report({
|
|
74
86
|
node: node.endTag,
|
|
75
87
|
messageId: MESSAGE_IDS.EXPECT_NEW_LINE_BEFORE,
|
|
76
88
|
data: { tag: `</${node.tagName}>` },
|
|
77
89
|
fix(fixer) {
|
|
78
|
-
|
|
90
|
+
if (node.endTag) {
|
|
91
|
+
return fixer.insertTextBefore(node.endTag, "\n");
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
79
94
|
},
|
|
80
95
|
});
|
|
81
96
|
}
|
|
82
97
|
}
|
|
83
98
|
}
|
|
84
99
|
return {
|
|
100
|
+
/**
|
|
101
|
+
* @param {ElementNode} node
|
|
102
|
+
*/
|
|
85
103
|
"*"(node) {
|
|
86
104
|
if (node.type !== NODE_TYPES.TEXT) {
|
|
87
105
|
checkSiblings(node.childNodes || []);
|
|
@@ -94,13 +112,15 @@ module.exports = {
|
|
|
94
112
|
|
|
95
113
|
/**
|
|
96
114
|
* Checks whether two nodes are on the same line or not.
|
|
97
|
-
* @param {
|
|
98
|
-
* @param {
|
|
115
|
+
* @param {AnyNode} nodeBefore A node before
|
|
116
|
+
* @param {AnyNode} nodeAfter A node after
|
|
99
117
|
* @returns {boolean} `true` if two nodes are on the same line, otherwise `false`.
|
|
100
118
|
*/
|
|
101
119
|
function isOnTheSameLine(nodeBefore, nodeAfter) {
|
|
102
120
|
if (nodeBefore && nodeAfter) {
|
|
121
|
+
// @ts-ignore
|
|
103
122
|
if (nodeBefore.endTag) {
|
|
123
|
+
// @ts-ignore
|
|
104
124
|
return nodeBefore.endTag.loc.end.line === nodeAfter.loc.start.line;
|
|
105
125
|
}
|
|
106
126
|
return nodeBefore.loc.start.line === nodeAfter.loc.start.line;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
6
|
const { NamingUtils, NodeUtils } = require("./utils");
|
|
3
7
|
|
|
@@ -5,7 +9,7 @@ const MESSAGE_IDS = {
|
|
|
5
9
|
WRONG: "wrong",
|
|
6
10
|
};
|
|
7
11
|
|
|
8
|
-
const
|
|
12
|
+
const CONVENTIONS = {
|
|
9
13
|
CAMEL_CASE: "camelCase",
|
|
10
14
|
SNAKE_CASE: "snake_case",
|
|
11
15
|
PASCAL_CASE: "PascalCase",
|
|
@@ -13,12 +17,15 @@ const CONEVNTIONS = {
|
|
|
13
17
|
};
|
|
14
18
|
|
|
15
19
|
const CONVENTION_CHECKERS = {
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
19
|
-
[
|
|
20
|
+
[CONVENTIONS.CAMEL_CASE]: NamingUtils.isCamelCase,
|
|
21
|
+
[CONVENTIONS.SNAKE_CASE]: NamingUtils.isSnakeCase,
|
|
22
|
+
[CONVENTIONS.PASCAL_CASE]: NamingUtils.isPascalCase,
|
|
23
|
+
[CONVENTIONS.KEBAB_CASE]: NamingUtils.isKebabCase,
|
|
20
24
|
};
|
|
21
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @type {Rule}
|
|
28
|
+
*/
|
|
22
29
|
module.exports = {
|
|
23
30
|
meta: {
|
|
24
31
|
type: "code",
|
|
@@ -32,7 +39,7 @@ module.exports = {
|
|
|
32
39
|
fixable: null,
|
|
33
40
|
schema: [
|
|
34
41
|
{
|
|
35
|
-
enum: Object.values(
|
|
42
|
+
enum: Object.values(CONVENTIONS),
|
|
36
43
|
},
|
|
37
44
|
],
|
|
38
45
|
messages: {
|
|
@@ -45,7 +52,7 @@ module.exports = {
|
|
|
45
52
|
const convention =
|
|
46
53
|
context.options && context.options.length
|
|
47
54
|
? context.options[0]
|
|
48
|
-
:
|
|
55
|
+
: CONVENTIONS.SNAKE_CASE;
|
|
49
56
|
|
|
50
57
|
const checkNaming = CONVENTION_CHECKERS[convention];
|
|
51
58
|
|
package/lib/rules/indent.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
// @ts-check
|
|
2
1
|
/**
|
|
3
|
-
* @typedef {import("../types").
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
* @typedef {import("../types").ElementNode} ElementNode
|
|
4
4
|
* @typedef {import("../types").AttrNode} AttrNode
|
|
5
|
+
* @typedef {import("../types").TagNode} TagNode
|
|
6
|
+
* @typedef {import("../types").AnyNode} AnyNode
|
|
5
7
|
* @typedef {import("../types").BaseNode} BaseNode
|
|
6
8
|
* @typedef {Object} IndentType
|
|
7
9
|
* @property {"tab"} TAB
|
|
@@ -12,6 +14,7 @@
|
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
16
|
const { RULE_CATEGORY, NODE_TYPES } = require("../constants");
|
|
17
|
+
const { NodeUtils } = require("./utils");
|
|
15
18
|
|
|
16
19
|
/** @type {MessageId} */
|
|
17
20
|
const MESSAGE_ID = {
|
|
@@ -31,6 +34,9 @@ const IGNORING_NODES = [
|
|
|
31
34
|
NODE_TYPES.XMP,
|
|
32
35
|
];
|
|
33
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @type {Rule}
|
|
39
|
+
*/
|
|
34
40
|
module.exports = {
|
|
35
41
|
meta: {
|
|
36
42
|
type: "code",
|
|
@@ -60,7 +66,6 @@ module.exports = {
|
|
|
60
66
|
"Expected indentation of {{expected}} but found {{actual}}.",
|
|
61
67
|
},
|
|
62
68
|
},
|
|
63
|
-
|
|
64
69
|
create(context) {
|
|
65
70
|
const sourceCode = context.getSourceCode();
|
|
66
71
|
const indentLevel = new IndentLevel();
|
|
@@ -79,7 +84,7 @@ module.exports = {
|
|
|
79
84
|
|
|
80
85
|
/**
|
|
81
86
|
* @param {BaseNode} node
|
|
82
|
-
* @param {
|
|
87
|
+
* @param {BaseNode} [nodeToReport]
|
|
83
88
|
*/
|
|
84
89
|
function checkIndent(node, nodeToReport) {
|
|
85
90
|
const codeBefore = getLineCodeBefore(node);
|
|
@@ -133,7 +138,7 @@ module.exports = {
|
|
|
133
138
|
}
|
|
134
139
|
|
|
135
140
|
/**
|
|
136
|
-
* @param {
|
|
141
|
+
* @param {AnyNode} startTag
|
|
137
142
|
*/
|
|
138
143
|
function checkEndOfStartTag(startTag) {
|
|
139
144
|
const start = startTag.range[1] - 1;
|
|
@@ -160,7 +165,7 @@ module.exports = {
|
|
|
160
165
|
let nodesToIgnoreChildren = [];
|
|
161
166
|
return {
|
|
162
167
|
/**
|
|
163
|
-
* @param {
|
|
168
|
+
* @param {ElementNode} node
|
|
164
169
|
*/
|
|
165
170
|
"*"(node) {
|
|
166
171
|
if (IGNORING_NODES.includes(node.type)) {
|
|
@@ -186,7 +191,11 @@ module.exports = {
|
|
|
186
191
|
}
|
|
187
192
|
});
|
|
188
193
|
|
|
189
|
-
if (
|
|
194
|
+
if (
|
|
195
|
+
(NodeUtils.isTextNode(node) || NodeUtils.isCommentNode(node)) &&
|
|
196
|
+
node.lineNodes &&
|
|
197
|
+
node.lineNodes.length
|
|
198
|
+
) {
|
|
190
199
|
if (!node.startTag) {
|
|
191
200
|
indentLevel.down();
|
|
192
201
|
}
|
|
@@ -219,6 +228,9 @@ module.exports = {
|
|
|
219
228
|
};
|
|
220
229
|
|
|
221
230
|
function getIndentTypeAndSize(options) {
|
|
231
|
+
/**
|
|
232
|
+
* @type {IndentType['SPACE'] | IndentType['TAB']}
|
|
233
|
+
*/
|
|
222
234
|
let indentType = INDENT_TYPES.SPACE;
|
|
223
235
|
let indentSize = 4;
|
|
224
236
|
if (options.length) {
|
package/lib/rules/index.js
CHANGED
|
@@ -25,6 +25,8 @@ const noDuplicateAttrs = require("./no-duplicate-attrs");
|
|
|
25
25
|
const noAbstractRoles = require("./no-abstract-roles");
|
|
26
26
|
const requireButtonType = require("./require-button-type");
|
|
27
27
|
const noAriaHiddenBody = require("./no-aria-hidden-body");
|
|
28
|
+
const noMultipleEmptyLines = require("./no-multiple-empty-lines");
|
|
29
|
+
const noAccesskeyAttrs = require("./no-accesskey-attrs");
|
|
28
30
|
|
|
29
31
|
module.exports = {
|
|
30
32
|
"require-lang": requireLang,
|
|
@@ -54,4 +56,6 @@ module.exports = {
|
|
|
54
56
|
"no-abstract-roles": noAbstractRoles,
|
|
55
57
|
"require-button-type": requireButtonType,
|
|
56
58
|
"no-aria-hidden-body": noAriaHiddenBody,
|
|
59
|
+
"no-multiple-empty-lines": noMultipleEmptyLines,
|
|
60
|
+
"no-accesskey-attrs": noAccesskeyAttrs,
|
|
57
61
|
};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
6
|
const { NodeUtils } = require("./utils");
|
|
3
7
|
|
|
@@ -20,6 +24,9 @@ const ABSTRACT_ROLE_SET = new Set([
|
|
|
20
24
|
"window",
|
|
21
25
|
]);
|
|
22
26
|
|
|
27
|
+
/**
|
|
28
|
+
* @type {Rule}
|
|
29
|
+
*/
|
|
23
30
|
module.exports = {
|
|
24
31
|
meta: {
|
|
25
32
|
type: "code",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { RULE_CATEGORY } = require("../constants");
|
|
6
|
+
const { NodeUtils } = require("./utils");
|
|
7
|
+
|
|
8
|
+
const MESSAGE_IDS = {
|
|
9
|
+
UNEXPECTED: "unexpected",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @type {Rule}
|
|
14
|
+
*/
|
|
15
|
+
module.exports = {
|
|
16
|
+
meta: {
|
|
17
|
+
type: "code",
|
|
18
|
+
|
|
19
|
+
docs: {
|
|
20
|
+
description: "Disallow to use of accesskey attribute",
|
|
21
|
+
category: RULE_CATEGORY.ACCESSIBILITY,
|
|
22
|
+
recommended: false,
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
fixable: null,
|
|
26
|
+
schema: [],
|
|
27
|
+
messages: {
|
|
28
|
+
[MESSAGE_IDS.UNEXPECTED]: "Unexpected use of accesskey attribute.",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
create(context) {
|
|
33
|
+
return {
|
|
34
|
+
"*"(node) {
|
|
35
|
+
const accessKeyAttr = NodeUtils.findAttr(node, "accesskey");
|
|
36
|
+
if (accessKeyAttr) {
|
|
37
|
+
context.report({
|
|
38
|
+
node: accessKeyAttr,
|
|
39
|
+
messageId: MESSAGE_IDS.UNEXPECTED,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
6
|
const { NodeUtils } = require("./utils");
|
|
3
7
|
|
|
@@ -5,6 +9,9 @@ const MESSAGE_IDS = {
|
|
|
5
9
|
UNEXPECTED: "unexpected",
|
|
6
10
|
};
|
|
7
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @type {Rule}
|
|
14
|
+
*/
|
|
8
15
|
module.exports = {
|
|
9
16
|
meta: {
|
|
10
17
|
type: "code",
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
6
|
|
|
3
7
|
const MESSAGE_IDS = {
|
|
4
8
|
DUPLICATE_ATTRS: "duplicateAttrs",
|
|
5
9
|
};
|
|
6
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @type {Rule}
|
|
13
|
+
*/
|
|
7
14
|
module.exports = {
|
|
8
15
|
meta: {
|
|
9
16
|
type: "code",
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
6
|
const { NodeUtils } = require("./utils");
|
|
3
7
|
|
|
@@ -5,6 +9,9 @@ const MESSAGE_IDS = {
|
|
|
5
9
|
DUPLICATE_ID: "duplicateId",
|
|
6
10
|
};
|
|
7
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @type {Rule}
|
|
14
|
+
*/
|
|
8
15
|
module.exports = {
|
|
9
16
|
meta: {
|
|
10
17
|
type: "code",
|
|
@@ -1,3 +1,10 @@
|
|
|
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
|
+
|
|
1
8
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
9
|
|
|
3
10
|
const MESSAGE_IDS = {
|
|
@@ -6,6 +13,9 @@ const MESSAGE_IDS = {
|
|
|
6
13
|
EXTRA_BEFORE: "unexpectedBefore",
|
|
7
14
|
};
|
|
8
15
|
|
|
16
|
+
/**
|
|
17
|
+
* @type {Rule}
|
|
18
|
+
*/
|
|
9
19
|
module.exports = {
|
|
10
20
|
meta: {
|
|
11
21
|
type: "code",
|
|
@@ -25,6 +35,9 @@ module.exports = {
|
|
|
25
35
|
},
|
|
26
36
|
},
|
|
27
37
|
create(context) {
|
|
38
|
+
/**
|
|
39
|
+
* @param {AttrNode[]} attrs
|
|
40
|
+
*/
|
|
28
41
|
function checkExtraSpacesBetweenAttrs(attrs) {
|
|
29
42
|
attrs.forEach((current, index, attrs) => {
|
|
30
43
|
if (index >= attrs.length - 1) {
|
|
@@ -50,9 +63,13 @@ module.exports = {
|
|
|
50
63
|
}
|
|
51
64
|
});
|
|
52
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* @param {TagNode} startTag
|
|
68
|
+
* @param {AttrNode} lastAttr
|
|
69
|
+
*/
|
|
53
70
|
function checkExtraSpaceAfter(startTag, lastAttr) {
|
|
54
71
|
if (startTag.loc.end.line !== lastAttr.loc.end.line) {
|
|
55
|
-
// skip the attribute on the
|
|
72
|
+
// skip the attribute on the different line with the start tag
|
|
56
73
|
return;
|
|
57
74
|
}
|
|
58
75
|
const spacesBetween = startTag.loc.end.column - lastAttr.loc.end.column;
|
|
@@ -72,9 +89,14 @@ module.exports = {
|
|
|
72
89
|
});
|
|
73
90
|
}
|
|
74
91
|
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {ElementNode} node
|
|
95
|
+
* @param {AttrNode} firstAttr
|
|
96
|
+
*/
|
|
75
97
|
function checkExtraSpaceBefore(node, firstAttr) {
|
|
76
98
|
if (node.loc.start.line !== firstAttr.loc.start.line) {
|
|
77
|
-
// skip the attribute on the
|
|
99
|
+
// skip the attribute on the different line with the start tag
|
|
78
100
|
return;
|
|
79
101
|
}
|
|
80
102
|
const nodeLength = node.tagName.length;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
6
|
const { NodeUtils } = require("./utils");
|
|
3
7
|
|
|
@@ -5,6 +9,9 @@ const MESSAGE_IDS = {
|
|
|
5
9
|
INLINE_STYLE: "unexpectedInlineStyle",
|
|
6
10
|
};
|
|
7
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @type {Rule}
|
|
14
|
+
*/
|
|
8
15
|
module.exports = {
|
|
9
16
|
meta: {
|
|
10
17
|
type: "code",
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { RULE_CATEGORY } = require("../constants");
|
|
6
|
+
|
|
7
|
+
const MESSAGE_IDS = {
|
|
8
|
+
UNEXPECTED: "unexpected",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @type {Rule}
|
|
13
|
+
*/
|
|
14
|
+
module.exports = {
|
|
15
|
+
meta: {
|
|
16
|
+
type: "code",
|
|
17
|
+
|
|
18
|
+
docs: {
|
|
19
|
+
description: "Disallow multiple empty lines",
|
|
20
|
+
category: RULE_CATEGORY.STYLE,
|
|
21
|
+
recommended: false,
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
fixable: "whitespace",
|
|
25
|
+
schema: [
|
|
26
|
+
{
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
max: {
|
|
30
|
+
type: "integer",
|
|
31
|
+
minimum: 0,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
required: ["max"],
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
messages: {
|
|
39
|
+
[MESSAGE_IDS.UNEXPECTED]: "More than {{max}} blank lines not allowed.",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
create(context) {
|
|
44
|
+
const sourceCode = context.getSourceCode();
|
|
45
|
+
const lines = sourceCode.lines;
|
|
46
|
+
const max = context.options.length ? context.options[0].max : 2;
|
|
47
|
+
return {
|
|
48
|
+
"Program:exit"(node) {
|
|
49
|
+
/** @type {number[]} */
|
|
50
|
+
const nonEmptyLineNumbers = [];
|
|
51
|
+
|
|
52
|
+
lines.forEach((line, index) => {
|
|
53
|
+
if (line.trim().length > 0) {
|
|
54
|
+
nonEmptyLineNumbers.push(index + 1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
nonEmptyLineNumbers.forEach((current, index, arr) => {
|
|
59
|
+
const before = arr[index - 1];
|
|
60
|
+
if (typeof before === "number") {
|
|
61
|
+
if (current - before - 1 > max) {
|
|
62
|
+
context.report({
|
|
63
|
+
node,
|
|
64
|
+
loc: {
|
|
65
|
+
start: { line: before, column: 0 },
|
|
66
|
+
end: { line: current, column: 0 },
|
|
67
|
+
},
|
|
68
|
+
messageId: MESSAGE_IDS.UNEXPECTED,
|
|
69
|
+
data: {
|
|
70
|
+
max,
|
|
71
|
+
},
|
|
72
|
+
fix(fixer) {
|
|
73
|
+
const start = sourceCode.getIndexFromLoc({
|
|
74
|
+
line: before + 1,
|
|
75
|
+
column: 0,
|
|
76
|
+
});
|
|
77
|
+
const end = sourceCode.getIndexFromLoc({
|
|
78
|
+
line: current - max,
|
|
79
|
+
column: 0,
|
|
80
|
+
});
|
|
81
|
+
return fixer.removeRange([start, end]);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
};
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
6
|
|
|
3
7
|
const MESSAGE_IDS = {
|
|
4
8
|
MULTIPLE_H1: "unexpectedMultiH1",
|
|
5
9
|
};
|
|
6
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @type {Rule}
|
|
13
|
+
*/
|
|
7
14
|
module.exports = {
|
|
8
15
|
meta: {
|
|
9
16
|
type: "code",
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
6
|
const { NodeUtils } = require("./utils");
|
|
3
7
|
|
|
@@ -5,6 +9,9 @@ const MESSAGE_IDS = {
|
|
|
5
9
|
UNEXPECTED: "unexpected",
|
|
6
10
|
};
|
|
7
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @type {Rule}
|
|
14
|
+
*/
|
|
8
15
|
module.exports = {
|
|
9
16
|
meta: {
|
|
10
17
|
type: "code",
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY, NODE_TYPES } = require("../constants");
|
|
2
6
|
const { OBSOLETE_TAGS } = require("../constants");
|
|
3
7
|
|
|
@@ -7,6 +11,9 @@ const MESSAGE_IDS = {
|
|
|
7
11
|
UNEXPECTED: "unexpected",
|
|
8
12
|
};
|
|
9
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @type {Rule}
|
|
16
|
+
*/
|
|
10
17
|
module.exports = {
|
|
11
18
|
meta: {
|
|
12
19
|
type: "code",
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
6
|
const { NodeUtils } = require("./utils");
|
|
3
7
|
|
|
@@ -5,6 +9,9 @@ const MESSAGE_IDS = {
|
|
|
5
9
|
UNEXPECTED: "unexpected",
|
|
6
10
|
};
|
|
7
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @type {Rule}
|
|
14
|
+
*/
|
|
8
15
|
module.exports = {
|
|
9
16
|
meta: {
|
|
10
17
|
type: "code",
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
6
|
|
|
3
7
|
const MESSAGE_IDS = {
|
|
4
8
|
UNEXPECTED: "unexpected",
|
|
5
9
|
};
|
|
6
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @type {Rule}
|
|
13
|
+
*/
|
|
7
14
|
module.exports = {
|
|
8
15
|
meta: {
|
|
9
16
|
type: "code",
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
6
|
const { NodeUtils } = require("./utils");
|
|
3
7
|
|
|
@@ -5,6 +9,9 @@ const MESSAGE_IDS = {
|
|
|
5
9
|
MISSING: "missing",
|
|
6
10
|
};
|
|
7
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @type {Rule}
|
|
14
|
+
*/
|
|
8
15
|
module.exports = {
|
|
9
16
|
meta: {
|
|
10
17
|
type: "code",
|
package/lib/rules/quotes.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
* @typedef {import("../types").Range} Range
|
|
4
|
+
* @typedef {import("../types").AttrNode} AttrNode
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
8
|
|
|
3
9
|
const MESSAGE_IDS = {
|
|
@@ -12,6 +18,9 @@ const QUOTES_STYLES = {
|
|
|
12
18
|
|
|
13
19
|
const QUOTES_CODES = [`"`, `'`];
|
|
14
20
|
|
|
21
|
+
/**
|
|
22
|
+
* @type {Rule}
|
|
23
|
+
*/
|
|
15
24
|
module.exports = {
|
|
16
25
|
meta: {
|
|
17
26
|
type: "code",
|
|
@@ -45,16 +54,28 @@ module.exports = {
|
|
|
45
54
|
|
|
46
55
|
const sourceCode = context.getSourceCode();
|
|
47
56
|
|
|
57
|
+
/**
|
|
58
|
+
* @param {Range} range
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
48
61
|
function getCodeIn(range) {
|
|
49
62
|
return sourceCode.text.slice(range[0], range[1]);
|
|
50
63
|
}
|
|
51
64
|
|
|
65
|
+
/**
|
|
66
|
+
* @param {AttrNode} attr
|
|
67
|
+
* @returns {Range}
|
|
68
|
+
*/
|
|
52
69
|
function getValueRange(attr) {
|
|
53
70
|
const attrCode = getCodeIn(attr.range);
|
|
54
71
|
const [matched = ""] = attrCode.match(/\S*?\s*=\s*/) || [];
|
|
55
72
|
return [attr.range[0] + matched.length, attr.range[1]];
|
|
56
73
|
}
|
|
57
74
|
|
|
75
|
+
/**
|
|
76
|
+
* @param {AttrNode} attr
|
|
77
|
+
* @returns {[string, string]}
|
|
78
|
+
*/
|
|
58
79
|
function getQuotes(attr) {
|
|
59
80
|
const [valueStart, valueEnd] = getValueRange(attr);
|
|
60
81
|
const opening = getCodeIn([valueStart, valueStart + 1]);
|
|
@@ -62,11 +83,18 @@ module.exports = {
|
|
|
62
83
|
return [opening, closing];
|
|
63
84
|
}
|
|
64
85
|
|
|
86
|
+
/**
|
|
87
|
+
* @param {AttrNode} attr
|
|
88
|
+
* @returns {boolean}
|
|
89
|
+
*/
|
|
65
90
|
function hasEqualSign(attr) {
|
|
66
91
|
const keyEnd = attr.range[0] + attr.name.length;
|
|
67
92
|
return getCodeIn([keyEnd, attr.range[1]]).trimStart().startsWith("=");
|
|
68
93
|
}
|
|
69
94
|
|
|
95
|
+
/**
|
|
96
|
+
* @param {AttrNode} attr
|
|
97
|
+
*/
|
|
70
98
|
function checkQuotes(attr) {
|
|
71
99
|
const [opening, closing] = getQuotes(attr);
|
|
72
100
|
if (QUOTES_CODES.includes(opening)) {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
6
|
const { NodeUtils } = require("./utils");
|
|
3
7
|
|
|
@@ -8,6 +12,9 @@ const MESSAGE_IDS = {
|
|
|
8
12
|
|
|
9
13
|
const VALID_BUTTON_TYPES_SET = new Set(["submit", "button", "reset"]);
|
|
10
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @type {Rule}
|
|
17
|
+
*/
|
|
11
18
|
module.exports = {
|
|
12
19
|
meta: {
|
|
13
20
|
type: "code",
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY, VOID_ELEMENTS } = require("../constants");
|
|
2
6
|
|
|
3
7
|
const VOID_ELEMENTS_SET = new Set(VOID_ELEMENTS);
|
|
@@ -8,6 +12,9 @@ const MESSAGE_IDS = {
|
|
|
8
12
|
UNEXPECTED: "unexpected",
|
|
9
13
|
};
|
|
10
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @type {Rule}
|
|
17
|
+
*/
|
|
11
18
|
module.exports = {
|
|
12
19
|
meta: {
|
|
13
20
|
type: "code",
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
6
|
const { NodeUtils } = require("./utils");
|
|
3
7
|
|
|
@@ -6,6 +10,9 @@ const MESSAGE_IDS = {
|
|
|
6
10
|
UNEXPECTED: "unexpected",
|
|
7
11
|
};
|
|
8
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @type {Rule}
|
|
15
|
+
*/
|
|
9
16
|
module.exports = {
|
|
10
17
|
meta: {
|
|
11
18
|
type: "code",
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
* @typedef {import("../types").ElementNode} ElementNode
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const { RULE_CATEGORY } = require("../constants");
|
|
2
7
|
|
|
3
8
|
const MESSAGE_IDS = {
|
|
4
9
|
MISSING_ALT: "missingAlt",
|
|
5
10
|
};
|
|
6
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @type {Rule}
|
|
14
|
+
*/
|
|
7
15
|
module.exports = {
|
|
8
16
|
meta: {
|
|
9
17
|
type: "code",
|
|
@@ -35,6 +43,11 @@ module.exports = {
|
|
|
35
43
|
},
|
|
36
44
|
};
|
|
37
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Checks whether a node has `alt` attribute value or not.
|
|
48
|
+
* @param {ElementNode} node a node to check.
|
|
49
|
+
* @returns {boolean} `true` if a node has `alt` attribute value.
|
|
50
|
+
*/
|
|
38
51
|
function hasAltAttrAndValue(node) {
|
|
39
52
|
return (node.attrs || []).some((attr) => {
|
|
40
53
|
return attr.name === "alt" && attr.value.trim().length > 0;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
2
5
|
const { RULE_CATEGORY } = require("../constants");
|
|
3
6
|
const { NodeUtils } = require("./utils");
|
|
4
7
|
|
|
@@ -7,6 +10,9 @@ const MESSAGE_IDS = {
|
|
|
7
10
|
EMPTY: "empty",
|
|
8
11
|
};
|
|
9
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @type {Rule}
|
|
15
|
+
*/
|
|
10
16
|
module.exports = {
|
|
11
17
|
meta: {
|
|
12
18
|
type: "code",
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY, NODE_TYPES } = require("../constants");
|
|
2
6
|
|
|
3
7
|
const MESSAGE_IDS = {
|
|
@@ -6,6 +10,9 @@ const MESSAGE_IDS = {
|
|
|
6
10
|
|
|
7
11
|
const VALID_CONTAINERS = [NODE_TYPES.UL, NODE_TYPES.OL, NODE_TYPES.MENU];
|
|
8
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @type {Rule}
|
|
15
|
+
*/
|
|
9
16
|
module.exports = {
|
|
10
17
|
meta: {
|
|
11
18
|
type: "code",
|
|
@@ -27,7 +34,12 @@ module.exports = {
|
|
|
27
34
|
create(context) {
|
|
28
35
|
return {
|
|
29
36
|
Li(node) {
|
|
30
|
-
if (!node.parent
|
|
37
|
+
if (!node.parent) {
|
|
38
|
+
context.report({
|
|
39
|
+
node,
|
|
40
|
+
messageId: MESSAGE_IDS.INVALID,
|
|
41
|
+
});
|
|
42
|
+
} else if (!VALID_CONTAINERS.includes(node.parent.type || "")) {
|
|
31
43
|
context.report({
|
|
32
44
|
node,
|
|
33
45
|
messageId: MESSAGE_IDS.INVALID,
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").ElementNode} ElementNode
|
|
3
|
+
* @typedef {import("../types").Context} Context
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const { RULE_CATEGORY, NODE_TYPES } = require("../constants");
|
|
2
7
|
const { NodeUtils } = require("./utils");
|
|
3
8
|
|
|
@@ -24,8 +29,14 @@ module.exports = {
|
|
|
24
29
|
},
|
|
25
30
|
},
|
|
26
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @param {Context} context
|
|
34
|
+
*/
|
|
27
35
|
create(context) {
|
|
28
36
|
return {
|
|
37
|
+
/**
|
|
38
|
+
* @param {ElementNode} node
|
|
39
|
+
*/
|
|
29
40
|
Head(node) {
|
|
30
41
|
const metaCharset = (node.childNodes || []).find((child) => {
|
|
31
42
|
return (
|
|
@@ -41,7 +52,7 @@ module.exports = {
|
|
|
41
52
|
return;
|
|
42
53
|
}
|
|
43
54
|
const charsetAttr = NodeUtils.findAttr(metaCharset, "charset");
|
|
44
|
-
if (
|
|
55
|
+
if (charsetAttr && !charsetAttr.value.length) {
|
|
45
56
|
context.report({
|
|
46
57
|
node: charsetAttr,
|
|
47
58
|
messageId: MESSAGE_IDS.EMPTY,
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").ElementNode} ElementNode
|
|
3
|
+
* @typedef {import("../types").Context} Context
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const { RULE_CATEGORY, NODE_TYPES } = require("../constants");
|
|
2
7
|
const { NodeUtils } = require("./utils");
|
|
3
8
|
|
|
@@ -25,8 +30,14 @@ module.exports = {
|
|
|
25
30
|
},
|
|
26
31
|
},
|
|
27
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @param {Context} context
|
|
35
|
+
*/
|
|
28
36
|
create(context) {
|
|
29
37
|
return {
|
|
38
|
+
/**
|
|
39
|
+
* @param {ElementNode} node
|
|
40
|
+
*/
|
|
30
41
|
Head(node) {
|
|
31
42
|
const metaTags = (node.childNodes || []).filter(
|
|
32
43
|
(child) => child.type === NODE_TYPES.META
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
// @ts-check
|
|
2
1
|
/**
|
|
3
|
-
* @typedef {import("../types").
|
|
4
|
-
* @typedef {import("../types").
|
|
2
|
+
* @typedef {import("../types").ElementNode} ElementNode
|
|
3
|
+
* @typedef {import("../types").Rule} Rule
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
6
|
const { RULE_CATEGORY, NODE_TYPES } = require("../constants");
|
|
@@ -12,6 +11,9 @@ const MESSAGE_IDS = {
|
|
|
12
11
|
EMPTY: "empty",
|
|
13
12
|
};
|
|
14
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @type {Rule}
|
|
16
|
+
*/
|
|
15
17
|
module.exports = {
|
|
16
18
|
meta: {
|
|
17
19
|
type: "code",
|
|
@@ -33,7 +35,7 @@ module.exports = {
|
|
|
33
35
|
|
|
34
36
|
create(context) {
|
|
35
37
|
/**
|
|
36
|
-
* @param {
|
|
38
|
+
* @param {ElementNode} node
|
|
37
39
|
* @returns {boolean}
|
|
38
40
|
*/
|
|
39
41
|
function isMetaViewport(node) {
|
|
@@ -44,9 +46,6 @@ module.exports = {
|
|
|
44
46
|
return false;
|
|
45
47
|
}
|
|
46
48
|
return {
|
|
47
|
-
/**
|
|
48
|
-
* @param {HTMLNode} node
|
|
49
|
-
*/
|
|
50
49
|
Head(node) {
|
|
51
50
|
const metaViewport = (node.childNodes || []).find(isMetaViewport);
|
|
52
51
|
if (!metaViewport) {
|
|
@@ -57,7 +56,12 @@ module.exports = {
|
|
|
57
56
|
return;
|
|
58
57
|
}
|
|
59
58
|
const contentAttr = NodeUtils.findAttr(metaViewport, "content");
|
|
60
|
-
if (!contentAttr
|
|
59
|
+
if (!contentAttr) {
|
|
60
|
+
context.report({
|
|
61
|
+
node: metaViewport,
|
|
62
|
+
messageId: MESSAGE_IDS.EMPTY,
|
|
63
|
+
});
|
|
64
|
+
} else if (!contentAttr.value.length) {
|
|
61
65
|
context.report({
|
|
62
66
|
node: contentAttr,
|
|
63
67
|
messageId: MESSAGE_IDS.EMPTY,
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const { RULE_CATEGORY, NODE_TYPES } = require("../constants");
|
|
6
|
+
const { NodeUtils } = require("./utils");
|
|
2
7
|
|
|
3
8
|
const MESSAGE_IDS = {
|
|
4
9
|
MISSING_TITLE: "missing",
|
|
5
10
|
EMPTY_TITLE: "empty",
|
|
6
11
|
};
|
|
7
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @type {Rule}
|
|
15
|
+
*/
|
|
8
16
|
module.exports = {
|
|
9
17
|
meta: {
|
|
10
18
|
type: "code",
|
|
@@ -23,7 +31,6 @@ module.exports = {
|
|
|
23
31
|
[MESSAGE_IDS.EMPTY_TITLE]: "Unexpected empty text in `<title><title/>`",
|
|
24
32
|
},
|
|
25
33
|
},
|
|
26
|
-
|
|
27
34
|
create(context) {
|
|
28
35
|
return {
|
|
29
36
|
Head(node) {
|
|
@@ -38,8 +45,7 @@ module.exports = {
|
|
|
38
45
|
});
|
|
39
46
|
} else if (
|
|
40
47
|
!(titleTag.childNodes || []).some(
|
|
41
|
-
(node) =>
|
|
42
|
-
node.type === NODE_TYPES.TEXT && node.value.trim().length > 0
|
|
48
|
+
(node) => NodeUtils.isTextNode(node) && node.value.trim().length > 0
|
|
43
49
|
)
|
|
44
50
|
) {
|
|
45
51
|
context.report({
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
// @ts-check
|
|
2
1
|
/**
|
|
3
|
-
* @typedef {import("../../types").
|
|
2
|
+
* @typedef {import("../../types").ElementNode} ElementNode
|
|
4
3
|
* @typedef {import("../../types").AttrNode} AttrNode
|
|
4
|
+
* @typedef {import("../../types").AnyNode} AnyNode
|
|
5
|
+
* @typedef {import("../../types").TextNode} TextNode
|
|
6
|
+
* @typedef {import("../../types").BaseNode} BaseNode
|
|
7
|
+
* @typedef {import("../../types").TextLineNode} TextLineNode
|
|
8
|
+
* @typedef {import("../../types").CommentNode} CommentNode
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
module.exports = {
|
|
8
12
|
/**
|
|
9
13
|
* Find attribute by name in the given node
|
|
10
|
-
* @param {
|
|
14
|
+
* @param {ElementNode} node node
|
|
11
15
|
* @param {string} name attribute name
|
|
12
16
|
* @return {AttrNode | void}
|
|
13
17
|
*/
|
|
@@ -20,7 +24,7 @@ module.exports = {
|
|
|
20
24
|
},
|
|
21
25
|
/**
|
|
22
26
|
* Checks a node has attribute with the given name or not.
|
|
23
|
-
* @param {
|
|
27
|
+
* @param {ElementNode} node node
|
|
24
28
|
* @param {string} name attribute name
|
|
25
29
|
* @return {boolean} `true` if the node has a attribute, otherwise `false`.
|
|
26
30
|
*/
|
|
@@ -29,10 +33,28 @@ module.exports = {
|
|
|
29
33
|
},
|
|
30
34
|
/**
|
|
31
35
|
* Checks whether a node's all tokens are on the same line or not.
|
|
32
|
-
* @param {
|
|
36
|
+
* @param {ElementNode} node A node to check
|
|
33
37
|
* @returns {boolean} `true` if a node's tokens are on the same line, otherwise `false`.
|
|
34
38
|
*/
|
|
35
39
|
isNodeTokensOnSameLine(node) {
|
|
36
40
|
return node.loc.start.line === node.loc.end.line;
|
|
37
41
|
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Checks whether a node is a TextNode or not.
|
|
45
|
+
* @param {Object} node A node to check
|
|
46
|
+
* @returns {node is TextNode} `true` if a node is `TextNode`, otherwise `false`.
|
|
47
|
+
*/
|
|
48
|
+
isTextNode(node) {
|
|
49
|
+
return !!(node && node.type === "text" && typeof node.value === "string");
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks whether a node is a CommentNode or not.
|
|
54
|
+
* @param {Object} node A node to check
|
|
55
|
+
* @returns {node is CommentNode} `true` if a node is `CommentNode`, otherwise `false`.
|
|
56
|
+
*/
|
|
57
|
+
isCommentNode(node) {
|
|
58
|
+
return !!(node && node.type === "comment");
|
|
59
|
+
},
|
|
38
60
|
};
|
package/lib/types.d.ts
CHANGED
|
@@ -1,49 +1,120 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import ESTree from "estree";
|
|
2
|
+
import ESLint from "eslint";
|
|
3
|
+
|
|
4
|
+
type Fix = ESLint.Rule.Fix;
|
|
5
|
+
type Token = ESLint.AST.Token;
|
|
6
|
+
export type Range = ESLint.AST.Range;
|
|
7
|
+
|
|
8
|
+
interface RuleListener {
|
|
9
|
+
[key: string]: (node: ElementNode) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Rule {
|
|
13
|
+
create(context: Context): RuleListener;
|
|
14
|
+
meta?: ESLint.Rule.RuleMetaData;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface RuleFixer {
|
|
18
|
+
insertTextAfter(nodeOrToken: AnyNode | Token, text: string): Fix;
|
|
19
|
+
|
|
20
|
+
insertTextAfterRange(range: Range, text: string): Fix;
|
|
21
|
+
|
|
22
|
+
insertTextBefore(nodeOrToken: AnyNode | Token, text: string): Fix;
|
|
23
|
+
|
|
24
|
+
insertTextBeforeRange(range: Range, text: string): Fix;
|
|
25
|
+
|
|
26
|
+
remove(nodeOrToken: AnyNode | Token): Fix;
|
|
27
|
+
|
|
28
|
+
removeRange(range: Range): Fix;
|
|
29
|
+
|
|
30
|
+
replaceText(nodeOrToken: AnyNode | Token, text: string): Fix;
|
|
31
|
+
|
|
32
|
+
replaceTextRange(range: Range, text: string): Fix;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ReportDescriptorOptionsBase {
|
|
36
|
+
data?: { [key: string]: string };
|
|
37
|
+
|
|
38
|
+
fix?:
|
|
39
|
+
| null
|
|
40
|
+
| ((fixer: RuleFixer) => null | Fix | IterableIterator<Fix> | Fix[]);
|
|
41
|
+
}
|
|
7
42
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
43
|
+
type SuggestionDescriptorMessage = { desc: string } | { messageId: string };
|
|
44
|
+
type SuggestionReportDescriptor = SuggestionDescriptorMessage &
|
|
45
|
+
ReportDescriptorOptionsBase;
|
|
46
|
+
|
|
47
|
+
interface ReportDescriptorOptions extends ReportDescriptorOptionsBase {
|
|
48
|
+
suggest?: SuggestionReportDescriptor[] | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type ReportDescriptor = ReportDescriptorMessage &
|
|
52
|
+
ReportDescriptorLocation &
|
|
53
|
+
ReportDescriptorOptions;
|
|
54
|
+
type ReportDescriptorMessage = { message: string } | { messageId: string };
|
|
55
|
+
type ReportDescriptorLocation = {
|
|
56
|
+
node?: BaseNode;
|
|
57
|
+
loc?: ESLint.AST.SourceLocation;
|
|
58
|
+
line?: number;
|
|
59
|
+
column?: number;
|
|
20
60
|
};
|
|
21
61
|
|
|
22
|
-
interface
|
|
62
|
+
export interface Context extends Omit<ESLint.Rule.RuleContext, "report"> {
|
|
63
|
+
report(descriptor: ReportDescriptor): void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface BaseNode {
|
|
67
|
+
parent?: null | AnyNode;
|
|
68
|
+
range: [number, number];
|
|
23
69
|
start: number;
|
|
24
70
|
end: number;
|
|
25
|
-
range: [number, number];
|
|
26
71
|
loc: {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
column: number;
|
|
30
|
-
};
|
|
31
|
-
start: {
|
|
32
|
-
line: number;
|
|
33
|
-
column: number;
|
|
34
|
-
};
|
|
72
|
+
start: ESTree.Position;
|
|
73
|
+
end: ESTree.Position;
|
|
35
74
|
};
|
|
75
|
+
type?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface TagNode extends BaseNode {
|
|
79
|
+
type: undefined;
|
|
36
80
|
}
|
|
37
81
|
|
|
38
|
-
export interface
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
82
|
+
export interface TextLineNode extends BaseNode {
|
|
83
|
+
textLine: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface TextNode extends BaseNode {
|
|
87
|
+
type: "text";
|
|
88
|
+
value: string;
|
|
89
|
+
lineNodes: TextLineNode[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface ElementNode extends BaseNode {
|
|
42
93
|
type: string;
|
|
43
|
-
|
|
94
|
+
tagName: string;
|
|
95
|
+
attrs: AttrNode[];
|
|
96
|
+
childNodes: ElementNode[];
|
|
97
|
+
startTag?: TagNode;
|
|
98
|
+
endTag?: TagNode;
|
|
44
99
|
}
|
|
45
100
|
|
|
46
101
|
export interface AttrNode extends BaseNode {
|
|
47
102
|
name: string;
|
|
48
103
|
value: string;
|
|
49
104
|
}
|
|
105
|
+
|
|
106
|
+
export interface CommentNode extends BaseNode {
|
|
107
|
+
type: "comment";
|
|
108
|
+
value: string;
|
|
109
|
+
startTag?: TagNode;
|
|
110
|
+
endTag?: TagNode;
|
|
111
|
+
lineNodes: TextLineNode[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type AnyNode =
|
|
115
|
+
| AttrNode
|
|
116
|
+
| ElementNode
|
|
117
|
+
| TextNode
|
|
118
|
+
| TextLineNode
|
|
119
|
+
| TagNode
|
|
120
|
+
| CommentNode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@html-eslint/eslint-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "ESLint plugin for html",
|
|
5
5
|
"author": "yeonjuan",
|
|
6
6
|
"homepage": "https://github.com/yeonjuan/html-eslint#readme",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"url": "git+https://github.com/yeonjuan/html-eslint.git"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
|
-
"test": "jest --coverage"
|
|
24
|
+
"test": "jest --coverage",
|
|
25
|
+
"check:ts": "tsc"
|
|
25
26
|
},
|
|
26
27
|
"bugs": {
|
|
27
28
|
"url": "https://github.com/yeonjuan/html-eslint/issues"
|
|
@@ -39,7 +40,10 @@
|
|
|
39
40
|
"accessibility"
|
|
40
41
|
],
|
|
41
42
|
"devDependencies": {
|
|
42
|
-
"@html-eslint/parser": "^0.
|
|
43
|
+
"@html-eslint/parser": "^0.11.0",
|
|
44
|
+
"@types/eslint": "^7.2.10",
|
|
45
|
+
"@types/estree": "^0.0.47",
|
|
46
|
+
"typescript": "^4.2.4"
|
|
43
47
|
},
|
|
44
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "a3873ecefae3b9306a5e98985cf4ccdbde95299a"
|
|
45
49
|
}
|