class Sorbet::Private::GemGeneratorTracepoint::TracepointSerializer

def anonymous_id

def anonymous_id
  @prev_anonymous_id += 1
end

def class_name(klass)

def class_name(klass)
  klass = @delegate_classes[Sorbet::Private::RealStdlib.real_object_id(klass)] || klass
  name = Sorbet::Private::RealStdlib.real_name(klass) if Sorbet::Private::RealStdlib.real_is_a?(klass, Module)
  # class/module has no name; it must be anonymous
  if name.nil? || name == ""
    middle = Sorbet::Private::RealStdlib.real_is_a?(klass, Class) ? klass.superclass : klass.class
    id = @anonymous_map[Sorbet::Private::RealStdlib.real_object_id(klass)] ||= anonymous_id
    return "Anonymous_#{class_name(middle).gsub('::', '_')}_#{id}"
  end
  # if the name doesn't only contain word characters and ':', or any part doesn't start with a capital, Sorbet doesn't support it
  if name !~ /^[\w:]+$/ || !name.split('::').all? { |part| part =~ /^[A-Z]/ }
    # warn("Invalid class name: #{name}")
    id = @anonymous_map[Sorbet::Private::RealStdlib.real_object_id(klass)] ||= anonymous_id
    return "InvalidName_#{name.gsub(/[^\w]/, '_').gsub(/0x([0-9a-f]+)/, '0x00')}_#{id}"
  end
  name
end

def detect_used(gem_class_defs)

def detect_used(gem_class_defs)
  # subclassed, included, or extended
  used = {}
  gem_class_defs.each do |gem, klass_ids|
    klass_ids.each do |klass_id, class_def|
      klass = class_def.klass
      # only add an anon module if it's used as a superclass of a non-anon module, or is included/extended by a non-anon module
      used_value = Sorbet::Private::RealStdlib.real_is_a?(klass, Module) && !Sorbet::Private::RealStdlib.real_name(klass).nil? ? true : Sorbet::Private::RealStdlib.real_object_id(klass) # if non-anon, set it to true
      (used[Sorbet::Private::RealStdlib.real_object_id(klass.superclass)] ||= Set.new) << used_value if Sorbet::Private::RealStdlib.real_is_a?(klass, Class)
      # otherwise link to next anon class
      class_def.defs.each do |item|
        (used[item[item[:type]].object_id] ||= Set.new) << used_value if [:extend, :include].include?(item[:type])
      end
    end
  end
  used
end

def files_to_gem_class_defs(files)

def files_to_gem_class_defs(files)
  # Transform tracer output into hash of gems to class definitions
  files.each_with_object({}) do |(path, defined), gem_class_defs|
    gem = gem_from_location(path)
    if gem.nil?
      warn("Can't find gem for #{path}") unless path.start_with?(Dir.pwd)
      next
    end
    next if gem[:gem] == 'ruby'
    # We're currently ignoring bundler, because we can't easily pin
    # everyone to the same version of bundler in tests and in CI.
    # There is an RBI for bundler in sorbet-typed.
    next if gem[:gem] == 'bundler'
    # We ignore sorbet-runtime because because we write the RBI for it into our payload.
    # For some reason, runtime reflection generates methods with incorrect arities.
    next if gem[:gem] == 'sorbet-runtime'
    gem_class_defs[gem] ||= {}
    defined.each do |item|
      klass = item[:module]
      klass_id = Sorbet::Private::RealStdlib.real_object_id(klass)
      class_def = gem_class_defs[gem][klass_id] ||= ClassDefinition.new(klass_id, klass, [])
      class_def.defs << item unless item[:type] == :module
    end
  end
end

def filter_unused(gem_class_defs)

def filter_unused(gem_class_defs)
  used = detect_used(gem_class_defs)
  gem_class_defs.each_with_object({}) do |(gem, klass_defs), hsh|
    hsh[gem] = klass_defs.select do |klass_id, klass_def|
      klass = klass_def.klass
      # Unused anon classes
      next if !((Sorbet::Private::RealStdlib.real_is_a?(klass, Module) && !Sorbet::Private::RealStdlib.real_name(klass).nil?) || used?(klass_id, used))
      # Anon delegate classes
      next if Sorbet::Private::RealStdlib.real_is_a?(klass, Class) && klass.superclass == Delegator && !klass.name
      # TODO should this be here?
      # next if [Object, BasicObject, Hash].include?(klass)
      true
    end
  end
end

def gem_from_location(location)

