sonicop 26.8.111 → 26.8.113

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (436) hide show
  1. checksums.yaml +4 -4
  2. data/CONFORMANCE.md +64 -31
  3. data/Cargo.lock +94 -19
  4. data/Cargo.toml +13 -8
  5. data/NOTICE +15 -3
  6. data/README.ja.md +145 -57
  7. data/README.md +140 -48
  8. data/build.rs +155 -0
  9. data/lib/sonicop/runner.rb +7 -1
  10. data/lib/sonicop/version.rb +1 -1
  11. data/src/cli.rs +216 -45
  12. data/src/config/inheritance.rs +637 -64
  13. data/src/config/mod.rs +130 -1
  14. data/src/config/paths.rs +550 -48
  15. data/src/cop_name.rs +18 -1
  16. data/src/diagnostic.rs +168 -3
  17. data/src/directives.rs +398 -77
  18. data/src/display_width.rs +82 -42
  19. data/src/display_width_table.rs +506 -0
  20. data/src/engine.rs +1532 -51
  21. data/src/formatter.rs +10 -1
  22. data/src/lib.rs +2 -0
  23. data/src/main.rs +112 -1
  24. data/src/nul_bytes.rs +245 -47
  25. data/src/parser.rs +37 -0
  26. data/src/profile.rs +174 -24
  27. data/src/ruby_version.rs +167 -21
  28. data/src/rules/bundler/duplicated_group.rs +3 -2
  29. data/src/rules/bundler/gem_comment.rs +5 -4
  30. data/src/rules/gemspec/attribute_assignment.rs +4 -3
  31. data/src/rules/gemspec/deprecated_attribute_assignment.rs +7 -6
  32. data/src/rules/gemspec/duplicated_assignment.rs +4 -3
  33. data/src/rules/gemspec/require_mfa.rs +10 -9
  34. data/src/rules/gemspec/required_ruby_version.rs +3 -2
  35. data/src/rules/gemspec/support.rs +28 -8
  36. data/src/rules/layout/access_modifier_indentation.rs +13 -6
  37. data/src/rules/layout/argument_alignment.rs +19 -7
  38. data/src/rules/layout/array_alignment.rs +1 -2
  39. data/src/rules/layout/block_alignment.rs +74 -30
  40. data/src/rules/layout/block_end_newline.rs +5 -5
  41. data/src/rules/layout/class_structure.rs +13 -4
  42. data/src/rules/layout/closing_heredoc_indentation.rs +24 -5
  43. data/src/rules/layout/closing_parenthesis_indentation.rs +3 -3
  44. data/src/rules/layout/comment_indentation.rs +2 -1
  45. data/src/rules/layout/condition_position.rs +3 -2
  46. data/src/rules/layout/def_end_alignment.rs +10 -6
  47. data/src/rules/layout/dot_position.rs +1 -2
  48. data/src/rules/layout/else_alignment.rs +24 -14
  49. data/src/rules/layout/empty_line_after_guard_clause.rs +8 -7
  50. data/src/rules/layout/empty_line_after_multiline_condition.rs +5 -5
  51. data/src/rules/layout/empty_line_between_defs.rs +8 -6
  52. data/src/rules/layout/empty_lines_around_access_modifier.rs +22 -10
  53. data/src/rules/layout/empty_lines_around_arguments.rs +4 -4
  54. data/src/rules/layout/empty_lines_around_body.rs +8 -2
  55. data/src/rules/layout/empty_lines_around_exception_handling_keywords.rs +1 -2
  56. data/src/rules/layout/empty_lines_around_method_body.rs +3 -2
  57. data/src/rules/layout/end_alignment.rs +32 -7
  58. data/src/rules/layout/end_of_line.rs +14 -1
  59. data/src/rules/layout/extra_spacing.rs +9 -11
  60. data/src/rules/layout/first_array_element_indentation.rs +1 -2
  61. data/src/rules/layout/first_hash_element_indentation.rs +8 -6
  62. data/src/rules/layout/hash_alignment.rs +113 -19
  63. data/src/rules/layout/heredoc_argument_closing_parenthesis.rs +6 -5
  64. data/src/rules/layout/heredoc_indentation.rs +4 -3
  65. data/src/rules/layout/indentation_style.rs +6 -4
  66. data/src/rules/layout/indentation_width.rs +76 -16
  67. data/src/rules/layout/initial_indentation.rs +18 -5
  68. data/src/rules/layout/line_continuation_leading_space.rs +4 -3
  69. data/src/rules/layout/line_continuation_spacing.rs +4 -3
  70. data/src/rules/layout/line_length.rs +417 -43
  71. data/src/rules/layout/mod.rs +1 -1
  72. data/src/rules/layout/multiline_assignment_layout.rs +20 -3
  73. data/src/rules/layout/multiline_block_layout.rs +1 -2
  74. data/src/rules/layout/multiline_expression.rs +60 -31
  75. data/src/rules/layout/multiline_hash_brace_layout.rs +4 -3
  76. data/src/rules/layout/multiline_method_call_indentation.rs +1 -1
  77. data/src/rules/layout/multiline_operation_indentation.rs +3 -3
  78. data/src/rules/layout/redundant_line_break.rs +16 -5
  79. data/src/rules/layout/rescue_ensure_alignment.rs +20 -12
  80. data/src/rules/layout/single_line_block_chain.rs +13 -4
  81. data/src/rules/layout/space_around_block_parameters.rs +16 -2
  82. data/src/rules/layout/space_around_equals_in_parameter_default.rs +1 -2
  83. data/src/rules/layout/space_around_keyword.rs +22 -4
  84. data/src/rules/layout/space_around_operators.rs +34 -2
  85. data/src/rules/layout/space_before_brackets.rs +13 -6
  86. data/src/rules/layout/space_before_comma.rs +3 -2
  87. data/src/rules/layout/space_inside_array_literal_brackets.rs +25 -10
  88. data/src/rules/layout/space_inside_array_percent_literal.rs +7 -1
  89. data/src/rules/layout/space_inside_parens.rs +170 -87
  90. data/src/rules/layout/space_inside_range_literal.rs +53 -19
  91. data/src/rules/layout/space_inside_reference_brackets.rs +65 -3
  92. data/src/rules/layout/space_inside_string_interpolation.rs +147 -48
  93. data/src/rules/layout/support.rs +172 -26
  94. data/src/rules/layout/tokens.rs +47 -7
  95. data/src/rules/layout/trailing_whitespace.rs +5 -2
  96. data/src/rules/lint/access_modifier.rs +50 -5
  97. data/src/rules/lint/ambiguity.rs +97 -7
  98. data/src/rules/lint/ambiguous_block_association.rs +29 -12
  99. data/src/rules/lint/ambiguous_operator.rs +8 -0
  100. data/src/rules/lint/ambiguous_operator_precedence.rs +18 -1
  101. data/src/rules/lint/ambiguous_range.rs +15 -1
  102. data/src/rules/lint/ambiguous_regexp_literal.rs +40 -1
  103. data/src/rules/lint/array_literal_in_regexp.rs +13 -5
  104. data/src/rules/lint/assignment_in_condition.rs +3 -2
  105. data/src/rules/lint/blocks.rs +2 -1
  106. data/src/rules/lint/boolean_symbol.rs +4 -4
  107. data/src/rules/lint/constant_definition_in_block.rs +1 -2
  108. data/src/rules/lint/constant_reassignment.rs +9 -13
  109. data/src/rules/lint/constant_resolution.rs +8 -0
  110. data/src/rules/lint/cop_directives.rs +28 -4
  111. data/src/rules/lint/debugger.rs +59 -9
  112. data/src/rules/lint/deprecated_open_ssl_constant.rs +1 -2
  113. data/src/rules/lint/disjunctive_assignment_in_constructor.rs +2 -2
  114. data/src/rules/lint/duplicate_branch.rs +16 -6
  115. data/src/rules/lint/duplicate_case_condition.rs +5 -5
  116. data/src/rules/lint/duplicate_hash_key.rs +8 -4
  117. data/src/rules/lint/duplicate_match_pattern.rs +7 -5
  118. data/src/rules/lint/duplicate_methods.rs +133 -54
  119. data/src/rules/lint/duplicate_require.rs +1 -2
  120. data/src/rules/lint/duplicate_set_element.rs +19 -5
  121. data/src/rules/lint/empty_conditional_body.rs +19 -3
  122. data/src/rules/lint/empty_expression.rs +16 -1
  123. data/src/rules/lint/empty_in_pattern.rs +3 -2
  124. data/src/rules/lint/empty_when.rs +4 -4
  125. data/src/rules/lint/ensure_return.rs +3 -2
  126. data/src/rules/lint/float_comparison.rs +8 -6
  127. data/src/rules/lint/flow.rs +8 -5
  128. data/src/rules/lint/format_parameter_mismatch.rs +4 -3
  129. data/src/rules/lint/hash_compare_by_identity.rs +5 -4
  130. data/src/rules/lint/heredoc_method_call_position.rs +3 -2
  131. data/src/rules/lint/implicit_string_concatenation.rs +33 -3
  132. data/src/rules/lint/ineffective_access_modifier.rs +5 -4
  133. data/src/rules/lint/inherit_exception.rs +4 -3
  134. data/src/rules/lint/literal_as_condition.rs +51 -31
  135. data/src/rules/lint/literal_assignment_in_condition.rs +4 -3
  136. data/src/rules/lint/literal_in_interpolation.rs +66 -5
  137. data/src/rules/lint/literals.rs +10 -8
  138. data/src/rules/lint/missing_cop_enable_directive.rs +95 -18
  139. data/src/rules/lint/mod.rs +1 -0
  140. data/src/rules/lint/nested_method_definition.rs +36 -11
  141. data/src/rules/lint/nil_receiver.rs +420 -0
  142. data/src/rules/lint/no_return_in_begin_end_blocks.rs +2 -2
  143. data/src/rules/lint/node_equality.rs +8 -5
  144. data/src/rules/lint/non_atomic_file_operation.rs +9 -3
  145. data/src/rules/lint/non_deterministic_require_order.rs +5 -3
  146. data/src/rules/lint/non_local_exit_from_iterator.rs +22 -9
  147. data/src/rules/lint/number_conversion.rs +1 -1
  148. data/src/rules/lint/out_of_range_regexp_ref.rs +6 -4
  149. data/src/rules/lint/parentheses_as_grouped_expression.rs +5 -0
  150. data/src/rules/lint/redundant_cop_enable_directive.rs +3 -1
  151. data/src/rules/lint/redundant_dir_glob_sort.rs +1 -2
  152. data/src/rules/lint/redundant_safe_navigation.rs +47 -22
  153. data/src/rules/lint/redundant_splat_expansion.rs +4 -2
  154. data/src/rules/lint/redundant_string_coercion.rs +3 -2
  155. data/src/rules/lint/redundant_type_conversion.rs +16 -4
  156. data/src/rules/lint/regexp.rs +1 -3
  157. data/src/rules/lint/regexp_source.rs +2 -2
  158. data/src/rules/lint/regexp_tree.rs +38 -5
  159. data/src/rules/lint/require_range_parentheses.rs +5 -5
  160. data/src/rules/lint/rescue_exception.rs +4 -3
  161. data/src/rules/lint/rescue_type.rs +3 -2
  162. data/src/rules/lint/return_in_void_context.rs +8 -3
  163. data/src/rules/lint/safe_navigation_chain.rs +47 -11
  164. data/src/rules/lint/self_assignment.rs +20 -2
  165. data/src/rules/lint/shadowed_argument.rs +2 -2
  166. data/src/rules/lint/shadowed_exception.rs +2 -2
  167. data/src/rules/lint/suppressed_exception.rs +21 -3
  168. data/src/rules/lint/suppressed_exception_in_number_conversion.rs +4 -3
  169. data/src/rules/lint/symbol_conversion.rs +56 -8
  170. data/src/rules/lint/syntax.rs +195 -5
  171. data/src/rules/lint/top_level_return_with_argument.rs +11 -1
  172. data/src/rules/lint/triple_quotes.rs +3 -2
  173. data/src/rules/lint/unescaped_bracket_in_regexp.rs +1 -1
  174. data/src/rules/lint/unexpected_block_arity.rs +23 -5
  175. data/src/rules/lint/unified_integer.rs +18 -12
  176. data/src/rules/lint/unmodified_reduce_accumulator.rs +36 -7
  177. data/src/rules/lint/unreachable_code.rs +4 -4
  178. data/src/rules/lint/unreachable_loop.rs +26 -10
  179. data/src/rules/lint/unreachable_pattern_branch.rs +7 -5
  180. data/src/rules/lint/unused_block_argument.rs +2 -2
  181. data/src/rules/lint/unused_method_argument.rs +1 -2
  182. data/src/rules/lint/useless_assignment.rs +3 -2
  183. data/src/rules/lint/useless_constant_scoping.rs +3 -2
  184. data/src/rules/lint/useless_else_without_rescue.rs +4 -3
  185. data/src/rules/lint/useless_rescue.rs +5 -4
  186. data/src/rules/lint/useless_ruby2_keywords.rs +2 -1
  187. data/src/rules/lint/useless_setter_call.rs +5 -4
  188. data/src/rules/lint/useless_times.rs +4 -3
  189. data/src/rules/lint/variable_force.rs +307 -79
  190. data/src/rules/lint/void.rs +32 -20
  191. data/src/rules/metrics/abc_size.rs +197 -7
  192. data/src/rules/metrics/block_length.rs +19 -10
  193. data/src/rules/metrics/block_nesting.rs +1 -2
  194. data/src/rules/metrics/class_length.rs +16 -4
  195. data/src/rules/metrics/collection_literal_length.rs +2 -1
  196. data/src/rules/metrics/complexity.rs +52 -14
  197. data/src/rules/metrics/fragments.rs +10 -14
  198. data/src/rules/metrics/locals.rs +35 -14
  199. data/src/rules/metrics/method_length.rs +14 -6
  200. data/src/rules/metrics/parameter_lists.rs +7 -6
  201. data/src/rules/metrics/perceived_complexity.rs +1 -3
  202. data/src/rules/metrics/support.rs +94 -4
  203. data/src/rules/mod.rs +525 -80
  204. data/src/rules/naming/ascii_identifiers.rs +3 -3
  205. data/src/rules/naming/binary_operator_parameter_name.rs +5 -4
  206. data/src/rules/naming/block_forwarding.rs +9 -8
  207. data/src/rules/naming/constant_name.rs +56 -18
  208. data/src/rules/naming/file_name.rs +174 -43
  209. data/src/rules/naming/method_name.rs +8 -1
  210. data/src/rules/naming/predicate_method.rs +42 -14
  211. data/src/rules/naming/predicate_prefix.rs +70 -4
  212. data/src/rules/naming/rescued_exceptions_variable_name.rs +14 -11
  213. data/src/rules/naming/support.rs +54 -295
  214. data/src/rules/naming/variable_name.rs +17 -4
  215. data/src/rules/naming/variable_number.rs +9 -10
  216. data/src/rules/node_ext.rs +1 -1
  217. data/src/rules/ruby_literal.rs +8 -3
  218. data/src/rules/security/compound_hash.rs +3 -2
  219. data/src/rules/security/open.rs +5 -4
  220. data/src/rules/send_node.rs +134 -0
  221. data/src/rules/single_line.rs +4 -4
  222. data/src/rules/style/access_modifier_declarations.rs +76 -22
  223. data/src/rules/style/accessor_grouping.rs +44 -6
  224. data/src/rules/style/alias.rs +4 -3
  225. data/src/rules/style/and_or.rs +2 -2
  226. data/src/rules/style/arguments_forwarding.rs +5 -5
  227. data/src/rules/style/array_coercion.rs +5 -5
  228. data/src/rules/style/array_first_last.rs +19 -4
  229. data/src/rules/style/array_intersect.rs +26 -8
  230. data/src/rules/style/array_intersect_with_single_element.rs +1 -1
  231. data/src/rules/style/ascii_comments.rs +2 -0
  232. data/src/rules/style/attr.rs +1 -1
  233. data/src/rules/style/auto_resource_cleanup.rs +1 -1
  234. data/src/rules/style/bitwise_predicate.rs +2 -2
  235. data/src/rules/style/block_args.rs +1 -1
  236. data/src/rules/style/block_comments.rs +11 -2
  237. data/src/rules/style/block_delimiters.rs +154 -10
  238. data/src/rules/style/case_equality.rs +7 -2
  239. data/src/rules/style/case_like_if.rs +26 -2
  240. data/src/rules/style/class_and_module_children.rs +162 -10
  241. data/src/rules/style/class_methods.rs +1 -1
  242. data/src/rules/style/class_methods_definitions.rs +9 -0
  243. data/src/rules/style/collection_compact.rs +4 -4
  244. data/src/rules/style/combinable_defined.rs +2 -2
  245. data/src/rules/style/combinable_loops.rs +9 -11
  246. data/src/rules/style/comments.rs +6 -3
  247. data/src/rules/style/comparable_clamp.rs +2 -2
  248. data/src/rules/style/concat_array_literals.rs +3 -3
  249. data/src/rules/style/conditional.rs +12 -1
  250. data/src/rules/style/conditional_assignment.rs +133 -4
  251. data/src/rules/style/constant_visibility.rs +3 -3
  252. data/src/rules/style/copyright.rs +64 -3
  253. data/src/rules/style/data_inheritance.rs +1 -1
  254. data/src/rules/style/def_with_parentheses.rs +1 -1
  255. data/src/rules/style/document_dynamic_eval_definition.rs +50 -12
  256. data/src/rules/style/documentation.rs +1 -1
  257. data/src/rules/style/each_for_simple_loop.rs +1 -1
  258. data/src/rules/style/each_with_object.rs +4 -4
  259. data/src/rules/style/empty_block_parameter.rs +1 -1
  260. data/src/rules/style/empty_case_condition.rs +6 -6
  261. data/src/rules/style/empty_class_definition.rs +1 -1
  262. data/src/rules/style/empty_else.rs +1 -1
  263. data/src/rules/style/empty_heredoc.rs +14 -1
  264. data/src/rules/style/empty_lambda_parameter.rs +1 -1
  265. data/src/rules/style/empty_literal.rs +34 -2
  266. data/src/rules/style/empty_method.rs +1 -1
  267. data/src/rules/style/empty_string_inside_interpolation.rs +1 -1
  268. data/src/rules/style/endless.rs +11 -3
  269. data/src/rules/style/env_home.rs +1 -1
  270. data/src/rules/style/eval_with_location.rs +6 -0
  271. data/src/rules/style/even_odd.rs +1 -1
  272. data/src/rules/style/exact_regexp_match.rs +1 -1
  273. data/src/rules/style/expand_path_arguments.rs +3 -3
  274. data/src/rules/style/explicit_block_argument.rs +3 -3
  275. data/src/rules/style/file_open.rs +5 -0
  276. data/src/rules/style/file_open_call.rs +3 -3
  277. data/src/rules/style/file_write.rs +1 -1
  278. data/src/rules/style/for.rs +1 -1
  279. data/src/rules/style/format_sequences.rs +6 -0
  280. data/src/rules/style/format_string.rs +75 -11
  281. data/src/rules/style/format_string_token.rs +61 -12
  282. data/src/rules/style/frozen_string_literal_comment.rs +62 -4
  283. data/src/rules/style/guard_clause.rs +8 -7
  284. data/src/rules/style/hash_as_last_array_item.rs +4 -4
  285. data/src/rules/style/hash_each_methods.rs +1 -1
  286. data/src/rules/style/hash_like_case.rs +8 -8
  287. data/src/rules/style/hash_lookup_method.rs +43 -3
  288. data/src/rules/style/hash_subset.rs +16 -10
  289. data/src/rules/style/hash_syntax.rs +609 -71
  290. data/src/rules/style/hash_transform.rs +20 -18
  291. data/src/rules/style/hash_transform_keys.rs +7 -0
  292. data/src/rules/style/hash_transform_values.rs +7 -0
  293. data/src/rules/style/identical_conditional_branches.rs +54 -5
  294. data/src/rules/style/if_inside_else.rs +19 -3
  295. data/src/rules/style/if_unless_modifier.rs +13 -14
  296. data/src/rules/style/if_with_boolean_literal_branches.rs +1 -1
  297. data/src/rules/style/if_with_semicolon.rs +7 -1
  298. data/src/rules/style/implicit_runtime_error.rs +7 -0
  299. data/src/rules/style/in_pattern_then.rs +2 -2
  300. data/src/rules/style/infinite_loop.rs +6 -0
  301. data/src/rules/style/inline_comment.rs +4 -1
  302. data/src/rules/style/inverse_methods.rs +12 -2
  303. data/src/rules/style/invertible_unless_condition.rs +12 -12
  304. data/src/rules/style/it_block_parameter.rs +5 -2
  305. data/src/rules/style/keyword_arguments_merging.rs +8 -2
  306. data/src/rules/style/keyword_parameters_order.rs +1 -1
  307. data/src/rules/style/line_end_concatenation.rs +1 -1
  308. data/src/rules/style/magic_comment_format.rs +14 -6
  309. data/src/rules/style/map_chain.rs +1 -1
  310. data/src/rules/style/map_compact_with_conditional_block.rs +3 -3
  311. data/src/rules/style/map_into_array.rs +7 -7
  312. data/src/rules/style/map_join.rs +3 -3
  313. data/src/rules/style/method_call_with_args_parentheses.rs +7 -7
  314. data/src/rules/style/method_call_without_args_parentheses.rs +2 -2
  315. data/src/rules/style/method_def_parentheses.rs +2 -3
  316. data/src/rules/style/min_max.rs +3 -3
  317. data/src/rules/style/min_max_comparison.rs +2 -2
  318. data/src/rules/style/missing_else.rs +1 -1
  319. data/src/rules/style/missing_respond_to_missing.rs +1 -1
  320. data/src/rules/style/mixin_grouping.rs +6 -7
  321. data/src/rules/style/multiline_block_chain.rs +6 -8
  322. data/src/rules/style/multiline_if_then.rs +1 -1
  323. data/src/rules/style/multiline_in_pattern_then.rs +16 -3
  324. data/src/rules/style/multiline_method_signature.rs +1 -1
  325. data/src/rules/style/multiline_when_then.rs +2 -2
  326. data/src/rules/style/mutable_constant.rs +31 -35
  327. data/src/rules/style/negated_if.rs +2 -2
  328. data/src/rules/style/negated_if_else_condition.rs +5 -3
  329. data/src/rules/style/negated_while.rs +1 -1
  330. data/src/rules/style/negative_array_index.rs +25 -3
  331. data/src/rules/style/nested_modifier.rs +1 -1
  332. data/src/rules/style/nested_parenthesized_calls.rs +2 -2
  333. data/src/rules/style/nested_ternary_operator.rs +2 -2
  334. data/src/rules/style/next.rs +12 -12
  335. data/src/rules/style/nil_lambda.rs +2 -2
  336. data/src/rules/style/nodes.rs +21 -0
  337. data/src/rules/style/non_nil_check.rs +82 -2
  338. data/src/rules/style/numbered_parameters.rs +4 -1
  339. data/src/rules/style/numbered_parameters_limit.rs +2 -2
  340. data/src/rules/style/numeric_literals.rs +17 -1
  341. data/src/rules/style/numeric_predicate.rs +25 -6
  342. data/src/rules/style/one_line_conditional.rs +24 -7
  343. data/src/rules/style/operator_method_call.rs +5 -0
  344. data/src/rules/style/optional_arguments.rs +1 -1
  345. data/src/rules/style/or_assignment.rs +1 -1
  346. data/src/rules/style/parallel_assignment.rs +20 -9
  347. data/src/rules/style/parens.rs +1 -1
  348. data/src/rules/style/parentheses_around_condition.rs +3 -3
  349. data/src/rules/style/partition_instead_of_double_select.rs +3 -3
  350. data/src/rules/style/percent.rs +18 -9
  351. data/src/rules/style/percent_q_literals.rs +40 -7
  352. data/src/rules/style/preferred_hash_methods.rs +1 -1
  353. data/src/rules/style/quoted_symbols.rs +6 -0
  354. data/src/rules/style/raise_args.rs +26 -20
  355. data/src/rules/style/random_with_offset.rs +1 -1
  356. data/src/rules/style/reduce_to_hash.rs +6 -6
  357. data/src/rules/style/redundant_argument.rs +1 -1
  358. data/src/rules/style/redundant_assignment.rs +5 -5
  359. data/src/rules/style/redundant_begin.rs +5 -3
  360. data/src/rules/style/redundant_capital_w.rs +3 -2
  361. data/src/rules/style/redundant_condition.rs +17 -8
  362. data/src/rules/style/redundant_current_directory_in_path.rs +2 -2
  363. data/src/rules/style/redundant_double_splat_hash_braces.rs +15 -5
  364. data/src/rules/style/redundant_exception.rs +3 -1
  365. data/src/rules/style/redundant_file_extension_in_require.rs +2 -2
  366. data/src/rules/style/redundant_format.rs +98 -20
  367. data/src/rules/style/redundant_freeze.rs +1 -1
  368. data/src/rules/style/redundant_heredoc_delimiter_quotes.rs +1 -1
  369. data/src/rules/style/redundant_initialize.rs +6 -3
  370. data/src/rules/style/redundant_interpolation.rs +8 -8
  371. data/src/rules/style/redundant_interpolation_unfreeze.rs +10 -3
  372. data/src/rules/style/redundant_line_continuation.rs +10 -7
  373. data/src/rules/style/redundant_parentheses.rs +96 -19
  374. data/src/rules/style/redundant_regexp_argument.rs +1 -1
  375. data/src/rules/style/redundant_regexp_character_class.rs +4 -3
  376. data/src/rules/style/redundant_regexp_escape.rs +3 -2
  377. data/src/rules/style/redundant_return.rs +1 -2
  378. data/src/rules/style/redundant_self.rs +28 -16
  379. data/src/rules/style/redundant_sort.rs +1 -1
  380. data/src/rules/style/redundant_sort_by.rs +1 -1
  381. data/src/rules/style/redundant_string_escape.rs +6 -5
  382. data/src/rules/style/regexp_literal.rs +3 -2
  383. data/src/rules/style/require_order.rs +2 -2
  384. data/src/rules/style/rescue_modifier.rs +9 -4
  385. data/src/rules/style/rescue_standard_error.rs +1 -1
  386. data/src/rules/style/return_nil.rs +2 -2
  387. data/src/rules/style/return_nil_in_predicate_method_definition.rs +2 -2
  388. data/src/rules/style/safe_navigation.rs +2 -2
  389. data/src/rules/style/safe_navigation_chain_length.rs +8 -1
  390. data/src/rules/style/sample.rs +3 -3
  391. data/src/rules/style/select_by.rs +6 -2
  392. data/src/rules/style/select_by_range.rs +1 -1
  393. data/src/rules/style/semicolon.rs +77 -4
  394. data/src/rules/style/signal_exception.rs +60 -3
  395. data/src/rules/style/single_argument_dig.rs +10 -2
  396. data/src/rules/style/single_line_block_params.rs +14 -4
  397. data/src/rules/style/single_line_do_end_block.rs +13 -12
  398. data/src/rules/style/single_line_methods.rs +2 -2
  399. data/src/rules/style/slicing_with_range.rs +2 -2
  400. data/src/rules/style/sole_nested_conditional.rs +4 -4
  401. data/src/rules/style/special_global_vars.rs +2 -2
  402. data/src/rules/style/stabby_lambda_parentheses.rs +1 -1
  403. data/src/rules/style/statement_modifier.rs +3 -2
  404. data/src/rules/style/static_class.rs +7 -1
  405. data/src/rules/style/string_concatenation.rs +12 -7
  406. data/src/rules/style/string_hash_keys.rs +3 -2
  407. data/src/rules/style/string_literals.rs +3 -3
  408. data/src/rules/style/string_literals_in_interpolation.rs +4 -1
  409. data/src/rules/style/struct_inheritance.rs +3 -3
  410. data/src/rules/style/super_arguments.rs +15 -7
  411. data/src/rules/style/super_with_args_parentheses.rs +49 -12
  412. data/src/rules/style/swap_values.rs +1 -1
  413. data/src/rules/style/symbol_array.rs +9 -2
  414. data/src/rules/style/symbol_proc.rs +3 -3
  415. data/src/rules/style/tally_method.rs +4 -4
  416. data/src/rules/style/ternary_parentheses.rs +30 -15
  417. data/src/rules/style/top_level_method_definition.rs +10 -5
  418. data/src/rules/style/trailing_body.rs +1 -1
  419. data/src/rules/style/trailing_comma.rs +206 -10
  420. data/src/rules/style/trailing_comma_in_arguments.rs +9 -10
  421. data/src/rules/style/trailing_comma_in_array_literal.rs +2 -1
  422. data/src/rules/style/trailing_comma_in_hash_literal.rs +2 -1
  423. data/src/rules/style/trailing_method_end_statement.rs +6 -1
  424. data/src/rules/style/trailing_underscore_variable.rs +4 -5
  425. data/src/rules/style/trivial_accessors.rs +2 -2
  426. data/src/rules/style/unless_logical_operators.rs +1 -1
  427. data/src/rules/style/unpack_first.rs +11 -2
  428. data/src/rules/style/when_then.rs +4 -4
  429. data/src/rules/style/while_until_do.rs +1 -1
  430. data/src/rules/style/while_until_modifier.rs +3 -3
  431. data/src/rules/style/word_array.rs +9 -7
  432. data/src/rules/style/yoda_condition.rs +2 -2
  433. data/src/rules/style/zero_length_predicate.rs +5 -0
  434. data/src/rules/support.rs +128 -11
  435. data/src/source.rs +18 -15
  436. metadata +5 -1
