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

ocaml / odoc / 3190

21 May 2026 08:24AM UTC coverage: 71.01% (-0.1%) from 71.138%
3190

Pull #1420

github

web-flow
Merge a1f6bca5b into a3b579e48
Pull Request #1420: OxCaml: Support for modalities

38 of 76 new or added lines in 7 files covered. (50.0%)

3 existing lines in 3 files now uncovered.

10452 of 14719 relevant lines covered (71.01%)

5869.0 hits per line

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

80.38
/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

100
    val render_fragment_any : Paths.Fragment.t -> string
101
  end = struct
102
    open Paths
103

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

152
    let dot prefix suffix = prefix ^ "." ^ suffix
49✔
153

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

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

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

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

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

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

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

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

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

307
  module Modalities : sig
308
    val format : Odoc_model.Lang.Modalities.t -> text
309
  end = struct
310
    let format = function
311
      | [] -> O.noop
1,492✔
NEW
312
      | mods ->
×
NEW
313
          O.txt " " ++ O.txt "@@" ++ O.txt " "
×
NEW
314
          ++ O.txt (String.concat ~sep:" " mods)
×
315
  end
316

317
  module Type_expression : sig
318
    val type_expr : ?needs_parentheses:bool -> Lang.TypeExpr.t -> text
319

320
    val format_type_path :
321
      delim:[ `parens | `brackets ] -> Lang.TypeExpr.t list -> text -> text
322

323
    val kind_annotation :
324
      ?needs_parentheses:bool -> Odoc_model.Lang.Kind.t -> text
325

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

386
    and te_object (t : Odoc_model.Lang.TypeExpr.Object.t) =
387
      let fields =
80✔
388
        O.list
389
          ~sep:(O.sp ++ O.txt Syntax.Obj.field_separator)
80✔
390
          t.fields
391
          ~f:(function
392
            | Odoc_model.Lang.TypeExpr.Object.Method { name; type_ } ->
120✔
393
                O.box_hv_no_indent
394
                @@ O.txt (name ^ Syntax.Type.annotation_separator)
120✔
395
                   ++ O.cut ++ type_expr type_
120✔
396
            | Inherit type_ -> O.box_hv_no_indent @@ type_expr type_)
×
397
      in
398
      let open_tag =
80✔
399
        if t.open_ then O.txt Syntax.Obj.open_tag_extendable
32✔
400
        else O.txt Syntax.Obj.open_tag_closed
48✔
401
      in
402
      let close_tag =
403
        if t.open_ then O.txt Syntax.Obj.close_tag_extendable
32✔
404
        else O.txt Syntax.Obj.close_tag_closed
48✔
405
      in
406
      O.span (open_tag ++ fields ++ close_tag)
80✔
407

408
    and format_type_path ~delim (params : Odoc_model.Lang.TypeExpr.t list)
409
        (path : text) : text =
410
      O.box_hv
179,533✔
411
      @@
412
      match params with
413
      | [] -> path
94,880✔
414
      | [ param ] ->
71,291✔
415
          let param = type_expr ~needs_parentheses:true param in
416
          let args =
71,291✔
417
            if Syntax.Type.parenthesize_constructor then
418
              O.txt "(" ++ param ++ O.txt ")"
×
419
            else param
71,291✔
420
          in
421
          Syntax.Type.handle_constructor_params path args
71,291✔
422
      | params ->
13,362✔
423
          let params = O.list params ~sep:(O.txt "," ++ O.sp) ~f:type_expr in
13,362✔
424
          let params =
13,362✔
425
            match delim with
426
            | `parens -> enclose ~l:"(" params ~r:")"
13,362✔
427
            | `brackets -> enclose ~l:"[" params ~r:"]"
×
428
          in
429
          Syntax.Type.handle_constructor_params path (O.box_hv params)
13,362✔
430

431
    and tuple ?(needs_parentheses = false) ~boxed lst =
×
432
      let opt_label = function
677✔
433
        | None -> O.noop
1,544✔
434
        | Some lbl -> tag "label" (O.txt lbl) ++ O.txt ":" ++ O.cut
×
435
      in
436
      let res =
437
        O.box_hv_no_indent
438
          (O.list lst ~sep:Syntax.Type.Tuple.element_separator
677✔
439
             ~f:(fun (lbl, typ) ->
440
               opt_label lbl ++ type_expr ~needs_parentheses:true typ))
1,544✔
441
      in
442
      let lparen = if boxed then "(" else "#(" in
×
443
      if Syntax.Type.Tuple.always_parenthesize || needs_parentheses || not boxed
×
444
      then enclose ~l:lparen res ~r:")"
131✔
445
      else res
546✔
446

NEW
447
    and kind_annotation ?(needs_parentheses = false)
×
448
        (k : Odoc_model.Lang.Kind.t) =
NEW
449
      let enclose_parens_if_needed res =
×
NEW
450
        if needs_parentheses then enclose ~l:"(" res ~r:")" else res
×
451
      in
452
      match k with
NEW
453
      | Default -> O.noop
×
NEW
454
      | Abbreviation frag ->
×
NEW
455
          O.txt (Link.render_fragment_any (frag :> Paths.Fragment.t))
×
NEW
456
      | Mod (base, modes) ->
×
457
          let res =
NEW
458
            kind_annotation ~needs_parentheses:true base
×
NEW
459
            ++ O.txt " " ++ O.keyword "mod"
×
NEW
460
            ++ O.txt (" " ^ String.concat ~sep:" " modes)
×
461
          in
NEW
462
          enclose_parens_if_needed res
×
NEW
463
      | With (base, ty, modalities) ->
×
464
          let res =
NEW
465
            kind_annotation ~needs_parentheses:true base
×
NEW
466
            ++ O.txt " " ++ O.keyword "with" ++ O.txt " " ++ type_expr ty
×
NEW
467
            ++ Modalities.format modalities
×
468
          in
NEW
469
          enclose_parens_if_needed res
×
NEW
470
      | Kind_of ty ->
×
NEW
471
          let res = O.keyword "kind_of_" ++ O.txt " " ++ type_expr ty in
×
NEW
472
          enclose_parens_if_needed res
×
NEW
473
      | Product ks ->
×
474
          let res =
NEW
475
            O.list ks ~sep:(O.txt " & ") ~f:(fun k ->
×
NEW
476
                kind_annotation ~needs_parentheses:true k)
×
477
          in
NEW
478
          enclose_parens_if_needed res
×
479

480
    and with_kind_annotation kind base =
481
      match kind with
648✔
482
      | Odoc_model.Lang.Kind.Default -> base
648✔
NEW
483
      | k -> O.txt "(" ++ base ++ O.txt " : " ++ kind_annotation k ++ O.txt ")"
×
484

485
    and type_expr ?(needs_parentheses = false) (t : Odoc_model.Lang.TypeExpr.t)
191,635✔
486
        =
487
      let enclose_parens_if_needed res =
312,108✔
488
        if needs_parentheses then enclose ~l:"(" res ~r:")" else res
831✔
489
      in
490
      match t with
491
      | Var s -> type_var (Syntax.Type.var_prefix ^ s)
9,469✔
492
      | Any -> type_var Syntax.Type.any
18✔
493
      | Alias (te, alias) ->
133✔
494
          enclose_parens_if_needed
495
            (type_expr ~needs_parentheses:true te
133✔
496
            ++ O.txt " " ++ O.keyword "as" ++ O.txt " '" ++ O.txt alias)
133✔
497
      | Arrow (None, src, dst) ->
43,802✔
498
          let res =
499
            O.span
43,802✔
500
              ((O.box_hv @@ type_expr ~needs_parentheses:true src)
43,802✔
501
              ++ O.txt " " ++ Syntax.Type.arrow)
43,802✔
502
            ++ O.sp ++ type_expr dst
43,802✔
503
            (* ++ O.end_hv *)
504
          in
505
          enclose_parens_if_needed res
43,802✔
506
      | Arrow (Some (RawOptional _ as lbl), _src, dst) ->
1✔
507
          let res =
508
            O.span
1✔
509
              (O.box_hv
1✔
510
              @@ label lbl ++ O.txt ":"
1✔
511
                 ++ tag "error" (O.txt "???")
1✔
512
                 ++ O.txt " " ++ Syntax.Type.arrow)
1✔
513
            ++ O.sp ++ type_expr dst
1✔
514
          in
515
          enclose_parens_if_needed res
1✔
516
      | Arrow (Some lbl, src, dst) ->
3,262✔
517
          let res =
518
            O.span
3,262✔
519
              ((O.box_hv
3,262✔
520
               @@ label lbl ++ O.txt ":" ++ O.cut
3,262✔
521
                  ++ (O.box_hv @@ type_expr ~needs_parentheses:true src))
3,262✔
522
              ++ O.txt " " ++ Syntax.Type.arrow)
3,262✔
523
            ++ O.sp ++ type_expr dst
3,262✔
524
          in
525
          enclose_parens_if_needed res
3,262✔
526
      | Tuple lst -> tuple ~needs_parentheses ~boxed:true lst
677✔
527
      | Unboxed_tuple lst -> tuple ~needs_parentheses ~boxed:false lst
×
528
      | Constr (path, args) ->
179,467✔
529
          let link = Link.from_path (path :> Paths.Path.t) in
530
          format_type_path ~delim:`parens args link
179,467✔
531
      | Polymorphic_variant v -> te_variant v
75,143✔
532
      | Object o -> te_object o
80✔
533
      | Class (path, args) ->
8✔
534
          format_type_path ~delim:`brackets args
535
            (Link.from_path (path :> Paths.Path.t))
8✔
536
      | Poly (polyvars, t) ->
24✔
537
          let format_poly_var (name, kind) =
538
            with_kind_annotation kind (O.txt ("'" ^ name))
