diff --git a/src/docchecks.jl b/src/docchecks.jl index 0f64573e27..f837862b61 100644 --- a/src/docchecks.jl +++ b/src/docchecks.jl @@ -40,8 +40,9 @@ end function missingbindings(doc::Document) @debug "checking for missing docstrings." - bindings = allbindings(doc.user.checkdocs, doc.blueprint.modules) - for object in keys(doc.internal.objects) + bindings = allbindings(doc.user.checkdocs, doc.blueprint.modules, doc.user.checkdocs_ignored_modules) + # Sort keys to ensure deterministic iteration order + for object in sort!(collect(keys(doc.internal.objects)); by = o -> (string(o.binding), string(o.signature))) if !is_canonical(object) continue end @@ -69,21 +70,37 @@ function missingbindings(doc::Document) return bindings end -function allbindings(checkdocs::Symbol, mods) +function allbindings(checkdocs::Symbol, mods, ignored_modules = Module[]) out = Dict{Binding, Set{Type}}() for m in mods - allbindings(checkdocs, m, out) + allbindings(checkdocs, m, out, ignored_modules) end return out end -function allbindings(checkdocs::Symbol, mod::Module, out = Dict{Binding, Set{Type}}()) - for (binding, doc) in meta(mod) +function allbindings(checkdocs::Symbol, mod::Module, out = Dict{Binding, Set{Type}}(), ignored_modules = Module[]) + # Sort the metadata entries to ensure deterministic iteration order + metadata = collect(meta(mod)) + sort!(metadata; by = entry -> string(entry[1])) + for (binding, doc) in metadata # The keys of the docs meta dictionary should always be Docs.Binding objects in # practice. However, the key type is Any, so it is theoretically possible that # some non-binding metadata gets added to the dict. So on the off-chance that has # happened, we simply ignore those entries. isa(binding, Docs.Binding) || continue + # Skip bindings from ignored modules or their submodules + any(ignored_modules) do ignored + # Check if binding.mod is the ignored module or a submodule of it + current = binding.mod + while current != Main && current != Base + current == ignored && return true + parent = parentmodule(current) + # Guard against infinite loops if parentmodule returns itself + parent == current && break + current = parent + end + return false + end && continue # We only consider a name exported only if it actually exists in the module, either # by virtue of being defined there, or if it has been brought into the scope with # import/using.