sonicop 26.8.102 → 26.8.103

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 (475) hide show
  1. checksums.yaml +4 -4
  2. data/CONFORMANCE.md +90 -30
  3. data/Cargo.lock +1 -1
  4. data/Cargo.toml +8 -1
  5. data/README.ja.md +40 -22
  6. data/README.md +25 -24
  7. data/lib/sonicop/version.rb +1 -1
  8. data/src/cli.rs +70 -2
  9. data/src/config/mod.rs +16 -1
  10. data/src/config/paths.rs +19 -0
  11. data/src/diagnostic.rs +54 -5
  12. data/src/directives.rs +924 -37
  13. data/src/display_width.rs +58 -0
  14. data/src/engine.rs +1185 -77
  15. data/src/formatter.rs +1661 -106
  16. data/src/lib.rs +3 -0
  17. data/src/magic_comment.rs +94 -21
  18. data/src/nul_bytes.rs +103 -0
  19. data/src/profile.rs +129 -0
  20. data/src/rules/bundler/duplicated_gem.rs +106 -0
  21. data/src/rules/bundler/duplicated_group.rs +105 -0
  22. data/src/rules/bundler/gem_filename.rs +64 -0
  23. data/src/rules/bundler/insecure_protocol_source.rs +46 -0
  24. data/src/rules/bundler/mod.rs +13 -0
  25. data/src/rules/bundler/ordered_gems.rs +30 -0
  26. data/src/rules/bundler/support.rs +32 -0
  27. data/src/rules/gemspec/duplicated_assignment.rs +182 -0
  28. data/src/rules/gemspec/mod.rs +12 -0
  29. data/src/rules/gemspec/ordered_dependencies.rs +63 -0
  30. data/src/rules/gemspec/required_ruby_version.rs +176 -0
  31. data/src/rules/gemspec/ruby_version_globals_usage.rs +53 -0
  32. data/src/rules/gemspec/support.rs +105 -0
  33. data/src/rules/layout/access_modifier_indentation.rs +101 -0
  34. data/src/rules/layout/alignment.rs +409 -0
  35. data/src/rules/layout/argument_alignment.rs +160 -0
  36. data/src/rules/layout/array_alignment.rs +165 -0
  37. data/src/rules/layout/assignment_indentation.rs +76 -0
  38. data/src/rules/layout/begin_end_alignment.rs +34 -0
  39. data/src/rules/layout/block_alignment.rs +496 -0
  40. data/src/rules/layout/block_end_newline.rs +161 -0
  41. data/src/rules/layout/case_indentation.rs +107 -0
  42. data/src/rules/layout/closing_heredoc_indentation.rs +185 -0
  43. data/src/rules/layout/closing_parenthesis_indentation.rs +211 -0
  44. data/src/rules/layout/comment_indentation.rs +193 -0
  45. data/src/rules/layout/condition_position.rs +78 -0
  46. data/src/rules/layout/def_end_alignment.rs +92 -0
  47. data/src/rules/layout/dot_position.rs +144 -0
  48. data/src/rules/layout/else_alignment.rs +248 -0
  49. data/src/rules/layout/empty_comment.rs +92 -0
  50. data/src/rules/layout/empty_line_after_guard_clause.rs +381 -0
  51. data/src/rules/layout/empty_line_between_defs.rs +254 -0
  52. data/src/rules/layout/empty_lines.rs +85 -0
  53. data/src/rules/layout/empty_lines_around_access_modifier.rs +362 -0
  54. data/src/rules/layout/empty_lines_around_arguments.rs +183 -0
  55. data/src/rules/layout/empty_lines_around_attribute_accessor.rs +178 -0
  56. data/src/rules/layout/empty_lines_around_begin_body.rs +19 -0
  57. data/src/rules/layout/empty_lines_around_block_body.rs +39 -0
  58. data/src/rules/layout/empty_lines_around_body.rs +372 -0
  59. data/src/rules/layout/empty_lines_around_class_body.rs +30 -0
  60. data/src/rules/layout/empty_lines_around_exception_handling_keywords.rs +130 -0
  61. data/src/rules/layout/empty_lines_around_method_body.rs +80 -0
  62. data/src/rules/layout/empty_lines_around_module_body.rs +21 -0
  63. data/src/rules/layout/end_alignment.rs +258 -0
  64. data/src/rules/layout/extra_spacing.rs +297 -0
  65. data/src/rules/layout/first_argument_indentation.rs +364 -0
  66. data/src/rules/layout/first_array_element_indentation.rs +189 -0
  67. data/src/rules/layout/first_hash_element_indentation.rs +229 -0
  68. data/src/rules/layout/first_parameter_indentation.rs +78 -0
  69. data/src/rules/layout/hash_alignment.rs +593 -0
  70. data/src/rules/layout/heredoc_indentation.rs +238 -0
  71. data/src/rules/layout/indentation_consistency.rs +122 -0
  72. data/src/rules/layout/indentation_style.rs +96 -0
  73. data/src/rules/layout/indentation_width.rs +663 -0
  74. data/src/rules/layout/initial_indentation.rs +62 -0
  75. data/src/rules/layout/leading_comment_space.rs +127 -0
  76. data/src/rules/layout/leading_empty_lines.rs +35 -0
  77. data/src/rules/layout/line_length.rs +69 -57
  78. data/src/rules/layout/mod.rs +80 -0
  79. data/src/rules/layout/multiline_array_brace_layout.rs +34 -0
  80. data/src/rules/layout/multiline_block_layout.rs +178 -0
  81. data/src/rules/layout/multiline_brace.rs +365 -0
  82. data/src/rules/layout/multiline_expression.rs +1053 -0
  83. data/src/rules/layout/multiline_hash_brace_layout.rs +43 -0
  84. data/src/rules/layout/multiline_method_call_brace_layout.rs +48 -0
  85. data/src/rules/layout/multiline_method_call_indentation.rs +812 -0
  86. data/src/rules/layout/multiline_method_definition_brace_layout.rs +36 -0
  87. data/src/rules/layout/multiline_operation_indentation.rs +144 -0
  88. data/src/rules/layout/parameter_alignment.rs +50 -0
  89. data/src/rules/layout/rescue_ensure_alignment.rs +232 -0
  90. data/src/rules/layout/space_after_colon.rs +68 -0
  91. data/src/rules/layout/space_after_comma.rs +2 -1
  92. data/src/rules/layout/space_after_method_name.rs +30 -0
  93. data/src/rules/layout/space_after_not.rs +34 -0
  94. data/src/rules/layout/space_after_semicolon.rs +53 -0
  95. data/src/rules/layout/space_around_block_parameters.rs +193 -0
  96. data/src/rules/layout/space_around_equals_in_parameter_default.rs +97 -0
  97. data/src/rules/layout/space_around_keyword.rs +330 -0
  98. data/src/rules/layout/space_around_method_call_operator.rs +102 -0
  99. data/src/rules/layout/space_around_operators.rs +30 -369
  100. data/src/rules/layout/space_before_block_braces.rs +86 -0
  101. data/src/rules/layout/space_before_comma.rs +54 -0
  102. data/src/rules/layout/space_before_comment.rs +34 -0
  103. data/src/rules/layout/space_before_first_arg.rs +105 -0
  104. data/src/rules/layout/space_before_semicolon.rs +67 -0
  105. data/src/rules/layout/space_in_lambda_literal.rs +61 -0
  106. data/src/rules/layout/space_inside_array_literal_brackets.rs +430 -0
  107. data/src/rules/layout/space_inside_array_percent_literal.rs +76 -0
  108. data/src/rules/layout/space_inside_block_braces.rs +382 -0
  109. data/src/rules/layout/space_inside_hash_literal_braces.rs +282 -0
  110. data/src/rules/layout/space_inside_percent_literal_delimiters.rs +100 -0
  111. data/src/rules/layout/space_inside_range_literal.rs +157 -0
  112. data/src/rules/layout/space_inside_reference_brackets.rs +205 -0
  113. data/src/rules/layout/space_inside_string_interpolation.rs +117 -0
  114. data/src/rules/layout/support.rs +856 -1
  115. data/src/rules/layout/tokens.rs +358 -0
  116. data/src/rules/layout/trailing_whitespace.rs +2 -1
  117. data/src/rules/lint/access_modifier.rs +262 -0
  118. data/src/rules/lint/ambiguity.rs +117 -0
  119. data/src/rules/lint/ambiguous_block_association.rs +285 -0
  120. data/src/rules/lint/ambiguous_operator.rs +39 -0
  121. data/src/rules/lint/ambiguous_regexp_literal.rs +18 -0
  122. data/src/rules/lint/assignment_in_condition.rs +234 -0
  123. data/src/rules/lint/big_decimal_new.rs +57 -0
  124. data/src/rules/lint/binary_operator_with_identical_operands.rs +61 -0
  125. data/src/rules/lint/blocks.rs +114 -0
  126. data/src/rules/lint/boolean_symbol.rs +112 -0
  127. data/src/rules/lint/circular_argument_reference.rs +72 -0
  128. data/src/rules/lint/conditions.rs +87 -0
  129. data/src/rules/lint/constant_definition_in_block.rs +62 -0
  130. data/src/rules/lint/cop_directives.rs +168 -0
  131. data/src/rules/lint/debugger.rs +224 -0
  132. data/src/rules/lint/deprecated_class_methods.rs +155 -0
  133. data/src/rules/lint/deprecated_open_ssl_constant.rs +229 -0
  134. data/src/rules/lint/disjunctive_assignment_in_constructor.rs +65 -0
  135. data/src/rules/lint/duplicate_case_condition.rs +43 -0
  136. data/src/rules/lint/duplicate_elsif_condition.rs +33 -0
  137. data/src/rules/lint/duplicate_hash_key.rs +31 -0
  138. data/src/rules/lint/duplicate_methods.rs +69 -68
  139. data/src/rules/lint/duplicate_require.rs +162 -0
  140. data/src/rules/lint/duplicate_rescue_exception.rs +53 -0
  141. data/src/rules/lint/each_with_object_argument.rs +60 -0
  142. data/src/rules/lint/else_layout.rs +114 -0
  143. data/src/rules/lint/empty_conditional_body.rs +166 -0
  144. data/src/rules/lint/empty_ensure.rs +28 -0
  145. data/src/rules/lint/empty_expression.rs +16 -0
  146. data/src/rules/lint/empty_file.rs +22 -0
  147. data/src/rules/lint/empty_interpolation.rs +53 -0
  148. data/src/rules/lint/empty_when.rs +102 -0
  149. data/src/rules/lint/ensure_return.rs +69 -0
  150. data/src/rules/lint/erb_new_arguments.rs +157 -0
  151. data/src/rules/lint/exception_hierarchy.rs +451 -0
  152. data/src/rules/lint/flip_flop.rs +16 -0
  153. data/src/rules/lint/float_comparison.rs +250 -0
  154. data/src/rules/lint/float_out_of_range.rs +40 -0
  155. data/src/rules/lint/flow.rs +167 -0
  156. data/src/rules/lint/format_parameter_mismatch.rs +275 -0
  157. data/src/rules/lint/format_string.rs +328 -0
  158. data/src/rules/lint/hash_compare_by_identity.rs +76 -0
  159. data/src/rules/lint/identity_comparison.rs +83 -0
  160. data/src/rules/lint/implicit_string_concatenation.rs +126 -0
  161. data/src/rules/lint/ineffective_access_modifier.rs +137 -0
  162. data/src/rules/lint/inherit_exception.rs +111 -0
  163. data/src/rules/lint/interpolation_check.rs +132 -0
  164. data/src/rules/lint/literal_as_condition.rs +483 -0
  165. data/src/rules/lint/literal_in_interpolation.rs +485 -0
  166. data/src/rules/lint/literals.rs +252 -0
  167. data/src/rules/lint/locals.rs +65 -0
  168. data/src/rules/lint/loop.rs +76 -0
  169. data/src/rules/lint/missing_cop_enable_directive.rs +62 -0
  170. data/src/rules/lint/missing_super.rs +150 -0
  171. data/src/rules/lint/mixed_regexp_capture_types.rs +21 -0
  172. data/src/rules/lint/mod.rs +134 -2
  173. data/src/rules/lint/multiple_comparison.rs +59 -0
  174. data/src/rules/lint/nested_method_definition.rs +112 -0
  175. data/src/rules/lint/nested_percent_literal.rs +35 -0
  176. data/src/rules/lint/next_without_accumulator.rs +70 -0
  177. data/src/rules/lint/nil_methods.rs +93 -0
  178. data/src/rules/lint/node_equality.rs +265 -0
  179. data/src/rules/lint/non_deterministic_require_order.rs +232 -0
  180. data/src/rules/lint/non_local_exit_from_iterator.rs +95 -0
  181. data/src/rules/lint/ordered_magic_comments.rs +73 -0
  182. data/src/rules/lint/out_of_range_regexp_ref.rs +247 -0
  183. data/src/rules/lint/parameters.rs +68 -0
  184. data/src/rules/lint/parentheses_as_grouped_expression.rs +81 -0
  185. data/src/rules/lint/percent_literal.rs +34 -0
  186. data/src/rules/lint/percent_string_array.rs +73 -0
  187. data/src/rules/lint/percent_symbol_array.rs +52 -0
  188. data/src/rules/lint/raise_exception.rs +108 -0
  189. data/src/rules/lint/rand_one.rs +48 -0
  190. data/src/rules/lint/ranges.rs +29 -0
  191. data/src/rules/lint/redundant_cop_disable_directive.rs +472 -0
  192. data/src/rules/lint/redundant_cop_enable_directive.rs +238 -0
  193. data/src/rules/lint/redundant_require_statement.rs +91 -0
  194. data/src/rules/lint/redundant_safe_navigation.rs +227 -0
  195. data/src/rules/lint/redundant_splat_expansion.rs +266 -0
  196. data/src/rules/lint/redundant_string_coercion.rs +108 -0
  197. data/src/rules/lint/redundant_with_index.rs +94 -0
  198. data/src/rules/lint/redundant_with_object.rs +79 -0
  199. data/src/rules/lint/regexp.rs +130 -0
  200. data/src/rules/lint/regexp_as_condition.rs +45 -0
  201. data/src/rules/lint/require_parentheses.rs +69 -0
  202. data/src/rules/lint/rescue_clause.rs +76 -0
  203. data/src/rules/lint/rescue_exception.rs +24 -0
  204. data/src/rules/lint/rescue_type.rs +68 -0
  205. data/src/rules/lint/return_in_void_context.rs +80 -0
  206. data/src/rules/lint/safe_navigation_chain.rs +306 -0
  207. data/src/rules/lint/safe_navigation_consistency.rs +234 -0
  208. data/src/rules/lint/safe_navigation_with_empty.rs +61 -0
  209. data/src/rules/lint/script_permission.rs +42 -0
  210. data/src/rules/lint/self_assignment.rs +413 -0
  211. data/src/rules/lint/send_with_mixin_argument.rs +81 -0
  212. data/src/rules/lint/shadowed_argument.rs +212 -0
  213. data/src/rules/lint/shadowed_exception.rs +147 -0
  214. data/src/rules/lint/statements.rs +169 -0
  215. data/src/rules/lint/struct_new_override.rs +176 -0
  216. data/src/rules/lint/suppressed_exception.rs +51 -0
  217. data/src/rules/lint/syntax.rs +46 -43
  218. data/src/rules/lint/to_json.rs +38 -0
  219. data/src/rules/lint/top_level_return_with_argument.rs +36 -0
  220. data/src/rules/lint/trailing_comma_in_attribute_declaration.rs +48 -0
  221. data/src/rules/lint/underscore_prefixed_variable_name.rs +32 -0
  222. data/src/rules/lint/unified_integer.rs +51 -0
  223. data/src/rules/lint/unreachable_code.rs +63 -0
  224. data/src/rules/lint/unreachable_loop.rs +313 -0
  225. data/src/rules/lint/unused_block_argument.rs +4 -3
  226. data/src/rules/lint/unused_method_argument.rs +254 -0
  227. data/src/rules/lint/uri_escape_unescape.rs +54 -0
  228. data/src/rules/lint/uri_regexp.rs +61 -0
  229. data/src/rules/lint/useless_access_modifier.rs +299 -0
  230. data/src/rules/lint/useless_assignment.rs +38 -37
  231. data/src/rules/lint/useless_else_without_rescue.rs +39 -0
  232. data/src/rules/lint/useless_method_definition.rs +134 -0
  233. data/src/rules/lint/useless_setter_call.rs +235 -0
  234. data/src/rules/lint/useless_times.rs +288 -0
  235. data/src/rules/lint/variable_force.rs +184 -128
  236. data/src/rules/lint/void.rs +653 -0
  237. data/src/rules/metrics/abc_size.rs +277 -0
  238. data/src/rules/metrics/block_length.rs +8 -7
  239. data/src/rules/metrics/block_nesting.rs +146 -0
  240. data/src/rules/metrics/class_length.rs +9 -8
  241. data/src/rules/metrics/complexity.rs +1202 -0
  242. data/src/rules/metrics/cyclomatic_complexity.rs +71 -0
  243. data/src/rules/metrics/fragments.rs +198 -0
  244. data/src/rules/metrics/locals.rs +634 -0
  245. data/src/rules/metrics/method_length.rs +6 -5
  246. data/src/rules/metrics/mod.rs +10 -0
  247. data/src/rules/metrics/module_length.rs +9 -8
  248. data/src/rules/metrics/parameter_lists.rs +25 -24
  249. data/src/rules/metrics/perceived_complexity.rs +160 -0
  250. data/src/rules/metrics/support.rs +14 -13
  251. data/src/rules/migration/department_name.rs +196 -0
  252. data/src/rules/migration/mod.rs +6 -0
  253. data/src/rules/mod.rs +196 -9
  254. data/src/rules/naming/accessor_method_name.rs +41 -0
  255. data/src/rules/naming/ascii_identifiers.rs +5 -4
  256. data/src/rules/naming/binary_operator_parameter_name.rs +102 -0
  257. data/src/rules/naming/block_parameter_name.rs +17 -0
  258. data/src/rules/naming/class_and_module_camel_case.rs +32 -0
  259. data/src/rules/naming/constant_name.rs +40 -39
  260. data/src/rules/naming/file_name.rs +98 -0
  261. data/src/rules/naming/heredoc_delimiter_case.rs +49 -0
  262. data/src/rules/naming/heredoc_delimiter_naming.rs +37 -0
  263. data/src/rules/naming/memoized_instance_variable_name.rs +366 -0
  264. data/src/rules/naming/method_name.rs +19 -95
  265. data/src/rules/naming/method_parameter_name.rs +17 -0
  266. data/src/rules/naming/mod.rs +15 -1
  267. data/src/rules/naming/predicate_prefix.rs +129 -0
  268. data/src/rules/naming/rescued_exceptions_variable_name.rs +277 -0
  269. data/src/rules/naming/support.rs +474 -13
  270. data/src/rules/naming/uncommunicative.rs +93 -0
  271. data/src/rules/naming/variable_name.rs +4 -3
  272. data/src/rules/naming/variable_number.rs +212 -0
  273. data/src/rules/node_ext.rs +190 -0
  274. data/src/rules/ordered_gem.rs +165 -0
  275. data/src/rules/regex_cache.rs +59 -0
  276. data/src/rules/security/eval.rs +12 -11
  277. data/src/rules/security/json_load.rs +55 -0
  278. data/src/rules/security/marshal_load.rs +48 -0
  279. data/src/rules/security/mod.rs +4 -0
  280. data/src/rules/security/open.rs +138 -0
  281. data/src/rules/security/yaml_load.rs +42 -0
  282. data/src/rules/send_node.rs +253 -0
  283. data/src/rules/style/access_modifier_declarations.rs +459 -0
  284. data/src/rules/style/accessor_grouping.rs +351 -0
  285. data/src/rules/style/alias.rs +266 -0
  286. data/src/rules/style/and_or.rs +319 -0
  287. data/src/rules/style/array_join.rs +42 -0
  288. data/src/rules/style/attr.rs +98 -0
  289. data/src/rules/style/bare_percent_literals.rs +43 -0
  290. data/src/rules/style/begin_block.rs +13 -0
  291. data/src/rules/style/bisected_attr_accessor.rs +262 -0
  292. data/src/rules/style/block_args.rs +59 -0
  293. data/src/rules/style/block_comments.rs +85 -0
  294. data/src/rules/style/block_delimiters.rs +484 -0
  295. data/src/rules/style/case_equality.rs +167 -0
  296. data/src/rules/style/case_like_if.rs +328 -0
  297. data/src/rules/style/character_literal.rs +50 -0
  298. data/src/rules/style/class_and_module_children.rs +276 -0
  299. data/src/rules/style/class_check.rs +39 -0
  300. data/src/rules/style/class_equality_comparison.rs +218 -0
  301. data/src/rules/style/class_methods.rs +72 -0
  302. data/src/rules/style/class_vars.rs +61 -0
  303. data/src/rules/style/colon_method_call.rs +57 -0
  304. data/src/rules/style/colon_method_definition.rs +31 -0
  305. data/src/rules/style/combinable_loops.rs +215 -0
  306. data/src/rules/style/command_literal.rs +105 -0
  307. data/src/rules/style/comment_annotation.rs +138 -0
  308. data/src/rules/style/commented_keyword.rs +102 -0
  309. data/src/rules/style/comments.rs +225 -0
  310. data/src/rules/style/conditional.rs +138 -0
  311. data/src/rules/style/conditional_assignment.rs +599 -0
  312. data/src/rules/style/def_with_parentheses.rs +58 -0
  313. data/src/rules/style/dir.rs +63 -0
  314. data/src/rules/style/documentation.rs +343 -0
  315. data/src/rules/style/double_cop_disable_directive.rs +65 -0
  316. data/src/rules/style/double_negation.rs +260 -0
  317. data/src/rules/style/each_for_simple_loop.rs +110 -0
  318. data/src/rules/style/each_with_object.rs +242 -0
  319. data/src/rules/style/empty_block_parameter.rs +37 -0
  320. data/src/rules/style/empty_case_condition.rs +224 -0
  321. data/src/rules/style/empty_else.rs +129 -0
  322. data/src/rules/style/empty_lambda_parameter.rs +31 -0
  323. data/src/rules/style/empty_literal.rs +222 -0
  324. data/src/rules/style/empty_method.rs +104 -0
  325. data/src/rules/style/encoding.rs +52 -0
  326. data/src/rules/style/end_block.rs +22 -0
  327. data/src/rules/style/eval_with_location.rs +382 -0
  328. data/src/rules/style/even_odd.rs +110 -0
  329. data/src/rules/style/expand_path_arguments.rs +230 -0
  330. data/src/rules/style/explicit_block_argument.rs +215 -0
  331. data/src/rules/style/exponential_notation.rs +84 -0
  332. data/src/rules/style/float_division.rs +225 -0
  333. data/src/rules/style/for.rs +145 -0
  334. data/src/rules/style/format_sequences.rs +329 -0
  335. data/src/rules/style/format_string.rs +288 -0
  336. data/src/rules/style/format_string_token.rs +357 -0
  337. data/src/rules/style/frozen_string.rs +87 -0
  338. data/src/rules/style/frozen_string_literal_comment.rs +23 -13
  339. data/src/rules/style/global_std_stream.rs +91 -0
  340. data/src/rules/style/global_vars.rs +34 -0
  341. data/src/rules/style/guard_clause.rs +622 -0
  342. data/src/rules/style/hash_as_last_array_item.rs +188 -0
  343. data/src/rules/style/hash_each_methods.rs +403 -0
  344. data/src/rules/style/hash_like_case.rs +164 -0
  345. data/src/rules/style/hash_syntax.rs +8 -7
  346. data/src/rules/style/hash_transform.rs +472 -0
  347. data/src/rules/style/hash_transform_keys.rs +6 -0
  348. data/src/rules/style/hash_transform_values.rs +6 -0
  349. data/src/rules/style/identical_conditional_branches.rs +368 -0
  350. data/src/rules/style/if_inside_else.rs +264 -0
  351. data/src/rules/style/if_unless_modifier.rs +844 -0
  352. data/src/rules/style/if_unless_modifier_of_if_unless.rs +63 -0
  353. data/src/rules/style/if_with_semicolon.rs +327 -0
  354. data/src/rules/style/infinite_loop.rs +199 -0
  355. data/src/rules/style/inverse_methods.rs +390 -0
  356. data/src/rules/style/keyword_parameters_order.rs +159 -0
  357. data/src/rules/style/lambda.rs +352 -0
  358. data/src/rules/style/lambda_call.rs +80 -0
  359. data/src/rules/style/line_end_concatenation.rs +124 -0
  360. data/src/rules/style/line_length_help.rs +262 -0
  361. data/src/rules/style/literal.rs +452 -0
  362. data/src/rules/style/method_call_without_args_parentheses.rs +154 -0
  363. data/src/rules/style/method_def_parentheses.rs +120 -0
  364. data/src/rules/style/min_max.rs +67 -0
  365. data/src/rules/style/missing_respond_to_missing.rs +69 -0
  366. data/src/rules/style/mixin_grouping.rs +163 -0
  367. data/src/rules/style/mixin_usage.rs +66 -0
  368. data/src/rules/style/mod.rs +185 -0
  369. data/src/rules/style/module_function.rs +76 -0
  370. data/src/rules/style/multiline_block_chain.rs +64 -0
  371. data/src/rules/style/multiline_if_modifier.rs +107 -0
  372. data/src/rules/style/multiline_if_then.rs +42 -0
  373. data/src/rules/style/multiline_memoization.rs +107 -0
  374. data/src/rules/style/multiline_ternary_operator.rs +157 -0
  375. data/src/rules/style/multiline_when_then.rs +42 -0
  376. data/src/rules/style/multiple_comparison.rs +231 -0
  377. data/src/rules/style/mutable_constant.rs +509 -0
  378. data/src/rules/style/negated_if.rs +95 -0
  379. data/src/rules/style/negated_unless.rs +54 -0
  380. data/src/rules/style/negated_while.rs +77 -0
  381. data/src/rules/style/nested_modifier.rs +161 -0
  382. data/src/rules/style/nested_parenthesized_calls.rs +133 -0
  383. data/src/rules/style/nested_ternary_operator.rs +112 -0
  384. data/src/rules/style/next.rs +380 -0
  385. data/src/rules/style/nil_comparison.rs +117 -0
  386. data/src/rules/style/nodes.rs +110 -0
  387. data/src/rules/style/non_nil_check.rs +109 -0
  388. data/src/rules/style/not.rs +109 -0
  389. data/src/rules/style/numeric_literal_prefix.rs +136 -0
  390. data/src/rules/style/numeric_literals.rs +7 -6
  391. data/src/rules/style/numeric_predicate.rs +260 -0
  392. data/src/rules/style/one_line_conditional.rs +245 -0
  393. data/src/rules/style/optional_arguments.rs +42 -0
  394. data/src/rules/style/optional_boolean_parameter.rs +43 -0
  395. data/src/rules/style/or_assignment.rs +136 -0
  396. data/src/rules/style/parallel_assignment.rs +375 -0
  397. data/src/rules/style/parameters.rs +97 -0
  398. data/src/rules/style/parens.rs +123 -0
  399. data/src/rules/style/parentheses_around_condition.rs +210 -0
  400. data/src/rules/style/percent.rs +132 -0
  401. data/src/rules/style/percent_array.rs +326 -0
  402. data/src/rules/style/percent_literal_delimiters.rs +99 -0
  403. data/src/rules/style/percent_q_literals.rs +80 -0
  404. data/src/rules/style/perl_backrefs.rs +70 -0
  405. data/src/rules/style/preferred_hash_methods.rs +43 -0
  406. data/src/rules/style/proc.rs +41 -0
  407. data/src/rules/style/raise_args.rs +183 -0
  408. data/src/rules/style/random_with_offset.rs +169 -0
  409. data/src/rules/style/ranges.rs +32 -0
  410. data/src/rules/style/redundant_assignment.rs +165 -0
  411. data/src/rules/style/redundant_begin.rs +463 -0
  412. data/src/rules/style/redundant_capital_w.rs +47 -0
  413. data/src/rules/style/redundant_condition.rs +651 -0
  414. data/src/rules/style/redundant_conditional.rs +97 -0
  415. data/src/rules/style/redundant_exception.rs +133 -0
  416. data/src/rules/style/redundant_fetch_block.rs +145 -0
  417. data/src/rules/style/redundant_file_extension_in_require.rs +60 -0
  418. data/src/rules/style/redundant_freeze.rs +210 -0
  419. data/src/rules/style/redundant_interpolation.rs +171 -0
  420. data/src/rules/style/redundant_parentheses.rs +1104 -0
  421. data/src/rules/style/redundant_percent_q.rs +118 -0
  422. data/src/rules/style/redundant_regexp_character_class.rs +376 -0
  423. data/src/rules/style/redundant_regexp_escape.rs +195 -0
  424. data/src/rules/style/redundant_return.rs +25 -24
  425. data/src/rules/style/redundant_self.rs +523 -0
  426. data/src/rules/style/redundant_self_assignment.rs +137 -0
  427. data/src/rules/style/redundant_sort.rs +172 -0
  428. data/src/rules/style/redundant_sort_by.rs +70 -0
  429. data/src/rules/style/regexp_literal.rs +224 -0
  430. data/src/rules/style/rescue_modifier.rs +109 -0
  431. data/src/rules/style/rescue_standard_error.rs +66 -0
  432. data/src/rules/style/safe_navigation.rs +1007 -0
  433. data/src/rules/style/sample.rs +245 -0
  434. data/src/rules/style/self_assignment.rs +108 -0
  435. data/src/rules/style/semicolon.rs +4 -3
  436. data/src/rules/style/signal_exception.rs +108 -0
  437. data/src/rules/style/single_argument_dig.rs +105 -0
  438. data/src/rules/style/single_line_methods.rs +367 -0
  439. data/src/rules/style/slicing_with_range.rs +263 -0
  440. data/src/rules/style/sole_nested_conditional.rs +503 -0
  441. data/src/rules/style/special_global_vars.rs +329 -0
  442. data/src/rules/style/stabby_lambda_parentheses.rs +71 -0
  443. data/src/rules/style/stderr_puts.rs +46 -0
  444. data/src/rules/style/string_concatenation.rs +503 -0
  445. data/src/rules/style/string_literals.rs +14 -13
  446. data/src/rules/style/string_literals_in_interpolation.rs +45 -0
  447. data/src/rules/style/strip.rs +53 -0
  448. data/src/rules/style/struct_inheritance.rs +178 -0
  449. data/src/rules/style/symbol_array.rs +170 -0
  450. data/src/rules/style/symbol_literal.rs +43 -0
  451. data/src/rules/style/symbol_proc.rs +356 -0
  452. data/src/rules/style/ternary_parentheses.rs +288 -0
  453. data/src/rules/style/trailing_body.rs +103 -0
  454. data/src/rules/style/trailing_body_on_class.rs +10 -0
  455. data/src/rules/style/trailing_body_on_method_definition.rs +20 -0
  456. data/src/rules/style/trailing_body_on_module.rs +10 -0
  457. data/src/rules/style/trailing_comma.rs +119 -0
  458. data/src/rules/style/trailing_comma_in_arguments.rs +72 -0
  459. data/src/rules/style/trailing_comma_in_array_literal.rs +24 -0
  460. data/src/rules/style/trailing_comma_in_hash_literal.rs +25 -0
  461. data/src/rules/style/trailing_method_end_statement.rs +38 -0
  462. data/src/rules/style/trailing_underscore_variable.rs +151 -0
  463. data/src/rules/style/trivial_accessors.rs +339 -0
  464. data/src/rules/style/unless_else.rs +72 -0
  465. data/src/rules/style/unpack_first.rs +91 -0
  466. data/src/rules/style/variable_interpolation.rs +45 -0
  467. data/src/rules/style/when_then.rs +46 -0
  468. data/src/rules/style/while_until_do.rs +39 -0
  469. data/src/rules/style/while_until_modifier.rs +288 -0
  470. data/src/rules/style/word_array.rs +188 -0
  471. data/src/rules/style/yoda_condition.rs +154 -0
  472. data/src/rules/style/zero_length_predicate.rs +185 -0
  473. data/src/rules/support.rs +364 -1
  474. data/src/source.rs +17 -0
  475. metadata +422 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea2dfe101378c37802dfe7e5cf3a4e3c3595942c75c91c9f25ade094ab11e632
