class TypeProf::Core::Service

Attributes

genv[R]

Public Class Methods

new(options) click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 3
def initialize(options)
  @options = options

  @rb_text_nodes = {}
  @rbs_text_nodes = {}

  @genv = GlobalEnv.new
  @genv.load_core_rbs(load_rbs_declarations(@options[:rbs_collection]).declarations)

  Builtin.new(genv).deploy
end

Public Instance Methods

add_workspace(rb_folder, rbs_folder) click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 40
def add_workspace(rb_folder, rbs_folder)
  Dir.glob(File.expand_path(rb_folder + "/**/*.{rb,rbs}")) do |path|
    update_file(path, nil)
  end
end
batch(files, output) click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 467
def batch(files, output)
  if @options[:output_typeprof_version]
    output.puts "# TypeProf #{ TypeProf::VERSION }"
    output.puts
  end

  i = 0
  show_files = files.select do |file|
    if @options[:display_indicator]
      $stderr << "\r[%d/%d] %s\e[K" % [i, files.size, file]
      i += 1
    end

    res = update_file(file, File.read(file))

    if res
      true
    else
      output.puts "# failed to analyze: #{ file }"
      false
    end
  end
  if @options[:display_indicator]
    $stderr << "\r\e[K"
  end

  first = true
  show_files.each do |file|
    next if File.extname(file) == ".rbs"
    output.puts unless first
    first = false
    output.puts "# #{ file }"
    if @options[:output_diagnostics]
      diagnostics(file) do |diag|
        output.puts "# #{ diag.code_range.to_s }:#{ diag.msg }"
      end
    end
    output.puts dump_declarations(file)
  end
end
code_lens(path) { |node.code_range, hint| ... } click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 329
def code_lens(path)
  cpaths = []
  @rb_text_nodes[path]&.traverse do |event, node|
    if node.is_a?(AST::ModuleBaseNode)
      if node.static_cpath
        if event == :enter
          cpaths << node.static_cpath
        else
          cpaths.pop
        end
      end
    else
      if event == :enter
        next if node.is_a?(AST::DefNode) && node.rbs_method_type
        node.boxes(:mdef) do |mdef|
          hint = mdef.show(@options[:output_parameter_names])
          if hint
            yield mdef.node.code_range, hint
          end
        end
      end
    end
  end
