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
@@ -136,28 +136,31 @@ pub(in crate::rules) struct Scope<'tree> {
136
136
  }
137
137
 
138
138
  pub(in crate::rules) struct Analysis<'tree> {
139
+ /// The file's node index, which answers a parent with one hash lookup. `Node::parent` walks
140
+ /// down from the root, and the force asks for a parent on nearly every node it visits.
141
+ index: &'tree super::super::AstIndex<'tree>,
139
142
  /// Every scope, in the order they were left, which is the order the cops report in.
140
143
  pub scopes: Vec<Scope<'tree>>,
141
144
  pub variables: Vec<Variable<'tree>>,
142
145
  /// The identifiers that resolved to a local variable. tree-sitter cannot tell a read of a
143
146
  /// local from a receiverless call, and only the analysis knows which one the parser upstream
144
147
  /// would have built.
145
- lvars: HashSet<usize>,
148
+ lvars: crate::rules::IdSet,
146
149
  /// The references Naming handlers see. This includes implicit numbered block parameters,
147
150
  /// which are local-variable reads but are not declarations VariableForce reports.
148
- naming_references: HashSet<usize>,
151
+ naming_references: crate::rules::IdSet,
149
152
  /// The `foo()` calls whose name a local variable already holds. The parentheses make these
150
153
  /// calls whatever the name resolves to, so nothing here is an `lvar` -- but writing the same
151
154
  /// name without them would be one, which is the question a cop that drops parentheses asks.
152
- shadowed_calls: HashSet<usize>,
155
+ shadowed_calls: crate::rules::IdSet,
153
156
  /// Receiverless call-name nodes whose spelling is already held by a local variable. Unlike
154
157
  /// `shadowed_calls`, this also records calls with arguments for syntax recovery such as
155
158
  /// `collection [0]`.
156
- local_method_names: HashSet<usize>,
159
+ local_method_names: crate::rules::IdSet,
157
160
  /// The identifier nodes that RuboCop dispatches as variable definitions. Pattern bindings and
158
161
  /// explicit block-local variables deliberately stay out: upstream names them `match_var` and
159
162
  /// `shadowarg`, neither of which the Naming cops handle.
160
- naming_definitions: HashSet<usize>,
163
+ naming_definitions: crate::rules::IdSet,
161
164
  }
162
165
 
