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
@@ -4,7 +4,7 @@ use tree_sitter::Node;
4
4
 
5
5
  use crate::diagnostic::{Edit, Offense};
6
6
  use crate::rules::RuleContext;
7
- use crate::rules::send_node::{arguments, named_children};
7
+ use crate::rules::send_node::arguments;
8
8
  use crate::rules::support;
9
9
 
10
10
  use super::blocks::{BLOCK_KINDS, BlockArgs};
@@ -12,6 +12,7 @@ use super::literals::{is_literal, literal_type};
12
12
  use super::locals::LocalVariables;
13
13
  use super::statements::{Branch, begin_containers, body_children, has_clause, statements};
14
14
  use crate::rules::node_ext::NodeExt;
15
+ use crate::rules::send_node::named_children_of;
15
16
 
16
17
  const SELF_MSG: &str = "`self` used in void context.";
17
18
 
@@ -270,15 +271,26 @@ impl Void<'_, '_> {
270
271
  .then(|| OperatorCall {
271
272
  method,
272
273
  selector: operator.byte_range(),
273
- receiver: node
274
- .field("left")
275
- .map(|left| left.byte_range()),
274
+ receiver: node.field("left").map(|left| left.byte_range()),
276
275
  argument_count: 1,
277
276
  dot: None,
278
277
  })
279
278
  }
