@herb-tools/linter 0.8.9 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (573) hide show
  1. package/README.md +5 -5
  2. package/dist/{src/cli → cli}/argument-parser.js +15 -2
  3. package/dist/cli/argument-parser.js.map +1 -0
  4. package/dist/{src/cli → cli}/file-processor.js +155 -9
  5. package/dist/cli/file-processor.js.map +1 -0
  6. package/dist/cli/file-url.js +6 -0
  7. package/dist/cli/file-url.js.map +1 -0
  8. package/dist/cli/formatters/base-formatter.js.map +1 -0
  9. package/dist/{src/cli → cli}/formatters/detailed-formatter.js +16 -19
  10. package/dist/cli/formatters/detailed-formatter.js.map +1 -0
  11. package/dist/cli/formatters/github-actions-formatter.js.map +1 -0
  12. package/dist/cli/formatters/index.js.map +1 -0
  13. package/dist/cli/formatters/json-formatter.js.map +1 -0
  14. package/dist/cli/formatters/simple-formatter.js +54 -0
  15. package/dist/cli/formatters/simple-formatter.js.map +1 -0
  16. package/dist/cli/index.js.map +1 -0
  17. package/dist/cli/lint-worker.js +143 -0
  18. package/dist/cli/lint-worker.js.map +1 -0
  19. package/dist/cli/output-manager.js.map +1 -0
  20. package/dist/{src/cli → cli}/summary-reporter.js +13 -16
  21. package/dist/cli/summary-reporter.js.map +1 -0
  22. package/dist/{src/cli.js → cli.js} +5 -3
  23. package/dist/cli.js.map +1 -0
  24. package/dist/{src/custom-rule-loader.js → custom-rule-loader.js} +20 -4
  25. package/dist/custom-rule-loader.js.map +1 -0
  26. package/dist/herb-disable-comment-utils.js.map +1 -0
  27. package/dist/herb-lint.js +60648 -17513
  28. package/dist/herb-lint.js.map +1 -1
  29. package/dist/index.cjs +2621 -934
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.js +2554 -873
  32. package/dist/index.js.map +1 -1
  33. package/dist/lint-worker.js +71462 -0
  34. package/dist/lint-worker.js.map +1 -0
  35. package/dist/linter-ignore.js.map +1 -0
  36. package/dist/{src/linter.js → linter.js} +89 -74
  37. package/dist/linter.js.map +1 -0
  38. package/dist/loader.cjs +31206 -7834
  39. package/dist/loader.cjs.map +1 -1
  40. package/dist/loader.js +31168 -7802
  41. package/dist/loader.js.map +1 -1
  42. package/dist/parse-cache.js +30 -0
  43. package/dist/parse-cache.js.map +1 -0
  44. package/dist/rules/actionview-no-silent-helper.js +45 -0
  45. package/dist/rules/actionview-no-silent-helper.js.map +1 -0
  46. package/dist/{src/rules → rules}/erb-comment-syntax.js +2 -2
  47. package/dist/rules/erb-comment-syntax.js.map +1 -0
  48. package/dist/{src/rules → rules}/erb-no-case-node-children.js +2 -2
  49. package/dist/rules/erb-no-case-node-children.js.map +1 -0
  50. package/dist/rules/erb-no-conditional-html-element.js +38 -0
  51. package/dist/rules/erb-no-conditional-html-element.js.map +1 -0
  52. package/dist/rules/erb-no-conditional-open-tag.js +24 -0
  53. package/dist/rules/erb-no-conditional-open-tag.js.map +1 -0
  54. package/dist/rules/erb-no-duplicate-branch-elements.js +245 -0
  55. package/dist/rules/erb-no-duplicate-branch-elements.js.map +1 -0
  56. package/dist/{src/rules → rules}/erb-no-empty-tags.js +2 -2
  57. package/dist/rules/erb-no-empty-tags.js.map +1 -0
  58. package/dist/{src/rules → rules}/erb-no-extra-newline.js +4 -21
  59. package/dist/rules/erb-no-extra-newline.js.map +1 -0
  60. package/dist/{src/rules → rules}/erb-no-extra-whitespace-inside-tags.js +39 -13
  61. package/dist/rules/erb-no-extra-whitespace-inside-tags.js.map +1 -0
  62. package/dist/rules/erb-no-inline-case-conditions.js +40 -0
  63. package/dist/rules/erb-no-inline-case-conditions.js.map +1 -0
  64. package/dist/rules/erb-no-instance-variables-in-partials.js +67 -0
  65. package/dist/rules/erb-no-instance-variables-in-partials.js.map +1 -0
  66. package/dist/rules/erb-no-interpolated-class-names.js +47 -0
  67. package/dist/rules/erb-no-interpolated-class-names.js.map +1 -0
  68. package/dist/rules/erb-no-javascript-tag-helper.js +34 -0
  69. package/dist/rules/erb-no-javascript-tag-helper.js.map +1 -0
  70. package/dist/{src/rules → rules}/erb-no-output-control-flow.js +9 -12
  71. package/dist/rules/erb-no-output-control-flow.js.map +1 -0
  72. package/dist/rules/erb-no-output-in-attribute-name.js +30 -0
  73. package/dist/rules/erb-no-output-in-attribute-name.js.map +1 -0
  74. package/dist/rules/erb-no-output-in-attribute-position.js +30 -0
  75. package/dist/rules/erb-no-output-in-attribute-position.js.map +1 -0
  76. package/dist/rules/erb-no-raw-output-in-attribute-value.js +35 -0
  77. package/dist/rules/erb-no-raw-output-in-attribute-value.js.map +1 -0
  78. package/dist/{src/rules → rules}/erb-no-silent-tag-in-attribute-name.js +2 -2
  79. package/dist/rules/erb-no-silent-tag-in-attribute-name.js.map +1 -0
  80. package/dist/rules/erb-no-statement-in-script.js +58 -0
  81. package/dist/rules/erb-no-statement-in-script.js.map +1 -0
  82. package/dist/rules/erb-no-then-in-control-flow.js +45 -0
  83. package/dist/rules/erb-no-then-in-control-flow.js.map +1 -0
  84. package/dist/rules/erb-no-trailing-whitespace.js +138 -0
  85. package/dist/rules/erb-no-trailing-whitespace.js.map +1 -0
  86. package/dist/rules/erb-no-unsafe-js-attribute.js +36 -0
  87. package/dist/rules/erb-no-unsafe-js-attribute.js.map +1 -0
  88. package/dist/rules/erb-no-unsafe-raw.js +63 -0
  89. package/dist/rules/erb-no-unsafe-raw.js.map +1 -0
  90. package/dist/rules/erb-no-unsafe-script-interpolation.js +54 -0
  91. package/dist/rules/erb-no-unsafe-script-interpolation.js.map +1 -0
  92. package/dist/{src/rules → rules}/erb-prefer-image-tag-helper.js +5 -4
  93. package/dist/rules/erb-prefer-image-tag-helper.js.map +1 -0
  94. package/dist/{src/rules → rules}/erb-require-trailing-newline.js +2 -2
  95. package/dist/rules/erb-require-trailing-newline.js.map +1 -0
  96. package/dist/{src/rules → rules}/erb-require-whitespace-inside-tags.js +39 -15
  97. package/dist/rules/erb-require-whitespace-inside-tags.js.map +1 -0
  98. package/dist/{src/rules → rules}/erb-right-trim.js +2 -2
  99. package/dist/rules/erb-right-trim.js.map +1 -0
  100. package/dist/{src/rules → rules}/erb-strict-locals-comment-syntax.js +5 -5
  101. package/dist/rules/erb-strict-locals-comment-syntax.js.map +1 -0
  102. package/dist/{src/rules → rules}/erb-strict-locals-required.js +2 -2
  103. package/dist/rules/erb-strict-locals-required.js.map +1 -0
  104. package/dist/rules/file-utils.js.map +1 -0
  105. package/dist/rules/herb-disable-comment-base.js.map +1 -0
  106. package/dist/{src/rules → rules}/herb-disable-comment-malformed.js +2 -2
  107. package/dist/rules/herb-disable-comment-malformed.js.map +1 -0
  108. package/dist/{src/rules → rules}/herb-disable-comment-missing-rules.js +2 -2
  109. package/dist/rules/herb-disable-comment-missing-rules.js.map +1 -0
  110. package/dist/{src/rules → rules}/herb-disable-comment-no-duplicate-rules.js +2 -2
  111. package/dist/rules/herb-disable-comment-no-duplicate-rules.js.map +1 -0
  112. package/dist/{src/rules → rules}/herb-disable-comment-no-redundant-all.js +2 -2
  113. package/dist/rules/herb-disable-comment-no-redundant-all.js.map +1 -0
  114. package/dist/{src/rules → rules}/herb-disable-comment-unnecessary.js +2 -2
  115. package/dist/rules/herb-disable-comment-unnecessary.js.map +1 -0
  116. package/dist/{src/rules → rules}/herb-disable-comment-valid-rule-name.js +2 -2
  117. package/dist/rules/herb-disable-comment-valid-rule-name.js.map +1 -0
  118. package/dist/rules/html-allowed-script-type.js +57 -0
  119. package/dist/rules/html-allowed-script-type.js.map +1 -0
  120. package/dist/rules/html-anchor-require-href.js +68 -0
  121. package/dist/rules/html-anchor-require-href.js.map +1 -0
  122. package/dist/{src/rules → rules}/html-aria-attribute-must-be-valid.js +3 -3
  123. package/dist/rules/html-aria-attribute-must-be-valid.js.map +1 -0
  124. package/dist/{src/rules → rules}/html-aria-label-is-well-formatted.js +3 -3
  125. package/dist/rules/html-aria-label-is-well-formatted.js.map +1 -0
  126. package/dist/{src/rules → rules}/html-aria-level-must-be-valid.js +3 -3
  127. package/dist/rules/html-aria-level-must-be-valid.js.map +1 -0
  128. package/dist/{src/rules → rules}/html-aria-role-heading-requires-level.js +5 -4
  129. package/dist/rules/html-aria-role-heading-requires-level.js.map +1 -0
  130. package/dist/{src/rules → rules}/html-aria-role-must-be-valid.js +3 -3
  131. package/dist/rules/html-aria-role-must-be-valid.js.map +1 -0
  132. package/dist/{src/rules → rules}/html-attribute-double-quotes.js +4 -4
  133. package/dist/rules/html-attribute-double-quotes.js.map +1 -0
  134. package/dist/{src/rules → rules}/html-attribute-equals-spacing.js +2 -2
  135. package/dist/rules/html-attribute-equals-spacing.js.map +1 -0
  136. package/dist/{src/rules → rules}/html-attribute-values-require-quotes.js +2 -2
  137. package/dist/rules/html-attribute-values-require-quotes.js.map +1 -0
  138. package/dist/{src/rules → rules}/html-avoid-both-disabled-and-aria-disabled.js +9 -9
  139. package/dist/rules/html-avoid-both-disabled-and-aria-disabled.js.map +1 -0
  140. package/dist/{src/rules → rules}/html-body-only-elements.js +5 -4
  141. package/dist/rules/html-body-only-elements.js.map +1 -0
  142. package/dist/{src/rules → rules}/html-boolean-attributes-no-value.js +4 -3
  143. package/dist/rules/html-boolean-attributes-no-value.js.map +1 -0
  144. package/dist/rules/html-details-has-summary.js +52 -0
  145. package/dist/rules/html-details-has-summary.js.map +1 -0
  146. package/dist/{src/rules → rules}/html-head-only-elements.js +6 -5
  147. package/dist/rules/html-head-only-elements.js.map +1 -0
  148. package/dist/{src/rules → rules}/html-iframe-has-title.js +8 -11
  149. package/dist/rules/html-iframe-has-title.js.map +1 -0
  150. package/dist/{src/rules → rules}/html-img-require-alt.js +11 -5
  151. package/dist/rules/html-img-require-alt.js.map +1 -0
  152. package/dist/{src/rules → rules}/html-input-require-autocomplete.js +7 -10
  153. package/dist/rules/html-input-require-autocomplete.js.map +1 -0
  154. package/dist/{src/rules → rules}/html-navigation-has-label.js +6 -5
  155. package/dist/rules/html-navigation-has-label.js.map +1 -0
  156. package/dist/rules/html-no-abstract-roles.js +29 -0
  157. package/dist/rules/html-no-abstract-roles.js.map +1 -0
  158. package/dist/rules/html-no-aria-hidden-on-body.js +42 -0
  159. package/dist/rules/html-no-aria-hidden-on-body.js.map +1 -0
  160. package/dist/{src/rules → rules}/html-no-aria-hidden-on-focusable.js +6 -5
  161. package/dist/rules/html-no-aria-hidden-on-focusable.js.map +1 -0
  162. package/dist/{src/rules → rules}/html-no-block-inside-inline.js +6 -9
  163. package/dist/rules/html-no-block-inside-inline.js.map +1 -0
  164. package/dist/{src/rules → rules}/html-no-duplicate-attributes.js +4 -3
  165. package/dist/rules/html-no-duplicate-attributes.js.map +1 -0
  166. package/dist/{src/rules → rules}/html-no-duplicate-ids.js +14 -11
  167. package/dist/rules/html-no-duplicate-ids.js.map +1 -0
  168. package/dist/{src/rules → rules}/html-no-duplicate-meta-names.js +22 -20
  169. package/dist/rules/html-no-duplicate-meta-names.js.map +1 -0
  170. package/dist/{src/rules → rules}/html-no-empty-attributes.js +2 -2
  171. package/dist/rules/html-no-empty-attributes.js.map +1 -0
  172. package/dist/rules/html-no-empty-headings.js +98 -0
  173. package/dist/rules/html-no-empty-headings.js.map +1 -0
  174. package/dist/{src/rules → rules}/html-no-nested-links.js +23 -15
  175. package/dist/rules/html-no-nested-links.js.map +1 -0
  176. package/dist/{src/rules → rules}/html-no-positive-tab-index.js +3 -3
  177. package/dist/rules/html-no-positive-tab-index.js.map +1 -0
  178. package/dist/{src/rules → rules}/html-no-self-closing.js +4 -4
  179. package/dist/rules/html-no-self-closing.js.map +1 -0
  180. package/dist/{src/rules → rules}/html-no-space-in-tag.js +4 -6
  181. package/dist/rules/html-no-space-in-tag.js.map +1 -0
  182. package/dist/{src/rules → rules}/html-no-title-attribute.js +6 -5
  183. package/dist/rules/html-no-title-attribute.js.map +1 -0
  184. package/dist/{src/rules → rules}/html-no-underscores-in-attribute-names.js +2 -2
  185. package/dist/rules/html-no-underscores-in-attribute-names.js.map +1 -0
  186. package/dist/rules/html-require-closing-tags.js +29 -0
  187. package/dist/rules/html-require-closing-tags.js.map +1 -0
  188. package/dist/{src/rules → rules}/html-tag-name-lowercase.js +13 -9
  189. package/dist/rules/html-tag-name-lowercase.js.map +1 -0
  190. package/dist/{src/rules → rules}/index.js +19 -0
  191. package/dist/rules/index.js.map +1 -0
  192. package/dist/{src/rules → rules}/parser-no-errors.js +3 -3
  193. package/dist/rules/parser-no-errors.js.map +1 -0
  194. package/dist/{src/rules → rules}/rule-utils.js +141 -219
  195. package/dist/rules/rule-utils.js.map +1 -0
  196. package/dist/rules/string-utils.js.map +1 -0
  197. package/dist/{src/rules → rules}/svg-tag-name-capitalization.js +7 -6
  198. package/dist/rules/svg-tag-name-capitalization.js.map +1 -0
  199. package/dist/rules/turbo-permanent-require-id.js +34 -0
  200. package/dist/rules/turbo-permanent-require-id.js.map +1 -0
  201. package/dist/{src/rules.js → rules.js} +56 -10
  202. package/dist/rules.js.map +1 -0
  203. package/dist/types/cli/argument-parser.d.ts +1 -0
  204. package/dist/types/cli/file-processor.d.ts +13 -0
  205. package/dist/types/cli/file-url.d.ts +1 -0
  206. package/dist/types/cli/index.d.ts +1 -0
  207. package/dist/types/cli/lint-worker.d.ts +34 -0
  208. package/dist/types/custom-rule-loader.d.ts +4 -0
  209. package/dist/types/index.d.ts +1 -0
  210. package/dist/types/linter.d.ts +13 -6
  211. package/dist/types/parse-cache.d.ts +9 -0
  212. package/dist/types/{src/rules/html-aria-level-must-be-valid.d.ts → rules/actionview-no-silent-helper.d.ts} +4 -3
  213. package/dist/types/rules/erb-comment-syntax.d.ts +1 -1
  214. package/dist/types/rules/erb-no-case-node-children.d.ts +1 -1
  215. package/dist/types/{src/rules/herb-disable-comment-malformed.d.ts → rules/erb-no-conditional-html-element.d.ts} +3 -3
  216. package/dist/types/{src/rules/erb-prefer-image-tag-helper.d.ts → rules/erb-no-conditional-open-tag.d.ts} +3 -3
  217. package/dist/types/rules/erb-no-duplicate-branch-elements.d.ts +17 -0
  218. package/dist/types/rules/erb-no-empty-tags.d.ts +1 -1
  219. package/dist/types/rules/erb-no-extra-newline.d.ts +1 -1
  220. package/dist/types/rules/erb-no-extra-whitespace-inside-tags.d.ts +1 -1
  221. package/dist/types/{src/rules/html-no-duplicate-attributes.d.ts → rules/erb-no-inline-case-conditions.d.ts} +4 -3
  222. package/dist/types/rules/erb-no-instance-variables-in-partials.d.ts +10 -0
  223. package/dist/types/{src/rules/html-no-aria-hidden-on-focusable.d.ts → rules/erb-no-interpolated-class-names.d.ts} +2 -2
  224. package/dist/types/{src/rules/html-aria-attribute-must-be-valid.d.ts → rules/erb-no-javascript-tag-helper.d.ts} +2 -2
  225. package/dist/types/rules/erb-no-output-control-flow.d.ts +1 -1
  226. package/dist/types/{src/rules/erb-no-silent-tag-in-attribute-name.d.ts → rules/erb-no-output-in-attribute-name.d.ts} +2 -2
  227. package/dist/types/{src/rules/herb-disable-comment-missing-rules.d.ts → rules/erb-no-output-in-attribute-position.d.ts} +2 -2
  228. package/dist/types/{src/rules/erb-no-empty-tags.d.ts → rules/erb-no-raw-output-in-attribute-value.d.ts} +2 -2
  229. package/dist/types/rules/erb-no-silent-tag-in-attribute-name.d.ts +1 -1
  230. package/dist/types/{src/rules/html-navigation-has-label.d.ts → rules/erb-no-statement-in-script.d.ts} +2 -2
  231. package/dist/types/rules/erb-no-then-in-control-flow.d.ts +9 -0
  232. package/dist/types/rules/erb-no-trailing-whitespace.d.ts +19 -0
  233. package/dist/types/{src/rules/html-no-positive-tab-index.d.ts → rules/erb-no-unsafe-js-attribute.d.ts} +2 -2
  234. package/dist/types/{src/rules/erb-no-case-node-children.d.ts → rules/erb-no-unsafe-raw.d.ts} +2 -2
  235. package/dist/types/rules/erb-no-unsafe-script-interpolation.d.ts +8 -0
  236. package/dist/types/rules/erb-prefer-image-tag-helper.d.ts +1 -1
  237. package/dist/types/rules/erb-require-trailing-newline.d.ts +1 -1
  238. package/dist/types/rules/erb-require-whitespace-inside-tags.d.ts +1 -1
  239. package/dist/types/rules/erb-right-trim.d.ts +1 -1
  240. package/dist/types/rules/erb-strict-locals-comment-syntax.d.ts +1 -1
  241. package/dist/types/rules/erb-strict-locals-required.d.ts +1 -1
  242. package/dist/types/rules/herb-disable-comment-malformed.d.ts +1 -1
  243. package/dist/types/rules/herb-disable-comment-missing-rules.d.ts +1 -1
  244. package/dist/types/rules/herb-disable-comment-no-duplicate-rules.d.ts +1 -1
  245. package/dist/types/rules/herb-disable-comment-no-redundant-all.d.ts +1 -1
  246. package/dist/types/rules/herb-disable-comment-unnecessary.d.ts +1 -1
  247. package/dist/types/rules/herb-disable-comment-valid-rule-name.d.ts +1 -1
  248. package/dist/types/{src/rules/html-anchor-require-href.d.ts → rules/html-allowed-script-type.d.ts} +2 -2
  249. package/dist/types/rules/html-anchor-require-href.d.ts +3 -2
  250. package/dist/types/rules/html-aria-attribute-must-be-valid.d.ts +1 -1
  251. package/dist/types/rules/html-aria-label-is-well-formatted.d.ts +1 -1
  252. package/dist/types/rules/html-aria-level-must-be-valid.d.ts +1 -1
  253. package/dist/types/rules/html-aria-role-heading-requires-level.d.ts +1 -1
  254. package/dist/types/rules/html-aria-role-must-be-valid.d.ts +1 -1
  255. package/dist/types/rules/html-attribute-double-quotes.d.ts +1 -1
  256. package/dist/types/rules/html-attribute-equals-spacing.d.ts +1 -1
  257. package/dist/types/rules/html-attribute-values-require-quotes.d.ts +1 -1
  258. package/dist/types/rules/html-avoid-both-disabled-and-aria-disabled.d.ts +1 -1
  259. package/dist/types/rules/html-body-only-elements.d.ts +1 -1
  260. package/dist/types/rules/html-boolean-attributes-no-value.d.ts +1 -1
  261. package/dist/types/{src/rules/html-no-empty-attributes.d.ts → rules/html-details-has-summary.d.ts} +4 -3
  262. package/dist/types/rules/html-head-only-elements.d.ts +1 -1
  263. package/dist/types/rules/html-iframe-has-title.d.ts +1 -1
  264. package/dist/types/rules/html-img-require-alt.d.ts +1 -1
  265. package/dist/types/rules/html-input-require-autocomplete.d.ts +1 -1
  266. package/dist/types/rules/html-navigation-has-label.d.ts +1 -1
  267. package/dist/types/{src/rules/html-no-empty-headings.d.ts → rules/html-no-abstract-roles.d.ts} +2 -2
  268. package/dist/types/{src/rules/erb-no-output-control-flow.d.ts → rules/html-no-aria-hidden-on-body.d.ts} +3 -3
  269. package/dist/types/rules/html-no-aria-hidden-on-focusable.d.ts +1 -1
  270. package/dist/types/rules/html-no-block-inside-inline.d.ts +1 -1
  271. package/dist/types/rules/html-no-duplicate-attributes.d.ts +1 -1
  272. package/dist/types/rules/html-no-duplicate-ids.d.ts +1 -1
  273. package/dist/types/rules/html-no-duplicate-meta-names.d.ts +1 -1
  274. package/dist/types/rules/html-no-empty-attributes.d.ts +1 -1
  275. package/dist/types/rules/html-no-empty-headings.d.ts +1 -1
  276. package/dist/types/rules/html-no-nested-links.d.ts +1 -1
  277. package/dist/types/rules/html-no-positive-tab-index.d.ts +1 -1
  278. package/dist/types/rules/html-no-self-closing.d.ts +1 -1
  279. package/dist/types/rules/html-no-space-in-tag.d.ts +1 -1
  280. package/dist/types/rules/html-no-title-attribute.d.ts +1 -1
  281. package/dist/types/rules/html-no-underscores-in-attribute-names.d.ts +1 -1
  282. package/dist/types/{src/rules/html-body-only-elements.d.ts → rules/html-require-closing-tags.d.ts} +4 -3
  283. package/dist/types/rules/html-tag-name-lowercase.d.ts +1 -1
  284. package/dist/types/rules/index.d.ts +19 -0
  285. package/dist/types/rules/parser-no-errors.d.ts +1 -1
  286. package/dist/types/rules/rule-utils.d.ts +35 -88
  287. package/dist/types/rules/svg-tag-name-capitalization.d.ts +1 -1
  288. package/dist/types/{src/rules/html-aria-role-must-be-valid.d.ts → rules/turbo-permanent-require-id.d.ts} +2 -2
  289. package/dist/types/types.d.ts +25 -7
  290. package/dist/types/urls.d.ts +1 -0
  291. package/dist/{src/types.js → types.js} +53 -0
  292. package/dist/types.js.map +1 -0
  293. package/dist/urls.js +5 -0
  294. package/dist/urls.js.map +1 -0
  295. package/docs/rules/README.md +23 -2
  296. package/docs/rules/actionview-no-silent-helper.md +57 -0
  297. package/docs/rules/erb-no-conditional-html-element.md +90 -0
  298. package/docs/rules/erb-no-conditional-open-tag.md +130 -0
  299. package/docs/rules/erb-no-duplicate-branch-elements.md +98 -0
  300. package/docs/rules/erb-no-inline-case-conditions.md +85 -0
  301. package/docs/rules/erb-no-instance-variables-in-partials.md +43 -0
  302. package/docs/rules/erb-no-interpolated-class-names.md +57 -0
  303. package/docs/rules/erb-no-javascript-tag-helper.md +33 -0
  304. package/docs/rules/erb-no-output-in-attribute-name.md +38 -0
  305. package/docs/rules/erb-no-output-in-attribute-position.md +60 -0
  306. package/docs/rules/erb-no-raw-output-in-attribute-value.md +37 -0
  307. package/docs/rules/erb-no-statement-in-script.md +68 -0
  308. package/docs/rules/erb-no-then-in-control-flow.md +86 -0
  309. package/docs/rules/erb-no-trailing-whitespace.md +69 -0
  310. package/docs/rules/erb-no-unsafe-js-attribute.md +41 -0
  311. package/docs/rules/erb-no-unsafe-raw.md +47 -0
  312. package/docs/rules/erb-no-unsafe-script-interpolation.md +73 -0
  313. package/docs/rules/html-allowed-script-type.md +59 -0
  314. package/docs/rules/html-anchor-require-href.md +19 -6
  315. package/docs/rules/html-details-has-summary.md +46 -0
  316. package/docs/rules/html-img-require-alt.md +5 -3
  317. package/docs/rules/html-no-abstract-roles.md +74 -0
  318. package/docs/rules/html-no-aria-hidden-on-body.md +44 -0
  319. package/docs/rules/html-require-closing-tags.md +142 -0
  320. package/docs/rules/parser-no-errors.md +4 -17
  321. package/docs/rules/turbo-permanent-require-id.md +41 -0
  322. package/package.json +12 -11
  323. package/src/cli/argument-parser.ts +20 -2
  324. package/src/cli/file-processor.ts +189 -10
  325. package/src/cli/file-url.ts +6 -0
  326. package/src/cli/formatters/detailed-formatter.ts +19 -21
  327. package/src/cli/formatters/simple-formatter.ts +23 -13
  328. package/src/cli/index.ts +2 -0
  329. package/src/cli/lint-worker.ts +208 -0
  330. package/src/cli/summary-reporter.ts +14 -15
  331. package/src/cli.ts +5 -3
  332. package/src/custom-rule-loader.ts +20 -5
  333. package/src/herb-disable-comment-utils.ts +0 -3
  334. package/src/index.ts +1 -0
  335. package/src/linter.ts +98 -79
  336. package/src/parse-cache.ts +39 -0
  337. package/src/rules/actionview-no-silent-helper.ts +58 -0
  338. package/src/rules/erb-comment-syntax.ts +2 -2
  339. package/src/rules/erb-no-case-node-children.ts +2 -2
  340. package/src/rules/erb-no-conditional-html-element.ts +53 -0
  341. package/src/rules/erb-no-conditional-open-tag.ts +37 -0
  342. package/src/rules/erb-no-duplicate-branch-elements.ts +320 -0
  343. package/src/rules/erb-no-empty-tags.ts +2 -2
  344. package/src/rules/erb-no-extra-newline.ts +5 -25
  345. package/src/rules/erb-no-extra-whitespace-inside-tags.ts +45 -15
  346. package/src/rules/erb-no-inline-case-conditions.ts +54 -0
  347. package/src/rules/erb-no-instance-variables-in-partials.ts +101 -0
  348. package/src/rules/erb-no-interpolated-class-names.ts +65 -0
  349. package/src/rules/erb-no-javascript-tag-helper.ts +47 -0
  350. package/src/rules/erb-no-output-control-flow.ts +10 -10
  351. package/src/rules/erb-no-output-in-attribute-name.ts +39 -0
  352. package/src/rules/erb-no-output-in-attribute-position.ts +39 -0
  353. package/src/rules/erb-no-raw-output-in-attribute-value.ts +47 -0
  354. package/src/rules/erb-no-silent-tag-in-attribute-name.ts +2 -2
  355. package/src/rules/erb-no-statement-in-script.ts +82 -0
  356. package/src/rules/erb-no-then-in-control-flow.ts +62 -0
  357. package/src/rules/erb-no-trailing-whitespace.ts +187 -0
  358. package/src/rules/erb-no-unsafe-js-attribute.ts +47 -0
  359. package/src/rules/erb-no-unsafe-raw.ts +83 -0
  360. package/src/rules/erb-no-unsafe-script-interpolation.ts +76 -0
  361. package/src/rules/erb-prefer-image-tag-helper.ts +5 -4
  362. package/src/rules/erb-require-trailing-newline.ts +2 -2
  363. package/src/rules/erb-require-whitespace-inside-tags.ts +42 -18
  364. package/src/rules/erb-right-trim.ts +2 -2
  365. package/src/rules/erb-strict-locals-comment-syntax.ts +5 -5
  366. package/src/rules/erb-strict-locals-required.ts +2 -2
  367. package/src/rules/herb-disable-comment-malformed.ts +2 -2
  368. package/src/rules/herb-disable-comment-missing-rules.ts +2 -2
  369. package/src/rules/herb-disable-comment-no-duplicate-rules.ts +2 -2
  370. package/src/rules/herb-disable-comment-no-redundant-all.ts +2 -2
  371. package/src/rules/herb-disable-comment-unnecessary.ts +2 -2
  372. package/src/rules/herb-disable-comment-valid-rule-name.ts +2 -2
  373. package/src/rules/html-allowed-script-type.ts +84 -0
  374. package/src/rules/html-anchor-require-href.ts +73 -11
  375. package/src/rules/html-aria-attribute-must-be-valid.ts +3 -3
  376. package/src/rules/html-aria-label-is-well-formatted.ts +3 -3
  377. package/src/rules/html-aria-level-must-be-valid.ts +3 -3
  378. package/src/rules/html-aria-role-heading-requires-level.ts +5 -4
  379. package/src/rules/html-aria-role-must-be-valid.ts +3 -3
  380. package/src/rules/html-attribute-double-quotes.ts +4 -4
  381. package/src/rules/html-attribute-equals-spacing.ts +2 -2
  382. package/src/rules/html-attribute-values-require-quotes.ts +2 -2
  383. package/src/rules/html-avoid-both-disabled-and-aria-disabled.ts +10 -11
  384. package/src/rules/html-body-only-elements.ts +5 -4
  385. package/src/rules/html-boolean-attributes-no-value.ts +4 -3
  386. package/src/rules/html-details-has-summary.ts +69 -0
  387. package/src/rules/html-head-only-elements.ts +6 -5
  388. package/src/rules/html-iframe-has-title.ts +8 -11
  389. package/src/rules/html-img-require-alt.ts +16 -5
  390. package/src/rules/html-input-require-autocomplete.ts +7 -10
  391. package/src/rules/html-navigation-has-label.ts +6 -5
  392. package/src/rules/html-no-abstract-roles.ts +40 -0
  393. package/src/rules/html-no-aria-hidden-on-body.ts +58 -0
  394. package/src/rules/html-no-aria-hidden-on-focusable.ts +6 -5
  395. package/src/rules/html-no-block-inside-inline.ts +7 -13
  396. package/src/rules/html-no-duplicate-attributes.ts +4 -3
  397. package/src/rules/html-no-duplicate-ids.ts +16 -13
  398. package/src/rules/html-no-duplicate-meta-names.ts +20 -19
  399. package/src/rules/html-no-empty-attributes.ts +2 -2
  400. package/src/rules/html-no-empty-headings.ts +44 -58
  401. package/src/rules/html-no-nested-links.ts +25 -16
  402. package/src/rules/html-no-positive-tab-index.ts +3 -3
  403. package/src/rules/html-no-self-closing.ts +5 -5
  404. package/src/rules/html-no-space-in-tag.ts +5 -8
  405. package/src/rules/html-no-title-attribute.ts +6 -5
  406. package/src/rules/html-no-underscores-in-attribute-names.ts +2 -2
  407. package/src/rules/html-require-closing-tags.ts +41 -0
  408. package/src/rules/html-tag-name-lowercase.ts +14 -9
  409. package/src/rules/index.ts +19 -0
  410. package/src/rules/parser-no-errors.ts +3 -3
  411. package/src/rules/rule-utils.ts +162 -279
  412. package/src/rules/svg-tag-name-capitalization.ts +10 -10
  413. package/src/rules/turbo-permanent-require-id.ts +49 -0
  414. package/src/rules.ts +60 -10
  415. package/src/types.ts +76 -7
  416. package/src/urls.ts +5 -0
  417. package/dist/package.json +0 -65
  418. package/dist/src/cli/argument-parser.js.map +0 -1
  419. package/dist/src/cli/file-processor.js.map +0 -1
  420. package/dist/src/cli/formatters/base-formatter.js.map +0 -1
  421. package/dist/src/cli/formatters/detailed-formatter.js.map +0 -1
  422. package/dist/src/cli/formatters/github-actions-formatter.js.map +0 -1
  423. package/dist/src/cli/formatters/index.js.map +0 -1
  424. package/dist/src/cli/formatters/json-formatter.js.map +0 -1
  425. package/dist/src/cli/formatters/simple-formatter.js +0 -44
  426. package/dist/src/cli/formatters/simple-formatter.js.map +0 -1
  427. package/dist/src/cli/index.js.map +0 -1
  428. package/dist/src/cli/output-manager.js.map +0 -1
  429. package/dist/src/cli/summary-reporter.js.map +0 -1
  430. package/dist/src/cli.js.map +0 -1
  431. package/dist/src/custom-rule-loader.js.map +0 -1
  432. package/dist/src/herb-disable-comment-utils.js.map +0 -1
  433. package/dist/src/herb-lint.js +0 -5
  434. package/dist/src/herb-lint.js.map +0 -1
  435. package/dist/src/index.js +0 -5
  436. package/dist/src/index.js.map +0 -1
  437. package/dist/src/linter-ignore.js.map +0 -1
  438. package/dist/src/linter.js.map +0 -1
  439. package/dist/src/loader.js +0 -17
  440. package/dist/src/loader.js.map +0 -1
  441. package/dist/src/rules/erb-comment-syntax.js.map +0 -1
  442. package/dist/src/rules/erb-no-case-node-children.js.map +0 -1
  443. package/dist/src/rules/erb-no-empty-tags.js.map +0 -1
  444. package/dist/src/rules/erb-no-extra-newline.js.map +0 -1
  445. package/dist/src/rules/erb-no-extra-whitespace-inside-tags.js.map +0 -1
  446. package/dist/src/rules/erb-no-output-control-flow.js.map +0 -1
  447. package/dist/src/rules/erb-no-silent-tag-in-attribute-name.js.map +0 -1
  448. package/dist/src/rules/erb-prefer-image-tag-helper.js.map +0 -1
  449. package/dist/src/rules/erb-require-trailing-newline.js.map +0 -1
  450. package/dist/src/rules/erb-require-whitespace-inside-tags.js.map +0 -1
  451. package/dist/src/rules/erb-right-trim.js.map +0 -1
  452. package/dist/src/rules/erb-strict-locals-comment-syntax.js.map +0 -1
  453. package/dist/src/rules/erb-strict-locals-required.js.map +0 -1
  454. package/dist/src/rules/file-utils.js.map +0 -1
  455. package/dist/src/rules/herb-disable-comment-base.js.map +0 -1
  456. package/dist/src/rules/herb-disable-comment-malformed.js.map +0 -1
  457. package/dist/src/rules/herb-disable-comment-missing-rules.js.map +0 -1
  458. package/dist/src/rules/herb-disable-comment-no-duplicate-rules.js.map +0 -1
  459. package/dist/src/rules/herb-disable-comment-no-redundant-all.js.map +0 -1
  460. package/dist/src/rules/herb-disable-comment-unnecessary.js.map +0 -1
  461. package/dist/src/rules/herb-disable-comment-valid-rule-name.js.map +0 -1
  462. package/dist/src/rules/html-anchor-require-href.js +0 -32
  463. package/dist/src/rules/html-anchor-require-href.js.map +0 -1
  464. package/dist/src/rules/html-aria-attribute-must-be-valid.js.map +0 -1
  465. package/dist/src/rules/html-aria-label-is-well-formatted.js.map +0 -1
  466. package/dist/src/rules/html-aria-level-must-be-valid.js.map +0 -1
  467. package/dist/src/rules/html-aria-role-heading-requires-level.js.map +0 -1
  468. package/dist/src/rules/html-aria-role-must-be-valid.js.map +0 -1
  469. package/dist/src/rules/html-attribute-double-quotes.js.map +0 -1
  470. package/dist/src/rules/html-attribute-equals-spacing.js.map +0 -1
  471. package/dist/src/rules/html-attribute-values-require-quotes.js.map +0 -1
  472. package/dist/src/rules/html-avoid-both-disabled-and-aria-disabled.js.map +0 -1
  473. package/dist/src/rules/html-body-only-elements.js.map +0 -1
  474. package/dist/src/rules/html-boolean-attributes-no-value.js.map +0 -1
  475. package/dist/src/rules/html-head-only-elements.js.map +0 -1
  476. package/dist/src/rules/html-iframe-has-title.js.map +0 -1
  477. package/dist/src/rules/html-img-require-alt.js.map +0 -1
  478. package/dist/src/rules/html-input-require-autocomplete.js.map +0 -1
  479. package/dist/src/rules/html-navigation-has-label.js.map +0 -1
  480. package/dist/src/rules/html-no-aria-hidden-on-focusable.js.map +0 -1
  481. package/dist/src/rules/html-no-block-inside-inline.js.map +0 -1
  482. package/dist/src/rules/html-no-duplicate-attributes.js.map +0 -1
  483. package/dist/src/rules/html-no-duplicate-ids.js.map +0 -1
  484. package/dist/src/rules/html-no-duplicate-meta-names.js.map +0 -1
  485. package/dist/src/rules/html-no-empty-attributes.js.map +0 -1
  486. package/dist/src/rules/html-no-empty-headings.js +0 -115
  487. package/dist/src/rules/html-no-empty-headings.js.map +0 -1
  488. package/dist/src/rules/html-no-nested-links.js.map +0 -1
  489. package/dist/src/rules/html-no-positive-tab-index.js.map +0 -1
  490. package/dist/src/rules/html-no-self-closing.js.map +0 -1
  491. package/dist/src/rules/html-no-space-in-tag.js.map +0 -1
  492. package/dist/src/rules/html-no-title-attribute.js.map +0 -1
  493. package/dist/src/rules/html-no-underscores-in-attribute-names.js.map +0 -1
  494. package/dist/src/rules/html-tag-name-lowercase.js.map +0 -1
  495. package/dist/src/rules/index.js.map +0 -1
  496. package/dist/src/rules/parser-no-errors.js.map +0 -1
  497. package/dist/src/rules/rule-utils.js.map +0 -1
  498. package/dist/src/rules/string-utils.js.map +0 -1
  499. package/dist/src/rules/svg-tag-name-capitalization.js.map +0 -1
  500. package/dist/src/rules.js.map +0 -1
  501. package/dist/src/types.js.map +0 -1
  502. package/dist/tsconfig.tsbuildinfo +0 -1
  503. package/dist/types/src/cli/argument-parser.d.ts +0 -25
  504. package/dist/types/src/cli/file-processor.d.ts +0 -43
  505. package/dist/types/src/cli/formatters/base-formatter.d.ts +0 -6
  506. package/dist/types/src/cli/formatters/detailed-formatter.d.ts +0 -13
  507. package/dist/types/src/cli/formatters/github-actions-formatter.d.ts +0 -17
  508. package/dist/types/src/cli/formatters/index.d.ts +0 -5
  509. package/dist/types/src/cli/formatters/json-formatter.d.ts +0 -48
  510. package/dist/types/src/cli/formatters/simple-formatter.d.ts +0 -8
  511. package/dist/types/src/cli/index.d.ts +0 -5
  512. package/dist/types/src/cli/output-manager.d.ts +0 -32
  513. package/dist/types/src/cli/summary-reporter.d.ts +0 -28
  514. package/dist/types/src/cli.d.ts +0 -28
  515. package/dist/types/src/custom-rule-loader.d.ts +0 -62
  516. package/dist/types/src/herb-disable-comment-utils.d.ts +0 -69
  517. package/dist/types/src/herb-lint.d.ts +0 -2
  518. package/dist/types/src/index.d.ts +0 -4
  519. package/dist/types/src/linter-ignore.d.ts +0 -12
  520. package/dist/types/src/linter.d.ts +0 -133
  521. package/dist/types/src/loader.d.ts +0 -20
  522. package/dist/types/src/rules/erb-comment-syntax.d.ts +0 -14
  523. package/dist/types/src/rules/erb-no-extra-newline.d.ts +0 -14
  524. package/dist/types/src/rules/erb-no-extra-whitespace-inside-tags.d.ts +0 -18
  525. package/dist/types/src/rules/erb-require-trailing-newline.d.ts +0 -9
  526. package/dist/types/src/rules/erb-require-whitespace-inside-tags.d.ts +0 -18
  527. package/dist/types/src/rules/erb-right-trim.d.ts +0 -14
  528. package/dist/types/src/rules/erb-strict-locals-comment-syntax.d.ts +0 -9
  529. package/dist/types/src/rules/erb-strict-locals-required.d.ts +0 -9
  530. package/dist/types/src/rules/file-utils.d.ts +0 -13
  531. package/dist/types/src/rules/herb-disable-comment-base.d.ts +0 -37
  532. package/dist/types/src/rules/herb-disable-comment-no-duplicate-rules.d.ts +0 -8
  533. package/dist/types/src/rules/herb-disable-comment-no-redundant-all.d.ts +0 -8
  534. package/dist/types/src/rules/herb-disable-comment-unnecessary.d.ts +0 -8
  535. package/dist/types/src/rules/herb-disable-comment-valid-rule-name.d.ts +0 -8
  536. package/dist/types/src/rules/html-aria-label-is-well-formatted.d.ts +0 -8
  537. package/dist/types/src/rules/html-aria-role-heading-requires-level.d.ts +0 -8
  538. package/dist/types/src/rules/html-attribute-double-quotes.d.ts +0 -15
  539. package/dist/types/src/rules/html-attribute-equals-spacing.d.ts +0 -14
  540. package/dist/types/src/rules/html-attribute-values-require-quotes.d.ts +0 -15
  541. package/dist/types/src/rules/html-avoid-both-disabled-and-aria-disabled.d.ts +0 -8
  542. package/dist/types/src/rules/html-boolean-attributes-no-value.d.ts +0 -14
  543. package/dist/types/src/rules/html-head-only-elements.d.ts +0 -9
  544. package/dist/types/src/rules/html-iframe-has-title.d.ts +0 -8
  545. package/dist/types/src/rules/html-img-require-alt.d.ts +0 -8
  546. package/dist/types/src/rules/html-input-require-autocomplete.d.ts +0 -8
  547. package/dist/types/src/rules/html-no-block-inside-inline.d.ts +0 -8
  548. package/dist/types/src/rules/html-no-duplicate-ids.d.ts +0 -8
  549. package/dist/types/src/rules/html-no-duplicate-meta-names.d.ts +0 -9
  550. package/dist/types/src/rules/html-no-nested-links.d.ts +0 -8
  551. package/dist/types/src/rules/html-no-self-closing.d.ts +0 -16
  552. package/dist/types/src/rules/html-no-space-in-tag.d.ts +0 -16
  553. package/dist/types/src/rules/html-no-title-attribute.d.ts +0 -8
  554. package/dist/types/src/rules/html-no-underscores-in-attribute-names.d.ts +0 -8
  555. package/dist/types/src/rules/html-tag-name-lowercase.d.ts +0 -18
  556. package/dist/types/src/rules/index.d.ts +0 -54
  557. package/dist/types/src/rules/parser-no-errors.d.ts +0 -9
  558. package/dist/types/src/rules/rule-utils.d.ts +0 -351
  559. package/dist/types/src/rules/string-utils.d.ts +0 -15
  560. package/dist/types/src/rules/svg-tag-name-capitalization.d.ts +0 -16
  561. package/dist/types/src/rules.d.ts +0 -2
  562. package/dist/types/src/types.d.ts +0 -190
  563. /package/dist/{src/cli → cli}/formatters/base-formatter.js +0 -0
  564. /package/dist/{src/cli → cli}/formatters/github-actions-formatter.js +0 -0
  565. /package/dist/{src/cli → cli}/formatters/index.js +0 -0
  566. /package/dist/{src/cli → cli}/formatters/json-formatter.js +0 -0
  567. /package/dist/{src/cli → cli}/index.js +0 -0
  568. /package/dist/{src/cli → cli}/output-manager.js +0 -0
  569. /package/dist/{src/herb-disable-comment-utils.js → herb-disable-comment-utils.js} +0 -0
  570. /package/dist/{src/linter-ignore.js → linter-ignore.js} +0 -0
  571. /package/dist/{src/rules → rules}/file-utils.js +0 -0
  572. /package/dist/{src/rules → rules}/herb-disable-comment-base.js +0 -0
  573. /package/dist/{src/rules → rules}/string-utils.js +0 -0
