@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
@@ -9,50 +9,58 @@ import (
9
9
  shimscanner "github.com/microsoft/typescript-go/shim/scanner"
10
10
  )
11
11
 
12
- // thirdPartyModulesPlaceholder is the bucket that absorbs any import
13
- // whose specifier doesn't match an explicit `importOrder` regex. Mirrors
14
- // `@trivago/prettier-plugin-sort-imports`.
15
- const thirdPartyModulesPlaceholder = "<THIRD_PARTY_MODULES>"
12
+ // Group-order placeholders accepted inside the `order` option. A single
13
+ // declarative array expresses group order, blank-line separation, and special
14
+ // groups all by position.
15
+ const (
16
+ // builtinModulesPlaceholder buckets Node built-in modules (`fs`,
17
+ // `node:path`, `fs/promises`, ...).
18
+ builtinModulesPlaceholder = "<BUILTIN_MODULES>"
19
+ // thirdPartyModulesPlaceholder absorbs any specifier that matched no
20
+ // earlier explicit group.
21
+ thirdPartyModulesPlaceholder = "<THIRD_PARTY_MODULES>"
22
+ // typesPlaceholderPrefix marks the group of `import type` declarations.
23
+ // A regex may follow (`<TYPES>^[.]`) to scope the group to matching
24
+ // specifiers.
25
+ typesPlaceholderPrefix = "<TYPES>"
26
+ // separatorPlaceholder ("") emits one blank line at its position.
27
+ separatorPlaceholder = ""
28
+ )
16
29
 
17
- // formatSortImportsOptions mirrors `TtscLintRuleOptions.SortImports`.
30
+ // formatSortImportsOptions mirrors `ITtscLintFormatSortImports`.
18
31
  type formatSortImportsOptions struct {
19
- ImportOrder []string `json:"importOrder"`
20
- ImportOrderSeparation *bool `json:"importOrderSeparation"`
21
- ImportOrderSortSpecifiers *bool `json:"importOrderSortSpecifiers"`
22
- ImportOrderCaseInsensitive bool `json:"importOrderCaseInsensitive"`
32
+ Order []string `json:"order"`
33
+ CaseSensitive bool `json:"caseSensitive"`
34
+ CombineTypeAndValue bool `json:"combineTypeAndValue"`
23
35
  }
24
36
 
25
- // defaultImportOrder mirrors the two-group MVP scheme used when the user
26
- // supplies no explicit `importOrder`. External modules first
27
- // (`<THIRD_PARTY_MODULES>`), relative imports second (`^\.`). The order
28
- // matches the implicit behavior the rule shipped with before options
29
- // landed, so omitting `importOrder` produces the same output.
30
- var defaultImportOrder = []string{thirdPartyModulesPlaceholder, `^\.`}
37
+ // defaultImportOrder is used when the user supplies no `order`: Node built-ins,
38
+ // then the third-party catch-all, then relative imports. No `""` entries, so
39
+ // the default emits no blank lines between groups (blank lines are opt-in by
40
+ // position).
41
+ var defaultImportOrder = []string{
42
+ builtinModulesPlaceholder,
43
+ thirdPartyModulesPlaceholder,
44
+ `^[.]`,
45
+ }
31
46
 
32
47
  // formatSortImports orders the file's top-level import declarations into
33
- // canonical groups and alphabetizes each group. Compatible-by-spirit with
34
- // `@trivago/prettier-plugin-sort-imports`. Groups are user-configurable via
35
- // the `importOrder` option; when omitted, the rule falls back to a
36
- // two-group scheme:
37
- //
38
- // 1. External modules (specifier does not start with `.`).
39
- // 2. Relative modules (specifier starts with `.`).
48
+ // canonical groups, alphabetizes each group, merges duplicate imports of the
49
+ // same module, and (when `combineTypeAndValue` is on) folds a type-only
50
+ // import into a value import of the same module. Groups are user-configurable
51
+ // via the `order` option; when omitted, the rule falls back to {@link
52
+ // defaultImportOrder}.
40
53
  //
41
- // Groups are separated by exactly one blank line (or omitted when
42
- // `importOrderSeparation: false`). Within each group, declarations are
43
- // sorted by their module-specifier text (ASCII order, or case-insensitive
44
- // when `importOrderCaseInsensitive: true`). Named specifiers inside each
45
- // declaration are alphabetized when `importOrderSortSpecifiers` is true
46
- // (the default).
54
+ // Within each group, declarations are sorted by their module-specifier text
55
+ // (ASCII order, or case-insensitive unless `caseSensitive: true`). Named
56
+ // specifiers inside each declaration are always sorted.
47
57
  //
48
58
  // Safety policy: if any byte between the contiguous imports is not
49
59
  // whitespace, the rule bails. Comments anchored to specific imports would
