@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
@@ -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:
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}.
37
53
  //
38
- // 1. External modules (specifier does not start with `.`).
39
- // 2. Relative modules (specifier starts with `.`).
40
- //
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})
149
- }
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}}
172
+ group.sepBefore = pendingSep
173
+ pendingSep = false
174
+ groups = append(groups, group)
154
175
  }
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
163
- }
164
- sortSpecifiers := true
165
- if raw.ImportOrderSortSpecifiers != nil {
166
- sortSpecifiers = *raw.ImportOrderSortSpecifiers
177
+ groups = append([]sortImportsGroup{{raw: thirdPartyModulesPlaceholder, thirdParty: true}}, groups...)
167
178
  }
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,51 @@ 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
+ }
363
+
364
+ // siDecl is the parsed shape of one import declaration used by the block
365
+ // rebuilder. Declarations with a namespace binding are not mergeable and are
366
+ // re-emitted from their original text.
367
+ type siDecl struct {
368
+ specifier string
369
+ specRaw string
370
+ typeOnly bool
371
+ defaultName string
372
+ named []siSpec
373
+ namespace bool
374
+ original string
375
+ semicolon bool
376
+ }
377
+
378
+ // siEntry is a (possibly merged) import declaration ready to be grouped,
379
+ // sorted, and emitted.
380
+ type siEntry struct {
381
+ group int
382
+ specifier string
383
+ sortKey string
384
+ typeOnly bool
385
+ text string
386
+ }
387
+
388
+ // buildSortedImportBlock returns the canonical group/sort/merge representation
389
+ // of the contiguous import declarations.
289
390
  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))
391
+ decls := make([]siDecl, 0, len(imports))
297
392
  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
- })
393
+ decls = append(decls, parseImportDecl(src, decl))
394
+ }
395
+ entries := mergeImportDecls(decls, opts)
396
+ for i := range entries {
397
+ entries[i].group = matchGroup(opts.groups, entries[i].specifier, entries[i].typeOnly)
398
+ entries[i].sortKey = foldCase(entries[i].specifier, opts.caseSensitive)
310
399
  }
