@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,122 @@
1
+ // typescript/prefer-regexp-exec: prefer `re.exec(str)` over
2
+ // `str.match(re)` when the regex has no `g` flag. Both shapes return
3
+ // the same `RegExpExecArray | null` for first-match queries, but
4
+ // `String#match` silently switches to "every match" the moment the
5
+ // regex gains the `g` flag — a typo at the regex literal can change
6
+ // the call's return shape from `[fullMatch, ...captures]` to a flat
7
+ // `string[]` of matches. `RegExp#exec` is the stable form.
8
+ // typescript-eslint:
9
+ // https://typescript-eslint.io/rules/prefer-regexp-exec/
10
+ //
11
+ // Type-aware. Without a Checker the rule cannot prove the receiver of
12
+ // `match` is string-like, so Context.Checker == nil short-circuits each
13
+ // Check to a no-op the way `prefer-includes` and
14
+ // `prefer-string-starts-ends-with` do. The argument must be a regex
15
+ // literal — the AST-only baseline reads the flag suffix directly off
16
+ // the token text; non-literal regex arguments (a `new RegExp(...)`,
17
+ // a `const re = /.../` aliased through a variable) are conservatively
18
+ // skipped because static flag tracking would explode in scope.
19
+ package linthost
20
+
21
+ import (
22
+ "strings"
23
+
24
+ shimast "github.com/microsoft/typescript-go/shim/ast"
25
+ shimchecker "github.com/microsoft/typescript-go/shim/checker"
26
+ )
27
+
28
+ type preferRegexpExec struct{}
29
+
30
+ func (preferRegexpExec) Name() string { return "typescript/prefer-regexp-exec" }
31
+ func (preferRegexpExec) NeedsTypeChecker() bool {
32
+ return true
33
+ }
34
+ func (preferRegexpExec) Visits() []shimast.Kind {
35
+ return []shimast.Kind{shimast.KindCallExpression}
36
+ }
37
+ func (preferRegexpExec) Check(ctx *Context, node *shimast.Node) {
38
+ if ctx.Checker == nil {
39
+ return
40
+ }
41
+ call := node.AsCallExpression()
42
+ if call == nil || call.Expression == nil {
43
+ return
44
+ }
45
+ receiver, method, ok := promisePropertyAccessParts(call.Expression)
46
+ if !ok || method != "match" {
47
+ return
48
+ }
49
+ if receiver == nil {
50
+ return
51
+ }
52
+ if call.Arguments == nil || len(call.Arguments.Nodes) != 1 {
53
+ return
54
+ }
55
+ arg := stripParens(call.Arguments.Nodes[0])
56
+ if arg == nil || arg.Kind != shimast.KindRegularExpressionLiteral {
57
+ return
58
+ }
59
+ // Skip when the regex literal carries the `g` flag — `String#match`
60
+ // returns every match in that mode, which `RegExp#exec` cannot
61
+ // replicate in a single call.
62
+ if preferRegexpExecHasGlobalFlag(ctx.File, arg) {
63
+ return
64
+ }
65
+ if !preferRegexpExecIsString(ctx.Checker.GetTypeAtLocation(receiver)) {
66
+ return
67
+ }
68
+ ctx.Report(node, preferRegexpExecMessage)
69
+ }
70
+
71
+ const preferRegexpExecMessage = "Prefer `RegExp#exec(str)` over `String#match(re)` when the regex has no `g` flag — `exec` returns the same first-match shape and avoids the `g`-flag fallthrough that silently changes the return type of `match`."
72
+
73
+ // preferRegexpExecHasGlobalFlag reports whether the regex literal at
74
+ // `node` carries the `g` flag. The flag string follows the closing `/`
75
+ // of the literal (e.g. `/foo/gi` has flags `"gi"`); the scanner has
76
+ // already validated the token, so a textual scan of the trailing
77
+ // suffix is sufficient.
78
+ func preferRegexpExecHasGlobalFlag(file *shimast.SourceFile, node *shimast.Node) bool {
79
+ raw := nodeText(file, node)
80
+ if len(raw) < 2 || raw[0] != '/' {
81
+ return false
82
+ }
83
+ closing := strings.LastIndexByte(raw, '/')
84
+ if closing <= 0 {
85
+ return false
86
+ }
87
+ flags := raw[closing+1:]
88
+ return strings.Contains(flags, "g")
89
+ }
90
+
91
+ // preferRegexpExecIsString reports whether t is provably string-like.
92
+ // Mirrors `preferStringStartsEndsWithIsString`: `any` / `unknown` /
93
+ // `never` are rejected so generic helpers don't fire. Union and
94
+ // intersection types must have every constituent string-like.
95
+ func preferRegexpExecIsString(t *shimchecker.Type) bool {
96
+ if t == nil {
97
+ return false
98
+ }
99
+ flags := t.Flags()
100
+ if flags&(shimchecker.TypeFlagsAny|shimchecker.TypeFlagsUnknown|shimchecker.TypeFlagsNever) != 0 {
101
+ return false
102
+ }
103
+ if flags&shimchecker.TypeFlagsStringLike != 0 {
104
+ return true
105
+ }
106
+ if flags&(shimchecker.TypeFlagsUnion|shimchecker.TypeFlagsIntersection) != 0 {
107
+ for _, part := range t.Types() {
108
+ if part == nil {
109
+ continue
110
+ }
111
+ if !preferRegexpExecIsString(part) {
112
+ return false
113
+ }
114
+ }
115
+ return true
116
+ }
117
+ return false
118
+ }
119
+
120
+ func init() {
121
+ Register(preferRegexpExec{})
122
+ }
@@ -0,0 +1,135 @@
1
+ // typescript/prefer-return-this-type: when an instance method always
2
+ // `return this`, declare its return type as `this` instead of the
3
+ // enclosing class name. The `this` polymorphic-receiver type keeps the
4
+ // narrower subclass type at the call site so chained calls stay
5
+ // polymorphic: `subclass.fluent().subclassOnly()` only type-checks if
6
+ // `fluent()` is declared `: this` rather than `: Base`.
7
+ // typescript-eslint:
8
+ // https://typescript-eslint.io/rules/prefer-return-this-type/
9
+ //
10
+ // Type-aware. Without a Checker the rule cannot read the method's
11
+ // declared return type or distinguish the class symbol, so
12
+ // Context.Checker == nil short-circuits each Check to a no-op the way
13
+ // other type-aware rules do.
14
+ //
15
+ // Skipped:
16
+ // - methods whose return type is already `this`;
17
+ // - methods without an explicit declared return type (the upstream
18
+ // rule only proposes a narrower annotation when one is already
19
+ // present — adding a brand-new annotation is the job of
20
+ // `explicit-function-return-type`);
21
+ // - constructors, accessors, generators, and `async` methods (each
22
+ // has return-shape semantics the `this` rewrite does not preserve);
23
+ // - methods with no body (overload signatures, abstract members);
24
+ // - methods that have at least one `return X;` where `X` is not the
25
+ // `this` keyword.
26
+ package linthost
27
+
28
+ import (
29
+ shimast "github.com/microsoft/typescript-go/shim/ast"
30
+ )
31
+
32
+ type preferReturnThisType struct{}
33
+
34
+ func (preferReturnThisType) Name() string { return "typescript/prefer-return-this-type" }
35
+ func (preferReturnThisType) NeedsTypeChecker() bool {
36
+ return true
37
+ }
38
+ func (preferReturnThisType) Visits() []shimast.Kind {
39
+ return []shimast.Kind{shimast.KindMethodDeclaration}
40
+ }
41
+ func (preferReturnThisType) Check(ctx *Context, node *shimast.Node) {
42
+ if ctx.Checker == nil {
43
+ return
44
+ }
45
+ decl := node.AsMethodDeclaration()
46
+ if decl == nil || decl.Body == nil {
47
+ return
48
+ }
49
+ // Skip async / generator methods — their return shape is wrapped
50
+ // (`Promise<...>` / `Generator<...>`) and not directly `this`.
51
+ if hasAsyncModifier(node) {
52
+ return
53
+ }
54
+ if decl.AsteriskToken != nil {
55
+ return
56
+ }
57
+ // Skip static methods — the upstream rule targets instance-method
58
+ // chains where the receiver type is the subclass; static dispatch
59
+ // uses the class itself and `this` rewriting changes nothing.
60
+ if hasModifier(node, shimast.KindStaticKeyword) {
61
+ return
62
+ }
63
+ // The method must have an explicit return-type annotation that is
64
+ // NOT already `this`. The rewrite-target is the existing annotation;
65
+ // without one, adding `this` would force a new annotation that the
66
+ // upstream rule does not propose.
67
+ if decl.Type == nil {
68
+ return
69
+ }
70
+ if decl.Type.Kind == shimast.KindThisType {
71
+ return
72
+ }
73
+ // The enclosing class must exist — the rule applies to instance
74
+ // methods of class declarations / expressions only.
75
+ parent := node.Parent
76
+ if parent == nil {
77
+ return
78
+ }
79
+ if parent.Kind != shimast.KindClassDeclaration &&
80
+ parent.Kind != shimast.KindClassExpression {
81
+ return
82
+ }
83
+ // Walk the method body. Every value-returning `return` must
84
+ // return exactly `this`; we also require at least one return
85
+ // statement so we don't fire on methods that fall off the end.
86
+ hasValueReturn, allAreThis := preferReturnThisTypeAnalyzeBody(decl.Body)
87
+ if !hasValueReturn || !allAreThis {
88
+ return
89
+ }
90
+ ctx.Report(decl.Type, preferReturnThisTypeMessage)
91
+ }
92
+
93
+ const preferReturnThisTypeMessage = "Method always returns `this` — declare the return type as `this` so subclass call sites keep the narrower receiver type."
94
+
95
+ // preferReturnThisTypeAnalyzeBody walks the method body (without
96
+ // descending into nested function-like scopes) and reports:
97
+ // - hasValueReturn: at least one `return <expression>;` exists.
98
+ // - allAreThis: every value-returning `return` returns the bare
99
+ // `this` keyword (after stripping parens).
100
+ //
101
+ // A bare `return;` is ignored — it returns `undefined`, which the rule
102
+ // cannot rewrite to `this` regardless. The conservative interpretation
103
+ // matches the upstream rule's behavior.
104
+ func preferReturnThisTypeAnalyzeBody(body *shimast.Node) (hasValueReturn, allAreThis bool) {
105
+ allAreThis = true
106
+ var walk func(*shimast.Node)
107
+ walk = func(n *shimast.Node) {
108
+ if n == nil {
109
+ return
110
+ }
111
+ if n != body && isFunctionLikeKind(n) {
112
+ return
113
+ }
114
+ if n.Kind == shimast.KindReturnStatement {
115
+ ret := n.AsReturnStatement()
116
+ if ret != nil && ret.Expression != nil {
117
+ hasValueReturn = true
118
+ inner := stripParens(ret.Expression)
119
+ if inner == nil || inner.Kind != shimast.KindThisKeyword {
120
+ allAreThis = false
121
+ }
122
+ }
123
+ }
124
+ n.ForEachChild(func(child *shimast.Node) bool {
125
+ walk(child)
126
+ return false
127
+ })
128
+ }
129
+ walk(body)
130
+ return hasValueReturn, allAreThis
131
+ }
132
+
133
+ func init() {
134
+ Register(preferReturnThisType{})
135
+ }
@@ -0,0 +1,360 @@
1
+ // typescript/prefer-string-starts-ends-with: prefer
2
+ // `str.startsWith(prefix)` / `str.endsWith(suffix)` over the older
3
+ // `indexOf` / `lastIndexOf` / anchored-regex shapes:
4
+ //
5
+ // - `str.indexOf(p) === 0` → startsWith
6
+ // - `str.indexOf(p, str.length - p.length) !== -1` → endsWith
7
+ // - `str.lastIndexOf(p) === str.length - p.length` → endsWith
8
+ // - `/^prefix/.test(str)` (a static-prefix regex) → startsWith
9
+ // - `/suffix$/.test(str)` (a static-suffix regex) → endsWith
10
+ //
11
+ // The dedicated methods state the intent directly and avoid the
12
+ // off-by-one arithmetic and regex-anchor pitfalls of the older shapes.
13
+ // typescript-eslint:
14
+ // https://typescript-eslint.io/rules/prefer-string-starts-ends-with/
15
+ //
16
+ // Type-aware. Without a Checker the rule cannot prove the receiver of
17
+ // `indexOf` / `lastIndexOf` (or the argument of `.test(str)`) is a
18
+ // `string`, so Context.Checker == nil short-circuits each Check to a
19
+ // no-op the way `no-base-to-string` and `prefer-includes` do. `any` /
20
+ // `unknown` / `never` are intentionally NOT treated as matching — they
21
+ // leak from generic helpers and would explode the false-positive
22
+ // volume on user-defined `indexOf` / `test` methods.
23
+ package linthost
24
+
25
+ import (
26
+ "strings"
27
+
28
+ shimast "github.com/microsoft/typescript-go/shim/ast"
29
+ shimchecker "github.com/microsoft/typescript-go/shim/checker"
30
+ )
31
+
32
+ type preferStringStartsEndsWith struct{}
33
+
34
+ func (preferStringStartsEndsWith) Name() string {
35
+ return "typescript/prefer-string-starts-ends-with"
36
+ }
37
+ func (preferStringStartsEndsWith) NeedsTypeChecker() bool {
38
+ return true
39
+ }
40
+ func (preferStringStartsEndsWith) Visits() []shimast.Kind {
41
+ return []shimast.Kind{
42
+ shimast.KindBinaryExpression,
43
+ shimast.KindCallExpression,
44
+ }
45
+ }
46
+ func (preferStringStartsEndsWith) Check(ctx *Context, node *shimast.Node) {
47
+ if ctx.Checker == nil {
48
+ return
49
+ }
50
+ switch node.Kind {
51
+ case shimast.KindBinaryExpression:
52
+ bin := node.AsBinaryExpression()
53
+ if bin == nil || bin.OperatorToken == nil {
54
+ return
55
+ }
56
+ if !preferStringStartsEndsWithIsEqualityOp(bin.OperatorToken.Kind) {
57
+ return
58
+ }
59
+ if preferStringStartsEndsWithBinary(ctx, bin.Left, bin.Right) {
60
+ ctx.Report(node, preferStringStartsEndsWithMessage)
61
+ return
62
+ }
63
+ if preferStringStartsEndsWithBinary(ctx, bin.Right, bin.Left) {
64
+ ctx.Report(node, preferStringStartsEndsWithMessage)
65
+ }
66
+ case shimast.KindCallExpression:
67
+ call := node.AsCallExpression()
68
+ if call == nil {
69
+ return
70
+ }
71
+ if preferStringStartsEndsWithRegexTest(ctx, call) {
72
+ ctx.Report(node, preferStringStartsEndsWithMessage)
73
+ }
74
+ }
75
+ }
76
+
77
+ const preferStringStartsEndsWithMessage = "Prefer `String#startsWith` / `String#endsWith` over the equivalent `indexOf` / `lastIndexOf` / anchored-regex idioms — clearer and avoids the off-by-one arithmetic."
78
+
79
+ // preferStringStartsEndsWithIsEqualityOp reports whether `kind` is one
80
+ // of the equality tokens the rule cares about. Only `==`, `===`,
81
+ // `!=`, `!==` matter: every recognized shape compares two integers
82
+ // for equality (or inequality with a sentinel) rather than ordering.
83
+ func preferStringStartsEndsWithIsEqualityOp(kind shimast.Kind) bool {
84
+ switch kind {
85
+ case shimast.KindEqualsEqualsToken,
86
+ shimast.KindEqualsEqualsEqualsToken,
87
+ shimast.KindExclamationEqualsToken,
88
+ shimast.KindExclamationEqualsEqualsToken:
89
+ return true
90
+ }
91
+ return false
92
+ }
93
+
94
+ // preferStringStartsEndsWithBinary inspects `call op other` for the
95
+ // `indexOf` / `lastIndexOf` shapes. Returns true when the receiver of
96
+ // the call is provably string-typed AND the right-hand side matches
97
+ // the sentinel of one of the recognized rewrites:
98
+ //
99
+ // str.indexOf(p) === 0
100
+ // str.indexOf(p, str.length - p.length) !== -1
101
+ // str.lastIndexOf(p) === str.length - p.length
102
+ func preferStringStartsEndsWithBinary(
103
+ ctx *Context,
104
+ callNode, otherNode *shimast.Node,
105
+ ) bool {
106
+ callNode = stripParens(callNode)
107
+ otherNode = stripParens(otherNode)
108
+ if callNode == nil || otherNode == nil {
109
+ return false
110
+ }
111
+ if callNode.Kind != shimast.KindCallExpression {
112
+ return false
113
+ }
114
+ call := callNode.AsCallExpression()
115
+ if call == nil || call.Expression == nil {
116
+ return false
117
+ }
118
+ receiver, method, ok := promisePropertyAccessParts(call.Expression)
119
+ if !ok {
120
+ return false
121
+ }
122
+ if method != "indexOf" && method != "lastIndexOf" {
123
+ return false
124
+ }
125
+ if receiver == nil {
126
+ return false
127
+ }
128
+ t := ctx.Checker.GetTypeAtLocation(receiver)
129
+ if !preferStringStartsEndsWithIsString(t) {
130
+ return false
131
+ }
132
+ args := call.Arguments
133
+ if args == nil || len(args.Nodes) == 0 {
134
+ return false
135
+ }
136
+ arg := stripParens(args.Nodes[0])
137
+ if arg == nil {
138
+ return false
139
+ }
140
+ receiverText := nodeText(ctx.File, receiver)
141
+ argText := nodeText(ctx.File, arg)
142
+ otherText := nodeText(ctx.File, otherNode)
143
+ if receiverText == "" || argText == "" {
144
+ return false
145
+ }
146
+ switch method {
147
+ case "indexOf":
148
+ // startsWith: `str.indexOf(p) === 0`
149
+ if len(args.Nodes) == 1 {
150
+ return otherText == "0"
151
+ }
152
+ // endsWith via positional indexOf: the second argument must
153
+ // equal `str.length - arg.length`, and the comparison must be
154
+ // against -1 (the sentinel for "not present at that
155
+ // position").
156
+ if len(args.Nodes) != 2 {
157
+ return false
158
+ }
159
+ second := stripParens(args.Nodes[1])
160
+ if !preferStringStartsEndsWithIsLengthMinusLength(ctx, second, receiverText, argText) {
161
+ return false
162
+ }
163
+ return preferStringStartsEndsWithIsMinusOne(otherText)
164
+ case "lastIndexOf":
165
+ // endsWith: `str.lastIndexOf(p) === str.length - p.length`
166
+ if len(args.Nodes) != 1 {
167
+ return false
168
+ }
169
+ return preferStringStartsEndsWithIsLengthMinusLengthText(otherText, receiverText, argText)
170
+ }
171
+ return false
172
+ }
173
+
174
+ // preferStringStartsEndsWithRegexTest reports whether `call` is a
175
+ // `/^.../.test(str)` or `/...$/.test(str)` invocation on a regex
176
+ // literal whose body is a static prefix or suffix — no metacharacters
177
+ // other than the anchor and no flags that change the matching
178
+ // semantics. The argument to `.test` must be a `string`-typed value;
179
+ // without that the call is not equivalent to a startsWith/endsWith
180
+ // rewrite (e.g. coercing a non-string regex argument).
181
+ func preferStringStartsEndsWithRegexTest(
182
+ ctx *Context,
183
+ call *shimast.CallExpression,
184
+ ) bool {
185
+ if call == nil || call.Expression == nil || call.Arguments == nil || len(call.Arguments.Nodes) != 1 {
186
+ return false
187
+ }
188
+ receiver, method, ok := promisePropertyAccessParts(call.Expression)
189
+ if !ok || method != "test" {
190
+ return false
191
+ }
192
+ receiver = stripParens(receiver)
193
+ if receiver == nil || receiver.Kind != shimast.KindRegularExpressionLiteral {
194
+ return false
195
+ }
196
+ raw := nodeText(ctx.File, receiver)
197
+ if !preferStringStartsEndsWithIsAnchoredStaticRegex(raw) {
198
+ return false
199
+ }
200
+ arg := stripParens(call.Arguments.Nodes[0])
201
+ if arg == nil {
202
+ return false
203
+ }
204
+ return preferStringStartsEndsWithIsString(ctx.Checker.GetTypeAtLocation(arg))
205
+ }
206
+
207
+ // preferStringStartsEndsWithIsAnchoredStaticRegex reports whether
208
+ // `raw` (the source text of a RegularExpressionLiteral, including the
209
+ // surrounding `/`) encodes either `^prefix` or `suffix$` with no
210
+ // metacharacters other than the anchor itself, and carries no flags
211
+ // that change matching semantics (`i`, `m`, `s`, `u`, `v`). A flag
212
+ // like `i` would lower-case both sides, which `startsWith` does not
213
+ // do — so the rewrite would change meaning.
214
+ func preferStringStartsEndsWithIsAnchoredStaticRegex(raw string) bool {
215
+ if len(raw) < 3 || raw[0] != '/' {
216
+ return false
217
+ }
218
+ closing := strings.LastIndexByte(raw, '/')
219
+ if closing <= 0 {
220
+ return false
221
+ }
222
+ pattern := raw[1:closing]
223
+ flags := raw[closing+1:]
224
+ if pattern == "" {
225
+ return false
226
+ }
227
+ for i := 0; i < len(flags); i++ {
228
+ switch flags[i] {
229
+ case 'g', 'y', 'd':
230
+ // stateful/position flags — fine for `.test` since we
231
+ // only call once
232
+ default:
233
+ return false
234
+ }
235
+ }
236
+ if pattern[0] == '^' {
237
+ return preferStringStartsEndsWithIsStaticString(pattern[1:])
238
+ }
239
+ if pattern[len(pattern)-1] == '$' {
240
+ return preferStringStartsEndsWithIsStaticString(pattern[:len(pattern)-1])
241
+ }
242
+ return false
243
+ }
244
+
245
+ // preferStringStartsEndsWithIsStaticString reports whether `pattern`
246
+ // is a regex body made up only of literal characters (and accepted
247
+ // escape sequences `\.`, `\\`, etc. that represent a single literal
248
+ // character). Any metacharacter — `^`, `$`, `*`, `+`, `?`, `(`, `)`,
249
+ // `[`, `]`, `{`, `}`, `|`, `.` — disqualifies the body because the
250
+ // startsWith/endsWith rewrite would change matching semantics.
251
+ func preferStringStartsEndsWithIsStaticString(pattern string) bool {
252
+ if pattern == "" {
253
+ return false
254
+ }
255
+ for i := 0; i < len(pattern); i++ {
256
+ ch := pattern[i]
257
+ if ch == '\\' {
258
+ // Accept only escapes that name a single literal
259
+ // character (`\.`, `\$`, `\^`, `\/`, `\\`, `\(`, `\)`,
260
+ // `\[`, `\]`, `\{`, `\}`, `\|`, `\+`, `\*`, `\?`).
261
+ // Reject character-class shortcuts (`\d`, `\w`, `\s`)
262
+ // and back-references — those would not survive a
263
+ // rewrite to startsWith/endsWith.
264
+ if i+1 >= len(pattern) {
265
+ return false
266
+ }
267
+ next := pattern[i+1]
268
+ switch next {
269
+ case '.', '$', '^', '/', '\\', '(', ')', '[', ']', '{', '}', '|', '+', '*', '?', '-':
270
+ i++
271
+ continue
272
+ }
273
+ return false
274
+ }
275
+ switch ch {
276
+ case '^', '$', '*', '+', '?', '(', ')', '[', ']', '{', '}', '|', '.':
277
+ return false
278
+ }
279
+ }
280
+ return true
281
+ }
282
+
283
+ // preferStringStartsEndsWithIsLengthMinusLength reports whether
284
+ // `node` textually evaluates to `<receiver>.length - <arg>.length`.
285
+ // The comparison is text-based (after stripping parens) because the
286
+ // rewrite only fires when the call site spelled the offset that way
287
+ // — matching the upstream rule's textual shape detector.
288
+ func preferStringStartsEndsWithIsLengthMinusLength(
289
+ ctx *Context,
290
+ node *shimast.Node,
291
+ receiverText, argText string,
292
+ ) bool {
293
+ if node == nil {
294
+ return false
295
+ }
296
+ return preferStringStartsEndsWithIsLengthMinusLengthText(nodeText(ctx.File, node), receiverText, argText)
297
+ }
298
+
299
+ // preferStringStartsEndsWithIsLengthMinusLengthText is the text-level
300
+ // half of `preferStringStartsEndsWithIsLengthMinusLength` — split out
301
+ // so the `lastIndexOf === str.length - p.length` shape can use the
302
+ // same matcher against the binary RHS text.
303
+ func preferStringStartsEndsWithIsLengthMinusLengthText(text, receiverText, argText string) bool {
304
+ wanted := receiverText + ".length-" + argText + ".length"
305
+ return preferStringStartsEndsWithStripSpaces(text) == wanted
306
+ }
307
+
308
+ // preferStringStartsEndsWithStripSpaces removes whitespace from `s`.
309
+ // Used for the textual equality match — `a.length - b.length` and
310
+ // `a.length-b.length` should both match the canonical form.
311
+ func preferStringStartsEndsWithStripSpaces(s string) string {
312
+ var b strings.Builder
313
+ for i := 0; i < len(s); i++ {
314
+ ch := s[i]
315
+ if ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' {
316
+ continue
317
+ }
318
+ b.WriteByte(ch)
319
+ }
320
+ return b.String()
321
+ }
322
+
323
+ // preferStringStartsEndsWithIsMinusOne reports whether `text` is the
324
+ // numeric literal `-1` (with or without internal whitespace).
325
+ func preferStringStartsEndsWithIsMinusOne(text string) bool {
326
+ return preferStringStartsEndsWithStripSpaces(text) == "-1"
327
+ }
328
+
329
+ // preferStringStartsEndsWithIsString reports whether t is provably
330
+ // string-like. Mirrors `isStringLikeType` in no-base-to-string but
331
+ // rejects `any` / `unknown` / `never` so generic helpers don't fire.
332
+ // Union and intersection types must have every constituent string-like.
333
+ func preferStringStartsEndsWithIsString(t *shimchecker.Type) bool {
334
+ if t == nil {
335
+ return false
336
+ }
337
+ flags := t.Flags()
338
+ if flags&(shimchecker.TypeFlagsAny|shimchecker.TypeFlagsUnknown|shimchecker.TypeFlagsNever) != 0 {
339
+ return false
340
+ }
341
+ if flags&shimchecker.TypeFlagsStringLike != 0 {
342
+ return true
343
+ }
344
+ if flags&(shimchecker.TypeFlagsUnion|shimchecker.TypeFlagsIntersection) != 0 {
345
+ for _, part := range t.Types() {
346
+ if part == nil {
347
+ continue
348
+ }
349
+ if !preferStringStartsEndsWithIsString(part) {
350
+ return false
351
+ }
352
+ }
353
+ return true
354
+ }
355
+ return false
356
+ }
357
+
358
+ func init() {
359
+ Register(preferStringStartsEndsWith{})
360
+ }
@@ -0,0 +1,77 @@
1
+ // promiseFunctionAsync requires a function whose return type is
2
+ // `Promise<T>` (or a Promise-like thenable) to be declared with the
3
+ // `async` keyword. typescript-eslint:
4
+ // https://typescript-eslint.io/rules/promise-function-async/
5
+ //
6
+ // Type-aware. An `async` function wraps a synchronous `throw` into a
7
+ // rejected Promise so the caller's `await` / `.catch(...)` observes
8
+ // it; the non-async equivalent throws synchronously and bypasses every
9
+ // Promise-aware handler downstream. Marking the function `async` keeps
10
+ // the rejection channel consistent with the declared return type.
11
+ //
12
+ // Skipped:
13
+ // - functions already declared `async` (the contract holds);
14
+ // - abstract methods and overload signatures (no body to wrap — the
15
+ // `async` keyword can only be applied to the implementation);
16
+ // - functions whose declared / inferred return type is not a
17
+ // Promise (`any` / `unknown` / `never` pass through, matching the
18
+ // conservative `isPromiseTypedExpression` baseline used by the
19
+ // surrounding async family).
20
+ package linthost
21
+
22
+ import (
23
+ shimast "github.com/microsoft/typescript-go/shim/ast"
24
+ )
25
+
26
+ type promiseFunctionAsync struct{}
27
+
28
+ func (promiseFunctionAsync) Name() string { return "typescript/promise-function-async" }
29
+ func (promiseFunctionAsync) NeedsTypeChecker() bool {
30
+ return true
31
+ }
32
+ func (promiseFunctionAsync) Visits() []shimast.Kind {
33
+ return []shimast.Kind{
34
+ shimast.KindFunctionDeclaration,
35
+ shimast.KindFunctionExpression,
36
+ shimast.KindArrowFunction,
37
+ shimast.KindMethodDeclaration,
38
+ }
39
+ }
40
+ func (promiseFunctionAsync) Check(ctx *Context, node *shimast.Node) {
41
+ if ctx.Checker == nil {
42
+ return
43
+ }
44
+ if hasAsyncModifier(node) {
45
+ return
46
+ }
47
+ // Skip overload signatures and abstract methods — only the
48
+ // implementation signature carries an executable body that the
49
+ // `async` keyword could wrap.
50
+ if node.Body() == nil {
51
+ return
52
+ }
53
+ if hasModifier(node, shimast.KindAbstractKeyword) {
54
+ return
55
+ }
56
+ sig := ctx.Checker.GetSignatureFromDeclaration(node)
57
+ if sig == nil {
58
+ return
59
+ }
60
+ returnType := ctx.Checker.GetReturnTypeOfSignature(sig)
61
+ if returnType == nil {
62
+ return
63
+ }
64
+ if !isPromiseTypedExpression(ctx.Checker, returnType) {
65
+ return
66
+ }
67
+ startPos := keywordStart(ctx.File, node, "function")
68
+ if startPos >= 0 {
69
+ ctx.ReportRange(startPos, startPos+len("function"), "Function returning a Promise should be declared `async` so synchronous throws surface as a rejected Promise.")
70
+ return
71
+ }
72
+ ctx.Report(node, "Function returning a Promise should be declared `async` so synchronous throws surface as a rejected Promise.")
73
+ }
74
+
75
+ func init() {
76
+ Register(promiseFunctionAsync{})
77
+ }