jazzy 0.13.4 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/Tests.yml +52 -0
  3. data/.rubocop.yml +123 -24
  4. data/CHANGELOG.md +103 -0
  5. data/CONTRIBUTING.md +5 -5
  6. data/Dangerfile +11 -8
  7. data/Gemfile +3 -1
  8. data/Gemfile.lock +91 -69
  9. data/README.md +85 -13
  10. data/Rakefile +19 -13
  11. data/bin/jazzy +3 -2
  12. data/bin/sourcekitten +0 -0
  13. data/jazzy.gemspec +8 -6
  14. data/js/package-lock.json +30 -25
  15. data/js/package.json +3 -3
  16. data/lib/jazzy.rb +2 -0
  17. data/lib/jazzy/config.rb +124 -70
  18. data/lib/jazzy/doc.rb +3 -1
  19. data/lib/jazzy/doc_builder.rb +75 -81
  20. data/lib/jazzy/docset_builder.rb +3 -1
  21. data/lib/jazzy/documentation_generator.rb +6 -2
  22. data/lib/jazzy/executable.rb +3 -0
  23. data/lib/jazzy/extensions/bitbucket/img/bitbucket.svg +11 -0
  24. data/lib/jazzy/{themes/apple/assets → extensions/github}/img/gh.png +0 -0
  25. data/lib/jazzy/extensions/gitlab/img/gitlab.svg +23 -0
  26. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_AMS-Regular.ttf +0 -0
  27. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_AMS-Regular.woff +0 -0
  28. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  29. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  30. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  31. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  32. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  33. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  34. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  35. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  36. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  37. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  38. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  39. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  40. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  41. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Bold.ttf +0 -0
  42. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Bold.woff +0 -0
  43. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Bold.woff2 +0 -0
  44. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  45. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  46. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  47. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Italic.ttf +0 -0
  48. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Italic.woff +0 -0
  49. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Italic.woff2 +0 -0
  50. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Regular.ttf +0 -0
  51. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Regular.woff +0 -0
  52. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Regular.woff2 +0 -0
  53. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  54. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  55. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  56. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-Italic.ttf +0 -0
  57. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-Italic.woff +0 -0
  58. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-Italic.woff2 +0 -0
  59. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  60. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  61. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  62. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  63. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  64. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  65. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  66. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  67. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  68. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Script-Regular.ttf +0 -0
  69. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Script-Regular.woff +0 -0
  70. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Script-Regular.woff2 +0 -0
  71. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size1-Regular.ttf +0 -0
  72. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size1-Regular.woff +0 -0
  73. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  74. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size2-Regular.ttf +0 -0
  75. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size2-Regular.woff +0 -0
  76. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  77. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size3-Regular.ttf +0 -0
  78. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size3-Regular.woff +0 -0
  79. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  80. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size4-Regular.ttf +0 -0
  81. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size4-Regular.woff +0 -0
  82. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  83. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  84. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  85. data/lib/jazzy/extensions/katex/css/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  86. data/lib/jazzy/extensions/katex/css/katex.min.css +1 -1
  87. data/lib/jazzy/extensions/katex/js/katex.min.js +1 -1
  88. data/lib/jazzy/gem_version.rb +3 -1
  89. data/lib/jazzy/highlighter.rb +5 -3
  90. data/lib/jazzy/jazzy_markdown.rb +101 -30
  91. data/lib/jazzy/podspec_documenter.rb +14 -16
  92. data/lib/jazzy/search_builder.rb +4 -3
  93. data/lib/jazzy/source_declaration.rb +24 -5
  94. data/lib/jazzy/source_declaration/access_control_level.rb +7 -5
  95. data/lib/jazzy/source_declaration/type.rb +33 -1
  96. data/lib/jazzy/source_document.rb +8 -5
  97. data/lib/jazzy/source_host.rb +111 -0
  98. data/lib/jazzy/source_mark.rb +8 -6
  99. data/lib/jazzy/source_module.rb +6 -6
  100. data/lib/jazzy/sourcekitten.rb +105 -79
  101. data/lib/jazzy/stats.rb +4 -2
  102. data/lib/jazzy/symbol_graph.rb +95 -0
  103. data/lib/jazzy/symbol_graph/constraint.rb +98 -0
  104. data/lib/jazzy/symbol_graph/ext_node.rb +116 -0
  105. data/lib/jazzy/symbol_graph/graph.rb +195 -0
  106. data/lib/jazzy/symbol_graph/relationship.rb +42 -0
  107. data/lib/jazzy/symbol_graph/sym_node.rb +163 -0
  108. data/lib/jazzy/symbol_graph/symbol.rb +222 -0
  109. data/lib/jazzy/themes/apple/assets/css/highlight.css.scss +63 -59
  110. data/lib/jazzy/themes/apple/assets/css/jazzy.css.scss +101 -4
  111. data/lib/jazzy/themes/apple/assets/img/spinner.gif +0 -0
  112. data/lib/jazzy/themes/apple/assets/js/jazzy.js +4 -0
  113. data/lib/jazzy/themes/apple/assets/js/jazzy.search.js +74 -0
  114. data/lib/jazzy/themes/apple/assets/js/jquery.min.js +2 -2
  115. data/lib/jazzy/themes/apple/assets/js/lunr.min.js +6 -0
  116. data/lib/jazzy/themes/apple/assets/js/typeahead.jquery.js +1694 -0
  117. data/lib/jazzy/themes/apple/templates/doc.mustache +35 -0
  118. data/lib/jazzy/themes/apple/templates/footer.mustache +1 -1
  119. data/lib/jazzy/themes/apple/templates/header.mustache +10 -3
  120. data/lib/jazzy/themes/apple/templates/task.mustache +4 -4
  121. data/lib/jazzy/themes/fullwidth/assets/css/highlight.css.scss +63 -59
  122. data/lib/jazzy/themes/fullwidth/assets/css/jazzy.css.scss +11 -2
  123. data/lib/jazzy/themes/fullwidth/assets/js/jazzy.js +4 -0
  124. data/lib/jazzy/themes/fullwidth/assets/js/jazzy.search.js +4 -0
  125. data/lib/jazzy/themes/fullwidth/assets/js/jquery.min.js +2 -2
  126. data/lib/jazzy/themes/fullwidth/assets/js/lunr.min.js +3 -3
  127. data/lib/jazzy/themes/fullwidth/templates/doc.mustache +30 -0
  128. data/lib/jazzy/themes/fullwidth/templates/footer.mustache +1 -1
  129. data/lib/jazzy/themes/fullwidth/templates/header.mustache +5 -5
  130. data/lib/jazzy/themes/fullwidth/templates/task.mustache +4 -4
  131. data/lib/jazzy/themes/jony/assets/css/highlight.css.scss +63 -59
  132. data/lib/jazzy/themes/jony/assets/css/jazzy.css.scss +34 -2
  133. data/lib/jazzy/themes/jony/assets/js/jazzy.js +4 -0
  134. data/lib/jazzy/themes/jony/assets/js/jquery.min.js +2 -2
  135. data/lib/jazzy/themes/jony/templates/doc.mustache +30 -0
  136. data/lib/jazzy/themes/jony/templates/footer.mustache +1 -1
  137. data/lib/jazzy/themes/jony/templates/header.mustache +5 -5
  138. data/lib/jazzy/themes/jony/templates/task.mustache +4 -4
  139. data/spec/integration_spec.rb +54 -42
  140. data/spec/spec_helper.rb +3 -1
  141. data/spec/spec_helper/pre_flight.rb +2 -0
  142. metadata +43 -19
  143. data/.circleci/config.yml +0 -83
  144. data/lib/jazzy/themes/fullwidth/assets/img/gh.png +0 -0
  145. data/lib/jazzy/themes/jony/assets/img/gh.png +0 -0
  146. data/spec/sourcekitten_spec.rb +0 -6
