rubocop 0.40.0 → 0.41.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubocop might be problematic. Click here for more details.

Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -1014
  3. data/config/default.yml +61 -5
  4. data/config/disabled.yml +6 -0
  5. data/config/enabled.yml +63 -4
  6. data/lib/rubocop.rb +17 -1
  7. data/lib/rubocop/ast_node.rb +56 -42
  8. data/lib/rubocop/ast_node/traversal.rb +3 -3
  9. data/lib/rubocop/cli.rb +14 -9
  10. data/lib/rubocop/comment_config.rb +85 -32
  11. data/lib/rubocop/config.rb +29 -8
  12. data/lib/rubocop/config_loader.rb +1 -1
  13. data/lib/rubocop/cop/cop.rb +1 -1
  14. data/lib/rubocop/cop/corrector.rb +13 -0
  15. data/lib/rubocop/cop/lint/block_alignment.rb +25 -11
  16. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +5 -2
  17. data/lib/rubocop/cop/lint/inherit_exception.rb +69 -0
  18. data/lib/rubocop/cop/lint/percent_string_array.rb +60 -0
  19. data/lib/rubocop/cop/lint/percent_symbol_array.rb +57 -0
  20. data/lib/rubocop/cop/lint/shadowed_exception.rb +95 -0
  21. data/lib/rubocop/cop/lint/useless_access_modifier.rb +28 -13
  22. data/lib/rubocop/cop/mixin/autocorrect_alignment.rb +25 -19
  23. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +16 -8
  24. data/lib/rubocop/cop/mixin/if_node.rb +1 -2
  25. data/lib/rubocop/cop/mixin/integer_node.rb +13 -0
  26. data/lib/rubocop/cop/mixin/match_range.rb +26 -0
  27. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +16 -7
  28. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +18 -1
  29. data/lib/rubocop/cop/mixin/negative_conditional.rb +6 -4
  30. data/lib/rubocop/cop/mixin/percent_literal.rb +10 -0
  31. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +24 -6
  32. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +20 -7
  33. data/lib/rubocop/cop/mixin/string_literals_help.rb +2 -2
  34. data/lib/rubocop/cop/mixin/trailing_comma.rb +34 -20
  35. data/lib/rubocop/cop/performance/flat_map.rb +23 -10
  36. data/lib/rubocop/cop/performance/push_splat.rb +47 -0
  37. data/lib/rubocop/cop/performance/redundant_block_call.rb +24 -1
  38. data/lib/rubocop/cop/performance/redundant_merge.rb +3 -5
  39. data/lib/rubocop/cop/performance/sample.rb +15 -11
  40. data/lib/rubocop/cop/rails/exit.rb +62 -0
  41. data/lib/rubocop/cop/rails/output_safety.rb +45 -0
  42. data/lib/rubocop/cop/rails/pluralization_grammar.rb +12 -4
  43. data/lib/rubocop/cop/rails/request_referer.rb +40 -0
  44. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +63 -28
  45. data/lib/rubocop/cop/rails/validation.rb +37 -23
  46. data/lib/rubocop/cop/style/alias.rb +10 -6
  47. data/lib/rubocop/cop/style/bare_percent_literals.rb +18 -7
  48. data/lib/rubocop/cop/style/block_delimiters.rb +15 -22
  49. data/lib/rubocop/cop/style/closing_parenthesis_indentation.rb +19 -8
  50. data/lib/rubocop/cop/style/comment_indentation.rb +13 -5
  51. data/lib/rubocop/cop/style/conditional_assignment.rb +111 -59
  52. data/lib/rubocop/cop/style/documentation.rb +7 -1
  53. data/lib/rubocop/cop/style/each_for_simple_loop.rb +43 -0
  54. data/lib/rubocop/cop/style/each_with_object.rb +25 -14
  55. data/lib/rubocop/cop/style/empty_else.rb +6 -10
  56. data/lib/rubocop/cop/style/extra_spacing.rb +20 -3
  57. data/lib/rubocop/cop/style/file_name.rb +16 -4
  58. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +1 -1
  59. data/lib/rubocop/cop/style/hash_syntax.rb +9 -2
  60. data/lib/rubocop/cop/style/if_unless_modifier.rb +20 -13
  61. data/lib/rubocop/cop/style/implicit_runtime_error.rb +32 -0
  62. data/lib/rubocop/cop/style/infinite_loop.rb +42 -5
  63. data/lib/rubocop/cop/style/lambda.rb +22 -0
  64. data/lib/rubocop/cop/style/method_def_parentheses.rb +12 -4
  65. data/lib/rubocop/cop/style/module_function.rb +28 -6
  66. data/lib/rubocop/cop/style/multiline_method_call_indentation.rb +49 -12
  67. data/lib/rubocop/cop/style/mutable_constant.rb +8 -1
  68. data/lib/rubocop/cop/style/nested_modifier.rb +1 -1
  69. data/lib/rubocop/cop/style/next.rb +43 -31
  70. data/lib/rubocop/cop/style/not.rb +33 -13
  71. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +92 -0
  72. data/lib/rubocop/cop/style/numeric_literals.rb +1 -4
  73. data/lib/rubocop/cop/style/parallel_assignment.rb +26 -8
  74. data/lib/rubocop/cop/style/{deprecated_hash_methods.rb → preferred_hash_methods.rb} +8 -8
  75. data/lib/rubocop/cop/style/redundant_parentheses.rb +29 -19
  76. data/lib/rubocop/cop/style/redundant_self.rb +13 -6
  77. data/lib/rubocop/cop/style/space_after_not.rb +7 -5
  78. data/lib/rubocop/cop/style/space_around_keyword.rb +6 -0
  79. data/lib/rubocop/cop/style/space_around_operators.rb +5 -1
  80. data/lib/rubocop/cop/style/space_before_first_arg.rb +21 -9
  81. data/lib/rubocop/cop/style/space_inside_array_percent_literal.rb +53 -0
  82. data/lib/rubocop/cop/style/space_inside_block_braces.rb +2 -2
  83. data/lib/rubocop/cop/style/space_inside_hash_literal_braces.rb +26 -6
  84. data/lib/rubocop/cop/style/space_inside_percent_literal_delimiters.rb +64 -0
  85. data/lib/rubocop/cop/style/string_literals.rb +37 -8
  86. data/lib/rubocop/cop/style/symbol_array.rb +21 -12
  87. data/lib/rubocop/cop/style/symbol_proc.rb +26 -19
  88. data/lib/rubocop/cop/style/word_array.rb +1 -5
  89. data/lib/rubocop/cop/style/zero_length_predicate.rb +6 -6
  90. data/lib/rubocop/cop/team.rb +40 -27
  91. data/lib/rubocop/cop/util.rb +13 -42
  92. data/lib/rubocop/formatter/disabled_config_formatter.rb +37 -14
  93. data/lib/rubocop/formatter/html_formatter.rb +3 -7
  94. data/lib/rubocop/result_cache.rb +18 -4
  95. data/{spec/support → lib/rubocop/rspec}/cop_helper.rb +3 -0
  96. data/lib/rubocop/rspec/host_environment_simulation_helper.rb +33 -0
  97. data/lib/rubocop/rspec/shared_contexts.rb +75 -0
  98. data/lib/rubocop/rspec/shared_examples.rb +101 -0
  99. data/lib/rubocop/rspec/support.rb +9 -0
  100. data/lib/rubocop/runner.rb +2 -2
  101. data/lib/rubocop/string_interpreter.rb +58 -0
  102. data/lib/rubocop/version.rb +1 -1
  103. metadata +27 -7
