rubocop 0.32.1 → 0.33.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -0
  3. data/README.md +22 -4
  4. data/config/default.yml +29 -10
  5. data/config/disabled.yml +8 -4
  6. data/config/enabled.yml +40 -1
  7. data/lib/rubocop.rb +8 -0
  8. data/lib/rubocop/cli.rb +1 -0
  9. data/lib/rubocop/config_loader.rb +23 -2
  10. data/lib/rubocop/cop/lint/block_alignment.rb +1 -1
  11. data/lib/rubocop/cop/lint/circular_argument_reference.rb +38 -0
  12. data/lib/rubocop/cop/lint/def_end_alignment.rb +8 -4
  13. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +38 -21
  14. data/lib/rubocop/cop/lint/duplicate_methods.rb +1 -1
  15. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +95 -0
  16. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -1
  17. data/lib/rubocop/cop/mixin/on_method_def.rb +4 -5
  18. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
  19. data/lib/rubocop/cop/performance/count.rb +2 -0
  20. data/lib/rubocop/cop/performance/detect.rb +11 -2
  21. data/lib/rubocop/cop/performance/flat_map.rb +3 -3
  22. data/lib/rubocop/cop/performance/string_replacement.rb +161 -0
  23. data/lib/rubocop/cop/rails/date.rb +8 -8
  24. data/lib/rubocop/cop/rails/time_zone.rb +22 -13
  25. data/lib/rubocop/cop/style/block_delimiters.rb +6 -1
  26. data/lib/rubocop/cop/style/documentation.rb +1 -1
  27. data/lib/rubocop/cop/style/extra_spacing.rb +84 -5
  28. data/lib/rubocop/cop/style/first_parameter_indentation.rb +2 -0
  29. data/lib/rubocop/cop/style/indentation_width.rb +28 -4
  30. data/lib/rubocop/cop/style/initial_indentation.rb +32 -0
  31. data/lib/rubocop/cop/style/method_call_parentheses.rb +20 -1
  32. data/lib/rubocop/cop/style/one_line_conditional.rb +8 -4
  33. data/lib/rubocop/cop/style/option_hash.rb +56 -0
  34. data/lib/rubocop/cop/style/optional_arguments.rb +49 -0
  35. data/lib/rubocop/cop/style/parallel_assignment.rb +3 -0
  36. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +3 -66
  37. data/lib/rubocop/cop/style/redundant_return.rb +20 -3
  38. data/lib/rubocop/cop/style/rescue_ensure_alignment.rb +77 -0
  39. data/lib/rubocop/cop/style/rescue_modifier.rb +4 -28
  40. data/lib/rubocop/cop/style/send.rb +18 -0
  41. data/lib/rubocop/cop/style/space_inside_string_interpolation.rb +32 -13
  42. data/lib/rubocop/cop/style/symbol_literal.rb +1 -1
  43. data/lib/rubocop/cop/style/trivial_accessors.rb +10 -1
  44. data/lib/rubocop/cop/style/while_until_do.rb +1 -1
  45. data/lib/rubocop/cop/style/word_array.rb +13 -1
  46. data/lib/rubocop/formatter/disabled_config_formatter.rb +54 -5
  47. data/lib/rubocop/options.rb +81 -55
  48. data/lib/rubocop/version.rb +1 -1
  49. data/relnotes/v0.33.0.md +157 -0
  50. metadata +11 -2
@@ -12,12 +12,12 @@ module RuboCop
12
12
  # The cop also reports warnings when you are using 'to_time' method,
13
13
  # because it doesn't know about Rails time zone too.
14
14
  #
15
- # Two styles are supported for this cop. When EnforcedStyle is 'always'
15
+ # Two styles are supported for this cop. When EnforcedStyle is 'strict'
16
16
  # then the Date methods (today, current, yesterday, tomorrow)
17
17
  # are prohibited and the usage of both 'to_time'
18
18
  # and 'to_time_in_current_zone' is reported as warning.
19
19
  #
20
- # When EnforcedStyle is 'acceptable' then only 'Date.today' is prohibited
20
+ # When EnforcedStyle is 'flexible' then only 'Date.today' is prohibited
21
21
  # and only 'to_time' is reported as warning.
22
22
  #
23
23
  # @example
@@ -46,9 +46,9 @@ module RuboCop
46
46
  BAD_DAYS = [:today, :current, :yesterday, :tomorrow]
47
47
 
