• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ocaml / odoc / 3114

15 Apr 2026 03:39PM UTC coverage: 71.375% (-0.08%) from 71.459%
3114

Pull #1410

github

web-flow
Merge d0a2d4e0d into 58a0431b1
Pull Request #1410: OxCaml: Support for kind annotations

20 of 45 new or added lines in 2 files covered. (44.44%)

138 existing lines in 2 files now uncovered.

10425 of 14606 relevant lines covered (71.37%)

5766.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.87
/src/document/generator.ml
1
(*
2
 * Copyright (c) 2016 Thomas Refis <trefis@janestreet.com>
3
 *
4
 * Permission to use, copy, modify, and distribute this software for any
5
 * purpose with or without fee is hereby granted, provided that the above
6
 * copyright notice and this permission notice appear in all copies.
7
 *
8
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
 *)
16

17
open Odoc_utils
18
open Odoc_model.Names
19
module Location = Odoc_model.Location_
20
module Paths = Odoc_model.Paths
21
open Types
22
module O = Codefmt
23
open O.Infix
24

25
let tag tag t = O.span ~attr:tag t
13,407✔
26

27
let label t =
28
  match t with
3,263✔
29
  | Odoc_model.Lang.TypeExpr.Label s -> tag "label" (O.txt s)
574✔
30
  | Optional s | RawOptional s -> tag "optlabel" (O.txt "?" ++ O.txt s)
1✔
31

32
let type_var tv = tag "type-var" (O.txt tv)
9,487✔
33

34
let enclose ~l ~r x = O.span (O.txt l ++ x ++ O.txt r)
14,348✔
35

36
let resolved p content =
37
  let link = { Link.target = Internal (Resolved p); content; tooltip = None } in
159,791✔
38
  O.elt [ inline @@ Link link ]
159,791✔
39

40
let path p content = resolved (Url.from_path p) content
2,955✔
41

42
let unresolved content =
43
  let link = { Link.target = Internal Unresolved; content; tooltip = None } in
582✔
44
  O.elt [ inline @@ Link link ]
582✔
45

46
let path_to_id path =
47
  let url = Url.Anchor.from_identifier (path :> Paths.Identifier.t) in
7,529✔
48
  Some url
7,529✔
49

50
let source_anchor source_loc =
51
  match source_loc with
7,891✔
52
  | Some id ->
104✔
53
      Some
54
        (Url.Anchor.from_identifier
104✔
55
           (id : Paths.Identifier.SourceLocation.t :> Paths.Identifier.t))
56
  | _ -> None
7,787✔
57

58
let attach_expansion ?(status = `Default) (eq, o, e) page text =
1,602✔
59
  match page with
3,445✔
60
  | None -> O.documentedSrc text
701✔
61
  | Some (page : Page.t) ->
2,744✔
62
      let url = page.url in
63
      let summary = O.render text in
64
      let expansion =
2,744✔
65
        O.documentedSrc (O.txt eq ++ O.keyword o)
2,744✔
66
        @ DocumentedSrc.[ Subpage { status; content = page } ]
67
        @ O.documentedSrc (O.keyword e)
2,744✔
68
      in
69
      DocumentedSrc.
70
        [ Alternative (Expansion { summary; url; status; expansion }) ]
71

72
let mk_heading ?(level = 1) ?label text =
366✔
73
  let title = [ inline @@ Text text ] in
366✔
74
  Item.Heading { label; level; title; source_anchor = None }
75

76
(** Returns the preamble as an item. Stop the preamble at the first heading. The
77
    rest is inserted into [items]. *)
78
let prepare_preamble comment items =
79
  let preamble, first_comment =
3,314✔
80
    List.split_at
81
      ~f:(function
82
        | { Odoc_model.Location_.value = `Heading _; _ } -> true | _ -> false)
32✔
83
      comment
84
  in
85
  (Comment.standalone preamble, Comment.standalone first_comment @ items)
3,314✔
86

87
let make_expansion_page ~source_anchor url comments items =
88
  let comment = List.concat comments in
3,314✔
89
  let preamble, items = prepare_preamble comment items in
3,314✔
90
  { Page.preamble; items; url; source_anchor }
3,314✔
91

92
include Generator_signatures
93

94
module Make (Syntax : SYNTAX) = struct
95
  module Link : sig
96
    val from_path : Paths.Path.t -> text
97

98
    val from_fragment : Paths.Fragment.leaf -> text
99
  end = struct
100
    open Paths
101

102
    let rec from_path : Path.t -> text =
103
     fun path ->
104
      match path with
181,953✔
105
      | `Identifier (id, _) ->
245✔
106
          unresolved [ inline @@ Text (Identifier.name id) ]
245✔
UNCOV
107
      | `Substituted m -> from_path (m :> Path.t)
×
UNCOV
108
      | `SubstitutedMT m -> from_path (m :> Path.t)
×
109
      | `SubstitutedT m -> from_path (m :> Path.t)
×
110
      | `SubstitutedCT m -> from_path (m :> Path.t)
×
111
      | `Root root -> unresolved [ inline @@ Text (ModuleName.to_string root) ]
305✔
112
      | `Forward root -> unresolved [ inline @@ Text root ] (* FIXME *)
×
113
      | `Dot (prefix, suffix) ->
305✔
114
          let link = from_path (prefix :> Path.t) in
115
          link ++ O.txt ("." ^ ModuleName.to_string suffix)
305✔
116
      | `DotT (prefix, suffix) ->
297✔
117
          let link = from_path (prefix :> Path.t) in
118
          link ++ O.txt ("." ^ TypeName.to_string suffix)
297✔
UNCOV
119
      | `DotMT (prefix, suffix) ->
×
120
          let link = from_path (prefix :> Path.t) in
121
          link ++ O.txt ("." ^ ModuleTypeName.to_string suffix)
×
UNCOV
122
      | `DotV (prefix, suffix) ->
×
123
          let link = from_path (prefix :> Path.t) in
124
          link ++ O.txt ("." ^ ValueName.to_string suffix)
×
UNCOV
125
      | `Apply (p1, p2) ->
×
126
          let link1 = from_path (p1 :> Path.t) in
127
          let link2 = from_path (p2 :> Path.t) in
×
UNCOV
128
          link1 ++ O.txt "(" ++ link2 ++ O.txt ")"
×
129
      | `Resolved _ when Paths.Path.is_hidden path ->
180,801✔
130
          let txt = Url.render_path path in
32✔
131
          unresolved [ inline @@ Text txt ]
32✔
132
      | `Resolved rp -> (
180,769✔
133
          (* If the path is pointing to an opaque module or module type
134
             there won't be a page generated - so we stop before; at
135
             the parent page, and link instead to the anchor representing
136
             the declaration of the opaque module(_type) *)
137
          let stop_before =
138
            match rp with
139
            | `OpaqueModule _ | `OpaqueModuleType _ -> true
8✔
140
            | _ -> false
180,561✔
141
          in
142
          let txt = [ inline @@ Text (Url.render_path path) ] in
180,769✔
143
          match Paths.Path.Resolved.identifier rp with
144
          | Some id ->
156,330✔
145
              let href = Url.from_identifier ~stop_before id in
146
              resolved href txt
156,330✔
147
          | None -> O.elt txt)
24,439✔
148

149
    let dot prefix suffix = prefix ^ "." ^ suffix
49✔
150

151
    let rec render_fragment_any : Fragment.t -> string =
152
     fun fragment ->
UNCOV
153
      match fragment with
×
UNCOV
154
      | `Resolved rr -> render_resolved_fragment rr
×
155
      | `Dot (`Root, suffix) -> suffix
×
156
      | `Dot (prefix, suffix) ->
×
157
          dot (render_fragment_any (prefix :> Fragment.t)) suffix
×
158
      | `Root -> assert false
159

160
    and render_resolved_fragment : Fragment.Resolved.t -> string =
161
      let open Fragment.Resolved in
162
      fun fragment ->
163
        match fragment with
376✔
164
        | `Root _ -> assert false
165
        | `Subst (_, rr) -> render_resolved_fragment (rr :> t)
8✔
166
        | `Alias (_, rr) -> render_resolved_fragment (rr :> t)
24✔
167
        | `Module (`Root _, s) -> ModuleName.to_string s
121✔
168
        | `Module_type (`Root _, s) -> ModuleTypeName.to_string s
32✔
169
        | `Type (`Root _, s) -> TypeName.to_string s
142✔
UNCOV
170
        | `Class (`Root _, s) -> TypeName.to_string s
×
UNCOV
171
        | `ClassType (`Root _, s) -> TypeName.to_string s
×
172
        | `Module (rr, s) ->
8✔
173
            dot (render_resolved_fragment (rr :> t)) (ModuleName.to_string s)
8✔
174
        | `Module_type (rr, s) ->
16✔
175
            dot
176
              (render_resolved_fragment (rr :> t))
16✔
177
              (ModuleTypeName.to_string s)
16✔
178
        | `Type (rr, s) ->
25✔
179
            dot (render_resolved_fragment (rr :> t)) (TypeName.to_string s)
25✔
UNCOV
180
        | `Class (rr, s) ->
×
UNCOV
181
            dot (render_resolved_fragment (rr :> t)) (TypeName.to_string s)
×
182
        | `ClassType (rr, s) ->
×
183
            dot (render_resolved_fragment (rr :> t)) (TypeName.to_string s)
×
184
        | `OpaqueModule r -> render_resolved_fragment (r :> t)
×
185

186
    let resolved_fragment_to_ir : Fragment.Resolved.leaf -> text =
187
     fun fragment ->
188
      let open Fragment in
295✔
189
      let id = Resolved.identifier (fragment :> Resolved.t) in
190
      let txt = render_resolved_fragment (fragment :> Resolved.t) in
295✔
191
      match id with
295✔
192
      | Some id ->
295✔
193
          let href = Url.from_identifier ~stop_before:false id in
194
          resolved href [ inline @@ Text txt ]
295✔
UNCOV
195
      | None -> unresolved [ inline @@ Text txt ]
×
196

197
    let from_fragment : Fragment.leaf -> text = function
198
      | `Resolved r
295✔
199
        when not (Fragment.Resolved.is_hidden (r :> Fragment.Resolved.t)) ->
295✔
200
          resolved_fragment_to_ir r
295✔
UNCOV
201
      | f ->
×
202
          let txt = render_fragment_any (f :> Fragment.t) in
203
          unresolved [ inline @@ Text txt ]
×
204
  end
205

206
  module Impl = struct
207
    let impl ~infos src =
208
      let l =
28✔
209
        infos
210
        |> List.sort (fun (_, (l1, e1)) (_, (l2, e2)) ->
211
               if l1 = l2 then compare e2 e1
133✔
212
                 (* If two intervals open at the same time, we open
213
                    first the one that closes last *)
214
               else compare l1 l2)
3,166✔
215
      in
216
      let get_src a b =
28✔
217
        let in_bound x = min (max x 0) (String.length src) in
1,696✔
218
        let a = in_bound a and b = in_bound b in
1,696✔
219
        let a, b = (min a b, max a b) in
1,696✔
220
        String.with_range src ~first:a ~len:(b - a)
221
      in
222
      let plain_code = function
223
        | "" -> []
596✔
224
        | s -> [ Types.Source_page.Plain_code s ]
1,100✔
225
      in
226
      let min (a : int) b = if a < b then a else b in
161✔
227
      let rec extract from to_ list aux =
228
        match list with
1,696✔
229
        | (k, (loc_start, loc_end)) :: q when loc_start < to_ ->
1,640✔
230
            let loc_end = min loc_end to_ in
834✔
231
            (* In case of inconsistent [a  [b    a] b]
232
               we do                   [a  [b  b]a] *)
233
            let initial = plain_code (get_src from loc_start) in
834✔
234
            let next, q = extract loc_start loc_end q [] in
834✔
235
            extract loc_end to_ q
834✔
236
              ([ Types.Source_page.Tagged_code (k, List.rev next) ]
834✔
237
              @ initial @ aux)
238
        | q -> (plain_code (get_src from to_) @ aux, q)
862✔
239
      in
240
      let doc, _ = extract 0 (String.length src) l [] in
28✔
241
      List.rev doc
28✔
242
  end
243

244
  module Source_page : sig
245
    val source :
246
      Paths.Identifier.SourcePage.t ->
247
      Syntax_highlighter.infos ->
248
      Lang.Source_info.t ->
249
      string ->
250
      Source_page.t
251
  end = struct
252
    let path id = Url.Path.from_identifier id
28✔
253

254
    let to_link { Lang.Source_info.documentation; implementation } =
255
      let documentation =
34✔
256
        (* Since documentation link are not rendered, we comment the code to
257
           extract the href, and always output [None] *)
258
        ignore documentation;
259
        None
260
        (* let open Paths.Path.Resolved in *)
261
        (* match documentation with *)
262
        (* | Some (`Resolved p) when not (is_hidden (p :> t)) -> ( *)
