- Publicity: Public Only All
doc-check-procs.tcl
Check all the proc documentation
- Location:
- packages/acs-tcl/tcl/test/doc-check-procs.tcl
- Created:
- 2005-02-28
- Authors:
- Jeff Davis
- Héctor Romojaro
Procedures in this file
Detailed information
[ hide source ] | [ make this the default ]Content File Source
ad_library { Check all the proc documentation @author Jeff Davis @author Héctor Romojaro <hector.romojaro@gmail.com> @creation-date 2005-02-28 } aa_register_case -cats {smoke production_safe} -procs { aa_log_result } documentation__check_proc_doc { checks if documentation exists for public procs. @author Jeff Davis davis@xarg.net } { set count 0 set good 0 # # Certain procs are defined outside the OpenACS installation # source tree, e.g. in nsf. If they fail the test, the regular # OpenACS administrator cannot do much about it, so we only # generate a warning for them. # set ignored_namespaces { nx nsshell } set excluded_proc_index { { Object ::ns_cache} { Object ::xo::lti::LTI::per-object-slot} } foreach p [lsort -dictionary [nsv_array names api_proc_doc]] { set pa [nsv_get api_proc_doc $p] if { [dict exists $pa protection] && "public" in [dict get $pa protection] && !([dict get $pa deprecated_p] || [dict get $pa warn_p]) && ![string match *::slot* $p] && ![string match "ns_*" $p] && $p ni $excluded_proc_index } { # # For nx objects and classes, we check, if we find the # place where it was defined (script_name). If thiswe # cannot determine the location this indicates that it # might not be defined by OpenACS, or it might hint a bug. # set obj [::xo::api object_from_proc_index $p] if {$obj ne "" && [nsf::is object,type=::nx::Object $obj]} { set obj [namespace which $obj] set skip [::acs::per_request_cache eval -key script-name-$obj { set script_name [::xo::api script_name -obj $obj {}] if {$script_name eq ""} { aa_log "Cannot determine script name for object '$obj' (proc_index: $p)" set result 1 } else { set result 0 } }] #ns_log notice "SKIP '$skip' for <$obj> // $p" if {$skip} { continue } } incr count if { [string is space [join [dict get $pa main]]] && (![dict exists $pa return] || [string is space [join [dict get $pa return]]]) && (![dict exists $pa param] || [string is space [join [dict get $pa param]]]) && (![dict exists $pa see] || [string is space [join [dict get $pa see]]]) && (![dict exists $pa author] || [string is space [join [dict get $pa author]]]) } { if {[regexp "^(\\s+Class ::)?([join $ignored_namespaces |])::.*\$" $p m]} { set test_result warning } else { set test_result fail } aa_log_result $test_result "No documentation for public proc <a href='/api-doc/proc-view?proc=[ns_urlencode $p]'>$p</a>" } else { incr good } } } aa_log "Found $good public procs with proper documentation (out of $count checked)" if {[::acs::icanuse "ns_parsehtml"]} { set nrTags 0 set nrNotAllowedTags 0 set allowedTags { h3 /h3 h4 /h4 p /p a /a blockquote /blockquote dd /dd dt /dt dl /dl ul /ul ol /ol li /li table /table td /td th /th tr /tr pre /pre code /code tt /tt strong /strong b /b i /i em /em span /span br } foreach p [lsort -dictionary [nsv_array names api_proc_doc]] { set dict [nsv_get api_proc_doc $p] if {[dict exists $dict main]} { set text [dict get $dict main] foreach chunk [::ns_parsehtml -- $text] { lassign $chunk what chunk content if {$what eq "tag"} { incr nrTags set tag [lindex $content 0] if {$tag ni $allowedTags} { aa_error "[api_proc_link $p]: tag '$tag' not allowed '[ns_quotehtml <$content>]'" incr nrNotAllowedTags } } } } } aa_log "Found $nrTags tags in documentation, $nrNotAllowedTags not allowed" } } aa_register_case -cats {smoke production_safe} -procs { aa_log_result } naming__proc_naming { Check if names of Tcl procs follow the naming conventions https://openacs.org/xowiki/Naming } { set count 0 set good 0 set allowedChars {^[a-zA-Z0-9_]+$} set allowedToplevel {^(_|(ad|acs|aa|adp|api|apm|chat|db|doc|ds|dt|cr|export|fs|general_comments|lc|news|ns|package|pkg_info|relation|rp|rss|sec|server_cluster|content_search|util|xml)_.+|callback|exec)$} set serverModuleProcs {^(h264open|h264length|h264read|h264eof|h264close|dom|bin|zip|transform|md5|base64|berkdb)$} set xmlRPC {^system\.(add|listMethods|multicall|methodHelp)$} set functionalOps {^f::(-|/)$} set internalUse {^(_.+|AcsSc[.].+|callback::.+|install::.+|.*[-](lob|text|gridfs|file))$} set prescribed {^((after|before|notifications)-([a-zA-Z0-9_]+))$} set nameWarning {public error private warning} foreach p [lsort -dictionary [nsv_array names api_proc_doc]] { if {[string match "* *" $p]} continue set info [nsv_get api_proc_doc $p] if {![dict exists $info script]} { aa_log "$p has no script (probably a referenced C-level cmd or a proc (no ad_proc)" } elseif {[dict get $info script] eq ""} { continue } incr count set tail [namespace tail $p] set qualifiers [regsub -all -- "::" [namespace qualifiers $p] "__"] if {[regexp $internalUse $p] || [regexp $serverModuleProcs $p] || [regexp $functionalOps $p] || [regexp $xmlRPC $p] } { continue } set protection [expr {[dict exists $info protection] && "public" in [dict get $info protection] ? "public" : "private"}] if {![regexp $allowedToplevel $p] && ![string match *::* $p]} { if {[dict exists $info deprecated_p] && [dict get $info deprecated_p]} { aa_log_result warning "deprecated proc '$p' ($protection) is not in a namespace" } else { aa_log_result fail "proc '$p' ($protection) is not in a namespace: $info" } } elseif { (![regexp $allowedChars $tail] || $qualifiers ne "" && ![regexp $allowedChars $qualifiers] ) && ![regexp $prescribed $tail] } { aa_log_result [dict get $nameWarning $protection] \ "proc '$p' ($protection): name/namespace contains invalid characters" } else { incr good } } aa_log "Found $good good of $count checked" } aa_register_case -cats {smoke production_safe} -error_level warning -procs { aa_log_result api_proc_link } documentation__check_deprecated_see { checks if deprecated procs have an @see clause @author Jeff Davis davis@xarg.net } { set count 0 set good 0 foreach p [lsort -dictionary [nsv_array names api_proc_doc]] { set pa [nsv_get api_proc_doc $p] if { ([dict exists $pa deprecated_p] && [dict get $pa deprecated_p]) || ([dict exists $pa warn_p] && [dict get $pa warn_p]) } { incr count if { ![dict exists $pa see] || [string is space [dict get $pa see]] } { aa_silence_log_entries -severities warning { aa_log_result fail "No @see for deprecated proc [api_proc_link $p]" } } else { incr good } } } aa_log "Found $good of $count procs checked" } aa_register_case -cats {smoke production_safe} -error_level warning -procs { aa_log_result acs_package_root_dir } documentation__check_typos { Search for spelling errors in the proc documentation, using a list of common typos based on the one included in the lintian Debian package: https://github.com/Debian/lintian/tree/master/data/spelling Limitations: 1- Only single words are tested. 2- Words are converted to lowercase before testing, so tests are case insensitive. 3- Every word is compared against more than 4000 typos (currently), so it may be slow depending on the particular setup. @author Héctor Romojaro <hector.romojaro@gmail.com> @creation-date 2018-07-23 } { set typo_list "[acs_package_root_dir acs-tcl]/tcl/test/doc-check-procs-common-typos.txt" set typos [dict create] # Create the typo dictionary with values from the common typos file set f [open $typo_list "r"] while {[gets $f line] >= 0} { if {[regexp {^(.*)[\|][\|](.*)$} [string tolower $line] . word replacement]} { dict set typos $word $replacement } } close $f aa_log "Created typo dictionary using data from $typo_list ([dict size $typos] typos loaded)" # Check for the typos set count 0 set good 0 set checks 0 set ignorechars { , " " ( " " ) " " < " " > " " \[ " " \] " " \{ " " \} " " < " " > " " . " " : " " ; " " ? " " ! " " = " " \r " " \" " " „ " " “ " " ” " " " " "" } foreach p [lsort -dictionary [nsv_array names api_proc_doc]] { incr count set typo_number 0 set proc_doc [nsv_get api_proc_doc $p] if {[dict exists $proc_doc main]} { set main_doc [string tolower [dict get $proc_doc main]] # # Remove extra characters from the doc. # set proc_doc_clean [string map $ignorechars $main_doc] if { [string length $proc_doc_clean] > 0} { # # Check the words of the documentation string # against the dictionary. # foreach word [lsort -unique $proc_doc_clean] { incr checks if {[dict exists $typos $word]} { # Typo found! incr typo_number aa_log_result fail "spelling error in proc $p: $word -> [dict get $typos $word]" } } } # Just count the number of procs without doc typos for summarizing if { $typo_number == 0 } { incr good } } } aa_log "Documentation seems typo free in $good of $count checked procs (total typo checks: $checks)" } aa_register_case -cats {smoke production_safe} -error_level warning -procs { aa_log_result } documentation__check_parameters { Check if the parameters defined in the proc doc as '@param' are actual parameters. Sometimes proc parameter changes are not reflected in the proc doc, this should take care of some of these cases. Test is case-sensitive. @author Héctor Romojaro <hector.romojaro@gmail.com> @creation-date 2018-07-24 } { set count 0 set good 0 set ignorechars { , " " ( " " ) " " < " " > " " \{ " " \} " " < " " > " " . " " : " " ; " " ? " " ! " " = " " \r " " \" " " „ " " “ " " ” " " " " "" } foreach p [lsort -dictionary [nsv_array names api_proc_doc]] { set param_unknown 0 set proc_doc [nsv_get api_proc_doc $p] set deprecated_p [expr {[dict exists $proc_doc deprecated_p] && [dict get $proc_doc deprecated_p]}] if {!$deprecated_p && [dict exists $proc_doc param]} { incr count set params [dict get $proc_doc param] # # Build the real parameters list # #ns_log notice "check args for '$p'" set real_params [list \ {*}[dict get $proc_doc switches0] \ {*}[dict get $proc_doc positionals] \ {*}[dict get $proc_doc switches1] \ ] # # Check if the last parameter is 'args', as it is not included into # 'switches' or 'positionals', and add it to the real parameter list # if {[dict get $proc_doc varargs_p]} { lappend real_params args } # # Check if the @param exists in the list of parameters # foreach param_doc $params { set param [lindex [string map $ignorechars $param_doc] 0] # Allow boolean parameter name with appended '_p' regsub -- _p$ $param "" param_trim_p if {$param ni $real_params && $param_trim_p ni $real_params} { # Nonexistent @param found! #ns_log notice "param_docs '$param_doc' real_params '$real_params'" incr param_unknown aa_log_result fail "Unknown parameter '$param' in documentation of proc '$p'" } } # Just count the number of procs without nonexistent @params if { $param_unknown == 0 } { incr good } } } aa_log "@param names seem coherent with the actual proc parameters in $good of $count checked procs" } if {[parameter::get \ -package_id [apm_package_id_from_key acs-api-browser] \ -parameter IncludeCallingInfo \ -default false]} { aa_register_case \ -cats {smoke production_safe} \ -error_level warning \ cross_package_called_private_functions { Search for cross-package calls of private functions. @author Gustaf Neumann @creation-date 2018-07-25 } { set count 0 set fails 0 set private 0 foreach called [lsort -dictionary [nsv_array names api_proc_doc]] { incr count set called_by_count 0 set called_info [nsv_get api_proc_doc $called] if {[dict exists $called_info calledby] && [dict exists $called_info script] && [dict exists $called_info protection] && [dict get $called_info protection] eq "private" } { incr private regexp {^packages/([^/]+)/} [dict get $called_info script] . called_package_key foreach caller [lsort [dict get $called_info calledby]] { incr called_by_count if {[nsv_get api_proc_doc $caller caller_info] && [dict exists $caller_info script] && ![string match "AcsSc.*" $caller] } { regexp {^packages/([^/]+)/} [dict get $caller_info script] . caller_package_key if {$caller_package_key ne $called_package_key} { incr fails set msg "" append msg \ "private function <$called_package_key $called> " \ "called by <$caller_package_key $caller><br>" \ [dict get $called_info script] "<br>" \ [dict get $caller_info script] aa_log_result fail $msg } } } ns_log notice "private function $called called by $called_by_count functions" } } aa_log "Found $fails cross-package private calls out of a total of $private private calls (total: $count call sites)" } } # Local variables: # mode: tcl # tcl-indent-level: 4 # indent-tabs-mode: nil # End: