rubocop 0.57.2 → 0.58.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -9
  3. data/bin/setup +7 -0
  4. data/config/default.yml +18 -2
  5. data/config/disabled.yml +4 -0
  6. data/lib/rubocop.rb +1 -0
  7. data/lib/rubocop/ast/node.rb +5 -0
  8. data/lib/rubocop/ast/node/str_node.rb +2 -0
  9. data/lib/rubocop/cli.rb +4 -7
  10. data/lib/rubocop/config.rb +4 -4
  11. data/lib/rubocop/config_loader.rb +4 -8
  12. data/lib/rubocop/cop/corrector.rb +25 -0
  13. data/lib/rubocop/cop/layout/closing_heredoc_indentation.rb +3 -7
  14. data/lib/rubocop/cop/layout/end_alignment.rb +1 -1
  15. data/lib/rubocop/cop/layout/indentation_width.rb +9 -1
  16. data/lib/rubocop/cop/layout/leading_blank_lines.rb +1 -1
  17. data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +28 -51
  18. data/lib/rubocop/cop/lint/redundant_with_object.rb +1 -1
  19. data/lib/rubocop/cop/lint/shadowed_argument.rb +7 -3
  20. data/lib/rubocop/cop/lint/unneeded_splat_expansion.rb +1 -1
  21. data/lib/rubocop/cop/lint/useless_access_modifier.rb +17 -4
  22. data/lib/rubocop/cop/metrics/line_length.rb +28 -6
  23. data/lib/rubocop/cop/mixin/check_assignment.rb +0 -2
  24. data/lib/rubocop/cop/mixin/statement_modifier.rb +6 -1
  25. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +79 -4
  26. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +9 -5
  27. data/lib/rubocop/cop/performance/range_include.rb +9 -3
  28. data/lib/rubocop/cop/performance/sample.rb +6 -4
  29. data/lib/rubocop/cop/rails/bulk_change_table.rb +11 -7
  30. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +3 -1
  31. data/lib/rubocop/cop/registry.rb +11 -2
  32. data/lib/rubocop/cop/style/encoding.rb +5 -0
  33. data/lib/rubocop/cop/style/end_block.rb +8 -0
  34. data/lib/rubocop/cop/style/if_unless_modifier.rb +2 -1
  35. data/lib/rubocop/cop/style/ip_addresses.rb +76 -0
  36. data/lib/rubocop/cop/style/multiple_comparison.rb +16 -2
  37. data/lib/rubocop/cop/style/symbol_proc.rb +4 -2
  38. data/lib/rubocop/cop/style/unneeded_condition.rb +19 -2
  39. data/lib/rubocop/formatter/disabled_config_formatter.rb +3 -3
  40. data/lib/rubocop/options.rb +20 -12
  41. data/lib/rubocop/processed_source.rb +2 -5
  42. data/lib/rubocop/rspec/cop_helper.rb +0 -4
  43. data/lib/rubocop/rspec/shared_contexts.rb +0 -4
  44. data/lib/rubocop/rspec/shared_examples.rb +0 -23
  45. data/lib/rubocop/version.rb +1 -1
  46. metadata +7 -11
@@ -59,7 +59,9 @@ module RuboCop
59
59
  PATTERN
60
60
 
61
61
  def_node_search :created_at_or_updated_at_included?, <<-PATTERN
62
- (send _var :datetime (sym {:created_at :updated_at}) ...)
62
+ (send _var :datetime
63
+ {(sym {:created_at :updated_at})(str {"created_at" "updated_at"})}
64
+ ...)
63
65
  PATTERN
64
66
 
65
67
  def on_send(node)
@@ -23,8 +23,9 @@ module RuboCop
23
23
  # Registry that tracks all cops by their badge and department.
24
24
  class Registry
25
25
  def initialize(cops = [])
26
- @registry = {}
26
+ @registry = {}
27
27
  @departments = {}
28
+ @cops_by_cop_name = Hash.new { |hash, key| hash[key] = [] }
28
29
 
29
30
  cops.each { |cop| enlist(cop) }
30
31
  end
@@ -33,6 +34,7 @@ module RuboCop
33
34
  @registry[cop.badge] = cop
34
35
  @departments[cop.department] ||= []
35
36
  @departments[cop.department] << cop
