DocCheck.jl
# Julia utilities for checking documentation
# This file contains a number of functions for checking julia documentation
#
# isdeprecated(v) : test if v is deprecated
# isdocumented(v) : true if v is documented
# undefined_exports(m) : returns a list of undefined exports in module m
# undocumented(m) : returns a list of undocumented exports in module m
# undocumented_by_file(m) : returns a dictionary of undocumented exports,
# with file, function, and line number information
# undocumented_rst(m) : produce a list of undocumented function suitable for
# pasting into github issue #2242
module DocCheck
import Base: argtype_decl, uncompressed_ast
export isdeprecated, isdocumented, undefined_exports, undocumented, undocumented_by_file, undocumented_rst,
gen_undocumented_template
isdeprecated(m::Module, v) = try endswith(functionloc(eval(m, v))[1], "deprecated.jl") catch return false end
isdeprecated(v) = try endswith(functionloc(eval(v))[1], "deprecated.jl") catch return false end
isdocumented(v) = (s=string(v); haskey(FUNCTION_DICT, s) || haskey(MODULE_DICT, s))
modfuncjoin(m::AbstractString, f::AbstractString) = startswith(f, '@') ? "@$m.$(f[2:end])" : "$m.$f"
modfuncjoin(m, f) = modfuncjoin(string(m), string(f))
# return a list of undefined exports in a module
undefined_exports(m::Module) = sort(filter(x->!isdefined(x), names(m)))
undefined_exports() = undefined(Base)
# Check for exported names that aren't documented,
# and return a Dict with (fn::Symbol, fullname::AbstractString) pairs
function undocumented(m::Module)
init_help()
undoc = Dict{Symbol, Array}()
for v in sort(names(m))
if isdefined(m,v) && !isdocumented(v) && !isdeprecated(m,v)
ms = modfuncjoin(m,v)
haskey(undoc, v) ? push!(undoc[v], ms) : (undoc[v] = [ms])
end
end
undoc
end
undocumented() = undocumented(Base)
# Check for exported names that aren't documented, and
# return the file, function names, and line numbers, if available
function undocumented_by_file(m::Module)
init_help()
undocf = Dict{AbstractString, Dict}()
for (f,_) in undocumented(m)
s = string(f)
try
for (file, line) in functionlocs(eval(f))
if startswith(file, JULIA_HOME)
file = replace(file, JULIA_HOME, "\$JULIA_HOME", 1)
end
if !haskey(undocf, file)
undocf[file] = Dict{AbstractString, Vector{Integer}}()
end
if !haskey(undocf[file], s)
undocf[file][s] = [line]
else
push!(undocf[file][s], line)
end
end
catch
if !haskey(undocf, "UNKNOWN_FILE")
undocf["UNKNOWN_FILE"] = Dict{AbstractString, Vector{Integer}}()
end
undocf["UNKNOWN_FILE"][s] = Integer[-1]
end
end
undocf
end
undocumented_by_file() = undocumented_by_file(Base)
# Unlike the above functions, this version parses base/exports.jl,
# because that file groups the functions in a more systematic manner.
# The output can be pasted into https://github.com/JuliaLang/julia/issues/2242
# This also only works with Base functions; the other "undocumented*"
# functions are more general.
# Based on code by @jihao
function _undocumented_rst()
init_help()
depdoc = havecount = total = 0
out = AbstractString["The following exports are not documented:"]
undoc_exports = Set()
exports=[strip(x) for x in split(replace(open(readall, "$JULIA_HOME/../../base/exports.jl"),",",""),"\n")]
for line in exports
if search(line, "deprecated")!=0:-1; continue end
if haskey(MODULE_DICT, line); havecount+=1; total+=1; continue end
if length(line)>1
if line[1]=='#'
if line[2]!= ' ' continue end
else
s = symbol(line) # for submodules: string(:Sort) == "Base.Sort"
if !isdefined(s) continue end
if haskey(FUNCTION_DICT, line) || haskey(MODULE_DICT, line)
m = eval(symbol(getkey(MODULE_DICT, line, "Base")))
isdeprecated(m,s) && continue
havecount+=1; total+=1; continue
end
push!(undoc_exports, line)
if line[1]=='@'; line = line[2:end] end
line=string("- [ ] ", line)
total+=1
end
end
push!(out, line)
end
append!(out, AbstractString["", "Documented and deprecated functions/exports (please update docs)", ""])
deprecated=[strip(x) for x in split(replace(open(readall, "$JULIA_HOME/../../base/deprecated.jl"),",",""),"\n")]
for line in deprecated
if startswith(line, "@deprecated")
fn = split(line, r" +")[2]
if haskey(MODULE_DICT, fn); push!(out, string("- [ ] ", fn)); depdoc += 1 end
elseif startswith(line, "export")
for fn in split(line, r"[ ,]+")[2:end]
if haskey(MODULE_DICT, fn); push!(out, string("- [ ]", fn)); depdoc += 1 end
end
end
end
prepend!(out, AbstractString["$havecount/$total exports have been documented",
"(Additionally, $depdoc deprecated functions are still documentated)",
""])
(join(out, "\n"), undoc_exports)
end
undocumented_rst() = println(_undocumented_rst()[1])
function gen_undocumented_template(outfile = "$JULIA_HOME/../../doc/UNDOCUMENTED.rst")
out = open(outfile, "w")
init_help()
println(out, ".. currentmodule:: Base")
println(out)
exports=[strip(x) for x in split(replace(open(readall, "$JULIA_HOME/../../base/exports.jl"),",",""),"\n")]
for line in exports
if search(line, "deprecated")!=0:-1; continue end
if haskey(MODULE_DICT, line); continue end
if length(line)>1
if line[1]=='#'
if line[2]!= ' ' continue end
println(out)
println(out, line[3:end])
println(out, repeat("-", length(line)-2))
println(out)
continue
else
s = symbol(line) # for submodules: string(:Sort) == "Base.Sort"
if !isdefined(s) continue end
if haskey(FUNCTION_DICT, line) || haskey(MODULE_DICT, line)
continue
end
if line[1]=='@'; line = line[2:end] end
sym = try eval(symbol(line)) catch :() end
if isa(sym, Function)
mt = methods(sym)
if length(mt) == 1 # easy case
m = mt.defs
li = m.func.code
e = uncompressed_ast(li)
argnames = e.args[1]
decls = map(argtype_decl, argnames, {m.sig...})
args = join(decls, ",")
line = line * "($args)"
else
line = line * "(...)"
end
println(out, ".. function:: "*line)
println(out)
println(out, " UNDOCUMENTED")
println(out)
elseif isa(sym, Module)
println(out, ".. module:: "*line)
println(out)
println(out, " UNDOCUMENTED (may not appear in helpdb.jl)")
println(out)
end
end
end
end
close(out)
nothing
end
end