rubocop 1.11.0 → 1.12.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/config/default.yml +16 -1
  4. data/lib/rubocop.rb +1 -0
  5. data/lib/rubocop/cli/command/suggest_extensions.rb +3 -2
  6. data/lib/rubocop/comment_config.rb +43 -94
  7. data/lib/rubocop/cop/correctors/alignment_corrector.rb +3 -6
  8. data/lib/rubocop/cop/layout/access_modifier_indentation.rb +11 -8
  9. data/lib/rubocop/cop/layout/argument_alignment.rb +6 -5
  10. data/lib/rubocop/cop/layout/array_alignment.rb +7 -6
  11. data/lib/rubocop/cop/layout/assignment_indentation.rb +6 -3
  12. data/lib/rubocop/cop/layout/block_end_newline.rb +4 -8
  13. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +14 -15
  14. data/lib/rubocop/cop/layout/comment_indentation.rb +16 -16
  15. data/lib/rubocop/cop/layout/else_alignment.rb +9 -6
  16. data/lib/rubocop/cop/layout/first_argument_indentation.rb +6 -5
  17. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +9 -6
  18. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +22 -15
  19. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +6 -5
  20. data/lib/rubocop/cop/layout/indentation_consistency.rb +9 -6
  21. data/lib/rubocop/cop/layout/indentation_style.rb +27 -30
  22. data/lib/rubocop/cop/layout/indentation_width.rb +19 -9
  23. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +6 -5
  24. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +6 -5
  25. data/lib/rubocop/cop/layout/parameter_alignment.rb +6 -5
  26. data/lib/rubocop/cop/lint/number_conversion.rb +7 -0
  27. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +1 -2
  28. data/lib/rubocop/cop/lint/suppressed_exception.rb +44 -1
  29. data/lib/rubocop/cop/lint/symbol_conversion.rb +89 -2
  30. data/lib/rubocop/cop/mixin/alignment.rb +10 -3
  31. data/lib/rubocop/cop/mixin/comments_help.rb +5 -1
  32. data/lib/rubocop/cop/mixin/documentation_comment.rb +1 -1
  33. data/lib/rubocop/cop/mixin/line_length_help.rb +11 -6
  34. data/lib/rubocop/cop/mixin/multiline_element_indentation.rb +3 -1
  35. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +4 -3
  36. data/lib/rubocop/cop/mixin/preferred_delimiters.rb +1 -1
  37. data/lib/rubocop/cop/mixin/uncommunicative_name.rb +4 -6
  38. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +4 -0
  39. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +10 -0
  40. data/lib/rubocop/cop/registry.rb +9 -0
  41. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -1
  42. data/lib/rubocop/cop/style/bisected_attr_accessor.rb +59 -71
  43. data/lib/rubocop/cop/style/bisected_attr_accessor/macro.rb +62 -0
  44. data/lib/rubocop/cop/style/case_like_if.rb +15 -4
  45. data/lib/rubocop/cop/style/class_equality_comparison.rb +2 -0
  46. data/lib/rubocop/cop/style/command_literal.rb +1 -1
  47. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +2 -2
  48. data/lib/rubocop/cop/style/documentation.rb +25 -3
  49. data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
  50. data/lib/rubocop/cop/style/hash_syntax.rb +16 -15
  51. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +40 -0
  52. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +20 -2
  53. data/lib/rubocop/cop/style/negated_if_else_condition.rb +15 -2
  54. data/lib/rubocop/cop/style/redundant_begin.rb +26 -3
  55. data/lib/rubocop/cop/style/redundant_return.rb +4 -0
  56. data/lib/rubocop/cop/style/redundant_self.rb +7 -3
  57. data/lib/rubocop/cop/style/regexp_literal.rb +1 -1
  58. data/lib/rubocop/cop/style/rescue_modifier.rb +17 -14
  59. data/lib/rubocop/cop/style/sole_nested_conditional.rb +16 -0
  60. data/lib/rubocop/cop/style/string_chars.rb +38 -0
  61. data/lib/rubocop/cop/style/struct_inheritance.rb +2 -0
  62. data/lib/rubocop/cop/style/trailing_body_on_method_definition.rb +5 -1
  63. data/lib/rubocop/cop/style/unless_logical_operators.rb +8 -2
  64. data/lib/rubocop/directive_comment.rb +64 -9
  65. data/lib/rubocop/ext/regexp_parser.rb +3 -6
  66. data/lib/rubocop/magic_comment.rb +1 -1
  67. data/lib/rubocop/target_finder.rb +1 -0
  68. data/lib/rubocop/version.rb +1 -1
  69. metadata +5 -3
