jazzy 0.10.0 → 0.13.0

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.
@@ -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
@@ -65,7 +65,7 @@ module Jazzy
65
65
  type_categories, uncategorized = group_type_categories(
66
66
  docs, custom_categories.any? ? 'Other ' : ''
67
67
  )
68
- custom_categories + type_categories + uncategorized
68
+ custom_categories + merge_categories(type_categories) + uncategorized
69
69
  end
70
70
 
71
71
  def self.group_custom_categories(docs)
@@ -98,6 +98,19 @@ module Jazzy
98
98
  [group.compact, docs]
99
99
  end
100
100
 
101
+ # Join categories with the same name (eg. ObjC and Swift classes)
102
+ def self.merge_categories(categories)
103
+ merged = []
104
+ categories.each do |new_category|
105
+ if existing = merged.find { |c| c.name == new_category.name }
106
+ existing.children += new_category.children
107
+ else
108
+ merged.append(new_category)
109
+ end
110
+ end
111
+ merged
112
+ end
113
+
101
114
  def self.make_group(group, name, abstract, url_name = nil)
102
115
  group.reject! { |doc| doc.name.empty? }
103
116
  unless group.empty?
@@ -111,6 +124,18 @@ module Jazzy
111
124
  end
112
125
  end
113
126
 
127
+ # Merge consecutive sections with the same mark into one section
128
+ def self.merge_consecutive_marks(docs)
129
+ prev_mark = nil
130
+ docs.each do |doc|
131
+ if prev_mark && prev_mark.can_merge?(doc.mark)
132
+ doc.mark = prev_mark
133
+ end
134
+ prev_mark = doc.mark
135
+ merge_consecutive_marks(doc.children)
136
+ end
137
+ end
138
+
114
139
  def self.sanitize_filename(doc)
115
140
  unsafe_filename = doc.url_name || doc.name
116
141
  sanitzation_enabled = Config.instance.use_safe_filenames
@@ -126,12 +151,11 @@ module Jazzy
126
151
  # @return [Hash] input docs with URLs
127
152
  def self.make_doc_urls(docs)
128
153
  docs.each do |doc|
129
- if !doc.parent_in_docs || doc.children.count > 0
130
- # Create HTML page for this doc if it has children or is root-level
154
+ if doc.render_as_page?
131
155
  doc.url = (
132
156
  subdir_for_doc(doc) +
133
157
  [sanitize_filename(doc) + '.html']
134
- ).join('/')
158
+ ).map { |path| ERB::Util.url_encode(path) }.join('/')
135
159
  doc.children = make_doc_urls(doc.children)
136
160
  else
137
161
  # Don't create HTML page for this doc if it doesn't have children
@@ -140,8 +164,8 @@ module Jazzy
140
164
  warn 'A compile error prevented ' + doc.fully_qualified_name +
141
165
  ' from receiving a unique USR. Documentation may be ' \
142
166
  'incomplete. Please check for compile errors by running ' \
143
- '`xcodebuild ' \
144
- "#{Config.instance.xcodebuild_arguments.shelljoin}`."
167
+ '`xcodebuild` or `swift build` with arguments ' \
168
+ "`#{Config.instance.build_tool_arguments.shelljoin}`."
145
169
  end
146
170
  id = doc.usr
147
171
  unless id
@@ -159,17 +183,17 @@ module Jazzy
159
183
  end
160
184
  # rubocop:enable Metrics/MethodLength
161
185
 
162
- # Determine the subdirectory in which a doc should be placed
186
+ # Determine the subdirectory in which a doc should be placed.
187
+ # Guides in the root for back-compatibility.
188
+ # Declarations under outer namespace type (Structures, Classes, etc.)
163
189
  def self.subdir_for_doc(doc)
164
- # We always want to create top-level subdirs according to type (Struct,
165
- # Class, etc).
190
+ return [] if doc.type.markdown?
166
191
  top_level_decl = doc.namespace_path.first
