rubocop 0.31.0 → 0.35.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 (177) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +315 -0
  3. data/README.md +199 -38
  4. data/config/default.yml +91 -12
  5. data/config/disabled.yml +45 -4
  6. data/config/enabled.yml +107 -9
  7. data/lib/rubocop/ast_node.rb +48 -0
  8. data/lib/rubocop/cli.rb +11 -1
  9. data/lib/rubocop/comment_config.rb +4 -1
  10. data/lib/rubocop/config.rb +26 -17
  11. data/lib/rubocop/config_loader.rb +61 -14
  12. data/lib/rubocop/cop/commissioner.rb +7 -12
  13. data/lib/rubocop/cop/cop.rb +43 -20
  14. data/lib/rubocop/cop/lint/block_alignment.rb +1 -1
  15. data/lib/rubocop/cop/lint/circular_argument_reference.rb +69 -0
  16. data/lib/rubocop/cop/lint/debugger.rb +9 -48
  17. data/lib/rubocop/cop/lint/def_end_alignment.rb +8 -4
  18. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +42 -23
  19. data/lib/rubocop/cop/lint/duplicate_methods.rb +2 -2
  20. data/lib/rubocop/cop/lint/duplicated_key.rb +37 -0
  21. data/lib/rubocop/cop/lint/end_alignment.rb +33 -13
  22. data/lib/rubocop/cop/lint/eval.rb +6 -2
  23. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +175 -0
  24. data/lib/rubocop/cop/lint/literal_in_condition.rb +0 -5
  25. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +10 -0
  26. data/lib/rubocop/cop/lint/nested_method_definition.rb +31 -0
  27. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +19 -1
  28. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -1
  29. data/lib/rubocop/cop/lint/space_before_first_arg.rb +1 -1
  30. data/lib/rubocop/cop/lint/unneeded_disable.rb +72 -0
  31. data/lib/rubocop/cop/lint/unused_block_argument.rb +6 -0
  32. data/lib/rubocop/cop/lint/unused_method_argument.rb +8 -0
  33. data/lib/rubocop/cop/metrics/abc_size.rb +17 -6
  34. data/lib/rubocop/cop/metrics/class_length.rb +1 -1
  35. data/lib/rubocop/cop/metrics/method_length.rb +1 -3
  36. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  37. data/lib/rubocop/cop/metrics/parameter_lists.rb +1 -1
  38. data/lib/rubocop/cop/mixin/access_modifier_node.rb +1 -1
  39. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -2
  40. data/lib/rubocop/cop/mixin/autocorrect_alignment.rb +28 -4
  41. data/lib/rubocop/cop/mixin/autocorrect_unless_changing_ast.rb +26 -3
  42. data/lib/rubocop/cop/mixin/check_assignment.rb +2 -3
  43. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +59 -12
  44. data/lib/rubocop/cop/mixin/configurable_max.rb +1 -1
  45. data/lib/rubocop/cop/mixin/configurable_naming.rb +14 -3
  46. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +1 -3
  47. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +10 -1
  48. data/lib/rubocop/cop/mixin/first_element_line_break.rb +41 -0
  49. data/lib/rubocop/cop/mixin/if_node.rb +10 -0
  50. data/lib/rubocop/cop/mixin/method_preference.rb +28 -0
  51. data/lib/rubocop/cop/mixin/negative_conditional.rb +1 -1
  52. data/lib/rubocop/cop/mixin/on_method_def.rb +4 -5
  53. data/lib/rubocop/cop/mixin/safe_assignment.rb +3 -14
  54. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +8 -1
  55. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +8 -1
  56. data/lib/rubocop/cop/mixin/statement_modifier.rb +4 -7
  57. data/lib/rubocop/cop/mixin/string_help.rb +1 -1
  58. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
  59. data/lib/rubocop/cop/mixin/surrounding_space.rb +5 -4
  60. data/lib/rubocop/cop/offense.rb +16 -3
  61. data/lib/rubocop/cop/performance/case_when_splat.rb +160 -0
  62. data/lib/rubocop/cop/performance/count.rb +35 -30
  63. data/lib/rubocop/cop/performance/detect.rb +16 -3
  64. data/lib/rubocop/cop/performance/fixed_size.rb +50 -0
  65. data/lib/rubocop/cop/performance/flat_map.rb +3 -3
  66. data/lib/rubocop/cop/performance/sample.rb +103 -59
  67. data/lib/rubocop/cop/performance/size.rb +2 -1
  68. data/lib/rubocop/cop/performance/string_replacement.rb +187 -0
  69. data/lib/rubocop/cop/rails/action_filter.rb +31 -5
  70. data/lib/rubocop/cop/rails/date.rb +15 -14
  71. data/lib/rubocop/cop/rails/pluralization_grammar.rb +97 -0
  72. data/lib/rubocop/cop/rails/read_write_attribute.rb +1 -1
  73. data/lib/rubocop/cop/rails/time_zone.rb +46 -18
  74. data/lib/rubocop/cop/style/alias.rb +1 -0
  75. data/lib/rubocop/cop/style/align_hash.rb +8 -15
  76. data/lib/rubocop/cop/style/align_parameters.rb +19 -7
  77. data/lib/rubocop/cop/style/and_or.rb +42 -13
  78. data/lib/rubocop/cop/style/auto_resource_cleanup.rb +2 -1
  79. data/lib/rubocop/cop/style/block_comments.rb +4 -2
  80. data/lib/rubocop/cop/style/block_delimiters.rb +69 -24
  81. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +40 -12
  82. data/lib/rubocop/cop/style/case_indentation.rb +18 -4
  83. data/lib/rubocop/cop/style/collection_methods.rb +2 -20
  84. data/lib/rubocop/cop/style/command_literal.rb +2 -10
  85. data/lib/rubocop/cop/style/comment_annotation.rb +29 -8
  86. data/lib/rubocop/cop/style/copyright.rb +5 -3
  87. data/lib/rubocop/cop/style/documentation.rb +21 -12
  88. data/lib/rubocop/cop/style/dot_position.rb +6 -0
  89. data/lib/rubocop/cop/style/double_negation.rb +4 -15
  90. data/lib/rubocop/cop/style/each_with_object.rb +17 -4
  91. data/lib/rubocop/cop/style/else_alignment.rb +2 -1
  92. data/lib/rubocop/cop/style/empty_else.rb +25 -0
  93. data/lib/rubocop/cop/style/empty_line_between_defs.rb +39 -14
  94. data/lib/rubocop/cop/style/encoding.rb +10 -4
  95. data/lib/rubocop/cop/style/extra_spacing.rb +126 -5
  96. data/lib/rubocop/cop/style/first_array_element_line_break.rb +41 -0
  97. data/lib/rubocop/cop/style/first_hash_element_line_break.rb +35 -0
  98. data/lib/rubocop/cop/style/first_method_argument_line_break.rb +37 -0
  99. data/lib/rubocop/cop/style/first_method_parameter_line_break.rb +42 -0
  100. data/lib/rubocop/cop/style/first_parameter_indentation.rb +5 -3
  101. data/lib/rubocop/cop/style/for.rb +2 -1
  102. data/lib/rubocop/cop/style/hash_syntax.rb +5 -0
  103. data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -5
  104. data/lib/rubocop/cop/style/indent_hash.rb +67 -37
  105. data/lib/rubocop/cop/style/indentation_width.rb +36 -10
  106. data/lib/rubocop/cop/style/initial_indentation.rb +37 -0
  107. data/lib/rubocop/cop/style/leading_comment_space.rb +3 -2
  108. data/lib/rubocop/cop/style/method_call_parentheses.rb +28 -1
  109. data/lib/rubocop/cop/style/method_def_parentheses.rb +10 -7
  110. data/lib/rubocop/cop/style/multiline_operation_indentation.rb +21 -24
  111. data/lib/rubocop/cop/style/mutable_constant.rb +35 -0
  112. data/lib/rubocop/cop/style/nested_modifier.rb +97 -0
  113. data/lib/rubocop/cop/style/next.rb +50 -15
  114. data/lib/rubocop/cop/style/non_nil_check.rb +12 -8
  115. data/lib/rubocop/cop/style/one_line_conditional.rb +8 -4
  116. data/lib/rubocop/cop/style/option_hash.rb +64 -0
  117. data/lib/rubocop/cop/style/optional_arguments.rb +49 -0
  118. data/lib/rubocop/cop/style/parallel_assignment.rb +218 -0
  119. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +3 -66
  120. data/lib/rubocop/cop/style/predicate_name.rb +7 -2
  121. data/lib/rubocop/cop/style/redundant_begin.rb +2 -13
  122. data/lib/rubocop/cop/style/redundant_freeze.rb +37 -0
  123. data/lib/rubocop/cop/style/redundant_return.rb +32 -3
  124. data/lib/rubocop/cop/style/regexp_literal.rb +2 -10
  125. data/lib/rubocop/cop/style/rescue_ensure_alignment.rb +81 -0
  126. data/lib/rubocop/cop/style/rescue_modifier.rb +30 -22
  127. data/lib/rubocop/cop/style/send.rb +18 -0
  128. data/lib/rubocop/cop/style/signal_exception.rb +24 -11
  129. data/lib/rubocop/cop/style/single_line_methods.rb +8 -9
  130. data/lib/rubocop/cop/style/single_space_before_first_arg.rb +1 -1
  131. data/lib/rubocop/cop/style/space_around_operators.rb +2 -0
  132. data/lib/rubocop/cop/style/space_inside_string_interpolation.rb +61 -0
  133. data/lib/rubocop/cop/style/special_global_vars.rb +4 -2
  134. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +108 -0
  135. data/lib/rubocop/cop/style/string_methods.rb +32 -0
  136. data/lib/rubocop/cop/style/struct_inheritance.rb +11 -10
  137. data/lib/rubocop/cop/style/symbol_literal.rb +1 -1
  138. data/lib/rubocop/cop/style/symbol_proc.rb +62 -13
  139. data/lib/rubocop/cop/style/trailing_blank_lines.rb +9 -1
  140. data/lib/rubocop/cop/style/trailing_comma.rb +17 -7
  141. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -2
  142. data/lib/rubocop/cop/style/trivial_accessors.rb +10 -1
  143. data/lib/rubocop/cop/style/unneeded_percent_q.rb +31 -20
  144. data/lib/rubocop/cop/style/variable_name.rb +5 -0
  145. data/lib/rubocop/cop/style/while_until_do.rb +1 -1
  146. data/lib/rubocop/cop/style/word_array.rb +15 -2
  147. data/lib/rubocop/cop/team.rb +25 -5
  148. data/lib/rubocop/cop/util.rb +7 -2
  149. data/lib/rubocop/cop/variable_force/locatable.rb +6 -6
  150. data/lib/rubocop/cop/variable_force.rb +10 -10
  151. data/lib/rubocop/formatter/base_formatter.rb +1 -1
  152. data/lib/rubocop/formatter/disabled_config_formatter.rb +70 -8
  153. data/lib/rubocop/formatter/formatter_set.rb +27 -1
  154. data/lib/rubocop/formatter/progress_formatter.rb +10 -2
  155. data/lib/rubocop/formatter/simple_text_formatter.rb +1 -1
  156. data/lib/rubocop/node_pattern.rb +390 -0
  157. data/lib/rubocop/options.rb +148 -81
  158. data/lib/rubocop/processed_source.rb +7 -2
  159. data/lib/rubocop/rake_task.rb +1 -1
  160. data/lib/rubocop/remote_config.rb +60 -0
  161. data/lib/rubocop/result_cache.rb +123 -0
  162. data/lib/rubocop/runner.rb +85 -22
  163. data/lib/rubocop/target_finder.rb +4 -4
  164. data/lib/rubocop/token.rb +2 -1
  165. data/lib/rubocop/version.rb +1 -1
  166. data/lib/rubocop/warning.rb +11 -0
  167. data/lib/rubocop.rb +32 -3
  168. data/relnotes/v0.32.0.md +139 -0
  169. data/relnotes/v0.32.1.md +122 -0
  170. data/relnotes/v0.33.0.md +157 -0
  171. data/relnotes/v0.34.0.md +182 -0
  172. data/relnotes/v0.34.1.md +129 -0
  173. data/relnotes/v0.34.2.md +139 -0
  174. data/relnotes/v0.35.0.md +210 -0
  175. data/rubocop.gemspec +4 -4
  176. metadata +50 -12
  177. data/lib/rubocop/cop/performance/parallel_assignment.rb +0 -79