263
        (*     let id = identifier (p :> t) in *)
264
        (*     match Url.from_identifier ~stop_before:false id with *)
265
        (*     | Ok link -> Some link *)
266
        (*     | _ -> None) *)
267
        (* | _ -> None *)
268
      in
269
      let implementation =
270
        match implementation with
271
        | Some (Odoc_model.Lang.Source_info.Resolved id) ->
15✔
272
            Some (Url.Anchor.from_identifier (id :> Paths.Identifier.t))
15✔
273
        | _ -> None
19✔
274
      in
275
      Some (Source_page.Link { implementation; documentation })
276

277
    let info_of_info : Lang.Source_info.annotation -> Source_page.info option =
278
      function
279
      | Definition id -> (
99✔
280
          match id.iv with
281
          | `SourceLocation (_, def) -> Some (Anchor (DefName.to_string def))
95✔
282
          | `SourceLocationInternal (_, local) ->
4✔
283
              Some (Anchor (LocalName.to_string local))
4✔
UNCOV
284
          | _ -> None)
×
285
      | Module v -> to_link v
11✔
286
      | ModuleType v -> to_link v
1✔
287
      | Type v -> to_link v
7✔
288
      | Value v -> to_link v
15✔
289

290
    let source id syntax_info infos source_code =
291
      let url = path id in
28✔
292
      let mapper (info, loc) =
28✔
UNCOV
293
        match info_of_info info with Some x -> Some (x, loc) | None -> None
×
294
      in
295
      let infos = Odoc_utils.List.filter_map mapper infos in
296
      let syntax_info =
28✔
297
        List.rev_map (fun (ty, loc) -> (Source_page.Syntax ty, loc)) syntax_info
701✔
298
        |> List.rev
28✔
299
      in
300
      let contents = Impl.impl ~infos:(infos @ syntax_info) source_code in
28✔
301
      { Source_page.url; contents }
28✔
302
  end
303

304
  module Type_expression : sig
305
    val type_expr : ?needs_parentheses:bool -> Lang.TypeExpr.t -> text
306

307
    val format_type_path :
308
      delim:[ `parens | `brackets ] -> Lang.TypeExpr.t list -> text -> text
309

310
    val kind_annotation : Odoc_model.Lang.KindAnnotation.t -> text
311

312
    val with_kind_annotation : Odoc_model.Lang.KindAnnotation.t -> text -> text
313
  end = struct
314
    let rec te_variant (t : Odoc_model.Lang.TypeExpr.Polymorphic_variant.t) =
315
      let style_arguments ~constant arguments =
75,143✔
316
        (* Multiple arguments in a polymorphic variant constructor correspond
317
           to a conjunction of types, not a product: [`Lbl int&float].
318
           If constant is [true], the conjunction starts with an empty type,
319
           for instance [`Lbl &int].
320
        *)
321
        let wrapped_type_expr =
1,873✔
322
          (* type conjunction in Reason is printed as `Lbl (t1)&(t2)` *)
UNCOV
323
          if Syntax.Type.Variant.parenthesize_params then fun x ->
×
UNCOV
324
            enclose ~l:"(" ~r:")" (type_expr x)
×
325
          else fun x -> type_expr x
1,873✔
326
        in
327
        let arguments =
328
          O.list arguments ~sep:(O.txt " & ") ~f:wrapped_type_expr
1,873✔
329
        in
UNCOV
330
        if constant then O.txt "& " ++ arguments else arguments
×
331
      in
332
      let rec style_elements ~add_pipe = function
333
        | [] -> O.noop
75,143✔
334
        | first :: rest ->
108,795✔
335
            let first =
336
              match first with
337
              | Odoc_model.Lang.TypeExpr.Polymorphic_variant.Type te ->
40,832✔
338
                  let res = O.box_hv @@ type_expr te in
40,832✔
339
                  if add_pipe then O.sp ++ O.span (O.txt "| " ++ res) else res
2,565✔
340
              | Constructor { constant; name; arguments; _ } ->
67,963✔
341
                  let constr =
342
                    let name = "`" ^ name in
343
                    if add_pipe then O.span (O.txt ("| " ^ name))
31,095✔
344
                    else O.txt name
36,868✔
345
                  in
346
                  let res =
347
                    O.box_hv
348
                      (match arguments with
349
                      | [] -> constr
66,090✔
350
                      | _ ->
1,873✔
351
                          let arguments = style_arguments ~constant arguments in
352
                          O.span
1,873✔
353
                            (if Syntax.Type.Variant.parenthesize_params then
UNCOV
354
                               constr ++ arguments
×
355
                             else constr ++ O.txt " of" ++ O.sp ++ arguments))
1,873✔
356
                  in
357
                  if add_pipe then O.sp ++ res else res
31,095✔
358
            in
359
            first ++ style_elements ~add_pipe:true rest
108,795✔
360
      in
361
      let elements = style_elements ~add_pipe:false t.elements in
362
      O.box_hv_no_indent
75,143✔
363
      @@ O.span
75,143✔
364
           (match t.kind with
365
           | Fixed -> O.txt "[ " ++ elements ++ O.txt " ]"
7,068✔
366
           | Open -> O.txt "[> " ++ elements ++ O.txt " ]"
40,908✔
367
           | Closed [] -> O.txt "[< " ++ elements ++ O.txt " ]"
27,132✔
368
           | Closed lst ->
35✔
369
               let constrs = String.concat ~sep:" " lst in
370
               O.txt "[< " ++ elements ++ O.txt (" " ^ constrs ^ " ]"))
35✔
371

372
    and te_object (t : Odoc_model.Lang.TypeExpr.Object.t) =
373
      let fields =
80✔
374
        O.list
375
          ~sep:(O.sp ++ O.txt Syntax.Obj.field_separator)
80✔
376
          t.fields
377
          ~f:(function
378
            | Odoc_model.Lang.TypeExpr.Object.Method { name; type_ } ->
120✔
379
                O.box_hv_no_indent
380
                @@ O.txt (name ^ Syntax.Type.annotation_separator)
120✔
381
                   ++ O.cut ++ type_expr type_
120✔
UNCOV
382
            | Inherit type_ -> O.box_hv_no_indent @@ type_expr type_)
×
383
      in
384
      let open_tag =
80✔
385
        if t.open_ then O.txt Syntax.Obj.open_tag_extendable
32✔
386
        else O.txt Syntax.Obj.open_tag_closed
48✔
387
      in
388
      let close_tag =
389
        if t.open_ then O.txt Syntax.Obj.close_tag_extendable
32✔
390
        else O.txt Syntax.Obj.close_tag_closed
48✔
391
      in
392
      O.span (open_tag ++ fields ++ close_tag)
80✔
393

394
    and format_type_path ~delim (params : Odoc_model.Lang.TypeExpr.t list)
395
        (path : text) : text =
396
      O.box_hv
179,533✔
397
      @@
398
      match params with
399
      | [] -> path
94,880✔
400
      | [ param ] ->
71,291✔
401
          let param = type_expr ~needs_parentheses:true param in
402
          let args =
71,291✔
403
            if Syntax.Type.parenthesize_constructor then
UNCOV
404
              O.txt "(" ++ param ++ O.txt ")"
×
405
            else param
71,291✔
406
          in
407
          Syntax.Type.handle_constructor_params path args
71,291✔
408
      | params ->
13,362✔
409
          let params = O.list params ~sep:(O.txt "," ++ O.sp) ~f:type_expr in
13,362✔
410
          let params =
13,362✔
411
            match delim with
412
            | `parens -> enclose ~l:"(" params ~r:")"
13,362✔
UNCOV
413
            | `brackets -> enclose ~l:"[" params ~r:"]"
×
414
          in
415
          Syntax.Type.handle_constructor_params path (O.box_hv params)
13,362✔
416

UNCOV
417
    and tuple ?(needs_parentheses = false) ~boxed lst =
×
418
      let opt_label = function
677✔
419
        | None -> O.noop
1,544✔
420
        | Some lbl -> tag "label" (O.txt lbl) ++ O.txt ":" ++ O.cut
×
421
      in
422
      let res =
423
        O.box_hv_no_indent
424
          (O.list lst ~sep:Syntax.Type.Tuple.element_separator
677✔
425
             ~f:(fun (lbl, typ) ->
426
               opt_label lbl ++ type_expr ~needs_parentheses:true typ))
1,544✔
427
      in
UNCOV
428
      let lparen = if boxed then "(" else "#(" in
×
UNCOV
429
      if Syntax.Type.Tuple.always_parenthesize || needs_parentheses || not boxed
×
430
      then enclose ~l:lparen res ~r:")"
131✔
431
      else res
546✔
432

433
    and kind_annotation (k : Odoc_model.Lang.KindAnnotation.t) =
NEW
UNCOV
434
      match k with
×
NEW
UNCOV
435
      | Default -> O.noop
×
NEW
UNCOV
436
      | Abbreviation s -> O.txt s
×
NEW
437
      | Mod (base, modes) ->
×
NEW
438
          kind_annotation base ++ O.txt " " ++ O.keyword "mod"
×
NEW
439
          ++ O.txt (" " ^ String.concat ~sep:" " modes)
×
NEW
440
      | With (base, ty, modalities) -> (
×
NEW
441
          kind_annotation base ++ O.txt " " ++ O.keyword "with" ++ O.txt " "
×
NEW
442
          ++ type_expr ty
×
443
          ++
444
          match modalities with
NEW
445
          | [] -> O.noop
×
NEW
446
          | mods ->
×
NEW
447
              O.txt " " ++ O.keyword "mod"
×
NEW
448
              ++ O.txt (" " ^ String.concat ~sep:" " mods))
×
NEW
449
      | Kind_of ty -> O.keyword "kind_of_" ++ O.txt " " ++ type_expr ty
×
NEW
450
      | Product ks -> O.list ks ~sep:(O.txt " & ") ~f:kind_annotation
×
451

452
    and with_kind_annotation kind base =
453
      match kind with
648✔
454
      | Odoc_model.Lang.KindAnnotation.Default -> base
648✔
NEW
455
      | k -> O.txt "(" ++ base ++ O.txt " : " ++ kind_annotation k ++ O.txt ")"
×
456

457
    and type_expr ?(needs_parentheses = false) (t : Odoc_model.Lang.TypeExpr.t)
191,635✔
458
        =
459
      let enclose_parens_if_needed res =
312,108✔
460
        if needs_parentheses then enclose ~l:"(" res ~r:")" else res
831✔
461
      in
462
      match t with
463
      | Var s -> type_var (Syntax.Type.var_prefix ^ s)
9,469✔
464
      | Any -> type_var Syntax.Type.any
18✔
465
      | Alias (te, alias) ->
133✔
466
          enclose_parens_if_needed
