rubocop 0.38.0 → 0.39.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -3
  3. data/config/default.yml +13 -0
  4. data/config/enabled.yml +15 -3
  5. data/lib/rubocop.rb +2 -0
  6. data/lib/rubocop/ast_node/builder.rb +6 -0
  7. data/lib/rubocop/cli.rb +2 -2
  8. data/lib/rubocop/config.rb +13 -13
  9. data/lib/rubocop/config_loader.rb +5 -28
  10. data/lib/rubocop/config_loader_resolver.rb +42 -0
  11. data/lib/rubocop/cop/cop.rb +4 -5
  12. data/lib/rubocop/cop/lint/assignment_in_condition.rb +1 -1
  13. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +9 -2
  14. data/lib/rubocop/cop/mixin/statement_modifier.rb +5 -4
  15. data/lib/rubocop/cop/performance/case_when_splat.rb +1 -1
  16. data/lib/rubocop/cop/performance/count.rb +20 -0
  17. data/lib/rubocop/cop/performance/detect.rb +12 -0
  18. data/lib/rubocop/cop/performance/redundant_merge.rb +10 -1
  19. data/lib/rubocop/cop/performance/times_map.rb +14 -0
  20. data/lib/rubocop/cop/style/case_indentation.rb +14 -7
  21. data/lib/rubocop/cop/style/conditional_assignment.rb +221 -20
  22. data/lib/rubocop/cop/style/file_name.rb +31 -17
  23. data/lib/rubocop/cop/style/if_unless_modifier.rb +8 -0
  24. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +45 -0
  25. data/lib/rubocop/cop/style/multiline_method_call_indentation.rb +3 -2
  26. data/lib/rubocop/cop/style/next.rb +6 -1
  27. data/lib/rubocop/cop/style/one_line_conditional.rb +37 -8
  28. data/lib/rubocop/cop/style/redundant_parentheses.rb +9 -0
  29. data/lib/rubocop/cop/style/redundant_self.rb +1 -1
  30. data/lib/rubocop/cop/style/space_around_keyword.rb +10 -1
  31. data/lib/rubocop/cop/style/special_global_vars.rb +23 -14
  32. data/lib/rubocop/cop/style/zero_length_predicate.rb +25 -0
  33. data/lib/rubocop/formatter/html_formatter.rb +3 -3
  34. data/lib/rubocop/result_cache.rb +2 -2
  35. data/lib/rubocop/runner.rb +3 -3
  36. data/lib/rubocop/target_finder.rb +2 -6
  37. data/lib/rubocop/version.rb +1 -1
  38. metadata +6 -4
@@ -21,28 +21,42 @@ module RuboCop
21
21
  file_path = processed_source.buffer.name
22
22
  return if config.file_to_include?(file_path)
23
23
 
24
+ for_bad_filename(file_path) do |range, msg|
25
+ add_offense(nil, range, msg)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def for_bad_filename(file_path)
24
32
  basename = File.basename(file_path)
25
- if filename_good?(basename)
26
- return unless expect_matching_definition?
27
- return if find_class_or_module(processed_source.ast,
28
- to_namespace(file_path))
29
- range = source_range(processed_source.buffer, 1, 0)
30
- msg = format(MSG_NO_DEFINITION,
31
- basename,
32
- to_namespace(file_path).join('::'))
33
- else
34
- first_line = processed_source.lines.first
35
- return if cop_config['IgnoreExecutableScripts'] &&
36
- shebang?(first_line)
33
+ msg = if filename_good?(basename)
34
+ return unless expect_matching_definition?
35
+ return if find_class_or_module(processed_source.ast,
36
+ to_namespace(file_path))
37
+ no_definition_message(basename, file_path)
38
+ else
39
+ return if cop_config['IgnoreExecutableScripts'] &&
40
+ shebang?(first_line)
41
+ other_message(basename)
42
+ end
43
+
44
+ yield source_range(processed_source.buffer, 1, 0), msg
45
+ end
37
46
 
