diff --git a/changelog/new_add_ast_in_pattern_node.md b/changelog/new_add_ast_in_pattern_node.md new file mode 100644 index 000000000..3edd5db11 --- /dev/null +++ b/changelog/new_add_ast_in_pattern_node.md @@ -0,0 +1 @@ +* [#183](https://github.com/rubocop-hq/rubocop-ast/pull/183): Add `AST::InPatternNode` node. ([@koic][]) diff --git a/lib/rubocop/ast.rb b/lib/rubocop/ast.rb index 062c3a1cd..4f4ab8498 100644 --- a/lib/rubocop/ast.rb +++ b/lib/rubocop/ast.rb @@ -53,6 +53,7 @@ require_relative 'ast/node/float_node' require_relative 'ast/node/hash_node' require_relative 'ast/node/if_node' +require_relative 'ast/node/in_pattern_node' require_relative 'ast/node/index_node' require_relative 'ast/node/indexasgn_node' require_relative 'ast/node/int_node' diff --git a/lib/rubocop/ast/builder.rb b/lib/rubocop/ast/builder.rb index 51409cbcc..067495958 100644 --- a/lib/rubocop/ast/builder.rb +++ b/lib/rubocop/ast/builder.rb @@ -49,6 +49,7 @@ class Builder < Parser::Builders::Default float: FloatNode, hash: HashNode, if: IfNode, + in_pattern: InPatternNode, int: IntNode, index: IndexNode, indexasgn: IndexasgnNode, diff --git a/lib/rubocop/ast/node/case_match_node.rb b/lib/rubocop/ast/node/case_match_node.rb index 12fac9a3d..2b0c6e934 100644 --- a/lib/rubocop/ast/node/case_match_node.rb +++ b/lib/rubocop/ast/node/case_match_node.rb @@ -24,9 +24,9 @@ def each_in_pattern(&block) self end - # Returns an array of all the when branches in the `case` statement. + # Returns an array of all the `in` pattern branches in the `case` statement. # - # @return [Array] an array of `in_pattern` nodes + # @return [Array] an array of `in_pattern` nodes def in_pattern_branches node_parts[1...-1] end diff --git a/lib/rubocop/ast/node/in_pattern_node.rb b/lib/rubocop/ast/node/in_pattern_node.rb new file mode 100644 index 000000000..b1470d99c --- /dev/null +++ b/lib/rubocop/ast/node/in_pattern_node.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module RuboCop + module AST + # A node extension for `in` nodes. This will be used in place of a plain + # node when the builder constructs the AST, making its methods available + # to all `in` nodes within RuboCop. + class InPatternNode < Node + # Returns the index of the `in` branch within the `case` statement. + # + # @return [Integer] the index of the `in` branch + def branch_index + parent.in_pattern_branches.index(self) + end + + # Checks whether the `in` node has a `then` keyword. + # + # @return [Boolean] whether the `in` node has a `then` keyword + def then? + loc.begin&.is?('then') + end + + # Returns the body of the `in` node. + # + # @return [Node, nil] the body of the `in` node + def body + node_parts[-1] + end + end + end +end diff --git a/spec/rubocop/ast/in_pattern_node_spec.rb b/spec/rubocop/ast/in_pattern_node_spec.rb new file mode 100644 index 000000000..0d6261c4d --- /dev/null +++ b/spec/rubocop/ast/in_pattern_node_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::AST::InPatternNode do + context 'when using Ruby 2.7 or newer', :ruby27 do + let(:in_pattern_node) { parse_source(source).ast.children[1] } + + describe '.new' do + let(:source) do + ['case condition', + 'in [42] then foo', + 'end'].join("\n") + end + + it { expect(in_pattern_node).to be_a(described_class) } + end + + describe '#then?' do + context 'with a then keyword' do + let(:source) do + ['case condition', + 'in [42] then foo', + 'end'].join("\n") + end + + it { expect(in_pattern_node).to be_then } + end + + context 'without a then keyword' do + let(:source) do + ['case condition', + 'in [42]', + ' foo', + 'end'].join("\n") + end + + it { expect(in_pattern_node).not_to be_then } + end + end + + describe '#body' do + context 'with a then keyword' do + let(:source) do + ['case condition', + 'in [42] then :foo', + 'end'].join("\n") + end + + it { expect(in_pattern_node.body).to be_sym_type } + end + + context 'without a then keyword' do + let(:source) do + ['case condition', + 'in [42]', + ' [:foo, :bar]', + 'end'].join("\n") + end + + it { expect(in_pattern_node.body).to be_array_type } + end + end + + describe '#branch_index' do + let(:source) do + ['case condition', + 'in [42] then 1', + 'in [43] then 2', + 'in [44] then 3', + 'end'].join("\n") + end + + let(:in_patterns) { parse_source(source).ast.children[1...-1] } + + it { expect(in_patterns[0].branch_index).to eq(0) } + it { expect(in_patterns[1].branch_index).to eq(1) } + it { expect(in_patterns[2].branch_index).to eq(2) } + end + end +end