163
166
  impl<'tree> Analysis<'tree> {
@@ -193,7 +196,7 @@ impl<'tree> Analysis<'tree> {
193
196
  /// declarations are naming definitions too, but an AST walk restricted to `lvasgn` does not
194
197
  /// visit them.
195
198
  pub(in crate::rules) fn is_local_assignment(&self, node: Node<'_>) -> bool {
196
- self.is_naming_definition(node) && structural_definition(node)
199
+ self.is_naming_definition(node) && structural_definition(node, self.index)
197
200
  }
198
201
 
199
202
  /// Whether the parser would dispatch this node to a Naming variable-definition handler.
@@ -204,33 +207,39 @@ impl<'tree> Analysis<'tree> {
204
207
  matches!(
205
208
  node.kind_str(),
206
209
  "instance_variable" | "class_variable" | "global_variable"
207
- ) && structural_definition(node)
210
+ ) && structural_definition(node, self.index)
208
211
  }
209
212
 
210
213
  pub(in crate::rules) fn is_definition(&self, node: Node<'_>) -> bool {
211
214
  self.is_naming_definition(node)
212
215
  }
213
216
 
214
- pub(in crate::rules) fn run(root: Node<'tree>, source: &SourceFile) -> Self {
217
+ pub(in crate::rules) fn run(
218
+ index: &'tree super::super::AstIndex<'tree>,
219
+ source: &SourceFile,
220
+ ) -> Self {
221
+ let root = index.root_node();
215
222
  let mut force = Force {
223
+ index,
216
224
  source,
217
225
  scopes: Vec::new(),
218
226
  variables: Vec::new(),
219
227
  stack: Vec::new(),
220
228
  branches: Vec::new(),
221
229
  branch_index: HashMap::new(),
222
- heredocs: heredoc_bodies(root),
223
- scanned: HashSet::new(),
224
- lvars: HashSet::new(),
225
- naming_references: HashSet::new(),
226
- shadowed_calls: HashSet::new(),
227
- local_method_names: HashSet::new(),
228
- naming_definitions: HashSet::new(),
230
+ heredocs: heredoc_bodies(index),
231
+ scanned: crate::rules::IdSet::default(),
232
+ lvars: crate::rules::IdSet::default(),
233
+ naming_references: crate::rules::IdSet::default(),
234
+ shadowed_calls: crate::rules::IdSet::default(),
235
+ local_method_names: crate::rules::IdSet::default(),
236
+ naming_definitions: crate::rules::IdSet::default(),
229
237
  };
230
238
  force.push_scope(root, true);
231
239
  force.process_children(root);
232
240
  force.pop_scope();
233
241
  Analysis {
242
+ index,
234
243
  scopes: force.scopes,
235
244
  variables: force.variables,
236
245
  lvars: force.lvars,
@@ -263,6 +272,7 @@ struct Frame<'tree> {
263
272
  }
264
273
 
265
274
  struct Force<'tree, 'a> {
275
+ index: &'tree super::super::AstIndex<'tree>,
266
276
  source: &'a SourceFile,
267
277
  scopes: Vec<Scope<'tree>>,
268
278
  variables: Vec<Variable<'tree>>,
@@ -272,14 +282,14 @@ struct Force<'tree, 'a> {
272
282
  /// Each heredoc's body, found from the `<<~X` that opened it. tree-sitter hangs the body off
273
283
  /// the enclosing statement, so a heredoc written inside a block would otherwise have its
274
284
  /// interpolations resolved in the wrong scope.
275
- heredocs: HashMap<usize, Node<'tree>>,
285
+ heredocs: crate::rules::IdKeyed<Node<'tree>>,
276
286
  /// Nodes already walked in an outer scope, which the scope they sit in must not walk again.
277
- scanned: HashSet<usize>,
278
- lvars: HashSet<usize>,
279
- naming_references: HashSet<usize>,
280
- shadowed_calls: HashSet<usize>,
281
- local_method_names: HashSet<usize>,
282
- naming_definitions: HashSet<usize>,
287
+ scanned: crate::rules::IdSet,
288
+ lvars: crate::rules::IdSet,
289
+ naming_references: crate::rules::IdSet,
290
+ shadowed_calls: crate::rules::IdSet,
291
+ local_method_names: crate::rules::IdSet,
292
+ naming_definitions: crate::rules::IdSet,
283
293
  }
284
294
 
285
295
  // ---------------------------------------------------------------------------
@@ -305,8 +315,8 @@ fn mislexed_match_operator(node: Node<'_>, source: &SourceFile) -> bool {
305
315
  /// definition. Local identifiers are recorded during the force walk, but instance, class and
306
316
  /// global variables do not participate in local-variable resolution and are cheaper to classify
307
317
  /// directly from their parent.
308
- fn structural_definition(node: Node<'_>) -> bool {
309
- let Some(parent) = node.parent() else {
318
+ fn structural_definition(node: Node<'_>, index: &super::super::AstIndex<'_>) -> bool {
319
+ let Some(parent) = index.parent(node) else {
310
320
  return false;
311
321
  };
312
322
  match parent.kind_str() {
@@ -421,20 +431,33 @@ pub(super) fn body_node<'tree>(scope: &Scope<'tree>) -> Option<Node<'tree>> {
421
431
  }
422
432
 
423
433
  /// The call a block was written on, which names the method the block was passed to.
424
- pub(super) fn block_call<'tree>(scope_node: Node<'tree>) -> Option<Node<'tree>> {
434
+ pub(super) fn block_call<'tree>(
435
+ scope_node: Node<'tree>,
436
+ index: &super::super::AstIndex<'tree>,
437
+ ) -> Option<Node<'tree>> {
425
438
  match scope_node.kind_str() {
426
- "block" | "do_block" => scope_node.parent().filter(|node| node.kind_str() == "call"),
439
+ "block" | "do_block" => index
440
+ .parent_in_tree(scope_node)
441
+ .filter(|node| node.kind_str() == "call"),
427
442
  _ => None,
428
443
  }
429
444
  }
430
445
 
431
446
  /// `BlockNode#lambda?`: both `->() {}` and `lambda {}` reach RuboCop as a block on `lambda`.
432
- pub(super) fn is_lambda(scope_node: Node<'_>, source: &SourceFile) -> bool {
433
- scope_node.kind_str() == "lambda" || block_method(scope_node, source) == Some("lambda")
447
+ pub(super) fn is_lambda(
448
+ scope_node: Node<'_>,
449
+ source: &SourceFile,
450
+ index: &super::super::AstIndex<'_>,
451
+ ) -> bool {
452
+ scope_node.kind_str() == "lambda" || block_method(scope_node, source, index) == Some("lambda")
434
453
  }
435
454
 
436
- pub(super) fn block_method<'a>(scope_node: Node<'_>, source: &'a SourceFile) -> Option<&'a str> {
437
- let call = block_call(scope_node)?;
455
+ pub(super) fn block_method<'a>(
456
+ scope_node: Node<'_>,
457
+ source: &'a SourceFile,
458
+ index: &super::super::AstIndex<'_>,
459
+ ) -> Option<&'a str> {
460
+ let call = block_call(scope_node, index)?;
438
461
  Some(source.node_text(call.field("method")?))
439
462
  }
440
463
 
@@ -471,19 +494,18 @@ impl<'tree> Force<'tree, '_> {
471
494
  });
472
495
  }
473
496
 
497
+ /// A node's named children, taken from the file's index. The list borrows the index rather
498
+ /// than this walk, so a `&mut self` call inside the loop is still allowed.
499
+ fn children(&self, node: Node<'tree>) -> std::borrow::Cow<'tree, [Node<'tree>]> {
500
+ super::super::send_node::named_children_in(node, self.index)
501
+ }
502
+
474
503
  fn process_children(&mut self, node: Node<'tree>) {
475
- let mut cursor = node.walk();
476
- if !cursor.goto_first_child() {
477
- return;
478
- }
479
- loop {
480
- let child = cursor.node();
481
- if child.is_named() && !self.scanned.contains(&child.id()) {
504
+ let children = self.children(node);
505
+ for child in children.iter().copied() {
506
+ if !self.scanned.contains(&child.id()) {
482
507
  self.process_node(child);
483
508
  }
484
- if !cursor.goto_next_sibling() {
485
- break;
486
- }
487
509
  }
488
510
  }
489
511
 
@@ -550,8 +572,9 @@ impl<'tree> Force<'tree, '_> {
550
572
  /// block node holding both the parameters and the body, so the inner node is not a scope.
551
573
  fn inline_block(&self, node: Node<'tree>) -> bool {
552
574
  node.kind_str() == "block"
553
- && node
554
- .parent()
575
+ && self
576
+ .index
577
+ .parent(node)
555
578
  .is_some_and(|parent| parent.kind_str() == "lambda")
556
579
  }
557
580
 
@@ -724,7 +747,7 @@ impl<'tree> Force<'tree, '_> {
724
747
  .push(node);
725
748
  }
726
749
  let assignment_node = self.variables[variable].assignments[index].node;
727
- if in_modifier_conditional(assignment_node, node) {
750
+ if in_modifier_conditional(assignment_node, node, self.index) {
728
751
  continue;
729
752
  }
730
753
  let Some(branch) = branch else { break };
@@ -879,10 +902,10 @@ impl<'tree> Force<'tree, '_> {
879
902
  // Only tree-sitter spells a method name with the same node type as a variable read; the
880
903
  // parser upstream turns `def foo` and `alias foo bar` into symbols. The receiver of
881
904
  // `def obj.foo` is a genuine read and stays.
882
- if node.parent().is_some_and(|parent| {
905
+ if self.index.parent(node).is_some_and(|parent| {
883
906
  matches!(parent.kind_str(), "alias" | "undef" | "setter")
884
907
  || (matches!(parent.kind_str(), "method" | "singleton_method")
885
- && field_name(node, parent) == Some("name"))
908
+ && field_name(node, parent, self.index) == Some("name"))
886
909
  }) {
887
910
  return;
888
911
  }
@@ -1331,7 +1354,7 @@ impl<'tree> Force<'tree, '_> {
1331
1354
  fn mark_assignments_referenced_in_loop(&mut self, node: Node<'tree>) {
1332
1355
  let mut names = Vec::new();
1333
1356
  let mut assignments = HashSet::new();
1334
- collect_loop_references(node, self.source, &mut names, &mut assignments);
1357
+ collect_loop_references(node, self.source, self.index, &mut names, &mut assignments);
1335
1358
  for name in names {
1336
1359
  let Some(variable) = self.find_variable(&name) else {
1337
1360
  continue;
@@ -1350,7 +1373,7 @@ impl<'tree> Force<'tree, '_> {
1350
1373
  };
1351
1374
  for &index in &indices {
1352
1375
  let assignment = &self.variables[variable].assignments[index];
1353
- if has_branch_ancestor(assignment.node) {
1376
+ if has_branch_ancestor(assignment.node, self.index) {
1354
1377
  self.variables[variable].assignments[index].referenced = true;
1355
1378
  }
1356
1379
  }
@@ -1379,8 +1402,8 @@ impl<'tree> Force<'tree, '_> {
1379
1402
  if !top_level && current.id() == scope_node.id() {
1380
1403
  return None;
1381
1404
  }
1382
- let parent = current.parent()?;
1383
- if let Some(role) = branch_role(current, parent)
1405
+ let parent = self.index.parent_in_tree(current)?;
1406
+ if let Some(role) = branch_role(current, parent, self.index)
1384
1407
  && role.branched
1385
1408
  {
1386
1409
  return Some(self.intern_branch(role, parent, scope_node, top_level));
@@ -1451,8 +1474,12 @@ fn implicit_numbered_parameter(name: &str) -> bool {
1451
1474
 
1452
1475
  /// Whether the child stands in a branch of the control structure above it, and how that branch
1453
1476
  /// behaves: `(may_run_incompletely, may_jump_to_other_branch, branched)`.
1454
- fn branch_role<'tree>(child: Node<'tree>, parent: Node<'tree>) -> Option<BranchRole<'tree>> {
1455
- let field = field_name(child, parent);
1477
+ fn branch_role<'tree>(
1478
+ child: Node<'tree>,
1479
+ parent: Node<'tree>,
1480
+ index: &'tree super::super::AstIndex<'tree>,
1481
+ ) -> Option<BranchRole<'tree>> {
1482
+ let field = field_name(child, parent, index);
1456
1483
  let always_run = BranchRole::always_run(child);
1457
1484
  let branched = BranchRole::branched(child);
1458
1485
  match parent.kind_str() {
@@ -1483,10 +1510,10 @@ fn branch_role<'tree>(child: Node<'tree>, parent: Node<'tree>) -> Option<BranchR
1483
1510
  // `begin … rescue … ensure … end` has no node of its own here: the clauses stand beside
1484
1511
  // the statements they guard. Upstream wraps those statements in one node, and the whole
1485
1512
  // main body is a single branch, so they all have to point at the same child.
1486
- _ if is_rescue_container(parent) => Some(match child.kind_str() {
1513
+ _ if is_rescue_container(parent, index) => Some(match child.kind_str() {
1487
1514
  "rescue" | "else" => branched,
1488
1515
  "ensure" => always_run,
1489
- _ => BranchRole::branched(main_body_anchor(parent)?).escaping(),
1516
+ _ => BranchRole::branched(main_body_anchor(parent, index)?).escaping(),
1490
1517
  }),
1491
1518
  _ => None,
1492
1519
  }
@@ -1531,23 +1558,45 @@ impl<'tree> BranchRole<'tree> {
1531
1558
  }
1532
1559
 
1533
1560
  /// The first statement of a `begin`'s main body, which stands for the whole of it.
1534
- fn main_body_anchor<'tree>(container: Node<'tree>) -> Option<Node<'tree>> {
1535
- named_children(container)
1536
- .into_iter()
1561
+ fn main_body_anchor<'tree>(
1562
+ container: Node<'tree>,
1563
+ index: &'tree super::super::AstIndex<'tree>,
1564
+ ) -> Option<Node<'tree>> {
1565
+ super::super::send_node::named_children_in(container, index)
1566
+ .iter()
1567
+ .copied()
1537
1568
  .find(|child| !matches!(child.kind_str(), "rescue" | "else" | "ensure"))
1538
1569
  }
1539
1570
 
1540
- fn is_rescue_container(node: Node<'_>) -> bool {
1541
- has_child_kind(node, "rescue") || has_child_kind(node, "ensure")
1571
+ fn is_rescue_container<'tree>(
1572
+ node: Node<'tree>,
1573
+ index: &'tree super::super::AstIndex<'tree>,
1574
+ ) -> bool {
1575
+ has_child_kind(node, "rescue", index) || has_child_kind(node, "ensure", index)
1542
1576
  }
1543
1577
 
1544
- fn has_child_kind(node: Node<'_>, kind: &str) -> bool {
1545
- let mut cursor = node.walk();
1546
- node.named_children(&mut cursor)
1547
- .any(|c| c.kind_str() == kind)
1578
+ fn has_child_kind<'tree>(
1579
+ node: Node<'tree>,
1580
+ kind: &str,
1581
+ index: &'tree super::super::AstIndex<'tree>,
1582
+ ) -> bool {
1583
+ super::super::send_node::named_children_in(node, index)
1584
+ .iter()
1585
+ .any(|child| child.kind_str() == kind)
1548
1586
  }
1549
1587
 
1550
- fn field_name(child: Node<'_>, parent: Node<'_>) -> Option<&'static str> {
1588
+ /// Which field the child fills in its parent.
1589
+ ///
1590
+ /// The index recorded this for every node of the file on the walk that built it, so the cursor
1591
+ /// walk below is only for a node from one of the extra trees `Metrics` parses.
1592
+ fn field_name(
1593
+ child: Node<'_>,
1594
+ parent: Node<'_>,
1595
+ index: &super::super::AstIndex<'_>,
1596
+ ) -> Option<&'static str> {
1597
+ if index.knows(child) {
1598
+ return index.field_name_of(child);
1599
+ }
1551
1600
  let mut cursor = parent.walk();
1552
1601
  if !cursor.goto_first_child() {
1553
1602
  return None;
@@ -1626,9 +1675,12 @@ fn contains_kind(node: Node<'_>, kind: &str) -> bool {
1626
1675
 
1627
1676
  /// The branch node types `reference_assignments` looks for: an assignment under one of them may
1628
1677
  /// be skipped on some iterations, so every one of them counts as read by the loop.
1629
- fn has_branch_ancestor(node: Node<'_>) -> bool {
1678
+ fn has_branch_ancestor<'tree>(
1679
+ node: Node<'tree>,
1680
+ index: &'tree super::super::AstIndex<'tree>,
1681
+ ) -> bool {
1630
1682
  let mut current = node;
1631
- while let Some(parent) = current.parent() {
1683
+ while let Some(parent) = index.parent(current) {
1632
1684
  let branching = matches!(
1633
1685
  parent.kind_str(),
1634
1686
  "if" | "elsif"
@@ -1640,7 +1692,8 @@ fn has_branch_ancestor(node: Node<'_>) -> bool {
1640
1692
  | "case_match"
1641
1693
  | "rescue"
1642
1694
  | "rescue_modifier"
1643
- ) || (parent.kind_str() != "program" && has_child_kind(parent, "rescue"));
1695
+ ) || (parent.kind_str() != "program"
1696
+ && has_child_kind(parent, "rescue", index));
1644
1697
  if branching {
1645
1698
  return true;
1646
1699
  }
@@ -1654,6 +1707,7 @@ fn has_branch_ancestor(node: Node<'_>) -> bool {
1654
1707
  fn collect_loop_references(
1655
1708
  node: Node<'_>,
1656
1709
  source: &SourceFile,
1710
+ index: &super::super::AstIndex<'_>,
1657
1711
  names: &mut Vec<String>,
1658
1712
  assignments: &mut HashSet<String>,
1659
1713
  ) {
@@ -1696,15 +1750,15 @@ fn collect_loop_references(
1696
1750
  // target all wear the same node type here. A `for` variable and a rescue clause's
1697
1751
  // variable are `lvasgn` nodes that carry no value.
1698
1752
  "identifier" => {
1699
- if is_variable_read(child) {
1753
+ if is_variable_read(child, index) {
1700
1754
  names.push(source.node_text(child).to_owned());
1701
- } else if bare_assignment_target(child) {
1755
+ } else if bare_assignment_target(child, index) {
1702
1756
  assignments.insert(shape(child, None, source));
1703
1757
  }
1704
1758
  }
1705
1759
  _ => {}
1706
1760
  }
1707
- collect_loop_references(child, source, names, assignments);
1761
+ collect_loop_references(child, source, index, names, assignments);
1708
1762
  }
1709
1763
  }
1710
1764
 
@@ -1739,15 +1793,15 @@ fn identifier_words(text: &str) -> Vec<String> {
1739
1793
 
1740
1794
  /// Whether an identifier stands where the parser upstream would have built an `lvar`, rather than
1741
1795
  /// a name being declared, written or called.
1742
- pub(super) fn is_variable_read(node: Node<'_>) -> bool {
1743
- let Some(parent) = node.parent() else {
1796
+ pub(super) fn is_variable_read(node: Node<'_>, index: &super::super::AstIndex<'_>) -> bool {
1797
+ let Some(parent) = index.parent(node) else {
1744
1798
  return true;
1745
1799
  };
1746
1800
  match parent.kind_str() {
1747
1801
  // The receiver of `foo.bar` and of `def obj.baz` is read; the selector is not.
1748
- "call" => field_name(node, parent) != Some("method"),
1749
- "method" | "singleton_method" => field_name(node, parent) != Some("name"),
1750
- "assignment" | "operator_assignment" => field_name(node, parent) != Some("left"),
1802
+ "call" => field_name(node, parent, index) != Some("method"),
1803
+ "method" | "singleton_method" => field_name(node, parent, index) != Some("name"),
1804
+ "assignment" | "operator_assignment" => field_name(node, parent, index) != Some("left"),
1751
1805
  "left_assignment_list"
1752
1806
  | "rest_assignment"
1753
1807
  | "destructured_left_assignment"
@@ -1764,8 +1818,8 @@ pub(super) fn is_variable_read(node: Node<'_>) -> bool {
1764
1818
  | "keyword_parameter"
1765
1819
  | "splat_parameter"
1766
1820
  | "hash_splat_parameter"
1767
- | "block_parameter" => field_name(node, parent) != Some("name"),
1768
- "for" => field_name(node, parent) != Some("pattern"),
1821
+ | "block_parameter" => field_name(node, parent, index) != Some("name"),
1822
+ "for" => field_name(node, parent, index) != Some("pattern"),
1769
1823
  _ => true,
1770
1824
  }
1771
1825
  }
@@ -1782,12 +1836,12 @@ fn swallowed_by_escape(node: Node<'_>, source: &SourceFile) -> bool {
1782
1836
  }
1783
1837
 
1784
1838
  /// Whether an identifier is a write that stores no value of its own.
1785
- fn bare_assignment_target(node: Node<'_>) -> bool {
1786
- let Some(parent) = node.parent() else {
1839
+ fn bare_assignment_target(node: Node<'_>, index: &super::super::AstIndex<'_>) -> bool {
1840
+ let Some(parent) = index.parent(node) else {
1787
1841
  return false;
1788
1842
  };
1789
1843
  match parent.kind_str() {
1790
- "for" => field_name(node, parent) == Some("pattern"),
1844
+ "for" => field_name(node, parent, index) == Some("pattern"),
1791
1845
  "exception_variable" => true,
1792
1846
  _ => false,
1793
1847
  }
@@ -1856,9 +1910,13 @@ fn named_captures(source: &str) -> Vec<String> {
1856
1910
 
1857
1911
  /// `in_modifier_conditional?`: an assignment made in `foo = 1 if bar` is not in scope to the left
1858
1912
  /// of the keyword, so a read there cannot be the one that uses it.
1859
- fn in_modifier_conditional(assignment: Node<'_>, reference: Node<'_>) -> bool {
1913
+ fn in_modifier_conditional(
1914
+ assignment: Node<'_>,
1915
+ reference: Node<'_>,
1916
+ index: &super::super::AstIndex<'_>,
1917
+ ) -> bool {
1860
1918
  let mut current = assignment;
1861
- while let Some(parent) = current.parent() {
1919
+ while let Some(parent) = index.parent(current) {
1862
1920
  if matches!(
1863
1921
  parent.kind_str(),
1864
1922
  "if_modifier" | "unless_modifier" | "while_modifier" | "until_modifier"
@@ -1879,10 +1937,13 @@ fn covers(container: Node<'_>, node: Node<'_>) -> bool {
1879
1937
 
1880
1938
  /// Each heredoc's body, paired with the `<<~X` that opened it. Both appear in the same order in
1881
1939
  /// the file, which is what makes matching them by position sound.
1882
- fn heredoc_bodies(root: Node<'_>) -> HashMap<usize, Node<'_>> {
1883
- let mut beginnings = Vec::new();
1884
- let mut bodies = Vec::new();
1885
- collect_heredocs(root, &mut beginnings, &mut bodies);
1940
+ fn heredoc_bodies<'tree>(
1941
+ index: &super::super::AstIndex<'tree>,
1942
+ ) -> crate::rules::IdKeyed<Node<'tree>> {
1943
+ // The index already groups the file's nodes by kind, so the recursive walk this used to make
1944
+ // -- which allocated a vector of children at every node of the tree -- answers nothing new.
1945
+ let mut beginnings: Vec<Node<'tree>> = index.nodes_of_kind("heredoc_beginning").collect();
1946
+ let mut bodies: Vec<Node<'tree>> = index.nodes_of_kind("heredoc_body").collect();
1886
1947
  beginnings.sort_by_key(|node: &Node<'_>| node.start_byte());
1887
1948
  bodies.sort_by_key(|node: &Node<'_>| node.start_byte());
1888
1949
  beginnings
@@ -1891,18 +1952,3 @@ fn heredoc_bodies(root: Node<'_>) -> HashMap<usize, Node<'_>> {
1891
1952
  .map(|(beginning, body)| (beginning.id(), body))
1892
1953
  .collect()
1893
1954
  }
1894
-
1895
- fn collect_heredocs<'tree>(
1896
- node: Node<'tree>,
1897
- beginnings: &mut Vec<Node<'tree>>,
1898
- bodies: &mut Vec<Node<'tree>>,
1899
- ) {
1900
- match node.kind_str() {
1901
- "heredoc_beginning" => beginnings.push(node),
1902
- "heredoc_body" => bodies.push(node),
1903
- _ => {}
1904
- }
1905
- for child in named_children(node) {
1906
- collect_heredocs(child, beginnings, bodies);
1907
- }
1908
- }
@@ -4,7 +4,7 @@ use tree_sitter::Node;
4
4
 
5
5
  use crate::diagnostic::{Edit, Offense};
6
6
  use crate::rules::RuleContext;
7
- use crate::rules::send_node::{arguments, named_children};
7
+ use crate::rules::send_node::arguments;
8
8
  use crate::rules::support;
9
9
 
10
10
  use super::blocks::{BLOCK_KINDS, BlockArgs};
@@ -12,6 +12,7 @@ use super::literals::{is_literal, literal_type};
12
12
  use super::locals::LocalVariables;
13
13
  use super::statements::{Branch, begin_containers, body_children, has_clause, statements};
14
14
  use crate::rules::node_ext::NodeExt;
15
+ use crate::rules::send_node::named_children_of;
15
16
 
16
17
  const SELF_MSG: &str = "`self` used in void context.";
17
18
 
@@ -341,7 +342,7 @@ impl Void<'_, '_> {
341
342
  return;
342
343
  }
343
344
  if matches!(expression.kind_str(), "case" | "case_match") {
344
- for child in named_children(expression) {
345
+ for child in named_children_of(expression, self.context) {
345
346
  match child.kind_str() {
346
347
  "when" | "in_clause" => {
347
348
  if let Branch::One(body) = Branch::of(child.field("body")) {
@@ -560,10 +561,10 @@ impl Void<'_, '_> {
560
561
  /// `entirely_literal?`.
561
562
  fn entirely_literal(&self, node: Node<'_>) -> bool {
562
563
  match node.kind_str() {
563
- "array" | "string_array" | "symbol_array" => named_children(node)
564
+ "array" | "string_array" | "symbol_array" => named_children_of(node, self.context)
564
565
  .into_iter()
565
566
  .all(|child| self.entirely_literal(child)),
566
- "hash" => named_children(node)
567
+ "hash" => named_children_of(node, self.context)
567
568
  .into_iter()
568
569
  .filter(|child| child.kind_str() == "pair")
569
570
  .all(|pair| {
@@ -127,8 +127,7 @@ fn clause_range(node: Node<'_>) -> Range<usize> {
127
127
  /// The `resbody` of `x rescue y`, which starts at the keyword and ends with the handler.
128
128
  fn modifier_clause(node: Node<'_>, handler: Node<'_>) -> Range<usize> {
129
129
  let mut cursor = node.walk();
130
- let keyword = node
131
- .children(&mut cursor)
130
+ let keyword = node.children(&mut cursor)
132
131
  .find(|child| !child.is_named() && child.kind_str() == "rescue")
133
132
  .map_or(node.start_byte(), |child| child.start_byte());
134
133
  keyword..handler.end_byte()
@@ -6,6 +6,7 @@ use crate::diagnostic::Offense;
6
6
  use crate::rules::RuleContext;
7
7
  use crate::rules::node_ext::NodeExt;
8
8
  use crate::rules::send_node::{named_children, top_level_constant};
9
+ use crate::rules::send_node::named_children_of;
9
10
 
10
11
  const MSG: &str = "Avoid hard coding large quantities of data in code. \
11
12
  Prefer reading the data from an external source.";
@@ -46,7 +47,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
46
47
  if !top_level_constant(object, "Set", context) || is_assignment_target(node) {
47
48
  continue;
48
49
  }
49
- let children = named_children(node);
50
+ let children = named_children_of(node, context);
50
51
  let indices = children.get(1..).unwrap_or_default();
51
52
  if fold_pairs(indices).len() >= threshold {
52
53
  offenses.push(context.offense(MSG, node.byte_range()));
@@ -108,6 +108,9 @@ pub(super) enum Order {
108
108
 
109
109
  pub(super) struct Walk<'a> {
110
110
  source: &'a SourceFile,
111
+ /// The file's node index. The walk asks for a parent on every identifier it meets, and
112
+ /// `Node::parent` walks down from the root each time.
113
+ index: &'a super::super::AstIndex<'a>,
111
114
  locals: &'a Locals,
112
115
  fragments: &'a Fragments,
113
116
  order: Order,
@@ -122,6 +125,7 @@ impl<'a> Walk<'a> {
122
125
  ) -> Self {
123
126
  Self {
124
127
  source: context.source,
128
+ index: context.ast_index(),
125
129
  locals,
126
130
  fragments,
127
131
  order,
@@ -704,9 +708,10 @@ impl<'a> Walk<'a> {
704
708
 
705
709
  fn visit_block_argument<F: FnMut(Emit<'a>)>(&self, node: Node<'a>, sink: &mut F) {
706
710
  let mut emit = Emit::of(Kind::BlockPass, node);
707
- emit.iterating = node
708
- .parent()
709
- .and_then(|arguments| arguments.parent())
711
+ emit.iterating = self
712
+ .index
713
+ .parent(node)
714
+ .and_then(|arguments| self.index.parent(arguments))
710
715
  .filter(|call| call.kind_str() == "call")
711
716
  .and_then(|call| self.iterating_call(call));
712
717
  self.around(emit, sink, |walk, sink| walk.children(node, sink));
@@ -829,7 +834,7 @@ impl<'a> Walk<'a> {
829
834
 
830
835
  /// Whether the identifier stands where the parser would have built `(send nil :name)`.
831
836
  fn receiverless_call(&self, node: Node<'_>) -> bool {
832
- is_receiverless_call(node)
837
+ is_receiverless_call(node, self.index)
833
838
  && !self.locals.is_lvar(node)
834
839
  && !is_keyword_literal(self.text(node))
835
840
  && !self.is_implicit_block_parameter(node)
@@ -841,7 +846,7 @@ impl<'a> Walk<'a> {
841
846
  if self.text(node) != "it" {
842
847
  return false;
843
848
  }
844
- let mut current = node.parent();
849
+ let mut current = self.index.parent(node);
845
850
  while let Some(parent) = current {
846
851
  if matches!(parent.kind_str(), "block" | "do_block") {
847
852
  return parent.field("parameters").is_none();
@@ -852,7 +857,7 @@ impl<'a> Walk<'a> {
852
857
  ) {
853
858
  return false;
854
859
  }
855
- current = parent.parent();
860
+ current = self.index.parent(parent);
856
861
  }
857
862
  false
858
863
  }
@@ -1091,14 +1096,14 @@ fn collect_targets<'a>(node: Node<'a>, targets: &mut Vec<Node<'a>>) {
1091
1096
 
1092
1097
  /// Whether the identifier stands where the parser would have built `(send nil :name)` rather than
1093
1098
  /// a name being declared, written or called on something else.
1094
- fn is_receiverless_call(node: Node<'_>) -> bool {
1095
- let Some(parent) = node.parent() else {
1099
+ fn is_receiverless_call(node: Node<'_>, index: &super::super::AstIndex<'_>) -> bool {
1100
+ let Some(parent) = index.parent(node) else {
1096
1101
  return true;
1097
1102
  };
1098
1103
  match parent.kind_str() {
1099
- "call" => field_name(node, parent) != Some("method"),
1100
- "method" | "singleton_method" => field_name(node, parent) != Some("name"),
1101
- "assignment" | "operator_assignment" => field_name(node, parent) != Some("left"),
1104
+ "call" => field_name(node, parent, index) != Some("method"),
1105
+ "method" | "singleton_method" => field_name(node, parent, index) != Some("name"),
1106
+ "assignment" | "operator_assignment" => field_name(node, parent, index) != Some("left"),
1102
1107
  "left_assignment_list"
1103
1108
  | "rest_assignment"
1104
1109
  | "destructured_left_assignment"
@@ -1114,8 +1119,8 @@ fn is_receiverless_call(node: Node<'_>) -> bool {
1114
1119
  | "keyword_parameter"
1115
1120
  | "splat_parameter"
1116
1121
  | "hash_splat_parameter"
1117
- | "block_parameter" => field_name(node, parent) != Some("name"),
1118
- "for" => field_name(node, parent) != Some("pattern"),
1122
+ | "block_parameter" => field_name(node, parent, index) != Some("name"),
1123
+ "for" => field_name(node, parent, index) != Some("pattern"),
1119
1124
  _ => true,
1120
1125
  }
1121
1126
  }