@html-eslint/eslint-plugin 0.28.0-alpha.0 → 0.28.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/attrs-newline.js +4 -5
- package/lib/rules/element-newline.js +22 -25
- package/lib/rules/id-naming-convention.js +63 -22
- package/lib/rules/indent.js +11 -4
- package/lib/rules/lowercase.js +4 -5
- package/lib/rules/no-abstract-roles.js +23 -19
- package/lib/rules/no-accesskey-attrs.js +18 -14
- package/lib/rules/no-aria-hidden-body.js +18 -12
- package/lib/rules/no-duplicate-attrs.js +27 -23
- package/lib/rules/no-duplicate-id.js +62 -21
- package/lib/rules/no-extra-spacing-attrs.js +104 -100
- package/lib/rules/no-extra-spacing-text.js +66 -26
- package/lib/rules/no-inline-styles.js +3 -25
- package/lib/rules/no-multiple-empty-lines.js +94 -42
- package/lib/rules/no-multiple-h1.js +1 -1
- package/lib/rules/no-obsolete-tags.js +3 -2
- package/lib/rules/no-positive-tabindex.js +24 -18
- package/lib/rules/no-restricted-attr-values.js +51 -47
- package/lib/rules/no-restricted-attrs.js +50 -45
- package/lib/rules/no-script-style-type.js +3 -2
- package/lib/rules/no-skip-heading-levels.js +1 -1
- package/lib/rules/no-target-blank.js +7 -2
- package/lib/rules/no-trailing-spaces.js +95 -39
- package/lib/rules/quotes.js +12 -8
- package/lib/rules/require-attrs.js +28 -20
- package/lib/rules/require-button-type.js +7 -3
- package/lib/rules/require-closing-tags.js +3 -2
- package/lib/rules/require-frame-title.js +3 -2
- package/lib/rules/require-img-alt.js +3 -2
- package/lib/rules/require-lang.js +3 -2
- package/lib/rules/require-li-container.js +1 -1
- package/lib/rules/require-meta-charset.js +5 -9
- package/lib/rules/require-meta-description.js +5 -5
- package/lib/rules/require-meta-viewport.js +5 -5
- package/lib/rules/require-open-graph-protocol.js +5 -5
- package/lib/rules/require-title.js +8 -7
- package/lib/rules/sort-attrs.js +5 -3
- package/lib/rules/utils/node.js +226 -68
- package/lib/rules/utils/visitors.js +52 -0
- package/lib/types.d.ts +15 -19
- package/package.json +9 -6
- package/types/configs/recommended.d.ts +1 -1
- package/types/constants/rule-category.d.ts +4 -4
- package/types/index.d.ts.map +1 -1
- package/types/rules/attrs-newline.d.ts +7 -4
- package/types/rules/attrs-newline.d.ts.map +1 -1
- package/types/rules/element-newline.d.ts +13 -11
- package/types/rules/element-newline.d.ts.map +1 -1
- package/types/rules/id-naming-convention.d.ts +7 -4
- package/types/rules/id-naming-convention.d.ts.map +1 -1
- package/types/rules/indent.d.ts +10 -7
- package/types/rules/indent.d.ts.map +1 -1
- package/types/rules/lowercase.d.ts +8 -4
- package/types/rules/lowercase.d.ts.map +1 -1
- package/types/rules/no-abstract-roles.d.ts +7 -4
- package/types/rules/no-abstract-roles.d.ts.map +1 -1
- package/types/rules/no-accesskey-attrs.d.ts +7 -4
- package/types/rules/no-accesskey-attrs.d.ts.map +1 -1
- package/types/rules/no-aria-hidden-body.d.ts +4 -1
- package/types/rules/no-aria-hidden-body.d.ts.map +1 -1
- package/types/rules/no-duplicate-attrs.d.ts +7 -4
- package/types/rules/no-duplicate-attrs.d.ts.map +1 -1
- package/types/rules/no-duplicate-id.d.ts +8 -4
- package/types/rules/no-duplicate-id.d.ts.map +1 -1
- package/types/rules/no-extra-spacing-attrs.d.ts +15 -12
- package/types/rules/no-extra-spacing-attrs.d.ts.map +1 -1
- package/types/rules/no-extra-spacing-text.d.ts +11 -5
- package/types/rules/no-extra-spacing-text.d.ts.map +1 -1
- package/types/rules/no-inline-styles.d.ts +5 -2
- package/types/rules/no-inline-styles.d.ts.map +1 -1
- package/types/rules/no-multiple-empty-lines.d.ts +8 -2
- package/types/rules/no-multiple-empty-lines.d.ts.map +1 -1
- package/types/rules/no-multiple-h1.d.ts +5 -2
- package/types/rules/no-multiple-h1.d.ts.map +1 -1
- package/types/rules/no-non-scalable-viewport.d.ts +4 -1
- package/types/rules/no-non-scalable-viewport.d.ts.map +1 -1
- package/types/rules/no-obsolete-tags.d.ts +4 -1
- package/types/rules/no-obsolete-tags.d.ts.map +1 -1
- package/types/rules/no-positive-tabindex.d.ts +7 -4
- package/types/rules/no-positive-tabindex.d.ts.map +1 -1
- package/types/rules/no-restricted-attr-values.d.ts +9 -6
- package/types/rules/no-restricted-attr-values.d.ts.map +1 -1
- package/types/rules/no-restricted-attrs.d.ts +9 -6
- package/types/rules/no-restricted-attrs.d.ts.map +1 -1
- package/types/rules/no-script-style-type.d.ts +7 -4
- package/types/rules/no-script-style-type.d.ts.map +1 -1
- package/types/rules/no-skip-heading-levels.d.ts +5 -2
- package/types/rules/no-skip-heading-levels.d.ts.map +1 -1
- package/types/rules/no-target-blank.d.ts +4 -1
- package/types/rules/no-target-blank.d.ts.map +1 -1
- package/types/rules/no-trailing-spaces.d.ts +6 -1
- package/types/rules/no-trailing-spaces.d.ts.map +1 -1
- package/types/rules/quotes.d.ts +9 -6
- package/types/rules/quotes.d.ts.map +1 -1
- package/types/rules/require-attrs.d.ts +7 -4
- package/types/rules/require-attrs.d.ts.map +1 -1
- package/types/rules/require-button-type.d.ts +4 -1
- package/types/rules/require-button-type.d.ts.map +1 -1
- package/types/rules/require-closing-tags.d.ts +5 -2
- package/types/rules/require-closing-tags.d.ts.map +1 -1
- package/types/rules/require-doctype.d.ts +4 -1
- package/types/rules/require-doctype.d.ts.map +1 -1
- package/types/rules/require-frame-title.d.ts +4 -1
- package/types/rules/require-frame-title.d.ts.map +1 -1
- package/types/rules/require-img-alt.d.ts +5 -2
- package/types/rules/require-img-alt.d.ts.map +1 -1
- package/types/rules/require-lang.d.ts +4 -1
- package/types/rules/require-lang.d.ts.map +1 -1
- package/types/rules/require-li-container.d.ts +4 -1
- package/types/rules/require-li-container.d.ts.map +1 -1
- package/types/rules/require-meta-charset.d.ts +6 -2
- package/types/rules/require-meta-charset.d.ts.map +1 -1
- package/types/rules/require-meta-description.d.ts +6 -2
- package/types/rules/require-meta-description.d.ts.map +1 -1
- package/types/rules/require-meta-viewport.d.ts +6 -2
- package/types/rules/require-meta-viewport.d.ts.map +1 -1
- package/types/rules/require-open-graph-protocol.d.ts +6 -2
- package/types/rules/require-open-graph-protocol.d.ts.map +1 -1
- package/types/rules/require-title.d.ts +7 -3
- package/types/rules/require-title.d.ts.map +1 -1
- package/types/rules/sort-attrs.d.ts +7 -4
- package/types/rules/sort-attrs.d.ts.map +1 -1
- package/types/rules/utils/array.d.ts.map +1 -1
- package/types/rules/utils/naming.d.ts.map +1 -1
- package/types/rules/utils/node.d.ts +65 -12
- package/types/rules/utils/node.d.ts.map +1 -1
- package/types/rules/utils/visitors.d.ts +10 -0
- package/types/rules/utils/visitors.d.ts.map +1 -0
|
@@ -38,7 +38,7 @@ module.exports = {
|
|
|
38
38
|
if (node.name !== "li") {
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
-
if (!node.parent || node.parent.type === NODE_TYPES.
|
|
41
|
+
if (!node.parent || node.parent.type === NODE_TYPES.Document) {
|
|
42
42
|
context.report({
|
|
43
43
|
node,
|
|
44
44
|
messageId: MESSAGE_IDS.INVALID,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef { import("../types").RuleModule } RuleModule
|
|
3
|
-
* @typedef { import("
|
|
3
|
+
* @typedef { import("es-html-parser").TagNode } TagNode
|
|
4
|
+
* @typedef { import("es-html-parser").AnyNode } AnyNode
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
const { NODE_TYPES } = require("@html-eslint/parser");
|
|
7
7
|
const { RULE_CATEGORY } = require("../constants");
|
|
8
8
|
const { find } = require("./utils/array");
|
|
9
|
-
const { findAttr } = require("./utils/node");
|
|
9
|
+
const { findAttr, isTag } = require("./utils/node");
|
|
10
10
|
|
|
11
11
|
const MESSAGE_IDS = {
|
|
12
12
|
MISSING: "missing",
|
|
@@ -14,15 +14,11 @@ const MESSAGE_IDS = {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* @param {
|
|
17
|
+
* @param {AnyNode} node
|
|
18
18
|
* @returns {node is TagNode}
|
|
19
19
|
*/
|
|
20
20
|
function isMetaCharset(node) {
|
|
21
|
-
return (
|
|
22
|
-
node.type === NODE_TYPES.Tag &&
|
|
23
|
-
node.name === "meta" &&
|
|
24
|
-
!!findAttr(node, "charset")
|
|
25
|
-
);
|
|
21
|
+
return isTag(node) && node.name === "meta" && !!findAttr(node, "charset");
|
|
26
22
|
}
|
|
27
23
|
|
|
28
24
|
/**
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef { import("../types").RuleModule } RuleModule
|
|
3
|
-
* @typedef { import("
|
|
3
|
+
* @typedef { import("es-html-parser").TagNode } TagNode
|
|
4
|
+
* @typedef { import("es-html-parser").AnyNode } AnyNode
|
|
4
5
|
*/
|
|
5
|
-
const { NODE_TYPES } = require("@html-eslint/parser");
|
|
6
6
|
const { RULE_CATEGORY } = require("../constants");
|
|
7
7
|
const { filter } = require("./utils/array");
|
|
8
|
-
const { findAttr } = require("./utils/node");
|
|
8
|
+
const { findAttr, isTag } = require("./utils/node");
|
|
9
9
|
|
|
10
10
|
const MESSAGE_IDS = {
|
|
11
11
|
MISSING: "missing",
|
|
@@ -13,11 +13,11 @@ const MESSAGE_IDS = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* @param {
|
|
16
|
+
* @param {AnyNode} node
|
|
17
17
|
* @returns {node is TagNode}
|
|
18
18
|
*/
|
|
19
19
|
function isMetaTagNode(node) {
|
|
20
|
-
return node
|
|
20
|
+
return isTag(node) && node.name === "meta";
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef { import("../types").RuleModule } RuleModule
|
|
3
|
-
* @typedef { import("
|
|
3
|
+
* @typedef { import("es-html-parser").TagNode } TagNode
|
|
4
|
+
* @typedef { import("es-html-parser").AnyNode } AnyNode
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
const { NODE_TYPES } = require("@html-eslint/parser");
|
|
7
7
|
const { RULE_CATEGORY } = require("../constants");
|
|
8
8
|
const { find } = require("./utils/array");
|
|
9
|
-
const { findAttr } = require("./utils/node");
|
|
9
|
+
const { findAttr, isTag } = require("./utils/node");
|
|
10
10
|
|
|
11
11
|
const MESSAGE_IDS = {
|
|
12
12
|
MISSING: "missing",
|
|
@@ -14,11 +14,11 @@ const MESSAGE_IDS = {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* @param {
|
|
17
|
+
* @param {AnyNode} node
|
|
18
18
|
* @returns {node is TagNode}
|
|
19
19
|
*/
|
|
20
20
|
function isMetaViewport(node) {
|
|
21
|
-
if (node
|
|
21
|
+
if (isTag(node) && node.name === "meta") {
|
|
22
22
|
const nameAttribute = findAttr(node, "name");
|
|
23
23
|
return !!(
|
|
24
24
|
nameAttribute &&
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef { import("../types").RuleModule } RuleModule
|
|
3
|
-
* @typedef { import("
|
|
3
|
+
* @typedef { import("es-html-parser").TagNode } TagNode
|
|
4
|
+
* @typedef { import("es-html-parser").AnyNode } AnyNode
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
const { NODE_TYPES } = require("@html-eslint/parser");
|
|
7
7
|
const { RULE_CATEGORY } = require("../constants");
|
|
8
8
|
const { filter } = require("./utils/array");
|
|
9
|
-
const { findAttr } = require("./utils/node");
|
|
9
|
+
const { findAttr, isTag } = require("./utils/node");
|
|
10
10
|
|
|
11
11
|
const MESSAGE_IDS = {
|
|
12
12
|
MISSING: "missing",
|
|
@@ -70,11 +70,11 @@ module.exports = {
|
|
|
70
70
|
);
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
* @param {
|
|
73
|
+
* @param {AnyNode} node
|
|
74
74
|
* @returns {node is TagNode}
|
|
75
75
|
*/
|
|
76
76
|
function isOgpMeta(node) {
|
|
77
|
-
const isMeta = node
|
|
77
|
+
const isMeta = isTag(node) && node.name === "meta";
|
|
78
78
|
const property = isMeta ? findAttr(node, "property") : undefined;
|
|
79
79
|
const hasOgProperty =
|
|
80
80
|
!!property &&
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef { import("../types").RuleModule } RuleModule
|
|
3
|
-
* @typedef { import("
|
|
4
|
-
* @typedef { import("
|
|
3
|
+
* @typedef { import("es-html-parser").TagNode } TagNode
|
|
4
|
+
* @typedef { import("es-html-parser").TextNode } TextNode
|
|
5
|
+
* @typedef { import("es-html-parser").AnyNode } AnyNode
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
const { NODE_TYPES } = require("@html-eslint/parser");
|
|
8
8
|
const { RULE_CATEGORY } = require("../constants");
|
|
9
9
|
const { find } = require("./utils/array");
|
|
10
|
+
const { isText, isTag } = require("./utils/node");
|
|
10
11
|
|
|
11
12
|
const MESSAGE_IDS = {
|
|
12
13
|
MISSING_TITLE: "missing",
|
|
@@ -14,19 +15,19 @@ const MESSAGE_IDS = {
|
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
|
-
* @param {
|
|
18
|
+
* @param {AnyNode} node
|
|
18
19
|
* @returns {node is TagNode}
|
|
19
20
|
*/
|
|
20
21
|
function isTitle(node) {
|
|
21
|
-
return node
|
|
22
|
+
return isTag(node) && node.name === "title";
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
|
-
* @param {
|
|
26
|
+
* @param {AnyNode} node
|
|
26
27
|
* @returns {node is TextNode}
|
|
27
28
|
*/
|
|
28
29
|
function isNonEmptyText(node) {
|
|
29
|
-
return node
|
|
30
|
+
return isText(node) && node.value.trim().length > 0;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
/**
|
package/lib/rules/sort-attrs.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const { RULE_CATEGORY } = require("../constants");
|
|
9
|
+
const { getSourceCode } = require("./utils/source-code");
|
|
10
|
+
const { createVisitors } = require("./utils/visitors");
|
|
9
11
|
|
|
10
12
|
const MESSAGE_IDS = {
|
|
11
13
|
UNSORTED: "unsorted",
|
|
@@ -43,7 +45,7 @@ module.exports = {
|
|
|
43
45
|
},
|
|
44
46
|
},
|
|
45
47
|
create(context) {
|
|
46
|
-
const sourceCode =
|
|
48
|
+
const sourceCode = getSourceCode(context);
|
|
47
49
|
const option = context.options[0] || {
|
|
48
50
|
priority: ["id", "type", "class", "style"],
|
|
49
51
|
};
|
|
@@ -150,7 +152,7 @@ module.exports = {
|
|
|
150
152
|
});
|
|
151
153
|
}
|
|
152
154
|
|
|
153
|
-
return {
|
|
155
|
+
return createVisitors(context, {
|
|
154
156
|
ScriptTag(node) {
|
|
155
157
|
checkSorting(node.attributes);
|
|
156
158
|
},
|
|
@@ -160,6 +162,6 @@ module.exports = {
|
|
|
160
162
|
StyleTag(node) {
|
|
161
163
|
checkSorting(node.attributes);
|
|
162
164
|
},
|
|
163
|
-
};
|
|
165
|
+
});
|
|
164
166
|
},
|
|
165
167
|
};
|
package/lib/rules/utils/node.js
CHANGED
|
@@ -1,83 +1,241 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef { import("
|
|
3
|
-
* @typedef { import("
|
|
4
|
-
* @typedef { import("
|
|
5
|
-
* @typedef { import("
|
|
6
|
-
* @typedef { import("
|
|
7
|
-
* @typedef { import("
|
|
8
|
-
* @typedef { import("
|
|
2
|
+
* @typedef { import("es-html-parser").TagNode } TagNode
|
|
3
|
+
* @typedef { import("es-html-parser").ScriptTagNode } ScriptTagNode
|
|
4
|
+
* @typedef { import("es-html-parser").StyleTagNode } StyleTagNode
|
|
5
|
+
* @typedef { import("es-html-parser").AttributeNode } AttributeNode
|
|
6
|
+
* @typedef { import("es-html-parser").AttributeValueNode } AttributeValueNode
|
|
7
|
+
* @typedef { import("es-html-parser").AnyNode } AnyNode
|
|
8
|
+
* @typedef { import("es-html-parser").TextNode } TextNode
|
|
9
|
+
* @typedef { import("es-html-parser").CommentContentNode } CommentContentNode
|
|
10
|
+
* @typedef { import("es-html-parser").CommentNode } CommentNode
|
|
11
|
+
* @typedef { import("es-html-parser").AnyToken} AnyToken
|
|
9
12
|
* @typedef { import("../../types").LineNode } LineNode
|
|
10
13
|
* @typedef { import("../../types").BaseNode } BaseNode
|
|
11
14
|
* @typedef { import("../../types").Location } Location
|
|
15
|
+
* @typedef { import("../../types").Range } Range
|
|
12
16
|
*/
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
)
|
|
24
|
-
|
|
18
|
+
const { NODE_TYPES } = require("@html-eslint/parser");
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {TagNode | ScriptTagNode | StyleTagNode} node
|
|
22
|
+
* @param {string} key
|
|
23
|
+
* @returns {AttributeNode | undefined}
|
|
24
|
+
*/
|
|
25
|
+
function findAttr(node, key) {
|
|
26
|
+
return node.attributes.find(
|
|
27
|
+
(attr) => attr.key && attr.key.value.toLowerCase() === key.toLowerCase()
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Checks whether a node's attributes is empty or not.
|
|
33
|
+
* @param {TagNode | ScriptTagNode | StyleTagNode} node
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
function isAttributesEmpty(node) {
|
|
37
|
+
return !node.attributes || node.attributes.length <= 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Checks whether a node's all tokens are on the same line or not.
|
|
42
|
+
* @param {AnyNode} node A node to check
|
|
43
|
+
* @returns {boolean} `true` if a node's tokens are on the same line, otherwise `false`.
|
|
44
|
+
*/
|
|
45
|
+
function isNodeTokensOnSameLine(node) {
|
|
46
|
+
return node.loc.start.line === node.loc.end.line;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
*
|
|
51
|
+
* @param {Range} rangeA
|
|
52
|
+
* @param {Range} rangeB
|
|
53
|
+
* @returns {boolean}
|
|
54
|
+
*/
|
|
55
|
+
function isRangesOverlap(rangeA, rangeB) {
|
|
56
|
+
return rangeA[0] < rangeB[1] && rangeB[0] < rangeA[1];
|
|
57
|
+
}
|
|
25
58
|
|
|
59
|
+
/**
|
|
60
|
+
* @param {(TextNode | CommentContentNode)['templates']} templates
|
|
61
|
+
* @param {Range} range
|
|
62
|
+
* @returns {boolean}
|
|
63
|
+
*/
|
|
64
|
+
function isOverlapWithTemplates(templates, range) {
|
|
65
|
+
return templates
|
|
66
|
+
.filter((template) => template.isTemplate)
|
|
67
|
+
.some((template) => isRangesOverlap(template.range, range));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
*
|
|
72
|
+
* @param {TextNode | CommentContentNode} node
|
|
73
|
+
* @returns {LineNode[]}
|
|
74
|
+
*/
|
|
75
|
+
function splitToLineNodes(node) {
|
|
76
|
+
let start = node.range[0];
|
|
77
|
+
let line = node.loc.start.line;
|
|
78
|
+
const startCol = node.loc.start.column;
|
|
26
79
|
/**
|
|
27
|
-
*
|
|
28
|
-
* @param {AnyNode} node A node to check
|
|
29
|
-
* @returns {boolean} `true` if a node's tokens are on the same line, otherwise `false`.
|
|
80
|
+
* @type {LineNode[]}
|
|
30
81
|
*/
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
},
|
|
34
|
-
|
|
82
|
+
const lineNodes = [];
|
|
83
|
+
const templates = node.templates || [];
|
|
35
84
|
/**
|
|
36
85
|
*
|
|
37
|
-
* @param {
|
|
38
|
-
* @returns {LineNode[]}
|
|
86
|
+
* @param {import("../../types").Range} range
|
|
39
87
|
*/
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
* @type {LineNode}
|
|
49
|
-
*/
|
|
50
|
-
const lineNode = {
|
|
51
|
-
type: "Line",
|
|
52
|
-
value,
|
|
53
|
-
range: [start, start + value.length],
|
|
54
|
-
loc: {
|
|
55
|
-
start: {
|
|
56
|
-
line,
|
|
57
|
-
column: columnStart,
|
|
58
|
-
},
|
|
59
|
-
end: {
|
|
60
|
-
line,
|
|
61
|
-
column: columnStart + value.length,
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
start += value.length + 1;
|
|
67
|
-
line += 1;
|
|
68
|
-
return lineNode;
|
|
88
|
+
function shouldSkipIndentCheck(range) {
|
|
89
|
+
const overlappedTemplates = templates.filter(
|
|
90
|
+
(template) =>
|
|
91
|
+
template.isTemplate && isRangesOverlap(template.range, range)
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const isLineInTemplate = overlappedTemplates.some((template) => {
|
|
95
|
+
return template.range[0] <= range[0] && template.range[1] >= range[1];
|
|
69
96
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
97
|
+
if (isLineInTemplate) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
const isLineBeforeTemplate = overlappedTemplates.some((template) => {
|
|
101
|
+
return template.range[0] <= range[0] && template.range[1] <= range[1];
|
|
102
|
+
});
|
|
103
|
+
if (isLineBeforeTemplate) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
const isLineAfterTemplate = overlappedTemplates.some((template) => {
|
|
107
|
+
return template.range[1] <= range[0];
|
|
108
|
+
});
|
|
109
|
+
if (isLineAfterTemplate) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
node.value.split("\n").forEach((value, index) => {
|
|
116
|
+
const columnStart = index === 0 ? startCol : 0;
|
|
117
|
+
/**
|
|
118
|
+
* @type {import("../../types").Range}
|
|
119
|
+
*/
|
|
120
|
+
const range = [start, start + value.length];
|
|
121
|
+
const loc = {
|
|
122
|
+
start: {
|
|
123
|
+
line,
|
|
124
|
+
column: columnStart,
|
|
125
|
+
},
|
|
126
|
+
end: {
|
|
127
|
+
line,
|
|
128
|
+
column: columnStart + value.length,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* @type {LineNode}
|
|
133
|
+
*/
|
|
134
|
+
const lineNode = {
|
|
135
|
+
type: "Line",
|
|
136
|
+
value,
|
|
137
|
+
range,
|
|
138
|
+
loc,
|
|
139
|
+
skipIndentCheck: shouldSkipIndentCheck(range),
|
|
81
140
|
};
|
|
82
|
-
|
|
141
|
+
|
|
142
|
+
start += value.length + 1;
|
|
143
|
+
line += 1;
|
|
144
|
+
|
|
145
|
+
lineNodes.push(lineNode);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return lineNodes;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get location between two nodes.
|
|
153
|
+
* @param {BaseNode} before A node placed in before
|
|
154
|
+
* @param {BaseNode} after A node placed in after
|
|
155
|
+
* @returns {Location} location between two nodes.
|
|
156
|
+
*/
|
|
157
|
+
function getLocBetween(before, after) {
|
|
158
|
+
return {
|
|
159
|
+
start: before.loc.end,
|
|
160
|
+
end: after.loc.start,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {AttributeValueNode} node
|
|
166
|
+
* @return {boolean}
|
|
167
|
+
*/
|
|
168
|
+
function isExpressionInTemplate(node) {
|
|
169
|
+
if (node.type === NODE_TYPES.AttributeValue) {
|
|
170
|
+
return node.value.indexOf("${") === 0;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @param {AnyNode} node
|
|
177
|
+
* @returns {node is TagNode}
|
|
178
|
+
*/
|
|
179
|
+
function isTag(node) {
|
|
180
|
+
return node.type === NODE_TYPES.Tag;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @param {AnyNode} node
|
|
185
|
+
* @returns {node is CommentNode}
|
|
186
|
+
*/
|
|
187
|
+
function isComment(node) {
|
|
188
|
+
return node.type === NODE_TYPES.Comment;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @param {AnyNode} node
|
|
193
|
+
* @returns {node is TextNode}
|
|
194
|
+
*/
|
|
195
|
+
function isText(node) {
|
|
196
|
+
return node.type === NODE_TYPES.Text;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const lineBreakPattern = /\r\n|[\r\n\u2028\u2029]/u;
|
|
200
|
+
const lineEndingPattern = new RegExp(lineBreakPattern.source, "gu");
|
|
201
|
+
/**
|
|
202
|
+
* @param {string} source
|
|
203
|
+
* @returns {string[]}
|
|
204
|
+
*/
|
|
205
|
+
function codeToLines(source) {
|
|
206
|
+
return source.split(lineEndingPattern);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
*
|
|
211
|
+
* @param {AnyToken[]} tokens
|
|
212
|
+
* @returns {((CommentContentNode | TextNode)['templates'][number])[]}
|
|
213
|
+
*/
|
|
214
|
+
function getTemplateTokens(tokens) {
|
|
215
|
+
return (
|
|
216
|
+
[]
|
|
217
|
+
.concat(
|
|
218
|
+
...tokens
|
|
219
|
+
// @ts-ignore
|
|
220
|
+
.map((token) => token["templates"] || [])
|
|
221
|
+
)
|
|
222
|
+
// @ts-ignore
|
|
223
|
+
.filter((token) => token.isTemplate)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports = {
|
|
228
|
+
findAttr,
|
|
229
|
+
isAttributesEmpty,
|
|
230
|
+
isNodeTokensOnSameLine,
|
|
231
|
+
splitToLineNodes,
|
|
232
|
+
getLocBetween,
|
|
233
|
+
isExpressionInTemplate,
|
|
234
|
+
isTag,
|
|
235
|
+
isComment,
|
|
236
|
+
isText,
|
|
237
|
+
isOverlapWithTemplates,
|
|
238
|
+
codeToLines,
|
|
239
|
+
isRangesOverlap,
|
|
240
|
+
getTemplateTokens,
|
|
83
241
|
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef { import("../../types").RuleListener } RuleListener
|
|
3
|
+
* @typedef { import("../../types").Context } Context
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
shouldCheckTaggedTemplateExpression,
|
|
8
|
+
shouldCheckTemplateLiteral,
|
|
9
|
+
} = require("./settings");
|
|
10
|
+
const { parse } = require("@html-eslint/template-parser");
|
|
11
|
+
const { getSourceCode } = require("./source-code");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {Context} context
|
|
15
|
+
* @param {any} visitors
|
|
16
|
+
* @returns {RuleListener}
|
|
17
|
+
*/
|
|
18
|
+
function createTemplateVisitors(context, visitors) {
|
|
19
|
+
return {
|
|
20
|
+
TaggedTemplateExpression(node) {
|
|
21
|
+
if (shouldCheckTaggedTemplateExpression(node, context)) {
|
|
22
|
+
parse(node.quasi, getSourceCode(context), visitors);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
TemplateLiteral(node) {
|
|
26
|
+
if (shouldCheckTemplateLiteral(node, context)) {
|
|
27
|
+
parse(node, getSourceCode(context), visitors);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {Context} context
|
|
35
|
+
* @param {RuleListener} visitors
|
|
36
|
+
* @param {any} [templateVisitors]
|
|
37
|
+
* @returns {RuleListener}
|
|
38
|
+
*/
|
|
39
|
+
function createVisitors(context, visitors, templateVisitors) {
|
|
40
|
+
const tmplVisitors = createTemplateVisitors(
|
|
41
|
+
context,
|
|
42
|
+
templateVisitors || visitors
|
|
43
|
+
);
|
|
44
|
+
return {
|
|
45
|
+
...visitors,
|
|
46
|
+
...tmplVisitors,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
createVisitors,
|
|
52
|
+
};
|
package/lib/types.d.ts
CHANGED
|
@@ -19,12 +19,6 @@ export interface BaseNode {
|
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export interface ProgramNode
|
|
23
|
-
extends Omit<ESHtml.DocumentNode, "type" | "children"> {
|
|
24
|
-
type: "Program";
|
|
25
|
-
body: ESHtml.DocumentNode["children"];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
22
|
interface AttributeKeyNode extends ESHtml.AttributeKeyNode {
|
|
29
23
|
parent: AttributeNode;
|
|
30
24
|
}
|
|
@@ -35,7 +29,7 @@ interface TextNode extends ESHtml.TextNode {
|
|
|
35
29
|
|
|
36
30
|
export interface TagNode extends ESHtml.TagNode {
|
|
37
31
|
attributes: AttributeNode[];
|
|
38
|
-
parent: TagNode |
|
|
32
|
+
parent: TagNode | HTMLNode;
|
|
39
33
|
openStart: OpenTagStartNode;
|
|
40
34
|
openEnd: OpenTagEndNode;
|
|
41
35
|
close: CloseTagNode;
|
|
@@ -78,7 +72,7 @@ interface AttributeValueWrapperStartNode
|
|
|
78
72
|
|
|
79
73
|
export interface ScriptTagNode extends ESHtml.ScriptTagNode {
|
|
80
74
|
attributes: AttributeNode[];
|
|
81
|
-
parent:
|
|
75
|
+
parent: HTMLNode | TagNode;
|
|
82
76
|
openStart: OpenScriptTagStartNode;
|
|
83
77
|
openEnd: OpenScriptTagEndNode;
|
|
84
78
|
}
|
|
@@ -101,7 +95,7 @@ interface ScriptTagContentNode extends ESHtml.ScriptTagContentNode {
|
|
|
101
95
|
|
|
102
96
|
export interface StyleTagNode extends ESHtml.StyleTagNode {
|
|
103
97
|
attributes: AttributeNode[];
|
|
104
|
-
parent: TagNode |
|
|
98
|
+
parent: TagNode | HTMLNode;
|
|
105
99
|
openStart: OpenStyleTagStartNode;
|
|
106
100
|
openEnd: OpenStyleTagEndNode;
|
|
107
101
|
}
|
|
@@ -123,7 +117,7 @@ interface CloseStyleTagNode extends ESHtml.CloseStyleTagNode {
|
|
|
123
117
|
}
|
|
124
118
|
|
|
125
119
|
interface CommentNode extends ESHtml.CommentNode {
|
|
126
|
-
parent:
|
|
120
|
+
parent: HTMLNode | TagNode;
|
|
127
121
|
}
|
|
128
122
|
|
|
129
123
|
interface CommentOpenNode extends ESHtml.CommentOpenNode {
|
|
@@ -139,7 +133,7 @@ interface CommentContentNode extends ESHtml.CommentContentNode {
|
|
|
139
133
|
}
|
|
140
134
|
|
|
141
135
|
interface DoctypeNode extends ESHtml.DoctypeNode {
|
|
142
|
-
parent:
|
|
136
|
+
parent: HTMLNode;
|
|
143
137
|
}
|
|
144
138
|
|
|
145
139
|
interface DoctypeOpenNode extends ESHtml.DoctypeOpenNode {
|
|
@@ -171,10 +165,18 @@ interface DoctypeAttributeWrapperEnd
|
|
|
171
165
|
interface LineNode extends BaseNode {
|
|
172
166
|
type: "Line";
|
|
173
167
|
value: string;
|
|
168
|
+
skipIndentCheck: boolean;
|
|
174
169
|
}
|
|
175
170
|
|
|
176
|
-
|
|
177
|
-
|
|
171
|
+
type PostFix<T, S extends string> = {
|
|
172
|
+
[K in keyof T as `${K & string}${S}`]: T[K];
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export type RuleListener = BaseRuleListener &
|
|
176
|
+
PostFix<BaseRuleListener, ":exit">;
|
|
177
|
+
|
|
178
|
+
interface BaseRuleListener {
|
|
179
|
+
Document?: (node: ESHtml.DocumentNode) => void;
|
|
178
180
|
AttributeKey?: (node: AttributeKeyNode) => void;
|
|
179
181
|
Text?: (node: TextNode) => void;
|
|
180
182
|
Tag?: (node: TagNode) => void;
|
|
@@ -264,12 +266,6 @@ export interface Context extends Omit<ESLint.Rule.RuleContext, "report"> {
|
|
|
264
266
|
report(descriptor: ReportDescriptor): void;
|
|
265
267
|
}
|
|
266
268
|
|
|
267
|
-
export type ChildType<T extends BaseNode> = T extends ProgramNode
|
|
268
|
-
? T["body"][number]
|
|
269
|
-
: T extends TagNode
|
|
270
|
-
? T["children"][number]
|
|
271
|
-
: never;
|
|
272
|
-
|
|
273
269
|
export type ContentNode =
|
|
274
270
|
| CommentNode
|
|
275
271
|
| DoctypeNode
|