Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Node#type? to reduce complexity of checking against multiple node types #329

Merged
merged 2 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/new_add_nodetype_to_reduce_complexity_of.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#329](https://github.com/rubocop/rubocop-ast/pull/329): Add `Node#type?` to reduce complexity of checking against multiple node types. ([@dvandersluis][])
48 changes: 43 additions & 5 deletions lib/rubocop/ast/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,34 @@ class Node < Parser::AST::Node # rubocop:disable Metrics/ClassLength
EMPTY_PROPERTIES = {}.freeze
private_constant :EMPTY_CHILDREN, :EMPTY_PROPERTIES

# @api private
GROUP_FOR_TYPE = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make this constant private

arg: :argument,
optarg: :argument,
restarg: :argument,
kwarg: :argument,
kwoptarg: :argument,
kwrestarg: :argument,
blockarg: :argument,
forward_arg: :argument,
shardowarg: :argument,

true: :boolean,
false: :boolean,

int: :numeric,
float: :numeric,
rational: :numeric,
complex: :numeric,

irange: :range,
erange: :range,

send: :call,
csend: :call
}.freeze
private_constant :GROUP_FOR_TYPE

# Define a +recursive_?+ predicate method for the given node kind.
private_class_method def self.def_recursive_literal_predicate(kind) # rubocop:disable Metrics/MethodLength
recursive_kind = "recursive_#{kind}?"
Expand Down Expand Up @@ -126,6 +154,16 @@ def initialize(type, children = EMPTY_CHILDREN, properties = EMPTY_PROPERTIES)
end
end

# Determine if the node is one of several node types in a single query
# Allows specific single node types, as well as "grouped" types
# (e.g. `:boolean` for `:true` or `:false`)
def type?(*types)
return true if types.include?(type)

group_type = GROUP_FOR_TYPE[type]
!group_type.nil? && types.include?(group_type)
end

(Parser::Meta::NODE_TYPES - [:send]).each do |node_type|
method_name = "#{node_type.to_s.gsub(/\W/, '')}_type?"
class_eval <<~RUBY, __FILE__, __LINE__ + 1
Expand Down Expand Up @@ -463,7 +501,7 @@ def parenthesized_call?
end

def call_type?
send_type? || csend_type?
GROUP_FOR_TYPE[type] == :call
end

def chained?
Expand All @@ -475,19 +513,19 @@ def argument?
end

def argument_type?
ARGUMENT_TYPES.include?(type)
GROUP_FOR_TYPE[type] == :argument
end

def boolean_type?
true_type? || false_type?
GROUP_FOR_TYPE[type] == :boolean
end

def numeric_type?
int_type? || float_type? || rational_type? || complex_type?
GROUP_FOR_TYPE[type] == :numeric
end

def range_type?
irange_type? || erange_type?
GROUP_FOR_TYPE[type] == :range
end

def guard_clause?
Expand Down
61 changes: 61 additions & 0 deletions spec/rubocop/ast/node_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1048,4 +1048,65 @@ class << expr
end
end
end

describe '#type?' do
let(:src) do
<<~RUBY
foo.bar
RUBY
end

context 'when it is one of the given types' do
it 'is true' do
expect(node).to be_type(:send, :const, :lvar)
end
end

context 'when it is not one of the given types' do
it 'is false' do
expect(node).not_to be_type(:if, :while, :until)
end
end

context 'when given :argument with an `arg` node' do
let(:src) { 'foo { |>> var <<| } ' }

it 'is true' do
arg_node = ast.procarg0_type? ? ast.child_nodes.first : node
expect(arg_node).to be_type(:argument)
end
end

context 'when given :boolean with an `true` node' do
let(:src) { 'true' }

it 'is true' do
expect(node).to be_type(:boolean)
end
end

context 'when given :numeric with an `int` node' do
let(:src) { '42' }

it 'is true' do
expect(node).to be_type(:numeric)
end
end

context 'when given :range with an `irange` node' do
let(:src) { '1..3' }

it 'is true' do
expect(node).to be_type(:range)
end
end

context 'when given :call with an `send` node' do
let(:src) { 'foo.bar' }

it 'is true' do
expect(node).to be_type(:call)
end
end
end
end