@@ -16,7 +16,7 @@ module RuboCop
16
16
 
17
17
  return false unless comment
18
18
 
19
- comment.text.match?(CommentConfig::COMMENT_DIRECTIVE_REGEXP)
19
+ !!DirectiveComment.new(comment).match_captures
20
20
  end
21
21
 
22
22
  def allow_uri?
@@ -24,9 +24,7 @@ module RuboCop
24
24
  end
25
25
 
26
26
  def allowed_uri_position?(line, uri_range)
27
- uri_range.begin < max_line_length &&
28
- (uri_range.end == line_length(line) ||
29
- uri_range.end == line_length(line) - 1)
27
+ uri_range.begin < max_line_length && uri_range.end == line_length(line)
30
28
  end
31
29
 
32
30
  def line_length(line)
@@ -40,6 +38,14 @@ module RuboCop
40
38
  begin_position, end_position = last_uri_match.offset(0).map do |pos|
41
39
  pos + indentation_difference(line)
42
40
  end
41
+
42
+ # Extend the end position until the start of the next word, if any.
43
+ # This allows for URIs that are wrapped in quotes or parens to be handled properly
44
+ # while not allowing additional words to be added after the URL.
45
+ if (match = line[end_position..line_length(line)]&.match(/^\S+(?=\s|$)/))
46
+ end_position += match.offset(0).last
47
+ end
48
+
43
49
  return nil if begin_position < max_line_length &&
44
50
  end_position < max_line_length
45
51
 
@@ -79,8 +85,7 @@ module RuboCop
79
85
  end
80
86
 
81
87
  def line_length_without_directive(line)
82
- before_comment, = line.split(CommentConfig::COMMENT_DIRECTIVE_REGEXP)
83
- before_comment.rstrip.length
88
+ DirectiveComment.before_comment(line).rstrip.length
84
89
  end
85
90
  end
86
91
  end
@@ -77,7 +77,9 @@ module RuboCop
77
77
  def incorrect_style_detected(styles, first, left_parenthesis)
78
78
  msg = message(base_description(left_parenthesis))
79
79
 
80
- add_offense(first, message: msg) do
80
+ add_offense(first, message: msg) do |corrector|
81
+ autocorrect(corrector, first)
82
+
81
83
  ambiguous_style_detected(*styles)
82
84
  end
83
85
  end
@@ -91,9 +91,10 @@ module RuboCop
91
91
  end
92
92
 
93
93
  def incorrect_style_detected(range, node, lhs, rhs)
94
- add_offense(range, location: range, message: message(node, lhs, rhs)) do
95
- if supported_styles.size > 2 ||
96
- offending_range(node, lhs, rhs, alternative_style)
94
+ add_offense(range, message: message(node, lhs, rhs)) do |corrector|
95
+ autocorrect(corrector, range)
96
+
97
+ if supported_styles.size > 2 || offending_range(node, lhs, rhs, alternative_style)
97
98
  unrecognized_style_detected
98
99
  else
99
100
  opposite_style_detected
@@ -15,7 +15,7 @@ module RuboCop
15
15
  end
16
16
 
17
17
  def delimiters
18
- preferred_delimiters[type].split('')
18
+ preferred_delimiters[type].chars
19
19
  end
20
20
 
21
21
  private
@@ -53,12 +53,10 @@ module RuboCop
53
53
  end
54
54
 
55
55
  def name_type(node)
56
- @name_type ||= begin
57
- case node.type
58
- when :block then 'block parameter'
59
- when :def, :defs then 'method parameter'
60
- end
61
- end
56
+ @name_type ||= case node.type
57
+ when :block then 'block parameter'
58
+ when :def, :defs then 'method parameter'
59
+ end
62
60
  end
63
61
 
64
62
  def num_offense(node, range)
@@ -139,6 +139,10 @@ module RuboCop
139
139
  # define_method(:foo) do
140
140
  # @_foo ||= calculate_expensive_thing