24✔
539
          in
540
          let vars = O.list polyvars ~sep:(O.txt " ") ~f:format_poly_var in
24✔
541
          enclose_parens_if_needed @@ (vars ++ O.txt ". " ++ type_expr t)
24✔
542
      | Quote t -> O.span (O.txt "<[ " ++ O.box_hv (type_expr t) ++ O.txt " ]>")
×
543
      | Splice t -> O.span (O.txt "$" ++ type_expr ~needs_parentheses:true t)
×
544
      | Package pkg ->
24✔
545
          enclose ~l:"(" ~r:")"
546
            (O.keyword "module" ++ O.txt " " ++ package_path pkg)
24✔
547
      | Arrow_functor (lbl, m_arg, dst) ->
×
548
          let lbl =
549
            match lbl with None -> O.noop | Some lbl -> label lbl ++ O.txt ":"
×
550
          in
551
          let name =
552
            match m_arg.id.iv with
553
            | `Parameter (_, name) -> ModuleName.to_string name
×
554
          in
555
          let dst = type_expr dst in
556
          let pkg =
×
557
            enclose ~l:"(" ~r:")"
558
            @@ O.keyword "module" ++ O.txt " " ++ O.txt name ++ O.txt " : "
×
559
               ++ package_path m_arg.package
×
560
          in
561
          lbl ++ pkg ++ O.sp ++ Syntax.Type.arrow ++ O.sp ++ dst
×
562

563
    and package_path pkg =
564
      Link.from_path (pkg.path :> Paths.Path.t)
24✔
565
      ++
566
      match pkg.substitutions with
567
      | [] -> O.noop
16✔
568
      | fst :: lst ->
8✔
569
          O.sp
570
          ++ O.box_hv (O.keyword "with" ++ O.txt " " ++ package_subst fst)
8✔
571
          ++ O.list lst ~f:(fun s ->
8✔
572
                 O.cut
8✔
573
                 ++ (O.box_hv
8✔
574
                    @@ O.txt " " ++ O.keyword "and" ++ O.txt " "
8✔
575
                       ++ package_subst s))
8✔
576

577
    and package_subst
578
        ((frag_typ, te) : Paths.Fragment.Type.t * Odoc_model.Lang.TypeExpr.t) :
579
        text =
580
      let typath = Link.from_fragment (frag_typ :> Paths.Fragment.leaf) in
16✔
581
      O.keyword "type" ++ O.txt " " ++ typath ++ O.txt " =" ++ O.sp
16✔
582
      ++ type_expr te
16✔
583
  end
584

585
  open Type_expression
586

587
  (* Also handles constructor declarations for exceptions and extensible
588
     variants, and exposes a few helpers used in formatting classes and signature
589
     constraints. *)
590
  module Type_declaration : sig
591
    val type_decl :
592
      ?is_substitution:bool ->
593
      Lang.Signature.recursive * Lang.TypeDecl.t ->
594
      Item.t
595

596
    val extension : Lang.Extension.t -> Item.t
597

598
    val record : Lang.TypeDecl.Field.t list -> DocumentedSrc.one list
599

600
    val unboxed_record :
601
      Lang.TypeDecl.UnboxedField.t list -> DocumentedSrc.one list
602

603
    val exn : Lang.Exception.t -> Item.t
604

605
    val format_params :
606
      ?delim:[ `parens | `brackets ] -> Lang.TypeDecl.param list -> text
607

608
    val format_manifest :
609
      ?is_substitution:bool ->
610
      ?compact_variants:bool ->
611
      Lang.TypeDecl.Equation.t ->
612
      text * bool
613

614
    val format_constraints : (Lang.TypeExpr.t * Lang.TypeExpr.t) list -> text
615
  end = struct
616
    let record fields =
617
      let field mutable_ id typ modalities =
86✔
618
        let url = Url.from_identifier ~stop_before:true id in
155✔
619
        let name = Paths.Identifier.name id in
155✔
620
        let attrs = [ "def"; "record"; Url.Anchor.string_of_kind url.kind ] in
155✔
621
        let cell =
622
          O.code
623
            ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
24✔
624
            ++ O.txt name
155✔
625
            ++ O.txt Syntax.Type.annotation_separator
155✔
626
            ++ type_expr typ
155✔
627
            ++ Modalities.format modalities
155✔
628
            ++ O.txt Syntax.Type.Record.field_separator)
155✔
629
        in
630
        (url, attrs, cell)
155✔
631
      in
632
      let rows =
633
        fields
634
        |> List.map (fun fld ->
635
               let open Odoc_model.Lang.TypeDecl.Field in
155✔
636
               let url, attrs, code =
637
                 field fld.mutable_
638
                   (fld.id :> Paths.Identifier.t)
639
                   fld.type_ fld.modalities
640
               in
641
               let anchor = Some url in
155✔
642
               let doc = fld.doc.elements in
643
               let rhs = Comment.to_ir doc in
644
               let doc = if not (Comment.has_doc doc) then [] else rhs in
64✔
645
               let markers = Syntax.Comment.markers in
646
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
647
      in
648
      let content =
86✔
649
        O.documentedSrc (O.txt "{") @ rows @ O.documentedSrc (O.txt "}")
86✔
650
      in
651
      content
652

653
    let unboxed_record fields =
654
      let field mutable_ id typ =
×
655
        let url = Url.from_identifier ~stop_before:true id in
×
656
        let name = Paths.Identifier.name id in
×
657
        let attrs = [ "def"; "record"; Url.Anchor.string_of_kind url.kind ] in
×
658
        let cell =
659
          (* O.td ~a:[ O.a_class ["def"; kind ] ]
660
           *   [O.a ~a:[O.a_href ("#" ^ anchor); O.a_class ["anchor"]] []
661
           *   ; *)
662
          O.code
663
            ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
×
664
            ++ O.txt name
×
665
            ++ O.txt Syntax.Type.annotation_separator
×
666
            ++ type_expr typ
×
667
            ++ O.txt Syntax.Type.Record.field_separator)
×
668
          (* ] *)
669
        in
670
        (url, attrs, cell)
×
671
      in
672
      let rows =
673
        fields
674
        |> List.map (fun fld ->
675
               let open Odoc_model.Lang.TypeDecl.UnboxedField in
×
676
               let url, attrs, code =
677
                 field fld.mutable_ (fld.id :> Paths.Identifier.t) fld.type_
678
               in
679
               let anchor = Some url in
×
680
               let doc = fld.doc.elements in
681
               let rhs = Comment.to_ir doc in
682
               let doc = if not (Comment.has_doc doc) then [] else rhs in
×
683
               let markers = Syntax.Comment.markers in
684
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
685
      in
686
      let content =
×
687
        O.documentedSrc (O.txt "#{") @ rows @ O.documentedSrc (O.txt "}")
×
688
      in
689
      content
690

691
    let constructor :
692
        Paths.Identifier.t ->
693
        Odoc_model.Lang.TypeDecl.Constructor.argument ->
694
        Odoc_model.Lang.TypeExpr.t option ->
695
        DocumentedSrc.t =
696
     fun id args ret_type ->
697
      let name = Paths.Identifier.name id in
656✔
698
      let kind = Url.(kind id |> Anchor.string_of_kind) in
656✔
699
      let cstr = tag kind (O.txt name) in
656✔
700
      let is_gadt, ret_type =
656✔
701
        match ret_type with
702
        | None -> (false, O.noop)
520✔
703
        | Some te ->
136✔
704
            let constant = match args with Tuple [] -> true | _ -> false in
48✔
705
            let ret_type =
706
              O.txt " "
136✔
707
              ++ (if constant then O.txt ":" else Syntax.Type.GADT.arrow)
48✔
708
              ++ O.txt " " ++ type_expr te
136✔
709
            in
710
            (true, ret_type)
136✔
711
      in
712
      match args with
713
      | Tuple [] -> O.documentedSrc (cstr ++ ret_type)
289✔
714
      | Tuple lst ->
350✔
715
          let params =
716
            O.list lst ~sep:Syntax.Type.Tuple.element_separator
717
              ~f:(fun (te, mods) ->
718
                type_expr ~needs_parentheses:is_gadt te
425✔
719
                ++ Modalities.format mods)
425✔
720
          in
721
          O.documentedSrc
350✔
722
            (cstr
723
            ++ (if Syntax.Type.Variant.parenthesize_params then
350✔
724
                  O.txt "(" ++ params ++ O.txt ")"
×
725
                else
726
                  (if is_gadt then O.txt Syntax.Type.annotation_separator
88✔
727
                   else O.txt " " ++ O.keyword "of" ++ O.txt " ")
262✔
728
                  ++ params)
350✔
729
            ++ ret_type)
350✔
730
      | Record fields ->
17✔
731
          if is_gadt then
732
            O.documentedSrc (cstr ++ O.txt Syntax.Type.annotation_separator)
×
733
            @ record fields @ O.documentedSrc ret_type
×
734
          else
735
            O.documentedSrc (cstr ++ O.txt " " ++ O.keyword "of" ++ O.txt " ")
17✔
736
            @ record fields
17✔
737

738
    let variant cstrs : DocumentedSrc.t =
739
      let constructor id args res =
235✔
740
        let url = Url.from_identifier ~stop_before:true id in
436✔
741
        let attrs = [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ] in
436✔
742
        let content =
743
          let doc = constructor id args res in
744
          O.documentedSrc (O.txt "| ") @ doc
436✔
745
        in
746
        (url, attrs, content)
747
      in
748
      match cstrs with
749
      | [] -> O.documentedSrc (O.txt "|")
×
750
      | _ :: _ ->
235✔
751
          let rows =
752
            cstrs
753
            |> List.map (fun cstr ->
754
                   let open Odoc_model.Lang.TypeDecl.Constructor in
436✔
755
                   let url, attrs, code =
756
                     constructor
757
                       (cstr.id :> Paths.Identifier.t)
758
                       cstr.args cstr.res
759
                   in
760
                   let anchor = Some url in
436✔
761
                   let doc = cstr.doc.elements in
762
                   let rhs = Comment.to_ir doc in
763
                   let doc = if not (Comment.has_doc doc) then [] else rhs in
73✔
764
                   let markers = Syntax.Comment.markers in
765
                   DocumentedSrc.Nested { anchor; attrs; code; doc; markers })
766
          in
767
          rows
235✔
768

769
    let extension_constructor (t : Odoc_model.Lang.Extension.Constructor.t) =
770
      let id = (t.id :> Paths.Identifier.t) in
154✔
771
      let url = Url.from_identifier ~stop_before:true id in
772
      let anchor = Some url in
154✔
773
      let attrs = [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ] in
154✔
774
      let code = O.documentedSrc (O.txt "| ") @ constructor id t.args t.res in
154✔
775
      let doc = Comment.to_ir t.doc.elements in
776
      let markers = Syntax.Comment.markers in
154✔
777
      DocumentedSrc.Nested { anchor; attrs; code; doc; markers }
778

779
    let extension (t : Odoc_model.Lang.Extension.t) =
780
      let prefix =
126✔
781
        O.keyword "type" ++ O.txt " "
126✔
782
        ++ Link.from_path (t.type_path :> Paths.Path.t)
126✔
783
        ++ O.txt " +=" ++ O.sp
126✔
784
        ++
785
        if t.private_ then O.keyword Syntax.Type.private_keyword ++ O.sp
8✔
786
        else O.noop
118✔
787
      in
788
      let content =
126✔
789
        O.documentedSrc prefix
126✔
790
        @ List.map extension_constructor t.constructors
126✔
791
        @ O.documentedSrc
126✔
792
            (if Syntax.Type.type_def_semicolon then O.txt ";" else O.noop)
×
793
      in
794
      let attr = [ "type"; "extension" ] in
795
      let anchor = Some (Url.Anchor.extension_decl t) in
126✔
796
      let doc = Comment.to_ir t.doc.elements in
797
      let source_anchor =
126✔
798
        (* Take the anchor from the first constructor only for consistency with
799
           regular variants. *)
800
        match t.constructors with
801
        | hd :: _ -> source_anchor hd.source_loc
126✔
802
        | [] -> None
×
803
      in
804
      Item.Declaration { attr; anchor; doc; content; source_anchor }
805

806
    let exn (t : Odoc_model.Lang.Exception.t) =
807
      let cstr = constructor (t.id :> Paths.Identifier.t) t.args t.res in
66✔
808
      let content =
66✔
809
        O.documentedSrc (O.keyword "exception" ++ O.txt " ")
66✔
810
        @ cstr
811
        @ O.documentedSrc
66✔
812
            (if Syntax.Type.Exception.semicolon then O.txt ";" else O.noop)
×
813
      in
814
      let attr = [ "exception" ] in
815
      let anchor = path_to_id t.id in
816
      let doc = Comment.to_ir t.doc.elements in
66✔
817
      let source_anchor = source_anchor t.source_loc in
66✔
818
      Item.Declaration { attr; anchor; doc; content; source_anchor }
66✔
819

820
    let polymorphic_variant ~type_ident
821
        (t : Odoc_model.Lang.TypeExpr.Polymorphic_variant.t) =
822
      let row item =
74✔
823
        let kind_approx, cstr, doc =
156✔
824
          match item with
825
          | Odoc_model.Lang.TypeExpr.Polymorphic_variant.Type te ->
24✔
826
              ("unknown", O.documentedSrc (type_expr te), None)
24✔
827
          | Constructor { constant; name; arguments; doc; _ } -> (
132✔
828
              let cstr = "`" ^ name in
