rubocop 1.11.0 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -50,6 +50,8 @@ module RuboCop
50
50
 
51
51
  def class_name(class_node, node)
52
52
  if node.children.first.method?(:name)
53
+ return class_node.receiver.source if class_node.receiver
54
+
53
55
  class_node.source.delete('"').delete("'")
54
56
  else
55
57
  class_node.source
@@ -165,7 +165,7 @@ module RuboCop
165
165
  end
166
166
 
167
167
  def preferred_delimiter
168
- (command_delimiter || default_delimiter).split('')
168
+ (command_delimiter || default_delimiter).chars
169
169
  end
170
170
 
171
171
  def command_delimiter
@@ -70,8 +70,8 @@ module RuboCop
70
70
  end
71
71
 
72
72
  def directive_cops(comment)
73
- match = CommentConfig::COMMENT_DIRECTIVE_REGEXP.match(comment.text)
74
- match && match[2] ? match[2].split(',').map(&:strip) : []
73
+ match_captures = DirectiveComment.new(comment).match_captures
74
+ match_captures && match_captures[1] ? match_captures[1].split(',').map(&:strip) : []
75
75
  end
76
76
 
77
77
  def allowed_cops
@@ -60,6 +60,15 @@ module RuboCop
60
60
  # extend Foo
61
61
  # end
62
62
  #
63
+ # @example AllowedConstants: ['ClassMethods']
64
+ #
65
+ # # good
66
+ # module A
67
+ # module ClassMethods
68
+ # # ...
69
+ # end
70
+ # end
71
+ #
63
72
  class Documentation < Base
64
73
  include DocumentationComment
65
74
 
@@ -90,14 +99,19 @@ module RuboCop
90
99
 
91
100
  def check(node, body, type)
92
101
  return if namespace?(body)
93
- return if documentation_comment?(node) || nodoc_comment?(node)
94
- return if compact_namespace?(node) &&
95
- nodoc_comment?(outer_module(node).first)
102
+ return if documentation_comment?(node)
103
+ return if constant_allowed?(node)
104
+ return if nodoc_self_or_outer_module?(node)
96
105
  return if macro_only?(body)
97
106
 
98
107
  add_offense(node.loc.keyword, message: format(MSG, type: type))
99
108
  end
100
109
 
110
+ def nodoc_self_or_outer_module?(node)
111
+ nodoc_comment?(node) ||
112
+ compact_namespace?(node) && nodoc_comment?(outer_module(node).first)
113
+ end
114
+
101
115
  def macro_only?(body)
102
116
  body.respond_to?(:macro?) && body.macro? ||
103
117
  body.respond_to?(:children) && body.children&.all? { |child| macro_only?(child) }
@@ -117,6 +131,10 @@ module RuboCop
117
131
  constant_definition?(node) || constant_visibility_declaration?(node)
118
132
  end
119
133
 
134
+ def constant_allowed?(node)
135
+ allowed_constants.include?(node.identifier.short_name)
136
+ end
137
+
120
138
  def compact_namespace?(node)
121
139
  /::/.match?(node.loc.name.source)
122
140
  end
@@ -143,6 +161,10 @@ module RuboCop
143
161
  def nodoc(node)
144
162
  processed_source.ast_with_comments[node.children.first].first
145
163
  end
164
+
165
+ def allowed_constants
166
+ @allowed_constants ||= cop_config.fetch('AllowedConstants', []).map(&:intern)
167
+ end
146
168
  end
147
169
  end
148
170
  end
@@ -224,7 +224,7 @@ module RuboCop
224
224
 
225
225
  register_offense(node) do |corrector|
226
226
  line_str = missing_line(node, code)
227
- corrector.insert_after(node.loc.expression.end, ", __FILE__, #{line_str}")
227
+ corrector.insert_after(node.last_argument.source_range.end, ", __FILE__, #{line_str}")
228
228
  end
229
229
  end
230
230
 
@@ -54,9 +54,10 @@ module RuboCop
54
54
  # # good
