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
@@ -6,24 +6,43 @@ module RuboCop
6
6
  # This cop checks whether method definitions are
7
7
  # separated by empty lines.
8
8
  class EmptyLineBetweenDefs < Cop
9
- MSG = 'Use empty lines between defs.'
9
+ include OnMethodDef
10
+ MSG = 'Use empty lines between method definitions.'
10
11
 
11
- def on_def(node)
12
- if @prev_def_end && (def_start(node) - @prev_def_end) == 1
13
- unless @prev_was_single_line && singe_line_def?(node) &&
14
- cop_config['AllowAdjacentOneLineDefs']
15
- add_offense(node, :keyword)
16
- end
17
- end
12
+ def on_method_def(node, _method_name, _args, _body)
13
+ return unless node.parent && node.parent.begin_type?
18
14
 
19
- @prev_def_end = def_end(node)
20
- @prev_was_single_line = singe_line_def?(node)
15
+ nodes = [prev_node(node), node]
16
+
17
+ return unless nodes.all?(&method(:def_node?))
18
+ return if blank_lines_between?(*nodes)
19
+ return if nodes.all?(&:single_line?) &&
20
+ cop_config['AllowAdjacentOneLineDefs']
21
+
22
+ add_offense(node, :keyword)
21
23
  end
22
24
 
23
25
  private
24
26
 
25
- def singe_line_def?(node)
26
- def_start(node) == def_end(node)
27
+ def def_node?(node)
28
+ return unless node
29
+ node.def_type? || node.defs_type?
30
+ end
31
+
32
+ def blank_lines_between?(first_def_node, second_def_node)
33
+ lines_between_defs(first_def_node, second_def_node).any?(&:blank?)
34
+ end
35
+
36
+ def prev_node(node)
37
+ return nil unless node.sibling_index > 0
38
+
39
+ node.parent.children[node.sibling_index - 1]
40
+ end
41
+
42
+ def lines_between_defs(first_def_node, second_def_node)
43
+ line_range = def_end(first_def_node)..(def_start(second_def_node) - 2)
44
+
45
+ processed_source.lines[line_range]
27
46
  end
28
47
 
29
48
  def def_start(node)
@@ -35,8 +54,14 @@ module RuboCop
35
54
  end
36
55
 
37
56
  def autocorrect(node)
38
- range = range_with_surrounding_space(node.loc.expression, :left)
39
- ->(corrector) { corrector.insert_before(range, "\n") }
57
+ prev_def = prev_node(node)
58
+ end_pos = prev_def.loc.end.end_pos
59
+ source_buffer = prev_def.loc.end.source_buffer
60
+ newline_pos = source_buffer.source.index("\n", end_pos)
61
+ newline = Parser::Source::Range.new(source_buffer,
62
+ newline_pos,
63
+ newline_pos + 1)
64
+ ->(corrector) { corrector.insert_after(newline, "\n") }
40
65
  end
41
66
  end
42
67
  end
@@ -16,7 +16,9 @@ module RuboCop
16
16
 
17
17
  MSG_MISSING = 'Missing utf-8 encoding comment.'
18
18
  MSG_UNNECESSARY = 'Unnecessary utf-8 encoding comment.'
19
- ENCODING_PATTERN = /#.*coding\s?[:=]\s?(?:UTF|utf)-8/
19
+ ENCODING_PATTERN = /#.*coding\s?[:=]\s?(?:UTF|utf)-8/.freeze
20
+ AUTO_CORRECT_ENCODING_COMMENT = 'AutoCorrectEncodingComment'.freeze
21
+ SHEBANG = '#!'.freeze
20
22
 
21
23
  def investigate(processed_source)
22
24
  return if processed_source.buffer.source.empty?
@@ -31,10 +33,14 @@ module RuboCop
31
33
  end
32
34
 
33
35
  def autocorrect(node)
34
- encoding = cop_config['AutoCorrectEncodingComment']
36
+ encoding = cop_config[AUTO_CORRECT_ENCODING_COMMENT]
35
37
  if encoding && encoding =~ ENCODING_PATTERN
36
38
  lambda do |corrector|
37
- corrector.replace(node.pos, "#{encoding}\n#{node.pos.source}")
39
+ if encoding_line_number(processed_source) == 0
40
+ corrector.insert_before(node.pos, "#{encoding}\n")
41
+ else
42
+ corrector.insert_after(node.pos, "\n#{encoding}")
43
+ end
38
44
  end
39
45
  else
40
46
  fail "#{encoding} does not match #{ENCODING_PATTERN}"
@@ -58,7 +64,7 @@ module RuboCop
58
64
 
59
65
  def encoding_line_number(processed_source)
60
66
  line_number = 0
61
- line_number += 1 if processed_source[line_number].start_with?('#!')
67
+ line_number += 1 if processed_source[line_number].start_with?(SHEBANG)
62
68
  line_number
