jazzy 0.13.7 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/Tests.yml +2 -2
  3. data/.rubocop.yml +123 -24
  4. data/CHANGELOG.md +34 -0
  5. data/Dangerfile +11 -8
  6. data/Gemfile +3 -1
  7. data/Gemfile.lock +49 -34
  8. data/README.md +23 -11
  9. data/Rakefile +13 -12
  10. data/bin/jazzy +3 -2
  11. data/jazzy.gemspec +8 -6
  12. data/lib/jazzy.rb +2 -0
  13. data/lib/jazzy/config.rb +116 -69
  14. data/lib/jazzy/doc.rb +3 -1
  15. data/lib/jazzy/doc_builder.rb +63 -81
  16. data/lib/jazzy/docset_builder.rb +3 -1
  17. data/lib/jazzy/documentation_generator.rb +6 -2
  18. data/lib/jazzy/executable.rb +3 -0
  19. data/lib/jazzy/extensions/bitbucket/img/bitbucket.svg +11 -0
  20. data/lib/jazzy/{themes/apple/assets → extensions/github}/img/gh.png +0 -0
  21. data/lib/jazzy/extensions/gitlab/img/gitlab.svg +23 -0
  22. data/lib/jazzy/gem_version.rb +3 -1
  23. data/lib/jazzy/highlighter.rb +5 -3
  24. data/lib/jazzy/jazzy_markdown.rb +63 -30
  25. data/lib/jazzy/podspec_documenter.rb +14 -16
  26. data/lib/jazzy/search_builder.rb +4 -3
  27. data/lib/jazzy/source_declaration.rb +9 -3
  28. data/lib/jazzy/source_declaration/access_control_level.rb +7 -5
  29. data/lib/jazzy/source_declaration/type.rb +3 -1
  30. data/lib/jazzy/source_document.rb +8 -5
  31. data/lib/jazzy/source_host.rb +111 -0
  32. data/lib/jazzy/source_mark.rb +8 -6
  33. data/lib/jazzy/source_module.rb +6 -6
  34. data/lib/jazzy/sourcekitten.rb +98 -72
  35. data/lib/jazzy/stats.rb +4 -2
  36. data/lib/jazzy/symbol_graph.rb +15 -15
  37. data/lib/jazzy/symbol_graph/constraint.rb +5 -1
  38. data/lib/jazzy/symbol_graph/ext_node.rb +3 -1
  39. data/lib/jazzy/symbol_graph/graph.rb +13 -11
  40. data/lib/jazzy/symbol_graph/relationship.rb +3 -0
  41. data/lib/jazzy/symbol_graph/sym_node.rb +11 -6
  42. data/lib/jazzy/symbol_graph/symbol.rb +18 -15
  43. data/lib/jazzy/themes/apple/assets/css/highlight.css.scss +63 -59
  44. data/lib/jazzy/themes/apple/assets/css/jazzy.css.scss +5 -1
  45. data/lib/jazzy/themes/apple/assets/js/jazzy.js +4 -0
  46. data/lib/jazzy/themes/apple/assets/js/jazzy.search.js +4 -0
  47. data/lib/jazzy/themes/apple/templates/doc.mustache +3 -3
  48. data/lib/jazzy/themes/apple/templates/footer.mustache +1 -1
  49. data/lib/jazzy/themes/apple/templates/header.mustache +3 -3
  50. data/lib/jazzy/themes/apple/templates/task.mustache +3 -3
  51. data/lib/jazzy/themes/fullwidth/assets/css/highlight.css.scss +63 -59
  52. data/lib/jazzy/themes/fullwidth/assets/css/jazzy.css.scss +6 -2
  53. data/lib/jazzy/themes/fullwidth/assets/js/jazzy.js +4 -0
  54. data/lib/jazzy/themes/fullwidth/assets/js/jazzy.search.js +4 -0
  55. data/lib/jazzy/themes/fullwidth/templates/doc.mustache +3 -3
  56. data/lib/jazzy/themes/fullwidth/templates/footer.mustache +1 -1
  57. data/lib/jazzy/themes/fullwidth/templates/header.mustache +5 -5
  58. data/lib/jazzy/themes/fullwidth/templates/task.mustache +3 -3
  59. data/lib/jazzy/themes/jony/assets/css/highlight.css.scss +63 -59
  60. data/lib/jazzy/themes/jony/assets/css/jazzy.css.scss +5 -1
  61. data/lib/jazzy/themes/jony/assets/js/jazzy.js +4 -0
  62. data/lib/jazzy/themes/jony/templates/doc.mustache +3 -3
  63. data/lib/jazzy/themes/jony/templates/footer.mustache +1 -1
  64. data/lib/jazzy/themes/jony/templates/header.mustache +5 -5
  65. data/lib/jazzy/themes/jony/templates/task.mustache +3 -3
  66. data/spec/integration_spec.rb +39 -36
  67. data/spec/spec_helper.rb +3 -1
  68. data/spec/spec_helper/pre_flight.rb +2 -0
  69. metadata +27 -13
  70. data/lib/jazzy/themes/fullwidth/assets/img/gh.png +0 -0
  71. data/lib/jazzy/themes/jony/assets/img/gh.png +0 -0
  72. data/spec/sourcekitten_spec.rb +0 -6
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tmpdir'
2
4
  require 'json'