37
+ @cops_by_cop_name[cop.cop_name] << cop
36
38
  end
37
39
 
38
40
  # @return [Array<Symbol>] list of departments for current cops.
@@ -102,8 +104,9 @@ module RuboCop
102
104
  end
103
105
  end
104
106
 
107
+ # @return [Hash{String => Array<Class>}]
105
108
  def to_h
106
- cops.group_by(&:cop_name)
109
+ @cops_by_cop_name
107
110
  end
108
111
 
109
112
  def cops
@@ -142,6 +145,12 @@ module RuboCop
142
145
  cops.each(&block)
143
146
  end
144
147
 
148
+ # @param [String] cop_name
149
+ # @return [Class, nil]
150
+ def find_by_cop_name(cop_name)
151
+ @cops_by_cop_name[cop_name].first
152
+ end
153
+
145
154
  private
146
155
 
147
156
  def with(cops)
@@ -4,6 +4,11 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # This cop checks ensures source files have no utf-8 encoding comments.
7
+ # @example
8
+ # # bad
9
+ # # encoding: UTF-8
10
+ # # coding: UTF-8
11
+ # # -*- coding: UTF-8 -*-
7
12
  class Encoding < Cop
8
13
  include RangeHelp
9
14
 
@@ -4,6 +4,14 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # This cop checks for END blocks.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # END { puts 'Goodbye!' }
11
+ #
12
+ # # good
13
+ # at_exit { puts 'Goodbye!' }
14
+ #
7
15
  class EndBlock < Cop
8
16
  MSG = 'Avoid the use of `END` blocks. ' \
9
17
  'Use `Kernel#at_exit` instead.'.freeze
@@ -5,7 +5,8 @@ module RuboCop
5
5
  module Style
6
6
  # Checks for if and unless statements that would fit on one line
7
7
  # if written as a modifier if/unless. The maximum line length is
8
- # configured in the `Metrics/LineLength` cop.
8
+ # configured in the `Metrics/LineLength` cop. The tab size is configured
9
+ # in the `IndentationWidth` of the `Layout/Tab` cop.
9
10
  #
10
11
  # @example
11
12
  # # bad
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'resolv'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Style
8
+ # This cop checks for hardcoded IP addresses, which can make code
9
+ # brittle. IP addresses are likely to need to be changed when code
10
+ # is deployed to a different server or environment, which may break
11
+ # a deployment if forgotten. Prefer setting IP addresses in ENV or
12
+ # other configuration.
13
+ #
14
+ # @example
15
+ #
16
+ # # bad
17
+ # ip_address = '127.59.241.29'
18
+ #
19
+ # # good
20
+ # ip_address = ENV['DEPLOYMENT_IP_ADDRESS']
21
+ class IpAddresses < Cop
22
+ include StringHelp
23
+
24
+ IPV6_MAX_SIZE = 45 # IPv4-mapped IPv6 is the longest
25
+ MSG = 'Do not hardcode IP addresses.'.freeze
26
+
27
+ def offense?(node)
28
+ contents = node.source[1...-1]
29
+ return false if contents.empty?
30
+
31
+ return false if whitelist.include?(contents.downcase)
32
+
33
+ # To try to avoid doing two regex checks on every string,
34
+ # shortcut out if the string does not look like an IP address
35
+ return false unless could_be_ip?(contents)
36
+
37
+ contents =~ ::Resolv::IPv4::Regex || contents =~ ::Resolv::IPv6::Regex
38
+ end
39
+
40
+ # Dummy implementation of method in ConfigurableEnforcedStyle that is
41
+ # called from StringHelp.
42
+ def opposite_style_detected; end
43
+
44
+ # Dummy implementation of method in ConfigurableEnforcedStyle that is
45
+ # called from StringHelp.
46
+ def correct_style_detected; end
47
+
48
+ private
49
+
50
+ def whitelist
51
+ whitelist = cop_config['Whitelist']
52
+ Array(whitelist).map(&:downcase)
53
+ end
54
+
55
+ def could_be_ip?(str)
56
+ # If the string is too long, it can't be an IP
57
+ return false if too_long?(str)
58
+
59
+ # If the string doesn't start with a colon or hexadecimal char,
60
+ # we know it's not an IP address
61
+ starts_with_hex_or_colon?(str)
62
+ end
63
+
64
+ def too_long?(str)
65
+ str.size > IPV6_MAX_SIZE
66
+ end
67
+
68
+ def starts_with_hex_or_colon?(str)
69
+ first_char = str[0].ord
70
+ (48..58).cover?(first_char) || (65..70).cover?(first_char) ||
71
+ (97..102).cover?(first_char)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -18,8 +18,12 @@ module RuboCop
18
18
  MSG = 'Avoid comparing a variable with multiple items ' \
