Skip to content

Add a way for users to tell Documenter about unexported names that are part of their package's public API #1507

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/src/man/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,46 @@ Order = [:type]
```
````

You may have some names in your package that are unexported but are still part
of your package's public API. You can tell Documenter about these names by
defining the `NONEXPORTED_PUBLIC_NAMES` vector in your package. The names in
`NONEXPORTED_PUBLIC_NAMES` will be treated as public by Documenter. Example
usage:

```julia
module MyPackage

export a

const NONEXPORTED_PUBLIC_NAMES = Symbol[:b, :c]

"""
`a` is exported, and it is part of the public API.
"""
function a end

"""
`b` is not exported, but it is part of the public API.
"""
function b end

"""
`c` is not exported, but it is part of the public API.
"""
function c end

"""
`d` is not exported, and it is private (internal).
"""
function d end
Comment on lines +165 to +183
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it might be worth turning these into 1-line docstrings to save a few lines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! What is the syntax for a one line docstring?


end # module
```

In the above example, `a`, `b`, and `c` will be considered by Documenter to be
public (even though `b` and `c` are not exported). `d` will be considered to be
private.

!!! note

When more complex sorting is needed then use `@docs` to define it
Expand Down
24 changes: 19 additions & 5 deletions src/Expanders.jl
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,20 @@ end

const AUTODOCS_DEFAULT_ORDER = [:module, :constant, :type, :function, :macro]

@inline function _name_is_public_but_nonexported(m::Module, name::Symbol)
isdefined(m, :NONEXPORTED_PUBLIC_NAMES) || return false
nonexported_public_names = getfield(m, :NONEXPORTED_PUBLIC_NAMES)
if !(nonexported_public_names isa AbstractVector{Symbol})
@warn("$(m).NONEXPORTED_PUBLIC_NAMES is not a vector of symbols, so Documenter will ignore it", maxlog = 1)
return false
end
return name in nonexported_public_names
end

@inline function name_is_public(m::Module, name::Symbol)
return Base.isexported(m, name) || _name_is_public_but_nonexported(m, name)
end

function Selectors.runner(::Type{AutoDocsBlocks}, x, page, doc)
curmod = get(page.globals.meta, :CurrentModule, Main)
fields = Dict{Symbol, Any}()
Expand Down Expand Up @@ -402,8 +416,8 @@ function Selectors.runner(::Type{AutoDocsBlocks}, x, page, doc)
for mod in modules
for (binding, multidoc) in Documenter.DocSystem.getmeta(mod)
# Which bindings should be included?
isexported = Base.isexported(mod, binding.var)
included = (isexported && public) || (!isexported && private)
ispublic = name_is_public(mod, binding.var)
included = (ispublic && public) || (!ispublic && private)
# What category does the binding belong to?
category = Documenter.DocSystem.category(binding)
if category in order && included
Expand All @@ -416,11 +430,11 @@ function Selectors.runner(::Type{AutoDocsBlocks}, x, page, doc)
path = normpath(docstr.data[:path])
object = Utilities.Object(binding, typesig)
if isempty(pages)
push!(results, (mod, path, category, object, isexported, docstr))
push!(results, (mod, path, category, object, ispublic, docstr))
else
for p in pages
if endswith(path, p)
push!(results, (mod, p, category, object, isexported, docstr))
push!(results, (mod, p, category, object, ispublic, docstr))
break
end
end
Expand All @@ -446,7 +460,7 @@ function Selectors.runner(::Type{AutoDocsBlocks}, x, page, doc)

# Finalise docstrings.
nodes = DocsNode[]
for (mod, path, category, object, isexported, docstr) in results
for (mod, path, category, object, ispublic, docstr) in results
if haskey(doc.internal.objects, object)
push!(doc.internal.errors, :autodocs_block)
@warn("""
Expand Down
47 changes: 47 additions & 0 deletions test/public_names.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Documenter
using Test

module TestingNonExportedPublicNames

export f

const NONEXPORTED_PUBLIC_NAMES = Symbol[:g]

"""
f is exported, and it is part of the public API
"""
function f end

"""
g is not exported, but it is part of the public API
"""
function g end

"""
h is not exported, and it is private (internal)
"""
function h end

end # module

@test Documenter.Expanders.name_is_public(TestingNonExportedPublicNames, :f)
@test Documenter.Expanders.name_is_public(TestingNonExportedPublicNames, :g)
@test !Documenter.Expanders.name_is_public(TestingNonExportedPublicNames, :h)

@test Base.isexported(TestingNonExportedPublicNames, :f)
@test !Base.isexported(TestingNonExportedPublicNames, :g)
@test !Base.isexported(TestingNonExportedPublicNames, :h)

@test !Documenter.Expanders._name_is_public_but_nonexported(TestingNonExportedPublicNames, :f)
@test Documenter.Expanders._name_is_public_but_nonexported(TestingNonExportedPublicNames, :g)
@test !Documenter.Expanders._name_is_public_but_nonexported(TestingNonExportedPublicNames, :h)

module BadNONEXPORTED_PUBLIC_NAMES

const NONEXPORTED_PUBLIC_NAMES = "foo"

function f end

end # module

@test @test_logs match_mode=:any (:warn, r"BadNONEXPORTED_PUBLIC_NAMES\.NONEXPORTED_PUBLIC_NAMES is not a vector of symbols, so Documenter will ignore it") !Documenter.Expanders.name_is_public(BadNONEXPORTED_PUBLIC_NAMES, :f)
2 changes: 2 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,5 @@ include("TestUtilities.jl"); using .TestUtilities
@info "doctest() Documenter's manual"
@quietly include("manual.jl")
end

include("public_names.jl")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we include this in the main testset?