Skip to content

Commit 5a741da

Browse files
authored
Merge pull request #942 from EmileTrotignon/fix-342
Fixes issue #342 : links and references are now allowed in headings. They are displayed as regular text inside the table of content, as the headings here are already links, and nesting links is a bad idea.
2 parents 519b740 + b3ddfdb commit 5a741da

File tree

10 files changed

+119
-30
lines changed

10 files changed

+119
-30
lines changed

src/document/comment.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ let block_element : Comment.block_element -> Block.t = function
332332
| `Heading (_, _, text) ->
333333
(* We are not supposed to receive Heading in this context.
334334
TODO: Remove heading in attached documentation in the model *)
335-
[ block @@ Paragraph (non_link_inline_element_list text) ]
335+
[ block @@ Paragraph (inline_element_list text) ]
336336

337337
let heading_level_to_int = function
338338
| `Title -> 0
@@ -345,7 +345,7 @@ let heading_level_to_int = function
345345
let heading
346346
(attrs, { Odoc_model.Paths.Identifier.iv = `Label (_, label); _ }, text) =
347347
let label = Odoc_model.Names.LabelName.to_string label in
348-
let title = non_link_inline_element_list text in
348+
let title = inline_element_list text in
349349
let level = heading_level_to_int attrs.Comment.heading_level in
350350
let label = Some label in
351351
let source_anchor = None in

src/document/doctree.ml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,27 @@ end = struct
5555
type t = one list
5656

5757
and one = { url : Url.t; text : Inline.t; children : t }
58+
let rec remove_links l =
59+
let open Inline in
60+
l
61+
|> List.map (fun one ->
62+
let return desc = [ { one with desc } ] in
63+
match one.desc with
64+
| Text _ as t -> return t
65+
| Entity _ as t -> return t
66+
| Linebreak as t -> return t
67+
| Styled (st, content) -> return (Styled (st, remove_links content))
68+
| Link (_, t) -> t
69+
| InternalLink { target = Resolved _; content = t; _ } -> t
70+
| InternalLink { target = Unresolved; content = t; _ } -> t
71+
| Source l ->
72+
let rec f = function
73+
| Source.Elt t -> Source.Elt (remove_links t)
74+
| Tag (tag, t) -> Tag (tag, List.map f t)
75+
in
76+
return @@ Source (List.map f l)
77+
| (Math _ | Raw_markup _) as t -> return t)
78+
|> List.concat
5879

5980
let classify ~on_sub (i : Item.t) : _ Rewire.action =
6081
match i with
@@ -63,6 +84,7 @@ end = struct
6384
if on_sub status then Rec content else Skip
6485
| Heading { label = None; _ } -> Skip
6586
| Heading { label = Some label; level; title; _ } ->
87+
let title = remove_links title in
6688
Heading ((label, title), level)
6789

6890
let node mkurl (anchor, text) children =

src/model/comment.ml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ type heading_attrs = {
8282

8383
type block_element =
8484
[ nestable_block_element
85-
| `Heading of heading_attrs * Identifier.Label.t * link_content
85+
| `Heading of
86+
heading_attrs * Identifier.Label.t * inline_element with_location list
8687
| `Tag of tag ]
8788

8889
type docs = block_element with_location list
@@ -94,3 +95,17 @@ type docs_or_stop = [ `Docs of docs | `Stop ]
9495
let synopsis = function
9596
| { Location_.value = `Paragraph p; _ } :: _ -> Some p
9697
| _ -> None
98+
99+
let rec link_content_of_inline_element :
100+
inline_element with_location -> link_content =
101+
fun x ->
102+
let v = x.Location_.value in
103+
match v with
104+
| #leaf_inline_element as e -> [ { x with value = e } ]
105+
| `Reference (_, r) -> r
106+
| `Link (_, l) -> l
107+
| `Styled (st, elems) ->
108+
[ { x with value = `Styled (st, link_content_of_inline_elements elems) } ]
109+
110+
and link_content_of_inline_elements l =
111+
l |> List.map link_content_of_inline_element |> List.concat

src/model/semantics.ml

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,7 @@ let leaf_inline_element :
150150
| Some target -> Location.same element (`Raw_markup (target, s)))
151151

152152
type surrounding =
153-
[ `Heading of
154-
int
155-
* string option
156-
* Odoc_parser.Ast.inline_element Location_.with_location list
157-
| `Link of
153+
[ `Link of
158154
string * Odoc_parser.Ast.inline_element Location_.with_location list
159155
| `Reference of
160156
[ `Simple | `With_text ]
@@ -290,7 +286,8 @@ let tag :
290286
291287
This must be done in the parser (i.e. early, not at HTML/other output
292288
generation time), so that the cross-referencer can see these anchors. *)
293-
let generate_heading_label : Comment.link_content -> string =
289+
let generate_heading_label : Comment.inline_element with_location list -> string
290+
=
294291
fun content ->
295292
(* Code spans can contain spaces, so we need to replace them with hyphens. We
296293
also lowercase all the letters, for consistency with the rest of this
@@ -308,13 +305,14 @@ let generate_heading_label : Comment.link_content -> string =
308305
Bytes.unsafe_to_string result
309306
in
310307

308+
let strip_locs li = List.map (fun ele -> ele.Location.value) li in
311309
(* Perhaps this should be done using a [Buffer.t]; we can switch to that as
312310
needed. *)
313311
let rec scan_inline_elements anchor = function
314312
| [] -> anchor
315313
| element :: more ->
316314
let anchor =
317-
match element.Location.value with
315+
match (element : Comment.inline_element) with
318316
| `Space -> anchor ^ "-"
319317
| `Word w -> anchor ^ Astring.String.Ascii.lowercase w
320318
| `Code_span c | `Math_span c ->
@@ -323,11 +321,22 @@ let generate_heading_label : Comment.link_content -> string =
323321
(* TODO Perhaps having raw markup in a section heading should be an
324322
error? *)
325323
anchor
326-
| `Styled (_, content) -> scan_inline_elements anchor content
324+
| `Styled (_, content) ->
325+
content |> strip_locs |> scan_inline_elements anchor
326+
| `Reference (_, content) ->
327+
content |> strip_locs
328+
|> List.map (fun (ele : Comment.non_link_inline_element) ->
329+
(ele :> Comment.inline_element))
330+
|> scan_inline_elements anchor
331+
| `Link (_, content) ->
332+
content |> strip_locs
333+
|> List.map (fun (ele : Comment.non_link_inline_element) ->
334+
(ele :> Comment.inline_element))
335+
|> scan_inline_elements anchor
327336
in
328337
scan_inline_elements anchor more
329338
in
330-
scan_inline_elements "" content
339+
content |> List.map (fun ele -> ele.Location.value) |> scan_inline_elements ""
331340

332341
let section_heading :
333342
status ->
@@ -338,11 +347,7 @@ let section_heading :
338347
fun status ~top_heading_level location heading ->
339348
let (`Heading (level, label, content)) = heading in
340349

341-
let text =
342-
non_link_inline_elements status
343-
~surrounding:(heading :> surrounding)
344-
content
345-
in
350+
let text = inline_elements status content in
346351

347352
let heading_label_explicit, label =
348353
match label with

src/xref2/component.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ and Label : sig
477477
type t = {
478478
attrs : Odoc_model.Comment.heading_attrs;
479479
label : Ident.label;
480-
text : Odoc_model.Comment.link_content;
480+
text : Odoc_model.Comment.paragraph;
481481
location : Odoc_model.Location_.span;
482482
}
483483
end =

src/xref2/component.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ and Label : sig
446446
type t = {
447447
attrs : Odoc_model.Comment.heading_attrs;
448448
label : Ident.label;
449-
text : Odoc_model.Comment.link_content;
449+
text : Odoc_model.Comment.paragraph;
450450
location : Odoc_model.Location_.span;
451451
}
452452
end

src/xref2/link.ml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,9 @@ let rec comment_inline_element :
212212
match (content, x) with
213213
| [], `Identifier ({ iv = #Id.Label.t_pv; _ } as i) -> (
214214
match Env.lookup_by_id Env.s_label i env with
215-
| Some (`Label (_, lbl)) -> lbl.Component.Label.text
215+
| Some (`Label (_, lbl)) ->
216+
Odoc_model.Comment.link_content_of_inline_elements
217+
lbl.Component.Label.text
216218
| None -> [])
217219
| content, _ -> content
218220
in
@@ -292,9 +294,14 @@ and comment_block_element env parent ~loc (x : Comment.block_element) =
292294
| #Comment.nestable_block_element as x ->
293295
(comment_nestable_block_element env parent ~loc x
294296
:> Comment.block_element)
295-
| `Heading h as x ->
297+
| `Heading (attrs, label, elems) ->
298+
let cie = comment_inline_element env in
299+
let elems =
300+
List.rev_map (fun ele -> with_location cie ele) elems |> List.rev
301+
in
302+
let h = (attrs, label, elems) in
296303
check_ambiguous_label ~loc env h;
297-
x
304+
`Heading h
298305
| `Tag t -> `Tag (comment_tag env parent ~loc t)
299306

300307
and with_location :

test/model/semantics/test.ml

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,13 +1823,11 @@ let%expect_test _ =
18231823
"`Heading": [
18241824
{ "heading_level": "`Subsection", "heading_label_explicit": "false" },
18251825
{ "`Label": [ { "`Page": [ "None", "f.ml" ] }, "" ] },
1826-
[ { "`Styled": [ "`Emphasis", [] ] } ]
1826+
[ { "`Link": [ "foo", [] ] } ]
18271827
]
18281828
}
18291829
],
1830-
"warnings": [
1831-
"File \"f.ml\", line 1, characters 3-11:\n'{{:...} ...}' (external link) is not allowed in '{2 ...}' (section heading)."
1832-
]
1830+
"warnings": []
18331831
} |}]
18341832