829
              ( "constructor",
830
                (match arguments with
831
                | [] -> O.documentedSrc (O.txt cstr)
58✔
832
                | _ ->
74✔
833
                    (* Multiple arguments in a polymorphic variant constructor correspond
834
                       to a conjunction of types, not a product: [`Lbl int&float].
835
                       If constant is [true], the conjunction starts with an empty type,
836
                       for instance [`Lbl &int].
837
                    *)
838
                    let wrapped_type_expr =
839
                      (* type conjunction in Reason is printed as `Lbl (t1)&(t2)` *)
840
                      if Syntax.Type.Variant.parenthesize_params then fun x ->
×
841
                        O.txt "(" ++ type_expr x ++ O.txt ")"
×
842
                      else fun x -> type_expr x
74✔
843
                    in
844
                    let params =
845
                      O.box_hv
846
                      @@ O.list arguments
74✔
847
                           ~sep:(O.txt " &" ++ O.sp)
74✔
848
                           ~f:wrapped_type_expr
849
                    in
850
                    let params =
74✔
851
                      if constant then O.txt "& " ++ params else params
×
852
                    in
853
                    O.documentedSrc
74✔
854
                      (O.txt cstr
74✔
855
                      ++
74✔
856
                      if Syntax.Type.Variant.parenthesize_params then params
×
857
                      else O.txt " " ++ O.keyword "of" ++ O.sp ++ params)),
74✔
858
                match doc with
859
                | { elements = []; _ } -> None
132✔
860
                | _ -> Some (Comment.to_ir doc.elements) ))
×
861
        in
862
        let markers = Syntax.Comment.markers in
863
        try
864
          let url = Url.Anchor.polymorphic_variant ~type_ident item in
865
          let attrs =
156✔
866
            [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ]
156✔
867
          in
868
          let anchor = Some url in
869
          let code = O.documentedSrc (O.txt "| ") @ cstr in
156✔
870
          let doc = match doc with None -> [] | Some doc -> doc in
×
871
          DocumentedSrc.Nested { attrs; anchor; code; doc; markers }
872
        with Failure s ->
×
873
          Printf.eprintf "ERROR: %s\n%!" s;
874
          let code = O.documentedSrc (O.txt "| ") @ cstr in
×
875
          let attrs = [ "def"; kind_approx ] in
876
          let doc = [] in
877
          let anchor = None in
878
          DocumentedSrc.Nested { attrs; anchor; code; doc; markers }
879
      in
880
      let variants = List.map row t.elements in
881
      let intro, ending =
74✔
882
        match t.kind with
883
        | Fixed -> (O.documentedSrc (O.txt "[ "), O.documentedSrc (O.txt " ]"))
66✔
884
        | Open -> (O.documentedSrc (O.txt "[> "), O.documentedSrc (O.txt " ]"))
8✔
885
        | Closed [] ->
×
886
            (O.documentedSrc (O.txt "[< "), O.documentedSrc (O.txt " ]"))
×
887
        | Closed lst ->
×
888
            let constrs = String.concat ~sep:" " lst in
889
            ( O.documentedSrc (O.txt "[< "),
×
890
              O.documentedSrc (O.txt (" " ^ constrs ^ " ]")) )
×
891
      in
892
      intro @ variants @ ending
893

894
    let format_params :
895
        'row.
896
        ?delim:[ `parens | `brackets ] ->
897
        Odoc_model.Lang.TypeDecl.param list ->
898
        text =
