class Jazzy::SymbolGraph::Graph

A Graph is the coordinator to import a symbolgraph json file. Deserialize it to Symbols and Relationships, then rebuild the AST shape using SymNodes and ExtNodes and extract SourceKit json.

Attributes

ext_module_name[RW]
ext_nodes[RW]
module_name[RW]
relationships[RW]
symbol_nodes[RW]

Public Class Methods

new(json, module_name, ext_module_name) click to toggle source

Parse the JSON into flat tables of data

# File lib/jazzy/symbol_graph/graph.rb, line 17
def initialize(json, module_name, ext_module_name)
  self.module_name = module_name
  self.ext_module_name = ext_module_name
  graph = JSON.parse(json, symbolize_names: true)

  self.symbol_nodes = {}
  self.ext_nodes = {}

  graph[:symbols].each do |hash|
    symbol = Symbol.new(hash)
    if symbol.extension?
      node = ExtSymNode.new(symbol)
      ext_nodes[node.ext_key] = node
    else
      symbol_nodes[symbol.usr] = SymNode.new(symbol)
    end
  end

  self.relationships =
    graph[:relationships].map { |hash| Relationship.new(hash) }
end

Public Instance Methods

add_ext_conformance(type_usr, type_name, protocol, constraints) click to toggle source
# File lib/jazzy/symbol_graph/graph.rb, line 52
def add_ext_conformance(type_usr,
                        type_name,
                        protocol,
                        constraints)
  key = ExtKey.new(type_usr, constraints.ext)
  if ext_node = ext_nodes[key]
    ext_node.add_conformance(protocol)
  else
    ext_nodes[key] =
      ExtNode.new_for_conformance(type_usr,
                                  type_name,
                                  protocol,
                                  constraints)
  end
end
add_ext_member(type_usr, member_node, constraints) click to toggle source

ExtNode index. ExtKey (type USR, extension constraints) -> ExtNode. This minimizes the number of extensions

# File lib/jazzy/symbol_graph/graph.rb, line 42
def add_ext_member(type_usr, member_node, constraints)
  key = ExtKey.new(type_usr, constraints.ext)
  if ext_node = ext_nodes[key]
    ext_node.add_child(member_node)
  else
    ext_nodes[key] =
      ExtNode.new_for_member(type_usr, member_node, constraints)
  end
end
rebuild_conformance(rel, source, target) click to toggle source

“source : target” either from type decl or ext decl

# File lib/jazzy/symbol_graph/graph.rb, line 109
def rebuild_conformance(rel, source, target)
  protocol_name = rel_target_name(rel, target)

  return if redundant_conformance?(rel, source, protocol_name)

  type_constraints = source&.constraints || []
  constraints =
    ExtConstraints.new(type_constraints,
                       rel.constraints - type_constraints)

  # Create an extension or enhance an existing one
  add_ext_conformance(rel.source_usr,
                      rel_source_name(rel, source),
                      protocol_name,
                      constraints)
end
rebuild_default_implementation(_rel, source, target) click to toggle source

“source is a default implementation of protocol requirement target”

# File lib/jazzy/symbol_graph/graph.rb, line 127
def rebuild_default_implementation(_rel, source, target)
  return unless source

  unless target &&
         (target_parent = target.parent) &&
         target_parent.is_a?(SymNode)
    # Could probably figure this out with demangle, but...
    warn "Can't resolve membership of default implementation " \
      "#{source.symbol.usr}."
    source.unlisted = true
    return
  end
  constraints =
    ExtConstraints.new(target_parent.constraints,
                       source.unique_context_constraints(target_parent))

  add_ext_member(target_parent.symbol.usr,
                 source,
                 constraints)
end
rebuild_inherits(_rel, source, target) click to toggle source

“source is a class that inherits from target”

# File lib/jazzy/symbol_graph/graph.rb, line 149
def rebuild_inherits(_rel, source, target)
  if source && target
    source.superclass_name = target.symbol.name
  end
end
rebuild_member(rel, source, target) click to toggle source

source is a member/protocol requirement of target

# File lib/jazzy/symbol_graph/graph.rb, line 94
def rebuild_member(rel, source, target)
  return unless source

  source.protocol_requirement = rel.protocol_requirement?
  constraints =
    ExtConstraints.new(target&.constraints,
                       source.unique_context_constraints(target))

  # Add to its parent or invent an extension
  unless target&.try_add_child(source, constraints.ext)
    add_ext_member(rel.target_usr, source, constraints)
  end
end
rebuild_rel(rel) click to toggle source

Process a structural relationship to link nodes

# File lib/jazzy/symbol_graph/graph.rb, line 165
def rebuild_rel(rel)
  source = symbol_nodes[rel.source_usr]
  target = symbol_nodes[rel.target_usr]

  case rel.kind
  when :memberOf, :optionalRequirementOf, :requirementOf
    rebuild_member(rel, source, target)

  when :conformsTo
    rebuild_conformance(rel, source, target)

  when :defaultImplementationOf
    rebuild_default_implementation(rel, source, target)

  when :inheritsFrom
    rebuild_inherits(rel, source, target)

  when :extensionTo
    unalias_extensions(rel.source_usr, rel.target_usr)
  end

  # don't seem to care about:
  # - overrides: not bothered, also unimplemented for protocols
end
redundant_conformance?(rel, type, protocol) click to toggle source

Protocol conformance is redundant if it’s unconditional and already expressed in the type’s declaration.

Skip implementation-detail conformances.

# File lib/jazzy/symbol_graph/graph.rb, line 86
def redundant_conformance?(rel, type, protocol)
  return false unless type

  (rel.constraints.empty? && type.conformance?(protocol)) ||
    (type.actor? && rel.actor_protocol?)
end
rel_source_name(rel, source_node) click to toggle source

Same for the source end. Less help from the tool here

# File lib/jazzy/symbol_graph/graph.rb, line 77
def rel_source_name(rel, source_node)
  source_node&.qualified_name ||
    Jazzy::SymbolGraph.demangle(rel.source_usr)
end
rel_target_name(rel, target_node) click to toggle source

Increasingly desparate ways to find the name of the symbol at the target end of a relationship

# File lib/jazzy/symbol_graph/graph.rb, line 70
def rel_target_name(rel, target_node)
  target_node&.symbol&.name ||
    rel.target_fallback ||
    Jazzy::SymbolGraph.demangle(rel.target_usr)
end
to_sourcekit() click to toggle source

Rebuild the AST structure and convert to SourceKit

# File lib/jazzy/symbol_graph/graph.rb, line 191
def to_sourcekit
  relationships.sort.each { |r| rebuild_rel(r) }

  root_symbol_nodes =
    symbol_nodes.values
      .select(&:top_level_decl?)
      .sort
      .map { |n| n.to_sourcekit(module_name) }

  root_ext_nodes =
    ext_nodes.values
      .sort
      .map { |n| n.to_sourcekit(module_name, ext_module_name) }
  {
    'key.diagnostic_stage' => 'parse',
    'key.substructure' => root_symbol_nodes + root_ext_nodes,
  }
end
unalias_extensions(fake_usr, real_usr) click to toggle source

“References to fake_usr should be real_usr”

# File lib/jazzy/symbol_graph/graph.rb, line 156
def unalias_extensions(fake_usr, real_usr)
  ext_nodes.each_pair do |key, ext|
    if key.usr == fake_usr
      ext.real_usr = real_usr
    end
  end
end