rubocop 0.57.2 → 0.58.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 (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 }