rubocop 0.74.0 → 0.75.1

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/config/default.yml +28 -4
  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/correctors/percent_literal_corrector.rb +1 -1
  16. data/lib/rubocop/cop/generator.rb +3 -3
  17. data/lib/rubocop/cop/generator/configuration_injector.rb +9 -4
  18. data/lib/rubocop/cop/generator/require_file_injector.rb +1 -1
  19. data/lib/rubocop/cop/layout/block_alignment.rb +2 -2
  20. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +1 -1
  21. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +22 -7
  22. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +2 -2
  23. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +2 -2
  24. data/lib/rubocop/cop/layout/extra_spacing.rb +0 -6
  25. data/lib/rubocop/cop/layout/indent_assignment.rb +10 -1
  26. data/lib/rubocop/cop/layout/indent_heredoc.rb +1 -1
  27. data/lib/rubocop/cop/layout/multiline_block_layout.rb +24 -2
  28. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +5 -1
  29. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +1 -1
  30. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +7 -0
  31. data/lib/rubocop/cop/layout/space_inside_string_interpolation.rb +2 -0
  32. data/lib/rubocop/cop/lint/assignment_in_condition.rb +17 -4
  33. data/lib/rubocop/cop/lint/debugger.rb +1 -1
  34. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +10 -36
  35. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  36. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +91 -0
  37. data/lib/rubocop/cop/lint/unneeded_cop_disable_directive.rb +1 -1
  38. data/lib/rubocop/cop/lint/unused_block_argument.rb +22 -6
  39. data/lib/rubocop/cop/lint/unused_method_argument.rb +23 -5
  40. data/lib/rubocop/cop/lint/void.rb +3 -22
  41. data/lib/rubocop/cop/message_annotator.rb +16 -7
  42. data/lib/rubocop/cop/migration/department_name.rb +44 -0
  43. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  44. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  45. data/lib/rubocop/cop/mixin/safe_mode.rb +2 -0
  46. data/lib/rubocop/cop/naming/method_name.rb +12 -1
  47. data/lib/rubocop/cop/naming/variable_name.rb +1 -0
  48. data/lib/rubocop/cop/offense.rb +18 -7
  49. data/lib/rubocop/cop/registry.rb +22 -1
  50. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -0
  51. data/lib/rubocop/cop/style/block_delimiters.rb +2 -1
  52. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +29 -10
  53. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -1
  54. data/lib/rubocop/cop/style/commented_keyword.rb +8 -2
  55. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -4
  56. data/lib/rubocop/cop/style/documentation_method.rb +44 -0
  57. data/lib/rubocop/cop/style/double_cop_disable_directive.rb +8 -2
  58. data/lib/rubocop/cop/style/expand_path_arguments.rb +1 -1
  59. data/lib/rubocop/cop/style/format_string_token.rb +18 -69
  60. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +18 -33
  61. data/lib/rubocop/cop/style/if_unless_modifier.rb +51 -15
  62. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  63. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -25
  64. data/lib/rubocop/cop/style/mixin_usage.rb +11 -1
  65. data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
  66. data/lib/rubocop/cop/style/nested_modifier.rb +18 -2
  67. data/lib/rubocop/cop/style/or_assignment.rb +6 -1
  68. data/lib/rubocop/cop/style/parentheses_around_condition.rb +14 -0
  69. data/lib/rubocop/cop/style/redundant_parentheses.rb +13 -4
  70. data/lib/rubocop/cop/style/redundant_return.rb +12 -0
  71. data/lib/rubocop/cop/style/redundant_self.rb +18 -1
  72. data/lib/rubocop/cop/style/rescue_modifier.rb +24 -0
  73. data/lib/rubocop/cop/style/safe_navigation.rb +17 -0
  74. data/lib/rubocop/cop/style/semicolon.rb +11 -0
  75. data/lib/rubocop/cop/style/single_line_methods.rb +8 -1
  76. data/lib/rubocop/cop/style/ternary_parentheses.rb +19 -0
  77. data/lib/rubocop/cop/utils/format_string.rb +128 -0
  78. data/lib/rubocop/cop/variable_force/variable.rb +15 -2
  79. data/lib/rubocop/core_ext/string.rb +0 -24
  80. data/lib/rubocop/formatter/clang_style_formatter.rb +8 -3
  81. data/lib/rubocop/formatter/emacs_style_formatter.rb +22 -9
  82. data/lib/rubocop/formatter/file_list_formatter.rb +1 -1
  83. data/lib/rubocop/formatter/formatter_set.rb +16 -15
  84. data/lib/rubocop/formatter/pacman_formatter.rb +80 -0
  85. data/lib/rubocop/formatter/simple_text_formatter.rb +16 -4
  86. data/lib/rubocop/formatter/tap_formatter.rb +17 -4
  87. data/lib/rubocop/magic_comment.rb +4 -0
  88. data/lib/rubocop/options.rb +5 -16
  89. data/lib/rubocop/runner.rb +14 -8
  90. data/lib/rubocop/version.rb +1 -1
  91. metadata +6 -3
  92. data/lib/rubocop/cop/mixin/ignored_method_patterns.rb +0 -19
