Skip to content

Commit

Permalink
Support mixed Swift and ObjC projects (#1113)
Browse files Browse the repository at this point in the history
Treat each declaration individually instead of the global `--objc` flag.
Support multiple sourcekitten json files with the `-s` argument.
  • Loading branch information
joesus authored and johnfairh committed Oct 19, 2019
1 parent 9ef4459 commit 30957db
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 58 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.
* Support for mixed Swift-ObjC modules: generate two sets of SourceKitten
json and pass them on using `--sourcekitten-sourcefile`.
[Joe Susnick](https://github.com/joesus)
[John Fairhurst](https://github.com/johnfairh)
[#447](https://github.com/realm/jazzy/issues/447)

##### Bug Fixes

Expand Down
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,33 @@ jazzy \
--module AFNetworking
```

### Mixed Objective-C / Swift

*This feature is new and has some rough edges.*

To generate documentation for a mixed Swift and Objective-C project you must first
generate two [SourceKitten][sourcekitten] files: one for Swift and one for Objective-C.

Then pass these files to Jazzy together using `--sourcekitten-sourcefile`.

#### Example

This is how docs are generated from an Xcode project for a module containing both
Swift and Objective-C files:

```shell
# Generate Swift SourceKitten output
sourcekitten doc -- -workspace MyProject.xcworkspace -scheme MyScheme > swiftDoc.json

# Generate Objective-C SourceKitten output
sourcekitten doc --objc $(pwd)/MyProject/MyProject.h \
-- -x objective-c -isysroot $(xcrun --show-sdk-path --sdk iphonesimulator) \
-I $(pwd) -fmodules > objcDoc.json

# Feed both outputs to Jazzy as a comma-separated list
jazzy --sourcekitten-sourcefile swiftDoc.json,objcDoc.json
```

### Themes

Three themes are provided with jazzy: `apple` (default), `fullwidth` and `jony`.
Expand Down Expand Up @@ -331,7 +358,7 @@ Instructions to build SourceKitten from source can be found at
- Leverage modern HTML templating ([Mustache][mustache])
- Leverage the power and accuracy of the [Clang AST][ast] and [SourceKit][sourcekit]
- Support for Dash docsets
- Support Swift and Objective-C (*mixed projects are a work in progress*)
- Support Swift and Objective-C

## License

Expand Down
21 changes: 15 additions & 6 deletions lib/jazzy/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ def expand_path(path)
Pathname(Dir[abs_path][0] || abs_path) # Use existing filesystem spelling
end

def hide_swift?
hide_declarations == 'swift'
end

def hide_objc?
hide_declarations == 'objc'
end

# ──────── Build ────────

# rubocop:disable Layout/AlignParameters
Expand Down Expand Up @@ -143,9 +151,9 @@ def expand_path(path)
command_line: '--hide-declarations [objc|swift] ',
description: 'Hide declarations in the specified language. Given that ' \
'generating Swift docs only generates Swift declarations, ' \
'this is only really useful to display just the Swift ' \
'declarations & names when generating docs for an ' \
'Objective-C framework.',
'this is useful for hiding a specific interface for ' \
'either Objective-C or mixed Objective-C and Swift ' \
'projects.',
default: ''

config_attr :config_file,
Expand All @@ -165,9 +173,10 @@ def expand_path(path)
description: 'Back-compatibility alias for build_tool_arguments.'

config_attr :sourcekitten_sourcefile,
command_line: ['-s', '--sourcekitten-sourcefile FILEPATH'],
description: 'File generated from sourcekitten output to parse',
parse: ->(s) { expand_path(s) }
command_line: ['-s', '--sourcekitten-sourcefile filepath1,…filepathN',
Array],
description: 'File(s) generated from sourcekitten output to parse',
parse: ->(paths) { paths.map { |path| expand_path(path) } }

config_attr :source_directory,
command_line: '--source-directory DIRPATH',
Expand Down
4 changes: 0 additions & 4 deletions lib/jazzy/doc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ def objc_first?
config.objc_mode && config.hide_declarations != 'objc'
end

def language
objc_first? ? 'Objective-C' : 'Swift'
end

def language_stub
objc_first? ? 'objc' : 'swift'
end
Expand Down
6 changes: 4 additions & 2 deletions lib/jazzy/doc_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ def self.doc_structure_for_docs(docs)
# @param [Config] options
# @return [SourceModule] the documented source module
def self.build(options)
if options.sourcekitten_sourcefile
stdout = options.sourcekitten_sourcefile.read
if options.sourcekitten_sourcefile_configured
stdout = '[' + options.sourcekitten_sourcefile.map(&:read)
.join(',') + ']'
elsif options.podspec_configured
pod_documenter = PodspecDocumenter.new(options.podspec)
stdout = pod_documenter.sourcekitten_output(options)
Expand Down Expand Up @@ -329,6 +330,7 @@ def self.render_item(item, source_module)
name: item.name,
abstract: abstract,
declaration: item.display_declaration,
language: item.display_language,
other_language_declaration: item.display_other_language_declaration,
usr: item.usr,
dash_type: item.type.dash_type,
Expand Down
18 changes: 10 additions & 8 deletions lib/jazzy/highlighter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
module Jazzy
# This module helps highlight code
module Highlighter
SWIFT = 'swift'.freeze
OBJC = 'objective_c'.freeze

class Formatter < Rouge::Formatters::HTML
def initialize(language)
@language = language
Expand All @@ -16,16 +19,15 @@ def stream(tokens, &b)
end
end

# What Rouge calls the language
def self.default_language
if Config.instance.objc_mode
'objective_c'
else
'swift'
end
def self.highlight_swift(source)
highlight(source, SWIFT)
end

def self.highlight_objc(source)
highlight(source, OBJC)
end

def self.highlight(source, language = default_language)
def self.highlight(source, language)
source && Rouge.highlight(source, language, Formatter.new(language))
end
end
Expand Down
26 changes: 20 additions & 6 deletions lib/jazzy/source_declaration.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'jazzy/source_declaration/access_control_level'
require 'jazzy/source_declaration/type'

# rubocop:disable Metrics/ClassLength
module Jazzy
class SourceDeclaration
# kind of declaration (e.g. class, variable, function)
Expand All @@ -13,6 +14,14 @@ def render_as_page?
children.any?
end

def swift?
type.swift_type?
end

def highlight_language
swift? ? Highlighter::SWIFT : Highlighter::OBJC
end

# When referencing this item from its parent category,
# include the content or just link to it directly?
def omit_content_from_parent?
Expand Down Expand Up @@ -67,17 +76,22 @@ def objc_category_name
name.split(/[\(\)]/) if type.objc_category?
end

# The language in the templates for display
def display_language
return 'Swift' if swift?

Config.instance.hide_objc? ? 'Swift' : 'Objective-C'
end

def display_declaration
if Config.instance.hide_declarations == 'objc'
other_language_declaration
else
declaration
end
return declaration if swift?

Config.instance.hide_objc? ? other_language_declaration : declaration
end

def display_other_language_declaration
other_language_declaration unless
%w[swift objc].include? Config.instance.hide_declarations
Config.instance.hide_objc? || Config.instance.hide_swift?
end

attr_accessor :file
Expand Down
4 changes: 4 additions & 0 deletions lib/jazzy/source_declaration/type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ def objc_class?
kind == 'sourcekitten.source.lang.objc.decl.class'
end

def swift_type?
kind.include? 'swift'
end

def swift_enum_case?
kind == 'source.lang.swift.decl.enumcase'
end
Expand Down
55 changes: 29 additions & 26 deletions lib/jazzy/sourcekitten.rb
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,13 @@ def self.availability_attribute?(doc)
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 if Config.instance.objc_mode
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.
Expand All @@ -272,7 +277,6 @@ def self.should_document?(doc)
end

# Document extensions & enum elements, since we can't tell their ACL.
type = SourceDeclaration::Type.new(doc['key.kind'])
return true if type.swift_enum_element?
if type.swift_extension?
return Array(doc['key.substructure']).any? do |subdoc|
Expand All @@ -296,14 +300,14 @@ def self.process_undocumented_token(doc, declaration)
make_default_doc_info(declaration)

filepath = doc['key.filepath']
objc = Config.instance.objc_mode
if objc || should_mark_undocumented(filepath)

if !declaration.swift? || should_mark_undocumented(filepath)
@stats.add_undocumented(declaration)
return nil if @skip_undocumented
declaration.abstract = undocumented_abstract
else
declaration.abstract = Markdown.render(doc['key.doc.comment'] || '',
Highlighter.default_language)
declaration.highlight_language)
end

declaration
Expand All @@ -322,31 +326,34 @@ def self.parameters(doc, discovered)
def self.make_doc_info(doc, declaration)
return unless should_document?(doc)

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

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'] || '',
Highlighter.default_language)
declaration.highlight_language)
declaration.discussion = ''
declaration.return = Markdown.rendered_returns
declaration.parameters = parameters(doc, Markdown.rendered_parameters)

@stats.add_documented
end

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(doc['key.parsed_declaration'])
declaration.other_language_declaration =
Highlighter.highlight_swift(doc['key.swift_declaration'])
end
end

def self.make_deprecation_info(doc, declaration)
if declaration.deprecated
declaration.deprecation_message =
Expand Down Expand Up @@ -473,8 +480,7 @@ def self.make_source_declarations(docs, parent = nil, mark = SourceMark.new)
declaration.type = SourceDeclaration::Type.new(doc['key.kind'])
declaration.typename = doc['key.typename']
declaration.objc_name = doc['key.name']
documented_name = if Config.instance.hide_declarations == 'objc' &&
doc['key.swift_name']
documented_name = if Config.instance.hide_objc? && doc['key.swift_name']
doc['key.swift_name']
else
declaration.objc_name
Expand Down Expand Up @@ -828,17 +834,14 @@ def self.parse(sourcekitten_output, min_acl, skip_undocumented, inject_docs)
@min_acl = min_acl
@skip_undocumented = skip_undocumented
@stats = Stats.new
sourcekitten_json = filter_files(JSON.parse(sourcekitten_output))
sourcekitten_json = filter_files(JSON.parse(sourcekitten_output).flatten)
docs = make_source_declarations(sourcekitten_json).concat inject_docs
docs = expand_extensions(docs)
docs = deduplicate_declarations(docs)
if Config.instance.objc_mode
docs = reject_objc_types(docs)
else
# Remove top-level enum cases because it means they have an ACL lower
# than min_acl
docs = docs.reject { |doc| doc.type.swift_enum_element? }
end
docs = reject_objc_types(docs)
# Remove top-level enum cases because it means they have an ACL lower
# than min_acl
docs = docs.reject { |doc| doc.type.swift_enum_element? }
ungrouped_docs = docs
docs = group_docs(docs)
make_doc_urls(docs)
Expand Down
23 changes: 20 additions & 3 deletions spec/integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,11 @@ def configure_cocoapods
File.write(
path,
File.read(path).gsub(
(ROOT + 'tmp').to_s,
c.temp_path.to_s,
'<TMP>',
).gsub(
c.spec_path.to_s,
'<SPEC>',
),
)
end
Expand Down Expand Up @@ -160,9 +163,23 @@ def configure_cocoapods
"--head #{realm_head.shellescape}"
end

describe 'Creates docs for ObjC project with a variety of contents' do
describe 'Creates docs for ObjC-Swift project with a variety of contents' do
base = ROOT + 'spec/integration_specs/misc_jazzy_objc_features/before'
Dir.chdir(base) do
sourcekitten = ROOT + 'bin/sourcekitten'
sdk = `xcrun --show-sdk-path --sdk iphonesimulator`.chomp
objc_args = "#{base}/MiscJazzyObjCFeatures/MiscJazzyObjCFeatures.h " \
'-- -x objective-c ' \
"-isysroot #{sdk} " \
"-I #{base} " \
'-fmodules'
`#{sourcekitten} doc --objc #{objc_args} > objc.json`
`#{sourcekitten} doc > swift.json`
end

behaves_like cli_spec 'misc_jazzy_objc_features',
'--theme fullwidth'
'--theme fullwidth '\
'-s objc.json,swift.json'
end
end if !spec_subset || spec_subset == 'objc'

Expand Down
2 changes: 1 addition & 1 deletion spec/integration_specs
Submodule integration_specs updated 51 files
+32 −1 misc_jazzy_objc_features/after/docs/Categories/NSValue(SomeAdditions).html
+32 −1 misc_jazzy_objc_features/after/docs/Categories/NSValue(SomeMoreAdditions).html
+195 −0 misc_jazzy_objc_features/after/docs/Classes/ObjCDifferentlyNamed.html
+195 −0 misc_jazzy_objc_features/after/docs/Classes/ObjCDifferentlyNamedSettings.html
+32 −1 misc_jazzy_objc_features/after/docs/Classes/ObjCLowLevelClass.html
+32 −1 misc_jazzy_objc_features/after/docs/Classes/ObjCMidLevelClass.html
+32 −1 misc_jazzy_objc_features/after/docs/Classes/ObjCTopLevelClass.html
+189 −0 misc_jazzy_objc_features/after/docs/Classes/SwiftMidLevelClass.html
+189 −0 misc_jazzy_objc_features/after/docs/Classes/TopLevelSwiftClass.html
+32 −1 misc_jazzy_objc_features/after/docs/Enums/SomeEnum.html
+223 −0 misc_jazzy_objc_features/after/docs/Extensions/DifferentlyNamed.html
+189 −0 misc_jazzy_objc_features/after/docs/Extensions/DifferentlyNamed/Settings.html
+189 −0 misc_jazzy_objc_features/after/docs/Extensions/ObjCTopLevelClass.html
+32 −1 misc_jazzy_objc_features/after/docs/Other Categories.html
+44 −30 misc_jazzy_objc_features/after/docs/Other Classes.html
+32 −1 misc_jazzy_objc_features/after/docs/Other Constants.html
+32 −1 misc_jazzy_objc_features/after/docs/Other Enums.html
+185 −0 misc_jazzy_objc_features/after/docs/Other Extensions.html
+64 −1 misc_jazzy_objc_features/after/docs/Ying.html
+2 −2 misc_jazzy_objc_features/after/docs/badge.svg
+32 −1 ...ures/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Categories/NSValue(SomeAdditions).html
+32 −1 .../after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Categories/NSValue(SomeMoreAdditions).html
+195 −0 ..._features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Classes/ObjCDifferentlyNamed.html
+195 −0 ...s/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Classes/ObjCDifferentlyNamedSettings.html
+32 −1 ...bjc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Classes/ObjCLowLevelClass.html
+32 −1 ...bjc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Classes/ObjCMidLevelClass.html
+32 −1 ...bjc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Classes/ObjCTopLevelClass.html
+189 −0 ...jc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Classes/SwiftMidLevelClass.html
+189 −0 ...jc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Classes/TopLevelSwiftClass.html
+32 −1 misc_jazzy_objc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Enums/SomeEnum.html
+223 −0 ...c_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Extensions/DifferentlyNamed.html
+189 −0 ...s/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Extensions/DifferentlyNamed/Settings.html
+189 −0 ..._features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Extensions/ObjCTopLevelClass.html
+32 −1 misc_jazzy_objc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Other Categories.html
+44 −30 misc_jazzy_objc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Other Classes.html
+32 −1 misc_jazzy_objc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Other Constants.html
+32 −1 misc_jazzy_objc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Other Enums.html
+185 −0 misc_jazzy_objc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Other Extensions.html
+64 −1 misc_jazzy_objc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/Ying.html
+32 −1 misc_jazzy_objc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/index.html
+1 −1 misc_jazzy_objc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/Documents/search.json
+42 −27 misc_jazzy_objc_features/after/docs/docsets/JazzyKit.docset/Contents/Resources/docSet.dsidx.csv
+32 −1 misc_jazzy_objc_features/after/docs/index.html
+1 −1 misc_jazzy_objc_features/after/docs/search.json
+10 −10 misc_jazzy_objc_features/after/docs/undocumented.json
+4 −4 misc_jazzy_objc_features/after/execution_output.txt
+4 −1 misc_jazzy_objc_features/before/.gitignore
+44 −0 misc_jazzy_objc_features/before/MiscJazzyObjCFeatures/SwiftTopLevelClass.swift
+10 −0 misc_jazzy_objc_features/before/MiscJazzyObjcFeatures.xcodeproj/project.pbxproj
+8 −0 ...c_features/before/MiscJazzyObjcFeatures.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+19 −1 misc_jazzy_objc_features/before/MiscJazzyObjcFeatures/ObjCTopLevelClass.h

0 comments on commit 30957db

Please sign in to comment.