@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
@@ -7,64 +7,64 @@
7
7
  package linthost
8
8
 
9
9
  import (
10
- shimast "github.com/microsoft/typescript-go/shim/ast"
10
+ shimast "github.com/microsoft/typescript-go/shim/ast"
11
11
  )
12
12
 
13
13
  type curly struct{}
14
14
 
15
15
  func (curly) Name() string { return "curly" }
16
16
  func (curly) Visits() []shimast.Kind {
17
- return []shimast.Kind{
18
- shimast.KindIfStatement,
19
- shimast.KindWhileStatement,
20
- shimast.KindDoStatement,
21
- shimast.KindForStatement,
22
- shimast.KindForInStatement,
23
- shimast.KindForOfStatement,
24
- }
17
+ return []shimast.Kind{
18
+ shimast.KindIfStatement,
19
+ shimast.KindWhileStatement,
20
+ shimast.KindDoStatement,
21
+ shimast.KindForStatement,
22
+ shimast.KindForInStatement,
23
+ shimast.KindForOfStatement,
24
+ }
25
25
  }
26
26
  func (curly) Check(ctx *Context, node *shimast.Node) {
27
- switch node.Kind {
28
- case shimast.KindIfStatement:
29
- stmt := node.AsIfStatement()
30
- if stmt == nil {
31
- return
32
- }
33
- curlyReportNonBlock(ctx, stmt.ThenStatement, "if")
34
- // Allow `else if (...)` — the else branch carries another
35
- // IfStatement that is itself visited and reported separately.
36
- if stmt.ElseStatement != nil && stmt.ElseStatement.Kind != shimast.KindIfStatement {
37
- curlyReportNonBlock(ctx, stmt.ElseStatement, "else")
38
- }
39
- case shimast.KindWhileStatement:
40
- if stmt := node.AsWhileStatement(); stmt != nil {
41
- curlyReportNonBlock(ctx, stmt.Statement, "while")
42
- }
43
- case shimast.KindDoStatement:
44
- if stmt := node.AsDoStatement(); stmt != nil {
45
- curlyReportNonBlock(ctx, stmt.Statement, "do")
46
- }
47
- case shimast.KindForStatement:
48
- if stmt := node.AsForStatement(); stmt != nil {
49
- curlyReportNonBlock(ctx, stmt.Statement, "for")
50
- }
51
- case shimast.KindForInStatement, shimast.KindForOfStatement:
52
- if stmt := node.AsForInOrOfStatement(); stmt != nil {
53
- curlyReportNonBlock(ctx, stmt.Statement, "for")
54
- }
55
- }
27
+ switch node.Kind {
28
+ case shimast.KindIfStatement:
29
+ stmt := node.AsIfStatement()
30
+ if stmt == nil {
31
+ return
32
+ }
33
+ curlyReportNonBlock(ctx, stmt.ThenStatement, "if")
34
+ // Allow `else if (...)` — the else branch carries another
35
+ // IfStatement that is itself visited and reported separately.
36
+ if stmt.ElseStatement != nil && stmt.ElseStatement.Kind != shimast.KindIfStatement {
37
+ curlyReportNonBlock(ctx, stmt.ElseStatement, "else")
38
+ }
39
+ case shimast.KindWhileStatement:
40
+ if stmt := node.AsWhileStatement(); stmt != nil {
41
+ curlyReportNonBlock(ctx, stmt.Statement, "while")
42
+ }
43
+ case shimast.KindDoStatement:
44
+ if stmt := node.AsDoStatement(); stmt != nil {
45
+ curlyReportNonBlock(ctx, stmt.Statement, "do")
46
+ }
47
+ case shimast.KindForStatement:
48
+ if stmt := node.AsForStatement(); stmt != nil {
49
+ curlyReportNonBlock(ctx, stmt.Statement, "for")
50
+ }
51
+ case shimast.KindForInStatement, shimast.KindForOfStatement:
52
+ if stmt := node.AsForInOrOfStatement(); stmt != nil {
53
+ curlyReportNonBlock(ctx, stmt.Statement, "for")
54
+ }
55
+ }
56
56
  }
