rubocop 1.4.0 → 1.5.2

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/assets/logo.png +0 -0
  4. data/assets/output.html.erb +261 -0
  5. data/config/default.yml +49 -9
  6. data/lib/rubocop.rb +4 -0
  7. data/lib/rubocop/cli.rb +5 -1
  8. data/lib/rubocop/cli/command/suggest_extensions.rb +108 -0
  9. data/lib/rubocop/config_loader.rb +1 -1
  10. data/lib/rubocop/config_loader_resolver.rb +5 -1
  11. data/lib/rubocop/config_obsoletion.rb +21 -3
  12. data/lib/rubocop/config_validator.rb +8 -1
  13. data/lib/rubocop/cop/autocorrect_logic.rb +21 -6
  14. data/lib/rubocop/cop/generator.rb +1 -1
  15. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +3 -3
  16. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +6 -1
  17. data/lib/rubocop/cop/layout/end_of_line.rb +5 -5
  18. data/lib/rubocop/cop/layout/first_argument_indentation.rb +7 -2
  19. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +12 -0
  20. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +7 -3
  21. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  22. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +1 -1
  23. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +13 -0
  24. data/lib/rubocop/cop/lint/unexpected_block_arity.rb +85 -0
  25. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +7 -2
  26. data/lib/rubocop/cop/metrics/abc_size.rb +25 -1
  27. data/lib/rubocop/cop/metrics/block_length.rb +13 -7
  28. data/lib/rubocop/cop/metrics/method_length.rb +7 -2
  29. data/lib/rubocop/cop/metrics/parameter_lists.rb +64 -1
  30. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +20 -10
  31. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +146 -0
  32. data/lib/rubocop/cop/metrics/utils/repeated_csend_discount.rb +6 -1
  33. data/lib/rubocop/cop/mixin/configurable_numbering.rb +3 -2
  34. data/lib/rubocop/cop/mixin/enforce_superclass.rb +9 -1
  35. data/lib/rubocop/cop/mixin/ignored_methods.rb +36 -3
  36. data/lib/rubocop/cop/mixin/method_complexity.rb +6 -0
  37. data/lib/rubocop/cop/naming/variable_number.rb +3 -1
  38. data/lib/rubocop/cop/style/and_or.rb +10 -0
  39. data/lib/rubocop/cop/style/class_and_module_children.rb +8 -3
  40. data/lib/rubocop/cop/style/format_string.rb +8 -3
  41. data/lib/rubocop/cop/style/if_with_semicolon.rb +39 -4
  42. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -2
  43. data/lib/rubocop/cop/style/numeric_literals.rb +14 -11
  44. data/lib/rubocop/cop/style/redundant_argument.rb +3 -1
  45. data/lib/rubocop/cop/style/redundant_condition.rb +2 -1
  46. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +24 -8
  47. data/lib/rubocop/cop/style/sole_nested_conditional.rb +59 -3
  48. data/lib/rubocop/cop/style/string_concatenation.rb +7 -1
  49. data/lib/rubocop/cop/style/symbol_proc.rb +5 -3
  50. data/lib/rubocop/core_ext/hash.rb +20 -0
  51. data/lib/rubocop/ext/regexp_node.rb +29 -12
  52. data/lib/rubocop/ext/regexp_parser.rb +20 -9
  53. data/lib/rubocop/version.rb +1 -1
  54. metadata +23 -5
@@ -69,10 +69,14 @@ module RuboCop
69
69
  if @options[:auto_gen_config]
70
70
  run_command(:auto_gen_config)
71
71
  else
72
- run_command(:execute_runner)
72
+ run_command(:execute_runner).tap { suggest_extensions }
73
73
  end
74
74
  end
75
75
 
76
+ def suggest_extensions
77
+ run_command(:suggest_extensions)
78
+ end
79
+
76
80
  def validate_options_vs_config
77
81
  if @options[:parallel] &&
