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

ocaml / odoc / 1537

29 Sep 2023 11:10AM UTC coverage: 63.267% (+0.01%) from 63.256%
1537

push

github

jonludlam
Formatting

3858 of 6098 relevant lines covered (63.27%)

1584.93 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

100
include Generator_signatures
101

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

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

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

146
    let dot prefix suffix = prefix ^ "." ^ suffix
36✔
147

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

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

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

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

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

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

255
    let info_of_info : Lang.Source_info.annotation -> Source_page.info option =
256
      function
257
      | Value id -> (
×
258
          match Url.Anchor.from_identifier (id :> Paths.Identifier.t) with
259
          | Ok url -> Some (Link url)
×
260
          | Error _ -> None)
×
261
      | Definition id -> (
×
262
          match id.iv with
263
          | `SourceLocation (_, def) -> Some (Anchor (DefName.to_string def))
×
264
          | `SourceLocationInternal (_, local) ->
×
265
              Some (Anchor (LocalName.to_string local))
×
266
          | _ -> None)
×
267

268
    let source id syntax_info infos source_code =
269
      let url = path id in
×
270
      let mapper (info, loc) =
×
271
        match info_of_info info with Some x -> Some (x, loc) | None -> None
×
272
      in
273
      let infos = filter_map [] mapper infos in
274
      let syntax_info =
×
275
        List.map (fun (ty, loc) -> (Source_page.Syntax ty, loc)) syntax_info
×
276
      in
277
      let contents = Impl.impl ~infos:(infos @ syntax_info) source_code in
×
278
      { Source_page.url; contents }
×
279
  end
280

281
  module Type_expression : sig
282
    val type_expr : ?needs_parentheses:bool -> Lang.TypeExpr.t -> text
283

284
    val format_type_path :
285
      delim:[ `parens | `brackets ] -> Lang.TypeExpr.t list -> text -> text
286
  end = struct
287
    let rec te_variant (t : Odoc_model.Lang.TypeExpr.Polymorphic_variant.t) =
288
      let style_arguments ~constant arguments =
132✔
289
        (* Multiple arguments in a polymorphic variant constructor correspond
290
           to a conjunction of types, not a product: [`Lbl int&float].
291
           If constant is [true], the conjunction starts with an empty type,
292
           for instance [`Lbl &int].
293
        *)
294
        let wrapped_type_expr =
84✔
295
          (* type conjunction in Reason is printed as `Lbl (t1)&(t2)` *)
296
          if Syntax.Type.Variant.parenthesize_params then fun x ->
×
297
            enclose ~l:"(" ~r:")" (type_expr x)
×
298
          else fun x -> type_expr x
84✔
299
        in
300
        let arguments =
301
          O.list arguments ~sep:(O.txt " & ") ~f:wrapped_type_expr
84✔
302
        in
303
        if constant then O.txt "& " ++ arguments else arguments
12✔
304
      in
305
      let rec style_elements ~add_pipe = function
306
        | [] -> O.noop
132✔
307
        | first :: rest ->
180✔
308
            let first =
309
              match first with
310
              | Odoc_model.Lang.TypeExpr.Polymorphic_variant.Type te ->
6✔
311
                  let res = O.box_hv @@ type_expr te in
6✔
312
                  if add_pipe then O.sp ++ O.span (O.txt "| " ++ res) else res
×
313
              | Constructor { constant; name; arguments; _ } ->
174✔
314
                  let constr =
315
                    let name = "`" ^ name in
316
                    if add_pipe then O.span (O.txt ("| " ^ name))
54✔
317
                    else O.txt name
120✔
318
                  in
319
                  let res =
320
                    O.box_hv
321
                      (match arguments with
322
                      | [] -> constr
90✔
323
                      | _ ->
84✔
324
                          let arguments = style_arguments ~constant arguments in
325
                          O.span
84✔
326
                            (if Syntax.Type.Variant.parenthesize_params then
327
                               constr ++ arguments
×
328
                             else constr ++ O.txt " of" ++ O.sp ++ arguments))
84✔
329
                  in
330
                  if add_pipe then O.sp ++ res else res
54✔
331
            in
332
            first ++ style_elements ~add_pipe:true rest
180✔
333
      in
334
      let elements = style_elements ~add_pipe:false t.elements in
335
      O.box_hv_no_indent
132✔
336
      @@ O.span
132✔
337
           (match t.kind with
338
           | Fixed -> O.txt "[ " ++ elements ++ O.txt " ]"
36✔
339
           | Open -> O.txt "[> " ++ elements ++ O.txt " ]"
36✔
340
           | Closed [] -> O.txt "[< " ++ elements ++ O.txt " ]"
54✔
341
           | Closed lst ->
6✔
342
               let constrs = String.concat " " lst in
343
               O.txt "[< " ++ elements ++ O.txt (" " ^ constrs ^ " ]"))
6✔
344

345
    and te_object (t : Odoc_model.Lang.TypeExpr.Object.t) =
346
      let fields =
60✔
347
        O.list
348
          ~sep:(O.sp ++ O.txt Syntax.Obj.field_separator)
60✔
349
          t.fields
350
          ~f:(function
351
            | Odoc_model.Lang.TypeExpr.Object.Method { name; type_ } ->
90✔
352
                O.box_hv_no_indent
353
                @@ O.txt (name ^ Syntax.Type.annotation_separator)
90✔
354
                   ++ O.cut ++ type_expr type_
90✔
355
            | Inherit type_ -> O.box_hv_no_indent @@ type_expr type_)
×
356
      in
357
      let open_tag =
60✔
358
        if t.open_ then O.txt Syntax.Obj.open_tag_extendable
24✔
359
        else O.txt Syntax.Obj.open_tag_closed
36✔
360
      in
361
      let close_tag =
362
        if t.open_ then O.txt Syntax.Obj.close_tag_extendable
24✔
363
        else O.txt Syntax.Obj.close_tag_closed
36✔
364
      in
365
      O.span (open_tag ++ fields ++ close_tag)
60✔
366

367
    and format_type_path ~delim (params : Odoc_model.Lang.TypeExpr.t list)
368
        (path : text) : text =
369
      O.box_hv
2,910✔
370
      @@
371
      match params with
372
      | [] -> path
2,502✔
373
      | [ param ] ->
252✔
374
          let param = type_expr ~needs_parentheses:true param in
375
          let args =
252✔
376
            if Syntax.Type.parenthesize_constructor then
377
              O.txt "(" ++ param ++ O.txt ")"
×
378
            else param
252✔
379
          in
380
          Syntax.Type.handle_constructor_params path args
252✔
381
      | params ->
156✔
382
          let params = O.list params ~sep:(O.txt "," ++ O.sp) ~f:type_expr in
156✔
383
          let params =
156✔
384
            match delim with
385
            | `parens -> enclose ~l:"(" params ~r:")"
156✔
386
            | `brackets -> enclose ~l:"[" params ~r:"]"
×
387
          in
388
          Syntax.Type.handle_constructor_params path (O.box_hv params)
156✔
389

390
    and type_expr ?(needs_parentheses = false) (t : Odoc_model.Lang.TypeExpr.t)
