diff --git a/CHANGELOG.md b/CHANGELOG.md index edee4e26b..036ffb475 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ [Brian Osborn](https://github.com/bosborn) [John Fairhurst](https://github.com/johnfairh) +* Support Swift concurrency features: identify actors and asynchronous + methods. + [John Fairhurst](https://github.com/johnfairh) + ##### Bug Fixes * None. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 79ebdef2b..fb3158a26 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ git push You'll need push access to the integration specs repo to do this. You can request access from one of the maintainers when filing your PR. -You must have Xcode 12.5 installed to build the integration specs. +You must have Xcode 13 installed to build the integration specs. ## Making changes to SourceKitten diff --git a/lib/jazzy/doc_builder.rb b/lib/jazzy/doc_builder.rb index 5ca4d12f6..8de6a5d8d 100644 --- a/lib/jazzy/doc_builder.rb +++ b/lib/jazzy/doc_builder.rb @@ -370,6 +370,8 @@ def self.render_item(item, source_module) deprecation_message: item.deprecation_message, unavailable_message: item.unavailable_message, usage_discouraged: item.usage_discouraged?, + async: item.async, + declaration_note: item.declaration_note, } end # rubocop:enable Metrics/MethodLength diff --git a/lib/jazzy/highlighter.rb b/lib/jazzy/highlighter.rb index 32bcae732..2320d98bf 100644 --- a/lib/jazzy/highlighter.rb +++ b/lib/jazzy/highlighter.rb @@ -2,6 +2,18 @@ require 'rouge' +# While Rouge is downlevel (Rouge PR#1715 unreleased) +module Rouge + module Lexers + class Swift + prepend :root do + rule(/\b(?:async|await|isolated)\b/, Keyword) + rule(/\b(?:actor|nonisolated)\b/, Keyword::Declaration) + end + end + end +end + module Jazzy # This module helps highlight code module Highlighter diff --git a/lib/jazzy/source_declaration.rb b/lib/jazzy/source_declaration.rb index 828056052..f31a6b6ee 100644 --- a/lib/jazzy/source_declaration.rb +++ b/lib/jazzy/source_declaration.rb @@ -138,6 +138,7 @@ def display_other_language_declaration attr_accessor :unavailable_message attr_accessor :generic_requirements attr_accessor :inherited_types + attr_accessor :async def usage_discouraged? unavailable || deprecated @@ -188,6 +189,14 @@ def type_from_doc_module? (swift? && usr && modulename.nil?) end + # Info text for contents page by collapsed item name + def declaration_note + notes = [default_impl_abstract ? 'default implementation' : nil, + from_protocol_extension ? 'extension method' : nil, + async ? 'asynchronous' : nil].compact + notes.join(', ').humanize unless notes.empty? + end + def alternative_abstract if file = alternative_abstract_file Pathname(file).read diff --git a/lib/jazzy/source_declaration/type.rb b/lib/jazzy/source_declaration/type.rb index 076071dde..26833ebc2 100644 --- a/lib/jazzy/source_declaration/type.rb +++ b/lib/jazzy/source_declaration/type.rb @@ -12,11 +12,24 @@ def self.all attr_reader :kind - def initialize(kind) + def initialize(kind, declaration = nil) + kind = fixup_kind(kind, declaration) if declaration @kind = kind @type = TYPES[kind] end + # Improve kind from full declaration + def fixup_kind(kind, declaration) + if kind == 'source.lang.swift.decl.class' && + declaration.include?( + 'actor', + ) + 'source.lang.swift.decl.actor' + else + kind + end + end + def dash_type @type && @type[:dash] end @@ -115,7 +128,8 @@ def swift_extension? end def swift_extensible? - kind =~ /^source\.lang\.swift\.decl\.(class|struct|protocol|enum)$/ + kind =~ + /^source\.lang\.swift\.decl\.(class|struct|protocol|enum|actor)$/ end def swift_protocol? @@ -274,6 +288,11 @@ def ==(other) }.freeze, # Swift + 'source.lang.swift.decl.actor' => { + jazzy: 'Actor', + dash: 'Actor', + global: true, + }.freeze, 'source.lang.swift.decl.function.accessor.address' => { jazzy: 'Addressor', dash: 'Function', diff --git a/lib/jazzy/sourcekitten.rb b/lib/jazzy/sourcekitten.rb index fa6d037c8..8cbc45eda 100644 --- a/lib/jazzy/sourcekitten.rb +++ b/lib/jazzy/sourcekitten.rb @@ -524,6 +524,14 @@ def self.make_swift_declaration(doc, declaration) .join("\n") end + # Exclude non-async routines that accept async closures + 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 + # Strip default property attributes because libclang # adds them all, even if absent in the original source code. DEFAULT_ATTRIBUTES = %w[atomic readwrite assign unsafe_unretained].freeze @@ -565,7 +573,9 @@ def self.make_source_declarations(docs, parent = nil, mark = SourceMark.new) end declaration = SourceDeclaration.new declaration.parent_in_code = parent - declaration.type = SourceDeclaration::Type.new(doc['key.kind']) + 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'] @@ -610,6 +620,11 @@ def self.make_source_declarations(docs, parent = nil, mark = SourceMark.new) 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) @@ -819,7 +834,9 @@ 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.name == ext_member.name && + p.type == ext_member.type && + p.async == ext_member.async end # Extension-only method, keep. diff --git a/lib/jazzy/symbol_graph/graph.rb b/lib/jazzy/symbol_graph/graph.rb index db98490e6..a738fb9c2 100644 --- a/lib/jazzy/symbol_graph/graph.rb +++ b/lib/jazzy/symbol_graph/graph.rb @@ -78,8 +78,13 @@ def rel_source_name(rel, source_node) # Protocol conformance is redundant if it's unconditional # and already expressed in the type's declaration. + # + # Skip implementation-detail conformances. def redundant_conformance?(rel, type, protocol) - type && rel.constraints.empty? && type.conformance?(protocol) + return false unless type + + (rel.constraints.empty? && type.conformance?(protocol)) || + (type.actor? && rel.actor_protocol?) end # source is a member/protocol requirement of target diff --git a/lib/jazzy/symbol_graph/relationship.rb b/lib/jazzy/symbol_graph/relationship.rb index 97959d140..a32a11b80 100644 --- a/lib/jazzy/symbol_graph/relationship.rb +++ b/lib/jazzy/symbol_graph/relationship.rb @@ -22,6 +22,12 @@ def default_implementation? kind == :defaultImplementationOf end + # Protocol conformances added by compiler to actor decls that + # users aren't interested in. + def actor_protocol? + %w[Actor Sendable].include?(target_fallback) + end + def initialize(hash) kind = hash[:kind] unless KINDS.include?(kind) diff --git a/lib/jazzy/symbol_graph/sym_node.rb b/lib/jazzy/symbol_graph/sym_node.rb index 65200b6c7..4b307ca96 100644 --- a/lib/jazzy/symbol_graph/sym_node.rb +++ b/lib/jazzy/symbol_graph/sym_node.rb @@ -25,6 +25,7 @@ def children_to_sourcekit # A SymNode is a node of the reconstructed syntax tree holding a symbol. # It can turn itself into SourceKit and helps decode extensions. + # rubocop:disable Metrics/ClassLength class SymNode < BaseNode attr_accessor :symbol attr_writer :override @@ -61,6 +62,10 @@ def protocol? symbol.kind.end_with?('protocol') end + def actor? + symbol.kind.end_with?('actor') + end + def constraints symbol.constraints end @@ -112,6 +117,11 @@ def inherits_clause " : #{superclass_name}" end + # approximately... + def async? + symbol.declaration =~ /\basync\b[^)]*$/ + end + def full_declaration symbol.attributes .append(symbol.declaration + inherits_clause + where_clause) @@ -130,6 +140,7 @@ def to_sourcekit 'key.accessibility' => symbol.acl, 'key.parsed_decl' => declaration, 'key.annotated_decl' => xml_declaration, + 'key.symgraph_async' => async?, } if docs = symbol.doc_comments hash['key.doc.comment'] = docs @@ -160,5 +171,6 @@ def <=>(other) symbol <=> other.symbol end end + # rubocop:enable Metrics/ClassLength end end diff --git a/lib/jazzy/symbol_graph/symbol.rb b/lib/jazzy/symbol_graph/symbol.rb index a2bb66c7d..7429934c6 100644 --- a/lib/jazzy/symbol_graph/symbol.rb +++ b/lib/jazzy/symbol_graph/symbol.rb @@ -25,8 +25,8 @@ def name def initialize(hash) self.usr = hash[:identifier][:precise] self.path_components = hash[:pathComponents] - raw_decl = hash[:declarationFragments].map { |f| f[:spelling] }.join - init_kind(hash[:kind][:identifier]) + raw_decl, keywords = parse_decl_fragments(hash[:declarationFragments]) + init_kind(hash[:kind][:identifier], keywords) init_declaration(raw_decl) if func_signature = hash[:functionSignature] init_func_signature(func_signature) @@ -44,6 +44,16 @@ def initialize(hash) init_generic_type_params(hash) end + def parse_decl_fragments(fragments) + decl = '' + keywords = Set.new + fragments.each do |frag| + decl += frag[:spelling] + keywords.add(frag[:spelling]) if frag[:kind] == 'keyword' + end + [decl, keywords] + end + # Repair problems with SymbolGraph's declprinter def init_declaration(raw_decl) @@ -89,17 +99,22 @@ def init_func_signature(func_signature) 'static.subscript' => 'function.subscript', 'typealias' => 'typealias', 'associatedtype' => 'associatedtype', + 'actor' => 'actor', }.freeze # We treat 'static var' differently to 'class var' - def adjust_kind_for_declaration(kind) - return kind unless declaration =~ /\bstatic\b/ + # We treat actors as first-class entities + def adjust_kind_for_declaration(kind, keywords) + if kind == 'swift.class' && keywords.member?('actor') + return 'swift.actor' + end + return kind unless keywords.member?('static') kind.gsub(/type/, 'static') end - def init_kind(kind) - adjusted = adjust_kind_for_declaration(kind) + def init_kind(kind, keywords) + adjusted = adjust_kind_for_declaration(kind, keywords) sourcekit_kind = KIND_MAP[adjusted.sub('swift.', '')] raise "Unknown symbol kind '#{kind}'" unless sourcekit_kind diff --git a/lib/jazzy/themes/apple/templates/task.mustache b/lib/jazzy/themes/apple/templates/task.mustache index ea738c009..1353dafd5 100644 --- a/lib/jazzy/themes/apple/templates/task.mustache +++ b/lib/jazzy/themes/apple/templates/task.mustache @@ -28,16 +28,11 @@ {{{name_html}}} {{/usage_discouraged}} - {{#default_impl_abstract}} + {{#declaration_note}} - Default implementation + {{.}} - {{/default_impl_abstract}} - {{#from_protocol_extension}} - - Extension method - - {{/from_protocol_extension}} + {{/declaration_note}}
diff --git a/lib/jazzy/themes/fullwidth/templates/task.mustache b/lib/jazzy/themes/fullwidth/templates/task.mustache index 456d7576a..a33f74f62 100644 --- a/lib/jazzy/themes/fullwidth/templates/task.mustache +++ b/lib/jazzy/themes/fullwidth/templates/task.mustache @@ -28,16 +28,11 @@ {{{name_html}}} {{/usage_discouraged}} - {{#default_impl_abstract}} + {{#declaration_note}} - Default implementation + {{.}} - {{/default_impl_abstract}} - {{#from_protocol_extension}} - - Extension method - - {{/from_protocol_extension}} + {{/declaration_note}}
diff --git a/lib/jazzy/themes/jony/templates/task.mustache b/lib/jazzy/themes/jony/templates/task.mustache index ea738c009..1353dafd5 100644 --- a/lib/jazzy/themes/jony/templates/task.mustache +++ b/lib/jazzy/themes/jony/templates/task.mustache @@ -28,16 +28,11 @@ {{{name_html}}} {{/usage_discouraged}} - {{#default_impl_abstract}} + {{#declaration_note}} - Default implementation + {{.}} - {{/default_impl_abstract}} - {{#from_protocol_extension}} - - Extension method - - {{/from_protocol_extension}} + {{/declaration_note}}
diff --git a/spec/integration_specs b/spec/integration_specs index 55663efa5..c28a0b31e 160000 --- a/spec/integration_specs +++ b/spec/integration_specs @@ -1 +1 @@ -Subproject commit 55663efa5dad0f3526c4b1cfa12a073c8c4704e1 +Subproject commit c28a0b31eb89969f59ea25f466f0c3cac7435d19