167
- if top_level_decl && top_level_decl.type && top_level_decl.type.name
168
- # File program elements under top ancestor’s type (Struct, Class, etc.)
192
+ if top_level_decl.type.name
169
193
  [top_level_decl.type.plural_url_name] +
170
194
  doc.namespace_ancestors.map(&:name)
171
195
  else
172
- # Categories live in their own directory
196
+ # Category - in the root
173
197
  []
174
198
  end
175
199
  end
@@ -183,22 +207,33 @@ module Jazzy
183
207
  end.select { |x| x }.flatten(1)
184
208
  end
185
209
 
210
+ def self.use_spm?(options)
211
+ options.swift_build_tool == :spm ||
212
+ (!options.swift_build_tool_configured &&
213
+ Dir['*.xcodeproj', '*.xcworkspace'].empty? &&
214
+ !options.build_tool_arguments.include?('-project') &&
215
+ !options.build_tool_arguments.include?('-workspace'))
216
+ end
217
+
186
218
  # Builds SourceKitten arguments based on Jazzy options
187
219
  def self.arguments_from_options(options)
188
220
  arguments = ['doc']
189
- arguments += if options.objc_mode
190
- objc_arguments_from_options(options)
191
- elsif !options.module_name.empty?
192
- ['--module-name', options.module_name, '--']
193
- else
194
- ['--']
195
- end
196
- arguments + options.xcodebuild_arguments
221
+ if options.objc_mode
222
+ arguments += objc_arguments_from_options(options)
223
+ else
224
+ arguments += ['--spm'] if use_spm?(options)
225
+ unless options.module_name.empty?
226
+ arguments += ['--module-name', options.module_name]
227
+ end
228
+ arguments += ['--']
229
+ end
230
+
231
+ arguments + options.build_tool_arguments
197
232
  end
198
233
 
199
234
  def self.objc_arguments_from_options(options)
200
235
  arguments = []
201
- if options.xcodebuild_arguments.empty?
236
+ if options.build_tool_arguments.empty?
202
237
  arguments += ['--objc', options.umbrella_header.to_s, '--', '-x',
203
238
  'objective-c', '-isysroot',
204
239
  `xcrun --show-sdk-path --sdk #{options.sdk}`.chomp,
@@ -252,8 +287,13 @@ module Jazzy
252
287
  def self.should_document?(doc)
253
288
  return false if doc['key.doc.comment'].to_s.include?(':nodoc:')
254
289
 
290
+ type = SourceDeclaration::Type.new(doc['key.kind'])
291
+
255
292
  # Always document Objective-C declarations.
256
- return true if Config.instance.objc_mode
293
+ return true unless type.swift_type?
294
+
295
+ # Don't document Swift types if we are hiding Swift
296
+ return false if Config.instance.hide_swift?
257
297
 
258
298
  # Don't document @available declarations with no USR, since it means
259
299
  # they're unavailable.
@@ -261,22 +301,29 @@ module Jazzy
261
301
  return false
262
302
  end
263
303
 
264
- # Document extensions & enum elements, since we can't tell their ACL.
265
- type = SourceDeclaration::Type.new(doc['key.kind'])
304
+ # Document enum elements, since we can't tell their ACL.
266
305
  return true if type.swift_enum_element?
267
- if type.swift_extension?
268
- return Array(doc['key.substructure']).any? do |subdoc|
269
- subtype = SourceDeclaration::Type.new(subdoc['key.kind'])
270
- !subtype.mark? && should_document?(subdoc)
271
- end
272
- end
306
+ # Document extensions if they might have parts covered by the ACL.
307
+ return should_document_swift_extension?(doc) if type.swift_extension?
273
308
 
274
309
  acl_ok = SourceDeclaration::AccessControlLevel.from_doc(doc) >= @min_acl
275
- acl_ok.tap { @stats.add_acl_skipped unless acl_ok }
310
+ unless acl_ok
311
+ @stats.add_acl_skipped
312
+ @inaccessible_protocols.append(doc['key.name']) if type.swift_protocol?
313
+ end
314
+ acl_ok
276
315
  end
277
316
  # rubocop:enable Metrics/CyclomaticComplexity
278
317
  # rubocop:enable Metrics/PerceivedComplexity
279
318
 
319
+ def self.should_document_swift_extension?(doc)
320
+ doc['key.inheritedtypes'] ||
321
+ Array(doc['key.substructure']).any? do |subdoc|
322
+ subtype = SourceDeclaration::Type.new(subdoc['key.kind'])
323
+ !subtype.mark? && should_document?(subdoc)
324
+ end
325
+ end
326
+
280
327
  def self.should_mark_undocumented(filepath)
281
328
  source_directory = Config.instance.source_directory.to_s
282
329
  (filepath || '').start_with?(source_directory)
@@ -286,14 +333,14 @@ module Jazzy
286
333
  make_default_doc_info(declaration)
287
334
 
288
335
  filepath = doc['key.filepath']
289
- objc = Config.instance.objc_mode
290
- if objc || should_mark_undocumented(filepath)
336
+
337
+ if !declaration.swift? || should_mark_undocumented(filepath)
291
338
  @stats.add_undocumented(declaration)
292
339
  return nil if @skip_undocumented
293
340
  declaration.abstract = undocumented_abstract
294
341
  else
295
342
  declaration.abstract = Markdown.render(doc['key.doc.comment'] || '',
296
- Highlighter.default_language)
343
+ declaration.highlight_language)
297
344
  end
