@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,11 +1,17 @@
1
- import { ParserRule } from "../types.js"
2
- import { BaseRuleVisitor, isVoidElement } from "./rule-utils.js"
3
- import { getTagName } from "@herb-tools/core"
1
+ import { ParserRule, BaseAutofixContext, Mutable } from "../types.js"
2
+ import { isVoidElement, findParent, BaseRuleVisitor } from "./rule-utils.js"
3
+ import { getTagName, isWhitespaceNode, Location, HTMLCloseTagNode } from "@herb-tools/core"
4
4
 
5
- import type { LintContext, LintOffense } from "../types.js"
6
- import type { HTMLOpenTagNode, HTMLElementNode, ParseResult } from "@herb-tools/core"
5
+ import type { UnboundLintOffense, LintContext, LintOffense, FullRuleConfig } from "../types.js"
6
+ import type { Node, HTMLOpenTagNode, HTMLElementNode, SerializedToken, ParseResult } from "@herb-tools/core"
7
7
 
8
- class NoSelfClosingVisitor extends BaseRuleVisitor {
8
+ interface NoSelfClosingAutofixContext extends BaseAutofixContext {
9
+ node: Mutable<HTMLOpenTagNode>
10
+ tagName: string
11
+ isVoid: boolean
12
+ }
13
+
14
+ class NoSelfClosingVisitor extends BaseRuleVisitor<NoSelfClosingAutofixContext> {
9
15
  visitHTMLElementNode(node: HTMLElementNode): void {
10
16
  if (getTagName(node) === "svg") {
11
17
  this.visit(node.open_tag)
@@ -22,20 +28,74 @@ class NoSelfClosingVisitor extends BaseRuleVisitor {
22
28
  this.addOffense(
23
29
  `Use \`${instead}\` instead of self-closing \`<${tagName} />\` for HTML compatibility.`,
24
30
  node.location,
25
- "error"
31
+ {
32
+ node,
33
+ tagName,
34
+ isVoid: isVoidElement(tagName)
35
+ }
26
36
  )
27
37
  }
28
38
  }
29
39
  }
30
40
 
31
- export class HTMLNoSelfClosingRule extends ParserRule {
41
+ export class HTMLNoSelfClosingRule extends ParserRule<NoSelfClosingAutofixContext> {
42
+ static autocorrectable = true
32
43
  name = "html-no-self-closing"
33
44
 
34
- check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
45
+ get defaultConfig(): FullRuleConfig {
46
+ return {
47
+ enabled: true,
48
+ severity: "error",
49
+ exclude: ["**/views/**/*_mailer/**/*"]
50
+ }
51
+ }
52
+
53
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<NoSelfClosingAutofixContext>[] {
35
54
  const visitor = new NoSelfClosingVisitor(this.name, context)
36
55
 
37
56
  visitor.visit(result.value)
38
57
 
39
58
  return visitor.offenses
40
59
  }
60
+
61
+ autofix(offense: LintOffense<NoSelfClosingAutofixContext>, result: ParseResult, _context?: Partial<LintContext>): ParseResult | null {
62
+ if (!offense.autofixContext) return null
63
+
64
+ const { node, tagName, isVoid } = offense.autofixContext
65
+ const { tag_closing } = node
66
+
67
+ if (!tag_closing) return null
68
+
69
+ tag_closing.value = ">"
70
+
71
+ if (node.children && Array.isArray(node.children)) {
72
+ const children = node.children as Node[]
73
+
74
+ if (children.length > 0 && isWhitespaceNode(children[children.length - 1])) {
75
+ node.children = children.slice(0, -1)
76
+ }
77
+ }
78
+
79
+ if (!isVoid) {
80
+ const parent = findParent(result.value, node as any as Node) as Mutable<HTMLElementNode> | null
81
+
82
+ if (parent && parent.type === "AST_HTML_ELEMENT_NODE") {
83
+ const tag_opening: SerializedToken = { type: "TOKEN_HTML_TAG_START_CLOSE", value: "</", location: Location.zero, range: [0, 0] }
84
+ const tag_name: SerializedToken = { type: "TOKEN_IDENTIFIER", value: tagName, location: Location.zero, range: [0, 0] }
85
+ const tag_closing: SerializedToken = { type: "TOKEN_HTML_TAG_END", value: ">", location: Location.zero, range: [0, 0] }
86
+
87
+ parent.close_tag = HTMLCloseTagNode.from({
88
+ type: "AST_HTML_CLOSE_TAG_NODE",
89
+ tag_opening,
90
+ tag_name,
91
+ tag_closing,
92
+ children: [],
93
+ errors: [],
94
+ location: Location.zero,
95
+ })
96
+ }
97
+ }
98
+
99
+ return result
100
+ }
41
101
  }
@@ -0,0 +1,221 @@
1
+ import { Token, Location, WhitespaceNode } from "@herb-tools/core"
2
+ import { ParserRule, BaseAutofixContext } from "../types.js"
3
+
4
+ import { findParent, BaseRuleVisitor } from "./rule-utils.js"
5
+ import { filterWhitespaceNodes, isWhitespaceNode, isHTMLOpenTagNode } from "@herb-tools/core"
6
+
7
+ import type { ParseResult, Node, HTMLCloseTagNode, HTMLOpenTagNode } from "@herb-tools/core"
8
+ import type { UnboundLintOffense, LintOffense, LintContext, Mutable, FullRuleConfig } from "../types.js"
9
+
10
+ const MESSAGES = {
11
+ EXTRA_SPACE_NO_SPACE: "Extra space detected where there should be no space.",
12
+ EXTRA_SPACE_SINGLE_SPACE: "Extra space detected where there should be a single space.",
13
+ EXTRA_SPACE_SINGLE_BREAK: "Extra space detected where there should be a single space or a single line break.",
14
+ NO_SPACE_SINGLE_SPACE: "No space detected where there should be a single space.",
15
+ } as const
16
+
17
+ interface HTMLNoSpaceInTagAutofixContext extends BaseAutofixContext {
18
+ node: WhitespaceNode | HTMLOpenTagNode
19
+ message: string
20
+ }
21
+
22
+ class HTMLNoSpaceInTagVisitor extends BaseRuleVisitor<HTMLNoSpaceInTagAutofixContext> {
23
+ visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
24
+ if (node.isSingleLine) {
25
+ this.checkSingleLineTag(node)
26
+ } else {
27
+ this.checkMultilineTag(node)
28
+ }
29
+ }
30
+
31
+ visitHTMLCloseTagNode(node: HTMLCloseTagNode): void {
32
+ this.reportAllWhitespace(node.children, MESSAGES.EXTRA_SPACE_NO_SPACE)
33
+ }
34
+
35
+ private checkSingleLineTag(node: HTMLOpenTagNode): void {
36
+ const { children, tag_closing } = node
37
+ const isSelfClosing = tag_closing ? this.isSelfClosing(tag_closing) : false
38
+
39
+ this.checkWhitespaceInSingleLineTag(children, isSelfClosing)
40
+ this.checkMissingSpaceBeforeSelfClosing(node, children, isSelfClosing)
41
+ }
42
+
43
+ private checkWhitespaceInSingleLineTag(children: Node[], isSelfClosing: boolean): void {
44
+ const whitespaceNodes = filterWhitespaceNodes(children)
45
+
46
+ whitespaceNodes.forEach((whitespace) => {
47
+ const content = this.getWhitespaceContent(whitespace)
48
+ if (!content) return
49
+
50
+ const isLastChild = children[children.length - 1] === whitespace
51
+ if (isLastChild) {
52
+ this.checkTrailingWhitespace(whitespace, content, isSelfClosing)
53
+ return
54
+ }
55
+
56
+ if (content.length > 1) {
57
+ this.addOffense(MESSAGES.EXTRA_SPACE_SINGLE_SPACE, whitespace.location, { node: whitespace, message: MESSAGES.EXTRA_SPACE_SINGLE_SPACE })
58
+ }
59
+ })
60
+ }
61
+
62
+ private checkTrailingWhitespace(whitespace: WhitespaceNode, content: string, isSelfClosing: boolean): void {
63
+ if (isSelfClosing && content === ' ') return
64
+
65
+ this.addOffense(MESSAGES.EXTRA_SPACE_NO_SPACE, whitespace.location, { node: whitespace, message: MESSAGES.EXTRA_SPACE_NO_SPACE })
66
+ }
67
+
68
+ private checkMissingSpaceBeforeSelfClosing(node: HTMLOpenTagNode, children: Node[], isSelfClosing: boolean): void {
69
+ if (!isSelfClosing) return
70
+
71
+ const lastChild = children[children.length - 1]
72
+ if (lastChild && isWhitespaceNode(lastChild)) return
73
+
74
+ const lastNonWhitespace = children.filter(child => !isWhitespaceNode(child)).pop()
75
+ const locationToReport = lastNonWhitespace?.location ?? node.tag_name?.location ?? node.location
76
+
77
+ this.addOffense(MESSAGES.NO_SPACE_SINGLE_SPACE, locationToReport, { node, message: MESSAGES.NO_SPACE_SINGLE_SPACE })
78
+ }
79
+
80
+ private checkMultilineTag(node: HTMLOpenTagNode): void {
81
+ const whitespaceNodes = filterWhitespaceNodes(node.children)
82
+ let previousWhitespace: WhitespaceNode | null = null
83
+
84
+ whitespaceNodes.forEach((whitespace, index) => {
85
+ const content = this.getWhitespaceContent(whitespace)
86
+ if (!content) return
87
+
88
+ if (this.hasConsecutiveNewlines(content, previousWhitespace)) {
89
+ this.addOffense(MESSAGES.EXTRA_SPACE_SINGLE_BREAK, whitespace.location, { node: whitespace, message: MESSAGES.EXTRA_SPACE_SINGLE_BREAK })
90
+ previousWhitespace = whitespace
91
+
92
+ return
93
+ }
94
+
95
+ if (this.isNonNewlineWhitespace(content)) {
96
+ this.checkIndentation(whitespace, index, whitespaceNodes.length, node)
97
+ }
98
+
99
+ previousWhitespace = whitespace
100
+ })
101
+ }
102
+
103
+ private hasConsecutiveNewlines(content: string, previousWhitespace: WhitespaceNode | null): boolean {
104
+ if (content === "\n") return previousWhitespace?.value?.value === "\n"
105
+ if (!content.includes("\n")) return false
106
+
107
+ const newlines = content.match(/\n/g)
108
+
109
+ return (newlines?.length ?? 0) > 1
110
+ }
111
+
112
+ private isNonNewlineWhitespace(content: string): boolean {
113
+ return !content.includes("\n")
114
+ }
115
+
116
+ private checkIndentation(whitespace: WhitespaceNode, index: number, totalWhitespaceNodes: number, node: HTMLOpenTagNode): void {
117
+ const isLastWhitespace = index === totalWhitespaceNodes - 1
118
+ const expectedIndent = isLastWhitespace ? node.location.start.column : node.location.start.column + 2
119
+
120
+ if (whitespace.location.end.column === expectedIndent) return
121
+
122
+ this.addOffense(MESSAGES.EXTRA_SPACE_NO_SPACE, whitespace.location, { node: whitespace, message: MESSAGES.EXTRA_SPACE_NO_SPACE })
123
+ }
124
+
125
+ private isSelfClosing(tag_closing: Token): boolean {
126
+ return tag_closing?.value?.includes('/') ?? false
127
+ }
128
+
129
+ private getWhitespaceContent(whitespace: WhitespaceNode): string | null {
130
+ return whitespace.value?.value ?? null
131
+ }
132
+
133
+ private reportAllWhitespace(nodes: Node[] | WhitespaceNode[], message: string): void {
134
+ const whitespaceNodes = Array.isArray(nodes) && nodes.length > 0 && !isWhitespaceNode(nodes[0])
135
+ ? filterWhitespaceNodes(nodes)
136
+ : nodes as WhitespaceNode[]
137
+
138
+ whitespaceNodes.forEach(whitespace => {
139
+ this.addOffense(message, whitespace.location, { node: whitespace, message })
140
+ })
141
+ }
142
+ }
143
+
144
+ export class HTMLNoSpaceInTagRule extends ParserRule<HTMLNoSpaceInTagAutofixContext> {
145
+ // TODO: enable and fix autofix
146
+ static autocorrectable = false
147
+ name = "html-no-space-in-tag"
148
+
149
+ get defaultConfig(): FullRuleConfig {
150
+ return {
151
+ enabled: false,
152
+ severity: "error"
153
+ }
154
+ }
155
+
156
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<HTMLNoSpaceInTagAutofixContext>[] {
157
+ const visitor = new HTMLNoSpaceInTagVisitor(this.name, context)
158
+
159
+ visitor.visit(result.value)
160
+
161
+ return visitor.offenses
162
+ }
163
+
164
+ autofix(offense: LintOffense<HTMLNoSpaceInTagAutofixContext>, result: ParseResult, _context?: Partial<LintContext>): ParseResult | null {
165
+ if (!offense.autofixContext) return null
166
+
167
+ const { node, message } = offense.autofixContext
168
+ if (!node) return null
169
+
170
+ if (isHTMLOpenTagNode(node)) {
171
+ const token = Token.from({ type: "TOKEN_WHITESPACE", value: " ", range: [0, 0], location: Location.zero })
172
+ const whitespace = new WhitespaceNode({ type: "AST_WHITESPACE_NODE", value: token, location: Location.zero, errors: [] })
173
+
174
+ node.children.push(whitespace)
175
+
176
+ return result
177
+ }
178
+
179
+ if (!isWhitespaceNode(node)) return null
180
+
181
+ const whitespaceNode = node as Mutable<WhitespaceNode>
182
+ if (!whitespaceNode.value) return null
183
+
184
+ switch (message) {
185
+ case MESSAGES.EXTRA_SPACE_NO_SPACE: {
186
+ let selfClosing = false
187
+ let beginningOfLine = false
188
+
189
+ const parent = findParent(result.value, node)
190
+
191
+ if (parent && isHTMLOpenTagNode(parent)) {
192
+ selfClosing = parent.tag_closing?.value === "/>"
193
+ beginningOfLine = node.location.start.column === 0
194
+ }
195
+
196
+ whitespaceNode.value.value = selfClosing && !beginningOfLine ? " " : ""
197
+
198
+ return result
199
+ }
200
+
201
+ case MESSAGES.EXTRA_SPACE_SINGLE_BREAK: {
202
+ if (whitespaceNode.value.value.includes("\n")) {
203
+ whitespaceNode.value.value = ""
204
+ } else {
205
+ whitespaceNode.value.value = " "
206
+ }
207
+
208
+ return result
209
+ }
210
+
211
+ case MESSAGES.EXTRA_SPACE_SINGLE_SPACE:
212
+ case MESSAGES.NO_SPACE_SINGLE_SPACE: {
213
+ whitespaceNode.value.value = " "
214
+
215
+ return result
216
+ }
217
+
218
+ default: return null
219
+ }
220
+ }
221
+ }
@@ -1,7 +1,7 @@
1
1
  import { ParserRule } from "../types.js"
2
2
  import { BaseRuleVisitor, getTagName, hasAttribute } 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 { HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
6
6
 
7
7
  class NoTitleAttributeVisitor extends BaseRuleVisitor {
@@ -23,7 +23,6 @@ class NoTitleAttributeVisitor extends BaseRuleVisitor {
23
23
  this.addOffense(
24
24
  "The `title` attribute should never be used as it is inaccessible for several groups of users. Use `aria-label` or `aria-describedby` instead. Exceptions are provided for `<iframe>` and `<link>` elements.",
25
25
  node.tag_name!.location,
26
- "error"
27
26
  )
28
27
  }
29
28
  }
@@ -32,7 +31,14 @@ class NoTitleAttributeVisitor extends BaseRuleVisitor {
32
31
  export class HTMLNoTitleAttributeRule extends ParserRule {
33
32
  name = "html-no-title-attribute"
34
33
 
35
- check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
34
+ get defaultConfig(): FullRuleConfig {
35
+ return {
36
+ enabled: false,
37
+ severity: "error"
38
+ }
39
+ }
40
+
41
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
36
42
  const visitor = new NoTitleAttributeVisitor(this.name, context)
37
43
 
38
44
  visitor.visit(result.value)
@@ -6,9 +6,11 @@ import {
6
6
  DynamicAttributeStaticValueParams,
7
7
  DynamicAttributeDynamicValueParams
8
8
  } from "./rule-utils.js"
9
+
9
10
  import { getStaticContentFromNodes } from "@herb-tools/core"
10
11
  import { IdentityPrinter } from "@herb-tools/printer"
11
- import type { LintContext, LintOffense } from "../types.js"
12
+
13
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
12
14
  import type { ParseResult, HTMLAttributeNode } from "@herb-tools/core"
13
15
 
14
16
  class HTMLNoUnderscoresInAttributeNamesVisitor extends AttributeVisitorMixin {
@@ -38,8 +40,7 @@ class HTMLNoUnderscoresInAttributeNamesVisitor extends AttributeVisitorMixin {
38
40
  if (attributeName.includes("_")) {
39
41
  this.addOffense(
40
42
  `Attribute \`${IdentityPrinter.print(attributeNode.name)}\` should not contain underscores. Use hyphens (-) instead.`,
41
- attributeNode.value!.location,
42
- "warning"
43
+ attributeNode.name?.location ?? attributeNode.location,
43
44
  )
44
45
  }
45
46
  }
@@ -48,7 +49,14 @@ class HTMLNoUnderscoresInAttributeNamesVisitor extends AttributeVisitorMixin {
48
49
  export class HTMLNoUnderscoresInAttributeNamesRule extends ParserRule {
49
50
  name = "html-no-underscores-in-attribute-names"
50
51
 
51
- check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
52
+ get defaultConfig(): FullRuleConfig {
53
+ return {
54
+ enabled: true,
55
+ severity: "warning"
56
+ }
57
+ }
58
+
59
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
52
60
  const visitor = new HTMLNoUnderscoresInAttributeNamesVisitor(this.name, context)
53
61
 
54
62
  visitor.visit(result.value)
@@ -1,9 +1,15 @@
1
- import { ParserRule } from "../types.js"
1
+ import { ParserRule, BaseAutofixContext, Mutable } from "../types.js"
2
2
  import { BaseRuleVisitor } from "./rule-utils.js"
3
+ import { isNode, getTagName, HTMLOpenTagNode } from "@herb-tools/core"
3
4
 
4
- import { isNode, getTagName, HTMLElementNode, HTMLOpenTagNode, HTMLCloseTagNode, ParseResult, XMLDeclarationNode, Node } from "@herb-tools/core"
5
+ import type { UnboundLintOffense, LintOffense, LintContext, FullRuleConfig } from "../types.js"
6
+ import type { HTMLElementNode, HTMLCloseTagNode, ParseResult, XMLDeclarationNode, Node } from "@herb-tools/core"
5
7
 
6
- import type { LintOffense, LintContext } from "../types.js"
8
+ interface TagNameAutofixContext extends BaseAutofixContext {
9
+ node: Mutable<HTMLOpenTagNode | HTMLCloseTagNode>
10
+ tagName: string
11
+ correctedTagName: string
12
+ }
7
13
 
8
14
  class XMLDeclarationChecker extends BaseRuleVisitor {
9
15
  hasXMLDeclaration: boolean = false
@@ -18,7 +24,7 @@ class XMLDeclarationChecker extends BaseRuleVisitor {
18
24
  }
19
25
  }
20
26
 
21
- class TagNameLowercaseVisitor extends BaseRuleVisitor {
27
+ class TagNameLowercaseVisitor extends BaseRuleVisitor<TagNameAutofixContext> {
22
28
  visitHTMLElementNode(node: HTMLElementNode): void {
23
29
  if (getTagName(node).toLowerCase() === "svg") {
24
30
  this.checkTagName(node.open_tag)
@@ -52,28 +58,53 @@ class TagNameLowercaseVisitor extends BaseRuleVisitor {
52
58
  this.addOffense(
53
59
  `${type} tag name \`${open}${tagName}>\` should be lowercase. Use \`${open}${lowercaseTagName}>\` instead.`,
54
60
  node.tag_name!.location,
55
- "error"
61
+ {
62
+ node,
63
+ tagName,
64
+ correctedTagName: lowercaseTagName
65
+ }
56
66
  )
57
67
  }
58
68
  }
59
69
  }
60
70
 
61
- export class HTMLTagNameLowercaseRule extends ParserRule {
71
+ export class HTMLTagNameLowercaseRule extends ParserRule<TagNameAutofixContext> {
72
+ static autocorrectable = true
62
73
  name = "html-tag-name-lowercase"
63
74
 
64
- isEnabled(result: ParseResult, context?: Partial<LintContext>): boolean {
65
- if (context?.fileName?.endsWith(".xml") || context?.fileName?.endsWith(".xml.erb")) {
66
- return false
75
+ get defaultConfig(): FullRuleConfig {
76
+ return {
77
+ enabled: true,
78
+ severity: "error",
79
+ exclude: ["**/*.xml", "**/*.xml.erb"]
67
80
  }
81
+ }
68
82
 
83
+ isEnabled(result: ParseResult, _context?: Partial<LintContext>): boolean {
69
84
  const checker = new XMLDeclarationChecker(this.name)
85
+
70
86
  checker.visit(result.value)
87
+
71
88
  return !checker.hasXMLDeclaration
72
89
  }
73
90
 
74
- check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
91
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<TagNameAutofixContext>[] {
75
92
  const visitor = new TagNameLowercaseVisitor(this.name, context)
93
+
76
94
  visitor.visit(result.value)
95
+
77
96
  return visitor.offenses
78
97
  }
98
+
99
+ autofix(offense: LintOffense<TagNameAutofixContext>, result: ParseResult, _context?: Partial<LintContext>): ParseResult | null {
100
+ if (!offense.autofixContext) return null
101
+
102
+ const { node: { tag_name }, correctedTagName } = offense.autofixContext
103
+
104
+ if (!tag_name) return null
105
+
106
+ tag_name.value = correctedTagName
107
+
108
+ return result
109
+ }
79
110
  }
@@ -1,9 +1,25 @@
1
1
  export * from "./rule-utils.js"
2
+ export * from "./herb-disable-comment-base.js"
3
+
4
+ export * from "./erb-comment-syntax.js"
5
+ export * from "./erb-no-case-node-children.js"
2
6
  export * from "./erb-no-empty-tags.js"
7
+ export * from "./erb-no-extra-newline.js"
8
+ export * from "./erb-no-extra-whitespace-inside-tags.js"
3
9
  export * from "./erb-no-output-control-flow.js"
4
10
  export * from "./erb-no-silent-tag-in-attribute-name.js"
5
11
  export * from "./erb-prefer-image-tag-helper.js"
6
- export * from "./erb-requires-trailing-newline.js"
12
+ export * from "./erb-require-trailing-newline.js"
13
+ export * from "./erb-require-whitespace-inside-tags.js"
14
+ export * from "./erb-right-trim.js"
15
+
16
+ export * from "./herb-disable-comment-valid-rule-name.js"
17
+ export * from "./herb-disable-comment-no-redundant-all.js"
18
+ export * from "./herb-disable-comment-no-duplicate-rules.js"
19
+ export * from "./herb-disable-comment-missing-rules.js"
20
+ export * from "./herb-disable-comment-malformed.js"
21
+ export * from "./herb-disable-comment-unnecessary.js"
22
+
7
23
  export * from "./html-anchor-require-href.js"
8
24
  export * from "./html-aria-label-is-well-formatted.js"
9
25
  export * from "./html-aria-level-must-be-valid.js"
@@ -13,20 +29,26 @@ export * from "./html-attribute-double-quotes.js"
13
29
  export * from "./html-attribute-equals-spacing.js"
14
30
  export * from "./html-attribute-values-require-quotes.js"
15
31
  export * from "./html-avoid-both-disabled-and-aria-disabled.js"
32
+ export * from "./html-body-only-elements.js"
16
33
  export * from "./html-boolean-attributes-no-value.js"
34
+ export * from "./html-head-only-elements.js"
17
35
  export * from "./html-iframe-has-title.js"
18
36
  export * from "./html-img-require-alt.js"
37
+ export * from "./html-input-require-autocomplete.js"
19
38
  export * from "./html-navigation-has-label.js"
20
39
  export * from "./html-no-aria-hidden-on-focusable.js"
21
40
  export * from "./html-no-block-inside-inline.js"
22
41
  export * from "./html-no-duplicate-attributes.js"
23
42
  export * from "./html-no-duplicate-ids.js"
43
+ export * from "./html-no-duplicate-meta-names.js"
24
44
  export * from "./html-no-empty-attributes.js"
25
45
  export * from "./html-no-empty-headings.js"
26
46
  export * from "./html-no-nested-links.js"
27
47
  export * from "./html-no-positive-tab-index.js"
28
48
  export * from "./html-no-self-closing.js"
49
+ export * from "./html-no-space-in-tag.js"
29
50
  export * from "./html-no-title-attribute.js"
51
+ export * from "./html-no-underscores-in-attribute-names.js"
30
52
  export * from "./html-tag-name-lowercase.js"
53
+
31
54
  export * from "./svg-tag-name-capitalization.js"
32
- export * from "./html-no-underscores-in-attribute-names.js"
@@ -1,11 +1,18 @@
1
1
  import { ParserRule } from "../types.js"
2
2
 
3
- import type { LintOffense } from "../types.js"
3
+ import type { LintOffense, FullRuleConfig } from "../types.js"
4
4
  import type { ParseResult, HerbError } from "@herb-tools/core"
5
5
 
6
6
  export class ParserNoErrorsRule extends ParserRule {
7
7
  name = "parser-no-errors"
8
8
 
9
+ get defaultConfig(): FullRuleConfig {
10
+ return {
11
+ enabled: true,
12
+ severity: "error"
13
+ }
14
+ }
15
+
9
16
  check(result: ParseResult): LintOffense[] {
10
17
  return result.recursiveErrors().map(error =>
11
18
  this.herbErrorToLintOffense(error)