@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.
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
@@ -2,6 +2,7 @@
2
2
  * @typedef { import("../types").RuleFixer } RuleFixer
3
3
  * @typedef { import("../types").RuleModule } RuleModule
4
4
  * @typedef { import("../types").TagNode } TagNode
5
+ * @typedef {import("../types").RuleListener}
5
6
  * @typedef {Object} MessageId
6
7
  * @property {"closeStyleWrong"} CLOSE_STYLE_WRONG
7
8
  * @property {"newlineMissing"} NEWLINE_MISSING
@@ -9,6 +10,7 @@
9
10
  */
10
11
 
11
12
  const { RULE_CATEGORY } = require("../constants");
13
+ const { createVisitors } = require("./utils/visitors");
12
14
 
13
15
  /**
14
16
  * @type {MessageId}
@@ -63,10 +65,7 @@ module.exports = {
63
65
  : options.ifAttrsMoreThan;
64
66
  const closeStyle = options.closeStyle || "newline";
65
67
 
66
- return {
67
- /**
68
- * @param {TagNode} node
69
- */
68
+ return createVisitors(context, {
70
69
  Tag(node) {
71
70
  const shouldBeMultiline = node.attributes.length > attrMin;
72
71
 
@@ -158,6 +157,6 @@ module.exports = {
158
157
  }
159
158
  }
160
159
  },
161
- };
160
+ });
162
161
  },
163
162
  };
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * @typedef { import("../types").RuleModule } RuleModule
3
- * @typedef { import("../types").ProgramNode } ProgramNode
4
3
  * @typedef { import("../types").TagNode } TagNode
5
4
  * @typedef { import("../types").BaseNode } BaseNode
6
5
  * @typedef { import("../types").CommentNode } CommentNode
@@ -17,7 +16,8 @@
17
16
  */
18
17
 
19
18
  const { RULE_CATEGORY } = require("../constants");