def gem_from_location(location)
  match =
    location&.match(/^.*\/(?:gems\/(?:(?:j?ruby-)?[\d.]+(?:@[^\/]+)?(?:\/bundler)?\/)?|ruby\/[\d.]+\/)gems\/([^\/]+)-([^-\/]+)\//i) || # gem
    location&.match(/^.*\/(ruby)\/([\d.]+)\//) || # ruby stdlib
    location&.match(/^.*\/(jruby)-([\d.]+)\//) || # jvm ruby stdlib
    location&.match(/^.*\/(site_ruby)\/([\d.]+)\//) # rubygems
  if match.nil?
    match_via_bundler_specs(location)
  else
    {
      path: match[0],
      gem: match[1],
      version: match[2],
    }
  end
end

def generate_method(method, instance, spaces = 2)

def generate_method(method, instance, spaces = 2)
  # method.parameters is an array of:
  # a      [:req, :a]
  # b = 1  [:opt, :b]
  # c:     [:keyreq, :c]
  # d: 1   [:key, :d]
  # *e     [:rest, :e]
  # **f    [:keyrest, :f]
  # &g     [:block, :g]
  prefix = ' ' * spaces
  parameters = method.parameters.map.with_index do |(type, name), index|
    name = "arg#{index}" if name.nil? || name.empty?
    case type
    when :req
      name
    when :opt
      "#{name} = nil"
    when :keyreq
      "#{name}:"
    when :key
      "#{name}: nil"
    when :rest
      case name
      when :* then "*"
      else "*#{name}"
      end
    when :keyrest
      case name
      when :** then "**"
      else "**#{name}"
      end
    when :block
      case name
      when :& then "&"
      else "&#{name}"
      end
    else
      raise "Unknown parameter type: #{type}"
    end
  end
  parameters = parameters.join(', ')
  parameters = "(#{parameters})" unless parameters.empty?
  "#{prefix}def #{instance ? '' : 'self.'}#{method.name}#{parameters}; end"
end

def initialize(files:, delegate_classes:)

def initialize(files:, delegate_classes:)
  @files = files
  @delegate_classes = delegate_classes
  @anonymous_map = {}
  @prev_anonymous_id = 0
end

def match_via_bundler_specs(location)

def match_via_bundler_specs(location)
  @bundler_specs ||= begin
    require 'bundler'
    begin
      Bundler.load.specs.map do |spec|
        spec.load_paths.map do |path|
          [path, [spec.name, spec.version.to_s]]
        end
      end.flatten(1).to_h
    rescue Bundler::BundlerError # bail out on any bundler error
      {}
    end
  rescue LoadError # bundler can't be loaded, abort!
    {}
  end
  path_to_find = Pathname.new(location)
  parent_path, (gem_name, gem_version) = @bundler_specs.detect do |path, _gem|
    path_to_find.fnmatch?(File.join(path, '**'))
  end
  if parent_path.nil? || gem_name.nil? || gem_version.nil?
    # uncomment to generate files for methods outside of gems
    # {
    #   path: location,
    #   gem: location.gsub(/[\/\.]/, '_'),
    #   version: '1.0.0',
    # }
    nil
  else
    {
      path: location,
      gem: gem_name,
      version: gem_version,
    }
  end
end

def preprocess(files)

def preprocess(files)
  gem_class_defs = files_to_gem_class_defs(files)
  filter_unused(gem_class_defs)
end

def serialize(output_dir)

def serialize(output_dir)
  gem_class_defs = preprocess(@files)
  FileUtils.mkdir_p(output_dir) unless gem_class_defs.empty?
  gem_class_defs.each do |gem, klass_ids|
    File.open("#{File.join(output_dir, gem[:gem])}.rbi", 'w') do |f|
      f.write(HEADER)
      f.write("#
ou would like to make changes to this file, great! Please create the gem's shim here:
tps://github.com/sorbet/sorbet-typed/new/master?filename=lib/#{gem[:gem]}/all/#{gem[:gem]}.rbi
      f.write("# #{gem[:gem]}-#{gem[:version]}\n\n")
      klass_ids.each do |klass_id, class_def|
        klass = class_def.klass
        f.write("#{Sorbet::Private::RealStdlib.real_is_a?(klass, Class) ? 'class' : 'module'} #{class_name(klass)}")
        f.write(" < #{class_name(klass.superclass)}") if Sorbet::Private::RealStdlib.real_is_a?(klass, Class) && ![Object, nil].include?(klass.superclass)
        f.write("\n")
        rows = class_def.defs.map do |item|
          case item[:type]
          when :method
            if !valid_method_name?(item[:method])
              # warn("Invalid method name: #{klass}.#{item[:method]}")
              next
            end
            if BAD_METHODS.include?([gem[:gem], class_name(klass), item[:method]])
              next
            end
            begin
              method = item[:singleton] ? Sorbet::Private::RealStdlib.real_method(klass, item[:method]) : klass.instance_method(item[:method])
              "#{generate_method(method, !item[:singleton])}"
            rescue NameError
            end
          when :include, :extend
            name = class_name(item[item[:type]])
            "  #{item[:type]} #{name}"
          end
        end
        rows = rows.compact.sort
        f.write(rows.join("\n"))
        f.write("\n") if !rows.empty?
        f.write("end\n")
      end
    end
  end
end

def used?(klass, used)

def used?(klass, used)
  used_by = used[klass] || []
  used_by.any? { |user| user == true || used?(user, used) }
end

def valid_method_name?(symbol)

def valid_method_name?(symbol)
  string = symbol.to_s
  return true if SPECIAL_METHOD_NAMES.include?(string)
  string =~ /^[[:word:]]+[?!=]?$/
end