@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,739 @@
1
+ // Extended ESLint-core rules — AST-only correctness checks the
2
+ // recommended preset ships but that the original @ttsc/lint port
3
+ // hadn't migrated yet.
4
+ //
5
+ // Implemented here:
6
+ // - no-await-in-loop: await inside a loop body iterates sequentially
7
+ // - no-dupe-class-members: duplicate class member declarations
8
+ // - no-this-before-super: `this` (or `super.x`) before `super()` in
9
+ // derived constructors
10
+ // - prefer-object-spread: `Object.assign({}, x, y)` → `{ ...x, ...y }`
11
+ package linthost
12
+
13
+ import (
14
+ shimast "github.com/microsoft/typescript-go/shim/ast"
15
+ )
16
+
17
+ // noAwaitInLoop reports an `await` expression evaluated inside the body
18
+ // of a sequential loop (for / while / do-while / for-in / for-of). Each
19
+ // iteration of the loop blocks on the previous one's microtask hop, so
20
+ // the loop body runs strictly serially even when the underlying
21
+ // operations are independent and could overlap with Promise.all.
22
+ // https://eslint.org/docs/latest/rules/no-await-in-loop
23
+ //
24
+ // `for await … of` loops are exempt — the for-await iterator protocol
25
+ // is the whole reason the loop exists, and rejecting its `await` would
26
+ // just suggest the developer abandon the iterator.
27
+ type noAwaitInLoop struct{}
28
+
29
+ func (noAwaitInLoop) Name() string { return "no-await-in-loop" }
30
+ func (noAwaitInLoop) Visits() []shimast.Kind {
31
+ return []shimast.Kind{shimast.KindAwaitExpression}
32
+ }
33
+ func (noAwaitInLoop) Check(ctx *Context, node *shimast.Node) {
34
+ for cur := node.Parent; cur != nil; cur = cur.Parent {
35
+ if isFunctionLikeKind(cur) {
36
+ return
37
+ }
38
+ switch cur.Kind {
39
+ case shimast.KindForStatement,
40
+ shimast.KindWhileStatement,
41
+ shimast.KindDoStatement,
42
+ shimast.KindForInStatement:
43
+ ctx.Report(node, "Unexpected `await` inside a loop — iterations run sequentially; prefer `Promise.all` when independent.")
44
+ return
45
+ case shimast.KindForOfStatement:
46
+ // `for await … of` loops are exempt by design — the await
47
+ // is the iteration itself, not a sequential block.
48
+ if isForAwaitOfStatement(ctx.File, cur) {
49
+ return
50
+ }
51
+ ctx.Report(node, "Unexpected `await` inside a loop — iterations run sequentially; prefer `Promise.all` when independent.")
52
+ return
53
+ }
54
+ }
55
+ }
56
+
57
+ // isForAwaitOfStatement reports whether the for-of statement at `node`
58
+ // is a `for await … of` (rather than a plain `for … of`). The shim AST
59
+ // does not expose `AwaitModifier` as a typed field, so the check is
60
+ // textual: locate the `for` keyword and look for the `await` keyword
61
+ // in the small window before the opening parenthesis. The width 24
62
+ // covers any reasonable spacing/comment-free header.
63
+ func isForAwaitOfStatement(file *shimast.SourceFile, node *shimast.Node) bool {
64
+ if file == nil || node == nil {
65
+ return false
66
+ }
67
+ forPos := keywordStart(file, node, "for")
68
+ if forPos < 0 {
69
+ return false
70
+ }
71
+ src := file.Text()
72
+ limit := forPos + 24
73
+ if limit > len(src) {
74
+ limit = len(src)
75
+ }
76
+ if node.End() < limit {
77
+ limit = node.End()
78
+ }
79
+ for i := forPos + 3; i < limit; i++ {
80
+ if src[i] == '(' {
81
+ limit = i
82
+ break
83
+ }
84
+ }
85
+ return findKeyword(file, forPos+3, limit, "await") >= 0
86
+ }
87
+
88
+ // noDupeClassMembers reports two declarations of the same member on a
89
+ // single class. The later declaration silently overwrites the earlier
90
+ // one at runtime; ESLint enforces this because the syntax does not.
91
+ // https://eslint.org/docs/latest/rules/no-dupe-class-members
92
+ //
93
+ // Members are deduplicated by their (name, static, kind) tuple — an
94
+ // instance property and a static property of the same name coexist, as
95
+ // do a getter and a setter for the same property, but a getter and a
96
+ // regular method on the same key do not.
97
+ type noDupeClassMembers struct{}
98
+
99
+ func (noDupeClassMembers) Name() string { return "no-dupe-class-members" }
100
+ func (noDupeClassMembers) Visits() []shimast.Kind {
101
+ return []shimast.Kind{shimast.KindClassDeclaration, shimast.KindClassExpression}
102
+ }
103
+ func (noDupeClassMembers) Check(ctx *Context, node *shimast.Node) {
104
+ members := classMembers(node)
105
+ if len(members) == 0 {
106
+ return
107
+ }
108
+ type slot struct {
109
+ name string
110
+ static bool
111
+ kind string
112
+ }
113
+ seen := map[slot]*shimast.Node{}
114
+ for _, member := range members {
115
+ if member == nil {
116
+ continue
117
+ }
118
+ name, kind, ok := classMemberSlot(member)
119
+ if !ok {
120
+ continue
121
+ }
122
+ key := slot{name: name, static: hasModifier(member, shimast.KindStaticKeyword), kind: kind}
123
+ if prior, exists := seen[key]; exists {
124
+ _ = prior
125
+ ctx.Report(member, "Duplicate class member `"+name+"`.")
126
+ continue
127
+ }
128
+ seen[key] = member
129
+ }
130
+ }
131
+
132
+ // classMembers returns the member list of a class declaration or
133
+ // expression. Returns nil for any other kind so the helper is safe to
134
+ // call from a multi-visit rule.
135
+ func classMembers(node *shimast.Node) []*shimast.Node {
136
+ switch node.Kind {
137
+ case shimast.KindClassDeclaration:
138
+ if decl := node.AsClassDeclaration(); decl != nil && decl.Members != nil {
139
+ return decl.Members.Nodes
140
+ }
141
+ case shimast.KindClassExpression:
142
+ if expr := node.AsClassExpression(); expr != nil && expr.Members != nil {
143
+ return expr.Members.Nodes
144
+ }
145
+ }
146
+ return nil
147
+ }
148
+
149
+ // classMemberSlot extracts the (name, kind) identity for a class
150
+ // member. Constructors and unnamed/computed members are skipped — the
151
+ // rule cannot reason about them statically. The kind distinguishes
152
+ // getter/setter pairs from regular method/property declarations so a
153
+ // getter+setter on the same name does not trip the dupe check.
154
+ func classMemberSlot(member *shimast.Node) (string, string, bool) {
155
+ if member == nil {
156
+ return "", "", false
157
+ }
158
+ switch member.Kind {
159
+ case shimast.KindMethodDeclaration,
160
+ shimast.KindPropertyDeclaration,
161
+ shimast.KindGetAccessor,
162
+ shimast.KindSetAccessor:
163
+ name := classMemberName(member)
164
+ if name == "" {
165
+ return "", "", false
166
+ }
167
+ var kind string
168
+ switch member.Kind {
169
+ case shimast.KindGetAccessor:
170
+ kind = "get"
171
+ case shimast.KindSetAccessor:
172
+ kind = "set"
173
+ default:
174
+ kind = "data"
175
+ }
176
+ return name, kind, true
177
+ }
178
+ return "", "", false
179
+ }
180
+
181
+ // classMemberName returns the textual name of a class member identifier
182
+ // or literal key. Computed property names return the empty string so
183
+ // the caller can skip them — the rule cannot prove equivalence of two
184
+ // computed expressions statically.
185
+ func classMemberName(member *shimast.Node) string {
186
+ name := member.Name()
187
+ if name == nil {
188
+ return ""
189
+ }
190
+ switch name.Kind {
191
+ case shimast.KindIdentifier, shimast.KindPrivateIdentifier:
192
+ return identifierText(name)
193
+ case shimast.KindStringLiteral:
194
+ return stringLiteralText(name)
195
+ case shimast.KindNumericLiteral:
196
+ return numericLiteralText(name)
197
+ }
198
+ return ""
199
+ }
200
+
201
+ // noThisBeforeSuper reports a derived constructor that references
202
+ // `this` (or `super.x`) before the first reachable `super()` call. ES
203
+ // throws a ReferenceError at runtime; the lint rule catches it before
204
+ // the program ever runs.
205
+ // https://eslint.org/docs/latest/rules/no-this-before-super
206
+ //
207
+ // Trigger: the class declaration has a `HeritageClause` of kind
208
+ // `extends`, the constructor body exists, and a `this` / `super.`
209
+ // reference appears textually before the first `super()` call. The
210
+ // walk stops at nested function-like boundaries so a `this` inside an
211
+ // inner arrow does not count.
212
+ type noThisBeforeSuper struct{}
213
+
214
+ func (noThisBeforeSuper) Name() string { return "no-this-before-super" }
215
+ func (noThisBeforeSuper) Visits() []shimast.Kind {
216
+ return []shimast.Kind{shimast.KindConstructor}
217
+ }
218
+ func (noThisBeforeSuper) Check(ctx *Context, node *shimast.Node) {
219
+ parent := node.Parent
220
+ if parent == nil || !classExtendsAnother(parent) {
221
+ return
222
+ }
223
+ ctor := node.AsConstructorDeclaration()
224
+ if ctor == nil || ctor.Body == nil {
225
+ return
226
+ }
227
+ var superCallPos = -1
228
+ walkConstructorBody(ctor.Body, func(child *shimast.Node) {
229
+ if child == nil || superCallPos >= 0 {
230
+ return
231
+ }
232
+ if child.Kind == shimast.KindCallExpression {
233
+ call := child.AsCallExpression()
234
+ if call != nil && call.Expression != nil && call.Expression.Kind == shimast.KindSuperKeyword {
235
+ superCallPos = child.Pos()
236
+ }
237
+ }
238
+ })
239
+ walkConstructorBody(ctor.Body, func(child *shimast.Node) {
240
+ if child == nil {
241
+ return
242
+ }
243
+ // Skip the call to super() itself.
244
+ if child.Kind == shimast.KindCallExpression {
245
+ call := child.AsCallExpression()
246
+ if call != nil && call.Expression != nil && call.Expression.Kind == shimast.KindSuperKeyword {
247
+ return
248
+ }
249
+ }
250
+ switch child.Kind {
251
+ case shimast.KindThisKeyword:
252
+ if superCallPos < 0 || child.Pos() < superCallPos {
253
+ ctx.Report(child, "`this` referenced before `super()` call in a derived constructor.")
254
+ }
255
+ case shimast.KindPropertyAccessExpression:
256
+ access := child.AsPropertyAccessExpression()
257
+ if access != nil && access.Expression != nil && access.Expression.Kind == shimast.KindSuperKeyword {
258
+ if superCallPos < 0 || child.Pos() < superCallPos {
259
+ ctx.Report(child, "`super.` access before `super()` call in a derived constructor.")
260
+ }
261
+ }
262
+ }
263
+ })
264
+ }
265
+
266
+ // classExtendsAnother reports whether the class declaration/expression
267
+ // has a non-empty `extends` heritage clause. Base classes (no extends)
268
+ // are skipped — `this` is legal before any super call there.
269
+ func classExtendsAnother(class *shimast.Node) bool {
270
+ var clauses []*shimast.Node
271
+ switch class.Kind {
272
+ case shimast.KindClassDeclaration:
273
+ decl := class.AsClassDeclaration()
274
+ if decl == nil || decl.HeritageClauses == nil {
275
+ return false
276
+ }
277
+ clauses = decl.HeritageClauses.Nodes
278
+ case shimast.KindClassExpression:
279
+ expr := class.AsClassExpression()
280
+ if expr == nil || expr.HeritageClauses == nil {
281
+ return false
282
+ }
283
+ clauses = expr.HeritageClauses.Nodes
284
+ default:
285
+ return false
286
+ }
287
+ for _, clause := range clauses {
288
+ if clause == nil {
289
+ continue
290
+ }
291
+ hc := clause.AsHeritageClause()
292
+ if hc == nil || hc.Token != shimast.KindExtendsKeyword {
293
+ continue
294
+ }
295
+ if hc.Types != nil && len(hc.Types.Nodes) > 0 {
296
+ return true
297
+ }
298
+ }
299
+ return false
300
+ }
301
+
302
+ // walkConstructorBody walks every descendant of body, skipping into
303
+ // nested function-like scopes so a `this` inside an inner arrow does
304
+ // not get attributed to the surrounding constructor.
305
+ func walkConstructorBody(body *shimast.Node, visit func(*shimast.Node)) {
306
+ if body == nil {
307
+ return
308
+ }
309
+ var walk func(*shimast.Node)
310
+ walk = func(n *shimast.Node) {
311
+ if n == nil {
312
+ return
313
+ }
314
+ if n != body && isFunctionLikeKind(n) {
315
+ return
316
+ }
317
+ visit(n)
318
+ n.ForEachChild(func(child *shimast.Node) bool {
319
+ walk(child)
320
+ return false
321
+ })
322
+ }
323
+ walk(body)
324
+ }
325
+
326
+ // preferObjectSpread reports `Object.assign({}, …)` calls that should
327
+ // be expressed with the modern spread syntax `{ …a, …b }`. The two
328
+ // forms are not exactly equivalent for property accessors and Symbol
329
+ // keys, but the spread form is the one the language has settled on and
330
+ // the readability gain is large for the common case.
331
+ // https://eslint.org/docs/latest/rules/prefer-object-spread
332
+ //
333
+ // Trigger: `Object.assign(target, …)` where the target is an empty
334
+ // object literal. Mutating `Object.assign` calls (non-empty first
335
+ // argument) are intentionally allowed because they have observable
336
+ // behavior the spread form does not preserve.
337
+ type preferObjectSpread struct{}
338
+
339
+ func (preferObjectSpread) Name() string { return "prefer-object-spread" }
340
+ func (preferObjectSpread) Visits() []shimast.Kind {
341
+ return []shimast.Kind{shimast.KindCallExpression}
342
+ }
343
+ func (preferObjectSpread) Check(ctx *Context, node *shimast.Node) {
344
+ call := node.AsCallExpression()
345
+ if call == nil || call.Expression == nil || call.Arguments == nil {
346
+ return
347
+ }
348
+ obj, method, ok := promisePropertyAccessParts(call.Expression)
349
+ if !ok || method != "assign" {
350
+ return
351
+ }
352
+ if identifierText(obj) != "Object" {
353
+ return
354
+ }
355
+ if len(call.Arguments.Nodes) < 2 {
356
+ return
357
+ }
358
+ first := stripParens(call.Arguments.Nodes[0])
359
+ if first == nil || first.Kind != shimast.KindObjectLiteralExpression {
360
+ return
361
+ }
362
+ if objectLiteralIsEmpty(first) {
363
+ ctx.Report(node, "Prefer object spread `{ ...a, ...b }` over `Object.assign({}, a, b)`.")
364
+ }
365
+ }
366
+
367
+ // objectLiteralIsEmpty reports whether node is the literal `{}` (no
368
+ // properties, no shorthand assignments, no spreads).
369
+ func objectLiteralIsEmpty(node *shimast.Node) bool {
370
+ if node == nil || node.Kind != shimast.KindObjectLiteralExpression {
371
+ return false
372
+ }
373
+ lit := node.AsObjectLiteralExpression()
374
+ if lit == nil || lit.Properties == nil {
375
+ return true
376
+ }
377
+ return len(lit.Properties.Nodes) == 0
378
+ }
379
+
380
+ // noDuplicateImports reports two import declarations that resolve to
381
+ // the same module specifier. Consolidating them into one import keeps
382
+ // the dependency graph at the head of the file readable and avoids
383
+ // surprising load-order interactions when the import has side effects.
384
+ // https://eslint.org/docs/latest/rules/no-duplicate-imports
385
+ //
386
+ // The check is textual on the module specifier string — two imports
387
+ // with the same exact string literal collide. `import type { … } from`
388
+ // is folded together with value imports because the runtime sees only
389
+ // one module load.
390
+ type noDuplicateImports struct{}
391
+
392
+ func (noDuplicateImports) Name() string { return "no-duplicate-imports" }
393
+ func (noDuplicateImports) Visits() []shimast.Kind {
394
+ return []shimast.Kind{shimast.KindSourceFile}
395
+ }
396
+ func (noDuplicateImports) Check(ctx *Context, node *shimast.Node) {
397
+ seen := map[string]bool{}
398
+ node.ForEachChild(func(child *shimast.Node) bool {
399
+ if child == nil || child.Kind != shimast.KindImportDeclaration {
400
+ return false
401
+ }
402
+ decl := child.AsImportDeclaration()
403
+ if decl == nil || decl.ModuleSpecifier == nil {
404
+ return false
405
+ }
406
+ spec := stringLiteralText(decl.ModuleSpecifier)
407
+ if spec == "" {
408
+ return false
409
+ }
410
+ if seen[spec] {
411
+ ctx.Report(child, "Module `"+spec+"` is already imported above; consolidate the imports.")
412
+ return false
413
+ }
414
+ seen[spec] = true
415
+ return false
416
+ })
417
+ }
418
+
419
+ // getterReturn reports a `get` accessor whose body completes without
420
+ // returning a value. The runtime returns `undefined` from such a
421
+ // getter; in practice that is always a bug — the caller expects the
422
+ // property to have a value.
423
+ // https://eslint.org/docs/latest/rules/getter-return
424
+ type getterReturn struct{}
425
+
426
+ func (getterReturn) Name() string { return "getter-return" }
427
+ func (getterReturn) Visits() []shimast.Kind {
428
+ return []shimast.Kind{shimast.KindGetAccessor}
429
+ }
430
+ func (getterReturn) Check(ctx *Context, node *shimast.Node) {
431
+ accessor := node.AsGetAccessorDeclaration()
432
+ if accessor == nil || accessor.Body == nil {
433
+ return
434
+ }
435
+ if !getterBodyAlwaysReturns(accessor.Body) {
436
+ ctx.Report(node, "Getter must return a value.")
437
+ }
438
+ }
439
+
440
+ // getterBodyAlwaysReturns walks a `get` accessor body and reports
441
+ // whether every reachable exit point returns a value. This is a
442
+ // shallow approximation — sufficient for the common case where the
443
+ // getter's body is a sequence of statements ending in `return X`.
444
+ func getterBodyAlwaysReturns(body *shimast.Node) bool {
445
+ if body == nil || body.Kind != shimast.KindBlock {
446
+ return false
447
+ }
448
+ statements := body.Statements()
449
+ if len(statements) == 0 {
450
+ return false
451
+ }
452
+ last := statements[len(statements)-1]
453
+ return statementReturnsValue(last)
454
+ }
455
+
456
+ // statementReturnsValue checks if a statement is a value-returning
457
+ // `return X;`, a `throw`, a block that ends in one of those, or a
458
+ // conditional whose every branch returns a value.
459
+ func statementReturnsValue(stmt *shimast.Node) bool {
460
+ if stmt == nil {
461
+ return false
462
+ }
463
+ switch stmt.Kind {
464
+ case shimast.KindReturnStatement:
465
+ ret := stmt.AsReturnStatement()
466
+ return ret != nil && ret.Expression != nil
467
+ case shimast.KindThrowStatement:
468
+ return true
469
+ case shimast.KindBlock:
470
+ stmts := stmt.Statements()
471
+ if len(stmts) == 0 {
472
+ return false
473
+ }
474
+ return statementReturnsValue(stmts[len(stmts)-1])
475
+ case shimast.KindIfStatement:
476
+ ifStmt := stmt.AsIfStatement()
477
+ if ifStmt == nil || ifStmt.ThenStatement == nil || ifStmt.ElseStatement == nil {
478
+ return false
479
+ }
480
+ return statementReturnsValue(ifStmt.ThenStatement) && statementReturnsValue(ifStmt.ElseStatement)
481
+ }
482
+ return false
483
+ }
484
+
485
+ // noNewSymbol reports `new Symbol(...)`. `Symbol` is a function but not
486
+ // a constructor; calling it with `new` throws a TypeError at runtime.
487
+ // https://eslint.org/docs/latest/rules/no-new-symbol — the upstream
488
+ // rule has been renamed `no-new-native-nonconstructor` but kept as an
489
+ // alias; we expose the legacy name because it remains the more readable
490
+ // pointer for this specific check.
491
+ type noNewSymbol struct{}
492
+
493
+ func (noNewSymbol) Name() string { return "no-new-symbol" }
494
+ func (noNewSymbol) Visits() []shimast.Kind {
495
+ return []shimast.Kind{shimast.KindNewExpression}
496
+ }
497
+ func (noNewSymbol) Check(ctx *Context, node *shimast.Node) {
498
+ ne := node.AsNewExpression()
499
+ if ne == nil {
500
+ return
501
+ }
502
+ if identifierText(ne.Expression) == "Symbol" {
503
+ ctx.Report(node, "`Symbol` cannot be called with `new`.")
504
+ }
505
+ }
506
+
507
+ // noConstructorReturn reports a constructor body that contains a
508
+ // `return X;` statement (i.e., the return statement carries a value).
509
+ // The returned value is ignored when the constructor is invoked with
510
+ // `new` unless it happens to be an object; relying on that behavior is
511
+ // always a misunderstanding of the constructor protocol.
512
+ // https://eslint.org/docs/latest/rules/no-constructor-return
513
+ type noConstructorReturn struct{}
514
+
515
+ func (noConstructorReturn) Name() string { return "no-constructor-return" }
516
+ func (noConstructorReturn) Visits() []shimast.Kind {
517
+ return []shimast.Kind{shimast.KindConstructor}
518
+ }
519
+ func (noConstructorReturn) Check(ctx *Context, node *shimast.Node) {
520
+ ctor := node.AsConstructorDeclaration()
521
+ if ctor == nil || ctor.Body == nil {
522
+ return
523
+ }
524
+ walkConstructorBody(ctor.Body, func(child *shimast.Node) {
525
+ if child == nil || child.Kind != shimast.KindReturnStatement {
526
+ return
527
+ }
528
+ ret := child.AsReturnStatement()
529
+ if ret != nil && ret.Expression != nil {
530
+ ctx.Report(child, "Class constructors should not return a value.")
531
+ }
532
+ })
533
+ }
534
+
535
+ // noUnsafeOptionalChaining reports member access or call expressions
536
+ // that chain off an optional chain WITHOUT continuing the optional
537
+ // chain. `(obj?.foo).bar` throws a TypeError if obj is null/undefined,
538
+ // because the outer `.bar` is no longer optional. Same for `obj?.foo()`
539
+ // followed by `.bar` — once the chain terminates, downstream accesses
540
+ // are unsafe again.
541
+ // https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining
542
+ type noUnsafeOptionalChaining struct{}
543
+
544
+ func (noUnsafeOptionalChaining) Name() string { return "no-unsafe-optional-chaining" }
545
+ func (noUnsafeOptionalChaining) Visits() []shimast.Kind {
546
+ return []shimast.Kind{
547
+ shimast.KindPropertyAccessExpression,
548
+ shimast.KindElementAccessExpression,
549
+ shimast.KindCallExpression,
550
+ }
551
+ }
552
+ func (noUnsafeOptionalChaining) Check(ctx *Context, node *shimast.Node) {
553
+ var receiver *shimast.Node
554
+ switch node.Kind {
555
+ case shimast.KindPropertyAccessExpression:
556
+ access := node.AsPropertyAccessExpression()
557
+ if access == nil {
558
+ return
559
+ }
560
+ // If this access is itself optional, the chain continues — safe.
561
+ if access.QuestionDotToken != nil {
562
+ return
563
+ }
564
+ receiver = access.Expression
565
+ case shimast.KindElementAccessExpression:
566
+ access := node.AsElementAccessExpression()
567
+ if access == nil {
568
+ return
569
+ }
570
+ if access.QuestionDotToken != nil {
571
+ return
572
+ }
573
+ receiver = access.Expression
574
+ case shimast.KindCallExpression:
575
+ call := node.AsCallExpression()
576
+ if call == nil {
577
+ return
578
+ }
579
+ if call.QuestionDotToken != nil {
580
+ return
581
+ }
582
+ receiver = call.Expression
583
+ }
584
+ if receiverEndsWithOptionalChain(receiver) {
585
+ ctx.Report(node, "Unsafe access after an optional chain — continue the chain with `?.` or check for nullish above.")
586
+ }
587
+ }
588
+
589
+ // receiverEndsWithOptionalChain reports whether the receiver expression
590
+ // terminates in an optional `?.` operator. If so, the result of the
591
+ // receiver may be undefined and subsequent member access is unsafe.
592
+ // A non-null assertion (`!`) does NOT make the access safe — at lint
593
+ // time the developer is suppressing the static-undefined warning, but
594
+ // at runtime the chain still resolves to undefined when the optional
595
+ // link short-circuits, so we look through `NonNullExpression` too.
596
+ func receiverEndsWithOptionalChain(node *shimast.Node) bool {
597
+ node = stripParens(node)
598
+ if node == nil {
599
+ return false
600
+ }
601
+ if node.Kind == shimast.KindNonNullExpression {
602
+ if nn := node.AsNonNullExpression(); nn != nil {
603
+ return receiverEndsWithOptionalChain(nn.Expression)
604
+ }
605
+ }
606
+ switch node.Kind {
607
+ case shimast.KindPropertyAccessExpression:
608
+ access := node.AsPropertyAccessExpression()
609
+ return access != nil && access.QuestionDotToken != nil
610
+ case shimast.KindElementAccessExpression:
611
+ access := node.AsElementAccessExpression()
612
+ return access != nil && access.QuestionDotToken != nil
613
+ case shimast.KindCallExpression:
614
+ call := node.AsCallExpression()
615
+ return call != nil && call.QuestionDotToken != nil
616
+ }
617
+ return false
618
+ }
619
+
620
+ // preferObjectHasOwn reports calls of the form
621
+ // `Object.prototype.hasOwnProperty.call(obj, key)` and suggests the
622
+ // `Object.hasOwn(obj, key)` shorthand introduced in ES2022. The new
623
+ // helper is shorter, less error-prone (no chance of a redefined
624
+ // `hasOwnProperty` on the host object), and matches the form linters
625
+ // elsewhere recommend.
626
+ // https://eslint.org/docs/latest/rules/prefer-object-has-own
627
+ type preferObjectHasOwn struct{}
628
+
629
+ func (preferObjectHasOwn) Name() string { return "prefer-object-has-own" }
630
+ func (preferObjectHasOwn) Visits() []shimast.Kind {
631
+ return []shimast.Kind{shimast.KindCallExpression}
632
+ }
633
+ func (preferObjectHasOwn) Check(ctx *Context, node *shimast.Node) {
634
+ call := node.AsCallExpression()
635
+ if call == nil || call.Expression == nil {
636
+ return
637
+ }
638
+ // Pattern: Object.prototype.hasOwnProperty.call(obj, key)
639
+ outer, method, ok := promisePropertyAccessParts(call.Expression)
640
+ if !ok || method != "call" {
641
+ return
642
+ }
643
+ inner, name, ok := promisePropertyAccessParts(outer)
644
+ if !ok || name != "hasOwnProperty" {
645
+ return
646
+ }
647
+ base, prop, ok := promisePropertyAccessParts(inner)
648
+ if !ok || prop != "prototype" {
649
+ return
650
+ }
651
+ if identifierText(base) != "Object" {
652
+ return
653
+ }
654
+ ctx.Report(node, "Prefer `Object.hasOwn(obj, key)` over `Object.prototype.hasOwnProperty.call(obj, key)`.")
655
+ }
656
+
657
+ // noImplicitCoercion reports the most common implicit coercion idioms:
658
+ // `!!x` for boolean coercion, `+x` for number, `"" + x` for string. ES
659
+ // has explicit coercion functions (`Boolean(x)`, `Number(x)`,
660
+ // `String(x)`) that read more clearly and avoid surprise around the
661
+ // edge cases (e.g. `+null === 0` vs `+undefined === NaN`).
662
+ // https://eslint.org/docs/latest/rules/no-implicit-coercion
663
+ type noImplicitCoercion struct{}
664
+
665
+ func (noImplicitCoercion) Name() string { return "no-implicit-coercion" }
666
+ func (noImplicitCoercion) Visits() []shimast.Kind {
667
+ return []shimast.Kind{
668
+ shimast.KindPrefixUnaryExpression,
669
+ shimast.KindBinaryExpression,
670
+ }
671
+ }
672
+ func (noImplicitCoercion) Check(ctx *Context, node *shimast.Node) {
673
+ switch node.Kind {
674
+ case shimast.KindPrefixUnaryExpression:
675
+ prefix := node.AsPrefixUnaryExpression()
676
+ if prefix == nil || prefix.Operand == nil {
677
+ return
678
+ }
679
+ switch prefix.Operator {
680
+ case shimast.KindExclamationToken:
681
+ // `!!x` → Boolean(x). The inner expression must be another `!`.
682
+ inner := stripParens(prefix.Operand)
683
+ if inner == nil || inner.Kind != shimast.KindPrefixUnaryExpression {
684
+ return
685
+ }
686
+ innerPrefix := inner.AsPrefixUnaryExpression()
687
+ if innerPrefix != nil && innerPrefix.Operator == shimast.KindExclamationToken {
688
+ ctx.Report(node, "Prefer `Boolean(x)` over `!!x` for explicit boolean coercion.")
689
+ }
690
+ case shimast.KindPlusToken:
691
+ // `+x` where x is not a numeric literal → Number(x). Skip
692
+ // numeric literals because `+0` / `+1` are the canonical form
693
+ // for explicit positive numbers.
694
+ operand := stripParens(prefix.Operand)
695
+ if operand == nil || operand.Kind == shimast.KindNumericLiteral {
696
+ return
697
+ }
698
+ ctx.Report(node, "Prefer `Number(x)` over `+x` for explicit number coercion.")
699
+ }
700
+ case shimast.KindBinaryExpression:
701
+ bin := node.AsBinaryExpression()
702
+ if bin == nil || bin.OperatorToken == nil || bin.OperatorToken.Kind != shimast.KindPlusToken {
703
+ return
704
+ }
705
+ // `"" + x` or `x + ""` → String(x).
706
+ left := stripParens(bin.Left)
707
+ right := stripParens(bin.Right)
708
+ if isEmptyStringLiteral(left) && right != nil && !isEmptyStringLiteral(right) {
709
+ ctx.Report(node, "Prefer `String(x)` over `\"\" + x` for explicit string coercion.")
710
+ return
711
+ }
712
+ if isEmptyStringLiteral(right) && left != nil && !isEmptyStringLiteral(left) {
713
+ ctx.Report(node, "Prefer `String(x)` over `x + \"\"` for explicit string coercion.")
714
+ }
715
+ }
716
+ }
717
+
718
+ // isEmptyStringLiteral reports whether node is the literal `""` or
719
+ // `''` (an empty-string string literal).
720
+ func isEmptyStringLiteral(node *shimast.Node) bool {
721
+ if node == nil || node.Kind != shimast.KindStringLiteral {
722
+ return false
723
+ }
724
+ return stringLiteralText(node) == ""
725
+ }
726
+
727
+ func init() {
728
+ Register(noAwaitInLoop{})
729
+ Register(noConstructorReturn{})
730
+ Register(noDupeClassMembers{})
731
+ Register(noDuplicateImports{})
732
+ Register(noImplicitCoercion{})
733
+ Register(noNewSymbol{})
734
+ Register(noThisBeforeSuper{})
735
+ Register(noUnsafeOptionalChaining{})
736
+ Register(getterReturn{})
737
+ Register(preferObjectHasOwn{})
738
+ Register(preferObjectSpread{})
739
+ }