@@ -136,17 +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,
149
+ /// The references Naming handlers see. This includes implicit numbered block parameters,
150
+ /// which are local-variable reads but are not declarations VariableForce reports.
151
+ naming_references: crate::rules::IdSet,
146
152
  /// The `foo()` calls whose name a local variable already holds. The parentheses make these
147
153
  /// calls whatever the name resolves to, so nothing here is an `lvar` -- but writing the same
148
154
  /// name without them would be one, which is the question a cop that drops parentheses asks.
149
- shadowed_calls: HashSet<usize>,
155
+ shadowed_calls: crate::rules::IdSet,
156
+ /// Receiverless call-name nodes whose spelling is already held by a local variable. Unlike
157
+ /// `shadowed_calls`, this also records calls with arguments for syntax recovery such as
158
+ /// `collection [0]`.
159
+ local_method_names: crate::rules::IdSet,
160
+ /// The identifier nodes that RuboCop dispatches as variable definitions. Pattern bindings and
161
+ /// explicit block-local variables deliberately stay out: upstream names them `match_var` and
162
+ /// `shadowarg`, neither of which the Naming cops handle.
163
+ naming_definitions: crate::rules::IdSet,
150
164
  }
151
165
 
152
166
  impl<'tree> Analysis<'tree> {
@@ -161,27 +175,78 @@ impl<'tree> Analysis<'tree> {
161
175
  self.shadowed_calls.contains(&node.id())
162
176
  }
163
177
 
164
- pub(in crate::rules) fn run(root: Node<'tree>, source: &SourceFile) -> Self {
178
+ pub(in crate::rules) fn names_a_local(&self, node: Node<'_>) -> bool {
179
+ self.local_method_names.contains(&node.id())
180
+ }
181
+
182
+ /// Whether the Naming cops see this node as a variable read or definition.
183
+ pub(in crate::rules) fn is_naming_variable(&self, node: Node<'_>) -> bool {
184
+ self.is_naming_definition(node) || self.naming_references.contains(&node.id())
185
+ }
186
+
187
+ pub(in crate::rules) fn is_variable(&self, node: Node<'_>) -> bool {
188
+ self.is_naming_variable(node)
189
+ }
190
+
191
+ pub(in crate::rules) fn is_reference(&self, node: Node<'_>) -> bool {
192
+ self.naming_references.contains(&node.id())
193
+ }
194
+
195
+ /// Whether this node is the target of the `lvasgn` shape upstream exposes. Parameter
196
+ /// declarations are naming definitions too, but an AST walk restricted to `lvasgn` does not
197
+ /// visit them.
198
+ pub(in crate::rules) fn is_local_assignment(&self, node: Node<'_>) -> bool {
199
+ self.is_naming_definition(node) && structural_definition(node, self.index)
200
+ }
201
+
202
+ /// Whether the parser would dispatch this node to a Naming variable-definition handler.
203
+ pub(in crate::rules) fn is_naming_definition(&self, node: Node<'_>) -> bool {
204
+ if node.kind_str() == "identifier" {
205
+ return self.naming_definitions.contains(&node.id());
206
+ }
207
+ matches!(
208
+ node.kind_str(),
209
+ "instance_variable" | "class_variable" | "global_variable"
210
+ ) && structural_definition(node, self.index)
211
+ }
212
+
213
+ pub(in crate::rules) fn is_definition(&self, node: Node<'_>) -> bool {
214
+ self.is_naming_definition(node)
215
+ }
216
+
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();
165
222
  let mut force = Force {
223
+ index,
166
224
  source,
167
225
  scopes: Vec::new(),
168
226
  variables: Vec::new(),
169
227
  stack: Vec::new(),
170
228
  branches: Vec::new(),
171
229
  branch_index: HashMap::new(),
172
- heredocs: heredoc_bodies(root),
173
- scanned: HashSet::new(),
174
- lvars: HashSet::new(),
175
- shadowed_calls: 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(),
176
237
  };
177
238
  force.push_scope(root, true);
178
239
  force.process_children(root);
179
240
  force.pop_scope();
180
241
  Analysis {
242
+ index,
181
243
  scopes: force.scopes,
182
244
  variables: force.variables,
183
245
  lvars: force.lvars,
246
+ naming_references: force.naming_references,
184
247
  shadowed_calls: force.shadowed_calls,
248
+ local_method_names: force.local_method_names,
249
+ naming_definitions: force.naming_definitions,
185
250
  }
186
251
  }
187
252
  }
