sonicop 26.8.112 → 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 (343) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +11 -11
  3. data/Cargo.toml +4 -4
  4. data/README.ja.md +36 -23
  5. data/README.md +38 -26
  6. data/lib/sonicop/version.rb +1 -1
  7. data/src/config/inheritance.rs +45 -3
  8. data/src/engine.rs +11 -7
  9. data/src/lib.rs +1 -0
  10. data/src/nul_bytes.rs +2 -6
  11. data/src/parser.rs +37 -0
  12. data/src/profile.rs +13 -1
  13. data/src/rules/bundler/duplicated_group.rs +3 -2
  14. data/src/rules/bundler/gem_comment.rs +5 -4
  15. data/src/rules/gemspec/attribute_assignment.rs +4 -3
  16. data/src/rules/gemspec/deprecated_attribute_assignment.rs +7 -6
  17. data/src/rules/gemspec/duplicated_assignment.rs +4 -3
  18. data/src/rules/gemspec/require_mfa.rs +10 -9
  19. data/src/rules/gemspec/required_ruby_version.rs +3 -2
  20. data/src/rules/gemspec/support.rs +3 -2
  21. data/src/rules/layout/array_alignment.rs +1 -2
  22. data/src/rules/layout/block_end_newline.rs +5 -5
  23. data/src/rules/layout/class_structure.rs +5 -3
  24. data/src/rules/layout/closing_heredoc_indentation.rs +4 -3
  25. data/src/rules/layout/closing_parenthesis_indentation.rs +1 -2
  26. data/src/rules/layout/condition_position.rs +3 -2
  27. data/src/rules/layout/def_end_alignment.rs +2 -2
  28. data/src/rules/layout/dot_position.rs +1 -2
  29. data/src/rules/layout/empty_line_after_guard_clause.rs +3 -6
  30. data/src/rules/layout/empty_line_after_multiline_condition.rs +5 -5
  31. data/src/rules/layout/empty_lines_around_access_modifier.rs +2 -3
  32. data/src/rules/layout/empty_lines_around_arguments.rs +4 -4
  33. data/src/rules/layout/empty_lines_around_exception_handling_keywords.rs +1 -2
  34. data/src/rules/layout/empty_lines_around_method_body.rs +3 -2
  35. data/src/rules/layout/end_alignment.rs +2 -2
  36. data/src/rules/layout/extra_spacing.rs +7 -6
  37. data/src/rules/layout/first_array_element_indentation.rs +1 -2
  38. data/src/rules/layout/first_hash_element_indentation.rs +8 -6
  39. data/src/rules/layout/hash_alignment.rs +4 -4
  40. data/src/rules/layout/heredoc_argument_closing_parenthesis.rs +6 -5
  41. data/src/rules/layout/heredoc_indentation.rs +4 -3
  42. data/src/rules/layout/indentation_style.rs +4 -3
  43. data/src/rules/layout/indentation_width.rs +14 -13
  44. data/src/rules/layout/line_continuation_leading_space.rs +4 -3
  45. data/src/rules/layout/line_continuation_spacing.rs +2 -2
  46. data/src/rules/layout/line_length.rs +101 -57
  47. data/src/rules/layout/multiline_assignment_layout.rs +4 -3
  48. data/src/rules/layout/multiline_block_layout.rs +1 -2
  49. data/src/rules/layout/multiline_expression.rs +60 -31
  50. data/src/rules/layout/multiline_hash_brace_layout.rs +4 -3
  51. data/src/rules/layout/multiline_method_call_indentation.rs +1 -1
  52. data/src/rules/layout/multiline_operation_indentation.rs +3 -3
  53. data/src/rules/layout/redundant_line_break.rs +3 -2
  54. data/src/rules/layout/single_line_block_chain.rs +4 -3
  55. data/src/rules/layout/space_around_block_parameters.rs +1 -2
  56. data/src/rules/layout/space_around_equals_in_parameter_default.rs +1 -2
  57. data/src/rules/layout/space_around_operators.rs +1 -2
  58. data/src/rules/layout/space_before_brackets.rs +6 -5
  59. data/src/rules/layout/space_before_comma.rs +3 -2
  60. data/src/rules/layout/space_inside_range_literal.rs +8 -6
  61. data/src/rules/layout/support.rs +9 -7
  62. data/src/rules/layout/tokens.rs +15 -6
  63. data/src/rules/lint/access_modifier.rs +19 -10
  64. data/src/rules/lint/ambiguity.rs +2 -2
  65. data/src/rules/lint/ambiguous_block_association.rs +7 -10
  66. data/src/rules/lint/ambiguous_regexp_literal.rs +7 -5
  67. data/src/rules/lint/array_literal_in_regexp.rs +4 -3
  68. data/src/rules/lint/assignment_in_condition.rs +3 -2
  69. data/src/rules/lint/blocks.rs +2 -1
  70. data/src/rules/lint/boolean_symbol.rs +4 -4
  71. data/src/rules/lint/constant_definition_in_block.rs +1 -2
  72. data/src/rules/lint/constant_reassignment.rs +2 -1
  73. data/src/rules/lint/deprecated_open_ssl_constant.rs +1 -2
  74. data/src/rules/lint/disjunctive_assignment_in_constructor.rs +2 -2
  75. data/src/rules/lint/duplicate_branch.rs +6 -5
  76. data/src/rules/lint/duplicate_case_condition.rs +5 -5
  77. data/src/rules/lint/duplicate_hash_key.rs +4 -3
  78. data/src/rules/lint/duplicate_match_pattern.rs +7 -5
  79. data/src/rules/lint/duplicate_methods.rs +4 -3
  80. data/src/rules/lint/duplicate_require.rs +1 -2
  81. data/src/rules/lint/duplicate_set_element.rs +3 -2
  82. data/src/rules/lint/empty_conditional_body.rs +4 -3
  83. data/src/rules/lint/empty_in_pattern.rs +3 -2
  84. data/src/rules/lint/empty_when.rs +4 -4
  85. data/src/rules/lint/ensure_return.rs +3 -2
  86. data/src/rules/lint/float_comparison.rs +8 -6
  87. data/src/rules/lint/format_parameter_mismatch.rs +4 -3
  88. data/src/rules/lint/hash_compare_by_identity.rs +5 -4
  89. data/src/rules/lint/heredoc_method_call_position.rs +3 -2
  90. data/src/rules/lint/implicit_string_concatenation.rs +7 -5
  91. data/src/rules/lint/ineffective_access_modifier.rs +5 -4
  92. data/src/rules/lint/inherit_exception.rs +4 -3
  93. data/src/rules/lint/literal_as_condition.rs +5 -3
  94. data/src/rules/lint/literal_assignment_in_condition.rs +4 -3
  95. data/src/rules/lint/literal_in_interpolation.rs +5 -5
  96. data/src/rules/lint/literals.rs +10 -8
  97. data/src/rules/lint/no_return_in_begin_end_blocks.rs +2 -2
  98. data/src/rules/lint/node_equality.rs +8 -5
  99. data/src/rules/lint/non_atomic_file_operation.rs +1 -1
  100. data/src/rules/lint/non_deterministic_require_order.rs +5 -3
  101. data/src/rules/lint/non_local_exit_from_iterator.rs +3 -2
  102. data/src/rules/lint/number_conversion.rs +1 -1
  103. data/src/rules/lint/out_of_range_regexp_ref.rs +6 -4
  104. data/src/rules/lint/redundant_dir_glob_sort.rs +1 -2
  105. data/src/rules/lint/redundant_safe_navigation.rs +4 -3
  106. data/src/rules/lint/redundant_splat_expansion.rs +4 -2
  107. data/src/rules/lint/redundant_string_coercion.rs +3 -2
  108. data/src/rules/lint/regexp_source.rs +2 -2
  109. data/src/rules/lint/require_range_parentheses.rs +5 -5
  110. data/src/rules/lint/rescue_exception.rs +4 -3
  111. data/src/rules/lint/rescue_type.rs +3 -2
  112. data/src/rules/lint/return_in_void_context.rs +2 -2
  113. data/src/rules/lint/safe_navigation_chain.rs +3 -2
  114. data/src/rules/lint/self_assignment.rs +3 -2
  115. data/src/rules/lint/shadowed_argument.rs +2 -2
  116. data/src/rules/lint/shadowed_exception.rs +2 -2
  117. data/src/rules/lint/suppressed_exception_in_number_conversion.rs +4 -3
  118. data/src/rules/lint/symbol_conversion.rs +6 -5
  119. data/src/rules/lint/syntax.rs +4 -3
  120. data/src/rules/lint/triple_quotes.rs +3 -2
  121. data/src/rules/lint/unescaped_bracket_in_regexp.rs +1 -1
  122. data/src/rules/lint/unexpected_block_arity.rs +3 -2
  123. data/src/rules/lint/unmodified_reduce_accumulator.rs +7 -6
  124. data/src/rules/lint/unreachable_pattern_branch.rs +4 -4
  125. data/src/rules/lint/unused_block_argument.rs +2 -2
  126. data/src/rules/lint/unused_method_argument.rs +1 -2
  127. data/src/rules/lint/useless_assignment.rs +3 -2
  128. data/src/rules/lint/useless_constant_scoping.rs +3 -2
  129. data/src/rules/lint/useless_else_without_rescue.rs +4 -3
  130. data/src/rules/lint/useless_rescue.rs +5 -4
  131. data/src/rules/lint/useless_ruby2_keywords.rs +2 -1
  132. data/src/rules/lint/useless_setter_call.rs +5 -4
  133. data/src/rules/lint/useless_times.rs +4 -3
  134. data/src/rules/lint/variable_force.rs +146 -100
  135. data/src/rules/lint/void.rs +5 -4
  136. data/src/rules/metrics/block_nesting.rs +1 -2
  137. data/src/rules/metrics/collection_literal_length.rs +2 -1
  138. data/src/rules/metrics/complexity.rs +18 -13
  139. data/src/rules/metrics/fragments.rs +10 -14
  140. data/src/rules/metrics/locals.rs +23 -12
  141. data/src/rules/metrics/parameter_lists.rs +7 -6
  142. data/src/rules/metrics/support.rs +1 -1
  143. data/src/rules/mod.rs +464 -62
  144. data/src/rules/naming/ascii_identifiers.rs +3 -3
  145. data/src/rules/naming/block_forwarding.rs +9 -8
  146. data/src/rules/naming/constant_name.rs +1 -1
  147. data/src/rules/naming/predicate_method.rs +6 -5
  148. data/src/rules/naming/predicate_prefix.rs +10 -8
  149. data/src/rules/naming/rescued_exceptions_variable_name.rs +8 -6
  150. data/src/rules/naming/support.rs +7 -5
  151. data/src/rules/naming/variable_number.rs +4 -4
  152. data/src/rules/node_ext.rs +1 -1
  153. data/src/rules/ruby_literal.rs +3 -2
  154. data/src/rules/security/compound_hash.rs +3 -2
  155. data/src/rules/security/open.rs +5 -4
  156. data/src/rules/send_node.rs +134 -0
  157. data/src/rules/single_line.rs +4 -4
  158. data/src/rules/style/access_modifier_declarations.rs +1 -1
  159. data/src/rules/style/alias.rs +1 -2
  160. data/src/rules/style/and_or.rs +2 -2
  161. data/src/rules/style/arguments_forwarding.rs +5 -5
  162. data/src/rules/style/array_coercion.rs +5 -5
  163. data/src/rules/style/array_first_last.rs +3 -3
  164. data/src/rules/style/array_intersect.rs +2 -2
  165. data/src/rules/style/array_intersect_with_single_element.rs +1 -1
  166. data/src/rules/style/attr.rs +1 -1
  167. data/src/rules/style/auto_resource_cleanup.rs +1 -1
  168. data/src/rules/style/bitwise_predicate.rs +2 -2
  169. data/src/rules/style/block_args.rs +1 -1
  170. data/src/rules/style/block_delimiters.rs +7 -6
  171. data/src/rules/style/case_equality.rs +3 -2
  172. data/src/rules/style/case_like_if.rs +1 -1
  173. data/src/rules/style/class_and_module_children.rs +7 -5
  174. data/src/rules/style/class_methods.rs +1 -1
  175. data/src/rules/style/collection_compact.rs +4 -4
  176. data/src/rules/style/combinable_defined.rs +2 -2
  177. data/src/rules/style/combinable_loops.rs +1 -1
  178. data/src/rules/style/comparable_clamp.rs +2 -2
  179. data/src/rules/style/concat_array_literals.rs +3 -3
  180. data/src/rules/style/conditional.rs +8 -1
  181. data/src/rules/style/constant_visibility.rs +3 -3
  182. data/src/rules/style/copyright.rs +1 -1
  183. data/src/rules/style/data_inheritance.rs +1 -1
  184. data/src/rules/style/def_with_parentheses.rs +1 -1
  185. data/src/rules/style/document_dynamic_eval_definition.rs +1 -1
  186. data/src/rules/style/documentation.rs +1 -1
  187. data/src/rules/style/each_for_simple_loop.rs +1 -1
  188. data/src/rules/style/each_with_object.rs +4 -4
  189. data/src/rules/style/empty_block_parameter.rs +1 -1
  190. data/src/rules/style/empty_case_condition.rs +6 -6
  191. data/src/rules/style/empty_class_definition.rs +1 -1
  192. data/src/rules/style/empty_else.rs +1 -1
  193. data/src/rules/style/empty_heredoc.rs +1 -1
  194. data/src/rules/style/empty_lambda_parameter.rs +1 -1
  195. data/src/rules/style/empty_literal.rs +2 -2
  196. data/src/rules/style/empty_method.rs +1 -1
  197. data/src/rules/style/empty_string_inside_interpolation.rs +1 -1
  198. data/src/rules/style/endless.rs +1 -1
  199. data/src/rules/style/env_home.rs +1 -1
  200. data/src/rules/style/even_odd.rs +1 -1
  201. data/src/rules/style/exact_regexp_match.rs +1 -1
  202. data/src/rules/style/expand_path_arguments.rs +3 -3
  203. data/src/rules/style/explicit_block_argument.rs +3 -3
  204. data/src/rules/style/file_open_call.rs +3 -3
  205. data/src/rules/style/file_write.rs +1 -1
  206. data/src/rules/style/for.rs +1 -1
  207. data/src/rules/style/format_string.rs +2 -2
  208. data/src/rules/style/format_string_token.rs +6 -6
  209. data/src/rules/style/guard_clause.rs +8 -7
  210. data/src/rules/style/hash_as_last_array_item.rs +4 -4
  211. data/src/rules/style/hash_each_methods.rs +1 -1
  212. data/src/rules/style/hash_like_case.rs +8 -8
  213. data/src/rules/style/hash_lookup_method.rs +3 -3
  214. data/src/rules/style/hash_subset.rs +16 -10
  215. data/src/rules/style/hash_syntax.rs +4 -4
  216. data/src/rules/style/hash_transform.rs +20 -18
  217. data/src/rules/style/identical_conditional_branches.rs +4 -5
  218. data/src/rules/style/if_inside_else.rs +3 -3
  219. data/src/rules/style/if_unless_modifier.rs +13 -14
  220. data/src/rules/style/if_with_boolean_literal_branches.rs +1 -1
  221. data/src/rules/style/if_with_semicolon.rs +1 -1
  222. data/src/rules/style/in_pattern_then.rs +2 -2
  223. data/src/rules/style/inverse_methods.rs +2 -2
  224. data/src/rules/style/invertible_unless_condition.rs +3 -3
  225. data/src/rules/style/it_block_parameter.rs +1 -1
  226. data/src/rules/style/keyword_arguments_merging.rs +2 -2
  227. data/src/rules/style/keyword_parameters_order.rs +1 -1
  228. data/src/rules/style/magic_comment_format.rs +3 -2
  229. data/src/rules/style/map_chain.rs +1 -1
  230. data/src/rules/style/map_compact_with_conditional_block.rs +3 -3
  231. data/src/rules/style/map_into_array.rs +7 -7
  232. data/src/rules/style/map_join.rs +3 -3
  233. data/src/rules/style/method_call_with_args_parentheses.rs +7 -7
  234. data/src/rules/style/method_call_without_args_parentheses.rs +2 -2
  235. data/src/rules/style/method_def_parentheses.rs +2 -3
  236. data/src/rules/style/min_max.rs +3 -3
  237. data/src/rules/style/min_max_comparison.rs +2 -2
  238. data/src/rules/style/missing_else.rs +1 -1
  239. data/src/rules/style/missing_respond_to_missing.rs +1 -1
  240. data/src/rules/style/mixin_grouping.rs +1 -1
  241. data/src/rules/style/multiline_block_chain.rs +2 -2
  242. data/src/rules/style/multiline_if_then.rs +1 -1
  243. data/src/rules/style/multiline_in_pattern_then.rs +1 -1
  244. data/src/rules/style/multiline_method_signature.rs +1 -1
  245. data/src/rules/style/multiline_when_then.rs +2 -2
  246. data/src/rules/style/mutable_constant.rs +3 -3
  247. data/src/rules/style/negated_if.rs +2 -2
  248. data/src/rules/style/negated_if_else_condition.rs +2 -2
  249. data/src/rules/style/negated_while.rs +1 -1
  250. data/src/rules/style/negative_array_index.rs +2 -2
  251. data/src/rules/style/nested_modifier.rs +1 -1
  252. data/src/rules/style/nested_parenthesized_calls.rs +2 -2
  253. data/src/rules/style/nested_ternary_operator.rs +2 -2
  254. data/src/rules/style/next.rs +1 -1
  255. data/src/rules/style/nil_lambda.rs +2 -2
  256. data/src/rules/style/nodes.rs +21 -0
  257. data/src/rules/style/non_nil_check.rs +2 -2
  258. data/src/rules/style/numbered_parameters_limit.rs +2 -2
  259. data/src/rules/style/one_line_conditional.rs +4 -4
  260. data/src/rules/style/optional_arguments.rs +1 -1
  261. data/src/rules/style/or_assignment.rs +1 -1
  262. data/src/rules/style/parallel_assignment.rs +9 -10
  263. data/src/rules/style/parens.rs +1 -1
  264. data/src/rules/style/parentheses_around_condition.rs +3 -3
  265. data/src/rules/style/partition_instead_of_double_select.rs +3 -3
  266. data/src/rules/style/percent.rs +9 -7
  267. data/src/rules/style/percent_q_literals.rs +4 -3
  268. data/src/rules/style/preferred_hash_methods.rs +1 -1
  269. data/src/rules/style/random_with_offset.rs +1 -1
  270. data/src/rules/style/reduce_to_hash.rs +6 -6
  271. data/src/rules/style/redundant_argument.rs +1 -1
  272. data/src/rules/style/redundant_assignment.rs +5 -5
  273. data/src/rules/style/redundant_begin.rs +2 -2
  274. data/src/rules/style/redundant_capital_w.rs +3 -2
  275. data/src/rules/style/redundant_condition.rs +9 -7
  276. data/src/rules/style/redundant_current_directory_in_path.rs +2 -2
  277. data/src/rules/style/redundant_double_splat_hash_braces.rs +4 -4
  278. data/src/rules/style/redundant_file_extension_in_require.rs +2 -2
  279. data/src/rules/style/redundant_format.rs +16 -14
  280. data/src/rules/style/redundant_freeze.rs +1 -1
  281. data/src/rules/style/redundant_heredoc_delimiter_quotes.rs +1 -1
  282. data/src/rules/style/redundant_initialize.rs +2 -2
  283. data/src/rules/style/redundant_interpolation.rs +2 -2
  284. data/src/rules/style/redundant_interpolation_unfreeze.rs +2 -2
  285. data/src/rules/style/redundant_line_continuation.rs +5 -4
  286. data/src/rules/style/redundant_parentheses.rs +16 -15
  287. data/src/rules/style/redundant_regexp_argument.rs +1 -1
  288. data/src/rules/style/redundant_regexp_character_class.rs +4 -3
  289. data/src/rules/style/redundant_regexp_escape.rs +3 -2
  290. data/src/rules/style/redundant_return.rs +1 -2
  291. data/src/rules/style/redundant_self.rs +11 -11
  292. data/src/rules/style/redundant_sort.rs +1 -1
  293. data/src/rules/style/redundant_sort_by.rs +1 -1
  294. data/src/rules/style/redundant_string_escape.rs +6 -5
  295. data/src/rules/style/regexp_literal.rs +3 -2
  296. data/src/rules/style/require_order.rs +2 -2
  297. data/src/rules/style/rescue_modifier.rs +1 -1
  298. data/src/rules/style/rescue_standard_error.rs +1 -1
  299. data/src/rules/style/return_nil.rs +2 -2
  300. data/src/rules/style/return_nil_in_predicate_method_definition.rs +2 -2
  301. data/src/rules/style/safe_navigation.rs +2 -2
  302. data/src/rules/style/sample.rs +3 -3
  303. data/src/rules/style/select_by.rs +2 -2
  304. data/src/rules/style/select_by_range.rs +1 -1
  305. data/src/rules/style/semicolon.rs +5 -4
  306. data/src/rules/style/single_argument_dig.rs +1 -1
  307. data/src/rules/style/single_line_block_params.rs +7 -6
  308. data/src/rules/style/single_line_do_end_block.rs +13 -12
  309. data/src/rules/style/single_line_methods.rs +2 -2
  310. data/src/rules/style/slicing_with_range.rs +2 -2
  311. data/src/rules/style/sole_nested_conditional.rs +4 -4
  312. data/src/rules/style/special_global_vars.rs +2 -2
  313. data/src/rules/style/stabby_lambda_parentheses.rs +1 -1
  314. data/src/rules/style/statement_modifier.rs +3 -2
  315. data/src/rules/style/static_class.rs +1 -1
  316. data/src/rules/style/string_concatenation.rs +9 -7
  317. data/src/rules/style/string_hash_keys.rs +3 -2
  318. data/src/rules/style/string_literals.rs +3 -3
  319. data/src/rules/style/string_literals_in_interpolation.rs +1 -1
  320. data/src/rules/style/struct_inheritance.rs +3 -3
  321. data/src/rules/style/super_arguments.rs +2 -2
  322. data/src/rules/style/super_with_args_parentheses.rs +1 -1
  323. data/src/rules/style/swap_values.rs +1 -1
  324. data/src/rules/style/symbol_array.rs +1 -1
  325. data/src/rules/style/symbol_proc.rs +3 -3
  326. data/src/rules/style/tally_method.rs +4 -4
  327. data/src/rules/style/ternary_parentheses.rs +4 -4
  328. data/src/rules/style/top_level_method_definition.rs +1 -1
  329. data/src/rules/style/trailing_body.rs +1 -1
  330. data/src/rules/style/trailing_comma_in_array_literal.rs +1 -1
  331. data/src/rules/style/trailing_comma_in_hash_literal.rs +1 -1
  332. data/src/rules/style/trailing_method_end_statement.rs +1 -1
  333. data/src/rules/style/trailing_underscore_variable.rs +4 -5
  334. data/src/rules/style/trivial_accessors.rs +2 -2
  335. data/src/rules/style/unless_logical_operators.rs +1 -1
  336. data/src/rules/style/unpack_first.rs +1 -1
  337. data/src/rules/style/when_then.rs +4 -4
  338. data/src/rules/style/while_until_do.rs +1 -1
  339. data/src/rules/style/while_until_modifier.rs +3 -3
  340. data/src/rules/style/word_array.rs +3 -3
  341. data/src/rules/style/yoda_condition.rs +2 -2
  342. data/src/rules/support.rs +33 -8
  343. metadata +2 -1