63
69
  end
64
70
  end
@@ -7,19 +7,36 @@ module RuboCop
7
7
  #
8
8
  # @example
9
9
  #
10
- # name = "RuboCop"
10
+ # # good if AllowForAlignment is true
11
+ # name = "RuboCop"
12
+ # # Some comment and an empty line
13
+ #
14
+ # website += "/bbatsov/rubocop" unless cond
15
+ # puts "rubocop" if debug
16
+ #
17
+ # # bad for any configuration
18
+ # set_app("RuboCop")
11
19
  # website = "https://github.com/bbatsov/rubocop"
12
20
  class ExtraSpacing < Cop
13
21
  MSG = 'Unnecessary spacing detected.'
14
22
 
15
23
  def investigate(processed_source)
24
+ ast = processed_source.ast
25
+
16
26
  processed_source.tokens.each_cons(2) do |t1, t2|
17
- next unless t1.pos.line == t2.pos.line
18
- next unless t2.pos.begin_pos - 1 > t1.pos.end_pos
19
- buffer = processed_source.buffer
27
+ next if t2.type == :tNL
28
+ next if t1.pos.line != t2.pos.line
29
+ next if t2.pos.begin_pos - 1 <= t1.pos.end_pos
30
+ next if allow_for_alignment? && aligned_with_something?(t2)
20
31
  start_pos = t1.pos.end_pos
32
+ next if ignored_ranges(ast).find { |r| r.include?(start_pos) }
33
+
21
34
  end_pos = t2.pos.begin_pos - 1
22
- range = Parser::Source::Range.new(buffer, start_pos, end_pos)
35
+ range = Parser::Source::Range.new(processed_source.buffer,
36
+ start_pos, end_pos)
37
+ # Unary + doesn't appear as a token and needs special handling.
38
+ next if unary_plus_non_offense?(range)
39
+
23
40
  add_offense(range, range, MSG)
24
41
  end
25
42
  end
@@ -27,6 +44,110 @@ module RuboCop
27
44
  def autocorrect(range)
28
45
  ->(corrector) { corrector.remove(range) }
29
46
  end
47
+
48
+ private
49
+
50
+ def unary_plus_non_offense?(range)
51
+ range.resize(range.size + 1).source =~ /^ ?\+$/
52
+ end
53
+
54
+ # Returns an array of ranges that should not be reported. It's the
55
+ # extra spaces between the keys and values in a hash, since those are
56
+ # handled by the Style/AlignHash cop.
57
+ def ignored_ranges(ast)
58
+ return [] unless ast
59
+
60
+ @ignored_ranges ||= begin
61
+ ranges = []
62
+ on_node(:pair, ast) do |pair|
63
+ key, value = *pair
64
+ r = key.loc.expression.end_pos...value.loc.expression.begin_pos
65
+ ranges << r
66
+ end
67
+ ranges
68
+ end
69
+ end
70
+
71
+ def allow_for_alignment?
72
+ cop_config['AllowForAlignment']
73
+ end
74
+
75
+ def aligned_with_something?(token)
76
+ return aligned_comments?(token) if token.type == :tCOMMENT
77
+
78
+ pre = (token.pos.line - 2).downto(0)
79
+ post = token.pos.line.upto(processed_source.lines.size - 1)
80
+ return true if aligned_with?(pre, token) || aligned_with?(post, token)
81
+
82
+ # If no aligned token was found, search for an aligned token on the
83
+ # nearest line with the same indentation as the checked line.
84
+ base_indentation = processed_source.lines[token.pos.line - 1] =~ /\S/
85
+ aligned_with?(pre, token, base_indentation) ||
86
+ aligned_with?(post, token, base_indentation)
87
+ end
88
+
89
+ def aligned_comments?(token)
90
+ ix = processed_source.comments.index do |c|
91
+ c.loc.expression.begin_pos == token.pos.begin_pos
92
+ end
93
+ aligned_with_previous_comment?(ix) || aligned_with_next_comment?(ix)
94
+ end
95
+
96
+ def aligned_with_previous_comment?(ix)
97
+ ix > 0 && comment_column(ix - 1) == comment_column(ix)
98
+ end
99
+
100
+ def aligned_with_next_comment?(ix)
101
+ ix < processed_source.comments.length - 1 &&
102
+ comment_column(ix + 1) == comment_column(ix)
103
+ end
104
+
105
+ def comment_column(ix)
106
+ processed_source.comments[ix].loc.column
107
+ end
108
+
109
+ # Returns true if the previous or next line, not counting empty or
110
+ # comment lines, contains a token that's aligned with the given
111
+ # token. If base_indentation is given, lines with different indentation
112
+ # than the base indentation are also skipped.
113
+ def aligned_with?(indices_to_check, token, base_indentation = nil)
114
+ indices_to_check.each do |ix|
115
+ next if comment_lines.include?(ix + 1)
116
+ line = processed_source.lines[ix]
117
+ next if line.strip.empty?
118
+ if base_indentation
119
+ indentation = line =~ /\S/
120
+ next if indentation != base_indentation
121
+ end
122
+ return (aligned_words?(token, line) ||
123
+ aligned_assignments?(token, line) ||
124
+ aligned_same_character?(token, line))
125
+ end
126
+ false # No line to check was found.
127
+ end
128
+
129
+ def comment_lines
130
+ @comment_lines ||=
131
+ begin
132
+ whole_line_comments = processed_source.comments.select do |c|
133
+ begins_its_line?(c.loc.expression)
134
+ end
135
+ whole_line_comments.map { |c| c.loc.line }
136
+ end
137
+ end
138
+
139
+ def aligned_words?(token, line)
140
+ line[token.pos.column - 1, 2] =~ /\s\S/
141
+ end
142
+
143
+ def aligned_assignments?(token, line)
144
+ token.type == :tOP_ASGN &&
145
+ line[token.pos.column + token.text.length] == '='
146
+ end
147
+
148
+ def aligned_same_character?(token, line)
149
+ line[token.pos.column] == token.text.to_s[0]
150
+ end
30
151
  end
