class RBI::TreeBuilder

Attributes

tree[R]

Public Class Methods

new(file:, comments: {}) click to toggle source
Calls superclass method
# File lib/rbi/parser.rb, line 105
def initialize(file:, comments: {})
  super()
  @file = file
  @comments = comments
  @tree = T.let(Tree.new, Tree)
  @scopes_stack = T.let([@tree], T::Array[Tree])
  @last_sigs = T.let([], T::Array[RBI::Sig])
end

Public Instance Methods

assoc_dangling_comments(comments) click to toggle source
# File lib/rbi/parser.rb, line 166
def assoc_dangling_comments(comments)
  last_line = T.let(nil, T.nilable(Integer))
  (comments - @comments.values.flatten).each do |comment|
    comment_line = comment.location.last_line
    text = comment.text[1..-1].strip
    loc = Loc.from_ast_loc(@file, comment.location)

    if last_line && comment_line > last_line + 1
      # Preserve empty lines in file headers
      tree.comments << EmptyComment.new(loc: loc)
    end

    tree.comments << Comment.new(text, loc: loc)
    last_line = comment_line
  end
end
separate_header_comments() click to toggle source
# File lib/rbi/parser.rb, line 144
def separate_header_comments
  return if @comments.empty?

  keep = []
  node = T.must(@comments.keys.first)
  comments = T.must(@comments.values.first)

  last_line = T.let(nil, T.nilable(Integer))
  comments.reverse.each do |comment|
    comment_line = comment.location.last_line

    break if last_line && comment_line < last_line - 1 ||
      !last_line && comment_line < node.first_line - 1

    keep << comment
    last_line = comment_line
  end

  @comments[node] = keep.reverse
end
visit(node) click to toggle source
# File lib/rbi/parser.rb, line 115
def visit(node)
  return unless node.is_a?(AST::Node)
  case node.type
  when :module, :class, :sclass
    scope = parse_scope(node)
    current_scope << scope
    @scopes_stack << scope
    visit_all(node.children)
    @scopes_stack.pop
  when :casgn
    current_scope << parse_const_assign(node)
  when :def, :defs
    current_scope << parse_def(node)
  when :send
    node = parse_send(node)
    current_scope << node if node
  when :block
    node = parse_block(node)
    if node.is_a?(Sig)
      @last_sigs << node
    elsif node
      current_scope << node
    end
  else
    visit_all(node.children)
  end
end

Private Instance Methods

current_scope() click to toggle source
# File lib/rbi/parser.rb, line 442
def current_scope
  T.must(@scopes_stack.last) # Should never be nil since we create a Tree as the root
end
current_sigs() click to toggle source
# File lib/rbi/parser.rb, line 447
def current_sigs
  sigs = @last_sigs.dup
  @last_sigs.clear
  sigs
end
node_comments(node) click to toggle source
# File lib/rbi/parser.rb, line 431
def node_comments(node)
  comments = @comments[node.location]
  return [] unless comments
  comments.map do |comment|
    text = comment.text[1..-1].strip
    loc = Loc.from_ast_loc(@file, comment.location)
    Comment.new(text, loc: loc)
  end
end
node_loc(node) click to toggle source
# File lib/rbi/parser.rb, line 426
def node_loc(node)
  Loc.from_ast_loc(@file, node.location)
end
parse_block(node) click to toggle source
# File lib/rbi/parser.rb, line 334
def parse_block(node)
  name = node.children[0].children[1]

  case name
  when :sig
    parse_sig(node)
  when :enums
    parse_enum(node)
  else
    raise ParseError.new("Unsupported block node type `#{name}`", node_loc(node))
  end
end
parse_const_assign(node) click to toggle source
# File lib/rbi/parser.rb, line 206
def parse_const_assign(node)
  node_value = node.children[2]
  if struct_definition?(node_value)
    parse_struct(node)
  else
    name = parse_name(node)
    value = parse_expr(node_value)
    loc = node_loc(node)
    comments = node_comments(node)
    Const.new(name, value, loc: loc, comments: comments)
  end
end
parse_def(node) click to toggle source
# File lib/rbi/parser.rb, line 220
def parse_def(node)
  loc = node_loc(node)

  case node.type
  when :def
    Method.new(
      node.children[0].to_s,
      params: node.children[1].children.map { |child| parse_param(child) },
      sigs: current_sigs,
      loc: loc,
      comments: node_comments(node)
    )
  when :defs
    Method.new(
      node.children[1].to_s,
      params: node.children[2].children.map { |child| parse_param(child) },
      is_singleton: true,
      sigs: current_sigs,
      loc: loc,
      comments: node_comments(node)
    )
  else
    raise ParseError.new("Unsupported def node type `#{node.type}`", loc)
  end
end
parse_enum(node) click to toggle source
# File lib/rbi/parser.rb, line 416
def parse_enum(node)
  enum = TEnumBlock.new
  node.children[2].children.each do |child|
    enum << parse_name(child)
  end
  enum.loc = node_loc(node)
  enum
end
parse_param(node) click to toggle source
# File lib/rbi/parser.rb, line 247
def parse_param(node)
  name = node.children[0].to_s
  loc = node_loc(node)
  comments = node_comments(node)

  case node.type
  when :arg
    ReqParam.new(name, loc: loc, comments: comments)
  when :optarg
    value = parse_expr(node.children[1])
    OptParam.new(name, value, loc: loc, comments: comments)
  when :restarg
    RestParam.new(name, loc: loc, comments: comments)
  when :kwarg
    KwParam.new(name, loc: loc, comments: comments)
  when :kwoptarg
    value = parse_expr(node.children[1])
    KwOptParam.new(name, value, loc: loc, comments: comments)
  when :kwrestarg
    KwRestParam.new(name, loc: loc, comments: comments)
  when :blockarg
    BlockParam.new(name, loc: loc, comments: comments)
  else
    raise ParseError.new("Unsupported param node type `#{node.type}`", loc)
  end