19
19
  'in a conditional, use `Array#include?` instead.'.freeze
20
20
 
21
- def on_if(node)
22
- return unless nested_variable_comparison?(node.condition)
21
+ def on_or(node)
22
+ root_of_or_node = root_of_or_node(node)
23
+
24
+ return unless node == root_of_or_node
25
+ return unless nested_variable_comparison?(root_of_or_node)
26
+
23
27
  add_offense(node)
24
28
  end
25
29
 
@@ -71,6 +75,16 @@ module RuboCop
71
75
  def comparison?(node)
72
76
  simple_comparison?(node) || nested_comparison?(node)
73
77
  end
78
+
79
+ def root_of_or_node(or_node)
80
+ return or_node unless or_node.parent
81
+
82
+ if or_node.parent.or_type?
83
+ root_of_or_node(or_node.parent)
84
+ else
85
+ or_node
86
+ end
87
+ end
74
88
  end
75
89
  end
76
90
  end
@@ -22,7 +22,7 @@ module RuboCop
22
22
  def_node_matcher :symbol_proc?, <<-PATTERN
23
23
  (block
24
24
  ${(send ...) (super ...) zsuper}
25
- (args (arg _var))
25
+ $(args (arg _var))
26
26
  (send (lvar _var) $_))
27
27
  PATTERN
28
28
 
@@ -31,7 +31,7 @@ module RuboCop
31
31
  end
32
32
 
33
33
  def on_block(node)
34
- symbol_proc?(node) do |send_or_super, method|
34
+ symbol_proc?(node) do |send_or_super, block_args, method|
35
35
  block_method_name = resolve_block_method_name(send_or_super)
36
36
 
37
37
  # TODO: Rails-specific handling that we should probably make
@@ -40,6 +40,8 @@ module RuboCop
40
40
  return if proc_node?(send_or_super)
41
41
  return if %i[lambda proc].include?(block_method_name)
42
42
  return if ignored_method?(block_method_name)
43
+ return if block_args.children.size == 1 &&
44
+ block_args.source.include?(',')
43
45
 
44
46
  offense(node, method, block_method_name)
45
47
  end
@@ -34,9 +34,12 @@ module RuboCop
34
34
  include RangeHelp
35
35
 
36
36
  MSG = 'Use double pipes `||` instead.'.freeze
37
+ UNNEEDED_CONDITION = 'This condition is not needed.'.freeze
37
38
 
38
39
  def on_if(node)
40
+ return if node.elsif_conditional?
39
41
  return unless offense?(node)
42
+
40
43
  add_offense(node, location: range_of_offense(node))
41
44
  end
42
45
 
@@ -44,6 +47,8 @@ module RuboCop
44
47
  lambda do |corrector|
45
48
  if node.ternary?
46
49
  corrector.replace(range_of_offense(node), '||')
50
+ elsif node.modifier_form?
51
+ corrector.replace(node.source_range, node.if_branch.source)
47
52
  else
48
53
  corrected = [node.if_branch.source,
49
54
  else_source(node.else_branch)].join(' || ')
@@ -55,16 +60,24 @@ module RuboCop
55
60
 
56
61
  private
57
62
 
63
+ def message(node)
64
+ if node.modifier_form?
65
+ UNNEEDED_CONDITION
66
+ else
67
+ MSG
68
+ end
69
+ end
70
+
58
71
  def range_of_offense(node)
59
72
  return :expression unless node.ternary?
60
73
  range_between(node.loc.question.begin_pos, node.loc.colon.end_pos)
61
74
  end
62
75
 
63
76
  def offense?(node)
64
- return false if node.elsif_conditional?
65
-
66
77
  condition, if_branch, else_branch = *node
67
78
 