2,910✔
391
        =
392
      match t with
4,158✔
393
      | Var s -> type_var (Syntax.Type.var_prefix ^ s)
546✔
394
      | Any -> type_var Syntax.Type.any
×
395
      | Alias (te, alias) ->
60✔
396
          type_expr ~needs_parentheses:true te
60✔
397
          ++ O.txt " " ++ O.keyword "as" ++ O.txt " '" ++ O.txt alias
60✔
398
      | Arrow (None, src, dst) ->
294✔
399
          let res =
400
            O.span
294✔
401
              ((O.box_hv @@ type_expr ~needs_parentheses:true src)
294✔
402
              ++ O.txt " " ++ Syntax.Type.arrow)
294✔
403
            ++ O.sp ++ type_expr dst
294✔
404
            (* ++ O.end_hv *)
405
          in
406
          if not needs_parentheses then res else enclose ~l:"(" res ~r:")"
24✔
407
      | Arrow (Some lbl, src, dst) ->
42✔
408
          let res =
409
            O.span
42✔
410
              ((O.box_hv
42✔
411
               @@ label lbl ++ O.txt ":" ++ O.cut
42✔
412
                  ++ (O.box_hv @@ type_expr ~needs_parentheses:true src))
42✔
413
              ++ O.txt " " ++ Syntax.Type.arrow)
42✔
414
            ++ O.sp ++ type_expr dst
42✔
415
          in
416
          if not needs_parentheses then res else enclose ~l:"(" res ~r:")"
12✔
417
      | Tuple lst ->
120✔
418
          let res =
419
            O.box_hv_no_indent
420
              (O.list lst ~sep:Syntax.Type.Tuple.element_separator
120✔
421
                 ~f:(type_expr ~needs_parentheses:true))
422
          in
423
          if Syntax.Type.Tuple.always_parenthesize || needs_parentheses then
×
424
            enclose ~l:"(" res ~r:")"
24✔
425
          else res
96✔
426
      | Constr (path, args) ->
2,862✔
427
          let link = Link.from_path (path :> Paths.Path.t) in
428
          format_type_path ~delim:`parens args link
2,862✔
429
      | Polymorphic_variant v -> te_variant v
132✔
430
      | Object o -> te_object o
60✔
431
      | Class (path, args) ->
6✔
432
          format_type_path ~delim:`brackets args
433
            (Link.from_path (path :> Paths.Path.t))
6✔
434
      | Poly (polyvars, t) ->
18✔
435
          O.txt ("'" ^ String.concat " '" polyvars ^ ". ") ++ type_expr t
18✔
436
      | Package pkg ->
18✔
437
          enclose ~l:"(" ~r:")"
438
            (O.keyword "module" ++ O.txt " "
18✔
439
            ++ Link.from_path (pkg.path :> Paths.Path.t)
18✔
440
            ++
18✔
441
            match pkg.substitutions with
442
            | [] -> O.noop
12✔
443
            | fst :: lst ->
6✔
444
                O.sp
445
                ++ O.box_hv (O.keyword "with" ++ O.txt " " ++ package_subst fst)
6✔
446
                ++ O.list lst ~f:(fun s ->
6✔
447
                       O.cut
6✔
448
                       ++ (O.box_hv
6✔
449
                          @@ O.txt " " ++ O.keyword "and" ++ O.txt " "
6✔
450
                             ++ package_subst s)))
6✔
451

452
    and package_subst
453
        ((frag_typ, te) : Paths.Fragment.Type.t * Odoc_model.Lang.TypeExpr.t) :
454
        text =
455
      let typath = Link.from_fragment (frag_typ :> Paths.Fragment.leaf) in
12✔
456
      O.keyword "type" ++ O.txt " " ++ typath ++ O.txt " =" ++ O.sp
12✔
457
      ++ type_expr te
12✔
458
  end
459

460
  open Type_expression
461

462
  (* Also handles constructor declarations for exceptions and extensible
463
     variants, and exposes a few helpers used in formatting classes and signature
464
     constraints. *)
465
  module Type_declaration : sig
466
    val type_decl :
467
      ?is_substitution:bool ->
468
      Lang.Signature.recursive * Lang.TypeDecl.t ->
469
      Item.t
470

471
    val extension : Lang.Extension.t -> Item.t
472

473
    val exn : Lang.Exception.t -> Item.t
474

475
    val format_params :
476
      ?delim:[ `parens | `brackets ] -> Lang.TypeDecl.param list -> text
477

478
    val format_manifest :
479
      ?is_substitution:bool ->
480
      ?compact_variants:bool ->
481
      Lang.TypeDecl.Equation.t ->
482
      text * bool
483

484
    val format_constraints : (Lang.TypeExpr.t * Lang.TypeExpr.t) list -> text
485
  end = struct
486
    let record fields =
487
      let field mutable_ id typ =
66✔
488
        match Url.from_identifier ~stop_before:true id with
114✔
489
        | Error e -> failwith (Url.Error.to_string e)
×
490
        | Ok url ->
114✔
491
            let name = Paths.Identifier.name id in
492
            let attrs =
114✔
493
              [ "def"; "record"; Url.Anchor.string_of_kind url.kind ]
114✔
494
            in
495
            let cell =
496
              (* O.td ~a:[ O.a_class ["def"; kind ] ]
497
               *   [O.a ~a:[O.a_href ("#" ^ anchor); O.a_class ["anchor"]] []
498
               *   ; *)
499
              O.code
500
                ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
18✔
501
                ++ O.txt name
114✔
502
                ++ O.txt Syntax.Type.annotation_separator
114✔
503
                ++ type_expr typ
114✔
504
                ++ O.txt Syntax.Type.Record.field_separator)
114✔
505
              (* ] *)
506
            in
507
            (url, attrs, cell)
114✔
508
      in
509
      let rows =
510
        fields
