@ttsc/lint 0.14.0-dev.20260529.1 → 0.14.0

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 +85 -87
  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 +108 -5
  68. package/linthost/print_nodes_call.go +515 -74
  69. package/linthost/print_nodes_function.go +25 -0
  70. package/linthost/print_nodes_imports.go +47 -14
  71. package/linthost/print_nodes_list.go +191 -8
  72. package/linthost/print_nodes_object.go +12 -6
  73. package/linthost/print_nodes_ternary.go +106 -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 +167 -0
  83. package/linthost/rules_format_bracket_spacing.go +117 -0
  84. package/linthost/rules_format_clause_join.go +172 -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 +185 -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 +456 -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
@@ -3,13 +3,14 @@ package linthost
3
3
  import (
4
4
  "encoding/json"
5
5
  "fmt"
6
+ "strings"
6
7
  )
7
8
 
8
9
  // expandFormatBlock translates a Prettier-style `format` block into the
9
10
  // per-rule severity + options shape the existing rule parsers consume.
10
11
  // The result is a `map[string]any` that mirrors what a user would
11
- // have written under `rules` directly entries like
12
- // `"format/semi": ["off", {"prefer": "always"}]` so the caller
12
+ // have written under `rules` directly, entries like
13
+ // `"format/semi": ["off", {"prefer": "always"}]`, so the caller
13
14
  // can route it through either `ParseRulesWithOptions` or
14
15
  // `parseExternalRuleMapInto` without duplicating option-decoding logic.
15
16
  //