141
141
  # end
142
+ #
143
+ # This cop relies on the pattern `@instance_var ||= ...`,
144
+ # but this is sometimes used for other purposes than memoization
145
+ # so this cop is considered unsafe.
142
146
  class MemoizedInstanceVariableName < Base
143
147
  include ConfigurableEnforcedStyle
144
148
 
@@ -9,6 +9,11 @@ module RuboCop
9
9
  # The `PreferredName` config option takes a `String`. It represents
10
10
  # the required name of the variable. Its default is `e`.
11
11
  #
12
+ # NOTE: This cop does not consider nested rescues because it cannot
13
+ # guarantee that the variable from the outer rescue is not used within
14
+ # the inner rescue (in which case, changing the inner variable would
15
+ # shadow the outer variable).
16
+ #
12
17
  # @example PreferredName: e (default)
13
18
  # # bad
14
19
  # begin
@@ -62,6 +67,11 @@ module RuboCop
62
67
  offending_name = variable_name(node)
63
68
  return unless offending_name
64
69
 
70
+ # Handle nested rescues by only requiring the outer one to use the
71
+ # configured variable name, so that nested rescues don't use the same
72
+ # variable.
73
+ return if node.each_ancestor(:resbody).any?
74
+
65
75
  preferred_name = preferred_name(offending_name)
66
76
  return if preferred_name.to_sym == offending_name
67
77
 
@@ -64,6 +64,11 @@ module RuboCop
64
64
  with(without_department.values.flatten)
65
65
  end
66
66
 
67
+ # @return [Boolean] Checks if given name is department
68
+ def department?(name)
69
+ departments.include? name.to_sym
70
+ end
71
+
67
72
  def contains_cop_matching?(names)
68
73
  cops.any? { |cop| cop.match?(names) }
69
74
  end
@@ -179,6 +184,10 @@ module RuboCop
179
184
  cops.map(&:cop_name)
180
185
  end
181
186
 
187
+ def names_for_department(department)
188
+ cops.select { |cop| cop.department == department.to_sym }.map(&:cop_name)
189
+ end
190
+
182
191
  def ==(other)
183
192
  cops == other.cops
184
193
  end
@@ -79,7 +79,7 @@ module RuboCop
79
79
 
80
80
  # @!method access_modifier_with_symbol?(node)
81
81
  def_node_matcher :access_modifier_with_symbol?, <<~PATTERN
82
- (send nil? {:private :protected :public} (sym _))
82
+ (send nil? {:private :protected :public :module_function} (sym _))
83
83
  PATTERN
84
84
 
85
85
  def on_send(node)
@@ -19,47 +19,61 @@ module RuboCop
19
19
  # end
20
20
  #
21
21
  class BisectedAttrAccessor < Base
22
- include VisibilityHelp
22
+ require_relative 'bisected_attr_accessor/macro'
23
+
24
+ include RangeHelp
23
25
  extend AutoCorrector
24
26
 
25
27
  MSG = 'Combine both accessors into `attr_accessor %<name>s`.'
26
28
 
29
+ def on_new_investigation
30
+ @macros_to_rewrite = {}
31
+ end
32
+
27
33
  def on_class(class_node)
28
- VISIBILITY_SCOPES.each do |visibility|
29
- reader_names, writer_names = accessor_names(class_node, visibility)
30
- next unless reader_names && writer_names
34
+ @macros_to_rewrite[class_node] = Set.new
35
+
36
+ find_macros(class_node.body).each do |_visibility, macros|
37
+ bisected = find_bisection(macros)
38
+ next unless bisected.any?
31
39
 
32
- accessor_macroses(class_node, visibility).each do |macro|
33
- check(macro, reader_names, writer_names)
40
+ macros.each do |macro|
41
+ attrs = macro.bisect(*bisected)
42
+ next if attrs.none?
43
+
44
+ @macros_to_rewrite[class_node] << macro
45
+ attrs.each { |attr| register_offense(attr) }
34
46
  end
35
47
  end
36
48
  end
37
49
  alias on_sclass on_class
38
50
  alias on_module on_class
39
51
 
