@ttsc/lint 0.13.0 → 0.14.0-dev.20250530.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 (498) hide show
  1. package/README.md +955 -85
  2. package/lib/defaultFormat.d.ts +15 -8
  3. package/lib/defaultFormat.js +15 -8
  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/TtscLintRuleSetting.d.ts +49 -0
  10. package/lib/structures/{TtscLintRuleOptions.js → TtscLintRuleSetting.js} +1 -1
  11. package/lib/structures/TtscLintRuleSetting.js.map +1 -0
  12. package/lib/structures/format/ITtscLintFormat.d.ts +128 -0
  13. package/lib/structures/{TtscLintRuleMap.js → format/ITtscLintFormat.js} +1 -1
  14. package/lib/structures/format/ITtscLintFormat.js.map +1 -0
  15. package/lib/structures/format/ITtscLintFormatJsDoc.d.ts +17 -0
  16. package/lib/structures/{ITtscLintFormatConfig.js → format/ITtscLintFormatJsDoc.js} +1 -1
  17. package/lib/structures/format/ITtscLintFormatJsDoc.js.map +1 -0
  18. package/lib/structures/format/ITtscLintFormatSortImports.d.ts +54 -0
  19. package/lib/structures/format/ITtscLintFormatSortImports.js +3 -0
  20. package/lib/structures/format/ITtscLintFormatSortImports.js.map +1 -0
  21. package/lib/structures/format/index.d.ts +3 -0
  22. package/lib/structures/format/index.js +20 -0
  23. package/lib/structures/format/index.js.map +1 -0
  24. package/lib/structures/index.d.ts +3 -4
  25. package/lib/structures/index.js +3 -4
  26. package/lib/structures/index.js.map +1 -1
  27. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +95 -0
  28. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js +13 -0
  29. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js.map +1 -0
  30. package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +75 -0
  31. package/lib/structures/rules/ITtscLintBoundariesRules.js +3 -0
  32. package/lib/structures/rules/ITtscLintBoundariesRules.js.map +1 -0
  33. package/lib/structures/rules/ITtscLintContributorRules.d.ts +16 -0
  34. package/lib/structures/rules/ITtscLintContributorRules.js +3 -0
  35. package/lib/structures/rules/ITtscLintContributorRules.js.map +1 -0
  36. package/lib/structures/rules/ITtscLintCoreRules.d.ts +1229 -0
  37. package/lib/structures/rules/ITtscLintCoreRules.js +3 -0
  38. package/lib/structures/rules/ITtscLintCoreRules.js.map +1 -0
  39. package/lib/structures/rules/ITtscLintCypressRuleOptions.d.ts +17 -0
  40. package/lib/structures/rules/ITtscLintCypressRuleOptions.js +10 -0
  41. package/lib/structures/rules/ITtscLintCypressRuleOptions.js.map +1 -0
  42. package/lib/structures/rules/ITtscLintCypressRules.d.ts +125 -0
  43. package/lib/structures/rules/ITtscLintCypressRules.js +3 -0
  44. package/lib/structures/rules/ITtscLintCypressRules.js.map +1 -0
  45. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.d.ts +154 -0
  46. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.js +13 -0
  47. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.js.map +1 -0
  48. package/lib/structures/rules/ITtscLintFunctionalRules.d.ts +162 -0
  49. package/lib/structures/rules/ITtscLintFunctionalRules.js +3 -0
  50. package/lib/structures/rules/ITtscLintFunctionalRules.js.map +1 -0
  51. package/lib/structures/rules/ITtscLintJestRules.d.ts +190 -0
  52. package/lib/structures/rules/ITtscLintJestRules.js +3 -0
  53. package/lib/structures/rules/ITtscLintJestRules.js.map +1 -0
  54. package/lib/structures/rules/ITtscLintJsdocRules.d.ts +133 -0
  55. package/lib/structures/rules/ITtscLintJsdocRules.js +3 -0
  56. package/lib/structures/rules/ITtscLintJsdocRules.js.map +1 -0
  57. package/lib/structures/rules/ITtscLintJsxA11yRules.d.ts +294 -0
  58. package/lib/structures/rules/ITtscLintJsxA11yRules.js +3 -0
  59. package/lib/structures/rules/ITtscLintJsxA11yRules.js.map +1 -0
  60. package/lib/structures/rules/ITtscLintNextjsRules.d.ts +180 -0
  61. package/lib/structures/rules/ITtscLintNextjsRules.js +3 -0
  62. package/lib/structures/rules/ITtscLintNextjsRules.js.map +1 -0
  63. package/lib/structures/rules/ITtscLintPlaywrightRules.d.ts +312 -0
  64. package/lib/structures/rules/ITtscLintPlaywrightRules.js +3 -0
  65. package/lib/structures/rules/ITtscLintPlaywrightRules.js.map +1 -0
  66. package/lib/structures/rules/ITtscLintPromiseRules.d.ts +179 -0
  67. package/lib/structures/rules/ITtscLintPromiseRules.js +3 -0
  68. package/lib/structures/rules/ITtscLintPromiseRules.js.map +1 -0
  69. package/lib/structures/rules/ITtscLintReactPerfRuleOptions.d.ts +18 -0
  70. package/lib/structures/rules/ITtscLintReactPerfRuleOptions.js +8 -0
  71. package/lib/structures/rules/ITtscLintReactPerfRuleOptions.js.map +1 -0
  72. package/lib/structures/rules/ITtscLintReactPerfRules.d.ts +49 -0
  73. package/lib/structures/rules/ITtscLintReactPerfRules.js +3 -0
  74. package/lib/structures/rules/ITtscLintReactPerfRules.js.map +1 -0
  75. package/lib/structures/rules/ITtscLintReactRuleOptions.d.ts +37 -0
  76. package/lib/structures/rules/ITtscLintReactRuleOptions.js +11 -0
  77. package/lib/structures/rules/ITtscLintReactRuleOptions.js.map +1 -0
  78. package/lib/structures/rules/ITtscLintReactRules.d.ts +276 -0
  79. package/lib/structures/rules/ITtscLintReactRules.js +3 -0
  80. package/lib/structures/rules/ITtscLintReactRules.js.map +1 -0
  81. package/lib/structures/rules/ITtscLintRegexpRules.d.ts +182 -0
  82. package/lib/structures/rules/ITtscLintRegexpRules.js +3 -0
  83. package/lib/structures/rules/ITtscLintRegexpRules.js.map +1 -0
  84. package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +65 -0
  85. package/lib/structures/rules/ITtscLintRuleOptionsMap.js +3 -0
  86. package/lib/structures/rules/ITtscLintRuleOptionsMap.js.map +1 -0
  87. package/lib/structures/rules/ITtscLintRules.d.ts +55 -0
  88. package/lib/structures/{TtscLintRule.js → rules/ITtscLintRules.js} +1 -1
  89. package/lib/structures/rules/ITtscLintRules.js.map +1 -0
  90. package/lib/structures/rules/ITtscLintSecurityRules.d.ts +141 -0
  91. package/lib/structures/rules/ITtscLintSecurityRules.js +3 -0
  92. package/lib/structures/rules/ITtscLintSecurityRules.js.map +1 -0
  93. package/lib/structures/rules/ITtscLintSolidRules.d.ts +178 -0
  94. package/lib/structures/rules/ITtscLintSolidRules.js +3 -0
  95. package/lib/structures/rules/ITtscLintSolidRules.js.map +1 -0
  96. package/lib/structures/rules/ITtscLintStorybookRuleOptions.d.ts +20 -0
  97. package/lib/structures/rules/ITtscLintStorybookRuleOptions.js +9 -0
  98. package/lib/structures/rules/ITtscLintStorybookRuleOptions.js.map +1 -0
  99. package/lib/structures/rules/ITtscLintStorybookRules.d.ts +165 -0
  100. package/lib/structures/rules/ITtscLintStorybookRules.js +3 -0
  101. package/lib/structures/rules/ITtscLintStorybookRules.js.map +1 -0
  102. package/lib/structures/rules/ITtscLintTanstackQueryRules.d.ts +88 -0
  103. package/lib/structures/rules/ITtscLintTanstackQueryRules.js +3 -0
  104. package/lib/structures/rules/ITtscLintTanstackQueryRules.js.map +1 -0
  105. package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.d.ts +21 -0
  106. package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.js +10 -0
  107. package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.js.map +1 -0
  108. package/lib/structures/rules/ITtscLintTestingLibraryRules.d.ts +293 -0
  109. package/lib/structures/rules/ITtscLintTestingLibraryRules.js +3 -0
  110. package/lib/structures/rules/ITtscLintTestingLibraryRules.js.map +1 -0
  111. package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +1074 -0
  112. package/lib/structures/rules/ITtscLintTypeScriptRules.js +3 -0
  113. package/lib/structures/rules/ITtscLintTypeScriptRules.js.map +1 -0
  114. package/lib/structures/rules/ITtscLintUnicornRules.d.ts +997 -0
  115. package/lib/structures/rules/ITtscLintUnicornRules.js +3 -0
  116. package/lib/structures/rules/ITtscLintUnicornRules.js.map +1 -0
  117. package/lib/structures/rules/ITtscLintVitestRules.d.ts +133 -0
  118. package/lib/structures/rules/ITtscLintVitestRules.js +3 -0
  119. package/lib/structures/rules/ITtscLintVitestRules.js.map +1 -0
  120. package/lib/structures/rules/index.d.ts +31 -0
  121. package/lib/structures/rules/index.js +48 -0
  122. package/lib/structures/rules/index.js.map +1 -0
  123. package/linthost/ast_helpers.go +30 -5
  124. package/linthost/compile.go +33 -2
  125. package/linthost/config.go +118 -71
  126. package/linthost/config_format.go +270 -104
  127. package/linthost/directives.go +67 -25
  128. package/linthost/dispatch.go +14 -3
  129. package/linthost/engine.go +160 -40
  130. package/linthost/fix.go +10 -3
  131. package/linthost/flags_gen.go +17 -15
  132. package/linthost/format.go +222 -25
  133. package/linthost/format_editor_settings.go +262 -0
  134. package/linthost/host.go +40 -3
  135. package/linthost/lsp.go +898 -0
  136. package/linthost/print_dispatch.go +5 -2
  137. package/linthost/print_engine.go +5 -5
  138. package/linthost/print_nodes_call.go +149 -67
  139. package/linthost/print_nodes_function.go +27 -2
  140. package/linthost/print_nodes_list.go +40 -5
  141. package/linthost/print_nodes_object.go +1 -1
  142. package/linthost/print_nodes_ternary.go +86 -0
  143. package/linthost/rule_names.go +14 -0
  144. package/linthost/rules_arrays.go +2 -2
  145. package/linthost/rules_boundaries.go +532 -0
  146. package/linthost/rules_camelcase.go +133 -0
  147. package/linthost/rules_complexity.go +95 -0
  148. package/linthost/rules_consistent_return.go +130 -0
  149. package/linthost/rules_console.go +1 -1
  150. package/linthost/rules_core_extra.go +739 -0
  151. package/linthost/rules_curly.go +70 -0
  152. package/linthost/rules_cypress.go +608 -0
  153. package/linthost/rules_debugger.go +2 -2
  154. package/linthost/rules_default_case.go +39 -0
  155. package/linthost/rules_default_case_last.go +46 -0
  156. package/linthost/rules_dupes.go +3 -3
  157. package/linthost/rules_empty.go +3 -3
  158. package/linthost/rules_escape.go +44 -24
  159. package/linthost/rules_eval.go +2 -2
  160. package/linthost/rules_finally.go +2 -2
  161. package/linthost/rules_format_arrow_parens.go +167 -0
  162. package/linthost/rules_format_bracket_spacing.go +117 -0
  163. package/linthost/rules_format_clause_join.go +172 -0
  164. package/linthost/rules_format_declaration_header.go +595 -0
  165. package/linthost/rules_format_indent.go +372 -0
  166. package/linthost/rules_format_jsdoc.go +5 -5
  167. package/linthost/rules_format_orphan_semi.go +106 -0
  168. package/linthost/rules_format_parameter_properties.go +175 -0
  169. package/linthost/rules_format_print_width.go +66 -55
  170. package/linthost/rules_format_quote_props.go +169 -0
  171. package/linthost/rules_format_quotes.go +76 -30
  172. package/linthost/rules_format_semi.go +186 -11
  173. package/linthost/rules_format_sort_imports.go +437 -118
  174. package/linthost/rules_format_statement_split.go +343 -0
  175. package/linthost/rules_format_ternary_nullish_parens.go +77 -0
  176. package/linthost/rules_format_trailing_comma.go +22 -3
  177. package/linthost/rules_format_whitespace.go +336 -0
  178. package/linthost/rules_functional.go +967 -0
  179. package/linthost/rules_gap.go +24 -45
  180. package/linthost/rules_grouped_accessor_pairs.go +87 -0
  181. package/linthost/rules_guard_for_in.go +198 -0
  182. package/linthost/rules_id_length.go +77 -0
  183. package/linthost/rules_imports.go +70 -2
  184. package/linthost/rules_init_declarations.go +53 -0
  185. package/linthost/rules_jest.go +618 -0
  186. package/linthost/rules_jsdoc.go +502 -0
  187. package/linthost/rules_jsx_a11y.go +1264 -0
  188. package/linthost/rules_logic.go +12 -12
  189. package/linthost/rules_loops.go +1 -1
  190. package/linthost/rules_max_classes_per_file.go +58 -0
  191. package/linthost/rules_max_depth.go +83 -0
  192. package/linthost/rules_max_lines.go +101 -0
  193. package/linthost/rules_max_lines_per_function.go +57 -0
  194. package/linthost/rules_max_nested_callbacks.go +61 -0
  195. package/linthost/rules_max_params.go +49 -0
  196. package/linthost/rules_max_statements.go +59 -0
  197. package/linthost/rules_misc.go +1 -1
  198. package/linthost/rules_nextjs.go +697 -0
  199. package/linthost/rules_no_else_return.go +75 -0
  200. package/linthost/rules_no_empty_named_blocks.go +91 -0
  201. package/linthost/rules_no_extend_native.go +51 -0
  202. package/linthost/rules_no_import_assign.go +140 -0
  203. package/linthost/rules_no_invalid_this.go +52 -0
  204. package/linthost/rules_no_loop_func.go +85 -0
  205. package/linthost/rules_no_magic_numbers.go +123 -0
  206. package/linthost/rules_no_mixed_operators.go +80 -0
  207. package/linthost/rules_no_param_reassign.go +98 -0
  208. package/linthost/rules_no_redeclare.go +270 -0
  209. package/linthost/rules_no_restricted_imports.go +105 -0
  210. package/linthost/rules_no_restricted_syntax.go +49 -0
  211. package/linthost/rules_no_shadow.go +147 -0
  212. package/linthost/rules_no_unreachable.go +77 -0
  213. package/linthost/rules_no_useless_assignment.go +120 -0
  214. package/linthost/rules_no_useless_constructor.go +245 -0
  215. package/linthost/rules_no_useless_return.go +51 -0
  216. package/linthost/rules_params.go +1 -1
  217. package/linthost/rules_playwright.go +788 -0
  218. package/linthost/rules_prefer_arrow_callback.go +124 -0
  219. package/linthost/rules_prefer_destructuring.go +98 -0
  220. package/linthost/rules_prefer_named_capture_group.go +129 -0
  221. package/linthost/rules_prefer_rest_params.go +94 -0
  222. package/linthost/rules_problems.go +41 -30
  223. package/linthost/rules_promise.go +1057 -2
  224. package/linthost/rules_protos.go +2 -2
  225. package/linthost/rules_react.go +773 -0
  226. package/linthost/rules_react_extras.go +227 -0
  227. package/linthost/rules_react_hooks.go +848 -0
  228. package/linthost/rules_react_perf.go +293 -0
  229. package/linthost/rules_react_refresh.go +583 -0
  230. package/linthost/rules_regexp.go +410 -0
  231. package/linthost/rules_security.go +830 -0
  232. package/linthost/rules_self.go +2 -2
  233. package/linthost/rules_solid.go +1073 -0
  234. package/linthost/rules_sort_imports.go +69 -0
  235. package/linthost/rules_sort_keys.go +134 -0
  236. package/linthost/rules_storybook.go +1176 -0
  237. package/linthost/rules_strings.go +4 -4
  238. package/linthost/rules_suggestions.go +38 -38
  239. package/linthost/rules_tanstack_query.go +844 -0
  240. package/linthost/rules_testing_library.go +1204 -0
  241. package/linthost/rules_throw.go +1 -1
  242. package/linthost/rules_ts.go +19 -19
  243. package/linthost/rules_ts_async.go +399 -0
  244. package/linthost/rules_ts_class_literal_property_style.go +115 -0
  245. package/linthost/rules_ts_consistent_type_exports.go +256 -0
  246. package/linthost/rules_ts_explicit_function_return_type.go +67 -0
  247. package/linthost/rules_ts_extra.go +159 -36
  248. package/linthost/rules_ts_no_base_to_string.go +218 -0
  249. package/linthost/rules_ts_no_confusing_void.go +72 -0
  250. package/linthost/rules_ts_no_deprecated.go +195 -0
  251. package/linthost/rules_ts_no_for_in_array.go +84 -0
  252. package/linthost/rules_ts_no_magic_numbers.go +124 -0
  253. package/linthost/rules_ts_no_meaningless_void_operator.go +84 -0
  254. package/linthost/rules_ts_no_misused_spread.go +104 -0
  255. package/linthost/rules_ts_no_redundant_type_constituents.go +113 -0
  256. package/linthost/rules_ts_no_restricted_types.go +62 -0
  257. package/linthost/rules_ts_no_unnecessary_boolean_literal_compare.go +129 -0
  258. package/linthost/rules_ts_no_unnecessary_condition.go +250 -0
  259. package/linthost/rules_ts_no_unnecessary_qualifier.go +118 -0
  260. package/linthost/rules_ts_no_unnecessary_template_expression.go +178 -0
  261. package/linthost/rules_ts_no_unnecessary_type_arguments.go +234 -0
  262. package/linthost/rules_ts_no_unnecessary_type_assertion.go +157 -0
  263. package/linthost/rules_ts_no_unsafe_argument.go +82 -0
  264. package/linthost/rules_ts_no_unsafe_assignment.go +102 -0
  265. package/linthost/rules_ts_no_unsafe_call.go +82 -0
  266. package/linthost/rules_ts_no_unsafe_enum_comparison.go +235 -0
  267. package/linthost/rules_ts_no_unsafe_member_access.go +58 -0
  268. package/linthost/rules_ts_no_unsafe_return.go +126 -0
  269. package/linthost/rules_ts_no_unsafe_unary_minus.go +81 -0
  270. package/linthost/rules_ts_no_useless_constructor.go +52 -0
  271. package/linthost/rules_ts_non_nullable_type_assertion_style.go +108 -0
  272. package/linthost/rules_ts_parameter_properties.go +38 -0
  273. package/linthost/rules_ts_prefer_find.go +178 -0
  274. package/linthost/rules_ts_prefer_includes.go +255 -0
  275. package/linthost/rules_ts_prefer_nullish_coalescing.go +104 -0
  276. package/linthost/rules_ts_prefer_optional_chain.go +228 -0
  277. package/linthost/rules_ts_prefer_promise_reject_errors.go +176 -0
  278. package/linthost/rules_ts_prefer_readonly.go +73 -0
  279. package/linthost/rules_ts_prefer_reduce_type_parameter.go +116 -0
  280. package/linthost/rules_ts_prefer_regexp_exec.go +122 -0
  281. package/linthost/rules_ts_prefer_return_this_type.go +135 -0
  282. package/linthost/rules_ts_prefer_string_starts_ends_with.go +360 -0
  283. package/linthost/rules_ts_promise_function_async.go +77 -0
  284. package/linthost/rules_ts_related_getter_setter_pairs.go +113 -0
  285. package/linthost/rules_ts_require_array_sort_compare.go +96 -0
  286. package/linthost/rules_ts_restrict_plus_operands.go +127 -0
  287. package/linthost/rules_ts_restrict_template_expressions.go +95 -0
  288. package/linthost/rules_ts_simple.go +387 -0
  289. package/linthost/rules_ts_sort_type_constituents.go +144 -0
  290. package/linthost/rules_ts_strict_boolean_expressions.go +157 -0
  291. package/linthost/rules_ts_switch_exhaustiveness_check.go +147 -0
  292. package/linthost/rules_ts_unbound_method.go +126 -0
  293. package/linthost/rules_tsdoc.go +136 -0
  294. package/linthost/rules_unicorn_better_regex.go +27 -0
  295. package/linthost/rules_unicorn_catch_error_name.go +46 -0
  296. package/linthost/rules_unicorn_consistent_assert.go +47 -0
  297. package/linthost/rules_unicorn_consistent_date_clone.go +54 -0
  298. package/linthost/rules_unicorn_consistent_destructuring.go +25 -0
  299. package/linthost/rules_unicorn_consistent_empty_array_spread.go +62 -0
  300. package/linthost/rules_unicorn_consistent_existence_index_check.go +80 -0
  301. package/linthost/rules_unicorn_consistent_function_scoping.go +25 -0
  302. package/linthost/rules_unicorn_consistent_template_literal_escape.go +25 -0
  303. package/linthost/rules_unicorn_custom_error_definition.go +138 -0
  304. package/linthost/rules_unicorn_empty_brace_spaces.go +73 -0
  305. package/linthost/rules_unicorn_error_message.go +64 -0
  306. package/linthost/rules_unicorn_escape_case.go +51 -0
  307. package/linthost/rules_unicorn_expiring_todo_comments.go +78 -0
  308. package/linthost/rules_unicorn_explicit_length_check.go +88 -0
  309. package/linthost/rules_unicorn_filename_case.go +29 -0
  310. package/linthost/rules_unicorn_import_style.go +28 -0
  311. package/linthost/rules_unicorn_isolated_functions.go +29 -0
  312. package/linthost/rules_unicorn_new_for_builtins.go +87 -0
  313. package/linthost/rules_unicorn_no_abusive_eslint_disable.go +63 -0
  314. package/linthost/rules_unicorn_no_accessor_recursion.go +115 -0
  315. package/linthost/rules_unicorn_no_anonymous_default_export.go +74 -0
  316. package/linthost/rules_unicorn_no_array_callback_reference.go +65 -0
  317. package/linthost/rules_unicorn_no_array_for_each.go +40 -0
  318. package/linthost/rules_unicorn_no_array_method_this_argument.go +61 -0
  319. package/linthost/rules_unicorn_no_array_reduce.go +41 -0
  320. package/linthost/rules_unicorn_no_array_reverse.go +44 -0
  321. package/linthost/rules_unicorn_no_array_sort.go +43 -0
  322. package/linthost/rules_unicorn_no_await_expression_member.go +40 -0
  323. package/linthost/rules_unicorn_no_await_in_promise_methods.go +69 -0
  324. package/linthost/rules_unicorn_no_console_spaces.go +61 -0
  325. package/linthost/rules_unicorn_no_document_cookie.go +41 -0
  326. package/linthost/rules_unicorn_no_empty_file.go +44 -0
  327. package/linthost/rules_unicorn_no_for_loop.go +102 -0
  328. package/linthost/rules_unicorn_no_hex_escape.go +42 -0
  329. package/linthost/rules_unicorn_no_immediate_mutation.go +90 -0
  330. package/linthost/rules_unicorn_no_instanceof_builtins.go +66 -0
  331. package/linthost/rules_unicorn_no_invalid_fetch_options.go +79 -0
  332. package/linthost/rules_unicorn_no_invalid_remove_event_listener.go +51 -0
  333. package/linthost/rules_unicorn_no_keyword_prefix.go +101 -0
  334. package/linthost/rules_unicorn_no_lonely_if.go +44 -0
  335. package/linthost/rules_unicorn_no_magic_array_flat_depth.go +51 -0
  336. package/linthost/rules_unicorn_no_named_default.go +54 -0
  337. package/linthost/rules_unicorn_no_negated_condition.go +85 -0
  338. package/linthost/rules_unicorn_no_negation_in_equality_check.go +50 -0
  339. package/linthost/rules_unicorn_no_nested_ternary.go +43 -0
  340. package/linthost/rules_unicorn_no_new_array.go +30 -0
  341. package/linthost/rules_unicorn_no_new_buffer.go +31 -0
  342. package/linthost/rules_unicorn_no_null.go +24 -0
  343. package/linthost/rules_unicorn_no_object_as_default_parameter.go +48 -0
  344. package/linthost/rules_unicorn_no_process_exit.go +43 -0
  345. package/linthost/rules_unicorn_no_single_promise_in_promise_methods.go +59 -0
  346. package/linthost/rules_unicorn_no_static_only_class.go +56 -0
  347. package/linthost/rules_unicorn_no_thenable.go +74 -0
  348. package/linthost/rules_unicorn_no_this_assignment.go +35 -0
  349. package/linthost/rules_unicorn_no_typeof_undefined.go +58 -0
  350. package/linthost/rules_unicorn_no_unnecessary_array_flat_depth.go +49 -0
  351. package/linthost/rules_unicorn_no_unnecessary_array_splice_count.go +70 -0
  352. package/linthost/rules_unicorn_no_unnecessary_await.go +51 -0
  353. package/linthost/rules_unicorn_no_unnecessary_polyfills.go +32 -0
  354. package/linthost/rules_unicorn_no_unnecessary_slice_end.go +48 -0
  355. package/linthost/rules_unicorn_no_unreadable_array_destructuring.go +115 -0
  356. package/linthost/rules_unicorn_no_unreadable_iife.go +50 -0
  357. package/linthost/rules_unicorn_no_unused_properties.go +30 -0
  358. package/linthost/rules_unicorn_no_useless_collection_argument.go +63 -0
  359. package/linthost/rules_unicorn_no_useless_error_capture_stack_trace.go +46 -0
  360. package/linthost/rules_unicorn_no_useless_fallback_in_spread.go +81 -0
  361. package/linthost/rules_unicorn_no_useless_iterator_to_array.go +72 -0
  362. package/linthost/rules_unicorn_no_useless_length_check.go +144 -0
  363. package/linthost/rules_unicorn_no_useless_promise_resolve_reject.go +81 -0
  364. package/linthost/rules_unicorn_no_useless_spread.go +67 -0
  365. package/linthost/rules_unicorn_no_useless_switch_case.go +55 -0
  366. package/linthost/rules_unicorn_no_useless_undefined.go +55 -0
  367. package/linthost/rules_unicorn_no_zero_fractions.go +52 -0
  368. package/linthost/rules_unicorn_number_literal_case.go +69 -0
  369. package/linthost/rules_unicorn_numeric_separators_style.go +75 -0
  370. package/linthost/rules_unicorn_prefer_add_event_listener.go +66 -0
  371. package/linthost/rules_unicorn_prefer_array_find.go +51 -0
  372. package/linthost/rules_unicorn_prefer_array_flat.go +52 -0
  373. package/linthost/rules_unicorn_prefer_array_flat_map.go +63 -0
  374. package/linthost/rules_unicorn_prefer_array_index_of.go +147 -0
  375. package/linthost/rules_unicorn_prefer_array_some.go +67 -0
  376. package/linthost/rules_unicorn_prefer_at.go +77 -0
  377. package/linthost/rules_unicorn_prefer_bigint_literals.go +77 -0
  378. package/linthost/rules_unicorn_prefer_blob_reading_methods.go +44 -0
  379. package/linthost/rules_unicorn_prefer_class_fields.go +93 -0
  380. package/linthost/rules_unicorn_prefer_classlist_toggle.go +120 -0
  381. package/linthost/rules_unicorn_prefer_code_point.go +49 -0
  382. package/linthost/rules_unicorn_prefer_date_now.go +78 -0
  383. package/linthost/rules_unicorn_prefer_default_parameters.go +144 -0
  384. package/linthost/rules_unicorn_prefer_dom_node_append.go +40 -0
  385. package/linthost/rules_unicorn_prefer_dom_node_dataset.go +55 -0
  386. package/linthost/rules_unicorn_prefer_dom_node_remove.go +41 -0
  387. package/linthost/rules_unicorn_prefer_dom_node_text_content.go +37 -0
  388. package/linthost/rules_unicorn_prefer_event_target.go +36 -0
  389. package/linthost/rules_unicorn_prefer_export_from.go +129 -0
  390. package/linthost/rules_unicorn_prefer_global_this.go +127 -0
  391. package/linthost/rules_unicorn_prefer_import_meta_properties.go +66 -0
  392. package/linthost/rules_unicorn_prefer_includes.go +149 -0
  393. package/linthost/rules_unicorn_prefer_json_parse_buffer.go +65 -0
  394. package/linthost/rules_unicorn_prefer_keyboard_event_key.go +38 -0
  395. package/linthost/rules_unicorn_prefer_logical_operator_over_ternary.go +48 -0
  396. package/linthost/rules_unicorn_prefer_math_min_max.go +60 -0
  397. package/linthost/rules_unicorn_prefer_math_trunc.go +62 -0
  398. package/linthost/rules_unicorn_prefer_modern_dom_apis.go +47 -0
  399. package/linthost/rules_unicorn_prefer_modern_math_apis.go +70 -0
  400. package/linthost/rules_unicorn_prefer_module.go +50 -0
  401. package/linthost/rules_unicorn_prefer_native_coercion_functions.go +114 -0
  402. package/linthost/rules_unicorn_prefer_negative_index.go +74 -0
  403. package/linthost/rules_unicorn_prefer_node_protocol.go +122 -0
  404. package/linthost/rules_unicorn_prefer_number_properties.go +88 -0
  405. package/linthost/rules_unicorn_prefer_object_from_entries.go +55 -0
  406. package/linthost/rules_unicorn_prefer_optional_catch_binding.go +62 -0
  407. package/linthost/rules_unicorn_prefer_prototype_methods.go +54 -0
  408. package/linthost/rules_unicorn_prefer_query_selector.go +56 -0
  409. package/linthost/rules_unicorn_prefer_reflect_apply.go +36 -0
  410. package/linthost/rules_unicorn_prefer_regexp_test_rule.go +80 -0
  411. package/linthost/rules_unicorn_prefer_response_static_json.go +53 -0
  412. package/linthost/rules_unicorn_prefer_set_has.go +42 -0
  413. package/linthost/rules_unicorn_prefer_set_size.go +48 -0
  414. package/linthost/rules_unicorn_prefer_simple_condition_first.go +34 -0
  415. package/linthost/rules_unicorn_prefer_single_call.go +96 -0
  416. package/linthost/rules_unicorn_prefer_spread.go +51 -0
  417. package/linthost/rules_unicorn_prefer_string_raw.go +43 -0
  418. package/linthost/rules_unicorn_prefer_string_replace_all.go +95 -0
  419. package/linthost/rules_unicorn_prefer_string_slice.go +43 -0
  420. package/linthost/rules_unicorn_prefer_string_starts_ends_with.go +148 -0
  421. package/linthost/rules_unicorn_prefer_string_trim_start_end.go +48 -0
  422. package/linthost/rules_unicorn_prefer_structured_clone.go +71 -0
  423. package/linthost/rules_unicorn_prefer_switch.go +103 -0
  424. package/linthost/rules_unicorn_prefer_ternary.go +71 -0
  425. package/linthost/rules_unicorn_prefer_top_level_await.go +72 -0
  426. package/linthost/rules_unicorn_prefer_type_error.go +127 -0
  427. package/linthost/rules_unicorn_prevent_abbreviations.go +127 -0
  428. package/linthost/rules_unicorn_relative_url_style.go +49 -0
  429. package/linthost/rules_unicorn_require_array_join_separator.go +45 -0
  430. package/linthost/rules_unicorn_require_module_attributes.go +54 -0
  431. package/linthost/rules_unicorn_require_module_specifiers.go +55 -0
  432. package/linthost/rules_unicorn_require_number_to_fixed_digits_argument.go +43 -0
  433. package/linthost/rules_unicorn_require_post_message_target_origin.go +44 -0
  434. package/linthost/rules_unicorn_string_content.go +27 -0
  435. package/linthost/rules_unicorn_switch_case_braces.go +42 -0
  436. package/linthost/rules_unicorn_switch_case_break_position.go +31 -0
  437. package/linthost/rules_unicorn_template_indent.go +28 -0
  438. package/linthost/rules_unicorn_text_encoding_identifier_case.go +72 -0
  439. package/linthost/rules_unicorn_throw_new_error.go +61 -0
  440. package/linthost/rules_var.go +10 -4
  441. package/linthost/rules_vitest.go +568 -0
  442. package/package.json +3 -3
  443. package/plugin/main.go +49 -3
  444. package/rule/astutil/astutil.go +10 -3
  445. package/rule/rule.go +12 -10
  446. package/src/defaultFormat.ts +17 -10
  447. package/src/index.ts +65 -43
  448. package/src/structures/ITtscLintConfig.ts +14 -8
  449. package/src/structures/TtscLintRuleSetting.ts +55 -0
  450. package/src/structures/format/ITtscLintFormat.ts +141 -0
  451. package/src/structures/format/ITtscLintFormatJsDoc.ts +18 -0
  452. package/src/structures/format/ITtscLintFormatSortImports.ts +62 -0
  453. package/src/structures/format/index.ts +3 -0
  454. package/src/structures/index.ts +3 -4
  455. package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +117 -0
  456. package/src/structures/rules/ITtscLintBoundariesRules.ts +91 -0
  457. package/src/structures/rules/ITtscLintContributorRules.ts +20 -0
  458. package/src/structures/rules/ITtscLintCoreRules.ts +1378 -0
  459. package/src/structures/rules/ITtscLintCypressRuleOptions.ts +18 -0
  460. package/src/structures/rules/ITtscLintCypressRules.ts +141 -0
  461. package/src/structures/rules/ITtscLintFunctionalRuleOptions.ts +200 -0
  462. package/src/structures/rules/ITtscLintFunctionalRules.ts +197 -0
  463. package/src/structures/rules/ITtscLintJestRules.ts +209 -0
  464. package/src/structures/rules/ITtscLintJsdocRules.ts +146 -0
  465. package/src/structures/rules/ITtscLintJsxA11yRules.ts +331 -0
  466. package/src/structures/rules/ITtscLintNextjsRules.ts +201 -0
  467. package/src/structures/rules/ITtscLintPlaywrightRules.ts +343 -0
  468. package/src/structures/rules/ITtscLintPromiseRules.ts +196 -0
  469. package/src/structures/rules/ITtscLintReactPerfRuleOptions.ts +19 -0
  470. package/src/structures/rules/ITtscLintReactPerfRules.ts +53 -0
  471. package/src/structures/rules/ITtscLintReactRuleOptions.ts +41 -0
  472. package/src/structures/rules/ITtscLintReactRules.ts +308 -0
  473. package/src/structures/rules/ITtscLintRegexpRules.ts +204 -0
  474. package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -0
  475. package/src/structures/rules/ITtscLintRules.ts +77 -0
  476. package/src/structures/rules/ITtscLintSecurityRules.ts +155 -0
  477. package/src/structures/rules/ITtscLintSolidRules.ts +199 -0
  478. package/src/structures/rules/ITtscLintStorybookRuleOptions.ts +22 -0
  479. package/src/structures/rules/ITtscLintStorybookRules.ts +184 -0
  480. package/src/structures/rules/ITtscLintTanstackQueryRules.ts +96 -0
  481. package/src/structures/rules/ITtscLintTestingLibraryRuleOptions.ts +23 -0
  482. package/src/structures/rules/ITtscLintTestingLibraryRules.ts +325 -0
  483. package/src/structures/rules/ITtscLintTypeScriptRules.ts +1172 -0
  484. package/src/structures/rules/ITtscLintUnicornRules.ts +1143 -0
  485. package/src/structures/rules/ITtscLintVitestRules.ts +146 -0
  486. package/src/structures/rules/index.ts +31 -0
  487. package/lib/structures/ITtscLintFormatConfig.d.ts +0 -153
  488. package/lib/structures/ITtscLintFormatConfig.js.map +0 -1
  489. package/lib/structures/TtscLintRule.d.ts +0 -17
  490. package/lib/structures/TtscLintRule.js.map +0 -1
  491. package/lib/structures/TtscLintRuleMap.d.ts +0 -46
  492. package/lib/structures/TtscLintRuleMap.js.map +0 -1
  493. package/lib/structures/TtscLintRuleOptions.d.ts +0 -137
  494. package/lib/structures/TtscLintRuleOptions.js.map +0 -1
  495. package/src/structures/ITtscLintFormatConfig.ts +0 -168
  496. package/src/structures/TtscLintRule.ts +0 -160
  497. package/src/structures/TtscLintRuleMap.ts +0 -55
  498. package/src/structures/TtscLintRuleOptions.ts +0 -151