@@ -20,16 +21,27 @@ import (
20
21
  //
21
22
  // The block's enablement matrix:
22
23
  //
23
- // - `format/semi` always on. `semi: false` flips to `prefer: "never"`.
24
- // - `format/quotes` always on. `singleQuote: true` flips to `prefer: "single"`.
25
- // - `format/trailing-comma` always on with the requested mode.
26
- // - `format/print-width` always on, driven by printWidth/tabWidth/useTabs/endOfLine.
27
- // - `format/sort-imports` — opt-in by setting `importOrder`.
28
- // - `format/jsdoc` opt-in by setting `jsdoc` truthy.
24
+ // - `format/semi`, always on. `semi: false` flips to `prefer: "never"`.
25
+ // - `format/quotes`, always on. `singleQuote: true` flips to `prefer: "single"`.
26
+ // - `format/arrow-parens`, always on. `arrowParens: "avoid"` strips a single bare-identifier arrow parameter's parens; default "always" adds them.
27
+ // - `format/bracket-spacing`, always on. `bracketSpacing: false` removes the inner space of single-line object/destructure/import/export/type braces; default true keeps it.
28
+ // - `format/quote-props`, always on. `quoteProps: "as-needed"` (default) unquotes identifier object keys; "consistent" keeps all keys quoted when any needs it; "preserve" leaves quoting alone.
29
+ // - `format/trailing-comma`, always on with the requested mode.
30
+ // - `format/print-width`, always on, driven by printWidth/tabWidth/useTabs/endOfLine.
31
+ // - `format/clause-join`, always on, joins a single-statement clause body that fits printWidth.
32
+ // - `format/declaration-header`, always on, reflows a class/interface header's type params and heritage clauses.
33
+ // - `format/ternary-nullish-parens`, always on, parenthesizes a `??` operand of a conditional expression.
34
+ // - `format/orphan-semi`, always on; under semi:false, merges a leading-semicolon ASI guard onto its statement.
35
+ // - `format/parameter-properties`, always on, breaks a constructor's parameter list when it has parameter properties.
36
+ // - `format/statement-split`, always on, driven by tabWidth/useTabs/endOfLine.
37
+ // - `format/indent`, always on, driven by tabWidth/useTabs/endOfLine.
38
+ // - `format/whitespace`, always on, driven by endOfLine.
39
+ // - `format/sort-imports`, opt-in by setting `sortImports`.
40
+ // - `format/jsdoc`, always-on; `jsDoc: false` opts out, an object customizes.
29
41
  //
30
42
  // The returned map is the raw form rules parsers expect. Callers MUST
31
43
  // merge any user-supplied `rules` map on top of this one (rules-wins
32
- // semantics) before invoking the existing parsers `mergeRuleMaps`
44
+ // semantics) before invoking the existing parsers, `mergeRuleMaps`
33
45
  // below performs the merge with the right precedence.
34
46
  func expandFormatBlock(raw map[string]any) (map[string]any, error) {
35
47
  if raw == nil {
@@ -60,6 +72,10 @@ func expandFormatBlock(raw map[string]any) (map[string]any, error) {
60
72
  }
61
73
  out["format/semi"] = ruleEntry(map[string]any{"prefer": semiPrefer})
62
74
 
75
+ // formatOrphanSemi, always on; mirrors the effective semi setting so
76
+ // it only merges the leading-semicolon ASI guard under semi:false.
77
+ out["format/orphan-semi"] = ruleEntry(map[string]any{"semi": semiPrefer == "always"})
78
+
63
79
  // formatQuotes
64
80
  quotesPrefer := "double"
65
81
  if v, ok := raw["singleQuote"]; ok {
@@ -73,6 +89,56 @@ func expandFormatBlock(raw map[string]any) (map[string]any, error) {
73
89
  }
74
90
  out["format/quotes"] = ruleEntry(map[string]any{"prefer": quotesPrefer})
75
91
 
92
+ // formatArrowParens, always on. Mirrors Prettier's arrowParens; the
93
+ // default "always" parenthesizes a single bare-identifier arrow parameter,
94
+ // "avoid" strips the parens.
95
+ arrowPrefer := "always"
96
+ if v, ok := raw["arrowParens"]; ok {
97
+ s, err := asString("format.arrowParens", v)
98
+ if err != nil {
99
+ return nil, err
100
+ }
101
+ switch s {
102
+ case "always", "avoid":
103
+ arrowPrefer = s
104
+ default:
105
+ return nil, fmt.Errorf("@ttsc/lint: format.arrowParens must be \"always\" or \"avoid\"; got %q", s)
106
+ }
107
+ }
108
+ out["format/arrow-parens"] = ruleEntry(map[string]any{"prefer": arrowPrefer})
109
+
110
+ // formatBracketSpacing, always on. Mirrors Prettier's bracketSpacing;
111
+ // the default true pads single-line object/destructure/import/export/type
112
+ // braces with one inner space, false removes it.
113
+ bracketSpacing := true
114
+ if v, ok := raw["bracketSpacing"]; ok {
115
+ b, err := asBool("format.bracketSpacing", v)
116
+ if err != nil {
117
+ return nil, err
118
+ }
119
+ bracketSpacing = b
120
+ }
121
+ out["format/bracket-spacing"] = ruleEntry(map[string]any{"spacing": bracketSpacing})
122
+
123
+ // formatQuoteProps, always on. Mirrors Prettier's quoteProps; the default
124
+ // "as-needed" unquotes object keys that are valid identifiers, "consistent"
125
+ // keeps every key quoted when any one needs it, "preserve" leaves quoting
126
+ // untouched.
127
+ quoteProps := "as-needed"
128
+ if v, ok := raw["quoteProps"]; ok {
129
+ s, err := asString("format.quoteProps", v)
130
+ if err != nil {
131
+ return nil, err
132
+ }
133
+ switch s {
134
+ case "as-needed", "consistent", "preserve":
135
+ quoteProps = s
136
+ default:
137
+ return nil, fmt.Errorf("@ttsc/lint: format.quoteProps must be \"as-needed\", \"consistent\", or \"preserve\"; got %q", s)
138
+ }
139
+ }
140
+ out["format/quote-props"] = ruleEntry(map[string]any{"mode": quoteProps})
141
+
76
142
  // formatTrailingComma
77
143
  tcMode := "all"
78
144
  if v, ok := raw["trailingComma"]; ok {
@@ -95,9 +161,14 @@ func expandFormatBlock(raw map[string]any) (map[string]any, error) {
95
161
  // the printer's broken-list reflow emits the same trailing-comma
96
162
  // shape `format/trailing-comma` does. Without the mirror the two
97
163
  // rules disagree on `es5` / `none` projects and oscillate on every
98
- // cascade pass the trailing-comma rule says "no comma" while the
164
+ // cascade pass, the trailing-comma rule says "no comma" while the
99
165
  // printer adds one back. See `printArgList` in print_nodes_call.go.
100
- pwOpts := map[string]any{"trailingComma": tcMode}
166
+ layoutOpts, err := collectLayoutOpts(raw)
167
+ if err != nil {
168
+ return nil, err
169
+ }
170
+ pwOpts := cloneStringAnyMap(layoutOpts)
171
+ pwOpts["trailingComma"] = tcMode
101
172
  if v, ok := raw["printWidth"]; ok {
102
173
  n, err := asInt("format.printWidth", v)
103
174
  if err != nil {
@@ -108,116 +179,153 @@ func expandFormatBlock(raw map[string]any) (map[string]any, error) {
108
179
  }
109
180
  pwOpts["printWidth"] = n
110
181
  }
111
- if v, ok := raw["tabWidth"]; ok {
112
- n, err := asInt("format.tabWidth", v)
113
- if err != nil {
114
- return nil, err
115
- }
116
- if n < 1 {
117
- return nil, fmt.Errorf("@ttsc/lint: format.tabWidth must be a positive integer; got %d", n)
118
- }
119
- pwOpts["tabWidth"] = n
120
- }
121
- if v, ok := raw["useTabs"]; ok {
122
- b, err := asBool("format.useTabs", v)
123
- if err != nil {
124
- return nil, err
125
- }
126
- pwOpts["useTabs"] = b
127
- }
128
- if v, ok := raw["endOfLine"]; ok {
129
- s, err := asString("format.endOfLine", v)
130
- if err != nil {
131
- return nil, err
132
- }
133
- if s != "lf" && s != "crlf" {
134
- return nil, fmt.Errorf("@ttsc/lint: format.endOfLine must be \"lf\" or \"crlf\"; got %q", s)
135
- }
136
- pwOpts["endOfLine"] = s
137
- }
138
182
  out["format/print-width"] = ruleEntry(pwOpts)
139
183
 
140
- // formatSortImports opt-in by `importOrder`.
141
- if v, ok := raw["importOrder"]; ok {
142
- siOpts := map[string]any{}
143
- order, err := asStringSlice("format.importOrder", v)
184
+ // formatClauseJoin, always on. Reuses the printWidth/tabWidth/useTabs
185
+ // budget so it only joins a single-statement clause body back onto its
186
+ // header when the joined line fits. A distinct map instance so it does
187
+ // not alias the print-width blob.
188
+ out["format/clause-join"] = ruleEntry(cloneStringAnyMap(pwOpts))
189
+
190
+ // formatDeclarationHeader, always on. Reflows a class/interface header
191
+ // (type parameters + heritage clauses) to Prettier's break shapes;
192
+ // needs the same printWidth/tabWidth/useTabs budget.
193
+ out["format/declaration-header"] = ruleEntry(cloneStringAnyMap(pwOpts))
194
+
195
+ // formatTernaryNullishParens, always on, no options: wraps a `??`
196
+ // operand of a conditional expression in parentheses (Prettier 3).
197
+ out["format/ternary-nullish-parens"] = ruleEntry(map[string]any{})
198
+
199
+ // formatStatementSplit + formatIndent, always on, Prettier-style.
200
+ // Both reuse the indentation/EOL settings to synthesize line breaks
201
+ // and indent strings. Only the keys the user actually set are mirrored
202
+ // in (same conditional shape as print-width's pwOpts), so defaults are
203
+ // applied rule-side. The two rules share the same option surface
204
+ // collected once into layoutOpts above.
205
+ //
206
+ // Distinct map instances so the two rule entries don't alias one blob.
207
+ out["format/statement-split"] = ruleEntry(cloneStringAnyMap(layoutOpts))
208
+ out["format/indent"] = ruleEntry(cloneStringAnyMap(layoutOpts))
209
+
210
+ // formatParameterProperties, always on. Force-breaks a constructor's
211
+ // parameter list when it declares parameter properties; needs only the
212
+ // indentation settings.
213
+ out["format/parameter-properties"] = ruleEntry(cloneStringAnyMap(layoutOpts))
214
+
215
+ // formatWhitespace, always on. Needs only endOfLine for the final
216
+ // newline; indentation is irrelevant to text hygiene.
217
+ wsOpts := map[string]any{}
218
+ if v, ok := layoutOpts["endOfLine"]; ok {
219
+ wsOpts["endOfLine"] = v
220
+ }
221
+ out["format/whitespace"] = ruleEntry(wsOpts)
222
+
223
+ // formatSortImports, opt-in by `sortImports` (a boolean or options object).
224
+ if v, ok := raw["sortImports"]; ok && v != nil {
225
+ siOpts, enabled, err := expandSortImportsBlock(v)
144
226
  if err != nil {
145
227
  return nil, err
146
228
  }
147
- if len(order) == 0 {
148
- return nil, fmt.Errorf("@ttsc/lint: format.importOrder must contain at least one entry; omit the field to keep formatSortImports off")
149
- }
150
- siOpts["importOrder"] = order
151
- if x, ok := raw["importOrderSeparation"]; ok {
152
- b, err := asBool("format.importOrderSeparation", x)
153
- if err != nil {
154
- return nil, err
155
- }
156
- siOpts["importOrderSeparation"] = b
157
- }
158
- if x, ok := raw["importOrderSortSpecifiers"]; ok {
159
- b, err := asBool("format.importOrderSortSpecifiers", x)
160
- if err != nil {
161
- return nil, err
162
- }
163
- siOpts["importOrderSortSpecifiers"] = b
164
- }
165
- if x, ok := raw["importOrderCaseInsensitive"]; ok {
166
- b, err := asBool("format.importOrderCaseInsensitive", x)
167
- if err != nil {
168
- return nil, err
169
- }
170
- siOpts["importOrderCaseInsensitive"] = b
229
+ if enabled {
230
+ out["format/sort-imports"] = ruleEntry(siOpts)
171
231
  }
172
- out["format/sort-imports"] = ruleEntry(siOpts)
173
232
  }
174
233
 
175
- // formatJsdoc — opt-in by `jsdoc` truthy (boolean or object).
176
- if v, ok := raw["jsdoc"]; ok && v != nil {
177
- jdOpts := map[string]any{}
178
- enabled := false
234
+ // formatJsdoc, always-on. `jsDoc: false` opts out; a
235
+ // `{ tagSynonyms, sortTags }` object customizes it. The config key is
236
+ // camelCased (jsDoc) to match the other multi-word keys; the emitted rule id
237
+ // stays `format/jsdoc`.
238
+ //
239
+ // Today the rule only rewrites tag synonyms (@return → @returns, ...); tag
240
+ // sorting, column alignment, and wrapping are on the roadmap. It is on by
241
+ // default so JSDoc tag names normalize without opt-in, matching the rest of
242
+ // the always-on format set.
243
+ jdOpts := map[string]any{}
244
+ jdEnabled := true
245
+ if v, ok := raw["jsDoc"]; ok && v != nil {
179
246
  switch j := v.(type) {
180
247
  case bool:
181
- enabled = j
248
+ jdEnabled = j
182
249
  case map[string]any:
183
- enabled = true
184
250
  for key, val := range j {
185
251
  switch key {
186
252
  case "tagSynonyms":
187
253
  ts, ok := val.(map[string]any)
188
254
  if !ok {
189
- return nil, fmt.Errorf("@ttsc/lint: format.jsdoc.tagSynonyms must be an object, got %T", val)
255
+ return nil, fmt.Errorf("@ttsc/lint: format.jsDoc.tagSynonyms must be an object, got %T", val)
190
256
  }
191
257
  // Element values must be strings; surface
192
258
  // typos early instead of after a downstream
193
259
  // JSON-decode failure.
194
260
  for k, v := range ts {
195
261
  if _, ok := v.(string); !ok {
196
- return nil, fmt.Errorf("@ttsc/lint: format.jsdoc.tagSynonyms[%q] must be a string, got %T", k, v)
262
+ return nil, fmt.Errorf("@ttsc/lint: format.jsDoc.tagSynonyms[%q] must be a string, got %T", k, v)
197
263
  }
198
264
  }
199
265
  jdOpts["tagSynonyms"] = ts
200
266
  case "sortTags":
201
- b, err := asBool("format.jsdoc.sortTags", val)
267
+ b, err := asBool("format.jsDoc.sortTags", val)
202
268
  if err != nil {
203
269
  return nil, err
204
270
  }
205
271
  jdOpts["sortTags"] = b
206
272
  default:
207
- return nil, fmt.Errorf("@ttsc/lint: format.jsdoc unknown key %q (allowed: tagSynonyms, sortTags)", key)
273
+ return nil, fmt.Errorf("@ttsc/lint: format.jsDoc unknown key %q (allowed: tagSynonyms, sortTags)", key)
208
274
  }
209
275
  }
210
276
  default:
211
- return nil, fmt.Errorf("@ttsc/lint: format.jsdoc must be a boolean or object, got %T", v)
212
- }
213
- if enabled {
214
- out["format/jsdoc"] = ruleEntry(jdOpts)
277
+ return nil, fmt.Errorf("@ttsc/lint: format.jsDoc must be a boolean or object, got %T", v)
215
278
  }
216
279
  }
280
+ if jdEnabled {
281
+ out["format/jsdoc"] = ruleEntry(jdOpts)
282
+ }
217
283
 
218
284
  return out, nil
219
285
  }
220
286
 
287
+ // expandSortImportsBlock translates the `format.sortImports` value (a boolean
288
+ // or an options object) into the rule's option map. The bool reports whether
289
+ // the rule is enabled: `true` and any object enable it, `false` keeps it off.
290
+ func expandSortImportsBlock(v any) (map[string]any, bool, error) {
291
+ switch sv := v.(type) {
292
+ case bool:
293
+ return map[string]any{}, sv, nil
294
+ case map[string]any:
295
+ opts := map[string]any{}
296
+ for key, val := range sv {
297
+ switch key {
298
+ case "order":
299
+ order, err := asStringSlice("format.sortImports.order", val)
300
+ if err != nil {
301
+ return nil, false, err
302
+ }
303
+ if len(order) == 0 {
304
+ return nil, false, fmt.Errorf("@ttsc/lint: format.sortImports.order must contain at least one entry; omit it to use the default order")
305
+ }
306
+ opts["order"] = order
307
+ case "caseSensitive":
308
+ b, err := asBool("format.sortImports.caseSensitive", val)
309
+ if err != nil {
310
+ return nil, false, err
311
+ }
312
+ opts["caseSensitive"] = b
313
+ case "combineTypeAndValue":
314
+ b, err := asBool("format.sortImports.combineTypeAndValue", val)
315
+ if err != nil {
316
+ return nil, false, err
317
+ }
318
+ opts["combineTypeAndValue"] = b
319
+ default:
320
+ return nil, false, fmt.Errorf("@ttsc/lint: format.sortImports unknown key %q (allowed: order, caseSensitive, combineTypeAndValue)", key)
321
+ }
322
+ }
323
+ return opts, true, nil
324
+ default:
325
+ return nil, false, fmt.Errorf("@ttsc/lint: format.sortImports must be a boolean or object, got %T", v)
326
+ }
327
+ }
328
+
221
329
  // formatBlockSeverity extracts the optional `severity` field from a format
222
330
  // block. Defaults to SeverityOff so that format rules never produce check/build
223
331
  // diagnostics unless the user explicitly opts in. SeverityOff still allows
@@ -234,12 +342,20 @@ func formatBlockSeverity(raw map[string]any) (Severity, error) {
234
342
  return severity, nil
235
343
  }
236
344
 
345
+ // isFormatRuleName reports whether `name` is a formatter rule id. Such
346
+ // rules are configured exclusively through the `format` block; a `format/*`
347
+ // key in the user's `rules` map is dropped before the merge, so the config
348
+ // layer never carries a formatter setting in two places.
349
+ func isFormatRuleName(name string) bool {
350
+ return strings.HasPrefix(name, "format/")
351
+ }
352
+
237
353
  // mergeRuleMaps overlays `overrides` on `base` and returns the merged
238
354
  // map. Identical keys in `overrides` replace the entire entry from
239
- // `base`; option objects are NOT deep-merged, which matches the
240
- // conflict-resolution policy spec: a `rules` entry that names a
241
- // `format/*` rule fully replaces the corresponding entry expanded
242
- // from the `format` block.
355
+ // `base`; option objects are NOT deep-merged. `overrides` (the user's
356
+ // `rules` map) never contains a `format/*` key, those are dropped before
357
+ // the merge, so the merge only ever layers lint-rule severities on top of
358
+ // the format block's expanded entries.
243
359
  func mergeRuleMaps(base, overrides map[string]any) map[string]any {
244
360
  out := make(map[string]any, len(base)+len(overrides))
245
361
  for k, v := range base {
@@ -251,6 +367,56 @@ func mergeRuleMaps(base, overrides map[string]any) map[string]any {
251
367
  return out
252
368
  }
253
369
 
370
+ // cloneStringAnyMap returns a shallow copy of `m`. expandFormatBlock uses
371
+ // it so `format/statement-split` and `format/indent` each receive their
372
+ // own options blob rather than aliasing one shared map, a later mutation
373
+ // or marshal of one entry then cannot leak into the other.
374
+ func cloneStringAnyMap(m map[string]any) map[string]any {
375
+ out := make(map[string]any, len(m))
376
+ for k, v := range m {
377
+ out[k] = v
378
+ }
379
+ return out
380
+ }
381
+
382
+ // collectLayoutOpts parses the shared tabWidth/useTabs/endOfLine layout
383
+ // fields from a format block once. Only keys the user actually set are
384
+ // emitted, so rule-side defaults still apply for absent fields. Both the
385
+ // print-width blob (which adds trailingComma + printWidth) and the
386
+ // statement-split/indent/parameter-properties entries derive from this
387
+ // single source so their parse-and-validate logic stays in one place.
388
+ func collectLayoutOpts(raw map[string]any) (map[string]any, error) {
389
+ layoutOpts := map[string]any{}
390
+ if v, ok := raw["tabWidth"]; ok {
391
+ n, err := asInt("format.tabWidth", v)
392
+ if err != nil {
393
+ return nil, err
394
+ }
395
+ if n < 1 {
396
+ return nil, fmt.Errorf("@ttsc/lint: format.tabWidth must be a positive integer; got %d", n)
397
+ }
398
+ layoutOpts["tabWidth"] = n
399
+ }
400
+ if v, ok := raw["useTabs"]; ok {
401
+ b, err := asBool("format.useTabs", v)
402
+ if err != nil {
403
+ return nil, err
404
+ }
405
+ layoutOpts["useTabs"] = b
406
+ }
407
+ if v, ok := raw["endOfLine"]; ok {
408
+ s, err := asString("format.endOfLine", v)
409
+ if err != nil {
410
+ return nil, err
411
+ }
412
+ if s != "lf" && s != "crlf" {
413
+ return nil, fmt.Errorf("@ttsc/lint: format.endOfLine must be \"lf\" or \"crlf\"; got %q", s)
414
+ }
415
+ layoutOpts["endOfLine"] = s
416
+ }
417
+ return layoutOpts, nil
418
+ }
419
+
254
420
  // asBool coerces a raw config value to a bool, returning a typed error on
255
421
  // failure. Used by expandFormatBlock to validate boolean format fields.
256
422
  func asBool(field string, v any) (bool, error) {
@@ -295,7 +461,7 @@ func asInt(field string, v any) (int, error) {
295
461
  }
296
462
 
297
463
  // asStringSlice coerces a raw config value to a []string, returning a typed
298
- // error on failure. Used by expandFormatBlock to validate importOrder.
464
+ // error on failure. Used by expandFormatBlock to validate sortImports.order.
299
465
  func asStringSlice(field string, v any) ([]string, error) {
300
466
  arr, ok := v.([]any)
301
467
  if !ok {
@@ -317,19 +483,19 @@ func asStringSlice(field string, v any) ([]string, error) {
317
483
  // key set mirrors `ITtscLintFormat` exactly.
318
484
  func rejectUnknownFormatKeys(raw map[string]any) error {
319
485
  allowed := map[string]struct{}{
320
- "severity": {},
321
- "semi": {},
322
- "singleQuote": {},
323
- "trailingComma": {},
324
- "printWidth": {},
325
- "tabWidth": {},
326
- "useTabs": {},
327
- "endOfLine": {},
328
- "importOrder": {},
329
- "importOrderSeparation": {},
330
- "importOrderSortSpecifiers": {},
331
- "importOrderCaseInsensitive": {},
332
- "jsdoc": {},
486
+ "severity": {},
487
+ "semi": {},
488
+ "singleQuote": {},
489
+ "arrowParens": {},
490
+ "bracketSpacing": {},
491
+ "quoteProps": {},
492
+ "trailingComma": {},
493
+ "printWidth": {},
494
+ "tabWidth": {},
495
+ "useTabs": {},
496
+ "endOfLine": {},
497
+ "sortImports": {},
498
+ "jsDoc": {},
333
499
  }
334
500
  for key := range raw {
335
501
  if _, ok := allowed[key]; !ok {
@@ -194,7 +194,11 @@ scan:
194
194
  }
195
195
  switch directive.kind {
196
196
  case lintDirectiveDisableLine:
197
- directives.lines[startLine] = append(directives.lines[startLine], directive.rules)
197
+ // Key on endLine, not startLine, so a multi-line block-comment
198
+ // `disable-line` reasons about the comment's end the same way
199
+ // disable-next-line does (endLine+1). A single-line comment has
200
+ // startLine == endLine, so same-line suppression is unchanged.
201
+ directives.lines[endLine] = append(directives.lines[endLine], directive.rules)
198
202
  case lintDirectiveDisableNextLine:
199
203
  directives.lines[endLine+1] = append(directives.lines[endLine+1], directive.rules)
200
204
  case lintDirectiveDisable:
@@ -0,0 +1,72 @@
1
+ package linthost
2
+
3
+ import "unicode"
4
+
5
+ // displayWidth returns the number of terminal columns a string occupies,
6
+ // matching Prettier's getStringWidth: an ASCII string's width equals its byte
7
+ // length, a zero-width combining mark counts as zero, and a wide East Asian
8
+ // (or emoji) code point counts as two. Width decisions across the formatter,
9
+ // the layout engine's fit checks and the print-width rule's source measurement,
10
+ // must use display columns rather than byte length, or a multi-byte token (a
11
+ // subscript digit, a Hangul or CJK identifier) is over-counted and the line is
12
+ // wrongly broken.
13
+ //
14
+ // A tab counts as one column here; callers that need tab-stop expansion handle
15
+ // it separately (the layout engine emits indentation outside of Text nodes, so
16
+ // an embedded tab is rare and was already charged as one byte before this).
17
+ func displayWidth(s string) int {
18
+ w := 0
19
+ for _, r := range s {
20
+ w += runeWidth(r)
21
+ }
22
+ return w
23
+ }
24
+
25
+ // displayWidthAfterLastNewline returns the display width of the substring that
26
+ // follows the final newline in s (the whole string when it has none). The
27
+ // layout engine uses it to reset the running column after emitting a verbatim
28
+ // slice that spans lines.
29
+ func displayWidthAfterLastNewline(s string) int {
30
+ for i := len(s) - 1; i >= 0; i-- {
31
+ if s[i] == '\n' {
32
+ return displayWidth(s[i+1:])
33
+ }
34
+ }
35
+ return displayWidth(s)
36
+ }
37
+
38
+ // runeWidth returns the column width of a single rune: zero for a combining
39
+ // mark or zero-width space, two for a wide East Asian or emoji code point, one
40
+ // otherwise (tabs included, see displayWidth).
41
+ func runeWidth(r rune) int {
42
+ if r == 0x200B || unicode.Is(unicode.Mn, r) || unicode.Is(unicode.Me, r) {
43
+ return 0
44
+ }
45
+ if isWideRune(r) {
46
+ return 2
47
+ }
48
+ return 1
49
+ }
50
+
51
+ // isWideRune reports whether a rune renders in two terminal columns, covering
52
+ // the East Asian Wide / Fullwidth ranges and the common emoji blocks the way
53
+ // Prettier's width table does.
54
+ func isWideRune(r rune) bool {
55
+ switch {
56
+ case r >= 0x1100 && r <= 0x115F, // Hangul Jamo
57
+ r >= 0x2E80 && r <= 0x303E, // CJK Radicals .. CJK Symbols (part)
58
+ r >= 0x3041 && r <= 0x33FF, // Hiragana .. CJK Compatibility
59
+ r >= 0x3400 && r <= 0x4DBF, // CJK Extension A
60
+ r >= 0x4E00 && r <= 0x9FFF, // CJK Unified Ideographs
61
+ r >= 0xA000 && r <= 0xA4CF, // Yi
62
+ r >= 0xAC00 && r <= 0xD7A3, // Hangul Syllables
63
+ r >= 0xF900 && r <= 0xFAFF, // CJK Compatibility Ideographs
64
+ r >= 0xFE30 && r <= 0xFE4F, // CJK Compatibility Forms
65
+ r >= 0xFF00 && r <= 0xFF60, // Fullwidth Forms
66
+ r >= 0xFFE0 && r <= 0xFFE6, // Fullwidth signs
67
+ r >= 0x1F300 && r <= 0x1FAFF, // emoji & pictographs
68
+ r >= 0x20000 && r <= 0x3FFFD: // CJK Extension B and beyond
69
+ return true
70
+ }
71
+ return false
72
+ }
package/linthost/fix.go CHANGED
@@ -43,7 +43,14 @@ func runFix(opts *subcommandOpts) int {
43
43
  fmt.Fprintln(os.Stderr, err)
44
44
  return 2
45
45
  }
46
- engine := NewEngineWithResolver(rules)
46
+ // `ttsc fix` is the run-everything entry point: lint fixes AND formatter
47
+ // edits. Formatting is configured solely through the `format` block (a
48
+ // format/* key in `rules` is dropped), so wrap the resolver the same way
49
+ // `ttsc format` and the LSP paths do — formatCommandResolver force-
50
+ // activates the format rules the format block configured. A project with
51
+ // no format block populates no format options, so nothing extra is enabled
52
+ // and fix stays a pure lint pass.
53
+ engine := NewEngineWithResolver(formatCommandResolver{inner: rules})
47
54
  engine.SetSerial(opts.singleThreaded)
48
55
  needsRuleChecker := engine.NeedsTypeChecker()
49
56
 
@@ -258,12 +265,30 @@ func selectTextEdits(sourceLen int, edits []TextEdit) []TextEdit {
258
265
 
259
266
  selected := make([]TextEdit, 0, len(sorted))
260
267
  lastEnd := -1
268
+ // lastInsertAt marks the offset of the previously-selected edit when that
269
+ // edit was a zero-width insert (Pos==End), else -1. A new zero-width
270
+ // insert at that same offset must be dropped: two coincident inserts both
271
+ // pass the `edit.Pos < lastEnd` gate, then apply in reverse sort order and
272
+ // concatenate at one point — silently corrupting the source (e.g. a `;`
273
+ // insert and a `\n` insert at EOF yielding `\n;`). The host contract keeps
274
+ // one winner and drops the rest (see rule.TextEdit). A zero-width insert
275
+ // sitting at the end of a prior NON-empty edit is left alone: it applies
276
+ // cleanly after the replacement and is a legitimate adjacency.
277
+ lastInsertAt := -1
261
278
  for _, edit := range sorted {
262
279
  if edit.Pos < lastEnd {
263
280
  continue
264
281
  }
282
+ if edit.Pos == edit.End && edit.Pos == lastInsertAt {
283
+ continue
284
+ }
265
285
  selected = append(selected, edit)
266
286
  lastEnd = edit.End
287
+ if edit.Pos == edit.End {
288
+ lastInsertAt = edit.Pos
289
+ } else {
290
+ lastInsertAt = -1
291
+ }
267
292
  }
268
293
  return selected
269
294
  }