38
- range = source_range(processed_source.buffer, 1, 0)
39
- msg = regex ? format(MSG_REGEX, basename, regex) : MSG_SNAKE_CASE
40
- end
47
+ def first_line
48
+ processed_source.lines.first
49
+ end
41
50
 
42
- add_offense(nil, range, msg)
51
+ def no_definition_message(basename, file_path)
52
+ format(MSG_NO_DEFINITION,
53
+ basename,
54
+ to_namespace(file_path).join('::'))
43
55
  end
44
56
 
45
- private
57
+ def other_message(basename)
58
+ regex ? format(MSG_REGEX, basename, regex) : MSG_SNAKE_CASE
59
+ end
46
60
 
47
61
  def shebang?(line)
48
62
  line && line.start_with?('#!')
@@ -26,6 +26,7 @@ module RuboCop
26
26
  return if if_else?(node)
27
27
  return if node.chained?
28
28
  return unless fit_within_line_as_modifier_form?(node)
29
+ return if nested_conditional?(node)
29
30
  add_offense(node, :keyword, message(node.loc.keyword.source))
30
31
  end
31
32
 
@@ -64,6 +65,13 @@ module RuboCop
64
65
 
65
66
  ->(corrector) { corrector.replace(node.source_range, oneline) }
66
67
  end
68
+
69
+ private
70
+
71
+ # returns false if the then or else children are conditionals
72
+ def nested_conditional?(node)
73
+ node.children[1, 2].any? { |child| child && child.type == :if }
74
+ end
67
75
  end
68
76
  end
69
77
  end
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module RuboCop
5
+ module Cop
6
+ module Style
7
+ # Checks for if and unless statements used as modifers of other if or
8
+ # unless statements.
9
+ #
10
+ # @example
11
+ #
12
+ # # bad
13
+ # tired? ? 'stop' : 'go faster' if running?
14
+ #
15
+ # # bad
16
+ # if tired?
17
+ # "please stop"
18
+ # else
19
+ # "keep going"
20
+ # end if running?
21
+ #
22
+ # # good
23
+ # if running?
24
+ # tired? ? 'stop' : 'go faster'
25
+ # end
26
+ class IfUnlessModifierOfIfUnless < Cop
27
+ include StatementModifier
28
+
29
+ MESSAGE = 'Avoid modifier `%s` after another conditional.'.freeze
30
+
31
+ def message(keyword)
32
+ format(MESSAGE, keyword)
33
+ end
34
+
35
+ def on_if(node)
36
+ return unless modifier_if?(node)
37
+ _cond, body, _else = if_node_parts(node)
38
+ if body.type == :if
39
+ add_offense(node, :keyword, message(node.loc.keyword.source))
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -126,9 +126,10 @@ module RuboCop
126
126
 
127
127
  def operation_rhs(node)
128
128
  receiver, = *node
129
- receiver.each_ancestor.select(&:send_type?).each do |a|
129
+ receiver.each_ancestor(:send) do |a|
130
130
  _, method, args = *a
131
- return args if operator?(method) && within_node?(receiver, args)
131
+ return args if operator?(method) && args &&
132
+ within_node?(receiver, args)
132
133
  end
133
134
  nil
134
135
  end
@@ -146,9 +146,14 @@ module RuboCop
146
146
  end
147
147
 
148
148
  def cond_range(node, cond)
149
+ end_pos = if node.loc.begin
150
+ node.loc.begin.end_pos # after "then"
151
+ else
152
+ cond.source_range.end_pos
153
+ end
149
154
  Parser::Source::Range.new(node.source_range.source_buffer,
150
155
  node.source_range.begin_pos,
151
- cond.source_range.end_pos)
156
+ end_pos)
152
157
  end
153
158
 
154
159
  def end_range(node)
@@ -28,18 +28,47 @@ module RuboCop
28
28
  end
29
29
 
30
30
  def replacement(node)
31
+ return ternary(node) unless node.parent
32
+ return "(#{ternary(node)})" if [:and, :or].include?(node.parent.type)
33
+
34
+ if node.parent.send_type? && operator?(node.parent.method_name)
35
+ return "(#{ternary(node)})"
36
+ end
37
+
38
+ ternary(node)
39
+ end
40
+
41
+ def ternary(node)
31
42
  cond, body, else_clause = *node
32
- ternary = "#{cond.source} ? #{body.source} : #{else_clause.source}"
43
+ "#{expr_replacement(cond)} ? #{expr_replacement(body)} : " +
44
+ expr_replacement(else_clause)
45
+ end
46
+
47
+ def expr_replacement(node)
48
+ requires_parentheses?(node) ? "(#{node.source})" : node.source
49
+ end
33
50
 
34
- return ternary unless node.parent
35
- return "(#{ternary})" if [:and, :or].include?(node.parent.type)
51
+ def requires_parentheses?(node)
52
+ return true if [:and, :or, :if].include?(node.type)
53
+ return true if node.assignment?
54
+ return true if method_call_with_changed_precedence?(node)
36
55
 
37
- if node.parent.send_type?
38
- _receiver, method_name, = *node.parent
39
- return "(#{ternary})" if operator?(method_name)
40
- end
56
+ keyword_with_changed_precedence?(node)
57
+ end
58
+
59
+ def method_call_with_changed_precedence?(node)
60
+ return false unless node.send_type?
61
+ return false if node.method_args.empty?
62
+ return false if parenthesized_call?(node)
63
+
64
+ !operator?(node.method_name)
65
+ end
66
+
67
+ def keyword_with_changed_precedence?(node)
68
+ return false unless node.keyword?
69
+ return true if node.keyword_not?
41
70
 
42
- ternary
71
+ !parenthesized_call?(node)
43
72
  end
44
73
  end
45
74
  end
@@ -69,6 +69,8 @@ module RuboCop
69
69
  offense(begin_node, 'an unary operation')
70
70
  else
71
71
  return unless method_call_with_redundant_parentheses?(node)
72
+ return if call_chain_starts_with_int?(begin_node, node)
73
+
72
74
  offense(begin_node, 'a method call')
73
75
  end
74
76
  end
@@ -115,6 +117,13 @@ module RuboCop
115
117
  _receiver, _method_name, *args = *send_node
116
118
  node.equal?(args.first)
117
119
  end
120
+
121
+ def call_chain_starts_with_int?(begin_node, send_node)
122
+ recv = first_part_of_call_chain(send_node)
123
+ recv && recv.int_type? && (parent = begin_node.parent) &&
124
+ parent.send_type? &&
125
+ (parent.method_name == :-@ || parent.method_name == :+@)
126
+ end
118
127
  end
119
128
  end
120
129
  end
@@ -120,7 +120,7 @@ module RuboCop
120
120
  end
121
121
 
122
122
  def keyword?(method_name)
