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
@@ -7,6 +7,7 @@ use crate::rules::lint::locals::LocalVariables;
7
7
 
8
8
  use super::conditional::{UpstreamParent, upstream_parent};
9
9
  use crate::rules::node_ext::NodeExt;
10
+ use crate::rules::send_node::all_children_iter;
10
11
 
11
12
  /// `ALLOWED_NODE_TYPES`: the parents whose child keeps its parentheses around a logical operator.
12
13
  /// `send` covers every shape the grammar spells as a call.
@@ -73,6 +74,31 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
73
74
  }
74
75
  check_group(context, &locals, node, &mut pending);
75
76
  }
77
+ // **`^(var)` is a `pin` holding a `begin` upstream**, so the parentheses are a node the walk
78
+ // above would find. The grammar writes them as part of the pin itself, which leaves
79
+ // `allowed_pin_operator?` -- `^(pin (begin !{lvar ivar cvar gvar}))` -- as the only thing to
80
+ // say about them: a pinned variable is reported, anything else is left alone.
81
+ for node in context.nodes_of("expression_reference_pattern") {
82
+ let Some(value) = node.field("value") else {
83
+ continue;
84
+ };
85
+ if !is_variable(context, &locals, value) {
86
+ continue;
87
+ }
88
+ let Some(range) = pinned_parentheses(node, context) else {
89
+ continue;
90
+ };
91
+ offenses.push(
92
+ context
93
+ .offense("Don't use parentheses around a variable.", range.clone())
94
+ .corrected_by(crate::diagnostic::Edit {
95
+ start: range.start,
96
+ end: range.end,
97
+ replacement: context.source.node_text(value).to_owned(),
98
+ safe: true,
99
+ }),
100
+ );
101
+ }
76
102
  // `on_investigation_end`: each candidate's exact correction is verified by reparsing before the
77
103
  // offense is registered, so redundancy never rests on a hand-kept list of the grammar's rules.