@@ -23,6 +23,7 @@ use super::support::{begins_its_line, character_column, line_indentation};
23
23
  use crate::rules::RuleContext;
24
24
  use crate::rules::lint::locals::LocalVariables;
25
25
  use crate::rules::node_ext::NodeExt;
26
+ use crate::rules::send_node::named_children_iter;
26
27
 
27
28
  /// Which of the nodes upstream's parser builds for one piece of source this stands for.
28
29
  #[derive(Clone, Copy, Eq, PartialEq)]
@@ -44,6 +45,10 @@ enum Role {
44
45
  pub(super) struct UpNode<'tree> {
45
46
  node: Node<'tree>,
46
47
  role: Role,
48
+ /// The file's node index, carried so that climbing to a parent is a lookup rather than a walk
49
+ /// down from the root of the tree. Every step of every chain this cop family follows asks for
50
+ /// one, and `Node::parent` was the largest cost of the three cops together.
51
+ index: &'tree crate::rules::AstIndex<'tree>,
47
52
  }
48
53
 
49
54
  /// The node types the ported code asks about by name.
@@ -114,11 +119,11 @@ fn block_of<'tree>(node: Node<'tree>) -> Option<Node<'tree>> {
114
119
 
115
120
  /// Whether the call was fused into the setter `send` its assignment builds, which leaves it
