@ttsc/lint 0.14.0-dev.20260529.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (380) hide show
  1. package/README.md +85 -87
  2. package/lib/defaultFormat.d.ts +10 -6
  3. package/lib/defaultFormat.js +10 -6
  4. package/lib/defaultFormat.js.map +1 -1
  5. package/lib/structures/ITtscLintConfig.d.ts +5 -5
  6. package/lib/structures/TtscLintRuleSetting.d.ts +19 -19
  7. package/lib/structures/{ITtscLintFormat.d.ts → format/ITtscLintFormat.d.ts} +45 -70
  8. package/lib/structures/format/ITtscLintFormat.js.map +1 -0
  9. package/lib/structures/format/ITtscLintFormatJsDoc.d.ts +15 -0
  10. package/lib/structures/format/ITtscLintFormatJsDoc.js +3 -0
  11. package/lib/structures/format/ITtscLintFormatJsDoc.js.map +1 -0
  12. package/lib/structures/format/ITtscLintFormatSortImports.d.ts +54 -0
  13. package/lib/structures/format/ITtscLintFormatSortImports.js +3 -0
  14. package/lib/structures/format/ITtscLintFormatSortImports.js.map +1 -0
  15. package/lib/structures/format/index.d.ts +3 -0
  16. package/lib/structures/format/index.js +20 -0
  17. package/lib/structures/format/index.js.map +1 -0
  18. package/lib/structures/index.d.ts +1 -1
  19. package/lib/structures/index.js +1 -1
  20. package/lib/structures/index.js.map +1 -1
  21. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +18 -27
  22. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js +4 -5
  23. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.js.map +1 -1
  24. package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +29 -33
  25. package/lib/structures/rules/ITtscLintContributorRules.d.ts +6 -7
  26. package/lib/structures/rules/ITtscLintCoreRules.d.ts +380 -408
  27. package/lib/structures/rules/ITtscLintCypressRuleOptions.d.ts +5 -5
  28. package/lib/structures/rules/ITtscLintCypressRuleOptions.js +2 -2
  29. package/lib/structures/rules/ITtscLintCypressRules.d.ts +34 -38
  30. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.d.ts +21 -24
  31. package/lib/structures/rules/ITtscLintFunctionalRuleOptions.js +2 -2
  32. package/lib/structures/rules/ITtscLintFunctionalRules.d.ts +51 -56
  33. package/lib/structures/rules/ITtscLintJestRules.d.ts +63 -74
  34. package/lib/structures/rules/ITtscLintJsdocRules.d.ts +47 -54
  35. package/lib/structures/rules/ITtscLintJsxA11yRules.d.ts +94 -102
  36. package/lib/structures/rules/ITtscLintNextjsRules.d.ts +50 -55
  37. package/lib/structures/rules/ITtscLintPlaywrightRules.d.ts +104 -122
  38. package/lib/structures/rules/ITtscLintPromiseRules.d.ts +62 -71
  39. package/lib/structures/rules/ITtscLintReactPerfRuleOptions.d.ts +4 -4
  40. package/lib/structures/rules/ITtscLintReactPerfRules.d.ts +17 -19
  41. package/lib/structures/rules/ITtscLintReactRuleOptions.d.ts +8 -10
  42. package/lib/structures/rules/ITtscLintReactRuleOptions.js +1 -2
  43. package/lib/structures/rules/ITtscLintReactRuleOptions.js.map +1 -1
  44. package/lib/structures/rules/ITtscLintReactRules.d.ts +91 -104
  45. package/lib/structures/rules/ITtscLintRegexpRules.d.ts +46 -54
  46. package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +7 -8
  47. package/lib/structures/rules/ITtscLintSecurityRules.d.ts +45 -52
  48. package/lib/structures/rules/ITtscLintSolidRules.d.ts +53 -56
  49. package/lib/structures/rules/ITtscLintStorybookRuleOptions.d.ts +3 -4
  50. package/lib/structures/rules/ITtscLintStorybookRules.d.ts +60 -69
  51. package/lib/structures/rules/ITtscLintTanstackQueryRules.d.ts +31 -37
  52. package/lib/structures/rules/ITtscLintTestingLibraryRuleOptions.d.ts +2 -3
  53. package/lib/structures/rules/ITtscLintTestingLibraryRules.d.ts +102 -119
  54. package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +460 -518
  55. package/lib/structures/rules/ITtscLintVitestRules.d.ts +43 -51
  56. package/linthost/config.go +89 -72
  57. package/linthost/config_format.go +265 -99
  58. package/linthost/directives.go +5 -1
  59. package/linthost/display_width.go +72 -0
  60. package/linthost/fix.go +26 -1
  61. package/linthost/format.go +222 -25
  62. package/linthost/format_editor_settings.go +262 -0
  63. package/linthost/lsp.go +8 -4
  64. package/linthost/print_dispatch.go +3 -0
  65. package/linthost/print_doc.go +13 -0
  66. package/linthost/print_engine.go +68 -11
  67. package/linthost/print_nodes_array.go +108 -5
  68. package/linthost/print_nodes_call.go +515 -74
  69. package/linthost/print_nodes_function.go +25 -0
  70. package/linthost/print_nodes_imports.go +47 -14
  71. package/linthost/print_nodes_list.go +191 -8
  72. package/linthost/print_nodes_object.go +12 -6
  73. package/linthost/print_nodes_ternary.go +106 -0
  74. package/linthost/rules_boundaries.go +400 -400
  75. package/linthost/rules_camelcase.go +81 -81
  76. package/linthost/rules_complexity.go +52 -51
  77. package/linthost/rules_consistent_return.go +81 -81
  78. package/linthost/rules_core_extra.go +472 -472
  79. package/linthost/rules_curly.go +43 -43
  80. package/linthost/rules_default_case.go +15 -15
  81. package/linthost/rules_default_case_last.go +22 -22
  82. package/linthost/rules_format_arrow_parens.go +167 -0
  83. package/linthost/rules_format_bracket_spacing.go +117 -0
  84. package/linthost/rules_format_clause_join.go +172 -0
  85. package/linthost/rules_format_declaration_header.go +664 -0
  86. package/linthost/rules_format_indent.go +616 -0
  87. package/linthost/rules_format_jsdoc.go +4 -4
  88. package/linthost/rules_format_orphan_semi.go +106 -0
  89. package/linthost/rules_format_parameter_properties.go +175 -0
  90. package/linthost/rules_format_print_width.go +185 -69
  91. package/linthost/rules_format_quote_props.go +169 -0
  92. package/linthost/rules_format_quotes.go +75 -29
  93. package/linthost/rules_format_semi.go +185 -10
  94. package/linthost/rules_format_sort_imports.go +456 -121
  95. package/linthost/rules_format_statement_split.go +368 -0
  96. package/linthost/rules_format_ternary_nullish_parens.go +77 -0
  97. package/linthost/rules_format_trailing_comma.go +21 -2
  98. package/linthost/rules_format_whitespace.go +336 -0
  99. package/linthost/rules_functional.go +688 -688
  100. package/linthost/rules_gap.go +146 -11
  101. package/linthost/rules_grouped_accessor_pairs.go +60 -60
  102. package/linthost/rules_guard_for_in.go +122 -122
  103. package/linthost/rules_id_length.go +44 -44
  104. package/linthost/rules_imports.go +11 -3
  105. package/linthost/rules_init_declarations.go +25 -25
  106. package/linthost/rules_jsx_a11y.go +844 -844
  107. package/linthost/rules_max_classes_per_file.go +24 -24
  108. package/linthost/rules_max_depth.go +43 -43
  109. package/linthost/rules_max_lines.go +53 -53
  110. package/linthost/rules_max_lines_per_function.go +28 -28
  111. package/linthost/rules_max_nested_callbacks.go +26 -26
  112. package/linthost/rules_max_params.go +20 -20
  113. package/linthost/rules_max_statements.go +29 -29
  114. package/linthost/rules_nextjs.go +5 -5
  115. package/linthost/rules_no_else_return.go +38 -38
  116. package/linthost/rules_no_empty_named_blocks.go +45 -45
  117. package/linthost/rules_no_import_assign.go +96 -96
  118. package/linthost/rules_no_invalid_this.go +19 -19
  119. package/linthost/rules_no_loop_func.go +43 -43
  120. package/linthost/rules_no_magic_numbers.go +60 -60
  121. package/linthost/rules_no_mixed_operators.go +44 -44
  122. package/linthost/rules_no_param_reassign.go +64 -64
  123. package/linthost/rules_no_restricted_imports.go +45 -45
  124. package/linthost/rules_no_restricted_syntax.go +17 -17
  125. package/linthost/rules_no_shadow.go +112 -112
  126. package/linthost/rules_no_unreachable.go +36 -36
  127. package/linthost/rules_no_useless_assignment.go +79 -79
  128. package/linthost/rules_no_useless_constructor.go +167 -167
  129. package/linthost/rules_no_useless_return.go +28 -28
  130. package/linthost/rules_prefer_arrow_callback.go +74 -74
  131. package/linthost/rules_prefer_destructuring.go +61 -61
  132. package/linthost/rules_prefer_named_capture_group.go +87 -87
  133. package/linthost/rules_prefer_rest_params.go +61 -61
  134. package/linthost/rules_promise.go +800 -800
  135. package/linthost/rules_react.go +600 -600
  136. package/linthost/rules_react_extras.go +162 -162
  137. package/linthost/rules_react_hooks.go +2 -2
  138. package/linthost/rules_react_perf.go +231 -231
  139. package/linthost/rules_regexp.go +293 -293
  140. package/linthost/rules_security.go +613 -613
  141. package/linthost/rules_sort_imports.go +37 -37
  142. package/linthost/rules_sort_keys.go +88 -88
  143. package/linthost/rules_suggestions.go +13 -0
  144. package/linthost/rules_ts.go +2 -2
  145. package/linthost/rules_ts_async.go +247 -247
  146. package/linthost/rules_ts_class_literal_property_style.go +64 -64
  147. package/linthost/rules_ts_consistent_type_exports.go +209 -209
  148. package/linthost/rules_ts_explicit_function_return_type.go +35 -35
  149. package/linthost/rules_ts_extra.go +6 -2
  150. package/linthost/rules_ts_no_base_to_string.go +140 -140
  151. package/linthost/rules_ts_no_confusing_void.go +28 -28
  152. package/linthost/rules_ts_no_deprecated.go +110 -110
  153. package/linthost/rules_ts_no_for_in_array.go +45 -45
  154. package/linthost/rules_ts_no_magic_numbers.go +67 -67
  155. package/linthost/rules_ts_no_meaningless_void_operator.go +44 -44
  156. package/linthost/rules_ts_no_misused_spread.go +64 -64
  157. package/linthost/rules_ts_no_redundant_type_constituents.go +72 -72
  158. package/linthost/rules_ts_no_restricted_types.go +28 -28
  159. package/linthost/rules_ts_no_unnecessary_boolean_literal_compare.go +87 -87
  160. package/linthost/rules_ts_no_unnecessary_condition.go +184 -184
  161. package/linthost/rules_ts_no_unnecessary_qualifier.go +72 -72
  162. package/linthost/rules_ts_no_unnecessary_template_expression.go +120 -120
  163. package/linthost/rules_ts_no_unnecessary_type_arguments.go +171 -171
  164. package/linthost/rules_ts_no_unnecessary_type_assertion.go +90 -90
  165. package/linthost/rules_ts_no_unsafe_argument.go +50 -50
  166. package/linthost/rules_ts_no_unsafe_assignment.go +60 -60
  167. package/linthost/rules_ts_no_unsafe_call.go +40 -40
  168. package/linthost/rules_ts_no_unsafe_enum_comparison.go +147 -147
  169. package/linthost/rules_ts_no_unsafe_member_access.go +29 -29
  170. package/linthost/rules_ts_no_unsafe_return.go +64 -64
  171. package/linthost/rules_ts_no_unsafe_unary_minus.go +42 -42
  172. package/linthost/rules_ts_no_useless_constructor.go +20 -20
  173. package/linthost/rules_ts_non_nullable_type_assertion_style.go +60 -60
  174. package/linthost/rules_ts_parameter_properties.go +10 -10
  175. package/linthost/rules_ts_prefer_find.go +116 -116
  176. package/linthost/rules_ts_prefer_includes.go +173 -173
  177. package/linthost/rules_ts_prefer_nullish_coalescing.go +54 -54
  178. package/linthost/rules_ts_prefer_optional_chain.go +156 -156
  179. package/linthost/rules_ts_prefer_promise_reject_errors.go +104 -104
  180. package/linthost/rules_ts_prefer_readonly.go +31 -31
  181. package/linthost/rules_ts_prefer_reduce_type_parameter.go +74 -74
  182. package/linthost/rules_ts_prefer_regexp_exec.go +69 -69
  183. package/linthost/rules_ts_prefer_return_this_type.go +79 -79
  184. package/linthost/rules_ts_prefer_string_starts_ends_with.go +237 -237
  185. package/linthost/rules_ts_promise_function_async.go +41 -41
  186. package/linthost/rules_ts_related_getter_setter_pairs.go +61 -61
  187. package/linthost/rules_ts_require_array_sort_compare.go +58 -58
  188. package/linthost/rules_ts_restrict_plus_operands.go +82 -82
  189. package/linthost/rules_ts_restrict_template_expressions.go +50 -50
  190. package/linthost/rules_ts_simple.go +249 -249
  191. package/linthost/rules_ts_sort_type_constituents.go +88 -88
  192. package/linthost/rules_ts_strict_boolean_expressions.go +103 -103
  193. package/linthost/rules_ts_switch_exhaustiveness_check.go +90 -90
  194. package/linthost/rules_ts_unbound_method.go +65 -65
  195. package/linthost/rules_tsdoc.go +88 -88
  196. package/linthost/rules_unicorn_better_regex.go +1 -1
  197. package/linthost/rules_unicorn_catch_error_name.go +19 -19
  198. package/linthost/rules_unicorn_consistent_assert.go +20 -20
  199. package/linthost/rules_unicorn_consistent_date_clone.go +27 -27
  200. package/linthost/rules_unicorn_consistent_destructuring.go +3 -3
  201. package/linthost/rules_unicorn_consistent_empty_array_spread.go +34 -34
  202. package/linthost/rules_unicorn_consistent_existence_index_check.go +42 -42
  203. package/linthost/rules_unicorn_consistent_function_scoping.go +3 -3
  204. package/linthost/rules_unicorn_consistent_template_literal_escape.go +3 -3
  205. package/linthost/rules_unicorn_custom_error_definition.go +92 -92
  206. package/linthost/rules_unicorn_empty_brace_spaces.go +47 -47
  207. package/linthost/rules_unicorn_error_message.go +34 -34
  208. package/linthost/rules_unicorn_escape_case.go +14 -14
  209. package/linthost/rules_unicorn_expiring_todo_comments.go +36 -36
  210. package/linthost/rules_unicorn_explicit_length_check.go +51 -51
  211. package/linthost/rules_unicorn_filename_case.go +1 -1
  212. package/linthost/rules_unicorn_import_style.go +1 -1
  213. package/linthost/rules_unicorn_isolated_functions.go +2 -2
  214. package/linthost/rules_unicorn_new_for_builtins.go +49 -49
  215. package/linthost/rules_unicorn_no_abusive_eslint_disable.go +31 -31
  216. package/linthost/rules_unicorn_no_accessor_recursion.go +69 -69
  217. package/linthost/rules_unicorn_no_anonymous_default_export.go +40 -40
  218. package/linthost/rules_unicorn_no_array_callback_reference.go +33 -33
  219. package/linthost/rules_unicorn_no_array_for_each.go +14 -14
  220. package/linthost/rules_unicorn_no_array_method_this_argument.go +29 -29
  221. package/linthost/rules_unicorn_no_array_reduce.go +15 -15
  222. package/linthost/rules_unicorn_no_array_reverse.go +17 -17
  223. package/linthost/rules_unicorn_no_array_sort.go +17 -17
  224. package/linthost/rules_unicorn_no_await_expression_member.go +12 -12
  225. package/linthost/rules_unicorn_no_await_in_promise_methods.go +38 -38
  226. package/linthost/rules_unicorn_no_console_spaces.go +30 -30
  227. package/linthost/rules_unicorn_no_document_cookie.go +13 -13
  228. package/linthost/rules_unicorn_no_empty_file.go +16 -16
  229. package/linthost/rules_unicorn_no_for_loop.go +58 -58
  230. package/linthost/rules_unicorn_no_hex_escape.go +11 -11
  231. package/linthost/rules_unicorn_no_immediate_mutation.go +55 -55
  232. package/linthost/rules_unicorn_no_instanceof_builtins.go +34 -34
  233. package/linthost/rules_unicorn_no_invalid_fetch_options.go +52 -52
  234. package/linthost/rules_unicorn_no_invalid_remove_event_listener.go +25 -25
  235. package/linthost/rules_unicorn_no_keyword_prefix.go +56 -56
  236. package/linthost/rules_unicorn_no_lonely_if.go +21 -21
  237. package/linthost/rules_unicorn_no_magic_array_flat_depth.go +22 -22
  238. package/linthost/rules_unicorn_no_named_default.go +25 -25
  239. package/linthost/rules_unicorn_no_negated_condition.go +54 -54
  240. package/linthost/rules_unicorn_no_negation_in_equality_check.go +23 -23
  241. package/linthost/rules_unicorn_no_nested_ternary.go +15 -15
  242. package/linthost/rules_unicorn_no_new_array.go +5 -5
  243. package/linthost/rules_unicorn_no_new_buffer.go +5 -5
  244. package/linthost/rules_unicorn_no_null.go +2 -2
  245. package/linthost/rules_unicorn_no_object_as_default_parameter.go +19 -19
  246. package/linthost/rules_unicorn_no_process_exit.go +17 -17
  247. package/linthost/rules_unicorn_no_single_promise_in_promise_methods.go +29 -29
  248. package/linthost/rules_unicorn_no_static_only_class.go +27 -27
  249. package/linthost/rules_unicorn_no_thenable.go +47 -47
  250. package/linthost/rules_unicorn_no_this_assignment.go +10 -10
  251. package/linthost/rules_unicorn_no_typeof_undefined.go +31 -31
  252. package/linthost/rules_unicorn_no_unnecessary_array_flat_depth.go +23 -23
  253. package/linthost/rules_unicorn_no_unnecessary_array_splice_count.go +37 -37
  254. package/linthost/rules_unicorn_no_unnecessary_await.go +24 -24
  255. package/linthost/rules_unicorn_no_unnecessary_slice_end.go +22 -22
  256. package/linthost/rules_unicorn_no_unreadable_array_destructuring.go +67 -67
  257. package/linthost/rules_unicorn_no_unreadable_iife.go +22 -22
  258. package/linthost/rules_unicorn_no_useless_collection_argument.go +34 -34
  259. package/linthost/rules_unicorn_no_useless_error_capture_stack_trace.go +18 -18
  260. package/linthost/rules_unicorn_no_useless_fallback_in_spread.go +49 -49
  261. package/linthost/rules_unicorn_no_useless_iterator_to_array.go +41 -41
  262. package/linthost/rules_unicorn_no_useless_length_check.go +82 -82
  263. package/linthost/rules_unicorn_no_useless_promise_resolve_reject.go +42 -42
  264. package/linthost/rules_unicorn_no_useless_spread.go +38 -38
  265. package/linthost/rules_unicorn_no_useless_switch_case.go +29 -29
  266. package/linthost/rules_unicorn_no_useless_undefined.go +25 -25
  267. package/linthost/rules_unicorn_no_zero_fractions.go +17 -17
  268. package/linthost/rules_unicorn_number_literal_case.go +33 -33
  269. package/linthost/rules_unicorn_numeric_separators_style.go +36 -36
  270. package/linthost/rules_unicorn_prefer_add_event_listener.go +30 -30
  271. package/linthost/rules_unicorn_prefer_array_find.go +27 -27
  272. package/linthost/rules_unicorn_prefer_array_flat.go +25 -25
  273. package/linthost/rules_unicorn_prefer_array_flat_map.go +37 -37
  274. package/linthost/rules_unicorn_prefer_array_index_of.go +108 -108
  275. package/linthost/rules_unicorn_prefer_array_some.go +43 -43
  276. package/linthost/rules_unicorn_prefer_at.go +42 -42
  277. package/linthost/rules_unicorn_prefer_bigint_literals.go +42 -42
  278. package/linthost/rules_unicorn_prefer_blob_reading_methods.go +15 -15
  279. package/linthost/rules_unicorn_prefer_class_fields.go +57 -57
  280. package/linthost/rules_unicorn_prefer_classlist_toggle.go +76 -76
  281. package/linthost/rules_unicorn_prefer_code_point.go +21 -21
  282. package/linthost/rules_unicorn_prefer_date_now.go +47 -47
  283. package/linthost/rules_unicorn_prefer_default_parameters.go +96 -96
  284. package/linthost/rules_unicorn_prefer_dom_node_append.go +14 -14
  285. package/linthost/rules_unicorn_prefer_dom_node_dataset.go +27 -27
  286. package/linthost/rules_unicorn_prefer_dom_node_remove.go +17 -17
  287. package/linthost/rules_unicorn_prefer_dom_node_text_content.go +11 -11
  288. package/linthost/rules_unicorn_prefer_event_target.go +10 -10
  289. package/linthost/rules_unicorn_prefer_export_from.go +99 -99
  290. package/linthost/rules_unicorn_prefer_global_this.go +91 -91
  291. package/linthost/rules_unicorn_prefer_import_meta_properties.go +38 -38
  292. package/linthost/rules_unicorn_prefer_includes.go +108 -108
  293. package/linthost/rules_unicorn_prefer_json_parse_buffer.go +37 -37
  294. package/linthost/rules_unicorn_prefer_keyboard_event_key.go +10 -10
  295. package/linthost/rules_unicorn_prefer_logical_operator_over_ternary.go +20 -20
  296. package/linthost/rules_unicorn_prefer_math_min_max.go +33 -33
  297. package/linthost/rules_unicorn_prefer_math_trunc.go +32 -32
  298. package/linthost/rules_unicorn_prefer_modern_dom_apis.go +18 -18
  299. package/linthost/rules_unicorn_prefer_modern_math_apis.go +38 -38
  300. package/linthost/rules_unicorn_prefer_module.go +22 -22
  301. package/linthost/rules_unicorn_prefer_native_coercion_functions.go +73 -73
  302. package/linthost/rules_unicorn_prefer_negative_index.go +46 -46
  303. package/linthost/rules_unicorn_prefer_node_protocol.go +90 -90
  304. package/linthost/rules_unicorn_prefer_number_properties.go +57 -57
  305. package/linthost/rules_unicorn_prefer_object_from_entries.go +26 -26
  306. package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
  307. package/linthost/rules_unicorn_prefer_prototype_methods.go +25 -25
  308. package/linthost/rules_unicorn_prefer_query_selector.go +25 -25
  309. package/linthost/rules_unicorn_prefer_reflect_apply.go +10 -10
  310. package/linthost/rules_unicorn_prefer_regexp_test_rule.go +47 -47
  311. package/linthost/rules_unicorn_prefer_response_static_json.go +26 -26
  312. package/linthost/rules_unicorn_prefer_set_has.go +16 -16
  313. package/linthost/rules_unicorn_prefer_set_size.go +22 -22
  314. package/linthost/rules_unicorn_prefer_simple_condition_first.go +3 -3
  315. package/linthost/rules_unicorn_prefer_single_call.go +59 -59
  316. package/linthost/rules_unicorn_prefer_spread.go +24 -24
  317. package/linthost/rules_unicorn_prefer_string_raw.go +12 -12
  318. package/linthost/rules_unicorn_prefer_string_replace_all.go +46 -46
  319. package/linthost/rules_unicorn_prefer_string_slice.go +17 -17
  320. package/linthost/rules_unicorn_prefer_string_starts_ends_with.go +104 -104
  321. package/linthost/rules_unicorn_prefer_string_trim_start_end.go +21 -21
  322. package/linthost/rules_unicorn_prefer_structured_clone.go +36 -36
  323. package/linthost/rules_unicorn_prefer_switch.go +65 -65
  324. package/linthost/rules_unicorn_prefer_ternary.go +35 -35
  325. package/linthost/rules_unicorn_prefer_top_level_await.go +34 -34
  326. package/linthost/rules_unicorn_prefer_type_error.go +84 -84
  327. package/linthost/rules_unicorn_prevent_abbreviations.go +68 -68
  328. package/linthost/rules_unicorn_relative_url_style.go +19 -19
  329. package/linthost/rules_unicorn_require_array_join_separator.go +18 -18
  330. package/linthost/rules_unicorn_require_module_attributes.go +25 -25
  331. package/linthost/rules_unicorn_require_module_specifiers.go +28 -28
  332. package/linthost/rules_unicorn_require_number_to_fixed_digits_argument.go +18 -18
  333. package/linthost/rules_unicorn_require_post_message_target_origin.go +18 -18
  334. package/linthost/rules_unicorn_string_content.go +1 -1
  335. package/linthost/rules_unicorn_switch_case_braces.go +16 -16
  336. package/linthost/rules_unicorn_switch_case_break_position.go +3 -3
  337. package/linthost/rules_unicorn_template_indent.go +1 -1
  338. package/linthost/rules_unicorn_text_encoding_identifier_case.go +36 -36
  339. package/linthost/rules_unicorn_throw_new_error.go +30 -30
  340. package/linthost/rules_var.go +273 -3
  341. package/package.json +3 -3
  342. package/src/defaultFormat.ts +11 -7
  343. package/src/structures/ITtscLintConfig.ts +5 -5
  344. package/src/structures/TtscLintRuleSetting.ts +19 -19
  345. package/src/structures/{ITtscLintFormat.ts → format/ITtscLintFormat.ts} +48 -75
  346. package/src/structures/format/ITtscLintFormatJsDoc.ts +16 -0
  347. package/src/structures/format/ITtscLintFormatSortImports.ts +62 -0
  348. package/src/structures/format/index.ts +3 -0
  349. package/src/structures/index.ts +1 -1
  350. package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +20 -31
  351. package/src/structures/rules/ITtscLintBoundariesRules.ts +32 -36
  352. package/src/structures/rules/ITtscLintContributorRules.ts +6 -7
  353. package/src/structures/rules/ITtscLintCoreRules.ts +380 -408
  354. package/src/structures/rules/ITtscLintCypressRuleOptions.ts +5 -5
  355. package/src/structures/rules/ITtscLintCypressRules.ts +34 -38
  356. package/src/structures/rules/ITtscLintFunctionalRuleOptions.ts +28 -41
  357. package/src/structures/rules/ITtscLintFunctionalRules.ts +51 -56
  358. package/src/structures/rules/ITtscLintJestRules.ts +63 -74
  359. package/src/structures/rules/ITtscLintJsdocRules.ts +47 -54
  360. package/src/structures/rules/ITtscLintJsxA11yRules.ts +94 -102
  361. package/src/structures/rules/ITtscLintNextjsRules.ts +50 -55
  362. package/src/structures/rules/ITtscLintPlaywrightRules.ts +104 -122
  363. package/src/structures/rules/ITtscLintPromiseRules.ts +62 -71
  364. package/src/structures/rules/ITtscLintReactPerfRuleOptions.ts +4 -4
  365. package/src/structures/rules/ITtscLintReactPerfRules.ts +17 -19
  366. package/src/structures/rules/ITtscLintReactRuleOptions.ts +8 -10
  367. package/src/structures/rules/ITtscLintReactRules.ts +91 -104
  368. package/src/structures/rules/ITtscLintRegexpRules.ts +46 -54
  369. package/src/structures/rules/ITtscLintRuleOptionsMap.ts +7 -8
  370. package/src/structures/rules/ITtscLintSecurityRules.ts +45 -52
  371. package/src/structures/rules/ITtscLintSolidRules.ts +53 -56
  372. package/src/structures/rules/ITtscLintStorybookRuleOptions.ts +3 -4
  373. package/src/structures/rules/ITtscLintStorybookRules.ts +60 -69
  374. package/src/structures/rules/ITtscLintTanstackQueryRules.ts +31 -37
  375. package/src/structures/rules/ITtscLintTestingLibraryRuleOptions.ts +2 -3
  376. package/src/structures/rules/ITtscLintTestingLibraryRules.ts +102 -119
  377. package/src/structures/rules/ITtscLintTypeScriptRules.ts +460 -518
  378. package/src/structures/rules/ITtscLintVitestRules.ts +43 -51
  379. package/lib/structures/ITtscLintFormat.js.map +0 -1
  380. /package/lib/structures/{ITtscLintFormat.js → format/ITtscLintFormat.js} +0 -0
