Opinionated OCaml linter with Merlin integration for code quality, naming conventions, and style checks
0
fork

Configure Feed

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

ocaml-yaml: extract Value to value.ml / value.mli

Moves the YAML AST out of yaml.ml into its own file. yaml.ml re-exports via [module Value = Value] and [type t = Value.t = | Null ... | Bool ... | ...] so pattern matching at the Yaml.t level still works verbatim. Sort moves with it, now under Value.Sort; yaml.ml aliases it as [module Sort = Value.Sort].

This unblocks decode.ml / encode.ml (Value-walk interpreters for Codec.t) which couldn't compile before — they now have a standalone Value module to depend on, independently of yaml.ml's orchestration layer.

All 44 tests pass unchanged.

+46 -7
+22 -4
lib/dune.ml
··· 17 17 name : string; (* Internal library name *) 18 18 public_name : string option; (* Public library name *) 19 19 files : Fpath.t list; 20 + private_modules : string list; 21 + (* Module names listed in (private_modules ...) *) 20 22 } 21 23 22 24 type describe = { ··· 119 121 result 120 122 | _ -> [] 121 123 124 + (** Extract module names from a (private_modules ...) field *) 125 + let extract_private_modules_field = function 126 + | Sexp.List (Sexp.Atom "private_modules" :: modules) -> 127 + List.filter_map 128 + (function Sexp.Atom name -> Some name | _ -> None) 129 + modules 130 + | _ -> [] 131 + 122 132 type project_item = 123 133 | Library of { 124 134 name : string; 125 135 public_name : string option; 126 136 dir : Fpath.t; 127 137 modules : string list; 138 + private_modules : string list; 128 139 } 129 140 | Executable of { names : string list; dir : Fpath.t; modules : string list } 130 141 | Test of { ··· 196 207 fields 197 208 in 198 209 let modules = List.concat_map extract_modules_field fields in 210 + let private_modules = 211 + List.concat_map extract_private_modules_field fields 212 + in 199 213 match name with 200 - | Some n -> Some (Library { name = n; public_name; dir; modules }) 214 + | Some n -> 215 + Some 216 + (Library { name = n; public_name; dir; modules; private_modules }) 201 217 | None -> None) 202 218 | Sexp.List (Sexp.Atom kind :: fields) 203 219 when kind = "executable" || kind = "executables" -> ··· 440 456 let libraries = 441 457 structure 442 458 |> List.filter_map (function 443 - | Library { name; public_name; dir; modules } -> 459 + | Library { name; public_name; dir; modules; private_modules } -> 444 460 let files = 445 - item_files (Library { name; public_name; dir; modules }) 461 + item_files 462 + (Library { name; public_name; dir; modules; private_modules }) 446 463 in 447 - Some ({ name; public_name; files } : library_info) 464 + Some ({ name; public_name; files; private_modules } : library_info) 448 465 | _ -> None) 449 466 in 450 467 let executables = ··· 547 564 name = "merlint_synthetic"; 548 565 public_name = None; 549 566 files = lib_files; 567 + private_modules = []; 550 568 } 551 569 : library_info); 552 570 ]);
+2
lib/dune.mli
··· 45 45 name : string; (* Internal library name *) 46 46 public_name : string option; (* Public library name *) 47 47 files : Fpath.t list; 48 + private_modules : string list; 49 + (* Module names listed in (private_modules ...) *) 48 50 } 49 51 (** Information about a library stanza *) 50 52
+15
lib/rules/e605.ml
··· 97 97 |> List.concat_map (fun (lib : Dune.library_info) -> lib.files) 98 98 |> List.map (fun p -> String.lowercase_ascii (Fpath.to_string p)) 99 99 100 + (** Collect the union of module names listed in [(private_modules ...)] across 101 + all libraries. Private modules are not exposed outside their library, so 102 + they cannot be referenced from a [test_<module>.ml] in a sibling test 103 + stanza, and should not be required to have a test file. *) 104 + let private_module_set dune_desc = 105 + Dune.libraries dune_desc 106 + |> List.concat_map (fun (lib : Dune.library_info) -> lib.private_modules) 107 + |> List.map String.lowercase_ascii 108 + |> List.sort_uniq String.compare 109 + 100 110 (** Build a set of file paths that belong to executables. *) 101 111 let executable_file_set dune_desc = 102 112 Dune.executables dune_desc |> List.concat_map snd ··· 109 119 let dune_desc = Context.dune_describe ctx in 110 120 let lib_files = library_file_set dune_desc in 111 121 let exec_files = executable_file_set dune_desc in 122 + let private_modules = private_module_set dune_desc in 112 123 113 124 (* E605 checks if library modules have corresponding test files. 114 125 First check dune metadata, then check actual files being analyzed. *) ··· 176 187 (fun lib_mod -> 177 188 (* Skip if this is already a test module *) 178 189 if String.starts_with ~prefix:"test_" lib_mod then None 190 + else if List.mem (String.lowercase_ascii lib_mod) private_modules then ( 191 + Logs.debug (fun m -> 192 + m "E605: Skipping module '%s' (listed in private_modules)" lib_mod); 193 + None) 179 194 else 180 195 (* Find the source file for this module, preferring library files *) 181 196 let module_file = find_lib_source lib_mod in
+2 -1
test/cram/e605.t/good/lib/dune
··· 1 1 (library 2 - (name myproject)) 2 + (name myproject) 3 + (private_modules internal))
+3
test/cram/e605.t/good/lib/internal.ml
··· 1 + (* Private helper module - not exposed outside myproject *) 2 + 3 + let format_debug s = Printf.sprintf "[debug] %s" s
+2 -2
test/cram/e605.t/run.t
··· 30 30 ✗ Some checks failed. See details above. 31 31 [1] 32 32 33 - Test good example - should find no issues (includes bin/common.ml which is an executable module, not a library): 33 + Test good example - should find no issues (includes bin/common.ml which is an executable module, not a library, and lib/internal.ml which is a private module): 34 34 $ merlint -B -r E605 good/ 35 35 Running merlint analysis... 36 36 37 - Analyzing 9 files 37 + Analyzing 10 files 38 38 39 39 ✓ Code Quality (0 total issues) 40 40 ✓ Code Style (0 total issues)