@ttsc/lint 0.13.1 → 0.14.0-dev.20260528.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 (464) hide show
  1. package/README.md +948 -69
  2. package/lib/defaultFormat.d.ts +9 -6
  3. package/lib/defaultFormat.js +9 -6
  4. package/lib/defaultFormat.js.map +1 -1
  5. package/lib/index.d.ts +46 -0
  6. package/lib/index.js +35 -10
  7. package/lib/index.js.map +1 -1
  8. package/lib/structures/ITtscLintConfig.d.ts +12 -6
  9. package/lib/structures/{ITtscLintFormatConfig.d.ts → ITtscLintFormat.d.ts} +2 -2
  10. package/lib/structures/{TtscLintRuleMap.js → ITtscLintFormat.js} +1 -1
  11. package/lib/structures/ITtscLintFormat.js.map +1 -0
  12. package/lib/structures/TtscLintRuleSetting.d.ts +49 -0
  13. package/lib/structures/{TtscLintRuleOptions.js → TtscLintRuleSetting.js} +1 -1
  14. package/lib/structures/TtscLintRuleSetting.js.map +1 -0
  15. package/lib/structures/index.d.ts +3 -4
  16. package/lib/structures/index.js +3 -4
  17. package/lib/structures/index.js.map +1 -1
  18. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +104 -0
  19. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js +14 -0
  20. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js.map +1 -0
  21. package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +79 -0
  22. package/lib/structures/rules/ITtscLintBoundariesRules.js +3 -0
  23. package/lib/structures/rules/ITtscLintBoundariesRules.js.map +1 -0
  24. package/lib/structures/rules/ITtscLintContributorRules.d.ts +17 -0
  25. package/lib/structures/rules/ITtscLintContributorRules.js +3 -0
  26. package/lib/structures/rules/ITtscLintContributorRules.js.map +1 -0
  27. package/lib/structures/rules/ITtscLintCoreRules.d.ts +1257 -0
  28. package/lib/structures/{ITtscLintFormatConfig.js → rules/ITtscLintCoreRules.js} +1 -1
  29. package/lib/structures/rules/ITtscLintCoreRules.js.map +1 -0
  30. package/lib/structures/rules/ITtscLintCypressRuleOptions.d.ts +17 -0
  31. package/lib/structures/rules/ITtscLintCypressRuleOptions.js +10 -0
  32. package/lib/structures/rules/ITtscLintCypressRuleOptions.js.map +1 -0
  33. package/lib/structures/rules/ITtscLintCypressRules.d.ts +129 -0
  34. package/lib/structures/rules/ITtscLintCypressRules.js +3 -0
  35. package/lib/structures/rules/ITtscLintCypressRules.js.map +1 -0
  36. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.d.ts +157 -0
  37. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.js +13 -0
  38. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.js.map +1 -0
  39. package/lib/structures/rules/ITtscLintFunctionalRules.d.ts +167 -0
  40. package/lib/structures/rules/ITtscLintFunctionalRules.js +3 -0
  41. package/lib/structures/rules/ITtscLintFunctionalRules.js.map +1 -0
  42. package/lib/structures/rules/ITtscLintJestRules.d.ts +201 -0
  43. package/lib/structures/rules/ITtscLintJestRules.js +3 -0
  44. package/lib/structures/rules/ITtscLintJestRules.js.map +1 -0
  45. package/lib/structures/rules/ITtscLintJsdocRules.d.ts +140 -0
  46. package/lib/structures/rules/ITtscLintJsdocRules.js +3 -0
  47. package/lib/structures/rules/ITtscLintJsdocRules.js.map +1 -0
  48. package/lib/structures/rules/ITtscLintJsxA11yRules.d.ts +302 -0
  49. package/lib/structures/rules/ITtscLintJsxA11yRules.js +3 -0
  50. package/lib/structures/rules/ITtscLintJsxA11yRules.js.map +1 -0
  51. package/lib/structures/rules/ITtscLintNextjsRules.d.ts +185 -0
  52. package/lib/structures/rules/ITtscLintNextjsRules.js +3 -0
  53. package/lib/structures/rules/ITtscLintNextjsRules.js.map +1 -0
  54. package/lib/structures/rules/ITtscLintPlaywrightRules.d.ts +330 -0
  55. package/lib/structures/rules/ITtscLintPlaywrightRules.js +3 -0
  56. package/lib/structures/rules/ITtscLintPlaywrightRules.js.map +1 -0
  57. package/lib/structures/rules/ITtscLintPromiseRules.d.ts +188 -0
  58. package/lib/structures/rules/ITtscLintPromiseRules.js +3 -0
  59. package/lib/structures/rules/ITtscLintPromiseRules.js.map +1 -0
  60. package/lib/structures/rules/ITtscLintReactPerfRuleOptions.d.ts +18 -0
  61. package/lib/structures/rules/ITtscLintReactPerfRuleOptions.js +8 -0
  62. package/lib/structures/rules/ITtscLintReactPerfRuleOptions.js.map +1 -0
  63. package/lib/structures/rules/ITtscLintReactPerfRules.d.ts +51 -0
  64. package/lib/structures/rules/ITtscLintReactPerfRules.js +3 -0
  65. package/lib/structures/rules/ITtscLintReactPerfRules.js.map +1 -0
  66. package/lib/structures/rules/ITtscLintReactRuleOptions.d.ts +39 -0
  67. package/lib/structures/rules/ITtscLintReactRuleOptions.js +12 -0
  68. package/lib/structures/rules/ITtscLintReactRuleOptions.js.map +1 -0
  69. package/lib/structures/rules/ITtscLintReactRules.d.ts +289 -0
  70. package/lib/structures/rules/ITtscLintReactRules.js +3 -0
  71. package/lib/structures/rules/ITtscLintReactRules.js.map +1 -0
  72. package/lib/structures/rules/ITtscLintRegexpRules.d.ts +190 -0
  73. package/lib/structures/rules/ITtscLintRegexpRules.js +3 -0
  74. package/lib/structures/rules/ITtscLintRegexpRules.js.map +1 -0
  75. package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +66 -0
  76. package/lib/structures/rules/ITtscLintRuleOptionsMap.js +3 -0
  77. package/lib/structures/rules/ITtscLintRuleOptionsMap.js.map +1 -0
  78. package/lib/structures/rules/ITtscLintRules.d.ts +55 -0
  79. package/lib/structures/{TtscLintRule.js → rules/ITtscLintRules.js} +1 -1
  80. package/lib/structures/rules/ITtscLintRules.js.map +1 -0
  81. package/lib/structures/rules/ITtscLintSecurityRules.d.ts +148 -0
  82. package/lib/structures/rules/ITtscLintSecurityRules.js +3 -0
  83. package/lib/structures/rules/ITtscLintSecurityRules.js.map +1 -0
  84. package/lib/structures/rules/ITtscLintSolidRules.d.ts +181 -0
  85. package/lib/structures/rules/ITtscLintSolidRules.js +3 -0
  86. package/lib/structures/rules/ITtscLintSolidRules.js.map +1 -0
  87. package/lib/structures/rules/ITtscLintStorybookRuleOptions.d.ts +21 -0
  88. package/lib/structures/rules/ITtscLintStorybookRuleOptions.js +9 -0
  89. package/lib/structures/rules/ITtscLintStorybookRuleOptions.js.map +1 -0
  90. package/lib/structures/rules/ITtscLintStorybookRules.d.ts +174 -0
  91. package/lib/structures/rules/ITtscLintStorybookRules.js +3 -0
  92. package/lib/structures/rules/ITtscLintStorybookRules.js.map +1 -0
  93. package/lib/structures/rules/ITtscLintTanstackQueryRules.d.ts +94 -0
  94. package/lib/structures/rules/ITtscLintTanstackQueryRules.js +3 -0
  95. package/lib/structures/rules/ITtscLintTanstackQueryRules.js.map +1 -0
  96. package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.d.ts +22 -0
  97. package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.js +10 -0
  98. package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.js.map +1 -0
  99. package/lib/structures/rules/ITtscLintTestingLibraryRules.d.ts +310 -0
  100. package/lib/structures/rules/ITtscLintTestingLibraryRules.js +3 -0
  101. package/lib/structures/rules/ITtscLintTestingLibraryRules.js.map +1 -0
  102. package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +1132 -0
  103. package/lib/structures/rules/ITtscLintTypeScriptRules.js +3 -0
  104. package/lib/structures/rules/ITtscLintTypeScriptRules.js.map +1 -0
  105. package/lib/structures/rules/ITtscLintUnicornRules.d.ts +997 -0
  106. package/lib/structures/rules/ITtscLintUnicornRules.js +3 -0
  107. package/lib/structures/rules/ITtscLintUnicornRules.js.map +1 -0
  108. package/lib/structures/rules/ITtscLintVitestRules.d.ts +141 -0
  109. package/lib/structures/rules/ITtscLintVitestRules.js +3 -0
  110. package/lib/structures/rules/ITtscLintVitestRules.js.map +1 -0
  111. package/lib/structures/rules/index.d.ts +31 -0
  112. package/lib/structures/rules/index.js +48 -0
  113. package/lib/structures/rules/index.js.map +1 -0
  114. package/linthost/ast_helpers.go +30 -5
  115. package/linthost/config.go +48 -17
  116. package/linthost/config_format.go +9 -9
  117. package/linthost/directives.go +67 -25
  118. package/linthost/dispatch.go +14 -3
  119. package/linthost/engine.go +152 -31
  120. package/linthost/fix.go +2 -2
  121. package/linthost/lsp.go +747 -0
  122. package/linthost/print_dispatch.go +2 -2
  123. package/linthost/print_nodes_function.go +2 -2
  124. package/linthost/print_nodes_object.go +1 -1
  125. package/linthost/rule_names.go +14 -0
  126. package/linthost/rules_arrays.go +2 -2
  127. package/linthost/rules_boundaries.go +532 -0
  128. package/linthost/rules_camelcase.go +133 -0
  129. package/linthost/rules_complexity.go +94 -0
  130. package/linthost/rules_consistent_return.go +130 -0
  131. package/linthost/rules_console.go +1 -1
  132. package/linthost/rules_core_extra.go +739 -0
  133. package/linthost/rules_curly.go +70 -0
  134. package/linthost/rules_cypress.go +608 -0
  135. package/linthost/rules_debugger.go +2 -2
  136. package/linthost/rules_default_case.go +39 -0
  137. package/linthost/rules_default_case_last.go +46 -0
  138. package/linthost/rules_dupes.go +3 -3
  139. package/linthost/rules_empty.go +3 -3
  140. package/linthost/rules_escape.go +44 -24
  141. package/linthost/rules_eval.go +2 -2
  142. package/linthost/rules_finally.go +2 -2
  143. package/linthost/rules_format_jsdoc.go +1 -1
  144. package/linthost/rules_format_print_width.go +9 -9
  145. package/linthost/rules_format_quotes.go +1 -1
  146. package/linthost/rules_format_semi.go +1 -1
  147. package/linthost/rules_format_sort_imports.go +1 -1
  148. package/linthost/rules_format_trailing_comma.go +1 -1
  149. package/linthost/rules_functional.go +967 -0
  150. package/linthost/rules_gap.go +24 -45
  151. package/linthost/rules_grouped_accessor_pairs.go +87 -0
  152. package/linthost/rules_guard_for_in.go +198 -0
  153. package/linthost/rules_id_length.go +77 -0
  154. package/linthost/rules_imports.go +70 -2
  155. package/linthost/rules_init_declarations.go +53 -0
  156. package/linthost/rules_jest.go +618 -0
  157. package/linthost/rules_jsdoc.go +502 -0
  158. package/linthost/rules_jsx_a11y.go +1264 -0
  159. package/linthost/rules_logic.go +12 -12
  160. package/linthost/rules_loops.go +1 -1
  161. package/linthost/rules_max_classes_per_file.go +58 -0
  162. package/linthost/rules_max_depth.go +83 -0
  163. package/linthost/rules_max_lines.go +101 -0
  164. package/linthost/rules_max_lines_per_function.go +57 -0
  165. package/linthost/rules_max_nested_callbacks.go +61 -0
  166. package/linthost/rules_max_params.go +49 -0
  167. package/linthost/rules_max_statements.go +59 -0
  168. package/linthost/rules_misc.go +1 -1
  169. package/linthost/rules_nextjs.go +697 -0
  170. package/linthost/rules_no_else_return.go +75 -0
  171. package/linthost/rules_no_empty_named_blocks.go +91 -0
  172. package/linthost/rules_no_extend_native.go +51 -0
  173. package/linthost/rules_no_import_assign.go +140 -0
  174. package/linthost/rules_no_invalid_this.go +52 -0
  175. package/linthost/rules_no_loop_func.go +85 -0
  176. package/linthost/rules_no_magic_numbers.go +123 -0
  177. package/linthost/rules_no_mixed_operators.go +80 -0
  178. package/linthost/rules_no_param_reassign.go +98 -0
  179. package/linthost/rules_no_redeclare.go +270 -0
  180. package/linthost/rules_no_restricted_imports.go +105 -0
  181. package/linthost/rules_no_restricted_syntax.go +49 -0
  182. package/linthost/rules_no_shadow.go +147 -0
  183. package/linthost/rules_no_unreachable.go +77 -0
  184. package/linthost/rules_no_useless_assignment.go +120 -0
  185. package/linthost/rules_no_useless_constructor.go +245 -0
  186. package/linthost/rules_no_useless_return.go +51 -0
  187. package/linthost/rules_params.go +1 -1
  188. package/linthost/rules_playwright.go +788 -0
  189. package/linthost/rules_prefer_arrow_callback.go +124 -0
  190. package/linthost/rules_prefer_destructuring.go +98 -0
  191. package/linthost/rules_prefer_named_capture_group.go +129 -0
  192. package/linthost/rules_prefer_rest_params.go +94 -0
  193. package/linthost/rules_problems.go +41 -30
  194. package/linthost/rules_promise.go +1128 -73
  195. package/linthost/rules_protos.go +2 -2
  196. package/linthost/rules_react.go +773 -0
  197. package/linthost/rules_react_extras.go +227 -0
  198. package/linthost/rules_react_hooks.go +848 -0
  199. package/linthost/rules_react_perf.go +293 -0
  200. package/linthost/rules_react_refresh.go +583 -0
  201. package/linthost/rules_regexp.go +410 -0
  202. package/linthost/rules_security.go +830 -0
  203. package/linthost/rules_self.go +2 -2
  204. package/linthost/rules_solid.go +1073 -0
  205. package/linthost/rules_sort_imports.go +69 -0
  206. package/linthost/rules_sort_keys.go +134 -0
  207. package/linthost/rules_storybook.go +1176 -0
  208. package/linthost/rules_strings.go +4 -4
  209. package/linthost/rules_suggestions.go +38 -38
  210. package/linthost/rules_tanstack_query.go +844 -0
  211. package/linthost/rules_testing_library.go +1204 -0
  212. package/linthost/rules_throw.go +1 -1
  213. package/linthost/rules_ts.go +19 -19
  214. package/linthost/rules_ts_async.go +399 -0
  215. package/linthost/rules_ts_class_literal_property_style.go +115 -0
  216. package/linthost/rules_ts_consistent_type_exports.go +256 -0
  217. package/linthost/rules_ts_explicit_function_return_type.go +67 -0
  218. package/linthost/rules_ts_extra.go +155 -36
  219. package/linthost/rules_ts_no_base_to_string.go +218 -0
  220. package/linthost/rules_ts_no_confusing_void.go +72 -0
  221. package/linthost/rules_ts_no_deprecated.go +195 -0
  222. package/linthost/rules_ts_no_for_in_array.go +84 -0
  223. package/linthost/rules_ts_no_magic_numbers.go +124 -0
  224. package/linthost/rules_ts_no_meaningless_void_operator.go +84 -0
  225. package/linthost/rules_ts_no_misused_spread.go +104 -0
  226. package/linthost/rules_ts_no_redundant_type_constituents.go +113 -0
  227. package/linthost/rules_ts_no_restricted_types.go +62 -0
  228. package/linthost/rules_ts_no_unnecessary_boolean_literal_compare.go +129 -0
  229. package/linthost/rules_ts_no_unnecessary_condition.go +250 -0
  230. package/linthost/rules_ts_no_unnecessary_qualifier.go +118 -0
  231. package/linthost/rules_ts_no_unnecessary_template_expression.go +178 -0
  232. package/linthost/rules_ts_no_unnecessary_type_arguments.go +234 -0
  233. package/linthost/rules_ts_no_unnecessary_type_assertion.go +157 -0
  234. package/linthost/rules_ts_no_unsafe_argument.go +82 -0
  235. package/linthost/rules_ts_no_unsafe_assignment.go +102 -0
  236. package/linthost/rules_ts_no_unsafe_call.go +82 -0
  237. package/linthost/rules_ts_no_unsafe_enum_comparison.go +235 -0
  238. package/linthost/rules_ts_no_unsafe_member_access.go +58 -0
  239. package/linthost/rules_ts_no_unsafe_return.go +126 -0
  240. package/linthost/rules_ts_no_unsafe_unary_minus.go +81 -0
  241. package/linthost/rules_ts_no_useless_constructor.go +52 -0
  242. package/linthost/rules_ts_non_nullable_type_assertion_style.go +108 -0
  243. package/linthost/rules_ts_parameter_properties.go +38 -0
  244. package/linthost/rules_ts_prefer_find.go +178 -0
  245. package/linthost/rules_ts_prefer_includes.go +255 -0
  246. package/linthost/rules_ts_prefer_nullish_coalescing.go +104 -0
  247. package/linthost/rules_ts_prefer_optional_chain.go +228 -0
  248. package/linthost/rules_ts_prefer_promise_reject_errors.go +176 -0
  249. package/linthost/rules_ts_prefer_readonly.go +73 -0
  250. package/linthost/rules_ts_prefer_reduce_type_parameter.go +116 -0
  251. package/linthost/rules_ts_prefer_regexp_exec.go +122 -0
  252. package/linthost/rules_ts_prefer_return_this_type.go +135 -0
  253. package/linthost/rules_ts_prefer_string_starts_ends_with.go +360 -0
  254. package/linthost/rules_ts_promise_function_async.go +77 -0
  255. package/linthost/rules_ts_related_getter_setter_pairs.go +113 -0
  256. package/linthost/rules_ts_require_array_sort_compare.go +96 -0
  257. package/linthost/rules_ts_restrict_plus_operands.go +127 -0
  258. package/linthost/rules_ts_restrict_template_expressions.go +95 -0
  259. package/linthost/rules_ts_simple.go +387 -0
  260. package/linthost/rules_ts_sort_type_constituents.go +144 -0
  261. package/linthost/rules_ts_strict_boolean_expressions.go +157 -0
  262. package/linthost/rules_ts_switch_exhaustiveness_check.go +147 -0
  263. package/linthost/rules_ts_unbound_method.go +126 -0
  264. package/linthost/rules_tsdoc.go +136 -0
  265. package/linthost/rules_unicorn_better_regex.go +27 -0
  266. package/linthost/rules_unicorn_catch_error_name.go +46 -0
  267. package/linthost/rules_unicorn_consistent_assert.go +47 -0
  268. package/linthost/rules_unicorn_consistent_date_clone.go +54 -0
  269. package/linthost/rules_unicorn_consistent_destructuring.go +25 -0
  270. package/linthost/rules_unicorn_consistent_empty_array_spread.go +62 -0
  271. package/linthost/rules_unicorn_consistent_existence_index_check.go +80 -0
  272. package/linthost/rules_unicorn_consistent_function_scoping.go +25 -0
  273. package/linthost/rules_unicorn_consistent_template_literal_escape.go +25 -0
  274. package/linthost/rules_unicorn_custom_error_definition.go +138 -0
  275. package/linthost/rules_unicorn_empty_brace_spaces.go +73 -0
  276. package/linthost/rules_unicorn_error_message.go +64 -0
  277. package/linthost/rules_unicorn_escape_case.go +51 -0
  278. package/linthost/rules_unicorn_expiring_todo_comments.go +78 -0
  279. package/linthost/rules_unicorn_explicit_length_check.go +88 -0
  280. package/linthost/rules_unicorn_filename_case.go +29 -0
  281. package/linthost/rules_unicorn_import_style.go +28 -0
  282. package/linthost/rules_unicorn_isolated_functions.go +29 -0
  283. package/linthost/rules_unicorn_new_for_builtins.go +87 -0
  284. package/linthost/rules_unicorn_no_abusive_eslint_disable.go +63 -0
  285. package/linthost/rules_unicorn_no_accessor_recursion.go +115 -0
  286. package/linthost/rules_unicorn_no_anonymous_default_export.go +74 -0
  287. package/linthost/rules_unicorn_no_array_callback_reference.go +65 -0
  288. package/linthost/rules_unicorn_no_array_for_each.go +40 -0
  289. package/linthost/rules_unicorn_no_array_method_this_argument.go +61 -0
  290. package/linthost/rules_unicorn_no_array_reduce.go +41 -0
  291. package/linthost/rules_unicorn_no_array_reverse.go +44 -0
  292. package/linthost/rules_unicorn_no_array_sort.go +43 -0
  293. package/linthost/rules_unicorn_no_await_expression_member.go +40 -0
  294. package/linthost/rules_unicorn_no_await_in_promise_methods.go +69 -0
  295. package/linthost/rules_unicorn_no_console_spaces.go +61 -0
  296. package/linthost/rules_unicorn_no_document_cookie.go +41 -0
  297. package/linthost/rules_unicorn_no_empty_file.go +44 -0
  298. package/linthost/rules_unicorn_no_for_loop.go +102 -0
  299. package/linthost/rules_unicorn_no_hex_escape.go +42 -0
  300. package/linthost/rules_unicorn_no_immediate_mutation.go +90 -0
  301. package/linthost/rules_unicorn_no_instanceof_builtins.go +66 -0
  302. package/linthost/rules_unicorn_no_invalid_fetch_options.go +79 -0
  303. package/linthost/rules_unicorn_no_invalid_remove_event_listener.go +51 -0
  304. package/linthost/rules_unicorn_no_keyword_prefix.go +101 -0
  305. package/linthost/rules_unicorn_no_lonely_if.go +44 -0
  306. package/linthost/rules_unicorn_no_magic_array_flat_depth.go +51 -0
  307. package/linthost/rules_unicorn_no_named_default.go +54 -0
  308. package/linthost/rules_unicorn_no_negated_condition.go +85 -0
  309. package/linthost/rules_unicorn_no_negation_in_equality_check.go +50 -0
  310. package/linthost/rules_unicorn_no_nested_ternary.go +43 -0
  311. package/linthost/rules_unicorn_no_new_array.go +30 -0
  312. package/linthost/rules_unicorn_no_new_buffer.go +31 -0
  313. package/linthost/rules_unicorn_no_null.go +24 -0
  314. package/linthost/rules_unicorn_no_object_as_default_parameter.go +48 -0
  315. package/linthost/rules_unicorn_no_process_exit.go +43 -0
  316. package/linthost/rules_unicorn_no_single_promise_in_promise_methods.go +59 -0
  317. package/linthost/rules_unicorn_no_static_only_class.go +56 -0
  318. package/linthost/rules_unicorn_no_thenable.go +74 -0
  319. package/linthost/rules_unicorn_no_this_assignment.go +35 -0
  320. package/linthost/rules_unicorn_no_typeof_undefined.go +58 -0
  321. package/linthost/rules_unicorn_no_unnecessary_array_flat_depth.go +49 -0
  322. package/linthost/rules_unicorn_no_unnecessary_array_splice_count.go +70 -0
  323. package/linthost/rules_unicorn_no_unnecessary_await.go +51 -0
  324. package/linthost/rules_unicorn_no_unnecessary_polyfills.go +32 -0
  325. package/linthost/rules_unicorn_no_unnecessary_slice_end.go +48 -0
  326. package/linthost/rules_unicorn_no_unreadable_array_destructuring.go +115 -0
  327. package/linthost/rules_unicorn_no_unreadable_iife.go +50 -0
  328. package/linthost/rules_unicorn_no_unused_properties.go +30 -0
  329. package/linthost/rules_unicorn_no_useless_collection_argument.go +63 -0
  330. package/linthost/rules_unicorn_no_useless_error_capture_stack_trace.go +46 -0
  331. package/linthost/rules_unicorn_no_useless_fallback_in_spread.go +81 -0
  332. package/linthost/rules_unicorn_no_useless_iterator_to_array.go +72 -0
  333. package/linthost/rules_unicorn_no_useless_length_check.go +144 -0
  334. package/linthost/rules_unicorn_no_useless_promise_resolve_reject.go +81 -0
  335. package/linthost/rules_unicorn_no_useless_spread.go +67 -0
  336. package/linthost/rules_unicorn_no_useless_switch_case.go +55 -0
  337. package/linthost/rules_unicorn_no_useless_undefined.go +55 -0
  338. package/linthost/rules_unicorn_no_zero_fractions.go +52 -0
  339. package/linthost/rules_unicorn_number_literal_case.go +69 -0
  340. package/linthost/rules_unicorn_numeric_separators_style.go +75 -0
  341. package/linthost/rules_unicorn_prefer_add_event_listener.go +66 -0
  342. package/linthost/rules_unicorn_prefer_array_find.go +51 -0
  343. package/linthost/rules_unicorn_prefer_array_flat.go +52 -0
  344. package/linthost/rules_unicorn_prefer_array_flat_map.go +63 -0
  345. package/linthost/rules_unicorn_prefer_array_index_of.go +147 -0
  346. package/linthost/rules_unicorn_prefer_array_some.go +67 -0
  347. package/linthost/rules_unicorn_prefer_at.go +77 -0
  348. package/linthost/rules_unicorn_prefer_bigint_literals.go +77 -0
  349. package/linthost/rules_unicorn_prefer_blob_reading_methods.go +44 -0
  350. package/linthost/rules_unicorn_prefer_class_fields.go +93 -0
  351. package/linthost/rules_unicorn_prefer_classlist_toggle.go +120 -0
  352. package/linthost/rules_unicorn_prefer_code_point.go +49 -0
  353. package/linthost/rules_unicorn_prefer_date_now.go +78 -0
  354. package/linthost/rules_unicorn_prefer_default_parameters.go +144 -0
  355. package/linthost/rules_unicorn_prefer_dom_node_append.go +40 -0
  356. package/linthost/rules_unicorn_prefer_dom_node_dataset.go +55 -0
  357. package/linthost/rules_unicorn_prefer_dom_node_remove.go +41 -0
  358. package/linthost/rules_unicorn_prefer_dom_node_text_content.go +37 -0
  359. package/linthost/rules_unicorn_prefer_event_target.go +36 -0
  360. package/linthost/rules_unicorn_prefer_export_from.go +129 -0
  361. package/linthost/rules_unicorn_prefer_global_this.go +127 -0
  362. package/linthost/rules_unicorn_prefer_import_meta_properties.go +66 -0
  363. package/linthost/rules_unicorn_prefer_includes.go +149 -0
  364. package/linthost/rules_unicorn_prefer_json_parse_buffer.go +65 -0
  365. package/linthost/rules_unicorn_prefer_keyboard_event_key.go +38 -0
  366. package/linthost/rules_unicorn_prefer_logical_operator_over_ternary.go +48 -0
  367. package/linthost/rules_unicorn_prefer_math_min_max.go +60 -0
  368. package/linthost/rules_unicorn_prefer_math_trunc.go +62 -0
  369. package/linthost/rules_unicorn_prefer_modern_dom_apis.go +47 -0
  370. package/linthost/rules_unicorn_prefer_modern_math_apis.go +70 -0
  371. package/linthost/rules_unicorn_prefer_module.go +50 -0
  372. package/linthost/rules_unicorn_prefer_native_coercion_functions.go +114 -0
  373. package/linthost/rules_unicorn_prefer_negative_index.go +74 -0
  374. package/linthost/rules_unicorn_prefer_node_protocol.go +122 -0
  375. package/linthost/rules_unicorn_prefer_number_properties.go +88 -0
  376. package/linthost/rules_unicorn_prefer_object_from_entries.go +55 -0
  377. package/linthost/rules_unicorn_prefer_optional_catch_binding.go +62 -0
  378. package/linthost/rules_unicorn_prefer_prototype_methods.go +54 -0
  379. package/linthost/rules_unicorn_prefer_query_selector.go +56 -0
  380. package/linthost/rules_unicorn_prefer_reflect_apply.go +36 -0
  381. package/linthost/rules_unicorn_prefer_regexp_test_rule.go +80 -0
  382. package/linthost/rules_unicorn_prefer_response_static_json.go +53 -0
  383. package/linthost/rules_unicorn_prefer_set_has.go +42 -0
  384. package/linthost/rules_unicorn_prefer_set_size.go +48 -0
  385. package/linthost/rules_unicorn_prefer_simple_condition_first.go +34 -0
  386. package/linthost/rules_unicorn_prefer_single_call.go +96 -0
  387. package/linthost/rules_unicorn_prefer_spread.go +51 -0
  388. package/linthost/rules_unicorn_prefer_string_raw.go +43 -0
  389. package/linthost/rules_unicorn_prefer_string_replace_all.go +95 -0
  390. package/linthost/rules_unicorn_prefer_string_slice.go +43 -0
  391. package/linthost/rules_unicorn_prefer_string_starts_ends_with.go +148 -0
  392. package/linthost/rules_unicorn_prefer_string_trim_start_end.go +48 -0
  393. package/linthost/rules_unicorn_prefer_structured_clone.go +71 -0
  394. package/linthost/rules_unicorn_prefer_switch.go +103 -0
  395. package/linthost/rules_unicorn_prefer_ternary.go +71 -0
  396. package/linthost/rules_unicorn_prefer_top_level_await.go +72 -0
  397. package/linthost/rules_unicorn_prefer_type_error.go +127 -0
  398. package/linthost/rules_unicorn_prevent_abbreviations.go +127 -0
  399. package/linthost/rules_unicorn_relative_url_style.go +49 -0
  400. package/linthost/rules_unicorn_require_array_join_separator.go +45 -0
  401. package/linthost/rules_unicorn_require_module_attributes.go +54 -0
  402. package/linthost/rules_unicorn_require_module_specifiers.go +55 -0
  403. package/linthost/rules_unicorn_require_number_to_fixed_digits_argument.go +43 -0
  404. package/linthost/rules_unicorn_require_post_message_target_origin.go +44 -0
  405. package/linthost/rules_unicorn_string_content.go +27 -0
  406. package/linthost/rules_unicorn_switch_case_braces.go +42 -0
  407. package/linthost/rules_unicorn_switch_case_break_position.go +31 -0
  408. package/linthost/rules_unicorn_template_indent.go +28 -0
  409. package/linthost/rules_unicorn_text_encoding_identifier_case.go +72 -0
  410. package/linthost/rules_unicorn_throw_new_error.go +61 -0
  411. package/linthost/rules_var.go +4 -4
  412. package/linthost/rules_vitest.go +568 -0
  413. package/package.json +3 -3
  414. package/plugin/main.go +49 -3
  415. package/rule/astutil/astutil.go +10 -3
  416. package/rule/rule.go +12 -10
  417. package/src/defaultFormat.ts +11 -8
  418. package/src/index.ts +40 -16
  419. package/src/structures/ITtscLintConfig.ts +12 -6
  420. package/src/structures/{ITtscLintFormatConfig.ts → ITtscLintFormat.ts} +2 -2
  421. package/src/structures/TtscLintRuleSetting.ts +55 -0
  422. package/src/structures/index.ts +3 -4
  423. package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +128 -0
  424. package/src/structures/rules/ITtscLintBoundariesRules.ts +95 -0
  425. package/src/structures/rules/ITtscLintContributorRules.ts +21 -0
  426. package/src/structures/rules/ITtscLintCoreRules.ts +1406 -0
  427. package/src/structures/rules/ITtscLintCypressRuleOptions.ts +18 -0
  428. package/src/structures/rules/ITtscLintCypressRules.ts +145 -0
  429. package/src/structures/rules/ITtscLintFunctionalRuleOptions.ts +213 -0
  430. package/src/structures/rules/ITtscLintFunctionalRules.ts +202 -0
  431. package/src/structures/rules/ITtscLintJestRules.ts +220 -0
  432. package/src/structures/rules/ITtscLintJsdocRules.ts +153 -0
  433. package/src/structures/rules/ITtscLintJsxA11yRules.ts +339 -0
  434. package/src/structures/rules/ITtscLintNextjsRules.ts +206 -0
  435. package/src/structures/rules/ITtscLintPlaywrightRules.ts +361 -0
  436. package/src/structures/rules/ITtscLintPromiseRules.ts +205 -0
  437. package/src/structures/rules/ITtscLintReactPerfRuleOptions.ts +19 -0
  438. package/src/structures/rules/ITtscLintReactPerfRules.ts +55 -0
  439. package/src/structures/rules/ITtscLintReactRuleOptions.ts +43 -0
  440. package/src/structures/rules/ITtscLintReactRules.ts +321 -0
  441. package/src/structures/rules/ITtscLintRegexpRules.ts +212 -0
  442. package/src/structures/rules/ITtscLintRuleOptionsMap.ts +89 -0
  443. package/src/structures/rules/ITtscLintRules.ts +77 -0
  444. package/src/structures/rules/ITtscLintSecurityRules.ts +162 -0
  445. package/src/structures/rules/ITtscLintSolidRules.ts +202 -0
  446. package/src/structures/rules/ITtscLintStorybookRuleOptions.ts +23 -0
  447. package/src/structures/rules/ITtscLintStorybookRules.ts +193 -0
  448. package/src/structures/rules/ITtscLintTanstackQueryRules.ts +102 -0
  449. package/src/structures/rules/ITtscLintTestingLibraryRuleOptions.ts +24 -0
  450. package/src/structures/rules/ITtscLintTestingLibraryRules.ts +342 -0
  451. package/src/structures/rules/ITtscLintTypeScriptRules.ts +1230 -0
  452. package/src/structures/rules/ITtscLintUnicornRules.ts +1143 -0
  453. package/src/structures/rules/ITtscLintVitestRules.ts +154 -0
  454. package/src/structures/rules/index.ts +31 -0
  455. package/lib/structures/ITtscLintFormatConfig.js.map +0 -1
  456. package/lib/structures/TtscLintRule.d.ts +0 -17
  457. package/lib/structures/TtscLintRule.js.map +0 -1
  458. package/lib/structures/TtscLintRuleMap.d.ts +0 -46
  459. package/lib/structures/TtscLintRuleMap.js.map +0 -1
  460. package/lib/structures/TtscLintRuleOptions.d.ts +0 -137
  461. package/lib/structures/TtscLintRuleOptions.js.map +0 -1
  462. package/src/structures/TtscLintRule.ts +0 -160
  463. package/src/structures/TtscLintRuleMap.ts +0 -55
  464. package/src/structures/TtscLintRuleOptions.ts +0 -151