48
48
  def on_const(node)
49
- _, klass = *node.children
50
-
51
- return unless method_send?(node)
49
+ mod, klass = *node.children
50
+ # we should only check core Date class (`Date` or `::Date`)
51
+ return unless (mod.nil? || mod.cbase_type?) && method_send?(node)
52
52
 
53
53
  check_date_node(node.parent) if klass == :Date
54
54
  end
@@ -75,7 +75,7 @@ module RuboCop
75
75
  add_offense(node, :selector,
76
76
  format(MSG,
77
77
  "Date.#{method_name}",
78
- 'Time.zone.today')
78
+ "Time.zone.#{method_name}")
79
79
  )
80
80
  end
81
81
 
@@ -104,7 +104,7 @@ module RuboCop
104
104
  end
105
105
 
106
106
  def good_days
107
- style == :always ? [] : [:current, :yesterday, :tomorrow]
107
+ style == :strict ? [] : [:current, :yesterday, :tomorrow]
108
108
  end
109
109
 
110
110
  def bad_days
@@ -112,7 +112,7 @@ module RuboCop
112
112
  end
113
113
 
114
114
  def bad_methods
115
- style == :always ? [:to_time, :to_time_in_current_zone] : [:to_time]
115
+ style == :strict ? [:to_time, :to_time_in_current_zone] : [:to_time]
116
116
  end
117
117
  end
118
118
  end
@@ -8,10 +8,10 @@ module RuboCop
8
8
  # Built on top of Ruby on Rails style guide (https://github.com/bbatsov/rails-style-guide#time)
9
9
  # and the article http://danilenko.org/2012/7/6/rails_timezones/ .
10
10
  #
11
- # Two styles are supported for this cop. When EnforcedStyle is 'always'
11
+ # Two styles are supported for this cop. When EnforcedStyle is 'strict'
12
12
  # then only use of Time.zone is allowed.
13
13
  #
14
- # When EnforcedStyle is 'acceptable' then it's also allowed
14
+ # When EnforcedStyle is 'flexible' then it's also allowed
15
15
  # to use Time.in_time_zone.
16
16
  #
17
17
  # @example
@@ -24,6 +24,7 @@ module RuboCop
24
24
  # Time.zone.parse('2015-03-02 19:05:37')
25
25
  #
26
26
  # # no offense only if style is 'acceptable'
27
+ # Time.current
27
28
  # DateTime.strptime(str, "%Y-%m-%d %H:%M %Z").in_time_zone
28
29
  # Time.at(timestamp).in_time_zone
29
30
  class TimeZone < Cop
@@ -35,18 +36,22 @@ module RuboCop
35
36
 
36
37
  MSG_LOCALTIME = 'Do not use `Time.localtime` without offset or zone.'
37
38
 
39
+ MSG_CURRENT = 'Do not use `%s`. Use `Time.zone.now` instead.'
40
+
38
41
  TIMECLASS = [:Time, :DateTime]
39
42
 
40
- DANGER_METHODS = [:now, :local, :new, :strftime, :parse, :at]
43
+ DANGEROUS_METHODS = [:now, :local, :new, :strftime,
44
+ :parse, :at, :current]
41
45
 
42
- ACCEPTED_METHODS = [:in_time_zone, :utc, :getlocal,
46
+ ACCEPTED_METHODS = [:current, :in_time_zone, :utc, :getlocal,
43
47
  :iso8601, :jisx0301, :rfc3339,
44
48
  :to_i, :to_f]
45
49
 
46
50
  def on_const(node)
47
- _module, klass = *node
48
-
49
- return unless method_send?(node)
51
+ mod, klass = *node
52
+ # we should only check core class
53
+ # (`DateTime`/`Time` or `::Date`/`::DateTime`)
54
+ return unless (mod.nil? || mod.cbase_type?) && method_send?(node)
50
55
 
51
56
  check_time_node(klass, node.parent) if TIMECLASS.include?(klass)
52
57
  end
@@ -59,7 +64,7 @@ module RuboCop
59
64
 
60
65
  return check_localtime(node) if need_check_localtime?(chain)
61
66
 
62
- method_name = (chain & DANGER_METHODS).join('.')
67
+ method_name = (chain & DANGEROUS_METHODS).join('.')
63
68
 
64
69
  message = build_message(klass, method_name, node)
65
70
 
@@ -72,11 +77,15 @@ module RuboCop
72
77
  "#{klass}.#{method_name}",
73
78
  acceptable_methods(klass, method_name, node).join(', ')
74
79
  )
