@ttsc/lint 0.13.0 → 0.14.0-dev.20250530.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (498) hide show
  1. package/README.md +955 -85
  2. package/lib/defaultFormat.d.ts +15 -8
  3. package/lib/defaultFormat.js +15 -8
  4. package/lib/defaultFormat.js.map +1 -1
  5. package/lib/index.d.ts +46 -0
  6. package/lib/index.js +60 -37
  7. package/lib/index.js.map +1 -1
  8. package/lib/structures/ITtscLintConfig.d.ts +12 -6
  9. package/lib/structures/TtscLintRuleSetting.d.ts +49 -0
  10. package/lib/structures/{TtscLintRuleOptions.js → TtscLintRuleSetting.js} +1 -1
  11. package/lib/structures/TtscLintRuleSetting.js.map +1 -0
  12. package/lib/structures/format/ITtscLintFormat.d.ts +128 -0
  13. package/lib/structures/{TtscLintRuleMap.js → format/ITtscLintFormat.js} +1 -1
  14. package/lib/structures/format/ITtscLintFormat.js.map +1 -0
  15. package/lib/structures/format/ITtscLintFormatJsDoc.d.ts +17 -0
  16. package/lib/structures/{ITtscLintFormatConfig.js → format/ITtscLintFormatJsDoc.js} +1 -1
  17. package/lib/structures/format/ITtscLintFormatJsDoc.js.map +1 -0
  18. package/lib/structures/format/ITtscLintFormatSortImports.d.ts +54 -0
  19. package/lib/structures/format/ITtscLintFormatSortImports.js +3 -0
  20. package/lib/structures/format/ITtscLintFormatSortImports.js.map +1 -0
  21. package/lib/structures/format/index.d.ts +3 -0
  22. package/lib/structures/format/index.js +20 -0
  23. package/lib/structures/format/index.js.map +1 -0
  24. package/lib/structures/index.d.ts +3 -4
  25. package/lib/structures/index.js +3 -4
  26. package/lib/structures/index.js.map +1 -1
  27. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +95 -0
  28. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js +13 -0
  29. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js.map +1 -0
  30. package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +75 -0
  31. package/lib/structures/rules/ITtscLintBoundariesRules.js +3 -0
  32. package/lib/structures/rules/ITtscLintBoundariesRules.js.map +1 -0
  33. package/lib/structures/rules/ITtscLintContributorRules.d.ts +16 -0
  34. package/lib/structures/rules/ITtscLintContributorRules.js +3 -0
  35. package/lib/structures/rules/ITtscLintContributorRules.js.map +1 -0
  36. package/lib/structures/rules/ITtscLintCoreRules.d.ts +1229 -0
  37. package/lib/structures/rules/ITtscLintCoreRules.js +3 -0
  38. package/lib/structures/rules/ITtscLintCoreRules.js.map +1 -0
  39. package/lib/structures/rules/ITtscLintCypressRuleOptions.d.ts +17 -0
  40. package/lib/structures/rules/ITtscLintCypressRuleOptions.js +10 -0
  41. package/lib/structures/rules/ITtscLintCypressRuleOptions.js.map +1 -0
  42. package/lib/structures/rules/ITtscLintCypressRules.d.ts +125 -0
  43. package/lib/structures/rules/ITtscLintCypressRules.js +3 -0
  44. package/lib/structures/rules/ITtscLintCypressRules.js.map +1 -0
  45. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.d.ts +154 -0
  46. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.js +13 -0
  47. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.js.map +1 -0
  48. package/lib/structures/rules/ITtscLintFunctionalRules.d.ts +162 -0
  49. package/lib/structures/rules/ITtscLintFunctionalRules.js +3 -0
  50. package/lib/structures/rules/ITtscLintFunctionalRules.js.map +1 -0
  51. package/lib/structures/rules/ITtscLintJestRules.d.ts +190 -0
  52. package/lib/structures/rules/ITtscLintJestRules.js +3 -0
  53. package/lib/structures/rules/ITtscLintJestRules.js.map +1 -0
  54. package/lib/structures/rules/ITtscLintJsdocRules.d.ts +133 -0
  55. package/lib/structures/rules/ITtscLintJsdocRules.js +3 -0
  56. package/lib/structures/rules/ITtscLintJsdocRules.js.map +1 -0
  57. package/lib/structures/rules/ITtscLintJsxA11yRules.d.ts +294 -0
  58. package/lib/structures/rules/ITtscLintJsxA11yRules.js +3 -0
  59. package/lib/structures/rules/ITtscLintJsxA11yRules.js.map +1 -0
  60. package/lib/structures/rules/ITtscLintNextjsRules.d.ts +180 -0
  61. package/lib/structures/rules/ITtscLintNextjsRules.js +3 -0
  62. package/lib/structures/rules/ITtscLintNextjsRules.js.map +1 -0
  63. package/lib/structures/rules/ITtscLintPlaywrightRules.d.ts +312 -0
  64. package/lib/structures/rules/ITtscLintPlaywrightRules.js +3 -0
  65. package/lib/structures/rules/ITtscLintPlaywrightRules.js.map +1 -0
  66. package/lib/structures/rules/ITtscLintPromiseRules.d.ts +179 -0
  67. package/lib/structures/rules/ITtscLintPromiseRules.js +3 -0
  68. package/lib/structures/rules/ITtscLintPromiseRules.js.map +1 -0
  69. package/lib/structures/rules/ITtscLintReactPerfRuleOptions.d.ts +18 -0
  70. package/lib/structures/rules/ITtscLintReactPerfRuleOptions.js +8 -0
  71. package/lib/structures/rules/ITtscLintReactPerfRuleOptions.js.map +1 -0
  72. package/lib/structures/rules/ITtscLintReactPerfRules.d.ts +49 -0
  73. package/lib/structures/rules/ITtscLintReactPerfRules.js +3 -0
  74. package/lib/structures/rules/ITtscLintReactPerfRules.js.map +1 -0
  75. package/lib/structures/rules/ITtscLintReactRuleOptions.d.ts +37 -0
  76. package/lib/structures/rules/ITtscLintReactRuleOptions.js +11 -0
  77. package/lib/structures/rules/ITtscLintReactRuleOptions.js.map +1 -0
  78. package/lib/structures/rules/ITtscLintReactRules.d.ts +276 -0
  79. package/lib/structures/rules/ITtscLintReactRules.js +3 -0
  80. package/lib/structures/rules/ITtscLintReactRules.js.map +1 -0
  81. package/lib/structures/rules/ITtscLintRegexpRules.d.ts +182 -0
  82. package/lib/structures/rules/ITtscLintRegexpRules.js +3 -0
  83. package/lib/structures/rules/ITtscLintRegexpRules.js.map +1 -0
  84. package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +65 -0
  85. package/lib/structures/rules/ITtscLintRuleOptionsMap.js +3 -0
  86. package/lib/structures/rules/ITtscLintRuleOptionsMap.js.map +1 -0
  87. package/lib/structures/rules/ITtscLintRules.d.ts +55 -0
  88. package/lib/structures/{TtscLintRule.js → rules/ITtscLintRules.js} +1 -1
  89. package/lib/structures/rules/ITtscLintRules.js.map +1 -0
  90. package/lib/structures/rules/ITtscLintSecurityRules.d.ts +141 -0
  91. package/lib/structures/rules/ITtscLintSecurityRules.js +3 -0
  92. package/lib/structures/rules/ITtscLintSecurityRules.js.map +1 -0
  93. package/lib/structures/rules/ITtscLintSolidRules.d.ts +178 -0
  94. package/lib/structures/rules/ITtscLintSolidRules.js +3 -0
  95. package/lib/structures/rules/ITtscLintSolidRules.js.map +1 -0
  96. package/lib/structures/rules/ITtscLintStorybookRuleOptions.d.ts +20 -0
  97. package/lib/structures/rules/ITtscLintStorybookRuleOptions.js +9 -0
  98. package/lib/structures/rules/ITtscLintStorybookRuleOptions.js.map +1 -0
  99. package/lib/structures/rules/ITtscLintStorybookRules.d.ts +165 -0
  100. package/lib/structures/rules/ITtscLintStorybookRules.js +3 -0
  101. package/lib/structures/rules/ITtscLintStorybookRules.js.map +1 -0
  102. package/lib/structures/rules/ITtscLintTanstackQueryRules.d.ts +88 -0
  103. package/lib/structures/rules/ITtscLintTanstackQueryRules.js +3 -0
  104. package/lib/structures/rules/ITtscLintTanstackQueryRules.js.map +1 -0
  105. package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.d.ts +21 -0
  106. package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.js +10 -0
  107. package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.js.map +1 -0
  108. package/lib/structures/rules/ITtscLintTestingLibraryRules.d.ts +293 -0
  109. package/lib/structures/rules/ITtscLintTestingLibraryRules.js +3 -0
  110. package/lib/structures/rules/ITtscLintTestingLibraryRules.js.map +1 -0
  111. package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +1074 -0
  112. package/lib/structures/rules/ITtscLintTypeScriptRules.js +3 -0
  113. package/lib/structures/rules/ITtscLintTypeScriptRules.js.map +1 -0
  114. package/lib/structures/rules/ITtscLintUnicornRules.d.ts +997 -0
  115. package/lib/structures/rules/ITtscLintUnicornRules.js +3 -0
  116. package/lib/structures/rules/ITtscLintUnicornRules.js.map +1 -0
  117. package/lib/structures/rules/ITtscLintVitestRules.d.ts +133 -0
  118. package/lib/structures/rules/ITtscLintVitestRules.js +3 -0
  119. package/lib/structures/rules/ITtscLintVitestRules.js.map +1 -0
  120. package/lib/structures/rules/index.d.ts +31 -0
  121. package/lib/structures/rules/index.js +48 -0
  122. package/lib/structures/rules/index.js.map +1 -0
  123. package/linthost/ast_helpers.go +30 -5
  124. package/linthost/compile.go +33 -2
  125. package/linthost/config.go +118 -71
  126. package/linthost/config_format.go +270 -104
  127. package/linthost/directives.go +67 -25
  128. package/linthost/dispatch.go +14 -3
  129. package/linthost/engine.go +160 -40
  130. package/linthost/fix.go +10 -3
  131. package/linthost/flags_gen.go +17 -15
  132. package/linthost/format.go +222 -25
  133. package/linthost/format_editor_settings.go +262 -0
  134. package/linthost/host.go +40 -3
  135. package/linthost/lsp.go +898 -0
  136. package/linthost/print_dispatch.go +5 -2
  137. package/linthost/print_engine.go +5 -5
  138. package/linthost/print_nodes_call.go +149 -67
  139. package/linthost/print_nodes_function.go +27 -2
  140. package/linthost/print_nodes_list.go +40 -5
  141. package/linthost/print_nodes_object.go +1 -1
  142. package/linthost/print_nodes_ternary.go +86 -0
  143. package/linthost/rule_names.go +14 -0
  144. package/linthost/rules_arrays.go +2 -2
  145. package/linthost/rules_boundaries.go +532 -0
  146. package/linthost/rules_camelcase.go +133 -0
  147. package/linthost/rules_complexity.go +95 -0
  148. package/linthost/rules_consistent_return.go +130 -0
  149. package/linthost/rules_console.go +1 -1
  150. package/linthost/rules_core_extra.go +739 -0
  151. package/linthost/rules_curly.go +70 -0
  152. package/linthost/rules_cypress.go +608 -0
  153. package/linthost/rules_debugger.go +2 -2
  154. package/linthost/rules_default_case.go +39 -0
  155. package/linthost/rules_default_case_last.go +46 -0
  156. package/linthost/rules_dupes.go +3 -3
  157. package/linthost/rules_empty.go +3 -3
  158. package/linthost/rules_escape.go +44 -24
  159. package/linthost/rules_eval.go +2 -2
  160. package/linthost/rules_finally.go +2 -2
  161. package/linthost/rules_format_arrow_parens.go +167 -0
  162. package/linthost/rules_format_bracket_spacing.go +117 -0
  163. package/linthost/rules_format_clause_join.go +172 -0
  164. package/linthost/rules_format_declaration_header.go +595 -0
  165. package/linthost/rules_format_indent.go +372 -0
  166. package/linthost/rules_format_jsdoc.go +5 -5
  167. package/linthost/rules_format_orphan_semi.go +106 -0
  168. package/linthost/rules_format_parameter_properties.go +175 -0
  169. package/linthost/rules_format_print_width.go +66 -55
  170. package/linthost/rules_format_quote_props.go +169 -0
  171. package/linthost/rules_format_quotes.go +76 -30
  172. package/linthost/rules_format_semi.go +186 -11
  173. package/linthost/rules_format_sort_imports.go +437 -118
  174. package/linthost/rules_format_statement_split.go +343 -0
  175. package/linthost/rules_format_ternary_nullish_parens.go +77 -0
  176. package/linthost/rules_format_trailing_comma.go +22 -3
  177. package/linthost/rules_format_whitespace.go +336 -0
  178. package/linthost/rules_functional.go +967 -0
  179. package/linthost/rules_gap.go +24 -45
  180. package/linthost/rules_grouped_accessor_pairs.go +87 -0
  181. package/linthost/rules_guard_for_in.go +198 -0
  182. package/linthost/rules_id_length.go +77 -0
  183. package/linthost/rules_imports.go +70 -2
  184. package/linthost/rules_init_declarations.go +53 -0
  185. package/linthost/rules_jest.go +618 -0
  186. package/linthost/rules_jsdoc.go +502 -0
  187. package/linthost/rules_jsx_a11y.go +1264 -0
  188. package/linthost/rules_logic.go +12 -12
  189. package/linthost/rules_loops.go +1 -1
  190. package/linthost/rules_max_classes_per_file.go +58 -0
  191. package/linthost/rules_max_depth.go +83 -0
  192. package/linthost/rules_max_lines.go +101 -0
  193. package/linthost/rules_max_lines_per_function.go +57 -0
  194. package/linthost/rules_max_nested_callbacks.go +61 -0
  195. package/linthost/rules_max_params.go +49 -0
  196. package/linthost/rules_max_statements.go +59 -0
  197. package/linthost/rules_misc.go +1 -1
  198. package/linthost/rules_nextjs.go +697 -0
  199. package/linthost/rules_no_else_return.go +75 -0
  200. package/linthost/rules_no_empty_named_blocks.go +91 -0
  201. package/linthost/rules_no_extend_native.go +51 -0
  202. package/linthost/rules_no_import_assign.go +140 -0
  203. package/linthost/rules_no_invalid_this.go +52 -0
  204. package/linthost/rules_no_loop_func.go +85 -0
  205. package/linthost/rules_no_magic_numbers.go +123 -0
  206. package/linthost/rules_no_mixed_operators.go +80 -0
  207. package/linthost/rules_no_param_reassign.go +98 -0
  208. package/linthost/rules_no_redeclare.go +270 -0
  209. package/linthost/rules_no_restricted_imports.go +105 -0
  210. package/linthost/rules_no_restricted_syntax.go +49 -0
  211. package/linthost/rules_no_shadow.go +147 -0
  212. package/linthost/rules_no_unreachable.go +77 -0
  213. package/linthost/rules_no_useless_assignment.go +120 -0
  214. package/linthost/rules_no_useless_constructor.go +245 -0
  215. package/linthost/rules_no_useless_return.go +51 -0
  216. package/linthost/rules_params.go +1 -1
  217. package/linthost/rules_playwright.go +788 -0
  218. package/linthost/rules_prefer_arrow_callback.go +124 -0
  219. package/linthost/rules_prefer_destructuring.go +98 -0
  220. package/linthost/rules_prefer_named_capture_group.go +129 -0
  221. package/linthost/rules_prefer_rest_params.go +94 -0
  222. package/linthost/rules_problems.go +41 -30
  223. package/linthost/rules_promise.go +1057 -2
  224. package/linthost/rules_protos.go +2 -2
  225. package/linthost/rules_react.go +773 -0
  226. package/linthost/rules_react_extras.go +227 -0
  227. package/linthost/rules_react_hooks.go +848 -0
  228. package/linthost/rules_react_perf.go +293 -0
  229. package/linthost/rules_react_refresh.go +583 -0
  230. package/linthost/rules_regexp.go +410 -0
  231. package/linthost/rules_security.go +830 -0
  232. package/linthost/rules_self.go +2 -2
  233. package/linthost/rules_solid.go +1073 -0
  234. package/linthost/rules_sort_imports.go +69 -0
  235. package/linthost/rules_sort_keys.go +134 -0
  236. package/linthost/rules_storybook.go +1176 -0
  237. package/linthost/rules_strings.go +4 -4
  238. package/linthost/rules_suggestions.go +38 -38
  239. package/linthost/rules_tanstack_query.go +844 -0
  240. package/linthost/rules_testing_library.go +1204 -0
  241. package/linthost/rules_throw.go +1 -1
  242. package/linthost/rules_ts.go +19 -19
  243. package/linthost/rules_ts_async.go +399 -0
  244. package/linthost/rules_ts_class_literal_property_style.go +115 -0
  245. package/linthost/rules_ts_consistent_type_exports.go +256 -0
  246. package/linthost/rules_ts_explicit_function_return_type.go +67 -0
  247. package/linthost/rules_ts_extra.go +159 -36
  248. package/linthost/rules_ts_no_base_to_string.go +218 -0
  249. package/linthost/rules_ts_no_confusing_void.go +72 -0
  250. package/linthost/rules_ts_no_deprecated.go +195 -0
  251. package/linthost/rules_ts_no_for_in_array.go +84 -0
  252. package/linthost/rules_ts_no_magic_numbers.go +124 -0
  253. package/linthost/rules_ts_no_meaningless_void_operator.go +84 -0
  254. package/linthost/rules_ts_no_misused_spread.go +104 -0
  255. package/linthost/rules_ts_no_redundant_type_constituents.go +113 -0
  256. package/linthost/rules_ts_no_restricted_types.go +62 -0
  257. package/linthost/rules_ts_no_unnecessary_boolean_literal_compare.go +129 -0
  258. package/linthost/rules_ts_no_unnecessary_condition.go +250 -0
  259. package/linthost/rules_ts_no_unnecessary_qualifier.go +118 -0
  260. package/linthost/rules_ts_no_unnecessary_template_expression.go +178 -0
  261. package/linthost/rules_ts_no_unnecessary_type_arguments.go +234 -0
  262. package/linthost/rules_ts_no_unnecessary_type_assertion.go +157 -0
  263. package/linthost/rules_ts_no_unsafe_argument.go +82 -0
  264. package/linthost/rules_ts_no_unsafe_assignment.go +102 -0
  265. package/linthost/rules_ts_no_unsafe_call.go +82 -0
  266. package/linthost/rules_ts_no_unsafe_enum_comparison.go +235 -0
  267. package/linthost/rules_ts_no_unsafe_member_access.go +58 -0
  268. package/linthost/rules_ts_no_unsafe_return.go +126 -0
  269. package/linthost/rules_ts_no_unsafe_unary_minus.go +81 -0
  270. package/linthost/rules_ts_no_useless_constructor.go +52 -0
  271. package/linthost/rules_ts_non_nullable_type_assertion_style.go +108 -0
  272. package/linthost/rules_ts_parameter_properties.go +38 -0
  273. package/linthost/rules_ts_prefer_find.go +178 -0
  274. package/linthost/rules_ts_prefer_includes.go +255 -0
  275. package/linthost/rules_ts_prefer_nullish_coalescing.go +104 -0
  276. package/linthost/rules_ts_prefer_optional_chain.go +228 -0
  277. package/linthost/rules_ts_prefer_promise_reject_errors.go +176 -0
  278. package/linthost/rules_ts_prefer_readonly.go +73 -0
  279. package/linthost/rules_ts_prefer_reduce_type_parameter.go +116 -0
  280. package/linthost/rules_ts_prefer_regexp_exec.go +122 -0
  281. package/linthost/rules_ts_prefer_return_this_type.go +135 -0
  282. package/linthost/rules_ts_prefer_string_starts_ends_with.go +360 -0
  283. package/linthost/rules_ts_promise_function_async.go +77 -0
  284. package/linthost/rules_ts_related_getter_setter_pairs.go +113 -0
  285. package/linthost/rules_ts_require_array_sort_compare.go +96 -0
  286. package/linthost/rules_ts_restrict_plus_operands.go +127 -0
  287. package/linthost/rules_ts_restrict_template_expressions.go +95 -0
  288. package/linthost/rules_ts_simple.go +387 -0
  289. package/linthost/rules_ts_sort_type_constituents.go +144 -0
  290. package/linthost/rules_ts_strict_boolean_expressions.go +157 -0
  291. package/linthost/rules_ts_switch_exhaustiveness_check.go +147 -0
  292. package/linthost/rules_ts_unbound_method.go +126 -0
  293. package/linthost/rules_tsdoc.go +136 -0
  294. package/linthost/rules_unicorn_better_regex.go +27 -0
  295. package/linthost/rules_unicorn_catch_error_name.go +46 -0
  296. package/linthost/rules_unicorn_consistent_assert.go +47 -0
  297. package/linthost/rules_unicorn_consistent_date_clone.go +54 -0
  298. package/linthost/rules_unicorn_consistent_destructuring.go +25 -0
  299. package/linthost/rules_unicorn_consistent_empty_array_spread.go +62 -0
  300. package/linthost/rules_unicorn_consistent_existence_index_check.go +80 -0
  301. package/linthost/rules_unicorn_consistent_function_scoping.go +25 -0
  302. package/linthost/rules_unicorn_consistent_template_literal_escape.go +25 -0
  303. package/linthost/rules_unicorn_custom_error_definition.go +138 -0
  304. package/linthost/rules_unicorn_empty_brace_spaces.go +73 -0
  305. package/linthost/rules_unicorn_error_message.go +64 -0
  306. package/linthost/rules_unicorn_escape_case.go +51 -0
  307. package/linthost/rules_unicorn_expiring_todo_comments.go +78 -0
  308. package/linthost/rules_unicorn_explicit_length_check.go +88 -0
  309. package/linthost/rules_unicorn_filename_case.go +29 -0
  310. package/linthost/rules_unicorn_import_style.go +28 -0
  311. package/linthost/rules_unicorn_isolated_functions.go +29 -0
  312. package/linthost/rules_unicorn_new_for_builtins.go +87 -0
  313. package/linthost/rules_unicorn_no_abusive_eslint_disable.go +63 -0
  314. package/linthost/rules_unicorn_no_accessor_recursion.go +115 -0
  315. package/linthost/rules_unicorn_no_anonymous_default_export.go +74 -0
  316. package/linthost/rules_unicorn_no_array_callback_reference.go +65 -0
  317. package/linthost/rules_unicorn_no_array_for_each.go +40 -0
  318. package/linthost/rules_unicorn_no_array_method_this_argument.go +61 -0
  319. package/linthost/rules_unicorn_no_array_reduce.go +41 -0
  320. package/linthost/rules_unicorn_no_array_reverse.go +44 -0
  321. package/linthost/rules_unicorn_no_array_sort.go +43 -0
  322. package/linthost/rules_unicorn_no_await_expression_member.go +40 -0
  323. package/linthost/rules_unicorn_no_await_in_promise_methods.go +69 -0
  324. package/linthost/rules_unicorn_no_console_spaces.go +61 -0
  325. package/linthost/rules_unicorn_no_document_cookie.go +41 -0
  326. package/linthost/rules_unicorn_no_empty_file.go +44 -0
  327. package/linthost/rules_unicorn_no_for_loop.go +102 -0
  328. package/linthost/rules_unicorn_no_hex_escape.go +42 -0
  329. package/linthost/rules_unicorn_no_immediate_mutation.go +90 -0
  330. package/linthost/rules_unicorn_no_instanceof_builtins.go +66 -0
  331. package/linthost/rules_unicorn_no_invalid_fetch_options.go +79 -0
  332. package/linthost/rules_unicorn_no_invalid_remove_event_listener.go +51 -0
  333. package/linthost/rules_unicorn_no_keyword_prefix.go +101 -0
  334. package/linthost/rules_unicorn_no_lonely_if.go +44 -0
  335. package/linthost/rules_unicorn_no_magic_array_flat_depth.go +51 -0
  336. package/linthost/rules_unicorn_no_named_default.go +54 -0
  337. package/linthost/rules_unicorn_no_negated_condition.go +85 -0
  338. package/linthost/rules_unicorn_no_negation_in_equality_check.go +50 -0
  339. package/linthost/rules_unicorn_no_nested_ternary.go +43 -0
  340. package/linthost/rules_unicorn_no_new_array.go +30 -0
  341. package/linthost/rules_unicorn_no_new_buffer.go +31 -0
  342. package/linthost/rules_unicorn_no_null.go +24 -0
  343. package/linthost/rules_unicorn_no_object_as_default_parameter.go +48 -0
  344. package/linthost/rules_unicorn_no_process_exit.go +43 -0
  345. package/linthost/rules_unicorn_no_single_promise_in_promise_methods.go +59 -0
  346. package/linthost/rules_unicorn_no_static_only_class.go +56 -0
  347. package/linthost/rules_unicorn_no_thenable.go +74 -0
  348. package/linthost/rules_unicorn_no_this_assignment.go +35 -0
  349. package/linthost/rules_unicorn_no_typeof_undefined.go +58 -0
  350. package/linthost/rules_unicorn_no_unnecessary_array_flat_depth.go +49 -0
  351. package/linthost/rules_unicorn_no_unnecessary_array_splice_count.go +70 -0
  352. package/linthost/rules_unicorn_no_unnecessary_await.go +51 -0
  353. package/linthost/rules_unicorn_no_unnecessary_polyfills.go +32 -0
  354. package/linthost/rules_unicorn_no_unnecessary_slice_end.go +48 -0
  355. package/linthost/rules_unicorn_no_unreadable_array_destructuring.go +115 -0
  356. package/linthost/rules_unicorn_no_unreadable_iife.go +50 -0
  357. package/linthost/rules_unicorn_no_unused_properties.go +30 -0
  358. package/linthost/rules_unicorn_no_useless_collection_argument.go +63 -0
  359. package/linthost/rules_unicorn_no_useless_error_capture_stack_trace.go +46 -0
  360. package/linthost/rules_unicorn_no_useless_fallback_in_spread.go +81 -0
  361. package/linthost/rules_unicorn_no_useless_iterator_to_array.go +72 -0
  362. package/linthost/rules_unicorn_no_useless_length_check.go +144 -0
  363. package/linthost/rules_unicorn_no_useless_promise_resolve_reject.go +81 -0
  364. package/linthost/rules_unicorn_no_useless_spread.go +67 -0
  365. package/linthost/rules_unicorn_no_useless_switch_case.go +55 -0
  366. package/linthost/rules_unicorn_no_useless_undefined.go +55 -0
  367. package/linthost/rules_unicorn_no_zero_fractions.go +52 -0
  368. package/linthost/rules_unicorn_number_literal_case.go +69 -0
  369. package/linthost/rules_unicorn_numeric_separators_style.go +75 -0
  370. package/linthost/rules_unicorn_prefer_add_event_listener.go +66 -0
  371. package/linthost/rules_unicorn_prefer_array_find.go +51 -0
  372. package/linthost/rules_unicorn_prefer_array_flat.go +52 -0
  373. package/linthost/rules_unicorn_prefer_array_flat_map.go +63 -0
  374. package/linthost/rules_unicorn_prefer_array_index_of.go +147 -0
  375. package/linthost/rules_unicorn_prefer_array_some.go +67 -0
  376. package/linthost/rules_unicorn_prefer_at.go +77 -0
  377. package/linthost/rules_unicorn_prefer_bigint_literals.go +77 -0
  378. package/linthost/rules_unicorn_prefer_blob_reading_methods.go +44 -0
  379. package/linthost/rules_unicorn_prefer_class_fields.go +93 -0
  380. package/linthost/rules_unicorn_prefer_classlist_toggle.go +120 -0
  381. package/linthost/rules_unicorn_prefer_code_point.go +49 -0
  382. package/linthost/rules_unicorn_prefer_date_now.go +78 -0
  383. package/linthost/rules_unicorn_prefer_default_parameters.go +144 -0
  384. package/linthost/rules_unicorn_prefer_dom_node_append.go +40 -0
  385. package/linthost/rules_unicorn_prefer_dom_node_dataset.go +55 -0
  386. package/linthost/rules_unicorn_prefer_dom_node_remove.go +41 -0
  387. package/linthost/rules_unicorn_prefer_dom_node_text_content.go +37 -0
  388. package/linthost/rules_unicorn_prefer_event_target.go +36 -0
  389. package/linthost/rules_unicorn_prefer_export_from.go +129 -0
  390. package/linthost/rules_unicorn_prefer_global_this.go +127 -0
  391. package/linthost/rules_unicorn_prefer_import_meta_properties.go +66 -0
  392. package/linthost/rules_unicorn_prefer_includes.go +149 -0
  393. package/linthost/rules_unicorn_prefer_json_parse_buffer.go +65 -0
  394. package/linthost/rules_unicorn_prefer_keyboard_event_key.go +38 -0
  395. package/linthost/rules_unicorn_prefer_logical_operator_over_ternary.go +48 -0
  396. package/linthost/rules_unicorn_prefer_math_min_max.go +60 -0
  397. package/linthost/rules_unicorn_prefer_math_trunc.go +62 -0
  398. package/linthost/rules_unicorn_prefer_modern_dom_apis.go +47 -0
  399. package/linthost/rules_unicorn_prefer_modern_math_apis.go +70 -0
  400. package/linthost/rules_unicorn_prefer_module.go +50 -0
  401. package/linthost/rules_unicorn_prefer_native_coercion_functions.go +114 -0
  402. package/linthost/rules_unicorn_prefer_negative_index.go +74 -0
  403. package/linthost/rules_unicorn_prefer_node_protocol.go +122 -0
  404. package/linthost/rules_unicorn_prefer_number_properties.go +88 -0
  405. package/linthost/rules_unicorn_prefer_object_from_entries.go +55 -0
  406. package/linthost/rules_unicorn_prefer_optional_catch_binding.go +62 -0
  407. package/linthost/rules_unicorn_prefer_prototype_methods.go +54 -0
  408. package/linthost/rules_unicorn_prefer_query_selector.go +56 -0
  409. package/linthost/rules_unicorn_prefer_reflect_apply.go +36 -0
  410. package/linthost/rules_unicorn_prefer_regexp_test_rule.go +80 -0
  411. package/linthost/rules_unicorn_prefer_response_static_json.go +53 -0
  412. package/linthost/rules_unicorn_prefer_set_has.go +42 -0
  413. package/linthost/rules_unicorn_prefer_set_size.go +48 -0
  414. package/linthost/rules_unicorn_prefer_simple_condition_first.go +34 -0
  415. package/linthost/rules_unicorn_prefer_single_call.go +96 -0
  416. package/linthost/rules_unicorn_prefer_spread.go +51 -0
  417. package/linthost/rules_unicorn_prefer_string_raw.go +43 -0
  418. package/linthost/rules_unicorn_prefer_string_replace_all.go +95 -0
  419. package/linthost/rules_unicorn_prefer_string_slice.go +43 -0
  420. package/linthost/rules_unicorn_prefer_string_starts_ends_with.go +148 -0
  421. package/linthost/rules_unicorn_prefer_string_trim_start_end.go +48 -0
  422. package/linthost/rules_unicorn_prefer_structured_clone.go +71 -0
  423. package/linthost/rules_unicorn_prefer_switch.go +103 -0
  424. package/linthost/rules_unicorn_prefer_ternary.go +71 -0
  425. package/linthost/rules_unicorn_prefer_top_level_await.go +72 -0
  426. package/linthost/rules_unicorn_prefer_type_error.go +127 -0
  427. package/linthost/rules_unicorn_prevent_abbreviations.go +127 -0
  428. package/linthost/rules_unicorn_relative_url_style.go +49 -0
  429. package/linthost/rules_unicorn_require_array_join_separator.go +45 -0
  430. package/linthost/rules_unicorn_require_module_attributes.go +54 -0
  431. package/linthost/rules_unicorn_require_module_specifiers.go +55 -0
  432. package/linthost/rules_unicorn_require_number_to_fixed_digits_argument.go +43 -0
  433. package/linthost/rules_unicorn_require_post_message_target_origin.go +44 -0
  434. package/linthost/rules_unicorn_string_content.go +27 -0
  435. package/linthost/rules_unicorn_switch_case_braces.go +42 -0
  436. package/linthost/rules_unicorn_switch_case_break_position.go +31 -0
  437. package/linthost/rules_unicorn_template_indent.go +28 -0
  438. package/linthost/rules_unicorn_text_encoding_identifier_case.go +72 -0
  439. package/linthost/rules_unicorn_throw_new_error.go +61 -0
  440. package/linthost/rules_var.go +10 -4
  441. package/linthost/rules_vitest.go +568 -0
  442. package/package.json +3 -3
  443. package/plugin/main.go +49 -3
  444. package/rule/astutil/astutil.go +10 -3
  445. package/rule/rule.go +12 -10
  446. package/src/defaultFormat.ts +17 -10
  447. package/src/index.ts +65 -43
  448. package/src/structures/ITtscLintConfig.ts +14 -8
  449. package/src/structures/TtscLintRuleSetting.ts +55 -0
  450. package/src/structures/format/ITtscLintFormat.ts +141 -0
  451. package/src/structures/format/ITtscLintFormatJsDoc.ts +18 -0
  452. package/src/structures/format/ITtscLintFormatSortImports.ts +62 -0
  453. package/src/structures/format/index.ts +3 -0
  454. package/src/structures/index.ts +3 -4
  455. package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +117 -0
  456. package/src/structures/rules/ITtscLintBoundariesRules.ts +91 -0
  457. package/src/structures/rules/ITtscLintContributorRules.ts +20 -0
  458. package/src/structures/rules/ITtscLintCoreRules.ts +1378 -0
  459. package/src/structures/rules/ITtscLintCypressRuleOptions.ts +18 -0
  460. package/src/structures/rules/ITtscLintCypressRules.ts +141 -0
  461. package/src/structures/rules/ITtscLintFunctionalRuleOptions.ts +200 -0
  462. package/src/structures/rules/ITtscLintFunctionalRules.ts +197 -0
  463. package/src/structures/rules/ITtscLintJestRules.ts +209 -0
  464. package/src/structures/rules/ITtscLintJsdocRules.ts +146 -0
  465. package/src/structures/rules/ITtscLintJsxA11yRules.ts +331 -0
  466. package/src/structures/rules/ITtscLintNextjsRules.ts +201 -0
  467. package/src/structures/rules/ITtscLintPlaywrightRules.ts +343 -0
  468. package/src/structures/rules/ITtscLintPromiseRules.ts +196 -0
  469. package/src/structures/rules/ITtscLintReactPerfRuleOptions.ts +19 -0
  470. package/src/structures/rules/ITtscLintReactPerfRules.ts +53 -0
  471. package/src/structures/rules/ITtscLintReactRuleOptions.ts +41 -0
  472. package/src/structures/rules/ITtscLintReactRules.ts +308 -0
  473. package/src/structures/rules/ITtscLintRegexpRules.ts +204 -0
  474. package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -0
  475. package/src/structures/rules/ITtscLintRules.ts +77 -0
  476. package/src/structures/rules/ITtscLintSecurityRules.ts +155 -0
  477. package/src/structures/rules/ITtscLintSolidRules.ts +199 -0
  478. package/src/structures/rules/ITtscLintStorybookRuleOptions.ts +22 -0
  479. package/src/structures/rules/ITtscLintStorybookRules.ts +184 -0
  480. package/src/structures/rules/ITtscLintTanstackQueryRules.ts +96 -0
  481. package/src/structures/rules/ITtscLintTestingLibraryRuleOptions.ts +23 -0
  482. package/src/structures/rules/ITtscLintTestingLibraryRules.ts +325 -0
  483. package/src/structures/rules/ITtscLintTypeScriptRules.ts +1172 -0
  484. package/src/structures/rules/ITtscLintUnicornRules.ts +1143 -0
  485. package/src/structures/rules/ITtscLintVitestRules.ts +146 -0
  486. package/src/structures/rules/index.ts +31 -0
  487. package/lib/structures/ITtscLintFormatConfig.d.ts +0 -153
  488. package/lib/structures/ITtscLintFormatConfig.js.map +0 -1
  489. package/lib/structures/TtscLintRule.d.ts +0 -17
  490. package/lib/structures/TtscLintRule.js.map +0 -1
  491. package/lib/structures/TtscLintRuleMap.d.ts +0 -46
  492. package/lib/structures/TtscLintRuleMap.js.map +0 -1
  493. package/lib/structures/TtscLintRuleOptions.d.ts +0 -137
  494. package/lib/structures/TtscLintRuleOptions.js.map +0 -1
  495. package/src/structures/ITtscLintFormatConfig.ts +0 -168
  496. package/src/structures/TtscLintRule.ts +0 -160
  497. package/src/structures/TtscLintRuleMap.ts +0 -55
  498. package/src/structures/TtscLintRuleOptions.ts +0 -151
