@ttsc/lint 0.14.0-dev.20260529.2 → 0.14.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 (380) hide show
  1. package/README.md +23 -25
  2. package/lib/defaultFormat.d.ts +10 -6
  3. package/lib/defaultFormat.js +10 -6
  4. package/lib/defaultFormat.js.map +1 -1
  5. package/lib/structures/ITtscLintConfig.d.ts +5 -5
  6. package/lib/structures/TtscLintRuleSetting.d.ts +19 -19
  7. package/lib/structures/{ITtscLintFormat.d.ts → format/ITtscLintFormat.d.ts} +45 -70
  8. package/lib/structures/format/ITtscLintFormat.js.map +1 -0
  9. package/lib/structures/format/ITtscLintFormatJsDoc.d.ts +15 -0
  10. package/lib/structures/format/ITtscLintFormatJsDoc.js +3 -0
  11. package/lib/structures/format/ITtscLintFormatJsDoc.js.map +1 -0
  12. package/lib/structures/format/ITtscLintFormatSortImports.d.ts +54 -0
  13. package/lib/structures/format/ITtscLintFormatSortImports.js +3 -0
  14. package/lib/structures/format/ITtscLintFormatSortImports.js.map +1 -0
  15. package/lib/structures/format/index.d.ts +3 -0
  16. package/lib/structures/format/index.js +20 -0
  17. package/lib/structures/format/index.js.map +1 -0
  18. package/lib/structures/index.d.ts +1 -1
  19. package/lib/structures/index.js +1 -1
  20. package/lib/structures/index.js.map +1 -1
  21. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +18 -27
  22. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js +4 -5
  23. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js.map +1 -1
  24. package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +29 -33
  25. package/lib/structures/rules/ITtscLintContributorRules.d.ts +6 -7
  26. package/lib/structures/rules/ITtscLintCoreRules.d.ts +380 -408
  27. package/lib/structures/rules/ITtscLintCypressRuleOptions.d.ts +5 -5
  28. package/lib/structures/rules/ITtscLintCypressRuleOptions.js +2 -2
  29. package/lib/structures/rules/ITtscLintCypressRules.d.ts +34 -38
  30. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.d.ts +21 -24
  31. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.js +2 -2
  32. package/lib/structures/rules/ITtscLintFunctionalRules.d.ts +51 -56
  33. package/lib/structures/rules/ITtscLintJestRules.d.ts +63 -74
  34. package/lib/structures/rules/ITtscLintJsdocRules.d.ts +47 -54
  35. package/lib/structures/rules/ITtscLintJsxA11yRules.d.ts +94 -102
  36. package/lib/structures/rules/ITtscLintNextjsRules.d.ts +50 -55
  37. package/lib/structures/rules/ITtscLintPlaywrightRules.d.ts +104 -122
  38. package/lib/structures/rules/ITtscLintPromiseRules.d.ts +62 -71
  39. package/lib/structures/rules/ITtscLintReactPerfRuleOptions.d.ts +4 -4
  40. package/lib/structures/rules/ITtscLintReactPerfRules.d.ts +17 -19
  41. package/lib/structures/rules/ITtscLintReactRuleOptions.d.ts +8 -10
  42. package/lib/structures/rules/ITtscLintReactRuleOptions.js +1 -2
  43. package/lib/structures/rules/ITtscLintReactRuleOptions.js.map +1 -1
  44. package/lib/structures/rules/ITtscLintReactRules.d.ts +91 -104
  45. package/lib/structures/rules/ITtscLintRegexpRules.d.ts +46 -54
  46. package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +7 -8
  47. package/lib/structures/rules/ITtscLintSecurityRules.d.ts +45 -52
  48. package/lib/structures/rules/ITtscLintSolidRules.d.ts +53 -56
  49. package/lib/structures/rules/ITtscLintStorybookRuleOptions.d.ts +3 -4
  50. package/lib/structures/rules/ITtscLintStorybookRules.d.ts +60 -69
  51. package/lib/structures/rules/ITtscLintTanstackQueryRules.d.ts +31 -37
  52. package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.d.ts +2 -3
  53. package/lib/structures/rules/ITtscLintTestingLibraryRules.d.ts +102 -119
  54. package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +460 -518
  55. package/lib/structures/rules/ITtscLintVitestRules.d.ts +43 -51
  56. package/linthost/config.go +89 -72
  57. package/linthost/config_format.go +265 -99
  58. package/linthost/directives.go +5 -1
  59. package/linthost/display_width.go +72 -0
  60. package/linthost/fix.go +26 -1
  61. package/linthost/format.go +222 -25
  62. package/linthost/format_editor_settings.go +262 -0
  63. package/linthost/lsp.go +8 -4
  64. package/linthost/print_dispatch.go +3 -0
  65. package/linthost/print_doc.go +13 -0
  66. package/linthost/print_engine.go +68 -11
  67. package/linthost/print_nodes_array.go +167 -5
  68. package/linthost/print_nodes_call.go +786 -77
  69. package/linthost/print_nodes_function.go +43 -0
  70. package/linthost/print_nodes_imports.go +64 -14
  71. package/linthost/print_nodes_list.go +214 -8
  72. package/linthost/print_nodes_object.go +14 -6
  73. package/linthost/print_nodes_ternary.go +147 -0
  74. package/linthost/rules_boundaries.go +400 -400
  75. package/linthost/rules_camelcase.go +81 -81
  76. package/linthost/rules_complexity.go +52 -51
  77. package/linthost/rules_consistent_return.go +81 -81
  78. package/linthost/rules_core_extra.go +472 -472
  79. package/linthost/rules_curly.go +43 -43
  80. package/linthost/rules_default_case.go +15 -15
  81. package/linthost/rules_default_case_last.go +22 -22
  82. package/linthost/rules_format_arrow_parens.go +213 -0
  83. package/linthost/rules_format_bracket_spacing.go +117 -0
  84. package/linthost/rules_format_clause_join.go +179 -0
  85. package/linthost/rules_format_declaration_header.go +664 -0
  86. package/linthost/rules_format_indent.go +616 -0
  87. package/linthost/rules_format_jsdoc.go +4 -4
  88. package/linthost/rules_format_orphan_semi.go +106 -0
  89. package/linthost/rules_format_parameter_properties.go +175 -0
  90. package/linthost/rules_format_print_width.go +180 -69
  91. package/linthost/rules_format_quote_props.go +169 -0
  92. package/linthost/rules_format_quotes.go +75 -29
  93. package/linthost/rules_format_semi.go +185 -10
  94. package/linthost/rules_format_sort_imports.go +517 -121
  95. package/linthost/rules_format_statement_split.go +368 -0
  96. package/linthost/rules_format_ternary_nullish_parens.go +77 -0
  97. package/linthost/rules_format_trailing_comma.go +21 -2
  98. package/linthost/rules_format_whitespace.go +336 -0
  99. package/linthost/rules_functional.go +688 -688
  100. package/linthost/rules_gap.go +146 -11
  101. package/linthost/rules_grouped_accessor_pairs.go +60 -60
  102. package/linthost/rules_guard_for_in.go +122 -122
  103. package/linthost/rules_id_length.go +44 -44
  104. package/linthost/rules_imports.go +11 -3
  105. package/linthost/rules_init_declarations.go +25 -25
  106. package/linthost/rules_jsx_a11y.go +844 -844
  107. package/linthost/rules_max_classes_per_file.go +24 -24
  108. package/linthost/rules_max_depth.go +43 -43
  109. package/linthost/rules_max_lines.go +53 -53
  110. package/linthost/rules_max_lines_per_function.go +28 -28
  111. package/linthost/rules_max_nested_callbacks.go +26 -26
  112. package/linthost/rules_max_params.go +20 -20
  113. package/linthost/rules_max_statements.go +29 -29
  114. package/linthost/rules_nextjs.go +5 -5
  115. package/linthost/rules_no_else_return.go +38 -38
  116. package/linthost/rules_no_empty_named_blocks.go +45 -45
  117. package/linthost/rules_no_import_assign.go +96 -96
  118. package/linthost/rules_no_invalid_this.go +19 -19
  119. package/linthost/rules_no_loop_func.go +43 -43
  120. package/linthost/rules_no_magic_numbers.go +60 -60
  121. package/linthost/rules_no_mixed_operators.go +44 -44
  122. package/linthost/rules_no_param_reassign.go +64 -64
  123. package/linthost/rules_no_restricted_imports.go +45 -45
  124. package/linthost/rules_no_restricted_syntax.go +17 -17
  125. package/linthost/rules_no_shadow.go +112 -112
  126. package/linthost/rules_no_unreachable.go +36 -36
  127. package/linthost/rules_no_useless_assignment.go +79 -79
  128. package/linthost/rules_no_useless_constructor.go +167 -167
  129. package/linthost/rules_no_useless_return.go +28 -28
  130. package/linthost/rules_prefer_arrow_callback.go +74 -74
  131. package/linthost/rules_prefer_destructuring.go +61 -61
  132. package/linthost/rules_prefer_named_capture_group.go +87 -87
  133. package/linthost/rules_prefer_rest_params.go +61 -61
  134. package/linthost/rules_promise.go +800 -800
  135. package/linthost/rules_react.go +600 -600
  136. package/linthost/rules_react_extras.go +162 -162
  137. package/linthost/rules_react_hooks.go +2 -2
  138. package/linthost/rules_react_perf.go +231 -231
  139. package/linthost/rules_regexp.go +293 -293
  140. package/linthost/rules_security.go +613 -613
  141. package/linthost/rules_sort_imports.go +37 -37
  142. package/linthost/rules_sort_keys.go +88 -88
  143. package/linthost/rules_suggestions.go +13 -0
  144. package/linthost/rules_ts.go +2 -2
  145. package/linthost/rules_ts_async.go +247 -247
  146. package/linthost/rules_ts_class_literal_property_style.go +64 -64
  147. package/linthost/rules_ts_consistent_type_exports.go +209 -209
  148. package/linthost/rules_ts_explicit_function_return_type.go +35 -35
  149. package/linthost/rules_ts_extra.go +6 -2
  150. package/linthost/rules_ts_no_base_to_string.go +140 -140
  151. package/linthost/rules_ts_no_confusing_void.go +28 -28
  152. package/linthost/rules_ts_no_deprecated.go +110 -110
  153. package/linthost/rules_ts_no_for_in_array.go +45 -45
  154. package/linthost/rules_ts_no_magic_numbers.go +67 -67
  155. package/linthost/rules_ts_no_meaningless_void_operator.go +44 -44
  156. package/linthost/rules_ts_no_misused_spread.go +64 -64
  157. package/linthost/rules_ts_no_redundant_type_constituents.go +72 -72
  158. package/linthost/rules_ts_no_restricted_types.go +28 -28
  159. package/linthost/rules_ts_no_unnecessary_boolean_literal_compare.go +87 -87
  160. package/linthost/rules_ts_no_unnecessary_condition.go +184 -184
  161. package/linthost/rules_ts_no_unnecessary_qualifier.go +72 -72
  162. package/linthost/rules_ts_no_unnecessary_template_expression.go +120 -120
  163. package/linthost/rules_ts_no_unnecessary_type_arguments.go +171 -171
  164. package/linthost/rules_ts_no_unnecessary_type_assertion.go +90 -90
  165. package/linthost/rules_ts_no_unsafe_argument.go +50 -50
  166. package/linthost/rules_ts_no_unsafe_assignment.go +60 -60
  167. package/linthost/rules_ts_no_unsafe_call.go +40 -40
  168. package/linthost/rules_ts_no_unsafe_enum_comparison.go +147 -147
  169. package/linthost/rules_ts_no_unsafe_member_access.go +29 -29
  170. package/linthost/rules_ts_no_unsafe_return.go +64 -64
  171. package/linthost/rules_ts_no_unsafe_unary_minus.go +42 -42
  172. package/linthost/rules_ts_no_useless_constructor.go +20 -20
  173. package/linthost/rules_ts_non_nullable_type_assertion_style.go +60 -60
  174. package/linthost/rules_ts_parameter_properties.go +10 -10
  175. package/linthost/rules_ts_prefer_find.go +116 -116
  176. package/linthost/rules_ts_prefer_includes.go +173 -173
  177. package/linthost/rules_ts_prefer_nullish_coalescing.go +54 -54
  178. package/linthost/rules_ts_prefer_optional_chain.go +156 -156
  179. package/linthost/rules_ts_prefer_promise_reject_errors.go +104 -104
  180. package/linthost/rules_ts_prefer_readonly.go +31 -31
  181. package/linthost/rules_ts_prefer_reduce_type_parameter.go +74 -74
  182. package/linthost/rules_ts_prefer_regexp_exec.go +69 -69
  183. package/linthost/rules_ts_prefer_return_this_type.go +79 -79
  184. package/linthost/rules_ts_prefer_string_starts_ends_with.go +237 -237
  185. package/linthost/rules_ts_promise_function_async.go +41 -41
  186. package/linthost/rules_ts_related_getter_setter_pairs.go +61 -61
  187. package/linthost/rules_ts_require_array_sort_compare.go +58 -58
  188. package/linthost/rules_ts_restrict_plus_operands.go +82 -82
  189. package/linthost/rules_ts_restrict_template_expressions.go +50 -50
  190. package/linthost/rules_ts_simple.go +249 -249
  191. package/linthost/rules_ts_sort_type_constituents.go +88 -88
  192. package/linthost/rules_ts_strict_boolean_expressions.go +103 -103
  193. package/linthost/rules_ts_switch_exhaustiveness_check.go +90 -90
  194. package/linthost/rules_ts_unbound_method.go +65 -65
  195. package/linthost/rules_tsdoc.go +88 -88
  196. package/linthost/rules_unicorn_better_regex.go +1 -1
  197. package/linthost/rules_unicorn_catch_error_name.go +19 -19
  198. package/linthost/rules_unicorn_consistent_assert.go +20 -20
  199. package/linthost/rules_unicorn_consistent_date_clone.go +27 -27
  200. package/linthost/rules_unicorn_consistent_destructuring.go +3 -3
  201. package/linthost/rules_unicorn_consistent_empty_array_spread.go +34 -34
  202. package/linthost/rules_unicorn_consistent_existence_index_check.go +42 -42
  203. package/linthost/rules_unicorn_consistent_function_scoping.go +3 -3
  204. package/linthost/rules_unicorn_consistent_template_literal_escape.go +3 -3
  205. package/linthost/rules_unicorn_custom_error_definition.go +92 -92
  206. package/linthost/rules_unicorn_empty_brace_spaces.go +47 -47
  207. package/linthost/rules_unicorn_error_message.go +34 -34
  208. package/linthost/rules_unicorn_escape_case.go +14 -14
  209. package/linthost/rules_unicorn_expiring_todo_comments.go +36 -36
  210. package/linthost/rules_unicorn_explicit_length_check.go +51 -51
  211. package/linthost/rules_unicorn_filename_case.go +1 -1
  212. package/linthost/rules_unicorn_import_style.go +1 -1
  213. package/linthost/rules_unicorn_isolated_functions.go +2 -2
  214. package/linthost/rules_unicorn_new_for_builtins.go +49 -49
  215. package/linthost/rules_unicorn_no_abusive_eslint_disable.go +31 -31
  216. package/linthost/rules_unicorn_no_accessor_recursion.go +69 -69
  217. package/linthost/rules_unicorn_no_anonymous_default_export.go +40 -40
  218. package/linthost/rules_unicorn_no_array_callback_reference.go +33 -33
  219. package/linthost/rules_unicorn_no_array_for_each.go +14 -14
  220. package/linthost/rules_unicorn_no_array_method_this_argument.go +29 -29
  221. package/linthost/rules_unicorn_no_array_reduce.go +15 -15
  222. package/linthost/rules_unicorn_no_array_reverse.go +17 -17
  223. package/linthost/rules_unicorn_no_array_sort.go +17 -17
  224. package/linthost/rules_unicorn_no_await_expression_member.go +12 -12
  225. package/linthost/rules_unicorn_no_await_in_promise_methods.go +38 -38
  226. package/linthost/rules_unicorn_no_console_spaces.go +30 -30
  227. package/linthost/rules_unicorn_no_document_cookie.go +13 -13
  228. package/linthost/rules_unicorn_no_empty_file.go +16 -16
  229. package/linthost/rules_unicorn_no_for_loop.go +58 -58
  230. package/linthost/rules_unicorn_no_hex_escape.go +11 -11
  231. package/linthost/rules_unicorn_no_immediate_mutation.go +55 -55
  232. package/linthost/rules_unicorn_no_instanceof_builtins.go +34 -34
  233. package/linthost/rules_unicorn_no_invalid_fetch_options.go +52 -52
  234. package/linthost/rules_unicorn_no_invalid_remove_event_listener.go +25 -25
  235. package/linthost/rules_unicorn_no_keyword_prefix.go +56 -56
  236. package/linthost/rules_unicorn_no_lonely_if.go +21 -21
  237. package/linthost/rules_unicorn_no_magic_array_flat_depth.go +22 -22
  238. package/linthost/rules_unicorn_no_named_default.go +25 -25
  239. package/linthost/rules_unicorn_no_negated_condition.go +54 -54
  240. package/linthost/rules_unicorn_no_negation_in_equality_check.go +23 -23
  241. package/linthost/rules_unicorn_no_nested_ternary.go +15 -15
  242. package/linthost/rules_unicorn_no_new_array.go +5 -5
  243. package/linthost/rules_unicorn_no_new_buffer.go +5 -5
  244. package/linthost/rules_unicorn_no_null.go +2 -2
  245. package/linthost/rules_unicorn_no_object_as_default_parameter.go +19 -19
  246. package/linthost/rules_unicorn_no_process_exit.go +17 -17
  247. package/linthost/rules_unicorn_no_single_promise_in_promise_methods.go +29 -29
  248. package/linthost/rules_unicorn_no_static_only_class.go +27 -27
  249. package/linthost/rules_unicorn_no_thenable.go +47 -47
  250. package/linthost/rules_unicorn_no_this_assignment.go +10 -10
  251. package/linthost/rules_unicorn_no_typeof_undefined.go +31 -31
  252. package/linthost/rules_unicorn_no_unnecessary_array_flat_depth.go +23 -23
  253. package/linthost/rules_unicorn_no_unnecessary_array_splice_count.go +37 -37
  254. package/linthost/rules_unicorn_no_unnecessary_await.go +24 -24
  255. package/linthost/rules_unicorn_no_unnecessary_slice_end.go +22 -22
  256. package/linthost/rules_unicorn_no_unreadable_array_destructuring.go +67 -67
  257. package/linthost/rules_unicorn_no_unreadable_iife.go +22 -22
  258. package/linthost/rules_unicorn_no_useless_collection_argument.go +34 -34
  259. package/linthost/rules_unicorn_no_useless_error_capture_stack_trace.go +18 -18
  260. package/linthost/rules_unicorn_no_useless_fallback_in_spread.go +49 -49
  261. package/linthost/rules_unicorn_no_useless_iterator_to_array.go +41 -41
  262. package/linthost/rules_unicorn_no_useless_length_check.go +82 -82
  263. package/linthost/rules_unicorn_no_useless_promise_resolve_reject.go +42 -42
  264. package/linthost/rules_unicorn_no_useless_spread.go +38 -38
  265. package/linthost/rules_unicorn_no_useless_switch_case.go +29 -29
  266. package/linthost/rules_unicorn_no_useless_undefined.go +25 -25
  267. package/linthost/rules_unicorn_no_zero_fractions.go +17 -17
  268. package/linthost/rules_unicorn_number_literal_case.go +33 -33
  269. package/linthost/rules_unicorn_numeric_separators_style.go +36 -36
  270. package/linthost/rules_unicorn_prefer_add_event_listener.go +30 -30
  271. package/linthost/rules_unicorn_prefer_array_find.go +27 -27
  272. package/linthost/rules_unicorn_prefer_array_flat.go +25 -25
  273. package/linthost/rules_unicorn_prefer_array_flat_map.go +37 -37
  274. package/linthost/rules_unicorn_prefer_array_index_of.go +108 -108
  275. package/linthost/rules_unicorn_prefer_array_some.go +43 -43
  276. package/linthost/rules_unicorn_prefer_at.go +42 -42
  277. package/linthost/rules_unicorn_prefer_bigint_literals.go +42 -42
  278. package/linthost/rules_unicorn_prefer_blob_reading_methods.go +15 -15
  279. package/linthost/rules_unicorn_prefer_class_fields.go +57 -57
  280. package/linthost/rules_unicorn_prefer_classlist_toggle.go +76 -76
  281. package/linthost/rules_unicorn_prefer_code_point.go +21 -21
  282. package/linthost/rules_unicorn_prefer_date_now.go +47 -47
  283. package/linthost/rules_unicorn_prefer_default_parameters.go +96 -96
  284. package/linthost/rules_unicorn_prefer_dom_node_append.go +14 -14
  285. package/linthost/rules_unicorn_prefer_dom_node_dataset.go +27 -27
  286. package/linthost/rules_unicorn_prefer_dom_node_remove.go +17 -17
  287. package/linthost/rules_unicorn_prefer_dom_node_text_content.go +11 -11
  288. package/linthost/rules_unicorn_prefer_event_target.go +10 -10
  289. package/linthost/rules_unicorn_prefer_export_from.go +99 -99
  290. package/linthost/rules_unicorn_prefer_global_this.go +91 -91
  291. package/linthost/rules_unicorn_prefer_import_meta_properties.go +38 -38
  292. package/linthost/rules_unicorn_prefer_includes.go +108 -108
  293. package/linthost/rules_unicorn_prefer_json_parse_buffer.go +37 -37
  294. package/linthost/rules_unicorn_prefer_keyboard_event_key.go +10 -10
  295. package/linthost/rules_unicorn_prefer_logical_operator_over_ternary.go +20 -20
  296. package/linthost/rules_unicorn_prefer_math_min_max.go +33 -33
  297. package/linthost/rules_unicorn_prefer_math_trunc.go +32 -32
  298. package/linthost/rules_unicorn_prefer_modern_dom_apis.go +18 -18
  299. package/linthost/rules_unicorn_prefer_modern_math_apis.go +38 -38
  300. package/linthost/rules_unicorn_prefer_module.go +22 -22
  301. package/linthost/rules_unicorn_prefer_native_coercion_functions.go +73 -73
  302. package/linthost/rules_unicorn_prefer_negative_index.go +46 -46
  303. package/linthost/rules_unicorn_prefer_node_protocol.go +90 -90
  304. package/linthost/rules_unicorn_prefer_number_properties.go +57 -57
  305. package/linthost/rules_unicorn_prefer_object_from_entries.go +26 -26
  306. package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
  307. package/linthost/rules_unicorn_prefer_prototype_methods.go +25 -25
  308. package/linthost/rules_unicorn_prefer_query_selector.go +25 -25
  309. package/linthost/rules_unicorn_prefer_reflect_apply.go +10 -10
  310. package/linthost/rules_unicorn_prefer_regexp_test_rule.go +47 -47
  311. package/linthost/rules_unicorn_prefer_response_static_json.go +26 -26
  312. package/linthost/rules_unicorn_prefer_set_has.go +16 -16
  313. package/linthost/rules_unicorn_prefer_set_size.go +22 -22
  314. package/linthost/rules_unicorn_prefer_simple_condition_first.go +3 -3
  315. package/linthost/rules_unicorn_prefer_single_call.go +59 -59
  316. package/linthost/rules_unicorn_prefer_spread.go +24 -24
  317. package/linthost/rules_unicorn_prefer_string_raw.go +12 -12
  318. package/linthost/rules_unicorn_prefer_string_replace_all.go +46 -46
  319. package/linthost/rules_unicorn_prefer_string_slice.go +17 -17
  320. package/linthost/rules_unicorn_prefer_string_starts_ends_with.go +104 -104
  321. package/linthost/rules_unicorn_prefer_string_trim_start_end.go +21 -21
  322. package/linthost/rules_unicorn_prefer_structured_clone.go +36 -36
  323. package/linthost/rules_unicorn_prefer_switch.go +65 -65
  324. package/linthost/rules_unicorn_prefer_ternary.go +35 -35
  325. package/linthost/rules_unicorn_prefer_top_level_await.go +34 -34
  326. package/linthost/rules_unicorn_prefer_type_error.go +84 -84
  327. package/linthost/rules_unicorn_prevent_abbreviations.go +68 -68
  328. package/linthost/rules_unicorn_relative_url_style.go +19 -19
  329. package/linthost/rules_unicorn_require_array_join_separator.go +18 -18
  330. package/linthost/rules_unicorn_require_module_attributes.go +25 -25
  331. package/linthost/rules_unicorn_require_module_specifiers.go +28 -28
  332. package/linthost/rules_unicorn_require_number_to_fixed_digits_argument.go +18 -18
  333. package/linthost/rules_unicorn_require_post_message_target_origin.go +18 -18
  334. package/linthost/rules_unicorn_string_content.go +1 -1
  335. package/linthost/rules_unicorn_switch_case_braces.go +16 -16
  336. package/linthost/rules_unicorn_switch_case_break_position.go +3 -3
  337. package/linthost/rules_unicorn_template_indent.go +1 -1
  338. package/linthost/rules_unicorn_text_encoding_identifier_case.go +36 -36
  339. package/linthost/rules_unicorn_throw_new_error.go +30 -30
  340. package/linthost/rules_var.go +273 -3
  341. package/package.json +3 -3
  342. package/src/defaultFormat.ts +11 -7
  343. package/src/structures/ITtscLintConfig.ts +5 -5
  344. package/src/structures/TtscLintRuleSetting.ts +19 -19
  345. package/src/structures/{ITtscLintFormat.ts → format/ITtscLintFormat.ts} +48 -75
  346. package/src/structures/format/ITtscLintFormatJsDoc.ts +16 -0
  347. package/src/structures/format/ITtscLintFormatSortImports.ts +62 -0
  348. package/src/structures/format/index.ts +3 -0
  349. package/src/structures/index.ts +1 -1
  350. package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +20 -31
  351. package/src/structures/rules/ITtscLintBoundariesRules.ts +32 -36
  352. package/src/structures/rules/ITtscLintContributorRules.ts +6 -7
  353. package/src/structures/rules/ITtscLintCoreRules.ts +380 -408
  354. package/src/structures/rules/ITtscLintCypressRuleOptions.ts +5 -5
  355. package/src/structures/rules/ITtscLintCypressRules.ts +34 -38
  356. package/src/structures/rules/ITtscLintFunctionalRuleOptions.ts +28 -41
  357. package/src/structures/rules/ITtscLintFunctionalRules.ts +51 -56
  358. package/src/structures/rules/ITtscLintJestRules.ts +63 -74
  359. package/src/structures/rules/ITtscLintJsdocRules.ts +47 -54
  360. package/src/structures/rules/ITtscLintJsxA11yRules.ts +94 -102
  361. package/src/structures/rules/ITtscLintNextjsRules.ts +50 -55
  362. package/src/structures/rules/ITtscLintPlaywrightRules.ts +104 -122
  363. package/src/structures/rules/ITtscLintPromiseRules.ts +62 -71
  364. package/src/structures/rules/ITtscLintReactPerfRuleOptions.ts +4 -4
  365. package/src/structures/rules/ITtscLintReactPerfRules.ts +17 -19
  366. package/src/structures/rules/ITtscLintReactRuleOptions.ts +8 -10
  367. package/src/structures/rules/ITtscLintReactRules.ts +91 -104
  368. package/src/structures/rules/ITtscLintRegexpRules.ts +46 -54
  369. package/src/structures/rules/ITtscLintRuleOptionsMap.ts +7 -8
  370. package/src/structures/rules/ITtscLintSecurityRules.ts +45 -52
  371. package/src/structures/rules/ITtscLintSolidRules.ts +53 -56
  372. package/src/structures/rules/ITtscLintStorybookRuleOptions.ts +3 -4
  373. package/src/structures/rules/ITtscLintStorybookRules.ts +60 -69
  374. package/src/structures/rules/ITtscLintTanstackQueryRules.ts +31 -37
  375. package/src/structures/rules/ITtscLintTestingLibraryRuleOptions.ts +2 -3
  376. package/src/structures/rules/ITtscLintTestingLibraryRules.ts +102 -119
  377. package/src/structures/rules/ITtscLintTypeScriptRules.ts +460 -518
  378. package/src/structures/rules/ITtscLintVitestRules.ts +43 -51
  379. package/lib/structures/ITtscLintFormat.js.map +0 -1
  380. /package/lib/structures/{ITtscLintFormat.js → format/ITtscLintFormat.js} +0 -0