899
     fun ?(delim = `parens) params ->
472✔
900
      let format_param_str
563✔
901
          { Odoc_model.Lang.TypeDecl.desc; variance; injectivity; kind = _ } =
902
        let desc =
624✔
903
          match desc with
904
          | Odoc_model.Lang.TypeDecl.Any -> [ "_" ]
37✔
905
          | Var s -> [ "'"; s ]
587✔
906
        in
907
        let var_desc =
908
          match variance with
909
          | None -> desc
608✔
910
          | Some Odoc_model.Lang.TypeDecl.Pos -> "+" :: desc
8✔
911
          | Some Odoc_model.Lang.TypeDecl.Neg -> "-" :: desc
8✔
912
          | Some Odoc_model.Lang.TypeDecl.Bivariant -> "+" :: "-" :: desc
×
913
        in
914
        let final = if injectivity then "!" :: var_desc else var_desc in
×
915
        String.concat ~sep:"" final
916
      in
917
      let format_param p =
918
        Type_expression.with_kind_annotation p.Odoc_model.Lang.TypeDecl.kind
236✔
919
          (O.txt (format_param_str p))
236✔
920
      in
921
      match params with
922
      | [] -> O.noop
59✔
923
      | [ x ] ->
388✔
924
          let base = format_param_str x |> Syntax.Type.handle_format_params in
388✔
925
          Type_expression.with_kind_annotation x.kind (O.txt base)
388✔
926
      | lst ->
116✔
927
          let left, right =
NEW
928
            match delim with `parens -> ("(", ")") | `brackets -> ("[", "]")
×
929
          in
930
          O.txt left
116✔
931
          ++ O.list lst ~sep:(O.txt ", ") ~f:format_param
116✔
932
          ++ O.txt right
116✔
933

934
    let format_constraints constraints =
935
      O.list constraints ~f:(fun (t1, t2) ->
3,103✔
936
          O.sp
104✔
937
          ++ (O.box_hv
104✔
938
             @@ O.keyword "constraint" ++ O.sp
104✔
939
                ++ O.box_hv_no_indent (type_expr t1)
104✔
940
                ++ O.txt " =" ++ O.sp
104✔
941
                ++ O.box_hv_no_indent (type_expr t2)))
104✔
942

943
    let format_manifest :
944
        'inner_row 'outer_row.
945
        ?is_substitution:bool ->
946
        ?compact_variants:bool ->
947
        Odoc_model.Lang.TypeDecl.Equation.t ->
948
        text * bool =
949
     fun ?(is_substitution = false) ?(compact_variants = true) equation ->
104✔
950
      let _ = compact_variants in
3,021✔
951
      (* TODO *)
952
      let private_ = equation.private_ in
953
      match equation.manifest with
954
      | None -> (O.noop, private_)
1,753✔
955
      | Some t ->
1,268✔
956
          let manifest =
957
            O.txt (if is_substitution then " :=" else " =")
23✔
958
            ++ O.sp
1,268✔
959
            ++ (if private_ then
1,268✔
960
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
961
                else O.noop)
1,260✔
962
            ++ type_expr t
1,268✔
963
          in
964
          (manifest, false)
1,268✔
965

966
    let type_decl ?(is_substitution = false)
2,968✔
967
        ((recursive, t) : Lang.Signature.recursive * Lang.TypeDecl.t) =
968
      let keyword' =
2,991✔
969
        match recursive with
970
        | Ordinary | Rec -> O.keyword "type"
×
971
        | And -> O.keyword "and"
18✔
972
        | Nonrec -> O.keyword "type" ++ O.txt " " ++ O.keyword "nonrec"
1✔
973
      in
974
      let tyname = Paths.Identifier.name t.id in
975
      let tconstr =
2,991✔
976
        match t.equation.params with
977
        | [] -> O.txt tyname
2,545✔
978
        | l ->
446✔
979
            let params = format_params l in
980
            Syntax.Type.handle_constructor_params (O.txt tyname) params
446✔
981
      in
982
      let kind_annot =
983
        match t.equation.kind with
984
        | Default -> O.noop
2,991✔
NEW
985
        | k -> O.txt " : " ++ Type_expression.kind_annotation k
×
986
      in
987
      let intro = keyword' ++ O.txt " " ++ tconstr ++ kind_annot in
2,991✔
988
      let constraints = format_constraints t.equation.constraints in
2,991✔
989
      let manifest, need_private, long_prefix =
2,991✔
990
        match t.equation.manifest with
991
        | Some (Odoc_model.Lang.TypeExpr.Polymorphic_variant variant) ->
74✔
992
            let code =
993
              polymorphic_variant
994
                ~type_ident:(t.id :> Paths.Identifier.t)
995
                variant
996
            in
997
            let manifest =
74✔
998
              O.documentedSrc
74✔
999
                (O.ignore intro
74✔
1000
                ++ O.txt (if is_substitution then " :=" else " =")
×
1001
                ++ O.sp
74✔
1002
                ++
74✔
1003
                if t.equation.private_ then
1004
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
1005
                else O.noop)
66✔
1006
              @ code
1007
            in
1008
            (manifest, false, O.noop)
1009
        | _ ->
2,917✔
1010
            let manifest, need_private =
1011
              format_manifest ~is_substitution t.equation
1012
            in
1013
            let text = O.ignore intro ++ manifest in
2,917✔
1014
            (O.documentedSrc @@ text, need_private, text)
2,917✔
1015
      in
1016
      let representation =
1017
        match t.representation with
1018
        | None -> []
2,629✔
1019
        | Some repr ->
362✔
1020
            let content =
1021
              match repr with
1022
              | Extensible -> O.documentedSrc (O.txt "..")
61✔
1023
              | Variant cstrs -> variant cstrs
235✔
1024
              | Record fields -> record fields
66✔
1025
              | Record_unboxed_product fields -> unboxed_record fields
×
1026
            in
1027
            if List.length content > 0 then
362✔
1028
              O.documentedSrc
362✔
1029
                (O.ignore long_prefix ++ O.txt " =" ++ O.sp
362✔
1030
                ++
362✔
1031
                if need_private then
1032
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
1033
                else O.noop)
354✔
1034
              @ content
1035
            else []
×
1036
      in
1037
      let content =
1038
        O.documentedSrc intro @ manifest @ representation
2,991✔
1039
        @ O.documentedSrc constraints
2,991✔
1040
        @ O.documentedSrc
2,991✔
1041
            (if Syntax.Type.type_def_semicolon then O.txt ";" else O.noop)
×
1042
      in
1043
      let attr = "type" :: (if is_substitution then [ "subst" ] else []) in
23✔
1044
      let anchor = path_to_id t.id in
1045
      let doc = Comment.to_ir t.doc.elements in
2,991✔
1046
      let source_anchor = source_anchor t.source_loc in
2,991✔
1047
      Item.Declaration { attr; anchor; doc; content; source_anchor }
2,991✔
1048
  end
1049

1050
  open Type_declaration
1051

1052
  module Value : sig
1053
    val value : Lang.Value.t -> Item.t
1054
  end = struct
1055
    let value (t : Odoc_model.Lang.Value.t) =
1056
      let extra_attr, semicolon =
912✔
1057
        match t.value with
1058
        | Abstract -> ([], Syntax.Value.semicolon)
888✔
1059
        | External _ -> ([ "external" ], Syntax.Type.External.semicolon)
24✔
1060
      in
1061
      let name = Paths.Identifier.name t.id in
1062
      let content =
912✔
1063
        O.documentedSrc
1064
          (O.box_hv
912✔
1065
          @@ O.keyword Syntax.Value.variable_keyword
912✔
1066
             ++ O.txt " " ++ O.txt name
912✔
1067
             ++ O.txt Syntax.Type.annotation_separator
912✔
1068
             ++ O.cut ++ type_expr t.type_
912✔
1069
             ++ Modalities.format t.modalities
912✔
UNCOV
1070
             ++ if semicolon then O.txt ";" else O.noop)
×
1071
      in
1072
      let attr = [ "value" ] @ extra_attr in
912✔
1073
      let anchor = path_to_id t.id in
1074
      let doc = Comment.to_ir t.doc.elements in
912✔
1075
      let source_anchor = source_anchor t.source_loc in
912✔
1076
      Item.Declaration { attr; anchor; doc; content; source_anchor }
912✔
1077
  end
1078

1079
  open Value
1080

1081
  (* This chunk of code is responsible for sectioning list of items
1082
     according to headings by extracting headings as Items.
1083

1084
     TODO: This sectioning would be better done as a pass on the model directly.
1085
  *)
1086
  module Sectioning : sig
1087
    open Odoc_model
1088

1089
    val comment_items : Comment.elements -> Item.t list
1090

1091
    val docs : Comment.elements -> Item.t list * Item.t list
1092
  end = struct
1093
    let take_until_heading_or_end (docs : Odoc_model.Comment.elements) =
1094
      let content, _, rest =
693✔
1095
        Doctree.Take.until docs ~classify:(fun b ->
1096
            match b.Location.value with
1,481✔
1097
            | `Heading _ -> Stop_and_keep
202✔
1098
            | #Odoc_model.Comment.attached_block_element as doc ->
1,279✔
1099
                let content = Comment.attached_block_element doc in
1100
                Accum content)
1,279✔
1101
      in
1102
      (content, rest)
693✔
1103

1104
    let comment_items (input0 : Odoc_model.Comment.elements) =
1105
      let rec loop input_comment acc =
963✔
1106
        match input_comment with
2,493✔
1107
        | [] -> List.rev acc
