rubocop 0.31.0 → 0.32.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubocop might be problematic. Click here for more details.

Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +13 -0
  4. data/config/disabled.yml +4 -0
  5. data/config/enabled.yml +34 -8
  6. data/lib/rubocop.rb +4 -1
  7. data/lib/rubocop/cop/cop.rb +18 -12
  8. data/lib/rubocop/cop/lint/debugger.rb +7 -1
  9. data/lib/rubocop/cop/lint/duplicate_methods.rb +1 -1
  10. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +10 -0
  11. data/lib/rubocop/cop/lint/nested_method_definition.rb +31 -0
  12. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +9 -0
  13. data/lib/rubocop/cop/lint/unneeded_disable.rb +53 -0
  14. data/lib/rubocop/cop/metrics/class_length.rb +1 -1
  15. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  16. data/lib/rubocop/cop/metrics/parameter_lists.rb +1 -1
  17. data/lib/rubocop/cop/mixin/autocorrect_alignment.rb +1 -1
  18. data/lib/rubocop/cop/mixin/if_node.rb +10 -0
  19. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +8 -1
  20. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +8 -1
  21. data/lib/rubocop/cop/mixin/statement_modifier.rb +2 -5
  22. data/lib/rubocop/cop/mixin/string_help.rb +1 -1
  23. data/lib/rubocop/cop/offense.rb +16 -3
  24. data/lib/rubocop/cop/performance/count.rb +33 -30
  25. data/lib/rubocop/cop/performance/sample.rb +103 -59
  26. data/lib/rubocop/cop/performance/size.rb +2 -1
  27. data/lib/rubocop/cop/rails/time_zone.rb +14 -6
  28. data/lib/rubocop/cop/style/align_hash.rb +7 -3
  29. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +39 -11
  30. data/lib/rubocop/cop/style/case_indentation.rb +18 -4
  31. data/lib/rubocop/cop/style/comment_annotation.rb +22 -7
  32. data/lib/rubocop/cop/style/documentation.rb +11 -5
  33. data/lib/rubocop/cop/style/empty_else.rb +25 -0
  34. data/lib/rubocop/cop/style/if_unless_modifier.rb +1 -5
  35. data/lib/rubocop/cop/style/indentation_width.rb +1 -5
  36. data/lib/rubocop/cop/style/multiline_operation_indentation.rb +12 -10
  37. data/lib/rubocop/cop/style/next.rb +1 -1
  38. data/lib/rubocop/cop/style/parallel_assignment.rb +196 -0
  39. data/lib/rubocop/cop/style/single_line_methods.rb +1 -4
  40. data/lib/rubocop/cop/style/space_inside_string_interpolation.rb +41 -0
  41. data/lib/rubocop/cop/style/struct_inheritance.rb +11 -10
  42. data/lib/rubocop/cop/style/trailing_blank_lines.rb +8 -0
  43. data/lib/rubocop/cop/style/trailing_comma.rb +1 -1
  44. data/lib/rubocop/cop/team.rb +8 -1
  45. data/lib/rubocop/formatter/disabled_config_formatter.rb +2 -1
  46. data/lib/rubocop/formatter/formatter_set.rb +24 -1
  47. data/lib/rubocop/options.rb +4 -0
  48. data/lib/rubocop/processed_source.rb +4 -1
  49. data/lib/rubocop/runner.rb +12 -7
  50. data/lib/rubocop/target_finder.rb +3 -3
  51. data/lib/rubocop/version.rb +1 -1
  52. data/relnotes/v0.32.0.md +139 -0
  53. data/rubocop.gemspec +2 -2
  54. metadata +12 -8
  55. data/lib/rubocop/cop/performance/parallel_assignment.rb +0 -79
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop detects instances of rubocop:disable comments that can be
7
+ # removed without causing any offenses to be reported. It's implemented
8
+ # as a cop in that it inherits from the Cop base class and calls
9
+ # add_offense. The unusual part of its implementation is that it doesn't
10
+ # have any on_* methods or an investigate method. This means that it
11
+ # doesn't take part in the investigation phase when the other cops do
12
+ # their work. Instead, it waits until it's called in a later stage of the
13
+ # execution. The reason it can't be implemented as a normal cop is that
14
+ # it depends on the results of all other cops to do its work.
15
+ class UnneededDisable < Cop
16
+ COP_NAME = 'Lint/UnneededDisable'
17
+
18
+ def check(file, offenses, cop_disabled_line_ranges, comments)
19
+ unneeded_cops = {}
20
+
21
+ cop_disabled_line_ranges[file].each do |cop, line_ranges|
22
+ cop_offenses = offenses.select { |o| o.cop_name == cop }
23
+ line_ranges.each do |line_range|
24
+ comment =
25
+ comments[file].find { |c| c.loc.line == line_range.begin }
26
+ unneeded_cop = find_unneeded(comment, offenses, cop, cop_offenses,
27
+ line_range)
28
+ if unneeded_cop
29
+ unneeded_cops[comment.loc.expression] ||= Set.new
30
+ unneeded_cops[comment.loc.expression].add(unneeded_cop)
31
+ end
32
+ end
33
+ end
34
+
35
+ unneeded_cops.each do |range, cops|
36
+ add_offense(range, range,
37
+ "Unnecessary disabling of #{cops.sort.join(', ')}.")
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def find_unneeded(comment, offenses, cop, cop_offenses, line_range)
44
+ if comment.loc.expression.source =~ /rubocop:disable\s+all\b/
45
+ 'all cops' if offenses.none? { |o| line_range.include?(o.line) }
46
+ elsif cop_offenses.none? { |o| line_range.include?(o.line) }
47
+ cop
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -16,7 +16,7 @@ module RuboCop
16
16
  private
