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

ocaml / odoc / 2743

16 Jan 2025 10:25PM UTC coverage: 73.403% (+0.004%) from 73.399%
2743

push

github

jonludlam
Update CHANGES

10258 of 13975 relevant lines covered (73.4%)

9961.61 hits per line

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

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

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

24
let tag tag t = O.span ~attr:tag t
249,565✔
25

26
let label t =
27
  match t with
41,949✔
28
  | Odoc_model.Lang.TypeExpr.Label s -> tag "label" (O.txt s)
37,432✔
29
  | Optional s | RawOptional s -> tag "optlabel" (O.txt "?" ++ O.txt s)
1✔
30

31
let type_var tv = tag "type-var" (O.txt tv)
207,055✔
32

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

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

39
let path p content = resolved (Url.from_path p) content
2,285✔
40

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

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

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

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

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

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

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

91
include Generator_signatures
92

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

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

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

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

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

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

185
    let resolved_fragment_to_ir : Fragment.Resolved.leaf -> text =
186
     fun fragment ->
187
      let open Fragment in
1,011✔
188
      let id = Resolved.identifier (fragment :> Resolved.t) in
189
      let txt = render_resolved_fragment (fragment :> Resolved.t) in
1,011✔
190
      let href = Url.from_identifier ~stop_before:false id in
1,011✔
191
      resolved href [ inline @@ Text txt ]
1,011✔
192

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

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

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

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

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

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

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

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

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

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

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

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

489
  open Type_expression
490

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

500
    val extension : Lang.Extension.t -> Item.t
501

502
    val record : Lang.TypeDecl.Field.t list -> DocumentedSrc.one list
503

504
    val exn : Lang.Exception.t -> Item.t
505

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

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

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

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

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

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

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

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

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

756
    let format_params :
757
          'row.
758
          ?delim:[ `parens | `brackets ] ->
759
          Odoc_model.Lang.TypeDecl.param list ->
760
          text =
761
     fun ?(delim = `parens) params ->
351✔
762
      let format_param { Odoc_model.Lang.TypeDecl.desc; variance; injectivity }
420✔
763
          =
764
        let desc =
459✔
765
          match desc with
766
          | Odoc_model.Lang.TypeDecl.Any -> [ "_" ]
31✔
767
          | Var s -> [ "'"; s ]
428✔
768
        in
769
        let var_desc =
770
          match variance with
771
          | None -> desc
447✔
772
          | Some Odoc_model.Lang.TypeDecl.Pos -> "+" :: desc
6✔
773
          | Some Odoc_model.Lang.TypeDecl.Neg -> "-" :: desc
6✔
774
        in
775
        let final = if injectivity then "!" :: var_desc else var_desc in
×
776
        String.concat "" final
777
      in
778
      O.txt
779
        (match params with
780
        | [] -> ""
45✔
781
        | [ x ] -> format_param x |> Syntax.Type.handle_format_params
291✔
782
        | lst -> (
84✔
783
            let params = String.concat ", " (List.map format_param lst) in
84✔
784
            (match delim with `parens -> "(" | `brackets -> "[")
×
785
            ^ params
786
            ^ match delim with `parens -> ")" | `brackets -> "]"))
×
787

788
    let format_constraints constraints =
789
      O.list constraints ~f:(fun (t1, t2) ->
2,406✔
790
          O.sp
78✔
791
          ++ (O.box_hv
78✔
792
             @@ O.keyword "constraint" ++ O.sp
78✔
793
                ++ O.box_hv_no_indent (type_expr t1)
78✔
794
                ++ O.txt " =" ++ O.sp
78✔
795
                ++ O.box_hv_no_indent (type_expr t2)))
78✔
796

797
    let format_manifest :
798
          'inner_row 'outer_row.
799
          ?is_substitution:bool ->
800
          ?compact_variants:bool ->
801
          Odoc_model.Lang.TypeDecl.Equation.t ->
802
          text * bool =
803
     fun ?(is_substitution = false) ?(compact_variants = true) equation ->
78✔
804
      let _ = compact_variants in
2,332✔
805
      (* TODO *)
806
      let private_ = equation.private_ in
807
      match equation.manifest with
808
      | None -> (O.noop, private_)
1,360✔
809
      | Some t ->
972✔
810
          let manifest =
811
            O.txt (if is_substitution then " :=" else " =")
18✔
812
            ++ O.sp
972✔
813
            ++ (if private_ then
972✔
814
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
6✔
815
                else O.noop)
966✔
816
            ++ type_expr t
972✔
817
          in
818
          (manifest, false)
972✔
819

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

898
  open Type_declaration
899

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

926
  open Value
927

928
  (* This chunk of code is responsible for sectioning list of items
929
     according to headings by extracting headings as Items.
930

931
     TODO: This sectioning would be better done as a pass on the model directly.
932
  *)
933
  module Sectioning : sig
934
    open Odoc_model
935

936
    val comment_items : Comment.elements -> Item.t list
937

938
    val docs : Comment.elements -> Item.t list * Item.t list
939
  end = struct
940
    let take_until_heading_or_end (docs : Odoc_model.Comment.elements) =
941
      let content, _, rest =
525✔
942
        Doctree.Take.until docs ~classify:(fun b ->
943
            match b.Location.value with
1,102✔
944
            | `Heading _ -> Stop_and_keep
146✔
945
            | #Odoc_model.Comment.attached_block_element as doc ->
956✔
946
                let content = Comment.attached_block_element doc in
947
                Accum content)