@@ -0,0 +1,83 @@
1
+ import { ParserRule } from "../types.js"
2
+ import { BaseRuleVisitor } from "./rule-utils.js"
3
+ import { isERBOutputNode, getTagLocalName, isHTMLOpenTagNode } from "@herb-tools/core"
4
+
5
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
6
+ import type { ParseResult, ERBContentNode, HTMLElementNode } from "@herb-tools/core"
7
+
8
+ const RAW_PATTERN = /\braw[\s(]/
9
+ const HTML_SAFE_PATTERN = /\.html_safe\b/
10
+
11
+ const RAW_TEXT_ELEMENTS = new Set([
12
+ "title",
13
+ "textarea",
14
+ "script",
15
+ "style",
16
+ "xmp",
17
+ "iframe",
18
+ "noembed",
19
+ "noframes",
20
+ "listing",
21
+ "plaintext",
22
+ ])
23
+
24
+ class ERBNoUnsafeRawVisitor extends BaseRuleVisitor {
25
+ private insideRawTextElement = false
26
+
27
+ visitHTMLElementNode(node: HTMLElementNode): void {
28
+ if (!isHTMLOpenTagNode(node.open_tag)) {
29
+ super.visitHTMLElementNode(node)
30
+ return
31
+ }
32
+
33
+ const tagName = getTagLocalName(node.open_tag)
34
+
35
+ if (tagName && RAW_TEXT_ELEMENTS.has(tagName)) {
36
+ const wasInside = this.insideRawTextElement
37
+ this.insideRawTextElement = true
38
+ super.visitHTMLElementNode(node)
39
+ this.insideRawTextElement = wasInside
40
+ return
41
+ }
42
+
43
+ super.visitHTMLElementNode(node)
44
+ }
45
+
46
+ visitERBContentNode(node: ERBContentNode): void {
47
+ if (this.insideRawTextElement) return
48
+ if (!isERBOutputNode(node)) return
49
+
50
+ const content = node.content?.value || ""
51
+
52
+ if (RAW_PATTERN.test(content)) {
53
+ this.addOffense(
54
+ "Avoid `raw()` in ERB output. It bypasses HTML escaping and can cause cross-site scripting (XSS) vulnerabilities.",
55
+ node.location,
56
+ )
57
+ }
58
+
59
+ if (HTML_SAFE_PATTERN.test(content)) {
60
+ this.addOffense(
61
+ "Avoid `.html_safe` in ERB output. It bypasses HTML escaping and can cause cross-site scripting (XSS) vulnerabilities.",
62
+ node.location,
63
+ )
64
+ }
65
+ }
66
+ }
67
+
68
+ export class ERBNoUnsafeRawRule extends ParserRule {
69
+ static ruleName = "erb-no-unsafe-raw"
70
+
71
+ get defaultConfig(): FullRuleConfig {
72
+ return {
73
+ enabled: true,
74
+ severity: "error"
75
+ }
76
+ }
77
+
78
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
79
+ const visitor = new ERBNoUnsafeRawVisitor(this.ruleName, context)
80
+ visitor.visit(result.value)
81
+ return visitor.offenses
82
+ }
83
+ }
@@ -0,0 +1,76 @@
1
+ import { ParserRule } from "../types.js"
2
+ import { BaseRuleVisitor } from "./rule-utils.js"
3
+ import {
4
+ getTagLocalName,
5
+ getAttribute,
6
+ getStaticAttributeValue,
7
+ isERBNode,
8
+ isERBOutputNode,
9
+ isHTMLOpenTagNode,
10
+ } from "@herb-tools/core"
11
+
12
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
13
+ import type { ParseResult, HTMLElementNode, Node } from "@herb-tools/core"
14
+
15
+ const SAFE_PATTERN = /\.to_json\b/
16
+
17
+ class ERBNoUnsafeScriptInterpolationVisitor extends BaseRuleVisitor {
18
+ visitHTMLElementNode(node: HTMLElementNode): void {
19
+ if (!isHTMLOpenTagNode(node.open_tag)) {
20
+ super.visitHTMLElementNode(node)
21
+ return
22
+ }
23
+
24
+ if (getTagLocalName(node.open_tag) === "script") {
25
+ this.checkScriptElement(node)
26
+ }
27
+
28
+ super.visitHTMLElementNode(node)
29
+ }
30
+
31
+ private checkScriptElement(node: HTMLElementNode): void {
32
+ if (!isHTMLOpenTagNode(node.open_tag)) return
33
+
34
+ const typeAttribute = getAttribute(node.open_tag, "type")
35
+ const typeValue = typeAttribute ? getStaticAttributeValue(typeAttribute) : null
36
+
37
+ if (typeValue === "text/html") return
38
+
39
+ if (!node.body || node.body.length === 0) return
40
+
41
+ this.checkNodesForUnsafeOutput(node.body)
42
+ }
43
+
44
+ private checkNodesForUnsafeOutput(nodes: Node[]): void {
45
+ for (const child of nodes) {
46
+ if (!isERBNode(child)) continue
47
+ if (!isERBOutputNode(child)) continue
48
+
49
+ const content = child.content?.value?.trim() || ""
50
+
51
+ if (SAFE_PATTERN.test(content)) continue
52
+
53
+ this.addOffense(
54
+ "Unsafe ERB output in `<script>` tag. Use `.to_json` to safely serialize values into JavaScript.",
55
+ child.location,
56
+ )
57
+ }
58
+ }
59
+ }
60
+
61
+ export class ERBNoUnsafeScriptInterpolationRule extends ParserRule {
62
+ static ruleName = "erb-no-unsafe-script-interpolation"
63
+
64
+ get defaultConfig(): FullRuleConfig {
65
+ return {
66
+ enabled: true,
67
+ severity: "error"
68
+ }
69
+ }
70
+
71
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
72
+ const visitor = new ERBNoUnsafeScriptInterpolationVisitor(this.ruleName, context)
73
+ visitor.visit(result.value)
74
+ return visitor.offenses
75
+ }
76
+ }
@@ -1,5 +1,6 @@
1
1
  import { ParserRule } from "../types.js"
