@herb-tools/linter 0.8.10 → 0.9.1

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 (587) 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 +61834 -17340
  28. package/dist/herb-lint.js.map +1 -1
  29. package/dist/index.cjs +3109 -956
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.js +2969 -897
  32. package/dist/index.js.map +1 -1
  33. package/dist/lint-worker.js +72889 -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 +32163 -7842
  39. package/dist/loader.cjs.map +1 -1
  40. package/dist/loader.js +32121 -7828
  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/rules/actionview-no-silent-render.js +31 -0
  47. package/dist/rules/actionview-no-silent-render.js.map +1 -0
  48. package/dist/{src/rules → rules}/erb-comment-syntax.js +2 -2
  49. package/dist/rules/erb-comment-syntax.js.map +1 -0
  50. package/dist/{src/rules → rules}/erb-no-case-node-children.js +5 -3
  51. package/dist/rules/erb-no-case-node-children.js.map +1 -0
  52. package/dist/rules/erb-no-conditional-html-element.js +38 -0
  53. package/dist/rules/erb-no-conditional-html-element.js.map +1 -0
  54. package/dist/rules/erb-no-conditional-open-tag.js +24 -0
  55. package/dist/rules/erb-no-conditional-open-tag.js.map +1 -0
  56. package/dist/rules/erb-no-duplicate-branch-elements.js +329 -0
  57. package/dist/rules/erb-no-duplicate-branch-elements.js.map +1 -0
  58. package/dist/rules/erb-no-empty-control-flow.js +190 -0
  59. package/dist/rules/erb-no-empty-control-flow.js.map +1 -0
  60. package/dist/{src/rules → rules}/erb-no-empty-tags.js +2 -2
  61. package/dist/rules/erb-no-empty-tags.js.map +1 -0
  62. package/dist/{src/rules → rules}/erb-no-extra-newline.js +4 -21
  63. package/dist/rules/erb-no-extra-newline.js.map +1 -0
  64. package/dist/{src/rules → rules}/erb-no-extra-whitespace-inside-tags.js +39 -13
  65. package/dist/rules/erb-no-extra-whitespace-inside-tags.js.map +1 -0
  66. package/dist/rules/erb-no-inline-case-conditions.js +40 -0
  67. package/dist/rules/erb-no-inline-case-conditions.js.map +1 -0
  68. package/dist/rules/erb-no-instance-variables-in-partials.js +67 -0
  69. package/dist/rules/erb-no-instance-variables-in-partials.js.map +1 -0
  70. package/dist/rules/erb-no-interpolated-class-names.js +47 -0
  71. package/dist/rules/erb-no-interpolated-class-names.js.map +1 -0
  72. package/dist/rules/erb-no-javascript-tag-helper.js +34 -0
  73. package/dist/rules/erb-no-javascript-tag-helper.js.map +1 -0
  74. package/dist/{src/rules → rules}/erb-no-output-control-flow.js +9 -12
  75. package/dist/rules/erb-no-output-control-flow.js.map +1 -0
  76. package/dist/rules/erb-no-output-in-attribute-name.js +30 -0
  77. package/dist/rules/erb-no-output-in-attribute-name.js.map +1 -0
  78. package/dist/rules/erb-no-output-in-attribute-position.js +30 -0
  79. package/dist/rules/erb-no-output-in-attribute-position.js.map +1 -0
  80. package/dist/rules/erb-no-raw-output-in-attribute-value.js +35 -0
  81. package/dist/rules/erb-no-raw-output-in-attribute-value.js.map +1 -0
  82. package/dist/rules/erb-no-silent-statement.js +44 -0
  83. package/dist/rules/erb-no-silent-statement.js.map +1 -0
  84. package/dist/{src/rules → rules}/erb-no-silent-tag-in-attribute-name.js +2 -2
  85. package/dist/rules/erb-no-silent-tag-in-attribute-name.js.map +1 -0
  86. package/dist/rules/erb-no-statement-in-script.js +58 -0
  87. package/dist/rules/erb-no-statement-in-script.js.map +1 -0
  88. package/dist/rules/erb-no-then-in-control-flow.js +45 -0
  89. package/dist/rules/erb-no-then-in-control-flow.js.map +1 -0
  90. package/dist/rules/erb-no-trailing-whitespace.js +138 -0
  91. package/dist/rules/erb-no-trailing-whitespace.js.map +1 -0
  92. package/dist/rules/erb-no-unsafe-js-attribute.js +36 -0
  93. package/dist/rules/erb-no-unsafe-js-attribute.js.map +1 -0
  94. package/dist/rules/erb-no-unsafe-raw.js +63 -0
  95. package/dist/rules/erb-no-unsafe-raw.js.map +1 -0
  96. package/dist/rules/erb-no-unsafe-script-interpolation.js +88 -0
  97. package/dist/rules/erb-no-unsafe-script-interpolation.js.map +1 -0
  98. package/dist/{src/rules → rules}/erb-prefer-image-tag-helper.js +5 -4
  99. package/dist/rules/erb-prefer-image-tag-helper.js.map +1 -0
  100. package/dist/{src/rules → rules}/erb-require-trailing-newline.js +2 -2
  101. package/dist/rules/erb-require-trailing-newline.js.map +1 -0
  102. package/dist/{src/rules → rules}/erb-require-whitespace-inside-tags.js +39 -15
  103. package/dist/rules/erb-require-whitespace-inside-tags.js.map +1 -0
  104. package/dist/{src/rules → rules}/erb-right-trim.js +2 -2
  105. package/dist/rules/erb-right-trim.js.map +1 -0
  106. package/dist/{src/rules → rules}/erb-strict-locals-comment-syntax.js +4 -4
  107. package/dist/rules/erb-strict-locals-comment-syntax.js.map +1 -0
  108. package/dist/{src/rules → rules}/erb-strict-locals-required.js +2 -2
  109. package/dist/rules/erb-strict-locals-required.js.map +1 -0
  110. package/dist/rules/file-utils.js.map +1 -0
  111. package/dist/rules/herb-disable-comment-base.js.map +1 -0
  112. package/dist/{src/rules → rules}/herb-disable-comment-malformed.js +2 -2
  113. package/dist/rules/herb-disable-comment-malformed.js.map +1 -0
  114. package/dist/{src/rules → rules}/herb-disable-comment-missing-rules.js +2 -2
  115. package/dist/rules/herb-disable-comment-missing-rules.js.map +1 -0
  116. package/dist/{src/rules → rules}/herb-disable-comment-no-duplicate-rules.js +2 -2
  117. package/dist/rules/herb-disable-comment-no-duplicate-rules.js.map +1 -0
  118. package/dist/{src/rules → rules}/herb-disable-comment-no-redundant-all.js +2 -2
  119. package/dist/rules/herb-disable-comment-no-redundant-all.js.map +1 -0
  120. package/dist/{src/rules → rules}/herb-disable-comment-unnecessary.js +2 -2
  121. package/dist/rules/herb-disable-comment-unnecessary.js.map +1 -0
  122. package/dist/{src/rules → rules}/herb-disable-comment-valid-rule-name.js +2 -2
  123. package/dist/rules/herb-disable-comment-valid-rule-name.js.map +1 -0
  124. package/dist/rules/html-allowed-script-type.js +57 -0
  125. package/dist/rules/html-allowed-script-type.js.map +1 -0
  126. package/dist/rules/html-anchor-require-href.js +68 -0
  127. package/dist/rules/html-anchor-require-href.js.map +1 -0
  128. package/dist/{src/rules → rules}/html-aria-attribute-must-be-valid.js +3 -3
  129. package/dist/rules/html-aria-attribute-must-be-valid.js.map +1 -0
  130. package/dist/{src/rules → rules}/html-aria-label-is-well-formatted.js +3 -3
  131. package/dist/rules/html-aria-label-is-well-formatted.js.map +1 -0
  132. package/dist/{src/rules → rules}/html-aria-level-must-be-valid.js +3 -3
  133. package/dist/rules/html-aria-level-must-be-valid.js.map +1 -0
  134. package/dist/{src/rules → rules}/html-aria-role-heading-requires-level.js +5 -4
  135. package/dist/rules/html-aria-role-heading-requires-level.js.map +1 -0
  136. package/dist/{src/rules → rules}/html-aria-role-must-be-valid.js +3 -3
  137. package/dist/rules/html-aria-role-must-be-valid.js.map +1 -0
  138. package/dist/{src/rules → rules}/html-attribute-double-quotes.js +4 -4
  139. package/dist/rules/html-attribute-double-quotes.js.map +1 -0
  140. package/dist/{src/rules → rules}/html-attribute-equals-spacing.js +2 -2
  141. package/dist/rules/html-attribute-equals-spacing.js.map +1 -0
  142. package/dist/{src/rules → rules}/html-attribute-values-require-quotes.js +2 -2
  143. package/dist/rules/html-attribute-values-require-quotes.js.map +1 -0
  144. package/dist/{src/rules → rules}/html-avoid-both-disabled-and-aria-disabled.js +9 -9
  145. package/dist/rules/html-avoid-both-disabled-and-aria-disabled.js.map +1 -0
  146. package/dist/{src/rules → rules}/html-body-only-elements.js +5 -4
  147. package/dist/rules/html-body-only-elements.js.map +1 -0
  148. package/dist/{src/rules → rules}/html-boolean-attributes-no-value.js +4 -3
  149. package/dist/rules/html-boolean-attributes-no-value.js.map +1 -0
  150. package/dist/rules/html-details-has-summary.js +52 -0
  151. package/dist/rules/html-details-has-summary.js.map +1 -0
  152. package/dist/{src/rules → rules}/html-head-only-elements.js +6 -5
  153. package/dist/rules/html-head-only-elements.js.map +1 -0
  154. package/dist/{src/rules → rules}/html-iframe-has-title.js +8 -11
  155. package/dist/rules/html-iframe-has-title.js.map +1 -0
  156. package/dist/{src/rules → rules}/html-img-require-alt.js +11 -5
  157. package/dist/rules/html-img-require-alt.js.map +1 -0
  158. package/dist/{src/rules → rules}/html-input-require-autocomplete.js +7 -10
  159. package/dist/rules/html-input-require-autocomplete.js.map +1 -0
  160. package/dist/{src/rules → rules}/html-navigation-has-label.js +6 -5
  161. package/dist/rules/html-navigation-has-label.js.map +1 -0
  162. package/dist/rules/html-no-abstract-roles.js +29 -0
  163. package/dist/rules/html-no-abstract-roles.js.map +1 -0
  164. package/dist/rules/html-no-aria-hidden-on-body.js +42 -0
  165. package/dist/rules/html-no-aria-hidden-on-body.js.map +1 -0
  166. package/dist/{src/rules → rules}/html-no-aria-hidden-on-focusable.js +6 -5
  167. package/dist/rules/html-no-aria-hidden-on-focusable.js.map +1 -0
  168. package/dist/{src/rules → rules}/html-no-block-inside-inline.js +6 -9
  169. package/dist/rules/html-no-block-inside-inline.js.map +1 -0
  170. package/dist/{src/rules → rules}/html-no-duplicate-attributes.js +4 -3
  171. package/dist/rules/html-no-duplicate-attributes.js.map +1 -0
  172. package/dist/{src/rules → rules}/html-no-duplicate-ids.js +14 -11
  173. package/dist/rules/html-no-duplicate-ids.js.map +1 -0
  174. package/dist/{src/rules → rules}/html-no-duplicate-meta-names.js +22 -20
  175. package/dist/rules/html-no-duplicate-meta-names.js.map +1 -0
  176. package/dist/{src/rules → rules}/html-no-empty-attributes.js +2 -2
  177. package/dist/rules/html-no-empty-attributes.js.map +1 -0
  178. package/dist/rules/html-no-empty-headings.js +98 -0
  179. package/dist/rules/html-no-empty-headings.js.map +1 -0
  180. package/dist/{src/rules → rules}/html-no-nested-links.js +23 -15
  181. package/dist/rules/html-no-nested-links.js.map +1 -0
  182. package/dist/{src/rules → rules}/html-no-positive-tab-index.js +3 -3
  183. package/dist/rules/html-no-positive-tab-index.js.map +1 -0
  184. package/dist/{src/rules → rules}/html-no-self-closing.js +4 -4
  185. package/dist/rules/html-no-self-closing.js.map +1 -0
  186. package/dist/{src/rules → rules}/html-no-space-in-tag.js +4 -6
  187. package/dist/rules/html-no-space-in-tag.js.map +1 -0
  188. package/dist/{src/rules → rules}/html-no-title-attribute.js +6 -5
  189. package/dist/rules/html-no-title-attribute.js.map +1 -0
  190. package/dist/{src/rules → rules}/html-no-underscores-in-attribute-names.js +2 -2
  191. package/dist/rules/html-no-underscores-in-attribute-names.js.map +1 -0
  192. package/dist/rules/html-require-closing-tags.js +29 -0
  193. package/dist/rules/html-require-closing-tags.js.map +1 -0
  194. package/dist/{src/rules → rules}/html-tag-name-lowercase.js +13 -9
  195. package/dist/rules/html-tag-name-lowercase.js.map +1 -0
  196. package/dist/{src/rules → rules}/index.js +27 -4
  197. package/dist/rules/index.js.map +1 -0
  198. package/dist/{src/rules → rules}/parser-no-errors.js +3 -3
  199. package/dist/rules/parser-no-errors.js.map +1 -0
  200. package/dist/{src/rules → rules}/rule-utils.js +144 -219
  201. package/dist/rules/rule-utils.js.map +1 -0
  202. package/dist/rules/string-utils.js.map +1 -0
  203. package/dist/{src/rules → rules}/svg-tag-name-capitalization.js +7 -6
  204. package/dist/rules/svg-tag-name-capitalization.js.map +1 -0
  205. package/dist/rules/turbo-permanent-require-id.js +34 -0
  206. package/dist/rules/turbo-permanent-require-id.js.map +1 -0
  207. package/dist/{src/rules.js → rules.js} +62 -10
  208. package/dist/rules.js.map +1 -0
  209. package/dist/types/cli/argument-parser.d.ts +1 -0
  210. package/dist/types/cli/file-processor.d.ts +13 -0
  211. package/dist/types/cli/file-url.d.ts +1 -0
  212. package/dist/types/cli/index.d.ts +1 -0
  213. package/dist/types/cli/lint-worker.d.ts +34 -0
  214. package/dist/types/custom-rule-loader.d.ts +4 -0
  215. package/dist/types/index.d.ts +2 -0
  216. package/dist/types/linter.d.ts +13 -6
  217. package/dist/types/parse-cache.d.ts +9 -0
  218. package/dist/types/{src/rules/html-aria-level-must-be-valid.d.ts → rules/actionview-no-silent-helper.d.ts} +4 -3
  219. package/dist/types/rules/actionview-no-silent-render.d.ts +9 -0
  220. package/dist/types/rules/erb-comment-syntax.d.ts +1 -1
  221. package/dist/types/rules/erb-no-case-node-children.d.ts +1 -1
  222. package/dist/types/{src/rules/herb-disable-comment-malformed.d.ts → rules/erb-no-conditional-html-element.d.ts} +3 -3
  223. package/dist/types/{src/rules/erb-prefer-image-tag-helper.d.ts → rules/erb-no-conditional-open-tag.d.ts} +3 -3
  224. package/dist/types/rules/erb-no-duplicate-branch-elements.d.ts +18 -0
  225. package/dist/types/{src/rules/html-anchor-require-href.d.ts → rules/erb-no-empty-control-flow.d.ts} +2 -2
  226. package/dist/types/rules/erb-no-empty-tags.d.ts +1 -1
  227. package/dist/types/rules/erb-no-extra-newline.d.ts +1 -1
  228. package/dist/types/rules/erb-no-extra-whitespace-inside-tags.d.ts +1 -1
  229. package/dist/types/{src/rules/html-no-duplicate-attributes.d.ts → rules/erb-no-inline-case-conditions.d.ts} +4 -3
  230. package/dist/types/rules/erb-no-instance-variables-in-partials.d.ts +10 -0
  231. package/dist/types/{src/rules/html-no-aria-hidden-on-focusable.d.ts → rules/erb-no-interpolated-class-names.d.ts} +2 -2
  232. package/dist/types/{src/rules/html-aria-attribute-must-be-valid.d.ts → rules/erb-no-javascript-tag-helper.d.ts} +2 -2
  233. package/dist/types/rules/erb-no-output-control-flow.d.ts +1 -1
  234. 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
  235. package/dist/types/{src/rules/herb-disable-comment-missing-rules.d.ts → rules/erb-no-output-in-attribute-position.d.ts} +2 -2
  236. package/dist/types/{src/rules/erb-no-empty-tags.d.ts → rules/erb-no-raw-output-in-attribute-value.d.ts} +2 -2
  237. package/dist/types/{src/rules/html-no-title-attribute.d.ts → rules/erb-no-silent-statement.d.ts} +4 -3
  238. package/dist/types/rules/erb-no-silent-tag-in-attribute-name.d.ts +1 -1
  239. package/dist/types/{src/rules/html-navigation-has-label.d.ts → rules/erb-no-statement-in-script.d.ts} +2 -2
  240. package/dist/types/rules/erb-no-then-in-control-flow.d.ts +9 -0
  241. package/dist/types/rules/erb-no-trailing-whitespace.d.ts +19 -0
  242. package/dist/types/{src/rules/html-no-positive-tab-index.d.ts → rules/erb-no-unsafe-js-attribute.d.ts} +2 -2
  243. package/dist/types/{src/rules/erb-no-case-node-children.d.ts → rules/erb-no-unsafe-raw.d.ts} +2 -2
  244. package/dist/types/rules/erb-no-unsafe-script-interpolation.d.ts +9 -0
  245. package/dist/types/rules/erb-prefer-image-tag-helper.d.ts +1 -1
  246. package/dist/types/rules/erb-require-trailing-newline.d.ts +1 -1
  247. package/dist/types/rules/erb-require-whitespace-inside-tags.d.ts +1 -1
  248. package/dist/types/rules/erb-right-trim.d.ts +1 -1
  249. package/dist/types/rules/erb-strict-locals-comment-syntax.d.ts +1 -1
  250. package/dist/types/rules/erb-strict-locals-required.d.ts +1 -1
  251. package/dist/types/rules/herb-disable-comment-malformed.d.ts +1 -1
  252. package/dist/types/rules/herb-disable-comment-missing-rules.d.ts +1 -1
  253. package/dist/types/rules/herb-disable-comment-no-duplicate-rules.d.ts +1 -1
  254. package/dist/types/rules/herb-disable-comment-no-redundant-all.d.ts +1 -1
  255. package/dist/types/rules/herb-disable-comment-unnecessary.d.ts +1 -1
  256. package/dist/types/rules/herb-disable-comment-valid-rule-name.d.ts +1 -1
  257. package/dist/types/{src/rules/html-no-empty-attributes.d.ts → rules/html-allowed-script-type.d.ts} +2 -2
  258. package/dist/types/rules/html-anchor-require-href.d.ts +3 -2
  259. package/dist/types/rules/html-aria-attribute-must-be-valid.d.ts +1 -1
  260. package/dist/types/rules/html-aria-label-is-well-formatted.d.ts +1 -1
  261. package/dist/types/rules/html-aria-level-must-be-valid.d.ts +1 -1
  262. package/dist/types/rules/html-aria-role-heading-requires-level.d.ts +1 -1
  263. package/dist/types/rules/html-aria-role-must-be-valid.d.ts +1 -1
  264. package/dist/types/rules/html-attribute-double-quotes.d.ts +1 -1
  265. package/dist/types/rules/html-attribute-equals-spacing.d.ts +1 -1
  266. package/dist/types/rules/html-attribute-values-require-quotes.d.ts +1 -1
  267. package/dist/types/rules/html-avoid-both-disabled-and-aria-disabled.d.ts +1 -1
  268. package/dist/types/rules/html-body-only-elements.d.ts +1 -1
  269. package/dist/types/rules/html-boolean-attributes-no-value.d.ts +1 -1
  270. package/dist/types/rules/html-details-has-summary.d.ts +9 -0
  271. package/dist/types/rules/html-head-only-elements.d.ts +1 -1
  272. package/dist/types/rules/html-iframe-has-title.d.ts +1 -1
  273. package/dist/types/rules/html-img-require-alt.d.ts +1 -1
  274. package/dist/types/rules/html-input-require-autocomplete.d.ts +1 -1
  275. package/dist/types/rules/html-navigation-has-label.d.ts +1 -1
  276. package/dist/types/{src/rules/html-no-empty-headings.d.ts → rules/html-no-abstract-roles.d.ts} +2 -2
  277. package/dist/types/{src/rules/erb-no-output-control-flow.d.ts → rules/html-no-aria-hidden-on-body.d.ts} +3 -3
  278. package/dist/types/rules/html-no-aria-hidden-on-focusable.d.ts +1 -1
  279. package/dist/types/rules/html-no-block-inside-inline.d.ts +1 -1
  280. package/dist/types/rules/html-no-duplicate-attributes.d.ts +1 -1
  281. package/dist/types/rules/html-no-duplicate-ids.d.ts +1 -1
  282. package/dist/types/rules/html-no-duplicate-meta-names.d.ts +1 -1
  283. package/dist/types/rules/html-no-empty-attributes.d.ts +1 -1
  284. package/dist/types/rules/html-no-empty-headings.d.ts +1 -1
  285. package/dist/types/rules/html-no-nested-links.d.ts +1 -1
  286. package/dist/types/rules/html-no-positive-tab-index.d.ts +1 -1
  287. package/dist/types/rules/html-no-self-closing.d.ts +1 -1
  288. package/dist/types/rules/html-no-space-in-tag.d.ts +1 -1
  289. package/dist/types/rules/html-no-title-attribute.d.ts +1 -1
  290. package/dist/types/rules/html-no-underscores-in-attribute-names.d.ts +1 -1
  291. package/dist/types/{src/rules/html-body-only-elements.d.ts → rules/html-require-closing-tags.d.ts} +4 -3
  292. package/dist/types/rules/html-tag-name-lowercase.d.ts +1 -1
  293. package/dist/types/rules/index.d.ts +27 -4
  294. package/dist/types/rules/parser-no-errors.d.ts +1 -1
  295. package/dist/types/rules/rule-utils.d.ts +36 -88
  296. package/dist/types/rules/svg-tag-name-capitalization.d.ts +1 -1
  297. package/dist/types/{src/rules/html-aria-role-must-be-valid.d.ts → rules/turbo-permanent-require-id.d.ts} +2 -2
  298. package/dist/types/types.d.ts +26 -7
  299. package/dist/types/urls.d.ts +1 -0
  300. package/dist/{src/types.js → types.js} +56 -0
  301. package/dist/types.js.map +1 -0
  302. package/dist/urls.js +5 -0
  303. package/dist/urls.js.map +1 -0
  304. package/docs/rules/README.md +26 -2
  305. package/docs/rules/actionview-no-silent-helper.md +57 -0
  306. package/docs/rules/actionview-no-silent-render.md +47 -0
  307. package/docs/rules/erb-no-conditional-html-element.md +90 -0
  308. package/docs/rules/erb-no-conditional-open-tag.md +130 -0
  309. package/docs/rules/erb-no-duplicate-branch-elements.md +98 -0
  310. package/docs/rules/erb-no-empty-control-flow.md +83 -0
  311. package/docs/rules/erb-no-inline-case-conditions.md +85 -0
  312. package/docs/rules/erb-no-instance-variables-in-partials.md +43 -0
  313. package/docs/rules/erb-no-interpolated-class-names.md +57 -0
  314. package/docs/rules/erb-no-javascript-tag-helper.md +33 -0
  315. package/docs/rules/erb-no-output-in-attribute-name.md +38 -0
  316. package/docs/rules/erb-no-output-in-attribute-position.md +60 -0
  317. package/docs/rules/erb-no-raw-output-in-attribute-value.md +37 -0
  318. package/docs/rules/erb-no-silent-statement.md +53 -0
  319. package/docs/rules/erb-no-statement-in-script.md +68 -0
  320. package/docs/rules/erb-no-then-in-control-flow.md +86 -0
  321. package/docs/rules/erb-no-trailing-whitespace.md +69 -0
  322. package/docs/rules/erb-no-unsafe-js-attribute.md +41 -0
  323. package/docs/rules/erb-no-unsafe-raw.md +47 -0
  324. package/docs/rules/erb-no-unsafe-script-interpolation.md +140 -0
  325. package/docs/rules/html-allowed-script-type.md +59 -0
  326. package/docs/rules/html-anchor-require-href.md +19 -6
  327. package/docs/rules/html-details-has-summary.md +46 -0
  328. package/docs/rules/html-img-require-alt.md +5 -3
  329. package/docs/rules/html-no-abstract-roles.md +74 -0
  330. package/docs/rules/html-no-aria-hidden-on-body.md +44 -0
  331. package/docs/rules/html-require-closing-tags.md +142 -0
  332. package/docs/rules/parser-no-errors.md +4 -17
  333. package/docs/rules/turbo-permanent-require-id.md +41 -0
  334. package/package.json +11 -10
  335. package/src/cli/argument-parser.ts +20 -2
  336. package/src/cli/file-processor.ts +189 -10
  337. package/src/cli/file-url.ts +6 -0
  338. package/src/cli/formatters/detailed-formatter.ts +19 -21
  339. package/src/cli/formatters/simple-formatter.ts +23 -13
  340. package/src/cli/index.ts +2 -0
  341. package/src/cli/lint-worker.ts +208 -0
  342. package/src/cli/summary-reporter.ts +14 -15
  343. package/src/cli.ts +5 -3
  344. package/src/custom-rule-loader.ts +20 -5
  345. package/src/herb-disable-comment-utils.ts +0 -3
  346. package/src/index.ts +22 -0
  347. package/src/linter.ts +98 -79
  348. package/src/parse-cache.ts +39 -0
  349. package/src/rules/actionview-no-silent-helper.ts +58 -0
  350. package/src/rules/actionview-no-silent-render.ts +44 -0
  351. package/src/rules/erb-comment-syntax.ts +2 -2
  352. package/src/rules/erb-no-case-node-children.ts +5 -3
  353. package/src/rules/erb-no-conditional-html-element.ts +53 -0
  354. package/src/rules/erb-no-conditional-open-tag.ts +37 -0
  355. package/src/rules/erb-no-duplicate-branch-elements.ts +436 -0
  356. package/src/rules/erb-no-empty-control-flow.ts +255 -0
  357. package/src/rules/erb-no-empty-tags.ts +2 -2
  358. package/src/rules/erb-no-extra-newline.ts +5 -25
  359. package/src/rules/erb-no-extra-whitespace-inside-tags.ts +45 -15
  360. package/src/rules/erb-no-inline-case-conditions.ts +54 -0
  361. package/src/rules/erb-no-instance-variables-in-partials.ts +101 -0
  362. package/src/rules/erb-no-interpolated-class-names.ts +65 -0
  363. package/src/rules/erb-no-javascript-tag-helper.ts +47 -0
  364. package/src/rules/erb-no-output-control-flow.ts +10 -10
  365. package/src/rules/erb-no-output-in-attribute-name.ts +39 -0
  366. package/src/rules/erb-no-output-in-attribute-position.ts +39 -0
  367. package/src/rules/erb-no-raw-output-in-attribute-value.ts +47 -0
  368. package/src/rules/erb-no-silent-statement.ts +58 -0
  369. package/src/rules/erb-no-silent-tag-in-attribute-name.ts +2 -2
  370. package/src/rules/erb-no-statement-in-script.ts +82 -0
  371. package/src/rules/erb-no-then-in-control-flow.ts +62 -0
  372. package/src/rules/erb-no-trailing-whitespace.ts +187 -0
  373. package/src/rules/erb-no-unsafe-js-attribute.ts +47 -0
  374. package/src/rules/erb-no-unsafe-raw.ts +83 -0
  375. package/src/rules/erb-no-unsafe-script-interpolation.ts +122 -0
  376. package/src/rules/erb-prefer-image-tag-helper.ts +5 -4
  377. package/src/rules/erb-require-trailing-newline.ts +2 -2
  378. package/src/rules/erb-require-whitespace-inside-tags.ts +42 -18
  379. package/src/rules/erb-right-trim.ts +2 -2
  380. package/src/rules/erb-strict-locals-comment-syntax.ts +4 -4
  381. package/src/rules/erb-strict-locals-required.ts +2 -2
  382. package/src/rules/herb-disable-comment-malformed.ts +2 -2
  383. package/src/rules/herb-disable-comment-missing-rules.ts +2 -2
  384. package/src/rules/herb-disable-comment-no-duplicate-rules.ts +2 -2
  385. package/src/rules/herb-disable-comment-no-redundant-all.ts +2 -2
  386. package/src/rules/herb-disable-comment-unnecessary.ts +2 -2
  387. package/src/rules/herb-disable-comment-valid-rule-name.ts +2 -2
  388. package/src/rules/html-allowed-script-type.ts +84 -0
  389. package/src/rules/html-anchor-require-href.ts +73 -11
  390. package/src/rules/html-aria-attribute-must-be-valid.ts +3 -3
  391. package/src/rules/html-aria-label-is-well-formatted.ts +3 -3
  392. package/src/rules/html-aria-level-must-be-valid.ts +3 -3
  393. package/src/rules/html-aria-role-heading-requires-level.ts +5 -4
  394. package/src/rules/html-aria-role-must-be-valid.ts +3 -3
  395. package/src/rules/html-attribute-double-quotes.ts +4 -4
  396. package/src/rules/html-attribute-equals-spacing.ts +2 -2
  397. package/src/rules/html-attribute-values-require-quotes.ts +2 -2
  398. package/src/rules/html-avoid-both-disabled-and-aria-disabled.ts +10 -11
  399. package/src/rules/html-body-only-elements.ts +5 -4
  400. package/src/rules/html-boolean-attributes-no-value.ts +4 -3
  401. package/src/rules/html-details-has-summary.ts +69 -0
  402. package/src/rules/html-head-only-elements.ts +6 -5
  403. package/src/rules/html-iframe-has-title.ts +8 -11
  404. package/src/rules/html-img-require-alt.ts +16 -5
  405. package/src/rules/html-input-require-autocomplete.ts +7 -10
  406. package/src/rules/html-navigation-has-label.ts +6 -5
  407. package/src/rules/html-no-abstract-roles.ts +40 -0
  408. package/src/rules/html-no-aria-hidden-on-body.ts +58 -0
  409. package/src/rules/html-no-aria-hidden-on-focusable.ts +6 -5
  410. package/src/rules/html-no-block-inside-inline.ts +7 -13
  411. package/src/rules/html-no-duplicate-attributes.ts +4 -3
  412. package/src/rules/html-no-duplicate-ids.ts +16 -13
  413. package/src/rules/html-no-duplicate-meta-names.ts +20 -19
  414. package/src/rules/html-no-empty-attributes.ts +2 -2
  415. package/src/rules/html-no-empty-headings.ts +44 -58
  416. package/src/rules/html-no-nested-links.ts +25 -16
  417. package/src/rules/html-no-positive-tab-index.ts +3 -3
  418. package/src/rules/html-no-self-closing.ts +5 -5
  419. package/src/rules/html-no-space-in-tag.ts +5 -8
  420. package/src/rules/html-no-title-attribute.ts +6 -5
  421. package/src/rules/html-no-underscores-in-attribute-names.ts +2 -2
  422. package/src/rules/html-require-closing-tags.ts +41 -0
  423. package/src/rules/html-tag-name-lowercase.ts +14 -9
  424. package/src/rules/index.ts +28 -4
  425. package/src/rules/parser-no-errors.ts +3 -3
  426. package/src/rules/rule-utils.ts +166 -279
  427. package/src/rules/svg-tag-name-capitalization.ts +10 -10
  428. package/src/rules/turbo-permanent-require-id.ts +49 -0
  429. package/src/rules.ts +66 -10
  430. package/src/types.ts +80 -7
  431. package/src/urls.ts +5 -0
  432. package/dist/package.json +0 -65
  433. package/dist/src/cli/argument-parser.js.map +0 -1
  434. package/dist/src/cli/file-processor.js.map +0 -1
  435. package/dist/src/cli/formatters/base-formatter.js.map +0 -1
  436. package/dist/src/cli/formatters/detailed-formatter.js.map +0 -1
  437. package/dist/src/cli/formatters/github-actions-formatter.js.map +0 -1
  438. package/dist/src/cli/formatters/index.js.map +0 -1
  439. package/dist/src/cli/formatters/json-formatter.js.map +0 -1
  440. package/dist/src/cli/formatters/simple-formatter.js +0 -44
  441. package/dist/src/cli/formatters/simple-formatter.js.map +0 -1
  442. package/dist/src/cli/index.js.map +0 -1
  443. package/dist/src/cli/output-manager.js.map +0 -1
  444. package/dist/src/cli/summary-reporter.js.map +0 -1
  445. package/dist/src/cli.js.map +0 -1
  446. package/dist/src/custom-rule-loader.js.map +0 -1
  447. package/dist/src/herb-disable-comment-utils.js.map +0 -1
  448. package/dist/src/herb-lint.js +0 -5
  449. package/dist/src/herb-lint.js.map +0 -1
  450. package/dist/src/index.js +0 -5
  451. package/dist/src/index.js.map +0 -1
  452. package/dist/src/linter-ignore.js.map +0 -1
  453. package/dist/src/linter.js.map +0 -1
  454. package/dist/src/loader.js +0 -17
  455. package/dist/src/loader.js.map +0 -1
  456. package/dist/src/rules/erb-comment-syntax.js.map +0 -1
  457. package/dist/src/rules/erb-no-case-node-children.js.map +0 -1
  458. package/dist/src/rules/erb-no-empty-tags.js.map +0 -1
  459. package/dist/src/rules/erb-no-extra-newline.js.map +0 -1
  460. package/dist/src/rules/erb-no-extra-whitespace-inside-tags.js.map +0 -1
  461. package/dist/src/rules/erb-no-output-control-flow.js.map +0 -1
  462. package/dist/src/rules/erb-no-silent-tag-in-attribute-name.js.map +0 -1
  463. package/dist/src/rules/erb-prefer-image-tag-helper.js.map +0 -1
  464. package/dist/src/rules/erb-require-trailing-newline.js.map +0 -1
  465. package/dist/src/rules/erb-require-whitespace-inside-tags.js.map +0 -1
  466. package/dist/src/rules/erb-right-trim.js.map +0 -1
  467. package/dist/src/rules/erb-strict-locals-comment-syntax.js.map +0 -1
  468. package/dist/src/rules/erb-strict-locals-required.js.map +0 -1
  469. package/dist/src/rules/file-utils.js.map +0 -1
  470. package/dist/src/rules/herb-disable-comment-base.js.map +0 -1
  471. package/dist/src/rules/herb-disable-comment-malformed.js.map +0 -1
  472. package/dist/src/rules/herb-disable-comment-missing-rules.js.map +0 -1
  473. package/dist/src/rules/herb-disable-comment-no-duplicate-rules.js.map +0 -1
  474. package/dist/src/rules/herb-disable-comment-no-redundant-all.js.map +0 -1
  475. package/dist/src/rules/herb-disable-comment-unnecessary.js.map +0 -1
  476. package/dist/src/rules/herb-disable-comment-valid-rule-name.js.map +0 -1
  477. package/dist/src/rules/html-anchor-require-href.js +0 -32
  478. package/dist/src/rules/html-anchor-require-href.js.map +0 -1
  479. package/dist/src/rules/html-aria-attribute-must-be-valid.js.map +0 -1
  480. package/dist/src/rules/html-aria-label-is-well-formatted.js.map +0 -1
  481. package/dist/src/rules/html-aria-level-must-be-valid.js.map +0 -1
  482. package/dist/src/rules/html-aria-role-heading-requires-level.js.map +0 -1
  483. package/dist/src/rules/html-aria-role-must-be-valid.js.map +0 -1
  484. package/dist/src/rules/html-attribute-double-quotes.js.map +0 -1
  485. package/dist/src/rules/html-attribute-equals-spacing.js.map +0 -1
  486. package/dist/src/rules/html-attribute-values-require-quotes.js.map +0 -1
  487. package/dist/src/rules/html-avoid-both-disabled-and-aria-disabled.js.map +0 -1
  488. package/dist/src/rules/html-body-only-elements.js.map +0 -1
  489. package/dist/src/rules/html-boolean-attributes-no-value.js.map +0 -1
  490. package/dist/src/rules/html-head-only-elements.js.map +0 -1
  491. package/dist/src/rules/html-iframe-has-title.js.map +0 -1
  492. package/dist/src/rules/html-img-require-alt.js.map +0 -1
  493. package/dist/src/rules/html-input-require-autocomplete.js.map +0 -1
  494. package/dist/src/rules/html-navigation-has-label.js.map +0 -1
  495. package/dist/src/rules/html-no-aria-hidden-on-focusable.js.map +0 -1
  496. package/dist/src/rules/html-no-block-inside-inline.js.map +0 -1
  497. package/dist/src/rules/html-no-duplicate-attributes.js.map +0 -1
  498. package/dist/src/rules/html-no-duplicate-ids.js.map +0 -1
  499. package/dist/src/rules/html-no-duplicate-meta-names.js.map +0 -1
  500. package/dist/src/rules/html-no-empty-attributes.js.map +0 -1
  501. package/dist/src/rules/html-no-empty-headings.js +0 -115
  502. package/dist/src/rules/html-no-empty-headings.js.map +0 -1
  503. package/dist/src/rules/html-no-nested-links.js.map +0 -1
  504. package/dist/src/rules/html-no-positive-tab-index.js.map +0 -1
  505. package/dist/src/rules/html-no-self-closing.js.map +0 -1
  506. package/dist/src/rules/html-no-space-in-tag.js.map +0 -1
  507. package/dist/src/rules/html-no-title-attribute.js.map +0 -1
  508. package/dist/src/rules/html-no-underscores-in-attribute-names.js.map +0 -1
  509. package/dist/src/rules/html-tag-name-lowercase.js.map +0 -1
  510. package/dist/src/rules/index.js.map +0 -1
  511. package/dist/src/rules/parser-no-errors.js.map +0 -1
  512. package/dist/src/rules/rule-utils.js.map +0 -1
  513. package/dist/src/rules/string-utils.js.map +0 -1
  514. package/dist/src/rules/svg-tag-name-capitalization.js.map +0 -1
  515. package/dist/src/rules.js.map +0 -1
  516. package/dist/src/types.js.map +0 -1
  517. package/dist/tsconfig.tsbuildinfo +0 -1
  518. package/dist/types/src/cli/argument-parser.d.ts +0 -25
  519. package/dist/types/src/cli/file-processor.d.ts +0 -43
  520. package/dist/types/src/cli/formatters/base-formatter.d.ts +0 -6
  521. package/dist/types/src/cli/formatters/detailed-formatter.d.ts +0 -13
  522. package/dist/types/src/cli/formatters/github-actions-formatter.d.ts +0 -17
  523. package/dist/types/src/cli/formatters/index.d.ts +0 -5
  524. package/dist/types/src/cli/formatters/json-formatter.d.ts +0 -48
  525. package/dist/types/src/cli/formatters/simple-formatter.d.ts +0 -8
  526. package/dist/types/src/cli/index.d.ts +0 -5
  527. package/dist/types/src/cli/output-manager.d.ts +0 -32
  528. package/dist/types/src/cli/summary-reporter.d.ts +0 -28
  529. package/dist/types/src/cli.d.ts +0 -28
  530. package/dist/types/src/custom-rule-loader.d.ts +0 -62
  531. package/dist/types/src/herb-disable-comment-utils.d.ts +0 -69
  532. package/dist/types/src/herb-lint.d.ts +0 -2
  533. package/dist/types/src/index.d.ts +0 -4
  534. package/dist/types/src/linter-ignore.d.ts +0 -12
  535. package/dist/types/src/linter.d.ts +0 -133
  536. package/dist/types/src/loader.d.ts +0 -20
  537. package/dist/types/src/rules/erb-comment-syntax.d.ts +0 -14
  538. package/dist/types/src/rules/erb-no-extra-newline.d.ts +0 -14
  539. package/dist/types/src/rules/erb-no-extra-whitespace-inside-tags.d.ts +0 -18
  540. package/dist/types/src/rules/erb-require-trailing-newline.d.ts +0 -9
  541. package/dist/types/src/rules/erb-require-whitespace-inside-tags.d.ts +0 -18
  542. package/dist/types/src/rules/erb-right-trim.d.ts +0 -14
  543. package/dist/types/src/rules/erb-strict-locals-comment-syntax.d.ts +0 -9
  544. package/dist/types/src/rules/erb-strict-locals-required.d.ts +0 -9
  545. package/dist/types/src/rules/file-utils.d.ts +0 -13
  546. package/dist/types/src/rules/herb-disable-comment-base.d.ts +0 -37
  547. package/dist/types/src/rules/herb-disable-comment-no-duplicate-rules.d.ts +0 -8
  548. package/dist/types/src/rules/herb-disable-comment-no-redundant-all.d.ts +0 -8
  549. package/dist/types/src/rules/herb-disable-comment-unnecessary.d.ts +0 -8
  550. package/dist/types/src/rules/herb-disable-comment-valid-rule-name.d.ts +0 -8
  551. package/dist/types/src/rules/html-aria-label-is-well-formatted.d.ts +0 -8
  552. package/dist/types/src/rules/html-aria-role-heading-requires-level.d.ts +0 -8
  553. package/dist/types/src/rules/html-attribute-double-quotes.d.ts +0 -15
  554. package/dist/types/src/rules/html-attribute-equals-spacing.d.ts +0 -14
  555. package/dist/types/src/rules/html-attribute-values-require-quotes.d.ts +0 -15
  556. package/dist/types/src/rules/html-avoid-both-disabled-and-aria-disabled.d.ts +0 -8
  557. package/dist/types/src/rules/html-boolean-attributes-no-value.d.ts +0 -14
  558. package/dist/types/src/rules/html-head-only-elements.d.ts +0 -9
  559. package/dist/types/src/rules/html-iframe-has-title.d.ts +0 -8
  560. package/dist/types/src/rules/html-img-require-alt.d.ts +0 -8
  561. package/dist/types/src/rules/html-input-require-autocomplete.d.ts +0 -8
  562. package/dist/types/src/rules/html-no-block-inside-inline.d.ts +0 -8
  563. package/dist/types/src/rules/html-no-duplicate-ids.d.ts +0 -8
  564. package/dist/types/src/rules/html-no-duplicate-meta-names.d.ts +0 -9
  565. package/dist/types/src/rules/html-no-nested-links.d.ts +0 -8
  566. package/dist/types/src/rules/html-no-self-closing.d.ts +0 -16
  567. package/dist/types/src/rules/html-no-space-in-tag.d.ts +0 -16
  568. package/dist/types/src/rules/html-no-underscores-in-attribute-names.d.ts +0 -8
  569. package/dist/types/src/rules/html-tag-name-lowercase.d.ts +0 -18
  570. package/dist/types/src/rules/index.d.ts +0 -54
  571. package/dist/types/src/rules/parser-no-errors.d.ts +0 -9
  572. package/dist/types/src/rules/rule-utils.d.ts +0 -351
  573. package/dist/types/src/rules/string-utils.d.ts +0 -15
  574. package/dist/types/src/rules/svg-tag-name-capitalization.d.ts +0 -16
  575. package/dist/types/src/rules.d.ts +0 -2
  576. package/dist/types/src/types.d.ts +0 -190
  577. /package/dist/{src/cli → cli}/formatters/base-formatter.js +0 -0
  578. /package/dist/{src/cli → cli}/formatters/github-actions-formatter.js +0 -0
  579. /package/dist/{src/cli → cli}/formatters/index.js +0 -0
  580. /package/dist/{src/cli → cli}/formatters/json-formatter.js +0 -0
  581. /package/dist/{src/cli → cli}/index.js +0 -0
  582. /package/dist/{src/cli → cli}/output-manager.js +0 -0
  583. /package/dist/{src/herb-disable-comment-utils.js → herb-disable-comment-utils.js} +0 -0
  584. /package/dist/{src/linter-ignore.js → linter-ignore.js} +0 -0
  585. /package/dist/{src/rules → rules}/file-utils.js +0 -0
  586. /package/dist/{src/rules → rules}/herb-disable-comment-base.js +0 -0
  587. /package/dist/{src/rules → rules}/string-utils.js +0 -0
