diff --git a/changelog/new_layout_space_before_brackets.md b/changelog/new_layout_space_before_brackets.md new file mode 100644 index 00000000000..e3033f038b5 --- /dev/null +++ b/changelog/new_layout_space_before_brackets.md @@ -0,0 +1 @@ +* [#9231](https://github.com/rubocop-hq/rubocop/pull/9231): Add new `Layout/SpaceBeforeBrackets` cop. ([@koic][]) diff --git a/config/default.yml b/config/default.yml index 2491bdd4110..a898ab296ad 100644 --- a/config/default.yml +++ b/config/default.yml @@ -1192,6 +1192,13 @@ Layout/SpaceBeforeBlockBraces: - no_space VersionChanged: '0.52' +Layout/SpaceBeforeBrackets: + Description: 'Checks for receiver with a space before the opening brackets.' + StyleGuide: '#space-in-brackets-access' + Enabled: pending + VersionAdded: '<>' + Safe: false + Layout/SpaceBeforeComma: Description: 'No spaces before commas.' Enabled: true diff --git a/lib/rubocop.rb b/lib/rubocop.rb index 7ab5666203f..347b60e7776 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -230,6 +230,7 @@ require_relative 'rubocop/cop/layout/space_around_method_call_operator' require_relative 'rubocop/cop/layout/space_around_operators' require_relative 'rubocop/cop/layout/space_before_block_braces' +require_relative 'rubocop/cop/layout/space_before_brackets' require_relative 'rubocop/cop/layout/space_before_comma' require_relative 'rubocop/cop/layout/space_before_comment' require_relative 'rubocop/cop/layout/space_before_first_arg' diff --git a/lib/rubocop/cop/layout/space_before_brackets.rb b/lib/rubocop/cop/layout/space_before_brackets.rb new file mode 100644 index 00000000000..1764c9a89c4 --- /dev/null +++ b/lib/rubocop/cop/layout/space_before_brackets.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Layout + # Checks for space between the name of a receiver and a left + # brackets. + # + # This cop is marked as unsafe because it can occur false positives + # for `do_something [this_is_an_array_literal_argument]` that take + # an array without parentheses as an argument. + # + # @example + # + # # bad + # collection [index_or_key] + # + # # good + # collection[index_or_key] + # + class SpaceBeforeBrackets < Base + include RangeHelp + extend AutoCorrector + + MSG = 'Remove the space before the opening brackets.' + + def_node_matcher :bad_method?, <<~PATTERN + (send nil? :bad_method ...) + PATTERN + + def on_send(node) + return if node.parenthesized? || node.parent&.send_type? + return unless (first_argument = node.first_argument) + + begin_pos = first_argument.source_range.begin_pos + + return unless (range = offense_range(node, first_argument, begin_pos)) + + register_offense(range) + end + + private + + def offense_range(node, first_argument, begin_pos) + if space_before_brackets?(node, first_argument) + range_between(node.loc.selector.end_pos, begin_pos) + elsif node.method?(:[]=) + end_pos = node.receiver.source_range.end_pos + + return if begin_pos - end_pos == 1 + + range_between(end_pos, begin_pos - 1) + end + end + + def register_offense(range) + add_offense(range) do |corrector| + corrector.remove(range) + end + end + + def space_before_brackets?(node, first_argument) + node.receiver.nil? && first_argument.array_type? && node.arguments.size == 1 + end + end + end + end +end diff --git a/spec/rubocop/cop/layout/space_before_brackets_spec.rb b/spec/rubocop/cop/layout/space_before_brackets_spec.rb new file mode 100644 index 00000000000..490b280647d --- /dev/null +++ b/spec/rubocop/cop/layout/space_before_brackets_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Layout::SpaceBeforeBrackets, :config do + subject(:cop) { described_class.new(config) } + + context 'when referencing' do + it 'registers an offense and corrects when using space between receiver and left brackets' do + expect_offense(<<~RUBY) + collection [index_or_key] + ^ Remove the space before the opening brackets. + RUBY + + expect_correction(<<~RUBY) + collection[index_or_key] + RUBY + end + + it 'does not register an offense when not using space between receiver and left brackets' do + expect_no_offenses(<<~RUBY) + collection[index_or_key] + RUBY + end + + it 'does not register an offense when array literal argument is enclosed in parentheses' do + expect_no_offenses(<<~RUBY) + collection([index_or_key]) + RUBY + end + + it 'does not register an offense when it is used as a method argument' do + expect_no_offenses(<<~RUBY) + expect(offenses).to eq [] + RUBY + end + + it 'does not register an offense when using multiple arguments' do + expect_no_offenses(<<~RUBY) + do_something [foo], bar + RUBY + end + + it 'does not register an offense when without receiver' do + expect_no_offenses(<<~RUBY) + [index_or_key] + RUBY + end + end + + context 'when assigning' do + it 'registers an offense and corrects when using space between receiver and left brackets' do + expect_offense(<<~RUBY) + @correction [index_or_key] = :value + ^ Remove the space before the opening brackets. + RUBY + + expect_correction(<<~RUBY) + @correction[index_or_key] = :value + RUBY + end + + it 'does not register an offense when not using space between receiver and left brackets' do + expect_no_offenses(<<~RUBY) + @correction[index_or_key] = :value + RUBY + end + end + + it 'does not register an offense when assigning an array' do + expect_no_offenses(<<~RUBY) + task.options = ['--no-output'] + RUBY + end +end