Introducing Documentation Generation for Swift Packages

I just completed and released a new feature for Workspace. It now has an in‐house documenter designed specifically for packages (it no longer uses Jazzy).

There are still bugs to fix, and I imagine the soon release of Swift 4.2 will throw a wrench in things, but I thought I’d put it out there anyway for anyone else who might already find it useful as‐is, or who wants to pitch in to help find and fix bugs.

Using it is as simple as running this in the package root:

$ workspace document

(Though you would have to configure a localization once first.)

Installation instructions are here, though as a Swift package, you can also use it any way you would another package tool (e.g. $ swift run workspace ...)

In contrast to Jazzy, this documenter is pure Swift and built on the Swift Package Manager and SwiftSyntax with no reliance on Xcode. This enables it to run on Linux and to understand multi‐target packages and #if conditions. Since it does not need to build the package, it is fully aware of macOS‐only or Linux‐only symbols even when run on the opposite system.

It has also been designed from the ground up with localized documentation in mind, though that aspect is not complete yet.

However, to give Jazzy the respect it is due—I have used it for years and contributed several bug fixes—, there are some things Jazzy does which Workspace will never do. Jazzy can document Objective C, and it natively deals with Xcode projects. If either of those are important to you, Jazzy will always be your tool of choice.

14 Likes

The latest release is now stable on and for Swift 4.2.

2 Likes

Hi, I am trying to use this for my project kelvin13/png, and I’m running into a lot of issues.

The proofreader is doing a lot of weird stuff such as

Line 2481
“>=” is obsolete. Use the greater‐than‐or‐equal sign (≥). (unicode)
                precondition(chunkSize >= 1, "chunk size must be positive")
                precondition(chunkSize ≥ 1, "chunk size must be positive")

what??

Line 67
U+0022 is obsolete. Use quotation marks (“, ”) or double prime (′′). (unicode)
  ///     let names = ["Jacqueline", "Ian", "Amy", "Juan", "Soroush", "Tiffany"]

It seems to think this colon in a doccomment is a swift colon

Line 4600
Colons should be preceded by spaces when denoting protocols or superclasses. (colonSpacing)
        - Returns: A chunk iterator, if the PNG magic signature was read from the
        - Returns : A chunk iterator, if the PNG magic signature was read from the

I cannot seem to turn off the colon rule using a custom .swiftlint.yml file

disabled_rules:
  - colon

it also just hangs on this step

$ swiftlint lint --strict --reporter emoji

when I run

png$ ~/.SDG/Tools/Workspace/workspace validate

Other than that, this seems like a really interesting tool and I would really like some help to get it working for my project

First of all, its great to hear from you!

I will answer each issue separately.

  1. You didn’t actually ask this, but if you just want to use the documentation generator this thread is about and not the other utilities (which is where all the errors are occurring), you can just run $ workspace document or $ workspace validate documentation instead of the general workspace validate (which is what the provided script does).

The unicode rule discourages ASCII workarounds in favour of real Unicode. is a valid operator in Swift. Workspace tries to recognize operator aliases like this one and not complain when the operator is just used in order to create a better alias for it. (The linked function does not trigger a violation.)

You can also configure Workspace to disable the unicode rule entirely:

let configuration = WorkspaceConfiguration()
configuration.proofreading.rules.remove(.unicode)

This is also the unicode rule at work. It allows syntactic use of the ASCII quote in Swift source, but prevents them in strings, documentation and comments. Workspace does not check comments to see if they might be commented source. I’m not sure how it would be possible to reliably tell the difference.

This one is a bug. I cannot reproduce it, so more context will be necessary to figure out what is wrong. Please report it over of GitHub. In the meantime you can disable it the same as described above. (It is a native Workspace colon rule, not SwiftLint’s colon rule. That is why the .swiftlint.yml is having no effect.)