79
+ return false if use_if_branch?(else_branch)
80
+
68
81
  condition == if_branch && !node.elsif? && (
69
82
  node.ternary? ||
70
83
  !else_branch.instance_of?(AST::Node) ||
@@ -72,6 +85,10 @@ module RuboCop
72
85
  )
73
86
  end
74
87
 
88
+ def use_if_branch?(else_branch)
89
+ else_branch && else_branch.if_type?
90
+ end
91
+
75
92
  def else_source(else_branch)
76
93
  wrap_else = MODIFIER_NODES.include?(else_branch.type) &&
77
94
  else_branch.modifier_form?
@@ -18,8 +18,6 @@ module RuboCop
18
18
  @config_to_allow_offenses = {}
19
19
  @detected_styles = {}
20
20
 
21
- COPS = Cop::Cop.registry.to_h
22
-
23
21
  class << self
24
22
  attr_accessor :config_to_allow_offenses, :detected_styles
25
23
  end
@@ -101,7 +99,9 @@ module RuboCop
101
99
  if @show_offense_counts
102
100
  output_buffer.puts "# Offense count: #{offense_count}"
103
101
  end
104
- if COPS[cop_name] && COPS[cop_name].first.new.support_autocorrect?
102
+
103
+ cop_class = Cop::Cop.registry.find_by_cop_name(cop_name)
104
+ if cop_class && cop_class.new.support_autocorrect?
105
105
  output_buffer.puts '# Cop supports --auto-correct.'
106
106
  end
107
107
 
@@ -5,6 +5,7 @@ require 'shellwords'
5
5
 
6
6
  module RuboCop
7
7
  class IncorrectCopNameError < StandardError; end
8
+ class OptionArgumentError < StandardError; end
8
9
 
9
10
  # This class handles command line options.
10
11
  class Options
@@ -26,7 +27,7 @@ module RuboCop
26
27
  # The parser has put the file name given after --stdin into
27
28
  # @options[:stdin]. The args array should be empty.
28
29
  if args.any?
29
- raise ArgumentError, '-s/--stdin requires exactly one path.'
30
+ raise OptionArgumentError, '-s/--stdin requires exactly one path.'
30
31
  end
31
32
  # We want the STDIN contents in @options[:stdin] and the file name in
32
33
  # args to simplify the rest of the processing.
@@ -241,21 +242,21 @@ module RuboCop
241
242
 
242
243
  def validate_compatibility # rubocop:disable Metrics/MethodLength
243
244
  if only_includes_unneeded_disable?
244
- raise ArgumentError, 'Lint/UnneededCopDisableDirective can not ' \
245
- 'be used with --only.'
245
+ raise OptionArgumentError, 'Lint/UnneededCopDisableDirective can not ' \
246
+ 'be used with --only.'
246
247
  end
247
248
  if except_syntax?
248
- raise ArgumentError, 'Syntax checking can not be turned off.'
249
+ raise OptionArgumentError, 'Syntax checking can not be turned off.'
249
250
  end
250
251
  unless boolean_or_empty_cache?
251
- raise ArgumentError, '-C/--cache argument must be true or false'
252
+ raise OptionArgumentError, '-C/--cache argument must be true or false'
252
253
  end
253
254
  validate_auto_gen_config
254
255
  validate_parallel
255
256
 
256
257
  return if incompatible_options.size <= 1
257
- raise ArgumentError, 'Incompatible cli options: ' \
258
- "#{incompatible_options.inspect}"
258
+ raise OptionArgumentError, 'Incompatible cli options: ' \
259
+ "#{incompatible_options.inspect}"
259
260
  end
260
261
 
261
262
  def validate_auto_gen_config
@@ -265,7 +266,8 @@ module RuboCop
265
266
 
266
267
  %i[exclude_limit no_offense_counts no_auto_gen_timestamp].each do |option|
267
268
  if @options.key?(option)
268
- raise ArgumentError, format(message, flag: option.to_s.tr('_', '-'))
269
+ raise OptionArgumentError,
270
+ format(message, flag: option.to_s.tr('_', '-'))
269
271
  end
270
272
  end
271
273
  end
@@ -274,11 +276,15 @@ module RuboCop
274
276
  return unless @options.key?(:parallel)
275
277
 
276
278
  if @options[:cache] == 'false'
277
- raise ArgumentError, '-P/--parallel uses caching to speed up ' \
278
- 'execution, so combining with --cache false is ' \
279
- 'not allowed.'
279
+ raise OptionArgumentError, '-P/--parallel uses caching to speed up ' \
280
+ 'execution, so combining with --cache ' \
281
+ 'false is not allowed.'
280
282
  end
281
283
 
284
+ validate_parallel_with_combo_option
285
+ end
286
+
287
+ def validate_parallel_with_combo_option
282
288
  combos = {
283
289
  auto_gen_config: '-P/--parallel uses caching to speed up execution, ' \
284
290
  'while --auto-gen-config needs a non-cached run, ' \
@@ -287,7 +293,9 @@ module RuboCop
287
293
  auto_correct: '-P/--parallel can not be combined with --auto-correct.'
288
294
  }
289
295
 
290
- combos.each { |key, msg| raise ArgumentError, msg if @options.key?(key) }
296
+ combos.each do |key, msg|
297
+ raise OptionArgumentError, msg if @options.key?(key)
298
+ end
291
299
  end
292
300
 
293
301
  def only_includes_unneeded_disable?
@@ -163,12 +163,9 @@ module RuboCop
163
163
  [ast, comments, tokens]
164
164
  end
165
165
 
166
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
166
+ # rubocop:disable Metrics/MethodLength
167
167
  def parser_class(ruby_version)
168
168
  case ruby_version
169
- when 2.1
170
- require 'parser/ruby21'
171
- Parser::Ruby21
172
169
  when 2.2
173
170
  require 'parser/ruby22'
174
171
  Parser::Ruby22
@@ -188,7 +185,7 @@ module RuboCop
188
185
  raise ArgumentError, "Unknown Ruby version: #{ruby_version.inspect}"
189
186
  end
190
187
  end
191
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
188
+ # rubocop:enable Metrics/MethodLength
192
189
 
193
190
  def create_parser(ruby_version)
194
191
  builder = RuboCop::AST::Builder.new
@@ -14,10 +14,6 @@ module CopHelper
14
14
  Tempfile.open('tmp') { |f| inspect_source(source, f) }
15
15
  end
16
16
 
17
- def inspect_gemfile(source)
18
- inspect_source(source, 'Gemfile')
19
- end
20
-
21
17
  def inspect_source(source, file = nil)
22
18
  if source.is_a?(Array) && source.size == 1
23
19
  raise "Don't use an array for a single line of code: #{source}"
@@ -59,10 +59,6 @@ shared_context 'config', :config do
59
59
  end
60
60
  end
61
61
 
62
- shared_context 'ruby 2.1', :ruby21 do
63
- let(:ruby_version) { 2.1 }
64
- end
65
-
66
62
  shared_context 'ruby 2.2', :ruby22 do
67
63
  let(:ruby_version) { 2.2 }
68
64
  end
@@ -9,29 +9,6 @@ shared_examples_for 'accepts' do
9
9
  end
10
10
  end
11
11
 
12
- shared_examples_for 'mimics MRI 2.1' do |grep_mri_warning|
13
- if RUBY_ENGINE == 'ruby' && RUBY_VERSION.start_with?('2.1')
14
- it "mimics MRI #{RUBY_VERSION} built-in syntax checking" do
15
- inspect_source(source)
16
- offenses_by_mri = MRISyntaxChecker.offenses_for_source(
17
- source, cop.name, grep_mri_warning
18
- )
19
-
20
- # Compare objects before comparing counts for clear failure output.
21
- cop.offenses.each_with_index do |offense_by_cop, index|
22
- offense_by_mri = offenses_by_mri[index]
23
- # Exclude column attribute since MRI does not
24
- # output column number.
25
- %i[severity line cop_name].each do |a|
26
- expect(offense_by_cop.send(a)).to eq(offense_by_mri.send(a))
27
- end
28
- end
29
-
30
- expect(cop.offenses.count).to eq(offenses_by_mri.count)
31
- end
32
- end
33
- end
34
-
35
12
  shared_examples_for 'misaligned' do |annotated_source, used_style|
36
13
  config_to_allow_offenses = if used_style
37
14
  { 'EnforcedStyleAlignWith' => used_style.to_s }