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

ocaml / odoc / 2974

30 Jun 2025 10:36AM UTC coverage: 73.124% (-0.2%) from 73.331%
2974

push

github

jonludlam
Update CHANGES

10369 of 14180 relevant lines covered (73.12%)

9800.41 hits per line

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

86.34
/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
249,493✔
26

27
let label t =
28
  match t with
41,949✔
29
  | Odoc_model.Lang.TypeExpr.Label s -> tag "label" (O.txt s)
37,432✔
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)
207,043✔
33

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

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

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

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

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

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

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

72
let mk_heading ?(level = 1) ?label text =
278✔
73
  let title = [ inline @@ Text text ] in
278✔
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 =
2,520✔
80
    List.split_at
81
      ~f:(function
82
        | { Odoc_model.Location_.value = `Heading _; _ } -> true | _ -> false)
24✔
83
      comment
84
  in
85
  (Comment.standalone preamble, Comment.standalone first_comment @ items)
2,520✔
86

87
let make_expansion_page ~source_anchor url comments items =
88
  let comment = List.concat comments in
2,520✔
89
  let preamble, items = prepare_preamble comment items in
2,520✔
90
  { Page.preamble; items; url; source_anchor }
2,520✔
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
240,167✔
105
      | `Identifier (id, _) ->
1,100✔
106
          unresolved [ inline @@ Text (Identifier.name id) ]
1,100✔
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) ]
5,775✔
112
      | `Forward root -> unresolved [ inline @@ Text root ] (* FIXME *)
×
113
      | `Dot (prefix, suffix) ->
5,739✔
114
          let link = from_path (prefix :> Path.t) in
115
          link ++ O.txt ("." ^ ModuleName.to_string suffix)
5,739✔
116
      | `DotT (prefix, suffix) ->
5,769✔
117
          let link = from_path (prefix :> Path.t) in
118
          link ++ O.txt ("." ^ TypeName.to_string suffix)
5,769✔
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 ->
221,784✔
130
          let txt = Url.render_path path in
438✔
131
          unresolved [ inline @@ Text txt ]
438✔
132
      | `Resolved rp -> (
221,346✔
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
6✔
140
            | _ -> false
221,190✔
141
          in
142
          let txt = [ inline @@ Text (Url.render_path path) ] in
221,346✔
143
          match Paths.Path.Resolved.identifier rp with
144
          | Some id ->
148,525✔
145
              let href = Url.from_identifier ~stop_before id in
146
              resolved href txt
148,525✔
147
          | None -> O.elt txt)
72,821✔
148

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

151
    let rec render_fragment_any : Fragment.t -> string =
152
     fun fragment ->
153
      match fragment with
18✔
154
      | `Resolved rr -> render_resolved_fragment rr
18✔
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
1,090✔
164
        | `Root _ -> assert false
165
        | `Subst (_, rr) -> render_resolved_fragment (rr :> t)
6✔
166
        | `Alias (_, rr) -> render_resolved_fragment (rr :> t)
18✔
167
        | `Module (`Root _, s) -> ModuleName.to_string s
92✔
168
        | `Module_type (`Root _, s) -> ModuleTypeName.to_string s
24✔
169
        | `Type (`Root _, s) -> TypeName.to_string s
913✔
170
        | `Class (`Root _, s) -> TypeName.to_string s
×
171
        | `ClassType (`Root _, s) -> TypeName.to_string s
×
172
        | `Module (rr, s) ->
6✔
173
            dot (render_resolved_fragment (rr :> t)) (ModuleName.to_string s)
6✔
174
        | `Module_type (rr, s) ->
12✔
175
            dot
176
              (render_resolved_fragment (rr :> t))
12✔
177
              (ModuleTypeName.to_string s)
