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
@@ -6,9 +6,14 @@ use crate::diagnostic::Offense;
6
6
  use crate::rules::RuleContext;
7
7
  use crate::rules::node_ext::NodeExt;
8
8
 
9
+ use super::blocks::BlockArgs;
10
+ use super::locals::LocalVariables;
11
+ use crate::rules::send_node::named_children_iter;
12
+
9
13
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
10
14
  let mut tracker = Tracker {
11
15
  context,
16
+ locals: LocalVariables::new(context),
12
17
  // Only the line of the earlier definition is ever read back, so the node itself does not
13
18
  // have to be kept alive alongside the key.
14
19
  definitions: HashMap::new(),
@@ -27,6 +32,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
27
32
 
28
33
  struct Tracker<'a, 'tree> {
29
34
  context: &'a RuleContext<'tree>,
35
+ locals: LocalVariables<'a, 'tree>,
30
36
  definitions: HashMap<String, usize>,
31
37
  /// Keys already redefined once inside a `rescue` or `ensure`. A body with such a clause is
32
38
  /// allowed one redefinition -- that is how a conditional fallback definition is written -- but
@@ -53,9 +59,7 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
53
59
  if has_if_ancestor(node) {
54
60
  return;
55
61
  }
56
- let (Some(name), Some(receiver)) =
57
- (self.name_text(node), node.field("object"))
58
- else {
62
+ let (Some(name), Some(receiver)) = (self.name_text(node), node.field("object")) else {
59
63
  return;
60
64
  };
61
65
  match receiver.kind_str() {
@@ -71,10 +75,7 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
71
75
  }
72
76
 
73
77
  fn on_alias(&mut self, node: Node<'_>, offenses: &mut Vec<Offense>) {
74
- let (Some(name), Some(original)) = (
75
- node.field("name"),
76
- node.field("alias"),
77
- ) else {
78
+ let (Some(name), Some(original)) = (node.field("name"), node.field("alias")) else {
78
79
  return;
79
80
  };
80
81
  let (Some(name), Some(original)) = (
@@ -118,6 +119,20 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
118
119
  }
119
120
  }
120
121
  accessor @ ("attr" | "attr_reader" | "attr_writer" | "attr_accessor") => {
122
+ // `attr :foo, true` carries a `true` that is no symbol, and `literal_arguments`
123
+ // answers a call with any such argument with nothing at all. `on_attr` reads only
124
+ // the first argument of an `attr`, so the name is recovered from that alone.
125
+ let arguments = match (accessor, arguments.is_empty()) {
126
+ ("attr", true) => attr_first_name(self.context, node)
127
+ .map(|name| vec![name])
128
+ .unwrap_or_default(),
129
+ _ => arguments,
130
+ };
131
+ // **`found_attr` names each argument through `sym_name`, which answers `nil` for a
132
+ // `str`.** `attr_reader 'foo'` defines the method all the same, but upstream never
133
+ // records it -- so pairing it with `attr_reader :foo` reported a duplicate that
134
+ // upstream is silent about.
135
+ let arguments: Vec<String> = attr_symbol_names(node, &arguments);
121
136
  if arguments.is_empty() || !in_macro_scope(node) {
122
137
  return;
123
138
  }
@@ -151,8 +166,10 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
151
166
  offenses: &mut Vec<Offense>,
152
167
  ) {
153
168
  let (readable, writable) = match accessor {
154
- // `attr :foo, true` is the historical writer form.
155
- "attr" => (true, false),
169
+ // `writable = args.size == 2 && args.last.true_type?`: `attr :foo, true` is the
170
+ // historical accessor form, and it defines the writer as well. `literal_arguments`
171
+ // keeps only the symbols, so the `true` has to be read off the call itself.
172
+ "attr" => (true, attr_with_writer(node)),
156
173
  "attr_reader" => (true, false),
157
174
  "attr_writer" => (false, true),
158
175
  _ => (true, true),
@@ -173,12 +190,13 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
173
190
  }
174
191
 
175
192
  fn found_instance_method(&mut self, node: Node<'_>, name: &str, offenses: &mut Vec<Offense>) {
176
- if let Some(scope) = parent_module_name(self.context, node) {
193
+ if let Some(scope) = parent_module_name(self.context, node, &self.locals) {
177
194
  let method = format!("{}{name}", humanize_scope(&scope));
178
195
  self.found_method(node, &method, None, offenses);
179
196
  } else if let Some(anon_block) = anonymous_class_block(self.context, node) {
180
- let base =
181
- qualified_object_scope(parent_module_name(self.context, anon_block).as_deref());
197
+ let base = qualified_object_scope(
198
+ parent_module_name(self.context, anon_block, &self.locals).as_deref(),
199
+ );
182
200
  let scope = if singleton_class_ancestor(node).is_some() {
183
201
  format!("#<Class:{base}>")
184
202
  } else {
@@ -193,11 +211,12 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
193
211
  }
194
212
 
195
213
  fn check_self_receiver(&mut self, node: Node<'_>, name: &str, offenses: &mut Vec<Offense>) {
196
- if let Some(enclosing) = parent_module_name(self.context, node) {
214
+ if let Some(enclosing) = parent_module_name(self.context, node, &self.locals) {
197
215
  self.found_method(node, &format!("{enclosing}.{name}"), None, offenses);
198
216
  } else if let Some(anon_block) = anonymous_class_block(self.context, node) {
199
- let scope =
200
- qualified_object_scope(parent_module_name(self.context, anon_block).as_deref());
217
+ let scope = qualified_object_scope(
218
+ parent_module_name(self.context, anon_block, &self.locals).as_deref(),
219
+ );
201
220
  let scope_id = anon_block_scope_id(self.context, anon_block);
202
221
  self.found_method(node, &format!("{scope}.{name}"), scope_id, offenses);
203
222
  }
@@ -212,13 +231,17 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
212
231
  let Some(receiver) = sclass.field("value") else {
213
232
  return;
214
233
  };
215
- if receiver.kind_str() != "call" {
216
- return;
217
- }
218
- let Some(method) = receiver.field("method") else {
219
- return;
234
+ // `singleton_receiver_node.send_type?` -- and a bare `blah` **is** a send upstream. The
235
+ // grammar spells a receiverless call as an `identifier`, so `class << blah` never reached
236
+ // the check and every method defined in it went unrecorded.
237
+ let method = match receiver.kind_str() {
238
+ "call" => match receiver.field("method") {
239
+ Some(method) => self.text(method).to_owned(),
240
+ None => return,
241
+ },
242
+ "identifier" => self.text(receiver).to_owned(),
243
+ _ => return,
220
244
  };
221
- let method = self.text(method).to_owned();
222
245
  self.found_method(node, &format!("{method}.{name}"), None, offenses);
223
246
  }
224
247
 
@@ -261,7 +284,7 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
261
284
  }
262
285
 
263
286
  fn track_self_alias(&mut self, node: Node<'_>, name: &str) {
264
- if let Some(scope) = parent_module_name(self.context, node) {
287
+ if let Some(scope) = parent_module_name(self.context, node, &self.locals) {
265
288
  self.self_aliased
266
289
  .insert(format!("{}{name}", humanize_scope(&scope)));
267
290
  }
@@ -283,7 +306,7 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
283
306
  if let Some(defined) = defined_module_name(self.context, parent) {
284
307
  let bare = defined.rsplit("::").next().unwrap_or(&defined);
285
308
  if bare == const_name.trim_start_matches("::") {
286
- let enclosing = parent_module_name(self.context, parent);
309
+ let enclosing = parent_module_name(self.context, parent, &self.locals);
287
310
  return Some(match enclosing.as_deref() {
288
311
  Some("Object") | None => defined,
289
312
  Some(name) => format!("{name}::{defined}"),
@@ -371,7 +394,11 @@ fn rescue_scope(node: Node<'_>) -> Option<&'static str> {
371
394
 
372
395
  /// The lexical namespace enclosing `node`, or `None` when a block breaks the chain -- a block body
373
396
  /// is ordinary code, so what a `def` inside one attaches to cannot be read off the source.
374
- fn parent_module_name(context: &RuleContext<'_>, node: Node<'_>) -> Option<String> {
397
+ fn parent_module_name(
398
+ context: &RuleContext<'_>,
399
+ node: Node<'_>,
400
+ locals: &LocalVariables<'_, '_>,
401
+ ) -> Option<String> {
375
402
  let mut parts: Vec<String> = Vec::new();
376
403
  let mut current = node;
377
404
  while let Some(parent) = current.parent_of(context) {
@@ -381,12 +408,22 @@ fn parent_module_name(context: &RuleContext<'_>, node: Node<'_>) -> Option<Strin
381
408
  parts.push(name);
382
409
  }
383
410
  }
384
- "singleton_class" => parts.push(singleton_class_name(context, parent)?),
385
- "block" | "do_block" => match block_module_name(context, parent) {
386
- BlockScope::Transparent => {}
387
- BlockScope::Named(name) => parts.push(name),
388
- BlockScope::Opaque => return None,
389
- },
411
+ "singleton_class" => parts.push(singleton_class_name(context, parent, locals)?),
412
+ // `each_ancestor(:class, :module, :sclass, :casgn, :block)` does not list `numblock`
413
+ // or `itblock`: a block whose parameter is implicit is a different node type upstream,
414
+ // and the walk passes straight over it. The grammar spells all three as `block`.
415
+ "block" | "do_block"
416
+ if matches!(
417
+ BlockArgs::of(parent, context, locals),
418
+ BlockArgs::Written(_)
419
+ ) =>
420
+ {
421
+ match block_module_name(context, parent) {
422
+ BlockScope::Transparent => {}
423
+ BlockScope::Named(name) => parts.push(name),
424
+ BlockScope::Opaque => return None,
425
+ }
426
+ }
390
427
  _ => {}
391
428
  }
392
429
  current = parent;
@@ -417,11 +454,13 @@ fn defined_module_name(context: &RuleContext<'_>, node: Node<'_>) -> Option<Stri
417
454
  }
418
455
 
419
456
  fn constant_name(context: &RuleContext<'_>, node: Node<'_>) -> String {
420
- context
421
- .source
422
- .node_text(node)
423
- .trim_start_matches("::")
424
- .to_owned()
457
+ let text = context.source.node_text(node);
458
+ // `qualified_name` interpolates `namespace.const_name`, and `(self)` has none -- `self::A`
459
+ // qualifies as `::A`.
460
+ if let Some(rest) = text.strip_prefix("self::") {
461
+ return format!("::{rest}");
462
+ }
463
+ text.trim_start_matches("::").to_owned()
425
464
  }
426
465
 
427
466
  /// `Class.new`/`Module.new`, optionally carrying a block. `global` demands the bare global
@@ -438,10 +477,7 @@ fn is_class_or_module_new(context: &RuleContext<'_>, node: Node<'_>, global: boo
438
477
  if !crate::rules::send_node::is_plain_send(call, context) {
439
478
  return false;
440
479
  }
441
- let (Some(receiver), Some(method)) = (
442
- call.field("receiver"),
443
- call.field("method"),
444
- ) else {
480
+ let (Some(receiver), Some(method)) = (call.field("receiver"), call.field("method")) else {
445
481
  return false;
446
482
  };
447
483
  if context.source.node_text(method) != "new" {
@@ -452,10 +488,15 @@ fn is_class_or_module_new(context: &RuleContext<'_>, node: Node<'_>, global: boo
452
488
  return false;
453
489
  }
454
490
  let bare = name.rsplit("::").next().unwrap_or(&name);
455
- matches!(receiver.kind_str(), "constant" | "scope_resolution") && matches!(bare, "Class" | "Module")
491
+ matches!(receiver.kind_str(), "constant" | "scope_resolution")
492
+ && matches!(bare, "Class" | "Module")
456
493
  }
457
494
 
458
- fn singleton_class_name(context: &RuleContext<'_>, node: Node<'_>) -> Option<String> {
495
+ fn singleton_class_name(
496
+ context: &RuleContext<'_>,
497
+ node: Node<'_>,
498
+ locals: &LocalVariables<'_, '_>,
499
+ ) -> Option<String> {
459
500
  let value = node.field("value")?;
460
501
  match value.kind_str() {
461
502
  "constant" | "scope_resolution" => {
@@ -463,7 +504,7 @@ fn singleton_class_name(context: &RuleContext<'_>, node: Node<'_>) -> Option<Str
463
504
  }
464
505
  "self" => Some(format!(
465
506
  "#<Class:{}>",
466
- parent_module_name(context, node).unwrap_or_default()
507
+ parent_module_name(context, node, locals).unwrap_or_default()
467
508
  )),
468
509
  _ => None,
469
510
  }
@@ -479,7 +520,10 @@ enum BlockScope {
479
520
  }
480
521
 
481
522
  fn block_module_name(context: &RuleContext<'_>, block: Node<'_>) -> BlockScope {
482
- let Some(call) = block.parent_of(context).filter(|parent| parent.kind_str() == "call") else {
523
+ let Some(call) = block
524
+ .parent_of(context)
525
+ .filter(|parent| parent.kind_str() == "call")
526
+ else {
483
527
  return BlockScope::Opaque;
484
528
  };
485
529
  // `on_send` は `csend` に呼ばれないので、`Class&.new do` のブロックは本家からは名前の付かない
@@ -733,15 +777,12 @@ fn named_receiver<'a>(context: &'a RuleContext<'_>, call: Node<'_>) -> Option<(&
733
777
  }
734
778
 
735
779
  fn is_class_new_block(context: &RuleContext<'_>, block: Node<'_>) -> bool {
736
- block
737
- .field("receiver")
738
- .is_some_and(|receiver| {
739
- constant_name(context, receiver)
740
- .rsplit("::")
741
- .next()
742
- .is_some_and(|name| name == "Class")
743
- })
744
- && is_class_or_module_new(context, block, false)
780
+ block.field("receiver").is_some_and(|receiver| {
781
+ constant_name(context, receiver)
782
+ .rsplit("::")
783
+ .next()
784
+ .is_some_and(|name| name == "Class")
785
+ }) && is_class_or_module_new(context, block, false)
745
786
  }
746
787
 
747
788
  fn anon_block_identity(context: &RuleContext<'_>, block: Node<'_>) -> String {
@@ -781,9 +822,9 @@ fn literal_arguments(context: &RuleContext<'_>, node: Node<'_>) -> Vec<String> {
781
822
  let Some(arguments) = node.field("arguments") else {
782
823
  return Vec::new();
783
824
  };
784
- let mut cursor = arguments.walk();
825
+ let _cursor = arguments.walk();
785
826
  let mut names = Vec::new();
786
- for argument in arguments.named_children(&mut cursor) {
827
+ for argument in named_children_iter(arguments, context) {
787
828
  match symbol_name(context.source.node_text(argument)) {
788
829
  Some(name) if matches!(argument.kind_str(), "simple_symbol" | "string") => {
789
830
  names.push(name.to_owned());
@@ -807,3 +848,41 @@ fn symbol_name(text: &str) -> Option<&str> {
807
848
  .unwrap_or(text);
808
849
  (!unquoted.is_empty() && !unquoted.contains(['#', '"', '\''])).then_some(unquoted)
809
850
  }
851
+
852
+ /// `args.size == 2 && args.last.true_type?`: the second argument of the historical `attr` form.
853
+ fn attr_with_writer(node: Node<'_>) -> bool {
854
+ let Some(list) = node.field("arguments") else {
855
+ return false;
856
+ };
857
+ let written = crate::rules::send_node::named_children(list);
858
+ matches!(written.as_slice(), [_, last] if last.kind_str() == "true")
859
+ }
860
+
861
+ /// The name the first argument of an `attr` spells, when a later argument stopped
862
+ /// `literal_arguments` from answering.
863
+ fn attr_first_name(context: &RuleContext<'_>, node: Node<'_>) -> Option<String> {
864
+ let list = node.field("arguments")?;
865
+ let first = *crate::rules::send_node::named_children_of(list, context).first()?;
866
+ if !matches!(first.kind_str(), "simple_symbol" | "string") {
867
+ return None;
868
+ }
869
+ symbol_name(context.source.node_text(first)).map(str::to_owned)
870
+ }
871
+
872
+ /// The `attr` arguments upstream records: `sym_name` answers only for a `sym`, so an accessor
873
+ /// named with a string contributes nothing.
874
+ fn attr_symbol_names(node: Node<'_>, fallback: &[String]) -> Vec<String> {
875
+ let Some(list) = node.field("arguments") else {
876
+ return fallback.to_vec();
877
+ };
878
+ let written = crate::rules::send_node::named_children(list);
879
+ if written.len() != fallback.len() {
880
+ return fallback.to_vec();
881
+ }
882
+ written
883
+ .iter()
884
+ .zip(fallback)
885
+ .filter(|(argument, _)| argument.kind_str() == "simple_symbol")
886
+ .map(|(_, name)| name.clone())
887
+ .collect()
888
+ }
@@ -156,7 +156,6 @@ fn body_parent(container: Node<'_>) -> Parent {
156
156
  /// statements it holds.
157
157
  fn split_body(container: Node<'_>) -> bool {
158
158
  let mut cursor = container.walk();
159
- container
160
- .named_children(&mut cursor)
159
+ container.named_children(&mut cursor)
161
160
  .any(|child| matches!(child.kind_str(), "rescue" | "else" | "ensure"))
162
161
  }
@@ -6,6 +6,7 @@ use crate::rules::node_ext::NodeExt;
6
6
  use crate::rules::send_node::{arguments, named_children};
7
7
 
8
8
  use super::node_equality::identical;
9
+ use crate::rules::send_node::named_children_of;
9
10
 
10
11
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
11
12
  for node in context.nodes_of_any(&["call", "element_reference"]) {
@@ -15,7 +16,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
15
16
  let mut seen: Vec<Node<'_>> = Vec::new();
16
17
  for (index, &element) in elements.iter().enumerate() {
17
18
  // Only a value the cop can compare: anything computed may differ at run time.
18
- if !is_comparable(element) {
19
+ if !is_comparable(element, context) {
19
20
  continue;
20
21
  }
21
22
  if !seen.iter().any(|other| identical(*other, element, context)) {
@@ -41,12 +42,12 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
41
42
  /// `set_init_elements`, paired with the name the message calls the class by.
42
43
  fn set_init_elements<'tree>(
43
44
  node: Node<'tree>,
44
- context: &RuleContext<'_>,
45
+ context: &'tree RuleContext<'_>,
45
46
  ) -> Option<(String, Vec<Node<'tree>>)> {
46
47
  if node.kind_str() == "element_reference" {
47
48
  let object = node.field("object")?;
48
49
  let name = set_class_name(object, context)?;
49
- let elements = named_children(node)
50
+ let elements = named_children_of(node, context)
50
51
  .into_iter()
51
52
  .filter(|child| child.kind_str() != "comment" && child.id() != object.id())
52
53
  .collect();
@@ -62,7 +63,10 @@ fn set_init_elements<'tree>(
62
63
  return None;
63
64
  };
64
65
  let array = only.first();
65
- (array.kind_str() == "array").then(|| (name, literal_elements(array)))
66
+ // `%i[…]` and `%w[…]` are `array` nodes upstream; the grammar names them by their
67
+ // percent spelling, so `Set.new(%i[foo bar foo])` fell out of the arm entirely.
68
+ matches!(array.kind_str(), "array" | "symbol_array" | "string_array")
69
+ .then(|| (name, literal_elements(array)))
66
70
  }
67
71
  // `(call (array $...) :to_set)`: the class is not written, so the message names `Set`.
68
72
  "to_set" => {
@@ -95,7 +99,12 @@ fn set_class_name(node: Node<'_>, context: &RuleContext<'_>) -> Option<String> {
95
99
  }
96
100
 
97
101
  /// `literal? || const_type? || variable?`: what the cop is willing to call the same twice.
98
- fn is_comparable(node: Node<'_>) -> bool {
102
+ fn is_comparable(node: Node<'_>, context: &RuleContext<'_>) -> bool {
103
+ // `variable?` is `lvar`/`ivar`/`cvar`/`gvar`. A bare name that was never assigned is a `send`
104
+ // upstream -- `Set[foo, foo]` calls `foo` twice and may well get two different values.
105
+ if node.kind_str() == "identifier" && !context.variable_analysis().is_variable_reference(node) {
106
+ return false;
107
+ }
99
108
  matches!(
100
109
  node.kind_str(),
101
110
  "integer"
@@ -105,6 +114,9 @@ fn is_comparable(node: Node<'_>) -> bool {
105
114
  | "string"
106
115
  | "simple_symbol"
107
116
  | "delimited_symbol"
117
+ // The elements of a `%i[…]` / `%w[…]`, which upstream spells as plain `sym` and `str`.
118
+ | "bare_symbol"
119
+ | "bare_string"
108
120
  | "hash_key_symbol"
109
121
  | "character"
110
122
  | "true"
@@ -122,6 +134,8 @@ fn is_comparable(node: Node<'_>) -> bool {
122
134
  | "instance_variable"
123
135
  | "class_variable"
124
136
  | "global_variable"
137
+ // `variable?` covers a local variable, which the grammar spells as a bare identifier.
138
+ | "identifier"
125
139
  | "self"
126
140
  )
127
141
  }
@@ -5,6 +5,7 @@ use crate::rules::RuleContext;
5
5
 
6
6
  use super::statements::statements;
7
7
  use crate::rules::node_ext::NodeExt;
8
+ use crate::rules::send_node::all_children_iter;
8
9
 
9
10
  /// The three conditionals whose branch can be written empty. A modifier form always has a body.
10
11
  const CONDITIONALS: &[&str] = &["if", "unless", "elsif"];
@@ -35,6 +36,21 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
35
36
  let alternative = node.field("alternative");
36
37
  let range = match alternative {
37
38
  Some(clause) => node.start_byte()..clause.start_byte(),
39
+ // `node.source_range` ends at the last node the **parser** built, and a comment is not
40
+ // one. An `elsif` holding nothing but a comment therefore ends at its condition; an
41
+ // `if` still reaches its own `end`, which the grammar keeps inside the node.
42
+ None if node.kind_str() == "elsif" => {
43
+ let end = node
44
+ .field("condition")
45
+ .map_or_else(|| node.end_byte(), |condition| condition.end_byte());
46
+ // The parser's range keeps the separator that closes the clause -- `elsif cond;`
47
+ // ends after the semicolon -- and stops before a comment or a line break.
48
+ let end = match context.source.text()[end..node.end_byte()].chars().next() {
49
+ Some(';') => end + 1,
50
+ _ => end,
51
+ };
52
+ node.start_byte()..end
53
+ }
38
54
  None => node.byte_range(),
39
55
  };
40
56
  let mut offense =
@@ -166,7 +182,7 @@ fn enclosing_end<'tree>(node: Node<'tree>, context: &RuleContext<'_>) -> Option<
166
182
  /// `node.loc.begin`: the `then` or `;` the branch was introduced with. The grammar puts a `then`
167
183
  /// inside the branch it opens, but a `;` stays beside the condition -- and a branch holding nothing
168
184
  /// is no node at all, which leaves the `;` as the only trace of it.
169
- fn then_keyword<'tree>(node: Node<'tree>, context: &RuleContext<'_>) -> Option<Node<'tree>> {
185
+ fn then_keyword<'tree>(node: Node<'tree>, context: &'tree RuleContext<'_>) -> Option<Node<'tree>> {
170
186
  if let Some(first) = node
171
187
  .field("consequence")
172
188
  .and_then(|branch| branch.child(0))
@@ -175,8 +191,8 @@ fn then_keyword<'tree>(node: Node<'tree>, context: &RuleContext<'_>) -> Option<N
175
191
  return Some(first);
176
192
  }
177
193
  let condition = node.field("condition")?;
178
- let mut cursor = node.walk();
179
- node.children(&mut cursor).find(|child| {
194
+ let _cursor = node.walk();
195
+ all_children_iter(node, context).find(|child| {
180
196
  child.start_byte() >= condition.end_byte()
181
197
  && matches!(context.source.node_text(*child), "then" | ";")
182
198
  })
@@ -1,5 +1,6 @@
1
1
  use crate::diagnostic::Offense;
2
2
  use crate::rules::RuleContext;
3
+ use crate::rules::node_ext::NodeExt;
3
4
 
4
5
  use super::statements::statements;
5
6
 
@@ -8,7 +9,21 @@ const MSG: &str = "Avoid empty expressions.";
8
9
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
9
10
  // `on_begin` reaches the `begin` node that `(...)` builds and the one an interpolation holds,
10
11
  // but never the `kwbegin` of `begin ... end`, which is a type of its own upstream.
11
- for node in context.nodes_of_any(&["parenthesized_statements", "interpolation"]) {
12
+ for node in context.nodes_of_any(&[
13
+ "parenthesized_statements",
14
+ "interpolation",
15
+ // **`return()` writes its parentheses as an argument list here.** Upstream builds the
16
+ // same empty `begin` a bare `()` does, so the jump's empty parentheses are an empty
17
+ // expression too -- the grammar just files them under a different kind.
18
+ "argument_list",
19
+ ]) {
20
+ if node.kind_str() == "argument_list"
21
+ && node
22
+ .parent()
23
+ .is_none_or(|parent| !matches!(parent.kind_str(), "return" | "break" | "next"))
24
+ {
25
+ continue;
26
+ }
12
27
  if statements(node).is_empty() {
13
28
  offenses.push(context.offense(MSG, node.byte_range()));
14
29
  }
@@ -11,6 +11,7 @@ use super::comments_help::comment_search_lines;
11
11
  use crate::ruby_version::RubyVersion;
12
12
 
13
13
  use super::statements::statements;
14
+ use crate::rules::send_node::named_children_iter;
14
15
 
15
16
  const MSG: &str = "Avoid `in` branches without a body.";
16
17
 
@@ -20,8 +21,8 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
20
21
  }
21
22
  let allow_comments: bool = context.setting("AllowComments").unwrap_or(true);
22
23
  for case in context.nodes_of("case_match") {
23
- let mut cursor = case.walk();
24
- let children: Vec<Node<'_>> = case.named_children(&mut cursor).collect();
24
+ let _cursor = case.walk();
25
+ let children: Vec<Node<'_>> = named_children_iter(case, context).collect();
25
26
  for (index, &branch) in children.iter().enumerate() {
26
27
  if branch.kind_str() != "in_clause" || has_body(branch) {
27
28
  continue;
@@ -8,14 +8,15 @@ use crate::rules::RuleContext;
8
8
  use crate::rules::node_ext::NodeExt;
9
9
 
10
10
  use super::comments_help::comment_search_lines;
11
+ use crate::rules::send_node::named_children_iter;
11
12
 
12
13
  const MSG: &str = "Avoid `when` branches without a body.";
13
14
 
14
15
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
15
16
  let allow_comments: bool = context.setting("AllowComments").unwrap_or(true);
16
17
  for case in context.nodes_of("case") {
17
- let mut cursor = case.walk();
18
- let children: Vec<Node<'_>> = case.named_children(&mut cursor).collect();
18
+ let _cursor = case.walk();
19
+ let children: Vec<Node<'_>> = named_children_iter(case, context).collect();
19
20
  for (index, &branch) in children.iter().enumerate() {
20
21
  if branch.kind_str() != "when" || has_body(branch) {
21
22
  continue;
@@ -32,8 +33,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
32
33
  /// the `;` or `then` that separates it from the body it does not have is no part of it.
33
34
  fn reported_range(branch: Node<'_>) -> std::ops::Range<usize> {
34
35
  let mut cursor = branch.walk();
35
- let last = branch
36
- .named_children(&mut cursor)
36
+ let last = branch.named_children(&mut cursor)
37
37
  .filter(|child| child.kind_str() == "pattern")
38
38
  .last();
39
39
  branch.start_byte()..last.map_or_else(|| branch.end_byte(), |last| last.end_byte())
@@ -5,6 +5,7 @@ use crate::rules::RuleContext;
5
5
 
6
6
  use super::statements::statements;
7
7
  use crate::rules::node_ext::NodeExt;
8
+ use crate::rules::send_node::named_children_iter;
8
9
 
9
10
  const MSG: &str = "Do not return from an `ensure` block.";
10
11
 
@@ -26,8 +27,8 @@ fn collect(
26
27
  if node.kind_str() == "return" && !returns_from_inner_scope(node, ensure_node, context) {
27
28
  offenses.push(context.offense(MSG, node.byte_range()));
28
29
  }
29
- let mut cursor = node.walk();
30
- let children: Vec<Node<'_>> = node.named_children(&mut cursor).collect();
30
+ let _cursor = node.walk();
31
+ let children: Vec<Node<'_>> = named_children_iter(node, context).collect();
31
32
  for child in children {
32
33
  collect(child, ensure_node, context, offenses);
33
34
  }
@@ -2,8 +2,10 @@ use tree_sitter::Node;
2
2
 
3
3
  use crate::diagnostic::Offense;
4
4
  use crate::rules::RuleContext;
5
- use crate::rules::send_node::{Argument, arguments, named_children, send_range};
5
+ use crate::rules::send_node::{Argument, arguments, send_range};
6
6
  use crate::rules::node_ext::NodeExt;
7
+ use crate::rules::send_node::named_children_of;
8
+ use crate::rules::send_node::named_children_iter;
7
9
 
8
10
  const MSG_EQUALITY: &str = "Avoid equality comparisons of floats as they are unreliable.";
9
11
  const MSG_INEQUALITY: &str = "Avoid inequality comparisons of floats as they are unreliable.";
@@ -52,15 +54,15 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
52
54
 
53
55
  /// `on_case`: every condition of every `when` branch, taken on its own.
54
56
  fn case_conditions(node: Node<'_>, context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
55
- for branch in named_children(node) {
57
+ for branch in named_children_of(node, context) {
56
58
  if branch.kind_str() != "when" {
57
59
  continue;
58
60
  }
59
- for pattern in named_children(branch) {
61
+ for pattern in named_children_of(branch, context) {
60
62
  if pattern.kind_str() != "pattern" {
61
63
  continue;
62
64
  }
63
- let Some(condition) = named_children(pattern).into_iter().next() else {
65
+ let Some(condition) = named_children_iter(pattern, context).next() else {
64
66
  continue;
65
67
  };
66
68
  if is_float(condition, context) && !literal_safe(condition, context) {
@@ -110,7 +112,7 @@ fn is_float(node: Node<'_>, context: &RuleContext<'_>) -> bool {
110
112
  // The parser folds a sign into the numeric literal it stands before.
111
113
  "unary" => signed_numeric(node, context).is_some_and(|operand| operand.kind_str() == "float"),
112
114
  "binary" | "call" => float_send(node, context),
113
- "parenthesized_statements" => named_children(node)
115
+ "parenthesized_statements" => named_children_of(node, context)
114
116
  .into_iter()
115
117
  .next()
116
118
  .is_some_and(|first| is_float(first, context)),
@@ -167,7 +169,7 @@ fn literal_safe(node: Node<'_>, context: &RuleContext<'_>) -> bool {
167
169
  "nil" => true,
168
170
  "integer" | "float" | "rational" | "complex" => is_zero(node, context),
169
171
  "unary" => signed_numeric(node, context).is_some_and(|operand| is_zero(operand, context)),
170
- "parenthesized_statements" => named_children(node)
172
+ "parenthesized_statements" => named_children_of(node, context)
171
173
  .into_iter()
172
174
  .next()
173
175
  .is_some_and(|first| literal_safe(first, context)),