40
- private
41
-
42
- def accessor_names(class_node, visibility)
43
- reader_names = nil
44
- writer_names = nil
45
-
46
- accessor_macroses(class_node, visibility).each do |macro|
47
- names = macro.arguments.map(&:source)
48
-
49
- names.each do |name|
50
- if attr_reader?(macro)
51
- (reader_names ||= Set.new).add(name)
52
+ # Each offending macro is captured and registered in `on_class` but correction
53
+ # happens in `after_class` because a macro might have multiple attributes
54
+ # rewritten from it
55
+ def after_class(class_node)
56
+ @macros_to_rewrite[class_node].each do |macro|
57
+ node = macro.node
58
+ range = range_by_whole_lines(node.loc.expression, include_final_newline: true)
59
+
60
+ correct(range) do |corrector|
61
+ if macro.writer?
62
+ correct_writer(corrector, macro, node, range)
52
63
  else
53
- (writer_names ||= Set.new).add(name)
64
+ correct_reader(corrector, macro, node, range)
54
65
  end
55
66
  end
56
67
  end
57
-
58
- [reader_names, writer_names]
59
68
  end
69
+ alias after_sclass after_class
70
+ alias after_module after_class
60
71
 
61
- def accessor_macroses(class_node, visibility)
62
- class_def = class_node.body
72
+ private
73
+
74
+ def find_macros(class_def)
75
+ # Find all the macros (`attr_reader`, `attr_writer`, etc.) in the class body
76
+ # and turn them into `Macro` objects so that they can be processed.
63
77
  return [] if !class_def || class_def.def_type?
64
78
 
65
79
  send_nodes =
@@ -69,66 +83,40 @@ module RuboCop
69
83
  class_def.each_child_node(:send)
70
84
  end
71
85
 
72
- send_nodes.select { |node| attr_within_visibility_scope?(node, visibility) }
86
+ send_nodes.each_with_object([]) do |node, macros|
87
+ macros << Macro.new(node) if Macro.macro?(node)
88
+ end.group_by(&:visibility)
73
89
  end
74
90
 
75
- def attr_within_visibility_scope?(node, visibility)
76
- node.macro? &&
77
- (attr_reader?(node) || attr_writer?(node)) &&
78
- node_visibility(node) == visibility
91
+ def find_bisection(macros)
92
+ # Find which attributes are defined in both readers and writers so that they
93
+ # can be replaced with accessors.
94
+ readers, writers = macros.partition(&:reader?)
95
+ readers.flat_map(&:attr_names) & writers.flat_map(&:attr_names)
79
96
  end
80
97
 
81
- def attr_reader?(send_node)
82
- send_node.method?(:attr_reader) || send_node.method?(:attr)
98
+ def register_offense(attr)
99
+ add_offense(attr, message: format(MSG, name: attr.source))
83
100
  end
84
101
 
85
- def attr_writer?(send_node)
86
- send_node.method?(:attr_writer)
87
- end
102
+ def correct_reader(corrector, macro, node, range)
103
+ attr_accessor = "attr_accessor #{macro.bisected_names.join(', ')}\n"
88
104
 
89
- def check(macro, reader_names, writer_names)
90
- macro.arguments.each do |arg_node|
91
- name = arg_node.source
92
-
93
- next unless (attr_reader?(macro) && writer_names.include?(name)) ||
94
- (attr_writer?(macro) && reader_names.include?(name))
95
-
96
- add_offense(arg_node, message: format(MSG, name: name)) do |corrector|
97
- macro = arg_node.parent
98
-
99
- corrector.replace(macro, replacement(macro, arg_node))
100
- end
101
- end
102
- end
103
-
104
- def replacement(macro, node)
105
- class_node = macro.each_ancestor(:class, :sclass, :module).first
106
- reader_names, writer_names = accessor_names(class_node, node_visibility(macro))
107
-
108
- rest_args = rest_args(macro.arguments, reader_names, writer_names)
109
-
110
- if attr_reader?(macro)
111
- attr_reader_replacement(macro, node, rest_args)
112
- elsif rest_args.empty?
113
- ''
105
+ if macro.all_bisected?
106
+ corrector.replace(range, "#{indent(node)}#{attr_accessor}")
114
107
  else
115
- "#{macro.method_name} #{rest_args.map(&:source).join(', ')}"
116
- end
117
- end
118
-
119
- def rest_args(args, reader_names, writer_names)
120
- args.reject do |arg|
121
- name = arg.source
122
- reader_names.include?(name) && writer_names.include?(name)
108
+ correction = "#{indent(node)}attr_reader #{macro.rest.join(', ')}"
109
+ corrector.insert_before(node, attr_accessor)
110
+ corrector.replace(node, correction)
123
111
  end