963✔
1108
        | element :: input_comment -> (
1,530✔
1109
            match element.Location.value with
1110
            | `Heading h ->
837✔
1111
                let item = Comment.heading h in
1112
                loop input_comment (item :: acc)
837✔
1113
            | _ ->
693✔
1114
                let content, input_comment =
1115
                  take_until_heading_or_end (element :: input_comment)
1116
                in
1117
                let item = Item.Text content in
693✔
1118
                loop input_comment (item :: acc))
1119
      in
1120
      loop input0 []
1121

1122
    (* For doc pages, we want the header to contain everything until
1123
       the first heading, then everything before the next heading which
1124
       is either lower, or a section.
1125
    *)
1126
    let docs input_comment =
1127
      let items = comment_items input_comment in
69✔
1128
      let until_first_heading, o, items =
69✔
1129
        Doctree.Take.until items ~classify:(function
1130
          | Item.Heading h as i -> Stop_and_accum ([ i ], Some h.level)
69✔
1131
          | i -> Accum [ i ])
×
1132
      in
1133
      match o with
69✔
1134
      | None -> (until_first_heading, items)
×
1135
      | Some level ->
69✔
1136
          let max_level = if level = 1 then 2 else level in
×
1137
          let before_second_heading, _, items =
1138
            Doctree.Take.until items ~classify:(function
1139
              | Item.Heading h when h.level >= max_level -> Stop_and_keep
13✔
1140
              | i -> Accum [ i ])
38✔
1141
          in
1142
          let header = until_first_heading @ before_second_heading in
69✔
1143
          (header, items)
1144
  end
1145

1146
  module Class : sig
1147
    val class_ : Lang.Class.t -> Item.t
1148

1149
    val class_type : Lang.ClassType.t -> Item.t
1150
  end = struct
1151
    let class_type_expr (cte : Odoc_model.Lang.ClassType.expr) =
1152
      match cte with
249✔
1153
      | Constr (path, args) ->
58✔
1154
          let link = Link.from_path (path :> Paths.Path.t) in
1155
          format_type_path ~delim:`brackets args link
58✔
1156
      | Signature _ ->
191✔
1157
          Syntax.Class.open_tag ++ O.txt " ... " ++ Syntax.Class.close_tag
191✔
1158

1159
    let method_ (t : Odoc_model.Lang.Method.t) =
1160
      let name = Paths.Identifier.name t.id in
90✔
1161
      let virtual_ =
90✔
1162
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1163
      in
1164
      let private_ =
1165
        if t.private_ then O.keyword "private" ++ O.txt " " else O.noop
8✔
1166
      in
1167
      let content =
1168
        O.documentedSrc
1169
          (O.keyword "method" ++ O.txt " " ++ private_ ++ virtual_ ++ O.txt name
90✔
1170
          ++ O.txt Syntax.Type.annotation_separator
90✔
1171
          ++ type_expr t.type_)
90✔
1172
      in
1173
      let attr = [ "method" ] in
90✔
1174
      let anchor = path_to_id t.id in
1175
      let doc = Comment.to_ir t.doc.elements in
90✔
1176
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
90✔
1177

1178
    let instance_variable (t : Odoc_model.Lang.InstanceVariable.t) =
1179
      let name = Paths.Identifier.name t.id in
17✔
1180
      let virtual_ =
17✔
1181
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1182
      in
1183
      let mutable_ =
1184
        if t.mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop
8✔
1185
      in
1186
      let content =
1187
        O.documentedSrc
1188
          (O.keyword "val" ++ O.txt " " ++ mutable_ ++ virtual_ ++ O.txt name
17✔
1189
          ++ O.txt Syntax.Type.annotation_separator
17✔
1190
          ++ type_expr t.type_)
17✔
1191
      in
1192
      let attr = [ "value"; "instance-variable" ] in
17✔
1193
      let anchor = path_to_id t.id in
1194
      let doc = Comment.to_ir t.doc.elements in
17✔
1195
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
17✔
1196

1197
    let inherit_ (ih : Lang.ClassSignature.Inherit.t) =
1198
      let cte =
16✔
1199
        match ih.expr with
1200
        | Signature _ -> assert false (* Bold. *)
1201
        | cty -> cty
16✔
1202
      in
1203
      let content =
1204
        O.documentedSrc (O.keyword "inherit" ++ O.txt " " ++ class_type_expr cte)
16✔
1205
      in
1206
      let attr = [ "inherit" ] in
16✔
1207
      let anchor = None in
1208
      let doc = Comment.to_ir ih.doc.elements in
1209
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
16✔
1210

1211
    let constraint_ (cst : Lang.ClassSignature.Constraint.t) =
1212
      let content =
8✔
1213
        O.documentedSrc (format_constraints [ (cst.left, cst.right) ])
8✔
1214
      in
1215
      let attr = [] in
8✔
1216
      let anchor = None in
1217
      let doc = Comment.to_ir cst.doc.elements in
1218
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
8✔
1219

1220
    let class_signature (c : Lang.ClassSignature.t) =
1221
      let rec loop l acc_items =
233✔
1222
        match l with
388✔
1223
        | [] -> List.rev acc_items
233✔
1224
        | item :: rest -> (
155✔
1225
            let continue item = loop rest (item :: acc_items) in
131✔
1226
            match (item : Lang.ClassSignature.item) with
1227
            | Inherit cty -> continue @@ inherit_ cty
16✔
1228
            | Method m -> continue @@ method_ m
90✔
1229
            | InstanceVariable v -> continue @@ instance_variable v
17✔
1230
            | Constraint cst -> continue @@ constraint_ cst
8✔
1231
            | Comment `Stop ->
8✔
1232
                let rest =
1233
                  List.skip_until rest ~p:(function
1234
                    | Lang.ClassSignature.Comment `Stop -> true
8✔
1235
                    | _ -> false)
8✔
1236
                in
1237
                loop rest acc_items
8✔
1238
            | Comment (`Docs c) ->
16✔
1239
                let items = Sectioning.comment_items c.elements in
1240
                loop rest (List.rev_append items acc_items))
16✔
1241
      in
1242
      (* FIXME: use [t.self] *)
1243
      (c.doc.elements, loop c.items [])
233✔
1244

1245
    let rec class_decl (cd : Odoc_model.Lang.Class.decl) =
1246
      match cd with
182✔
1247
      | ClassType expr -> class_type_expr expr
166✔
1248
      (* TODO: factorize the following with [type_expr] *)
1249
      | Arrow (None, src, dst) ->
16✔
1250
          O.span
16✔
1251
            (type_expr ~needs_parentheses:true src
16✔
1252
            ++ O.txt " " ++ Syntax.Type.arrow)
16✔
1253
          ++ O.txt " " ++ class_decl dst
16✔
1254
      | Arrow (Some (RawOptional _ as lbl), _src, dst) ->
×
1255
          O.span
×
1256
            (O.box_hv
×
1257
            @@ label lbl ++ O.txt ":"
×
1258
               ++ tag "error" (O.txt "???")
×
1259
               ++ O.txt " " ++ Syntax.Type.arrow)
×
1260
          ++ O.sp ++ class_decl dst
×
1261
      | Arrow (Some lbl, src, dst) ->
×
1262
          O.span
×
1263
            (label lbl ++ O.txt ":"
×
1264
            ++ type_expr ~needs_parentheses:true src
×
1265
            ++ O.txt " " ++ Syntax.Type.arrow)
×
1266
          ++ O.txt " " ++ class_decl dst
×
1267

1268
    let class_ (t : Odoc_model.Lang.Class.t) =
1269
      let name = Paths.Identifier.name t.id in
166✔
1270
      let params =
166✔
1271
        match t.params with
1272
        | [] -> O.noop
142✔
1273
        | _ :: _ as params -> format_params ~delim:`brackets params ++ O.txt " "
24✔
1274
      in
1275
      let virtual_ =
1276
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
24✔
1277
      in
1278

1279
      let source_anchor = source_anchor t.source_loc in
1280
      let cname, expansion, expansion_doc =
166✔
1281
        match t.expansion with
1282
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1283
        | Some csig ->
166✔
1284
            let expansion_doc, items = class_signature csig in
1285
            let url = Url.Path.from_identifier t.id in
166✔
1286
            let page =
166✔
1287
              make_expansion_page ~source_anchor url
1288
                [ t.doc.elements; expansion_doc ]
1289
                items
1290
            in
1291
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
166✔
1292
              Some page,
1293
              Some expansion_doc )
1294
      in
1295
      let summary =
1296
        O.txt Syntax.Type.annotation_separator ++ class_decl t.type_
166✔
1297
      in
1298
      let cd =
166✔
1299
        attach_expansion
1300
          (Syntax.Type.annotation_separator, "object", "end")
1301
          expansion summary
1302
      in
1303
      let content =
166✔
1304
        O.documentedSrc (O.keyword "class" ++ O.txt " " ++ virtual_ ++ params)
166✔
1305
        @ cname @ cd
1306
      in
1307
      let attr = [ "class" ] in
1308
      let anchor = path_to_id t.id in
1309
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
166✔
1310
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1311

1312
    let class_type (t : Odoc_model.Lang.ClassType.t) =
1313
      let name = Paths.Identifier.name t.id in
67✔
1314
      let params = format_params ~delim:`brackets t.params in
67✔
1315
      let virtual_ =
67✔
1316
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1317
      in
1318
      let source_anchor = source_anchor t.source_loc in
1319
      let cname, expansion, expansion_doc =
67✔
1320
        match t.expansion with
1321
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1322
        | Some csig ->
67✔
1323
            let url = Url.Path.from_identifier t.id in
1324
            let expansion_doc, items = class_signature csig in
67✔
1325
            let page =
67✔
1326
              make_expansion_page ~source_anchor url
1327
                [ t.doc.elements; expansion_doc ]
1328
                items
1329
            in
1330
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
67✔
1331
              Some page,
1332
              Some expansion_doc )
1333
      in
1334
      let summary = O.txt " = " ++ class_type_expr t.expr in
67✔
1335
      let expr = attach_expansion (" = ", "object", "end") expansion summary in
67✔
1336
      let content =
67✔
1337
        O.documentedSrc
67✔
1338
          (O.keyword "class" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
67✔
1339
         ++ virtual_ ++ params ++ O.txt " ")
67✔
1340
        @ cname @ expr
1341
      in
1342
      let attr = [ "class-type" ] in
1343
      let anchor = path_to_id t.id in
1344
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
67✔
1345
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1346
  end
1347

1348
  open Class
1349

1350
  module Module : sig
1351
    val signature : Lang.Signature.t -> Comment.Comment.elements * Item.t list
1352
    (** Returns [header_doc, content]. *)
1353
  end = struct
1354
    let internal_module m =
1355
      let open Lang.Module in
1,924✔
1356
      match m.id.iv with
1357
      | `Module (_, name) when ModuleName.is_hidden name -> true
81✔
1358
      | _ -> false
1,843✔
1359

1360
    let internal_type t =
1361
      let open Lang.TypeDecl in
2,969✔
1362
      match t.id.iv with
1363
      | `Type (_, name) when TypeName.is_hidden name -> true
1✔
1364
      | _ -> false
2,968✔
1365

1366
    let internal_value v =
1367
      let open Lang.Value in
1,018✔
1368
      match v.id.iv with
1369
      | `Value (_, name) when ValueName.is_hidden name -> true
106✔
1370
      | _ -> false
912✔
1371

1372
    let internal_module_type t =
1373
      let open Lang.ModuleType in
1,361✔
1374
      match t.id.iv with
1375
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1376
      | _ -> false
1,361✔
1377

1378
    let internal_module_substitution t =
1379
      let open Lang.ModuleSubstitution in
8✔
1380
      match t.id.iv with
1381
      | `Module (_, name) when ModuleName.is_hidden name -> true
×
1382
      | _ -> false
8✔
1383

1384
    let internal_module_type_substitution t =
1385
      let open Lang.ModuleTypeSubstitution in
8✔
1386
      match t.id.iv with
1387
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1388
      | _ -> false
8✔
1389

1390
    let rec signature (s : Lang.Signature.t) =
1391
      let rec loop l acc_items =
3,582✔
1392
        match l with
12,543✔
1393
        | [] -> List.rev acc_items
3,582✔
1394
        | item :: rest -> (
8,961✔
1395
            let continue (item : Item.t) = loop rest (item :: acc_items) in
7,838✔
1396
            match (item : Lang.Signature.item) with
1397
            | Module (_, m) when internal_module m -> loop rest acc_items
81✔
1398
            | Type (_, t) when internal_type t -> loop rest acc_items
1✔
1399
            | Value v when internal_value v -> loop rest acc_items
106✔
1400
            | ModuleType m when internal_module_type m -> loop rest acc_items
×
1401
            | ModuleSubstitution m when internal_module_substitution m ->
8✔
1402
                loop rest acc_items
×
1403
            | ModuleTypeSubstitution m when internal_module_type_substitution m
8✔
1404
              ->
1405
                loop rest acc_items
×
1406
            | ModuleTypeSubstitution m -> continue @@ module_type_substitution m
8✔
1407
            | Module (_, m) -> continue @@ module_ m
1,843✔
1408
            | ModuleType m -> continue @@ module_type m
1,361✔
1409
            | Class (_, c) -> continue @@ class_ c
166✔
1410
            | ClassType (_, c) -> continue @@ class_type c
67✔
1411
            | Include m -> continue @@ include_ m
290✔
1412
            | ModuleSubstitution m -> continue @@ module_substitution m
8✔
1413
            | TypeSubstitution t ->
23✔
1414
                continue @@ type_decl ~is_substitution:true (Ordinary, t)
23✔
1415
            | Type (r, t) -> continue @@ type_decl (r, t)
2,968✔
1416
            | TypExt e -> continue @@ extension e
126✔
1417
            | Exception e -> continue @@ exn e
66✔
1418
            | Value v -> continue @@ value v
912✔
1419
            | Open o ->
82✔
1420
                let items = Sectioning.comment_items o.doc.elements in
1421
                loop rest (List.rev_append items acc_items)
82✔
1422
            | Comment `Stop ->
57✔
1423
                let rest =
1424
                  List.skip_until rest ~p:(function
1425
                    | Lang.Signature.Comment `Stop -> true
49✔
1426
                    | _ -> false)
65✔
1427
                in
1428
                loop rest acc_items
57✔
1429
            | Comment (`Docs c) ->
796✔
1430
                let items = Sectioning.comment_items c.elements in
1431
                loop rest (List.rev_append items acc_items))