@@ -7,14 +7,37 @@ module RuboCop
7
7
  # where the opening brace and the first key are on separate lines. The
8
8
  # other keys' indentations are handled by the AlignHash cop.
9
9
  #
10
- # Hash literals that are arguments in a method call with parentheses, and
11
- # where the opening curly brace of the hash is on the same line as the
12
- # opening parenthesis of the method call, shall have their first key
13
- # indented one step (two spaces) more than the position inside the
14
- # opening parenthesis.
10
+ # By default, Hash literals that are arguments in a method call with
11
+ # parentheses, and where the opening curly brace of the hash is on the
12
+ # same line as the opening parenthesis of the method call, shall have
13
+ # their first key indented one step (two spaces) more than the position
14
+ # inside the opening parenthesis.
15
15
  #
16
16
  # Other hash literals shall have their first key indented one step more
17
17
  # than the start of the line where the opening curly brace is.
18
+ #
19
+ # This default style is called 'special_inside_parentheses'. Alternative
20
+ # styles are 'consistent' and 'align_braces'. Here are examples:
21
+ #
22
+ # # special_inside_parentheses
23
+ # hash = {
24
+ # key: :value
25
+ # }
26
+ # but_in_a_method_call({
27
+ # its_like: :this
28
+ # })
29
+ # # consistent
30
+ # hash = {
31
+ # key: :value
32
+ # }
33
+ # and_in_a_method_call({
34
+ # no: :difference
35
+ # })
36
+ # # align_braces
37
+ # and_now_for_something = {
38
+ # completely: :different
39
+ # }
40
+ #
18
41
  class IndentHash < Cop
