rubocop 0.85.0 → 0.88.0

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 (215) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +25 -17
  3. data/bin/rubocop-profile +31 -0
  4. data/config/default.yml +132 -11
  5. data/lib/rubocop.rb +17 -1
  6. data/lib/rubocop/cli.rb +2 -4
  7. data/lib/rubocop/cli/command/auto_genenerate_config.rb +42 -7
  8. data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
  9. data/lib/rubocop/cli/command/show_cops.rb +1 -1
  10. data/lib/rubocop/config.rb +1 -1
  11. data/lib/rubocop/config_loader.rb +39 -67
  12. data/lib/rubocop/config_loader_resolver.rb +1 -1
  13. data/lib/rubocop/config_obsoletion.rb +0 -1
  14. data/lib/rubocop/config_store.rb +4 -0
  15. data/lib/rubocop/cop/autocorrect_logic.rb +14 -24
  16. data/lib/rubocop/cop/badge.rb +1 -1
  17. data/lib/rubocop/cop/base.rb +407 -0
  18. data/lib/rubocop/cop/bundler/insecure_protocol_source.rb +10 -20
  19. data/lib/rubocop/cop/commissioner.rb +48 -50
  20. data/lib/rubocop/cop/cop.rb +91 -235
  21. data/lib/rubocop/cop/corrector.rb +38 -115
  22. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +26 -0
  23. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +7 -2
  24. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +1 -1
  25. data/lib/rubocop/cop/generator.rb +1 -1
  26. data/lib/rubocop/cop/generator/configuration_injector.rb +1 -1
  27. data/lib/rubocop/cop/internal_affairs/node_type_predicate.rb +11 -14
  28. data/lib/rubocop/cop/layout/case_indentation.rb +18 -19
  29. data/lib/rubocop/cop/layout/class_structure.rb +2 -37
  30. data/lib/rubocop/cop/layout/comment_indentation.rb +3 -3
  31. data/lib/rubocop/cop/layout/empty_comment.rb +1 -1
  32. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  33. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +3 -8
  34. data/lib/rubocop/cop/layout/end_alignment.rb +3 -2
  35. data/lib/rubocop/cop/layout/end_of_line.rb +1 -1
  36. data/lib/rubocop/cop/layout/first_argument_indentation.rb +5 -1
  37. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +1 -1
  38. data/lib/rubocop/cop/layout/hash_alignment.rb +2 -3
  39. data/lib/rubocop/cop/layout/heredoc_indentation.rb +1 -1
  40. data/lib/rubocop/cop/layout/multiline_block_layout.rb +17 -7
  41. data/lib/rubocop/cop/layout/space_after_colon.rb +1 -1
  42. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +22 -27
  43. data/lib/rubocop/cop/layout/space_around_keyword.rb +2 -2
  44. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +27 -68
  45. data/lib/rubocop/cop/layout/space_around_operators.rb +1 -1
  46. data/lib/rubocop/cop/layout/space_before_block_braces.rb +14 -0
  47. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +4 -3
  48. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +1 -1
  49. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +2 -2
  50. data/lib/rubocop/cop/legacy/corrections_proxy.rb +49 -0
  51. data/lib/rubocop/cop/legacy/corrector.rb +29 -0
  52. data/lib/rubocop/cop/lint/constant_resolution.rb +89 -0
  53. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +4 -4
  54. data/lib/rubocop/cop/lint/disjunctive_assignment_in_constructor.rb +8 -2
  55. data/lib/rubocop/cop/lint/duplicate_elsif_condition.rb +39 -0
  56. data/lib/rubocop/cop/lint/duplicate_methods.rb +2 -2
  57. data/lib/rubocop/cop/lint/float_out_of_range.rb +1 -1
  58. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +38 -2
  59. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +3 -2
  60. data/lib/rubocop/cop/lint/interpolation_check.rb +13 -0
  61. data/lib/rubocop/cop/lint/literal_as_condition.rb +11 -1
  62. data/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb +8 -1
  63. data/lib/rubocop/cop/lint/nested_method_definition.rb +14 -20
  64. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +69 -2
  65. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +8 -3
  66. data/lib/rubocop/cop/lint/percent_string_array.rb +1 -1
  67. data/lib/rubocop/cop/lint/percent_symbol_array.rb +1 -1
  68. data/lib/rubocop/cop/lint/raise_exception.rb +12 -4
  69. data/lib/rubocop/cop/lint/rand_one.rb +1 -1
  70. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +31 -25
  71. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +2 -2
  72. data/lib/rubocop/cop/lint/regexp_as_condition.rb +6 -0
  73. data/lib/rubocop/cop/lint/safe_navigation_with_empty.rb +9 -1
  74. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  75. data/lib/rubocop/cop/lint/syntax.rb +11 -26
  76. data/lib/rubocop/cop/lint/unused_method_argument.rb +1 -1
  77. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -1
  78. data/lib/rubocop/cop/metrics/block_length.rb +22 -0
  79. data/lib/rubocop/cop/metrics/class_length.rb +25 -2
  80. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +35 -3
  81. data/lib/rubocop/cop/metrics/method_length.rb +23 -0
  82. data/lib/rubocop/cop/metrics/module_length.rb +25 -2
  83. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +1 -1
  84. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +129 -0
  85. data/lib/rubocop/cop/metrics/utils/iterating_block.rb +61 -0
  86. data/lib/rubocop/cop/mixin/allowed_methods.rb +19 -0
  87. data/lib/rubocop/cop/mixin/auto_corrector.rb +12 -0
  88. data/lib/rubocop/cop/mixin/code_length.rb +4 -0
  89. data/lib/rubocop/cop/mixin/configurable_formatting.rb +1 -1
  90. data/lib/rubocop/cop/mixin/configurable_naming.rb +1 -1
  91. data/lib/rubocop/cop/mixin/documentation_comment.rb +2 -2
  92. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -1
  93. data/lib/rubocop/cop/mixin/enforce_superclass.rb +3 -1
  94. data/lib/rubocop/cop/mixin/first_element_line_break.rb +1 -1
  95. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
  96. data/lib/rubocop/cop/mixin/nil_methods.rb +3 -5
  97. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +6 -1
  98. data/lib/rubocop/cop/mixin/parentheses.rb +1 -2
  99. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +1 -1
  100. data/lib/rubocop/cop/mixin/range_help.rb +1 -1
  101. data/lib/rubocop/cop/mixin/regexp_literal_help.rb +27 -0
  102. data/lib/rubocop/cop/mixin/statement_modifier.rb +3 -3
  103. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
  104. data/lib/rubocop/cop/mixin/surrounding_space.rb +10 -5
  105. data/lib/rubocop/cop/mixin/too_many_lines.rb +3 -13
  106. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  107. data/lib/rubocop/cop/mixin/uncommunicative_name.rb +6 -4
  108. data/lib/rubocop/cop/mixin/visibility_help.rb +50 -0
  109. data/lib/rubocop/cop/naming/ascii_identifiers.rb +27 -4
  110. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +3 -3
  111. data/lib/rubocop/cop/naming/file_name.rb +1 -3
  112. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +1 -1
  113. data/lib/rubocop/cop/naming/method_name.rb +1 -1
  114. data/lib/rubocop/cop/naming/method_parameter_name.rb +1 -1
  115. data/lib/rubocop/cop/naming/predicate_name.rb +3 -5
  116. data/lib/rubocop/cop/naming/variable_name.rb +1 -1
  117. data/lib/rubocop/cop/naming/variable_number.rb +1 -1
  118. data/lib/rubocop/cop/offense.rb +16 -2
  119. data/lib/rubocop/cop/registry.rb +62 -7
  120. data/lib/rubocop/cop/style/accessor_grouping.rb +147 -0
  121. data/lib/rubocop/cop/style/array_coercion.rb +63 -0
  122. data/lib/rubocop/cop/style/auto_resource_cleanup.rb +3 -2
  123. data/lib/rubocop/cop/style/bare_percent_literals.rb +1 -1
  124. data/lib/rubocop/cop/style/bisected_attr_accessor.rb +146 -0
  125. data/lib/rubocop/cop/style/block_delimiters.rb +2 -4
  126. data/lib/rubocop/cop/style/case_like_if.rb +217 -0
  127. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -1
  128. data/lib/rubocop/cop/style/class_vars.rb +21 -0
  129. data/lib/rubocop/cop/style/command_literal.rb +1 -1
  130. data/lib/rubocop/cop/style/commented_keyword.rb +5 -2
  131. data/lib/rubocop/cop/style/conditional_assignment.rb +2 -2
  132. data/lib/rubocop/cop/style/copyright.rb +3 -3
  133. data/lib/rubocop/cop/style/date_time.rb +1 -1
  134. data/lib/rubocop/cop/style/dir.rb +2 -2
  135. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +1 -1
  136. data/lib/rubocop/cop/style/documentation.rb +2 -2
  137. data/lib/rubocop/cop/style/empty_case_condition.rb +8 -6
  138. data/lib/rubocop/cop/style/empty_literal.rb +5 -5
  139. data/lib/rubocop/cop/style/encoding.rb +1 -1
  140. data/lib/rubocop/cop/style/expand_path_arguments.rb +2 -2
  141. data/lib/rubocop/cop/style/exponential_notation.rb +8 -10
  142. data/lib/rubocop/cop/style/float_division.rb +7 -10
  143. data/lib/rubocop/cop/style/format_string_token.rb +5 -5
  144. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +1 -1
  145. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +62 -0
  146. data/lib/rubocop/cop/style/hash_like_case.rb +76 -0
  147. data/lib/rubocop/cop/style/identical_conditional_branches.rb +1 -1
  148. data/lib/rubocop/cop/style/if_inside_else.rb +1 -1
  149. data/lib/rubocop/cop/style/if_unless_modifier.rb +11 -11
  150. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +12 -0
  151. data/lib/rubocop/cop/style/inverse_methods.rb +1 -1
  152. data/lib/rubocop/cop/style/ip_addresses.rb +1 -1
  153. data/lib/rubocop/cop/style/missing_else.rb +1 -11
  154. data/lib/rubocop/cop/style/multiline_block_chain.rb +10 -1
  155. data/lib/rubocop/cop/style/multiline_if_then.rb +1 -1
  156. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +17 -6
  157. data/lib/rubocop/cop/style/mutable_constant.rb +4 -4
  158. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +2 -5
  159. data/lib/rubocop/cop/style/nested_ternary_operator.rb +27 -0
  160. data/lib/rubocop/cop/style/next.rb +2 -2
  161. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +2 -2
  162. data/lib/rubocop/cop/style/numeric_predicate.rb +3 -4
  163. data/lib/rubocop/cop/style/parallel_assignment.rb +3 -3
  164. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +1 -1
  165. data/lib/rubocop/cop/style/proc.rb +1 -1
  166. data/lib/rubocop/cop/style/random_with_offset.rb +4 -10
  167. data/lib/rubocop/cop/style/redundant_assignment.rb +117 -0
  168. data/lib/rubocop/cop/style/redundant_conditional.rb +4 -3
  169. data/lib/rubocop/cop/style/redundant_exception.rb +14 -10
  170. data/lib/rubocop/cop/style/redundant_fetch_block.rb +122 -0
  171. data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +50 -0
  172. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -1
  173. data/lib/rubocop/cop/style/redundant_parentheses.rb +8 -2
  174. data/lib/rubocop/cop/style/redundant_percent_q.rb +2 -2
  175. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +4 -3
  176. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +14 -23
  177. data/lib/rubocop/cop/style/redundant_self.rb +6 -9
  178. data/lib/rubocop/cop/style/redundant_sort.rb +3 -2
  179. data/lib/rubocop/cop/style/rescue_standard_error.rb +1 -1
  180. data/lib/rubocop/cop/style/sample.rb +1 -1
  181. data/lib/rubocop/cop/style/semicolon.rb +1 -1
  182. data/lib/rubocop/cop/style/signal_exception.rb +1 -1
  183. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +3 -2
  184. data/lib/rubocop/cop/style/stderr_puts.rb +1 -1
  185. data/lib/rubocop/cop/style/struct_inheritance.rb +23 -2
  186. data/lib/rubocop/cop/style/symbol_array.rb +5 -5
  187. data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
  188. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  189. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +9 -32
  190. data/lib/rubocop/cop/style/trivial_accessors.rb +8 -7
  191. data/lib/rubocop/cop/style/word_array.rb +1 -1
  192. data/lib/rubocop/cop/style/yoda_condition.rb +18 -1
  193. data/lib/rubocop/cop/style/zero_length_predicate.rb +2 -2
  194. data/lib/rubocop/cop/team.rb +105 -81
  195. data/lib/rubocop/cop/util.rb +2 -2
  196. data/lib/rubocop/cop/utils/format_string.rb +19 -2
  197. data/lib/rubocop/cop/variable_force/variable.rb +5 -3
  198. data/lib/rubocop/file_finder.rb +12 -12
  199. data/lib/rubocop/formatter/disabled_config_formatter.rb +2 -2
  200. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  201. data/lib/rubocop/name_similarity.rb +7 -3
  202. data/lib/rubocop/options.rb +15 -8
  203. data/lib/rubocop/path_util.rb +4 -19
  204. data/lib/rubocop/platform.rb +1 -1
  205. data/lib/rubocop/rake_task.rb +6 -9
  206. data/lib/rubocop/result_cache.rb +12 -8
  207. data/lib/rubocop/rspec/cop_helper.rb +4 -4
  208. data/lib/rubocop/rspec/expect_offense.rb +65 -21
  209. data/lib/rubocop/rspec/shared_contexts.rb +19 -16
  210. data/lib/rubocop/runner.rb +34 -33
  211. data/lib/rubocop/target_finder.rb +3 -3
  212. data/lib/rubocop/target_ruby.rb +2 -2
  213. data/lib/rubocop/version.rb +1 -1
  214. metadata +34 -9
  215. data/lib/rubocop/cop/mixin/classish_length.rb +0 -37
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop enforces the use of `Array()` instead of explicit `Array` check or `[*var]`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # paths = [paths] unless paths.is_a?(Array)
11
+ # paths.each { |path| do_something(path) }
12
+ #
13
+ # # bad (always creates a new Array instance)
14
+ # [*paths].each { |path| do_something(path) }
15
+ #
16
+ # # good (and a bit more readable)
17
+ # Array(paths).each { |path| do_something(path) }
18
+ #
19
+ class ArrayCoercion < Base
20
+ extend AutoCorrector
21
+
22
+ SPLAT_MSG = 'Use `Array(%<arg>s)` instead of `[*%<arg>s]`.'
23
+ CHECK_MSG = 'Use `Array(%<arg>s)` instead of explicit `Array` check.'
24
+
25
+ def_node_matcher :array_splat?, <<~PATTERN
26
+ (array (splat $_))
27
+ PATTERN
28
+
29
+ def_node_matcher :unless_array?, <<~PATTERN
30
+ (if
31
+ (send
32
+ (lvar $_) :is_a?
33
+ (const nil? :Array)) nil?
34
+ (lvasgn $_
35
+ (array
36
+ (lvar $_))))
37
+ PATTERN
38
+
39
+ def on_array(node)
40
+ return unless node.square_brackets?
41
+
42
+ array_splat?(node) do |arg_node|
43
+ message = format(SPLAT_MSG, arg: arg_node.source)
44
+ add_offense(node, message: message) do |corrector|
45
+ corrector.replace(node, "Array(#{arg_node.source})")
46
+ end
47
+ end
48
+ end
49
+
50
+ def on_if(node)
51
+ unless_array?(node) do |var_a, var_b, var_c|
52
+ if var_a == var_b && var_c == var_b
53
+ message = format(CHECK_MSG, arg: var_a)
54
+ add_offense(node, message: message) do |corrector|
55
+ corrector.replace(node, "#{var_a} = Array(#{var_a})")
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -25,10 +25,11 @@ module RuboCop
25
25
 
