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
data/src/directives.rs CHANGED
@@ -10,11 +10,56 @@ use crate::diagnostic::Offense;
10
10
  use crate::engine::{Selection, is_mandatory_cop};
11
11
  use crate::source::SourceFile;
12
12
 
13
+ /// What the last directive to speak for a name decided, which is the shape `analyze_cop` leaves
14
+ /// one cop's analysis in.
15
+ ///
16
+ /// An `enable` records a verdict rather than dropping the entry, because the cop it names may sit
17
+ /// under a department -- or under `all` -- that a directive above it switched off, and upstream
18
+ /// closes that cop's range while leaving the rest of the department alone.
19
+ #[derive(Clone, Debug)]
20
+ enum Verdict {
21
+ /// `analyze_disabled`, carrying the `-- reason` the directive was written with.
22
+ Disabled(Option<String>),
23
+ /// `analyze_rest`.
24
+ Enabled,
25
+ }
26
+
13
27
  #[derive(Clone, Debug, Default)]
14
28
  struct Snapshot {
15
- all: bool,
16
- all_reason: Option<String>,
17
- cops: HashMap<String, Option<String>>,
29
+ /// The verdict a directive that named `all` left in force.
30
+ all: Option<Verdict>,
31
+ /// The verdict for every name a directive spelled out, department names included. See
32
+ /// [`Snapshot::verdict_for`] for why a department is not expanded here.
33
+ cops: HashMap<String, Verdict>,
34
+ }
35
+
36
+ impl Snapshot {
37
+ /// The directive in force for `cop`: the one that named it, else the one that named its
38
+ /// department, else the one that named `all`.
39
+ ///
40
+ /// `DirectiveComment#cop_names` expands a department and `all` into concrete cop names when
41
+ /// the directive is read, so upstream needs no lookup order at all. Expanding here instead
42
+ /// would mean copying a couple of hundred names into a snapshot that every line of the file
43
+ /// clones, and a department is exactly the prefix of the cops it holds -- so the expansion is
44
+ /// left implicit and read back here, with the entries it supersedes dropped by
45
+ /// [`record`] as the directive is applied.
46
+ fn verdict_for(&self, cop: &str) -> Option<&Verdict> {
47
+ // `parsed_cop_names` subtracts `Lint/Syntax` from every directive, `all` and departments
48
+ // included: a file cannot switch off the report of the parse it was not read by.
49
+ if cop == UNDISABLEABLE_COPS[1] {
50
+ return None;
51
+ }
52
+ if let Some(verdict) = self.cops.get(cop) {
53
+ return Some(verdict);
54
+ }
55
+ // `exclude_lint_department_cops` takes the cop that reads directives out of `all` and out
56
+ // of the `Lint` department, so only a directive that spells its name out reaches it:
57
+ // "this cop is not disabled when disabling all cops".
58
+ if cop == UNDISABLEABLE_COPS[0] {
59
+ return None;
60
+ }
61
+ self.cops.get(department(cop)).or(self.all.as_ref())
62
+ }
18
63
  }
19
64
 
20
65
  #[derive(Debug, Default)]