298
345
 
299
346
  declaration
@@ -312,16 +359,7 @@ module Jazzy
312
359
  def self.make_doc_info(doc, declaration)
313
360
  return unless should_document?(doc)
314
361
 
315
- if Config.instance.objc_mode
316
- declaration.declaration =
317
- Highlighter.highlight(doc['key.parsed_declaration'])
318
- declaration.other_language_declaration =
319
- Highlighter.highlight(doc['key.swift_declaration'], 'swift')
320
- else
321
- declaration.declaration =
322
- Highlighter.highlight(make_swift_declaration(doc, declaration))
323
- end
324
-
362
+ highlight_declaration(doc, declaration)
325
363
  make_deprecation_info(doc, declaration)
326
364
 
327
365
  unless doc['key.doc.full_as_xml']
@@ -329,7 +367,7 @@ module Jazzy
329
367
  end
330
368
 
331
369
  declaration.abstract = Markdown.render(doc['key.doc.comment'] || '',
332
- Highlighter.default_language)
370
+ declaration.highlight_language)
333
371
  declaration.discussion = ''
334
372
  declaration.return = Markdown.rendered_returns
335
373
  declaration.parameters = parameters(doc, Markdown.rendered_parameters)
@@ -337,6 +375,18 @@ module Jazzy
337
375
  @stats.add_documented
338
376
  end
339
377
 
378
+ def self.highlight_declaration(doc, declaration)
379
+ if declaration.swift?
380
+ declaration.declaration =
381
+ Highlighter.highlight_swift(make_swift_declaration(doc, declaration))
382
+ else
383
+ declaration.declaration =
384
+ Highlighter.highlight_objc(doc['key.parsed_declaration'])
385
+ declaration.other_language_declaration =
386
+ Highlighter.highlight_swift(doc['key.swift_declaration'])
387
+ end
388
+ end
389
+
340
390
  def self.make_deprecation_info(doc, declaration)
341
391
  if declaration.deprecated
342
392
  declaration.deprecation_message =
@@ -391,8 +441,7 @@ module Jazzy
391
441
  parsed &&
392
442
  (annotated.include?(' = default') || # SR-2608
393
443
  parsed.match('@autoclosure|@escaping') || # SR-6321
394
- parsed.include?("\n") ||
395
- parsed.include?('extension '))
444
+ parsed.include?("\n"))
396
445
  end