19
42
  include AutocorrectAlignment
20
43
  include ConfigurableEnforcedStyle
@@ -62,13 +85,16 @@ module RuboCop
62
85
  end
63
86
 
64
87
  def check_right_brace(right_brace, left_brace, left_parenthesis)
88
+ # if the right brace is on the same line as the last value, accept
65
89
  return if right_brace.source_line[0...right_brace.column] =~ /\S/
66
90
 
67
91
  expected_column = base_column(left_brace, left_parenthesis)
68
92
  @column_delta = expected_column - right_brace.column
69
93
  return if @column_delta == 0
70
94
 
71
- msg = if style == :special_inside_parentheses && left_parenthesis
95
+ msg = if style == :align_braces
96
+ 'Indent the right brace the same as the left brace.'
97
+ elsif style == :special_inside_parentheses && left_parenthesis
72
98
  'Indent the right brace the same as the first position ' \
73
99
  'after the preceding left parenthesis.'
74
100
  else
@@ -93,15 +119,24 @@ module RuboCop
93
119
  end
94
120
 
95
121
  def check_first_pair(first_pair, left_brace, left_parenthesis, offset)
96
- column = first_pair.loc.expression.column
122
+ actual_column = first_pair.loc.expression.column
97
123
  expected_column = base_column(left_brace, left_parenthesis) +
