module Jazzy::SourceKitten
This module interacts with the sourcekitten command-line executable
Constants
- AUTOLINK_HIGHLIGHT_FIELDS
- AUTOLINK_TEXT_FIELDS
- DEFAULT_ATTRIBUTES
Strip default property attributes because libclang adds them all, even if absent in the original source code.
Public Class Methods
Builds SourceKitten
arguments based on Jazzy
options
# File lib/jazzy/sourcekitten.rb, line 182 def self.arguments_from_options(module_config) arguments = ['doc'] if module_config.objc_mode arguments += objc_arguments_from_options(module_config) else arguments += ['--spm'] if use_spm?(module_config) unless module_config.module_name.empty? arguments += ['--module-name', module_config.module_name] end arguments += ['--'] end arguments + module_config.build_tool_arguments end
# File lib/jazzy/sourcekitten.rb, line 244 def self.attribute?(doc, attr_name) doc['key.attributes']&.find do |attribute| attribute['key.attribute'] == "source.decl.attribute.#{attr_name}" end end
Regexp to match an @attribute. Complex to handle @available().
# File lib/jazzy/sourcekitten.rb, line 398 def self.attribute_regexp(name) qstring = /"(?:[^"\\]*|\\.)*"/ %r{@#{name} # @attr name (?:\s*\( # optionally followed by spaces + parens, (?: # containing any number of either.. [^")]*| # normal characters or... #{qstring} # quoted strings. )* # (end parens content) \))? # (end optional parens) }x end
# File lib/jazzy/sourcekitten.rb, line 1002 def self.autolink(docs) docs.each do |doc| doc.children = autolink(doc.children) autolink_text_fields(doc) autolink_highlight_fields(doc) end end
For autolinking external markdown documents
# File lib/jazzy/sourcekitten.rb, line 1011 def self.autolink_document(html, doc) autolink_text(html, doc) end
# File lib/jazzy/sourcekitten.rb, line 993 def self.autolink_highlight_fields(doc) AUTOLINK_HIGHLIGHT_FIELDS.each do |field| if text = doc.send(field) doc.send(field + '=', autolink_text(text, doc, after_highlight: true)) end end end
Links recognized top-level declarations within
-
inlined code within docs
-
method signatures after they’ve been processed by the highlighter
The ‘after_highlight` flag is used to differentiate between the two modes.
DocC link format - follow Xcode and don’t display slash-separated parts.
# File lib/jazzy/sourcekitten.rb, line 958 def self.autolink_text(text, doc, after_highlight: false) text.autolink_block(doc.url, '[^\s]+', after_highlight) do |raw_name| sym_name = (raw_name[/^<doc:(.*)>$/, 1] || raw_name).sub(/(?<!^)-.+$/, '') [@doc_index.lookup(sym_name, doc), sym_name.sub(%r{^.*/}, '')] end.autolink_block(doc.url, '[+-]\[\w+(?: ?\(\w+\))? [\w:]+\]', after_highlight) do |raw_name| [@doc_index.lookup(raw_name, doc), raw_name] end.autolink_block(doc.url, '[+-]\w[\w:]*', after_highlight) do |raw_name| [@doc_index.lookup(raw_name, doc), raw_name] end end
# File lib/jazzy/sourcekitten.rb, line 977 def self.autolink_text_fields(doc) AUTOLINK_TEXT_FIELDS.each do |field| if text = doc.send(field) doc.send(field + '=', autolink_text(text, doc)) end end (doc.parameters || []).each do |param| param[:discussion] = autolink_text(param[:discussion], doc) end end
# File lib/jazzy/sourcekitten.rb, line 250 def self.availability_attribute?(doc) attribute?(doc, 'available') end
Merges multiple extensions of the same entity into a single document.
Merges extensions into the protocol/class/struct/enum they extend, if it occurs in the same project.
Merges redundant declarations when documenting podspecs.
# File lib/jazzy/sourcekitten.rb, line 688 def self.deduplicate_declarations(declarations) duplicate_groups = declarations .group_by { |d| deduplication_key(d, declarations) } .values duplicate_groups.flat_map do |group| # Put extended type (if present) before extensions merge_declarations(group) end.compact end
Two declarations get merged if they have the same deduplication key.
# File lib/jazzy/sourcekitten.rb, line 738 def self.deduplication_key(decl, root_decls) mod_key = module_deduplication_key(decl) # Swift extension of objc class if decl.swift_objc_extension? [decl.swift_extension_objc_name, :objc_class_and_categories, mod_key] # Swift type or Swift extension of Swift type elsif mergeable_swift?(decl) [decl.usr, decl.name, mod_key] # Objc categories and classes elsif mergeable_objc?(decl, root_decls) # Using the ObjC name to match swift_objc_extension. name, _ = decl.objc_category_name || decl.objc_name [name, :objc_class_and_categories, mod_key] # Non-mergable declarations (funcs, typedefs etc...) else [decl.usr, decl.name, decl.type.kind, ''] end end
# File lib/jazzy/sourcekitten.rb, line 661 def self.expand_extension(extension, name_parts, decls) return extension if name_parts.empty? name = name_parts.shift candidates = decls.select { |decl| decl.name == name } SourceDeclaration.new.tap do |decl| make_default_doc_info(decl) decl.name = name decl.module_name = extension.module_name decl.doc_module_name = extension.doc_module_name decl.type = extension.type decl.mark = extension.mark decl.usr = candidates.first.usr unless candidates.empty? child = expand_extension(extension, name_parts, candidates.flat_map(&:children).uniq) child.parent_in_code = decl decl.children = [child] end end
Expands extensions of nested types declared at the top level into a tree so they can be deduplicated properly
# File lib/jazzy/sourcekitten.rb, line 647 def self.expand_extensions(decls) decls.map do |decl| next decl unless decl.type.extension? && decl.name.include?('.') # Don't expand the Swift namespace if we're in ObjC mode. # ex: NS_SWIFT_NAME(Foo.Bar) should not create top-level Foo next decl if decl.swift_objc_extension? && !Config.instance.hide_objc? name_parts = decl.name.split('.') decl.name = name_parts.pop expand_extension(decl, name_parts, decls) end end
Get all attributes of some name
# File lib/jazzy/sourcekitten.rb, line 411 def self.extract_attributes(declaration, name = '\w+') attrs = declaration.scan(attribute_regexp(name)) # Rouge #806 workaround, use unicode lookalike for ')' inside attributes. attrs.map { |str| str.gsub(/\)(?!\s*$)/, "\ufe5a") } end
# File lib/jazzy/sourcekitten.rb, line 424 def self.extract_availability(declaration) extract_attributes(declaration, 'available') end
Keep everything except instructions to us
# File lib/jazzy/sourcekitten.rb, line 418 def self.extract_documented_attributes(declaration) extract_attributes(declaration).reject do |attr| attr.start_with?('@_documentation') end end
Swift 6 workaround: @_spi attribute & SPI group missing
# File lib/jazzy/sourcekitten.rb, line 494 def self.fabricate_spi_attributes(doc, attrs) return [] unless spi_attribute?(doc) return [] if attrs =~ /@_spi/ ['@_spi(<<unknown>>)'] end
Filter based on the “excluded” flag.
# File lib/jazzy/sourcekitten.rb, line 1041 def self.filter_excluded_files(json) excluded_files = Config.instance.excluded_files json.map do |doc| key = doc.keys.first doc unless excluded_files.detect do |exclude| File.fnmatch?(exclude, key) end end.compact end
Apply filtering based on the “included” and “excluded” flags.
# File lib/jazzy/sourcekitten.rb, line 1020 def self.filter_files(json) json = filter_included_files(json) if Config.instance.included_files.any? json = filter_excluded_files(json) if Config.instance.excluded_files.any? json.map do |doc| key = doc.keys.first doc[key] end.compact end
Filter based on the “included” flag.
# File lib/jazzy/sourcekitten.rb, line 1030 def self.filter_included_files(json) included_files = Config.instance.included_files json.map do |doc| key = doc.keys.first doc if included_files.detect do |include| File.fnmatch?(include, key) end end.compact end
rubocop:enable Metrics/PerceivedComplexity rubocop:enable Metrics/CyclomaticComplexity rubocop:enable Metrics/MethodLength
# File lib/jazzy/sourcekitten.rb, line 634 def self.find_generic_requirements(parsed_declaration) parsed_declaration =~ /\bwhere\s+(.*)$/m return nil unless Regexp.last_match Regexp.last_match[1].gsub(/\s+/, ' ') end
Apply fixes to improve the compiler’s declaration
# File lib/jazzy/sourcekitten.rb, line 446 def self.fix_up_compiler_decl(annotated_decl, declaration) annotated_decl. # Replace the fully qualified name of a type with its base name gsub(declaration.fully_qualified_name_regexp, declaration.name). # Workaround for SR-9816 gsub(" {\n get\n }", ''). # Workaround for SR-12139 gsub(/mutating\s+mutating/, 'mutating') end
# File lib/jazzy/sourcekitten.rb, line 364 def self.highlight_declaration(doc, declaration) if declaration.swift? declaration.declaration = Highlighter.highlight_swift(make_swift_declaration(doc, declaration)) else declaration.declaration = Highlighter.highlight_objc( make_objc_declaration(doc['key.parsed_declaration']), ) declaration.other_language_declaration = Highlighter.highlight_swift(doc['key.swift_declaration']) end end
SourceDeclaration
generation
# File lib/jazzy/sourcekitten.rb, line 237 def self.make_default_doc_info(declaration) # @todo: Fix these declaration.abstract = '' declaration.parameters = [] declaration.children = [] end
# File lib/jazzy/sourcekitten.rb, line 378 def self.make_deprecation_info(doc, declaration) if declaration.deprecated declaration.deprecation_message = Markdown.render(doc['key.deprecation_message'] || '') end if declaration.unavailable declaration.unavailable_message = Markdown.render(doc['key.unavailable_message'] || '') end end
# File lib/jazzy/sourcekitten.rb, line 345 def self.make_doc_info(doc, declaration) return unless should_document?(doc) highlight_declaration(doc, declaration) make_deprecation_info(doc, declaration) unless doc['key.doc.full_as_xml'] return process_undocumented_token(doc, declaration) end declaration.abstract = Markdown.render(doc['key.doc.comment'] || '', declaration.highlight_language) declaration.discussion = '' declaration.return = Markdown.rendered_returns declaration.parameters = parameters(doc, Markdown.rendered_parameters) @stats.add_documented end
rubocop:disable Metrics/MethodLength Generate doc URL by prepending its parents’ URLs @return [Hash] input docs with URLs
# File lib/jazzy/sourcekitten.rb, line 82 def self.make_doc_urls(docs) docs.each do |doc| if doc.render_as_page? doc.url = ( subdir_for_doc(doc) + [sanitize_filename(doc) + '.html'] ).map { |path| ERB::Util.url_encode(path) }.join('/') doc.children = make_doc_urls(doc.children) else # Don't create HTML page for this doc if it doesn't have children # Instead, make its link a hash-link on its parent's page if doc.typename == '<<error type>>' warn "A compile error prevented #{doc.fully_qualified_name} " \ 'from receiving a unique USR. Documentation may be ' \ 'incomplete. Please check for compile errors by running ' \ '`xcodebuild` or `swift build` with arguments ' \ "`#{Config.instance.build_tool_arguments.shelljoin}`." end id = doc.usr unless id id = doc.name || 'unknown' warn "`#{id}` has no USR. First make sure all modules used in " \ 'your project have been imported. If all used modules are ' \ 'imported, please report this problem by filing an issue at ' \ 'https://github.com/realm/jazzy/issues along with your ' \ 'Xcode project. If this token is declared in an `#if` block, ' \ 'please ignore this message.' end doc.url = "#{doc.parent_in_docs.url}#/#{id}" end end end
# File lib/jazzy/sourcekitten.rb, line 513 def self.make_objc_declaration(declaration) return declaration if Config.instance.keep_property_attributes declaration =~ /\A@property\s+\((.*?)\)/ return declaration unless Regexp.last_match attrs = Regexp.last_match[1].split(',').map(&:strip) - DEFAULT_ATTRIBUTES attrs_text = attrs.empty? ? '' : " (#{attrs.join(', ')})" declaration .sub(/(?<=@property)\s+\(.*?\)/, attrs_text) .gsub(/\s+/, ' ') end
rubocop:disable Metrics/MethodLength rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/PerceivedComplexity
# File lib/jazzy/sourcekitten.rb, line 538 def self.make_source_declarations(docs, parent = nil, mark = SourceMark.new) declarations = [] current_mark = mark Array(docs).each do |doc| if doc.key?('key.diagnostic_stage') declarations += make_source_declarations( doc['key.substructure'], parent ) next end declaration = SourceDeclaration.new declaration.parent_in_code = parent declaration.type = SourceDeclaration::Type.new(doc['key.kind'], doc['key.fully_annotated_decl']) declaration.typename = doc['key.typename'] declaration.objc_name = doc['key.name'] documented_name = if Config.instance.hide_objc? && doc['key.swift_name'] doc['key.swift_name'] else declaration.objc_name end if declaration.type.task_mark?(documented_name) current_mark = SourceMark.new(documented_name) end if declaration.type.swift_enum_case? # Enum "cases" are thin wrappers around enum "elements". declarations += make_source_declarations( doc['key.substructure'], parent, current_mark ) next end next unless declaration.type.should_document? unless declaration.type.name raise 'Please file an issue at ' \ 'https://github.com/realm/jazzy/issues about adding support ' \ "for `#{declaration.type.kind}`." end unless documented_name warn 'Found a declaration without `key.name` that will be ' \ 'ignored. Documentation may be incomplete. This is probably ' \ 'caused by unresolved compiler errors: check the sourcekitten ' \ 'output for error messages.' next end declaration.file = Pathname(doc['key.filepath']) if doc['key.filepath'] declaration.usr = doc['key.usr'] declaration.type_usr = doc['key.typeusr'] declaration.module_name = if declaration.swift? # Filter out Apple sub-framework implementation names doc['key.modulename']&.sub(/\..*$/, '') else # ObjC best effort, category original module is unavailable @current_module_name end declaration.doc_module_name = @current_module_name declaration.name = documented_name declaration.mark = current_mark declaration.access_control_level = SourceDeclaration::AccessControlLevel.from_doc(doc) declaration.line = doc['key.doc.line'] || doc['key.line'] declaration.column = doc['key.doc.column'] || doc['key.column'] declaration.start_line = doc['key.parsed_scope.start'] declaration.end_line = doc['key.parsed_scope.end'] declaration.deprecated = doc['key.always_deprecated'] declaration.unavailable = doc['key.always_unavailable'] declaration.generic_requirements = find_generic_requirements(doc['key.parsed_declaration']) inherited_types = doc['key.inheritedtypes'] || [] declaration.inherited_types = inherited_types.map { |type| type['key.name'] }.compact declaration.async = doc['key.symgraph_async'] || if xml_declaration = doc['key.fully_annotated_decl'] swift_async?(xml_declaration) end next unless make_doc_info(doc, declaration) declaration.children = make_substructure(doc, declaration) next if declaration.type.extension? && declaration.children.empty? && !declaration.inherited_types? declarations << declaration end declarations end
# File lib/jazzy/sourcekitten.rb, line 527 def self.make_substructure(doc, declaration) return [] unless subdocs = doc['key.substructure'] make_source_declarations(subdocs, declaration, declaration.mark_for_children) end
Find the best Swift declaration
# File lib/jazzy/sourcekitten.rb, line 458 def self.make_swift_declaration(doc, declaration) # From compiler 'quick help' style annotated_decl_xml = doc['key.annotated_decl'] return nil unless annotated_decl_xml annotated_decl_attrs, annotated_decl_body = split_decl_attributes(xml_to_text(annotated_decl_xml)) # From source code parsed_decl = doc['key.parsed_declaration'] # Don't present type attributes on extensions return parsed_decl if declaration.type.extension? decl = if prefer_parsed_decl?(parsed_decl, annotated_decl_body, declaration.type) # Strip any attrs captured by parsed version inline_attrs, parsed_decl_body = split_decl_attributes(parsed_decl) parsed_decl_body.unindent(inline_attrs.length) else # Improve the compiler declaration fix_up_compiler_decl(annotated_decl_body, declaration) end # @available attrs only in compiler 'interface' style extract_availability(doc['key.doc.declaration'] || '') .concat(extract_documented_attributes(annotated_decl_attrs)) .concat(fabricate_spi_attributes(doc, annotated_decl_attrs)) .push(decl) .join("\n") end
Protocol extensions.
If any of the extensions provide default implementations for methods in the given protocol, merge those members into the protocol doc instead of keeping them on the extension. These get a “Default implementation”annotation in the generated docs. Default implementations added by conditional extensions are annotated but listed separately.
Protocol methods provided only in an extension and not in the protocol itself are a special beast: they do not use dynamic dispatch. These get an “Extension method” annotation in the generated docs.
# File lib/jazzy/sourcekitten.rb, line 852 def self.mark_and_merge_protocol_extensions(protocol, extensions) extensions.each do |ext| ext.children = ext.children.select do |ext_member| proto_member = protocol.children.find do |p| p.name == ext_member.name && p.type == ext_member.type && p.async == ext_member.async end # Extension-only method, keep. unless proto_member ext_member.from_protocol_extension = true next true end # Default impl but constrained, mark and keep. if ext.constrained_extension? ext_member.default_impl_abstract = ext_member.abstract ext_member.abstract = nil next true end # Default impl for all users, merge. proto_member.default_impl_abstract = ext_member.abstract next false end end end
Spot and mark any categories on classes not declared in these docs
# File lib/jazzy/sourcekitten.rb, line 1073 def self.mark_objc_external_categories(docs) class_names = docs.select { |doc| doc.type.objc_class? }.to_set(&:name) docs.map do |doc| if (names = doc.objc_category_name) && !class_names.include?(names.first) doc.module_name = '(Imported)' end doc end end
Merge useful information added by extensions into the main declaration: public protocol conformances and, for top-level extensions, further conditional extensions of the same type.
# File lib/jazzy/sourcekitten.rb, line 908 def self.merge_code_declaration(decls) declarations = decls[1..].select do |decl| decl.type.swift_extension? && (decl.other_inherited_types?(@inaccessible_protocols) || (decls.first.type.swift_extension? && decl.constrained_extension?)) end.prepend(decls.first) html_declaration = '' until declarations.empty? module_decls, declarations = next_doc_module_group(declarations) first = module_decls.first if need_doc_module_note?(first, html_declaration) html_declaration += "<span class='declaration-note'>From #{first.doc_module_name}:</span>" end html_declaration += module_decls.map(&:declaration).uniq.join end # Must preserve `nil` for edge cases decls.first.declaration = html_declaration unless html_declaration.empty? end
rubocop:disable Metrics/MethodLength rubocop:disable Metrics/PerceivedComplexity Merges all of the given types and extensions into a single document.
# File lib/jazzy/sourcekitten.rb, line 760 def self.merge_declarations(decls) extensions, typedecls = decls.partition { |d| d.type.extension? } if typedecls.size > 1 info = typedecls .map { |t| "#{t.type.name.downcase} #{t.name}" } .join(', ') warn 'Found conflicting type declarations with the same name, which ' \ "may indicate a build issue or a bug in Jazzy: #{info}" end typedecl = typedecls.first extensions = reject_inaccessible_extensions(typedecl, extensions) if typedecl if typedecl.type.swift_protocol? mark_and_merge_protocol_extensions(typedecl, extensions) extensions.reject! { |ext| ext.children.empty? } end merge_objc_declaration_marks(typedecl, extensions) end # Keep type-aliases separate from any extensions if typedecl&.type&.swift_typealias? [merge_type_and_extensions(typedecls, []), merge_type_and_extensions([], extensions)] else merge_type_and_extensions(typedecls, extensions) end end
Mark children merged from categories with the name of category (unless they already have a mark)
# File lib/jazzy/sourcekitten.rb, line 883 def self.merge_objc_declaration_marks(typedecl, extensions) return unless typedecl.type.objc_class? extensions.each do |ext| _, category_name = ext.objc_category_name ext.children.each { |c| c.mark.name ||= category_name } end end
rubocop:enable Metrics/PerceivedComplexity rubocop:enable Metrics/MethodLength
# File lib/jazzy/sourcekitten.rb, line 794 def self.merge_type_and_extensions(typedecls, extensions) # Constrained extensions at the end constrained, regular_exts = extensions.partition(&:constrained_extension?) decls = typedecls + regular_exts + constrained return nil if decls.empty? move_merged_extension_marks(decls) merge_code_declaration(decls) decls.first.tap do |merged| merged.children = deduplicate_declarations( decls.flat_map(&:children).uniq, ) merged.children.each do |child| child.parent_in_code = merged end end end
Returns true if an Objective-C declaration is mergeable.
# File lib/jazzy/sourcekitten.rb, line 700 def self.mergeable_objc?(decl, root_decls) decl.type.objc_class? || (decl.type.objc_category? && (category_classname = decl.objc_category_name[0]) && root_decls.any? { |d| d.name == category_classname }) end
Returns if a Swift declaration is mergeable. Start off merging in typealiases to help understand extensions.
# File lib/jazzy/sourcekitten.rb, line 709 def self.mergeable_swift?(decl) decl.type.swift_extensible? || decl.type.swift_extension? || decl.type.swift_typealias? end
Normally merge all extensions into their types and each other.
:none means only merge within a module – so two extensions to
some type get merged, but an extension to a type from another documented module does not get merged into that type
:extensions means extensions of documented modules get merged,
but if we're documenting ModA and ModB, and they both provide extensions to Swift.String, then those two extensions still appear separately.
(The USR part of the dedup key means ModA.Foo and ModB.Foo do not get merged.)
# File lib/jazzy/sourcekitten.rb, line 727 def self.module_deduplication_key(decl) if (Config.instance.merge_modules == :none) || (Config.instance.merge_modules == :extensions && decl.extension_of_external_type?) decl.doc_module_name else '' end end
For each extension to be merged, move any MARK from the extension declaration down to the extension contents so it still shows up.
# File lib/jazzy/sourcekitten.rb, line 894 def self.move_merged_extension_marks(decls) return unless to_be_merged = decls[1..] to_be_merged.each do |ext| child = ext.children.first if child && child.mark.empty? child.mark.copy(ext.mark) end end end
Does this extension/type need a note explaining which doc module it is from? Only for extensions, if there actually are multiple modules. Last condition avoids it for simple ‘extension Array’.
# File lib/jazzy/sourcekitten.rb, line 939 def self.need_doc_module_note?(decl, html_declaration) Config.instance.multiple_modules? && decl.type.swift_extension? && !(html_declaration.empty? && !decl.constrained_extension? && !decl.inherited_types?) end
Grab all the extensions from the same doc module
# File lib/jazzy/sourcekitten.rb, line 930 def self.next_doc_module_group(decls) decls.partition do |decl| decl.doc_module_name == decls.first.doc_module_name end end
# File lib/jazzy/sourcekitten.rb, line 197 def self.objc_arguments_from_options(module_config) arguments = [] if module_config.build_tool_arguments.empty? arguments += ['--objc', module_config.umbrella_header.to_s, '--', '-x', 'objective-c', '-isysroot', `xcrun --show-sdk-path --sdk #{module_config.sdk}`.chomp, '-I', module_config.framework_root.to_s, '-fmodules'] end # add additional -I arguments for each subdirectory of framework_root unless module_config.framework_root.nil? rec_path(Pathname.new(module_config.framework_root.to_s)).collect do |child| if child.directory? arguments += ['-I', child.to_s] end end end arguments end
# File lib/jazzy/sourcekitten.rb, line 335 def self.parameters(doc, discovered) (doc['key.doc.parameters'] || []).map do |p| name = p['name'] { name: name, discussion: discovered[name], } end.reject { |param| param[:discussion].nil? } end
Parse sourcekitten STDOUT output as JSON @return [Hash] structured docs
# File lib/jazzy/sourcekitten.rb, line 1086 def self.parse(sourcekitten_output, options, inject_docs) @min_acl = options.min_acl @skip_undocumented = options.skip_undocumented @stats = Stats.new @inaccessible_protocols = [] # Process each module separately to inject the source module name docs = sourcekitten_output.zip(options.module_names).map do |json, name| @current_module_name = name sourcekitten_dicts = filter_files(JSON.parse(json).flatten) make_source_declarations(sourcekitten_dicts) end.flatten + inject_docs docs = expand_extensions(docs) docs = deduplicate_declarations(docs) docs = reject_objc_types(docs) docs = reject_swift_types(docs) docs = mark_objc_external_categories(docs) @doc_index = DocIndex.new(docs) docs = Grouper.group_docs(docs, @doc_index) make_doc_urls(docs) autolink(docs) [docs, @stats] end
# File lib/jazzy/sourcekitten.rb, line 434 def self.prefer_parsed_decl?(parsed, annotated, type) return true if annotated.empty? return false unless parsed return false if type.swift_variable? # prefer { get }-style annotated.include?(' = default') || # SR-2608 (parsed.scan(/@autoclosure|@escaping/).count > annotated.scan(/@autoclosure|@escaping/).count) || # SR-6321 parsed.include?("\n") # user formatting end
# File lib/jazzy/sourcekitten.rb, line 319 def self.process_undocumented_token(doc, declaration) make_default_doc_info(declaration) if declaration.mark_undocumented? @stats.add_undocumented(declaration) return nil if @skip_undocumented declaration.abstract = undocumented_abstract else declaration.abstract = Markdown.render(doc['key.doc.comment'] || '', declaration.highlight_language) end declaration end
returns all subdirectories of specified path
# File lib/jazzy/sourcekitten.rb, line 165 def self.rec_path(path) path.children.collect do |child| if child.directory? rec_path(child) + [child] end end.select { |x| x }.flatten(1) end
Now we know all the public types and all the private protocols, reject extensions that add public protocols to private types or add private protocols to public types.
# File lib/jazzy/sourcekitten.rb, line 816 def self.reject_inaccessible_extensions(typedecl, extensions) swift_exts, objc_exts = extensions.partition(&:swift?) # Reject extensions that are just conformances to private protocols unwanted_exts, wanted_exts = swift_exts.partition do |ext| ext.children.empty? && !ext.other_inherited_types?(@inaccessible_protocols) end # Given extensions of a type from this module, without the # type itself, the type must be private and the extensions # should be rejected. if !typedecl && wanted_exts.first && wanted_exts.first.type_from_doc_module? unwanted_exts += wanted_exts wanted_exts = [] end # Don't tell the user to document them unwanted_exts.each { |e| @stats.remove_undocumented(e) } objc_exts + wanted_exts end
# File lib/jazzy/sourcekitten.rb, line 1051 def self.reject_objc_types(docs) enums = docs.map do |doc| [doc, doc.children] end.flatten.select { |child| child.type.objc_enum? }.map(&:objc_name) docs.map do |doc| doc.children = doc.children.reject do |child| child.type.objc_typedef? && enums.include?(child.name) end doc end.reject do |doc| doc.type.objc_unexposed? || (doc.type.objc_typedef? && enums.include?(doc.name)) end end
Remove top-level enum cases because it means they have an ACL lower than min_acl
# File lib/jazzy/sourcekitten.rb, line 1068 def self.reject_swift_types(docs) docs.reject { |doc| doc.type.swift_enum_element? } end
Run sourcekitten with given arguments and return STDOUT
# File lib/jazzy/sourcekitten.rb, line 218 def self.run_sourcekitten(arguments) if swift_version = Config.instance.swift_version unless xcode = XCInvoke::Xcode.find_swift_version(swift_version) raise "Unable to find an Xcode with swift version #{swift_version}." end env = xcode.as_env else env = ENV end bin_path = Pathname(__FILE__) + '../../../bin/sourcekitten' output, = Executable.execute_command(bin_path, arguments, true, env: env) output end
URL assignment
# File lib/jazzy/sourcekitten.rb, line 69 def self.sanitize_filename(doc) unsafe_filename = doc.docs_filename sanitzation_enabled = Config.instance.use_safe_filenames if sanitzation_enabled && !doc.type.name_controlled_manually? CGI.escape(unsafe_filename).gsub('_', '%5F').tr('%', '_') else unsafe_filename end end
# File lib/jazzy/sourcekitten.rb, line 258 def self.should_document?(doc) return false if doc['key.doc.comment'].to_s.include?(':nodoc:') type = SourceDeclaration::Type.new(doc['key.kind']) # Always document Objective-C declarations. return true unless type.swift_type? # Don't document Swift types if we are hiding Swift return false if Config.instance.hide_swift? # Don't document @available declarations with no USR, since it means # they're unavailable. if availability_attribute?(doc) && !doc['key.usr'] return false end # Only document @_spi declarations in some scenarios return false unless should_document_spi?(doc) # Don't document declarations excluded by the min_acl setting if type.swift_extension? should_document_swift_extension?(doc) else should_document_acl?(type, doc) end end
Check visibility: access control
# File lib/jazzy/sourcekitten.rb, line 297 def self.should_document_acl?(type, doc) # Include all enum elements for now, can't tell their ACL. return true if type.swift_enum_element? acl_ok = SourceDeclaration::AccessControlLevel.from_doc(doc) >= @min_acl unless acl_ok @stats.add_acl_skipped @inaccessible_protocols.append(doc['key.name']) if type.swift_protocol? end acl_ok end
Check visibility: SPI
# File lib/jazzy/sourcekitten.rb, line 287 def self.should_document_spi?(doc) spi_ok = @min_acl < SourceDeclaration::AccessControlLevel.public || Config.instance.include_spi_declarations || (!spi_attribute?(doc) && !doc['key.symgraph_spi']) @stats.add_spi_skipped unless spi_ok spi_ok end
Document extensions if they add protocol conformances, or have any member that needs to be documented.
# File lib/jazzy/sourcekitten.rb, line 311 def self.should_document_swift_extension?(doc) doc['key.inheritedtypes'] || Array(doc['key.substructure']).any? do |subdoc| subtype = SourceDeclaration::Type.new(subdoc['key.kind']) !subtype.mark? && should_document?(subdoc) end end
# File lib/jazzy/sourcekitten.rb, line 254 def self.spi_attribute?(doc) attribute?(doc, '_spi') end
Split leading attributes from a decl, returning both parts.
# File lib/jazzy/sourcekitten.rb, line 429 def self.split_decl_attributes(declaration) declaration =~ /^((?:#{attribute_regexp('\w+')}\s*)*)(.*)$/m Regexp.last_match.captures end
Determine the subdirectory in which a doc should be placed. Guides in the root for back-compatibility. Declarations under outer namespace type (Structures, Classes, etc.)
# File lib/jazzy/sourcekitten.rb, line 119 def self.subdir_for_doc(doc) if Config.instance.multiple_modules? subdir_for_doc_multi_module(doc) else # Back-compatibility layout version subdir_for_doc_single_module(doc) end end
Multi-module site layout, separate each module that is being documented.
# File lib/jazzy/sourcekitten.rb, line 140 def self.subdir_for_doc_multi_module(doc) # Guides + Groups in the root return [] if doc.type.markdown? || doc.type.overview? root_decl = doc.namespace_path.first # Extensions need an extra dir to allow for extending # ExternalModule1.TypeName and ExternalModule2.TypeName namespace_subdir = if root_decl.type.swift_extension? ['Extensions', root_decl.module_name] else [doc.namespace_path.first.type.plural_url_name] end [root_decl.doc_module_name] + namespace_subdir + doc.namespace_ancestors.map(&:name) end
Pre-multi-module site layout, does not allow for types with the same name.
# File lib/jazzy/sourcekitten.rb, line 130 def self.subdir_for_doc_single_module(doc) # Guides + Groups in the root return [] if doc.type.markdown? || doc.type.overview? [doc.namespace_path.first.type.plural_url_name] + doc.namespace_ancestors.map(&:name) end
Exclude non-async routines that accept async closures
# File lib/jazzy/sourcekitten.rb, line 502 def self.swift_async?(fully_annotated_decl) document = REXML::Document.new(fully_annotated_decl) !document.elements['/*/syntaxtype.keyword[text()="async"]'].nil? rescue StandardError nil end
# File lib/jazzy/sourcekitten.rb, line 59 def self.undocumented_abstract @undocumented_abstract ||= Markdown.render( Config.instance.undocumented_text, ).freeze end
# File lib/jazzy/sourcekitten.rb, line 173 def self.use_spm?(module_config) module_config.swift_build_tool == :spm || (!module_config.swift_build_tool_configured && Dir['*.xcodeproj', '*.xcworkspace'].empty? && !module_config.build_tool_arguments.include?('-project') && !module_config.build_tool_arguments.include?('-workspace')) end
Strip tags and convert entities
# File lib/jazzy/sourcekitten.rb, line 390 def self.xml_to_text(xml) document = REXML::Document.new(xml) REXML::XPath.match(document.root, '//text()').map(&:value).join rescue StandardError '' end