397
446
 
398
447
  # Replace the fully qualified name of a type with its base name
@@ -414,6 +463,9 @@ module Jazzy
414
463
  # From source code
415
464
  parsed_decl = doc['key.parsed_declaration']
416
465
 
466
+ # Don't present type attributes on extensions
467
+ return parsed_decl if declaration.type.extension?
468
+
417
469
  decl =
418
470
  if prefer_parsed_decl?(parsed_decl, annotated_decl_body)
419
471
  # Strip any attrs captured by parsed version
@@ -435,14 +487,10 @@ module Jazzy
435
487
  end
436
488
 
437
489
  def self.make_substructure(doc, declaration)
438
- declaration.children = if doc['key.substructure']
439
- make_source_declarations(
440
- doc['key.substructure'],
441
- declaration,
442
- )
443
- else
444
- []
445
- end
490
+ return [] unless subdocs = doc['key.substructure']
491
+ make_source_declarations(subdocs,
492
+ declaration,
493
+ declaration.mark_for_children)
446
494
  end
447
495
 
448
496
  # rubocop:disable Metrics/MethodLength
@@ -463,8 +511,7 @@ module Jazzy
463
511
  declaration.type = SourceDeclaration::Type.new(doc['key.kind'])
464
512
  declaration.typename = doc['key.typename']
465
513
  declaration.objc_name = doc['key.name']
466
- documented_name = if Config.instance.hide_declarations == 'objc' &&
467
- doc['key.swift_name']
514
+ documented_name = if Config.instance.hide_objc? && doc['key.swift_name']
468
515
  doc['key.swift_name']
469
516
  else
470
517
  declaration.objc_name
@@ -500,10 +547,17 @@ module Jazzy
500
547
  declaration.end_line = doc['key.parsed_scope.end']
501
548
  declaration.deprecated = doc['key.always_deprecated']
502
549
  declaration.unavailable = doc['key.always_unavailable']
550
+ declaration.generic_requirements =
551
+ find_generic_requirements(doc['key.parsed_declaration'])
552
+ inherited_types = doc['key.inheritedtypes'] || []
553
+ declaration.inherited_types =
554
+ inherited_types.map { |type| type['key.name'] }.compact
503
555
 
504
556
  next unless make_doc_info(doc, declaration)
505
- make_substructure(doc, declaration)
506
- next if declaration.type.extension? && declaration.children.empty?
557
+ declaration.children = make_substructure(doc, declaration)
558
+ next if declaration.type.extension? &&
559
+ declaration.children.empty? &&
560
+ !declaration.inherited_types?
507
561
  declarations << declaration
508
562
  end
509
563
  declarations
@@ -512,12 +566,22 @@ module Jazzy
512
566
  # rubocop:enable Metrics/CyclomaticComplexity
513
567
  # rubocop:enable Metrics/MethodLength
514
568
 
569
+ def self.find_generic_requirements(parsed_declaration)
570
+ parsed_declaration =~ /\bwhere\s+(.*)$/m
571
+ return nil unless Regexp.last_match
572
+ Regexp.last_match[1].gsub(/\s+/, ' ')
573
+ end
574
+
515
575
  # Expands extensions of nested types declared at the top level into
516
576
  # a tree so they can be deduplicated properly
517
577
  def self.expand_extensions(decls)
518
578
  decls.map do |decl|
519
579
  next decl unless decl.type.extension? && decl.name.include?('.')
520
580
 
581
+ # Don't expand the Swift namespace if we're in ObjC mode.
582
+ # ex: NS_SWIFT_NAME(Foo.Bar) should not create top-level Foo
583
+ next decl if decl.swift_objc_extension? && !Config.instance.hide_objc?
584
+
521
585
  name_parts = decl.name.split('.')
522
586
  decl.name = name_parts.pop