@@ -207,6 +272,7 @@ struct Frame<'tree> {
207
272
  }
208
273
 
209
274
  struct Force<'tree, 'a> {
275
+ index: &'tree super::super::AstIndex<'tree>,
210
276
  source: &'a SourceFile,
211
277
  scopes: Vec<Scope<'tree>>,
212
278
  variables: Vec<Variable<'tree>>,
@@ -216,11 +282,14 @@ struct Force<'tree, 'a> {
216
282
  /// Each heredoc's body, found from the `<<~X` that opened it. tree-sitter hangs the body off
217
283
  /// the enclosing statement, so a heredoc written inside a block would otherwise have its
218
284
  /// interpolations resolved in the wrong scope.
219
- heredocs: HashMap<usize, Node<'tree>>,
285
+ heredocs: crate::rules::IdKeyed<Node<'tree>>,
220
286
  /// Nodes already walked in an outer scope, which the scope they sit in must not walk again.
221
- scanned: HashSet<usize>,
222
- lvars: HashSet<usize>,
223
- shadowed_calls: 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,
224
293
  }
225
294
 
226
295
  // ---------------------------------------------------------------------------
@@ -242,6 +311,34 @@ fn mislexed_match_operator(node: Node<'_>, source: &SourceFile) -> bool {
242
311
  operator.end_byte() == right.start_byte() && source.node_text(right).starts_with('~')
243
312
  }
244
313
 
314
+ /// Whether a non-local variable node is written in a position RuboCop dispatches as a variable
315
+ /// definition. Local identifiers are recorded during the force walk, but instance, class and
316
+ /// global variables do not participate in local-variable resolution and are cheaper to classify
317
+ /// directly from their parent.
318
+ fn structural_definition(node: Node<'_>, index: &super::super::AstIndex<'_>) -> bool {
319
+ let Some(parent) = index.parent(node) else {
320
+ return false;
321
+ };
322
+ match parent.kind_str() {
323
+ "assignment" | "operator_assignment" => parent
324
+ .field("left")
325
+ .is_some_and(|left| left.id() == node.id()),
326
+ "left_assignment_list" if spurious_assignment_list(parent) => {
327
+ let mut cursor = parent.walk();
328
+ parent
329
+ .named_children(&mut cursor)
330
+ .last()
331
+ .is_some_and(|last| last.id() == node.id())
332
+ }
333
+ "left_assignment_list" | "destructured_left_assignment" | "rest_assignment" => true,
334
+ "for" => parent
335
+ .field("pattern")
336
+ .is_some_and(|pattern| pattern.id() == node.id()),
337
+ "exception_variable" => true,
338
+ _ => false,
339
+ }
340
+ }
341
+
245
342
  /// What an assignment really stores. When the grammar swallowed the neighbouring items of a
246
343
  /// comma-separated list, the node it made the right-hand side spans all of them, and only its
247
344
  /// first element belongs to this write.
@@ -275,12 +372,29 @@ pub(super) fn scope_nodes<'tree>(scope: &Scope<'tree>) -> Vec<Node<'tree>> {
275
372
  }