@@ -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)
@@ -47,6 +47,50 @@ module RuboCop
47
47
  # def foo.bar
48
48
  # puts baz
49
49
  # end
50
+ #
51
+ # @example RequireForNonPublicMethods: false (default)
52
+ # # good
53
+ # class Foo
54
+ # protected
55
+ # def do_something
56
+ # end
57
+ # end
58
+ #
59
+ # class Foo
60
+ # private
61
+ # def do_something
62
+ # end
63
+ # end
64
+ #
65
+ # @example RequireForNonPublicMethods: true
66
+ # # bad
67
+ # class Foo
68
+ # protected
69
+ # def do_something
70
+ # end
71
+ # end
72
+ #
73
+ # class Foo
74
+ # private
75
+ # def do_something
76
+ # end
77
+ # end
78
+ #
79
+ # # good
80
+ # class Foo
81
+ # protected
82
+ # # Documentation
83
+ # def do_something
84
+ # end
85
+ # end
86
+ #
87
+ # class Foo
88
+ # private
89
+ # # Documentation
90
+ # def do_something
91
+ # end
92
+ # end
93
+ #
50
94
  class DocumentationMethod < Cop
51
95
  include DocumentationComment
52
96
  include DefNode
@@ -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
@@ -160,7 +160,7 @@ module RuboCop
160
160
  def depth(current_path)
161
161
  paths = current_path.split(File::SEPARATOR)
162
162
 
163
- paths.reject { |path| path == '.' }.count
163
+ paths.count { |path| path != '.' }
164
164
  end
165
165
 
166
166
  def parent_path(current_path)
@@ -8,7 +8,7 @@ module RuboCop
8
8
  # **Note:**
9
9
  # `unannotated` style cop only works for strings
10
10
  # which are passed as arguments to those methods:
11
- # `sprintf`, `format`, `%`.
11
+ # `printf`, `sprintf`, `format`, `%`.
12
12
  # The reason is that *unannotated* format is very similar
13
13
  # to encoded URLs or Date/Time formatting strings.
14
14
  #
