/swift-doc

Generates documentation for Swift projects

Primary LanguageSwiftMIT LicenseMIT

swift-doc

CI

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.

Command-Line Utility

swift-doc can be used from the command-line on macOS and Linux.

Installation

Homebrew

Run the following command to install using Homebrew:

$ brew install swiftdocorg/formulae/swift-doc

Manually

Run the following commands to build and install manually:

$ git clone https://github.com/SwiftDocOrg/swift-doc
$ cd swift-doc
$ make install

Usage

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.

GitHub Action

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

  • inputs: One or more paths to Swift files in your workspace. (Default: "./Sources")
  • output: The path for generated output. (Default: "./.build/documentation")

Example Workflow

# .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 }}

Experimental Command-Line Tools

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 the swift-doc target in Makefile.


swift-dcov

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

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

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:

Excerpt of swift-doc-api Diagram for Alamofire

Motivation

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:

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! 🙇‍♂️

Project Roadmap

(Coming soon!)

License

MIT

Contact

Mattt (@mattt)