rubocop 0.74.0 → 0.75.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/config/default.yml +27 -3
  4. data/lib/rubocop.rb +6 -1
  5. data/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +1 -12
  6. data/lib/rubocop/comment_config.rb +3 -2
  7. data/lib/rubocop/config.rb +4 -0
  8. data/lib/rubocop/config_loader.rb +20 -2
  9. data/lib/rubocop/config_loader_resolver.rb +2 -2
  10. data/lib/rubocop/config_obsoletion.rb +12 -0
  11. data/lib/rubocop/cop/autocorrect_logic.rb +2 -2
  12. data/lib/rubocop/cop/cop.rb +4 -3
  13. data/lib/rubocop/cop/correctors/alignment_corrector.rb +43 -17
  14. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +2 -2
  15. data/lib/rubocop/cop/generator.rb +3 -3
  16. data/lib/rubocop/cop/generator/configuration_injector.rb +9 -4
  17. data/lib/rubocop/cop/generator/require_file_injector.rb +1 -1
  18. data/lib/rubocop/cop/layout/block_alignment.rb +2 -2
  19. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +1 -1
  20. data/lib/rubocop/cop/layout/extra_spacing.rb +0 -6
  21. data/lib/rubocop/cop/layout/indent_assignment.rb +9 -1
  22. data/lib/rubocop/cop/layout/indent_heredoc.rb +1 -1
  23. data/lib/rubocop/cop/layout/multiline_block_layout.rb +24 -2
  24. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +5 -1
  25. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +1 -1
  26. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +7 -0
  27. data/lib/rubocop/cop/layout/space_inside_string_interpolation.rb +2 -0
  28. data/lib/rubocop/cop/lint/assignment_in_condition.rb +17 -4
  29. data/lib/rubocop/cop/lint/debugger.rb +1 -1
  30. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +10 -36
  31. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  32. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +91 -0
  33. data/lib/rubocop/cop/lint/unneeded_cop_disable_directive.rb +1 -1
  34. data/lib/rubocop/cop/message_annotator.rb +16 -7
  35. data/lib/rubocop/cop/migration/department_name.rb +44 -0
  36. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  37. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  38. data/lib/rubocop/cop/mixin/safe_mode.rb +2 -0
  39. data/lib/rubocop/cop/naming/method_name.rb +12 -1
  40. data/lib/rubocop/cop/naming/variable_name.rb +1 -0
  41. data/lib/rubocop/cop/offense.rb +18 -7
  42. data/lib/rubocop/cop/registry.rb +22 -1
  43. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -0
  44. data/lib/rubocop/cop/style/block_delimiters.rb +2 -1
  45. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +29 -10
  46. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -1
  47. data/lib/rubocop/cop/style/commented_keyword.rb +8 -2
  48. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -4
  49. data/lib/rubocop/cop/style/double_cop_disable_directive.rb +8 -2
  50. data/lib/rubocop/cop/style/format_string_token.rb +10 -40
  51. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +18 -33
  52. data/lib/rubocop/cop/style/if_unless_modifier.rb +51 -15
  53. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  54. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +5 -5
  55. data/lib/rubocop/cop/style/mixin_usage.rb +11 -1
  56. data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
  57. data/lib/rubocop/cop/style/nested_modifier.rb +18 -2
  58. data/lib/rubocop/cop/style/or_assignment.rb +6 -1
  59. data/lib/rubocop/cop/style/parentheses_around_condition.rb +14 -0
  60. data/lib/rubocop/cop/style/redundant_parentheses.rb +13 -4
  61. data/lib/rubocop/cop/style/redundant_self.rb +18 -1
  62. data/lib/rubocop/cop/style/rescue_modifier.rb +24 -0
  63. data/lib/rubocop/cop/style/safe_navigation.rb +9 -0
  64. data/lib/rubocop/cop/style/single_line_methods.rb +8 -1
  65. data/lib/rubocop/cop/style/ternary_parentheses.rb +19 -0
  66. data/lib/rubocop/cop/utils/format_string.rb +128 -0
  67. data/lib/rubocop/cop/variable_force/variable.rb +15 -2
  68. data/lib/rubocop/core_ext/string.rb +0 -24
  69. data/lib/rubocop/formatter/emacs_style_formatter.rb +8 -5
  70. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  71. data/lib/rubocop/formatter/pacman_formatter.rb +80 -0
  72. data/lib/rubocop/formatter/simple_text_formatter.rb +9 -1
  73. data/lib/rubocop/formatter/tap_formatter.rb +9 -1
  74. data/lib/rubocop/magic_comment.rb +4 -0
  75. data/lib/rubocop/options.rb +2 -1
  76. data/lib/rubocop/runner.rb +14 -8
  77. data/lib/rubocop/version.rb +1 -1
  78. metadata +6 -3
  79. data/lib/rubocop/cop/mixin/ignored_method_patterns.rb +0 -19
