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

ocaml / odoc / 3054

19 Feb 2026 11:33AM UTC coverage: 71.709% (-1.2%) from 72.946%
3054

Pull #1399

github

web-flow
Merge 8fa44a817 into c3f0f46ee
Pull Request #1399: Upstream OxCaml

20 of 281 new or added lines in 21 files covered. (7.12%)

162 existing lines in 11 files now uncovered.

10400 of 14503 relevant lines covered (71.71%)

7007.14 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

92
include Generator_signatures
93

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

429
    and type_expr ?(needs_parentheses = false) (t : Odoc_model.Lang.TypeExpr.t)
191,591✔
430
        =
431
      let enclose_parens_if_needed res =
312,055✔
432
        if needs_parentheses then enclose ~l:"(" res ~r:")" else res
32✔
433
      in
434
      match t with
435
      | Var s -> type_var (Syntax.Type.var_prefix ^ s)
9,461✔
436
      | Any -> type_var Syntax.Type.any
18✔
437
      | Alias (te, alias) ->
133✔
438
          enclose_parens_if_needed
439
            (type_expr ~needs_parentheses:true te
133✔
440
            ++ O.txt " " ++ O.keyword "as" ++ O.txt " '" ++ O.txt alias)
133✔
441
      | Arrow (None, src, dst) ->
43,800✔
442
          let res =
443
            O.span
43,800✔
444
              ((O.box_hv @@ type_expr ~needs_parentheses:true src)
43,800✔
445
              ++ O.txt " " ++ Syntax.Type.arrow)
43,800✔
446
            ++ O.sp ++ type_expr dst
43,800✔
447
            (* ++ O.end_hv *)
448
          in
449
          if not needs_parentheses then res else enclose ~l:"(" res ~r:")"
783✔
450
      | Arrow (Some (RawOptional _ as lbl), _src, dst) ->
1✔
451
          let res =
452
            O.span
1✔
453
              (O.box_hv
1✔
454
              @@ label lbl ++ O.txt ":"
1✔
455
                 ++ tag "error" (O.txt "???")
1✔
456
                 ++ O.txt " " ++ Syntax.Type.arrow)
1✔
457
            ++ O.sp ++ type_expr dst
1✔
458
          in
459
          if not needs_parentheses then res else enclose ~l:"(" res ~r:")"
×
460
      | Arrow (Some lbl, src, dst) ->
3,262✔
461
          let res =
462
            O.span
3,262✔
463
              ((O.box_hv
3,262✔
464
               @@ label lbl ++ O.txt ":" ++ O.cut
3,262✔
465
                  ++ (O.box_hv @@ type_expr ~needs_parentheses:true src))
3,262✔
466
              ++ O.txt " " ++ Syntax.Type.arrow)
3,262✔
467
            ++ O.sp ++ type_expr dst
3,262✔
468
          in
469
          if not needs_parentheses then res else enclose ~l:"(" res ~r:")"
16✔
470
      | Tuple lst -> tuple ~needs_parentheses ~boxed:true lst
677✔
NEW
471
      | Unboxed_tuple lst -> tuple ~needs_parentheses ~boxed:false lst
×
472
      | Constr (path, args) ->
179,424✔
473
          let link = Link.from_path (path :> Paths.Path.t) in
474
          format_type_path ~delim:`parens args link
179,424✔
475
      | Polymorphic_variant v -> te_variant v
75,143✔
476
      | Object o -> te_object o
80✔
477
      | Class (path, args) ->
8✔
478
          format_type_path ~delim:`brackets args
479
            (Link.from_path (path :> Paths.Path.t))
8✔
480
      | Poly (polyvars, t) ->
24✔
481
          O.txt ("'" ^ String.concat ~sep:" '" polyvars ^ ". ") ++ type_expr t
24✔
NEW
482
      | Quote t -> O.span (O.txt "<[ " ++ O.box_hv (type_expr t) ++ O.txt " ]>")
×
NEW
483
      | Splice t -> O.span (O.txt "$" ++ type_expr ~needs_parentheses:true t)
×
484
      | Package pkg ->
24✔
485
          enclose ~l:"(" ~r:")"
486
            (O.keyword "module" ++ O.txt " "
24✔
487
            ++ Link.from_path (pkg.path :> Paths.Path.t)
24✔
488
            ++
24✔
489
            match pkg.substitutions with
490
            | [] -> O.noop
16✔
491
            | fst :: lst ->
8✔
492
                O.sp
493
                ++ O.box_hv (O.keyword "with" ++ O.txt " " ++ package_subst fst)
8✔
494
                ++ O.list lst ~f:(fun s ->
8✔
495
                       O.cut
8✔
496
                       ++ (O.box_hv
8✔
497
                          @@ O.txt " " ++ O.keyword "and" ++ O.txt " "
8✔
498
                             ++ package_subst s)))
8✔
499

500
    and package_subst
501
        ((frag_typ, te) : Paths.Fragment.Type.t * Odoc_model.Lang.TypeExpr.t) :
502
        text =
503
      let typath = Link.from_fragment (frag_typ :> Paths.Fragment.leaf) in
16✔
504
      O.keyword "type" ++ O.txt " " ++ typath ++ O.txt " =" ++ O.sp
16✔
505
      ++ type_expr te
16✔
506
  end
507

508
  open Type_expression
509

510
  (* Also handles constructor declarations for exceptions and extensible
511
     variants, and exposes a few helpers used in formatting classes and signature
512
     constraints. *)
513
  module Type_declaration : sig
514
    val type_decl :
515
      ?is_substitution:bool ->
516
      Lang.Signature.recursive * Lang.TypeDecl.t ->
517
      Item.t
518

519
    val extension : Lang.Extension.t -> Item.t
520

521
    val record : Lang.TypeDecl.Field.t list -> DocumentedSrc.one list
522

523
    val unboxed_record :
524
      Lang.TypeDecl.UnboxedField.t list -> DocumentedSrc.one list
525

526
    val exn : Lang.Exception.t -> Item.t
527

528
    val format_params :
529
      ?delim:[ `parens | `brackets ] -> Lang.TypeDecl.param list -> text
530

531
    val format_manifest :
532
      ?is_substitution:bool ->
533
      ?compact_variants:bool ->
534
      Lang.TypeDecl.Equation.t ->
535
      text * bool
536

537
    val format_constraints : (Lang.TypeExpr.t * Lang.TypeExpr.t) list -> text
538
  end = struct
539
    let record fields =
540
      let field mutable_ id typ =
85✔
541
        let url = Url.from_identifier ~stop_before:true id in
154✔
542
        let name = Paths.Identifier.name id in
154✔
543
        let attrs = [ "def"; "record"; Url.Anchor.string_of_kind url.kind ] in
154✔
544
        let cell =
545
          (* O.td ~a:[ O.a_class ["def"; kind ] ]
546
           *   [O.a ~a:[O.a_href ("#" ^ anchor); O.a_class ["anchor"]] []
547
           *   ; *)
548
          O.code
549
            ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
24✔
550
            ++ O.txt name
154✔
551
            ++ O.txt Syntax.Type.annotation_separator
154✔
552
            ++ type_expr typ
154✔
553
            ++ O.txt Syntax.Type.Record.field_separator)
154✔
554
          (* ] *)
555
        in
556
        (url, attrs, cell)
154✔
557
      in
558
      let rows =
559
        fields
560
        |> List.map (fun fld ->
561
               let open Odoc_model.Lang.TypeDecl.Field in
154✔
562
               let url, attrs, code =
563
                 field fld.mutable_ (fld.id :> Paths.Identifier.t) fld.type_
564
               in
565
               let anchor = Some url in
154✔
566
               let doc = fld.doc.elements in
567
               let rhs = Comment.to_ir doc in
568
               let doc = if not (Comment.has_doc doc) then [] else rhs in
64✔
569
               let markers = Syntax.Comment.markers in
570
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
571
      in
572
      let content =
85✔
573
        O.documentedSrc (O.txt "{") @ rows @ O.documentedSrc (O.txt "}")
85✔
574
      in
575
      content
576

577
    let unboxed_record fields =
NEW
578
      let field mutable_ id typ =
×
NEW
579
        let url = Url.from_identifier ~stop_before:true id in
×
NEW
580
        let name = Paths.Identifier.name id in
×
NEW
581
        let attrs = [ "def"; "record"; Url.Anchor.string_of_kind url.kind ] in
×
582
        let cell =
583
          (* O.td ~a:[ O.a_class ["def"; kind ] ]
584
           *   [O.a ~a:[O.a_href ("#" ^ anchor); O.a_class ["anchor"]] []
585
           *   ; *)
586
          O.code
NEW
587
            ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
×
NEW
588
            ++ O.txt name
×
NEW
589
            ++ O.txt Syntax.Type.annotation_separator
×
NEW
590
            ++ type_expr typ
×
NEW
591
            ++ O.txt Syntax.Type.Record.field_separator)
×
592
          (* ] *)