@@ -56,7 +101,9 @@ impl DirectiveState {
56
101
  ) -> Self {
57
102
  let mut current = Snapshot::default();
58
103
  for cop in disabled_by_config {
59
- current.cops.insert((*cop).to_owned(), None);
104
+ current
105
+ .cops
106
+ .insert((*cop).to_owned(), Verdict::Disabled(None));
60
107
  }
61
108
  let mut stack = Vec::new();
62
109
  let mut line_states = Vec::with_capacity(source.line_count());
@@ -81,14 +128,21 @@ impl DirectiveState {
81
128
  .and_then(|&start| parse_directive(line, start));
82
129
 
83
130
  if let Some(directive) = directive {
84
- if directive.inline && matches!(directive.action, Action::Disable) {
85
- let mut line_only = before;
86
- apply_disable(&mut line_only, &directive.cops, directive.reason);
87
- line_states.push(line_only);
88
- continue;
89
- }
90
-
91
131
  match directive.action {
132
+ // `analyze_single_line`, which is where `analyze_cop` sends a directive that
133
+ // shares its line with code or that was written behind prose: it covers that
134
+ // one line and no more.
135
+ Action::Disable if directive.inline => {
136
+ let mut line_only = before;
137
+ apply_disable(&mut line_only, &directive.cops, directive.reason);
138
+ line_states.push(line_only);
139
+ continue;
140
+ }
141
+ // `return analysis unless directive.disabled?`: an `enable` written there is a
142
+ // no-op, and in particular does not close the range a block-form `disable`
143
+ // above it opened. `push` and `pop` are routed before that check upstream, so
144
+ // they act wherever they are written.
145
+ Action::Enable if directive.inline => {}
92
146
  Action::Disable => {
93
147
  apply_disable(&mut current, &directive.cops, directive.reason);
94
148
  }
@@ -135,10 +189,7 @@ impl DirectiveState {
135
189
  let Some(state) = self.line_states.get(line - 1) else {
136
190
  break;
137
191
  };
138
- if !(state.all
139
- || state.cops.contains_key(cop)
140
- || state.cops.contains_key(department(cop)))
141
- {
192
+ if !matches!(state.verdict_for(cop), Some(Verdict::Disabled(_))) {
142
193
  continue;
143
194
  }
144
195
  match ranges.last_mut() {
@@ -161,19 +212,10 @@ impl DirectiveState {
161
212
  }
162
213
  let (line, _) = source.line_column(offense.start);
163
214
  let state = self.line_states.get(line.saturating_sub(1))?;
164
- if let Some(reason) = state.cops.get(offense.cop_name) {
165
- return Some(reason.clone());
166
- }
167
- // `exclude_lint_department_cops` takes the cop that reads directives out of `all` and out
168
- // of the `Lint` department, so only a directive that spells its name out switches it off:
169
- // "this cop is not disabled when disabling all cops".
170
- if offense.cop_name == UNDISABLEABLE_COPS[0] {
171
- return None;
215
+ match state.verdict_for(offense.cop_name)? {
216
+ Verdict::Disabled(reason) => Some(reason.clone()),
217
+ Verdict::Enabled => None,
172
218
  }
173
- if let Some(reason) = state.cops.get(department(offense.cop_name)) {
174
- return Some(reason.clone());
175
- }
176
- state.all.then(|| state.all_reason.clone())
177
219
  }
178
220
  }
179
221
 
@@ -206,28 +248,102 @@ pub fn opted_in_cops(source: &SourceFile, comment_ranges: &[Range<usize>]) -> Ha
206
248
  }
207
249
 
208
250
  fn apply_disable(state: &mut Snapshot, cops: &[String], reason: Option<String>) {
209
- if cops.iter().any(|cop| cop == "all") {
210
- state.all = true;
211
- state.all_reason = reason;
212
- } else {
213
- state
214
- .cops
215
- .extend(cops.iter().cloned().map(|cop| (cop, reason.clone())));
251
+ for cop in cops {
252
+ record(state, cop, Verdict::Disabled(reason.clone()));
216
253
  }
217
254
  }
218
255
 
219
256
  fn apply_enable(state: &mut Snapshot, cops: &[String]) {
220
- if cops.iter().any(|cop| cop == "all") {
221
- state.all = false;
222
- state.all_reason = None;
223
- state.cops.clear();
224
- } else {
225
- for cop in cops {
226
- state.cops.remove(cop);
227
- }
257
+ for cop in cops {
258
+ record(state, cop, Verdict::Enabled);
228
259
  }
229
260
  }
230
261
 
262
+ /// One name of one directive, applied to the snapshot.
263
+ ///
264
+ /// `CommentConfig#analyze` writes the verdict against every cop `DirectiveComment#cop_names`
265
+ /// expanded the name into, so a directive that names `all` or a department speaks for every cop
266
+ /// below it and nothing an earlier directive left against one of them survives. Keeping the
267
+ /// expansion implicit ([`Snapshot::verdict_for`] reads a department back off the cop's own name)
268
+ /// means dropping those superseded entries by hand -- and leaving alone the two cops the
269
+ /// expansion does not reach, whose own entry outlives the broader directive that follows it.
270
+ fn record(state: &mut Snapshot, cop: &str, verdict: Verdict) {
271
+ if cop == "all" {
272
+ state.all = Some(verdict);
273
+ state.cops.retain(|name, _| undisableable(name));
274
+ return;
275
+ }
276
+ let cop = directive_cop_name(cop);
277
+ // `parsed_cop_names` ends in `cops - [LINT_SYNTAX_COP]`, so a directive that names the parse
278
+ // report names nothing at all -- not even enough to supersede what stands.
279
+ if cop == UNDISABLEABLE_COPS[1] {
280
+ return;
281
+ }
282
+ state.cops.retain(|name, _| {
283
+ department(name) != cop || (cop == LINT_DEPARTMENT && undisableable(name))
284
+ });
285
+ state.cops.insert(cop.to_owned(), verdict);
286
+ }
287
+
288
+ /// `exclude_lint_department_cops`: whether `all` and the `Lint` department leave this cop alone.
289
+ fn undisableable(cop: &str) -> bool {
290
+ UNDISABLEABLE_COPS.contains(&cop)
291
+ }
292
+
293
+ /// `Cop::Registry.global`: the cops a directive resolves its names against.
294
+ ///
295
+ /// Both `CommentConfig#qualified_cop_name` and `DirectiveComment#initialize` read the *global*
296
+ /// registry rather than the one the run mobilized, so what `# rubocop:disable For` means depends
297
+ /// on neither the configuration nor `--only`. [`CopRegistry`] is neither -- it is built from the
298
+ /// configuration, and only for the cop that reviews directives -- so the names come from the cop
299
+ /// list itself, which is what the global registry holds upstream.
300
+ struct GlobalRegistry {
301
+ departments: HashSet<&'static str>,
302
+ /// `Registry#qualify_badge`, worked out once: the cop that carries a bare name, for the names
303
+ /// exactly one cop carries. An ambiguous one is left out, which is what makes a directive that
304
+ /// uses it resolve to nothing rather than to whichever cop was registered first.
305
+ by_cop_name: HashMap<&'static str, &'static str>,
306
+ }
307
+
308
+ fn global_registry() -> &'static GlobalRegistry {
309
+ static REGISTRY: OnceLock<GlobalRegistry> = OnceLock::new();
310
+ REGISTRY.get_or_init(|| {
311
+ let mut departments = HashSet::new();
312
+ let mut candidates: HashMap<&'static str, Option<&'static str>> = HashMap::new();
313
+ for name in crate::rules::rule_names() {
314
+ departments.insert(department(name));
315
+ let cop_name = name.rsplit('/').next().unwrap_or(name);
316
+ candidates
317
+ .entry(cop_name)
318
+ .and_modify(|found| *found = None)
319
+ .or_insert(Some(name));
320
+ }
321
+ GlobalRegistry {
322
+ departments,
323
+ by_cop_name: candidates
324
+ .into_iter()
325
+ .filter_map(|(cop_name, full)| Some((cop_name, full?)))
326
+ .collect(),
327
+ }
328
+ })
329
+ }
330
+
331
+ /// `CommentConfig#qualified_cop_name`, for one name a directive spelled out.
332
+ ///
333
+ /// A department is left as it is: upstream expands it into the cops it holds, each of which is
334
+ /// already qualified. Everything else goes through `Registry.qualified_cop_name`, which is what
335
+ /// makes `# rubocop:disable For` switch off `Style/For` -- RuboCop warns about the missing
336
+ /// department through `Migration/DepartmentName`, but it honours the directive -- and what makes
337
+ /// a name written under the wrong department reach the cop that really carries it.
338
+ fn directive_cop_name(name: &str) -> &str {
339
+ let registry = global_registry();
340
+ if registry.departments.contains(name) {
341
+ return name;
342
+ }
343
+ let cop_name = name.rsplit('/').next().unwrap_or(name);
344
+ registry.by_cop_name.get(cop_name).copied().unwrap_or(name)
345
+ }
346
+
231
347
  #[derive(Clone, Copy, Debug)]
232
348
  enum Action {
233
349
  Disable,
@@ -270,7 +386,16 @@ fn parse_directive(line: &str, comment_start: usize) -> Option<Directive> {
270
386
  // `!comment_only_line?(directive.line_number) || directive.single_line?`: a directive covers
271
387
  // its own line alone when that line holds code as well, and also when it was written behind
272
388
  // prose rather than opening its comment.
273
- let inline = !line[..comment_start].trim().is_empty() || header.start != 0;
389
+ //
390
+ // A byte order mark is not code. It is not in `White_Space` either, so `trim` leaves it and
391
+ // the first line of a file that carries one would read as holding something -- upstream never
392
+ // sees it, because `comment_only_line?` asks the token stream and `parser` strips the mark
393
+ // before it lexes anything.
394
+ let before_marker = &line[..comment_start];
395
+ let before_marker = before_marker
396
+ .strip_prefix(crate::source::BYTE_ORDER_MARK)
397
+ .unwrap_or(before_marker);
398
+ let inline = !before_marker.trim().is_empty() || header.start != 0;
274
399
 
275
400
  if matches!(action, Action::Push) {
276
401
  let push_operations = arguments
@@ -367,6 +492,9 @@ pub(crate) fn cop_name_length(text: &str) -> Option<usize> {
367
492
  /// directives by naming their department.
368
493
  const UNDISABLEABLE_COPS: [&str; 2] = ["Lint/RedundantCopDisableDirective", "Lint/Syntax"];
369
494
 
495
+ /// `DirectiveComment::LINT_DEPARTMENT`, the one department whose expansion leaves cops out.
496
+ const LINT_DEPARTMENT: &str = "Lint";
497
+
370
498
  /// The cop `CommentConfig` refuses to record a directive for once it is explicitly enabled, so
371
499
  /// that a file cannot switch off the cop that objects to its directives.
372
500
  const DISABLE_COPS_DIRECTIVE_COP: &str = "Style/DisableCopsWithinSourceCodeDirective";
@@ -1262,7 +1390,11 @@ fn lines_with_code(source: &SourceFile, comment_ranges: &[Range<usize>]) -> Hash
1262
1390
  let mut lines = HashSet::new();
1263
1391
  let mut comments = comment_ranges.iter().peekable();
1264
1392
  let mut line = 1;
1265
- let mut index = 0;
1393
+ // `parser` strips a byte order mark before it lexes, so upstream never has a token for one and
1394
+ // the first line of a file that carries one is still a comment-only line. The mark is not in
1395
+ // `White_Space`, so nothing below would take it for a blank.
1396
+ let mut index = usize::from(source.text().starts_with(crate::source::BYTE_ORDER_MARK))
1397
+ * crate::source::BYTE_ORDER_MARK.len();
1266
1398
  while index < text.len() {
1267
1399
  if comments
1268
1400
  .peek()
@@ -1374,57 +1506,246 @@ fn pop_state(
1374
1506
 
1375
1507
  #[cfg(test)]
1376
1508
  mod tests {
1509
+ use std::ops::Range;
1510
+ use std::path::Path;
1511
+
1512
+ use crate::config::Config;
1377
1513
  use crate::diagnostic::{Offense, Severity};
1514
+ use crate::engine::{self, Selection};
1378
1515
  use crate::source::SourceFile;
1379
1516
 
1380
1517
  use super::DirectiveState;
1381
1518
 
1382
- #[test]
1383
- fn handles_block_inline_reason_and_push_pop_directives() {
1384
- let source = SourceFile::new(
1385
- "test.rb",
1386
- "# rubocop:disable Layout -- legacy\n\
1387
- a = 1 \n\
1388
- # rubocop:enable Layout\n\
1389
- b = 2 # rubocop:disable Layout/TrailingWhitespace\n\
1390
- # rubocop:push -Layout/TrailingWhitespace\n\
1391
- c = 3 \n\
1392
- # rubocop:pop\n\
1393
- d = 4 \n"
1394
- .to_owned(),
1395
- );
1396
- let comment_ranges: Vec<_> = (1..=source.line_count())
1519
+ /// Every comment of a file whose comments are all written with a bare `#`, which is what the
1520
+ /// parse hands [`DirectiveState`] for real.
1521
+ fn comment_ranges(source: &SourceFile) -> Vec<Range<usize>> {
1522
+ (1..=source.line_count())
1397
1523
  .filter_map(|line_number| {
1398
1524
  let line = source.line(line_number);
1399
1525
  let local_start = line.find('#')?;
1400
1526
  let start = source.line_start(line_number) + local_start;
1401
1527
  Some(start..start + line[local_start..].trim_end().len())
1402
1528
  })
1403
- .collect();
1404
- let directives = DirectiveState::parse(&source, &comment_ranges);
1529
+ .collect()
1530
+ }
1531
+
1532
+ fn suppression_of(text: &str, cop: &'static str, line: usize) -> Option<Option<String>> {
1533
+ let source = SourceFile::new("test.rb", text.to_owned());
1534
+ let ranges = comment_ranges(&source);
1535
+ let offense = Offense::new(
1536
+ cop,
1537
+ Severity::Convention,
1538
+ "test",
1539
+ source.line_start(line),
1540
+ source.line_start(line) + 1,
1541
+ );
1542
+ DirectiveState::parse(&source, &ranges).suppression(&offense, &source)
1543
+ }
1544
+
1545
+ /// The whole run, so that the directives are read the way the engine reads them rather than
1546
+ /// the way the test builds them: `--only Style/For` over a file of `for` loops reports one
1547
+ /// offense per loop the directives left switched on.
1548
+ fn offense_count(text: &str) -> usize {
1549
+ let config = Config::load_with_options(None, Path::new("/"), true)
1550
+ .expect("the embedded default configuration has to load");
1551
+ let selection = Selection {
1552
+ only: vec!["Style/For".to_owned()],
1553
+ ..Selection::default()
1554
+ };
1555
+ engine::inspect_source("example.rb", text.to_owned(), &config, &selection)
1556
+ .expect("the source has to be inspectable")
1557
+ .offenses
1558
+ .len()
1559
+ }
1560
+
1561
+ const FIRST_LOOP: &str = "for i in 1..2 do\n puts i\nend\n";
1562
+ const SECOND_LOOP: &str = "for j in 1..2 do\n puts j\nend\n";
1563
+
1564
+ #[test]
1565
+ fn handles_block_inline_reason_and_push_pop_directives() {
1566
+ let text = "# rubocop:disable Layout -- legacy\n\
1567
+ a = 1 \n\
1568
+ # rubocop:enable Layout\n\
1569
+ b = 2 # rubocop:disable Layout/TrailingWhitespace\n\
1570
+ # rubocop:push -Layout/TrailingWhitespace\n\
1571
+ c = 3 \n\
1572
+ # rubocop:pop\n\
1573
+ d = 4 \n";
1405
1574
  for (line, expected) in [(2, true), (4, true), (6, true), (8, false)] {
1406
- let offense = Offense::new(
1407
- "Layout/TrailingWhitespace",
1408
- Severity::Convention,
1409
- "test",
1410
- source.line_start(line),
1411
- source.line_start(line) + 1,
1412
- );
1413
1575
  assert_eq!(
1414
- directives.suppression(&offense, &source).is_some(),
1576
+ suppression_of(text, "Layout/TrailingWhitespace", line).is_some(),
1415
1577
  expected
1416
1578
  );
1417
1579
  }
1418
- let offense = Offense::new(
1419
- "Layout/TrailingWhitespace",
1420
- Severity::Convention,
1421
- "test",
1422
- source.line_start(2),
1423
- source.line_start(2) + 1,
1424
- );
1425
1580
  assert_eq!(
1426
- directives.suppression(&offense, &source),
1581
+ suppression_of(text, "Layout/TrailingWhitespace", 2),
1427
1582
  Some(Some("legacy".to_owned()))
1428
1583
  );
1429
1584
  }
1585
+
1586
+ /// `analyze_single_line` is `return analysis unless directive.disabled?`, so an `enable` that
1587
+ /// shares its line with code changes nothing -- least of all closing the range a block-form
1588
+ /// `disable` above it opened, which is what it used to do here and what left the rest of the
1589
+ /// file reporting again.
1590
+ #[test]
1591
+ fn an_inline_enable_does_not_close_a_block_disable() {
1592
+ let text = format!(
1593
+ "# rubocop:disable Style/For\n{FIRST_LOOP}\
1594
+ x = 1 # rubocop:enable Style/For\n{SECOND_LOOP}"
1595
+ );
1596
+ assert!(suppression_of(&text, "Style/For", 6).is_some());
1597
+ assert_eq!(offense_count(&text), 0);
1598
+ }
1599
+
1600
+ /// The same for a `push`, which `CommentConfig#analyze` routes before it ever asks whether the
1601
+ /// directive covers one line: it acts wherever it is written.
1602
+ #[test]
1603
+ fn an_inline_push_and_pop_still_act() {
1604
+ let text = format!(
1605
+ "x = 1 # rubocop:push -Style/For\n{FIRST_LOOP}\
1606
+ y = 2 # rubocop:pop\n{SECOND_LOOP}"
1607
+ );
1608
+ assert!(suppression_of(&text, "Style/For", 2).is_some());
1609
+ assert!(suppression_of(&text, "Style/For", 6).is_none());
1610
+ assert_eq!(offense_count(&text), 1);
1611
+ }
1612
+
1613
+ /// `DirectiveComment#cop_names` expands a department into the cops it holds, so an `enable`
1614
+ /// that names one of them closes that cop's range and leaves the rest of the department
1615
+ /// switched off. Recording the department verbatim and only ever removing the name an `enable`
1616
+ /// spelled out kept the cop disabled to the end of the file, which is the dangerous direction:
1617
+ /// it hides offenses that RuboCop reports.
1618
+ #[test]
1619
+ fn an_enable_reopens_one_cop_of_a_disabled_department() {
1620
+ let text = format!(
1621
+ "# rubocop:disable Style\n{FIRST_LOOP}\
1622
+ # rubocop:enable Style/For\n{SECOND_LOOP}"
1623
+ );
1624
+ assert!(suppression_of(&text, "Style/For", 2).is_some());
1625
+ assert!(suppression_of(&text, "Style/For", 6).is_none());
1626
+ // The department is still switched off for everything it did not name.
1627
+ assert!(suppression_of(&text, "Style/RedundantSelf", 6).is_some());
1628
+ assert_eq!(offense_count(&text), 1);
1629
+ }
1630
+
1631
+ /// `all` expands the same way.
1632
+ #[test]
1633
+ fn an_enable_reopens_one_cop_of_a_disabled_all() {
1634
+ let text = format!(
1635
+ "# rubocop:disable all\n{FIRST_LOOP}\
1636
+ # rubocop:enable Style/For\n{SECOND_LOOP}"
1637
+ );
1638
+ assert!(suppression_of(&text, "Style/For", 2).is_some());
1639
+ assert!(suppression_of(&text, "Style/For", 6).is_none());
1640
+ assert!(suppression_of(&text, "Layout/TrailingWhitespace", 6).is_some());
1641
+ assert_eq!(offense_count(&text), 1);
1642
+ }
1643
+
1644
+ /// A directive that names a department speaks for every cop below it, so what an earlier
1645
+ /// directive left against one of them is gone.
1646
+ #[test]
1647
+ fn a_department_directive_supersedes_the_cops_below_it() {
1648
+ let reopened_then_closed = format!(
1649
+ "# rubocop:disable Style\n{FIRST_LOOP}\
1650
+ # rubocop:enable Style/For\n\
1651
+ # rubocop:disable Style\n{SECOND_LOOP}"
1652
+ );
1653
+ assert!(suppression_of(&reopened_then_closed, "Style/For", 7).is_some());
1654
+
1655
+ let closed_then_reopened = format!(
1656
+ "# rubocop:disable Style/For\n{FIRST_LOOP}\
1657
+ # rubocop:enable Style\n{SECOND_LOOP}"
1658
+ );
1659
+ assert!(suppression_of(&closed_then_reopened, "Style/For", 6).is_none());
1660
+ }
1661
+
1662
+ /// `Registry.qualified_cop_name`: RuboCop warns about the missing department through
1663
+ /// `Migration/DepartmentName` but honours the directive, so a name written without one -- or
1664
+ /// under the wrong one -- reaches the cop that carries it.
1665
+ #[test]
1666
+ fn an_unqualified_cop_name_reaches_the_cop_that_carries_it() {
1667
+ let bare = format!("# rubocop:disable For\n{FIRST_LOOP}");
1668
+ assert!(suppression_of(&bare, "Style/For", 2).is_some());
1669
+ assert_eq!(offense_count(&bare), 0);
1670
+
1671
+ let wrong_department = format!("# rubocop:disable Lint/For\n{FIRST_LOOP}");
1672
+ assert!(suppression_of(&wrong_department, "Style/For", 2).is_some());
1673
+ assert_eq!(offense_count(&wrong_department), 0);
1674
+
1675
+ let unqualified_enable = format!(
1676
+ "# rubocop:disable Style/For\n{FIRST_LOOP}\
1677
+ # rubocop:enable For\n{SECOND_LOOP}"
1678
+ );
1679
+ assert!(suppression_of(&unqualified_enable, "Style/For", 6).is_none());
1680
+ assert_eq!(offense_count(&unqualified_enable), 1);
1681
+
1682
+ // A name no cop carries stays what it was written as and switches nothing off.
1683
+ let unknown = format!("# rubocop:disable NotACop\n{FIRST_LOOP}");
1684
+ assert!(suppression_of(&unknown, "Style/For", 2).is_none());
1685
+ assert_eq!(offense_count(&unknown), 1);
1686
+ }
1687
+
1688
+ /// `parser` strips the byte order mark before it lexes, so upstream's directive comment starts
1689
+ /// at its `#` and its line holds no code. The mark is not in `White_Space`, so `trim` left it
1690
+ /// behind and the directive read as one written behind prose -- covering line one alone.
1691
+ #[test]
1692
+ fn a_byte_order_mark_leaves_a_first_line_directive_block_form() {
1693
+ let text = format!(
1694
+ "{}# rubocop:disable Style/For\n{FIRST_LOOP}",
1695
+ crate::source::BYTE_ORDER_MARK
1696
+ );
1697
+ assert!(suppression_of(&text, "Style/For", 2).is_some());
1698
+ assert_eq!(offense_count(&text), 0);
1699
+ }
1700
+
1701
+ /// The mark does not make line one hold code for `CommentConfig` either, which is the model
1702
+ /// the cops that review directives read.
1703
+ #[test]
1704
+ fn a_byte_order_mark_leaves_the_first_line_comment_only() {
1705
+ let text = format!(
1706
+ "{}# rubocop:disable Style/For\n{FIRST_LOOP}",
1707
+ crate::source::BYTE_ORDER_MARK
1708
+ );
1709
+ let source = SourceFile::new("test.rb", text);
1710
+ assert!(!super::lines_with_code(&source, &comment_ranges(&source)).contains(&1));
1711
+ }
1712
+
1713
+ /// `exclude_lint_department_cops`: the cop that reads directives is out of `all` and out of
1714
+ /// the `Lint` department, and `Lint/Syntax` is out of every directive there is.
1715
+ #[test]
1716
+ fn the_two_undisableable_cops_stay_out_of_all_and_of_their_department() {
1717
+ for directive in ["all", "Lint"] {
1718
+ let text = format!("# rubocop:disable {directive}\nx = 1\n");
1719
+ assert!(suppression_of(&text, "Lint/RedundantCopDisableDirective", 2).is_none());
1720
+ assert!(suppression_of(&text, "Lint/Syntax", 2).is_none());
1721
+ assert!(suppression_of(&text, "Lint/UselessAssignment", 2).is_some());
1722
+ }
1723
+ let named = "# rubocop:disable Lint/RedundantCopDisableDirective\nx = 1\n";
1724
+ assert!(suppression_of(named, "Lint/RedundantCopDisableDirective", 2).is_some());
1725
+ }
1726
+
1727
+ /// The other half of `exclude_lint_department_cops`: a later `all` or `Lint` directive does not
1728
+ /// reach those two either, so what a directive that named one of them outright left behind
1729
+ /// stands. Superseding the cops a broad directive expands into must not take them with it.
1730
+ #[test]
1731
+ fn a_broad_directive_does_not_supersede_the_two_cops_it_cannot_reach() {
1732
+ for directive in [
1733
+ "# rubocop:enable Lint",
1734
+ "# rubocop:disable all",
1735
+ "# rubocop:enable all",
1736
+ ] {
1737
+ let text = format!(
1738
+ "# rubocop:disable Lint/RedundantCopDisableDirective\n{directive}\nx = 1\n"
1739
+ );
1740
+ assert!(
1741
+ suppression_of(&text, "Lint/RedundantCopDisableDirective", 3).is_some(),
1742
+ "{directive} closed a range it does not reach"
1743
+ );
1744
+ }
1745
+ // Spelling the cop out does close it.
1746
+ let closed = "# rubocop:disable Lint/RedundantCopDisableDirective\n\
1747
+ # rubocop:enable Lint/RedundantCopDisableDirective\n\
1748
+ x = 1\n";
1749
+ assert!(suppression_of(closed, "Lint/RedundantCopDisableDirective", 3).is_none());
1750
+ }
1430
1751
  }