@html-eslint/eslint-plugin 0.28.0-alpha.0 → 0.28.0-alpha.3

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.
Files changed (128) hide show
  1. package/lib/rules/attrs-newline.js +4 -5
  2. package/lib/rules/element-newline.js +22 -25
  3. package/lib/rules/id-naming-convention.js +63 -22
  4. package/lib/rules/indent.js +11 -4
  5. package/lib/rules/lowercase.js +4 -5
  6. package/lib/rules/no-abstract-roles.js +23 -19
  7. package/lib/rules/no-accesskey-attrs.js +18 -14
  8. package/lib/rules/no-aria-hidden-body.js +18 -12
  9. package/lib/rules/no-duplicate-attrs.js +27 -23
  10. package/lib/rules/no-duplicate-id.js +62 -21
  11. package/lib/rules/no-extra-spacing-attrs.js +104 -100
  12. package/lib/rules/no-extra-spacing-text.js +66 -26
  13. package/lib/rules/no-inline-styles.js +3 -25
  14. package/lib/rules/no-multiple-empty-lines.js +94 -42
  15. package/lib/rules/no-multiple-h1.js +1 -1
  16. package/lib/rules/no-obsolete-tags.js +3 -2
  17. package/lib/rules/no-positive-tabindex.js +24 -18
  18. package/lib/rules/no-restricted-attr-values.js +51 -47
  19. package/lib/rules/no-restricted-attrs.js +50 -45
  20. package/lib/rules/no-script-style-type.js +3 -2
  21. package/lib/rules/no-skip-heading-levels.js +1 -1
  22. package/lib/rules/no-target-blank.js +7 -2
  23. package/lib/rules/no-trailing-spaces.js +95 -39
  24. package/lib/rules/quotes.js +12 -8
  25. package/lib/rules/require-attrs.js +28 -20
  26. package/lib/rules/require-button-type.js +7 -3
  27. package/lib/rules/require-closing-tags.js +3 -2
  28. package/lib/rules/require-frame-title.js +3 -2
  29. package/lib/rules/require-img-alt.js +3 -2
  30. package/lib/rules/require-lang.js +3 -2
  31. package/lib/rules/require-li-container.js +1 -1
  32. package/lib/rules/require-meta-charset.js +5 -9
  33. package/lib/rules/require-meta-description.js +5 -5
  34. package/lib/rules/require-meta-viewport.js +5 -5
  35. package/lib/rules/require-open-graph-protocol.js +5 -5
  36. package/lib/rules/require-title.js +8 -7
  37. package/lib/rules/sort-attrs.js +5 -3
  38. package/lib/rules/utils/node.js +226 -68
  39. package/lib/rules/utils/visitors.js +52 -0
  40. package/lib/types.d.ts +15 -19
  41. package/package.json +9 -6
  42. package/types/configs/recommended.d.ts +1 -1
  43. package/types/constants/rule-category.d.ts +4 -4
  44. package/types/index.d.ts.map +1 -1
  45. package/types/rules/attrs-newline.d.ts +7 -4
  46. package/types/rules/attrs-newline.d.ts.map +1 -1
  47. package/types/rules/element-newline.d.ts +13 -11
  48. package/types/rules/element-newline.d.ts.map +1 -1
  49. package/types/rules/id-naming-convention.d.ts +7 -4
  50. package/types/rules/id-naming-convention.d.ts.map +1 -1
  51. package/types/rules/indent.d.ts +10 -7
  52. package/types/rules/indent.d.ts.map +1 -1
  53. package/types/rules/lowercase.d.ts +8 -4
  54. package/types/rules/lowercase.d.ts.map +1 -1
  55. package/types/rules/no-abstract-roles.d.ts +7 -4
  56. package/types/rules/no-abstract-roles.d.ts.map +1 -1
  57. package/types/rules/no-accesskey-attrs.d.ts +7 -4
  58. package/types/rules/no-accesskey-attrs.d.ts.map +1 -1
  59. package/types/rules/no-aria-hidden-body.d.ts +4 -1
  60. package/types/rules/no-aria-hidden-body.d.ts.map +1 -1
  61. package/types/rules/no-duplicate-attrs.d.ts +7 -4
  62. package/types/rules/no-duplicate-attrs.d.ts.map +1 -1
  63. package/types/rules/no-duplicate-id.d.ts +8 -4
  64. package/types/rules/no-duplicate-id.d.ts.map +1 -1
  65. package/types/rules/no-extra-spacing-attrs.d.ts +15 -12
  66. package/types/rules/no-extra-spacing-attrs.d.ts.map +1 -1
  67. package/types/rules/no-extra-spacing-text.d.ts +11 -5
  68. package/types/rules/no-extra-spacing-text.d.ts.map +1 -1
  69. package/types/rules/no-inline-styles.d.ts +5 -2
  70. package/types/rules/no-inline-styles.d.ts.map +1 -1
  71. package/types/rules/no-multiple-empty-lines.d.ts +8 -2
  72. package/types/rules/no-multiple-empty-lines.d.ts.map +1 -1
  73. package/types/rules/no-multiple-h1.d.ts +5 -2
  74. package/types/rules/no-multiple-h1.d.ts.map +1 -1
  75. package/types/rules/no-non-scalable-viewport.d.ts +4 -1
  76. package/types/rules/no-non-scalable-viewport.d.ts.map +1 -1
  77. package/types/rules/no-obsolete-tags.d.ts +4 -1
  78. package/types/rules/no-obsolete-tags.d.ts.map +1 -1
  79. package/types/rules/no-positive-tabindex.d.ts +7 -4
  80. package/types/rules/no-positive-tabindex.d.ts.map +1 -1
  81. package/types/rules/no-restricted-attr-values.d.ts +9 -6
  82. package/types/rules/no-restricted-attr-values.d.ts.map +1 -1
  83. package/types/rules/no-restricted-attrs.d.ts +9 -6
  84. package/types/rules/no-restricted-attrs.d.ts.map +1 -1
  85. package/types/rules/no-script-style-type.d.ts +7 -4
  86. package/types/rules/no-script-style-type.d.ts.map +1 -1
  87. package/types/rules/no-skip-heading-levels.d.ts +5 -2
  88. package/types/rules/no-skip-heading-levels.d.ts.map +1 -1
  89. package/types/rules/no-target-blank.d.ts +4 -1
  90. package/types/rules/no-target-blank.d.ts.map +1 -1
  91. package/types/rules/no-trailing-spaces.d.ts +6 -1
  92. package/types/rules/no-trailing-spaces.d.ts.map +1 -1
  93. package/types/rules/quotes.d.ts +9 -6
  94. package/types/rules/quotes.d.ts.map +1 -1
  95. package/types/rules/require-attrs.d.ts +7 -4
  96. package/types/rules/require-attrs.d.ts.map +1 -1
  97. package/types/rules/require-button-type.d.ts +4 -1
  98. package/types/rules/require-button-type.d.ts.map +1 -1
  99. package/types/rules/require-closing-tags.d.ts +5 -2
  100. package/types/rules/require-closing-tags.d.ts.map +1 -1
  101. package/types/rules/require-doctype.d.ts +4 -1
  102. package/types/rules/require-doctype.d.ts.map +1 -1
  103. package/types/rules/require-frame-title.d.ts +4 -1
  104. package/types/rules/require-frame-title.d.ts.map +1 -1
  105. package/types/rules/require-img-alt.d.ts +5 -2
  106. package/types/rules/require-img-alt.d.ts.map +1 -1
  107. package/types/rules/require-lang.d.ts +4 -1
  108. package/types/rules/require-lang.d.ts.map +1 -1
  109. package/types/rules/require-li-container.d.ts +4 -1
  110. package/types/rules/require-li-container.d.ts.map +1 -1
  111. package/types/rules/require-meta-charset.d.ts +6 -2
  112. package/types/rules/require-meta-charset.d.ts.map +1 -1
  113. package/types/rules/require-meta-description.d.ts +6 -2
  114. package/types/rules/require-meta-description.d.ts.map +1 -1
  115. package/types/rules/require-meta-viewport.d.ts +6 -2
  116. package/types/rules/require-meta-viewport.d.ts.map +1 -1
  117. package/types/rules/require-open-graph-protocol.d.ts +6 -2
  118. package/types/rules/require-open-graph-protocol.d.ts.map +1 -1
  119. package/types/rules/require-title.d.ts +7 -3
  120. package/types/rules/require-title.d.ts.map +1 -1
  121. package/types/rules/sort-attrs.d.ts +7 -4
  122. package/types/rules/sort-attrs.d.ts.map +1 -1
  123. package/types/rules/utils/array.d.ts.map +1 -1
  124. package/types/rules/utils/naming.d.ts.map +1 -1
  125. package/types/rules/utils/node.d.ts +65 -12
  126. package/types/rules/utils/node.d.ts.map +1 -1
  127. package/types/rules/utils/visitors.d.ts +10 -0
  128. 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.Program) {
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("../types").TagNode } TagNode
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 { import("../types").ChildType<TagNode>} node
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("../types").TagNode } TagNode
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 {import("../types").ChildType<TagNode>} node
16
+ * @param {AnyNode} node
17
17
  * @returns {node is TagNode}