467
            (type_expr ~needs_parentheses:true te
133✔
468
            ++ O.txt " " ++ O.keyword "as" ++ O.txt " '" ++ O.txt alias)
133✔
469
      | Arrow (None, src, dst) ->
43,802✔
470
          let res =
471
            O.span
43,802✔
472
              ((O.box_hv @@ type_expr ~needs_parentheses:true src)
43,802✔
473
              ++ O.txt " " ++ Syntax.Type.arrow)
43,802✔
474
            ++ O.sp ++ type_expr dst
43,802✔
475
            (* ++ O.end_hv *)
476
          in
477
          enclose_parens_if_needed res
43,802✔
478
      | Arrow (Some (RawOptional _ as lbl), _src, dst) ->
1✔
479
          let res =
480
            O.span
1✔
481
              (O.box_hv
1✔
482
              @@ label lbl ++ O.txt ":"
1✔
483
                 ++ tag "error" (O.txt "???")
1✔
484
                 ++ O.txt " " ++ Syntax.Type.arrow)
1✔
485
            ++ O.sp ++ type_expr dst
1✔
486
          in
487
          enclose_parens_if_needed res
1✔
488
      | Arrow (Some lbl, src, dst) ->
3,262✔
489
          let res =
490
            O.span
3,262✔
491
              ((O.box_hv
3,262✔
492
               @@ label lbl ++ O.txt ":" ++ O.cut
3,262✔
493
                  ++ (O.box_hv @@ type_expr ~needs_parentheses:true src))
3,262✔
494
              ++ O.txt " " ++ Syntax.Type.arrow)
3,262✔
495
            ++ O.sp ++ type_expr dst
3,262✔
496
          in
497
          enclose_parens_if_needed res
3,262✔
498
      | Tuple lst -> tuple ~needs_parentheses ~boxed:true lst
677✔
UNCOV
499
      | Unboxed_tuple lst -> tuple ~needs_parentheses ~boxed:false lst
×
500
      | Constr (path, args) ->
179,467✔
501
          let link = Link.from_path (path :> Paths.Path.t) in
502
          format_type_path ~delim:`parens args link
179,467✔
503
      | Polymorphic_variant v -> te_variant v
75,143✔
504
      | Object o -> te_object o
80✔
505
      | Class (path, args) ->
8✔
506
          format_type_path ~delim:`brackets args
507
            (Link.from_path (path :> Paths.Path.t))
8✔
508
      | Poly (polyvars, t) ->
24✔
509
          let format_poly_var (name, kind) =
510
            with_kind_annotation kind (O.txt ("'" ^ name))
24✔
511
          in
512
          let vars = O.list polyvars ~sep:(O.txt " ") ~f:format_poly_var in
24✔
513
          enclose_parens_if_needed @@ (vars ++ O.txt ". " ++ type_expr t)
24✔
UNCOV
514
      | Quote t -> O.span (O.txt "<[ " ++ O.box_hv (type_expr t) ++ O.txt " ]>")
×
UNCOV
515
      | Splice t -> O.span (O.txt "$" ++ type_expr ~needs_parentheses:true t)
×
516
      | Package pkg ->
24✔
517
          enclose ~l:"(" ~r:")"
518
            (O.keyword "module" ++ O.txt " "
24✔
519
            ++ Link.from_path (pkg.path :> Paths.Path.t)
24✔
520
            ++
24✔
521
            match pkg.substitutions with
522
            | [] -> O.noop
16✔
523
            | fst :: lst ->
8✔
524
                O.sp
525
                ++ O.box_hv (O.keyword "with" ++ O.txt " " ++ package_subst fst)
8✔
526
                ++ O.list lst ~f:(fun s ->
8✔
527
                       O.cut
8✔
528
                       ++ (O.box_hv
8✔
529
                          @@ O.txt " " ++ O.keyword "and" ++ O.txt " "
8✔
530
                             ++ package_subst s)))
8✔
531

532
    and package_subst
533
        ((frag_typ, te) : Paths.Fragment.Type.t * Odoc_model.Lang.TypeExpr.t) :
534
        text =
535
      let typath = Link.from_fragment (frag_typ :> Paths.Fragment.leaf) in
16✔
536
      O.keyword "type" ++ O.txt " " ++ typath ++ O.txt " =" ++ O.sp
16✔
537
      ++ type_expr te
16✔
538
  end
539

540
  open Type_expression
541

542
  (* Also handles constructor declarations for exceptions and extensible
543
     variants, and exposes a few helpers used in formatting classes and signature
544
     constraints. *)
545
  module Type_declaration : sig
546
    val type_decl :
547
      ?is_substitution:bool ->
548
      Lang.Signature.recursive * Lang.TypeDecl.t ->
549
      Item.t
550

551
    val extension : Lang.Extension.t -> Item.t
552

553
    val record : Lang.TypeDecl.Field.t list -> DocumentedSrc.one list
554

555
    val unboxed_record :
556
      Lang.TypeDecl.UnboxedField.t list -> DocumentedSrc.one list
557

558
    val exn : Lang.Exception.t -> Item.t
559

560
    val format_params :
561
      ?delim:[ `parens | `brackets ] -> Lang.TypeDecl.param list -> text
562

563
    val format_manifest :
564
      ?is_substitution:bool ->
565
      ?compact_variants:bool ->
566
      Lang.TypeDecl.Equation.t ->
567
      text * bool
568

569
    val format_constraints : (Lang.TypeExpr.t * Lang.TypeExpr.t) list -> text
570
  end = struct
571
    let record fields =
572
      let field mutable_ id typ =
86✔
573
        let url = Url.from_identifier ~stop_before:true id in
155✔
574
        let name = Paths.Identifier.name id in
155✔
575
        let attrs = [ "def"; "record"; Url.Anchor.string_of_kind url.kind ] in
155✔
576
        let cell =
577
          (* O.td ~a:[ O.a_class ["def"; kind ] ]
578
           *   [O.a ~a:[O.a_href ("#" ^ anchor); O.a_class ["anchor"]] []
579
           *   ; *)
580
          O.code
581
            ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
24✔
582
            ++ O.txt name
155✔
583
            ++ O.txt Syntax.Type.annotation_separator
155✔
584
            ++ type_expr typ
155✔
585
            ++ O.txt Syntax.Type.Record.field_separator)
155✔
586
          (* ] *)
587
        in
588
        (url, attrs, cell)
155✔
589
      in
590
      let rows =
591
        fields
592
        |> List.map (fun fld ->
593
               let open Odoc_model.Lang.TypeDecl.Field in
155✔
594
               let url, attrs, code =
595
                 field fld.mutable_ (fld.id :> Paths.Identifier.t) fld.type_
596
               in
597
               let anchor = Some url in
155✔
598
               let doc = fld.doc.elements in
599
               let rhs = Comment.to_ir doc in
600
               let doc = if not (Comment.has_doc doc) then [] else rhs in
64✔
601
               let markers = Syntax.Comment.markers in
602
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
603
      in
604
      let content =
86✔
605
        O.documentedSrc (O.txt "{") @ rows @ O.documentedSrc (O.txt "}")
86✔
606
      in
607
      content
608

609
    let unboxed_record fields =
UNCOV
610
      let field mutable_ id typ =
×
UNCOV
611
        let url = Url.from_identifier ~stop_before:true id in
×
UNCOV
612
        let name = Paths.Identifier.name id in
×
UNCOV
613
        let attrs = [ "def"; "record"; Url.Anchor.string_of_kind url.kind ] in
×
614
        let cell =
615
          (* O.td ~a:[ O.a_class ["def"; kind ] ]
616
           *   [O.a ~a:[O.a_href ("#" ^ anchor); O.a_class ["anchor"]] []
617
           *   ; *)
618
          O.code
UNCOV
619
            ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
×
UNCOV
620
            ++ O.txt name
×
UNCOV
621
            ++ O.txt Syntax.Type.annotation_separator
×
UNCOV
622
            ++ type_expr typ
×
UNCOV
623
            ++ O.txt Syntax.Type.Record.field_separator)
×
624
          (* ] *)
625
        in
UNCOV
626
        (url, attrs, cell)
×
627
      in
628
      let rows =
629
        fields
