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
@@ -79,7 +79,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
79
79
  /// `(call $_ ${:first :last :[] :at :slice} $...)`: the receiver, the selector's name and the
80
80
  /// arguments of a call that takes one element out of a collection.
81
81
  fn indexing<'tree>(
82
- context: &RuleContext<'_>,
82
+ context: &'tree RuleContext<'_>,
83
83
  node: Node<'tree>,
84
84
  ) -> Option<(Node<'tree>, &'static str, Vec<Argument<'tree>>)> {
85
85
  if node.kind_str() == "element_reference" {
@@ -101,7 +101,7 @@ fn indexing<'tree>(
101
101
  };
102
102
  let written = node
103
103
  .field("arguments")
104
- .map(|list| super::nodes::children(list))
104
+ .map(|list| super::nodes::children_in(list, context))
105
105
  .unwrap_or_default();
106
106
  Some((receiver, method, group(&written)))
107
107
  }
@@ -128,7 +128,7 @@ fn shuffle_call(
128
128
  }
129
129
  let written = node
130
130
  .field("arguments")
131
- .map(|list| super::nodes::children(list))
131
+ .map(|list| super::nodes::children_in(list, context))
132
132
  .unwrap_or_default();
133
133
  let first = group(&written)
134
134
  .first()
@@ -107,6 +107,10 @@ fn receiver_allowed(receiver: Option<Node<'_>>, context: &RuleContext<'_>) -> bo
107
107
  };