116
121
  /// without a node of its own upstream.
117
- fn is_fused_setter_target(node: Node<'_>) -> bool {
122
+ fn is_fused_setter_target(node: Node<'_>, index: &crate::rules::AstIndex<'_>) -> bool {
118
123
  if !matches!(node.kind_str(), "call" | "element_reference") {
119
124
  return false;
120
125
  }
121
- node.parent().is_some_and(|parent| {
126
+ index.parent(node).is_some_and(|parent| {
122
127
  parent.kind_str() == "assignment"
123
128
  && parent
124
129
  .field("left")
@@ -129,19 +134,20 @@ fn is_fused_setter_target(node: Node<'_>) -> bool {
129
134
  impl<'tree> UpNode<'tree> {
130
135
  /// The node a reference from elsewhere -- a receiver, an argument -- resolves to. A call
131
136
  /// written with a block is the `block` node there.
132
- pub(super) fn of(node: Node<'tree>) -> Self {
137
+ pub(super) fn of(node: Node<'tree>, index: &'tree crate::rules::AstIndex<'tree>) -> Self {
133
138
  let role = if block_of(node).is_some() {
134
139
  Role::Block
135
140
  } else {
136
141
  Role::Plain
137
142
  };
138
- Self { node, role }
143
+ Self { node, role, index }
139
144
  }
140
145
 
141
- pub(super) fn plain(node: Node<'tree>) -> Self {
146
+ pub(super) fn plain(node: Node<'tree>, index: &'tree crate::rules::AstIndex<'tree>) -> Self {
142
147
  Self {
143
148
  node,
144
149
  role: Role::Plain,
150
+ index,
145
151
  }
146
152
  }
147
153
 
@@ -226,13 +232,19 @@ impl<'tree> UpNode<'tree> {
226
232
  return None;
227
233
  }
228
234
  match node.kind_str() {
229
- "call" | "method_call" => node.field("receiver").map(Self::of),
230
- "element_reference" => node.field("object").map(Self::of),
231
- "binary" => node.field("left").map(Self::of),
232
- "unary" => node.field("operand").map(Self::of),
235
+ "call" | "method_call" => node
236
+ .field("receiver")
237
+ .map(|receiver| Self::of(receiver, self.index)),
238
+ "element_reference" => node
239
+ .field("object")
240
+ .map(|object| Self::of(object, self.index)),
241
+ "binary" => node.field("left").map(|left| Self::of(left, self.index)),
242
+ "unary" => node
243
+ .field("operand")
244
+ .map(|operand| Self::of(operand, self.index)),
233
245
  "assignment" => {
234
246
  let left = node.field("left")?;
235
- Self::plain(left).receiver(context)
247
+ Self::plain(left, self.index).receiver(context)
236
248
  }
237
249
  _ => None,
238
250
  }
@@ -342,29 +354,29 @@ impl<'tree> UpNode<'tree> {
342
354
  }
343
355
  match self.node.kind_str() {
344
356
  "call" | "method_call" => match self.node.field("arguments") {
345
- Some(list) => fold_arguments(list, &children_of(list)),
357
+ Some(list) => fold_arguments(list, &children_of(list), self.index),
346
358
  None => Vec::new(),
347
359
  },
348
360
  "element_reference" => {
349
361
  let indices: Vec<Node<'tree>> =
350
362
  children_of(self.node).into_iter().skip(1).collect();
351
- fold_arguments(self.node, &indices)
363
+ fold_arguments(self.node, &indices, self.index)
352
364
  }
353
365
  "binary" if plain_kind(context, self.node) == UpKind::Send => self
354
366
  .node
355
367
  .field("right")
356
- .map_or_else(Vec::new, |right| vec![Self::of(right)]),
368
+ .map_or_else(Vec::new, |right| vec![Self::of(right, self.index)]),
357
369
  "assignment" => {
358
370
  let Some(left) = self.node.field("left") else {
359
371
  return Vec::new();
360
372
  };
361
373
  let mut arguments = if left.kind_str() == "element_reference" {
362
- Self::plain(left).arguments(context)
374
+ Self::plain(left, self.index).arguments(context)
363
375
  } else {
364
376
  Vec::new()
365
377
  };
366
378
  if let Some(right) = self.node.field("right") {
367
- arguments.push(Self::of(right));
379
+ arguments.push(Self::of(right, self.index));
368
380
  }
369
381
  arguments
370
382
  }
@@ -385,13 +397,14 @@ impl<'tree> UpNode<'tree> {
385
397
  (self.role == Role::Plain && block_of(self.node).is_some()).then_some(Self {
386
398
  node: self.node,
387
399
  role: Role::Block,
400
+ index: self.index,
388
401
  })
389
402
  }
390
403
 
391
404
  /// `BlockNode#send_node`.
392
405
  pub(super) fn send_node(self) -> Self {
393
406
  match self.role {
394
- Role::Block => Self::plain(self.node),
407
+ Role::Block => Self::plain(self.node, self.index),
395
408
  _ => self,
396
409
  }
397
410
  }
@@ -431,7 +444,7 @@ impl<'tree> UpNode<'tree> {
431
444
 
432
445
  /// Whether the call was fused into the setter send its assignment builds.
433
446
  pub(super) fn is_fused_setter_target(self) -> bool {
434
- self.role == Role::Plain && is_fused_setter_target(self.node)
447
+ self.role == Role::Plain && is_fused_setter_target(self.node, self.index)
435
448
  }
436
449
 
437
450
  /// One of the node's own fields, for the few places the ported code reads a part the model
@@ -449,6 +462,7 @@ impl<'tree> UpNode<'tree> {
449
462
  return Some(Self {
450
463
  node,
451
464
  role: Role::Block,
465
+ index: self.index,
452
466
  });
453
467
  }
454
468
  let mut cursor = node.walk();
@@ -461,12 +475,12 @@ impl<'tree> UpNode<'tree> {
461
475
 
462
476
  pub(super) fn parent(self) -> Option<Self> {
463
477
  match self.role {
464
- Role::Block | Role::Array => raw_parent(self.node),
478
+ Role::Block | Role::Array => raw_parent(self.node, self.index),
465
479
  Role::Hash => {
466
480
  if self.node.kind_str() == "argument_list" {
467
- raw_parent(self.node)
481
+ raw_parent(self.node, self.index)
468
482
  } else {
469
- Some(Self::plain(self.node))
483
+ Some(Self::plain(self.node, self.index))
470
484
  }
471
485
  }
472
486
  Role::Plain => {
@@ -474,15 +488,17 @@ impl<'tree> UpNode<'tree> {
474
488
  return Some(Self {
475
489
  node: self.node,
476
490
  role: Role::Block,
491
+ index: self.index,
477
492
  });
478
493
  }
479
494
  if is_lone_assigned_splat(self.node) {
480
495
  return Some(Self {
481
496
  node: self.node,
482
497
  role: Role::Array,
498
+ index: self.index,
483
499
  });
484
500
  }
485
- raw_parent(self.node)
501
+ raw_parent(self.node, self.index)
486
502
  }
487
503
  }
488
504
  }
@@ -523,7 +539,11 @@ fn children_of<'tree>(node: Node<'tree>) -> Vec<Node<'tree>> {
523
539
  }
524
540
 
525
541
  /// Groups a run of `key: value` pairs into the one `hash` argument upstream's parser folds it into.
526
- fn fold_arguments<'tree>(container: Node<'tree>, children: &[Node<'tree>]) -> Vec<UpNode<'tree>> {
542
+ fn fold_arguments<'tree>(
543
+ container: Node<'tree>,
544
+ children: &[Node<'tree>],
545
+ own_index: &'tree crate::rules::AstIndex<'tree>,
546
+ ) -> Vec<UpNode<'tree>> {
527
547
  let mut arguments = Vec::new();
528
548
  let mut index = 0;
529
549
  while index < children.len() {
@@ -534,10 +554,11 @@ fn fold_arguments<'tree>(container: Node<'tree>, children: &[Node<'tree>]) -> Ve
534
554
  arguments.push(UpNode {
535
555
  node: container,
536
556
  role: Role::Hash,
557
+ index: own_index,
537
558
  });
538
559
  continue;
539
560
  }
540
- arguments.push(UpNode::of(children[index]));
561
+ arguments.push(UpNode::of(children[index], own_index));
541
562
  index += 1;
542
563
  }
543
564
  arguments
@@ -563,32 +584,37 @@ fn hash_run_range(container: Node<'_>) -> Range<usize> {
563
584
 
564
585
  /// Climbs to the node upstream would call this one's parent, stepping over the containers the
565
586
  /// parser has no node for.
566
- fn raw_parent<'tree>(node: Node<'tree>) -> Option<UpNode<'tree>> {
587
+ fn raw_parent<'tree>(
588
+ node: Node<'tree>,
589
+ index: &'tree crate::rules::AstIndex<'tree>,
590
+ ) -> Option<UpNode<'tree>> {
567
591
  let mut current = node;
568
592
  loop {
569
- let parent = current.parent()?;
593
+ let parent = index.parent_in_tree(current)?;
570
594
  if parent.kind_str() == "block" || parent.kind_str() == "do_block" {
571
595
  // The body of a literal block hangs off the `block` node upstream, whose own parent is
572
596
  // the parent of the call the block was written on.
573
597
  return Some(UpNode {
574
- node: parent.parent()?,
598
+ node: index.parent_in_tree(parent)?,
575
599
  role: Role::Block,
600
+ index,
576
601
  });
577
602
  }
578
603
  if is_hash_element(current) && HASH_CONTAINERS.contains(&parent.kind_str()) {
579
604
  return Some(UpNode {
580
605
  node: parent,
581
606
  role: Role::Hash,
607
+ index,
582
608
  });
583
609
  }
584
610
  if TRANSPARENT.contains(&parent.kind_str())
585
- || is_fused_setter_target(parent)
611
+ || is_fused_setter_target(parent, index)
586
612
  || is_defined_parentheses(parent)
587
613
  {
588
614
  current = parent;
589
615
  continue;
590
616
  }
591
- return Some(UpNode::plain(parent));
617
+ return Some(UpNode::plain(parent, index));
592
618
  }
593
619
  }
594
620
 
@@ -974,7 +1000,10 @@ impl<'tree> Mixin<'_, 'tree> {
974
1000
  pub(super) fn assignment_rhs(&self, node: UpNode<'tree>) -> Option<UpNode<'tree>> {
975
1001
  match node.kind(self.context) {
976
1002
  UpKind::Send | UpKind::Csend => node.last_argument(self.context),
977
- _ => node.node.field("right").map(UpNode::of),
1003
+ _ => node
1004
+ .node
1005
+ .field("right")
1006
+ .map(|right| UpNode::of(right, node.index)),
978
1007
  }
979
1008
  }
980
1009
 
@@ -1046,8 +1075,8 @@ fn is_numbered_block(context: &RuleContext<'_>, call: Node<'_>) -> bool {
1046
1075
  if matches!(node.kind_str(), "block" | "do_block") {
1047
1076
  continue;
1048
1077
  }
1049
- let mut cursor = node.walk();
1050
- stack.extend(node.named_children(&mut cursor));
1078
+ let _cursor = node.walk();
1079
+ stack.extend(named_children_iter(node, context));
1051
1080
  }
1052
1081
  false
1053
1082
  }
@@ -4,6 +4,7 @@ use super::multiline_brace::{Literal, Messages, check_brace_layout, delimiters};
4
4
  use crate::diagnostic::Offense;
5
5
  use crate::rules::RuleContext;
6
6
  use crate::rules::node_ext::NodeExt;
7
+ use crate::rules::send_node::named_children_of;
7
8
 
8
9
  const MESSAGES: Messages = Messages {
9
10
  same_line: "Closing hash brace must be on the same line as the last hash element when opening \
@@ -21,10 +22,10 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
21
22
  let Some((open, close)) = delimiters(node, &["{"]) else {
22
23
  continue;
23
24
  };
24
- let mut cursor = node.walk();
25
+ let _cursor = node.walk();
25
26
  // Each pair is a child of its own here: only a *brace-less* run folds into one `hash`.
26
- let elements = node
27
- .named_children(&mut cursor)
27
+ let elements = named_children_of(node, context)
28
+ .into_iter()
28
29
  .filter(|child| !matches!(child.kind_str(), "comment" | "heredoc_body"))
29
30
  .map(|child| vec![child])
30
31
  .collect();
@@ -38,7 +38,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
38
38
  };
39
39
 
40
40
  for ts in context.nodes_of_any(&["call", "method_call", "assignment"]) {
41
- let node = UpNode::plain(ts);
41
+ let node = UpNode::plain(ts, context.ast_index());
42
42
  if !matches!(node.kind(context), UpKind::Send | UpKind::Csend) {
43
43
  continue;
44
44
  }
@@ -15,13 +15,13 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
15
15
  .is_none_or(|style| style == "aligned");
16
16
 
17
17
  for ts in context.nodes_of_any(&["binary", "assignment"]) {
18
- let node = UpNode::plain(ts);
18
+ let node = UpNode::plain(ts, context.ast_index());
19
19
  let checked = match node.kind(context) {
20
20
  // `on_and` / `on_or`.
21
21
  UpKind::And | UpKind::Or => {
22
22
  let (Some(lhs), Some(rhs)) = (
23
- ts.field("left").map(UpNode::of),
24
- ts.field("right").map(UpNode::of),
23
+ ts.field("left").map(|left| UpNode::of(left, context.ast_index())),
24
+ ts.field("right").map(|right| UpNode::of(right, context.ast_index())),
25
25
  ) else {
26
26
  continue;
27
27
  };
@@ -5,11 +5,12 @@ 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::{is_plain_send, named_children, send_range};
8
+ use crate::rules::send_node::{is_plain_send, send_range};
9
9
  use crate::rules::single_line::{
10
10
  descendants, is_suitable_as_single_line, last_byte, max_line_length, to_single_line,
11
11
  };
12
12
  use crate::rules::support::{Verification, verified_by_reparse};
13
+ use crate::rules::send_node::named_children_of;
13
14
 
14
15
  const MSG: &str = "Redundant line break detected.";
15
16
 
@@ -293,7 +294,7 @@ fn is_top(node: Node<'_>, context: &RuleContext<'_>) -> bool {
293
294
  /// an `or` is ever inspected.
294
295
  fn spine_has_send(node: Node<'_>, context: &RuleContext<'_>) -> bool {
295
296
  if is_logical(node, context) {
296
- return named_children(node)
297
+ return named_children_of(node, context)
297
298
  .into_iter()
298
299
  .any(|child| spine_has_send(child, context));
299
300
  }
@@ -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::all_children_iter;
6
7
 
7
8
  const MSG: &str = "Put method call on a separate line if chained to a single line block.";
8
9
 
@@ -73,10 +74,10 @@ fn block_of<'tree>(receiver: Node<'tree>) -> Option<Node<'tree>> {
73
74
  fn dot_of<'tree>(
74
75
  node: Node<'tree>,
75
76
  receiver: Node<'_>,
76
- context: &RuleContext<'_>,
77
+ context: &'tree RuleContext<'_>,
77
78
  ) -> Option<Node<'tree>> {
78
- let mut cursor = node.walk();
79
- node.children(&mut cursor)
79
+ let _cursor = node.walk();
80
+ all_children_iter(node, context)
80
81
  .filter(|child| !child.is_named() && child.start_byte() >= receiver.end_byte())
81
82
  .find(|child| DOTS.contains(&context.source.node_text(*child)))
82
83
  }
@@ -194,8 +194,7 @@ fn delimiters<'tree>(parameters: Node<'tree>) -> (Option<Node<'tree>>, Option<No
194
194
 
195
195
  fn block_arguments<'tree>(parameters: Node<'tree>) -> Vec<Node<'tree>> {
196
196
  let mut cursor = parameters.walk();
197
- parameters
198
- .named_children(&mut cursor)
197
+ parameters.named_children(&mut cursor)
199
198
  .filter(|child| !matches!(child.kind_str(), "comment" | "heredoc_body"))
200
199
  .collect()
201
200
  }
@@ -65,8 +65,7 @@ fn optional_parameters(name: Node<'_>, value: Node<'_>) -> Vec<(usize, usize)> {
65
65
  .filter(|left| left.kind_str() == "left_assignment_list")
66
66
  .and_then(|left| {
67
67
  let mut cursor = left.walk();
68
- let parts: Vec<Node<'_>> = left
69
- .named_children(&mut cursor)
68
+ let parts: Vec<Node<'_>> = left.named_children(&mut cursor)
70
69
  .filter(|part| !matches!(part.kind_str(), "comment" | "heredoc_body"))
71
70
  .collect();
72
71
  match (parts.len() == 2, value.field("right")) {
@@ -407,8 +407,7 @@ fn pairs_on_same_line(pair: Node<'_>) -> bool {
407
407
  return false;
408
408
  };
409
409
  let mut cursor = parent.walk();
410
- let lines: Vec<usize> = parent
411
- .named_children(&mut cursor)
410
+ let lines: Vec<usize> = parent.named_children(&mut cursor)
412
411
  .filter(|child| child.kind_str() == "pair")
413
412
  .map(|child| child.start_position().row)
414
413
  .collect();
@@ -3,7 +3,8 @@ 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;
6
+ use crate::rules::send_node::named_children_of;
7
+ use crate::rules::send_node::all_children_iter;
7
8
 
8
9
  const MSG: &str = "Remove the space before the opening brackets.";
9
10
 
@@ -56,7 +57,7 @@ fn index_gap(node: Node<'_>, context: &RuleContext<'_>) -> Option<(usize, usize)
56
57
  return None;
57
58
  }
58
59
  let arguments = node.field("arguments")?;
59
- let children = named_children(arguments);
60
+ let children = named_children_of(arguments, context);
60
61
  let [argument] = children.as_slice() else {
61
62
  return None;
62
63
  };
@@ -72,8 +73,8 @@ fn index_gap(node: Node<'_>, context: &RuleContext<'_>) -> Option<(usize, usize)
72
73
  }
73
74
 
74
75
  /// `node.loc.selector`, which for an index read begins at the `[`.
75
- fn opening_bracket<'tree>(node: Node<'tree>, context: &RuleContext<'_>) -> Option<Node<'tree>> {
76
- let mut cursor = node.walk();
77
- node.children(&mut cursor)
76
+ fn opening_bracket<'tree>(node: Node<'tree>, context: &'tree RuleContext<'_>) -> Option<Node<'tree>> {
77
+ let _cursor = node.walk();
78
+ all_children_iter(node, context)
78
79
  .find(|child| !child.is_named() && context.source.node_text(*child) == "[")
79
80
  }
@@ -4,6 +4,7 @@ use super::support::whitespace_before;
4
4
  use crate::diagnostic::{Edit, Offense};
5
5
  use crate::rules::RuleContext;
6
6
  use crate::rules::node_ext::NodeExt;
7
+ use crate::rules::send_node::all_children_iter;
7
8
 
8
9
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
9
10
  let text = context.source.text();
@@ -17,8 +18,8 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
17
18
  let block_braces: Vec<usize> = context
18
19
  .nodes_of("block")
19
20
  .filter_map(|node| {
20
- let mut cursor = node.walk();
21
- node.children(&mut cursor)
21
+ let _cursor = node.walk();
22
+ all_children_iter(node, context)
22
23
  .find(|child| child.kind_str() == "{")
23
24
  .map(|child| child.start_byte())
24
25
  })
@@ -5,6 +5,8 @@ use tree_sitter::Node;
5
5
  use crate::diagnostic::{Edit, Offense};
6
6
  use crate::rules::RuleContext;
7
7
  use crate::rules::node_ext::NodeExt;
8
+ use crate::rules::send_node::named_children_of;
9
+ use crate::rules::send_node::all_children_of;
8
10
 
9
11
  const MSG: &str = "Space inside range literal.";
10
12
 
@@ -14,9 +16,9 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
14
16
  if is_flip_flop(context, node) {
15
17
  continue;
16
18
  }
17
- let mut cursor = node.walk();
18
- let Some(operator) = node
19
- .children(&mut cursor)
19
+ let _cursor = node.walk();
20
+ let Some(operator) = all_children_of(node, context)
21
+ .into_iter()
20
22
  .find(|child| matches!(child.kind_str(), ".." | "..."))
21
23
  else {
22
24
  continue;
@@ -83,9 +85,9 @@ fn is_flip_flop(context: &RuleContext<'_>, node: Node<'_>) -> bool {
83
85
  while let Some(parent) = current.parent_of(context) {
84
86
  match parent.kind_str() {
85
87
  "parenthesized_statements" => {
86
- let mut cursor = parent.walk();
87
- if parent
88
- .named_children(&mut cursor)
88
+ let _cursor = parent.walk();
89
+ if named_children_of(parent, context)
90
+ .into_iter()
89
91
  .filter(|child| !matches!(child.kind_str(), "comment" | "heredoc_body"))
90
92
  .count()
91
93
  != 1
@@ -8,6 +8,8 @@ use tree_sitter::Node;
8
8
  use crate::diagnostic::{Edit, Offense};
9
9
  use crate::rules::RuleContext;
10
10
  use crate::rules::node_ext::NodeExt;
11
+ use crate::rules::send_node::all_children_of;
12
+ use crate::rules::send_node::named_children_of;
11
13
  pub(super) use crate::rules::support::final_pos;
12
14
 
13
15
  /// The run of spaces and tabs ending at `offset`.
@@ -518,13 +520,13 @@ pub(super) fn holds_block_comment(context: &RuleContext<'_>, expr: &Range<usize>
518
520
  /// This is `each_argument_node`, which walks each argument's subtree but stops at anything that is
519
521
  /// a method call upstream -- so a literal nested inside another call belongs to that call instead.
520
522
  pub(super) fn argument_literals<'tree>(
521
- context: &RuleContext<'_>,
523
+ context: &'tree RuleContext<'_>,
522
524
  call: Node<'tree>,
523
525
  kinds: &[&str],
524
526
  ) -> Vec<(Node<'tree>, Node<'tree>)> {
525
- let mut cursor = call.walk();
526
- let Some(list) = call
527
- .children(&mut cursor)
527
+ let _cursor = call.walk();
528
+ let Some(list) = all_children_of(call, context)
529
+ .into_iter()
528
530
  .find(|child| child.kind_str() == "argument_list")
529
531
  else {
530
532
  return Vec::new();
@@ -893,9 +895,9 @@ pub(super) fn heredoc_terminators(context: &RuleContext<'_>) -> Vec<(usize, Rang
893
895
  .enumerate()
894
896
  .filter_map(|(index, body)| {
895
897
  let opener = *openers.get(index)?;
896
- let mut cursor = body.walk();
897
- let terminator = body
898
- .named_children(&mut cursor)
898
+ let _cursor = body.walk();
899
+ let terminator = named_children_of(body, context)
900
+ .into_iter()
899
901
  .find(|child| child.kind_str() == "heredoc_end")?;
900
902
  Some((opener, terminator.byte_range()))
901
903
  })
@@ -182,8 +182,7 @@ impl Builder<'_, '_> {
182
182
  self.push(node);
183
183
  return;
184
184
  }
185
- let mut cursor = node.walk();
186
- for child in node.children(&mut cursor) {
185
+ for child in children_of(self.context, node) {
187
186
  self.walk(child);
188
187
  }
189
188
  }
@@ -208,8 +207,7 @@ impl Builder<'_, '_> {
208
207
  return;
209
208
  }
210
209
  let mut offset = from;
211
- let mut cursor = node.walk();
212
- for child in node.children(&mut cursor) {
210
+ for child in children_of(self.context, node) {
213
211
  if child.end_byte() <= from {
214
212
  continue;
215
213
  }
@@ -229,8 +227,7 @@ impl Builder<'_, '_> {
229
227
 
230
228
  /// The two delimiters of an empty percent literal, emitted as neighbours.
231
229
  fn emit_delimiters(&mut self, node: Node<'_>, from: usize) {
232
- let mut cursor = node.walk();
233
- for child in node.children(&mut cursor) {
230
+ for child in children_of(self.context, node) {
234
231
  if child.end_byte() <= from {
235
232
  continue;
236
233
  }
@@ -387,3 +384,15 @@ fn opens_a_command_argument(node: Node<'_>) -> bool {
387
384
  .and_then(|list| list.parent())
388
385
  .is_some_and(|parent| parent.kind_str() == "call")
389
386
  }
387
+
388
+ /// A node's children, from the file's index. Collected rather than iterated because the walk
389
+ /// takes `&mut self`, and a list of nodes is far cheaper than the cursor walk it replaces.
390
+ fn children_of<'node>(context: &'node RuleContext<'_>, node: Node<'node>) -> Vec<Node<'node>> {
391
+ match context.children(node) {
392
+ Some(children) => children.collect(),
393
+ None => {
394
+ let mut cursor = node.walk();
395
+ node.children(&mut cursor).collect()
396
+ }
397
+ }
398
+ }
@@ -10,6 +10,8 @@ use tree_sitter::Node;
10
10
 
11
11
  use crate::rules::RuleContext;
12
12
  use crate::rules::node_ext::NodeExt;
13
+ use crate::rules::send_node::named_children_iter;
14
+ use crate::rules::send_node::named_children_of;
13
15
 
14
16
  /// The names `bare_access_modifier_declaration?` matches, interned so a visibility can be carried
15
17
  /// around as a `&'static str` rather than borrowed from the source it was read out of.
@@ -33,7 +35,7 @@ pub(in crate::rules) fn send_name<'a>(
33
35
  context: &'a RuleContext<'_>,
34
36
  ) -> Option<&'a str> {
35
37
  match node.kind_str() {
36
- "identifier" if is_send_identifier(node) => Some(context.source.node_text(node)),
38
+ "identifier" if is_send_identifier(node, context) => Some(context.source.node_text(node)),
37
39
  "call" if node.field("block").is_none() => {
38
40
  let method = node.field("method")?;
39
41
  crate::rules::send_node::is_plain_send(node, context)
@@ -76,11 +78,11 @@ pub(super) fn bare_access_modifier(
76
78
  /// What this cannot tell is a read of a local variable, which is an `lvar` upstream and a plain
77
79
  /// identifier here as well. Only a file that assigned `private` (or one of its siblings) to a
78
80
  /// local could be misread, and the name would have to be one of five.
79
- fn is_send_identifier(node: Node<'_>) -> bool {
80
- let Some(parent) = node.parent() else {
81
+ fn is_send_identifier(node: Node<'_>, context: &RuleContext<'_>) -> bool {
82
+ let Some(parent) = context.parent(node) else {
81
83
  return true;
82
84
  };
83
- let field = field_name(node, parent);
85
+ let field = field_name(node, parent, context.ast_index());
84
86
  match parent.kind_str() {
85
87
  "call" => field != Some("method"),
86
88
  "method" | "singleton_method" => field != Some("name"),
@@ -106,7 +108,14 @@ fn is_send_identifier(node: Node<'_>) -> bool {
106
108
  }
107
109
  }
108
110
 
109
- fn field_name<'tree>(node: Node<'tree>, parent: Node<'tree>) -> Option<&'static str> {
111
+ fn field_name<'tree>(
112
+ node: Node<'tree>,
113
+ parent: Node<'tree>,
114
+ index: &super::super::AstIndex<'_>,
115
+ ) -> Option<&'static str> {
116
+ if index.knows(node) {
117
+ return index.field_name_of(node);
118
+ }
110
119
  let mut cursor = parent.walk();
111
120
  if !cursor.goto_first_child() {
112
121
  return None;
@@ -193,9 +202,9 @@ fn branch_repeats_condition(
193
202
  context: &RuleContext<'_>,
194
203
  ) -> bool {
195
204
  let text = context.source.node_text(condition);
196
- let mut cursor = conditional.walk();
197
- let branches: Vec<Node<'_>> = conditional
198
- .named_children(&mut cursor)
205
+ let _cursor = conditional.walk();
206
+ let branches: Vec<Node<'_>> = named_children_of(conditional, context)
207
+ .into_iter()
199
208
  .filter(|child| child.id() != condition.id())
200
209
  .collect();
201
210
  branches.into_iter().any(|branch| {
@@ -203,8 +212,8 @@ fn branch_repeats_condition(
203
212
  // statement is that statement, and one holding several is a `begin` no condition matches.
204
213
  let branch = match branch.kind_str() {
205
214
  "then" | "else" => {
206
- let mut cursor = branch.walk();
207
- let mut children = branch.named_children(&mut cursor);
215
+ let _cursor = branch.walk();
216
+ let mut children = named_children_iter(branch, context);
208
217
  match (children.next(), children.next()) {
209
218
  (Some(only), None) => only,
210
219
  _ => return false,