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.

merlint: add E520 (lib/ convention) and E521 (cram under test/cram/)

Two project-structure rules:

- E520 flags packages whose library code lives in src/ (the monorepo
convention is lib/, used by 155+ packages). Auto-fix: git mv src lib.

- E521 flags cram tests (.t files or .t directories with run.t) at
test/ rather than test/cram/. Shared driver exes belong in
test/cram/helpers/; shell setup in test/cram/helpers.sh sourced via
(setup_scripts helpers.sh).

+227
+2
lib/data.ml
··· 35 35 E505.rule; 36 36 E510.rule; 37 37 E515.rule; 38 + E520.rule; 39 + E521.rule; 38 40 E600.rule; 39 41 E605.rule; 40 42 E606.rule;
+43
lib/rules/e520.ml
··· 1 + (** E520: Package library directory should be named lib/, not src/. 2 + 3 + The monorepo convention is that a package's primary library lives under 4 + [lib/]. Older packages that still use [src/] must be renamed. *) 5 + 6 + type payload = { package : string } 7 + 8 + let check (ctx : Context.project) = 9 + let root = ctx.project_root in 10 + let try_readdir d = 11 + try Sys.readdir d |> Array.to_list with Sys_error _ -> [] 12 + in 13 + let issues = ref [] in 14 + let packages = try_readdir root in 15 + List.iter 16 + (fun pkg -> 17 + let pkg_dir = Filename.concat root pkg in 18 + if 19 + Sys.file_exists pkg_dir && Sys.is_directory pkg_dir && pkg <> "_build" 20 + && pkg <> ".git" && pkg <> "_opam" 21 + then 22 + let src_dir = Filename.concat pkg_dir "src" in 23 + let has_ml f = Filename.check_suffix f ".ml" in 24 + if Sys.file_exists src_dir && Sys.is_directory src_dir then 25 + let src_has_ml = List.exists has_ml (try_readdir src_dir) in 26 + if src_has_ml then issues := Issue.v { package = pkg } :: !issues) 27 + packages; 28 + !issues 29 + 30 + let pp ppf { package } = 31 + Fmt.pf ppf 32 + "%s uses src/ for its library; rename to lib/ to match the monorepo \ 33 + convention" 34 + package 35 + 36 + let rule = 37 + Rule.v ~code:"E520" ~title:"Library directory should be lib/, not src/" 38 + ~category:Rule.Project_structure 39 + ~hint: 40 + "The monorepo convention is lib/ for library code. Rename src/ to lib/ \ 41 + with `git mv`; no dune changes are needed because dune auto-discovers \ 42 + modules in either directory." 43 + ~examples:[] ~pp (Project check)
+62
lib/rules/e521.ml
··· 1 + (** E521: Cram tests belong under test/cram/, not scattered in test/. 2 + 3 + The monorepo convention is that all cram tests for a package live under 4 + [<package>/test/cram/]: 5 + 6 + {v 7 + test/cram/ 8 + ├── dune # one (cram ...) stanza 9 + ├── helpers.sh # auto-sourced, exports PATH etc. 10 + ├── helpers/ # driver exe sources 11 + │ ├── dune 12 + │ └── demo.ml 13 + └── cli.t/ 14 + └── run.t 15 + v} 16 + 17 + This rule flags cram tests (.t files or .t directories containing run.t) 18 + that sit directly in test/ rather than under test/cram/. *) 19 + 20 + type payload = { package : string; path : string } 21 + 22 + let check (ctx : Context.project) = 23 + let root = ctx.project_root in 24 + let try_readdir d = 25 + try Sys.readdir d |> Array.to_list with Sys_error _ -> [] 26 + in 27 + let issues = ref [] in 28 + let packages = try_readdir root in 29 + List.iter 30 + (fun pkg -> 31 + let test_dir = Filename.concat (Filename.concat root pkg) "test" in 32 + let is_cram name full = 33 + Filename.check_suffix name ".t" 34 + && ((not (Sys.is_directory full && Sys.file_exists full)) 35 + || Sys.file_exists (Filename.concat full "run.t")) 36 + in 37 + if pkg <> "_build" && pkg <> "_opam" && Sys.file_exists test_dir then 38 + let entries = try_readdir test_dir in 39 + List.iter 40 + (fun name -> 41 + let full = Filename.concat test_dir name in 42 + let ok = try is_cram name full with Sys_error _ -> false in 43 + if ok then 44 + issues := 45 + Issue.v { package = pkg; path = Filename.concat "test" name } 46 + :: !issues) 47 + entries) 48 + packages; 49 + !issues 50 + 51 + let pp ppf { package; path } = 52 + Fmt.pf ppf "%s/%s should live under %s/test/cram/" package path package 53 + 54 + let rule = 55 + Rule.v ~code:"E521" ~title:"Cram test outside test/cram/" 56 + ~category:Rule.Project_structure 57 + ~hint: 58 + "Move cram tests (.t files or .t/ directories) under the package's \ 59 + test/cram/ umbrella. Shared driver exes go in test/cram/helpers/; shell \ 60 + setup goes in test/cram/helpers.sh (sourced via (setup_scripts \ 61 + helpers.sh))." 62 + ~examples:[] ~pp (Project check)
+1
test/cram/e520.t/bad/dune-project
··· 1 + (lang dune 3.21)
+1
test/cram/e520.t/bad/pkg/src/a.ml
··· 1 + let _ = ()
+1
test/cram/e520.t/good/dune-project
··· 1 + (lang dune 3.21)
+1
test/cram/e520.t/good/pkg/lib/a.ml
··· 1 + let _ = ()
+56
test/cram/e520.t/run.t
··· 1 + Test bad example - package uses src/ instead of lib/: 2 + $ merlint -B -r E520 bad/ 3 + Running merlint analysis... 4 + 5 + Analyzing 0 files 6 + 7 + ✓ Code Quality (0 total issues) 8 + ✓ Code Style (0 total issues) 9 + ✓ Naming Conventions (0 total issues) 10 + ✓ Documentation (0 total issues) 11 + ✗ Project Structure (1 total issues) 12 + [E520] Library directory should be lib/, not src/ (1 issue) 13 + The monorepo convention is lib/ for library code. Rename src/ to lib/ with 14 + `git mv`; no dune changes are needed because dune auto-discovers modules in 15 + either directory. 16 + - (global) pkg uses src/ for its library; rename to lib/ to match the monorepo convention 17 + ✓ Test Quality (0 total issues) 18 + ✓ Interop Testing (0 total issues) 19 + ✓ Code Generation (0 total issues) 20 + 21 + ╭───────────────────┬──────────────────────────────────────────────────╮ 22 + │ Category │ Issues │ 23 + ├───────────────────┼──────────────────────────────────────────────────┤ 24 + │ Project Structure │ 1 (1 library directory should be lib/, not src/) │ 25 + ╰───────────────────┴──────────────────────────────────────────────────╯ 26 + 27 + 28 + Summary: ✗ 1 total issue (applied 1 rule) 29 + ✗ Some checks failed. See details above. 30 + [1] 31 + 32 + 33 + 34 + 35 + 36 + 37 + Test good example - package uses lib/: 38 + $ merlint -B -r E520 good/ 39 + Running merlint analysis... 40 + 41 + Analyzing 0 files 42 + 43 + ✓ Code Quality (0 total issues) 44 + ✓ Code Style (0 total issues) 45 + ✓ Naming Conventions (0 total issues) 46 + ✓ Documentation (0 total issues) 47 + ✓ Project Structure (0 total issues) 48 + ✓ Test Quality (0 total issues) 49 + ✓ Interop Testing (0 total issues) 50 + ✓ Code Generation (0 total issues) 51 + 52 + Summary: ✓ 0 total issues (applied 1 rule) 53 + ✓ All checks passed! 54 + 55 + 56 +
+1
test/cram/e521.t/bad/dune-project
··· 1 + (lang dune 3.21)
+1
test/cram/e521.t/bad/pkg/test/foo.t/run.t
··· 1 + $ echo ok
+1
test/cram/e521.t/good/dune-project
··· 1 + (lang dune 3.21)
+1
test/cram/e521.t/good/pkg/test/cram/foo.t/run.t
··· 1 + $ echo ok
+56
test/cram/e521.t/run.t
··· 1 + Test bad example - cram test at test/ rather than test/cram/: 2 + $ merlint -B -r E521 bad/ 3 + Running merlint analysis... 4 + 5 + Analyzing 0 files 6 + 7 + ✓ Code Quality (0 total issues) 8 + ✓ Code Style (0 total issues) 9 + ✓ Naming Conventions (0 total issues) 10 + ✓ Documentation (0 total issues) 11 + ✗ Project Structure (1 total issues) 12 + [E521] Cram test outside test/cram/ (1 issue) 13 + Move cram tests (.t files or .t/ directories) under the package's test/cram/ 14 + umbrella. Shared driver exes go in test/cram/helpers/; shell setup goes in 15 + test/cram/helpers.sh (sourced via (setup_scripts helpers.sh)). 16 + - (global) pkg/test/foo.t should live under pkg/test/cram/ 17 + ✓ Test Quality (0 total issues) 18 + ✓ Interop Testing (0 total issues) 19 + ✓ Code Generation (0 total issues) 20 + 21 + ╭───────────────────┬────────────────────────────────────╮ 22 + │ Category │ Issues │ 23 + ├───────────────────┼────────────────────────────────────┤ 24 + │ Project Structure │ 1 (1 cram test outside test/cram/) │ 25 + ╰───────────────────┴────────────────────────────────────╯ 26 + 27 + 28 + Summary: ✗ 1 total issue (applied 1 rule) 29 + ✗ Some checks failed. See details above. 30 + [1] 31 + 32 + 33 + 34 + 35 + 36 + 37 + Test good example - cram test under test/cram/: 38 + $ merlint -B -r E521 good/ 39 + Running merlint analysis... 40 + 41 + Analyzing 0 files 42 + 43 + ✓ Code Quality (0 total issues) 44 + ✓ Code Style (0 total issues) 45 + ✓ Naming Conventions (0 total issues) 46 + ✓ Documentation (0 total issues) 47 + ✓ Project Structure (0 total issues) 48 + ✓ Test Quality (0 total issues) 49 + ✓ Interop Testing (0 total issues) 50 + ✓ Code Generation (0 total issues) 51 + 52 + Summary: ✓ 0 total issues (applied 1 rule) 53 + ✓ All checks passed! 54 + 55 + 56 +