12✔
178
        | `Type (rr, s) ->
19✔
179
            dot (render_resolved_fragment (rr :> t)) (TypeName.to_string s)
19✔
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
1,011✔
189
      let id = Resolved.identifier (fragment :> Resolved.t) in
190
      let txt = render_resolved_fragment (fragment :> Resolved.t) in
1,011✔
191
      let href = Url.from_identifier ~stop_before:false id in
1,011✔
192
      resolved href [ inline @@ Text txt ]
1,011✔
193

194
    let from_fragment : Fragment.leaf -> text = function
195
      | `Resolved r
1,029✔
196
        when not (Fragment.Resolved.is_hidden (r :> Fragment.Resolved.t)) ->
1,029✔
197
          resolved_fragment_to_ir r
1,011✔
198
      | f ->
18✔
199
          let txt = render_fragment_any (f :> Fragment.t) in
200
          unresolved [ inline @@ Text txt ]
18✔
201
  end
202

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

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

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

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

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

301
  module Type_expression : sig
302
    val type_expr : ?needs_parentheses:bool -> Lang.TypeExpr.t -> text
303

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

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

387
    and format_type_path ~delim (params : Odoc_model.Lang.TypeExpr.t list)
388
        (path : text) : text =
389
      O.box_hv
226,426✔
390
      @@
391
      match params with
392
      | [] -> path
121,625✔
393
      | [ param ] ->
58,905✔
394
          let param = type_expr ~needs_parentheses:true param in
395
          let args =
58,905✔
396
            if Syntax.Type.parenthesize_constructor then
397
              O.txt "(" ++ param ++ O.txt ")"
×
398
            else param
58,905✔
399
          in
400
          Syntax.Type.handle_constructor_params path args
58,905✔
401
      | params ->
45,896✔
402
          let params = O.list params ~sep:(O.txt "," ++ O.sp) ~f:type_expr in
45,896✔
403
          let params =
45,896✔
404
            match delim with
405
            | `parens -> enclose ~l:"(" params ~r:")"
45,896✔
406
            | `brackets -> enclose ~l:"[" params ~r:"]"
×
407
          in
408
          Syntax.Type.handle_constructor_params path (O.box_hv params)
45,896✔
409

410
    and type_expr ?(needs_parentheses = false) (t : Odoc_model.Lang.TypeExpr.t)
371,419✔
411
        =
412
      match t with
622,187✔
413
      | Var s -> type_var (Syntax.Type.var_prefix ^ s)
197,881✔
414
      | Any -> type_var Syntax.Type.any
9,162✔
415
      | Alias (te, alias) ->
60✔
416
          type_expr ~needs_parentheses:true te
60✔
417
          ++ O.txt " " ++ O.keyword "as" ++ O.txt " '" ++ O.txt alias
60✔
418
      | Arrow (None, src, dst) ->
136,680✔
419
          let res =
420
            O.span
136,680✔
421
              ((O.box_hv @@ type_expr ~needs_parentheses:true src)
136,680✔
422
              ++ O.txt " " ++ Syntax.Type.arrow)
136,680✔
423
            ++ O.sp ++ type_expr dst
136,680✔
424
            (* ++ O.end_hv *)
425
          in
426
          if not needs_parentheses then res else enclose ~l:"(" res ~r:")"
23,986✔
427
      | Arrow (Some (RawOptional _ as lbl), _src, dst) ->
1✔
428
          let res =
429
            O.span
1✔
430
              (O.box_hv
1✔
431
              @@ label lbl ++ O.txt ":"
1✔
432
                 ++ tag "error" (O.txt "???")
1✔
433
                 ++ O.txt " " ++ Syntax.Type.arrow)
1✔
434
            ++ O.sp ++ type_expr dst
1✔
435
          in
436
          if not needs_parentheses then res else enclose ~l:"(" res ~r:")"
×
437
      | Arrow (Some lbl, src, dst) ->
41,948✔
438
          let res =
439
            O.span
41,948✔
440
              ((O.box_hv
41,948✔
441
               @@ label lbl ++ O.txt ":" ++ O.cut
41,948✔
442
                  ++ (O.box_hv @@ type_expr ~needs_parentheses:true src))
41,948✔
443
              ++ O.txt " " ++ Syntax.Type.arrow)
41,948✔
444
            ++ O.sp ++ type_expr dst