593
        in
NEW
594
        (url, attrs, cell)
×
595
      in
596
      let rows =
597
        fields
598
        |> List.map (fun fld ->
NEW
599
               let open Odoc_model.Lang.TypeDecl.UnboxedField in
×
600
               let url, attrs, code =
601
                 field fld.mutable_ (fld.id :> Paths.Identifier.t) fld.type_
602
               in
NEW
603
               let anchor = Some url in
×
604
               let doc = fld.doc.elements in
605
               let rhs = Comment.to_ir doc in
NEW
606
               let doc = if not (Comment.has_doc doc) then [] else rhs in
×
607
               let markers = Syntax.Comment.markers in
608
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
609
      in
NEW
610
      let content =
×
NEW
611
        O.documentedSrc (O.txt "#{") @ rows @ O.documentedSrc (O.txt "}")
×
612
      in
613
      content
614

615
    let constructor :
616
        Paths.Identifier.t ->
617
        Odoc_model.Lang.TypeDecl.Constructor.argument ->
618
        Odoc_model.Lang.TypeExpr.t option ->
619
        DocumentedSrc.t =
620
     fun id args ret_type ->
621
      let name = Paths.Identifier.name id in
656✔
622
      let kind = Url.(kind id |> Anchor.string_of_kind) in
656✔
623
      let cstr = tag kind (O.txt name) in
656✔
624
      let is_gadt, ret_type =
656✔
625
        match ret_type with
626
        | None -> (false, O.noop)
520✔
627
        | Some te ->
136✔
628
            let constant = match args with Tuple [] -> true | _ -> false in
48✔
629
            let ret_type =
630
              O.txt " "
136✔
631
              ++ (if constant then O.txt ":" else Syntax.Type.GADT.arrow)
48✔
632
              ++ O.txt " " ++ type_expr te
136✔
633
            in
634
            (true, ret_type)
136✔
635
      in
636
      match args with
637
      | Tuple [] -> O.documentedSrc (cstr ++ ret_type)
289✔
638
      | Tuple lst ->
350✔
639
          let params =
640
            O.list lst ~sep:Syntax.Type.Tuple.element_separator
641
              ~f:(type_expr ~needs_parentheses:is_gadt)
642
          in
643
          O.documentedSrc
350✔
644
            (cstr
645
            ++ (if Syntax.Type.Variant.parenthesize_params then
350✔
646
                  O.txt "(" ++ params ++ O.txt ")"
×
647
                else
648
                  (if is_gadt then O.txt Syntax.Type.annotation_separator
88✔
649
                   else O.txt " " ++ O.keyword "of" ++ O.txt " ")
262✔
650
                  ++ params)
350✔
651
            ++ ret_type)
350✔
652
      | Record fields ->
17✔
653
          if is_gadt then
654
            O.documentedSrc (cstr ++ O.txt Syntax.Type.annotation_separator)
×
655
            @ record fields @ O.documentedSrc ret_type
×
656
          else
657
            O.documentedSrc (cstr ++ O.txt " " ++ O.keyword "of" ++ O.txt " ")
17✔
658
            @ record fields
17✔
659

660
    let variant cstrs : DocumentedSrc.t =
661
      let constructor id args res =
235✔
662
        let url = Url.from_identifier ~stop_before:true id in
436✔
663
        let attrs = [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ] in
436✔
664
        let content =
665
          let doc = constructor id args res in
666
          O.documentedSrc (O.txt "| ") @ doc
436✔
667
        in
668
        (url, attrs, content)
669
      in
670
      match cstrs with
671
      | [] -> O.documentedSrc (O.txt "|")
×
672
      | _ :: _ ->
235✔
673
          let rows =
674
            cstrs
675
            |> List.map (fun cstr ->
676
                   let open Odoc_model.Lang.TypeDecl.Constructor in
436✔
677
                   let url, attrs, code =
678
                     constructor
679
                       (cstr.id :> Paths.Identifier.t)
680
                       cstr.args cstr.res
681
                   in
682
                   let anchor = Some url in
436✔
683
                   let doc = cstr.doc.elements in
684
                   let rhs = Comment.to_ir doc in
685
                   let doc = if not (Comment.has_doc doc) then [] else rhs in
73✔
686
                   let markers = Syntax.Comment.markers in
687
                   DocumentedSrc.Nested { anchor; attrs; code; doc; markers })
688
          in
689
          rows
235✔
690

691
    let extension_constructor (t : Odoc_model.Lang.Extension.Constructor.t) =
692
      let id = (t.id :> Paths.Identifier.t) in
154✔
693
      let url = Url.from_identifier ~stop_before:true id in
694
      let anchor = Some url in
154✔
695
      let attrs = [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ] in
154✔
696
      let code = O.documentedSrc (O.txt "| ") @ constructor id t.args t.res in
154✔
697
      let doc = Comment.to_ir t.doc.elements in
698
      let markers = Syntax.Comment.markers in
154✔
699
      DocumentedSrc.Nested { anchor; attrs; code; doc; markers }
700

701
    let extension (t : Odoc_model.Lang.Extension.t) =
702
      let prefix =
126✔
703
        O.keyword "type" ++ O.txt " "
126✔
704
        ++ Link.from_path (t.type_path :> Paths.Path.t)
126✔
705
        ++ O.txt " +=" ++ O.sp
126✔
706
        ++
707
        if t.private_ then O.keyword Syntax.Type.private_keyword ++ O.sp
8✔
708
        else O.noop
118✔
709
      in
710
      let content =
126✔
711
        O.documentedSrc prefix
126✔
712
        @ List.map extension_constructor t.constructors
126✔
713
        @ O.documentedSrc
126✔
714
            (if Syntax.Type.type_def_semicolon then O.txt ";" else O.noop)
×
715
      in
716
      let attr = [ "type"; "extension" ] in
717
      let anchor = Some (Url.Anchor.extension_decl t) in
126✔
718
      let doc = Comment.to_ir t.doc.elements in
719
      let source_anchor =
126✔
720
        (* Take the anchor from the first constructor only for consistency with
721
           regular variants. *)
722
        match t.constructors with
723
        | hd :: _ -> source_anchor hd.source_loc
126✔
724
        | [] -> None
×
725
      in
726
      Item.Declaration { attr; anchor; doc; content; source_anchor }
727

728
    let exn (t : Odoc_model.Lang.Exception.t) =
729
      let cstr = constructor (t.id :> Paths.Identifier.t) t.args t.res in
66✔
730
      let content =
66✔
731
        O.documentedSrc (O.keyword "exception" ++ O.txt " ")
66✔
732
        @ cstr
733
        @ O.documentedSrc
66✔
734
            (if Syntax.Type.Exception.semicolon then O.txt ";" else O.noop)
×
735
      in
736
      let attr = [ "exception" ] in
737
      let anchor = path_to_id t.id in
738
      let doc = Comment.to_ir t.doc.elements in
66✔
739
      let source_anchor = source_anchor t.source_loc in
66✔
740
      Item.Declaration { attr; anchor; doc; content; source_anchor }
66✔
741

742
    let polymorphic_variant ~type_ident
743
        (t : Odoc_model.Lang.TypeExpr.Polymorphic_variant.t) =
744
      let row item =
74✔
745
        let kind_approx, cstr, doc =
156✔
746
          match item with
747
          | Odoc_model.Lang.TypeExpr.Polymorphic_variant.Type te ->
24✔
748
              ("unknown", O.documentedSrc (type_expr te), None)
24✔
749
          | Constructor { constant; name; arguments; doc; _ } -> (
132✔
750
              let cstr = "`" ^ name in
751
              ( "constructor",
752
                (match arguments with
753
                | [] -> O.documentedSrc (O.txt cstr)
58✔
754
                | _ ->
74✔
755
                    (* Multiple arguments in a polymorphic variant constructor correspond
756
                       to a conjunction of types, not a product: [`Lbl int&float].
757
                       If constant is [true], the conjunction starts with an empty type,
758
                       for instance [`Lbl &int].
759
                    *)
760
                    let wrapped_type_expr =
761
                      (* type conjunction in Reason is printed as `Lbl (t1)&(t2)` *)
762
                      if Syntax.Type.Variant.parenthesize_params then fun x ->
×
763
                        O.txt "(" ++ type_expr x ++ O.txt ")"
×
764
                      else fun x -> type_expr x
74✔
765
                    in
766
                    let params =
767
                      O.box_hv
768
                      @@ O.list arguments
74✔
769
                           ~sep:(O.txt " &" ++ O.sp)
74✔
770
                           ~f:wrapped_type_expr
771
                    in
772
                    let params =
74✔
773
                      if constant then O.txt "& " ++ params else params
×
774
                    in
775
                    O.documentedSrc
74✔
776
                      (O.txt cstr
74✔
777
                      ++
74✔
778
                      if Syntax.Type.Variant.parenthesize_params then params
×
779
                      else O.txt " " ++ O.keyword "of" ++ O.sp ++ params)),
74✔
780
                match doc with
781
                | { elements = []; _ } -> None
132✔
782
                | _ -> Some (Comment.to_ir doc.elements) ))
×
783
        in
784
        let markers = Syntax.Comment.markers in
785
        try
786
          let url = Url.Anchor.polymorphic_variant ~type_ident item in
787
          let attrs =
156✔
788
            [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ]
156✔
789
          in
790
          let anchor = Some url in
791
          let code = O.documentedSrc (O.txt "| ") @ cstr in
156✔
792
          let doc = match doc with None -> [] | Some doc -> doc in
×
793
          DocumentedSrc.Nested { attrs; anchor; code; doc; markers }
794
        with Failure s ->
×
795
          Printf.eprintf "ERROR: %s\n%!" s;
796
          let code = O.documentedSrc (O.txt "| ") @ cstr in
×
797
          let attrs = [ "def"; kind_approx ] in
798
          let doc = [] in
799
          let anchor = None in
800
          DocumentedSrc.Nested { attrs; anchor; code; doc; markers }
801
      in
802
      let variants = List.map row t.elements in
803
      let intro, ending =
74✔
804
        match t.kind with
805
        | Fixed -> (O.documentedSrc (O.txt "[ "), O.documentedSrc (O.txt " ]"))
66✔
806
        | Open -> (O.documentedSrc (O.txt "[> "), O.documentedSrc (O.txt " ]"))
8✔
807
        | Closed [] ->
×
808
            (O.documentedSrc (O.txt "[< "), O.documentedSrc (O.txt " ]"))
×
809
        | Closed lst ->
×
810
            let constrs = String.concat ~sep:" " lst in
811
            ( O.documentedSrc (O.txt "[< "),
×
812
              O.documentedSrc (O.txt (" " ^ constrs ^ " ]")) )
×
813
      in
814
      intro @ variants @ ending
815

816
    let format_params :
817
        'row.
818
        ?delim:[ `parens | `brackets ] ->
819
        Odoc_model.Lang.TypeDecl.param list ->
820
        text =
821
     fun ?(delim = `parens) params ->
461✔
822
      let format_param { Odoc_model.Lang.TypeDecl.desc; variance; injectivity }
552✔
823
          =
824
        let desc =
605✔
825
          match desc with
826
          | Odoc_model.Lang.TypeDecl.Any -> [ "_" ]
33✔
827
          | Var s -> [ "'"; s ]
572✔
828
        in
829
        let var_desc =
830
          match variance with
831
          | None -> desc
589✔
832
          | Some Odoc_model.Lang.TypeDecl.Pos -> "+" :: desc
8✔
833
          | Some Odoc_model.Lang.TypeDecl.Neg -> "-" :: desc
8✔
834
          | Some Odoc_model.Lang.TypeDecl.Bivariant -> "+" :: "-" :: desc
×
835
        in
836
        let final = if injectivity then "!" :: var_desc else var_desc in
×
837
        String.concat ~sep:"" final
838
      in
839
      O.txt
840
        (match params with
841
        | [] -> ""
59✔
842
        | [ x ] -> format_param x |> Syntax.Type.handle_format_params
381✔
843
        | lst -> (
112✔
844
            let params = String.concat ~sep:", " (List.map format_param lst) in
112✔
845
            (match delim with `parens -> "(" | `brackets -> "[")
×
846
            ^ params
847
            ^ match delim with `parens -> ")" | `brackets -> "]"))
×
848

849
    let format_constraints constraints =
850
      O.list constraints ~f:(fun (t1, t2) ->
3,084✔
851
          O.sp
104✔
852
          ++ (O.box_hv
104✔
853
             @@ O.keyword "constraint" ++ O.sp
104✔
854
                ++ O.box_hv_no_indent (type_expr t1)
104✔
855
                ++ O.txt " =" ++ O.sp
104✔
856
                ++ O.box_hv_no_indent (type_expr t2)))
104✔
857

858
    let format_manifest :
859
        'inner_row 'outer_row.
860
        ?is_substitution:bool ->
861
        ?compact_variants:bool ->
862
        Odoc_model.Lang.TypeDecl.Equation.t ->
863
        text * bool =
864
     fun ?(is_substitution = false) ?(compact_variants = true) equation ->
104✔
865
      let _ = compact_variants in
3,002✔
866
      (* TODO *)
867
      let private_ = equation.private_ in
868
      match equation.manifest with
869
      | None -> (O.noop, private_)
1,742✔
870
      | Some t ->
1,260✔
871
          let manifest =
872
            O.txt (if is_substitution then " :=" else " =")
16✔
873
            ++ O.sp
1,260✔
874
            ++ (if private_ then
1,260✔
875
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
876
                else O.noop)
1,252✔
877
            ++ type_expr t
1,260✔
878
          in
879
          (manifest, false)
1,260✔
880

881
    let type_decl ?(is_substitution = false)
2,956✔
882
        ((recursive, t) : Lang.Signature.recursive * Lang.TypeDecl.t) =
883
      let keyword' =
2,972✔
884
        match recursive with
885
        | Ordinary | Rec -> O.keyword "type"
×
886
        | And -> O.keyword "and"
18✔
887
        | Nonrec -> O.keyword "type" ++ O.txt " " ++ O.keyword "nonrec"
1✔
888
      in
889
      let tyname = Paths.Identifier.name t.id in
890
      let tconstr =
2,972✔
891
        match t.equation.params with
892
        | [] -> O.txt tyname
2,535✔
893
        | l ->
437✔
894
            let params = format_params l in
895
            Syntax.Type.handle_constructor_params (O.txt tyname) params
437✔
896
      in
897
      let intro = keyword' ++ O.txt " " ++ tconstr in
2,972✔
898
      let constraints = format_constraints t.equation.constraints in
2,972✔
899
      let manifest, need_private, long_prefix =
2,972✔
900
        match t.equation.manifest with
901
        | Some (Odoc_model.Lang.TypeExpr.Polymorphic_variant variant) ->
74✔
902
            let code =
903
              polymorphic_variant
904
                ~type_ident:(t.id :> Paths.Identifier.t)
905
                variant
906
            in
907
            let manifest =
74✔
908
              O.documentedSrc
74✔
909
                (O.ignore intro
74✔
910
                ++ O.txt (if is_substitution then " :=" else " =")
×
911
                ++ O.sp
74✔
912
                ++
74✔
913
                if t.equation.private_ then
914
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
915
                else O.noop)
66✔
916
              @ code
917
            in
918
            (manifest, false, O.noop)
919
        | _ ->
2,898✔
920
            let manifest, need_private =
921
              format_manifest ~is_substitution t.equation
922
            in
923
            let text = O.ignore intro ++ manifest in
2,898✔
924
            (O.documentedSrc @@ text, need_private, text)
2,898✔
925
      in
926
      let representation =
927
        match t.representation with
928
        | None -> []
2,611✔
929
        | Some repr ->
361✔
930
            let content =
931
              match repr with
932
              | Extensible -> O.documentedSrc (O.txt "..")
61✔
933
              | Variant cstrs -> variant cstrs
235✔
934
              | Record fields -> record fields
65✔
NEW
935
              | Record_unboxed_product fields -> unboxed_record fields
×
936
            in
937
            if List.length content > 0 then
361✔
938
              O.documentedSrc
361✔
939
                (O.ignore long_prefix ++ O.txt " =" ++ O.sp
361✔
940
                ++
361✔
941
                if need_private then
942
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
943
                else O.noop)
353✔
944
              @ content
945
            else []
×
946
      in
947
      let content =
948
        O.documentedSrc intro @ manifest @ representation
2,972✔
949
        @ O.documentedSrc constraints
2,972✔
950
        @ O.documentedSrc
2,972✔
951
            (if Syntax.Type.type_def_semicolon then O.txt ";" else O.noop)
×
952
      in
953
      let attr = "type" :: (if is_substitution then [ "subst" ] else []) in
16✔
954
      let anchor = path_to_id t.id in
955
      let doc = Comment.to_ir t.doc.elements in
2,972✔
956
      let source_anchor = source_anchor t.source_loc in
2,972✔
957
      Item.Declaration { attr; anchor; doc; content; source_anchor }
2,972✔
958
  end
959

960
  open Type_declaration
961

962
  module Value : sig
963
    val value : Lang.Value.t -> Item.t
964
  end = struct
965
    let value (t : Odoc_model.Lang.Value.t) =
966
      let extra_attr, semicolon =
893✔
967
        match t.value with
968
        | Abstract -> ([], Syntax.Value.semicolon)
869✔
969
        | External _ -> ([ "external" ], Syntax.Type.External.semicolon)
24✔
970
      in
971
      let name = Paths.Identifier.name t.id in
972
      let content =
893✔
973
        O.documentedSrc
974
          (O.box_hv
893✔
975
          @@ O.keyword Syntax.Value.variable_keyword
893✔
976
             ++ O.txt " " ++ O.txt name
893✔
977
             ++ O.txt Syntax.Type.annotation_separator
893✔
978
             ++ O.cut ++ type_expr t.type_
893✔
979
             ++ if semicolon then O.txt ";" else O.noop)
×
980
      in
981
      let attr = [ "value" ] @ extra_attr in
893✔
982
      let anchor = path_to_id t.id in
983
      let doc = Comment.to_ir t.doc.elements in
893✔
984
      let source_anchor = source_anchor t.source_loc in
893✔
985
      Item.Declaration { attr; anchor; doc; content; source_anchor }
893✔
986
  end
987

988
  open Value
989

990
  (* This chunk of code is responsible for sectioning list of items
991
     according to headings by extracting headings as Items.
992

993
     TODO: This sectioning would be better done as a pass on the model directly.
994
  *)
995
  module Sectioning : sig
996
    open Odoc_model
997

998
    val comment_items : Comment.elements -> Item.t list
999

1000
    val docs : Comment.elements -> Item.t list * Item.t list
1001
  end = struct
1002
    let take_until_heading_or_end (docs : Odoc_model.Comment.elements) =
1003
      let content, _, rest =
688✔
1004
        Doctree.Take.until docs ~classify:(fun b ->
1005
            match b.Location.value with
1,476✔
1006
            | `Heading _ -> Stop_and_keep
202✔
1007
            | #Odoc_model.Comment.attached_block_element as doc ->
1,274✔
1008
                let content = Comment.attached_block_element doc in
1009
                Accum content)
1,274✔
1010
      in
1011
      (content, rest)
688✔
1012

1013
    let comment_items (input0 : Odoc_model.Comment.elements) =
1014
      let rec loop input_comment acc =
958✔
1015
        match input_comment with
2,483✔
1016
        | [] -> List.rev acc
958✔
1017
        | element :: input_comment -> (
1,525✔
1018
            match element.Location.value with
1019
            | `Heading h ->
837✔
1020
                let item = Comment.heading h in
1021
                loop input_comment (item :: acc)
837✔
1022
            | _ ->
688✔
1023
                let content, input_comment =
1024
                  take_until_heading_or_end (element :: input_comment)
1025
                in
1026
                let item = Item.Text content in
688✔
1027
                loop input_comment (item :: acc))
1028
      in
1029
      loop input0 []
1030

1031
    (* For doc pages, we want the header to contain everything until
1032
       the first heading, then everything before the next heading which
1033
       is either lower, or a section.
1034
    *)
1035
    let docs input_comment =
1036
      let items = comment_items input_comment in
69✔
1037
      let until_first_heading, o, items =
69✔
1038
        Doctree.Take.until items ~classify:(function
1039
          | Item.Heading h as i -> Stop_and_accum ([ i ], Some h.level)
69✔
1040
          | i -> Accum [ i ])
×
1041
      in
1042
      match o with
69✔
1043
      | None -> (until_first_heading, items)
×
1044
      | Some level ->
69✔
1045
          let max_level = if level = 1 then 2 else level in
×
1046
          let before_second_heading, _, items =
1047
            Doctree.Take.until items ~classify:(function
1048
              | Item.Heading h when h.level >= max_level -> Stop_and_keep
13✔
1049
              | i -> Accum [ i ])
38✔
1050
          in
1051
          let header = until_first_heading @ before_second_heading in
69✔
1052
          (header, items)
1053
  end
1054

1055
  module Class : sig
1056
    val class_ : Lang.Class.t -> Item.t
1057

1058
    val class_type : Lang.ClassType.t -> Item.t
1059
  end = struct
1060
    let class_type_expr (cte : Odoc_model.Lang.ClassType.expr) =
1061
      match cte with
249✔
1062
      | Constr (path, args) ->
58✔
1063
          let link = Link.from_path (path :> Paths.Path.t) in
1064
          format_type_path ~delim:`brackets args link
58✔
1065
      | Signature _ ->
191✔
1066
          Syntax.Class.open_tag ++ O.txt " ... " ++ Syntax.Class.close_tag
191✔
1067

1068
    let method_ (t : Odoc_model.Lang.Method.t) =
1069
      let name = Paths.Identifier.name t.id in
90✔
1070
      let virtual_ =
90✔
1071
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1072
      in
1073
      let private_ =
1074
        if t.private_ then O.keyword "private" ++ O.txt " " else O.noop
8✔
1075
      in
1076
      let content =
1077
        O.documentedSrc
1078
          (O.keyword "method" ++ O.txt " " ++ private_ ++ virtual_ ++ O.txt name
90✔
1079
          ++ O.txt Syntax.Type.annotation_separator
90✔
1080
          ++ type_expr t.type_)
90✔
1081
      in
1082
      let attr = [ "method" ] in
90✔
1083
      let anchor = path_to_id t.id in
1084
      let doc = Comment.to_ir t.doc.elements in
90✔
1085
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
90✔
1086

1087
    let instance_variable (t : Odoc_model.Lang.InstanceVariable.t) =
1088
      let name = Paths.Identifier.name t.id in
17✔
1089
      let virtual_ =
17✔
1090
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1091
      in
1092
      let mutable_ =
1093
        if t.mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop
8✔
1094
      in
1095
      let content =
1096
        O.documentedSrc
1097
          (O.keyword "val" ++ O.txt " " ++ mutable_ ++ virtual_ ++ O.txt name
17✔
1098
          ++ O.txt Syntax.Type.annotation_separator
17✔
1099
          ++ type_expr t.type_)
17✔
1100
      in
1101
      let attr = [ "value"; "instance-variable" ] in
17✔
1102
      let anchor = path_to_id t.id in
1103
      let doc = Comment.to_ir t.doc.elements in
17✔
1104
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
17✔
1105

1106
    let inherit_ (ih : Lang.ClassSignature.Inherit.t) =
1107
      let cte =
16✔
1108
        match ih.expr with
1109
        | Signature _ -> assert false (* Bold. *)
1110
        | cty -> cty
16✔
1111
      in
1112
      let content =
1113
        O.documentedSrc (O.keyword "inherit" ++ O.txt " " ++ class_type_expr cte)
16✔
1114
      in
1115
      let attr = [ "inherit" ] in
16✔
1116
      let anchor = None in
1117
      let doc = Comment.to_ir ih.doc.elements in
1118
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
16✔
1119

1120
    let constraint_ (cst : Lang.ClassSignature.Constraint.t) =
1121
      let content =
8✔
1122
        O.documentedSrc (format_constraints [ (cst.left, cst.right) ])
8✔
1123
      in
1124
      let attr = [] in
8✔
1125
      let anchor = None in
1126
      let doc = Comment.to_ir cst.doc.elements in
1127
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
8✔
1128

1129
    let class_signature (c : Lang.ClassSignature.t) =
1130
      let rec loop l acc_items =
233✔
1131
        match l with
388✔
1132
        | [] -> List.rev acc_items
233✔
1133
        | item :: rest -> (
155✔
1134
            let continue item = loop rest (item :: acc_items) in
131✔
1135
            match (item : Lang.ClassSignature.item) with
1136
            | Inherit cty -> continue @@ inherit_ cty
16✔
1137
            | Method m -> continue @@ method_ m
90✔
1138
            | InstanceVariable v -> continue @@ instance_variable v
17✔
1139
            | Constraint cst -> continue @@ constraint_ cst
8✔
1140
            | Comment `Stop ->
8✔
1141
                let rest =
1142
                  List.skip_until rest ~p:(function
1143
                    | Lang.ClassSignature.Comment `Stop -> true
8✔
1144
                    | _ -> false)
8✔
1145
                in
1146
                loop rest acc_items
8✔
1147
            | Comment (`Docs c) ->
16✔
1148
                let items = Sectioning.comment_items c.elements in
1149
                loop rest (List.rev_append items acc_items))
16✔
1150
      in
1151
      (* FIXME: use [t.self] *)
1152
      (c.doc.elements, loop c.items [])
233✔
1153

1154
    let rec class_decl (cd : Odoc_model.Lang.Class.decl) =
1155
      match cd with
182✔
1156
      | ClassType expr -> class_type_expr expr
166✔
1157
      (* TODO: factorize the following with [type_expr] *)
1158
      | Arrow (None, src, dst) ->
16✔
1159
          O.span
16✔
1160
            (type_expr ~needs_parentheses:true src
16✔
1161
            ++ O.txt " " ++ Syntax.Type.arrow)
16✔
1162
          ++ O.txt " " ++ class_decl dst
16✔
1163
      | Arrow (Some (RawOptional _ as lbl), _src, dst) ->
×
1164
          O.span
×
1165
            (O.box_hv
×
1166
            @@ label lbl ++ O.txt ":"
×
1167
               ++ tag "error" (O.txt "???")
×
1168
               ++ O.txt " " ++ Syntax.Type.arrow)
×
1169
          ++ O.sp ++ class_decl dst
×
1170
      | Arrow (Some lbl, src, dst) ->
×
1171
          O.span
×
1172
            (label lbl ++ O.txt ":"
×
1173
            ++ type_expr ~needs_parentheses:true src
×
1174
            ++ O.txt " " ++ Syntax.Type.arrow)
×
1175
          ++ O.txt " " ++ class_decl dst
×
1176

1177
    let class_ (t : Odoc_model.Lang.Class.t) =
1178
      let name = Paths.Identifier.name t.id in
166✔
1179
      let params =
166✔
1180
        match t.params with
1181
        | [] -> O.noop
142✔
1182
        | _ :: _ as params -> format_params ~delim:`brackets params ++ O.txt " "
24✔
1183
      in
1184
      let virtual_ =
1185
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
24✔
1186
      in
1187

1188
      let source_anchor = source_anchor t.source_loc in
1189
      let cname, expansion, expansion_doc =
166✔
1190
        match t.expansion with
1191
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1192
        | Some csig ->
166✔
1193
            let expansion_doc, items = class_signature csig in
1194
            let url = Url.Path.from_identifier t.id in
166✔
1195
            let page =
166✔
1196
              make_expansion_page ~source_anchor url
1197
                [ t.doc.elements; expansion_doc ]
1198
                items
1199
            in
1200
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
166✔
1201
              Some page,
1202
              Some expansion_doc )
1203
      in
1204
      let summary =
1205
        O.txt Syntax.Type.annotation_separator ++ class_decl t.type_
166✔
1206
      in
1207
      let cd =
166✔
1208
        attach_expansion
1209
          (Syntax.Type.annotation_separator, "object", "end")
1210
          expansion summary
1211
      in
1212
      let content =
166✔
1213
        O.documentedSrc (O.keyword "class" ++ O.txt " " ++ virtual_ ++ params)
166✔
1214
        @ cname @ cd
1215
      in
1216
      let attr = [ "class" ] in
1217
      let anchor = path_to_id t.id in
1218
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
166✔
1219
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1220

1221
    let class_type (t : Odoc_model.Lang.ClassType.t) =
1222
      let name = Paths.Identifier.name t.id in
67✔
1223
      let params = format_params ~delim:`brackets t.params in
67✔
1224
      let virtual_ =
67✔
1225
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1226
      in
1227
      let source_anchor = source_anchor t.source_loc in
1228
      let cname, expansion, expansion_doc =
67✔
1229
        match t.expansion with
1230
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1231
        | Some csig ->
67✔
1232
            let url = Url.Path.from_identifier t.id in
1233
            let expansion_doc, items = class_signature csig in
67✔
1234
            let page =
67✔
1235
              make_expansion_page ~source_anchor url
1236
                [ t.doc.elements; expansion_doc ]
1237
                items
1238
            in
1239
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
67✔
1240
              Some page,
1241
              Some expansion_doc )
1242
      in
1243
      let summary = O.txt " = " ++ class_type_expr t.expr in
67✔
1244
      let expr = attach_expansion (" = ", "object", "end") expansion summary in
67✔
1245
      let content =
67✔
1246
        O.documentedSrc
67✔
1247
          (O.keyword "class" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
67✔
1248
         ++ virtual_ ++ params ++ O.txt " ")
67✔
1249
        @ cname @ expr
1250
      in
1251
      let attr = [ "class-type" ] in
1252
      let anchor = path_to_id t.id in
1253
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
67✔
1254
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1255
  end
1256

1257
  open Class
1258

1259
  module Module : sig
1260
    val signature : Lang.Signature.t -> Comment.Comment.elements * Item.t list
1261
    (** Returns [header_doc, content]. *)
1262
  end = struct
1263
    let internal_module m =
1264
      let open Lang.Module in
1,924✔
1265
      match m.id.iv with
1266
      | `Module (_, name) when ModuleName.is_hidden name -> true
81✔
1267
      | _ -> false
1,843✔
1268

1269
    let internal_type t =
1270
      let open Lang.TypeDecl in
2,957✔
1271
      match t.id.iv with
1272
      | `Type (_, name) when TypeName.is_hidden name -> true
1✔
1273
      | _ -> false
2,956✔
1274

1275
    let internal_value v =
1276
      let open Lang.Value in
999✔
1277
      match v.id.iv with
1278
      | `Value (_, name) when ValueName.is_hidden name -> true
106✔
1279
      | _ -> false
893✔
1280

1281
    let internal_module_type t =
1282
      let open Lang.ModuleType in
1,342✔
1283
      match t.id.iv with
1284
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1285
      | _ -> false
1,342✔
1286

1287
    let internal_module_substitution t =
1288
      let open Lang.ModuleSubstitution in
8✔
1289
      match t.id.iv with
1290
      | `Module (_, name) when ModuleName.is_hidden name -> true
×
1291
      | _ -> false
8✔
1292

1293
    let internal_module_type_substitution t =
1294
      let open Lang.ModuleTypeSubstitution in
8✔
1295
      match t.id.iv with
1296
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1297
      | _ -> false
8✔
1298

1299
    let rec signature (s : Lang.Signature.t) =
1300
      let rec loop l acc_items =
3,536✔
1301
        match l with
12,411✔
1302
        | [] -> List.rev acc_items
3,536✔
1303
        | item :: rest -> (
8,875✔
1304
            let continue (item : Item.t) = loop rest (item :: acc_items) in
7,757✔
1305
            match (item : Lang.Signature.item) with
1306
            | Module (_, m) when internal_module m -> loop rest acc_items
81✔
1307
            | Type (_, t) when internal_type t -> loop rest acc_items
1✔
1308
            | Value v when internal_value v -> loop rest acc_items
106✔
1309
            | ModuleType m when internal_module_type m -> loop rest acc_items
×
1310
            | ModuleSubstitution m when internal_module_substitution m ->
8✔
1311
                loop rest acc_items
×
1312
            | ModuleTypeSubstitution m when internal_module_type_substitution m
8✔
1313
              ->
1314
                loop rest acc_items
×
1315
            | ModuleTypeSubstitution m -> continue @@ module_type_substitution m
8✔
1316
            | Module (_, m) -> continue @@ module_ m
1,843✔
1317
            | ModuleType m -> continue @@ module_type m
1,342✔
1318
            | Class (_, c) -> continue @@ class_ c
166✔
1319
            | ClassType (_, c) -> continue @@ class_type c
67✔
1320
            | Include m -> continue @@ include_ m
266✔
1321
            | ModuleSubstitution m -> continue @@ module_substitution m
8✔
1322
            | TypeSubstitution t ->
16✔
1323
                continue @@ type_decl ~is_substitution:true (Ordinary, t)
16✔
1324
            | Type (r, t) -> continue @@ type_decl (r, t)
2,956✔
1325
            | TypExt e -> continue @@ extension e
126✔
1326
            | Exception e -> continue @@ exn e
66✔
1327
            | Value v -> continue @@ value v
893✔
1328
            | Open o ->
82✔
1329
                let items = Sectioning.comment_items o.doc.elements in
1330
                loop rest (List.rev_append items acc_items)
82✔
1331
            | Comment `Stop ->
57✔
1332
                let rest =
1333
                  List.skip_until rest ~p:(function
1334
                    | Lang.Signature.Comment `Stop -> true
49✔
1335
                    | _ -> false)
65✔
1336
                in
1337
                loop rest acc_items
57✔
1338
            | Comment (`Docs c) ->
791✔
1339
                let items = Sectioning.comment_items c.elements in
1340
                loop rest (List.rev_append items acc_items))
791✔
1341
      in
1342
      ((Lang.extract_signature_doc s).elements, loop s.items [])
3,536✔
1343

1344
    and functor_parameter :
1345
        Odoc_model.Lang.FunctorParameter.parameter -> DocumentedSrc.t =
1346
     fun arg ->
1347
      let open Odoc_model.Lang.FunctorParameter in
211✔
1348
      let name = Paths.Identifier.name arg.id in
1349
      let render_ty = arg.expr in
211✔
1350
      let modtyp =
1351
        mty_in_decl (arg.id :> Paths.Identifier.Signature.t) render_ty
1352
      in
1353
      let modname, mod_decl =
211✔
1354
        match expansion_of_module_type_expr arg.expr with
1355
        | None ->
×
1356
            let modname = O.txt (Paths.Identifier.name arg.id) in
×
1357
            (modname, O.documentedSrc modtyp)
×
1358
        | Some (expansion_doc, items) ->
211✔
1359
            let url = Url.Path.from_identifier arg.id in
1360
            let modname = path url [ inline @@ Text name ] in
211✔
1361
            let type_with_expansion =
211✔
1362
              let content =
1363
                make_expansion_page ~source_anchor:None url [ expansion_doc ]
1364
                  items
1365
              in
1366
              let summary = O.render modtyp in
211✔
1367
              let status = `Default in
211✔
1368
              let expansion =
1369
                O.documentedSrc
211✔
1370
                  (O.txt Syntax.Type.annotation_separator ++ O.keyword "sig")
211✔
1371
                @ DocumentedSrc.[ Subpage { content; status } ]
1372
                @ O.documentedSrc (O.keyword "end")
211✔
1373
              in
1374
              DocumentedSrc.
1375
                [
1376
                  Alternative
1377
                    (Expansion { status = `Default; summary; url; expansion });