@@ -0,0 +1,898 @@
1
+ package linthost
2
+
3
+ import (
4
+ "encoding/json"
5
+ "errors"
6
+ "flag"
7
+ "fmt"
8
+ "io"
9
+ "io/fs"
10
+ "net/url"
11
+ "os"
12
+ "path/filepath"
13
+ "strings"
14
+ "unicode/utf16"
15
+ "unicode/utf8"
16
+
17
+ shimast "github.com/microsoft/typescript-go/shim/ast"
18
+ shimcore "github.com/microsoft/typescript-go/shim/core"
19
+ shimparser "github.com/microsoft/typescript-go/shim/parser"
20
+ )
21
+
22
+ const (
23
+ commandLintFixAll = "ttsc.lint.fixAll"
24
+ commandFormatDocument = "ttsc.format.document"
25
+ )
26
+
27
+ type lspPosition struct {
28
+ Line int `json:"line"`
29
+ Character int `json:"character"`
30
+ }
31
+
32
+ type lspRange struct {
33
+ Start lspPosition `json:"start"`
34
+ End lspPosition `json:"end"`
35
+ }
36
+
37
+ type lspDiagnostic struct {
38
+ Range lspRange `json:"range"`
39
+ Severity int `json:"severity,omitempty"`
40
+ Code string `json:"code,omitempty"`
41
+ Source string `json:"source,omitempty"`
42
+ Message string `json:"message"`
43
+ }
44
+
45
+ type lspCommand struct {
46
+ Title string `json:"title"`
47
+ Command string `json:"command"`
48
+ Arguments []json.RawMessage `json:"arguments,omitempty"`
49
+ }
50
+
51
+ type lspCodeAction struct {
52
+ Title string `json:"title"`
53
+ Kind string `json:"kind,omitempty"`
54
+ Command *lspCommand `json:"command,omitempty"`
55
+ IsPreferred bool `json:"isPreferred,omitempty"`
56
+ }
57
+
58
+ type lspCodeActionContextWire struct {
59
+ Only []string `json:"only,omitempty"`
60
+ }
61
+
62
+ type lspTextEdit struct {
63
+ Range lspRange `json:"range"`
64
+ NewText string `json:"newText"`
65
+ }
66
+
67
+ type lspWorkspaceEdit struct {
68
+ Changes map[string][]lspTextEdit `json:"changes,omitempty"`
69
+ }
70
+
71
+ type lspCommandOptions struct {
72
+ argumentsJSON string
73
+ command string
74
+ contextJSON string
75
+ // contentStdin reports whether the caller passed --content-stdin. When
76
+ // set, RunLSPExecuteCommand reads the FULL document buffer from os.Stdin
77
+ // (to EOF) and formats that text in memory instead of reading the target
78
+ // file from disk. See lspFormatBuffer.
79
+ contentStdin bool
80
+ cwd string
81
+ pluginsJSON string
82
+ // rangeJSON is accepted from the ttsc LSP server
83
+ // (`internal/lspserver/lsp_native_plugin_source.go`) for forward
84
+ // compatibility, but no current code path consumes it — code actions
85
+ // always operate on the full file. Range-aware actions can read this
86
+ // field once they're implemented.
87
+ rangeJSON string
88
+ tsconfig string
89
+ uri string
90
+ }
91
+
92
+ // RunLSPCommandIDs prints the workspace/executeCommand ids owned by @ttsc/lint.
93
+ func RunLSPCommandIDs([]string) int {
94
+ return writeJSON([]string{commandLintFixAll, commandFormatDocument})
95
+ }
96
+
97
+ // RunLSPCodeActionKinds prints the CodeActionKind values @ttsc/lint may return.
98
+ func RunLSPCodeActionKinds([]string) int {
99
+ return writeJSON([]string{"source.fixAll.ttsc", "source.format"})
100
+ }
101
+
102
+ // RunLSPDiagnostics prints lint diagnostics for one file URI as LSP JSON.
103
+ func RunLSPDiagnostics(args []string) int {
104
+ opts, ok := parseLSPCommandOptions("lsp-diagnostics", args)
105
+ if !ok {
106
+ return 2
107
+ }
108
+ findings, _, closeProgram, code := lspFindings(opts, false)
109
+ if closeProgram != nil {
110
+ defer closeProgram()
111
+ }
112
+ if code != 0 {
113
+ return code
114
+ }
115
+ diagnostics := make([]lspDiagnostic, 0, len(findings))
116
+ for _, finding := range findings {
117
+ diagnostics = append(diagnostics, findingToLSPDiagnostic(finding))
118
+ }
119
+ return writeJSON(diagnostics)
120
+ }
121
+
122
+ // RunLSPCodeActions prints code actions available for one file URI/range.
123
+ func RunLSPCodeActions(args []string) int {
124
+ opts, ok := parseLSPCommandOptions("lsp-code-actions", args)
125
+ if !ok {
126
+ return 2
127
+ }
128
+ acceptsLint := acceptsActionKind(opts.contextJSON, "source.fixAll.ttsc")
129
+ acceptsFormat := acceptsActionKind(opts.contextJSON, "source.format")
130
+ if !acceptsLint && !acceptsFormat {
131
+ return writeJSON([]lspCodeAction{})
132
+ }
133
+ if lspProjectTargetHasSegment(opts, "node_modules") {
134
+ return writeJSON([]lspCodeAction{})
135
+ }
136
+ if lspProjectTargetOutsideCwd(opts) {
137
+ return writeJSON([]lspCodeAction{})
138
+ }
139
+ findings, _, closeProgram, code := lspFindings(opts, acceptsFormat)
140
+ if closeProgram != nil {
141
+ defer closeProgram()
142
+ }
143
+ if code != 0 {
144
+ return code
145
+ }
146
+ lintFindings := filterLintFindings(findings)
147
+ formatFindings := filterFormatFindings(findings)
148
+ var actions []lspCodeAction
149
+ if acceptsLint && hasFixableFinding(lintFindings) {
150
+ uriArg, _ := json.Marshal(opts.uri)
151
+ actions = append(actions, lspCodeAction{
152
+ Title: "Fix all ttsc lint issues",
153
+ Kind: "source.fixAll.ttsc",
154
+ IsPreferred: true,
155
+ Command: &lspCommand{
156
+ Title: "Fix all ttsc lint issues",
157
+ Command: commandLintFixAll,
158
+ Arguments: []json.RawMessage{uriArg},
159
+ },
160
+ })
161
+ }
162
+ if acceptsFormat && hasFormatFinding(formatFindings) {
163
+ uriArg, _ := json.Marshal(opts.uri)
164
+ actions = append(actions, lspCodeAction{
165
+ Title: "Format document with ttsc",
166
+ Kind: "source.format",
167
+ Command: &lspCommand{
168
+ Title: "Format document with ttsc",
169
+ Command: commandFormatDocument,
170
+ Arguments: []json.RawMessage{uriArg},
171
+ },
172
+ })
173
+ }
174
+ return writeJSON(actions)
175
+ }
176
+
177
+ // RunLSPExecuteCommand returns a WorkspaceEdit for a lint-owned command.
178
+ func RunLSPExecuteCommand(args []string) int {
179
+ opts, ok := parseLSPCommandOptions("lsp-execute-command", args)
180
+ if !ok {
181
+ return 2
182
+ }
183
+ if opts.command != commandLintFixAll && opts.command != commandFormatDocument {
184
+ fmt.Fprintf(os.Stderr, "@ttsc/lint lsp-execute-command: unknown command %q\n", opts.command)
185
+ return 2
186
+ }
187
+ uri, err := firstURIArgument(opts.argumentsJSON)
188
+ if err != nil {
189
+ fmt.Fprintln(os.Stderr, err)
190
+ return 2
191
+ }
192
+ opts.uri = uri
193
+ // --content-stdin selects the lightweight in-memory format path: the full
194
+ // document buffer is read from stdin and formatted with AST+source rules
195
+ // only, with no temp-workspace copy and no tsgo Program. It applies to
196
+ // ttsc.format.document; ttsc.lint.fixAll under --content-stdin is out of
197
+ // scope (lint-class fixes can require a type checker), so it falls back to
198
+ // the disk-based path below.
199
+ if opts.contentStdin && opts.command == commandFormatDocument {
200
+ content, err := io.ReadAll(os.Stdin)
201
+ if err != nil {
202
+ fmt.Fprintf(os.Stderr, "@ttsc/lint lsp-execute-command: read --content-stdin: %v\n", err)
203
+ return 2
204
+ }
205
+ edit, code := lspFormatBuffer(string(content), opts)
206
+ if code != 0 {
207
+ return code
208
+ }
209
+ return writeJSON(edit)
210
+ }
211
+ edit, code := lspWorkspaceEditForCommand(opts)
212
+ if code != 0 {
213
+ return code
214
+ }
215
+ return writeJSON(edit)
216
+ }
217
+
218
+ func parseLSPCommandOptions(name string, args []string) (*lspCommandOptions, bool) {
219
+ fs := flag.NewFlagSet(name, flag.ContinueOnError)
220
+ fs.SetOutput(os.Stderr)
221
+ cwd := fs.String("cwd", "", "")
222
+ tsconfig := fs.String("tsconfig", "tsconfig.json", "")
223
+ pluginsJSON := fs.String("plugins-json", "", "")
224
+ uri := fs.String("uri", "", "")
225
+ rangeJSON := fs.String("range-json", "", "")
226
+ contextJSON := fs.String("context-json", "", "")
227
+ command := fs.String("command", "", "")
228
+ argumentsJSON := fs.String("arguments-json", "", "")
229
+ contentStdin := fs.Bool("content-stdin", false, "")
230
+ if err := fs.Parse(args); err != nil {
231
+ return nil, false
232
+ }
233
+ resolvedCwd, err := resolveCwd(*cwd)
234
+ if err != nil {
235
+ fmt.Fprintln(os.Stderr, err)
236
+ return nil, false
237
+ }
238
+ return &lspCommandOptions{
239
+ argumentsJSON: *argumentsJSON,
240
+ command: *command,
241
+ contextJSON: *contextJSON,
242
+ contentStdin: *contentStdin,
243
+ cwd: resolvedCwd,
244
+ pluginsJSON: *pluginsJSON,
245
+ rangeJSON: *rangeJSON,
246
+ tsconfig: *tsconfig,
247
+ uri: *uri,
248
+ }, true
249
+ }
250
+
251
+ func lspFindings(opts *lspCommandOptions, includeFormatDefaults bool) ([]*Finding, *program, func(), int) {
252
+ if opts.uri == "" {
253
+ fmt.Fprintln(os.Stderr, "@ttsc/lint: lsp command requires --uri")
254
+ return nil, nil, nil, 2
255
+ }
256
+ target, err := filePathFromURI(opts.uri)
257
+ if err != nil {
258
+ fmt.Fprintln(os.Stderr, err)
259
+ return nil, nil, nil, 2
260
+ }
261
+ rules, err := loadRules(opts.pluginsJSON, opts.cwd, opts.tsconfig)
262
+ if err != nil {
263
+ fmt.Fprintln(os.Stderr, err)
264
+ return nil, nil, nil, 2
265
+ }
266
+ if includeFormatDefaults {
267
+ rules = formatCommandResolver{inner: rules}
268
+ }
269
+ engine := NewEngineWithResolver(rules)
270
+ prog, parseDiags, err := loadProgram(opts.cwd, opts.tsconfig, loadProgramOptions{
271
+ forceNoEmit: true,
272
+ needsRuleChecker: engine.NeedsTypeChecker(),
273
+ })
274
+ if err != nil {
275
+ fmt.Fprintf(os.Stderr, "@ttsc/lint: %v\n", err)
276
+ return nil, nil, nil, 2
277
+ }
278
+ if len(parseDiags) > 0 {
279
+ // tsgo already owns parse diagnostics in the upstream LSP process.
280
+ return nil, prog, prog.close, 0
281
+ }
282
+ findings := filterFindingsForPath(engine.Run(prog.userSourceFiles(), prog.checker), target)
283
+ return findings, prog, prog.close, 0
284
+ }
285
+
286
+ func filterFindingsForPath(findings []*Finding, target string) []*Finding {
287
+ target = canonicalProjectPath("", target)
288
+ out := make([]*Finding, 0, len(findings))
289
+ for _, finding := range findings {
290
+ if finding == nil || finding.File == nil {
291
+ continue
292
+ }
293
+ if canonicalProjectPath("", finding.File.FileName()) == target {
294
+ out = append(out, finding)
295
+ }
296
+ }
297
+ return out
298
+ }
299
+
300
+ func findingToLSPDiagnostic(finding *Finding) lspDiagnostic {
301
+ return lspDiagnostic{
302
+ Range: lspRangeForFinding(finding),
303
+ Severity: lspSeverity(finding.Severity),
304
+ Code: finding.Rule,
305
+ Source: "@ttsc/lint",
306
+ Message: finding.Message,
307
+ }
308
+ }
309
+
310
+ func lspRangeForFinding(finding *Finding) lspRange {
311
+ text := ""
312
+ if finding != nil && finding.File != nil {
313
+ text = finding.File.Text()
314
+ }
315
+ return lspRange{
316
+ Start: byteOffsetToLSPPosition(text, finding.Pos),
317
+ End: byteOffsetToLSPPosition(text, finding.End),
318
+ }
319
+ }
320
+
321
+ func lspSeverity(severity Severity) int {
322
+ if severity == SeverityError {
323
+ return 1
324
+ }
325
+ return 2
326
+ }
327
+
328
+ func hasFixableFinding(findings []*Finding) bool {
329
+ for _, finding := range findings {
330
+ if finding != nil && len(finding.Fix) > 0 {
331
+ return true
332
+ }
333
+ }
334
+ return false
335
+ }
336
+
337
+ func hasFormatFinding(findings []*Finding) bool {
338
+ for _, finding := range findings {
339
+ if finding != nil && finding.IsFormat && len(finding.Fix) > 0 {
340
+ return true
341
+ }
342
+ }
343
+ return false
344
+ }
345
+
346
+ func filterLintFindings(findings []*Finding) []*Finding {
347
+ out := make([]*Finding, 0, len(findings))
348
+ for _, finding := range findings {
349
+ if finding != nil && !finding.IsFormat {
350
+ out = append(out, finding)
351
+ }
352
+ }
353
+ return out
354
+ }
355
+
356
+ func acceptsActionKind(raw string, kind string) bool {
357
+ var ctx lspCodeActionContextWire
358
+ if strings.TrimSpace(raw) == "" || json.Unmarshal([]byte(raw), &ctx) != nil || len(ctx.Only) == 0 {
359
+ return true
360
+ }
361
+ for _, only := range ctx.Only {
362
+ if only == kind || strings.HasPrefix(kind, only+".") {
363
+ return true
364
+ }
365
+ }
366
+ return false
367
+ }
368
+
369
+ func lspWorkspaceEditForCommand(opts *lspCommandOptions) (*lspWorkspaceEdit, int) {
370
+ target, err := filePathFromURI(opts.uri)
371
+ if err != nil {
372
+ fmt.Fprintln(os.Stderr, err)
373
+ return nil, 2
374
+ }
375
+ if _, ok := projectRelativePath(opts.cwd, target); !ok {
376
+ fmt.Fprintf(os.Stderr, "@ttsc/lint: LSP command target %s is outside cwd %s\n", target, opts.cwd)
377
+ return nil, 2
378
+ }
379
+ if projectPathHasSegment(opts.cwd, target, "node_modules") {
380
+ return nil, 0
381
+ }
382
+ original, err := os.ReadFile(target)
383
+ if err != nil {
384
+ fmt.Fprintf(os.Stderr, "@ttsc/lint: read %s: %v\n", target, err)
385
+ return nil, 2
386
+ }
387
+ tempRoot, tempTarget, tempTsconfig, cleanup, err := prepareLSPCommandWorkspace(opts.cwd, opts.tsconfig, target)
388
+ if err != nil {
389
+ fmt.Fprintln(os.Stderr, err)
390
+ return nil, 2
391
+ }
392
+ defer cleanup()
393
+
394
+ pluginsJSON := remapLSPPluginsJSONForTempWorkspace(opts.pluginsJSON, opts.cwd, tempRoot)
395
+ rules, err := loadRules(pluginsJSON, tempRoot, tempTsconfig)
396
+ if err != nil {
397
+ fmt.Fprintln(os.Stderr, err)
398
+ return nil, 2
399
+ }
400
+ if opts.command == commandFormatDocument {
401
+ rules = formatCommandResolver{inner: rules}
402
+ }
403
+ engine := NewEngineWithResolver(rules)
404
+ needsRuleChecker := engine.NeedsTypeChecker()
405
+ maxPasses := maxFixPasses
406
+ if opts.command == commandFormatDocument {
407
+ maxPasses = maxFormatPasses
408
+ }
409
+ converged := false
410
+ for pass := 0; pass < maxPasses; pass++ {
411
+ prog, parseDiags, err := loadProgram(tempRoot, tempTsconfig, loadProgramOptions{
412
+ forceNoEmit: true,
413
+ needsRuleChecker: needsRuleChecker,
414
+ })
415
+ if err != nil {
416
+ fmt.Fprintf(os.Stderr, "@ttsc/lint: %v\n", err)
417
+ return nil, 2
418
+ }
419
+ if len(parseDiags) > 0 {
420
+ prog.close()
421
+ return nil, 0
422
+ }
423
+ findings := filterFindingsForPath(engine.Run(prog.userSourceFiles(), prog.checker), tempTarget)
424
+ prog.close()
425
+ if opts.command == commandFormatDocument {
426
+ findings = filterFormatFindings(findings)
427
+ } else {
428
+ findings = filterLintFindings(findings)
429
+ }
430
+ fixed, err := applyFindingFixes(tempRoot, findings)
431
+ if err != nil {
432
+ fmt.Fprintln(os.Stderr, err)
433
+ return nil, 3
434
+ }
435
+ if fixed == 0 {
436
+ converged = true
437
+ break
438
+ }
439
+ }
440
+ if !converged {
441
+ fmt.Fprintf(os.Stderr,
442
+ "@ttsc/lint: LSP %s cascade did not converge after %d passes\n",
443
+ opts.command, maxPasses)
444
+ return nil, 2
445
+ }
446
+ next, err := os.ReadFile(tempTarget)
447
+ if err != nil {
448
+ fmt.Fprintf(os.Stderr, "@ttsc/lint: read cascaded %s: %v\n", tempTarget, err)
449
+ return nil, 2
450
+ }
451
+ return workspaceEditForFullDocument(opts.uri, string(original), string(next)), 0
452
+ }
453
+
454
+ // lspFormatBuffer formats an in-memory document buffer using only the
455
+ // format-class rules, with no tsgo Program and no temp-workspace copy. It is
456
+ // the lightweight path behind --content-stdin for ttsc.format.document.
457
+ //
458
+ // Format rules are AST+source only (`IsFormat() == true`); none implement
459
+ // typeAwareRule, so the engine runs them with a nil checker. The document
460
+ // content comes entirely from `content` — the file on disk at opts.uri is
461
+ // never read — so an editor can format an unsaved buffer without paying the
462
+ // disk-copy + full-program-load cost of lspWorkspaceEditForCommand.
463
+ //
464
+ // Returns the same WorkspaceEdit shape as the disk path, or (nil, 0) on a
465
+ // no-op (no fixable findings, or text unchanged after convergence).
466
+ func lspFormatBuffer(content string, opts *lspCommandOptions) (*lspWorkspaceEdit, int) {
467
+ target, err := filePathFromURI(opts.uri)
468
+ if err != nil {
469
+ fmt.Fprintln(os.Stderr, err)
470
+ return nil, 2
471
+ }
472
+ // Guard rails mirror lspWorkspaceEditForCommand: skip targets outside the
473
+ // project root or inside node_modules.
474
+ if _, ok := projectRelativePath(opts.cwd, target); !ok {
475
+ fmt.Fprintf(os.Stderr, "@ttsc/lint: LSP command target %s is outside cwd %s\n", target, opts.cwd)
476
+ return nil, 2
477
+ }
478
+ if projectPathHasSegment(opts.cwd, target, "node_modules") {
479
+ return nil, 0
480
+ }
481
+
482
+ rules, err := loadFormatRules(opts.pluginsJSON, opts.cwd, opts.tsconfig)
483
+ if err != nil {
484
+ fmt.Fprintln(os.Stderr, err)
485
+ return nil, 2
486
+ }
487
+ resolver, err := newFormatCommandResolver(rules, filepath.Dir(target), vscodeLanguageID(target))
488
+ if err != nil {
489
+ fmt.Fprintln(os.Stderr, err)
490
+ return nil, 2
491
+ }
492
+ engine := NewEngineWithResolver(resolver)
493
+ if engine.NeedsTypeChecker() {
494
+ // A format-class contributor rule (formatContributorAdapter) needs the type
495
+ // checker, which the single-file in-memory parse can't supply. Fall back to
496
+ // the disk path, which builds a full program with a checker, so the result
497
+ // matches `ttsc format`; the dirty buffer can't be honored for these rules.
498
+ // Built-in format rules are AST-only, so they keep the fast in-memory path.
499
+ return lspWorkspaceEditForCommand(opts)
500
+ }
501
+ scriptKind := scriptKindForPath(target)
502
+
503
+ text := content
504
+ converged := false
505
+ for pass := 0; pass < maxFormatPasses; pass++ {
506
+ file := shimparser.ParseSourceFile(shimast.SourceFileParseOptions{FileName: target}, text, scriptKind)
507
+ if file == nil {
508
+ // Match the disk path: a buffer we can't parse is a benign no-op, not a
509
+ // hard error — don't fight the editor's own diagnostics on a dirty buffer.
510
+ return nil, 0
511
+ }
512
+ findings := filterFormatFindings(engine.Run([]*shimast.SourceFile{file}, nil))
513
+ next, applied := applyFindingFixesToText(text, findings)
514
+ if applied == 0 {
515
+ converged = true
516
+ break
517
+ }
518
+ text = next
519
+ }
520
+ if !converged {
521
+ fmt.Fprintf(os.Stderr,
522
+ "@ttsc/lint: LSP %s cascade did not converge after %d passes\n",
523
+ opts.command, maxFormatPasses)
524
+ return nil, 2
525
+ }
526
+ return workspaceEditForFullDocument(opts.uri, content, text), 0
527
+ }
528
+
529
+ // scriptKindForPath maps a file extension to the tsgo ScriptKind the parser
530
+ // needs so TS/JSX-only syntax is recognized. Mirrors the test helpers'
531
+ // ScriptKind selection (helpers_test.go parseTSFile/parseTSXFile).
532
+ func scriptKindForPath(path string) shimcore.ScriptKind {
533
+ switch strings.ToLower(filepath.Ext(path)) {
534
+ case ".tsx":
535
+ return shimcore.ScriptKindTSX
536
+ case ".jsx":
537
+ return shimcore.ScriptKindJSX
538
+ case ".js", ".cjs", ".mjs":
539
+ return shimcore.ScriptKindJS
540
+ default:
541
+ return shimcore.ScriptKindTS
542
+ }
543
+ }
544
+
545
+ // applyFindingFixesToText is the in-memory counterpart of
546
+ // applyFindingFixes/applyTextEditsToFile (fix.go): it collects every fixable
547
+ // finding's TextEdit, selects a non-overlapping set with the same
548
+ // selectTextEdits logic, applies them right-to-left to `text`, and returns the
549
+ // new string plus the number of edits applied. It never writes to disk and
550
+ // never reloads a Program. Findings carry byte offsets into the same `text`
551
+ // that was just parsed, so no per-file grouping is needed.
552
+ func applyFindingFixesToText(text string, findings []*Finding) (string, int) {
553
+ edits := make([]TextEdit, 0, len(findings))
554
+ for _, finding := range findings {
555
+ if finding == nil || len(finding.Fix) == 0 {
556
+ continue
557
+ }
558
+ edits = append(edits, finding.Fix...)
559
+ }
560
+ selected := selectTextEdits(len(text), edits)
561
+ if len(selected) == 0 {
562
+ return text, 0
563
+ }
564
+ next := text
565
+ for i := len(selected) - 1; i >= 0; i-- {
566
+ edit := selected[i]
567
+ next = next[:edit.Pos] + edit.Text + next[edit.End:]
568
+ }
569
+ if next == text {
570
+ return text, 0
571
+ }
572
+ return next, len(selected)
573
+ }
574
+
575
+ func workspaceEditForFullDocument(uri string, original string, next string) *lspWorkspaceEdit {
576
+ if original == next {
577
+ return nil
578
+ }
579
+ return &lspWorkspaceEdit{Changes: map[string][]lspTextEdit{uri: {{
580
+ Range: lspRange{
581
+ Start: lspPosition{Line: 0, Character: 0},
582
+ End: byteOffsetToLSPPosition(original, len(original)),
583
+ },
584
+ NewText: next,
585
+ }}}}
586
+ }
587
+
588
+ func prepareLSPCommandWorkspace(cwd string, tsconfig string, target string) (string, string, string, func(), error) {
589
+ tempRoot, err := os.MkdirTemp("", "ttsc-lint-lsp-")
590
+ if err != nil {
591
+ return "", "", "", nil, fmt.Errorf("@ttsc/lint: create LSP temp workspace: %w", err)
592
+ }
593
+ cleanup := func() { _ = os.RemoveAll(tempRoot) }
594
+ if _, ok := tempPathFor(cwd, tempRoot, target); !ok {
595
+ cleanup()
596
+ return "", "", "", nil, fmt.Errorf("@ttsc/lint: LSP command target %s is outside cwd %s", target, cwd)
597
+ }
598
+ if err := copyLSPCommandWorkspace(cwd, tempRoot); err != nil {
599
+ cleanup()
600
+ return "", "", "", nil, err
601
+ }
602
+ if err := linkNearestNodeModules(tempRoot, cwd); err != nil {
603
+ cleanup()
604
+ return "", "", "", nil, err
605
+ }
606
+ tempTarget, _ := tempPathFor(cwd, tempRoot, target)
607
+ tempTsconfig := tsconfig
608
+ if filepath.IsAbs(tsconfig) {
609
+ if mapped, ok := tempPathFor(cwd, tempRoot, tsconfig); ok {
610
+ tempTsconfig = mapped
611
+ }
612
+ }
613
+ return tempRoot, tempTarget, tempTsconfig, cleanup, nil
614
+ }
615
+
616
+ func copyLSPCommandWorkspace(src string, dst string) error {
617
+ seenDirs := map[string]struct{}{}
618
+ return filepath.WalkDir(src, func(current string, entry fs.DirEntry, walkErr error) error {
619
+ if walkErr != nil {
620
+ return walkErr
621
+ }
622
+ if current == src {
623
+ return nil
624
+ }
625
+ rel, err := filepath.Rel(src, current)
626
+ if err != nil {
627
+ return err
628
+ }
629
+ if shouldSkipLSPCommandWorkspaceDir(entry.Name()) {
630
+ if entry.IsDir() {
631
+ return filepath.SkipDir
632
+ }
633
+ if entry.Type()&fs.ModeSymlink != 0 {
634
+ return nil
635
+ }
636
+ }
637
+ return copyLSPCommandWorkspaceEntry(current, filepath.Join(dst, rel), seenDirs)
638
+ })
639
+ }
640
+
641
+ func copyLSPCommandWorkspaceEntry(src string, dst string, seenDirs map[string]struct{}) error {
642
+ info, err := os.Stat(src)
643
+ if err != nil {
644
+ return err
645
+ }
646
+ mode := info.Mode()
647
+ if info.IsDir() {
648
+ linkInfo, err := os.Lstat(src)
649
+ if err != nil {
650
+ return err
651
+ }
652
+ isSymlink := linkInfo.Mode()&os.ModeSymlink != 0
653
+ if isSymlink {
654
+ realDir, err := filepath.EvalSymlinks(src)
655
+ if err == nil {
656
+ if _, ok := seenDirs[realDir]; ok {
657
+ return nil
658
+ }
659
+ // Track the real path only for the active recursion branch.
660
+ // Releasing it on return lets sibling aliases pointing at the
661
+ // same real directory (e.g. `src-a -> real-src` and
662
+ // `src-b -> real-src` in different tsconfig entries) each get
663
+ // materialized; the test
664
+ // `TestLSPExecuteCommandMaterializesDuplicateSymlinkedDirectories`
665
+ // pins this contract.
666
+ seenDirs[realDir] = struct{}{}
667
+ defer delete(seenDirs, realDir)
668
+ }
669
+ }
670
+ if err := os.MkdirAll(dst, mode.Perm()); err != nil {
671
+ return err
672
+ }
673
+ if !isSymlink {
674
+ return nil
675
+ }
676
+ entries, err := os.ReadDir(src)
677
+ if err != nil {
678
+ return err
679
+ }
680
+ for _, entry := range entries {
681
+ if shouldSkipLSPCommandWorkspaceDir(entry.Name()) {
682
+ continue
683
+ }
684
+ if err := copyLSPCommandWorkspaceEntry(
685
+ filepath.Join(src, entry.Name()),
686
+ filepath.Join(dst, entry.Name()),
687
+ seenDirs,
688
+ ); err != nil {
689
+ return err
690
+ }
691
+ }
692
+ return nil
693
+ }
694
+ if !mode.IsRegular() {
695
+ return nil
696
+ }
697
+ data, err := os.ReadFile(src)
698
+ if err != nil {
699
+ return err
700
+ }
701
+ if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
702
+ return err
703
+ }
704
+ return os.WriteFile(dst, data, mode.Perm()|0o200)
705
+ }
706
+
707
+ func shouldSkipLSPCommandWorkspaceDir(name string) bool {
708
+ switch name {
709
+ case ".git", ".hg", ".svn", "node_modules":
710
+ return true
711
+ default:
712
+ return false
713
+ }
714
+ }
715
+
716
+ func pathHasSegment(file string, segment string) bool {
717
+ for _, part := range strings.Split(filepath.Clean(file), string(filepath.Separator)) {
718
+ if part == segment {
719
+ return true
720
+ }
721
+ }
722
+ return false
723
+ }
724
+
725
+ func projectPathHasSegment(cwd string, file string, segment string) bool {
726
+ rel, ok := projectRelativePath(cwd, file)
727
+ if !ok {
728
+ return false
729
+ }
730
+ return pathHasSegment(rel, segment)
731
+ }
732
+
733
+ func lspProjectTargetHasSegment(opts *lspCommandOptions, segment string) bool {
734
+ if opts.uri == "" {
735
+ return false
736
+ }
737
+ target, err := filePathFromURI(opts.uri)
738
+ if err != nil {
739
+ return false
740
+ }
741
+ return projectPathHasSegment(opts.cwd, target, segment)
742
+ }
743
+
744
+ func lspProjectTargetOutsideCwd(opts *lspCommandOptions) bool {
745
+ if opts.uri == "" {
746
+ return false
747
+ }
748
+ target, err := filePathFromURI(opts.uri)
749
+ if err != nil {
750
+ return false
751
+ }
752
+ _, ok := projectRelativePath(opts.cwd, target)
753
+ return !ok
754
+ }
755
+
756
+ func remapLSPPluginsJSONForTempWorkspace(raw string, cwd string, tempRoot string) string {
757
+ if strings.TrimSpace(raw) == "" {
758
+ return raw
759
+ }
760
+ var entries []map[string]any
761
+ if err := json.Unmarshal([]byte(raw), &entries); err != nil {
762
+ return raw
763
+ }
764
+ changed := false
765
+ for _, entry := range entries {
766
+ config, ok := entry["config"].(map[string]any)
767
+ if !ok {
768
+ continue
769
+ }
770
+ configFile, ok := config["configFile"].(string)
771
+ if !ok || !filepath.IsAbs(configFile) {
772
+ continue
773
+ }
774
+ mapped, ok := tempPathFor(cwd, tempRoot, configFile)
775
+ if !ok {
776
+ continue
777
+ }
778
+ config["configFile"] = mapped
779
+ changed = true
780
+ }
781
+ if !changed {
782
+ return raw
783
+ }
784
+ next, err := json.Marshal(entries)
785
+ if err != nil {
786
+ return raw
787
+ }
788
+ return string(next)
789
+ }
790
+
791
+ func tempPathFor(cwd string, tempRoot string, file string) (string, bool) {
792
+ rel, err := filepath.Rel(cwd, file)
793
+ if err != nil || rel == "." || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || filepath.IsAbs(rel) {
794
+ return "", false
795
+ }
796
+ return filepath.Join(tempRoot, rel), true
797
+ }
798
+
799
+ func projectRelativePath(cwd string, file string) (string, bool) {
800
+ rel, err := filepath.Rel(cwd, file)
801
+ if err != nil || rel == "." || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || filepath.IsAbs(rel) {
802
+ return "", false
803
+ }
804
+ return rel, true
805
+ }
806
+
807
+ func firstURIArgument(raw string) (string, error) {
808
+ if strings.TrimSpace(raw) == "" {
809
+ return "", errors.New("@ttsc/lint lsp-execute-command: missing URI argument")
810
+ }
811
+ var args []json.RawMessage
812
+ if err := json.Unmarshal([]byte(raw), &args); err != nil {
813
+ return "", fmt.Errorf("@ttsc/lint lsp-execute-command: invalid arguments JSON: %w", err)
814
+ }
815
+ if len(args) == 0 {
816
+ return "", errors.New("@ttsc/lint lsp-execute-command: missing URI argument")
817
+ }
818
+ var uri string
819
+ if err := json.Unmarshal(args[0], &uri); err != nil || strings.TrimSpace(uri) == "" {
820
+ return "", errors.New("@ttsc/lint lsp-execute-command: first argument must be a document URI")
821
+ }
822
+ return uri, nil
823
+ }
824
+
825
+ func filePathFromURI(raw string) (string, error) {
826
+ parsed, err := url.Parse(raw)
827
+ if err != nil {
828
+ return "", fmt.Errorf("@ttsc/lint: invalid file URI %q: %w", raw, err)
829
+ }
830
+ if parsed.Scheme != "file" {
831
+ return "", fmt.Errorf("@ttsc/lint: expected file URI, got %q", raw)
832
+ }
833
+ path := parsed.Path
834
+ if parsed.Host != "" {
835
+ path = "//" + parsed.Host + path
836
+ }
837
+ if path == "" {
838
+ return "", fmt.Errorf("@ttsc/lint: empty file URI path: %q", raw)
839
+ }
840
+ if os.PathSeparator == '\\' && strings.HasPrefix(path, "/") && len(path) >= 3 && path[2] == ':' {
841
+ path = path[1:]
842
+ }
843
+ abs, err := filepath.Abs(path)
844
+ if err != nil {
845
+ return "", err
846
+ }
847
+ return abs, nil
848
+ }
849
+
850
+ func byteOffsetToLSPPosition(text string, offset int) lspPosition {
851
+ if offset < 0 {
852
+ offset = 0
853
+ }
854
+ if offset > len(text) {
855
+ offset = len(text)
856
+ }
857
+ line, character := 0, 0
858
+ for i := 0; i < offset; {
859
+ r, size := utf8.DecodeRuneInString(text[i:])
860
+ if r == utf8.RuneError && size == 0 {
861
+ break
862
+ }
863
+ if i+size > offset {
864
+ break
865
+ }
866
+ switch r {
867
+ case '\r':
868
+ line++
869
+ character = 0
870
+ i += size
871
+ if i < offset && i < len(text) && text[i] == '\n' {
872
+ i++
873
+ }
874
+ continue
875
+ case '\n':
876
+ line++
877
+ character = 0
878
+ default:
879
+ if n := utf16.RuneLen(r); n > 0 {
880
+ character += n
881
+ } else {
882
+ character++
883
+ }
884
+ }
885
+ i += size
886
+ }
887
+ return lspPosition{Line: line, Character: character}
888
+ }
889
+
890
+ func writeJSON(value any) int {
891
+ data, err := json.Marshal(value)
892
+ if err != nil {
893
+ fmt.Fprintln(os.Stderr, err)
894
+ return 2
895
+ }
896
+ fmt.Fprintln(os.Stdout, string(data))
897
+ return 0
898
+ }