@@ -98,18 +98,13 @@ module RuboCop
98
98
  end
99
99
 
100
100
  def nil_check(node, else_clause)
101
- return unless else_clause && else_clause.type == :nil
102
- add_offense(node, node.location, MSG)
101
+ return unless else_clause && else_clause.nil_type?
102
+ add_offense(node, :else, MSG)
103
103
  end
104
104
 
105
105
  def both_check(node, else_clause)
106
- return if node.loc.else.nil?
107
-
108
- if else_clause.nil?
109
- add_offense(node, :else, MSG)
110
- elsif else_clause.type == :nil
111
- add_offense(node, :else, MSG)
112
- end
106
+ empty_check(node, else_clause)
107
+ nil_check(node, else_clause)
113
108
  end
114
109
 
115
110
  def autocorrect(node)
@@ -119,8 +114,9 @@ module RuboCop
119
114
  end_pos = if node.loc.end
120
115
  node.loc.end.begin_pos
121
116
  else
122
- node.source_range.end_pos + 1
117
+ node.parent.loc.end.begin_pos
123
118
  end
119
+
124
120
  range = Parser::Source::Range.new(node.source_range.source_buffer,
125
121
  node.loc.else.begin_pos,
126
122
  end_pos)
@@ -27,8 +27,15 @@ module RuboCop
27
27
  MSG_UNALIGNED_ASGN = '`=` is not aligned with the %s assignment.'.freeze
