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

ocaml / odoc / 2072

10 Jun 2024 10:09AM UTC coverage: 71.986% (+0.2%) from 71.774%
2072

Pull #1145

github

web-flow
Merge 1b7b0910b into 1ced6f23f
Pull Request #1145: "Global" Sidebar

195 of 220 new or added lines in 12 files covered. (88.64%)

630 existing lines in 13 files now uncovered.

9834 of 13661 relevant lines covered (71.99%)

3561.61 hits per line

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

82.97
/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_model.Names
18
module Location = Odoc_model.Location_
19
module Paths = Odoc_model.Paths
20
open Types
21
module O = Codefmt
22
open O.Infix
23

24
let tag tag t = O.span ~attr:tag t
1,140✔
25

26
let rec filter_map acc f = function
27
  | hd :: tl ->
125✔
UNCOV
28
      let acc = match f hd with Some x -> x :: acc | None -> acc in
×
29
      filter_map acc f tl
30
  | [] -> List.rev acc
26✔
31

32
let label t =
33
  match t with
42✔
34
  | Odoc_model.Lang.TypeExpr.Label s -> tag "label" (O.txt s)
18✔
35
  | Optional s -> tag "optlabel" (O.txt "?" ++ O.txt s)
24✔
36

37
let type_var tv = tag "type-var" (O.txt tv)
546✔
38

39
let enclose ~l ~r x = O.span (O.txt l ++ x ++ O.txt r)
234✔
40

41
let resolved p content =
42
  let link = { InternalLink.target = Resolved p; content; tooltip = None } in
5,292✔
43
  O.elt [ inline @@ InternalLink link ]
5,292✔
44

45
let path p content = resolved (Url.from_path p) content
2,274✔
46

47
let unresolved content =
48
  let link = { InternalLink.target = Unresolved; content; tooltip = None } in
42✔
49
  O.elt [ inline @@ InternalLink link ]
42✔
50

51
let path_to_id path =
52
  match Url.Anchor.from_identifier (path :> Paths.Identifier.t) with
5,688✔
UNCOV
53
  | Error _ -> None
×
54
  | Ok url -> Some url
5,688✔
55

56
let source_anchor source_loc =
57
  (* Remove when dropping support for OCaml < 4.08 *)
UNCOV
58
  let to_option = function Result.Ok x -> Some x | Result.Error _ -> None in
×
59
  match source_loc with
60
  | Some id ->
95✔
61
      Url.Anchor.from_identifier
62
        (id : Paths.Identifier.SourceLocation.t :> Paths.Identifier.t)
63
      |> to_option
95✔
64
  | _ -> None
5,868✔
65

66
let attach_expansion ?(status = `Default) (eq, o, e) page text =
1,209✔
67
  match page with
2,635✔
68
  | None -> O.documentedSrc text
528✔
69
  | Some (page : Page.t) ->
2,107✔
70
      let url = page.url in
71
      let summary = O.render text in
72
      let expansion =
2,107✔
73
        O.documentedSrc (O.txt eq ++ O.keyword o)
2,107✔
74
        @ DocumentedSrc.[ Subpage { status; content = page } ]
75
        @ O.documentedSrc (O.keyword e)
2,107✔
76
      in
77
      DocumentedSrc.
78
        [ Alternative (Expansion { summary; url; status; expansion }) ]
79

80
let mk_heading ?(level = 1) ?label text =
290✔
81
  let title = [ inline @@ Text text ] in
290✔
82
  Item.Heading { label; level; title; source_anchor = None }
83

84
(** Returns the preamble as an item. Stop the preamble at the first heading. The
85
    rest is inserted into [items]. *)
86
let prepare_preamble comment items =
87
  let preamble, first_comment =
2,550✔
88
    Utils.split_at
89
      ~f:(function
90
        | { Odoc_model.Location_.value = `Heading _; _ } -> true | _ -> false)
24✔
91
      comment
92
  in
93
  (Comment.standalone preamble, Comment.standalone first_comment @ items)
2,550✔
94

95
let make_expansion_page ~sidebar ~source_anchor url comments items =
96
  let comment = List.concat comments in
2,550✔
97
  let preamble, items = prepare_preamble comment items in
2,550✔
98
  { Page.preamble; items; url; source_anchor; sidebar }
2,550✔
99

100
include Generator_signatures
101

102
module Make (Syntax : SYNTAX) = struct
103
  module Link : sig
104
    val from_path : Paths.Path.t -> text
105

106
    val from_fragment : Paths.Fragment.leaf -> text
107
  end = struct
108
    open Paths
109

110
    let rec from_path : Path.t -> text =
111
     fun path ->
112
      match path with
4,634✔
113
      | `Identifier (id, _) ->
12✔
114
          unresolved [ inline @@ Text (Identifier.name id) ]
12✔
UNCOV
115
      | `Substituted m -> from_path (m :> Path.t)
×
UNCOV
116
      | `SubstitutedMT m -> from_path (m :> Path.t)
×
UNCOV
117
      | `SubstitutedT m -> from_path (m :> Path.t)
×
118
      | `SubstitutedCT m -> from_path (m :> Path.t)
×
119
      | `Root root -> unresolved [ inline @@ Text root ]
6✔
120
      | `Forward root -> unresolved [ inline @@ Text root ] (* FIXME *)
×
121
      | `Dot (prefix, suffix) ->
6✔
122
          let link = from_path (prefix :> Path.t) in
123
          link ++ O.txt ("." ^ suffix)
6✔
UNCOV
124
      | `Apply (p1, p2) ->
×
125
          let link1 = from_path (p1 :> Path.t) in
UNCOV
126
          let link2 = from_path (p2 :> Path.t) in
×
UNCOV
127
          link1 ++ O.txt "(" ++ link2 ++ O.txt ")"
×
128
      | `Resolved _ when Paths.Path.is_hidden path ->
4,610✔
129
          let txt = Url.render_path path in
24✔
130
          unresolved [ inline @@ Text txt ]
24✔
131
      | `Resolved rp -> (
4,586✔
132
          (* If the path is pointing to an opaque module or module type
133
             there won't be a page generated - so we stop before; at
134
             the parent page, and link instead to the anchor representing
135
             the declaration of the opaque module(_type) *)
136
          let stop_before =
137
            match rp with
138
            | `OpaqueModule _ | `OpaqueModuleType _ -> true
6✔
139
            | _ -> false
4,430✔
140
          in
141
          let id = Paths.Path.Resolved.identifier rp in
142
          let txt = Url.render_path path in
4,586✔
143
          match Url.from_identifier ~stop_before id with
4,586✔
144
          | Ok href -> resolved href [ inline @@ Text txt ]
2,632✔
145
          | Error (Url.Error.Not_linkable _) -> O.txt txt
1,954✔
UNCOV
146
          | Error exn ->
×
UNCOV
147
              Printf.eprintf "Id.href failed: %S\n%!" (Url.Error.to_string exn);
×
148
              O.txt txt)
×
149

150
    let dot prefix suffix = prefix ^ "." ^ suffix
37✔
151

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

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

187
    let resolved_fragment_to_ir : Fragment.Resolved.leaf -> text =
188
     fun fragment ->
189
      let open Fragment in
219✔
190
      let id = Resolved.identifier (fragment :> Resolved.t) in
191
      let txt = render_resolved_fragment (fragment :> Resolved.t) in
219✔
192
      match Url.from_identifier ~stop_before:false id with
219✔
193
      | Ok href -> resolved href [ inline @@ Text txt ]
219✔
UNCOV
194
      | Error (Not_linkable _) -> unresolved [ inline @@ Text txt ]
×
UNCOV
195
      | Error exn ->
×
196
          Printf.eprintf "[FRAG] Id.href failed: %S\n%!"
UNCOV
197
            (Url.Error.to_string exn);
×
198
          unresolved [ inline @@ Text txt ]
×
199

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

209
  module Impl = struct
210
    let impl ~infos src =
211
      let l =
26✔
212
        infos
213
        |> List.sort (fun (_, (l1, e1)) (_, (l2, e2)) ->
214
               if l1 = l2 then compare e2 e1
125✔
215
                 (* If two intervals open at the same time, we open
216
                    first the one that closes last *)
217
               else compare l1 l2)
3,027✔
218
      in
219
      let get_src a b =
26✔
220
        let in_bound x = min (max x 0) (String.length src) in
1,590✔
221
        let a = in_bound a and b = in_bound b in
1,590✔
222
        let a, b = (min a b, max a b) in
1,590✔
223
        String.sub src a (b - a)
224
      in
225
      let plain_code = function
226
        | "" -> []
561✔
227
        | s -> [ Types.Source_page.Plain_code s ]
1,029✔
228
      in
229
      let min (a : int) b = if a < b then a else b in
151✔
230
      let rec extract from to_ list aux =
231
        match list with
1,590✔
232
        | (k, (loc_start, loc_end)) :: q when loc_start < to_ ->
1,538✔
233
            let loc_end = min loc_end to_ in
782✔
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
782✔
237
            let next, q = extract loc_start loc_end q [] in
782✔
238
            extract loc_end to_ q
782✔
239
              ([ Types.Source_page.Tagged_code (k, List.rev next) ]
782✔
240
              @ initial @ aux)
241
        | q -> (plain_code (get_src from to_) @ aux, q)
808✔
242
      in
243
      let doc, _ = extract 0 (String.length src) l [] in
26✔
244
      List.rev doc
26✔
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
26✔
256

257
    let to_link { Lang.Source_info.documentation; implementation } =
258
      let documentation =
33✔
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
            match Url.Anchor.from_identifier (id :> Paths.Identifier.t) with
276
            | Ok url -> Some url
15✔
UNCOV
277
            | Error _ -> None)
×
278
        | _ -> None
18✔
279
      in
280
      Some (Source_page.Link { implementation; documentation })
281

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

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

308
  module Type_expression : sig
309
    val type_expr : ?needs_parentheses:bool -> Lang.TypeExpr.t -> text
310

311
    val format_type_path :
312
      delim:[ `parens | `brackets ] -> Lang.TypeExpr.t list -> text -> text
313
  end = struct
314
    let rec te_variant (t : Odoc_model.Lang.TypeExpr.Polymorphic_variant.t) =
315
      let style_arguments ~constant arguments =
133✔
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 =
84✔
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
84✔
326
        in
327
        let arguments =
328
          O.list arguments ~sep:(O.txt " & ") ~f:wrapped_type_expr
84✔
329
        in
330
        if constant then O.txt "& " ++ arguments else arguments
12✔
331
      in
332
      let rec style_elements ~add_pipe = function
333
        | [] -> O.noop
133✔
334
        | first :: rest ->
181✔
335
            let first =
336
              match first with
337
              | Odoc_model.Lang.TypeExpr.Polymorphic_variant.Type te ->
6✔
338
                  let res = O.box_hv @@ type_expr te in
6✔
UNCOV
339
                  if add_pipe then O.sp ++ O.span (O.txt "| " ++ res) else res
×
340
              | Constructor { constant; name; arguments; _ } ->
175✔
341
                  let constr =
342
                    let name = "`" ^ name in
343
                    if add_pipe then O.span (O.txt ("| " ^ name))
54✔
344
                    else O.txt name
121✔
345
                  in
346
                  let res =
347
                    O.box_hv
348
                      (match arguments with
349
                      | [] -> constr
91✔
350
                      | _ ->
84✔
351
                          let arguments = style_arguments ~constant arguments in
352
                          O.span
84✔
353
                            (if Syntax.Type.Variant.parenthesize_params then
UNCOV
354
                               constr ++ arguments
×
355
                             else constr ++ O.txt " of" ++ O.sp ++ arguments))
84✔
356
                  in
357
                  if add_pipe then O.sp ++ res else res
54✔
358
            in
359
            first ++ style_elements ~add_pipe:true rest
181✔
360
      in
361
      let elements = style_elements ~add_pipe:false t.elements in
362
      O.box_hv_no_indent
133✔
363
      @@ O.span
133✔
364
           (match t.kind with
365
           | Fixed -> O.txt "[ " ++ elements ++ O.txt " ]"
37✔
366
           | Open -> O.txt "[> " ++ elements ++ O.txt " ]"
36✔
367
           | Closed [] -> O.txt "[< " ++ elements ++ O.txt " ]"
54✔
368
           | Closed lst ->
6✔
369
               let constrs = String.concat " " lst in
370
               O.txt "[< " ++ elements ++ O.txt (" " ^ constrs ^ " ]"))
6✔
371

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

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

417
    and type_expr ?(needs_parentheses = false) (t : Odoc_model.Lang.TypeExpr.t)
3,187✔
418
        =
419
      match t with
4,526✔
420
      | Var s -> type_var (Syntax.Type.var_prefix ^ s)
546✔
UNCOV
421
      | Any -> type_var Syntax.Type.any
×
422
      | Alias (te, alias) ->
60✔
423
          type_expr ~needs_parentheses:true te
60✔
424
          ++ O.txt " " ++ O.keyword "as" ++ O.txt " '" ++ O.txt alias
60✔
425
      | Arrow (None, src, dst) ->
302✔
426
          let res =
427
            O.span
302✔
428
              ((O.box_hv @@ type_expr ~needs_parentheses:true src)
302✔
429
              ++ O.txt " " ++ Syntax.Type.arrow)
302✔
430
            ++ O.sp ++ type_expr dst
302✔
431
            (* ++ O.end_hv *)
432
          in
433
          if not needs_parentheses then res else enclose ~l:"(" res ~r:")"
24✔
434
      | Arrow (Some lbl, src, dst) ->
42✔
435
          let res =
436
            O.span
42✔
437
              ((O.box_hv
42✔
438
               @@ label lbl ++ O.txt ":" ++ O.cut
42✔
439
                  ++ (O.box_hv @@ type_expr ~needs_parentheses:true src))
42✔
440
              ++ O.txt " " ++ Syntax.Type.arrow)
42✔
441
            ++ O.sp ++ type_expr dst
42✔
442
          in
443
          if not needs_parentheses then res else enclose ~l:"(" res ~r:")"
12✔
444
      | Tuple lst ->
138✔
445
          let res =
446
            O.box_hv_no_indent
447
              (O.list lst ~sep:Syntax.Type.Tuple.element_separator
138✔
448
                 ~f:(type_expr ~needs_parentheses:true))
449
          in
UNCOV
450
          if Syntax.Type.Tuple.always_parenthesize || needs_parentheses then
×
451
            enclose ~l:"(" res ~r:")"
24✔
452
          else res
114✔
453
      | Constr (path, args) ->
3,203✔
454
          let link = Link.from_path (path :> Paths.Path.t) in
455
          format_type_path ~delim:`parens args link
3,203✔
456
      | Polymorphic_variant v -> te_variant v
133✔
457
      | Object o -> te_object o
60✔
458
      | Class (path, args) ->
6✔
459
          format_type_path ~delim:`brackets args
460
            (Link.from_path (path :> Paths.Path.t))
6✔
461
      | Poly (polyvars, t) ->
18✔
462
          O.txt ("'" ^ String.concat " '" polyvars ^ ". ") ++ type_expr t
18✔
463
      | Package pkg ->
18✔
464
          enclose ~l:"(" ~r:")"
465
            (O.keyword "module" ++ O.txt " "
18✔
466
            ++ Link.from_path (pkg.path :> Paths.Path.t)
18✔
467
            ++
18✔
468
            match pkg.substitutions with
469
            | [] -> O.noop
12✔
470
            | fst :: lst ->
6✔
471
                O.sp
472
                ++ O.box_hv (O.keyword "with" ++ O.txt " " ++ package_subst fst)
6✔
473
                ++ O.list lst ~f:(fun s ->
6✔
474
                       O.cut
6✔
475
                       ++ (O.box_hv
6✔
476
                          @@ O.txt " " ++ O.keyword "and" ++ O.txt " "
6✔
477
                             ++ package_subst s)))
6✔
478

479
    and package_subst
480
        ((frag_typ, te) : Paths.Fragment.Type.t * Odoc_model.Lang.TypeExpr.t) :
481
        text =
482
      let typath = Link.from_fragment (frag_typ :> Paths.Fragment.leaf) in
12✔
483
      O.keyword "type" ++ O.txt " " ++ typath ++ O.txt " =" ++ O.sp
12✔
484
      ++ type_expr te
12✔
485
  end
486

487
  open Type_expression
488

489
  (* Also handles constructor declarations for exceptions and extensible
490
     variants, and exposes a few helpers used in formatting classes and signature
491
     constraints. *)
492
  module Type_declaration : sig
493
    val type_decl :
494
      ?is_substitution:bool ->
495
      Lang.Signature.recursive * Lang.TypeDecl.t ->
496
      Item.t
497

498
    val extension : Lang.Extension.t -> Item.t
499

500
    val record : Lang.TypeDecl.Field.t list -> DocumentedSrc.one list
501

502
    val exn : Lang.Exception.t -> Item.t
503

504
    val format_params :
505
      ?delim:[ `parens | `brackets ] -> Lang.TypeDecl.param list -> text
506

507
    val format_manifest :
508
      ?is_substitution:bool ->
509
      ?compact_variants:bool ->
510
      Lang.TypeDecl.Equation.t ->
511
      text * bool
512

513
    val format_constraints : (Lang.TypeExpr.t * Lang.TypeExpr.t) list -> text
514
  end = struct
515
    let record fields =
516
      let field mutable_ id typ =
72✔
517
        match Url.from_identifier ~stop_before:true id with
120✔
UNCOV
518
        | Error e -> failwith (Url.Error.to_string e)
×
519
        | Ok url ->
120✔
520
            let name = Paths.Identifier.name id in
521
            let attrs =
120✔
522
              [ "def"; "record"; Url.Anchor.string_of_kind url.kind ]
120✔
523
            in
524
            let cell =
525
              (* O.td ~a:[ O.a_class ["def"; kind ] ]
526
               *   [O.a ~a:[O.a_href ("#" ^ anchor); O.a_class ["anchor"]] []
527
               *   ; *)
528
              O.code
529
                ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
18✔
530
                ++ O.txt name
120✔
531
                ++ O.txt Syntax.Type.annotation_separator
120✔
532
                ++ type_expr typ
120✔
533
                ++ O.txt Syntax.Type.Record.field_separator)
120✔
534
              (* ] *)
535
            in
536
            (url, attrs, cell)
120✔
537
      in
538
      let rows =
539
        fields
