@ttsc/lint 0.13.0 → 0.14.0-dev.20260528

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 (467) 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 +60 -37
  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/compile.go +33 -2
  116. package/linthost/config.go +48 -17
  117. package/linthost/config_format.go +9 -9
  118. package/linthost/directives.go +67 -25
  119. package/linthost/dispatch.go +14 -3
  120. package/linthost/engine.go +160 -40
  121. package/linthost/fix.go +2 -2
  122. package/linthost/flags_gen.go +17 -15
  123. package/linthost/host.go +40 -3
  124. package/linthost/lsp.go +747 -0
  125. package/linthost/print_dispatch.go +2 -2
  126. package/linthost/print_nodes_function.go +2 -2
  127. package/linthost/print_nodes_object.go +1 -1
  128. package/linthost/rule_names.go +14 -0
  129. package/linthost/rules_arrays.go +2 -2
  130. package/linthost/rules_boundaries.go +532 -0
  131. package/linthost/rules_camelcase.go +133 -0
  132. package/linthost/rules_complexity.go +94 -0
  133. package/linthost/rules_consistent_return.go +130 -0
  134. package/linthost/rules_console.go +1 -1
  135. package/linthost/rules_core_extra.go +739 -0
  136. package/linthost/rules_curly.go +70 -0
  137. package/linthost/rules_cypress.go +608 -0
  138. package/linthost/rules_debugger.go +2 -2
  139. package/linthost/rules_default_case.go +39 -0
  140. package/linthost/rules_default_case_last.go +46 -0
  141. package/linthost/rules_dupes.go +3 -3
  142. package/linthost/rules_empty.go +3 -3
  143. package/linthost/rules_escape.go +44 -24
  144. package/linthost/rules_eval.go +2 -2
  145. package/linthost/rules_finally.go +2 -2
  146. package/linthost/rules_format_jsdoc.go +1 -1
  147. package/linthost/rules_format_print_width.go +9 -9
  148. package/linthost/rules_format_quotes.go +1 -1
  149. package/linthost/rules_format_semi.go +1 -1
  150. package/linthost/rules_format_sort_imports.go +1 -1
  151. package/linthost/rules_format_trailing_comma.go +1 -1
  152. package/linthost/rules_functional.go +967 -0
  153. package/linthost/rules_gap.go +24 -45
  154. package/linthost/rules_grouped_accessor_pairs.go +87 -0
  155. package/linthost/rules_guard_for_in.go +198 -0
  156. package/linthost/rules_id_length.go +77 -0
  157. package/linthost/rules_imports.go +70 -2
  158. package/linthost/rules_init_declarations.go +53 -0
  159. package/linthost/rules_jest.go +618 -0
  160. package/linthost/rules_jsdoc.go +502 -0
  161. package/linthost/rules_jsx_a11y.go +1264 -0
  162. package/linthost/rules_logic.go +12 -12
  163. package/linthost/rules_loops.go +1 -1
  164. package/linthost/rules_max_classes_per_file.go +58 -0
  165. package/linthost/rules_max_depth.go +83 -0
  166. package/linthost/rules_max_lines.go +101 -0
  167. package/linthost/rules_max_lines_per_function.go +57 -0
  168. package/linthost/rules_max_nested_callbacks.go +61 -0
  169. package/linthost/rules_max_params.go +49 -0
  170. package/linthost/rules_max_statements.go +59 -0
  171. package/linthost/rules_misc.go +1 -1
  172. package/linthost/rules_nextjs.go +697 -0
  173. package/linthost/rules_no_else_return.go +75 -0
  174. package/linthost/rules_no_empty_named_blocks.go +91 -0
  175. package/linthost/rules_no_extend_native.go +51 -0
  176. package/linthost/rules_no_import_assign.go +140 -0
  177. package/linthost/rules_no_invalid_this.go +52 -0
  178. package/linthost/rules_no_loop_func.go +85 -0
  179. package/linthost/rules_no_magic_numbers.go +123 -0
  180. package/linthost/rules_no_mixed_operators.go +80 -0
  181. package/linthost/rules_no_param_reassign.go +98 -0
  182. package/linthost/rules_no_redeclare.go +270 -0
  183. package/linthost/rules_no_restricted_imports.go +105 -0
  184. package/linthost/rules_no_restricted_syntax.go +49 -0
  185. package/linthost/rules_no_shadow.go +147 -0
  186. package/linthost/rules_no_unreachable.go +77 -0
  187. package/linthost/rules_no_useless_assignment.go +120 -0
  188. package/linthost/rules_no_useless_constructor.go +245 -0
  189. package/linthost/rules_no_useless_return.go +51 -0
  190. package/linthost/rules_params.go +1 -1
  191. package/linthost/rules_playwright.go +788 -0
  192. package/linthost/rules_prefer_arrow_callback.go +124 -0
  193. package/linthost/rules_prefer_destructuring.go +98 -0
  194. package/linthost/rules_prefer_named_capture_group.go +129 -0
  195. package/linthost/rules_prefer_rest_params.go +94 -0
  196. package/linthost/rules_problems.go +41 -30
  197. package/linthost/rules_promise.go +1128 -73
  198. package/linthost/rules_protos.go +2 -2
  199. package/linthost/rules_react.go +773 -0
  200. package/linthost/rules_react_extras.go +227 -0
  201. package/linthost/rules_react_hooks.go +848 -0
  202. package/linthost/rules_react_perf.go +293 -0
  203. package/linthost/rules_react_refresh.go +583 -0
  204. package/linthost/rules_regexp.go +410 -0
  205. package/linthost/rules_security.go +830 -0
  206. package/linthost/rules_self.go +2 -2
  207. package/linthost/rules_solid.go +1073 -0
  208. package/linthost/rules_sort_imports.go +69 -0
  209. package/linthost/rules_sort_keys.go +134 -0
  210. package/linthost/rules_storybook.go +1176 -0
  211. package/linthost/rules_strings.go +4 -4
  212. package/linthost/rules_suggestions.go +38 -38
  213. package/linthost/rules_tanstack_query.go +844 -0
  214. package/linthost/rules_testing_library.go +1204 -0
  215. package/linthost/rules_throw.go +1 -1
  216. package/linthost/rules_ts.go +19 -19
  217. package/linthost/rules_ts_async.go +399 -0
  218. package/linthost/rules_ts_class_literal_property_style.go +115 -0
  219. package/linthost/rules_ts_consistent_type_exports.go +256 -0
  220. package/linthost/rules_ts_explicit_function_return_type.go +67 -0
  221. package/linthost/rules_ts_extra.go +155 -36
  222. package/linthost/rules_ts_no_base_to_string.go +218 -0
  223. package/linthost/rules_ts_no_confusing_void.go +72 -0
  224. package/linthost/rules_ts_no_deprecated.go +195 -0
  225. package/linthost/rules_ts_no_for_in_array.go +84 -0
  226. package/linthost/rules_ts_no_magic_numbers.go +124 -0
  227. package/linthost/rules_ts_no_meaningless_void_operator.go +84 -0
  228. package/linthost/rules_ts_no_misused_spread.go +104 -0
  229. package/linthost/rules_ts_no_redundant_type_constituents.go +113 -0
  230. package/linthost/rules_ts_no_restricted_types.go +62 -0
  231. package/linthost/rules_ts_no_unnecessary_boolean_literal_compare.go +129 -0
  232. package/linthost/rules_ts_no_unnecessary_condition.go +250 -0
  233. package/linthost/rules_ts_no_unnecessary_qualifier.go +118 -0
  234. package/linthost/rules_ts_no_unnecessary_template_expression.go +178 -0
  235. package/linthost/rules_ts_no_unnecessary_type_arguments.go +234 -0
  236. package/linthost/rules_ts_no_unnecessary_type_assertion.go +157 -0
  237. package/linthost/rules_ts_no_unsafe_argument.go +82 -0
  238. package/linthost/rules_ts_no_unsafe_assignment.go +102 -0
  239. package/linthost/rules_ts_no_unsafe_call.go +82 -0
  240. package/linthost/rules_ts_no_unsafe_enum_comparison.go +235 -0
  241. package/linthost/rules_ts_no_unsafe_member_access.go +58 -0
  242. package/linthost/rules_ts_no_unsafe_return.go +126 -0
  243. package/linthost/rules_ts_no_unsafe_unary_minus.go +81 -0
  244. package/linthost/rules_ts_no_useless_constructor.go +52 -0
  245. package/linthost/rules_ts_non_nullable_type_assertion_style.go +108 -0
  246. package/linthost/rules_ts_parameter_properties.go +38 -0
  247. package/linthost/rules_ts_prefer_find.go +178 -0
  248. package/linthost/rules_ts_prefer_includes.go +255 -0
  249. package/linthost/rules_ts_prefer_nullish_coalescing.go +104 -0
  250. package/linthost/rules_ts_prefer_optional_chain.go +228 -0
  251. package/linthost/rules_ts_prefer_promise_reject_errors.go +176 -0
  252. package/linthost/rules_ts_prefer_readonly.go +73 -0
  253. package/linthost/rules_ts_prefer_reduce_type_parameter.go +116 -0
  254. package/linthost/rules_ts_prefer_regexp_exec.go +122 -0
  255. package/linthost/rules_ts_prefer_return_this_type.go +135 -0
  256. package/linthost/rules_ts_prefer_string_starts_ends_with.go +360 -0
  257. package/linthost/rules_ts_promise_function_async.go +77 -0
  258. package/linthost/rules_ts_related_getter_setter_pairs.go +113 -0
  259. package/linthost/rules_ts_require_array_sort_compare.go +96 -0
  260. package/linthost/rules_ts_restrict_plus_operands.go +127 -0
  261. package/linthost/rules_ts_restrict_template_expressions.go +95 -0
  262. package/linthost/rules_ts_simple.go +387 -0
  263. package/linthost/rules_ts_sort_type_constituents.go +144 -0
  264. package/linthost/rules_ts_strict_boolean_expressions.go +157 -0
  265. package/linthost/rules_ts_switch_exhaustiveness_check.go +147 -0
  266. package/linthost/rules_ts_unbound_method.go +126 -0
  267. package/linthost/rules_tsdoc.go +136 -0
  268. package/linthost/rules_unicorn_better_regex.go +27 -0
  269. package/linthost/rules_unicorn_catch_error_name.go +46 -0
  270. package/linthost/rules_unicorn_consistent_assert.go +47 -0
  271. package/linthost/rules_unicorn_consistent_date_clone.go +54 -0
  272. package/linthost/rules_unicorn_consistent_destructuring.go +25 -0
  273. package/linthost/rules_unicorn_consistent_empty_array_spread.go +62 -0
  274. package/linthost/rules_unicorn_consistent_existence_index_check.go +80 -0
  275. package/linthost/rules_unicorn_consistent_function_scoping.go +25 -0
  276. package/linthost/rules_unicorn_consistent_template_literal_escape.go +25 -0
  277. package/linthost/rules_unicorn_custom_error_definition.go +138 -0
  278. package/linthost/rules_unicorn_empty_brace_spaces.go +73 -0
  279. package/linthost/rules_unicorn_error_message.go +64 -0
  280. package/linthost/rules_unicorn_escape_case.go +51 -0
  281. package/linthost/rules_unicorn_expiring_todo_comments.go +78 -0
  282. package/linthost/rules_unicorn_explicit_length_check.go +88 -0
  283. package/linthost/rules_unicorn_filename_case.go +29 -0
  284. package/linthost/rules_unicorn_import_style.go +28 -0
  285. package/linthost/rules_unicorn_isolated_functions.go +29 -0
  286. package/linthost/rules_unicorn_new_for_builtins.go +87 -0
  287. package/linthost/rules_unicorn_no_abusive_eslint_disable.go +63 -0
  288. package/linthost/rules_unicorn_no_accessor_recursion.go +115 -0
  289. package/linthost/rules_unicorn_no_anonymous_default_export.go +74 -0
  290. package/linthost/rules_unicorn_no_array_callback_reference.go +65 -0
  291. package/linthost/rules_unicorn_no_array_for_each.go +40 -0
  292. package/linthost/rules_unicorn_no_array_method_this_argument.go +61 -0
  293. package/linthost/rules_unicorn_no_array_reduce.go +41 -0
  294. package/linthost/rules_unicorn_no_array_reverse.go +44 -0
  295. package/linthost/rules_unicorn_no_array_sort.go +43 -0
  296. package/linthost/rules_unicorn_no_await_expression_member.go +40 -0
  297. package/linthost/rules_unicorn_no_await_in_promise_methods.go +69 -0
  298. package/linthost/rules_unicorn_no_console_spaces.go +61 -0
  299. package/linthost/rules_unicorn_no_document_cookie.go +41 -0
  300. package/linthost/rules_unicorn_no_empty_file.go +44 -0
  301. package/linthost/rules_unicorn_no_for_loop.go +102 -0
  302. package/linthost/rules_unicorn_no_hex_escape.go +42 -0
  303. package/linthost/rules_unicorn_no_immediate_mutation.go +90 -0
  304. package/linthost/rules_unicorn_no_instanceof_builtins.go +66 -0
  305. package/linthost/rules_unicorn_no_invalid_fetch_options.go +79 -0
  306. package/linthost/rules_unicorn_no_invalid_remove_event_listener.go +51 -0
  307. package/linthost/rules_unicorn_no_keyword_prefix.go +101 -0
  308. package/linthost/rules_unicorn_no_lonely_if.go +44 -0
  309. package/linthost/rules_unicorn_no_magic_array_flat_depth.go +51 -0
  310. package/linthost/rules_unicorn_no_named_default.go +54 -0
  311. package/linthost/rules_unicorn_no_negated_condition.go +85 -0
  312. package/linthost/rules_unicorn_no_negation_in_equality_check.go +50 -0
  313. package/linthost/rules_unicorn_no_nested_ternary.go +43 -0
  314. package/linthost/rules_unicorn_no_new_array.go +30 -0
  315. package/linthost/rules_unicorn_no_new_buffer.go +31 -0
  316. package/linthost/rules_unicorn_no_null.go +24 -0
  317. package/linthost/rules_unicorn_no_object_as_default_parameter.go +48 -0
  318. package/linthost/rules_unicorn_no_process_exit.go +43 -0
  319. package/linthost/rules_unicorn_no_single_promise_in_promise_methods.go +59 -0
  320. package/linthost/rules_unicorn_no_static_only_class.go +56 -0
  321. package/linthost/rules_unicorn_no_thenable.go +74 -0
  322. package/linthost/rules_unicorn_no_this_assignment.go +35 -0
  323. package/linthost/rules_unicorn_no_typeof_undefined.go +58 -0
  324. package/linthost/rules_unicorn_no_unnecessary_array_flat_depth.go +49 -0
  325. package/linthost/rules_unicorn_no_unnecessary_array_splice_count.go +70 -0
  326. package/linthost/rules_unicorn_no_unnecessary_await.go +51 -0
  327. package/linthost/rules_unicorn_no_unnecessary_polyfills.go +32 -0
  328. package/linthost/rules_unicorn_no_unnecessary_slice_end.go +48 -0
  329. package/linthost/rules_unicorn_no_unreadable_array_destructuring.go +115 -0
  330. package/linthost/rules_unicorn_no_unreadable_iife.go +50 -0
  331. package/linthost/rules_unicorn_no_unused_properties.go +30 -0
  332. package/linthost/rules_unicorn_no_useless_collection_argument.go +63 -0
  333. package/linthost/rules_unicorn_no_useless_error_capture_stack_trace.go +46 -0
  334. package/linthost/rules_unicorn_no_useless_fallback_in_spread.go +81 -0
  335. package/linthost/rules_unicorn_no_useless_iterator_to_array.go +72 -0
  336. package/linthost/rules_unicorn_no_useless_length_check.go +144 -0
  337. package/linthost/rules_unicorn_no_useless_promise_resolve_reject.go +81 -0
  338. package/linthost/rules_unicorn_no_useless_spread.go +67 -0
  339. package/linthost/rules_unicorn_no_useless_switch_case.go +55 -0
  340. package/linthost/rules_unicorn_no_useless_undefined.go +55 -0
  341. package/linthost/rules_unicorn_no_zero_fractions.go +52 -0
  342. package/linthost/rules_unicorn_number_literal_case.go +69 -0
  343. package/linthost/rules_unicorn_numeric_separators_style.go +75 -0
  344. package/linthost/rules_unicorn_prefer_add_event_listener.go +66 -0
  345. package/linthost/rules_unicorn_prefer_array_find.go +51 -0
  346. package/linthost/rules_unicorn_prefer_array_flat.go +52 -0
  347. package/linthost/rules_unicorn_prefer_array_flat_map.go +63 -0
  348. package/linthost/rules_unicorn_prefer_array_index_of.go +147 -0
  349. package/linthost/rules_unicorn_prefer_array_some.go +67 -0
  350. package/linthost/rules_unicorn_prefer_at.go +77 -0
  351. package/linthost/rules_unicorn_prefer_bigint_literals.go +77 -0
  352. package/linthost/rules_unicorn_prefer_blob_reading_methods.go +44 -0
  353. package/linthost/rules_unicorn_prefer_class_fields.go +93 -0
  354. package/linthost/rules_unicorn_prefer_classlist_toggle.go +120 -0
  355. package/linthost/rules_unicorn_prefer_code_point.go +49 -0
  356. package/linthost/rules_unicorn_prefer_date_now.go +78 -0
  357. package/linthost/rules_unicorn_prefer_default_parameters.go +144 -0
  358. package/linthost/rules_unicorn_prefer_dom_node_append.go +40 -0
  359. package/linthost/rules_unicorn_prefer_dom_node_dataset.go +55 -0
  360. package/linthost/rules_unicorn_prefer_dom_node_remove.go +41 -0
  361. package/linthost/rules_unicorn_prefer_dom_node_text_content.go +37 -0
  362. package/linthost/rules_unicorn_prefer_event_target.go +36 -0
  363. package/linthost/rules_unicorn_prefer_export_from.go +129 -0
  364. package/linthost/rules_unicorn_prefer_global_this.go +127 -0
  365. package/linthost/rules_unicorn_prefer_import_meta_properties.go +66 -0
  366. package/linthost/rules_unicorn_prefer_includes.go +149 -0
  367. package/linthost/rules_unicorn_prefer_json_parse_buffer.go +65 -0
  368. package/linthost/rules_unicorn_prefer_keyboard_event_key.go +38 -0
  369. package/linthost/rules_unicorn_prefer_logical_operator_over_ternary.go +48 -0
  370. package/linthost/rules_unicorn_prefer_math_min_max.go +60 -0
  371. package/linthost/rules_unicorn_prefer_math_trunc.go +62 -0
  372. package/linthost/rules_unicorn_prefer_modern_dom_apis.go +47 -0
  373. package/linthost/rules_unicorn_prefer_modern_math_apis.go +70 -0
  374. package/linthost/rules_unicorn_prefer_module.go +50 -0
  375. package/linthost/rules_unicorn_prefer_native_coercion_functions.go +114 -0
  376. package/linthost/rules_unicorn_prefer_negative_index.go +74 -0
  377. package/linthost/rules_unicorn_prefer_node_protocol.go +122 -0
  378. package/linthost/rules_unicorn_prefer_number_properties.go +88 -0
  379. package/linthost/rules_unicorn_prefer_object_from_entries.go +55 -0
  380. package/linthost/rules_unicorn_prefer_optional_catch_binding.go +62 -0
  381. package/linthost/rules_unicorn_prefer_prototype_methods.go +54 -0
  382. package/linthost/rules_unicorn_prefer_query_selector.go +56 -0
  383. package/linthost/rules_unicorn_prefer_reflect_apply.go +36 -0
  384. package/linthost/rules_unicorn_prefer_regexp_test_rule.go +80 -0
  385. package/linthost/rules_unicorn_prefer_response_static_json.go +53 -0
  386. package/linthost/rules_unicorn_prefer_set_has.go +42 -0
  387. package/linthost/rules_unicorn_prefer_set_size.go +48 -0
  388. package/linthost/rules_unicorn_prefer_simple_condition_first.go +34 -0
  389. package/linthost/rules_unicorn_prefer_single_call.go +96 -0
  390. package/linthost/rules_unicorn_prefer_spread.go +51 -0
  391. package/linthost/rules_unicorn_prefer_string_raw.go +43 -0
  392. package/linthost/rules_unicorn_prefer_string_replace_all.go +95 -0
  393. package/linthost/rules_unicorn_prefer_string_slice.go +43 -0
  394. package/linthost/rules_unicorn_prefer_string_starts_ends_with.go +148 -0
  395. package/linthost/rules_unicorn_prefer_string_trim_start_end.go +48 -0
  396. package/linthost/rules_unicorn_prefer_structured_clone.go +71 -0
  397. package/linthost/rules_unicorn_prefer_switch.go +103 -0
  398. package/linthost/rules_unicorn_prefer_ternary.go +71 -0
  399. package/linthost/rules_unicorn_prefer_top_level_await.go +72 -0
  400. package/linthost/rules_unicorn_prefer_type_error.go +127 -0
  401. package/linthost/rules_unicorn_prevent_abbreviations.go +127 -0
  402. package/linthost/rules_unicorn_relative_url_style.go +49 -0
  403. package/linthost/rules_unicorn_require_array_join_separator.go +45 -0
  404. package/linthost/rules_unicorn_require_module_attributes.go +54 -0
  405. package/linthost/rules_unicorn_require_module_specifiers.go +55 -0
  406. package/linthost/rules_unicorn_require_number_to_fixed_digits_argument.go +43 -0
  407. package/linthost/rules_unicorn_require_post_message_target_origin.go +44 -0
  408. package/linthost/rules_unicorn_string_content.go +27 -0
  409. package/linthost/rules_unicorn_switch_case_braces.go +42 -0
  410. package/linthost/rules_unicorn_switch_case_break_position.go +31 -0
  411. package/linthost/rules_unicorn_template_indent.go +28 -0
  412. package/linthost/rules_unicorn_text_encoding_identifier_case.go +72 -0
  413. package/linthost/rules_unicorn_throw_new_error.go +61 -0
  414. package/linthost/rules_var.go +10 -4
  415. package/linthost/rules_vitest.go +568 -0
  416. package/package.json +3 -3
  417. package/plugin/main.go +49 -3
  418. package/rule/astutil/astutil.go +10 -3
  419. package/rule/rule.go +12 -10
  420. package/src/defaultFormat.ts +11 -8
  421. package/src/index.ts +65 -43
  422. package/src/structures/ITtscLintConfig.ts +12 -6
  423. package/src/structures/{ITtscLintFormatConfig.ts → ITtscLintFormat.ts} +2 -2
  424. package/src/structures/TtscLintRuleSetting.ts +55 -0
  425. package/src/structures/index.ts +3 -4
  426. package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +128 -0
  427. package/src/structures/rules/ITtscLintBoundariesRules.ts +95 -0
  428. package/src/structures/rules/ITtscLintContributorRules.ts +21 -0
  429. package/src/structures/rules/ITtscLintCoreRules.ts +1406 -0
  430. package/src/structures/rules/ITtscLintCypressRuleOptions.ts +18 -0
  431. package/src/structures/rules/ITtscLintCypressRules.ts +145 -0
  432. package/src/structures/rules/ITtscLintFunctionalRuleOptions.ts +213 -0
  433. package/src/structures/rules/ITtscLintFunctionalRules.ts +202 -0
  434. package/src/structures/rules/ITtscLintJestRules.ts +220 -0
  435. package/src/structures/rules/ITtscLintJsdocRules.ts +153 -0
  436. package/src/structures/rules/ITtscLintJsxA11yRules.ts +339 -0
  437. package/src/structures/rules/ITtscLintNextjsRules.ts +206 -0
  438. package/src/structures/rules/ITtscLintPlaywrightRules.ts +361 -0
  439. package/src/structures/rules/ITtscLintPromiseRules.ts +205 -0
  440. package/src/structures/rules/ITtscLintReactPerfRuleOptions.ts +19 -0
  441. package/src/structures/rules/ITtscLintReactPerfRules.ts +55 -0
  442. package/src/structures/rules/ITtscLintReactRuleOptions.ts +43 -0
  443. package/src/structures/rules/ITtscLintReactRules.ts +321 -0
  444. package/src/structures/rules/ITtscLintRegexpRules.ts +212 -0
  445. package/src/structures/rules/ITtscLintRuleOptionsMap.ts +89 -0
  446. package/src/structures/rules/ITtscLintRules.ts +77 -0
  447. package/src/structures/rules/ITtscLintSecurityRules.ts +162 -0
  448. package/src/structures/rules/ITtscLintSolidRules.ts +202 -0
  449. package/src/structures/rules/ITtscLintStorybookRuleOptions.ts +23 -0
  450. package/src/structures/rules/ITtscLintStorybookRules.ts +193 -0
  451. package/src/structures/rules/ITtscLintTanstackQueryRules.ts +102 -0
  452. package/src/structures/rules/ITtscLintTestingLibraryRuleOptions.ts +24 -0
  453. package/src/structures/rules/ITtscLintTestingLibraryRules.ts +342 -0
  454. package/src/structures/rules/ITtscLintTypeScriptRules.ts +1230 -0
  455. package/src/structures/rules/ITtscLintUnicornRules.ts +1143 -0
  456. package/src/structures/rules/ITtscLintVitestRules.ts +154 -0
  457. package/src/structures/rules/index.ts +31 -0
  458. package/lib/structures/ITtscLintFormatConfig.js.map +0 -1
  459. package/lib/structures/TtscLintRule.d.ts +0 -17
  460. package/lib/structures/TtscLintRule.js.map +0 -1
  461. package/lib/structures/TtscLintRuleMap.d.ts +0 -46
  462. package/lib/structures/TtscLintRuleMap.js.map +0 -1
  463. package/lib/structures/TtscLintRuleOptions.d.ts +0 -137
  464. package/lib/structures/TtscLintRuleOptions.js.map +0 -1
  465. package/src/structures/TtscLintRule.ts +0 -160
  466. package/src/structures/TtscLintRuleMap.ts +0 -55
  467. package/src/structures/TtscLintRuleOptions.ts +0 -151