@@ -0,0 +1,1176 @@
1
+ // Storybook CSF and interaction rules. These are AST-only ports of the
2
+ // public eslint-plugin-storybook rule surface that applies to TypeScript,
3
+ // TSX story files, and `.storybook/main.ts` addon configs.
4
+ package linthost
5
+
6
+ import (
7
+ "encoding/json"
8
+ "os"
9
+ "path/filepath"
10
+ "regexp"
11
+ "strings"
12
+ "unicode"
13
+
14
+ shimast "github.com/microsoft/typescript-go/shim/ast"
15
+ )
16
+
17
+ type storybookAwaitInteractions struct{}
18
+
19
+ func (storybookAwaitInteractions) Name() string { return "storybook/await-interactions" }
20
+ func (storybookAwaitInteractions) Visits() []shimast.Kind {
21
+ return []shimast.Kind{shimast.KindSourceFile}
22
+ }
23
+ func (storybookAwaitInteractions) Check(ctx *Context, node *shimast.Node) {
24
+ walkDescendants(node, func(child *shimast.Node) {
25
+ if child == nil || child.Kind != shimast.KindCallExpression || storybookCallIsAwaited(child) || storybookCallIsReturned(child) {
26
+ return
27
+ }
28
+ call := child.AsCallExpression()
29
+ if call == nil {
30
+ return
31
+ }
32
+ if method := storybookAwaitedMethodName(call); method != "" {
33
+ ctx.Report(child, "Interaction should be awaited: "+method+".")
34
+ }
35
+ })
36
+ }
37
+
38
+ type storybookContextInPlayFunction struct{}
39
+
40
+ func (storybookContextInPlayFunction) Name() string { return "storybook/context-in-play-function" }
41
+ func (storybookContextInPlayFunction) Visits() []shimast.Kind {
42
+ return []shimast.Kind{shimast.KindSourceFile}
43
+ }
44
+ func (storybookContextInPlayFunction) Check(ctx *Context, node *shimast.Node) {
45
+ walkDescendants(node, func(child *shimast.Node) {
46
+ if child == nil || child.Kind != shimast.KindCallExpression {
47
+ return
48
+ }
49
+ call := child.AsCallExpression()
50
+ if call == nil || !storybookIsPlayCall(call.Expression) {
51
+ return
52
+ }
53
+ contextName := storybookEnclosingFunctionContextName(child)
54
+ if contextName == "" || !storybookCallPassesContext(call, contextName) {
55
+ ctx.Report(child, "Pass a context when invoking play function of another story.")
56
+ }
57
+ })
58
+ }
59
+
60
+ type storybookCsfComponent struct{}
61
+
62
+ func (storybookCsfComponent) Name() string { return "storybook/csf-component" }
63
+ func (storybookCsfComponent) Visits() []shimast.Kind {
64
+ return []shimast.Kind{shimast.KindSourceFile}
65
+ }
66
+ func (storybookCsfComponent) Check(ctx *Context, node *shimast.Node) {
67
+ meta := storybookDefaultMeta(ctx.File)
68
+ if meta == nil || meta.Object == nil {
69
+ return
70
+ }
71
+ if _, _, ok := storybookObjectProperty(meta.Object, "component"); !ok {
72
+ ctx.Report(meta.Node, "Missing component property.")
73
+ }
74
+ }
75
+
76
+ type storybookDefaultExports struct{}
77
+
78
+ func (storybookDefaultExports) Name() string { return "storybook/default-exports" }
79
+ func (storybookDefaultExports) Visits() []shimast.Kind {
80
+ return []shimast.Kind{shimast.KindSourceFile}
81
+ }
82
+ func (storybookDefaultExports) Check(ctx *Context, node *shimast.Node) {
83
+ if storybookHasDefaultExport(ctx.File) || storybookHasStoriesOfImport(ctx.File) || storybookHasCsf4MetaCall(ctx.File) {
84
+ return
85
+ }
86
+ ctx.Report(storybookFirstNonImportStatement(ctx.File, node), "The file should have a default export.")
87
+ }
88
+
89
+ type storybookHierarchySeparator struct{}
90
+
91
+ func (storybookHierarchySeparator) Name() string { return "storybook/hierarchy-separator" }
92
+ func (storybookHierarchySeparator) Visits() []shimast.Kind {
93
+ return []shimast.Kind{shimast.KindSourceFile}
94
+ }
95
+ func (storybookHierarchySeparator) Check(ctx *Context, node *shimast.Node) {
96
+ meta := storybookDefaultMeta(ctx.File)
97
+ if meta == nil || meta.Object == nil {
98
+ return
99
+ }
100
+ prop, value, ok := storybookObjectProperty(meta.Object, "title")
101
+ if !ok || !strings.Contains(storybookLiteralString(value), "|") {
102
+ return
103
+ }
104
+ ctx.Report(prop, "Deprecated hierarchy separator in title property.")
105
+ }
106
+
107
+ type storybookMetaInlineProperties struct{}
108
+
109
+ func (storybookMetaInlineProperties) Name() string { return "storybook/meta-inline-properties" }
110
+ func (storybookMetaInlineProperties) Visits() []shimast.Kind {
111
+ return []shimast.Kind{shimast.KindSourceFile}
112
+ }
113
+ func (storybookMetaInlineProperties) Check(ctx *Context, node *shimast.Node) {
114
+ meta := storybookDefaultMeta(ctx.File)
115
+ if meta == nil || meta.Object == nil {
116
+ return
117
+ }
118
+ for _, name := range []string{"title", "args"} {
119
+ prop, value, ok := storybookObjectProperty(meta.Object, name)
120
+ if ok && !storybookIsInlineMetaValue(value) {
121
+ ctx.Report(prop, "Meta should only have inline properties: "+name+".")
122
+ }
123
+ }
124
+ }
125
+
126
+ type storybookMetaSatisfiesType struct{}
127
+
128
+ func (storybookMetaSatisfiesType) Name() string { return "storybook/meta-satisfies-type" }
129
+ func (storybookMetaSatisfiesType) Visits() []shimast.Kind {
130
+ return []shimast.Kind{shimast.KindSourceFile}
131
+ }
132
+ func (storybookMetaSatisfiesType) Check(ctx *Context, node *shimast.Node) {
133
+ meta := storybookDefaultMeta(ctx.File)
134
+ if meta == nil || meta.Object == nil || meta.Satisfies {
135
+ return
136
+ }
137
+ ctx.Report(meta.Object, "CSF Meta should use `satisfies` for type safety.")
138
+ }
139
+
140
+ type storybookNoRedundantStoryName struct{}
141
+
142
+ func (storybookNoRedundantStoryName) Name() string { return "storybook/no-redundant-story-name" }
143
+ func (storybookNoRedundantStoryName) Visits() []shimast.Kind {
144
+ return []shimast.Kind{shimast.KindSourceFile}
145
+ }
146
+ func (storybookNoRedundantStoryName) Check(ctx *Context, node *shimast.Node) {
147
+ for _, story := range storybookNamedExports(ctx.File) {
148
+ if story.Init == nil || story.Init.Kind != shimast.KindObjectLiteralExpression {
149
+ continue
150
+ }
151
+ for _, propName := range []string{"name", "storyName"} {
152
+ prop, value, ok := storybookObjectProperty(story.Init, propName)
153
+ if ok && storybookLiteralString(value) == storybookNameFromExport(story.Name) {
154
+ ctx.Report(prop, "Named exports should not use a redundant story name annotation.")
155
+ }
156
+ }
157
+ }
158
+ walkDescendants(node, func(child *shimast.Node) {
159
+ if child == nil || child.Kind != shimast.KindBinaryExpression {
160
+ return
161
+ }
162
+ expr := child.AsBinaryExpression()
163
+ if expr == nil || expr.OperatorToken == nil || expr.OperatorToken.Kind != shimast.KindEqualsToken {
164
+ return
165
+ }
166
+ objectName, propName := storybookPropertyAccessParts(expr.Left)
167
+ if propName == "storyName" && storybookLiteralString(expr.Right) == storybookNameFromExport(objectName) {
168
+ ctx.Report(child, "Named exports should not use a redundant story name annotation.")
169
+ }
170
+ })
171
+ }
172
+
173
+ type storybookNoRendererPackages struct{}
174
+
175
+ func (storybookNoRendererPackages) Name() string { return "storybook/no-renderer-packages" }
176
+ func (storybookNoRendererPackages) Visits() []shimast.Kind {
177
+ return []shimast.Kind{shimast.KindImportDeclaration}
178
+ }
179
+ func (storybookNoRendererPackages) Check(ctx *Context, node *shimast.Node) {
180
+ decl := node.AsImportDeclaration()
181
+ if decl == nil {
182
+ return
183
+ }
184
+ if _, ok := storybookRendererPackages[storybookLiteralString(decl.ModuleSpecifier)]; ok {
185
+ ctx.Report(node, "Do not import Storybook renderer packages directly. Use a framework package instead.")
186
+ }
187
+ }
188
+
189
+ type storybookNoStoriesOf struct{}
190
+
191
+ func (storybookNoStoriesOf) Name() string { return "storybook/no-stories-of" }
192
+ func (storybookNoStoriesOf) Visits() []shimast.Kind {
193
+ return []shimast.Kind{shimast.KindImportSpecifier}
194
+ }
195
+ func (storybookNoStoriesOf) Check(ctx *Context, node *shimast.Node) {
196
+ spec := node.AsImportSpecifier()
197
+ if spec != nil && storybookImportSpecifierImportedName(spec) == "storiesOf" {
198
+ ctx.Report(node, "storiesOf is deprecated and should not be used.")
199
+ }
200
+ }
201
+
202
+ type storybookNoTitlePropertyInMeta struct{}
203
+
204
+ func (storybookNoTitlePropertyInMeta) Name() string { return "storybook/no-title-property-in-meta" }
205
+ func (storybookNoTitlePropertyInMeta) Visits() []shimast.Kind {
206
+ return []shimast.Kind{shimast.KindSourceFile}
207
+ }
208
+ func (storybookNoTitlePropertyInMeta) Check(ctx *Context, node *shimast.Node) {
209
+ meta := storybookDefaultMeta(ctx.File)
210
+ if meta == nil || meta.Object == nil {
211
+ return
212
+ }
213
+ if prop, _, ok := storybookObjectProperty(meta.Object, "title"); ok {
214
+ ctx.Report(prop, "CSF3 does not need a title in meta.")
215
+ }
216
+ }
217
+
218
+ type storybookNoUninstalledAddons struct{}
219
+
220
+ type storybookNoUninstalledAddonsOptions struct {
221
+ PackageJSONLocation string `json:"packageJsonLocation"`
222
+ Ignore []string `json:"ignore"`
223
+ }
224
+
225
+ func (storybookNoUninstalledAddons) Name() string { return "storybook/no-uninstalled-addons" }
226
+ func (storybookNoUninstalledAddons) Visits() []shimast.Kind {
227
+ return []shimast.Kind{shimast.KindSourceFile}
228
+ }
229
+ func (storybookNoUninstalledAddons) Check(ctx *Context, node *shimast.Node) {
230
+ opts := storybookNoUninstalledAddonsOptions{}
231
+ _ = ctx.DecodeOptions(&opts)
232
+ installed := storybookInstalledPackages(ctx.File, opts.PackageJSONLocation)
233
+ if len(installed) == 0 {
234
+ return
235
+ }
236
+ ignore := map[string]struct{}{}
237
+ for _, item := range opts.Ignore {
238
+ ignore[item] = struct{}{}
239
+ }
240
+ for _, addon := range storybookAddonEntries(ctx.File) {
241
+ normalized := storybookNormalizeAddonName(addon.Name)
242
+ if normalized == "" || storybookIsLocalAddon(addon.Name) {
243
+ continue
244
+ }
245
+ if _, ok := ignore[addon.Name]; ok {
246
+ continue
247
+ }
248
+ if _, ok := installed[normalized]; !ok {
249
+ ctx.Report(addon.Node, "Storybook addon "+addon.Name+" is not installed in package.json.")
250
+ }
251
+ }
252
+ }
253
+
254
+ type storybookPreferPascalCase struct{}
255
+
256
+ func (storybookPreferPascalCase) Name() string { return "storybook/prefer-pascal-case" }
257
+ func (storybookPreferPascalCase) Visits() []shimast.Kind {
258
+ return []shimast.Kind{shimast.KindSourceFile}
259
+ }
260
+ func (storybookPreferPascalCase) Check(ctx *Context, node *shimast.Node) {
261
+ if storybookHasStoriesOfImport(ctx.File) {
262
+ return
263
+ }
264
+ filters := storybookStoryFilters(storybookDefaultMeta(ctx.File))
265
+ for _, story := range storybookNamedExports(ctx.File) {
266
+ if story.Name == "" || strings.HasPrefix(story.Name, "_") || !storybookIsStoryExport(story.Name, filters) {
267
+ continue
268
+ }
269
+ if !storybookIsPascalCase(story.Name) {
270
+ ctx.Report(story.Node, "The story should use PascalCase notation: "+story.Name+".")
271
+ }
272
+ }
273
+ }
274
+
275
+ type storybookStoryExports struct{}
276
+
277
+ func (storybookStoryExports) Name() string { return "storybook/story-exports" }
278
+ func (storybookStoryExports) Visits() []shimast.Kind {
279
+ return []shimast.Kind{shimast.KindSourceFile}
280
+ }
281
+ func (storybookStoryExports) Check(ctx *Context, node *shimast.Node) {
282
+ meta := storybookDefaultMeta(ctx.File)
283
+ if meta == nil || storybookHasStoriesOfImport(ctx.File) {
284
+ return
285
+ }
286
+ filters := storybookStoryFilters(meta)
287
+ for _, story := range storybookNamedExports(ctx.File) {
288
+ if storybookIsStoryExport(story.Name, filters) {
289
+ return
290
+ }
291
+ }
292
+ ctx.Report(storybookFirstNonImportStatement(ctx.File, node), "The file should have at least one story export.")
293
+ }
294
+
295
+ type storybookUseStorybookExpect struct{}
296
+
297
+ func (storybookUseStorybookExpect) Name() string { return "storybook/use-storybook-expect" }
298
+ func (storybookUseStorybookExpect) Visits() []shimast.Kind {
299
+ return []shimast.Kind{shimast.KindSourceFile}
300
+ }
301
+ func (storybookUseStorybookExpect) Check(ctx *Context, node *shimast.Node) {
302
+ if storybookHasNamedImport(ctx.File, "expect", "@storybook/test", "storybook/test", "@storybook/jest") {
303
+ return
304
+ }
305
+ walkDescendants(node, func(child *shimast.Node) {
306
+ if child == nil || child.Kind != shimast.KindCallExpression {
307
+ return
308
+ }
309
+ call := child.AsCallExpression()
310
+ if call != nil && identifierText(call.Expression) == "expect" {
311
+ ctx.Report(call.Expression, "Do not use global expect directly in the story. Import it from @storybook/test instead.")
312
+ }
313
+ })
314
+ }
315
+
316
+ type storybookUseStorybookTestingLibrary struct{}
317
+
318
+ func (storybookUseStorybookTestingLibrary) Name() string {
319
+ return "storybook/use-storybook-testing-library"
320
+ }
321
+ func (storybookUseStorybookTestingLibrary) Visits() []shimast.Kind {
322
+ return []shimast.Kind{shimast.KindImportDeclaration}
323
+ }
324
+ func (storybookUseStorybookTestingLibrary) Check(ctx *Context, node *shimast.Node) {
325
+ decl := node.AsImportDeclaration()
326
+ if decl == nil {
327
+ return
328
+ }
329
+ module := storybookLiteralString(decl.ModuleSpecifier)
330
+ if strings.Contains(module, "@testing-library") {
331
+ ctx.Report(node, "Do not use "+module+" directly in the story. Import from storybook/test instead.")
332
+ }
333
+ }
334
+
335
+ type storybookMetaInfo struct {
336
+ File *shimast.SourceFile
337
+ Node *shimast.Node
338
+ Object *shimast.Node
339
+ Satisfies bool
340
+ }
341
+
342
+ type storybookNamedExport struct {
343
+ Name string
344
+ Node *shimast.Node
345
+ Init *shimast.Node
346
+ }
347
+
348
+ type storybookStoryFiltersInfo struct {
349
+ Include []storybookDescriptor
350
+ Exclude []storybookDescriptor
351
+ }
352
+
353
+ type storybookDescriptor struct {
354
+ Text string
355
+ Pattern *regexp.Regexp
356
+ }
357
+
358
+ type storybookAddonEntry struct {
359
+ Name string
360
+ Node *shimast.Node
361
+ }
362
+
363
+ var storybookRendererPackages = map[string][]string{
364
+ "@storybook/html": {"@storybook/html-vite", "@storybook/html-webpack5"},
365
+ "@storybook/preact": {"@storybook/preact-vite", "@storybook/preact-webpack5"},
366
+ "@storybook/react": {"@storybook/nextjs", "@storybook/react-vite", "@storybook/react-webpack5"},
367
+ "@storybook/server": {"@storybook/server-webpack5"},
368
+ "@storybook/svelte": {"@storybook/svelte-vite", "@storybook/svelte-webpack5", "@storybook/sveltekit"},
369
+ "@storybook/vue3": {"@storybook/vue3-vite", "@storybook/vue3-webpack5"},
370
+ "@storybook/web-components": {"@storybook/web-components-vite", "@storybook/web-components-webpack5"},
371
+ }
372
+
373
+ func storybookDefaultMeta(file *shimast.SourceFile) *storybookMetaInfo {
374
+ if file == nil || file.Statements == nil {
375
+ return nil
376
+ }
377
+ variables := storybookTopLevelVariables(file)
378
+ for _, stmt := range file.Statements.Nodes {
379
+ if stmt == nil {
380
+ continue
381
+ }
382
+ if stmt.Kind == shimast.KindExportAssignment {
383
+ assignment := stmt.AsExportAssignment()
384
+ if assignment == nil || assignment.IsExportEquals {
385
+ continue
386
+ }
387
+ expr := assignment.Expression
388
+ if name := identifierText(expr); name != "" {
389
+ expr = variables[name]
390
+ }
391
+ object, satisfies := storybookMetaObjectExpression(expr)
392
+ if object != nil {
393
+ return &storybookMetaInfo{File: file, Node: stmt, Object: object, Satisfies: satisfies}
394
+ }
395
+ }
396
+ if hasModifier(stmt, shimast.KindExportKeyword) && hasModifier(stmt, shimast.KindDefaultKeyword) {
397
+ if stmt.Kind == shimast.KindVariableStatement {
398
+ if found := storybookDefaultMetaFromVariableStatement(file, stmt); found != nil {
399
+ return found
400
+ }
401
+ }
402
+ }
403
+ }
404
+ return nil
405
+ }
406
+
407
+ func storybookDefaultMetaFromVariableStatement(file *shimast.SourceFile, stmt *shimast.Node) *storybookMetaInfo {
408
+ varStmt := stmt.AsVariableStatement()
409
+ if varStmt == nil || varStmt.DeclarationList == nil {
410
+ return nil
411
+ }
412
+ list := varStmt.DeclarationList.AsVariableDeclarationList()
413
+ if list == nil || list.Declarations == nil || len(list.Declarations.Nodes) == 0 {
414
+ return nil
415
+ }
416
+ decl := list.Declarations.Nodes[0].AsVariableDeclaration()
417
+ if decl == nil {
418
+ return nil
419
+ }
420
+ object, satisfies := storybookMetaObjectExpression(decl.Initializer)
421
+ if object == nil {
422
+ return nil
423
+ }
424
+ return &storybookMetaInfo{File: file, Node: stmt, Object: object, Satisfies: satisfies}
425
+ }
426
+
427
+ func storybookMetaObjectExpression(expr *shimast.Node) (*shimast.Node, bool) {
428
+ satisfies := false
429
+ for expr != nil {
430
+ switch expr.Kind {
431
+ case shimast.KindParenthesizedExpression:
432
+ paren := expr.AsParenthesizedExpression()
433
+ if paren == nil {
434
+ return nil, satisfies
435
+ }
436
+ expr = paren.Expression
437
+ case shimast.KindAsExpression:
438
+ as := expr.AsAsExpression()
439
+ if as == nil {
440
+ return nil, satisfies
441
+ }
442
+ expr = as.Expression
443
+ case shimast.KindSatisfiesExpression:
444
+ sat := expr.AsSatisfiesExpression()
445
+ if sat == nil {
446
+ return nil, satisfies
447
+ }
448
+ satisfies = true
449
+ expr = sat.Expression
450
+ case shimast.KindTypeAssertionExpression:
451
+ assertion := expr.AsTypeAssertion()
452
+ if assertion == nil {
453
+ return nil, satisfies
454
+ }
455
+ expr = assertion.Expression
456
+ default:
457
+ if expr.Kind == shimast.KindObjectLiteralExpression {
458
+ return expr, satisfies
459
+ }
460
+ return nil, satisfies
461
+ }
462
+ }
463
+ return nil, satisfies
464
+ }
465
+
466
+ func storybookHasDefaultExport(file *shimast.SourceFile) bool {
467
+ if file == nil || file.Statements == nil {
468
+ return false
469
+ }
470
+ for _, stmt := range file.Statements.Nodes {
471
+ if stmt == nil {
472
+ continue
473
+ }
474
+ if stmt.Kind == shimast.KindExportAssignment {
475
+ assignment := stmt.AsExportAssignment()
476
+ if assignment != nil && !assignment.IsExportEquals {
477
+ return true
478
+ }
479
+ }
480
+ if hasModifier(stmt, shimast.KindExportKeyword) && hasModifier(stmt, shimast.KindDefaultKeyword) {
481
+ return true
482
+ }
483
+ if storybookExportsDefaultSpecifier(stmt) {
484
+ return true
485
+ }
486
+ }
487
+ return false
488
+ }
489
+
490
+ func storybookExportsDefaultSpecifier(stmt *shimast.Node) bool {
491
+ if stmt == nil || stmt.Kind != shimast.KindExportDeclaration {
492
+ return false
493
+ }
494
+ decl := stmt.AsExportDeclaration()
495
+ if decl == nil || decl.ExportClause == nil || decl.ExportClause.Kind != shimast.KindNamedExports {
496
+ return false
497
+ }
498
+ exports := decl.ExportClause.AsNamedExports()
499
+ if exports == nil || exports.Elements == nil {
500
+ return false
501
+ }
502
+ for _, item := range exports.Elements.Nodes {
503
+ spec := item.AsExportSpecifier()
504
+ if spec != nil && identifierText(spec.Name()) == "default" {
505
+ return true
506
+ }
507
+ }
508
+ return false
509
+ }
510
+
511
+ func storybookTopLevelVariables(file *shimast.SourceFile) map[string]*shimast.Node {
512
+ variables := map[string]*shimast.Node{}
513
+ if file == nil || file.Statements == nil {
514
+ return variables
515
+ }
516
+ for _, stmt := range file.Statements.Nodes {
517
+ if stmt == nil || stmt.Kind != shimast.KindVariableStatement {
518
+ continue
519
+ }
520
+ varStmt := stmt.AsVariableStatement()
521
+ if varStmt == nil || varStmt.DeclarationList == nil {
522
+ continue
523
+ }
524
+ list := varStmt.DeclarationList.AsVariableDeclarationList()
525
+ if list == nil || list.Declarations == nil {
526
+ continue
527
+ }
528
+ for _, item := range list.Declarations.Nodes {
529
+ decl := item.AsVariableDeclaration()
530
+ if decl != nil {
531
+ if name := identifierText(decl.Name()); name != "" {
532
+ variables[name] = decl.Initializer
533
+ }
534
+ }
535
+ }
536
+ }
537
+ return variables
538
+ }
539
+
540
+ func storybookObjectProperty(object *shimast.Node, name string) (*shimast.Node, *shimast.Node, bool) {
541
+ if object == nil || object.Kind != shimast.KindObjectLiteralExpression {
542
+ return nil, nil, false
543
+ }
544
+ obj := object.AsObjectLiteralExpression()
545
+ if obj == nil || obj.Properties == nil {
546
+ return nil, nil, false
547
+ }
548
+ for _, prop := range obj.Properties.Nodes {
549
+ if prop == nil || prop.Kind != shimast.KindPropertyAssignment {
550
+ continue
551
+ }
552
+ assignment := prop.AsPropertyAssignment()
553
+ if assignment == nil || storybookPropertyName(assignment.Name()) != name {
554
+ continue
555
+ }
556
+ return prop, assignment.Initializer, true
557
+ }
558
+ for _, prop := range obj.Properties.Nodes {
559
+ if prop == nil || prop.Kind != shimast.KindShorthandPropertyAssignment {
560
+ continue
561
+ }
562
+ assignment := prop.AsShorthandPropertyAssignment()
563
+ if assignment != nil && storybookPropertyName(assignment.Name()) == name {
564
+ return prop, nil, true
565
+ }
566
+ }
567
+ return nil, nil, false
568
+ }
569
+
570
+ func storybookPropertyName(node *shimast.Node) string {
571
+ if text := identifierText(node); text != "" {
572
+ return text
573
+ }
574
+ return storybookLiteralString(node)
575
+ }
576
+
577
+ func storybookLiteralString(node *shimast.Node) string {
578
+ node = stripParens(node)
579
+ if node == nil {
580
+ return ""
581
+ }
582
+ switch node.Kind {
583
+ case shimast.KindStringLiteral:
584
+ if lit := node.AsStringLiteral(); lit != nil {
585
+ return lit.Text
586
+ }
587
+ case shimast.KindNoSubstitutionTemplateLiteral:
588
+ if lit := node.AsNoSubstitutionTemplateLiteral(); lit != nil {
589
+ return lit.Text
590
+ }
591
+ }
592
+ return ""
593
+ }
594
+
595
+ func storybookIsInlineMetaValue(node *shimast.Node) bool {
596
+ node = stripParens(node)
597
+ if node == nil {
598
+ return false
599
+ }
600
+ switch node.Kind {
601
+ case shimast.KindObjectLiteralExpression,
602
+ shimast.KindArrayLiteralExpression,
603
+ shimast.KindStringLiteral,
604
+ shimast.KindNoSubstitutionTemplateLiteral,
605
+ shimast.KindNumericLiteral,
606
+ shimast.KindTrueKeyword,
607
+ shimast.KindFalseKeyword,
608
+ shimast.KindNullKeyword:
609
+ return true
610
+ }
611
+ return false
612
+ }
613
+
614
+ func storybookNamedExports(file *shimast.SourceFile) []storybookNamedExport {
615
+ if file == nil || file.Statements == nil {
616
+ return nil
617
+ }
618
+ exports := []storybookNamedExport{}
619
+ for _, stmt := range file.Statements.Nodes {
620
+ if stmt == nil {
621
+ continue
622
+ }
623
+ switch stmt.Kind {
624
+ case shimast.KindVariableStatement:
625
+ if !hasModifier(stmt, shimast.KindExportKeyword) || hasModifier(stmt, shimast.KindDefaultKeyword) {
626
+ continue
627
+ }
628
+ varStmt := stmt.AsVariableStatement()
629
+ if varStmt == nil || varStmt.DeclarationList == nil {
630
+ continue
631
+ }
632
+ list := varStmt.DeclarationList.AsVariableDeclarationList()
633
+ if list == nil || list.Declarations == nil {
634
+ continue
635
+ }
636
+ for _, item := range list.Declarations.Nodes {
637
+ decl := item.AsVariableDeclaration()
638
+ if decl == nil {
639
+ continue
640
+ }
641
+ if name := identifierText(decl.Name()); name != "" {
642
+ exports = append(exports, storybookNamedExport{Name: name, Node: decl.Name(), Init: decl.Initializer})
643
+ }
644
+ }
645
+ case shimast.KindFunctionDeclaration, shimast.KindClassDeclaration:
646
+ if hasModifier(stmt, shimast.KindExportKeyword) && !hasModifier(stmt, shimast.KindDefaultKeyword) {
647
+ if name := identifierText(stmt.Name()); name != "" {
648
+ exports = append(exports, storybookNamedExport{Name: name, Node: stmt.Name()})
649
+ }
650
+ }
651
+ case shimast.KindExportDeclaration:
652
+ decl := stmt.AsExportDeclaration()
653
+ if decl == nil || decl.ModuleSpecifier != nil || decl.ExportClause == nil || decl.ExportClause.Kind != shimast.KindNamedExports {
654
+ continue
655
+ }
656
+ named := decl.ExportClause.AsNamedExports()
657
+ if named == nil || named.Elements == nil {
658
+ continue
659
+ }
660
+ for _, item := range named.Elements.Nodes {
661
+ spec := item.AsExportSpecifier()
662
+ if spec == nil || spec.IsTypeOnly {
663
+ continue
664
+ }
665
+ if name := identifierText(spec.Name()); name != "" {
666
+ exports = append(exports, storybookNamedExport{Name: name, Node: spec.Name()})
667
+ }
668
+ }
669
+ }
670
+ }
671
+ return exports
672
+ }
673
+
674
+ func storybookStoryFilters(meta *storybookMetaInfo) storybookStoryFiltersInfo {
675
+ filters := storybookStoryFiltersInfo{}
676
+ if meta == nil || meta.Object == nil {
677
+ return filters
678
+ }
679
+ if _, value, ok := storybookObjectProperty(meta.Object, "includeStories"); ok {
680
+ filters.Include = storybookDescriptors(meta.File, value)
681
+ }
682
+ if _, value, ok := storybookObjectProperty(meta.Object, "excludeStories"); ok {
683
+ filters.Exclude = storybookDescriptors(meta.File, value)
684
+ }
685
+ return filters
686
+ }
687
+
688
+ func storybookDescriptors(file *shimast.SourceFile, node *shimast.Node) []storybookDescriptor {
689
+ node = stripParens(node)
690
+ if node == nil {
691
+ return nil
692
+ }
693
+ if node.Kind == shimast.KindArrayLiteralExpression {
694
+ arr := node.AsArrayLiteralExpression()
695
+ if arr == nil || arr.Elements == nil {
696
+ return nil
697
+ }
698
+ out := []storybookDescriptor{}
699
+ for _, item := range arr.Elements.Nodes {
700
+ out = append(out, storybookDescriptorFromNode(file, item)...)
701
+ }
702
+ return out
703
+ }
704
+ return storybookDescriptorFromNode(file, node)
705
+ }
706
+
707
+ func storybookDescriptorFromNode(file *shimast.SourceFile, node *shimast.Node) []storybookDescriptor {
708
+ if text := storybookLiteralString(node); text != "" {
709
+ return []storybookDescriptor{{Text: text}}
710
+ }
711
+ if node != nil && node.Kind == shimast.KindRegularExpressionLiteral {
712
+ text := nodeText(file, node)
713
+ if pattern, ok := storybookRegexpPattern(text); ok {
714
+ if compiled, err := regexp.Compile(pattern); err == nil {
715
+ return []storybookDescriptor{{Pattern: compiled}}
716
+ }
717
+ }
718
+ }
719
+ return nil
720
+ }
721
+
722
+ func storybookRegexpPattern(text string) (string, bool) {
723
+ if !strings.HasPrefix(text, "/") {
724
+ return "", false
725
+ }
726
+ last := strings.LastIndex(text, "/")
727
+ if last <= 0 {
728
+ return "", false
729
+ }
730
+ return text[1:last], true
731
+ }
732
+
733
+ func storybookIsStoryExport(name string, filters storybookStoryFiltersInfo) bool {
734
+ if name == "" || name == "__namedExportsOrder" || strings.HasPrefix(name, "_") {
735
+ return false
736
+ }
737
+ if len(filters.Include) > 0 && !storybookDescriptorsMatch(filters.Include, name) {
738
+ return false
739
+ }
740
+ if len(filters.Exclude) > 0 && storybookDescriptorsMatch(filters.Exclude, name) {
741
+ return false
742
+ }
743
+ return true
744
+ }
745
+
746
+ func storybookDescriptorsMatch(descriptors []storybookDescriptor, name string) bool {
747
+ for _, descriptor := range descriptors {
748
+ if descriptor.Text != "" && descriptor.Text == name {
749
+ return true
750
+ }
751
+ if descriptor.Pattern != nil && descriptor.Pattern.MatchString(name) {
752
+ return true
753
+ }
754
+ }
755
+ return false
756
+ }
757
+
758
+ func storybookHasStoriesOfImport(file *shimast.SourceFile) bool {
759
+ return storybookHasNamedImport(file, "storiesOf", "@storybook/react", "@storybook/vue", "@storybook/vue3", "@storybook/angular", "@storybook/svelte", "@storybook/html", "@storybook/web-components")
760
+ }
761
+
762
+ func storybookHasNamedImport(file *shimast.SourceFile, name string, modules ...string) bool {
763
+ wantedModules := map[string]struct{}{}
764
+ for _, module := range modules {
765
+ wantedModules[module] = struct{}{}
766
+ }
767
+ if file == nil || file.Statements == nil {
768
+ return false
769
+ }
770
+ for _, stmt := range file.Statements.Nodes {
771
+ if stmt == nil || stmt.Kind != shimast.KindImportDeclaration {
772
+ continue
773
+ }
774
+ decl := stmt.AsImportDeclaration()
775
+ if decl == nil {
776
+ continue
777
+ }
778
+ if _, ok := wantedModules[storybookLiteralString(decl.ModuleSpecifier)]; !ok {
779
+ continue
780
+ }
781
+ if storybookImportHasSpecifier(decl, name) {
782
+ return true
783
+ }
784
+ }
785
+ return false
786
+ }
787
+
788
+ func storybookImportHasSpecifier(decl *shimast.ImportDeclaration, name string) bool {
789
+ if decl == nil || decl.ImportClause == nil {
790
+ return false
791
+ }
792
+ clause := decl.ImportClause.AsImportClause()
793
+ if clause == nil || clause.NamedBindings == nil || clause.NamedBindings.Kind != shimast.KindNamedImports {
794
+ return false
795
+ }
796
+ named := clause.NamedBindings.AsNamedImports()
797
+ if named == nil || named.Elements == nil {
798
+ return false
799
+ }
800
+ for _, item := range named.Elements.Nodes {
801
+ spec := item.AsImportSpecifier()
802
+ if spec != nil && storybookImportSpecifierImportedName(spec) == name {
803
+ return true
804
+ }
805
+ }
806
+ return false
807
+ }
808
+
809
+ func storybookImportSpecifierImportedName(spec *shimast.ImportSpecifier) string {
810
+ if spec == nil {
811
+ return ""
812
+ }
813
+ if spec.PropertyName != nil {
814
+ return storybookPropertyName(spec.PropertyName)
815
+ }
816
+ return identifierText(spec.Name())
817
+ }
818
+
819
+ func storybookHasCsf4MetaCall(file *shimast.SourceFile) bool {
820
+ if file == nil || file.Statements == nil {
821
+ return false
822
+ }
823
+ for _, stmt := range file.Statements.Nodes {
824
+ found := false
825
+ walkDescendants(stmt, func(node *shimast.Node) {
826
+ if found || node == nil || node.Kind != shimast.KindCallExpression {
827
+ return
828
+ }
829
+ call := node.AsCallExpression()
830
+ if call == nil {
831
+ return
832
+ }
833
+ _, prop := storybookPropertyAccessParts(call.Expression)
834
+ found = prop == "meta"
835
+ })
836
+ if found {
837
+ return true
838
+ }
839
+ }
840
+ return false
841
+ }
842
+
843
+ func storybookFirstNonImportStatement(file *shimast.SourceFile, fallback *shimast.Node) *shimast.Node {
844
+ if file == nil || file.Statements == nil {
845
+ return fallback
846
+ }
847
+ for _, stmt := range file.Statements.Nodes {
848
+ if stmt != nil && stmt.Kind != shimast.KindImportDeclaration {
849
+ return stmt
850
+ }
851
+ }
852
+ if len(file.Statements.Nodes) > 0 && file.Statements.Nodes[0] != nil {
853
+ return file.Statements.Nodes[0]
854
+ }
855
+ return fallback
856
+ }
857
+
858
+ func storybookPropertyAccessParts(node *shimast.Node) (string, string) {
859
+ node = stripParens(node)
860
+ if node == nil || node.Kind != shimast.KindPropertyAccessExpression {
861
+ return "", ""
862
+ }
863
+ access := node.AsPropertyAccessExpression()
864
+ if access == nil {
865
+ return "", ""
866
+ }
867
+ return identifierText(access.Expression), identifierText(access.Name())
868
+ }
869
+
870
+ func storybookIsPlayCall(node *shimast.Node) bool {
871
+ _, prop := storybookPropertyAccessParts(node)
872
+ if prop == "play" {
873
+ return true
874
+ }
875
+ if node != nil && node.Kind == shimast.KindNonNullExpression {
876
+ nonNull := node.AsNonNullExpression()
877
+ if nonNull != nil {
878
+ _, prop = storybookPropertyAccessParts(nonNull.Expression)
879
+ return prop == "play"
880
+ }
881
+ }
882
+ return false
883
+ }
884
+
885
+ func storybookEnclosingFunctionContextName(node *shimast.Node) string {
886
+ for cur := node.Parent; cur != nil; cur = cur.Parent {
887
+ if !isFunctionLikeKind(cur) {
888
+ continue
889
+ }
890
+ params := cur.Parameters()
891
+ if len(params) == 0 {
892
+ return ""
893
+ }
894
+ param := params[0].AsParameterDeclaration()
895
+ if param == nil {
896
+ return ""
897
+ }
898
+ return storybookContextNameFromBinding(param.Name())
899
+ }
900
+ return ""
901
+ }
902
+
903
+ func storybookContextNameFromBinding(node *shimast.Node) string {
904
+ if name := identifierText(node); name != "" {
905
+ return name
906
+ }
907
+ if node == nil || node.Kind != shimast.KindObjectBindingPattern {
908
+ return ""
909
+ }
910
+ children := node.Children()
911
+ if children == nil {
912
+ return ""
913
+ }
914
+ for _, child := range children.Nodes {
915
+ if child == nil || child.Kind != shimast.KindBindingElement {
916
+ continue
917
+ }
918
+ binding := child.AsBindingElement()
919
+ if binding == nil {
920
+ continue
921
+ }
922
+ if identifierText(binding.Name()) == "context" || storybookPropertyName(binding.PropertyName) == "context" {
923
+ return "context"
924
+ }
925
+ if binding.DotDotDotToken != nil {
926
+ return identifierText(binding.Name())
927
+ }
928
+ }
929
+ return ""
930
+ }
931
+
932
+ func storybookCallPassesContext(call *shimast.CallExpression, contextName string) bool {
933
+ if call == nil || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
934
+ return false
935
+ }
936
+ first := stripParens(call.Arguments.Nodes[0])
937
+ if identifierText(first) == contextName {
938
+ return true
939
+ }
940
+ if first == nil || first.Kind != shimast.KindObjectLiteralExpression {
941
+ return false
942
+ }
943
+ obj := first.AsObjectLiteralExpression()
944
+ if obj == nil || obj.Properties == nil {
945
+ return false
946
+ }
947
+ for _, prop := range obj.Properties.Nodes {
948
+ if prop != nil && prop.Kind == shimast.KindSpreadAssignment {
949
+ spread := prop.AsSpreadAssignment()
950
+ if spread != nil && identifierText(spread.Expression) == contextName {
951
+ return true
952
+ }
953
+ }
954
+ }
955
+ return false
956
+ }
957
+
958
+ func storybookAwaitedMethodName(call *shimast.CallExpression) string {
959
+ if call == nil || call.Expression == nil {
960
+ return ""
961
+ }
962
+ callee := stripParens(call.Expression)
963
+ if name := identifierText(callee); storybookShouldAwaitName(name) {
964
+ return name
965
+ }
966
+ if object, prop := storybookPropertyAccessParts(callee); storybookShouldAwaitName(object) {
967
+ return object
968
+ } else if storybookShouldAwaitName(prop) {
969
+ return prop
970
+ }
971
+ if callee != nil && callee.Kind == shimast.KindPropertyAccessExpression {
972
+ access := callee.AsPropertyAccessExpression()
973
+ if access != nil && access.Expression != nil && access.Expression.Kind == shimast.KindCallExpression {
974
+ nested := access.Expression.AsCallExpression()
975
+ if nested != nil && identifierText(nested.Expression) == "expect" {
976
+ return identifierText(access.Name())
977
+ }
978
+ }
979
+ }
980
+ return ""
981
+ }
982
+
983
+ func storybookShouldAwaitName(name string) bool {
984
+ if strings.HasPrefix(name, "findBy") {
985
+ return true
986
+ }
987
+ switch name {
988
+ case "waitFor", "waitForElementToBeRemoved", "wait", "waitForElement", "waitForDomChange", "userEvent", "play":
989
+ return true
990
+ }
991
+ return false
992
+ }
993
+
994
+ func storybookCallIsAwaited(node *shimast.Node) bool {
995
+ parent := node.Parent
996
+ for parent != nil && parent.Kind == shimast.KindParenthesizedExpression {
997
+ parent = parent.Parent
998
+ }
999
+ return parent != nil && parent.Kind == shimast.KindAwaitExpression
1000
+ }
1001
+
1002
+ func storybookCallIsReturned(node *shimast.Node) bool {
1003
+ parent := node.Parent
1004
+ for parent != nil && parent.Kind == shimast.KindParenthesizedExpression {
1005
+ parent = parent.Parent
1006
+ }
1007
+ return parent != nil && (parent.Kind == shimast.KindReturnStatement || parent.Kind == shimast.KindArrowFunction)
1008
+ }
1009
+
1010
+ func storybookNameFromExport(name string) string {
1011
+ if name == "" {
1012
+ return ""
1013
+ }
1014
+ name = strings.ReplaceAll(name, "_", " ")
1015
+ name = strings.ReplaceAll(name, "-", " ")
1016
+ var out []rune
1017
+ prev := rune(0)
1018
+ for i, r := range name {
1019
+ if i > 0 && unicode.IsUpper(r) && (unicode.IsLower(prev) || unicode.IsDigit(prev)) {
1020
+ out = append(out, ' ')
1021
+ }
1022
+ out = append(out, r)
1023
+ prev = r
1024
+ }
1025
+ fields := strings.Fields(string(out))
1026
+ for i, field := range fields {
1027
+ if field == "" {
1028
+ continue
1029
+ }
1030
+ runes := []rune(field)
1031
+ runes[0] = unicode.ToUpper(runes[0])
1032
+ fields[i] = string(runes)
1033
+ }
1034
+ return strings.Join(fields, " ")
1035
+ }
1036
+
1037
+ func storybookIsPascalCase(name string) bool {
1038
+ if name == "" {
1039
+ return false
1040
+ }
1041
+ first := rune(name[0])
1042
+ return first >= 'A' && first <= 'Z' && !strings.ContainsAny(name, "-_ ")
1043
+ }
1044
+
1045
+ func storybookAddonEntries(file *shimast.SourceFile) []storybookAddonEntry {
1046
+ if file == nil {
1047
+ return nil
1048
+ }
1049
+ entries := []storybookAddonEntry{}
1050
+ walkDescendants(file.AsNode(), func(node *shimast.Node) {
1051
+ if node == nil || node.Kind != shimast.KindPropertyAssignment {
1052
+ return
1053
+ }
1054
+ prop := node.AsPropertyAssignment()
1055
+ if prop == nil || storybookPropertyName(prop.Name()) != "addons" {
1056
+ return
1057
+ }
1058
+ entries = append(entries, storybookAddonEntriesFromArray(prop.Initializer)...)
1059
+ })
1060
+ for _, story := range storybookNamedExports(file) {
1061
+ if story.Name == "addons" {
1062
+ entries = append(entries, storybookAddonEntriesFromArray(story.Init)...)
1063
+ }
1064
+ }
1065
+ return entries
1066
+ }
1067
+
1068
+ func storybookAddonEntriesFromArray(node *shimast.Node) []storybookAddonEntry {
1069
+ node = stripParens(node)
1070
+ if node == nil || node.Kind != shimast.KindArrayLiteralExpression {
1071
+ return nil
1072
+ }
1073
+ arr := node.AsArrayLiteralExpression()
1074
+ if arr == nil || arr.Elements == nil {
1075
+ return nil
1076
+ }
1077
+ entries := []storybookAddonEntry{}
1078
+ for _, item := range arr.Elements.Nodes {
1079
+ if name := storybookLiteralString(item); name != "" {
1080
+ entries = append(entries, storybookAddonEntry{Name: name, Node: item})
1081
+ continue
1082
+ }
1083
+ if item == nil || item.Kind != shimast.KindObjectLiteralExpression {
1084
+ continue
1085
+ }
1086
+ _, value, ok := storybookObjectProperty(item, "name")
1087
+ if ok {
1088
+ if name := storybookLiteralString(value); name != "" {
1089
+ entries = append(entries, storybookAddonEntry{Name: name, Node: value})
1090
+ }
1091
+ }
1092
+ }
1093
+ return entries
1094
+ }
1095
+
1096
+ func storybookInstalledPackages(file *shimast.SourceFile, explicit string) map[string]struct{} {
1097
+ location := explicit
1098
+ if location == "" {
1099
+ location = storybookFindPackageJSON(file)
1100
+ }
1101
+ if location == "" {
1102
+ return nil
1103
+ }
1104
+ data, err := os.ReadFile(location)
1105
+ if err != nil {
1106
+ return nil
1107
+ }
1108
+ var pkg struct {
1109
+ Dependencies map[string]json.RawMessage `json:"dependencies"`
1110
+ DevDependencies map[string]json.RawMessage `json:"devDependencies"`
1111
+ }
1112
+ if err := json.Unmarshal(data, &pkg); err != nil {
1113
+ return nil
1114
+ }
1115
+ installed := map[string]struct{}{}
1116
+ for name := range pkg.Dependencies {
1117
+ installed[name] = struct{}{}
1118
+ }
1119
+ for name := range pkg.DevDependencies {
1120
+ installed[name] = struct{}{}
1121
+ }
1122
+ return installed
1123
+ }
1124
+
1125
+ func storybookFindPackageJSON(file *shimast.SourceFile) string {
1126
+ if file == nil {
1127
+ return ""
1128
+ }
1129
+ dir := filepath.Dir(file.FileName())
1130
+ for {
1131
+ candidate := filepath.Join(dir, "package.json")
1132
+ if _, err := os.Stat(candidate); err == nil {
1133
+ return candidate
1134
+ }
1135
+ parent := filepath.Dir(dir)
1136
+ if parent == dir {
1137
+ return ""
1138
+ }
1139
+ dir = parent
1140
+ }
1141
+ }
1142
+
1143
+ func storybookNormalizeAddonName(name string) string {
1144
+ name = strings.TrimSuffix(name, ".mjs")
1145
+ name = strings.TrimSuffix(name, ".cjs")
1146
+ name = strings.TrimSuffix(name, ".js")
1147
+ name = strings.TrimSuffix(name, "/register")
1148
+ name = strings.TrimSuffix(name, "/preset")
1149
+ return name
1150
+ }
1151
+
1152
+ func storybookIsLocalAddon(name string) bool {
1153
+ return strings.HasPrefix(name, ".") ||
1154
+ strings.HasPrefix(name, "/") ||
1155
+ strings.HasPrefix(name, "\\") ||
1156
+ regexp.MustCompile(`^[A-Za-z]:`).MatchString(name)
1157
+ }
1158
+
1159
+ func init() {
1160
+ Register(storybookAwaitInteractions{})
1161
+ Register(storybookContextInPlayFunction{})
1162
+ Register(storybookCsfComponent{})
1163
+ Register(storybookDefaultExports{})
1164
+ Register(storybookHierarchySeparator{})
1165
+ Register(storybookMetaInlineProperties{})
1166
+ Register(storybookMetaSatisfiesType{})
1167
+ Register(storybookNoRedundantStoryName{})
1168
+ Register(storybookNoRendererPackages{})
1169
+ Register(storybookNoStoriesOf{})
1170
+ Register(storybookNoTitlePropertyInMeta{})
1171
+ Register(storybookNoUninstalledAddons{})
1172
+ Register(storybookPreferPascalCase{})
1173
+ Register(storybookStoryExports{})
1174
+ Register(storybookUseStorybookExpect{})
1175
+ Register(storybookUseStorybookTestingLibrary{})
1176
+ }