80
+ elsif method_name == 'current'
81
+ format(MSG_CURRENT,
82
+ "#{klass}.#{method_name}"
83
+ )
75
84
  else
76
85
  safe_method_name = safe_method(method_name, node)
77
86
  format(MSG,
78
87
  "#{klass}.#{method_name}",
79
- "#{klass}.zone.#{safe_method_name}"
88
+ "Time.zone.#{safe_method_name}"
80
89
  )
81
90
  end
82
91
  end
@@ -128,7 +137,7 @@ module RuboCop
128
137
  end
129
138
 
130
139
  def danger_chain?(chain)
131
- (chain & DANGER_METHODS).empty? || !(chain & good_methods).empty?
140
+ (chain & DANGEROUS_METHODS).empty? || !(chain & good_methods).empty?
132
141
  end
133
142
 
134
143
  def need_check_localtime?(chain)
@@ -136,16 +145,16 @@ module RuboCop
136
145
  end
137
146
 
138
147
  def acceptable?
139
- style == :acceptable
148
+ style == :flexible
140
149
  end
141
150
 
142
151
  def good_methods
143
- style == :always ? [:zone] : [:zone] + ACCEPTED_METHODS
152
+ style == :strict ? [:zone] : [:zone] + ACCEPTED_METHODS
144
153
  end
145
154
 
146
155
  def acceptable_methods(klass, method_name, node)
147
156
  acceptable = [
148
- "`#{klass}.zone.#{safe_method(method_name, node)}`"
157
+ "`Time.zone.#{safe_method(method_name, node)}`"
149
158
  ]
150
159
 
151
160
  ACCEPTED_METHODS.each do |am|
@@ -155,7 +155,8 @@ module RuboCop
155
155
  def return_value_of_scope?(node)
156
156
  return unless node.parent
157
157
 
158
- conditional?(node.parent) || node.parent.children.last == node
158
+ conditional?(node.parent) || array_or_range?(node.parent) ||
159
+ node.parent.children.last == node
159
160
  end
160
161
 
161
162
  def procedural_methods
@@ -173,6 +174,10 @@ module RuboCop
173
174
  def conditional?(node)
174
175
  node.if_type? || node.or_type? || node.and_type?
175
176
  end
177
+
178
+ def array_or_range?(node)
179
+ node.array_type? || node.irange_type? || node.erange_type?
180
+ end
176
181
  end
177
182
  end
178
183
  end
@@ -6,7 +6,7 @@ module RuboCop
6
6
  # This cop checks for missing top-level documentation of
7
7
  # classes and modules. Classes with no body are exempt from the
8
8
  # check and so are namespace modules - modules that have nothing in
9
- # their bodies except classes or other other modules.
9
+ # their bodies except classes or other modules.
10
10
  #
11
11
  # The documentation requirement is annulled if the class or module has
12
12
  # a "#:nodoc:" comment next to it. Likewise, "#:nodoc: all" does the
@@ -7,19 +7,29 @@ module RuboCop
7
7
  #
8
8
  # @example
9
9
  #
10
- # name = "RuboCop"
10
+ # # good if AllowForAlignment is true
11
+ # name = "RuboCop"
12
+ # # Some comment and an empty line
13
+ #
14
+ # website += "/bbatsov/rubocop" unless cond
15
+ # puts "rubocop" if debug
16
+ #
17
+ # # bad for any configuration
18
+ # set_app("RuboCop")
11
19
  # website = "https://github.com/bbatsov/rubocop"
12
20
  class ExtraSpacing < Cop
13
21
  MSG = 'Unnecessary spacing detected.'
14
22
 
15
23
  def investigate(processed_source)
16
24
  processed_source.tokens.each_cons(2) do |t1, t2|
17
- next unless t1.pos.line == t2.pos.line
18
- next unless t2.pos.begin_pos - 1 > t1.pos.end_pos
19
- buffer = processed_source.buffer
25
+ next if t2.type == :tNL
26
+ next if t1.pos.line != t2.pos.line
27
+ next if t2.pos.begin_pos - 1 <= t1.pos.end_pos
28
+ next if allow_for_alignment? && aligned_with_something?(t2)
20
29
  start_pos = t1.pos.end_pos
21
30
  end_pos = t2.pos.begin_pos - 1
