@html-eslint/eslint-plugin 0.9.0-alpha.0 → 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/configs/recommended.js +1 -0
- 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 +16 -9
- package/lib/rules/indent.js +19 -7
- package/lib/rules/index.js +14 -2
- package/lib/rules/no-abstract-roles.js +62 -0
- package/lib/rules/no-accesskey-attrs.js +45 -0
- package/lib/rules/no-aria-hidden-body.js +46 -0
- package/lib/rules/no-duplicate-attrs.js +54 -0
- package/lib/rules/no-duplicate-id.js +7 -0
- package/lib/rules/no-extra-spacing-attrs.js +27 -5
- 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 +30 -2
- package/lib/rules/require-button-type.js +58 -0
- package/lib/rules/require-closing-tags.js +11 -4
- 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 +10 -4
- 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 +15 -4
- 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
|
@@ -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,28 +54,47 @@ 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
|
-
const
|
|
81
|
+
const opening = getCodeIn([valueStart, valueStart + 1]);
|
|
61
82
|
const closing = getCodeIn([valueEnd - 1, valueEnd]);
|
|
62
|
-
return [
|
|
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)) {
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
MISSING: "missing",
|
|
10
|
+
INVALID: "invalid",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const VALID_BUTTON_TYPES_SET = new Set(["submit", "button", "reset"]);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @type {Rule}
|
|
17
|
+
*/
|
|
18
|
+
module.exports = {
|
|
19
|
+
meta: {
|
|
20
|
+
type: "code",
|
|
21
|
+
|
|
22
|
+
docs: {
|
|
23
|
+
description: "Require use of button element with a valid type attribute.",
|
|
24
|
+
category: RULE_CATEGORY.BEST_PRACTICE,
|
|
25
|
+
recommended: false,
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
fixable: null,
|
|
29
|
+
schema: [],
|
|
30
|
+
messages: {
|
|
31
|
+
[MESSAGE_IDS.MISSING]: "Missing a type attribute for button",
|
|
32
|
+
[MESSAGE_IDS.INVALID]:
|
|
33
|
+
'"{{type}}" is an invalid value for button type attribute.',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
create(context) {
|
|
38
|
+
return {
|
|
39
|
+
Button(node) {
|
|
40
|
+
const typeAttr = NodeUtils.findAttr(node, "type");
|
|
41
|
+
if (!typeAttr) {
|
|
42
|
+
context.report({
|
|
43
|
+
node: node.startTag || node,
|
|
44
|
+
messageId: MESSAGE_IDS.MISSING,
|
|
45
|
+
});
|
|
46
|
+
} else if (!VALID_BUTTON_TYPES_SET.has(typeAttr.value)) {
|
|
47
|
+
context.report({
|
|
48
|
+
node: typeAttr,
|
|
49
|
+
messageId: MESSAGE_IDS.INVALID,
|
|
50
|
+
data: {
|
|
51
|
+
type: typeAttr.value,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
};
|
|
@@ -1,19 +1,26 @@
|
|
|
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);
|
|
4
8
|
|
|
5
9
|
const MESSAGE_IDS = {
|
|
6
|
-
|
|
10
|
+
MISSING: "missing",
|
|
7
11
|
MISSING_SELF: "missingSelf",
|
|
8
12
|
UNEXPECTED: "unexpected",
|
|
9
13
|
};
|
|
10
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @type {Rule}
|
|
17
|
+
*/
|
|
11
18
|
module.exports = {
|
|
12
19
|
meta: {
|
|
13
20
|
type: "code",
|
|
14
21
|
|
|
15
22
|
docs: {
|
|
16
|
-
description: "
|
|
23
|
+
description: "Require closing tags.",
|
|
17
24
|
category: RULE_CATEGORY.BEST_PRACTICE,
|
|
18
25
|
recommended: true,
|
|
19
26
|
},
|
|
@@ -31,7 +38,7 @@ module.exports = {
|
|
|
31
38
|
},
|
|
32
39
|
],
|
|
33
40
|
messages: {
|
|
34
|
-
[MESSAGE_IDS.
|
|
41
|
+
[MESSAGE_IDS.MISSING]: "Missing closing tag for {{tag}}.",
|
|
35
42
|
[MESSAGE_IDS.MISSING_SELF]: "Missing self closing tag for {{tag}}",
|
|
36
43
|
[MESSAGE_IDS.UNEXPECTED]: "Unexpected self closing tag for {{tag}}.",
|
|
37
44
|
},
|
|
@@ -55,7 +62,7 @@ module.exports = {
|
|
|
55
62
|
data: {
|
|
56
63
|
tag: node.tagName,
|
|
57
64
|
},
|
|
58
|
-
messageId: MESSAGE_IDS.
|
|
65
|
+
messageId: MESSAGE_IDS.MISSING,
|
|
59
66
|
});
|
|
60
67
|
}
|
|
61
68
|
}
|
|
@@ -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,12 +1,18 @@
|
|
|
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
|
|
|
5
8
|
const MESSAGE_IDS = {
|
|
6
|
-
|
|
9
|
+
MISSING: "missing",
|
|
7
10
|
EMPTY: "empty",
|
|
8
11
|
};
|
|
9
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @type {Rule}
|
|
15
|
+
*/
|
|
10
16
|
module.exports = {
|
|
11
17
|
meta: {
|
|
12
18
|
type: "code",
|
|
@@ -20,7 +26,7 @@ module.exports = {
|
|
|
20
26
|
fixable: null,
|
|
21
27
|
schema: [],
|
|
22
28
|
messages: {
|
|
23
|
-
[MESSAGE_IDS.
|
|
29
|
+
[MESSAGE_IDS.MISSING]: "Missing `lang` attribute in `<html>` tag.",
|
|
24
30
|
[MESSAGE_IDS.EMPTY]: "Unexpected empty `lang` in in `<html>` tag.",
|
|
25
31
|
},
|
|
26
32
|
},
|
|
@@ -32,7 +38,7 @@ module.exports = {
|
|
|
32
38
|
if (!langAttr) {
|
|
33
39
|
context.report({
|
|
34
40
|
node: node.startTag,
|
|
35
|
-
messageId: MESSAGE_IDS.
|
|
41
|
+
messageId: MESSAGE_IDS.MISSING,
|
|
36
42
|
});
|
|
37
43
|
} else if (langAttr.value.trim().length === 0) {
|
|
38
44
|
context.report({
|
|
@@ -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
|
|
|
@@ -21,28 +26,34 @@ module.exports = {
|
|
|
21
26
|
messages: {
|
|
22
27
|
[MESSAGE_IDS.MISSING]: 'Missing `<meta name="description">`.',
|
|
23
28
|
[MESSAGE_IDS.EMPTY]:
|
|
24
|
-
'Unexpected
|
|
29
|
+
'Unexpected empty `content` in `<meta name="description">`',
|
|
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
|
|
33
44
|
);
|
|
34
|
-
const
|
|
45
|
+
const descriptionMetaTags = metaTags.filter((meta) => {
|
|
35
46
|
const nameAttr = NodeUtils.findAttr(meta, "name");
|
|
36
47
|
return !!nameAttr && nameAttr.value.toLowerCase() === "description";
|
|
37
48
|
});
|
|
38
49
|
|
|
39
|
-
if (
|
|
50
|
+
if (descriptionMetaTags.length === 0) {
|
|
40
51
|
context.report({
|
|
41
52
|
node,
|
|
42
53
|
messageId: MESSAGE_IDS.MISSING,
|
|
43
54
|
});
|
|
44
55
|
} else {
|
|
45
|
-
|
|
56
|
+
descriptionMetaTags.forEach((meta) => {
|
|
46
57
|
const content = NodeUtils.findAttr(meta, "content");
|
|
47
58
|
if (!content || !content.value.trim().length) {
|
|
48
59
|
context.report({
|
|
@@ -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({
|