@@ -1,10 +1,10 @@
1
1
  package linthost
2
2
 
3
3
  import (
4
- "strings"
5
- "unicode/utf8"
4
+ "strings"
5
+ "unicode/utf8"
6
6
 
7
- shimast "github.com/microsoft/typescript-go/shim/ast"
7
+ shimast "github.com/microsoft/typescript-go/shim/ast"
8
8
  )
9
9
 
10
10
  const securityRulePrefix = "security/"
@@ -12,819 +12,819 @@ const securityRulePrefix = "security/"
12
12
  type securityDetectBidiCharacters struct{}
13
13
 
14
14
  func (securityDetectBidiCharacters) Name() string {
15
- return securityRulePrefix + "detect-bidi-characters"
15
+ return securityRulePrefix + "detect-bidi-characters"
16
16
  }
17
17
  func (securityDetectBidiCharacters) Visits() []shimast.Kind {
18
- return []shimast.Kind{shimast.KindSourceFile}
18
+ return []shimast.Kind{shimast.KindSourceFile}
19
19
  }
20
20
  func (securityDetectBidiCharacters) Check(ctx *Context, node *shimast.Node) {
21
- if ctx.File == nil {
22
- return
23
- }
24
- for pos, r := range ctx.File.Text() {
25
- if isBidiControlRune(r) {
26
- ctx.ReportRange(pos, pos+utf8.RuneLen(r), "Detected potential trojan source bidi control character.")
27
- }
28
- }
21
+ if ctx.File == nil {
22
+ return
23
+ }
24
+ for pos, r := range ctx.File.Text() {
25
+ if isBidiControlRune(r) {
26
+ ctx.ReportRange(pos, pos+utf8.RuneLen(r), "Detected potential trojan source bidi control character.")
27
+ }
28
+ }
29
29
  }