78
82
  !@config_store.for_pwd.for_all_cops['UseCache']
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ class CLI
5
+ module Command
6
+ # Run all the selected cops and report the result.
7
+ # @api private
8
+ class SuggestExtensions < Base
9
+ # Combination of short and long formatter names.
10
+ INCLUDED_FORMATTERS = %w[p progress fu fuubar pa pacman].freeze
11
+
12
+ self.command_name = :suggest_extensions
13
+
14
+ class << self
15
+ # Gems that the current bundle depends on
16
+ # Suggestions are only made for gems that are 1st party dependencies
17
+ def dependent_gems
18
+ bundler do |gems|
19
+ gems.dependencies.map(&:name)
20
+ end
21
+ end
22
+
23
+ # All installed gems in the current bundle
24
+ # If a suggestion is installed but not depended on, it still should not
25
+ # be suggested.
26
+ def installed_gems
27
+ bundler do
28
+ # Load specs from the lockfile without trying to resolve them
29
+ return [] unless Bundler.default_lockfile
30
+
31
+ lockfile = Bundler.read_file(Bundler.default_lockfile)
32
+ Bundler::LockfileParser.new(lockfile).specs.map(&:name)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def bundler
39
+ return [] unless defined?(Bundler)
40
+
41
+ yield Bundler.load
42
+ rescue Bundler::BundlerError
43
+ []
44
+ end
45
+ end
46
+
47
+ def run
48
+ return if skip? || extensions.none?
49
+
50
+ puts
51
+ puts 'Tip: Based on detected gems, the following '\
52
+ 'RuboCop extension libraries might be helpful:'
53
+
54
+ extensions.sort.each do |extension|
55
+ puts " * #{extension} (http://github.com/rubocop-hq/#{extension})"
56
+ end
57
+
58
+ puts
59
+ puts 'You can opt out of this message by adding the following to your config '\
60
+ '(see https://docs.rubocop.org/rubocop/extensions.html#extension-suggestions '\
61
+ 'for more options):'
62
+ puts ' AllCops:'
63
+ puts ' SuggestExtensions: false'
64
+
65
+ puts if @options[:display_time]
66
+ end
67
+
68
+ private
69
+
70
+ def skip?
71
+ # Disable outputting the notification:
72
+ # 1. On CI
73
+ # 2. When given RuboCop options that it doesn't make sense for
74
+ # 3. For all formatters except specified in `INCLUDED_FORMATTERS'`
75
+ ENV['CI'] ||
76
+ @options[:only] || @options[:debug] || @options[:list_target_files] || @options[:out] ||
77
+ !INCLUDED_FORMATTERS.include?(current_formatter)
78
+ end
79
+
80
+ def current_formatter
81
+ @options[:format] || @config_store.for_pwd.for_all_cops['DefaultFormatter'] || 'p'
82
+ end
83
+
84
+ def extensions
85
+ return [] unless dependent_gems.any?
86
+
87
+ @extensions ||= begin
88
+ extensions = @config_store.for_pwd.for_all_cops['SuggestExtensions'] || {}
89
+ extensions.select { |_, v| (Array(v) & dependent_gems).any? }.keys - installed_gems
90
+ end
91
+ end
92
+
93
+ def puts(*args)
94
+ output = (@options[:stderr] ? $stderr : $stdout)
95
+ output.puts(*args)
96
+ end
97
+
98
+ def dependent_gems
99
+ @dependent_gems ||= self.class.dependent_gems
100
+ end
101
+
102
+ def installed_gems
103
+ @installed_gems ||= self.class.installed_gems
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -109,7 +109,7 @@ module RuboCop
109
109
  end
110
110
 
111
111
  merge_with_default(config, config_file).tap do |merged_config|
112
- warn_on_pending_cops(merged_config.pending_cops) unless possible_new_cops?(config)
112
+ warn_on_pending_cops(merged_config.pending_cops) unless possible_new_cops?(merged_config)
113
113
  end
114
114
  end
115
115
 
@@ -92,7 +92,7 @@ module RuboCop
92
92
  keys_appearing_in_both.each do |key|
93
93
  if opts[:unset_nil] && derived_hash[key].nil?
94
94
  result.delete(key)
95
- elsif base_hash[key].is_a?(Hash)
95
+ elsif merge_hashes?(base_hash, derived_hash, key)
96
96
  result[key] = merge(base_hash[key], derived_hash[key], **opts)
97
97
  elsif should_union?(base_hash, key, opts[:inherit_mode])
98
98
  result[key] = base_hash[key] | derived_hash[key]
@@ -164,6 +164,10 @@ module RuboCop
164
164
  inherit_mode['merge'].include?(key)
165
165
  end
166
166
 
167
+ def merge_hashes?(base_hash, derived_hash, key)
168
+ base_hash[key].is_a?(Hash) && derived_hash[key].is_a?(Hash)
169
+ end
170
+
167
171
  def base_configs(path, inherit_from, file)
168
172
  configs = Array(inherit_from).compact.map do |f|
169
173
  ConfigLoader.load_file(inherited_file(path, f, file))
@@ -104,6 +104,7 @@ module RuboCop
104
104
  OBSOLETE_COPS = Hash[*(RENAMED_COPS + MOVED_COPS + REMOVED_COPS +
105
105
  REMOVED_COPS_WITH_REASON + SPLIT_COPS).flatten]
106
106
 
107
+ # Parameters can be deprecated but not disabled by setting `severity: :warning`
107
108
  OBSOLETE_PARAMETERS = [
108
109
  {
109
110
  cops: %w[Layout/SpaceAroundOperators Style/SpaceAroundOperators],
@@ -201,6 +202,12 @@ module RuboCop
201
202
  parameters: 'NameWhitelist',
202
203
  alternative: '`NameWhitelist` has been renamed to ' \
203
204
  '`AllowedMethods`.'
205
+ },
206
+ {
207
+ cops: %w[Metrics/BlockLength Metrics/MethodLength],
208
+ parameters: 'ExcludedMethods',
209
+ alternative: '`ExcludedMethods` has been renamed to `IgnoredMethods`.',
210
+ severity: :warning
204
211
  }
205
212
  ].freeze
206
213
 
@@ -214,8 +221,11 @@ module RuboCop
214
221
  }
215
222
  ].freeze
216
223
 
224
+ attr_reader :warnings
225
+
217
226
  def initialize(config)
218
227
  @config = config
228
+ @warnings = []
219
229
  end
220
230
 
221
231
  def reject_obsolete_cops_and_parameters
@@ -256,9 +266,17 @@ module RuboCop
256
266
  end
257
267
 
258
268
  def obsolete_parameters
259
- OBSOLETE_PARAMETERS.map do |params|
260
- obsolete_parameter_message(params[:cops], params[:parameters],
261
- params[:alternative])
269
+ OBSOLETE_PARAMETERS.collect do |params|
270
+ messages = obsolete_parameter_message(params[:cops], params[:parameters],
271
+ params[:alternative])
272
+
273
+ # Warnings are collected separately and not added to the error message
274
+ if messages && params.fetch(:severity, :error) == :warning
275
+ @warnings.concat(messages)
276
+ next
277
+ end
278
+
279
+ messages
262
280
  end
263
281
  end
264
282
 
@@ -42,7 +42,7 @@ module RuboCop
42
42
  ConfigLoader.default_configuration.key?(key)
43
43
  end
44
44
 
45
- @config_obsoletion.reject_obsolete_cops_and_parameters
45
+ check_obsoletions
46
46
 
47
47
  alert_about_unrecognized_cops(invalid_cop_names)
48
48
  check_target_ruby
@@ -68,6 +68,13 @@ module RuboCop
68
68
 
69
69
  attr_reader :target_ruby
70
70
 
71
+ def check_obsoletions
72
+ @config_obsoletion.reject_obsolete_cops_and_parameters
73
+ return unless @config_obsoletion.warnings.any?
74
+
75
+ warn Rainbow("Warning: #{@config_obsoletion.warnings.join("\n")}").yellow
76
+ end
77
+
71
78
  def check_target_ruby
72
79
  return if target_ruby.supported?
73
80
 
@@ -39,16 +39,31 @@ module RuboCop
39
39
  private
40
40
 
41
41
  def disable_offense(range)
42
- eol_comment = " # rubocop:todo #{cop_name}"
43
- needed_line_length = (range.source_line + eol_comment).length
44
- if needed_line_length <= max_line_length
45
- disable_offense_at_end_of_line(range_of_first_line(range),
46
- eol_comment)
42
+ heredoc_range = surrounding_heredoc(range)
43
+ if heredoc_range
44
+ disable_offense_before_and_after(range_by_lines(heredoc_range))
47
45
  else
48
- disable_offense_before_and_after(range_by_lines(range))
46
+ eol_comment = " # rubocop:todo #{cop_name}"
47
+ needed_line_length = (range.source_line + eol_comment).length
48
+ if needed_line_length <= max_line_length
49
+ disable_offense_at_end_of_line(range_of_first_line(range), eol_comment)
50
+ else
51
+ disable_offense_before_and_after(range_by_lines(range))
52
+ end
49
53
  end
50
54
  end
51
55
 
56
+ def surrounding_heredoc(offense_range)
57
+ # The empty offense range is an edge case that can be reached from the Lint/Syntax cop.
58
+ return nil if offense_range.empty?
59
+
60
+ heredoc_nodes = processed_source.ast.each_descendant.select do |node|
61
+ node.respond_to?(:heredoc?) && node.heredoc?
62
+ end
63
+ heredoc_nodes.map { |node| node.loc.expression.join(node.loc.heredoc_end) }
64
+ .find { |range| range.contains?(offense_range) }
65
+ end
66
+
52
67
  def range_of_first_line(range)
53
68
  begin_of_first_line = range.begin_pos - range.column
54
69
  end_of_first_line = begin_of_first_line + range.source_line.length
@@ -139,7 +139,7 @@ module RuboCop
139
139
  badge: badge,
140
140
  version_added: version_added)
141
141
 
142
- injector.inject do
142
+ injector.inject do # rubocop:disable Lint/UnexpectedBlockArity
143
143
  output.puts(format(CONFIGURATION_ADDED_MESSAGE,
144
144
  configuration_file_path: config_file_path))
145
145
  end
@@ -3,15 +3,15 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Layout
6
- # This cop checks whether method definitions are
7
- # separated by one empty line.
6
+ # This cop checks whether class/module/method definitions are
7
+ # separated by one or more empty lines.
8
8
  #
9
9
  # `NumberOfEmptyLines` can be an integer (default is 1) or
10
10
  # an array (e.g. [1, 2]) to specify a minimum and maximum
11
11
  # number of empty lines permitted.
12
12
  #
13
13
  # `AllowAdjacentOneLineDefs` configures whether adjacent
14
- # one-line method definitions are considered an offense.
14
+ # one-line definitions are considered an offense.
15
15
  #
16
16
  # @example EmptyLineBetweenMethodDefs: true (default)
17
17
  # # checks for empty lines between method definitions.
@@ -45,7 +45,8 @@ module RuboCop
45
45
  MSG = 'Empty line detected around arguments.'
46
46
 
47
47
  def on_send(node)
48
- return if node.single_line? || node.arguments.empty?
48
+ return if node.single_line? || node.arguments.empty? ||
49
+ receiver_and_method_call_on_different_lines?(node)
49
50
 
50
51
  extra_lines(node) do |range|
51
52
  add_offense(range) do |corrector|
@@ -57,6 +58,10 @@ module RuboCop
57
58
 
58
59
  private
59
60
 
61
+ def receiver_and_method_call_on_different_lines?(node)
62
+ node.receiver && node.receiver.loc.last_line != node.loc.selector&.line
63
+ end
64
+
60
65
  def empty_lines(node)
61
66
  lines = processed_lines(node)
62
67
  lines.select! { |code, _| code.empty? }
@@ -37,14 +37,14 @@ module RuboCop
37
37
  # # good
38
38
  # puts 'Hello' # Return character is CR+LF on all platfoms.
39
39
  #
40
- class EndOfLine < Cop
40
+ class EndOfLine < Base
41
41
  include ConfigurableEnforcedStyle
42
42
  include RangeHelp
43
43
 
44
44
  MSG_DETECTED = 'Carriage return character detected.'
45
45
  MSG_MISSING = 'Carriage return character missing.'
46
46
 
47
- def investigate(processed_source)
47
+ def on_new_investigation
48
48
  last_line = last_line(processed_source)
49
49
 
50
50
  processed_source.raw_source.each_line.with_index do |line, index|
@@ -54,9 +54,9 @@ module RuboCop
54
54
  next unless msg
55
55
  next if unimportant_missing_cr?(index, last_line, line)
56
56
 
57
- range =
58
- source_range(processed_source.buffer, index + 1, 0, line.length)
59
- add_offense(nil, location: range, message: msg)
57
+ range = source_range(processed_source.buffer, index + 1, 0, line.length)
58
+
59
+ add_offense(range, message: msg)
60
60
  # Usually there will be carriage return characters on all or none
61
61
  # of the lines in a file, so we report only one offense.
62
62
  break
@@ -207,8 +207,13 @@ module RuboCop
207
207
  PATTERN
208
208
 
209
209
  def base_range(send_node, arg_node)
210
- range_between(send_node.source_range.begin_pos,
211
- arg_node.source_range.begin_pos)
210
+ parent = send_node.parent
211
+ start_node = if parent && (parent.splat_type? || parent.kwsplat_type?)
212
+ send_node.parent
213
+ else
214
+ send_node
215
+ end
216
+ range_between(start_node.source_range.begin_pos, arg_node.source_range.begin_pos)
212
217
  end
213
218
 
214
219
  # Returns the column of the given range. For single line ranges, this
@@ -69,6 +69,7 @@ module RuboCop
69
69
  return unless outermost_send
70
70
  return unless outermost_send.loc.end
71
71
  return unless heredoc_arg.first_line != outermost_send.loc.end.line
72
+ return if subsequent_closing_parentheses_in_same_line?(outermost_send)
72
73
 
73
74
  add_offense(outermost_send.loc.end) do |corrector|
74
75
  autocorrect(corrector, outermost_send)
@@ -160,6 +161,17 @@ module RuboCop
160
161
 
161
162
  # Closing parenthesis helpers.
162
163
 
164
+ def subsequent_closing_parentheses_in_same_line?(outermost_send)
165
+ last_arg_of_outer_send = outermost_send.last_argument
166
+ return false unless last_arg_of_outer_send&.loc.respond_to?(:end) &&
167
+ (end_of_last_arg_of_outer_send = last_arg_of_outer_send.loc.end)
168
+
169
+ end_of_outer_send = outermost_send.loc.end
170
+
171
+ end_of_outer_send.line == end_of_last_arg_of_outer_send.line &&
172
+ end_of_outer_send.column == end_of_last_arg_of_outer_send.column + 1
173
+ end
174
+
163
175
  def fix_closing_parenthesis(node, corrector)
164
176
  remove_incorrect_closing_paren(node, corrector)
165
177
  add_correct_closing_paren(node, corrector)
@@ -77,7 +77,7 @@ module RuboCop
77
77
 
78
78
  @base = alignment_base(node, rhs, given_style)
79
79
  correct_column = if @base
80
- @base.column + extra_indentation(given_style)
80
+ @base.column + extra_indentation(given_style, node.parent)
81
81
  else
82
82
  indentation(lhs) + correct_indentation(node)
83
83
  end
@@ -85,9 +85,13 @@ module RuboCop
85
85
  rhs if @column_delta.nonzero?
86
86
  end
87
87
 
88
- def extra_indentation(given_style)
88
+ def extra_indentation(given_style, parent)
89
89
  if given_style == :indented_relative_to_receiver
90
- configured_indentation_width
90
+ if parent && (parent.splat_type? || parent.kwsplat_type?)
91
+ configured_indentation_width - parent.loc.operator.length
92
+ else
93
+ configured_indentation_width
94
+ end
91
95
  else
92
96
  0
93
97
  end
@@ -23,10 +23,11 @@ module RuboCop
23
23
  'Use double quoted strings if you need interpolation.'
24
24
 
25
25
  def on_str(node)
26
- parent = node.parent
27
- return if parent && (parent.dstr_type? || parent.regexp_type?)
26
+ return unless node
27
+ return if string_or_regex?(node.parent)
28
28
  return unless /(?<!\\)#\{.*\}/.match?(node.source)
29
29
  return if heredoc?(node)
30
+ return unless node.loc.begin && node.loc.end
30
31
 
31
32
  add_offense(node) do |corrector|
32
33
  autocorrect(corrector, node)
@@ -35,6 +36,10 @@ module RuboCop
35
36
 
36
37
  private
37
38
 
39
+ def string_or_regex?(node)
40
+ node&.dstr_type? || node&.regexp_type?
41
+ end
42
+
38
43
  def autocorrect(corrector, node)
39
44
  starting_token, ending_token = if node.source.include?('"')
40
45
  ['%{', '}']
@@ -40,7 +40,7 @@ module RuboCop
40
40
  # do_something
41
41
  # end
42
42
  #
43
- class NoReturnInBeginEndBlocks < Cop
43
+ class NoReturnInBeginEndBlocks < Base
44
44
  MSG = 'Do not `return` in `begin..end` blocks in assignment contexts.'
45
45
 
46
46
  def on_lvasgn(node)