17
17
 
18
18
  def message(length, max_length)
19
- format('Class definition is too long. [%d/%d]', length, max_length)
19
+ format('Class has too many lines. [%d/%d]', length, max_length)
20
20
  end
21
21
  end
22
22
  end
@@ -16,7 +16,7 @@ module RuboCop
16
16
  private
17
17
 
18
18
  def message(length, max_length)
19
- format('Module definition is too long. [%d/%d]', length, max_length)
19
+ format('Module has too many lines. [%d/%d]', length, max_length)
20
20
  end
21
21
  end
22
22
  end
@@ -27,7 +27,7 @@ module RuboCop
27
27
  if count_keyword_args?
28
28
  node.children.size
29
29
  else
30
- node.children.count { |a| a.type != :kwoptarg }
30
+ node.children.count { |a| ![:kwoptarg, :kwarg].include?(a.type) }
31
31
  end
32
32
  end
33
33
 
@@ -75,7 +75,7 @@ module RuboCop
75
75
  end
76
76
 
77
77
  def block_comment_within?(expr)
78
- processed_source.comments.select(&:document?).find do |c|
78
+ processed_source.comments.select(&:document?).any? do |c|
79
79
  within?(c.loc.expression, expr)
80
80
  end
81
81
  end
@@ -22,6 +22,16 @@ module RuboCop
22
22
  def if_else?(node)
23
23
  node.loc.respond_to?(:else) && node.loc.else
24
24
  end
25
+
26
+ def if_node_parts(node)
27
+ case node.loc.keyword.source
28
+ when 'if', 'elsif' then condition, body, else_clause = *node
29
+ when 'unless' then condition, else_clause, body = *node
30
+ else condition, body = *node
31
+ end
32
+
33
+ [condition, body, else_clause]
34
+ end
25
35
  end
26
36
  end
27
37
  end
@@ -11,12 +11,19 @@ module RuboCop
11
11
  processed_source.tokens.each_cons(2) do |t1, t2|
12
12
  next unless kind(t1) && t1.pos.line == t2.pos.line &&