276
373
 
277
374
  fn scan_scope<'tree>(node: Node<'tree>, scope_node: Node<'tree>, nodes: &mut Vec<Node<'tree>>) {
278
- for child in named_children(node) {
375
+ let mut cursor = node.walk();
376
+ if !cursor.goto_first_child() {
377
+ return;
378
+ }
379
+ loop {
380
+ let child = cursor.node();
381
+ if !child.is_named() {
382
+ if !cursor.goto_next_sibling() {
383
+ break;
384
+ }
385
+ continue;
386
+ }
279
387
  if !owned_by_scope(child, node, scope_node) {
388
+ if !cursor.goto_next_sibling() {
389
+ break;
390
+ }
280
391
  continue;
281
392
  }
282
393
  nodes.push(child);
283
394
  scan_scope(child, scope_node, nodes);
395
+ if !cursor.goto_next_sibling() {
396
+ break;
397
+ }
284
398
  }
285
399
  }
286
400
 
@@ -317,20 +431,33 @@ pub(super) fn body_node<'tree>(scope: &Scope<'tree>) -> Option<Node<'tree>> {
317
431
  }
318
432
 
319
433
  /// The call a block was written on, which names the method the block was passed to.
320
- 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>> {
321
438
  match scope_node.kind_str() {
322
- "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"),
323
442
  _ => None,
324
443
  }