98
124
  configured_indentation_width + offset
99
- @column_delta = expected_column - column
125
+ @column_delta = expected_column - actual_column
100
126
 
101
127
  if @column_delta == 0
102
- correct_style_detected
128
+ # which column was actually used as 'base column' for indentation?
129
+ # (not the column which we think should be the 'base column',
130
+ # but the one which has actually been used for that purpose)
131
+ base_column = actual_column - configured_indentation_width - offset
132
+ styles = detected_styles(base_column, left_parenthesis, left_brace)
133
+ if styles.size > 1
134
+ ambiguous_style_detected(*styles)
135
+ else
136
+ correct_style_detected
137
+ end
103
138
  else
104
- incorrect_style_detected(column, offset, first_pair,
139
+ incorrect_style_detected(actual_column, offset, first_pair,
105
140
  left_parenthesis, left_brace)
106
141
  end
107
142
  end
@@ -110,17 +145,29 @@ module RuboCop
110
145
  left_parenthesis, left_brace)
111
146
  add_offense(first_pair, :expression,
112
147
  message(base_description(left_parenthesis))) do
113
- if column == unexpected_column(left_brace, left_parenthesis,
114
- offset)
115
- opposite_style_detected
116
- else
117
- unrecognized_style_detected
118
- end
148
+ base_column = column - configured_indentation_width - offset
149
+ styles = detected_styles(base_column, left_parenthesis, left_brace)
150
+ ambiguous_style_detected(*styles)
119
151
  end
120
152
  end
121
153
 
154
+ def detected_styles(column, left_parenthesis, left_brace)
155
+ styles = []
156
+ if column == (left_brace.source_line =~ /\S/)
157
+ styles << :consistent
158
+ styles << :special_inside_parentheses unless left_parenthesis
159
+ end
160
+ if left_parenthesis && column == left_parenthesis.column + 1
161
+ styles << :special_inside_parentheses
162
+ end
163
+ styles << :align_braces if column == left_brace.column
164
+ styles
165
+ end
166
+
122
167
  def base_column(left_brace, left_parenthesis)
123
- if left_parenthesis && style == :special_inside_parentheses
168
+ if style == :align_braces
169
+ left_brace.column
170
+ elsif left_parenthesis && style == :special_inside_parentheses
124
171
  left_parenthesis.column + 1
125
172
  else
126
173
  left_brace.source_line =~ /\S/
@@ -129,32 +176,15 @@ module RuboCop
129
176
 
130
177
  # Returns the description of what the correct indentation is based on.
131
178
  def base_description(left_parenthesis)
