rubocop 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -5
  3. data/config/default.yml +52 -3
  4. data/lib/rubocop.rb +8 -0
  5. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  6. data/lib/rubocop/comment_config.rb +1 -1
  7. data/lib/rubocop/cop/bundler/duplicated_gem.rb +23 -3
  8. data/lib/rubocop/cop/commissioner.rb +9 -9
  9. data/lib/rubocop/cop/corrector.rb +3 -1
  10. data/lib/rubocop/cop/force.rb +1 -1
  11. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  12. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  13. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -2
  14. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  15. data/lib/rubocop/cop/lint/debugger.rb +2 -3
  16. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +77 -0
  17. data/lib/rubocop/cop/lint/empty_block.rb +46 -0
  18. data/lib/rubocop/cop/lint/flip_flop.rb +8 -2
  19. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +17 -3
  20. data/lib/rubocop/cop/lint/number_conversion.rb +46 -13
  21. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +27 -8
  22. data/lib/rubocop/cop/lint/to_enum_arguments.rb +95 -0
  23. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +185 -0
  24. data/lib/rubocop/cop/lint/useless_access_modifier.rb +2 -2
  25. data/lib/rubocop/cop/mixin/line_length_help.rb +1 -1
  26. data/lib/rubocop/cop/naming/predicate_name.rb +2 -1
  27. data/lib/rubocop/cop/offense.rb +3 -3
  28. data/lib/rubocop/cop/style/arguments_forwarding.rb +142 -0
  29. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +67 -0
  30. data/lib/rubocop/cop/style/multiple_comparison.rb +54 -7
  31. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +7 -1
  32. data/lib/rubocop/cop/style/semicolon.rb +3 -0
  33. data/lib/rubocop/cop/style/swap_values.rb +108 -0
  34. data/lib/rubocop/cop/team.rb +6 -1
  35. data/lib/rubocop/cop/util.rb +1 -1
  36. data/lib/rubocop/ext/regexp_node.rb +10 -7
  37. data/lib/rubocop/ext/regexp_parser.rb +77 -0
  38. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  39. data/lib/rubocop/magic_comment.rb +2 -2
  40. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  41. data/lib/rubocop/version.rb +1 -1
  42. metadata +13 -5
@@ -255,7 +255,7 @@ module RuboCop
255
255
  PATTERN
256
256
  end
257
257
 
258
- send(matcher_name, child)
258
+ public_send(matcher_name, child)
259
259
  end
260
260
  end
261
261
 
@@ -279,7 +279,7 @@ module RuboCop
279
279
  PATTERN
280
280
  end
281
281
 
282
- send(matcher_name, child)
282
+ public_send(matcher_name, child)
283
283
  end
284
284
  end
285
285
  end
@@ -57,7 +57,7 @@ module RuboCop
57
57
  def indentation_difference(line)
58
58
  return 0 unless tab_indentation_width
59
59
 
60
- line.match(/^\t*/)[0].size * (tab_indentation_width - 1)
60
+ (line.index(/[^\t]/) || 0) * (tab_indentation_width - 1)
61
61
  end
62
62
 
63
63
  def tab_indentation_width
@@ -67,7 +67,8 @@ module RuboCop
67
67
  private
68
68
 
69
69
  def allowed_method_name?(method_name, prefix)