41,948✔
445
          in
446
          if not needs_parentheses then res else enclose ~l:"(" res ~r:")"
2,577✔
447
      | Tuple lst ->
6,208✔
448
          let res =
449
            O.box_hv_no_indent
450
              (O.list lst ~sep:Syntax.Type.Tuple.element_separator
6,208✔
451
                 ~f:(fun (lbl, ty) ->
452
                   match lbl with
12,842✔
453
                   | None -> type_expr ~needs_parentheses:true ty
12,842✔
454
                   | Some lbl ->
×
455
                       tag "label" (O.txt lbl)
×
456
                       ++ O.txt ":" ++ O.cut
×
457
                       ++ type_expr ~needs_parentheses:true ty))
×
458
          in
459
          if Syntax.Type.Tuple.always_parenthesize || needs_parentheses then
×
460
            enclose ~l:"(" res ~r:")"
2,976✔
461
          else res
3,232✔
462
      | Constr (path, args) ->
226,376✔
463
          let link = Link.from_path (path :> Paths.Path.t) in
464
          format_type_path ~delim:`parens args link
226,376✔
465
      | Polymorphic_variant v -> te_variant v
2,365✔
466
      | Object o -> te_object o
60✔
467
      | Class (path, args) ->
6✔
468
          format_type_path ~delim:`brackets args
469
            (Link.from_path (path :> Paths.Path.t))
6✔
470
      | Poly (polyvars, t) ->
549✔
471
          O.txt ("'" ^ String.concat ~sep:" '" polyvars ^ ". ") ++ type_expr t
549✔
472
      | Package pkg ->
891✔
473
          enclose ~l:"(" ~r:")"
474
            (O.keyword "module" ++ O.txt " "
891✔
475
            ++ Link.from_path (pkg.path :> Paths.Path.t)
891✔
476
            ++
891✔
477
            match pkg.substitutions with
478
            | [] -> O.noop
120✔
479
            | fst :: lst ->
771✔
480
                O.sp
481
                ++ O.box_hv (O.keyword "with" ++ O.txt " " ++ package_subst fst)
771✔
482
                ++ O.list lst ~f:(fun s ->
771✔
483
                       O.cut
51✔
484
                       ++ (O.box_hv
51✔
485
                          @@ O.txt " " ++ O.keyword "and" ++ O.txt " "
51✔
486
                             ++ package_subst s)))
51✔
487

488
    and package_subst
489
        ((frag_typ, te) : Paths.Fragment.Type.t * Odoc_model.Lang.TypeExpr.t) :
490
        text =
491
      let typath = Link.from_fragment (frag_typ :> Paths.Fragment.leaf) in
822✔
492
      O.keyword "type" ++ O.txt " " ++ typath ++ O.txt " =" ++ O.sp
822✔
493
      ++ type_expr te
822✔
494
  end
495

496
  open Type_expression
497

498
  (* Also handles constructor declarations for exceptions and extensible
499
     variants, and exposes a few helpers used in formatting classes and signature
500
     constraints. *)
501
  module Type_declaration : sig
502
    val type_decl :
503
      ?is_substitution:bool ->
504
      Lang.Signature.recursive * Lang.TypeDecl.t ->
505
      Item.t
506

507
    val extension : Lang.Extension.t -> Item.t
508

509
    val record : Lang.TypeDecl.Field.t list -> DocumentedSrc.one list
510

511
    val exn : Lang.Exception.t -> Item.t
512

513
    val format_params :
514
      ?delim:[ `parens | `brackets ] -> Lang.TypeDecl.param list -> text
515

516
    val format_manifest :
517
      ?is_substitution:bool ->
518
      ?compact_variants:bool ->
519
      Lang.TypeDecl.Equation.t ->
520
      text * bool
521

522
    val format_constraints : (Lang.TypeExpr.t * Lang.TypeExpr.t) list -> text
523
  end = struct
524
    let record fields =
525
      let field mutable_ id typ =
236✔
526
        let url = Url.from_identifier ~stop_before:true id in