31
152
  end
32
153
  end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for a line break before the first element in a
7
+ # multi-line array.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # [ :a,
13
+ # :b]
14
+ #
15
+ # # good
16
+ # [
17
+ # :a,
18
+ # :b]
19
+ #
20
+ class FirstArrayElementLineBreak < Cop
21
+ include FirstElementLineBreak
22
+
23
+ MSG = 'Add a line break before the first element of a ' \
24
+ 'multi-line array.'
25
+
26
+ def on_array(node)
27
+ return if !node.loc.begin && !assignment_on_same_line?(node)
28
+
29
+ check_children_line_break(node, node.children)
30
+ end
31
+
32
+ private
33
+
34
+ def assignment_on_same_line?(node)
35
+ source = node.loc.expression.source_line[0...node.loc.column]
36
+ source =~ /\s*\=\s*$/
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for a line break before the first element in a
7
+ # multi-line hash.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # { a: 1,
13
+ # b: 2}
14
+ #
15
+ # # good
16
+ # {
17
+ # a: 1,
18
+ # b: 2 }
19
+ class FirstHashElementLineBreak < Cop
20
+ include FirstElementLineBreak
21
+
22
+ MSG = 'Add a line break before the first element of a ' \
23
+ 'multi-line hash.'
24
+
25
+ def on_hash(node)
26
+ if node.loc.begin
27
+ check_children_line_break(node, node.children)
28
+ elsif method_uses_parens?(node.parent, node)
29
+ check_children_line_break(node, node.children, node.parent)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for a line break before the first argument in a
7
+ # multi-line method call.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # method(foo, bar,
13
+ # baz)
14
+ #
15
+ # # good
16
+ # method(
17
+ # foo, bar,
18
+ # baz)
19
+ #
20
+ # # ignored
21
+ # method foo, bar,
22
+ # baz
23
+ class FirstMethodArgumentLineBreak < Cop
24
+ include FirstElementLineBreak
25
+
26
+ MSG = 'Add a line break before the first argument of a ' \
27
+ 'multi-line method argument list.'
28
+
29
+ def on_send(node)
30
+ _receiver, _name, *args = *node
31
+
32
+ check_method_line_break(node, args)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for a line break before the first parameter in a
7
+ # multi-line method parameter definition.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # def method(foo, bar,
13
+ # baz)
14
+ # do_something
15
+ # end
16
+ #
17
+ # # good
18
+ # def method(
19
+ # foo, bar,
20
+ # baz)
21
+ # do_something
22
+ # end
23
+ #
24
+ # # ignored
25
+ # def method foo,
26
+ # bar
27
+ # do_something
28
+ # end
29
+ class FirstMethodParameterLineBreak < Cop
30
+ include OnMethodDef
31
+ include FirstElementLineBreak
32
+
33
+ MSG = 'Add a line break before the first parameter of a ' \
34
+ 'multi-line method parameter list.'
35
+
36
+ def on_method_def(node, _method_name, args, _body)
37
+ check_method_line_break(node, args.to_a)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -39,14 +39,16 @@ module RuboCop
39
39
  private
40
40
 
41
41
  def message(arg_node)
42
+ return 'Bad indentation of the first parameter.' if arg_node.nil?
43
+
42
44
  send_node = arg_node.parent
43
45
  text = base_range(send_node, arg_node).source.strip
44
46
  base = if text !~ /\n/ && special_inner_call_indentation?(send_node)
45
47
  "`#{text}`"