4
- data.tar.gz: 01e5d99dcd5989fee69f66403712918e792d0c268ea3b82096ec260dcfd3f176
3
+ metadata.gz: 11d1ded7e100a86c067bfd508cf4b7b3b402e2c964fb5bcbabb4f95e800551dc
4
+ data.tar.gz: 2e06b2ad86065842b5fda6dd9b41b798175a87b70633a205fd73fddaa611e1e2
5
5
  SHA512:
6
- metadata.gz: 726107889fe04bf60cf2f056c8ca7d34c5c825650426120281e9afa88d2c819d38d7342d99cdc45dd0b81a942011e5d130f6745fd7ee6cc6c5249663526d159d
7
- data.tar.gz: 51b76434f05a2b35cecc62eda0e3636592f7058ff123ab936872f577082502301a5c609828fc5d0659abd94f3d67291aa3dbc71ddd6ffc0be2fc344a13e0bf51
6
+ metadata.gz: e52e6bd4a26c68bd2ed6bda10bfba3772e6760c1573d81c6212391cd0bf1acdccece6b102eed1d9bbf9c2e652556772f12fa626628bb6266d9eee7d94826c314
7
+ data.tar.gz: 240b544b1e90d355b92025d226d4cdd4efd358e81e9bc26449d5e9f641505230c510b4389487efc936d8e6fcb8fcf281c7b18180ade2a6c0b66fe18c980912df
data/CONFORMANCE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # RuboCop conformance
2
2
 
