Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "SciMLTesting"
uuid = "09d9d899-5365-40a9-917a-5f67fddea283"
authors = ["SciML"]
version = "2.6.2"
version = "2.6.3"

[deps]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
Expand Down
30 changes: 28 additions & 2 deletions src/SciMLTesting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,23 @@ else
end
end

# A module name that is public only because the module itself was reexported. Julia
# exports a module's own name, so `using Dep` inside a facade puts `:Dep` into the
# facade's public API; the docstring obligation for it belongs to the package that
# defines the module, not to every reexporter. Deliberately restricted to `Module`
# values: a reexported function, type or constant still owes a docstring, since that
# one resolves through the binding to the defining package's docs.
function _is_external_module_reexport(pkg::Module, name::Symbol)
isdefined(pkg, name) || return false
value = try
getfield(pkg, name)
catch
return false
end
value isa Module || return false
return !_is_within_module(value, pkg)
end

# Only names owned by this package's module hierarchy require a local rendered API
# entry. External public reexports are audited separately by `public_reexports`.
function _requires_local_rendering(pkg::Module, name::Symbol)
Expand Down Expand Up @@ -1141,7 +1158,10 @@ Two checks, each its own nested `@testset`:
* **docstrings** (`docstrings = true`, on by default): every public API name has a
docstring. A re-exported name documented in its defining package counts as
documented (the check follows the binding, not a local docstring), so a repo is
not forced to redocument names it re-exports from a dependency.
not forced to redocument names it re-exports from a dependency. A re-exported
*module* name — public only because `using Dep` exports `Dep` itself — is exempt
entirely, mirroring the rendered check; the obligation to document `Dep` belongs
to `Dep`. Re-exported functions, types and constants are not exempt.
* **rendered** (`rendered = true`, on by default): every public API name appears inside a
```` ```@docs ```` block somewhere under `docs_src`, so it is rendered in the
manual. Re-exported modules inherit the defining package's rendered module
Expand Down Expand Up @@ -1199,7 +1219,13 @@ function run_api_docs(
@testset "$testset" begin
if docstrings
skip = Set{Symbol}(Symbol.(ignore))
undocumented = sort!(filter(n -> !(n in skip) && !_has_docstring(pkg, n), api))
undocumented = sort!(
filter(
n -> !(n in skip) && !_is_external_module_reexport(pkg, n) &&
!_has_docstring(pkg, n),
api,
)
)
@testset "public API has docstrings" begin
docstrings_broken ? (@test_broken isempty(undocumented)) :
(@test isempty(undocumented))
Expand Down
51 changes: 51 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,15 @@ module LocalModuleFixture
export LocalSubmodule
end

# Reexports an undocumented external module and an undocumented external function
# next to an undocumented local name, so the docstrings check can be shown to exempt
# the module and only the module.
module UndocumentedModuleReexportFixture
import ..ReexportOwnerFixture: OwnedModule, owned_function
export OwnedModule, owned_function, local_undocumented
local_undocumented() = nothing
end

# A minimal AbstractTestSet that just collects every recorded result (including
# nested testsets) and NEVER throws on finish. Wrapping a run_qa call in one lets a
# test inspect the Broken/Pass/Fail/Error counts a broken-marker produced without
Expand Down Expand Up @@ -1145,6 +1154,48 @@ end
@test c[:fail] == 0 && c[:error] == 0
@test c[:pass] == 1

# A reexported external module is public only because Julia exports a module's
# own name; documenting it belongs to the package that defines it.
c = counts_of() do
run_api_docs(
UndocumentedModuleReexportFixture;
rendered = false,
ignore = (:owned_function, :local_undocumented),
)
end
@test c[:fail] == 0 && c[:error] == 0

# The exemption covers modules only: a reexported function is still reported.
c = counts_of() do
run_api_docs(
UndocumentedModuleReexportFixture;
rendered = false,
ignore = (:local_undocumented,),
)
end
@test c[:fail] == 1

@test SciMLTesting._is_external_module_reexport(
UndocumentedModuleReexportFixture, :OwnedModule
)
@test !SciMLTesting._is_external_module_reexport(
UndocumentedModuleReexportFixture, :owned_function
)
@test !SciMLTesting._is_external_module_reexport(
LocalModuleFixture, :LocalSubmodule
)

# A package's own undocumented submodule is still the package's to document.
# Only on 1.11+: the 1.10 `@doc` fallback renders an undocumented module as
# "No docstring or readme file found", which `_has_docstring`'s substring never
# matched, so no module is ever reported as undocumented there.
@static if VERSION >= v"1.11"
c = counts_of() do
run_api_docs(LocalModuleFixture; rendered = false)
end
@test c[:fail] == 1
end

# docstrings_broken records Broken while names remain undocumented (migration).
c = counts_of() do
run_api_docs(ApiFixture; rendered = false, docstrings_broken = true)
Expand Down
Loading