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
@@ -58,14 +58,14 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
58
58
  /// `single_negative?`: the condition, once its parentheses are peeled off, is one `!` applied to
59
59
  /// something that is not itself a negation. `not x` is the same `send` upstream, so it counts too.
60
60
  fn single_negative<'tree>(
61
- context: &RuleContext<'_>,
61
+ context: &'tree RuleContext<'_>,
62
62
  condition: Node<'tree>,
63
63
  ) -> Option<send_node::Negation<'tree>> {
64
64
  let mut current = condition;
65
65
  // `condition = condition.children.last while condition.begin_type?`, with `(begin)` -- an
66
66
  // empty condition -- excluded before the loop even starts.
67
67
  while current.kind_str() == "parenthesized_statements" {
68
- current = *super::nodes::children(current).last()?;
68
+ current = *super::nodes::children_in(current, context).last()?;
69
69
  }
70
70
  let found = send_node::negation(current, context)?;
71
71
  // `!(send _ :!)`: `!!x` negates a negation and is left alone. **The inner one may be written
@@ -29,7 +29,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
29
29
  // `else_branch &&`: an `else` that holds nothing is no branch at all upstream, where an
30
30
  // empty body is `nil` rather than a node. The grammar still writes the `else` keyword as a
31
31
  // node, so asking whether the field is present answers a different question.