26
26
  def on_send(node)
27
27
  TARGET_METHODS.each do |target_class, target_method|
28
- target_receiver = s(:const, nil, target_class)
28
+ next if node.method_name != target_method
29
29
 
30
+ target_receiver = s(:const, nil, target_class)
30
31
  next if node.receiver != target_receiver
31
- next if node.method_name != target_method
32
+
32
33
  next if cleanup?(node)
33
34
 
34
35
  add_offense(node,
@@ -60,7 +60,7 @@ module RuboCop
60
60
  end
61
61
 
62
62
  def requires_percent_q?(source)
63
- style == :percent_q && source =~ /^%[^\w]/
63
+ style == :percent_q && /^%[^\w]/.match?(source)
64
64
  end
65
65
 
66
66
  def requires_bare_percent?(source)
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Style
8
+ # This cop checks for places where `attr_reader` and `attr_writer`
9
+ # for the same method can be combined into single `attr_accessor`.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # class Foo
14
+ # attr_reader :bar
15
+ # attr_writer :bar
16
+ # end
17
+ #
18
+ # # good
19
+ # class Foo
20
+ # attr_accessor :bar
21
+ # end
22
+ #
23
+ class BisectedAttrAccessor < Cop
24
+ include VisibilityHelp
25
+
26
+ MSG = 'Combine both accessors into `attr_accessor %<name>s`.'
27
+
28
+ def on_class(class_node)
29
+ VISIBILITY_SCOPES.each do |visibility|
30
+ reader_names, writer_names = accessor_names(class_node, visibility)
31
+ next unless reader_names && writer_names
32
+
33
+ accessor_macroses(class_node, visibility).each do |macro|
34
+ check(macro, reader_names, writer_names)
35
+ end
36
+ end
37
+ end
38
+ alias on_sclass on_class
39
+ alias on_module on_class
40
+
41
+ def autocorrect(node)
42
+ macro = node.parent
43
+
44
+ lambda do |corrector|
45
+ corrector.replace(macro, replacement(macro, node))
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def accessor_names(class_node, visibility)
52
+ reader_names = nil
53
+ writer_names = nil
54
+
55
+ accessor_macroses(class_node, visibility).each do |macro|
56
+ names = macro.arguments.map(&:source)
57
+
58
+ names.each do |name|
59
+ if attr_reader?(macro)
60
+ (reader_names ||= Set.new).add(name)
61
+ else
62
+ (writer_names ||= Set.new).add(name)
63
+ end
64
+ end
65
+ end
66
+
67
+ [reader_names, writer_names]
68
+ end
69
+
70
+ def accessor_macroses(class_node, visibility)
71
+ class_def = class_node.body
72
+ return [] if !class_def || class_def.def_type?
73
+
74
+ send_nodes =
75
+ if class_def.send_type?
76
+ [class_def]
77
+ else
78
+ class_def.each_child_node(:send)
79
+ end
80
+
81
+ send_nodes.select { |node| attr_within_visibility_scope?(node, visibility) }
82
+ end
83
+
84
+ def attr_within_visibility_scope?(node, visibility)
85
+ node.macro? &&
86
+ (attr_reader?(node) || attr_writer?(node)) &&
87
+ node_visibility(node) == visibility
88
+ end
89
+
90
+ def attr_reader?(send_node)
91
+ send_node.method?(:attr_reader) || send_node.method?(:attr)
92
+ end
93
+
94
+ def attr_writer?(send_node)
95
+ send_node.method?(:attr_writer)
96
+ end
97
+
98
+ def check(macro, reader_names, writer_names)
99
+ macro.arguments.each do |arg_node|
100
+ name = arg_node.source
101
+
102
+ if (attr_reader?(macro) && writer_names.include?(name)) ||
103
+ (attr_writer?(macro) && reader_names.include?(name))
104
+ add_offense(arg_node, message: format(MSG, name: name))
105
+ end
106
+ end
107
+ end
108
+
109
+ def replacement(macro, node)
110
+ class_node = macro.each_ancestor(:class, :sclass, :module).first
111
+ reader_names, writer_names = accessor_names(class_node, node_visibility(macro))
112
+
113
+ rest_args = rest_args(macro.arguments, reader_names, writer_names)
114
+
115
+ if attr_reader?(macro)
116
+ attr_reader_replacement(macro, node, rest_args)
117
+ elsif rest_args.empty?
118
+ ''
119
+ else
120
+ "#{macro.method_name} #{rest_args.map(&:source).join(', ')}"
121
+ end
122
+ end
123
+
124
+ def rest_args(args, reader_names, writer_names)
125
+ args.reject do |arg|
126
+ name = arg.source
127
+ reader_names.include?(name) && writer_names.include?(name)
128
+ end
129
+ end
130
+
131
+ def attr_reader_replacement(macro, node, rest_args)
132
+ if rest_args.empty?
133
+ "attr_accessor #{node.source}"
134
+ else
135
+ "attr_accessor #{node.source}\n"\
136
+ "#{indent(macro)}#{macro.method_name} #{rest_args.map(&:source).join(', ')}"
137
+ end
138
+ end
139
+
140
+ def indent(node)
141
+ ' ' * node.loc.column
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -245,14 +245,13 @@ module RuboCop
245
245
  end
246
246
 
247
247
  def whitespace_before?(range)
248
- range.source_buffer.source[range.begin_pos - 1, 1] =~ /\s/
248
+ /\s/.match?(range.source_buffer.source[range.begin_pos - 1, 1])
249
249
  end
250
250
 
251
251
  def whitespace_after?(range, length = 1)
252
- range.source_buffer.source[range.begin_pos + length, 1] =~ /\s/
252
+ /\s/.match?(range.source_buffer.source[range.begin_pos + length, 1])
253
253
  end
254
254
 
255
- # rubocop:disable Metrics/CyclomaticComplexity
256
255
  def get_blocks(node, &block)
257
256
  case node.type
258
257
  when :block
@@ -270,7 +269,6 @@ module RuboCop
270
269
  node.each_child_node { |child| get_blocks(child, &block) }
271
270
  end
272
271
  end
273
- # rubocop:enable Metrics/CyclomaticComplexity
274
272
 
275
273
  def proper_block_style?(node)
276
274
  return special_method_proper_block_style?(node) if special_method?(node.method_name)
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop identifies places where `if-elsif` constructions
7
+ # can be replaced with `case-when`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # if status == :active
12
+ # perform_action
13
+ # elsif status == :inactive || status == :hibernating
14
+ # check_timeout
15
+ # else
16
+ # final_action
17
+ # end
18
+ #
19
+ # # good
20
+ # case status
21
+ # when :active
22
+ # perform_action
23
+ # when :inactive, :hibernating
24
+ # check_timeout
25
+ # else
26
+ # final_action
27
+ # end
28
+ #
29
+ class CaseLikeIf < Cop
30
+ include RangeHelp
31
+
32
+ MSG = 'Convert `if-elsif` to `case-when`.'
33
+
34
+ def on_if(node)
35
+ return unless should_check?(node)
36
+
37
+ target = find_target(node.condition)
38
+ return unless target
39
+
40
+ conditions = []
41
+ convertible = true
42
+
43
+ branch_conditions(node).each do |branch_condition|
44
+ conditions << []
45
+ convertible = collect_conditions(branch_condition, target, conditions.last)
46
+ break unless convertible
47
+ end
48
+
49
+ add_offense(node) if convertible
50
+ end
51
+
52
+ def autocorrect(node)
53
+ target = find_target(node.condition)
54
+
55
+ lambda do |corrector|
56
+ corrector.insert_before(node, "case #{target.source}\n#{indent(node)}")
57
+
58
+ branch_conditions(node).each do |branch_condition|
59
+ conditions = []
60
+ collect_conditions(branch_condition, target, conditions)
61
+
62
+ range = correction_range(branch_condition)
63
+ branch_replacement = "when #{conditions.map(&:source).join(', ')}"
64
+ corrector.replace(range, branch_replacement)
65
+ end
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def should_check?(node)
72
+ !node.unless? && !node.elsif? && !node.modifier_form? && !node.ternary? &&
73
+ node.elsif_conditional?
74
+ end
75
+
76
+ # rubocop:disable Metrics/MethodLength
77
+ def find_target(node)
78
+ case node.type
79
+ when :begin
80
+ find_target(node.children.first)
81
+ when :or
82
+ find_target(node.lhs)
83
+ when :match_with_lvasgn
84
+ lhs, rhs = *node
85
+ if lhs.regexp_type?
86
+ rhs
87
+ elsif rhs.regexp_type?
88
+ lhs
89
+ end
90
+ when :send
91
+ find_target_in_send_node(node)
92
+ end
93
+ end
94
+ # rubocop:enable Metrics/MethodLength
95
+
96
+ def find_target_in_send_node(node)
97
+ case node.method_name
98
+ when :is_a?
99
+ node.receiver
100
+ when :==, :eql?, :equal?
101
+ find_target_in_equality_node(node)
102
+ when :===
103
+ node.arguments.first
104
+ when :include?, :cover?
105
+ receiver = deparenthesize(node.receiver)
106
+ node.arguments.first if receiver.range_type?
107
+ when :match, :match?
108
+ find_target_in_match_node(node)
109
+ end
110
+ end
111
+
112
+ def find_target_in_equality_node(node)
113
+ argument = node.arguments.first
114
+ receiver = node.receiver
115
+
116
+ if argument.literal? || const_reference?(argument)
117
+ receiver
118
+ elsif receiver.literal? || const_reference?(receiver)
119
+ argument
120
+ end
121
+ end
122
+
123
+ def find_target_in_match_node(node)
124
+ argument = node.arguments.first
125
+ receiver = node.receiver
126
+
127
+ if receiver.regexp_type?
128
+ argument
129
+ elsif argument.regexp_type?
130
+ receiver
131
+ end
132
+ end
133
+
134
+ def collect_conditions(node, target, conditions)
135
+ condition =
136
+ case node.type
137
+ when :begin
138
+ return collect_conditions(node.children.first, target, conditions)
139
+ when :or
140
+ return collect_conditions(node.lhs, target, conditions) &&
141
+ collect_conditions(node.rhs, target, conditions)
142
+ when :match_with_lvasgn
143
+ lhs, rhs = *node
144
+ condition_from_binary_op(lhs, rhs, target)
145
+ when :send
146
+ condition_from_send_node(node, target)
147
+ end
148
+
149
+ conditions << condition if condition
150
+ end
151
+
152
+ # rubocop:disable Metrics/AbcSize
153
+ # rubocop:disable Metrics/CyclomaticComplexity
154
+ def condition_from_send_node(node, target)
155
+ case node.method_name
156
+ when :is_a?
157
+ node.arguments.first if node.receiver == target
158
+ when :==, :eql?, :equal?, :=~, :match, :match?
159
+ lhs, _method, rhs = *node
160
+ condition_from_binary_op(lhs, rhs, target)
161
+ when :===
162
+ lhs, _method, rhs = *node
163
+ lhs if rhs == target
164
+ when :include?, :cover?
165
+ receiver = deparenthesize(node.receiver)
166
+ receiver if receiver.range_type? && node.arguments.first == target
167
+ end
168
+ end
169
+ # rubocop:enable Metrics/CyclomaticComplexity
170
+ # rubocop:enable Metrics/AbcSize
171
+
172
+ def condition_from_binary_op(lhs, rhs, target)
173
+ lhs = deparenthesize(lhs)
174
+ rhs = deparenthesize(rhs)
175
+
176
+ if lhs == target
177
+ rhs
178
+ elsif rhs == target
179
+ lhs
180
+ end
181
+ end
182
+
183
+ def branch_conditions(node)
184
+ conditions = []
185
+ while node&.if_type?
186
+ conditions << node.condition
187
+ node = node.else_branch
188
+ end
189
+ conditions
190
+ end
191
+
192
+ def const_reference?(node)
193
+ return false unless node.const_type?
194
+
195
+ name = node.children[1].to_s
196
+
197
+ # We can no be sure if, e.g. `C`, represents a constant or a class reference
198
+ name.length > 1 &&
199
+ name == name.upcase
200
+ end
201
+
202
+ def deparenthesize(node)
203
+ node = node.children.last while node.begin_type?
204
+ node
205
+ end
206
+
207
+ def correction_range(node)
208
+ range_between(node.parent.loc.keyword.begin_pos, node.loc.expression.end_pos)
209
+ end
210
+
211
+ def indent(node)
212
+ ' ' * node.loc.column
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end