sonicop 26.8.111 → 26.8.113

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 (436) hide show
  1. checksums.yaml +4 -4
  2. data/CONFORMANCE.md +64 -31
  3. data/Cargo.lock +94 -19
  4. data/Cargo.toml +13 -8
  5. data/NOTICE +15 -3
  6. data/README.ja.md +145 -57
  7. data/README.md +140 -48
  8. data/build.rs +155 -0
  9. data/lib/sonicop/runner.rb +7 -1
  10. data/lib/sonicop/version.rb +1 -1
  11. data/src/cli.rs +216 -45
  12. data/src/config/inheritance.rs +637 -64
  13. data/src/config/mod.rs +130 -1
  14. data/src/config/paths.rs +550 -48
  15. data/src/cop_name.rs +18 -1
  16. data/src/diagnostic.rs +168 -3
  17. data/src/directives.rs +398 -77
  18. data/src/display_width.rs +82 -42
  19. data/src/display_width_table.rs +506 -0
  20. data/src/engine.rs +1532 -51
  21. data/src/formatter.rs +10 -1
  22. data/src/lib.rs +2 -0
  23. data/src/main.rs +112 -1
  24. data/src/nul_bytes.rs +245 -47
  25. data/src/parser.rs +37 -0
  26. data/src/profile.rs +174 -24
  27. data/src/ruby_version.rs +167 -21
  28. data/src/rules/bundler/duplicated_group.rs +3 -2
  29. data/src/rules/bundler/gem_comment.rs +5 -4
  30. data/src/rules/gemspec/attribute_assignment.rs +4 -3
  31. data/src/rules/gemspec/deprecated_attribute_assignment.rs +7 -6
  32. data/src/rules/gemspec/duplicated_assignment.rs +4 -3
  33. data/src/rules/gemspec/require_mfa.rs +10 -9
  34. data/src/rules/gemspec/required_ruby_version.rs +3 -2
  35. data/src/rules/gemspec/support.rs +28 -8
  36. data/src/rules/layout/access_modifier_indentation.rs +13 -6
  37. data/src/rules/layout/argument_alignment.rs +19 -7
  38. data/src/rules/layout/array_alignment.rs +1 -2
  39. data/src/rules/layout/block_alignment.rs +74 -30
  40. data/src/rules/layout/block_end_newline.rs +5 -5
  41. data/src/rules/layout/class_structure.rs +13 -4
  42. data/src/rules/layout/closing_heredoc_indentation.rs +24 -5
  43. data/src/rules/layout/closing_parenthesis_indentation.rs +3 -3
  44. data/src/rules/layout/comment_indentation.rs +2 -1
  45. data/src/rules/layout/condition_position.rs +3 -2
  46. data/src/rules/layout/def_end_alignment.rs +10 -6
  47. data/src/rules/layout/dot_position.rs +1 -2
  48. data/src/rules/layout/else_alignment.rs +24 -14
  49. data/src/rules/layout/empty_line_after_guard_clause.rs +8 -7
  50. data/src/rules/layout/empty_line_after_multiline_condition.rs +5 -5
  51. data/src/rules/layout/empty_line_between_defs.rs +8 -6
  52. data/src/rules/layout/empty_lines_around_access_modifier.rs +22 -10
  53. data/src/rules/layout/empty_lines_around_arguments.rs +4 -4
  54. data/src/rules/layout/empty_lines_around_body.rs +8 -2
  55. data/src/rules/layout/empty_lines_around_exception_handling_keywords.rs +1 -2
  56. data/src/rules/layout/empty_lines_around_method_body.rs +3 -2
  57. data/src/rules/layout/end_alignment.rs +32 -7
  58. data/src/rules/layout/end_of_line.rs +14 -1
  59. data/src/rules/layout/extra_spacing.rs +9 -11
  60. data/src/rules/layout/first_array_element_indentation.rs +1 -2
  61. data/src/rules/layout/first_hash_element_indentation.rs +8 -6
  62. data/src/rules/layout/hash_alignment.rs +113 -19
  63. data/src/rules/layout/heredoc_argument_closing_parenthesis.rs +6 -5
  64. data/src/rules/layout/heredoc_indentation.rs +4 -3
  65. data/src/rules/layout/indentation_style.rs +6 -4
  66. data/src/rules/layout/indentation_width.rs +76 -16
  67. data/src/rules/layout/initial_indentation.rs +18 -5
  68. data/src/rules/layout/line_continuation_leading_space.rs +4 -3
  69. data/src/rules/layout/line_continuation_spacing.rs +4 -3
  70. data/src/rules/layout/line_length.rs +417 -43
  71. data/src/rules/layout/mod.rs +1 -1
  72. data/src/rules/layout/multiline_assignment_layout.rs +20 -3
  73. data/src/rules/layout/multiline_block_layout.rs +1 -2
  74. data/src/rules/layout/multiline_expression.rs +60 -31
  75. data/src/rules/layout/multiline_hash_brace_layout.rs +4 -3
  76. data/src/rules/layout/multiline_method_call_indentation.rs +1 -1
  77. data/src/rules/layout/multiline_operation_indentation.rs +3 -3
  78. data/src/rules/layout/redundant_line_break.rs +16 -5
  79. data/src/rules/layout/rescue_ensure_alignment.rs +20 -12
  80. data/src/rules/layout/single_line_block_chain.rs +13 -4
  81. data/src/rules/layout/space_around_block_parameters.rs +16 -2
  82. data/src/rules/layout/space_around_equals_in_parameter_default.rs +1 -2
  83. data/src/rules/layout/space_around_keyword.rs +22 -4
  84. data/src/rules/layout/space_around_operators.rs +34 -2
  85. data/src/rules/layout/space_before_brackets.rs +13 -6
  86. data/src/rules/layout/space_before_comma.rs +3 -2
  87. data/src/rules/layout/space_inside_array_literal_brackets.rs +25 -10
  88. data/src/rules/layout/space_inside_array_percent_literal.rs +7 -1
  89. data/src/rules/layout/space_inside_parens.rs +170 -87
  90. data/src/rules/layout/space_inside_range_literal.rs +53 -19
  91. data/src/rules/layout/space_inside_reference_brackets.rs +65 -3
  92. data/src/rules/layout/space_inside_string_interpolation.rs +147 -48
  93. data/src/rules/layout/support.rs +172 -26
  94. data/src/rules/layout/tokens.rs +47 -7
  95. data/src/rules/layout/trailing_whitespace.rs +5 -2
  96. data/src/rules/lint/access_modifier.rs +50 -5
  97. data/src/rules/lint/ambiguity.rs +97 -7
  98. data/src/rules/lint/ambiguous_block_association.rs +29 -12
  99. data/src/rules/lint/ambiguous_operator.rs +8 -0
  100. data/src/rules/lint/ambiguous_operator_precedence.rs +18 -1
  101. data/src/rules/lint/ambiguous_range.rs +15 -1
  102. data/src/rules/lint/ambiguous_regexp_literal.rs +40 -1
  103. data/src/rules/lint/array_literal_in_regexp.rs +13 -5
  104. data/src/rules/lint/assignment_in_condition.rs +3 -2
  105. data/src/rules/lint/blocks.rs +2 -1
  106. data/src/rules/lint/boolean_symbol.rs +4 -4
  107. data/src/rules/lint/constant_definition_in_block.rs +1 -2
  108. data/src/rules/lint/constant_reassignment.rs +9 -13
  109. data/src/rules/lint/constant_resolution.rs +8 -0
  110. data/src/rules/lint/cop_directives.rs +28 -4
  111. data/src/rules/lint/debugger.rs +59 -9
  112. data/src/rules/lint/deprecated_open_ssl_constant.rs +1 -2
  113. data/src/rules/lint/disjunctive_assignment_in_constructor.rs +2 -2
  114. data/src/rules/lint/duplicate_branch.rs +16 -6
  115. data/src/rules/lint/duplicate_case_condition.rs +5 -5
  116. data/src/rules/lint/duplicate_hash_key.rs +8 -4
  117. data/src/rules/lint/duplicate_match_pattern.rs +7 -5
  118. data/src/rules/lint/duplicate_methods.rs +133 -54
  119. data/src/rules/lint/duplicate_require.rs +1 -2
  120. data/src/rules/lint/duplicate_set_element.rs +19 -5
  121. data/src/rules/lint/empty_conditional_body.rs +19 -3
  122. data/src/rules/lint/empty_expression.rs +16 -1
  123. data/src/rules/lint/empty_in_pattern.rs +3 -2
  124. data/src/rules/lint/empty_when.rs +4 -4
  125. data/src/rules/lint/ensure_return.rs +3 -2
  126. data/src/rules/lint/float_comparison.rs +8 -6
  127. data/src/rules/lint/flow.rs +8 -5
  128. data/src/rules/lint/format_parameter_mismatch.rs +4 -3
  129. data/src/rules/lint/hash_compare_by_identity.rs +5 -4
  130. data/src/rules/lint/heredoc_method_call_position.rs +3 -2
  131. data/src/rules/lint/implicit_string_concatenation.rs +33 -3
  132. data/src/rules/lint/ineffective_access_modifier.rs +5 -4
  133. data/src/rules/lint/inherit_exception.rs +4 -3
  134. data/src/rules/lint/literal_as_condition.rs +51 -31
  135. data/src/rules/lint/literal_assignment_in_condition.rs +4 -3
  136. data/src/rules/lint/literal_in_interpolation.rs +66 -5
  137. data/src/rules/lint/literals.rs +10 -8
  138. data/src/rules/lint/missing_cop_enable_directive.rs +95 -18
  139. data/src/rules/lint/mod.rs +1 -0
  140. data/src/rules/lint/nested_method_definition.rs +36 -11
  141. data/src/rules/lint/nil_receiver.rs +420 -0
  142. data/src/rules/lint/no_return_in_begin_end_blocks.rs +2 -2
  143. data/src/rules/lint/node_equality.rs +8 -5
  144. data/src/rules/lint/non_atomic_file_operation.rs +9 -3
  145. data/src/rules/lint/non_deterministic_require_order.rs +5 -3
  146. data/src/rules/lint/non_local_exit_from_iterator.rs +22 -9
  147. data/src/rules/lint/number_conversion.rs +1 -1
  148. data/src/rules/lint/out_of_range_regexp_ref.rs +6 -4
  149. data/src/rules/lint/parentheses_as_grouped_expression.rs +5 -0
  150. data/src/rules/lint/redundant_cop_enable_directive.rs +3 -1
  151. data/src/rules/lint/redundant_dir_glob_sort.rs +1 -2
  152. data/src/rules/lint/redundant_safe_navigation.rs +47 -22
  153. data/src/rules/lint/redundant_splat_expansion.rs +4 -2
  154. data/src/rules/lint/redundant_string_coercion.rs +3 -2
  155. data/src/rules/lint/redundant_type_conversion.rs +16 -4
  156. data/src/rules/lint/regexp.rs +1 -3
  157. data/src/rules/lint/regexp_source.rs +2 -2
  158. data/src/rules/lint/regexp_tree.rs +38 -5
  159. data/src/rules/lint/require_range_parentheses.rs +5 -5
  160. data/src/rules/lint/rescue_exception.rs +4 -3
  161. data/src/rules/lint/rescue_type.rs +3 -2
  162. data/src/rules/lint/return_in_void_context.rs +8 -3
  163. data/src/rules/lint/safe_navigation_chain.rs +47 -11
  164. data/src/rules/lint/self_assignment.rs +20 -2
  165. data/src/rules/lint/shadowed_argument.rs +2 -2
  166. data/src/rules/lint/shadowed_exception.rs +2 -2
  167. data/src/rules/lint/suppressed_exception.rs +21 -3
  168. data/src/rules/lint/suppressed_exception_in_number_conversion.rs +4 -3
  169. data/src/rules/lint/symbol_conversion.rs +56 -8
  170. data/src/rules/lint/syntax.rs +195 -5
  171. data/src/rules/lint/top_level_return_with_argument.rs +11 -1
  172. data/src/rules/lint/triple_quotes.rs +3 -2
  173. data/src/rules/lint/unescaped_bracket_in_regexp.rs +1 -1
  174. data/src/rules/lint/unexpected_block_arity.rs +23 -5
  175. data/src/rules/lint/unified_integer.rs +18 -12
  176. data/src/rules/lint/unmodified_reduce_accumulator.rs +36 -7
  177. data/src/rules/lint/unreachable_code.rs +4 -4
  178. data/src/rules/lint/unreachable_loop.rs +26 -10
  179. data/src/rules/lint/unreachable_pattern_branch.rs +7 -5
  180. data/src/rules/lint/unused_block_argument.rs +2 -2
  181. data/src/rules/lint/unused_method_argument.rs +1 -2
  182. data/src/rules/lint/useless_assignment.rs +3 -2
  183. data/src/rules/lint/useless_constant_scoping.rs +3 -2
  184. data/src/rules/lint/useless_else_without_rescue.rs +4 -3
  185. data/src/rules/lint/useless_rescue.rs +5 -4
  186. data/src/rules/lint/useless_ruby2_keywords.rs +2 -1
  187. data/src/rules/lint/useless_setter_call.rs +5 -4
  188. data/src/rules/lint/useless_times.rs +4 -3
  189. data/src/rules/lint/variable_force.rs +307 -79
  190. data/src/rules/lint/void.rs +32 -20
  191. data/src/rules/metrics/abc_size.rs +197 -7
  192. data/src/rules/metrics/block_length.rs +19 -10
  193. data/src/rules/metrics/block_nesting.rs +1 -2
  194. data/src/rules/metrics/class_length.rs +16 -4
  195. data/src/rules/metrics/collection_literal_length.rs +2 -1
  196. data/src/rules/metrics/complexity.rs +52 -14
  197. data/src/rules/metrics/fragments.rs +10 -14
  198. data/src/rules/metrics/locals.rs +35 -14
  199. data/src/rules/metrics/method_length.rs +14 -6
  200. data/src/rules/metrics/parameter_lists.rs +7 -6
  201. data/src/rules/metrics/perceived_complexity.rs +1 -3
  202. data/src/rules/metrics/support.rs +94 -4
  203. data/src/rules/mod.rs +525 -80
  204. data/src/rules/naming/ascii_identifiers.rs +3 -3
  205. data/src/rules/naming/binary_operator_parameter_name.rs +5 -4
  206. data/src/rules/naming/block_forwarding.rs +9 -8
  207. data/src/rules/naming/constant_name.rs +56 -18
  208. data/src/rules/naming/file_name.rs +174 -43
  209. data/src/rules/naming/method_name.rs +8 -1
  210. data/src/rules/naming/predicate_method.rs +42 -14
  211. data/src/rules/naming/predicate_prefix.rs +70 -4
  212. data/src/rules/naming/rescued_exceptions_variable_name.rs +14 -11
  213. data/src/rules/naming/support.rs +54 -295
  214. data/src/rules/naming/variable_name.rs +17 -4
  215. data/src/rules/naming/variable_number.rs +9 -10
  216. data/src/rules/node_ext.rs +1 -1
  217. data/src/rules/ruby_literal.rs +8 -3
  218. data/src/rules/security/compound_hash.rs +3 -2
  219. data/src/rules/security/open.rs +5 -4
  220. data/src/rules/send_node.rs +134 -0
  221. data/src/rules/single_line.rs +4 -4
  222. data/src/rules/style/access_modifier_declarations.rs +76 -22
  223. data/src/rules/style/accessor_grouping.rs +44 -6
  224. data/src/rules/style/alias.rs +4 -3
  225. data/src/rules/style/and_or.rs +2 -2
  226. data/src/rules/style/arguments_forwarding.rs +5 -5
  227. data/src/rules/style/array_coercion.rs +5 -5
  228. data/src/rules/style/array_first_last.rs +19 -4
  229. data/src/rules/style/array_intersect.rs +26 -8
  230. data/src/rules/style/array_intersect_with_single_element.rs +1 -1
  231. data/src/rules/style/ascii_comments.rs +2 -0
  232. data/src/rules/style/attr.rs +1 -1
  233. data/src/rules/style/auto_resource_cleanup.rs +1 -1
  234. data/src/rules/style/bitwise_predicate.rs +2 -2
  235. data/src/rules/style/block_args.rs +1 -1
  236. data/src/rules/style/block_comments.rs +11 -2
  237. data/src/rules/style/block_delimiters.rs +154 -10
  238. data/src/rules/style/case_equality.rs +7 -2
  239. data/src/rules/style/case_like_if.rs +26 -2
  240. data/src/rules/style/class_and_module_children.rs +162 -10
  241. data/src/rules/style/class_methods.rs +1 -1
  242. data/src/rules/style/class_methods_definitions.rs +9 -0
  243. data/src/rules/style/collection_compact.rs +4 -4
  244. data/src/rules/style/combinable_defined.rs +2 -2
  245. data/src/rules/style/combinable_loops.rs +9 -11
  246. data/src/rules/style/comments.rs +6 -3
  247. data/src/rules/style/comparable_clamp.rs +2 -2
  248. data/src/rules/style/concat_array_literals.rs +3 -3
  249. data/src/rules/style/conditional.rs +12 -1
  250. data/src/rules/style/conditional_assignment.rs +133 -4
  251. data/src/rules/style/constant_visibility.rs +3 -3
  252. data/src/rules/style/copyright.rs +64 -3
  253. data/src/rules/style/data_inheritance.rs +1 -1
  254. data/src/rules/style/def_with_parentheses.rs +1 -1
  255. data/src/rules/style/document_dynamic_eval_definition.rs +50 -12
  256. data/src/rules/style/documentation.rs +1 -1
  257. data/src/rules/style/each_for_simple_loop.rs +1 -1
  258. data/src/rules/style/each_with_object.rs +4 -4
  259. data/src/rules/style/empty_block_parameter.rs +1 -1
  260. data/src/rules/style/empty_case_condition.rs +6 -6
  261. data/src/rules/style/empty_class_definition.rs +1 -1
  262. data/src/rules/style/empty_else.rs +1 -1
  263. data/src/rules/style/empty_heredoc.rs +14 -1
  264. data/src/rules/style/empty_lambda_parameter.rs +1 -1
  265. data/src/rules/style/empty_literal.rs +34 -2
  266. data/src/rules/style/empty_method.rs +1 -1
  267. data/src/rules/style/empty_string_inside_interpolation.rs +1 -1
  268. data/src/rules/style/endless.rs +11 -3
  269. data/src/rules/style/env_home.rs +1 -1
  270. data/src/rules/style/eval_with_location.rs +6 -0
  271. data/src/rules/style/even_odd.rs +1 -1
  272. data/src/rules/style/exact_regexp_match.rs +1 -1
  273. data/src/rules/style/expand_path_arguments.rs +3 -3
  274. data/src/rules/style/explicit_block_argument.rs +3 -3
  275. data/src/rules/style/file_open.rs +5 -0
  276. data/src/rules/style/file_open_call.rs +3 -3
  277. data/src/rules/style/file_write.rs +1 -1
  278. data/src/rules/style/for.rs +1 -1
  279. data/src/rules/style/format_sequences.rs +6 -0
  280. data/src/rules/style/format_string.rs +75 -11
  281. data/src/rules/style/format_string_token.rs +61 -12
  282. data/src/rules/style/frozen_string_literal_comment.rs +62 -4
  283. data/src/rules/style/guard_clause.rs +8 -7
  284. data/src/rules/style/hash_as_last_array_item.rs +4 -4
  285. data/src/rules/style/hash_each_methods.rs +1 -1
  286. data/src/rules/style/hash_like_case.rs +8 -8
  287. data/src/rules/style/hash_lookup_method.rs +43 -3
  288. data/src/rules/style/hash_subset.rs +16 -10
  289. data/src/rules/style/hash_syntax.rs +609 -71
  290. data/src/rules/style/hash_transform.rs +20 -18
  291. data/src/rules/style/hash_transform_keys.rs +7 -0
  292. data/src/rules/style/hash_transform_values.rs +7 -0
  293. data/src/rules/style/identical_conditional_branches.rs +54 -5
  294. data/src/rules/style/if_inside_else.rs +19 -3
  295. data/src/rules/style/if_unless_modifier.rs +13 -14
  296. data/src/rules/style/if_with_boolean_literal_branches.rs +1 -1
  297. data/src/rules/style/if_with_semicolon.rs +7 -1
  298. data/src/rules/style/implicit_runtime_error.rs +7 -0
  299. data/src/rules/style/in_pattern_then.rs +2 -2
  300. data/src/rules/style/infinite_loop.rs +6 -0
  301. data/src/rules/style/inline_comment.rs +4 -1
  302. data/src/rules/style/inverse_methods.rs +12 -2
  303. data/src/rules/style/invertible_unless_condition.rs +12 -12
  304. data/src/rules/style/it_block_parameter.rs +5 -2
  305. data/src/rules/style/keyword_arguments_merging.rs +8 -2
  306. data/src/rules/style/keyword_parameters_order.rs +1 -1
  307. data/src/rules/style/line_end_concatenation.rs +1 -1
  308. data/src/rules/style/magic_comment_format.rs +14 -6
  309. data/src/rules/style/map_chain.rs +1 -1
  310. data/src/rules/style/map_compact_with_conditional_block.rs +3 -3
  311. data/src/rules/style/map_into_array.rs +7 -7
  312. data/src/rules/style/map_join.rs +3 -3
  313. data/src/rules/style/method_call_with_args_parentheses.rs +7 -7
  314. data/src/rules/style/method_call_without_args_parentheses.rs +2 -2
  315. data/src/rules/style/method_def_parentheses.rs +2 -3
  316. data/src/rules/style/min_max.rs +3 -3
  317. data/src/rules/style/min_max_comparison.rs +2 -2
  318. data/src/rules/style/missing_else.rs +1 -1
  319. data/src/rules/style/missing_respond_to_missing.rs +1 -1
  320. data/src/rules/style/mixin_grouping.rs +6 -7
  321. data/src/rules/style/multiline_block_chain.rs +6 -8
  322. data/src/rules/style/multiline_if_then.rs +1 -1
  323. data/src/rules/style/multiline_in_pattern_then.rs +16 -3
  324. data/src/rules/style/multiline_method_signature.rs +1 -1
  325. data/src/rules/style/multiline_when_then.rs +2 -2
  326. data/src/rules/style/mutable_constant.rs +31 -35
  327. data/src/rules/style/negated_if.rs +2 -2
  328. data/src/rules/style/negated_if_else_condition.rs +5 -3
  329. data/src/rules/style/negated_while.rs +1 -1
  330. data/src/rules/style/negative_array_index.rs +25 -3
  331. data/src/rules/style/nested_modifier.rs +1 -1
  332. data/src/rules/style/nested_parenthesized_calls.rs +2 -2
  333. data/src/rules/style/nested_ternary_operator.rs +2 -2
  334. data/src/rules/style/next.rs +12 -12
  335. data/src/rules/style/nil_lambda.rs +2 -2
  336. data/src/rules/style/nodes.rs +21 -0
  337. data/src/rules/style/non_nil_check.rs +82 -2
  338. data/src/rules/style/numbered_parameters.rs +4 -1
  339. data/src/rules/style/numbered_parameters_limit.rs +2 -2
  340. data/src/rules/style/numeric_literals.rs +17 -1
  341. data/src/rules/style/numeric_predicate.rs +25 -6
  342. data/src/rules/style/one_line_conditional.rs +24 -7
  343. data/src/rules/style/operator_method_call.rs +5 -0
  344. data/src/rules/style/optional_arguments.rs +1 -1
  345. data/src/rules/style/or_assignment.rs +1 -1
  346. data/src/rules/style/parallel_assignment.rs +20 -9
  347. data/src/rules/style/parens.rs +1 -1
  348. data/src/rules/style/parentheses_around_condition.rs +3 -3
  349. data/src/rules/style/partition_instead_of_double_select.rs +3 -3
  350. data/src/rules/style/percent.rs +18 -9
  351. data/src/rules/style/percent_q_literals.rs +40 -7
  352. data/src/rules/style/preferred_hash_methods.rs +1 -1
  353. data/src/rules/style/quoted_symbols.rs +6 -0
  354. data/src/rules/style/raise_args.rs +26 -20
  355. data/src/rules/style/random_with_offset.rs +1 -1
  356. data/src/rules/style/reduce_to_hash.rs +6 -6
  357. data/src/rules/style/redundant_argument.rs +1 -1
  358. data/src/rules/style/redundant_assignment.rs +5 -5
  359. data/src/rules/style/redundant_begin.rs +5 -3
  360. data/src/rules/style/redundant_capital_w.rs +3 -2
  361. data/src/rules/style/redundant_condition.rs +17 -8
  362. data/src/rules/style/redundant_current_directory_in_path.rs +2 -2
  363. data/src/rules/style/redundant_double_splat_hash_braces.rs +15 -5
  364. data/src/rules/style/redundant_exception.rs +3 -1
  365. data/src/rules/style/redundant_file_extension_in_require.rs +2 -2
  366. data/src/rules/style/redundant_format.rs +98 -20
  367. data/src/rules/style/redundant_freeze.rs +1 -1
  368. data/src/rules/style/redundant_heredoc_delimiter_quotes.rs +1 -1
  369. data/src/rules/style/redundant_initialize.rs +6 -3
  370. data/src/rules/style/redundant_interpolation.rs +8 -8
  371. data/src/rules/style/redundant_interpolation_unfreeze.rs +10 -3
  372. data/src/rules/style/redundant_line_continuation.rs +10 -7
  373. data/src/rules/style/redundant_parentheses.rs +96 -19
  374. data/src/rules/style/redundant_regexp_argument.rs +1 -1
  375. data/src/rules/style/redundant_regexp_character_class.rs +4 -3
  376. data/src/rules/style/redundant_regexp_escape.rs +3 -2
  377. data/src/rules/style/redundant_return.rs +1 -2
  378. data/src/rules/style/redundant_self.rs +28 -16
  379. data/src/rules/style/redundant_sort.rs +1 -1
  380. data/src/rules/style/redundant_sort_by.rs +1 -1
  381. data/src/rules/style/redundant_string_escape.rs +6 -5
  382. data/src/rules/style/regexp_literal.rs +3 -2
  383. data/src/rules/style/require_order.rs +2 -2
  384. data/src/rules/style/rescue_modifier.rs +9 -4
  385. data/src/rules/style/rescue_standard_error.rs +1 -1
  386. data/src/rules/style/return_nil.rs +2 -2
  387. data/src/rules/style/return_nil_in_predicate_method_definition.rs +2 -2
  388. data/src/rules/style/safe_navigation.rs +2 -2
  389. data/src/rules/style/safe_navigation_chain_length.rs +8 -1
  390. data/src/rules/style/sample.rs +3 -3
  391. data/src/rules/style/select_by.rs +6 -2
  392. data/src/rules/style/select_by_range.rs +1 -1
  393. data/src/rules/style/semicolon.rs +77 -4
  394. data/src/rules/style/signal_exception.rs +60 -3
  395. data/src/rules/style/single_argument_dig.rs +10 -2
  396. data/src/rules/style/single_line_block_params.rs +14 -4
  397. data/src/rules/style/single_line_do_end_block.rs +13 -12
  398. data/src/rules/style/single_line_methods.rs +2 -2
  399. data/src/rules/style/slicing_with_range.rs +2 -2
  400. data/src/rules/style/sole_nested_conditional.rs +4 -4
  401. data/src/rules/style/special_global_vars.rs +2 -2
  402. data/src/rules/style/stabby_lambda_parentheses.rs +1 -1
  403. data/src/rules/style/statement_modifier.rs +3 -2
  404. data/src/rules/style/static_class.rs +7 -1
  405. data/src/rules/style/string_concatenation.rs +12 -7
  406. data/src/rules/style/string_hash_keys.rs +3 -2
  407. data/src/rules/style/string_literals.rs +3 -3
  408. data/src/rules/style/string_literals_in_interpolation.rs +4 -1
  409. data/src/rules/style/struct_inheritance.rs +3 -3
  410. data/src/rules/style/super_arguments.rs +15 -7
  411. data/src/rules/style/super_with_args_parentheses.rs +49 -12
  412. data/src/rules/style/swap_values.rs +1 -1
  413. data/src/rules/style/symbol_array.rs +9 -2
  414. data/src/rules/style/symbol_proc.rs +3 -3
  415. data/src/rules/style/tally_method.rs +4 -4
  416. data/src/rules/style/ternary_parentheses.rs +30 -15
  417. data/src/rules/style/top_level_method_definition.rs +10 -5
  418. data/src/rules/style/trailing_body.rs +1 -1
  419. data/src/rules/style/trailing_comma.rs +206 -10
  420. data/src/rules/style/trailing_comma_in_arguments.rs +9 -10
  421. data/src/rules/style/trailing_comma_in_array_literal.rs +2 -1
  422. data/src/rules/style/trailing_comma_in_hash_literal.rs +2 -1
  423. data/src/rules/style/trailing_method_end_statement.rs +6 -1
  424. data/src/rules/style/trailing_underscore_variable.rs +4 -5
  425. data/src/rules/style/trivial_accessors.rs +2 -2
  426. data/src/rules/style/unless_logical_operators.rs +1 -1
  427. data/src/rules/style/unpack_first.rs +11 -2
  428. data/src/rules/style/when_then.rs +4 -4
  429. data/src/rules/style/while_until_do.rs +1 -1
  430. data/src/rules/style/while_until_modifier.rs +3 -3
  431. data/src/rules/style/word_array.rs +9 -7
  432. data/src/rules/style/yoda_condition.rs +2 -2
  433. data/src/rules/style/zero_length_predicate.rs +5 -0
  434. data/src/rules/support.rs +128 -11
  435. data/src/source.rs +18 -15
  436. metadata +5 -1