55
55
  # {a: 1, b: 2}
56
56
  # {:c => 3, 'd' => 4}
57
- class HashSyntax < Cop
57
+ class HashSyntax < Base
58
58
  include ConfigurableEnforcedStyle
59
59
  include RangeHelp
60
+ extend AutoCorrector
60
61
 
61
62
  MSG_19 = 'Use the new Ruby 1.9 hash syntax.'
62
63
  MSG_NO_MIXED_KEYS = "Don't mix styles in the same hash."
@@ -104,18 +105,6 @@ module RuboCop
104
105
  end
105
106
  end
106
107
 
107
- def autocorrect(node)
108
- lambda do |corrector|
109
- if style == :hash_rockets || force_hash_rockets?(node.parent.pairs)
110
- autocorrect_hash_rockets(corrector, node)
111
- elsif style == :ruby19_no_mixed_keys || style == :no_mixed_keys
112
- autocorrect_no_mixed_keys(corrector, node)
113
- else
114
- autocorrect_ruby19(corrector, node)
115
- end
116
- end
117
- end
118
-
119
108
  def alternative_style
120
109
  case style
121
110
  when :hash_rockets
@@ -127,6 +116,16 @@ module RuboCop
127
116
 
128
117
  private
129
118
 
119
+ def autocorrect(corrector, node)
120
+ if style == :hash_rockets || force_hash_rockets?(node.parent.pairs)
121
+ autocorrect_hash_rockets(corrector, node)
122
+ elsif style == :ruby19_no_mixed_keys || style == :no_mixed_keys
123
+ autocorrect_no_mixed_keys(corrector, node)
124
+ else
125
+ autocorrect_ruby19(corrector, node)
126
+ end
127
+ end
128
+
130
129
  def sym_indices?(pairs)
131
130
  pairs.all? { |p| word_symbol_pair?(p) }
132
131
  end
@@ -152,14 +151,16 @@ module RuboCop
152
151
  return true if /\A[_a-z]\w*[?!]?\z/i.match?(sym_name)
153
152
 
154
153
  # For more complicated hash keys, let the parser validate the syntax.
155
- parse("{ #{sym_name}: :foo }").valid_syntax?
154
+ ProcessedSource.new("{ #{sym_name}: :foo }", target_ruby_version).valid_syntax?
156
155
  end
157
156
 
158
157
  def check(pairs, delim, msg)
159
158
  pairs.each do |pair|
160
159
  if pair.delimiter == delim
161
160
  location = pair.source_range.begin.join(pair.loc.operator)
162
- add_offense(pair, location: location, message: msg) do
161
+ add_offense(location, message: msg) do |corrector|
162
+ autocorrect(corrector, pair)
163
+
163
164
  opposite_style_detected
164
165
  end
165
166
  else
@@ -79,6 +79,30 @@ module RuboCop
79
79
  # # good
80
80
  # foo.enforce strict: true
81
81
  #
82
+ # # good
83
+ # # Allows parens for calls that won't produce valid Ruby or be ambiguous.
84
+ # model.validate strict(true)
85
+ #
86
+ # # good
87
+ # # Allows parens for calls that won't produce valid Ruby or be ambiguous.
88
+ # yield path, File.basename(path)
89
+ #
90
+ # # good
91
+ # # Operators methods calls with parens
92
+ # array&.[](index)
93
+ #
94
+ # # good
95
+ # # Operators methods without parens, if you prefer
96
+ # array.[] index
97
+ #
98
+ # # good
99
+ # # Operators methods calls with parens
100
+ # array&.[](index)
101
+ #
102
+ # # good
103
+ # # Operators methods without parens, if you prefer
104
+ # array.[] index
105
+ #
82
106
  # @example IgnoreMacros: true (default)
83
107
  #
84
108
  # # good
@@ -146,6 +170,22 @@ module RuboCop
146
170
  #
147
171
  # # good
148
172
  # Array 1