132
- if left_parenthesis && style == :special_inside_parentheses
179
+ if style == :align_braces
180
+ 'the position of the opening brace'
181
+ elsif left_parenthesis && style == :special_inside_parentheses
133
182
  'the first position after the preceding left parenthesis'
134
183
  else
135
184
  'the start of the line where the left curly brace is'
136
185
  end
137
186
  end
138
187
 
139
- # Returns the "unexpected column", which is the column that would be
140
- # correct if the configuration was changed.
141
- def unexpected_column(left_brace, left_parenthesis, offset)
142
- # Set a crazy value by default, indicating that there's no other
143
- # configuration that can be chosen to make the used indentation
144
- # accepted.
145
- unexpected_base_column = -1000
146
-
147
- if left_parenthesis
148
- unexpected_base_column = if style == :special_inside_parentheses
149
- left_brace.source_line =~ /\S/
150
- else
151
- left_parenthesis.column + 1
152
- end
153
- end
154
-
155
- unexpected_base_column + configured_indentation_width + offset
156
- end
157
-
158
188
  def message(base_description)
159
189
  format('Use %d spaces for indentation in a hash, relative to %s.',
160
190
  configured_indentation_width, base_description)
@@ -13,6 +13,7 @@ module RuboCop
13
13
  # end
14
14
  # end
15
15
  class IndentationWidth < Cop # rubocop:disable Metrics/ClassLength
16
+ include EndKeywordAlignment
16
17
  include AutocorrectAlignment
17
18
  include OnMethodDef
18
19
  include CheckAssignment
@@ -82,10 +83,11 @@ module RuboCop
82
83
  def on_send(node)
83
84
  super
84
85
  receiver, method_name, *args = *node
85
- return unless visibility_and_def_on_same_line?(receiver, method_name,
86
- args)
86
+ return unless modifier_and_def_on_same_line?(receiver, method_name,
87
+ args)
88
+
89
+ *_, body = *args.first
87
90
 
88
- _method_name, _args, body = *args.first
89
91
  def_end_config = config.for_cop('Lint/DefEndAlignment')
90
92
  style = if def_end_config['Enabled']
91
93
  def_end_config['AlignWith']
@@ -157,11 +159,7 @@ module RuboCop
157
159
  return if ternary_op?(node)
158
160
  return if modifier_if?(node)
159
161
 
160
- case node.loc.keyword.source
161
- when 'if', 'elsif' then _condition, body, else_clause = *node
162
- when 'unless' then _condition, else_clause, body = *node
163
- else _condition, body = *node
164
- end
162
+ _condition, body, else_clause = if_node_parts(node)
165
163
 
166
164
  check_if(node, body, else_clause, base.loc) if body
167
165
  end
@@ -177,7 +175,7 @@ module RuboCop
177
175
 
178
176
  end_config = config.for_cop('Lint/EndAlignment')
179
177
  style = end_config['Enabled'] ? end_config['AlignWith'] : 'keyword'
180
- base = style == 'variable' ? node : rhs
178
+ base = variable_alignment?(node.loc, rhs, style.to_sym) ? node : rhs
181
179
 
182
180
  case rhs.type
183
181
  when :if then on_if(rhs, base)
@@ -215,13 +213,36 @@ module RuboCop
215
213
  body_node = body_node.children.first
216
214
  end
217
215
 
216
+ # Since autocorrect changes a number of lines, and not only the line
217
+ # where the reported offending range is, we avoid auto-correction if
218
+ # this cop has already found other offenses is the same
219
+ # range. Otherwise, two corrections can interfere with each other,
220
+ # resulting in corrupted code.
221
+ node = if autocorrect? && other_offense_in_same_range?(body_node)
222
+ nil
223
+ else
224
+ body_node
225
+ end
226
+
218
227
  indentation_name = style == 'normal' ? '' : "#{style} "
