module Jazzy::SourceKitten

This module interacts with the sourcekitten command-line executable

Constants

DEFAULT_ATTRIBUTES

Strip default property attributes because libclang adds them all, even if absent in the original source code.

Public Class Methods

arguments_from_options(module_config) click to toggle source

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
attribute?(doc, attr_name) click to toggle source
# 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
attribute_regexp(name) click to toggle source

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
availability_attribute?(doc) click to toggle source
# File lib/jazzy/sourcekitten.rb, line 250
def self.availability_attribute?(doc)
  attribute?(doc, 'available')
end
deduplicate_declarations(declarations) click to toggle source

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
deduplication_key(decl, root_decls) click to toggle source

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
expand_extension(extension, name_parts, decls) click to toggle source
# 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
expand_extensions(decls) click to toggle source

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
extract_attributes(declaration, name = '\w+') click to toggle source

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
extract_availability(declaration) click to toggle source
# File lib/jazzy/sourcekitten.rb, line 424
def self.extract_availability(declaration)
  extract_attributes(declaration, 'available')
end
extract_documented_attributes(declaration) click to toggle source

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
fabricate_spi_attributes(doc, attrs) click to toggle source

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_excluded_files(json) click to toggle source

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
filter_files(json) click to toggle source

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_included_files(json) click to toggle source

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
find_generic_requirements(parsed_declaration) click to toggle source

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
fix_up_compiler_decl(annotated_decl, declaration) click to toggle source

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
highlight_declaration(doc, declaration) click to toggle source
# 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
make_default_doc_info(declaration) click to toggle source

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
make_deprecation_info(doc, declaration) click to toggle source
# 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
make_doc_info(doc, declaration) click to toggle source
# 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
make_doc_urls(docs) click to toggle source

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
make_objc_declaration(declaration) click to toggle source
# 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
make_source_declarations(docs, parent = nil, mark = SourceMark.new) click to toggle source

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
make_substructure(doc, declaration) click to toggle source
# 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
make_swift_declaration(doc, declaration) click to toggle source

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
mark_and_merge_protocol_extensions(protocol, extensions) click to toggle source

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
mark_objc_external_categories(docs) click to toggle source

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_code_declaration(decls) click to toggle source

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
merge_declarations(decls) click to toggle source

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
merge_objc_declaration_marks(typedecl, extensions) click to toggle source

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
merge_type_and_extensions(typedecls, extensions) click to toggle source

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
mergeable_objc?(decl, root_decls) click to toggle source

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
mergeable_swift?(decl) click to toggle source

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
module_deduplication_key(decl) click to toggle source

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
move_merged_extension_marks(decls) click to toggle source

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
need_doc_module_note?(decl, html_declaration) click to toggle source

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
next_doc_module_group(decls) click to toggle source

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
objc_arguments_from_options(module_config) click to toggle source
# 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
parameters(doc, discovered) click to toggle source
# 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_output, options, inject_docs) click to toggle source

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
prefer_parsed_decl?(parsed, annotated, type) click to toggle source
# 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
process_undocumented_token(doc, declaration) click to toggle source
# 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
rec_path(path) click to toggle source

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
reject_inaccessible_extensions(typedecl, extensions) click to toggle source

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
reject_objc_types(docs) click to toggle source
# 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
reject_swift_types(docs) click to toggle source

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(arguments) click to toggle source

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
sanitize_filename(doc) click to toggle source

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
should_document?(doc) click to toggle source
# 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
should_document_acl?(type, doc) click to toggle source

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
should_document_spi?(doc) click to toggle source

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
should_document_swift_extension?(doc) click to toggle source

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
spi_attribute?(doc) click to toggle source
# File lib/jazzy/sourcekitten.rb, line 254
def self.spi_attribute?(doc)
  attribute?(doc, '_spi')
end
split_decl_attributes(declaration) click to toggle source

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
subdir_for_doc(doc) click to toggle source

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
subdir_for_doc_multi_module(doc) click to toggle source

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
subdir_for_doc_single_module(doc) click to toggle source

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
swift_async?(fully_annotated_decl) click to toggle source

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
undocumented_abstract() click to toggle source
# File lib/jazzy/sourcekitten.rb, line 59
def self.undocumented_abstract
  @undocumented_abstract ||= Markdown.render(
    Config.instance.undocumented_text,
  ).freeze
end
use_spm?(module_config) click to toggle source
# 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
xml_to_text(xml) click to toggle source

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