rubocop 1.6.0 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +3 -2
  4. data/config/default.yml +142 -19
  5. data/lib/rubocop.rb +15 -1
  6. data/lib/rubocop/cli/command/auto_genenerate_config.rb +5 -4
  7. data/lib/rubocop/comment_config.rb +6 -6
  8. data/lib/rubocop/config.rb +10 -7
  9. data/lib/rubocop/config_loader.rb +11 -14
  10. data/lib/rubocop/config_loader_resolver.rb +21 -4
  11. data/lib/rubocop/config_obsoletion.rb +5 -3
  12. data/lib/rubocop/config_obsoletion/extracted_cop.rb +6 -6
  13. data/lib/rubocop/config_store.rb +12 -1
  14. data/lib/rubocop/cop/base.rb +1 -1
  15. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +3 -2
  16. data/lib/rubocop/cop/generator.rb +1 -3
  17. data/lib/rubocop/cop/internal_affairs.rb +6 -1
  18. data/lib/rubocop/cop/internal_affairs/empty_line_between_expect_offense_and_correction.rb +68 -0
  19. data/lib/rubocop/cop/internal_affairs/example_description.rb +89 -0
  20. data/lib/rubocop/cop/internal_affairs/redundant_described_class_as_subject.rb +61 -0
  21. data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +64 -0
  22. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +145 -0
  23. data/lib/rubocop/cop/layout/class_structure.rb +7 -2
  24. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +19 -3
  25. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +14 -0
  26. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -2
  27. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +3 -10
  28. data/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb +1 -0
  29. data/lib/rubocop/cop/layout/space_before_block_braces.rb +2 -0
  30. data/lib/rubocop/cop/layout/space_before_brackets.rb +62 -0
  31. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +13 -10
  32. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +2 -2
  33. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +59 -0
  34. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +7 -2
  35. data/lib/rubocop/cop/lint/deprecated_constants.rb +75 -0
  36. data/lib/rubocop/cop/lint/duplicate_branch.rb +64 -2
  37. data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +44 -0
  38. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +10 -6
  39. data/lib/rubocop/cop/lint/number_conversion.rb +41 -6
  40. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +47 -0
  41. data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +39 -0
  42. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +2 -1
  43. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +50 -0
  44. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +50 -17
  45. data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -11
  46. data/lib/rubocop/cop/lint/symbol_conversion.rb +102 -0
  47. data/lib/rubocop/cop/lint/triple_quotes.rb +71 -0
  48. data/lib/rubocop/cop/lint/unreachable_loop.rb +17 -0
  49. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  50. data/lib/rubocop/cop/mixin/allowed_identifiers.rb +18 -0
  51. data/lib/rubocop/cop/mixin/check_line_breakable.rb +5 -0
  52. data/lib/rubocop/cop/mixin/comments_help.rb +1 -11
  53. data/lib/rubocop/cop/mixin/first_element_line_break.rb +1 -1
  54. data/lib/rubocop/cop/mixin/uncommunicative_name.rb +5 -1
  55. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +59 -5
  56. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +38 -5
  57. data/lib/rubocop/cop/naming/variable_name.rb +2 -0
  58. data/lib/rubocop/cop/naming/variable_number.rb +2 -9
  59. data/lib/rubocop/cop/registry.rb +10 -0
  60. data/lib/rubocop/cop/severity.rb +3 -3
  61. data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -1
  62. data/lib/rubocop/cop/style/ascii_comments.rb +1 -1
  63. data/lib/rubocop/cop/style/collection_methods.rb +14 -1
  64. data/lib/rubocop/cop/style/commented_keyword.rb +22 -5
  65. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +49 -9
  66. data/lib/rubocop/cop/style/empty_literal.rb +6 -2
  67. data/lib/rubocop/cop/style/endless_method.rb +102 -0
  68. data/lib/rubocop/cop/style/eval_with_location.rb +63 -34
  69. data/lib/rubocop/cop/style/explicit_block_argument.rb +10 -0
  70. data/lib/rubocop/cop/style/float_division.rb +3 -0
  71. data/lib/rubocop/cop/style/for.rb +2 -0
  72. data/lib/rubocop/cop/style/format_string_token.rb +18 -2
  73. data/lib/rubocop/cop/style/hash_except.rb +95 -0
  74. data/lib/rubocop/cop/style/hash_like_case.rb +2 -1
  75. data/lib/rubocop/cop/style/if_inside_else.rb +22 -10
  76. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +96 -0
  77. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -2
  78. data/lib/rubocop/cop/style/lambda_call.rb +2 -1
  79. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +7 -0
  80. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +16 -6
  81. data/lib/rubocop/cop/style/method_def_parentheses.rb +7 -0
  82. data/lib/rubocop/cop/style/multiline_method_signature.rb +26 -1
  83. data/lib/rubocop/cop/style/multiline_when_then.rb +3 -1
  84. data/lib/rubocop/cop/style/mutable_constant.rb +13 -3
  85. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +4 -0
  86. data/lib/rubocop/cop/style/nil_comparison.rb +1 -0
  87. data/lib/rubocop/cop/style/non_nil_check.rb +23 -13
  88. data/lib/rubocop/cop/style/raise_args.rb +5 -2
  89. data/lib/rubocop/cop/style/redundant_argument.rb +7 -1
  90. data/lib/rubocop/cop/style/redundant_freeze.rb +8 -4
  91. data/lib/rubocop/cop/style/redundant_return.rb +1 -1
  92. data/lib/rubocop/cop/style/single_line_methods.rb +34 -2
  93. data/lib/rubocop/cop/style/sole_nested_conditional.rb +29 -5
  94. data/lib/rubocop/cop/style/string_concatenation.rb +1 -1
  95. data/lib/rubocop/cop/style/symbol_proc.rb +5 -4
  96. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  97. data/lib/rubocop/cop/style/while_until_modifier.rb +2 -4
  98. data/lib/rubocop/cop/util.rb +3 -1
  99. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -0
  100. data/lib/rubocop/magic_comment.rb +30 -1
  101. data/lib/rubocop/options.rb +10 -10
  102. data/lib/rubocop/rspec/cop_helper.rb +0 -4
  103. data/lib/rubocop/rspec/expect_offense.rb +37 -22
  104. data/lib/rubocop/runner.rb +17 -1
  105. data/lib/rubocop/target_finder.rb +4 -2
  106. data/lib/rubocop/target_ruby.rb +47 -11
  107. data/lib/rubocop/util.rb +16 -0
  108. data/lib/rubocop/version.rb +8 -2
  109. metadata +26 -7
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # This cop checks for redundant `subject(:cop) { described_class.new }`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # RSpec.describe RuboCop::Cop::Department::Foo do
11
+ # subject(:cop) { described_class.new(config) }
12
+ # end
13
+ #
14
+ # # good
15
+ # RSpec.describe RuboCop::Cop::Department::Foo, :config do
16
+ # end
17
+ #
18
+ class RedundantDescribedClassAsSubject < Base
19
+ include RangeHelp
20
+ extend AutoCorrector
21
+
22
+ MSG = 'Remove the redundant `subject`%<additional_message>s.'
23
+
24
+ def_node_matcher :described_class_subject?, <<~PATTERN
25
+ (block
26
+ (send nil? :subject
27
+ (sym :cop))
28
+ (args)
29
+ (send
30
+ (send nil? :described_class) :new
31
+ $...))
32
+ PATTERN
33
+
34
+ def on_block(node)
35
+ return unless (described_class_arguments = described_class_subject?(node))
36
+ return if described_class_arguments.count >= 2
37
+
38
+ describe = find_describe_method_node(node)
39
+
40
+ unless (exist_config = describe.last_argument.source == ':config')
41
+ additional_message = ' and specify `:config` in `describe`'
42
+ end
43
+
44
+ message = format(MSG, additional_message: additional_message)
45
+
46
+ add_offense(node, message: message) do |corrector|
47
+ corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
48
+
49
+ corrector.insert_after(describe.last_argument, ', :config') unless exist_config
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def find_describe_method_node(block_node)
56
+ block_node.ancestors.find { |node| node.block_type? && node.method?(:describe) }.send_node
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # This cop checks that `let` is `RuboCop::Config.new` with no arguments.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # RSpec.describe RuboCop::Cop::Department::Foo, :config do
11
+ # let(:config) { RuboCop::Config.new }
12
+ # end
13
+ #
14
+ # # good
15
+ # RSpec.describe RuboCop::Cop::Department::Foo, :config do
16
+ # end
17
+ #
18
+ # RSpec.describe RuboCop::Cop::Department::Foo, :config do
19
+ # let(:config) { RuboCop::Config.new(argument) }
20
+ # end
21
+ #
22
+ class RedundantLetRuboCopConfigNew < Base
23
+ include RangeHelp
24
+ extend AutoCorrector
25
+
26
+ MSG = 'Remove `let` that is `RuboCop::Config.new` with no arguments%<additional_message>s.'
27
+
28
+ def_node_matcher :let_rubocop_config_new?, <<~PATTERN
29
+ (block
30
+ (send nil? :let
31
+ (sym :config))
32
+ (args)
33
+ (send
34
+ (const
35
+ (const nil? :RuboCop) :Config) :new))
36
+ PATTERN
37
+
38
+ def on_block(node)
39
+ return unless let_rubocop_config_new?(node)
40
+
41
+ describe = find_describe_method_node(node)
42
+
43
+ unless (exist_config = describe.last_argument.source == ':config')
44
+ additional_message = ' and specify `:config` in `describe`'
45
+ end
46
+
47
+ message = format(MSG, additional_message: additional_message)
48
+
49
+ add_offense(node, message: message) do |corrector|
50
+ corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
51
+
52
+ corrector.insert_after(describe.last_argument, ', :config') unless exist_config
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def find_describe_method_node(block_node)
59
+ block_node.ancestors.find { |node| node.block_type? && node.method?(:describe) }.send_node
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # Checks for correct use of the style_detected API provided by
7
+ # `ConfigurableEnforcedStyle`. If `correct_style_detected` is used
8
+ # then `opposite_style_detected`, `unexpected_style_detected`,
9
+ # `ambiguous_style_detected`, `conflicting_styles_detected`,
10
+ # `unrecognized_style_detected` or `no_acceptable_style!` should be
11
+ # used too, and vice versa. The `xxx_style_detected` methods
12
+ # should not be used as predicates either.
13
+ #
14
+ # @example
15
+ #
16
+ # # bad
17
+ # def on_send(node)
18
+ # return add_offense(node) if opposite_style_detected
19
+ #
20
+ # correct_style_detected
21
+ # end
22
+ #
23
+ # def on_send(node)
24
+ # if offense?
25
+ # add_offense(node)
26
+ # else
27
+ # correct_style_detected
28
+ # end
29
+ # end
30
+ #
31
+ # def on_send(node)
32
+ # return unless offense?
33
+ #
34
+ # add_offense(node)
35
+ # opposite_style_detected
36
+ # end
37
+ #
38
+ # # good
39
+ # def on_send(node)
40
+ # if offense?
41
+ # add_offense(node)
42
+ # opposite_style_detected
43
+ # else
44
+ # correct_style_detected
45
+ # end
46
+ # end
47
+ #
48
+ # def on_send(node)
49
+ # add_offense(node) if offense?
50
+ # end
51
+ #
52
+ class StyleDetectedApiUse < Base
53
+ include RangeHelp
54
+
55
+ MSG_FOR_POSITIVE_WITHOUT_NEGATIVE =
56
+ '`correct_style_detected` method called without ' \
57
+ 'calling a negative `*_style_detected` method.'
58
+ MSG_FOR_NEGATIVE_WITHOUT_POSITIVE =
59
+ 'negative `*_style_detected` methods called without ' \
60
+ 'calling `correct_style_detected` method.'
61
+ MSG_FOR_CONDITIONAL_USE =
62
+ '`*_style_detected` method called in conditional.'
63
+ RESTRICT_ON_SEND = %i[
64
+ correct_style_detected opposite_style_detected
65
+ unexpected_style_detected ambiguous_style_detected
66
+ conflicting_styles_detected unrecognized_style_detected
67
+ no_acceptable_style! style_detected
68
+ ].freeze
69
+
70
+ def_node_matcher :correct_style_detected_check, <<~PATTERN
71
+ (send nil? :correct_style_detected)
72
+ PATTERN
73
+
74
+ def_node_matcher :negative_style_detected_method_check, <<~PATTERN
75
+ (send nil? /(?:opposite|unexpected|ambiguous|unrecognized)_style_detected|conflicting_styles_detected/ ...)
76
+ PATTERN
77
+
78
+ def_node_matcher :no_acceptable_style_check, <<~PATTERN
79
+ (send nil? :no_acceptable_style!)
80
+ PATTERN
81
+
82
+ def_node_matcher :style_detected_check, <<~PATTERN
83
+ (send nil? :style_detected ...)
84
+ PATTERN
85
+
86
+ def on_new_investigation
87
+ @correct_style_detected_called = false
88
+ @negative_style_detected_methods_called = false
89
+ @style_detected_called = false
90
+ end
91
+
92
+ def on_investigation_end
93
+ return if style_detected_called
94
+ return unless correct_style_detected_called ^ negative_style_detected_methods_called
95
+
96
+ add_global_offense(MSG_FOR_POSITIVE_WITHOUT_NEGATIVE) if positive_without_negative?
97
+ add_global_offense(MSG_FOR_NEGATIVE_WITHOUT_POSITIVE) if negative_without_positive?
98
+ end
99
+
100
+ def on_send(node)
101
+ if correct_style_detected_check(node)
102
+ @correct_style_detected_called = true
103
+ elsif negative_style_detected_method_check(node) || no_acceptable_style_check(node)
104
+ @negative_style_detected_methods_called = true
105
+ elsif style_detected_check(node)
106
+ @style_detected_called = true
107
+ end
108
+ end
109
+
110
+ def on_if(node)
111
+ traverse_condition(node.condition) do |cond|
112
+ add_offense(cond, message: MSG_FOR_CONDITIONAL_USE) if style_detected_api_used?(cond)
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ attr_reader :correct_style_detected_called,
119
+ :negative_style_detected_methods_called,
120
+ :style_detected_called
121
+
122
+ def positive_without_negative?
123
+ correct_style_detected_called && !negative_style_detected_methods_called
124
+ end
125
+
126
+ def negative_without_positive?
127
+ negative_style_detected_methods_called && !correct_style_detected_called
128
+ end
129
+
130
+ def style_detected_api_used?(node)
131
+ correct_style_detected_check(node) ||
132
+ negative_style_detected_method_check(node) ||
133
+ no_acceptable_style_check(node) ||
134
+ style_detected_check(node)
135
+ end
136
+
137
+ def traverse_condition(condition, &block)
138
+ yield condition if condition.send_type?
139
+
140
+ condition.each_child_node { |child| traverse_condition(child, &block) }
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -213,7 +213,12 @@ module RuboCop
213
213
  name = node.method_name.to_s