3
- Snapshot date: 2026-08-12
3
+ Snapshot date: 2026-08-14
4
4
  Reference: RuboCop 1.89.0 on Ruby 4.0.6
5
5
  Configuration: RuboCop 1.89.0 built-in defaults (`--force-default-config`) on both sides
6
6
 
@@ -9,32 +9,38 @@ Re-fetch it with `scripts/sync_default_yml.sh <rubocop-version>`.
9
9
 
10
10
  ## What is measured
11
11
 
12
- Five Ruby projects, 18,244 target files between them, are linted by both tools and compared offense
12
+ Five Ruby projects, 18,246 target files between them, are linted by both tools and compared offense
13
13
  by offense. An offense is keyed by cop name, path, line and column; at each shared key the last
14
14
  line, last column, length, message, severity and correctability are compared as well.
15
15
 
16
16
  | Corpus | Commit | Target files | Reference offenses |
17
17
  |---|---|---:|---:|
18
- | rubocop/rubocop | `e82df38` | 1,765 | 4,142 |
19
- | rails/rails | `729d2e9` | 3,550 | 117,541 |
20
- | ruby/ruby | `52975b7` | 7,465 | 603,331 |
21
- | Homebrew/brew | `5d49126` | 2,175 | 38,742 |
22
- | mastodon/mastodon | `e5db3aa` | 3,289 | 7,610 |
18
+ | rubocop/rubocop | `f009b33` | 1,765 | 5,766 |
19
+ | rails/rails | `ce95deb` | 3,550 | 167,535 |
20
+ | ruby/ruby | `a9c656e` | 7,465 | 756,427 |
21
+ | Homebrew/brew | `6cf9b12` | 2,176 | 49,733 |
22
+ | mastodon/mastodon | `3f5c32f` | 3,290 | 15,286 |
23
+
24
+ ruby/ruby's reference had to be assembled from chunked runs, and 15 files under `test/ruby/` are
25
+ excluded from it: RuboCop finds offenses in them whose text is not valid UTF-8, and its JSON
26
+ formatter raises `source sequence is illegal/malformed utf-8` rather than emitting a document. The
27
+ comparison for that corpus therefore covers 7,450 of its 7,465 files.
23
28
 
