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
@@ -17,7 +17,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
17
17
  }
18
18
  let Some(argument) = node
19
19
  .field("arguments")
20
- .and_then(|arguments| super::nodes::children(arguments).into_iter().next())
20
+ .and_then(|arguments| super::nodes::children_in(arguments, context).into_iter().next())
21
21
  else {
22
22
  continue;
23
23
  };
@@ -56,7 +56,7 @@ fn leading_path_content<'a>(node: Node<'_>, context: &'a RuleContext<'_>) -> Opt
56
56
  if node.kind_str() != "string" {
57
57
  return None;
58
58
  }
59
- let first = super::nodes::children(node).into_iter().next()?;
59
+ let first = super::nodes::children_in(node, context).into_iter().next()?;
60
60
  if first.kind_str() != "string_content" {
61
61
  return None;
62
62
  }
@@ -14,7 +14,15 @@ const MERGE_METHODS: &[&str] = &["merge", "merge!"];
14
14
 
15
15
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
16
16
  for node in context.nodes_of("hash") {
17
- let pairs = super::nodes::children(node);
17
+ // `node.pairs` lists the `pair` children alone -- a nested `**{…}` is a `kwsplat` and is
18
+ // not one of them. Counting it as a pair made every hash holding one look rocket-written
19
+ // and dropped the **outer** hash of `**{a: 1, **{b: 2}}` on the floor.
20
+ let written = super::nodes::children_in(node, context);
21
+ let pairs: Vec<Node<'_>> = written
22
+ .iter()
23
+ .copied()
24
+ .filter(|child| child.kind_str() == "pair")
25
+ .collect();
18
26
  if pairs.is_empty() || pairs.iter().any(|pair| is_hash_rocket(*pair, context)) {
19
27
  continue;
20
28
  }
@@ -35,7 +43,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
35
43
  offenses.push(
36
44
  context
37
45
  .offense(MSG, splat.byte_range())
38
- .corrected_by_all(autocorrect(context, node, splat, &pairs)),
46
+ .corrected_by_all(autocorrect(context, node, splat, &written)),
39
47
  );
40
48
  }
41
49
  }
