@html-eslint/eslint-plugin 0.41.0 → 0.43.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 (195) hide show
  1. package/lib/configs/recommended.js +1 -0
  2. package/lib/data/entities.json +2299 -0
  3. package/lib/languages/html-language.js +3 -3
  4. package/lib/languages/html-source-code.js +9 -11
  5. package/lib/languages/html-traversal-step.js +4 -3
  6. package/lib/rules/attrs-newline.js +3 -4
  7. package/lib/rules/element-newline.js +4 -14
  8. package/lib/rules/id-naming-convention.js +3 -6
  9. package/lib/rules/indent/indent-level.js +1 -1
  10. package/lib/rules/indent/indent.js +18 -23
  11. package/lib/rules/index.js +8 -0
  12. package/lib/rules/lowercase.js +11 -10
  13. package/lib/rules/max-element-depth.js +4 -6
  14. package/lib/rules/no-abstract-roles.js +3 -5
  15. package/lib/rules/no-accesskey-attrs.js +3 -5
  16. package/lib/rules/no-aria-hidden-body.js +2 -2
  17. package/lib/rules/no-aria-hidden-on-focusable.js +118 -0
  18. package/lib/rules/no-duplicate-attrs.js +3 -7
  19. package/lib/rules/no-duplicate-class.js +3 -6
  20. package/lib/rules/no-duplicate-id.js +6 -9
  21. package/lib/rules/no-duplicate-in-head.js +188 -0
  22. package/lib/rules/no-empty-headings.js +121 -0
  23. package/lib/rules/no-extra-spacing-attrs.js +4 -14
  24. package/lib/rules/no-extra-spacing-text.js +4 -10
  25. package/lib/rules/no-heading-inside-button.js +2 -2
  26. package/lib/rules/no-inline-styles.js +2 -2
  27. package/lib/rules/no-invalid-entity.js +107 -0
  28. package/lib/rules/no-invalid-role.js +2 -2
  29. package/lib/rules/no-multiple-empty-lines.js +10 -11
  30. package/lib/rules/no-multiple-h1.js +3 -3
  31. package/lib/rules/no-nested-interactive.js +3 -3
  32. package/lib/rules/no-non-scalable-viewport.js +2 -2
  33. package/lib/rules/no-obsolete-tags.js +2 -2
  34. package/lib/rules/no-positive-tabindex.js +3 -5
  35. package/lib/rules/no-restricted-attr-values.js +4 -6
  36. package/lib/rules/no-restricted-attrs.js +4 -6
  37. package/lib/rules/no-script-style-type.js +3 -5
  38. package/lib/rules/no-skip-heading-levels.js +3 -3
  39. package/lib/rules/no-target-blank.js +2 -2
  40. package/lib/rules/no-trailing-spaces.js +10 -9
  41. package/lib/rules/prefer-https.js +3 -6
  42. package/lib/rules/quotes.js +25 -10
  43. package/lib/rules/require-attrs.js +3 -8
  44. package/lib/rules/require-button-type.js +3 -4
  45. package/lib/rules/require-closing-tags.js +3 -3
  46. package/lib/rules/require-doctype.js +2 -2
  47. package/lib/rules/require-explicit-size.js +2 -5
  48. package/lib/rules/require-form-method.js +2 -2
  49. package/lib/rules/require-frame-title.js +2 -2
  50. package/lib/rules/require-img-alt.js +40 -17
  51. package/lib/rules/require-input-label.js +3 -3
  52. package/lib/rules/require-lang.js +2 -2
  53. package/lib/rules/require-li-container.js +2 -2
  54. package/lib/rules/require-meta-charset.js +3 -4
  55. package/lib/rules/require-meta-description.js +3 -4
  56. package/lib/rules/require-meta-viewport.js +3 -4
  57. package/lib/rules/require-open-graph-protocol.js +3 -6
  58. package/lib/rules/require-title.js +3 -5
  59. package/lib/rules/sort-attrs.js +4 -5
  60. package/lib/rules/use-baseline.js +3 -6
  61. package/lib/rules/utils/baseline.js +7 -4
  62. package/lib/rules/utils/node.js +11 -26
  63. package/lib/rules/utils/settings.js +4 -7
  64. package/lib/rules/utils/source-code.js +2 -2
  65. package/lib/rules/utils/template-literal.js +43 -0
  66. package/lib/rules/utils/visitors.js +6 -7
  67. package/package.json +6 -6
  68. package/types/configs/recommended.d.ts +1 -0
  69. package/types/index.d.ts +2 -0
  70. package/types/index.d.ts.map +1 -1
  71. package/types/languages/html-language.d.ts +0 -5
  72. package/types/languages/html-language.d.ts.map +1 -1
  73. package/types/languages/html-source-code.d.ts +13 -14
  74. package/types/languages/html-source-code.d.ts.map +1 -1
  75. package/types/languages/html-traversal-step.d.ts +5 -5
  76. package/types/languages/html-traversal-step.d.ts.map +1 -1
  77. package/types/rules/attrs-newline.d.ts +3 -4
  78. package/types/rules/attrs-newline.d.ts.map +1 -1
  79. package/types/rules/element-newline.d.ts +5 -13
  80. package/types/rules/element-newline.d.ts.map +1 -1
  81. package/types/rules/id-naming-convention.d.ts +3 -6
  82. package/types/rules/id-naming-convention.d.ts.map +1 -1
  83. package/types/rules/indent/indent-level.d.ts +3 -3
  84. package/types/rules/indent/indent-level.d.ts.map +1 -1
  85. package/types/rules/indent/indent.d.ts +5 -18
  86. package/types/rules/indent/indent.d.ts.map +1 -1
  87. package/types/rules/lowercase.d.ts +2 -8
  88. package/types/rules/lowercase.d.ts.map +1 -1
  89. package/types/rules/max-element-depth.d.ts +3 -6
  90. package/types/rules/max-element-depth.d.ts.map +1 -1
  91. package/types/rules/no-abstract-roles.d.ts +2 -8
  92. package/types/rules/no-abstract-roles.d.ts.map +1 -1
  93. package/types/rules/no-accesskey-attrs.d.ts +2 -8
  94. package/types/rules/no-accesskey-attrs.d.ts.map +1 -1
  95. package/types/rules/no-aria-hidden-body.d.ts +2 -5
  96. package/types/rules/no-aria-hidden-body.d.ts.map +1 -1
  97. package/types/rules/no-aria-hidden-on-focusable.d.ts +4 -0
  98. package/types/rules/no-aria-hidden-on-focusable.d.ts.map +1 -0
  99. package/types/rules/no-duplicate-attrs.d.ts +2 -10
  100. package/types/rules/no-duplicate-attrs.d.ts.map +1 -1
  101. package/types/rules/no-duplicate-class.d.ts +3 -7
  102. package/types/rules/no-duplicate-class.d.ts.map +1 -1
  103. package/types/rules/no-duplicate-id.d.ts +2 -9
  104. package/types/rules/no-duplicate-id.d.ts.map +1 -1
  105. package/types/rules/no-duplicate-in-head.d.ts +4 -0
  106. package/types/rules/no-duplicate-in-head.d.ts.map +1 -0
  107. package/types/rules/no-empty-headings.d.ts +4 -0
  108. package/types/rules/no-empty-headings.d.ts.map +1 -0
  109. package/types/rules/no-extra-spacing-attrs.d.ts +3 -14
  110. package/types/rules/no-extra-spacing-attrs.d.ts.map +1 -1
  111. package/types/rules/no-extra-spacing-text.d.ts +3 -9
  112. package/types/rules/no-extra-spacing-text.d.ts.map +1 -1
  113. package/types/rules/no-heading-inside-button.d.ts +2 -5
  114. package/types/rules/no-heading-inside-button.d.ts.map +1 -1
  115. package/types/rules/no-inline-styles.d.ts +2 -5
  116. package/types/rules/no-inline-styles.d.ts.map +1 -1
  117. package/types/rules/no-invalid-entity.d.ts +11 -0
  118. package/types/rules/no-invalid-entity.d.ts.map +1 -0
  119. package/types/rules/no-invalid-role.d.ts +2 -5
  120. package/types/rules/no-invalid-role.d.ts.map +1 -1
  121. package/types/rules/no-multiple-empty-lines.d.ts +3 -7
  122. package/types/rules/no-multiple-empty-lines.d.ts.map +1 -1
  123. package/types/rules/no-multiple-h1.d.ts +2 -6
  124. package/types/rules/no-multiple-h1.d.ts.map +1 -1
  125. package/types/rules/no-nested-interactive.d.ts +2 -6
  126. package/types/rules/no-nested-interactive.d.ts.map +1 -1
  127. package/types/rules/no-non-scalable-viewport.d.ts +2 -5
  128. package/types/rules/no-non-scalable-viewport.d.ts.map +1 -1
  129. package/types/rules/no-obsolete-tags.d.ts +2 -5
  130. package/types/rules/no-obsolete-tags.d.ts.map +1 -1
  131. package/types/rules/no-positive-tabindex.d.ts +2 -8
  132. package/types/rules/no-positive-tabindex.d.ts.map +1 -1
  133. package/types/rules/no-restricted-attr-values.d.ts +3 -7
  134. package/types/rules/no-restricted-attr-values.d.ts.map +1 -1
  135. package/types/rules/no-restricted-attrs.d.ts +3 -7
  136. package/types/rules/no-restricted-attrs.d.ts.map +1 -1
  137. package/types/rules/no-script-style-type.d.ts +2 -8
  138. package/types/rules/no-script-style-type.d.ts.map +1 -1
  139. package/types/rules/no-skip-heading-levels.d.ts +2 -6
  140. package/types/rules/no-skip-heading-levels.d.ts.map +1 -1
  141. package/types/rules/no-target-blank.d.ts +2 -5
  142. package/types/rules/no-target-blank.d.ts.map +1 -1
  143. package/types/rules/no-trailing-spaces.d.ts +2 -7
  144. package/types/rules/no-trailing-spaces.d.ts.map +1 -1
  145. package/types/rules/prefer-https.d.ts +2 -9
  146. package/types/rules/prefer-https.d.ts.map +1 -1
  147. package/types/rules/quotes.d.ts +7 -9
  148. package/types/rules/quotes.d.ts.map +1 -1
  149. package/types/rules/require-attrs.d.ts +3 -8
  150. package/types/rules/require-attrs.d.ts.map +1 -1
  151. package/types/rules/require-button-type.d.ts +2 -7
  152. package/types/rules/require-button-type.d.ts.map +1 -1
  153. package/types/rules/require-closing-tags.d.ts +3 -4
  154. package/types/rules/require-closing-tags.d.ts.map +1 -1
  155. package/types/rules/require-doctype.d.ts +2 -5
  156. package/types/rules/require-doctype.d.ts.map +1 -1
  157. package/types/rules/require-explicit-size.d.ts +3 -5
  158. package/types/rules/require-explicit-size.d.ts.map +1 -1
  159. package/types/rules/require-form-method.d.ts +2 -5
  160. package/types/rules/require-form-method.d.ts.map +1 -1
  161. package/types/rules/require-frame-title.d.ts +2 -5
  162. package/types/rules/require-frame-title.d.ts.map +1 -1
  163. package/types/rules/require-img-alt.d.ts +3 -4
  164. package/types/rules/require-img-alt.d.ts.map +1 -1
  165. package/types/rules/require-input-label.d.ts +2 -6
  166. package/types/rules/require-input-label.d.ts.map +1 -1
  167. package/types/rules/require-lang.d.ts +2 -5
  168. package/types/rules/require-lang.d.ts.map +1 -1
  169. package/types/rules/require-li-container.d.ts +2 -5
  170. package/types/rules/require-li-container.d.ts.map +1 -1
  171. package/types/rules/require-meta-charset.d.ts +2 -7
  172. package/types/rules/require-meta-charset.d.ts.map +1 -1
  173. package/types/rules/require-meta-description.d.ts +2 -7
  174. package/types/rules/require-meta-description.d.ts.map +1 -1
  175. package/types/rules/require-meta-viewport.d.ts +2 -7
  176. package/types/rules/require-meta-viewport.d.ts.map +1 -1
  177. package/types/rules/require-open-graph-protocol.d.ts +3 -5
  178. package/types/rules/require-open-graph-protocol.d.ts.map +1 -1
  179. package/types/rules/require-title.d.ts +2 -8
  180. package/types/rules/require-title.d.ts.map +1 -1
  181. package/types/rules/sort-attrs.d.ts +3 -6
  182. package/types/rules/sort-attrs.d.ts.map +1 -1
  183. package/types/rules/use-baseline.d.ts +3 -7
  184. package/types/rules/use-baseline.d.ts.map +1 -1
  185. package/types/rules/utils/baseline.d.ts.map +1 -1
  186. package/types/rules/utils/node.d.ts +25 -29
  187. package/types/rules/utils/node.d.ts.map +1 -1
  188. package/types/rules/utils/settings.d.ts +7 -9
  189. package/types/rules/utils/settings.d.ts.map +1 -1
  190. package/types/rules/utils/source-code.d.ts +4 -4
  191. package/types/rules/utils/source-code.d.ts.map +1 -1
  192. package/types/rules/utils/template-literal.d.ts +18 -0
  193. package/types/rules/utils/template-literal.d.ts.map +1 -0
  194. package/types/rules/utils/visitors.d.ts +4 -4
  195. package/types/rules/utils/visitors.d.ts.map +1 -1