24
29
  The **target file lists match exactly** on all five — not just the counts but the paths. That is
25
30
  worth stating separately because file discovery is where a port silently diverges first: RuboCop
26
31
  never reads `.gitignore`, applies a shebang test only to extensionless files, descends through
27
32
  directory symlinks, and treats a hidden path by its *first* component rather than by any dot in it.
28
33
 
29
- Only the cops Sonicop implements are compared; the reference process is restricted to them with
30
- `--only`. RuboCop's extension plugins are not installed, so this measures the range that can be
31
- checked without them.
34
+ Sonicop implements **394 of RuboCop's 609 cops**: every cop RuboCop enables by default, matched name
35
+ for name, with nothing extra on either side. The other 215 are the ones RuboCop ships switched off —
36
+ 159 `Enabled: pending` and 56 `Enabled: false` — and are not implemented yet.
32
37
 
33
- ```bash
34
- cops=$(sonicop --show-cops --force-default-config \
35
- | grep -B3 'Implemented: true' | grep '^[A-Z].*:$' | tr -d ':' | paste -sd, -)
38
+ Because the two enabled sets are identical, neither side is restricted with `--only`; the plain run
39
+ is already like for like. RuboCop's extension plugins are not installed, so this measures the range
40
+ that can be checked without them.
36
41
 
37
- rubocop --force-default-config --cache false --only "$cops" -f json
42
+ ```bash
43
+ rubocop --force-default-config --cache false -f json
38
44
  sonicop --force-default-config --format json
