Skip to content

Commit

Permalink
Use the compiler's version of Swift declarations
Browse files Browse the repository at this point in the history
...except when the parsed one has more information or looks
better.  Notes on the various declarations from sourcekitten:

[fully_]annotated_declaration - present if the declaration is
compiled (not #if'd out).  'Quick Help' print options, includes
all attributes except @available.

doc.declaration - from the full_as_xml, so only if there is a doc
comment + the decl is compiled.  'Interface' print options.  Subtle
differences to annotated_declaration: does include @available, does
not include @discardableResult, ignores `deinit`.

parsed_declaration - from source code, omits attributes on lines
above the start of the Swift decl.
  • Loading branch information
johnfairh committed Apr 4, 2018
1 parent 686908a commit c0bea0f
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 10 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
[Nick Fox](https://github.com/nicholasffox)
[#949](https://github.com/realm/jazzy/issues/949)

* Improve Swift declarations to look more like the Xcode Quick Help version,
for example including { get set }, and include all attributes.
[John Fairhurst](https://github.com/johnfairh)
[#768](https://github.com/realm/jazzy/issues/768)
[#591](https://github.com/realm/jazzy/issues/591)

##### Bug Fixes

* Preserve `MARK` comment headings associated with extensions and enum cases.
Expand Down
8 changes: 8 additions & 0 deletions lib/jazzy/source_declaration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ def fully_qualified_name
namespace_path.map(&:name).join('.')
end

# :name doesn't include any generic type params.
# This regexp matches any generic type params in parent names.
def fully_qualified_name_regexp
Regexp.new(namespace_path.map(&:name)
.map { |n| Regexp.escape(n) }
.join('(?:<.*?>)?\.'))
end

# If this declaration is an objc category, returns an array with the name
# of the extended objc class and the category name itself, i.e.
# ["NSString", "MyMethods"], nil otherwise.
Expand Down
102 changes: 93 additions & 9 deletions lib/jazzy/sourcekitten.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'shellwords'
require 'xcinvoke'
require 'cgi'
require 'rexml/document'

require 'jazzy/config'
require 'jazzy/executable'
Expand Down Expand Up @@ -43,6 +44,10 @@ def autolink_block(doc_url, middle_regex, after_highlight)
end
end
end

def unindent(count)
gsub(/^#{' ' * count}/, '')
end
end

module Jazzy
Expand Down Expand Up @@ -304,17 +309,17 @@ def self.parameters(doc, discovered)
end.reject { |param| param[:discussion].nil? }
end

# rubocop:disable Metrics/CyclomaticComplexity
def self.make_doc_info(doc, declaration)
return unless should_document?(doc)

declaration.declaration = Highlighter.highlight(
doc['key.parsed_declaration'] || doc['key.doc.declaration'],
)
if Config.instance.objc_mode && doc['key.swift_declaration']
declaration.other_language_declaration = Highlighter.highlight(
doc['key.swift_declaration'], 'swift'
)
if Config.instance.objc_mode
declaration.declaration =
Highlighter.highlight(doc['key.parsed_declaration'])
declaration.other_language_declaration =
Highlighter.highlight(doc['key.swift_declaration'], 'swift')
else
declaration.declaration =
Highlighter.highlight(make_swift_declaration(doc, declaration))
end

unless doc['key.doc.full_as_xml']
Expand All @@ -329,7 +334,86 @@ def self.make_doc_info(doc, declaration)

@stats.add_documented
end
# rubocop:enable Metrics/CyclomaticComplexity

# Strip tags and convert entities
def self.xml_to_text(xml)
document = REXML::Document.new(xml)
REXML::XPath.match(document.root, '//text()').map(&:value).join
end

# Regexp to match an @attribute. Complex to handle @available().
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

# Get all attributes of some name
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

def self.extract_availability(declaration)
extract_attributes(declaration, 'available')
end

# Split leading attributes from a decl, returning both parts.
def self.split_decl_attributes(declaration)
declaration =~ /^((?:#{attribute_regexp('\w+')}\s*)*)(.*)$/m
Regexp.last_match.captures
end

def self.prefer_parsed_decl?(parsed, annotated)
parsed &&
(annotated.include?(' = default') || # SR-2608
parsed.match('@autoclosure|@escaping') || # SR-6321
parsed.include?("\n"))
end

# Replace the fully qualified name of a type with its base name
def self.unqualify_name(annotated_decl, declaration)
annotated_decl.gsub(declaration.fully_qualified_name_regexp,
declaration.name)
end

# Find the best Swift declaration
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']

decl =
if prefer_parsed_decl?(parsed_decl, annotated_decl_body)
# 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
# Strip ugly references to decl type name
unqualify_name(annotated_decl_body, declaration)
end

# @available attrs only in compiler 'interface' style
available_attrs = extract_availability(doc['key.doc.declaration'] || '')

available_attrs.concat(extract_attributes(annotated_decl_attrs))
.push(decl)
.join("\n")
end

def self.make_substructure(doc, declaration)
declaration.children = if doc['key.substructure']
Expand Down
2 changes: 1 addition & 1 deletion spec/integration_specs
Submodule integration_specs updated 304 files

0 comments on commit c0bea0f

Please sign in to comment.