70
- !method_name.match?(/^#{prefix}[^0-9]/) ||
70
+ !(method_name.start_with?(prefix) && # cheap check to avoid allocating Regexp
71
+ method_name.match?(/^#{prefix}[^0-9]/)) ||
71
72
  method_name == expected_name(method_name, prefix) ||
72
73
  method_name.end_with?('=') ||
73
74
  allowed_method?(method_name)
@@ -213,7 +213,7 @@ module RuboCop
213
213
  # returns `true` if two offenses contain same attributes
214
214
  def ==(other)
215
215
  COMPARISON_ATTRIBUTES.all? do |attribute|
216
- send(attribute) == other.send(attribute)
216
+ public_send(attribute) == other.public_send(attribute)
217
217
  end
218
218
  end
219
219
 
@@ -221,7 +221,7 @@ module RuboCop
221
221
 
222
222
  def hash
223
223
  COMPARISON_ATTRIBUTES.reduce(0) do |hash, attribute|
224
- hash ^ send(attribute).hash
224
+ hash ^ public_send(attribute).hash
225
225
  end
226
226
  end
227
227
 
@@ -234,7 +234,7 @@ module RuboCop
234
234
  # comparison result
235
235
  def <=>(other)
236
236
  COMPARISON_ATTRIBUTES.each do |attribute|
237
- result = send(attribute) <=> other.send(attribute)
237
+ result = public_send(attribute) <=> other.public_send(attribute)
238
238
  return result unless result.zero?
239
239
  end
240
240
  0
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # In Ruby 2.7, arguments forwarding has been added.
7
+ #
8
+ # This cop identifies places where `do_something(*args, &block)`
9
+ # can be replaced by `do_something(...)`.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # def foo(*args, &block)
14
+ # bar(*args, &block)
15
+ # end
16
+ #
17
+ # # bad
18
+ # def foo(*args, **kwargs, &block)
19
+ # bar(*args, **kwargs, &block)
20
+ # end
21
+ #
22
+ # # good
23
+ # def foo(...)
24
+ # bar(...)
25
+ # end
26
+ #
27
+ # @example AllowOnlyRestArgument: true (default)
28
+ # # good
29
+ # def foo(*args)
30
+ # bar(*args)
31
+ # end
32
+ #
33
+ # @example AllowOnlyRestArgument: false
34
+ # # bad
35
+ # # The following code can replace the arguments with `...`,
36
+ # # but it will change the behavior. Because `...` forwards block also.
37
+ # def foo(*args)
38
+ # bar(*args)
39
+ # end
40
+ #
41
+ class ArgumentsForwarding < Base
42
+ include RangeHelp
43
+ extend AutoCorrector
44
+ extend TargetRubyVersion
45
+
46
+ minimum_target_ruby_version 2.7
47
+
48
+ MSG = 'Use arguments forwarding.'
49
+
50
+ def_node_matcher :use_rest_arguments?, <<~PATTERN
51
+ (args (restarg $_) $...)
52
+ PATTERN
53
+
54
+ def_node_matcher :only_rest_arguments?, <<~PATTERN
55
+ (send _ _ (splat (lvar %1)))
56
+ PATTERN
57
+
58
+ def_node_matcher :forwarding_method_arguments?, <<~PATTERN
59
+ {
60
+ (send _ _
61
+ (splat (lvar %1))
62
+ (block-pass (lvar %2)))
63
+ (send _ _
64
+ (splat (lvar %1))
65
+ (hash (kwsplat (lvar %3)))
66
+ (block-pass (lvar %2)))
67
+ }
68
+ PATTERN
69
+
70
+ def on_def(node)
71
+ return unless node.body
72
+ return unless (rest_args_name, args = use_rest_arguments?(node.arguments))
73
+
74
+ node.each_descendant(:send) do |send_node|
75
+ kwargs_name, block_name = extract_argument_names_from(args)
76
+
77
+ next unless forwarding_method?(send_node, rest_args_name, kwargs_name, block_name) &&
78
+ all_lvars_as_forwarding_method_arguments?(node, send_node)
79
+
80
+ register_offense_to_forwarding_method_arguments(send_node)
81
+ register_offense_to_method_definition_arguments(node)
82
+ end
83
+ end
84
+ alias on_defs on_def
85
+
86
+ private
87
+
88
+ def extract_argument_names_from(args)
89
+ kwargs_name = args.first.source.delete('**') if args.first&.kwrestarg_type?
90
+ block_arg_name = args.last.source.delete('&') if args.last&.blockarg_type?
91
+
92
+ [kwargs_name, block_arg_name].map { |name| name&.to_sym }
93
+ end
94
+
95
+ def forwarding_method?(node, rest_arg, kwargs, block_arg)
96
+ return only_rest_arguments?(node, rest_arg) unless allow_only_rest_arguments?
97
+
98
+ forwarding_method_arguments?(node, rest_arg, block_arg, kwargs)
99
+ end
100
+
101
+ def all_lvars_as_forwarding_method_arguments?(def_node, forwarding_method)
102
+ lvars = def_node.body.each_descendant(:lvar, :lvasgn)
103
+
104
+ begin_pos = forwarding_method.source_range.begin_pos
105
+ end_pos = forwarding_method.source_range.end_pos
106
+
107
+ lvars.all? do |lvar|
108
+ lvar.source_range.begin_pos.between?(begin_pos, end_pos)
109
+ end
110
+ end
111
+
112
+ def register_offense_to_forwarding_method_arguments(forwarding_method)
113
+ add_offense(arguments_range(forwarding_method)) do |corrector|
114
+ range = range_between(
115
+ forwarding_method.loc.selector.end_pos, forwarding_method.source_range.end_pos
116
+ )
117
+ corrector.replace(range, '(...)')
118
+ end
119
+ end
120
+
121
+ def register_offense_to_method_definition_arguments(method_definition)
122
+ add_offense(arguments_range(method_definition)) do |corrector|
123
+ arguments_range = range_with_surrounding_space(
124
+ range: method_definition.arguments.source_range, side: :left
125
+ )
126
+ corrector.replace(arguments_range, '(...)')
127
+ end
128
+ end
129
+
130
+ def arguments_range(node)
131
+ arguments = node.arguments
132
+
133
+ range_between(arguments.first.source_range.begin_pos, arguments.last.source_range.end_pos)
134
+ end
135
+
136
+ def allow_only_rest_arguments?
137
+ cop_config.fetch('AllowOnlyRestArgument', true)
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # When using `class_eval` (or other `eval`) with string interpolation,
7
+ # add a comment block showing its appearance if interpolated (a practice used in Rails code).
8
+ #
9
+ # @example
10
+ # # from activesupport/lib/active_support/core_ext/string/output_safety.rb
11
+ #
12
+ # # bad
13
+ # UNSAFE_STRING_METHODS.each do |unsafe_method|
14
+ # if 'String'.respond_to?(unsafe_method)
15
+ # class_eval <<-EOT, __FILE__, __LINE__ + 1
16
+ # def #{unsafe_method}(*params, &block)
17
+ # to_str.#{unsafe_method}(*params, &block)
18
+ # end
19
+ #
20
+ # def #{unsafe_method}!(*params)
21
+ # @dirty = true
22
+ # super
23
+ # end
24
+ # EOT
25
+ # end
26
+ # end
27
+ #
28
+ # # good
29
+ # UNSAFE_STRING_METHODS.each do |unsafe_method|
30
+ # if 'String'.respond_to?(unsafe_method)
31
+ # class_eval <<-EOT, __FILE__, __LINE__ + 1
32
+ # def #{unsafe_method}(*params, &block) # def capitalize(*params, &block)
33
+ # to_str.#{unsafe_method}(*params, &block) # to_str.capitalize(*params, &block)
34
+ # end # end
35
+ #
36
+ # def #{unsafe_method}!(*params) # def capitalize!(*params)
37
+ # @dirty = true # @dirty = true
38
+ # super # super
39
+ # end # end
40
+ # EOT
41
+ # end
42
+ # end
43
+ #
44
+ class DocumentDynamicEvalDefinition < Base
45
+ MSG = 'Add a comment block showing its appearance if interpolated.'
46
+
47
+ RESTRICT_ON_SEND = %i[eval class_eval module_eval instance_eval].freeze
48
+
49
+ def on_send(node)
50
+ arg_node = node.first_argument
51
+ return unless arg_node&.dstr_type?
52
+
53
+ add_offense(node.loc.selector) unless comment_docs?(arg_node)
54
+ end
55
+
56
+ private
57
+
58
+ def comment_docs?(node)
59
+ node.each_child_node(:begin).all? do |begin_node|
60
+ source_line = processed_source.lines[begin_node.first_line - 1]
61
+ source_line.match?(/\s*#[^{]+/)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -4,7 +4,10 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # This cop checks against comparing a variable with multiple items, where
7
- # `Array#include?` could be used instead to avoid code repetition.
7
+ # `Array#include?`, `Set#include?` or a `case` could be used instead
8
+ # to avoid code repetition.
9
+ # It accepts comparisons of multiple method calls to avoid unnecessary method calls
10
+ # by default. It can be configured by `AllowMethodComparison` option.
8
11
  #
9
12
  # @example
10
13
  # # bad
@@ -14,25 +17,61 @@ module RuboCop
14
17
  # # good
15
18
  # a = 'a'
16
19
  # foo if ['a', 'b', 'c'].include?(a)
20
+ #
21
+ # VALUES = Set['a', 'b', 'c'].freeze
22
+ # # elsewhere...
23
+ # foo if VALUES.include?(a)
24
+ #
25
+ # case foo
26
+ # when 'a', 'b', 'c' then foo
27
+ # # ...
28
+ # end
29
+ #
30
+ # # accepted (but consider `case` as above)
31
+ # foo if a == b.lightweight || a == b.heavyweight
32
+ #
33
+ # @example AllowMethodComparison: true (default)
34
+ # # good
35
+ # foo if a == b.lightweight || a == b.heavyweight
36
+ #
37
+ # @example AllowMethodComparison: false
38
+ # # bad
39
+ # foo if a == b.lightweight || a == b.heavyweight
40
+ #
41
+ # # good
42
+ # foo if [b.lightweight, b.heavyweight].include?(a)
17
43
  class MultipleComparison < Base
44
+ extend AutoCorrector
45
+
18
46
  MSG = 'Avoid comparing a variable with multiple items ' \
19
47
  'in a conditional, use `Array#include?` instead.'
20
48
 
49
+ def on_new_investigation
50
+ @compared_elements = []
51
+ end
52
+
21
53
  def on_or(node)
22
54
  root_of_or_node = root_of_or_node(node)
23
55
 
24
56
  return unless node == root_of_or_node
25
57
  return unless nested_variable_comparison?(root_of_or_node)
26
58
 
27
- add_offense(node)
59
+ add_offense(node) do |corrector|
60
+ elements = @compared_elements.join(', ')
61
+ prefer_method = "[#{elements}].include?(#{variables_in_node(node).first})"
62
+
63
+ corrector.replace(node, prefer_method)
64
+ end
28
65
  end
29
66
 
30
67
  private
31
68
 
32
69
  def_node_matcher :simple_double_comparison?, '(send $lvar :== $lvar)'
33
- def_node_matcher :simple_comparison?, <<~PATTERN
34
- {(send $lvar :== _)
35
- (send _ :== $lvar)}
70
+ def_node_matcher :simple_comparison_lhs?, <<~PATTERN
71
+ (send $lvar :== $_)
72
+ PATTERN
73
+ def_node_matcher :simple_comparison_rhs?, <<~PATTERN
74
+ (send $_ :== $lvar)
36
75
  PATTERN
37
76
 
38
77
  def nested_variable_comparison?(node)
@@ -55,9 +94,13 @@ module RuboCop
55
94
  simple_double_comparison?(node) do |var1, var2|
56
95
  return [variable_name(var1), variable_name(var2)]
57
96
  end
58
- simple_comparison?(node) do |var|
97
+ if (var, obj = simple_comparison_lhs?(node)) || (obj, var = simple_comparison_rhs?(node))
98
+ return [] if allow_method_comparison? && obj.send_type?
99
+
100
+ @compared_elements << obj.source
59
101
  return [variable_name(var)]
60
102
  end
103
+
61
104
  []
62
105
  end
63
106
 
@@ -74,7 +117,7 @@ module RuboCop
74
117
  end
75
118
 
76
119
  def comparison?(node)
77
- simple_comparison?(node) || nested_comparison?(node)
120
+ simple_comparison_lhs?(node) || simple_comparison_rhs?(node) || nested_comparison?(node)
78
121
  end
79
122
 
80
123
  def root_of_or_node(or_node)
@@ -86,6 +129,10 @@ module RuboCop
86
129
  or_node
87
130
  end
88
131
  end
132
+
133
+ def allow_method_comparison?
134
+ cop_config.fetch('AllowMethodComparison', true)
135
+ end
89
136
  end
90
137
  end
91
138
  end
@@ -19,6 +19,12 @@ module RuboCop
19
19
  # # good
20
20
  # r = /\s/
21
21
  #
22
+ # # bad
23
+ # r = %r{/[b]}
24
+ #
25
+ # # good
26
+ # r = %r{/b}
27
+ #
22
28
  # # good
23
29
  # r = /[ab]/
24
30
  class RedundantRegexpCharacterClass < Base
@@ -48,7 +54,7 @@ module RuboCop
48
54
  each_single_element_character_class(node) do |char_class|
49
55
  next unless redundant_single_element_character_class?(node, char_class)
50
56
 
51
- yield node.loc.begin.adjust(begin_pos: 1 + char_class.ts, end_pos: char_class.te)
57
+ yield char_class.loc.body
52
58
  end
53
59
  end
54
60
 
@@ -70,6 +70,9 @@ module RuboCop
70
70
  private
71
71
 
72
72
  def check_for_line_terminator_or_opener
73
+ # Make the obvious check first
74
+ return unless @processed_source.raw_source.include?(';')
75
+
73
76
  each_semicolon { |line, column| convention_on(line, column, true) }
74
77
  end
75
78
 
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop enforces the use of shorthand-style swapping of 2 variables.
7
+ # Its autocorrection is marked as unsafe, because it can erroneously remove
8
+ # the temporary variable which is used later.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # tmp = x
13
+ # x = y
14
+ # y = tmp
15
+ #
16
+ # # good
17
+ # x, y = y, x
18
+ #
19
+ class SwapValues < Base
20
+ include RangeHelp
21
+ extend AutoCorrector
22
+
23
+ MSG = 'Replace this and assignments at lines %<x_line>d '\
24
+ 'and %<y_line>d with `%<replacement>s`.'
25
+
26
+ SIMPLE_ASSIGNMENT_TYPES = %i[lvasgn ivasgn cvasgn gvasgn casgn].to_set.freeze
27
+
28
+ def on_asgn(node)
29
+ return if allowed_assignment?(node)
30
+
31
+ tmp_assign = node
32
+ x_assign, y_assign = *node.right_siblings.take(2)
33
+ return unless x_assign && y_assign && swapping_values?(tmp_assign, x_assign, y_assign)
34
+
35
+ add_offense(node, message: message(x_assign, y_assign)) do |corrector|
36
+ range = correction_range(tmp_assign, y_assign)
37
+ corrector.replace(range, replacement(x_assign))
38
+ end
39
+ end
40
+
41
+ SIMPLE_ASSIGNMENT_TYPES.each { |asgn_type| alias_method :"on_#{asgn_type}", :on_asgn }
42
+
43
+ private
44
+
45
+ def allowed_assignment?(node)
46
+ node.parent&.mlhs_type? || node.parent&.shorthand_asgn?
47
+ end
48
+
49
+ def swapping_values?(tmp_assign, x_assign, y_assign)
50
+ simple_assignment?(tmp_assign) &&
51
+ simple_assignment?(x_assign) &&
52
+ simple_assignment?(y_assign) &&
53
+ lhs(x_assign) == rhs(tmp_assign) &&
54
+ lhs(y_assign) == rhs(x_assign) &&
55
+ rhs(y_assign) == lhs(tmp_assign)
56
+ end
57
+
58
+ def simple_assignment?(node)
59
+ SIMPLE_ASSIGNMENT_TYPES.include?(node.type)
60
+ end
61
+
62
+ def message(x_assign, y_assign)
63
+ format(
64
+ MSG,
65
+ x_line: x_assign.first_line,
66
+ y_line: y_assign.first_line,
67
+ replacement: replacement(x_assign)
68
+ )
69
+ end
70
+
71
+ def replacement(x_assign)
72
+ x = lhs(x_assign)
73
+ y = rhs(x_assign)
74
+ "#{x}, #{y} = #{y}, #{x}"
75
+ end
76
+
77
+ def lhs(node)
78
+ case node.type
79
+ when :casgn
80
+ namespace, name, = *node
81
+ if namespace
82
+ "#{namespace.const_name}::#{name}"
83
+ else
84
+ name.to_s
85
+ end
86
+ else
87
+ node.children[0].to_s
88
+ end
89
+ end
90
+
91
+ def rhs(node)
92
+ case node.type
93
+ when :casgn
94
+ node.children[2].source
95
+ else
96
+ node.children[1].source
97
+ end
98
+ end
99
+
100
+ def correction_range(tmp_assign, y_assign)
101
+ range_by_whole_lines(
102
+ range_between(tmp_assign.source_range.begin_pos, y_assign.source_range.end_pos)
103
+ )
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end