class RuboCop::AST::Node

rubocop:disable Metrics/ClassLength
lvar_node = node.each_descendant.find(&:lvar_type?)
# Find the first lvar node under the receiver node.
node.defined_type? # Equivalent to: ‘node.type == :defined?`
# Non-word characters (other than a-zA-Z0-9_) in type names are omitted.
node.op_asgn_type? # Equivalent to: `node.type == :op_asgn`
node.send_type? # Equivalent to: `node.type == :send`
@example
It has predicate methods for every node type, like this:
the power of `Enumerable`.
access to parent nodes and an object-oriented way to traverse an AST with
`RuboCop::AST::Node` is a subclass of `Parser::AST::Node`. It provides

def ancestors

Returns:
  • (Array) - an array of ancestor nodes
def ancestors
  each_ancestor.to_a
end

def argument?

def argument?
  parent&.send_type? && parent.arguments.include?(self)
end

def argument_type?

def argument_type?
  ARGUMENT_TYPES.include?(type)
end

def assignment?

def assignment?
  ASSIGNMENTS.include?(type)
end

def basic_conditional?

def basic_conditional?
  BASIC_CONDITIONALS.include?(type)
end

def basic_literal?

def basic_literal?
  BASIC_LITERALS.include?(type)
end

def begin_value_used?

def begin_value_used?
  # the last child node determines the value of the parent
  sibling_index == parent.children.size - 1 ? parent.value_used? : false
end

def boolean_type?

def boolean_type?
  true_type? || false_type?
end

def call_type?

def call_type?
  send_type? || csend_type?
end

def case_if_value_used?

def case_if_value_used?
  # (case <condition> <when...>)
  # (if <condition> <truebranch> <falsebranch>)
  sibling_index.zero? ? true : parent.value_used?
end

def chained?

def chained?
  parent&.call_type? && eql?(parent.receiver)
end

def complete!

def complete!
  @mutable_attributes.freeze
  each_child_node(&:complete!)
end

def complete?

def complete?
  @mutable_attributes.frozen?
end

def conditional?

def conditional?
  CONDITIONALS.include?(type)
end

def const_name

def const_name
  return unless const_type?
  namespace, name = *self
  if namespace && !namespace.cbase_type?
    "#{namespace.const_name}::#{name}"
  else
    name.to_s
  end
end

def defined_module

def defined_module
  namespace, name = *defined_module0
  s(:const, namespace, name) if name
end

def defined_module_name

def defined_module_name
  (const = defined_module) && const.const_name
end

def each_ancestor(*types, &block)

Returns:
  • (Enumerator) - if no block is given
  • (self) - if a block is given

Other tags:
    Yieldparam: node - each ancestor node

Parameters:
  • type_b (Symbol) -- a node type
  • type_a (Symbol) -- a node type
  • type (Symbol) -- a node type

Overloads:
  • each_ancestor(type_a, type_b, ...)
  • each_ancestor(type)
  • each_ancestor
def each_ancestor(*types, &block)
  return to_enum(__method__, *types) unless block
  visit_ancestors(types, &block)
  self
end

def empty_source?

def empty_source?
  source_length.zero?
end

def equals_asgn?

def equals_asgn?
  EQUALS_ASSIGNMENTS.include?(type)
end

def falsey_literal?

def falsey_literal?
  FALSEY_LITERALS.include?(type)
end

def first_line

def first_line
  loc.line
end

def for_value_used?

def for_value_used?
  # `for var in enum; body; end`
  # (for <var> <enum> <body>)
  sibling_index == 2 ? parent.value_used? : true
end

def guard_clause?

def guard_clause?
  node = and_type? || or_type? ? rhs : self
  node.match_guard_clause?
end

def immutable_literal?

def immutable_literal?
  IMMUTABLE_LITERALS.include?(type)
end

def initialize(type, children = EMPTY_CHILDREN, properties = EMPTY_PROPERTIES)

Other tags:
    See: https://www.rubydoc.info/gems/ast/AST/Node:initialize -