280
279
  "unary" => {
281
280
  let operator = node.field("operator")?;
281
+ // **A sign in front of a number is part of the number upstream.** `-3` parses to
282
+ // `(int -3)`, not to a `-@` send, so it is a literal here and no operator at all --
283
+ // the grammar keeps the two apart and would otherwise report both.
284
+ if matches!(self.text(operator), "-" | "+")
285
+ && node.field("operand").is_some_and(|operand| {
286
+ matches!(
287
+ operand.kind_str(),
288
+ "integer" | "float" | "rational" | "complex"
289
+ )
290
+ })
291
+ {
292
+ return None;
293
+ }
282
294
  let method = match self.text(operator) {
283
295
  "-" => "-@",
284
296
  "+" => "+@",
@@ -291,9 +303,7 @@ impl Void<'_, '_> {
291
303
  Some(OperatorCall {
292
304
  method,
293
305
  selector: operator.byte_range(),
294
- receiver: node
295
- .field("operand")
296
- .map(|operand| operand.byte_range()),
306
+ receiver: node.field("operand").map(|operand| operand.byte_range()),
297
307
  argument_count: 0,
298
308
  dot: None,
299
309
  })
@@ -309,13 +319,9 @@ impl Void<'_, '_> {
309
319
  Some(OperatorCall {
310
320
  method: name,
311
321
  selector: method.byte_range(),
312
- receiver: node
313
- .field("receiver")
314
- .map(|receiver| receiver.byte_range()),
322
+ receiver: node.field("receiver").map(|receiver| receiver.byte_range()),
315
323
  argument_count: arguments(node).len(),
316
- dot: node
317
- .field("operator")
318
- .map(|operator| operator.byte_range()),
324
+ dot: node.field("operator").map(|operator| operator.byte_range()),
319
325
  })
320
326
  }
321
327
  _ => None,
@@ -336,7 +342,7 @@ impl Void<'_, '_> {
336
342
  return;
337
343
  }
338
344
  if matches!(expression.kind_str(), "case" | "case_match") {
339
- for child in named_children(expression) {
345
+ for child in named_children_of(expression, self.context) {
340
346
  match child.kind_str() {
341
347
  "when" | "in_clause" => {
342
348
  if let Branch::One(body) = Branch::of(child.field("body")) {
@@ -387,7 +393,11 @@ impl Void<'_, '_> {
387
393
  node.kind_str(),
388
394
  "instance_variable" | "global_variable" | "class_variable"
389
395
  ) || (node.kind_str() == "identifier"
390
- && (self.locals.is_lvar(node) || self.is_block_parameter(node)));
396
+ && (self.locals.is_lvar(node)
397
+ || self.is_block_parameter(node)
398
+ // `special_keyword?`: upstream parses these as `const` and reports them with the
399
+ // variable message. The grammar spells them as plain identifiers.
400
+ || matches!(self.text(node), "__FILE__" | "__LINE__" | "__ENCODING__")));
391
401
  if !constant && !variable {
392
402
  return;
393
403
  }
@@ -406,8 +416,9 @@ impl Void<'_, '_> {
406
416
  while let Some(ancestor) = current {
407
417
  if BLOCK_KINDS.contains(&ancestor.kind_str()) {
408
418
  return match BlockArgs::of(ancestor, self.context, &self.locals) {
409
- BlockArgs::Numbered(highest) => (1..=highest)
410
- .any(|index| self.text(node) == format!("_{index}")),
419
+ BlockArgs::Numbered(highest) => {
420
+ (1..=highest).any(|index| self.text(node) == format!("_{index}"))
421
+ }
411
422
  BlockArgs::It => self.text(node) == "it",
412
423
  BlockArgs::Written(_) => false,
413
424
  };
@@ -501,7 +512,8 @@ impl Void<'_, '_> {
501
512
  if parent.is_some_and(|parent| {
502
513
  matches!(
503
514
  parent.kind_str(),
504
- "if" | "elsif" | "unless"
515
+ "if" | "elsif"
516
+ | "unless"
505
517
  | "if_modifier"
506
518
  | "unless_modifier"
507
519
  | "conditional"
@@ -549,10 +561,10 @@ impl Void<'_, '_> {
549
561
  /// `entirely_literal?`.
550
562
  fn entirely_literal(&self, node: Node<'_>) -> bool {
551
563
  match node.kind_str() {
552
- "array" | "string_array" | "symbol_array" => named_children(node)
564
+ "array" | "string_array" | "symbol_array" => named_children_of(node, self.context)
553
565
  .into_iter()
554
566
  .all(|child| self.entirely_literal(child)),
555
- "hash" => named_children(node)
567
+ "hash" => named_children_of(node, self.context)
556
568
  .into_iter()
557
569
  .filter(|child| child.kind_str() == "pair")
558
570
  .all(|pair| {
@@ -1,12 +1,16 @@
1
1
  //! `Metrics/AbcSize`, a port of `Utils::AbcSizeCalculator`.
2
2
  //!
3
- //! `CountRepeatedAttributes` is not read: the bundled configuration leaves it at `true`, which is
4
- //! what turns `Utils::RepeatedAttributeDiscount` off upstream, so the counting here is the counting
5
- //! every default run does. Setting it to `false` would ask for a discount this cop does not apply.
3
+ //! `CountRepeatedAttributes` is `true` in the bundled configuration, which turns
4
+ //! `Utils::RepeatedAttributeDiscount` off; [`Attributes`] is what a run setting it to `false` gets.
5
+
6
+ use std::collections::HashMap;
7
+
8
+ use tree_sitter::Node;
6
9
 
7
10
  use super::complexity::{Allowed, CsendDiscount, Emit, Kind, Order, Walk, measured};
8
11
  use crate::diagnostic::Offense;
9
12
  use crate::rules::RuleContext;
13
+ use crate::rules::node_ext::NodeExt;
10
14
 
11
15
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
12
16
  let max = context
@@ -21,8 +25,12 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
21
25
  let fragments = context.fragments();
22
26
  let locals = context.metric_locals();
23
27
  let walk = Walk::new(context, locals, fragments, Order::Post);
28
+ // `discount_repeated_attributes: !cop_config['CountRepeatedAttributes']`.
29
+ let discounting = !context
30
+ .setting::<bool>("CountRepeatedAttributes")
31
+ .unwrap_or(true);
24
32
  for method in methods {
25
- let vector = Vector::of(&walk, method.body);
33
+ let vector = Vector::of(&walk, method.body, context, locals, discounting);
26
34
  let size = vector.size();
27
35
  if size <= max {
28
36
  continue;
@@ -49,20 +57,47 @@ struct Vector {
49
57
  }
50
58
 
51
59
  impl Vector {
52
- fn of(walk: &Walk<'_>, body: tree_sitter::Node<'_>) -> Self {
60
+ fn of(
61
+ walk: &Walk<'_>,
62
+ body: tree_sitter::Node<'_>,
63
+ context: &RuleContext<'_>,
64
+ locals: &super::locals::Locals,
65
+ discounting: bool,
66
+ ) -> Self {
53
67
  let mut vector = Self::default();
54
68
  let mut discount = CsendDiscount::default();
55
- walk.run(body, &mut |emit| vector.count(emit, &mut discount));
69
+ let mut attributes = discounting.then(Attributes::default);
70
+ walk.run(body, &mut |emit| {
71
+ vector.count(emit, &mut discount, context, locals, attributes.as_mut());
72
+ });
56
73
  vector
57
74
  }
58
75
 
59
76
  /// `AbcSizeCalculator#calculate_node`, whose two arms are exclusive: a call is never also
60
77
  /// counted as a condition, though it may add one.
61
- fn count<'a>(&mut self, emit: Emit<'a>, discount: &mut CsendDiscount<'a>) {
78
+ fn count<'a>(
79
+ &mut self,
80
+ emit: Emit<'a>,
81
+ discount: &mut CsendDiscount<'a>,
82
+ context: &RuleContext<'_>,
83
+ locals: &super::locals::Locals,
84
+ attributes: Option<&mut Attributes>,
85
+ ) {
86
+ // `RepeatedAttributeDiscount#calculate_node` invalidates before the count, so an
87
+ // assignment to a receiver does not discount the reads that follow it.
88
+ let repeated = match attributes {
89
+ Some(known) => {
90
+ known.invalidate(emit, context, locals);
91
+ matches!(emit.kind, Kind::Send | Kind::Csend)
92
+ && known.repeats(emit.node, context, locals)
93
+ }
94
+ None => false,
95
+ };
62
96
  if self.assignment(emit, discount) {
63
97
  self.assignment += 1;
64
98
  }
65
99
  match emit.kind {
100
+ Kind::Send | Kind::Csend if repeated => {}
66
101
  Kind::Send | Kind::Csend | Kind::Yield => self.branch_node(emit, discount),
67
102
  // A block on a method that is known not to iterate is not a decision point.
68
103
  kind if is_condition(kind) && emit.iterating != Some(false) => {
@@ -275,3 +310,158 @@ fn scaled(value: u128, twos: i32, tens: i32) -> Option<u128> {
275
310
  let value = value.checked_mul(2u128.checked_pow(u32::try_from(twos).ok()?)?)?;
276
311
  value.checked_mul(10u128.checked_pow(u32::try_from(tens).ok()?)?)
277
312
  }
313
+
314
+ /// `Utils::RepeatedAttributeDiscount`: the attribute reads already seen, so a second read of the
315
+ /// same one is not counted as another branch.
316
+ ///
317
+ /// Upstream keys the tree by the receiver node itself and makes the `nil` and `self` receivers
318
+ /// share one entry, which is what lets `bar`, `self.bar` and `bar` count once between them. The
319
+ /// keys here spell the same identity as a string.
320
+ #[derive(Default)]
321
+ struct Attributes {
322
+ tree: HashMap<String, Attributes>,
323
+ }
324
+
325
+ impl Attributes {
326
+ /// `discount_repeated_attribute?`: walks the receiver chain, recording what it has not seen.
327
+ /// True only when every step of the chain was already there.
328
+ fn repeats(
329
+ &mut self,
330
+ node: Node<'_>,
331
+ context: &RuleContext<'_>,
332
+ locals: &super::locals::Locals,
333
+ ) -> bool {
334
+ match key_path(node, context, locals) {
335
+ Some(path) => self.record(&path),
336
+ None => false,
337
+ }
338
+ }
339
+
340
+ fn record(&mut self, path: &[String]) -> bool {
341
+ let Some((head, rest)) = path.split_first() else {
342
+ return true;
343
+ };
344
+ let known = self.tree.contains_key(head);
345
+ let deeper = self.tree.entry(head.clone()).or_default().record(rest);
346
+ known & deeper
347
+ }
348
+
349
+ /// `update_repeated_attribute`: writing to a receiver invalidates what was read through it.
350
+ fn invalidate(
351
+ &mut self,
352
+ emit: Emit<'_>,
353
+ context: &RuleContext<'_>,
354
+ locals: &super::locals::Locals,
355
+ ) {
356
+ let Some((path, method)) = setter_to_getter(emit, context, locals) else {
357
+ return;
358
+ };
359
+ let Some(branch) = self.navigate(&path) else {
360
+ return;
361
+ };
362
+ match method {
363
+ Some(method) => drop(branch.tree.remove(&method)),
364
+ None => branch.tree.clear(),
365
+ }
366
+ }
367
+
368
+ fn navigate(&mut self, path: &[String]) -> Option<&mut Self> {
369
+ let mut current = self;
370
+ for key in path {
371
+ current = current.tree.get_mut(key)?;
372
+ }
373
+ Some(current)
374
+ }
375
+ }
376
+
377
+ /// `setter_to_getter`, as the receiver chain to invalidate and the one attribute under it -- or
378
+ /// the whole branch, when a variable itself was written.
379
+ fn setter_to_getter(
380
+ emit: Emit<'_>,
381
+ context: &RuleContext<'_>,
382
+ locals: &super::locals::Locals,
383
+ ) -> Option<(Vec<String>, Option<String>)> {
384
+ match emit.kind {
385
+ // `(lvasgn :my_var _)` and its three siblings: everything read through the name goes.
386
+ Kind::Lvasgn | Kind::Asgn => {
387
+ let target = emit.node.field("left")?;
388
+ Some((vec![root_key(target, context, locals)?], None))
389
+ }
390
+ // `foo.bar = 1` and `foo.bar ||= 1` both name one attribute of one receiver.
391
+ Kind::Send | Kind::Csend if emit.setter => {
392
+ let receiver = emit.node.field("receiver");
393
+ let method = context.source.node_text(emit.node.field("method")?);
394
+ let path = match receiver {
395
+ Some(receiver) => key_path(receiver, context, locals)?,
396
+ None => vec![ROOT.to_owned()],
397
+ };
398
+ Some((path, Some(method.trim_end_matches('=').to_owned())))
399
+ }
400
+ Kind::OpAsgn | Kind::OrAsgn | Kind::AndAsgn => {
401
+ let target = emit.node.field("left")?;
402
+ match target.kind_str() {
403
+ "call" => {
404
+ let method = context.source.node_text(target.field("method")?);
405
+ let path = match target.field("receiver") {
406
+ Some(receiver) => key_path(receiver, context, locals)?,
407
+ None => vec![ROOT.to_owned()],
408
+ };
409
+ Some((path, Some(method.to_owned())))
410
+ }
411
+ _ => Some((vec![root_key(target, context, locals)?], None)),
412
+ }
413
+ }
414
+ _ => None,
415
+ }
416
+ }
417
+
418
+ /// The entry `nil` and `self` share upstream, by sharing one hash between the two keys.
419
+ const ROOT: &str = "self";
420
+
421
+ /// `find_attributes`, flattened: the chain of names a read walks through, or `None` when the node
422
+ /// is not a series of argument-less calls over a root the tree can key on.
423
+ fn key_path(
424
+ node: Node<'_>,
425
+ context: &RuleContext<'_>,
426
+ locals: &super::locals::Locals,
427
+ ) -> Option<Vec<String>> {
428
+ if let Some(root) = root_key(node, context, locals) {
429
+ return Some(vec![root]);
430
+ }
431
+ match node.kind_str() {
432
+ // `attribute_call?` is `(call _receiver _method)` -- two children, so no arguments.
433
+ "call" if node.field("arguments").is_none() && node.field("block").is_none() => {
434
+ let method = context.source.node_text(node.field("method")?);
435
+ let mut path = match node.field("receiver") {
436
+ Some(receiver) => key_path(receiver, context, locals)?,
437
+ None => vec![ROOT.to_owned()],
438
+ };
439
+ path.push(method.to_owned());
440
+ Some(path)
441
+ }
442
+ // A bare name that is not a local is `(send nil :name)`.
443
+ "identifier" => Some(vec![
444
+ ROOT.to_owned(),
445
+ context.source.node_text(node).to_owned(),
446
+ ]),
447
+ _ => None,
448
+ }
449
+ }
450
+
451
+ /// `root_node?`: the receivers the tree keys on directly rather than walking through.
452
+ fn root_key(
453
+ node: Node<'_>,
454
+ context: &RuleContext<'_>,
455
+ locals: &super::locals::Locals,
456
+ ) -> Option<String> {
457
+ let text = || context.source.node_text(node);
458
+ match node.kind_str() {
459
+ "self" => Some(ROOT.to_owned()),
460
+ "identifier" if locals.is_lvar(node) => Some(format!("lvar:{}", text())),
461
+ "instance_variable" => Some(format!("ivar:{}", text())),
462
+ "class_variable" => Some(format!("cvar:{}", text())),
463
+ "global_variable" => Some(format!("gvar:{}", text())),
464
+ "constant" | "scope_resolution" => Some(format!("const:{}", text())),
465
+ _ => None,
466
+ }
467
+ }
@@ -7,10 +7,17 @@ use crate::rules::node_ext::NodeExt;
7
7
 
8
8
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
9
9
  let max: usize = context.setting("Max").unwrap_or(25);
10
- let allowed: Vec<String> = context.setting("AllowedMethods").unwrap_or_default();
10
+ let allowed = crate::rules::support::allowed_methods(context);
11
+ // `matches_allowed_pattern?(node.method_name)`: the patterns are matched against the method
12
+ // name alone, never against the receiver.
13
+ let patterns = crate::rules::naming::support::forbidden_patterns_named(context, "AllowedPatterns");
11
14
  let heredocs = HeredocEnds::new(context);
12
15
  for node in context.nodes_of_any(&["block", "do_block"]) {
13
- if block_method_allowed(node, context, &allowed) || class_constructor(node, context) {
16
+ if block_method_allowed(node, context, &allowed)
17
+ || block_method_name(node, context)
18
+ .is_some_and(|name| patterns.iter().any(|pattern| pattern.is_match(name)))
19
+ || class_constructor(node, context)
20
+ {
14
21
  continue;
15
22
  }
16
23
  report_length(
@@ -40,13 +47,15 @@ fn block_method_name<'a>(node: Node<'_>, context: &'a RuleContext<'_>) -> Option
40
47
  )
41
48
  }
42
49
 
43
- fn block_receiver<'a>(node: Node<'_>, context: &'a RuleContext<'_>) -> Option<&'a str> {
44
- let call = node.parent_of(context).filter(|parent| parent.kind_str() == "call")?;
45
- Some(
46
- context
47
- .source
48
- .node_text(call.field("receiver")?),
49
- )
50
+ /// `node.receiver&.source&.gsub(/\s+/, '')`: the receiver as written, with every run of
51
+ /// whitespace removed -- a chain broken over lines (`Foo::\n Bar.baz`) names the same receiver
52
+ /// as the one written on a single line.
53
+ fn block_receiver(node: Node<'_>, context: &RuleContext<'_>) -> Option<String> {
54
+ let call = node
55
+ .parent_of(context)
56
+ .filter(|parent| parent.kind_str() == "call")?;
57
+ let written = context.source.node_text(call.field("receiver")?);
58
+ Some(written.split_whitespace().collect())
50
59
  }
51
60
 
52
61
  /// `AllowedMethods` entries are either a bare method name or `Receiver.method`; the second form
@@ -58,7 +67,7 @@ fn block_method_allowed(node: Node<'_>, context: &RuleContext<'_>, allowed: &[St
58
67
  let receiver = block_receiver(node, context);
59
68
  allowed.iter().any(|entry| match entry.split_once('.') {
60
69
  Some((entry_receiver, entry_method)) => {
61
- entry_method == method && Some(entry_receiver) == receiver
70
+ entry_method == method && receiver.as_deref() == Some(entry_receiver)
62
71
  }
63
72
  None => entry == method,
64
73
  })
@@ -127,8 +127,7 @@ fn clause_range(node: Node<'_>) -> Range<usize> {
127
127
  /// The `resbody` of `x rescue y`, which starts at the keyword and ends with the handler.
128
128
  fn modifier_clause(node: Node<'_>, handler: Node<'_>) -> Range<usize> {
129
129
  let mut cursor = node.walk();
130
- let keyword = node
131
- .children(&mut cursor)
130
+ let keyword = node.children(&mut cursor)
132
131
  .find(|child| !child.is_named() && child.kind_str() == "rescue")
133
132
  .map_or(node.start_byte(), |child| child.start_byte());
134
133
  keyword..handler.end_byte()
@@ -8,7 +8,12 @@ use crate::rules::node_ext::NodeExt;
8
8
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
9
9
  let max: usize = context.setting("Max").unwrap_or(100);
10
10
  let heredocs = HeredocEnds::new(context);
11
- for node in context.nodes_of_any(&["class", "singleton_class", "assignment"]) {
11
+ for node in context.nodes_of_any(&[
12
+ "class",
13
+ "singleton_class",
14
+ "assignment",
15
+ "operator_assignment",
16
+ ]) {
12
17
  let (measured, target) = match node.kind_str() {
13
18
  // A `class << self` inside a class body is part of that class's length rather than a
14
19
  // class of its own. Only `class` and `module` are classlike to `CodeLengthCalculator`,
@@ -17,8 +22,10 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
17
22
  "class" => (node, LengthTarget::Classlike),
18
23
  // `CONST = Class.new { ... }` and `CONST = Struct.new(...) { ... }` are class
19
24
  // definitions written as expressions, which `Node#class_definition?` recognises and
20
- // `on_casgn` measures here rather than as blocks.
21
- "assignment" => match class_definition_block(context, node) {
25
+ // `on_casgn` measures here rather than as blocks. `CONST ||= Struct.new { ... }`
26
+ // reaches upstream the same way: the `casgn` has no expression of its own, so
27
+ // `find_expression_within_parent` reads the assignment wrapping it.
28
+ "assignment" | "operator_assignment" => match class_definition_block(context, node) {
22
29
  Some(block) => (block, LengthTarget::Block),
23
30
  None => continue,
24
31
  },
@@ -46,7 +53,12 @@ fn class_definition_block<'tree>(
46
53
  assignment: Node<'tree>,
47
54
  ) -> Option<Node<'tree>> {
48
55
  let target = assignment.field("left")?;
49
- if !matches!(target.kind_str(), "constant" | "scope_resolution") {
56
+ // `Foo, Bar = Struct.new { ... }`: the `casgn` sits inside an `masgn` upstream, which
57
+ // `find_expression_within_parent` reads through.
58
+ if !matches!(
59
+ target.kind_str(),
60
+ "constant" | "scope_resolution" | "left_assignment_list"
61
+ ) {
50
62
  return None;
51
63
  }
52
64
  let call = assignment
@@ -6,6 +6,7 @@ use crate::diagnostic::Offense;
6
6
  use crate::rules::RuleContext;
7
7
  use crate::rules::node_ext::NodeExt;
8
8
  use crate::rules::send_node::{named_children, top_level_constant};
9
+ use crate::rules::send_node::named_children_of;
9
10
 
10
11
  const MSG: &str = "Avoid hard coding large quantities of data in code. \
11
12
  Prefer reading the data from an external source.";
@@ -46,7 +47,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
46
47
  if !top_level_constant(object, "Set", context) || is_assignment_target(node) {
47
48
  continue;
48
49
  }
49
- let children = named_children(node);
50
+ let children = named_children_of(node, context);
50
51
  let indices = children.get(1..).unwrap_or_default();
51
52
  if fold_pairs(indices).len() >= threshold {
52
53
  offenses.push(context.offense(MSG, node.byte_range()));
@@ -108,6 +108,9 @@ pub(super) enum Order {
108
108
 
109
109
  pub(super) struct Walk<'a> {
110
110
  source: &'a SourceFile,
111
+ /// The file's node index. The walk asks for a parent on every identifier it meets, and
112
+ /// `Node::parent` walks down from the root each time.
113
+ index: &'a super::super::AstIndex<'a>,
111
114
  locals: &'a Locals,
112
115
  fragments: &'a Fragments,
113
116
  order: Order,
@@ -122,6 +125,7 @@ impl<'a> Walk<'a> {
122
125
  ) -> Self {
123
126
  Self {
124
127
  source: context.source,
128
+ index: context.ast_index(),
125
129
  locals,
126
130
  fragments,
127
131
  order,
@@ -137,8 +141,18 @@ impl<'a> Walk<'a> {
137
141
  }
138
142
 
139
143
  fn children<F: FnMut(Emit<'a>)>(&self, node: Node<'a>, sink: &mut F) {
140
- for child in named_children(node) {
141
- self.visit(child, sink);
144
+ let mut cursor = node.walk();
145
+ if !cursor.goto_first_child() {
146
+ return;
147
+ }
148
+ loop {
149
+ let child = cursor.node();
150
+ if child.is_named() {
151
+ self.visit(child, sink);
152
+ }
153
+ if !cursor.goto_next_sibling() {
154
+ break;
155
+ }
142
156
  }
143
157
  }
144
158
 
@@ -694,9 +708,10 @@ impl<'a> Walk<'a> {
694
708
 
695
709
  fn visit_block_argument<F: FnMut(Emit<'a>)>(&self, node: Node<'a>, sink: &mut F) {
696
710
  let mut emit = Emit::of(Kind::BlockPass, node);
697
- emit.iterating = node
698
- .parent()
699
- .and_then(|arguments| arguments.parent())
711
+ emit.iterating = self
712
+ .index
713
+ .parent(node)
714
+ .and_then(|arguments| self.index.parent(arguments))
700
715
  .filter(|call| call.kind_str() == "call")
701
716
  .and_then(|call| self.iterating_call(call));
702
717
  self.around(emit, sink, |walk, sink| walk.children(node, sink));
@@ -819,9 +834,32 @@ impl<'a> Walk<'a> {
819
834
 
820
835
  /// Whether the identifier stands where the parser would have built `(send nil :name)`.
821
836
  fn receiverless_call(&self, node: Node<'_>) -> bool {
822
- is_receiverless_call(node)
837
+ is_receiverless_call(node, self.index)
823
838
  && !self.locals.is_lvar(node)
824
839
  && !is_keyword_literal(self.text(node))
840
+ && !self.is_implicit_block_parameter(node)
841
+ }
842
+
843
+ /// `it` inside a block that declares no parameters is that block's parameter -- an `itblock`
844
+ /// upstream, where the name is a variable rather than a call.
845
+ fn is_implicit_block_parameter(&self, node: Node<'_>) -> bool {
846
+ if self.text(node) != "it" {
847
+ return false;
848
+ }
849
+ let mut current = self.index.parent(node);
850
+ while let Some(parent) = current {
851
+ if matches!(parent.kind_str(), "block" | "do_block") {
852
+ return parent.field("parameters").is_none();
853
+ }
854
+ if matches!(
855
+ parent.kind_str(),
856
+ "method" | "singleton_method" | "class" | "module" | "lambda"
857
+ ) {
858
+ return false;
859
+ }
860
+ current = self.index.parent(parent);
861
+ }
862
+ false
825
863
  }
826
864
 
827
865
  /// Whether a block takes its parameters implicitly through `_1`, which makes it a `numblock`
@@ -986,7 +1024,7 @@ pub(super) struct Allowed {
986
1024
 
987
1025
  impl Allowed {
988
1026
  pub(super) fn new(context: &RuleContext<'_>) -> Self {
989
- let methods: Vec<String> = context.setting("AllowedMethods").unwrap_or_default();
1027
+ let methods = crate::rules::support::allowed_methods(context);
990
1028
  let patterns: Vec<String> = context.setting("AllowedPatterns").unwrap_or_default();
991
1029
  Self {
992
1030
  methods,
@@ -1058,14 +1096,14 @@ fn collect_targets<'a>(node: Node<'a>, targets: &mut Vec<Node<'a>>) {
1058
1096
 
1059
1097
  /// Whether the identifier stands where the parser would have built `(send nil :name)` rather than
1060
1098
  /// a name being declared, written or called on something else.
1061
- fn is_receiverless_call(node: Node<'_>) -> bool {
1062
- let Some(parent) = node.parent() else {
1099
+ fn is_receiverless_call(node: Node<'_>, index: &super::super::AstIndex<'_>) -> bool {
1100
+ let Some(parent) = index.parent(node) else {
1063
1101
  return true;
1064
1102
  };
1065
1103
  match parent.kind_str() {
1066
- "call" => field_name(node, parent) != Some("method"),
1067
- "method" | "singleton_method" => field_name(node, parent) != Some("name"),
1068
- "assignment" | "operator_assignment" => field_name(node, parent) != Some("left"),
1104
+ "call" => field_name(node, parent, index) != Some("method"),
1105
+ "method" | "singleton_method" => field_name(node, parent, index) != Some("name"),
1106
+ "assignment" | "operator_assignment" => field_name(node, parent, index) != Some("left"),
1069
1107
  "left_assignment_list"
1070
1108
  | "rest_assignment"
1071
1109
  | "destructured_left_assignment"
@@ -1081,8 +1119,8 @@ fn is_receiverless_call(node: Node<'_>) -> bool {
1081
1119
  | "keyword_parameter"
1082
1120
  | "splat_parameter"
1083
1121
  | "hash_splat_parameter"
1084
- | "block_parameter" => field_name(node, parent) != Some("name"),
1085
- "for" => field_name(node, parent) != Some("pattern"),
1122
+ | "block_parameter" => field_name(node, parent, index) != Some("name"),
1123
+ "for" => field_name(node, parent, index) != Some("pattern"),
1086
1124
  _ => true,
1087
1125
  }
1088
1126
  }