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
-
(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)
-
(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)
- 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
-
(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
-
(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?
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
-
(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?
-
(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
-
(Node, nil)
- the right (aka next) sibling
def right_sibling return unless parent parent.children[sibling_index + 1].freeze end
def right_siblings
-
(Array
- the right (aka next) siblings)
def right_siblings return [].freeze unless parent parent.children[sibling_index + 1..].freeze end
def root?
-
(Boolean)
-
def root? !parent end
def send_type?
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
-
(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
-
(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 = {})
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?
`(...; 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