1378
                ]
1379
            in
1380
            (modname, type_with_expansion)
1381
      in
1382
      O.documentedSrc (O.keyword "module" ++ O.txt " ")
211✔
1383
      @ O.documentedSrc modname @ mod_decl
211✔
1384

1385
    and module_substitution (t : Odoc_model.Lang.ModuleSubstitution.t) =
1386
      let name = Paths.Identifier.name t.id in
8✔
1387
      let path = Link.from_path (t.manifest :> Paths.Path.t) in
8✔
1388
      let content =
8✔
1389
        O.documentedSrc
1390
          (O.keyword "module" ++ O.txt " " ++ O.txt name ++ O.txt " :=" ++ O.sp
8✔
1391
         ++ path)
8✔
1392
      in
1393
      let attr = [ "module-substitution" ] in
8✔
1394
      let anchor = path_to_id t.id in
1395
      let doc = Comment.to_ir t.doc.elements in
8✔
1396
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
8✔
1397

1398
    and module_type_substitution (t : Odoc_model.Lang.ModuleTypeSubstitution.t)
1399
        =
1400
      let prefix =
8✔
1401
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
8✔
1402
      in
1403
      let source_anchor = None in
8✔
1404
      let modname = Paths.Identifier.name t.id in
1405
      let modname, expansion_doc, mty =
