A package for generating documentation for Swift projects.
This project is under active development and is expected to change significantly before its first stable release.
Given a directory of Swift files,
swift-doc
generates CommonMark (Markdown) files
for each class, structure, enumeration, and protocol
as well as top-level type aliases, functions, and variables.
For an example of generated documentation, check out the Wiki for our fork of Alamofire.
Note: Output is currently limited to CommonMark, but the plan is to support HTML and other formats as well.
swift-doc
can be used from the command-line on macOS and Linux.
Run the following command to install using Homebrew:
$ brew install swiftdocorg/formulae/swift-doc
Run the following commands to build and install manually:
$ git clone https://github.com/SwiftDocOrg/swift-doc
$ cd swift-doc
$ make install
swift-doc
takes one or more paths and enumerates them recursively,
collecting all Swift files into a single "module"
and generating documentation accordingly.
$ swift doc path/to/SwiftProject/Sources --output Documentation
$ tree Documentation
$ Documentation/
├── Home
├── (...)
├── _Footer.md
└── _Sidebar.md
By default,
output files are written to .build/documentation
,
but you can change that with the --output
option flag.
This repository also hosts a GitHub Action that you can incorporate into your project's workflow.
The CommonMark files generated by swift-doc
are formatted for publication to your project's GitHub Wiki,
which you can do with
github-wiki-publish-action.
Alternatively,
you could publish swift-doc
-generated documentation to GitHub Pages,
or bundle them into a release artifact.
inputs
: One or more paths to Swift files in your workspace. (Default:"./Sources"
)output
: The path for generated output. (Default:"./.build/documentation"
)
# .github/workflows/documentation.yml
name: Documentation
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Generate Documentation
uses: SwiftDocOrg/swift-doc@master
with:
inputs: "Source"
output: "Documentation"
- name: Upload Documentation to Wiki
uses: SwiftDocOrg/github-wiki-publish-action@master
with:
path: "Documentation"
env:
GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.GITHUB_PERSONAL_ACCESS_TOKEN }}
In addition to swift-doc
,
this project currently ships with several, experimental tools
that offer complementary functionality.
It's unclear how everything will ultimately fit together,
but for now,
they're incubating in a shared monorepo
(the intent is for each of them to eventually become
an option, subcommand, or plugin of swift-doc
).
Note: We recommend building and running these tools via
swift run
. If you prefer to have compiled binaries, you can generate them with the same commands used for theswift-doc
target inMakefile
.
swift-dcov
generates documentation coverage statistics for Swift files.
$ git clone https://github.com/SwiftDocOrg/SwiftSemantics.git
$ swift run swift-dcov SwiftSemantics/Sources/ | jq ".data.totals"
{
"count": 207,
"documented": 199,
"percent": 96.1352657004831
}
$ swift run swift-dcov SwiftSemantics/Sources/ | jq ".data.symbols[] | select(.documented == false)"
{
"file": "SwiftSemantics/Supporting Types/GenericRequirement.swift",
"line": 67,
"column": 6,
"name": "GenericRequirement.init?(_:)",
"type": "Initializer",
"documented": false
}
...
While there are plenty of tools for assessing test coverage for code, we weren't able to find anything analogous for documentation coverage. To this end, we've contrived a simple JSON format inspired by llvm-cov.
If you know of an existing standard that you think might be better suited for this purpose, please reach out by opening an Issue!
swift-api-inventory
lists the public API symbols of Swift files.
$ git clone https://github.com/SwiftDocOrg/SwiftSemantics.git
$ swift run swift-api-inventory SwiftSemantics/Sources | less
struct AssociatedType: Declaration, Hashable, Codable, ExpressibleBySyntax
var AssociatedType.attributes { get }
var AssociatedType.context { get }
var AssociatedType.keyword { get }
var AssociatedType.modifiers { get }
var AssociatedType.name { get }
...
$ swift run swift-api-inventory SwiftSemantics/Sources | wc
207 1023 8993
Because each symbol is printed on its own line,
you can feed the output of swift-api-inventory
to conventional diffing tools
to determine API changes between different releases of a project.
For example, here's an API diff between the first beta and latest release candidate of Alamofire 5:
$ git clone https://github.com/Alamofire/Alamofire.git
$ (cd Alamofire; git co 5.0.0-beta.1; swift run swift-api-inventory Source > ../Alamofire-5.0.0-beta.1.txt)
$ (cd Alamofire; git co 5.0.0-rc.3; swift run swift-api-inventory Source > ../Alamofire-5.0.0-rc.3.txt)
$ diff -u Alamofire-5.0.0-beta.1.txt Alamofire-5.0.0-rc.3.txt | diffstat
Alamofire-5.0.0-rc.3.txt | 346 ++++++++++++++++++++++++++++++++—————
1 file changed, 238 insertions(+), 108 deletions(-)
Example diff between Alamofire 5 RC3 and RC1
$ diff -u Alamofire-5.0.0-rc.1.txt Alamofire-5.0.0-rc.3.txt
— Alamofire-5.0.0-rc.1.txt
+++ Alamofire-5.0.0-rc.3.txt
@@ -77,6 +77,7 @@
case AFError.ServerTrustFailureReason.revocationCheckFailed(output: Output, options: RevocationTrustEvaluator.Options)
case AFError.ServerTrustFailureReason.revocationPolicyCreationFailed
case AFError.ServerTrustFailureReason.settingAnchorCertificatesFailed(status: OSStatus, certificates: [SecCertificate])
+case AFError.ServerTrustFailureReason.trustEvaluationFailed(error: Error?)
enum AFError.URLRequestValidationFailureReason
case AFError.URLRequestValidationFailureReason.bodyDataInGETRequest(_: Data)
case AFError.createURLRequestFailed(error: Error)
@@ -613,13 +614,14 @@
case URLEncodedFormEncoder.SpaceEncoding.percentEscaped
case URLEncodedFormEncoder.SpaceEncoding.plusReplaced
var URLEncodedFormEncoder.allowedCharacters { get set }
+var URLEncodedFormEncoder.alphabetizeKeyValuePairs { get }
var URLEncodedFormEncoder.arrayEncoding { get }
var URLEncodedFormEncoder.boolEncoding { get }
var URLEncodedFormEncoder.dataEncoding { get }
var URLEncodedFormEncoder.dateEncoding { get }
func URLEncodedFormEncoder.encode(_: Encodable) throws -> String
func URLEncodedFormEncoder.encode(_: Encodable) throws -> Data
-init URLEncodedFormEncoder(arrayEncoding: ArrayEncoding, boolEncoding: BoolEncoding, dataEncoding: DataEncoding, dateEncoding: DateEncoding, keyEncoding: KeyEncoding, spaceEncoding: SpaceEncoding, allowedCharacters: CharacterSet)
+init URLEncodedFormEncoder(alphabetizeKeyValuePairs: Bool, arrayEncoding: ArrayEncoding, boolEncoding: BoolEncoding, dataEncoding: DataEncoding, dateEncoding: DateEncoding, keyEncoding: KeyEncoding, spaceEncoding: SpaceEncoding, allowedCharacters: CharacterSet)
swift-api-diagram
generates a graph of APIs in DOT format
that can be rendered by GraphViz into a diagram.
$ swift run swift-api-diagram Alamofire/Source > graph.dot
$ head graph.dot
digraph Anonymous {
"Session" [shape=box];
"NetworkReachabilityManager" [shape=box];
"URLEncodedFormEncoder" [shape=box,peripheries=2];
"ServerTrustManager" [shape=box];
"MultipartFormData" [shape=box];
subgraph cluster_Request {
"DataRequest" [shape=box];
"Request" [shape=box];
$ dot -T svg graph.dot > graph.svg
Here's an excerpt of the graph generated for Alamofire:
From its earliest days, Swift has been fortunate to have Jazzy, which is a fantastic tool for generating documentation for both Swift and Objective-C projects. Over time, however, the way we write Swift code — and indeed the language itself — has evolved to incorporate patterns and features that are difficult to understand using the same documentation standards that served us well for Objective-C.
Whereas in Objective-C, you could get a complete view of a type's functionality from its class hierarchy, Swift code today tends to layer and distribute functionality across a network of types. While adopting a protocol-oriented paradigm can make Swift easier and more expressive to write, it can also make Swift code more difficult to understand.
Our primary goal for swift-doc
is to make Swift documentation more useful
by surfacing the information you need to understand how an API works
and presenting it in a way that can be easily searched and accessed.
We want developers to be empowered to use Swift packages to their full extent,
without being reliant on (often outdated) blog posts or Stack Overflow threads.
We want documentation coverage to become as important as test coverage:
a valuable metric for code quality,
and an expected part of first-rate open source projects.
Jazzy styles itself after Apple's official documentation circa 2014 (code-named "Jazz", as it were), which was well-suited to understanding Swift code as we wrote it back then when it was more similar to Objective-C. But this design is less capable of documenting the behavior of generically-constrained types, default implementations, dynamic member lookup, property wrappers, or function builders. (Alas, Apple's most recent take on reference documentation hasn't improved matters, having instead focused on perceived cosmetic issues.)
Without much in the way of strong design guidance, we're not entirely sure what Swift documentation should look like. But we do think plain text is a good place to start. We look forward to soliciting feedback and ideas from everyone so that we can identify those needs and figure out the best ways to meet them.
In the meantime,
we've set ourselves up for success
by investing in the kind of foundation we'll need
to build whatever we decide best solves the problems at hand.
swift-doc
is built on top of a constellation of projects
that take advantage of modern infrastructure and tooling:
- SwiftSemantics: Parses Swift code into its constituent declarations using SwiftSyntax
- SwiftMarkup: Parses Swift documentation comments into structured entities using CommonMark
- github-wiki-publish-action: Publishes the contents of a directory to your project's wiki
These new technologies have already yielded some promising results.
swift-doc
is built in Swift,
and can be installed on both macOS and Linux as a small, standalone binary.
Because it relies only on a syntactic reading of Swift source code,
without needing code first to be compiled,
swift-doc
is quite fast.
As a baseline,
compare its performance to Jazzy
when generating documentation for SwiftSemantics:
$ cd SwiftSemantics
$ time swift-doc Sources
0.21 real 0.16 user 0.02 sys
$ time jazzy # fresh build
jam out ♪♫ to your fresh new docs in `docs`
67.36 real 98.76 user 8.89 sys
$ time jazzy # with build cache
jam out ♪♫ to your fresh new docs in `docs`
17.70 real 2.17 user 0.88 sys
Of course,
some of that is simply Jazzy doing more,
generating HTML, CSS, and a search index instead of just text.
Compare its generated HTML output
to a GitHub wiki generated with swift-doc
.
What About SwiftDoc.org?
tl;dr: SwiftDoc.org is now updated for Swift 5.1, but we're still working to migrate over a few missing parts (notably, the beloved type inheritance graphs).
SwiftDoc.org, originally "Swifter", was created by Nate Cook (@natecook1000) in September 2014. At the time, Swift tooling was still in its infancy, so Nate actually wrote a parser (from scratch!) to pull symbols and documentation from the Swift standard library. Nate became managing editor of NSHipster in 2015, bringing SwiftDoc with him as an affiliated project. When Mattt took over NSHipster duties for Nate in 2018, he inherited SwiftDoc along with it.
After the hand-off,
we were able to get the site updated for Swift 4.2 without too much trouble.
But when it came time to regenerate the site for Swift 5,
we found ourselves deep in "dependency hell"
(something to do with the regular expression library
that Nate had used for the parser).
After begging and pleading with
the spirits possessing our node_modules
directory to no avail,
we decided to roll up our sleeves and get started on a long-term replacement —
this time, written in Swift.
Thanks for all of your encouragement about the site over the years and your patience throughout this whole process. We're sorry it took so long to get around to getting it updated, but we hope this all will have been worth the wait! 🙇♂️
(Coming soon!)
MIT
Mattt (@mattt)