diff --git a/lib/rubocop/ast/node.rb b/lib/rubocop/ast/node.rb index 60e148e4f..c2f745527 100644 --- a/lib/rubocop/ast/node.rb +++ b/lib/rubocop/ast/node.rb @@ -497,7 +497,7 @@ def operator_keyword? end def parenthesized_call? - loc.respond_to?(:begin) && loc.begin&.is?('(') + loc_is?(:begin, '(') end def call_type? @@ -534,6 +534,17 @@ def guard_clause? node.match_guard_clause? end + # Shortcut to safely test a particular location, even if + # this location does not exist or is `nil` + def loc_is?(which_loc, str) + return false unless loc.respond_to?(which_loc) + + location = loc.public_send(which_loc) + return false unless location + + location.is?(str) + end + # @!method match_guard_clause?(node = self) def_node_matcher :match_guard_clause?, <<~PATTERN [${(send nil? {:raise :fail} ...) return break next} single_line?] diff --git a/lib/rubocop/ast/node/array_node.rb b/lib/rubocop/ast/node/array_node.rb index 19c9a507a..2ab058871 100644 --- a/lib/rubocop/ast/node/array_node.rb +++ b/lib/rubocop/ast/node/array_node.rb @@ -34,7 +34,7 @@ def each_value(&block) # # @return [Boolean] whether the array is enclosed in square brackets def square_brackets? - loc.begin&.is?('[') + loc_is?(:begin, '[') end # Checks whether the `array` literal is delimited by percent brackets. diff --git a/lib/rubocop/ast/node/for_node.rb b/lib/rubocop/ast/node/for_node.rb index 18c64530d..b83099fb6 100644 --- a/lib/rubocop/ast/node/for_node.rb +++ b/lib/rubocop/ast/node/for_node.rb @@ -17,7 +17,7 @@ def keyword # # @return [Boolean] whether the `for` node has a `do` keyword def do? - loc.begin&.is?('do') + loc_is?(:begin, 'do') end # Checks whether this node body is a void context. diff --git a/lib/rubocop/ast/node/hash_node.rb b/lib/rubocop/ast/node/hash_node.rb index 2c5b92750..0368482de 100644 --- a/lib/rubocop/ast/node/hash_node.rb +++ b/lib/rubocop/ast/node/hash_node.rb @@ -115,7 +115,7 @@ def mixed_delimiters? # # @return [Boolean] whether the `hash` literal is enclosed in braces def braces? - loc.end&.is?('}') + loc_is?(:end, '}') end end end diff --git a/lib/rubocop/ast/node/in_pattern_node.rb b/lib/rubocop/ast/node/in_pattern_node.rb index cdea7ed46..1cac31350 100644 --- a/lib/rubocop/ast/node/in_pattern_node.rb +++ b/lib/rubocop/ast/node/in_pattern_node.rb @@ -24,7 +24,7 @@ def branch_index # # @return [Boolean] whether the `in` node has a `then` keyword def then? - loc.begin&.is?('then') + loc_is?(:begin, 'then') end # Returns the body of the `in` node. diff --git a/lib/rubocop/ast/node/mixin/method_dispatch_node.rb b/lib/rubocop/ast/node/mixin/method_dispatch_node.rb index 40e484ad3..a1fb3716c 100644 --- a/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +++ b/lib/rubocop/ast/node/mixin/method_dispatch_node.rb @@ -117,7 +117,7 @@ def setter_method? # # @return [Boolean] whether the method was called with a connecting dot def dot? - loc.respond_to?(:dot) && loc.dot&.is?('.') + loc_is?(:dot, '.') end # Checks whether the dispatched method uses a double colon to connect the @@ -125,7 +125,7 @@ def dot? # # @return [Boolean] whether the method was called with a connecting dot def double_colon? - loc.respond_to?(:dot) && loc.dot&.is?('::') + loc_is?(:dot, '::') end # Checks whether the dispatched method uses a safe navigation operator to @@ -133,7 +133,7 @@ def double_colon? # # @return [Boolean] whether the method was called with a connecting dot def safe_navigation? - loc.respond_to?(:dot) && loc.dot&.is?('&.') + loc_is?(:dot, '&.') end # Checks whether the *explicit* receiver of this method dispatch is diff --git a/lib/rubocop/ast/node/mixin/parameterized_node.rb b/lib/rubocop/ast/node/mixin/parameterized_node.rb index f890aa352..2c751b543 100644 --- a/lib/rubocop/ast/node/mixin/parameterized_node.rb +++ b/lib/rubocop/ast/node/mixin/parameterized_node.rb @@ -13,7 +13,7 @@ module ParameterizedNode # @return [Boolean] whether this node's arguments are # wrapped in parentheses def parenthesized? - loc.end&.is?(')') + loc_is?(:end, ')') end # A shorthand for getting the first argument of the node. diff --git a/lib/rubocop/ast/node/str_node.rb b/lib/rubocop/ast/node/str_node.rb index 8aaae7e93..6e17bf1e3 100644 --- a/lib/rubocop/ast/node/str_node.rb +++ b/lib/rubocop/ast/node/str_node.rb @@ -9,7 +9,7 @@ class StrNode < Node include BasicLiteralNode def character_literal? - loc.respond_to?(:begin) && loc.begin&.is?('?') + loc_is?(:begin, '?') end def heredoc? diff --git a/lib/rubocop/ast/node/until_node.rb b/lib/rubocop/ast/node/until_node.rb index 54f1783c8..54bbd3118 100644 --- a/lib/rubocop/ast/node/until_node.rb +++ b/lib/rubocop/ast/node/until_node.rb @@ -28,7 +28,7 @@ def inverse_keyword # # @return [Boolean] whether the `until` node has a `do` keyword def do? - loc.begin&.is?('do') + loc_is?(:begin, 'do') end end end diff --git a/lib/rubocop/ast/node/when_node.rb b/lib/rubocop/ast/node/when_node.rb index c37b8b2af..ef916b0ae 100644 --- a/lib/rubocop/ast/node/when_node.rb +++ b/lib/rubocop/ast/node/when_node.rb @@ -33,7 +33,7 @@ def branch_index # # @return [Boolean] whether the `when` node has a `then` keyword def then? - loc.begin&.is?('then') + loc_is?(:begin, 'then') end # Returns the body of the `when` node. diff --git a/lib/rubocop/ast/node/while_node.rb b/lib/rubocop/ast/node/while_node.rb index 61f46e95e..dac04a72e 100644 --- a/lib/rubocop/ast/node/while_node.rb +++ b/lib/rubocop/ast/node/while_node.rb @@ -28,7 +28,7 @@ def inverse_keyword # # @return [Boolean] whether the `until` node has a `do` keyword def do? - loc.begin&.is?('do') + loc_is?(:begin, 'do') end end end diff --git a/spec/rubocop/ast/node_spec.rb b/spec/rubocop/ast/node_spec.rb index 96a795078..34cc4d8cd 100644 --- a/spec/rubocop/ast/node_spec.rb +++ b/spec/rubocop/ast/node_spec.rb @@ -1109,4 +1109,28 @@ class << expr end end end + + describe '#loc_is?' do + let(:src) { '%i[>> sym << sym2]' } + + context 'when loc exists' do + let(:src) { ':sym' } + + it 'returns true when loc matches argument' do + expect(node).to be_loc_is(:begin, ':') + end + + it 'returns false when loc does not match argument' do + expect(node).not_to be_loc_is(:begin, '!') + end + end + + it 'returns false when requested loc is `nil`' do + expect(node).not_to be_loc_is(:begin, ':') + end + + it 'returns false when requested loc does not exist' do + expect(node).not_to be_loc_is(:foo, ':') + end + end end