@@ -5,6 +5,7 @@ import (
5
5
  "fmt"
6
6
  "os"
7
7
  "sort"
8
+ "sync"
8
9
  )
9
10
 
10
11
  // maxFormatPasses bounds the format cascade for the same reason
@@ -36,12 +37,16 @@ func RunFormat(args []string) int {
36
37
  // runFormat is the internal implementation of RunFormat. It drives the
37
38
  // cascade loop and applies format-rule edits until convergence.
38
39
  func runFormat(opts *subcommandOpts) int {
39
- rules, err := loadRules(opts.pluginsJSON, opts.cwd, opts.tsconfig)
40
+ rules, err := loadFormatRules(opts.pluginsJSON, opts.cwd, opts.tsconfig)
41
+ if err != nil {
42
+ fmt.Fprintln(os.Stderr, err)
43
+ return 2
44
+ }
45
+ resolver, err := newFormatCommandResolver(rules, opts.cwd, "")
40
46
  if err != nil {
41
47
  fmt.Fprintln(os.Stderr, err)
42
48
  return 2
43
49
  }
44
- resolver := formatCommandResolver{inner: rules}
45
50
  engine := NewEngineWithResolver(resolver)
46
51
  engine.SetSerial(opts.singleThreaded)
47
52
  needsRuleChecker := engine.NeedsTypeChecker()
@@ -100,6 +105,110 @@ func runFormat(opts *subcommandOpts) int {
100
105
  // without requiring explicit rule declarations in the project config.
101
106
  type formatCommandResolver struct {
102
107
  inner RuleResolver
108
+ // ruleNames memoizes formatOptionRuleNames. The resolver's option set is
109
+ // immutable for the whole run, so the sorted format-rule slice is computed
110
+ // once and reused across ResolveRules (per file per pass), ActiveRuleNames,
111
+ // and EnabledRuleConfig. The field is a pointer so a nil zero value stays
112
+ // valid: callers that build formatCommandResolver{inner: ...} without it
113
+ // (tests, lsp) fall back to direct computation, and copying the struct by
114
+ // value (it is stored in the engine's RuleResolver interface and passed
115
+ // around as a value) shares the same underlying cache rather than copying a
116
+ // sync.Once lock.
117
+ ruleNames *formatRuleNamesCache
118
+ // entryDecisions memoizes the per-fileName ignored/matches decision that
119
+ // ResolveRules derives from fileIsIgnoredByEntry and fileMatchesAnyEntry.
120
+ // Both walk store.entries with glob matching and are recomputed identically
121
+ // for the same fileName on every format pass, so the result is cached once
122
+ // per file and reused. The field is a pointer (a *sync.Map) so the nil zero
123
+ // value stays valid for construction sites that omit it, and copying the
124
+ // resolver by value shares the same underlying map. sync.Map handles the
125
+ // engine's concurrent per-file ResolveRules calls without an extra lock.
126
+ entryDecisions *sync.Map
127
+ // defaultOptions holds the always-on format rules' options (from
128
+ // expandFormatBlock) to apply when the project config declares no `format`
129
+ // block — no block in lint.config.*, or no config file at all. nil when a
130
+ // format block is configured, so that block wins entirely. Keys are canonical
131
+ // format rule names; values are the marshaled options blob each rule decodes,
132
+ // exactly as a configured block would supply them.
133
+ defaultOptions RuleOptionsMap
134
+ }
135
+
136
+ // newFormatCommandResolver wraps inner for the format command / LSP buffer path.
137
+ // When inner declares no `format` rules (no `format` block in lint.config.*, or
138
+ // no config file at all) it loads the documented default format rules, letting
139
+ // the nearest .vscode/settings.json under startDir override the indentation/eol
140
+ // keys. language scopes the settings.json language section ("" skips sections,
141
+ // e.g. the project-wide CLI path). A configured `format` block leaves
142
+ // defaultOptions nil so the block stays authoritative.
143
+ func newFormatCommandResolver(inner RuleResolver, startDir string, language string) (formatCommandResolver, error) {
144
+ r := formatCommandResolver{
145
+ inner: inner,
146
+ ruleNames: &formatRuleNamesCache{},
147
+ entryDecisions: &sync.Map{},
148
+ }
149
+ if !hasInnerFormatRules(inner) {
150
+ opts, err := defaultFormatOptions(editorFormatOverrides(startDir, language))
151
+ if err != nil {
152
+ return formatCommandResolver{}, err
153
+ }
154
+ r.defaultOptions = opts
155
+ }
156
+ return r, nil
157
+ }
158
+
159
+ // hasInnerFormatRules reports whether inner already carries format-rule options,
160
+ // i.e. the project configured a `format` block (format/* options only ever come
161
+ // from a block). When true the block is authoritative and defaults are skipped.
162
+ func hasInnerFormatRules(inner RuleResolver) bool {
163
+ for name := range resolverOptions(inner) {
164
+ if isRegisteredFormatRule(name) {
165
+ return true
166
+ }
167
+ }
168
+ return false
169
+ }
170
+
171
+ // defaultFormatOptions expands the always-on default format ruleset (optionally
172
+ // overridden by editor settings) into the per-rule options map the resolver
173
+ // returns through RuleOptions. It reuses expandFormatBlock so the defaults are
174
+ // produced by exactly the same code path as a user-authored format block.
175
+ func defaultFormatOptions(overrides map[string]any) (RuleOptionsMap, error) {
176
+ expanded, err := expandFormatBlock(overrides)
177
+ if err != nil {
178
+ return nil, err
179
+ }
180
+ out := make(RuleOptionsMap, len(expanded))
181
+ for name, entry := range expanded {
182
+ tuple, ok := entry.([]any)
183
+ if !ok || len(tuple) < 2 {
184
+ continue
185
+ }
186
+ options, ok := tuple[1].(map[string]any)
187
+ if !ok {
188
+ continue
189
+ }
190
+ raw, err := json.Marshal(options)
191
+ if err != nil {
192
+ return nil, err
193
+ }
194
+ out[name] = raw
195
+ }
196
+ return out, nil
197
+ }
198
+
199
+ // formatRuleNamesCache lazily computes and stores the sorted format-rule name
200
+ // slice for a resolver. sync.Once guarantees a single computation even when
201
+ // ResolveRules runs concurrently across files in the engine's parallel walk.
202
+ type formatRuleNamesCache struct {
203
+ once sync.Once
204
+ names []string
205
+ }
206
+
207
+ // formatEntryDecision is the cached per-fileName outcome of the two entry-scope
208
+ // checks ResolveRules runs before applying its format-rule upgrade.
209
+ type formatEntryDecision struct {
210
+ ignoredByEntry bool
211
+ matchesEntry bool
103
212
  }
104
213
 
105
214
  // ResolveRules implements RuleResolver. It delegates to the inner resolver
@@ -127,10 +236,11 @@ func (r formatCommandResolver) ResolveRules(fileName string) ResolvedRuleConfig
127
236
  if resolved.Ignored {
128
237
  return resolved
129
238
  }
130
- if r.fileIsIgnoredByEntry(fileName) {
239
+ decision := r.entryDecision(fileName)
240
+ if decision.ignoredByEntry {
131
241
  return resolved
132
242
  }
133
- if !r.fileMatchesAnyEntry(fileName) {
243
+ if !decision.matchesEntry {
134
244
  return resolved
135
245
  }
136
246
  if resolved.Rules == nil {
@@ -144,6 +254,36 @@ func (r formatCommandResolver) ResolveRules(fileName string) ResolvedRuleConfig
144
254
  return resolved
145
255
  }
146
256
 
257
+ // entryDecision returns the per-fileName ignored/matches outcome that
258
+ // ResolveRules needs, computing it from fileIsIgnoredByEntry and
259
+ // fileMatchesAnyEntry on first use and reusing it on later passes. When the
260
+ // memo is absent (a resolver built without entryDecisions, e.g. in tests or
261
+ // lsp) it computes the decision directly so behavior is identical, just
262
+ // uncached.
263
+ func (r formatCommandResolver) entryDecision(fileName string) formatEntryDecision {
264
+ if r.entryDecisions == nil {
265
+ return r.computeEntryDecision(fileName)
266
+ }
267
+ if cached, ok := r.entryDecisions.Load(fileName); ok {
268
+ return cached.(formatEntryDecision)
269
+ }
270
+ decision := r.computeEntryDecision(fileName)
271
+ // LoadOrStore keeps the first writer's value so concurrent passes over the
272
+ // same file agree; the decision is a pure function of fileName, so either
273
+ // value is correct.
274
+ actual, _ := r.entryDecisions.LoadOrStore(fileName, decision)
275
+ return actual.(formatEntryDecision)
276
+ }
277
+
278
+ // computeEntryDecision runs the two uncached entry-scope checks behind
279
+ // entryDecision.
280
+ func (r formatCommandResolver) computeEntryDecision(fileName string) formatEntryDecision {
281
+ return formatEntryDecision{
282
+ ignoredByEntry: r.fileIsIgnoredByEntry(fileName),
283
+ matchesEntry: r.fileMatchesAnyEntry(fileName),
284
+ }
285
+ }
286
+
147
287
  // fileIsIgnoredByEntry reports whether any non-IgnoreOnly entry in the inner
148
288
  // ConfigStore has an `ignores` glob that matches `fileName`. IgnoreOnly
149
289
  // entries are already handled by ResolvedRuleConfig.Ignored — they are
@@ -154,19 +294,34 @@ func (r formatCommandResolver) ResolveRules(fileName string) ResolvedRuleConfig
154
294
  // independently because its job is to upgrade format rules to `warn`, not to
155
295
  // read the engine's resolved severity map.
156
296
  func (r formatCommandResolver) fileIsIgnoredByEntry(fileName string) bool {
297
+ matched, _ := r.anyNonIgnoreEntry(func(entry *ConfigEntry) bool {
298
+ return entry.matchesIgnores(fileName)
299
+ })
300
+ return matched
301
+ }
302
+
303
+ // anyNonIgnoreEntry resolves the inner resolver to its concrete *ConfigStore
304
+ // once and reports whether any non-IgnoreOnly entry satisfies `pred`. The
305
+ // second return value is false when the inner resolver is not a *ConfigStore
306
+ // (or is a nil one), letting each caller pick its own non-store default. This
307
+ // collapses the shared store-resolution and entry-walk that fileIsIgnoredByEntry
308
+ // and fileMatchesAnyEntry would otherwise duplicate; the per-caller default and
309
+ // the empty-entries base case stay in the callers where they differ.
310
+ func (r formatCommandResolver) anyNonIgnoreEntry(pred func(*ConfigEntry) bool) (matched bool, hasStore bool) {
157
311
  store, ok := r.inner.(*ConfigStore)
158
312
  if !ok || store == nil {
159
- return false
313
+ return false, false
160
314
  }
161
- for _, entry := range store.entries {
315
+ for i := range store.entries {
316
+ entry := &store.entries[i]
162
317
  if entry.IgnoreOnly {
163
318
  continue
164
319
  }
165
- if entry.matchesIgnores(fileName) {
166
- return true
320
+ if pred(entry) {
321
+ return true, true
167
322
  }
168
323
  }
169
- return false
324
+ return false, true
170
325
  }
171
326
 
172
327
  // fileMatchesAnyEntry reports whether `fileName` falls inside the `files`
@@ -193,22 +348,32 @@ func (r formatCommandResolver) fileIsIgnoredByEntry(fileName string) bool {
193
348
  // rule-eligibility reasoned about from the outside, so the format
194
349
  // upgrade applies the same as it does for an entry without `files`.
195
350
  func (r formatCommandResolver) fileMatchesAnyEntry(fileName string) bool {
196
- store, ok := r.inner.(*ConfigStore)
197
- if !ok || store == nil {
351
+ matched, hasStore := r.anyNonIgnoreEntry(func(entry *ConfigEntry) bool {
352
+ return entry.matchesFile(fileName)
353
+ })
354
+ if !hasStore {
355
+ // Not a *ConfigStore: conservative default, see the doc comment above.
198
356
  return true
199
357
  }
200
- if len(store.entries) == 0 {
358
+ // A store with no entries (or only IgnoreOnly ones that the walk skipped)
359
+ // produces matched == false; an empty entries slice must still return true
360
+ // so format mode applies its default upgrade, matching ConfigStore behavior.
361
+ // The all-IgnoreOnly case is distinguished by a non-empty entries slice.
362
+ if !matched && len(r.storeEntries()) == 0 {
201
363
  return true
202
364
  }
203
- for _, entry := range store.entries {
204
- if entry.IgnoreOnly {
205
- continue
206
- }
207
- if entry.matchesFile(fileName) {
208
- return true
209
- }
365
+ return matched
366
+ }
367
+
368
+ // storeEntries returns the inner resolver's config entries, or nil when the
369
+ // inner resolver is not a *ConfigStore. Used by fileMatchesAnyEntry to tell the
370
+ // empty-entries base case (return true) apart from the all-IgnoreOnly case
371
+ // (return false) after anyNonIgnoreEntry reports no match.
372
+ func (r formatCommandResolver) storeEntries() []ConfigEntry {
373
+ if store, ok := r.inner.(*ConfigStore); ok && store != nil {
374
+ return store.entries
210
375
  }
211
- return false
376
+ return nil
212
377
  }
213
378
 
214
379
  // ActiveRuleNames implements RuleResolver. Returns the union of the inner
@@ -239,25 +404,57 @@ func (r formatCommandResolver) EnabledRuleConfig() RuleConfig {
239
404
  return enabled
240
405
  }
241
406
 
242
- // RuleOptions implements RuleResolver by delegating directly to the inner resolver.
407
+ // RuleOptions implements RuleResolver. It prefers the inner resolver's options
408
+ // and falls back to the default format options for rules the project did not
409
+ // configure, so the default always-on rules receive their (possibly
410
+ // settings.json-overridden) options.
243
411
  func (r formatCommandResolver) RuleOptions(name string) json.RawMessage {
244
- return r.inner.RuleOptions(name)
412
+ if raw := r.inner.RuleOptions(name); len(raw) > 0 {
413
+ return raw
414
+ }
415
+ if r.defaultOptions != nil {
416
+ if raw, ok := r.defaultOptions[name]; ok {
417
+ return raw
418
+ }
419
+ }
420
+ return nil
245
421
  }
246
422
 
247
423
  // formatOptionRuleNames returns the sorted list of rule names from the inner
248
424
  // resolver's options that are registered as format rules. These are the rules
249
425
  // that formatCommandResolver promotes from off to warn.
250
426
  func (r formatCommandResolver) formatOptionRuleNames() []string {
251
- options := resolverOptions(r.inner)
252
- if len(options) == 0 {
253
- return nil
427
+ if r.ruleNames == nil {
428
+ // Nil cache (a resolver built without the memo, e.g. in tests or lsp):
429
+ // compute directly. Construction sites that want the memo set ruleNames.
430
+ return r.computeFormatOptionRuleNames()
254
431
  }
432
+ r.ruleNames.once.Do(func() {
433
+ r.ruleNames.names = r.computeFormatOptionRuleNames()
434
+ })
435
+ return r.ruleNames.names
436
+ }
437
+
438
+ // computeFormatOptionRuleNames does the uncached work behind
439
+ // formatOptionRuleNames: collect the inner resolver's option names that are
440
+ // registered format rules, sorted. The result order is stable (sorted) so
441
+ // memoizing it does not change any caller's observed ordering.
442
+ func (r formatCommandResolver) computeFormatOptionRuleNames() []string {
443
+ options := resolverOptions(r.inner)
255
444
  names := make([]string, 0, len(options))
256
445
  for name := range options {
257
446
  if isRegisteredFormatRule(name) {
258
447
  names = append(names, name)
259
448
  }
260
449
  }
450
+ if len(names) == 0 {
451
+ // No `format` block configured: fall back to the default always-on set so
452
+ // the formatter still runs, with documented defaults plus any
453
+ // .vscode/settings.json overrides folded into defaultOptions.
454
+ for name := range r.defaultOptions {
455
+ names = append(names, name)
456
+ }
457
+ }
261
458
  sort.Strings(names)
262
459
  return names
263
460
  }
@@ -0,0 +1,262 @@
1
+ package linthost
2
+
3
+ import (
4
+ "encoding/json"
5
+ "errors"
6
+ "os"
7
+ "path/filepath"
8
+ "strings"
9
+ )
10
+
11
+ // errNoLintConfigFile is the sentinel LoadConfigResolver wraps when no
12
+ // lint.config.*/ttsc-lint.config.* file is found. The format paths
13
+ // (loadFormatRules) treat it as "use defaults" rather than a hard error, so
14
+ // formatOnSave / `ttsc format` work even when a project ships no lint config.
15
+ var errNoLintConfigFile = errors.New("@ttsc/lint: no lint.config.* or ttsc-lint.config.* file found")
16
+
17
+ // loadFormatRules loads the rule resolver for the format paths, tolerating a
18
+ // missing lint config. The lint check/build path requires a config and surfaces
19
+ // the error, but formatting falls back to the documented defaults: a project
20
+ // with no lint.config.* still formats on save. Any other load error (a broken
21
+ // or malformed config) is propagated unchanged.
22
+ func loadFormatRules(pluginsJSON, cwd, tsconfigPath string) (RuleResolver, error) {
23
+ rules, err := loadRules(pluginsJSON, cwd, tsconfigPath)
24
+ if err != nil {
25
+ if errors.Is(err, errNoLintConfigFile) {
26
+ return RuleConfig{}, nil
27
+ }
28
+ return nil, err
29
+ }
30
+ return rules, nil
31
+ }
32
+
33
+ // editorFormatOverrides reads the nearest ancestor `.vscode/settings.json`
34
+ // relative to startDir and maps the editor's formatting settings onto the
35
+ // `format` block keys the default formatter understands. Only keys the user
36
+ // explicitly set are returned, so any setting that is absent falls through to
37
+ // the documented format defaults.
38
+ //
39
+ // This is consulted only when lint.config.* configures no `format` block: a
40
+ // configured format block always wins and never reaches this path. Within
41
+ // settings.json a language-specific section matching `language` (e.g.
42
+ // "[typescript]" or a combined "[javascript][typescript][json]") wins over the
43
+ // top-level keys, mirroring VS Code's own resolution. Pass language="" to skip
44
+ // language sections (e.g. the project-wide `ttsc format` CLI path).
45
+ //
46
+ // Mapping (values use JSON types so they parse identically to a real `format`
47
+ // block, where numbers decode as float64):
48
+ //
49
+ // - editor.tabSize -> tabWidth (number)
50
+ // - editor.insertSpaces -> useTabs (bool, inverted)
51
+ // - files.eol -> endOfLine ("\n" -> "lf", "\r\n" -> "crlf")
52
+ //
53
+ // editor.detectIndentation auto-detection is intentionally not emulated; the
54
+ // explicit tabSize/insertSpaces are honored as written. A relative startDir is
55
+ // ignored (returns no overrides) so unit tests that format from a bare cwd stay
56
+ // hermetic and never walk up into a real workspace's .vscode directory.
57
+ func editorFormatOverrides(startDir string, language string) map[string]any {
58
+ out := map[string]any{}
59
+ if !filepath.IsAbs(startDir) {
60
+ return out
61
+ }
62
+ settings, ok := loadNearestVSCodeSettings(startDir)
63
+ if !ok {
64
+ return out
65
+ }
66
+ // Top-level keys first, then language-specific sections override them.
67
+ applyEditorSettings(out, settings)
68
+ if language != "" {
69
+ for key, value := range settings {
70
+ if !sectionMatchesLanguage(key, language) {
71
+ continue
72
+ }
73
+ if section, ok := value.(map[string]any); ok {
74
+ applyEditorSettings(out, section)
75
+ }
76
+ }
77
+ }
78
+ return out
79
+ }
80
+
81
+ // applyEditorSettings reads the recognized editor keys from a settings object
82
+ // (either the top-level document or a language-specific section) and writes the
83
+ // corresponding format-block overrides into out, overwriting earlier values so
84
+ // callers can layer language sections over the top-level defaults.
85
+ func applyEditorSettings(out map[string]any, settings map[string]any) {
86
+ if v, ok := settings["editor.tabSize"]; ok {
87
+ if f, ok := v.(float64); ok {
88
+ out["tabWidth"] = f
89
+ }
90
+ }
91
+ if v, ok := settings["editor.insertSpaces"]; ok {
92
+ if b, ok := v.(bool); ok {
93
+ out["useTabs"] = !b
94
+ }
95
+ }
96
+ if v, ok := settings["files.eol"]; ok {
97
+ if s, ok := v.(string); ok {
98
+ switch s {
99
+ case "\n":
100
+ out["endOfLine"] = "lf"
101
+ case "\r\n":
102
+ out["endOfLine"] = "crlf"
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ // sectionMatchesLanguage reports whether a settings.json key is a
109
+ // language-specific section header (e.g. "[typescript]" or the combined
110
+ // "[javascript][typescript][json]") that applies to language. An empty
111
+ // language never matches.
112
+ func sectionMatchesLanguage(key string, language string) bool {
113
+ if language == "" {
114
+ return false
115
+ }
116
+ if !strings.HasPrefix(key, "[") || !strings.HasSuffix(key, "]") {
117
+ return false
118
+ }
119
+ inner := strings.TrimSuffix(strings.TrimPrefix(key, "["), "]")
120
+ for _, lang := range strings.Split(inner, "][") {
121
+ if lang == language {
122
+ return true
123
+ }
124
+ }
125
+ return false
126
+ }
127
+
128
+ // vscodeLanguageID maps a file extension to the VS Code language identifier
129
+ // used in settings.json language sections. Extensions outside the formatter's
130
+ // supported set return "" so no language section matches.
131
+ func vscodeLanguageID(filePath string) string {
132
+ switch strings.ToLower(filepath.Ext(filePath)) {
133
+ case ".ts", ".mts", ".cts":
134
+ return "typescript"
135
+ case ".tsx":
136
+ return "typescriptreact"
137
+ case ".js", ".mjs", ".cjs":
138
+ return "javascript"
139
+ case ".jsx":
140
+ return "javascriptreact"
141
+ default:
142
+ return ""
143
+ }
144
+ }
145
+
146
+ // loadNearestVSCodeSettings walks up from startDir looking for a
147
+ // `.vscode/settings.json`, returning the first one found decoded as a JSONC
148
+ // document. It returns ok=false when none exists or the file cannot be parsed,
149
+ // so a broken settings file degrades to the format defaults instead of breaking
150
+ // the formatter.
151
+ func loadNearestVSCodeSettings(startDir string) (map[string]any, bool) {
152
+ dir := startDir
153
+ for {
154
+ candidate := filepath.Join(dir, ".vscode", "settings.json")
155
+ if data, err := os.ReadFile(candidate); err == nil {
156
+ var parsed map[string]any
157
+ if json.Unmarshal(stripJSONC(data), &parsed) == nil {
158
+ return parsed, true
159
+ }
160
+ return nil, false
161
+ }
162
+ parent := filepath.Dir(dir)
163
+ if parent == dir {
164
+ return nil, false
165
+ }
166
+ dir = parent
167
+ }
168
+ }
169
+
170
+ // stripJSONC removes `//` line comments, `/* */` block comments, and trailing
171
+ // commas from a JSONC byte slice so encoding/json can parse VS Code's
172
+ // settings.json. It is string-literal aware: comment markers and commas inside
173
+ // string values are preserved.
174
+ func stripJSONC(data []byte) []byte {
175
+ out := make([]byte, 0, len(data))
176
+ inString := false
177
+ escaped := false
178
+ for i := 0; i < len(data); i++ {
179
+ c := data[i]
180
+ if inString {
181
+ out = append(out, c)
182
+ if escaped {
183
+ escaped = false
184
+ continue
185
+ }
186
+ switch c {
187
+ case '\\':
188
+ escaped = true
189
+ case '"':
190
+ inString = false
191
+ }
192
+ continue
193
+ }
194
+ switch {
195
+ case c == '"':
196
+ inString = true
197
+ out = append(out, c)
198
+ case c == '/' && i+1 < len(data) && data[i+1] == '/':
199
+ for i < len(data) && data[i] != '\n' {
200
+ i++
201
+ }
202
+ if i < len(data) {
203
+ out = append(out, data[i])
204
+ }
205
+ case c == '/' && i+1 < len(data) && data[i+1] == '*':
206
+ i += 2
207
+ for i+1 < len(data) && !(data[i] == '*' && data[i+1] == '/') {
208
+ i++
209
+ }
210
+ i++
211
+ default:
212
+ out = append(out, c)
213
+ }
214
+ }
215
+ return dropTrailingCommas(out)
216
+ }
217
+
218
+ // dropTrailingCommas removes a comma that is followed (after optional
219
+ // whitespace) by a closing `}` or `]`, the one JSONC affordance encoding/json
220
+ // rejects. It is string-literal aware so commas inside strings survive.
221
+ func dropTrailingCommas(data []byte) []byte {
222
+ out := make([]byte, 0, len(data))
223
+ inString := false
224
+ escaped := false
225
+ for i := 0; i < len(data); i++ {
226
+ c := data[i]
227
+ if inString {
228
+ out = append(out, c)
229
+ if escaped {
230
+ escaped = false
231
+ continue
232
+ }
233
+ switch c {
234
+ case '\\':
235
+ escaped = true
236
+ case '"':
237
+ inString = false
238
+ }
239
+ continue
240
+ }
241
+ if c == '"' {
242
+ inString = true
243
+ out = append(out, c)
244
+ continue
245
+ }
246
+ if c == ',' {
247
+ j := i + 1
248
+ for j < len(data) && isJSONSpace(data[j]) {
249
+ j++
250
+ }
251
+ if j < len(data) && (data[j] == '}' || data[j] == ']') {
252
+ continue
253
+ }
254
+ }
255
+ out = append(out, c)
256
+ }
257
+ return out
258
+ }
259
+
260
+ func isJSONSpace(c byte) bool {
261
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r'
262
+ }
package/linthost/host.go CHANGED
@@ -14,6 +14,7 @@ import (
14
14
  "errors"
15
15
  "fmt"
16
16
  "path/filepath"
17
+ "strings"
17
18
 
18
19
  shimast "github.com/microsoft/typescript-go/shim/ast"
19
20
  "github.com/microsoft/typescript-go/shim/bundled"
@@ -157,12 +158,19 @@ func (p *program) close() {
157
158
  }
158
159
  }
159
160
 
160
- // userSourceFiles returns the program's user-authored source files
161
- // (declaration files filtered out — those belong to library typings).
161
+ // userSourceFiles returns the tsconfig-selected source files the lint engine
162
+ // owns. The tsconfig file list is the boundary: imported libraries, generated
163
+ // output, and JSON modules may still appear in Program.SourceFiles(), but lint
164
+ // and format should not walk them unless the project selected them as TS/JS
165
+ // source roots.
162
166
  func (p *program) userSourceFiles() []*shimast.SourceFile {
167
+ roots := p.userSourceFileNames()
163
168
  out := make([]*shimast.SourceFile, 0)
164
169
  for _, f := range p.tsProgram.SourceFiles() {
165
- if f == nil || f.IsDeclarationFile {
170
+ if f == nil {
171
+ continue
172
+ }
173
+ if _, ok := roots[canonicalProjectPath(p.cwd, f.FileName())]; !ok {
166
174
  continue
167
175
  }
168
176
  out = append(out, f)
@@ -170,6 +178,35 @@ func (p *program) userSourceFiles() []*shimast.SourceFile {
170
178
  return out
171
179
  }
172
180
 
181
+ func (p *program) userSourceFileNames() map[string]struct{} {
182
+ out := make(map[string]struct{})
183
+ if p == nil || p.parsed == nil || p.parsed.ParsedConfig == nil {
184
+ return out
185
+ }
186
+ for _, fileName := range p.parsed.ParsedConfig.FileNames {
187
+ if isLintSourceFileName(fileName) {
188
+ out[canonicalProjectPath(p.cwd, fileName)] = struct{}{}
189
+ }
190
+ }
191
+ return out
192
+ }
193
+
194
+ func canonicalProjectPath(cwd, fileName string) string {
195
+ if !filepath.IsAbs(fileName) {
196
+ fileName = filepath.Join(cwd, fileName)
197
+ }
198
+ return filepath.ToSlash(filepath.Clean(fileName))
199
+ }
200
+
201
+ func isLintSourceFileName(fileName string) bool {
202
+ switch strings.ToLower(filepath.Ext(fileName)) {
203
+ case ".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs":
204
+ return true
205
+ default:
206
+ return false
207
+ }
208
+ }
209
+
173
210
  // programDiagnostics returns the bind + semantic diagnostics for the
174
211
  // loaded program. Same surface tsgo's CLI prints when you run a regular
175
212
  // `tsgo --noEmit`.