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

ocaml / odoc / 2931

17 Mar 2025 07:06PM UTC coverage: 73.263% (-0.1%) from 73.409%
2931

push

github

jonludlam
OCaml 4.02 compat

10388 of 14179 relevant lines covered (73.26%)

9820.52 hits per line

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

82.43
/src/document/comment.ml
1
(*
2
 * Copyright (c) 2016, 2017 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 Types
18
module Comment = Odoc_model.Comment
19
open Odoc_model.Names
20

21
let default_lang_tag = "ocaml"
22

23
let source_of_code s =
24
  if s = "" then [] else [ Source.Elt [ inline @@ Inline.Text s ] ]
×
25

26
module Reference = struct
27
  open Odoc_model.Paths
28

29
  let rec render_resolved : Reference.Resolved.t -> string =
30
   fun r ->
31
    let open Reference.Resolved in
1,666✔
32
    match r with
33
    | `Identifier id -> Identifier.name id
971✔
34
    | `Alias (_, r) -> render_resolved (r :> t)
216✔
35
    | `AliasModuleType (_, r) -> render_resolved (r :> t)
47✔
36
    | `Module (r, s) -> render_resolved (r :> t) ^ "." ^ ModuleName.to_string s
87✔
37
    | `Hidden p -> render_resolved (p :> t)
×
38
    | `ModuleType (r, s) ->
36✔
39
        render_resolved (r :> t) ^ "." ^ ModuleTypeName.to_string s
36✔
40
    | `Type (r, s) -> render_resolved (r :> t) ^ "." ^ TypeName.to_string s
66✔
41
    | `Constructor (r, s) ->
5✔
42
        render_resolved (r :> t) ^ "." ^ ConstructorName.to_string s
5✔
43
    | `PolyConstructor (r, s) ->
8✔
44
        render_resolved (r :> t) ^ ".`" ^ ConstructorName.to_string s
8✔
45
    | `Field (r, s) -> render_resolved (r :> t) ^ "." ^ FieldName.to_string s
×
46
    | `Extension (r, s) ->
3✔
47
        render_resolved (r :> t) ^ "." ^ ExtensionName.to_string s
3✔
48
    | `ExtensionDecl (r, _, s) ->
2✔
49
        render_resolved (r :> t) ^ "." ^ ExtensionName.to_string s
2✔
50
    | `Exception (r, s) ->
×
51
        render_resolved (r :> t) ^ "." ^ ExceptionName.to_string s
×
52
    | `Value (r, s) -> render_resolved (r :> t) ^ "." ^ ValueName.to_string s
183✔
53
    | `Class (r, s) -> render_resolved (r :> t) ^ "." ^ TypeName.to_string s
×
54
    | `ClassType (r, s) -> render_resolved (r :> t) ^ "." ^ TypeName.to_string s
×
55
    | `Method (r, s) ->
×
56
        (* CR trefis: do we really want to print anything more than [s] here?  *)
57
        render_resolved (r :> t) ^ "." ^ MethodName.to_string s
×
58
    | `InstanceVariable (r, s) ->
×
59
        (* CR trefis: the following makes no sense to me... *)
60
        render_resolved (r :> t) ^ "." ^ InstanceVariableName.to_string s
×
61
    | `Label (_, s) -> LabelName.to_string s
42✔
62

63
  let render_path (tag, cs) =
64
    let tag =
6✔
65
      match tag with
66
      | `TRelativePath -> "./"
6✔
67
      | `TAbsolutePath -> "/"
×
68
      | `TCurrentPackage -> "//"
×
69
    in
70
    tag ^ String.concat "/" cs
6✔
71

72
  let rec render_unresolved : Reference.t -> string =
73
    let open Reference in
74
    function
75
    | `Resolved r -> render_resolved r
6✔
76
    | `Root (n, _) -> n
51✔
77
    | `Dot (p, f) -> render_unresolved (p :> t) ^ "." ^ f
27✔
78
    | `Page_path p -> render_path p
×
79
    | `Asset_path p -> render_path p
6✔
80
    | `Module_path p -> render_path p
×
81
    | `Any_path p -> render_path p
×
82
    | `Module (p, f) ->
×
83
        render_unresolved (p :> t) ^ "." ^ ModuleName.to_string f
×
84
    | `ModuleType (p, f) ->
×
85
        render_unresolved (p :> t) ^ "." ^ ModuleTypeName.to_string f
×
86
    | `Type (p, f) -> render_unresolved (p :> t) ^ "." ^ TypeName.to_string f
×
87
    | `Constructor (p, f) ->
1✔
88
        render_unresolved (p :> t) ^ "." ^ ConstructorName.to_string f
1✔
89
    | `Field (p, f) -> render_unresolved (p :> t) ^ "." ^ FieldName.to_string f
×
90
    | `Extension (p, f) ->
×
91
        render_unresolved (p :> t) ^ "." ^ ExtensionName.to_string f
×
92
    | `ExtensionDecl (p, f) ->
×
93
        render_unresolved (p :> t) ^ "." ^ ExtensionName.to_string f
×
94
    | `Exception (p, f) ->
×
95
        render_unresolved (p :> t) ^ "." ^ ExceptionName.to_string f
×
96
    | `Value (p, f) -> render_unresolved (p :> t) ^ "." ^ ValueName.to_string f
×
97
    | `Class (p, f) -> render_unresolved (p :> t) ^ "." ^ TypeName.to_string f
×
98
    | `ClassType (p, f) ->
×
99
        render_unresolved (p :> t) ^ "." ^ TypeName.to_string f
×
100
    | `Method (p, f) ->
×
101
        render_unresolved (p :> t) ^ "." ^ MethodName.to_string f
×
102
    | `InstanceVariable (p, f) ->
×
103
        render_unresolved (p :> t) ^ "." ^ InstanceVariableName.to_string f
×
104
    | `Label (p, f) -> render_unresolved (p :> t) ^ "." ^ LabelName.to_string f
×
105

106
  (* This is the entry point. *)
107
  let to_ir : ?text:Inline.t -> Reference.t -> Inline.t =
108
   fun ?text ref ->
109
    match ref with
1,058✔
110
    | `Resolved r ->
1,007✔
111
        (* IDENTIFIER MUST BE RENAMED TO DEFINITION. *)
112
        let id = Reference.Resolved.identifier r in
113
        let rendered = render_resolved r in
1,007✔
114
        let content =
1,007✔
115
          match text with
116
          | None -> [ inline @@ Inline.Source (source_of_code rendered) ]
742✔
117
          | Some s -> s
265✔
118
        and tooltip =
119
          (* Add a tooltip if the content is not the rendered reference. *)
120
          match text with
121
          | None -> None
742✔
122
          | Some _ -> Some rendered
265✔
123
        in
124
        let url = Url.from_identifier ~stop_before:false id in
125
        let target = Target.Internal (Resolved url) in
1,007✔
126
        let link = { Link.target; content; tooltip } in
127
        [ inline @@ Inline.Link link ]
1,007✔
128
    | _ -> (
51✔
129
        let s = render_unresolved ref in
130
        match text with
51✔
131
        | None ->
50✔
132
            let s = source_of_code s in
133
            [ inline @@ Inline.Source s ]
50✔
134
        | Some content ->
1✔
135
            let link =
136
              { Link.target = Internal Unresolved; content; tooltip = Some s }
137
            in
138
            [ inline @@ Inline.Link link ])
1✔
139
end
140

141
let leaf_inline_element : Comment.leaf_inline_element -> Inline.one = function
142
  | `Space -> inline @@ Text " "
396,615✔
143
  | `Word s -> inline @@ Text s
385,992✔
144
  | `Code_span s -> inline @@ Source (source_of_code s)
62,126✔
145
  | `Math_span s -> inline @@ Math s
12✔
146
  | `Raw_markup (target, s) -> inline @@ Raw_markup (target, s)
12✔
147

148
let rec non_link_inline_element : Comment.non_link_inline_element -> Inline.one
149
    = function
150
  | #Comment.leaf_inline_element as e -> leaf_inline_element e
1,400✔
151
  | `Styled (style, content) ->
72✔
152
      inline @@ Styled (style, non_link_inline_element_list content)
72✔
153

154
and non_link_inline_element_list : _ -> Inline.t =
155
 fun elements ->
156
  List.map
460✔
157
    (fun elt -> non_link_inline_element elt.Odoc_model.Location_.value)
1,472✔
158
    elements
159

160
let link_content = non_link_inline_element_list
161

162
let rec inline_element : Comment.inline_element -> Inline.t = function
163
  | #Comment.leaf_inline_element as e -> [ leaf_inline_element e ]
843,357✔
164
  | `Styled (style, content) ->
385✔
165
      [ inline @@ Styled (style, inline_element_list content) ]
385✔
166
  | `Reference (path, content) ->
998✔
167
      (* TODO Rework that ugly function. *)
168
      (* TODO References should be set in code style, if they are to code
169
              elements. *)
170
      let content =
171
        match content with
172
        | [] -> None
732✔
173
        | _ -> Some (non_link_inline_element_list content)
266✔
174
        (* XXX Span *)
175
      in
176
      Reference.to_ir ?text:content path
177
  | `Link (target, content) ->
117✔
178
      let content =
179
        match content with
180
        | [] -> [ inline @@ Text target ]
25✔
181
        | _ -> non_link_inline_element_list content
92✔
182
      in
183
      [ inline @@ Link { target = External target; content; tooltip = None } ]
117✔
184

185
and inline_element_list elements =
186
  List.concat
29,111✔
187
  @@ List.map
29,111✔
188
       (fun elt -> inline_element elt.Odoc_model.Location_.value)
844,833✔
189
       elements
190

191
let module_references ms =
192
  let module_reference (m : Comment.module_reference) =
30✔
193
    let reference =
60✔
194
      Reference.to_ir (m.module_reference :> Odoc_model.Paths.Reference.t)
60✔
195
    and synopsis =
196
      match m.module_synopsis with
197
      | Some synopsis ->
18✔
198
          [
199
            block ~attr:[ "synopsis" ] @@ Inline (inline_element_list synopsis);
18✔
200
          ]
201
      | None -> []
42✔
202
    in
203
    { Description.attr = []; key = reference; definition = synopsis }
204
  in
205
  let items = List.map module_reference ms in
206
  block ~attr:[ "modules" ] @@ Description items
30✔
207

208
let rec nestable_block_element :
209
    Comment.nestable_block_element -> Block.one list =
210
 fun content ->
211
  match content with
28,536✔
212
  | `Paragraph p -> [ paragraph p ]
26,391✔
213
  | `Code_block (lang_tag, code, outputs) ->
1,034✔
214
      let lang_tag =
215
        match lang_tag with None -> default_lang_tag | Some t -> t
×
216
      in
217
      let rest =
218
        match outputs with
219
        | Some xs -> nestable_block_element_list xs
×
220
        | None -> []
1,034✔
221
      in
222
      [
223
        block
1,034✔
224
        @@ Source (lang_tag, source_of_code (Odoc_model.Location_.value code));
1,034✔
225
      ]
226
      @ rest
227
  | `Math_block s -> [ block @@ Math s ]
6✔
228
  | `Verbatim s -> [ block @@ Verbatim s ]
528✔
229
  | `Modules ms -> [ module_references ms ]
30✔
230
  | `List (kind, items) ->
478✔
231
      let kind =
232
        match kind with
233
        | `Unordered -> Block.Unordered
445✔
234
        | `Ordered -> Block.Ordered
33✔
235
      in
236
      let f = function
237
        | [ { Odoc_model.Location_.value = `Paragraph content; _ } ] ->
1,017✔
238
            [ block @@ Block.Inline (inline_element_list content) ]
1,017✔
239
        | item -> nestable_block_element_list item
66✔
240
      in
241
      let items = List.map f items in
242
      [ block @@ Block.List (kind, items) ]
478✔
243
  | `Table { data; align } ->
30✔
244
      let data =
245
        List.map
246
          (List.map (fun (cell, cell_type) ->
30✔
247
               (nestable_block_element_list cell, cell_type)))
192✔
248
          data
249
      in
250
      let generate_align data =
30✔
251
        let max (a : int) b = if a < b then b else a in
18✔
252
        (* Length of the longest line of the table *)
253
        let max_length =
254
          List.fold_left (fun m l -> max m (List.length l)) 0 data
36✔
255
        in
256
        let rec list_init i =
18✔
257
          if i <= 0 then [] else Table.Default :: list_init (i - 1)
18✔
258
        in
259
        list_init max_length
260
      in
261
      let align =
262
        match align with
263
        | None -> generate_align data
18✔
264
        | Some align ->
12✔
265
            List.map
12✔
266
              (function
267
                | None -> Table.Default
12✔
268
                | Some `Right -> Right
12✔
269
                | Some `Left -> Left
12✔
270
                | Some `Center -> Center)
12✔
271
              align
272
        (* We should also check wellness of number of table cells vs alignment,
273
           and raise warnings *)
274
      in
275
      [ block @@ Table { data; align } ]
30✔
276
  | `Media (href, media, content) ->
39✔
277
      let content =
278
        match (content, href) with
279
        | "", `Reference path ->
12✔
280
            Reference.render_unresolved (path :> Comment.Reference.t)
12✔
281
        | "", `Link href -> href
12✔
282
        | _ -> content
15✔
283
      in
284
      let url =
285
        match href with
286
        | `Reference (`Resolved r) -> (
9✔
287
            let id =
288
              Odoc_model.Paths.Reference.Resolved.Asset.(identifier (r :> t))
9✔
289
            in
290
            match Url.from_asset_identifier id with
291
            | url -> Target.Internal (Resolved url))
9✔
292
        | `Reference _ -> Internal Unresolved
9✔
293
        | `Link href -> External href
21✔
294
      in
295
      let i =
296
        match media with
297
        | `Audio -> Block.Audio (url, content)
9✔
298
        | `Video -> Video (url, content)
9✔
299
        | `Image -> Image (url, content)
21✔
300
      in
301
      [ block i ]
39✔
302

303
and paragraph : Comment.paragraph -> Block.one = function
304
  | [ { value = `Raw_markup (target, s); _ } ] ->
6✔
305
      block @@ Block.Raw_markup (target, s)
306
  | p -> block @@ Block.Paragraph (inline_element_list p)
26,950✔
307

308
and nestable_block_element_list :
309
    Comment.nestable_block_element Comment.with_location list -> Block.one list
310
    =
311
 fun elements ->
312
  elements
417✔
313
  |> List.map Odoc_model.Location_.value
314
  |> List.map nestable_block_element
417✔
315
  |> List.concat
417✔
316

317
let tag : Comment.tag -> Description.one =
318
 fun t ->
319
  let sp = inline (Text " ") in
255✔
320
  let item ?value ~tag definition =
255✔
321
    let tag_name = inline ~attr:[ "at-tag" ] (Text tag) in
255✔
322
    let tag_value = match value with None -> [] | Some t -> sp :: t in
117✔
323
    let key = tag_name :: tag_value in
324
    { Description.attr = [ tag ]; key; definition }
325
  in
326
  let mk_value desc = [ inline ~attr:[ "value" ] desc ] in
93✔
327
  let text_def s = [ block (Block.Inline [ inline @@ Text s ]) ] in
36✔
328
  let content_to_inline ?(prefix = []) content =
48✔
329
    match content with
60✔
330
    | None -> []
18✔
331
    | Some content -> prefix @ [ inline @@ Text content ]
42✔
332
  in
333
  match t with
334
  | `Author s -> item ~tag:"author" (text_def s)
12✔
335
  | `Deprecated content ->
24✔
336
      item ~tag:"deprecated" (nestable_block_element_list content)
24✔
337
  | `Param (name, content) ->
18✔
338
      let value = mk_value (Inline.Text name) in
339
      item ~tag:"parameter" ~value (nestable_block_element_list content)
18✔
340
  | `Raise (kind, content) ->
24✔
341
      let value = inline_element (kind :> Comment.inline_element) in
342
      item ~tag:"raises" ~value (nestable_block_element_list content)
24✔
343
  | `Return content -> item ~tag:"returns" (nestable_block_element_list content)
18✔
344
  | `See (kind, target, content) ->
42✔
345
      let value =
346
        match kind with
347
        | `Url ->
12✔
348
            mk_value
12✔
349
              (Inline.Link
350
                 {
351
                   target = External target;
352
                   content = [ inline @@ Text target ];
12✔
353
                   tooltip = None;
354
                 })
355
        | `File -> mk_value (Inline.Source (source_of_code target))
12✔
356
        | `Document -> mk_value (Inline.Text target)
18✔
357
      in
358
      item ~tag:"see" ~value (nestable_block_element_list content)
42✔
359
  | `Since s -> item ~tag:"since" (text_def s)
12✔
360
  | `Before (version, content) ->
33✔
361
      let value = mk_value (Inline.Text version) in
362
      item ~tag:"before" ~value (nestable_block_element_list content)
33✔
363
  | `Version s -> item ~tag:"version" (text_def s)
12✔
364
  | `Alert ("deprecated", content) ->
48✔
365
      let content = content_to_inline content in
366
      item ~tag:"deprecated" [ block (Block.Inline content) ]
48✔
367
  | `Alert (tag, content) ->
12✔
368
      let content = content_to_inline ~prefix:[ sp ] content in
369
      item ~tag:"alert"
12✔
370
        [ block (Block.Inline ([ inline @@ Text tag ] @ content)) ]
12✔
371

372
let attached_block_element : Comment.attached_block_element -> Block.t =
373
  function
374
  | #Comment.nestable_block_element as e -> nestable_block_element e
28,032✔
375
  | `Tag t -> [ block ~attr:[ "at-tags" ] @@ Description [ tag t ] ]
255✔
376

377
(* TODO collaesce tags *)
378

379
let block_element : Comment.block_element -> Block.t = function
380
  | #Comment.attached_block_element as e -> attached_block_element e
26,621✔
381
  | `Heading (_, _, text) ->
76✔
382
      (* We are not supposed to receive Heading in this context.
383
         TODO: Remove heading in attached documentation in the model *)
384
      [ block @@ Paragraph (inline_element_list text) ]
76✔
385

386
let heading_level_to_int = function
387
  | `Title -> 0
66✔
388
  | `Section -> 1
400✔
389
  | `Subsection -> 2
97✔
390
  | `Subsubsection -> 3
84✔
391
  | `Paragraph -> 4
12✔
392
  | `Subparagraph -> 5
6✔
393

394
let heading
395
    (attrs, { Odoc_model.Paths.Identifier.iv = `Label (_, label); _ }, text) =
396
  let label = Odoc_model.Names.LabelName.to_string label in
665✔
397
  let title = inline_element_list text in
665✔
398
  let level = heading_level_to_int attrs.Comment.heading_level in
665✔
399
  let label = Some label in
665✔
400
  let source_anchor = None in
401
  Item.Heading { label; level; title; source_anchor }
402

403
let item_element : Comment.block_element -> Item.t list = function
404
  | #Comment.attached_block_element as e ->
710✔
405
      [ Item.Text (attached_block_element e) ]
710✔
406
  | `Heading h -> [ heading h ]
24✔
407

408
(** The documentation of the expansion is used if there is no comment attached
409
    to the declaration. *)
410
let synopsis ~decl_doc ~expansion_doc =
411
  let ([], Some docs | docs, _) = (decl_doc, expansion_doc) in
2,648✔
412
  match Comment.synopsis docs with Some p -> [ paragraph p ] | None -> []
565✔
413

414
let standalone docs =
415
  Utils.flatmap ~f:item_element
5,148✔
416
  @@ List.map (fun x -> x.Odoc_model.Location_.value) docs
734✔
417

418
let to_ir (docs : Comment.elements) =
419
  Utils.flatmap ~f:block_element
24,786✔
420
  @@ List.map (fun x -> x.Odoc_model.Location_.value) docs
24,786✔
421

422
let has_doc docs = docs <> []
900✔
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