50
60
  // otherwise move with the wrong declaration, which is a strictly worse
51
- // outcome than declining to sort.
52
- //
53
- // Side-effect imports (`import "foo"`), namespace imports
54
- // (`import * as ns from`), default imports, and `import type` all
55
- // participate in the same group based on their module specifier.
61
+ // outcome than declining to sort. Side-effect imports (`import "foo"`) also
62
+ // bail the whole block because their evaluation order can carry meaning the
63
+ // rule cannot reason about.
56
64
  type formatSortImports struct{}
57
65
 
58
66
  func (formatSortImports) Name() string { return "format/sort-imports" }
@@ -80,7 +88,7 @@ func (formatSortImports) Check(ctx *Context, node *shimast.Node) {
80
88
  // its module's top-level code for its observable effect; sorting it
81
89
  // across a sibling import that depends on the polyfill being
82
90
  // initialized first would silently change runtime behavior. The
83
- // rule conservatively refuses to sort the entire block in that
91
+ // rule conservatively refuses to reorder the entire block in that
84
92
  // case.
85
93
  if len(imports) >= 2 &&
86
94
  leadingTriviaIsAllWhitespace(src, imports) &&
@@ -101,11 +109,8 @@ func (formatSortImports) Check(ctx *Context, node *shimast.Node) {
101
109
  return
102
110
  }
103
111
  }
104
- if !opts.sortSpecifiers {
105
- return
106
- }
107
- for _, decl := range collectLeadingImports(statements.Nodes) {
108
- reportNamedSpecifierSort(ctx, decl, opts.caseInsensitive)
112
+ for _, decl := range imports {
113
+ reportNamedSpecifierSort(ctx, decl, opts.caseSensitive)
109
114
  }
110
115
  }
111
116
 