57
57
 
58
58
  // curlyReportNonBlock reports `body` when it is a single bare statement
59
59
  // instead of a Block. A nil body means there is nothing to wrap (e.g. an
60
60
  // IfStatement with no else clause), so it is silently skipped.
61
61
  func curlyReportNonBlock(ctx *Context, body *shimast.Node, keyword string) {
62
- if body == nil || body.Kind == shimast.KindBlock {
63
- return
64
- }
65
- ctx.Report(body, "Expected { after '"+keyword+"' condition.")
62
+ if body == nil || body.Kind == shimast.KindBlock {
63
+ return
64
+ }
65
+ ctx.Report(body, "Expected { after '"+keyword+"' condition.")
66
66
  }
67
67
 
68
68
  func init() {
69
- Register(curly{})
69
+ Register(curly{})
70
70
  }
@@ -18,22 +18,22 @@ type defaultCase struct{}
18
18
  func (defaultCase) Name() string { return "default-case" }
19
19
  func (defaultCase) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSwitchStatement} }
20
20
  func (defaultCase) Check(ctx *Context, node *shimast.Node) {
21
- sw := node.AsSwitchStatement()
22
- if sw == nil || sw.CaseBlock == nil {
23
- return
24
- }
25
- block := sw.CaseBlock.AsCaseBlock()
26
- if block == nil || block.Clauses == nil {
27
- return
28
- }
29
- for _, clause := range block.Clauses.Nodes {
30
- if clause != nil && clause.Kind == shimast.KindDefaultClause {
31
- return
32
- }
33
- }
34
- ctx.Report(node, "Expected a default case.")
21
+ sw := node.AsSwitchStatement()
22
+ if sw == nil || sw.CaseBlock == nil {
23
+ return
24
+ }
25
+ block := sw.CaseBlock.AsCaseBlock()
26
+ if block == nil || block.Clauses == nil {
27
+ return
28
+ }
29
+ for _, clause := range block.Clauses.Nodes {
30
+ if clause != nil && clause.Kind == shimast.KindDefaultClause {
31
+ return
32
+ }
33
+ }
34
+ ctx.Report(node, "Expected a default case.")
35
35
  }
36
36
 
37
37
  func init() {
38
- Register(defaultCase{})
38
+ Register(defaultCase{})
39
39
  }
@@ -16,31 +16,31 @@ type defaultCaseLast struct{}
16
16
 
17
17
  func (defaultCaseLast) Name() string { return "default-case-last" }
18
18
  func (defaultCaseLast) Visits() []shimast.Kind {
19
- return []shimast.Kind{shimast.KindSwitchStatement}
19
+ return []shimast.Kind{shimast.KindSwitchStatement}
20
20
  }
21
21
  func (defaultCaseLast) Check(ctx *Context, node *shimast.Node) {
22
- sw := node.AsSwitchStatement()
23
- if sw == nil || sw.CaseBlock == nil {
24
- return
25
- }
26
- block := sw.CaseBlock.AsCaseBlock()
27
- if block == nil || block.Clauses == nil {
28
- return
29
- }
30
- clauses := block.Clauses.Nodes
31
- if len(clauses) == 0 {
32
- return
33
- }
34
- last := clauses[len(clauses)-1]
35
- for i := 0; i < len(clauses)-1; i++ {
36
- clause := clauses[i]
37
- if clause != nil && clause.Kind == shimast.KindDefaultClause {
38
- _ = last
39
- ctx.Report(clause, "Default clause should be the last clause.")
40
- }
41
- }
22
+ sw := node.AsSwitchStatement()
23
+ if sw == nil || sw.CaseBlock == nil {
24
+ return
25
+ }
26
+ block := sw.CaseBlock.AsCaseBlock()
27
+ if block == nil || block.Clauses == nil {
28
+ return
29
+ }
30
+ clauses := block.Clauses.Nodes
31
+ if len(clauses) == 0 {
32
+ return
33
+ }
34
+ last := clauses[len(clauses)-1]
35
+ for i := 0; i < len(clauses)-1; i++ {
36
+ clause := clauses[i]
37
+ if clause != nil && clause.Kind == shimast.KindDefaultClause {
38
+ _ = last
39
+ ctx.Report(clause, "Default clause should be the last clause.")
40
+ }
41
+ }
42
42
  }
