@herb-tools/linter 0.7.4 → 0.8.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 (395) hide show
  1. package/README.md +253 -13
  2. package/dist/herb-lint.js +26087 -3414
  3. package/dist/herb-lint.js.map +1 -1
  4. package/dist/index.cjs +5783 -1568
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.js +5749 -1569
  7. package/dist/index.js.map +1 -1
  8. package/dist/loader.cjs +17010 -0
  9. package/dist/loader.cjs.map +1 -0
  10. package/dist/loader.js +16879 -0
  11. package/dist/loader.js.map +1 -0
  12. package/dist/package.json +13 -5
  13. package/dist/src/cli/argument-parser.js +42 -35
  14. package/dist/src/cli/argument-parser.js.map +1 -1
  15. package/dist/src/cli/file-processor.js +124 -23
  16. package/dist/src/cli/file-processor.js.map +1 -1
  17. package/dist/src/cli/formatters/detailed-formatter.js +18 -3
  18. package/dist/src/cli/formatters/detailed-formatter.js.map +1 -1
  19. package/dist/src/cli/formatters/github-actions-formatter.js +15 -1
  20. package/dist/src/cli/formatters/github-actions-formatter.js.map +1 -1
  21. package/dist/src/cli/formatters/json-formatter.js +3 -0
  22. package/dist/src/cli/formatters/json-formatter.js.map +1 -1
  23. package/dist/src/cli/formatters/simple-formatter.js +20 -7
  24. package/dist/src/cli/formatters/simple-formatter.js.map +1 -1
  25. package/dist/src/cli/output-manager.js +22 -3
  26. package/dist/src/cli/output-manager.js.map +1 -1
  27. package/dist/src/cli/summary-reporter.js +26 -3
  28. package/dist/src/cli/summary-reporter.js.map +1 -1
  29. package/dist/src/cli.js +109 -43
  30. package/dist/src/cli.js.map +1 -1
  31. package/dist/src/custom-rule-loader.js +139 -0
  32. package/dist/src/custom-rule-loader.js.map +1 -0
  33. package/dist/src/herb-disable-comment-utils.js +129 -0
  34. package/dist/src/herb-disable-comment-utils.js.map +1 -0
  35. package/dist/src/index.js +1 -0
  36. package/dist/src/index.js.map +1 -1
  37. package/dist/src/linter.js +369 -34
  38. package/dist/src/linter.js.map +1 -1
  39. package/dist/src/loader.js +17 -0
  40. package/dist/src/loader.js.map +1 -0
  41. package/dist/src/rules/erb-comment-syntax.js +48 -0
  42. package/dist/src/rules/erb-comment-syntax.js.map +1 -0
  43. package/dist/src/rules/erb-no-case-node-children.js +52 -0
  44. package/dist/src/rules/erb-no-case-node-children.js.map +1 -0
  45. package/dist/src/rules/erb-no-empty-tags.js +7 -1
  46. package/dist/src/rules/erb-no-empty-tags.js.map +1 -1
  47. package/dist/src/rules/erb-no-extra-newline.js +65 -0
  48. package/dist/src/rules/erb-no-extra-newline.js.map +1 -0
  49. package/dist/src/rules/erb-no-extra-whitespace-inside-tags.js +95 -0
  50. package/dist/src/rules/erb-no-extra-whitespace-inside-tags.js.map +1 -0
  51. package/dist/src/rules/erb-no-output-control-flow.js +7 -1
  52. package/dist/src/rules/erb-no-output-control-flow.js.map +1 -1
  53. package/dist/src/rules/erb-no-silent-tag-in-attribute-name.js +7 -1
  54. package/dist/src/rules/erb-no-silent-tag-in-attribute-name.js.map +1 -1
  55. package/dist/src/rules/erb-prefer-image-tag-helper.js +7 -1
  56. package/dist/src/rules/erb-prefer-image-tag-helper.js.map +1 -1
  57. package/dist/src/rules/erb-require-trailing-newline.js +35 -0
  58. package/dist/src/rules/erb-require-trailing-newline.js.map +1 -0
  59. package/dist/src/rules/erb-require-whitespace-inside-tags.js +70 -20
  60. package/dist/src/rules/erb-require-whitespace-inside-tags.js.map +1 -1
  61. package/dist/src/rules/erb-right-trim.js +45 -0
  62. package/dist/src/rules/erb-right-trim.js.map +1 -0
  63. package/dist/src/rules/herb-disable-comment-base.js +51 -0
  64. package/dist/src/rules/herb-disable-comment-base.js.map +1 -0
  65. package/dist/src/rules/herb-disable-comment-malformed.js +51 -0
  66. package/dist/src/rules/herb-disable-comment-malformed.js.map +1 -0
  67. package/dist/src/rules/herb-disable-comment-missing-rules.js +29 -0
  68. package/dist/src/rules/herb-disable-comment-missing-rules.js.map +1 -0
  69. package/dist/src/rules/herb-disable-comment-no-duplicate-rules.js +32 -0
  70. package/dist/src/rules/herb-disable-comment-no-duplicate-rules.js.map +1 -0
  71. package/dist/src/rules/herb-disable-comment-no-redundant-all.js +31 -0
  72. package/dist/src/rules/herb-disable-comment-no-redundant-all.js.map +1 -0
  73. package/dist/src/rules/herb-disable-comment-unnecessary.js +65 -0
  74. package/dist/src/rules/herb-disable-comment-unnecessary.js.map +1 -0
  75. package/dist/src/rules/herb-disable-comment-valid-rule-name.js +44 -0
  76. package/dist/src/rules/herb-disable-comment-valid-rule-name.js.map +1 -0
  77. package/dist/src/rules/html-anchor-require-href.js +7 -1
  78. package/dist/src/rules/html-anchor-require-href.js.map +1 -1
  79. package/dist/src/rules/html-aria-attribute-must-be-valid.js +7 -1
  80. package/dist/src/rules/html-aria-attribute-must-be-valid.js.map +1 -1
  81. package/dist/src/rules/html-aria-label-is-well-formatted.js +9 -3
  82. package/dist/src/rules/html-aria-label-is-well-formatted.js.map +1 -1
  83. package/dist/src/rules/html-aria-level-must-be-valid.js +6 -0
  84. package/dist/src/rules/html-aria-level-must-be-valid.js.map +1 -1
  85. package/dist/src/rules/html-aria-role-heading-requires-level.js +7 -1
  86. package/dist/src/rules/html-aria-role-heading-requires-level.js.map +1 -1
  87. package/dist/src/rules/html-aria-role-must-be-valid.js +7 -1
  88. package/dist/src/rules/html-aria-role-must-be-valid.js.map +1 -1
  89. package/dist/src/rules/html-attribute-double-quotes.js +29 -2
  90. package/dist/src/rules/html-attribute-double-quotes.js.map +1 -1
  91. package/dist/src/rules/html-attribute-equals-spacing.js +18 -2
  92. package/dist/src/rules/html-attribute-equals-spacing.js.map +1 -1
  93. package/dist/src/rules/html-attribute-values-require-quotes.js +39 -3
  94. package/dist/src/rules/html-attribute-values-require-quotes.js.map +1 -1
  95. package/dist/src/rules/html-avoid-both-disabled-and-aria-disabled.js +7 -1
  96. package/dist/src/rules/html-avoid-both-disabled-and-aria-disabled.js.map +1 -1
  97. package/dist/src/rules/html-body-only-elements.js +46 -0
  98. package/dist/src/rules/html-body-only-elements.js.map +1 -0
  99. package/dist/src/rules/html-boolean-attributes-no-value.js +18 -1
  100. package/dist/src/rules/html-boolean-attributes-no-value.js.map +1 -1
  101. package/dist/src/rules/html-head-only-elements.js +51 -0
  102. package/dist/src/rules/html-head-only-elements.js.map +1 -0
  103. package/dist/src/rules/html-iframe-has-title.js +8 -2
  104. package/dist/src/rules/html-iframe-has-title.js.map +1 -1
  105. package/dist/src/rules/html-img-require-alt.js +7 -1
  106. package/dist/src/rules/html-img-require-alt.js.map +1 -1
  107. package/dist/src/rules/html-input-require-autocomplete.js +70 -0
  108. package/dist/src/rules/html-input-require-autocomplete.js.map +1 -0
  109. package/dist/src/rules/html-navigation-has-label.js +7 -1
  110. package/dist/src/rules/html-navigation-has-label.js.map +1 -1
  111. package/dist/src/rules/html-no-aria-hidden-on-focusable.js +7 -1
  112. package/dist/src/rules/html-no-aria-hidden-on-focusable.js.map +1 -1
  113. package/dist/src/rules/html-no-block-inside-inline.js +7 -1
  114. package/dist/src/rules/html-no-block-inside-inline.js.map +1 -1
  115. package/dist/src/rules/html-no-duplicate-attributes.js +7 -1
  116. package/dist/src/rules/html-no-duplicate-attributes.js.map +1 -1
  117. package/dist/src/rules/html-no-duplicate-ids.js +9 -3
  118. package/dist/src/rules/html-no-duplicate-ids.js.map +1 -1
  119. package/dist/src/rules/html-no-duplicate-meta-names.js +136 -0
  120. package/dist/src/rules/html-no-duplicate-meta-names.js.map +1 -0
  121. package/dist/src/rules/html-no-empty-attributes.js +45 -7
  122. package/dist/src/rules/html-no-empty-attributes.js.map +1 -1
  123. package/dist/src/rules/html-no-empty-headings.js +7 -6
  124. package/dist/src/rules/html-no-empty-headings.js.map +1 -1
  125. package/dist/src/rules/html-no-nested-links.js +7 -1
  126. package/dist/src/rules/html-no-nested-links.js.map +1 -1
  127. package/dist/src/rules/html-no-positive-tab-index.js +7 -1
  128. package/dist/src/rules/html-no-positive-tab-index.js.map +1 -1
  129. package/dist/src/rules/html-no-self-closing.js +48 -3
  130. package/dist/src/rules/html-no-self-closing.js.map +1 -1
  131. package/dist/src/rules/html-no-space-in-tag.js +173 -0
  132. package/dist/src/rules/html-no-space-in-tag.js.map +1 -0
  133. package/dist/src/rules/html-no-title-attribute.js +7 -1
  134. package/dist/src/rules/html-no-title-attribute.js.map +1 -1
  135. package/dist/src/rules/html-no-underscores-in-attribute-names.js +7 -1
  136. package/dist/src/rules/html-no-underscores-in-attribute-names.js.map +1 -1
  137. package/dist/src/rules/html-tag-name-lowercase.js +23 -5
  138. package/dist/src/rules/html-tag-name-lowercase.js.map +1 -1
  139. package/dist/src/rules/index.js +20 -2
  140. package/dist/src/rules/index.js.map +1 -1
  141. package/dist/src/rules/parser-no-errors.js +6 -0
  142. package/dist/src/rules/parser-no-errors.js.map +1 -1
  143. package/dist/src/rules/rule-utils.js +211 -31
  144. package/dist/src/rules/rule-utils.js.map +1 -1
  145. package/dist/src/rules/svg-tag-name-capitalization.js +22 -2
  146. package/dist/src/rules/svg-tag-name-capitalization.js.map +1 -1
  147. package/dist/src/{default-rules.js → rules.js} +46 -14
  148. package/dist/src/rules.js.map +1 -0
  149. package/dist/src/types.js +34 -1
  150. package/dist/src/types.js.map +1 -1
  151. package/dist/tsconfig.tsbuildinfo +1 -1
  152. package/dist/types/cli/argument-parser.d.ts +8 -2
  153. package/dist/types/cli/file-processor.d.ts +15 -0
  154. package/dist/types/cli/formatters/json-formatter.d.ts +6 -0
  155. package/dist/types/cli/formatters/simple-formatter.d.ts +1 -0
  156. package/dist/types/cli/summary-reporter.d.ts +6 -0
  157. package/dist/types/cli.d.ts +9 -4
  158. package/dist/types/custom-rule-loader.d.ts +62 -0
  159. package/dist/types/herb-disable-comment-utils.d.ts +69 -0
  160. package/dist/types/index.d.ts +1 -0
  161. package/dist/types/linter.d.ts +99 -3
  162. package/dist/types/loader.d.ts +20 -0
  163. package/dist/types/rules/erb-comment-syntax.d.ts +14 -0
  164. package/dist/types/rules/erb-no-case-node-children.d.ts +8 -0
  165. package/dist/types/rules/erb-no-empty-tags.d.ts +3 -2
  166. package/dist/types/rules/erb-no-extra-newline.d.ts +14 -0
  167. package/dist/types/rules/erb-no-extra-whitespace-inside-tags.d.ts +18 -0
  168. package/dist/types/rules/erb-no-output-control-flow.d.ts +3 -2
  169. package/dist/types/rules/erb-no-silent-tag-in-attribute-name.d.ts +3 -2
  170. package/dist/types/rules/erb-prefer-image-tag-helper.d.ts +3 -2
  171. package/dist/types/rules/erb-require-trailing-newline.d.ts +9 -0
  172. package/dist/types/rules/erb-require-whitespace-inside-tags.d.ts +16 -5
  173. package/dist/types/rules/erb-right-trim.d.ts +14 -0
  174. package/dist/types/rules/herb-disable-comment-base.d.ts +37 -0
  175. package/dist/types/rules/herb-disable-comment-malformed.d.ts +8 -0
  176. package/dist/types/rules/herb-disable-comment-missing-rules.d.ts +8 -0
  177. package/dist/types/rules/herb-disable-comment-no-duplicate-rules.d.ts +8 -0
  178. package/dist/types/rules/herb-disable-comment-no-redundant-all.d.ts +8 -0
  179. package/dist/types/rules/herb-disable-comment-unnecessary.d.ts +8 -0
  180. package/dist/types/rules/herb-disable-comment-valid-rule-name.d.ts +8 -0
  181. package/dist/types/rules/html-anchor-require-href.d.ts +3 -2
  182. package/dist/types/rules/html-aria-attribute-must-be-valid.d.ts +3 -2
  183. package/dist/types/rules/html-aria-label-is-well-formatted.d.ts +3 -2
  184. package/dist/types/rules/html-aria-level-must-be-valid.d.ts +3 -2
  185. package/dist/types/rules/html-aria-role-heading-requires-level.d.ts +3 -2
  186. package/dist/types/rules/html-aria-role-must-be-valid.d.ts +3 -2
  187. package/dist/types/rules/html-attribute-double-quotes.d.ts +13 -5
  188. package/dist/types/rules/html-attribute-equals-spacing.d.ts +12 -5
  189. package/dist/types/rules/html-attribute-values-require-quotes.d.ts +13 -5
  190. package/dist/types/rules/html-avoid-both-disabled-and-aria-disabled.d.ts +3 -2
  191. package/dist/types/rules/html-body-only-elements.d.ts +9 -0
  192. package/dist/types/rules/html-boolean-attributes-no-value.d.ts +12 -5
  193. package/dist/types/rules/html-head-only-elements.d.ts +9 -0
  194. package/dist/types/rules/html-iframe-has-title.d.ts +3 -2
  195. package/dist/types/rules/html-img-require-alt.d.ts +3 -2
  196. package/dist/types/rules/html-input-require-autocomplete.d.ts +8 -0
  197. package/dist/types/rules/html-navigation-has-label.d.ts +3 -2
  198. package/dist/types/rules/html-no-aria-hidden-on-focusable.d.ts +3 -2
  199. package/dist/types/rules/html-no-block-inside-inline.d.ts +3 -2
  200. package/dist/types/rules/html-no-duplicate-attributes.d.ts +3 -2
  201. package/dist/types/rules/html-no-duplicate-ids.d.ts +3 -2
  202. package/dist/types/rules/html-no-duplicate-meta-names.d.ts +9 -0
  203. package/dist/types/rules/html-no-empty-attributes.d.ts +3 -2
  204. package/dist/types/rules/html-no-empty-headings.d.ts +3 -2
  205. package/dist/types/rules/html-no-nested-links.d.ts +3 -2
  206. package/dist/types/rules/html-no-positive-tab-index.d.ts +3 -2
  207. package/dist/types/rules/html-no-self-closing.d.ts +14 -5
  208. package/dist/types/rules/html-no-space-in-tag.d.ts +16 -0
  209. package/dist/types/rules/html-no-title-attribute.d.ts +3 -2
  210. package/dist/types/rules/html-no-underscores-in-attribute-names.d.ts +3 -2
  211. package/dist/types/rules/html-tag-name-lowercase.d.ts +16 -6
  212. package/dist/types/rules/index.d.ts +20 -2
  213. package/dist/types/rules/parser-no-errors.d.ts +2 -1
  214. package/dist/types/rules/rule-utils.d.ts +72 -25
  215. package/dist/types/rules/svg-tag-name-capitalization.d.ts +13 -4
  216. package/dist/types/rules.d.ts +2 -0
  217. package/dist/types/src/cli/argument-parser.d.ts +8 -2
  218. package/dist/types/src/cli/file-processor.d.ts +15 -0
  219. package/dist/types/src/cli/formatters/json-formatter.d.ts +6 -0
  220. package/dist/types/src/cli/formatters/simple-formatter.d.ts +1 -0
  221. package/dist/types/src/cli/summary-reporter.d.ts +6 -0
  222. package/dist/types/src/cli.d.ts +9 -4
  223. package/dist/types/src/custom-rule-loader.d.ts +62 -0
  224. package/dist/types/src/herb-disable-comment-utils.d.ts +69 -0
  225. package/dist/types/src/index.d.ts +1 -0
  226. package/dist/types/src/linter.d.ts +99 -3
  227. package/dist/types/src/loader.d.ts +20 -0
  228. package/dist/types/src/rules/erb-comment-syntax.d.ts +14 -0
  229. package/dist/types/src/rules/erb-no-case-node-children.d.ts +8 -0
  230. package/dist/types/src/rules/erb-no-empty-tags.d.ts +3 -2
  231. package/dist/types/src/rules/erb-no-extra-newline.d.ts +14 -0
  232. package/dist/types/src/rules/erb-no-extra-whitespace-inside-tags.d.ts +18 -0
  233. package/dist/types/src/rules/erb-no-output-control-flow.d.ts +3 -2
  234. package/dist/types/src/rules/erb-no-silent-tag-in-attribute-name.d.ts +3 -2
  235. package/dist/types/src/rules/erb-prefer-image-tag-helper.d.ts +3 -2
  236. package/dist/types/src/rules/erb-require-trailing-newline.d.ts +9 -0
  237. package/dist/types/src/rules/erb-require-whitespace-inside-tags.d.ts +16 -5
  238. package/dist/types/src/rules/erb-right-trim.d.ts +14 -0
  239. package/dist/types/src/rules/herb-disable-comment-base.d.ts +37 -0
  240. package/dist/types/src/rules/herb-disable-comment-malformed.d.ts +8 -0
  241. package/dist/types/src/rules/herb-disable-comment-missing-rules.d.ts +8 -0
  242. package/dist/types/src/rules/herb-disable-comment-no-duplicate-rules.d.ts +8 -0
  243. package/dist/types/src/rules/herb-disable-comment-no-redundant-all.d.ts +8 -0
  244. package/dist/types/src/rules/herb-disable-comment-unnecessary.d.ts +8 -0
  245. package/dist/types/src/rules/herb-disable-comment-valid-rule-name.d.ts +8 -0
  246. package/dist/types/src/rules/html-anchor-require-href.d.ts +3 -2
  247. package/dist/types/src/rules/html-aria-attribute-must-be-valid.d.ts +3 -2
  248. package/dist/types/src/rules/html-aria-label-is-well-formatted.d.ts +3 -2
  249. package/dist/types/src/rules/html-aria-level-must-be-valid.d.ts +3 -2
  250. package/dist/types/src/rules/html-aria-role-heading-requires-level.d.ts +3 -2
  251. package/dist/types/src/rules/html-aria-role-must-be-valid.d.ts +3 -2
  252. package/dist/types/src/rules/html-attribute-double-quotes.d.ts +13 -5
  253. package/dist/types/src/rules/html-attribute-equals-spacing.d.ts +12 -5
  254. package/dist/types/src/rules/html-attribute-values-require-quotes.d.ts +13 -5
  255. package/dist/types/src/rules/html-avoid-both-disabled-and-aria-disabled.d.ts +3 -2
  256. package/dist/types/src/rules/html-body-only-elements.d.ts +9 -0
  257. package/dist/types/src/rules/html-boolean-attributes-no-value.d.ts +12 -5
  258. package/dist/types/src/rules/html-head-only-elements.d.ts +9 -0
  259. package/dist/types/src/rules/html-iframe-has-title.d.ts +3 -2
  260. package/dist/types/src/rules/html-img-require-alt.d.ts +3 -2
  261. package/dist/types/src/rules/html-input-require-autocomplete.d.ts +8 -0
  262. package/dist/types/src/rules/html-navigation-has-label.d.ts +3 -2
  263. package/dist/types/src/rules/html-no-aria-hidden-on-focusable.d.ts +3 -2
  264. package/dist/types/src/rules/html-no-block-inside-inline.d.ts +3 -2
  265. package/dist/types/src/rules/html-no-duplicate-attributes.d.ts +3 -2
  266. package/dist/types/src/rules/html-no-duplicate-ids.d.ts +3 -2
  267. package/dist/types/src/rules/html-no-duplicate-meta-names.d.ts +9 -0
  268. package/dist/types/src/rules/html-no-empty-attributes.d.ts +3 -2
  269. package/dist/types/src/rules/html-no-empty-headings.d.ts +3 -2
  270. package/dist/types/src/rules/html-no-nested-links.d.ts +3 -2
  271. package/dist/types/src/rules/html-no-positive-tab-index.d.ts +3 -2
  272. package/dist/types/src/rules/html-no-self-closing.d.ts +14 -5
  273. package/dist/types/src/rules/html-no-space-in-tag.d.ts +16 -0
  274. package/dist/types/src/rules/html-no-title-attribute.d.ts +3 -2
  275. package/dist/types/src/rules/html-no-underscores-in-attribute-names.d.ts +3 -2
  276. package/dist/types/src/rules/html-tag-name-lowercase.d.ts +16 -6
  277. package/dist/types/src/rules/index.d.ts +20 -2
  278. package/dist/types/src/rules/parser-no-errors.d.ts +2 -1
  279. package/dist/types/src/rules/rule-utils.d.ts +72 -25
  280. package/dist/types/src/rules/svg-tag-name-capitalization.d.ts +13 -4
  281. package/dist/types/src/rules.d.ts +2 -0
  282. package/dist/types/src/types.d.ts +102 -11
  283. package/dist/types/types.d.ts +102 -11
  284. package/docs/rules/README.md +19 -3
  285. package/docs/rules/erb-comment-syntax.md +44 -0
  286. package/docs/rules/erb-no-case-node-children.md +50 -0
  287. package/docs/rules/erb-no-extra-newline.md +74 -0
  288. package/docs/rules/erb-no-extra-whitespace-inside-tags.md +39 -0
  289. package/docs/rules/{erb-requires-trailing-newline.md → erb-require-trailing-newline.md} +1 -1
  290. package/docs/rules/erb-right-trim.md +52 -0
  291. package/docs/rules/herb-disable-comment-malformed.md +45 -0
  292. package/docs/rules/herb-disable-comment-missing-rules.md +60 -0
  293. package/docs/rules/herb-disable-comment-no-duplicate-rules.md +49 -0
  294. package/docs/rules/herb-disable-comment-no-redundant-all.md +53 -0
  295. package/docs/rules/herb-disable-comment-unnecessary.md +44 -0
  296. package/docs/rules/herb-disable-comment-valid-rule-name.md +41 -0
  297. package/docs/rules/html-aria-attribute-must-be-valid.md +2 -5
  298. package/docs/rules/html-aria-label-is-well-formatted.md +1 -1
  299. package/docs/rules/html-attribute-double-quotes.md +2 -2
  300. package/docs/rules/html-attribute-equals-spacing.md +2 -2
  301. package/docs/rules/html-attribute-values-require-quotes.md +3 -3
  302. package/docs/rules/html-avoid-both-disabled-and-aria-disabled.md +2 -2
  303. package/docs/rules/html-body-only-elements.md +99 -0
  304. package/docs/rules/html-boolean-attributes-no-value.md +2 -2
  305. package/docs/rules/html-head-only-elements.md +81 -0
  306. package/docs/rules/html-input-require-autocomplete.md +64 -0
  307. package/docs/rules/html-no-aria-hidden-on-focusable.md +2 -2
  308. package/docs/rules/html-no-duplicate-attributes.md +2 -2
  309. package/docs/rules/html-no-duplicate-meta-names.md +64 -0
  310. package/docs/rules/html-no-empty-attributes.md +3 -3
  311. package/docs/rules/html-no-empty-headings.md +4 -26
  312. package/docs/rules/html-no-positive-tab-index.md +1 -2
  313. package/docs/rules/html-no-self-closing.md +17 -2
  314. package/docs/rules/html-no-space-in-tag.md +66 -0
  315. package/docs/rules/html-no-title-attribute.md +2 -2
  316. package/docs/rules/html-no-underscores-in-attribute-names.md +2 -2
  317. package/docs/rules/html-tag-name-lowercase.md +2 -2
  318. package/package.json +13 -5
  319. package/src/cli/argument-parser.ts +50 -39
  320. package/src/cli/file-processor.ts +159 -28
  321. package/src/cli/formatters/detailed-formatter.ts +21 -3
  322. package/src/cli/formatters/github-actions-formatter.ts +17 -1
  323. package/src/cli/formatters/json-formatter.ts +9 -0
  324. package/src/cli/formatters/simple-formatter.ts +24 -8
  325. package/src/cli/output-manager.ts +23 -3
  326. package/src/cli/summary-reporter.ts +40 -3
  327. package/src/cli.ts +137 -52
  328. package/src/custom-rule-loader.ts +189 -0
  329. package/src/herb-disable-comment-utils.ts +175 -0
  330. package/src/index.ts +2 -0
  331. package/src/linter.ts +501 -36
  332. package/src/loader.ts +30 -0
  333. package/src/rules/erb-comment-syntax.ts +73 -0
  334. package/src/rules/erb-no-case-node-children.ts +68 -0
  335. package/src/rules/erb-no-empty-tags.ts +9 -3
  336. package/src/rules/erb-no-extra-newline.ts +91 -0
  337. package/src/rules/erb-no-extra-whitespace-inside-tags.ts +147 -0
  338. package/src/rules/erb-no-output-control-flow.ts +9 -3
  339. package/src/rules/erb-no-silent-tag-in-attribute-name.ts +9 -3
  340. package/src/rules/erb-prefer-image-tag-helper.ts +9 -3
  341. package/src/rules/erb-require-trailing-newline.ts +47 -0
  342. package/src/rules/erb-require-whitespace-inside-tags.ts +96 -26
  343. package/src/rules/erb-right-trim.ts +67 -0
  344. package/src/rules/herb-disable-comment-base.ts +76 -0
  345. package/src/rules/herb-disable-comment-malformed.ts +66 -0
  346. package/src/rules/herb-disable-comment-missing-rules.ts +41 -0
  347. package/src/rules/herb-disable-comment-no-duplicate-rules.ts +46 -0
  348. package/src/rules/herb-disable-comment-no-redundant-all.ts +40 -0
  349. package/src/rules/herb-disable-comment-unnecessary.ts +103 -0
  350. package/src/rules/herb-disable-comment-valid-rule-name.ts +62 -0
  351. package/src/rules/html-anchor-require-href.ts +9 -3
  352. package/src/rules/html-aria-attribute-must-be-valid.ts +9 -3
  353. package/src/rules/html-aria-label-is-well-formatted.ts +9 -5
  354. package/src/rules/html-aria-level-must-be-valid.ts +9 -2
  355. package/src/rules/html-aria-role-heading-requires-level.ts +9 -3
  356. package/src/rules/html-aria-role-must-be-valid.ts +9 -3
  357. package/src/rules/html-attribute-double-quotes.ts +42 -8
  358. package/src/rules/html-attribute-equals-spacing.ts +31 -7
  359. package/src/rules/html-attribute-values-require-quotes.ts +56 -10
  360. package/src/rules/html-avoid-both-disabled-and-aria-disabled.ts +9 -3
  361. package/src/rules/html-body-only-elements.ts +60 -0
  362. package/src/rules/html-boolean-attributes-no-value.ts +31 -6
  363. package/src/rules/html-head-only-elements.ts +65 -0
  364. package/src/rules/html-iframe-has-title.ts +9 -4
  365. package/src/rules/html-img-require-alt.ts +10 -4
  366. package/src/rules/html-input-require-autocomplete.ts +85 -0
  367. package/src/rules/html-navigation-has-label.ts +9 -3
  368. package/src/rules/html-no-aria-hidden-on-focusable.ts +9 -3
  369. package/src/rules/html-no-block-inside-inline.ts +9 -3
  370. package/src/rules/html-no-duplicate-attributes.ts +9 -3
  371. package/src/rules/html-no-duplicate-ids.ts +11 -7
  372. package/src/rules/html-no-duplicate-meta-names.ts +188 -0
  373. package/src/rules/html-no-empty-attributes.ts +58 -10
  374. package/src/rules/html-no-empty-headings.ts +10 -8
  375. package/src/rules/html-no-nested-links.ts +10 -4
  376. package/src/rules/html-no-positive-tab-index.ts +9 -3
  377. package/src/rules/html-no-self-closing.ts +69 -9
  378. package/src/rules/html-no-space-in-tag.ts +221 -0
  379. package/src/rules/html-no-title-attribute.ts +9 -3
  380. package/src/rules/html-no-underscores-in-attribute-names.ts +12 -4
  381. package/src/rules/html-tag-name-lowercase.ts +41 -10
  382. package/src/rules/index.ts +24 -2
  383. package/src/rules/parser-no-errors.ts +8 -1
  384. package/src/rules/rule-utils.ts +250 -44
  385. package/src/rules/svg-tag-name-capitalization.ts +39 -6
  386. package/src/{default-rules.ts → rules.ts} +53 -13
  387. package/src/types.ts +133 -15
  388. package/dist/src/default-rules.js.map +0 -1
  389. package/dist/src/rules/erb-requires-trailing-newline.js +0 -22
  390. package/dist/src/rules/erb-requires-trailing-newline.js.map +0 -1
  391. package/dist/types/default-rules.d.ts +0 -2
  392. package/dist/types/rules/erb-requires-trailing-newline.d.ts +0 -6
  393. package/dist/types/src/default-rules.d.ts +0 -2
  394. package/dist/types/src/rules/erb-requires-trailing-newline.d.ts +0 -6
  395. package/src/rules/erb-requires-trailing-newline.ts +0 -29