8✔
1406
        module_type_manifest ~subst:true ~source_anchor modname t.id
1407
          t.doc.elements (Some t.manifest) prefix
1408
      in
1409
      let content =
8✔
1410
        O.documentedSrc (prefix ++ modname)
8✔
1411
        @ mty
1412
        @ O.documentedSrc
8✔
1413
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1414
      in
1415
      let attr = [ "module-type" ] in
1416
      let anchor = path_to_id t.id in
1417
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
8✔
1418
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1419

1420
    and simple_expansion :
1421
        Odoc_model.Lang.ModuleType.simple_expansion ->
1422
        Comment.Comment.elements * Item.t list =
1423
     fun t ->
1424
      let rec extract_functor_params
2,914✔
1425
          (f : Odoc_model.Lang.ModuleType.simple_expansion) =
1426
        match f with
3,133✔
1427
        | Signature sg -> (None, sg)
2,914✔
1428
        | Functor (p, expansion) ->
219✔
1429
            let add_to params =
1430
              match p with Unit -> params | Named p -> p :: params
8✔
1431
            in
1432
            let params, sg = extract_functor_params expansion in
1433
            let params = match params with None -> [] | Some p -> p in
36✔
1434
            (Some (add_to params), sg)
219✔
1435
      in
1436
      match extract_functor_params t with