540
        |> List.map (fun fld ->
541
               let open Odoc_model.Lang.TypeDecl.Field in
120✔
542
               let url, attrs, code =
543
                 field fld.mutable_ (fld.id :> Paths.Identifier.t) fld.type_
544
               in
545
               let anchor = Some url in
120✔
546
               let rhs = Comment.to_ir fld.doc in
547
               let doc = if not (Comment.has_doc fld.doc) then [] else rhs in
48✔
548
               let markers = Syntax.Comment.markers in
549
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
550
      in
551
      let content =
72✔
552
        O.documentedSrc (O.txt "{") @ rows @ O.documentedSrc (O.txt "}")
72✔
553
      in
554
      content
555

556
    let constructor :
557
        Paths.Identifier.t ->
558
        Odoc_model.Lang.TypeDecl.Constructor.argument ->
559
        Odoc_model.Lang.TypeExpr.t option ->
560
        DocumentedSrc.t =
561
     fun id args ret_type ->
562
      let name = Paths.Identifier.name id in
552✔
563
      let kind = Url.(kind id |> Anchor.string_of_kind) in
552✔
564
      let cstr = tag kind (O.txt name) in
552✔
565
      let is_gadt, ret_type =
552✔
566
        match ret_type with
567
        | None -> (false, O.noop)
420✔
568
        | Some te ->
132✔
569
            let constant = match args with Tuple [] -> true | _ -> false in
42✔
570
            let ret_type =
571
              O.txt " "
132✔
572
              ++ (if constant then O.txt ":" else Syntax.Type.GADT.arrow)
42✔
573
              ++ O.txt " " ++ type_expr te
132✔
574
            in
575
            (true, ret_type)
132✔
576
      in
577
      match args with
578
      | Tuple [] -> O.documentedSrc (cstr ++ ret_type)
243✔
579
      | Tuple lst ->
285✔
580
          let params =
581
            O.list lst ~sep:Syntax.Type.Tuple.element_separator
582
              ~f:(type_expr ~needs_parentheses:is_gadt)
583
          in
584
          O.documentedSrc
285✔
585
            (cstr
586
            ++ (if Syntax.Type.Variant.parenthesize_params then
285✔
UNCOV
587
                  O.txt "(" ++ params ++ O.txt ")"
×
588
                else
589
                  (if is_gadt then O.txt Syntax.Type.annotation_separator
84✔
590
                   else O.txt " " ++ O.keyword "of" ++ O.txt " ")
201✔
591
                  ++ params)
285✔
592
            ++ ret_type)
285✔
593
      | Record fields ->
24✔
594
          if is_gadt then
595
            O.documentedSrc (cstr ++ O.txt Syntax.Type.annotation_separator)
6✔
596
            @ record fields @ O.documentedSrc ret_type
6✔
597
          else
598
            O.documentedSrc (cstr ++ O.txt " " ++ O.keyword "of" ++ O.txt " ")
18✔
599
            @ record fields
18✔
600

601
    let variant cstrs : DocumentedSrc.t =
602
      let constructor id args res =
206✔
603
        match Url.from_identifier ~stop_before:true id with
385✔
UNCOV
604
        | Error e -> failwith (Url.Error.to_string e)
×
605
        | Ok url ->
385✔
606
            let attrs =
607
              [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ]
385✔
608
            in
609
            let content =
610
              let doc = constructor id args res in
611
              O.documentedSrc (O.txt "| ") @ doc
385✔
612
            in
613
            (url, attrs, content)
614
      in
615
      match cstrs with
616
      | [] -> O.documentedSrc (O.txt "|")
6✔
617
      | _ :: _ ->
200✔
618
          let rows =
619
            cstrs
620
            |> List.map (fun cstr ->
621
                   let open Odoc_model.Lang.TypeDecl.Constructor in
385✔
622
                   let url, attrs, code =
623
                     constructor
624
                       (cstr.id :> Paths.Identifier.t)
625
                       cstr.args cstr.res
626
                   in
627
                   let anchor = Some url in
385✔
628
                   let rhs = Comment.to_ir cstr.doc in
629
                   let doc =
385✔
630
                     if not (Comment.has_doc cstr.doc) then [] else rhs
73✔
631
                   in
632
                   let markers = Syntax.Comment.markers in
633
                   DocumentedSrc.Nested { anchor; attrs; code; doc; markers })
634
          in
635
          rows
200✔
636

637
    let extension_constructor (t : Odoc_model.Lang.Extension.Constructor.t) =
638
      let id = (t.id :> Paths.Identifier.t) in
117✔
639
      match Url.from_identifier ~stop_before:true id with
UNCOV
640
      | Error e -> failwith (Url.Error.to_string e)
×
641
      | Ok url ->
117✔
642
          let anchor = Some url in
643
          let attrs =
644
            [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ]
117✔
645
          in
646
          let code =
647
            O.documentedSrc (O.txt "| ") @ constructor id t.args t.res
117✔
648
          in
649
          let doc = Comment.to_ir t.doc in
650
          let markers = Syntax.Comment.markers in
117✔
651
          DocumentedSrc.Nested { anchor; attrs; code; doc; markers }
652

653
    let extension (t : Odoc_model.Lang.Extension.t) =
654
      let prefix =
95✔
655
        O.keyword "type" ++ O.txt " "
95✔
656
        ++ Link.from_path (t.type_path :> Paths.Path.t)
95✔
657
        ++ O.txt " +=" ++ O.sp
95✔
658
        ++
659
        if t.private_ then O.keyword Syntax.Type.private_keyword ++ O.sp
6✔
660
        else O.noop
89✔
661
      in
662
      let content =
95✔
663
        O.documentedSrc prefix
95✔
664
        @ List.map extension_constructor t.constructors
95✔
665
        @ O.documentedSrc
95✔
UNCOV
666
            (if Syntax.Type.type_def_semicolon then O.txt ";" else O.noop)
×
667
      in
668
      let attr = [ "type"; "extension" ] in
669
      let anchor = Some (Url.Anchor.extension_decl t) in
95✔
670
      let doc = Comment.to_ir t.doc in
671
      let source_anchor =
95✔
672
        (* Take the anchor from the first constructor only for consistency with
673
           regular variants. *)
674
        match t.constructors with
675
        | hd :: _ -> source_anchor hd.source_loc
95✔
UNCOV
676
        | [] -> None
×
677
      in
678
      Item.Declaration { attr; anchor; doc; content; source_anchor }
679

680
    let exn (t : Odoc_model.Lang.Exception.t) =
681
      let cstr = constructor (t.id :> Paths.Identifier.t) t.args t.res in
50✔
682
      let content =
50✔
683
        O.documentedSrc (O.keyword "exception" ++ O.txt " ")
50✔
684
        @ cstr
685
        @ O.documentedSrc
50✔
UNCOV
686
            (if Syntax.Type.Exception.semicolon then O.txt ";" else O.noop)
×
687
      in
688
      let attr = [ "exception" ] in
689
      let anchor = path_to_id t.id in
690
      let doc = Comment.to_ir t.doc in
50✔
691
      let source_anchor = source_anchor t.source_loc in
50✔
692
      Item.Declaration { attr; anchor; doc; content; source_anchor }
50✔
693

694
    let polymorphic_variant ~type_ident
695
        (t : Odoc_model.Lang.TypeExpr.Polymorphic_variant.t) =
696
      let row item =
67✔
697
        let kind_approx, cstr, doc =
152✔
698
          match item with
699
          | Odoc_model.Lang.TypeExpr.Polymorphic_variant.Type te ->
18✔
700
              ("unknown", O.documentedSrc (type_expr te), None)
18✔
701
          | Constructor { constant; name; arguments; doc; _ } -> (
134✔
702
              let cstr = "`" ^ name in
703
              ( "constructor",
704
                (match arguments with
705
                | [] -> O.documentedSrc (O.txt cstr)
72✔
706
                | _ ->
62✔
707
                    (* Multiple arguments in a polymorphic variant constructor correspond
708
                       to a conjunction of types, not a product: [`Lbl int&float].
709
                       If constant is [true], the conjunction starts with an empty type,
710
                       for instance [`Lbl &int].
711
                    *)
712
                    let wrapped_type_expr =
713
                      (* type conjunction in Reason is printed as `Lbl (t1)&(t2)` *)
UNCOV
714
                      if Syntax.Type.Variant.parenthesize_params then fun x ->
×
UNCOV
715
                        O.txt "(" ++ type_expr x ++ O.txt ")"
×
716
                      else fun x -> type_expr x
62✔
717
                    in
718
                    let params =
719
                      O.box_hv
720
                      @@ O.list arguments
62✔
721
                           ~sep:(O.txt " &" ++ O.sp)
62✔
722
                           ~f:wrapped_type_expr
723
                    in
724
                    let params =
62✔
UNCOV
725
                      if constant then O.txt "& " ++ params else params
×
726
                    in
727
                    O.documentedSrc
62✔
728
                      (O.txt cstr
62✔
729
                      ++
62✔
UNCOV
730
                      if Syntax.Type.Variant.parenthesize_params then params
×
731
                      else O.txt " " ++ O.keyword "of" ++ O.sp ++ params)),
62✔
732
                match doc with [] -> None | _ -> Some (Comment.to_ir doc) ))
12✔
733
        in
734
        let markers = Syntax.Comment.markers in
735
        try
736
          let url = Url.Anchor.polymorphic_variant ~type_ident item in
737
          let attrs =
152✔
738
            [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ]
152✔
739
          in
740
          let anchor = Some url in
741
          let code = O.documentedSrc (O.txt "| ") @ cstr in
152✔
742
          let doc = match doc with None -> [] | Some doc -> doc in
12✔
743
          DocumentedSrc.Nested { attrs; anchor; code; doc; markers }
UNCOV
744
        with Failure s ->
×
745
          Printf.eprintf "ERROR: %s\n%!" s;
UNCOV
746
          let code = O.documentedSrc (O.txt "| ") @ cstr in
×
747
          let attrs = [ "def"; kind_approx ] in
748
          let doc = [] in
749
          let anchor = None in
750
          DocumentedSrc.Nested { attrs; anchor; code; doc; markers }
751
      in
752
      let variants = List.map row t.elements in
753
      let intro, ending =
67✔
754
        match t.kind with
755
        | Fixed -> (O.documentedSrc (O.txt "[ "), O.documentedSrc (O.txt " ]"))
61✔
756
        | Open -> (O.documentedSrc (O.txt "[> "), O.documentedSrc (O.txt " ]"))
6✔
UNCOV
757
        | Closed [] ->
×
UNCOV
758
            (O.documentedSrc (O.txt "[< "), O.documentedSrc (O.txt " ]"))
×
UNCOV
759
        | Closed lst ->
×
760
            let constrs = String.concat " " lst in
UNCOV
761
            ( O.documentedSrc (O.txt "[< "),
×
UNCOV
762
              O.documentedSrc (O.txt (" " ^ constrs ^ " ]")) )
×
763
      in
764
      intro @ variants @ ending
765

766
    let format_params :
767
          'row.
768
          ?delim:[ `parens | `brackets ] ->
769
          Odoc_model.Lang.TypeDecl.param list ->
770
          text =
771
     fun ?(delim = `parens) params ->
348✔
772
      let format_param { Odoc_model.Lang.TypeDecl.desc; variance; injectivity }
416✔
773
          =
774
        let desc =
456✔
775
          match desc with
776
          | Odoc_model.Lang.TypeDecl.Any -> [ "_" ]
30✔
777
          | Var s -> [ "'"; s ]
426✔
778
        in
779
        let var_desc =
780
          match variance with
781
          | None -> desc
444✔
782
          | Some Odoc_model.Lang.TypeDecl.Pos -> "+" :: desc
6✔
783
          | Some Odoc_model.Lang.TypeDecl.Neg -> "-" :: desc
6✔
784
        in
UNCOV
785
        let final = if injectivity then "!" :: var_desc else var_desc in
×
786
        String.concat "" final
787
      in
788
      O.txt
789
        (match params with
790
        | [] -> ""
44✔
791
        | [ x ] -> format_param x |> Syntax.Type.handle_format_params
288✔
792
        | lst -> (
84✔
793
            let params = String.concat ", " (List.map format_param lst) in
84✔
UNCOV
794
            (match delim with `parens -> "(" | `brackets -> "[")
×
795
            ^ params
UNCOV
796
            ^ match delim with `parens -> ")" | `brackets -> "]"))
×
797

798
    let format_constraints constraints =
799
      O.list constraints ~f:(fun (t1, t2) ->
2,389✔
800
          O.sp
78✔
801
          ++ (O.box_hv
78✔
802
             @@ O.keyword "constraint" ++ O.sp
78✔
803
                ++ O.box_hv_no_indent (type_expr t1)
78✔
804
                ++ O.txt " =" ++ O.sp
78✔
805
                ++ O.box_hv_no_indent (type_expr t2)))
78✔
806

807
    let format_manifest :
808
          'inner_row 'outer_row.
809
          ?is_substitution:bool ->
810
          ?compact_variants:bool ->
811
          Odoc_model.Lang.TypeDecl.Equation.t ->
812
          text * bool =
813
     fun ?(is_substitution = false) ?(compact_variants = true) equation ->
78✔
814
      let _ = compact_variants in
2,316✔
815
      (* TODO *)
816
      let private_ = equation.private_ in
817
      match equation.manifest with
818
      | None -> (O.noop, private_)
1,355✔
819
      | Some t ->
961✔
820
          let manifest =
821
            O.txt (if is_substitution then " :=" else " =")
18✔
822
            ++ O.sp
961✔
823
            ++ (if private_ then
961✔
824
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
6✔
825
                else O.noop)
955✔
826
            ++ type_expr t
961✔
827
          in
828
          (manifest, false)
961✔
829

830
    let type_decl ?(is_substitution = false)
2,287✔
831
        ((recursive, t) : Lang.Signature.recursive * Lang.TypeDecl.t) =
832
      let keyword' =
2,305✔
833
        match recursive with
UNCOV
834
        | Ordinary | Rec -> O.keyword "type"
×
835
        | And -> O.keyword "and"
14✔
836
        | Nonrec -> O.keyword "type" ++ O.txt " " ++ O.keyword "nonrec"
7✔
837
      in
838
      let tyname = Paths.Identifier.name t.id in
839
      let tconstr =
2,305✔
840
        match t.equation.params with
841
        | [] -> O.txt tyname
1,975✔
842
        | l ->
330✔
843
            let params = format_params l in
844
            Syntax.Type.handle_constructor_params (O.txt tyname) params
330✔
845
      in
846
      let intro = keyword' ++ O.txt " " ++ tconstr in
2,305✔
847
      let constraints = format_constraints t.equation.constraints in
2,305✔
848
      let manifest, need_private, long_prefix =
2,305✔
849
        match t.equation.manifest with
850
        | Some (Odoc_model.Lang.TypeExpr.Polymorphic_variant variant) ->
67✔
851
            let code =
852
              polymorphic_variant
853
                ~type_ident:(t.id :> Paths.Identifier.t)
854
                variant
855
            in
856
            let manifest =
67✔
857
              O.documentedSrc
67✔
858
                (O.ignore intro
67✔
UNCOV
859
                ++ O.txt (if is_substitution then " :=" else " =")
×
860
                ++ O.sp
67✔
861
                ++
67✔
862
                if t.equation.private_ then
863
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
6✔
864
                else O.noop)
61✔
865
              @ code
866
            in
867
            (manifest, false, O.noop)
868
        | _ ->
2,238✔
869
            let manifest, need_private =
870
              format_manifest ~is_substitution t.equation
871
            in
872
            let text = O.ignore intro ++ manifest in
2,238✔
873
            (O.documentedSrc @@ text, need_private, text)
2,238✔
874
      in
875
      let representation =
876
        match t.representation with
877
        | None -> []
2,005✔
878
        | Some repr ->
300✔
879
            let content =
880
              match repr with
881
              | Extensible -> O.documentedSrc (O.txt "..")
46✔
882
              | Variant cstrs -> variant cstrs
206✔
883
              | Record fields -> record fields
48✔
884
            in
885
            if List.length content > 0 then
300✔
886
              O.documentedSrc
300✔
887
                (O.ignore long_prefix ++ O.txt " =" ++ O.sp
300✔
888
                ++
300✔
889
                if need_private then
890
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
6✔
891
                else O.noop)
294✔
892
              @ content
893
            else []
×
894
      in
895
      let content =
896
        O.documentedSrc intro @ manifest @ representation
2,305✔
897
        @ O.documentedSrc constraints
2,305✔
898
        @ O.documentedSrc
2,305✔
UNCOV
899
            (if Syntax.Type.type_def_semicolon then O.txt ";" else O.noop)
×
900
      in
901
      let attr = "type" :: (if is_substitution then [ "subst" ] else []) in
18✔
902
      let anchor = path_to_id t.id in
903
      let doc = Comment.to_ir t.doc in
2,305✔
904
      let source_anchor = source_anchor t.source_loc in
2,305✔
905
      Item.Declaration { attr; anchor; doc; content; source_anchor }
2,305✔
906
  end
907

908
  open Type_declaration
909

910
  module Value : sig
911
    val value : Lang.Value.t -> Item.t
912
  end = struct
913
    let value (t : Odoc_model.Lang.Value.t) =
914
      let extra_attr, semicolon =
608✔
915
        match t.value with
916
        | Abstract -> ([], Syntax.Value.semicolon)
590✔
917
        | External _ -> ([ "external" ], Syntax.Type.External.semicolon)
18✔
918
      in
919
      let name = Paths.Identifier.name t.id in
920
      let content =
608✔
921
        O.documentedSrc
922
          (O.box_hv
608✔
923
          @@ O.keyword Syntax.Value.variable_keyword
608✔
924
             ++ O.txt " " ++ O.txt name
608✔
925
             ++ O.txt Syntax.Type.annotation_separator
608✔
926
             ++ O.cut ++ type_expr t.type_
608✔
UNCOV
927
             ++ if semicolon then O.txt ";" else O.noop)
×
928
      in
929
      let attr = [ "value" ] @ extra_attr in
608✔
930
      let anchor = path_to_id t.id in
931
      let doc = Comment.to_ir t.doc in
608✔
932
      let source_anchor = source_anchor t.source_loc in
608✔
933
      Item.Declaration { attr; anchor; doc; content; source_anchor }
608✔
934
  end
935

936
  open Value
937

938
  (* This chunk of code is responsible for sectioning list of items
939
     according to headings by extracting headings as Items.
940

941
     TODO: This sectioning would be better done as a pass on the model directly.
942
  *)
943
  module Sectioning : sig
944
    open Odoc_model
945

946
    val comment_items : Comment.docs -> Item.t list
947

948
    val docs : Comment.docs -> Item.t list * Item.t list
949
  end = struct
950
    let take_until_heading_or_end (docs : Odoc_model.Comment.docs) =
951
      let content, _, rest =
569✔
952
        Doctree.Take.until docs ~classify:(fun b ->
953
            match b.Location.value with
1,112✔
954
            | `Heading _ -> Stop_and_keep
128✔
955
            | #Odoc_model.Comment.attached_block_element as doc ->
984✔
956
                let content = Comment.attached_block_element doc in
957
                Accum content)
984✔
958
      in
959
      (content, rest)
569✔
960

961
    let comment_items (input0 : Odoc_model.Comment.docs) =
962
      let rec loop input_comment acc =
796✔
963
        match input_comment with
1,959✔
964
        | [] -> List.rev acc
796✔
965
        | element :: input_comment -> (
1,163✔
966
            match element.Location.value with
967
            | `Heading h ->
594✔
968
                let item = Comment.heading h in
969
                loop input_comment (item :: acc)
594✔
970
            | _ ->
569✔
971
                let content, input_comment =
972
                  take_until_heading_or_end (element :: input_comment)
973
                in
974
                let item = Item.Text content in
569✔
975
                loop input_comment (item :: acc))
976
      in
977
      loop input0 []
978

979
    (* For doc pages, we want the header to contain everything until
980
       the first heading, then everything before the next heading which
981
       is either lower, or a section.
982
    *)
983
    let docs input_comment =
984
      let items = comment_items input_comment in
46✔
985
      let until_first_heading, o, items =
46✔
986
        Doctree.Take.until items ~classify:(function
987
          | Item.Heading h as i -> Stop_and_accum ([ i ], Some h.level)
46✔
UNCOV
988
          | i -> Accum [ i ])
×
989
      in
990
      match o with
46✔
UNCOV
991
      | None -> (until_first_heading, items)
×
992
      | Some level ->
46✔
UNCOV
993
          let max_level = if level = 1 then 2 else level in
×
994
          let before_second_heading, _, items =
995
            Doctree.Take.until items ~classify:(function
996
              | Item.Heading h when h.level >= max_level -> Stop_and_keep
7✔
997
              | i -> Accum [ i ])
30✔
998
          in
999
          let header = until_first_heading @ before_second_heading in
46✔
1000
          (header, items)
1001
  end
1002

1003
  module Class : sig
1004
    val class_ : sidebar:Block.t option -> Lang.Class.t -> Item.t
1005

1006
    val class_type : sidebar:Block.t option -> Lang.ClassType.t -> Item.t
1007
  end = struct
1008
    let class_type_expr (cte : Odoc_model.Lang.ClassType.expr) =
1009
      match cte with
187✔
1010
      | Constr (path, args) ->
44✔
1011
          let link = Link.from_path (path :> Paths.Path.t) in
1012
          format_type_path ~delim:`brackets args link
44✔
1013
      | Signature _ ->
143✔
1014
          Syntax.Class.open_tag ++ O.txt " ... " ++ Syntax.Class.close_tag
143✔
1015

1016
    let method_ (t : Odoc_model.Lang.Method.t) =
1017
      let name = Paths.Identifier.name t.id in
66✔
1018
      let virtual_ =
66✔
1019
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
6✔
1020
      in
1021
      let private_ =
1022
        if t.private_ then O.keyword "private" ++ O.txt " " else O.noop
6✔
1023
      in
1024
      let content =
1025
        O.documentedSrc
1026
          (O.keyword "method" ++ O.txt " " ++ private_ ++ virtual_ ++ O.txt name
66✔
1027
          ++ O.txt Syntax.Type.annotation_separator
66✔
1028
          ++ type_expr t.type_)
66✔
1029
      in
1030
      let attr = [ "method" ] in
66✔
1031
      let anchor = path_to_id t.id in
1032
      let doc = Comment.to_ir t.doc in
66✔
1033
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
66✔
1034

1035
    let instance_variable (t : Odoc_model.Lang.InstanceVariable.t) =
1036
      let name = Paths.Identifier.name t.id in
12✔
1037
      let virtual_ =
12✔
1038
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
6✔
1039
      in
1040
      let mutable_ =
1041
        if t.mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop
6✔
1042
      in
1043
      let content =
1044
        O.documentedSrc
1045
          (O.keyword "val" ++ O.txt " " ++ mutable_ ++ virtual_ ++ O.txt name
12✔
1046
          ++ O.txt Syntax.Type.annotation_separator
12✔
1047
          ++ type_expr t.type_)
12✔
1048
      in
1049
      let attr = [ "value"; "instance-variable" ] in
12✔
1050
      let anchor = path_to_id t.id in
1051
      let doc = Comment.to_ir t.doc in
12✔
1052
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
12✔
1053

1054
    let inherit_ (ih : Lang.ClassSignature.Inherit.t) =
1055
      let cte =
12✔
1056
        match ih.expr with
1057
        | Signature _ -> assert false (* Bold. *)
1058
        | cty -> cty
12✔
1059
      in
1060
      let content =
1061
        O.documentedSrc (O.keyword "inherit" ++ O.txt " " ++ class_type_expr cte)
12✔
1062
      in
1063
      let attr = [ "inherit" ] in
12✔
1064
      let anchor = None in
1065
      let doc = Comment.to_ir ih.doc in
1066
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
12✔
1067

1068
    let constraint_ (cst : Lang.ClassSignature.Constraint.t) =
1069
      let content =
6✔
1070
        O.documentedSrc (format_constraints [ (cst.left, cst.right) ])
6✔
1071
      in
1072
      let attr = [] in
6✔
1073
      let anchor = None in
1074
      let doc = Comment.to_ir cst.doc in
1075
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
6✔
1076

1077
    let class_signature (c : Lang.ClassSignature.t) =
1078
      let rec loop l acc_items =
175✔
1079
        match l with
289✔
1080
        | [] -> List.rev acc_items
175✔
1081
        | item :: rest -> (
114✔
1082
            let continue item = loop rest (item :: acc_items) in
96✔
1083
            match (item : Lang.ClassSignature.item) with
1084
            | Inherit cty -> continue @@ inherit_ cty
12✔
1085
            | Method m -> continue @@ method_ m
66✔
1086
            | InstanceVariable v -> continue @@ instance_variable v
12✔
1087
            | Constraint cst -> continue @@ constraint_ cst
6✔
1088
            | Comment `Stop ->
6✔
1089
                let rest =
1090
                  Utils.skip_until rest ~p:(function
1091
                    | Lang.ClassSignature.Comment `Stop -> true
6✔
1092
                    | _ -> false)
6✔
1093
                in
1094
                loop rest acc_items
6✔
1095
            | Comment (`Docs c) ->
12✔
1096
                let items = Sectioning.comment_items c in
1097
                loop rest (List.rev_append items acc_items))
12✔
1098
      in
1099
      (* FIXME: use [t.self] *)
1100
      (c.doc, loop c.items [])
175✔
1101

1102
    let rec class_decl (cd : Odoc_model.Lang.Class.decl) =
1103
      match cd with
137✔
1104
      | ClassType expr -> class_type_expr expr
125✔
1105
      (* TODO: factorize the following with [type_expr] *)
1106
      | Arrow (None, src, dst) ->
12✔
1107
          O.span
12✔
1108
            (type_expr ~needs_parentheses:true src
12✔
1109
            ++ O.txt " " ++ Syntax.Type.arrow)
12✔
1110
          ++ O.txt " " ++ class_decl dst
12✔
UNCOV
1111
      | Arrow (Some lbl, src, dst) ->
×
UNCOV
1112
          O.span
×
UNCOV
1113
            (label lbl ++ O.txt ":"
×
UNCOV
1114
            ++ type_expr ~needs_parentheses:true src
×
UNCOV
1115
            ++ O.txt " " ++ Syntax.Type.arrow)
×
UNCOV
1116
          ++ O.txt " " ++ class_decl dst
×
1117

1118
    let class_ ~sidebar (t : Odoc_model.Lang.Class.t) =
1119
      let name = Paths.Identifier.name t.id in
125✔
1120
      let params =
125✔
1121
        match t.params with
1122
        | [] -> O.noop
107✔
1123
        | _ :: _ as params -> format_params ~delim:`brackets params ++ O.txt " "
18✔
1124
      in
1125
      let virtual_ =
1126
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
18✔
1127
      in
1128

1129
      let source_anchor = source_anchor t.source_loc in
1130
      let cname, expansion, expansion_doc =
125✔
1131
        match t.expansion with
UNCOV
1132
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1133
        | Some csig ->
125✔
1134
            let expansion_doc, items = class_signature csig in
1135
            let url = Url.Path.from_identifier t.id in
125✔
1136
            let page =
125✔
1137
              make_expansion_page ~sidebar ~source_anchor url
1138
                [ t.doc; expansion_doc ] items
1139
            in
1140
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
125✔
1141
              Some page,
1142
              Some expansion_doc )
1143
      in
1144
      let summary =
1145
        O.txt Syntax.Type.annotation_separator ++ class_decl t.type_
125✔
1146
      in
1147
      let cd =
125✔
1148
        attach_expansion
1149
          (Syntax.Type.annotation_separator, "object", "end")
1150
          expansion summary
1151
      in
1152
      let content =
125✔
1153
        O.documentedSrc (O.keyword "class" ++ O.txt " " ++ virtual_ ++ params)
125✔
1154
        @ cname @ cd
1155
      in
1156
      let attr = [ "class" ] in
1157
      let anchor = path_to_id t.id in
1158
      let doc = Comment.synopsis ~decl_doc:t.doc ~expansion_doc in
125✔
1159
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1160

1161
    let class_type ~sidebar (t : Odoc_model.Lang.ClassType.t) =
1162
      let name = Paths.Identifier.name t.id in
50✔
1163
      let params = format_params ~delim:`brackets t.params in
50✔
1164
      let virtual_ =
50✔
1165
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
6✔
1166
      in
1167
      let source_anchor = source_anchor t.source_loc in
1168
      let cname, expansion, expansion_doc =
50✔
1169
        match t.expansion with
UNCOV
1170
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1171
        | Some csig ->
50✔
1172
            let url = Url.Path.from_identifier t.id in
1173
            let expansion_doc, items = class_signature csig in
50✔
1174
            let page =
50✔
1175
              make_expansion_page ~sidebar ~source_anchor url
1176
                [ t.doc; expansion_doc ] items
1177
            in
1178
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
50✔
1179
              Some page,
1180
              Some expansion_doc )
1181
      in
1182
      let summary = O.txt " = " ++ class_type_expr t.expr in
50✔
1183
      let expr = attach_expansion (" = ", "object", "end") expansion summary in
50✔
1184
      let content =
50✔
1185
        O.documentedSrc
50✔
1186
          (O.keyword "class" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
50✔
1187
         ++ virtual_ ++ params ++ O.txt " ")
50✔
1188
        @ cname @ expr
1189
      in
1190
      let attr = [ "class-type" ] in
1191
      let anchor = path_to_id t.id in
1192
      let doc = Comment.synopsis ~decl_doc:t.doc ~expansion_doc in
50✔
1193
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1194
  end
1195

1196
  open Class
1197

1198
  module Module : sig
1199
    val signature :
1200
      sidebar:Block.t option ->
1201
      Lang.Signature.t ->
1202
      Comment.Comment.docs * Item.t list
1203
    (** Returns [header_doc, content]. *)
1204
  end = struct
1205
    let internal_module m =
1206
      let open Lang.Module in
1,489✔
1207
      match m.id.iv with
1208
      | `Module (_, name) when ModuleName.is_hidden name -> true
63✔
1209
      | _ -> false
1,426✔
1210

1211
    let internal_type t =
1212
      let open Lang.TypeDecl in
2,288✔
1213
      match t.id.iv with
1214
      | `Type (_, name) when TypeName.is_hidden name -> true
1✔
1215
      | _ -> false
2,287✔
1216

1217
    let internal_value v =
1218
      let open Lang.Value in
690✔
1219
      match v.id.iv with
1220
      | `Value (_, name) when ValueName.is_hidden name -> true
82✔
1221
      | _ -> false
608✔
1222

1223
    let internal_module_type t =
1224
      let open Lang.ModuleType in
1,028✔
1225
      match t.id.iv with
UNCOV
1226
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1227
      | _ -> false
1,028✔
1228

1229
    let internal_module_substitution t =
1230
      let open Lang.ModuleSubstitution in
12✔
1231
      match t.id.iv with
UNCOV
1232
      | `Module (_, name) when ModuleName.is_hidden name -> true
×
1233
      | _ -> false
12✔
1234

1235
    let internal_module_type_substitution t =
1236
      let open Lang.ModuleTypeSubstitution in
6✔
1237
      match t.id.iv with
UNCOV
1238
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1239
      | _ -> false
6✔
1240

1241
    let rec signature ~sidebar (s : Lang.Signature.t) =
1242
      let rec loop l acc_items =
2,739✔
1243
        match l with
9,568✔
1244
        | [] -> List.rev acc_items
2,739✔
1245
        | item :: rest -> (
6,829✔
1246
            let continue (item : Item.t) = loop rest (item :: acc_items) in
5,902✔
1247
            match (item : Lang.Signature.item) with
1248
            | Module (_, m) when internal_module m -> loop rest acc_items
63✔
1249
            | Type (_, t) when internal_type t -> loop rest acc_items
1✔
1250
            | Value v when internal_value v -> loop rest acc_items
82✔
UNCOV
1251
            | ModuleType m when internal_module_type m -> loop rest acc_items
×
1252
            | ModuleSubstitution m when internal_module_substitution m ->
12✔
UNCOV
1253
                loop rest acc_items
×
1254
            | ModuleTypeSubstitution m when internal_module_type_substitution m
6✔
1255
              ->
UNCOV
1256
                loop rest acc_items
×
1257
            | ModuleTypeSubstitution m ->
6✔
1258
                continue @@ module_type_substitution ~sidebar m
6✔
1259
            | Module (_, m) -> continue @@ module_ ~sidebar m
1,426✔
1260
            | ModuleType m -> continue @@ module_type ~sidebar m
1,028✔
1261
            | Class (_, c) -> continue @@ class_ ~sidebar c
125✔
1262
            | ClassType (_, c) -> continue @@ class_type ~sidebar c
50✔
1263
            | Include m -> continue @@ include_ ~sidebar m
197✔
1264
            | ModuleSubstitution m -> continue @@ module_substitution m
12✔
1265
            | TypeSubstitution t ->
18✔
1266
                continue @@ type_decl ~is_substitution:true (Ordinary, t)
18✔
1267
            | Type (r, t) -> continue @@ type_decl (r, t)
2,287✔
1268
            | TypExt e -> continue @@ extension e
95✔
1269
            | Exception e -> continue @@ exn e
50✔
1270
            | Value v -> continue @@ value v
608✔
1271
            | Open o ->
62✔
1272
                let items = Sectioning.comment_items o.doc in
1273
                loop rest (List.rev_append items acc_items)
62✔
1274
            | Comment `Stop ->
43✔
1275
                let rest =
1276
                  Utils.skip_until rest ~p:(function
1277
                    | Lang.Signature.Comment `Stop -> true
37✔
1278
                    | _ -> false)
49✔
1279
                in
1280
                loop rest acc_items
43✔
1281
            | Comment (`Docs c) ->
676✔
1282
                let items = Sectioning.comment_items c in
1283
                loop rest (List.rev_append items acc_items))
676✔
1284
      in
1285
      (Lang.extract_signature_doc s, loop s.items [])
2,739✔
1286

1287
    and functor_parameter :
1288
        sidebar:_ ->
1289
        Odoc_model.Lang.FunctorParameter.parameter ->
1290
        DocumentedSrc.t =
1291
     fun ~sidebar arg ->
1292
      let open Odoc_model.Lang.FunctorParameter in
167✔
1293
      let name = Paths.Identifier.name arg.id in
1294
      let render_ty = arg.expr in
167✔
1295
      let modtyp =
1296
        mty_in_decl ~sidebar (arg.id :> Paths.Identifier.Signature.t) render_ty
1297
      in
1298
      let modname, mod_decl =
167✔
1299
        match expansion_of_module_type_expr ~sidebar arg.expr with
UNCOV
1300
        | None ->
×
UNCOV
1301
            let modname = O.txt (Paths.Identifier.name arg.id) in
×
UNCOV
1302
            (modname, O.documentedSrc modtyp)
×
1303
        | Some (expansion_doc, items) ->
167✔
1304
            let url = Url.Path.from_identifier arg.id in
1305
            let modname = path url [ inline @@ Text name ] in
167✔
1306
            let type_with_expansion =
167✔
1307
              let content =
1308
                make_expansion_page ~sidebar ~source_anchor:None url
1309
                  [ expansion_doc ] items
1310
              in
1311
              let summary = O.render modtyp in
167✔
1312
              let status = `Default in
167✔
1313
              let expansion =
1314
                O.documentedSrc
167✔
1315
                  (O.txt Syntax.Type.annotation_separator ++ O.keyword "sig")
167✔
1316
                @ DocumentedSrc.[ Subpage { content; status } ]
1317
                @ O.documentedSrc (O.keyword "end")
167✔
1318
              in
1319
              DocumentedSrc.
1320
                [
1321
                  Alternative
1322
                    (Expansion { status = `Default; summary; url; expansion });