123
- [:alias, :and, :begin, :break, :case, :class, :def, :defined, :do,
123
+ [:alias, :and, :begin, :break, :case, :class, :def, :defined?, :do,
124
124
  :else, :elsif, :end, :ensure, :false, :for, :if, :in, :module,
125
125
  :next, :nil, :not, :or, :redo, :rescue, :retry, :return, :self,
126
126
  :super, :then, :true, :undef, :unless, :until, :when, :while,
@@ -32,6 +32,8 @@ module RuboCop
32
32
  DO = 'do'.freeze
33
33
  ACCEPT_LEFT_PAREN =
34
34
  %w(break defined? next not rescue return super yield).freeze
35
+ ACCEPT_LEFT_SQUARE_BRACKET =
36
+ %w(super yield).freeze
35
37
 
36
38
  def on_and(node)
37
39
  check(node, [:operator].freeze) if node.keyword?
@@ -177,7 +179,10 @@ module RuboCop
177
179
  pos = range.end_pos
178
180
  char = range.source_buffer.source[pos]
179
181
  return false unless char
180
- return false if accept_left_parenthesis?(range) && char == '('.freeze
182
+ return false if accept_left_parenthesis?(range) &&
183
+ char == '('.freeze
184
+ return false if accept_left_square_bracket?(range) &&
185
+ char == '['.freeze
181
186
 
182
187
  char !~ /[\s;,#\\\)\}\]\.]/
183
188
  end
@@ -186,6 +191,10 @@ module RuboCop
186
191
  ACCEPT_LEFT_PAREN.include?(range.source)
187
192
  end
188
193
 
194
+ def accept_left_square_bracket?(range)
195
+ ACCEPT_LEFT_SQUARE_BRACKET.include?(range.source)
196
+ end
197
+
189
198
  def preceded_by_operator?(node, _range)
190
199
  # regular dotted method calls bind more tightly than operators
191
200
  # so we need to climb up the AST past them
@@ -104,25 +104,28 @@ module RuboCop
104
104
  node.parent.children.one?
105
105
  node = node.parent
106
106
  end
107
- parent_type = node.parent && node.parent.type
108
-
109
- if [:dstr, :xstr, :regexp].include?(parent_type)
110
- if style == :use_english_names
111
- corrector.replace(node.source_range,
112
- "{#{preferred_names(global_var).first}}")
113
- else
114
- corrector.replace(node.source_range,
115
- "##{preferred_names(global_var).first}")
116
- end
117
- else
118
- corrector.replace(node.source_range,
119
- preferred_names(global_var).first.to_s)
120
- end
107
+
108
+ corrector.replace(node.source_range, replacement(node, global_var))
121
109
  end
122
110
  end
123
111
 
124
112
  private
125
113
 
114
+ def replacement(node, global_var)
115
+ parent_type = node.parent && node.parent.type
116
+ preferred_name = preferred_names(global_var).first
117
+
118
+ unless [:dstr, :xstr, :regexp].include?(parent_type)
119
+ return preferred_name.to_s
120
+ end
121
+
122
+ if style == :use_english_names
123
+ return english_name_replacement(preferred_name, node)
124
+ end
125
+
126
+ "##{preferred_name}"
127
+ end
128
+
126
129
  def preferred_names(global)
127
130
  if style == :use_english_names
128
131
  ENGLISH_VARS[global]
@@ -130,6 +133,12 @@ module RuboCop
130
133
  PERL_VARS[global]
131
134
  end
132
135
  end
136
+
137
+ def english_name_replacement(preferred_name, node)
138
+ return "\#{#{preferred_name}}" if node.begin_type?
139
+
140
+ "{#{preferred_name}}"
141
+ end
133
142
  end
134
143
  end
135
144
  end
@@ -51,6 +51,31 @@ module RuboCop
51
51
  {(send (send _ ${:length :size}) ${:> :!=} (int $0))
52
52
  (send (int $0) ${:< :!=} (send _ ${:length :size}))}
53
53
  END
54
+
55
+ def autocorrect(node)
56
+ lambda do |corrector|
57
+ corrector.replace(node.loc.expression, replacement(node))
58
+ end
59
+ end
60
+
61
+ def replacement(node)
62
+ receiver = zero_length_receiver(node)
63
+ return "#{receiver.source}.empty?" if receiver
64
+
65
+ "!#{other_receiver(node).source}.empty?"
66
+ end
67
+
68
+ def_node_matcher :zero_length_receiver, <<-END
69
+ {(send (send $_ _) :== (int 0))
70
+ (send (int 0) :== (send $_ _))
71
+ (send (send $_ _) :< (int 1))
72
+ (send (int 1) :> (send $_ _))}
73
+ END
74
+
75
+ def_node_matcher :other_receiver, <<-END
76
+ {(send (send $_ _) _ _)
77
+ (send _ _ (send $_ _))}
78
+ END
54
79
  end
55
80
  end
56
81
  end
@@ -9,11 +9,11 @@ require 'rubocop/formatter/text_util'
9
9
 
10
10
  module RuboCop
11
11
  module Formatter
12
- # This formatter saves the output as a html file.
12
+ # This formatter saves the output as an html file.
13
13
  class HTMLFormatter < BaseFormatter
14
14
  ELLIPSES = '<span class="extra-code">...</span>'.freeze
15
- TEMPLATE_PATH =
16
- File.expand_path('../../../../assets/output.html.erb', __FILE__)
15
+ TEMPLATE_PATH = File.expand_path('../../../../assets/output.html.erb',
16
+ __FILE__).encode('utf-8')
17
17
 
18
18
  Color = Struct.new(:red, :green, :blue, :alpha) do
19
19
  def to_s
@@ -24,7 +24,7 @@ module RuboCop
24
24
  return unless File.exist?(cache_root)
25
25
 
26
26
  files, dirs = Find.find(cache_root).partition { |path| File.file?(path) }
27
- if files.length > config_store.for('.')['AllCops']['MaxFilesInCache'] &&
27
+ if files.length > config_store.for('.').for_all_cops['MaxFilesInCache'] &&
28
28
  files.length > 1
29
29
  # Add 1 to half the number of files, so that we remove the file if
30
30
  # there's only 1 left.
@@ -51,7 +51,7 @@ module RuboCop
51
51
  end
52
52
 
53
53
  def self.cache_root(config_store)
54
- root = config_store.for('.')['AllCops']['CacheRootDirectory']
54
+ root = config_store.for('.').for_all_cops['CacheRootDirectory']
55
55
  if root == '/tmp'
56
56
  tmpdir = File.realpath(Dir.tmpdir)
57
57
  # Include user ID in the path to make sure the user has write access.
@@ -122,7 +122,7 @@ module RuboCop
122
122
  @cached_run ||=
123
123
  (@options[:cache] == 'true' ||
124
124
  @options[:cache] != 'false' &&
125
- @config_store.for(Dir.pwd)['AllCops']['UseCache']) &&
125
+ @config_store.for(Dir.pwd).for_all_cops['UseCache']) &&
126
126
  # When running --auto-gen-config, there's some processing done in the
127
127
  # cops related to calculating the Max parameters for Metrics cops. We
128
128
  # need to do that processing and can not use caching.
@@ -240,7 +240,7 @@ module RuboCop
240
240
  end
241
241
 
242
242
  def style_guide_cops_only?(config)
243
- @options[:only_guide_cops] || config['AllCops']['StyleGuideCopsOnly']
243
+ @options[:only_guide_cops] || config.for_all_cops['StyleGuideCopsOnly']
244
244
  end
245
245
 
246
246
  def formatter_set
@@ -271,7 +271,7 @@ module RuboCop
271
271
  end
272
272
 
273
273
  def get_processed_source(file)
274
- ruby_version = @config_store.for(file)['AllCops']['TargetRubyVersion']
274
+ ruby_version = @config_store.for(file).for_all_cops['TargetRubyVersion']
275
275
 
276
276
  if @options[:stdin]
277
277
  ProcessedSource.new(@options[:stdin], ruby_version, file)
@@ -110,7 +110,7 @@ module RuboCop
110
110
  end
111
111
 
112
112
  def excluded_dirs(base_dir)
113
- all_cops_config = @config_store.for(base_dir)['AllCops']
113
+ all_cops_config = @config_store.for(base_dir).for_all_cops
114
114
  dir_tree_excludes = all_cops_config['Exclude'].select do |pattern|
115
115
  pattern.is_a?(String) && pattern.end_with?('/**/*')
116
116
  end
@@ -127,11 +127,7 @@ module RuboCop
127
127
  end
128
128
 
129
129
  def process_explicit_path(path)
130
- files = if path.include?('*')
131
- Dir[path]
132
- else
133
- [path]
134
- end
130
+ files = path.include?('*') ? Dir[path] : [path]
135
131
 
136
132
  return files unless force_exclusion?
137
133