28
28
 
29
29
  def investigate(processed_source)
30
+ return if processed_source.ast.nil?
31
+
30
32
  if force_equal_sign_alignment?
31
33
  @asgn_tokens = processed_source.tokens.select { |t| equal_sign?(t) }
34
+ # we don't want to operate on equals signs which are part of an
35
+ # optarg in a method definition
36
+ # e.g.: def method(optarg = default_val); end
37
+ @asgn_tokens = remove_optarg_equals(@asgn_tokens, processed_source)
38
+
32
39
  # Only attempt to align the first = on each line
33
40
  @asgn_tokens = Set.new(@asgn_tokens.uniq { |t| t.pos.line })
34
41
  @asgn_lines = @asgn_tokens.map { |t| t.pos.line }
@@ -94,13 +101,13 @@ module RuboCop
94
101
 
95
102
  def check_other(t1, t2, ast)
96
103
  return if t1.pos.line != t2.pos.line
97
- return if t2.pos.begin_pos - 1 <= t1.pos.end_pos
98
104
  return if allow_for_alignment? && aligned_tok?(t2)
99
105
 
100
106
  start_pos = t1.pos.end_pos
101
- return if ignored_ranges(ast).find { |r| r.include?(start_pos) }
102
-
103
107
  end_pos = t2.pos.begin_pos - 1
108
+ return if end_pos <= start_pos
109
+ return if ignored_range?(ast, start_pos)
110
+
104
111
  range = Parser::Source::Range.new(processed_source.buffer,
105
112
  start_pos, end_pos)
106
113
  # Unary + doesn't appear as a token and needs special handling.
@@ -117,6 +124,10 @@ module RuboCop
117
124
  end
118
125
  end
119
126
 
127
+ def ignored_range?(ast, start_pos)
128
+ ignored_ranges(ast).any? { |r| r.include?(start_pos) }
129
+ end
130
+
120
131
  def unary_plus_non_offense?(range)
121
132
  range.resize(range.size + 1).source =~ /^ ?\+$/
122
133
  end
@@ -201,6 +212,12 @@ module RuboCop
201
212
  spaces = leading.size - (leading =~ / *\Z/)
202
213
  asgn_token.pos.last_column - spaces + 1
203
214
  end
215
+
216
+ def remove_optarg_equals(asgn_tokens, processed_source)
217
+ optargs = processed_source.ast.each_node(:optarg)
218
+ optarg_eql = optargs.map { |o| o.loc.operator.begin_pos }.to_set
219
+ asgn_tokens.reject { |t| optarg_eql.include?(t.pos.begin_pos) }
220
+ end
204
221
  end
205
222
  end
206
223
  end
@@ -125,11 +125,23 @@ module RuboCop
125
125
  # We can't assume that the working directory, or any other, is the
126
126
  # "starting point" to build a namespace