630
        |> List.map (fun fld ->
631
               let open Odoc_model.Lang.TypeDecl.UnboxedField in
×
632
               let url, attrs, code =
633
                 field fld.mutable_ (fld.id :> Paths.Identifier.t) fld.type_
634
               in
UNCOV
635
               let anchor = Some url in
×
636
               let doc = fld.doc.elements in
637
               let rhs = Comment.to_ir doc in
UNCOV
638
               let doc = if not (Comment.has_doc doc) then [] else rhs in
×
639
               let markers = Syntax.Comment.markers in
640
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
641
      in
642
      let content =
×
643
        O.documentedSrc (O.txt "#{") @ rows @ O.documentedSrc (O.txt "}")
×
644
      in
645
      content
646

647
    let constructor :
648
        Paths.Identifier.t ->
649
        Odoc_model.Lang.TypeDecl.Constructor.argument ->
650
        Odoc_model.Lang.TypeExpr.t option ->
651
        DocumentedSrc.t =
652
     fun id args ret_type ->
653
      let name = Paths.Identifier.name id in
656✔
654
      let kind = Url.(kind id |> Anchor.string_of_kind) in
656✔
655
      let cstr = tag kind (O.txt name) in
656✔
656
      let is_gadt, ret_type =
656✔
657
        match ret_type with
658
        | None -> (false, O.noop)
520✔
659
        | Some te ->
136✔
660
            let constant = match args with Tuple [] -> true | _ -> false in
48✔
661
            let ret_type =
662
              O.txt " "
136✔
663
              ++ (if constant then O.txt ":" else Syntax.Type.GADT.arrow)
48✔
664
              ++ O.txt " " ++ type_expr te
136✔
665
            in
666
            (true, ret_type)
136✔
667
      in
668
      match args with
669
      | Tuple [] -> O.documentedSrc (cstr ++ ret_type)
289✔
670
      | Tuple lst ->
350✔
671
          let params =
672
            O.list lst ~sep:Syntax.Type.Tuple.element_separator
673
              ~f:(type_expr ~needs_parentheses:is_gadt)
674
          in
675
          O.documentedSrc
350✔
676
            (cstr
677
            ++ (if Syntax.Type.Variant.parenthesize_params then
350✔
UNCOV
678
                  O.txt "(" ++ params ++ O.txt ")"
×
679
                else
680
                  (if is_gadt then O.txt Syntax.Type.annotation_separator
88✔
681
                   else O.txt " " ++ O.keyword "of" ++ O.txt " ")
262✔
682
                  ++ params)
350✔
683
            ++ ret_type)
350✔
684
      | Record fields ->
17✔
685
          if is_gadt then
UNCOV
686
            O.documentedSrc (cstr ++ O.txt Syntax.Type.annotation_separator)
×
UNCOV
687
            @ record fields @ O.documentedSrc ret_type
×
688
          else
689
            O.documentedSrc (cstr ++ O.txt " " ++ O.keyword "of" ++ O.txt " ")
17✔
690
            @ record fields
17✔
691

692
    let variant cstrs : DocumentedSrc.t =
693
      let constructor id args res =
235✔
694
        let url = Url.from_identifier ~stop_before:true id in
436✔
695
        let attrs = [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ] in
436✔
696
        let content =
697
          let doc = constructor id args res in
698
          O.documentedSrc (O.txt "| ") @ doc
436✔
699
        in
700
        (url, attrs, content)
701
      in
702
      match cstrs with
UNCOV
703
      | [] -> O.documentedSrc (O.txt "|")
×
704
      | _ :: _ ->
235✔
705
          let rows =
706
            cstrs
707
            |> List.map (fun cstr ->
708
                   let open Odoc_model.Lang.TypeDecl.Constructor in
436✔
709
                   let url, attrs, code =
710
                     constructor
711
                       (cstr.id :> Paths.Identifier.t)
712
                       cstr.args cstr.res
713
                   in
714
                   let anchor = Some url in
436✔
715
                   let doc = cstr.doc.elements in
716
                   let rhs = Comment.to_ir doc in
717
                   let doc = if not (Comment.has_doc doc) then [] else rhs in
73✔
718
                   let markers = Syntax.Comment.markers in
719
                   DocumentedSrc.Nested { anchor; attrs; code; doc; markers })
720
          in
721
          rows
235✔
722

723
    let extension_constructor (t : Odoc_model.Lang.Extension.Constructor.t) =
724
      let id = (t.id :> Paths.Identifier.t) in
154✔
725
      let url = Url.from_identifier ~stop_before:true id in
726
      let anchor = Some url in
154✔
727
      let attrs = [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ] in
154✔
728
      let code = O.documentedSrc (O.txt "| ") @ constructor id t.args t.res in
154✔
729
      let doc = Comment.to_ir t.doc.elements in
730
      let markers = Syntax.Comment.markers in
154✔
731
      DocumentedSrc.Nested { anchor; attrs; code; doc; markers }
732

733
    let extension (t : Odoc_model.Lang.Extension.t) =
734
      let prefix =
126✔
735
        O.keyword "type" ++ O.txt " "
126✔
736
        ++ Link.from_path (t.type_path :> Paths.Path.t)
126✔
737
        ++ O.txt " +=" ++ O.sp
126✔
738
        ++
739
        if t.private_ then O.keyword Syntax.Type.private_keyword ++ O.sp
8✔
740
        else O.noop
118✔
741
      in
742
      let content =
126✔
743
        O.documentedSrc prefix
126✔
744
        @ List.map extension_constructor t.constructors
126✔
745
        @ O.documentedSrc
126✔
UNCOV
746
            (if Syntax.Type.type_def_semicolon then O.txt ";" else O.noop)
×
747
      in
748
      let attr = [ "type"; "extension" ] in
749
      let anchor = Some (Url.Anchor.extension_decl t) in
126✔
750
      let doc = Comment.to_ir t.doc.elements in
751
      let source_anchor =
126✔
752
        (* Take the anchor from the first constructor only for consistency with
753
           regular variants. *)
754
        match t.constructors with
755
        | hd :: _ -> source_anchor hd.source_loc
126✔
UNCOV
756
        | [] -> None
×
757
      in
758
      Item.Declaration { attr; anchor; doc; content; source_anchor }
759

760
    let exn (t : Odoc_model.Lang.Exception.t) =
761
      let cstr = constructor (t.id :> Paths.Identifier.t) t.args t.res in
66✔
762
      let content =
66✔
763
        O.documentedSrc (O.keyword "exception" ++ O.txt " ")
66✔
764
        @ cstr
765
        @ O.documentedSrc
66✔
766
            (if Syntax.Type.Exception.semicolon then O.txt ";" else O.noop)
×
767
      in
768
      let attr = [ "exception" ] in
769
      let anchor = path_to_id t.id in
770
      let doc = Comment.to_ir t.doc.elements in
66✔
771
      let source_anchor = source_anchor t.source_loc in
66✔
772
      Item.Declaration { attr; anchor; doc; content; source_anchor }
66✔
773

774
    let polymorphic_variant ~type_ident
775
        (t : Odoc_model.Lang.TypeExpr.Polymorphic_variant.t) =
776
      let row item =
74✔
777
        let kind_approx, cstr, doc =
156✔
778
          match item with
779
          | Odoc_model.Lang.TypeExpr.Polymorphic_variant.Type te ->
24✔
780
              ("unknown", O.documentedSrc (type_expr te), None)
24✔
781
          | Constructor { constant; name; arguments; doc; _ } -> (
132✔
782
              let cstr = "`" ^ name in
783
              ( "constructor",
784
                (match arguments with
785
                | [] -> O.documentedSrc (O.txt cstr)
58✔
786
                | _ ->
74✔
787
                    (* Multiple arguments in a polymorphic variant constructor correspond
788
                       to a conjunction of types, not a product: [`Lbl int&float].
789
                       If constant is [true], the conjunction starts with an empty type,
790
                       for instance [`Lbl &int].
791
                    *)
792
                    let wrapped_type_expr =
793
                      (* type conjunction in Reason is printed as `Lbl (t1)&(t2)` *)
UNCOV
794
                      if Syntax.Type.Variant.parenthesize_params then fun x ->
×
UNCOV
795
                        O.txt "(" ++ type_expr x ++ O.txt ")"
×
796
                      else fun x -> type_expr x
74✔
797
                    in
798
                    let params =
799
                      O.box_hv
800
                      @@ O.list arguments
74✔
801
                           ~sep:(O.txt " &" ++ O.sp)
74✔
802
                           ~f:wrapped_type_expr
803
                    in
804
                    let params =
74✔
UNCOV
805
                      if constant then O.txt "& " ++ params else params
×
806
                    in
807
                    O.documentedSrc
74✔
808
                      (O.txt cstr
74✔
809
                      ++
74✔
UNCOV
810
                      if Syntax.Type.Variant.parenthesize_params then params
×
811
                      else O.txt " " ++ O.keyword "of" ++ O.sp ++ params)),
74✔
812
                match doc with
813
                | { elements = []; _ } -> None
132✔
814
                | _ -> Some (Comment.to_ir doc.elements) ))
×
815
        in
816
        let markers = Syntax.Comment.markers in
817
        try
818
          let url = Url.Anchor.polymorphic_variant ~type_ident item in
819
          let attrs =
156✔
820
            [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ]
156✔
821
          in
822
          let anchor = Some url in
823
          let code = O.documentedSrc (O.txt "| ") @ cstr in
156✔
UNCOV
824
          let doc = match doc with None -> [] | Some doc -> doc in
×
825
          DocumentedSrc.Nested { attrs; anchor; code; doc; markers }
UNCOV
826
        with Failure s ->
×
827
          Printf.eprintf "ERROR: %s\n%!" s;
UNCOV
828
          let code = O.documentedSrc (O.txt "| ") @ cstr in
×
829
          let attrs = [ "def"; kind_approx ] in
830
          let doc = [] in
831
          let anchor = None in
832
          DocumentedSrc.Nested { attrs; anchor; code; doc; markers }
833
      in
834
      let variants = List.map row t.elements in
835
      let intro, ending =
74✔
836
        match t.kind with
837
        | Fixed -> (O.documentedSrc (O.txt "[ "), O.documentedSrc (O.txt " ]"))
66✔
838
        | Open -> (O.documentedSrc (O.txt "[> "), O.documentedSrc (O.txt " ]"))
8✔
UNCOV
839
        | Closed [] ->
×
UNCOV
840
            (O.documentedSrc (O.txt "[< "), O.documentedSrc (O.txt " ]"))
×
UNCOV
841
        | Closed lst ->
×
842
            let constrs = String.concat ~sep:" " lst in
UNCOV
843
            ( O.documentedSrc (O.txt "[< "),
×
844
              O.documentedSrc (O.txt (" " ^ constrs ^ " ]")) )
×
845
      in
846
      intro @ variants @ ending
847

848
    let format_params :
849
        'row.
850
        ?delim:[ `parens | `brackets ] ->
851
        Odoc_model.Lang.TypeDecl.param list ->
852
        text =
853
     fun ?(delim = `parens) params ->
472✔
854
      let format_param_str
563✔
855
          { Odoc_model.Lang.TypeDecl.desc; variance; injectivity; kind = _ } =
856
        let desc =
624✔
857
          match desc with
858
          | Odoc_model.Lang.TypeDecl.Any -> [ "_" ]
37✔
859
          | Var s -> [ "'"; s ]
587✔
860
        in
861
        let var_desc =
862
          match variance with
863
          | None -> desc
608✔
864
          | Some Odoc_model.Lang.TypeDecl.Pos -> "+" :: desc
8✔
865
          | Some Odoc_model.Lang.TypeDecl.Neg -> "-" :: desc
8✔
UNCOV
866
          | Some Odoc_model.Lang.TypeDecl.Bivariant -> "+" :: "-" :: desc
×
867
        in
UNCOV
868
        let final = if injectivity then "!" :: var_desc else var_desc in
×
869
        String.concat ~sep:"" final
870
      in
871
      let format_param p =
872
        Type_expression.with_kind_annotation p.Odoc_model.Lang.TypeDecl.kind
236✔
873
          (O.txt (format_param_str p))
236✔
874
      in
875
      match params with
876
      | [] -> O.noop
59✔
877
      | [ x ] ->
388✔
878
          let base = format_param_str x |> Syntax.Type.handle_format_params in
388✔
879
          Type_expression.with_kind_annotation x.kind (O.txt base)
388✔
880
      | lst ->
116✔
881
          let left, right =
NEW
UNCOV
882
            match delim with `parens -> ("(", ")") | `brackets -> ("[", "]")
×
883
          in
884
          O.txt left
116✔
885
          ++ O.list lst ~sep:(O.txt ", ") ~f:format_param
116✔
886
          ++ O.txt right
116✔
887

888
    let format_constraints constraints =
889
      O.list constraints ~f:(fun (t1, t2) ->
3,103✔
890
          O.sp
104✔
891
          ++ (O.box_hv
104✔
892
             @@ O.keyword "constraint" ++ O.sp
104✔
893
                ++ O.box_hv_no_indent (type_expr t1)
104✔
894
                ++ O.txt " =" ++ O.sp
104✔
895
                ++ O.box_hv_no_indent (type_expr t2)))
104✔
896

897
    let format_manifest :
898
        'inner_row 'outer_row.
899
        ?is_substitution:bool ->
900
        ?compact_variants:bool ->
901
        Odoc_model.Lang.TypeDecl.Equation.t ->
902
        text * bool =
903
     fun ?(is_substitution = false) ?(compact_variants = true) equation ->
104✔
904
      let _ = compact_variants in
3,021✔
905
      (* TODO *)
906
      let private_ = equation.private_ in
907
      match equation.manifest with
908
      | None -> (O.noop, private_)
1,753✔
909
      | Some t ->
1,268✔
910
          let manifest =
911
            O.txt (if is_substitution then " :=" else " =")
23✔
912
            ++ O.sp
1,268✔
913
            ++ (if private_ then
1,268✔
914
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
915
                else O.noop)
1,260✔
916
            ++ type_expr t
1,268✔
917
          in
918
          (manifest, false)
1,268✔
919

920
    let type_decl ?(is_substitution = false)
2,968✔
921
        ((recursive, t) : Lang.Signature.recursive * Lang.TypeDecl.t) =
922
      let keyword' =
2,991✔
923
        match recursive with
924
        | Ordinary | Rec -> O.keyword "type"
×
925
        | And -> O.keyword "and"
18✔
926
        | Nonrec -> O.keyword "type" ++ O.txt " " ++ O.keyword "nonrec"
1✔
927
      in
928
      let tyname = Paths.Identifier.name t.id in
929
      let tconstr =
2,991✔
930
        match t.equation.params with
931
        | [] -> O.txt tyname
2,545✔
932
        | l ->
446✔
933
            let params = format_params l in
934
            Syntax.Type.handle_constructor_params (O.txt tyname) params
446✔
935
      in
936
      let kind_annot =
937
        match t.equation.kind with
938
        | Default -> O.noop
2,991✔
NEW
UNCOV
939
        | k -> O.txt " : " ++ Type_expression.kind_annotation k
×
940
      in
941
      let intro = keyword' ++ O.txt " " ++ tconstr ++ kind_annot in
2,991✔
942
      let constraints = format_constraints t.equation.constraints in
2,991✔
943
      let manifest, need_private, long_prefix =
2,991✔
944
        match t.equation.manifest with
945
        | Some (Odoc_model.Lang.TypeExpr.Polymorphic_variant variant) ->
74✔
946
            let code =
947
              polymorphic_variant
948
                ~type_ident:(t.id :> Paths.Identifier.t)
949
                variant
950
            in
951
            let manifest =
74✔
952
              O.documentedSrc
74✔
953
                (O.ignore intro
74✔
UNCOV
954
                ++ O.txt (if is_substitution then " :=" else " =")
×
955
                ++ O.sp
74✔
956
                ++
74✔
957
                if t.equation.private_ then
958
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
959
                else O.noop)
66✔
960
              @ code
961
            in
962
            (manifest, false, O.noop)
963
        | _ ->
2,917✔
964
            let manifest, need_private =
965
              format_manifest ~is_substitution t.equation
966
            in
967
            let text = O.ignore intro ++ manifest in
2,917✔
968
            (O.documentedSrc @@ text, need_private, text)
2,917✔
969
      in
970
      let representation =
971
        match t.representation with
972
        | None -> []
2,629✔
973
        | Some repr ->
362✔
974
            let content =
975
              match repr with
976
              | Extensible -> O.documentedSrc (O.txt "..")
61✔
977
              | Variant cstrs -> variant cstrs
235✔
978
              | Record fields -> record fields
66✔
979
              | Record_unboxed_product fields -> unboxed_record fields
×
980
            in
981
            if List.length content > 0 then
362✔
982
              O.documentedSrc
362✔
983
                (O.ignore long_prefix ++ O.txt " =" ++ O.sp
362✔
984
                ++
362✔
985
                if need_private then
986
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
987
                else O.noop)
354✔
988
              @ content
UNCOV
989
            else []
×
990
      in
991
      let content =
992
        O.documentedSrc intro @ manifest @ representation
2,991✔
993
        @ O.documentedSrc constraints
2,991✔
994
        @ O.documentedSrc
2,991✔
UNCOV
995
            (if Syntax.Type.type_def_semicolon then O.txt ";" else O.noop)
×
996
      in
997
      let attr = "type" :: (if is_substitution then [ "subst" ] else []) in
23✔
998
      let anchor = path_to_id t.id in
999
      let doc = Comment.to_ir t.doc.elements in
2,991✔
1000
      let source_anchor = source_anchor t.source_loc in
2,991✔
1001
      Item.Declaration { attr; anchor; doc; content; source_anchor }
2,991✔
1002
  end
1003

1004
  open Type_declaration
1005

1006
  module Value : sig
1007
    val value : Lang.Value.t -> Item.t
1008
  end = struct
1009
    let value (t : Odoc_model.Lang.Value.t) =
1010
      let extra_attr, semicolon =
912✔
1011
        match t.value with
1012
        | Abstract -> ([], Syntax.Value.semicolon)
888✔
1013
        | External _ -> ([ "external" ], Syntax.Type.External.semicolon)
24✔
1014
      in
1015
      let name = Paths.Identifier.name t.id in
1016
      let content =
912✔
1017
        O.documentedSrc
1018
          (O.box_hv
912✔
1019
          @@ O.keyword Syntax.Value.variable_keyword
912✔
1020
             ++ O.txt " " ++ O.txt name
912✔
1021
             ++ O.txt Syntax.Type.annotation_separator
912✔
1022
             ++ O.cut ++ type_expr t.type_
912✔
UNCOV
1023
             ++ if semicolon then O.txt ";" else O.noop)
×
1024
      in
1025
      let attr = [ "value" ] @ extra_attr in
912✔
1026
      let anchor = path_to_id t.id in
1027
      let doc = Comment.to_ir t.doc.elements in
912✔
1028
      let source_anchor = source_anchor t.source_loc in
912✔
1029
      Item.Declaration { attr; anchor; doc; content; source_anchor }
912✔
1030
  end
1031

1032
  open Value
1033

1034
  (* This chunk of code is responsible for sectioning list of items
1035
     according to headings by extracting headings as Items.
1036

1037
     TODO: This sectioning would be better done as a pass on the model directly.
1038
  *)
1039
  module Sectioning : sig
1040
    open Odoc_model
1041

1042
    val comment_items : Comment.elements -> Item.t list
1043

1044
    val docs : Comment.elements -> Item.t list * Item.t list
1045
  end = struct
1046
    let take_until_heading_or_end (docs : Odoc_model.Comment.elements) =
1047
      let content, _, rest =
693✔
1048
        Doctree.Take.until docs ~classify:(fun b ->
1049
            match b.Location.value with
1,481✔
1050
            | `Heading _ -> Stop_and_keep
202✔
1051
            | #Odoc_model.Comment.attached_block_element as doc ->
1,279✔
1052
                let content = Comment.attached_block_element doc in
1053
                Accum content)
1,279✔
1054
      in
1055
      (content, rest)
693✔
1056

1057
    let comment_items (input0 : Odoc_model.Comment.elements) =
1058
      let rec loop input_comment acc =
963✔
1059
        match input_comment with
2,493✔
1060
        | [] -> List.rev acc
963✔
1061
        | element :: input_comment -> (
1,530✔
1062
            match element.Location.value with
1063
            | `Heading h ->
837✔
1064
                let item = Comment.heading h in
1065
                loop input_comment (item :: acc)
837✔
1066
            | _ ->
693✔
1067
                let content, input_comment =
1068
                  take_until_heading_or_end (element :: input_comment)
1069
                in
1070
                let item = Item.Text content in
693✔
1071
                loop input_comment (item :: acc))
1072
      in
1073
      loop input0 []
1074

1075
    (* For doc pages, we want the header to contain everything until
1076
       the first heading, then everything before the next heading which
1077
       is either lower, or a section.
1078
    *)
1079
    let docs input_comment =
1080
      let items = comment_items input_comment in
69✔
1081
      let until_first_heading, o, items =
69✔
1082
        Doctree.Take.until items ~classify:(function
1083
          | Item.Heading h as i -> Stop_and_accum ([ i ], Some h.level)
69✔
UNCOV
1084
          | i -> Accum [ i ])
×
1085
      in
1086
      match o with
69✔
UNCOV
1087
      | None -> (until_first_heading, items)
×
1088
      | Some level ->
69✔
UNCOV
1089
          let max_level = if level = 1 then 2 else level in
×
1090
          let before_second_heading, _, items =
1091
            Doctree.Take.until items ~classify:(function
1092
              | Item.Heading h when h.level >= max_level -> Stop_and_keep
13✔
1093
              | i -> Accum [ i ])
38✔
1094
          in
1095
          let header = until_first_heading @ before_second_heading in
69✔
1096
          (header, items)
1097
  end
1098

1099
  module Class : sig
1100
    val class_ : Lang.Class.t -> Item.t
1101

1102
    val class_type : Lang.ClassType.t -> Item.t
1103
  end = struct
1104
    let class_type_expr (cte : Odoc_model.Lang.ClassType.expr) =
1105
      match cte with
249✔
1106
      | Constr (path, args) ->
58✔
1107
          let link = Link.from_path (path :> Paths.Path.t) in
1108
          format_type_path ~delim:`brackets args link
58✔
1109
      | Signature _ ->
191✔
1110
          Syntax.Class.open_tag ++ O.txt " ... " ++ Syntax.Class.close_tag
191✔
1111

1112
    let method_ (t : Odoc_model.Lang.Method.t) =
1113
      let name = Paths.Identifier.name t.id in
90✔
1114
      let virtual_ =
90✔
1115
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1116
      in
1117
      let private_ =
1118
        if t.private_ then O.keyword "private" ++ O.txt " " else O.noop
8✔
1119
      in
1120
      let content =
1121
        O.documentedSrc
1122
          (O.keyword "method" ++ O.txt " " ++ private_ ++ virtual_ ++ O.txt name
90✔
1123
          ++ O.txt Syntax.Type.annotation_separator
90✔
1124
          ++ type_expr t.type_)
90✔
1125
      in
1126
      let attr = [ "method" ] in
90✔
1127
      let anchor = path_to_id t.id in
1128
      let doc = Comment.to_ir t.doc.elements in
90✔
1129
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
90✔
1130

1131
    let instance_variable (t : Odoc_model.Lang.InstanceVariable.t) =
1132
      let name = Paths.Identifier.name t.id in
17✔
1133
      let virtual_ =
17✔
1134
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1135
      in
1136
      let mutable_ =
1137
        if t.mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop
8✔
1138
      in
1139
      let content =
1140
        O.documentedSrc
1141
          (O.keyword "val" ++ O.txt " " ++ mutable_ ++ virtual_ ++ O.txt name
17✔
1142
          ++ O.txt Syntax.Type.annotation_separator
17✔
1143
          ++ type_expr t.type_)
17✔
1144
      in
1145
      let attr = [ "value"; "instance-variable" ] in
17✔
1146
      let anchor = path_to_id t.id in
1147
      let doc = Comment.to_ir t.doc.elements in
17✔
1148
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
17✔
1149

1150
    let inherit_ (ih : Lang.ClassSignature.Inherit.t) =
1151
      let cte =
16✔
1152
        match ih.expr with
1153
        | Signature _ -> assert false (* Bold. *)
1154
        | cty -> cty
16✔
1155
      in
1156
      let content =
1157
        O.documentedSrc (O.keyword "inherit" ++ O.txt " " ++ class_type_expr cte)
16✔
1158
      in
1159
      let attr = [ "inherit" ] in
16✔
1160
      let anchor = None in
1161
      let doc = Comment.to_ir ih.doc.elements in
1162
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
16✔
1163

1164
    let constraint_ (cst : Lang.ClassSignature.Constraint.t) =
1165
      let content =
8✔
1166
        O.documentedSrc (format_constraints [ (cst.left, cst.right) ])
8✔
1167
      in
1168
      let attr = [] in
8✔
1169
      let anchor = None in
1170
      let doc = Comment.to_ir cst.doc.elements in
1171
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
8✔
1172

1173
    let class_signature (c : Lang.ClassSignature.t) =
1174
      let rec loop l acc_items =
233✔
1175
        match l with
388✔
1176
        | [] -> List.rev acc_items
233✔
1177
        | item :: rest -> (
155✔
1178
            let continue item = loop rest (item :: acc_items) in
131✔
1179
            match (item : Lang.ClassSignature.item) with
1180
            | Inherit cty -> continue @@ inherit_ cty
16✔
1181
            | Method m -> continue @@ method_ m
90✔
1182
            | InstanceVariable v -> continue @@ instance_variable v
17✔
1183
            | Constraint cst -> continue @@ constraint_ cst
8✔
1184
            | Comment `Stop ->
8✔
1185
                let rest =
1186
                  List.skip_until rest ~p:(function
1187
                    | Lang.ClassSignature.Comment `Stop -> true
8✔
1188
                    | _ -> false)
8✔
1189
                in
1190
                loop rest acc_items
8✔
1191
            | Comment (`Docs c) ->
16✔
1192
                let items = Sectioning.comment_items c.elements in
1193
                loop rest (List.rev_append items acc_items))
16✔
1194
      in
1195
      (* FIXME: use [t.self] *)
1196
      (c.doc.elements, loop c.items [])
233✔
1197

1198
    let rec class_decl (cd : Odoc_model.Lang.Class.decl) =
1199
      match cd with
182✔
1200
      | ClassType expr -> class_type_expr expr
166✔
1201
      (* TODO: factorize the following with [type_expr] *)
1202
      | Arrow (None, src, dst) ->
16✔
1203
          O.span
16✔
1204
            (type_expr ~needs_parentheses:true src
16✔
1205
            ++ O.txt " " ++ Syntax.Type.arrow)
16✔
1206
          ++ O.txt " " ++ class_decl dst
16✔
UNCOV
1207
      | Arrow (Some (RawOptional _ as lbl), _src, dst) ->
×
UNCOV
1208
          O.span
×
UNCOV
1209
            (O.box_hv
×
UNCOV
1210
            @@ label lbl ++ O.txt ":"
×
UNCOV
1211
               ++ tag "error" (O.txt "???")
×
UNCOV
1212
               ++ O.txt " " ++ Syntax.Type.arrow)
×
UNCOV
1213
          ++ O.sp ++ class_decl dst
×
UNCOV
1214
      | Arrow (Some lbl, src, dst) ->
×
UNCOV
1215
          O.span
×
UNCOV
1216
            (label lbl ++ O.txt ":"
×
UNCOV
1217
            ++ type_expr ~needs_parentheses:true src
×
UNCOV
1218
            ++ O.txt " " ++ Syntax.Type.arrow)
×
UNCOV
1219
          ++ O.txt " " ++ class_decl dst
×
1220

1221
    let class_ (t : Odoc_model.Lang.Class.t) =
1222
      let name = Paths.Identifier.name t.id in
166✔
1223
      let params =
166✔
1224
        match t.params with
1225
        | [] -> O.noop
142✔
1226
        | _ :: _ as params -> format_params ~delim:`brackets params ++ O.txt " "
24✔
1227
      in
1228
      let virtual_ =
1229
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
24✔
1230
      in
1231

1232
      let source_anchor = source_anchor t.source_loc in
1233
      let cname, expansion, expansion_doc =
166✔
1234
        match t.expansion with
1235
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1236
        | Some csig ->
166✔
1237
            let expansion_doc, items = class_signature csig in
1238
            let url = Url.Path.from_identifier t.id in
166✔
1239
            let page =
166✔
1240
              make_expansion_page ~source_anchor url
1241
                [ t.doc.elements; expansion_doc ]
1242
                items
1243
            in
1244
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
166✔
1245
              Some page,
1246
              Some expansion_doc )
1247
      in
1248
      let summary =
1249
        O.txt Syntax.Type.annotation_separator ++ class_decl t.type_
166✔
1250
      in
1251
      let cd =
166✔
1252
        attach_expansion
1253
          (Syntax.Type.annotation_separator, "object", "end")
1254
          expansion summary
1255
      in
1256
      let content =
166✔
1257
        O.documentedSrc (O.keyword "class" ++ O.txt " " ++ virtual_ ++ params)
166✔
1258
        @ cname @ cd
1259
      in
1260
      let attr = [ "class" ] in
1261
      let anchor = path_to_id t.id in
1262
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
166✔
1263
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1264

1265
    let class_type (t : Odoc_model.Lang.ClassType.t) =
1266
      let name = Paths.Identifier.name t.id in
67✔
1267
      let params = format_params ~delim:`brackets t.params in
67✔
1268
      let virtual_ =
67✔
1269
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1270
      in
1271
      let source_anchor = source_anchor t.source_loc in
1272
      let cname, expansion, expansion_doc =
67✔
1273
        match t.expansion with
UNCOV
1274
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1275
        | Some csig ->
67✔
1276
            let url = Url.Path.from_identifier t.id in
1277
            let expansion_doc, items = class_signature csig in
67✔
1278
            let page =
67✔
1279
              make_expansion_page ~source_anchor url
1280
                [ t.doc.elements; expansion_doc ]
1281
                items
1282
            in
1283
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
67✔
1284
              Some page,
1285
              Some expansion_doc )
1286
      in
1287
      let summary = O.txt " = " ++ class_type_expr t.expr in
67✔
1288
      let expr = attach_expansion (" = ", "object", "end") expansion summary in
67✔
1289
      let content =
67✔
1290
        O.documentedSrc
67✔
1291
          (O.keyword "class" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
67✔
1292
         ++ virtual_ ++ params ++ O.txt " ")
67✔
1293
        @ cname @ expr
1294
      in
1295
      let attr = [ "class-type" ] in
1296
      let anchor = path_to_id t.id in
1297
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
67✔
1298
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1299
  end
1300

1301
  open Class
1302

1303
  module Module : sig
1304
    val signature : Lang.Signature.t -> Comment.Comment.elements * Item.t list
1305
    (** Returns [header_doc, content]. *)
1306
  end = struct
1307
    let internal_module m =
1308
      let open Lang.Module in
1,924✔
1309
      match m.id.iv with
1310
      | `Module (_, name) when ModuleName.is_hidden name -> true
81✔
1311
      | _ -> false
1,843✔
1312

1313
    let internal_type t =
1314
      let open Lang.TypeDecl in
2,969✔
1315
      match t.id.iv with
1316
      | `Type (_, name) when TypeName.is_hidden name -> true
1✔
1317
      | _ -> false
2,968✔
1318

1319
    let internal_value v =
1320
      let open Lang.Value in
1,018✔
1321
      match v.id.iv with
1322
      | `Value (_, name) when ValueName.is_hidden name -> true
106✔
1323
      | _ -> false
912✔
1324

1325
    let internal_module_type t =
1326
      let open Lang.ModuleType in
1,361✔
1327
      match t.id.iv with
UNCOV
1328
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1329
      | _ -> false
1,361✔
1330

1331
    let internal_module_substitution t =
1332
      let open Lang.ModuleSubstitution in
8✔
1333
      match t.id.iv with
UNCOV
1334
      | `Module (_, name) when ModuleName.is_hidden name -> true
×
1335
      | _ -> false
8✔
1336

1337
    let internal_module_type_substitution t =
1338
      let open Lang.ModuleTypeSubstitution in
8✔
1339
      match t.id.iv with
UNCOV
1340
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1341
      | _ -> false
8✔
1342

1343
    let rec signature (s : Lang.Signature.t) =
1344
      let rec loop l acc_items =
3,582✔
1345
        match l with
12,543✔
1346
        | [] -> List.rev acc_items
3,582✔
1347
        | item :: rest -> (
8,961✔
1348
            let continue (item : Item.t) = loop rest (item :: acc_items) in
7,838✔
1349
            match (item : Lang.Signature.item) with
1350
            | Module (_, m) when internal_module m -> loop rest acc_items
81✔
1351
            | Type (_, t) when internal_type t -> loop rest acc_items
1✔
1352
            | Value v when internal_value v -> loop rest acc_items
106✔
UNCOV
1353
            | ModuleType m when internal_module_type m -> loop rest acc_items
×
1354
            | ModuleSubstitution m when internal_module_substitution m ->
8✔
UNCOV
1355
                loop rest acc_items
×
1356
            | ModuleTypeSubstitution m when internal_module_type_substitution m
8✔
1357
              ->
UNCOV
1358
                loop rest acc_items
×
1359
            | ModuleTypeSubstitution m -> continue @@ module_type_substitution m
8✔
1360
            | Module (_, m) -> continue @@ module_ m
1,843✔
1361
            | ModuleType m -> continue @@ module_type m
1,361✔
1362
            | Class (_, c) -> continue @@ class_ c
166✔
1363
            | ClassType (_, c) -> continue @@ class_type c
67✔
1364
            | Include m -> continue @@ include_ m
290✔
1365
            | ModuleSubstitution m -> continue @@ module_substitution m
8✔
1366
            | TypeSubstitution t ->
23✔
1367
                continue @@ type_decl ~is_substitution:true (Ordinary, t)
23✔
1368
            | Type (r, t) -> continue @@ type_decl (r, t)
2,968✔
1369
            | TypExt e -> continue @@ extension e
126✔
1370
            | Exception e -> continue @@ exn e
66✔
1371
            | Value v -> continue @@ value v
912✔
1372
            | Open o ->
82✔
1373
                let items = Sectioning.comment_items o.doc.elements in
1374
                loop rest (List.rev_append items acc_items)
82✔
1375
            | Comment `Stop ->
57✔
1376
                let rest =
1377
                  List.skip_until rest ~p:(function
1378
                    | Lang.Signature.Comment `Stop -> true
49✔
1379
                    | _ -> false)
65✔
1380
                in
1381
                loop rest acc_items
57✔
1382
            | Comment (`Docs c) ->
796✔
1383
                let items = Sectioning.comment_items c.elements in
1384
                loop rest (List.rev_append items acc_items))
796✔
1385
      in
1386
      ((Lang.extract_signature_doc s).elements, loop s.items [])
3,582✔
1387

1388
    and functor_parameter :
1389
        Odoc_model.Lang.FunctorParameter.parameter -> DocumentedSrc.t =
1390
     fun arg ->
1391
      let open Odoc_model.Lang.FunctorParameter in
211✔
1392
      let name = Paths.Identifier.name arg.id in
1393
      let render_ty = arg.expr in
211✔
1394
      let modtyp =
1395
        mty_in_decl (arg.id :> Paths.Identifier.Signature.t) render_ty
1396
      in
1397
      let modname, mod_decl =
211✔
1398
        match expansion_of_module_type_expr arg.expr with
UNCOV
1399
        | None ->
×
UNCOV
1400
            let modname = O.txt (Paths.Identifier.name arg.id) in
×
UNCOV
1401
            (modname, O.documentedSrc modtyp)
×
1402
        | Some (expansion_doc, items) ->
211✔
1403
            let url = Url.Path.from_identifier arg.id in
1404
            let modname = path url [ inline @@ Text name ] in
211✔
1405
            let type_with_expansion =
211✔
1406
              let content =
1407
                make_expansion_page ~source_anchor:None url [ expansion_doc ]
1408
                  items
1409
              in
1410
              let summary = O.render modtyp in
211✔
1411
              let status = `Default in
211✔
1412
              let expansion =
1413
                O.documentedSrc
211✔
1414
                  (O.txt Syntax.Type.annotation_separator ++ O.keyword "sig")
211✔
1415
                @ DocumentedSrc.[ Subpage { content; status } ]
1416
                @ O.documentedSrc (O.keyword "end")
211✔
1417
              in
1418
              DocumentedSrc.
1419
                [
1420
                  Alternative
1421
                    (Expansion { status = `Default; summary; url; expansion });