data/lib/jazzy/stats.rb CHANGED
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jazzy
2
4
  # Collect + report metadata about a processed module
3
5
  class Stats
4
6
  include Config::Mixin
5
7
 
6
- attr_reader :documented, :acl_skipped
7
- attr_reader :undocumented_decls
8
+ attr_reader :documented, :acl_skipped, :undocumented_decls
8
9
 
9
10
  def add_documented
10
11
  @documented += 1
@@ -57,6 +58,7 @@ module Jazzy
57
58
 
58
59
  def doc_coverage
59
60
  return 0 if acl_included == 0
61
+
60
62
  (100 * documented) / acl_included
61
63
  end
62
64
 
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require 'jazzy/symbol_graph/graph'
5
+ require 'jazzy/symbol_graph/constraint'
6
+ require 'jazzy/symbol_graph/symbol'
7
+ require 'jazzy/symbol_graph/relationship'
8
+ require 'jazzy/symbol_graph/sym_node'
9
+ require 'jazzy/symbol_graph/ext_node'
10
+
11
+ # This is the top-level symbolgraph driver that deals with
12
+ # figuring out arguments, running the tool, and loading the
13
+ # results.
14
+
15
+ module Jazzy
16
+ module SymbolGraph
17
+ # Run `swift symbolgraph-extract` with configured args,
18
+ # parse the results, and return as JSON in SourceKit[ten]
19
+ # format.
20
+ def self.build(config)
21
+ Dir.mktmpdir do |tmp_dir|
22
+ args = arguments(config, tmp_dir)
23
+
24
+ Executable.execute_command('swift',
25
+ args.unshift('symbolgraph-extract'),
26
+ true) # raise on error
27
+
28
+ Dir[tmp_dir + '/*.symbols.json'].map do |filename|
29
+ # The @ part is for extensions in our module (before the @)
30
+ # of types in another module (after the @).
31
+ filename =~ /(.*?)(@(.*?))?\.symbols/
32
+ module_name = Regexp.last_match[3] || Regexp.last_match[1]
33
+ {
34
+ filename =>
35
+ Graph.new(File.read(filename), module_name).to_sourcekit,
36
+ }
37
+ end.to_json
38
+ end
39
+ end
40
+
41
+ # Figure out the args to pass to symbolgraph-extract
42
+ def self.arguments(config, output_path)
43
+ if config.module_name.empty?
44
+ raise 'error: `--swift-build-tool symbolgraph` requires `--module`.'
45
+ end
46
+
47
+ user_args = config.build_tool_arguments.join
48
+
49
+ if user_args =~ /-(?:module-name|minimum-access-level|output-dir)/
50
+ raise 'error: `--build-tool-arguments` for '\
51
+ "`--swift-build-tool symbolgraph` can't use `-module`, "\
52
+ '`-minimum-access-level`, or `-output-dir`.'
53
+ end
54
+
55
+ # Default set
56
+ args = [
57
+ '-module-name', config.module_name,
58
+ '-minimum-access-level', 'private',
59
+ '-output-dir', output_path,
60
+ '-skip-synthesized-members'
61
+ ]
62
+
63
+ # Things user can override
64
+ args += ['-sdk', sdk(config)] unless user_args =~ /-sdk/
65
+ args += ['-target', target] unless user_args =~ /-target/
66
+ args += ['-F', config.source_directory.to_s] unless user_args =~ /-F(?!s)/
67
+ args += ['-I', config.source_directory.to_s] unless user_args =~ /-I/
68
+
69
+ args + config.build_tool_arguments
70
+ end
71
+
72
+ # Get the SDK path. On !darwin this just isn't needed.
73
+ def self.sdk(config)
74
+ `xcrun --show-sdk-path --sdk #{config.sdk}`.chomp
75
+ end
76
+
77
+ # Guess a default LLVM target. Feels like the tool should figure this
78
+ # out from sdk + the binary somehow?
79
+ def self.target
80
+ `swift -version` =~ /Target: (.*?)$/
81
+ Regexp.last_match[1] || 'x86_64-apple-macosx10.15'
82
+ end
83
+
84
+ # This is a last-ditch fallback for when symbolgraph doesn't
85
+ # provide a name - at least conforming external types to local
86
+ # protocols.
87
+ def self.demangle(usr)
88
+ args = %w[demangle -simplified -compact].append(usr.sub(/^s:/, 's'))
89
+ output, = Executable.execute_command('swift', args, true)
90
+ output.chomp
91
+ rescue StandardError
92
+ usr
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jazzy
4
+ module SymbolGraph
5
+ # Constraint is a tidied-up JSON object, used by both Symbol and
6
+ # Relationship, and key to reconstructing extensions.
7
+ class Constraint
8
+ attr_accessor :kind
9
+ attr_accessor :lhs
10
+ attr_accessor :rhs
11
+
12
+ private
13
+
14
+ def initialize(kind, lhs, rhs)
15
+ self.kind = kind # "==" or ":"
16
+ self.lhs = lhs
17
+ self.rhs = rhs
18
+ end
19
+
20
+ public
21
+
22
+ KIND_MAP = {
23
+ 'conformance' => ':',
24
+ 'superclass' => ':',
25
+ 'sameType' => '==',
26
+ }.freeze
27
+
28
+ # Init from a JSON hash
29
+ def self.new_hash(hash)
30
+ kind = KIND_MAP[hash[:kind]]
31
+ raise "Unknown constraint kind '#{kind}'" unless kind
32
+
33
+ lhs = hash[:lhs].sub(/^Self\./, '')
34
+ rhs = hash[:rhs].sub(/^Self\./, '')
35
+ new(kind, lhs, rhs)
36
+ end
37
+
38
+ # Init from a Swift declaration fragment eg. 'A : B'
39
+ def self.new_declaration(decl)
40
+ decl =~ /^(.*?)\s*([:<=]+)\s*(.*)$/
41
+ new(Regexp.last_match[2],
42
+ Regexp.last_match[1],
43
+ Regexp.last_match[3])
44
+ end
45
+
46
+ def to_swift
47
+ "#{lhs} #{kind} #{rhs}"
48
+ end
49
+
50
+ # The first component of types in the constraint
51
+ def type_names
52
+ Set.new([lhs, rhs].map { |n| n.sub(/\..*$/, '') })
53
+ end
54
+
55
+ def self.new_list(hash_list)
56
+ hash_list.map { |h| Constraint.new_hash(h) }.sort.uniq
57
+ end
58
+
59
+ # Swift protocols and reqs have an implementation/hidden conformance
60
+ # to their own protocol: we don't want to think about this in docs.
61
+ def self.new_list_for_symbol(hash_list, path_components)
62
+ hash_list.map do |hash|
63
+ if hash[:lhs] == 'Self' &&
64
+ hash[:kind] == 'conformance' &&
65
+ path_components.include?(hash[:rhs])
66
+ next nil
67
+ end
68
+
69
+ Constraint.new_hash(hash)
70
+ end.compact
71
+ end
72
+
73
+ # Workaround Swift 5.3 bug with missing constraint rels
74
+ def self.new_list_from_declaration(decl)
75
+ decl.split(/\s*,\s*/).map { |cons| Constraint.new_declaration(cons) }
76
+ end
77
+
78
+ # Sort order - by Swift text
79
+ include Comparable
80
+
81
+ def <=>(other)
82
+ to_swift <=> other.to_swift
83
+ end
84
+
85
+ alias eql? ==
86
+
87
+ def hash
88
+ to_swift.hash
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ class Array
95
+ def to_where_clause
96
+ empty? ? '' : " where #{map(&:to_swift).join(', ')}"
97
+ end
98
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jazzy
4
+ module SymbolGraph
5
+ # For extensions we need to track constraints of the extended type
6
+ # and the constraints introduced by the extension.
7
+ class ExtConstraints
8
+ attr_accessor :type # array
9
+ attr_accessor :ext # array
10
+
11
+ # all constraints inherited by members of the extension
12
+ def merged
13
+ (type + ext).sort
14
+ end
15
+
16
+ def initialize(type_constraints, ext_constraints)
17
+ self.type = type_constraints || []
18
+ self.ext = ext_constraints || []
19
+ end
20
+ end
21
+
22
+ # An ExtNode is a node of the reconstructed syntax tree representing
23
+ # an extension that we fabricate to resolve certain relationships.
24
+ class ExtNode < BaseNode
25
+ attr_accessor :usr
26
+ attr_accessor :name
27
+ attr_accessor :all_constraints # ExtConstraints
28
+ attr_accessor :conformances # array, can be empty
29
+
30
+ # Deduce an extension from a member of an unknown type or
31
+ # of known type with additional constraints
32
+ def self.new_for_member(type_usr,
33
+ member,
34
+ constraints)
35
+ new(type_usr,
36
+ member.parent_qualified_name,
37
+ constraints).tap { |o| o.add_child(member) }
38
+ end
39
+
40
+ # Deduce an extension from a protocol conformance for some type
41
+ def self.new_for_conformance(type_usr,
42
+ type_name,
43
+ protocol,
44
+ constraints)
45
+ new(type_usr, type_name, constraints).tap do |o|
46
+ o.add_conformance(protocol)
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def initialize(usr, name, constraints)
53
+ self.usr = usr
54
+ self.name = name
55
+ self.all_constraints = constraints
56
+ self.conformances = []
57
+ super()
58
+ end
59
+
60
+ public
61
+
62
+ def constraints
63
+ all_constraints.merged
64
+ end
65
+
66
+ def add_conformance(protocol)
67
+ conformances.append(protocol).sort!
68
+ end
69
+
70
+ def full_declaration
71
+ decl = "extension #{name}"
72
+ unless conformances.empty?
73
+ decl += " : #{conformances.join(', ')}"
74
+ end
75
+ decl + all_constraints.ext.to_where_clause
76
+ end
77
+
78
+ def to_sourcekit(module_name)
79
+ declaration = full_declaration
80
+ xml_declaration = "<swift>#{CGI.escapeHTML(declaration)}</swift>"
81
+
82
+ hash = {
83
+ 'key.kind' => 'source.lang.swift.decl.extension',
84
+ 'key.usr' => usr,
85
+ 'key.name' => name,
86
+ 'key.modulename' => module_name,
87
+ 'key.parsed_declaration' => declaration,
88
+ 'key.annotated_decl' => xml_declaration,
89
+ }
90
+
91
+ unless conformances.empty?
92
+ hash['key.inheritedtypes'] = conformances.map do |conformance|
93
+ { 'key.name' => conformance }
94
+ end
95
+ end
96
+
97
+ unless children.empty?
98
+ hash['key.substructure'] = children_to_sourcekit
99
+ end
100
+
101
+ hash
102
+ end
103
+
104
+ # Sort order - by type name then constraint
105
+ include Comparable
106
+
107
+ def sort_key
108
+ name + constraints.map(&:to_swift).join
109
+ end
110
+
111
+ def <=>(other)
112
+ sort_key <=> other.sort_key
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/ClassLength
4
+ module Jazzy
5
+ module SymbolGraph
6
+ # A Graph is the coordinator to import a symbolgraph json file.
7
+ # Deserialize it to Symbols and Relationships, then rebuild
8
+ # the AST shape using SymNodes and ExtNodes and extract SourceKit json.
9
+ class Graph
10
+ attr_accessor :module_name
11
+ attr_accessor :symbol_nodes # usr -> SymNode
12
+ attr_accessor :relationships # [Relationship]
13
+ attr_accessor :ext_nodes # (usr, constraints) -> ExtNode
14
+
15
+ # Parse the JSON into flat tables of data
16
+ def initialize(json, module_name)
17
+ self.module_name = module_name
18
+ graph = JSON.parse(json, symbolize_names: true)
19
+
20
+ self.symbol_nodes = {}
21
+ graph[:symbols].each do |hash|
22
+ symbol = Symbol.new(hash)
23
+ symbol_nodes[symbol.usr] = SymNode.new(symbol)
24
+ end
25
+
26
+ self.relationships =
27
+ graph[:relationships].map { |hash| Relationship.new(hash) }
28
+
29
+ self.ext_nodes = {}
30
+ end
31
+
32
+ # ExtNode index. (type USR, extension constraints) -> ExtNode.
33
+ # This minimizes the number of extensions
34
+
35
+ def ext_key(usr, constraints)
36
+ usr + constraints.map(&:to_swift).join
37
+ end
38
+
39
+ def add_ext_member(type_usr, member_node, constraints)
40
+ key = ext_key(type_usr, constraints.ext)
41
+ if ext_node = ext_nodes[key]
42
+ ext_node.add_child(member_node)
43
+ else
44
+ ext_nodes[key] =
45
+ ExtNode.new_for_member(type_usr, member_node, constraints)
46
+ end
47
+ end
48
+
49
+ def add_ext_conformance(type_usr,
50
+ type_name,
51
+ protocol,
52
+ constraints)
53
+ key = ext_key(type_usr, constraints.ext)
54
+ if ext_node = ext_nodes[key]
55
+ ext_node.add_conformance(protocol)
56
+ else
57
+ ext_nodes[key] =
58
+ ExtNode.new_for_conformance(type_usr,
59
+ type_name,
60
+ protocol,
61
+ constraints)
62
+ end
63
+ end
64
+
65
+ # Increasingly desparate ways to find the name of the symbol
66
+ # at the target end of a relationship
67
+ def rel_target_name(rel, target_node)
68
+ target_node&.symbol&.name ||
69
+ rel.target_fallback ||
70
+ Jazzy::SymbolGraph.demangle(rel.target_usr)
71
+ end
72
+
73
+ # Same for the source end. Less help from the tool here
74
+ def rel_source_name(rel, source_node)
75
+ source_node&.qualified_name ||
76
+ Jazzy::SymbolGraph.demangle(rel.source_usr)
77
+ end
78
+
79
+ # Protocol conformance is redundant if it's unconditional
80
+ # and already expressed in the type's declaration.
81
+ def redundant_conformance?(rel, type, protocol)
82
+ type && rel.constraints.empty? && type.conformance?(protocol)
83
+ end
84
+
85
+ # source is a member/protocol requirement of target
86
+ def rebuild_member(rel, source, target)
87
+ return unless source
88
+
89
+ source.protocol_requirement = rel.protocol_requirement?
90
+ constraints =
91
+ ExtConstraints.new(target&.constraints,
92
+ source.unique_context_constraints(target))
93
+
94
+ # Add to its parent or invent an extension
95
+ unless target&.try_add_child(source, constraints.ext)
96
+ add_ext_member(rel.target_usr, source, constraints)
97
+ end
98
+ end
99
+
100
+ # "source : target" either from type decl or ext decl
101
+ def rebuild_conformance(rel, source, target)
102
+ protocol_name = rel_target_name(rel, target)
103
+
104
+ return if redundant_conformance?(rel, source, protocol_name)
105
+
106
+ type_constraints = source&.constraints || []
107
+ constraints =
108
+ ExtConstraints.new(type_constraints,
109
+ rel.constraints - type_constraints)
110
+
111
+ # Create an extension or enhance an existing one
112
+ add_ext_conformance(rel.source_usr,
113
+ rel_source_name(rel, source),
114
+ protocol_name,
115
+ constraints)
116
+ end
117
+
118
+ # "source is a default implementation of protocol requirement target"
119
+ def rebuild_default_implementation(_rel, source, target)
120
+ return unless source
121
+
122
+ unless target &&
123
+ (target_parent = target.parent) &&
124
+ target_parent.is_a?(SymNode)
125
+ # Could probably figure this out with demangle, but...
126
+ warn "Can't resolve membership of default implementation "\
127
+ "#{source.symbol.usr}."
128
+ source.unlisted = true
129
+ return
130
+ end
131
+ constraints =
132
+ ExtConstraints.new(target_parent.constraints,
133
+ source.unique_context_constraints(target_parent))
134
+
135
+ add_ext_member(target_parent.symbol.usr,
136
+ source,
137
+ constraints)
138
+ end
139
+
140
+ # "source is a class that inherits from target"
141
+ def rebuild_inherits(_rel, source, target)
142
+ if source && target
143
+ source.superclass_name = target.symbol.name
144
+ end
145
+ end
146
+
147
+ # Process a structural relationship to link nodes
148
+ def rebuild_rel(rel)
149
+ source = symbol_nodes[rel.source_usr]
150
+ target = symbol_nodes[rel.target_usr]
151
+
152
+ case rel.kind
153
+ when :memberOf, :optionalRequirementOf, :requirementOf
154
+ rebuild_member(rel, source, target)
155
+
156
+ when :conformsTo
157
+ rebuild_conformance(rel, source, target)
158
+
159
+ when :defaultImplementationOf
160
+ rebuild_default_implementation(rel, source, target)
161
+
162
+ when :inheritsFrom
163
+ rebuild_inherits(rel, source, target)
164
+ end
165
+ # don't seem to care about:
166
+ # - overrides: not bothered, also unimplemented for protocols
167
+ end
168
+
169
+ # Rebuild the AST structure and convert to SourceKit
170
+ def to_sourcekit
171
+ # Do default impls after the others so we can find protocol
172
+ # type nodes from protocol requirements.
173
+ default_impls, other_rels =
174
+ relationships.partition(&:default_implementation?)
175
+ (other_rels + default_impls).each { |r| rebuild_rel(r) }
176
+
177
+ root_symbol_nodes =
178
+ symbol_nodes.values
179
+ .select(&:top_level_decl?)
180
+ .sort
181
+ .map(&:to_sourcekit)
182
+
183
+ root_ext_nodes =
184
+ ext_nodes.values
185
+ .sort
186
+ .map { |n| n.to_sourcekit(module_name) }
187
+ {
188
+ 'key.diagnostic_stage' => 'parse',
189
+ 'key.substructure' => root_symbol_nodes + root_ext_nodes,
190
+ }
191
+ end
192
+ end
193
+ end
194
+ end
195
+ # rubocop:enable Metrics/ClassLength