It may just be fetching and building SwiftLint if Workspace cannot locate it already on the system (such as within Xcode’s sandbox. Though it gets cached, it can take a while the first time (maybe 5 mins), and depending on the number of cores and the environment, Workspace might not be getting the output until swift run completely finishes.

Another alternative is that SwiftLint itself is hanging... which I can do little about.

Is kelvin13/png the project that encountered this? If so, I can troubleshoot it in more depth myself.

It seems to be asking for a localization which I don’t know how to provide

png$ ~/tools/sources/Workspace/.build/release/workspace document

Generating documentation... (§1)

Loading “Workspace.swift”...
There are no localizations specified. (documentation.localizations)

✗ Failed to generate documentation. (See Ctrl + F “§1”)

“PNG” fails validation.

Are you sure this is a good default setting? Very very few mature Swift projects allow let alone encourage unicode operators.

Again, I don’t know how much value this provides. Straight quotes in body copy indicate “computer strings” as opposed to English words or phrases, which take curly quotes.

Some of these substitutions also seem just wrong:

Line 73
U+2026 may be lost in normalization; use “...” instead. (compatibilityCharacters)
### *...safe*
### *...safe*

... and should never be considered interchangeable, this comes straight from Butterick’s Practical Typography. Very modern fonts are capable of spacing the three-periods version correctly via a ligature but few text rendering engines support this.

Right now this is failing with

$ swiftlint lint --strict --reporter emoji
Fatal error: POSIX command failed with error: 13: file Foundation/Process.swift, line 543
Illegal instruction (core dumped)

Yes

I got the documentation running but I’m running into a few more issues:

  • Symbols which have @_specialize-ations seem to get omitted from the documentation. Symbols which have @inlinable seem to lose their documentation.

  • The parsed declatations seem completely messed up:

static func convert(rgba: [, size: (, to code: Code, chromaKey: RGBA<UInt16>? = default, path outputPath: String, level: Int = default) throws
  • This declaration for the PNG.rgba(path:of:) method is missing the generic parameters, making for some odd spellings
static func rgba(path: String, of type: Type) throws -> (
  • The index.html in the root of the docs/ folder redirects me to png/docs/🇺🇸EN/index.html which 404s.

  • It seems to not be finding documentation for some documented symbols. For example, it claims

A symbol has no description:
PNG.Properties.Format

But it does. It also seems to think it only has one case.

@taylorswift

I will take a closer look at all of these in direct connection with your project in the next few days. You have just bumped them all to the top of my to‐do list.

I have created a central GitHub issue for this in general, and will spin off the particulars as I look into them. You can watch that issue to track my progress.

Your feedback is very useful. Thank you.

1 Like

@taylorswift,

I’ve finished reading over your comments more closely and spinning of separate issues for each aspect on GitHub. (Here.) Some I was already aware of and already planned a fix, but just hadn’t had time to implement yet (those tagged “Designed”). Others are new to me and will take longer to narrow down where the problem really is.

The next thing I will do is look at the set‐up for your particular project. I will do that before closing the catch‐all issue as a duplicate.

The only thing I will still comment on here in the forums is what you say about the ellipse:

I wholeheartedly agree with you, but our hands are tied by Unicode.

The violation is coming from the compatibilityCharacters rule, not the unicode one. This rule is not about the right way to encode text, but about the reliable way.

I wish the ellipse were not a compatibility character. The Unicode Consortium designed it wrong. Unicode defines a “compatibility character” as a character which “would not have been encoded except for compatibility and round‐trip convertibility with other standards”. They are marked in the code charts with the “≈” symbol. Unicode allows compliant programs to normalize them away by replacement with the way they would have been encoded had they been initially designed directly for Unicode. Use of any such characters is what the rule is designed to flag.

Why Unicode decided an ellipse should be encoded as three full stops is a mystery to me. It seems to go directly against their general rule of representing semantics instead of glyphs. The two characters don’t mean anything close to the same thing. The ellipse should have been a character in its own right. But unfortunately, in Unicode’s eyes, the only reason the separate, compatibility ellipse (U+2026) is available at all is that it was inherited from Windows‐1252 (hex byte 85).

Because any Unicode‐compliant program is free to normalize it to three full stops at any time, it is unsafe to rely on any distinction between that character and three full stops, either for meaning or appearance. Those of us who want our ellipses to display correctly—hopefully all of us—have to select a font that does it right, because workarounds involving U+2026 aren’t reliable. For the same reason, we cannot rely on the presence of U+2026 for any distinction when parsing strings for syntax or semantics, whether in a human or computer language. Anyone using U+2026 needs to be aware that it might vanish, which is why the rule flags it with a warning:

U+2026 may be lost in normalization; use “...” instead. (compatibilityCharacters)

2 Likes

The ellipsis is so common in prose and documentation, and this ligature is supported in so few fonts (I believe this is because many font designers actually oppose implementing this at the font level), that there should at least be a way to suppress this warning. I would rather my s be typeset correctly, and risk them getting converted to ...s by other applications, than ruin them manually.

Of course. And that is exactly what I did for you in the pull request I just submitted to your project.

1 Like

can I whitelist just the U+2026 character?

Sorry, right now you can only whitelist the entire rule.

The release of Workspace 0.15.0 deals with all the issues mentioned above. Special thanks to @taylorswift, whose differing coding style was an immense help finding testing gaps.

(I’m sure there are more waiting to be found though...)

1 Like

How do you upgrade to 0.15.0? I tried replacing all the instances of 0.14.2 with 0.15.0 in the refresh scripts, but whenever I run it, it seems to revert itself to 0.14.2.

There are two versions involved:

  1. The version permanently installed on the system and independent of any project. This will be used as an override whenever you manually use any command from a shell.
  2. The version any particular project declares it prefers. This will be fetched for use if you (or someone who has cloned your project, or CI) execute one of the scripts in your project root. It allows you to have several different projects all using different versions of Workspace.

$ workspace refresh scripts will set the project’s preferred version (2) to whatever version you are executing. If you executed it from a terminal exactly as written, you would be setting them to match the system install (1). It basically just replaces the version numbers in the scripts like you did manually.

The intended process is to update the system install, then use it to $ workspace refresh scripts on each of your projects as they become ready to switch.

As to what you observed, I suspect you still have 0.14.2 installed on your system (1), and after your find‐and‐replace operation the scripts were at 0.15.0. At that point the scripts worked fine and used the new version. However, as soon as you ran a relevant manual command however (refresh or refresh scripts) that set your project back to match your system install of 0.14.2.

Why doesn’t it work when I just clone and build it like a normal SPM project (i prefer to manually manage the tooling updates)?

~/tools/sources$ git clone git@github.com:SDGGiesbrecht/Workspace.git
~/tools/sources$ cd Workspace/
~/tools/sources/Workspace$ swift build -c release
png$ ls
benchmarks  COPYING  doc  doc.json  docs  max.png  maxpng.svg  Package.swift  README.md  sources  tests  typemanifest.txt  Workspace.swift

png$ ~/tools/sources/Workspace/.build/release/workspace refresh

Refreshing “PNG”...

Loading “Workspace.swift”...

Refreshing scripts...

Writing to “Refresh (macOS).command”...
Writing to “Refresh (Linux).sh”...
Writing to “Validate (macOS).command”...
Writing to “Validate (Linux).sh”...

Refreshing resources...


Refreshing examples...


Refreshing inherited documentation...

$ swift package resolve

Normalizing files...


Workspace encountered unsupported file types:

LICENSE (tests/unit/PngSuite.LICENSE)
README (tests/unit/PngSuite.README)
rgba (tests/unit/rgba/PngSuite.png.rgba)

All such files were skipped.
If these are standard file types, please report them at
https://github.com/SDGGiesbrecht/Workspace/issues
To silence this warning for non‐standard file types, configure “repository.ignoredFileTypes”.


“PNG” is refreshed and ready.

png$ ~/tools/sources/Workspace/.build/release/workspace validate

Refreshing “PNG”...

Loading “Workspace.swift”...

Refreshing scripts...


Refreshing resources...


Refreshing examples...


Refreshing inherited documentation...

$ swift package resolve

Normalizing files...


Validating “PNG”...


Normalizing files...


Proofreading source code... (§1)

.gitignore
.swiftlint.yml
.travis.yml
The operation could not be completed

You can certainly use Workspace that way instead; the error message is unrelated.

The error is because SwiftSyntax cannot handle the #error() in your code, as I explained in our conversation on GitHub. Have you seen that post yet?

1 Like

so basically it’s upstream bugs in swiftsyntax that are the problem?

  • #error is an integral part of the language, and fatalError (a runtime check) isn’t the same thing. In a Package.swift it’s ends up being the same thing, so the workaround works, but i would call this a bug in swiftsyntax. have you filed it with Apple yet?

  • @_specialize is not the same thing as @inlineable, because with specialize, the generic type switch takes place inside the entry point, so you still have resilience (as i understand it). It’s a small amount of overhead, but i think it is worth it if you want to avoid the generic abstraction cost without giving up on library resilience. If swiftsyntax just treats it as garbage text, is it possible to just skip over it and treat the attribute as if it’s not there?

Exactly.

No need. This one has already been fixed, just not released.

That’s what it does. But that means the documentation which comes before it ends up belonging to unparsed garbage instead of belonging to the symbol found by the scan. The code that fishes for the stray documentation has already been improved somewhat since the release, but dissecting garbage interference is clumsy and unpredictable. It does not cause outright failure, so you can just put up with missing documentation comments for now if that is what you would rather do.

You are correct that they are not the same, and also correct in your understanding of each. I mentioned it as a viable workaround in your specific case because ABI resilience is irrelevant to your package. Your package does not vend any dynamic library; its product will always be statically linked. Such use was one of the major driving forces behind SE‐0193 in the first place.