1437
      | None, sg -> signature sg
2,731✔
1438
      | Some params, sg ->
183✔
1439
          let sg_doc, content = signature sg in
1440
          let params =
183✔
1441
            let decl_of_arg arg =
1442
              let content = functor_parameter arg in
211✔
1443
              let attr = [ "parameter" ] in
211✔
1444
              let anchor =
1445
                Some (Url.Anchor.from_identifier (arg.id :> Paths.Identifier.t))
211✔
1446
              in
1447
              let doc = [] in
1448
              [
1449
                Item.Declaration
1450
                  { content; anchor; attr; doc; source_anchor = None };
1451
              ]
1452
            in
1453
            List.concat_map decl_of_arg params
183✔
1454
          in
1455
          let prelude = mk_heading ~label:"parameters" "Parameters" :: params
183✔
1456
          and content = mk_heading ~label:"signature" "Signature" :: content in
183✔
1457
          (sg_doc, prelude @ content)
1458

1459
    and expansion_of_module_type_expr :
1460
        Odoc_model.Lang.ModuleType.expr ->
1461
        (Comment.Comment.elements * Item.t list) option =
1462
     fun t ->
1463
      let rec simple_expansion_of (t : Odoc_model.Lang.ModuleType.expr) =
3,199✔
1464
        match t with
