@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
@@ -1,24 +1,76 @@
1
1
  import { Diagnostic, LexResult, ParseResult } from "@herb-tools/core";
2
- import type { defaultRules } from "./default-rules.js";
2
+ import type { rules } from "./rules.js";
3
+ import type { Node } from "@herb-tools/core";
4
+ import type { RuleConfig } from "@herb-tools/config";
5
+ import type { Mutable } from "@herb-tools/rewriter";
6
+ export type { Mutable } from "@herb-tools/rewriter";
3
7
  export type LintSeverity = "error" | "warning" | "info" | "hint";
8
+ export type FullRuleConfig = Required<Pick<RuleConfig, 'enabled' | 'severity'>> & Omit<RuleConfig, 'enabled' | 'severity'>;
4
9
  /**
5
10
  * Automatically inferred union type of all available linter rule names.
6
11
  * This type extracts the 'name' property from each rule class instance.
7
12
  */
8
- export type LinterRule = InstanceType<typeof defaultRules[number]>['name'];
9
- export interface LintOffense extends Diagnostic {
13
+ export type LinterRule = InstanceType<typeof rules[number]>['name'];
14
+ /**
15
+ * Base context for autofix operations. Contains the offending node.
16
+ * Rules can extend this interface to include rule-specific autofix data.
17
+ * Note: The node is typed as Mutable to allow direct mutation in autofix methods.
18
+ */
19
+ export interface BaseAutofixContext {
20
+ /** The AST node, token, or data structure that caused the offense (mutable) */
21
+ node: Mutable<Node>;
22
+ }
23
+ /**
24
+ * A lint offense without severity bound. Rules produce these, and the Linter
25
+ * binds severity based on the rule's defaultConfig and user config overrides.
26
+ */
27
+ export interface UnboundLintOffense<TAutofixContext extends BaseAutofixContext = BaseAutofixContext> extends Omit<Diagnostic, 'severity'> {
10
28
  rule: LinterRule;
29
+ /** Context data for autofix, including the offending node and rule-specific data */
30
+ autofixContext?: TAutofixContext;
31
+ }
32
+ /**
33
+ * A lint offense with severity bound. The Linter produces these by binding
34
+ * severity to UnboundLintOffenses based on rule configuration.
35
+ */
36
+ export interface LintOffense<TAutofixContext extends BaseAutofixContext = BaseAutofixContext> extends UnboundLintOffense<TAutofixContext> {
11
37
  severity: LintSeverity;
12
38
  }
13
- export interface LintResult {
14
- offenses: LintOffense[];
39
+ export interface LintResult<TAutofixContext extends BaseAutofixContext = BaseAutofixContext> {
40
+ offenses: LintOffense<TAutofixContext>[];
15
41
  errors: number;
16
42
  warnings: number;
43
+ info: number;
44
+ hints: number;
45
+ ignored: number;
46
+ wouldBeIgnored?: number;
47
+ }
48
+ /**
49
+ * Result of applying autofixes to source code
50
+ */
51
+ export interface AutofixResult<TAutofixContext extends BaseAutofixContext = BaseAutofixContext> {
52
+ /** The corrected source code with all fixes applied */
53
+ source: string;
54
+ /** Offenses that were successfully fixed */
55
+ fixed: LintOffense<TAutofixContext>[];
56
+ /** Offenses that could not be automatically fixed */
57
+ unfixed: LintOffense<TAutofixContext>[];
17
58
  }
18
- export declare abstract class ParserRule {
59
+ /**
60
+ * Default configuration for rules when defaultConfig is not specified.
61
+ * Custom rules can omit defaultConfig and will use these defaults.
62
+ */
63
+ export declare const DEFAULT_RULE_CONFIG: FullRuleConfig;
64
+ /**
65
+ * Base class for parser rules.
66
+ */
67
+ export declare abstract class ParserRule<TAutofixContext extends BaseAutofixContext = BaseAutofixContext> {
19
68
  static type: "parser";
69
+ /** Indicates whether this rule supports autofix. Defaults to false. */
70
+ static autocorrectable: boolean;
20
71
  abstract name: string;
21
- abstract check(result: ParseResult, context?: Partial<LintContext>): LintOffense[];
72
+ get defaultConfig(): FullRuleConfig;
73
+ abstract check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<TAutofixContext>[];
22
74
  /**
23
75
  * Optional method to determine if this rule should run.
24
76
  * If not implemented, rule is always enabled.
@@ -27,11 +79,26 @@ export declare abstract class ParserRule {
27
79
  * @returns true if rule should run, false to skip
28
80
  */
29
81
  isEnabled?(result: ParseResult, context?: Partial<LintContext>): boolean;
82
+ /**
83
+ * Optional method to automatically fix an offense by mutating the AST.
84
+ * If not implemented, the rule does not support autofix.
85
+ * @param offense - The offense to fix (includes autofixContext with node and rule-specific data)
86
+ * @param result - The parse result containing the AST (mutate it directly and return it)
87
+ * @param context - Optional context for linting
88
+ * @returns The mutated ParseResult if fixed, or null if the offense could not be fixed
89
+ */
90
+ autofix?(offense: LintOffense<TAutofixContext>, result: ParseResult, context?: Partial<LintContext>): ParseResult | null;
30
91
  }
31
- export declare abstract class LexerRule {
92
+ /**
93
+ * Base class for lexer rules.
94
+ */
95
+ export declare abstract class LexerRule<TAutofixContext extends BaseAutofixContext = BaseAutofixContext> {
32
96
  static type: "lexer";
97
+ /** Indicates whether this rule supports autofix. Defaults to false. */
98
+ static autocorrectable: boolean;
33
99
  abstract name: string;
34
- abstract check(lexResult: LexResult, context?: Partial<LintContext>): LintOffense[];
100
+ get defaultConfig(): FullRuleConfig;
101
+ abstract check(lexResult: LexResult, context?: Partial<LintContext>): UnboundLintOffense<TAutofixContext>[];
35
102
  /**
36
103
  * Optional method to determine if this rule should run.
37
104
  * If not implemented, rule is always enabled.
@@ -40,6 +107,15 @@ export declare abstract class LexerRule {
40
107
  * @returns true if rule should run, false to skip
41
108
  */
42
109
  isEnabled?(lexResult: LexResult, context?: Partial<LintContext>): boolean;
110
+ /**
111
+ * Optional method to automatically fix an offense by mutating tokens.
112
+ * If not implemented, the rule does not support autofix.
113
+ * @param offense - The offense to fix (includes autofixContext with node and rule-specific data)
114
+ * @param lexResult - The lex result containing tokens (mutate them directly and return)
115
+ * @param context - Optional context for linting
116
+ * @returns The mutated LexResult if fixed, or null if the offense could not be fixed
117
+ */
118
+ autofix?(offense: LintOffense<TAutofixContext>, lexResult: LexResult, context?: Partial<LintContext>): LexResult | null;
43
119
  }
44
120
  export interface LexerRuleConstructor {
45
121
  type: "lexer";
@@ -51,15 +127,21 @@ export interface LexerRuleConstructor {
51
127
  */
52
128
  export interface LintContext {
53
129
  fileName: string | undefined;
130
+ validRuleNames: string[] | undefined;
131
+ ignoredOffensesByLine: Map<number, Set<string>> | undefined;
132
+ ignoreDisableComments: boolean | undefined;
54
133
  }
55
134
  /**
56
135
  * Default context object with all keys defined but set to undefined
57
136
  */
58
137
  export declare const DEFAULT_LINT_CONTEXT: LintContext;
59
- export declare abstract class SourceRule {
138
+ export declare abstract class SourceRule<TAutofixContext extends BaseAutofixContext = BaseAutofixContext> {
60
139
  static type: "source";
140
+ /** Indicates whether this rule supports autofix. Defaults to false. */
141
+ static autocorrectable: boolean;
61
142
  abstract name: string;
62
- abstract check(source: string, context?: Partial<LintContext>): LintOffense[];
143
+ get defaultConfig(): FullRuleConfig;
144
+ abstract check(source: string, context?: Partial<LintContext>): UnboundLintOffense<TAutofixContext>[];
63
145
  /**
64
146
  * Optional method to determine if this rule should run.
65
147
  * If not implemented, rule is always enabled.
@@ -68,6 +150,15 @@ export declare abstract class SourceRule {
68
150
  * @returns true if rule should run, false to skip
69
151
  */
70
152
  isEnabled?(source: string, context?: Partial<LintContext>): boolean;
153
+ /**
154
+ * Optional method to automatically fix an offense.
155
+ * If not implemented, the rule does not support autofix.
156
+ * @param offense - The offense to fix (includes autofixContext with node and rule-specific data)
157
+ * @param source - The original source code
158
+ * @param context - Optional context for linting
159
+ * @returns The corrected source if the offense can be fixed, null otherwise
160
+ */
161
+ autofix?(offense: LintOffense<TAutofixContext>, source: string, context?: Partial<LintContext>): string | null;
71
162
  }
72
163
  export interface SourceRuleConstructor {
73
164
  type: "source";
@@ -4,12 +4,23 @@ This page contains documentation for all Herb Linter rules.
4
4
 
5
5
  ## Available Rules
6
6
 
7
+ - [`erb-comment-syntax`](./erb-comment-syntax.md) - Disallow Ruby comments immediately after ERB tags
8
+ - [`erb-no-case-node-children`](./erb-no-case-node-children.md) - Don't use `children` for `case/when` and `case/in` nodes
7
9
  - [`erb-no-empty-tags`](./erb-no-empty-tags.md) - Disallow empty ERB tags
10
+ - [`erb-no-extra-newline`](./erb-no-extra-newline.md) - Disallow extra newlines.
11
+ - [`erb-no-extra-whitespace-inside-tags`](./erb-no-extra-whitespace-inside-tags.md) - Disallow multiple consecutive spaces inside ERB tags
8
12
  - [`erb-no-output-control-flow`](./erb-no-output-control-flow.md) - Prevents outputting control flow blocks
9
13
  - [`erb-no-silent-tag-in-attribute-name`](./erb-no-silent-tag-in-attribute-name.md) - Disallow ERB silent tags in HTML attribute names
10
14
  - [`erb-prefer-image-tag-helper`](./erb-prefer-image-tag-helper.md) - Prefer `image_tag` helper over `<img>` with ERB expressions
11
15
  - [`erb-require-whitespace-inside-tags`](./erb-require-whitespace-inside-tags.md) - Requires whitespace around ERB tags
12
- - [`erb-requires-trailing-newline`](./erb-requires-trailing-newline.md) - Enforces that all HTML+ERB template files end with exactly one trailing newline character.
16
+ - [`erb-require-trailing-newline`](./erb-require-trailing-newline.md) - Enforces that all HTML+ERB template files end with exactly one trailing newline character.
17
+ - [`erb-right-trim`](./erb-right-trim.md) - Enforce consistent right-trimming syntax.
18
+ - [`herb-disable-comment-malformed`](./herb-disable-comment-malformed.md) - Detect malformed `herb:disable` comments.
19
+ - [`herb-disable-comment-missing-rules`](./herb-disable-comment-missing-rules.md) - Require rule names in `herb:disable` comments.
20
+ - [`herb-disable-comment-no-duplicate-rules`](./herb-disable-comment-no-duplicate-rules.md) - Disallow duplicate rule names in `herb:disable` comments.
21
+ - [`herb-disable-comment-no-redundant-all`](./herb-disable-comment-no-redundant-all.md) - Disallow redundant use of `all` in `herb:disable` comments.
22
+ - [`herb-disable-comment-unnecessary`](./herb-disable-comment-unnecessary.md) - Detect unnecessary `herb:disable` comments.
23
+ - [`herb-disable-comment-valid-rule-name`](./herb-disable-comment-valid-rule-name.md) - Validate rule names in `herb:disable` comments.
13
24
  - [`html-anchor-require-href`](./html-anchor-require-href.md) - Requires an href attribute on anchor tags
14
25
  - [`html-aria-attribute-must-be-valid`](./html-aria-attribute-must-be-valid.md) - Disallow invalid or unknown `aria-*` attributes.
15
26
  - [`html-aria-label-is-well-formatted`](./html-aria-label-is-well-formatted.md) - `aria-label` must be well-formatted
@@ -20,21 +31,26 @@ This page contains documentation for all Herb Linter rules.
20
31
  - [`html-attribute-equals-spacing`](./html-attribute-equals-spacing.md) - No whitespace around `=` in HTML attributes
21
32
  - [`html-attribute-values-require-quotes`](./html-attribute-values-require-quotes.md) - Requires quotes around attribute values
22
33
  - [`html-avoid-both-disabled-and-aria-disabled`](./html-avoid-both-disabled-and-aria-disabled.md) - Avoid using both `disabled` and `aria-disabled` attributes
34
+ - [`html-body-only-elements`](./html-body-only-elements.md) - Require content elements inside `<body>`.
23
35
  - [`html-boolean-attributes-no-value`](./html-boolean-attributes-no-value.md) - Prevents values on boolean attributes
36
+ - [`html-head-only-elements`](./html-head-only-elements.md) - Require head-scoped elements inside `<head>`.
24
37
  - [`html-iframe-has-title`](./html-iframe-has-title.md) - `iframe` elements must have a `title` attribute
38
+ - [`html-input-require-autocomplete`](./html-input-require-autocomplete.md) - Require `autocomplete` attributes on `<input>` tags.
25
39
  - [`html-img-require-alt`](./html-img-require-alt.md) - Requires `alt` attributes on `<img>` tags
26
40
  - [`html-navigation-has-label`](./html-navigation-has-label.md) - Navigation landmarks must have accessible labels
27
41
  - [`html-no-aria-hidden-on-focusable`](./html-no-aria-hidden-on-focusable.md) - Focusable elements should not have `aria-hidden="true"`
28
42
  - [`html-no-block-inside-inline`](./html-no-block-inside-inline.md) - Prevents block-level elements inside inline elements
29
43
  - [`html-no-duplicate-attributes`](./html-no-duplicate-attributes.md) - Prevents duplicate attributes on HTML elements
30
44
  - [`html-no-duplicate-ids`](./html-no-duplicate-ids.md) - Prevents duplicate IDs within a document
45
+ - [`html-no-duplicate-meta-names`](./html-no-duplicate-meta-names.md) - Duplicate `<meta>` name attributes are not allowed.
31
46
  - [`html-no-empty-attributes`](./html-no-empty-attributes.md) - Attributes must not have empty values
32
47
  - [`html-no-nested-links`](./html-no-nested-links.md) - Prevents nested anchor tags
33
48
  - [`html-no-positive-tab-index`](./html-no-positive-tab-index.md) - Avoid positive `tabindex` values
34
- - [`html-no-self-closing`](./html-no-self-closing.md.md) - Disallow self closing tags
49
+ - [`html-no-self-closing`](./html-no-self-closing.md) - Disallow self closing tags
50
+ - [`html-no-space-in-tag`](./html-no-space-in-tag.md) - Disallow spaces in HTML tags
35
51
  - [`html-no-title-attribute`](./html-no-title-attribute.md) - Avoid using the `title` attribute
36
- - [`html-tag-name-lowercase`](./html-tag-name-lowercase.md) - Enforces lowercase tag names in HTML
37
52
  - [`html-no-underscores-in-attribute-names`](./html-no-underscores-in-attribute-names.md) - Disallow underscores in HTML attribute names
53
+ - [`html-tag-name-lowercase`](./html-tag-name-lowercase.md) - Enforces lowercase tag names in HTML
38
54
  - [`parser-no-errors`](./parser-no-errors.md) - Disallow parser errors in HTML+ERB documents
39
55
  - [`svg-tag-name-capitalization`](./svg-tag-name-capitalization.md) - Enforces proper camelCase capitalization for SVG elements
40
56
 
@@ -0,0 +1,44 @@
1
+ # Linter Rule: Disallow Ruby comments immediately after ERB tags
2
+
3
+ **Rule:** `erb-comment-syntax`
4
+
5
+ ## Description
6
+
7
+ Disallow ERB tags that start with `<% #` (with a space before the `#`). Use the ERB comment syntax `<%#` instead.
8
+
9
+ ## Rationale
10
+
11
+ Ruby comments starting immediately after an ERB tag opening (e.g., `<% # comment %>`) can cause parsing issues in some contexts. The proper ERB comment syntax `<%# comment %>` is more reliable and explicitly designed for comments in templates.
12
+
13
+ For multi-line comments or actual Ruby code with comments, ensure the content starts on a new line after the opening tag.
14
+
15
+ ## Examples
16
+
17
+ ### ✅ Good
18
+
19
+ ```erb
20
+ <%# This is a proper ERB comment %>
21
+
22
+ <%
23
+ # This is a proper ERB comment
24
+ %>
25
+
26
+ <%
27
+ # Multi-line Ruby comment
28
+ # spanning multiple lines
29
+ %>
30
+ ```
31
+
32
+ ### 🚫 Bad
33
+
34
+ ```erb
35
+ <% # This should be an ERB comment %>
36
+
37
+ <%= # This should also be an ERB comment %>
38
+
39
+ <%== # This should also be an ERB comment %>
40
+ ```
41
+
42
+ ## References
43
+
44
+ - [Inspiration: ERB Lint `CommentSyntax` rule](https://github.com/shopify/erb_lint?tab=readme-ov-file#commentsyntax)
@@ -0,0 +1,50 @@
1
+ # Linter Rule: Don't use `children` for `case/when` and `case/in` nodes
2
+
3
+ **Rule:** `erb-no-case-node-children`
4
+
5
+ ## Description
6
+
7
+ Disallow placing content or expressions directly between the opening `<% case %>` and the first `<% when %>` or `<% in %>` clause in an HTML+ERB template.
8
+
9
+ In Ruby and ERB, `case` expressions are intended to branch execution. Any content placed between the `case` and its `when`/`in` clauses is not executed as part of the branching logic, and may lead to confusion, orphaned output, or silent bugs.
10
+
11
+ ## Rationale
12
+
13
+ Including content directly inside a `case block` is likely unintended and often leads to unclear or broken rendering logic.
14
+
15
+ Most developers expect that nothing happens until the first `when`/`in` matches. Content in that location is often a copy/paste or indentation mistake and should be flagged to maintain clarity and correctness in templates.
16
+
17
+ This mirrors how `case` works in Ruby itself and helps avoid surprising output.
18
+
19
+ ## Examples
20
+
21
+ ### ✅ Good
22
+
23
+ ```erb
24
+ <% case variable %>
25
+ <% when "a" %>
26
+ A
27
+ <% when "b" %>
28
+ B
29
+ <% else %>
30
+ C
31
+ <% end %>
32
+ ```
33
+
34
+ ### 🚫 Bad
35
+
36
+ ```erb
37
+ <% case variable %>
38
+ This content is outside of any when/in/else block!
39
+ <% when "a" %>
40
+ A
41
+ <% when "b" %>
42
+ B
43
+ <% else %>
44
+ C
45
+ <% end %>
46
+ ```
47
+
48
+ ## References
49
+
50
+ \-
@@ -0,0 +1,74 @@
1
+ # Linter Rule: Disallow extra newlines
2
+
3
+ **Rule:** `erb-no-extra-newline`
4
+
5
+ ## Description
6
+
7
+ Disallow more than two consecutive blank lines in ERB templates. This rule enforces a maximum of two blank lines between content to maintain consistent vertical spacing throughout your templates.
8
+
9
+ ## Rationale
10
+
11
+ Excessive blank lines can make templates harder to read and maintain. While some vertical spacing improves readability by visually separating logical sections, too many blank lines create unnecessary whitespace that:
12
+
13
+ * Makes it harder to see related code on the same screen
14
+ * Creates inconsistent visual rhythm in the codebase
15
+ * Can accidentally accumulate through merge conflicts or refactoring
16
+ * Provides no additional clarity beyond what 1-2 blank lines already achieve
17
+
18
+ Limiting to two consecutive blank lines strikes a balance between allowing clear section separation while maintaining code density and readability.
19
+
20
+ ## Examples
21
+
22
+ ### ✅ Good
23
+
24
+ ```html
25
+ line 1
26
+
27
+ line 3
28
+
29
+ <div>
30
+ <h1>Title</h1>
31
+ </div>
32
+
33
+ <div>
34
+ <h1>Section 1</h1>
35
+
36
+ <p>Content here</p>
37
+ </div>
38
+
39
+ <div>
40
+ <h1>Section 1</h1>
41
+
42
+
43
+ <h1>Section 2</h1>
44
+ </div>
45
+ ```
46
+
47
+ ### 🚫 Bad
48
+
49
+ ```erb
50
+ line 1
51
+
52
+
53
+
54
+ line 3
55
+
56
+ <div>
57
+ <h1>Title</h1>
58
+
59
+
60
+
61
+ <p>Content</p>
62
+ </div>
63
+
64
+ <%= user.name %>
65
+
66
+
67
+
68
+
69
+ <%= user.email %>
70
+ ```
71
+
72
+ ## References
73
+
74
+ - [Inspiration: ERB Lint `ExtraNewline` rule](https://github.com/Shopify/erb_lint/blob/main/README.md)
@@ -0,0 +1,39 @@
1
+ # Linter Rule: Avoid extra whitespace inside ERB tags
2
+
3
+ **Rule:** `erb-no-extra-whitespace-inside-tags`
4
+
5
+ ## Description
6
+
7
+ This rule disallows **multiple consecutive spaces** immediately inside ERB tags (`<%`, `<%=`) or before the closing delimiter (`%>`). It ensures that ERB code is consistently and cleanly formatted, with exactly one space after the opening tag and one space before the closing tag (when appropriate).
8
+
9
+ ## Rationale
10
+
11
+ Excess whitespace inside ERB tags can lead to inconsistent formatting and untidy templates. By enforcing a consistent amount of whitespace inside ERB tags, this rule improves code readability, aligns with the formatter and style guide expectations, and avoids unnecessary visual noise.
12
+
13
+ ## Examples
14
+
15
+ ### ✅ Good
16
+
17
+ ```erb
18
+ <%= output %>
19
+
20
+ <% if condition %>
21
+ True
22
+ <% end %>
23
+ ```
24
+
25
+ ### 🚫 Bad
26
+
27
+ ```erb
28
+ <%= output %>
29
+
30
+ <%= output %>
31
+
32
+ <% if condition %>
33
+ True
34
+ <% end %>
35
+ ```
36
+
37
+ ## References
38
+
39
+ \-
@@ -1,6 +1,6 @@
1
1
  # Linter Rule: Enforce trailing newline
2
2
 
3
- **Rule:** `erb-requires-trailing-newline`
3
+ **Rule:** `erb-require-trailing-newline`
4
4
 
5
5
  ## Description
6
6
 
@@ -0,0 +1,52 @@
1
+ # Linter Rule: Enforce consistent right-trimming syntax
2
+
3
+ **Rule:** `erb-right-trim`
4
+
5
+ ## Description
6
+
7
+ This rule enforces the use of `-%>` for right-trimming ERB output tags (like `<%= %>`) instead of `=%>`.
8
+
9
+ ## Rationale
10
+
11
+ While `=%>` can be used for right-trimming whitespace in some ERB engines (like Erubi), it is an obscure and not well-defined syntax that lacks consistent support across most ERB implementations.
12
+
13
+ The `-%>` syntax is the standard, well-documented approach for right-trimming that is universally supported and consistent with left-trimming syntax (`<%-`). Using `-%>` ensures compatibility across different ERB engines, improves code clarity, and aligns with established Rails and ERB conventions.
14
+
15
+ ## Examples
16
+
17
+ ### ✅ Good
18
+
19
+ ```erb
20
+ <%= title -%>
21
+
22
+ <% if condition? %>
23
+ <h1>Content</h1>
24
+ <% end %>
25
+
26
+ <% items.each do |item| %>
27
+ <li><%= item -%></li>
28
+ <% end %>
29
+ ```
30
+
31
+ ### 🚫 Bad
32
+
33
+ ```erb
34
+ <%= title =%>
35
+
36
+
37
+ <% title =%>
38
+
39
+
40
+ <% if true =%>
41
+ <h1>Content</h1>
42
+ <% end %>
43
+
44
+
45
+ <% items.each do |item| =%>
46
+ <li><%= item %></li>
47
+ <% end %>
48
+ ```
49
+
50
+ ## References
51
+
52
+ - [Inspiration: ERB Lint `RightTrim` rule](https://github.com/Shopify/erb_lint/blob/main/README.md#righttrim)
@@ -0,0 +1,45 @@
1
+ # Linter Rule: Detect malformed `herb:disable` comments
2
+
3
+ **Rule:** `herb-disable-comment-malformed`
4
+
5
+ ## Description
6
+
7
+ Detects malformed `<%# herb:disable ... %>` comments that have syntax errors like trailing commas, leading commas, consecutive commas, or missing spaces after `herb:disable`.
8
+
9
+ ## Rationale
10
+
11
+ Malformed `<%# herb:disable ... %>` comments can fail to parse correctly, leading to unexpected behavior where rules aren't actually disabled. This rule catches common syntax errors to ensure your disable comments work as intended.
12
+
13
+ ## Examples
14
+
15
+ ### ✅ Good
16
+
17
+ ```erb
18
+ <DIV>test</DIV> <%# herb:disable html-tag-name-lowercase %>
19
+
20
+ <DIV class='value'>test</DIV> <%# herb:disable html-tag-name-lowercase, html-attribute-double-quotes %>
21
+
22
+ <DIV class='value'>test</DIV> <%# herb:disable html-tag-name-lowercase , html-attribute-double-quotes %>
23
+
24
+ <DIV>test</DIV> <%# herb:disable all %>
25
+ ```
26
+
27
+ ### 🚫 Bad
28
+
29
+ ```erb
30
+ <div>test</div> <%# herb:disable html-tag-name-lowercase, %>
31
+
32
+ <div>test</div> <%# herb:disable , html-tag-name-lowercase %>
33
+
34
+ <div>test</div> <%# herb:disable html-tag-name-lowercase,, html-attribute-double-quotes %>
35
+
36
+ <div>test</div> <%# herb:disable html-tag-name-lowercase,, %>
37
+
38
+ <DIV>test</DIV> <%# herb:disableall %>
39
+
40
+ <DIV>test</DIV> <%# herb:disablehtml-tag-name-lowercase %>
41
+ ```
42
+
43
+ ## References
44
+
45
+ \-
@@ -0,0 +1,60 @@
1
+ # Linter Rule: Require rule names in `herb:disable` comments
2
+
3
+ **Rule:** `herb-disable-comment-missing-rules`
4
+
5
+ ## Description
6
+
7
+ Requires that `<%# herb:disable %>` comments specify either `all` or at least one specific rule name.
8
+
9
+ ## Rationale
10
+
11
+ A `<%# herb:disable %>` comment without any rule names serves no purpose and likely indicates an incomplete edit or mistake. The developer either:
12
+
13
+ - Forgot to specify which rules to disable
14
+ - Intended to use `herb:disable all` but forgot to add `all`
15
+ - Started typing a comment but didn't finish
16
+
17
+ This rule ensures all `<%# herb:disable %>` comments are complete and functional.
18
+
19
+ ## Examples
20
+
21
+ ### ✅ Good
22
+
23
+ ```erb
24
+ <DIV class='value'>test</DIV> <%# herb:disable all %>
25
+
26
+ <DIV>test</DIV> <%# herb:disable html-tag-name-lowercase %>
27
+
28
+ <DIV class='value'>test</DIV> <%# herb:disable html-tag-name-lowercase, html-attribute-double-quotes %>
29
+ ```
30
+
31
+ ### 🚫 Bad
32
+
33
+ ```erb
34
+ <div>test</div> <%# herb:disable %>
35
+
36
+ <div>test</div> <%# herb:disable %>
37
+ ```
38
+
39
+ ## Fix
40
+
41
+ Add either `all` or specific rule names:
42
+
43
+ **Option 1:** Disable all rules
44
+ ```erb
45
+ <DIV>test</DIV> <%# herb:disable all %>
46
+ ```
47
+
48
+ **Option 2:** Disable specific rules
49
+ ```erb
50
+ <DIV>test</DIV> <%# herb:disable html-tag-name-lowercase %>
51
+ ```
52
+
53
+ **Option 3:** Remove the comment if it's not needed
54
+ ```erb
55
+ <div>test</div>
56
+ ```
57
+
58
+ ## References
59
+
60
+ \-
@@ -0,0 +1,49 @@
1
+ # Linter Rule: Disallow duplicate rule names in `herb:disable` comments
2
+
3
+ **Rule:** `herb-disable-comment-no-duplicate-rules`
4
+
5
+ ## Description
6
+
7
+ Prevents listing the same rule name multiple times in a `<%# herb:disable ... %>` comment.
8
+
9
+ ## Rationale
10
+
11
+ Listing a rule name more than once in a `<%# herb:disable ... %>` comment is unnecessary and likely indicates a copy-paste error or mistake. Each rule only needs to be mentioned once to be disabled.
12
+
13
+ This rule helps keep disable comments clean and prevents confusion about which rules are being disabled.
14
+
15
+ ## Examples
16
+
17
+ ### ✅ Good
18
+
19
+ ```erb
20
+ <DIV class='value'>test</DIV> <%# herb:disable html-tag-name-lowercase, html-attribute-double-quotes %>
21
+
22
+ <DIV>test</DIV> <%# herb:disable html-tag-name-lowercase %>
23
+
24
+ <DIV>test</DIV> <%# herb:disable all %>
25
+ ```
26
+
27
+ ### 🚫 Bad
28
+
29
+ ```erb
30
+ <DIV>test</DIV> <%# herb:disable html-tag-name-lowercase, html-tag-name-lowercase %>
31
+
32
+ <DIV class='value'>test</DIV> <%# herb:disable html-attribute-double-quotes, html-tag-name-lowercase, html-tag-name-lowercase %>
33
+
34
+ <DIV class='value'>test</DIV> <%# herb:disable html-tag-name-lowercase, html-tag-name-lowercase, html-attribute-double-quotes, html-attribute-double-quotes %>
35
+
36
+ <DIV>test</DIV> <%# herb:disable all, all %>
37
+ ```
38
+
39
+ ## Fix
40
+
41
+ Remove the duplicate rule names, keeping only one instance of each:
42
+
43
+ ```erb
44
+ <DIV class='value'>test</DIV> <%# herb:disable html-tag-name-lowercase, html-attribute-double-quotes %>
45
+ ```
46
+
47
+ ## References
48
+
49
+ \-