511
        |> List.map (fun fld ->
512
               let open Odoc_model.Lang.TypeDecl.Field in
114✔
513
               let url, attrs, code =
514
                 field fld.mutable_ (fld.id :> Paths.Identifier.t) fld.type_
515
               in
516
               let anchor = Some url in
114✔
517
               let rhs = Comment.to_ir fld.doc in
518
               let doc = if not (Comment.has_doc fld.doc) then [] else rhs in
48✔
519
               let markers = Syntax.Comment.markers in
520
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
521
      in
522
      let content =
66✔
523
        O.documentedSrc (O.txt "{") @ rows @ O.documentedSrc (O.txt "}")
66✔
524
      in
525
      content
526

527
    let constructor :
528
        Paths.Identifier.t ->
529
        Odoc_model.Lang.TypeDecl.Constructor.argument ->
530
        Odoc_model.Lang.TypeExpr.t option ->
531
        DocumentedSrc.t =
532
     fun id args ret_type ->
533
      let name = Paths.Identifier.name id in
504✔
534
      let kind = Url.(kind id |> Anchor.string_of_kind) in
504✔
535
      let cstr = tag kind (O.txt name) in
504✔
536
      let is_gadt, ret_type =
504✔
537
        match ret_type with
538
        | None -> (false, O.noop)
372✔
539
        | Some te ->
132✔
540
            let constant = match args with Tuple [] -> true | _ -> false in
42✔
541
            let ret_type =
542
              O.txt " "
132✔
543
              ++ (if constant then O.txt ":" else Syntax.Type.GADT.arrow)
42✔
544
              ++ O.txt " " ++ type_expr te
132✔
545
            in
546
            (true, ret_type)
132✔
547
      in
548
      match args with
549
      | Tuple [] -> O.documentedSrc (cstr ++ ret_type)
222✔
550
      | Tuple lst ->
264✔
551
          let params =
552
            O.list lst ~sep:Syntax.Type.Tuple.element_separator
553
              ~f:(type_expr ~needs_parentheses:is_gadt)
554
          in
555
          O.documentedSrc
264✔
556
            (cstr
557
            ++ (if Syntax.Type.Variant.parenthesize_params then
264✔
558
                  O.txt "(" ++ params ++ O.txt ")"
×
559
                else
560
                  (if is_gadt then O.txt Syntax.Type.annotation_separator
84✔
561
                   else O.txt " " ++ O.keyword "of" ++ O.txt " ")
180✔
562
                  ++ params)
264✔
563
            ++ ret_type)
264✔
564
      | Record fields ->
18✔
565
          if is_gadt then
566
            O.documentedSrc (cstr ++ O.txt Syntax.Type.annotation_separator)
6✔
567
            @ record fields @ O.documentedSrc ret_type
6✔
568
          else
569
            O.documentedSrc (cstr ++ O.txt " " ++ O.keyword "of" ++ O.txt " ")
12✔
570
            @ record fields
12✔
571

572
    let rec read_typ_exp typ_expr =
573
      let open Lang.TypeExpr in
366✔
574
      let open Paths.Path in
575
      match typ_expr with
576
      | Constr (p, ts) ->
234✔
577
          is_hidden (p :> Paths.Path.t)
24✔
578
          || List.exists (fun t -> read_typ_exp t) ts
24✔
579
      | Poly (_, t) | Alias (t, _) -> read_typ_exp t
×
580
      | Arrow (_, t, t2) -> read_typ_exp t || read_typ_exp t2
×
581
      | Tuple ts | Class (_, ts) -> List.exists (fun t -> read_typ_exp t) ts
×
582
      | _ -> false
96✔
583

584
    let internal_cstr_arg t =
585
      let open Lang.TypeDecl.Constructor in
372✔
586
      let open Lang.TypeDecl.Field in
587
      match t.args with
588
      | Tuple type_exprs ->
348✔
589
          List.exists (fun type_expr -> read_typ_exp type_expr) type_exprs
246✔
590
      | Record fields ->
24✔
591
          List.exists (fun field -> read_typ_exp field.type_) fields
24✔
592

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

630
    let extension_constructor (t : Odoc_model.Lang.Extension.Constructor.t) =
631
      let id = (t.id :> Paths.Identifier.t) in
108✔
632
      match Url.from_identifier ~stop_before:true id with
633
      | Error e -> failwith (Url.Error.to_string e)
×
634
      | Ok url ->
108✔
635
          let anchor = Some url in
636
          let attrs = [ "def"; Url.Anchor.string_of_kind url.kind ] in
108✔
637
          let code =
638
            O.documentedSrc (O.txt "| ") @ constructor id t.args t.res
108✔
639
          in
640
          let doc = Comment.to_ir t.doc in
641
          let markers = Syntax.Comment.markers in
108✔
642
          DocumentedSrc.Nested { anchor; attrs; code; doc; markers }
643

644
    let extension (t : Odoc_model.Lang.Extension.t) =
645
      let prefix =
90✔
646
        O.keyword "type" ++ O.txt " "
90✔
647
        ++ Link.from_path (t.type_path :> Paths.Path.t)
90✔
648
        ++ O.txt " +=" ++ O.sp
90✔
649
      in
650
      let content =
90✔
651
        O.documentedSrc prefix
90✔
652
        @ List.map extension_constructor t.constructors
90✔
653
        @ O.documentedSrc
90✔
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
90✔
658
      let doc = Comment.to_ir t.doc in
659
      let source_anchor =
90✔
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.locs
90✔
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
48✔
670
      let content =
48✔
671
        O.documentedSrc (O.keyword "exception" ++ O.txt " ")
48✔
672
        @ cstr
673
        @ O.documentedSrc
48✔
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 in
48✔
679
      let source_anchor = source_anchor t.locs in
48✔
680
      Item.Declaration { attr; anchor; doc; content; source_anchor }
48✔
681

682
    let polymorphic_variant ~type_ident
683
        (t : Odoc_model.Lang.TypeExpr.Polymorphic_variant.t) =
684
      let row item =
66✔
685
        let kind_approx, cstr, doc =
150✔
686
          match item with
687
          | Odoc_model.Lang.TypeExpr.Polymorphic_variant.Type te ->
18✔
688
              ("unknown", O.code (type_expr te), None)
18✔
689
          | Constructor { constant; name; arguments; doc; _ } -> (
132✔
690
              let cstr = "`" ^ name in
691
              ( "constructor",
692
                (match arguments with
693
                | [] -> O.code (O.txt cstr)
72✔
694
                | _ ->
60✔
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
60✔
705
                    in
706
                    let params =
707
                      O.box_hv
708
                      @@ O.list arguments
60✔
709
                           ~sep:(O.txt " &" ++ O.sp)
60✔
710
                           ~f:wrapped_type_expr
711
                    in
712
                    let params =
60✔
713
                      if constant then O.txt "& " ++ params else params
×
714
                    in
715
                    O.code
60✔
716
                      (O.txt cstr
60✔
717
                      ++
60✔
718
                      if Syntax.Type.Variant.parenthesize_params then params
×
719
                      else O.txt " " ++ O.keyword "of" ++ O.sp ++ params)),
60✔
720
                match doc with [] -> None | _ -> Some (Comment.to_ir doc) ))
12✔
721
        in
722
        let markers = Syntax.Comment.markers in
723
        try
724
          let url = Url.Anchor.polymorphic_variant ~type_ident item in
725
          let attrs = [ "def"; Url.Anchor.string_of_kind url.kind ] in
150✔
726
          let anchor = Some url in
727
          let code = O.code (O.txt "| ") @ cstr in
150✔
728
          let doc = match doc with None -> [] | Some doc -> doc in
12✔
729
          DocumentedSrc.Documented { attrs; anchor; code; doc; markers }
730
        with Failure s ->
×
731
          Printf.eprintf "ERROR: %s\n%!" s;
732
          let code = O.code (O.txt "| ") @ cstr in
×
733
          let attrs = [ "def"; kind_approx ] in
734
          let doc = [] in
735
          let anchor = None in
736
          DocumentedSrc.Documented { attrs; anchor; code; doc; markers }
737
      in
738
      let variants = List.map row t.elements in
739
      let intro, ending =
66✔
740
        match t.kind with
741
        | Fixed -> (O.documentedSrc (O.txt "[ "), O.documentedSrc (O.txt " ]"))
60✔
742
        | Open -> (O.documentedSrc (O.txt "[> "), O.documentedSrc (O.txt " ]"))
6✔
743
        | Closed [] ->
×
744
            (O.documentedSrc (O.txt "[< "), O.documentedSrc (O.txt " ]"))
×
745
        | Closed lst ->
×
746
            let constrs = String.concat " " lst in
747
            ( O.documentedSrc (O.txt "[< "),
×
748
              O.documentedSrc (O.txt (" " ^ constrs ^ " ]")) )
×
749
      in
750
      intro @ variants @ ending
751

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

784
    let format_constraints constraints =
785
      O.list constraints ~f:(fun (t1, t2) ->
2,232✔
786
          O.sp
78✔
787
          ++ (O.box_hv
78✔
788
             @@ O.keyword "constraint" ++ O.sp
78✔
789
                ++ O.box_hv_no_indent (type_expr t1)
78✔
790
                ++ O.txt " =" ++ O.sp
78✔
791
                ++ O.box_hv_no_indent (type_expr t2)))
78✔
792

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

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

894
  open Type_declaration
895

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

923
  open Value
924

925
  (* This chunk of code is responsible for sectioning list of items
926
     according to headings by extracting headings as Items.
927

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

933
    val comment_items : Comment.docs -> Item.t list
934

935
    val docs : Comment.docs -> Item.t list * Item.t list
936
  end = struct
937
    let take_until_heading_or_end (docs : Odoc_model.Comment.docs) =
938
      let content, _, rest =
510✔
939
        Doctree.Take.until docs ~classify:(fun b ->
940
            match b.Location.value with
1,008✔
941
            | `Heading _ -> Stop_and_keep
126✔
942
            | #Odoc_model.Comment.attached_block_element as doc ->
882✔
943
                let content = Comment.attached_block_element doc in
944
                Accum content)
882✔
945
      in
946
      (content, rest)
510✔
947

948
    let comment_items (input0 : Odoc_model.Comment.docs) =
949
      let rec loop input_comment acc =
702✔
950
        match input_comment with
1,734✔
951
        | [] -> List.rev acc
702✔
952
        | element :: input_comment -> (
1,032✔
953
            match element.Location.value with
954
            | `Heading h ->
522✔
955
                let item = Comment.heading h in
956
                loop input_comment (item :: acc)
522✔
957
            | _ ->
510✔
958
                let content, input_comment =
959
                  take_until_heading_or_end (element :: input_comment)
960
                in
961
                let item = Item.Text content in
510✔
962
                loop input_comment (item :: acc))
963
      in
964
      loop input0 []
965

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

990
  module Class : sig
991
    val class_ : Lang.Class.t -> Item.t
992

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

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

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

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

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

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

1089
    let rec class_decl (cd : Odoc_model.Lang.Class.decl) =
1090
      match cd with
132✔
1091
      | ClassType expr -> class_type_expr expr
120✔
1092
      (* TODO: factorize the following with [type_expr] *)
1093
      | Arrow (None, src, dst) ->
12✔
1094
          O.span
12✔
1095
            (type_expr ~needs_parentheses:true src
12✔
1096
            ++ O.txt " " ++ Syntax.Type.arrow)
12✔
1097
          ++ O.txt " " ++ class_decl dst
12✔
1098
      | Arrow (Some lbl, src, dst) ->
×
1099
          O.span
×
1100
            (label lbl ++ O.txt ":"
×
1101
            ++ type_expr ~needs_parentheses:true src
×
1102
            ++ O.txt " " ++ Syntax.Type.arrow)
×
1103
          ++ O.txt " " ++ class_decl dst
×
1104

1105
    let class_ (t : Odoc_model.Lang.Class.t) =
1106
      let name = Paths.Identifier.name t.id in
120✔
1107
      let params =
120✔
1108
        match t.params with
1109
        | [] -> O.noop
102✔
1110
        | _ :: _ as params -> format_params ~delim:`brackets params ++ O.txt " "
18✔
1111
      in
1112
      let virtual_ =
1113
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
18✔
1114
      in
1115

1116
      let source_anchor = source_anchor t.locs in
1117
      let cname, expansion, expansion_doc =
120✔
1118
        match t.expansion with
1119
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1120
        | Some csig ->
120✔
1121
            let expansion_doc, items = class_signature csig in
1122
            let url = Url.Path.from_identifier t.id in
120✔
1123
            let page =
120✔
1124
              make_expansion_page ~source_anchor url [ t.doc; expansion_doc ]
1125
                items
1126
            in
1127
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
120✔
1128
              Some page,
1129
              Some expansion_doc )
1130
      in
1131
      let summary =
1132
        O.txt Syntax.Type.annotation_separator ++ class_decl t.type_
120✔
1133
      in
1134
      let cd =
120✔
1135
        attach_expansion
1136
          (Syntax.Type.annotation_separator, "object", "end")
1137
          expansion summary
1138
      in
1139
      let content =
120✔
1140
        O.documentedSrc (O.keyword "class" ++ O.txt " " ++ virtual_ ++ params)
120✔
1141
        @ cname @ cd
1142
      in
1143
      let attr = [ "class" ] in
1144
      let anchor = path_to_id t.id in
1145
      let doc = Comment.synopsis ~decl_doc:t.doc ~expansion_doc in
120✔
1146
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1147

1148
    let class_type (t : Odoc_model.Lang.ClassType.t) =
1149
      let name = Paths.Identifier.name t.id in
48✔
1150
      let params = format_params ~delim:`brackets t.params in
48✔
1151
      let virtual_ =
48✔
1152
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
6✔
1153
      in
1154
      let source_anchor = source_anchor t.locs in
1155
      let cname, expansion, expansion_doc =
48✔
1156
        match t.expansion with
1157
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1158
        | Some csig ->
48✔
1159
            let url = Url.Path.from_identifier t.id in
1160
            let expansion_doc, items = class_signature csig in
48✔
1161
            let page =
48✔
1162
              make_expansion_page ~source_anchor url [ t.doc; expansion_doc ]
1163
                items
1164
            in
1165
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
48✔
1166
              Some page,
1167
              Some expansion_doc )
1168
      in
1169
      let summary = O.txt " = " ++ class_type_expr t.expr in
48✔
1170
      let expr = attach_expansion (" = ", "object", "end") expansion summary in
48✔
1171
      let content =
48✔
1172
        O.documentedSrc
48✔
1173
          (O.keyword "class" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
48✔
1174
         ++ virtual_ ++ params ++ O.txt " ")
48✔
1175
        @ cname @ expr
1176
      in
1177
      let attr = [ "class-type" ] in
1178
      let anchor = path_to_id t.id in
1179
      let doc = Comment.synopsis ~decl_doc:t.doc ~expansion_doc in
48✔
1180
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1181
  end
1182

1183
  open Class
1184

1185
  module Module : sig
1186
    val signature : Lang.Signature.t -> Comment.Comment.docs * Item.t list
1187
    (** Returns [header_doc, content]. *)
1188
  end = struct
1189
    let internal_module m =
1190
      let open Lang.Module in
1,326✔
1191
      match m.id.iv with
1192
      | `Module (_, name) when ModuleName.is_internal name -> true
54✔
1193
      | _ -> false
1,272✔
1194

1195
    let internal_type t =
1196
      let open Lang.TypeDecl in
2,130✔
1197
      match t.id.iv with
1198
      | `Type (_, name) when TypeName.is_internal name -> true
×
1199
      | _ -> false
2,130✔
1200

1201
    let internal_value v =
1202
      let open Lang.Value in
594✔
1203
      match v.id.iv with
1204
      | `Value (_, name) when ValueName.is_internal name -> true
72✔
1205
      | _ -> false
522✔
1206

1207
    let internal_module_type t =
1208
      let open Lang.ModuleType in
990✔
1209
      match t.id.iv with
1210
      | `ModuleType (_, name) when ModuleTypeName.is_internal name -> true
×
1211
      | _ -> false
990✔
1212

1213
    let internal_module_substitution t =
1214
      let open Lang.ModuleSubstitution in
12✔
1215
      match t.id.iv with
1216
      | `Module (_, name) when ModuleName.is_internal name -> true
×
1217
      | _ -> false
12✔
1218

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

1225
    let rec signature (s : Lang.Signature.t) =
1226
      let rec loop l acc_items =
2,424✔
1227
        match l with
8,718✔
1228
        | [] -> List.rev acc_items
2,424✔
1229
        | item :: rest -> (
6,294✔
1230
            let continue (item : Item.t) = loop rest (item :: acc_items) in
5,442✔
1231
            match (item : Lang.Signature.item) with
1232
            | Module (_, m) when internal_module m -> loop rest acc_items
54✔
1233
            | Type (_, t) when internal_type t -> loop rest acc_items
×
1234
            | Value v when internal_value v -> loop rest acc_items
72✔
1235
            | ModuleType m when internal_module_type m -> loop rest acc_items
×
1236
            | ModuleSubstitution m when internal_module_substitution m ->
12✔
1237
                loop rest acc_items
×
1238
            | ModuleTypeSubstitution m when internal_module_type_substitution m
6✔
1239
              ->
1240
                loop rest acc_items
×
1241
            | ModuleTypeSubstitution m -> continue @@ module_type_substitution m
6✔
1242
            | Module (_, m) -> continue @@ module_ m
1,272✔
1243
            | ModuleType m -> continue @@ module_type m
990✔
1244
            | Class (_, c) -> continue @@ class_ c
120✔
1245
            | ClassType (_, c) -> continue @@ class_type c
48✔
1246
            | Include m -> continue @@ include_ m
186✔
1247
            | ModuleSubstitution m -> continue @@ module_substitution m
12✔
1248
            | TypeSubstitution t ->
18✔
1249
                continue @@ type_decl ~is_substitution:true (Ordinary, t)
18✔
1250
            | Type (r, t) -> continue @@ type_decl (r, t)
2,130✔
1251
            | TypExt e -> continue @@ extension e
90✔
1252
            | Exception e -> continue @@ exn e
48✔
1253
            | Value v -> continue @@ value v
522✔
1254
            | Open o ->
60✔
1255
                let items = Sectioning.comment_items o.doc in
1256
                loop rest (List.rev_append items acc_items)
60✔
1257
            | Comment `Stop ->
42✔
1258
                let rest =
1259
                  Utils.skip_until rest ~p:(function
1260
                    | Lang.Signature.Comment `Stop -> true
36✔
1261
                    | _ -> false)
48✔
1262
                in
1263
                loop rest acc_items
42✔
1264
            | Comment (`Docs c) ->
624✔
1265
                let items = Sectioning.comment_items c in
1266
                loop rest (List.rev_append items acc_items))