46
48
  elsif text.lines.reverse_each.first =~ /^\s*#/
47
- 'the previous line (not counting the comment)'
49
+ 'the start of the previous line (not counting the comment)'
48
50
  else
49
- 'the previous line'
51
+ 'the start of the previous line'
50
52
  end
51
53
  format('Indent the first parameter one step more than %s.', base)
52
54
  end
@@ -97,7 +99,7 @@ module RuboCop
97
99
  .map { |c| c.loc.line }
98
100
 
99
101
  line = ''
100
- while line =~ /^\s*$/ || @comment_lines.include?(line_number)
102
+ while line.blank? || @comment_lines.include?(line_number)
101
103
  line_number -= 1
102
104
  line = processed_source.lines[line_number - 1]
103
105
  end
@@ -9,6 +9,7 @@ module RuboCop
9
9
  # allowed, however.
10
10
  class For < Cop
11
11
  include ConfigurableEnforcedStyle
12
+ EACH_LENGTH = 'each'.length
12
13
 
13
14
  def on_for(node)
14
15
  if style == :each
@@ -32,7 +33,7 @@ module RuboCop
32
33
  if style == :for
33
34
  end_pos = method.loc.expression.end_pos
34
35
  range = Parser::Source::Range.new(processed_source.buffer,
35
- end_pos - 'each'.length,
36
+ end_pos - EACH_LENGTH,
36
37
  end_pos)
37
38
  add_offense(range, range, 'Prefer `for` over `each`.') do
38
39
  opposite_style_detected
@@ -101,6 +101,11 @@ module RuboCop
101
101
 
102
102
  def valid_19_syntax_symbol?(sym_name)
103
103
  sym_name.sub!(/\A:/, '')
104
+
105
+ # Most hash keys can be matched against a simple regex.
106
+ return true if sym_name =~ /\A[_a-z]\w*[?!]?\z/i
107
+
108
+ # For more complicated hash keys, let the Parser validate the syntax.
104
109
  RuboCop::ProcessedSource.new("{ #{sym_name}: :foo }").valid_syntax?
105
110
  end
106
111
 
@@ -9,6 +9,8 @@ module RuboCop
9
9
  class IfUnlessModifier < Cop
10
10
  include StatementModifier
11
11
 
12
+ ASSIGNMENT_TYPES = [:lvasgn, :casgn, :cvasgn, :gvasgn, :ivasgn, :masgn]
13
+
12
14
  def message(keyword)
13
15
  "Favor modifier `#{keyword}` usage when having a single-line body." \
14
16
  ' Another good alternative is the usage of control flow `&&`/`||`.'
@@ -20,17 +22,41 @@ module RuboCop
20
22
  return if modifier_if?(node)
21
23
  return if elsif?(node)
22
24
  return if if_else?(node)
25
+ return if chained?(node)
23
26
  return unless fit_within_line_as_modifier_form?(node)
24
27
  add_offense(node, :keyword, message(node.loc.keyword.source))
25
28
  end
26
29
 
27
- def autocorrect(node)
28
- if node.loc.keyword.is?('if')
29
- cond, body = *node
30
- else
31
- cond, _else, body = *node
30
+ def chained?(node)
31
+ # Don't register offense for `if ... end.method`
32
+ return false if node.parent.nil? || !node.parent.send_type?
33
+ receiver = node.parent.children[0]
34
+ node.equal?(receiver)
35
+ end
36
+
37
+ def parenthesize?(node)
38
+ # Parenthesize corrected expression if changing to modifier-if form
39
+ # would change the meaning of the parent expression
40
+ # (due to the low operator precedence of modifier-if)
41
+ return false if node.parent.nil?
42
+ return true if ASSIGNMENT_TYPES.include?(node.parent.type)
43
+
44
+ if node.parent.send_type?
45
+ _receiver, _name, *args = *node.parent
46
+ return !method_uses_parens?(node.parent, args.first)
32
47
  end
33
48
 
49
+ false
50
+ end
51
+
52
+ def method_uses_parens?(node, limit)
53
+ source = node.loc.expression.source_line[0...limit.loc.column]
54
+ source =~ /\s*\(\s*$/
55
+ end
56
+
57
+ def autocorrect(node)
58
+ cond, body, _else = if_node_parts(node)
59
+
34
60
  oneline =
35
61
  "#{body.loc.expression.source} #{node.loc.keyword.source} " +
36
62
  cond.loc.expression.source
@@ -40,6 +66,7 @@ module RuboCop
40
66
  if first_line_comment
41
67
  oneline << ' ' << first_line_comment.loc.expression.source
42
68
  end
69
+ oneline = "(#{oneline})" if parenthesize?(node)
43
70
 
44
71
  ->(corrector) { corrector.replace(node.loc.expression, oneline) }
45
72
  end