20
-
19
+ const { isTag, isComment, isText } = require("./utils/node");
20
+ const { createVisitors } = require("./utils/visitors");
21
21
  const MESSAGE_IDS = {
22
22
  EXPECT_NEW_LINE_AFTER: "expectAfter",
23
23
  EXPECT_NEW_LINE_AFTER_OPEN: "expectAfterOpen",
@@ -159,7 +159,7 @@ module.exports = {
159
159
 
160
160
  const nodeShouldBeNewline = shouldBeNewline(node);
161
161
 
162
- if (node.type === `Tag` && skipTags.includes(node.name) === false) {
162
+ if (isTag(node) && skipTags.includes(node.name) === false) {
163
163
  const nodeMeta = checkSiblings(node.children);
164
164
  const nodeChildShouldBeNewline = nodeMeta.shouldBeNewline;
165
165
 
@@ -260,16 +260,13 @@ module.exports = {
260
260
  */
261
261
  function label(node, options = {}) {
262
262
  const isClose = options.isClose || false;
263
-
264
- switch (node.type) {
265
- case `Tag`:
266
- if (isClose) {
267
- return `</${node.name}>`;
268
- }
269
- return `<${node.name}>`;
270
- default:
271
- return `<${node.type}>`;
263
+ if (isTag(node)) {
264
+ if (isClose) {
265
+ return `</${node.name}>`;
266
+ }
267
+ return `<${node.name}>`;
272
268
  }
269
+ return `<${node.type}>`;
273
270
  }
274
271
 
275
272
  /**
@@ -292,23 +289,23 @@ module.exports = {
292
289
  * @param {NewlineNode} node
293
290
  */
294
291
  function shouldBeNewline(node) {
295
- switch (node.type) {
296
- case `Comment`:
297
- return /[\n\r]+/.test(node.value.value.trim());
298
- case `Tag`:
299
- return inlineTags.includes(node.name.toLowerCase()) === false;
300
- case `Text`:
301
- return /[\n\r]+/.test(node.value.trim());
302
- default:
303
- return true;
292
+ if (isComment(node)) {
293
+ return /[\n\r]+/.test(node.value.value.trim());
294
+ }
295
+ if (isTag(node)) {
296
+ return inlineTags.includes(node.name.toLowerCase()) === false;
297
+ }
298
+ if (isText(node)) {
299
+ return /[\n\r]+/.test(node.value.trim());
304
300
  }
301
+ return true;
305
302
  }
306
303
 
307
- return {
308
- Program(node) {
304
+ return createVisitors(context, {
305
+ Document(node) {
309
306
  // @ts-ignore
310
- checkSiblings(node.body);
307
+ checkSiblings(node.children);
311
308
  },
312
- };
309
+ });
313
310
  },
314
311
  };
@@ -12,7 +12,12 @@ const {
12
12
  isPascalCase,
13
13
  isKebabCase,
14
14
  } = require("./utils/naming");
15
- const { findAttr } = require("./utils/node");
15
+ const {
16
+ findAttr,
17
+ isAttributesEmpty,
18
+ isExpressionInTemplate,
19
+ } = require("./utils/node");
20
+ const { createVisitors } = require("./utils/visitors");
16
21
 
17
22
  const MESSAGE_IDS = {
18
23
  WRONG: "wrong",
@@ -81,27 +86,63 @@ module.exports = {
81
86
  ).test(name)
82
87
  : CONVENTION_CHECKERS[convention];
83
88
 
84
- return {
85
- /**
86
- * @param {TagNode | ScriptTagNode | StyleTagNode} node
87
- * @returns
88
- */
89
- [["Tag", "ScriptTag", "StyleTag"].join(",")](node) {
90
- if (!node.attributes || node.attributes.length <= 0) {
91
- return;
92
- }
93
- const idAttr = findAttr(node, "id");
94
- if (idAttr && idAttr.value && !checkNaming(idAttr.value.value)) {
95
- context.report({
96
- node: idAttr,
97
- data: {
98
- actual: idAttr.value.value,
99
- convention,
100
- },
101
- messageId: MESSAGE_IDS.WRONG,
102
- });
103
- }
89
+ /**
90
+ * @param {TagNode | ScriptTagNode | StyleTagNode} node
91
+ */
92
+ function check(node) {
93
+ if (isAttributesEmpty(node)) {
94
+ return;
95
+ }
96
+ const idAttr = findAttr(node, "id");
97
+ if (idAttr && idAttr.value && !checkNaming(idAttr.value.value)) {
98
+ context.report({
99
+ node: idAttr,
100
+ data: {
101
+ actual: idAttr.value.value,
102
+ convention,
103
+ },
104
+ messageId: MESSAGE_IDS.WRONG,
105
+ });
106
+ }
107
+ }
108
+
109
+ /**
110
+ * @param {TagNode | ScriptTagNode | StyleTagNode} node
111
+ */
112
+ function checkInTemplate(node) {
113
+ if (isAttributesEmpty(node)) {
114
+ return;
115
+ }
116
+ const idAttr = findAttr(node, "id");
117
+ if (
118
+ idAttr &&
119
+ idAttr.value &&
120
+ !isExpressionInTemplate(idAttr.value) &&
121
+ !checkNaming(idAttr.value.value)
122
+ ) {
123
+ context.report({
124
+ node: idAttr,
125
+ data: {
126
+ actual: idAttr.value.value,
127
+ convention,
128
+ },
129
+ messageId: MESSAGE_IDS.WRONG,
130
+ });
131
+ }
132
+ }
133
+
134
+ return createVisitors(
135
+ context,
136
+ {
137
+ Tag: check,
138
+ ScriptTag: check,
139
+ StyleTag: check,
104
140
  },
105
- };
141
+ {
142
+ Tag: checkInTemplate,
143
+ ScriptTag: checkInTemplate,
144
+ StyleTag: checkInTemplate,
145
+ }
146
+ );
106
147
  },
107
148
  };
@@ -13,6 +13,8 @@
13
13
 
14
14
  const { RULE_CATEGORY } = require("../constants");
15
15
  const { splitToLineNodes } = require("./utils/node");
16
+ const { getSourceCode } = require("./utils/source-code");
17
+ const { createVisitors } = require("./utils/visitors");
16
18
 
17
19
  /** @type {MessageId} */
18
20
  const MESSAGE_ID = {
@@ -60,7 +62,7 @@ module.exports = {
60
62
  },
61
63
  },
62
64
  create(context) {
63
- const sourceCode = context.getSourceCode();
65
+ const sourceCode = getSourceCode(context);
64
66
  let indentLevel = -1;
65
67
  let parentIgnoringChildCount = 0;
66
68
 
@@ -213,8 +215,7 @@ module.exports = {
213
215
  }
214
216
  }
215
217
 
216
- return {
217
- // Tag
218
+ return createVisitors(context, {
218
219
  Tag(node) {
219
220
  if (IGNORING_NODES.includes(node.name)) {
220
221
  parentIgnoringChildCount++;
@@ -253,6 +254,9 @@ module.exports = {
253
254
  indent();
254
255
  const lineNodes = splitToLineNodes(node);
255
256
  lineNodes.forEach((lineNode) => {
257
+ if (lineNode.skipIndentCheck) {
258
+ return;
259
+ }
256
260
  if (lineNode.value.trim().length) {
257
261
  checkIndent(lineNode);
258
262
  }
@@ -267,6 +271,9 @@ module.exports = {
267
271
  indent();
268
272
  const lineNodes = splitToLineNodes(node);
269
273
  lineNodes.forEach((lineNode) => {
274
+ if (lineNode.skipIndentCheck) {
275
+ return;
276
+ }
270
277
  if (lineNode.value.trim().length) {
271
278
  checkIndent(lineNode);
272
279
  }
@@ -275,6 +282,6 @@ module.exports = {
275
282
  CommentClose: checkIndent,
276
283
  "Comment:exit": unindent,
277
284
  "CommentContent:exit": unindent,
278
- };
285
+ });
279
286
  },
280
287
  };
@@ -3,11 +3,13 @@
3
3
  * @typedef { import("../types").TagNode } TagNode
4
4
  * @typedef { import("../types").StyleTagNode } StyleTagNode
5
5
  * @typedef { import("../types").ScriptTagNode } ScriptTagNode
6
+ * @typedef { import("../types").RuleListener } RuleListener
6
7
  */
7
8
 
8
9
  const { NODE_TYPES } = require("@html-eslint/parser");
9
10
  const { RULE_CATEGORY } = require("../constants");
10
11
  const SVG_CAMEL_CASE_ATTRIBUTES = require("../constants/svg-camel-case-attributes");
12
+ const { createVisitors } = require("./utils/visitors");
11
13
 
12
14
  const MESSAGE_IDS = {
13
15
  UNEXPECTED: "unexpected",
@@ -120,16 +122,13 @@ module.exports = {
120
122
  }
121
123
  }
122
124
 
123
- return {
125
+ return createVisitors(context, {
124
126
  Tag(node) {
125
127
  if (node.name.toLocaleLowerCase() === "svg") {
126
128
  enterSvg(node);
127
129
  }
128
130
  check(node);
129
131
  },
130
- /**
131
- * @param {TagNode} node
132
- */
133
132
  "Tag:exit"(node) {
134
133
  if (node.name.toLocaleLowerCase() === "svg") {
135
134
  exitSvg();
@@ -137,6 +136,6 @@ module.exports = {
137
136
  },
138
137
  StyleTag: check,
139
138
  ScriptTag: check,
140
- };
139
+ });
141
140
  },
142
141
  };
@@ -7,6 +7,7 @@
7
7
 
8
8
  const { RULE_CATEGORY } = require("../constants");
9
9
  const { findAttr } = require("./utils/node");
10
+ const { createVisitors } = require("./utils/visitors");
10
11
 
11
12
  const MESSAGE_IDS = {
12
13
  UNEXPECTED: "unexpected",
@@ -48,24 +49,27 @@ module.exports = {
48
49
  },
49
50
 
50
51
  create(context) {
51
- return {
52
- /**
53
- *
54
- * @param {TagNode | ScriptTagNode | StyleTagNode} node
55
- */
56
- [["Tag", "ScriptTag", "StyleTag"].join(",")](node) {
57
- const roleAttr = findAttr(node, "role");
58
- if (
59
- roleAttr &&
60
- roleAttr.value &&
61
- ABSTRACT_ROLE_SET.has(roleAttr.value.value)
62
- ) {
63
- context.report({
64
- messageId: MESSAGE_IDS.UNEXPECTED,
65
- node: roleAttr,
66
- });
67
- }
68
- },
69
- };
52
+ /**
53
+ * @param {TagNode | ScriptTagNode | StyleTagNode} node
54
+ */
55
+ function check(node) {
56
+ const roleAttr = findAttr(node, "role");
57
+ if (
58
+ roleAttr &&
59
+ roleAttr.value &&
60
+ ABSTRACT_ROLE_SET.has(roleAttr.value.value)
61
+ ) {
62
+ context.report({
63
+ messageId: MESSAGE_IDS.UNEXPECTED,
64
+ node: roleAttr,
65
+ });
66
+ }
67
+ }
68
+
69
+ return createVisitors(context, {
70
+ Tag: check,
71
+ ScriptTag: check,
72
+ StyleTag: check,
73
+ });
70
74
  },
71
75
  };
@@ -7,6 +7,7 @@
7
7
 
8
8
  const { RULE_CATEGORY } = require("../constants");
9
9
  const { findAttr } = require("./utils/node");
10
+ const { createVisitors } = require("./utils/visitors");
10
11
 
11
12
  const MESSAGE_IDS = {
12
13
  UNEXPECTED: "unexpected",
@@ -33,19 +34,22 @@ module.exports = {
33
34
  },
34
35
 
35
36
  create(context) {
36
- return {
37
- /**
38
- * @param {TagNode | ScriptTagNode | StyleTagNode} node
39
- */
40
- [["Tag", "ScriptTag", "StyleTag"].join(",")](node) {
41
- const accessKeyAttr = findAttr(node, "accesskey");
42
- if (accessKeyAttr) {
43
- context.report({
44
- node: accessKeyAttr,
45
- messageId: MESSAGE_IDS.UNEXPECTED,
46
- });
47
- }
48
- },
49
- };
37
+ /**
38
+ * @param {TagNode | ScriptTagNode | StyleTagNode} node
39
+ */
40
+ function check(node) {
41
+ const accessKeyAttr = findAttr(node, "accesskey");
42
+ if (accessKeyAttr) {
43
+ context.report({
44
+ node: accessKeyAttr,
45
+ messageId: MESSAGE_IDS.UNEXPECTED,
46
+ });
47
+ }
48
+ }
49
+ return createVisitors(context, {
50
+ Tag: check,
51
+ ScriptTag: check,
52
+ StyleTag: check,
53
+ });
50
54
  },
51
55
  };
@@ -4,6 +4,7 @@
4
4
 
5
5
  const { RULE_CATEGORY } = require("../constants");
6
6
  const { findAttr } = require("./utils/node");
7
+ const { createVisitors } = require("./utils/visitors");
7
8
 
8
9
  const MESSAGE_IDS = {
9
10
  UNEXPECTED: "unexpected",
@@ -31,24 +32,29 @@ module.exports = {
31
32
  },
32
33
 
33
34
  create(context) {
34
- return {
35
+ return createVisitors(context, {
35
36
  Tag(node) {
36
37
  if (node.name !== "body") {
37
38
  return;
38
39
  }
39
40
  const ariaHiddenAttr = findAttr(node, "aria-hidden");
40
- if (ariaHiddenAttr) {
41
- if (
42
- (ariaHiddenAttr.value && ariaHiddenAttr.value.value !== "false") ||
43
- !ariaHiddenAttr.value
44
- ) {
45
- context.report({
46
- node: ariaHiddenAttr,
47
- messageId: MESSAGE_IDS.UNEXPECTED,
48
- });
49
- }
41
+ if (!ariaHiddenAttr) {
42
+ return;
43
+ }
44
+ if (ariaHiddenAttr.value && ariaHiddenAttr.value.templates.length) {
45
+ return;
46
+ }
47
+
48
+ if (
49
+ (ariaHiddenAttr.value && ariaHiddenAttr.value.value !== "false") ||
50
+ !ariaHiddenAttr.value
51
+ ) {
52
+ context.report({
53
+ node: ariaHiddenAttr,
54
+ messageId: MESSAGE_IDS.UNEXPECTED,
55
+ });
50
56
  }
51
57
  },
52
- };
58
+ });
53
59
  },
54
60
  };
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  const { RULE_CATEGORY } = require("../constants");
9
+ const { createVisitors } = require("./utils/visitors");
9
10
 
10
11
  const MESSAGE_IDS = {
11
12
  DUPLICATE_ATTRS: "duplicateAttrs",
@@ -33,28 +34,31 @@ module.exports = {
33
34
  },
34
35
 
35
36
  create(context) {
36
- return {
37
- /**
38
- * @param {TagNode | StyleTagNode | ScriptTagNode} node
39
- */
40
- [["Tag", "StyleTag", "ScriptTag"].join(",")](node) {
41
- if (Array.isArray(node.attributes)) {
42
- const attrsSet = new Set();
43
- node.attributes.forEach((attr) => {
44
- if (attr.key && attrsSet.has(attr.key.value)) {
45
- context.report({
46
- node: attr,
47
- data: {
48
- attrName: attr.key.value,
49
- },
50
- messageId: MESSAGE_IDS.DUPLICATE_ATTRS,
51
- });
52
- } else {
53
- attrsSet.add(attr.key.value);
54
- }
55
- });
56
- }
57
- },
58
- };
37
+ /**
38
+ * @param {TagNode | StyleTagNode | ScriptTagNode} node
39
+ */
40
+ function check(node) {
41
+ if (Array.isArray(node.attributes)) {
42
+ const attrsSet = new Set();
43
+ node.attributes.forEach((attr) => {
44
+ if (attr.key && attrsSet.has(attr.key.value)) {
45
+ context.report({
46
+ node: attr,
47
+ data: {
48
+ attrName: attr.key.value,
49
+ },
50
+ messageId: MESSAGE_IDS.DUPLICATE_ATTRS,
51
+ });
52
+ } else {
53
+ attrsSet.add(attr.key.value);
54
+ }
55
+ });
56
+ }
57
+ }
58
+ return createVisitors(context, {
59
+ Tag: check,
60
+ StyleTag: check,
61
+ ScriptTag: check,
62
+ });
59
63
  },
60
64
  };
@@ -3,10 +3,17 @@
3
3
  * @typedef { import("../types").TagNode } TagNode
4
4
  * @typedef { import("../types").StyleTagNode } StyleTagNode
5
5
  * @typedef { import("../types").ScriptTagNode } ScriptTagNode
6
+ * @typedef { import("es-html-parser").AttributeValueNode } AttributeValueNode
6
7
  */
7
8
 
9
+ const { parse } = require("@html-eslint/template-parser");
8
10
  const { RULE_CATEGORY } = require("../constants");
9
11
  const { findAttr } = require("./utils/node");
12
+ const {
13
+ shouldCheckTaggedTemplateExpression,
14
+ shouldCheckTemplateLiteral,
15
+ } = require("./utils/settings");
16
+ const { getSourceCode } = require("./utils/source-code");
10
17
 
11
18
  const MESSAGE_IDS = {
12
19
  DUPLICATE_ID: "duplicateId",
@@ -33,37 +40,71 @@ module.exports = {
33
40
  },
34
41
 
35
42
  create(context) {
36
- const IdAttrsMap = new Map();
37
- return {
43
+ const htmlIdAttrsMap = new Map();
44
+ /**
45
+ * @param {Map<string, AttributeValueNode[]>} map
46
+ */
47
+ function createTagVisitor(map) {
38
48
  /**
39
- * @param {TagNode | ScriptTagNode | StyleTagNode} node
40
- * @returns
49
+ * @param {TagNode} node
41
50
  */
42
- Tag(node) {
51
+ return function (node) {
43
52
  if (!node.attributes || node.attributes.length <= 0) {
44
53
  return;
45
54
  }
46
55
  const idAttr = findAttr(node, "id");
47
56
  if (idAttr && idAttr.value) {
48
- if (!IdAttrsMap.has(idAttr.value.value)) {
49
- IdAttrsMap.set(idAttr.value.value, []);
57
+ if (!map.has(idAttr.value.value)) {
58
+ map.set(idAttr.value.value, []);
59
+ }
60
+ const nodes = map.get(idAttr.value.value);
61
+ if (nodes) {
62
+ nodes.push(idAttr.value);
50
63
  }
51
- const nodes = IdAttrsMap.get(idAttr.value.value);
52
- nodes.push(idAttr.value);
53
64
  }
54
- },
55
- "Program:exit"() {
56
- IdAttrsMap.forEach((attrs) => {
57
- if (Array.isArray(attrs) && attrs.length > 1) {
58
- attrs.forEach((attr) => {
59
- context.report({
60
- node: attr,
61
- data: { id: attr.value },
62
- messageId: MESSAGE_IDS.DUPLICATE_ID,
63
- });
65
+ };
66
+ }
67
+
68
+ /**
69
+ *
70
+ * @param {Map<string, AttributeValueNode[]>} map
71
+ */
72
+ function report(map) {
73
+ map.forEach((attrs) => {
74
+ if (Array.isArray(attrs) && attrs.length > 1) {
75
+ attrs.forEach((attr) => {
76
+ context.report({
77
+ node: attr,
78
+ data: { id: attr.value },
79
+ messageId: MESSAGE_IDS.DUPLICATE_ID,
64
80
  });
65
- }
66
- });
81
+ });
82
+ }
83
+ });
84
+ }
85
+
86
+ return {
87
+ Tag: createTagVisitor(htmlIdAttrsMap),
88
+ "Document:exit"() {
89
+ report(htmlIdAttrsMap);
90
+ },
91
+ TaggedTemplateExpression(node) {
92
+ const idAttrsMap = new Map();
93
+ if (shouldCheckTaggedTemplateExpression(node, context)) {
94
+ parse(node.quasi, getSourceCode(context), {
95
+ Tag: createTagVisitor(idAttrsMap),
96
+ });
97
+ }
98
+ report(idAttrsMap);
99
+ },
100
+ TemplateLiteral(node) {
101
+ const idAttrsMap = new Map();
102
+ if (shouldCheckTemplateLiteral(node, context)) {
103
+ parse(node, getSourceCode(context), {
104
+ Tag: createTagVisitor(idAttrsMap),
105
+ });
106
+ }
107
+ report(idAttrsMap);
67
108
  },
68
109
  };
69
110
  },