@@ -0,0 +1,67 @@
1
+ import { BaseRuleVisitor } from "./rule-utils.js"
2
+ import { ParserRule, BaseAutofixContext, Mutable } from "../types.js"
3
+
4
+ import type { UnboundLintOffense, LintOffense, LintContext, FullRuleConfig } from "../types.js"
5
+ import type { ERBNode, ParseResult } from "@herb-tools/core"
6
+
7
+ interface ERBRightTrimAutofixContext extends BaseAutofixContext {
8
+ node: Mutable<ERBNode>
9
+ }
10
+
11
+ class ERBRightTrimVisitor extends BaseRuleVisitor<ERBRightTrimAutofixContext> {
12
+ visitERBNode(node: ERBNode): void {
13
+ if (!node.tag_closing) return
14
+
15
+ const trimClosing = node.tag_closing.value
16
+
17
+ if (trimClosing !== "=%>") return
18
+
19
+ this.addOffense(
20
+ "Use `-%>` instead of `=%>` for right-trimming. The `=%>` syntax is obscure and not well-supported in most ERB engines.",
21
+ node.tag_closing.location,
22
+ { node }
23
+ )
24
+ }
25
+ }
26
+
27
+ export class ERBRightTrimRule extends ParserRule<ERBRightTrimAutofixContext> {
28
+ static autocorrectable = true
29
+ name = "erb-right-trim"
30
+
31
+ get defaultConfig(): FullRuleConfig {
32
+ return {
33
+ enabled: true,
34
+ severity: "error"
35
+ }
36
+ }
37
+
38
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<ERBRightTrimAutofixContext>[] {
39
+ const visitor = new ERBRightTrimVisitor(this.name, context)
40
+
41
+ visitor.visit(result.value)
42
+
43
+ return visitor.offenses
44
+ }
45
+
46
+ autofix(offense: LintOffense<ERBRightTrimAutofixContext>, result: ParseResult, _context?: Partial<LintContext>): ParseResult | null {
47
+ if (!offense.autofixContext) return null
48
+
49
+ const { node } = offense.autofixContext
50
+
51
+ if (!node.tag_closing) return null
52
+
53
+ const closing = node.tag_closing
54
+
55
+ if (closing.value === "=%>") {
56
+ closing.value = "-%>"
57
+ return result
58
+ }
59
+
60
+ if (closing.value === "-%>") {
61
+ closing.value = "%>"
62
+ return result
63
+ }
64
+
65
+ return null
66
+ }
67
+ }
@@ -0,0 +1,76 @@
1
+ import { BaseRuleVisitor } from "./rule-utils.js"
2
+ import { ERBContentNode, Location } from "@herb-tools/core"
3
+
4
+ import { parseHerbDisableContent } from "../herb-disable-comment-utils.js"
5
+
6
+ import type { LintContext } from "../types.js"
7
+ import type { HerbDisableComment, HerbDisableRuleName } from "../herb-disable-comment-utils.js"
8
+
9
+ /**
10
+ * Base visitor class for herb:disable comment validation rules.
11
+ * Handles common patterns like checking ERB comments and parsing herb:disable content.
12
+ */
13
+ export abstract class HerbDisableCommentBaseVisitor extends BaseRuleVisitor {
14
+ constructor(ruleName: string, context?: Partial<LintContext>) {
15
+ super(ruleName, context)
16
+ }
17
+
18
+ visitERBContentNode(node: ERBContentNode): void {
19
+ if (node.tag_opening?.value !== "<%#") return
20
+
21
+ const content = node.content?.value
22
+ if (!content) return
23
+
24
+ this.checkHerbDisableComment(node, content)
25
+ }
26
+
27
+ /**
28
+ * Override this method to implement rule-specific logic.
29
+ * This is called for every ERB comment node.
30
+ */
31
+ protected abstract checkHerbDisableComment(node: ERBContentNode, content: string): void
32
+
33
+ /**
34
+ * Helper to create a precise location for a specific rule name within the comment.
35
+ * Returns null if content location is not available.
36
+ */
37
+ protected createRuleNameLocation(node: ERBContentNode, ruleDetail: HerbDisableRuleName): Location | null {
38
+ const contentLocation = node.content?.location
39
+ if (!contentLocation) return null
40
+
41
+ const startLine = contentLocation.start.line
42
+ const startColumn = contentLocation.start.column + ruleDetail.offset
43
+
44
+ return Location.from(
45
+ startLine,
46
+ startColumn,
47
+ startLine,
48
+ startColumn + ruleDetail.length
49
+ )
50
+ }
51
+
52
+ /**
53
+ * Helper to add an offense with a fallback to node location if precise location unavailable.
54
+ */
55
+ protected addOffenseWithFallback(message: string, preciseLocation: Location | null, node: ERBContentNode): void {
56
+ this.addOffense(message, preciseLocation || node.location)
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Base visitor for rules that need to process parsed herb:disable comments.
62
+ * Only calls the abstract method if the content successfully parses as a herb:disable comment.
63
+ */
64
+ export abstract class HerbDisableCommentParsedVisitor extends HerbDisableCommentBaseVisitor {
65
+ protected checkHerbDisableComment(node: ERBContentNode, content: string): void {
66
+ const herbDisable = parseHerbDisableContent(content)
67
+ if (!herbDisable) return
68
+
69
+ this.checkParsedHerbDisable(node, content, herbDisable)
70
+ }
71
+
72
+ /**
73
+ * Override this method to implement rule-specific logic for parsed herb:disable comments.
74
+ */
75
+ protected abstract checkParsedHerbDisable(node: ERBContentNode, content: string, herbDisable: HerbDisableComment): void
76
+ }
@@ -0,0 +1,66 @@
1
+ import { ParserRule } from "../types.js"
2
+ import { HerbDisableCommentBaseVisitor } from "./herb-disable-comment-base.js"
3
+ import { parseHerbDisableContent } from "../herb-disable-comment-utils.js"
4
+
5
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
6
+ import type { ERBContentNode, ParseResult } from "@herb-tools/core"
7
+
8
+ class HerbDisableCommentMalformedVisitor extends HerbDisableCommentBaseVisitor {
9
+ protected checkHerbDisableComment(node: ERBContentNode, content: string): void {
10
+ const trimmed = content.trim()
11
+ const looksLikeHerbDisable = trimmed.startsWith("herb:disable")
12
+ if (!looksLikeHerbDisable) return
13
+
14
+ if (trimmed.length > "herb:disable".length) {
15
+ const charAfterPrefix = trimmed["herb:disable".length]
16
+
17
+ if (charAfterPrefix !== ' ' && charAfterPrefix !== '\t' && charAfterPrefix !== '\n') {
18
+ this.addOffense(
19
+ "`herb:disable` comment is missing a space after `herb:disable`. Add a space before the rule names.",
20
+ node.location,
21
+ )
22
+
23
+ return
24
+ }
25
+ }
26
+
27
+ const afterPrefix = trimmed.substring("herb:disable".length).trim()
28
+ if (afterPrefix.length === 0) return
29
+
30
+ const parsed = parseHerbDisableContent(content)
31
+ if (parsed !== null) return
32
+
33
+ let message = "`herb:disable` comment is malformed."
34
+
35
+ const rulesString = afterPrefix.trim()
36
+
37
+ if (rulesString.endsWith(',')) {
38
+ message = "`herb:disable` comment has a trailing comma. Remove the trailing comma."
39
+ } else if (rulesString.includes(',,') || rulesString.match(/,\s*,/)) {
40
+ message = "`herb:disable` comment has consecutive commas. Remove extra commas."
41
+ } else if (rulesString.startsWith(',')) {
42
+ message = "`herb:disable` comment starts with a comma. Remove the leading comma."
43
+ }
44
+
45
+ this.addOffense(message, node.location)
46
+ }
47
+ }
48
+
49
+ export class HerbDisableCommentMalformedRule extends ParserRule {
50
+ name = "herb-disable-comment-malformed"
51
+
52
+ get defaultConfig(): FullRuleConfig {
53
+ return {
54
+ enabled: true,
55
+ severity: "error"
56
+ }
57
+ }
58
+
59
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
60
+ const visitor = new HerbDisableCommentMalformedVisitor(this.name, context)
61
+
62
+ visitor.visit(result.value)
63
+
64
+ return visitor.offenses
65
+ }
66
+ }
@@ -0,0 +1,41 @@
1
+ import { ParserRule } from "../types.js"
2
+ import { HerbDisableCommentBaseVisitor } from "./herb-disable-comment-base.js"
3
+
4
+ import { parseHerbDisableContent } from "../herb-disable-comment-utils.js"
5
+
6
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
7
+ import type { ERBContentNode, ParseResult } from "@herb-tools/core"
8
+
9
+ class HerbDisableCommentMissingRulesVisitor extends HerbDisableCommentBaseVisitor {
10
+ protected checkHerbDisableComment(node: ERBContentNode, content: string): void {
11
+ const herbDisable = parseHerbDisableContent(content)
12
+ if (herbDisable) return
13
+
14
+ const emptyFormat = /^\s*herb:disable\s*$/
15
+ if (!emptyFormat.test(content)) return
16
+
17
+ this.addOffense(
18
+ `\`herb:disable\` comment is missing rule names. Specify \`all\` or list specific rules to disable.`,
19
+ node.location,
20
+ )
21
+ }
22
+ }
23
+
24
+ export class HerbDisableCommentMissingRulesRule extends ParserRule {
25
+ name = "herb-disable-comment-missing-rules"
26
+
27
+ get defaultConfig(): FullRuleConfig {
28
+ return {
29
+ enabled: true,
30
+ severity: "error"
31
+ }
32
+ }
33
+
34
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
35
+ const visitor = new HerbDisableCommentMissingRulesVisitor(this.name, context)
36
+
37
+ visitor.visit(result.value)
38
+
39
+ return visitor.offenses
40
+ }
41
+ }
@@ -0,0 +1,46 @@
1
+ import { ParserRule } from "../types.js"
2
+ import { HerbDisableCommentParsedVisitor } from "./herb-disable-comment-base.js"
3
+
4
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
5
+ import type { ERBContentNode, ParseResult } from "@herb-tools/core"
6
+ import type { HerbDisableComment } from "../herb-disable-comment-utils.js"
7
+
8
+ class HerbDisableCommentNoDuplicateRulesVisitor extends HerbDisableCommentParsedVisitor {
9
+ protected checkParsedHerbDisable(node: ERBContentNode, _content: string, herbDisable: HerbDisableComment): void {
10
+ const seenRules = new Map<string, number>()
11
+
12
+ herbDisable.ruleNameDetails.forEach((ruleDetail, index) => {
13
+ const firstIndex = seenRules.get(ruleDetail.name)
14
+
15
+ if (firstIndex !== undefined) {
16
+ const location = this.createRuleNameLocation(node, ruleDetail)
17
+ const message = `Duplicate rule \`${ruleDetail.name}\` in \`herb:disable\` comment. Remove the duplicate.`
18
+
19
+ this.addOffenseWithFallback(message, location, node)
20
+
21
+ return
22
+ }
23
+
24
+ seenRules.set(ruleDetail.name, index)
25
+ })
26
+ }
27
+ }
28
+
29
+ export class HerbDisableCommentNoDuplicateRulesRule extends ParserRule {
30
+ name = "herb-disable-comment-no-duplicate-rules"
31
+
32
+ get defaultConfig(): FullRuleConfig {
33
+ return {
34
+ enabled: true,
35
+ severity: "warning"
36
+ }
37
+ }
38
+
39
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
40
+ const visitor = new HerbDisableCommentNoDuplicateRulesVisitor(this.name, context)
41
+
42
+ visitor.visit(result.value)
43
+
44
+ return visitor.offenses
45
+ }
46
+ }
@@ -0,0 +1,40 @@
1
+ import { ParserRule } from "../types.js"
2
+ import { HerbDisableCommentParsedVisitor } from "./herb-disable-comment-base.js"
3
+
4
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
5
+ import type { ERBContentNode, ParseResult } from "@herb-tools/core"
6
+ import type { HerbDisableComment } from "../herb-disable-comment-utils.js"
7
+
8
+ class HerbDisableCommentNoRedundantAllVisitor extends HerbDisableCommentParsedVisitor {
9
+ protected checkParsedHerbDisable(node: ERBContentNode, _content: string, herbDisable: HerbDisableComment): void {
10
+ if (!herbDisable.ruleNames.includes("all")) return
11
+ if (herbDisable.ruleNames.length <= 1) return
12
+
13
+ const allDetail = herbDisable.ruleNameDetails.find(detail => detail.name === "all")
14
+ if (!allDetail) return
15
+
16
+ const location = this.createRuleNameLocation(node, allDetail)
17
+ const message = `Using \`all\` with specific rules is redundant. Use \`herb:disable all\` by itself or list only specific rules.`
18
+
19
+ this.addOffenseWithFallback(message, location, node)
20
+ }
21
+ }
22
+
23
+ export class HerbDisableCommentNoRedundantAllRule extends ParserRule {
24
+ name = "herb-disable-comment-no-redundant-all"
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 HerbDisableCommentNoRedundantAllVisitor(this.name, context)
35
+
36
+ visitor.visit(result.value)
37
+
38
+ return visitor.offenses
39
+ }
40
+ }
@@ -0,0 +1,103 @@
1
+ import { ParserRule } from "../types.js"
2
+ import { HerbDisableCommentParsedVisitor } from "./herb-disable-comment-base.js"
3
+
4
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
5
+ import type { ERBContentNode, ParseResult } from "@herb-tools/core"
6
+ import type { HerbDisableComment } from "../herb-disable-comment-utils.js"
7
+
8
+ class HerbDisableCommentUnnecessaryVisitor extends HerbDisableCommentParsedVisitor {
9
+ private ignoredOffensesByLine: Map<number, Set<string>>
10
+ private validRuleNames: Set<string>
11
+
12
+ constructor(ruleName: string, ignoredOffensesByLine: Map<number, Set<string>>, validRuleNames: string[], context?: Partial<LintContext>) {
13
+ super(ruleName, context)
14
+
15
+ this.ignoredOffensesByLine = ignoredOffensesByLine
16
+ this.validRuleNames = new Set([...validRuleNames, "all"])
17
+ }
18
+
19
+ protected checkParsedHerbDisable(node: ERBContentNode, _content: string, herbDisable: HerbDisableComment): void {
20
+ const line = node.location.start.line
21
+ const usedRuleNames = this.ignoredOffensesByLine.get(line) || new Set<string>()
22
+
23
+ if (herbDisable.ruleNames.includes("all")) {
24
+ if (herbDisable.ruleNames.length > 1) return
25
+ if (usedRuleNames.size > 0) return
26
+
27
+ this.addOffense(
28
+ `No offenses to disable on this line. Remove the \`herb:disable all\` comment.`,
29
+ node.location,
30
+ )
31
+
32
+ return
33
+ }
34
+
35
+ const unnecessaryRules = herbDisable.ruleNameDetails.filter(
36
+ detail => this.validRuleNames.has(detail.name) && !usedRuleNames.has(detail.name)
37
+ )
38
+
39
+ if (unnecessaryRules.length === 0) return
40
+
41
+ const validRuleCount = herbDisable.ruleNames.filter(name => this.validRuleNames.has(name)).length
42
+
43
+ if (unnecessaryRules.length === validRuleCount) {
44
+ if (unnecessaryRules.length === 1) {
45
+ const ruleName = unnecessaryRules[0].name
46
+
47
+ this.addOffense(
48
+ `No offenses from \`${ruleName}\` on this line. Remove the \`herb:disable\` comment.`,
49
+ node.location,
50
+ )
51
+
52
+ return
53
+ }
54
+
55
+ const unnecessaryRuleNames = unnecessaryRules.map(rule => `\`${rule.name}\``).join(", ")
56
+
57
+ this.addOffense(
58
+ `No offenses from rules ${unnecessaryRuleNames} on this line. Remove them from the \`herb:disable\` comment.`,
59
+ node.location,
60
+ )
61
+
62
+ return
63
+ }
64
+
65
+ for (const unnecessaryRule of unnecessaryRules) {
66
+ const location = this.createRuleNameLocation(node, unnecessaryRule)
67
+ const message = `No offenses from \`${unnecessaryRule.name}\` on this line. Remove it from the \`herb:disable\` comment.`
68
+
69
+ this.addOffenseWithFallback(message, location, node)
70
+ }
71
+ }
72
+ }
73
+
74
+ export class HerbDisableCommentUnnecessaryRule extends ParserRule {
75
+ name = "herb-disable-comment-unnecessary"
76
+
77
+ get defaultConfig(): FullRuleConfig {
78
+ return {
79
+ enabled: true,
80
+ severity: "warning"
81
+ }
82
+ }
83
+
84
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
85
+ const validRuleNames = context?.validRuleNames
86
+ const ignoredOffensesByLine = context?.ignoredOffensesByLine
87
+
88
+ if (!validRuleNames) return []
89
+ if (validRuleNames.length === 0) return []
90
+ if (!ignoredOffensesByLine) return []
91
+
92
+ const visitor = new HerbDisableCommentUnnecessaryVisitor(
93
+ this.name,
94
+ ignoredOffensesByLine,
95
+ validRuleNames,
96
+ context
97
+ )
98
+
99
+ visitor.visit(result.value)
100
+
101
+ return visitor.offenses
102
+ }
103
+ }
@@ -0,0 +1,62 @@
1
+ import { ParserRule } from "../types.js"
2
+ import { HerbDisableCommentParsedVisitor } from "./herb-disable-comment-base.js"
3
+
4
+ import { didyoumean } from "@herb-tools/core"
5
+
6
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
7
+ import type { ERBContentNode, ParseResult } from "@herb-tools/core"
8
+ import type { HerbDisableComment } from "../herb-disable-comment-utils.js"
9
+
10
+ class HerbDisableCommentValidRuleNameVisitor extends HerbDisableCommentParsedVisitor {
11
+ private validRuleNames: Set<string> = new Set()
12
+ private validRuleNamesList: string[] = []
13
+
14
+ constructor(ruleName: string, validRuleNames: string[], context?: Partial<LintContext>) {
15
+ super(ruleName, context)
16
+
17
+ this.validRuleNames = new Set([...validRuleNames, "all"])
18
+ this.validRuleNamesList = Array.from(this.validRuleNames)
19
+ }
20
+
21
+ protected checkParsedHerbDisable(node: ERBContentNode, _content: string, herbDisable: HerbDisableComment): void {
22
+ herbDisable.ruleNameDetails.forEach(ruleDetail => {
23
+ if (this.validRuleNames.has(ruleDetail.name)) return
24
+
25
+ const suggestion = didyoumean(ruleDetail.name, this.validRuleNamesList)
26
+ const message = suggestion
27
+ ? `Unknown rule \`${ruleDetail.name}\`. Did you mean \`${suggestion}\`?`
28
+ : `Unknown rule \`${ruleDetail.name}\`.`
29
+
30
+ const location = this.createRuleNameLocation(node, ruleDetail)
31
+ this.addOffenseWithFallback(message, location, node)
32
+ })
33
+ }
34
+ }
35
+
36
+ export class HerbDisableCommentValidRuleNameRule extends ParserRule {
37
+ name = "herb-disable-comment-valid-rule-name"
38
+
39
+ get defaultConfig(): FullRuleConfig {
40
+ return {
41
+ enabled: true,
42
+ severity: "warning"
43
+ }
44
+ }
45
+
46
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
47
+ const validRuleNames = context?.validRuleNames
48
+
49
+ if (!validRuleNames) return []
50
+ if (validRuleNames.length === 0) return []
51
+
52
+ const visitor = new HerbDisableCommentValidRuleNameVisitor(
53
+ this.name,
54
+ validRuleNames,
55
+ context
56
+ )
57
+
58
+ visitor.visit(result.value)
59
+
60
+ return visitor.offenses
61
+ }
62
+ }
@@ -1,7 +1,7 @@
1
1
  import { BaseRuleVisitor, getTagName, hasAttribute } from "./rule-utils.js"
