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

ocaml / odoc / 3053

19 Feb 2026 10:47AM UTC coverage: 71.712% (-1.2%) from 72.946%
3053

Pull #1399

github

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

20 of 280 new or added lines in 21 files covered. (7.14%)

162 existing lines in 11 files now uncovered.

10399 of 14501 relevant lines covered (71.71%)

7008.01 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

92
include Generator_signatures
93

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

510
  open Type_expression
511

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

521
    val extension : Lang.Extension.t -> Item.t
522

523
    val record : Lang.TypeDecl.Field.t list -> DocumentedSrc.one list
524

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

963
  open Type_declaration
964

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

991
  open Value
992

993
  (* This chunk of code is responsible for sectioning list of items
994
     according to headings by extracting headings as Items.
995

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

1001
    val comment_items : Comment.elements -> Item.t list
1002

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

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

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

1058
  module Class : sig
1059
    val class_ : Lang.Class.t -> Item.t
1060

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

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

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

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

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

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

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

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

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

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

1260
  open Class
1261

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1672
    and mty : Odoc_model.Lang.ModuleType.expr -> text =
1673
     fun m ->
1674
      if mty_hidden m then
3,397✔
1675
        Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1676
      else
1677
        match m with
3,397✔
1678
        | Path { p_path = mty_path; _ } ->
837✔
1679
            Link.from_path (mty_path :> Paths.Path.t)
1680
        | Functor (Unit, expr) ->
×
1681
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1682
            ++ O.span (O.txt " () " ++ Syntax.Type.arrow)
×
1683
            ++ O.sp ++ mty expr
×
1684
        | Functor (Named arg, expr) ->
48✔
1685
            let arg_expr = arg.expr in
1686
            let stop_before = expansion_of_module_type_expr arg_expr = None in
48✔
1687
            let name =
1688
              let open Odoc_model.Lang.FunctorParameter in
1689
              let name = Paths.Identifier.name arg.id in
1690
              let href =
48✔
1691
                Url.from_identifier ~stop_before (arg.id :> Paths.Identifier.t)
1692
              in
1693
              resolved href [ inline @@ Text name ]
48✔
1694
            in
1695
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1696
            ++ (O.box_hv @@ O.span
48✔
1697
               @@ O.txt " (" ++ name
48✔
1698
                  ++ O.txt Syntax.Type.annotation_separator
48✔
1699
                  ++ mty arg_expr ++ O.txt ")" ++ O.txt " " ++ Syntax.Type.arrow
48✔
1700
               )
1701
            ++ O.sp ++ mty expr
48✔
1702
        | With { w_expr; _ } when is_elidable_with_u w_expr ->
218✔
1703
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1704
        | With { w_substitutions; w_expr; _ } ->
218✔
1705
            O.box_hv @@ mty_with w_substitutions w_expr
218✔
1706
        | TypeOf { t_desc; _ } -> mty_typeof t_desc
64✔
1707
        | Signature _ ->
2,230✔
1708
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
2,230✔
NEW
1709
        | Strengthen { s_expr; _ } when is_elidable_with_u s_expr ->
×
NEW
1710
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
NEW
1711
        | Strengthen { s_expr; s_path; _ } ->
×
NEW
1712
            O.box_hv @@ mty_strengthen s_expr (s_path :> Paths.Path.t)
×
1713
    and mty_in_decl :
1714
        Paths.Identifier.Signature.t -> Odoc_model.Lang.ModuleType.expr -> text
1715
        =
1716
     fun base -> function
NEW
1717
      | (Path _ | Signature _ | With _ | TypeOf _ | Strengthen _) as m ->
×
1718
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
1,868✔
1719
      | Functor _ as m when not Syntax.Mod.functor_contraction ->
171✔
1720
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
×
1721
      | Functor (arg, expr) ->
171✔
1722
          let text_arg =
1723
            match arg with
1724
            | Unit -> O.txt "()"
8✔
1725
            | Named arg ->
163✔
1726
                let arg_expr = arg.expr in
1727
                let stop_before =
1728
                  expansion_of_module_type_expr arg_expr = None
163✔
1729
                in
1730
                let name =
1731
                  let open Odoc_model.Lang.FunctorParameter in
1732
                  let name = Paths.Identifier.name arg.id in
1733
                  let href =
163✔
1734
                    Url.from_identifier ~stop_before
1735
                      (arg.id :> Paths.Identifier.t)
1736
                  in
1737
                  resolved href [ inline @@ Text name ]
163✔
1738
                in
1739
                O.box_hv
163✔
1740
                @@ O.txt "(" ++ name
163✔
1741
                   ++ O.txt Syntax.Type.annotation_separator
163✔
1742
                   ++ O.cut ++ mty arg.expr ++ O.txt ")"
163✔
1743
          in
1744
          O.sp ++ text_arg ++ mty_in_decl base expr
171✔
1745

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

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

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

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

1829
  open Module
1830

1831
  module Page : sig
1832
    val compilation_unit : Lang.Compilation_unit.t -> Document.t
1833

1834
    val page : Lang.Page.t -> Document.t
1835

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

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

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

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

1894
  include Page
1895

1896
  let type_expr = type_expr
1897

1898
  let record = record
1899

1900
  let unboxed_record = unboxed_record
1901
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