1323
                ]
1324
            in
1325
            (modname, type_with_expansion)
1326
      in
1327
      O.documentedSrc (O.keyword "module" ++ O.txt " ")
167✔
1328
      @ O.documentedSrc modname @ mod_decl
167✔
1329

1330
    and module_substitution (t : Odoc_model.Lang.ModuleSubstitution.t) =
1331
      let name = Paths.Identifier.name t.id in
12✔
1332
      let path = Link.from_path (t.manifest :> Paths.Path.t) in
12✔
1333
      let content =
12✔
1334
        O.documentedSrc
1335
          (O.keyword "module" ++ O.txt " " ++ O.txt name ++ O.txt " :=" ++ O.sp
12✔
1336
         ++ path)
12✔
1337
      in
1338
      let attr = [ "module-substitution" ] in
12✔
1339
      let anchor = path_to_id t.id in
1340
      let doc = Comment.to_ir t.doc in
12✔
1341
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
12✔
1342

1343
    and module_type_substitution ~sidebar
1344
        (t : Odoc_model.Lang.ModuleTypeSubstitution.t) =
1345
      let prefix =
6✔
1346
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
6✔
1347
      in
1348
      let source_anchor = None in
6✔
1349
      let modname = Paths.Identifier.name t.id in
1350
      let modname, expansion_doc, mty =