end
parse_scope(node) click to toggle source
# File lib/rbi/parser.rb, line 186
def parse_scope(node)
  loc = node_loc(node)
  comments = node_comments(node)

  case node.type
  when :module
    name = parse_name(node.children[0])
    Module.new(name, loc: loc, comments: comments)
  when :class
    name = parse_name(node.children[0])
    superclass_name = ConstBuilder.visit(node.children[1])
    Class.new(name, superclass_name: superclass_name, loc: loc, comments: comments)
  when :sclass
    SingletonClass.new(loc: loc, comments: comments)
  else
    raise ParseError.new("Unsupported scope node type `#{node.type}`", loc)
  end
end
parse_send(node) click to toggle source
# File lib/rbi/parser.rb, line 275
def parse_send(node)
  recv = node.children[0]
  return nil if recv && recv != :self

  method_name = node.children[1]
  loc = node_loc(node)
  comments = node_comments(node)

  case method_name
  when :attr_reader
    symbols = node.children[2..-1].map { |child| child.children[0] }
    AttrReader.new(*symbols, sigs: current_sigs, loc: loc, comments: comments)
  when :attr_writer
    symbols = node.children[2..-1].map { |child| child.children[0] }
    AttrWriter.new(*symbols, sigs: current_sigs, loc: loc, comments: comments)
  when :attr_accessor
    symbols = node.children[2..-1].map { |child| child.children[0] }
    AttrAccessor.new(*symbols, sigs: current_sigs, loc: loc, comments: comments)
  when :include
    names = node.children[2..-1].map { |child| parse_name(child) }
    Include.new(*names, loc: loc, comments: comments)
  when :extend
    names = node.children[2..-1].map { |child| parse_name(child) }
    Extend.new(*names, loc: loc, comments: comments)
  when :abstract!, :sealed!, :interface!
    Helper.new(method_name.to_s.delete_suffix("!"), loc: loc, comments: comments)
  when :mixes_in_class_methods
    names = node.children[2..-1].map { |child| parse_name(child) }
    MixesInClassMethods.new(*names, loc: loc, comments: comments)
  when :public, :protected, :private
    visibility = Visibility.new(method_name, loc: loc)
    nested_node = node.children[2]
    case nested_node&.type
    when :def, :defs
      method = parse_def(nested_node)
      method.visibility = visibility
      method
    when :send
      snode = parse_send(nested_node)
      raise ParseError.new("Unexpected token `private` before `#{nested_node.type}`", loc) unless snode.is_a?(Attr)
      snode.visibility = visibility
      snode
    when nil
      visibility
    else
      raise ParseError.new("Unexpected token `private` before `#{nested_node.type}`", loc)
    end
  when :prop
    name, type, default_value = parse_tstruct_prop(node)
    TStructProp.new(name, type, default: default_value, loc: loc, comments: comments)
  when :const
    name, type, default_value = parse_tstruct_prop(node)
    TStructConst.new(name, type, default: default_value, loc: loc, comments: comments)
  else
    raise ParseError.new("Unsupported send node with name `#{method_name}`", loc)
  end
end
parse_sig(node) click to toggle source
# File lib/rbi/parser.rb, line 409
def parse_sig(node)
  sig = SigBuilder.build(node)
  sig.loc = node_loc(node)
  sig
end
parse_struct(node) click to toggle source
# File lib/rbi/parser.rb, line 354
def parse_struct(node)
  name = parse_name(node)
  loc = node_loc(node)
  comments = node_comments(node)

  send = node.children[2]
  body = []

  if send.type == :block
    if send.children[2].type == :begin
      body = send.children[2].children
    else
      body << send.children[2]
    end
    send = send.children[0]
  end

  members = []
  keyword_init = T.let(false, T::Boolean)
  send.children[2..].each do |child|
    if child.type == :sym
      members << child.children[0]
    elsif child.type == :kwargs
      pair = child.children[0]
      if pair.children[0].children[0] == :keyword_init
        keyword_init = true if pair.children[1].type == :true
      end
    end
  end

  struct = Struct.new(name, members: members, keyword_init: keyword_init, loc: loc, comments: comments)
  @scopes_stack << struct
  visit_all(body)
  @scopes_stack.pop

  struct
end
parse_tstruct_prop(node) click to toggle source
# File lib/rbi/parser.rb, line 393
def parse_tstruct_prop(node)
  name = node.children[2].children[0].to_s
  type = parse_expr(node.children[3])
  has_default = node.children[4]
    &.children&.fetch(0, nil)
    &.children&.fetch(0, nil)
    &.children&.fetch(0, nil) == :default
  default_value = if has_default
    parse_expr(node.children.fetch(4, nil)
      &.children&.fetch(0, nil)
      &.children&.fetch(1, nil))
  end
  [name, type, default_value]
end
struct_definition?(node) click to toggle source
# File lib/rbi/parser.rb, line 348
def struct_definition?(node)
  (node.type == :send && node.children[0]&.type == :const && node.children[0].children[1] == :Struct) ||
    (node.type == :block && struct_definition?(node.children[0]))
end