39
45
  ```
40
46
 
@@ -45,19 +51,46 @@ sonicop --force-default-config --format json
45
51
  | rubocop/rubocop | 0 | 0 | none |
46
52
  | rails/rails | 0 | 0 | none |
47
53
  | mastodon/mastodon | 0 | 0 | none |
48
- | Homebrew/brew | 253 | 996 | none |
49
- | ruby/ruby | 89 | 346 | 1 |
54
+ | Homebrew/brew | 254 | 999 | none |
55
+ | ruby/ruby | 156 | 616 | 5 |
50
56
 
51
- Homebrew's remaining difference is entirely `Lint/Syntax`; every other cop matches exactly. Most of
52
- ruby/ruby's is too — 117 of the 346 missing and 70 of the 89 excess are `Lint/Syntax` itself. The
53
- rest follows from it: a file the two disagree about is inspected by one tool and skipped by the
54
- other, so every offense in it lands on one side of the ledger. Both are explained under
57
+ Three of the five agree offense for offense RuboCop's own tree, Rails and Mastodon — with no
58
+ excess, no shortfall and no metadata differences anywhere.
59
+
60
+ Homebrew's remaining difference is entirely `Lint/Syntax`; every other cop matches exactly. Much of
61
+ ruby/ruby's is too, with 116 of the 616 missing and 44 of the 156 excess being `Lint/Syntax` itself,
62
+ and the rest follows from it: a file the two disagree about is inspected by one tool and skipped by
63
+ the other, so every offense in it lands on one side of the ledger. That is why the residue there is
64
+ spread thinly across the Layout department instead of sitting in one cop. Both are explained under
55
65
  *Known divergences*.
56
66
 
57
- Autocorrect is compared the same way, byte for byte over the whole tree: run `-a` (and separately
58
- `-A`) with both tools from a clean checkout and diff the results. On all four corpora measured this way —
59
- rubocop/rubocop, rails/rails, Homebrew/brew and mastodon/mastodon the corrected trees are
60
- **identical**.
67
+ ruby/ruby also carries five differences at positions both tools reported: two `correctable` flags and
68
+ one `last_column`/`length` pair, all on indentation cops inside files the two parse differently, plus
69
+ one `Lint/Syntax` message naming a different token (`tLCURLY` where RuboCop says `tLAMBEG`).
70
+
71
+ Autocorrect is compared the same way, byte for byte over the whole tree: run `-A` with both tools
72
+ from a clean checkout and diff the results.
73
+
74
+ | Corpus | Corrected tree |
75
+ |---|---|
76
+ | rubocop/rubocop | **identical** |
77
+ | mastodon/mastodon | **identical** |
78
+ | Homebrew/brew | 2 files differ |
79
+ | rails/rails | 25 files differ |
80
+ | ruby/ruby | not measurable |
81
+
82
+ RuboCop's own tree and Mastodon are the hard line: a change that breaks byte equality on either is a
83
+ regression to be fixed, not a new known divergence to be recorded.
84
+
85
+ The rails/rails residue is one shape, and Homebrew contributes one more of it: the body of a
86
+ `begin`/`rescue`/`end` ends up indented two columns off. It appears only in a full `-A` run — the
87
+ first-pass detection matches exactly, and reducing the case to a single file reproduces nothing —
88
+ because what differs is which correction pass a nested indentation fix lands in. Homebrew's other
89
+ file pairs a `disable`/`enable` around `Lint/EmptyBlock`, a cop RuboCop ships as pending; the
90
+ upstream run leaves both comments alone and Sonicop removes them.
91
+
92
+ ruby/ruby cannot be measured this way at all: RuboCop's own run does not finish on it (see *Reading a
93
+ measurement*), so there is no complete reference tree to diff against.
61
94
 
62
95
  Run the comparison on a copy of the corpus. Autocorrect rewrites the tree in place, so a run that
63
96
  shares a checkout with anything else — another comparison, a lint measurement — has both tools
@@ -78,6 +111,15 @@ every offense Sonicop found in them counts as "excess". Check `summary.inspected
78
111
  against `summary.target_file_count` on every run. To get a complete reference for that corpus, split
79
112
  the file list into chunks and re-run each chunk past the file that killed it.
80
113
 
114
+ **And sometimes it emits nothing at all.** Fifteen files under `test/ruby/` hold offenses whose text
115
+ is not valid UTF-8, and `JSONFormatter#finished` raises `source sequence is illegal/malformed utf-8`
116
+ while serializing — before a single byte reaches stdout. The failure therefore looks like an empty
117
+ file rather than a truncated one, and a chunked reference run has to tell it apart from the case
118
+ above: retrying one file at a time is right when a file killed the parser, and useless when the run
119
+ never had a chance to start. Two other stdout notices break JSON the same way — `--parallel` being
120
+ ignored under `--cache false`, and the plugin suggestions RuboCop prints after a run — so a reference
121
+ reader should skip to the first `{` rather than trust byte zero.
122
+
81
123
  ## Known divergences
82
124
 
83
125
  `tests/conformance/known_divergences.yml` is the machine-checked record: a divergence that is listed
@@ -88,19 +130,31 @@ but no longer reproduces fails the test, and so does one that appears without be
88
130
  RuboCop parses with `parser`, an LALR parser that recovers from an error and keeps going, emitting
89
131
  further diagnostics from the recovered state. Sonicop parses with tree-sitter, which does not model
90
132
  that recovery, so the follow-on diagnostics cannot be reproduced. On Homebrew this accounts for the
91
- 996 missing `Lint/Syntax` offenses: `class definition in method body`, `dynamic constant assignment`,
133
+ 999 missing `Lint/Syntax` offenses: `class definition in method body`, `dynamic constant assignment`,
92
134
  `cannot assign to a keyword`, and repeated `unexpected token` inside one multi-line hash.
93
135
 
94
- The first diagnostic in each file does match, which is what decides whether the file is inspected at
95
- all: RuboCop runs no cop other than `Lint/Syntax` on a file that does not parse, and Sonicop does the
96
- same.
136
+ What the divergence does not change is **which** files are held to be unparseable, and that is what
137
+ decides whether a file is inspected at all: RuboCop runs no cop other than `Lint/Syntax` on a file
138
+ that does not parse, and Sonicop does the same. On Homebrew both tools flag the same 568 files —
139
+ neither has one the other lacks. This is why every cop other than `Lint/Syntax` matches exactly there
140
+ despite the 1,253 offenses of difference.
141
+
142
+ Within those 568 files the agreement is partial, as recovery cannot be reproduced: 498 report the
143
+ same first diagnostic (position and message), and 223 report an identical list end to end. The 70
144
+ files whose first diagnostic differs follow one shape — an endless method definition (`def to_s =
145
+ to_str`, valid from Ruby 3.0, rejected by the default `TargetRubyVersion: 2.7`) leaves `parser`'s
146
+ method context open, so the enclosing `class` emits `class definition in method body` when it is
147
+ finally reduced. The diagnostic is reported at the `class` keyword, far above the line that actually
148
+ failed, which puts it first in source order.
97
149
 
98
150
  ### Grammar gaps
99
151
 
100
152
  Where tree-sitter rejects code that Ruby accepts, Sonicop reports a syntax error and — following the
101
153
  rule above — reports nothing else for that file. One such gap costs every offense in the file at
102
154
  once, which makes them worth hunting: fixing eight lexer rules in the grammar fork took ruby/ruby
103
- from 24 affected files to 5, and the offenses Sonicop was missing there from 2,457 to 346.
155
+ from 24 affected files to 5, and cut the offenses Sonicop was missing there by more than sevenfold.
156
+ What is left moves with how many cops are implemented — one unparseable file costs every offense
157
+ every cop would have reported in it — so the shortfall grows as coverage does.
104
158
 
105
159
  What remains are constructs whose ambiguity Ruby resolves with information a grammar does not have.
106
160
  `$a?0:1` is the clearest: whether `?0` is a character literal or the start of a ternary depends on
