@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
@@ -15,7 +15,7 @@ import (
15
15
  // node kinds whose per-node printers are registered with the
16
16
  // dispatcher (object/array literals, call/new expressions, named
17
17
  // import / export clauses, top-level import declarations). For every
18
- // other kind, the rule abstains it never emits an edit that would
18
+ // other kind, the rule abstains, it never emits an edit that would
19
19
  // modify bytes the dispatcher does not fully control. Coverage
20
20
  // expands by adding kinds to the dispatcher; the rule itself does not
21
21
  // need to grow.
@@ -23,7 +23,7 @@ import (
23
23
  // Per-node decision flow:
24
24
  //
25
25
  // 1. Skip leading trivia to find the node's actual first byte.
26
- // 2. Count the leading column on that line that becomes the
26
+ // 2. Count the leading column on that line, that becomes the
27
27
  // printer's StartingIndent so continuation lines align under the
28
28
  // opening token and fit measurement charges the prefix against
29
29
  // the budget.
@@ -41,7 +41,7 @@ import (
41
41
  // idempotent: a second pass renders identical bytes, the comparison
42
42
  // short-circuits, and the cascade converges. The `covered` abstain in
43
43
  // step 4 is the safety floor: `ttsc format` either reflows correctly or
44
- // leaves the node byte-identical it never emits a half-reflowed,
44
+ // leaves the node byte-identical, it never emits a half-reflowed,
45
45
  // inconsistently indented shape.
46
46
  //
47
47
  // The rule is a format-class rule (IsFormat == true) so `ttsc format`
@@ -54,7 +54,7 @@ type formatPrintWidth struct{}
54
54
  // formatPrintWidthOptions mirrors `TtscLintRuleOptions.PrintWidth`.
55
55
  //
56
56
  // TrailingComma reaches this rule because the printer's reflow decides
57
- // whether to emit a trailing comma on every multi-line list and that
57
+ // whether to emit a trailing comma on every multi-line list, and that
58
58
  // decision must match the user's `format.trailingComma` setting or the
59
59
  // reflow oscillates against `format/trailing-comma` on every cascade
60
60
  // pass. The config layer mirrors `format.trailingComma` into both
@@ -79,6 +79,7 @@ func (formatPrintWidth) Visits() []shimast.Kind {
79
79
  shimast.KindNamedImports,
80
80
  shimast.KindNamedExports,
81
81
  shimast.KindImportDeclaration,
82
+ shimast.KindConditionalExpression,
82
83
  }
83
84
  }
84
85
 
@@ -92,19 +93,21 @@ func (formatPrintWidth) Visits() []shimast.Kind {
92
93
  // re-render still over-breaks some of these; the next slice should
93
94
  // measure fitsFirstLine against `pw - col - trailingNonComment`
94
95
  // before committing to the broken layout.
95
- // - Single-line `export type { X } from "long-path"` reexports.
96
- // Prettier 3 keeps them flat even when the whole declaration
97
- // overflows; ttsc-lint visits `KindNamedExports` in isolation and
98
- // breaks the brace clause. A real fix requires teaching the rule
99
- // about the surrounding ExportDeclaration so the brace clause is
100
- // measured against the full declaration line, or visiting
101
- // ExportDeclaration directly so the `from "..."` tail joins the
102
- // reflow surface.
96
+ // - Default-combined imports (`import D, { X } from "x"`) are kept
97
+ // verbatim by printImportDeclaration (its `clause.Name() != nil`
98
+ // guard), so a default import whose named clause overflows is not
99
+ // broken; Prettier breaks it. Lifting the guard, threading the default
100
+ // name into the printed prefix, is a future default-import reflow slice.
103
101
  //
104
- // Both cases are tracked benchmark cases that forced
105
- // `formatPrintWidth: 'off'` on the ttsc-lint branch. They are listed
106
- // here so a future slice can pick them up without rediscovering the
107
- // divergence.
102
+ // (A single-specifier `import/export { X } from "long-path"` that overflows
103
+ // only because of the `from "..."` tail is now kept inline by the
104
+ // isSingleSpecifierNamedClause abstain, matching Prettier, which never
105
+ // breaks a one-name clause. Multi-specifier clauses still break correctly:
106
+ // the trailing-width charge measures the `from "..."` tail against the line
107
+ // budget, so a brace that fits but whose declaration overflows is broken.)
108
+ //
109
+ // These cases are tracked benchmark divergences. They are listed here so a
110
+ // future slice can pick them up without rediscovering the divergence.
108
111
  func (formatPrintWidth) Check(ctx *Context, node *shimast.Node) {
109
112
  if ctx == nil || ctx.File == nil || node == nil {
110
113
  return
@@ -135,6 +138,54 @@ func (formatPrintWidth) Check(ctx *Context, node *shimast.Node) {
135
138
  return
136
139
  }
137
140
 
141
+ printOpts.StartingColumn = leadingColumn(src, start, printOpts.TabWidth)
142
+
143
+ // trailingWidth is the column span of the tokens that stay on the
144
+ // node's last line after `end`, a `;`, a `);`, a `) {`. The reflow
145
+ // replaces only [start, end) and cannot move them, so both the fast
146
+ // path and the layout budget must reserve those columns; otherwise
147
+ // the rule emits a line that overflows by exactly the suffix.
148
+ trailingWidth := trailingLineWidth(src, end, printOpts.TabWidth)
149
+
150
+ // Fast path: if the node's existing single-line bytes already fit
151
+ // the printWidth budget, prefix column, node width and trailing
152
+ // suffix all charged, the reflowed output cannot differ from the
153
+ // source (the printer would render the same flat shape). Skip the
154
+ // Doc build + render entirely. This is the common case on
155
+ // well-formatted code, every short call, every short literal,
156
+ // and saves the allocations from PrintNode + Print.
157
+ //
158
+ // This runs before the abstain guards below on purpose: those guards
159
+ // only ever return (they never emit an edit), so for a node that
160
+ // already fits the outcome is identical whichever fires first, and
161
+ // the fast path is far cheaper than the guards' ancestor walks and
162
+ // the per-byte comment scan in hasNonChildComments. Charging that
163
+ // cost only on nodes that actually overflow keeps the hot path on
164
+ // well-formatted code allocation- and scan-free.
165
+ // A call with two or more function/arrow arguments explodes regardless of
166
+ // width (Prettier's multiple-callback rule), and an array of same-kind
167
+ // multi-child arrays/objects explodes under Prettier's shouldBreak heuristic,
168
+ // so a flat one-line node of either shape still needs a reflow. Skip the fast
169
+ // path for them; the printer's ForceBreak then produces the exploded shape.
170
+ // Everything else that fits flat is byte-identical after reflow, so the fast
171
+ // path stands.
172
+ if !callForcesFunctionBreak(node) &&
173
+ !arrayForcesBreak(node) &&
174
+ !sliceContainsNewline(src, start, end) &&
175
+ printOpts.StartingColumn+displayWidth(src[start:end])+trailingWidth <= printOpts.PrintWidth {
176
+ return
177
+ }
178
+
179
+ // A named import/export clause with a single specifier is never broken by
180
+ // Prettier 3: `import { X } from "long"` and `export type { X } from "long"`
181
+ // stay inline even when the `from "..."` tail pushes the whole declaration
182
+ // past printWidth (Prettier breaks a brace clause only at two or more
183
+ // specifiers). The rule visits the brace clause (or the import declaration)
184
+ // in isolation, so without this it would break a one-name clause and diverge.
185
+ if isSingleSpecifierNamedClause(node) {
186
+ return
187
+ }
188
+
138
189
  // Skip nested-print fires: if the visiting node has an ancestor
139
190
  // that also belongs to the rule's set, the outer reflow already
140
191
  // includes us. Acting at every level would emit overlapping edits
@@ -144,8 +195,8 @@ func (formatPrintWidth) Check(ctx *Context, node *shimast.Node) {
144
195
  }
145
196
 
146
197
  // Abstain on any node nested inside a template-literal substitution.
147
- // Prettier renders `${…}` expressions at printWidth:Infinity it
148
- // never breaks an interpolation the source wrote on one line so
198
+ // Prettier renders `${…}` expressions at printWidth:Infinity, it
199
+ // never breaks an interpolation the source wrote on one line, so
149
200
  // reflowing a call or literal inside `${…}` would split the template
150
201
  // across lines and diverge from Prettier. See the printWidth:Infinity
151
202
  // branch in Prettier's printTemplateExpression.
@@ -153,50 +204,42 @@ func (formatPrintWidth) Check(ctx *Context, node *shimast.Node) {
153
204
  return
154
205
  }
155
206
 
207
+ // Abstain on any node nested inside a JSX expression container (`{…}`),
208
+ // the JSX analogue of the template-substitution guard above. A reflow
209
+ // node (call / conditional) that sits inside BOTH a JSX attribute
210
+ // initializer (`x={cond ? a : b}`) and a JSX child
211
+ // (`{items.map(…)}`) gets broken here, then the next pass measures each
212
+ // fragment flat, finds it fits, and reverts — `ttsc format` oscillates to
213
+ // the 10-pass cap and exits 2. `KindJsxExpression` wraps both the
214
+ // attribute `{…}` and the child `{…}`, so this one guard covers both.
215
+ if hasJsxExpressionAncestor(node) {
216
+ return
217
+ }
218
+
156
219
  // Safety: abstain when the node carries comments outside its
157
220
  // children. The per-node printers join child docs with a fresh
158
221
  // `, ` separator and have no path for trivia between siblings, so
159
222
  // reflowing such a node would silently delete the comment.
160
- // Conservative false positives mean a missed reflow opportunity,
223
+ // Conservative, false positives mean a missed reflow opportunity,
161
224
  // not a regression. Coverage of inline comments inside lists is
162
225
  // the next slice of work.
163
226
  if hasNonChildComments(node, src, start, end) {
164
227
  return
165
228
  }
166
229
 
167
- printOpts.StartingColumn = leadingColumn(src, start, printOpts.TabWidth)
168
230
  // A node reflowed on a ternary-arm continuation line (`? expr` or
169
231
  // `: expr`) hangs its broken continuation under the arm's expression,
170
- // two columns past the `?`/`:` marker not under the marker itself.
232
+ // two columns past the `?`/`:` marker, not under the marker itself.
171
233
  printOpts.BaseIndent = lineLeadingIndent(src, start, printOpts.TabWidth) +
172
234
  ternaryArmIndentBonus(src, start)
173
235
 
174
- // trailingWidth is the column span of the tokens that stay on the
175
- // node's last line after `end` — a `;`, a `);`, a `) {`. The reflow
176
- // replaces only [start, end) and cannot move them, so both the fast
177
- // path and the layout budget must reserve those columns; otherwise
178
- // the rule emits a line that overflows by exactly the suffix.
179
- trailingWidth := trailingLineWidth(src, end, printOpts.TabWidth)
180
-
181
- // Fast path: if the node's existing single-line bytes already fit
182
- // the printWidth budget — prefix column, node width and trailing
183
- // suffix all charged — the reflowed output cannot differ from the
184
- // source (the printer would render the same flat shape). Skip the
185
- // Doc build + render entirely. This is the common case on
186
- // well-formatted code — every short call, every short literal —
187
- // and saves the allocations from PrintNode + Print.
188
- if !sliceContainsNewline(src, start, end) &&
189
- printOpts.StartingColumn+(end-start)+trailingWidth <= printOpts.PrintWidth {
190
- return
191
- }
192
-
193
236
  printCtx := NewPrintContext(ctx.File, printOpts)
194
237
  doc, covered := PrintNode(printCtx, node)
195
238
  if doc.IsNil() {
196
239
  return
197
240
  }
198
241
  // Safety abstain: the printed subtree contains a multi-line verbatim
199
- // node one the dispatcher has no printer for. Such a node keeps the
242
+ // node, one the dispatcher has no printer for. Such a node keeps the
200
243
  // source columns its lines were written at, while the reflow
201
244
  // re-indents everything around it. Emitting the edit would produce
202
245
  // inconsistently indented, corrupt output (a callback header at one
@@ -207,8 +250,8 @@ func (formatPrintWidth) Check(ctx *Context, node *shimast.Node) {
207
250
  return
208
251
  }
209
252
  // Render at the full printWidth budget. A reflow that breaks across
210
- // lines then makes every layout decision which call argument hugs,
211
- // where a list explodes against the true column budget. The
253
+ // lines then makes every layout decision, which call argument hugs,
254
+ // where a list explodes, against the true column budget. The
212
255
  // un-movable trailing suffix (`;`, `) {`, ` satisfies T`) lands on a
213
256
  // short last line; charging it against the whole budget would
214
257
  // wrongly penalize the interior lines and over-break the node.
@@ -217,7 +260,7 @@ func (formatPrintWidth) Check(ctx *Context, node *shimast.Node) {
217
260
  // reflow that collapses to a single line. When the flat form plus
218
261
  // the suffix would overflow, re-render under a budget shrunk by the
219
262
  // suffix so the node breaks instead of spilling the suffix past
220
- // printWidth the regression that keeps a call flat at exactly
263
+ // printWidth, the regression that keeps a call flat at exactly
221
264
  // printWidth while the trailing `;` runs over.
222
265
  if trailingWidth > 0 &&
223
266
  !strings.Contains(rendered, "\n") &&
@@ -233,8 +276,8 @@ func (formatPrintWidth) Check(ctx *Context, node *shimast.Node) {
233
276
  }
234
277
  // Safety floor: never emit an edit that makes the widest line wider
235
278
  // than it already was. The reflow may be unable to break an
236
- // un-breakable token run a long string literal, a verbatim object
237
- // member but it must never *worsen* the worst line. That is exactly
279
+ // un-breakable token run, a long string literal, a verbatim object
280
+ // member, but it must never *worsen* the worst line. That is exactly
238
281
  // the regression this guards: `ttsc format` collapsing an
239
282
  // already-broken, fitting call into one over-wide line. A reflow that
240
283
  // only fixes indentation and leaves a pre-existing over-wide line
@@ -255,14 +298,14 @@ func (formatPrintWidth) Check(ctx *Context, node *shimast.Node) {
255
298
  // trailingLineWidth returns the visual column width of src[end:] up to
256
299
  // the next newline, with trailing whitespace trimmed. The formatPrintWidth
257
300
  // reflow replaces only the node's own byte range, so whatever
258
- // shares the node's last source line a statement `;`, a `) {` header
259
- // tail, a `, nextArg)` continuation stays put. Charging that width
301
+ // shares the node's last source line, a statement `;`, a `) {` header
302
+ // tail, a `, nextArg)` continuation, stays put. Charging that width
260
303
  // against the budget keeps the rule from emitting a line that overflows
261
304
  // by exactly the suffix it could never move.
262
305
  //
263
306
  // Trailing `//` line comments are excluded from the budget. A line
264
307
  // comment runs to the end of the source line by definition, so breaking
265
- // the reflowed node to make the comment fit cannot help Prettier 3
308
+ // the reflowed node to make the comment fit cannot help, Prettier 3
266
309
  // keeps the node inline and lets the comment trail (see typeorm's
267
310
  // `comment.replaceAll(...) // Null bytes' shape that pushed
268
311
  // `formatPrintWidth: 'off'` onto the ttsc-lint benchmark branch).
@@ -278,11 +321,11 @@ func trailingLineWidth(src string, end int, tabWidth int) int {
278
321
  }
279
322
  lineEnd := trailingSuffixEnd(src, end)
280
323
  col := 0
281
- for i := end; i < lineEnd; i++ {
282
- if src[i] == '\t' {
324
+ for _, r := range src[end:lineEnd] {
325
+ if r == '\t' {
283
326
  col += tabWidth - (col % tabWidth)
284
327
  } else {
285
- col++
328
+ col += runeWidth(r)
286
329
  }
287
330
  }
288
331
  return col
@@ -302,7 +345,7 @@ func trailingSuffixEnd(src string, end int) int {
302
345
  if src[lineEnd] == '/' && lineEnd+1 < len(src) {
303
346
  next := src[lineEnd+1]
304
347
  if next == '/' {
305
- // `//` line comment Prettier treats the whole tail as a
348
+ // `//` line comment, Prettier treats the whole tail as a
306
349
  // trailing comment that runs to EOL. Drop it from the suffix
307
350
  // budget so the rule does not break the node to chase a
308
351
  // comment that cannot be moved or wrapped.
@@ -322,8 +365,8 @@ func trailingSuffixEnd(src string, end int) int {
322
365
  }
323
366
 
324
367
  // maxLineWidth returns the widest effective column span among the lines
325
- // of `text`. The first line is charged `startingColumn` the prefix
326
- // already on that source line that the reflow does not re-emit and
368
+ // of `text`. The first line is charged `startingColumn`, the prefix
369
+ // already on that source line that the reflow does not re-emit, and
327
370
  // the last line is charged `trailingWidth` for the un-movable suffix
328
371
  // that follows the node. Tabs count as `tabWidth` columns.
329
372
  //
@@ -343,7 +386,7 @@ func maxLineWidth(text string, startingColumn, trailingWidth, tabWidth int) int
343
386
  if r == '\t' {
344
387
  width += tabWidth - (width % tabWidth)
345
388
  } else if r != '\r' {
346
- width++
389
+ width += runeWidth(r)
347
390
  }
348
391
  }
349
392
  if i == 0 {
@@ -372,11 +415,11 @@ func leadingColumn(src string, pos int, tabWidth int) int {
372
415
  }
373
416
  lineStart := lineStartOffset(src, pos)
374
417
  col := 0
375
- for i := lineStart; i < pos; i++ {
376
- if src[i] == '\t' {
418
+ for _, r := range src[lineStart:pos] {
419
+ if r == '\t' {
377
420
  col += tabWidth - (col % tabWidth)
378
421
  } else {
379
- col++
422
+ col += runeWidth(r)
380
423
  }
381
424
  }
382
425
  return col
@@ -389,7 +432,7 @@ func leadingColumn(src string, pos int, tabWidth int) int {
389
432
  //
390
433
  // Continuation lines (Hardline / broken-mode Line) emitted by the
391
434
  // reflow doc should align relative to this value, not relative to the
392
- // node's leading column see PrintOptions.BaseIndent for the contract.
435
+ // node's leading column, see PrintOptions.BaseIndent for the contract.
393
436
  //
394
437
  // When the visited node lives on a *continuation line* (e.g. an RHS
395
438
  // expression hanging below a binary operator on the previous line),
@@ -462,7 +505,7 @@ func lineStartOffset(src string, pos int) int {
462
505
  // the arm's expression rather than under the `?`/`:` token. In practice
463
506
  // only a ternary arm opens a reflow target's line with `? ` / `: `; the
464
507
  // two-byte prefix is a heuristic, and a rare false positive only shifts
465
- // a broken continuation by two columns it never corrupts bytes.
508
+ // a broken continuation by two columns, it never corrupts bytes.
466
509
  func ternaryArmIndentBonus(src string, pos int) int {
467
510
  i := lineStartOffset(src, pos)
468
511
  for i < len(src) && (src[i] == ' ' || src[i] == '\t') {
@@ -508,6 +551,26 @@ func hasTemplateSubstitutionAncestor(node *shimast.Node) bool {
508
551
  return false
509
552
  }
510
553
 
554
+ // hasJsxExpressionAncestor reports whether `node` sits inside a JSX
555
+ // expression container (`{…}`). formatPrintWidth abstains on such nodes for
556
+ // the same reason as hasTemplateSubstitutionAncestor: a reflow node that lives
557
+ // inside both a JSX attribute initializer and a JSX child breaks under
558
+ // print-width and then reverts on the next pass when each fragment measures
559
+ // flat, so `ttsc format` never converges and exits 2. `KindJsxExpression`
560
+ // wraps both the attribute `{…}` and the child `{…}`, so a single ancestor
561
+ // walk covers both placements.
562
+ func hasJsxExpressionAncestor(node *shimast.Node) bool {
563
+ if node == nil {
564
+ return false
565
+ }
566
+ for parent := node.Parent; parent != nil; parent = parent.Parent {
567
+ if parent.Kind == shimast.KindJsxExpression {
568
+ return true
569
+ }
570
+ }
571
+ return false
572
+ }
573
+
511
574
  // isReflowKind reports whether `k` is one of the node kinds the
512
575
  // formatPrintWidth rule visits. Kept in sync with Visits() so
513
576
  // hasReflowAncestor does not need to call Visits() at runtime.
@@ -519,7 +582,8 @@ func isReflowKind(k shimast.Kind) bool {
519
582
  shimast.KindNewExpression,
520
583
  shimast.KindNamedImports,
521
584
  shimast.KindNamedExports,
522
- shimast.KindImportDeclaration:
585
+ shimast.KindImportDeclaration,
586
+ shimast.KindConditionalExpression:
523
587
  return true
524
588
  }
525
589
  return false
@@ -528,7 +592,7 @@ func isReflowKind(k shimast.Kind) bool {
528
592
  // hasNonChildComments scans the byte range [start, end) and returns true
529
593
  // if any `//` or `/*` lives outside the union of `node`'s direct child
530
594
  // byte ranges. The rule uses this to abstain from reflows that would
531
- // drop inter-child comments the v1 list printers join children with
595
+ // drop inter-child comments, the v1 list printers join children with
532
596
  // a fresh separator that has no slot for trivia between them.
533
597
  //
534
598
  // The scan is byte-level for simplicity: a TS scanner would also work
@@ -537,7 +601,7 @@ func isReflowKind(k shimast.Kind) bool {
537
601
  // are children, so `"//"` inside them never reaches the comment
538
602
  // check). The residual conservative case is comment-shaped bytes
539
603
  // inside an inter-child gap that the TS grammar would never tokenize
540
- // as a comment effectively nil for valid TypeScript source.
604
+ // as a comment, effectively nil for valid TypeScript source.
541
605
  //
542
606
  // `format/sort-imports` chose the opposite path on a similar shape:
543
607
  // it actively preserves inter-specifier comments by walking the
@@ -558,18 +622,36 @@ func hasNonChildComments(node *shimast.Node, src string, start, end int) bool {
558
622
  // child.Pos() points at the start of leading trivia, which
559
623
  // can include comments belonging to the sibling boundary.
560
624
  // Trim past trivia so the inChild check only covers the
561
- // child's actual token bytes comments between siblings
625
+ // child's actual token bytes, comments between siblings
562
626
  // then surface to the scanner below.
563
627
  children = append(children, span{shimscanner.SkipTrivia(src, child.Pos()), child.End()})
564
628
  return false
565
629
  })
630
+ // inChild reports whether offset `i` lies inside any child's [pos, end)
631
+ // token range. The children are appended in source order (ForEachChild
632
+ // walks the AST in source order) with non-overlapping ranges, so a binary
633
+ // search for the last child whose `pos <= i` answers the query in O(log n)
634
+ // instead of the former O(n) per-byte linear scan. The boolean returned is
635
+ // identical to the linear scan for every offset; this is a pure speedup of
636
+ // the predicate, it changes no formatting output.
566
637
  inChild := func(i int) bool {
567
- for _, c := range children {
568
- if i >= c.pos && i < c.end {
569
- return true
638
+ lo, hi := 0, len(children)
639
+ for lo < hi {
640
+ mid := (lo + hi) / 2
641
+ if children[mid].pos <= i {
642
+ lo = mid + 1
643
+ } else {
644
+ hi = mid
570
645
  }
571
646
  }
572
- return false
647
+ // lo is the count of children with pos <= i; the candidate is the one
648
+ // just before it. A non-overlapping, source-ordered child list means at
649
+ // most this single candidate can contain `i`.
650
+ if lo == 0 {
651
+ return false
652
+ }
653
+ c := children[lo-1]
654
+ return i >= c.pos && i < c.end
573
655
  }
574
656
  for i := start; i < end-1 && i < len(src)-1; i++ {
575
657
  if inChild(i) {
@@ -582,6 +664,40 @@ func hasNonChildComments(node *shimast.Node, src string, start, end int) bool {
582
664
  return false
583
665
  }
584
666
 
667
+ // isSingleSpecifierNamedClause reports whether `node` is a named import/export
668
+ // brace clause (or an import declaration whose binding is one) carrying exactly
669
+ // one specifier. Prettier never breaks such a clause; the print-width rule
670
+ // abstains so a one-name `{ X }` survives a `from "long-path"` overflow inline.
671
+ func isSingleSpecifierNamedClause(node *shimast.Node) bool {
672
+ switch node.Kind {
673
+ case shimast.KindNamedImports:
674
+ ni := node.AsNamedImports()
675
+ return ni != nil && ni.Elements != nil && len(ni.Elements.Nodes) == 1
676
+ case shimast.KindNamedExports:
677
+ ne := node.AsNamedExports()
678
+ return ne != nil && ne.Elements != nil && len(ne.Elements.Nodes) == 1
679
+ case shimast.KindImportDeclaration:
680
+ imp := node.AsImportDeclaration()
681
+ if imp == nil || imp.ImportClause == nil {
682
+ return false
683
+ }
684
+ clause := imp.ImportClause.AsImportClause()
685
+ if clause == nil || clause.NamedBindings == nil ||
686
+ clause.NamedBindings.Kind != shimast.KindNamedImports {
687
+ return false
688
+ }
689
+ // A default binding alongside the named clause (`import D, { X } from …`)
690
+ // makes Prettier break the brace even for a single specifier, so the
691
+ // abstain applies only to a bare `import { X } from …`.
692
+ if clause.Name() != nil {
693
+ return false
694
+ }
695
+ ni := clause.NamedBindings.AsNamedImports()
696
+ return ni != nil && ni.Elements != nil && len(ni.Elements.Nodes) == 1
697
+ }
698
+ return false
699
+ }
700
+
585
701
  func init() {
586
702
  Register(formatPrintWidth{})
587
703
  }
@@ -0,0 +1,169 @@
1
+ package linthost
2
+
3
+ import (
4
+ shimast "github.com/microsoft/typescript-go/shim/ast"
5
+ shimscanner "github.com/microsoft/typescript-go/shim/scanner"
6
+ )
7
+
8
+ // formatQuoteProps normalizes quoting of object-literal property keys,
9
+ // mirroring Prettier's `quoteProps`:
10
+ //
11
+ // - "as-needed" (default): drop the quotes from a string key that is a
12
+ // valid identifier and not a numeric-looking key. `{ "foo": 1 }` becomes
13
+ // `{ foo: 1 }`; `{ "bar-baz": 1 }` and `{ "123": 1 }` keep their quotes.
14
+ // - "consistent": if ANY key in the object needs quotes, leave the object
15
+ // alone; otherwise unquote every removable key.
16
+ // - "preserve": never change quoting.
17
+ //
18
+ // Only quoted keys the rule can SAFELY unquote are ever touched: a string
19
+ // whose content is a plain ASCII identifier (letters, `_`, `$`, digits after
20
+ // the first) with no escapes. A key with escapes, unicode, a leading digit
21
+ // (numeric-looking), or any non-identifier byte is left quoted in every
22
+ // mode, so the rule can never produce an invalid key. Idempotent.
23
+ type formatQuoteProps struct{}
24
+
25
+ type formatQuotePropsOptions struct {
26
+ Mode string `json:"mode"`
27
+ }
28
+
29
+ func (formatQuoteProps) Name() string { return "format/quote-props" }
30
+ func (formatQuoteProps) IsFormat() bool { return true }
31
+
32
+ func (formatQuoteProps) Visits() []shimast.Kind {
33
+ return []shimast.Kind{shimast.KindObjectLiteralExpression}
34
+ }
35
+
36
+ func (formatQuoteProps) Check(ctx *Context, node *shimast.Node) {
37
+ if ctx == nil || ctx.File == nil || node == nil {
38
+ return
39
+ }
40
+ obj := node.AsObjectLiteralExpression()
41
+ if obj == nil || obj.Properties == nil {
42
+ return
43
+ }
44
+ var opts formatQuotePropsOptions
45
+ _ = ctx.DecodeOptions(&opts)
46
+ mode := opts.Mode
47
+ switch mode {
48
+ case "consistent", "preserve", "as-needed":
49
+ default:
50
+ mode = "as-needed"
51
+ }
52
+ if mode == "preserve" {
53
+ return
54
+ }
55
+
56
+ src := ctx.File.Text()
57
+
58
+ // removable collects each quoted key that is safely unquotable, paired
59
+ // with its bare identifier. anyMustStayQuoted records whether some quoted
60
+ // key cannot be unquoted (drives "consistent").
61
+ type removableKey struct {
62
+ start, end int
63
+ ident string
64
+ }
65
+ var removable []removableKey
66
+ anyMustStayQuoted := false
67
+
68
+ for _, prop := range obj.Properties.Nodes {
69
+ if prop == nil {
70
+ continue
71
+ }
72
+ name := propertyKeyName(prop)
73
+ if name == nil || name.Kind != shimast.KindStringLiteral {
74
+ continue
75
+ }
76
+ ks := shimscanner.SkipTrivia(src, name.Pos())
77
+ ke := name.End()
78
+ if ks < 0 || ke <= ks || ke > len(src) {
79
+ return // give up on the whole object rather than risk a partial edit
80
+ }
81
+ ident := unquotableIdentifier(src[ks:ke])
82
+ if ident == "" {
83
+ anyMustStayQuoted = true
84
+ continue
85
+ }
86
+ removable = append(removable, removableKey{start: ks, end: ke, ident: ident})
87
+ }
88
+
89
+ if len(removable) == 0 {
90
+ return
91
+ }
92
+ // In "consistent" mode, if any key must stay quoted, the whole object is
93
+ // kept quoted, so unquote nothing.
94
+ if mode == "consistent" && anyMustStayQuoted {
95
+ return
96
+ }
97
+
98
+ var edits []TextEdit
99
+ for _, k := range removable {
100
+ edits = append(edits, TextEdit{Pos: k.start, End: k.end, Text: k.ident})
101
+ }
102
+ ctx.ReportRangeFix(
103
+ edits[0].Pos,
104
+ edits[0].End,
105
+ "Object property key does not need quotes.",
106
+ edits...,
107
+ )
108
+ }
109
+
110
+ // propertyKeyName returns the static name node of an object-literal property
111
+ // (assignment, method, or accessor), or nil when it has no static name.
112
+ func propertyKeyName(prop *shimast.Node) *shimast.Node {
113
+ switch prop.Kind {
114
+ case shimast.KindPropertyAssignment:
115
+ if p := prop.AsPropertyAssignment(); p != nil {
116
+ return p.Name()
117
+ }
118
+ case shimast.KindMethodDeclaration,
119
+ shimast.KindGetAccessor,
120
+ shimast.KindSetAccessor:
121
+ return prop.Name()
122
+ }
123
+ return nil
124
+ }
125
+
126
+ // unquotableIdentifier returns the bare identifier a quoted string key can
127
+ // safely become, or "" when the key must stay quoted. `raw` includes the
128
+ // surrounding quotes. A key is unquotable only when its content is a plain
129
+ // ASCII identifier with no escapes, conservative on purpose: a
130
+ // numeric-looking key (`"123"`), a unicode key, an escaped key, or anything
131
+ // with a non-identifier byte stays quoted, matching Prettier's never
132
+ // unquoting `"123"` and guaranteeing the result is a valid bare key.
133
+ func unquotableIdentifier(raw string) string {
134
+ if len(raw) < 2 {
135
+ return ""
136
+ }
137
+ q := raw[0]
138
+ if (q != '"' && q != '\'') || raw[len(raw)-1] != q {
139
+ return ""
140
+ }
141
+ inner := raw[1 : len(raw)-1]
142
+ if len(inner) == 0 {
143
+ return ""
144
+ }
145
+ if inner == "__proto__" {
146
+ // A bare `__proto__:` key in an object literal is the spec-special
147
+ // prototype setter (sets [[Prototype]]), whereas a quoted `"__proto__"`
148
+ // key is an ordinary own data property. Unquoting would change runtime
149
+ // semantics, so keep it quoted (Prettier does the same).
150
+ return ""
151
+ }
152
+ for i := 0; i < len(inner); i++ {
153
+ c := inner[i]
154
+ isLetter := (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$'
155
+ isDigit := c >= '0' && c <= '9'
156
+ if i == 0 {
157
+ if !isLetter {
158
+ return "" // leading digit (numeric-looking) or other byte: keep quoted
159
+ }
160
+ } else if !isLetter && !isDigit {
161
+ return ""
162
+ }
163
+ }
164
+ return inner
165
+ }
166
+
167
+ func init() {
168
+ Register(formatQuoteProps{})
169
+ }