30
30
 
31
31
  type securityDetectBufferNoassert struct{}
32
32
 
33
33
  func (securityDetectBufferNoassert) Name() string {
34
- return securityRulePrefix + "detect-buffer-noassert"
34
+ return securityRulePrefix + "detect-buffer-noassert"
35
35
  }
36
36
  func (securityDetectBufferNoassert) Visits() []shimast.Kind {
37
- return []shimast.Kind{shimast.KindCallExpression}
37
+ return []shimast.Kind{shimast.KindCallExpression}
38
38
  }
39
39
  func (securityDetectBufferNoassert) Check(ctx *Context, node *shimast.Node) {
40
- call := node.AsCallExpression()
41
- if call == nil || call.Arguments == nil {
42
- return
43
- }
44
- _, method, ok := propertyAccessParts(call.Expression)
45
- if !ok {
46
- return
47
- }
48
- idx, ok := bufferNoAssertIndex(method)
49
- if !ok || len(call.Arguments.Nodes) <= idx || !isTrueLiteral(call.Arguments.Nodes[idx]) {
50
- return
51
- }
52
- ctx.Report(node, "Buffer method called with noAssert set to true.")
40
+ call := node.AsCallExpression()
41
+ if call == nil || call.Arguments == nil {
42
+ return
43
+ }
44
+ _, method, ok := propertyAccessParts(call.Expression)
45
+ if !ok {
46
+ return
47
+ }
48
+ idx, ok := bufferNoAssertIndex(method)
49
+ if !ok || len(call.Arguments.Nodes) <= idx || !isTrueLiteral(call.Arguments.Nodes[idx]) {
50
+ return
51
+ }
52
+ ctx.Report(node, "Buffer method called with noAssert set to true.")
53
53
  }
54
54
 
55
55
  type securityDetectChildProcess struct{}
56
56
 
57
57
  func (securityDetectChildProcess) Name() string { return securityRulePrefix + "detect-child-process" }
58
58
  func (securityDetectChildProcess) Visits() []shimast.Kind {
59
- return []shimast.Kind{shimast.KindCallExpression}
59
+ return []shimast.Kind{shimast.KindCallExpression}
60
60
  }
61
61
  func (securityDetectChildProcess) Check(ctx *Context, node *shimast.Node) {
62
- call := node.AsCallExpression()
63
- if call == nil {
64
- return
65
- }
66
- bindings := collectSecurityBindings(ctx.File)
67
- if module, ok := requireCallModule(call); ok && isChildProcessModule(module) {
68
- if isInlineChildProcessExecRequire(node, bindings) {
69
- return
70
- }
71
- ctx.Report(node, "Found require(\""+module+"\").")
72
- return
73
- }
74
- if call.Arguments == nil || len(call.Arguments.Nodes) == 0 ||
75
- isSecurityStaticExpression(call.Arguments.Nodes[0], bindings, nil) {
76
- return
77
- }
78
- if isChildProcessExecCall(call, bindings) {
79
- ctx.Report(node, "Found child_process.exec() with non-literal first argument.")
80
- }
62
+ call := node.AsCallExpression()
63
+ if call == nil {
64
+ return
65
+ }
66
+ bindings := collectSecurityBindings(ctx.File)
67
+ if module, ok := requireCallModule(call); ok && isChildProcessModule(module) {
68
+ if isInlineChildProcessExecRequire(node, bindings) {
69
+ return
70
+ }
71
+ ctx.Report(node, "Found require(\""+module+"\").")
72
+ return
73
+ }
74
+ if call.Arguments == nil || len(call.Arguments.Nodes) == 0 ||
75
+ isSecurityStaticExpression(call.Arguments.Nodes[0], bindings, nil) {
76
+ return
77
+ }
78
+ if isChildProcessExecCall(call, bindings) {
79
+ ctx.Report(node, "Found child_process.exec() with non-literal first argument.")
80
+ }
81
81
  }