6✔
1351
        module_type_manifest ~sidebar ~subst:true ~source_anchor modname t.id
1352
          t.doc (Some t.manifest) prefix
1353
      in
1354
      let content =
6✔
1355
        O.documentedSrc (prefix ++ modname)
6✔
1356
        @ mty
1357
        @ O.documentedSrc
6✔
UNCOV
1358
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1359
      in
1360
      let attr = [ "module-type" ] in
1361
      let anchor = path_to_id t.id in
1362
      let doc = Comment.synopsis ~decl_doc:t.doc ~expansion_doc in
6✔
1363
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1364

1365
    and simple_expansion :
1366
        sidebar:_ ->
1367
        Odoc_model.Lang.ModuleType.simple_expansion ->
1368
        Comment.Comment.docs * Item.t list =
1369
     fun ~sidebar t ->
1370
      let rec extract_functor_params
2,266✔
1371
          (f : Odoc_model.Lang.ModuleType.simple_expansion) =
1372
        match f with
2,439✔
1373
        | Signature sg -> (None, sg)
2,266✔
1374
        | Functor (p, expansion) ->
173✔
1375
            let add_to params =
1376
              match p with Unit -> params | Named p -> p :: params
6✔
1377
            in
1378
            let params, sg = extract_functor_params expansion in
1379
            let params = match params with None -> [] | Some p -> p in
28✔
1380
            (Some (add_to params), sg)
173✔
1381
      in
1382
      match extract_functor_params t with
1383
      | None, sg -> signature ~sidebar sg