@@ -155,7 +155,7 @@ module RuboCop
155
155
  end
156
156
 
157
157
  def all_disabled?(comment)
158
- comment.text =~ /rubocop\s*:\s*disable\s+all\b/
158
+ comment.text =~ /rubocop\s*:\s*(?:disable|todo)\s+all\b/
159
159
  end
160
160
 
161
161
  def ignore_offense?(disabled_ranges, line_range)
@@ -9,11 +9,11 @@ module RuboCop
9
9
  #
10
10
  # @example
11
11
  # RuboCop::Cop::MessageAnnotator.new(
12
- # config, cop_config, @options
13
- # ).annotate('message', 'Cop/CopName')
12
+ # config, cop_name, cop_config, @options
13
+ # ).annotate('message')
14
14
  # #=> 'Cop/CopName: message (http://example.org/styleguide)'
15
15
  class MessageAnnotator
16
- attr_reader :options, :config, :cop_config
16
+ attr_reader :options, :config, :cop_name, :cop_config
17
17
 
18
18
  @style_guide_urls = {}
19
19
 
@@ -29,6 +29,7 @@ module RuboCop
29
29
  # :ExtraDetails [Boolean] Include cop details
30
30
  # :DisplayCopNames [Boolean] Include cop name
31
31
  #
32
+ # @param [String] cop_name for specific cop name
32
33
  # @param [Hash] cop_config configs for specific cop, from config#for_cop
33
34
  # @option cop_config [String] :StyleGuide Extension of base styleguide URL
34
35
  # @option cop_config [String] :Reference Full reference URL
@@ -43,8 +44,9 @@ module RuboCop
43
44
  # Include debug output
44
45
  # @option options [Boolean] :display_cop_names
45
46
  # Include cop name
46
- def initialize(config, cop_config, options)
47
+ def initialize(config, cop_name, cop_config, options)
47
48
  @config = config
49
+ @cop_name = cop_name
48
50
  @cop_config = cop_config || {}
49
51
  @options = options
50
52
  end
@@ -53,8 +55,8 @@ module RuboCop
53
55
  # based on params passed into initializer
54
56
  #
55
57
  # @return [String] annotated message
56
- def annotate(message, name)
57
- message = "#{name}: #{message}" if display_cop_names?
58
+ def annotate(message)
59
+ message = "#{cop_name}: #{message}" if display_cop_names?
58
60
  message += " #{details}" if extra_details? && details
59
61
  if display_style_guide?
60
62
  links = urls.join(', ')
@@ -74,7 +76,7 @@ module RuboCop
74
76
  return nil if url.nil? || url.empty?
75
77
 
76
78
  self.class.style_guide_urls[url] ||= begin
77
- base_url = config.for_all_cops['StyleGuideBaseURL']
79
+ base_url = style_guide_base_url
78
80
  if base_url.nil? || base_url.empty?
79
81
  url
80
82
  else
@@ -83,6 +85,13 @@ module RuboCop
83
85
  end