def initialize(type, children = EMPTY_CHILDREN, properties = EMPTY_PROPERTIES)
  @mutable_attributes = {}
  # ::AST::Node#initialize freezes itself.
  super
  # #parent= may be invoked multiple times for a node because there are
  # pending nodes while constructing AST and they are replaced later.
  # For example, `lvar` and `send` type nodes are initially created as an
  # `ident` type node and fixed to the appropriate type later.
  # So, the #parent attribute needs to be mutable.
  each_child_node do |child_node|
    child_node.parent = self unless child_node.complete?
  end
end

def keyword?

def keyword?
  return true if special_keyword? || (send_type? && prefix_not?)
  return false unless KEYWORDS.include?(type)
  !OPERATOR_KEYWORDS.include?(type) || loc.operator.is?(type.to_s)
end

def last_line

def last_line
  loc.last_line
end

def left_sibling

Returns:
  • (Node, nil) - the left (aka previous) sibling
def left_sibling
  i = sibling_index
  return if i.nil? || i.zero?
  parent.children[i - 1].freeze
end

def left_siblings

Returns:
  • (Array) - the left (aka previous) siblings
def left_siblings
  return [].freeze unless parent
  parent.children[0...sibling_index].freeze
end

def line_count

def line_count
  return 0 unless source_range
  source_range.last_line - source_range.first_line + 1
end

def literal?

def literal?
  LITERALS.include?(type)
end

def loop_keyword?

NOTE: `loop { }` is a normal method call and thus not a loop keyword.
def loop_keyword?
  LOOP_TYPES.include?(type)
end

def multiline?

def multiline?
  line_count > 1
end

def mutable_literal?

def mutable_literal?
  MUTABLE_LITERALS.include?(type)
end

def nonempty_line_count

def nonempty_line_count
  source.lines.grep(/\S/).size
end

def numeric_type?

def numeric_type?
  int_type? || float_type? || rational_type? || complex_type?
end

def operator_keyword?

def operator_keyword?
  OPERATOR_KEYWORDS.include?(type)
end

def parent

Returns:
  • (Node, nil) - the parent node or `nil`
def parent
  @mutable_attributes[:parent]
end

def parent=(node)

def parent=(node)
  @mutable_attributes[:parent] = node
end

def parent?

Returns:
  • (Boolean) -
def parent?
  !!parent
end

def parent_module_name

def parent_module_name
  # what class or module is this method/constant/etc definition in?
  # returns nil if answer cannot be determined
  ancestors = each_ancestor(:class, :module, :sclass, :casgn, :block)
  result    = ancestors.map do |ancestor|
    parent_module_name_part(ancestor) do |full_name|
      return nil unless full_name
      full_name
    end
  end.compact.reverse.join('::')
  result.empty? ? 'Object' : result
end

def parent_module_name_for_block(ancestor)

def parent_module_name_for_block(ancestor)
  if ancestor.method?(:class_eval)
    # `class_eval` with no receiver applies to whatever module or class
    # we are currently in
    return unless (receiver = ancestor.receiver)
    yield unless receiver.const_type?
    receiver.const_name
  elsif !new_class_or_module_block?(ancestor)
    yield
  end
end

def parent_module_name_for_sclass(sclass_node)

def parent_module_name_for_sclass(sclass_node)
  # TODO: look for constant definition and see if it is nested
  # inside a class or module
  subject = sclass_node.children[0]
  if subject.const_type?
    "#<Class:#{subject.const_name}>"
  elsif subject.self_type?
    "#<Class:#{sclass_node.parent_module_name}>"
  end
end

def parent_module_name_part(node)

def parent_module_name_part(node)
  case node.type
  when :class, :module, :casgn
    # TODO: if constant name has cbase (leading ::), then we don't need
    # to keep traversing up through nested classes/modules
    node.defined_module_name
  when :sclass
    yield parent_module_name_for_sclass(node)
  else # block
    parent_module_name_for_block(node) { yield nil }
  end
end

def parenthesized_call?

def parenthesized_call?
  loc.respond_to?(:begin) && loc.begin && loc.begin.is?('(')
end

def post_condition_loop?

def post_condition_loop?
  POST_CONDITION_LOOP_TYPES.include?(type)
end

def pure?