end
completion(path, trigger, pos) { |mid, "#{ cpath.join("::" )}#{ singleton ? "." : "#" }#{ mid } : #{ sig }"| ... } click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 354
def completion(path, trigger, pos)
  @rb_text_nodes[path]&.retrieve_at(pos) do |node|
    if node.code_range.last == pos.right
      node.ret.types.map do |ty, _source|
        base_ty = ty.base_type(genv)

        @genv.each_superclass(base_ty.mod, base_ty.is_a?(Type::Singleton)) do |mod, singleton|
          mod.methods[singleton].each do |mid, me|
            sig = nil
            me.decls.each do |mdecl|
              sig = mdecl.method_types.map {|method_type| method_type.instance_variable_get(:@raw_node).to_s }.join(" | ")
              break
            end
            unless sig
              me.defs.each do |mdef|
                sig = mdef.show(@options[:output_parameter_names])
                break
              end
            end
            yield mid, "#{ mod.cpath.join("::" )}#{ singleton ? "." : "#" }#{ mid } : #{ sig }" if sig
          end
        end
      end
      return
    end
  end
end
definitions(path, pos) click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 142
def definitions(path, pos)
  defs = []
  @rb_text_nodes[path]&.retrieve_at(pos) do |node|
    node.boxes(:cread) do |box|
      if box.const_read && box.const_read.cdef
        box.const_read.cdef.defs.each do |cdef_node|
          defs << [cdef_node.lenv.path, cdef_node.cname_code_range]
        end
      end
    end
    node.boxes(:mcall) do |box|
      boxes = []
      box.changes.boxes.each do |key, box|
        # ad-hocly handle Class#new calls
        if key[0] == :mcall && key[3] == :initialize # XXX: better condition?
          boxes << box
        end
      end
      boxes << box if boxes.empty?
      boxes.each do |box|
        box.resolve(genv, nil) do |me, _ty, mid, _orig_ty|
          next unless me
          me.defs.each do |mdef|
            code_range =
              if mdef.node.respond_to?(:mname_code_range)
                mdef.node.mname_code_range(mid)
              else
                mdef.node.code_range
              end

            defs << [mdef.node.lenv.path, code_range]
          end
        end
      end
    end
    return defs unless defs.empty?
  end
  return defs
end
diagnostics(path, &blk) click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 138
def diagnostics(path, &blk)
  @rb_text_nodes[path]&.diagnostics(@genv, &blk)
end
dump_declarations(path) click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 382
def dump_declarations(path)
  stack = []
  out = []
  @rb_text_nodes[path]&.traverse do |event, node|
    case node
    when AST::ModuleNode
      if node.static_cpath
        if event == :enter
          out << "  " * stack.size + "module #{ node.static_cpath.join("::") }"
          if stack == [:toplevel]
            out << "end"
            stack.pop
          end
          stack.push(node)
        else
          stack.pop
          out << "  " * stack.size + "end"
        end
      end
    when AST::ClassNode, AST::SingletonClassNode
      if node.static_cpath
        next if stack.any? { node.is_a?(AST::SingletonClassNode) && (_1.is_a?(AST::ClassNode) || _1.is_a?(AST::ModuleNode)) && node.static_cpath == _1.static_cpath }

        if event == :enter
          s = "class #{ node.static_cpath.join("::") }"
          mod = @genv.resolve_cpath(node.static_cpath)
          superclass = mod.superclass
          if superclass == nil
            s << " # failed to identify its superclass"
          elsif superclass.cpath != []
            s << " < #{ superclass.show_cpath }"
          end
          if stack == [:toplevel]
            out << "end"
            stack.pop
          end
          out << "  " * stack.size + s
          stack.push(node)
          mod.included_modules.each do |inc_def, inc_mod|
            if inc_def.is_a?(AST::ConstantReadNode) && inc_def.lenv.path == path
              out << "  " * stack.size + "include #{ inc_mod.show_cpath }"
            end
          end
        else
          stack.pop
          out << "  " * stack.size + "end"
        end
      end
    when AST::ConstantWriteNode
      if node.static_cpath
        if event == :enter
          out << "  " * stack.size + "#{ node.static_cpath.join("::") }: #{ node.ret.show }"
        end
      end
    else
      if event == :enter
        node.boxes(:mdef) do |mdef|
          if stack.empty?
            out << "  " * stack.size + "class Object"
            stack << :toplevel
          end
          if @options[:output_source_locations]
            pos = mdef.node.code_range.first
            out << "  " * stack.size + "# #{ path }:#{ pos.lineno }:#{ pos.column + 1 }"
          end
          out << "  " * stack.size + "def #{ mdef.singleton ? "self." : "" }#{ mdef.mid }: " + mdef.show(@options[:output_parameter_names])
        end
      end
    end
  end
  if stack == [:toplevel]
    out << "end"
    stack.pop
  end
  out.join("\n") + "\n"
end
get_method_sig(cpath, singleton, mid) click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 459
def get_method_sig(cpath, singleton, mid)
  s = []
  @genv.resolve_method(cpath, singleton, mid).defs.each do |mdef|
    s << "def #{ mid }: " + mdef.show
  end
  s
end
hover(path, pos) click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 296
def hover(path, pos)
  @rb_text_nodes[path]&.retrieve_at(pos) do |node|
    node.boxes(:mcall) do |box|
      boxes = []
      box.changes.boxes.each do |key, box|
        # ad-hocly handle Class#new calls
        if key[0] == :mcall && key[3] == :initialize # XXX: better condition?
          boxes << box
        end
      end
      boxes << box if boxes.empty?
      boxes.each do |box|
        box.resolve(genv, nil) do |me, ty, mid, orig_ty|
          if me
            if !me.decls.empty?
              me.decls.each do |mdecl|
                return "#{ orig_ty.show }##{ mid } : #{ mdecl.show }"
              end
            end
            if !me.defs.empty?
              me.defs.each do |mdef|
                return "#{ orig_ty.show }##{ mid } : #{ mdef.show(@options[:output_parameter_names]) }"
              end
            end
          end
        end
      end
      return "??? failed to hover"
    end
    return node.ret ? node.ret.show : "??? no type ???"
  end
end
load_rbs_declarations(rbs_collection) click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 15
def load_rbs_declarations(rbs_collection)
  if rbs_collection
    loader = RBS::EnvironmentLoader.new
    loader.add_collection(rbs_collection)
    RBS::Environment.from_loader(loader)
  else
    return $raw_rbs_env if defined?($raw_rbs_env)
    loader = RBS::EnvironmentLoader.new
    $raw_rbs_env = RBS::Environment.from_loader(loader)
  end
end
references(path, pos) click to toggle source

: (String, TypeProf::CodePosition) -> Array[[String?, TypeProf::CodeRange]]?

# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 203
def references(path, pos)
  refs = []
  @rb_text_nodes[path]&.retrieve_at(pos) do |node|
    case node
    when AST::DefNode
      if node.mid_code_range.include?(pos)
        node.boxes(:mdef) do |mdef|
          me = @genv.resolve_method(mdef.cpath, mdef.singleton, mdef.mid)
          if me
            me.method_call_boxes.each do |box|
              node = box.node
              refs << [node.lenv.path, node.code_range]
            end
          end
        end
      end
    # TODO: Callsite
    when AST::ConstantReadNode
      if node.cname_code_range.include?(pos)
        node.boxes(:cread) do |cread_box|
          @genv.resolve_const(cread_box.const_read.cpath).read_boxes.each do |box|
            node = box.node
            refs << [node.lenv.path, node.code_range]
          end
        end
      end
    when AST::ConstantWriteNode
      if node.cname_code_range && node.cname_code_range.include?(pos) && node.static_cpath
        @genv.resolve_const(node.static_cpath).read_boxes.each do |box|
          node = box.node
          refs << [node.lenv.path, node.code_range]
        end
      end
    end
  end
  refs = refs.uniq
  return refs.empty? ? nil : refs
end
rename(path, pos) click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 242
def rename(path, pos)
  mdefs = []
  cdefs = []
  @rb_text_nodes[path]&.retrieve_at(pos) do |node|
    node.boxes(:mcall) do |box|
      box.resolve(genv, nil) do |me, _ty, _mid, _orig_ty|
        next unless me
        me.defs.each do |mdef|
          mdefs << mdef
        end
      end
    end
    node.boxes(:cread) do |box|
      if box.node.cname_code_range.include?(pos)
        box.const_read.cdef.defs.each do |cdef|
          cdefs << cdef
        end
      end
    end
    if node.is_a?(AST::DefNode) && node.mid_code_range.include?(pos)
      node.boxes(:mdef) do |mdef|
        mdefs << mdef
      end
    end
  end
  targets = []
  mdefs.each do |mdef|
    # TODO: support all method definition nodes rather than defn/defs (e.g., attr_reader, alias, SIG_DEF, etc.)
    targets << [mdef.node.lenv.path, mdef.node.mid_code_range]
    me = @genv.resolve_method(mdef.cpath, mdef.singleton, mdef.mid)
    if me
      me.method_call_boxes.each do |box|
        # TODO: if it is a super node, we need to change its method name too
        targets << [box.node.lenv.path, box.node.mid_code_range]
      end
    end
  end
  cdefs.each do |cdef|
    if cdef.is_a?(AST::ConstantWriteNode)
      targets << [cdef.lenv.path, cdef.cname_code_range] if cdef.cname_code_range
    end
    ve = @genv.resolve_const(cdef.static_cpath)
    ve.read_boxes.each do |box|
      targets << [box.node.lenv.path, box.node.cname_code_range]
    end
  end
  if targets.all? {|_path, cr| cr }
    targets.uniq
  else
    # TODO: report an error
    nil
  end
end
reset!() click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 29
def reset!
  @rb_text_nodes.each_value {|node| node.undefine(@genv) }
  @rbs_text_nodes.each_value {|nodes| nodes.each {|n| n.undefine(@genv) } }
  @genv.define_all
  @rb_text_nodes.each_value {|node| node.uninstall(@genv) }
  @rbs_text_nodes.each_value {|nodes| nodes.each {|n| n.uninstall(@genv) } }
  @genv.run_all
  @rb_text_nodes.clear
  @rbs_text_nodes.clear
end
type_definitions(path, pos) click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 182
def type_definitions(path, pos)
  @rb_text_nodes[path]&.retrieve_at(pos) do |node|
    if node.ret
      ty_defs = []
      node.ret.types.map do |ty, _source|
        if ty.is_a?(Type::Instance)
          ty.mod.module_decls.each do |mdecl|
            # TODO
          end
          ty.mod.module_defs.each do |mdef_node|
            ty_defs << [mdef_node.lenv.path, mdef_node.code_range]
          end
        end
      end
      return ty_defs
    end
  end
  []
end
update_file(path, code) click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 46
def update_file(path, code)
  if File.extname(path) == ".rbs"
    update_rbs_file(path, code)
  else
    update_rb_file(path, code)
  end
end
update_rb_file(path, code) click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 54
def update_rb_file(path, code)
  prev_node = @rb_text_nodes[path]

  code = File.read(path) unless code
  node = AST.parse_rb(path, code)
  return false unless node

  node.diff(@rb_text_nodes[path]) if prev_node
  @rb_text_nodes[path] = node

  node.define(@genv)
  prev_node.undefine(@genv) if prev_node
  @genv.define_all

  node.install(@genv)
  prev_node.uninstall(@genv) if prev_node
  @genv.run_all

  # invariant validation
  if prev_node
    live_vtxs = []
    node.get_vertexes(live_vtxs)
    set = Set[]
    live_vtxs.uniq.each {|vtx| set << vtx }
    live_vtxs = set

    dead_vtxs = []
    prev_node.get_vertexes(dead_vtxs)
    set = Set[]
    dead_vtxs.uniq.each {|vtx| set << vtx }
    dead_vtxs = set

    live_vtxs.each do |vtx|
      next unless vtx
      raise vtx.inspect if dead_vtxs.include?(vtx)
    end

    global_vtxs = []
    @genv.get_vertexes(global_vtxs)
    set = Set[]
    global_vtxs.uniq.each {|vtx| set << vtx }
    global_vtxs = set

    global_vtxs.each do |global_vtx|
      next unless global_vtx.is_a?(Vertex)
      raise if dead_vtxs.include?(global_vtx)
      global_vtx.types.each_value do |prev_vtxs|
        prev_vtxs.each do |prev_vtx|
          raise if dead_vtxs.include?(prev_vtx)
        end
      end
      global_vtx.next_vtxs.each do |next_vtx|
        raise "#{ next_vtx }" if dead_vtxs.include?(next_vtx)
      end
    end
  end

  return true
end
update_rbs_file(path, code) click to toggle source
# File typeprof-0.30.1/lib/typeprof/core/service.rb, line 114
def update_rbs_file(path, code)
  prev_decls = @rbs_text_nodes[path]

  code = File.read(path) unless code
  begin
    decls = AST.parse_rbs(path, code)
  rescue RBS::ParsingError
    return false
  end

  # TODO: diff
  @rbs_text_nodes[path] = decls

  decls.each {|decl| decl.define(@genv) }
  prev_decls.each {|decl| decl.undefine(@genv) } if prev_decls
  @genv.define_all

  decls.each {|decl| decl.install(@genv) }
  prev_decls.each {|decl| decl.uninstall(@genv) } if prev_decls
  @genv.run_all

  true
end