@@ -41,17 +41,7 @@ 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
- FORMAT_STRING_METHODS = %i[sprintf format %].freeze
46
-
47
- STYLE_PATTERNS = {
48
- annotated: /(?<token>%<[^>]+>#{FIELD_CHARACTERS})/,
49
- template: /(?<token>%\{[^\}]+\})/,
50
- unannotated: /(?<token>%#{FIELD_CHARACTERS})/
51
- }.freeze
52
-
53
44
  def on_str(node)
54
- return if placeholder_argument?(node)
55
45
  return if node.each_ancestor(:xstr, :regexp).any?
56
46
 
57
47
  tokens(node) do |detected_style, token_range|
@@ -68,14 +58,16 @@ module RuboCop
68
58
 
69
59
  private
70
60
 
71
- def includes_format_methods?(node)
72
- node.each_ancestor(:send).any? do |ancestor|
73
- FORMAT_STRING_METHODS.include?(ancestor.method_name)
74
- end
75
- end
61
+ def_node_matcher :format_string_in_typical_context?, <<~PATTERN
62
+ {
63
+ ^(send _ {:format :sprintf :printf} %0 ...)
64
+ ^(send %0 :% _)
65
+ }
66
+ PATTERN
76
67
 
77
68
  def unannotated_format?(node, detected_style)
78
- detected_style == :unannotated && !includes_format_methods?(node)
69
+ detected_style == :unannotated &&
70
+ !format_string_in_typical_context?(node)
79
71
  end
80
72
 
81
73
  def message(detected_style)
@@ -102,67 +94,24 @@ module RuboCop
102
94
  if source_map.is_a?(Parser::Source::Map::Heredoc)
103
95
  source_map.heredoc_body
104
96
  elsif source_map.begin
105
- slice_source(
106
- source_map.expression,
107
- source_map.expression.begin_pos + 1,
108
- source_map.expression.end_pos - 1
109
- )
97
+ source_map.expression.adjust(begin_pos: +1, end_pos: -1)
110
98
  else
111
99
  source_map.expression
112
100
  end
113
101
  end
114
102
 
115
103
  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
104
+ format_string = RuboCop::Cop::Utils::FormatString.new(contents.source)
131
105
 
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
106
+ format_string.format_sequences.each do |seq|
107
+ detected_style = seq.style
108
+ token = contents.begin.adjust(
109
+ begin_pos: seq.begin_pos,
110
+ end_pos: seq.end_pos
148
111
  )
149
112
 
150
- [token, remainder]
151
- end
152
-
153
- def slice_source(source_range, new_begin, new_end)
154
- Parser::Source::Range.new(
155
- source_range.source_buffer,
156
- new_begin,
157
- new_end
158
- )
159
- end
160
-
161
- def placeholder_argument?(node)
162
- return false unless node.parent
163
- return true if node.parent.pair_type?
164
-
165
- placeholder_argument?(node.parent)
113
+ yield(detected_style, token)
114
+ end
166
115
  end
167
116
  end
168
117
  end
@@ -3,11 +3,13 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # This cop is designed to help upgrade to after Ruby 3.0. It will add the
7
- # comment `# frozen_string_literal: true` to the top of files to
8
- # enable frozen string literals. Frozen string literals may be default
9
- # after Ruby 3.0. The comment will be added below a shebang and encoding
10
- # comment. The frozen string literal comment is only valid in Ruby 2.3+.
6
+ # This cop is designed to help you transition from mutable string literals
7
+ # to frozen string literals.
8
+ # It will add the comment `# frozen_string_literal: true` to the top of
9
+ # files to enable frozen string literals. Frozen string literals may be
10
+ # default in future Ruby. The comment will be added below a shebang and
11
+ # encoding comment. The frozen string literal comment is only valid in
12
+ # Ruby 2.3+.
11
13
  #
12
14
  # @example EnforcedStyle: always (default)
13
15
  # # The `always` style will always add the frozen string literal comment
@@ -121,42 +123,25 @@ module RuboCop
121
123
  end
122
124
 
123
125
  def insert_comment(corrector)
124
- last_special_comment = last_special_comment(processed_source)
125
- if last_special_comment.nil?
126
- corrector.insert_before(correction_range, preceding_comment)
127
- else
128
- corrector.insert_after(correction_range, proceeding_comment)
129
- end
130
- end
126
+ comment = last_special_comment(processed_source)
131
127
 
132
- def preceding_comment
133
- if processed_source.tokens[0].space_before?
134
- "#{FROZEN_STRING_LITERAL_ENABLED}\n"
128
+ if comment
129
+ corrector.insert_after(line_range(comment.line), following_comment)
135
130
  else
136
- "#{FROZEN_STRING_LITERAL_ENABLED}\n\n"
131
+ corrector.insert_before(line_range(1), preceding_comment)
137
132
  end
138
133
  end
139
134
 
140
- def proceeding_comment
141
- last_special_comment = last_special_comment(processed_source)
142
- following_line = processed_source.following_line(last_special_comment)
143
-
144
- if following_line&.empty?
145
- "\n#{FROZEN_STRING_LITERAL_ENABLED}"
146
- else
147
- "\n#{FROZEN_STRING_LITERAL_ENABLED}\n"
148
- end
135
+ def line_range(line)
136
+ processed_source.buffer.line_range(line)
149
137
  end
150
138
 
151
- def correction_range
152
- last_special_comment = last_special_comment(processed_source)
139
+ def preceding_comment
140
+ "#{FROZEN_STRING_LITERAL_ENABLED}\n"
141
+ end
153
142
 
154
- if last_special_comment.nil?
155
- range_with_surrounding_space(range: processed_source.tokens[0],
156
- side: :left)
157
- else
158
- last_special_comment.pos
159
- end
143
+ def following_comment
144
+ "\n#{FROZEN_STRING_LITERAL_ENABLED}"
160
145
  end
161
146
  end
162
147
  end