127
127
  start = %w(lib spec test src)
128
- if components.find { |c| start.include?(c) }
129
- components = components.drop_while { |c| !start.include?(c) }
130
- components.drop(1).map { |fn| to_module_name(fn) }
131
- else
128
+ start_index = nil
129
+
130
+ # To find the closest namespace root take the path components, and
131
+ # then work through them backwards until we find a candidate. This
132
+ # makes sure we work from the actual root in the case of a path like
133
+ # /home/user/src/project_name/lib.
134
+ components.reverse.each_with_index do |c, i|
135
+ if start.include?(c)
136
+ start_index = components.size - i
137
+ break
138
+ end
139
+ end
140
+
141
+ if start_index.nil?
132
142
  [to_module_name(components.last)]
143
+ else
144
+ components[start_index..-1].map { |c| to_module_name(c) }
133
145
  end
134
146
  end
135
147
 
@@ -18,7 +18,7 @@ module RuboCop
18
18
 
19
19
  def investigate(processed_source)
20
20
  return if style == :when_needed && target_ruby_version < 2.3
21
- return if processed_source.buffer.source.empty?
21
+ return if processed_source.tokens.empty?
22
22
 
23
23
  return if frozen_string_literal_comment_exists?(processed_source)
24
24
 
@@ -96,12 +96,19 @@ module RuboCop
96
96
 
97
97
  return false unless key.sym_type?
98
98
 
99
- valid_19_syntax_symbol?(key.source)
99
+ acceptable_19_syntax_symbol?(key.source)
100
100
  end
101
101
 
102
- def valid_19_syntax_symbol?(sym_name)
102
+ def acceptable_19_syntax_symbol?(sym_name)
103
103
  sym_name.sub!(/\A:/, '')
104
104
 