So, is evaluation of this node free of side effects?
expressions which are equivalent in value.
number of times they are evaluated, or replace them with other
and have no side effects, that means we can reorder them, change the
If we know that expressions are useful only for their return values,
effects, and some for both.
Some expressions are evaluated for their value, some for their side
def pure?
  # Be conservative and return false if we're not sure
  case type
  when :__FILE__, :__LINE__, :const, :cvar, :defined?, :false, :float,
       :gvar, :int, :ivar, :lvar, :nil, :str, :sym, :true, :regopt
    true
  when :and, :array, :begin, :case, :dstr, :dsym, :eflipflop, :ensure,
       :erange, :for, :hash, :if, :iflipflop, :irange, :kwbegin, :not,
       :or, :pair, :regexp, :until, :until_post, :when, :while,
       :while_post
    child_nodes.all?(&:pure?)
  else
    false
  end
end

def range_type?

def range_type?
  irange_type? || erange_type?
end

def reference?

def reference?
  REFERENCES.include?(type)
end

def right_sibling

Returns:
  • (Node, nil) - the right (aka next) sibling
def right_sibling
  return unless parent
  parent.children[sibling_index + 1].freeze
end

def right_siblings

Returns:
  • (Array) - the right (aka next) siblings
def right_siblings
  return [].freeze unless parent
  parent.children[sibling_index + 1..].freeze
end

def root?

Returns:
  • (Boolean) -
def root?
  !parent
end

def send_type?

separately to make this check as fast as possible.
Most nodes are of 'send' type, so this method is defined
def send_type?
  false
end

def shorthand_asgn?

def shorthand_asgn?
  SHORTHAND_ASSIGNMENTS.include?(type)
end

def sibling_index

Returns:
  • (Integer, nil) - the index of the receiver node in its siblings
def sibling_index
  parent&.children&.index { |sibling| sibling.equal?(self) }
end

def single_line?

def single_line?
  line_count == 1
end

def source

Returns:
  • (String, nil) -
def source
  loc.expression&.source
end

def source_length

def source_length
  source_range ? source_range.size : 0
end

def source_range

def source_range
  loc.expression
end

def special_keyword?

def special_keyword?
  SPECIAL_KEYWORDS.include?(source)
end

def truthy_literal?

def truthy_literal?
  TRUTHY_LITERALS.include?(type)
end

def updated(type = nil, children = nil, properties = {})

part of it is changed.
identical subtrees. Rather, the entire AST must be copied any time any
not just the other way around, we cannot update an AST and share
mutate our ASTs. Since we keep references from children to parents and
Override `AST::Node#updated` so that `AST::Processor` does not try to
def updated(type = nil, children = nil, properties = {})
  properties[:location] ||= @location
  klass = RuboCop::AST::Builder::NODE_MAP[type || @type] || Node
  klass.new(type || @type, children || @children, properties)
end

def value_used?

rubocop:disable Metrics/MethodLength

`(...; nil)`, might that affect anything?
So, does the return value of this node matter? If we changed it to
change the return value
means we can transform it in ways which preserve the side effects, but
If we know that an expression is useful only for its side effects, that
effects, and some for both
Some expressions are evaluated for their value, some for their side
def value_used?
  # Be conservative and return true if we're not sure.
  return false if parent.nil?
  case parent.type
  when :array, :defined?, :dstr, :dsym, :eflipflop, :erange, :float,
       :hash, :iflipflop, :irange, :not, :pair, :regexp, :str, :sym,
       :when, :xstr
    parent.value_used?
  when :begin, :kwbegin
    begin_value_used?
  when :for
    for_value_used?
  when :case, :if
    case_if_value_used?
  when :while, :until, :while_post, :until_post
    while_until_value_used?
  else
    true
  end
end

def variable?

def variable?
  VARIABLES.include?(type)
end

def visit_ancestors(types)

def visit_ancestors(types)
  last_node = self
  while (current_node = last_node.parent)
    yield current_node if types.empty? ||
                          types.include?(current_node.type)
    last_node = current_node
  end
end

def while_until_value_used?

def while_until_value_used?
  # (while <condition> <body>) -> always evaluates to `nil`
  sibling_index.zero?
end