@herb-tools/linter 0.8.9 → 0.9.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 (573) hide show
  1. package/README.md +5 -5
  2. package/dist/{src/cli → cli}/argument-parser.js +15 -2
  3. package/dist/cli/argument-parser.js.map +1 -0
  4. package/dist/{src/cli → cli}/file-processor.js +155 -9
  5. package/dist/cli/file-processor.js.map +1 -0
  6. package/dist/cli/file-url.js +6 -0
  7. package/dist/cli/file-url.js.map +1 -0
  8. package/dist/cli/formatters/base-formatter.js.map +1 -0
  9. package/dist/{src/cli → cli}/formatters/detailed-formatter.js +16 -19
  10. package/dist/cli/formatters/detailed-formatter.js.map +1 -0
  11. package/dist/cli/formatters/github-actions-formatter.js.map +1 -0
  12. package/dist/cli/formatters/index.js.map +1 -0
  13. package/dist/cli/formatters/json-formatter.js.map +1 -0
  14. package/dist/cli/formatters/simple-formatter.js +54 -0
  15. package/dist/cli/formatters/simple-formatter.js.map +1 -0
  16. package/dist/cli/index.js.map +1 -0
  17. package/dist/cli/lint-worker.js +143 -0
  18. package/dist/cli/lint-worker.js.map +1 -0
  19. package/dist/cli/output-manager.js.map +1 -0
  20. package/dist/{src/cli → cli}/summary-reporter.js +13 -16
  21. package/dist/cli/summary-reporter.js.map +1 -0
  22. package/dist/{src/cli.js → cli.js} +5 -3
  23. package/dist/cli.js.map +1 -0
  24. package/dist/{src/custom-rule-loader.js → custom-rule-loader.js} +20 -4
  25. package/dist/custom-rule-loader.js.map +1 -0
  26. package/dist/herb-disable-comment-utils.js.map +1 -0
  27. package/dist/herb-lint.js +60648 -17513
  28. package/dist/herb-lint.js.map +1 -1
  29. package/dist/index.cjs +2621 -934
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.js +2554 -873
  32. package/dist/index.js.map +1 -1
  33. package/dist/lint-worker.js +71462 -0
  34. package/dist/lint-worker.js.map +1 -0
  35. package/dist/linter-ignore.js.map +1 -0
  36. package/dist/{src/linter.js → linter.js} +89 -74
  37. package/dist/linter.js.map +1 -0
  38. package/dist/loader.cjs +31206 -7834
  39. package/dist/loader.cjs.map +1 -1
  40. package/dist/loader.js +31168 -7802
  41. package/dist/loader.js.map +1 -1
  42. package/dist/parse-cache.js +30 -0
  43. package/dist/parse-cache.js.map +1 -0
  44. package/dist/rules/actionview-no-silent-helper.js +45 -0
  45. package/dist/rules/actionview-no-silent-helper.js.map +1 -0
  46. package/dist/{src/rules → rules}/erb-comment-syntax.js +2 -2
  47. package/dist/rules/erb-comment-syntax.js.map +1 -0
  48. package/dist/{src/rules → rules}/erb-no-case-node-children.js +2 -2
  49. package/dist/rules/erb-no-case-node-children.js.map +1 -0
  50. package/dist/rules/erb-no-conditional-html-element.js +38 -0
  51. package/dist/rules/erb-no-conditional-html-element.js.map +1 -0
  52. package/dist/rules/erb-no-conditional-open-tag.js +24 -0
  53. package/dist/rules/erb-no-conditional-open-tag.js.map +1 -0
  54. package/dist/rules/erb-no-duplicate-branch-elements.js +245 -0
  55. package/dist/rules/erb-no-duplicate-branch-elements.js.map +1 -0
  56. package/dist/{src/rules → rules}/erb-no-empty-tags.js +2 -2
  57. package/dist/rules/erb-no-empty-tags.js.map +1 -0
  58. package/dist/{src/rules → rules}/erb-no-extra-newline.js +4 -21
  59. package/dist/rules/erb-no-extra-newline.js.map +1 -0
  60. package/dist/{src/rules → rules}/erb-no-extra-whitespace-inside-tags.js +39 -13
  61. package/dist/rules/erb-no-extra-whitespace-inside-tags.js.map +1 -0
  62. package/dist/rules/erb-no-inline-case-conditions.js +40 -0
  63. package/dist/rules/erb-no-inline-case-conditions.js.map +1 -0
  64. package/dist/rules/erb-no-instance-variables-in-partials.js +67 -0
  65. package/dist/rules/erb-no-instance-variables-in-partials.js.map +1 -0
  66. package/dist/rules/erb-no-interpolated-class-names.js +47 -0
  67. package/dist/rules/erb-no-interpolated-class-names.js.map +1 -0
  68. package/dist/rules/erb-no-javascript-tag-helper.js +34 -0
  69. package/dist/rules/erb-no-javascript-tag-helper.js.map +1 -0
  70. package/dist/{src/rules → rules}/erb-no-output-control-flow.js +9 -12
  71. package/dist/rules/erb-no-output-control-flow.js.map +1 -0
  72. package/dist/rules/erb-no-output-in-attribute-name.js +30 -0
  73. package/dist/rules/erb-no-output-in-attribute-name.js.map +1 -0
  74. package/dist/rules/erb-no-output-in-attribute-position.js +30 -0
  75. package/dist/rules/erb-no-output-in-attribute-position.js.map +1 -0
  76. package/dist/rules/erb-no-raw-output-in-attribute-value.js +35 -0
  77. package/dist/rules/erb-no-raw-output-in-attribute-value.js.map +1 -0
  78. package/dist/{src/rules → rules}/erb-no-silent-tag-in-attribute-name.js +2 -2
  79. package/dist/rules/erb-no-silent-tag-in-attribute-name.js.map +1 -0
  80. package/dist/rules/erb-no-statement-in-script.js +58 -0
  81. package/dist/rules/erb-no-statement-in-script.js.map +1 -0
  82. package/dist/rules/erb-no-then-in-control-flow.js +45 -0
  83. package/dist/rules/erb-no-then-in-control-flow.js.map +1 -0
  84. package/dist/rules/erb-no-trailing-whitespace.js +138 -0
  85. package/dist/rules/erb-no-trailing-whitespace.js.map +1 -0
  86. package/dist/rules/erb-no-unsafe-js-attribute.js +36 -0
  87. package/dist/rules/erb-no-unsafe-js-attribute.js.map +1 -0
  88. package/dist/rules/erb-no-unsafe-raw.js +63 -0
  89. package/dist/rules/erb-no-unsafe-raw.js.map +1 -0
  90. package/dist/rules/erb-no-unsafe-script-interpolation.js +54 -0
  91. package/dist/rules/erb-no-unsafe-script-interpolation.js.map +1 -0
  92. package/dist/{src/rules → rules}/erb-prefer-image-tag-helper.js +5 -4
  93. package/dist/rules/erb-prefer-image-tag-helper.js.map +1 -0
  94. package/dist/{src/rules → rules}/erb-require-trailing-newline.js +2 -2
  95. package/dist/rules/erb-require-trailing-newline.js.map +1 -0
  96. package/dist/{src/rules → rules}/erb-require-whitespace-inside-tags.js +39 -15
  97. package/dist/rules/erb-require-whitespace-inside-tags.js.map +1 -0
  98. package/dist/{src/rules → rules}/erb-right-trim.js +2 -2
  99. package/dist/rules/erb-right-trim.js.map +1 -0
  100. package/dist/{src/rules → rules}/erb-strict-locals-comment-syntax.js +5 -5
  101. package/dist/rules/erb-strict-locals-comment-syntax.js.map +1 -0
  102. package/dist/{src/rules → rules}/erb-strict-locals-required.js +2 -2
  103. package/dist/rules/erb-strict-locals-required.js.map +1 -0
  104. package/dist/rules/file-utils.js.map +1 -0
  105. package/dist/rules/herb-disable-comment-base.js.map +1 -0
  106. package/dist/{src/rules → rules}/herb-disable-comment-malformed.js +2 -2
  107. package/dist/rules/herb-disable-comment-malformed.js.map +1 -0
  108. package/dist/{src/rules → rules}/herb-disable-comment-missing-rules.js +2 -2
  109. package/dist/rules/herb-disable-comment-missing-rules.js.map +1 -0
  110. package/dist/{src/rules → rules}/herb-disable-comment-no-duplicate-rules.js +2 -2
  111. package/dist/rules/herb-disable-comment-no-duplicate-rules.js.map +1 -0
  112. package/dist/{src/rules → rules}/herb-disable-comment-no-redundant-all.js +2 -2
  113. package/dist/rules/herb-disable-comment-no-redundant-all.js.map +1 -0
  114. package/dist/{src/rules → rules}/herb-disable-comment-unnecessary.js +2 -2
  115. package/dist/rules/herb-disable-comment-unnecessary.js.map +1 -0
  116. package/dist/{src/rules → rules}/herb-disable-comment-valid-rule-name.js +2 -2
  117. package/dist/rules/herb-disable-comment-valid-rule-name.js.map +1 -0
  118. package/dist/rules/html-allowed-script-type.js +57 -0
  119. package/dist/rules/html-allowed-script-type.js.map +1 -0
  120. package/dist/rules/html-anchor-require-href.js +68 -0
  121. package/dist/rules/html-anchor-require-href.js.map +1 -0
  122. package/dist/{src/rules → rules}/html-aria-attribute-must-be-valid.js +3 -3
  123. package/dist/rules/html-aria-attribute-must-be-valid.js.map +1 -0
  124. package/dist/{src/rules → rules}/html-aria-label-is-well-formatted.js +3 -3
  125. package/dist/rules/html-aria-label-is-well-formatted.js.map +1 -0
  126. package/dist/{src/rules → rules}/html-aria-level-must-be-valid.js +3 -3
  127. package/dist/rules/html-aria-level-must-be-valid.js.map +1 -0
  128. package/dist/{src/rules → rules}/html-aria-role-heading-requires-level.js +5 -4
  129. package/dist/rules/html-aria-role-heading-requires-level.js.map +1 -0
  130. package/dist/{src/rules → rules}/html-aria-role-must-be-valid.js +3 -3
  131. package/dist/rules/html-aria-role-must-be-valid.js.map +1 -0
  132. package/dist/{src/rules → rules}/html-attribute-double-quotes.js +4 -4
  133. package/dist/rules/html-attribute-double-quotes.js.map +1 -0
  134. package/dist/{src/rules → rules}/html-attribute-equals-spacing.js +2 -2
  135. package/dist/rules/html-attribute-equals-spacing.js.map +1 -0
  136. package/dist/{src/rules → rules}/html-attribute-values-require-quotes.js +2 -2
  137. package/dist/rules/html-attribute-values-require-quotes.js.map +1 -0
  138. package/dist/{src/rules → rules}/html-avoid-both-disabled-and-aria-disabled.js +9 -9
  139. package/dist/rules/html-avoid-both-disabled-and-aria-disabled.js.map +1 -0
  140. package/dist/{src/rules → rules}/html-body-only-elements.js +5 -4
  141. package/dist/rules/html-body-only-elements.js.map +1 -0
  142. package/dist/{src/rules → rules}/html-boolean-attributes-no-value.js +4 -3
  143. package/dist/rules/html-boolean-attributes-no-value.js.map +1 -0
  144. package/dist/rules/html-details-has-summary.js +52 -0
  145. package/dist/rules/html-details-has-summary.js.map +1 -0
  146. package/dist/{src/rules → rules}/html-head-only-elements.js +6 -5
  147. package/dist/rules/html-head-only-elements.js.map +1 -0
  148. package/dist/{src/rules → rules}/html-iframe-has-title.js +8 -11
  149. package/dist/rules/html-iframe-has-title.js.map +1 -0
  150. package/dist/{src/rules → rules}/html-img-require-alt.js +11 -5
  151. package/dist/rules/html-img-require-alt.js.map +1 -0
  152. package/dist/{src/rules → rules}/html-input-require-autocomplete.js +7 -10
  153. package/dist/rules/html-input-require-autocomplete.js.map +1 -0
  154. package/dist/{src/rules → rules}/html-navigation-has-label.js +6 -5
  155. package/dist/rules/html-navigation-has-label.js.map +1 -0
  156. package/dist/rules/html-no-abstract-roles.js +29 -0
  157. package/dist/rules/html-no-abstract-roles.js.map +1 -0
  158. package/dist/rules/html-no-aria-hidden-on-body.js +42 -0
  159. package/dist/rules/html-no-aria-hidden-on-body.js.map +1 -0
  160. package/dist/{src/rules → rules}/html-no-aria-hidden-on-focusable.js +6 -5
  161. package/dist/rules/html-no-aria-hidden-on-focusable.js.map +1 -0
  162. package/dist/{src/rules → rules}/html-no-block-inside-inline.js +6 -9
  163. package/dist/rules/html-no-block-inside-inline.js.map +1 -0
  164. package/dist/{src/rules → rules}/html-no-duplicate-attributes.js +4 -3
  165. package/dist/rules/html-no-duplicate-attributes.js.map +1 -0
  166. package/dist/{src/rules → rules}/html-no-duplicate-ids.js +14 -11
  167. package/dist/rules/html-no-duplicate-ids.js.map +1 -0
  168. package/dist/{src/rules → rules}/html-no-duplicate-meta-names.js +22 -20
  169. package/dist/rules/html-no-duplicate-meta-names.js.map +1 -0
  170. package/dist/{src/rules → rules}/html-no-empty-attributes.js +2 -2
  171. package/dist/rules/html-no-empty-attributes.js.map +1 -0
  172. package/dist/rules/html-no-empty-headings.js +98 -0
  173. package/dist/rules/html-no-empty-headings.js.map +1 -0
  174. package/dist/{src/rules → rules}/html-no-nested-links.js +23 -15
  175. package/dist/rules/html-no-nested-links.js.map +1 -0
  176. package/dist/{src/rules → rules}/html-no-positive-tab-index.js +3 -3
  177. package/dist/rules/html-no-positive-tab-index.js.map +1 -0
  178. package/dist/{src/rules → rules}/html-no-self-closing.js +4 -4
  179. package/dist/rules/html-no-self-closing.js.map +1 -0
  180. package/dist/{src/rules → rules}/html-no-space-in-tag.js +4 -6
  181. package/dist/rules/html-no-space-in-tag.js.map +1 -0
  182. package/dist/{src/rules → rules}/html-no-title-attribute.js +6 -5
  183. package/dist/rules/html-no-title-attribute.js.map +1 -0
  184. package/dist/{src/rules → rules}/html-no-underscores-in-attribute-names.js +2 -2
  185. package/dist/rules/html-no-underscores-in-attribute-names.js.map +1 -0
  186. package/dist/rules/html-require-closing-tags.js +29 -0
  187. package/dist/rules/html-require-closing-tags.js.map +1 -0
  188. package/dist/{src/rules → rules}/html-tag-name-lowercase.js +13 -9
  189. package/dist/rules/html-tag-name-lowercase.js.map +1 -0
  190. package/dist/{src/rules → rules}/index.js +19 -0
  191. package/dist/rules/index.js.map +1 -0
  192. package/dist/{src/rules → rules}/parser-no-errors.js +3 -3
  193. package/dist/rules/parser-no-errors.js.map +1 -0
  194. package/dist/{src/rules → rules}/rule-utils.js +141 -219
  195. package/dist/rules/rule-utils.js.map +1 -0
  196. package/dist/rules/string-utils.js.map +1 -0
  197. package/dist/{src/rules → rules}/svg-tag-name-capitalization.js +7 -6
  198. package/dist/rules/svg-tag-name-capitalization.js.map +1 -0
  199. package/dist/rules/turbo-permanent-require-id.js +34 -0
  200. package/dist/rules/turbo-permanent-require-id.js.map +1 -0
  201. package/dist/{src/rules.js → rules.js} +56 -10
  202. package/dist/rules.js.map +1 -0
  203. package/dist/types/cli/argument-parser.d.ts +1 -0
  204. package/dist/types/cli/file-processor.d.ts +13 -0
  205. package/dist/types/cli/file-url.d.ts +1 -0
  206. package/dist/types/cli/index.d.ts +1 -0
  207. package/dist/types/cli/lint-worker.d.ts +34 -0
  208. package/dist/types/custom-rule-loader.d.ts +4 -0
  209. package/dist/types/index.d.ts +1 -0
  210. package/dist/types/linter.d.ts +13 -6
  211. package/dist/types/parse-cache.d.ts +9 -0
  212. package/dist/types/{src/rules/html-aria-level-must-be-valid.d.ts → rules/actionview-no-silent-helper.d.ts} +4 -3
  213. package/dist/types/rules/erb-comment-syntax.d.ts +1 -1
  214. package/dist/types/rules/erb-no-case-node-children.d.ts +1 -1
  215. package/dist/types/{src/rules/herb-disable-comment-malformed.d.ts → rules/erb-no-conditional-html-element.d.ts} +3 -3
  216. package/dist/types/{src/rules/erb-prefer-image-tag-helper.d.ts → rules/erb-no-conditional-open-tag.d.ts} +3 -3
  217. package/dist/types/rules/erb-no-duplicate-branch-elements.d.ts +17 -0
  218. package/dist/types/rules/erb-no-empty-tags.d.ts +1 -1
  219. package/dist/types/rules/erb-no-extra-newline.d.ts +1 -1
  220. package/dist/types/rules/erb-no-extra-whitespace-inside-tags.d.ts +1 -1
  221. package/dist/types/{src/rules/html-no-duplicate-attributes.d.ts → rules/erb-no-inline-case-conditions.d.ts} +4 -3
  222. package/dist/types/rules/erb-no-instance-variables-in-partials.d.ts +10 -0
  223. package/dist/types/{src/rules/html-no-aria-hidden-on-focusable.d.ts → rules/erb-no-interpolated-class-names.d.ts} +2 -2
  224. package/dist/types/{src/rules/html-aria-attribute-must-be-valid.d.ts → rules/erb-no-javascript-tag-helper.d.ts} +2 -2
  225. package/dist/types/rules/erb-no-output-control-flow.d.ts +1 -1
  226. package/dist/types/{src/rules/erb-no-silent-tag-in-attribute-name.d.ts → rules/erb-no-output-in-attribute-name.d.ts} +2 -2
  227. package/dist/types/{src/rules/herb-disable-comment-missing-rules.d.ts → rules/erb-no-output-in-attribute-position.d.ts} +2 -2
  228. package/dist/types/{src/rules/erb-no-empty-tags.d.ts → rules/erb-no-raw-output-in-attribute-value.d.ts} +2 -2
  229. package/dist/types/rules/erb-no-silent-tag-in-attribute-name.d.ts +1 -1
  230. package/dist/types/{src/rules/html-navigation-has-label.d.ts → rules/erb-no-statement-in-script.d.ts} +2 -2
  231. package/dist/types/rules/erb-no-then-in-control-flow.d.ts +9 -0
  232. package/dist/types/rules/erb-no-trailing-whitespace.d.ts +19 -0
  233. package/dist/types/{src/rules/html-no-positive-tab-index.d.ts → rules/erb-no-unsafe-js-attribute.d.ts} +2 -2
  234. package/dist/types/{src/rules/erb-no-case-node-children.d.ts → rules/erb-no-unsafe-raw.d.ts} +2 -2
  235. package/dist/types/rules/erb-no-unsafe-script-interpolation.d.ts +8 -0
  236. package/dist/types/rules/erb-prefer-image-tag-helper.d.ts +1 -1
  237. package/dist/types/rules/erb-require-trailing-newline.d.ts +1 -1
  238. package/dist/types/rules/erb-require-whitespace-inside-tags.d.ts +1 -1
  239. package/dist/types/rules/erb-right-trim.d.ts +1 -1
  240. package/dist/types/rules/erb-strict-locals-comment-syntax.d.ts +1 -1
  241. package/dist/types/rules/erb-strict-locals-required.d.ts +1 -1
  242. package/dist/types/rules/herb-disable-comment-malformed.d.ts +1 -1
  243. package/dist/types/rules/herb-disable-comment-missing-rules.d.ts +1 -1
  244. package/dist/types/rules/herb-disable-comment-no-duplicate-rules.d.ts +1 -1
  245. package/dist/types/rules/herb-disable-comment-no-redundant-all.d.ts +1 -1
  246. package/dist/types/rules/herb-disable-comment-unnecessary.d.ts +1 -1
  247. package/dist/types/rules/herb-disable-comment-valid-rule-name.d.ts +1 -1
  248. package/dist/types/{src/rules/html-anchor-require-href.d.ts → rules/html-allowed-script-type.d.ts} +2 -2
  249. package/dist/types/rules/html-anchor-require-href.d.ts +3 -2
  250. package/dist/types/rules/html-aria-attribute-must-be-valid.d.ts +1 -1
  251. package/dist/types/rules/html-aria-label-is-well-formatted.d.ts +1 -1
  252. package/dist/types/rules/html-aria-level-must-be-valid.d.ts +1 -1
  253. package/dist/types/rules/html-aria-role-heading-requires-level.d.ts +1 -1
  254. package/dist/types/rules/html-aria-role-must-be-valid.d.ts +1 -1
  255. package/dist/types/rules/html-attribute-double-quotes.d.ts +1 -1
  256. package/dist/types/rules/html-attribute-equals-spacing.d.ts +1 -1
  257. package/dist/types/rules/html-attribute-values-require-quotes.d.ts +1 -1
  258. package/dist/types/rules/html-avoid-both-disabled-and-aria-disabled.d.ts +1 -1
  259. package/dist/types/rules/html-body-only-elements.d.ts +1 -1
  260. package/dist/types/rules/html-boolean-attributes-no-value.d.ts +1 -1
  261. package/dist/types/{src/rules/html-no-empty-attributes.d.ts → rules/html-details-has-summary.d.ts} +4 -3
  262. package/dist/types/rules/html-head-only-elements.d.ts +1 -1
  263. package/dist/types/rules/html-iframe-has-title.d.ts +1 -1
  264. package/dist/types/rules/html-img-require-alt.d.ts +1 -1
  265. package/dist/types/rules/html-input-require-autocomplete.d.ts +1 -1
  266. package/dist/types/rules/html-navigation-has-label.d.ts +1 -1
  267. package/dist/types/{src/rules/html-no-empty-headings.d.ts → rules/html-no-abstract-roles.d.ts} +2 -2
  268. package/dist/types/{src/rules/erb-no-output-control-flow.d.ts → rules/html-no-aria-hidden-on-body.d.ts} +3 -3
  269. package/dist/types/rules/html-no-aria-hidden-on-focusable.d.ts +1 -1
  270. package/dist/types/rules/html-no-block-inside-inline.d.ts +1 -1
  271. package/dist/types/rules/html-no-duplicate-attributes.d.ts +1 -1
  272. package/dist/types/rules/html-no-duplicate-ids.d.ts +1 -1
  273. package/dist/types/rules/html-no-duplicate-meta-names.d.ts +1 -1
  274. package/dist/types/rules/html-no-empty-attributes.d.ts +1 -1
  275. package/dist/types/rules/html-no-empty-headings.d.ts +1 -1
  276. package/dist/types/rules/html-no-nested-links.d.ts +1 -1
  277. package/dist/types/rules/html-no-positive-tab-index.d.ts +1 -1
  278. package/dist/types/rules/html-no-self-closing.d.ts +1 -1
  279. package/dist/types/rules/html-no-space-in-tag.d.ts +1 -1
  280. package/dist/types/rules/html-no-title-attribute.d.ts +1 -1
  281. package/dist/types/rules/html-no-underscores-in-attribute-names.d.ts +1 -1
  282. package/dist/types/{src/rules/html-body-only-elements.d.ts → rules/html-require-closing-tags.d.ts} +4 -3
  283. package/dist/types/rules/html-tag-name-lowercase.d.ts +1 -1
  284. package/dist/types/rules/index.d.ts +19 -0
  285. package/dist/types/rules/parser-no-errors.d.ts +1 -1
  286. package/dist/types/rules/rule-utils.d.ts +35 -88
  287. package/dist/types/rules/svg-tag-name-capitalization.d.ts +1 -1
  288. package/dist/types/{src/rules/html-aria-role-must-be-valid.d.ts → rules/turbo-permanent-require-id.d.ts} +2 -2
  289. package/dist/types/types.d.ts +25 -7
  290. package/dist/types/urls.d.ts +1 -0
  291. package/dist/{src/types.js → types.js} +53 -0
  292. package/dist/types.js.map +1 -0
  293. package/dist/urls.js +5 -0
  294. package/dist/urls.js.map +1 -0
  295. package/docs/rules/README.md +23 -2
  296. package/docs/rules/actionview-no-silent-helper.md +57 -0
  297. package/docs/rules/erb-no-conditional-html-element.md +90 -0
  298. package/docs/rules/erb-no-conditional-open-tag.md +130 -0
  299. package/docs/rules/erb-no-duplicate-branch-elements.md +98 -0
  300. package/docs/rules/erb-no-inline-case-conditions.md +85 -0
  301. package/docs/rules/erb-no-instance-variables-in-partials.md +43 -0
  302. package/docs/rules/erb-no-interpolated-class-names.md +57 -0
  303. package/docs/rules/erb-no-javascript-tag-helper.md +33 -0
  304. package/docs/rules/erb-no-output-in-attribute-name.md +38 -0
  305. package/docs/rules/erb-no-output-in-attribute-position.md +60 -0
  306. package/docs/rules/erb-no-raw-output-in-attribute-value.md +37 -0
  307. package/docs/rules/erb-no-statement-in-script.md +68 -0
  308. package/docs/rules/erb-no-then-in-control-flow.md +86 -0
  309. package/docs/rules/erb-no-trailing-whitespace.md +69 -0
  310. package/docs/rules/erb-no-unsafe-js-attribute.md +41 -0
  311. package/docs/rules/erb-no-unsafe-raw.md +47 -0
  312. package/docs/rules/erb-no-unsafe-script-interpolation.md +73 -0
  313. package/docs/rules/html-allowed-script-type.md +59 -0
  314. package/docs/rules/html-anchor-require-href.md +19 -6
  315. package/docs/rules/html-details-has-summary.md +46 -0
  316. package/docs/rules/html-img-require-alt.md +5 -3
  317. package/docs/rules/html-no-abstract-roles.md +74 -0
  318. package/docs/rules/html-no-aria-hidden-on-body.md +44 -0
  319. package/docs/rules/html-require-closing-tags.md +142 -0
  320. package/docs/rules/parser-no-errors.md +4 -17
  321. package/docs/rules/turbo-permanent-require-id.md +41 -0
  322. package/package.json +12 -11
  323. package/src/cli/argument-parser.ts +20 -2
  324. package/src/cli/file-processor.ts +189 -10
  325. package/src/cli/file-url.ts +6 -0
  326. package/src/cli/formatters/detailed-formatter.ts +19 -21
  327. package/src/cli/formatters/simple-formatter.ts +23 -13
  328. package/src/cli/index.ts +2 -0
  329. package/src/cli/lint-worker.ts +208 -0
  330. package/src/cli/summary-reporter.ts +14 -15
  331. package/src/cli.ts +5 -3
  332. package/src/custom-rule-loader.ts +20 -5
  333. package/src/herb-disable-comment-utils.ts +0 -3
  334. package/src/index.ts +1 -0
  335. package/src/linter.ts +98 -79
  336. package/src/parse-cache.ts +39 -0
  337. package/src/rules/actionview-no-silent-helper.ts +58 -0
  338. package/src/rules/erb-comment-syntax.ts +2 -2
  339. package/src/rules/erb-no-case-node-children.ts +2 -2
  340. package/src/rules/erb-no-conditional-html-element.ts +53 -0
  341. package/src/rules/erb-no-conditional-open-tag.ts +37 -0
  342. package/src/rules/erb-no-duplicate-branch-elements.ts +320 -0
  343. package/src/rules/erb-no-empty-tags.ts +2 -2
  344. package/src/rules/erb-no-extra-newline.ts +5 -25
  345. package/src/rules/erb-no-extra-whitespace-inside-tags.ts +45 -15
  346. package/src/rules/erb-no-inline-case-conditions.ts +54 -0
  347. package/src/rules/erb-no-instance-variables-in-partials.ts +101 -0
  348. package/src/rules/erb-no-interpolated-class-names.ts +65 -0
  349. package/src/rules/erb-no-javascript-tag-helper.ts +47 -0
  350. package/src/rules/erb-no-output-control-flow.ts +10 -10
  351. package/src/rules/erb-no-output-in-attribute-name.ts +39 -0
  352. package/src/rules/erb-no-output-in-attribute-position.ts +39 -0
  353. package/src/rules/erb-no-raw-output-in-attribute-value.ts +47 -0
  354. package/src/rules/erb-no-silent-tag-in-attribute-name.ts +2 -2
  355. package/src/rules/erb-no-statement-in-script.ts +82 -0
  356. package/src/rules/erb-no-then-in-control-flow.ts +62 -0
  357. package/src/rules/erb-no-trailing-whitespace.ts +187 -0
  358. package/src/rules/erb-no-unsafe-js-attribute.ts +47 -0
  359. package/src/rules/erb-no-unsafe-raw.ts +83 -0
  360. package/src/rules/erb-no-unsafe-script-interpolation.ts +76 -0
  361. package/src/rules/erb-prefer-image-tag-helper.ts +5 -4
  362. package/src/rules/erb-require-trailing-newline.ts +2 -2
  363. package/src/rules/erb-require-whitespace-inside-tags.ts +42 -18
  364. package/src/rules/erb-right-trim.ts +2 -2
  365. package/src/rules/erb-strict-locals-comment-syntax.ts +5 -5
  366. package/src/rules/erb-strict-locals-required.ts +2 -2
  367. package/src/rules/herb-disable-comment-malformed.ts +2 -2
  368. package/src/rules/herb-disable-comment-missing-rules.ts +2 -2
  369. package/src/rules/herb-disable-comment-no-duplicate-rules.ts +2 -2
  370. package/src/rules/herb-disable-comment-no-redundant-all.ts +2 -2
  371. package/src/rules/herb-disable-comment-unnecessary.ts +2 -2
  372. package/src/rules/herb-disable-comment-valid-rule-name.ts +2 -2
  373. package/src/rules/html-allowed-script-type.ts +84 -0
  374. package/src/rules/html-anchor-require-href.ts +73 -11
  375. package/src/rules/html-aria-attribute-must-be-valid.ts +3 -3
  376. package/src/rules/html-aria-label-is-well-formatted.ts +3 -3
  377. package/src/rules/html-aria-level-must-be-valid.ts +3 -3
  378. package/src/rules/html-aria-role-heading-requires-level.ts +5 -4
  379. package/src/rules/html-aria-role-must-be-valid.ts +3 -3
  380. package/src/rules/html-attribute-double-quotes.ts +4 -4
  381. package/src/rules/html-attribute-equals-spacing.ts +2 -2
  382. package/src/rules/html-attribute-values-require-quotes.ts +2 -2
  383. package/src/rules/html-avoid-both-disabled-and-aria-disabled.ts +10 -11
  384. package/src/rules/html-body-only-elements.ts +5 -4
  385. package/src/rules/html-boolean-attributes-no-value.ts +4 -3
  386. package/src/rules/html-details-has-summary.ts +69 -0
  387. package/src/rules/html-head-only-elements.ts +6 -5
  388. package/src/rules/html-iframe-has-title.ts +8 -11
  389. package/src/rules/html-img-require-alt.ts +16 -5
  390. package/src/rules/html-input-require-autocomplete.ts +7 -10
  391. package/src/rules/html-navigation-has-label.ts +6 -5
  392. package/src/rules/html-no-abstract-roles.ts +40 -0
  393. package/src/rules/html-no-aria-hidden-on-body.ts +58 -0
  394. package/src/rules/html-no-aria-hidden-on-focusable.ts +6 -5
  395. package/src/rules/html-no-block-inside-inline.ts +7 -13
  396. package/src/rules/html-no-duplicate-attributes.ts +4 -3
  397. package/src/rules/html-no-duplicate-ids.ts +16 -13
  398. package/src/rules/html-no-duplicate-meta-names.ts +20 -19
  399. package/src/rules/html-no-empty-attributes.ts +2 -2
  400. package/src/rules/html-no-empty-headings.ts +44 -58
  401. package/src/rules/html-no-nested-links.ts +25 -16
  402. package/src/rules/html-no-positive-tab-index.ts +3 -3
  403. package/src/rules/html-no-self-closing.ts +5 -5
  404. package/src/rules/html-no-space-in-tag.ts +5 -8
  405. package/src/rules/html-no-title-attribute.ts +6 -5
  406. package/src/rules/html-no-underscores-in-attribute-names.ts +2 -2
  407. package/src/rules/html-require-closing-tags.ts +41 -0
  408. package/src/rules/html-tag-name-lowercase.ts +14 -9
  409. package/src/rules/index.ts +19 -0
  410. package/src/rules/parser-no-errors.ts +3 -3
  411. package/src/rules/rule-utils.ts +162 -279
  412. package/src/rules/svg-tag-name-capitalization.ts +10 -10
  413. package/src/rules/turbo-permanent-require-id.ts +49 -0
  414. package/src/rules.ts +60 -10
  415. package/src/types.ts +76 -7
  416. package/src/urls.ts +5 -0
  417. package/dist/package.json +0 -65
  418. package/dist/src/cli/argument-parser.js.map +0 -1
  419. package/dist/src/cli/file-processor.js.map +0 -1
  420. package/dist/src/cli/formatters/base-formatter.js.map +0 -1
  421. package/dist/src/cli/formatters/detailed-formatter.js.map +0 -1
  422. package/dist/src/cli/formatters/github-actions-formatter.js.map +0 -1
  423. package/dist/src/cli/formatters/index.js.map +0 -1
  424. package/dist/src/cli/formatters/json-formatter.js.map +0 -1
  425. package/dist/src/cli/formatters/simple-formatter.js +0 -44
  426. package/dist/src/cli/formatters/simple-formatter.js.map +0 -1
  427. package/dist/src/cli/index.js.map +0 -1
  428. package/dist/src/cli/output-manager.js.map +0 -1
  429. package/dist/src/cli/summary-reporter.js.map +0 -1
  430. package/dist/src/cli.js.map +0 -1
  431. package/dist/src/custom-rule-loader.js.map +0 -1
  432. package/dist/src/herb-disable-comment-utils.js.map +0 -1
  433. package/dist/src/herb-lint.js +0 -5
  434. package/dist/src/herb-lint.js.map +0 -1
  435. package/dist/src/index.js +0 -5
  436. package/dist/src/index.js.map +0 -1
  437. package/dist/src/linter-ignore.js.map +0 -1
  438. package/dist/src/linter.js.map +0 -1
  439. package/dist/src/loader.js +0 -17
  440. package/dist/src/loader.js.map +0 -1
  441. package/dist/src/rules/erb-comment-syntax.js.map +0 -1
  442. package/dist/src/rules/erb-no-case-node-children.js.map +0 -1
  443. package/dist/src/rules/erb-no-empty-tags.js.map +0 -1
  444. package/dist/src/rules/erb-no-extra-newline.js.map +0 -1
  445. package/dist/src/rules/erb-no-extra-whitespace-inside-tags.js.map +0 -1
  446. package/dist/src/rules/erb-no-output-control-flow.js.map +0 -1
  447. package/dist/src/rules/erb-no-silent-tag-in-attribute-name.js.map +0 -1
  448. package/dist/src/rules/erb-prefer-image-tag-helper.js.map +0 -1
  449. package/dist/src/rules/erb-require-trailing-newline.js.map +0 -1
  450. package/dist/src/rules/erb-require-whitespace-inside-tags.js.map +0 -1
  451. package/dist/src/rules/erb-right-trim.js.map +0 -1
  452. package/dist/src/rules/erb-strict-locals-comment-syntax.js.map +0 -1
  453. package/dist/src/rules/erb-strict-locals-required.js.map +0 -1
  454. package/dist/src/rules/file-utils.js.map +0 -1
  455. package/dist/src/rules/herb-disable-comment-base.js.map +0 -1
  456. package/dist/src/rules/herb-disable-comment-malformed.js.map +0 -1
  457. package/dist/src/rules/herb-disable-comment-missing-rules.js.map +0 -1
  458. package/dist/src/rules/herb-disable-comment-no-duplicate-rules.js.map +0 -1
  459. package/dist/src/rules/herb-disable-comment-no-redundant-all.js.map +0 -1
  460. package/dist/src/rules/herb-disable-comment-unnecessary.js.map +0 -1
  461. package/dist/src/rules/herb-disable-comment-valid-rule-name.js.map +0 -1
  462. package/dist/src/rules/html-anchor-require-href.js +0 -32
  463. package/dist/src/rules/html-anchor-require-href.js.map +0 -1
  464. package/dist/src/rules/html-aria-attribute-must-be-valid.js.map +0 -1
  465. package/dist/src/rules/html-aria-label-is-well-formatted.js.map +0 -1
  466. package/dist/src/rules/html-aria-level-must-be-valid.js.map +0 -1
  467. package/dist/src/rules/html-aria-role-heading-requires-level.js.map +0 -1
  468. package/dist/src/rules/html-aria-role-must-be-valid.js.map +0 -1
  469. package/dist/src/rules/html-attribute-double-quotes.js.map +0 -1
  470. package/dist/src/rules/html-attribute-equals-spacing.js.map +0 -1
  471. package/dist/src/rules/html-attribute-values-require-quotes.js.map +0 -1
  472. package/dist/src/rules/html-avoid-both-disabled-and-aria-disabled.js.map +0 -1
  473. package/dist/src/rules/html-body-only-elements.js.map +0 -1
  474. package/dist/src/rules/html-boolean-attributes-no-value.js.map +0 -1
  475. package/dist/src/rules/html-head-only-elements.js.map +0 -1
  476. package/dist/src/rules/html-iframe-has-title.js.map +0 -1
  477. package/dist/src/rules/html-img-require-alt.js.map +0 -1
  478. package/dist/src/rules/html-input-require-autocomplete.js.map +0 -1
  479. package/dist/src/rules/html-navigation-has-label.js.map +0 -1
  480. package/dist/src/rules/html-no-aria-hidden-on-focusable.js.map +0 -1
  481. package/dist/src/rules/html-no-block-inside-inline.js.map +0 -1
  482. package/dist/src/rules/html-no-duplicate-attributes.js.map +0 -1
  483. package/dist/src/rules/html-no-duplicate-ids.js.map +0 -1
  484. package/dist/src/rules/html-no-duplicate-meta-names.js.map +0 -1
  485. package/dist/src/rules/html-no-empty-attributes.js.map +0 -1
  486. package/dist/src/rules/html-no-empty-headings.js +0 -115
  487. package/dist/src/rules/html-no-empty-headings.js.map +0 -1
  488. package/dist/src/rules/html-no-nested-links.js.map +0 -1
  489. package/dist/src/rules/html-no-positive-tab-index.js.map +0 -1
  490. package/dist/src/rules/html-no-self-closing.js.map +0 -1
  491. package/dist/src/rules/html-no-space-in-tag.js.map +0 -1
  492. package/dist/src/rules/html-no-title-attribute.js.map +0 -1
  493. package/dist/src/rules/html-no-underscores-in-attribute-names.js.map +0 -1
  494. package/dist/src/rules/html-tag-name-lowercase.js.map +0 -1
  495. package/dist/src/rules/index.js.map +0 -1
  496. package/dist/src/rules/parser-no-errors.js.map +0 -1
  497. package/dist/src/rules/rule-utils.js.map +0 -1
  498. package/dist/src/rules/string-utils.js.map +0 -1
  499. package/dist/src/rules/svg-tag-name-capitalization.js.map +0 -1
  500. package/dist/src/rules.js.map +0 -1
  501. package/dist/src/types.js.map +0 -1
  502. package/dist/tsconfig.tsbuildinfo +0 -1
  503. package/dist/types/src/cli/argument-parser.d.ts +0 -25
  504. package/dist/types/src/cli/file-processor.d.ts +0 -43
  505. package/dist/types/src/cli/formatters/base-formatter.d.ts +0 -6
  506. package/dist/types/src/cli/formatters/detailed-formatter.d.ts +0 -13
  507. package/dist/types/src/cli/formatters/github-actions-formatter.d.ts +0 -17
  508. package/dist/types/src/cli/formatters/index.d.ts +0 -5
  509. package/dist/types/src/cli/formatters/json-formatter.d.ts +0 -48
  510. package/dist/types/src/cli/formatters/simple-formatter.d.ts +0 -8
  511. package/dist/types/src/cli/index.d.ts +0 -5
  512. package/dist/types/src/cli/output-manager.d.ts +0 -32
  513. package/dist/types/src/cli/summary-reporter.d.ts +0 -28
  514. package/dist/types/src/cli.d.ts +0 -28
  515. package/dist/types/src/custom-rule-loader.d.ts +0 -62
  516. package/dist/types/src/herb-disable-comment-utils.d.ts +0 -69
  517. package/dist/types/src/herb-lint.d.ts +0 -2
  518. package/dist/types/src/index.d.ts +0 -4
  519. package/dist/types/src/linter-ignore.d.ts +0 -12
  520. package/dist/types/src/linter.d.ts +0 -133
  521. package/dist/types/src/loader.d.ts +0 -20
  522. package/dist/types/src/rules/erb-comment-syntax.d.ts +0 -14
  523. package/dist/types/src/rules/erb-no-extra-newline.d.ts +0 -14
  524. package/dist/types/src/rules/erb-no-extra-whitespace-inside-tags.d.ts +0 -18
  525. package/dist/types/src/rules/erb-require-trailing-newline.d.ts +0 -9
  526. package/dist/types/src/rules/erb-require-whitespace-inside-tags.d.ts +0 -18
  527. package/dist/types/src/rules/erb-right-trim.d.ts +0 -14
  528. package/dist/types/src/rules/erb-strict-locals-comment-syntax.d.ts +0 -9
  529. package/dist/types/src/rules/erb-strict-locals-required.d.ts +0 -9
  530. package/dist/types/src/rules/file-utils.d.ts +0 -13
  531. package/dist/types/src/rules/herb-disable-comment-base.d.ts +0 -37
  532. package/dist/types/src/rules/herb-disable-comment-no-duplicate-rules.d.ts +0 -8
  533. package/dist/types/src/rules/herb-disable-comment-no-redundant-all.d.ts +0 -8
  534. package/dist/types/src/rules/herb-disable-comment-unnecessary.d.ts +0 -8
  535. package/dist/types/src/rules/herb-disable-comment-valid-rule-name.d.ts +0 -8
  536. package/dist/types/src/rules/html-aria-label-is-well-formatted.d.ts +0 -8
  537. package/dist/types/src/rules/html-aria-role-heading-requires-level.d.ts +0 -8
  538. package/dist/types/src/rules/html-attribute-double-quotes.d.ts +0 -15
  539. package/dist/types/src/rules/html-attribute-equals-spacing.d.ts +0 -14
  540. package/dist/types/src/rules/html-attribute-values-require-quotes.d.ts +0 -15
  541. package/dist/types/src/rules/html-avoid-both-disabled-and-aria-disabled.d.ts +0 -8
  542. package/dist/types/src/rules/html-boolean-attributes-no-value.d.ts +0 -14
  543. package/dist/types/src/rules/html-head-only-elements.d.ts +0 -9
  544. package/dist/types/src/rules/html-iframe-has-title.d.ts +0 -8
  545. package/dist/types/src/rules/html-img-require-alt.d.ts +0 -8
  546. package/dist/types/src/rules/html-input-require-autocomplete.d.ts +0 -8
  547. package/dist/types/src/rules/html-no-block-inside-inline.d.ts +0 -8
  548. package/dist/types/src/rules/html-no-duplicate-ids.d.ts +0 -8
  549. package/dist/types/src/rules/html-no-duplicate-meta-names.d.ts +0 -9
  550. package/dist/types/src/rules/html-no-nested-links.d.ts +0 -8
  551. package/dist/types/src/rules/html-no-self-closing.d.ts +0 -16
  552. package/dist/types/src/rules/html-no-space-in-tag.d.ts +0 -16
  553. package/dist/types/src/rules/html-no-title-attribute.d.ts +0 -8
  554. package/dist/types/src/rules/html-no-underscores-in-attribute-names.d.ts +0 -8
  555. package/dist/types/src/rules/html-tag-name-lowercase.d.ts +0 -18
  556. package/dist/types/src/rules/index.d.ts +0 -54
  557. package/dist/types/src/rules/parser-no-errors.d.ts +0 -9
  558. package/dist/types/src/rules/rule-utils.d.ts +0 -351
  559. package/dist/types/src/rules/string-utils.d.ts +0 -15
  560. package/dist/types/src/rules/svg-tag-name-capitalization.d.ts +0 -16
  561. package/dist/types/src/rules.d.ts +0 -2
  562. package/dist/types/src/types.d.ts +0 -190
  563. /package/dist/{src/cli → cli}/formatters/base-formatter.js +0 -0
  564. /package/dist/{src/cli → cli}/formatters/github-actions-formatter.js +0 -0
  565. /package/dist/{src/cli → cli}/formatters/index.js +0 -0
  566. /package/dist/{src/cli → cli}/formatters/json-formatter.js +0 -0
  567. /package/dist/{src/cli → cli}/index.js +0 -0
  568. /package/dist/{src/cli → cli}/output-manager.js +0 -0
  569. /package/dist/{src/herb-disable-comment-utils.js → herb-disable-comment-utils.js} +0 -0
  570. /package/dist/{src/linter-ignore.js → linter-ignore.js} +0 -0
  571. /package/dist/{src/rules → rules}/file-utils.js +0 -0
  572. /package/dist/{src/rules → rules}/herb-disable-comment-base.js +0 -0
  573. /package/dist/{src/rules → rules}/string-utils.js +0 -0