3
5
 
@@ -22,8 +24,8 @@ module Jazzy
22
24
  installer.install!
23
25
  stdout = Dir.chdir(sandbox.root) do
24
26
  targets = installer.pod_targets
25
- .select { |pt| pt.pod_name == podspec.root.name }
26
- .map(&:label)
27
+ .select { |pt| pt.pod_name == podspec.root.name }
28
+ .map(&:label)
27
29
 
28
30
  targets.map do |t|
29
31
  args = %W[doc --module-name #{podspec.module_name} -- -target #{t}]
@@ -42,8 +44,6 @@ module Jazzy
42
44
  end
43
45
  end
44
46
 
45
- # rubocop:disable Metrics/CyclomaticComplexity
46
- # rubocop:disable Metrics/PerceivedComplexity
47
47
  # rubocop:disable Metrics/MethodLength
48
48
  def self.apply_config_defaults(podspec, config)
49
49
  return unless podspec
@@ -64,9 +64,9 @@ module Jazzy
64
64
  config.version = podspec.version.to_s
65
65
  config.version_configured = true
66
66
  end
67
- unless config.github_file_prefix_configured
68
- config.github_file_prefix = github_file_prefix(podspec)
69
- config.github_file_prefix_configured = true
67
+ unless config.source_host_files_url_configured
68
+ config.source_host_files_url = github_file_prefix(podspec)
69
+ config.source_host_files_url_configured = true
70
70
  end
71
71
  unless config.swift_version_configured
72
72
  trunk_swift_build = podspec.attributes_hash['pushed_with_swift_version']
@@ -74,8 +74,6 @@ module Jazzy
74
74
  config.swift_version_configured = true
75
75
  end
76
76
  end
77
- # rubocop:enable Metrics/CyclomaticComplexity
78
- # rubocop:enable Metrics/PerceivedComplexity
79
77
  # rubocop:enable Metrics/MethodLength
80
78
 
81
79
  private
@@ -94,17 +92,17 @@ module Jazzy
94
92
 
95
93
  def self.github_file_prefix(podspec)
96
94
  return unless podspec.source[:url] =~ %r{github.com[:/]+(.+)/(.+)}
95
+
97
96
  org, repo = Regexp.last_match
98
- return unless org && repo
99
- repo.sub!(/\.git$/, '')
100
97
  return unless rev = podspec.source[:tag] || podspec.source[:commit]
101
- "https://github.com/#{org}/#{repo}/blob/#{rev}"
98
+
99
+ "https://github.com/#{org}/#{repo.sub(/\.git$/, '')}/blob/#{rev}"
102
100
  end
103
101
 
104
102
  private_class_method :github_file_prefix
105
103
 
106
104
  # Latest valid value for SWIFT_VERSION.
107
- LATEST_SWIFT_VERSION = '5'.freeze
105
+ LATEST_SWIFT_VERSION = '5'
108
106
 
109
107
  # All valid values for SWIFT_VERSION that are longer
110
108
  # than a major version number. Ordered ascending.
@@ -162,9 +160,8 @@ module Jazzy
162
160
  # Travis builds take too long when building docs for all available
163
161
  # platforms for the Moya integration spec, so we just document OSX.
164
162
  # TODO: remove once jazzy is fast enough.
165
- if ENV['JAZZY_INTEGRATION_SPECS']
166
- next if p.name != :osx
167
- end
163
+ next if ENV['JAZZY_INTEGRATION_SPECS'] && p.name != :osx
164
+
168
165
  target("Jazzy-#{ss.name.gsub('/', '__')}-#{p.name}") do
169
166
  use_frameworks!
170
167
  platform p.name, p.deployment_target
@@ -177,4 +174,5 @@ module Jazzy
177
174
  end
178
175
  # rubocop:enable Metrics/MethodLength
179
176
  end
177
+ # rubocop:enable Metrics/ClassLength
180
178
  end
@@ -1,18 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jazzy
2
4
  module SearchBuilder
3
5
  def self.build(source_module, output_dir)
4
6
  decls = source_module.all_declarations.select do |d|
5
7
  d.type && d.name && !d.name.empty?
6
8
  end
7
- index = Hash[decls.map do |d|
9
+ index = decls.map do |d|
8
10
  [d.url,
9
11
  {
10
12
  name: d.name,
11
13
  abstract: d.abstract && d.abstract.split(/\n/).map(&:strip).first,
12
14
  parent_name: d.parent_in_code && d.parent_in_code.name,
13
15
  }.reject { |_, v| v.nil? || v.empty? }]
14
- end
15
- ]
16
+ end.to_h
16
17
  File.open(File.join(output_dir, 'search.json'), 'w') do |f|
17
18
  f.write(index.to_json)
18
19
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'jazzy/source_declaration/access_control_level'
2
4
  require 'jazzy/source_declaration/type'
3
5
 
4
- # rubocop:disable Metrics/ClassLength
5
6
  module Jazzy
7
+ # rubocop:disable Metrics/ClassLength
6
8
  class SourceDeclaration
7
9
  # kind of declaration (e.g. class, variable, function)
8
10
  attr_accessor :type
@@ -39,7 +41,7 @@ module Jazzy
39
41
  attr_accessor :parent_in_docs
40
42
 
41
43
  # counterpart of parent_in_docs
42
- attr_accessor :children
44
+ attr_reader :children
43
45
 
44
46
  def children=(new_children)
45
47
  # Freeze to ensure that parent_in_docs stays in sync
@@ -76,7 +78,7 @@ module Jazzy
76
78
  # of the extended objc class and the category name itself, i.e.
77
79
  # ["NSString", "MyMethods"], nil otherwise.
78
80
  def objc_category_name
79
- name.split(/[\(\)]/) if type.objc_category?
81
+ name.split(/[()]/) if type.objc_category?
80
82
  end
81
83
 
82
84
  def swift_objc_extension?
@@ -151,6 +153,7 @@ module Jazzy
151
153
  # Workaround functions sharing names with
152
154
  # different argument types (f(a:Int) vs. f(a:String))
153
155
  return result unless type.swift_global_function?
156
+
154
157
  result + "_#{type_usr}"
155
158
  end
156
159
 
@@ -175,6 +178,7 @@ module Jazzy
175
178
  # Is there at least one inherited type that is not in the given list?
176
179
  def other_inherited_types?(unwanted)
177
180
  return false unless inherited_types?
181
+
178
182
  inherited_types.any? { |t| !unwanted.include?(t) }
179
183
  end
180
184
 
@@ -201,7 +205,9 @@ module Jazzy
201
205
  return [] unless
202
206
  Config.instance.abstract_glob_configured &&
203
207
  Config.instance.abstract_glob
208
+
204
209
  Config.instance.abstract_glob.select { |e| File.file? e }
205
210
  end
206
211
  end
212
+ # rubocop:enable Metrics/ClassLength
207
213
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jazzy
2
4
  class SourceDeclaration
3
5
  class AccessControlLevel
@@ -5,12 +7,12 @@ module Jazzy
5
7
 
6
8
  attr_reader :level
7
9
 
8
- ACCESSIBILITY_PRIVATE = 'source.lang.swift.accessibility.private'.freeze
10
+ ACCESSIBILITY_PRIVATE = 'source.lang.swift.accessibility.private'
9
11
  ACCESSIBILITY_FILEPRIVATE =
10
- 'source.lang.swift.accessibility.fileprivate'.freeze
11
- ACCESSIBILITY_INTERNAL = 'source.lang.swift.accessibility.internal'.freeze
12
- ACCESSIBILITY_PUBLIC = 'source.lang.swift.accessibility.public'.freeze
13
- ACCESSIBILITY_OPEN = 'source.lang.swift.accessibility.open'.freeze
12
+ 'source.lang.swift.accessibility.fileprivate'
13
+ ACCESSIBILITY_INTERNAL = 'source.lang.swift.accessibility.internal'
14
+ ACCESSIBILITY_PUBLIC = 'source.lang.swift.accessibility.public'
15
+ ACCESSIBILITY_OPEN = 'source.lang.swift.accessibility.open'
14
16
 
15
17
  def initialize(accessibility)
16
18
  @level = case accessibility
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/inflector'
2
4
 
3
5
  module Jazzy
@@ -151,7 +153,7 @@ module Jazzy
151
153
  Type.new('Overview')
152
154
  end
153
155
 
154
- MARKDOWN_KIND = 'document.markdown'.freeze
156
+ MARKDOWN_KIND = 'document.markdown'
155
157
 
156
158
  def self.markdown
157
159
  Type.new(MARKDOWN_KIND)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pathname'
2
4
 
3
5
  require 'jazzy/jazzy_markdown'
@@ -43,6 +45,7 @@ module Jazzy
43
45
 
44
46
  def content(source_module)
45
47
  return readme_content(source_module) if name == 'index'
48
+
46
49
  overview
47
50
  end
48
51
 
@@ -51,7 +54,7 @@ module Jazzy
51
54
  end
52
55
 
53
56
  def config_readme
54
- readme_path.read if readme_path && readme_path.exist?
57
+ readme_path.read if readme_path&.exist?
55
58
  end
56
59
 
57
60
  def fallback_readme
@@ -67,7 +70,7 @@ module Jazzy
67
70
  ### License
68
71
 
69
72
  # <a href="#{license[:url]}">#{license[:license]}</a>
70
- <<-EOS
73
+ <<-README
71
74
  # #{podspec.name}
72
75
 
73
76
  ### #{podspec.summary}
@@ -83,15 +86,15 @@ pod '#{podspec.name}'
83
86
  ### Authors
84
87
 
85
88
  #{source_module.author_name}
86
- EOS
89
+ README
87
90
  else
88
- <<-EOS
91
+ <<-README
89
92
  # #{source_module.name}
90
93
 
91
94
  ### Authors
92
95
 
93
96
  #{source_module.author_name}
94
- EOS
97
+ README
95
98
  end
96
99
  end
97
100
  end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jazzy
4
+ # Deal with different source code repositories
5
+ module SourceHost
6
+ # Factory to create the right source host
7
+ def self.create(options)
8
+ return unless options.source_host_url || options.source_host_files_url
9
+
10
+ case options.source_host
11
+ when :github then GitHub.new
12
+ when :gitlab then GitLab.new
13
+ when :bitbucket then BitBucket.new
14
+ end
15
+ end
16
+
17
+ # Use GitHub as the default behaviour.
18
+ class GitHub
19
+ include Config::Mixin
20
+
21
+ # Human readable name, appears in UI
22
+ def name
23
+ 'GitHub'
24
+ end
25
+
26
+ # Jazzy extension with logo
27
+ def extension
28
+ name.downcase
29
+ end
30
+
31
+ # Logo image filename within extension
32
+ def image
33
+ 'gh.png'
34
+ end
35
+
36
+ # URL to link to from logo
37
+ def url
38
+ config.source_host_url
39
+ end
40
+
41
+ # URL to link to from a SourceDeclaration.
42
+ # Compare using `realpath` because `item.file` comes out of
43
+ # SourceKit/etc.
44
+ def item_url(item)
45
+ return unless files_url && item.file
46
+
47
+ realpath = item.file.realpath
48
+ return unless realpath.to_path.start_with?(local_root_realpath)
49
+
50
+ path = realpath.relative_path_from(local_root_realpath)
51
+ fragment =
52
+ if item.start_line && (item.start_line != item.end_line)
53
+ item_url_multiline_fragment(item.start_line, item.end_line)
54
+ else
55
+ item_url_line_fragment(item.line)
56
+ end
57
+
58
+ "#{files_url}/#{path}##{fragment}"
59
+ end
60
+
61
+ private
62
+
63
+ def files_url
64
+ config.source_host_files_url
65
+ end
66
+
67
+ def local_root_realpath
68
+ @local_root_realpath ||= config.source_directory.realpath.to_path
69
+ end
70
+
71
+ # Source host's line numbering link scheme
72
+ def item_url_line_fragment(line)
73
+ "L#{line}"
74
+ end
75
+
76
+ def item_url_multiline_fragment(start_line, end_line)
77
+ "L#{start_line}-L#{end_line}"
78
+ end
79
+ end
80
+
81
+ # GitLab very similar to GitHub
82
+ class GitLab < GitHub
83
+ def name
84
+ 'GitLab'
85
+ end
86
+
87
+ def image
88
+ 'gitlab.svg'
89
+ end
90
+ end
91
+
92
+ # BitBucket has its own line number system
93
+ class BitBucket < GitHub
94
+ def name
95
+ 'Bitbucket'
96
+ end
97
+
98
+ def image
99
+ 'bitbucket.svg'
100
+ end
101
+
102
+ def item_url_line_fragment(line)
103
+ "lines-#{line}"
104
+ end
105
+
106
+ def item_url_multiline_fragment(start_line, end_line)
107
+ "lines-#{start_line}:#{end_line}"
108
+ end
109
+ end
110
+ end
111
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jazzy
2
4
  class SourceMark
3
5
  attr_accessor :name
@@ -8,24 +10,24 @@ module Jazzy
8
10
  return unless mark_string
9
11
 
10
12
  # Format: 'MARK: - NAME -' with dashes optional
11
- mark_string.sub!(/^MARK: /, '')
13
+ mark_content = mark_string.sub(/^MARK: /, '')
12
14
 
13
- if mark_string.empty?
15
+ if mark_content.empty?
14
16
  # Empty
15
17
  return
16
- elsif mark_string == '-'
18
+ elsif mark_content == '-'
17
19
  # Separator
18
20
  self.has_start_dash = true
19
21
  return
20
22
  end
21
23
 
22
- self.has_start_dash = mark_string.start_with?('- ')
23
- self.has_end_dash = mark_string.end_with?(' -')
24
+ self.has_start_dash = mark_content.start_with?('- ')
25
+ self.has_end_dash = mark_content.end_with?(' -')
24
26
 
25
27
  start_index = has_start_dash ? 2 : 0
26
28
  end_index = has_end_dash ? -3 : -1
27
29
 
28
- self.name = mark_string[start_index..end_index]
30
+ self.name = mark_content[start_index..end_index]
29
31
  end
30
32
 
31
33
  def self.new_generic_requirements(requirements)
@@ -1,32 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
 
3
5
  require 'jazzy/config'
4
6
  require 'jazzy/source_declaration'
7
+ require 'jazzy/source_host'
5
8
 
6
9
  module Jazzy
7
10
  class SourceModule
8
11
  attr_accessor :name
9
- attr_accessor :root_path
10
12
  attr_accessor :docs
11
13
  attr_accessor :doc_coverage
12
14
  attr_accessor :doc_structure
13
15
  attr_accessor :author_name
14
- attr_accessor :github_url
15
- attr_accessor :github_file_prefix
16
16
  attr_accessor :author_url
17
17
  attr_accessor :dash_url
18
+ attr_accessor :host
18
19
 
19
20
  def initialize(options, docs, doc_structure, doc_coverage)
20
21
  self.docs = docs
21
- self.root_path = options.source_directory
22
22
  self.doc_structure = doc_structure
23
23
  self.doc_coverage = doc_coverage
24
24
  self.name = options.module_name
25
25
  self.author_name = options.author_name
26
- self.github_url = options.github_url
27
- self.github_file_prefix = options.github_file_prefix
28
26
  self.author_url = options.author_url
27
+ self.host = SourceHost.create(options)
29
28
  return unless options.dash_url
29
+
30
30
  self.dash_url =
31
31
  "dash-feed://#{ERB::Util.url_encode(options.dash_url.to_s)}"
32
32
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'pathname'
3
5
  require 'shellwords'
@@ -12,7 +14,7 @@ require 'jazzy/source_declaration'
12
14
  require 'jazzy/source_mark'
13
15
  require 'jazzy/stats'
14
16
 
15
- ELIDED_AUTOLINK_TOKEN = '36f8f5912051ae747ef441d6511ca4cb'.freeze
17
+ ELIDED_AUTOLINK_TOKEN = '36f8f5912051ae747ef441d6511ca4cb'
16
18
 
17
19
  def autolink_regex(middle_regex, after_highlight)
18
20
  start_tag_re, end_tag_re =
@@ -29,16 +31,15 @@ class String
29
31
  gsub(autolink_regex(middle_regex, after_highlight)) do
30
32
  original = Regexp.last_match(0)
31
33
  start_tag, raw_name, end_tag = Regexp.last_match.captures
32
- link_target = yield(CGI.unescape_html(raw_name))
34
+ link_target, display_name = yield(CGI.unescape_html(raw_name))
33
35
 
34
36
  if link_target &&
35
37
  !link_target.type.extension? &&
36
38
  link_target.url &&
37
39
  link_target.url != doc_url.split('#').first && # Don't link to parent
38
40
  link_target.url != doc_url # Don't link to self
39
- start_tag +
40
- "<a href=\"#{ELIDED_AUTOLINK_TOKEN}#{link_target.url}\">" +
41
- raw_name + '</a>' + end_tag
41
+ "#{start_tag}<a href=\"#{ELIDED_AUTOLINK_TOKEN}#{link_target.url}\">" \
42
+ "#{CGI.escape_html(display_name)}</a>#{end_tag}"
42
43
  else
43
44
  original
44
45
  end
@@ -74,8 +75,8 @@ module Jazzy
74
75
  children = category['children'].flat_map do |name|
75
76
  docs_with_name, docs = docs.partition { |doc| doc.name == name }
76
77
  if docs_with_name.empty?
77
- STDERR.puts 'WARNING: No documented top-level declarations match ' \
78
- "name \"#{name}\" specified in categories file"
78
+ warn 'WARNING: No documented top-level declarations match ' \
79
+ "name \"#{name}\" specified in categories file"
79
80
  end
80
81
  docs_with_name
81
82
  end
@@ -129,7 +130,7 @@ module Jazzy
129
130
  def self.merge_consecutive_marks(docs)
130
131
  prev_mark = nil
131
132
  docs.each do |doc|
132
- if prev_mark && prev_mark.can_merge?(doc.mark)
133
+ if prev_mark&.can_merge?(doc.mark)
133
134
  doc.mark = prev_mark
134
135
  end
135
136
  prev_mark = doc.mark
@@ -141,9 +142,9 @@ module Jazzy
141
142
  unsafe_filename = doc.docs_filename
142
143
  sanitzation_enabled = Config.instance.use_safe_filenames
143
144
  if sanitzation_enabled && !doc.type.name_controlled_manually?
144
- return CGI.escape(unsafe_filename).gsub('_', '%5F').tr('%', '_')
145
+ CGI.escape(unsafe_filename).gsub('_', '%5F').tr('%', '_')
145
146
  else
146
- return unsafe_filename
147
+ unsafe_filename
147
148
  end
148
149
  end
149
150
 
@@ -162,11 +163,11 @@ module Jazzy
162
163
  # Don't create HTML page for this doc if it doesn't have children
163
164
  # Instead, make its link a hash-link on its parent's page
164
165
  if doc.typename == '<<error type>>'
165
- warn 'A compile error prevented ' + doc.fully_qualified_name +
166
- ' from receiving a unique USR. Documentation may be ' \
167
- 'incomplete. Please check for compile errors by running ' \
168
- '`xcodebuild` or `swift build` with arguments ' \
169
- "`#{Config.instance.build_tool_arguments.shelljoin}`."
166
+ warn "A compile error prevented #{doc.fully_qualified_name} " \
167
+ 'from receiving a unique USR. Documentation may be ' \
168
+ 'incomplete. Please check for compile errors by running ' \
169
+ '`xcodebuild` or `swift build` with arguments ' \
170
+ "`#{Config.instance.build_tool_arguments.shelljoin}`."
170
171
  end
171
172
  id = doc.usr
172
173
  unless id
@@ -174,11 +175,11 @@ module Jazzy
174
175
  warn "`#{id}` has no USR. First make sure all modules used in " \
175
176
  'your project have been imported. If all used modules are ' \
176
177
  'imported, please report this problem by filing an issue at ' \
177
- 'https://github.com/realm/jazzy/issues along with your Xcode ' \
178
- 'project. If this token is declared in an `#if` block, please ' \
179
- 'ignore this message.'
178
+ 'https://github.com/realm/jazzy/issues along with your ' \
179
+ 'Xcode project. If this token is declared in an `#if` block, ' \
180
+ 'please ignore this message.'
180
181
  end
181
- doc.url = doc.parent_in_docs.url + '#/' + id
182
+ doc.url = "#{doc.parent_in_docs.url}#/#{id}"
182
183
  end
183
184
  end
184
185
  end
@@ -189,6 +190,7 @@ module Jazzy
189
190
  # Declarations under outer namespace type (Structures, Classes, etc.)
190
191
  def self.subdir_for_doc(doc)
191
192
  return [] if doc.type.markdown?
193
+
192
194
  top_level_decl = doc.namespace_path.first
193
195
  if top_level_decl.type.name
194
196
  [top_level_decl.type.plural_url_name] +
@@ -258,6 +260,7 @@ module Jazzy
258
260
  unless xcode = XCInvoke::Xcode.find_swift_version(swift_version)
259
261
  raise "Unable to find an Xcode with swift version #{swift_version}."
260
262
  end
263
+
261
264
  env = xcode.as_env
262
265
  else
263
266
  env = ENV
@@ -269,8 +272,6 @@ module Jazzy
269
272
 
270
273
  def self.make_default_doc_info(declaration)
271
274
  # @todo: Fix these
272
- declaration.line = nil
273
- declaration.column = nil
274
275
  declaration.abstract = ''
275
276
  declaration.parameters = []
276
277
  declaration.children = []
@@ -278,13 +279,12 @@ module Jazzy
278
279
 
279
280
  def self.availability_attribute?(doc)
280
281
  return false unless doc['key.attributes']
282
+
281
283
  !doc['key.attributes'].select do |attribute|
282
284
  attribute.values.first == 'source.decl.attribute.available'
283
285
  end.empty?
284
286
  end
285
287
 
286
- # rubocop:disable Metrics/CyclomaticComplexity
287
- # rubocop:disable Metrics/PerceivedComplexity
288
288
  def self.should_document?(doc)
289
289
  return false if doc['key.doc.comment'].to_s.include?(':nodoc:')
290
290
 
@@ -314,8 +314,6 @@ module Jazzy
314
314
  end
315
315
  acl_ok
316
316
  end
317
- # rubocop:enable Metrics/CyclomaticComplexity
318
- # rubocop:enable Metrics/PerceivedComplexity
319
317
 
320
318
  def self.should_document_swift_extension?(doc)
321
319
  doc['key.inheritedtypes'] ||
@@ -337,6 +335,7 @@ module Jazzy
337
335
  if !declaration.swift? || should_mark_undocumented(declaration)
338
336
  @stats.add_undocumented(declaration)
339
337
  return nil if @skip_undocumented
338
+
340
339
  declaration.abstract = undocumented_abstract
341
340
  else
342
341
  declaration.abstract = Markdown.render(doc['key.doc.comment'] || '',
@@ -404,7 +403,7 @@ module Jazzy
404
403
  def self.xml_to_text(xml)
405
404
  document = REXML::Document.new(xml)
406
405
  REXML::XPath.match(document.root, '//text()').map(&:value).join
407
- rescue
406
+ rescue StandardError
408
407
  ''
409
408
  end
410
409
 
@@ -490,11 +489,10 @@ module Jazzy
490
489
  end
491
490
 
492
491
  # @available attrs only in compiler 'interface' style
493
- available_attrs = extract_availability(doc['key.doc.declaration'] || '')
494
-
495
- available_attrs.concat(extract_attributes(annotated_decl_attrs))
496
- .push(decl)
497
- .join("\n")
492
+ extract_availability(doc['key.doc.declaration'] || '')
493
+ .concat(extract_attributes(annotated_decl_attrs))
494
+ .push(decl)
495
+ .join("\n")
498
496
  end
499
497
 
500
498
  # Strip default property attributes because libclang
@@ -510,12 +508,14 @@ module Jazzy
510
508
  attrs = Regexp.last_match[1].split(',').map(&:strip) - DEFAULT_ATTRIBUTES
511
509
  attrs_text = attrs.empty? ? '' : " (#{attrs.join(', ')})"
512
510
 
513
- declaration.sub(/(?<=@property)\s+\(.*?\)/, attrs_text)
514
- .gsub(/\s+/, ' ')
511
+ declaration
512
+ .sub(/(?<=@property)\s+\(.*?\)/, attrs_text)
513
+ .gsub(/\s+/, ' ')
515
514
  end
516
515
 
517
516
  def self.make_substructure(doc, declaration)
518
517
  return [] unless subdocs = doc['key.substructure']
518
+
519
519
  make_source_declarations(subdocs,
520
520
  declaration,
521
521
  declaration.mark_for_children)
@@ -558,8 +558,8 @@ module Jazzy
558
558
 
559
559
  unless declaration.type.name
560
560
  raise 'Please file an issue at ' \
561
- 'https://github.com/realm/jazzy/issues about adding support ' \
562
- "for `#{declaration.type.kind}`."
561
+ 'https://github.com/realm/jazzy/issues about adding support ' \
562
+ "for `#{declaration.type.kind}`."
563
563
  end
564
564
 
565
565
  declaration.file = Pathname(doc['key.filepath']) if doc['key.filepath']
@@ -570,8 +570,8 @@ module Jazzy
570
570
  declaration.mark = current_mark
571
571
  declaration.access_control_level =
572
572
  SourceDeclaration::AccessControlLevel.from_doc(doc)
573
- declaration.line = doc['key.doc.line']
574
- declaration.column = doc['key.doc.column']
573
+ declaration.line = doc['key.doc.line'] || doc['key.line']
574
+ declaration.column = doc['key.doc.column'] || doc['key.column']
575
575
  declaration.start_line = doc['key.parsed_scope.start']
576
576
  declaration.end_line = doc['key.parsed_scope.end']
577
577
  declaration.deprecated = doc['key.always_deprecated']
@@ -583,10 +583,12 @@ module Jazzy
583
583
  inherited_types.map { |type| type['key.name'] }.compact
584
584
 
585
585
  next unless make_doc_info(doc, declaration)
586
+
586
587
  declaration.children = make_substructure(doc, declaration)
587
588
  next if declaration.type.extension? &&
588
589
  declaration.children.empty? &&
589
590
  !declaration.inherited_types?
591
+
590
592
  declarations << declaration
591
593
  end
592
594
  declarations
@@ -598,6 +600,7 @@ module Jazzy
598
600
  def self.find_generic_requirements(parsed_declaration)
599
601
  parsed_declaration =~ /\bwhere\s+(.*)$/m
600
602
  return nil unless Regexp.last_match
603
+
601
604
  Regexp.last_match[1].gsub(/\s+/, ' ')
602
605
  end
603
606
 
@@ -619,6 +622,7 @@ module Jazzy
619
622
 
620
623
  def self.expand_extension(extension, name_parts, decls)
621
624
  return extension if name_parts.empty?
625
+
622
626
  name = name_parts.shift
623
627
  candidates = decls.select { |decl| decl.name == name }
624
628
  SourceDeclaration.new.tap do |decl|
@@ -644,8 +648,8 @@ module Jazzy
644
648
  # Merges redundant declarations when documenting podspecs.
645
649
  def self.deduplicate_declarations(declarations)
646
650
  duplicate_groups = declarations
647
- .group_by { |d| deduplication_key(d, declarations) }
648
- .values
651
+ .group_by { |d| deduplication_key(d, declarations) }
652
+ .values
649
653
 
650
654
  duplicate_groups.flat_map do |group|
651
655
  # Put extended type (if present) before extensions
@@ -688,15 +692,17 @@ module Jazzy
688
692
  end
689
693
 
690
694
  # rubocop:disable Metrics/MethodLength
695
+ # rubocop:disable Metrics/PerceivedComplexity
691
696
  # Merges all of the given types and extensions into a single document.
692
697
  def self.merge_declarations(decls)
693
698
  extensions, typedecls = decls.partition { |d| d.type.extension? }
694
699
 
695
700
  if typedecls.size > 1
701
+ info = typedecls
702
+ .map { |t| "#{t.type.name.downcase} #{t.name}" }
703
+ .join(', ')
696
704
  warn 'Found conflicting type declarations with the same name, which ' \
697
- 'may indicate a build issue or a bug in Jazzy: ' +
698
- typedecls.map { |t| "#{t.type.name.downcase} #{t.name}" }
699
- .join(', ')
705
+ "may indicate a build issue or a bug in Jazzy: #{info}"
700
706
  end
701
707
  typedecl = typedecls.first
702
708
 
@@ -712,13 +718,14 @@ module Jazzy
712
718
  end
713
719
 
714
720
  # Keep type-aliases separate from any extensions
715
- if typedecl && typedecl.type.swift_typealias?
721
+ if typedecl&.type&.swift_typealias?
716
722
  [merge_type_and_extensions(typedecls, []),
717
723
  merge_type_and_extensions([], extensions)]
718
724
  else
719
725
  merge_type_and_extensions(typedecls, extensions)
720
726
  end
721
727
  end
728
+ # rubocop:enable Metrics/PerceivedComplexity
722
729
  # rubocop:enable Metrics/MethodLength
723
730
 
724
731
  def self.merge_type_and_extensions(typedecls, extensions)
@@ -810,6 +817,7 @@ module Jazzy
810
817
  # (unless they already have a mark)
811
818
  def self.merge_objc_declaration_marks(typedecl, extensions)
812
819
  return unless typedecl.type.objc_class?
820
+
813
821
  extensions.each do |ext|
814
822
  _, category_name = ext.objc_category_name
815
823
  ext.children.each { |c| c.mark.name ||= category_name }
@@ -819,7 +827,8 @@ module Jazzy
819
827
  # For each extension to be merged, move any MARK from the extension
820
828
  # declaration down to the extension contents so it still shows up.
821
829
  def self.move_merged_extension_marks(decls)
822
- return unless to_be_merged = decls[1..-1]
830
+ return unless to_be_merged = decls[1..]
831
+
823
832
  to_be_merged.each do |ext|
824
833
  child = ext.children.first
825
834
  if child && child.mark.empty?
@@ -834,7 +843,7 @@ module Jazzy
834
843
  def self.merge_code_declaration(decls)
835
844
  first = decls.first
836
845
 
837
- declarations = decls[1..-1].select do |decl|
846
+ declarations = decls[1..].select do |decl|
838
847
  decl.type.swift_extension? &&
839
848
  (decl.other_inherited_types?(@inaccessible_protocols) ||
840
849
  (first.type.swift_extension? && decl.constrained_extension?))
@@ -879,9 +888,11 @@ module Jazzy
879
888
 
880
889
  def self.name_match(name_part, docs)
881
890
  return nil unless name_part
891
+
882
892
  wildcard_expansion = Regexp.escape(name_part)
883
- .gsub('\.\.\.', '[^)]*')
884
- .gsub(/<.*>/, '')
893
+ .gsub('\.\.\.', '[^)]*')
894
+ .gsub(/<.*>/, '')
895
+
885
896
  whole_name_pat = /\A#{wildcard_expansion}\Z/
886
897
  docs.find do |doc|
887
898
  whole_name_pat =~ doc.name
@@ -911,19 +922,26 @@ module Jazzy
911
922
  # - method signatures after they've been processed by the highlighter
912
923
  #
913
924
  # The `after_highlight` flag is used to differentiate between the two modes.
914
- def self.autolink_text(text, doc, root_decls, after_highlight = false)
925
+ #
926
+ # DocC link format - follow Xcode and don't display slash-separated parts.
927
+ # rubocop:disable Metrics/MethodLength
928
+ def self.autolink_text(text, doc, root_decls, after_highlight: false)
915
929
  text.autolink_block(doc.url, '[^\s]+', after_highlight) do |raw_name|
916
- parts = raw_name.sub(/^@/, '') # ignore for custom attribute ref
917
- .split(/(?<!\.)\.(?!\.)/) # dot with no neighboring dots
918
- .reject(&:empty?)
930
+ sym_name =
931
+ (raw_name[/^<doc:(.*)>$/, 1] || raw_name).sub(/(?<!^)-.+$/, '')
932
+
933
+ parts = sym_name
934
+ .sub(/^@/, '') # ignore for custom attribute ref
935
+ .split(%r{(?<!\.)[/.](?!\.)}) # dot or slash, but not '...'
936
+ .reject(&:empty?)
919
937
 
920
938
  # First dot-separated component can match any ancestor or top-level doc
921
939
  first_part = parts.shift
922
940
  name_root = ancestor_name_match(first_part, doc) ||
923
941
  name_match(first_part, root_decls)
924
942
 
925
- # Traverse children via subsequence components, if any
926
- name_traversal(parts, name_root)
943
+ # Traverse children via subsequent components, if any
944
+ [name_traversal(parts, name_root), sym_name.sub(%r{^.*/}, '')]
927
945
  end.autolink_block(doc.url, '[+-]\[\w+(?: ?\(\w+\))? [\w:]+\]',
928
946
  after_highlight) do |raw_name|
929
947
  match = raw_name.match(/([+-])\[(\w+(?: ?\(\w+\))?) ([\w:]+)\]/)
@@ -935,42 +953,50 @@ module Jazzy
935
953
 
936
954
  if name_root
937
955
  # Look up the verb in the subject’s children
938
- name_match(match[1] + match[3], name_root.children)
956
+ [name_match(match[1] + match[3], name_root.children), raw_name]
939
957
  end
940
958
  end.autolink_block(doc.url, '[+-]\w[\w:]*', after_highlight) do |raw_name|
941
- name_match(raw_name, doc.children)
959
+ [name_match(raw_name, doc.children), raw_name]
942
960
  end
943
961
  end
962
+ # rubocop:enable Metrics/MethodLength
944
963
 
945
964
  AUTOLINK_TEXT_FIELDS = %w[return
946
965
  abstract
947
966
  unavailable_message
948
967
  deprecation_message].freeze
949
968
 
969
+ def self.autolink_text_fields(doc, root_decls)
970
+ AUTOLINK_TEXT_FIELDS.each do |field|
971
+ if text = doc.send(field)
972
+ doc.send(field + '=', autolink_text(text, doc, root_decls))
973
+ end
974
+ end
975
+
976
+ (doc.parameters || []).each do |param|
977
+ param[:discussion] =
978
+ autolink_text(param[:discussion], doc, root_decls)
979
+ end
980
+ end
981
+
950
982
  AUTOLINK_HIGHLIGHT_FIELDS = %w[declaration
951
983
  other_language_declaration].freeze
952
984
 
985
+ def self.autolink_highlight_fields(doc, root_decls)
986
+ AUTOLINK_HIGHLIGHT_FIELDS.each do |field|
987
+ if text = doc.send(field)
988
+ doc.send(field + '=',
989
+ autolink_text(text, doc, root_decls, after_highlight: true))
990
+ end
991
+ end
992
+ end
993
+
953
994
  def self.autolink(docs, root_decls)
954
995
  @autolink_root_decls = root_decls
955
996
  docs.each do |doc|
956
997
  doc.children = autolink(doc.children, root_decls)
957
-
958
- AUTOLINK_TEXT_FIELDS.each do |field|
959
- if text = doc.send(field)
960
- doc.send(field + '=', autolink_text(text, doc, root_decls))
961
- end
962
- end
963
-
964
- AUTOLINK_HIGHLIGHT_FIELDS.each do |field|
965
- if text = doc.send(field)
966
- doc.send(field + '=', autolink_text(text, doc, root_decls, true))
967
- end
968
- end
969
-
970
- (doc.parameters || []).each do |param|
971
- param[:discussion] =
972
- autolink_text(param[:discussion], doc, root_decls)
973
- end
998
+ autolink_text_fields(doc, root_decls)
999
+ autolink_highlight_fields(doc, root_decls)
974
1000
  end
975
1001
  end
976
1002