@ttsc/lint 0.13.1 → 0.14.0-dev.20260528.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 (464) hide show
  1. package/README.md +948 -69
  2. package/lib/defaultFormat.d.ts +9 -6
  3. package/lib/defaultFormat.js +9 -6
  4. package/lib/defaultFormat.js.map +1 -1
  5. package/lib/index.d.ts +46 -0
  6. package/lib/index.js +35 -10
  7. package/lib/index.js.map +1 -1
  8. package/lib/structures/ITtscLintConfig.d.ts +12 -6
  9. package/lib/structures/{ITtscLintFormatConfig.d.ts → ITtscLintFormat.d.ts} +2 -2
  10. package/lib/structures/{TtscLintRuleMap.js → ITtscLintFormat.js} +1 -1
  11. package/lib/structures/ITtscLintFormat.js.map +1 -0
  12. package/lib/structures/TtscLintRuleSetting.d.ts +49 -0
  13. package/lib/structures/{TtscLintRuleOptions.js → TtscLintRuleSetting.js} +1 -1
  14. package/lib/structures/TtscLintRuleSetting.js.map +1 -0
  15. package/lib/structures/index.d.ts +3 -4
  16. package/lib/structures/index.js +3 -4
  17. package/lib/structures/index.js.map +1 -1
  18. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +104 -0
  19. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js +14 -0
  20. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js.map +1 -0
  21. package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +79 -0
  22. package/lib/structures/rules/ITtscLintBoundariesRules.js +3 -0
  23. package/lib/structures/rules/ITtscLintBoundariesRules.js.map +1 -0
  24. package/lib/structures/rules/ITtscLintContributorRules.d.ts +17 -0
  25. package/lib/structures/rules/ITtscLintContributorRules.js +3 -0
  26. package/lib/structures/rules/ITtscLintContributorRules.js.map +1 -0
  27. package/lib/structures/rules/ITtscLintCoreRules.d.ts +1257 -0
  28. package/lib/structures/{ITtscLintFormatConfig.js → rules/ITtscLintCoreRules.js} +1 -1
  29. package/lib/structures/rules/ITtscLintCoreRules.js.map +1 -0
  30. package/lib/structures/rules/ITtscLintCypressRuleOptions.d.ts +17 -0
  31. package/lib/structures/rules/ITtscLintCypressRuleOptions.js +10 -0
  32. package/lib/structures/rules/ITtscLintCypressRuleOptions.js.map +1 -0
  33. package/lib/structures/rules/ITtscLintCypressRules.d.ts +129 -0
  34. package/lib/structures/rules/ITtscLintCypressRules.js +3 -0
  35. package/lib/structures/rules/ITtscLintCypressRules.js.map +1 -0
  36. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.d.ts +157 -0
  37. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.js +13 -0
  38. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.js.map +1 -0
  39. package/lib/structures/rules/ITtscLintFunctionalRules.d.ts +167 -0
  40. package/lib/structures/rules/ITtscLintFunctionalRules.js +3 -0
  41. package/lib/structures/rules/ITtscLintFunctionalRules.js.map +1 -0
  42. package/lib/structures/rules/ITtscLintJestRules.d.ts +201 -0
  43. package/lib/structures/rules/ITtscLintJestRules.js +3 -0
  44. package/lib/structures/rules/ITtscLintJestRules.js.map +1 -0
  45. package/lib/structures/rules/ITtscLintJsdocRules.d.ts +140 -0
  46. package/lib/structures/rules/ITtscLintJsdocRules.js +3 -0
  47. package/lib/structures/rules/ITtscLintJsdocRules.js.map +1 -0
  48. package/lib/structures/rules/ITtscLintJsxA11yRules.d.ts +302 -0
  49. package/lib/structures/rules/ITtscLintJsxA11yRules.js +3 -0
  50. package/lib/structures/rules/ITtscLintJsxA11yRules.js.map +1 -0
  51. package/lib/structures/rules/ITtscLintNextjsRules.d.ts +185 -0
  52. package/lib/structures/rules/ITtscLintNextjsRules.js +3 -0
  53. package/lib/structures/rules/ITtscLintNextjsRules.js.map +1 -0
  54. package/lib/structures/rules/ITtscLintPlaywrightRules.d.ts +330 -0
  55. package/lib/structures/rules/ITtscLintPlaywrightRules.js +3 -0
  56. package/lib/structures/rules/ITtscLintPlaywrightRules.js.map +1 -0
  57. package/lib/structures/rules/ITtscLintPromiseRules.d.ts +188 -0
  58. package/lib/structures/rules/ITtscLintPromiseRules.js +3 -0
  59. package/lib/structures/rules/ITtscLintPromiseRules.js.map +1 -0
  60. package/lib/structures/rules/ITtscLintReactPerfRuleOptions.d.ts +18 -0
  61. package/lib/structures/rules/ITtscLintReactPerfRuleOptions.js +8 -0
  62. package/lib/structures/rules/ITtscLintReactPerfRuleOptions.js.map +1 -0
  63. package/lib/structures/rules/ITtscLintReactPerfRules.d.ts +51 -0
  64. package/lib/structures/rules/ITtscLintReactPerfRules.js +3 -0
  65. package/lib/structures/rules/ITtscLintReactPerfRules.js.map +1 -0
  66. package/lib/structures/rules/ITtscLintReactRuleOptions.d.ts +39 -0
  67. package/lib/structures/rules/ITtscLintReactRuleOptions.js +12 -0
  68. package/lib/structures/rules/ITtscLintReactRuleOptions.js.map +1 -0
  69. package/lib/structures/rules/ITtscLintReactRules.d.ts +289 -0
  70. package/lib/structures/rules/ITtscLintReactRules.js +3 -0
  71. package/lib/structures/rules/ITtscLintReactRules.js.map +1 -0
  72. package/lib/structures/rules/ITtscLintRegexpRules.d.ts +190 -0
  73. package/lib/structures/rules/ITtscLintRegexpRules.js +3 -0
  74. package/lib/structures/rules/ITtscLintRegexpRules.js.map +1 -0
  75. package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +66 -0
  76. package/lib/structures/rules/ITtscLintRuleOptionsMap.js +3 -0
  77. package/lib/structures/rules/ITtscLintRuleOptionsMap.js.map +1 -0
  78. package/lib/structures/rules/ITtscLintRules.d.ts +55 -0
  79. package/lib/structures/{TtscLintRule.js → rules/ITtscLintRules.js} +1 -1
  80. package/lib/structures/rules/ITtscLintRules.js.map +1 -0
  81. package/lib/structures/rules/ITtscLintSecurityRules.d.ts +148 -0
  82. package/lib/structures/rules/ITtscLintSecurityRules.js +3 -0
  83. package/lib/structures/rules/ITtscLintSecurityRules.js.map +1 -0
  84. package/lib/structures/rules/ITtscLintSolidRules.d.ts +181 -0
  85. package/lib/structures/rules/ITtscLintSolidRules.js +3 -0
  86. package/lib/structures/rules/ITtscLintSolidRules.js.map +1 -0
  87. package/lib/structures/rules/ITtscLintStorybookRuleOptions.d.ts +21 -0
  88. package/lib/structures/rules/ITtscLintStorybookRuleOptions.js +9 -0
  89. package/lib/structures/rules/ITtscLintStorybookRuleOptions.js.map +1 -0
  90. package/lib/structures/rules/ITtscLintStorybookRules.d.ts +174 -0
  91. package/lib/structures/rules/ITtscLintStorybookRules.js +3 -0
  92. package/lib/structures/rules/ITtscLintStorybookRules.js.map +1 -0
  93. package/lib/structures/rules/ITtscLintTanstackQueryRules.d.ts +94 -0
  94. package/lib/structures/rules/ITtscLintTanstackQueryRules.js +3 -0
  95. package/lib/structures/rules/ITtscLintTanstackQueryRules.js.map +1 -0
  96. package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.d.ts +22 -0
  97. package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.js +10 -0
  98. package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.js.map +1 -0
  99. package/lib/structures/rules/ITtscLintTestingLibraryRules.d.ts +310 -0
  100. package/lib/structures/rules/ITtscLintTestingLibraryRules.js +3 -0
  101. package/lib/structures/rules/ITtscLintTestingLibraryRules.js.map +1 -0
  102. package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +1132 -0
  103. package/lib/structures/rules/ITtscLintTypeScriptRules.js +3 -0
  104. package/lib/structures/rules/ITtscLintTypeScriptRules.js.map +1 -0
  105. package/lib/structures/rules/ITtscLintUnicornRules.d.ts +997 -0
  106. package/lib/structures/rules/ITtscLintUnicornRules.js +3 -0
  107. package/lib/structures/rules/ITtscLintUnicornRules.js.map +1 -0
  108. package/lib/structures/rules/ITtscLintVitestRules.d.ts +141 -0
  109. package/lib/structures/rules/ITtscLintVitestRules.js +3 -0
  110. package/lib/structures/rules/ITtscLintVitestRules.js.map +1 -0
  111. package/lib/structures/rules/index.d.ts +31 -0
  112. package/lib/structures/rules/index.js +48 -0
  113. package/lib/structures/rules/index.js.map +1 -0
  114. package/linthost/ast_helpers.go +30 -5
  115. package/linthost/config.go +48 -17
  116. package/linthost/config_format.go +9 -9
  117. package/linthost/directives.go +67 -25
  118. package/linthost/dispatch.go +14 -3
  119. package/linthost/engine.go +152 -31
  120. package/linthost/fix.go +2 -2
  121. package/linthost/lsp.go +747 -0
  122. package/linthost/print_dispatch.go +2 -2
  123. package/linthost/print_nodes_function.go +2 -2
  124. package/linthost/print_nodes_object.go +1 -1
  125. package/linthost/rule_names.go +14 -0
  126. package/linthost/rules_arrays.go +2 -2
  127. package/linthost/rules_boundaries.go +532 -0
  128. package/linthost/rules_camelcase.go +133 -0
  129. package/linthost/rules_complexity.go +94 -0
  130. package/linthost/rules_consistent_return.go +130 -0
  131. package/linthost/rules_console.go +1 -1
  132. package/linthost/rules_core_extra.go +739 -0
  133. package/linthost/rules_curly.go +70 -0
  134. package/linthost/rules_cypress.go +608 -0
  135. package/linthost/rules_debugger.go +2 -2
  136. package/linthost/rules_default_case.go +39 -0
  137. package/linthost/rules_default_case_last.go +46 -0
  138. package/linthost/rules_dupes.go +3 -3
  139. package/linthost/rules_empty.go +3 -3
  140. package/linthost/rules_escape.go +44 -24
  141. package/linthost/rules_eval.go +2 -2
  142. package/linthost/rules_finally.go +2 -2
  143. package/linthost/rules_format_jsdoc.go +1 -1
  144. package/linthost/rules_format_print_width.go +9 -9
  145. package/linthost/rules_format_quotes.go +1 -1
  146. package/linthost/rules_format_semi.go +1 -1
  147. package/linthost/rules_format_sort_imports.go +1 -1
  148. package/linthost/rules_format_trailing_comma.go +1 -1
  149. package/linthost/rules_functional.go +967 -0
  150. package/linthost/rules_gap.go +24 -45
  151. package/linthost/rules_grouped_accessor_pairs.go +87 -0
  152. package/linthost/rules_guard_for_in.go +198 -0
  153. package/linthost/rules_id_length.go +77 -0
  154. package/linthost/rules_imports.go +70 -2
  155. package/linthost/rules_init_declarations.go +53 -0
  156. package/linthost/rules_jest.go +618 -0
  157. package/linthost/rules_jsdoc.go +502 -0
  158. package/linthost/rules_jsx_a11y.go +1264 -0
  159. package/linthost/rules_logic.go +12 -12
  160. package/linthost/rules_loops.go +1 -1
  161. package/linthost/rules_max_classes_per_file.go +58 -0
  162. package/linthost/rules_max_depth.go +83 -0
  163. package/linthost/rules_max_lines.go +101 -0
  164. package/linthost/rules_max_lines_per_function.go +57 -0
  165. package/linthost/rules_max_nested_callbacks.go +61 -0
  166. package/linthost/rules_max_params.go +49 -0
  167. package/linthost/rules_max_statements.go +59 -0
  168. package/linthost/rules_misc.go +1 -1
  169. package/linthost/rules_nextjs.go +697 -0
  170. package/linthost/rules_no_else_return.go +75 -0
  171. package/linthost/rules_no_empty_named_blocks.go +91 -0
  172. package/linthost/rules_no_extend_native.go +51 -0
  173. package/linthost/rules_no_import_assign.go +140 -0
  174. package/linthost/rules_no_invalid_this.go +52 -0
  175. package/linthost/rules_no_loop_func.go +85 -0
  176. package/linthost/rules_no_magic_numbers.go +123 -0
  177. package/linthost/rules_no_mixed_operators.go +80 -0
  178. package/linthost/rules_no_param_reassign.go +98 -0
  179. package/linthost/rules_no_redeclare.go +270 -0
  180. package/linthost/rules_no_restricted_imports.go +105 -0
  181. package/linthost/rules_no_restricted_syntax.go +49 -0
  182. package/linthost/rules_no_shadow.go +147 -0
  183. package/linthost/rules_no_unreachable.go +77 -0
  184. package/linthost/rules_no_useless_assignment.go +120 -0
  185. package/linthost/rules_no_useless_constructor.go +245 -0
  186. package/linthost/rules_no_useless_return.go +51 -0
  187. package/linthost/rules_params.go +1 -1
  188. package/linthost/rules_playwright.go +788 -0
  189. package/linthost/rules_prefer_arrow_callback.go +124 -0
  190. package/linthost/rules_prefer_destructuring.go +98 -0
  191. package/linthost/rules_prefer_named_capture_group.go +129 -0
  192. package/linthost/rules_prefer_rest_params.go +94 -0
  193. package/linthost/rules_problems.go +41 -30
  194. package/linthost/rules_promise.go +1128 -73
  195. package/linthost/rules_protos.go +2 -2
  196. package/linthost/rules_react.go +773 -0
  197. package/linthost/rules_react_extras.go +227 -0
  198. package/linthost/rules_react_hooks.go +848 -0
  199. package/linthost/rules_react_perf.go +293 -0
  200. package/linthost/rules_react_refresh.go +583 -0
  201. package/linthost/rules_regexp.go +410 -0
  202. package/linthost/rules_security.go +830 -0
  203. package/linthost/rules_self.go +2 -2
  204. package/linthost/rules_solid.go +1073 -0
  205. package/linthost/rules_sort_imports.go +69 -0
  206. package/linthost/rules_sort_keys.go +134 -0
  207. package/linthost/rules_storybook.go +1176 -0
  208. package/linthost/rules_strings.go +4 -4
  209. package/linthost/rules_suggestions.go +38 -38
  210. package/linthost/rules_tanstack_query.go +844 -0
  211. package/linthost/rules_testing_library.go +1204 -0
  212. package/linthost/rules_throw.go +1 -1
  213. package/linthost/rules_ts.go +19 -19
  214. package/linthost/rules_ts_async.go +399 -0
  215. package/linthost/rules_ts_class_literal_property_style.go +115 -0
  216. package/linthost/rules_ts_consistent_type_exports.go +256 -0
  217. package/linthost/rules_ts_explicit_function_return_type.go +67 -0
  218. package/linthost/rules_ts_extra.go +155 -36
  219. package/linthost/rules_ts_no_base_to_string.go +218 -0
  220. package/linthost/rules_ts_no_confusing_void.go +72 -0
  221. package/linthost/rules_ts_no_deprecated.go +195 -0
  222. package/linthost/rules_ts_no_for_in_array.go +84 -0
  223. package/linthost/rules_ts_no_magic_numbers.go +124 -0
  224. package/linthost/rules_ts_no_meaningless_void_operator.go +84 -0
  225. package/linthost/rules_ts_no_misused_spread.go +104 -0
  226. package/linthost/rules_ts_no_redundant_type_constituents.go +113 -0
  227. package/linthost/rules_ts_no_restricted_types.go +62 -0
  228. package/linthost/rules_ts_no_unnecessary_boolean_literal_compare.go +129 -0
  229. package/linthost/rules_ts_no_unnecessary_condition.go +250 -0
  230. package/linthost/rules_ts_no_unnecessary_qualifier.go +118 -0
  231. package/linthost/rules_ts_no_unnecessary_template_expression.go +178 -0
  232. package/linthost/rules_ts_no_unnecessary_type_arguments.go +234 -0
  233. package/linthost/rules_ts_no_unnecessary_type_assertion.go +157 -0
  234. package/linthost/rules_ts_no_unsafe_argument.go +82 -0
  235. package/linthost/rules_ts_no_unsafe_assignment.go +102 -0
  236. package/linthost/rules_ts_no_unsafe_call.go +82 -0
  237. package/linthost/rules_ts_no_unsafe_enum_comparison.go +235 -0
  238. package/linthost/rules_ts_no_unsafe_member_access.go +58 -0
  239. package/linthost/rules_ts_no_unsafe_return.go +126 -0
  240. package/linthost/rules_ts_no_unsafe_unary_minus.go +81 -0
  241. package/linthost/rules_ts_no_useless_constructor.go +52 -0
  242. package/linthost/rules_ts_non_nullable_type_assertion_style.go +108 -0
  243. package/linthost/rules_ts_parameter_properties.go +38 -0
  244. package/linthost/rules_ts_prefer_find.go +178 -0
  245. package/linthost/rules_ts_prefer_includes.go +255 -0
  246. package/linthost/rules_ts_prefer_nullish_coalescing.go +104 -0
  247. package/linthost/rules_ts_prefer_optional_chain.go +228 -0
  248. package/linthost/rules_ts_prefer_promise_reject_errors.go +176 -0
  249. package/linthost/rules_ts_prefer_readonly.go +73 -0
  250. package/linthost/rules_ts_prefer_reduce_type_parameter.go +116 -0
  251. package/linthost/rules_ts_prefer_regexp_exec.go +122 -0
  252. package/linthost/rules_ts_prefer_return_this_type.go +135 -0
  253. package/linthost/rules_ts_prefer_string_starts_ends_with.go +360 -0
  254. package/linthost/rules_ts_promise_function_async.go +77 -0
  255. package/linthost/rules_ts_related_getter_setter_pairs.go +113 -0
  256. package/linthost/rules_ts_require_array_sort_compare.go +96 -0
  257. package/linthost/rules_ts_restrict_plus_operands.go +127 -0
  258. package/linthost/rules_ts_restrict_template_expressions.go +95 -0
  259. package/linthost/rules_ts_simple.go +387 -0
  260. package/linthost/rules_ts_sort_type_constituents.go +144 -0
  261. package/linthost/rules_ts_strict_boolean_expressions.go +157 -0
  262. package/linthost/rules_ts_switch_exhaustiveness_check.go +147 -0
  263. package/linthost/rules_ts_unbound_method.go +126 -0
  264. package/linthost/rules_tsdoc.go +136 -0
  265. package/linthost/rules_unicorn_better_regex.go +27 -0
  266. package/linthost/rules_unicorn_catch_error_name.go +46 -0
  267. package/linthost/rules_unicorn_consistent_assert.go +47 -0
  268. package/linthost/rules_unicorn_consistent_date_clone.go +54 -0
  269. package/linthost/rules_unicorn_consistent_destructuring.go +25 -0
  270. package/linthost/rules_unicorn_consistent_empty_array_spread.go +62 -0
  271. package/linthost/rules_unicorn_consistent_existence_index_check.go +80 -0
  272. package/linthost/rules_unicorn_consistent_function_scoping.go +25 -0
  273. package/linthost/rules_unicorn_consistent_template_literal_escape.go +25 -0
  274. package/linthost/rules_unicorn_custom_error_definition.go +138 -0
  275. package/linthost/rules_unicorn_empty_brace_spaces.go +73 -0
  276. package/linthost/rules_unicorn_error_message.go +64 -0
  277. package/linthost/rules_unicorn_escape_case.go +51 -0
  278. package/linthost/rules_unicorn_expiring_todo_comments.go +78 -0
  279. package/linthost/rules_unicorn_explicit_length_check.go +88 -0
  280. package/linthost/rules_unicorn_filename_case.go +29 -0
  281. package/linthost/rules_unicorn_import_style.go +28 -0
  282. package/linthost/rules_unicorn_isolated_functions.go +29 -0
  283. package/linthost/rules_unicorn_new_for_builtins.go +87 -0
  284. package/linthost/rules_unicorn_no_abusive_eslint_disable.go +63 -0
  285. package/linthost/rules_unicorn_no_accessor_recursion.go +115 -0
  286. package/linthost/rules_unicorn_no_anonymous_default_export.go +74 -0
  287. package/linthost/rules_unicorn_no_array_callback_reference.go +65 -0
  288. package/linthost/rules_unicorn_no_array_for_each.go +40 -0
  289. package/linthost/rules_unicorn_no_array_method_this_argument.go +61 -0
  290. package/linthost/rules_unicorn_no_array_reduce.go +41 -0
  291. package/linthost/rules_unicorn_no_array_reverse.go +44 -0
  292. package/linthost/rules_unicorn_no_array_sort.go +43 -0
  293. package/linthost/rules_unicorn_no_await_expression_member.go +40 -0
  294. package/linthost/rules_unicorn_no_await_in_promise_methods.go +69 -0
  295. package/linthost/rules_unicorn_no_console_spaces.go +61 -0
  296. package/linthost/rules_unicorn_no_document_cookie.go +41 -0
  297. package/linthost/rules_unicorn_no_empty_file.go +44 -0
  298. package/linthost/rules_unicorn_no_for_loop.go +102 -0
  299. package/linthost/rules_unicorn_no_hex_escape.go +42 -0
  300. package/linthost/rules_unicorn_no_immediate_mutation.go +90 -0
  301. package/linthost/rules_unicorn_no_instanceof_builtins.go +66 -0
  302. package/linthost/rules_unicorn_no_invalid_fetch_options.go +79 -0
  303. package/linthost/rules_unicorn_no_invalid_remove_event_listener.go +51 -0
  304. package/linthost/rules_unicorn_no_keyword_prefix.go +101 -0
  305. package/linthost/rules_unicorn_no_lonely_if.go +44 -0
  306. package/linthost/rules_unicorn_no_magic_array_flat_depth.go +51 -0
  307. package/linthost/rules_unicorn_no_named_default.go +54 -0
  308. package/linthost/rules_unicorn_no_negated_condition.go +85 -0
  309. package/linthost/rules_unicorn_no_negation_in_equality_check.go +50 -0
  310. package/linthost/rules_unicorn_no_nested_ternary.go +43 -0
  311. package/linthost/rules_unicorn_no_new_array.go +30 -0
  312. package/linthost/rules_unicorn_no_new_buffer.go +31 -0
  313. package/linthost/rules_unicorn_no_null.go +24 -0
  314. package/linthost/rules_unicorn_no_object_as_default_parameter.go +48 -0
  315. package/linthost/rules_unicorn_no_process_exit.go +43 -0
  316. package/linthost/rules_unicorn_no_single_promise_in_promise_methods.go +59 -0
  317. package/linthost/rules_unicorn_no_static_only_class.go +56 -0
  318. package/linthost/rules_unicorn_no_thenable.go +74 -0
  319. package/linthost/rules_unicorn_no_this_assignment.go +35 -0
  320. package/linthost/rules_unicorn_no_typeof_undefined.go +58 -0
  321. package/linthost/rules_unicorn_no_unnecessary_array_flat_depth.go +49 -0
  322. package/linthost/rules_unicorn_no_unnecessary_array_splice_count.go +70 -0
  323. package/linthost/rules_unicorn_no_unnecessary_await.go +51 -0
  324. package/linthost/rules_unicorn_no_unnecessary_polyfills.go +32 -0
  325. package/linthost/rules_unicorn_no_unnecessary_slice_end.go +48 -0
  326. package/linthost/rules_unicorn_no_unreadable_array_destructuring.go +115 -0
  327. package/linthost/rules_unicorn_no_unreadable_iife.go +50 -0
  328. package/linthost/rules_unicorn_no_unused_properties.go +30 -0
  329. package/linthost/rules_unicorn_no_useless_collection_argument.go +63 -0
  330. package/linthost/rules_unicorn_no_useless_error_capture_stack_trace.go +46 -0
  331. package/linthost/rules_unicorn_no_useless_fallback_in_spread.go +81 -0
  332. package/linthost/rules_unicorn_no_useless_iterator_to_array.go +72 -0
  333. package/linthost/rules_unicorn_no_useless_length_check.go +144 -0
  334. package/linthost/rules_unicorn_no_useless_promise_resolve_reject.go +81 -0
  335. package/linthost/rules_unicorn_no_useless_spread.go +67 -0
  336. package/linthost/rules_unicorn_no_useless_switch_case.go +55 -0
  337. package/linthost/rules_unicorn_no_useless_undefined.go +55 -0
  338. package/linthost/rules_unicorn_no_zero_fractions.go +52 -0
  339. package/linthost/rules_unicorn_number_literal_case.go +69 -0
  340. package/linthost/rules_unicorn_numeric_separators_style.go +75 -0
  341. package/linthost/rules_unicorn_prefer_add_event_listener.go +66 -0
  342. package/linthost/rules_unicorn_prefer_array_find.go +51 -0
  343. package/linthost/rules_unicorn_prefer_array_flat.go +52 -0
  344. package/linthost/rules_unicorn_prefer_array_flat_map.go +63 -0
  345. package/linthost/rules_unicorn_prefer_array_index_of.go +147 -0
  346. package/linthost/rules_unicorn_prefer_array_some.go +67 -0
  347. package/linthost/rules_unicorn_prefer_at.go +77 -0
  348. package/linthost/rules_unicorn_prefer_bigint_literals.go +77 -0
  349. package/linthost/rules_unicorn_prefer_blob_reading_methods.go +44 -0
  350. package/linthost/rules_unicorn_prefer_class_fields.go +93 -0
  351. package/linthost/rules_unicorn_prefer_classlist_toggle.go +120 -0
  352. package/linthost/rules_unicorn_prefer_code_point.go +49 -0
  353. package/linthost/rules_unicorn_prefer_date_now.go +78 -0
  354. package/linthost/rules_unicorn_prefer_default_parameters.go +144 -0
  355. package/linthost/rules_unicorn_prefer_dom_node_append.go +40 -0
  356. package/linthost/rules_unicorn_prefer_dom_node_dataset.go +55 -0
  357. package/linthost/rules_unicorn_prefer_dom_node_remove.go +41 -0
  358. package/linthost/rules_unicorn_prefer_dom_node_text_content.go +37 -0
  359. package/linthost/rules_unicorn_prefer_event_target.go +36 -0
  360. package/linthost/rules_unicorn_prefer_export_from.go +129 -0
  361. package/linthost/rules_unicorn_prefer_global_this.go +127 -0
  362. package/linthost/rules_unicorn_prefer_import_meta_properties.go +66 -0
  363. package/linthost/rules_unicorn_prefer_includes.go +149 -0
  364. package/linthost/rules_unicorn_prefer_json_parse_buffer.go +65 -0
  365. package/linthost/rules_unicorn_prefer_keyboard_event_key.go +38 -0
  366. package/linthost/rules_unicorn_prefer_logical_operator_over_ternary.go +48 -0
  367. package/linthost/rules_unicorn_prefer_math_min_max.go +60 -0
  368. package/linthost/rules_unicorn_prefer_math_trunc.go +62 -0
  369. package/linthost/rules_unicorn_prefer_modern_dom_apis.go +47 -0
  370. package/linthost/rules_unicorn_prefer_modern_math_apis.go +70 -0
  371. package/linthost/rules_unicorn_prefer_module.go +50 -0
  372. package/linthost/rules_unicorn_prefer_native_coercion_functions.go +114 -0
  373. package/linthost/rules_unicorn_prefer_negative_index.go +74 -0
  374. package/linthost/rules_unicorn_prefer_node_protocol.go +122 -0
  375. package/linthost/rules_unicorn_prefer_number_properties.go +88 -0
  376. package/linthost/rules_unicorn_prefer_object_from_entries.go +55 -0
  377. package/linthost/rules_unicorn_prefer_optional_catch_binding.go +62 -0
  378. package/linthost/rules_unicorn_prefer_prototype_methods.go +54 -0
  379. package/linthost/rules_unicorn_prefer_query_selector.go +56 -0
  380. package/linthost/rules_unicorn_prefer_reflect_apply.go +36 -0
  381. package/linthost/rules_unicorn_prefer_regexp_test_rule.go +80 -0
  382. package/linthost/rules_unicorn_prefer_response_static_json.go +53 -0
  383. package/linthost/rules_unicorn_prefer_set_has.go +42 -0
  384. package/linthost/rules_unicorn_prefer_set_size.go +48 -0
  385. package/linthost/rules_unicorn_prefer_simple_condition_first.go +34 -0
  386. package/linthost/rules_unicorn_prefer_single_call.go +96 -0
  387. package/linthost/rules_unicorn_prefer_spread.go +51 -0
  388. package/linthost/rules_unicorn_prefer_string_raw.go +43 -0
  389. package/linthost/rules_unicorn_prefer_string_replace_all.go +95 -0
  390. package/linthost/rules_unicorn_prefer_string_slice.go +43 -0
  391. package/linthost/rules_unicorn_prefer_string_starts_ends_with.go +148 -0
  392. package/linthost/rules_unicorn_prefer_string_trim_start_end.go +48 -0
  393. package/linthost/rules_unicorn_prefer_structured_clone.go +71 -0
  394. package/linthost/rules_unicorn_prefer_switch.go +103 -0
  395. package/linthost/rules_unicorn_prefer_ternary.go +71 -0
  396. package/linthost/rules_unicorn_prefer_top_level_await.go +72 -0
  397. package/linthost/rules_unicorn_prefer_type_error.go +127 -0
  398. package/linthost/rules_unicorn_prevent_abbreviations.go +127 -0
  399. package/linthost/rules_unicorn_relative_url_style.go +49 -0
  400. package/linthost/rules_unicorn_require_array_join_separator.go +45 -0
  401. package/linthost/rules_unicorn_require_module_attributes.go +54 -0
  402. package/linthost/rules_unicorn_require_module_specifiers.go +55 -0
  403. package/linthost/rules_unicorn_require_number_to_fixed_digits_argument.go +43 -0
  404. package/linthost/rules_unicorn_require_post_message_target_origin.go +44 -0
  405. package/linthost/rules_unicorn_string_content.go +27 -0
  406. package/linthost/rules_unicorn_switch_case_braces.go +42 -0
  407. package/linthost/rules_unicorn_switch_case_break_position.go +31 -0
  408. package/linthost/rules_unicorn_template_indent.go +28 -0
  409. package/linthost/rules_unicorn_text_encoding_identifier_case.go +72 -0
  410. package/linthost/rules_unicorn_throw_new_error.go +61 -0
  411. package/linthost/rules_var.go +4 -4
  412. package/linthost/rules_vitest.go +568 -0
  413. package/package.json +3 -3
  414. package/plugin/main.go +49 -3
  415. package/rule/astutil/astutil.go +10 -3
  416. package/rule/rule.go +12 -10
  417. package/src/defaultFormat.ts +11 -8
  418. package/src/index.ts +40 -16
  419. package/src/structures/ITtscLintConfig.ts +12 -6
  420. package/src/structures/{ITtscLintFormatConfig.ts → ITtscLintFormat.ts} +2 -2
  421. package/src/structures/TtscLintRuleSetting.ts +55 -0
  422. package/src/structures/index.ts +3 -4
  423. package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +128 -0
  424. package/src/structures/rules/ITtscLintBoundariesRules.ts +95 -0
  425. package/src/structures/rules/ITtscLintContributorRules.ts +21 -0
  426. package/src/structures/rules/ITtscLintCoreRules.ts +1406 -0
  427. package/src/structures/rules/ITtscLintCypressRuleOptions.ts +18 -0
  428. package/src/structures/rules/ITtscLintCypressRules.ts +145 -0
  429. package/src/structures/rules/ITtscLintFunctionalRuleOptions.ts +213 -0
  430. package/src/structures/rules/ITtscLintFunctionalRules.ts +202 -0
  431. package/src/structures/rules/ITtscLintJestRules.ts +220 -0
  432. package/src/structures/rules/ITtscLintJsdocRules.ts +153 -0
  433. package/src/structures/rules/ITtscLintJsxA11yRules.ts +339 -0
  434. package/src/structures/rules/ITtscLintNextjsRules.ts +206 -0
  435. package/src/structures/rules/ITtscLintPlaywrightRules.ts +361 -0
  436. package/src/structures/rules/ITtscLintPromiseRules.ts +205 -0
  437. package/src/structures/rules/ITtscLintReactPerfRuleOptions.ts +19 -0
  438. package/src/structures/rules/ITtscLintReactPerfRules.ts +55 -0
  439. package/src/structures/rules/ITtscLintReactRuleOptions.ts +43 -0
  440. package/src/structures/rules/ITtscLintReactRules.ts +321 -0
  441. package/src/structures/rules/ITtscLintRegexpRules.ts +212 -0
  442. package/src/structures/rules/ITtscLintRuleOptionsMap.ts +89 -0
  443. package/src/structures/rules/ITtscLintRules.ts +77 -0
  444. package/src/structures/rules/ITtscLintSecurityRules.ts +162 -0
  445. package/src/structures/rules/ITtscLintSolidRules.ts +202 -0
  446. package/src/structures/rules/ITtscLintStorybookRuleOptions.ts +23 -0
  447. package/src/structures/rules/ITtscLintStorybookRules.ts +193 -0
  448. package/src/structures/rules/ITtscLintTanstackQueryRules.ts +102 -0
  449. package/src/structures/rules/ITtscLintTestingLibraryRuleOptions.ts +24 -0
  450. package/src/structures/rules/ITtscLintTestingLibraryRules.ts +342 -0
  451. package/src/structures/rules/ITtscLintTypeScriptRules.ts +1230 -0
  452. package/src/structures/rules/ITtscLintUnicornRules.ts +1143 -0
  453. package/src/structures/rules/ITtscLintVitestRules.ts +154 -0
  454. package/src/structures/rules/index.ts +31 -0
  455. package/lib/structures/ITtscLintFormatConfig.js.map +0 -1
  456. package/lib/structures/TtscLintRule.d.ts +0 -17
  457. package/lib/structures/TtscLintRule.js.map +0 -1
  458. package/lib/structures/TtscLintRuleMap.d.ts +0 -46
  459. package/lib/structures/TtscLintRuleMap.js.map +0 -1
  460. package/lib/structures/TtscLintRuleOptions.d.ts +0 -137
  461. package/lib/structures/TtscLintRuleOptions.js.map +0 -1
  462. package/src/structures/TtscLintRule.ts +0 -160
  463. package/src/structures/TtscLintRuleMap.ts +0 -55
  464. package/src/structures/TtscLintRuleOptions.ts +0 -151