@@ -1,5 +1,5 @@
1
- import { getTagName } from "@herb-tools/core"
2
- import { BaseRuleVisitor, getAttribute, getAttributeValue, getStaticAttributeValueContent } from "./rule-utils.js"
1
+ import { getTagLocalName, getStaticAttributeValue, getAttribute, getAttributeValue } from "@herb-tools/core"
2
+ import { BaseRuleVisitor } from "./rule-utils.js"
3
3
  import { ParserRule } from "../types.js"
4
4
 
5
5
  import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
@@ -30,10 +30,7 @@ class HTMLInputRequireAutocompleteVisitor extends BaseRuleVisitor {
30
30
  private checkInputTag(node: HTMLOpenTagNode): void {
31
31
  if (!this.isInputTag(node) || this.hasAutocomplete(node)) return
32
32
 
33
- const typeAttribute = getAttribute(node, "type");
34
- if (!typeAttribute) return
35
-
36
- const typeValue = getStaticAttributeValueContent(typeAttribute)
33
+ const typeValue = getStaticAttributeValue(node, "type")
37
34
  if (!typeValue) return
38
35
 
39
36
  if (!this.HTML_INPUT_TYPES_REQUIRING_AUTOCOMPLETE.has(typeValue)) return
@@ -55,7 +52,7 @@ class HTMLInputRequireAutocompleteVisitor extends BaseRuleVisitor {
55
52
  }
56
53
 
57
54
  private isInputTag(node: HTMLOpenTagNode) {
58
- const tagName = getTagName(node);
55
+ const tagName = getTagLocalName(node);
59
56
 
60
57
  if (tagName === "input") {
61
58
  return true
@@ -66,17 +63,17 @@ class HTMLInputRequireAutocompleteVisitor extends BaseRuleVisitor {
66
63
  }
67
64
 
68
65
  export class HTMLInputRequireAutocompleteRule extends ParserRule {
69
- name = "html-input-require-autocomplete"
66
+ static ruleName = "html-input-require-autocomplete"
70
67
 
71
68
  get defaultConfig(): FullRuleConfig {
72
69
  return {
73
70
  enabled: true,
74
- severity: "error"
71
+ severity: "warning"
75
72
  }
76
73
  }
77
74
 
78
75
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
79
- const visitor = new HTMLInputRequireAutocompleteVisitor(this.name, context)
76
+ const visitor = new HTMLInputRequireAutocompleteVisitor(this.ruleName, context)
80
77
 
81
78
  visitor.visit(result.value)
82
79
 
@@ -1,5 +1,6 @@
1
1
  import { ParserRule } from "../types.js"
2
- import { BaseRuleVisitor, getTagName, hasAttribute, getAttributeValue, findAttributeByName, getAttributes } from "./rule-utils.js"
2
+ import { BaseRuleVisitor } from "./rule-utils.js"
3
+ import { hasAttribute, getAttributeValue, findAttributeByName, getAttributes, getTagLocalName } from "@herb-tools/core"
3
4
 
4
5
  import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
5
6
  import type { HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
@@ -11,7 +12,7 @@ class NavigationHasLabelVisitor extends BaseRuleVisitor {
11
12
  }
12
13
 
13
14
  private checkNavigationElement(node: HTMLOpenTagNode): void {
14
- const tagName = getTagName(node)
15
+ const tagName = getTagLocalName(node)
15
16
  const isNavElement = tagName === "nav"
16
17
  const hasNavigationRole = this.hasRoleNavigation(node)
17
18
 
@@ -51,17 +52,17 @@ class NavigationHasLabelVisitor extends BaseRuleVisitor {
51
52
  }
52
53
 
53
54
  export class HTMLNavigationHasLabelRule extends ParserRule {
54
- name = "html-navigation-has-label"
55
+ static ruleName = "html-navigation-has-label"
55
56
 
56
57
  get defaultConfig(): FullRuleConfig {
57
58
  return {
58
59
  enabled: false,
59
- severity: "error"
60
+ severity: "warning"
60
61
  }
61
62
  }
62
63
 
63
64
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
64
- const visitor = new NavigationHasLabelVisitor(this.name, context)
65
+ const visitor = new NavigationHasLabelVisitor(this.ruleName, context)
65
66
 
66
67
  visitor.visit(result.value)
67
68
 
@@ -0,0 +1,40 @@
1
+ import { ParserRule } from "../types.js"
2
+ import { AttributeVisitorMixin, ABSTRACT_ARIA_ROLES, StaticAttributeStaticValueParams } from "./rule-utils.js"
3
+
4
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
5
+ import type { ParseResult } from "@herb-tools/core"
6
+
7
+ class NoAbstractRolesVisitor extends AttributeVisitorMixin {
8
+ protected checkStaticAttributeStaticValue({ attributeName, attributeValue, attributeNode }: StaticAttributeStaticValueParams): void {
9
+ if (attributeName !== "role") return
10
+ if (!attributeValue) return
11
+
12
+ const normalizedValue = attributeValue.toLowerCase()
13
+
14
+ if (!ABSTRACT_ARIA_ROLES.has(normalizedValue)) return
15
+
16
+ this.addOffense(
17
+ `The \`role\` attribute must not use abstract ARIA role \`${attributeValue}\`. Abstract roles are not meant to be used directly.`,
18
+ attributeNode.location,
19
+ )
20
+ }
21
+ }
22
+
23
+ export class HTMLNoAbstractRolesRule extends ParserRule {
24
+ static ruleName = "html-no-abstract-roles"
25
+
26
+ get defaultConfig(): FullRuleConfig {
27
+ return {
28
+ enabled: true,
29
+ severity: "warning"
30
+ }
31
+ }
32
+
33
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
34
+ const visitor = new NoAbstractRolesVisitor(this.ruleName, context)
35
+
36
+ visitor.visit(result.value)
37
+
38
+ return visitor.offenses
39
+ }
40
+ }
@@ -0,0 +1,58 @@
1
+ import { ParserRule } from "../types.js"
2
+ import { BaseRuleVisitor } from "./rule-utils.js"
3
+ import { hasAttribute, getAttributeValue, findAttributeByName, getAttributes, getTagLocalName } from "@herb-tools/core"
4
+
5
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
6
+ import type { HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
7
+
8
+ class NoAriaHiddenBodyVisitor extends BaseRuleVisitor {
9
+ visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
10
+ this.checkAriaHiddenOnBody(node)
11
+ super.visitHTMLOpenTagNode(node)
12
+ }
13
+
14
+ private checkAriaHiddenOnBody(node: HTMLOpenTagNode): void {
15
+ const tagName = getTagLocalName(node)
16
+
17
+ if (tagName !== "body") return
18
+
19
+ if (this.hasAriaHidden(node)) {
20
+ this.addOffense(
21
+ "The `aria-hidden` attribute should never be present on the `<body>` element, as it hides the entire document from assistive technology users.",
22
+ node.tag_name!.location,
23
+ )
24
+ }
25
+ }
26
+
27
+ private hasAriaHidden(node: HTMLOpenTagNode): boolean {
28
+ if (!hasAttribute(node, "aria-hidden")) return false
29
+
30
+ const attributes = getAttributes(node)
31
+ const ariaHiddenAttr = findAttributeByName(attributes, "aria-hidden")
32
+
33
+ if (!ariaHiddenAttr) return false
34
+
35
+ const value = getAttributeValue(ariaHiddenAttr)
36
+
37
+ return value === null || value === "" || value === "true"
38
+ }
39
+ }
40
+
41
+ export class HTMLNoAriaHiddenOnBodyRule extends ParserRule {
42
+ static ruleName = "html-no-aria-hidden-on-body"
43
+
44
+ get defaultConfig(): FullRuleConfig {
45
+ return {
46
+ enabled: true,
47
+ severity: "warning"
48
+ }
49
+ }
50
+
51
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
52
+ const visitor = new NoAriaHiddenBodyVisitor(this.ruleName, context)
53
+
54
+ visitor.visit(result.value)
55
+
56
+ return visitor.offenses
57
+ }
58
+ }
@@ -1,5 +1,6 @@
1
1
  import { ParserRule } from "../types.js"
2
- import { BaseRuleVisitor, getTagName, hasAttribute, getAttributeValue, findAttributeByName, getAttributes } from "./rule-utils.js"
2
+ import { BaseRuleVisitor } from "./rule-utils.js"
3
+ import { hasAttribute, getAttributeValue, findAttributeByName, getAttributes, getTagLocalName } from "@herb-tools/core"
3
4
 
4
5
  import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
5
6
  import type { HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
@@ -37,7 +38,7 @@ class NoAriaHiddenOnFocusableVisitor extends BaseRuleVisitor {
37
38
  }
38
39
 
39
40
  private isFocusable(node: HTMLOpenTagNode): boolean {
40
- const tagName = getTagName(node)
41
+ const tagName = getTagLocalName(node)
41
42
  if (!tagName) return false
42
43
 
43
44
  const tabIndexValue = this.getTabIndexValue(node)
@@ -77,17 +78,17 @@ class NoAriaHiddenOnFocusableVisitor extends BaseRuleVisitor {
77
78
  }
78
79
 
79
80
  export class HTMLNoAriaHiddenOnFocusableRule extends ParserRule {
80
- name = "html-no-aria-hidden-on-focusable"
81
+ static ruleName = "html-no-aria-hidden-on-focusable"
81
82
 
82
83
  get defaultConfig(): FullRuleConfig {
83
84
  return {
84
85
  enabled: true,
85
- severity: "error"
86
+ severity: "warning"
86
87
  }
87
88
  }
88
89
 
89
90
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
90
- const visitor = new NoAriaHiddenOnFocusableVisitor(this.name, context)
91
+ const visitor = new NoAriaHiddenOnFocusableVisitor(this.ruleName, context)
91
92
 
92
93
  visitor.visit(result.value)
93
94
 
@@ -1,16 +1,13 @@
1
1
  import { BaseRuleVisitor, isInlineElement, isBlockElement } from "./rule-utils.js"
2
-
3
2
  import { ParserRule } from "../types.js"
3
+ import { isHTMLOpenTagNode } from "@herb-tools/core"
4
+
4
5
  import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
5
6
  import type { HTMLOpenTagNode, HTMLElementNode, ParseResult } from "@herb-tools/core"
6
7
 
7
8
  class BlockInsideInlineVisitor extends BaseRuleVisitor {
8
9
  private inlineStack: string[] = []
9
10
 
10
- private isValidHTMLOpenTag(node: HTMLElementNode): boolean {
11
- return !!(node.open_tag && node.open_tag.type === "AST_HTML_OPEN_TAG_NODE")
12
- }
13
-
14
11
  private getElementType(tagName: string): { isInline: boolean; isBlock: boolean; isUnknown: boolean } {
15
12
  const isInline = isInlineElement(tagName)
16
13
  const isBlock = isBlockElement(tagName)
@@ -43,25 +40,22 @@ class BlockInsideInlineVisitor extends BaseRuleVisitor {
43
40
  }
44
41
 
45
42
  visitHTMLElementNode(node: HTMLElementNode): void {
46
- if (!this.isValidHTMLOpenTag(node)) {
43
+ if (!isHTMLOpenTagNode(node.open_tag)) {
47
44
  super.visitHTMLElementNode(node)
48
-
49
45
  return
50
46
  }
51
47
 
52
- const openTag = node.open_tag as HTMLOpenTagNode
53
- const tagName = openTag.tag_name?.value.toLowerCase()
48
+ const tagName = node.open_tag.tag_name?.value.toLowerCase()
54
49
 
55
50
  if (!tagName) {
56
51
  super.visitHTMLElementNode(node)
57
-
58
52
  return
59
53
  }
60
54
 
61
55
  const { isInline, isBlock, isUnknown } = this.getElementType(tagName)
62
56
 
63
57
  if ((isBlock || isUnknown) && this.inlineStack.length > 0) {
64
- this.addOffenseMessage(tagName, isBlock, openTag)
58
+ this.addOffenseMessage(tagName, isBlock, node.open_tag)
65
59
  }
66
60
 
67
61
  if (isInline) {
@@ -74,7 +68,7 @@ class BlockInsideInlineVisitor extends BaseRuleVisitor {
74
68
  }
75
69
 
76
70
  export class HTMLNoBlockInsideInlineRule extends ParserRule {
77
- name = "html-no-block-inside-inline"
71
+ static ruleName = "html-no-block-inside-inline"
78
72
 
79
73
  get defaultConfig(): FullRuleConfig {
80
74
  return {
@@ -84,7 +78,7 @@ export class HTMLNoBlockInsideInlineRule extends ParserRule {
84
78
  }
85
79
 
86
80
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
87
- const visitor = new BlockInsideInlineVisitor(this.name, context)
81
+ const visitor = new BlockInsideInlineVisitor(this.ruleName, context)
88
82
  visitor.visit(result.value)
89
83
  return visitor.offenses
90
84
  }
@@ -1,5 +1,6 @@
1
1
  import { ParserRule, BaseAutofixContext } from "../types.js"
2
- import { ControlFlowTrackingVisitor, ControlFlowType, getAttributeName } from "./rule-utils.js"
2
+ import { ControlFlowTrackingVisitor, ControlFlowType } from "./rule-utils.js"
3
+ import { getAttributeName } from "@herb-tools/core"
3
4
 
4
5
  import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
5
6
  import type { HTMLOpenTagNode, HTMLAttributeNode, ParseResult, Location } from "@herb-tools/core"
@@ -162,7 +163,7 @@ class NoDuplicateAttributesVisitor extends ControlFlowTrackingVisitor<
162
163
  }
163
164
 
164
165
  export class HTMLNoDuplicateAttributesRule extends ParserRule {
165
- name = "html-no-duplicate-attributes"
166
+ static ruleName = "html-no-duplicate-attributes"
166
167
 
167
168
  get defaultConfig(): FullRuleConfig {
168
169
  return {
@@ -172,7 +173,7 @@ export class HTMLNoDuplicateAttributesRule extends ParserRule {
172
173
  }
173
174
 
174
175
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
175
- const visitor = new NoDuplicateAttributesVisitor(this.name, context)
176
+ const visitor = new NoDuplicateAttributesVisitor(this.ruleName, context)
176
177
 
177
178
  visitor.visit(result.value)
178
179
 
@@ -93,40 +93,41 @@ class NoDuplicateIdsVisitor extends ControlFlowTrackingVisitor<BaseAutofixContex
93
93
  return getStaticAttributeName(attributeNode.name) === "id"
94
94
  }
95
95
 
96
- private extractIdValue(attributeNode: HTMLAttributeNode): { identifier: string; shouldTrackDuplicates: boolean } | null {
96
+ private extractIdValue(attributeNode: HTMLAttributeNode): { identifier: string; shouldTrackDuplicates: boolean; isDynamic: boolean } | null {
97
97
  const valueNodes = attributeNode.value?.children || []
98
+ const isDynamic = hasERBOutput(valueNodes)
98
99
 
99
- if (hasERBOutput(valueNodes) && this.isInControlFlow && this.currentControlFlowType === ControlFlowType.LOOP) {
100
+ if (isDynamic && this.isInControlFlow && this.currentControlFlowType === ControlFlowType.LOOP) {
100
101
  return null
101
102
  }
102
103
 
103
104
  const identifier = isEffectivelyStatic(valueNodes) ? getValidatableStaticContent(valueNodes) : OutputPrinter.print(valueNodes)
104
105
  if (!identifier) return null
105
106
 
106
- return { identifier, shouldTrackDuplicates: true }
107
+ return { identifier, shouldTrackDuplicates: true, isDynamic }
107
108
  }
108
109
 
109
110
  private isWhitespaceOnlyId(identifier: string): boolean {
110
111
  return identifier !== '' && identifier.trim() === ''
111
112
  }
112
113
 
113
- private processIdDuplicate(idValue: { identifier: string; shouldTrackDuplicates: boolean }, attributeNode: HTMLAttributeNode): void {
114
- const { identifier, shouldTrackDuplicates } = idValue
114
+ private processIdDuplicate(idValue: { identifier: string; shouldTrackDuplicates: boolean; isDynamic: boolean }, attributeNode: HTMLAttributeNode): void {
115
+ const { identifier, shouldTrackDuplicates, isDynamic } = idValue
115
116
 
116
117
  if (!shouldTrackDuplicates) return
117
118
 
118
119
  if (this.isInControlFlow) {
119
- this.handleControlFlowId(identifier, attributeNode)
120
+ this.handleControlFlowId(identifier, attributeNode, isDynamic)
120
121
  } else {
121
122
  this.handleGlobalId(identifier, attributeNode)
122
123
  }
123
124
  }
124
125
 
125
- private handleControlFlowId(identifier: string, attributeNode: HTMLAttributeNode): void {
126
+ private handleControlFlowId(identifier: string, attributeNode: HTMLAttributeNode, isDynamic: boolean): void {
126
127
  if (this.currentControlFlowType === ControlFlowType.LOOP) {
127
128
  this.handleLoopId(identifier, attributeNode)
128
129
  } else {
129
- this.handleConditionalId(identifier, attributeNode)
130
+ this.handleConditionalId(identifier, attributeNode, isDynamic)
130
131
  }
131
132
 
132
133
  this.currentBranchIds.add(identifier)
@@ -145,18 +146,20 @@ class NoDuplicateIdsVisitor extends ControlFlowTrackingVisitor<BaseAutofixContex
145
146
  }
146
147
  }
147
148
 
148
- private handleConditionalId(identifier: string, attributeNode: HTMLAttributeNode): void {
149
+ private handleConditionalId(identifier: string, attributeNode: HTMLAttributeNode, isDynamic: boolean): void {
149
150
  if (this.currentBranchIds.has(identifier)) {
150
151
  this.addSameBranchOffense(identifier, attributeNode.location)
151
152
  return
152
153
  }
153
154
 
154
- if (this.documentIds.has(identifier)) {
155
+ if (!isDynamic && this.documentIds.has(identifier)) {
155
156
  this.addDuplicateIdOffense(identifier, attributeNode.location)
156
157
  return
157
158
  }
158
159
 
159
- this.controlFlowIds.add(identifier)
160
+ if (!isDynamic) {
161
+ this.controlFlowIds.add(identifier)
162
+ }
160
163
  }
161
164
 
162
165
  private handleGlobalId(identifier: string, attributeNode: HTMLAttributeNode): void {
@@ -199,7 +202,7 @@ class NoDuplicateIdsVisitor extends ControlFlowTrackingVisitor<BaseAutofixContex
199
202
  }
200
203
 
201
204
  export class HTMLNoDuplicateIdsRule extends ParserRule {
202
- name = "html-no-duplicate-ids"
205
+ static ruleName = "html-no-duplicate-ids"
203
206
 
204
207
  get defaultConfig(): FullRuleConfig {
205
208
  return {
@@ -209,7 +212,7 @@ export class HTMLNoDuplicateIdsRule extends ParserRule {
209
212
  }
210
213
 
211
214
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
212
- const visitor = new NoDuplicateIdsVisitor(this.name, context)
215
+ const visitor = new NoDuplicateIdsVisitor(this.ruleName, context)
213
216
 
214
217
  visitor.visit(result.value)
215
218
 
@@ -1,5 +1,5 @@
1
- import { isHTMLElementNode } from "@herb-tools/core"
2
- import { getTagName, getAttributeName, getAttributeValue, forEachAttribute } from "./rule-utils"
1
+ import { isHTMLElementNode, isHTMLOpenTagNode, getAttributeName, getAttributeValue, forEachAttribute } from "@herb-tools/core"
2
+ import { getTagLocalName } from "@herb-tools/core"
3
3
 
4
4
  import { ControlFlowTrackingVisitor, ControlFlowType } from "./rule-utils"
5
5
  import { ParserRule, BaseAutofixContext } from "../types"
@@ -30,7 +30,7 @@ class HTMLNoDuplicateMetaNamesVisitor extends ControlFlowTrackingVisitor<BaseAut
30
30
  private controlFlowMetas: MetaTag[] = []
31
31
 
32
32
  visitHTMLElementNode(node: HTMLElementNode): void {
33
- const tagName = getTagName(node)?.toLowerCase()
33
+ const tagName = getTagLocalName(node)
34
34
  if (!tagName) return
35
35
 
36
36
  if (tagName === "head") {
@@ -104,20 +104,21 @@ class HTMLNoDuplicateMetaNamesVisitor extends ControlFlowTrackingVisitor<BaseAut
104
104
  }
105
105
 
106
106
  private extractAttributes(node: HTMLElementNode, metaTag: MetaTag): void {
107
- if (isHTMLElementNode(node) && node.open_tag) {
108
- forEachAttribute(node.open_tag as any, (attributeNode: HTMLAttributeNode) => {
109
- const name = getAttributeName(attributeNode)
110
- const value = getAttributeValue(attributeNode)?.trim()
111
-
112
- if (name === "name" && value) {
113
- metaTag.nameValue = value
114
- } else if (name === "http-equiv" && value) {
115
- metaTag.httpEquivValue = value
116
- } else if (name === "media" && value) {
117
- metaTag.mediaValue = value
118
- }
119
- })
120
- }
107
+ if (!isHTMLElementNode(node)) return
108
+ if (!isHTMLOpenTagNode(node.open_tag)) return
109
+
110
+ forEachAttribute(node.open_tag, (attributeNode: HTMLAttributeNode) => {
111
+ const name = getAttributeName(attributeNode)
112
+ const value = getAttributeValue(attributeNode)?.trim()
113
+
114
+ if (name === "name" && value) {
115
+ metaTag.nameValue = value
116
+ } else if (name === "http-equiv" && value) {
117
+ metaTag.httpEquivValue = value
118
+ } else if (name === "media" && value) {
119
+ metaTag.mediaValue = value
120
+ }
121
+ })
121
122
  }
122
123
 
123
124
  private handleControlFlowMeta(metaTag: MetaTag): void {
@@ -182,7 +183,7 @@ class HTMLNoDuplicateMetaNamesVisitor extends ControlFlowTrackingVisitor<BaseAut
182
183
 
183
184
  export class HTMLNoDuplicateMetaNamesRule extends ParserRule {
184
185
  static autocorrectable = false
185
- name = "html-no-duplicate-meta-names"
186
+ static ruleName = "html-no-duplicate-meta-names"
186
187
 
187
188
  get defaultConfig(): FullRuleConfig {
188
189
  return {
@@ -192,7 +193,7 @@ export class HTMLNoDuplicateMetaNamesRule extends ParserRule {
192
193
  }
193
194
 
194
195
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
195
- const visitor = new HTMLNoDuplicateMetaNamesVisitor(this.name, context)
196
+ const visitor = new HTMLNoDuplicateMetaNamesVisitor(this.ruleName, context)
196
197
 
197
198
  visitor.visit(result.value)
198
199
 
@@ -120,7 +120,7 @@ class NoEmptyAttributesVisitor extends AttributeVisitorMixin {
120
120
  }
121
121
 
122
122
  export class HTMLNoEmptyAttributesRule extends ParserRule {
123
- name = "html-no-empty-attributes"
123
+ static ruleName = "html-no-empty-attributes"
124
124
 
125
125
  get defaultConfig(): FullRuleConfig {
126
126
  return {
@@ -130,7 +130,7 @@ export class HTMLNoEmptyAttributesRule extends ParserRule {
130
130
  }
131
131
 
132
132
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
133
- const visitor = new NoEmptyAttributesVisitor(this.name, context)
133
+ const visitor = new NoEmptyAttributesVisitor(this.ruleName, context)
134
134
 
135
135
  visitor.visit(result.value)
136
136
 
@@ -1,14 +1,15 @@
1
- import { BaseRuleVisitor, getTagName, getAttributes, findAttributeByName, getAttributeValue, HEADING_TAGS } from "./rule-utils.js"
2
- import { isHTMLOpenTagNode, isLiteralNode, isHTMLTextNode, isHTMLElementNode } from "@herb-tools/core"
3
-
4
1
  import { ParserRule } from "../types.js"
2
+ import { BaseRuleVisitor, HEADING_TAGS } from "./rule-utils.js"
3
+ import { getTagLocalName } from "@herb-tools/core"
4
+ import { isLiteralNode, isHTMLTextNode, isHTMLElementNode, isERBOutputNode, isERBControlFlowNode, getStaticAttributeValue } from "@herb-tools/core"
5
5
 
6
+ import type { Node } from "@herb-tools/core"
6
7
  import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
7
- import type { HTMLElementNode, HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
8
+ import type { HTMLElementNode, ParseResult } from "@herb-tools/core"
8
9
 
9
10
  class NoEmptyHeadingsVisitor extends BaseRuleVisitor {
10
11
  visitHTMLElementNode(node: HTMLElementNode): void {
11
- const tagName = getTagName(node.open_tag)?.toLowerCase()
12
+ const tagName = getTagLocalName(node)
12
13
  if (tagName === "template") return
13
14
 
14
15
  this.checkHeadingElement(node)
@@ -16,14 +17,11 @@ class NoEmptyHeadingsVisitor extends BaseRuleVisitor {
16
17
  }
17
18
 
18
19
  private checkHeadingElement(node: HTMLElementNode): void {
19
- if (!node.open_tag) return
20
- if (!isHTMLOpenTagNode(node.open_tag)) return
21
-
22
- const tagName = getTagName(node.open_tag)
20
+ const tagName = getTagLocalName(node)
23
21
  if (!tagName) return
24
22
 
25
23
  const isStandardHeading = HEADING_TAGS.has(tagName)
26
- const isAriaHeading = this.hasHeadingRole(node.open_tag)
24
+ const isAriaHeading = this.hasHeadingRole(node)
27
25
 
28
26
  if (!isStandardHeading && !isAriaHeading) {
29
27
  return
@@ -46,90 +44,78 @@ class NoEmptyHeadingsVisitor extends BaseRuleVisitor {
46
44
  return true
47
45
  }
48
46
 
49
- let hasAccessibleContent = false
47
+ return !this.hasAccessibleContent(node.body)
48
+ }
50
49
 
51
- for (const child of node.body) {
52
- if (isLiteralNode(child) || isHTMLTextNode(child)) {
50
+ private hasAccessibleContent(nodes: Node[]): boolean {
51
+ for (const child of nodes) {
52
+ if (isLiteralNode(child) || isHTMLTextNode(child)) {
53
53
  if (child.content.trim().length > 0) {
54
- hasAccessibleContent = true
55
- break
54
+ return true
56
55
  }
57
56
  } else if (isHTMLElementNode(child)) {
58
57
  if (this.isElementAccessible(child)) {
59
- hasAccessibleContent = true
60
- break
58
+ return true
59
+ }
60
+ } else if (isERBOutputNode(child)) {
61
+ return true
62
+ } else if (isERBControlFlowNode(child)) {
63
+ if (this.hasAccessibleContentInControlFlow(child)) {
64
+ return true
61
65
  }
62
- } else {
63
- hasAccessibleContent = true
64
- break
65
66
  }
66
67
  }
67
68
 
68
- return !hasAccessibleContent
69
+ return false
69
70
  }
70
71
 
71
- private hasHeadingRole(node: HTMLOpenTagNode): boolean {
72
- const attributes = getAttributes(node)
73
- const roleAttribute = findAttributeByName(attributes, "role")
72
+ private hasAccessibleContentInControlFlow(node: Node): boolean {
73
+ const nodeWithStatements = node as { statements?: Node[], body?: Node[], subsequent?: Node }
74
74
 
75
- if (!roleAttribute) {
76
- return false
75
+ if (nodeWithStatements.statements && this.hasAccessibleContent(nodeWithStatements.statements)) {
76
+ return true
77
77
  }
78
78
 
79
- const roleValue = getAttributeValue(roleAttribute)
80
- return roleValue === "heading"
81
- }
79
+ if (nodeWithStatements.body && this.hasAccessibleContent(nodeWithStatements.body)) {
80
+ return true
81
+ }
82
82
 
83
- private isElementAccessible(node: HTMLElementNode): boolean {
84
- if (!node.open_tag) return true
85
- if (!isHTMLOpenTagNode(node.open_tag)) return true
83
+ if (nodeWithStatements.subsequent) {
84
+ return this.hasAccessibleContentInControlFlow(nodeWithStatements.subsequent)
85
+ }
86
86
 
87
- const attributes = getAttributes(node.open_tag)
88
- const ariaHiddenAttribute = findAttributeByName(attributes, "aria-hidden")
87
+ return false
88
+ }
89
89
 
90
- if (ariaHiddenAttribute) {
91
- const ariaHiddenValue = getAttributeValue(ariaHiddenAttribute)
90
+ private hasHeadingRole(node: HTMLElementNode): boolean {
91
+ return getStaticAttributeValue(node, "role") === "heading"
92
+ }
92
93
 
93
- if (ariaHiddenValue === "true") {
94
- return false
95
- }
94
+ private isElementAccessible(node: HTMLElementNode): boolean {
95
+ if (getStaticAttributeValue(node, "aria-hidden") === "true") {
96
+ return false
96
97
  }
97
98
 
98
99
  if (!node.body || node.body.length === 0) {
99
100
  return false
100
101
  }
101
102
 
102
- for (const child of node.body) {
103
- if (isLiteralNode(child) || isHTMLTextNode(child)) {
104
- if (child.content.trim().length > 0) {
105
- return true
106
- }
107
- } else if (isHTMLElementNode(child)) {
108
- if (this.isElementAccessible(child)) {
109
- return true
110
- }
111
- } else {
112
- // If there's any non-literal/non-text/non-element content (like ERB), consider it accessible
113
- return true
114
- }
115
- }
116
-
117
- return false
103
+ return this.hasAccessibleContent(node.body)
118
104
  }
119
105
  }
120
106
 
121
107
  export class HTMLNoEmptyHeadingsRule extends ParserRule {
122
- name = "html-no-empty-headings"
108
+ static ruleName = "html-no-empty-headings"
123
109
 
124
110
  get defaultConfig(): FullRuleConfig {
125
111
  return {
126
112
  enabled: true,
127
- severity: "error"
113
+ severity: "warning"
128
114
  }
129
115
  }
130
116
 
131
117
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
132
- const visitor = new NoEmptyHeadingsVisitor(this.name, context)
118
+ const visitor = new NoEmptyHeadingsVisitor(this.ruleName, context)
133
119
  visitor.visit(result.value)
134
120
  return visitor.offenses
135
121
  }