108
108
  match receiver.kind_str() {
109
109
  "hash" => true,
110
+ // `(call _ {:to_h :to_hash} ...)` matches a receiverless call too -- `to_h.reject { … }`
111
+ // is `(send (send nil :to_h) :reject)`. The grammar writes a bare name as an `identifier`,
112
+ // which fell through to "not a hash".
113
+ "identifier" => matches!(context.source.node_text(receiver), "to_h" | "to_hash"),
110
114
  // `env_const?`: `(const {nil? cbase} :ENV)`.
111
115
  "constant" => context.source.node_text(receiver) == "ENV",
112
116
  "scope_resolution" => {
@@ -150,7 +154,7 @@ fn is_hash_constant(node: Node<'_>, context: &RuleContext<'_>) -> bool {
150
154
  /// an `itblock` reading `it`.
151
155
  fn block_argument(context: &RuleContext<'_>, block: Node<'_>) -> Option<String> {
152
156
  if let Some(parameters) = block.field("parameters") {
153
- let declared = super::nodes::children(parameters);
157
+ let declared = super::nodes::children_in(parameters, context);
154
158
  let [only] = declared.as_slice() else {
155
159
  return None;
156
160
  };
@@ -170,7 +174,7 @@ fn block_argument(context: &RuleContext<'_>, block: Node<'_>) -> Option<String>
170
174
  /// How many numbered parameters the body reads, and whether it reads `it`. A nested block's names
171
175
  /// belong to that block, not to this one.
172
176
  fn scan_implicit(context: &RuleContext<'_>, node: Node<'_>, numbered: &mut usize, it: &mut bool) {
173
- for child in super::nodes::children(node) {
177
+ for child in super::nodes::children_in(node, context) {
174
178
  if matches!(child.kind_str(), "block" | "do_block" | "lambda") {
175
179
  continue;
176
180
  }
@@ -140,7 +140,7 @@ fn find_range(statement: Node<'_>, context: &RuleContext<'_>) -> Option<String>
140
140
  }
141
141
  let receiver = inner.field("receiver")?;
142
142
  let receiver = match receiver.kind_str() {
143
- "parenthesized_statements" => super::nodes::children(receiver).first().copied()?,
143
+ "parenthesized_statements" => super::nodes::children_in(receiver, context).first().copied()?,
144
144
  _ => receiver,
145
145
  };
146
146
  Some(context.source.node_text(receiver).to_owned())
@@ -2,9 +2,10 @@ use std::collections::HashSet;
2
2
 
3
3
  use crate::diagnostic::{Edit, Offense};
4
4
  use crate::rules::RuleContext;
5
+ use crate::rules::node_ext::NodeExt;
5
6
  use crate::rules::support::Interpolations;
6
7
  use crate::source::is_protected;
7
- use crate::rules::node_ext::NodeExt;
8
+ use crate::rules::send_node::named_children_iter;
8
9
 
9
10
  /// Node kinds whose named children are a sequence of statements.
10
11
  ///
@@ -39,7 +40,6 @@ const NON_STATEMENT_KINDS: &[&str] = &["comment", "empty_statement", "rescue", "
39
40
  /// walks a token stream rather than the text, so none of these is a semicolon to it.
40
41
  const SEMICOLON_BEARING_TOKENS: &[&str] = &["global_variable", "character", "delimited_symbol"];
41
42
 
42
-
43
43
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
44
44
  let text = context.source.text();
45
45
  if !text.contains(';') {
@@ -113,6 +113,23 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
113
113
  // `corrector.wrap(node, '(', ')') if node`: an endless range whose `;` goes away would
114
114
  // reach on to the next line and swallow it (`42..;\n42...;` becomes one range). Upstream
115
115
  // puts parentheses around the range first.
116
+ // `token_before_semicolon&.type == :tLABEL`: `m key:;` has to keep reading as an argument
117
+ // list once the semicolon goes, so the hash gains parentheses and the space in front of it
118
+ // is removed. Without it `m key:` is a label standing on its own.
119
+ if let Some((space, arguments)) = value_omission_before(context, offset) {
120
+ offenses.push(offense.corrected_by_all([
121
+ Edit {
122
+ start: space.start,
123
+ end: space.end,
124
+ replacement: String::new(),
125
+ safe: true,
126
+ },
127
+ insert(arguments.start, "("),
128
+ insert(arguments.end, ")"),
129
+ removal,
130
+ ]));
131
+ continue;
132
+ }
116
133
  if let Some(range) = endless_range_before(context, offset) {
117
134
  offenses.push(offense.corrected_by_all([
118
135
  insert(range.start, "("),
@@ -207,6 +224,12 @@ fn line_terminator_or_opener(
207
224
  if after_last == "}" {
208
225
  return Some(last);
209
226
  }
227
+ // `exist_semicolon_before_right_string_interpolation_brace?`: the `}` closing an interpolation
228
+ // is a `tSTRING_DEND`, and the string it sits in goes on past it -- so the line does not end
229
+ // there, but the semicolon still terminates the last expression of the interpolation.
230
+ if closes_an_interpolation(context, last) {
231
+ return Some(last);
232
+ }
210
233
  let before_first = text[code.start..first].trim_end();
211
234
  if before_first.ends_with('{') {
212
235
  return Some(first);
@@ -214,6 +237,17 @@ fn line_terminator_or_opener(
214
237
  None
215
238
  }
216
239
 
240
+ /// Whether the first thing after the semicolon is the brace that closes an interpolation.
241
+ fn closes_an_interpolation(context: &RuleContext<'_>, semicolon: usize) -> bool {
242
+ let text = context.source.text();
243
+ let rest = &text[semicolon + 1..];
244
+ let brace = semicolon + 1 + (rest.len() - rest.trim_start().len());
245
+ text[brace..].starts_with('}')
246
+ && context
247
+ .nodes_of("interpolation")
248
+ .any(|node| node.end_byte() == brace + 1)
249
+ }
250
+
217
251
  /// The byte range of `line_number` with comments and surrounding whitespace removed.
218
252
  fn code_range(context: &RuleContext<'_>, line_number: usize) -> Option<std::ops::Range<usize>> {
219
253
  let text = context.source.text();
@@ -235,8 +269,8 @@ fn code_range(context: &RuleContext<'_>, line_number: usize) -> Option<std::ops:
235
269
  fn expression_separator_lines(context: &RuleContext<'_>) -> HashSet<usize> {
236
270
  let mut lines = HashSet::new();
237
271
  for node in context.nodes_of_any(STATEMENT_SEQUENCE_KINDS) {
238
- let mut cursor = node.walk();
239
- let children: Vec<_> = node.named_children(&mut cursor).collect();
272
+ let _cursor = node.walk();
273
+ let children: Vec<_> = named_children_iter(node, context).collect();
240
274
  // `begin ... end` holds its statements directly upstream rather than wrapping them in the
241
275
  // `begin` node the cop looks for, so `begin a; b end` is not an offense. Add a `rescue` and
242
276
  // the protected body becomes such a node after all.
@@ -295,3 +329,42 @@ fn heredoc_opened_before(
295
329
  .iter()
296
330
  .any(|(line, end)| *line == line_number && *end <= offset)
297
331
  }
332
+
333
+ /// The space to drop and the span to bracket when the semicolon follows a value-omitted label.
334
+ ///
335
+ /// `value_omission_pair_nodes` upstream; the pair's grandparent is the call whose selector the
336
+ /// space belongs to.
337
+ fn value_omission_before(
338
+ context: &RuleContext<'_>,
339
+ offset: usize,
340
+ ) -> Option<(std::ops::Range<usize>, std::ops::Range<usize>)> {
341
+ let before = context.source.text()[..offset].trim_end().len();
342
+ for pair in context.nodes_of("pair") {
343
+ if pair.field("value").is_some() || pair.end_byte() != before {
344
+ continue;
345
+ }
346
+ let list = pair.parent()?;
347
+ if list.kind_str() != "argument_list" {
348
+ continue;
349
+ }
350
+ let call = list.parent()?;
351
+ if call.kind_str() != "call" {
352
+ continue;
353
+ }
354
+ let selector = call.field("method")?;
355
+ // A call already written with brackets needs neither edit.
356
+ if list
357
+ .child(0)
358
+ .is_some_and(|first| context.source.node_text(first) == "(")
359
+ {
360
+ continue;
361
+ }
362
+ let _cursor = list.walk();
363
+ let first = named_children_iter(list, context).next()?;
364
+ return Some((
365
+ selector.end_byte()..first.start_byte(),
366
+ first.start_byte()..list.end_byte(),
367
+ ));
368
+ }
369
+ None
370
+ }
@@ -3,18 +3,20 @@ use tree_sitter::Node;
3
3
  use crate::diagnostic::{Edit, Offense};
4
4
  use crate::rules::RuleContext;
5
5
  use crate::rules::lint::locals::LocalVariables;
6
- use crate::rules::send_node::{is_plain_send, top_level_constant};
7
6
  use crate::rules::node_ext::NodeExt;
7
+ use crate::rules::send_node::{is_plain_send, top_level_constant};
8
+
9
+ const FAIL_MSG: &str = "Use `fail` instead of `raise` to signal exceptions.";
10
+ const RAISE_MSG: &str = "Use `raise` instead of `fail` to rethrow exceptions.";
8
11
 
9
12
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
10
13
  let style = context
11
14
  .setting::<String>("EnforcedStyle")
12
15
  .unwrap_or_else(|| "only_raise".to_owned());
13
- // `:semantic` decides per rescue clause which of the two words belongs where; it is not the
14
- // default and is left to the pass that ports `on_rescue`.
15
16
  let (looked_for, preferred) = match style.as_str() {
16
17
  "only_raise" => ("fail", "raise"),
17
18
  "only_fail" => ("raise", "fail"),
19
+ "semantic" => return check_semantic(context, offenses),
18
20
  _ => return,
19
21
  };
20
22
  // `custom_fail_defined?`: a file that defines its own `fail` is not talking about `Kernel#fail`.
@@ -45,6 +47,61 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
45
47
  }
46
48
  }
47
49
 
50
+ /// `:semantic`: `raise` belongs inside a rescue clause and `fail` everywhere else.
51
+ ///
52
+ /// Upstream reaches the same split from two directions -- `on_rescue` walks the body for `raise`
53
+ /// and each `resbody` for `fail`, while `on_send` catches every remaining `raise` -- with
54
+ /// `ignore_node` keeping the two from reporting the same call twice. Asking each call where it
55
+ /// sits gives the same set without the bookkeeping: a call under a `rescue` clause's body may say
56
+ /// `raise`, and one anywhere else may say `fail`.
57
+ fn check_semantic(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
58
+ let locals = LocalVariables::new(context);
59
+ for node in context.nodes_of_any(&["call", "identifier"]) {
60
+ if !is_plain_send(node, context) {
61
+ continue;
62
+ }
63
+ let rescuing = inside_rescue_body(node);
64
+ let looked_for = match rescuing {
65
+ true => "fail",
66
+ false => "raise",
67
+ };
68
+ let Some(selector) = signal_selector(node, looked_for, context, &locals) else {
69
+ continue;
70
+ };
71
+ let (message, preferred) = match rescuing {
72
+ true => (RAISE_MSG, "raise"),
73
+ false => (FAIL_MSG, "fail"),
74
+ };
75
+ offenses.push(
76
+ context
77
+ .offense(message, selector.byte_range())
78
+ .corrected_by(Edit {
79
+ start: selector.start_byte(),
80
+ end: selector.end_byte(),
81
+ replacement: preferred.to_owned(),
82
+ safe: true,
83
+ }),
84
+ );
85
+ }
86
+ }
87
+
88
+ /// Whether the call sits in the body of a `rescue` clause -- not in the exception list or the
89
+ /// `=> e` binding, which are part of the same node but not part of the clause's body.
90
+ fn inside_rescue_body(node: Node<'_>) -> bool {
91
+ let mut current = node;
92
+ while let Some(parent) = current.parent() {
93
+ if parent.kind_str() == "rescue"
94
+ && parent.field("body").is_some_and(|body| {
95
+ body.start_byte() <= node.start_byte() && node.end_byte() <= body.end_byte()
96
+ })
97
+ {
98
+ return true;
99
+ }
100
+ current = parent;
101
+ }
102
+ false
103
+ }
104
+
48
105
  /// `command_or_kernel_call?`: the `raise` / `fail` token of a receiverless call or of one made
49
106
  /// through `Kernel`.
50
107
  fn signal_selector<'tree>(
@@ -107,8 +107,16 @@ fn is_dig(context: &RuleContext<'_>, node: Node<'_>) -> bool {
107
107
  let Some(arguments) = node.field("arguments") else {
108
108
  return false;
109
109
  };
110
- let arguments = super::nodes::children(arguments);
111
- !arguments.is_empty()
110
+ let arguments = super::nodes::children_in(arguments, context);
111
+ // `(call _ :dig !{hash block_pass}+)`: at least one argument that is neither a hash nor a
112
+ // block pass. The grammar spells a brace-less hash as its own pairs and splats, and an
113
+ // anonymous `**` as a lone `hash_splat_argument` -- all of them one `hash` upstream.
114
+ arguments.iter().any(|argument| {
115
+ !matches!(
116
+ argument.kind_str(),
117
+ "hash" | "pair" | "hash_splat_argument" | "block_argument"
118
+ )
119
+ })
112
120
  && arguments
113
121
  .iter()
114
122
  .all(|argument| !matches!(argument.kind_str(), "hash" | "pair" | "block_argument"))
@@ -6,6 +6,7 @@ use crate::diagnostic::{Edit, Offense};
6
6
  use crate::rules::RuleContext;
7
7
  use crate::rules::lint::locals::LocalVariables;
8
8
  use crate::rules::node_ext::NodeExt;
9
+ use crate::rules::send_node::all_children_of;
9
10
 
10
11
  /// One entry of `Methods`, which upstream writes as a list of single-key hashes.
11
12
  type Methods = Vec<HashMap<String, Vec<String>>>;
@@ -43,11 +44,20 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
43
44
  let Some(list) = block.field("parameters") else {
44
45
  continue;
45
46
  };
46
- // `eligible_arguments?`: every parameter has to be a plain one.
47
- let written = super::nodes::children(list);
47
+ // `eligible_arguments?`: every parameter has to be a plain one. A block-local written
48
+ // after a `;` is a `shadowarg` upstream, which fails `arg_type?` -- the grammar spells it
49
+ // as another identifier in the same list and marks it only by the separator.
50
+ let written = super::nodes::children_in(list, context);
48
51
  if written.is_empty() || written.iter().any(|arg| arg.kind_str() != "identifier") {
49
52
  continue;
50
53
  }
54
+ let _cursor = list.walk();
55
+ if all_children_of(list, context)
56
+ .into_iter()
57
+ .any(|child| !child.is_named() && child.kind_str() == ";")
58
+ {
59
+ continue;
60
+ }
51
61
  let names: Vec<&str> = written
52
62
  .iter()
53
63
  .map(|arg| context.source.node_text(*arg))
@@ -116,12 +126,12 @@ fn rename_reads(
116
126
  locals: &LocalVariables<'_, '_>,
117
127
  edits: &mut Vec<Edit>,
118
128
  ) {
119
- let mut stack: Vec<Node<'_>> = super::nodes::children(node)
129
+ let mut stack: Vec<Node<'_>> = super::nodes::children_in(node, context)
120
130
  .into_iter()
121
131
  .filter(|child| child.kind_str() != "block_parameters")
122
132
  .collect();
123
133
  while let Some(current) = stack.pop() {
124
- stack.extend(super::nodes::children(current));
134
+ stack.extend(super::nodes::children_in(current, context));
125
135
  if current.kind_str() != "identifier" || !locals.is_lvar(current) {
126
136
  continue;
127
137
  }
@@ -6,6 +6,8 @@ use crate::diagnostic::{Edit, Offense};
6
6
  use crate::rules::RuleContext;
7
7
  use crate::rules::node_ext::NodeExt;
8
8
  use crate::rules::send_node::heredoc_body;
9
+ use crate::rules::send_node::named_children_of;
10
+ use crate::rules::send_node::all_children_of;
9
11
 
10
12
  const MSG: &str = "Prefer multiline `do`...`end` block.";
11
13
 
@@ -82,14 +84,14 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
82
84
  /// any and after the `do` when it does not.
83
85
  fn do_line(context: &RuleContext<'_>, block: Node<'_>) -> Option<usize> {
84
86
  if let Some(parameters) = block.field("parameters")
85
- && !super::nodes::children(parameters).is_empty()
87
+ && !super::nodes::children_in(parameters, context).is_empty()
86
88
  {
87
89
  return Some(parameters.end_byte());
88
90
  }
89
91
  let _ = context;
90
- let mut cursor = block.walk();
91
- block
92
- .children(&mut cursor)
92
+ let _cursor = block.walk();
93
+ all_children_of(block, context)
94
+ .into_iter()
93
95
  .find(|child| child.kind_str() == "do")
94
96
  .map(|token| token.end_byte())
95
97
  }
@@ -97,8 +99,7 @@ fn do_line(context: &RuleContext<'_>, block: Node<'_>) -> Option<usize> {
97
99
  /// `node.loc.end`.
98
100
  fn end_keyword<'tree>(block: Node<'tree>) -> Option<Node<'tree>> {
99
101
  let mut cursor = block.walk();
100
- block
101
- .children(&mut cursor)
102
+ block.children(&mut cursor)
102
103
  .find(|child| child.kind_str() == "end")
103
104
  }
104
105
 
@@ -110,9 +111,9 @@ fn trailing_heredoc(context: &RuleContext<'_>, block: Node<'_>) -> Option<usize>
110
111
  if node.kind_str() == "heredoc_beginning"
111
112
  && let Some(body) = heredoc_body(node, context)
112
113
  {
113
- let mut cursor = body.walk();
114
- if let Some(terminator) = body
115
- .named_children(&mut cursor)
114
+ let _cursor = body.walk();
115
+ if let Some(terminator) = named_children_of(body, context)
116
+ .into_iter()
116
117
  .find(|child| child.kind_str() == "heredoc_end")
117
118
  {
118
119
  last = Some(last.map_or(terminator.end_byte(), |end: usize| {
@@ -120,7 +121,7 @@ fn trailing_heredoc(context: &RuleContext<'_>, block: Node<'_>) -> Option<usize>
120
121
  }));
121
122
  }
122
123
  }
123
- crate::rules::push_named_children(node, &mut stack);
124
+ crate::rules::push_named_children_in(node, context, &mut stack);
124
125
  }
125
126
  last
126
127
  }
@@ -200,7 +201,7 @@ fn comment_within(context: &RuleContext<'_>, node: Node<'_>) -> bool {
200
201
  /// `safe_to_split?`.
201
202
  fn safe_to_split(context: &RuleContext<'_>, node: Node<'_>) -> bool {
202
203
  let mut stack: Vec<Node<'_>> = Vec::new();
203
- crate::rules::push_named_children(node, &mut stack);
204
+ crate::rules::push_named_children_in(node, context, &mut stack);
204
205
  while let Some(child) = stack.pop() {
205
206
  if UNSAFE_TO_SPLIT.contains(&child.kind_str()) {
206
207
  return false;
@@ -220,7 +221,7 @@ fn safe_to_split(context: &RuleContext<'_>, node: Node<'_>) -> bool {
220
221
  {
221
222
  return false;
222
223
  }
223
- crate::rules::push_named_children(child, &mut stack);
224
+ crate::rules::push_named_children_in(child, context, &mut stack);
224
225
  }
225
226
  true
226
227
  }
@@ -164,7 +164,7 @@ fn endless_correction(context: &RuleContext<'_>, node: Node<'_>, body: &Body) ->
164
164
  .map_or("", |name| context.source.node_text(name));
165
165
  let arguments = node.field("parameters").map_or_else(
166
166
  || "()".to_owned(),
167
- |list| match super::nodes::children(list).is_empty() {
167
+ |list| match super::nodes::children_in(list, context).is_empty() {
168
168
  true => "()".to_owned(),
169
169
  false => context.source.node_text(list).to_owned(),
170
170
  },
@@ -198,7 +198,7 @@ fn method_body_source(context: &RuleContext<'_>, node: Node<'_>, body: &Body) ->
198
198
  let (receiver, name, arguments) = match expression.kind_str() {
199
199
  "call" => {
200
200
  let list = expression.field("arguments")?;
201
- let arguments = super::nodes::children(list);
201
+ let arguments = super::nodes::children_in(list, context);
202
202
  if arguments.is_empty() {
203
203
  return None;
204
204
  }
@@ -62,7 +62,7 @@ struct Slice<'tree> {
62
62
  }
63
63
 
64
64
  impl<'tree> Slice<'tree> {
65
- fn read(node: Node<'tree>, context: &RuleContext<'_>) -> Option<Self> {
65
+ fn read(node: Node<'tree>, context: &'tree RuleContext<'_>) -> Option<Self> {
66
66
  match node.kind_str() {
67
67
  "element_reference" => {
68
68
  // `ary[0..-1] = x` is a call to `:[]=`, which the cop does not restrict itself to.
@@ -70,7 +70,7 @@ impl<'tree> Slice<'tree> {
70
70
  if is_assignment_target(node) {
71
71
  return None;
72
72
  }
73
- let children = super::nodes::children(node);
73
+ let children = super::nodes::children_in(node, context);
74
74
  let [_, argument] = children.as_slice() else {
75
75
  return None;
76
76
  };
@@ -118,7 +118,7 @@ fn use_variable_assignment_in_condition(
118
118
  return false;
119
119
  };
120
120
  let inner_source = context.source.node_text(inner);
121
- super::conditional::descendants(condition)
121
+ super::conditional::descendants(condition, context)
122
122
  .into_iter()
123
123
  .filter(|descendant| descendant.kind_str() == "assignment")
124
124
  .filter_map(|assignment| assignment.field("left"))
@@ -399,7 +399,7 @@ fn add_parentheses(context: &RuleContext<'_>, node: Node<'_>) -> bool {
399
399
  /// `assignment_in_and?`.
400
400
  fn assignment_in_and(context: &RuleContext<'_>, node: Node<'_>) -> bool {
401
401
  is_and(context, node)
402
- && super::conditional::descendants(node)
402
+ && super::conditional::descendants(node, context)
403
403
  .into_iter()
404
404
  .skip(1)
405
405
  .any(|descendant| matches!(descendant.kind_str(), "assignment" | "operator_assignment"))
@@ -413,7 +413,7 @@ fn call_shape(context: &RuleContext<'_>, node: Node<'_>) -> Option<(bool, bool,
413
413
  let arguments = node.field("arguments");
414
414
  let parenthesized =
415
415
  arguments.is_some_and(|list| context.source.node_text(list).starts_with('('));
416
- let any = arguments.is_some_and(|list| !super::nodes::children(list).is_empty());
416
+ let any = arguments.is_some_and(|list| !super::nodes::children_in(list, context).is_empty());
417
417
  Some((any, parenthesized, false))
418
418
  }
419
419
  // `a[0]` is `(send a :[] 0)`: an argument, and no parentheses around it.
@@ -469,7 +469,7 @@ fn parenthesized_method_arguments(context: &RuleContext<'_>, node: Node<'_>) ->
469
469
  return context.source.node_text(node).to_owned();
470
470
  };
471
471
  let call = context.source.slice(node.start_byte()..selector.end_byte());
472
- let first = super::nodes::children(arguments)
472
+ let first = super::nodes::children_in(arguments, context)
473
473
  .first()
474
474
  .map_or(arguments.start_byte(), Node::start_byte);
475
475
  let rest = context.source.slice(first..node.end_byte());
@@ -204,9 +204,9 @@ fn is_lone_begin(context: &RuleContext<'_>, node: Node<'_>) -> bool {
204
204
  // `"#$!"` interpolates without a `begin`: upstream hangs the global off the `dstr` itself.
205
205
  "interpolation" => {
206
206
  context.source.node_text(node).starts_with("#{")
207
- && super::nodes::children(node).len() == 1
207
+ && super::nodes::children_in(node, context).len() == 1
208
208
  }
209
- "parenthesized_statements" => super::nodes::children(node).len() == 1,
209
+ "parenthesized_statements" => super::nodes::children_in(node, context).len() == 1,
210
210
  _ => false,
211
211
  }
212
212
  }
@@ -17,7 +17,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
17
17
  let Some(parameters) = node.field("parameters") else {
18
18
  continue;
19
19
  };
20
- if super::nodes::children(parameters).is_empty() {
20
+ if super::nodes::children_in(parameters, context).is_empty() {
21
21
  continue;
22
22
  }
23
23
  let text = context.source.node_text(parameters);
@@ -7,12 +7,13 @@ use tree_sitter::Node;
7
7
 
8
8
  use super::conditional::descendants;
9
9
  use super::nodes;
10
+ use crate::rules::RuleContext;
10
11
  use crate::rules::node_ext::NodeExt;
11
12
 
12
13
  /// `condition.each_node.any?(&:lvasgn_type?)`: a condition that binds a local cannot move behind
13
14
  /// the body that reads it.
14
- pub(super) fn non_eligible_condition(condition: Node<'_>) -> bool {
15
- descendants(condition).into_iter().any(|node| {
15
+ pub(super) fn non_eligible_condition(condition: Node<'_>, context: &RuleContext<'_>) -> bool {
16
+ descendants(condition, context).into_iter().any(|node| {
16
17
  matches!(node.kind_str(), "assignment" | "operator_assignment")
17
18
  && node.field("left").is_some_and(binds_local)
18
19
  })
@@ -111,6 +111,12 @@ fn convertible(node: Node<'_>, context: &RuleContext<'_>) -> bool {
111
111
  "call" => node
112
112
  .field("method")
113
113
  .is_some_and(|selector| context.source.node_text(selector) == "extend"),
114
+ // A receiverless call with no arguments is a bare name to the grammar and a `send`
115
+ // upstream, which `extend_call?` answers to just the same.
116
+ "identifier" => {
117
+ context.source.node_text(node) == "extend"
118
+ && !context.variable_analysis().is_variable_reference(node)
119
+ }
114
120
  _ => false,
115
121
  }
116
122
  }
@@ -135,7 +141,7 @@ fn is_public(node: Node<'_>, context: &RuleContext<'_>) -> bool {
135
141
  let Some(parent) = node.parent() else {
136
142
  return true;
137
143
  };
138
- let siblings = super::nodes::children(parent);
144
+ let siblings = super::nodes::children_in(parent, context);
139
145
  let Some(position) = siblings.iter().position(|child| child.id() == node.id()) else {
140
146
  return true;
141
147
  };
@@ -10,6 +10,8 @@ use crate::rules::send_node::is_plain_send;
10
10
 
11
11
  use super::literal::{self, Quoting};
12
12
  use crate::rules::node_ext::NodeExt;
13
+ use crate::rules::send_node::all_children_of;
14
+ use crate::rules::send_node::all_children_iter;
13
15
 
14
16
  const MSG: &str = "Prefer string interpolation to string concatenation.";
15
17
 
@@ -240,7 +242,7 @@ fn adjust_str(context: &RuleContext<'_>, node: Node<'_>, encoding: LiteralEncodi
240
242
  },
241
243
  // `'a' 'b'`, `(...)` and `#{...}` are all a list of children upstream.
242
244
  "chained_string" | "parenthesized_statements" | "interpolation" => {
243
- super::nodes::children(node)
245
+ super::nodes::children_in(node, context)
244
246
  .into_iter()
245
247
  .map(|child| adjust_str(context, child, encoding))
246
248
  .collect()
@@ -322,11 +324,11 @@ struct Interpolated<'tree> {
322
324
  /// The children upstream's `dstr` holds, or `None` when the literal has no interpolation and is a
323
325
  /// plain `str`.
324
326
  fn interpolated<'tree>(
325
- context: &RuleContext<'_>,
327
+ context: &'tree RuleContext<'_>,
326
328
  node: Node<'tree>,
327
329
  ) -> Option<Interpolated<'tree>> {
328
- let mut cursor = node.walk();
329
- let children: Vec<Node<'tree>> = node.children(&mut cursor).collect();
330
+ let _cursor = node.walk();
331
+ let children: Vec<Node<'tree>> = all_children_iter(node, context).collect();
330
332
  if !children.iter().any(|child| child.kind_str() == "interpolation") {
331
333
  return None;
332
334
  }
@@ -424,6 +426,9 @@ impl Heredocs {
424
426
  "string" => interpolated(context, node).is_none() && written_on_one_line(context, node),
425
427
  "character" => true,
426
428
  "heredoc_beginning" => self.plain.get(&node.id()).copied().unwrap_or(false),
429
+ // The parser resolves `__FILE__` to the path it stands for, which is a `str`. The
430
+ // grammar leaves it an identifier.
431
+ "identifier" => context.source.node_text(node) == "__FILE__",
427
432
  _ => false,
428
433
  }
429
434
  }
@@ -472,9 +477,9 @@ fn body_is_str(context: &RuleContext<'_>, body: Node<'_>) -> bool {
472
477
  if has_interpolation_descendant(body) {
473
478
  return false;
474
479
  }
475
- let mut cursor = body.walk();
476
- let end = body
477
- .children(&mut cursor)
480
+ let _cursor = body.walk();
481
+ let end = all_children_of(body, context)
482
+ .into_iter()
478
483
  .find(|child| child.kind_str() == "heredoc_end")
479
484
  .map_or(body.end_byte(), |terminator| terminator.start_byte());
480
485
  let text = &context.source.text()[body.start_byte()..end];
@@ -5,6 +5,7 @@ use crate::rules::RuleContext;
5
5
  use crate::rules::node_ext::NodeExt;
6
6
  use crate::rules::ruby_literal;
7
7
  use crate::rules::send_node;
8
+ use crate::rules::send_node::named_children_iter;
8
9
 
9
10
  const MSG: &str = "Prefer symbols instead of strings as hash keys.";
10
11
 
@@ -62,8 +63,8 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
62
63
  /// for the replacement resolves such an escape to a character, which loses the distinction.
63
64
  fn is_valid_encoding(key: Node<'_>, context: &RuleContext<'_>) -> bool {
64
65
  let mut bytes: Vec<u8> = Vec::new();
65
- let mut cursor = key.walk();
66
- for child in key.named_children(&mut cursor) {
66
+ let _cursor = key.walk();
67
+ for child in named_children_iter(key, context) {
67
68
  let text = context.source.node_text(child);
68
69
  match raw_byte(text).filter(|_| child.kind_str() == "escape_sequence") {
69
70
  Some(byte) => bytes.push(byte),