496✔
527
        let name = Paths.Identifier.name id in
496✔
528
        let attrs = [ "def"; "record"; Url.Anchor.string_of_kind url.kind ] in
496✔
529
        let cell =
530
          (* O.td ~a:[ O.a_class ["def"; kind ] ]
531
           *   [O.a ~a:[O.a_href ("#" ^ anchor); O.a_class ["anchor"]] []
532
           *   ; *)
533
          O.code
534
            ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
135✔
535
            ++ O.txt name
496✔
536
            ++ O.txt Syntax.Type.annotation_separator
496✔
537
            ++ type_expr typ
496✔
538
            ++ O.txt Syntax.Type.Record.field_separator)
496✔
539
          (* ] *)
540
        in
541
        (url, attrs, cell)
496✔
542
      in
543
      let rows =
544
        fields
545
        |> List.map (fun fld ->
546
               let open Odoc_model.Lang.TypeDecl.Field in
496✔
547
               let url, attrs, code =
548
                 field fld.mutable_ (fld.id :> Paths.Identifier.t) fld.type_
549
               in
550
               let anchor = Some url in
496✔
551
               let doc = fld.doc.elements in
552
               let rhs = Comment.to_ir doc in
553
               let doc = if not (Comment.has_doc doc) then [] else rhs in
57✔
554
               let markers = Syntax.Comment.markers in
555
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
556
      in
557
      let content =
236✔
558
        O.documentedSrc (O.txt "{") @ rows @ O.documentedSrc (O.txt "}")
236✔
559
      in
560
      content
561

562
    let constructor :
563
        Paths.Identifier.t ->
564
        Odoc_model.Lang.TypeDecl.Constructor.argument ->
565
        Odoc_model.Lang.TypeExpr.t option ->
566
        DocumentedSrc.t =
567
     fun id args ret_type ->
568
      let name = Paths.Identifier.name id in
500✔
569
      let kind = Url.(kind id |> Anchor.string_of_kind) in
500✔
570
      let cstr = tag kind (O.txt name) in
500✔
571
      let is_gadt, ret_type =
500✔
572
        match ret_type with
573
        | None -> (false, O.noop)
398✔
574
        | Some te ->
102✔
575
            let constant = match args with Tuple [] -> true | _ -> false in
36✔
576
            let ret_type =
577
              O.txt " "
102✔
578
              ++ (if constant then O.txt ":" else Syntax.Type.GADT.arrow)
36✔
579
              ++ O.txt " " ++ type_expr te
102✔
580
            in
581
            (true, ret_type)
102✔
582
      in
583
      match args with
584
      | Tuple [] -> O.documentedSrc (cstr ++ ret_type)
223✔
585
      | Tuple lst ->
264✔
586
          let params =
587
            O.list lst ~sep:Syntax.Type.Tuple.element_separator
588
              ~f:(type_expr ~needs_parentheses:is_gadt)
589
          in
590
          O.documentedSrc
264✔
591
            (cstr
592
            ++ (if Syntax.Type.Variant.parenthesize_params then
264✔
593
                  O.txt "(" ++ params ++ O.txt ")"
×
594
                else
595
                  (if is_gadt then O.txt Syntax.Type.annotation_separator
66✔
596
                   else O.txt " " ++ O.keyword "of" ++ O.txt " ")
198✔
597
                  ++ params)
264✔
598
            ++ ret_type)
264✔
599
      | Record fields ->
13✔
600
          if is_gadt then
601
            O.documentedSrc (cstr ++ O.txt Syntax.Type.annotation_separator)
×
602
            @ record fields @ O.documentedSrc ret_type
×
603
          else
604
            O.documentedSrc (cstr ++ O.txt " " ++ O.keyword "of" ++ O.txt " ")
13✔
605
            @ record fields
13✔
606

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

638
    let extension_constructor (t : Odoc_model.Lang.Extension.Constructor.t) =
639
      let id = (t.id :> Paths.Identifier.t) in
118✔
640
      let url = Url.from_identifier ~stop_before:true id in
641
      let anchor = Some url in
118✔
642
      let attrs = [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ] in
118✔
643
      let code = O.documentedSrc (O.txt "| ") @ constructor id t.args t.res in
118✔
644
      let doc = Comment.to_ir t.doc.elements in
645
      let markers = Syntax.Comment.markers in
118✔
646
      DocumentedSrc.Nested { anchor; attrs; code; doc; markers }
647

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

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

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

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

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

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

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

906
  open Type_declaration
907

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

934
  open Value
935

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

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

944
    val comment_items : Comment.elements -> Item.t list
945

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

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

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

1001
  module Class : sig
1002
    val class_ : Lang.Class.t -> Item.t
1003

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

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

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

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

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

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

1100
    let rec class_decl (cd : Odoc_model.Lang.Class.decl) =
1101
      match cd with
138✔
1102
      | ClassType expr -> class_type_expr expr
126✔
1103
      (* TODO: factorize the following with [type_expr] *)
1104
      | Arrow (None, src, dst) ->
12✔
1105
          O.span
12✔
1106
            (type_expr ~needs_parentheses:true src
12✔
1107
            ++ O.txt " " ++ Syntax.Type.arrow)
12✔
1108
          ++ O.txt " " ++ class_decl dst
12✔
1109
      | Arrow (Some (RawOptional _ as lbl), _src, dst) ->
×
1110
          O.span
×
1111
            (O.box_hv
×
1112
            @@ label lbl ++ O.txt ":"
×
1113
               ++ tag "error" (O.txt "???")
×
1114
               ++ O.txt " " ++ Syntax.Type.arrow)
×
1115
          ++ O.sp ++ class_decl dst
×
1116
      | Arrow (Some lbl, src, dst) ->
×
1117
          O.span
×
1118
            (label lbl ++ O.txt ":"
×
1119
            ++ type_expr ~needs_parentheses:true src
×
1120
            ++ O.txt " " ++ Syntax.Type.arrow)
×
1121
          ++ O.txt " " ++ class_decl dst
×
1122

1123
    let class_ (t : Odoc_model.Lang.Class.t) =
1124
      let name = Paths.Identifier.name t.id in
126✔
1125
      let params =
126✔
1126
        match t.params with
1127
        | [] -> O.noop
108✔
1128
        | _ :: _ as params -> format_params ~delim:`brackets params ++ O.txt " "
18✔
1129
      in
1130
      let virtual_ =
1131
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
18✔
1132
      in
1133

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

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

1203
  open Class
1204

1205
  module Module : sig
1206
    val signature : Lang.Signature.t -> Comment.Comment.elements * Item.t list
1207
    (** Returns [header_doc, content]. *)
1208
  end = struct
1209
    let internal_module m =
1210
      let open Lang.Module in
1,474✔
1211
      match m.id.iv with
1212
      | `Module (_, name) when ModuleName.is_hidden name -> true
63✔
1213
      | _ -> false
1,411✔
1214

1215
    let internal_type t =
1216
      let open Lang.TypeDecl in
2,239✔
1217
      match t.id.iv with
1218
      | `Type (_, name) when TypeName.is_hidden name -> true
1✔
1219
      | _ -> false
2,238✔
1220

1221
    let internal_value v =
1222
      let open Lang.Value in
762✔
1223
      match v.id.iv with
1224
      | `Value (_, name) when ValueName.is_hidden name -> true
82✔
1225
      | _ -> false
680✔
1226

1227
    let internal_module_type t =
1228
      let open Lang.ModuleType in
1,012✔
1229
      match t.id.iv with
1230
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1231
      | _ -> false
1,012✔
1232

1233
    let internal_module_substitution t =
1234
      let open Lang.ModuleSubstitution in
6✔
1235
      match t.id.iv with
1236
      | `Module (_, name) when ModuleName.is_hidden name -> true
×
1237
      | _ -> false
6✔
1238

1239
    let internal_module_type_substitution t =
1240
      let open Lang.ModuleTypeSubstitution in
6✔
1241
      match t.id.iv with
1242
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1243
      | _ -> false
6✔
1244

1245
    let rec signature (s : Lang.Signature.t) =
1246
      let rec loop l acc_items =
2,707✔
1247
        match l with
9,451✔
1248
        | [] -> List.rev acc_items
2,707✔
1249
        | item :: rest -> (
6,744✔
1250
            let continue (item : Item.t) = loop rest (item :: acc_items) in
5,891✔
1251
            match (item : Lang.Signature.item) with
1252
            | Module (_, m) when internal_module m -> loop rest acc_items
63✔
1253
            | Type (_, t) when internal_type t -> loop rest acc_items
1✔
1254
            | Value v when internal_value v -> loop rest acc_items
82✔
1255
            | ModuleType m when internal_module_type m -> loop rest acc_items
×
1256
            | ModuleSubstitution m when internal_module_substitution m ->
6✔
1257
                loop rest acc_items
×
1258
            | ModuleTypeSubstitution m when internal_module_type_substitution m
6✔
1259
              ->
1260
                loop rest acc_items
×
1261
            | ModuleTypeSubstitution m -> continue @@ module_type_substitution m
6✔
1262
            | Module (_, m) -> continue @@ module_ m
1,411✔
1263
            | ModuleType m -> continue @@ module_type m
1,012✔
1264
            | Class (_, c) -> continue @@ class_ c
126✔
1265
            | ClassType (_, c) -> continue @@ class_type c
51✔
1266
            | Include m -> continue @@ include_ m
203✔
1267
            | ModuleSubstitution m -> continue @@ module_substitution m
6✔
1268
            | TypeSubstitution t ->
12✔
1269
                continue @@ type_decl ~is_substitution:true (Ordinary, t)
12✔
1270
            | Type (r, t) -> continue @@ type_decl (r, t)
2,238✔
1271
            | TypExt e -> continue @@ extension e
96✔
1272
            | Exception e -> continue @@ exn e
50✔
1273
            | Value v -> continue @@ value v
680✔
1274
            | Open o ->
62✔
1275
                let items = Sectioning.comment_items o.doc.elements in
1276
                loop rest (List.rev_append items acc_items)
62✔
1277
            | Comment `Stop ->
43✔
1278
                let rest =
1279
                  List.skip_until rest ~p:(function
1280
                    | Lang.Signature.Comment `Stop -> true
37✔
1281
                    | _ -> false)
49✔
1282
                in
1283
                loop rest acc_items
43✔
1284
            | Comment (`Docs c) ->
602✔
1285
                let items = Sectioning.comment_items c.elements in
1286
                loop rest (List.rev_append items acc_items))