82
82
 
83
83
  type securityDetectDisableMustacheEscape struct{}
84
84
 
85
85
  func (securityDetectDisableMustacheEscape) Name() string {
86
- return securityRulePrefix + "detect-disable-mustache-escape"
86
+ return securityRulePrefix + "detect-disable-mustache-escape"
87
87
  }
88
88
  func (securityDetectDisableMustacheEscape) Visits() []shimast.Kind {
89
- return []shimast.Kind{shimast.KindBinaryExpression}
89
+ return []shimast.Kind{shimast.KindBinaryExpression}
90
90
  }
91
91
  func (securityDetectDisableMustacheEscape) Check(ctx *Context, node *shimast.Node) {
92
- expr := node.AsBinaryExpression()
93
- if expr == nil || expr.OperatorToken == nil || expr.OperatorToken.Kind != shimast.KindEqualsToken {
94
- return
95
- }
96
- _, prop, ok := propertyAccessParts(expr.Left)
97
- if ok && prop == "escapeMarkup" && isFalseLiteral(expr.Right) {
98
- ctx.Report(node, "Markup escaping disabled.")
99
- }
92
+ expr := node.AsBinaryExpression()
93
+ if expr == nil || expr.OperatorToken == nil || expr.OperatorToken.Kind != shimast.KindEqualsToken {
94
+ return
95
+ }
96
+ _, prop, ok := propertyAccessParts(expr.Left)
97
+ if ok && prop == "escapeMarkup" && isFalseLiteral(expr.Right) {
98
+ ctx.Report(node, "Markup escaping disabled.")
99
+ }
100
100
  }
101
101
 
102
102
  type securityDetectEvalWithExpression struct{}
103
103
 
104
104
  func (securityDetectEvalWithExpression) Name() string {
105
- return securityRulePrefix + "detect-eval-with-expression"
105
+ return securityRulePrefix + "detect-eval-with-expression"
106
106
  }
107
107
  func (securityDetectEvalWithExpression) Visits() []shimast.Kind {
108
- return []shimast.Kind{shimast.KindCallExpression}
108
+ return []shimast.Kind{shimast.KindCallExpression}
109
109
  }
110
110
  func (securityDetectEvalWithExpression) Check(ctx *Context, node *shimast.Node) {
111
- call := node.AsCallExpression()
112
- if call == nil || callCalleeName(call) != "eval" || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
113
- return
114
- }
115
- bindings := collectSecurityBindings(ctx.File)
116
- if !isSecurityStaticExpression(call.Arguments.Nodes[0], bindings, nil) {
117
- ctx.Report(node, "eval called with a non-literal expression.")
118
- }
111
+ call := node.AsCallExpression()
112
+ if call == nil || callCalleeName(call) != "eval" || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
113
+ return
114
+ }
115
+ bindings := collectSecurityBindings(ctx.File)
116
+ if !isSecurityStaticExpression(call.Arguments.Nodes[0], bindings, nil) {
117
+ ctx.Report(node, "eval called with a non-literal expression.")
118
+ }
119
119
  }
120
120
 
121
121
  type securityDetectNewBuffer struct{}
122
122
 
123
123
  func (securityDetectNewBuffer) Name() string { return securityRulePrefix + "detect-new-buffer" }
124
124
  func (securityDetectNewBuffer) Visits() []shimast.Kind {
125
- return []shimast.Kind{shimast.KindNewExpression}
125
+ return []shimast.Kind{shimast.KindNewExpression}
126
126
  }
127
127
  func (securityDetectNewBuffer) Check(ctx *Context, node *shimast.Node) {
128
- expr := node.AsNewExpression()
129
- if expr == nil || identifierText(expr.Expression) != "Buffer" || expr.Arguments == nil || len(expr.Arguments.Nodes) == 0 {
130
- return
131
- }
132
- bindings := collectSecurityBindings(ctx.File)
133
- if !isSecurityStaticExpression(expr.Arguments.Nodes[0], bindings, nil) {
134
- ctx.Report(node, "Found new Buffer with a non-literal argument.")
135
- }
128
+ expr := node.AsNewExpression()
129
+ if expr == nil || identifierText(expr.Expression) != "Buffer" || expr.Arguments == nil || len(expr.Arguments.Nodes) == 0 {
130
+ return
131
+ }
132
+ bindings := collectSecurityBindings(ctx.File)
133
+ if !isSecurityStaticExpression(expr.Arguments.Nodes[0], bindings, nil) {
134
+ ctx.Report(node, "Found new Buffer with a non-literal argument.")
135
+ }
136
136
  }
137
137
 
138
138
  type securityDetectNoCSRFBeforeMethodOverride struct{}
139
139
 
140
140
  func (securityDetectNoCSRFBeforeMethodOverride) Name() string {
141
- return securityRulePrefix + "detect-no-csrf-before-method-override"
141
+ return securityRulePrefix + "detect-no-csrf-before-method-override"
142
142
  }
143
143
  func (securityDetectNoCSRFBeforeMethodOverride) Visits() []shimast.Kind {
144
- return []shimast.Kind{shimast.KindSourceFile}
144
+ return []shimast.Kind{shimast.KindSourceFile}
145
145
  }
146
146
  func (securityDetectNoCSRFBeforeMethodOverride) Check(ctx *Context, node *shimast.Node) {
147
- seenCSRF := false
148
- walkDescendants(node, func(child *shimast.Node) {
149
- if child == nil || child.Kind != shimast.KindCallExpression {
150
- return
151
- }
152
- call := child.AsCallExpression()
153
- _, method, ok := propertyAccessParts(call.Expression)
154
- if !ok {
155
- return
156
- }
157
- switch method {
158
- case "csrf":
159
- seenCSRF = true
160
- case "methodOverride":
161
- if seenCSRF {
162
- ctx.Report(child, "CSRF middleware is configured before methodOverride.")
163
- }
164
- }
165
- })
147
+ seenCSRF := false
148
+ walkDescendants(node, func(child *shimast.Node) {
149
+ if child == nil || child.Kind != shimast.KindCallExpression {
150
+ return
151
+ }
152
+ call := child.AsCallExpression()
153
+ _, method, ok := propertyAccessParts(call.Expression)
154
+ if !ok {
155
+ return
156
+ }
157
+ switch method {
158
+ case "csrf":
159
+ seenCSRF = true
160
+ case "methodOverride":
161
+ if seenCSRF {
162
+ ctx.Report(child, "CSRF middleware is configured before methodOverride.")
163
+ }
164
+ }
165
+ })
166
166
  }
167
167
 
168
168
  type securityDetectNonLiteralFSFilename struct{}
169
169
 
170
170
  func (securityDetectNonLiteralFSFilename) Name() string {
171
- return securityRulePrefix + "detect-non-literal-fs-filename"
171
+ return securityRulePrefix + "detect-non-literal-fs-filename"
172
172
  }
173
173
  func (securityDetectNonLiteralFSFilename) Visits() []shimast.Kind {
174
- return []shimast.Kind{shimast.KindCallExpression}
174
+ return []shimast.Kind{shimast.KindCallExpression}
175
175
  }
176
176
  func (securityDetectNonLiteralFSFilename) Check(ctx *Context, node *shimast.Node) {
177
- call := node.AsCallExpression()
178
- if call == nil || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
179
- return
180
- }
181
- bindings := collectSecurityBindings(ctx.File)
182
- method, module, ok := fsCallInfo(call, bindings)
183
- if !ok || !isFSFilenameMethod(method) {
184
- return
185
- }
186
- if !isSecurityStaticExpression(call.Arguments.Nodes[0], bindings, nil) {
187
- ctx.Report(node, "Found "+method+" from package \""+module+"\" with non-literal filename argument.")
188
- }
177
+ call := node.AsCallExpression()
178
+ if call == nil || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
179
+ return
180
+ }
181
+ bindings := collectSecurityBindings(ctx.File)
182
+ method, module, ok := fsCallInfo(call, bindings)
183
+ if !ok || !isFSFilenameMethod(method) {
184
+ return
185
+ }
186
+ if !isSecurityStaticExpression(call.Arguments.Nodes[0], bindings, nil) {
187
+ ctx.Report(node, "Found "+method+" from package \""+module+"\" with non-literal filename argument.")
188
+ }
189
189
  }
190
190
 
191
191
  type securityDetectNonLiteralRegexp struct{}
192
192
 
193
193
  func (securityDetectNonLiteralRegexp) Name() string {
194
- return securityRulePrefix + "detect-non-literal-regexp"
194
+ return securityRulePrefix + "detect-non-literal-regexp"
195
195
  }
196
196
  func (securityDetectNonLiteralRegexp) Visits() []shimast.Kind {
197
- return []shimast.Kind{shimast.KindNewExpression, shimast.KindCallExpression}
197
+ return []shimast.Kind{shimast.KindNewExpression, shimast.KindCallExpression}
198
198
  }
199
199
  func (securityDetectNonLiteralRegexp) Check(ctx *Context, node *shimast.Node) {
200
- var args []*shimast.Node
201
- switch node.Kind {
202
- case shimast.KindNewExpression:
203
- expr := node.AsNewExpression()
204
- if expr == nil || identifierText(expr.Expression) != "RegExp" || expr.Arguments == nil {
205
- return
206
- }
207
- args = expr.Arguments.Nodes
208
- case shimast.KindCallExpression:
209
- call := node.AsCallExpression()
210
- if call == nil || identifierText(call.Expression) != "RegExp" || call.Arguments == nil {
211
- return
212
- }
213
- args = call.Arguments.Nodes
214
- }
215
- if len(args) == 0 {
216
- return
217
- }
218
- bindings := collectSecurityBindings(ctx.File)
219
- if !isSecurityStaticExpression(args[0], bindings, nil) {
220
- ctx.Report(node, "Found non-literal argument to RegExp constructor.")
221
- }
200
+ var args []*shimast.Node
201
+ switch node.Kind {
202
+ case shimast.KindNewExpression:
203
+ expr := node.AsNewExpression()
204
+ if expr == nil || identifierText(expr.Expression) != "RegExp" || expr.Arguments == nil {
205
+ return
206
+ }
207
+ args = expr.Arguments.Nodes
208
+ case shimast.KindCallExpression:
209
+ call := node.AsCallExpression()
210
+ if call == nil || identifierText(call.Expression) != "RegExp" || call.Arguments == nil {
211
+ return
212
+ }
213
+ args = call.Arguments.Nodes
214
+ }
215
+ if len(args) == 0 {
216
+ return
217
+ }
218
+ bindings := collectSecurityBindings(ctx.File)
219
+ if !isSecurityStaticExpression(args[0], bindings, nil) {
220
+ ctx.Report(node, "Found non-literal argument to RegExp constructor.")
221
+ }
222
222
  }
223
223
 
224
224
  type securityDetectNonLiteralRequire struct{}
225
225
 
226
226
  func (securityDetectNonLiteralRequire) Name() string {
227
- return securityRulePrefix + "detect-non-literal-require"
227
+ return securityRulePrefix + "detect-non-literal-require"
228
228
  }
229
229
  func (securityDetectNonLiteralRequire) Visits() []shimast.Kind {
230
- return []shimast.Kind{shimast.KindCallExpression}
230
+ return []shimast.Kind{shimast.KindCallExpression}
231
231
  }