3,418✔
1465
        | Path { p_expansion = None; _ }
379✔
1466
        | TypeOf { t_expansion = None; _ }
8✔
NEW
1467
        | With { w_expansion = None; _ }
×
NEW
1468
        | Strengthen { s_expansion = None; _ } ->
×
1469
            None
1470
        | Path { p_expansion = Some e; _ }
426✔
1471
        | TypeOf { t_expansion = Some e; _ }
56✔
1472
        | With { w_expansion = Some e; _ }
218✔
NEW
1473
        | Strengthen { s_expansion = Some e; _ } ->
×
1474
            Some e
1475
        | Signature sg -> Some (Signature sg)
2,112✔
1476
        | Functor (f_parameter, e) -> (
219✔
1477
            match simple_expansion_of e with
1478
            | Some e -> Some (Functor (f_parameter, e))
211✔
1479
            | None -> None)
8✔
1480
      in
1481
      match simple_expansion_of t with
1482
      | None -> None
387✔
1483
      | Some e -> Some (simple_expansion e)
2,812✔
1484

1485
    and module_ : Odoc_model.Lang.Module.t -> Item.t =
1486
     fun t ->
1487
      let modname = Paths.Identifier.name t.id in
1,843✔
1488
      let expansion =
1,843✔
1489
        match t.type_ with
1490
        | Alias (_, Some e) -> Some (simple_expansion e)
102✔
1491
        | Alias (_, None) -> None
186✔
1492
        | ModuleType e -> expansion_of_module_type_expr e
1,555✔
1493
      in
1494
      let source_anchor = source_anchor t.source_loc in
1495
      let modname, status, expansion, expansion_doc =
1,843✔
1496
        match expansion with
