@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,747 @@
1
+ package linthost
2
+
3
+ import (
4
+ "encoding/json"
5
+ "errors"
6
+ "flag"
7
+ "fmt"
8
+ "io/fs"
9
+ "net/url"
10
+ "os"
11
+ "path/filepath"
12
+ "strings"
13
+ "unicode/utf16"
14
+ "unicode/utf8"
15
+ )
16
+
17
+ const (
18
+ commandLintFixAll = "ttsc.lint.fixAll"
19
+ commandFormatDocument = "ttsc.format.document"
20
+ )
21
+
22
+ type lspPosition struct {
23
+ Line int `json:"line"`
24
+ Character int `json:"character"`
25
+ }
26
+
27
+ type lspRange struct {
28
+ Start lspPosition `json:"start"`
29
+ End lspPosition `json:"end"`
30
+ }
31
+
32
+ type lspDiagnostic struct {
33
+ Range lspRange `json:"range"`
34
+ Severity int `json:"severity,omitempty"`
35
+ Code string `json:"code,omitempty"`
36
+ Source string `json:"source,omitempty"`
37
+ Message string `json:"message"`
38
+ }
39
+
40
+ type lspCommand struct {
41
+ Title string `json:"title"`
42
+ Command string `json:"command"`
43
+ Arguments []json.RawMessage `json:"arguments,omitempty"`
44
+ }
45
+
46
+ type lspCodeAction struct {
47
+ Title string `json:"title"`
48
+ Kind string `json:"kind,omitempty"`
49
+ Command *lspCommand `json:"command,omitempty"`
50
+ IsPreferred bool `json:"isPreferred,omitempty"`
51
+ }
52
+
53
+ type lspCodeActionContextWire struct {
54
+ Only []string `json:"only,omitempty"`
55
+ }
56
+
57
+ type lspTextEdit struct {
58
+ Range lspRange `json:"range"`
59
+ NewText string `json:"newText"`
60
+ }
61
+
62
+ type lspWorkspaceEdit struct {
63
+ Changes map[string][]lspTextEdit `json:"changes,omitempty"`
64
+ }
65
+
66
+ type lspCommandOptions struct {
67
+ argumentsJSON string
68
+ command string
69
+ contextJSON string
70
+ cwd string
71
+ pluginsJSON string
72
+ // rangeJSON is accepted from the ttsc LSP server
73
+ // (`internal/lspserver/lsp_native_plugin_source.go`) for forward
74
+ // compatibility, but no current code path consumes it — code actions
75
+ // always operate on the full file. Range-aware actions can read this
76
+ // field once they're implemented.
77
+ rangeJSON string
78
+ tsconfig string
79
+ uri string
80
+ }
81
+
82
+ // RunLSPCommandIDs prints the workspace/executeCommand ids owned by @ttsc/lint.
83
+ func RunLSPCommandIDs([]string) int {
84
+ return writeJSON([]string{commandLintFixAll, commandFormatDocument})
85
+ }
86
+
87
+ // RunLSPCodeActionKinds prints the CodeActionKind values @ttsc/lint may return.
88
+ func RunLSPCodeActionKinds([]string) int {
89
+ return writeJSON([]string{"source.fixAll.ttsc", "source.format"})
90
+ }
91
+
92
+ // RunLSPDiagnostics prints lint diagnostics for one file URI as LSP JSON.
93
+ func RunLSPDiagnostics(args []string) int {
94
+ opts, ok := parseLSPCommandOptions("lsp-diagnostics", args)
95
+ if !ok {
96
+ return 2
97
+ }
98
+ findings, _, closeProgram, code := lspFindings(opts, false)
99
+ if closeProgram != nil {
100
+ defer closeProgram()
101
+ }
102
+ if code != 0 {
103
+ return code
104
+ }
105
+ diagnostics := make([]lspDiagnostic, 0, len(findings))
106
+ for _, finding := range findings {
107
+ diagnostics = append(diagnostics, findingToLSPDiagnostic(finding))
108
+ }
109
+ return writeJSON(diagnostics)
110
+ }
111
+
112
+ // RunLSPCodeActions prints code actions available for one file URI/range.
113
+ func RunLSPCodeActions(args []string) int {
114
+ opts, ok := parseLSPCommandOptions("lsp-code-actions", args)
115
+ if !ok {
116
+ return 2
117
+ }
118
+ acceptsLint := acceptsActionKind(opts.contextJSON, "source.fixAll.ttsc")
119
+ acceptsFormat := acceptsActionKind(opts.contextJSON, "source.format")
120
+ if !acceptsLint && !acceptsFormat {
121
+ return writeJSON([]lspCodeAction{})
122
+ }
123
+ if lspProjectTargetHasSegment(opts, "node_modules") {
124
+ return writeJSON([]lspCodeAction{})
125
+ }
126
+ if lspProjectTargetOutsideCwd(opts) {
127
+ return writeJSON([]lspCodeAction{})
128
+ }
129
+ findings, _, closeProgram, code := lspFindings(opts, acceptsFormat)
130
+ if closeProgram != nil {
131
+ defer closeProgram()
132
+ }
133
+ if code != 0 {
134
+ return code
135
+ }
136
+ lintFindings := filterLintFindings(findings)
137
+ formatFindings := filterFormatFindings(findings)
138
+ var actions []lspCodeAction
139
+ if acceptsLint && hasFixableFinding(lintFindings) {
140
+ uriArg, _ := json.Marshal(opts.uri)
141
+ actions = append(actions, lspCodeAction{
142
+ Title: "Fix all ttsc lint issues",
143
+ Kind: "source.fixAll.ttsc",
144
+ IsPreferred: true,
145
+ Command: &lspCommand{
146
+ Title: "Fix all ttsc lint issues",
147
+ Command: commandLintFixAll,
148
+ Arguments: []json.RawMessage{uriArg},
149
+ },
150
+ })
151
+ }
152
+ if acceptsFormat && hasFormatFinding(formatFindings) {
153
+ uriArg, _ := json.Marshal(opts.uri)
154
+ actions = append(actions, lspCodeAction{
155
+ Title: "Format document with ttsc",
156
+ Kind: "source.format",
157
+ Command: &lspCommand{
158
+ Title: "Format document with ttsc",
159
+ Command: commandFormatDocument,
160
+ Arguments: []json.RawMessage{uriArg},
161
+ },
162
+ })
163
+ }
164
+ return writeJSON(actions)
165
+ }
166
+
167
+ // RunLSPExecuteCommand returns a WorkspaceEdit for a lint-owned command.
168
+ func RunLSPExecuteCommand(args []string) int {
169
+ opts, ok := parseLSPCommandOptions("lsp-execute-command", args)
170
+ if !ok {
171
+ return 2
172
+ }
173
+ if opts.command != commandLintFixAll && opts.command != commandFormatDocument {
174
+ fmt.Fprintf(os.Stderr, "@ttsc/lint lsp-execute-command: unknown command %q\n", opts.command)
175
+ return 2
176
+ }
177
+ uri, err := firstURIArgument(opts.argumentsJSON)
178
+ if err != nil {
179
+ fmt.Fprintln(os.Stderr, err)
180
+ return 2
181
+ }
182
+ opts.uri = uri
183
+ edit, code := lspWorkspaceEditForCommand(opts)
184
+ if code != 0 {
185
+ return code
186
+ }
187
+ return writeJSON(edit)
188
+ }
189
+
190
+ func parseLSPCommandOptions(name string, args []string) (*lspCommandOptions, bool) {
191
+ fs := flag.NewFlagSet(name, flag.ContinueOnError)
192
+ fs.SetOutput(os.Stderr)
193
+ cwd := fs.String("cwd", "", "")
194
+ tsconfig := fs.String("tsconfig", "tsconfig.json", "")
195
+ pluginsJSON := fs.String("plugins-json", "", "")
196
+ uri := fs.String("uri", "", "")
197
+ rangeJSON := fs.String("range-json", "", "")
198
+ contextJSON := fs.String("context-json", "", "")
199
+ command := fs.String("command", "", "")
200
+ argumentsJSON := fs.String("arguments-json", "", "")
201
+ if err := fs.Parse(args); err != nil {
202
+ return nil, false
203
+ }
204
+ resolvedCwd, err := resolveCwd(*cwd)
205
+ if err != nil {
206
+ fmt.Fprintln(os.Stderr, err)
207
+ return nil, false
208
+ }
209
+ return &lspCommandOptions{
210
+ argumentsJSON: *argumentsJSON,
211
+ command: *command,
212
+ contextJSON: *contextJSON,
213
+ cwd: resolvedCwd,
214
+ pluginsJSON: *pluginsJSON,
215
+ rangeJSON: *rangeJSON,
216
+ tsconfig: *tsconfig,
217
+ uri: *uri,
218
+ }, true
219
+ }
220
+
221
+ func lspFindings(opts *lspCommandOptions, includeFormatDefaults bool) ([]*Finding, *program, func(), int) {
222
+ if opts.uri == "" {
223
+ fmt.Fprintln(os.Stderr, "@ttsc/lint: lsp command requires --uri")
224
+ return nil, nil, nil, 2
225
+ }
226
+ target, err := filePathFromURI(opts.uri)
227
+ if err != nil {
228
+ fmt.Fprintln(os.Stderr, err)
229
+ return nil, nil, nil, 2
230
+ }
231
+ rules, err := loadRules(opts.pluginsJSON, opts.cwd, opts.tsconfig)
232
+ if err != nil {
233
+ fmt.Fprintln(os.Stderr, err)
234
+ return nil, nil, nil, 2
235
+ }
236
+ if includeFormatDefaults {
237
+ rules = formatCommandResolver{inner: rules}
238
+ }
239
+ engine := NewEngineWithResolver(rules)
240
+ prog, parseDiags, err := loadProgram(opts.cwd, opts.tsconfig, loadProgramOptions{
241
+ forceNoEmit: true,
242
+ needsRuleChecker: engine.NeedsTypeChecker(),
243
+ })
244
+ if err != nil {
245
+ fmt.Fprintf(os.Stderr, "@ttsc/lint: %v\n", err)
246
+ return nil, nil, nil, 2
247
+ }
248
+ if len(parseDiags) > 0 {
249
+ // tsgo already owns parse diagnostics in the upstream LSP process.
250
+ return nil, prog, prog.close, 0
251
+ }
252
+ findings := filterFindingsForPath(engine.Run(prog.userSourceFiles(), prog.checker), target)
253
+ return findings, prog, prog.close, 0
254
+ }
255
+
256
+ func filterFindingsForPath(findings []*Finding, target string) []*Finding {
257
+ target = canonicalProjectPath("", target)
258
+ out := make([]*Finding, 0, len(findings))
259
+ for _, finding := range findings {
260
+ if finding == nil || finding.File == nil {
261
+ continue
262
+ }
263
+ if canonicalProjectPath("", finding.File.FileName()) == target {
264
+ out = append(out, finding)
265
+ }
266
+ }
267
+ return out
268
+ }
269
+
270
+ func findingToLSPDiagnostic(finding *Finding) lspDiagnostic {
271
+ return lspDiagnostic{
272
+ Range: lspRangeForFinding(finding),
273
+ Severity: lspSeverity(finding.Severity),
274
+ Code: finding.Rule,
275
+ Source: "ttsc/lint",
276
+ Message: finding.Message,
277
+ }
278
+ }
279
+
280
+ func lspRangeForFinding(finding *Finding) lspRange {
281
+ text := ""
282
+ if finding != nil && finding.File != nil {
283
+ text = finding.File.Text()
284
+ }
285
+ return lspRange{
286
+ Start: byteOffsetToLSPPosition(text, finding.Pos),
287
+ End: byteOffsetToLSPPosition(text, finding.End),
288
+ }
289
+ }
290
+
291
+ func lspSeverity(severity Severity) int {
292
+ if severity == SeverityError {
293
+ return 1
294
+ }
295
+ return 2
296
+ }
297
+
298
+ func hasFixableFinding(findings []*Finding) bool {
299
+ for _, finding := range findings {
300
+ if finding != nil && len(finding.Fix) > 0 {
301
+ return true
302
+ }
303
+ }
304
+ return false
305
+ }
306
+
307
+ func hasFormatFinding(findings []*Finding) bool {
308
+ for _, finding := range findings {
309
+ if finding != nil && finding.IsFormat && len(finding.Fix) > 0 {
310
+ return true
311
+ }
312
+ }
313
+ return false
314
+ }
315
+
316
+ func filterLintFindings(findings []*Finding) []*Finding {
317
+ out := make([]*Finding, 0, len(findings))
318
+ for _, finding := range findings {
319
+ if finding != nil && !finding.IsFormat {
320
+ out = append(out, finding)
321
+ }
322
+ }
323
+ return out
324
+ }
325
+
326
+ func acceptsActionKind(raw string, kind string) bool {
327
+ var ctx lspCodeActionContextWire
328
+ if strings.TrimSpace(raw) == "" || json.Unmarshal([]byte(raw), &ctx) != nil || len(ctx.Only) == 0 {
329
+ return true
330
+ }
331
+ for _, only := range ctx.Only {
332
+ if only == kind || strings.HasPrefix(kind, only+".") {
333
+ return true
334
+ }
335
+ }
336
+ return false
337
+ }
338
+
339
+ func lspWorkspaceEditForCommand(opts *lspCommandOptions) (*lspWorkspaceEdit, int) {
340
+ target, err := filePathFromURI(opts.uri)
341
+ if err != nil {
342
+ fmt.Fprintln(os.Stderr, err)
343
+ return nil, 2
344
+ }
345
+ if _, ok := projectRelativePath(opts.cwd, target); !ok {
346
+ fmt.Fprintf(os.Stderr, "@ttsc/lint: LSP command target %s is outside cwd %s\n", target, opts.cwd)
347
+ return nil, 2
348
+ }
349
+ if projectPathHasSegment(opts.cwd, target, "node_modules") {
350
+ return nil, 0
351
+ }
352
+ original, err := os.ReadFile(target)
353
+ if err != nil {
354
+ fmt.Fprintf(os.Stderr, "@ttsc/lint: read %s: %v\n", target, err)
355
+ return nil, 2
356
+ }
357
+ tempRoot, tempTarget, tempTsconfig, cleanup, err := prepareLSPCommandWorkspace(opts.cwd, opts.tsconfig, target)
358
+ if err != nil {
359
+ fmt.Fprintln(os.Stderr, err)
360
+ return nil, 2
361
+ }
362
+ defer cleanup()
363
+
364
+ pluginsJSON := remapLSPPluginsJSONForTempWorkspace(opts.pluginsJSON, opts.cwd, tempRoot)
365
+ rules, err := loadRules(pluginsJSON, tempRoot, tempTsconfig)
366
+ if err != nil {
367
+ fmt.Fprintln(os.Stderr, err)
368
+ return nil, 2
369
+ }
370
+ if opts.command == commandFormatDocument {
371
+ rules = formatCommandResolver{inner: rules}
372
+ }
373
+ engine := NewEngineWithResolver(rules)
374
+ needsRuleChecker := engine.NeedsTypeChecker()
375
+ maxPasses := maxFixPasses
376
+ if opts.command == commandFormatDocument {
377
+ maxPasses = maxFormatPasses
378
+ }
379
+ converged := false
380
+ for pass := 0; pass < maxPasses; pass++ {
381
+ prog, parseDiags, err := loadProgram(tempRoot, tempTsconfig, loadProgramOptions{
382
+ forceNoEmit: true,
383
+ needsRuleChecker: needsRuleChecker,
384
+ })
385
+ if err != nil {
386
+ fmt.Fprintf(os.Stderr, "@ttsc/lint: %v\n", err)
387
+ return nil, 2
388
+ }
389
+ if len(parseDiags) > 0 {
390
+ prog.close()
391
+ return nil, 0
392
+ }
393
+ findings := filterFindingsForPath(engine.Run(prog.userSourceFiles(), prog.checker), tempTarget)
394
+ prog.close()
395
+ if opts.command == commandFormatDocument {
396
+ findings = filterFormatFindings(findings)
397
+ } else {
398
+ findings = filterLintFindings(findings)
399
+ }
400
+ fixed, err := applyFindingFixes(tempRoot, findings)
401
+ if err != nil {
402
+ fmt.Fprintln(os.Stderr, err)
403
+ return nil, 3
404
+ }
405
+ if fixed == 0 {
406
+ converged = true
407
+ break
408
+ }
409
+ }
410
+ if !converged {
411
+ fmt.Fprintf(os.Stderr,
412
+ "@ttsc/lint: LSP %s cascade did not converge after %d passes\n",
413
+ opts.command, maxPasses)
414
+ return nil, 2
415
+ }
416
+ next, err := os.ReadFile(tempTarget)
417
+ if err != nil {
418
+ fmt.Fprintf(os.Stderr, "@ttsc/lint: read cascaded %s: %v\n", tempTarget, err)
419
+ return nil, 2
420
+ }
421
+ return workspaceEditForFullDocument(opts.uri, string(original), string(next)), 0
422
+ }
423
+
424
+ func workspaceEditForFullDocument(uri string, original string, next string) *lspWorkspaceEdit {
425
+ if original == next {
426
+ return nil
427
+ }
428
+ return &lspWorkspaceEdit{Changes: map[string][]lspTextEdit{uri: {{
429
+ Range: lspRange{
430
+ Start: lspPosition{Line: 0, Character: 0},
431
+ End: byteOffsetToLSPPosition(original, len(original)),
432
+ },
433
+ NewText: next,
434
+ }}}}
435
+ }
436
+
437
+ func prepareLSPCommandWorkspace(cwd string, tsconfig string, target string) (string, string, string, func(), error) {
438
+ tempRoot, err := os.MkdirTemp("", "ttsc-lint-lsp-")
439
+ if err != nil {
440
+ return "", "", "", nil, fmt.Errorf("@ttsc/lint: create LSP temp workspace: %w", err)
441
+ }
442
+ cleanup := func() { _ = os.RemoveAll(tempRoot) }
443
+ if _, ok := tempPathFor(cwd, tempRoot, target); !ok {
444
+ cleanup()
445
+ return "", "", "", nil, fmt.Errorf("@ttsc/lint: LSP command target %s is outside cwd %s", target, cwd)
446
+ }
447
+ if err := copyLSPCommandWorkspace(cwd, tempRoot); err != nil {
448
+ cleanup()
449
+ return "", "", "", nil, err
450
+ }
451
+ if err := linkNearestNodeModules(tempRoot, cwd); err != nil {
452
+ cleanup()
453
+ return "", "", "", nil, err
454
+ }
455
+ tempTarget, _ := tempPathFor(cwd, tempRoot, target)
456
+ tempTsconfig := tsconfig
457
+ if filepath.IsAbs(tsconfig) {
458
+ if mapped, ok := tempPathFor(cwd, tempRoot, tsconfig); ok {
459
+ tempTsconfig = mapped
460
+ }
461
+ }
462
+ return tempRoot, tempTarget, tempTsconfig, cleanup, nil
463
+ }
464
+
465
+ func copyLSPCommandWorkspace(src string, dst string) error {
466
+ seenDirs := map[string]struct{}{}
467
+ return filepath.WalkDir(src, func(current string, entry fs.DirEntry, walkErr error) error {
468
+ if walkErr != nil {
469
+ return walkErr
470
+ }
471
+ if current == src {
472
+ return nil
473
+ }
474
+ rel, err := filepath.Rel(src, current)
475
+ if err != nil {
476
+ return err
477
+ }
478
+ if shouldSkipLSPCommandWorkspaceDir(entry.Name()) {
479
+ if entry.IsDir() {
480
+ return filepath.SkipDir
481
+ }
482
+ if entry.Type()&fs.ModeSymlink != 0 {
483
+ return nil
484
+ }
485
+ }
486
+ return copyLSPCommandWorkspaceEntry(current, filepath.Join(dst, rel), seenDirs)
487
+ })
488
+ }
489
+
490
+ func copyLSPCommandWorkspaceEntry(src string, dst string, seenDirs map[string]struct{}) error {
491
+ info, err := os.Stat(src)
492
+ if err != nil {
493
+ return err
494
+ }
495
+ mode := info.Mode()
496
+ if info.IsDir() {
497
+ linkInfo, err := os.Lstat(src)
498
+ if err != nil {
499
+ return err
500
+ }
501
+ isSymlink := linkInfo.Mode()&os.ModeSymlink != 0
502
+ if isSymlink {
503
+ realDir, err := filepath.EvalSymlinks(src)
504
+ if err == nil {
505
+ if _, ok := seenDirs[realDir]; ok {
506
+ return nil
507
+ }
508
+ // Track the real path only for the active recursion branch.
509
+ // Releasing it on return lets sibling aliases pointing at the
510
+ // same real directory (e.g. `src-a -> real-src` and
511
+ // `src-b -> real-src` in different tsconfig entries) each get
512
+ // materialized; the test
513
+ // `TestLSPExecuteCommandMaterializesDuplicateSymlinkedDirectories`
514
+ // pins this contract.
515
+ seenDirs[realDir] = struct{}{}
516
+ defer delete(seenDirs, realDir)
517
+ }
518
+ }
519
+ if err := os.MkdirAll(dst, mode.Perm()); err != nil {
520
+ return err
521
+ }
522
+ if !isSymlink {
523
+ return nil
524
+ }
525
+ entries, err := os.ReadDir(src)
526
+ if err != nil {
527
+ return err
528
+ }
529
+ for _, entry := range entries {
530
+ if shouldSkipLSPCommandWorkspaceDir(entry.Name()) {
531
+ continue
532
+ }
533
+ if err := copyLSPCommandWorkspaceEntry(
534
+ filepath.Join(src, entry.Name()),
535
+ filepath.Join(dst, entry.Name()),
536
+ seenDirs,
537
+ ); err != nil {
538
+ return err
539
+ }
540
+ }
541
+ return nil
542
+ }
543
+ if !mode.IsRegular() {
544
+ return nil
545
+ }
546
+ data, err := os.ReadFile(src)
547
+ if err != nil {
548
+ return err
549
+ }
550
+ if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
551
+ return err
552
+ }
553
+ return os.WriteFile(dst, data, mode.Perm()|0o200)
554
+ }
555
+
556
+ func shouldSkipLSPCommandWorkspaceDir(name string) bool {
557
+ switch name {
558
+ case ".git", ".hg", ".svn", "node_modules":
559
+ return true
560
+ default:
561
+ return false
562
+ }
563
+ }
564
+
565
+ func pathHasSegment(file string, segment string) bool {
566
+ for _, part := range strings.Split(filepath.Clean(file), string(filepath.Separator)) {
567
+ if part == segment {
568
+ return true
569
+ }
570
+ }
571
+ return false
572
+ }
573
+
574
+ func projectPathHasSegment(cwd string, file string, segment string) bool {
575
+ rel, ok := projectRelativePath(cwd, file)
576
+ if !ok {
577
+ return false
578
+ }
579
+ return pathHasSegment(rel, segment)
580
+ }
581
+
582
+ func lspProjectTargetHasSegment(opts *lspCommandOptions, segment string) bool {
583
+ if opts.uri == "" {
584
+ return false
585
+ }
586
+ target, err := filePathFromURI(opts.uri)
587
+ if err != nil {
588
+ return false
589
+ }
590
+ return projectPathHasSegment(opts.cwd, target, segment)
591
+ }
592
+
593
+ func lspProjectTargetOutsideCwd(opts *lspCommandOptions) bool {
594
+ if opts.uri == "" {
595
+ return false
596
+ }
597
+ target, err := filePathFromURI(opts.uri)
598
+ if err != nil {
599
+ return false
600
+ }
601
+ _, ok := projectRelativePath(opts.cwd, target)
602
+ return !ok
603
+ }
604
+
605
+ func remapLSPPluginsJSONForTempWorkspace(raw string, cwd string, tempRoot string) string {
606
+ if strings.TrimSpace(raw) == "" {
607
+ return raw
608
+ }
609
+ var entries []map[string]any
610
+ if err := json.Unmarshal([]byte(raw), &entries); err != nil {
611
+ return raw
612
+ }
613
+ changed := false
614
+ for _, entry := range entries {
615
+ config, ok := entry["config"].(map[string]any)
616
+ if !ok {
617
+ continue
618
+ }
619
+ configFile, ok := config["configFile"].(string)
620
+ if !ok || !filepath.IsAbs(configFile) {
621
+ continue
622
+ }
623
+ mapped, ok := tempPathFor(cwd, tempRoot, configFile)
624
+ if !ok {
625
+ continue
626
+ }
627
+ config["configFile"] = mapped
628
+ changed = true
629
+ }
630
+ if !changed {
631
+ return raw
632
+ }
633
+ next, err := json.Marshal(entries)
634
+ if err != nil {
635
+ return raw
636
+ }
637
+ return string(next)
638
+ }
639
+
640
+ func tempPathFor(cwd string, tempRoot string, file string) (string, bool) {
641
+ rel, err := filepath.Rel(cwd, file)
642
+ if err != nil || rel == "." || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || filepath.IsAbs(rel) {
643
+ return "", false
644
+ }
645
+ return filepath.Join(tempRoot, rel), true
646
+ }
647
+
648
+ func projectRelativePath(cwd string, file string) (string, bool) {
649
+ rel, err := filepath.Rel(cwd, file)
650
+ if err != nil || rel == "." || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || filepath.IsAbs(rel) {
651
+ return "", false
652
+ }
653
+ return rel, true
654
+ }
655
+
656
+ func firstURIArgument(raw string) (string, error) {
657
+ if strings.TrimSpace(raw) == "" {
658
+ return "", errors.New("@ttsc/lint lsp-execute-command: missing URI argument")
659
+ }
660
+ var args []json.RawMessage
661
+ if err := json.Unmarshal([]byte(raw), &args); err != nil {
662
+ return "", fmt.Errorf("@ttsc/lint lsp-execute-command: invalid arguments JSON: %w", err)
663
+ }
664
+ if len(args) == 0 {
665
+ return "", errors.New("@ttsc/lint lsp-execute-command: missing URI argument")
666
+ }
667
+ var uri string
668
+ if err := json.Unmarshal(args[0], &uri); err != nil || strings.TrimSpace(uri) == "" {
669
+ return "", errors.New("@ttsc/lint lsp-execute-command: first argument must be a document URI")
670
+ }
671
+ return uri, nil
672
+ }
673
+
674
+ func filePathFromURI(raw string) (string, error) {
675
+ parsed, err := url.Parse(raw)
676
+ if err != nil {
677
+ return "", fmt.Errorf("@ttsc/lint: invalid file URI %q: %w", raw, err)
678
+ }
679
+ if parsed.Scheme != "file" {
680
+ return "", fmt.Errorf("@ttsc/lint: expected file URI, got %q", raw)
681
+ }
682
+ path := parsed.Path
683
+ if parsed.Host != "" {
684
+ path = "//" + parsed.Host + path
685
+ }
686
+ if path == "" {
687
+ return "", fmt.Errorf("@ttsc/lint: empty file URI path: %q", raw)
688
+ }
689
+ if os.PathSeparator == '\\' && strings.HasPrefix(path, "/") && len(path) >= 3 && path[2] == ':' {
690
+ path = path[1:]
691
+ }
692
+ abs, err := filepath.Abs(path)
693
+ if err != nil {
694
+ return "", err
695
+ }
696
+ return abs, nil
697
+ }
698
+
699
+ func byteOffsetToLSPPosition(text string, offset int) lspPosition {
700
+ if offset < 0 {
701
+ offset = 0
702
+ }
703
+ if offset > len(text) {
704
+ offset = len(text)
705
+ }
706
+ line, character := 0, 0
707
+ for i := 0; i < offset; {
708
+ r, size := utf8.DecodeRuneInString(text[i:])
709
+ if r == utf8.RuneError && size == 0 {
710
+ break
711
+ }
712
+ if i+size > offset {
713
+ break
714
+ }
715
+ switch r {
716
+ case '\r':
717
+ line++
718
+ character = 0
719
+ i += size
720
+ if i < offset && i < len(text) && text[i] == '\n' {
721
+ i++
722
+ }
723
+ continue
724
+ case '\n':
725
+ line++
726
+ character = 0
727
+ default:
728
+ if n := utf16.RuneLen(r); n > 0 {
729
+ character += n
730
+ } else {
731
+ character++
732
+ }
733
+ }
734
+ i += size
735
+ }
736
+ return lspPosition{Line: line, Character: character}
737
+ }
738
+
739
+ func writeJSON(value any) int {
740
+ data, err := json.Marshal(value)
741
+ if err != nil {
742
+ fmt.Fprintln(os.Stderr, err)
743
+ return 2
744
+ }
745
+ fmt.Fprintln(os.Stdout, string(data))
746
+ return 0
747
+ }