2,121✔
1384
      | Some params, sg ->
145✔
1385
          let sg_doc, content = signature ~sidebar sg in
1386
          let params =
145✔
1387
            Utils.flatmap params ~f:(fun arg ->
1388
                let content = functor_parameter ~sidebar arg in
167✔
1389
                let attr = [ "parameter" ] in
167✔
1390
                let anchor =
1391
                  Utils.option_of_result
1392
                  @@ Url.Anchor.from_identifier (arg.id :> Paths.Identifier.t)
167✔
1393
                in
1394
                let doc = [] in
167✔
1395
                [
1396
                  Item.Declaration
1397
                    { content; anchor; attr; doc; source_anchor = None };
1398
                ])
1399
          in
1400
          let prelude = mk_heading ~label:"parameters" "Parameters" :: params
145✔
1401
          and content = mk_heading ~label:"signature" "Signature" :: content in
145✔
1402
          (sg_doc, prelude @ content)
1403

1404
    and expansion_of_module_type_expr :
1405
        sidebar:_ ->
1406
        Odoc_model.Lang.ModuleType.expr ->
1407
        (Comment.Comment.docs * Item.t list) option =
1408
     fun ~sidebar t ->
1409
      let rec simple_expansion_of (t : Odoc_model.Lang.ModuleType.expr) =
2,475✔
1410
        match t with
2,648✔
1411
        | Path { p_expansion = None; _ }
285✔
1412
        | TypeOf { t_expansion = None; _ }
6✔
UNCOV
1413
        | With { w_expansion = None; _ } ->
×
1414
            None
1415
        | Path { p_expansion = Some e; _ }
338✔
1416
        | TypeOf { t_expansion = Some e; _ }
42✔
1417
        | With { w_expansion = Some e; _ } ->
165✔
1418
            Some e
1419
        | Signature sg -> Some (Signature sg)
1,639✔
1420
        | Functor (f_parameter, e) -> (
173✔
1421
            match simple_expansion_of e with
1422
            | Some e -> Some (Functor (f_parameter, e))
167✔
1423
            | None -> None)
6✔
1424
      in
1425
      match simple_expansion_of t with
1426
      | None -> None
291✔
1427
      | Some e -> Some (simple_expansion ~sidebar e)
2,184✔
1428

1429
    and module_ : sidebar:_ -> Odoc_model.Lang.Module.t -> Item.t =
1430
     fun ~sidebar t ->
1431
      let modname = Paths.Identifier.name t.id in
1,426✔
1432
      let expansion =
1,426✔
1433
        match t.type_ with
1434
        | Alias (_, Some e) -> Some (simple_expansion ~sidebar e)
82✔
1435
        | Alias (_, None) -> None
141✔
1436
        | ModuleType e -> expansion_of_module_type_expr ~sidebar e
1,203✔
1437
      in
1438
      let source_anchor = source_anchor t.source_loc in
1439
      let modname, status, expansion, expansion_doc =
1,426✔
1440
        match expansion with
