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.
  • Loading branch information
johnfairh committed Nov 10, 2017
1 parent 783d33d commit 3ef7b57
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 14 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@

##### Enhancements

* None.
* Improve Swift declarations to look more like the Xcode Quick Help version,
for example including { get set }, and include more 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

Expand Down
8 changes: 8 additions & 0 deletions lib/jazzy/highlighter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,13 @@ module Highlighter
def self.highlight(source, language)
source && Rouge.highlight(source, language, 'html')
end

def self.highlight_objc(source)
highlight(source, 'objc')
end

def self.highlight_swift(source)
highlight(source, 'swift')
end
end
end
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
78 changes: 66 additions & 12 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 @@ -301,19 +302,17 @@ def self.parameters(doc, discovered)
end.reject { |param| param[:discussion].nil? }
end

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

declaration.declaration = Highlighter.highlight(
doc['key.parsed_declaration'] || doc['key.doc.declaration'],
Config.instance.objc_mode ? 'objc' : 'swift',
)
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_objc(doc['key.parsed_declaration'])
declaration.other_language_declaration =
Highlighter.highlight_swift(doc['key.swift_declaration'])
else
declaration.declaration =
Highlighter.highlight_swift(make_swift_declaration(doc, declaration))
end

unless doc['key.doc.full_as_xml']
Expand All @@ -327,8 +326,63 @@ def self.make_doc_info(doc, declaration)

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

# 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

# Pull out attributes and their parameters
def self.extract_attributes(declaration, name = '\w+')
declaration.scan(/@#{name}\s*(?:\((?:[^")]*|"(?:[^"\\]*|\\.)*")*\))?/m)
end

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

def self.prefer_parsed_decl?(parsed, annotated_xml)
parsed &&
(annotated_xml.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

# From source code
parsed_decl = doc['key.parsed_declaration']

decl =
if prefer_parsed_decl?(parsed_decl, annotated_decl_xml)
parsed_decl
else
unqualify_name(xml_to_text(annotated_decl_xml), declaration)
end

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

# Other declaration attributes
decl =~ /^((?:@\w+\s*(?:\([^)]*\)\s*)?)*)(.*)$/m
decl_attr_part, decl_main_part = Regexp.last_match.captures

available_attrs.concat(extract_attributes(decl_attr_part))
.push(decl_main_part)
.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 3ef7b57

Please sign in to comment.