32
- if alternative.kind_str() == "else" && super::nodes::children(alternative).is_empty() {
32
+ if alternative.kind_str() == "else" && super::nodes::children_in(alternative, context).is_empty() {
33
33
  continue;
34
34
  }
35
35
  let Some(condition) = node.field("condition").map(unwrap_parentheses) else {
@@ -118,7 +118,7 @@ fn swap_branches(
118
118
  // line goes away instead.
119
119
  if node
120
120
  .field("consequence")
121
- .is_none_or(|branch| super::nodes::children(branch).is_empty())
121
+ .is_none_or(|branch| super::nodes::children_in(branch, context).is_empty())
122
122
  {
123
123
  let keyword = alternative.start_byte()..alternative.start_byte() + ELSE_LENGTH;
124
124
  return vec![replace(support::whole_lines(keyword, context), String::new())];
@@ -140,9 +140,11 @@ fn swap_branches(
140
140
  /// `unwrap_begin_nodes`.
141
141
  fn unwrap_parentheses<'tree>(node: Node<'tree>) -> Node<'tree> {
142
142
  let mut current = node;
143
+ // `unwrap_begin_nodes` peels a `begin` as well as a `kwbegin`, which the grammar writes as
144
+ // `begin … end`. Leaving that one on hid `begin / x !~ y / end ? a : b` from the cop.
143
145
  while matches!(
144
146
  current.kind_str(),
145
- "parenthesized_statements" | "begin_block"
147
+ "parenthesized_statements" | "begin_block" | "begin"
146
148
  ) {
147
149
  match super::nodes::children(current).as_slice() {
148
150
  [only] => current = *only,
@@ -52,7 +52,7 @@ pub(super) fn single_negative<'tree>(
52
52
  ) -> Option<send_node::Negation<'tree>> {
53
53
  // `empty_condition?` is `(begin)`: `while ()` has nothing to negate.
54
54
  if condition.kind_str() == "parenthesized_statements"
55
- && super::nodes::children(condition).is_empty()
55
+ && super::nodes::children_in(condition, context).is_empty()
56
56
  {
57
57
  return None;
58
58
  }
@@ -136,10 +136,21 @@ fn range_offense(
136
136
  /// The receiver and the single index of a subscript, in both spellings of `[]`.
137
137
  fn subscript<'tree>(
138
138
  node: Node<'tree>,
139
- context: &RuleContext<'_>,
139
+ context: &'tree RuleContext<'_>,
140
140
  ) -> Option<(Node<'tree>, Node<'tree>)> {
141
141
  match node.kind_str() {
142
142
  "element_reference" => {
143
+ // `RESTRICT_ON_SEND = %i[[]]`: `arr[i] = value` dispatches `[]=`, which this cop never
144
+ // sees. The grammar writes the assignment around the same `element_reference`, so the
145
+ // read and the write look alike until the parent is checked.
146
+ if node.parent_of(context).is_some_and(|parent| {
147
+ matches!(parent.kind_str(), "assignment" | "operator_assignment")
148
+ && parent
149
+ .field("left")
150
+ .is_some_and(|left| left.id() == node.id())
151
+ }) {
152
+ return None;
153
+ }
143
154
  let object = node.field("object")?;
144
155
  let children = super::nodes::children(node);
145
156
  Some((object, *children.get(1)?))
@@ -150,7 +161,7 @@ fn subscript<'tree>(
150
161
  return None;
151
162
  }
152
163
  let receiver = node.field("receiver")?;
153
- let arguments = super::nodes::children(node.field("arguments")?);
164
+ let arguments = super::nodes::children_in(node.field("arguments")?, context);
154
165
  Some((receiver, *arguments.first()?))
155
166
  }
156
167
  }
@@ -179,7 +190,13 @@ fn length_subtraction<'tree>(
179
190
  }
180
191
  let left = node.field("left")?;
181
192
  match left.kind_str() {
182
- "identifier" if LENGTH_METHODS.contains(&context.source.node_text(left)) => {
193
+ // `(send $_ {:length :size :count})` matches a call, and a local variable named `length`
194
+ // is an `lvar` -- `length = do_something; self[length - 1]` subtracts from a number nobody
195
+ // asked the receiver for, so there is no `[-1]` to rewrite it to.
196
+ "identifier"
197
+ if LENGTH_METHODS.contains(&context.source.node_text(left))
198
+ && !context.variable_analysis().is_variable_reference(left) =>
199
+ {
183
200
  Some((None, count))
184
201
  }
185
202
  "call" => {
@@ -187,6 +204,11 @@ fn length_subtraction<'tree>(
187
204
  if !LENGTH_METHODS.contains(&context.source.node_text(selector)) {
188
205
  return None;
189
206
  }
207
+ // `(send $_ {:length :size :count})` names no arguments, so it matches only the call
208
+ // that asks for the length: `arr.size(1)` counts something else entirely.
209
+ if left.field("arguments").is_some() || left.field("block").is_some() {
210
+ return None;
211
+ }
190
212
  Some((left.field("receiver"), count))
191
213
  }
192
214
  _ => None,
@@ -117,7 +117,7 @@ fn parenthesize_arguments(context: &RuleContext<'_>, node: Node<'_>) -> Option<S
117
117
  if selector.kind_str() == "operator" {
118
118
  return None;
119
119
  }
120
- let arguments = super::nodes::children(node.field("arguments")?);
120
+ let arguments = super::nodes::children_in(node.field("arguments")?, context);
121
121
  if arguments.is_empty() {
122
122
  return None;
123
123
  }
@@ -44,7 +44,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
44
44
  {
45
45
  continue;
46
46
  }
47
- let outer_arguments = super::nodes::children(arguments);
47
+ let outer_arguments = super::nodes::children_in(arguments, context);
48
48
  // `each_child_node(:call)` walks the receiver too, since it is a child of the send.
49
49
  let children = node
50
50
  .field("receiver")
@@ -57,7 +57,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
57
57
  let Some(nested_arguments) = nested.field("arguments") else {
58
58
  continue;
59
59
  };
60
- let inner = super::nodes::children(nested_arguments);
60
+ let inner = super::nodes::children_in(nested_arguments, context);
61
61
  if inner.is_empty()
62
62
  || nested_arguments
63
63
  .child(0)
@@ -15,9 +15,9 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
15
15
  let mut reported: HashSet<(usize, usize)> = HashSet::new();
16
16
 
17
17
  for node in context.nodes_of("conditional") {
18
- let nested: Vec<Node<'_>> = super::nodes::children(node)
18
+ let nested: Vec<Node<'_>> = super::nodes::children_in(node, context)
19
19
  .into_iter()
20
- .flat_map(super::conditional::descendants)
20
+ .flat_map(|node| super::conditional::descendants(node, context))
21
21
  .filter(|descendant| descendant.kind_str() == "conditional")
22
22
  .collect();
23
23
  for descendant in nested {
@@ -128,9 +128,13 @@ impl Cop<'_, '_> {
128
128
  let Some(condition) = last.field("condition") else {
129
129
  return;
130
130
  };
131
+ // The reported range stops at the condition, but the correction reaches the `then` beyond
132
+ // it and the `end` that closes the branch. Anchoring on the whole conditional is what keeps
133
+ // those edits inside the offense they belong to.
131
134
  let offense = self
132
135
  .context
133
- .offense(MSG, last.start_byte()..condition.end_byte());
136
+ .offense(MSG, last.start_byte()..condition.end_byte())
137
+ .corrections_anchored_at(last.byte_range());
134
138
  offenses.push(offense.corrected_by_all(self.autocorrect(last, condition, reindented)));
135
139
  }
136
140
 
@@ -164,16 +168,12 @@ impl Cop<'_, '_> {
164
168
  .field("consequence")
165
169
  .map(body_of)
166
170
  .or_else(|| node.field("body").map(Body::One));
167
- [
168
- node.field("condition"),
169
- body.and_then(|body| body.single()),
170
- ]
171
- .into_iter()
172
- .flatten()
173
- .any(|child| {
174
- matches!(child.kind_str(), "if" | "unless")
175
- && child.field("alternative").is_some()
176
- })
171
+ [node.field("condition"), body.and_then(|body| body.single())]
172
+ .into_iter()
173
+ .flatten()
174
+ .any(|child| {
175
+ matches!(child.kind_str(), "if" | "unless") && child.field("alternative").is_some()
176
+ })
177
177
  }
178
178
 
179
179
  fn min_body_length(&self, node: Node<'_>) -> bool {
@@ -306,7 +306,7 @@ impl Cop<'_, '_> {
306
306
 
307
307
  /// `heredoc_lines`: `(body.line...body.last_line)` of every heredoc the node opens.
308
308
  fn heredoc_lines(&self, node: Node<'_>) -> Vec<usize> {
309
- let beginnings: Vec<usize> = descendants(node)
309
+ let beginnings: Vec<usize> = descendants(node, self.context)
310
310
  .into_iter()
311
311
  .filter(|inner| inner.kind_str() == "heredoc_beginning")
312
312
  .map(|inner| inner.start_byte())
@@ -108,7 +108,7 @@ fn returns_nil(node: Node<'_>, context: &RuleContext<'_>) -> bool {
108
108
  match node.kind_str() {
109
109
  "nil" => true,
110
110
  "return" | "next" | "break" => {
111
- let arguments = super::nodes::children(node);
111
+ let arguments = super::nodes::children_in(node, context);
112
112
  let [list] = arguments.as_slice() else {
113
113
  return false;
114
114
  };
@@ -116,7 +116,7 @@ fn returns_nil(node: Node<'_>, context: &RuleContext<'_>) -> bool {
116
116
  return false;
117
117
  }
118
118
  let _ = context;
119
- matches!(super::nodes::children(*list).as_slice(), [only] if only.kind_str() == "nil")
119
+ matches!(super::nodes::children_in(*list, context).as_slice(), [only] if only.kind_str() == "nil")
120
120
  }
121
121
  _ => false,
122
122
  }
@@ -57,6 +57,10 @@ pub(super) fn is_operator_method(name: &str) -> bool {
57
57
  }
58
58
 
59
59
  /// The node's children as upstream's AST holds them.
60
+ ///
61
+ /// The list comes from the file's index rather than from a tree cursor opened here: this is one of
62
+ /// the most-called helpers in the registry, and the cursor walk was among the largest single costs
63
+ /// a sampling profile found.
60
64
  pub(super) fn children<'tree>(node: Node<'tree>) -> Vec<Node<'tree>> {
61
65
  let mut cursor = node.walk();
62
66
  node.named_children(&mut cursor)
@@ -64,6 +68,23 @@ pub(super) fn children<'tree>(node: Node<'tree>) -> Vec<Node<'tree>> {
64
68
  .collect()
65
69
  }
66
70
 
71
+ /// [`children`] answered from the file's index, for a caller that has the cop's context to hand.
72
+ /// This is one of the most-called helpers in the registry, and the cursor walk `children` opens
73
+ /// was among the largest single costs a sampling profile found.
74
+ pub(super) fn children_in<'tree>(
75
+ node: Node<'tree>,
76
+ context: &'tree RuleContext<'_>,
77
+ ) -> Vec<Node<'tree>> {
78
+ match context.named_children(node) {
79
+ Some(found) => found
80
+ .iter()
81
+ .copied()
82
+ .filter(|child| is_child(*child))
83
+ .collect(),
84
+ None => children(node),
85
+ }
86
+ }
87
+
67
88
  /// `ProcessedSource#contains_comment?`: whether a comment sits on any line the range spans. It
68
89
  /// reads *lines* rather than the range itself, so a trailing comment on the closing line counts.
69
90
  pub(super) fn contains_comment(range: &std::ops::Range<usize>, context: &RuleContext<'_>) -> bool {
@@ -26,6 +26,17 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
26
26
  }
27
27
 
28
28
  for node in context.nodes_of_any(&["binary", "call", "unary"]) {
29
+ if semantic_changes
30
+ && !is_predicate_method_result(context, node)
31
+ && let Some(edits) = semantic_change(context, node)
32
+ {
33
+ offenses.push(
34
+ context
35
+ .offense(MSG_FOR_REDUNDANCY, node.byte_range())
36
+ .corrected_by_all(edits),
37
+ );
38
+ continue;
39
+ }
29
40
  let Some(receiver) = comparison_with_nil(context, node) else {
30
41
  continue;
31
42
  };
@@ -56,6 +67,75 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
56
67
  }
57
68
  }
58
69
 
70
+ /// The two checks `IncludeSemanticChanges` adds: `not_and_nil_check?` (`!x.nil?`) and
71
+ /// `unless_and_nil_check?` (`unless x.nil?`). Both drop the `nil?` call itself, which is the
72
+ /// semantic change the option is named for -- `x` is falsy for `false` too.
73
+ fn semantic_change(context: &RuleContext<'_>, node: Node<'_>) -> Option<Vec<Edit>> {
74
+ // `autocorrect_non_nil`: `!x.nil?` becomes `x`, and a receiverless `!nil?` becomes `self`.
75
+ // `not x.nil?` is the same `:!` send as `!x.nil?`. The grammar gives `!` an `operator` field
76
+ // and leaves `not` as an anonymous child, so both spellings have to be read.
77
+ let negation = node.kind_str() == "unary"
78
+ && node
79
+ .field("operator")
80
+ .or_else(|| node.child(0))
81
+ .is_some_and(|operator| matches!(context.source.node_text(operator), "!" | "not"));
82
+ if negation {
83
+ let operand = node.field("operand")?;
84
+ if !is_nil_predicate(context, operand) {
85
+ return None;
86
+ }
87
+ let replacement = match operand.field("receiver") {
88
+ Some(receiver) => context.source.node_text(receiver).to_owned(),
89
+ None => "self".to_owned(),
90
+ };
91
+ return Some(vec![Edit {
92
+ start: node.start_byte(),
93
+ end: node.end_byte(),
94
+ replacement,
95
+ safe: true,
96
+ }]);
97
+ }
98
+
99
+ // `autocorrect_unless_nil`: the keyword becomes `if` and the condition loses its `nil?`.
100
+ if !is_nil_predicate(context, node) {
101
+ return None;
102
+ }
103
+ let parent = node.parent_of(context)?;
104
+ if !matches!(parent.kind_str(), "unless" | "unless_modifier") {
105
+ return None;
106
+ }
107
+ if parent.field("condition")?.id() != node.id() {
108
+ return None;
109
+ }
110
+ let keyword = (0..parent.child_count())
111
+ .filter_map(|index| parent.child(index as u32))
112
+ .find(|child| context.source.node_text(*child) == "unless")?;
113
+ let receiver = node.field("receiver")?;
114
+ Some(vec![
115
+ Edit {
116
+ start: keyword.start_byte(),
117
+ end: keyword.end_byte(),
118
+ replacement: "if".to_owned(),
119
+ safe: true,
120
+ },
121
+ Edit {
122
+ start: node.start_byte(),
123
+ end: node.end_byte(),
124
+ replacement: context.source.node_text(receiver).to_owned(),
125
+ safe: true,
126
+ },
127
+ ])
128
+ }
129
+
130
+ /// `nil_check?`: `(send _ :nil?)` with no arguments.
131
+ fn is_nil_predicate(context: &RuleContext<'_>, node: Node<'_>) -> bool {
132
+ node.kind_str() == "call"
133
+ && node
134
+ .field("method")
135
+ .is_some_and(|method| context.source.node_text(method) == "nil?")
136
+ && node.field("arguments").is_none()
137
+ }
138
+
59
139
  /// `not_equal_to_nil?`: `(send _ :!= nil)`. The two checks `IncludeSemanticChanges` adds are
60
140
  /// `!x.nil?` and `unless x.nil?`, which the default configuration leaves alone.
61
141
  fn comparison_with_nil<'t>(context: &RuleContext<'_>, node: Node<'t>) -> Option<Node<'t>> {
@@ -66,7 +146,7 @@ fn comparison_with_nil<'t>(context: &RuleContext<'_>, node: Node<'t>) -> Option<
66
146
  node.field("right")?,
67
147
  ),
68
148
  "call" => {
69
- let arguments = super::nodes::children(node.field("arguments")?);
149
+ let arguments = super::nodes::children_in(node.field("arguments")?, context);
70
150
  let [only] = arguments.as_slice() else {
71
151
  return None;
72
152
  };
@@ -103,7 +183,7 @@ fn is_predicate_method_result(context: &RuleContext<'_>, node: Node<'_>) -> bool
103
183
  return false;
104
184
  }
105
185
  // The body is the statement itself when it stands alone, and its last statement otherwise.
106
- super::nodes::children(parent)
186
+ super::nodes::children_in(parent, context)
107
187
  .last()
108
188
  .is_some_and(|last| last.id() == node.id())
109
189
  }
@@ -20,9 +20,12 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
20
20
  .setting::<String>("EnforcedStyle")
21
21
  .is_some_and(|style| style == "disallow");
22
22
  let locals = LocalVariables::new(context);
23
- for node in context.nodes_of("call") {
23
+ // **A lambda literal is a `block` upstream too.** `-> { _1 }` is a `numblock` whose send is
24
+ // the `lambda` call, while the grammar gives the arrow a node of its own that holds the block.
25
+ for node in context.nodes_of_any(&["call", "lambda"]) {
24
26
  let Some(block) = node
25
27
  .field("block")
28
+ .or_else(|| (node.kind_str() == "lambda").then(|| node.field("body")).flatten())
26
29
  .filter(|block| BLOCK_KINDS.contains(&block.kind_str()))
27
30
  else {
28
31
  continue;
@@ -53,9 +53,9 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
53
53
  /// `each_descendant(:lvar)` filtered by `/\A_[1-9]\z/`.
54
54
  fn numbered_parameters(node: Node<'_>, context: &RuleContext<'_>) -> HashSet<String> {
55
55
  let mut found = HashSet::new();
56
- let mut stack: Vec<Node<'_>> = super::nodes::children(node);
56
+ let mut stack: Vec<Node<'_>> = super::nodes::children_in(node, context);
57
57
  while let Some(current) = stack.pop() {
58
- stack.extend(super::nodes::children(current));
58
+ stack.extend(super::nodes::children_in(current, context));
59
59
  if current.kind_str() != "identifier" {
60
60
  continue;
61
61
  }
@@ -14,6 +14,19 @@ const MSG: &str =
14
14
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
15
15
  let min_digits: usize = context.setting("MinDigits").unwrap_or(5);
16
16
  let strict: bool = context.setting("Strict").unwrap_or(false);
17
+ // `allowed_numbers.include?(int)`: compared as **strings**, against the integer part alone.
18
+ let allowed: Vec<String> = context
19
+ .setting::<Vec<serde_yaml_ng::Value>>("AllowedNumbers")
20
+ .unwrap_or_default()
21
+ .iter()
22
+ .map(|value| match value {
23
+ serde_yaml_ng::Value::String(text) => text.clone(),
24
+ other => serde_yaml_ng::to_string(other)
25
+ .unwrap_or_default()
26
+ .trim()
27
+ .to_owned(),
28
+ })
29
+ .collect();
17
30
 
18
31
  for node in context.nodes_of_any(&["integer", "float"]) {
19
32
  // A rational or imaginary suffix makes a different literal upstream (`rational` / `complex`
@@ -30,7 +43,10 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
30
43
  let integer = integer_part(source);
31
44
  // Anything starting with `0` is a non-decimal literal (`0x`, `0b`, `0o`, or a leading-zero
32
45
  // octal), which this cop does not know how to group.
33
- if integer.starts_with('0') || integer.chars().count() < min_digits {
46
+ if integer.starts_with('0')
47
+ || allowed.iter().any(|entry| entry == integer)
48
+ || integer.chars().count() < min_digits
49
+ {
34
50
  continue;
35
51
  }
36
52
  if !offending(integer, strict) {
@@ -4,6 +4,11 @@ use tree_sitter::Node;
4
4
  use crate::diagnostic::{Edit, Offense};
5
5
  use crate::rules::RuleContext;
6
6
  use crate::rules::node_ext::NodeExt;
7
+ use crate::rules::send_node;
8
+
9
+ /// The version `positive?` and `negative?` were added in.
10
+ const PREDICATES_SINCE: crate::ruby_version::RubyVersion =
11
+ crate::ruby_version::RubyVersion::new(2, 3);
7
12
 
8
13
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
9
14
  let predicate_style = context
@@ -25,6 +30,11 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
25
30
  {
26
31
  continue;
27
32
  }
33
+ // `replacement_supported?`: `positive?` and `negative?` arrived in 2.3, so a comparison is
34
+ // left alone below that -- `zero?` has always been there.
35
+ if matches!(operator, ">" | "<") && context.target_ruby_version() < PREDICATES_SINCE {
36
+ continue;
37
+ }
28
38
 
29
39
  let replacement = match predicate_style {
30
40
  true => format!(
@@ -86,7 +96,13 @@ fn predicate<'tree>(
86
96
  context: &RuleContext<'_>,
87
97
  node: Node<'tree>,
88
98
  ) -> Option<(Node<'tree>, &'static str)> {
89
- if node.kind_str() != "call" || node.field("arguments").is_some() {
99
+ // Upstream dispatches `on_send` but does not alias it to `on_csend`: a predicate reached
100
+ // through `&.` is deliberately outside this cop even though the grammar calls both forms a
101
+ // `call`.
102
+ if node.kind_str() != "call"
103
+ || !send_node::is_plain_send(node, context)
104
+ || node.field("arguments").is_some()
105
+ {
90
106
  return None;
91
107
  }
92
108
  let receiver = node.field("receiver")?;
@@ -127,6 +143,11 @@ fn operands<'a, 'tree>(
127
143
  ))
128
144
  }
129
145
  _ => {
146
+ // `comparison` is a `(send …)` pattern, and `&.` is a `csend`: the grammar spells both
147
+ // a `call`, so the operator reached through a safe navigation has to be excluded here.
148
+ if !send_node::is_plain_send(node, context) {
149
+ return None;
150
+ }
130
151
  let method = node.field("method")?;
131
152
  let arguments = super::nodes::children(node.field("arguments")?);
132
153
  match arguments.as_slice() {
@@ -174,11 +195,9 @@ fn parenthesized_source(context: &RuleContext<'_>, node: Node<'_>) -> String {
174
195
  fn require_parentheses(context: &RuleContext<'_>, node: Node<'_>) -> bool {
175
196
  match node.kind_str() {
176
197
  // `&&` and `||` are not sends upstream, so they are not binary operations either.
177
- "binary" => node
178
- .field("operator")
179
- .is_some_and(|operator| {
180
- super::nodes::is_operator_method(context.source.node_text(operator))
181
- }),
198
+ "binary" => node.field("operator").is_some_and(|operator| {
199
+ super::nodes::is_operator_method(context.source.node_text(operator))
200
+ }),
182
201
  // `a[b]` is a call to `:[]`, and its `loc.begin` is a bracket rather than a parenthesis.
183
202
  "element_reference" => true,
184
203
  "call" => {
@@ -34,6 +34,15 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
34
34
  let Some(alternative) = node.field("alternative") else {
35
35
  continue;
36
36
  };
37
+ // `return unless node.else_branch`: upstream's `else_branch` is what the clause **holds**,
38
+ // so a bare `else end` answers `nil`. The grammar keeps the empty clause as a node, and
39
+ // taking its presence for a branch made `if cond then run else end` look convertible.
40
+ if !crate::rules::send_node::named_children_of(alternative, context)
41
+ .iter()
42
+ .any(|child| child.kind_str() != "comment")
43
+ {
44
+ continue;
45
+ }
37
46
  let consequence = node.field("consequence");
38
47
  // `node.if_branch&.begin_type?`: a then-branch upstream reads as a `begin` has no ternary
39
48
  // arm to become.
@@ -141,7 +150,7 @@ fn branch(context: &RuleContext<'_>, node: Option<Node<'_>>) -> String {
141
150
  return "nil".to_owned();
142
151
  };
143
152
  let (source, node) = match node.kind_str() {
144
- "then" | "else" => match super::nodes::children(node).as_slice() {
153
+ "then" | "else" => match super::nodes::children_in(node, context).as_slice() {
145
154
  [only] => (context.source.node_text(*only).to_owned(), Some(*only)),
146
155
  [] => return "nil".to_owned(),
147
156
  [first, .., last] => (
@@ -191,11 +200,19 @@ fn requires_parentheses(context: &RuleContext<'_>, node: Node<'_>) -> bool {
191
200
  {
192
201
  return true;
193
202
  }
194
- // `keyword_with_changed_precedence?`: `not x`, and a keyword written with arguments.
203
+ // `keyword_with_changed_precedence?`: `not x`, and a keyword written with arguments -- which
204
+ // includes `defined? :A`, a `defined?` node upstream and a `unary` here. Only `not` was read,
205
+ // so `defined? :A` lost the parentheses that keep the `?` of the ternary out of its argument.
195
206
  if node.kind_str() == "unary" {
196
- return node
197
- .field("operator")
198
- .is_some_and(|operator| context.source.node_text(operator) == "not");
207
+ return node.field("operator").is_some_and(|operator| {
208
+ match context.source.node_text(operator) {
209
+ "not" => true,
210
+ "defined?" => node
211
+ .field("operand")
212
+ .is_some_and(|operand| !context.source.node_text(operand).starts_with('(')),
213
+ _ => false,
214
+ }
215
+ });
199
216
  }
200
217
  // `node.arguments? && !node.parenthesized_call?`: a keyword whose arguments are already in
201
218
  // parentheses takes nothing more from what follows, so it needs no parentheses of its own.
@@ -204,7 +221,7 @@ fn requires_parentheses(context: &RuleContext<'_>, node: Node<'_>) -> bool {
204
221
  && node
205
222
  .child(1)
206
223
  .is_some_and(|arguments| !context.source.node_text(arguments).starts_with('('))
207
- && !super::nodes::children(node).is_empty()
224
+ && !super::nodes::children_in(node, context).is_empty()
208
225
  }
209
226
 
210
227
  /// `IfThenCorrector`: the same conditional written over several lines.
@@ -246,7 +263,7 @@ fn expanded(context: &RuleContext<'_>, node: Node<'_>, width: usize, column: usi
246
263
 
247
264
  /// The source of everything a `then` or `else` clause holds.
248
265
  fn clause_source(context: &RuleContext<'_>, clause: Node<'_>) -> String {
249
- let written = super::nodes::children(clause);
266
+ let written = super::nodes::children_in(clause, context);
250
267
  match (written.first(), written.last()) {
251
268
  (Some(first), Some(last)) => context
252
269
  .source
@@ -127,6 +127,11 @@ fn has_first_child(node: Node<'_>, locals: &LocalVariables<'_, '_>) -> bool {
127
127
  // A bare name is an `lvar` when it names a variable and a receiverless `send` otherwise.
128
128
  "identifier" => locals.is_lvar(node),
129
129
  "call" => node.field("receiver").is_some(),
130
+ // `(const nil :CONST)`: the namespace is the first child, and a plain constant has none.
131
+ // `::Foo` and `Foo::Bar` both carry one, which the grammar spells as a `scope_resolution`.
132
+ "constant" => false,
133
+ // An empty literal has no children either.
134
+ "array" | "hash" | "string_array" | "symbol_array" => node.named_child_count() > 0,
130
135
  "nil" | "true" | "false" | "self" | "super" => false,
131
136
  _ => true,
132
137
  }
@@ -14,7 +14,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
14
14
  let Some(parameters) = node.field("parameters") else {
15
15
  continue;
16
16
  };
17
- let arguments = super::nodes::children(parameters);
17
+ let arguments = super::nodes::children_in(parameters, context);
18
18
  let optional: Vec<usize> = positions(&arguments, "optional_parameter");
19
19
  // `arg_type?`: only a plain argument counts. A destructuring `(a, b)` is an `mlhs`
20
20
  // upstream, and a keyword or splat argument is a type of its own.
@@ -72,7 +72,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
72
72
  if empty_if
73
73
  && node
74
74
  .field("consequence")
75
- .is_some_and(|then| !super::nodes::children(then).is_empty())
75
+ .is_some_and(|then| !super::nodes::children_in(then, context).is_empty())
76
76
  {
77
77
  continue;
78
78
  }