602✔
1287
      in
1288
      ((Lang.extract_signature_doc s).elements, loop s.items [])
2,707✔
1289

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

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

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

1366
    and simple_expansion :
1367
        Odoc_model.Lang.ModuleType.simple_expansion ->
1368
        Comment.Comment.elements * Item.t list =
1369
     fun t ->
1370
      let rec extract_functor_params
2,221✔
1371
          (f : Odoc_model.Lang.ModuleType.simple_expansion) =
1372
        match f with
2,388✔
1373
        | Signature sg -> (None, sg)
2,221✔
1374
        | Functor (p, expansion) ->
167✔
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)
167✔
1381
      in
1382
      match extract_functor_params t with
1383
      | None, sg -> signature sg
2,082✔
1384
      | Some params, sg ->
139✔
1385
          let sg_doc, content = signature sg in
1386
          let params =
139✔
1387
            let decl_of_arg arg =
1388
              let content = functor_parameter arg in
161✔
1389
              let attr = [ "parameter" ] in
161✔
1390
              let anchor =
1391
                Some (Url.Anchor.from_identifier (arg.id :> Paths.Identifier.t))
161✔
1392
              in
1393
              let doc = [] in
1394
              [
1395
                Item.Declaration
1396
                  { content; anchor; attr; doc; source_anchor = None };
1397
              ]