173
+ #
174
+ # @example AllowParenthesesInStringInterpolation: false (default)
175
+ #
176
+ # # bad
177
+ # "#{t('this.is.bad')}"
178
+ #
179
+ # # good
180
+ # "#{t 'this.is.better'}"
181
+ #
182
+ # @example AllowParenthesesInStringInterpolation: true
183
+ #
184
+ # # good
185
+ # "#{t('this.is.good')}"
186
+ #
187
+ # # good
188
+ # "#{t 'this.is.also.good'}"
149
189
  class MethodCallWithArgsParentheses < Base
150
190
  require_relative 'method_call_with_args_parentheses/omit_parentheses'
151
191
  require_relative 'method_call_with_args_parentheses/require_parentheses'
@@ -5,6 +5,7 @@ module RuboCop
5
5
  module Style
6
6
  class MethodCallWithArgsParentheses
7
7
  # Style omit_parentheses
8
+ # rubocop:disable Metrics/ModuleLength
8
9
  module OmitParentheses
9
10
  TRAILING_WHITESPACE_REGEX = /\s+\Z/.freeze
10
11
  OMIT_MSG = 'Omit parentheses for method calls with arguments.'
@@ -12,18 +13,21 @@ module RuboCop
12
13
 
13
14
  private
14
15
 
16
+ # rubocop:disable Metrics/CyclomaticComplexity
15
17
  def omit_parentheses(node)
16
18
  return unless node.parenthesized?
17
19
  return if inside_endless_method_def?(node)
18
- return if node.implicit_call?
20
+ return if syntax_like_method_call?(node)
19
21
  return if super_call_without_arguments?(node)
20
22
  return if allowed_camel_case_method_call?(node)
21
23
  return if legitimate_call_with_parentheses?(node)
24
+ return if allowed_string_interpolation_method_call?(node)
22
25
 
23
26
  add_offense(offense_range(node), message: OMIT_MSG) do |corrector|
24
27
  auto_correct(corrector, node)
25
28
  end
26
29
  end
30
+ # rubocop:enable Metrics/CyclomaticComplexity
27
31
 
28
32
  def auto_correct(corrector, node)
29
33
  if parentheses_at_the_end_of_multiline_call?(node)
@@ -43,6 +47,10 @@ module RuboCop
43
47
  node.each_ancestor(:def).any?(&:endless?) && node.arguments.any?
44
48
  end
45
49
 
50
+ def syntax_like_method_call?(node)
51
+ node.implicit_call? || node.operator_method?
52
+ end
53
+
46
54
  def super_call_without_arguments?(node)
47
55
  node.super_type? && node.arguments.none?
48
56
  end
@@ -53,6 +61,11 @@ module RuboCop
53
61
  cop_config['AllowParenthesesInCamelCaseMethod'])
54
62
  end
55
63
 
64
+ def allowed_string_interpolation_method_call?(node)
65
+ cop_config['AllowParenthesesInStringInterpolation'] &&
66
+ inside_string_interpolation?(node)
67
+ end
68
+
56
69
  def parentheses_at_the_end_of_multiline_call?(node)
57
70
  node.multiline? &&
58
71
  node.loc.begin.source_line
@@ -114,7 +127,7 @@ module RuboCop
114
127
  def call_as_argument_or_chain?(node)
115
128
  node.parent &&
116
129
  (node.parent.send_type? && !assigned_before?(node.parent, node) ||
117
- node.parent.csend_type? || node.parent.super_type?)
130
+ node.parent.csend_type? || node.parent.super_type? || node.parent.yield_type?)
118
131
  end
119
132
 
120
133
  def hash_literal_in_arguments?(node)
@@ -172,7 +185,12 @@ module RuboCop
172
185
  node.assignment? &&
173
186
  node.loc.operator.begin < target.loc.begin
174
187
  end
188
+
189
+ def inside_string_interpolation?(node)
190
+ node.ancestors.drop_while { |a| !a.begin_type? }.any?(&:dstr_type?)
191
+ end
175
192
  end