1422
                ]
1423
            in
1424
            (modname, type_with_expansion)
1425
      in
1426
      O.documentedSrc (O.keyword "module" ++ O.txt " ")
211✔
1427
      @ O.documentedSrc modname @ mod_decl
211✔
1428

1429
    and module_substitution (t : Odoc_model.Lang.ModuleSubstitution.t) =
1430
      let name = Paths.Identifier.name t.id in
8✔
1431
      let path = Link.from_path (t.manifest :> Paths.Path.t) in
8✔
1432
      let content =
8✔
1433
        O.documentedSrc
1434
          (O.keyword "module" ++ O.txt " " ++ O.txt name ++ O.txt " :=" ++ O.sp
8✔
1435
         ++ path)
8✔
1436
      in
1437
      let attr = [ "module-substitution" ] in
8✔
1438
      let anchor = path_to_id t.id in
1439
      let doc = Comment.to_ir t.doc.elements in
8✔
1440
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
8✔
1441

1442
    and module_type_substitution (t : Odoc_model.Lang.ModuleTypeSubstitution.t)
1443
        =
1444
      let prefix =
8✔
1445
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
8✔
1446
      in
1447
      let source_anchor = None in
8✔
1448
      let modname = Paths.Identifier.name t.id in
1449
      let modname, expansion_doc, mty =
8✔
1450
        module_type_manifest ~subst:true ~source_anchor modname t.id
1451
          t.doc.elements (Some t.manifest) prefix
1452
      in
1453
      let content =
8✔
1454
        O.documentedSrc (prefix ++ modname)
8✔
1455
        @ mty
1456
        @ O.documentedSrc
8✔
UNCOV
1457
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1458
      in
1459
      let attr = [ "module-type" ] in
1460
      let anchor = path_to_id t.id in
1461
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
8✔
1462
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1463

1464
    and simple_expansion :
1465
        Odoc_model.Lang.ModuleType.simple_expansion ->
1466
        Comment.Comment.elements * Item.t list =
1467
     fun t ->
1468
      let rec extract_functor_params
2,933✔
1469
          (f : Odoc_model.Lang.ModuleType.simple_expansion) =
1470
        match f with
3,152✔
1471
        | Signature sg -> (None, sg)
2,933✔
1472
        | Functor (p, expansion) ->
219✔
1473
            let add_to params =
1474
              match p with Unit -> params | Named p -> p :: params
8✔
1475
            in
1476
            let params, sg = extract_functor_params expansion in
1477
            let params = match params with None -> [] | Some p -> p in
36✔
1478
            (Some (add_to params), sg)
219✔
1479
      in