@@ -117,7 +171,7 @@ cp932 and you get mojibake, not the source you had.
117
171
  when the correction holds a character that encoding cannot represent, leaving the file untouched
118
172
  rather than substituting something else. Drop-in compatibility reaches a long way, but not as far as
119
173
  reproducing data loss on purpose. This is the only place Sonicop knowingly differs, and it only
120
- shows up on files that declare a non-UTF-8 encoding — 8 of the 18,244 files measured here.
174
+ shows up on files that declare a non-UTF-8 encoding — 8 of the 18,246 files measured here.
121
175
 
122
176
  A source declaring `ASCII-8BIT` or `binary` needs no such treatment: Ruby measures it one byte at a
123
177
  time, so Sonicop reads it that way too and the bytes go back out unchanged.
@@ -129,6 +183,12 @@ current list of what these corpora never exercise; the main ones are non-default
129
183
  values, extension plugins, and Windows line endings. Cops that never fire contribute nothing to a
130
184
  match count, and their silence is indistinguishable from agreement.
131
185
 
186
+ The 215 cops RuboCop ships switched off are a limit of a different kind. Sonicop recognizes them —
187
+ they appear in `--show-cops` and a configuration naming them still loads — but it does not implement
188
+ them, so enabling one through `.rubocop.yml` or `--only` checks nothing at all. Sonicop prints a
189
+ warning to stderr in that case rather than letting an unchecked file look clean, but a project whose
190
+ configuration leans on those cops is outside what this document measures.
191
+
132
192
  Closing that gap properly means porting RuboCop's own spec suite, which exercises each cop against
133
193
  inputs written to break it. Until that lands, this document records what was measured.
134
194
 
data/Cargo.lock CHANGED
@@ -742,7 +742,7 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
742
742
 
743
743
  [[package]]
744
744
  name = "sonicop"
