rubocop 0.14.1 → 0.15.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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -3
  3. data/CHANGELOG.md +245 -198
  4. data/README.md +7 -0
  5. data/Rakefile +5 -1
  6. data/config/default.yml +27 -4
  7. data/config/enabled.yml +18 -4
  8. data/lib/rubocop.rb +13 -1
  9. data/lib/rubocop/cli.rb +83 -23
  10. data/lib/rubocop/config.rb +1 -1
  11. data/lib/rubocop/cop/cop.rb +31 -6
  12. data/lib/rubocop/cop/lint/block_alignment.rb +11 -8
  13. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +21 -14
  14. data/lib/rubocop/cop/lint/useless_setter_call.rb +1 -1
  15. data/lib/rubocop/cop/rails/output.rb +35 -0
  16. data/lib/rubocop/cop/style/{access_control.rb → access_modifier_indentation.rb} +18 -15
  17. data/lib/rubocop/cop/style/alias.rb +14 -2
  18. data/lib/rubocop/cop/style/align_hash.rb +174 -109
  19. data/lib/rubocop/cop/style/autocorrect_alignment.rb +38 -18
  20. data/lib/rubocop/cop/style/blocks.rb +4 -6
  21. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +3 -3
  22. data/lib/rubocop/cop/style/cyclomatic_complexity.rb +46 -0
  23. data/lib/rubocop/cop/style/empty_lines_around_access_modifier.rb +48 -0
  24. data/lib/rubocop/cop/style/empty_lines_around_body.rb +62 -0
  25. data/lib/rubocop/cop/style/end_of_line.rb +6 -2
  26. data/lib/rubocop/cop/style/favor_modifier.rb +11 -1
  27. data/lib/rubocop/cop/style/final_newline.rb +10 -4
  28. data/lib/rubocop/cop/style/hash_syntax.rb +32 -21
  29. data/lib/rubocop/cop/style/leading_comment_space.rb +9 -0
  30. data/lib/rubocop/cop/style/method_call_parentheses.rb +11 -1
  31. data/lib/rubocop/cop/style/numeric_literals.rb +11 -15
  32. data/lib/rubocop/cop/style/redundant_return.rb +7 -4
  33. data/lib/rubocop/cop/style/redundant_self.rb +3 -3
  34. data/lib/rubocop/cop/style/signal_exception.rb +4 -2
  35. data/lib/rubocop/cop/style/space_after_comma_etc.rb +7 -1
  36. data/lib/rubocop/cop/style/space_after_control_keyword.rb +6 -0
  37. data/lib/rubocop/cop/style/space_after_method_name.rb +7 -1
  38. data/lib/rubocop/cop/style/space_after_not.rb +6 -2
  39. data/lib/rubocop/cop/style/space_around_block_braces.rb +149 -0
  40. data/lib/rubocop/cop/style/space_around_equals_in_parameter_default.rb +33 -0
  41. data/lib/rubocop/cop/style/space_around_operators.rb +169 -0
  42. data/lib/rubocop/cop/style/space_before_modifier_keyword.rb +6 -0
  43. data/lib/rubocop/cop/style/space_inside.rb +35 -0
  44. data/lib/rubocop/cop/style/space_inside_brackets.rb +18 -0
  45. data/lib/rubocop/cop/style/space_inside_hash_literal_braces.rb +99 -0
  46. data/lib/rubocop/cop/style/space_inside_parens.rb +18 -0
  47. data/lib/rubocop/cop/style/special_global_vars.rb +52 -25
  48. data/lib/rubocop/cop/style/string_literals.rb +1 -1
  49. data/lib/rubocop/cop/style/surrounding_space.rb +1 -344
  50. data/lib/rubocop/cop/style/trailing_blank_lines.rb +17 -5
  51. data/lib/rubocop/cop/style/trailing_whitespace.rb +9 -5
  52. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -2
  53. data/lib/rubocop/cop/style/word_array.rb +16 -1
  54. data/lib/rubocop/cop/team.rb +5 -5
  55. data/lib/rubocop/cop/util.rb +1 -0
  56. data/lib/rubocop/formatter/offence_count_formatter.rb +0 -1
  57. data/lib/rubocop/options.rb +76 -111
  58. data/lib/rubocop/rake_task.rb +4 -2
  59. data/lib/rubocop/target_finder.rb +3 -3
  60. data/lib/rubocop/version.rb +1 -1
  61. data/spec/rubocop/cli_spec.rb +123 -13
  62. data/spec/rubocop/config_spec.rb +2 -2
  63. data/spec/rubocop/cop/lint/rescue_exception_spec.rb +10 -0
  64. data/spec/rubocop/cop/lint/useless_setter_call_spec.rb +13 -0
  65. data/spec/rubocop/cop/offence_spec.rb +2 -0
  66. data/spec/rubocop/cop/rails/output_spec.rb +40 -0
  67. data/spec/rubocop/cop/style/access_modifier_indentation_spec.rb +243 -0
  68. data/spec/rubocop/cop/style/alias_spec.rb +8 -0
  69. data/spec/rubocop/cop/style/align_array_spec.rb +12 -0
  70. data/spec/rubocop/cop/style/align_hash_spec.rb +15 -0
  71. data/spec/rubocop/cop/style/braces_around_hash_parameters_spec.rb +7 -4
  72. data/spec/rubocop/cop/style/cyclomatic_complexity_spec.rb +203 -0
  73. data/spec/rubocop/cop/style/empty_lines_around_access_modifier_spec.rb +56 -0
  74. data/spec/rubocop/cop/style/empty_lines_around_body_spec.rb +87 -0
  75. data/spec/rubocop/cop/style/end_of_line_spec.rb +17 -8
  76. data/spec/rubocop/cop/style/favor_modifier_spec.rb +34 -0
  77. data/spec/rubocop/cop/style/final_newline_spec.rb +5 -0
  78. data/spec/rubocop/cop/style/hash_syntax_spec.rb +22 -2
  79. data/spec/rubocop/cop/style/leading_comment_space_spec.rb +5 -0
  80. data/spec/rubocop/cop/style/method_call_parentheses_spec.rb +39 -4
  81. data/spec/rubocop/cop/style/numeric_literals_spec.rb +5 -0
  82. data/spec/rubocop/cop/style/signal_exception_spec.rb +11 -0
  83. data/spec/rubocop/cop/style/space_after_colon_spec.rb +7 -0
  84. data/spec/rubocop/cop/style/space_after_comma_spec.rb +5 -0
  85. data/spec/rubocop/cop/style/space_after_control_keyword_spec.rb +29 -8
  86. data/spec/rubocop/cop/style/space_after_method_name_spec.rb +15 -0
  87. data/spec/rubocop/cop/style/space_after_semicolon_spec.rb +5 -0
  88. data/spec/rubocop/cop/style/space_around_block_braces_spec.rb +68 -0
  89. data/spec/rubocop/cop/style/space_around_equals_in_default_parameter_spec.rb +5 -0
  90. data/spec/rubocop/cop/style/space_around_operators_spec.rb +43 -0
  91. data/spec/rubocop/cop/style/space_before_modifier_keyword_spec.rb +23 -0
  92. data/spec/rubocop/cop/style/space_inside_brackets_spec.rb +7 -0
  93. data/spec/rubocop/cop/style/space_inside_hash_literal_braces_spec.rb +65 -23
  94. data/spec/rubocop/cop/style/space_inside_parens_spec.rb +7 -0
  95. data/spec/rubocop/cop/style/special_global_vars_spec.rb +12 -2
  96. data/spec/rubocop/cop/style/string_literals_spec.rb +6 -0
  97. data/spec/rubocop/cop/style/symbol_array_spec.rb +5 -7
  98. data/spec/rubocop/cop/style/trailing_blank_lines_spec.rb +26 -1
  99. data/spec/rubocop/cop/style/trailing_whitespace_spec.rb +7 -0
  100. data/spec/rubocop/cop/style/trivial_accessors_spec.rb +8 -0
  101. data/spec/rubocop/cop/style/word_array_spec.rb +33 -2
  102. data/spec/rubocop/cop/team_spec.rb +4 -4
  103. data/spec/rubocop/formatter/json_formatter_spec.rb +1 -1
  104. data/spec/rubocop/options_spec.rb +5 -96
  105. data/spec/rubocop/processed_source_spec.rb +3 -3
  106. data/spec/spec_helper.rb +28 -23
  107. data/spec/support/mri_syntax_checker.rb +20 -16
  108. metadata +24 -5
  109. data/spec/rubocop/cop/style/access_control_spec.rb +0 -164