1480
      match extract_functor_params t with
1481
      | None, sg -> signature sg
2,750✔
1482
      | Some params, sg ->
183✔
1483
          let sg_doc, content = signature sg in
1484
          let params =
183✔
1485
            let decl_of_arg arg =
1486
              let content = functor_parameter arg in
211✔
1487
              let attr = [ "parameter" ] in
211✔
1488
              let anchor =
1489
                Some (Url.Anchor.from_identifier (arg.id :> Paths.Identifier.t))
211✔
1490
              in
1491
              let doc = [] in
1492
              [
1493
                Item.Declaration
1494
                  { content; anchor; attr; doc; source_anchor = None };
1495
              ]
1496
            in
1497
            List.concat_map decl_of_arg params
183✔
1498
          in
1499
          let prelude = mk_heading ~label:"parameters" "Parameters" :: params
183✔
1500
          and content = mk_heading ~label:"signature" "Signature" :: content in
183✔
1501
          (sg_doc, prelude @ content)
1502

1503
    and expansion_of_module_type_expr :
1504
        Odoc_model.Lang.ModuleType.expr ->
1505
        (Comment.Comment.elements * Item.t list) option =
1506
     fun t ->
1507
      let rec simple_expansion_of (t : Odoc_model.Lang.ModuleType.expr) =
3,218✔
1508
        match t with
3,437✔
1509
        | Path { p_expansion = None; _ }
379✔
1510
        | TypeOf { t_expansion = None; _ }
8✔
UNCOV
1511
        | With { w_expansion = None; _ }
×
UNCOV
1512
        | Strengthen { s_expansion = None; _ } ->
×
1513
            None
1514
        | Path { p_expansion = Some e; _ }
426✔
1515
        | TypeOf { t_expansion = Some e; _ }
56✔
1516
        | With { w_expansion = Some e; _ }
218✔
UNCOV
1517
        | Strengthen { s_expansion = Some e; _ } ->
×
1518
            Some e
1519
        | Signature sg -> Some (Signature sg)
2,131✔
1520
        | Functor (f_parameter, e) -> (
219✔
1521
            match simple_expansion_of e with
1522
            | Some e -> Some (Functor (f_parameter, e))
211✔
1523
            | None -> None)
8✔
1524
      in
1525
      match simple_expansion_of t with
1526
      | None -> None
387✔
1527
      | Some e -> Some (simple_expansion e)
2,831✔
1528

1529
    and module_ : Odoc_model.Lang.Module.t -> Item.t =
1530
     fun t ->
1531
      let modname = Paths.Identifier.name t.id in
1,843✔
1532
      let expansion =
1,843✔
1533
        match t.type_ with
1534
        | Alias (_, Some e) -> Some (simple_expansion e)
102✔
1535
        | Alias (_, None) -> None
186✔
1536
        | ModuleType e -> expansion_of_module_type_expr e
1,555✔
1537
      in
1538
      let source_anchor = source_anchor t.source_loc in
1539
      let modname, status, expansion, expansion_doc =
1,843✔
1540
        match expansion with