@@ -5,6 +5,7 @@ import (
5
5
  "fmt"
6
6
  "os"
7
7
  "sort"
8
+ "sync"
8
9
  )
9
10
 
10
11
  // maxFormatPasses bounds the format cascade for the same reason
@@ -36,12 +37,16 @@ func RunFormat(args []string) int {
36
37
  // runFormat is the internal implementation of RunFormat. It drives the
37
38
  // cascade loop and applies format-rule edits until convergence.
38
39
  func runFormat(opts *subcommandOpts) int {
39
- rules, err := loadRules(opts.pluginsJSON, opts.cwd, opts.tsconfig)
40
+ rules, err := loadFormatRules(opts.pluginsJSON, opts.cwd, opts.tsconfig)
41
+ if err != nil {
42
+ fmt.Fprintln(os.Stderr, err)
43
+ return 2
44
+ }
45
+ resolver, err := newFormatCommandResolver(rules, opts.cwd, "")
40
46
  if err != nil {
41
47
  fmt.Fprintln(os.Stderr, err)
42
48
  return 2
43
49
  }
44
- resolver := formatCommandResolver{inner: rules}
45
50
  engine := NewEngineWithResolver(resolver)
46
51
  engine.SetSerial(opts.singleThreaded)
47
52
  needsRuleChecker := engine.NeedsTypeChecker()
@@ -100,6 +105,110 @@ func runFormat(opts *subcommandOpts) int {
100
105
  // without requiring explicit rule declarations in the project config.
101
106
  type formatCommandResolver struct {
102
107
  inner RuleResolver
108
+ // ruleNames memoizes formatOptionRuleNames. The resolver's option set is
109
+ // immutable for the whole run, so the sorted format-rule slice is computed
110
+ // once and reused across ResolveRules (per file per pass), ActiveRuleNames,
111
+ // and EnabledRuleConfig. The field is a pointer so a nil zero value stays
112
+ // valid: callers that build formatCommandResolver{inner: ...} without it
113
+ // (tests, lsp) fall back to direct computation, and copying the struct by
114
+ // value (it is stored in the engine's RuleResolver interface and passed
115
+ // around as a value) shares the same underlying cache rather than copying a
116
+ // sync.Once lock.
117
+ ruleNames *formatRuleNamesCache
118
+ // entryDecisions memoizes the per-fileName ignored/matches decision that
119
+ // ResolveRules derives from fileIsIgnoredByEntry and fileMatchesAnyEntry.
120
+ // Both walk store.entries with glob matching and are recomputed identically
121
+ // for the same fileName on every format pass, so the result is cached once
122
+ // per file and reused. The field is a pointer (a *sync.Map) so the nil zero
123
+ // value stays valid for construction sites that omit it, and copying the
124
+ // resolver by value shares the same underlying map. sync.Map handles the
125
+ // engine's concurrent per-file ResolveRules calls without an extra lock.
126
+ entryDecisions *sync.Map
127
+ // defaultOptions holds the always-on format rules' options (from
128
+ // expandFormatBlock) to apply when the project config declares no `format`
129
+ // block — no block in lint.config.*, or no config file at all. nil when a
130
+ // format block is configured, so that block wins entirely. Keys are canonical
131
+ // format rule names; values are the marshaled options blob each rule decodes,
132
+ // exactly as a configured block would supply them.
133
+ defaultOptions RuleOptionsMap
134
+ }
135
+
136
+ // newFormatCommandResolver wraps inner for the format command / LSP buffer path.
137
+ // When inner declares no `format` rules (no `format` block in lint.config.*, or
138
+ // no config file at all) it loads the documented default format rules, letting
139
+ // the nearest .vscode/settings.json under startDir override the indentation/eol
140
+ // keys. language scopes the settings.json language section ("" skips sections,
141
+ // e.g. the project-wide CLI path). A configured `format` block leaves
142
+ // defaultOptions nil so the block stays authoritative.
143
+ func newFormatCommandResolver(inner RuleResolver, startDir string, language string) (formatCommandResolver, error) {
144
+ r := formatCommandResolver{
145
+ inner: inner,
146
+ ruleNames: &formatRuleNamesCache{},
147
+ entryDecisions: &sync.Map{},
148
+ }
149
+ if !hasInnerFormatRules(inner) {
150
+ opts, err := defaultFormatOptions(editorFormatOverrides(startDir, language))
151
+ if err != nil {
152
+ return formatCommandResolver{}, err
153
+ }
154
+ r.defaultOptions = opts
155
+ }
156
+ return r, nil
157
+ }
158
+
159
+ // hasInnerFormatRules reports whether inner already carries format-rule options,
160
+ // i.e. the project configured a `format` block (format/* options only ever come
161
+ // from a block). When true the block is authoritative and defaults are skipped.
162
+ func hasInnerFormatRules(inner RuleResolver) bool {
163
+ for name := range resolverOptions(inner) {
164
+ if isRegisteredFormatRule(name) {
165
+ return true
166
+ }
167
+ }
168
+ return false
169
+ }
170
+
171
+ // defaultFormatOptions expands the always-on default format ruleset (optionally
172
+ // overridden by editor settings) into the per-rule options map the resolver
173
+ // returns through RuleOptions. It reuses expandFormatBlock so the defaults are
174
+ // produced by exactly the same code path as a user-authored format block.
175
+ func defaultFormatOptions(overrides map[string]any) (RuleOptionsMap, error) {
176
+ expanded, err := expandFormatBlock(overrides)
177
+ if err != nil {
178
+ return nil, err
179
+ }
180
+ out := make(RuleOptionsMap, len(expanded))
181
+ for name, entry := range expanded {
182
+ tuple, ok := entry.([]any)
183
+ if !ok || len(tuple) < 2 {
184
+ continue
185
+ }
186
+ options, ok := tuple[1].(map[string]any)
187
+ if !ok {
188
+ continue
189
+ }
190
+ raw, err := json.Marshal(options)
191
+ if err != nil {
192
+ return nil, err
193
+ }
194
+ out[name] = raw
195
+ }
196
+ return out, nil
197
+ }
198
+
199
+ // formatRuleNamesCache lazily computes and stores the sorted format-rule name
200
+ // slice for a resolver. sync.Once guarantees a single computation even when
201
+ // ResolveRules runs concurrently across files in the engine's parallel walk.
202
+ type formatRuleNamesCache struct {
203
+ once sync.Once
204
+ names []string
205
+ }
206
+
207
+ // formatEntryDecision is the cached per-fileName outcome of the two entry-scope
208
+ // checks ResolveRules runs before applying its format-rule upgrade.
209
+ type formatEntryDecision struct {
210
+ ignoredByEntry bool
211
+ matchesEntry bool
103
212
  }
104
213
 
105
214
  // ResolveRules implements RuleResolver. It delegates to the inner resolver
@@ -127,10 +236,11 @@ func (r formatCommandResolver) ResolveRules(fileName string) ResolvedRuleConfig
127
236
  if resolved.Ignored {
128
237
  return resolved
129
238
  }
130
- if r.fileIsIgnoredByEntry(fileName) {
239
+ decision := r.entryDecision(fileName)
240
+ if decision.ignoredByEntry {
131
241
  return resolved
132
242
  }
133
- if !r.fileMatchesAnyEntry(fileName) {
243
+ if !decision.matchesEntry {
134
244
  return resolved
135
245
  }
136
246
  if resolved.Rules == nil {
@@ -144,6 +254,36 @@ func (r formatCommandResolver) ResolveRules(fileName string) ResolvedRuleConfig
144
254
  return resolved
145
255
  }
146
256
 
257
+ // entryDecision returns the per-fileName ignored/matches outcome that
258
+ // ResolveRules needs, computing it from fileIsIgnoredByEntry and
259
+ // fileMatchesAnyEntry on first use and reusing it on later passes. When the
260
+ // memo is absent (a resolver built without entryDecisions, e.g. in tests or
261
+ // lsp) it computes the decision directly so behavior is identical, just
262
+ // uncached.
263
+ func (r formatCommandResolver) entryDecision(fileName string) formatEntryDecision {
264
+ if r.entryDecisions == nil {
265
+ return r.computeEntryDecision(fileName)
266
+ }
267
+ if cached, ok := r.entryDecisions.Load(fileName); ok {
268
+ return cached.(formatEntryDecision)
269
+ }
270
+ decision := r.computeEntryDecision(fileName)
271
+ // LoadOrStore keeps the first writer's value so concurrent passes over the
272
+ // same file agree; the decision is a pure function of fileName, so either
273
+ // value is correct.
274
+ actual, _ := r.entryDecisions.LoadOrStore(fileName, decision)
275
+ return actual.(formatEntryDecision)
276
+ }
277
+
278
+ // computeEntryDecision runs the two uncached entry-scope checks behind
279
+ // entryDecision.
280
+ func (r formatCommandResolver) computeEntryDecision(fileName string) formatEntryDecision {
281
+ return formatEntryDecision{
282
+ ignoredByEntry: r.fileIsIgnoredByEntry(fileName),
283
+ matchesEntry: r.fileMatchesAnyEntry(fileName),
284
+ }
285
+ }
286
+
147
287
  // fileIsIgnoredByEntry reports whether any non-IgnoreOnly entry in the inner
148
288
  // ConfigStore has an `ignores` glob that matches `fileName`. IgnoreOnly
149
289
  // entries are already handled by ResolvedRuleConfig.Ignored — they are
@@ -154,19 +294,34 @@ func (r formatCommandResolver) ResolveRules(fileName string) ResolvedRuleConfig
154
294
  // independently because its job is to upgrade format rules to `warn`, not to
155
295
  // read the engine's resolved severity map.
156
296
  func (r formatCommandResolver) fileIsIgnoredByEntry(fileName string) bool {
297
+ matched, _ := r.anyNonIgnoreEntry(func(entry *ConfigEntry) bool {
298
+ return entry.matchesIgnores(fileName)
299
+ })
300
+ return matched
301
+ }
302
+
303
+ // anyNonIgnoreEntry resolves the inner resolver to its concrete *ConfigStore
304
+ // once and reports whether any non-IgnoreOnly entry satisfies `pred`. The
305
+ // second return value is false when the inner resolver is not a *ConfigStore
306
+ // (or is a nil one), letting each caller pick its own non-store default. This
307
+ // collapses the shared store-resolution and entry-walk that fileIsIgnoredByEntry
308
+ // and fileMatchesAnyEntry would otherwise duplicate; the per-caller default and
309
+ // the empty-entries base case stay in the callers where they differ.
310
+ func (r formatCommandResolver) anyNonIgnoreEntry(pred func(*ConfigEntry) bool) (matched bool, hasStore bool) {
157
311
  store, ok := r.inner.(*ConfigStore)
158
312
  if !ok || store == nil {
159
- return false
313
+ return false, false
160
314
  }
161
- for _, entry := range store.entries {
315
+ for i := range store.entries {
316
+ entry := &store.entries[i]
162
317
  if entry.IgnoreOnly {
163
318
  continue
164
319
  }
165
- if entry.matchesIgnores(fileName) {
166
- return true
320
+ if pred(entry) {
321
+ return true, true
167
322
  }
168
323
  }
169
- return false
324
+ return false, true
170
325
  }
171
326
 
172
327
  // fileMatchesAnyEntry reports whether `fileName` falls inside the `files`
@@ -193,22 +348,32 @@ func (r formatCommandResolver) fileIsIgnoredByEntry(fileName string) bool {
193
348
  // rule-eligibility reasoned about from the outside, so the format
194
349
  // upgrade applies the same as it does for an entry without `files`.
195
350
  func (r formatCommandResolver) fileMatchesAnyEntry(fileName string) bool {
196
- store, ok := r.inner.(*ConfigStore)
197
- if !ok || store == nil {
351
+ matched, hasStore := r.anyNonIgnoreEntry(func(entry *ConfigEntry) bool {
352
+ return entry.matchesFile(fileName)
353
+ })
354
+ if !hasStore {
355
+ // Not a *ConfigStore: conservative default, see the doc comment above.
198
356
  return true
199
357
  }
200
- if len(store.entries) == 0 {
358
+ // A store with no entries (or only IgnoreOnly ones that the walk skipped)
359
+ // produces matched == false; an empty entries slice must still return true
360
+ // so format mode applies its default upgrade, matching ConfigStore behavior.
361
+ // The all-IgnoreOnly case is distinguished by a non-empty entries slice.
362
+ if !matched && len(r.storeEntries()) == 0 {
201
363
  return true
202
364
  }
203
- for _, entry := range store.entries {
204
- if entry.IgnoreOnly {
205
- continue
206
- }
207
- if entry.matchesFile(fileName) {
208
- return true
209
- }
365
+ return matched
366
+ }
367
+
368
+ // storeEntries returns the inner resolver's config entries, or nil when the
369
+ // inner resolver is not a *ConfigStore. Used by fileMatchesAnyEntry to tell the
370
+ // empty-entries base case (return true) apart from the all-IgnoreOnly case
371
+ // (return false) after anyNonIgnoreEntry reports no match.
372
+ func (r formatCommandResolver) storeEntries() []ConfigEntry {
373
+ if store, ok := r.inner.(*ConfigStore); ok && store != nil {
374
+ return store.entries
210
375
  }
211
- return false
376
+ return nil
212
377
  }
213
378
 
214
379
  // ActiveRuleNames implements RuleResolver. Returns the union of the inner
@@ -239,25 +404,57 @@ func (r formatCommandResolver) EnabledRuleConfig() RuleConfig {
239
404
  return enabled
240
405
  }
241
406
 
242
- // RuleOptions implements RuleResolver by delegating directly to the inner resolver.
407
+ // RuleOptions implements RuleResolver. It prefers the inner resolver's options
408
+ // and falls back to the default format options for rules the project did not
409
+ // configure, so the default always-on rules receive their (possibly
410
+ // settings.json-overridden) options.
243
411
  func (r formatCommandResolver) RuleOptions(name string) json.RawMessage {
244
- return r.inner.RuleOptions(name)
412
+ if raw := r.inner.RuleOptions(name); len(raw) > 0 {
413
+ return raw
414
+ }
415
+ if r.defaultOptions != nil {
416
+ if raw, ok := r.defaultOptions[name]; ok {
417
+ return raw
418
+ }
419
+ }
420
+ return nil
245
421
  }
246
422
 
247
423
  // formatOptionRuleNames returns the sorted list of rule names from the inner
248
424
  // resolver's options that are registered as format rules. These are the rules
249
425
  // that formatCommandResolver promotes from off to warn.
250
426
  func (r formatCommandResolver) formatOptionRuleNames() []string {
251
- options := resolverOptions(r.inner)
252
- if len(options) == 0 {
253
- return nil
427
+ if r.ruleNames == nil {
428
+ // Nil cache (a resolver built without the memo, e.g. in tests or lsp):
429
+ // compute directly. Construction sites that want the memo set ruleNames.
430
+ return r.computeFormatOptionRuleNames()
254
431
  }
432
+ r.ruleNames.once.Do(func() {
433
+ r.ruleNames.names = r.computeFormatOptionRuleNames()
434
+ })
435
+ return r.ruleNames.names
436
+ }
437
+
438
+ // computeFormatOptionRuleNames does the uncached work behind
439
+ // formatOptionRuleNames: collect the inner resolver's option names that are
440
+ // registered format rules, sorted. The result order is stable (sorted) so
441
+ // memoizing it does not change any caller's observed ordering.
442
+ func (r formatCommandResolver) computeFormatOptionRuleNames() []string {
443
+ options := resolverOptions(r.inner)
255
444
  names := make([]string, 0, len(options))
256
445
  for name := range options {
257
446
  if isRegisteredFormatRule(name) {
258
447
  names = append(names, name)
259
448
  }
260
449
  }
450
+ if len(names) == 0 {
451
+ // No `format` block configured: fall back to the default always-on set so
452
+ // the formatter still runs, with documented defaults plus any
453
+ // .vscode/settings.json overrides folded into defaultOptions.
454
+ for name := range r.defaultOptions {
455
+ names = append(names, name)
456
+ }
457
+ }
261
458
  sort.Strings(names)
262
459
  return names
263
460
  }
@@ -0,0 +1,262 @@
1
+ package linthost
2
+
3
+ import (
4
+ "encoding/json"
5
+ "errors"
6
+ "os"
7
+ "path/filepath"
8
+ "strings"
9
+ )
10
+
11
+ // errNoLintConfigFile is the sentinel LoadConfigResolver wraps when no
12
+ // lint.config.*/ttsc-lint.config.* file is found. The format paths
13
+ // (loadFormatRules) treat it as "use defaults" rather than a hard error, so
14
+ // formatOnSave / `ttsc format` work even when a project ships no lint config.
15
+ var errNoLintConfigFile = errors.New("@ttsc/lint: no lint.config.* or ttsc-lint.config.* file found")
16
+
17
+ // loadFormatRules loads the rule resolver for the format paths, tolerating a
18
+ // missing lint config. The lint check/build path requires a config and surfaces
19
+ // the error, but formatting falls back to the documented defaults: a project
20
+ // with no lint.config.* still formats on save. Any other load error (a broken
21
+ // or malformed config) is propagated unchanged.
22
+ func loadFormatRules(pluginsJSON, cwd, tsconfigPath string) (RuleResolver, error) {
23
+ rules, err := loadRules(pluginsJSON, cwd, tsconfigPath)
24
+ if err != nil {
25
+ if errors.Is(err, errNoLintConfigFile) {
26
+ return RuleConfig{}, nil
27
+ }
28
+ return nil, err
29
+ }
30
+ return rules, nil
31
+ }
32
+
33
+ // editorFormatOverrides reads the nearest ancestor `.vscode/settings.json`
34
+ // relative to startDir and maps the editor's formatting settings onto the
35
+ // `format` block keys the default formatter understands. Only keys the user
36
+ // explicitly set are returned, so any setting that is absent falls through to
37
+ // the documented format defaults.
38
+ //
39
+ // This is consulted only when lint.config.* configures no `format` block: a
40
+ // configured format block always wins and never reaches this path. Within
41
+ // settings.json a language-specific section matching `language` (e.g.
42
+ // "[typescript]" or a combined "[javascript][typescript][json]") wins over the
43
+ // top-level keys, mirroring VS Code's own resolution. Pass language="" to skip
44
+ // language sections (e.g. the project-wide `ttsc format` CLI path).
45
+ //
46
+ // Mapping (values use JSON types so they parse identically to a real `format`
47
+ // block, where numbers decode as float64):
48
+ //
49
+ // - editor.tabSize -> tabWidth (number)
50
+ // - editor.insertSpaces -> useTabs (bool, inverted)
51
+ // - files.eol -> endOfLine ("\n" -> "lf", "\r\n" -> "crlf")
52
+ //
53
+ // editor.detectIndentation auto-detection is intentionally not emulated; the
54
+ // explicit tabSize/insertSpaces are honored as written. A relative startDir is
55
+ // ignored (returns no overrides) so unit tests that format from a bare cwd stay
56
+ // hermetic and never walk up into a real workspace's .vscode directory.
57
+ func editorFormatOverrides(startDir string, language string) map[string]any {
58
+ out := map[string]any{}
59
+ if !filepath.IsAbs(startDir) {
60
+ return out
61
+ }
62
+ settings, ok := loadNearestVSCodeSettings(startDir)
63
+ if !ok {
64
+ return out
65
+ }
66
+ // Top-level keys first, then language-specific sections override them.
67
+ applyEditorSettings(out, settings)
68
+ if language != "" {
69
+ for key, value := range settings {
70
+ if !sectionMatchesLanguage(key, language) {
71
+ continue
72
+ }
73
+ if section, ok := value.(map[string]any); ok {
74
+ applyEditorSettings(out, section)
75
+ }
76
+ }
77
+ }
78
+ return out
79
+ }
80
+
81
+ // applyEditorSettings reads the recognized editor keys from a settings object
82
+ // (either the top-level document or a language-specific section) and writes the
83
+ // corresponding format-block overrides into out, overwriting earlier values so
84
+ // callers can layer language sections over the top-level defaults.
85
+ func applyEditorSettings(out map[string]any, settings map[string]any) {
86
+ if v, ok := settings["editor.tabSize"]; ok {
87
+ if f, ok := v.(float64); ok {
88
+ out["tabWidth"] = f
89
+ }
90
+ }
91
+ if v, ok := settings["editor.insertSpaces"]; ok {
92
+ if b, ok := v.(bool); ok {
93
+ out["useTabs"] = !b
94
+ }
95
+ }
96
+ if v, ok := settings["files.eol"]; ok {
97
+ if s, ok := v.(string); ok {
98
+ switch s {
99
+ case "\n":
100
+ out["endOfLine"] = "lf"
101
+ case "\r\n":
102
+ out["endOfLine"] = "crlf"
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ // sectionMatchesLanguage reports whether a settings.json key is a
109
+ // language-specific section header (e.g. "[typescript]" or the combined
110
+ // "[javascript][typescript][json]") that applies to language. An empty
111
+ // language never matches.
112
+ func sectionMatchesLanguage(key string, language string) bool {
113
+ if language == "" {
114
+ return false
115
+ }
116
+ if !strings.HasPrefix(key, "[") || !strings.HasSuffix(key, "]") {
117
+ return false
118
+ }
119
+ inner := strings.TrimSuffix(strings.TrimPrefix(key, "["), "]")
120
+ for _, lang := range strings.Split(inner, "][") {
121
+ if lang == language {
122
+ return true
123
+ }
124
+ }
125
+ return false
126
+ }
127
+
128
+ // vscodeLanguageID maps a file extension to the VS Code language identifier
129
+ // used in settings.json language sections. Extensions outside the formatter's
130
+ // supported set return "" so no language section matches.
131
+ func vscodeLanguageID(filePath string) string {
132
+ switch strings.ToLower(filepath.Ext(filePath)) {
133
+ case ".ts", ".mts", ".cts":
134
+ return "typescript"
135
+ case ".tsx":
136
+ return "typescriptreact"
137
+ case ".js", ".mjs", ".cjs":
138
+ return "javascript"
139
+ case ".jsx":
140
+ return "javascriptreact"
141
+ default:
142
+ return ""
143
+ }
144
+ }
145
+
146
+ // loadNearestVSCodeSettings walks up from startDir looking for a
147
+ // `.vscode/settings.json`, returning the first one found decoded as a JSONC
148
+ // document. It returns ok=false when none exists or the file cannot be parsed,
149
+ // so a broken settings file degrades to the format defaults instead of breaking
150
+ // the formatter.
151
+ func loadNearestVSCodeSettings(startDir string) (map[string]any, bool) {
152
+ dir := startDir
153
+ for {
154
+ candidate := filepath.Join(dir, ".vscode", "settings.json")
155
+ if data, err := os.ReadFile(candidate); err == nil {
156
+ var parsed map[string]any
157
+ if json.Unmarshal(stripJSONC(data), &parsed) == nil {
158
+ return parsed, true
159
+ }
160
+ return nil, false
161
+ }
162
+ parent := filepath.Dir(dir)
163
+ if parent == dir {
164
+ return nil, false
165
+ }
166
+ dir = parent
167
+ }
168
+ }
169
+
170
+ // stripJSONC removes `//` line comments, `/* */` block comments, and trailing
171
+ // commas from a JSONC byte slice so encoding/json can parse VS Code's
172
+ // settings.json. It is string-literal aware: comment markers and commas inside
173
+ // string values are preserved.
174
+ func stripJSONC(data []byte) []byte {
175
+ out := make([]byte, 0, len(data))
176
+ inString := false
177
+ escaped := false
178
+ for i := 0; i < len(data); i++ {
179
+ c := data[i]
180
+ if inString {
181
+ out = append(out, c)
182
+ if escaped {
183
+ escaped = false
184
+ continue
185
+ }
186
+ switch c {
187
+ case '\\':
188
+ escaped = true
189
+ case '"':
190
+ inString = false
191
+ }
192
+ continue
193
+ }
194
+ switch {
195
+ case c == '"':
196
+ inString = true
197
+ out = append(out, c)
198
+ case c == '/' && i+1 < len(data) && data[i+1] == '/':
199
+ for i < len(data) && data[i] != '\n' {
200
+ i++
201
+ }
202
+ if i < len(data) {
203
+ out = append(out, data[i])
204
+ }
205
+ case c == '/' && i+1 < len(data) && data[i+1] == '*':
206
+ i += 2
207
+ for i+1 < len(data) && !(data[i] == '*' && data[i+1] == '/') {
208
+ i++
209
+ }
210
+ i++
211
+ default:
212
+ out = append(out, c)
213
+ }
214
+ }
215
+ return dropTrailingCommas(out)
216
+ }
217
+
218
+ // dropTrailingCommas removes a comma that is followed (after optional
219
+ // whitespace) by a closing `}` or `]`, the one JSONC affordance encoding/json
220
+ // rejects. It is string-literal aware so commas inside strings survive.
221
+ func dropTrailingCommas(data []byte) []byte {
222
+ out := make([]byte, 0, len(data))
223
+ inString := false
224
+ escaped := false
225
+ for i := 0; i < len(data); i++ {
226
+ c := data[i]
227
+ if inString {
228
+ out = append(out, c)
229
+ if escaped {
230
+ escaped = false
231
+ continue
232
+ }
233
+ switch c {
234
+ case '\\':
235
+ escaped = true
236
+ case '"':
237
+ inString = false
238
+ }
239
+ continue
240
+ }
241
+ if c == '"' {
242
+ inString = true
243
+ out = append(out, c)
244
+ continue
245
+ }
246
+ if c == ',' {
247
+ j := i + 1
248
+ for j < len(data) && isJSONSpace(data[j]) {
249
+ j++
250
+ }
251
+ if j < len(data) && (data[j] == '}' || data[j] == ']') {
252
+ continue
253
+ }
254
+ }
255
+ out = append(out, c)
256
+ }
257
+ return out
258
+ }
259
+
260
+ func isJSONSpace(c byte) bool {
261
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r'
262
+ }
package/linthost/lsp.go CHANGED
@@ -302,7 +302,7 @@ func findingToLSPDiagnostic(finding *Finding) lspDiagnostic {
302
302
  Range: lspRangeForFinding(finding),
303
303
  Severity: lspSeverity(finding.Severity),
304
304
  Code: finding.Rule,
305
- Source: "ttsc/lint",
305
+ Source: "@ttsc/lint",
306
306
  Message: finding.Message,
307
307
  }
308
308
  }
@@ -479,13 +479,17 @@ func lspFormatBuffer(content string, opts *lspCommandOptions) (*lspWorkspaceEdit
479
479
  return nil, 0
480
480
  }
481
481
 
482
- rules, err := loadRules(opts.pluginsJSON, opts.cwd, opts.tsconfig)
482
+ rules, err := loadFormatRules(opts.pluginsJSON, opts.cwd, opts.tsconfig)
483
483
  if err != nil {
484
484
  fmt.Fprintln(os.Stderr, err)
485
485
  return nil, 2
486
486
  }
487
- rules = formatCommandResolver{inner: rules}
488
- engine := NewEngineWithResolver(rules)
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)
489
493
  if engine.NeedsTypeChecker() {
490
494
  // A format-class contributor rule (formatContributorAdapter) needs the type
491
495
  // checker, which the single-file in-memory parse can't supply. Fall back to
@@ -118,6 +118,9 @@ func dispatchNode(ctx *PrintContext, node *shimast.Node) (Doc, bool, bool) {
118
118
  case shimast.KindReturnStatement:
119
119
  doc, covered := printReturnStatement(ctx, node)
120
120
  return doc, covered, true
121
+ case shimast.KindConditionalExpression:
122
+ doc, covered := printConditionalExpression(ctx, node)
123
+ return doc, covered, true
121
124
  }
122
125
  return Doc{}, false, false
123
126
  }
@@ -79,6 +79,7 @@ const (
79
79
  docConcat
80
80
  docLineSuffix
81
81
  docConditionalGroup
82
+ docFill
82
83
  )
83
84
 
84
85
  // Doc is one node in the layout tree. Only the fields relevant to the
@@ -135,6 +136,15 @@ func ConditionalGroup(options ...Doc) Doc {
135
136
  return Doc{Kind: docConditionalGroup, Children: options}
136
137
  }
137
138
 
139
+ // Fill renders an alternating [content, separator, content, separator, …,
140
+ // content] sequence with Wadler/Prettier "fill" semantics: it places as many
141
+ // contents on a line as fit, breaking a separator to a new line only when the
142
+ // next content (with its separator) would overflow. Used for concisely-printed
143
+ // numeric arrays (`[1, 2, 3, … ]` packed several per line), where a plain
144
+ // one-item-per-line break would waste space. `parts` must have odd length
145
+ // (content at even indices, separators at odd).
146
+ func Fill(parts ...Doc) Doc { return Doc{Kind: docFill, Children: parts} }
147
+
138
148
  // Indent adds `width` columns of indentation to every newline emitted by
139
149
  // the child doc. Nesting composes.
140
150
  func Indent(width int, parts ...Doc) Doc {
@@ -162,6 +172,9 @@ func Concat(parts ...Doc) Doc {
162
172
 
163
173
  // LineSuffix queues output until the next line break. Used for trailing
164
174
  // line comments that must appear after the current source line ends.
175
+ //
176
+ // The payload is expected to be single-line: it is emitted verbatim at the
177
+ // line break and is NOT re-indented across any embedded newlines.
165
178
  func LineSuffix(parts ...Doc) Doc {
166
179
  return Doc{Kind: docLineSuffix, Children: parts}
167
180
  }