@@ -97,7 +97,7 @@ impl Cop<'_, '_> {
97
97
  };
98
98
  let lhs_elements = assignments(left);
99
99
  let rhs_elements = match RHS_LISTS.contains(&rhs.kind_str()) {
100
- true => super::nodes::children(rhs),
100
+ true => super::nodes::children_in(rhs, self.context),
101
101
  false => Vec::new(),
102
102
  };
103
103
 
@@ -122,7 +122,7 @@ impl Cop<'_, '_> {
122
122
  };
123
123
  // `contains_heredoc?`: splitting the assignment would drop the following lines into the
124
124
  // heredoc's body.
125
- if descendants(rhs)
125
+ if descendants(rhs, self.context)
126
126
  .into_iter()
127
127
  .any(|inner| inner.kind_str() == "heredoc_beginning")
128
128
  {
@@ -177,7 +177,7 @@ impl Cop<'_, '_> {
177
177
  "identifier" | "instance_variable" | "global_variable" | "class_variable"
178
178
  | "constant" => {
179
179
  let name = self.source(lhs);
180
- descendants(rhs)
180
+ descendants(rhs, self.context)
181
181
  .into_iter()
182
182
  .any(|inner| self.reads_name(inner, name))
183
183
  }
@@ -206,7 +206,19 @@ impl Cop<'_, '_> {
206
206
  };
207
207
  let receiver = self.source(receiver);
208
208
  let method = self.source(method);
209
- descendants(rhs).into_iter().any(|inner| {
209
+ // `add_self_to_getters`: a bare name on the right is read as `self.name` before the
210
+ // dependencies are worked out, so `self.a, self.b = b, a` is the swap it looks like.
211
+ if receiver == "self"
212
+ && descendants(rhs, self.context).into_iter().any(|inner| {
213
+ inner.kind_str() == "identifier"
214
+ && self.source(inner) == method
215
+ // `(send nil? $_)`: a name the file never assigned is a call, not a variable.
216
+ && !self.context.variable_analysis().is_variable_reference(inner)
217
+ })
218
+ {
219
+ return true;
220
+ }
221
+ descendants(rhs, self.context).into_iter().any(|inner| {
210
222
  matches!(inner.kind_str(), "call" | "method_call")
211
223
  && inner
212
224
  .field("receiver")
@@ -224,7 +236,7 @@ impl Cop<'_, '_> {
224
236
  };
225
237
  let object = self.source(object);
226
238
  let arguments = index_arguments(self, lhs);
227
- descendants(rhs).into_iter().any(|inner| {
239
+ descendants(rhs, self.context).into_iter().any(|inner| {
228
240
  inner.kind_str() == "element_reference"
229
241
  && inner
230
242
  .field("object")
@@ -329,7 +341,7 @@ impl Cop<'_, '_> {
329
341
  }
330
342
 
331
343
  fn index_arguments(cop: &Cop<'_, '_>, node: Node<'_>) -> Vec<String> {
332
- let mut arguments = super::nodes::children(node);
344
+ let mut arguments = super::nodes::children_in(node, cop.context);
333
345
  if node.field("object").is_some() && !arguments.is_empty() {
334
346
  arguments.remove(0);
335
347
  }
@@ -342,8 +354,7 @@ fn index_arguments(cop: &Cop<'_, '_>, node: Node<'_>) -> Vec<String> {
342
354
  /// `modifier_range`: the keyword and the condition behind it, which becomes the head of the block.
343
355
  fn modifier_range(modifier: Node<'_>) -> Range<usize> {
344
356
  let mut cursor = modifier.walk();
345
- let keyword = modifier
346
- .children(&mut cursor)
357
+ let keyword = modifier.children(&mut cursor)
347
358
  .find(|child| !child.is_named())
348
359
  .map_or(modifier.start_byte(), |child| child.start_byte());
349
360
  keyword..modifier.end_byte()
@@ -410,7 +421,7 @@ fn sole_statement_of_a_method(context: &RuleContext<'_>, node: Node<'_>) -> bool
410
421
  }
411
422
  rescue.parent_of(context).is_some_and(|body| {
412
423
  body.kind_str() == "body_statement"
413
- && super::nodes::children(body).len() == 1
424
+ && super::nodes::children_in(body, context).len() == 1
414
425
  && body
415
426
  .parent_of(context)
416
427
  .is_some_and(|owner| matches!(owner.kind_str(), "method" | "singleton_method"))
@@ -191,7 +191,7 @@ fn is_ternary_condition_before_question_mark(context: &RuleContext<'_>, node: No
191
191
 
192
192
  /// `comment_above_close_paren_swallows_chain?`.
193
193
  fn comment_above_close_paren(context: &RuleContext<'_>, node: Node<'_>) -> bool {
194
- let Some(last) = super::nodes::children(node).last().copied() else {
194
+ let Some(last) = super::nodes::children_in(node, context).last().copied() else {
195
195
  return false;
196
196
  };
197
197
  let close = node.end_byte() - 1;
@@ -40,7 +40,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
40
40
  if condition.kind_str() != "parenthesized_statements" {
41
41
  continue;
42
42
  }
43
- let children = super::nodes::children(condition);
43
+ let children = super::nodes::children_in(condition, context);
44
44
  let Some(first) = children.first().copied() else {
45
45
  continue;
46
46
  };
@@ -139,7 +139,7 @@ fn parens_required(context: &RuleContext<'_>, node: Node<'_>) -> bool {
139
139
  /// `safe_assignment?`: `(begin {equals_asgn? #setter_method?})`, the parenthesized assignment that
140
140
  /// says the assignment was meant.
141
141
  fn is_safe_assignment(context: &RuleContext<'_>, condition: Node<'_>) -> bool {
142
- let children = super::nodes::children(condition);
142
+ let children = super::nodes::children_in(condition, context);
143
143
  let [only] = children.as_slice() else {
144
144
  return false;
145
145
  };
@@ -187,7 +187,7 @@ fn unwrap(context: &RuleContext<'_>, condition: Node<'_>) -> Vec<Edit> {
187
187
  }
188
188
 
189
189
  fn comment_above_close_paren(context: &RuleContext<'_>, condition: Node<'_>) -> bool {
190
- let Some(last) = super::nodes::children(condition).last().copied() else {
190
+ let Some(last) = super::nodes::children_in(condition, context).last().copied() else {
191
191
  return false;
192
192
  };
193
193
  let close = condition.end_byte() - 1;
@@ -39,7 +39,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
39
39
  if !holds_a_begin(parent) {
40
40
  continue;
41
41
  }
42
- let statements: Vec<Node<'_>> = super::nodes::children(parent);
42
+ let statements: Vec<Node<'_>> = super::nodes::children_in(parent, context);
43
43
  for window in statements.windows(2) {
44
44
  let [sibling_container, container] = window else {
45
45
  continue;
@@ -278,7 +278,7 @@ fn block_matches_block_pass(block: Node<'_>, send: Node<'_>, context: &RuleConte
278
278
  let Some(argument) = last_argument(send) else {
279
279
  return false;
280
280
  };
281
- let Some(symbol) = super::nodes::children(argument).into_iter().next() else {
281
+ let Some(symbol) = super::nodes::children_in(argument, context).into_iter().next() else {
282
282
  return false;
283
283
  };
284
284
  send_node::symbol_name(symbol, context) == Some(method)
@@ -287,7 +287,7 @@ fn block_matches_block_pass(block: Node<'_>, send: Node<'_>, context: &RuleConte
287
287
  /// `symbol_proc_method?`: `(block _ (args (arg _name)) (send (lvar _name) $_method_name))`.
288
288
  fn symbol_proc_method<'a>(node: Node<'_>, context: &'a RuleContext<'_>) -> Option<&'a str> {
289
289
  let block = block_of(node)?;
290
- let parameters = super::nodes::children(block.field("parameters")?);
290
+ let parameters = super::nodes::children_in(block.field("parameters")?, context);
291
291
  let [only] = parameters.as_slice() else {
292
292
  return None;
293
293
  };
@@ -10,6 +10,7 @@ use tree_sitter::Node;
10
10
 
11
11
  use crate::rules::RuleContext;
12
12
  use crate::rules::node_ext::NodeExt;
13
+ use crate::rules::send_node::named_children_iter;
13
14
 
14
15
  /// Node kinds that can be written as a `%`-literal. A node of one of these kinds is only a percent
15
16
  /// literal when its opening delimiter actually starts with `%`; `"a"` and `%q(a)` share a kind.
@@ -49,7 +50,7 @@ pub(super) struct PercentLiteral {
49
50
 
50
51
  impl PercentLiteral {
51
52
  pub(super) fn new(node: Node<'_>, context: &RuleContext<'_>) -> Option<Self> {
52
- if node.child_count() < 2 || is_modulo_operand(node) {
53
+ if node.child_count() < 2 || is_modulo_operand(node, context) {
53
54
  return None;
54
55
  }
55
56
  let begin = node.child(0)?;
@@ -67,13 +68,20 @@ impl PercentLiteral {
67
68
  if !types_for(node.kind_str()).contains(&percent_type.as_str()) {
68
69
  return None;
69
70
  }
71
+ // **A literal closed by a newline leaves the grammar with an empty closing node.** `%\n\n`
72
+ // is a string whose delimiter is the line break: the break that ends it is counted into the
73
+ // content and the closing node sits after it with no text of its own. The delimiter is
74
+ // still there in the source, one character back from where the literal ends.
70
75
  let close_text = context.source.node_text(close);
71
- let close_len = close_text.chars().next()?.len_utf8();
76
+ let close = match close_text.chars().next() {
77
+ Some(character) => close.start_byte()..close.start_byte() + character.len_utf8(),
78
+ None => node.end_byte().saturating_sub(opening.len_utf8())..node.end_byte(),
79
+ };
72
80
  Some(Self {
73
81
  percent_type,
74
82
  opening,
75
83
  begin: begin.byte_range(),
76
- close: close.start_byte()..close.start_byte() + close_len,
84
+ close,
77
85
  })
78
86
  }
79
87
  }
@@ -91,8 +99,8 @@ pub(super) fn literal_segments<'a>(
91
99
  let text = context.source.text();
92
100
  let mut segments = Vec::new();
93
101
  if matches!(node.kind_str(), "string_array" | "symbol_array") {
94
- let mut cursor = node.walk();
95
- for element in node.named_children(&mut cursor) {
102
+ let _cursor = node.walk();
103
+ for element in named_children_iter(node, context) {
96
104
  if !holds_interpolation(element) {
97
105
  segments.push(&text[element.byte_range()]);
98
106
  }
@@ -102,8 +110,8 @@ pub(super) fn literal_segments<'a>(
102
110
 
103
111
  // Everything else is one run of text, broken only where an interpolation replaces it with code.
104
112
  let mut start = literal.begin.end;
105
- let mut cursor = node.walk();
106
- for child in node.named_children(&mut cursor) {
113
+ let _cursor = node.walk();
114
+ for child in named_children_iter(node, context) {
107
115
  if child.kind_str() != "interpolation" {
108
116
  continue;
109
117
  }
@@ -120,8 +128,9 @@ pub(super) fn literal_segments<'a>(
120
128
  /// is modulo and `"%s" %[x]` is a `send` whose argument is an array. tree-sitter chains the two into
121
129
  /// one literal anyway, which would otherwise hand every such call a percent literal that upstream
122
130
  /// never saw.
123
- pub(super) fn is_modulo_operand(node: Node<'_>) -> bool {
124
- node.parent()
131
+ pub(super) fn is_modulo_operand(node: Node<'_>, context: &RuleContext<'_>) -> bool {
132
+ context
133
+ .parent(node)
125
134
  .is_some_and(|parent| parent.kind_str() == "chained_string")
126
135
  && node.prev_named_sibling().is_some()
127
136
  }
@@ -3,6 +3,7 @@
3
3
  use crate::diagnostic::{Edit, Offense};
4
4
  use crate::rules::RuleContext;
5
5
  use crate::rules::node_ext::NodeExt;
6
+ use crate::rules::send_node::named_children_of;
6
7
 
7
8
  const LOWER_CASE_Q_MSG: &str = "Do not use `%Q` unless interpolation is needed. Use `%q`.";
8
9
  const UPPER_CASE_Q_MSG: &str = "Use `%Q` instead of `%q`.";
@@ -21,7 +22,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
21
22
  // `process(node, '%Q', '%q')` then `correct_literal_style?`.
22
23
  if !matches!(literal.percent_type.as_str(), "%q" | "%Q")
23
24
  || literal.percent_type == wanted
24
- || !is_str(node)
25
+ || !is_str(node, context, &literal)
25
26
  {
26
27
  continue;
27
28
  }
@@ -31,7 +32,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
31
32
  let delimiters = [literal.opening, closing_of(context, &literal)];
32
33
  let single = super::literal::decode(body, super::literal::Quoting::Single, &delimiters);
33
34
  let double = super::literal::decode(body, super::literal::Quoting::Double, &delimiters);
34
- if single.value != double.value {
35
+ if single.value != double.value || (!lower_case && has_active_interpolation(body)) {
35
36
  continue;
36
37
  }
37
38
  let text = context.source.node_text(node);
@@ -60,6 +61,26 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
60
61
  }
61
62
  }
62
63
 
64
+ /// Whether changing a single-quoted percent literal to `%Q` would turn text into interpolation.
65
+ /// An even run of backslashes leaves `#` active; an odd run escapes it (and the value comparison
66
+ /// above separately catches the backslash whose meaning changes in that case).
67
+ fn has_active_interpolation(body: &str) -> bool {
68
+ let bytes = body.as_bytes();
69
+ for index in 0..bytes.len().saturating_sub(1) {
70
+ if bytes[index] != b'#' || !matches!(bytes[index + 1], b'{' | b'$' | b'@') {
71
+ continue;
72
+ }
73
+ let mut slash = index;
74
+ while slash > 0 && bytes[slash - 1] == b'\\' {
75
+ slash -= 1;
76
+ }
77
+ if (index - slash) % 2 == 0 {
78
+ return true;
79
+ }
80
+ }
81
+ false
82
+ }
83
+
63
84
  fn closing_of(context: &RuleContext<'_>, literal: &super::percent::PercentLiteral) -> char {
64
85
  context
65
86
  .source
@@ -71,10 +92,22 @@ fn closing_of(context: &RuleContext<'_>, literal: &super::percent::PercentLitera
71
92
 
72
93
  /// `node.str_type?`: a literal is a `dstr` upstream both when it interpolates and when its text
73
94
  /// does not fit on one line.
74
- fn is_str(node: tree_sitter::Node<'_>) -> bool {
75
- let mut cursor = node.walk();
76
- !node
77
- .named_children(&mut cursor)
95
+ fn is_str(
96
+ node: tree_sitter::Node<'_>,
97
+ context: &RuleContext<'_>,
98
+ literal: &super::percent::PercentLiteral,
99
+ ) -> bool {
100
+ let _cursor = node.walk();
101
+ if named_children_of(node, context)
102
+ .into_iter()
78
103
  .any(|child| child.kind_str() == "interpolation")
79
- && node.start_position().row == node.end_position().row
104
+ {
105
+ return false;
106
+ }
107
+ // **The parser cuts the contents after every line break**, so a literal written over lines is
108
+ // a `dstr` of one `str` per line and `on_str` never sees it. A break that closes the contents
109
+ // leaves a single `str`, which is why `%Q{\n}` still reaches this cop.
110
+ let body = context.source.slice(literal.begin.end..literal.close.start);
111
+ !body.strip_suffix('\n').unwrap_or(body).contains('\n')
80
112
  }
113
+
@@ -14,7 +14,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
14
14
  // `node.arguments.one?`: the predicate takes exactly the key it asks about.
15
15
  let one_argument = node
16
16
  .field("arguments")
17
- .is_some_and(|arguments| super::nodes::children(arguments).len() == 1);
17
+ .is_some_and(|arguments| super::nodes::children_in(arguments, context).len() == 1);
18
18
  if !one_argument {
19
19
  continue;
20
20
  }
@@ -63,6 +63,12 @@ fn symbol_kind(node: Node<'_>, context: &RuleContext<'_>) -> Option<bool> {
63
63
  return None;
64
64
  }
65
65
  if node.kind_str() == "delimited_symbol" {
66
+ // A literal running over more than one line is a `dsym` too: the parser gives each line a
67
+ // `str` of its own and hangs them under one, so `on_sym` never sees it. A break that closes
68
+ // the contents leaves one `str`, and `:"two\n"` stays a `sym`.
69
+ if crate::rules::support::quoted_spans_lines(context.source.node_text(node)) {
70
+ return None;
71
+ }
66
72
  // An empty `:""` is a `dsym` upstream as well -- but an empty hash key is a plain `sym`.
67
73
  return (node.named_child_count() > 0).then_some(false);
68
74
  }
@@ -37,15 +37,19 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
37
37
  let written = arguments(node);
38
38
  let range = send_range(node, context);
39
39
  let offense = if compact {
40
- let Some(edit) = exploded_to_compact(context, node, &written, &range) else {
40
+ if !reports_compact(&written) {
41
41
  continue;
42
- };
43
- context
44
- .offense(
45
- format!("Provide an exception object as an argument to `{method}`."),
46
- range.clone(),
47
- )
48
- .corrected_by(edit)
42
+ }
43
+ let offense = context.offense(
44
+ format!("Provide an exception object as an argument to `{method}`."),
45
+ range.clone(),
46
+ );
47
+ // `correction_exploded_to_compact` hands back the original source when there is more
48
+ // than one message argument, so the offense stands without a rewrite.
49
+ match exploded_to_compact(context, node, &written, &range) {
50
+ Some(edit) => offense.corrected_by(edit),
51
+ None => offense,
52
+ }
49
53
  } else {
50
54
  let Some(edit) = compact_to_exploded(context, node, &written, &range, &allowed) else {
51
55
  continue;
@@ -115,27 +119,29 @@ fn compact_to_exploded(
115
119
  }
116
120
 
117
121
  /// `check_compact` plus `correction_exploded_to_compact`.
122
+ /// `check_compact`: more than one argument, unless the exception itself was handed a hash.
123
+ fn reports_compact(written: &[crate::rules::send_node::Argument<'_>]) -> bool {
124
+ if written.len() <= 1 {
125
+ return false;
126
+ }
127
+ let exception = written[0].first();
128
+ // A call whose own first argument is a hash is `raise Klass, key: value`, which stays.
129
+ !(exception.kind_str() == "call"
130
+ && arguments(exception)
131
+ .first()
132
+ .is_some_and(|argument| matches!(argument.first().kind_str(), "pair" | "hash")))
133
+ }
134
+
118
135
  fn exploded_to_compact(
119
136
  context: &RuleContext<'_>,
120
137
  node: Node<'_>,
121
138
  written: &[crate::rules::send_node::Argument<'_>],
122
139
  range: &std::ops::Range<usize>,
123
140
  ) -> Option<Edit> {
124
- if written.len() <= 1 {
141
+ if !reports_compact(written) || written.len() > 2 {
125
142
  return None;
126
143
  }
127
144
  let exception = written[0].first();
128
- // A call whose own first argument is a hash is `raise Klass, key: value`, which stays.
129
- if exception.kind_str() == "call"
130
- && arguments(exception)
131
- .first()
132
- .is_some_and(|argument| matches!(argument.first().kind_str(), "pair" | "hash"))
133
- {
134
- return None;
135
- }
136
- if written.len() > 2 {
137
- return None;
138
- }
139
145
  let argument = context.source.slice(written[1].range());
140
146
  let exception_class = exception.field("receiver").map_or_else(
141
147
  || context.source.node_text(exception),
@@ -117,7 +117,7 @@ fn random_call(context: &RuleContext<'_>, node: Node<'_>) -> Option<Random> {
117
117
  format!("{source}.rand")
118
118
  }
119
119
  };
120
- let arguments = super::nodes::children(node.field("arguments")?);
120
+ let arguments = super::nodes::children_in(node.field("arguments")?, context);
121
121
  let [argument] = arguments.as_slice() else {
122
122
  return None;
123
123
  };
@@ -95,12 +95,12 @@ struct Built<'tree> {
95
95
  fn hash_builder<'tree>(
96
96
  node: Node<'tree>,
97
97
  block: Node<'tree>,
98
- context: &RuleContext<'_>,
98
+ context: &'tree RuleContext<'_>,
99
99
  locals: &LocalVariables<'_, '_>,
100
100
  ) -> Option<Built<'tree>> {
101
101
  let folding = context.source.node_text(node.field("method")?) != "each_with_object";
102
102
  // `(hash)`: the seed has to be an empty literal.
103
- let arguments = super::nodes::children(node.field("arguments")?);
103
+ let arguments = super::nodes::children_in(node.field("arguments")?, context);
104
104
  match arguments.as_slice() {
105
105
  [only] if only.kind_str() == "hash" && only.named_child_count() == 0 => {}
106
106
  _ => return None,
@@ -153,7 +153,7 @@ fn hash_builder<'tree>(
153
153
  fn subscript_assignment<'tree>(
154
154
  node: Node<'tree>,
155
155
  accumulator: &str,
156
- context: &RuleContext<'_>,
156
+ context: &'tree RuleContext<'_>,
157
157
  ) -> Option<(Node<'tree>, Node<'tree>)> {
158
158
  if node.kind_str() != "assignment" {
159
159
  return None;
@@ -166,7 +166,7 @@ fn subscript_assignment<'tree>(
166
166
  if context.source.node_text(object) != accumulator {
167
167
  return None;
168
168
  }
169
- let indices = super::nodes::children(target);
169
+ let indices = super::nodes::children_in(target, context);
170
170
  let [_, key] = indices.as_slice() else {
171
171
  return None;
172
172
  };
@@ -178,7 +178,7 @@ fn reads(node: Node<'_>, name: &str, context: &RuleContext<'_>) -> bool {
178
178
  if node.kind_str() == "identifier" && context.source.node_text(node) == name {
179
179
  return true;
180
180
  }
181
- super::nodes::children(node)
181
+ super::nodes::children_in(node, context)
182
182
  .into_iter()
183
183
  .any(|child| reads(child, name, context))
184
184
  }
@@ -200,7 +200,7 @@ fn nested_match(
200
200
  {
201
201
  return true;
202
202
  }
203
- super::nodes::children(node)
203
+ super::nodes::children_in(node, context)
204
204
  .into_iter()
205
205
  .any(|child| nested_match(child, context, locals))
206
206
  }
@@ -53,7 +53,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
53
53
  let Some(arguments) = node.field("arguments") else {
54
54
  continue;
55
55
  };
56
- let written = super::nodes::children(arguments);
56
+ let written = super::nodes::children_in(arguments, context);
57
57
  let [only] = written.as_slice() else {
58
58
  continue;
59
59
  };
@@ -21,9 +21,9 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
21
21
  /// `EnsureNode#branch` -- the *ensure* clause -- so the code it guards is never looked at. A body
22
22
  /// split by a `rescue` alone is a `rescue` node, every branch of which is followed.
23
23
  fn check_container(context: &RuleContext<'_>, container: Node<'_>, offenses: &mut Vec<Offense>) {
24
- let children = super::nodes::children(container);
24
+ let children = super::nodes::children_in(container, context);
25
25
  if let Some(ensure) = children.iter().find(|child| child.kind_str() == "ensure") {
26
- let statements = super::nodes::children(*ensure);
26
+ let statements = super::nodes::children_in(*ensure, context);
27
27
  check_statements(context, &statements, offenses);
28
28
  return;
29
29
  }
@@ -44,7 +44,7 @@ fn check_container(context: &RuleContext<'_>, container: Node<'_>, offenses: &mu
44
44
  }
45
45
  }
46
46
  "else" => {
47
- let statements = super::nodes::children(*clause);
47
+ let statements = super::nodes::children_in(*clause, context);
48
48
  check_statements(context, &statements, offenses);
49
49
  }
50
50
  _ => {}
@@ -131,7 +131,7 @@ fn comments_between(context: &RuleContext<'_>, assignment: Node<'_>, reference:
131
131
  fn check_branch(context: &RuleContext<'_>, node: Node<'_>, offenses: &mut Vec<Offense>) {
132
132
  match node.kind_str() {
133
133
  "case" | "case_match" => {
134
- for child in super::nodes::children(node) {
134
+ for child in super::nodes::children_in(node, context) {
135
135
  match child.kind_str() {
136
136
  "when" | "in_clause" => {
137
137
  if let Some(body) = child.field("body") {
@@ -139,7 +139,7 @@ fn check_branch(context: &RuleContext<'_>, node: Node<'_>, offenses: &mut Vec<Of
139
139
  }
140
140
  }
141
141
  "else" => {
142
- let statements = super::nodes::children(child);
142
+ let statements = super::nodes::children_in(child, context);
143
143
  check_statements(context, &statements, offenses);
144
144
  }
145
145
  _ => {}
@@ -19,7 +19,9 @@ const IMPLICIT_BEGIN_VERSION: RubyVersion = RubyVersion::new(2, 5);
19
19
 
20
20
  /// The clauses that make the parser put a `rescue` or an `ensure` node between the `kwbegin` and
21
21
  /// its statements, which is what leaves it a single child.
22
- const CLAUSES: &[&str] = &["rescue", "ensure", "else"];
22
+ /// `contain_rescue_or_ensure?` asks for a `rescue` or an `ensure` and **not** for an `else`: a
23
+ /// `begin ... else ... end` written without a `rescue` is the redundant `begin` this cop reports.
24
+ const CLAUSES: &[&str] = &["rescue", "ensure"];
23
25
 
24
26
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
25
27
  // `add_offense` refuses a range it has already reported, and every handler here reports the
@@ -193,7 +195,7 @@ fn replace_begin_with_statement(
193
195
  edits: &mut Vec<Edit>,
194
196
  anchor: &mut Option<Range<usize>>,
195
197
  ) {
196
- let Some(first) = super::nodes::children(node).first().copied() else {
198
+ let Some(first) = super::nodes::children_in(node, context).first().copied() else {
197
199
  return;
198
200
  };
199
201
  let source = context.source.node_text(first);
@@ -236,7 +238,7 @@ fn correct_modifier_form(
236
238
  anchor: &mut Option<Range<usize>>,
237
239
  ) {
238
240
  let (Some(first), Some(keyword), Some(condition)) = (
239
- super::nodes::children(node).first().copied(),
241
+ super::nodes::children_in(node, context).first().copied(),
240
242
  modifier_keyword(parent),
241
243
  parent.field("condition"),
242
244
  ) else {
@@ -3,6 +3,7 @@
3
3
  use crate::diagnostic::{Edit, Offense};
4
4
  use crate::rules::RuleContext;
5
5
  use crate::rules::node_ext::NodeExt;
6
+ use crate::rules::send_node::named_children_iter;
6
7
 
7
8
  const MSG: &str = "Do not use `%W` unless interpolation is needed. If not, use `%w`.";
8
9
 
@@ -16,8 +17,8 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
16
17
  }
17
18
  // `requires_interpolation?`: an element that interpolates, or one whose text could not be
18
19
  // written between single quotes.
19
- let mut cursor = node.walk();
20
- let interpolates = node.named_children(&mut cursor).any(|element| {
20
+ let _cursor = node.walk();
21
+ let interpolates = named_children_iter(node, context).any(|element| {
21
22
  holds_interpolation(element)
22
23
  || super::literal::double_quotes_required(context.source.node_text(element))
23
24
  });
@@ -5,6 +5,8 @@ use tree_sitter::Node;
5
5
  use crate::diagnostic::{Edit, Offense};
6
6
  use crate::rules::RuleContext;
7
7
  use crate::rules::node_ext::NodeExt;
8
+ use crate::rules::send_node::all_children_of;
9
+ use crate::rules::send_node::all_children_iter;
8
10
 
9
11
  const MSG: &str = "Use double pipes `||` instead.";
10
12
  const REDUNDANT_CONDITION: &str = "This condition is not needed.";
@@ -63,8 +65,12 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
63
65
  "unless" => (normalized_else.clone(), normalized_if.clone()),
64
66
  _ => (normalized_if.clone(), normalized_else.clone()),
65
67
  };
68
+ // `if_branch_is_true_type_and_else_is_not?` returns early unless the conditional was
69
+ // written as an `if` or a ternary: `unless x then true else false end` is outside it.
70
+ let is_if = node.kind_str() != "unless";
66
71
  if !is_offense(
67
72
  context,
73
+ is_if,
68
74
  ternary,
69
75
  condition,
70
76
  &raw_if,
@@ -111,6 +117,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
111
117
  #[allow(clippy::too_many_arguments)]
112
118
  fn is_offense(
113
119
  context: &RuleContext<'_>,
120
+ is_if: bool,
114
121
  ternary: bool,
115
122
  condition: Node<'_>,
116
123
  raw_if: &Option<Branch<'_>>,
@@ -137,6 +144,7 @@ fn is_offense(
137
144
  }
138
145
  if !synonymous(
139
146
  context,
147
+ is_if,
140
148
  condition,
141
149
  raw_if,
142
150
  normalized_if,
@@ -179,6 +187,7 @@ fn is_plain_node(branch: &Branch<'_>) -> bool {
179
187
 
180
188
  fn synonymous(
181
189
  context: &RuleContext<'_>,
190
+ is_if: bool,
182
191
  condition: Node<'_>,
183
192
  raw_if: &Option<Branch<'_>>,
184
193
  normalized_if: &Option<Branch<'_>>,
@@ -192,7 +201,7 @@ fn synonymous(
192
201
  {
193
202
  return true;
194
203
  }
195
- if if_branch_is_true(context, condition, normalized_if, normalized_else, allowed) {
204
+ if is_if && if_branch_is_true(context, condition, normalized_if, normalized_else, allowed) {
196
205
  return true;
197
206
  }
198
207
  if assignment_pair(context, normalized_if, normalized_else).is_some()
@@ -446,8 +455,8 @@ fn branch<'tree>(node: Node<'tree>, want_consequence: bool) -> Option<Branch<'tr
446
455
 
447
456
  /// `range_between(node.loc.question.begin_pos, node.loc.colon.end_pos)`.
448
457
  fn question_to_colon(context: &RuleContext<'_>, node: Node<'_>) -> Option<Range<usize>> {
449
- let mut cursor = node.walk();
450
- let children: Vec<Node<'_>> = node.children(&mut cursor).collect();
458
+ let _cursor = node.walk();
459
+ let children: Vec<Node<'_>> = all_children_iter(node, context).collect();
451
460
  let question = children
452
461
  .iter()
453
462
  .find(|child| context.source.node_text(**child) == "?")?;
@@ -591,16 +600,16 @@ fn stands_in_a_send(context: &RuleContext<'_>, node: Node<'_>) -> bool {
591
600
 
592
601
  /// The operator token a binary expression is written with, which the grammar keeps unnamed.
593
602
  fn binary_operator<'a>(context: &'a RuleContext<'_>, node: Node<'_>) -> Option<&'a str> {
594
- let mut cursor = node.walk();
595
- let operator = node
596
- .children(&mut cursor)
603
+ let _cursor = node.walk();
604
+ let operator = all_children_of(node, context)
605
+ .into_iter()
597
606
  .find(|child| !child.is_named() && !child.is_extra())?;
598
607
  Some(context.source.node_text(operator))
599
608
  }
600
609
 
601
610
  fn writes_through_safe_navigation(context: &RuleContext<'_>, call: Node<'_>) -> bool {
602
- let mut cursor = call.walk();
603
- call.children(&mut cursor)
611
+ let _cursor = call.walk();
612
+ all_children_iter(call, context)
604
613
  .any(|child| !child.is_named() && context.source.node_text(child) == "&.")
605
614
  }
606
615