193
+ # rubocop:enable Metrics/ModuleLength
176
194
  end
177
195
  end
178
196
  end
@@ -29,6 +29,7 @@ module RuboCop
29
29
  #
30
30
  class NegatedIfElseCondition < Base
31
31
  include RangeHelp
32
+ include CommentsHelp
32
33
  extend AutoCorrector
33
34
 
34
35
  MSG = 'Invert the negated condition and swap the %<type>s branches.'
@@ -97,10 +98,22 @@ module RuboCop
97
98
  if node.if_branch.nil?
98
99
  corrector.remove(range_by_whole_lines(node.loc.else, include_final_newline: true))
99
100
  else
100
- corrector.replace(node.if_branch, node.else_branch.source)
101
- corrector.replace(node.else_branch, node.if_branch.source)
101
+ if_range = node_with_comments(node.if_branch)
102
+ else_range = node_with_comments(node.else_branch)
103
+
104
+ corrector.replace(if_range, else_range.source)
105
+ corrector.replace(else_range, if_range.source)
102
106
  end
103
107
  end
108
+
109
+ def node_with_comments(node)
110
+ first_statement = node.begin_type? ? node.children[0] : node
111
+ return node if processed_source.ast_with_comments[first_statement].empty?
112
+
113
+ begin_pos = source_range_with_comment(first_statement).begin_pos
114
+ end_pos = node.source_range.end_pos
115
+ Parser::Source::Range.new(buffer, begin_pos, end_pos)
116
+ end
104
117
  end
105
118
  end
106
119
  end
@@ -63,6 +63,7 @@ module RuboCop
63
63
  # end
64
64
  # end
65
65
  class RedundantBegin < Base
66
+ include RangeHelp
66
67
  extend AutoCorrector
67
68
 
68
69
  MSG = 'Redundant `begin` block detected.'
@@ -95,12 +96,26 @@ module RuboCop
95
96
  private
96
97
 
97
98
  def register_offense(node)
98
- add_offense(node.loc.begin) do |corrector|
99
- corrector.remove(node.loc.begin)
99
+ offense_range = node.loc.begin
100
+
101
+ add_offense(offense_range) do |corrector|
102
+ if any_ancestor_assignment_node?(node)
103
+ replace_begin_with_statement(corrector, offense_range, node)
104
+ else
105
+ corrector.remove(offense_range)
106
+ end
107
+
100
108
  corrector.remove(node.loc.end)
101
109
  end
102
110
  end
103
111
 
112
+ def replace_begin_with_statement(corrector, offense_range, node)
113
+ first_child = node.children.first
114
+
115
+ corrector.replace(offense_range, first_child.source)
116
+ corrector.remove(range_between(offense_range.end_pos, first_child.source_range.end_pos))
117
+ end
118
+
104
119
  def empty_begin?(node)
105
120
  node.children.empty?
106
121
  end
@@ -114,9 +129,17 @@ module RuboCop
114
129
  def valid_context_using_only_begin?(node)
115
130
  parent = node.parent
116
131
 
117
- node.each_ancestor.any?(&:assignment?) || parent&.post_condition_loop? ||
132
+ valid_begin_assignment?(node) || parent&.post_condition_loop? ||
118
133
  parent&.send_type? || parent&.operator_keyword?
119
134
  end
135
+
136
+ def valid_begin_assignment?(node)
137
+ any_ancestor_assignment_node?(node) && !node.children.one?
138
+ end
139
+
140
+ def any_ancestor_assignment_node?(node)
141
+ node.each_ancestor.any?(&:assignment?)
142
+ end
120
143
  end
121
144
  end
122
145
  end
@@ -71,6 +71,10 @@ module RuboCop
71
71
  elsif hash_without_braces?(return_node.first_argument)
72
72
  add_braces(corrector, return_node.first_argument)
73
73
  end
74
+ if return_node.splat_argument?
75
+ first_argument = return_node.first_argument
76
+ corrector.replace(first_argument, first_argument.source.gsub(/\A\*/, ''))
77
+ end
74
78
 