@@ -1,10 +1,7 @@
1
1
  /**
2
- * @typedef { import("@html-eslint/types").StyleTag } StyleTag
3
- * @typedef { import("@html-eslint/types").Attribute } Attribute
4
- * @typedef { import("@html-eslint/types").Tag } Tag
5
- * @typedef { import("@html-eslint/types").ScriptTag } ScriptTag
2
+ * @import {StyleTag, Attribute, Tag, ScriptTag } from "@html-eslint/types";
3
+ * @import {RuleModule} from "../types";
6
4
  * @typedef {{attrPatterns: string[], attrValuePatterns: string[], message?: string}[]} Options
7
- * @typedef { import("../types").RuleModule<Options> } RuleModule
8
5
  */
9
6
 
10
7
  const { RULE_CATEGORY } = require("../constants");
@@ -16,7 +13,7 @@ const MESSAGE_IDS = {
16
13
  };
17
14
 
18
15
  /**
19
- * @type {RuleModule}
16
+ * @type {RuleModule<Options>}
20
17
  */
21
18
  module.exports = {
22
19
  meta: {
@@ -53,6 +50,7 @@ module.exports = {
53
50
  type: "string",
54
51
  },
55
52
  },
53
+ additionalProperties: false,
56
54
  },
57
55
  },