214
214
  category, = categories.find { |_, names| names.include?(name) }
215
215
  key = category || name
216
- visibility_key = "#{node_visibility(node)}_#{key}"
216
+ visibility_key =
217
+ if node.def_modifier?
218
+ "#{name}_methods"
219
+ else
220
+ "#{node_visibility(node)}_#{key}"
221
+ end
217
222
  expected_order.include?(visibility_key) ? visibility_key : key
218
223
  end
219
224
 
@@ -264,7 +269,7 @@ module RuboCop
264
269
 
265
270
  def source_range_with_comment(node)
266
271
  begin_pos, end_pos =
267
- if node.def_type?
272
+ if node.def_type? && !node.method?(:initialize) || node.send_type? && node.def_modifier?
268
273
  start_node = find_visibility_start(node) || node
269
274
  end_node = find_visibility_end(node) || node
270
275
  [begin_pos_with_comment(start_node),
@@ -76,6 +76,14 @@ module RuboCop
76
76
  #
77
77
  # def b
78
78
  # end
79
+ #
80
+ # @example AllowAdjacentOneLineDefs: true
81
+ #
82
+ # # good
83
+ # class ErrorA < BaseError; end
84
+ # class ErrorB < BaseError; end
85
+ # class ErrorC < BaseError; end
86
+ #
79
87
  class EmptyLineBetweenDefs < Base
80
88
  include RangeHelp
81
89
  extend AutoCorrector
@@ -113,8 +121,8 @@ module RuboCop
113
121
 
114
122
  def autocorrect(corrector, prev_def, node)
115
123
  # finds position of first newline
116
- end_pos = prev_def.loc.end.end_pos
117
- source_buffer = prev_def.loc.end.source_buffer
124
+ end_pos = end_loc(prev_def).end_pos
125
+ source_buffer = end_loc(prev_def).source_buffer
118
126
  newline_pos = source_buffer.source.index("\n", end_pos)
119
127
 
120
128
  # Handle the case when multiple one-liners are on the same line.
@@ -198,7 +206,15 @@ module RuboCop
198
206
  end
199
207
 
200
208
  def def_end(node)
201
- node.loc.end.line
209
+ end_loc(node).line
210
+ end
211
+
212
+ def end_loc(node)
213
+ if (node.def_type? || node.defs_type?) && node.endless?
214
+ node.loc.expression.end
215
+ else
216
+ node.loc.end
217
+ end
202
218
  end
203
219
 
204
220
  def autocorrect_remove_lines(corrector, newline_pos, count)
@@ -70,6 +70,7 @@ module RuboCop
70
70
  return unless outermost_send.loc.end
71
71
  return unless heredoc_arg.first_line != outermost_send.loc.end.line
72
72
  return if subsequent_closing_parentheses_in_same_line?(outermost_send)
73
+ return if exist_argument_between_heredoc_end_and_closing_parentheses?(node)
73
74
 
74
75
  add_offense(outermost_send.loc.end) do |corrector|
75
76
  autocorrect(corrector, outermost_send)
@@ -215,6 +216,19 @@ module RuboCop
215
216
  end
216
217
  end
217
218
 
219
+ def exist_argument_between_heredoc_end_and_closing_parentheses?(node)
220
+ return false unless (heredoc_end = find_most_bottom_of_heredoc_end(node.arguments))
221
+
222
+ heredoc_end < node.loc.end.begin_pos &&
223
+ range_between(heredoc_end, node.loc.end.begin_pos).source.strip != ''
224
+ end
225
+
226
+ def find_most_bottom_of_heredoc_end(arguments)
227
+ arguments.map do |argument|
228
+ argument.loc.heredoc_end.end_pos if argument.loc.respond_to?(:heredoc_end)
229
+ end.compact.max
230
+ end
231
+
218
232
  # Internal trailing comma helpers.
219
233
 
220
234
  def remove_internal_trailing_comma(node, corrector)
@@ -16,14 +16,14 @@ module RuboCop
16
16
  # if a +
17
17
  # b
18
18
  # something &&
19
- # something_else
19
+ # something_else
20
20
  # end
21
21
  #
22
22
  # # good
23
23
  # if a +
24
24
  # b
25
25
  # something &&
26
- # something_else
26
+ # something_else
27
27
  # end
28
28
  #
29
29
  # @example EnforcedStyle: indented
@@ -191,16 +191,9 @@ module RuboCop
191
191
  end
192
192
 
193
193
  def begin_end_alignment_style
194
- # FIXME: Workaround for pending status for `Layout/BeginEndAlignment` cop
195
- # When RuboCop 1.0 is released, please replace it with the following condition.
196
- #
197
- # config.for_cop('Layout/BeginEndAlignment')['Enabled'] &&
198
- # config.for_cop('Layout/BeginEndAlignment')['EnforcedStyleAlignWith']
199
- if config.for_all_cops['NewCops'] == 'enable' ||
200
- config.for_cop('Layout/BeginEndAlignment')['Enabled'] &&
201
- config.for_cop('Layout/BeginEndAlignment')['Enabled'] != 'pending'
202
- config.for_cop('Layout/BeginEndAlignment')['EnforcedStyleAlignWith']
203
- end
194
+ begin_end_alignment_conf = config.for_cop('Layout/BeginEndAlignment')
195
+
196
+ begin_end_alignment_conf['Enabled'] && begin_end_alignment_conf['EnforcedStyleAlignWith']
204
197
  end
205
198
  end
206
199
  end
@@ -60,6 +60,7 @@ module RuboCop
60
60
 
61
61
  add_offense(range) do |corrector|
62
62
  autocorrect(corrector, range)
63
+ opposite_style_detected
63
64
  end
64
65
  end
65
66
 
@@ -106,6 +106,7 @@ module RuboCop
106
106
  def space_missing(left_brace)
107
107
  add_offense(left_brace, message: MISSING_MSG) do |corrector|
108
108
  autocorrect(corrector, left_brace)
109
+ opposite_style_detected
109
110
  end
110
111
  end
111
112
 
@@ -114,6 +115,7 @@ module RuboCop
114
115
 
115
116
  add_offense(space, message: DETECTED_MSG) do |corrector|
116
117
  autocorrect(corrector, space)
118
+ opposite_style_detected
117
119
  end
118
120
  end
119
121
 
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Layout
6
+ # Checks for space between the name of a receiver and a left
7
+ # brackets.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # collection [index_or_key]
13
+ #
14
+ # # good
15
+ # collection[index_or_key]
16
+ #
17
+ class SpaceBeforeBrackets < Base
18
+ include RangeHelp
19
+ extend AutoCorrector
20
+
21
+ MSG = 'Remove the space before the opening brackets.'
22
+
23
+ def on_send(node)
24
+ return unless (first_argument = node.first_argument)
25
+
26
+ begin_pos = first_argument.source_range.begin_pos
27
+ return unless (range = offense_range(node, begin_pos))
28
+
29
+ register_offense(range)
30
+ end
31
+
32
+ private
33
+
34
+ def offense_range(node, begin_pos)
35
+ if reference_variable_with_brackets?(node)
36
+ receiver_end_pos = node.receiver.source_range.end_pos
37
+ selector_begin_pos = node.loc.selector.begin_pos
38
+ return if receiver_end_pos >= selector_begin_pos
39
+
40
+ range_between(receiver_end_pos, selector_begin_pos)
41
+ elsif node.method?(:[]=)
42
+ end_pos = node.receiver.source_range.end_pos
43
+
44
+ return if begin_pos - end_pos == 1
45
+
46
+ range_between(end_pos, begin_pos - 1)
47
+ end
48
+ end
49
+
50
+ def register_offense(range)
51
+ add_offense(range) do |corrector|
52
+ corrector.remove(range)
53
+ end
54
+ end
55
+
56
+ def reference_variable_with_brackets?(node)
57
+ node.receiver&.variable? && node.method?(:[]) && node.arguments.size == 1
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end