325
444
  }
326
445
 
327
446
  /// `BlockNode#lambda?`: both `->() {}` and `lambda {}` reach RuboCop as a block on `lambda`.
328
- pub(super) fn is_lambda(scope_node: Node<'_>, source: &SourceFile) -> bool {
329
- 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")
330
453
  }
331
454
 
332
- pub(super) fn block_method<'a>(scope_node: Node<'_>, source: &'a SourceFile) -> Option<&'a str> {
333
- 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)?;
334
461
  Some(source.node_text(call.field("method")?))
335
462
  }
336
463
 
@@ -367,8 +494,15 @@ impl<'tree> Force<'tree, '_> {
367
494
  });
368
495
  }
369
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
+
370
503
  fn process_children(&mut self, node: Node<'tree>) {
371
- for child in named_children(node) {
504
+ let children = self.children(node);
505
+ for child in children.iter().copied() {
372
506
  if !self.scanned.contains(&child.id()) {
373
507
  self.process_node(child);
374
508
  }
@@ -391,6 +525,14 @@ impl<'tree> Force<'tree, '_> {
391
525
  }
392
526
  // `foo = 1 if bar` runs its condition first, but tree-sitter writes the body first.
393
527
  "if_modifier" | "unless_modifier" => {
528
+ // The parser registers a local the moment it *reads* the assignment, so the `paths`
529
+ // in `paths = [paths] unless paths.is_a?(Array)` is an `lvar` on both sides of the
530
+ // keyword -- which is the whole of what `Style/ArrayCoercion` matches on. Declaring
531
+ // the names the body writes, with no assignment behind them yet, gets that spelling
532
+ // right while the walk below still reaches them in execution order.
533
+ if let Some(body) = node.field("body") {
534
+ self.declare_lexically(body);
535
+ }
394
536
  if let Some(condition) = node.field("condition") {
395
537
  self.process_node(condition);
396
538
  }
@@ -430,8 +572,9 @@ impl<'tree> Force<'tree, '_> {
430
572
  /// block node holding both the parameters and the body, so the inner node is not a scope.
431
573
  fn inline_block(&self, node: Node<'tree>) -> bool {
432
574
  node.kind_str() == "block"
433
- && node
434
- .parent()
575
+ && self
576
+ .index
577
+ .parent(node)
435
578
  .is_some_and(|parent| parent.kind_str() == "lambda")
436
579
  }
437
580
 
@@ -469,6 +612,9 @@ impl<'tree> Force<'tree, '_> {
469
612
  name_node: Node<'tree>,
470
613
  kind: Declaration,
471
614
  ) {
615
+ if matches!(kind, Declaration::Argument(_)) {
616
+ self.naming_definitions.insert(name_node.id());
617
+ }
472
618
  let index = self.variables.len();
473
619
  let frame = self.stack.last_mut().expect("scope stack is never empty");
474
620
  self.variables.push(Variable {
@@ -496,6 +642,31 @@ impl<'tree> Force<'tree, '_> {
496
642
  }
497
643
  }
498
644
 
645
+ /// Introduces the names an assignment inside `node` would introduce, without recording the
646
+ /// assignment itself -- the lexical half of what the parser does when it reaches one.
647
+ ///
648
+ /// Anything opening a scope of its own is left alone: a name written inside a block or a `def`
649
+ /// does not survive it, so the parser does not spell it as a variable outside either.
650
+ fn declare_lexically(&mut self, node: Node<'tree>) {
651
+ if opens_a_scope(node) {
652
+ return;
653
+ }
654
+ // Only the plain `foo = ...` target is introduced here. A multiple assignment reaches the
655
+ // same names through `process_assignment`, whose handling of `left_assignment_list` is not
656
+ // worth duplicating for a shape the spelling question has never turned on.
657
+ if matches!(node.kind_str(), "assignment" | "operator_assignment")
658
+ && let Some(left) = node.field("left")
659
+ && left.kind_str() == "identifier"
660
+ {
661
+ let name = self.text(left).to_owned();
662
+ self.declare_unless_known(&name, node, left);
663
+ }
664
+ let mut cursor = node.walk();
665
+ for child in node.children(&mut cursor).collect::<Vec<_>>() {
666
+ self.declare_lexically(child);
667
+ }
668
+ }
669
+
499
670
  /// Whether the scope now being walked is a block other than the one the variable belongs to,
500
671
  /// which is what makes the variable outlive the reads this walk can see.
501
672
  fn capture_if_needed(&mut self, variable: usize) {
@@ -515,6 +686,9 @@ impl<'tree> Force<'tree, '_> {
515
686
  value: Option<Node<'tree>>,
516
687
  kind: AssignmentKind,
517
688
  ) {
689
+ if kind == AssignmentKind::Plain {
690
+ self.naming_definitions.insert(name.id());
691
+ }
518
692
  self.capture_if_needed(variable);
519
693
  let branch = self.branch_of(node);
520
694
  let captured = self.variables[variable].captured_by_block;
@@ -573,7 +747,7 @@ impl<'tree> Force<'tree, '_> {
573
747
  .push(node);
574
748
  }
575
749
  let assignment_node = self.variables[variable].assignments[index].node;
576
- if in_modifier_conditional(assignment_node, node) {
750
+ if in_modifier_conditional(assignment_node, node, self.index) {
577
751
  continue;
578
752
  }
579
753
  let Some(branch) = branch else { break };
@@ -728,23 +902,35 @@ impl<'tree> Force<'tree, '_> {
728
902
  // Only tree-sitter spells a method name with the same node type as a variable read; the
729
903
  // parser upstream turns `def foo` and `alias foo bar` into symbols. The receiver of
730
904
  // `def obj.foo` is a genuine read and stays.
731
- if node.parent().is_some_and(|parent| {
905
+ if self.index.parent(node).is_some_and(|parent| {
732
906
  matches!(parent.kind_str(), "alias" | "undef" | "setter")
733
907
  || (matches!(parent.kind_str(), "method" | "singleton_method")
734
- && field_name(node, parent) == Some("name"))
908
+ && field_name(node, parent, self.index) == Some("name"))
735
909
  }) {
736
910
  return;
737
911
  }
738
912
  let name = self.text(node);
739
913
  if let Some(variable) = self.find_variable(name) {
740
914
  self.lvars.insert(node.id());
915
+ self.naming_references.insert(node.id());
741
916
  self.reference(variable, node);
917
+ } else if implicit_numbered_parameter(name)
918
+ && self.stack.last().is_some_and(|frame| frame.block)
919
+ {
920
+ self.naming_references.insert(node.id());
742
921
  } else if name == "binding" {
743
922
  self.reference_everything(node);
744
923
  }
745
924
  }
746
925
 
747
926
  fn process_call(&mut self, node: Node<'tree>) {
927
+ if node.field("receiver").is_none()
928
+ && let Some(method) = node.field("method")
929
+ && method.kind_str() == "identifier"
930
+ && self.find_variable(self.text(method)).is_some()
931
+ {
932
+ self.local_method_names.insert(method.id());
933
+ }
748
934
  if self.binary_operator_on_a_local(node) {
749
935
  return;
750
936
  }
@@ -1168,7 +1354,7 @@ impl<'tree> Force<'tree, '_> {
1168
1354
  fn mark_assignments_referenced_in_loop(&mut self, node: Node<'tree>) {
1169
1355
  let mut names = Vec::new();
1170
1356
  let mut assignments = HashSet::new();
1171
- collect_loop_references(node, self.source, &mut names, &mut assignments);
1357
+ collect_loop_references(node, self.source, self.index, &mut names, &mut assignments);
1172
1358
  for name in names {
1173
1359
  let Some(variable) = self.find_variable(&name) else {
1174
1360
  continue;
@@ -1187,7 +1373,7 @@ impl<'tree> Force<'tree, '_> {
1187
1373
  };
1188
1374
  for &index in &indices {
1189
1375
  let assignment = &self.variables[variable].assignments[index];
1190
- if has_branch_ancestor(assignment.node) {
1376
+ if has_branch_ancestor(assignment.node, self.index) {
1191
1377
  self.variables[variable].assignments[index].referenced = true;
1192
1378
  }
1193
1379
  }
@@ -1216,8 +1402,8 @@ impl<'tree> Force<'tree, '_> {
1216
1402
  if !top_level && current.id() == scope_node.id() {
1217
1403
  return None;
1218
1404
  }
1219
- let parent = current.parent()?;
1220
- 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)
1221
1407
  && role.branched
1222
1408
  {
1223
1409
  return Some(self.intern_branch(role, parent, scope_node, top_level));
@@ -1278,14 +1464,22 @@ impl<'tree> Force<'tree, '_> {
1278
1464
  }
1279
1465
  }
1280
1466
 
1467
+ fn implicit_numbered_parameter(name: &str) -> bool {
1468
+ matches!(name.as_bytes(), [b'_', b'1'..=b'9'])
1469
+ }
1470
+
1281
1471
  // ---------------------------------------------------------------------------
1282
1472
  // Node helpers
1283
1473
  // ---------------------------------------------------------------------------
1284
1474
 
1285
1475
  /// Whether the child stands in a branch of the control structure above it, and how that branch
1286
1476
  /// behaves: `(may_run_incompletely, may_jump_to_other_branch, branched)`.
1287
- fn branch_role<'tree>(child: Node<'tree>, parent: Node<'tree>) -> Option<BranchRole<'tree>> {
1288
- 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);
1289
1483
  let always_run = BranchRole::always_run(child);
1290
1484
  let branched = BranchRole::branched(child);
1291
1485
  match parent.kind_str() {
@@ -1316,10 +1510,10 @@ fn branch_role<'tree>(child: Node<'tree>, parent: Node<'tree>) -> Option<BranchR
1316
1510
  // `begin … rescue … ensure … end` has no node of its own here: the clauses stand beside
1317
1511
  // the statements they guard. Upstream wraps those statements in one node, and the whole
1318
1512
  // main body is a single branch, so they all have to point at the same child.
1319
- _ if is_rescue_container(parent) => Some(match child.kind_str() {
1513
+ _ if is_rescue_container(parent, index) => Some(match child.kind_str() {
1320
1514
  "rescue" | "else" => branched,
1321
1515
  "ensure" => always_run,
1322
- _ => BranchRole::branched(main_body_anchor(parent)?).escaping(),
1516
+ _ => BranchRole::branched(main_body_anchor(parent, index)?).escaping(),
1323
1517
  }),
1324
1518
  _ => None,
1325
1519
  }
@@ -1364,23 +1558,45 @@ impl<'tree> BranchRole<'tree> {
1364
1558
  }
1365
1559
 
1366
1560
  /// The first statement of a `begin`'s main body, which stands for the whole of it.
1367
- fn main_body_anchor<'tree>(container: Node<'tree>) -> Option<Node<'tree>> {
1368
- named_children(container)
1369
- .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()
1370
1568
  .find(|child| !matches!(child.kind_str(), "rescue" | "else" | "ensure"))
1371
1569
  }
1372
1570
 
1373
- fn is_rescue_container(node: Node<'_>) -> bool {
1374
- 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)
1375
1576
  }
1376
1577
 
1377
- fn has_child_kind(node: Node<'_>, kind: &str) -> bool {
1378
- let mut cursor = node.walk();
1379
- node.named_children(&mut cursor)
1380
- .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)
1381
1586
  }
1382
1587
 
1383
- 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
+ }
1384
1600
  let mut cursor = parent.walk();
1385
1601
  if !cursor.goto_first_child() {
1386
1602
  return None;
@@ -1433,6 +1649,21 @@ fn opaque_binding_argument(node: Node<'_>) -> bool {
1433
1649
  }
1434
1650
  }
1435
1651
 
1652
+ /// The node types that hold their own local variables, which a name written inside does not leave.
1653
+ fn opens_a_scope(node: Node<'_>) -> bool {
1654
+ matches!(
1655
+ node.kind_str(),
1656
+ "method"
1657
+ | "singleton_method"
1658
+ | "class"
1659
+ | "singleton_class"
1660
+ | "module"
1661
+ | "block"
1662
+ | "do_block"
1663
+ | "lambda"
1664
+ )
1665
+ }
1666
+
1436
1667
  fn contains_kind(node: Node<'_>, kind: &str) -> bool {
1437
1668
  if node.kind_str() == kind {
1438
1669
  return true;
@@ -1444,9 +1675,12 @@ fn contains_kind(node: Node<'_>, kind: &str) -> bool {
1444
1675
 
1445
1676
  /// The branch node types `reference_assignments` looks for: an assignment under one of them may
1446
1677
  /// be skipped on some iterations, so every one of them counts as read by the loop.
1447
- 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 {
1448
1682
  let mut current = node;
1449
- while let Some(parent) = current.parent() {
1683
+ while let Some(parent) = index.parent(current) {
1450
1684
  let branching = matches!(
1451
1685
  parent.kind_str(),
1452
1686
  "if" | "elsif"
@@ -1458,7 +1692,8 @@ fn has_branch_ancestor(node: Node<'_>) -> bool {
1458
1692
  | "case_match"
1459
1693
  | "rescue"
1460
1694
  | "rescue_modifier"
1461
- ) || (parent.kind_str() != "program" && has_child_kind(parent, "rescue"));
1695
+ ) || (parent.kind_str() != "program"
1696
+ && has_child_kind(parent, "rescue", index));
1462
1697
  if branching {
1463
1698
  return true;
1464
1699
  }
@@ -1472,6 +1707,7 @@ fn has_branch_ancestor(node: Node<'_>) -> bool {
1472
1707
  fn collect_loop_references(
1473
1708
  node: Node<'_>,
1474
1709
  source: &SourceFile,
1710
+ index: &super::super::AstIndex<'_>,
1475
1711
  names: &mut Vec<String>,
1476
1712
  assignments: &mut HashSet<String>,
1477
1713
  ) {
@@ -1514,15 +1750,15 @@ fn collect_loop_references(
1514
1750
  // target all wear the same node type here. A `for` variable and a rescue clause's
1515
1751
  // variable are `lvasgn` nodes that carry no value.
1516
1752
  "identifier" => {
1517
- if is_variable_read(child) {
1753
+ if is_variable_read(child, index) {
1518
1754
  names.push(source.node_text(child).to_owned());
1519
- } else if bare_assignment_target(child) {
1755
+ } else if bare_assignment_target(child, index) {
1520
1756
  assignments.insert(shape(child, None, source));
1521
1757
  }
1522
1758
  }
1523
1759
  _ => {}
1524
1760
  }
1525
- collect_loop_references(child, source, names, assignments);
1761
+ collect_loop_references(child, source, index, names, assignments);
1526
1762
  }
1527
1763
  }
1528
1764
 
@@ -1557,15 +1793,15 @@ fn identifier_words(text: &str) -> Vec<String> {
1557
1793
 
1558
1794
  /// Whether an identifier stands where the parser upstream would have built an `lvar`, rather than
1559
1795
  /// a name being declared, written or called.
1560
- pub(super) fn is_variable_read(node: Node<'_>) -> bool {
1561
- 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 {
1562
1798
  return true;
1563
1799
  };
1564
1800
  match parent.kind_str() {
1565
1801
  // The receiver of `foo.bar` and of `def obj.baz` is read; the selector is not.
1566
- "call" => field_name(node, parent) != Some("method"),
1567
- "method" | "singleton_method" => field_name(node, parent) != Some("name"),
1568
- "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"),
1569
1805
  "left_assignment_list"
1570
1806
  | "rest_assignment"
1571
1807
  | "destructured_left_assignment"
@@ -1582,8 +1818,8 @@ pub(super) fn is_variable_read(node: Node<'_>) -> bool {
1582
1818
  | "keyword_parameter"
1583
1819
  | "splat_parameter"
1584
1820
  | "hash_splat_parameter"
1585
- | "block_parameter" => field_name(node, parent) != Some("name"),
1586
- "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"),
1587
1823
  _ => true,
1588
1824
  }
1589
1825
  }
@@ -1600,12 +1836,12 @@ fn swallowed_by_escape(node: Node<'_>, source: &SourceFile) -> bool {
1600
1836
  }
1601
1837
 
1602
1838
  /// Whether an identifier is a write that stores no value of its own.
1603
- fn bare_assignment_target(node: Node<'_>) -> bool {
1604
- 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 {
1605
1841
  return false;
1606
1842
  };
1607
1843
  match parent.kind_str() {
1608
- "for" => field_name(node, parent) == Some("pattern"),
1844
+ "for" => field_name(node, parent, index) == Some("pattern"),
1609
1845
  "exception_variable" => true,
1610
1846
  _ => false,
1611
1847
  }
@@ -1674,9 +1910,13 @@ fn named_captures(source: &str) -> Vec<String> {
1674
1910
 
1675
1911
  /// `in_modifier_conditional?`: an assignment made in `foo = 1 if bar` is not in scope to the left
1676
1912
  /// of the keyword, so a read there cannot be the one that uses it.
1677
- 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 {
1678
1918
  let mut current = assignment;
1679
- while let Some(parent) = current.parent() {
1919
+ while let Some(parent) = index.parent(current) {
1680
1920
  if matches!(
1681
1921
  parent.kind_str(),
1682
1922
  "if_modifier" | "unless_modifier" | "while_modifier" | "until_modifier"
@@ -1697,10 +1937,13 @@ fn covers(container: Node<'_>, node: Node<'_>) -> bool {
1697
1937
 
1698
1938
  /// Each heredoc's body, paired with the `<<~X` that opened it. Both appear in the same order in
1699
1939
  /// the file, which is what makes matching them by position sound.
1700
- fn heredoc_bodies(root: Node<'_>) -> HashMap<usize, Node<'_>> {
1701
- let mut beginnings = Vec::new();
1702
- let mut bodies = Vec::new();
1703
- 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();
1704
1947
  beginnings.sort_by_key(|node: &Node<'_>| node.start_byte());
1705
1948
  bodies.sort_by_key(|node: &Node<'_>| node.start_byte());
1706
1949
  beginnings
@@ -1709,18 +1952,3 @@ fn heredoc_bodies(root: Node<'_>) -> HashMap<usize, Node<'_>> {
1709
1952
  .map(|(beginning, body)| (beginning.id(), body))
1710
1953
  .collect()
1711
1954
  }
1712
-
1713
- fn collect_heredocs<'tree>(
1714
- node: Node<'tree>,
1715
- beginnings: &mut Vec<Node<'tree>>,
1716
- bodies: &mut Vec<Node<'tree>>,
1717
- ) {
1718
- match node.kind_str() {
1719
- "heredoc_beginning" => beginnings.push(node),
1720
- "heredoc_body" => bodies.push(node),
1721
- _ => {}
1722
- }
1723
- for child in named_children(node) {
1724
- collect_heredocs(child, beginnings, bodies);
1725
- }
1726
- }