1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
# frozen_string_literal: true
module RuboCop
module AST
# A node extension for `block` nodes. This will be used in place of a plain
# node when the builder constructs the AST, making its methods available
# to all `send` nodes within RuboCop.
#
# A `block` node is essentially a method send with a block. Parser nests
# the `send` node inside the `block` node.
class BlockNode < Node
include MethodIdentifierPredicates
VOID_CONTEXT_METHODS = %i[each tap].freeze
private_constant :VOID_CONTEXT_METHODS
# The `send` node associated with this block.
#
# @return [SendNode] the `send` node associated with the `block` node
def send_node
node_parts[0]
end
# The arguments of this block.
# Note that if the block has destructured arguments, `arguments` will
# return a `mlhs` node, whereas `argument_list` will return only
# actual argument nodes.
#
# @return [Array<Node>]
def arguments
if numblock_type?
[].freeze # Numbered parameters have no block arguments.
else
node_parts[1]
end
end
# Returns a collection of all descendants of this node that are
# argument type nodes. See `ArgsNode#argument_list` for details.
#
# @return [Array<Node>]
def argument_list
if numblock_type?
numbered_arguments
else
arguments.argument_list
end
end
# The body of this block.
#
# @return [Node, nil] the body of the `block` node or `nil`
def body
node_parts[2]
end
# The name of the dispatched method as a symbol.
#
# @return [Symbol] the name of the dispatched method
def method_name
send_node.method_name
end
# Checks whether this block takes any arguments.
#
# @return [Boolean] whether this `block` node takes any arguments
def arguments?
!arguments.empty?
end
# Checks whether the `block` literal is delimited by curly braces.
#
# @return [Boolean] whether the `block` literal is enclosed in braces
def braces?
loc.end&.is?('}')
end
# Checks whether the `block` literal is delimited by `do`-`end` keywords.
#
# @return [Boolean] whether the `block` literal is enclosed in `do`-`end`
def keywords?
loc.end&.is?('end')
end
# The delimiters for this `block` literal.
#
# @return [Array<String>] the delimiters for the `block` literal
def delimiters
[loc.begin.source, loc.end.source].freeze
end
# The opening delimiter for this `block` literal.
#
# @return [String] the opening delimiter for the `block` literal
def opening_delimiter
delimiters.first
end
# The closing delimiter for this `block` literal.
#
# @return [String] the closing delimiter for the `block` literal
def closing_delimiter
delimiters.last
end
# Checks whether this is a single line block. This is overridden here
# because the general version in `Node` does not work for `block` nodes.
#
# @return [Boolean] whether the `block` literal is on a single line
def single_line?
loc.begin.line == loc.end.line
end
# Checks whether this is a multiline block. This is overridden here
# because the general version in `Node` does not work for `block` nodes.
#
# @return [Boolean] whether the `block` literal is on a several lines
def multiline?
!single_line?
end
# Checks whether this `block` literal belongs to a lambda.
#
# @return [Boolean] whether the `block` literal belongs to a lambda
def lambda?
send_node.method?(:lambda)
end
# Checks whether this node body is a void context.
#
# @return [Boolean] whether the `block` node body is a void context
def void_context?
VOID_CONTEXT_METHODS.include?(method_name)
end
private
# Numbered arguments of this `numblock`.
def numbered_arguments
return [].freeze unless numblock_type?
max_param = children[1]
1.upto(max_param).map do |i|
ArgNode.new(:arg, [:"_#{i}"])
end.freeze
end
end
end
end
|