@@ -13,26 +13,15 @@ module Rubocop
13
13
  MSG = '(...) interpreted as grouped expression.'
14
14
 
15
15
  def on_send(node)
16
- receiver, method_name, args = *node
16
+ _receiver, method_name, args = *node
17
17
  if OPERATOR_METHODS.include?(method_name) ||
18
18
  method_name.to_s.end_with?('=')
19
19
  return
20
20
  end
21
21
  if args && args.loc.expression.source.start_with?('(')
22
- receiver_length = if receiver
23
- receiver.loc.expression.source.length
24
- else
25
- 0
26
- end
27
- without_receiver = node.loc.expression.source[receiver_length..-1]
28
-
29
- # Escape question mark if any.
30
- method_regexp = Regexp.escape(method_name)
31
-
32
- if (match =
33
- without_receiver.match(/^\s*\.?\s*#{method_regexp}(\s+)\(/))
22
+ space_length = spaces_before_left_parenthesis(node)
23
+ if space_length > 0
34
24
  expr = args.loc.expression
35
- space_length = match.captures[0].length
36
25
  space_range =
37
26
  Parser::Source::Range.new(expr.source_buffer,
38
27
  expr.begin_pos - space_length,
@@ -41,6 +30,24 @@ module Rubocop
41
30
  end
42
31
  end
43
32
  end
33
+
34
+ private
35
+
36
+ def spaces_before_left_parenthesis(node)
37
+ receiver, method_name, _args = *node
38
+ receiver_length = if receiver
39
+ receiver.loc.expression.source.length
40
+ else
41
+ 0
42
+ end
43
+ without_receiver = node.loc.expression.source[receiver_length..-1]
44
+
45
+ # Escape question mark if any.
46
+ method_regexp = Regexp.escape(method_name)
47
+
48
+ match = without_receiver.match(/^\s*\.?\s*#{method_regexp}(\s+)\(/)
49
+ match ? match.captures[0].length : 0
50
+ end
44
51
  end
45
52
  end
46
53
  end
@@ -119,7 +119,7 @@ module Rubocop
119
119
  if mrhs_node.type == :array && rhs_node
120
120
  process_assignment(lhs_variable_name, rhs_node)
121
121
  else
122
- table[lhs_variable_name] = false
122
+ @table[lhs_variable_name] = false
123
123
  end
124
124
  end
125
125
 
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for the use of output calls like puts and print
7
+ class Output < Cop
8
+ MSG = 'Do not write to stdout. Use Rails\' logger if you want to log.'
9
+
10
+ BLACKLIST = [:puts,
11
+ :print,
12
+ :p,
13
+ :pp,
14
+ :pretty_print]
15
+
16
+ def on_send(node)
17
+ return if matches_blacklist?(processed_source)
18
+ receiver, method_name, *_args = *node
19
+
20
+ if receiver.nil? && BLACKLIST.include?(method_name)
21
+ convention(node, :selector)
22
+ end
23
+ end
24
+
25
+ def matches_blacklist?(source)
26
+ ignore_paths.any? { |regex| source.buffer.name =~ /#{regex}/ }
27
+ end
28
+
29
+ def ignore_paths
30
+ cop_config['Ignore']
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -6,9 +6,8 @@ module Rubocop
6
6
  # A couple of checks related to the use method visibility modifiers.
7
7
  # Modifiers should be indented as deeps are method definitions and
8
8
  # surrounded by blank lines.
9
- class AccessControl < Cop
10
- INDENT_MSG = 'Indent %s as deep as method definitions.'
11
- BLANK_MSG = 'Keep a blank line before and after %s.'
9
+ class AccessModifierIndentation < Cop
10
+ MSG = '%s access modifiers like %s.'
12
11
 
13
12
  PRIVATE_NODE = s(:send, nil, :private)
14
13
  PROTECTED_NODE = s(:send, nil, :protected)
@@ -30,19 +29,9 @@ module Rubocop
30
29
  on_node(:send, node, [:class, :module, :sclass]) do |send_node|
31
30
  if modifier_node?(send_node)
32
31
  send_start_col = send_node.loc.expression.column
33
- selector = send_node.loc.selector.source
34
32
 
35
- if send_start_col - 2 != class_start_col
36
- convention(send_node, :expression,
37
- format(INDENT_MSG, selector))
38
- end
39
-
40
- send_line = send_node.loc.line
41
-
42
- unless processed_source[send_line].chomp.empty? &&
43
- processed_source[send_line - 2].chomp.empty?
44
- convention(send_node, :expression,
45
- format(BLANK_MSG, selector))
33
+ if send_start_col != class_start_col + expected_indent_offset
34
+ convention(send_node, :expression)
46
35
  end
47
36
  end
48
37
  end
@@ -52,6 +41,12 @@ module Rubocop
52
41
 
53
42
  private
54
43
 
44
+ def message(node)
45
+ format(MSG,
46
+ cop_config['EnforcedStyle'].capitalize,
47
+ node.loc.selector.source)
48
+ end
49
+
55
50
  def class_constructor?(block_node)
56
51
  send_node = block_node.children.first
57
52
  receiver_node, method_name, *_ = *send_node
@@ -59,6 +54,14 @@ module Rubocop
59
54
  %w(Class Module).include?(Util.const_name(receiver_node))
60
55
  end
61
56
 
57
+ def expected_indent_offset
58
+ case cop_config['EnforcedStyle'].downcase
59
+ when 'outdent' then 0
60
+ when 'indent' then 2
61
+ else fail 'Unknown EnforcedStyle specified'
62
+ end
63
+ end
64
+
62
65
  def modifier_node?(node)
63
66
  [PRIVATE_NODE, PROTECTED_NODE, PUBLIC_NODE].include?(node)
64
67
  end
@@ -8,9 +8,21 @@ module Rubocop
8
8
  class Alias < Cop
9
9
  MSG = 'Use alias_method instead of alias.'
10
10
 
11
- # TODO: Make this check context aware - alias_method is not
12
- # available outside of classes/modules.
11
+ def on_block(node)
12
+ method, _args, body = *node
13
+
14
+ _receiver, method_name = *method
15
+
16
+ # using alias is the only option in certain scenarios
17
+ # in such scenarios we don't want to report an offence
18
+ if method_name == :instance_exec
19
+ on_node(:alias, body) { |n| ignore_node(n) }
20
+ end
21
+ end
22
+
13
23
  def on_alias(node)
24
+ return if ignored_node?(node)
25
+
14
26
  # alias_method can't be used with global variables
15
27
  new, old = *node
16
28
 
@@ -6,154 +6,219 @@ module Rubocop
6
6
  # Here we check if the keys, separators, and values of a multi-line hash
7
7
  # literal are aligned.
8
8
  class AlignHash < Cop
9
- MSG = 'Align the elements of a hash literal if they span more than ' +
10
- 'one line.'
9
+ # Handles calculation of deltas (deviations from correct alignment)
10
+ # when the enforced style is 'key'.
11
+ class KeyAlignment
12
+ def checkable_layout(_node)
13
+ true
14
+ end
11
15
 
12
- def on_hash(node)
13
- first_pair = node.children.first
16
+ def deltas_for_first_pair(*_)
17
+ {} # The first pair is always considered correct.
18
+ end
19
+
20
+ def deltas(first_pair, prev_pair, current_pair)
21
+ if current_pair.loc.line == prev_pair.loc.line
22
+ {}
23
+ else
24
+ { key: first_pair.loc.column - current_pair.loc.column }
25
+ end
26
+ end
27
+ end
14
28
 
15
- styles = [cop_config['EnforcedHashRocketStyle'],
16
- cop_config['EnforcedColonStyle']]
29
+ # Common functionality for the styles where not only keys, but also
30
+ # values are aligned.
31
+ class AlignmentOfValues
32
+ def checkable_layout(node)
33
+ !any_pairs_on_the_same_line?(node) && all_have_same_sparator?(node)
34
+ end
35
+
36
+ def deltas(first_pair, prev_pair, current_pair)
37
+ key_delta = key_delta(first_pair, current_pair)
38
+ current_separator = current_pair.loc.operator
39
+ separator_delta = separator_delta(first_pair, current_separator,
40
+ key_delta)
41
+ value_delta = value_delta(first_pair, current_pair) -
42
+ key_delta - separator_delta
43
+
44
+ { key: key_delta, separator: separator_delta, value: value_delta }
45
+ end
46
+
47
+ private
17
48
 
18
- if styles.include?('table') || styles.include?('separator')
19
- return if any_pairs_on_the_same_line?(node)
49
+ def any_pairs_on_the_same_line?(node)
50
+ lines_of_the_children = node.children.map do |pair|
51
+ key, _value = *pair
52
+ key.loc.line
53
+ end
54
+ lines_of_the_children.uniq.size < lines_of_the_children.size
55
+ end
56
+
57
+ def all_have_same_sparator?(node)
58
+ first_separator = node.children.first.loc.operator.source
59
+ node.children[1..-1].all? do |pair|
60
+ pair.loc.operator.is?(first_separator)
61
+ end
20
62
  end
63
+ end
21
64
 
22
- if styles.include?('table')
65
+ # Handles calculation of deltas when the enforced style is 'table'.
66
+ class TableAlignment < AlignmentOfValues
67
+ # The table style is the only one where the first key-value pair can
68
+ # be considered to have bad alignment.
69
+ def deltas_for_first_pair(first_pair, node)
23
70
  key_widths = node.children.map do |pair|
24
71
  key, _value = *pair
25
72
  key.loc.expression.source.length
26
73
  end
27
74
  @max_key_width = key_widths.max
28
- if first_pair && value_delta(nil, first_pair, @max_key_width) != 0
29
- @column_deltas = {}
30
- convention(first_pair, :expression)
75
+
76
+ separator_delta = separator_delta(first_pair,
77
+ first_pair.loc.operator, 0)
78
+ {
79
+ separator: separator_delta,
80
+ value: value_delta(first_pair, first_pair) - separator_delta
81
+ }
82
+ end
83
+
84
+ private
85
+
86
+ def key_delta(first_pair, current_pair)
87
+ first_pair.loc.column - current_pair.loc.column
88
+ end
89
+
90
+ def separator_delta(first_pair, current_separator, key_delta)
91
+ if current_separator.is?(':')
92
+ 0 # Colon follows directly after key
93
+ else
94
+ first_pair.loc.column + @max_key_width + 1 -
95
+ current_separator.column - key_delta
31
96
  end
32
97
  end
33
98
 
34
- node.children.each_cons(2) do |prev, current|
35
- @column_deltas = deltas(first_pair, prev, current, @max_key_width)
36
- convention(current, :expression) unless good_alignment?
99
+ def value_delta(first_pair, current_pair)
100
+ first_key, _ = *first_pair
101
+ _, current_value = *current_pair
102
+ correct_value_column = first_key.loc.column +
103
+ spaced_separator(current_pair).length + @max_key_width
104
+ correct_value_column - current_value.loc.column
37
105
  end
38
- end
39
106
 
40
- def any_pairs_on_the_same_line?(node)
41
- lines_of_the_children = node.children.map do |pair|
42
- key, _value = *pair
43
- key.loc.line
107
+ def spaced_separator(node)
108
+ node.loc.operator.is?('=>') ? ' => ' : ': '
44
109
  end
45
- lines_of_the_children.uniq.size < lines_of_the_children.size
46
110
  end
47
111
 
48
- def autocorrect(node)
49
- # We can't use the instance variable inside the lambda. That would
50
- # just give each lambda the same reference and they would all get the
51
- # last value of each. Some local variables fix the problem.
52
- max_key_width = @max_key_width
53
- key_delta = @column_deltas[:key] || 0
112
+ # Handles calculation of deltas when the enforced style is 'separator'.
113
+ class SeparatorAlignment < AlignmentOfValues
114
+ def deltas_for_first_pair(first_pair, node)
115
+ {} # The first pair is always considered correct.
116
+ end
54
117
 
55
- key, value = *node
118
+ private
56
119
 
57
- @corrections << lambda do |corrector|
58
- expr = node.loc.expression
59
- b = expr.begin_pos
60
- b -= key_delta.abs if key_delta < 0
61
- range = Parser::Source::Range.new(expr.source_buffer, b,
62
- expr.end_pos)
63
- source = ' ' * [key_delta, 0].max +
64
- if enforced_style(node) == 'key'
65
- expr.source
66
- else
67
- key_source = key.loc.expression.source
68
- padded_separator = case enforced_style(node)
69
- when 'separator'
70
- spaced_separator(node)
71
- when 'table'
72
- space = ' ' * (max_key_width -
73
- key_source.length)
74
- if node.loc.operator.is?('=>')
75
- space + spaced_separator(node)
76
- else
77
- spaced_separator(node) + space
78
- end
79
- end
80
- key_source + padded_separator + value.loc.expression.source
81
- end
82
- corrector.replace(range, source)
120
+ def key_delta(first_pair, current_pair)
121
+ key_end_column(first_pair) - key_end_column(current_pair)
83
122
  end
84
- end
85
123
 
86
- private
124
+ def key_end_column(pair)
125
+ key, _value = *pair
126
+ key.loc.column + key.loc.expression.source.length
127
+ end
87
128
 
88
- def good_alignment?
89
- @column_deltas.values.compact.none? { |v| v != 0 }
129
+ def separator_delta(first_pair, current_separator, key_delta)
130
+ if current_separator.is?(':')
131
+ 0 # Colon follows directly after key
132
+ else
133
+ first_pair.loc.operator.column - current_separator.column -
134
+ key_delta
135
+ end
136
+ end
137
+
138
+ def value_delta(first_pair, current_pair)
139
+ _, first_value = *first_pair
140
+ _, current_value = *current_pair
141
+ first_value.loc.column - current_value.loc.column
142
+ end
90
143
  end
91
144
 
92
- def deltas(first_pair, prev_pair, current_pair, max_key_width)
93
- enforced_style = enforced_style(current_pair)
94
- unless %w(key separator table).include?(enforced_style)
95
- fail "Unknown #{config_parameter(current_pair)}: #{enforced_style}"
145
+ MSG = 'Align the elements of a hash literal if they span more than ' +
146
+ 'one line.'
147
+
148
+ def on_hash(node)
149
+ return if node.children.empty?
150
+
151
+ @alignment_for_hash_rockets ||=
152
+ new_alignment('EnforcedHashRocketStyle')
153
+ @alignment_for_colons ||= new_alignment('EnforcedColonStyle')
154
+
155
+ first_pair = node.children.first
156
+
157
+ unless @alignment_for_hash_rockets.checkable_layout(node) &&
158
+ @alignment_for_colons.checkable_layout(node)
159
+ return
96
160
  end
97
161
 
98
- return {} if current_pair.loc.line == prev_pair.loc.line
162
+ @column_deltas = alignment_for(first_pair)
163
+ .deltas_for_first_pair(first_pair, node)
164
+ convention(first_pair, :expression) unless good_alignment?
99
165
 
100
- key_left_alignment_delta = (first_pair.loc.column -
101
- current_pair.loc.column)
102
- if enforced_style == 'key'
103
- { key: key_left_alignment_delta }
104
- else
105
- {
106
- key: if enforced_style == 'table'
107
- key_left_alignment_delta
108
- else
109
- key_end_column(first_pair) - key_end_column(current_pair)
110
- end,
111
- separator: if current_pair.loc.operator.is?(':') &&
112
- enforced_style == 'table'
113
- # Colon follows directly after key
114
- (key_end_column(current_pair) -
115
- current_pair.loc.operator.column)
116
- else
117
- # Aligned separator
118
- (first_pair.loc.operator.column -
119
- current_pair.loc.operator.column)
120
- end,
121
- value: value_delta(first_pair, current_pair, max_key_width)
122
- }
166
+ node.children.each_cons(2) do |prev, current|
167
+ @column_deltas = alignment_for(current).deltas(first_pair, prev,
168
+ current)
169
+ convention(current, :expression) unless good_alignment?
123
170
  end
124
171
  end
125
172
 
126
- def key_end_column(pair)
127
- key, _value = *pair
128
- key.loc.column + key.loc.expression.source.length
173
+ private
174
+
175
+ def alignment_for(pair)
176
+ if pair.loc.operator.is?('=>')
177
+ @alignment_for_hash_rockets
178
+ else
179
+ @alignment_for_colons
180
+ end
129
181
  end
130
182
 
131
- def value_delta(first_pair, current_pair, max_key_width)
132
- key, value = *current_pair
133
- correct_value_column = if enforced_style(current_pair) == 'table'
134
- key.loc.column +
135
- spaced_separator(current_pair).length +
136
- max_key_width
137
- elsif first_pair.nil? # Only one pair?
138
- value.loc.column
139
- else
140
- _key1, value1 = *first_pair
141
- value1.loc.column
142
- end
143
- correct_value_column - value.loc.column
183
+ def autocorrect(node)
184
+ # We can't use the instance variable inside the lambda. That would
185
+ # just give each lambda the same reference and they would all get the
186
+ # last value of each. Some local variables fix the problem.
187
+ key_delta = @column_deltas[:key] || 0
188
+ separator_delta = @column_deltas[:separator] || 0
189
+ value_delta = @column_deltas[:value] || 0
190
+
191
+ key, value = *node
192
+
193
+ @corrections << lambda do |corrector|
194
+ adjust(corrector, key_delta, key.loc.expression)
195
+ adjust(corrector, separator_delta, node.loc.operator)
196
+ adjust(corrector, value_delta, value.loc.expression)
197
+ end
144
198
  end
145
199
 
146
- def spaced_separator(node)
147
- node.loc.operator.is?('=>') ? ' => ' : ': '
200
+ def new_alignment(key)
201
+ case cop_config[key]
202
+ when 'key' then KeyAlignment.new
203
+ when 'table' then TableAlignment.new
204
+ when 'separator' then SeparatorAlignment.new
205
+ else fail "Unknown #{key}: #{cop_config[key]}"
206
+ end
148
207
  end
149
208
 
150
- def enforced_style(node)
151
- cop_config[config_parameter(node)]
209
+ def adjust(corrector, delta, range)
210
+ if delta > 0
211
+ corrector.insert_before(range, ' ' * delta)
212
+ elsif delta < 0
213
+ range = Parser::Source::Range.new(range.source_buffer,
214
+ range.begin_pos - delta.abs,
215
+ range.begin_pos)
216
+ corrector.remove(range)
217
+ end
152
218
  end
153
219
 
154
- def config_parameter(node)
155
- separator = node.loc.operator.is?('=>') ? 'HashRocket' : 'Colon'
156
- "Enforced#{separator}Style"
220
+ def good_alignment?
221
+ @column_deltas.values.compact.none? { |v| v != 0 }
157
222
  end
158
223
  end
159
224
  end