219
- add_offense(body_node, offending_range(body_node, indentation),
228
+ add_offense(node, offending_range(body_node, indentation),
220
229
  format("Use #{configured_indentation_width} (not %d) " \
221
230
  "spaces for #{indentation_name}indentation.",
222
231
  indentation))
223
232
  end
224
233
 
234
+ # Returns true if the given node is within another node that has
235
+ # already been marked for auto-correction by this cop.
236
+ def other_offense_in_same_range?(node)
237
+ expr = node.loc.expression
238
+ @offense_ranges ||= []
239
+
240
+ return true if @offense_ranges.any? { |r| within?(expr, r) }
241
+
242
+ @offense_ranges << expr
243
+ false
244
+ end
245
+
225
246
  def indentation_to_check?(base_loc, body_node)
226
247
  return false unless body_node
227
248
 
@@ -235,6 +256,11 @@ module RuboCop
235
256
  first_char_pos_on_line = body_node.loc.expression.source_line =~ /\S/
236
257
  return false unless body_node.loc.column == first_char_pos_on_line
237
258
 
259
+ if [:rescue, :ensure].include?(body_node.type)
260
+ block_body, = *body_node
261
+ return unless block_body
262
+ end
263
+
238
264
  true
239
265
  end
240
266
 
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cops checks for indentation of the first non-blank non-comment
7
+ # line in a file.
8
+ class InitialIndentation < Cop
9
+ MSG = 'Indentation of first line in file detected.'
10
+
11
+ def investigate(processed_source)
12
+ first_token = processed_source.tokens.find do |t|
13
+ !t.text.start_with?('#')
14
+ end
15
+ return unless first_token
16
+ return if first_token.pos.column == 0
17
+
18
+ with_space = range_with_surrounding_space(first_token.pos, :left,
19
+ nil, !:with_newline)
20
+ # If the file starts with a byte order mark (BOM), the column can be
21
+ # non-zero, but then we find out here if there's no space to the left
22
+ # of the first token.
23
+ return if with_space == first_token.pos
24
+
25
+ space = Parser::Source::Range.new(processed_source.buffer,
26
+ with_space.begin_pos,
27
+ first_token.pos.begin_pos)
28
+ add_offense(space, first_token.pos)
29
+ end
30
+
31
+ def autocorrect(range)
32
+ ->(corrector) { corrector.remove(range) }
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -6,13 +6,14 @@ module RuboCop
6
6
  # This cop checks whether comments have a leading space
7
7
  # after the # denoting the start of the comment. The
8
8
  # leading space is not required for some RDoc special syntax,
9
- # like #++, #--, #:nodoc, etc.
9
+ # like #++, #--, #:nodoc, etc. Neither is it required for
10
+ # =begin/=end comments.
10
11
  class LeadingCommentSpace < Cop
11
12
  MSG = 'Missing space after #.'
12
13
 
13
14
  def investigate(processed_source)
14
15
  processed_source.comments.each do |comment|
15
- next unless comment.text =~ /^#+[^#\s=:+-]/
16
+ next unless comment.text =~ /\A#+[^#\s=:+-]/
16
17
  next if comment.text.start_with?('#!') && comment.loc.line == 1
17
18
 
18
19
  add_offense(comment, :expression)
@@ -7,13 +7,17 @@ module RuboCop
7
7
  class MethodCallParentheses < Cop
8
8
  MSG = 'Do not use parentheses for method calls with no arguments.'
9
9
 
10
+ ASGN_NODES = [:lvasgn, :masgn] + Util::SHORTHAND_ASGN_NODES
11
+
10
12
  def on_send(node)
11
13
  _receiver, method_name, *args = *node
12
14
 
13
15
  # methods starting with a capital letter should be skipped
14
16
  return if method_name =~ /\A[A-Z]/
17
+ return unless args.empty? && node.loc.begin
18
+ return if same_name_assignment?(node)
15
19
 
16
- add_offense(node, :begin) if args.empty? && node.loc.begin
20
+ add_offense(node, :begin)
17
21
  end
18
22
 
19
23
  def autocorrect(node)
@@ -22,6 +26,29 @@ module RuboCop
22
26
  corrector.remove(node.loc.end)
23
27
  end
24
28
  end
29
+
30
+ private
31
+
32
+ def same_name_assignment?(node)
33
+ _receiver, method_name, *_args = *node
34
+
35
+ node.each_ancestor(ASGN_NODES).any? do |asgn_node|
36
+ if asgn_node.masgn_type?
37
+ mlhs_node, _mrhs_node = *asgn_node
38
+ asgn_node = mlhs_node.children[node.sibling_index]
39
+ end
40
+ # `obj.method = value` parses as (send ... :method= ...), and will
41
+ # not be returned as an `asgn_node` here
42
+ # however, `obj.method ||= value` parses as (or-asgn (send ...) ...)
43
+ # which IS an `asgn_node`
44
+ if asgn_node.or_asgn_type? || asgn_node.and_asgn_type?
45
+ asgn_node, _value = *asgn_node
46
+ return false if asgn_node.send_type?
47
+ end
48
+
49
+ asgn_node.loc.name.source == method_name.to_s
50
+ end
51
+ end
25
52
  end
26
53
  end
27
54
  end
@@ -10,7 +10,9 @@ module RuboCop
10
10
  include ConfigurableEnforcedStyle
11
11
 
12
12
  def on_method_def(node, _method_name, args, _body)
13
- if style == :require_parentheses
13
+ if style == :require_parentheses ||
14
+ (style == :require_no_parentheses_except_multiline &&
15
+ args.multiline?)
14
16
  if arguments?(args) && !parentheses?(args)
15
17
  missing_parentheses(node, args)
16
18
  else
@@ -25,7 +27,11 @@ module RuboCop
25
27
 
26
28
  def autocorrect(node)
27
29
  lambda do |corrector|
28
- if style == :require_parentheses
30
+ if node.args_type?
31
+ # offense is registered on args node when parentheses are unwanted
32
+ corrector.replace(node.loc.begin, ' ')
33
+ corrector.remove(node.loc.end)
34
+ else
29
35
  args_expr = args_node(node).loc.expression
30
36
  args_with_space = range_with_surrounding_space(args_expr, :left)
31
37
  just_space = Parser::Source::Range.new(args_expr.source_buffer,
@@ -33,9 +39,6 @@ module RuboCop
33
39
  args_expr.begin_pos)
34
40
  corrector.replace(just_space, '(')
35
41
  corrector.insert_after(args_expr, ')')
36
- elsif style == :require_no_parentheses
37
- corrector.replace(node.loc.begin, ' ')
38
- corrector.remove(node.loc.end)
39
42
  end
40
43
  end
41
44
  end
@@ -45,13 +48,13 @@ module RuboCop
45
48
  def missing_parentheses(node, args)
46
49
  add_offense(node, args.loc.expression,
47
50
  'Use def with parentheses when there are parameters.') do
48
- opposite_style_detected
51
+ unexpected_style_detected(:require_no_parentheses)
49
52
  end
50
53
  end
51
54
 
52
55
  def unwanted_parentheses(args)
53
56
  add_offense(args, :expression, 'Use def without parentheses.') do
54
- opposite_style_detected
57
+ unexpected_style_detected(:require_parentheses)
55
58
  end
56
59
  end
57
60
 
@@ -12,7 +12,7 @@ module RuboCop
12
12
  # b
13
13
  # something
14
14
  # end
15
- class MultilineOperationIndentation < Cop # rubocop:disable ClassLength
15
+ class MultilineOperationIndentation < Cop
16
16
  include ConfigurableEnforcedStyle
17
17
  include AutocorrectAlignment
18
18
 
@@ -98,7 +98,7 @@ module RuboCop
98
98
  article = kw =~ /^[iu]/ ? 'an' : 'a'
99
99
  "a #{kind} in #{article} `#{kw}` statement"
100
100
  else
101
- 'an expression' + (assignment?(node) ? ' in an assignment' : '')
101
+ 'an expression' + (assignment_rhs?(node) ? ' in an assignment' : '')
102
102
  end
103
103
  end
104
104
 
@@ -121,15 +121,17 @@ module RuboCop
121
121
  _, method_name, *args = *send_node
122
122
  if operator?(method_name) && args.any?
123
123
  args.first.loc.expression
124
- elsif send_node.loc.dot &&
125
- send_node.loc.selector &&
126
- send_node.loc.dot.line == send_node.loc.selector.line
127
- send_node.loc.dot.join(send_node.loc.selector)
128
- elsif send_node.loc.selector
129
- send_node.loc.selector
130
- elsif send_node.loc.dot.line == send_node.loc.begin.line
131
- # lambda.(args)
132
- send_node.loc.dot.join(send_node.loc.begin)
124
+ else
125
+ dot = send_node.loc.dot
126
+ selector = send_node.loc.selector
127
+ if dot && selector && dot.line == selector.line
128
+ dot.join(selector)
129
+ elsif selector
130
+ selector
131
+ elsif dot.line == send_node.loc.begin.line
132
+ # lambda.(args)
133
+ dot.join(send_node.loc.begin)
134
+ end
133
135
  end
134
136
  end
135
137
 
@@ -140,7 +142,7 @@ module RuboCop
140
142
 
141
143
  def should_align?(node, given_style)
142
144
  given_style == :aligned && (kw_node_with_special_indentation(node) ||
143
- assignment?(node))
145
+ assignment_rhs?(node))
144
146
  end