@@ -0,0 +1,1204 @@
1
+ package linthost
2
+
3
+ import (
4
+ "encoding/json"
5
+ "path/filepath"
6
+ "regexp"
7
+ "strings"
8
+
9
+ shimast "github.com/microsoft/typescript-go/shim/ast"
10
+ )
11
+
12
+ type testingLibraryRule struct {
13
+ name string
14
+ }
15
+
16
+ func (r testingLibraryRule) Name() string { return "testing-library/" + r.name }
17
+ func (r testingLibraryRule) Visits() []shimast.Kind {
18
+ return []shimast.Kind{shimast.KindSourceFile}
19
+ }
20
+ func (r testingLibraryRule) Check(ctx *Context, node *shimast.Node) {
21
+ state := collectTestingLibraryState(ctx)
22
+ if !state.hasTestingLibrary && r.name != "no-dom-import" {
23
+ return
24
+ }
25
+ switch r.name {
26
+ case "await-async-events":
27
+ state.reportUnhandledAsyncEvents(ctx)
28
+ case "await-async-queries":
29
+ state.reportUnhandledAsyncQueries(ctx)
30
+ case "await-async-utils":
31
+ state.reportUnhandledAsyncUtils(ctx)
32
+ case "consistent-data-testid":
33
+ state.reportInconsistentDataTestIDs(ctx)
34
+ case "no-await-sync-events":
35
+ state.reportAwaitedSyncEvents(ctx)
36
+ case "no-await-sync-queries":
37
+ state.reportAwaitedSyncQueries(ctx)
38
+ case "no-container":
39
+ state.reportContainerAccess(ctx)
40
+ case "no-debugging-utils":
41
+ state.reportDebuggingUtils(ctx)
42
+ case "no-dom-import":
43
+ state.reportDOMImports(ctx)
44
+ case "no-global-regexp-flag-in-query":
45
+ state.reportGlobalRegexQueries(ctx)
46
+ case "no-manual-cleanup":
47
+ state.reportManualCleanup(ctx)
48
+ case "no-node-access":
49
+ state.reportNodeAccess(ctx)
50
+ case "no-promise-in-fire-event":
51
+ state.reportPromiseInFireEvent(ctx)
52
+ case "no-render-in-lifecycle":
53
+ state.reportRenderInLifecycle(ctx)
54
+ case "no-test-id-queries":
55
+ state.reportTestIDQueries(ctx)
56
+ case "no-unnecessary-act":
57
+ state.reportUnnecessaryAct(ctx)
58
+ case "no-wait-for-multiple-assertions":
59
+ state.reportWaitForMultipleAssertions(ctx)
60
+ case "no-wait-for-side-effects":
61
+ state.reportWaitForSideEffects(ctx)
62
+ case "no-wait-for-snapshot":
63
+ state.reportWaitForSnapshot(ctx)
64
+ case "prefer-explicit-assert":
65
+ state.reportPreferExplicitAssert(ctx)
66
+ case "prefer-find-by":
67
+ state.reportPreferFindBy(ctx)
68
+ case "prefer-implicit-assert":
69
+ state.reportPreferImplicitAssert(ctx)
70
+ case "prefer-presence-queries":
71
+ state.reportPreferPresenceQueries(ctx)
72
+ case "prefer-query-by-disappearance":
73
+ state.reportPreferQueryByDisappearance(ctx)
74
+ case "prefer-query-matchers":
75
+ state.reportPreferQueryMatchers(ctx)
76
+ case "prefer-screen-queries":
77
+ state.reportPreferScreenQueries(ctx)
78
+ case "prefer-user-event":
79
+ state.reportPreferUserEvent(ctx)
80
+ case "prefer-user-event-setup":
81
+ state.reportPreferUserEventSetup(ctx)
82
+ case "render-result-naming-convention":
83
+ state.reportRenderResultNames(ctx)
84
+ }
85
+ }
86
+
87
+ type testingLibraryState struct {
88
+ hasTestingLibrary bool
89
+
90
+ imports []*shimast.Node
91
+ calls []*shimast.Node
92
+ propertyAccesses []*shimast.Node
93
+ variables []*shimast.Node
94
+ bindingElements []*shimast.Node
95
+ jsxAttributes []*shimast.Node
96
+
97
+ imported map[string]string
98
+ screenNames map[string]bool
99
+ fireEventNames map[string]bool
100
+ userEventNames map[string]bool
101
+ userEventSetups map[string]bool
102
+ renderNames map[string]bool
103
+ cleanupNames map[string]bool
104
+ asyncUtilNames map[string]bool
105
+ renderResultVars map[string]bool
106
+ containerNames map[string]bool
107
+ renderQueries map[string]bool
108
+ }
109
+
110
+ func collectTestingLibraryState(ctx *Context) *testingLibraryState {
111
+ state := &testingLibraryState{
112
+ imported: map[string]string{},
113
+ screenNames: map[string]bool{},
114
+ fireEventNames: map[string]bool{},
115
+ userEventNames: map[string]bool{},
116
+ userEventSetups: map[string]bool{},
117
+ renderNames: map[string]bool{},
118
+ cleanupNames: map[string]bool{},
119
+ asyncUtilNames: map[string]bool{},
120
+ renderResultVars: map[string]bool{},
121
+ containerNames: map[string]bool{},
122
+ renderQueries: map[string]bool{},
123
+ }
124
+ if ctx == nil || ctx.File == nil {
125
+ return state
126
+ }
127
+ for _, stmt := range ctx.File.Statements.Nodes {
128
+ if stmt == nil || stmt.Kind != shimast.KindImportDeclaration {
129
+ continue
130
+ }
131
+ state.imports = append(state.imports, stmt)
132
+ state.collectImport(stmt)
133
+ }
134
+ walkDescendants(ctx.File.AsNode(), func(child *shimast.Node) {
135
+ if child == nil {
136
+ return
137
+ }
138
+ switch child.Kind {
139
+ case shimast.KindCallExpression:
140
+ state.calls = append(state.calls, child)
141
+ case shimast.KindPropertyAccessExpression:
142
+ state.propertyAccesses = append(state.propertyAccesses, child)
143
+ case shimast.KindVariableDeclaration:
144
+ state.variables = append(state.variables, child)
145
+ state.collectVariable(child)
146
+ case shimast.KindBindingElement:
147
+ state.bindingElements = append(state.bindingElements, child)
148
+ case shimast.KindJsxAttribute:
149
+ state.jsxAttributes = append(state.jsxAttributes, child)
150
+ }
151
+ })
152
+ return state
153
+ }
154
+
155
+ func (s *testingLibraryState) collectImport(node *shimast.Node) {
156
+ decl := node.AsImportDeclaration()
157
+ if decl == nil {
158
+ return
159
+ }
160
+ module := stringLiteralText(decl.ModuleSpecifier)
161
+ if module == "" {
162
+ return
163
+ }
164
+ fromTestingLibrary := strings.HasPrefix(module, "@testing-library/")
165
+ if fromTestingLibrary {
166
+ s.hasTestingLibrary = true
167
+ }
168
+ if !fromTestingLibrary || decl.ImportClause == nil {
169
+ return
170
+ }
171
+ clause := decl.ImportClause.AsImportClause()
172
+ if clause == nil {
173
+ return
174
+ }
175
+ if name := identifierText(clause.Name()); name != "" {
176
+ if strings.Contains(module, "user-event") {
177
+ s.userEventNames[name] = true
178
+ }
179
+ s.imported[name] = "default"
180
+ }
181
+ bindings := clause.NamedBindings
182
+ if bindings == nil {
183
+ return
184
+ }
185
+ switch bindings.Kind {
186
+ case shimast.KindNamedImports:
187
+ named := bindings.AsNamedImports()
188
+ if named == nil || named.Elements == nil {
189
+ return
190
+ }
191
+ for _, specNode := range named.Elements.Nodes {
192
+ spec := specNode.AsImportSpecifier()
193
+ if spec == nil {
194
+ continue
195
+ }
196
+ imported := moduleExportNameText(spec.PropertyName)
197
+ local := identifierText(spec.Name())
198
+ if imported == "" {
199
+ imported = local
200
+ }
201
+ if local == "" {
202
+ continue
203
+ }
204
+ s.imported[local] = imported
205
+ s.markTestingLibraryName(local, imported)
206
+ }
207
+ case shimast.KindNamespaceImport:
208
+ ns := bindings.AsNamespaceImport()
209
+ if ns == nil {
210
+ return
211
+ }
212
+ local := identifierText(ns.Name())
213
+ if local != "" {
214
+ s.imported[local] = "*"
215
+ }
216
+ }
217
+ }
218
+
219
+ func (s *testingLibraryState) markTestingLibraryName(local, imported string) {
220
+ switch imported {
221
+ case "screen":
222
+ s.screenNames[local] = true
223
+ case "fireEvent":
224
+ s.fireEventNames[local] = true
225
+ case "userEvent":
226
+ s.userEventNames[local] = true
227
+ case "render":
228
+ s.renderNames[local] = true
229
+ case "cleanup":
230
+ s.cleanupNames[local] = true
231
+ case "waitFor", "waitForElementToBeRemoved":
232
+ s.asyncUtilNames[local] = true
233
+ }
234
+ if isTestingLibraryQueryName(imported) {
235
+ s.renderQueries[local] = true
236
+ }
237
+ }
238
+
239
+ func (s *testingLibraryState) collectVariable(node *shimast.Node) {
240
+ decl := node.AsVariableDeclaration()
241
+ if decl == nil {
242
+ return
243
+ }
244
+ init := stripParens(decl.Initializer)
245
+ name := decl.Name()
246
+ if local := identifierText(name); local != "" {
247
+ if s.isRenderCall(init) {
248
+ s.renderResultVars[local] = true
249
+ }
250
+ if s.isUserEventSetupCall(init) {
251
+ s.userEventSetups[local] = true
252
+ }
253
+ }
254
+ if name == nil || name.Kind != shimast.KindObjectBindingPattern || !s.isRenderCall(init) {
255
+ return
256
+ }
257
+ binding := name.AsBindingPattern()
258
+ if binding == nil || binding.Elements == nil {
259
+ return
260
+ }
261
+ for _, elementNode := range binding.Elements.Nodes {
262
+ el := elementNode.AsBindingElement()
263
+ if el == nil {
264
+ continue
265
+ }
266
+ imported := moduleExportNameText(el.PropertyName)
267
+ local := identifierText(el.Name())
268
+ if imported == "" {
269
+ imported = local
270
+ }
271
+ if local == "" {
272
+ continue
273
+ }
274
+ if imported == "container" {
275
+ s.containerNames[local] = true
276
+ }
277
+ if isTestingLibraryQueryName(imported) || imported == "debug" {
278
+ s.renderQueries[local] = true
279
+ }
280
+ }
281
+ }
282
+
283
+ func (s *testingLibraryState) reportUnhandledAsyncQueries(ctx *Context) {
284
+ for _, node := range s.calls {
285
+ call := node.AsCallExpression()
286
+ if call == nil || !s.isQueryCall(call, queryAsync) || isPromiseHandled(node) {
287
+ continue
288
+ }
289
+ ctx.Report(node, "Handle the Promise returned by async Testing Library queries.")
290
+ }
291
+ }
292
+
293
+ func (s *testingLibraryState) reportUnhandledAsyncEvents(ctx *Context) {
294
+ for _, node := range s.calls {
295
+ call := node.AsCallExpression()
296
+ if call == nil || !s.isUserEventCall(call) || isPromiseHandled(node) {
297
+ continue
298
+ }
299
+ ctx.Report(node, "Handle the Promise returned by async user-event methods.")
300
+ }
301
+ }
302
+
303
+ func (s *testingLibraryState) reportUnhandledAsyncUtils(ctx *Context) {
304
+ for _, node := range s.calls {
305
+ call := node.AsCallExpression()
306
+ if call == nil || !s.isAsyncUtilCall(call) || isPromiseHandled(node) {
307
+ continue
308
+ }
309
+ ctx.Report(node, "Handle the Promise returned by async Testing Library utilities.")
310
+ }
311
+ }
312
+
313
+ func (s *testingLibraryState) reportAwaitedSyncEvents(ctx *Context) {
314
+ for _, node := range s.calls {
315
+ call := node.AsCallExpression()
316
+ if call == nil || !s.isFireEventCall(call) || !isDirectlyAwaited(node) {
317
+ continue
318
+ }
319
+ ctx.Report(node, "Do not await synchronous fireEvent calls.")
320
+ }
321
+ }
322
+
323
+ func (s *testingLibraryState) reportAwaitedSyncQueries(ctx *Context) {
324
+ for _, node := range s.calls {
325
+ call := node.AsCallExpression()
326
+ if call == nil || !s.isQueryCall(call, querySync) || !isDirectlyAwaited(node) {
327
+ continue
328
+ }
329
+ ctx.Report(node, "Do not await synchronous Testing Library queries.")
330
+ }
331
+ }
332
+
333
+ func (s *testingLibraryState) reportContainerAccess(ctx *Context) {
334
+ for _, node := range s.bindingElements {
335
+ if s.isRenderContainerBinding(node) {
336
+ ctx.Report(node, "Avoid destructuring `container`; prefer Testing Library queries.")
337
+ }
338
+ }
339
+ for _, node := range s.calls {
340
+ call := node.AsCallExpression()
341
+ info := callInfoFromCall(call)
342
+ if info.name == "" || !s.containerNames[info.receiver] || !isContainerMethod(info.name) {
343
+ continue
344
+ }
345
+ ctx.Report(node, "Avoid querying through `container`; use screen queries instead.")
346
+ }
347
+ }
348
+
349
+ func (s *testingLibraryState) isRenderContainerBinding(node *shimast.Node) bool {
350
+ el := node.AsBindingElement()
351
+ if el == nil {
352
+ return false
353
+ }
354
+ imported := moduleExportNameText(el.PropertyName)
355
+ local := identifierText(el.Name())
356
+ if imported == "" {
357
+ imported = local
358
+ }
359
+ if imported != "container" {
360
+ return false
361
+ }
362
+ for cur := node.Parent; cur != nil; cur = cur.Parent {
363
+ if cur.Kind != shimast.KindVariableDeclaration {
364
+ continue
365
+ }
366
+ decl := cur.AsVariableDeclaration()
367
+ return decl != nil && s.isRenderCall(stripParens(decl.Initializer))
368
+ }
369
+ return false
370
+ }
371
+
372
+ func (s *testingLibraryState) reportDebuggingUtils(ctx *Context) {
373
+ for _, node := range s.calls {
374
+ call := node.AsCallExpression()
375
+ info := callInfoFromCall(call)
376
+ if info.name == "" {
377
+ continue
378
+ }
379
+ if isDebugUtility(info.name) && (info.receiver == "" || s.screenNames[info.receiver] || s.renderQueries[info.receiver]) {
380
+ ctx.Report(node, "Remove Testing Library debugging utilities from committed tests.")
381
+ }
382
+ }
383
+ }
384
+
385
+ func (s *testingLibraryState) reportDOMImports(ctx *Context) {
386
+ for _, node := range s.imports {
387
+ decl := node.AsImportDeclaration()
388
+ if decl == nil || stringLiteralText(decl.ModuleSpecifier) != "@testing-library/dom" {
389
+ continue
390
+ }
391
+ ctx.Report(node, "Import from the framework Testing Library package instead of @testing-library/dom.")
392
+ }
393
+ }
394
+
395
+ func (s *testingLibraryState) reportGlobalRegexQueries(ctx *Context) {
396
+ for _, node := range s.calls {
397
+ call := node.AsCallExpression()
398
+ if call == nil || !s.isQueryCall(call, queryAny) || call.Arguments == nil {
399
+ continue
400
+ }
401
+ for _, arg := range call.Arguments.Nodes {
402
+ if arg != nil && arg.Kind == shimast.KindRegularExpressionLiteral && regexLiteralHasGlobalFlag(ctx.File, arg) {
403
+ ctx.Report(arg, "Do not use the global RegExp flag in Testing Library queries.")
404
+ }
405
+ }
406
+ }
407
+ }
408
+
409
+ func (s *testingLibraryState) reportManualCleanup(ctx *Context) {
410
+ for _, node := range s.calls {
411
+ call := node.AsCallExpression()
412
+ if call == nil {
413
+ continue
414
+ }
415
+ info := callInfoFromCall(call)
416
+ if s.cleanupNames[info.name] {
417
+ ctx.Report(node, "Testing Library cleanup is automatic; remove manual cleanup().")
418
+ }
419
+ }
420
+ }
421
+
422
+ func (s *testingLibraryState) reportNodeAccess(ctx *Context) {
423
+ for _, node := range s.propertyAccesses {
424
+ access := node.AsPropertyAccessExpression()
425
+ if access == nil {
426
+ continue
427
+ }
428
+ name := identifierText(access.Name())
429
+ if !isNodeAccessName(name) {
430
+ continue
431
+ }
432
+ receiver := stripParens(access.Expression)
433
+ if receiver == nil {
434
+ continue
435
+ }
436
+ if s.containerNames[identifierText(receiver)] || s.isQueryExpression(receiver) {
437
+ ctx.Report(node, "Avoid direct DOM node access; use Testing Library queries and matchers.")
438
+ }
439
+ }
440
+ }
441
+
442
+ func (s *testingLibraryState) reportPromiseInFireEvent(ctx *Context) {
443
+ for _, node := range s.calls {
444
+ call := node.AsCallExpression()
445
+ if call == nil || !s.isFireEventCall(call) || call.Arguments == nil {
446
+ continue
447
+ }
448
+ for _, arg := range call.Arguments.Nodes {
449
+ if containsNode(arg, func(child *shimast.Node) bool {
450
+ if child == nil {
451
+ return false
452
+ }
453
+ if child.Kind == shimast.KindAwaitExpression {
454
+ return true
455
+ }
456
+ if child.Kind != shimast.KindCallExpression {
457
+ return false
458
+ }
459
+ inner := child.AsCallExpression()
460
+ return inner != nil && (s.isQueryCall(inner, queryAsync) || s.isAsyncUtilCall(inner) || s.isUserEventCall(inner))
461
+ }) {
462
+ ctx.Report(node, "Do not pass Promises or async Testing Library calls to fireEvent.")
463
+ break
464
+ }
465
+ }
466
+ }
467
+ }
468
+
469
+ func (s *testingLibraryState) reportRenderInLifecycle(ctx *Context) {
470
+ for _, node := range s.calls {
471
+ call := node.AsCallExpression()
472
+ if call == nil || !s.isRenderCall(node) || !isInsideLifecycleCallback(node) {
473
+ continue
474
+ }
475
+ ctx.Report(node, "Do not call render inside test lifecycle hooks.")
476
+ }
477
+ }
478
+
479
+ func (s *testingLibraryState) reportTestIDQueries(ctx *Context) {
480
+ for _, node := range s.calls {
481
+ call := node.AsCallExpression()
482
+ if call == nil {
483
+ continue
484
+ }
485
+ info := callInfoFromCall(call)
486
+ if strings.Contains(info.name, "ByTestId") && s.isQueryCall(call, queryAny) {
487
+ ctx.Report(node, "Avoid Testing Library test-id queries when accessible queries are possible.")
488
+ }
489
+ }
490
+ }
491
+
492
+ func (s *testingLibraryState) reportUnnecessaryAct(ctx *Context) {
493
+ for _, node := range s.calls {
494
+ call := node.AsCallExpression()
495
+ if call == nil || callInfoFromCall(call).name != "act" || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
496
+ continue
497
+ }
498
+ callback := call.Arguments.Nodes[0]
499
+ if containsNode(callback, func(child *shimast.Node) bool {
500
+ if child == nil || child.Kind != shimast.KindCallExpression {
501
+ return false
502
+ }
503
+ inner := child.AsCallExpression()
504
+ return inner != nil && (s.isRenderCall(child) || s.isFireEventCall(inner) || s.isUserEventCall(inner))
505
+ }) {
506
+ reportTestingLibraryCallName(ctx, node, "act", "Testing Library already wraps these updates in act().")
507
+ }
508
+ }
509
+ }
510
+
511
+ func (s *testingLibraryState) reportWaitForMultipleAssertions(ctx *Context) {
512
+ s.forEachWaitFor(func(_, body *shimast.Node) {
513
+ count := countCalls(body, func(call *shimast.CallExpression) bool {
514
+ return callInfoFromCall(call).name == "expect"
515
+ })
516
+ if count > 1 {
517
+ reportTestingLibraryCallBefore(ctx, body, "waitFor", "Keep only one assertion inside waitFor().")
518
+ }
519
+ })
520
+ }
521
+
522
+ func (s *testingLibraryState) reportWaitForSideEffects(ctx *Context) {
523
+ s.forEachWaitFor(func(_, body *shimast.Node) {
524
+ if containsNode(body, func(child *shimast.Node) bool {
525
+ if child == nil || child.Kind != shimast.KindCallExpression {
526
+ return false
527
+ }
528
+ call := child.AsCallExpression()
529
+ if call == nil {
530
+ return false
531
+ }
532
+ return s.isFireEventCall(call) || s.isUserEventCall(call) || s.isRenderCall(child)
533
+ }) {
534
+ reportTestingLibraryCallBefore(ctx, body, "waitFor", "Do not run side effects inside waitFor().")
535
+ }
536
+ })
537
+ }
538
+
539
+ func (s *testingLibraryState) reportWaitForSnapshot(ctx *Context) {
540
+ s.forEachWaitFor(func(_, body *shimast.Node) {
541
+ if containsCall(body, func(call *shimast.CallExpression) bool {
542
+ name := callInfoFromCall(call).name
543
+ return name == "toMatchSnapshot" || name == "toMatchInlineSnapshot"
544
+ }) {
545
+ reportTestingLibraryCallBefore(ctx, body, "waitFor", "Do not create snapshots inside waitFor().")
546
+ }
547
+ })
548
+ }
549
+
550
+ func (s *testingLibraryState) reportPreferFindBy(ctx *Context) {
551
+ s.forEachWaitFor(func(_, body *shimast.Node) {
552
+ if containsCall(body, func(call *shimast.CallExpression) bool {
553
+ return s.isQueryCall(call, queryGet)
554
+ }) {
555
+ reportTestingLibraryCallBefore(ctx, body, "waitFor", "Use findBy* queries instead of waitFor() around getBy* queries.")
556
+ }
557
+ })
558
+ }
559
+
560
+ func (s *testingLibraryState) reportPreferExplicitAssert(ctx *Context) {
561
+ for _, node := range s.calls {
562
+ call := node.AsCallExpression()
563
+ if call == nil || !s.isQueryCall(call, queryGet|queryAsync) {
564
+ continue
565
+ }
566
+ if parent := nearestNonQueryParent(node); parent != nil && parent.Kind == shimast.KindExpressionStatement {
567
+ ctx.Report(node, "Wrap standalone Testing Library queries in an explicit assertion.")
568
+ }
569
+ }
570
+ }
571
+
572
+ func (s *testingLibraryState) reportPreferImplicitAssert(ctx *Context) {
573
+ for _, node := range s.calls {
574
+ call := node.AsCallExpression()
575
+ info := callInfoFromCall(call)
576
+ if info.name != "toBeInTheDocument" || !isExpectMatcherCall(node) {
577
+ continue
578
+ }
579
+ expectArg := firstExpectArgument(node)
580
+ if expectArg != nil {
581
+ inner := expectArg.AsCallExpression()
582
+ if inner != nil && s.isQueryCall(inner, queryGet|queryAsync) {
583
+ ctx.Report(node, "Prefer the implicit assertion from getBy* or findBy* queries.")
584
+ }
585
+ }
586
+ }
587
+ }
588
+
589
+ func (s *testingLibraryState) reportPreferPresenceQueries(ctx *Context) {
590
+ for _, node := range s.calls {
591
+ call := node.AsCallExpression()
592
+ info := callInfoFromCall(call)
593
+ if info.name != "toBeInTheDocument" || !isExpectMatcherCall(node) {
594
+ continue
595
+ }
596
+ expectArg := firstExpectArgument(node)
597
+ if expectArg == nil {
598
+ continue
599
+ }
600
+ inner := expectArg.AsCallExpression()
601
+ if inner == nil {
602
+ continue
603
+ }
604
+ negated := matcherCallIsNegated(node)
605
+ if !negated && s.isQueryCall(inner, queryQuery) {
606
+ ctx.Report(expectArg, "Use getBy* queries for presence assertions.")
607
+ }
608
+ if negated && s.isQueryCall(inner, queryGet) {
609
+ ctx.Report(expectArg, "Use queryBy* queries for absence assertions.")
610
+ }
611
+ }
612
+ }
613
+
614
+ func (s *testingLibraryState) reportPreferQueryByDisappearance(ctx *Context) {
615
+ s.forEachWaitFor(func(_, body *shimast.Node) {
616
+ if containsNode(body, func(child *shimast.Node) bool {
617
+ call := child.AsCallExpression()
618
+ if call == nil {
619
+ return false
620
+ }
621
+ info := callInfoFromCall(call)
622
+ if info.name != "toBeInTheDocument" {
623
+ return false
624
+ }
625
+ arg := firstExpectArgument(child)
626
+ if arg == nil {
627
+ return false
628
+ }
629
+ inner := arg.AsCallExpression()
630
+ return matcherCallIsNegated(child) && inner != nil && s.isQueryCall(inner, queryGet|queryAsync)
631
+ }) {
632
+ reportTestingLibraryCallBefore(ctx, body, "waitFor", "Use queryBy* queries when waiting for disappearance.")
633
+ }
634
+ })
635
+ }
636
+
637
+ func (s *testingLibraryState) reportPreferQueryMatchers(ctx *Context) {
638
+ for _, node := range s.calls {
639
+ call := node.AsCallExpression()
640
+ info := callInfoFromCall(call)
641
+ if info.name != "toBeNull" && info.name != "toBeTruthy" && info.name != "toBeFalsy" {
642
+ continue
643
+ }
644
+ arg := firstExpectArgument(node)
645
+ if arg == nil {
646
+ continue
647
+ }
648
+ inner := arg.AsCallExpression()
649
+ if inner != nil && s.isQueryCall(inner, queryAny) {
650
+ ctx.Report(node, "Use jest-dom document matchers with Testing Library queries.")
651
+ }
652
+ }
653
+ }
654
+
655
+ func (s *testingLibraryState) reportPreferScreenQueries(ctx *Context) {
656
+ for _, node := range s.calls {
657
+ call := node.AsCallExpression()
658
+ if call == nil {
659
+ continue
660
+ }
661
+ info := callInfoFromCall(call)
662
+ if s.renderQueries[info.name] && isTestingLibraryQueryName(info.name) {
663
+ ctx.Report(node, "Use screen.* queries instead of destructured render queries.")
664
+ }
665
+ if s.renderResultVars[info.receiver] && isTestingLibraryQueryName(info.name) {
666
+ ctx.Report(node, "Use screen.* queries instead of render result queries.")
667
+ }
668
+ }
669
+ }
670
+
671
+ func (s *testingLibraryState) reportPreferUserEvent(ctx *Context) {
672
+ for _, node := range s.calls {
673
+ call := node.AsCallExpression()
674
+ if call != nil && s.isFireEventCall(call) {
675
+ ctx.Report(node, "Use userEvent instead of fireEvent for user interactions.")
676
+ }
677
+ }
678
+ }
679
+
680
+ func (s *testingLibraryState) reportPreferUserEventSetup(ctx *Context) {
681
+ for _, node := range s.calls {
682
+ call := node.AsCallExpression()
683
+ if call == nil || !s.isUserEventCall(call) {
684
+ continue
685
+ }
686
+ info := callInfoFromCall(call)
687
+ if !s.userEventSetups[info.receiver] {
688
+ ctx.Report(node, "Use userEvent.setup() and call methods on the returned user object.")
689
+ }
690
+ }
691
+ }
692
+
693
+ func (s *testingLibraryState) reportRenderResultNames(ctx *Context) {
694
+ for _, node := range s.variables {
695
+ decl := node.AsVariableDeclaration()
696
+ if decl == nil || !s.isRenderCall(stripParens(decl.Initializer)) {
697
+ continue
698
+ }
699
+ name := identifierText(decl.Name())
700
+ if name == "" || name == "view" || name == "utils" || name == "renderResult" {
701
+ continue
702
+ }
703
+ ctx.Report(decl.Name(), "Name render() results `view`, `utils`, or destructure the needed queries.")
704
+ }
705
+ }
706
+
707
+ func (s *testingLibraryState) reportInconsistentDataTestIDs(ctx *Context) {
708
+ var opts testingLibraryConsistentDataTestIDOptions
709
+ if err := ctx.DecodeOptions(&opts); err != nil || opts.TestIDPattern == "" {
710
+ return
711
+ }
712
+ pattern := strings.ReplaceAll(opts.TestIDPattern, "{fileName}", testingLibraryFileName(ctx.File.FileName()))
713
+ re, err := regexp.Compile(pattern)
714
+ if err != nil {
715
+ return
716
+ }
717
+ attrs := opts.testIDAttributes()
718
+ for _, node := range s.jsxAttributes {
719
+ attr := node.AsJsxAttribute()
720
+ if attr == nil || !attrs[identifierText(attr.Name())] || attr.Initializer == nil {
721
+ continue
722
+ }
723
+ value := stringLiteralText(attr.Initializer)
724
+ if value != "" && !re.MatchString(value) {
725
+ ctx.Report(node, "`data-testid` value does not match the configured pattern.")
726
+ }
727
+ }
728
+ }
729
+
730
+ type testingLibraryConsistentDataTestIDOptions struct {
731
+ TestIDPattern string `json:"testIdPattern"`
732
+ TestIDAttribute json.RawMessage `json:"testIdAttribute"`
733
+ }
734
+
735
+ func (o testingLibraryConsistentDataTestIDOptions) testIDAttributes() map[string]bool {
736
+ if len(o.TestIDAttribute) == 0 {
737
+ return map[string]bool{"data-testid": true}
738
+ }
739
+ var single string
740
+ if json.Unmarshal(o.TestIDAttribute, &single) == nil && single != "" {
741
+ return map[string]bool{single: true}
742
+ }
743
+ var list []string
744
+ attrs := map[string]bool{}
745
+ if json.Unmarshal(o.TestIDAttribute, &list) == nil {
746
+ for _, attr := range list {
747
+ if attr != "" {
748
+ attrs[attr] = true
749
+ }
750
+ }
751
+ }
752
+ if len(attrs) == 0 {
753
+ attrs["data-testid"] = true
754
+ }
755
+ return attrs
756
+ }
757
+
758
+ func (s *testingLibraryState) forEachWaitFor(visit func(_, body *shimast.Node)) {
759
+ for _, node := range s.calls {
760
+ call := node.AsCallExpression()
761
+ if call == nil || !s.isWaitForCall(call) || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
762
+ continue
763
+ }
764
+ body := functionBody(call.Arguments.Nodes[0])
765
+ if body != nil {
766
+ visit(node, body)
767
+ }
768
+ }
769
+ }
770
+
771
+ type testingLibraryQueryKind int
772
+
773
+ const (
774
+ queryGet testingLibraryQueryKind = 1 << iota
775
+ queryQuery
776
+ queryAsync
777
+ queryAny = queryGet | queryQuery | queryAsync
778
+ querySync = queryGet | queryQuery
779
+ )
780
+
781
+ func (s *testingLibraryState) isQueryCall(call *shimast.CallExpression, kinds testingLibraryQueryKind) bool {
782
+ info := callInfoFromCall(call)
783
+ if !isTestingLibraryQueryName(info.name) {
784
+ return false
785
+ }
786
+ if info.receiver != "" && !s.screenNames[info.receiver] && !s.renderResultVars[info.receiver] {
787
+ return false
788
+ }
789
+ if info.receiver == "" && !s.renderQueries[info.name] && s.imported[info.name] == "" {
790
+ return false
791
+ }
792
+ if strings.HasPrefix(info.name, "getBy") || strings.HasPrefix(info.name, "getAllBy") {
793
+ return kinds&queryGet != 0
794
+ }
795
+ if strings.HasPrefix(info.name, "queryBy") || strings.HasPrefix(info.name, "queryAllBy") {
796
+ return kinds&queryQuery != 0
797
+ }
798
+ if strings.HasPrefix(info.name, "findBy") || strings.HasPrefix(info.name, "findAllBy") {
799
+ return kinds&queryAsync != 0
800
+ }
801
+ return false
802
+ }
803
+
804
+ func (s *testingLibraryState) isQueryExpression(node *shimast.Node) bool {
805
+ node = stripParens(node)
806
+ if node == nil || node.Kind != shimast.KindCallExpression {
807
+ return false
808
+ }
809
+ call := node.AsCallExpression()
810
+ return s.isQueryCall(call, queryAny)
811
+ }
812
+
813
+ func (s *testingLibraryState) isFireEventCall(call *shimast.CallExpression) bool {
814
+ info := callInfoFromCall(call)
815
+ return info.receiver != "" && s.fireEventNames[info.receiver]
816
+ }
817
+
818
+ func (s *testingLibraryState) isUserEventCall(call *shimast.CallExpression) bool {
819
+ info := callInfoFromCall(call)
820
+ return info.receiver != "" && info.name != "setup" && (s.userEventNames[info.receiver] || s.userEventSetups[info.receiver])
821
+ }
822
+
823
+ func (s *testingLibraryState) isUserEventSetupCall(node *shimast.Node) bool {
824
+ node = stripParens(node)
825
+ if node == nil || node.Kind != shimast.KindCallExpression {
826
+ return false
827
+ }
828
+ call := node.AsCallExpression()
829
+ info := callInfoFromCall(call)
830
+ return info.name == "setup" && s.userEventNames[info.receiver]
831
+ }
832
+
833
+ func (s *testingLibraryState) isAsyncUtilCall(call *shimast.CallExpression) bool {
834
+ info := callInfoFromCall(call)
835
+ return s.asyncUtilNames[info.name]
836
+ }
837
+
838
+ func (s *testingLibraryState) isWaitForCall(call *shimast.CallExpression) bool {
839
+ return callInfoFromCall(call).name == "waitFor" && s.isAsyncUtilCall(call)
840
+ }
841
+
842
+ func (s *testingLibraryState) isRenderCall(node *shimast.Node) bool {
843
+ node = stripParens(node)
844
+ if node == nil || node.Kind != shimast.KindCallExpression {
845
+ return false
846
+ }
847
+ call := node.AsCallExpression()
848
+ info := callInfoFromCall(call)
849
+ return s.renderNames[info.name]
850
+ }
851
+
852
+ type testingLibraryCallInfo struct {
853
+ name string
854
+ receiver string
855
+ }
856
+
857
+ func callInfoFromCall(call *shimast.CallExpression) testingLibraryCallInfo {
858
+ if call == nil {
859
+ return testingLibraryCallInfo{}
860
+ }
861
+ expr := stripParens(call.Expression)
862
+ if expr == nil {
863
+ return testingLibraryCallInfo{}
864
+ }
865
+ if expr.Kind == shimast.KindPropertyAccessExpression {
866
+ access := expr.AsPropertyAccessExpression()
867
+ return testingLibraryCallInfo{
868
+ name: identifierText(access.Name()),
869
+ receiver: receiverName(access.Expression),
870
+ }
871
+ }
872
+ return testingLibraryCallInfo{name: identifierText(expr)}
873
+ }
874
+
875
+ func receiverName(node *shimast.Node) string {
876
+ node = stripParens(node)
877
+ if node == nil {
878
+ return ""
879
+ }
880
+ if name := identifierText(node); name != "" {
881
+ return name
882
+ }
883
+ if node.Kind == shimast.KindPropertyAccessExpression {
884
+ access := node.AsPropertyAccessExpression()
885
+ return identifierText(access.Name())
886
+ }
887
+ return ""
888
+ }
889
+
890
+ func moduleExportNameText(node *shimast.Node) string {
891
+ if node == nil {
892
+ return ""
893
+ }
894
+ if text := identifierText(node); text != "" {
895
+ return text
896
+ }
897
+ return stringLiteralText(node)
898
+ }
899
+
900
+ func isTestingLibraryQueryName(name string) bool {
901
+ return strings.HasPrefix(name, "getBy") ||
902
+ strings.HasPrefix(name, "getAllBy") ||
903
+ strings.HasPrefix(name, "queryBy") ||
904
+ strings.HasPrefix(name, "queryAllBy") ||
905
+ strings.HasPrefix(name, "findBy") ||
906
+ strings.HasPrefix(name, "findAllBy")
907
+ }
908
+
909
+ func isContainerMethod(name string) bool {
910
+ switch name {
911
+ case "querySelector", "querySelectorAll", "getElementsByClassName", "getElementsByTagName", "getElementById":
912
+ return true
913
+ }
914
+ return false
915
+ }
916
+
917
+ func isNodeAccessName(name string) bool {
918
+ switch name {
919
+ case "closest", "children", "childNodes", "firstChild", "firstElementChild", "lastChild", "lastElementChild",
920
+ "nextSibling", "nextElementSibling", "parentElement", "parentNode", "previousSibling", "previousElementSibling",
921
+ "querySelector", "querySelectorAll":
922
+ return true
923
+ }
924
+ return false
925
+ }
926
+
927
+ func isDebugUtility(name string) bool {
928
+ switch name {
929
+ case "debug", "prettyDOM", "logRoles", "logTestingPlaygroundURL":
930
+ return true
931
+ }
932
+ return false
933
+ }
934
+
935
+ func isPromiseHandled(node *shimast.Node) bool {
936
+ for cur := node.Parent; cur != nil; cur = cur.Parent {
937
+ switch cur.Kind {
938
+ case shimast.KindParenthesizedExpression, shimast.KindPropertyAccessExpression, shimast.KindCallExpression:
939
+ continue
940
+ case shimast.KindAwaitExpression, shimast.KindReturnStatement:
941
+ return true
942
+ default:
943
+ return false
944
+ }
945
+ }
946
+ return false
947
+ }
948
+
949
+ func isDirectlyAwaited(node *shimast.Node) bool {
950
+ for cur := node.Parent; cur != nil; cur = cur.Parent {
951
+ if cur.Kind == shimast.KindParenthesizedExpression {
952
+ continue
953
+ }
954
+ return cur.Kind == shimast.KindAwaitExpression
955
+ }
956
+ return false
957
+ }
958
+
959
+ func containsNode(node *shimast.Node, match func(*shimast.Node) bool) bool {
960
+ if node == nil {
961
+ return false
962
+ }
963
+ found := false
964
+ walkDescendants(node, func(child *shimast.Node) {
965
+ if found {
966
+ return
967
+ }
968
+ if match(child) {
969
+ found = true
970
+ }
971
+ })
972
+ return found
973
+ }
974
+
975
+ func containsCall(node *shimast.Node, match func(*shimast.CallExpression) bool) bool {
976
+ return containsNode(node, func(child *shimast.Node) bool {
977
+ if child == nil || child.Kind != shimast.KindCallExpression {
978
+ return false
979
+ }
980
+ call := child.AsCallExpression()
981
+ return call != nil && match(call)
982
+ })
983
+ }
984
+
985
+ func countCalls(node *shimast.Node, match func(*shimast.CallExpression) bool) int {
986
+ count := 0
987
+ walkDescendants(node, func(child *shimast.Node) {
988
+ if child == nil || child.Kind != shimast.KindCallExpression {
989
+ return
990
+ }
991
+ call := child.AsCallExpression()
992
+ if call != nil && match(call) {
993
+ count++
994
+ }
995
+ })
996
+ return count
997
+ }
998
+
999
+ func functionBody(node *shimast.Node) *shimast.Node {
1000
+ node = stripParens(node)
1001
+ if node == nil {
1002
+ return nil
1003
+ }
1004
+ switch node.Kind {
1005
+ case shimast.KindArrowFunction, shimast.KindFunctionExpression:
1006
+ return node.Body()
1007
+ }
1008
+ return nil
1009
+ }
1010
+
1011
+ func isInsideLifecycleCallback(node *shimast.Node) bool {
1012
+ for cur := node.Parent; cur != nil; cur = cur.Parent {
1013
+ if cur.Kind != shimast.KindArrowFunction && cur.Kind != shimast.KindFunctionExpression {
1014
+ continue
1015
+ }
1016
+ parent := cur.Parent
1017
+ if parent == nil || parent.Kind != shimast.KindCallExpression {
1018
+ continue
1019
+ }
1020
+ call := parent.AsCallExpression()
1021
+ switch callInfoFromCall(call).name {
1022
+ case "beforeEach", "beforeAll", "afterEach", "afterAll":
1023
+ return true
1024
+ }
1025
+ }
1026
+ return false
1027
+ }
1028
+
1029
+ func regexLiteralHasGlobalFlag(file *shimast.SourceFile, node *shimast.Node) bool {
1030
+ text := nodeText(file, node)
1031
+ slash := strings.LastIndexByte(text, '/')
1032
+ return slash >= 0 && strings.Contains(text[slash+1:], "g")
1033
+ }
1034
+
1035
+ func reportTestingLibraryCallName(ctx *Context, node *shimast.Node, name, message string) {
1036
+ if ctx == nil || ctx.File == nil || node == nil {
1037
+ return
1038
+ }
1039
+ call := node.AsCallExpression()
1040
+ if call != nil && call.Expression != nil {
1041
+ ctx.Report(call.Expression, message)
1042
+ return
1043
+ }
1044
+ ctx.Report(node, message)
1045
+ }
1046
+
1047
+ func reportTestingLibraryCallBefore(ctx *Context, anchor *shimast.Node, name, message string) {
1048
+ if ctx == nil || ctx.File == nil || anchor == nil {
1049
+ return
1050
+ }
1051
+ src := ctx.File.Text()
1052
+ end := anchor.Pos()
1053
+ if end > len(src) {
1054
+ end = len(src)
1055
+ }
1056
+ if end <= 0 {
1057
+ end = len(src)
1058
+ }
1059
+ idx := strings.LastIndex(src[:end], name)
1060
+ if idx < 0 {
1061
+ ctx.Report(anchor, message)
1062
+ return
1063
+ }
1064
+ ctx.ReportRange(idx, idx+len(name), message)
1065
+ }
1066
+
1067
+ func nearestNonQueryParent(node *shimast.Node) *shimast.Node {
1068
+ for cur := node.Parent; cur != nil; cur = cur.Parent {
1069
+ if cur.Kind == shimast.KindParenthesizedExpression || cur.Kind == shimast.KindPropertyAccessExpression {
1070
+ continue
1071
+ }
1072
+ return cur
1073
+ }
1074
+ return nil
1075
+ }
1076
+
1077
+ func isExpectMatcherCall(node *shimast.Node) bool {
1078
+ return expectCallFromMatcherNode(node) != nil
1079
+ }
1080
+
1081
+ func firstExpectArgument(node *shimast.Node) *shimast.Node {
1082
+ call := expectCallFromMatcherNode(node)
1083
+ if call != nil && call.Arguments != nil && len(call.Arguments.Nodes) > 0 {
1084
+ return stripParens(call.Arguments.Nodes[0])
1085
+ }
1086
+ return nil
1087
+ }
1088
+
1089
+ func expectCallFromMatcherNode(node *shimast.Node) *shimast.CallExpression {
1090
+ if call := expectCallInExpression(node); call != nil {
1091
+ return call
1092
+ }
1093
+ for cur := node; cur != nil; cur = cur.Parent {
1094
+ call := cur.AsCallExpression()
1095
+ if call == nil || callInfoFromCall(call).name != "expect" || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
1096
+ continue
1097
+ }
1098
+ return call
1099
+ }
1100
+ return nil
1101
+ }
1102
+
1103
+ func expectCallInExpression(node *shimast.Node) *shimast.CallExpression {
1104
+ node = stripParens(node)
1105
+ if node == nil {
1106
+ return nil
1107
+ }
1108
+ switch node.Kind {
1109
+ case shimast.KindCallExpression:
1110
+ call := node.AsCallExpression()
1111
+ if callInfoFromCall(call).name == "expect" {
1112
+ return call
1113
+ }
1114
+ return expectCallInExpression(call.Expression)
1115
+ case shimast.KindPropertyAccessExpression:
1116
+ access := node.AsPropertyAccessExpression()
1117
+ if access == nil {
1118
+ return nil
1119
+ }
1120
+ return expectCallInExpression(access.Expression)
1121
+ }
1122
+ return nil
1123
+ }
1124
+
1125
+ func matcherCallIsNegated(node *shimast.Node) bool {
1126
+ if matcherExpressionHasProperty(node, "not") {
1127
+ return true
1128
+ }
1129
+ for cur := node.Parent; cur != nil; cur = cur.Parent {
1130
+ if cur.Kind != shimast.KindPropertyAccessExpression {
1131
+ continue
1132
+ }
1133
+ if identifierText(cur.AsPropertyAccessExpression().Name()) == "not" {
1134
+ return true
1135
+ }
1136
+ }
1137
+ return false
1138
+ }
1139
+
1140
+ func matcherExpressionHasProperty(node *shimast.Node, name string) bool {
1141
+ node = stripParens(node)
1142
+ if node == nil {
1143
+ return false
1144
+ }
1145
+ switch node.Kind {
1146
+ case shimast.KindCallExpression:
1147
+ call := node.AsCallExpression()
1148
+ return call != nil && matcherExpressionHasProperty(call.Expression, name)
1149
+ case shimast.KindPropertyAccessExpression:
1150
+ access := node.AsPropertyAccessExpression()
1151
+ if access == nil {
1152
+ return false
1153
+ }
1154
+ if identifierText(access.Name()) == name {
1155
+ return true
1156
+ }
1157
+ return matcherExpressionHasProperty(access.Expression, name)
1158
+ }
1159
+ return false
1160
+ }
1161
+
1162
+ func testingLibraryFileName(fileName string) string {
1163
+ base := filepath.Base(fileName)
1164
+ if dot := strings.IndexByte(base, '.'); dot > 0 {
1165
+ return base[:dot]
1166
+ }
1167
+ return base
1168
+ }
1169
+
1170
+ func init() {
1171
+ for _, name := range []string{
1172
+ "await-async-events",
1173
+ "await-async-queries",
1174
+ "await-async-utils",
1175
+ "consistent-data-testid",
1176
+ "no-await-sync-events",
1177
+ "no-await-sync-queries",
1178
+ "no-container",
1179
+ "no-debugging-utils",
1180
+ "no-dom-import",
1181
+ "no-global-regexp-flag-in-query",
1182
+ "no-manual-cleanup",
1183
+ "no-node-access",
1184
+ "no-promise-in-fire-event",
1185
+ "no-render-in-lifecycle",
1186
+ "no-test-id-queries",
1187
+ "no-unnecessary-act",
1188
+ "no-wait-for-multiple-assertions",
1189
+ "no-wait-for-side-effects",
1190
+ "no-wait-for-snapshot",
1191
+ "prefer-explicit-assert",
1192
+ "prefer-find-by",
1193
+ "prefer-implicit-assert",
1194
+ "prefer-presence-queries",
1195
+ "prefer-query-by-disappearance",
1196
+ "prefer-query-matchers",
1197
+ "prefer-screen-queries",
1198
+ "prefer-user-event",
1199
+ "prefer-user-event-setup",
1200
+ "render-result-naming-convention",
1201
+ } {
1202
+ Register(testingLibraryRule{name: name})
1203
+ }
1204
+ }