956✔
948
      in
949
      (content, rest)
525✔
950

951
    let comment_items (input0 : Odoc_model.Comment.elements) =
952
      let rec loop input_comment acc =
741✔
953
        match input_comment with
1,906✔
954
        | [] -> List.rev acc
741✔
955
        | element :: input_comment -> (
1,165✔
956
            match element.Location.value with
957
            | `Heading h ->
640✔
958
                let item = Comment.heading h in
959
                loop input_comment (item :: acc)
640✔
960
            | _ ->
525✔
961
                let content, input_comment =
962
                  take_until_heading_or_end (element :: input_comment)
963
                in
964
                let item = Item.Text content in
525✔
965
                loop input_comment (item :: acc))
966
      in
967
      loop input0 []
968

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

993
  module Class : sig
994
    val class_ : Lang.Class.t -> Item.t
995

996
    val class_type : Lang.ClassType.t -> Item.t
997
  end = struct
998
    let class_type_expr (cte : Odoc_model.Lang.ClassType.expr) =
999
      match cte with
189✔
1000
      | Constr (path, args) ->
44✔
1001
          let link = Link.from_path (path :> Paths.Path.t) in
1002
          format_type_path ~delim:`brackets args link
44✔
1003
      | Signature _ ->
145✔
1004
          Syntax.Class.open_tag ++ O.txt " ... " ++ Syntax.Class.close_tag
145✔
1005

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

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

1044
    let inherit_ (ih : Lang.ClassSignature.Inherit.t) =
1045
      let cte =
12✔
1046
        match ih.expr with
1047
        | Signature _ -> assert false (* Bold. *)
1048
        | cty -> cty
12✔
1049
      in
1050
      let content =
1051
        O.documentedSrc (O.keyword "inherit" ++ O.txt " " ++ class_type_expr cte)
12✔
1052
      in
1053
      let attr = [ "inherit" ] in
12✔
1054
      let anchor = None in
1055
      let doc = Comment.to_ir ih.doc.elements in
1056
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
12✔
1057

1058
    let constraint_ (cst : Lang.ClassSignature.Constraint.t) =
1059
      let content =
6✔
1060
        O.documentedSrc (format_constraints [ (cst.left, cst.right) ])
6✔
1061
      in
1062
      let attr = [] in
6✔
1063
      let anchor = None in
1064
      let doc = Comment.to_ir cst.doc.elements in
1065
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
6✔
1066

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

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

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

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

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

1195
  open Class
1196

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

1207
    let internal_type t =
1208
      let open Lang.TypeDecl in
2,305✔
1209
      match t.id.iv with
1210
      | `Type (_, name) when TypeName.is_hidden name -> true
1✔
1211
      | _ -> false
2,304✔
1212

1213
    let internal_value v =
1214
      let open Lang.Value in
774✔
1215
      match v.id.iv with
1216
      | `Value (_, name) when ValueName.is_hidden name -> true
82✔
1217
      | _ -> false
692✔
1218

1219
    let internal_module_type t =
1220
      let open Lang.ModuleType in
1,030✔
1221
      match t.id.iv with
1222
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1223
      | _ -> false
1,030✔
1224

1225
    let internal_module_substitution t =
1226
      let open Lang.ModuleSubstitution in
12✔
1227
      match t.id.iv with
1228
      | `Module (_, name) when ModuleName.is_hidden name -> true
×
1229
      | _ -> false
12✔
1230

1231
    let internal_module_type_substitution t =
1232
      let open Lang.ModuleTypeSubstitution in
6✔
1233
      match t.id.iv with
1234
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1235
      | _ -> false
6✔
1236

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

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

1323
    and module_substitution (t : Odoc_model.Lang.ModuleSubstitution.t) =
1324
      let name = Paths.Identifier.name t.id in
12✔
1325
      let path = Link.from_path (t.manifest :> Paths.Path.t) in
12✔
1326
      let content =
12✔
1327
        O.documentedSrc
1328
          (O.keyword "module" ++ O.txt " " ++ O.txt name ++ O.txt " :=" ++ O.sp
12✔
1329
         ++ path)
12✔
1330
      in
1331
      let attr = [ "module-substitution" ] in
12✔
1332
      let anchor = path_to_id t.id in
1333
      let doc = Comment.to_ir t.doc.elements in
12✔
1334
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
12✔
1335

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

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

1396
    and expansion_of_module_type_expr :
1397
        Odoc_model.Lang.ModuleType.expr ->
1398
        (Comment.Comment.elements * Item.t list) option =
1399
     fun t ->
1400
      let rec simple_expansion_of (t : Odoc_model.Lang.ModuleType.expr) =
2,484✔
1401
        match t with
2,657✔
1402
        | Path { p_expansion = None; _ }
285✔
1403
        | TypeOf { t_expansion = None; _ }
6✔
1404
        | With { w_expansion = None; _ } ->
×
1405
            None
1406
        | Path { p_expansion = Some e; _ }
338✔
1407
        | TypeOf { t_expansion = Some e; _ }
42✔
1408
        | With { w_expansion = Some e; _ } ->
165✔
1409
            Some e
1410
        | Signature sg -> Some (Signature sg)
1,648✔
1411
        | Functor (f_parameter, e) -> (
173✔
1412
            match simple_expansion_of e with
1413
            | Some e -> Some (Functor (f_parameter, e))
167✔
1414
            | None -> None)
6✔
1415
      in
1416
      match simple_expansion_of t with
1417
      | None -> None
291✔
1418
      | Some e -> Some (simple_expansion e)
2,193✔
1419

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

1465
    and simple_expansion_in_decl (base : Paths.Identifier.Module.t) se =
1466
      let rec ty_of_se :
82✔
1467
          Lang.ModuleType.simple_expansion -> Lang.ModuleType.expr = function
1468
        | Signature sg -> Signature sg
82✔
1469
        | Functor (arg, sg) -> Functor (arg, ty_of_se sg)
×
1470
      in
1471
      mty_in_decl (base :> Paths.Identifier.Signature.t) (ty_of_se se)
82✔
1472

1473
    and mdexpr_in_decl (base : Paths.Identifier.Module.t) md =
1474
      let sig_dotdotdot =
1,435✔
1475
        O.txt Syntax.Type.annotation_separator
1,435✔
1476
        ++ O.cut ++ Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
1,435✔
1477
      in
1478
      match md with
1,435✔
1479
      | Alias (_, Some se) -> simple_expansion_in_decl base se
82✔
1480
      | Alias (p, _) when not Paths.Path.(is_hidden (p :> t)) ->
143✔
1481
          O.txt " =" ++ O.sp ++ mdexpr md
143✔
1482
      | Alias _ -> sig_dotdotdot
×
1483
      | ModuleType mt -> mty_in_decl (base :> Paths.Identifier.Signature.t) mt
1,210✔
1484

1485
    and mdexpr : Odoc_model.Lang.Module.decl -> text = function
1486
      | Alias (mod_path, _) -> Link.from_path (mod_path :> Paths.Path.t)
185✔
1487
      | ModuleType mt -> mty mt
×
1488

1489
    and module_type_manifest ~subst ~source_anchor modname id doc manifest
1490
        prefix =
1491
      let expansion =
1,036✔
1492
        match manifest with
1493
        | None -> None
96✔
1494
        | Some e -> expansion_of_module_type_expr e
940✔
1495
      in
1496
      let modname, expansion, expansion_doc =
1497
        match expansion with
1498
        | None -> (O.txt modname, None, None)
285✔
1499
        | Some (expansion_doc, items) ->
751✔
1500
            let url = Url.Path.from_identifier id in
1501
            let link = path url [ inline @@ Text modname ] in
751✔
1502
            let page =
751✔
1503
              make_expansion_page ~source_anchor url [ doc; expansion_doc ]
1504
                items
1505
            in
1506
            (link, Some page, Some expansion_doc)
751✔
1507
      in
1508
      let summary =
1509
        match manifest with
1510
        | None -> O.noop
96✔
1511
        | Some expr ->
940✔
1512
            O.ignore (prefix ++ modname)
940✔
1513
            ++ (if subst then O.txt " :=" ++ O.sp else O.txt " =" ++ O.sp)
6✔
1514
            ++ mty expr
940✔
1515
      in
1516
      ( modname,
1517
        expansion_doc,
1518
        attach_expansion (" = ", "sig", "end") expansion summary )
1,036✔
1519

1520
    and module_type (t : Odoc_model.Lang.ModuleType.t) =
1521
      let prefix =
1,030✔
1522
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
1,030✔
1523
      in
1524
      let modname = Paths.Identifier.name t.id in
1,030✔
1525
      let source_anchor = source_anchor t.source_loc in
1,030✔
1526
      let modname, expansion_doc, mty =
1,030✔
1527
        module_type_manifest ~subst:false ~source_anchor modname t.id
1528
          t.doc.elements t.expr prefix
1529
      in
1530
      let content =
1,030✔
1531
        O.documentedSrc (prefix ++ modname)
1,030✔
1532
        @ mty
1533
        @ O.documentedSrc
1,030✔
1534
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1535
      in
1536
      let attr = [ "module-type" ] in
1537
      let anchor = path_to_id t.id in
1538
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
1,030✔
1539
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1540

1541
    and umty_hidden : Odoc_model.Lang.ModuleType.U.expr -> bool = function
1542
      | Path p -> Paths.Path.(is_hidden (p :> t))
289✔
1543
      | With (_, expr) -> umty_hidden expr
14✔
1544
      | TypeOf (ModPath m, _) | TypeOf (StructInclude m, _) ->
32✔
1545
          Paths.Path.(is_hidden (m :> t))
1546
      | Signature _ -> false
8✔
1547

1548
    and mty_hidden : Odoc_model.Lang.ModuleType.expr -> bool = function
1549
      | Path { p_path = mty_path; _ } -> Paths.Path.(is_hidden (mty_path :> t))
647✔
1550
      | With { w_expr; _ } -> umty_hidden w_expr
165✔
1551
      | TypeOf { t_desc = ModPath m; _ }
36✔
1552
      | TypeOf { t_desc = StructInclude m; _ } ->
12✔
1553
          Paths.Path.(is_hidden (m :> t))
1554
      | _ -> false
1,784✔
1555

1556
    and mty_with subs expr =
1557
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
177✔
1558
      ++ O.list
177✔
1559
           ~sep:(O.cut ++ O.txt " " ++ O.keyword "and" ++ O.txt " ")
177✔
1560
           ~f:(fun x -> O.span (substitution x))
207✔
1561
           subs
1562

1563
    and mty_typeof t_desc =
1564
      match t_desc with
118✔
1565
      | Odoc_model.Lang.ModuleType.ModPath m ->
68✔
1566
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
68✔
1567
          ++ O.keyword "of" ++ O.txt " "
68✔
1568
          ++ Link.from_path (m :> Paths.Path.t)
68✔
1569
      | StructInclude m ->
50✔
1570
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
50✔
1571
          ++ O.keyword "of" ++ O.txt " " ++ O.keyword "struct" ++ O.txt " "
50✔
1572
          ++ O.keyword "include" ++ O.txt " "
50✔
1573
          ++ Link.from_path (m :> Paths.Path.t)
50✔
1574
          ++ O.txt " " ++ O.keyword "end"
50✔
1575

1576
    and is_elidable_with_u : Odoc_model.Lang.ModuleType.U.expr -> bool =
1577
      function
1578
      | Path _ -> false
158✔
1579
      | Signature _ -> true
2✔
1580
      | With (_, expr) -> is_elidable_with_u expr
×
1581
      | TypeOf _ -> false
19✔
1582

1583
    and umty : Odoc_model.Lang.ModuleType.U.expr -> text =
1584
     fun m ->
1585
      match m with
379✔
1586
      | Path p -> Link.from_path (p :> Paths.Path.t)
289✔
1587
      | Signature _ ->
6✔
1588
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
6✔
1589
      | With (_, expr) when is_elidable_with_u expr ->
14✔
1590
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
2✔
1591
      | With (subs, expr) -> mty_with subs expr
12✔
1592
      | TypeOf (t_desc, _) -> mty_typeof t_desc
70✔
1593

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

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

1665
    (* TODO : Centralize the list juggling for type parameters *)
1666
    and type_expr_in_subst td typath =
1667
      let typath = Link.from_fragment typath in
110✔
1668
      match td.Lang.TypeDecl.Equation.params with
110✔
1669
      | [] -> typath
92✔
1670
      | l -> Syntax.Type.handle_substitution_params typath (format_params l)
18✔
1671

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

1712
    and include_ (t : Odoc_model.Lang.Include.t) =
1713
      let decl_hidden =
203✔
1714
        match t.decl with
1715
        | Alias p -> Paths.Path.(is_hidden (p :> t))
×
1716
        | ModuleType mty -> umty_hidden mty
203✔
1717
      in
1718
      let status = if decl_hidden then `Inline else t.status in
1✔
1719

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

1748
  open Module
1749

1750
  module Page : sig
1751
    val compilation_unit : Lang.Compilation_unit.t -> Document.t
1752

1753
    val page : Lang.Page.t -> Document.t
1754

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

1781
    let compilation_unit (t : Odoc_model.Lang.Compilation_unit.t) =
1782
      let url = Url.Path.from_identifier t.id in
289✔
1783
      let unit_doc, items =
289✔
1784
        match t.content with
1785
        | Module sign -> signature sign
289✔
1786
        | Pack packed -> ([], pack packed)
×
1787
      in
1788
      let source_anchor = source_anchor t.source_loc in
1789
      let page = make_expansion_page ~source_anchor url [ unit_doc ] items in
289✔
1790
      Document.Page page
289✔
1791

1792
    let page (t : Odoc_model.Lang.Page.t) =
1793
      (*let name =
1794
          match t.name.iv with `Page (_, name) | `LeafPage (_, name) -> name
1795
        in*)
1796
      (*let title = Odoc_model.Names.PageName.to_string name in*)
1797
      let url = Url.Path.from_identifier t.name in
65✔
1798
      let preamble, items = Sectioning.docs t.content.elements in
65✔
1799
      let source_anchor = None in
65✔
1800
      Document.Page { Page.preamble; items; url; source_anchor }
1801

1802
    let implementation (v : Odoc_model.Lang.Implementation.t) syntax_info
1803
        source_code =
1804
      match v.id with
27✔
1805
      | None -> []
×
1806
      | Some id ->
27✔
1807
          [
1808
            Document.Source_page
1809
              (Source_page.source id syntax_info v.source_info source_code);
27✔
1810
          ]
1811
  end
1812

1813
  include Page
1814

1815
  let type_expr = type_expr
1816

1817
  let record = record
1818
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