105
+ if cop_config['PreferHashRocketsForNonAlnumEndingSymbols']
106
+ # Prefer { :production? => false } over { production?: false } and
107
+ # similarly for other non-alnum final characters (except quotes,
108
+ # to prefer { "x y": 1 } over { :"x y" => 1 }).
109
+ return false unless sym_name =~ /[\p{Alnum}"']\z/
110
+ end
111
+
105
112
  # Most hash keys can be matched against a simple regex.
106
113
  return true if sym_name =~ /\A[_a-z]\w*[?!]?\z/i
107
114
 
@@ -52,19 +52,7 @@ module RuboCop
52
52
  end
53
53
 
54
54
  def autocorrect(node)
55
- cond, body, _else = if_node_parts(node)
56
-
57
- oneline =
58
- "#{body.source} #{node.loc.keyword.source} " + cond.source
59
- first_line_comment = processed_source.comments.find do |c|
60
- c.loc.line == node.loc.line
61
- end
62
- if first_line_comment
63
- oneline << ' ' << first_line_comment.loc.expression.source
64
- end
65
- oneline = "(#{oneline})" if parenthesize?(node)
66
-
67
- ->(corrector) { corrector.replace(node.source_range, oneline) }
55
+ ->(corrector) { corrector.replace(node.source_range, oneline(node)) }
68
56
  end
69
57
 
70
58
  private
@@ -73,6 +61,25 @@ module RuboCop
73
61
  def nested_conditional?(node)
74
62
  node.children[1, 2].any? { |child| child && child.type == :if }
75
63
  end
64
+
65
+ def oneline(node)
66
+ cond, body, _else = if_node_parts(node)
67
+
68
+ expr = "#{body.source} #{node.loc.keyword.source} " + cond.source
69
+ if (comment_after = first_line_comment(node))
70
+ expr << ' ' << comment_after
71
+ end
72
+ expr = "(#{expr})" if parenthesize?(node)
73
+
74
+ expr
75
+ end
76
+
77
+ def first_line_comment(node)
78
+ comment =
79
+ processed_source.comments.find { |c| c.loc.line == node.loc.line }
80
+
81
+ comment ? comment.loc.expression.source : nil
82
+ end
76
83
  end
77
84
  end
78
85
  end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module RuboCop
5
+ module Cop
6
+ module Style
7
+ # This cop checks for `raise` or `fail` statements which do not specify an
8
+ # explicit exception class. (This raises a `RuntimeError`. Some projects
9
+ # might prefer to use exception classes which more precisely identify the
10
+ # nature of the error.)
11
+ #
12
+ # @example
13
+ # @bad
14
+ # raise 'Error message here'
15
+ #
16
+ # @good
17
+ # raise ArgumentError, 'Error message here'
18
+ class ImplicitRuntimeError < Cop
19
+ def_node_matcher :implicit_runtime_error_raise_or_fail,
20
+ '(send nil ${:raise :fail} {str dstr})'
21
+
22
+ def on_send(node)
23
+ implicit_runtime_error_raise_or_fail(node) do |method|
24
+ add_offense(node, :expression, "Use `#{method}` with an explicit " \
25
+ 'exception class and message, ' \
26
+ 'rather than just a message.')
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -21,7 +21,6 @@ module RuboCop
21
21
 
22
22
  def on_while(node)
23
23
  condition, = *node
24
-
25
24
  return unless condition.truthy_literal?
26
25
 
27
26
  add_offense(node, :keyword)
@@ -29,13 +28,49 @@ module RuboCop
29
28
 
30
29
  def on_until(node)
31
30
  condition, = *node
32
-
33
31
  return unless condition.falsey_literal?
34
32
 
35
33
  add_offense(node, :keyword)
36
34
  end
37
35
 
36
+ alias on_while_post on_while
37
+ alias on_until_post on_until
38
+
38
39
  def autocorrect(node)
40
+ if node.while_post_type? || node.until_post_type?
41
+ _, body = *node
42
+ return lambda do |corrector|
43
+ corrector.replace(body.loc.begin, 'loop do')
44
+ corrector.remove(body.loc.end.end.join(node.source_range.end))
45
+ end
46
+ end
47
+
48
+ if node.modifier_form?
49
+ range = node.source_range
50
+ replacement = modifier_replacement(node)
51
+ else
52
+ range = non_modifier_range(node)
53
+ replacement = 'loop do'
54
+ end
55
+
56
+ ->(corrector) { corrector.replace(range, replacement) }
57
+ end
58
+
59
+ private
60
+
61
+ def modifier_replacement(node)
62
+ _, body = *node
63
+ if node.single_line?
64
+ 'loop { ' + body.source + ' }'
65
+ else
66
+ indentation = body.loc.expression.source_line[/\A(\s*)/]
67
+ "loop do\n" + indentation +
68
+ body.source.gsub(/^/, ' ' * configured_indentation_width) +
69
+ "\n#{indentation}end"
70
+ end
71
+ end
72
+
73
+ def non_modifier_range(node)
39
74
  condition_node, = *node
40
75
  start_range = node.loc.keyword.begin
41
76
  end_range = if node.loc.begin
@@ -43,9 +78,11 @@ module RuboCop
43
78
  else
44
79
  condition_node.source_range.end
45
80
  end
46
- lambda do |corrector|
47
- corrector.replace(start_range.join(end_range), 'loop do')
48
- end
81
+ start_range.join(end_range)
82
+ end
83
+
84
+ def configured_indentation_width
85
+ config.for_cop('IndentationWidth')['Width']
49
86
  end
50
87
  end
51
88
  end
@@ -137,6 +137,10 @@ module RuboCop
137
137
 
138
138
  def autocorrect_literal_to_method(corrector, node)
139
139
  block_method, args = *node
140
+
141
+ # Check for unparenthesized args' preceding and trailing whitespaces.
142
+ remove_unparenthesized_whitespaces(corrector, node)
143
+
140
144
  # Avoid correcting to `lambdado` by inserting whitespace
141
145
  # if none exists before or after the lambda arguments.
142
146
  if needs_whitespace?(block_method, args, node)
@@ -181,6 +185,24 @@ module RuboCop
181
185
  index = parent.children.index { |c| c.equal?(node) }
182
186
  index >= 2
183
187
  end
188
+
189
+ def remove_unparenthesized_whitespaces(corrector, node)
190
+ block_method, args = *node
191
+ return unless unparenthesized_literal_args?(args)
192
+ # First, remove leading whitespaces (beetween arrow and args)
193
+ corrector.remove_preceding(
194
+ args.source_range,
195
+ args.source_range.begin_pos - block_method.source_range.end_pos
196
+ )
197
+
198
+ # Then, remove trailing whitespaces (beetween args and 'do')
199
+ delta = node.loc.begin.begin_pos - args.source_range.end_pos - 1
200
+ corrector.remove_preceding(node.loc.begin, delta)
201
+ end
202
+
203
+ def unparenthesized_literal_args?(args)
204
+ args.source_range && args.source_range.begin && !parentheses?(args)
205
+ end
184
206
  end
185
207
  end
186
208
  end
@@ -11,10 +11,8 @@ module RuboCop
11
11
  include ConfigurableEnforcedStyle
12
12
 
13
13
  def on_method_def(node, _method_name, args, _body)
14
- if style == :require_parentheses ||
15
- (style == :require_no_parentheses_except_multiline &&
16
- args.multiline?)
17
- if arguments?(args) && !parentheses?(args)
14
+ if require_parentheses?(args)
15
+ if arguments_without_parentheses?(args)
18
16
  missing_parentheses(node, args)
19
17
  else
20
18
  correct_style_detected
@@ -46,6 +44,16 @@ module RuboCop
46
44
 
47
45
  private
48
46
 
47
+ def require_parentheses?(args)
48
+ style == :require_parentheses ||
49
+ (style == :require_no_parentheses_except_multiline &&
50
+ args.multiline?)
51
+ end
52
+
53
+ def arguments_without_parentheses?(args)
54
+ arguments?(args) && !parentheses?(args)
55
+ end
56
+
49
57
  def missing_parentheses(node, args)
50
58
  add_offense(node, args.source_range,
51
59
  'Use def with parentheses when there are parameters.') do
@@ -4,26 +4,48 @@
4
4
  module RuboCop
5
5
  module Cop
6
6
  module Style
7
- # This cops checks for use of `extend self` in a module.
7
+ # This cops checks for use of `extend self` or `module_function` in a
8
+ # module.
9
+ #
10
+ # Supported styles are: module_function, extend_self.
8
11
  #
9
12
  # @example
10
13
  #
14
+ # # Good if EnforcedStyle is module_function
11
15
  # module Test
12
- # extend self
16
+ # module_function
17
+ # ...
18
+ # end
13
19
  #
20
+ # # Good if EnforcedStyle is extend_self
21
+ # module Test
22
+ # extend self
14
23
  # ...
15
- # end
24
+ # end
25
+ #
26
+ # These offenses are not auto-corrected since there are different
27
+ # implications to each approach.
16
28
  class ModuleFunction < Cop
17
- MSG = 'Use `module_function` instead of `extend self`.'.freeze
29
+ include ConfigurableEnforcedStyle
30
+
31
+ MODULE_FUNCTION_MSG = 'Use `module_function` instead of `extend self`.'
32
+ .freeze
33
+ EXTEND_SELF_MSG = 'Use `extend self` instead of `module_function`.'
34
+ .freeze
18
35
 
19
- TARGET_NODE = s(:send, nil, :extend, s(:self))
36
+ MODULE_FUNCTION_NODE = s(:send, nil, :module_function)
37
+ EXTEND_SELF_NODE = s(:send, nil, :extend, s(:self))
20
38
 
21
39
  def on_module(node)
22
40
  _name, body = *node
23
41
  return unless body && body.type == :begin
24
42
 
25
43
  body.children.each do |body_node|
26
- add_offense(body_node, :expression) if body_node == TARGET_NODE
44
+ if style == :module_function && body_node == EXTEND_SELF_NODE
45
+ add_offense(body_node, :expression, MODULE_FUNCTION_MSG)
46
+ elsif style == :extend_self && body_node == MODULE_FUNCTION_NODE
47
+ add_offense(body_node, :expression, EXTEND_SELF_MSG)
48
+ end
27
49
  end
28
50
  end
29
51
  end
@@ -57,7 +57,7 @@ module RuboCop
57
57
 
58
58
  @base = alignment_base(node, rhs, given_style)
59
59
  correct_column = if @base
60
- @base.column
60
+ @base.column + extra_indentation(given_style)
61
61
  else
62
62
  indentation(lhs) + correct_indentation(node)
63
63
  end
@@ -65,20 +65,39 @@ module RuboCop
65
65
  rhs if @column_delta != 0
66
66
  end
67
67
 
68
+ def extra_indentation(given_style)
69
+ if given_style == :indented_relative_to_receiver
70
+ configured_indentation_width
71
+ else
72
+ 0
73
+ end
74
+ end
75
+
68
76
  def message(node, lhs, rhs)
69
- what = operation_description(node, rhs)
70
77
  if @base
71
- "Align `#{rhs.source}` with `#{@base.source[/[^\n]*/]}` on " \
78
+ base_source = @base.source[/[^\n]*/]
79
+ if style == :indented_relative_to_receiver
80
+ "Indent `#{rhs.source}` #{configured_indentation_width} spaces " \
81
+ "more than `#{base_source}` on line #{@base.line}."
82
+ else
83
+ "Align `#{rhs.source}` with `#{base_source}` on " \
72
84
  "line #{@base.line}."
85
+ end
73
86
  else
74
87
  used_indentation = rhs.column - indentation(lhs)
88
+ what = operation_description(node, rhs)
75
89
  "Use #{correct_indentation(node)} (not #{used_indentation}) " \
76
90
  "spaces for indenting #{what} spanning multiple lines."
77
91
  end
78
92
  end
79
93
 
80
94
  def alignment_base(node, rhs, given_style)
81
- return nil unless given_style == :aligned
95
+ return nil if given_style == :indented
96
+
97
+ if given_style == :indented_relative_to_receiver
98
+ receiver_base = receiver_alignment_base(node)
99
+ return receiver_base if receiver_base
100
+ end
82
101
 
83
102
  semantic_alignment_base(node, rhs) ||
84
103
  syntactic_alignment_base(node, rhs)
@@ -111,17 +130,35 @@ module RuboCop
111
130
  # a.b
112
131
  # .c
113
132
  def semantic_alignment_base(node, rhs)
114
- return nil unless rhs.source.start_with?('.')
115
- return nil if argument_in_method_call(node)
133
+ return unless rhs.source.start_with?('.')
134
+
135
+ node = semantic_alignment_node(node)
136
+ return unless node
137
+
138
+ node.loc.dot.join(node.loc.selector)
139
+ end
140
+
141
+ # a
142
+ # .b
143
+ # .c
144
+ def receiver_alignment_base(node)
145
+ node = semantic_alignment_node(node)
146
+ return unless node
147
+
148
+ node.receiver.source_range
149
+ end
116
150
 
117
- node, = *node while node.send_type? && node.loc.dot ||
118
- node.block_type?
119
- return nil unless node.parent.send_type?
151
+ def semantic_alignment_node(node)
152
+ return if argument_in_method_call(node)
120
153
 
121
- first_send = node.parent
122
- return nil if first_send.loc.dot.line != first_send.loc.line
154
+ # descend to root of method chain
155
+ node = node.receiver while node.receiver
156
+ # ascend to first call which has a dot
157
+ node = node.parent
158
+ node = node.parent until node.loc.dot
123
159
 
124
- first_send.loc.dot.join(first_send.loc.selector)
160
+ return if node.loc.dot.line != node.loc.line
161
+ node
125
162
  end
126
163
 
127
164
  def operation_rhs(node)