rubocop 1.0.0 → 1.1.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 (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