58
56
  messages: {
@@ -1,11 +1,8 @@
1
1
  /**
2
- * @typedef { import("@html-eslint/types").StyleTag } StyleTag
3
- * @typedef { import("@html-eslint/types").Attribute } Attribute
4
- * @typedef { import("@html-eslint/types").Tag } Tag
5
- * @typedef { import("@html-eslint/types").ScriptTag } ScriptTag
2
+ * @import {StyleTag, Attribute, Tag, ScriptTag} from "@html-eslint/types";
3
+ * @import {RuleModule} from "../types";
6
4
  * @typedef {{tagPatterns: string[], attrPatterns: string[], message?: string}[]} Options
7
5
  *
8
- * @typedef { import("../types").RuleModule<Options> } RuleModule
9
6
  */
10
7
 
11
8
  const { NODE_TYPES } = require("@html-eslint/parser");
@@ -18,7 +15,7 @@ const MESSAGE_IDS = {
18
15
  };
19
16
 
20
17
  /**
21
- * @type {RuleModule}
18
+ * @type {RuleModule<Options>}
22
19
  */
23
20
  module.exports = {
24
21
  meta: {
@@ -55,6 +52,7 @@ module.exports = {
55
52
  type: "string",
56
53
  },
57
54
  },
55
+ additionalProperties: false,
58
56
  },
59
57
  },
