Duplicate code detection across OCaml packages
0
fork

Configure Feed

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

refactor(dupfind): rename scan subcommand and fix package detection

Rename `inter` to `scan` for clarity, change `--intra` to `--no-intra`
so all duplicates are shown by default. Fix `package_of_file` to use
root basename for named roots and first path segment for dot roots.
Update cram test with hash subcommand coverage.

+48 -39
+11 -9
bin/main.ml
··· 22 22 23 23 (* inter subcommand *) 24 24 25 - let run_inter eio min_size intra format exclude top paths = 25 + let run_scan eio min_size no_intra format exclude top paths = 26 26 let fs = Eio.Stdenv.fs eio in 27 27 let index = Dupfind.Index.v () in 28 28 List.iter ··· 36 36 List.iter (Dupfind.Index.add index) fragments) 37 37 files) 38 38 paths; 39 + let intra = not no_intra in 39 40 let clusters = Dupfind.Cluster.get index ~intra in 40 41 Dupfind.Report.output ~format ~top clusters 41 42 ··· 199 200 value & opt int 5 200 201 & info [ "min-size" ] ~docv:"N" ~doc:"Minimum AST node count for fragments.") 201 202 202 - let intra = 203 + let no_intra = 203 204 Arg.( 204 - value & flag & info [ "intra" ] ~doc:"Also show within-package duplicates.") 205 + value & flag 206 + & info [ "no-intra" ] ~doc:"Only show cross-package duplicates.") 205 207 206 208 let format_opt = 207 209 let formats = ··· 244 246 value & pos_right 0 string [ "." ] 245 247 & info [] ~docv:"DIR" ~doc:"Directories to scan.") 246 248 247 - let inter_cmd eio = 248 - let doc = "Find cross-package duplicate code." in 249 - let info = Cmd.info "inter" ~doc in 249 + let scan_cmd eio = 250 + let doc = "Scan directories for duplicate code." in 251 + let info = Cmd.info "scan" ~doc in 250 252 Cmd.v info 251 253 Term.( 252 - const (run_inter eio) 253 - $ min_size $ intra $ format_opt $ exclude $ top $ paths) 254 + const (run_scan eio) 255 + $ min_size $ no_intra $ format_opt $ exclude $ top $ paths) 254 256 255 257 let search_cmd eio = 256 258 let doc = "Find duplicates of a specific function." in ··· 277 279 let info = Cmd.info "dupfind" ~version:"0.1.0" ~doc in 278 280 Cmd.group info 279 281 ~default:Term.(ret (const (`Help (`Pager, None)))) 280 - [ inter_cmd eio; search_cmd eio; hash_cmd ] 282 + [ scan_cmd eio; search_cmd eio; hash_cmd ] 281 283 282 284 let () = 283 285 Memtrace.trace_if_requested ();
+6 -6
lib/discover.ml
··· 34 34 walk [] root 35 35 36 36 let package_of_file ~root file = 37 - match Fpath.rem_prefix root file with 38 - | Some rel -> ( 39 - match Fpath.segs rel with 40 - | pkg :: _ :: _ -> pkg 41 - | _ -> Fpath.to_string root) 42 - | None -> Fpath.to_string root 37 + let base = Fpath.basename root in 38 + if base = "" || base = "." || base = ".." then 39 + let segs = Fpath.segs file in 40 + let segs = List.filter (fun s -> s <> "." && s <> "..") segs in 41 + match segs with pkg :: _ :: _ -> pkg | _ -> "unknown" 42 + else base
+20 -21
test/cram/inter.t
··· 20 20 > let unique_b () = print_endline "only in b" 21 21 > EOF 22 22 23 - Find cross-package duplicates (encode functions are structurally identical): 23 + Scan detects structurally identical functions after alpha-renaming: 24 24 25 - $ dupfind inter pkg_a pkg_b 25 + $ dupfind scan pkg_a pkg_b 26 + Clone #1 (21 nodes, 2 occurrences) 27 + pkg_b/lib/b.ml:1 encode 28 + pkg_a/lib/a.ml:1 encode 29 + 26 30 27 - Found 0 clone clusters across 0 packages (0 total duplicated nodes) 31 + Found 1 clone clusters across 2 packages (42 total duplicated nodes) 28 32 29 33 30 34 31 - No duplicates without --intra when only one package: 32 - 33 - $ dupfind inter pkg_a 34 - 35 - Found 0 clone clusters across 0 packages (0 total duplicated nodes) 35 + With --no-intra, only cross-package duplicates are shown: 36 36 37 - 38 - With --intra, same-package dupes show up (none here since functions differ): 39 - 40 - $ dupfind inter --intra pkg_a 37 + $ dupfind scan --no-intra pkg_a 41 38 42 39 Found 0 clone clusters across 0 packages (0 total duplicated nodes) 43 40 44 41 45 - Help works: 46 - 47 - $ dupfind --help | head -3 48 - DUPFIND(1) Dupfind Manual DUPFIND(1) 49 - 50 - NNAAMMEE 51 - 52 - 53 42 Find subcommand: 54 43 55 44 $ dupfind find A.encode pkg_a pkg_b ··· 58 47 pkg_a/lib/a.ml:1 encode 59 48 60 49 61 - Found 1 clone clusters across 1 packages (42 total duplicated nodes) 50 + Found 1 clone clusters across 2 packages (42 total duplicated nodes) 62 51 63 52 64 53 65 54 $ dupfind find A.unique_a pkg_a pkg_b 66 55 No duplicates found for A.unique_a. 56 + 57 + Hash subcommand shows normalized AST: 58 + 59 + $ dupfind hash "let f x = x + 1" 60 + AST: fun _0 -> + _0 1 61 + Hash: db4909941d07713e84ced7187adab6c8 62 + 63 + $ dupfind hash "let f y = y + 1" 64 + AST: fun _0 -> + _0 1 65 + Hash: db4909941d07713e84ced7187adab6c8
+11 -3
test/test_discover.ml
··· 3 3 Dupfind.Discover.package_of_file ~root:(Fpath.v "repos") 4 4 (Fpath.v "repos/mylib/lib/foo.ml") 5 5 in 6 - Alcotest.(check string) "package" "mylib" pkg 6 + Alcotest.(check string) "package" "repos" pkg 7 7 8 8 let test_package_of_file_deep () = 9 9 let pkg = 10 10 Dupfind.Discover.package_of_file ~root:(Fpath.v "/src") 11 11 (Fpath.v "/src/core/lib/sub/deep.ml") 12 12 in 13 - Alcotest.(check string) "package" "core" pkg 13 + Alcotest.(check string) "package" "src" pkg 14 14 15 15 let test_package_of_file_at_root () = 16 16 let pkg = 17 + Dupfind.Discover.package_of_file ~root:(Fpath.v ".") 18 + (Fpath.v "./ocaml-aos/lib/aos.ml") 19 + in 20 + Alcotest.(check string) "package" "ocaml-aos" pkg 21 + 22 + let test_package_of_file_flat () = 23 + let pkg = 17 24 Dupfind.Discover.package_of_file ~root:(Fpath.v ".") (Fpath.v "./foo.ml") 18 25 in 19 - Alcotest.(check string) "package" "." pkg 26 + Alcotest.(check string) "package" "unknown" pkg 20 27 21 28 let suite = 22 29 ( "discover", ··· 26 33 Alcotest.test_case "package_of_file deep" `Quick test_package_of_file_deep; 27 34 Alcotest.test_case "package_of_file at root" `Quick 28 35 test_package_of_file_at_root; 36 + Alcotest.test_case "package_of_file flat" `Quick test_package_of_file_flat; 29 37 ] )