13
13
  t2.pos.column == t1.pos.column + offset &&
14
- ![:tRPAREN, :tRBRACK, :tPIPE].include?(t2.type)
14
+ ![:tRPAREN, :tRBRACK, :tPIPE].include?(t2.type) &&
15
+ !(t2.type == :tRCURLY && space_forbidden_before_rcurly?)
15
16
 
16
17
  add_offense(t1, t1.pos, format(MSG, kind(t1)))
17
18
  end
18
19
  end
19
20
 
21
+ def space_forbidden_before_rcurly?
22
+ cfg = config.for_cop('Style/SpaceInsideBlockBraces')
23
+ style = cfg['Enabled'] ? cfg['EnforcedStyle'] : 'space'
24
+ style == 'no_space'
25
+ end
26
+
20
27
  # The normal offset, i.e., the distance from the punctuation
21
28
  # token where a space should be, is 1.
22
29
  def offset
@@ -10,7 +10,8 @@ module RuboCop
10
10
  def investigate(processed_source)
11
11
  processed_source.tokens.each_cons(2) do |t1, t2|
12
12
  next unless kind(t2) && t1.pos.line == t2.pos.line &&
13
- t2.pos.begin_pos > t1.pos.end_pos
13
+ t2.pos.begin_pos > t1.pos.end_pos &&
14
+ !(t1.type == :tLCURLY && space_required_after_lcurly?)
14
15
  buffer = processed_source.buffer