624✔
1267
      in
1268
      (Lang.extract_signature_doc s, loop s.items [])
2,424✔
1269

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

1311
    and module_substitution (t : Odoc_model.Lang.ModuleSubstitution.t) =
1312
      let name = Paths.Identifier.name t.id in
12✔
1313
      let path = Link.from_path (t.manifest :> Paths.Path.t) in
12✔
1314
      let content =
12✔
1315
        O.documentedSrc
1316
          (O.keyword "module" ++ O.txt " " ++ O.txt name ++ O.txt " :=" ++ O.sp
12✔
1317
         ++ path)
12✔
1318
      in
1319
      let attr = [ "module-substitution" ] in
12✔
1320
      let anchor = path_to_id t.id in
1321
      let doc = Comment.to_ir t.doc in
12✔
1322
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
12✔
1323

1324
    and module_type_substitution (t : Odoc_model.Lang.ModuleTypeSubstitution.t)
1325
        =
1326
      let prefix =
6✔
1327
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
6✔
1328
      in
1329
      let source_anchor = None in
6✔
1330
      let modname = Paths.Identifier.name t.id in
1331
      let modname, expansion_doc, mty =
6✔
1332
        module_type_manifest ~subst:true ~source_anchor modname t.id t.doc
1333
          (Some t.manifest) prefix