18
18
  */
19
19
  function isMetaTagNode(node) {
20
- return node.type === NODE_TYPES.Tag && node.name === "meta";
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("../types").TagNode } TagNode
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 {import("../types").ChildType<TagNode>} node
17
+ * @param {AnyNode} node
18
18
  * @returns {node is TagNode}
19
19
  */
20
20
  function isMetaViewport(node) {
21
- if (node.type === NODE_TYPES.Tag && node.name === "meta") {
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("../types").TagNode } TagNode
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 {import("../types").ChildType<TagNode>} node
73
+ * @param {AnyNode} node
74
74
  * @returns {node is TagNode}
75
75
  */
76
76
  function isOgpMeta(node) {
77
- const isMeta = node.type === NODE_TYPES.Tag && node.name === "meta";
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("../types").TagNode } TagNode
4
- * @typedef { import("../types").TextNode } TextNode
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 {import("../types").ChildType<TagNode>} node
18
+ * @param {AnyNode} node
18
19
  * @returns {node is TagNode}
19
20
  */
20
21
  function isTitle(node) {
21
- return node.type === NODE_TYPES.Tag && node.name === "title";
22
+ return isTag(node) && node.name === "title";
22
23
  }
23
24
 
24
25
  /**
25
- * @param {import("../types").ChildType<TagNode>} node
26
+ * @param {AnyNode} node
26
27
  * @returns {node is TextNode}
27
28
  */
28
29
  function isNonEmptyText(node) {
29
- return node.type === NODE_TYPES.Text && node.value.trim().length > 0;
30
+ return isText(node) && node.value.trim().length > 0;
30
31
  }
31
32
 
32
33
  /**
@@ -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 = context.getSourceCode();
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
  };
@@ -1,83 +1,241 @@
1
1
  /**
2
- * @typedef { import("../../types").TagNode } TagNode
3
- * @typedef { import("../../types").ScriptTagNode } ScriptTagNode
4
- * @typedef { import("../../types").StyleTagNode } StyleTagNode
5
- * @typedef { import("../../types").AttributeNode } AttributeNode
6
- * @typedef { import("../../types").AnyNode } AnyNode
7
- * @typedef { import("../../types").TextNode } TextNode
8
- * @typedef { import("../../types").CommentContentNode } CommentContentNode
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
- module.exports = {
15
- /**
16
- * @param {TagNode | ScriptTagNode | StyleTagNode} node
17
- * @param {string} key
18
- * @returns {AttributeNode | undefined}
19
- */
20
- findAttr(node, key) {
21
- return node.attributes.find(
22
- (attr) => attr.key && attr.key.value.toLowerCase() === key.toLowerCase()
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] < rangeB[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
- * Checks whether a node's all tokens are on the same line or not.
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
- isNodeTokensOnSameLine(node) {
32
- return node.loc.start.line === node.loc.end.line;
33
- },
34
-
82
+ const lineNodes = [];
83
+ const templates = node.templates || [];
35
84
  /**
36
85
  *
37
- * @param {TextNode | CommentContentNode} node
38
- * @returns {LineNode[]}
86
+ * @param {import("../../types").Range} range
39
87
  */
40
- splitToLineNodes(node) {
41
- let start = node.range[0];
42
- let line = node.loc.start.line;
43
- const startCol = node.loc.start.column;
44
-
45
- return node.value.split("\n").map((value, index) => {
46
- const columnStart = index === 0 ? startCol : 0;
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
- * Get location between two nodes.
73
- * @param {BaseNode} before A node placed in before
74
- * @param {BaseNode} after A node placed in after
75
- * @returns {Location} location between two nodes.
76
- */
77
- getLocBetween(before, after) {
78
- return {
79
- start: before.loc.end,
80
- end: after.loc.start,
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 | ProgramNode;
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: ProgramNode | TagNode;
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 | ProgramNode;
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: ProgramNode | TagNode;
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: ProgramNode;
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
- interface RuleListener {
177
- Program?: (node: ProgramNode) => void;
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