15
16
  pos_before_punctuation = Parser::Source::Range.new(buffer,
16
17
  t1.pos.end_pos,
@@ -22,6 +23,12 @@ module RuboCop
22
23
  end
23
24
  end
24
25
 
26
+ def space_required_after_lcurly?
27
+ cfg = config.for_cop('Style/SpaceInsideBlockBraces')
28
+ style = cfg['Enabled'] ? cfg['EnforcedStyle'] : 'space'
29
+ style == 'space'
30
+ end
31
+
25
32
  def autocorrect(pos_before_punctuation)
26
33
  ->(corrector) { corrector.remove(pos_before_punctuation) }
27
34
  end
@@ -7,13 +7,10 @@ module RuboCop
7
7
  include IfNode
8
8
 
9
9
  def fit_within_line_as_modifier_form?(node)
10
- case node.loc.keyword.source
11
- when 'if' then cond, body, _else = *node
12
- when 'unless' then cond, _else, body = *node
13
- else cond, body = *node
14
- end
10
+ cond, body, _else = if_node_parts(node)
15
11
 
16
12
  return false if length(node) > 3
13
+ return false if body && body.begin_type? # multiple statements
17
14
 
18
15
  body_length = body_length(body)
19
16
 
@@ -31,7 +31,7 @@ module RuboCop
31
31
  def inside_interpolation?(node)
32
32
  # A :begin node inside a :dstr node is an interpolation.
33
33
  begin_found = false
34
- node.each_ancestor.find do |a|
34
+ node.each_ancestor.any? do |a|
35
35
  begin_found = true if a.type == :begin
36
36
  begin_found && a.type == :dstr
37
37
  end
@@ -56,16 +56,29 @@ module RuboCop
56
56
  #
57
57
  # @return [Boolean]
58
58
  # whether this offense is automatically corrected.
59
- attr_reader :corrected
59
+ def corrected
60
+ @status == :unsupported ? nil : @status == :corrected
61
+ end
60
62
  alias_method :corrected?, :corrected
61
63
 
64
+ # @api public
65
+ #
66
+ # @!attribute [r] disabled?
67
+ #
68
+ # @return [Boolean]
69
+ # whether this offense was locally disabled where it occurred
70
+ def disabled?
71
+ @status == :disabled
72
+ end
73
+
62
74
  # @api private
63
- def initialize(severity, location, message, cop_name, corrected = false)
75
+ def initialize(severity, location, message, cop_name,
76
+ status = :uncorrected)
64
77
  @severity = RuboCop::Cop::Severity.new(severity)
65
78
  @location = location
66
79
  @message = message.freeze
67
80
  @cop_name = cop_name.freeze
68
- @corrected = corrected
81
+ @status = status
69
82
  freeze
70
83
  end
71
84
 
@@ -14,12 +14,15 @@ module RuboCop
14
14
  # [1, 2, 3].reject { |e| e > 2 }.length
15
15
  # [1, 2, 3].select { |e| e > 2 }.count { |e| e.odd? }
16
16
  # [1, 2, 3].reject { |e| e > 2 }.count { |e| e.even? }
17
+ # array.select(&:value).count
17
18
  #
18
19
  # # good
19
20
  # [1, 2, 3].count { |e| e > 2 }
20
21
  # [1, 2, 3].count { |e| e < 2 }
21
22
  # [1, 2, 3].count { |e| e > 2 && e.odd? }
22
23
  # [1, 2, 3].count { |e| e < 2 && e.even? }
24
+ # Model.select('field AS field_one').count
25
+ # Model.select(:value).count
23
26
  class Count < Cop
24
27
  MSG = 'Use `count` instead of `%s...%s`.'
25
28
 
@@ -27,56 +30,56 @@ module RuboCop
27
30
  COUNTERS = [:count, :length, :size]
28
31
 
29
32
  def on_send(node)
30
- expression, first_method, second_method, third_method = parse(node)
31
-
32
- return unless COUNTERS.include?(third_method)
33
-
34
- begin_pos = if SELECTORS.include?(first_method)
35
- return if second_method.is_a?(Symbol)
36
- expression.loc.selector.begin_pos
37
- else
38
- return unless SELECTORS.include?(second_method)
39
- expression.parent.loc.selector.begin_pos
40
- end
41
-
33
+ selector, selector_loc, params, counter = parse(node)
34
+ return unless COUNTERS.include?(counter)
35
+ return unless SELECTORS.include?(selector)
36
+ return if params && !params.block_pass_type?
42
37
  return if node.parent && node.parent.block_type?
43
38
 
44
39
  range = Parser::Source::Range.new(node.loc.expression.source_buffer,
45
- begin_pos,
40
+ selector_loc.begin_pos,
46
41
  node.loc.expression.end_pos)
47
42
 
48
- add_offense(node, range,
49
- format(MSG, first_method || second_method, third_method))
43
+ add_offense(node, range, format(MSG, selector, counter))
50
44
  end
51
45
 
52
46
  def autocorrect(node)
53
- expression, first_method, second_method, = parse(node)
47
+ selector, selector_loc = parse(node)
54
48
 
55
- return if first_method == :reject || second_method == :reject
49
+ return if selector == :reject
56
50
 
57
- selector = if SELECTORS.include?(first_method)
58
- expression.loc.selector
59
- else
60
- expression.parent.loc.selector
61
- end
51
+ range = Parser::Source::Range.new(node.loc.expression.source_buffer,
52
+ node.loc.dot.begin_pos,
53
+ node.loc.expression.end_pos)
62
54
 
63
55
  lambda do |corrector|
64
- range = Parser::Source::Range.new(node.loc.expression.source_buffer,
65
- node.loc.dot.begin_pos,
66
- node.loc.expression.end_pos)
67
56
  corrector.remove(range)
68
- corrector.replace(selector, 'count')
57
+ corrector.replace(selector_loc, 'count')
69
58
  end
70
59
  end
71
60
 
72
61
  private
73
62
 
74
63
  def parse(node)
75
- left, third_method = *node
76
- expression, second_method = *left
77
- _enumerable, first_method = *expression
64
+ left, counter = *node
65
+ expression, selector, params = *left
66
+
67
+ selector_loc =
68
+ if selector.is_a?(Symbol)
69
+ if expression && expression.parent.loc.respond_to?(:selector)
70
+ expression.parent.loc.selector
71
+ end
72
+ else
73
+ _enumerable, selector, params = *expression
74
+
75
+ expression.loc.selector if contains_selector?(expression)
76
+ end
77
+
78
+ [selector, selector_loc, params, counter]
79
+ end
78
80
 
79
- [expression, first_method, second_method, third_method]
81
+ def contains_selector?(node)
82
+ node.respond_to?(:loc) && node.loc.respond_to?(:selector)
80
83
  end
81
84
  end
82
85
  end
@@ -1,89 +1,133 @@
1
- # encoding: utf-8
1
+ # encoding: UTF-8
2
2
 
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop is used to identify usages of `shuffle.first` and
7
- # change them to use `sample` instead.
6
+ # This cop is used to identify usages of `shuffle.first`, `shuffle.last`
7
+ # and `shuffle[]` and change them to use `sample` instead.
8
8
  #
9
9
  # @example
10
10
  # # bad
11
11
  # [1, 2, 3].shuffle.first
12
+ # [1, 2, 3].shuffle.first(2)
12
13
  # [1, 2, 3].shuffle.last
13
- # [1, 2, 3].shuffle[0]
14
- # [1, 2, 3].shuffle[0, 3]
15
- # [1, 2, 3].shuffle(random: Random.new(1))
14
+ # [1, 2, 3].shuffle[2]
15
+ # [1, 2, 3].shuffle[0, 2] # sample(2) will do the same
16
+ # [1, 2, 3].shuffle[0..2] # sample(3) will do the same
17
+ # [1, 2, 3].shuffle(random: Random.new).first
16
18
  #
17
19
  # # good
18
20
  # [1, 2, 3].shuffle
19
21
  # [1, 2, 3].sample
20
22
  # [1, 2, 3].sample(3)
21
- # [1, 2, 3].sample(random: Random.new(1))
23
+ # [1, 2, 3].shuffle[1, 3] # sample(3) might return a longer Array
24
+ # [1, 2, 3].shuffle[1..3] # sample(3) might return a longer Array
25
+ # [1, 2, 3].shuffle[foo, bar]
26
+ # [1, 2, 3].shuffle(random: Random.new)
22
27
  class Sample < Cop
23
- MSG = 'Use `sample` instead of `shuffle%s`.'
24
- RANGE_TYPES = [:irange, :erange]
25
- VALID_ARRAY_SELECTORS = [:first, :last, :[], nil]
28
+ MSG = 'Use `%<correct>s` instead of `%<incorrect>s`.'
26
29
 
27
30
  def on_send(node)
28
- _receiver, first_method, params, = *node
29
- return unless first_method == :shuffle
30
- _receiver, second_method, params, = *node.parent if params.nil?
31
- return unless VALID_ARRAY_SELECTORS.include?(second_method)
32
- return if second_method.nil? && params.nil?
33
-
34
- add_offense(node, range_of_shuffle(node), message(node, params))
31
+ analyzer = ShuffleAnalyzer.new(node)
32
+ return unless analyzer.offensive?
33
+ add_offense(node, analyzer.source_range, analyzer.message)
35
34
  end
36
35
 
37
36
  def autocorrect(node)
38
- _receiver, _method, params, selector = *node
39
- _receiver, _method, params, selector = *node.parent if params.nil?
40
-
41
- return if params && RANGE_TYPES.include?(params.type)
42
-
43
- range = if params && (params.hash_type? || params.lvar_type?)
44
- range_of_shuffle(node)
45
- else
46
- Parser::Source::Range.new(node.loc.expression.source_buffer,
47
- node.loc.selector.begin_pos,
48
- node.parent.loc.selector.end_pos)
49
- end
50
-
51
- lambda do |corrector|
52
- corrector.replace(range, 'sample')
53
- return if selector.nil?
54
- corrector.insert_after(range, "(#{selector.loc.expression.source})")
55
- end
37
+ ShuffleAnalyzer.new(node).autocorrect
56
38
  end
57
39
 
58
- private
59
-
60
- def message(node, params)
61
- if params && params.lvar_type?
62
- format(MSG, shuffle_params(node))
63
- elsif node.parent
64
- _params, selector = *node.parent
65
- if selector == :[]
66
- format(MSG, node.parent.loc.selector.source)
67
- else
68
- format(MSG, ".#{node.parent.loc.selector.source}")
40
+ # An internal class for representing a shuffle + method node analyzer.
41
+ class ShuffleAnalyzer
42
+ def initialize(shuffle_node)
43
+ @shuffle_node = shuffle_node
44
+ @method_node = shuffle_node.parent
45
+ end
46
+
47
+ def autocorrect
48
+ ->(corrector) { corrector.replace(source_range, correct) }
49
+ end
50
+
51
+ def message
52
+ format(MSG, correct: correct, incorrect: source_range.source)
53
+ end
54
+
55
+ def offensive?
56
+ shuffle_node.to_a[1] == :shuffle && corrigible?
57
+ end
58
+
59
+ def source_range
60
+ Parser::Source::Range.new(shuffle_node.loc.expression.source_buffer,
61
+ shuffle_node.loc.selector.begin_pos,
62
+ method_node.loc.expression.end_pos)
63
+ end
64
+
65
+ private
66
+
67
+ attr_reader :method_node, :shuffle_node
68
+
69
+ def correct
70
+ args = [sample_arg, shuffle_arg].compact.join(', ')
71
+ args.empty? ? 'sample' : "sample(#{args})"
72
+ end
73
+
74
+ def corrigible?
75
+ case method
76
+ when :first, :last then true
77
+ when :[] then sample_size != :unknown
78
+ else false
69
79
  end
70
- else
71
- format(MSG, shuffle_params(node))
72
80
  end
73
- end
74
81
 
75
- def range_of_shuffle(node)
76
- Parser::Source::Range.new(node.loc.expression.source_buffer,
77
- node.loc.selector.begin_pos,
78
- node.loc.selector.end_pos)
79
- end
82
+ def method
83
+ method_node.to_a[1]
84
+ end
80
85
 
81
- def shuffle_params(node)
82
- params = Parser::Source::Range.new(node.loc.expression.source_buffer,
83
- node.loc.selector.end_pos,
84
- node.loc.expression.end_pos)
86
+ def method_arg
87
+ _, _, arg = *method_node
88
+ arg.loc.expression.source if arg
89
+ end
90
+
91
+ # FIXME: use Range#size once Ruby 1.9 support is dropped
92
+ def range_size(range_node)
93
+ vals = *range_node
94
+ return :unknown unless vals.all?(&:int_type?)
95
+ low, high = *vals.map(&:to_a).map(&:first)
96
+ return :unknown unless low.zero? && high >= 0
97
+ case range_node.type
98
+ when :erange then high - low
99
+ when :irange then high - low + 1
100
+ end
101
+ end
85
102
 
86
- params.source
103
+ def sample_arg
104
+ case method
105
+ when :first, :last then method_arg
106
+ when :[] then sample_size
107
+ end
108
+ end
109
+
110
+ def sample_size
111
+ _, _, *args = *method_node
112
+ case args.size
113
+ when 1
114
+ arg = args.first
115
+ case arg.type
116
+ when :erange, :irange then range_size(arg)
117
+ when :int then arg.to_a.first.zero? ? nil : :unknown
118
+ else :unknown
119
+ end
120
+ when 2
121
+ first, second = *args
122
+ return :unknown unless first.int_type? && first.to_a.first.zero?
123
+ second.int_type? ? second.to_a.first : :unknown
124
+ end
125
+ end
126
+
127
+ def shuffle_arg
128
+ _, _, arg = *shuffle_node
129
+ arg.loc.expression.source if arg
130
+ end
87
131
  end
88
132
  end
89
133
  end