232
232
  func (securityDetectNonLiteralRequire) Check(ctx *Context, node *shimast.Node) {
233
- call := node.AsCallExpression()
234
- if call == nil || callCalleeName(call) != "require" || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
235
- return
236
- }
237
- bindings := collectSecurityBindings(ctx.File)
238
- if !isSecurityStaticExpression(call.Arguments.Nodes[0], bindings, nil) {
239
- ctx.Report(node, "Found non-literal argument in require.")
240
- }
233
+ call := node.AsCallExpression()
234
+ if call == nil || callCalleeName(call) != "require" || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
235
+ return
236
+ }
237
+ bindings := collectSecurityBindings(ctx.File)
238
+ if !isSecurityStaticExpression(call.Arguments.Nodes[0], bindings, nil) {
239
+ ctx.Report(node, "Found non-literal argument in require.")
240
+ }
241
241
  }
242
242
 
243
243
  type securityDetectObjectInjection struct{}
244
244
 
245
245
  func (securityDetectObjectInjection) Name() string {
246
- return securityRulePrefix + "detect-object-injection"
246
+ return securityRulePrefix + "detect-object-injection"
247
247
  }
248
248
  func (securityDetectObjectInjection) Visits() []shimast.Kind {
249
- return []shimast.Kind{shimast.KindElementAccessExpression}
249
+ return []shimast.Kind{shimast.KindElementAccessExpression}
250
250
  }
251
251
  func (securityDetectObjectInjection) Check(ctx *Context, node *shimast.Node) {
252
- access := node.AsElementAccessExpression()
253
- if access == nil || isStaticPropertyKey(access.ArgumentExpression) {
254
- return
255
- }
256
- ctx.Report(node, "Generic object injection sink.")
252
+ access := node.AsElementAccessExpression()
253
+ if access == nil || isStaticPropertyKey(access.ArgumentExpression) {
254
+ return
255
+ }
256
+ ctx.Report(node, "Generic object injection sink.")
257
257
  }
258
258
 
259
259
  type securityDetectPossibleTimingAttacks struct{}
260
260
 
261
261
  func (securityDetectPossibleTimingAttacks) Name() string {
262
- return securityRulePrefix + "detect-possible-timing-attacks"
262
+ return securityRulePrefix + "detect-possible-timing-attacks"
263
263
  }
264
264
  func (securityDetectPossibleTimingAttacks) Visits() []shimast.Kind {
265
- return []shimast.Kind{shimast.KindBinaryExpression}
265
+ return []shimast.Kind{shimast.KindBinaryExpression}
266
266
  }
267
267
  func (securityDetectPossibleTimingAttacks) Check(ctx *Context, node *shimast.Node) {
268
- expr := node.AsBinaryExpression()
269
- if expr == nil || expr.OperatorToken == nil || !isEqualityOperator(expr.OperatorToken.Kind) {
270
- return
271
- }
272
- if containsSecretIdentifier(expr.Left) || containsSecretIdentifier(expr.Right) {
273
- ctx.Report(node, "Potential timing attack from direct secret comparison.")
274
- }
268
+ expr := node.AsBinaryExpression()
269
+ if expr == nil || expr.OperatorToken == nil || !isEqualityOperator(expr.OperatorToken.Kind) {
270
+ return
271
+ }
272
+ if containsSecretIdentifier(expr.Left) || containsSecretIdentifier(expr.Right) {
273
+ ctx.Report(node, "Potential timing attack from direct secret comparison.")
274
+ }
275
275
  }
276
276
 
277
277
  type securityDetectPseudoRandomBytes struct{}
278
278
 
279
279
  func (securityDetectPseudoRandomBytes) Name() string {
280
- return securityRulePrefix + "detect-pseudoRandomBytes"
280
+ return securityRulePrefix + "detect-pseudoRandomBytes"
281
281
  }
282
282
  func (securityDetectPseudoRandomBytes) Visits() []shimast.Kind {
283
- return []shimast.Kind{shimast.KindPropertyAccessExpression}
283
+ return []shimast.Kind{shimast.KindPropertyAccessExpression}
284
284
  }
285
285
  func (securityDetectPseudoRandomBytes) Check(ctx *Context, node *shimast.Node) {
286
- obj, prop, ok := propertyAccessParts(node)
287
- if ok && prop == "pseudoRandomBytes" && identifierText(obj) == "crypto" {
288
- ctx.Report(node, "Found crypto.pseudoRandomBytes which is not cryptographically strong.")
289
- }
286
+ obj, prop, ok := propertyAccessParts(node)
287
+ if ok && prop == "pseudoRandomBytes" && identifierText(obj) == "crypto" {
288
+ ctx.Report(node, "Found crypto.pseudoRandomBytes which is not cryptographically strong.")
289
+ }
290
290
  }
291
291
 
292
292
  type securityDetectUnsafeRegex struct{}
293
293
 
294
294
  func (securityDetectUnsafeRegex) Name() string { return securityRulePrefix + "detect-unsafe-regex" }
295
295
  func (securityDetectUnsafeRegex) Visits() []shimast.Kind {
296
- return []shimast.Kind{shimast.KindRegularExpressionLiteral, shimast.KindNewExpression}
296
+ return []shimast.Kind{shimast.KindRegularExpressionLiteral, shimast.KindNewExpression}
297
297
  }
298
298
  func (securityDetectUnsafeRegex) Check(ctx *Context, node *shimast.Node) {
299
- pattern := ""
300
- if node.Kind == shimast.KindRegularExpressionLiteral {
301
- pattern = regexpPatternFromLiteral(nodeText(ctx.File, node))
302
- } else {
303
- expr := node.AsNewExpression()
304
- if expr == nil || identifierText(expr.Expression) != "RegExp" || expr.Arguments == nil || len(expr.Arguments.Nodes) == 0 {
305
- return
306
- }
307
- pattern = stringLiteralText(expr.Arguments.Nodes[0])
308
- }
309
- if looksUnsafeRegex(pattern) {
310
- ctx.Report(node, "Unsafe regular expression.")
311
- }
299
+ pattern := ""
300
+ if node.Kind == shimast.KindRegularExpressionLiteral {
301
+ pattern = regexpPatternFromLiteral(nodeText(ctx.File, node))
302
+ } else {
303
+ expr := node.AsNewExpression()
304
+ if expr == nil || identifierText(expr.Expression) != "RegExp" || expr.Arguments == nil || len(expr.Arguments.Nodes) == 0 {
305
+ return
306
+ }
307
+ pattern = stringLiteralText(expr.Arguments.Nodes[0])
308
+ }
309
+ if looksUnsafeRegex(pattern) {
310
+ ctx.Report(node, "Unsafe regular expression.")
311
+ }
312
312
  }
313
313
 
314
314
  type securityNamedBinding struct {
315
- Module string
316
- Import string
315
+ Module string
316
+ Import string
317
317
  }
318
318
 
319
319
  type securityBindings struct {
320
- Modules map[string]string
321
- Named map[string]securityNamedBinding
322
- Static map[string]bool
320
+ Modules map[string]string
321
+ Named map[string]securityNamedBinding
322
+ Static map[string]bool
323
323
  }
324
324
 
325
325
  func collectSecurityBindings(file *shimast.SourceFile) securityBindings {
326
- bindings := securityBindings{
327
- Modules: map[string]string{},
328
- Named: map[string]securityNamedBinding{},
329
- Static: map[string]bool{},
330
- }
331
- if file == nil {
332
- return bindings
333
- }
334
- assigned := collectSecurityAssignedNames(file.AsNode())
335
- walkDescendants(file.AsNode(), func(node *shimast.Node) {
336
- if node == nil {
337
- return
338
- }
339
- switch node.Kind {
340
- case shimast.KindImportDeclaration:
341
- collectSecurityImportBindings(node, bindings)
342
- case shimast.KindVariableDeclaration:
343
- collectSecurityVariableBinding(node, bindings, assigned)
344
- }
345
- })
346
- return bindings
326
+ bindings := securityBindings{
327
+ Modules: map[string]string{},
328
+ Named: map[string]securityNamedBinding{},
329
+ Static: map[string]bool{},
330
+ }
331
+ if file == nil {
332
+ return bindings
333
+ }
334
+ assigned := collectSecurityAssignedNames(file.AsNode())
335
+ walkDescendants(file.AsNode(), func(node *shimast.Node) {
336
+ if node == nil {
337
+ return
338
+ }
339
+ switch node.Kind {
340
+ case shimast.KindImportDeclaration:
341
+ collectSecurityImportBindings(node, bindings)
342
+ case shimast.KindVariableDeclaration:
343
+ collectSecurityVariableBinding(node, bindings, assigned)
344
+ }
345
+ })
346
+ return bindings
347
347
  }
348
348
 
349
349
  func collectSecurityAssignedNames(node *shimast.Node) map[string]bool {
350
- assigned := map[string]bool{}
351
- walkDescendants(node, func(child *shimast.Node) {
352
- if child == nil {
353
- return
354
- }
355
- switch child.Kind {
356
- case shimast.KindBinaryExpression:
357
- expr := child.AsBinaryExpression()
358
- if expr == nil || expr.OperatorToken == nil || !isAssignmentOperator(expr.OperatorToken.Kind) {
359
- return
360
- }
361
- for _, name := range assignmentTargetNames(expr.Left) {
362
- assigned[name] = true
363
- }
364
- case shimast.KindPrefixUnaryExpression:
365
- expr := child.AsPrefixUnaryExpression()
366
- if expr == nil {
367
- return
368
- }
369
- if expr.Operator == shimast.KindPlusPlusToken || expr.Operator == shimast.KindMinusMinusToken {
370
- if name := identifierText(expr.Operand); name != "" {
371
- assigned[name] = true
372
- }
373
- }
374
- case shimast.KindPostfixUnaryExpression:
375
- expr := child.AsPostfixUnaryExpression()
376
- if expr == nil {
377
- return
378
- }
379
- if expr.Operator == shimast.KindPlusPlusToken || expr.Operator == shimast.KindMinusMinusToken {
380
- if name := identifierText(expr.Operand); name != "" {
381
- assigned[name] = true
382
- }
383
- }
384
- }
385
- })
386
- return assigned
350
+ assigned := map[string]bool{}
351
+ walkDescendants(node, func(child *shimast.Node) {
352
+ if child == nil {
353
+ return
354
+ }
355
+ switch child.Kind {
356
+ case shimast.KindBinaryExpression:
357
+ expr := child.AsBinaryExpression()
358
+ if expr == nil || expr.OperatorToken == nil || !isAssignmentOperator(expr.OperatorToken.Kind) {
359
+ return
360
+ }
361
+ for _, name := range assignmentTargetNames(expr.Left) {
362
+ assigned[name] = true
363
+ }
364
+ case shimast.KindPrefixUnaryExpression:
365
+ expr := child.AsPrefixUnaryExpression()
366
+ if expr == nil {
367
+ return
368
+ }
369
+ if expr.Operator == shimast.KindPlusPlusToken || expr.Operator == shimast.KindMinusMinusToken {
370
+ if name := identifierText(expr.Operand); name != "" {
371
+ assigned[name] = true
372
+ }
373
+ }
374
+ case shimast.KindPostfixUnaryExpression:
375
+ expr := child.AsPostfixUnaryExpression()
376
+ if expr == nil {
377
+ return
378
+ }
379
+ if expr.Operator == shimast.KindPlusPlusToken || expr.Operator == shimast.KindMinusMinusToken {
380
+ if name := identifierText(expr.Operand); name != "" {
381
+ assigned[name] = true
382
+ }
383
+ }
384
+ }
385
+ })
386
+ return assigned
387
387
  }