43
43
 
44
44
  func init() {
45
- Register(defaultCaseLast{})
45
+ Register(defaultCaseLast{})
46
46
  }
@@ -0,0 +1,167 @@
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
+ // formatArrowParens normalizes the parentheses around a single-parameter
9
+ // arrow function, mirroring Prettier's `arrowParens`:
10
+ //
11
+ // - prefer "always" (Prettier default): a bare `x => x` gains parens,
12
+ // becoming `(x) => x`.
13
+ // - prefer "avoid": `(x) => x` drops them, becoming `x => x`.
14
+ //
15
+ // Only a single parameter that is a plain identifier with no type
16
+ // annotation, default, rest, optional `?`, or modifier is affected, every
17
+ // other shape (`(x: T)`, `({ x })`, `(...x)`, `(x = 1)`, `(x?)`, `(x, y)`,
18
+ // `()`) keeps its parentheses in both modes, exactly as Prettier does.
19
+ // `async x => x` is handled too: the modifier sits before the parameter, so
20
+ // the parameter-name span is unaffected.
21
+ //
22
+ // The rule rewrites only the parameter span (`x` <-> `(x)`), never the body
23
+ // or the `=>`, so a chained arrow `a => b => …` has each eligible arm
24
+ // normalized independently. Idempotent: a parameter already in the
25
+ // preferred shape compares equal and emits nothing.
26
+ type formatArrowParens struct{}
27
+
28
+ type formatArrowParensOptions struct {
29
+ Prefer string `json:"prefer"`
30
+ }
31
+
32
+ func (formatArrowParens) Name() string { return "format/arrow-parens" }
33
+ func (formatArrowParens) IsFormat() bool { return true }
34
+
35
+ func (formatArrowParens) Visits() []shimast.Kind {
36
+ return []shimast.Kind{shimast.KindArrowFunction}
37
+ }
38
+
39
+ func (formatArrowParens) Check(ctx *Context, node *shimast.Node) {
40
+ if ctx == nil || ctx.File == nil || node == nil {
41
+ return
42
+ }
43
+ arrow := node.AsArrowFunction()
44
+ if arrow == nil || arrow.Parameters == nil {
45
+ return
46
+ }
47
+ params := arrow.Parameters.Nodes
48
+ if len(params) != 1 || params[0] == nil {
49
+ return
50
+ }
51
+ param := params[0]
52
+ if !isBareIdentifierParam(param) {
53
+ return
54
+ }
55
+ // A type-parameter list (`<T>(x) => …`) forces the parameter parens in
56
+ // both modes (a bare `<T>x =>` is not valid), so leave such an arrow
57
+ // alone, its parens are mandatory, not stylistic.
58
+ if arrow.TypeParameters != nil && len(arrow.TypeParameters.Nodes) > 0 {
59
+ return
60
+ }
61
+ // A return-type annotation (`(x): T => …`) also requires the parens.
62
+ if arrow.Type != nil {
63
+ return
64
+ }
65
+
66
+ var opts formatArrowParensOptions
67
+ _ = ctx.DecodeOptions(&opts)
68
+ prefer := opts.Prefer
69
+ if prefer != "avoid" {
70
+ prefer = "always"
71
+ }
72
+
73
+ src := ctx.File.Text()
74
+ nameStart := shimscanner.SkipTrivia(src, param.Pos())
75
+ nameEnd := param.End()
76
+ if nameStart < 0 || nameEnd <= nameStart || nameEnd > len(src) {
77
+ return
78
+ }
79
+
80
+ // Is the parameter already wrapped in `(` … `)`? Scan over whitespace on
81
+ // each side; the sole parameter of an arrow is delimited by the
82
+ // parameter-list parens when present.
83
+ openParen := scanBackForByte(src, nameStart, '(')
84
+ closeParen := scanForwardForByte(src, nameEnd, ')')
85
+ wrapped := openParen >= 0 && closeParen >= 0
86
+
87
+ switch prefer {
88
+ case "avoid":
89
+ if !wrapped {
90
+ return // already bare
91
+ }
92
+ // Replace `( … name … )` with just the name.
93
+ ctx.ReportRangeFix(
94
+ openParen,
95
+ closeParen+1,
96
+ "Single-parameter arrow should omit parentheses.",
97
+ TextEdit{Pos: openParen, End: closeParen + 1, Text: src[nameStart:nameEnd]},
98
+ )
99
+ default: // always
100
+ if wrapped {
101
+ return // already parenthesized
102
+ }
103
+ ctx.ReportRangeFix(
104
+ nameStart,
105
+ nameEnd,
106
+ "Single-parameter arrow should keep parentheses.",
107
+ TextEdit{Pos: nameStart, End: nameEnd, Text: "(" + src[nameStart:nameEnd] + ")"},
108
+ )
109
+ }
110
+ }
111
+
112
+ // isBareIdentifierParam reports whether `param` is a plain identifier
113
+ // parameter: no rest `...`, no optional `?`, no type annotation, no default,
114
+ // no modifier (a parameter property), and an identifier name. Only such a
115
+ // parameter is eligible for arrow-paren normalization; every richer shape
116
+ // keeps its parentheses in both Prettier modes.
117
+ func isBareIdentifierParam(param *shimast.Node) bool {
118
+ decl := param.AsParameterDeclaration()
119
+ if decl == nil {
120
+ return false
121
+ }
122
+ if decl.DotDotDotToken != nil || decl.QuestionToken != nil ||
123
+ decl.Type != nil || decl.Initializer != nil {
124
+ return false
125
+ }
126
+ if mods := param.Modifiers(); mods != nil && len(mods.Nodes) > 0 {
127
+ return false
128
+ }
129
+ name := param.Name()
130
+ return name != nil && name.Kind == shimast.KindIdentifier
131
+ }
132
+
133
+ // scanBackForByte returns the offset of the nearest `target` byte at or
134
+ // before `from`-1, scanning over whitespace only; -1 if a non-whitespace,
135
+ // non-target byte is reached first.
136
+ func scanBackForByte(src string, from int, target byte) int {
137
+ for i := from - 1; i >= 0; i-- {
138
+ c := src[i]
139
+ if c == target {
140
+ return i
141
+ }
142
+ if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
143
+ return -1
144
+ }
145
+ }
146
+ return -1
147
+ }
148
+
149
+ // scanForwardForByte returns the offset of the nearest `target` byte at or
150
+ // after `from`, scanning over whitespace only; -1 if a non-whitespace,
151
+ // non-target byte is reached first.
152
+ func scanForwardForByte(src string, from int, target byte) int {
153
+ for i := from; i < len(src); i++ {
154
+ c := src[i]
155
+ if c == target {
156
+ return i
157
+ }
158
+ if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
159
+ return -1
160
+ }
161
+ }
162
+ return -1
163
+ }
164
+
165
+ func init() {
166
+ Register(formatArrowParens{})
167
+ }
@@ -0,0 +1,117 @@
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
+ // formatBracketSpacing normalizes the inner padding of single-line brace
9
+ // pairs, mirroring Prettier's `bracketSpacing`:
10
+ //
11
+ // - prefer true (Prettier default): one space just inside the braces,
12
+ // `{ x: 1 }`, `{ a, b }`, `import { foo } from "m"`.
13
+ // - prefer false: no inner space, `{x: 1}`, `{a, b}`, `import {foo}`.
14
+ //
15
+ // It applies to object literals, object binding patterns (destructuring),
16
+ // named imports/exports, and type literals, the brace kinds Prettier's
17
+ // bracketSpacing governs. Block, class, interface, enum, and module braces
18
+ // are NOT affected (their layout is owned by the indentation rules).
19
+ //
20
+ // The rule touches only a brace pair that opens and closes on the SAME
21
+ // line: a multi-line container's interior is the indentation rules' surface,
22
+ // and an empty `{}` has no interior to pad. It rewrites just the whitespace
23
+ // run immediately inside each brace, so it never disturbs the contents.
24
+ // Idempotent: a pair already in the preferred shape compares equal.
25
+ type formatBracketSpacing struct{}
26
+
27
+ type formatBracketSpacingOptions struct {
28
+ Spacing *bool `json:"spacing"`
29
+ }
30
+
31
+ func (formatBracketSpacing) Name() string { return "format/bracket-spacing" }
32
+ func (formatBracketSpacing) IsFormat() bool { return true }
33
+
34
+ func (formatBracketSpacing) Visits() []shimast.Kind {
35
+ return []shimast.Kind{
36
+ shimast.KindObjectLiteralExpression,
37
+ shimast.KindObjectBindingPattern,
38
+ shimast.KindNamedImports,
39
+ shimast.KindNamedExports,
40
+ shimast.KindTypeLiteral,
41
+ }
42
+ }
43
+
44
+ func (formatBracketSpacing) Check(ctx *Context, node *shimast.Node) {
45
+ if ctx == nil || ctx.File == nil || node == nil {
46
+ return
47
+ }
48
+ var opts formatBracketSpacingOptions
49
+ _ = ctx.DecodeOptions(&opts)
50
+ spacing := true
51
+ if opts.Spacing != nil {
52
+ spacing = *opts.Spacing
53
+ }
54
+
55
+ src := ctx.File.Text()
56
+ start := shimscanner.SkipTrivia(src, node.Pos())
57
+ end := node.End()
58
+ if start < 0 || end <= start || end > len(src) {
59
+ return
60
+ }
61
+ // The node's first byte should be `{` and last `}`; a parenthesized or
62
+ // otherwise-wrapped form is out of scope.
63
+ if src[start] != '{' || src[end-1] != '}' {
64
+ return
65
+ }
66
+ inner := src[start+1 : end-1]
67
+ if len(inner) == 0 {
68
+ return // empty `{}`, nothing to pad
69
+ }
70
+ // Multi-line: the interior belongs to the indentation rules.
71
+ for i := 0; i < len(inner); i++ {
72
+ if inner[i] == '\n' || inner[i] == '\r' {
73
+ return
74
+ }
75
+ }
76
+ // Only-whitespace interior (`{ }`) is treated as empty: collapse to the
77
+ // canonical empty form rather than padding nothing.
78
+ trimmed := trimASCIISpace(inner)
79
+ if len(trimmed) == 0 {
80
+ return
81
+ }
82
+
83
+ // Compute the desired interior: exactly one leading+trailing space when
84
+ // spacing is on, none when off.
85
+ var want string
86
+ if spacing {
87
+ want = " " + trimmed + " "
88
+ } else {
89
+ want = trimmed
90
+ }
91
+ if inner == want {
92
+ return
93
+ }
94
+ ctx.ReportRangeFix(
95
+ start+1,
96
+ end-1,
97
+ "Normalize brace spacing to match bracketSpacing.",
98
+ TextEdit{Pos: start + 1, End: end - 1, Text: want},
99
+ )
100
+ }
101
+
102
+ // trimASCIISpace strips leading and trailing spaces and tabs from s.
103
+ func trimASCIISpace(s string) string {
104
+ i := 0
105
+ for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
106
+ i++
107
+ }
108
+ j := len(s)
109
+ for j > i && (s[j-1] == ' ' || s[j-1] == '\t') {
110
+ j--
111
+ }
112
+ return s[i:j]
113
+ }
114
+
115
+ func init() {
116
+ Register(formatBracketSpacing{})
117
+ }
@@ -0,0 +1,172 @@
1
+ package linthost
2
+
3
+ import (
4
+ "strings"
5
+
6
+ shimast "github.com/microsoft/typescript-go/shim/ast"
7
+ shimscanner "github.com/microsoft/typescript-go/shim/scanner"
8
+ )
9
+
10
+ // formatClauseJoin joins a single non-block statement that sits on its
11
+ // own line back onto its control-flow header, mirroring Prettier:
12
+ //
13
+ // if (a)
14
+ // b();
15
+ //
16
+ // becomes `if (a) b();` when the joined line fits printWidth. The same
17
+ // applies to `for`, `for-in`, `for-of`, and `while` headers. A braced
18
+ // body, a body that already shares the header line, a multi-line body,
19
+ // or a join that would overflow printWidth is left untouched.
20
+ //
21
+ // The rule only ever rewrites the whitespace gap between a header's
22
+ // closing `)` and the controlled statement, so its edits never overlap
23
+ // `format/indent` (leading whitespace of a line) or `format/print-width`
24
+ // (interior reflow). Idempotent: once joined the gap holds no newline
25
+ // and the rule abstains.
26
+ type formatClauseJoin struct{}
27
+
28
+ // formatClauseJoinOptions mirrors the printWidth/indent keys the rule
29
+ // needs to decide whether the joined line fits. The config layer mirrors
30
+ // `format.printWidth`/`tabWidth`/`useTabs` into this rule's option blob
31
+ // (see expandFormatBlock).
32
+ type formatClauseJoinOptions struct {
33
+ PrintWidth *int `json:"printWidth"`
34
+ TabWidth *int `json:"tabWidth"`
35
+ UseTabs *bool `json:"useTabs"`
36
+ EndOfLine *string `json:"endOfLine"`
37
+ }
38
+
39
+ func (formatClauseJoin) Name() string { return "format/clause-join" }
40
+ func (formatClauseJoin) IsFormat() bool { return true }
41
+
42
+ func (formatClauseJoin) Visits() []shimast.Kind {
43
+ return []shimast.Kind{
44
+ shimast.KindIfStatement,
45
+ shimast.KindWhileStatement,
46
+ shimast.KindForStatement,
47
+ shimast.KindForInStatement,
48
+ shimast.KindForOfStatement,
49
+ }
50
+ }
51
+
52
+ func (formatClauseJoin) Check(ctx *Context, node *shimast.Node) {
53
+ if ctx == nil || ctx.File == nil || node == nil {
54
+ return
55
+ }
56
+ var opts formatClauseJoinOptions
57
+ _ = ctx.DecodeOptions(&opts)
58
+ printWidth := 80
59
+ if opts.PrintWidth != nil && *opts.PrintWidth > 0 {
60
+ printWidth = *opts.PrintWidth
61
+ }
62
+ tabWidth := 2
63
+ if opts.TabWidth != nil && *opts.TabWidth > 0 {
64
+ tabWidth = *opts.TabWidth
65
+ }
66
+ joinClauseBody(ctx, ctx.File.Text(), node, clauseControlledBody(node), printWidth, tabWidth)
67
+ }
68
+
69
+ // clauseControlledBody returns the single controlled statement of a
70
+ // control-flow header, the `then` branch for `if`, the loop body for
71
+ // the iteration statements. The `else` branch is intentionally excluded:
72
+ // its body is anchored after the `else` keyword rather than a `)`, so it
73
+ // does not share this rule's `)`-anchored join shape.
74
+ func clauseControlledBody(node *shimast.Node) *shimast.Node {
75
+ switch node.Kind {
76
+ case shimast.KindIfStatement:
77
+ return node.AsIfStatement().ThenStatement
78
+ case shimast.KindWhileStatement:
79
+ return node.AsWhileStatement().Statement
80
+ case shimast.KindForStatement:
81
+ return node.AsForStatement().Statement
82
+ case shimast.KindForInStatement, shimast.KindForOfStatement:
83
+ return node.AsForInOrOfStatement().Statement
84
+ }
85
+ return nil
86
+ }
87
+
88
+ func joinClauseBody(
89
+ ctx *Context,
90
+ src string,
91
+ node *shimast.Node,
92
+ body *shimast.Node,
93
+ printWidth int,
94
+ tabWidth int,
95
+ ) {
96
+ if body == nil || body.Kind == shimast.KindBlock {
97
+ return
98
+ }
99
+ bodyStart := shimscanner.SkipTrivia(src, body.Pos())
100
+ bodyEnd := body.End()
101
+ if bodyStart < 0 || bodyEnd < bodyStart || bodyEnd > len(src) {
102
+ return
103
+ }
104
+ // The gap is the whitespace run immediately before the body. Walk back
105
+ // over horizontal whitespace and newlines; the byte before it must be
106
+ // the header's closing `)` so a comment between header and body (which
107
+ // SkipTrivia would have stepped over) can never be swallowed.
108
+ gapStart := bodyStart
109
+ for gapStart > 0 && isClauseGapByte(src[gapStart-1]) {
110
+ gapStart--
111
+ }
112
+ if gapStart == 0 || src[gapStart-1] != ')' {
113
+ return
114
+ }
115
+ gap := src[gapStart:bodyStart]
116
+ if !strings.Contains(gap, "\n") {
117
+ return // body already shares the header line
118
+ }
119
+ // The header and the body must each be single-line; a multi-line body
120
+ // (e.g. a nested clause not yet joined) waits for the cascade to settle
121
+ // its inner join first.
122
+ headerStart := shimscanner.SkipTrivia(src, node.Pos())
123
+ if headerStart < 0 || headerStart > gapStart {
124
+ return
125
+ }
126
+ headerLineStart := lineStartOffset(src, headerStart)
127
+ if strings.ContainsRune(src[headerLineStart:gapStart], '\n') {
128
+ return
129
+ }
130
+ if strings.ContainsRune(src[bodyStart:bodyEnd], '\n') {
131
+ return
132
+ }
133
+ joined := visualWidth(src[headerLineStart:gapStart], tabWidth) + 1 +
134
+ visualWidth(src[bodyStart:bodyEnd], tabWidth)
135
+ if joined > printWidth {
136
+ return
137
+ }
138
+ ctx.ReportRangeFix(
139
+ gapStart,
140
+ bodyStart,
141
+ "Single-statement clause body should join its header line.",
142
+ TextEdit{Pos: gapStart, End: bodyStart, Text: " "},
143
+ )
144
+ }
145
+
146
+ // isClauseGapByte reports whether `c` is whitespace that may appear in
147
+ // the gap between a clause header and its controlled statement.
148
+ func isClauseGapByte(c byte) bool {
149
+ return c == ' ' || c == '\t' || c == '\r' || c == '\n'
150
+ }
151
+
152
+ // visualWidth returns the column width of `s`, expanding tab characters
153
+ // to `tabWidth` columns. Non-tab bytes count as one column, which is
154
+ // exact for the ASCII that dominates control-flow headers and the common
155
+ // case; it can under/over-count multi-byte runes, but clause bodies that
156
+ // land exactly on the printWidth boundary are rare enough that the
157
+ // approximation never changes a real join decision.
158
+ func visualWidth(s string, tabWidth int) int {
159
+ width := 0
160
+ for _, r := range s {
161
+ if r == '\t' {
162
+ width += tabWidth
163
+ continue
164
+ }
165
+ width += runeWidth(r)
166
+ }
167
+ return width
168
+ }
169
+
170
+ func init() {
171
+ Register(formatClauseJoin{})
172
+ }