145
147
 
146
148
  def kw_node_with_special_indentation(node)
@@ -158,22 +160,17 @@ module RuboCop
158
160
  end
159
161
  end
160
162
 
161
- def assignment?(node)
162
- node.each_ancestor.find do |a|
163
- case a.type
164
- when :send
165
- _receiver, method_name, *_args = *a
166
- # The []= operator is the only assignment operator that is parsed
167
- # as a :send node.
168
- method_name == :[]=
169
- when *ASGN_NODES
170
- true
171
- end
163
+ def assignment_rhs?(node)
164
+ node.ancestors.unshift(node).each_cons(2).any? do |child, parent|
165
+ return true if parent.asgn_rhs.equal?(child)
166
+ grandparent = parent.parent
167
+ return true if grandparent && grandparent.masgn_type? &&
168
+ grandparent.asgn_rhs.equal?(parent)
172
169
  end
173
170
  end
174
171
 
175
172
  def not_for_this_cop?(node)
176
- node.each_ancestor.find do |ancestor|
173
+ node.each_ancestor.any? do |ancestor|
177
174
  grouped_expression?(ancestor) ||
178
175
  inside_arg_list_parentheses?(node, ancestor)
179
176
  end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks whether some constant value isn't a
7
+ # mutable literal (e.g. array or hash).
8
+ #
9
+ # @example
10
+ # # bad
11
+ # CONST = [1, 2, 3]
12
+ #
13
+ # # good
14
+ # CONST = [1, 2, 3].freeze
15
+ class MutableConstant < Cop
16
+ MSG = 'Freeze mutable objects assigned to constants.'
17
+
18
+ MUTABLE_TYPES = [:array, :hash, :str, :dstr].freeze
19
+
20
+ def on_casgn(node)
21
+ _scope, _const_name, value = *node
22
+
23
+ return if value && !MUTABLE_TYPES.include?(value.type)
24
+
25
+ add_offense(value, :expression)
26
+ end
27
+
28
+ def autocorrect(node)
29
+ expr = node.loc.expression
30
+ ->(corrector) { corrector.replace(expr, "#{expr.source}.freeze") }
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,97 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for nested use of if, unless, while and until in their
7
+ # modifier form.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # something if a if b
13
+ #
14
+ # # good
15
+ # something if b && a
16
+ class NestedModifier < Cop
17
+ include IfNode
18
+
19
+ MSG = 'Avoid using nested modifiers.'
20
+
21
+ def on_while(node)
22
+ check(node)
23
+ end
24
+
25
+ def on_until(node)
26
+ check(node)
27
+ end
28
+
29
+ def on_if(node)
30
+ check(node)
31
+ end
32
+
33
+ def check(node)
34
+ return if part_of_ignored_node?(node)
35
+ return unless modifier?(node)
36
+
37
+ ancestor = node.ancestors.first
38
+ return unless ancestor &&
39
+ [:if, :while, :until].include?(ancestor.type) &&
40
+ modifier?(ancestor)
41
+
42
+ add_offense(node, :keyword)
43
+ ignore_node(node)
44
+ end
45
+
46
+ def modifier?(node)
47
+ modifier_if?(node) || modifier_while_or_until?(node)
48
+ end
49
+
50
+ def modifier_while_or_until?(node)
51
+ node.loc.respond_to?(:keyword) &&
52
+ %w(while until).include?(node.loc.keyword.source) &&
53
+ node.loc.respond_to?(:end) && node.loc.end.nil?
54
+ end
55
+
56
+ def autocorrect(node)
57
+ return unless node.if_type?
58
+
59
+ ancestor = node.ancestors.first
60
+ return unless ancestor.if_type?
61
+
62
+ autocorrect_if_unless(ancestor, node)
63
+ end
64
+
65
+ def autocorrect_if_unless(outer_node, inner_node)
66
+ outer_cond, = *outer_node
67
+
68
+ range =
69
+ Parser::Source::Range.new(inner_node.loc.expression.source_buffer,
70
+ inner_node.loc.keyword.begin_pos,
71
+ outer_cond.loc.expression.end_pos)
72
+
73
+ lambda do |corrector|
74
+ corrector.replace(range, new_expression(outer_node, inner_node))
75
+ end
76
+ end
77
+
78
+ def new_expression(outer_node, inner_node)
79
+ outer_cond, = *outer_node
80
+ inner_cond, = *inner_node
81
+
82
+ outer_keyword = outer_node.loc.keyword.source
83
+ inner_keyword = inner_node.loc.keyword.source
84
+
85
+ operator = outer_keyword == 'if' ? '&&' : '||'
86
+
87
+ inner_expr = inner_cond.loc.expression.source
88
+ inner_expr = "(#{inner_expr})" if inner_cond.or_type?
89
+ inner_expr = "!#{inner_expr}" unless outer_keyword == inner_keyword
90
+
91
+ "#{outer_node.loc.keyword.source} " \
92
+ "#{outer_cond.loc.expression.source} #{operator} #{inner_expr}"
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end