78
104
  let verified = crate::rules::support::verified_by_reparse(
@@ -168,7 +194,7 @@ fn parent_child_count(context: &RuleContext<'_>, node: Node<'_>) -> usize {
168
194
  match parent {
169
195
  UpstreamParent::Begin(parent) => match parent.kind_str() {
170
196
  "parenthesized_statements" | "interpolation" | "begin" => {
171
- super::nodes::children(parent).len()
197
+ super::nodes::children_in(parent, context).len()
172
198
  }
173
199
  // A statement list the grammar wraps -- a `then`, an `else`, a definition's body --
174
200
  // is a `begin` of the statements it holds, less the clauses that are not statements
@@ -177,7 +203,7 @@ fn parent_child_count(context: &RuleContext<'_>, node: Node<'_>) -> usize {
177
203
  },
178
204
  UpstreamParent::Node(parent) => match parent.kind_str() {
179
205
  "array" | "string_array" | "symbol_array" | "exceptions" => {
180
- super::nodes::children(parent).len()
206
+ super::nodes::children_in(parent, context).len()
181
207
  }
182
208
  "splat_argument"
183
209
  | "hash_splat_argument"
@@ -187,10 +213,10 @@ fn parent_child_count(context: &RuleContext<'_>, node: Node<'_>) -> usize {
187
213
  | "begin_block"
188
214
  | "end_block" => 1,
189
215
  kind if JUMPS.contains(&kind) => {
190
- super::nodes::children(parent)
216
+ super::nodes::children_in(parent, context)
191
217
  .first()
192
218
  .map_or(0, |list| match list.kind_str() {
193
- "argument_list" => super::nodes::children(*list).len(),
219
+ "argument_list" => super::nodes::children_in(*list, context).len(),
194
220
  _ => 1,
195
221
  })
196
222
  }
@@ -203,14 +229,34 @@ fn parent_child_count(context: &RuleContext<'_>, node: Node<'_>) -> usize {
203
229
  true => 1,
204
230
  false => 2,
205
231
  },
232
+ // **`children.one?` counts what is there, and a missing clause is `nil`.** `if (x)\nend`
233
+ // reaches upstream as `(if (begin …) nil nil)`, whose `one?` is true because the two
234
+ // absent branches are falsy -- so the parentheses are redundant. Reading the keyword
235
+ // and the clauses the grammar writes as children instead made the count 2 and the
236
+ // offense disappear.
237
+ kind if CONDITIONALS.contains(&kind) => {
238
+ ["condition", "consequence", "alternative", "body"]
239
+ .iter()
240
+ .filter(|field| {
241
+ // A `while` always carries a `do`, empty or not, and an empty one is the
242
+ // `nil` body upstream holds.
243
+ parent
244
+ .field(field)
245
+ .is_some_and(|clause| clause.named_child_count() > 0)
246
+ })
247
+ .count()
248
+ }
206
249
  _ => 2,
207
250
  },
208
251
  }
209
252
  }
210
253
 
254
+ /// The keywords whose absent clauses upstream spells as `nil` children.
255
+ const CONDITIONALS: &[&str] = &["if", "unless", "while", "until", "elsif"];
256
+
211
257
  /// `parens_allowed?`.
212
258
  fn parens_allowed(context: &RuleContext<'_>, node: Node<'_>) -> bool {
213
- let children = super::nodes::children(node);
259
+ let children = super::nodes::children_in(node, context);
214
260
  // `empty_parentheses?`: `()` says something no rewrite could keep.
215
261
  if children.is_empty() {
216
262
  return true;
@@ -251,11 +297,10 @@ fn in_pattern_matching_in_method_argument(
251
297
  let Some(first) = children.first() else {
252
298
  return false;
253
299
  };
254
- // `match_pattern_p_type?` above Ruby 2.7, where `in` builds that node rather than the `=>` one.
255
- match context.target_ruby_version() <= crate::ruby_version::RubyVersion::new(2, 7) {
256
- true => first.kind_str() == "match_pattern",
257
- false => first.kind_str() == "test_pattern",
258
- }
300
+ // Upstream names the node differently per version -- 2.7 builds `match_pattern` for `in`, and
301
+ // 3.0 renamed that to `match_pattern_p` while giving `=>` the freed-up name. **Either way the
302
+ // question is about `in`**, which the grammar always spells `test_pattern`.
303
+ first.kind_str() == "test_pattern"
259
304
  }
260
305
 
261
306
  /// `allowed_pin_operator?`: `^(pin (begin !{lvar ivar cvar gvar}))`.
@@ -422,7 +467,7 @@ fn like_method_argument_parentheses(
422
467
  let Some(arguments) = argument_list(parent) else {
423
468
  return false;
424
469
  };
425
- let list = super::nodes::children(arguments);
470
+ let list = super::nodes::children_in(arguments, context);
426
471
  if list.len() != 1 || list[0].id() != node.id() {
427
472
  return false;
428
473
  }
@@ -476,12 +521,12 @@ fn multiline_control_flow_statements(
476
521
 
477
522
  /// `check`.
478
523
  fn check_group<'tree>(
479
- context: &RuleContext<'_>,
524
+ context: &'tree RuleContext<'_>,
480
525
  locals: &LocalVariables<'_, '_>,
481
526
  node: Node<'tree>,
482
527
  pending: &mut Vec<(Node<'tree>, &'static str)>,
483
528
  ) {
484
- let children = super::nodes::children(node);
529
+ let children = super::nodes::children_in(node, context);
485
530
  let Some(inner) = children.first().copied() else {
486
531
  return;
487
532
  };
@@ -604,6 +649,23 @@ fn is_block(node: Node<'_>) -> bool {
604
649
 
605
650
  /// `node.variable?`: an instance, class or global variable, or a bare name the parser resolved
606
651
  /// into a local variable read.
652
+ /// The `(` … `)` a pin operator wrote around its value.
653
+ fn pinned_parentheses(
654
+ node: Node<'_>,
655
+ context: &RuleContext<'_>,
656
+ ) -> Option<std::ops::Range<usize>> {
657
+ let _cursor = node.walk();
658
+ let children: Vec<Node<'_>> = all_children_iter(node, context).collect();
659
+ let open = children
660
+ .iter()
661
+ .find(|child| !child.is_named() && context.source.node_text(**child) == "(")?;
662
+ let close = children
663
+ .iter()
664
+ .rev()
665
+ .find(|child| !child.is_named() && context.source.node_text(**child) == ")")?;
666
+ Some(open.start_byte()..close.end_byte())
667
+ }
668
+
607
669
  fn is_variable(context: &RuleContext<'_>, locals: &LocalVariables<'_, '_>, node: Node<'_>) -> bool {
608
670
  match node.kind_str() {
609
671
  "instance_variable" | "class_variable" | "global_variable" => true,
@@ -947,7 +1009,7 @@ fn body_range(context: &RuleContext<'_>, node: Node<'_>, inner: Node<'_>) -> boo
947
1009
  return false;
948
1010
  };
949
1011
  let statements = match parent.kind_str() {
950
- "parenthesized_statements" | "interpolation" | "begin" => super::nodes::children(parent),
1012
+ "parenthesized_statements" | "interpolation" | "begin" => super::nodes::children_in(parent, context),
951
1013
  _ => super::conditional::self_statements(parent),
952
1014
  };
953
1015
  let beginless = inner.field("begin").is_none()
@@ -995,7 +1057,7 @@ fn keyword_with_redundant_parentheses(context: &RuleContext<'_>, node: Node<'_>)
995
1057
  None => has_own_parentheses(context, node),
996
1058
  };
997
1059
  }
998
- _ => super::nodes::children(node),
1060
+ _ => super::nodes::children_in(node, context),
999
1061
  };
1000
1062
  // `only_begin_arg?`: the keyword's single argument is itself a parenthesized group.
1001
1063
  if let [only] = arguments.as_slice()
@@ -1031,6 +1093,9 @@ fn call_has_arguments(node: Node<'_>) -> bool {
1031
1093
  // A setter call takes the assigned value, and an indexing setter its subscripts too.
1032
1094
  "binary" | "assignment" => true,
1033
1095
  "element_reference" => !super::nodes::children(node).is_empty(),
1096
+ // A `defined?` node's child **is** its argument upstream; the grammar writes it as a
1097
+ // `unary` with an operand and no argument list, so it read as taking nothing.
1098
+ "unary" => node.field("operand").is_some(),
1034
1099
  _ => argument_list(node).is_some_and(|list| !super::nodes::children(list).is_empty()),
1035
1100
  }
1036
1101
  }
@@ -1114,7 +1179,19 @@ fn is_prefix_not(context: &RuleContext<'_>, node: Node<'_>) -> bool {
1114
1179
  }
1115
1180
 
1116
1181
  fn is_suspect_unary(context: &RuleContext<'_>, node: Node<'_>) -> bool {
1117
- is_unary_operation(context, node) && !is_prefix_not(context, node)
1182
+ // `node.send_type? && ...`: `defined? arg` is a `defined?` node upstream, not a `send`, so the
1183
+ // walk down through the operators stops in front of it. Stepping past it handed
1184
+ // `method_call_with_redundant_parentheses?` the bare argument, and `foo && (!defined? arg)`
1185
+ // came out as redundant parentheses around a unary operation.
1186
+ !is_defined(context, node) && is_unary_operation(context, node) && !is_prefix_not(context, node)
1187
+ }
1188
+
1189
+ /// `defined? x`, which the grammar spells as a `unary` and upstream as a node type of its own.
1190
+ fn is_defined(context: &RuleContext<'_>, node: Node<'_>) -> bool {
1191
+ node.kind_str() == "unary"
1192
+ && node
1193
+ .field("operator")
1194
+ .is_some_and(|operator| context.source.node_text(operator) == "defined?")
1118
1195
  }
1119
1196
 
1120
1197
  /// `method_call_with_redundant_parentheses?`.
@@ -1137,7 +1214,7 @@ fn method_call_with_redundant_parentheses(
1137
1214
  if singular_parenthesized_parent(context, node) {
1138
1215
  return true;
1139
1216
  }
1140
- !call_has_arguments(call) || has_own_parentheses(context, call) || square_brackets(call)
1217
+ !call_has_arguments(call) || has_own_parentheses(context, call) || square_brackets(call, context)
1141
1218
  }
1142
1219
 
1143
1220
  /// `singular_parenthesized_parent?`.
@@ -1154,14 +1231,14 @@ fn singular_parenthesized_parent(context: &RuleContext<'_>, node: Node<'_>) -> b
1154
1231
  }
1155
1232
 
1156
1233
  /// `square_brackets?`: an index written on something the parentheses cannot be part of.
1157
- fn square_brackets(node: Node<'_>) -> bool {
1234
+ fn square_brackets(node: Node<'_>, context: &RuleContext<'_>) -> bool {
1158
1235
  if node.kind_str() != "element_reference" {
1159
1236
  return false;
1160
1237
  }
1161
1238
  let Some(object) = node.field("object") else {
1162
1239
  return false;
1163
1240
  };
1164
- super::conditional::descendants(object)
1241
+ super::conditional::descendants(object, context)
1165
1242
  .into_iter()
1166
1243
  .any(|descendant| match descendant.kind_str() {
1167
1244
  "string" | "array" | "string_array" | "symbol_array" | "hash" => true,
@@ -46,7 +46,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
46
46
  }
47
47
  let Some(argument) = node
48
48
  .field("arguments")
49
- .and_then(|arguments| super::nodes::children(arguments).into_iter().next())
49
+ .and_then(|arguments| super::nodes::children_in(arguments, context).into_iter().next())
50
50
  else {
51
51
  continue;
52
52
  };
@@ -14,6 +14,7 @@ use tree_sitter::Node;
14
14
  use crate::diagnostic::{Edit, Offense};
15
15
  use crate::rules::RuleContext;
16
16
  use crate::rules::node_ext::NodeExt;
17
+ use crate::rules::send_node::named_children_of;
17
18
 
18
19
  /// `REQUIRES_ESCAPE_OUTSIDE_CHAR_CLASS_CHARS`: what a character class is the escape for.
19
20
  const REQUIRES_ESCAPE: [char; 10] = ['.', '*', '+', '?', '{', '}', '(', ')', '|', '$'];
@@ -114,9 +115,9 @@ impl Pattern {
114
115
  let flags = &context.source.node_text(closing)[1..];
115
116
  let start = opening.end_byte();
116
117
  let end = closing.start_byte();
117
- let mut cursor = node.walk();
118
- let interpolations: Vec<Range<usize>> = node
119
- .named_children(&mut cursor)
118
+ let _cursor = node.walk();
119
+ let interpolations: Vec<Range<usize>> = named_children_of(node, context)
120
+ .into_iter()
120
121
  .filter(|child| child.kind_str() == "interpolation")
121
122
  .map(|child| child.byte_range())
122
123
  .collect();
@@ -3,6 +3,7 @@ use tree_sitter::Node;
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 = "Redundant escape inside regexp literal";
8
9
 
@@ -67,8 +68,8 @@ impl Literal {
67
68
  let mut pattern = context.source.slice(start..closing.start_byte()).to_owned();
68
69
  // `with_interpolations_blanked`: the code inside `#{}` is no part of the pattern, but its
69
70
  // width has to stay so that the offsets still line up.
70
- let mut cursor = node.walk();
71
- for child in node.named_children(&mut cursor) {
71
+ let _cursor = node.walk();
72
+ for child in named_children_iter(node, context) {
72
73
  if child.kind_str() != "interpolation" {
73
74
  continue;
74
75
  }
@@ -157,8 +157,7 @@ const NOT_A_STATEMENT: &[&str] = &["comment", "empty_statement", "heredoc_body"]
157
157
  /// protected body -- so it is skipped even though it is a child here.
158
158
  fn check_sequence<'tree>(node: Node<'tree>, returns: &mut Vec<Node<'tree>>) {
159
159
  let mut cursor = node.walk();
160
- let children: Vec<Node<'tree>> = node
161
- .named_children(&mut cursor)
160
+ let children: Vec<Node<'tree>> = node.named_children(&mut cursor)
162
161
  .filter(|child| !NOT_A_STATEMENT.contains(&child.kind_str()))
163
162
  .collect();
164
163
 
@@ -14,8 +14,8 @@ use tree_sitter::Node;
14
14
 
15
15
  use crate::diagnostic::{Edit, Offense};
16
16
  use crate::rules::RuleContext;
17
- use crate::rules::send_node;
18
17
  use crate::rules::node_ext::NodeExt;
18
+ use crate::rules::send_node;
19
19
 
20
20
  const MSG: &str = "Redundant `self` detected.";
21
21
 
@@ -157,6 +157,11 @@ const CONDITIONALS: &[&str] = &[
157
157
  type Scope = Rc<RefCell<Vec<String>>>;
158
158
 
159
159
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
160
+ // Without an explicit `self` token no send can be reported. Building the scope table is a
161
+ // whole-file operation, so avoid it for the common file that has no candidate at all.
162
+ if context.nodes_of("self").next().is_none() {
163
+ return;
164
+ }
160
165
  let mut cop = Cop {
161
166
  context,
162
167
  allowed: HashSet::new(),
@@ -204,10 +209,10 @@ impl<'tree> Cop<'_, 'tree> {
204
209
 
205
210
  /// `add_scope`: every node under this one answers with the same list.
206
211
  fn add_scope(&mut self, node: Node<'tree>, scope: Scope) {
207
- let mut stack = send_node::named_children(node);
212
+ let mut stack = send_node::named_children_of(node, self.context);
208
213
  while let Some(current) = stack.pop() {
209
214
  self.scopes.insert(current.id(), Scope::clone(&scope));
210
- stack.extend(send_node::named_children(current));
215
+ stack.extend(send_node::named_children_of(current, self.context));
211
216
  }
212
217
  }
213
218
 
@@ -245,16 +250,13 @@ impl<'tree> Cop<'_, 'tree> {
245
250
  }
246
251
 
247
252
  fn on_assignment(&mut self, node: Node<'tree>) {
248
- let (Some(left), Some(right)) = (
249
- node.field("left"),
250
- node.field("right"),
251
- ) else {
253
+ let (Some(left), Some(right)) = (node.field("left"), node.field("right")) else {
252
254
  return;
253
255
  };
254
256
  match left.kind_str() {
255
257
  // `on_masgn`: every name the left-hand side binds.
256
258
  "left_assignment_list" => {
257
- for target in send_node::named_children(left) {
259
+ for target in send_node::named_children_of(left, self.context) {
258
260
  let name = self.context.source.node_text(target).to_owned();
259
261
  self.add_lhs_to_scopes(right, name);
260
262
  }
@@ -288,13 +290,13 @@ impl<'tree> Cop<'_, 'tree> {
288
290
  }
289
291
 
290
292
  fn on_resbody(&mut self, node: Node<'tree>) {
291
- let Some(variable) = send_node::named_children(node)
293
+ let Some(variable) = send_node::named_children_of(node, self.context)
292
294
  .into_iter()
293
295
  .find(|child| child.kind_str() == "exception_variable")
294
296
  else {
295
297
  return;
296
298
  };
297
- let Some(name) = send_node::named_children(variable)
299
+ let Some(name) = send_node::named_children_of(variable, self.context)
298
300
  .first()
299
301
  .filter(|bound| bound.kind_str() == "identifier")
300
302
  .map(|bound| self.context.source.node_text(*bound).to_owned())
@@ -307,12 +309,12 @@ impl<'tree> Cop<'_, 'tree> {
307
309
  /// `add_match_var_scopes`: `in [Integer => n]` binds `n` for the branch.
308
310
  fn on_in_pattern(&mut self, node: Node<'tree>) {
309
311
  let mut names = Vec::new();
310
- let mut stack = send_node::named_children(node);
312
+ let mut stack = send_node::named_children_of(node, self.context);
311
313
  while let Some(current) = stack.pop() {
312
314
  if is_match_var(current) {
313
315
  names.push(self.context.source.node_text(current).to_owned());
314
316
  }
315
- stack.extend(send_node::named_children(current));
317
+ stack.extend(send_node::named_children_of(current, self.context));
316
318
  }
317
319
  self.scope(node.id()).borrow_mut().extend(names);
318
320
  }
@@ -324,7 +326,7 @@ impl<'tree> Cop<'_, 'tree> {
324
326
  return;
325
327
  };
326
328
  let mut names = Vec::new();
327
- let mut stack = send_node::named_children(node);
329
+ let mut stack = send_node::named_children_of(node, self.context);
328
330
  while let Some(current) = stack.pop() {
329
331
  if current.kind_str() == "assignment"
330
332
  && let Some(left) = current.field("left")
@@ -332,14 +334,14 @@ impl<'tree> Cop<'_, 'tree> {
332
334
  match left.kind_str() {
333
335
  "identifier" => names.push(self.context.source.node_text(left).to_owned()),
334
336
  "left_assignment_list" => names.extend(
335
- send_node::named_children(left)
337
+ send_node::named_children_of(left, self.context)
336
338
  .into_iter()
337
339
  .map(|target| self.context.source.node_text(target).to_owned()),
338
340
  ),
339
341
  _ => {}
340
342
  }
341
343
  }
342
- stack.extend(send_node::named_children(current));
344
+ stack.extend(send_node::named_children_of(current, self.context));
343
345
  }
344
346
  for name in names {
345
347
  self.add_lhs_to_scopes(condition, name);
@@ -483,7 +485,7 @@ impl<'tree> Cop<'_, 'tree> {
483
485
  let mut current = node.parent_of(self.context);
484
486
  while let Some(visited) = current {
485
487
  if matches!(visited.kind_str(), "do_block" | "block" | "lambda") {
486
- return send_node::named_children(visited)
488
+ return send_node::named_children_of(visited, self.context)
487
489
  .iter()
488
490
  .all(|child| child.kind_str() != "block_parameters");
489
491
  }
@@ -518,6 +520,16 @@ fn is_match_var(node: Node<'_>) -> bool {
518
520
  ) || parent.kind_str() == "array_pattern"
519
521
  || parent.kind_str() == "find_pattern"
520
522
  || parent.kind_str() == "hash_pattern"
523
+ // `in {x: foo}`: the grammar puts a `keyword_pattern` between the hash pattern
524
+ // and the name. Its key is a `hash_key_symbol`, so an identifier here is always
525
+ // the binding -- `in {x:}` binds `x` the same way.
526
+ || parent.kind_str() == "keyword_pattern"
521
527
  || parent.kind_str() == "alternative_pattern"
528
+ // `in *bar` is a `match_rest` holding a `match_var` upstream, so the name it
529
+ // catches is bound for the branch just like any other. A bare `*` binds nothing
530
+ // and has no identifier under it.
531
+ || parent.kind_str() == "splat_parameter"
532
+ // `^foo` is a `pin`, not a binding: the identifier under it stays a read, which is
533
+ // why `variable_reference_pattern` is deliberately absent from this list.
522
534
  })
523
535
  }
@@ -85,7 +85,7 @@ fn redundant_sort<'tree>(context: &RuleContext<'_>, node: Node<'tree>) -> Option
85
85
  // `sorted[0]` is a call to `:[]` upstream, whose selector is the bracket.
86
86
  "element_reference" => {
87
87
  let object = node.field("object")?;
88
- let indices = super::nodes::children(node);
88
+ let indices = super::nodes::children_in(node, context);
89
89
  let index = match indices.as_slice() {
90
90
  [_, only] => *only,
91
91
  _ => return None,
@@ -30,7 +30,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
30
30
  }
31
31
  let read = context.source.node_text(*statement);
32
32
  let message = match block.field("parameters") {
33
- Some(parameters) => match super::nodes::children(parameters).as_slice() {
33
+ Some(parameters) => match super::nodes::children_in(parameters, context).as_slice() {
34
34
  [only]
35
35
  if only.kind_str() == "identifier" && context.source.node_text(*only) == read =>
36
36
  {
@@ -7,6 +7,7 @@ use tree_sitter::Node;
7
7
  use crate::diagnostic::{Edit, Offense};
8
8
  use crate::rules::RuleContext;
9
9
  use crate::rules::node_ext::NodeExt;
10
+ use crate::rules::send_node::all_children_of;
10
11
 
11
12
  /// One literal as `on_str` sees it: the text to scan, plus what the delimiters make of a backslash.
12
13
  struct Literal {
@@ -58,7 +59,7 @@ fn literals(context: &RuleContext<'_>) -> Vec<Literal> {
58
59
  for array in context.nodes_of("string_array") {
59
60
  let upper = delimiter_text(array, context, 0).starts_with("%W");
60
61
  let delimiters = array_delimiters(array, context);
61
- for word in super::nodes::children(array) {
62
+ for word in super::nodes::children_in(array, context) {
62
63
  if word.kind_str() != "bare_string" {
63
64
  continue;
64
65
  }
@@ -73,7 +74,7 @@ fn literals(context: &RuleContext<'_>) -> Vec<Literal> {
73
74
  }
74
75
  }
75
76
  for (opener, body) in heredocs(context) {
76
- let Some(terminator) = super::nodes::children(body)
77
+ let Some(terminator) = super::nodes::children_in(body, context)
77
78
  .into_iter()
78
79
  .find(|child| child.kind_str() == "heredoc_end")
79
80
  else {
@@ -171,9 +172,9 @@ fn disabling_interpolation(source: &str, start: usize) -> bool {
171
172
 
172
173
  /// The delimiter written at one end of a literal, which the grammar keeps as an unnamed token.
173
174
  fn delimiter_text<'a>(node: Node<'_>, context: &'a RuleContext<'_>, end: usize) -> &'a str {
174
- let mut cursor = node.walk();
175
- let children: Vec<Node<'_>> = node
176
- .children(&mut cursor)
175
+ let _cursor = node.walk();
176
+ let children: Vec<Node<'_>> = all_children_of(node, context)
177
+ .into_iter()
177
178
  .filter(|c| !c.is_named())
178
179
  .collect();
179
180
  let token = match end {
@@ -5,6 +5,7 @@ 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::named_children_iter;
8
9
 
9
10
  const MSG_USE_SLASHES: &str = "Use `//` around regular expression.";
10
11
  const MSG_USE_PERCENT_R: &str = "Use `%r` around regular expression.";
@@ -123,8 +124,8 @@ impl<'a> Literal<'a> {
123
124
 
124
125
  let mut literal_text = String::new();
125
126
  let mut start = begin.end_byte();
126
- let mut cursor = node.walk();
127
- for child in node.named_children(&mut cursor) {
127
+ let _cursor = node.walk();
128
+ for child in named_children_iter(node, context) {
128
129
  if child.kind_str() != "interpolation" {
129
130
  continue;
130
131
  }
@@ -80,7 +80,7 @@ fn method_name<'a>(call: Node<'_>, context: &'a RuleContext<'_>) -> Option<&'a s
80
80
  /// `find_previous_older_sibling`: the nearest `require` above that this one sorts before, with the
81
81
  /// search stopping at anything that is not a `require` of the same kind.
82
82
  fn find_previous_older_sibling<'tree>(
83
- context: &RuleContext<'_>,
83
+ context: &'tree RuleContext<'_>,
84
84
  call: Node<'tree>,
85
85
  parent: Node<'tree>,
86
86
  ) -> Option<Node<'tree>> {
@@ -90,7 +90,7 @@ fn find_previous_older_sibling<'tree>(
90
90
  false => call,
91
91
  };
92
92
  let container = search.parent()?;
93
- let statements = super::nodes::children(container);
93
+ let statements = super::nodes::children_in(container, context);
94
94
  let position = statements
95
95
  .iter()
96
96
  .position(|statement| statement.id() == search.id())?;
@@ -3,6 +3,7 @@
3
3
  use tree_sitter::Node;
4
4
 
5
5
  use crate::diagnostic::{Edit, Offense};
6
+ use crate::ruby_version::RubyVersion;
6
7
  use crate::rules::RuleContext;
7
8
  use crate::rules::node_ext::NodeExt;
8
9
  use crate::rules::send_node;
@@ -30,9 +31,13 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
30
31
  // **多重代入のときだけ**である。`w = 1, 2 rescue nil` は `(rescue (lvasgn w (array 1 2))
31
32
  // ..)` で修飾子が外に出るが、`a, b = 1, 2 rescue nil` は `(masgn (mlhs ..) (rescue ..))`
32
33
  // で中に入る。左辺が代入の並びかどうかで分かれる。
33
- let operation = match operation
34
- .field("left")
35
- .is_some_and(|left| left.kind_str() == "left_assignment_list")
34
+ // その振り分けは **2.7 以降のパーサ**の話である。2.6 では多重代入もろとも `rescue` の
35
+ // 中に入る (`(rescue (masgn ..) ..)`) ので、文法が作る形がそのまま本家の形になる。
36
+ let splits_multiple_assignment = context.target_ruby_version() >= RubyVersion::new(2, 7);
37
+ let operation = match splits_multiple_assignment
38
+ && operation
39
+ .field("left")
40
+ .is_some_and(|left| left.kind_str() == "left_assignment_list")
36
41
  {
37
42
  true => match operation.field("right") {
38
43
  Some(right) => right,
@@ -127,7 +132,7 @@ fn heredoc_end(context: &RuleContext<'_>, operation: Node<'_>) -> Option<usize>
127
132
  .map(send_node::Argument::first)
128
133
  .find(|argument| argument.kind_str() == "heredoc_beginning")?;
129
134
  let body = send_node::heredoc_body(beginning, context)?;
130
- super::nodes::children(body)
135
+ super::nodes::children_in(body, context)
131
136
  .into_iter()
132
137
  .find(|child| child.kind_str() == "heredoc_end")
133
138
  .map(|terminator| terminator.end_byte())
@@ -49,7 +49,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
49
49
  /// `(array (const {nil? cbase} :StandardError))`: the one name, unqualified or written from the
50
50
  /// root.
51
51
  fn only_standard_error(context: &RuleContext<'_>, list: Node<'_>) -> bool {
52
- let exceptions = super::nodes::children(list);
52
+ let exceptions = super::nodes::children_in(list, context);
53
53
  let [only] = exceptions.as_slice() else {
54
54
  return false;
55
55
  };
@@ -13,11 +13,11 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
13
13
  .setting::<String>("EnforcedStyle")
14
14
  .is_some_and(|style| style == "return_nil");
15
15
  for node in context.nodes_of("return") {
16
- let written = super::nodes::children(node);
16
+ let written = super::nodes::children_in(node, context);
17
17
  let returns_nil = matches!(
18
18
  written.as_slice(),
19
19
  [list] if list.kind_str() == "argument_list"
20
- && matches!(super::nodes::children(*list).as_slice(),
20
+ && matches!(super::nodes::children_in(*list, context).as_slice(),
21
21
  [only] if only.kind_str() == "nil")
22
22
  );
23
23
  // `correct_style?`: only one of the two spellings is ever wrong, and a `return 1` is
@@ -43,9 +43,9 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
43
43
  };
44
44
  // `body.each_descendant(:return)`: every `return` **below** the body. A body that is
45
45
  // itself a bare `return nil` is not a descendant of itself, so upstream misses it.
46
- let mut stack: Vec<Node<'_>> = super::nodes::children(body);
46
+ let mut stack: Vec<Node<'_>> = super::nodes::children_in(body, context);
47
47
  while let Some(current) = stack.pop() {
48
- stack.extend(super::nodes::children(current));
48
+ stack.extend(super::nodes::children_in(current, context));
49
49
  if current.kind_str() == "return" && returns_nil(current) {
50
50
  offenses.push(replacement(context, current, "return false"));
51
51
  }
@@ -533,7 +533,7 @@ fn and_with_rhs_or(context: &RuleContext<'_>, node: Node<'_>) -> bool {
533
533
  return true;
534
534
  }
535
535
  right.kind_str() == "parenthesized_statements"
536
- && matches!(super::nodes::children(right).as_slice(), [only] if is_or(context, *only))
536
+ && matches!(super::nodes::children_in(right, context).as_slice(), [only] if is_or(context, *only))
537
537
  }
538
538
 
539
539
  /// `find_matching_receiver_invocation`.
@@ -686,7 +686,7 @@ fn and_parts<'tree>(context: &RuleContext<'_>, node: Node<'tree>) -> Vec<Part<'t
686
686
  fn and_inside_begin(context: &RuleContext<'_>, node: Node<'_>) -> bool {
687
687
  send_node::any_descendant(node, &mut |candidate| {
688
688
  candidate.kind_str() == "parenthesized_statements"
689
- && super::nodes::children(candidate)
689
+ && super::nodes::children_in(candidate, context)
690
690
  .first()
691
691
  .is_some_and(|first| is_and(context, *first))
692
692
  })
@@ -52,7 +52,14 @@ fn safe_navigation_chains<'tree>(node: Node<'tree>, context: &RuleContext<'_>) -
52
52
  let Some(parent) = enclosing(current) else {
53
53
  break;
54
54
  };
55
- if !is_safe_navigation(parent, context) {
55
+ // **`other&.foo = x` is one `csend` of `:foo=` upstream.** The grammar writes it as an
56
+ // assignment whose target is the safe navigation, so the assignment stands where that
57
+ // `csend` would.
58
+ let is_csend_setter = parent.kind_str() == "assignment"
59
+ && parent
60
+ .field("left")
61
+ .is_some_and(|left| is_safe_navigation(left, context));
62
+ if !is_csend_setter && !is_safe_navigation(parent, context) {
56
63
  break;
57
64
  }
58
65
  chains.push(parent);