@@ -0,0 +1,1264 @@
1
+ package linthost
2
+
3
+ import (
4
+ "strconv"
5
+ "strings"
6
+
7
+ shimast "github.com/microsoft/typescript-go/shim/ast"
8
+ )
9
+
10
+ type jsxAttr struct {
11
+ node *shimast.Node
12
+ value string
13
+ known bool
14
+ boolean bool
15
+ }
16
+
17
+ type jsxElementInfo struct {
18
+ opening *shimast.Node
19
+ tag string
20
+ attrs map[string]jsxAttr
21
+ children *shimast.NodeList
22
+ }
23
+
24
+ func jsxElementFromNode(node *shimast.Node) jsxElementInfo {
25
+ info := jsxElementInfo{attrs: map[string]jsxAttr{}}
26
+ if node == nil {
27
+ return info
28
+ }
29
+ switch node.Kind {
30
+ case shimast.KindJsxElement:
31
+ el := node.AsJsxElement()
32
+ if el == nil || el.OpeningElement == nil {
33
+ return info
34
+ }
35
+ info.opening = el.OpeningElement.AsNode()
36
+ info.tag = jsxTagName(el.OpeningElement.TagName())
37
+ info.attrs = jsxAttrs(el.OpeningElement.Attributes())
38
+ info.children = el.Children
39
+ case shimast.KindJsxSelfClosingElement:
40
+ el := node.AsJsxSelfClosingElement()
41
+ if el == nil {
42
+ return info
43
+ }
44
+ info.opening = node
45
+ info.tag = jsxTagName(el.TagName)
46
+ info.attrs = jsxAttrs(el.Attributes)
47
+ case shimast.KindJsxOpeningElement:
48
+ el := node.AsJsxOpeningElement()
49
+ if el == nil {
50
+ return info
51
+ }
52
+ info.opening = node
53
+ info.tag = jsxTagName(el.TagName)
54
+ info.attrs = jsxAttrs(el.Attributes)
55
+ }
56
+ return info
57
+ }
58
+
59
+ func jsxTagName(node *shimast.Node) string {
60
+ if node == nil {
61
+ return ""
62
+ }
63
+ if name := identifierText(node); name != "" {
64
+ return name
65
+ }
66
+ return ""
67
+ }
68
+
69
+ func jsxAttrs(node *shimast.Node) map[string]jsxAttr {
70
+ out := map[string]jsxAttr{}
71
+ if node == nil {
72
+ return out
73
+ }
74
+ attrs := node.AsJsxAttributes()
75
+ if attrs == nil || attrs.Properties == nil {
76
+ return out
77
+ }
78
+ for _, prop := range attrs.Properties.Nodes {
79
+ attr := prop.AsJsxAttribute()
80
+ if attr == nil || attr.Name() == nil {
81
+ continue
82
+ }
83
+ name := jsxAttrName(attr.Name())
84
+ if name == "" {
85
+ continue
86
+ }
87
+ out[name] = jsxAttr{
88
+ node: prop,
89
+ value: jsxAttrValue(attr.Initializer),
90
+ known: jsxAttrValueKnown(attr.Initializer),
91
+ boolean: attr.Initializer == nil,
92
+ }
93
+ }
94
+ return out
95
+ }
96
+
97
+ func jsxAttrName(node *shimast.Node) string {
98
+ if node == nil {
99
+ return ""
100
+ }
101
+ if name := identifierText(node); name != "" {
102
+ return name
103
+ }
104
+ if node.Kind == shimast.KindJsxNamespacedName {
105
+ ns := node.AsJsxNamespacedName()
106
+ if ns != nil {
107
+ left := identifierText(ns.Namespace)
108
+ right := identifierText(ns.Name())
109
+ if left != "" && right != "" {
110
+ return left + ":" + right
111
+ }
112
+ }
113
+ }
114
+ return ""
115
+ }
116
+
117
+ func jsxAttrValue(node *shimast.Node) string {
118
+ if node == nil {
119
+ return "true"
120
+ }
121
+ if text := stringLiteralText(node); text != "" || node.Kind == shimast.KindStringLiteral || node.Kind == shimast.KindNoSubstitutionTemplateLiteral {
122
+ return text
123
+ }
124
+ if node.Kind == shimast.KindJsxExpression {
125
+ expr := node.AsJsxExpression()
126
+ if expr == nil || expr.Expression == nil {
127
+ return ""
128
+ }
129
+ if text := stringLiteralText(expr.Expression); text != "" || expr.Expression.Kind == shimast.KindStringLiteral || expr.Expression.Kind == shimast.KindNoSubstitutionTemplateLiteral {
130
+ return text
131
+ }
132
+ if value, ok := isLiteralBoolean(expr.Expression); ok {
133
+ if value {
134
+ return "true"
135
+ }
136
+ return "false"
137
+ }
138
+ if expr.Expression.Kind == shimast.KindNumericLiteral {
139
+ return numericLiteralText(expr.Expression)
140
+ }
141
+ }
142
+ return ""
143
+ }
144
+
145
+ func jsxAttrValueKnown(node *shimast.Node) bool {
146
+ if node == nil {
147
+ return true
148
+ }
149
+ switch node.Kind {
150
+ case shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral:
151
+ return true
152
+ case shimast.KindJsxExpression:
153
+ expr := node.AsJsxExpression()
154
+ if expr == nil || expr.Expression == nil {
155
+ return true
156
+ }
157
+ switch expr.Expression.Kind {
158
+ case shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral, shimast.KindTrueKeyword, shimast.KindFalseKeyword, shimast.KindNumericLiteral:
159
+ return true
160
+ }
161
+ }
162
+ return false
163
+ }
164
+
165
+ func jsxHasAttr(attrs map[string]jsxAttr, names ...string) bool {
166
+ for _, name := range names {
167
+ if _, ok := attrs[name]; ok {
168
+ return true
169
+ }
170
+ }
171
+ return false
172
+ }
173
+
174
+ func jsxKnownAttr(attrs map[string]jsxAttr, name string) (jsxAttr, bool) {
175
+ attr, ok := attrs[name]
176
+ if !ok || !attr.known {
177
+ return jsxAttr{}, false
178
+ }
179
+ return attr, true
180
+ }
181
+
182
+ func jsxBoolAttr(attrs map[string]jsxAttr, name string) (bool, bool) {
183
+ attr, ok := jsxKnownAttr(attrs, name)
184
+ if !ok {
185
+ return false, false
186
+ }
187
+ if attr.boolean {
188
+ return true, true
189
+ }
190
+ switch strings.ToLower(strings.TrimSpace(attr.value)) {
191
+ case "true":
192
+ return true, true
193
+ case "false":
194
+ return false, true
195
+ }
196
+ return false, false
197
+ }
198
+
199
+ func jsxNumericAttr(attrs map[string]jsxAttr, names ...string) (int, bool) {
200
+ for _, name := range names {
201
+ attr, ok := jsxKnownAttr(attrs, name)
202
+ if !ok || strings.TrimSpace(attr.value) == "" {
203
+ continue
204
+ }
205
+ value, err := strconv.Atoi(strings.TrimSpace(attr.value))
206
+ if err == nil {
207
+ return value, true
208
+ }
209
+ }
210
+ return 0, false
211
+ }
212
+
213
+ func jsxHasAccessibleLabel(info jsxElementInfo) bool {
214
+ if attr, ok := jsxKnownAttr(info.attrs, "aria-label"); ok && strings.TrimSpace(attr.value) != "" {
215
+ return true
216
+ }
217
+ if attr, ok := jsxKnownAttr(info.attrs, "aria-labelledby"); ok && strings.TrimSpace(attr.value) != "" {
218
+ return true
219
+ }
220
+ if attr, ok := jsxKnownAttr(info.attrs, "alt"); ok && strings.TrimSpace(attr.value) != "" {
221
+ return true
222
+ }
223
+ if attr, ok := jsxKnownAttr(info.attrs, "title"); ok && strings.TrimSpace(attr.value) != "" {
224
+ return true
225
+ }
226
+ return jsxChildrenHaveAccessibleContent(info.children)
227
+ }
228
+
229
+ func jsxChildrenHaveAccessibleContent(children *shimast.NodeList) bool {
230
+ if children == nil {
231
+ return false
232
+ }
233
+ for _, child := range children.Nodes {
234
+ if child == nil {
235
+ continue
236
+ }
237
+ switch child.Kind {
238
+ case shimast.KindJsxText:
239
+ text := child.AsJsxText()
240
+ if text != nil && !text.ContainsOnlyTriviaWhiteSpaces && strings.TrimSpace(text.Text) != "" {
241
+ return true
242
+ }
243
+ case shimast.KindJsxExpression:
244
+ expr := child.AsJsxExpression()
245
+ if expr != nil && expr.Expression != nil {
246
+ if text := stringLiteralText(expr.Expression); strings.TrimSpace(text) != "" {
247
+ return true
248
+ }
249
+ if expr.Expression.Kind != shimast.KindNullKeyword && identifierText(expr.Expression) != "undefined" {
250
+ return true
251
+ }
252
+ }
253
+ case shimast.KindJsxElement, shimast.KindJsxSelfClosingElement:
254
+ nested := jsxElementFromNode(child)
255
+ if jsxHasAccessibleLabel(nested) {
256
+ return true
257
+ }
258
+ }
259
+ }
260
+ return false
261
+ }
262
+
263
+ func jsxHasDescendantControl(children *shimast.NodeList) bool {
264
+ if children == nil {
265
+ return false
266
+ }
267
+ for _, child := range children.Nodes {
268
+ if child == nil {
269
+ continue
270
+ }
271
+ switch child.Kind {
272
+ case shimast.KindJsxElement, shimast.KindJsxSelfClosingElement:
273
+ info := jsxElementFromNode(child)
274
+ if jsxIsFormControl(info) {
275
+ return true
276
+ }
277
+ if jsxHasDescendantControl(info.children) {
278
+ return true
279
+ }
280
+ }
281
+ }
282
+ return false
283
+ }
284
+
285
+ func jsxHasTrackCaption(children *shimast.NodeList) bool {
286
+ if children == nil {
287
+ return false
288
+ }
289
+ for _, child := range children.Nodes {
290
+ info := jsxElementFromNode(child)
291
+ if info.tag != "track" {
292
+ continue
293
+ }
294
+ attr, ok := jsxKnownAttr(info.attrs, "kind")
295
+ if !ok {
296
+ return true
297
+ }
298
+ kind := strings.ToLower(strings.TrimSpace(attr.value))
299
+ if kind == "captions" || kind == "subtitles" {
300
+ return true
301
+ }
302
+ }
303
+ return false
304
+ }
305
+
306
+ func jsxIsFormControl(info jsxElementInfo) bool {
307
+ switch info.tag {
308
+ case "button", "select", "textarea":
309
+ return true
310
+ case "input":
311
+ if attr, ok := jsxKnownAttr(info.attrs, "type"); ok && strings.EqualFold(attr.value, "hidden") {
312
+ return false
313
+ }
314
+ return true
315
+ }
316
+ return false
317
+ }
318
+
319
+ func jsxIsHidden(info jsxElementInfo) bool {
320
+ if value, ok := jsxBoolAttr(info.attrs, "aria-hidden"); ok && value {
321
+ return true
322
+ }
323
+ _, hidden := info.attrs["hidden"]
324
+ return hidden
325
+ }
326
+
327
+ func jsxHasKeyboardHandler(attrs map[string]jsxAttr) bool {
328
+ return jsxHasAttr(attrs, "onKeyDown", "onKeyUp", "onKeyPress", "onkeydown", "onkeyup", "onkeypress")
329
+ }
330
+
331
+ func jsxHasMouseOrKeyboardInteraction(attrs map[string]jsxAttr) bool {
332
+ return jsxHasAttr(
333
+ attrs,
334
+ "onClick", "onclick",
335
+ "onMouseDown", "onmousedown",
336
+ "onMouseUp", "onmouseup",
337
+ "onKeyDown", "onkeydown",
338
+ "onKeyUp", "onkeyup",
339
+ "onKeyPress", "onkeypress",
340
+ )
341
+ }
342
+
343
+ func jsxRole(attrs map[string]jsxAttr) (string, bool) {
344
+ attr, ok := jsxKnownAttr(attrs, "role")
345
+ if !ok {
346
+ return "", false
347
+ }
348
+ for _, token := range strings.Fields(strings.ToLower(attr.value)) {
349
+ if token != "" {
350
+ return token, true
351
+ }
352
+ }
353
+ return "", true
354
+ }
355
+
356
+ func jsxImplicitRole(info jsxElementInfo) string {
357
+ switch info.tag {
358
+ case "a", "area":
359
+ if jsxHasAttr(info.attrs, "href") {
360
+ return "link"
361
+ }
362
+ case "article":
363
+ return "article"
364
+ case "button":
365
+ return "button"
366
+ case "img":
367
+ return "img"
368
+ case "input":
369
+ if attr, ok := jsxKnownAttr(info.attrs, "type"); ok {
370
+ switch strings.ToLower(attr.value) {
371
+ case "button", "submit", "reset":
372
+ return "button"
373
+ case "checkbox":
374
+ return "checkbox"
375
+ case "radio":
376
+ return "radio"
377
+ case "range":
378
+ return "slider"
379
+ case "email", "password", "search", "tel", "text", "url":
380
+ return "textbox"
381
+ }
382
+ }
383
+ return "textbox"
384
+ case "select":
385
+ return "combobox"
386
+ case "textarea":
387
+ return "textbox"
388
+ case "nav":
389
+ return "navigation"
390
+ case "main":
391
+ return "main"
392
+ case "ul", "ol":
393
+ return "list"
394
+ case "li":
395
+ return "listitem"
396
+ case "table":
397
+ return "table"
398
+ case "tr":
399
+ return "row"
400
+ case "td":
401
+ return "cell"
402
+ case "th":
403
+ return "columnheader"
404
+ case "h1", "h2", "h3", "h4", "h5", "h6":
405
+ return "heading"
406
+ }
407
+ return ""
408
+ }
409
+
410
+ func jsxIsInteractive(info jsxElementInfo) bool {
411
+ if role, ok := jsxRole(info.attrs); ok && jsxInteractiveRoles[role] {
412
+ return true
413
+ }
414
+ switch jsxImplicitRole(info) {
415
+ case "button", "checkbox", "combobox", "link", "radio", "slider", "textbox":
416
+ return true
417
+ }
418
+ return false
419
+ }
420
+
421
+ func jsxIsFocusable(info jsxElementInfo) bool {
422
+ if disabled, ok := jsxBoolAttr(info.attrs, "disabled"); ok && disabled {
423
+ return false
424
+ }
425
+ if value, ok := jsxNumericAttr(info.attrs, "tabIndex", "tabindex"); ok && value >= 0 {
426
+ return true
427
+ }
428
+ switch info.tag {
429
+ case "button", "input", "select", "textarea":
430
+ return true
431
+ case "a", "area":
432
+ return jsxHasAttr(info.attrs, "href")
433
+ }
434
+ return false
435
+ }
436
+
437
+ func jsxIsNonInteractiveElement(tag string) bool {
438
+ switch tag {
439
+ case "article", "aside", "div", "footer", "header", "li", "main", "nav", "ol", "p", "section", "ul",
440
+ "h1", "h2", "h3", "h4", "h5", "h6":
441
+ return true
442
+ }
443
+ return false
444
+ }
445
+
446
+ var jsxA11yRoles = map[string]bool{
447
+ "alert": true, "alertdialog": true, "application": true, "article": true, "banner": true,
448
+ "button": true, "cell": true, "checkbox": true, "columnheader": true, "combobox": true,
449
+ "complementary": true, "contentinfo": true, "definition": true, "dialog": true, "directory": true,
450
+ "document": true, "feed": true, "figure": true, "form": true, "grid": true, "gridcell": true,
451
+ "group": true, "heading": true, "img": true, "link": true, "list": true, "listbox": true,
452
+ "listitem": true, "log": true, "main": true, "marquee": true, "math": true, "menu": true,
453
+ "menubar": true, "menuitem": true, "menuitemcheckbox": true, "menuitemradio": true,
454
+ "meter": true, "navigation": true, "none": true, "note": true, "option": true, "presentation": true,
455
+ "progressbar": true, "radio": true, "radiogroup": true, "region": true, "row": true,
456
+ "rowgroup": true, "rowheader": true, "scrollbar": true, "search": true, "searchbox": true,
457
+ "separator": true, "slider": true, "spinbutton": true, "status": true, "switch": true,
458
+ "tab": true, "table": true, "tablist": true, "tabpanel": true, "term": true, "textbox": true,
459
+ "timer": true, "toolbar": true, "tooltip": true, "tree": true, "treegrid": true, "treeitem": true,
460
+ }
461
+
462
+ var jsxInteractiveRoles = map[string]bool{
463
+ "button": true, "checkbox": true, "combobox": true, "gridcell": true, "link": true,
464
+ "listbox": true, "menuitem": true, "menuitemcheckbox": true, "menuitemradio": true,
465
+ "option": true, "radio": true, "scrollbar": true, "searchbox": true, "slider": true,
466
+ "spinbutton": true, "switch": true, "tab": true, "textbox": true, "treeitem": true,
467
+ }
468
+
469
+ var jsxNonInteractiveRoles = map[string]bool{
470
+ "article": true, "complementary": true, "contentinfo": true, "definition": true,
471
+ "document": true, "feed": true, "figure": true, "group": true, "heading": true,
472
+ "img": true, "list": true, "listitem": true, "main": true, "navigation": true,
473
+ "none": true, "note": true, "presentation": true, "region": true, "row": true,
474
+ "rowgroup": true, "separator": true, "table": true, "term": true, "toolbar": true,
475
+ }
476
+
477
+ var jsxAriaProps = map[string]bool{
478
+ "aria-activedescendant": true, "aria-atomic": true, "aria-autocomplete": true, "aria-braillelabel": true,
479
+ "aria-brailleroledescription": true, "aria-busy": true, "aria-checked": true, "aria-colcount": true,
480
+ "aria-colindex": true, "aria-colindextext": true, "aria-colspan": true, "aria-controls": true,
481
+ "aria-current": true, "aria-describedby": true, "aria-description": true, "aria-details": true,
482
+ "aria-disabled": true, "aria-dropeffect": true, "aria-errormessage": true, "aria-expanded": true,
483
+ "aria-flowto": true, "aria-grabbed": true, "aria-haspopup": true, "aria-hidden": true,
484
+ "aria-invalid": true, "aria-keyshortcuts": true, "aria-label": true, "aria-labelledby": true,
485
+ "aria-level": true, "aria-live": true, "aria-modal": true, "aria-multiline": true,
486
+ "aria-multiselectable": true, "aria-orientation": true, "aria-owns": true, "aria-placeholder": true,
487
+ "aria-posinset": true, "aria-pressed": true, "aria-readonly": true, "aria-relevant": true,
488
+ "aria-required": true, "aria-roledescription": true, "aria-rowcount": true, "aria-rowindex": true,
489
+ "aria-rowindextext": true, "aria-rowspan": true, "aria-selected": true, "aria-setsize": true,
490
+ "aria-sort": true, "aria-valuemax": true, "aria-valuemin": true, "aria-valuenow": true,
491
+ "aria-valuetext": true,
492
+ }
493
+
494
+ var jsxGlobalAriaProps = map[string]bool{
495
+ "aria-atomic": true, "aria-busy": true, "aria-controls": true, "aria-current": true,
496
+ "aria-describedby": true, "aria-description": true, "aria-details": true, "aria-disabled": true,
497
+ "aria-dropeffect": true, "aria-errormessage": true, "aria-flowto": true, "aria-grabbed": true,
498
+ "aria-haspopup": true, "aria-hidden": true, "aria-invalid": true, "aria-keyshortcuts": true,
499
+ "aria-label": true, "aria-labelledby": true, "aria-live": true, "aria-owns": true,
500
+ "aria-relevant": true, "aria-roledescription": true,
501
+ }
502
+
503
+ var jsxRoleRequiredProps = map[string][]string{
504
+ "checkbox": {"aria-checked"},
505
+ "combobox": {"aria-controls", "aria-expanded"},
506
+ "heading": {"aria-level"},
507
+ "menuitemcheckbox": {"aria-checked"},
508
+ "menuitemradio": {"aria-checked"},
509
+ "option": {"aria-selected"},
510
+ "radio": {"aria-checked"},
511
+ "scrollbar": {"aria-controls", "aria-valuenow"},
512
+ "slider": {"aria-valuenow"},
513
+ "spinbutton": {"aria-valuenow"},
514
+ "switch": {"aria-checked"},
515
+ }
516
+
517
+ var jsxRoleSupportedProps = map[string]map[string]bool{
518
+ "button": {"aria-expanded": true, "aria-pressed": true},
519
+ "checkbox": {"aria-checked": true, "aria-readonly": true},
520
+ "combobox": {"aria-activedescendant": true, "aria-autocomplete": true, "aria-controls": true, "aria-expanded": true, "aria-readonly": true, "aria-required": true},
521
+ "heading": {"aria-level": true},
522
+ "img": {"aria-label": true, "aria-labelledby": true},
523
+ "link": {"aria-expanded": true},
524
+ "radio": {"aria-checked": true},
525
+ "slider": {"aria-orientation": true, "aria-valuemax": true, "aria-valuemin": true, "aria-valuenow": true, "aria-valuetext": true},
526
+ "spinbutton": {"aria-valuemax": true, "aria-valuemin": true, "aria-valuenow": true, "aria-valuetext": true},
527
+ "switch": {"aria-checked": true},
528
+ "tab": {"aria-controls": true, "aria-selected": true},
529
+ "textbox": {"aria-activedescendant": true, "aria-autocomplete": true, "aria-multiline": true, "aria-placeholder": true, "aria-readonly": true, "aria-required": true},
530
+ }
531
+
532
+ var jsxRedundantRoles = map[string]string{
533
+ "button": "button", "img": "img", "main": "main", "nav": "navigation", "ul": "list", "ol": "list",
534
+ "li": "listitem", "table": "table", "tr": "row", "td": "cell",
535
+ }
536
+
537
+ var jsxRolePreferredTag = map[string]string{
538
+ "button": "button", "checkbox": "input", "heading": "h1-h6", "img": "img", "link": "a",
539
+ "list": "ul or ol", "listitem": "li", "main": "main", "navigation": "nav", "radio": "input",
540
+ "table": "table", "textbox": "input or textarea",
541
+ }
542
+
543
+ var jsxAutocompleteTokens = map[string]bool{
544
+ "on": true, "off": true, "name": true, "honorific-prefix": true, "given-name": true,
545
+ "additional-name": true, "family-name": true, "honorific-suffix": true, "nickname": true,
546
+ "email": true, "username": true, "new-password": true, "current-password": true,
547
+ "one-time-code": true, "organization-title": true, "organization": true,
548
+ "street-address": true, "address-line1": true, "address-line2": true, "address-line3": true,
549
+ "address-level1": true, "address-level2": true, "address-level3": true, "address-level4": true,
550
+ "country": true, "country-name": true, "postal-code": true, "cc-name": true,
551
+ "cc-given-name": true, "cc-additional-name": true, "cc-family-name": true, "cc-number": true,
552
+ "cc-exp": true, "cc-exp-month": true, "cc-exp-year": true, "cc-csc": true, "cc-type": true,
553
+ "transaction-currency": true, "transaction-amount": true, "language": true, "bday": true,
554
+ "bday-day": true, "bday-month": true, "bday-year": true, "sex": true, "tel": true,
555
+ "tel-country-code": true, "tel-national": true, "tel-area-code": true, "tel-local": true,
556
+ "tel-extension": true, "impp": true, "url": true, "photo": true,
557
+ }
558
+
559
+ type jsxA11yAltText struct{}
560
+
561
+ func (jsxA11yAltText) Name() string { return "jsx-a11y/alt-text" }
562
+ func (jsxA11yAltText) Visits() []shimast.Kind {
563
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
564
+ }
565
+ func (jsxA11yAltText) Check(ctx *Context, node *shimast.Node) {
566
+ info := jsxElementFromNode(node)
567
+ switch info.tag {
568
+ case "img", "area":
569
+ if !jsxHasAttr(info.attrs, "alt", "aria-label", "aria-labelledby") {
570
+ ctx.Report(info.opening, "Image-like elements must have alt text.")
571
+ }
572
+ case "input":
573
+ attr, ok := jsxKnownAttr(info.attrs, "type")
574
+ if ok && strings.EqualFold(attr.value, "image") && !jsxHasAttr(info.attrs, "alt", "aria-label", "aria-labelledby") {
575
+ ctx.Report(info.opening, "Image input elements must have alt text.")
576
+ }
577
+ case "object":
578
+ if !jsxHasAccessibleLabel(info) {
579
+ ctx.Report(info.opening, "Object elements must have accessible text.")
580
+ }
581
+ }
582
+ }
583
+
584
+ type jsxA11yAnchorHasContent struct{}
585
+
586
+ func (jsxA11yAnchorHasContent) Name() string { return "jsx-a11y/anchor-has-content" }
587
+ func (jsxA11yAnchorHasContent) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindJsxElement} }
588
+ func (jsxA11yAnchorHasContent) Check(ctx *Context, node *shimast.Node) {
589
+ info := jsxElementFromNode(node)
590
+ if info.tag == "a" && !jsxHasAccessibleLabel(info) {
591
+ ctx.Report(info.opening, "Anchors must have accessible content.")
592
+ }
593
+ }
594
+
595
+ type jsxA11yAnchorIsValid struct{}
596
+
597
+ func (jsxA11yAnchorIsValid) Name() string { return "jsx-a11y/anchor-is-valid" }
598
+ func (jsxA11yAnchorIsValid) Visits() []shimast.Kind {
599
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
600
+ }
601
+ func (jsxA11yAnchorIsValid) Check(ctx *Context, node *shimast.Node) {
602
+ info := jsxElementFromNode(node)
603
+ if info.tag != "a" {
604
+ return
605
+ }
606
+ href, ok := jsxKnownAttr(info.attrs, "href")
607
+ if !ok {
608
+ ctx.Report(info.opening, "Anchor elements must have a valid href.")
609
+ return
610
+ }
611
+ value := strings.TrimSpace(strings.ToLower(href.value))
612
+ if value == "" || value == "#" || strings.HasPrefix(value, "javascript:") {
613
+ ctx.Report(href.node, "Anchor href must be a valid navigation target.")
614
+ }
615
+ }
616
+
617
+ type jsxA11yAriaActivedescendantHasTabindex struct{}
618
+
619
+ func (jsxA11yAriaActivedescendantHasTabindex) Name() string {
620
+ return "jsx-a11y/aria-activedescendant-has-tabindex"
621
+ }
622
+ func (jsxA11yAriaActivedescendantHasTabindex) Visits() []shimast.Kind {
623
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
624
+ }
625
+ func (jsxA11yAriaActivedescendantHasTabindex) Check(ctx *Context, node *shimast.Node) {
626
+ info := jsxElementFromNode(node)
627
+ if jsxHasAttr(info.attrs, "aria-activedescendant") && !jsxHasAttr(info.attrs, "tabIndex", "tabindex") {
628
+ ctx.Report(info.opening, "Elements with aria-activedescendant must define tabIndex.")
629
+ }
630
+ }
631
+
632
+ type jsxA11yAriaProps struct{}
633
+
634
+ func (jsxA11yAriaProps) Name() string { return "jsx-a11y/aria-props" }
635
+ func (jsxA11yAriaProps) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindJsxAttribute} }
636
+ func (jsxA11yAriaProps) Check(ctx *Context, node *shimast.Node) {
637
+ attr := node.AsJsxAttribute()
638
+ if attr == nil || attr.Name() == nil {
639
+ return
640
+ }
641
+ name := strings.ToLower(jsxAttrName(attr.Name()))
642
+ if strings.HasPrefix(name, "aria-") && !jsxAriaProps[name] {
643
+ ctx.Report(node, "Unknown ARIA property "+name+".")
644
+ }
645
+ }
646
+
647
+ type jsxA11yAriaProptypes struct{}
648
+
649
+ func (jsxA11yAriaProptypes) Name() string { return "jsx-a11y/aria-proptypes" }
650
+ func (jsxA11yAriaProptypes) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindJsxAttribute} }
651
+ func (jsxA11yAriaProptypes) Check(ctx *Context, node *shimast.Node) {
652
+ attr := node.AsJsxAttribute()
653
+ if attr == nil || attr.Name() == nil {
654
+ return
655
+ }
656
+ name := strings.ToLower(jsxAttrName(attr.Name()))
657
+ value := strings.ToLower(strings.TrimSpace(jsxAttrValue(attr.Initializer)))
658
+ if !jsxAttrValueKnown(attr.Initializer) || value == "" {
659
+ return
660
+ }
661
+ switch name {
662
+ case "aria-hidden", "aria-disabled", "aria-expanded", "aria-modal", "aria-multiline", "aria-multiselectable", "aria-readonly", "aria-required", "aria-selected":
663
+ if value != "true" && value != "false" {
664
+ ctx.Report(node, name+" must be true or false.")
665
+ }
666
+ case "aria-checked", "aria-pressed":
667
+ if value != "true" && value != "false" && value != "mixed" {
668
+ ctx.Report(node, name+" must be true, false, or mixed.")
669
+ }
670
+ case "aria-level", "aria-valuemax", "aria-valuemin", "aria-valuenow", "aria-posinset", "aria-setsize", "aria-colcount", "aria-colindex", "aria-colspan", "aria-rowcount", "aria-rowindex", "aria-rowspan":
671
+ if _, err := strconv.Atoi(value); err != nil {
672
+ ctx.Report(node, name+" must be numeric.")
673
+ }
674
+ }
675
+ }
676
+
677
+ type jsxA11yAriaRole struct{}
678
+
679
+ func (jsxA11yAriaRole) Name() string { return "jsx-a11y/aria-role" }
680
+ func (jsxA11yAriaRole) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindJsxAttribute} }
681
+ func (jsxA11yAriaRole) Check(ctx *Context, node *shimast.Node) {
682
+ attr := node.AsJsxAttribute()
683
+ if attr == nil || attr.Name() == nil || jsxAttrName(attr.Name()) != "role" || !jsxAttrValueKnown(attr.Initializer) {
684
+ return
685
+ }
686
+ tokens := strings.Fields(strings.ToLower(jsxAttrValue(attr.Initializer)))
687
+ if len(tokens) == 0 {
688
+ ctx.Report(node, "Role must not be empty.")
689
+ return
690
+ }
691
+ for _, token := range tokens {
692
+ if jsxA11yRoles[token] {
693
+ return
694
+ }
695
+ }
696
+ ctx.Report(node, "Role must be a valid ARIA role.")
697
+ }
698
+
699
+ type jsxA11yAriaUnsupportedElements struct{}
700
+
701
+ func (jsxA11yAriaUnsupportedElements) Name() string { return "jsx-a11y/aria-unsupported-elements" }
702
+ func (jsxA11yAriaUnsupportedElements) Visits() []shimast.Kind {
703
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
704
+ }
705
+ func (jsxA11yAriaUnsupportedElements) Check(ctx *Context, node *shimast.Node) {
706
+ info := jsxElementFromNode(node)
707
+ switch info.tag {
708
+ case "meta", "html", "script", "style":
709
+ if jsxHasAttr(info.attrs, "role") {
710
+ ctx.Report(info.opening, "This element does not support ARIA roles.")
711
+ return
712
+ }
713
+ for name := range info.attrs {
714
+ if strings.HasPrefix(strings.ToLower(name), "aria-") {
715
+ ctx.Report(info.opening, "This element does not support ARIA attributes.")
716
+ return
717
+ }
718
+ }
719
+ }
720
+ }
721
+
722
+ type jsxA11yAutocompleteValid struct{}
723
+
724
+ func (jsxA11yAutocompleteValid) Name() string { return "jsx-a11y/autocomplete-valid" }
725
+ func (jsxA11yAutocompleteValid) Visits() []shimast.Kind {
726
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
727
+ }
728
+ func (jsxA11yAutocompleteValid) Check(ctx *Context, node *shimast.Node) {
729
+ info := jsxElementFromNode(node)
730
+ attr, ok := jsxKnownAttr(info.attrs, "autoComplete")
731
+ if !ok {
732
+ attr, ok = jsxKnownAttr(info.attrs, "autocomplete")
733
+ }
734
+ if !ok || strings.TrimSpace(attr.value) == "" {
735
+ return
736
+ }
737
+ for _, token := range strings.Fields(strings.ToLower(attr.value)) {
738
+ if strings.HasPrefix(token, "section-") || token == "shipping" || token == "billing" || token == "home" || token == "work" || token == "mobile" || token == "fax" || token == "pager" {
739
+ continue
740
+ }
741
+ if !jsxAutocompleteTokens[token] {
742
+ ctx.Report(attr.node, "autocomplete contains an invalid token.")
743
+ return
744
+ }
745
+ }
746
+ }
747
+
748
+ type jsxA11yClickEventsHaveKeyEvents struct{}
749
+
750
+ func (jsxA11yClickEventsHaveKeyEvents) Name() string { return "jsx-a11y/click-events-have-key-events" }
751
+ func (jsxA11yClickEventsHaveKeyEvents) Visits() []shimast.Kind {
752
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
753
+ }
754
+ func (jsxA11yClickEventsHaveKeyEvents) Check(ctx *Context, node *shimast.Node) {
755
+ info := jsxElementFromNode(node)
756
+ if !jsxHasAttr(info.attrs, "onClick", "onclick") || jsxIsHidden(info) || jsxIsInteractive(info) || jsxHasKeyboardHandler(info.attrs) {
757
+ return
758
+ }
759
+ ctx.Report(info.opening, "Clickable non-interactive elements must also handle keyboard events.")
760
+ }
761
+
762
+ type jsxA11yControlHasAssociatedLabel struct{}
763
+
764
+ func (jsxA11yControlHasAssociatedLabel) Name() string { return "jsx-a11y/control-has-associated-label" }
765
+ func (jsxA11yControlHasAssociatedLabel) Visits() []shimast.Kind {
766
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
767
+ }
768
+ func (jsxA11yControlHasAssociatedLabel) Check(ctx *Context, node *shimast.Node) {
769
+ info := jsxElementFromNode(node)
770
+ if (jsxIsFormControl(info) || jsxIsInteractive(info)) && !jsxHasAccessibleLabel(info) {
771
+ ctx.Report(info.opening, "Interactive controls must have an accessible label.")
772
+ }
773
+ }
774
+
775
+ type jsxA11yHeadingHasContent struct{}
776
+
777
+ func (jsxA11yHeadingHasContent) Name() string { return "jsx-a11y/heading-has-content" }
778
+ func (jsxA11yHeadingHasContent) Visits() []shimast.Kind {
779
+ return []shimast.Kind{shimast.KindJsxElement}
780
+ }
781
+ func (jsxA11yHeadingHasContent) Check(ctx *Context, node *shimast.Node) {
782
+ info := jsxElementFromNode(node)
783
+ switch info.tag {
784
+ case "h1", "h2", "h3", "h4", "h5", "h6":
785
+ if !jsxHasAccessibleLabel(info) {
786
+ ctx.Report(info.opening, "Headings must have accessible content.")
787
+ }
788
+ }
789
+ }
790
+
791
+ type jsxA11yHtmlHasLang struct{}
792
+
793
+ func (jsxA11yHtmlHasLang) Name() string { return "jsx-a11y/html-has-lang" }
794
+ func (jsxA11yHtmlHasLang) Visits() []shimast.Kind {
795
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
796
+ }
797
+ func (jsxA11yHtmlHasLang) Check(ctx *Context, node *shimast.Node) {
798
+ info := jsxElementFromNode(node)
799
+ if info.tag == "html" && !jsxHasAttr(info.attrs, "lang") {
800
+ ctx.Report(info.opening, "The html element must have a lang attribute.")
801
+ }
802
+ }
803
+
804
+ type jsxA11yIframeHasTitle struct{}
805
+
806
+ func (jsxA11yIframeHasTitle) Name() string { return "jsx-a11y/iframe-has-title" }
807
+ func (jsxA11yIframeHasTitle) Visits() []shimast.Kind {
808
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
809
+ }
810
+ func (jsxA11yIframeHasTitle) Check(ctx *Context, node *shimast.Node) {
811
+ info := jsxElementFromNode(node)
812
+ if info.tag == "iframe" {
813
+ attr, ok := jsxKnownAttr(info.attrs, "title")
814
+ if !ok || strings.TrimSpace(attr.value) == "" {
815
+ ctx.Report(info.opening, "Iframes must have a non-empty title.")
816
+ }
817
+ }
818
+ }
819
+
820
+ type jsxA11yImgRedundantAlt struct{}
821
+
822
+ func (jsxA11yImgRedundantAlt) Name() string { return "jsx-a11y/img-redundant-alt" }
823
+ func (jsxA11yImgRedundantAlt) Visits() []shimast.Kind {
824
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
825
+ }
826
+ func (jsxA11yImgRedundantAlt) Check(ctx *Context, node *shimast.Node) {
827
+ info := jsxElementFromNode(node)
828
+ if info.tag != "img" {
829
+ return
830
+ }
831
+ attr, ok := jsxKnownAttr(info.attrs, "alt")
832
+ if !ok {
833
+ return
834
+ }
835
+ value := strings.ToLower(attr.value)
836
+ if strings.Contains(value, "image") || strings.Contains(value, "photo") || strings.Contains(value, "picture") {
837
+ ctx.Report(attr.node, "Image alt text should not contain redundant words like image, photo, or picture.")
838
+ }
839
+ }
840
+
841
+ type jsxA11yInteractiveSupportsFocus struct{}
842
+
843
+ func (jsxA11yInteractiveSupportsFocus) Name() string { return "jsx-a11y/interactive-supports-focus" }
844
+ func (jsxA11yInteractiveSupportsFocus) Visits() []shimast.Kind {
845
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
846
+ }
847
+ func (jsxA11yInteractiveSupportsFocus) Check(ctx *Context, node *shimast.Node) {
848
+ info := jsxElementFromNode(node)
849
+ if role, ok := jsxRole(info.attrs); ok && jsxInteractiveRoles[role] && !jsxIsFocusable(info) {
850
+ ctx.Report(info.opening, "Elements with interactive roles must be focusable.")
851
+ }
852
+ }
853
+
854
+ type jsxA11yLabelHasAssociatedControl struct{}
855
+
856
+ func (jsxA11yLabelHasAssociatedControl) Name() string { return "jsx-a11y/label-has-associated-control" }
857
+ func (jsxA11yLabelHasAssociatedControl) Visits() []shimast.Kind {
858
+ return []shimast.Kind{shimast.KindJsxElement}
859
+ }
860
+ func (jsxA11yLabelHasAssociatedControl) Check(ctx *Context, node *shimast.Node) {
861
+ info := jsxElementFromNode(node)
862
+ if info.tag == "label" && !jsxHasAttr(info.attrs, "htmlFor", "for") && !jsxHasDescendantControl(info.children) {
863
+ ctx.Report(info.opening, "Labels must be associated with a control.")
864
+ }
865
+ }
866
+
867
+ type jsxA11yLabelHasFor struct{}
868
+
869
+ func (jsxA11yLabelHasFor) Name() string { return "jsx-a11y/label-has-for" }
870
+ func (jsxA11yLabelHasFor) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindJsxElement} }
871
+ func (jsxA11yLabelHasFor) Check(ctx *Context, node *shimast.Node) {
872
+ jsxA11yLabelHasAssociatedControl{}.Check(ctx, node)
873
+ }
874
+
875
+ type jsxA11yLang struct{}
876
+
877
+ func (jsxA11yLang) Name() string { return "jsx-a11y/lang" }
878
+ func (jsxA11yLang) Visits() []shimast.Kind {
879
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
880
+ }
881
+ func (jsxA11yLang) Check(ctx *Context, node *shimast.Node) {
882
+ info := jsxElementFromNode(node)
883
+ attr, ok := jsxKnownAttr(info.attrs, "lang")
884
+ if !ok {
885
+ return
886
+ }
887
+ value := strings.TrimSpace(attr.value)
888
+ parts := strings.Split(value, "-")
889
+ if value == "" || len(parts[0]) < 2 || len(parts[0]) > 3 {
890
+ ctx.Report(attr.node, "lang must be a valid BCP 47 language tag.")
891
+ }
892
+ }
893
+
894
+ type jsxA11yMediaHasCaption struct{}
895
+
896
+ func (jsxA11yMediaHasCaption) Name() string { return "jsx-a11y/media-has-caption" }
897
+ func (jsxA11yMediaHasCaption) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindJsxElement} }
898
+ func (jsxA11yMediaHasCaption) Check(ctx *Context, node *shimast.Node) {
899
+ info := jsxElementFromNode(node)
900
+ if (info.tag == "audio" || info.tag == "video") && !jsxHasTrackCaption(info.children) {
901
+ ctx.Report(info.opening, "Media elements must provide caption tracks.")
902
+ }
903
+ }
904
+
905
+ type jsxA11yMouseEventsHaveKeyEvents struct{}
906
+
907
+ func (jsxA11yMouseEventsHaveKeyEvents) Name() string { return "jsx-a11y/mouse-events-have-key-events" }
908
+ func (jsxA11yMouseEventsHaveKeyEvents) Visits() []shimast.Kind {
909
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
910
+ }
911
+ func (jsxA11yMouseEventsHaveKeyEvents) Check(ctx *Context, node *shimast.Node) {
912
+ info := jsxElementFromNode(node)
913
+ if jsxHasAttr(info.attrs, "onMouseOver", "onmouseover") && !jsxHasAttr(info.attrs, "onFocus", "onfocus") {
914
+ ctx.Report(info.opening, "onMouseOver must be paired with onFocus.")
915
+ return
916
+ }
917
+ if jsxHasAttr(info.attrs, "onMouseOut", "onmouseout") && !jsxHasAttr(info.attrs, "onBlur", "onblur") {
918
+ ctx.Report(info.opening, "onMouseOut must be paired with onBlur.")
919
+ }
920
+ }
921
+
922
+ type jsxA11yNoAccessKey struct{}
923
+
924
+ func (jsxA11yNoAccessKey) Name() string { return "jsx-a11y/no-access-key" }
925
+ func (jsxA11yNoAccessKey) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindJsxAttribute} }
926
+ func (jsxA11yNoAccessKey) Check(ctx *Context, node *shimast.Node) {
927
+ attr := node.AsJsxAttribute()
928
+ if attr != nil && attr.Name() != nil && jsxAttrName(attr.Name()) == "accessKey" {
929
+ ctx.Report(node, "Do not use accessKey.")
930
+ }
931
+ }
932
+
933
+ type jsxA11yNoAriaHiddenOnFocusable struct{}
934
+
935
+ func (jsxA11yNoAriaHiddenOnFocusable) Name() string { return "jsx-a11y/no-aria-hidden-on-focusable" }
936
+ func (jsxA11yNoAriaHiddenOnFocusable) Visits() []shimast.Kind {
937
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
938
+ }
939
+ func (jsxA11yNoAriaHiddenOnFocusable) Check(ctx *Context, node *shimast.Node) {
940
+ info := jsxElementFromNode(node)
941
+ if hidden, ok := jsxBoolAttr(info.attrs, "aria-hidden"); ok && hidden && jsxIsFocusable(info) {
942
+ ctx.Report(info.opening, "Focusable elements must not be aria-hidden.")
943
+ }
944
+ }
945
+
946
+ type jsxA11yNoAutofocus struct{}
947
+
948
+ func (jsxA11yNoAutofocus) Name() string { return "jsx-a11y/no-autofocus" }
949
+ func (jsxA11yNoAutofocus) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindJsxAttribute} }
950
+ func (jsxA11yNoAutofocus) Check(ctx *Context, node *shimast.Node) {
951
+ attr := node.AsJsxAttribute()
952
+ if attr == nil || attr.Name() == nil {
953
+ return
954
+ }
955
+ name := jsxAttrName(attr.Name())
956
+ if name == "autoFocus" || name == "autofocus" {
957
+ ctx.Report(node, "Do not use autoFocus.")
958
+ }
959
+ }
960
+
961
+ type jsxA11yNoDistractingElements struct{}
962
+
963
+ func (jsxA11yNoDistractingElements) Name() string { return "jsx-a11y/no-distracting-elements" }
964
+ func (jsxA11yNoDistractingElements) Visits() []shimast.Kind {
965
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
966
+ }
967
+ func (jsxA11yNoDistractingElements) Check(ctx *Context, node *shimast.Node) {
968
+ info := jsxElementFromNode(node)
969
+ if info.tag == "blink" || info.tag == "marquee" {
970
+ ctx.Report(info.opening, "Do not use distracting elements.")
971
+ }
972
+ }
973
+
974
+ type jsxA11yNoInteractiveElementToNoninteractiveRole struct{}
975
+
976
+ func (jsxA11yNoInteractiveElementToNoninteractiveRole) Name() string {
977
+ return "jsx-a11y/no-interactive-element-to-noninteractive-role"
978
+ }
979
+ func (jsxA11yNoInteractiveElementToNoninteractiveRole) Visits() []shimast.Kind {
980
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
981
+ }
982
+ func (jsxA11yNoInteractiveElementToNoninteractiveRole) Check(ctx *Context, node *shimast.Node) {
983
+ info := jsxElementFromNode(node)
984
+ role, ok := jsxRole(info.attrs)
985
+ if ok && jsxIsInteractive(info) && jsxNonInteractiveRoles[role] {
986
+ ctx.Report(info.opening, "Interactive elements must not use non-interactive roles.")
987
+ }
988
+ }
989
+
990
+ type jsxA11yNoNoninteractiveElementInteractions struct{}
991
+
992
+ func (jsxA11yNoNoninteractiveElementInteractions) Name() string {
993
+ return "jsx-a11y/no-noninteractive-element-interactions"
994
+ }
995
+ func (jsxA11yNoNoninteractiveElementInteractions) Visits() []shimast.Kind {
996
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
997
+ }
998
+ func (jsxA11yNoNoninteractiveElementInteractions) Check(ctx *Context, node *shimast.Node) {
999
+ info := jsxElementFromNode(node)
1000
+ if jsxIsNonInteractiveElement(info.tag) && jsxHasMouseOrKeyboardInteraction(info.attrs) {
1001
+ ctx.Report(info.opening, "Non-interactive elements must not have interaction handlers.")
1002
+ }
1003
+ }
1004
+
1005
+ type jsxA11yNoNoninteractiveElementToInteractiveRole struct{}
1006
+
1007
+ func (jsxA11yNoNoninteractiveElementToInteractiveRole) Name() string {
1008
+ return "jsx-a11y/no-noninteractive-element-to-interactive-role"
1009
+ }
1010
+ func (jsxA11yNoNoninteractiveElementToInteractiveRole) Visits() []shimast.Kind {
1011
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
1012
+ }
1013
+ func (jsxA11yNoNoninteractiveElementToInteractiveRole) Check(ctx *Context, node *shimast.Node) {
1014
+ info := jsxElementFromNode(node)
1015
+ role, ok := jsxRole(info.attrs)
1016
+ if ok && jsxIsNonInteractiveElement(info.tag) && jsxInteractiveRoles[role] {
1017
+ ctx.Report(info.opening, "Non-interactive elements must not use interactive roles.")
1018
+ }
1019
+ }
1020
+
1021
+ type jsxA11yNoNoninteractiveTabindex struct{}
1022
+
1023
+ func (jsxA11yNoNoninteractiveTabindex) Name() string { return "jsx-a11y/no-noninteractive-tabindex" }
1024
+ func (jsxA11yNoNoninteractiveTabindex) Visits() []shimast.Kind {
1025
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
1026
+ }
1027
+ func (jsxA11yNoNoninteractiveTabindex) Check(ctx *Context, node *shimast.Node) {
1028
+ info := jsxElementFromNode(node)
1029
+ if _, ok := jsxNumericAttr(info.attrs, "tabIndex", "tabindex"); ok && !jsxIsInteractive(info) {
1030
+ ctx.Report(info.opening, "Non-interactive elements must not be focusable with tabIndex.")
1031
+ }
1032
+ }
1033
+
1034
+ type jsxA11yNoRedundantRoles struct{}
1035
+
1036
+ func (jsxA11yNoRedundantRoles) Name() string { return "jsx-a11y/no-redundant-roles" }
1037
+ func (jsxA11yNoRedundantRoles) Visits() []shimast.Kind {
1038
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
1039
+ }
1040
+ func (jsxA11yNoRedundantRoles) Check(ctx *Context, node *shimast.Node) {
1041
+ info := jsxElementFromNode(node)
1042
+ role, ok := jsxRole(info.attrs)
1043
+ if !ok {
1044
+ return
1045
+ }
1046
+ implicit := jsxImplicitRole(info)
1047
+ if implicit == "" {
1048
+ implicit = jsxRedundantRoles[info.tag]
1049
+ }
1050
+ if implicit == role {
1051
+ ctx.Report(info.opening, "This role is redundant on the element.")
1052
+ }
1053
+ }
1054
+
1055
+ type jsxA11yNoStaticElementInteractions struct{}
1056
+
1057
+ func (jsxA11yNoStaticElementInteractions) Name() string {
1058
+ return "jsx-a11y/no-static-element-interactions"
1059
+ }
1060
+ func (jsxA11yNoStaticElementInteractions) Visits() []shimast.Kind {
1061
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
1062
+ }
1063
+ func (jsxA11yNoStaticElementInteractions) Check(ctx *Context, node *shimast.Node) {
1064
+ info := jsxElementFromNode(node)
1065
+ if info.tag == "" || jsxIsInteractive(info) || jsxIsNonInteractiveElement(info.tag) || jsxHasAttr(info.attrs, "role") || !jsxHasMouseOrKeyboardInteraction(info.attrs) {
1066
+ return
1067
+ }
1068
+ ctx.Report(info.opening, "Static elements with interaction handlers must have an ARIA role.")
1069
+ }
1070
+
1071
+ type jsxA11yPreferTagOverRole struct{}
1072
+
1073
+ func (jsxA11yPreferTagOverRole) Name() string { return "jsx-a11y/prefer-tag-over-role" }
1074
+ func (jsxA11yPreferTagOverRole) Visits() []shimast.Kind {
1075
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
1076
+ }
1077
+ func (jsxA11yPreferTagOverRole) Check(ctx *Context, node *shimast.Node) {
1078
+ info := jsxElementFromNode(node)
1079
+ if info.tag == "" || info.tag != "div" && info.tag != "span" {
1080
+ return
1081
+ }
1082
+ role, ok := jsxRole(info.attrs)
1083
+ if ok {
1084
+ if tag, exists := jsxRolePreferredTag[role]; exists {
1085
+ ctx.Report(info.opening, "Prefer the native "+tag+" element over role="+role+".")
1086
+ }
1087
+ }
1088
+ }
1089
+
1090
+ type jsxA11yRoleHasRequiredAriaProps struct{}
1091
+
1092
+ func (jsxA11yRoleHasRequiredAriaProps) Name() string { return "jsx-a11y/role-has-required-aria-props" }
1093
+ func (jsxA11yRoleHasRequiredAriaProps) Visits() []shimast.Kind {
1094
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
1095
+ }
1096
+ func (jsxA11yRoleHasRequiredAriaProps) Check(ctx *Context, node *shimast.Node) {
1097
+ info := jsxElementFromNode(node)
1098
+ role, ok := jsxRole(info.attrs)
1099
+ if !ok {
1100
+ return
1101
+ }
1102
+ for _, required := range jsxRoleRequiredProps[role] {
1103
+ if !jsxHasAttr(info.attrs, required) {
1104
+ ctx.Report(info.opening, "Role "+role+" requires "+required+".")
1105
+ return
1106
+ }
1107
+ }
1108
+ }
1109
+
1110
+ type jsxA11yRoleSupportsAriaProps struct{}
1111
+
1112
+ func (jsxA11yRoleSupportsAriaProps) Name() string { return "jsx-a11y/role-supports-aria-props" }
1113
+ func (jsxA11yRoleSupportsAriaProps) Visits() []shimast.Kind {
1114
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
1115
+ }
1116
+ func (jsxA11yRoleSupportsAriaProps) Check(ctx *Context, node *shimast.Node) {
1117
+ info := jsxElementFromNode(node)
1118
+ role, ok := jsxRole(info.attrs)
1119
+ if !ok {
1120
+ return
1121
+ }
1122
+ if role == "" {
1123
+ return
1124
+ }
1125
+ supported := jsxRoleSupportedProps[role]
1126
+ for name, attr := range info.attrs {
1127
+ lower := strings.ToLower(name)
1128
+ if !strings.HasPrefix(lower, "aria-") || !jsxAriaProps[lower] || jsxGlobalAriaProps[lower] {
1129
+ continue
1130
+ }
1131
+ if supported == nil || !supported[lower] {
1132
+ ctx.Report(attr.node, "ARIA property "+lower+" is not supported by role "+role+".")
1133
+ return
1134
+ }
1135
+ }
1136
+ }
1137
+
1138
+ type jsxA11yScope struct{}
1139
+
1140
+ func (jsxA11yScope) Name() string { return "jsx-a11y/scope" }
1141
+ func (jsxA11yScope) Visits() []shimast.Kind {
1142
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
1143
+ }
1144
+ func (jsxA11yScope) Check(ctx *Context, node *shimast.Node) {
1145
+ info := jsxElementFromNode(node)
1146
+ if jsxHasAttr(info.attrs, "scope") && info.tag != "th" {
1147
+ ctx.Report(info.opening, "The scope attribute is only valid on th elements.")
1148
+ }
1149
+ }
1150
+
1151
+ type jsxA11yTabindexNoPositive struct{}
1152
+
1153
+ func (jsxA11yTabindexNoPositive) Name() string { return "jsx-a11y/tabindex-no-positive" }
1154
+ func (jsxA11yTabindexNoPositive) Visits() []shimast.Kind {
1155
+ return []shimast.Kind{shimast.KindJsxElement, shimast.KindJsxSelfClosingElement}
1156
+ }
1157
+ func (jsxA11yTabindexNoPositive) Check(ctx *Context, node *shimast.Node) {
1158
+ info := jsxElementFromNode(node)
1159
+ if value, ok := jsxNumericAttr(info.attrs, "tabIndex", "tabindex"); ok && value > 0 {
1160
+ ctx.Report(info.opening, "tabIndex must not be positive.")
1161
+ }
1162
+ }
1163
+
1164
+ // jsxA11yAnchorAmbiguousText reports `<a>` elements whose visible text
1165
+ // is one of a small set of phrases that carry no information out of
1166
+ // context. Screen-reader users frequently navigate by listing all
1167
+ // links; "click here" / "more" / "read more" turn that list into
1168
+ // uninformative noise.
1169
+ // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/anchor-ambiguous-text.md
1170
+ type jsxA11yAnchorAmbiguousText struct{}
1171
+
1172
+ func (jsxA11yAnchorAmbiguousText) Name() string { return "jsx-a11y/anchor-ambiguous-text" }
1173
+ func (jsxA11yAnchorAmbiguousText) Visits() []shimast.Kind {
1174
+ return []shimast.Kind{shimast.KindJsxElement}
1175
+ }
1176
+ func (jsxA11yAnchorAmbiguousText) Check(ctx *Context, node *shimast.Node) {
1177
+ info := jsxElementFromNode(node)
1178
+ if info.tag != "a" {
1179
+ return
1180
+ }
1181
+ text := strings.ToLower(strings.TrimSpace(jsxAnchorText(info.children)))
1182
+ if text == "" {
1183
+ return
1184
+ }
1185
+ switch text {
1186
+ case "click here", "here", "link", "a link", "click", "more", "read more":
1187
+ ctx.Report(info.opening, "Anchor text \""+text+"\" is too ambiguous out of context; describe the destination.")
1188
+ }
1189
+ }
1190
+
1191
+ // jsxAnchorText concatenates the visible text content of a JSX
1192
+ // element's children. Nested JSX is recursively concatenated. JSX
1193
+ // expressions that wrap a plain string literal contribute their literal
1194
+ // text; other expression shapes are skipped because their runtime value
1195
+ // is not known at lint time.
1196
+ func jsxAnchorText(children *shimast.NodeList) string {
1197
+ if children == nil {
1198
+ return ""
1199
+ }
1200
+ var b strings.Builder
1201
+ for _, child := range children.Nodes {
1202
+ if child == nil {
1203
+ continue
1204
+ }
1205
+ switch child.Kind {
1206
+ case shimast.KindJsxText:
1207
+ t := child.AsJsxText()
1208
+ if t != nil && !t.ContainsOnlyTriviaWhiteSpaces {
1209
+ b.WriteString(t.Text)
1210
+ }
1211
+ case shimast.KindJsxExpression:
1212
+ expr := child.AsJsxExpression()
1213
+ if expr != nil && expr.Expression != nil {
1214
+ if s := stringLiteralText(expr.Expression); s != "" {
1215
+ b.WriteString(s)
1216
+ }
1217
+ }
1218
+ case shimast.KindJsxElement:
1219
+ inner := jsxElementFromNode(child)
1220
+ b.WriteString(jsxAnchorText(inner.children))
1221
+ }
1222
+ }
1223
+ return b.String()
1224
+ }
1225
+
1226
+ func init() {
1227
+ Register(jsxA11yAltText{})
1228
+ Register(jsxA11yAnchorAmbiguousText{})
1229
+ Register(jsxA11yAnchorHasContent{})
1230
+ Register(jsxA11yAnchorIsValid{})
1231
+ Register(jsxA11yAriaActivedescendantHasTabindex{})
1232
+ Register(jsxA11yAriaProps{})
1233
+ Register(jsxA11yAriaProptypes{})
1234
+ Register(jsxA11yAriaRole{})
1235
+ Register(jsxA11yAriaUnsupportedElements{})
1236
+ Register(jsxA11yAutocompleteValid{})
1237
+ Register(jsxA11yClickEventsHaveKeyEvents{})
1238
+ Register(jsxA11yControlHasAssociatedLabel{})
1239
+ Register(jsxA11yHeadingHasContent{})
1240
+ Register(jsxA11yHtmlHasLang{})
1241
+ Register(jsxA11yIframeHasTitle{})
1242
+ Register(jsxA11yImgRedundantAlt{})
1243
+ Register(jsxA11yInteractiveSupportsFocus{})
1244
+ Register(jsxA11yLabelHasAssociatedControl{})
1245
+ Register(jsxA11yLabelHasFor{})
1246
+ Register(jsxA11yLang{})
1247
+ Register(jsxA11yMediaHasCaption{})
1248
+ Register(jsxA11yMouseEventsHaveKeyEvents{})
1249
+ Register(jsxA11yNoAccessKey{})
1250
+ Register(jsxA11yNoAriaHiddenOnFocusable{})
1251
+ Register(jsxA11yNoAutofocus{})
1252
+ Register(jsxA11yNoDistractingElements{})
1253
+ Register(jsxA11yNoInteractiveElementToNoninteractiveRole{})
1254
+ Register(jsxA11yNoNoninteractiveElementInteractions{})
1255
+ Register(jsxA11yNoNoninteractiveElementToInteractiveRole{})
1256
+ Register(jsxA11yNoNoninteractiveTabindex{})
1257
+ Register(jsxA11yNoRedundantRoles{})
1258
+ Register(jsxA11yNoStaticElementInteractions{})
1259
+ Register(jsxA11yPreferTagOverRole{})
1260
+ Register(jsxA11yRoleHasRequiredAriaProps{})
1261
+ Register(jsxA11yRoleSupportsAriaProps{})
1262
+ Register(jsxA11yScope{})
1263
+ Register(jsxA11yTabindexNoPositive{})
1264
+ }