@@ -98,6 +106,8 @@ fn autocorrect(
98
106
  context: &RuleContext<'_>,
99
107
  node: Node<'_>,
100
108
  splat: Node<'_>,
109
+ // Everything the braces hold, `kwsplat` children included: the braces come off around all of
110
+ // it, not just around the pairs.
101
111
  pairs: &[Node<'_>],
102
112
  ) -> Vec<Edit> {
103
113
  let mut edits = Vec::new();
@@ -157,15 +167,15 @@ fn autocorrect(
157
167
  }
158
168
 
159
169
  /// `select_merge_method_nodes`, in the order `each_descendant` walks them.
160
- fn merge_calls<'tree>(splat: Node<'tree>, context: &RuleContext<'_>) -> Vec<Node<'tree>> {
170
+ fn merge_calls<'tree>(splat: Node<'tree>, context: &'tree RuleContext<'_>) -> Vec<Node<'tree>> {
161
171
  let mut found = Vec::new();
162
172
  let mut stack: Vec<Node<'tree>> = Vec::new();
163
- crate::rules::push_named_children(splat, &mut stack);
173
+ crate::rules::push_named_children_in(splat, context, &mut stack);
164
174
  while let Some(node) = stack.pop() {
165
175
  if node.kind_str() == "call" && mergeable(node, context) {
166
176
  found.push(node);
167
177
  }
168
- crate::rules::push_named_children(node, &mut stack);
178
+ crate::rules::push_named_children_in(node, context, &mut stack);
169
179
  }
170
180
  found.sort_by_key(Node::start_byte);
171
181
  found
@@ -117,7 +117,9 @@ fn argument_source(context: &RuleContext<'_>, message: &Argument<'_>) -> String
117
117
  let literal = message.parts().len() == 1
118
118
  && matches!(
119
119
  message.first().kind_str(),
120
- "string" | "chained_string" | "character" | "heredoc_beginning"
120
+ // `any_str_type?` is `str`, `dstr` **and `xstr`** -- a backtick command is a string
121
+ // literal, so it needs no `.to_s`.
122
+ "string" | "chained_string" | "character" | "heredoc_beginning" | "subshell"
121
123
  );
122
124
  if literal {
123
125
  text.to_owned()
@@ -21,13 +21,13 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
21
21
  let Some(arguments) = node.field("arguments") else {
22
22
  continue;
23
23
  };
24
- let [name] = super::nodes::children(arguments)[..] else {
24
+ let [name] = super::nodes::children_in(arguments, context)[..] else {
25
25
  continue;
26
26
  };
27
27
  // `$str_type?`: an interpolated or multi-line literal is a `dstr` upstream.
28
28
  if name.kind_str() != "string"
29
29
  || name.start_position().row != name.end_position().row
30
- || super::nodes::children(name)
30
+ || super::nodes::children_in(name, context)
31
31
  .iter()
32
32
  .any(|child| child.kind_str() == "interpolation")
33
33
  {
@@ -10,6 +10,8 @@ use crate::rules::ruby_literal::{string_value, unescape};
10
10
  use crate::rules::send_node::{
11
11
  Argument, arguments, has_interpolation, heredoc_body, send_range, symbol_name,
12
12
  };
13
+ use crate::rules::send_node::all_children_of;
14
+ use crate::rules::send_node::named_children_iter;
13
15
 
14
16
  /// `RESTRICT_ON_SEND`.
15
17
  const FORMAT_METHODS: &[&str] = &["format", "sprintf"];
@@ -97,8 +99,8 @@ fn static_string_value(node: Node<'_>, context: &RuleContext<'_>) -> Option<Stri
97
99
  "string" if !has_interpolation(node) => Some(string_value(node, context)),
98
100
  "string" => {
99
101
  let mut text = String::new();
100
- let mut cursor = node.walk();
101
- for child in node.named_children(&mut cursor) {
102
+ let _cursor = node.walk();
103
+ for child in named_children_iter(node, context) {
102
104
  if child.kind_str() != "interpolation" {
103
105
  text.push_str(&string_value(node, context));
104
106
  break;
@@ -108,8 +110,8 @@ fn static_string_value(node: Node<'_>, context: &RuleContext<'_>) -> Option<Stri
108
110
  }
109
111
  "chained_string" => {
110
112
  let mut text = String::new();
111
- let mut cursor = node.walk();
112
- for child in node.named_children(&mut cursor) {
113
+ let _cursor = node.walk();
114
+ for child in named_children_iter(node, context) {
113
115
  if child.kind_str() == "string" && !has_interpolation(child) {
114
116
  text.push_str(&string_value(child, context));
115
117
  }
@@ -120,8 +122,8 @@ fn static_string_value(node: Node<'_>, context: &RuleContext<'_>) -> Option<Stri
120
122
  "heredoc_beginning" => {
121
123
  let body = heredoc_body(node, context)?;
122
124
  let mut text = String::new();
123
- let mut cursor = body.walk();
124
- for child in body.named_children(&mut cursor) {
125
+ let _cursor = body.walk();
126
+ for child in named_children_iter(body, context) {
125
127
  if child.kind_str() == "heredoc_content" {
126
128
  text.push_str(context.source.node_text(child));
127
129
  }
@@ -148,6 +150,11 @@ fn detect_unnecessary_fields(
148
150
  if template.kind_str() != "string" || has_interpolation(*template) || rest.is_empty() {
149
151
  return;
150
152
  }
153
+ // `$str`: a template written over lines is a `dstr` of one `str` per line, which the pattern
154
+ // does not match.
155
+ if crate::rules::support::quoted_spans_lines(context.source.node_text(*template)) {
156
+ return;
157
+ }
151
158
  if splatted_arguments(rest) {
152
159
  return;
153
160
  }
@@ -204,6 +211,22 @@ fn all_fields_literal(
204
211
  if unknown_variable_width(sequence, &pending, values) {
205
212
  continue;
206
213
  }
214
+ // `%999…$d`: upstream raises on an argument number no argument answers, and a raising
215
+ // `format` is not a redundant one. Reading the unparsable number as "no number given"
216
+ // instead made the cop fold the call to whatever the first argument printed as.
217
+ if unreadable_argument_number(sequence) {
218
+ return false;
219
+ }
220
+ // `format('%{y}', 2015)` raises `ArgumentError: one hash required`. A named sequence has
221
+ // nothing to read without a hash to read it from, so there is no value to fold to.
222
+ if hash.is_none()
223
+ && matches!(
224
+ sequence.style,
225
+ SequenceStyle::Annotated | SequenceStyle::Template
226
+ )
227
+ {
228
+ return false;
229
+ }
207
230
  let Some(index) = find_argument(sequence, &mut pending, hash, values) else {
208
231
  continue;
209
232
  };
@@ -348,11 +371,30 @@ fn as_integer(value: &Value) -> Option<i128> {
348
371
  Value::Float(number) => Some(number.trunc() as i128),
349
372
  // `Integer('0x10')` is 16: the prefixes count, which a plain parse would refuse.
350
373
  Value::Text(text) => parse_integer(text.trim()),
374
+ // `Integer((5+0i))` is 5: a complex whose imaginary part is zero converts, and one whose
375
+ // is not raises. `Value::Complex` keeps what the literal prints as, which is where the
376
+ // two halves are read from.
377
+ Value::Complex(text) => real_part_of_complex(text),
351
378
  Value::Computed(inner) => as_integer(inner),
352
379
  _ => None,
353
380
  }
354
381
  }
355
382
 
383
+ /// The real part of a complex literal whose imaginary part is zero, as `Integer()` reads it.
384
+ fn real_part_of_complex(text: &str) -> Option<i128> {
385
+ let body = text.trim();
386
+ let imaginary = body.rfind(['+', '-']).filter(|index| *index > 0)?;
387
+ if !body.ends_with('i') {
388
+ return None;
389
+ }
390
+ let tail = &body[imaginary + 1..body.len() - 1];
391
+ // `(5+0i)` converts, `(5+1i)` does not.
392
+ if tail.parse::<f64>().ok()? != 0.0 {
393
+ return None;
394
+ }
395
+ parse_integer(body[..imaginary].trim())
396
+ }
397
+
356
398
  /// `numeric?` together with `Float(value, exception: false)`.
357
399
  fn as_float(value: &Value) -> Option<f64> {
358
400
  match value {
@@ -360,6 +402,7 @@ fn as_float(value: &Value) -> Option<f64> {
360
402
  Value::Rational(numerator, denominator) => Some(*numerator as f64 / *denominator as f64),
361
403
  Value::Float(number) => Some(*number),
362
404
  Value::Text(text) => text.trim().parse::<f64>().ok(),
405
+ Value::Complex(text) => real_part_of_complex(text).map(|number| number as f64),
363
406
  Value::Computed(inner) => as_float(inner),
364
407
  _ => None,
365
408
  }
@@ -458,7 +501,7 @@ fn pair_value(context: &RuleContext<'_>, pair: Node<'_>) -> Option<(String, Valu
458
501
  /// `argument_value`, for the literals this cop can reproduce.
459
502
  fn argument_value(context: &RuleContext<'_>, node: Node<'_>) -> Option<Value> {
460
503
  let node = match node.kind_str() {
461
- "parenthesized_statements" => *super::nodes::children(node).first()?,
504
+ "parenthesized_statements" => *super::nodes::children_in(node, context).first()?,
462
505
  _ => node,
463
506
  };
464
507
  let text = context.source.node_text(node);
@@ -490,6 +533,9 @@ fn argument_value(context: &RuleContext<'_>, node: Node<'_>) -> Option<Value> {
490
533
  "delimited_symbol" if !has_interpolation(node) => {
491
534
  symbol_name(node, context).map(|name| Value::Text(name.to_owned()))
492
535
  }
536
+ // `dsym_value`: a symbol written with an interpolation formats to what its own text reads
537
+ // as, so `:"#{foo}"` carries the same value the string `"#{foo}"` does.
538
+ "delimited_symbol" => Some(Value::Dynamic(dstr_value(node, context))),
493
539
  // `?a`, which the parser resolves into the one-character string it stands for.
494
540
  "character" => Some(Value::Text(crate::rules::ruby_literal::character_value(
495
541
  text,
@@ -501,7 +547,7 @@ fn argument_value(context: &RuleContext<'_>, node: Node<'_>) -> Option<Value> {
501
547
  "complex" => complex_value(text).map(Value::Complex),
502
548
  "hash" => {
503
549
  let mut held = Vec::new();
504
- for pair in super::nodes::children(node) {
550
+ for pair in super::nodes::children_in(node, context) {
505
551
  held.push(pair_value(context, pair)?);
506
552
  }
507
553
  Some(Value::Pairs(held))
@@ -541,7 +587,10 @@ fn computed_number(context: &RuleContext<'_>, node: Node<'_>) -> Option<Value> {
541
587
  true => "",
542
588
  false => "+",
543
589
  };
544
- let imaginary = imaginary.trim_start_matches("0+").trim_start_matches("0");
590
+ // Only the `0+` real part that `complex_value` writes comes off. Trimming a further
591
+ // `0` turned `(5+0i)` into `5+i`, which is `5+1i` -- and `Integer()` then refused it,
592
+ // because a complex with a non-zero imaginary part does not convert.
593
+ let imaginary = imaginary.trim_start_matches("0+");
545
594
  Some(Value::Computed(Box::new(Value::Complex(format!(
546
595
  "{left_text}{sign}{imaginary}"
547
596
  )))))
@@ -554,7 +603,7 @@ fn computed_number(context: &RuleContext<'_>, node: Node<'_>) -> Option<Value> {
554
603
  /// interpolated contributes the source it was written as.
555
604
  fn dstr_value(node: Node<'_>, context: &RuleContext<'_>) -> String {
556
605
  if node.kind_str() == "chained_string" {
557
- return super::nodes::children(node)
606
+ return super::nodes::children_in(node, context)
558
607
  .into_iter()
559
608
  .map(|child| dstr_value(child, context))
560
609
  .collect();
@@ -563,8 +612,8 @@ fn dstr_value(node: Node<'_>, context: &RuleContext<'_>) -> String {
563
612
  return string_value(node, context);
564
613
  }
565
614
  let mut value = String::new();
566
- let mut cursor = node.walk();
567
- for child in node.named_children(&mut cursor) {
615
+ let _cursor = node.walk();
616
+ for child in named_children_iter(node, context) {
568
617
  match child.kind_str() {
569
618
  "string_content" => value.push_str(context.source.node_text(child)),
570
619
  "escape_sequence" => unescape(context.source.node_text(child), &mut value),
@@ -645,6 +694,15 @@ fn apply_format(text: &str, values: &[Value]) -> Option<String> {
645
694
  }
646
695
  false => sequence.width.parse::<isize>().ok(),
647
696
  };
697
+ // `%.*f` takes its precision from an argument too, and consumes it before the value.
698
+ let precision = match sequence.precision.starts_with('*') {
699
+ true => {
700
+ let taken = *pending.first()?;
701
+ pending.remove(0);
702
+ Some(as_integer(values.get(taken)?)?.max(0) as usize)
703
+ }
704
+ false => None,
705
+ };
648
706
  let index = match hash.is_some()
649
707
  && matches!(
650
708
  sequence.style,
@@ -661,17 +719,27 @@ fn apply_format(text: &str, values: &[Value]) -> Option<String> {
661
719
  },
662
720
  };
663
721
  let value = value_at(values, hash, sequence, index)?;
664
- out.push_str(&render(sequence, value, width)?);
722
+ out.push_str(&render(sequence, value, width, precision)?);
665
723
  }
666
724
  out.push_str(text.get(cursor..)?);
667
725
  Some(out)
668
726
  }
669
727
 
670
728
  /// One formatted field.
671
- fn render(sequence: &Sequence, value: &Value, width: Option<isize>) -> Option<String> {
672
- let precision: Option<usize> = match sequence.precision.is_empty() {
673
- true => None,
674
- false => Some(sequence.precision.parse().ok()?),
729
+ fn render(
730
+ sequence: &Sequence,
731
+ value: &Value,
732
+ width: Option<isize>,
733
+ from_argument: Option<usize>,
734
+ ) -> Option<String> {
735
+ let precision: Option<usize> = match from_argument {
736
+ Some(precision) => Some(precision),
737
+ None => match (sequence.has_precision, sequence.precision.is_empty()) {
738
+ (false, _) => None,
739
+ // `%.d`: the dot with no digits behind it asks for a precision of zero.
740
+ (true, true) => Some(0),
741
+ (true, false) => Some(sequence.precision.parse().ok()?),
742
+ },
675
743
  };
676
744
  let kind = match sequence.style {
677
745
  SequenceStyle::Template => 's',
@@ -689,6 +757,11 @@ fn render(sequence: &Sequence, value: &Value, width: Option<isize>) -> Option<St
689
757
  let number = as_integer(value)?;
690
758
  let mut digits = number.unsigned_abs().to_string();
691
759
  if let Some(precision) = precision {
760
+ // `format('%.d', 0)` is `''`: a precision of zero asks for no digits at all, and
761
+ // zero has none to keep. Padding up to the precision left the `0` in place.
762
+ if precision == 0 && number == 0 {
763
+ digits.clear();
764
+ }
692
765
  while digits.len() < precision {
693
766
  digits.insert(0, '0');
694
767
  }
@@ -784,9 +857,9 @@ fn to_s(value: &Value) -> String {
784
857
 
785
858
  /// `quote`: the result keeps the delimiters the template was written with.
786
859
  fn quote(context: &RuleContext<'_>, template: Node<'_>, call: Node<'_>, formatted: &str) -> String {
787
- let mut cursor = template.walk();
788
- let delimiters: Vec<Node<'_>> = template
789
- .children(&mut cursor)
860
+ let _cursor = template.walk();
861
+ let delimiters: Vec<Node<'_>> = all_children_of(template, context)
862
+ .into_iter()
790
863
  .filter(|child| !child.is_named())
791
864
  .collect();
792
865
  let mut opening = delimiters.first().map_or("'".to_owned(), |token| {
@@ -863,3 +936,8 @@ fn report(context: &RuleContext<'_>, call: Node<'_>, replacement: String) -> Off
863
936
  safe: true,
864
937
  })
865
938
  }
939
+
940
+ /// A `%N$` whose `N` no `usize` holds -- upstream raises rather than formatting.
941
+ fn unreadable_argument_number(sequence: &Sequence) -> bool {
942
+ sequence.flags.contains('$') && argument_number(sequence).is_none()
943
+ }
@@ -132,7 +132,7 @@ fn operation_produces_immutable_object(
132
132
  if node.kind_str() != "parenthesized_statements" {
133
133
  return false;
134
134
  }
135
- let children = super::nodes::children(node);
135
+ let children = super::nodes::children_in(node, context);
136
136
  let [only] = children.as_slice() else {
137
137
  return false;
138
138
  };
@@ -67,7 +67,7 @@ fn needs_quotes(
67
67
  let Some(body) = send_node::heredoc_body(node, context) else {
68
68
  return true;
69
69
  };
70
- let children = super::nodes::children(body);
70
+ let children = super::nodes::children_in(body, context);
71
71
  let Some(end) = children
72
72
  .last()
73
73
  .filter(|last| last.kind_str() == "heredoc_end")
@@ -42,7 +42,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
42
42
  let statements = node
43
43
  .field("body")
44
44
  .map(|body| {
45
- super::nodes::children(body)
45
+ super::nodes::children_in(body, context)
46
46
  .into_iter()
47
47
  .filter(|child| child.kind_str() != "comment")
48
48
  .collect::<Vec<Node<'_>>>()
@@ -122,10 +122,13 @@ fn holds_comments(node: Node<'_>, context: &RuleContext<'_>) -> bool {
122
122
  /// `find_end_line` for a definition.
123
123
  fn find_end_line(node: Node<'_>, context: &RuleContext<'_>) -> usize {
124
124
  let fallback = context.source.line_column(node.end_byte()).0;
125
- let Some(parent) = node.parent() else {
125
+ // `node.parent` is nil at the top level upstream, and the `|| node.loc.end.line` fallback is
126
+ // what answers there. The grammar hands back `program` instead, which would otherwise put the
127
+ // end line at 1 and hide every comment inside the definition.
128
+ let Some(parent) = node.parent().filter(|parent| parent.kind_str() != "program") else {
126
129
  return fallback;
127
130
  };
128
- let siblings: Vec<Node<'_>> = super::nodes::children(parent)
131
+ let siblings: Vec<Node<'_>> = super::nodes::children_in(parent, context)
129
132
  .into_iter()
130
133
  .filter(|child| child.kind_str() != "comment")
131
134
  .collect();
@@ -5,8 +5,8 @@ use tree_sitter::Node;
5
5
  use crate::diagnostic::{Edit, Offense};
6
6
  use crate::ruby_version::RubyVersion;
7
7
  use crate::rules::RuleContext;
8
- use crate::rules::send_node;
9
8
  use crate::rules::node_ext::NodeExt;
9
+ use crate::rules::send_node;
10
10
 
11
11
  const MSG: &str = "Prefer `to_s` over string interpolation.";
12
12
 
@@ -20,7 +20,7 @@ const NOT_ON_ITS_OWN: &[&str] = &["chained_string", "string_array", "symbol_arra
20
20
 
21
21
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
22
22
  for node in context.nodes_of("string") {
23
- let children = super::nodes::children(node);
23
+ let children = super::nodes::children_in(node, context);
24
24
  let [interpolation] = children.as_slice() else {
25
25
  continue;
26
26
  };
@@ -39,12 +39,14 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
39
39
  if is_symbol_key(context, node) {
40
40
  continue;
41
41
  }
42
- let embedded = super::nodes::children(*interpolation);
43
- // `use_match_pattern?`: `"#{x => y}"` binds a name rather than producing one.
42
+ let embedded = super::nodes::children_in(*interpolation, context);
43
+ // `use_match_pattern?`: `"#{x => y}"` binds a name rather than producing one. The test it
44
+ // runs is `match_pattern_type?`, which `x in y` fails -- that is a `match_pattern_p`, and
45
+ // `"#{42 in var}"` is reported like any other interpolation.
44
46
  if context.target_ruby_version() > RubyVersion::new(2, 7)
45
47
  && embedded
46
48
  .iter()
47
- .any(|child| matches!(child.kind_str(), "match_pattern" | "test_pattern"))
49
+ .any(|child| child.kind_str() == "match_pattern")
48
50
  {
49
51
  continue;
50
52
  }
@@ -162,9 +164,7 @@ fn is_symbol_key(context: &RuleContext<'_>, node: Node<'_>) -> bool {
162
164
  return false;
163
165
  };
164
166
  parent.kind_str() == "pair"
165
- && parent
166
- .field("key")
167
- .is_some_and(|key| key.id() == node.id())
167
+ && parent.field("key").is_some_and(|key| key.id() == node.id())
168
168
  && parent
169
169
  .child(1)
170
170
  .is_some_and(|separator| context.source.node_text(separator) == ":")
@@ -47,7 +47,7 @@ fn interpolates(node: Node<'_>, context: &RuleContext<'_>) -> bool {
47
47
  } else {
48
48
  node
49
49
  };
50
- super::nodes::children(literal)
50
+ super::nodes::children_in(literal, context)
51
51
  .iter()
52
52
  .any(|child| match child.kind_str() {
53
53
  "interpolation" => true,
@@ -83,7 +83,14 @@ fn unfreezing(
83
83
  return None;
84
84
  }
85
85
  let selector = parent.field("method")?;
86
- if context.source.node_text(selector) != "dup" {
86
+ // `:+@` written as an ordinary call (`"...".+@`) reaches the same matcher as the
87
+ // unary form; the grammar only spells the operator one as `unary`.
88
+ if !matches!(context.source.node_text(selector), "dup" | "+@") {
89
+ return None;
90
+ }
91
+ // `(send dstr_type? {:+@ :dup})` takes no arguments: `"…".dup(baz)` is a different
92
+ // call and upstream leaves it alone.
93
+ if !crate::rules::send_node::arguments(parent).is_empty() {
87
94
  return None;
88
95
  }
89
96
  Some((
@@ -106,7 +113,7 @@ fn unfreezing(
106
113
  if context.source.node_text(selector) != "new" {
107
114
  return None;
108
115
  }
109
- match super::nodes::children(parent).as_slice() {
116
+ match super::nodes::children_in(parent, context).as_slice() {
110
117
  [only] if only.id() == node.id() => {}
111
118
  _ => return None,
112
119
  }
@@ -6,8 +6,9 @@ use tree_sitter::Node;
6
6
 
7
7
  use crate::diagnostic::{Edit, Offense};
8
8
  use crate::rules::RuleContext;
9
- use crate::rules::layout::tokens::{TokenKind, tokens};
9
+ use crate::rules::layout::tokens::{Token, TokenKind, tokens};
10
10
  use crate::rules::node_ext::NodeExt;
11
+ use crate::rules::send_node::named_children_of;
11
12
 
12
13
  const MSG: &str = "Redundant line continuation.";
13
14
 
@@ -66,10 +67,12 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
66
67
  /// continuation written after the last of them.
67
68
  fn ast_range(context: &RuleContext<'_>) -> Option<Range<usize>> {
68
69
  let root = context.root_node();
69
- let mut cursor = root.walk();
70
- let statements: Vec<Node<'_>> = root
71
- .named_children(&mut cursor)
72
- .filter(|child| child.kind_str() != "comment")
70
+ let _cursor = root.walk();
71
+ let statements: Vec<Node<'_>> = named_children_of(root, context)
72
+ .into_iter()
73
+ // `__END__` and what follows it is `DATA`, not code: upstream's AST stops at the keyword,
74
+ // so a `\` down there is out of the range the candidates are searched in.
75
+ .filter(|child| !matches!(child.kind_str(), "comment" | "uninterpreted"))
73
76
  .collect();
74
77
  let (first, last) = (statements.first()?, statements.last()?);
75
78
  Some(first.start_byte()..last.end_byte())
@@ -102,7 +105,7 @@ fn within_comment(context: &RuleContext<'_>, range: &Range<usize>) -> bool {
102
105
  /// `implicit_string_concatenation?`: two string literals written one under the other, which the
103
106
  /// continuation is what joins.
104
107
  fn implicit_string_concatenation(context: &RuleContext<'_>, range: &Range<usize>) -> bool {
105
- let stream = tokens(context);
108
+ let stream: &[Token] = tokens(context);
106
109
  let line = context.source.line_column(range.start).0;
107
110
  let before = stream.iter().rfind(|token| token.range.end <= range.start);
108
111
  let ends = before.is_some_and(|token| {
@@ -131,7 +134,7 @@ fn string_content_ranges(context: &RuleContext<'_>) -> Vec<Range<usize>> {
131
134
  let mut stack = vec![context.root_node()];
132
135
  while let Some(node) = stack.pop() {
133
136
  if !LITERALS.contains(&node.kind_str()) {
134
- crate::rules::push_named_children(node, &mut stack);
137
+ crate::rules::push_named_children_in(node, context, &mut stack);
135
138
  continue;
136
139
  }
137
140
  // What an interpolation holds is code again, so it is cut out of the literal and walked.