[help] implement `--write-website`

Summary:
Write documentation for all documented issue types and all user-facing
checkers in the "next" version of the documentation. Next diff shows the
new website.

Reviewed By: dulmarod

Differential Revision: D21934370

fbshipit-source-id: 53315d2b4
master
Jules Villard 5 years ago committed by Facebook GitHub Bot
parent 0bf23c66fd
commit a1b7016e11

@ -590,7 +590,7 @@ config_tests: test_build ocaml_unit_test validate-skel mod_dep
$(MAKE) endtoend_test checkCopyright
$(MAKE) manuals
ifneq ($(filter endtoend_test,${MAKECMDGOALS}),)
ifneq ($(filter endtoend_test,$(MAKECMDGOALS)),)
checkCopyright: src_build
toplevel_test: checkCopyright
endif
@ -854,7 +854,7 @@ doc: src_build_common
$(QUIET)$(call silent_on_success,Generating infer documentation,\
$(MAKE_SOURCE) doc)
# do not call the browser if we are publishing the docs
ifeq ($(filter doc-publish,${MAKECMDGOALS}),)
ifneq ($(NO_BROWSE_DOC),yes)
$(QUIET)$(call silent_on_success,Opening in browser,\
browse $(INFER_DIR)/_build/default/_doc/_html/index.html)
$(QUIET)echo "Tip: you can generate the doc for all the opam dependencies of infer like this:"
@ -864,22 +864,28 @@ ifeq ($(filter doc-publish,${MAKECMDGOALS}),)
endif
.PHONY: doc-publish
doc-publish: $(INFER_GROFF_MANUALS)
doc-publish:
ifeq ($(IS_FACEBOOK_TREE),yes)
$(QUIET)$(call silent_on_success,Cleaning up FB-only files,\
$(MAKE) -C $(SRC_DIR) clean; \
$(MAKE) -C facebook clean)
endif
$(QUIET)$(call silent_on_success,Building infer and manuals,\
$(MAKE) $(INFER_GROFF_MANUALS))
$(QUIET)$(MKDIR_P) "$(WEBSITE_DIR)"/static/man/next "$(WEBSITE_DIR)"/static/odoc/next
$(QUIET)$(call silent_on_success,Copying man pages,\
$(REMOVE) "$(WEBSITE_DIR)"/static/man/*; \
for man in $(INFER_GROFF_MANUALS); do \
groff -Thtml "$$man" > "$(WEBSITE_DIR)"/static/man/next/$$(basename "$$man").html; \
done)
ifeq ($(IS_FACEBOOK_TREE),yes)
$(QUIET)$(call silent_on_success,Cleaning up FB-only files,\
$(MAKE) -C $(SRC_DIR) clean; \
$(MAKE) -C facebook clean)
endif
$(QUIET)$(call silent_on_success,Building OCaml modules documentation,\
$(MAKE) IS_FACEBOOK_TREE=no doc)
$(MAKE) IS_FACEBOOK_TREE=no NO_BROWSE_DOC=yes doc)
$(QUIET)$(call silent_on_success,Copying OCaml modules documentation,\
rsync -a --delete $(BUILD_DIR)/default/_doc/_html/ "$(WEBSITE_DIR)"/static/odoc/next/)
$(QUIET)$(call silent_on_success,Building infer,\
$(MAKE) src_build)
$(QUIET)$(call silent_on_success,Calling 'infer help --write-website',\
$(INFER_BIN) help --write-website "$(WEBSITE_DIR)")
# print list of targets
.PHONY: show-targets

@ -6,7 +6,7 @@
*)
open! IStd
type t = Clang | Java [@@deriving compare]
type t = Clang | Java [@@deriving compare, enumerate]
let equal = [%compare.equal: t]

@ -7,7 +7,7 @@
open! IStd
type t = Clang | Java [@@deriving compare]
type t = Clang | Java [@@deriving compare, enumerate]
val equal : t -> t -> bool

@ -6,10 +6,78 @@
*)
open! IStd
module F = Format
module L = Logging
let list_checkers () = assert false
let mk_markdown_docs_path ~website_root ~basename = website_root ^/ "docs" ^/ basename ^ ".md"
let escape_double_quotes s = String.substr_replace_all s ~pattern:"\"" ~with_:"\\\""
let all_issues_basename = "all-issue-types"
let basename_checker_prefix = "checker-"
let basename_of_checker {Checker.id} = basename_checker_prefix ^ id
let get_checker_web_documentation (checker : Checker.config) =
match checker.kind with
| UserFacing {title; markdown_body} ->
Some (title, markdown_body, None)
| UserFacingDeprecated {title; markdown_body; deprecation_message} ->
Some (title, markdown_body, Some deprecation_message)
| Internal | Exercise ->
None
let markdown_one_issue f (issue_type : IssueType.t) =
F.fprintf f "## %s@\n@\n" issue_type.unique_id ;
let checker_config = Checker.config issue_type.checker in
if Option.is_none (get_checker_web_documentation checker_config) then
L.die InternalError
"Checker %s can report user-facing issue %s but is not of type UserFacing in \
src/base/Checker.ml. Please fix!"
checker_config.id issue_type.unique_id ;
F.fprintf f "Reported as \"%s\" by [%s](%s.md).@\n@\n" issue_type.hum checker_config.id
(basename_of_checker checker_config) ;
match issue_type.user_documentation with
| None ->
()
| Some documentation ->
F.pp_print_string f documentation
let all_issues_header =
{|---
title: List of all issue types
---
Here is an overview of the issue types currently reported by Infer. Currently outdated and being worked on!
|}
(* TODO: instead of just taking issues that have documentation, enforce that (some, eg enabled
by default) issue types always have documentation *)
let all_issues =
lazy
( IssueType.all_issues ()
|> List.filter ~f:(fun {IssueType.user_documentation} -> Option.is_some user_documentation)
|> List.sort ~compare:(fun {IssueType.unique_id= id1} {IssueType.unique_id= id2} ->
String.compare id1 id2 ) )
let all_issues_website ~website_root =
let issues_to_document = Lazy.force all_issues in
Utils.with_file_out (mk_markdown_docs_path ~website_root ~basename:all_issues_basename)
~f:(fun out_channel ->
let f = F.formatter_of_out_channel out_channel in
F.fprintf f "%s@\n%a@\n%!" all_issues_header
(Pp.seq ~sep:"\n" markdown_one_issue)
issues_to_document )
let list_issue_types () =
L.progress
"@[Format:@\n\
@ -66,4 +134,118 @@ let show_issue_types issue_types =
L.result "@]%!"
let write_website ~website_root:_ = assert false
let mk_checkers_json checkers_base_filenames =
`Assoc
[ ( "README"
, `String
(Printf.sprintf
"This is a %cgenerated file, run `make doc-publish` from the root of the infer \
repository to generate it"
(* avoid tooling thinking this source file itself is generated because of the string _at_generated appearing in it *)
'@') )
; ( "doc_entries"
, `List
( `String all_issues_basename
:: List.map checkers_base_filenames ~f:(fun filename -> `String filename) ) ) ]
(** Writes an index of all the checkers documentation pages. Must correspond to all the pages
written in docs/! *)
let write_checkers_json ~path =
let json =
List.filter_map Checker.all ~f:(fun checker ->
let config = Checker.config checker in
if Option.is_some (get_checker_web_documentation config) then
Some (basename_of_checker config)
else None )
|> mk_checkers_json
in
Utils.with_file_out path ~f:(fun out_channel ->
Yojson.pretty_to_channel ~std:true out_channel json )
let pp_checker_webpage_header f ~title ~short_documentation =
F.fprintf f {|---
title: "%s"
description: "%s"
---
%s
|} (escape_double_quotes title)
(escape_double_quotes short_documentation)
short_documentation
let pp_checker_deprecation_message f message =
F.fprintf f "**\\*\\*\\*DEPRECATED\\*\\*\\*** %s@\n@\n" message
let pp_checker_cli_flags f checker_config =
F.fprintf f "Activate with `--%s`.@\n@\n" checker_config.Checker.id
let string_of_support (support : Checker.support) =
match support with NoSupport -> "No" | ExperimentalSupport -> "Experimental" | Support -> "Yes"
let pp_checker_language_support f support =
F.fprintf f "Supported languages:@\n" ;
List.iter Language.all ~f:(fun language ->
F.fprintf f "- %s: %s@\n" (Language.to_string language) (string_of_support (support language)) ) ;
F.pp_print_newline f ()
let pp_checker_issue_types f checker =
F.fprintf f "@\n@\n## List of Issue Types@\n@\n" ;
F.fprintf f "The following issue types are reported by this checker:@\n" ;
let checker_issues =
List.filter (Lazy.force all_issues) ~f:(fun {IssueType.checker= issue_checker} ->
Checker.equal issue_checker checker )
in
let pp_issue f {IssueType.unique_id} =
F.fprintf f "- [%s](%s.md#%s)@\n" unique_id all_issues_basename (String.lowercase unique_id)
in
List.iter checker_issues ~f:(pp_issue f)
let write_checker_webpage ~website_root (checker : Checker.t) =
let checker_config = Checker.config checker in
match get_checker_web_documentation checker_config with
| None ->
()
| Some (title, markdown_body, deprecated_opt) ->
Utils.with_file_out
(mk_markdown_docs_path ~website_root ~basename:(basename_of_checker checker_config))
~f:(fun out_channel ->
let f = F.formatter_of_out_channel out_channel in
pp_checker_webpage_header f ~title ~short_documentation:checker_config.short_documentation ;
Option.iter deprecated_opt ~f:(pp_checker_deprecation_message f) ;
Option.iter checker_config.cli_flags ~f:(fun _ -> pp_checker_cli_flags f checker_config) ;
pp_checker_language_support f checker_config.support ;
F.pp_print_string f markdown_body ;
pp_checker_issue_types f checker ;
() )
(** delete all files that look like they were generated by a previous invocation of
[--write-website] to avoid keeping documentation for deleted checkers around *)
let delete_checkers_website ~website_root =
Utils.directory_iter
(fun path ->
if String.is_prefix ~prefix:basename_checker_prefix (Filename.basename path) then (
L.progress "deleting '%s'@\n" path ;
Unix.unlink path ) )
(website_root ^/ "docs")
let all_checkers_website ~website_root =
delete_checkers_website ~website_root ;
List.iter Checker.all ~f:(fun checker -> write_checker_webpage ~website_root checker)
let write_website ~website_root =
write_checkers_json ~path:(website_root ^/ "checkers.json") ;
all_checkers_website ~website_root ;
all_issues_website ~website_root ;
()

@ -0,0 +1,7 @@
{
"doc_entries": [
"checkers-bug-types",
"eradicate-warnings",
"linters-bug-types"
]
}

@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
const checkers = require("./checkers");
module.exports = {
docs: {
"Quick Start": ["getting-started", "hello-world"],
@ -26,11 +28,7 @@ module.exports = {
"separation-logic-and-bi-abduction",
"limitations",
],
"Bug Types Reference": [
"checkers-bug-types",
"eradicate-warnings",
"linters-bug-types",
],
"Analyses and Issue Types": checkers.doc_entries,
Contribute: ["absint-framework", "adding-checkers", "internal-API"],
Versions: ["versions"],
},

Loading…
Cancel
Save