60
58
  messages: {
@@ -1,8 +1,6 @@
1
1
  /**
2
- * @typedef { import("../types").RuleModule<[]> } RuleModule
3
- * @typedef { import("@html-eslint/types").StyleTag } StyleTag
4
- * @typedef { import("@html-eslint/types").Tag } Tag
5
- * @typedef { import("@html-eslint/types").ScriptTag } ScriptTag
2
+ * @import {RuleModule} from "../types";
3
+ * @import {StyleTag, Tag, ScriptTag} from "@html-eslint/types";
6
4
  */
7
5
 
8
6
  const { RULE_CATEGORY } = require("../constants");
@@ -15,7 +13,7 @@ const MESSAGE_IDS = {
15
13
  };
16
14
 
17
15
  /**
18
- * @type {RuleModule}
16
+ * @type {RuleModule<[]>}
19
17
  */
20
18
  module.exports = {
21
19
  meta: {
@@ -1,6 +1,6 @@
1
1
  /**
2
- * @typedef { import("../types").RuleModule<[]> } RuleModule
3
- * @typedef { import("@html-eslint/types").Tag } Tag
2
+ * @import {RuleModule} from "../types";
3
+ * @import {Tag} from "@html-eslint/types";
4
4
  */
5
5
 
6
6
  const { RULE_CATEGORY } = require("../constants");
@@ -11,7 +11,7 @@ const MESSAGE_IDS = {
11
11
  };
12
12
 
13
13
  /**
14
- * @type {RuleModule}
14
+ * @type {RuleModule<[]>}
15
15
  */
16
16
  module.exports = {
17
17
  meta: {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @typedef { import("../types").RuleModule<[]> } RuleModule
2
+ * @import {RuleModule} from "../types";
3
3
  */
4
4
 
5
5
  const { RULE_CATEGORY } = require("../constants");
@@ -12,7 +12,7 @@ const MESSAGE_IDS = {
12
12
  };
13
13
 
14
14
  /**
15
- * @type {RuleModule}
15
+ * @type {RuleModule<[]>}
16
16
  */
17
17
  module.exports = {
18
18
  meta: {
@@ -1,10 +1,9 @@
1
1
  /**
2
- * @typedef { import("../types").RuleModule<[]> } RuleModule
3
- * @typedef { import("@html-eslint/types").CommentContent } CommentContent
4
- * @typedef { import("@html-eslint/types").Text } Text
2
+ * @import {RuleModule} from "../types";
3
+ * @import {CommentContent, Text} from "@html-eslint/types";
5
4
  */
6
5
 
7
- const { parse } = require("@html-eslint/template-parser");
6
+ const { parseTemplateLiteral } = require("./utils/template-literal");
8
7
  const { RULE_CATEGORY } = require("../constants");
9
8
  const {
10
9
  getTemplateTokens,
@@ -23,7 +22,7 @@ const MESSAGE_IDS = {
23
22
  };
24
23
 
25
24
  /**
26
- * @type {RuleModule}
25
+ * @type {RuleModule<[]>}
27
26
  */
28
27
  module.exports = {
29
28
  meta: {
@@ -108,10 +107,9 @@ module.exports = {
108
107
  },
109
108
  TaggedTemplateExpression(node) {
110
109
  if (shouldCheckTaggedTemplateExpression(node, context)) {
111
- const { html, tokens } = parse(
110
+ const { html, tokens } = parseTemplateLiteral(
112
111
  node.quasi,
113
- getSourceCode(context),
114
- {}
112
+ getSourceCode(context)
115
113
  );
116
114
  const lines = codeToLines(html);
117
115
  check(
@@ -128,7 +126,10 @@ module.exports = {
128
126
  },
129
127
  TemplateLiteral(node) {
130
128
  if (shouldCheckTemplateLiteral(node, context)) {
131
- const { html, tokens } = parse(node, getSourceCode(context), {});
129
+ const { html, tokens } = parseTemplateLiteral(
130
+ node,
131
+ getSourceCode(context)
132
+ );
132
133
  const lines = codeToLines(html);
133
134
  check(
134
135
  html,
@@ -1,9 +1,6 @@
1
1
  /**
2
- * @typedef { import("../types").RuleModule<[]> } RuleModule
3
- * @typedef { import("@html-eslint/types").Tag } Tag
4
- * @typedef { import("@html-eslint/types").ScriptTag } ScriptTag
5
- * @typedef { import("@html-eslint/types").Attribute } Attribute
6
- * @typedef { import("@html-eslint/types").AttributeValue } AttributeValue
2
+ * @import {Tag, ScriptTag, Attribute, AttributeValue} from "@html-eslint/types";
3
+ * @import {RuleModule} from "../types";
7
4
  */
8
5
 
9
6
  const { RULE_CATEGORY } = require("../constants");
@@ -66,7 +63,7 @@ function getResourceAttributeValue(node) {
66
63
  }
67
64
 
68
65
  /**
69
- * @type {RuleModule}
66
+ * @type {RuleModule<[]>}
70
67
  */
71
68
  module.exports = {
72
69
  meta: {
@@ -1,12 +1,10 @@
1
1
  /**
2
- * @typedef { import("eslint").AST.Range } Range
3
- * @typedef { import("@html-eslint/types").Attribute } Attribute
4
- * @typedef { import("@html-eslint/types").Tag } Tag
5
- * @typedef { import("@html-eslint/types").ScriptTag } ScriptTag
6
- * @typedef { import("@html-eslint/types").StyleTag } StyleTag
2
+ * @import {AST} from "eslint";
3
+ * @import {Attribute, Tag, ScriptTag, StyleTag} from "@html-eslint/types";
4
+ * @import {RuleModule} from "../types";
7
5
  *
8
- * @typedef {"single" | "double"} Option
9
- * @typedef { import("../types").RuleModule<[Option]> } RuleModule
6
+ * @typedef {"single" | "double"} SingleOrQuoteOption
7
+ * @typedef {{enforceTemplatedAttrValue: boolean}} ObjectOption
10
8
  */
11
9
 
12
10
  const { NODE_TYPES } = require("@html-eslint/parser");
@@ -28,7 +26,7 @@ const QUOTES_STYLES = {
28
26
  const QUOTES_CODES = [`"`, `'`];
29
27
 
30
28
  /**
31
- * @type {RuleModule}
29
+ * @type {RuleModule<[SingleOrQuoteOption, ObjectOption]>}
32
30
  */
33
31
  module.exports = {
34
32
  meta: {
@@ -46,6 +44,16 @@ module.exports = {
46
44
  {
47
45
  enum: [QUOTES_STYLES.SINGLE, QUOTES_STYLES.DOUBLE],
48
46
  },
47
+ {
48
+ type: "object",
49
+ properties: {
50
+ enforceTemplatedAttrValue: {
51
+ type: "boolean",
52
+ default: false,
53
+ },
54
+ },
55
+ additionalProperties: false,
56
+ },
49
57
  ],
50
58
  messages: {
51
59
  [MESSAGE_IDS.UNEXPECTED]:
@@ -61,11 +69,15 @@ module.exports = {
61
69
  ? context.options[0]
62
70
  : QUOTES_STYLES.DOUBLE;
63
71
  const expectedQuote = SELECTED_STYLE === QUOTES_STYLES.DOUBLE ? `"` : `'`;
72
+ const enforceTemplatedAttrValue =
73
+ context.options &&
74
+ context.options[1] &&
75
+ context.options[1].enforceTemplatedAttrValue;
64
76
 
65
77
  const sourceCode = getSourceCode(context);
66
78
 
67
79
  /**
68
- * @param {Range} range
80
+ * @param {AST.Range} range
69
81
  * @returns {string}
70
82
  */
71
83
  function getCodeIn(range) {
@@ -94,7 +106,10 @@ module.exports = {
94
106
  * Allow template expression.
95
107
  * ex: html`<div foo=${foo}></div>`
96
108
  */
97
- if (attr.value.parts.some((part) => part.type === NODE_TYPES.Template)) {
109
+ if (
110
+ !enforceTemplatedAttrValue &&
111
+ attr.value.parts.some((part) => part.type === NODE_TYPES.Template)
112
+ ) {
98
113
  return;
99
114
  }
100
115
 
@@ -1,17 +1,12 @@
1
1
  /**
2
- * @typedef { import("@html-eslint/types").Tag } Tag
3
- * @typedef { import("@html-eslint/types").ScriptTag } ScriptTag
4
- * @typedef { import("@html-eslint/types").StyleTag } StyleTag
5
- * @typedef { import("@html-eslint/types").Attribute } Attribute
2
+ * @import {Tag, ScriptTag, StyleTag, Attribute} from "@html-eslint/types";
3
+ * @import {RuleModule, ReportFixFunction} from "../types";
6
4
  *
7
5
  * @typedef {Object} Option
8
6
  * @property {string} tag
9
7
  * @property {string} attr
10
8
  * @property {string} [value]
11
9
  *
12
- * @typedef { import("../types").RuleModule<Option[]> } RuleModule
13
- * @typedef { import("../types").ReportFixFunction} ReportFixFunction
14
- *
15
10
  */
16
11
 
17
12
  const { NODE_TYPES } = require("@html-eslint/parser");
@@ -25,7 +20,7 @@ const MESSAGE_IDS = {
25
20
  };
26
21
 
27
22
  /**
28
- * @type {RuleModule}
23
+ * @type {RuleModule<Option[]>}
29
24
  */
30
25
  module.exports = {
31
26
  meta: {
@@ -1,7 +1,6 @@
1
1
  /**
2
- * @typedef { import("../types").RuleModule<[]> } RuleModule
3
- * @typedef { import("@html-eslint/types").AttributeValue } AttributeValue
4
- * @typedef { import("../types").SuggestionReportDescriptor } SuggestionReportDescriptor
2
+ * @import {RuleModule, SuggestionReportDescriptor} from "../types";
3
+ * @import {AttributeValue} from "@html-eslint/types";
5
4
  */
6
5
 
7
6
  const { RULE_CATEGORY } = require("../constants");
@@ -20,7 +19,7 @@ const MESSAGE_IDS = {
20
19
  const VALID_BUTTON_TYPES_SET = new Set(["submit", "button", "reset"]);
21
20
 
22
21
  /**
23
- * @type {RuleModule}
22
+ * @type {RuleModule<[]>}
24
23
  */
25
24
  module.exports = {
26
25
  meta: {
@@ -1,10 +1,10 @@
1
1
  /**
2
- * @typedef { import("@html-eslint/types").Tag } Tag
2
+ * @import {Tag} from "@html-eslint/types";
3
+ * @import {RuleModule} from "../types";
3
4
  *
4
5
  * @typedef {Object} Option
5
6
  * @property {"always" | "never"} [Option.selfClosing]
6
7
  * @property {string[]} [Option.selfClosingCustomPatterns]
7
- * @typedef { import("../types").RuleModule<[Option]> } RuleModule
8
8
  */
9
9
 
10
10
  const { RULE_CATEGORY, VOID_ELEMENTS } = require("../constants");
@@ -20,7 +20,7 @@ const MESSAGE_IDS = {
20
20
  };
21
21
 
22
22
  /**
23
- * @type {RuleModule}
23
+ * @type {RuleModule<[Option]>}
24
24
  */
25
25
  module.exports = {
26
26
  meta: {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @typedef { import("../types").RuleModule<[]> } RuleModule
2
+ * @import {RuleModule} from "../types";
3
3
  */
4
4
 
5
5
  const { RULE_CATEGORY } = require("../constants");
@@ -10,7 +10,7 @@ const MESSAGE_IDS = {
10
10
  };
11
11
 
12
12
  /**
13
- * @type {RuleModule}
13
+ * @type {RuleModule<[]>}
14
14
  */
15
15
  module.exports = {
16
16
  meta: {
@@ -1,12 +1,9 @@
1
1
  /**
2
- * @typedef { import("@html-eslint/types").Tag } Tag
3
- * @typedef { import("@html-eslint/types").AnyNode } AnyNode
2
+ * @import {RuleModule} from "../types";
4
3
  *
5
4
  * @typedef {Object} Option
6
5
  * @property {string[]} [Option.allowClass]
7
6
  * @property {string[]} [Option.allowId]
8
- * @typedef { import("../types").RuleModule<[Option]> } RuleModule
9
- *
10
7
  */
11
8
 
12
9
  const { RULE_CATEGORY } = require("../constants");
@@ -22,7 +19,7 @@ const MESSAGE_IDS = {
22
19
  const TARGET_ELEMENTS = ["img", "iframe"];
23
20
 
24
21
  /**
25
- * @type {RuleModule}
22
+ * @type {RuleModule<[Option]>}
26
23
  */
27
24
  module.exports = {
28
25
  meta: {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @typedef { import("../types").RuleModule<[]> } RuleModule
2
+ * @import {RuleModule} from "../types";
3
3
  */
4
4
 
5
5
  const { NODE_TYPES } = require("@html-eslint/parser");
@@ -17,7 +17,7 @@ const MESSAGE_IDS = {
17
17
  const ALLOWED_METHODS = new Set(["GET", "POST", "DIALOG"]);
18
18
 
19
19
  /**
20
- * @type {RuleModule}
20
+ * @type {RuleModule<[]>}
21
21
  */
22
22
  module.exports = {
23
23
  meta: {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @typedef { import("../types").RuleModule<[]> } RuleModule
2
+ * @import {RuleModule} from "../types";
3
3
  */
4
4
 
5
5
  const { RULE_CATEGORY } = require("../constants");
@@ -13,7 +13,7 @@ const MESSAGE_IDS = {
13
13
  };
14
14
 
15
15
  /**
16
- * @type {RuleModule}
16
+ * @type {RuleModule<[]>}
17
17
  */
18
18
  module.exports = {
19
19
  meta: {
@@ -1,10 +1,9 @@
1
1
  /**
2
- * @typedef { import("@html-eslint/types").Tag } Tag
2
+ * @import {Tag} from "@html-eslint/types";
3
+ * @import {RuleModule} from "../types";
3
4
  *
4
5
  * @typedef {Object} Option
5
- * @property {string[]} [Option.substitute]
6
- *
7
- * @typedef { import("../types").RuleModule<[Option]> } RuleModule
6
+ * @property {string[]} [substitute]
8
7
  */
9
8
 
10
9
  const { RULE_CATEGORY } = require("../constants");
@@ -13,14 +12,15 @@ const { getRuleUrl } = require("./utils/rule");
13
12
 
14
13
  const MESSAGE_IDS = {
15
14
  MISSING_ALT: "missingAlt",
15
+ INSERT_ALT: "insertAlt",
16
16
  };
17
17
 
18
18
  /**
19
- * @type {RuleModule}
19
+ * @type {RuleModule<[Option]>}
20
20
  */
21
21
  module.exports = {
22
22
  meta: {
23
- type: "code",
23
+ type: "suggestion",
24
24
 
25
25
  docs: {
26
26
  description: "Require `alt` attribute at `<img>` tag",
@@ -30,6 +30,7 @@ module.exports = {
30
30
  },
31
31
 
32
32
  fixable: null,
33
+ hasSuggestions: true,
33
34
  schema: [
34
35
  {
35
36
  type: "object",
@@ -41,10 +42,12 @@ module.exports = {
41
42
  },
42
43
  },
43
44
  },
45
+ additionalProperties: false,
44
46
  },
45
47
  ],
46
48
  messages: {
47
49
  [MESSAGE_IDS.MISSING_ALT]: "Missing `alt` attribute at `<img>` tag",
50
+ [MESSAGE_IDS.INSERT_ALT]: 'Insert `alt=""` at `<img>` tag',
48
51
  },
49
52
  },
50
53
 
@@ -60,13 +63,27 @@ module.exports = {
60
63
  if (node.name !== "img") {
61
64
  return;
62
65
  }
63
- if (!hasAltAttrAndValue(node, substitute)) {
66
+
67
+ const hasAlt = hasValidAltOrSubstitute(node, substitute);
68
+ const hasSubstituteOption = substitute.length > 0;
69
+
70
+ if (!hasAlt) {
64
71
  context.report({
65
72
  loc: {
66
73
  start: node.openStart.loc.start,
67
74
  end: node.openEnd.loc.end,
68
75
  },
69
76
  messageId: MESSAGE_IDS.MISSING_ALT,
77
+ suggest: hasSubstituteOption
78
+ ? null
79
+ : [
80
+ {
81
+ messageId: MESSAGE_IDS.INSERT_ALT,
82
+ fix(fixer) {
83
+ return fixer.insertTextBefore(node.openEnd, ' alt=""');
84
+ },
85
+ },
86
+ ],
70
87
  });
71
88
  }
72
89
  },
@@ -77,16 +94,22 @@ module.exports = {
77
94
  /**
78
95
  * @param {Tag} node
79
96
  * @param {string[]} substitute
80
- * @returns
97
+ * @returns {boolean}
81
98
  */
82
- function hasAltAttrAndValue(node, substitute = []) {
83
- return node.attributes.some((attr) => {
84
- if (attr.key && attr.value) {
85
- return (
86
- (attr.key.value === "alt" || substitute.includes(attr.key.value)) &&
87
- typeof attr.value.value === "string"
88
- );
99
+ function hasValidAltOrSubstitute(node, substitute) {
100
+ let hasAnyAlt = false;
101
+
102
+ for (const attr of node.attributes) {
103
+ if (attr.key && attr.key.value) {
104
+ const isAltAttr = attr.key.value === "alt";
105
+ const isSubstituteAttr = substitute.includes(attr.key.value);
106
+
107
+ if (isAltAttr || isSubstituteAttr) {
108
+ hasAnyAlt = true;
109
+ break;
110
+ }
89
111
  }
90
- return false;
91
- });
112
+ }
113
+
114
+ return hasAnyAlt;
92
115
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * @typedef { import("@html-eslint/types").Tag } Tag
3
- * @typedef { import("../types").RuleModule<[]> } RuleModule
2
+ * @import {RuleModule} from "../types";
3
+ * @import {Tag} from "@html-eslint/types";
4
4
  */
5
5
 
6
6
  const { RULE_CATEGORY } = require("../constants");
@@ -17,7 +17,7 @@ const INPUT_TAGS = new Set(["input", "textarea", "select"]);
17
17
  const LABEL_ATTRIBUTES = new Set(["id", "aria-labelledby", "aria-label"]);
18
18
 
19
19
  /**
20
- * @type {RuleModule}
20
+ * @type {RuleModule<[]>}
21
21
  */
22
22
  module.exports = {
23
23
  meta: {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @typedef { import("../types").RuleModule<[]> } RuleModule
2
+ * @import {RuleModule} from "../types";
3
3
  */
4
4
 
5
5
  const { RULE_CATEGORY } = require("../constants");
@@ -13,7 +13,7 @@ const MESSAGE_IDS = {
13
13
  };
14
14
 
15
15
  /**
16
- * @type {RuleModule}
16
+ * @type {RuleModule<[]>}
17
17
  */
18
18
  module.exports = {
19
19
  meta: {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @typedef { import("../types").RuleModule<[]> } RuleModule
2
+ * @import {RuleModule} from "../types";
3
3
  */
4
4
 
5
5
  const { NODE_TYPES } = require("@html-eslint/parser");
@@ -13,7 +13,7 @@ const MESSAGE_IDS = {
13
13
  const VALID_CONTAINERS = ["ul", "ol", "menu"];
14
14
 
15
15
  /**
16
- * @type {RuleModule}
16
+ * @type {RuleModule<[]>}
17
17
  */
18
18
  module.exports = {
19
19
  meta: {
@@ -1,7 +1,6 @@
1
1
  /**
2
- * @typedef { import("@html-eslint/types").Tag } Tag
3
- * @typedef { import("@html-eslint/types").AnyNode } AnyNode
4
- * @typedef { import("../types").RuleModule<[]> } RuleModule
2
+ * @import {RuleModule} from "../types";
3
+ * @import {Tag, AnyNode} from "@html-eslint/types";
5
4
  */
6
5
 
7
6
  const { RULE_CATEGORY } = require("../constants");
@@ -23,7 +22,7 @@ function isMetaCharset(node) {
23
22
  }
24
23
 
25
24
  /**
26
- * @type {RuleModule}
25
+ * @type {RuleModule<[]>}
27
26
  */
28
27
  module.exports = {
29
28
  meta: {
@@ -1,7 +1,6 @@
1
1
  /**
2
- * @typedef { import("@html-eslint/types").Tag } Tag
3
- * @typedef { import("@html-eslint/types").AnyNode } AnyNode
4
- * @typedef { import("../types").RuleModule<[]> } RuleModule
2
+ * @import {Tag, AnyNode} from "@html-eslint/types";
3
+ * @import {RuleModule} from "../types";
5
4
  */
6
5
  const { RULE_CATEGORY } = require("../constants");
7
6
  const { filter } = require("./utils/array");
@@ -22,7 +21,7 @@ function isMetaTagNode(node) {
22
21
  }
23
22
 
24
23
  /**
25
- * @type {RuleModule}
24
+ * @type {RuleModule<[]>}
26
25
  */
27
26
  module.exports = {
28
27
  meta: {
@@ -1,7 +1,6 @@
1
1
  /**
2
- * @typedef { import("@html-eslint/types").Tag } Tag
3
- * @typedef { import("@html-eslint/types").AnyNode } AnyNode
4
- * @typedef { import("../types").RuleModule<[]> } RuleModule
2
+ * @import {Tag, AnyNode} from "@html-eslint/types";
3
+ * @import {RuleModule} from "../types";
5
4
  */
6
5
 
7
6
  const { RULE_CATEGORY } = require("../constants");
@@ -31,7 +30,7 @@ function isMetaViewport(node) {
31
30
  }
32
31
 
33
32
  /**
34
- * @type {RuleModule}
33
+ * @type {RuleModule<[]>}
35
34
  */
36
35
  module.exports = {
37
36
  meta: {
@@ -1,10 +1,7 @@
1
1
  /**
2
- * @typedef { import("@html-eslint/types").Tag } Tag
3
- * @typedef { import("@html-eslint/types").AnyNode } AnyNode
4
- *
2
+ * @import {Tag, AnyNode} from "@html-eslint/types";
3
+ * @import {RuleModule} from "../types";
5
4
  * @typedef {string[]} Option
6
- *
7
- * @typedef { import("../types").RuleModule<[Option]> } RuleModule
8
5
  */
9
6
 
10
7
  const { RULE_CATEGORY } = require("../constants");
@@ -36,7 +33,7 @@ function normalize(properties) {
36
33
  }
37
34
 
38
35
  /**
39
- * @type {RuleModule}
36
+ * @type {RuleModule<[Option]>}
40
37
  */
41
38
  module.exports = {
42
39
  meta: {
@@ -1,8 +1,6 @@
1
1
  /**
2
- * @typedef { import("@html-eslint/types").Tag } Tag
3
- * @typedef { import("@html-eslint/types").Text } Text
4
- * @typedef { import("@html-eslint/types").AnyNode } AnyNode
5
- * @typedef { import("../types").RuleModule<[]> } RuleModule
2
+ * @import {Tag, Text, AnyNode} from "@html-eslint/types";
3
+ * @import {RuleModule} from "../types";
6
4
  */
7
5
 
8
6
  const { RULE_CATEGORY } = require("../constants");
@@ -32,7 +30,7 @@ function isNonEmptyText(node) {
32
30
  }
33
31
 
34
32
  /**
35
- * @type {RuleModule}
33
+ * @type {RuleModule<[]>}
36
34
  */
37
35
  module.exports = {
38
36
  meta: {