523
587
  expand_extension(decl, name_parts, decls)
@@ -531,6 +595,7 @@ module Jazzy
531
595
  SourceDeclaration.new.tap do |decl|
532
596
  make_default_doc_info(decl)
533
597
  decl.name = name
598
+ decl.modulename = extension.modulename
534
599
  decl.type = extension.type
535
600
  decl.mark = extension.mark
536
601
  decl.usr = candidates.first.usr unless candidates.empty?
@@ -553,10 +618,10 @@ module Jazzy
553
618
  .group_by { |d| deduplication_key(d, declarations) }
554
619
  .values
555
620
 
556
- duplicate_groups.map do |group|
621
+ duplicate_groups.flat_map do |group|
557
622
  # Put extended type (if present) before extensions
558
623
  merge_declarations(group)
559
- end
624
+ end.compact
560
625
  end
561
626
 
562
627
  # Returns true if an Objective-C declaration is mergeable.
@@ -566,13 +631,28 @@ module Jazzy
566
631
  && name_match(decl.objc_category_name[0], root_decls))
567
632
  end
568
633
 
634
+ # Returns if a Swift declaration is mergeable.
635
+ # Start off merging in typealiases to help understand extensions.
636
+ def self.mergeable_swift?(decl)
637
+ decl.type.swift_extensible? ||
638
+ decl.type.swift_extension? ||
639
+ decl.type.swift_typealias?
640
+ end
641
+
569
642
  # Two declarations get merged if they have the same deduplication key.
570
643
  def self.deduplication_key(decl, root_decls)
571
- if decl.type.swift_extensible? || decl.type.swift_extension?
644
+ # Swift extension of objc class
645
+ if decl.swift_objc_extension?
646
+ [decl.swift_extension_objc_name, :objc_class_and_categories]
647
+ # Swift type or Swift extension of Swift type
648
+ elsif mergeable_swift?(decl)
572
649
  [decl.usr, decl.name]
650
+ # Objc categories and classes
573
651
  elsif mergeable_objc?(decl, root_decls)
574
- name, _ = decl.objc_category_name || decl.name
652
+ # Using the ObjC name to match swift_objc_extension.
653
+ name, _ = decl.objc_category_name || decl.objc_name
575
654
  [name, :objc_class_and_categories]
655
+ # Non-mergable declarations (funcs, typedefs etc...)
576
656
  else
577
657
  [decl.usr, decl.name, decl.type.kind]
578
658
  end
@@ -591,17 +671,36 @@ module Jazzy
591
671
  end
592
672
  typedecl = typedecls.first
593
673
 
674
+ extensions = reject_inaccessible_extensions(typedecl, extensions)
675
+
594
676
  if typedecl
595
677
  if typedecl.type.swift_protocol?
596
- merge_default_implementations_into_protocol(typedecl, extensions)
597
- mark_members_from_protocol_extension(extensions)
678
+ mark_and_merge_protocol_extensions(typedecl, extensions)
598
679
  extensions.reject! { |ext| ext.children.empty? }
599
680
  end
600
681
 
601
- merge_declaration_marks(typedecl, extensions)
682
+ merge_objc_declaration_marks(typedecl, extensions)
602
683
  end
603
684
 
604
- decls = typedecls + extensions
685
+ # Keep type-aliases separate from any extensions
686
+ if typedecl && typedecl.type.swift_typealias?
687
+ [merge_type_and_extensions(typedecls, []),
688
+ merge_type_and_extensions([], extensions)]
689
+ else
690
+ merge_type_and_extensions(typedecls, extensions)
691
+ end
692
+ end
693
+ # rubocop:enable Metrics/MethodLength
694
+
695
+ def self.merge_type_and_extensions(typedecls, extensions)
696
+ # Constrained extensions at the end
697
+ constrained, regular_exts = extensions.partition(&:constrained_extension?)
698
+ decls = typedecls + regular_exts + constrained
699
+ return nil if decls.empty?
700
+
701
+ move_merged_extension_marks(decls)
702
+ merge_code_declaration(decls)
703
+
605
704
  decls.first.tap do |merged|