1497
        | None -> (O.txt modname, `Default, None, None)
322✔
1498
        | Some (expansion_doc, items) ->
1,521✔
1499
            let status =
1500
              match t.type_ with
1501
              | ModuleType (Signature _) -> `Inline
987✔
1502
              | _ -> `Default
534✔
1503
            in
1504
            let url = Url.Path.from_identifier t.id in
1505
            let link = path url [ inline @@ Text modname ] in
1,521✔
1506
            let page =
1,521✔
1507
              make_expansion_page ~source_anchor url
1508
                [ t.doc.elements; expansion_doc ]
1509
                items
1510
            in
1511
            (link, status, Some page, Some expansion_doc)
1,521✔
1512
      in
1513
      let intro = O.keyword "module" ++ O.txt " " ++ modname in
1,843✔
1514
      let summary = O.ignore intro ++ mdexpr_in_decl t.id t.type_ in
1,843✔
1515
      let modexpr =
1,843✔
1516
        attach_expansion ~status
1517
          (Syntax.Type.annotation_separator, "sig", "end")
1518
          expansion summary
1519
      in
1520
      let content =
1,843✔
1521
        O.documentedSrc intro @ modexpr
1,843✔
1522
        @ O.documentedSrc
1,843✔
1523
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1524
      in
1525
      let attr = [ "module" ] in
1526
      let anchor = path_to_id t.id in
1527
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
1,843✔
1528
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1529

1530
    and simple_expansion_in_decl (base : Paths.Identifier.Module.t) se =
1531
      let rec ty_of_se :
102✔
1532
          Lang.ModuleType.simple_expansion -> Lang.ModuleType.expr = function
1533
        | Signature sg -> Signature sg
102✔
1534
        | Functor (arg, sg) -> Functor (arg, ty_of_se sg)
×
1535
      in
1536
      mty_in_decl (base :> Paths.Identifier.Signature.t) (ty_of_se se)
102✔
1537

1538
    and mdexpr_in_decl (base : Paths.Identifier.Module.t) md =
1539
      let sig_dotdotdot =
1,843✔
1540
        O.txt Syntax.Type.annotation_separator
1,843✔
1541
        ++ O.cut ++ Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
1,843✔
1542
      in
1543
      match md with
1,843✔
1544
      | Alias (_, Some se) -> simple_expansion_in_decl base se
102✔
1545
      | Alias (p, _) when not Paths.Path.(is_hidden (p :> t)) ->
186✔
1546
          O.txt " =" ++ O.sp ++ mdexpr md
186✔
1547
      | Alias _ -> sig_dotdotdot
×
1548
      | ModuleType mt -> mty_in_decl (base :> Paths.Identifier.Signature.t) mt
1,555✔
1549

1550
    and mdexpr : Odoc_model.Lang.Module.decl -> text = function
1551
      | Alias (mod_path, _) -> Link.from_path (mod_path :> Paths.Path.t)
242✔
1552
      | ModuleType mt -> mty mt
×
1553

1554
    and module_type_manifest ~subst ~source_anchor modname id doc manifest
1555
        prefix =
1556
      let expansion =
1,350✔
1557
        match manifest with
1558
        | None -> None
128✔
1559
        | Some e -> expansion_of_module_type_expr e
1,222✔
1560
      in
1561
      let modname, expansion, expansion_doc =
1562
        match expansion with
1563
        | None -> (O.txt modname, None, None)
379✔
1564
        | Some (expansion_doc, items) ->
971✔
1565
            let url = Url.Path.from_identifier id in
1566
            let link = path url [ inline @@ Text modname ] in
971✔
1567
            let page =
971✔
1568
              make_expansion_page ~source_anchor url [ doc; expansion_doc ]
1569
                items
1570
            in
1571
            (link, Some page, Some expansion_doc)
971✔
1572
      in
1573
      let summary =
1574
        match manifest with
1575
        | None -> O.noop
128✔
1576
        | Some expr ->
1,222✔
1577
            O.ignore (prefix ++ modname)
1,222✔
1578
            ++ (if subst then O.txt " :=" ++ O.sp else O.txt " =" ++ O.sp)
8✔
1579
            ++ mty expr
1,222✔
1580
      in
1581
      ( modname,
1582
        expansion_doc,
1583
        attach_expansion (" = ", "sig", "end") expansion summary )
1,350✔
1584

1585
    and module_type (t : Odoc_model.Lang.ModuleType.t) =
1586
      let prefix =
1,342✔
1587
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
1,342✔
1588
      in
1589
      let modname = Paths.Identifier.name t.id in
1,342✔
1590
      let source_anchor = source_anchor t.source_loc in
1,342✔
1591
      let modname, expansion_doc, mty =
1,342✔
1592
        module_type_manifest ~subst:false ~source_anchor modname t.id
1593
          t.doc.elements t.expr prefix
1594
      in
1595
      let content =
1,342✔
1596
        O.documentedSrc (prefix ++ modname)
1,342✔
1597
        @ mty
1598
        @ O.documentedSrc
1,342✔
1599
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1600
      in
1601
      let attr = [ "module-type" ] in
1602
      let anchor = path_to_id t.id in
1603
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
1,342✔
1604
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1605

1606
    and umty_hidden : Odoc_model.Lang.ModuleType.U.expr -> bool = function
1607
      | Path p -> Paths.Path.(is_hidden (p :> t))
383✔
1608
      | With (_, expr) -> umty_hidden expr
18✔
1609
      | TypeOf (ModPath m, _) | TypeOf (StructInclude m, _) ->
42✔
1610
          Paths.Path.(is_hidden (m :> t))
1611
      | Signature _ -> false
10✔
NEW
1612
      | Strengthen (expr, p, _) ->
×
NEW
1613
          umty_hidden expr || Paths.Path.(is_hidden (p :> t))
×
1614

1615
    and mty_hidden : Odoc_model.Lang.ModuleType.expr -> bool = function
1616
      | Path { p_path = mty_path; _ } -> Paths.Path.(is_hidden (mty_path :> t))
837✔
1617
      | With { w_expr; _ } -> umty_hidden w_expr
218✔
1618
      | TypeOf { t_desc = ModPath m; _ }
48✔
1619
      | TypeOf { t_desc = StructInclude m; _ } ->
16✔
1620
          Paths.Path.(is_hidden (m :> t))
1621
      | _ -> false
2,278✔
1622

1623
    and mty_with subs expr =
1624
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
234✔
1625
      ++ O.list
234✔
1626
           ~sep:(O.cut ++ O.txt " " ++ O.keyword "and" ++ O.txt " ")
234✔
1627
           ~f:(fun x -> O.span (substitution x))
274✔
1628
           subs
1629

1630
    and mty_strengthen expr path =
NEW
1631
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
×
NEW
1632
      ++ Link.from_path (path :> Paths.Path.t)
×
1633

1634
    and mty_typeof t_desc =
1635
      match t_desc with
154✔
1636
      | Odoc_model.Lang.ModuleType.ModPath m ->
90✔
1637
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
90✔
1638
          ++ O.keyword "of" ++ O.txt " "
90✔
1639
          ++ Link.from_path (m :> Paths.Path.t)
90✔
1640
      | StructInclude m ->
64✔
1641
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
64✔
1642
          ++ O.keyword "of" ++ O.txt " " ++ O.keyword "struct" ++ O.txt " "
64✔
1643
          ++ O.keyword "include" ++ O.txt " "
64✔
1644
          ++ Link.from_path (m :> Paths.Path.t)
64✔
1645
          ++ O.txt " " ++ O.keyword "end"
64✔
1646

1647
    and is_elidable_with_u : Odoc_model.Lang.ModuleType.U.expr -> bool =
1648
      function
1649
      | Path _ -> false
209✔
1650
      | Signature _ -> true
2✔
1651
      | With (_, expr) -> is_elidable_with_u expr
×
1652
      | TypeOf _ -> false
25✔
NEW
1653
      | Strengthen (expr, _, _) -> is_elidable_with_u expr
×
1654

1655
    and umty : Odoc_model.Lang.ModuleType.U.expr -> text =
1656
     fun m ->
1657
      match m with
499✔
1658
      | Path p -> Link.from_path (p :> Paths.Path.t)
383✔
1659
      | Signature _ ->
8✔
1660
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
8✔
1661
      | With (_, expr) when is_elidable_with_u expr ->
18✔
1662
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
2✔
1663
      | With (subs, expr) -> mty_with subs expr
16✔
1664
      | TypeOf (t_desc, _) -> mty_typeof t_desc
90✔
NEW
1665
      | Strengthen (expr, _, _) when is_elidable_with_u expr ->
×
NEW
1666
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
NEW
1667
      | Strengthen (expr, p, _) -> mty_strengthen expr (p :> Paths.Path.t)
×
1668

1669
    and mty : Odoc_model.Lang.ModuleType.expr -> text =
1670
     fun m ->
1671
      if mty_hidden m then
3,397✔
1672
        Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1673
      else
1674
        match m with
3,397✔
1675
        | Path { p_path = mty_path; _ } ->
837✔
1676
            Link.from_path (mty_path :> Paths.Path.t)
1677
        | Functor (Unit, expr) ->
×
1678
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1679
            ++ O.span (O.txt " () " ++ Syntax.Type.arrow)
×
1680
            ++ O.sp ++ mty expr
×
1681
        | Functor (Named arg, expr) ->
48✔
1682
            let arg_expr = arg.expr in
1683
            let stop_before = expansion_of_module_type_expr arg_expr = None in
48✔
1684
            let name =
1685
              let open Odoc_model.Lang.FunctorParameter in
1686
              let name = Paths.Identifier.name arg.id in
1687
              let href =
48✔
1688
                Url.from_identifier ~stop_before (arg.id :> Paths.Identifier.t)
1689
              in
1690
              resolved href [ inline @@ Text name ]
48✔
1691
            in
1692
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1693
            ++ (O.box_hv @@ O.span
48✔
1694
               @@ O.txt " (" ++ name
48✔
1695
                  ++ O.txt Syntax.Type.annotation_separator
48✔
1696
                  ++ mty arg_expr ++ O.txt ")" ++ O.txt " " ++ Syntax.Type.arrow
48✔
1697
               )
1698
            ++ O.sp ++ mty expr
48✔
1699
        | With { w_expr; _ } when is_elidable_with_u w_expr ->
218✔
1700
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1701
        | With { w_substitutions; w_expr; _ } ->
218✔
1702
            O.box_hv @@ mty_with w_substitutions w_expr
218✔
1703
        | TypeOf { t_desc; _ } -> mty_typeof t_desc
64✔
1704
        | Signature _ ->
2,230✔
1705
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
2,230✔
NEW
1706
        | Strengthen { s_expr; _ } when is_elidable_with_u s_expr ->
×
NEW
1707
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
NEW
1708
        | Strengthen { s_expr; s_path; _ } ->
×
NEW
1709
            O.box_hv @@ mty_strengthen s_expr (s_path :> Paths.Path.t)
×
1710

1711
    and mty_in_decl :
1712
        Paths.Identifier.Signature.t -> Odoc_model.Lang.ModuleType.expr -> text
1713
        =
1714
     fun base -> function
NEW
1715
      | (Path _ | Signature _ | With _ | TypeOf _ | Strengthen _) as m ->
×
1716
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
1,868✔
1717
      | Functor _ as m when not Syntax.Mod.functor_contraction ->
171✔
1718
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
×
1719
      | Functor (arg, expr) ->
171✔
1720
          let text_arg =
1721
            match arg with
1722
            | Unit -> O.txt "()"
8✔
1723
            | Named arg ->
163✔
1724
                let arg_expr = arg.expr in
1725
                let stop_before =
1726
                  expansion_of_module_type_expr arg_expr = None
163✔
1727
                in
1728
                let name =
1729
                  let open Odoc_model.Lang.FunctorParameter in
1730
                  let name = Paths.Identifier.name arg.id in
1731
                  let href =
163✔
1732
                    Url.from_identifier ~stop_before
1733
                      (arg.id :> Paths.Identifier.t)
1734
                  in
1735
                  resolved href [ inline @@ Text name ]
163✔
1736
                in
1737
                O.box_hv
163✔
1738
                @@ O.txt "(" ++ name
163✔
1739
                   ++ O.txt Syntax.Type.annotation_separator
163✔
1740
                   ++ O.cut ++ mty arg.expr ++ O.txt ")"
163✔
1741
          in
1742
          O.sp ++ text_arg ++ mty_in_decl base expr
171✔
1743

1744
    (* TODO : Centralize the list juggling for type parameters *)
1745
    and type_expr_in_subst td typath =
1746
      let typath = Link.from_fragment typath in
146✔
1747
      match td.Lang.TypeDecl.Equation.params with
146✔
1748
      | [] -> typath
122✔
1749
      | l -> Syntax.Type.handle_substitution_params typath (format_params l)
24✔
1750

1751
    and substitution : Odoc_model.Lang.ModuleType.substitution -> text =
1752
      function
1753
      | ModuleEq (frag_mod, md) ->
56✔
1754
          O.box_hv
1755
          @@ O.keyword "module" ++ O.txt " "
56✔
1756
             ++ Link.from_fragment (frag_mod :> Paths.Fragment.leaf)
56✔
1757
             ++ O.txt " =" ++ O.sp ++ mdexpr md
56✔
1758
      | ModuleTypeEq (frag_mty, md) ->
32✔
1759
          O.box_hv
1760
          @@ O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
32✔
1761
             ++ Link.from_fragment (frag_mty :> Paths.Fragment.leaf)
32✔
1762
             ++ O.txt " =" ++ O.sp ++ mty md
32✔
1763
      | TypeEq (frag_typ, td) ->
104✔
1764
          O.box_hv
1765
          @@ O.keyword "type" ++ O.txt " "
104✔
1766
             ++ type_expr_in_subst td (frag_typ :> Paths.Fragment.leaf)
104✔
1767
             ++ fst (format_manifest td)
104✔
1768
             ++ format_constraints
104✔
1769
                  td.Odoc_model.Lang.TypeDecl.Equation.constraints
1770
      | ModuleSubst (frag_mod, mod_path) ->
24✔
1771
          O.box_hv
1772
          @@ O.keyword "module" ++ O.txt " "
24✔
1773
             ++ Link.from_fragment (frag_mod :> Paths.Fragment.leaf)
24✔
1774
             ++ O.txt " :=" ++ O.sp
24✔
1775
             ++ Link.from_path (mod_path :> Paths.Path.t)
24✔
1776
      | ModuleTypeSubst (frag_mty, md) ->
16✔
1777
          O.box_hv
1778
          @@ O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
16✔
1779
             ++ Link.from_fragment (frag_mty :> Paths.Fragment.leaf)
16✔
1780
             ++ O.txt " :=" ++ O.sp ++ mty md
16✔
1781
      | TypeSubst (frag_typ, td) -> (
42✔
1782
          O.box_hv
1783
          @@ O.keyword "type" ++ O.txt " "
42✔
1784
             ++ type_expr_in_subst td (frag_typ :> Paths.Fragment.leaf)
42✔
1785
             ++ O.txt " :=" ++ O.sp
42✔
1786
             ++
42✔
1787
             match td.Lang.TypeDecl.Equation.manifest with
1788
             | None -> assert false (* cf loader/cmti *)
1789
             | Some te -> type_expr te)
42✔
1790

1791
    and include_ (t : Odoc_model.Lang.Include.t) =
1792
      let decl_hidden =
266✔
1793
        match t.decl with
1794
        | Alias p -> Paths.Path.(is_hidden (p :> t))
×
1795
        | ModuleType mty -> umty_hidden mty
266✔
1796
      in
1797
      let status = if decl_hidden then `Inline else t.status in
1✔
1798

1799
      let _, content = signature t.expansion.content in
1800
      let summary =
266✔
1801
        if decl_hidden then O.render (O.keyword "include" ++ O.txt " ...")
1✔
1802
        else
1803
          let include_decl =
265✔
1804
            match t.decl with
1805
            | Odoc_model.Lang.Include.Alias mod_path ->
×
1806
                Link.from_path (mod_path :> Paths.Path.t)
×
1807
            | ModuleType mt -> umty mt
265✔
1808
          in
1809
          O.render
265✔
1810
            (O.keyword "include" ++ O.txt " " ++ include_decl
265✔
1811
            ++ if Syntax.Mod.include_semicolon then O.keyword ";" else O.noop)
×
1812
      in
1813
      let content = { Include.content; status; summary } in
1814
      let attr = [ "include" ] in
1815
      let anchor = None in
1816
      let doc =
1817
        (* Documentation attached to includes behave differently than other
1818
           declarations, which show only the synopsis. We can't only show the
1819
           synopsis because no page is generated to render it and we'd loose
1820
           the full documentation.
1821
           The documentation from the expansion is not used. *)
1822
        Comment.to_ir t.doc.elements
1823
      in
1824
      Item.Include { attr; anchor; doc; content; source_anchor = None }
266✔
1825
  end
1826

1827
  open Module
1828

1829
  module Page : sig
1830
    val compilation_unit : Lang.Compilation_unit.t -> Document.t
1831

1832
    val page : Lang.Page.t -> Document.t
1833

1834
    val implementation :
1835
      Lang.Implementation.t ->
1836
      Syntax_highlighter.infos ->
1837
      string ->
1838
      Document.t list
1839
  end = struct
1840
    let pack : Lang.Compilation_unit.Packed.t -> Item.t list =
1841
     fun t ->
1842
      let f x =
×
1843
        let id = x.Lang.Compilation_unit.Packed.id in
×
1844
        let modname = Paths.Identifier.name id in
1845
        let md_def =
×
1846
          O.keyword "module" ++ O.txt " " ++ O.txt modname ++ O.txt " = "
×
1847
          ++ Link.from_path (x.path :> Paths.Path.t)
×
1848
        in
1849
        let content = O.documentedSrc md_def in
×
1850
        let anchor =
×
1851
          Some (Url.Anchor.from_identifier (id :> Paths.Identifier.t))
×
1852
        in
1853
        let attr = [ "modules" ] in
1854
        let doc = [] in
1855
        let decl = { Item.anchor; content; attr; doc; source_anchor = None } in
1856
        Item.Declaration decl
1857
      in
1858
      List.map f t
1859

1860
    let compilation_unit (t : Odoc_model.Lang.Compilation_unit.t) =
1861
      let url = Url.Path.from_identifier t.id in
356✔
1862
      let unit_doc, items =
356✔
1863
        match t.content with
1864
        | Module sign -> signature sign
356✔
1865
        | Pack packed -> ([], pack packed)
×
1866
      in
1867
      let source_anchor = source_anchor t.source_loc in
1868
      let page = make_expansion_page ~source_anchor url [ unit_doc ] items in
356✔
1869
      Document.Page page
356✔
1870

1871
    let page (t : Odoc_model.Lang.Page.t) =
1872
      (*let name =
1873
          match t.name.iv with `Page (_, name) | `LeafPage (_, name) -> name
1874
        in*)
1875
      (*let title = Odoc_model.Names.PageName.to_string name in*)
1876
      let url = Url.Path.from_identifier t.name in
69✔
1877
      let preamble, items = Sectioning.docs t.content.elements in
69✔
1878
      let source_anchor = None in
69✔
1879
      Document.Page { Page.preamble; items; url; source_anchor }
1880

1881
    let implementation (v : Odoc_model.Lang.Implementation.t) syntax_info
1882
        source_code =
1883
      match v.id with
28✔
1884
      | None -> []
×
1885
      | Some id ->
28✔
1886
          [
1887
            Document.Source_page
1888
              (Source_page.source id syntax_info v.source_info source_code);
28✔
1889
          ]
1890
  end
1891

1892
  include Page
1893

1894
  let type_expr = type_expr
1895

1896
  let record = record
1897

1898
  let unboxed_record = unboxed_record
1899
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