@@ -113,63 +118,93 @@ func (formatSortImports) Check(ctx *Context, node *shimast.Node) {
113
118
  // during one Check call. All option defaults are applied here so the
114
119
  // rest of the rule code does not branch on nil-ness.
115
120
  type resolvedSortImportsOptions struct {
116
- groups []sortImportsGroup
117
- separation bool
118
- sortSpecifiers bool
119
- caseInsensitive bool
121
+ groups []sortImportsGroup
122
+ caseSensitive bool
123
+ combineTypeAndValue bool
120
124
  }
121
125
 
126
+ // sortImportsGroup is one resolved entry of the `order` array. A separator
127
+ // entry ("") is not a real group; it is folded into the following group's
128
+ // sepBefore flag during parsing and never appears in the resolved list.
122
129
  type sortImportsGroup struct {
123
130
  raw string
124
- pattern *regexp.Regexp
131
+ pattern *regexp.Regexp // nil for placeholder groups without a regex
125
132
  thirdParty bool
133
+ builtin bool
134
+ typesOnly bool // <TYPES> group: matches `import type` declarations
135
+ sepBefore bool // a "" preceded this group in the `order` array
126
136
  }
127
137
 
128
138
  func loadSortImportsOptions(ctx *Context) resolvedSortImportsOptions {
129
139
  var raw formatSortImportsOptions
130
140
  _ = ctx.DecodeOptions(&raw)
131
- order := raw.ImportOrder
141
+ order := raw.Order
132
142
  if len(order) == 0 {
133
143
  order = defaultImportOrder
134
144
  }
145
+ groups := parseImportOrder(order)
146
+ return resolvedSortImportsOptions{
147
+ groups: groups,
148
+ caseSensitive: raw.CaseSensitive,
149
+ combineTypeAndValue: raw.CombineTypeAndValue,
150
+ }
151
+ }
152
+
153
+ // parseImportOrder turns the raw `order` array into resolved groups. "" entries
154
+ // fold into the next group's sepBefore flag. A third-party catch-all is
155
+ // injected at the front when the user omitted one so unmatched specifiers
156
+ // always land in a real group rather than after every explicit one.
157
+ func parseImportOrder(order []string) []sortImportsGroup {
135
158
  groups := make([]sortImportsGroup, 0, len(order))
159
+ pendingSep := false
136
160
  for _, pat := range order {
137
- if pat == thirdPartyModulesPlaceholder {
138
- groups = append(groups, sortImportsGroup{raw: pat, thirdParty: true})
161
+ if pat == separatorPlaceholder {
162
+ pendingSep = true
139
163
  continue
140
164
  }
141
- compiled, err := regexp.Compile(pat)
142
- if err != nil {
165
+ group, ok := compileImportOrderGroup(pat)
166
+ if !ok {
143
167
  // Bad regex: skip the group rather than failing the entire run.
144
- // The diagnostic surface for misconfigured rules is intentionally
145
- // limited; users see "no match" behavior instead of a crash.
168
+ // Users see "no match" behavior instead of a crash; a stray
169
+ // separator in front of the skipped group carries to the next.
146
170
  continue
147
171
  }
148
- groups = append(groups, sortImportsGroup{raw: pat, pattern: compiled})
172
+ group.sepBefore = pendingSep
173
+ pendingSep = false
174
+ groups = append(groups, group)
149
175
  }
150
- if len(groups) == 0 {
151
- // Empty `importOrder` collapses to the third-party catchall so the
152
- // rule still produces a deterministic ordering.
153
- groups = []sortImportsGroup{{raw: thirdPartyModulesPlaceholder, thirdParty: true}}
154
- }
155
- // Ensure a third-party catchall exists; if the user omitted it,
156
- // append one so unmatched specifiers don't land in a phantom group.
157
176
  if !hasThirdPartyGroup(groups) {
158
- groups = append(groups, sortImportsGroup{raw: thirdPartyModulesPlaceholder, thirdParty: true})
159
- }
160
- separation := true
161
- if raw.ImportOrderSeparation != nil {
162
- separation = *raw.ImportOrderSeparation
177
+ groups = append([]sortImportsGroup{{raw: thirdPartyModulesPlaceholder, thirdParty: true}}, groups...)
163
178
  }
164
- sortSpecifiers := true
165
- if raw.ImportOrderSortSpecifiers != nil {
166
- sortSpecifiers = *raw.ImportOrderSortSpecifiers
167
- }
168
- return resolvedSortImportsOptions{
169
- groups: groups,
170
- separation: separation,
171
- sortSpecifiers: sortSpecifiers,
172
- caseInsensitive: raw.ImportOrderCaseInsensitive,
179
+ return groups
180
+ }
181
+
182
+ // compileImportOrderGroup resolves one non-separator `order` entry into a
183
+ // group. The bool is false only when a regex entry fails to compile.
184
+ func compileImportOrderGroup(pat string) (sortImportsGroup, bool) {
185
+ switch {
186
+ case pat == builtinModulesPlaceholder:
187
+ return sortImportsGroup{raw: pat, builtin: true}, true
188
+ case pat == thirdPartyModulesPlaceholder:
189
+ return sortImportsGroup{raw: pat, thirdParty: true}, true
190
+ case strings.HasPrefix(pat, typesPlaceholderPrefix):
191
+ rest := strings.TrimPrefix(pat, typesPlaceholderPrefix)
192
+ group := sortImportsGroup{raw: pat, typesOnly: true}
193
+ if rest == "" {
194
+ return group, true
195
+ }
196
+ compiled, err := regexp.Compile(rest)
197
+ if err != nil {
198
+ return sortImportsGroup{}, false
199
+ }
200
+ group.pattern = compiled
201
+ return group, true
202
+ default:
203
+ compiled, err := regexp.Compile(pat)
204
+ if err != nil {
205
+ return sortImportsGroup{}, false
206
+ }
207
+ return sortImportsGroup{raw: pat, pattern: compiled}, true
173
208
  }
174
209
  }
175
210
 
@@ -184,32 +219,64 @@ func hasThirdPartyGroup(groups []sortImportsGroup) bool {
184
219
  return false
185
220
  }
186
221
 
187
- // matchGroup returns the index of the first group that claims `specifier`.
188
- // Non-third-party groups are checked in order; the third-party catchall
189
- // absorbs anything that did not match an earlier explicit pattern.
190
- func matchGroup(groups []sortImportsGroup, specifier string) int {
222
+ // matchGroup returns the index of the first group that claims a declaration
223
+ // with the given specifier and type-only flag. Groups are checked in order, so
224
+ // a `<TYPES>` group placed ahead of a regex group claims type-only imports
225
+ // first; the third-party catchall absorbs anything left over.
226
+ func matchGroup(groups []sortImportsGroup, specifier string, typeOnly bool) int {
191
227
  thirdPartyIdx := -1
192
228
  for i, g := range groups {
193
- if g.thirdParty {
229
+ switch {
230
+ case g.thirdParty:
194
231
  if thirdPartyIdx < 0 {
195
232
  thirdPartyIdx = i
196
233
  }
197
- continue
198
- }
199
- if g.pattern != nil && g.pattern.MatchString(specifier) {
234
+ case g.builtin:
235
+ if isBuiltinModule(specifier) {
236
+ return i
237
+ }
238
+ case g.typesOnly:
239
+ if typeOnly && (g.pattern == nil || g.pattern.MatchString(specifier)) {
240
+ return i
241
+ }
242
+ case g.pattern != nil && g.pattern.MatchString(specifier):
200
243
  return i
201
244
  }
202
245
  }
203
- if thirdPartyIdx >= 0 {
204
- return thirdPartyIdx
205
- }
206
- return len(groups) // sentinel "no match"
246
+ return thirdPartyIdx
247
+ }
248
+
249
+ // nodeBuiltinModules is the set of Node.js built-in module names recognized by
250
+ // the `<BUILTIN_MODULES>` group. A `node:` prefix and a `pkg/subpath` suffix
251
+ // are both stripped before the lookup.
252
+ var nodeBuiltinModules = map[string]struct{}{
253
+ "assert": {}, "async_hooks": {}, "buffer": {}, "child_process": {},
254
+ "cluster": {}, "console": {}, "constants": {}, "crypto": {},
255
+ "dgram": {}, "diagnostics_channel": {}, "dns": {}, "domain": {},
256
+ "events": {}, "fs": {}, "http": {}, "http2": {}, "https": {},
257
+ "inspector": {}, "module": {}, "net": {}, "os": {}, "path": {},
258
+ "perf_hooks": {}, "process": {}, "punycode": {}, "querystring": {},
259
+ "readline": {}, "repl": {}, "stream": {}, "string_decoder": {},
260
+ "sys": {}, "test": {}, "timers": {}, "tls": {}, "trace_events": {},
261
+ "tty": {}, "url": {}, "util": {}, "v8": {}, "vm": {}, "wasi": {},
262
+ "worker_threads": {}, "zlib": {},
263
+ }
264
+
265
+ // isBuiltinModule reports whether the specifier names a Node.js built-in
266
+ // module. `node:fs`, `fs`, and `fs/promises` all qualify.
267
+ func isBuiltinModule(specifier string) bool {
268
+ name := strings.TrimPrefix(specifier, "node:")
269
+ if slash := strings.IndexByte(name, '/'); slash >= 0 {
270
+ name = name[:slash]
271
+ }
272
+ _, ok := nodeBuiltinModules[name]
273
+ return ok
207
274
  }
208
275
 
209
276
  // specifierListHasCommentTrivia returns true when the source between
210
277
  // adjacent specifiers contains any byte that is not whitespace,
211
278
  // comma, or whitespace-equivalent. A `/* x */` or `// x` inside the
212
- // list anchors the rule into a no-op sorting would re-emit the
279
+ // list anchors the rule into a no-op, sorting would re-emit the
213
280
  // specifier texts joined with `", "` and silently drop the comment.
214
281
  func specifierListHasCommentTrivia(src string, specifiers []*shimast.Node) bool {
215
282
  for i := 1; i < len(specifiers); i++ {
@@ -284,29 +351,62 @@ func leadingTriviaIsAllWhitespace(src string, imports []*shimast.Node) bool {
284
351
  return true
285
352
  }
286
353
 
287
- // buildSortedImportBlock returns the canonical group/sort representation of
288
- // the contiguous import declarations.
354
+ // siSpec is one named import specifier captured for merge/sort: its local
355
+ // name drives the sort key, its source text is re-emitted verbatim, and the
356
+ // type-only flag of the owning declaration drives `combineTypeAndValue`
357
+ // promotion.
358
+ type siSpec struct {
359
+ sortKey string
360
+ text string
361
+ fromTypeOnlyDecl bool
362
+ // inlineType is the specifier's own AST `type` modifier (`import { type Foo }`),
363
+ // distinct from fromTypeOnlyDecl (the whole `import type { … }` declaration).
364
+ // It classifies a specifier without re-parsing its text, so a binding literally
365
+ // named `type` (`import { type as bar }` -> name `type`, alias `bar`, NOT a type
366
+ // specifier) is not mistaken for a type modifier by a `"type "` string prefix.
367
+ inlineType bool
368
+ }
369
+
370
+ // siDecl is the parsed shape of one import declaration used by the block
371
+ // rebuilder. Declarations with a namespace binding are not mergeable and are
372
+ // re-emitted from their original text.
373
+ type siDecl struct {
374
+ specifier string
375
+ specRaw string
376
+ typeOnly bool
377
+ defaultName string
378
+ named []siSpec
379
+ namespace bool
380
+ // hasSpecComment marks a declaration whose named-import braces carry a comment.
381
+ // The merge rebuilder joins specifier texts with ", " and would drop such a
382
+ // comment, so a flagged declaration is made unmergeable (like a namespace
383
+ // binding) and re-emitted from its original text. See mergeKey.
384
+ hasSpecComment bool
385
+ original string
386
+ semicolon bool
387
+ }
388
+
389
+ // siEntry is a (possibly merged) import declaration ready to be grouped,
390
+ // sorted, and emitted.
391
+ type siEntry struct {
392
+ group int
393
+ specifier string
394
+ sortKey string
395
+ typeOnly bool
396
+ text string
397
+ }
398
+
399
+ // buildSortedImportBlock returns the canonical group/sort/merge representation
400
+ // of the contiguous import declarations.
289
401
  func buildSortedImportBlock(src string, imports []*shimast.Node, opts resolvedSortImportsOptions) string {
290
- type entry struct {
291
- group int
292
- specifier string
293
- sortKey string
294
- text string
295
- }
296
- entries := make([]entry, 0, len(imports))
402
+ decls := make([]siDecl, 0, len(imports))
297
403
  for _, decl := range imports {
298
- spec := moduleSpecifierText(decl)
299
- text := importStatementText(src, decl)
300
- sortKey := spec
301
- if opts.caseInsensitive {
302
- sortKey = strings.ToLower(spec)
303
- }
304
- entries = append(entries, entry{
305
- group: matchGroup(opts.groups, spec),
306
- specifier: spec,
307
- sortKey: sortKey,
308
- text: text,
309
- })
404
+ decls = append(decls, parseImportDecl(src, decl))
405
+ }
406
+ entries := mergeImportDecls(decls, opts)
407
+ for i := range entries {
408
+ entries[i].group = matchGroup(opts.groups, entries[i].specifier, entries[i].typeOnly)
409
+ entries[i].sortKey = foldCase(entries[i].specifier, opts.caseSensitive)
310
410
  }
311
411
  sort.SliceStable(entries, func(i, j int) bool {
312
412
  if entries[i].group != entries[j].group {
@@ -319,7 +419,7 @@ func buildSortedImportBlock(src string, imports []*shimast.Node, opts resolvedSo
319
419
  prevGroup := entries[0].group
320
420
  for i, e := range entries {
321
421
  if i > 0 {
322
- if e.group != prevGroup && opts.separation {
422
+ if e.group != prevGroup && separatorBetween(opts.groups, prevGroup, e.group) {
323
423
  b.WriteString("\n\n")
324
424
  } else {
325
425
  b.WriteString("\n")
@@ -331,6 +431,276 @@ func buildSortedImportBlock(src string, imports []*shimast.Node, opts resolvedSo
331
431
  return b.String()
332
432
  }
333
433
 
434
+ // separatorBetween reports whether a blank line should sit between two
435
+ // consecutive (in sorted output) groups. Groups skipped because they had no
436
+ // imports still contribute their sepBefore flag, so `[A, "", B]` with an empty
437
+ // A and B that both fall through to a later group collapses to one blank line.
438
+ func separatorBetween(groups []sortImportsGroup, prev, curr int) bool {
439
+ for k := prev + 1; k <= curr && k < len(groups); k++ {
440
+ if groups[k].sepBefore {
441
+ return true
442
+ }
443
+ }
444
+ return false
445
+ }
446
+
447
+ // parseImportDecl captures the merge-relevant shape of one import declaration.
448
+ func parseImportDecl(src string, decl *shimast.Node) siDecl {
449
+ out := siDecl{
450
+ specifier: moduleSpecifierText(decl),
451
+ specRaw: moduleSpecifierRaw(src, decl),
452
+ original: importStatementText(src, decl),
453
+ }
454
+ out.semicolon = strings.HasSuffix(out.original, ";")
455
+ imp := decl.AsImportDeclaration()
456
+ if imp == nil || imp.ImportClause == nil {
457
+ return out
458
+ }
459
+ clause := imp.ImportClause.AsImportClause()
460
+ if clause == nil {
461
+ return out
462
+ }
463
+ out.typeOnly = clause.PhaseModifier == shimast.KindTypeKeyword
464
+ out.defaultName = identifierText(clause.Name())
465
+ // A comment anywhere in the rebuilt import prefix (`import [type] [D] [, { … }]
466
+ // from `) would be lost when renderMergedDecl reconstructs the statement
467
+ // field-by-field. Flag it so mergeKey keeps the declaration unmergeable and
468
+ // its original text is preserved. This runs BEFORE the no-named-bindings /
469
+ // namespace early returns so a default-only import (`import a /* c */ from
470
+ // "m"`) is flagged too; it depends only on decl.Pos() and the module
471
+ // specifier, not on the named bindings. The prefix holds no module-path
472
+ // string, so a `//` or `/*` there is unambiguously a comment (a leading
473
+ // comment before `import` is excluded: SkipTrivia advances to `import`).
474
+ if imp.ModuleSpecifier != nil {
475
+ pStart := shimscanner.SkipTrivia(src, decl.Pos())
476
+ pEnd := shimscanner.SkipTrivia(src, imp.ModuleSpecifier.Pos())
477
+ if pStart >= 0 && pEnd <= len(src) && pStart < pEnd {
478
+ if span := src[pStart:pEnd]; strings.Contains(span, "//") || strings.Contains(span, "/*") {
479
+ out.hasSpecComment = true
480
+ }
481
+ }
482
+ // Also the module-specifier -> `;` tail (`from "m" /* keep */;`): a block
483
+ // comment there is interior to the declaration but outside the
484
+ // inter-declaration gap that leadingTriviaIsAllWhitespace scans, so the
485
+ // merge rebuilder would drop it. The module string itself is excluded (the
486
+ // span starts at ModuleSpecifier.End()), so no `//`-in-a-path false positive.
487
+ if tStart, tEnd := imp.ModuleSpecifier.End(), decl.End(); tStart >= 0 && tEnd <= len(src) && tStart < tEnd {
488
+ if span := src[tStart:tEnd]; strings.Contains(span, "//") || strings.Contains(span, "/*") {
489
+ out.hasSpecComment = true
490
+ }
491
+ }
492
+ }
493
+ // An import-attributes clause (`with { type: "json" }` / `assert { … }`) is not
494
+ // reconstructed by renderMergedDecl, so merging would silently drop those
495
+ // bytes. Flag the declaration unmergeable (its original text is then re-emitted
496
+ // verbatim), mirroring the print-width import printer, which bails to verbatim
497
+ // on `imp.Attributes != nil` for the same reason.
498
+ if imp.Attributes != nil {
499
+ out.hasSpecComment = true
500
+ }
501
+ if clause.NamedBindings == nil {
502
+ return out
503
+ }
504
+ if clause.NamedBindings.Kind == shimast.KindNamespaceImport {
505
+ out.namespace = true
506
+ return out
507
+ }
508
+ if clause.NamedBindings.Kind != shimast.KindNamedImports {
509
+ return out
510
+ }
511
+ named := clause.NamedBindings.AsNamedImports()
512
+ if named == nil || named.Elements == nil {
513
+ return out
514
+ }
515
+ for _, spec := range named.Elements.Nodes {
516
+ s := spec.AsImportSpecifier()
517
+ if s == nil {
518
+ continue
519
+ }
520
+ start := shimscanner.SkipTrivia(src, spec.Pos())
521
+ out.named = append(out.named, siSpec{
522
+ sortKey: identifierText(s.Name()),
523
+ text: src[start:spec.End()],
524
+ fromTypeOnlyDecl: out.typeOnly,
525
+ inlineType: s.IsTypeOnly,
526
+ })
527
+ }
528
+ return out
529
+ }
530
+
531
+ // mergeImportDecls collapses declarations of the same module into one entry.
532
+ // Same-module value imports always merge; same-module type-only imports always
533
+ // merge; a value import and a type-only import of the same module merge only
534
+ // when `combineTypeAndValue` is set. Namespace imports and merge conflicts fall
535
+ // back to one entry per original declaration.
536
+ func mergeImportDecls(decls []siDecl, opts resolvedSortImportsOptions) []siEntry {
537
+ order := make([]string, 0, len(decls))
538
+ buckets := make(map[string][]siDecl)
539
+ for _, d := range decls {
540
+ key := mergeKey(d, opts.combineTypeAndValue)
541
+ if _, seen := buckets[key]; !seen {
542
+ order = append(order, key)
543
+ }
544
+ buckets[key] = append(buckets[key], d)
545
+ }
546
+ entries := make([]siEntry, 0, len(decls))
547
+ for _, key := range order {
548
+ entries = append(entries, mergeBucket(buckets[key], opts)...)
549
+ }
550
+ return entries
551
+ }
552
+
553
+ // mergeKey groups declarations that are eligible to merge. Namespace imports
554
+ // get a unique key (a per-declaration counter via the original text plus a
555
+ // sentinel) so they never merge.
556
+ func mergeKey(d siDecl, combine bool) string {
557
+ if d.namespace {
558
+ return "\x00ns\x00" + d.original
559
+ }
560
+ // A named-import list carrying a comment is unmergeable so the comment
561
+ // survives in the declaration's original text (a per-declaration key).
562
+ if d.hasSpecComment {
563
+ return "\x00cmt\x00" + d.original
564
+ }
565
+ if combine || !d.typeOnly {
566
+ return "v\x00" + d.specifier
567
+ }
568
+ return "t\x00" + d.specifier
569
+ }
570
+
571
+ // mergeBucket renders a bucket of same-key declarations. A single declaration
572
+ // (or an unmergeable conflict) is emitted from its original text; otherwise the
573
+ // declarations fold into one rebuilt statement.
574
+ func mergeBucket(group []siDecl, opts resolvedSortImportsOptions) []siEntry {
575
+ if len(group) == 1 {
576
+ return []siEntry{originalEntry(group[0])}
577
+ }
578
+ text, typeOnly, ok := renderMergedDecl(group, opts)
579
+ if !ok {
580
+ out := make([]siEntry, 0, len(group))
581
+ for _, d := range group {
582
+ out = append(out, originalEntry(d))
583
+ }
584
+ return out
585
+ }
586
+ return []siEntry{{specifier: group[0].specifier, typeOnly: typeOnly, text: text}}
587
+ }
588
+
589
+ // originalEntry wraps one declaration's untouched source text as an entry.
590
+ func originalEntry(d siDecl) siEntry {
591
+ return siEntry{specifier: d.specifier, typeOnly: d.typeOnly, text: d.original}
592
+ }
593
+
594
+ // renderMergedDecl folds a bucket of mergeable declarations into one statement.
595
+ // It returns the rendered text, whether the merged result is a type-only import,
596
+ // and an `ok` flag that is false when the declarations cannot be merged
597
+ // (conflicting default names, or a type-only default that cannot survive in a
598
+ // mixed value import).
599
+ func renderMergedDecl(group []siDecl, opts resolvedSortImportsOptions) (string, bool, bool) {
600
+ mergedTypeOnly := true
601
+ for _, d := range group {
602
+ if !d.typeOnly {
603
+ mergedTypeOnly = false
604
+ }
605
+ }
606
+ defaultName := ""
607
+ defaultTypeOnly := false
608
+ for _, d := range group {
609
+ if d.defaultName == "" {
610
+ continue
611
+ }
612
+ if defaultName != "" && defaultName != d.defaultName {
613
+ return "", false, false
614
+ }
615
+ defaultName = d.defaultName
616
+ defaultTypeOnly = d.typeOnly
617
+ }
618
+ // A type-only default cannot be expressed inside a mixed value import
619
+ // (`import type Foo, { value }` would retype `value`).
620
+ if defaultName != "" && defaultTypeOnly && !mergedTypeOnly {
621
+ return "", false, false
622
+ }
623
+ specs := collectMergedSpecs(group, mergedTypeOnly, opts.caseSensitive)
624
+ if defaultName == "" && len(specs) == 0 {
625
+ return "", false, false
626
+ }
627
+
628
+ var b strings.Builder
629
+ b.WriteString("import ")
630
+ if mergedTypeOnly {
631
+ b.WriteString("type ")
632
+ }
633
+ if defaultName != "" {
634
+ b.WriteString(defaultName)
635
+ if len(specs) > 0 {
636
+ b.WriteString(", ")
637
+ }
638
+ }
639
+ if len(specs) > 0 {
640
+ b.WriteString("{ ")
641
+ b.WriteString(strings.Join(specs, ", "))
642
+ b.WriteString(" }")
643
+ }
644
+ b.WriteString(" from ")
645
+ b.WriteString(group[0].specRaw)
646
+ if group[0].semicolon {
647
+ b.WriteString(";")
648
+ }
649
+ return b.String(), mergedTypeOnly, true
650
+ }
651
+
652
+ // collectMergedSpecs gathers, de-duplicates, and sorts the named specifiers of
653
+ // a merged declaration. A specifier from a type-only declaration folded into a
654
+ // mixed value import gains an inline `type ` prefix. Specifiers are
655
+ // de-duplicated by local binding name — keeping the value form, since one
656
+ // binding cannot be both a value and a type import — and ordered value before
657
+ // type, then by name.
658
+ func collectMergedSpecs(group []siDecl, mergedTypeOnly, caseSensitive bool) []string {
659
+ type spec struct {
660
+ name string
661
+ isType bool
662
+ text string
663
+ }
664
+ index := make(map[string]int)
665
+ items := make([]spec, 0)
666
+ for _, d := range group {
667
+ for _, s := range d.named {
668
+ text := s.text
669
+ // Classify by AST flags, not a `"type "` text prefix. A binding literally
670
+ // named `type` (`import { type as bar }`) has inlineType=false and must stay
671
+ // a value specifier; a string-prefix check would mis-promote it to a type
672
+ // specifier and skip the modifier when folding a type-only declaration.
673
+ isType := s.inlineType
674
+ if s.fromTypeOnlyDecl && !mergedTypeOnly {
675
+ // A specifier from `import type { … }` carries no inline `type` modifier
676
+ // (TS forbids it there), so folding into a mixed value import must add one.
677
+ text = "type " + text
678
+ isType = true
679
+ }
680
+ cur := spec{name: s.sortKey, isType: isType, text: text}
681
+ if at, dup := index[s.sortKey]; dup {
682
+ if items[at].isType && !cur.isType {
683
+ items[at] = cur
684
+ }
685
+ continue
686
+ }
687
+ index[s.sortKey] = len(items)
688
+ items = append(items, cur)
689
+ }
690
+ }
691
+ sort.SliceStable(items, func(i, j int) bool {
692
+ if items[i].isType != items[j].isType {
693
+ return !items[i].isType
694
+ }
695
+ return foldCase(items[i].name, caseSensitive) < foldCase(items[j].name, caseSensitive)
696
+ })
697
+ texts := make([]string, len(items))
698
+ for i, it := range items {
699
+ texts[i] = it.text
700
+ }
701
+ return texts
702
+ }
703
+
334
704
  // importStatementText returns the import declaration's source text,
335
705
  // stripped of any leading whitespace/comment trivia.
336
706
  func importStatementText(src string, decl *shimast.Node) string {
@@ -355,17 +725,43 @@ func moduleSpecifierText(decl *shimast.Node) string {
355
725
  return stringLiteralText(imp.ModuleSpecifier)
356
726
  }
357
727
 
358
- // specifierEntry is the sortable pair captured for each named import
359
- // specifier: the identifier used as the sort key and the literal source
728
+ // moduleSpecifierRaw returns the module specifier's source text including its
729
+ // surrounding quotes, so a merged declaration preserves the original quote
730
+ // style.
731
+ func moduleSpecifierRaw(src string, decl *shimast.Node) string {
732
+ imp := decl.AsImportDeclaration()
733
+ if imp == nil || imp.ModuleSpecifier == nil {
734
+ return ""
735
+ }
736
+ start := shimscanner.SkipTrivia(src, imp.ModuleSpecifier.Pos())
737
+ end := imp.ModuleSpecifier.End()
738
+ if start < 0 || start > end || end > len(src) {
739
+ return ""
740
+ }
741
+ return src[start:end]
742
+ }
743
+
744
+ // foldCase lowercases `s` unless case-sensitive ordering was requested.
745
+ func foldCase(s string, caseSensitive bool) string {
746
+ if caseSensitive {
747
+ return s
748
+ }
749
+ return strings.ToLower(s)
750
+ }
751
+
752
+ // specifierEntry is the sortable record captured for each named import
753
+ // specifier: the local name used as the sort key, whether it is an inline
754
+ // `type` specifier (those sort after value specifiers), and the literal source
360
755
  // text that gets re-emitted in canonical order.
361
756
  type specifierEntry struct {
362
- key string
363
- text string
757
+ key string
758
+ isType bool
759
+ text string
364
760
  }
365
761
 
366
762
  // reportNamedSpecifierSort reports a fix when an import's `{ a, b }` list
367
763
  // is out of alphabetical order. Type-only specifiers participate in the
368
- // same sort key as value specifiers prettier's plugin-sort-imports
764
+ // same sort key as value specifiers, prettier's plugin-sort-imports
369
765
  // matches this behavior.
370
766
  //
371
767
  // Safety policy: comment trivia between specifiers anchors the rule
@@ -373,7 +769,7 @@ type specifierEntry struct {
373
769
  // would discard any `/* x */` comment carried inside the list. The
374
770
  // block-level sort applies the same policy via
375
771
  // `leadingTriviaIsAllWhitespace`; this is the per-specifier analog.
376
- func reportNamedSpecifierSort(ctx *Context, decl *shimast.Node, caseInsensitive bool) {
772
+ func reportNamedSpecifierSort(ctx *Context, decl *shimast.Node, caseSensitive bool) {
377
773
  imp := decl.AsImportDeclaration()
378
774
  if imp == nil || imp.ImportClause == nil {
379
775
  return
@@ -410,13 +806,10 @@ func reportNamedSpecifierSort(ctx *Context, decl *shimast.Node, caseInsensitive
410
806
  }
411
807
  start := shimscanner.SkipTrivia(src, spec.Pos())
412
808
  end := spec.End()
413
- key := name
414
- if caseInsensitive {
415
- key = strings.ToLower(name)
416
- }
417
809
  entries = append(entries, specifierEntry{
418
- key: key,
419
- text: src[start:end],
810
+ key: foldCase(name, caseSensitive),
811
+ isType: s.IsTypeOnly,
812
+ text: src[start:end],
420
813
  })
421
814
  }
422
815
  if len(entries) < 2 {
@@ -425,11 +818,14 @@ func reportNamedSpecifierSort(ctx *Context, decl *shimast.Node, caseInsensitive
425
818
  sorted := make([]specifierEntry, len(entries))
426
819
  copy(sorted, entries)
427
820
  sort.SliceStable(sorted, func(i, j int) bool {
821
+ if sorted[i].isType != sorted[j].isType {
822
+ return !sorted[i].isType
823
+ }
428
824
  return sorted[i].key < sorted[j].key
429
825
  })
430
826
  changed := false
431
827
  for i := range entries {
432
- if entries[i].key != sorted[i].key {
828
+ if entries[i].key != sorted[i].key || entries[i].isType != sorted[i].isType {
433
829
  changed = true
434
830
  break
435
831
  }