1441
        | None -> (O.txt modname, `Default, None, None)
243✔
1442
        | Some (expansion_doc, items) ->
1,183✔
1443
            let status =
1444
              match t.type_ with
1445
              | ModuleType (Signature _) -> `Inline
775✔
1446
              | _ -> `Default
408✔
1447
            in
1448
            let url = Url.Path.from_identifier t.id in
1449
            let link = path url [ inline @@ Text modname ] in
1,183✔
1450
            let page =
1,183✔
1451
              make_expansion_page ~sidebar ~source_anchor url
1452
                [ t.doc; expansion_doc ] items
1453
            in
1454
            (link, status, Some page, Some expansion_doc)
1,183✔
1455
      in
1456
      let intro = O.keyword "module" ++ O.txt " " ++ modname in
1,426✔
1457
      let summary = O.ignore intro ++ mdexpr_in_decl ~sidebar t.id t.type_ in
1,426✔
1458
      let modexpr =
1,426✔
1459
        attach_expansion ~status
1460
          (Syntax.Type.annotation_separator, "sig", "end")
1461
          expansion summary
1462
      in
1463
      let content =
1,426✔
1464
        O.documentedSrc intro @ modexpr
1,426✔
1465
        @ O.documentedSrc
1,426✔
UNCOV
1466
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1467
      in
1468
      let attr = [ "module" ] in
1469
      let anchor = path_to_id t.id in
1470
      let doc = Comment.synopsis ~decl_doc:t.doc ~expansion_doc in
1,426✔
1471
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1472

1473
    and simple_expansion_in_decl (base : Paths.Identifier.Module.t) se =
1474
      let rec ty_of_se :
82✔
1475
          Lang.ModuleType.simple_expansion -> Lang.ModuleType.expr = function
1476
        | Signature sg -> Signature sg
82✔
UNCOV
1477
        | Functor (arg, sg) -> Functor (arg, ty_of_se sg)
×
1478
      in
1479
      mty_in_decl (base :> Paths.Identifier.Signature.t) (ty_of_se se)
82✔
1480

1481
    and mdexpr_in_decl ~sidebar (base : Paths.Identifier.Module.t) md =
1482
      let sig_dotdotdot =
1,426✔
1483
        O.txt Syntax.Type.annotation_separator
1,426✔
1484
        ++ O.cut ++ Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
1,426✔
1485
      in
1486
      match md with
1,426✔
1487
      | Alias (_, Some se) -> simple_expansion_in_decl ~sidebar base se
82✔
1488
      | Alias (p, _) when not Paths.Path.(is_hidden (p :> t)) ->
141✔
1489
          O.txt " =" ++ O.sp ++ mdexpr ~sidebar md
141✔
UNCOV
1490
      | Alias _ -> sig_dotdotdot
×
1491
      | ModuleType mt ->
1,203✔
1492
          mty_in_decl ~sidebar (base :> Paths.Identifier.Signature.t) mt
1493

1494
    and mdexpr : sidebar:_ -> Odoc_model.Lang.Module.decl -> text =
1495
     fun ~sidebar -> function
1496
      | Alias (mod_path, _) -> Link.from_path (mod_path :> Paths.Path.t)
183✔
NEW
UNCOV
1497
      | ModuleType mt -> mty ~sidebar mt
×
1498

1499
    and module_type_manifest ~sidebar ~subst ~source_anchor modname id doc
1500
        manifest prefix =
1501
      let expansion =
1,034✔
1502
        match manifest with
1503
        | None -> None
96✔
1504
        | Some e -> expansion_of_module_type_expr ~sidebar e
938✔
1505
      in
1506
      let modname, expansion, expansion_doc =
1507
        match expansion with
1508
        | None -> (O.txt modname, None, None)
285✔
1509
        | Some (expansion_doc, items) ->
749✔
1510
            let url = Url.Path.from_identifier id in
1511
            let link = path url [ inline @@ Text modname ] in
749✔
1512
            let page =
749✔
1513
              make_expansion_page ~sidebar ~source_anchor url
1514
                [ doc; expansion_doc ] items
1515
            in
1516
            (link, Some page, Some expansion_doc)
749✔
1517
      in
1518
      let summary =
1519
        match manifest with
1520
        | None -> O.noop
96✔
1521
        | Some expr ->
938✔
1522
            O.ignore (prefix ++ modname)
938✔
1523
            ++ (if subst then O.txt " :=" ++ O.sp else O.txt " =" ++ O.sp)
6✔
1524
            ++ mty ~sidebar expr
938✔
1525
      in
1526
      ( modname,
1527
        expansion_doc,
1528
        attach_expansion (" = ", "sig", "end") expansion summary )
1,034✔
1529

1530
    and module_type ~sidebar (t : Odoc_model.Lang.ModuleType.t) =
1531
      let prefix =
1,028✔
1532
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
1,028✔
1533
      in
1534
      let modname = Paths.Identifier.name t.id in
1,028✔
1535
      let source_anchor = source_anchor t.source_loc in
1,028✔
1536
      let modname, expansion_doc, mty =
1,028✔
1537
        module_type_manifest ~sidebar ~subst:false ~source_anchor modname t.id
1538
          t.doc t.expr prefix
1539
      in
1540
      let content =
1,028✔
1541
        O.documentedSrc (prefix ++ modname)
1,028✔
1542
        @ mty
1543
        @ O.documentedSrc
1,028✔
UNCOV
1544
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1545
      in
1546
      let attr = [ "module-type" ] in
1547
      let anchor = path_to_id t.id in
1548
      let doc = Comment.synopsis ~decl_doc:t.doc ~expansion_doc in
1,028✔
1549
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1550

1551
    and umty_hidden : Odoc_model.Lang.ModuleType.U.expr -> bool = function
1552
      | Path p -> Paths.Path.(is_hidden (p :> t))
287✔
1553
      | With (_, expr) -> umty_hidden expr
14✔
1554
      | TypeOf (ModPath m, _) | TypeOf (StructInclude m, _) ->
30✔
1555
          Paths.Path.(is_hidden (m :> t))
1556
      | Signature _ -> false
8✔
1557

1558
    and mty_hidden : Odoc_model.Lang.ModuleType.expr -> bool = function
1559
      | Path { p_path = mty_path; _ } -> Paths.Path.(is_hidden (mty_path :> t))
647✔
1560
      | With { w_expr; _ } -> umty_hidden w_expr
165✔
1561
      | TypeOf { t_desc = ModPath m; _ }
36✔
1562
      | TypeOf { t_desc = StructInclude m; _ } ->
12✔
1563
          Paths.Path.(is_hidden (m :> t))
1564
      | _ -> false
1,775✔
1565

1566
    and mty_with ~sidebar subs expr =
1567
      umty ~sidebar expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
177✔
1568
      ++ O.list
177✔
1569
           ~sep:(O.cut ++ O.txt " " ++ O.keyword "and" ++ O.txt " ")
177✔
1570
           ~f:(fun x -> O.span (substitution ~sidebar x))
207✔
1571
           subs
1572

1573
    and mty_typeof t_desc =
1574
      match t_desc with
114✔
1575
      | Odoc_model.Lang.ModuleType.ModPath m ->
66✔
1576
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
66✔
1577
          ++ O.keyword "of" ++ O.txt " "
66✔
1578
          ++ Link.from_path (m :> Paths.Path.t)
66✔
1579
      | StructInclude m ->
48✔
1580
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
48✔
1581
          ++ O.keyword "of" ++ O.txt " " ++ O.keyword "struct" ++ O.txt " "
48✔
1582
          ++ O.keyword "include" ++ O.txt " "
48✔
1583
          ++ Link.from_path (m :> Paths.Path.t)
48✔
1584
          ++ O.txt " " ++ O.keyword "end"
48✔
1585

1586
    and is_elidable_with_u : Odoc_model.Lang.ModuleType.U.expr -> bool =
1587
      function
1588
      | Path _ -> false
158✔
1589
      | Signature _ -> true
2✔
UNCOV
1590
      | With (_, expr) -> is_elidable_with_u expr
×
1591
      | TypeOf _ -> false
19✔
1592

1593
    and umty : sidebar:_ -> Odoc_model.Lang.ModuleType.U.expr -> text =
1594
     fun ~sidebar m ->
1595
      match m with
373✔
1596
      | Path p -> Link.from_path (p :> Paths.Path.t)
287✔
1597
      | Signature _ ->
6✔
1598
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
6✔
1599
      | With (_, expr) when is_elidable_with_u expr ->
14✔
1600
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
2✔
1601
      | With (subs, expr) -> mty_with ~sidebar subs expr
12✔
1602
      | TypeOf (t_desc, _) -> mty_typeof t_desc
66✔
1603

1604
    and mty : sidebar:_ -> Odoc_model.Lang.ModuleType.expr -> text =
1605
     fun ~sidebar m ->
1606
      if mty_hidden m then
2,635✔
UNCOV
1607
        Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1608
      else
1609
        match m with
2,635✔
1610
        | Path { p_path = mty_path; _ } ->
647✔
1611
            Link.from_path (mty_path :> Paths.Path.t)
1612
        | Functor (Unit, expr) ->
×
UNCOV
1613
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
UNCOV
1614
            ++ O.span (O.txt " () " ++ Syntax.Type.arrow)
×
NEW
UNCOV
1615
            ++ O.sp ++ mty ~sidebar expr
×
1616
        | Functor (Named arg, expr) ->
42✔
1617
            let arg_expr = arg.expr in
1618
            let stop_before =
1619
              expansion_of_module_type_expr ~sidebar arg_expr = None
42✔
1620
            in
1621
            let name =
1622
              let open Odoc_model.Lang.FunctorParameter in
1623
              let name = Paths.Identifier.name arg.id in
1624
              match
42✔
1625
                Url.from_identifier ~stop_before (arg.id :> Paths.Identifier.t)
1626
              with
UNCOV
1627
              | Error _ -> O.txt name
×
1628
              | Ok href -> resolved href [ inline @@ Text name ]
42✔
1629
            in
UNCOV
1630
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1631
            ++ (O.box_hv @@ O.span
42✔
1632
               @@ O.txt " (" ++ name
42✔
1633
                  ++ O.txt Syntax.Type.annotation_separator
42✔
1634
                  ++ mty ~sidebar arg_expr ++ O.txt ")" ++ O.txt " "
42✔
1635
                  ++ Syntax.Type.arrow)
42✔
1636
            ++ O.sp ++ mty ~sidebar expr
42✔
1637
        | With { w_expr; _ } when is_elidable_with_u w_expr ->
165✔
UNCOV
1638
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1639
        | With { w_substitutions; w_expr; _ } ->
165✔
1640
            O.box_hv @@ mty_with w_substitutions ~sidebar w_expr
165✔
1641
        | TypeOf { t_desc; _ } -> mty_typeof t_desc
48✔
1642
        | Signature _ ->
1,733✔
1643
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
1,733✔
1644

1645
    and mty_in_decl :
1646
        sidebar:_ ->
1647
        Paths.Identifier.Signature.t ->
1648
        Odoc_model.Lang.ModuleType.expr ->
1649
        text =
1650
     fun ~sidebar base -> function
1651
      | (Path _ | Signature _ | With _ | TypeOf _) as m ->
30✔
1652
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty ~sidebar m
1,452✔
1653
      | Functor _ as m when not Syntax.Mod.functor_contraction ->
131✔
NEW
UNCOV
1654
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty ~sidebar m
×
1655
      | Functor (arg, expr) ->
131✔
1656
          let text_arg =
1657
            match arg with
1658
            | Unit -> O.txt "()"
6✔
1659
            | Named arg ->
125✔
1660
                let arg_expr = arg.expr in
1661
                let stop_before =
1662
                  expansion_of_module_type_expr ~sidebar arg_expr = None
125✔
1663
                in
1664
                let name =
1665
                  let open Odoc_model.Lang.FunctorParameter in
1666
                  let name = Paths.Identifier.name arg.id in
1667
                  match
125✔
1668
                    Url.from_identifier ~stop_before
1669
                      (arg.id :> Paths.Identifier.t)
1670
                  with
UNCOV
1671
                  | Error _ -> O.txt name
×
1672
                  | Ok href -> resolved href [ inline @@ Text name ]
125✔
1673
                in
1674
                O.box_hv
125✔
1675
                @@ O.txt "(" ++ name
125✔
1676
                   ++ O.txt Syntax.Type.annotation_separator
125✔
1677
                   ++ O.cut ++ mty ~sidebar arg.expr ++ O.txt ")"
125✔
1678
          in
1679
          O.sp ++ text_arg ++ mty_in_decl ~sidebar base expr
131✔
1680

1681
    (* TODO : Centralize the list juggling for type parameters *)
1682
    and type_expr_in_subst td typath =
1683
      let typath = Link.from_fragment typath in
110✔
1684
      match td.Lang.TypeDecl.Equation.params with
110✔
1685
      | [] -> typath
92✔
1686
      | l -> Syntax.Type.handle_substitution_params typath (format_params l)
18✔
1687

1688
    and substitution :
1689
        sidebar:_ -> Odoc_model.Lang.ModuleType.substitution -> text =
1690
     fun ~sidebar -> function
1691
      | ModuleEq (frag_mod, md) ->
42✔
1692
          O.box_hv
1693
          @@ O.keyword "module" ++ O.txt " "
42✔
1694
             ++ Link.from_fragment (frag_mod :> Paths.Fragment.leaf)
42✔
1695
             ++ O.txt " =" ++ O.sp ++ mdexpr ~sidebar md
42✔
1696
      | ModuleTypeEq (frag_mty, md) ->
24✔
1697
          O.box_hv
1698
          @@ O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
24✔
1699
             ++ Link.from_fragment (frag_mty :> Paths.Fragment.leaf)
24✔
1700
             ++ O.txt " =" ++ O.sp ++ mty ~sidebar md
24✔
1701
      | TypeEq (frag_typ, td) ->
78✔
1702
          O.box_hv
1703
          @@ O.keyword "type" ++ O.txt " "
78✔
1704
             ++ type_expr_in_subst td (frag_typ :> Paths.Fragment.leaf)
78✔
1705
             ++ fst (format_manifest td)
78✔
1706
             ++ format_constraints
78✔
1707
                  td.Odoc_model.Lang.TypeDecl.Equation.constraints
1708
      | ModuleSubst (frag_mod, mod_path) ->
19✔
1709
          O.box_hv
1710
          @@ O.keyword "module" ++ O.txt " "
19✔
1711
             ++ Link.from_fragment (frag_mod :> Paths.Fragment.leaf)
19✔
1712
             ++ O.txt " :=" ++ O.sp
19✔
1713
             ++ Link.from_path (mod_path :> Paths.Path.t)
19✔
1714
      | ModuleTypeSubst (frag_mty, md) ->
12✔
1715
          O.box_hv
1716
          @@ O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
12✔
1717
             ++ Link.from_fragment (frag_mty :> Paths.Fragment.leaf)
12✔
1718
             ++ O.txt " :=" ++ O.sp ++ mty ~sidebar md
12✔
1719
      | TypeSubst (frag_typ, td) -> (
32✔
1720
          O.box_hv
1721
          @@ O.keyword "type" ++ O.txt " "
32✔
1722
             ++ type_expr_in_subst td (frag_typ :> Paths.Fragment.leaf)
32✔
1723
             ++ O.txt " :=" ++ O.sp
32✔
1724
             ++
32✔
1725
             match td.Lang.TypeDecl.Equation.manifest with
1726
             | None -> assert false (* cf loader/cmti *)
1727
             | Some te -> type_expr te)
32✔
1728

1729
    and include_ ~sidebar (t : Odoc_model.Lang.Include.t) =
1730
      let decl_hidden =
197✔
1731
        match t.decl with
UNCOV
1732
        | Alias p -> Paths.Path.(is_hidden (p :> t))
×
1733
        | ModuleType mty -> umty_hidden mty
197✔
1734
      in
1735
      let status = if decl_hidden then `Inline else t.status in
1✔
1736

1737
      let _, content = signature ~sidebar t.expansion.content in
1738
      let summary =
197✔
1739
        if decl_hidden then O.render (O.keyword "include" ++ O.txt " ...")
1✔
1740
        else
1741
          let include_decl =
196✔
1742
            match t.decl with
UNCOV
1743
            | Odoc_model.Lang.Include.Alias mod_path ->
×
UNCOV
1744
                Link.from_path (mod_path :> Paths.Path.t)
×
1745
            | ModuleType mt -> umty ~sidebar mt
196✔
1746
          in
1747
          O.render
196✔
1748
            (O.keyword "include" ++ O.txt " " ++ include_decl
196✔
UNCOV
1749
            ++ if Syntax.Mod.include_semicolon then O.keyword ";" else O.noop)
×
1750
      in
1751
      let content = { Include.content; status; summary } in
1752
      let attr = [ "include" ] in
1753
      let anchor = None in
1754
      let doc =
1755
        (* Documentation attached to includes behave differently than other
1756
           declarations, which show only the synopsis. We can't only show the
1757
           synopsis because no page is generated to render it and we'd loose
1758
           the full documentation.
1759
           The documentation from the expansion is not used. *)
1760
        Comment.to_ir t.doc
1761
      in
1762
      Item.Include { attr; anchor; doc; content; source_anchor = None }
197✔
1763
  end
1764

1765
  open Module
1766

1767
  module Page : sig
1768
    val compilation_unit :
1769
      ?sidebar:Lang.Sidebar.t -> Lang.Compilation_unit.t -> Document.t
1770

1771
    val page : ?sidebar:Lang.Sidebar.t -> Lang.Page.t -> Document.t
1772

1773
    val source_tree : Lang.SourceTree.t -> Document.t list
1774

1775
    val implementation :
1776
      Lang.Implementation.t ->
1777
      Syntax_highlighter.infos ->
1778
      string ->
1779
      Document.t list
1780
  end = struct
1781
    let pack : Lang.Compilation_unit.Packed.t -> Item.t list =
1782
     fun t ->
UNCOV
1783
      let f x =
×
UNCOV
1784
        let id = x.Lang.Compilation_unit.Packed.id in
×
1785
        let modname = Paths.Identifier.name id in
UNCOV
1786
        let md_def =
×
UNCOV
1787
          O.keyword "module" ++ O.txt " " ++ O.txt modname ++ O.txt " = "
×
UNCOV
1788
          ++ Link.from_path (x.path :> Paths.Path.t)
×
1789
        in
UNCOV
1790
        let content = O.documentedSrc md_def in
×
UNCOV
1791
        let anchor =
×
1792
          Utils.option_of_result
UNCOV
1793
          @@ Url.Anchor.from_identifier (id :> Paths.Identifier.t)
×
1794
        in
UNCOV
1795
        let attr = [ "modules" ] in
×
1796
        let doc = [] in
1797
        let decl = { Item.anchor; content; attr; doc; source_anchor = None } in
1798
        Item.Declaration decl
1799
      in
1800
      List.map f t
1801

1802
    module Hierarchy = struct
1803
      type 'a dir = 'a option * (string * 'a t) list
1804
      and 'a t = Leaf of 'a | Dir of 'a dir
1805

1806
      let rec add_entry_to_dir (dir : 'a dir) payload path =
1807
        match (path, dir) with
70✔
1808
        | [], _ -> assert false
1809
        | [ "index" ], (None, l) -> (Some payload, l)
10✔
1810
        | [ name ], (p, l) -> (p, (name, Leaf payload) :: l)
10✔
1811
        | name :: rest, (p, l) ->
50✔
1812
            let rec add_to_dir (l : (string * 'a t) list) =
1813
              match l with
60✔
1814
              | [] -> [ (name, Dir (add_entry_to_dir (None, []) payload rest)) ]
15✔
1815
              | (name2, Dir d) :: q when String.equal name name2 ->
35✔
1816
                  (name2, Dir (add_entry_to_dir d payload rest)) :: q
35✔
1817
              | d :: q -> d :: add_to_dir q
10✔
1818
            in
1819
            (p, add_to_dir l)
50✔
1820

1821
      let make l =
1822
        let empty = (None, []) in
5✔
1823
        let add_entry_to_dir acc (path, payload) =
1824
          add_entry_to_dir acc path payload
20✔
1825
        in
1826
        List.fold_left add_entry_to_dir empty l
1827

1828
      let rec remove_common_root = function
1829
        | None, [ (_, Dir d) ] -> remove_common_root d
10✔
1830
        | x -> x
5✔
1831

1832
      let rec to_sidebar ?(fallback = "root") convert (name, content) =
5✔
1833
        let name =
10✔
1834
          match name with
1835
          | Some v -> convert v
10✔
NEW
UNCOV
1836
          | None -> block (Block.Inline [ inline (Text fallback) ])
×
1837
        in
1838
        let content =
1839
          let content = List.map (t_to_sidebar convert) content in
10✔
1840
          block (Block.List (Block.Unordered, content))
10✔
1841
        in
1842
        [ name; content ]
1843

1844
      and t_to_sidebar convert = function
1845
        | _, Leaf payload -> [ convert payload ]
10✔
1846
        | fallback, Dir d -> to_sidebar ~fallback convert d
5✔
1847
    end
1848

1849
    let sidebar root_id (v : Odoc_model.Lang.Sidebar.t) =
1850
      let root_id = (root_id :> Paths.Identifier.t) in
5✔
1851
      let prepare (link_content, x) =
1852
        let x = (x :> Paths.Identifier.t) in
20✔
1853
        let payload =
1854
          (link_content, x, Odoc_model.Paths.Identifier.equal x root_id)
20✔
1855
        in
1856
        let path = Odoc_model.Paths.Identifier.fullname x in
1857
        (payload, path)
20✔
1858
      in
1859
      let title t =
1860
        block
15✔
1861
          (Inline [ inline (Inline.Styled (`Bold, [ inline (Inline.Text t) ])) ])
15✔
1862
      in
1863
      let page_hierarchy { Odoc_model.Lang.Sidebar.page_name; pages } =
NEW
UNCOV
1864
        if List.is_empty pages then []
×
1865
        else
1866
          let pages = List.map prepare pages in
5✔
1867
          let hierarchy =
5✔
1868
            Hierarchy.make pages |> Hierarchy.remove_common_root
5✔
1869
          in
1870
          let convert (content, id, is_highlighted) =
5✔
1871
            let url = Url.from_identifier ~stop_before:false id in
20✔
1872
            match url with
20✔
1873
            | Ok href ->
20✔
1874
                let target = InternalLink.Resolved href in
1875
                let content = Comment.link_content content in
1876
                let link = { InternalLink.target; content; tooltip = None } in
20✔
1877
                let attr = if is_highlighted then [ "current_unit" ] else [] in
4✔
1878
                block (Inline [ inline ~attr @@ Inline.InternalLink link ])
20✔
NEW
UNCOV
1879
            | Error _ ->
×
1880
                let content = Comment.link_content content in
1881
                (* let attr = if is_highlighted then [ "current_unit" ] else [] in *)
NEW
UNCOV
1882
                block (Inline content)
×
1883
          in
1884

1885
          let pages = Hierarchy.to_sidebar convert hierarchy in
1886
          let pages = [ block (Block.List (Block.Unordered, [ pages ])) ] in
5✔
1887
          [ title @@ page_name ^ "'s Pages" ] @ pages
5✔
1888
      in
1889
      let page_hierarchies = List.concat_map page_hierarchy v.pages in
1890
      let units =
5✔
1891
        let item id =
1892
          let id = (id :> Paths.Identifier.t) in
5✔
1893
          let href, name, is_highlighted =
1894
            ( Url.from_identifier ~stop_before:false id,
5✔
1895
              Paths.Identifier.name id,
5✔
1896
              Odoc_model.Paths.Identifier.equal id root_id )
5✔
1897
          in
1898
          match href with
NEW
UNCOV
1899
          | Error _ -> None
×
1900
          | Ok href ->
5✔
1901
              let target = InternalLink.Resolved href in
1902
              let content = [ inline @@ Text name ] in
5✔
1903
              let link = { InternalLink.target; content; tooltip = None } in
1904
              let attr = if is_highlighted then [ "current_unit" ] else [] in
1✔
1905
              let elem =
1906
                [ block (Inline [ inline ~attr @@ Inline.InternalLink link ]) ]
5✔
1907
              in
1908
              Some elem
1909
        in
1910
        List.map
5✔
1911
          (fun Odoc_model.Lang.Sidebar.{ units; name } ->
1912
            let units = List.filter_map item units in
5✔
1913
            [ title name; block (List (Block.Unordered, units)) ])
5✔
1914
          v.libraries
1915
      in
1916
      let units = block (Block.List (Block.Unordered, units)) in
1917
      let units = [ title "Libraries"; units ] in
5✔
1918
      page_hierarchies @ units
1919

1920
    let compilation_unit ?sidebar:sb (t : Odoc_model.Lang.Compilation_unit.t) =
1921
      let url = Url.Path.from_identifier t.id in
276✔
1922
      let sidebar = Option.map (sidebar t.id) sb in
276✔
1923
      let unit_doc, items =
276✔
1924
        match t.content with
1925
        | Module sign -> signature ~sidebar sign
276✔
UNCOV
1926
        | Pack packed -> ([], pack packed)
×
1927
      in
1928
      let source_anchor = source_anchor t.source_loc in
1929
      let page =
276✔
1930
        make_expansion_page ~sidebar ~source_anchor url [ unit_doc ] items
1931
      in
1932
      Document.Page page
276✔
1933

1934
    let page ?sidebar:sb (t : Odoc_model.Lang.Page.t) =
1935
      let url = Url.Path.from_identifier t.name in
46✔
1936
      let preamble, items = Sectioning.docs t.content in
46✔
1937
      let source_anchor = None in
46✔
1938
      let sidebar = Option.map (sidebar t.name) sb in
46✔
1939
      Document.Page { Page.preamble; items; url; source_anchor; sidebar }
46✔
1940

1941
    let source_tree t =
UNCOV
1942
      let dir_pages = t.Odoc_model.Lang.SourceTree.source_children in
×
1943
      let open Paths.Identifier in
1944
      let module Set = Set.Make (SourceDir) in
1945
      let module M = Map.Make (SourceDir) in
1946
      (* mmap is a from a [SourceDir.t] to its [SourceDir.t] and [SourcePage.t]
1947
         children *)
1948
      let mmap =
1949
        let add parent f mmap =
UNCOV
1950
          let old_value =
×
UNCOV
1951
            try M.find parent mmap with Not_found -> (Set.empty, [])
×
1952
          in
UNCOV
1953
          M.add parent (f old_value) mmap
×
UNCOV
1954
        and add_file file (set, lp) = (set, file :: lp)
×
UNCOV
1955
        and add_dir dir (set, lp) = (Set.add dir set, lp) in
×
1956
        let rec dir_ancestors_add dir mmap =
UNCOV
1957
          match dir.iv with
×
UNCOV
1958
          | `SourceDir (parent, _) ->
×
UNCOV
1959
              let mmap = add parent (add_dir dir) mmap in
×
UNCOV
1960
              dir_ancestors_add parent mmap
×
UNCOV
1961
          | `Page _ -> mmap
×
1962
        in
1963
        let file_ancestors_add ({ iv = `SourcePage (parent, _); _ } as file)
1964
            mmap =
UNCOV
1965
          let mmap = add parent (add_file file) mmap in
×
UNCOV
1966
          dir_ancestors_add parent mmap
×
1967
        in
UNCOV
1968
        List.fold_left
×
UNCOV
1969
          (fun mmap file -> file_ancestors_add file mmap)
×
1970
          M.empty dir_pages
1971
      in
1972
      let page_of_dir (dir : SourceDir.t) (dir_children, file_children) =
UNCOV
1973
        let url = Url.Path.from_identifier dir in
×
UNCOV
1974
        let block ?(attr = []) desc = Block.{ attr; desc } in
×
UNCOV
1975
        let inline ?(attr = []) desc = Inline.[ { attr; desc } ] in
×
1976
        let header =
UNCOV
1977
          let title = inline (Text (name dir)) in
×
UNCOV
1978
          Item.Heading
×
1979
            Heading.{ label = None; level = 0; title; source_anchor = None }
1980
        in
UNCOV
1981
        let li ?(attr = []) name url =
×
UNCOV
1982
          let link url desc =
×
UNCOV
1983
            let content = [ Inline.{ attr = []; desc } ] and tooltip = None in
×
1984
            Inline.InternalLink
1985
              { InternalLink.target = Resolved url; content; tooltip }
1986
          in
UNCOV
1987
          [ block ~attr @@ Block.Inline (inline @@ link url (Text name)) ]
×
1988
        in
1989
        let li_of_child child =
UNCOV
1990
          match child with
×
1991
          | { iv = `Page _; _ } ->
1992
              assert false (* No [`Page] is child of a [`SourceDir] *)
UNCOV
1993
          | { iv = `SourceDir (_, name); _ } ->
×
UNCOV
1994
              let url = child |> Url.Path.from_identifier |> Url.from_path in
×
UNCOV
1995
              (name, url)
×
1996
        in
1997
        let li_of_file_child ({ iv = `SourcePage (_, name); _ } as child) =
UNCOV
1998
          let url = child |> Url.Path.from_identifier |> Url.from_path in
×
UNCOV
1999
          (name, url)
×
2000
        in
2001
        let items =
UNCOV
2002
          let text ?(attr = []) desc = Item.Text [ { attr; desc } ] in
×
UNCOV
2003
          let list l = Block.List (Block.Unordered, l) in
×
2004
          let list_of_children =
2005
            let dir_list =
UNCOV
2006
              Set.fold
×
UNCOV
2007
                (fun child acc -> li_of_child child :: acc)
×
2008
                dir_children []
2009
            and file_list =
UNCOV
2010
              List.map (fun child -> li_of_file_child child) file_children
×
2011
            in
UNCOV
2012
            let sort ?(attr = []) l =
×
UNCOV
2013
              l
×
UNCOV
2014
              |> List.sort (fun (n1, _) (n2, _) -> String.compare n1 n2)
×
UNCOV
2015
              |> List.map (fun (name, url) -> li ~attr name url)
×
2016
            in
UNCOV
2017
            sort ~attr:[ "odoc-directory" ] dir_list
×
UNCOV
2018
            @ sort ~attr:[ "odoc-file" ] file_list
×
2019
          in
2020
          header
UNCOV
2021
          :: [ text ~attr:[ "odoc-folder-list" ] @@ list list_of_children ]
×
2022
        in
2023
        Document.Page
2024
          {
2025
            Types.Page.preamble = [];
2026
            items;
2027
            url;
2028
            source_anchor = None;
2029
            sidebar = None;
2030
          }
2031
      in
UNCOV
2032
      M.fold (fun dir children acc -> page_of_dir dir children :: acc) mmap []
×
2033

2034
    let implementation (v : Odoc_model.Lang.Implementation.t) syntax_info
2035
        source_code =
2036
      match v.id with
26✔
UNCOV
2037
      | None -> []
×
2038
      | Some id ->
26✔
2039
          [
2040
            Document.Source_page
2041
              (Source_page.source id syntax_info v.source_info source_code);
26✔
2042
          ]
2043
  end
2044

2045
  include Page
2046

2047
  let type_expr = type_expr
2048

2049
  let record = record
2050
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