1541
        | None -> (O.txt modname, `Default, None, None)
322✔
1542
        | Some (expansion_doc, items) ->
1,521✔
1543
            let status =
1544
              match t.type_ with
1545
              | ModuleType (Signature _) -> `Inline
987✔
1546
              | _ -> `Default
534✔
1547
            in
1548
            let url = Url.Path.from_identifier t.id in
1549
            let link = path url [ inline @@ Text modname ] in
1,521✔
1550
            let page =
1,521✔
1551
              make_expansion_page ~source_anchor url
1552
                [ t.doc.elements; expansion_doc ]
1553
                items
1554
            in
1555
            (link, status, Some page, Some expansion_doc)
1,521✔
1556
      in
1557
      let intro = O.keyword "module" ++ O.txt " " ++ modname in
1,843✔
1558
      let summary = O.ignore intro ++ mdexpr_in_decl t.id t.type_ in
1,843✔
1559
      let modexpr =
1,843✔
1560
        attach_expansion ~status
1561
          (Syntax.Type.annotation_separator, "sig", "end")
1562
          expansion summary
1563
      in
1564
      let content =
1,843✔
1565
        O.documentedSrc intro @ modexpr
1,843✔
1566
        @ O.documentedSrc
1,843✔
UNCOV
1567
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1568
      in
1569
      let attr = [ "module" ] in
1570
      let anchor = path_to_id t.id in
1571
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
1,843✔
1572
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1573

1574
    and simple_expansion_in_decl (base : Paths.Identifier.Module.t) se =
1575
      let rec ty_of_se :
102✔
1576
          Lang.ModuleType.simple_expansion -> Lang.ModuleType.expr = function
1577
        | Signature sg -> Signature sg
102✔
UNCOV
1578
        | Functor (arg, sg) -> Functor (arg, ty_of_se sg)
×
1579
      in
1580
      mty_in_decl (base :> Paths.Identifier.Signature.t) (ty_of_se se)
102✔
1581

1582
    and mdexpr_in_decl (base : Paths.Identifier.Module.t) md =
1583
      let sig_dotdotdot =
1,843✔
1584
        O.txt Syntax.Type.annotation_separator
1,843✔
1585
        ++ O.cut ++ Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
1,843✔
1586
      in
1587
      match md with
1,843✔
1588
      | Alias (_, Some se) -> simple_expansion_in_decl base se
102✔
1589
      | Alias (p, _) when not Paths.Path.(is_hidden (p :> t)) ->
186✔
1590
          O.txt " =" ++ O.sp ++ mdexpr md
186✔
UNCOV
1591
      | Alias _ -> sig_dotdotdot
×
1592
      | ModuleType mt -> mty_in_decl (base :> Paths.Identifier.Signature.t) mt
1,555✔
1593

1594
    and mdexpr : Odoc_model.Lang.Module.decl -> text = function
1595
      | Alias (mod_path, _) -> Link.from_path (mod_path :> Paths.Path.t)
242✔
UNCOV
1596
      | ModuleType mt -> mty mt
×
1597

1598
    and module_type_manifest ~subst ~source_anchor modname id doc manifest
1599
        prefix =
1600
      let expansion =
1,369✔
1601
        match manifest with
1602
        | None -> None
128✔
1603
        | Some e -> expansion_of_module_type_expr e
1,241✔
1604
      in
1605
      let modname, expansion, expansion_doc =
1606
        match expansion with
1607
        | None -> (O.txt modname, None, None)
379✔
1608
        | Some (expansion_doc, items) ->
990✔
1609
            let url = Url.Path.from_identifier id in
1610
            let link = path url [ inline @@ Text modname ] in
990✔
1611
            let page =
990✔
1612
              make_expansion_page ~source_anchor url [ doc; expansion_doc ]
1613
                items
1614
            in
1615
            (link, Some page, Some expansion_doc)
990✔
1616
      in
1617
      let summary =
1618
        match manifest with
1619
        | None -> O.noop
128✔
1620
        | Some expr ->
1,241✔
1621
            O.ignore (prefix ++ modname)
1,241✔
1622
            ++ (if subst then O.txt " :=" ++ O.sp else O.txt " =" ++ O.sp)
8✔
1623
            ++ mty expr
1,241✔
1624
      in
1625
      ( modname,
1626
        expansion_doc,
1627
        attach_expansion (" = ", "sig", "end") expansion summary )
1,369✔
1628

1629
    and module_type (t : Odoc_model.Lang.ModuleType.t) =
1630
      let prefix =
1,361✔
1631
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
1,361✔
1632
      in
1633
      let modname = Paths.Identifier.name t.id in
1,361✔
1634
      let source_anchor = source_anchor t.source_loc in
1,361✔
1635
      let modname, expansion_doc, mty =
1,361✔
1636
        module_type_manifest ~subst:false ~source_anchor modname t.id
1637
          t.doc.elements t.expr prefix
1638
      in
1639
      let content =
1,361✔
1640
        O.documentedSrc (prefix ++ modname)
1,361✔
1641
        @ mty
1642
        @ O.documentedSrc
1,361✔
UNCOV
1643
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1644
      in
1645
      let attr = [ "module-type" ] in
1646
      let anchor = path_to_id t.id in
1647
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
1,361✔
1648
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1649

1650
    and umty_hidden : Odoc_model.Lang.ModuleType.U.expr -> bool = function
1651
      | Path p -> Paths.Path.(is_hidden (p :> t))
403✔
1652
      | With (_, expr) -> umty_hidden expr
25✔
1653
      | TypeOf (ModPath m, _) | TypeOf (StructInclude m, _) ->
42✔
1654
          Paths.Path.(is_hidden (m :> t))
1655
      | Signature _ -> false
14✔
UNCOV
1656
      | Strengthen (expr, p, _) ->
×
UNCOV
1657
          umty_hidden expr || Paths.Path.(is_hidden (p :> t))
×
1658

1659
    and mty_hidden : Odoc_model.Lang.ModuleType.expr -> bool = function
1660
      | Path { p_path = mty_path; _ } -> Paths.Path.(is_hidden (mty_path :> t))
837✔
1661
      | With { w_expr; _ } -> umty_hidden w_expr
218✔
1662
      | TypeOf { t_desc = ModPath m; _ }
48✔
1663
      | TypeOf { t_desc = StructInclude m; _ } ->
16✔
1664
          Paths.Path.(is_hidden (m :> t))
1665
      | _ -> false
2,297✔
1666

1667
    and mty_with subs expr =
1668
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
238✔
1669
      ++ O.list
238✔
1670
           ~sep:(O.cut ++ O.txt " " ++ O.keyword "and" ++ O.txt " ")
238✔
1671
           ~f:(fun x -> O.span (substitution x))
279✔
1672
           subs
1673

1674
    and mty_strengthen expr path =
UNCOV
1675
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
×
1676
      ++ Link.from_path (path :> Paths.Path.t)
×
1677

1678
    and mty_typeof t_desc =
1679
      match t_desc with
154✔
1680
      | Odoc_model.Lang.ModuleType.ModPath m ->
90✔
1681
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
90✔
1682
          ++ O.keyword "of" ++ O.txt " "
90✔
1683
          ++ Link.from_path (m :> Paths.Path.t)
90✔
1684
      | StructInclude m ->
64✔
1685
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
64✔
1686
          ++ O.keyword "of" ++ O.txt " " ++ O.keyword "struct" ++ O.txt " "
64✔
1687
          ++ O.keyword "include" ++ O.txt " "
64✔
1688
          ++ Link.from_path (m :> Paths.Path.t)
64✔
1689
          ++ O.txt " " ++ O.keyword "end"
64✔
1690

1691
    and is_elidable_with_u : Odoc_model.Lang.ModuleType.U.expr -> bool =
1692
      function
1693
      | Path _ -> false
213✔
1694
      | Signature _ -> true
5✔
1695
      | With (_, expr) -> is_elidable_with_u expr
×
1696
      | TypeOf _ -> false
25✔
UNCOV
1697
      | Strengthen (expr, _, _) -> is_elidable_with_u expr
×
1698

1699
    and umty : Odoc_model.Lang.ModuleType.U.expr -> text =
1700
     fun m ->
1701
      match m with
527✔
1702
      | Path p -> Link.from_path (p :> Paths.Path.t)
403✔
1703
      | Signature _ ->
9✔
1704
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
9✔
1705
      | With (_, expr) when is_elidable_with_u expr ->
25✔
1706
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
5✔
1707
      | With (subs, expr) -> mty_with subs expr
20✔
1708
      | TypeOf (t_desc, _) -> mty_typeof t_desc
90✔
UNCOV
1709
      | Strengthen (expr, _, _) when is_elidable_with_u expr ->
×
UNCOV
1710
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
UNCOV
1711
      | Strengthen (expr, p, _) -> mty_strengthen expr (p :> Paths.Path.t)
×
1712

1713
    and mty : Odoc_model.Lang.ModuleType.expr -> text =
1714
     fun m ->
1715
      if mty_hidden m then
3,416✔
UNCOV
1716
        Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1717
      else
1718
        match m with
3,416✔
1719
        | Path { p_path = mty_path; _ } ->
837✔
1720
            Link.from_path (mty_path :> Paths.Path.t)
UNCOV
1721
        | Functor (Unit, expr) ->
×
UNCOV
1722
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
UNCOV
1723
            ++ O.span (O.txt " () " ++ Syntax.Type.arrow)
×
UNCOV
1724
            ++ O.sp ++ mty expr
×
1725
        | Functor (Named arg, expr) ->
48✔
1726
            let arg_expr = arg.expr in
1727
            let stop_before = expansion_of_module_type_expr arg_expr = None in
48✔
1728
            let name =
1729
              let open Odoc_model.Lang.FunctorParameter in
1730
              let name = Paths.Identifier.name arg.id in
1731
              let href =
48✔
1732
                Url.from_identifier ~stop_before (arg.id :> Paths.Identifier.t)
1733
              in
1734
              resolved href [ inline @@ Text name ]
48✔
1735
            in
1736
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1737
            ++ (O.box_hv @@ O.span
48✔
1738
               @@ O.txt " (" ++ name
48✔
1739
                  ++ O.txt Syntax.Type.annotation_separator
48✔
1740
                  ++ mty arg_expr ++ O.txt ")" ++ O.txt " " ++ Syntax.Type.arrow
48✔
1741
               )
1742
            ++ O.sp ++ mty expr
48✔
1743
        | With { w_expr; _ } when is_elidable_with_u w_expr ->
218✔
1744
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1745
        | With { w_substitutions; w_expr; _ } ->
218✔
1746
            O.box_hv @@ mty_with w_substitutions w_expr
218✔
1747
        | TypeOf { t_desc; _ } -> mty_typeof t_desc
64✔
1748
        | Signature _ ->
2,249✔
1749
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
2,249✔
UNCOV
1750
        | Strengthen { s_expr; _ } when is_elidable_with_u s_expr ->
×
UNCOV
1751
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
UNCOV
1752
        | Strengthen { s_expr; s_path; _ } ->
×
UNCOV
1753
            O.box_hv @@ mty_strengthen s_expr (s_path :> Paths.Path.t)
×
1754

1755
    and mty_in_decl :
1756
        Paths.Identifier.Signature.t -> Odoc_model.Lang.ModuleType.expr -> text
1757
        =
1758
     fun base -> function
UNCOV
1759
      | (Path _ | Signature _ | With _ | TypeOf _ | Strengthen _) as m ->
×
1760
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
1,868✔
1761
      | Functor _ as m when not Syntax.Mod.functor_contraction ->
171✔
UNCOV
1762
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
×
1763
      | Functor (arg, expr) ->
171✔
1764
          let text_arg =
1765
            match arg with
1766
            | Unit -> O.txt "()"
8✔
1767
            | Named arg ->
163✔
1768
                let arg_expr = arg.expr in
1769
                let stop_before =
1770
                  expansion_of_module_type_expr arg_expr = None
163✔
1771
                in
1772
                let name =
1773
                  let open Odoc_model.Lang.FunctorParameter in
1774
                  let name = Paths.Identifier.name arg.id in
1775
                  let href =
163✔
1776
                    Url.from_identifier ~stop_before
1777
                      (arg.id :> Paths.Identifier.t)
1778
                  in
1779
                  resolved href [ inline @@ Text name ]
163✔
1780
                in
1781
                O.box_hv
163✔
1782
                @@ O.txt "(" ++ name
163✔
1783
                   ++ O.txt Syntax.Type.annotation_separator
163✔
1784
                   ++ O.cut ++ mty arg.expr ++ O.txt ")"
163✔
1785
          in
1786
          O.sp ++ text_arg ++ mty_in_decl base expr
171✔
1787

1788
    (* TODO : Centralize the list juggling for type parameters *)
1789
    and type_expr_in_subst td typath =
1790
      let typath = Link.from_fragment typath in
151✔
1791
      match td.Lang.TypeDecl.Equation.params with
151✔
1792
      | [] -> typath
125✔
1793
      | l -> Syntax.Type.handle_substitution_params typath (format_params l)
26✔
1794

1795
    and substitution : Odoc_model.Lang.ModuleType.substitution -> text =
1796
      function
1797
      | ModuleEq (frag_mod, md) ->
56✔
1798
          O.box_hv
1799
          @@ O.keyword "module" ++ O.txt " "
56✔
1800
             ++ Link.from_fragment (frag_mod :> Paths.Fragment.leaf)
56✔
1801
             ++ O.txt " =" ++ O.sp ++ mdexpr md
56✔
1802
      | ModuleTypeEq (frag_mty, md) ->
32✔
1803
          O.box_hv
1804
          @@ O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
32✔
1805
             ++ Link.from_fragment (frag_mty :> Paths.Fragment.leaf)
32✔
1806
             ++ O.txt " =" ++ O.sp ++ mty md
32✔
1807
      | TypeEq (frag_typ, td) ->
104✔
1808
          O.box_hv
1809
          @@ O.keyword "type" ++ O.txt " "
104✔
1810
             ++ type_expr_in_subst td (frag_typ :> Paths.Fragment.leaf)
104✔
1811
             ++ fst (format_manifest td)
104✔
1812
             ++ format_constraints
104✔
1813
                  td.Odoc_model.Lang.TypeDecl.Equation.constraints
1814
      | ModuleSubst (frag_mod, mod_path) ->
24✔
1815
          O.box_hv
1816
          @@ O.keyword "module" ++ O.txt " "
24✔
1817
             ++ Link.from_fragment (frag_mod :> Paths.Fragment.leaf)
24✔
1818
             ++ O.txt " :=" ++ O.sp
24✔
1819
             ++ Link.from_path (mod_path :> Paths.Path.t)
24✔
1820
      | ModuleTypeSubst (frag_mty, md) ->
16✔
1821
          O.box_hv
1822
          @@ O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
16✔
1823
             ++ Link.from_fragment (frag_mty :> Paths.Fragment.leaf)
16✔
1824
             ++ O.txt " :=" ++ O.sp ++ mty md
16✔
1825
      | TypeSubst (frag_typ, td) -> (
47✔
1826
          O.box_hv
1827
          @@ O.keyword "type" ++ O.txt " "
47✔
1828
             ++ type_expr_in_subst td (frag_typ :> Paths.Fragment.leaf)
47✔
1829
             ++ O.txt " :=" ++ O.sp
47✔
1830
             ++
47✔
1831
             match td.Lang.TypeDecl.Equation.manifest with
1832
             | None -> assert false (* cf loader/cmti *)
1833
             | Some te -> type_expr te)
47✔
1834

1835
    and include_ (t : Odoc_model.Lang.Include.t) =
1836
      let decl_hidden =
290✔
1837
        match t.decl with
UNCOV
1838
        | Alias p -> Paths.Path.(is_hidden (p :> t))
×
1839
        | ModuleType mty -> umty_hidden mty
290✔
1840
      in
1841
      let status = if decl_hidden then `Inline else t.status in
1✔
1842

1843
      let _, content = signature t.expansion.content in
1844
      let summary =
290✔
1845
        if decl_hidden then O.render (O.keyword "include" ++ O.txt " ...")
1✔
1846
        else
1847
          let include_decl =
289✔
1848
            match t.decl with
UNCOV
1849
            | Odoc_model.Lang.Include.Alias mod_path ->
×
UNCOV
1850
                Link.from_path (mod_path :> Paths.Path.t)
×
1851
            | ModuleType mt -> umty mt
289✔
1852
          in
1853
          O.render
289✔
1854
            (O.keyword "include" ++ O.txt " " ++ include_decl
289✔
UNCOV
1855
            ++ if Syntax.Mod.include_semicolon then O.keyword ";" else O.noop)
×
1856
      in
1857
      let content = { Include.content; status; summary } in
1858
      let attr = [ "include" ] in
1859
      let anchor = None in
1860
      let doc =
1861
        (* Documentation attached to includes behave differently than other
1862
           declarations, which show only the synopsis. We can't only show the
1863
           synopsis because no page is generated to render it and we'd loose
1864
           the full documentation.
1865
           The documentation from the expansion is not used. *)
1866
        Comment.to_ir t.doc.elements
1867
      in
1868
      Item.Include { attr; anchor; doc; content; source_anchor = None }
290✔
1869
  end
1870

1871
  open Module
1872

1873
  module Page : sig
1874
    val compilation_unit : Lang.Compilation_unit.t -> Document.t
1875

1876
    val page : Lang.Page.t -> Document.t
1877

1878
    val implementation :
1879
      Lang.Implementation.t ->
1880
      Syntax_highlighter.infos ->
1881
      string ->
1882
      Document.t list
1883
  end = struct
1884
    let pack : Lang.Compilation_unit.Packed.t -> Item.t list =
1885
     fun t ->
UNCOV
1886
      let f x =
×
UNCOV
1887
        let id = x.Lang.Compilation_unit.Packed.id in
×
1888
        let modname = Paths.Identifier.name id in
UNCOV
1889
        let md_def =
×
UNCOV
1890
          O.keyword "module" ++ O.txt " " ++ O.txt modname ++ O.txt " = "
×
UNCOV
1891
          ++ Link.from_path (x.path :> Paths.Path.t)
×
1892
        in
UNCOV
1893
        let content = O.documentedSrc md_def in
×
UNCOV
1894
        let anchor =
×
UNCOV
1895
          Some (Url.Anchor.from_identifier (id :> Paths.Identifier.t))
×
1896
        in
1897
        let attr = [ "modules" ] in
1898
        let doc = [] in
1899
        let decl = { Item.anchor; content; attr; doc; source_anchor = None } in
1900
        Item.Declaration decl
1901
      in
1902
      List.map f t
1903

1904
    let compilation_unit (t : Odoc_model.Lang.Compilation_unit.t) =
1905
      let url = Url.Path.from_identifier t.id in
359✔
1906
      let unit_doc, items =
359✔
1907
        match t.content with
1908
        | Module sign -> signature sign
359✔
1909
        | Pack packed -> ([], pack packed)
×
1910
      in
1911
      let source_anchor = source_anchor t.source_loc in
1912
      let page = make_expansion_page ~source_anchor url [ unit_doc ] items in
359✔
1913
      Document.Page page
359✔
1914

1915
    let page (t : Odoc_model.Lang.Page.t) =
1916
      (*let name =
1917
          match t.name.iv with `Page (_, name) | `LeafPage (_, name) -> name
1918
        in*)
1919
      (*let title = Odoc_model.Names.PageName.to_string name in*)
1920
      let url = Url.Path.from_identifier t.name in
69✔
1921
      let preamble, items = Sectioning.docs t.content.elements in
69✔
1922
      let source_anchor = None in
69✔
1923
      Document.Page { Page.preamble; items; url; source_anchor }
1924

1925
    let implementation (v : Odoc_model.Lang.Implementation.t) syntax_info
1926
        source_code =
1927
      match v.id with
28✔
UNCOV
1928
      | None -> []
×
1929
      | Some id ->
28✔
1930
          [
1931
            Document.Source_page
1932
              (Source_page.source id syntax_info v.source_info source_code);
28✔
1933
          ]
1934
  end
1935

1936
  include Page
1937

1938
  let type_expr = type_expr
1939

1940
  let record = record
1941

1942
  let unboxed_record = unboxed_record
1943
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc