@html-eslint/eslint-plugin 0.27.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 (134) 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 +4 -3
  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/settings.js +88 -0
  40. package/lib/rules/utils/source-code.js +14 -0
  41. package/lib/rules/utils/visitors.js +52 -0
  42. package/lib/types.d.ts +34 -22
  43. package/package.json +11 -6
  44. package/types/configs/recommended.d.ts +1 -1
  45. package/types/constants/rule-category.d.ts +4 -4
  46. package/types/index.d.ts.map +1 -1
  47. package/types/rules/attrs-newline.d.ts +7 -4
  48. package/types/rules/attrs-newline.d.ts.map +1 -1
  49. package/types/rules/element-newline.d.ts +13 -11
  50. package/types/rules/element-newline.d.ts.map +1 -1
  51. package/types/rules/id-naming-convention.d.ts +7 -4
  52. package/types/rules/id-naming-convention.d.ts.map +1 -1
  53. package/types/rules/indent.d.ts +10 -7
  54. package/types/rules/indent.d.ts.map +1 -1
  55. package/types/rules/lowercase.d.ts +8 -4
  56. package/types/rules/lowercase.d.ts.map +1 -1
  57. package/types/rules/no-abstract-roles.d.ts +7 -4
  58. package/types/rules/no-abstract-roles.d.ts.map +1 -1
  59. package/types/rules/no-accesskey-attrs.d.ts +7 -4
  60. package/types/rules/no-accesskey-attrs.d.ts.map +1 -1
  61. package/types/rules/no-aria-hidden-body.d.ts +4 -1
  62. package/types/rules/no-aria-hidden-body.d.ts.map +1 -1
  63. package/types/rules/no-duplicate-attrs.d.ts +7 -4
  64. package/types/rules/no-duplicate-attrs.d.ts.map +1 -1
  65. package/types/rules/no-duplicate-id.d.ts +8 -4
  66. package/types/rules/no-duplicate-id.d.ts.map +1 -1
  67. package/types/rules/no-extra-spacing-attrs.d.ts +15 -12
  68. package/types/rules/no-extra-spacing-attrs.d.ts.map +1 -1
  69. package/types/rules/no-extra-spacing-text.d.ts +11 -5
  70. package/types/rules/no-extra-spacing-text.d.ts.map +1 -1
  71. package/types/rules/no-inline-styles.d.ts +5 -1
  72. package/types/rules/no-inline-styles.d.ts.map +1 -1
  73. package/types/rules/no-multiple-empty-lines.d.ts +8 -2
  74. package/types/rules/no-multiple-empty-lines.d.ts.map +1 -1
  75. package/types/rules/no-multiple-h1.d.ts +5 -2
  76. package/types/rules/no-multiple-h1.d.ts.map +1 -1
  77. package/types/rules/no-non-scalable-viewport.d.ts +4 -1
  78. package/types/rules/no-non-scalable-viewport.d.ts.map +1 -1
  79. package/types/rules/no-obsolete-tags.d.ts +4 -1
  80. package/types/rules/no-obsolete-tags.d.ts.map +1 -1
  81. package/types/rules/no-positive-tabindex.d.ts +7 -4
  82. package/types/rules/no-positive-tabindex.d.ts.map +1 -1
  83. package/types/rules/no-restricted-attr-values.d.ts +9 -6
  84. package/types/rules/no-restricted-attr-values.d.ts.map +1 -1
  85. package/types/rules/no-restricted-attrs.d.ts +9 -6
  86. package/types/rules/no-restricted-attrs.d.ts.map +1 -1
  87. package/types/rules/no-script-style-type.d.ts +7 -4
  88. package/types/rules/no-script-style-type.d.ts.map +1 -1
  89. package/types/rules/no-skip-heading-levels.d.ts +5 -2
  90. package/types/rules/no-skip-heading-levels.d.ts.map +1 -1
  91. package/types/rules/no-target-blank.d.ts +4 -1
  92. package/types/rules/no-target-blank.d.ts.map +1 -1
  93. package/types/rules/no-trailing-spaces.d.ts +6 -1
  94. package/types/rules/no-trailing-spaces.d.ts.map +1 -1
  95. package/types/rules/quotes.d.ts +9 -6
  96. package/types/rules/quotes.d.ts.map +1 -1
  97. package/types/rules/require-attrs.d.ts +7 -4
  98. package/types/rules/require-attrs.d.ts.map +1 -1
  99. package/types/rules/require-button-type.d.ts +4 -1
  100. package/types/rules/require-button-type.d.ts.map +1 -1
  101. package/types/rules/require-closing-tags.d.ts +5 -2
  102. package/types/rules/require-closing-tags.d.ts.map +1 -1
  103. package/types/rules/require-doctype.d.ts +4 -1
  104. package/types/rules/require-doctype.d.ts.map +1 -1
  105. package/types/rules/require-frame-title.d.ts +4 -1
  106. package/types/rules/require-frame-title.d.ts.map +1 -1
  107. package/types/rules/require-img-alt.d.ts +5 -2
  108. package/types/rules/require-img-alt.d.ts.map +1 -1
  109. package/types/rules/require-lang.d.ts +4 -1
  110. package/types/rules/require-lang.d.ts.map +1 -1
  111. package/types/rules/require-li-container.d.ts +4 -1
  112. package/types/rules/require-li-container.d.ts.map +1 -1
  113. package/types/rules/require-meta-charset.d.ts +6 -2
  114. package/types/rules/require-meta-charset.d.ts.map +1 -1
  115. package/types/rules/require-meta-description.d.ts +6 -2
  116. package/types/rules/require-meta-description.d.ts.map +1 -1
  117. package/types/rules/require-meta-viewport.d.ts +6 -2
  118. package/types/rules/require-meta-viewport.d.ts.map +1 -1
  119. package/types/rules/require-open-graph-protocol.d.ts +6 -2
  120. package/types/rules/require-open-graph-protocol.d.ts.map +1 -1
  121. package/types/rules/require-title.d.ts +7 -3
  122. package/types/rules/require-title.d.ts.map +1 -1
  123. package/types/rules/sort-attrs.d.ts +7 -4
  124. package/types/rules/sort-attrs.d.ts.map +1 -1
  125. package/types/rules/utils/array.d.ts.map +1 -1
  126. package/types/rules/utils/naming.d.ts.map +1 -1
  127. package/types/rules/utils/node.d.ts +65 -12
  128. package/types/rules/utils/node.d.ts.map +1 -1
  129. package/types/rules/utils/settings.d.ts +19 -0
  130. package/types/rules/utils/settings.d.ts.map +1 -0
  131. package/types/rules/utils/source-code.d.ts +9 -0
  132. package/types/rules/utils/source-code.d.ts.map +1 -0
  133. package/types/rules/utils/visitors.d.ts +10 -0
  134. 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,88 @@
1
+ /**
2
+ * @typedef {import("../../types").MaybeHTMLSettings} MaybeHTMLSettings
3
+ * @typedef {import("../../types").HTMLSettings} HTMLSettings
4
+ * @typedef {import("../../types").Context} Context
5
+ * @typedef {import("estree").TaggedTemplateExpression} TaggedTemplateExpression
6
+ * @typedef {import("estree").TemplateLiteral} TemplateLiteral
7
+ */
8
+
9
+ const { getSourceCode } = require("../utils/source-code");
10
+
11
+ const DEFAULT_SETTINGS = {
12
+ templateLiterals: {
13
+ tags: ["^html$"],
14
+ comments: ["^\\s*html\\s*$"],
15
+ },
16
+ };
17
+
18
+ /**
19
+ * @type {HTMLSettings | null}
20
+ */
21
+ let cachedSettings = null;
22
+
23
+ /**
24
+ * @param {{ html?: MaybeHTMLSettings }} settings
25
+ * @returns {HTMLSettings}
26
+ */
27
+ function getSettings(settings) {
28
+ const tags =
29
+ (settings &&
30
+ settings.html &&
31
+ settings.html.templateLiterals &&
32
+ settings.html.templateLiterals.tags) ||
33
+ DEFAULT_SETTINGS.templateLiterals.tags;
34
+
35
+ const comments =
36
+ (settings &&
37
+ settings.html &&
38
+ settings.html.templateLiterals &&
39
+ settings.html.templateLiterals.comments) ||
40
+ DEFAULT_SETTINGS.templateLiterals.comments;
41
+
42
+ if (cachedSettings) {
43
+ return cachedSettings;
44
+ }
45
+ cachedSettings = {
46
+ templateLiterals: {
47
+ tags: tags.map((tag) => new RegExp(tag, "u")),
48
+ comments: comments.map((comment) => new RegExp(comment, "u")),
49
+ },
50
+ };
51
+ return cachedSettings;
52
+ }
53
+
54
+ /**
55
+ * @param {TaggedTemplateExpression} node
56
+ * @param {Context} context
57
+ * @returns {boolean}
58
+ */
59
+ function shouldCheckTaggedTemplateExpression(node, context) {
60
+ const { templateLiterals } = getSettings(context.settings);
61
+ const tags = templateLiterals.tags;
62
+ const tagNode = node.tag;
63
+ return !!(
64
+ tagNode.type === "Identifier" && tags.some((tag) => tag.test(tagNode.name))
65
+ );
66
+ }
67
+
68
+ /**
69
+ *
70
+ * @param {TemplateLiteral} node
71
+ * @param {Context} context
72
+ * @returns {boolean}
73
+ */
74
+ function shouldCheckTemplateLiteral(node, context) {
75
+ const sourceCode = getSourceCode(context);
76
+ const { templateLiterals } = getSettings(context.settings);
77
+ const comments = sourceCode.getCommentsBefore(node);
78
+ const last = comments[comments.length - 1];
79
+ return !!(
80
+ last &&
81
+ templateLiterals.comments.some((comment) => comment.test(last.value))
82
+ );
83
+ }
84
+
85
+ module.exports = {
86
+ shouldCheckTemplateLiteral,
87
+ shouldCheckTaggedTemplateExpression,
88
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @typedef {import("../../types").Context} Context
3
+ */
4
+
5
+ /**
6
+ * @param {Context} context
7
+ */
8
+ function getSourceCode(context) {
9
+ return context.sourceCode || context.getSourceCode();
10
+ }
11
+
12
+ module.exports = {
13
+ getSourceCode,
14
+ };
@@ -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
+ };