1334
      in
1335
      let content =
6✔
1336
        O.documentedSrc (prefix ++ modname)
6✔
1337
        @ mty
1338
        @ O.documentedSrc
6✔
1339
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1340
      in
1341
      let attr = [ "module-type" ] in
1342
      let anchor = path_to_id t.id in
1343
      let doc = Comment.synopsis ~decl_doc:t.doc ~expansion_doc in
6✔
1344
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1345

1346
    and simple_expansion :
1347
        Odoc_model.Lang.ModuleType.simple_expansion ->
1348
        Comment.Comment.docs * Item.t list =
1349
     fun t ->
1350
      let rec extract_functor_params
2,058✔
1351
          (f : Odoc_model.Lang.ModuleType.simple_expansion) =
1352
        match f with
2,214✔
1353
        | Signature sg -> (None, sg)
2,058✔
1354
        | Functor (p, expansion) ->
156✔
1355
            let add_to params =
1356
              match p with Unit -> params | Named p -> p :: params
6✔
1357
            in
1358
            let params, sg = extract_functor_params expansion in
1359
            let params = match params with None -> [] | Some p -> p in
24✔
1360
            (Some (add_to params), sg)
156✔
1361
      in
1362
      match extract_functor_params t with
1363
      | None, sg -> signature sg
1,926✔
1364
      | Some params, sg ->
132✔
1365
          let sg_doc, content = signature sg in
1366
          let params =
132✔
1367
            Utils.flatmap params ~f:(fun arg ->
1368
                let content = functor_parameter arg in
150✔
1369
                let attr = [ "parameter" ] in
150✔
1370
                let anchor =
1371
                  Utils.option_of_result
1372
                  @@ Url.Anchor.from_identifier (arg.id :> Paths.Identifier.t)
150✔
1373
                in
1374
                let doc = [] in
150✔
1375
                [
1376
                  Item.Declaration
1377
                    { content; anchor; attr; doc; source_anchor = None };
1378
                ])
1379
          in
1380
          let prelude = mk_heading ~label:"parameters" "Parameters" :: params
132✔
1381
          and content = mk_heading ~label:"signature" "Signature" :: content in
132✔
1382
          (sg_doc, prelude @ content)
1383

1384
    and expansion_of_module_type_expr :