1398
            in
1399
            List.concat_map decl_of_arg params
139✔
1400
          in
1401
          let prelude = mk_heading ~label:"parameters" "Parameters" :: params
139✔
1402
          and content = mk_heading ~label:"signature" "Signature" :: content in
139✔
1403
          (sg_doc, prelude @ content)
1404

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1674
    (* TODO : Centralize the list juggling for type parameters *)
1675
    and type_expr_in_subst td typath =
1676
      let typath = Link.from_fragment typath in
110✔
1677
      match td.Lang.TypeDecl.Equation.params with
110✔
1678
      | [] -> typath
92✔
1679
      | l -> Syntax.Type.handle_substitution_params typath (format_params l)
18✔
1680

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

1721
    and include_ (t : Odoc_model.Lang.Include.t) =
1722
      let decl_hidden =
203✔
1723
        match t.decl with
1724
        | Alias p -> Paths.Path.(is_hidden (p :> t))
×
1725
        | ModuleType mty -> umty_hidden mty
203✔
1726
      in
1727
      let status = if decl_hidden then `Inline else t.status in
1✔
1728

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

1757
  open Module
1758

1759
  module Page : sig
1760
    val compilation_unit : Lang.Compilation_unit.t -> Document.t
1761

1762
    val page : Lang.Page.t -> Document.t
1763

1764
    val implementation :
1765
      Lang.Implementation.t ->
1766
      Syntax_highlighter.infos ->
1767
      string ->
1768
      Document.t list
1769
  end = struct
1770
    let pack : Lang.Compilation_unit.Packed.t -> Item.t list =
1771
     fun t ->
1772
      let f x =
×
1773
        let id = x.Lang.Compilation_unit.Packed.id in
×
1774
        let modname = Paths.Identifier.name id in
1775
        let md_def =
×
1776
          O.keyword "module" ++ O.txt " " ++ O.txt modname ++ O.txt " = "
×
1777
          ++ Link.from_path (x.path :> Paths.Path.t)
×
1778
        in
1779
        let content = O.documentedSrc md_def in
×
1780
        let anchor =
×
1781
          Some (Url.Anchor.from_identifier (id :> Paths.Identifier.t))
×
1782
        in
1783
        let attr = [ "modules" ] in
1784
        let doc = [] in
1785
        let decl = { Item.anchor; content; attr; doc; source_anchor = None } in
1786
        Item.Declaration decl
1787
      in
1788
      List.map f t
1789

1790
    let compilation_unit (t : Odoc_model.Lang.Compilation_unit.t) =
1791
      let url = Url.Path.from_identifier t.id in
283✔
1792
      let unit_doc, items =
283✔
1793
        match t.content with
1794
        | Module sign -> signature sign
283✔
1795
        | Pack packed -> ([], pack packed)
×
1796
      in
1797
      let source_anchor = source_anchor t.source_loc in
1798
      let page = make_expansion_page ~source_anchor url [ unit_doc ] items in
283✔
1799
      Document.Page page
283✔
1800

1801
    let page (t : Odoc_model.Lang.Page.t) =
1802
      (*let name =
1803
          match t.name.iv with `Page (_, name) | `LeafPage (_, name) -> name
1804
        in*)
1805
      (*let title = Odoc_model.Names.PageName.to_string name in*)
1806
      let url = Url.Path.from_identifier t.name in
66✔
1807
      let preamble, items = Sectioning.docs t.content.elements in
66✔
1808
      let source_anchor = None in
66✔
1809
      Document.Page { Page.preamble; items; url; source_anchor }
1810

1811
    let implementation (v : Odoc_model.Lang.Implementation.t) syntax_info
1812
        source_code =
1813
      match v.id with
27✔
1814
      | None -> []
×
1815
      | Some id ->
27✔
1816
          [
1817
            Document.Source_page
1818
              (Source_page.source id syntax_info v.source_info source_code);
27✔
1819
          ]
1820
  end
1821

1822
  include Page
1823

1824
  let type_expr = type_expr
1825

1826
  let record = record
1827
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