@@ -3,13 +3,17 @@ import { Linter } from "../linter.js"
3
3
  import { loadCustomRules } from "../loader.js"
4
4
  import { Config } from "@herb-tools/config"
5
5
 
6
- import { readFileSync, writeFileSync } from "fs"
7
- import { resolve } from "path"
6
+ import { Worker } from "node:worker_threads"
7
+ import { readFileSync, writeFileSync } from "node:fs"
8
+ import { resolve, dirname, join } from "node:path"
9
+ import { fileURLToPath } from "node:url"
10
+ import { availableParallelism } from "node:os"
8
11
  import { colorize } from "@herb-tools/highlighter"
9
12
 
10
13
  import type { Diagnostic } from "@herb-tools/core"
11
14
  import type { FormatOption } from "./argument-parser.js"
12
15
  import type { HerbConfigOptions } from "@herb-tools/config"
16
+ import type { WorkerInput, WorkerResult } from "./lint-worker.js"
13
17
 
14
18
  export interface ProcessedFile {
15
19
  filename: string
@@ -20,6 +24,7 @@ export interface ProcessedFile {
20
24
 
21
25
  export interface ProcessingContext {
22
26
  projectPath?: string
27
+ configPath?: string
23
28
  pattern?: string
24
29
  fix?: boolean
25
30
  fixUnsafe?: boolean
@@ -27,6 +32,7 @@ export interface ProcessingContext {
27
32
  linterConfig?: HerbConfigOptions['linter']
28
33
  config?: Config
29
34
  loadCustomRules?: boolean
35
+ jobs?: number
30
36
  }
31
37
 
32
38
  export interface ProcessingResult {
@@ -44,6 +50,13 @@ export interface ProcessingResult {
44
50
  context?: ProcessingContext
45
51
  }
46
52
 
53
+ /**
54
+ * Minimum number of files required to use parallel processing.
55
+ * Below this threshold, sequential processing is faster due to
56
+ * worker thread startup overhead (loading WASM, config, etc.).
57
+ */
58
+ const PARALLEL_FILE_THRESHOLD = 10
59
+
47
60
  export class FileProcessor {
48
61
  private linter: Linter | null = null
49
62
  private customRulesLoaded: boolean = false
@@ -51,18 +64,27 @@ export class FileProcessor {
51
64
  private isRuleAutocorrectable(ruleName: string): boolean {
52
65
  if (!this.linter) return false
53
66
 
54
- const RuleClass = (this.linter as any).rules.find((rule: any) => {
55
- const instance = new rule()
56
-
57
- return instance.name === ruleName
58
- })
67
+ const ruleClass = (this.linter as any).rules.find(
68
+ (rule: any) => rule.ruleName === ruleName
69
+ )
59
70
 
60
- if (!RuleClass) return false
71
+ if (!ruleClass) return false
61
72
 
62
- return RuleClass.autocorrectable === true
73
+ return ruleClass.autocorrectable === true
63
74
  }
64
75
 
65
76
  async processFiles(files: string[], formatOption: FormatOption = 'detailed', context?: ProcessingContext): Promise<ProcessingResult> {
77
+ const jobs = context?.jobs ?? 1
78
+ const shouldParallelize = jobs > 1 && files.length >= PARALLEL_FILE_THRESHOLD
79
+
80
+ if (shouldParallelize) {
81
+ return this.processFilesInParallel(files, jobs, formatOption, context)
82
+ }
83
+
84
+ return this.processFilesSequentially(files, formatOption, context)
85
+ }
86
+
87
+ private async processFilesSequentially(files: string[], formatOption: FormatOption = 'detailed', context?: ProcessingContext): Promise<ProcessingResult> {
66
88
  let totalErrors = 0
67
89
  let totalWarnings = 0
68
90
  let totalInfo = 0
@@ -147,7 +169,7 @@ export class FileProcessor {
147
169
  filesFixed++
148
170
 
149
171
  if (formatOption !== 'json') {
150
- console.log(`${colorize("✓", "brightGreen")} ${colorize(filename, "cyan")} - ${colorize(`Fixed ${autofixResult.fixed.length} offense(s)`, "green")}`)
172
+ console.log(`${colorize("✓", "brightGreen")} ${colorize(filename, "cyan")} - ${colorize(`Fixed ${autofixResult.fixed.length} ${autofixResult.fixed.length === 1 ? "offense" : "offenses"}`, "green")}`)
151
173
  }
152
174
  }
153
175
 
@@ -225,4 +247,161 @@ export class FileProcessor {
225
247
 
226
248
  return result
227
249
  }
250
+
251
+ private async processFilesInParallel(files: string[], jobs: number, formatOption: FormatOption, context?: ProcessingContext): Promise<ProcessingResult> {
252
+ const workerCount = Math.min(jobs, files.length)
253
+ const chunks = this.splitIntoChunks(files, workerCount)
254
+ const workerPath = this.resolveWorkerPath()
255
+
256
+ const workerPromises = chunks.map(chunk => this.runWorker(workerPath, chunk, context))
257
+ const workerResults = await Promise.all(workerPromises)
258
+
259
+ for (const result of workerResults) {
260
+ if (result.error) {
261
+ throw new Error(`Worker error: ${result.error}`)
262
+ }
263
+ }
264
+
265
+ return this.aggregateWorkerResults(workerResults, formatOption, context)
266
+ }
267
+
268
+ private resolveWorkerPath(): string {
269
+ try {
270
+ const currentDir = dirname(fileURLToPath(import.meta.url))
271
+
272
+ return join(currentDir, "lint-worker.js")
273
+ } catch {
274
+ return join(__dirname, "lint-worker.js")
275
+ }
276
+ }
277
+
278
+ private splitIntoChunks(files: string[], chunkCount: number): string[][] {
279
+ const chunks: string[][] = Array.from({ length: chunkCount }, () => [])
280
+
281
+ for (let i = 0; i < files.length; i++) {
282
+ chunks[i % chunkCount].push(files[i])
283
+ }
284
+
285
+ return chunks.filter(chunk => chunk.length > 0)
286
+ }
287
+
288
+ private runWorker(workerPath: string, files: string[], context?: ProcessingContext): Promise<WorkerResult> {
289
+ return new Promise((resolve, reject) => {
290
+ const workerData: WorkerInput = {
291
+ files,
292
+ projectPath: context?.projectPath || process.cwd(),
293
+ configPath: context?.configPath,
294
+ fix: context?.fix || false,
295
+ fixUnsafe: context?.fixUnsafe || false,
296
+ ignoreDisableComments: context?.ignoreDisableComments || false,
297
+ loadCustomRules: context?.loadCustomRules || false,
298
+ }
299
+
300
+ const worker = new Worker(workerPath, { workerData })
301
+
302
+ worker.on("message", (result: WorkerResult) => {
303
+ resolve(result)
304
+ })
305
+
306
+ worker.on("error", (error) => {
307
+ reject(error)
308
+ })
309
+
310
+ worker.on("exit", (code) => {
311
+ if (code !== 0) {
312
+ reject(new Error(`Worker exited with code ${code}`))
313
+ }
314
+ })
315
+ })
316
+ }
317
+
318
+ private aggregateWorkerResults(results: WorkerResult[], formatOption: FormatOption, context?: ProcessingContext): ProcessingResult {
319
+ let totalErrors = 0
320
+ let totalWarnings = 0
321
+ let totalInfo = 0
322
+ let totalHints = 0
323
+ let totalIgnored = 0
324
+ let totalWouldBeIgnored = 0
325
+ let filesWithOffenses = 0
326
+ let filesFixed = 0
327
+ let ruleCount = 0
328
+
329
+ const allOffenses: ProcessedFile[] = []
330
+ const ruleOffenses = new Map<string, { count: number, files: Set<string> }>()
331
+
332
+ for (const result of results) {
333
+ totalErrors += result.totalErrors
334
+ totalWarnings += result.totalWarnings
335
+ totalInfo += result.totalInfo
336
+ totalHints += result.totalHints
337
+ totalIgnored += result.totalIgnored
338
+ totalWouldBeIgnored += result.totalWouldBeIgnored
339
+ filesWithOffenses += result.filesWithOffenses
340
+ filesFixed += result.filesFixed
341
+
342
+ if (result.ruleCount > 0) {
343
+ ruleCount = result.ruleCount
344
+ }
345
+
346
+ for (const offense of result.offenses) {
347
+ allOffenses.push({
348
+ filename: offense.filename,
349
+ offense: offense.offense,
350
+ content: offense.content,
351
+ autocorrectable: offense.autocorrectable
352
+ })
353
+ }
354
+
355
+ for (const [rule, data] of result.ruleOffenses) {
356
+ const existing = ruleOffenses.get(rule) || { count: 0, files: new Set<string>() }
357
+ existing.count += data.count
358
+
359
+ for (const file of data.files) {
360
+ existing.files.add(file)
361
+ }
362
+
363
+ ruleOffenses.set(rule, existing)
364
+ }
365
+
366
+ if (formatOption !== 'json') {
367
+ for (const fixMessage of result.fixMessages) {
368
+ const [filename, countStr] = fixMessage.split("\t")
369
+ const count = parseInt(countStr, 10)
370
+ console.log(`${colorize("\u2713", "brightGreen")} ${colorize(filename, "cyan")} - ${colorize(`Fixed ${count} ${count === 1 ? "offense" : "offenses"}`, "green")}`)
371
+ }
372
+ }
373
+ }
374
+
375
+ const processingResult: ProcessingResult = {
376
+ totalErrors,
377
+ totalWarnings,
378
+ totalInfo,
379
+ totalHints,
380
+ totalIgnored,
381
+ filesWithOffenses,
382
+ filesFixed,
383
+ ruleCount,
384
+ allOffenses,
385
+ ruleOffenses,
386
+ context
387
+ }
388
+
389
+ if (totalWouldBeIgnored > 0) {
390
+ processingResult.totalWouldBeIgnored = totalWouldBeIgnored
391
+ }
392
+
393
+ return processingResult
394
+ }
395
+
396
+ /**
397
+ * Returns the default number of parallel jobs based on available CPU cores.
398
+ * Returns 1 if parallelism detection fails.
399
+ */
400
+ static defaultJobs(): number {
401
+ try {
402
+ return availableParallelism()
403
+ } catch {
404
+ return 1
405
+ }
406
+ }
228
407
  }
@@ -0,0 +1,6 @@
1
+ import { resolve } from "node:path"
2
+
3
+ export function fileUrl(filePath: string): string {
4
+ const absolutePath = resolve(filePath)
5
+ return `file://${absolutePath}`
6
+ }
@@ -2,6 +2,8 @@ import { colorize, Highlighter, type ThemeInput, DEFAULT_THEME } from "@herb-too
2
2
 
3
3
  import { BaseFormatter } from "./base-formatter.js"
4
4
  import { LineWrapper } from "@herb-tools/highlighter"
5
+ import { ruleDocumentationUrl } from "../../urls.js"
6
+ import { fileUrl } from "../file-url.js"
5
7
 
6
8
  import type { Diagnostic } from "@herb-tools/core"
7
9
  import type { ProcessedFile } from "../file-processor.js"
@@ -27,24 +29,24 @@ export class DetailedFormatter extends BaseFormatter {
27
29
  await this.highlighter.initialize()
28
30
  }
29
31
 
32
+ const correctableTag = colorize(colorize("[Correctable]", "green"), "bold")
33
+ const autocorrectableSet = new Set(
34
+ allOffenses.filter(item => item.autocorrectable).map(item => item.offense)
35
+ )
36
+
30
37
  if (isSingleFile) {
31
38
  const { filename, content } = allOffenses[0]
32
- const diagnostics = allOffenses.map(item => {
33
- if (item.autocorrectable && item.offense.code) {
34
- return {
35
- ...item.offense,
36
- message: `${item.offense.message} ${colorize(colorize("[Correctable]", "green"), "bold")}`
37
- }
38
- }
39
- return item.offense
40
- })
39
+ const diagnostics = allOffenses.map(item => item.offense)
41
40
 
42
41
  const highlighted = this.highlighter.highlight(filename, content, {
43
42
  diagnostics: diagnostics,
44
43
  splitDiagnostics: true,
45
44
  contextLines: 2,
46
45
  wrapLines: this.wrapLines,
47
- truncateLines: this.truncateLines
46
+ truncateLines: this.truncateLines,
47
+ codeUrlBuilder: ruleDocumentationUrl,
48
+ fileUrlBuilder: (path) => fileUrl(path),
49
+ suffixBuilder: (diagnostic) => autocorrectableSet.has(diagnostic) ? correctableTag : undefined,
48
50
  })
49
51
 
50
52
  console.log(`\n${highlighted}`)
@@ -54,19 +56,15 @@ export class DetailedFormatter extends BaseFormatter {
54
56
  for (let i = 0; i < allOffenses.length; i++) {
55
57
  const { filename, offense, content, autocorrectable } = allOffenses[i]
56
58
 
57
- let modifiedOffense = offense
58
-
59
- if (autocorrectable && offense.code) {
60
- modifiedOffense = {
61
- ...offense,
62
- message: `${offense.message} ${colorize(colorize("[Correctable]", "green"), "bold")}`
63
- }
64
- }
65
-
66
- const formatted = this.highlighter.highlightDiagnostic(filename, modifiedOffense, content, {
59
+ const codeUrl = offense.code ? ruleDocumentationUrl(offense.code) : undefined
60
+ const suffix = autocorrectable ? correctableTag : undefined
61
+ const formatted = this.highlighter.highlightDiagnostic(filename, offense, content, {
67
62
  contextLines: 2,
68
63
  wrapLines: this.wrapLines,
69
- truncateLines: this.truncateLines
64
+ truncateLines: this.truncateLines,
65
+ codeUrl,
66
+ fileUrl: fileUrl(filename),
67
+ suffix,
70
68
  })
71
69
  console.log(`\n${formatted}`)
72
70
 
@@ -1,6 +1,8 @@
1
- import { colorize } from "@herb-tools/highlighter"
1
+ import { colorize, hyperlink, TextFormatter } from "@herb-tools/highlighter"
2
2
 
3
3
  import { BaseFormatter } from "./base-formatter.js"
4
+ import { ruleDocumentationUrl } from "../../urls.js"
5
+ import { fileUrl } from "../file-url.js"
4
6
 
5
7
  import type { Diagnostic } from "@herb-tools/core"
6
8
  import type { ProcessedFile } from "../file-processor.js"
@@ -24,33 +26,41 @@ export class SimpleFormatter extends BaseFormatter {
24
26
  }
25
27
 
26
28
  formatFile(filename: string, offenses: Diagnostic[]): void {
27
- console.log(`${colorize(filename, "cyan")}:`)
29
+ const filenameText = colorize(filename, "cyan")
30
+ const filenameLink = hyperlink(filenameText, fileUrl(filename))
31
+ console.log(`${filenameLink}:`)
28
32
 
29
33
  for (const offense of offenses) {
30
34
  const isError = offense.severity === "error"
31
35
  const severity = isError ? colorize("✗", "brightRed") : colorize("⚠", "brightYellow")
32
- const rule = colorize(`(${offense.code})`, "blue")
33
- const locationString = `${offense.location.start.line}:${offense.location.start.column}`
34
- const paddedLocation = locationString.padEnd(4)
36
+ const ruleText = `(${offense.code})`
37
+ const rule = offense.code ? hyperlink(ruleText, ruleDocumentationUrl(offense.code)) : ruleText
38
+ const { line, column } = offense.location.start
39
+ const locationString = `${line}:${column}`
40
+ const paddedLocation = colorize(locationString.padEnd(4), "gray")
41
+ const message = TextFormatter.highlightBackticks(offense.message)
35
42
 
36
- console.log(` ${colorize(paddedLocation, "gray")} ${severity} ${offense.message} ${rule}`)
43
+ console.log(` ${paddedLocation} ${severity} ${message} ${rule}`)
37
44
  }
38
- console.log("")
39
45
  }
40
46
 
41
47
  formatFileProcessed(filename: string, processedFiles: ProcessedFile[]): void {
42
- console.log(`${colorize(filename, "cyan")}:`)
48
+ const filenameText = colorize(filename, "cyan")
49
+ const filenameLink = hyperlink(filenameText, fileUrl(filename))
50
+ console.log(`${filenameLink}:`)
43
51
 
44
52
  for (const { offense, autocorrectable } of processedFiles) {
45
53
  const isError = offense.severity === "error"
46
54
  const severity = isError ? colorize("✗", "brightRed") : colorize("⚠", "brightYellow")
47
- const rule = colorize(`(${offense.code})`, "blue")
48
- const locationString = `${offense.location.start.line}:${offense.location.start.column}`
49
- const paddedLocation = locationString.padEnd(4)
55
+ const ruleText = `(${offense.code})`
56
+ const rule = offense.code ? hyperlink(ruleText, ruleDocumentationUrl(offense.code)) : ruleText
57
+ const { line, column } = offense.location.start
58
+ const locationString = `${line}:${column}`
59
+ const paddedLocation = colorize(locationString.padEnd(4), "gray")
50
60
  const correctable = autocorrectable ? colorize(colorize(" [Correctable]", "green"), "bold") : ""
61
+ const message = TextFormatter.highlightBackticks(offense.message)
51
62
 
52
- console.log(` ${colorize(paddedLocation, "gray")} ${severity} ${offense.message} ${rule}${correctable}`)
63
+ console.log(` ${paddedLocation} ${severity} ${message} ${rule}${correctable}`)
53
64
  }
54
- console.log("")
55
65
  }
56
66
  }
package/src/cli/index.ts CHANGED
@@ -3,4 +3,6 @@ export { FileProcessor } from "./file-processor.js"
3
3
  export { SummaryReporter } from "./summary-reporter.js"
4
4
  export { OutputManager } from "./output-manager.js"
5
5
 
6
+ export type { WorkerInput, WorkerResult, WorkerOffense } from "./lint-worker.js"
7
+
6
8
  export * from "./formatters/index.js"
@@ -0,0 +1,208 @@
1
+ import { workerData, parentPort } from "node:worker_threads"
2
+ import { readFileSync, writeFileSync } from "node:fs"
3
+ import { resolve } from "node:path"
4
+
5
+ import { Herb } from "@herb-tools/node-wasm"
6
+ import { Config } from "@herb-tools/config"
7
+
8
+ import { Diagnostic } from "@herb-tools/core"
9
+ import { Linter } from "../linter.js"
10
+ import { loadCustomRules } from "../loader.js"
11
+
12
+ export interface WorkerInput {
13
+ files: string[]
14
+ projectPath: string
15
+ configPath?: string
16
+ fix: boolean
17
+ fixUnsafe: boolean
18
+ ignoreDisableComments: boolean
19
+ loadCustomRules: boolean
20
+ }
21
+
22
+ export interface WorkerOffense {
23
+ filename: string
24
+ offense: Diagnostic
25
+ content: string
26
+ autocorrectable: boolean
27
+ }
28
+
29
+ export interface WorkerResult {
30
+ totalErrors: number
31
+ totalWarnings: number
32
+ totalInfo: number
33
+ totalHints: number
34
+ totalIgnored: number
35
+ totalWouldBeIgnored: number
36
+ filesWithOffenses: number
37
+ filesFixed: number
38
+ ruleCount: number
39
+ offenses: WorkerOffense[]
40
+ ruleOffenses: [string, { count: number, files: string[] }][]
41
+ fixMessages: string[]
42
+ error?: string
43
+ }
44
+
45
+ async function run() {
46
+ const data = workerData as WorkerInput
47
+
48
+ await Herb.load()
49
+
50
+ const config = await Config.load(data.configPath || data.projectPath, {
51
+ exitOnError: false,
52
+ createIfMissing: false,
53
+ silent: true
54
+ })
55
+
56
+ let customRules = undefined
57
+
58
+ if (data.loadCustomRules) {
59
+ try {
60
+ const result = await loadCustomRules({ baseDir: data.projectPath, silent: true })
61
+ customRules = result.rules
62
+ } catch {
63
+ // Silently ignore custom rule loading failures in workers
64
+ }
65
+ }
66
+
67
+ const linter = Linter.from(Herb, config, customRules)
68
+
69
+ let totalErrors = 0
70
+ let totalWarnings = 0
71
+ let totalInfo = 0
72
+ let totalHints = 0
73
+ let totalIgnored = 0
74
+ let totalWouldBeIgnored = 0
75
+ let filesWithOffenses = 0
76
+ let filesFixed = 0
77
+
78
+ const ruleCount = linter.getRuleCount()
79
+ const allOffenses: WorkerOffense[] = []
80
+ const ruleOffenses = new Map<string, { count: number, files: Set<string> }>()
81
+ const fixMessages: string[] = []
82
+
83
+ const isRuleAutocorrectable = (ruleName: string): boolean => {
84
+ const ruleClass = linter.rules.find(
85
+ (rule) => rule.ruleName === ruleName
86
+ )
87
+
88
+ if (!ruleClass) return false
89
+
90
+ // TODO: fix types
91
+ return (ruleClass as any).autocorrectable === true
92
+ }
93
+
94
+ for (const filename of data.files) {
95
+ const filePath = data.projectPath ? resolve(data.projectPath, filename) : resolve(filename)
96
+ let content = readFileSync(filePath, "utf-8")
97
+
98
+ const lintResult = linter.lint(content, {
99
+ fileName: filename,
100
+ ignoreDisableComments: data.ignoreDisableComments
101
+ })
102
+
103
+ if (data.fix && lintResult.offenses.length > 0) {
104
+ const autofixResult = linter.autofix(content, {
105
+ fileName: filename,
106
+ ignoreDisableComments: data.ignoreDisableComments
107
+ }, undefined, { includeUnsafe: data.fixUnsafe })
108
+
109
+ if (autofixResult.fixed.length > 0) {
110
+ writeFileSync(filePath, autofixResult.source, "utf-8")
111
+ filesFixed++
112
+ fixMessages.push(`${filename}\t${autofixResult.fixed.length}`)
113
+ }
114
+
115
+ content = autofixResult.source
116
+
117
+ for (const offense of autofixResult.unfixed) {
118
+ allOffenses.push({
119
+ filename,
120
+ offense,
121
+ content,
122
+ autocorrectable: isRuleAutocorrectable(offense.rule)
123
+ })
124
+
125
+ const ruleData = ruleOffenses.get(offense.rule) || { count: 0, files: new Set() }
126
+ ruleData.count++
127
+ ruleData.files.add(filename)
128
+ ruleOffenses.set(offense.rule, ruleData)
129
+ }
130
+
131
+ if (autofixResult.unfixed.length > 0) {
132
+ totalErrors += autofixResult.unfixed.filter(o => o.severity === "error").length
133
+ totalWarnings += autofixResult.unfixed.filter(o => o.severity === "warning").length
134
+ totalInfo += autofixResult.unfixed.filter(o => o.severity === "info").length
135
+ totalHints += autofixResult.unfixed.filter(o => o.severity === "hint").length
136
+ filesWithOffenses++
137
+ }
138
+ } else if (lintResult.offenses.length > 0) {
139
+ for (const offense of lintResult.offenses) {
140
+ allOffenses.push({
141
+ filename,
142
+ offense,
143
+ content,
144
+ autocorrectable: isRuleAutocorrectable(offense.rule)
145
+ })
146
+
147
+ const ruleData = ruleOffenses.get(offense.rule) || { count: 0, files: new Set() }
148
+ ruleData.count++
149
+ ruleData.files.add(filename)
150
+ ruleOffenses.set(offense.rule, ruleData)
151
+ }
152
+
153
+ totalErrors += lintResult.errors
154
+ totalWarnings += lintResult.warnings
155
+ totalInfo += lintResult.offenses.filter(o => o.severity === "info").length
156
+ totalHints += lintResult.offenses.filter(o => o.severity === "hint").length
157
+ filesWithOffenses++
158
+ }
159
+
160
+ totalIgnored += lintResult.ignored
161
+
162
+ if (lintResult.wouldBeIgnored) {
163
+ totalWouldBeIgnored += lintResult.wouldBeIgnored
164
+ }
165
+ }
166
+
167
+ const serializedRuleOffenses: [string, { count: number, files: string[] }][] =
168
+ Array.from(ruleOffenses.entries()).map(
169
+ ([rule, data]) => [rule, { count: data.count, files: Array.from(data.files) }]
170
+ )
171
+
172
+ const result: WorkerResult = {
173
+ totalErrors,
174
+ totalWarnings,
175
+ totalInfo,
176
+ totalHints,
177
+ totalIgnored,
178
+ totalWouldBeIgnored,
179
+ filesWithOffenses,
180
+ filesFixed,
181
+ ruleCount,
182
+ offenses: allOffenses,
183
+ ruleOffenses: serializedRuleOffenses,
184
+ fixMessages
185
+ }
186
+
187
+ parentPort!.postMessage(result)
188
+ }
189
+
190
+ run().catch(error => {
191
+ const errorResult: WorkerResult = {
192
+ totalErrors: 0,
193
+ totalWarnings: 0,
194
+ totalInfo: 0,
195
+ totalHints: 0,
196
+ totalIgnored: 0,
197
+ totalWouldBeIgnored: 0,
198
+ filesWithOffenses: 0,
199
+ filesFixed: 0,
200
+ ruleCount: 0,
201
+ offenses: [],
202
+ ruleOffenses: [],
203
+ fixMessages: [],
204
+ error: error instanceof Error ? error.message : String(error)
205
+ }
206
+
207
+ parentPort!.postMessage(errorResult)
208
+ })