1385
        Odoc_model.Lang.ModuleType.expr ->
1386
        (Comment.Comment.docs * Item.t list) option =
1387
     fun t ->
1388
      let rec simple_expansion_of (t : Odoc_model.Lang.ModuleType.expr) =
2,280✔
1389
        match t with
2,430✔
1390
        | Path { p_expansion = None; _ }
282✔
1391
        | TypeOf { t_expansion = None; _ }
×
1392
        | With { w_expansion = None; _ } ->
×
1393
            None
1394
        | Path { p_expansion = Some e; _ }
330✔
1395
        | TypeOf { t_expansion = Some e; _ }
30✔
1396
        | With { w_expansion = Some e; _ } ->
174✔
1397
            Some e
1398
        | Signature sg -> Some (Signature sg)
1,464✔
1399
        | Functor (f_parameter, e) -> (
150✔
1400
            match simple_expansion_of e with
1401
            | Some e -> Some (Functor (f_parameter, e))
150✔
1402
            | None -> None)
×
1403
      in
1404
      match simple_expansion_of t with
1405
      | None -> None
282✔
1406
      | Some e -> Some (simple_expansion e)
1,998✔
1407

1408
    and module_ : Odoc_model.Lang.Module.t -> Item.t =
1409
     fun t ->
1410
      let modname = Paths.Identifier.name t.id in
1,272✔
1411
      let expansion =
1,272✔
1412
        match t.type_ with
1413
        | Alias (_, Some e) -> Some (simple_expansion e)
60✔
1414
        | Alias (_, None) -> None
126✔
1415
        | ModuleType e -> expansion_of_module_type_expr e
1,086✔
1416
      in
1417
      let source_anchor = source_anchor t.locs in
1418
      let modname, status, expansion, expansion_doc =
1,272✔
1419
        match expansion with
