jazzy 0.11.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +3 -20
- data/CHANGELOG.md +133 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile.lock +37 -31
- data/README.md +51 -3
- data/bin/sourcekitten +0 -0
- data/jazzy.gemspec +2 -2
- data/lib/jazzy/config.rb +27 -6
- data/lib/jazzy/doc.rb +0 -4
- data/lib/jazzy/doc_builder.rb +36 -20
- data/lib/jazzy/gem_version.rb +1 -1
- data/lib/jazzy/highlighter.rb +10 -8
- data/lib/jazzy/jazzy_markdown.rb +3 -4
- data/lib/jazzy/source_declaration.rb +62 -6
- data/lib/jazzy/source_declaration/type.rb +8 -0
- data/lib/jazzy/source_mark.rb +11 -0
- data/lib/jazzy/sourcekitten.rb +239 -93
- data/lib/jazzy/stats.rb +4 -0
- data/lib/jazzy/themes/apple/assets/css/jazzy.css.scss +36 -2
- data/lib/jazzy/themes/apple/templates/task.mustache +4 -3
- data/lib/jazzy/themes/fullwidth/assets/css/jazzy.css.scss +35 -7
- data/lib/jazzy/themes/fullwidth/templates/doc.mustache +1 -1
- data/lib/jazzy/themes/fullwidth/templates/task.mustache +4 -3
- data/lib/jazzy/themes/jony/assets/css/jazzy.css.scss +35 -2
- data/lib/jazzy/themes/jony/templates/task.mustache +4 -3
- data/spec/integration_spec.rb +23 -10
- metadata +5 -4
data/bin/sourcekitten
CHANGED
Binary file
|
data/jazzy.gemspec
CHANGED
@@ -5,8 +5,8 @@ require File.expand_path('lib/jazzy/gem_version.rb', File.dirname(__FILE__))
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = 'jazzy'
|
7
7
|
spec.version = Jazzy::VERSION
|
8
|
-
spec.authors = ['JP Simard', 'Tim Anglade', 'Samuel Giddins']
|
9
|
-
spec.email = ['jp@
|
8
|
+
spec.authors = ['JP Simard', 'Tim Anglade', 'Samuel Giddins', 'John Fairhurst']
|
9
|
+
spec.email = ['jp@jpsim.com']
|
10
10
|
spec.summary = 'Soulful docs for Swift & Objective-C.'
|
11
11
|
spec.description = 'Soulful docs for Swift & Objective-C. ' \
|
12
12
|
"Run in your Xcode project's root directory for " \
|
data/lib/jazzy/config.rb
CHANGED
@@ -102,6 +102,14 @@ module Jazzy
|
|
102
102
|
Pathname(Dir[abs_path][0] || abs_path) # Use existing filesystem spelling
|
103
103
|
end
|
104
104
|
|
105
|
+
def hide_swift?
|
106
|
+
hide_declarations == 'swift'
|
107
|
+
end
|
108
|
+
|
109
|
+
def hide_objc?
|
110
|
+
hide_declarations == 'objc'
|
111
|
+
end
|
112
|
+
|
105
113
|
# ──────── Build ────────
|
106
114
|
|
107
115
|
# rubocop:disable Layout/AlignParameters
|
@@ -143,9 +151,9 @@ module Jazzy
|
|
143
151
|
command_line: '--hide-declarations [objc|swift] ',
|
144
152
|
description: 'Hide declarations in the specified language. Given that ' \
|
145
153
|
'generating Swift docs only generates Swift declarations, ' \
|
146
|
-
'this is
|
147
|
-
'
|
148
|
-
'
|
154
|
+
'this is useful for hiding a specific interface for ' \
|
155
|
+
'either Objective-C or mixed Objective-C and Swift ' \
|
156
|
+
'projects.',
|
149
157
|
default: ''
|
150
158
|
|
151
159
|
config_attr :config_file,
|
@@ -165,9 +173,10 @@ module Jazzy
|
|
165
173
|
description: 'Back-compatibility alias for build_tool_arguments.'
|
166
174
|
|
167
175
|
config_attr :sourcekitten_sourcefile,
|
168
|
-
command_line: ['-s', '--sourcekitten-sourcefile
|
169
|
-
|
170
|
-
|
176
|
+
command_line: ['-s', '--sourcekitten-sourcefile filepath1,…filepathN',
|
177
|
+
Array],
|
178
|
+
description: 'File(s) generated from sourcekitten output to parse',
|
179
|
+
parse: ->(paths) { [paths].flatten.map { |path| expand_path(path) } }
|
171
180
|
|
172
181
|
config_attr :source_directory,
|
173
182
|
command_line: '--source-directory DIRPATH',
|
@@ -346,6 +355,18 @@ module Jazzy
|
|
346
355
|
'Example: https://git.io/v4Bcp'],
|
347
356
|
default: []
|
348
357
|
|
358
|
+
config_attr :custom_categories_unlisted_prefix,
|
359
|
+
description: "Prefix for navigation section names that aren't "\
|
360
|
+
'explicitly listed in `custom_categories`.',
|
361
|
+
default: 'Other '
|
362
|
+
|
363
|
+
config_attr :hide_unlisted_documentation,
|
364
|
+
command_line: '--[no-]hide-unlisted-documentation',
|
365
|
+
description: "Don't include documentation in the sidebar from the "\
|
366
|
+
"`documentation` config value that aren't explicitly "\
|
367
|
+
'listed in `custom_categories`.',
|
368
|
+
default: false
|
369
|
+
|
349
370
|
config_attr :custom_head,
|
350
371
|
command_line: '--head HTML',
|
351
372
|
description: 'Custom HTML to inject into <head></head>.',
|
data/lib/jazzy/doc.rb
CHANGED
data/lib/jazzy/doc_builder.rb
CHANGED
@@ -29,23 +29,36 @@ module Jazzy
|
|
29
29
|
# @return [Array] doc structure comprised of
|
30
30
|
# section names & child names & URLs
|
31
31
|
def self.doc_structure_for_docs(docs)
|
32
|
-
docs
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end).map do |sub_child|
|
41
|
-
{ name: "– #{sub_child.name}", url: sub_child.url }
|
42
|
-
end
|
32
|
+
docs
|
33
|
+
.map do |doc|
|
34
|
+
children = children_for_doc(doc)
|
35
|
+
{
|
36
|
+
section: doc.name,
|
37
|
+
url: doc.url,
|
38
|
+
children: children,
|
39
|
+
}
|
43
40
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
41
|
+
.select do |structure|
|
42
|
+
if Config.instance.hide_unlisted_documentation
|
43
|
+
unlisted_prefix = Config.instance.custom_categories_unlisted_prefix
|
44
|
+
structure[:section] != "#{unlisted_prefix}Guides"
|
45
|
+
else
|
46
|
+
true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.children_for_doc(doc)
|
52
|
+
doc.children
|
53
|
+
.sort_by { |c| [c.nav_order, c.name, c.usr || ''] }
|
54
|
+
.flat_map do |child|
|
55
|
+
# FIXME: include arbitrarily nested extensible types
|
56
|
+
[{ name: child.name, url: child.url }] +
|
57
|
+
Array(child.children.select do |sub_child|
|
58
|
+
sub_child.type.swift_extensible? || sub_child.type.extension?
|
59
|
+
end).map do |sub_child|
|
60
|
+
{ name: "– #{sub_child.name}", url: sub_child.url }
|
61
|
+
end
|
49
62
|
end
|
50
63
|
end
|
51
64
|
|
@@ -53,8 +66,9 @@ module Jazzy
|
|
53
66
|
# @param [Config] options
|
54
67
|
# @return [SourceModule] the documented source module
|
55
68
|
def self.build(options)
|
56
|
-
if options.
|
57
|
-
stdout = options.sourcekitten_sourcefile.read
|
69
|
+
if options.sourcekitten_sourcefile_configured
|
70
|
+
stdout = '[' + options.sourcekitten_sourcefile.map(&:read)
|
71
|
+
.join(',') + ']'
|
58
72
|
elsif options.podspec_configured
|
59
73
|
pod_documenter = PodspecDocumenter.new(options.podspec)
|
60
74
|
stdout = pod_documenter.sourcekitten_output(options)
|
@@ -329,6 +343,7 @@ module Jazzy
|
|
329
343
|
name: item.name,
|
330
344
|
abstract: abstract,
|
331
345
|
declaration: item.display_declaration,
|
346
|
+
language: item.display_language,
|
332
347
|
other_language_declaration: item.display_other_language_declaration,
|
333
348
|
usr: item.usr,
|
334
349
|
dash_type: item.type.dash_type,
|
@@ -348,9 +363,10 @@ module Jazzy
|
|
348
363
|
end
|
349
364
|
# rubocop:enable Metrics/MethodLength
|
350
365
|
|
351
|
-
def self.make_task(mark, uid, items)
|
366
|
+
def self.make_task(mark, uid, items, doc_model)
|
352
367
|
{
|
353
368
|
name: mark.name,
|
369
|
+
name_html: (render(doc_model, mark.name) if mark.name),
|
354
370
|
uid: URI.encode(uid),
|
355
371
|
items: items,
|
356
372
|
pre_separator: mark.has_start_dash,
|
@@ -374,7 +390,7 @@ module Jazzy
|
|
374
390
|
else
|
375
391
|
mark_names_counts[uid] = 1
|
376
392
|
end
|
377
|
-
make_task(mark, uid, items)
|
393
|
+
make_task(mark, uid, items, mark_children.first)
|
378
394
|
end
|
379
395
|
end
|
380
396
|
|
data/lib/jazzy/gem_version.rb
CHANGED
data/lib/jazzy/highlighter.rb
CHANGED
@@ -3,6 +3,9 @@ require 'rouge'
|
|
3
3
|
module Jazzy
|
4
4
|
# This module helps highlight code
|
5
5
|
module Highlighter
|
6
|
+
SWIFT = 'swift'.freeze
|
7
|
+
OBJC = 'objective_c'.freeze
|
8
|
+
|
6
9
|
class Formatter < Rouge::Formatters::HTML
|
7
10
|
def initialize(language)
|
8
11
|
@language = language
|
@@ -16,16 +19,15 @@ module Jazzy
|
|
16
19
|
end
|
17
20
|
end
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
22
|
+
def self.highlight_swift(source)
|
23
|
+
highlight(source, SWIFT)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.highlight_objc(source)
|
27
|
+
highlight(source, OBJC)
|
26
28
|
end
|
27
29
|
|
28
|
-
def self.highlight(source, language
|
30
|
+
def self.highlight(source, language)
|
29
31
|
source && Rouge.highlight(source, language, Formatter.new(language))
|
30
32
|
end
|
31
33
|
end
|
data/lib/jazzy/jazzy_markdown.rb
CHANGED
@@ -82,21 +82,21 @@ module Jazzy
|
|
82
82
|
|
83
83
|
def render_aside(type, text)
|
84
84
|
<<-HTML
|
85
|
-
|
85
|
+
</ul><div class="aside aside-#{type.underscore.tr('_', '-')}">
|
86
86
|
<p class="aside-title">#{type.underscore.humanize}</p>
|
87
87
|
#{text}
|
88
|
-
</div>
|
88
|
+
</div><ul>
|
89
89
|
HTML
|
90
90
|
end
|
91
91
|
|
92
92
|
def list(text, list_type)
|
93
93
|
elided = text.gsub!(ELIDED_LI_TOKEN, '')
|
94
94
|
return if text =~ /\A\s*\Z/ && elided
|
95
|
-
return text if text =~ /class="aside-title"/
|
96
95
|
str = "\n"
|
97
96
|
str << (list_type == :ordered ? "<ol>\n" : "<ul>\n")
|
98
97
|
str << text
|
99
98
|
str << (list_type == :ordered ? "</ol>\n" : "</ul>\n")
|
99
|
+
str.gsub(%r{\n?<ul>\n<\/ul>}, '')
|
100
100
|
end
|
101
101
|
|
102
102
|
def block_code(code, language)
|
@@ -112,7 +112,6 @@ module Jazzy
|
|
112
112
|
autolink: true,
|
113
113
|
fenced_code_blocks: true,
|
114
114
|
no_intra_emphasis: true,
|
115
|
-
quote: true,
|
116
115
|
strikethrough: true,
|
117
116
|
space_after_headers: false,
|
118
117
|
tables: true,
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'jazzy/source_declaration/access_control_level'
|
2
2
|
require 'jazzy/source_declaration/type'
|
3
3
|
|
4
|
+
# rubocop:disable Metrics/ClassLength
|
4
5
|
module Jazzy
|
5
6
|
class SourceDeclaration
|
6
7
|
# kind of declaration (e.g. class, variable, function)
|
@@ -13,6 +14,14 @@ module Jazzy
|
|
13
14
|
children.any?
|
14
15
|
end
|
15
16
|
|
17
|
+
def swift?
|
18
|
+
type.swift_type?
|
19
|
+
end
|
20
|
+
|
21
|
+
def highlight_language
|
22
|
+
swift? ? Highlighter::SWIFT : Highlighter::OBJC
|
23
|
+
end
|
24
|
+
|
16
25
|
# When referencing this item from its parent category,
|
17
26
|
# include the content or just link to it directly?
|
18
27
|
def omit_content_from_parent?
|
@@ -67,17 +76,32 @@ module Jazzy
|
|
67
76
|
name.split(/[\(\)]/) if type.objc_category?
|
68
77
|
end
|
69
78
|
|
79
|
+
def swift_objc_extension?
|
80
|
+
type.swift_extension? && usr && usr.start_with?('c:objc')
|
81
|
+
end
|
82
|
+
|
83
|
+
def swift_extension_objc_name
|
84
|
+
return unless type.swift_extension? && usr
|
85
|
+
|
86
|
+
usr.split('(cs)').last
|
87
|
+
end
|
88
|
+
|
89
|
+
# The language in the templates for display
|
90
|
+
def display_language
|
91
|
+
return 'Swift' if swift?
|
92
|
+
|
93
|
+
Config.instance.hide_objc? ? 'Swift' : 'Objective-C'
|
94
|
+
end
|
95
|
+
|
70
96
|
def display_declaration
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
declaration
|
75
|
-
end
|
97
|
+
return declaration if swift?
|
98
|
+
|
99
|
+
Config.instance.hide_objc? ? other_language_declaration : declaration
|
76
100
|
end
|
77
101
|
|
78
102
|
def display_other_language_declaration
|
79
103
|
other_language_declaration unless
|
80
|
-
|
104
|
+
Config.instance.hide_objc? || Config.instance.hide_swift?
|
81
105
|
end
|
82
106
|
|
83
107
|
attr_accessor :file
|
@@ -106,6 +130,8 @@ module Jazzy
|
|
106
130
|
attr_accessor :deprecation_message
|
107
131
|
attr_accessor :unavailable
|
108
132
|
attr_accessor :unavailable_message
|
133
|
+
attr_accessor :generic_requirements
|
134
|
+
attr_accessor :inherited_types
|
109
135
|
|
110
136
|
def usage_discouraged?
|
111
137
|
unavailable || deprecated
|
@@ -115,6 +141,36 @@ module Jazzy
|
|
115
141
|
CGI.unescape(url)
|
116
142
|
end
|
117
143
|
|
144
|
+
def constrained_extension?
|
145
|
+
type.swift_extension? &&
|
146
|
+
generic_requirements
|
147
|
+
end
|
148
|
+
|
149
|
+
def mark_for_children
|
150
|
+
if constrained_extension?
|
151
|
+
SourceMark.new_generic_requirements(generic_requirements)
|
152
|
+
else
|
153
|
+
SourceMark.new
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def inherited_types?
|
158
|
+
inherited_types &&
|
159
|
+
!inherited_types.empty?
|
160
|
+
end
|
161
|
+
|
162
|
+
# Is there at least one inherited type that is not in the given list?
|
163
|
+
def other_inherited_types?(unwanted)
|
164
|
+
return false unless inherited_types?
|
165
|
+
inherited_types.any? { |t| !unwanted.include?(t) }
|
166
|
+
end
|
167
|
+
|
168
|
+
# SourceKit only sets modulename for imported modules
|
169
|
+
def type_from_doc_module?
|
170
|
+
!type.extension? ||
|
171
|
+
(swift? && usr && modulename.nil?)
|
172
|
+
end
|
173
|
+
|
118
174
|
def alternative_abstract
|
119
175
|
if file = alternative_abstract_file
|
120
176
|
Pathname(file).read
|
@@ -77,6 +77,10 @@ module Jazzy
|
|
77
77
|
kind == 'sourcekitten.source.lang.objc.decl.class'
|
78
78
|
end
|
79
79
|
|
80
|
+
def swift_type?
|
81
|
+
kind.include? 'swift'
|
82
|
+
end
|
83
|
+
|
80
84
|
def swift_enum_case?
|
81
85
|
kind == 'source.lang.swift.decl.enumcase'
|
82
86
|
end
|
@@ -110,6 +114,10 @@ module Jazzy
|
|
110
114
|
kind == 'source.lang.swift.decl.protocol'
|
111
115
|
end
|
112
116
|
|
117
|
+
def swift_typealias?
|
118
|
+
kind == 'source.lang.swift.decl.typealias'
|
119
|
+
end
|
120
|
+
|
113
121
|
def param?
|
114
122
|
# SourceKit strangely categorizes initializer parameters as local
|
115
123
|
# variables, so both kinds represent a parameter in jazzy.
|
data/lib/jazzy/source_mark.rb
CHANGED
@@ -28,6 +28,12 @@ module Jazzy
|
|
28
28
|
self.name = mark_string[start_index..end_index]
|
29
29
|
end
|
30
30
|
|
31
|
+
def self.new_generic_requirements(requirements)
|
32
|
+
marked_up = requirements.gsub(/\b([^=:]\S*)\b/, '`\1`')
|
33
|
+
text = "Available where #{marked_up}"
|
34
|
+
new(text)
|
35
|
+
end
|
36
|
+
|
31
37
|
def empty?
|
32
38
|
!name && !has_start_dash && !has_end_dash
|
33
39
|
end
|
@@ -37,5 +43,10 @@ module Jazzy
|
|
37
43
|
self.has_start_dash = other.has_start_dash
|
38
44
|
self.has_end_dash = other.has_end_dash
|
39
45
|
end
|
46
|
+
|
47
|
+
# Can we merge the contents of another mark into our own?
|
48
|
+
def can_merge?(other)
|
49
|
+
other.empty? || other.name == name
|
50
|
+
end
|
40
51
|
end
|
41
52
|
end
|
data/lib/jazzy/sourcekitten.rb
CHANGED
@@ -62,10 +62,11 @@ module Jazzy
|
|
62
62
|
# Group root-level docs by custom categories (if any) and type
|
63
63
|
def self.group_docs(docs)
|
64
64
|
custom_categories, docs = group_custom_categories(docs)
|
65
|
+
unlisted_prefix = Config.instance.custom_categories_unlisted_prefix
|
65
66
|
type_categories, uncategorized = group_type_categories(
|
66
|
-
docs, custom_categories.any? ?
|
67
|
+
docs, custom_categories.any? ? unlisted_prefix : ''
|
67
68
|
)
|
68
|
-
custom_categories + type_categories + uncategorized
|
69
|
+
custom_categories + merge_categories(type_categories) + uncategorized
|
69
70
|
end
|
70
71
|
|
71
72
|
def self.group_custom_categories(docs)
|
@@ -98,6 +99,19 @@ module Jazzy
|
|
98
99
|
[group.compact, docs]
|
99
100
|
end
|
100
101
|
|
102
|
+
# Join categories with the same name (eg. ObjC and Swift classes)
|
103
|
+
def self.merge_categories(categories)
|
104
|
+
merged = []
|
105
|
+
categories.each do |new_category|
|
106
|
+
if existing = merged.find { |c| c.name == new_category.name }
|
107
|
+
existing.children += new_category.children
|
108
|
+
else
|
109
|
+
merged.append(new_category)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
merged
|
113
|
+
end
|
114
|
+
|
101
115
|
def self.make_group(group, name, abstract, url_name = nil)
|
102
116
|
group.reject! { |doc| doc.name.empty? }
|
103
117
|
unless group.empty?
|
@@ -111,6 +125,18 @@ module Jazzy
|
|
111
125
|
end
|
112
126
|
end
|
113
127
|
|
128
|
+
# Merge consecutive sections with the same mark into one section
|
129
|
+
def self.merge_consecutive_marks(docs)
|
130
|
+
prev_mark = nil
|
131
|
+
docs.each do |doc|
|
132
|
+
if prev_mark && prev_mark.can_merge?(doc.mark)
|
133
|
+
doc.mark = prev_mark
|
134
|
+
end
|
135
|
+
prev_mark = doc.mark
|
136
|
+
merge_consecutive_marks(doc.children)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
114
140
|
def self.sanitize_filename(doc)
|
115
141
|
unsafe_filename = doc.url_name || doc.name
|
116
142
|
sanitzation_enabled = Config.instance.use_safe_filenames
|
@@ -185,7 +211,9 @@ module Jazzy
|
|
185
211
|
def self.use_spm?(options)
|
186
212
|
options.swift_build_tool == :spm ||
|
187
213
|
(!options.swift_build_tool_configured &&
|
188
|
-
Dir['*.xcodeproj'].empty?
|
214
|
+
Dir['*.xcodeproj', '*.xcworkspace'].empty? &&
|
215
|
+
!options.build_tool_arguments.include?('-project') &&
|
216
|
+
!options.build_tool_arguments.include?('-workspace'))
|
189
217
|
end
|
190
218
|
|
191
219
|
# Builds SourceKitten arguments based on Jazzy options
|
@@ -195,7 +223,7 @@ module Jazzy
|
|
195
223
|
arguments += objc_arguments_from_options(options)
|
196
224
|
else
|
197
225
|
arguments += ['--spm'] if use_spm?(options)
|
198
|
-
|
226
|
+
unless options.module_name.empty?
|
199
227
|
arguments += ['--module-name', options.module_name]
|
200
228
|
end
|
201
229
|
arguments += ['--']
|
@@ -260,8 +288,13 @@ module Jazzy
|
|
260
288
|
def self.should_document?(doc)
|
261
289
|
return false if doc['key.doc.comment'].to_s.include?(':nodoc:')
|
262
290
|
|
291
|
+
type = SourceDeclaration::Type.new(doc['key.kind'])
|
292
|
+
|
263
293
|
# Always document Objective-C declarations.
|
264
|
-
return true
|
294
|
+
return true unless type.swift_type?
|
295
|
+
|
296
|
+
# Don't document Swift types if we are hiding Swift
|
297
|
+
return false if Config.instance.hide_swift?
|
265
298
|
|
266
299
|
# Don't document @available declarations with no USR, since it means
|
267
300
|
# they're unavailable.
|
@@ -269,22 +302,29 @@ module Jazzy
|
|
269
302
|
return false
|
270
303
|
end
|
271
304
|
|
272
|
-
# Document
|
273
|
-
type = SourceDeclaration::Type.new(doc['key.kind'])
|
305
|
+
# Document enum elements, since we can't tell their ACL.
|
274
306
|
return true if type.swift_enum_element?
|
275
|
-
if
|
276
|
-
|
277
|
-
subtype = SourceDeclaration::Type.new(subdoc['key.kind'])
|
278
|
-
!subtype.mark? && should_document?(subdoc)
|
279
|
-
end
|
280
|
-
end
|
307
|
+
# Document extensions if they might have parts covered by the ACL.
|
308
|
+
return should_document_swift_extension?(doc) if type.swift_extension?
|
281
309
|
|
282
310
|
acl_ok = SourceDeclaration::AccessControlLevel.from_doc(doc) >= @min_acl
|
283
|
-
|
311
|
+
unless acl_ok
|
312
|
+
@stats.add_acl_skipped
|
313
|
+
@inaccessible_protocols.append(doc['key.name']) if type.swift_protocol?
|
314
|
+
end
|
315
|
+
acl_ok
|
284
316
|
end
|
285
317
|
# rubocop:enable Metrics/CyclomaticComplexity
|
286
318
|
# rubocop:enable Metrics/PerceivedComplexity
|
287
319
|
|
320
|
+
def self.should_document_swift_extension?(doc)
|
321
|
+
doc['key.inheritedtypes'] ||
|
322
|
+
Array(doc['key.substructure']).any? do |subdoc|
|
323
|
+
subtype = SourceDeclaration::Type.new(subdoc['key.kind'])
|
324
|
+
!subtype.mark? && should_document?(subdoc)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
288
328
|
def self.should_mark_undocumented(filepath)
|
289
329
|
source_directory = Config.instance.source_directory.to_s
|
290
330
|
(filepath || '').start_with?(source_directory)
|
@@ -294,14 +334,14 @@ module Jazzy
|
|
294
334
|
make_default_doc_info(declaration)
|
295
335
|
|
296
336
|
filepath = doc['key.filepath']
|
297
|
-
|
298
|
-
if
|
337
|
+
|
338
|
+
if !declaration.swift? || should_mark_undocumented(filepath)
|
299
339
|
@stats.add_undocumented(declaration)
|
300
340
|
return nil if @skip_undocumented
|
301
341
|
declaration.abstract = undocumented_abstract
|
302
342
|
else
|
303
343
|
declaration.abstract = Markdown.render(doc['key.doc.comment'] || '',
|
304
|
-
|
344
|
+
declaration.highlight_language)
|
305
345
|
end
|
306
346
|
|
307
347
|
declaration
|
@@ -320,16 +360,7 @@ module Jazzy
|
|
320
360
|
def self.make_doc_info(doc, declaration)
|
321
361
|
return unless should_document?(doc)
|
322
362
|
|
323
|
-
|
324
|
-
declaration.declaration =
|
325
|
-
Highlighter.highlight(doc['key.parsed_declaration'])
|
326
|
-
declaration.other_language_declaration =
|
327
|
-
Highlighter.highlight(doc['key.swift_declaration'], 'swift')
|
328
|
-
else
|
329
|
-
declaration.declaration =
|
330
|
-
Highlighter.highlight(make_swift_declaration(doc, declaration))
|
331
|
-
end
|
332
|
-
|
363
|
+
highlight_declaration(doc, declaration)
|
333
364
|
make_deprecation_info(doc, declaration)
|
334
365
|
|
335
366
|
unless doc['key.doc.full_as_xml']
|
@@ -337,7 +368,7 @@ module Jazzy
|
|
337
368
|
end
|
338
369
|
|
339
370
|
declaration.abstract = Markdown.render(doc['key.doc.comment'] || '',
|
340
|
-
|
371
|
+
declaration.highlight_language)
|
341
372
|
declaration.discussion = ''
|
342
373
|
declaration.return = Markdown.rendered_returns
|
343
374
|
declaration.parameters = parameters(doc, Markdown.rendered_parameters)
|
@@ -345,6 +376,18 @@ module Jazzy
|
|
345
376
|
@stats.add_documented
|
346
377
|
end
|
347
378
|
|
379
|
+
def self.highlight_declaration(doc, declaration)
|
380
|
+
if declaration.swift?
|
381
|
+
declaration.declaration =
|
382
|
+
Highlighter.highlight_swift(make_swift_declaration(doc, declaration))
|
383
|
+
else
|
384
|
+
declaration.declaration =
|
385
|
+
Highlighter.highlight_objc(doc['key.parsed_declaration'])
|
386
|
+
declaration.other_language_declaration =
|
387
|
+
Highlighter.highlight_swift(doc['key.swift_declaration'])
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
348
391
|
def self.make_deprecation_info(doc, declaration)
|
349
392
|
if declaration.deprecated
|
350
393
|
declaration.deprecation_message =
|
@@ -398,9 +441,9 @@ module Jazzy
|
|
398
441
|
annotated.empty? ||
|
399
442
|
parsed &&
|
400
443
|
(annotated.include?(' = default') || # SR-2608
|
401
|
-
|
402
|
-
|
403
|
-
parsed.include?(
|
444
|
+
(parsed.scan(/@autoclosure|@escaping/).count >
|
445
|
+
annotated.scan(/@autoclosure|@escaping/).count) || # SR-6321
|
446
|
+
parsed.include?("\n"))
|
404
447
|
end
|
405
448
|
|
406
449
|
# Replace the fully qualified name of a type with its base name
|
@@ -422,6 +465,9 @@ module Jazzy
|
|
422
465
|
# From source code
|
423
466
|
parsed_decl = doc['key.parsed_declaration']
|
424
467
|
|
468
|
+
# Don't present type attributes on extensions
|
469
|
+
return parsed_decl if declaration.type.extension?
|
470
|
+
|
425
471
|
decl =
|
426
472
|
if prefer_parsed_decl?(parsed_decl, annotated_decl_body)
|
427
473
|
# Strip any attrs captured by parsed version
|
@@ -443,14 +489,10 @@ module Jazzy
|
|
443
489
|
end
|
444
490
|
|
445
491
|
def self.make_substructure(doc, declaration)
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
)
|
451
|
-
else
|
452
|
-
[]
|
453
|
-
end
|
492
|
+
return [] unless subdocs = doc['key.substructure']
|
493
|
+
make_source_declarations(subdocs,
|
494
|
+
declaration,
|
495
|
+
declaration.mark_for_children)
|
454
496
|
end
|
455
497
|
|
456
498
|
# rubocop:disable Metrics/MethodLength
|
@@ -471,8 +513,7 @@ module Jazzy
|
|
471
513
|
declaration.type = SourceDeclaration::Type.new(doc['key.kind'])
|
472
514
|
declaration.typename = doc['key.typename']
|
473
515
|
declaration.objc_name = doc['key.name']
|
474
|
-
documented_name = if Config.instance.
|
475
|
-
doc['key.swift_name']
|
516
|
+
documented_name = if Config.instance.hide_objc? && doc['key.swift_name']
|
476
517
|
doc['key.swift_name']
|
477
518
|
else
|
478
519
|
declaration.objc_name
|
@@ -508,10 +549,17 @@ module Jazzy
|
|
508
549
|
declaration.end_line = doc['key.parsed_scope.end']
|
509
550
|
declaration.deprecated = doc['key.always_deprecated']
|
510
551
|
declaration.unavailable = doc['key.always_unavailable']
|
552
|
+
declaration.generic_requirements =
|
553
|
+
find_generic_requirements(doc['key.parsed_declaration'])
|
554
|
+
inherited_types = doc['key.inheritedtypes'] || []
|
555
|
+
declaration.inherited_types =
|
556
|
+
inherited_types.map { |type| type['key.name'] }.compact
|
511
557
|
|
512
558
|
next unless make_doc_info(doc, declaration)
|
513
|
-
make_substructure(doc, declaration)
|
514
|
-
next if declaration.type.extension? &&
|
559
|
+
declaration.children = make_substructure(doc, declaration)
|
560
|
+
next if declaration.type.extension? &&
|
561
|
+
declaration.children.empty? &&
|
562
|
+
!declaration.inherited_types?
|
515
563
|
declarations << declaration
|
516
564
|
end
|
517
565
|
declarations
|
@@ -520,12 +568,22 @@ module Jazzy
|
|
520
568
|
# rubocop:enable Metrics/CyclomaticComplexity
|
521
569
|
# rubocop:enable Metrics/MethodLength
|
522
570
|
|
571
|
+
def self.find_generic_requirements(parsed_declaration)
|
572
|
+
parsed_declaration =~ /\bwhere\s+(.*)$/m
|
573
|
+
return nil unless Regexp.last_match
|
574
|
+
Regexp.last_match[1].gsub(/\s+/, ' ')
|
575
|
+
end
|
576
|
+
|
523
577
|
# Expands extensions of nested types declared at the top level into
|
524
578
|
# a tree so they can be deduplicated properly
|
525
579
|
def self.expand_extensions(decls)
|
526
580
|
decls.map do |decl|
|
527
581
|
next decl unless decl.type.extension? && decl.name.include?('.')
|
528
582
|
|
583
|
+
# Don't expand the Swift namespace if we're in ObjC mode.
|
584
|
+
# ex: NS_SWIFT_NAME(Foo.Bar) should not create top-level Foo
|
585
|
+
next decl if decl.swift_objc_extension? && !Config.instance.hide_objc?
|
586
|
+
|
529
587
|
name_parts = decl.name.split('.')
|
530
588
|
decl.name = name_parts.pop
|
531
589
|
expand_extension(decl, name_parts, decls)
|
@@ -539,6 +597,7 @@ module Jazzy
|
|
539
597
|
SourceDeclaration.new.tap do |decl|
|
540
598
|
make_default_doc_info(decl)
|
541
599
|
decl.name = name
|
600
|
+
decl.modulename = extension.modulename
|
542
601
|
decl.type = extension.type
|
543
602
|
decl.mark = extension.mark
|
544
603
|
decl.usr = candidates.first.usr unless candidates.empty?
|
@@ -561,10 +620,10 @@ module Jazzy
|
|
561
620
|
.group_by { |d| deduplication_key(d, declarations) }
|
562
621
|
.values
|
563
622
|
|
564
|
-
duplicate_groups.
|
623
|
+
duplicate_groups.flat_map do |group|
|
565
624
|
# Put extended type (if present) before extensions
|
566
625
|
merge_declarations(group)
|
567
|
-
end
|
626
|
+
end.compact
|
568
627
|
end
|
569
628
|
|
570
629
|
# Returns true if an Objective-C declaration is mergeable.
|
@@ -574,13 +633,28 @@ module Jazzy
|
|
574
633
|
&& name_match(decl.objc_category_name[0], root_decls))
|
575
634
|
end
|
576
635
|
|
636
|
+
# Returns if a Swift declaration is mergeable.
|
637
|
+
# Start off merging in typealiases to help understand extensions.
|
638
|
+
def self.mergeable_swift?(decl)
|
639
|
+
decl.type.swift_extensible? ||
|
640
|
+
decl.type.swift_extension? ||
|
641
|
+
decl.type.swift_typealias?
|
642
|
+
end
|
643
|
+
|
577
644
|
# Two declarations get merged if they have the same deduplication key.
|
578
645
|
def self.deduplication_key(decl, root_decls)
|
579
|
-
|
646
|
+
# Swift extension of objc class
|
647
|
+
if decl.swift_objc_extension?
|
648
|
+
[decl.swift_extension_objc_name, :objc_class_and_categories]
|
649
|
+
# Swift type or Swift extension of Swift type
|
650
|
+
elsif mergeable_swift?(decl)
|
580
651
|
[decl.usr, decl.name]
|
652
|
+
# Objc categories and classes
|
581
653
|
elsif mergeable_objc?(decl, root_decls)
|
582
|
-
|
654
|
+
# Using the ObjC name to match swift_objc_extension.
|
655
|
+
name, _ = decl.objc_category_name || decl.objc_name
|
583
656
|
[name, :objc_class_and_categories]
|
657
|
+
# Non-mergable declarations (funcs, typedefs etc...)
|
584
658
|
else
|
585
659
|
[decl.usr, decl.name, decl.type.kind]
|
586
660
|
end
|
@@ -599,17 +673,36 @@ module Jazzy
|
|
599
673
|
end
|
600
674
|
typedecl = typedecls.first
|
601
675
|
|
676
|
+
extensions = reject_inaccessible_extensions(typedecl, extensions)
|
677
|
+
|
602
678
|
if typedecl
|
603
679
|
if typedecl.type.swift_protocol?
|
604
|
-
|
605
|
-
mark_members_from_protocol_extension(extensions)
|
680
|
+
mark_and_merge_protocol_extensions(typedecl, extensions)
|
606
681
|
extensions.reject! { |ext| ext.children.empty? }
|
607
682
|
end
|
608
683
|
|
609
|
-
|
684
|
+
merge_objc_declaration_marks(typedecl, extensions)
|
610
685
|
end
|
611
686
|
|
612
|
-
|
687
|
+
# Keep type-aliases separate from any extensions
|
688
|
+
if typedecl && typedecl.type.swift_typealias?
|
689
|
+
[merge_type_and_extensions(typedecls, []),
|
690
|
+
merge_type_and_extensions([], extensions)]
|
691
|
+
else
|
692
|
+
merge_type_and_extensions(typedecls, extensions)
|
693
|
+
end
|
694
|
+
end
|
695
|
+
# rubocop:enable Metrics/MethodLength
|
696
|
+
|
697
|
+
def self.merge_type_and_extensions(typedecls, extensions)
|
698
|
+
# Constrained extensions at the end
|
699
|
+
constrained, regular_exts = extensions.partition(&:constrained_extension?)
|
700
|
+
decls = typedecls + regular_exts + constrained
|
701
|
+
return nil if decls.empty?
|
702
|
+
|
703
|
+
move_merged_extension_marks(decls)
|
704
|
+
merge_code_declaration(decls)
|
705
|
+
|
613
706
|
decls.first.tap do |merged|
|
614
707
|
merged.children = deduplicate_declarations(
|
615
708
|
decls.flat_map(&:children).uniq,
|
@@ -619,58 +712,112 @@ module Jazzy
|
|
619
712
|
end
|
620
713
|
end
|
621
714
|
end
|
622
|
-
# rubocop:enable Metrics/MethodLength
|
623
715
|
|
716
|
+
# Now we know all the public types and all the private protocols,
|
717
|
+
# reject extensions that add public protocols to private types
|
718
|
+
# or add private protocols to public types.
|
719
|
+
def self.reject_inaccessible_extensions(typedecl, extensions)
|
720
|
+
swift_exts, objc_exts = extensions.partition(&:swift?)
|
721
|
+
|
722
|
+
# Reject extensions that are just conformances to private protocols
|
723
|
+
unwanted_exts, wanted_exts = swift_exts.partition do |ext|
|
724
|
+
ext.children.empty? &&
|
725
|
+
!ext.other_inherited_types?(@inaccessible_protocols)
|
726
|
+
end
|
727
|
+
|
728
|
+
# Given extensions of a type from this module, without the
|
729
|
+
# type itself, the type must be private and the extensions
|
730
|
+
# should be rejected.
|
731
|
+
if !typedecl &&
|
732
|
+
wanted_exts.first &&
|
733
|
+
wanted_exts.first.type_from_doc_module?
|
734
|
+
unwanted_exts += wanted_exts
|
735
|
+
wanted_exts = []
|
736
|
+
end
|
737
|
+
|
738
|
+
# Don't tell the user to document them
|
739
|
+
unwanted_exts.each { |e| @stats.remove_undocumented(e) }
|
740
|
+
|
741
|
+
objc_exts + wanted_exts
|
742
|
+
end
|
743
|
+
|
744
|
+
# Protocol extensions.
|
745
|
+
#
|
624
746
|
# If any of the extensions provide default implementations for methods in
|
625
747
|
# the given protocol, merge those members into the protocol doc instead of
|
626
748
|
# keeping them on the extension. These get a “Default implementation”
|
627
|
-
# annotation in the generated docs.
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
749
|
+
# annotation in the generated docs. Default implementations added by
|
750
|
+
# conditional extensions are annotated but listed separately.
|
751
|
+
#
|
752
|
+
# Protocol methods provided only in an extension and not in the protocol
|
753
|
+
# itself are a special beast: they do not use dynamic dispatch. These get an
|
754
|
+
# “Extension method” annotation in the generated docs.
|
755
|
+
def self.mark_and_merge_protocol_extensions(protocol, extensions)
|
756
|
+
extensions.each do |ext|
|
757
|
+
ext.children = ext.children.select do |ext_member|
|
758
|
+
proto_member = protocol.children.find do |p|
|
759
|
+
p.name == ext_member.name && p.type == ext_member.type
|
633
760
|
end
|
634
|
-
|
635
|
-
|
636
|
-
|
761
|
+
|
762
|
+
# Extension-only method, keep.
|
763
|
+
unless proto_member
|
764
|
+
ext_member.from_protocol_extension = true
|
765
|
+
next true
|
766
|
+
end
|
767
|
+
|
768
|
+
# Default impl but constrained, mark and keep.
|
769
|
+
if ext.constrained_extension?
|
770
|
+
ext_member.default_impl_abstract = ext_member.abstract
|
771
|
+
ext_member.abstract = nil
|
772
|
+
next true
|
637
773
|
end
|
774
|
+
|
775
|
+
# Default impl for all users, merge.
|
776
|
+
proto_member.default_impl_abstract = ext_member.abstract
|
777
|
+
next false
|
638
778
|
end
|
639
779
|
end
|
640
780
|
end
|
641
781
|
|
642
|
-
#
|
643
|
-
#
|
644
|
-
|
645
|
-
|
782
|
+
# Mark children merged from categories with the name of category
|
783
|
+
# (unless they already have a mark)
|
784
|
+
def self.merge_objc_declaration_marks(typedecl, extensions)
|
785
|
+
return unless typedecl.type.objc_class?
|
646
786
|
extensions.each do |ext|
|
647
|
-
ext.
|
648
|
-
|
649
|
-
end
|
787
|
+
_, category_name = ext.objc_category_name
|
788
|
+
ext.children.each { |c| c.mark.name ||= category_name }
|
650
789
|
end
|
651
790
|
end
|
652
791
|
|
653
|
-
#
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
end
|
662
|
-
else
|
663
|
-
# If the Swift extension has a mark and the first child doesn't
|
664
|
-
# then copy the mark contents down so it still shows up.
|
665
|
-
extensions.each do |ext|
|
666
|
-
child = ext.children.first
|
667
|
-
if child && child.mark.empty?
|
668
|
-
child.mark.copy(ext.mark)
|
669
|
-
end
|
792
|
+
# For each extension to be merged, move any MARK from the extension
|
793
|
+
# declaration down to the extension contents so it still shows up.
|
794
|
+
def self.move_merged_extension_marks(decls)
|
795
|
+
return unless to_be_merged = decls[1..-1]
|
796
|
+
to_be_merged.each do |ext|
|
797
|
+
child = ext.children.first
|
798
|
+
if child && child.mark.empty?
|
799
|
+
child.mark.copy(ext.mark)
|
670
800
|
end
|
671
801
|
end
|
672
802
|
end
|
673
803
|
|
804
|
+
# Merge useful information added by extensions into the main
|
805
|
+
# declaration: public protocol conformances and, for top-level extensions,
|
806
|
+
# further conditional extensions of the same type.
|
807
|
+
def self.merge_code_declaration(decls)
|
808
|
+
first = decls.first
|
809
|
+
|
810
|
+
declarations = decls[1..-1].select do |decl|
|
811
|
+
decl.type.swift_extension? &&
|
812
|
+
(decl.other_inherited_types?(@inaccessible_protocols) ||
|
813
|
+
(first.type.swift_extension? && decl.constrained_extension?))
|
814
|
+
end.map(&:declaration)
|
815
|
+
|
816
|
+
unless declarations.empty?
|
817
|
+
first.declaration = declarations.prepend(first.declaration).uniq.join
|
818
|
+
end
|
819
|
+
end
|
820
|
+
|
674
821
|
# Apply filtering based on the "included" and "excluded" flags.
|
675
822
|
def self.filter_files(json)
|
676
823
|
json = filter_included_files(json) if Config.instance.included_files.any?
|
@@ -826,19 +973,18 @@ module Jazzy
|
|
826
973
|
@min_acl = min_acl
|
827
974
|
@skip_undocumented = skip_undocumented
|
828
975
|
@stats = Stats.new
|
829
|
-
|
976
|
+
@inaccessible_protocols = []
|
977
|
+
sourcekitten_json = filter_files(JSON.parse(sourcekitten_output).flatten)
|
830
978
|
docs = make_source_declarations(sourcekitten_json).concat inject_docs
|
831
979
|
docs = expand_extensions(docs)
|
832
980
|
docs = deduplicate_declarations(docs)
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
# than min_acl
|
838
|
-
docs = docs.reject { |doc| doc.type.swift_enum_element? }
|
839
|
-
end
|
981
|
+
docs = reject_objc_types(docs)
|
982
|
+
# Remove top-level enum cases because it means they have an ACL lower
|
983
|
+
# than min_acl
|
984
|
+
docs = docs.reject { |doc| doc.type.swift_enum_element? }
|
840
985
|
ungrouped_docs = docs
|
841
986
|
docs = group_docs(docs)
|
987
|
+
merge_consecutive_marks(docs)
|
842
988
|
make_doc_urls(docs)
|
843
989
|
autolink(docs, ungrouped_docs)
|
844
990
|
[docs, @stats]
|