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
@@ -18,7 +18,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
18
18
  // unreported even when they hold non-ascii characters. tree-sitter has no such token
19
19
  // distinction: it reuses `identifier` for keyword-argument labels, which are `tLABEL`
20
20
  // upstream and must be skipped here to keep the two in step.
21
- if is_keyword_argument_label(node) {
21
+ if is_keyword_argument_label(node, context) {
22
22
  continue;
23
23
  }
24
24
 
@@ -42,8 +42,8 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
42
42
  }
43
43
  }
44
44
 
45
- fn is_keyword_argument_label(node: tree_sitter::Node<'_>) -> bool {
46
- let Some(parent) = node.parent() else {
45
+ fn is_keyword_argument_label(node: tree_sitter::Node<'_>, context: &RuleContext<'_>) -> bool {
46
+ let Some(parent) = context.parent(node) else {
47
47
  return false;
48
48
  };
49
49
  matches!(parent.kind_str(), "keyword_parameter" | "pair")
@@ -1,8 +1,9 @@
1
1
  use tree_sitter::Node;
2
2
 
3
- use super::support::{ParameterKind, Variables, parameters};
3
+ use super::support::{ParameterKind, parameters};
4
4
  use crate::diagnostic::{Edit, Offense};
5
5
  use crate::rules::RuleContext;
6
+ use crate::rules::lint::variable_force::Analysis;
6
7
  use crate::rules::node_ext::NodeExt;
7
8
 
8
9
  /// `EXCLUDED`: operators whose sole parameter is not the other operand. Indexing takes a key,
@@ -38,7 +39,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
38
39
  if text == "other" || text == "_other" {
39
40
  continue;
40
41
  }
41
- let variables = variables.get_or_insert_with(|| context.variable_roles());
42
+ let variables = variables.get_or_insert_with(|| context.variable_analysis());
42
43
  offenses.push(
43
44
  context
44
45
  .offense(
@@ -59,7 +60,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
59
60
  /// one and is put off to the next pass.
60
61
  fn rename(
61
62
  context: &RuleContext<'_>,
62
- variables: &Variables,
63
+ variables: &Analysis<'_>,
63
64
  definition: Node<'_>,
64
65
  parameter: Node<'_>,
65
66
  ) -> Vec<Edit> {
@@ -69,7 +70,7 @@ fn rename(
69
70
  if node.start_byte() <= parameter.start_byte()
70
71
  || node.end_byte() > definition.end_byte()
71
72
  || context.source.node_text(node) != name
72
- || !variables.is_variable(node)
73
+ || !(variables.is_reference(node) || variables.is_local_assignment(node))
73
74
  {
74
75
  continue;
75
76
  }
@@ -4,8 +4,9 @@ use crate::diagnostic::{Edit, Offense};
4
4
  use crate::ruby_version::RubyVersion;
5
5
  use crate::rules::RuleContext;
6
6
  use crate::rules::node_ext::NodeExt;
7
- use crate::rules::push_named_children;
8
- use crate::rules::send_node::{named_children, send_range};
7
+ use crate::rules::{push_named_children, push_named_children_in};
8
+ use crate::rules::send_node::send_range;
9
+ use crate::rules::send_node::named_children_of;
9
10
 
10
11
  /// `minimum_target_ruby_version 3.1`: anonymous block forwarding is 3.1 syntax.
11
12
  const MINIMUM: RubyVersion = RubyVersion::new(3, 1);
@@ -42,7 +43,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
42
43
  let Some(parameters) = node.field("parameters") else {
43
44
  continue;
44
45
  };
45
- let Some(last) = named_children(parameters).last().copied() else {
46
+ let Some(last) = named_children_of(parameters, context).last().copied() else {
46
47
  continue;
47
48
  };
48
49
  if expected_style(node, last, anonymous, context) {
@@ -96,13 +97,13 @@ fn expected_style(
96
97
  fn forwarded_arguments<'tree>(
97
98
  node: Node<'tree>,
98
99
  last: Node<'tree>,
99
- context: &RuleContext<'_>,
100
+ context: &'tree RuleContext<'_>,
100
101
  ) -> Option<Vec<Node<'tree>>> {
101
102
  let unsafe_in_block = context.target_ruby_version() <= ANONYMOUS_IN_BLOCK_REJECTED_THROUGH;
102
103
  let name = context.source.node_text(last);
103
104
  let mut found = Vec::new();
104
105
  let mut stack = Vec::new();
105
- push_named_children(node, &mut stack);
106
+ push_named_children_in(node, context, &mut stack);
106
107
  while let Some(current) = stack.pop() {
107
108
  if current.kind_str() == "block_argument" {
108
109
  // `invalidates_syntax?` is asked of every block pass, matching or not, and aborts the
@@ -111,14 +112,14 @@ fn forwarded_arguments<'tree>(
111
112
  return None;
112
113
  }
113
114
  // `&:sym` forwards a symbol rather than the block parameter.
114
- let symbolic = named_children(current).first().is_some_and(|child| {
115
+ let symbolic = named_children_of(current, context).first().is_some_and(|child| {
115
116
  matches!(child.kind_str(), "simple_symbol" | "delimited_symbol")
116
117
  });
117
118
  if !symbolic && context.source.node_text(current) == name {
118
119
  found.push(current);
119
120
  }
120
121
  }
121
- push_named_children(current, &mut stack);
122
+ push_named_children_in(current, context, &mut stack);
122
123
  }
123
124
  Some(found)
124
125
  }
@@ -313,7 +314,7 @@ fn reads_as_variable(node: Node<'_>, name: Option<&str>, context: &RuleContext<'
313
314
  {
314
315
  return true;
315
316
  }
316
- push_named_children(current, &mut stack);
317
+ push_named_children_in(current, context, &mut stack);
317
318
  }
318
319
  false
319
320
  }
@@ -3,7 +3,7 @@ use std::sync::LazyLock;
3
3
  use regex::Regex;
4
4
  use tree_sitter::Node;
5
5
 
6
- use super::support::{Variables, last_named_child};
6
+ use super::support::last_named_child;
7
7
  use crate::diagnostic::Offense;
8
8
  use crate::rules::RuleContext;
9
9
  use crate::rules::node_ext::NodeExt;
@@ -15,17 +15,16 @@ static SCREAMING_SNAKE_CASE: LazyLock<Regex> =
15
15
  LazyLock::new(|| Regex::new(r"^[0-9\p{Uppercase}_]+$").unwrap());
16
16
 
17
17
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
18
- let variables = context.variable_roles();
19
18
  // `rescue => Foo` and `for Foo in bar` are `casgn` nodes upstream just as much as `Foo = bar`
20
19
  // is, and neither carries a value, so neither can be excused by what it was assigned.
21
20
  for node in context.nodes_of("exception_variable") {
22
21
  if let Some(target) = node.named_child(0) {
23
- report(context, offenses, target, None, variables);
22
+ report(context, offenses, target, None);
24
23
  }
25
24
  }
26
25
  for node in context.nodes_of("for") {
27
26
  if let Some(target) = node.field("pattern") {
28
- report(context, offenses, target, None, variables);
27
+ report(context, offenses, target, None);
29
28
  }
30
29
  }
31
30
  for node in context.nodes_of_any(&["assignment", "operator_assignment"]) {
@@ -45,7 +44,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
45
44
  // The grammar swallowed the items written before the assignment; only the last of them
46
45
  // is really assigned to, and it keeps the value on the right.
47
46
  if let Some(target) = last_named_child(left) {
48
- report(context, offenses, target, value, variables);
47
+ report(context, offenses, target, value);
49
48
  }
50
49
  } else if left.kind_str() == "left_assignment_list" {
51
50
  // Every target of a multiple assignment is a casgn without an expression, so none of
@@ -53,10 +52,10 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
53
52
  let mut targets = Vec::new();
54
53
  collect_targets(left, &mut targets);
55
54
  for target in targets {
56
- report(context, offenses, target, None, variables);
55
+ report(context, offenses, target, None);
57
56
  }
58
57
  } else {
59
- report(context, offenses, left, value, variables);
58
+ report(context, offenses, left, value);
60
59
  }
61
60
  }
62
61
  }
@@ -66,12 +65,11 @@ fn report(
66
65
  offenses: &mut Vec<Offense>,
67
66
  target: Node<'_>,
68
67
  value: Option<Node<'_>>,
69
- variables: &Variables,
70
68
  ) {
71
69
  let Some(name_node) = constant_name(target) else {
72
70
  return;
73
71
  };
74
- if allowed_assignment(value, variables) {
72
+ if allowed_assignment(value, context) {
75
73
  return;
76
74
  }
77
75
  if SCREAMING_SNAKE_CASE.is_match(context.source.node_text(name_node)) {
@@ -110,11 +108,11 @@ fn collect_targets<'tree>(node: Node<'tree>, targets: &mut Vec<Node<'tree>>) {
110
108
  /// The shapes `allowed_assignment?` lets through. RuboCop only judges a constant's spelling once
111
109
  /// it can tell the value is not a class or module, because `SomeClass = SomeOtherClass` is a
112
110
  /// perfectly good CamelCase constant.
113
- fn allowed_assignment(value: Option<Node<'_>>, variables: &Variables) -> bool {
111
+ fn allowed_assignment(value: Option<Node<'_>>, context: &RuleContext<'_>) -> bool {
114
112
  let Some(value) = value else {
115
113
  return false;
116
114
  };
117
- match value_kind(value, variables) {
115
+ match value_kind(value, context) {
118
116
  Value::ClassLike => true,
119
117
  Value::Call { receiver } => receiver.is_none_or(|receiver| !literal_receiver(receiver)),
120
118
  Value::Conditional => branches(value).into_iter().any(is_constant),
@@ -133,7 +131,7 @@ enum Value<'tree> {
133
131
  Other,
134
132
  }
135
133
 
136
- fn value_kind<'tree>(node: Node<'tree>, variables: &Variables) -> Value<'tree> {
134
+ fn value_kind<'tree>(node: Node<'tree>, context: &RuleContext<'_>) -> Value<'tree> {
137
135
  match node.kind_str() {
138
136
  "constant" => Value::ClassLike,
139
137
  "scope_resolution" => match node.field("name") {
@@ -148,14 +146,28 @@ fn value_kind<'tree>(node: Node<'tree>, variables: &Variables) -> Value<'tree> {
148
146
  _ => Value::Other,
149
147
  },
150
148
  // A method call carrying a block is a `block` node upstream, not a `send`.
151
- "call" if node.field("block").is_some() => Value::ClassLike,
149
+ //
150
+ // **`numblock` and `itblock` are not `block`.** A block using `_1` or `it` reaches upstream
151
+ // as a type of its own, which the `%i[block const casgn]` list does not name -- so
152
+ // `Foo = Module.new do a(_1) end` is reported where the same block spelling its parameter
153
+ // is not.
154
+ // **A `numblock` passes every allowance.** It is not in `%i[block const casgn]`, it is no
155
+ // `send`, and `class_or_struct_return_method?` never reaches it -- so upstream reports the
156
+ // constant that a plain block would excuse.
157
+ "call" if node.field("block").is_some() => match node
158
+ .field("block")
159
+ .is_some_and(|block| is_numbered_block(context, block))
160
+ {
161
+ true => Value::Other,
162
+ false => Value::ClassLike,
163
+ },
152
164
  "lambda" => Value::ClassLike,
153
165
  "call" => Value::Call {
154
166
  receiver: node.field("receiver"),
155
167
  },
156
168
  // A bare identifier is a receiverless call unless the parser resolved it to a local.
157
169
  "identifier" => {
158
- if variables.is_reference(node) {
170
+ if context.variable_analysis().is_reference(node) {
159
171
  Value::Other
160
172
  } else {
161
173
  Value::Call { receiver: None }
@@ -171,9 +183,7 @@ fn value_kind<'tree>(node: Node<'tree>, variables: &Variables) -> Value<'tree> {
171
183
  "unary" => match operator(node) {
172
184
  Some("defined?") => Value::Other,
173
185
  // A signed number reaches the parser as one numeric literal, not a call to `-@`.
174
- Some("-" | "+") if node.field("operand").is_some_and(numeric) => {
175
- Value::Other
176
- }
186
+ Some("-" | "+") if node.field("operand").is_some_and(numeric) => Value::Other,
177
187
  _ => Value::Call {
178
188
  receiver: node.field("operand"),
179
189
  },
@@ -227,7 +237,10 @@ fn is_literal(node: Node<'_>) -> bool {
227
237
  }
228
238
 
229
239
  fn numeric(node: Node<'_>) -> bool {
230
- matches!(node.kind_str(), "integer" | "float" | "rational" | "complex")
240
+ matches!(
241
+ node.kind_str(),
242
+ "integer" | "float" | "rational" | "complex"
243
+ )
231
244
  }
232
245
 
233
246
  fn is_constant(node: Node<'_>) -> bool {
@@ -282,3 +295,28 @@ fn operator(node: Node<'_>) -> Option<&'static str> {
282
295
  }
283
296
  }
284
297
  }
298
+
299
+ /// The version `it` becomes a block parameter rather than a method call.
300
+ const IT_VERSION: crate::ruby_version::RubyVersion = crate::ruby_version::RubyVersion::new(3, 4);
301
+
302
+ /// `numblock` / `itblock`: a block with no parameter list that names `_1`…`_9` or `it` instead.
303
+ fn is_numbered_block(context: &RuleContext<'_>, block: Node<'_>) -> bool {
304
+ if block.field("parameters").is_some() {
305
+ return false;
306
+ }
307
+ let mut found = false;
308
+ crate::rules::walk_named(block, context, &mut |node| {
309
+ if found || node.kind_str() != "identifier" {
310
+ return;
311
+ }
312
+ let text = context.source.node_text(node);
313
+ // `it` is an `itblock` only from 3.4 on; before that it is an ordinary method call, and
314
+ // the block stays a `block`.
315
+ found |= (context.target_ruby_version() >= IT_VERSION && text == "it")
316
+ || (text.len() == 2
317
+ && text.starts_with('_')
318
+ && text.as_bytes()[1].is_ascii_digit()
319
+ && text.as_bytes()[1] != b'0');
320
+ });
321
+ found
322
+ }
@@ -1,12 +1,12 @@
1
- use std::path::Path;
2
1
  use std::sync::LazyLock;
3
2
 
4
- use globset::Glob;
5
3
  use regex::Regex;
4
+ use tree_sitter::Node;
6
5
 
7
6
  use super::support::{ruby_regex, ruby_regex_to_s};
8
7
  use crate::diagnostic::Offense;
9
8
  use crate::rules::RuleContext;
9
+ use crate::rules::node_ext::NodeExt;
10
10
 
11
11
  /// `SNAKE_CASE`, whose POSIX class is Unicode-aware in Ruby. A dot is allowed because only the
12
12
  /// last extension is stripped before the name is judged.
@@ -15,7 +15,7 @@ static SNAKE_CASE: LazyLock<Regex> =
15
15
 
16
16
  pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
17
17
  let path = context.source.path();
18
- if allowed_camel_case_file(context, path) {
18
+ if context.config.allowed_camel_case_file(path) {
19
19
  return;
20
20
  }
21
21
  let Some(basename) = path.file_name().and_then(|name| name.to_str()) else {
@@ -25,9 +25,13 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
25
25
  .setting::<serde_yaml_ng::Value>("Regex")
26
26
  .as_ref()
27
27
  .and_then(ruby_regex);
28
- // `ExpectMatchingDefinition` is off by default, and the checks it turns on are the only ones
29
- // that inspect the file's contents; a name that is already snake_case is otherwise fine.
28
+ // `for_bad_filename` branches both ways: a name that *is* good is where the class-and-module
29
+ // checks run, and a name that is not is where the naming message comes from. Returning early
30
+ // on a good name skipped `ExpectMatchingDefinition` entirely.
30
31
  if filename_good(basename, pattern.unwrap_or(&SNAKE_CASE)) {
32
+ if let Some(message) = class_and_module_naming(context, path, basename) {
33
+ offenses.push(context.offense(message, 0..0));
34
+ }
31
35
  return;
32
36
  }
33
37
  let ignore_scripts: bool = context.setting("IgnoreExecutableScripts").unwrap_or(true);
@@ -47,6 +51,171 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
47
51
  offenses.push(context.offense(message, 0..0));
48
52
  }
49
53
 
54
+ /// `perform_class_and_module_naming_checks`: with `ExpectMatchingDefinition` on, a well-named file
55
+ /// still has to define the constant its name spells.
56
+ fn class_and_module_naming(
57
+ context: &RuleContext<'_>,
58
+ path: &std::path::Path,
59
+ basename: &str,
60
+ ) -> Option<String> {
61
+ if !context
62
+ .setting::<bool>("ExpectMatchingDefinition")
63
+ .unwrap_or(false)
64
+ {
65
+ return None;
66
+ }
67
+ let hierarchy: bool = context
68
+ .setting("CheckDefinitionPathHierarchy")
69
+ .unwrap_or(true);
70
+ let acronyms: Vec<String> = context.setting("AllowedAcronyms").unwrap_or_default();
71
+ // The two arms differ only in what the namespace is read off: the whole path, or the base name
72
+ // alone. The message quotes whichever was used.
73
+ let (namespace, quoted) = match hierarchy {
74
+ true => (
75
+ to_namespace(context, path),
76
+ path.to_string_lossy().into_owned(),
77
+ ),
78
+ false => (
79
+ to_namespace(context, std::path::Path::new(basename)),
80
+ basename.to_owned(),
81
+ ),
82
+ };
83
+ if finds_definition(context, &namespace, &acronyms) {
84
+ return None;
85
+ }
86
+ let _ = quoted;
87
+ Some(format!(
88
+ "`{basename}` should define a class or module called `{}`.",
89
+ namespace.join("::")
90
+ ))
91
+ }
92
+
93
+ /// `to_namespace`: the path split into constant names, starting at the innermost directory the
94
+ /// configuration calls a root. With no root in the path only the file's own name is used.
95
+ fn to_namespace(context: &RuleContext<'_>, path: &std::path::Path) -> Vec<String> {
96
+ let roots: Vec<String> = context
97
+ .setting("CheckDefinitionPathHierarchyRoots")
98
+ .unwrap_or_else(|| ["lib", "spec", "test", "src"].map(str::to_owned).to_vec());
99
+ let components: Vec<String> = path
100
+ .iter()
101
+ .map(|part| part.to_string_lossy().into_owned())
102
+ .collect();
103
+ let start = components
104
+ .iter()
105
+ .rposition(|part| roots.contains(part))
106
+ .map(|index| index + 1);
107
+ match start {
108
+ Some(index) if index < components.len() => components[index..]
109
+ .iter()
110
+ .map(|part| module_name(part))
111
+ .collect(),
112
+ _ => components
113
+ .last()
114
+ .map(|part| vec![module_name(part)])
115
+ .unwrap_or_default(),
116
+ }
117
+ }
118
+
119
+ /// `to_module_name`: the extension goes, the underscores split words, each word is capitalised.
120
+ fn module_name(basename: &str) -> String {
121
+ let stem = basename.split_once('.').map_or(basename, |(head, _)| head);
122
+ stem.split('_')
123
+ .map(|word| {
124
+ let mut characters = word.chars();
125
+ match characters.next() {
126
+ Some(first) => first.to_uppercase().collect::<String>() + characters.as_str(),
127
+ None => String::new(),
128
+ }
129
+ })
130
+ .collect()
131
+ }
132
+
133
+ /// `find_class_or_module`: whether the file defines the innermost name, nested in the rest.
134
+ ///
135
+ /// The nesting is checked by name rather than by walking upwards the way upstream does -- the
136
+ /// namespaces a file writes are what `class A::B` and `module A; module B` both spell out.
137
+ fn finds_definition(context: &RuleContext<'_>, namespace: &[String], acronyms: &[String]) -> bool {
138
+ let Some((name, outer)) = namespace.split_last() else {
139
+ return true;
140
+ };
141
+ context
142
+ .nodes_of_any(&["class", "module", "assignment"])
143
+ .any(|node| {
144
+ let Some(defined) = defined_constant(context, node) else {
145
+ return false;
146
+ };
147
+ let Some((last, prefix)) = defined.split_last() else {
148
+ return false;
149
+ };
150
+ if last != name && !matches_acronym(name, last, acronyms) {
151
+ return false;
152
+ }
153
+ outer.is_empty() || enclosing_names(context, node, prefix).ends_with(outer)
154
+ })
155
+ }
156
+
157
+ /// The constant a `class` / `module` / `Struct.new` assignment defines, as its written path.
158
+ fn defined_constant(context: &RuleContext<'_>, node: Node<'_>) -> Option<Vec<String>> {
159
+ let named = match node.kind_str() {
160
+ "class" | "module" => node.field("name")?,
161
+ // `Foo = Struct.new(...)` defines `Foo` the same way.
162
+ "assignment" => {
163
+ let left = node.field("left")?;
164
+ let right = node.field("right")?;
165
+ let struct_new = right.kind_str() == "call"
166
+ && right
167
+ .field("receiver")
168
+ .is_some_and(|receiver| context.source.node_text(receiver) == "Struct")
169
+ && right
170
+ .field("method")
171
+ .is_some_and(|method| context.source.node_text(method) == "new");
172
+ if !struct_new {
173
+ return None;
174
+ }
175
+ left
176
+ }
177
+ _ => return None,
178
+ };
179
+ let text = context.source.node_text(named);
180
+ Some(
181
+ text.trim_start_matches("::")
182
+ .split("::")
183
+ .map(str::to_owned)
184
+ .collect(),
185
+ )
186
+ }
187
+
188
+ /// The names a definition is written inside, innermost last, with what its own path already spells.
189
+ fn enclosing_names(context: &RuleContext<'_>, node: Node<'_>, prefix: &[String]) -> Vec<String> {
190
+ let mut names: Vec<String> = Vec::new();
191
+ let mut current = node.parent();
192
+ while let Some(ancestor) = current {
193
+ if matches!(ancestor.kind_str(), "class" | "module")
194
+ && let Some(defined) = defined_constant(context, ancestor)
195
+ {
196
+ for part in defined.into_iter().rev() {
197
+ names.push(part);
198
+ }
199
+ }
200
+ current = ancestor.parent();
201
+ }
202
+ names.reverse();
203
+ names.extend(prefix.iter().cloned());
204
+ names
205
+ }
206
+
207
+ /// `match_acronym?`: an acronym listed in `AllowedAcronyms` may be written in full caps.
208
+ fn matches_acronym(expected: &str, name: &str, acronyms: &[String]) -> bool {
209
+ let folded = acronyms.iter().fold(name.to_owned(), |result, acronym| {
210
+ let mut capitalized = acronym.to_lowercase();
211
+ if let Some(first) = capitalized.get_mut(0..1) {
212
+ first.make_ascii_uppercase();
213
+ }
214
+ result.replace(acronym.as_str(), &capitalized)
215
+ });
216
+ expected == folded
217
+ }
218
+
50
219
  /// `filename_good?`: the leading dot and the last extension are dropped, the one `+` an Action Pack
51
220
  /// variant name carries becomes an underscore, and what is left has to match the pattern.
52
221
  fn filename_good(basename: &str, pattern: &Regex) -> bool {
@@ -58,41 +227,3 @@ fn filename_good(basename: &str, pattern: &Regex) -> bool {
58
227
  let stem = stem.replacen('+', "_", 1);
59
228
  pattern.is_match(&stem)
60
229
  }
61
-
62
- /// `allowed_camel_case_file?`: a gemspec may use dashes to spell a namespace, and a file that only
63
- /// reaches the run through an `Include` pattern written with capitals -- `Rakefile`, `Gemfile` --
64
- /// is named after that pattern rather than after Ruby's conventions.
65
- fn allowed_camel_case_file(context: &RuleContext<'_>, path: &Path) -> bool {
66
- if path
67
- .extension()
68
- .is_some_and(|extension| extension == "gemspec")
69
- {
70
- return true;
71
- }
72
- let Some(includes) = context.setting_of::<Vec<String>>("AllCops", "Include") else {
73
- return false;
74
- };
75
- let candidates: Vec<String> = [
76
- path.to_str().map(str::to_owned),
77
- path.file_name()
78
- .and_then(|name| name.to_str())
79
- .map(str::to_owned),
80
- ]
81
- .into_iter()
82
- .flatten()
83
- .collect();
84
- includes
85
- .iter()
86
- .filter(|pattern| {
87
- pattern
88
- .chars()
89
- .any(|character| character.is_ascii_uppercase())
90
- })
91
- .filter_map(|pattern| Glob::new(pattern).ok())
92
- .any(|glob| {
93
- let matcher = glob.compile_matcher();
94
- candidates
95
- .iter()
96
- .any(|candidate| matcher.is_match(candidate))
97
- })
98
- }
@@ -24,6 +24,8 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
24
24
  .setting("EnforcedStyle")
25
25
  .unwrap_or_else(|| "snake_case".to_owned()),
26
26
  forbidden: context.setting("ForbiddenIdentifiers").unwrap_or_default(),
27
+ // `forbidden_name?` is `forbidden_identifier? || forbidden_pattern?`.
28
+ forbidden_patterns: super::support::forbidden_patterns(context),
27
29
  reported: HashSet::new(),
28
30
  offenses,
29
31
  };
@@ -40,6 +42,7 @@ struct Check<'a, 'tree> {
40
42
  context: &'a RuleContext<'tree>,
41
43
  style: String,
42
44
  forbidden: Vec<String>,
45
+ forbidden_patterns: Vec<&'static regex::Regex>,
43
46
  /// `Base#add_offense` drops a second offense at a range it already reported, and
44
47
  /// `attr_accessor :aB, :cD` reports both names over the same range.
45
48
  reported: HashSet<Range<usize>>,
@@ -171,7 +174,7 @@ impl Check<'_, '_> {
171
174
  }
172
175
 
173
176
  fn class_emitter_method(&self, node: Node<'_>, name: &str) -> bool {
174
- super::support::class_emitter_method(node, name, self.context.source)
177
+ super::support::class_emitter_method(node, name, self.context)
175
178
  }
176
179
 
177
180
  /// Whether the call's receiver is the bare `Struct`/`Data` constant. `(const {nil? cbase} …)`
@@ -228,6 +231,10 @@ impl Check<'_, '_> {
228
231
 
229
232
  fn is_forbidden(&self, name: &str) -> bool {
230
233
  self.forbidden.iter().any(|forbidden| forbidden == name)
234
+ || self
235
+ .forbidden_patterns
236
+ .iter()
237
+ .any(|pattern| pattern.is_match(name))
231
238
  }
232
239
 
233
240
  fn style_offense(&mut self, range: Range<usize>) {