796✔
1432
      in
1433
      ((Lang.extract_signature_doc s).elements, loop s.items [])
3,582✔
1434

1435
    and functor_parameter :
1436
        Odoc_model.Lang.FunctorParameter.parameter -> DocumentedSrc.t =
1437
     fun arg ->
1438
      let open Odoc_model.Lang.FunctorParameter in
211✔
1439
      let name = Paths.Identifier.name arg.id in
1440
      let render_ty = arg.expr in
211✔
1441
      let modtyp =
1442
        mty_in_decl (arg.id :> Paths.Identifier.Signature.t) render_ty
1443
      in
1444
      let modname, mod_decl =
211✔
1445
        match expansion_of_module_type_expr arg.expr with
1446
        | None ->
×
1447
            let modname = O.txt (Paths.Identifier.name arg.id) in
×
1448
            (modname, O.documentedSrc modtyp)
×
1449
        | Some (expansion_doc, items) ->
211✔
1450
            let url = Url.Path.from_identifier arg.id in
1451
            let modname = path url [ inline @@ Text name ] in
211✔
1452
            let type_with_expansion =
211✔
1453
              let content =
1454
                make_expansion_page ~source_anchor:None url [ expansion_doc ]
1455
                  items
1456
              in
1457
              let summary = O.render modtyp in
211✔
1458
              let status = `Default in
211✔
1459
              let expansion =
1460
                O.documentedSrc
211✔
1461
                  (O.txt Syntax.Type.annotation_separator ++ O.keyword "sig")
211✔
1462
                @ DocumentedSrc.[ Subpage { content; status } ]
1463
                @ O.documentedSrc (O.keyword "end")
211✔
1464
              in
1465
              DocumentedSrc.
1466
                [
1467
                  Alternative
1468
                    (Expansion { status = `Default; summary; url; expansion });
1469
                ]
1470
            in
1471
            (modname, type_with_expansion)
1472
      in
1473
      O.documentedSrc (O.keyword "module" ++ O.txt " ")
211✔
1474
      @ O.documentedSrc modname @ mod_decl
211✔
1475

1476
    and module_substitution (t : Odoc_model.Lang.ModuleSubstitution.t) =
1477
      let name = Paths.Identifier.name t.id in
8✔
1478
      let path = Link.from_path (t.manifest :> Paths.Path.t) in
8✔
1479
      let content =
8✔
1480
        O.documentedSrc
1481
          (O.keyword "module" ++ O.txt " " ++ O.txt name ++ O.txt " :=" ++ O.sp
8✔
1482
         ++ path)
8✔
1483
      in
1484
      let attr = [ "module-substitution" ] in
8✔
1485
      let anchor = path_to_id t.id in
1486
      let doc = Comment.to_ir t.doc.elements in
8✔
1487
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
8✔
1488

1489
    and module_type_substitution (t : Odoc_model.Lang.ModuleTypeSubstitution.t)
1490
        =
1491
      let prefix =
8✔
1492
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
8✔
1493
      in
1494
      let source_anchor = None in
8✔
1495
      let modname = Paths.Identifier.name t.id in
1496
      let modname, expansion_doc, mty =
8✔
1497
        module_type_manifest ~subst:true ~source_anchor modname t.id
1498
          t.doc.elements (Some t.manifest) prefix
1499
      in
1500
      let content =
8✔
1501
        O.documentedSrc (prefix ++ modname)
8✔
1502
        @ mty
1503
        @ O.documentedSrc
8✔
1504
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1505
      in
1506
      let attr = [ "module-type" ] in
1507
      let anchor = path_to_id t.id in
1508
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
8✔
1509
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1510

1511
    and simple_expansion :
1512
        Odoc_model.Lang.ModuleType.simple_expansion ->
1513
        Comment.Comment.elements * Item.t list =
1514
     fun t ->
1515
      let rec extract_functor_params
2,933✔
1516
          (f : Odoc_model.Lang.ModuleType.simple_expansion) =
1517
        match f with
3,152✔
1518
        | Signature sg -> (None, sg)
2,933✔
1519
        | Functor (p, expansion) ->
219✔
1520
            let add_to params =
1521
              match p with Unit -> params | Named p -> p :: params
8✔
1522
            in
1523
            let params, sg = extract_functor_params expansion in
1524
            let params = match params with None -> [] | Some p -> p in
36✔
1525
            (Some (add_to params), sg)
219✔
1526
      in
1527
      match extract_functor_params t with
1528
      | None, sg -> signature sg
2,750✔
1529
      | Some params, sg ->
183✔
1530
          let sg_doc, content = signature sg in
1531
          let params =
183✔
1532
            let decl_of_arg arg =
1533
              let content = functor_parameter arg in
211✔
1534
              let attr = [ "parameter" ] in
211✔
1535
              let anchor =
1536
                Some (Url.Anchor.from_identifier (arg.id :> Paths.Identifier.t))
211✔
1537
              in
1538
              let doc = [] in
1539
              [
1540
                Item.Declaration
1541
                  { content; anchor; attr; doc; source_anchor = None };
1542
              ]
1543
            in
1544
            List.concat_map decl_of_arg params
183✔
1545
          in
1546
          let prelude = mk_heading ~label:"parameters" "Parameters" :: params
183✔
1547
          and content = mk_heading ~label:"signature" "Signature" :: content in
183✔
1548
          (sg_doc, prelude @ content)
1549

1550
    and expansion_of_module_type_expr :
1551
        Odoc_model.Lang.ModuleType.expr ->
1552
        (Comment.Comment.elements * Item.t list) option =
1553
     fun t ->
1554
      let rec simple_expansion_of (t : Odoc_model.Lang.ModuleType.expr) =
3,218✔
1555
        match t with
3,437✔
1556
        | Path { p_expansion = None; _ }
379✔
1557
        | TypeOf { t_expansion = None; _ }
8✔
1558
        | With { w_expansion = None; _ }
×
1559
        | Strengthen { s_expansion = None; _ } ->
×
1560
            None
1561
        | Path { p_expansion = Some e; _ }
426✔
1562
        | TypeOf { t_expansion = Some e; _ }
56✔
1563
        | With { w_expansion = Some e; _ }
218✔
1564
        | Strengthen { s_expansion = Some e; _ } ->
×
1565
            Some e
1566
        | Signature sg -> Some (Signature sg)
2,131✔
1567
        | Functor (f_parameter, e) -> (
219✔
1568
            match simple_expansion_of e with
1569
            | Some e -> Some (Functor (f_parameter, e))
211✔
1570
            | None -> None)
8✔
1571
      in
1572
      match simple_expansion_of t with
1573
      | None -> None
387✔
1574
      | Some e -> Some (simple_expansion e)
2,831✔
1575

1576
    and module_ : Odoc_model.Lang.Module.t -> Item.t =
1577
     fun t ->
1578
      let modname = Paths.Identifier.name t.id in
1,843✔
1579
      let expansion =
1,843✔
1580
        match t.type_ with
1581
        | Alias (_, Some e) -> Some (simple_expansion e)
102✔
1582
        | Alias (_, None) -> None
186✔
1583
        | ModuleType e -> expansion_of_module_type_expr e
1,555✔
1584
      in
1585
      let source_anchor = source_anchor t.source_loc in
1586
      let modname, status, expansion, expansion_doc =
1,843✔
1587
        match expansion with
