Monorepo management for opam overlays
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

monopam quality: split detect into static / doc / tools (E001/E010)

Brings the cyclomatic complexity of detect down from 14 to single
digits and the nesting of read_policy down from 5 to 2 by extracting
detect_static, detect_doc, detect_lib_tool, detect_tools, read_file,
parse_x_quality_line, and read_policy_from_opam helpers. Behaviour
unchanged.

+87 -66
+87 -66
lib/quality.ml
··· 76 76 (!vals - min !documented !vals, !vals) 77 77 with Sys_error _ -> (0, 0) 78 78 79 - (** Detect which features a package actually has. *) 80 - let detect ?(tools = false) pkg_dir = 79 + (** Static features inferable from directory layout alone. *) 80 + let detect_static pkg_dir = 81 81 let lib_dir = Filename.concat pkg_dir "lib" in 82 82 let test_dir = Filename.concat pkg_dir "test" in 83 83 let fuzz_dir = Filename.concat pkg_dir "fuzz" in 84 84 let interop_dir = Filename.concat test_dir "interop" in 85 85 let has_lib = dir_exists lib_dir && has_files lib_dir ".ml" in 86 86 let has_dune = Sys.file_exists (Filename.concat pkg_dir "dune-project") in 87 - let present = ref [] in 88 - let add f = present := f :: !present in 89 - if has_lib && has_dune then add "build"; 90 - if dir_exists test_dir && has_files test_dir ".ml" then add "test"; 91 - if dir_exists fuzz_dir && has_files fuzz_dir ".ml" then add "fuzz"; 92 - if dir_exists interop_dir then add "interop"; 93 - if dir_exists test_dir && has_subdirs_with_suffix test_dir ".t" then 94 - add "cram"; 95 - (* doc: all .mli vals documented *) 96 - (if has_lib then 97 - let mli_files = 98 - try 99 - Sys.readdir lib_dir |> Array.to_list 100 - |> List.filter (fun f -> Filename.check_suffix f ".mli") 101 - with Sys_error _ -> [] 102 - in 103 - if mli_files <> [] then 104 - let undoc, total = 105 - List.fold_left 106 - (fun (u, v) f -> 107 - let un, va = count_undocumented_vals (Filename.concat lib_dir f) in 108 - (u + un, v + va)) 109 - (0, 0) mli_files 110 - in 111 - if total > 0 && undoc = 0 then add "doc"); 112 - (* proof: declaration-only — no auto-detection *) 113 - (* tools: merlint, prune, dupfind — only when requested *) 114 - if tools && has_lib then ( 115 - let q = Filename.quote lib_dir in 116 - if run_tool (Fmt.str "dune exec -B -- merlint --json %s >/dev/null 2>&1" q) 117 - then add "merlint"; 118 - if run_tool (Fmt.str "dune exec -B -- prune --quiet %s >/dev/null 2>&1" q) 119 - then add "prune"; 120 - if run_tool (Fmt.str "dune exec -B -- dupfind --quiet %s >/dev/null 2>&1" q) 121 - then add "dupfind"); 122 - List.sort_uniq String.compare !present 87 + let conds = 88 + [ 89 + ("build", has_lib && has_dune); 90 + ("test", dir_exists test_dir && has_files test_dir ".ml"); 91 + ("fuzz", dir_exists fuzz_dir && has_files fuzz_dir ".ml"); 92 + ("interop", dir_exists interop_dir); 93 + ("cram", dir_exists test_dir && has_subdirs_with_suffix test_dir ".t"); 94 + ] 95 + in 96 + List.filter_map (fun (f, ok) -> if ok then Some f else None) conds 97 + 98 + (** [doc] is present when every public value in every [.mli] under [lib/] has a 99 + doc comment. *) 100 + let detect_doc pkg_dir = 101 + let lib_dir = Filename.concat pkg_dir "lib" in 102 + if not (dir_exists lib_dir && has_files lib_dir ".ml") then [] 103 + else 104 + let mli_files = 105 + try 106 + Sys.readdir lib_dir |> Array.to_list 107 + |> List.filter (fun f -> Filename.check_suffix f ".mli") 108 + with Sys_error _ -> [] 109 + in 110 + if mli_files = [] then [] 111 + else 112 + let undoc, total = 113 + List.fold_left 114 + (fun (u, v) f -> 115 + let un, va = count_undocumented_vals (Filename.concat lib_dir f) in 116 + (u + un, v + va)) 117 + (0, 0) mli_files 118 + in 119 + if total > 0 && undoc = 0 then [ "doc" ] else [] 120 + 121 + (** Run an external lint tool and return its feature name when it succeeds. *) 122 + let detect_lib_tool pkg_dir tool = 123 + let q = Filename.quote (Filename.concat pkg_dir "lib") in 124 + let flag = if tool = "merlint" then "--json" else "--quiet" in 125 + let cmd = Fmt.str "dune exec -B -- %s %s %s >/dev/null 2>&1" tool flag q in 126 + if run_tool cmd then [ tool ] else [] 127 + 128 + let detect_tools pkg_dir = 129 + let lib_dir = Filename.concat pkg_dir "lib" in 130 + if not (dir_exists lib_dir && has_files lib_dir ".ml") then [] 131 + else 132 + List.concat_map (detect_lib_tool pkg_dir) [ "merlint"; "prune"; "dupfind" ] 133 + 134 + (** Detect which features a package actually has. *) 135 + let detect ?(tools = false) pkg_dir = 136 + let static = detect_static pkg_dir in 137 + let doc = detect_doc pkg_dir in 138 + let tools_features = if tools then detect_tools pkg_dir else [] in 139 + (* proof: declaration-only — no auto-detection. *) 140 + List.sort_uniq String.compare (static @ doc @ tools_features) 123 141 124 142 (* ===== Policy ===== *) 125 143 144 + let read_file path = 145 + try 146 + let ic = open_in path in 147 + let content = In_channel.input_all ic in 148 + close_in ic; 149 + Some content 150 + with Sys_error _ -> None 151 + 152 + let parse_x_quality_line line = 153 + let t = String.trim line in 154 + let prefix = "x-quality:" in 155 + let plen = String.length prefix in 156 + if String.length t <= plen || String.sub t 0 plen <> prefix then [] 157 + else 158 + let rest = String.sub t plen (String.length t - plen) in 159 + (* Parse ["build" "test" "fuzz"] *) 160 + String.split_on_char '"' rest 161 + |> List.filter_map (fun s -> 162 + let s = String.trim s in 163 + if s = "" || s = "[" || s = "]" then None else Some s) 164 + 165 + let read_policy_from_opam path = 166 + match read_file path with 167 + | None -> [] 168 + | Some content -> 169 + String.split_on_char '\n' content |> List.concat_map parse_x_quality_line 170 + 126 171 (** Read quality policy from [*.opam] files. Looks for 127 172 [x-quality: ["build" "test" ...]] in any .opam file under [pkg_dir]. *) 128 173 let read_policy pkg_dir = 129 174 try 130 - let files = Sys.readdir pkg_dir |> Array.to_list in 131 - let opam_files = 132 - List.filter (fun f -> Filename.check_suffix f ".opam") files 133 - in 134 - List.concat_map 135 - (fun f -> 136 - let path = Filename.concat pkg_dir f in 137 - try 138 - let ic = open_in path in 139 - let content = In_channel.input_all ic in 140 - close_in ic; 141 - let lines = String.split_on_char '\n' content in 142 - List.concat_map 143 - (fun line -> 144 - let t = String.trim line in 145 - let prefix = "x-quality:" in 146 - let plen = String.length prefix in 147 - if String.length t > plen && String.sub t 0 plen = prefix then 148 - let rest = String.sub t plen (String.length t - plen) in 149 - (* Parse ["build" "test" "fuzz"] *) 150 - String.split_on_char '"' rest 151 - |> List.filter (fun s -> 152 - let s = String.trim s in 153 - s <> "" && s <> "[" && s <> "]" && s <> " ") 154 - else []) 155 - lines 156 - with Sys_error _ -> []) 157 - opam_files 175 + Sys.readdir pkg_dir |> Array.to_list 176 + |> List.filter (fun f -> Filename.check_suffix f ".opam") 177 + |> List.concat_map (fun f -> 178 + read_policy_from_opam (Filename.concat pkg_dir f)) 158 179 with Sys_error _ -> [] 159 180 160 181 (* ===== Check ===== *)