22
- range = Parser::Source::Range.new(buffer, start_pos, end_pos)
31
+ range = Parser::Source::Range.new(processed_source.buffer,
32
+ start_pos, end_pos)
23
33
  add_offense(range, range, MSG)
24
34
  end
25
35
  end
@@ -27,6 +37,75 @@ module RuboCop
27
37
  def autocorrect(range)
28
38
  ->(corrector) { corrector.remove(range) }
29
39
  end
40
+
41
+ private
42
+
43
+ def allow_for_alignment?
44
+ cop_config['AllowForAlignment']
45
+ end
46
+
47
+ def aligned_with_something?(token)
48
+ return aligned_comments?(token) if token.type == :tCOMMENT
49
+
50
+ pre = (token.pos.line - 2).downto(0)
51
+ post = token.pos.line.upto(processed_source.lines.size - 1)
52
+ aligned_with?(pre, token) || aligned_with?(post, token)
53
+ end
54
+
55
+ def aligned_comments?(token)
56
+ ix = processed_source.comments.index do |c|
57
+ c.loc.expression.begin_pos == token.pos.begin_pos
58
+ end
59
+ aligned_with_previous_comment?(ix) || aligned_with_next_comment?(ix)
60
+ end
61
+
62
+ def aligned_with_previous_comment?(ix)
63
+ ix > 0 && comment_column(ix - 1) == comment_column(ix)
64
+ end
65
+
66
+ def aligned_with_next_comment?(ix)
67
+ ix < processed_source.comments.length - 1 &&
68
+ comment_column(ix + 1) == comment_column(ix)
69
+ end
70
+
71
+ def comment_column(ix)
72
+ processed_source.comments[ix].loc.column
73
+ end
74
+
75
+ def aligned_with?(indices_to_check, token)
76
+ indices_to_check.each do |ix|
77
+ next if comment_lines.include?(ix + 1)
78
+ line = processed_source.lines[ix]
79
+ next if line.strip.empty?
80
+ return (aligned_words?(token, line) ||
81
+ aligned_assignments?(token, line) ||
82
+ aligned_same_character?(token, line))
83
+ end
84
+ false # No line to check was found.
85
+ end
86
+
87
+ def comment_lines
88
+ @comment_lines ||=
89
+ begin
90
+ whole_line_comments = processed_source.comments.select do |c|
91
+ begins_its_line?(c.loc.expression)
92
+ end
93
+ whole_line_comments.map(&:loc).map(&:line)
94
+ end
95
+ end
96
+
97
+ def aligned_words?(token, line)
98
+ line[token.pos.column - 1, 2] =~ /\s\S/
99
+ end
100
+
101
+ def aligned_assignments?(token, line)
102
+ token.type == :tOP_ASGN &&
103
+ line[token.pos.column + token.text.length] == '='
104
+ end
105
+
106
+ def aligned_same_character?(token, line)
107
+ line[token.pos.column] == token.text[0]
108
+ end
30
109
  end
31
110
  end
32
111
  end
@@ -39,6 +39,8 @@ module RuboCop
39
39
  private
40
40
 
41
41
  def message(arg_node)
42
+ return 'Bad indentation of the first parameter.' if arg_node.nil?
43
+
42
44
  send_node = arg_node.parent
43
45
  text = base_range(send_node, arg_node).source.strip
44
46
  base = if text !~ /\n/ && special_inner_call_indentation?(send_node)
@@ -82,10 +82,11 @@ module RuboCop
82
82
  def on_send(node)
83
83
  super
84
84
  receiver, method_name, *args = *node
85
- return unless visibility_and_def_on_same_line?(receiver, method_name,
86
- args)
85
+ return unless modifier_and_def_on_same_line?(receiver, method_name,
86
+ args)
87
+
88
+ *_, body = *args.first
87
89
 
88
- _method_name, _args, body = *args.first
89
90
  def_end_config = config.for_cop('Lint/DefEndAlignment')
90
91
  style = if def_end_config['Enabled']
91
92
  def_end_config['AlignWith']
@@ -211,13 +212,36 @@ module RuboCop
211
212
  body_node = body_node.children.first
212
213
  end
213
214
 
215
+ # Since autocorrect changes a number of lines, and not only the line
216
+ # where the reported offending range is, we avoid auto-correction if
217
+ # this cop has already found other offenses is the same
218
+ # range. Otherwise, two corrections can interfere with each other,
219
+ # resulting in corrupted code.
220
+ node = if autocorrect? && other_offense_in_same_range?(body_node)
221
+ nil
222
+ else
223
+ body_node
224
+ end
225
+
214
226
  indentation_name = style == 'normal' ? '' : "#{style} "
215
- add_offense(body_node, offending_range(body_node, indentation),
227
+ add_offense(node, offending_range(body_node, indentation),
216
228
  format("Use #{configured_indentation_width} (not %d) " \
217
229
  "spaces for #{indentation_name}indentation.",
218
230
  indentation))
219
231
  end
220
232
 
233
+ # Returns true if the given node is within another node that has
234
+ # already been marked for auto-correction by this cop.
235
+ def other_offense_in_same_range?(node)
236
+ expr = node.loc.expression
237
+ @offense_ranges ||= []
238
+
239
+ return true if @offense_ranges.any? { |r| within?(expr, r) }
240
+
241
+ @offense_ranges << expr
242
+ false
243
+ end
244
+
221
245
  def indentation_to_check?(base_loc, body_node)
222
246
  return false unless body_node
223
247
 
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cops checks for indentation of the first non-blank non-comment
7
+ # line in a file.
8
+ class InitialIndentation < Cop
9
+ MSG = 'Indentation of first line in file detected.'
10
+
11
+ def investigate(processed_source)
12
+ first_token = processed_source.tokens.find do |t|
13
+ !t.text.start_with?('#')
14
+ end
15
+ return unless first_token
16
+ return if first_token.pos.column == 0
17
+
18
+ with_space = range_with_surrounding_space(first_token.pos, :left,
19
+ nil, !:with_newline)
20
+ space = Parser::Source::Range.new(processed_source.buffer,
21
+ with_space.begin_pos,
22
+ first_token.pos.begin_pos)
23
+ add_offense(space, first_token.pos)
24
+ end
25
+
26
+ def autocorrect(range)
27
+ ->(corrector) { corrector.remove(range) }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -7,13 +7,17 @@ module RuboCop
7
7
  class MethodCallParentheses < Cop
8
8
  MSG = 'Do not use parentheses for method calls with no arguments.'
9
9
 
10
+ ASGN_NODES = [:lvasgn, :masgn] + Util::SHORTHAND_ASGN_NODES
11
+
10
12
  def on_send(node)
11
13
  _receiver, method_name, *args = *node
12
14
 
13
15
  # methods starting with a capital letter should be skipped
14
16
  return if method_name =~ /\A[A-Z]/
17
+ return unless args.empty? && node.loc.begin
18
+ return if same_name_assignment?(node)
15
19
 
16
- add_offense(node, :begin) if args.empty? && node.loc.begin
20
+ add_offense(node, :begin)
17
21
  end
18
22
 
19
23
  def autocorrect(node)
@@ -22,6 +26,21 @@ module RuboCop
22
26
  corrector.remove(node.loc.end)
23
27
  end
24
28
  end
29
+
30
+ private
31
+
32
+ def same_name_assignment?(node)
33
+ _receiver, method_name, *_args = *node
34
+
35
+ node.each_ancestor(ASGN_NODES).any? do |asgn_node|
36
+ if asgn_node.masgn_type?
37
+ mlhs_node, _mrhs_node = *asgn_node
38
+ asgn_node = mlhs_node.children[node.sibling_index]
39
+ end
40
+
41
+ asgn_node.loc.name.source == method_name.to_s
42
+ end
43
+ end
25
44
  end
26
45
  end
27
46
  end
@@ -8,12 +8,16 @@ module RuboCop
8
8
  class OneLineConditional < Cop
9
9
  include OnNormalIfUnless
10
10
 
11
- MSG = 'Favor the ternary operator (?:) ' \
12
- 'over if/then/else/end constructs.'
11
+ MSG = 'Favor the ternary operator (`?:`) ' \
12
+ 'over `%s/then/else/end` constructs.'
13
13
 
14
14
  def on_normal_if_unless(node)
15
- return if node.loc.expression.source.include?("\n")
16
- add_offense(node, :expression, MSG)
15
+ exp = node.loc.expression.source
16
+ return if exp.include?("\n")
17
+ return unless node.loc.respond_to?(:else) && node.loc.else
18
+ condition = exp.include?('if') ? 'if' : 'unless'
19
+
20
+ add_offense(node, :expression, format(MSG, condition))
17
21
  end
18
22
  end
19
23
  end