606
705
  merged.children = deduplicate_declarations(
607
706
  decls.flat_map(&:children).uniq,
@@ -611,58 +710,112 @@ module Jazzy
611
710
  end
612
711
  end
613
712
  end
614
- # rubocop:enable Metrics/MethodLength
615
713
 
714
+ # Now we know all the public types and all the private protocols,
715
+ # reject extensions that add public protocols to private types
716
+ # or add private protocols to public types.
717
+ def self.reject_inaccessible_extensions(typedecl, extensions)
718
+ swift_exts, objc_exts = extensions.partition(&:swift?)
719
+
720
+ # Reject extensions that are just conformances to private protocols
721
+ unwanted_exts, wanted_exts = swift_exts.partition do |ext|
722
+ ext.children.empty? &&
723
+ !ext.other_inherited_types?(@inaccessible_protocols)
724
+ end
725
+
726
+ # Given extensions of a type from this module, without the
727
+ # type itself, the type must be private and the extensions
728
+ # should be rejected.
729
+ if !typedecl &&
730
+ wanted_exts.first &&
731
+ wanted_exts.first.type_from_doc_module?
732
+ unwanted_exts += wanted_exts
733
+ wanted_exts = []
734
+ end
735
+
736
+ # Don't tell the user to document them
737
+ unwanted_exts.each { |e| @stats.remove_undocumented(e) }
738
+
739
+ objc_exts + wanted_exts
740
+ end
741
+
742
+ # Protocol extensions.
743
+ #
616
744
  # If any of the extensions provide default implementations for methods in
617
745
  # the given protocol, merge those members into the protocol doc instead of
618
746
  # keeping them on the extension. These get a “Default implementation”
619
- # annotation in the generated docs.
620
- def self.merge_default_implementations_into_protocol(protocol, extensions)
621
- protocol.children.each do |proto_method|
622
- extensions.each do |ext|
623
- defaults, ext.children = ext.children.partition do |ext_member|
624
- ext_member.name == proto_method.name
747
+ # annotation in the generated docs. Default implementations added by
748
+ # conditional extensions are annotated but listed separately.
749
+ #
750
+ # Protocol methods provided only in an extension and not in the protocol
751
+ # itself are a special beast: they do not use dynamic dispatch. These get an
752
+ # “Extension method” annotation in the generated docs.
753
+ def self.mark_and_merge_protocol_extensions(protocol, extensions)
754
+ extensions.each do |ext|
755
+ ext.children = ext.children.select do |ext_member|
756
+ proto_member = protocol.children.find do |p|
757
+ p.name == ext_member.name && p.type == ext_member.type
758
+ end
759
+
760
+ # Extension-only method, keep.
761
+ unless proto_member
762
+ ext_member.from_protocol_extension = true
763
+ next true
625
764
  end
626
- unless defaults.empty?
627
- proto_method.default_impl_abstract =
628
- defaults.flat_map { |d| [d.abstract, d.discussion] }.join
765
+
766
+ # Default impl but constrained, mark and keep.
767
+ if ext.constrained_extension?
768
+ ext_member.default_impl_abstract = ext_member.abstract
769
+ ext_member.abstract = nil
770
+ next true
629
771
  end
772
+
773
+ # Default impl for all users, merge.
774
+ proto_member.default_impl_abstract = ext_member.abstract
775
+ next false
630
776
  end
631
777
  end
632
778
  end
633
779
 
634
- # Protocol methods provided only in an extension and not in the protocol
635
- # itself are a special beast: they do not use dynamic dispatch. These get an
636
- # “Extension method” annotation in the generated docs.
637
- def self.mark_members_from_protocol_extension(extensions)
780
+ # Mark children merged from categories with the name of category
781
+ # (unless they already have a mark)
782
+ def self.merge_objc_declaration_marks(typedecl, extensions)
783
+ return unless typedecl.type.objc_class?
638
784
  extensions.each do |ext|
639
- ext.children.each do |ext_member|
640
- ext_member.from_protocol_extension = true
641
- end
785
+ _, category_name = ext.objc_category_name
786
+ ext.children.each { |c| c.mark.name ||= category_name }
642
787
  end
643
788
  end
644
789
 
645
- # Customize marks associated with to-be-merged declarations
646
- def self.merge_declaration_marks(typedecl, extensions)
647
- if typedecl.type.objc_class?
648
- # Mark children merged from categories with the name of category
649
- # (unless they already have a mark)
650
- extensions.each do |ext|
651
- _, category_name = ext.objc_category_name
652
- ext.children.each { |c| c.mark.name ||= category_name }
653
- end
654
- else
655
- # If the Swift extension has a mark and the first child doesn't
656
- # then copy the mark contents down so it still shows up.
657
- extensions.each do |ext|
658
- child = ext.children.first
659
- if child && child.mark.empty?
660
- child.mark.copy(ext.mark)
661
- end
790
+ # For each extension to be merged, move any MARK from the extension
791
+ # declaration down to the extension contents so it still shows up.
792
+ def self.move_merged_extension_marks(decls)
793
+ return unless to_be_merged = decls[1..-1]
794
+ to_be_merged.each do |ext|
795
+ child = ext.children.first
796
+ if child && child.mark.empty?
797
+ child.mark.copy(ext.mark)
662
798
  end
663
799
  end
664
800
  end
665
801
 
802
+ # Merge useful information added by extensions into the main
803
+ # declaration: public protocol conformances and, for top-level extensions,
804
+ # further conditional extensions of the same type.
805
+ def self.merge_code_declaration(decls)
806
+ first = decls.first
807
+
808
+ declarations = decls[1..-1].select do |decl|
809
+ decl.type.swift_extension? &&
810
+ (decl.other_inherited_types?(@inaccessible_protocols) ||
811
+ (first.type.swift_extension? && decl.constrained_extension?))
812
+ end.map(&:declaration)
813
+
814
+ unless declarations.empty?
815
+ first.declaration = declarations.prepend(first.declaration).uniq.join
816
+ end
817
+ end
818
+
666
819
  # Apply filtering based on the "included" and "excluded" flags.
667
820
  def self.filter_files(json)
668
821
  json = filter_included_files(json) if Config.instance.included_files.any?
@@ -818,19 +971,18 @@ module Jazzy
818
971
  @min_acl = min_acl
819
972
  @skip_undocumented = skip_undocumented
820
973
  @stats = Stats.new
821
- sourcekitten_json = filter_files(JSON.parse(sourcekitten_output))
974
+ @inaccessible_protocols = []
975
+ sourcekitten_json = filter_files(JSON.parse(sourcekitten_output).flatten)
822
976
  docs = make_source_declarations(sourcekitten_json).concat inject_docs
823
977
  docs = expand_extensions(docs)
824
978
  docs = deduplicate_declarations(docs)
825
- if Config.instance.objc_mode
826
- docs = reject_objc_types(docs)
827
- else
828
- # Remove top-level enum cases because it means they have an ACL lower
829
- # than min_acl
830
- docs = docs.reject { |doc| doc.type.swift_enum_element? }
831
- end
979
+ docs = reject_objc_types(docs)
980
+ # Remove top-level enum cases because it means they have an ACL lower
981
+ # than min_acl
982
+ docs = docs.reject { |doc| doc.type.swift_enum_element? }
832
983
  ungrouped_docs = docs
833
984
  docs = group_docs(docs)
985
+ merge_consecutive_marks(docs)
834
986
  make_doc_urls(docs)
835
987
  autolink(docs, ungrouped_docs)
836
988
  [docs, @stats]