2
2
 
3
3
  import { ParserRule } from "../types.js"
4
- import type { LintOffense, LintContext } from "../types.js"
4
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
5
5
  import type { HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
6
6
 
7
7
  class AnchorRechireHrefVisitor extends BaseRuleVisitor {
@@ -21,7 +21,6 @@ class AnchorRechireHrefVisitor extends BaseRuleVisitor {
21
21
  this.addOffense(
22
22
  "Add an `href` attribute to `<a>` to ensure it is focusable and accessible.",
23
23
  node.tag_name!.location,
24
- "error",
25
24
  )
26
25
  }
27
26
  }
@@ -30,7 +29,14 @@ class AnchorRechireHrefVisitor extends BaseRuleVisitor {
30
29
  export class HTMLAnchorRequireHrefRule extends ParserRule {
31
30
  name = "html-anchor-require-href"
32
31
 
33
- check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
32
+ get defaultConfig(): FullRuleConfig {
33
+ return {
34
+ enabled: true,
35
+ severity: "error"
36
+ }
37
+ }
38
+
39
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
34
40
  const visitor = new AnchorRechireHrefVisitor(this.name, context)
35
41
 
36
42
  visitor.visit(result.value)
@@ -1,7 +1,7 @@
1
1
  import { ParserRule } from "../types.js"
2
2
  import { ARIA_ATTRIBUTES, AttributeVisitorMixin, StaticAttributeStaticValueParams, StaticAttributeDynamicValueParams } from "./rule-utils.js"
3
3
 
4
- import type { LintOffense, LintContext } from "../types.js"
4
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
5
5
  import type { ParseResult, HTMLAttributeNode } from "@herb-tools/core"
6
6
 
7
7
  class AriaAttributeMustBeValid extends AttributeVisitorMixin {
@@ -20,7 +20,6 @@ class AriaAttributeMustBeValid extends AttributeVisitorMixin {
20
20
  this.addOffense(
21
21
  `The attribute \`${attributeName}\` is not a valid ARIA attribute. ARIA attributes must match the WAI-ARIA specification.`,
22
22
  attributeNode.location,
23
- "error"
24
23
  )
25
24
  }
26
25
  }
@@ -28,7 +27,14 @@ class AriaAttributeMustBeValid extends AttributeVisitorMixin {
28
27
  export class HTMLAriaAttributeMustBeValid extends ParserRule {
29
28
  name = "html-aria-attribute-must-be-valid"
30
29
 
31
- check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
30
+ get defaultConfig(): FullRuleConfig {
31
+ return {
32
+ enabled: true,
33
+ severity: "error"
34
+ }
35
+ }
36
+
37
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
32
38
  const visitor = new AriaAttributeMustBeValid(this.name, context)
33
39
 
34
40
  visitor.visit(result.value)
@@ -1,7 +1,7 @@
1
1
  import { ParserRule } from "../types.js"
2
2
  import { AttributeVisitorMixin, StaticAttributeStaticValueParams } from "./rule-utils.js"
3
3
 
4
- import type { LintOffense, LintContext } from "../types.js"
4
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
5
5
  import type { ParseResult } from "@herb-tools/core"
6
6
 
7
7
  class AriaLabelIsWellFormattedVisitor extends AttributeVisitorMixin {
@@ -12,7 +12,6 @@ class AriaLabelIsWellFormattedVisitor extends AttributeVisitorMixin {
12
12
  this.addOffense(
13
13
  "The `aria-label` attribute value text should not contain line breaks. Use concise, single-line descriptions.",
14
14
  attributeNode.location,
15
- "error"
16
15
  )
17
16
 
18
17
  return
@@ -22,7 +21,6 @@ class AriaLabelIsWellFormattedVisitor extends AttributeVisitorMixin {
22
21
  this.addOffense(
23
22
  "The `aria-label` attribute value should not be formatted like an ID. Use natural, sentence-case text instead.",
24
23
  attributeNode.location,
25
- "error"
26
24
  )
27
25
 
28
26
  return
@@ -32,7 +30,6 @@ class AriaLabelIsWellFormattedVisitor extends AttributeVisitorMixin {
32
30
  this.addOffense(
33
31
  "The `aria-label` attribute value text should be formatted like visual text. Use sentence case (capitalize the first letter).",
34
32
  attributeNode.location,
35
- "error"
36
33
  )
37
34
  }
38
35
  }
@@ -49,7 +46,14 @@ class AriaLabelIsWellFormattedVisitor extends AttributeVisitorMixin {
49
46
  export class HTMLAriaLabelIsWellFormattedRule extends ParserRule {
50
47
  name = "html-aria-label-is-well-formatted"
51
48
 
52
- check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
49
+ get defaultConfig(): FullRuleConfig {
50
+ return {
51
+ enabled: true,
52
+ severity: "error"
53
+ }
54
+ }
55
+
56
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
53
57
  const visitor = new AriaLabelIsWellFormattedVisitor(this.name, context)
54
58
 
55
59
  visitor.visit(result.value)
@@ -2,7 +2,7 @@ import { ParserRule } from "../types.js"
2
2
  import { AttributeVisitorMixin, StaticAttributeStaticValueParams, StaticAttributeDynamicValueParams } from "./rule-utils.js"
3
3
  import { getValidatableStaticContent, hasERBOutput, filterLiteralNodes, filterERBContentNodes, isERBOutputNode } from "@herb-tools/core"
4
4
 
5
- import type { LintOffense, LintContext } from "../types.js"
5
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
6
6
  import type { ParseResult, HTMLAttributeNode } from "@herb-tools/core"
7
7
 
8
8
  class HTMLAriaLevelMustBeValidVisitor extends AttributeVisitorMixin {
@@ -65,7 +65,14 @@ class HTMLAriaLevelMustBeValidVisitor extends AttributeVisitorMixin {
65
65
  export class HTMLAriaLevelMustBeValidRule extends ParserRule {
66
66
  name = "html-aria-level-must-be-valid"
67
67
 
68
- check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
68
+ get defaultConfig(): FullRuleConfig {
69
+ return {
70
+ enabled: true,
71
+ severity: "error"
72
+ }
73
+ }
74
+
75
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
69
76
  const visitor = new HTMLAriaLevelMustBeValidVisitor(this.name, context)
70
77
 
71
78
  visitor.visit(result.value)
@@ -1,7 +1,7 @@
1
1
  import { ParserRule } from "../types.js"
2
2
  import { AttributeVisitorMixin, getAttributeName, getAttributes, StaticAttributeStaticValueParams } from "./rule-utils.js"
3
3
 
4
- import type { LintOffense, LintContext } from "../types.js"
4
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
5
5
  import type { ParseResult } from "@herb-tools/core"
6
6
 
7
7
  class AriaRoleHeadingRequiresLevel extends AttributeVisitorMixin {
@@ -15,7 +15,6 @@ class AriaRoleHeadingRequiresLevel extends AttributeVisitorMixin {
15
15
  this.addOffense(
16
16
  `Element with \`role="heading"\` must have an \`aria-level\` attribute.`,
17
17
  attributeNode.location,
18
- "error"
19
18
  )
20
19
  }
21
20
  }
@@ -23,7 +22,14 @@ class AriaRoleHeadingRequiresLevel extends AttributeVisitorMixin {
23
22
  export class HTMLAriaRoleHeadingRequiresLevelRule extends ParserRule {
24
23
  name = "html-aria-role-heading-requires-level"
25
24
 
26
- check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
25
+ get defaultConfig(): FullRuleConfig {
26
+ return {
27
+ enabled: true,
28
+ severity: "error"
29
+ }
30
+ }
31
+
32
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
27
33
  const visitor = new AriaRoleHeadingRequiresLevel(this.name, context)
28
34
 
29
35
  visitor.visit(result.value)