Source file clerk_coverage.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
open Catala_utils
open Shared_ast
type coverage_line_result = {
total_reachable_lines : int;
total_reached_lines : int;
total_unreached_lines : int;
}
module LineMap = Map.Make (struct
include Int
let format fmt x = Format.pp_print_int fmt x
end)
let compute_coverage_per_line (coverage : Coverage.coverage_map) :
coverage_line_result =
let line_map =
Coverage.fold
(fun (file, { Coverage.start_line; end_line; _ }) coverage acc ->
let lines =
List.init (end_line - start_line + 1) (fun i -> start_line + i)
|> List.map (fun l -> l, coverage <> Coverage.Unreached)
|> LineMap.of_list
in
String.Map.update file
(function
| None -> Some lines
| Some l ->
Some
(LineMap.union
(fun _ l r ->
Some (l && r))
l lines))
acc)
coverage String.Map.empty
in
let empty =
{
total_reachable_lines = 0;
total_reached_lines = 0;
total_unreached_lines = 0;
}
in
String.Map.fold
(fun _ lines acc ->
LineMap.fold
(fun _ cov_bool
{
total_reachable_lines;
total_reached_lines;
total_unreached_lines;
} ->
let total_reached_lines, total_unreached_lines =
if cov_bool then succ total_reached_lines, total_unreached_lines
else total_reached_lines, succ total_unreached_lines
in
{
total_reachable_lines = succ total_reachable_lines;
total_reached_lines;
total_unreached_lines;
})
lines acc)
line_map empty
let itv_to_vscode_range (li, i) (lj, j) =
`Assoc
[
"start", `Assoc ["line", `Int (li - 1); "character", `Int (i - 1)];
"end", `Assoc ["line", `Int (lj - 1); "character", `Int (j - 1)];
]
let itv_to_range { Coverage.start_line; start_col; end_line; end_col } =
itv_to_vscode_range (start_line, start_col) (end_line, end_col)
let pos_to_json_range p =
let open Pos in
itv_to_vscode_range
(get_start_line p, get_start_column p)
(get_end_line p, get_end_column p)
let pfile_abs_path ~build_dir ~cwd file =
File.(cwd / remove_prefix build_dir file)
let pos_to_json_location ~build_dir ~cwd pos =
let file = Pos.get_file pos in
`Assoc
[
"file", `String (pfile_abs_path ~build_dir ~cwd file);
"range", pos_to_json_range pos;
]
let coverage_to_json ~build_dir ~cwd (coverage : Coverage.coverage_map) :
Yojson.t =
let itv_trees = Coverage.compute_interval_trees coverage in
let all_scopes =
Coverage.FileMap.fold
(fun _ tree acc -> Coverage.ScopeSet.union (Coverage.all_scopes tree) acc)
itv_trees Coverage.ScopeSet.empty
in
let scope_idx =
let l =
List.mapi (fun i s -> s, i) (Coverage.ScopeSet.elements all_scopes)
in
let scope_lookup s =
List.find_opt (fun (s', _) -> Coverage.ScopeSet.same_scope s s') l
|> Option.map snd
in
fun s ->
match scope_lookup s with
| None ->
Message.error "Cannot find index of scope %a in coverage tree"
ScopeName.format s
| Some i -> `Int i
in
let itv_tree_to_json (f, tree) =
let rec loop : Coverage.interval_tree -> [< `List of Yojson.t list ] =
let open Coverage in
function
| [] -> `List []
| { itv; cover; children } :: t ->
let reached_by =
match cover with
| Unreached -> []
| Reached_by { scopes } ->
Coverage.ScopeSet.elements scopes |> List.map scope_idx
in
let node : Yojson.t =
`Assoc
[
"range", itv_to_range itv;
"reached_by", `List reached_by;
"subtree", (loop children :> Yojson.t);
]
in
let (`List t) = loop t in
`List (node :: t)
in
`Assoc
[
"file", `String (pfile_abs_path ~build_dir ~cwd f);
"tree", (loop tree :> Yojson.t);
]
in
let scopes_index_json : string * Yojson.t =
( "scopes",
`List
(Coverage.ScopeSet.elements all_scopes
|> List.map (fun s ->
let pos = ScopeName.get_info s |> Mark.get in
`Assoc
[
"index", scope_idx s;
"name", `String (ScopeName.to_string s);
"location", pos_to_json_location ~build_dir ~cwd pos;
])) )
in
let coverage_locations_json =
( "locations",
`List (Coverage.FileMap.bindings itv_trees |> List.map itv_tree_to_json) )
in
`Assoc [scopes_index_json; coverage_locations_json]