388
388
 
389
389
  func collectSecurityImportBindings(node *shimast.Node, bindings securityBindings) {
390
- decl := node.AsImportDeclaration()
391
- if decl == nil || decl.ImportClause == nil {
392
- return
393
- }
394
- module := stringLiteralText(decl.ModuleSpecifier)
395
- if module == "" {
396
- return
397
- }
398
- clause := decl.ImportClause.AsImportClause()
399
- if clause == nil {
400
- return
401
- }
402
- if name := identifierText(clause.Name()); name != "" {
403
- bindings.Modules[name] = module
404
- }
405
- if clause.NamedBindings == nil {
406
- return
407
- }
408
- switch clause.NamedBindings.Kind {
409
- case shimast.KindNamespaceImport:
410
- if ns := clause.NamedBindings.AsNamespaceImport(); ns != nil {
411
- bindings.Modules[identifierText(ns.Name())] = module
412
- }
413
- case shimast.KindNamedImports:
414
- named := clause.NamedBindings.AsNamedImports()
415
- if named == nil || named.Elements == nil {
416
- return
417
- }
418
- for _, el := range named.Elements.Nodes {
419
- spec := el.AsImportSpecifier()
420
- if spec == nil {
421
- continue
422
- }
423
- local := identifierText(spec.Name())
424
- imported := local
425
- if spec.PropertyName != nil {
426
- imported = identifierText(spec.PropertyName)
427
- }
428
- if local != "" && imported != "" {
429
- bindings.Named[local] = securityNamedBinding{Module: module, Import: imported}
430
- }
431
- }
432
- }
390
+ decl := node.AsImportDeclaration()
391
+ if decl == nil || decl.ImportClause == nil {
392
+ return
393
+ }
394
+ module := stringLiteralText(decl.ModuleSpecifier)
395
+ if module == "" {
396
+ return
397
+ }
398
+ clause := decl.ImportClause.AsImportClause()
399
+ if clause == nil {
400
+ return
401
+ }
402
+ if name := identifierText(clause.Name()); name != "" {
403
+ bindings.Modules[name] = module
404
+ }
405
+ if clause.NamedBindings == nil {
406
+ return
407
+ }
408
+ switch clause.NamedBindings.Kind {
409
+ case shimast.KindNamespaceImport:
410
+ if ns := clause.NamedBindings.AsNamespaceImport(); ns != nil {
411
+ bindings.Modules[identifierText(ns.Name())] = module
412
+ }
413
+ case shimast.KindNamedImports:
414
+ named := clause.NamedBindings.AsNamedImports()
415
+ if named == nil || named.Elements == nil {
416
+ return
417
+ }
418
+ for _, el := range named.Elements.Nodes {
419
+ spec := el.AsImportSpecifier()
420
+ if spec == nil {
421
+ continue
422
+ }
423
+ local := identifierText(spec.Name())
424
+ imported := local
425
+ if spec.PropertyName != nil {
426
+ imported = identifierText(spec.PropertyName)
427
+ }
428
+ if local != "" && imported != "" {
429
+ bindings.Named[local] = securityNamedBinding{Module: module, Import: imported}
430
+ }
431
+ }
432
+ }
433
433
  }
434
434
 
435
435
  func collectSecurityVariableBinding(node *shimast.Node, bindings securityBindings, assigned map[string]bool) {
436
- decl := node.AsVariableDeclaration()
437
- if decl == nil || decl.Initializer == nil {
438
- return
439
- }
440
- name := decl.Name()
441
- if local := identifierText(name); local != "" {
442
- if isStableSecurityDeclaration(node, local, assigned) {
443
- collectSecurityIdentifierBinding(local, decl.Initializer, bindings)
444
- }
445
- if isStableSecurityDeclaration(node, local, assigned) &&
446
- isSecurityStaticExpression(decl.Initializer, bindings, map[string]bool{local: true}) {
447
- bindings.Static[local] = true
448
- } else {
449
- delete(bindings.Static, local)
450
- }
451
- return
452
- }
453
- if name == nil || name.Kind != shimast.KindObjectBindingPattern {
454
- return
455
- }
456
- module, ok := moduleFromExpression(decl.Initializer, bindings)
457
- if !ok {
458
- return
459
- }
460
- pattern := name.AsBindingPattern()
461
- if pattern == nil || pattern.Elements == nil {
462
- return
463
- }
464
- for _, el := range pattern.Elements.Nodes {
465
- binding := el.AsBindingElement()
466
- if binding == nil {
467
- continue
468
- }
469
- local := identifierText(binding.Name())
470
- imported := local
471
- if binding.PropertyName != nil {
472
- imported = identifierText(binding.PropertyName)
473
- }
474
- if local != "" && imported != "" && isStableSecurityDeclaration(node, local, assigned) {
475
- bindings.Named[local] = securityNamedBinding{Module: module, Import: imported}
476
- }
477
- }
436
+ decl := node.AsVariableDeclaration()
437
+ if decl == nil || decl.Initializer == nil {
438
+ return
439
+ }
440
+ name := decl.Name()
441
+ if local := identifierText(name); local != "" {
442
+ if isStableSecurityDeclaration(node, local, assigned) {
443
+ collectSecurityIdentifierBinding(local, decl.Initializer, bindings)
444
+ }
445
+ if isStableSecurityDeclaration(node, local, assigned) &&
446
+ isSecurityStaticExpression(decl.Initializer, bindings, map[string]bool{local: true}) {
447
+ bindings.Static[local] = true
448
+ } else {
449
+ delete(bindings.Static, local)
450
+ }
451
+ return
452
+ }
453
+ if name == nil || name.Kind != shimast.KindObjectBindingPattern {
454
+ return
455
+ }
456
+ module, ok := moduleFromExpression(decl.Initializer, bindings)
457
+ if !ok {
458
+ return
459
+ }
460
+ pattern := name.AsBindingPattern()
461
+ if pattern == nil || pattern.Elements == nil {
462
+ return
463
+ }
464
+ for _, el := range pattern.Elements.Nodes {
465
+ binding := el.AsBindingElement()
466
+ if binding == nil {
467
+ continue
468
+ }
469
+ local := identifierText(binding.Name())
470
+ imported := local
471
+ if binding.PropertyName != nil {
472
+ imported = identifierText(binding.PropertyName)
473
+ }
474
+ if local != "" && imported != "" && isStableSecurityDeclaration(node, local, assigned) {
475
+ bindings.Named[local] = securityNamedBinding{Module: module, Import: imported}
476
+ }
477
+ }
478
478
  }
479
479
 
480
480
  func isStableSecurityDeclaration(node *shimast.Node, local string, assigned map[string]bool) bool {
481
- if local == "" || assigned[local] {
482
- return false
483
- }
484
- return shimast.IsConst(node) || shimast.IsLet(node) || shimast.IsVar(node)
481
+ if local == "" || assigned[local] {
482
+ return false
483
+ }
484
+ return shimast.IsConst(node) || shimast.IsLet(node) || shimast.IsVar(node)
485
485
  }
486
486
 
487
487
  func collectSecurityIdentifierBinding(local string, init *shimast.Node, bindings securityBindings) {
488
- if module, ok := requireExpressionModule(init); ok {
489
- bindings.Modules[local] = module
490
- return
491
- }
492
- if obj, prop, ok := propertyAccessParts(init); ok {
493
- if module, ok := requireExpressionModule(obj); ok {
494
- if prop == "promises" {
495
- bindings.Modules[local] = module
496
- } else {
497
- bindings.Named[local] = securityNamedBinding{Module: module, Import: prop}
498
- }
499
- return
500
- }
501
- if module, ok := bindings.Modules[identifierText(obj)]; ok && prop == "promises" {
502
- bindings.Modules[local] = module
503
- }
504
- }
488
+ if module, ok := requireExpressionModule(init); ok {
489
+ bindings.Modules[local] = module
490
+ return
491
+ }
492
+ if obj, prop, ok := propertyAccessParts(init); ok {
493
+ if module, ok := requireExpressionModule(obj); ok {
494
+ if prop == "promises" {
495
+ bindings.Modules[local] = module
496
+ } else {
497
+ bindings.Named[local] = securityNamedBinding{Module: module, Import: prop}
498
+ }
499
+ return
500
+ }
501
+ if module, ok := bindings.Modules[identifierText(obj)]; ok && prop == "promises" {
502
+ bindings.Modules[local] = module
503
+ }
504
+ }
505
505
  }
506
506
 
507
507
  func moduleFromExpression(node *shimast.Node, bindings securityBindings) (string, bool) {
508
- if module, ok := requireExpressionModule(node); ok {
509
- return module, true
510
- }
511
- if obj, prop, ok := propertyAccessParts(node); ok && prop == "promises" {
512
- if module, ok := requireExpressionModule(obj); ok {
513
- return module, true
514
- }
515
- if module, ok := bindings.Modules[identifierText(obj)]; ok {
516
- return module, true
517
- }
518
- }
519
- return "", false
508
+ if module, ok := requireExpressionModule(node); ok {
509
+ return module, true
510
+ }
511
+ if obj, prop, ok := propertyAccessParts(node); ok && prop == "promises" {
512
+ if module, ok := requireExpressionModule(obj); ok {
513
+ return module, true
514
+ }
515
+ if module, ok := bindings.Modules[identifierText(obj)]; ok {
516
+ return module, true
517
+ }
518
+ }
519
+ return "", false
520
520
  }
521
521
 
522
522
  func propertyAccessParts(node *shimast.Node) (*shimast.Node, string, bool) {
523
- node = stripParens(node)
524
- if node == nil || node.Kind != shimast.KindPropertyAccessExpression {
525
- return nil, "", false
526
- }
527
- access := node.AsPropertyAccessExpression()
528
- if access == nil {
529
- return nil, "", false
530
- }
531
- prop := identifierText(access.Name())
532
- return access.Expression, prop, prop != ""
523
+ node = stripParens(node)
524
+ if node == nil || node.Kind != shimast.KindPropertyAccessExpression {
525
+ return nil, "", false
526
+ }
527
+ access := node.AsPropertyAccessExpression()
528
+ if access == nil {
529
+ return nil, "", false
530
+ }
531
+ prop := identifierText(access.Name())
532
+ return access.Expression, prop, prop != ""
533
533
  }
534
534
 
535
535
  func requireCallModule(call *shimast.CallExpression) (string, bool) {
536
- if call == nil || callCalleeName(call) != "require" || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
537
- return "", false
538
- }
539
- module := stringLiteralText(call.Arguments.Nodes[0])
540
- return module, module != ""
536
+ if call == nil || callCalleeName(call) != "require" || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
537
+ return "", false
538
+ }
539
+ module := stringLiteralText(call.Arguments.Nodes[0])
540
+ return module, module != ""
541
541
  }
542
542
 
543
543
  func requireExpressionModule(node *shimast.Node) (string, bool) {
544
- node = stripParens(node)
545
- if node == nil || node.Kind != shimast.KindCallExpression {
546
- return "", false
547
- }
548
- return requireCallModule(node.AsCallExpression())
544
+ node = stripParens(node)
545
+ if node == nil || node.Kind != shimast.KindCallExpression {
546
+ return "", false
547
+ }
548
+ return requireCallModule(node.AsCallExpression())
549
549
  }
550
550
 
551
551
  func isSecurityStaticExpression(node *shimast.Node, bindings securityBindings, seen map[string]bool) bool {
552
- node = stripParens(node)
553
- if node == nil {
554
- return false
555
- }
556
- switch node.Kind {
557
- case shimast.KindStringLiteral,
558
- shimast.KindNumericLiteral,
559
- shimast.KindBigIntLiteral,
560
- shimast.KindNoSubstitutionTemplateLiteral,
561
- shimast.KindRegularExpressionLiteral,
562
- shimast.KindTrueKeyword,
563
- shimast.KindFalseKeyword,
564
- shimast.KindNullKeyword:
565
- return true
566
- case shimast.KindIdentifier:
567
- name := identifierText(node)
568
- if name == "__dirname" || name == "__filename" {
569
- return true
570
- }
571
- if seen != nil && seen[name] {
572
- return false
573
- }
574
- return bindings.Static[name]
575
- case shimast.KindTemplateExpression:
576
- expr := node.AsTemplateExpression()
577
- if expr == nil || expr.TemplateSpans == nil {
578
- return false
579
- }
580
- for _, spanNode := range expr.TemplateSpans.Nodes {
581
- span := spanNode.AsTemplateSpan()
582
- if span == nil || !isSecurityStaticExpression(span.Expression, bindings, seen) {
583
- return false
584
- }
585
- }
586
- return true
587
- case shimast.KindBinaryExpression:
588
- expr := node.AsBinaryExpression()
589
- return expr != nil && expr.OperatorToken != nil && expr.OperatorToken.Kind == shimast.KindPlusToken &&
590
- isSecurityStaticExpression(expr.Left, bindings, seen) &&
591
- isSecurityStaticExpression(expr.Right, bindings, seen)
592
- case shimast.KindCallExpression:
593
- return isSecurityStaticCall(node.AsCallExpression(), bindings, seen)
594
- case shimast.KindPropertyAccessExpression:
595
- return isMatchingPropertyAccess(node, "import", "meta", "url")
596
- case shimast.KindMetaProperty:
597
- return nodeText(nil, node) == "import.meta"
598
- }
599
- return false
552
+ node = stripParens(node)
553
+ if node == nil {
554
+ return false
555
+ }
556
+ switch node.Kind {
557
+ case shimast.KindStringLiteral,
558
+ shimast.KindNumericLiteral,
559
+ shimast.KindBigIntLiteral,
560
+ shimast.KindNoSubstitutionTemplateLiteral,
561
+ shimast.KindRegularExpressionLiteral,
562
+ shimast.KindTrueKeyword,
563
+ shimast.KindFalseKeyword,
564
+ shimast.KindNullKeyword:
565
+ return true
566
+ case shimast.KindIdentifier:
567
+ name := identifierText(node)
568
+ if name == "__dirname" || name == "__filename" {
569
+ return true
570
+ }
571
+ if seen != nil && seen[name] {
572
+ return false
573
+ }
574
+ return bindings.Static[name]
575
+ case shimast.KindTemplateExpression:
576
+ expr := node.AsTemplateExpression()
577
+ if expr == nil || expr.TemplateSpans == nil {
578
+ return false
579
+ }
580
+ for _, spanNode := range expr.TemplateSpans.Nodes {
581
+ span := spanNode.AsTemplateSpan()
582
+ if span == nil || !isSecurityStaticExpression(span.Expression, bindings, seen) {
583
+ return false
584
+ }
585
+ }
586
+ return true
587
+ case shimast.KindBinaryExpression:
588
+ expr := node.AsBinaryExpression()
589
+ return expr != nil && expr.OperatorToken != nil && expr.OperatorToken.Kind == shimast.KindPlusToken &&
590
+ isSecurityStaticExpression(expr.Left, bindings, seen) &&
591
+ isSecurityStaticExpression(expr.Right, bindings, seen)
592
+ case shimast.KindCallExpression:
593
+ return isSecurityStaticCall(node.AsCallExpression(), bindings, seen)
594
+ case shimast.KindPropertyAccessExpression:
595
+ return isMatchingPropertyAccess(node, "import", "meta", "url")
596
+ case shimast.KindMetaProperty:
597
+ return nodeText(nil, node) == "import.meta"
598
+ }
599
+ return false
600
600
  }
601
601
 
602
602
  func isSecurityStaticCall(call *shimast.CallExpression, bindings securityBindings, seen map[string]bool) bool {
603
- if call == nil || call.Arguments == nil {
604
- return false
605
- }
606
- if isMatchingPropertyAccess(call.Expression, "process", "cwd") && len(call.Arguments.Nodes) == 0 {
607
- return true
608
- }
609
- if isMatchingPropertyAccess(call.Expression, "require", "resolve") {
610
- return len(call.Arguments.Nodes) > 0 && isSecurityStaticExpression(call.Arguments.Nodes[0], bindings, seen)
611
- }
612
- obj, method, ok := propertyAccessParts(call.Expression)
613
- if !ok || call.Arguments == nil {
614
- return false
615
- }
616
- objectName := identifierText(obj)
617
- if (objectName == "path" || bindings.Modules[objectName] == "path" || bindings.Modules[objectName] == "node:path") &&
618
- (method == "join" || method == "resolve" || method == "dirname") {
619
- for _, arg := range call.Arguments.Nodes {
620
- if !isSecurityStaticExpression(arg, bindings, seen) {
621
- return false
622
- }
623
- }
624
- return true
625
- }
626
- return false
603
+ if call == nil || call.Arguments == nil {
604
+ return false
605
+ }
606
+ if isMatchingPropertyAccess(call.Expression, "process", "cwd") && len(call.Arguments.Nodes) == 0 {
607
+ return true
608
+ }
609
+ if isMatchingPropertyAccess(call.Expression, "require", "resolve") {
610
+ return len(call.Arguments.Nodes) > 0 && isSecurityStaticExpression(call.Arguments.Nodes[0], bindings, seen)
611
+ }
612
+ obj, method, ok := propertyAccessParts(call.Expression)
613
+ if !ok || call.Arguments == nil {
614
+ return false
615
+ }
616
+ objectName := identifierText(obj)
617
+ if (objectName == "path" || bindings.Modules[objectName] == "path" || bindings.Modules[objectName] == "node:path") &&
618
+ (method == "join" || method == "resolve" || method == "dirname") {
619
+ for _, arg := range call.Arguments.Nodes {
620
+ if !isSecurityStaticExpression(arg, bindings, seen) {
621
+ return false
622
+ }
623
+ }
624
+ return true
625
+ }
626
+ return false
627
627
  }
628
628
 
629
629
  func isChildProcessExecCall(call *shimast.CallExpression, bindings securityBindings) bool {
630
- callee := stripParens(call.Expression)
631
- if name := identifierText(callee); name != "" {
632
- binding, ok := bindings.Named[name]
633
- return ok && isChildProcessModule(binding.Module) && binding.Import == "exec"
634
- }
635
- obj, prop, ok := propertyAccessParts(callee)
636
- if !ok || prop != "exec" {
637
- return false
638
- }
639
- if module, ok := requireExpressionModule(obj); ok {
640
- return isChildProcessModule(module)
641
- }
642
- module, ok := bindings.Modules[identifierText(obj)]
643
- return ok && isChildProcessModule(module)
630
+ callee := stripParens(call.Expression)
631
+ if name := identifierText(callee); name != "" {
632
+ binding, ok := bindings.Named[name]
633
+ return ok && isChildProcessModule(binding.Module) && binding.Import == "exec"
634
+ }
635
+ obj, prop, ok := propertyAccessParts(callee)
636
+ if !ok || prop != "exec" {
637
+ return false
638
+ }
639
+ if module, ok := requireExpressionModule(obj); ok {
640
+ return isChildProcessModule(module)
641
+ }
642
+ module, ok := bindings.Modules[identifierText(obj)]
643
+ return ok && isChildProcessModule(module)
644
644
  }
645
645
 
646
646
  func isInlineChildProcessExecRequire(node *shimast.Node, bindings securityBindings) bool {
647
- parent := node.Parent
648
- if parent == nil || parent.Kind != shimast.KindPropertyAccessExpression {
649
- return false
650
- }
651
- access := parent.AsPropertyAccessExpression()
652
- if access == nil || access.Expression != node || identifierText(access.Name()) != "exec" {
653
- return false
654
- }
655
- outerNode := parent.Parent
656
- if outerNode == nil || outerNode.Kind != shimast.KindCallExpression {
657
- return false
658
- }
659
- outer := outerNode.AsCallExpression()
660
- if outer == nil || outer.Expression != parent || outer.Arguments == nil || len(outer.Arguments.Nodes) == 0 {
661
- return false
662
- }
663
- return !isSecurityStaticExpression(outer.Arguments.Nodes[0], bindings, nil) &&
664
- isChildProcessExecCall(outer, bindings)
647
+ parent := node.Parent
648
+ if parent == nil || parent.Kind != shimast.KindPropertyAccessExpression {
649
+ return false
650
+ }
651
+ access := parent.AsPropertyAccessExpression()
652
+ if access == nil || access.Expression != node || identifierText(access.Name()) != "exec" {
653
+ return false
654
+ }
655
+ outerNode := parent.Parent
656
+ if outerNode == nil || outerNode.Kind != shimast.KindCallExpression {
657
+ return false
658
+ }
659
+ outer := outerNode.AsCallExpression()
660
+ if outer == nil || outer.Expression != parent || outer.Arguments == nil || len(outer.Arguments.Nodes) == 0 {
661
+ return false
662
+ }
663
+ return !isSecurityStaticExpression(outer.Arguments.Nodes[0], bindings, nil) &&
664
+ isChildProcessExecCall(outer, bindings)
665
665
  }
666
666
 
667
667
  func fsCallInfo(call *shimast.CallExpression, bindings securityBindings) (string, string, bool) {
668
- callee := stripParens(call.Expression)
669
- if name := identifierText(callee); name != "" {
670
- binding, ok := bindings.Named[name]
671
- if ok && isFSModule(binding.Module) {
672
- return binding.Import, binding.Module, true
673
- }
674
- }
675
- obj, method, ok := propertyAccessParts(callee)
676
- if !ok {
677
- return "", "", false
678
- }
679
- if inner, prop, ok := propertyAccessParts(obj); ok && prop == "promises" {
680
- if module, ok := requireExpressionModule(inner); ok && isFSModule(module) {
681
- return method, module, true
682
- }
683
- if module, ok := bindings.Modules[identifierText(inner)]; ok && isFSModule(module) {
684
- return method, module, true
685
- }
686
- }
687
- if module, ok := requireExpressionModule(obj); ok && isFSModule(module) {
688
- return method, module, true
689
- }
690
- if module, ok := bindings.Modules[identifierText(obj)]; ok && isFSModule(module) {
691
- return method, module, true
692
- }
693
- return "", "", false
668
+ callee := stripParens(call.Expression)
669
+ if name := identifierText(callee); name != "" {
670
+ binding, ok := bindings.Named[name]
671
+ if ok && isFSModule(binding.Module) {
672
+ return binding.Import, binding.Module, true
673
+ }
674
+ }
675
+ obj, method, ok := propertyAccessParts(callee)
676
+ if !ok {
677
+ return "", "", false
678
+ }
679
+ if inner, prop, ok := propertyAccessParts(obj); ok && prop == "promises" {
680
+ if module, ok := requireExpressionModule(inner); ok && isFSModule(module) {
681
+ return method, module, true
682
+ }
683
+ if module, ok := bindings.Modules[identifierText(inner)]; ok && isFSModule(module) {
684
+ return method, module, true
685
+ }
686
+ }
687
+ if module, ok := requireExpressionModule(obj); ok && isFSModule(module) {
688
+ return method, module, true
689
+ }
690
+ if module, ok := bindings.Modules[identifierText(obj)]; ok && isFSModule(module) {
691
+ return method, module, true
692
+ }
693
+ return "", "", false
694
694
  }