311
400
  sort.SliceStable(entries, func(i, j int) bool {
312
401
  if entries[i].group != entries[j].group {
@@ -319,7 +408,7 @@ func buildSortedImportBlock(src string, imports []*shimast.Node, opts resolvedSo
319
408
  prevGroup := entries[0].group
320
409
  for i, e := range entries {
321
410
  if i > 0 {
322
- if e.group != prevGroup && opts.separation {
411
+ if e.group != prevGroup && separatorBetween(opts.groups, prevGroup, e.group) {
323
412
  b.WriteString("\n\n")
324
413
  } else {
325
414
  b.WriteString("\n")
@@ -331,6 +420,226 @@ func buildSortedImportBlock(src string, imports []*shimast.Node, opts resolvedSo
331
420
  return b.String()
332
421
  }
333
422
 
423
+ // separatorBetween reports whether a blank line should sit between two
424
+ // consecutive (in sorted output) groups. Groups skipped because they had no
425
+ // imports still contribute their sepBefore flag, so `[A, "", B]` with an empty
426
+ // A and B that both fall through to a later group collapses to one blank line.
427
+ func separatorBetween(groups []sortImportsGroup, prev, curr int) bool {
428
+ for k := prev + 1; k <= curr && k < len(groups); k++ {
429
+ if groups[k].sepBefore {
430
+ return true
431
+ }
432
+ }
433
+ return false
434
+ }
435
+
436
+ // parseImportDecl captures the merge-relevant shape of one import declaration.
437
+ func parseImportDecl(src string, decl *shimast.Node) siDecl {
438
+ out := siDecl{
439
+ specifier: moduleSpecifierText(decl),
440
+ specRaw: moduleSpecifierRaw(src, decl),
441
+ original: importStatementText(src, decl),
442
+ }
443
+ out.semicolon = strings.HasSuffix(out.original, ";")
444
+ imp := decl.AsImportDeclaration()
445
+ if imp == nil || imp.ImportClause == nil {
446
+ return out
447
+ }
448
+ clause := imp.ImportClause.AsImportClause()
449
+ if clause == nil {
450
+ return out
451
+ }
452
+ out.typeOnly = clause.PhaseModifier == shimast.KindTypeKeyword
453
+ out.defaultName = identifierText(clause.Name())
454
+ if clause.NamedBindings == nil {
455
+ return out
456
+ }
457
+ if clause.NamedBindings.Kind == shimast.KindNamespaceImport {
458
+ out.namespace = true
459
+ return out
460
+ }
461
+ if clause.NamedBindings.Kind != shimast.KindNamedImports {
462
+ return out
463
+ }
464
+ named := clause.NamedBindings.AsNamedImports()
465
+ if named == nil || named.Elements == nil {
466
+ return out
467
+ }
468
+ for _, spec := range named.Elements.Nodes {
469
+ s := spec.AsImportSpecifier()
470
+ if s == nil {
471
+ continue
472
+ }
473
+ start := shimscanner.SkipTrivia(src, spec.Pos())
474
+ out.named = append(out.named, siSpec{
475
+ sortKey: identifierText(s.Name()),
476
+ text: src[start:spec.End()],
477
+ fromTypeOnlyDecl: out.typeOnly,
478
+ })
479
+ }
480
+ return out
481
+ }
482
+
483
+ // mergeImportDecls collapses declarations of the same module into one entry.
484
+ // Same-module value imports always merge; same-module type-only imports always
485
+ // merge; a value import and a type-only import of the same module merge only
486
+ // when `combineTypeAndValue` is set. Namespace imports and merge conflicts fall
487
+ // back to one entry per original declaration.
488
+ func mergeImportDecls(decls []siDecl, opts resolvedSortImportsOptions) []siEntry {
489
+ order := make([]string, 0, len(decls))
490
+ buckets := make(map[string][]siDecl)
491
+ for _, d := range decls {
492
+ key := mergeKey(d, opts.combineTypeAndValue)
493
+ if _, seen := buckets[key]; !seen {
494
+ order = append(order, key)
495
+ }
496
+ buckets[key] = append(buckets[key], d)
497
+ }
498
+ entries := make([]siEntry, 0, len(decls))
499
+ for _, key := range order {
500
+ entries = append(entries, mergeBucket(buckets[key], opts)...)
501
+ }
502
+ return entries
503
+ }
504
+
505
+ // mergeKey groups declarations that are eligible to merge. Namespace imports
506
+ // get a unique key (a per-declaration counter via the original text plus a
507
+ // sentinel) so they never merge.
508
+ func mergeKey(d siDecl, combine bool) string {
509
+ if d.namespace {
510
+ return "\x00ns\x00" + d.original
511
+ }
512
+ if combine || !d.typeOnly {
513
+ return "v\x00" + d.specifier
514
+ }
515
+ return "t\x00" + d.specifier
516
+ }
517
+
518
+ // mergeBucket renders a bucket of same-key declarations. A single declaration
519
+ // (or an unmergeable conflict) is emitted from its original text; otherwise the
520
+ // declarations fold into one rebuilt statement.
521
+ func mergeBucket(group []siDecl, opts resolvedSortImportsOptions) []siEntry {
522
+ if len(group) == 1 {
523
+ return []siEntry{originalEntry(group[0])}
524
+ }
525
+ text, typeOnly, ok := renderMergedDecl(group, opts)
526
+ if !ok {
527
+ out := make([]siEntry, 0, len(group))
528
+ for _, d := range group {
529
+ out = append(out, originalEntry(d))
530
+ }
531
+ return out
532
+ }
533
+ return []siEntry{{specifier: group[0].specifier, typeOnly: typeOnly, text: text}}
534
+ }
535
+
536
+ // originalEntry wraps one declaration's untouched source text as an entry.
537
+ func originalEntry(d siDecl) siEntry {
538
+ return siEntry{specifier: d.specifier, typeOnly: d.typeOnly, text: d.original}
539
+ }
540
+
541
+ // renderMergedDecl folds a bucket of mergeable declarations into one statement.
542
+ // It returns the rendered text, whether the merged result is a type-only import,
543
+ // and an `ok` flag that is false when the declarations cannot be merged
544
+ // (conflicting default names, or a type-only default that cannot survive in a
545
+ // mixed value import).
546
+ func renderMergedDecl(group []siDecl, opts resolvedSortImportsOptions) (string, bool, bool) {
547
+ mergedTypeOnly := true
548
+ for _, d := range group {
549
+ if !d.typeOnly {
550
+ mergedTypeOnly = false
551
+ }
552
+ }
553
+ defaultName := ""
554
+ defaultTypeOnly := false
555
+ for _, d := range group {
556
+ if d.defaultName == "" {
557
+ continue
558
+ }
559
+ if defaultName != "" && defaultName != d.defaultName {
560
+ return "", false, false
561
+ }
562
+ defaultName = d.defaultName
563
+ defaultTypeOnly = d.typeOnly
564
+ }
565
+ // A type-only default cannot be expressed inside a mixed value import
566
+ // (`import type Foo, { value }` would retype `value`).
567
+ if defaultName != "" && defaultTypeOnly && !mergedTypeOnly {
568
+ return "", false, false
569
+ }
570
+ specs := collectMergedSpecs(group, mergedTypeOnly, opts.caseSensitive)
571
+ if defaultName == "" && len(specs) == 0 {
572
+ return "", false, false
573
+ }
574
+
575
+ var b strings.Builder
576
+ b.WriteString("import ")
577
+ if mergedTypeOnly {
578
+ b.WriteString("type ")
579
+ }
580
+ if defaultName != "" {
581
+ b.WriteString(defaultName)
582
+ if len(specs) > 0 {
583
+ b.WriteString(", ")
584
+ }
585
+ }
586
+ if len(specs) > 0 {
587
+ b.WriteString("{ ")
588
+ b.WriteString(strings.Join(specs, ", "))
589
+ b.WriteString(" }")
590
+ }
591
+ b.WriteString(" from ")
592
+ b.WriteString(group[0].specRaw)
593
+ if group[0].semicolon {
594
+ b.WriteString(";")
595
+ }
596
+ return b.String(), mergedTypeOnly, true
597
+ }
598
+
599
+ // collectMergedSpecs gathers, de-duplicates, and sorts the named specifiers of
600
+ // a merged declaration. A specifier from a type-only declaration folded into a
601
+ // mixed value import gains an inline `type ` prefix. Specifiers are
602
+ // de-duplicated by local binding name — keeping the value form, since one
603
+ // binding cannot be both a value and a type import — and ordered value before
604
+ // type, then by name.
605
+ func collectMergedSpecs(group []siDecl, mergedTypeOnly, caseSensitive bool) []string {
606
+ type spec struct {
607
+ name string
608
+ isType bool
609
+ text string
610
+ }
611
+ index := make(map[string]int)
612
+ items := make([]spec, 0)
613
+ for _, d := range group {
614
+ for _, s := range d.named {
615
+ text := s.text
616
+ if s.fromTypeOnlyDecl && !mergedTypeOnly && !strings.HasPrefix(text, "type ") {
617
+ text = "type " + text
618
+ }
619
+ cur := spec{name: s.sortKey, isType: strings.HasPrefix(text, "type "), text: text}
620
+ if at, dup := index[s.sortKey]; dup {
621
+ if items[at].isType && !cur.isType {
622
+ items[at] = cur
623
+ }
624
+ continue
625
+ }
626
+ index[s.sortKey] = len(items)
627
+ items = append(items, cur)
628
+ }
629
+ }
630
+ sort.SliceStable(items, func(i, j int) bool {
631
+ if items[i].isType != items[j].isType {
632
+ return !items[i].isType
633
+ }
634
+ return foldCase(items[i].name, caseSensitive) < foldCase(items[j].name, caseSensitive)
635
+ })
636
+ texts := make([]string, len(items))
637
+ for i, it := range items {
638
+ texts[i] = it.text
639
+ }
640
+ return texts
641
+ }
642
+
334
643
  // importStatementText returns the import declaration's source text,
335
644
  // stripped of any leading whitespace/comment trivia.
336
645
  func importStatementText(src string, decl *shimast.Node) string {
@@ -355,17 +664,43 @@ func moduleSpecifierText(decl *shimast.Node) string {
355
664
  return stringLiteralText(imp.ModuleSpecifier)
356
665
  }
357
666
 
358
- // specifierEntry is the sortable pair captured for each named import
359
- // specifier: the identifier used as the sort key and the literal source
667
+ // moduleSpecifierRaw returns the module specifier's source text including its
668
+ // surrounding quotes, so a merged declaration preserves the original quote
669
+ // style.
670
+ func moduleSpecifierRaw(src string, decl *shimast.Node) string {
671
+ imp := decl.AsImportDeclaration()
672
+ if imp == nil || imp.ModuleSpecifier == nil {
673
+ return ""
674
+ }
675
+ start := shimscanner.SkipTrivia(src, imp.ModuleSpecifier.Pos())
676
+ end := imp.ModuleSpecifier.End()
677
+ if start < 0 || start > end || end > len(src) {
678
+ return ""
679
+ }
680
+ return src[start:end]
681
+ }
682
+
683
+ // foldCase lowercases `s` unless case-sensitive ordering was requested.
684
+ func foldCase(s string, caseSensitive bool) string {
685
+ if caseSensitive {
686
+ return s
687
+ }
688
+ return strings.ToLower(s)
689
+ }
690
+
691
+ // specifierEntry is the sortable record captured for each named import
692
+ // specifier: the local name used as the sort key, whether it is an inline
693
+ // `type` specifier (those sort after value specifiers), and the literal source
360
694
  // text that gets re-emitted in canonical order.
361
695
  type specifierEntry struct {
362
- key string
363
- text string
696
+ key string
697
+ isType bool
698
+ text string
364
699
  }
365
700
 
366
701
  // reportNamedSpecifierSort reports a fix when an import's `{ a, b }` list
367
702
  // is out of alphabetical order. Type-only specifiers participate in the
368
- // same sort key as value specifiers prettier's plugin-sort-imports
703
+ // same sort key as value specifiers, prettier's plugin-sort-imports
369
704
  // matches this behavior.
370
705
  //
371
706
  // Safety policy: comment trivia between specifiers anchors the rule
@@ -373,7 +708,7 @@ type specifierEntry struct {
373
708
  // would discard any `/* x */` comment carried inside the list. The
374
709
  // block-level sort applies the same policy via
375
710
  // `leadingTriviaIsAllWhitespace`; this is the per-specifier analog.
376
- func reportNamedSpecifierSort(ctx *Context, decl *shimast.Node, caseInsensitive bool) {
711
+ func reportNamedSpecifierSort(ctx *Context, decl *shimast.Node, caseSensitive bool) {
377
712
  imp := decl.AsImportDeclaration()
378
713
  if imp == nil || imp.ImportClause == nil {
379
714
  return
@@ -410,13 +745,10 @@ func reportNamedSpecifierSort(ctx *Context, decl *shimast.Node, caseInsensitive
410
745
  }
411
746
  start := shimscanner.SkipTrivia(src, spec.Pos())
412
747
  end := spec.End()
413
- key := name
414
- if caseInsensitive {
415
- key = strings.ToLower(name)
416
- }
417
748
  entries = append(entries, specifierEntry{
418
- key: key,
419
- text: src[start:end],
749
+ key: foldCase(name, caseSensitive),
750
+ isType: s.IsTypeOnly,
751
+ text: src[start:end],
420
752
  })
421
753
  }
422
754
  if len(entries) < 2 {
@@ -425,11 +757,14 @@ func reportNamedSpecifierSort(ctx *Context, decl *shimast.Node, caseInsensitive
425
757
  sorted := make([]specifierEntry, len(entries))
426
758
  copy(sorted, entries)
427
759
  sort.SliceStable(sorted, func(i, j int) bool {
760
+ if sorted[i].isType != sorted[j].isType {
761
+ return !sorted[i].isType
762
+ }
428
763
  return sorted[i].key < sorted[j].key
429
764
  })
430
765
  changed := false
431
766
  for i := range entries {
432
- if entries[i].key != sorted[i].key {
767
+ if entries[i].key != sorted[i].key || entries[i].isType != sorted[i].isType {
433
768
  changed = true
434
769
  break
435
770
  }