18351833
let reference_in_markup =
@@ -1842,13 +1840,11 @@ let%expect_test _ =
18421840
"`Heading": [
18431841
{ "heading_level": "`Subsection", "heading_label_explicit": "false" },
18441842
{ "`Label": [ { "`Page": [ "None", "f.ml" ] }, "" ] },
1845-
[ { "`Styled": [ "`Emphasis", [] ] } ]
1843+
[ { "`Reference": [ { "`Root": [ "foo", "`TUnknown" ] }, [] ] } ]
18461844
]
18471845
}
18481846
],
1849-
"warnings": [
1850-
"File \"f.ml\", line 1, characters 3-9:\n'{!...}' (cross-reference) is not allowed in '{2 ...}' (section heading)."
1851-
]
1847+
"warnings": []
18521848
} |}]
18531849

18541850
let two =

test/xref2/github_issue_342.t/foo.mli

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module A : sig end
2+
3+
(** {1 References {!A} and {{!A}with text} in title}
4+
5+
{1 An url {:http://ocaml.org} and {{:http://ocaml.org}with text} in a title}
6+
7+
*)
8+

test/xref2/github_issue_342.t/run.t

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
A quick test to repro the issue found in #342
2+
3+
$ ocamlc -bin-annot -c foo.mli
4+
5+
$ odoc compile foo.cmti
6+
$ odoc link foo.odoc
7+
8+
$ odoc html-generate --indent -o html/ foo.odocl
9+
10+
The table of content:
11+
12+
$ cat html/Foo/index.html | grep "odoc-toc" -A 9
13+
<nav class="odoc-toc">
14+
<ul>
15+
<li>
16+
<a href="#references--and-with-text-in-title">References <code>A</code>
17+
and with text in title
18+
</a>
19+
</li>
20+
<li>
21+
<a href="#an-url--and-with-text-in-a-title">An url http://ocaml.org
22+
and with text in a title
23+
24+
The rendered headings
25+
26+
$ cat html/Foo/index.html | grep "<h2" -A 3
27+
<h2 id="references--and-with-text-in-title">
28+
<a href="#references--and-with-text-in-title" class="anchor"></a>
29+
References <a href="A/index.html"><code>A</code></a> and
30+
<a href="A/index.html" title="A">with text</a> in title
31+
--
32+
<h2 id="an-url--and-with-text-in-a-title">
33+
<a href="#an-url--and-with-text-in-a-title" class="anchor"></a>An
34+
url <a href="http://ocaml.org">http://ocaml.org</a> and
35+
<a href="http://ocaml.org">with text</a> in a title
36+

0 commit comments

Comments
 (0)