Class: Jazzy::SymbolGraph::Graph

Inherits:
Object
  • Object
show all
Defined in:
lib/jazzy/symbol_graph/graph.rb

Overview

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.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(json, module_name, ext_module_name) ⇒ Graph

Parse the JSON into flat tables of data



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 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

Instance Attribute Details

#ext_module_nameObject

Module being extended



11
12
13
# File 'lib/jazzy/symbol_graph/graph.rb', line 11

def ext_module_name
  @ext_module_name
end

#ext_nodesObject

(usr, constraints) -> ExtNode



14
15
16
# File 'lib/jazzy/symbol_graph/graph.rb', line 14

def ext_nodes
  @ext_nodes
end

#module_nameObject

Our module



10
11
12
# File 'lib/jazzy/symbol_graph/graph.rb', line 10

def module_name
  @module_name
end

#relationshipsObject

Relationship


13
14
15
# File 'lib/jazzy/symbol_graph/graph.rb', line 13

def relationships
  @relationships
end

#symbol_nodesObject

usr -> SymNode



12
13
14
# File 'lib/jazzy/symbol_graph/graph.rb', line 12

def symbol_nodes
  @symbol_nodes
end

Instance Method Details

#add_ext_conformance(type_usr, type_name, protocol, constraints) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 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) ⇒ Object

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



42
43
44
45
46
47
48
49
50
# 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) ⇒ Object

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



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# 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) ⇒ Object

“source is a default implementation of protocol requirement target”



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# 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) ⇒ Object

“source is a class that inherits from target”



149
150
151
152
153
# 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) ⇒ Object

source is a member/protocol requirement of target



94
95
96
97
98
99
100
101
102
103
104
105
106
# 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) ⇒ Object

Process a structural relationship to link nodes



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# 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) ⇒ Boolean

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

Skip implementation-detail conformances.

Returns:

  • (Boolean)


86
87
88
89
90
91
# 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) ⇒ Object

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



77
78
79
80
# 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) ⇒ Object

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



70
71
72
73
74
# 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_sourcekitObject

Rebuild the AST structure and convert to SourceKit



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# 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) ⇒ Object

“References to fake_usr should be real_usr”



156
157
158
159
160
161
162
# 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