745
- version = "26.8.102"
745
+ version = "26.8.103"
746
746
  dependencies = [
747
747
  "anyhow",
748
748
  "assert_cmd",
data/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sonicop"
3
- version = "26.8.102"
3
+ version = "26.8.103"
4
4
  edition = "2024"
5
5
  rust-version = "1.85"
6
6
  description = "A fast, native RuboCop-compatible Ruby linter and formatter"
@@ -38,3 +38,10 @@ assert_cmd = "2.2.2"
38
38
  lto = "thin"
39
39
  codegen-units = 1
40
40
  strip = true
41
+
42
+ # `cargo build --profile profiling` keeps the symbols a sampling profiler needs while
43
+ # generating exactly the code `--release` does.
44
+ [profile.profiling]
45
+ inherits = "release"
46
+ debug = 1
47
+ strip = "none"
data/README.ja.md CHANGED
@@ -32,7 +32,7 @@ CI へ最小限の変更で導入できます。
32
32
 
33
33
  ## 主な機能
34
34
 
35
- Layout、Lint、Metrics、Naming、Security、Style の各部門の Cop を実装しています。
35
+ Bundler、Gemspec、Layout、Lint、Metrics、Migration、Naming、Security、Style の各部門の Cop を実装しています。
36
36
  実装済みの Cop はリリースごとに増えるため、一覧はバイナリ自身が正本です。
37
37
 
38
38
  ```bash
@@ -107,29 +107,44 @@ Ruby プラグイン実行、キャッシュ再利用、カスタム Cop、実
107
107
 
108
108
  ### 適合性
109
109
 
110
- 実装済み Cop は、正規化した JSON offense を使って RuboCop 1.89 との一致を検証済みです。
111
- 本家の全 1,759 ファイルを本家既定設定で検査した現在のスナップショットは、参照 4,052 件中
112
- 4,052 件すべての位置が一致(**recall 100%**)し、**誤検出とメタデータ差もともに 0 件**です。コマンド、比較範囲、測定結果は
113
- [CONFORMANCE.md](CONFORMANCE.md) にまとめています。Rails では Ruby 対象 3,453 ファイルと
114
- ルート/`guides/` の有効 Cop 一覧が完全一致し、全体の誤検出は 0 件です。
110
+ 実装済み Cop は、RuboCop 自身・Rails・Ruby・Homebrew・Mastodon 5 プロジェクト
111
+ 18,246 ファイルに対して、両者とも本家既定設定で検証しています。offense Cop 名・パス・
112
+ 行・桁・終端行・終端桁・長さ・メッセージ・重大度・修正可否のすべてで突き合わせます。
113
+
114
+ 5 つのうち 3 つが**完全一致**です。RuboCop 自身のツリー(5,766 件)、Rails(167,535 件)、
115
+ Mastodon(15,286 件)で、過剰も不足もメタデータ差もありません。対象ファイル一覧は 5 つすべてで
116
+ 一致します。残る差分は `Lint/Syntax` に集中しており、本家の LALR パーサが構文エラーから回復して
117
+ 出す追加診断を tree-sitter では再現できないことによるものです。autocorrect は RuboCop 自身のツリーと
118
+ Mastodon でバイト単位に一致します。この 2 つは死守ラインとして扱い、バイト一致が崩れた場合は
119
+ 既知差分ではなく退行として直します。
120
+
121
+ コマンド、コーパスごとの数値、この種の計測が誤った結論を導く 2 つの罠は
122
+ [CONFORMANCE.md](CONFORMANCE.md) にまとめています。
115
123
 
116
124
  ### 性能
117
125
 
118
- Rails 8.2.0.alpha のソースツリーで測定しました。両者とも同梱の既定設定
119
- (`--force-default-config`)で走らせているため Rails の `.rubocop.yml` は読まず、
120
- **対象ファイルは 3,550 件で完全に一致**しています。
126
+ 適合性検証に使う 5 コーパスすべてで測定しました。両者とも同梱の既定設定
127
+ (`--force-default-config`)で走らせているためプロジェクト側の `.rubocop.yml` は読まず、
128
+ どのコーパスでも**対象ファイル一覧は完全に一致**しています。
129
+
130
+ 両者とも既定の全 Cop で走らせています。**同じ 394 Cop** が名前まで一致しているため、
131
+ どちらも絞る必要がなく、素の実行がそのまま対等な比較になります。
132
+ 各値は暖機後 2 回の最速値です。
133
+
134
+ | コーパス | ファイル | RuboCop 並列 | Sonicop 並列 | RuboCop 単一 | Sonicop 単一 |
135
+ |---|---:|---:|---:|---:|---:|
136
+ | rubocop/rubocop | 1,765 | 10.64 秒 | **2.84 秒** | 40.66 秒 | **13.60 秒** |
137
+ | mastodon/mastodon | 3,290 | 10.78 秒 | **3.13 秒** | 33.22 秒 | **13.57 秒** |
138
+ | Homebrew/brew | 2,176 | 11.13 秒 | **4.61 秒** | 37.89 秒 | **10.81 秒** |
139
+ | rails/rails | 3,550 | 23.84 秒 | **10.15 秒** | 84.93 秒 | **33.69 秒** |
140
+ | ruby/ruby | 7,465 | 95.71 秒 | **35.74 秒** | 258.87 秒 | **107.37 秒** |
121
141
 
122
- | 実行条件 | RuboCop 1.89.0 | Sonicop 26.8.101 | 比 |
123
- |---|---:|---:|---:|
124
- | 同一の 28 Cop・並列 | 10.44 秒 | **2.78 秒** | 3.8 倍 |
125
- | 同一の 28 Cop・単一プロセス | 33.82 秒 | **10.17 秒** | 3.3 倍 |
126
- | 各ツールが既定で有効にする全 Cop | 22.52 秒 *(394 Cop・並列)* | **2.78 秒** *(28 Cop・並列)* | — |
142
+ 差は並列で 2.4〜3.8 倍、単一プロセスで 2.4〜3.5 倍と幅があり、1 コーパスでは代表できません。
143
+ 単一プロセスの列のほうが安定しています。エンジンそのものを測っており、
144
+ 各ツールの並列化が対象ツリーにたまたま噛み合ったかどうかに左右されないためです。
127
145
 
128
- 最後の行は素で叩いたときの実測ですが、**対等な比較ではありません**。Sonicop RuboCop
129
- 609 Cop のうち 28 個しか実装しておらず、答えている問いがそもそも小さいためです。
130
- 上 2 行は RuboCop を同じ 28 Cop に絞ったもので、エンジンの速度差はこちらが実態を表します。
131
- この 28 Cop での検出数は 117,541 件中 243 件(0.2%)しか違わないため、
132
- 仕事を省いて速いわけではありません。
146
+ 仕事を省いて速いわけではありません。この同じ 394 Cop について、RuboCop 自身のツリーと
147
+ Mastodon では**すべての offense が一致**し、autocorrect もバイト単位で一致します。
133
148
 
134
149
  再現時に注意が必要な点が 2 つあります。RuboCop は **`--cache false` と併用すると
135
150
  `--parallel` を黙って無効化します**。そのためここでの並列実行はキャッシュを有効にしたうえで
@@ -138,16 +153,19 @@ Rails 8.2.0.alpha のソースツリーで測定しました。両者とも同
138
153
  Sonicop は `--no-parallel` を渡さない限り並列です。
139
154
 
140
155
  ```bash
141
- # RuboCop(並列・キャッシュは毎回空・Sonicop の実装済み Cop に限定)
156
+ # RuboCop(並列・キャッシュは毎回空・既定の全 394 Cop
142
157
  rubocop --force-default-config --cache true --cache-root "$(mktemp -d)" \
143
- --no-color --parallel --only "$COPS" -f quiet
158
+ --no-color --parallel -f quiet
144
159
 
145
160
  # Sonicop
146
161
  sonicop --force-default-config --format quiet
147
162
  ```
148
163
 
149
164
  測定機は Apple M2(8 コア)、Ruby 4.0.6(YJIT 利用可)、RubyGems 導入の RuboCop 1.89.0。
150
- 各値は暖機後 23 回の最速値です。
165
+ 上の値は 1 分平均のロードアベレージが 429 の状態で採ったもので、**アイドル状態ではありません**。
166
+ 両者を同じ条件で測っているため倍率は保たれますが、秒数そのものは下限ではなく、静かなマシンなら
167
+ より速く出ます。コアを奪い合うものが動いていると両者とも膨らみ、その度合いは一致しないため、
168
+ 秒数そのものが重要なときは他に負荷のない状態で測ってください。
151
169
 
152
170
  ## 開発
153
171
 
data/README.md CHANGED
@@ -32,7 +32,8 @@ existing editor and CI integrations with minimal changes.
32
32
 
33
33
  ## Features
34
34
 
35
- Sonicop implements cops in the Layout, Lint, Metrics, Naming, Security, and Style departments.
35
+ Sonicop implements cops in the Bundler, Gemspec, Layout, Lint, Metrics, Migration, Naming,
36
+ Security, and Style departments.
36
37
  The implemented set grows with each release, so the binary itself is the authoritative list:
37
38
 
38
39
  ```bash
@@ -117,15 +118,16 @@ reuse, custom Ruby cops, or cops outside the implemented set.
117
118
  ### Conformance
118
119
 
119
120
  The implemented cops are verified against RuboCop 1.89.0 over five Ruby projects — RuboCop itself,
120
- Rails, Ruby, Homebrew and Mastodon — totalling 18,244 files, with the upstream default
121
+ Rails, Ruby, Homebrew and Mastodon — totalling 18,246 files, with the upstream default
121
122
  configuration on both sides. Every offense is compared by cop, path, line, column, last line, last
122
123
  column, length, message, severity and correctability.
123
124
 
124
- Three of the five match **exactly**: RuboCop's own tree (4,142 offenses), Rails (117,541) and
125
- Mastodon (7,610), with no excess, no shortfall and no metadata differences. The target file lists
125
+ Three of the five match **exactly**: RuboCop's own tree (5,766 offenses), Rails (167,535) and
126
+ Mastodon (15,286), with no excess, no shortfall and no metadata differences. The target file lists
126
127
  match exactly on all five. What remains is concentrated in `Lint/Syntax`, where RuboCop's LALR
127
128
  parser recovers from an error and emits diagnostics a tree-sitter parse cannot reconstruct.
128
- Autocorrect is byte-identical on every corpus it was run against.
129
+ Autocorrect is byte-identical on RuboCop's own tree and on Mastodon, the two corpora held as a hard
130
+ line: a change that breaks byte equality there is a regression, not a new known divergence.
129
131
 
130
132
  See [CONFORMANCE.md](CONFORMANCE.md) for the commands, the per-corpus numbers, and the two ways a
131
133
  measurement of this kind can mislead you.
@@ -136,28 +138,24 @@ Measured over all five conformance corpora. Both tools were given their own bund
136
138
  configuration (`--force-default-config`), so neither reads the project's `.rubocop.yml`, and on every
137
139
  corpus the two resolve **the same file list**.
138
140
 
139
- RuboCop is restricted to the 28 cops Sonicop implements, which is the only like-for-like comparison
140
- available. Times are the fastest of two warmed runs.
141
+ Both tools run their full default set — **the same 394 cops**, matched name for name so neither
142
+ side is restricted and the comparison is like-for-like as it stands. Times are the fastest of two
143
+ warmed runs.
141
144
 
142
145
  | Corpus | Files | RuboCop parallel | Sonicop parallel | RuboCop single | Sonicop single |
143
146
  |---|---:|---:|---:|---:|---:|
144
- | rubocop/rubocop | 1,765 | 5.30 s | **1.41 s** | 20.88 s | **6.22 s** |
145
- | mastodon/mastodon | 3,289 | 3.77 s | **1.54 s** | 11.18 s | **5.67 s** |
146
- | Homebrew/brew | 2,175 | 8.52 s | **1.47 s** | 19.26 s | **5.20 s** |
147
- | rails/rails | 3,550 | 10.62 s | **5.07 s** | 32.89 s | **15.58 s** |
148
- | ruby/ruby | 7,465 | 27.31 s | **11.19 s** | 96.19 s | **34.85 s** |
147
+ | rubocop/rubocop | 1,765 | 10.64 s | **2.84 s** | 40.66 s | **13.60 s** |
148
+ | mastodon/mastodon | 3,290 | 10.78 s | **3.13 s** | 33.22 s | **13.57 s** |
149
+ | Homebrew/brew | 2,176 | 11.13 s | **4.61 s** | 37.89 s | **10.81 s** |
150
+ | rails/rails | 3,550 | 23.84 s | **10.15 s** | 84.93 s | **33.69 s** |
151
+ | ruby/ruby | 7,465 | 95.71 s | **35.74 s** | 258.87 s | **107.37 s** |
149
152
 
150
- The gap is 2.1x to 5.8x in parallel and 2.0x to 3.7x single-process, so no single corpus summarizes
153
+ The gap is 2.4x to 3.8x in parallel and 2.4x to 3.5x single-process, so no single corpus summarizes
151
154
  it. The single-process column is the steadier of the two — it measures the engines rather than how
152
155
  well each one's parallelism happens to fit the tree it was pointed at.
153
156
 
154
- Out of the box the two commands do different amounts of work RuboCop enables 394 cops by default
155
- against Sonicop's 28 so that comparison is **not** like-for-like. For the record, RuboCop's default
156
- parallel run takes 10.46 s on its own tree, 9.91 s on Mastodon, 10.83 s on Homebrew, 20.73 s on
157
- Rails and 75.05 s on Ruby.
158
-
159
- Over the 28 shared cops the two agree on **every offense** on RuboCop's own tree, on Rails and on
160
- Mastodon, so the speed is not bought by skipping work.
157
+ The speed is not bought by skipping work: over those same 394 cops the two agree on **every offense**
158
+ on RuboCop's own tree and on Mastodon, and autocorrect there is byte-identical.
161
159
 
162
160
  Two details matter for reproducing this. RuboCop **silently turns `--parallel` off when combined
163
161
  with `--cache false`**, so its parallel runs here use a cache directory that is deleted before each
@@ -166,17 +164,20 @@ overstates the difference. RuboCop's default is a single process, while Sonicop
166
164
  `--no-parallel` is passed.
167
165
 
168
166
  ```bash
169
- # RuboCop, parallel, cold cache, restricted to the cops Sonicop implements
167
+ # RuboCop, parallel, cold cache, its full default set of 394 cops
170
168
  rubocop --force-default-config --cache true --cache-root "$(mktemp -d)" \
171
- --no-color --parallel --only "$COPS" -f quiet
169
+ --no-color --parallel -f quiet
172
170
 
173
171
  # Sonicop
174
172
  sonicop --force-default-config --format quiet
175
173
  ```
176
174
 
177
175
  Machine: Apple M2 (8 cores), Ruby 4.0.6 with YJIT available, RubyGems-installed RuboCop 1.89.0.
178
- Measure on an otherwise idle machine: anything else competing for cores inflates both sides
179
- unevenly, and a contended run here reported RuboCop's Rails time as 21 s rather than 10.6 s.
176
+ The numbers above were taken with a one-minute load average between 4 and 29 — the machine was in
177
+ use, not idle. Both tools ran under the same conditions, so the ratios hold, but the absolute
178
+ seconds are not a floor: expect better on a quiet machine. Anything competing for cores inflates
179
+ both sides, and not by the same factor on each, so measure on an otherwise idle machine when the
180
+ absolute numbers matter to you.
180
181
 
181
182
  ## Development
182
183
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Generated from Cargo.toml by `rake version:sync`. Do not edit by hand.
4
4
  module Sonicop
5
- VERSION = '26.8.102'
5
+ VERSION = '26.8.103'
6
6
  end
data/src/cli.rs CHANGED
@@ -238,6 +238,13 @@ impl Cli {
238
238
  }
239
239
 
240
240
  pub fn run() -> i32 {
241
+ crate::profile::set_enabled(std::env::var_os("SONICOP_PROFILE").is_some());
242
+ let code = run_inner();
243
+ crate::profile::report(&crate::rules::rule_names().collect::<Vec<_>>());
244
+ code
245
+ }
246
+
247
+ fn run_inner() -> i32 {
241
248
  let arguments = match composed_arguments() {
242
249
  Ok(arguments) => arguments,
243
250
  Err(error) => {
@@ -402,8 +409,12 @@ fn try_run(cli: Cli, outputs: &[Option<PathBuf>]) -> Result<i32> {
402
409
  safe_only: cli.safe,
403
410
  ignore_disable_comments: cli.ignore_disable_comments,
404
411
  display_suppressed: cli.display_suppressed,
412
+ correcting: cli.correct_mode() != CorrectMode::None,
405
413
  };
406
414
  let correct_mode = cli.correct_mode();
415
+ if !cli.list_target_files {
416
+ warn_unimplemented_enabled(&config, &selection);
417
+ }
407
418
 
408
419
  let parallel = !cli.no_parallel && (cli.parallel || cli.stdin.is_none());
409
420
  let mut reports = if let Some(stdin_path) = &cli.stdin {
@@ -478,7 +489,7 @@ fn try_run(cli: Cli, outputs: &[Option<PathBuf>]) -> Result<i32> {
478
489
  eprintln!("{message}");
479
490
  run_errors += 1;
480
491
  }
481
- if outcome.corrected_count > 0 {
492
+ if outcome.rewritten {
482
493
  if cli.stdin.is_some() {
483
494
  stdin_corrected = Some(outcome.text);
484
495
  } else {
@@ -553,6 +564,19 @@ impl FailLevel {
553
564
  }
554
565
 
555
566
  fn validate_compatibility(cli: &Cli) -> Result<()> {
567
+ // `Lint/RedundantCopDisableDirective` reports directives that switched off cops with nothing
568
+ // to say, so a run narrowed to a handful of cops could only ever report every directive in the
569
+ // file. RuboCop refuses the combination rather than answering wrongly.
570
+ if cli.only.as_deref().is_some_and(|value| {
571
+ csv(Some(value)).iter().any(|name| {
572
+ matches!(
573
+ name.as_str(),
574
+ "Lint/RedundantCopDisableDirective" | "RedundantCopDisableDirective"
575
+ )
576
+ })
577
+ }) {
578
+ bail!("Lint/RedundantCopDisableDirective cannot be used with --only.");
579
+ }
556
580
  if cli.except.as_deref().is_some_and(|value| {
557
581
  csv(Some(value))
558
582
  .iter()
@@ -683,6 +707,49 @@ fn validate_selection(values: &[String], flag: &str, config: &Config) -> Result<
683
707
  Ok(())
684
708
  }
685
709
 
710
+ /// Tell the user about cops that resolved to enabled but have no implementation here.
711
+ ///
712
+ /// An unimplemented cop is never planned, so a configuration that switches one on — or an explicit
713
+ /// `--only` naming it — yields no offenses at all where RuboCop would report some. Reporting 394 of
714
+ /// RuboCop's 609 cops is fine as long as nobody mistakes silence for a clean file, so say which
715
+ /// checks did not happen.
716
+ ///
717
+ /// The note goes to stderr, never stdout: RuboCop's own `-P/--parallel is being ignored` notice
718
+ /// goes to stdout and corrupts its JSON output, which is a mistake worth not repeating.
719
+ fn warn_unimplemented_enabled(config: &Config, selection: &Selection) {
720
+ let implemented: HashSet<&str> = rule_names().collect();
721
+ let mut skipped = config
722
+ .known_cop_names()
723
+ .filter(|name| !implemented.contains(name))
724
+ .filter(|&name| {
725
+ let enabled = config.rule_enabled_with_pending(
726
+ name,
727
+ selection.enable_pending,
728
+ selection.disable_pending,
729
+ );
730
+ selection.includes(name, enabled, config.rule_safe(name))
731
+ })
732
+ .collect::<Vec<_>>();
733
+ if skipped.is_empty() {
734
+ return;
735
+ }
736
+ skipped.sort_unstable();
737
+ const SHOWN: usize = 5;
738
+ let listed = skipped[..skipped.len().min(SHOWN)].join(", ");
739
+ let more = skipped.len().saturating_sub(SHOWN);
740
+ let rest = if more > 0 {
741
+ format!(" and {more} more (--debug lists every unimplemented cop)")
742
+ } else {
743
+ String::new()
744
+ };
745
+ let plural = if skipped.len() == 1 { "cop" } else { "cops" };
746
+ eprintln!(
747
+ "warning: {} enabled {plural} not implemented by Sonicop; nothing was checked for {}: {listed}{rest}",
748
+ skipped.len(),
749
+ if skipped.len() == 1 { "it" } else { "them" }
750
+ );
751
+ }
752
+
686
753
  fn debug_report(cwd: &Path, config: &Config, targets: usize, parallel: bool) {
687
754
  let implemented: HashSet<&str> = rule_names().collect();
688
755
  let mut unimplemented = config
@@ -821,7 +888,7 @@ fn filter_displayed_offenses(reports: &mut [FileReport], cli: &Cli, fail_level:
821
888
  && (!cli.display_only_correctable || offense.is_correctable())
822
889
  && (!cli.display_only_safe_correctable
823
890
  || (offense.is_correctable()
824
- && offense.correction.as_ref().is_some_and(|edit| edit.safe)))
891
+ && offense.corrections.iter().all(|edit| edit.safe)))
825
892
  });
826
893
  }
827
894
  }
@@ -880,6 +947,7 @@ fn render_outputs(request: &RenderRequest<'_>, reports: &[FileReport]) -> Result
880
947
  corrected_count,
881
948
  fail_level: fail_level.severity(),
882
949
  safe_autocorrect: cli.correct_mode() == CorrectMode::Safe,
950
+ display_only_failed: cli.display_only_failed,
883
951
  },
884
952
  )?;
885
953
  if cli.display_time {
data/src/config/mod.rs CHANGED
@@ -19,7 +19,7 @@ use crate::ruby_version::{
19
19
 
20
20
  use inheritance::{load_with_inheritance, merge_config};
21
21
  use loader::{find_config, find_project_root};
22
- use paths::{PathPatterns, compile_excludes, cop_patterns, has_hidden_component};
22
+ use paths::{PathPatterns, compile_excludes, compile_includes, cop_patterns, has_hidden_component};
23
23
  use plugin::{belongs_to_plugin, configured_plugin_departments};
24
24
 
25
25
  pub use store::ConfigStore;
@@ -40,6 +40,7 @@ pub struct Config {
40
40
  unrecognized_cops: Vec<String>,
41
41
  includes: PathPatterns,
42
42
  excludes: HashMap<String, PathPatterns>,
43
+ cop_includes: HashMap<String, PathPatterns>,
43
44
  }
44
45
 
45
46
  impl Config {
@@ -106,6 +107,7 @@ impl Config {
106
107
 
107
108
  let includes = cop_patterns(&raw, "AllCops", "Include").unwrap_or_default();
108
109
  let excludes = compile_excludes(&raw);
110
+ let cop_includes = compile_includes(&raw);
109
111
 
110
112
  Ok(Self {
111
113
  raw,
@@ -118,6 +120,7 @@ impl Config {
118
120
  unrecognized_cops,
119
121
  includes,
120
122
  excludes,
123
+ cop_includes,
121
124
  })
122
125
  }
123
126
 
@@ -269,6 +272,18 @@ impl Config {
269
272
  self.excluded_by(name, path)
270
273
  }
271
274
 
275
+ /// Whether `name`'s own `Include` list reaches `path`.
276
+ ///
277
+ /// This is the `Include` half of RuboCop's `Cop::Base#relevant_file?`, which is what keeps the
278
+ /// `Bundler` and `Gemspec` cops on the files their configuration names -- `**/Gemfile`,
279
+ /// `**/*.gemspec` -- rather than on every Ruby file the run targets. A cop that names no
280
+ /// `Include` applies to all of them, and one whose list is empty to none.
281
+ pub fn rule_included(&self, name: &str, path: &Path) -> bool {
282
+ self.cop_includes
283
+ .get(name)
284
+ .is_none_or(|patterns| patterns.matches_includes(path, &self.project_root))
285
+ }
286
+
272
287
  fn excluded_by(&self, name: &str, path: &Path) -> bool {
273
288
  self.excludes
274
289
  .get(name)
data/src/config/paths.rs CHANGED
@@ -19,6 +19,25 @@ pub(super) fn compile_excludes(raw: &Value) -> HashMap<String, PathPatterns> {
19
19
  .collect()
20
20
  }
21
21
 
22
+ /// Every cop's own `Include` list.
23
+ ///
24
+ /// `AllCops/Include` chooses the files a run inspects; a cop's own `Include` chooses which of them
25
+ /// it applies to, which is how the `Bundler` and `Gemspec` departments stay off ordinary Ruby.
26
+ /// A cop that names no `Include` has no entry here and applies to everything.
27
+ pub(super) fn compile_includes(raw: &Value) -> HashMap<String, PathPatterns> {
28
+ let Some(mapping) = raw.as_mapping() else {
29
+ return HashMap::new();
30
+ };
31
+ mapping
32
+ .iter()
33
+ .filter_map(|(name, value)| {
34
+ let name = name.as_str()?;
35
+ let patterns = mapping_patterns(value.as_mapping()?, "Include")?;
36
+ Some((name.to_owned(), patterns))
37
+ })
38
+ .collect()
39
+ }
40
+
22
41
  pub(super) fn cop_patterns(raw: &Value, name: &str, key: &str) -> Option<PathPatterns> {
23
42
  mapping_patterns(raw.as_mapping()?.get(name)?.as_mapping()?, key)
24
43
  }