1588
        | None -> (O.txt modname, `Default, None, None)
322✔
1589
        | Some (expansion_doc, items) ->
1,521✔
1590
            let status =
1591
              match t.type_ with
1592
              | ModuleType (Signature _) -> `Inline
987✔
1593
              | _ -> `Default
534✔
1594
            in
1595
            let url = Url.Path.from_identifier t.id in
1596
            let link = path url [ inline @@ Text modname ] in
1,521✔
1597
            let page =
1,521✔
1598
              make_expansion_page ~source_anchor url
1599
                [ t.doc.elements; expansion_doc ]
1600
                items
1601
            in
1602
            (link, status, Some page, Some expansion_doc)
1,521✔
1603
      in
1604
      let intro = O.keyword "module" ++ O.txt " " ++ modname in
1,843✔
1605
      let summary = O.ignore intro ++ mdexpr_in_decl t.id t.type_ in
1,843✔
1606
      let modexpr =
1,843✔
1607
        attach_expansion ~status
1608
          (Syntax.Type.annotation_separator, "sig", "end")
1609
          expansion summary
1610
      in
1611
      let content =
1,843✔
1612
        O.documentedSrc intro @ modexpr
1,843✔
1613
        @ O.documentedSrc
1,843✔
1614
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1615
      in
1616
      let attr = [ "module" ] in
1617
      let anchor = path_to_id t.id in
1618
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
1,843✔
1619
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1620

1621
    and simple_expansion_in_decl (base : Paths.Identifier.Module.t) se =
1622
      let rec ty_of_se :
102✔
1623
          Lang.ModuleType.simple_expansion -> Lang.ModuleType.expr = function
1624
        | Signature sg -> Signature sg
102✔
1625
        | Functor (arg, sg) -> Functor (arg, ty_of_se sg)
×
1626
      in
1627
      mty_in_decl (base :> Paths.Identifier.Signature.t) (ty_of_se se)
102✔
1628

1629
    and mdexpr_in_decl (base : Paths.Identifier.Module.t) md =
1630
      let sig_dotdotdot =
1,843✔
1631
        O.txt Syntax.Type.annotation_separator
1,843✔
1632
        ++ O.cut ++ Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
1,843✔
1633
      in
1634
      match md with
1,843✔
1635
      | Alias (_, Some se) -> simple_expansion_in_decl base se
102✔
1636
      | Alias (p, _) when not Paths.Path.(is_hidden (p :> t)) ->
186✔
1637
          O.txt " =" ++ O.sp ++ mdexpr md
186✔
1638
      | Alias _ -> sig_dotdotdot
×
1639
      | ModuleType mt -> mty_in_decl (base :> Paths.Identifier.Signature.t) mt
1,555✔
1640

1641
    and mdexpr : Odoc_model.Lang.Module.decl -> text = function
1642
      | Alias (mod_path, _) -> Link.from_path (mod_path :> Paths.Path.t)
242✔
1643
      | ModuleType mt -> mty mt
×
1644

1645
    and module_type_manifest ~subst ~source_anchor modname id doc manifest
1646
        prefix =
1647
      let expansion =
1,369✔
1648
        match manifest with
1649
        | None -> None
128✔
1650
        | Some e -> expansion_of_module_type_expr e
1,241✔
1651
      in
1652
      let modname, expansion, expansion_doc =
1653
        match expansion with
1654
        | None -> (O.txt modname, None, None)
379✔
1655
        | Some (expansion_doc, items) ->
990✔
1656
            let url = Url.Path.from_identifier id in
1657
            let link = path url [ inline @@ Text modname ] in
990✔
1658
            let page =
990✔
1659
              make_expansion_page ~source_anchor url [ doc; expansion_doc ]
1660
                items
1661
            in
1662
            (link, Some page, Some expansion_doc)
990✔
1663
      in
1664
      let summary =
1665
        match manifest with
1666
        | None -> O.noop
128✔
1667
        | Some expr ->
1,241✔
1668
            O.ignore (prefix ++ modname)
1,241✔
1669
            ++ (if subst then O.txt " :=" ++ O.sp else O.txt " =" ++ O.sp)
8✔
1670
            ++ mty expr
1,241✔
1671
      in
1672
      ( modname,
1673
        expansion_doc,
1674
        attach_expansion (" = ", "sig", "end") expansion summary )
1,369✔
1675

1676
    and module_type (t : Odoc_model.Lang.ModuleType.t) =
1677
      let prefix =
1,361✔
1678
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
1,361✔
1679
      in
1680
      let modname = Paths.Identifier.name t.id in
1,361✔
1681
      let source_anchor = source_anchor t.source_loc in
1,361✔
1682
      let modname, expansion_doc, mty =
1,361✔
1683
        module_type_manifest ~subst:false ~source_anchor modname t.id
1684
          t.doc.elements t.expr prefix
1685
      in
1686
      let content =
1,361✔
1687
        O.documentedSrc (prefix ++ modname)
1,361✔
1688
        @ mty
1689
        @ O.documentedSrc
1,361✔
1690
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1691
      in
1692
      let attr = [ "module-type" ] in
1693
      let anchor = path_to_id t.id in
1694
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
1,361✔
1695
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1696

1697
    and umty_hidden : Odoc_model.Lang.ModuleType.U.expr -> bool = function
1698
      | Path p -> Paths.Path.(is_hidden (p :> t))
403✔
1699
      | With (_, expr) -> umty_hidden expr
25✔
1700
      | TypeOf (ModPath m, _) | TypeOf (StructInclude m, _) ->
42✔
1701
          Paths.Path.(is_hidden (m :> t))
1702
      | Signature _ -> false
14✔
1703
      | Strengthen (expr, p, _) ->
×
1704
          umty_hidden expr || Paths.Path.(is_hidden (p :> t))
×
1705

1706
    and mty_hidden : Odoc_model.Lang.ModuleType.expr -> bool = function
1707
      | Path { p_path = mty_path; _ } -> Paths.Path.(is_hidden (mty_path :> t))
837✔
1708
      | With { w_expr; _ } -> umty_hidden w_expr
218✔
1709
      | TypeOf { t_desc = ModPath m; _ }
48✔
1710
      | TypeOf { t_desc = StructInclude m; _ } ->
16✔
1711
          Paths.Path.(is_hidden (m :> t))
1712
      | _ -> false
2,297✔
1713

1714
    and mty_with subs expr =
1715
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
238✔
1716
      ++ O.list
238✔
1717
           ~sep:(O.cut ++ O.txt " " ++ O.keyword "and" ++ O.txt " ")
238✔
1718
           ~f:(fun x -> O.span (substitution x))
279✔
1719
           subs
1720

1721
    and mty_strengthen expr path =
1722
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
×
1723
      ++ Link.from_path (path :> Paths.Path.t)
×
1724

1725
    and mty_typeof t_desc =
1726
      match t_desc with
154✔
1727
      | Odoc_model.Lang.ModuleType.ModPath m ->
90✔
1728
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
90✔
1729
          ++ O.keyword "of" ++ O.txt " "
90✔
1730
          ++ Link.from_path (m :> Paths.Path.t)
90✔
1731
      | StructInclude m ->
64✔
1732
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
64✔
1733
          ++ O.keyword "of" ++ O.txt " " ++ O.keyword "struct" ++ O.txt " "
64✔
1734
          ++ O.keyword "include" ++ O.txt " "
64✔
1735
          ++ Link.from_path (m :> Paths.Path.t)
64✔
1736
          ++ O.txt " " ++ O.keyword "end"
64✔
1737

1738
    and is_elidable_with_u : Odoc_model.Lang.ModuleType.U.expr -> bool =
1739
      function
1740
      | Path _ -> false
213✔
1741
      | Signature _ -> true
5✔
1742
      | With (_, expr) -> is_elidable_with_u expr
×
1743
      | TypeOf _ -> false
25✔
1744
      | Strengthen (expr, _, _) -> is_elidable_with_u expr
×
1745

1746
    and umty : Odoc_model.Lang.ModuleType.U.expr -> text =
1747
     fun m ->
1748
      match m with
527✔
1749
      | Path p -> Link.from_path (p :> Paths.Path.t)
403✔
1750
      | Signature _ ->
9✔
1751
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
9✔
1752
      | With (_, expr) when is_elidable_with_u expr ->
25✔
1753
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
5✔
1754
      | With (subs, expr) -> mty_with subs expr
20✔
1755
      | TypeOf (t_desc, _) -> mty_typeof t_desc
90✔
1756
      | Strengthen (expr, _, _) when is_elidable_with_u expr ->
×
1757
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1758
      | Strengthen (expr, p, _) -> mty_strengthen expr (p :> Paths.Path.t)
×
1759

1760
    and mty : Odoc_model.Lang.ModuleType.expr -> text =
1761
     fun m ->
1762
      if mty_hidden m then
3,416✔
1763
        Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1764
      else
1765
        match m with
3,416✔
1766
        | Path { p_path = mty_path; _ } ->
837✔
1767
            Link.from_path (mty_path :> Paths.Path.t)
1768
        | Functor (Unit, expr) ->
×
1769
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1770
            ++ O.span (O.txt " () " ++ Syntax.Type.arrow)
×
1771
            ++ O.sp ++ mty expr
×
1772
        | Functor (Named arg, expr) ->
48✔
1773
            let arg_expr = arg.expr in
1774
            let stop_before = expansion_of_module_type_expr arg_expr = None in
48✔
1775
            let name =
1776
              let open Odoc_model.Lang.FunctorParameter in
1777
              let name = Paths.Identifier.name arg.id in
1778
              let href =
48✔
1779
                Url.from_identifier ~stop_before (arg.id :> Paths.Identifier.t)
1780
              in
1781
              resolved href [ inline @@ Text name ]
48✔
1782
            in
1783
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1784
            ++ (O.box_hv @@ O.span
48✔
1785
               @@ O.txt " (" ++ name
48✔
1786
                  ++ O.txt Syntax.Type.annotation_separator
48✔
1787
                  ++ mty arg_expr ++ O.txt ")" ++ O.txt " " ++ Syntax.Type.arrow
48✔
1788
               )
1789
            ++ O.sp ++ mty expr
48✔
1790
        | With { w_expr; _ } when is_elidable_with_u w_expr ->
218✔
1791
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1792
        | With { w_substitutions; w_expr; _ } ->
218✔
1793
            O.box_hv @@ mty_with w_substitutions w_expr
218✔
1794
        | TypeOf { t_desc; _ } -> mty_typeof t_desc
64✔
1795
        | Signature _ ->
2,249✔
1796
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
2,249✔
1797
        | Strengthen { s_expr; _ } when is_elidable_with_u s_expr ->
×
1798
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1799
        | Strengthen { s_expr; s_path; _ } ->
×
1800
            O.box_hv @@ mty_strengthen s_expr (s_path :> Paths.Path.t)
×
1801

1802
    and mty_in_decl :
1803
        Paths.Identifier.Signature.t -> Odoc_model.Lang.ModuleType.expr -> text
1804
        =
1805
     fun base -> function
1806
      | (Path _ | Signature _ | With _ | TypeOf _ | Strengthen _) as m ->
×
1807
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
1,868✔
1808
      | Functor _ as m when not Syntax.Mod.functor_contraction ->
171✔
1809
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
×
1810
      | Functor (arg, expr) ->
171✔
1811
          let text_arg =
1812
            match arg with
1813
            | Unit -> O.txt "()"
8✔
1814
            | Named arg ->
163✔
1815
                let arg_expr = arg.expr in
1816
                let stop_before =
1817
                  expansion_of_module_type_expr arg_expr = None
163✔
1818
                in
1819
                let name =
1820
                  let open Odoc_model.Lang.FunctorParameter in
1821
                  let name = Paths.Identifier.name arg.id in
1822
                  let href =
163✔
1823
                    Url.from_identifier ~stop_before
1824
                      (arg.id :> Paths.Identifier.t)
1825
                  in
1826
                  resolved href [ inline @@ Text name ]
163✔
1827
                in
1828
                O.box_hv
163✔
1829
                @@ O.txt "(" ++ name
163✔
1830
                   ++ O.txt Syntax.Type.annotation_separator
163✔
1831
                   ++ O.cut ++ mty arg.expr ++ O.txt ")"
163✔
1832
          in
1833
          O.sp ++ text_arg ++ mty_in_decl base expr
171✔
1834

1835
    (* TODO : Centralize the list juggling for type parameters *)
1836
    and type_expr_in_subst td typath =
1837
      let typath = Link.from_fragment typath in
151✔
1838
      match td.Lang.TypeDecl.Equation.params with
151✔
1839
      | [] -> typath
125✔
1840
      | l -> Syntax.Type.handle_substitution_params typath (format_params l)
26✔
1841

1842
    and substitution : Odoc_model.Lang.ModuleType.substitution -> text =
1843
      function
1844
      | ModuleEq (frag_mod, md) ->
56✔
1845
          O.box_hv
1846
          @@ O.keyword "module" ++ O.txt " "
56✔
1847
             ++ Link.from_fragment (frag_mod :> Paths.Fragment.leaf)
56✔
1848
             ++ O.txt " =" ++ O.sp ++ mdexpr md
56✔
1849
      | ModuleTypeEq (frag_mty, md) ->
32✔
1850
          O.box_hv
1851
          @@ O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
32✔
1852
             ++ Link.from_fragment (frag_mty :> Paths.Fragment.leaf)
32✔
1853
             ++ O.txt " =" ++ O.sp ++ mty md
32✔
1854
      | TypeEq (frag_typ, td) ->
104✔
1855
          O.box_hv
1856
          @@ O.keyword "type" ++ O.txt " "
104✔
1857
             ++ type_expr_in_subst td (frag_typ :> Paths.Fragment.leaf)
104✔
1858
             ++ fst (format_manifest td)
104✔
1859
             ++ format_constraints
104✔
1860
                  td.Odoc_model.Lang.TypeDecl.Equation.constraints
1861
      | ModuleSubst (frag_mod, mod_path) ->
24✔
1862
          O.box_hv
1863
          @@ O.keyword "module" ++ O.txt " "
24✔
1864
             ++ Link.from_fragment (frag_mod :> Paths.Fragment.leaf)
24✔
1865
             ++ O.txt " :=" ++ O.sp
24✔
1866
             ++ Link.from_path (mod_path :> Paths.Path.t)
24✔
1867
      | ModuleTypeSubst (frag_mty, md) ->
16✔
1868
          O.box_hv
1869
          @@ O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
16✔
1870
             ++ Link.from_fragment (frag_mty :> Paths.Fragment.leaf)
16✔
1871
             ++ O.txt " :=" ++ O.sp ++ mty md
16✔
1872
      | TypeSubst (frag_typ, td) -> (
47✔
1873
          O.box_hv
1874
          @@ O.keyword "type" ++ O.txt " "
47✔
1875
             ++ type_expr_in_subst td (frag_typ :> Paths.Fragment.leaf)
47✔
1876
             ++ O.txt " :=" ++ O.sp
47✔
1877
             ++
47✔
1878
             match td.Lang.TypeDecl.Equation.manifest with
1879
             | None -> assert false (* cf loader/cmti *)
1880
             | Some te -> type_expr te)
47✔
1881

1882
    and include_ (t : Odoc_model.Lang.Include.t) =
1883
      let decl_hidden =
290✔
1884
        match t.decl with
1885
        | Alias p -> Paths.Path.(is_hidden (p :> t))
×
1886
        | ModuleType mty -> umty_hidden mty
290✔
1887
      in
1888
      let status = if decl_hidden then `Inline else t.status in
1✔
1889

1890
      let _, content = signature t.expansion.content in
1891
      let summary =
290✔
1892
        if decl_hidden then O.render (O.keyword "include" ++ O.txt " ...")
1✔
1893
        else
1894
          let include_decl =
289✔
1895
            match t.decl with
1896
            | Odoc_model.Lang.Include.Alias mod_path ->
×
1897
                Link.from_path (mod_path :> Paths.Path.t)
×
1898
            | ModuleType mt -> umty mt
289✔
1899
          in
1900
          O.render
289✔
1901
            (O.keyword "include" ++ O.txt " " ++ include_decl
289✔
1902
            ++ if Syntax.Mod.include_semicolon then O.keyword ";" else O.noop)
×
1903
      in
1904
      let content = { Include.content; status; summary } in
1905
      let attr = [ "include" ] in
1906
      let anchor = None in
1907
      let doc =
1908
        (* Documentation attached to includes behave differently than other
1909
           declarations, which show only the synopsis. We can't only show the
1910
           synopsis because no page is generated to render it and we'd loose
1911
           the full documentation.
1912
           The documentation from the expansion is not used. *)
1913
        Comment.to_ir t.doc.elements
1914
      in
1915
      Item.Include { attr; anchor; doc; content; source_anchor = None }
290✔
1916
  end
1917

1918
  open Module
1919

1920
  module Page : sig
1921
    val compilation_unit : Lang.Compilation_unit.t -> Document.t
1922

1923
    val page : Lang.Page.t -> Document.t
1924

1925
    val implementation :
1926
      Lang.Implementation.t ->
1927
      Syntax_highlighter.infos ->
1928
      string ->
1929
      Document.t list
1930
  end = struct
1931
    let pack : Lang.Compilation_unit.Packed.t -> Item.t list =
1932
     fun t ->
1933
      let f x =
×
1934
        let id = x.Lang.Compilation_unit.Packed.id in
×
1935
        let modname = Paths.Identifier.name id in
1936
        let md_def =
×
1937
          O.keyword "module" ++ O.txt " " ++ O.txt modname ++ O.txt " = "
×
1938
          ++ Link.from_path (x.path :> Paths.Path.t)
×
1939
        in
1940
        let content = O.documentedSrc md_def in
×
1941
        let anchor =
×
1942
          Some (Url.Anchor.from_identifier (id :> Paths.Identifier.t))
×
1943
        in
1944
        let attr = [ "modules" ] in
1945
        let doc = [] in
1946
        let decl = { Item.anchor; content; attr; doc; source_anchor = None } in
1947
        Item.Declaration decl
1948
      in
1949
      List.map f t
1950

1951
    let compilation_unit (t : Odoc_model.Lang.Compilation_unit.t) =
1952
      let url = Url.Path.from_identifier t.id in
359✔
1953
      let unit_doc, items =
359✔
1954
        match t.content with
1955
        | Module sign -> signature sign
359✔
1956
        | Pack packed -> ([], pack packed)
×
1957
      in
1958
      let source_anchor = source_anchor t.source_loc in
1959
      let page = make_expansion_page ~source_anchor url [ unit_doc ] items in
359✔
1960
      Document.Page page
359✔
1961

1962
    let page (t : Odoc_model.Lang.Page.t) =
1963
      (*let name =
1964
          match t.name.iv with `Page (_, name) | `LeafPage (_, name) -> name
1965
        in*)
1966
      (*let title = Odoc_model.Names.PageName.to_string name in*)
1967
      let url = Url.Path.from_identifier t.name in
69✔
1968
      let preamble, items = Sectioning.docs t.content.elements in
69✔
1969
      let source_anchor = None in
69✔
1970
      Document.Page { Page.preamble; items; url; source_anchor }
1971

1972
    let implementation (v : Odoc_model.Lang.Implementation.t) syntax_info
1973
        source_code =
1974
      match v.id with
28✔
1975
      | None -> []
×
1976
      | Some id ->
28✔
1977
          [
1978
            Document.Source_page
1979
              (Source_page.source id syntax_info v.source_info source_code);
28✔
1980
          ]
1981
  end
1982

1983
  include Page
1984

1985
  let type_expr = type_expr
1986

1987
  let record = record
1988

1989
  let unboxed_record = unboxed_record
1990
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