84
86
  end
85
87
 
88
+ def style_guide_base_url
89
+ department_name = cop_name.split('/').first
90
+
91
+ config.for_department(department_name)['StyleGuideBaseURL'] ||
92
+ config.for_all_cops['StyleGuideBaseURL']
93
+ end
94
+
86
95
  def display_style_guide?
87
96
  (options[:display_style_guide] ||
88
97
  config.for_all_cops['DisplayStyleGuide']) &&
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Migration
6
+ # Check that cop names in rubocop:disable comments are given with
7
+ # department name.
8
+ class DepartmentName < Cop
9
+ include RangeHelp
10
+
11
+ MSG = 'Department name is missing.'
12
+
13
+ def investigate(processed_source)
14
+ processed_source.each_comment do |comment|
15
+ next if comment.text !~ /\A(# *rubocop:((dis|en)able|todo) +)(.*)/
16
+
17
+ offset = Regexp.last_match(1).length
18
+ Regexp.last_match(4).scan(%r{[\w/]+|\W+}) do |name|
19
+ check_cop_name(name, comment, offset)
20
+ offset += name.length
21
+ end
22
+ end
23
+ end
24
+
25
+ def autocorrect(range)
26
+ shall_warn = false
27
+ qualified_cop_name = Cop.registry.qualified_cop_name(range.source,
28
+ nil, shall_warn)
29
+ ->(corrector) { corrector.replace(range, qualified_cop_name) }
30
+ end
31
+
32
+ private
33
+
34
+ def check_cop_name(name, comment, offset)
35
+ return if name !~ /^[A-Z]/ || name =~ %r{/}
36
+
37
+ start = comment.location.expression.begin_pos + offset
38
+ range = range_between(start, start + name.length)
39
+ add_offense(range, location: range)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -13,7 +13,7 @@ module RuboCop
13
13
 
14
14
  def configured_indentation_width
15
15
  cop_config['IndentationWidth'] ||
16
- config.for_cop('IndentationWidth')['Width']
16
+ config.for_cop('Layout/IndentationWidth')['Width']
17
17
  end
18
18
 
19
19
  def indentation(node)
@@ -12,7 +12,7 @@ module RuboCop
12
12
 
13
13
  def frozen_string_literal_comment_exists?
14
14
  leading_comment_lines.any? do |line|
15
- MagicComment.parse(line).frozen_string_literal_specified?
15
+ MagicComment.parse(line).valid_literal_value?
16
16
  end
17
17
  end
18
18
 
@@ -3,6 +3,8 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  # Common functionality for Rails safe mode.
6
+ #
7
+ # This module can be removed from RuboCop 0.76.
6
8
  module SafeMode
7
9
  private
8
10
 
@@ -6,6 +6,15 @@ module RuboCop
6
6
  # This cop makes sure that all methods use the configured style,
7
7
  # snake_case or camelCase, for their names.
8
8
  #
9
+ # This cop has `IgnoredPatterns` configuration option.
10
+ #
11
+ # Naming/MethodName:
12
+ # IgnoredPatterns:
13
+ # - '\A\s*onSelectionBulkChange\s*'
14
+ # - '\A\s*onSelectionCleared\s*'
15
+ #
16
+ # Method names matching patterns are always allowed.
17
+ #
9
18
  # @example EnforcedStyle: snake_case (default)
10
19
  # # bad
11
20
  # def fooBar; end
@@ -21,11 +30,13 @@ module RuboCop
21
30
  # def fooBar; end
22
31
  class MethodName < Cop
23
32
  include ConfigurableNaming
33
+ include IgnoredPattern
24
34
 
25
35
  MSG = 'Use %<style>s for method names.'
26
36
 
27
37
  def on_def(node)
28
- return if node.operator_method?
38
+ return if node.operator_method? ||
39
+ matches_ignored_pattern?(node.method_name)
29
40
 
30
41
  check_name(node, node.method_name, node.loc.name)
31
42
  end
@@ -39,6 +39,7 @@ module RuboCop
39
39
  alias on_kwarg on_lvasgn
40
40
  alias on_kwrestarg on_lvasgn
41
41
  alias on_blockarg on_lvasgn
42
+ alias on_lvar on_lvasgn
42
43
 
43
44
  private
44
45
 
@@ -67,23 +67,34 @@ module RuboCop
67
67
 
68
68
  # @api public
69
69
  #
70
- # @!attribute [r] corrected
70
+ # @!attribute [r] corrected?
71
71
  #
72
72
  # @return [Boolean]
73
- # whether this offense is automatically corrected.
74
- def corrected
75
- @status == :corrected
73
+ # whether this offense is automatically corrected via
74
+ # autocorrect or a todo.
75
+ def corrected?
76
+ @status == :corrected || @status == :corrected_with_todo
77
+ end
78
+
79
+ # @api public
80
+ #
81
+ # @!attribute [r] corrected_with_todo?
82
+ #
83
+ # @return [Boolean]
84
+ # whether this offense is automatically disabled via a todo.
85
+ def corrected_with_todo?
86
+ @status == :corrected_with_todo
76
87
  end
77
- alias corrected? corrected
78
88
 
79
89
  # @api public
80
90
  #
81
91
  # @!attribute [r] disabled?
82
92
  #
83
93
  # @return [Boolean]
84
- # whether this offense was locally disabled where it occurred
94
+ # whether this offense was locally disabled with a
95
+ # disable or todo where it occurred.
85
96
  def disabled?
86
- @status == :disabled
97
+ @status == :disabled || @status == :todo
87
98
  end
88
99
 
89
100
  # @api public
@@ -91,8 +91,11 @@ module RuboCop
91
91
  # @note Emits a warning if the provided name has an incorrect namespace
92
92
  #
93
93
  # @return [String] Qualified cop name
94
- def qualified_cop_name(name, path)
94
+ def qualified_cop_name(name, path, shall_warn = true)
95
95
  badge = Badge.parse(name)
96
+ if shall_warn && department_missing?(badge, name)
97
+ print_warning(name, path)
98
+ end
96
99
  return name if registered?(badge)
97
100
 
98
101
  potential_badges = qualify_badge(badge)
@@ -104,6 +107,24 @@ module RuboCop
104
107
  end
105
108
  end
106
109
 
110
+ def department_missing?(badge, name)
111
+ !badge.qualified? && unqualified_cop_names.include?(name)
112
+ end
113
+
114
+ def print_warning(name, path)
115
+ message = "#{path}: Warning: no department given for #{name}."
116
+ if path.end_with?('.rb')
117
+ message += ' Run `rubocop -a --only Migration/DepartmentName` to fix.'
118
+ end
119
+ warn message
120
+ end
121
+
122
+ def unqualified_cop_names
123
+ @unqualified_cop_names ||=
124
+ Set.new(@cops_by_cop_name.keys.map { |qn| File.basename(qn) }) <<
125
+ 'UnneededCopDisableDirective'
126
+ end
127
+
107
128
  # @return [Hash{String => Array<Class>}]
108
129
  def to_h
109
130
  @cops_by_cop_name
@@ -63,6 +63,7 @@ module RuboCop
63
63
 
64
64
  def on_send(node)
65
65
  return unless node.access_modifier?
66
+ return if node.parent.pair_type?
66
67
 
67
68
  if offense?(node)
68
69
  add_offense(node, location: :selector) do
@@ -114,7 +114,8 @@ module RuboCop
114
114
 
115
115
  def on_send(node)
116
116
  return unless node.arguments?
117
- return if node.parenthesized? || node.operator_method?
117
+ return if node.parenthesized?
118
+ return if node.operator_method? || node.assignment_method?
118
119
 
119
120
  node.arguments.each do |arg|
120
121
  get_blocks(arg) do |block|
@@ -55,10 +55,6 @@ module RuboCop
55
55
  end
56
56
  alias on_csend on_send
57
57
 
58
- # We let AutocorrectUnlessChangingAST#autocorrect work with the send
59
- # node, because that context is needed. When parsing the code to see if
60
- # the AST has changed, a braceless hash would not be parsed as a hash
61
- # otherwise.
62
58
  def autocorrect(send_node)
63
59
  hash_node = send_node.last_argument
64
60
 
@@ -76,20 +72,32 @@ module RuboCop
76
72
  private
77
73
 
78
74
  def check(arg, args)
79
- if style == :braces && !arg.braces?
80
- add_arg_offense(arg, :missing)
81
- elsif style == :no_braces && arg.braces?
82
- add_arg_offense(arg, :redundant)
83
- elsif style == :context_dependent
75
+ case style
76
+ when :braces
77
+ check_braces(arg)
78
+ when :no_braces
79
+ check_no_braces(arg)
80
+ when :context_dependent
84
81
  check_context_dependent(arg, args)
85
82
  end
86
83
  end
87
84
 
85
+ def check_braces(arg)
86
+ add_arg_offense(arg, :missing) unless arg.braces?
87
+ end
88
+
89
+ def check_no_braces(arg)
90
+ return unless arg.braces? && !braces_needed_for_semantics?(arg)
91
+
92
+ add_arg_offense(arg, :redundant)
93
+ end
94
+
88
95
  def check_context_dependent(arg, args)
89
96
  braces_around_second_from_end = args.size > 1 && args[-2].hash_type?
90
97
 
91
98
  if arg.braces?
92
- unless braces_around_second_from_end
99
+ unless braces_around_second_from_end ||
100
+ braces_needed_for_semantics?(arg)
93
101
  add_arg_offense(arg, :redundant)
94
102
  end
95
103
  elsif braces_around_second_from_end
@@ -97,6 +105,17 @@ module RuboCop
97
105
  end
98
106
  end
99
107
 
108
+ # Returns true if there's block inside the braces of the given hash arg
109
+ # and that block uses do..end. The reason for wanting to check this is
110
+ # that the do..end could bind to a different method invocation if the
111
+ # hash braces were removed.
112
+ def braces_needed_for_semantics?(arg)
113
+ arg.each_pair do |_key, value|
114
+ return true if value.block_type? && !value.braces?
115
+ end
116
+ false
117
+ end
118
+
100
119
  def add_arg_offense(arg, type)
101
120
  add_offense(arg.parent, location: arg.source_range,
102
121
  message: format(MSG,
@@ -115,7 +115,7 @@ module RuboCop
115
115
  end
116
116
 
117
117
  def indent_width
118
- @config.for_cop('IndentationWidth')['Width'] || 2
118
+ @config.for_cop('Layout/IndentationWidth')['Width'] || 2
119
119
  end
120
120
 
121
121
  def check_style(node, body)
@@ -6,7 +6,8 @@ module RuboCop
6
6
  # This cop checks for comments put on the same line as some keywords.
7
7
  # These keywords are: `begin`, `class`, `def`, `end`, `module`.
8
8
  #
9
- # Note that some comments (`:nodoc:`, `:yields:`, and `rubocop:disable`)
9
+ # Note that some comments
10
+ # (`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`)
10
11
  # are allowed.
11
12
  #
12
13
  # @example
@@ -45,7 +46,12 @@ module RuboCop
45
46
  private
46
47
 
47
48
  KEYWORDS = %w[begin class def end module].freeze
48
- ALLOWED_COMMENTS = %w[:nodoc: :yields: rubocop:disable].freeze
49
+ ALLOWED_COMMENTS = %w[
50
+ :nodoc:
51
+ :yields:
52
+ rubocop:disable
53
+ rubocop:todo
54
+ ].freeze
49
55
 
50
56
  def offensive?(comment)
51
57
  line = line(comment)
@@ -72,12 +72,12 @@ module RuboCop
72
72
 
73
73
  elsif_branches << node.if_branch
74
74
 
75
- if node.else_branch&.if_type?
76
- expand_elsif(node.else_branch, elsif_branches)
75
+ else_branch = node.else_branch
76
+ if else_branch&.if_type? && else_branch&.elsif?
77
+ expand_elsif(else_branch, elsif_branches)
77
78
  else
78
- elsif_branches << node.else_branch
79
+ elsif_branches << else_branch
79
80
  end
80
- elsif_branches
81
81
  end
82
82
 
83
83
  def lhs_for_send(node)
@@ -31,16 +31,22 @@ module RuboCop
31
31
 
32
32
  def investigate(processed_source)
33
33
  processed_source.comments.each do |comment|
34
- next unless comment.text.scan('# rubocop:disable').size > 1
34
+ next unless comment.text.scan(/# rubocop:(?:disable|todo)/).size > 1
35
35
 
36
36
  add_offense(comment)
37
37
  end
38
38
  end
39
39
 
40
40
  def autocorrect(comment)
41
+ prefix = if comment.text.start_with?('# rubocop:disable')
42
+ '# rubocop:disable'
43
+ else
44
+ '# rubocop:todo'
45
+ end
46
+
41
47
  lambda do |corrector|
42
48
  corrector.replace(comment.loc.expression,
43
- comment.text[/# rubocop:disable \S+/])
49
+ comment.text[/#{prefix} \S+/])
44
50
  end
45
51
  end
46
52
  end
@@ -41,15 +41,8 @@ module RuboCop
41
41
  class FormatStringToken < Cop
42
42
  include ConfigurableEnforcedStyle
43
43
 
44
- FIELD_CHARACTERS = Regexp.union(%w[A B E G X a b c d e f g i o p s u x])
45
44
  FORMAT_STRING_METHODS = %i[sprintf format %].freeze
46
45
 
47
- STYLE_PATTERNS = {
48
- annotated: /(?<token>%<[^>]+>#{FIELD_CHARACTERS})/,
49
- template: /(?<token>%\{[^\}]+\})/,
50
- unannotated: /(?<token>%#{FIELD_CHARACTERS})/
51
- }.freeze
52
-
53
46
  def on_str(node)
54
47
  return if placeholder_argument?(node)
55
48
  return if node.each_ancestor(:xstr, :regexp).any?
@@ -113,41 +106,18 @@ module RuboCop
113
106
  end
114
107
 
115
108
  def token_ranges(contents)
116
- while (offending_match = match_token(contents))
117
- detected_style, *range = *offending_match
118
- token, contents = split_token(contents, *range)
119
- yield(detected_style, token)
120
- end
121
- end
122
-
123
- def match_token(source_range)
124
- supported_styles.each do |style_name|
125
- pattern = STYLE_PATTERNS.fetch(style_name)
126
- match = source_range.source.match(pattern)
127
- next unless match
128
-
129
- return [style_name, match.begin(:token), match.end(:token)]
130
- end
131
-
132
- nil
133
- end
134
-
135
- def split_token(source_range, match_begin, match_end)
136
- token =
137
- slice_source(
138
- source_range,
139
- source_range.begin_pos + match_begin,
140
- source_range.begin_pos + match_end
141
- )
142
-
143
- remainder =
144
- slice_source(
145
- source_range,
146
- source_range.begin_pos + match_end,
147
- source_range.end_pos
109
+ format_string = RuboCop::Cop::Utils::FormatString.new(contents.source)
110
+
111
+ format_string.format_sequences.each do |seq|
112
+ detected_style = seq.style
113
+ token = slice_source(
114
+ contents,
115
+ contents.begin_pos + seq.begin_pos,
116
+ contents.begin_pos + seq.end_pos
148
117
  )
149
118
 
150
- [token, remainder]
119
+ yield(detected_style, token)
120
+ end
151
121
  end
152
122
 
153
123
  def slice_source(source_range, new_begin, new_end)