695
695
 
696
696
  func bufferNoAssertIndex(method string) (int, bool) {
697
- if strings.HasPrefix(method, "read") {
698
- switch method {
699
- case "readUInt8", "readUInt16LE", "readUInt16BE", "readUInt32LE", "readUInt32BE",
700
- "readInt8", "readInt16LE", "readInt16BE", "readInt32LE", "readInt32BE",
701
- "readFloatLE", "readFloatBE", "readDoubleLE", "readDoubleBE":
702
- return 1, true
703
- }
704
- }
705
- if strings.HasPrefix(method, "write") {
706
- switch method {
707
- case "writeUInt8", "writeUInt16LE", "writeUInt16BE", "writeUInt32LE", "writeUInt32BE",
708
- "writeInt8", "writeInt16LE", "writeInt16BE", "writeInt32LE", "writeInt32BE",
709
- "writeFloatLE", "writeFloatBE", "writeDoubleLE", "writeDoubleBE":
710
- return 2, true
711
- }
712
- }
713
- return 0, false
697
+ if strings.HasPrefix(method, "read") {
698
+ switch method {
699
+ case "readUInt8", "readUInt16LE", "readUInt16BE", "readUInt32LE", "readUInt32BE",
700
+ "readInt8", "readInt16LE", "readInt16BE", "readInt32LE", "readInt32BE",
701
+ "readFloatLE", "readFloatBE", "readDoubleLE", "readDoubleBE":
702
+ return 1, true
703
+ }
704
+ }
705
+ if strings.HasPrefix(method, "write") {
706
+ switch method {
707
+ case "writeUInt8", "writeUInt16LE", "writeUInt16BE", "writeUInt32LE", "writeUInt32BE",
708
+ "writeInt8", "writeInt16LE", "writeInt16BE", "writeInt32LE", "writeInt32BE",
709
+ "writeFloatLE", "writeFloatBE", "writeDoubleLE", "writeDoubleBE":
710
+ return 2, true
711
+ }
712
+ }
713
+ return 0, false
714
714
  }
715
715
 
716
716
  func isFSFilenameMethod(method string) bool {
717
- switch method {
718
- case "access", "appendFile", "chmod", "chown", "copyFile", "createReadStream", "createWriteStream",
719
- "exists", "lchmod", "lchown", "link", "lstat", "mkdir", "mkdtemp", "open", "opendir",
720
- "readFile", "readdir", "readlink", "realpath", "rename", "rm", "rmdir", "stat", "truncate",
721
- "unlink", "utimes", "watch", "writeFile",
722
- "accessSync", "appendFileSync", "chmodSync", "chownSync", "copyFileSync", "existsSync",
723
- "lchmodSync", "lchownSync", "linkSync", "lstatSync", "mkdirSync", "mkdtempSync", "openSync",
724
- "opendirSync", "readFileSync", "readdirSync", "readlinkSync", "realpathSync", "renameSync",
725
- "rmSync", "rmdirSync", "statSync", "truncateSync", "unlinkSync", "utimesSync", "watchFile",
726
- "writeFileSync":
727
- return true
728
- }
729
- return false
717
+ switch method {
718
+ case "access", "appendFile", "chmod", "chown", "copyFile", "createReadStream", "createWriteStream",
719
+ "exists", "lchmod", "lchown", "link", "lstat", "mkdir", "mkdtemp", "open", "opendir",
720
+ "readFile", "readdir", "readlink", "realpath", "rename", "rm", "rmdir", "stat", "truncate",
721
+ "unlink", "utimes", "watch", "writeFile",
722
+ "accessSync", "appendFileSync", "chmodSync", "chownSync", "copyFileSync", "existsSync",
723
+ "lchmodSync", "lchownSync", "linkSync", "lstatSync", "mkdirSync", "mkdtempSync", "openSync",
724
+ "opendirSync", "readFileSync", "readdirSync", "readlinkSync", "realpathSync", "renameSync",
725
+ "rmSync", "rmdirSync", "statSync", "truncateSync", "unlinkSync", "utimesSync", "watchFile",
726
+ "writeFileSync":
727
+ return true
728
+ }
729
+ return false
730
730
  }
731
731
 
732
732
  func isFSModule(module string) bool {
733
- return module == "fs" || module == "node:fs" || module == "fs/promises" ||
734
- module == "node:fs/promises" || module == "fs-extra"
733
+ return module == "fs" || module == "node:fs" || module == "fs/promises" ||
734
+ module == "node:fs/promises" || module == "fs-extra"
735
735
  }
736
736
 
737
737
  func isChildProcessModule(module string) bool {
738
- return module == "child_process" || module == "node:child_process"
738
+ return module == "child_process" || module == "node:child_process"
739
739
  }
740
740
 
741
741
  func isTrueLiteral(node *shimast.Node) bool {
742
- value, ok := isLiteralBoolean(stripParens(node))
743
- return ok && value
742
+ value, ok := isLiteralBoolean(stripParens(node))
743
+ return ok && value
744
744
  }
745
745
 
746
746
  func isFalseLiteral(node *shimast.Node) bool {
747
- value, ok := isLiteralBoolean(stripParens(node))
748
- return ok && !value
747
+ value, ok := isLiteralBoolean(stripParens(node))
748
+ return ok && !value
749
749
  }
750
750
 
751
751
  func isEqualityOperator(kind shimast.Kind) bool {
752
- switch kind {
753
- case shimast.KindEqualsEqualsToken,
754
- shimast.KindEqualsEqualsEqualsToken,
755
- shimast.KindExclamationEqualsToken,
756
- shimast.KindExclamationEqualsEqualsToken:
757
- return true
758
- }
759
- return false
752
+ switch kind {
753
+ case shimast.KindEqualsEqualsToken,
754
+ shimast.KindEqualsEqualsEqualsToken,
755
+ shimast.KindExclamationEqualsToken,
756
+ shimast.KindExclamationEqualsEqualsToken:
757
+ return true
758
+ }
759
+ return false
760
760
  }
761
761
 
762
762
  func containsSecretIdentifier(node *shimast.Node) bool {
763
- found := false
764
- walkDescendants(node, func(child *shimast.Node) {
765
- name := strings.ToLower(identifierText(child))
766
- if name == "" {
767
- return
768
- }
769
- if strings.Contains(name, "password") ||
770
- strings.Contains(name, "passwd") ||
771
- strings.Contains(name, "secret") ||
772
- strings.Contains(name, "token") ||
773
- strings.Contains(name, "apikey") ||
774
- strings.Contains(name, "api_key") {
775
- found = true
776
- }
777
- })
778
- return found
763
+ found := false
764
+ walkDescendants(node, func(child *shimast.Node) {
765
+ name := strings.ToLower(identifierText(child))
766
+ if name == "" {
767
+ return
768
+ }
769
+ if strings.Contains(name, "password") ||
770
+ strings.Contains(name, "passwd") ||
771
+ strings.Contains(name, "secret") ||
772
+ strings.Contains(name, "token") ||
773
+ strings.Contains(name, "apikey") ||
774
+ strings.Contains(name, "api_key") {
775
+ found = true
776
+ }
777
+ })
778
+ return found
779
779
  }
780
780
 
781
781
  func isBidiControlRune(r rune) bool {
782
- switch r {
783
- case '\u202A', '\u202B', '\u202C', '\u202D', '\u202E', '\u2066', '\u2067', '\u2068', '\u2069':
784
- return true
785
- }
786
- return false
782
+ switch r {
783
+ case '\u202A', '\u202B', '\u202C', '\u202D', '\u202E', '\u2066', '\u2067', '\u2068', '\u2069':
784
+ return true
785
+ }
786
+ return false
787
787
  }
788
788
 
789
789
  func regexpPatternFromLiteral(text string) string {
790
- if !strings.HasPrefix(text, "/") {
791
- return text
792
- }
793
- escaped := false
794
- for i := len(text) - 1; i > 0; i-- {
795
- if text[i] != '/' || escaped {
796
- escaped = text[i] == '\\' && !escaped
797
- continue
798
- }
799
- return text[1:i]
800
- }
801
- return text
790
+ if !strings.HasPrefix(text, "/") {
791
+ return text
792
+ }
793
+ escaped := false
794
+ for i := len(text) - 1; i > 0; i-- {
795
+ if text[i] != '/' || escaped {
796
+ escaped = text[i] == '\\' && !escaped
797
+ continue
798
+ }
799
+ return text[1:i]
800
+ }
801
+ return text
802
802
  }
803
803
 
804
804
  func looksUnsafeRegex(pattern string) bool {
805
- if pattern == "" {
806
- return false
807
- }
808
- return strings.Contains(pattern, "+)+") ||
809
- strings.Contains(pattern, "*)+") ||
810
- strings.Contains(pattern, "+}+") ||
811
- strings.Contains(pattern, "*)*") ||
812
- strings.Contains(pattern, "(.*)+")
805
+ if pattern == "" {
806
+ return false
807
+ }
808
+ return strings.Contains(pattern, "+)+") ||
809
+ strings.Contains(pattern, "*)+") ||
810
+ strings.Contains(pattern, "+}+") ||
811
+ strings.Contains(pattern, "*)*") ||
812
+ strings.Contains(pattern, "(.*)+")
813
813
  }
814
814
 
815
815
  func init() {
816
- Register(securityDetectBidiCharacters{})
817
- Register(securityDetectBufferNoassert{})
818
- Register(securityDetectChildProcess{})
819
- Register(securityDetectDisableMustacheEscape{})
820
- Register(securityDetectEvalWithExpression{})
821
- Register(securityDetectNewBuffer{})
822
- Register(securityDetectNoCSRFBeforeMethodOverride{})
823
- Register(securityDetectNonLiteralFSFilename{})
824
- Register(securityDetectNonLiteralRegexp{})
825
- Register(securityDetectNonLiteralRequire{})
826
- Register(securityDetectObjectInjection{})
827
- Register(securityDetectPossibleTimingAttacks{})
828
- Register(securityDetectPseudoRandomBytes{})
829
- Register(securityDetectUnsafeRegex{})
816
+ Register(securityDetectBidiCharacters{})
817
+ Register(securityDetectBufferNoassert{})
818
+ Register(securityDetectChildProcess{})
819
+ Register(securityDetectDisableMustacheEscape{})
820
+ Register(securityDetectEvalWithExpression{})
821
+ Register(securityDetectNewBuffer{})
822
+ Register(securityDetectNoCSRFBeforeMethodOverride{})
823
+ Register(securityDetectNonLiteralFSFilename{})
824
+ Register(securityDetectNonLiteralRegexp{})
825
+ Register(securityDetectNonLiteralRequire{})
826
+ Register(securityDetectObjectInjection{})
827
+ Register(securityDetectPossibleTimingAttacks{})
828
+ Register(securityDetectPseudoRandomBytes{})
829
+ Register(securityDetectUnsafeRegex{})
830
830
  }