2
- import { BaseRuleVisitor, getTagName, findAttributeByName, getAttributes } from "./rule-utils.js"
2
+ import { BaseRuleVisitor } from "./rule-utils.js"
3
+ import { findAttributeByName, getAttributes, getTagLocalName } from "@herb-tools/core"
3
4
 
4
5
  import { ERBToRubyStringPrinter } from "@herb-tools/printer"
5
6
  import { filterNodes, ERBContentNode, LiteralNode, isNode } from "@herb-tools/core"
@@ -14,7 +15,7 @@ class ERBPreferImageTagHelperVisitor extends BaseRuleVisitor {
14
15
  }
15
16
 
16
17
  private checkImgTag(openTag: HTMLOpenTagNode): void {
17
- const tagName = getTagName(openTag)
18
+ const tagName = getTagLocalName(openTag)
18
19
 
19
20
  if (tagName !== "img") return
20
21
 
@@ -90,7 +91,7 @@ class ERBPreferImageTagHelperVisitor extends BaseRuleVisitor {
90
91
  }
91
92
 
92
93
  export class ERBPreferImageTagHelperRule extends ParserRule {
93
- name = "erb-prefer-image-tag-helper"
94
+ static ruleName = "erb-prefer-image-tag-helper"
94
95
 
95
96
  get defaultConfig(): FullRuleConfig {
96
97
  return {
@@ -100,7 +101,7 @@ export class ERBPreferImageTagHelperRule extends ParserRule {
100
101
  }
101
102
 
102
103
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
103
- const visitor = new ERBPreferImageTagHelperVisitor(this.name, context)
104
+ const visitor = new ERBPreferImageTagHelperVisitor(this.ruleName, context)
104
105
  visitor.visit(result.value)
105
106
  return visitor.offenses
106
107
  }
@@ -24,7 +24,7 @@ class ERBRequireTrailingNewlineVisitor extends BaseSourceRuleVisitor {
24
24
 
25
25
  export class ERBRequireTrailingNewlineRule extends SourceRule {
26
26
  static autocorrectable = true
27
- name = "erb-require-trailing-newline"
27
+ static ruleName = "erb-require-trailing-newline"
28
28
 
29
29
  get defaultConfig(): FullRuleConfig {
30
30
  return {
@@ -34,7 +34,7 @@ export class ERBRequireTrailingNewlineRule extends SourceRule {
34
34
  }
35
35
 
36
36
  check(source: string, context?: Partial<LintContext>): UnboundLintOffense[] {
37
- const visitor = new ERBRequireTrailingNewlineVisitor(this.name, context)
37
+ const visitor = new ERBRequireTrailingNewlineVisitor(this.ruleName, context)
38
38
 
39
39
  visitor.visit(source)
40
40
 
@@ -34,7 +34,28 @@ class RequireWhitespaceInsideTags extends BaseRuleVisitor<ERBRequireWhitespaceAu
34
34
  }
35
35
 
36
36
  private checkCommentTagWhitespace(node: ERBNode, openTag: Token, closeTag: Token, content: string): void {
37
- if (!content.startsWith(" ") && !content.startsWith("\n") && !content.startsWith("=")) {
37
+ const commentedTagPrefix = this.getCommentedTagPrefix(content)
38
+
39
+ if (commentedTagPrefix) {
40
+ const afterPrefix = content.substring(commentedTagPrefix.length)
41
+ const tag = `<%#${commentedTagPrefix}`
42
+
43
+ if (afterPrefix.length > 0 && !afterPrefix[0].match(/\s/)) {
44
+ this.addOffense(
45
+ `Add whitespace after \`${tag}\`. This looks like a temporarily commented ERB tag.`,
46
+ openTag.location,
47
+ {
48
+ node,
49
+ openTag,
50
+ closeTag,
51
+ content,
52
+ fixType: "after-comment-equals",
53
+ unsafe: true,
54
+ },
55
+ "info"
56
+ )
57
+ }
58
+ } else if (!content.startsWith(" ") && !content.startsWith("\n")) {
38
59
  this.addOffense(
39
60
  `Add whitespace after \`${openTag.value}\`.`,
40
61
  openTag.location,
@@ -46,18 +67,6 @@ class RequireWhitespaceInsideTags extends BaseRuleVisitor<ERBRequireWhitespaceAu
46
67
  fixType: "after-open"
47
68
  }
48
69
  )
49
- } else if (content.startsWith("=") && content.length > 1 && !content[1].match(/\s/)) {
50
- this.addOffense(
51
- `Add whitespace after \`<%#=\`.`,
52
- openTag.location,
53
- {
54
- node,
55
- openTag,
56
- closeTag,
57
- content,
58
- fixType: "after-comment-equals"
59
- }
60
- )
61
70
  }
62
71
 
63
72
  if (!content.endsWith(" ") && !content.endsWith("\n")) {
@@ -93,6 +102,17 @@ class RequireWhitespaceInsideTags extends BaseRuleVisitor<ERBRequireWhitespaceAu
93
102
  )
94
103
  }
95
104
 
105
+ private getCommentedTagPrefix(content: string): string | null {
106
+ if (content.startsWith("graphql")) return "graphql"
107
+ if (content.startsWith("%=")) return "%="
108
+ if (content.startsWith("==")) return "=="
109
+ if (content.startsWith("%")) return "%"
110
+ if (content.startsWith("=")) return "="
111
+ if (content.startsWith("-")) return "-"
112
+
113
+ return null
114
+ }
115
+
96
116
  private checkCloseTagWhitespace(node: ERBNode, openTag: Token, closeTag: Token, content: string):void {
97
117
  if (content.endsWith(" ") || content.endsWith("\n")) {
98
118
  return
@@ -114,7 +134,7 @@ class RequireWhitespaceInsideTags extends BaseRuleVisitor<ERBRequireWhitespaceAu
114
134
 
115
135
  export class ERBRequireWhitespaceRule extends ParserRule<ERBRequireWhitespaceAutofixContext> {
116
136
  static autocorrectable = true
117
- name = "erb-require-whitespace-inside-tags"
137
+ static ruleName = "erb-require-whitespace-inside-tags"
118
138
 
119
139
  get defaultConfig(): FullRuleConfig {
120
140
  return {
@@ -124,7 +144,7 @@ export class ERBRequireWhitespaceRule extends ParserRule<ERBRequireWhitespaceAut
124
144
  }
125
145
 
126
146
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<ERBRequireWhitespaceAutofixContext>[] {
127
- const visitor = new RequireWhitespaceInsideTags(this.name, context)
147
+ const visitor = new RequireWhitespaceInsideTags(this.ruleName, context)
128
148
 
129
149
  visitor.visit(result.value)
130
150
 
@@ -152,10 +172,14 @@ export class ERBRequireWhitespaceRule extends ParserRule<ERBRequireWhitespaceAut
152
172
  return result
153
173
  }
154
174
 
155
- if (fixType === "after-comment-equals" && content.startsWith("=")) {
156
- node.content.value = "= " + content.substring(1)
175
+ if (fixType === "after-comment-equals") {
176
+ const prefix = content.startsWith("graphql") ? "graphql" : content.startsWith("%=") ? "%=" : content.startsWith("==") ? "==" : content.startsWith("%") ? "%" : content.startsWith("=") ? "=" : content.startsWith("-") ? "-" : null
157
177
 
158
- return result
178
+ if (prefix) {
179
+ node.content.value = prefix + " " + content.substring(prefix.length)
180
+
181
+ return result
182
+ }
159
183
  }
160
184
 
161
185
  return null
@@ -26,7 +26,7 @@ class ERBRightTrimVisitor extends BaseRuleVisitor<ERBRightTrimAutofixContext> {
26
26
 
27
27
  export class ERBRightTrimRule extends ParserRule<ERBRightTrimAutofixContext> {
28
28
  static autocorrectable = true
29
- name = "erb-right-trim"
29
+ static ruleName = "erb-right-trim"
30
30
 
31
31
  get defaultConfig(): FullRuleConfig {
32
32
  return {
@@ -36,7 +36,7 @@ export class ERBRightTrimRule extends ParserRule<ERBRightTrimAutofixContext> {
36
36
  }
37
37
 
38
38
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<ERBRightTrimAutofixContext>[] {
39
- const visitor = new ERBRightTrimVisitor(this.name, context)
39
+ const visitor = new ERBRightTrimVisitor(this.ruleName, context)
40
40
 
41
41
  visitor.visit(result.value)
42
42
 
@@ -7,7 +7,7 @@ import { hasBalancedParentheses, splitByTopLevelComma } from "./string-utils.js"
7
7
  import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
8
8
  import type { ParseResult, ERBContentNode } from "@herb-tools/core"
9
9
 
10
- export const STRICT_LOCALS_PATTERN = /^locals:\s+\([^)]*\)\s*$/
10
+ export const STRICT_LOCALS_PATTERN = /^locals:\s+\(.*\)\s*$/s
11
11
 
12
12
  function isValidStrictLocalsFormat(content: string): boolean {
13
13
  return STRICT_LOCALS_PATTERN.test(content)
@@ -42,7 +42,7 @@ function detectLocalsWithoutColon(content: string): boolean {
42
42
  }
43
43
 
44
44
  function detectSingularLocal(content: string): boolean {
45
- return /^local:/.test(content)
45
+ return content.startsWith('local:')
46
46
  }
47
47
 
48
48
  function detectMissingColonBeforeParens(content: string): boolean {
@@ -50,7 +50,7 @@ function detectMissingColonBeforeParens(content: string): boolean {
50
50
  }
51
51
 
52
52
  function detectMissingSpaceAfterColon(content: string): boolean {
53
- return /^locals:\(/.test(content)
53
+ return content.startsWith('locals:(')
54
54
  }
55
55
 
56
56
  function detectMissingParentheses(content: string): boolean {
@@ -264,7 +264,7 @@ class ERBStrictLocalsCommentSyntaxVisitor extends BaseRuleVisitor {
264
264
  }
265
265
 
266
266
  export class ERBStrictLocalsCommentSyntaxRule extends ParserRule {
267
- name = "erb-strict-locals-comment-syntax"
267
+ static ruleName = "erb-strict-locals-comment-syntax"
268
268
 
269
269
  get defaultConfig(): FullRuleConfig {
270
270
  return {
@@ -274,7 +274,7 @@ export class ERBStrictLocalsCommentSyntaxRule extends ParserRule {
274
274
  }
275
275
 
276
276
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
277
- const visitor = new ERBStrictLocalsCommentSyntaxVisitor(this.name, context)
277
+ const visitor = new ERBStrictLocalsCommentSyntaxVisitor(this.ruleName, context)
278
278
 
279
279
  visitor.visit(result.value)
280
280
 
@@ -29,7 +29,7 @@ class ERBStrictLocalsRequiredVisitor extends BaseSourceRuleVisitor {
29
29
 
30
30
  export class ERBStrictLocalsRequiredRule extends SourceRule {
31
31
  static unsafeAutocorrectable = true
32
- name = "erb-strict-locals-required"
32
+ static ruleName = "erb-strict-locals-required"
33
33
 
34
34
  get defaultConfig(): FullRuleConfig {
35
35
  return {
@@ -39,7 +39,7 @@ export class ERBStrictLocalsRequiredRule extends SourceRule {
39
39
  }
40
40
 
41
41
  check(source: string, context?: Partial<LintContext>): UnboundLintOffense[] {
42
- const visitor = new ERBStrictLocalsRequiredVisitor(this.name, context)
42
+ const visitor = new ERBStrictLocalsRequiredVisitor(this.ruleName, context)
43
43
 
44
44
  visitor.visit(source)
45
45
 
@@ -47,7 +47,7 @@ class HerbDisableCommentMalformedVisitor extends HerbDisableCommentBaseVisitor {
47
47
  }
48
48
 
49
49
  export class HerbDisableCommentMalformedRule extends ParserRule {
50
- name = "herb-disable-comment-malformed"
50
+ static ruleName = "herb-disable-comment-malformed"
51
51
 
52
52
  get defaultConfig(): FullRuleConfig {
53
53
  return {
@@ -57,7 +57,7 @@ export class HerbDisableCommentMalformedRule extends ParserRule {
57
57
  }
58
58
 
59
59
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
60
- const visitor = new HerbDisableCommentMalformedVisitor(this.name, context)
60
+ const visitor = new HerbDisableCommentMalformedVisitor(this.ruleName, context)
61
61
 
62
62
  visitor.visit(result.value)
63
63
 
@@ -22,7 +22,7 @@ class HerbDisableCommentMissingRulesVisitor extends HerbDisableCommentBaseVisito
22
22
  }
23
23
 
24
24
  export class HerbDisableCommentMissingRulesRule extends ParserRule {
25
- name = "herb-disable-comment-missing-rules"
25
+ static ruleName = "herb-disable-comment-missing-rules"
26
26
 
27
27
  get defaultConfig(): FullRuleConfig {
28
28
  return {
@@ -32,7 +32,7 @@ export class HerbDisableCommentMissingRulesRule extends ParserRule {
32
32
  }
33
33
 
34
34
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
35
- const visitor = new HerbDisableCommentMissingRulesVisitor(this.name, context)
35
+ const visitor = new HerbDisableCommentMissingRulesVisitor(this.ruleName, context)
36
36
 
37
37
  visitor.visit(result.value)
38
38
 
@@ -27,7 +27,7 @@ class HerbDisableCommentNoDuplicateRulesVisitor extends HerbDisableCommentParsed
27
27
  }
28
28
 
29
29
  export class HerbDisableCommentNoDuplicateRulesRule extends ParserRule {
30
- name = "herb-disable-comment-no-duplicate-rules"
30
+ static ruleName = "herb-disable-comment-no-duplicate-rules"
31
31
 
32
32
  get defaultConfig(): FullRuleConfig {
33
33
  return {
@@ -37,7 +37,7 @@ export class HerbDisableCommentNoDuplicateRulesRule extends ParserRule {
37
37
  }
38
38
 
39
39
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
40
- const visitor = new HerbDisableCommentNoDuplicateRulesVisitor(this.name, context)
40
+ const visitor = new HerbDisableCommentNoDuplicateRulesVisitor(this.ruleName, context)
41
41
 
42
42
  visitor.visit(result.value)
43
43
 
@@ -21,7 +21,7 @@ class HerbDisableCommentNoRedundantAllVisitor extends HerbDisableCommentParsedVi
21
21
  }
22
22
 
23
23
  export class HerbDisableCommentNoRedundantAllRule extends ParserRule {
24
- name = "herb-disable-comment-no-redundant-all"
24
+ static ruleName = "herb-disable-comment-no-redundant-all"
25
25
 
26
26
  get defaultConfig(): FullRuleConfig {
27
27
  return {
@@ -31,7 +31,7 @@ export class HerbDisableCommentNoRedundantAllRule extends ParserRule {
31
31
  }
32
32
 
33
33
  check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
34
- const visitor = new HerbDisableCommentNoRedundantAllVisitor(this.name, context)
34
+ const visitor = new HerbDisableCommentNoRedundantAllVisitor(this.ruleName, context)
35
35
 
36
36
  visitor.visit(result.value)
37
37
 
@@ -72,7 +72,7 @@ class HerbDisableCommentUnnecessaryVisitor extends HerbDisableCommentParsedVisit
72
72
  }
73
73
 
74
74
  export class HerbDisableCommentUnnecessaryRule extends ParserRule {
75
- name = "herb-disable-comment-unnecessary"
75
+ static ruleName = "herb-disable-comment-unnecessary"
76
76
 
77
77
  get defaultConfig(): FullRuleConfig {
78
78
  return {
@@ -90,7 +90,7 @@ export class HerbDisableCommentUnnecessaryRule extends ParserRule {
90
90
  if (!ignoredOffensesByLine) return []
91
91
 
92
92
  const visitor = new HerbDisableCommentUnnecessaryVisitor(
93
- this.name,
93
+ this.ruleName,
94
94
  ignoredOffensesByLine,
95
95
  validRuleNames,
96
96
  context
@@ -34,7 +34,7 @@ class HerbDisableCommentValidRuleNameVisitor extends HerbDisableCommentParsedVis
34
34
  }
35
35
 
36
36
  export class HerbDisableCommentValidRuleNameRule extends ParserRule {
37
- name = "herb-disable-comment-valid-rule-name"
37
+ static ruleName = "herb-disable-comment-valid-rule-name"
38
38
 
39
39
  get defaultConfig(): FullRuleConfig {
40
40
  return {
@@ -50,7 +50,7 @@ export class HerbDisableCommentValidRuleNameRule extends ParserRule {
50
50
  if (validRuleNames.length === 0) return []
51
51
 
52
52
  const visitor = new HerbDisableCommentValidRuleNameVisitor(
53
- this.name,
53
+ this.ruleName,
54
54
  validRuleNames,
55
55
  context
56
56
  )
@@ -0,0 +1,84 @@
1
+ import { ParserRule } from "../types.js"
2
+ import { BaseRuleVisitor } from "./rule-utils.js"
3
+ import { getTagLocalName, getAttribute, getStaticAttributeValue, hasAttributeValue } from "@herb-tools/core"
4
+
5
+ import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
6
+ import type { HTMLAttributeNode, HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
7
+
8
+ const ALLOWED_TYPES = ["text/javascript"]
9
+ // NOTE: Rules are not configurable for now, keep some sane defaults
10
+ // See https://github.com/marcoroth/herb/issues/1204
11
+ const ALLOW_BLANK = true
12
+
13
+ class AllowedScriptTypeVisitor extends BaseRuleVisitor {
14
+ visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
15
+ if (getTagLocalName(node) === "script") {
16
+ this.visitScriptNode(node)
17
+ }
18
+ }
19
+
20
+ private visitScriptNode(node: HTMLOpenTagNode): void {
21
+ const typeAttribute = getAttribute(node, "type")
22
+
23
+ if (!typeAttribute) {
24
+ if (!ALLOW_BLANK) {
25
+ this.addOffense("`type` attribute required for `<script>` tag.", node.location)
26
+ }
27
+
28
+ return
29
+ }
30
+
31
+ if (!hasAttributeValue(typeAttribute)) {
32
+ this.addOffense(
33
+ "Avoid using an empty `type` attribute on the `<script>` tag. Either set a valid type or remove the attribute entirely.",
34
+ typeAttribute.location
35
+ )
36
+
37
+ return
38
+ }
39
+
40
+ this.validateTypeAttribute(typeAttribute)
41
+ }
42
+
43
+ private validateTypeAttribute(typeAttribute: HTMLAttributeNode): void {
44
+ const typeValue = getStaticAttributeValue(typeAttribute)
45
+ if (typeValue === null) return
46
+
47
+ if (typeValue === "") {
48
+ this.addOffense(
49
+ "Avoid using an empty `type` attribute on the `<script>` tag. Either set a valid type or remove the attribute entirely.",
50
+ typeAttribute.location
51
+ )
52
+
53
+ return
54
+ }
55
+
56
+ if (ALLOWED_TYPES.includes(typeValue)) return
57
+
58
+ this.addOffense(
59
+ `Avoid using \`${typeValue}\` as the \`type\` attribute for the \`<script>\` tag. ` +
60
+ `Must be one of: ${ALLOWED_TYPES.map(t => `\`${t}\``).join(", ")}` +
61
+ `${ALLOW_BLANK ? " or blank" : ""}.`,
62
+ typeAttribute.location
63
+ )
64
+ }
65
+ }
66
+
67
+ export class HTMLAllowedScriptTypeRule extends ParserRule {
68
+ static ruleName = "html-allowed-script-type"
69
+
70
+ get defaultConfig(): FullRuleConfig {
71
+ return {
72
+ enabled: true,
73
+ severity: "error"
74
+ }
75
+ }
76
+
77
+ check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
78
+ const visitor = new AllowedScriptTypeVisitor(this.ruleName, context)
79
+
80
+ visitor.visit(result.value)
81
+
82
+ return visitor.offenses
83
+ }
84
+ }