124
112
  end
125
113
 
126
- def attr_reader_replacement(macro, node, rest_args)
127
- if rest_args.empty?
128
- "attr_accessor #{node.source}"
114
+ def correct_writer(corrector, macro, node, range)
115
+ if macro.all_bisected?
116
+ corrector.remove(range)
129
117
  else
130
- "attr_accessor #{node.source}\n"\
131
- "#{indent(macro)}#{macro.method_name} #{rest_args.map(&:source).join(', ')}"
118
+ correction = "attr_writer #{macro.rest.join(', ')}"
119
+ corrector.replace(node, correction)
132
120
  end
133
121
  end
134
122
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ class BisectedAttrAccessor
7
+ # Representation of an `attr_reader`, `attr_writer` or `attr` macro
8
+ # for use by `Style/BisectedAttrAccessor`.
9
+ # @api private
10
+ class Macro
11
+ include VisibilityHelp
12
+
13
+ attr_reader :node, :attrs, :bisection
14
+
15
+ def self.macro?(node)
16
+ node.method?(:attr_reader) || node.method?(:attr_writer) || node.method?(:attr)
17
+ end
18
+
19
+ def initialize(node)
20
+ @node = node
21
+ @attrs = node.arguments.map do |attr|
22
+ [attr.source, attr]
23
+ end.to_h
24
+ @bisection = []
25
+ end
26
+
27
+ def bisect(*names)
28
+ @bisection = attrs.slice(*names).values
29
+ end
30
+
31
+ def attr_names
32
+ @attr_names ||= attrs.keys
33
+ end
34
+
35
+ def bisected_names
36
+ bisection.map(&:source)
37
+ end
38
+
39
+ def visibility
40
+ @visibility ||= node_visibility(node)
41
+ end
42
+
43
+ def reader?
44
+ node.method?(:attr_reader) || node.method?(:attr)
45
+ end
46
+
47
+ def writer?
48
+ node.method?(:attr_writer)
49
+ end
50
+
51
+ def all_bisected?
52
+ rest.none?
53
+ end
54
+
55
+ def rest
56
+ @rest ||= attr_names - bisected_names
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -105,8 +105,7 @@ module RuboCop
105
105
  when :===
106
106
  node.arguments.first
107
107
  when :include?, :cover?
108
- receiver = deparenthesize(node.receiver)
109
- node.arguments.first if receiver.range_type?
108
+ find_target_in_include_or_cover_node(node)
110
109
  when :match, :match?, :=~
111
110
  find_target_in_match_node(node)
112
111
  end
@@ -124,6 +123,12 @@ module RuboCop
124
123
  end
125
124
  end
126
125
 
126
+ def find_target_in_include_or_cover_node(node)
127
+ return unless (receiver = node.receiver)
128
+
129
+ node.first_argument if deparenthesize(receiver).range_type?
130
+ end
131
+
127
132
  def find_target_in_match_node(node)
128
133
  argument = node.arguments.first
129
134
  receiver = node.receiver
@@ -167,8 +172,7 @@ module RuboCop
167
172
  lhs, _method, rhs = *node
168
173
  lhs if rhs == target
169
174
  when :include?, :cover?
170
- receiver = deparenthesize(node.receiver)
171
- receiver if receiver.range_type? && node.arguments.first == target
175
+ condition_from_include_or_cover_node(node, target)
172
176
  end
173
177
  end
174
178
  # rubocop:enable Metrics/CyclomaticComplexity
@@ -184,6 +188,13 @@ module RuboCop
184
188
  condition_from_binary_op(lhs, rhs, target)
185
189
  end
186
190
 
191
+ def condition_from_include_or_cover_node(node, target)
192
+ return unless (receiver = node.receiver)
193
+
194
+ receiver = deparenthesize(receiver)
195
+ receiver if receiver.range_type? && node.first_argument == target
196
+ end
197
+
187
198
  def condition_from_binary_op(lhs, rhs, target)
188
199
  lhs = deparenthesize(lhs)
189
200
  rhs = deparenthesize(rhs)