75
79
  keyword = range_with_surrounding_space(range: return_node.loc.keyword,
76
80
  side: :right)
@@ -11,7 +11,7 @@ module RuboCop
11
11
  # presence of a method name clash with an argument or a local
12
12
  # variable.
13
13
  #
14
- # * Calling an attribute writer to prevent an local variable assignment.
14
+ # * Calling an attribute writer to prevent a local variable assignment.
15
15
  #
16
16
  # Note, with using explicit self you can only send messages with public or
17
17
  # protected scope, you cannot send private messages this way.
@@ -144,8 +144,12 @@ module RuboCop
144
144
  end
145
145
 
146
146
  def on_argument(node)
147
- name, = *node
148
- @local_variables_scopes[node] << name
147
+ if node.mlhs_type?
148
+ on_args(node)
149
+ else
150
+ name, = *node
151
+ @local_variables_scopes[node] << name
152
+ end
149
153
  end
150
154
 
151
155
  def allow_self(node)
@@ -150,7 +150,7 @@ module RuboCop
150
150
 
151
151
  def preferred_delimiters
152
152
  config.for_cop('Style/PercentLiteralDelimiters') \
153
- ['PreferredDelimiters']['%r'].split('')
153
+ ['PreferredDelimiters']['%r'].chars
154
154
  end
155
155
 
156
156
  def correct_delimiters(node, corrector)
@@ -39,23 +39,23 @@ module RuboCop
39
39
  # rescue SomeException
40
40
  # handle_error
41
41
  # end
42
- class RescueModifier < Cop
42
+ class RescueModifier < Base
43
43
  include Alignment
44
+ include RangeHelp
44
45
  include RescueNode
46
+ extend AutoCorrector
45
47
 
46
48
  MSG = 'Avoid using `rescue` in its modifier form.'
47
49
 
48
50
  def on_resbody(node)
49
51
  return unless rescue_modifier?(node)
50
52
 
51
- add_offense(node.parent)
52
- end
53
+ rescue_node = node.parent
54
+ add_offense(rescue_node) do |corrector|
55
+ parenthesized = parenthesized?(rescue_node)
53
56
 
54
- def autocorrect(node)
55
- parenthesized = parenthesized?(node)
56
- lambda do |corrector|
57
- corrector.replace(node, corrected_block(node, parenthesized))
58
- ParenthesesCorrector.correct(corrector, node.parent) if parenthesized
57
+ correct_rescue_block(corrector, rescue_node, parenthesized)
58
+ ParenthesesCorrector.correct(corrector, rescue_node.parent) if parenthesized
59
59
  end
60
60
  end
61
61
 
@@ -65,17 +65,20 @@ module RuboCop
65
65
  node.parent && parentheses?(node.parent)
66
66
  end
67
67
 
68
- def corrected_block(node, parenthesized)
68
+ def correct_rescue_block(corrector, node, parenthesized)
69
69
  operation, rescue_modifier, = *node
70
70
  *_, rescue_args = *rescue_modifier
71
71
 
72
72
  node_indentation, node_offset = indentation_and_offset(node, parenthesized)
73
73
 
74
- "begin\n" \
75
- "#{operation.source.gsub(/^/, node_indentation)}" \
76
- "\n#{node_offset}rescue\n" \
77
- "#{rescue_args.source.gsub(/^/, node_indentation)}" \
78
- "\n#{node_offset}end"
74
+ corrector.remove(range_between(operation.source_range.end_pos, node.source_range.end_pos))
75
+ corrector.insert_before(operation, "begin\n#{node_indentation}")
76
+ corrector.insert_after(operation, <<~RESCUE_CLAUSE.chop)
77
+
78
+ #{node_offset}rescue
79
+ #{node_indentation}#{rescue_args.source}
80
+ #{node_offset}end
81
+ RESCUE_CLAUSE
79
82
  end
80
83
 
81
84
  def indentation_and_offset(node, parenthesized)