CLI option to choose protection level when using the swift-docc-plugin

I didn't want to derail the earlier Swift-DocC plugin thread, but I would like to propose an additional CLI option for that plugin:

--access level with options to accept the following values:
public, internal, private, fileprivate, and open. (these are the access levels defined in Swift Package Manager)

Background:

The plugin currently invokes the PackageManager to generate the symbol graph using one of these access levels, and two other Boolean options: --includeSPI and --includeSynthesized.

My primary interest is in exposing private level symbols, but I also don't actively use the SPI flag/wrapper setup that's used in internal Apple libraries.

I've opened a bug (SR-15765) to track this, but also put this forward here to get input on what options would be most useful for the use case of creating documentation for an App (or generally, an executable target), or internal documentation for libraries (for anyone needing to support or extend)

2 Likes

Could you elaborate a bit on why all five access levels of these would be included?

Would --access internal only produce docs for the internal API, or would it include public (and open) definitions too? If the latter, then I see no reason for the fileprivate or open options here. I think public, internal, and private seem sufficient for all reasonable documentation use cases.

4 Likes

I am strongly +1 this change but I think it should be proposed as an overall "internal documentation" feature rather than this one change. Alternatively, so that we can make incremental forward progress, I would support exposing this as an experimental flag. I think there are challenges that need to be solved in UI and authoring before this is possible.

For example, symbol paths become more complicated (for authoring and for page urls) because you can now introduce conflicts. Suppose you define the following function, and you copy-paste it into two different files (or you just happen to create a function with the same name):

fileprivate func printInt(_ x: Int) { print(x) }

Following the existing DocC URL rules, the url for this type would be MyModule/printInt however because the function was defined in two files, you'd have two pages with the same url. There are straightforward solutions to these problems—for example, you could add the file name into the path somewhere, or use the symbol identifier hash suffix—but they need to be enumerated and addressed.

Another issue that needs resolving is around authoring documentation that may be built at different access levels. For example, suppose I want to build documentation including in the internal access level for my team, and I also want to build documentation at the public access level for framework clients. How do we handle links in prose and in groups that only resolve under the internal access level? Do we need special handling for diagnostics, or a way to omit them with some sort of @AccessControl(internal) { ... prose ... } directive? What about sentences, paragraphs, assets, or even entire files that should only appear in the internal docs?

3 Likes

I was trying to be complete, but I'm pretty sure (meaning I haven't tried the symbol graph generation process explicitly to verify this hypothesis) that that any level includes everything above it.

Really, I only wanted to enable private, as that was my own primary interest - so I'd be happy clipping this down to internal and private, with the default being public.

Excellent points Jack - I hadn't considered the various extensions that would be needed for full internal documentation, and while I'm reasonably confident I can knock out my proposed update since it's so limited, I'm not sure that I know have sufficient knowledge or background to know how to handle the topics you're bringing up.

The mixed documentation content - some "internal", some "public" wasn't anything I'd considered, but has some really interesting applications. I'm also somewhat terrified of what it might be like to manage and write for content that "if internal" could suddenly have a whole new structure based on that flag alone.

If the semantics of --level private are to also include greater access levels (internal, public)—which, I think, they should—then I'd argue the UI should convey the access level of APIs in some form.

When I'm looking up documentation for the source project I'm working on, I'd like to be able to tell if the API I'm currently browsing is public, internal, or private, so that I know whether it's accessible in my current context. Same goes for the lists of APIs in the API's Topics section. A way to filter by access levels while browsing the docs could be interesting as well.

2 Likes

I like the idea of being able to write docs for different audiences for the same API. An @If directive could help generalise conditionally including prose:

@If(accessControl: internal) {
  …prose…
}
1 Like

Yes, for a mixed public/internal/private documentation it would be necessary to see which access level every documented declaration has.

I'm getting off-topic, but a UI for filtering by supported platform (i.e. by my required deployment target) would be equally useful in my opinion. I think both filters could behave similarly.

1 Like

An option for ABI-public declarations (@inlinable and @usableFromInline) might be useful.

It definitely would!

Interestingly (by which I mean enabling this kind of capability is more complicated than I might have expected), the details about @inlineable and @usableFromInline aren't exposed via the Symbol Graph for the symbols. I'm only just getting to know what all is IN the symbol graph - but I had a local library that had a static function I'd made @inlineable I could easily double-check - and nope, no detail in the bits emitted from the compiler.

All of which means in order to support anything like this: we'd need to consider an update to what the compiler generates to somewhere/somehow include that a function has these relevant parameters, and that would need to be reflected in swift-docc-symbolkit, and then we could leverage it from within DocC and the renderer.