1420
        | None -> (O.txt modname, `Default, None, None)
222✔
1421
        | Some (expansion_doc, items) ->
1,050✔
1422
            let status =
1423
              match t.type_ with
1424
              | ModuleType (Signature _) -> `Inline
690✔
1425
              | _ -> `Default
360✔
1426
            in
1427
            let url = Url.Path.from_identifier t.id in
1428
            let link = path url [ inline @@ Text modname ] in
1,050✔
1429
            let page =
1,050✔
1430
              make_expansion_page ~source_anchor url [ t.doc; expansion_doc ]
1431
                items
1432
            in
1433
            (link, status, Some page, Some expansion_doc)
1,050✔
1434
      in
1435
      (* TODO: link to source *)
1436
      let intro = O.keyword "module" ++ O.txt " " ++ modname in
1,272✔
1437
      let summary = O.ignore intro ++ mdexpr_in_decl t.id t.type_ in
1,272✔
1438
      let modexpr =
1,272✔
1439
        attach_expansion ~status
1440
          (Syntax.Type.annotation_separator, "sig", "end")
1441
          expansion summary
1442
      in
1443
      let content =
1,272✔
1444
        O.documentedSrc intro @ modexpr
1,272✔
1445
        @ O.documentedSrc
1,272✔
1446
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1447
      in
1448
      let attr = [ "module" ] in
1449
      let anchor = path_to_id t.id in
1450
      let doc = Comment.synopsis ~decl_doc:t.doc ~expansion_doc in
1,272✔
1451
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1452

1453
    and simple_expansion_in_decl (base : Paths.Identifier.Module.t) se =
1454
      let rec ty_of_se :
60✔
1455
          Lang.ModuleType.simple_expansion -> Lang.ModuleType.expr = function
1456
        | Signature sg -> Signature sg
60✔
1457
        | Functor (arg, sg) -> Functor (arg, ty_of_se sg)
×
1458
      in
1459
      mty_in_decl (base :> Paths.Identifier.Signature.t) (ty_of_se se)
60✔
1460

1461
    and mdexpr_in_decl (base : Paths.Identifier.Module.t) md =
1462
      let sig_dotdotdot =
1,272✔
1463
        O.txt Syntax.Type.annotation_separator
1,272✔
1464
        ++ O.cut ++ Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
1,272✔
1465
      in
1466
      match md with
1,272✔
1467
      | Alias (_, Some se) -> simple_expansion_in_decl base se
60✔
1468
      | Alias (p, _) when not Paths.Path.(is_hidden (p :> t)) ->
126✔
1469
          O.txt " =" ++ O.sp ++ mdexpr md
126✔
1470
      | Alias _ -> sig_dotdotdot
×
1471
      | ModuleType mt -> mty_in_decl (base :> Paths.Identifier.Signature.t) mt
1,086✔
1472

1473
    and mdexpr : Odoc_model.Lang.Module.decl -> text = function
1474
      | Alias (mod_path, _) -> Link.from_path (mod_path :> Paths.Path.t)
162✔
1475
      | ModuleType mt -> mty mt
×
1476

1477
    and module_type_manifest ~subst ~source_anchor modname id doc manifest
1478
        prefix =
1479
      let expansion =
996✔
1480
        match manifest with
1481
        | None -> None
96✔
1482
        | Some e -> expansion_of_module_type_expr e
900✔
1483
      in
1484
      let modname, expansion, expansion_doc =
1485
        match expansion with
1486
        | None -> (O.txt modname, None, None)
282✔
1487
        | Some (expansion_doc, items) ->
714✔
1488
            let url = Url.Path.from_identifier id in
1489
            let link = path url [ inline @@ Text modname ] in
714✔
1490
            let page =
714✔
1491
              make_expansion_page ~source_anchor url [ doc; expansion_doc ]
1492
                items
1493
            in
1494
            (link, Some page, Some expansion_doc)
714✔
1495
      in
1496
      let summary =
1497
        match manifest with
1498
        | None -> O.noop
96✔
1499
        | Some expr ->
900✔
1500
            O.ignore (prefix ++ modname)
900✔
1501
            ++ (if subst then O.txt " :=" ++ O.sp else O.txt " =" ++ O.sp)
6✔
1502
            ++ mty expr
900✔
1503
      in
1504
      ( modname,
1505
        expansion_doc,
1506
        attach_expansion (" = ", "sig", "end") expansion summary )
996✔
1507

1508
    and module_type (t : Odoc_model.Lang.ModuleType.t) =
1509
      let prefix =
990✔
1510
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
990✔
1511
      in
1512
      let modname = Paths.Identifier.name t.id in
990✔
1513
      let source_anchor = source_anchor t.locs in
990✔
1514
      let modname, expansion_doc, mty =
990✔
1515
        module_type_manifest ~subst:false ~source_anchor modname t.id t.doc
1516
          t.expr prefix
1517
      in
1518
      let content =
990✔
1519
        O.documentedSrc (prefix ++ modname)
990✔
1520
        @ mty
1521
        @ O.documentedSrc
990✔
1522
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1523
      in
1524
      let attr = [ "module-type" ] in
1525
      let anchor = path_to_id t.id in
1526
      let doc = Comment.synopsis ~decl_doc:t.doc ~expansion_doc in
990✔
1527
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1528

1529
    and umty_hidden : Odoc_model.Lang.ModuleType.U.expr -> bool = function
1530
      | Path p -> Paths.Path.(is_hidden (p :> t))
276✔
1531
      | With (_, expr) -> umty_hidden expr
12✔
1532
      | TypeOf { t_desc = ModPath m; _ }
30✔
1533
      | TypeOf { t_desc = StructInclude m; _ } ->
30✔
1534
          Paths.Path.(is_hidden (m :> t))
1535
      | Signature _ -> false
24✔
1536

1537
    and mty_hidden : Odoc_model.Lang.ModuleType.expr -> bool = function
1538
      | Path { p_path = mty_path; _ } -> Paths.Path.(is_hidden (mty_path :> t))
636✔
1539
      | With { w_expr; _ } -> umty_hidden w_expr
174✔
1540
      | TypeOf { t_desc = ModPath m; _ }
30✔
1541
      | TypeOf { t_desc = StructInclude m; _ } ->
×
1542
          Paths.Path.(is_hidden (m :> t))
1543
      | _ -> false
1,578✔
1544

1545
    and mty_with subs expr =
1546
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
168✔
1547
      ++ O.list
168✔
1548
           ~sep:(O.cut ++ O.txt " " ++ O.keyword "and" ++ O.txt " ")
168✔
1549
           ~f:(fun x -> O.span (substitution x))
198✔
1550
           subs
1551

1552
    and mty_typeof t_desc =
1553
      match t_desc with
90✔
1554
      | Odoc_model.Lang.ModuleType.ModPath m ->
60✔
1555
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
60✔
1556
          ++ O.keyword "of" ++ O.txt " "
60✔
1557
          ++ Link.from_path (m :> Paths.Path.t)
60✔
1558
      | StructInclude m ->
30✔
1559
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
30✔
1560
          ++ O.keyword "of" ++ O.txt " " ++ O.keyword "struct" ++ O.txt " "
30✔
1561
          ++ O.keyword "include" ++ O.txt " "
30✔
1562
          ++ Link.from_path (m :> Paths.Path.t)
30✔
1563
          ++ O.txt " " ++ O.keyword "end"
30✔
1564

1565
    and is_elidable_with_u : Odoc_model.Lang.ModuleType.U.expr -> bool =
1566
      function
1567
      | Path _ -> false
150✔
1568
      | Signature _ -> true
18✔
1569
      | With (_, expr) -> is_elidable_with_u expr
×
1570
      | TypeOf _ -> false
18✔
1571

1572
    and umty : Odoc_model.Lang.ModuleType.U.expr -> text =
1573
     fun m ->
1574
      match m with
354✔
1575
      | Path p -> Link.from_path (p :> Paths.Path.t)
276✔
1576
      | Signature _ ->
6✔
1577
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
6✔
1578
      | With (_, expr) when is_elidable_with_u expr ->
12✔
1579
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1580
      | With (subs, expr) -> mty_with subs expr
12✔
1581
      | TypeOf { t_desc; _ } -> mty_typeof t_desc
60✔
1582

1583
    and mty : Odoc_model.Lang.ModuleType.expr -> text =
1584
     fun m ->
1585
      if mty_hidden m then
2,418✔
1586
        Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1587
      else
1588
        match m with
2,418✔
1589
        | Path { p_path = mty_path; _ } ->
636✔
1590
            Link.from_path (mty_path :> Paths.Path.t)
1591
        | Functor (Unit, expr) ->
×
1592
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1593
            ++ O.span (O.txt " () " ++ Syntax.Type.arrow)
×
1594
            ++ O.sp ++ mty expr
×
1595
        | Functor (Named arg, expr) ->
42✔
1596
            let arg_expr = arg.expr in
1597
            let stop_before = expansion_of_module_type_expr arg_expr = None in
42✔
1598
            let name =
1599
              let open Odoc_model.Lang.FunctorParameter in
1600
              let name = Paths.Identifier.name arg.id in
1601
              match
42✔
1602
                Url.from_identifier ~stop_before (arg.id :> Paths.Identifier.t)
1603
              with
1604
              | Error _ -> O.txt name
×
1605
              | Ok href -> resolved href [ inline @@ Text name ]
42✔
1606
            in
1607
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1608
            ++ (O.box_hv @@ O.span
42✔
1609
               @@ O.txt " (" ++ name
42✔
1610
                  ++ O.txt Syntax.Type.annotation_separator
42✔
1611
                  ++ mty arg_expr ++ O.txt ")" ++ O.txt " " ++ Syntax.Type.arrow
42✔
1612
               )
1613
            ++ O.sp ++ mty expr
42✔
1614
        | With { w_expr; _ } when is_elidable_with_u w_expr ->
174✔
1615
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
18✔
1616
        | With { w_substitutions; w_expr; _ } ->
156✔
1617
            O.box_hv @@ mty_with w_substitutions w_expr
156✔
1618
        | TypeOf { t_desc; _ } -> mty_typeof t_desc
30✔
1619
        | Signature _ ->
1,536✔
1620
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
1,536✔
1621

1622
    and mty_in_decl :
1623
        Paths.Identifier.Signature.t -> Odoc_model.Lang.ModuleType.expr -> text
1624
        =
1625
     fun base -> function
1626
      | (Path _ | Signature _ | With _ | TypeOf _) as m ->
12✔
1627
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
1,296✔
1628
      | Functor _ as m when not Syntax.Mod.functor_contraction ->
108✔
1629
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
×
1630
      | Functor (arg, expr) ->
108✔
1631
          let text_arg =
1632
            match arg with
1633
            | Unit -> O.txt "()"
6✔
1634
            | Named arg ->
102✔
1635
                let arg_expr = arg.expr in
1636
                let stop_before =
1637
                  expansion_of_module_type_expr arg_expr = None
102✔
1638
                in
1639
                let name =
1640
                  let open Odoc_model.Lang.FunctorParameter in
1641
                  let name = Paths.Identifier.name arg.id in
1642
                  match
102✔
1643
                    Url.from_identifier ~stop_before
1644
                      (arg.id :> Paths.Identifier.t)
1645
                  with
1646
                  | Error _ -> O.txt name
×
1647
                  | Ok href -> resolved href [ inline @@ Text name ]
102✔
1648
                in
1649
                O.box_hv
102✔
1650
                @@ O.txt "(" ++ name
102✔
1651
                   ++ O.txt Syntax.Type.annotation_separator
102✔
1652
                   ++ O.cut ++ mty arg.expr ++ O.txt ")"
102✔
1653
          in
1654
          O.sp ++ text_arg ++ mty_in_decl base expr
108✔
1655

1656
    (* TODO : Centralize the list juggling for type parameters *)
1657
    and type_expr_in_subst td typath =
1658
      let typath = Link.from_fragment typath in
108✔
1659
      match td.Lang.TypeDecl.Equation.params with
108✔
1660
      | [] -> typath
90✔
1661
      | l -> Syntax.Type.handle_substitution_params typath (format_params l)
18✔
1662

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

1703
    and include_ (t : Odoc_model.Lang.Include.t) =
1704
      let decl_hidden =
186✔
1705
        match t.decl with
1706
        | Alias p -> Paths.Path.(is_hidden (p :> t))
×
1707
        | ModuleType mty -> umty_hidden mty
186✔
1708
      in
1709
      let status = if decl_hidden then `Inline else t.status in
×
1710

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

1739
  open Module
1740

1741
  module Page : sig
1742
    val compilation_unit : Lang.Compilation_unit.t -> Document.t
1743

1744
    val page : Lang.Page.t -> Document.t
1745

1746
    val source_tree : Lang.SourceTree.t -> Document.t list
1747
  end = struct
1748
    let pack : Lang.Compilation_unit.Packed.t -> Item.t list =
1749
     fun t ->
1750
      let f x =
×
1751
        let id = x.Lang.Compilation_unit.Packed.id in
×
1752
        let modname = Paths.Identifier.name id in
1753
        let md_def =
×
1754
          O.keyword "module" ++ O.txt " " ++ O.txt modname ++ O.txt " = "
×
1755
          ++ Link.from_path (x.path :> Paths.Path.t)
×
1756
        in
1757
        let content = O.documentedSrc md_def in
×
1758
        let anchor =
×
1759
          Utils.option_of_result
1760
          @@ Url.Anchor.from_identifier (id :> Paths.Identifier.t)
×
1761
        in
1762
        let attr = [ "modules" ] in
×
1763
        let doc = [] in
1764
        let decl = { Item.anchor; content; attr; doc; source_anchor = None } in
1765
        Item.Declaration decl
1766
      in
1767
      List.map f t
1768

1769
    let compilation_unit (t : Odoc_model.Lang.Compilation_unit.t) =
1770
      let url = Url.Path.from_identifier t.id in
180✔
1771
      let unit_doc, items =
180✔
1772
        match t.content with
1773
        | Module sign -> signature sign
180✔
1774
        | Pack packed -> ([], pack packed)
×
1775
      in
1776
      let source_anchor =
1777
        match t.source_info with
1778
        | Some src -> Some (Source_page.url src.id)
×
1779
        | None -> None
180✔
1780
      in
1781
      let page = make_expansion_page ~source_anchor url [ unit_doc ] items in
1782
      Document.Page page
180✔
1783

1784
    let page (t : Odoc_model.Lang.Page.t) =
1785
      (*let name =
1786
          match t.name.iv with `Page (_, name) | `LeafPage (_, name) -> name
1787
        in*)
1788
      (*let title = Odoc_model.Names.PageName.to_string name in*)
1789
      let url = Url.Path.from_identifier t.name in
6✔
1790
      let preamble, items = Sectioning.docs t.content in
6✔
1791
      let source_anchor = None in
6✔
1792
      Document.Page { Page.preamble; items; url; source_anchor }
1793

1794
    let source_tree t =
1795
      let dir_pages = t.Odoc_model.Lang.SourceTree.source_children in
×
1796
      let open Paths.Identifier in
1797
      let module Set = Set.Make (SourceDir) in
1798
      let module M = Map.Make (SourceDir) in
1799
      (* mmap is a from a [SourceDir.t] to its [SourceDir.t] and [SourcePage.t]
1800
         children *)
1801
      let mmap =
1802
        let add parent f mmap =
1803
          let old_value =
×
1804
            try M.find parent mmap with Not_found -> (Set.empty, [])
×
1805
          in
1806
          M.add parent (f old_value) mmap
×
1807
        and add_file file (set, lp) = (set, file :: lp)
×
1808
        and add_dir dir (set, lp) = (Set.add dir set, lp) in
×
1809
        let rec dir_ancestors_add dir mmap =
1810
          match dir.iv with
×
1811
          | `SourceDir (parent, _) ->
×
1812
              let mmap = add parent (add_dir dir) mmap in
×
1813
              dir_ancestors_add parent mmap
×
1814
          | `Page _ -> mmap
×
1815
        in
1816
        let file_ancestors_add ({ iv = `SourcePage (parent, _); _ } as file)
1817
            mmap =
1818
          let mmap = add parent (add_file file) mmap in
×
1819
          dir_ancestors_add parent mmap
×
1820
        in
1821
        List.fold_left
×
1822
          (fun mmap file -> file_ancestors_add file mmap)
×
1823
          M.empty dir_pages
1824
      in
1825
      let page_of_dir (dir : SourceDir.t) (dir_children, file_children) =
1826
        let url = Url.Path.from_identifier dir in
×
1827
        let block ?(attr = []) desc = Block.{ attr; desc } in
×
1828
        let inline ?(attr = []) desc = Inline.[ { attr; desc } ] in
×
1829
        let header =
1830
          let title = inline (Text (name dir)) in
×
1831
          Item.Heading
×
1832
            Heading.{ label = None; level = 0; title; source_anchor = None }
1833
        in
1834
        let li ?(attr = []) name url =
×
1835
          let link url desc =
×
1836
            let content = [ Inline.{ attr = []; desc } ] and tooltip = None in
×
1837
            Inline.InternalLink
1838
              { InternalLink.target = Resolved url; content; tooltip }
1839
          in
1840
          [ block ~attr @@ Block.Inline (inline @@ link url (Text name)) ]
×
1841
        in
1842
        let li_of_child child =
1843
          match child with
×
1844
          | { iv = `Page _; _ } ->
1845
              assert false (* No [`Page] is child of a [`SourceDir] *)
1846
          | { iv = `SourceDir (_, name); _ } ->
×
1847
              let url = child |> Url.Path.from_identifier |> Url.from_path in
×
1848
              (name, url)
×
1849
        in
1850
        let li_of_file_child ({ iv = `SourcePage (_, name); _ } as child) =
1851
          let url = child |> Url.Path.from_identifier |> Url.from_path in
×
1852
          (name, url)
×
1853
        in
1854
        let items =
1855
          let text ?(attr = []) desc = Item.Text [ { attr; desc } ] in
×
1856
          let list l = Block.List (Block.Unordered, l) in
×
1857
          let list_of_children =
1858
            let dir_list =
1859
              Set.fold
×
1860
                (fun child acc -> li_of_child child :: acc)
×
1861
                dir_children []
1862
            and file_list =
1863
              List.map (fun child -> li_of_file_child child) file_children
×
1864
            in
1865
            let sort ?(attr = []) l =
×
1866
              l
×
1867
              |> List.sort (fun (n1, _) (n2, _) -> String.compare n1 n2)
×
1868
              |> List.map (fun (name, url) -> li ~attr name url)
×
1869
            in
1870
            sort ~attr:[ "odoc-directory" ] dir_list
×
1871
            @ sort ~attr:[ "odoc-file" ] file_list
×
1872
          in
1873
          header
1874
          :: [ text ~attr:[ "odoc-folder-list" ] @@ list list_of_children ]
×
1875
        in
1876
        Document.Page
1877
          { Types.Page.preamble = []; items; url; source_anchor = None }
1878
      in
1879
      M.fold (fun dir children acc -> page_of_dir dir children :: acc) mmap []
×
1880
  end
1881

1882
  include Page
1883

1884
  let source_page id syntax_info infos source_code =
1885
    Document.Source_page (Source_page.source id syntax_info infos source_code)
×
1886
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