From e4bcbb05d7cc8180f2e6362bb18f1cebff91ab12 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Fri, 22 May 2026 17:16:18 +0200 Subject: [PATCH 1/3] First try to fix issue #865. --- src/parser/syntax.ml | 14 ++++++++++++-- test/model/semantics/test.ml | 8 ++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/parser/syntax.ml b/src/parser/syntax.ml index 454aa20c9d..1614c5b5cf 100644 --- a/src/parser/syntax.ml +++ b/src/parser/syntax.ml @@ -1,3 +1,4 @@ + (* This module is a recursive descent parser for the ocamldoc syntax. The parser consumes a token stream of type [Token.t Stream.t], provided by the lexer, and produces a comment AST of the type defined in [Parser_.Ast]. @@ -167,6 +168,15 @@ type token_that_always_begins_an_inline_element = | `Begin_link_with_replacement_text of string | `Math_span of string ] +let multilines_link_to_link link = + String.split_on_char '\n' link + |> List.map (fun line -> + String.trim @@ + if String.ends_with ~suffix:{|\|} line then + String.(sub line 0 (length line - 1)) + else line) + |> String.concat "" + (* Check that the token constructors above actually are all in [Token.t]. *) let _check_subset : token_that_always_begins_an_inline_element -> Token.t = fun t -> (t :> Token.t) @@ -277,7 +287,7 @@ let rec inline_element : location |> add_warning input; - Loc.at location (`Link (u, [])) + Loc.at location (`Link (multilines_link_to_link u, [])) | `Begin_link_with_replacement_text u as parent_markup -> junk input; @@ -295,7 +305,7 @@ let rec inline_element : input in - `Link (u, content) |> Loc.at (Loc.span [ location; brace_location ]) + `Link ((multilines_link_to_link u), content) |> Loc.at (Loc.span [ location; brace_location ]) (* Consumes tokens that make up a sequence of inline elements that is ended by a '}', a [`Right_brace] token. The brace token is also consumed. diff --git a/test/model/semantics/test.ml b/test/model/semantics/test.ml index a45f592ad3..20cf3cc66b 100644 --- a/test/model/semantics/test.ml +++ b/test/model/semantics/test.ml @@ -690,6 +690,14 @@ let%expect_test _ = {| {"value":[{"`Heading":[{"heading_level":"`Subsection","heading_label_explicit":"false"},{"`Label":[{"`Page":["None","f.ml"]},""]},[{"`Link":["foo",[]]}]]}],"warnings":[]} |}] + let multilines_link_in_markup = + test {|{{:https://github.com/ocaml/\ + odoc/\ +issues/\ + 865}this issue}|}; + [%expect + {| {"value":[{"`Paragraph":[{"`Link":["https://github.com/ocaml/odoc/issues/865",[{"`Word":"this"},"`Space",{"`Word":"issue"}]]}]}],"warnings":["File \"f.ml.mld\":\nPages (.mld files) should start with a heading."]} |}] + let reference_in_markup = test "{2 {!foo}}"; [%expect From c245e0dcde4d57b6a32f675b801ebcd060981551 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Wed, 27 May 2026 12:43:15 +0200 Subject: [PATCH 2/3] Rework the function for escaping link. --- src/parser/syntax.ml | 39 +++++++++++++++++++++++++----------- test/model/semantics/test.ml | 4 ++-- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/parser/syntax.ml b/src/parser/syntax.ml index 1614c5b5cf..02c3d240fd 100644 --- a/src/parser/syntax.ml +++ b/src/parser/syntax.ml @@ -168,14 +168,29 @@ type token_that_always_begins_an_inline_element = | `Begin_link_with_replacement_text of string | `Math_span of string ] -let multilines_link_to_link link = - String.split_on_char '\n' link - |> List.map (fun line -> - String.trim @@ - if String.ends_with ~suffix:{|\|} line then - String.(sub line 0 (length line - 1)) - else line) - |> String.concat "" +let escape_link link = + let link = String.trim link in + let buf = Buffer.create (String.length link) in + let last_state = + String.fold_left + (fun acc chr -> + match (acc, chr) with + | `Char, '\\' -> `Backslash + | `Char, _ -> + Buffer.add_char buf chr; + `Char + | (`Backslash | `Escaping), (' ' | '\t' | '\n') -> `Escaping + | (`Backslash | `Escaping), _ -> + Buffer.add_char buf chr; + `Char) + `Char link + in + let () = + match last_state with + | `Backslash -> Buffer.add_char buf '\\' + | `Escaping | `Char -> () + in + Buffer.contents buf (* Check that the token constructors above actually are all in [Token.t]. *) let _check_subset : token_that_always_begins_an_inline_element -> Token.t = @@ -279,7 +294,7 @@ let rec inline_element : | `Simple_link u -> junk input; - let u = String.trim u in + let u = escape_link u |> String.trim in if u = "" then Parse_error.should_not_be_empty @@ -287,11 +302,11 @@ let rec inline_element : location |> add_warning input; - Loc.at location (`Link (multilines_link_to_link u, [])) + Loc.at location (`Link (u, [])) | `Begin_link_with_replacement_text u as parent_markup -> junk input; - let u = String.trim u in + let u = escape_link u |> String.trim in if u = "" then Parse_error.should_not_be_empty @@ -305,7 +320,7 @@ let rec inline_element : input in - `Link ((multilines_link_to_link u), content) |> Loc.at (Loc.span [ location; brace_location ]) + `Link (u, content) |> Loc.at (Loc.span [ location; brace_location ]) (* Consumes tokens that make up a sequence of inline elements that is ended by a '}', a [`Right_brace] token. The brace token is also consumed. diff --git a/test/model/semantics/test.ml b/test/model/semantics/test.ml index 20cf3cc66b..e9c739b5b6 100644 --- a/test/model/semantics/test.ml +++ b/test/model/semantics/test.ml @@ -694,9 +694,9 @@ let%expect_test _ = test {|{{:https://github.com/ocaml/\ odoc/\ issues/\ - 865}this issue}|}; + 865\ }this issue}|}; [%expect - {| {"value":[{"`Paragraph":[{"`Link":["https://github.com/ocaml/odoc/issues/865",[{"`Word":"this"},"`Space",{"`Word":"issue"}]]}]}],"warnings":["File \"f.ml.mld\":\nPages (.mld files) should start with a heading."]} |}] + {| {"value":[{"`Paragraph":[{"`Link":["https://github.com/ocaml/odoc/issues/865\\",[{"`Word":"this"},"`Space",{"`Word":"issue"}]]}]}],"warnings":["File \"f.ml.mld\":\nPages (.mld files) should start with a heading."]} |}] let reference_in_markup = test "{2 {!foo}}"; From 22a5733875c2aa50d2eb1e50a9671d351548106c Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Wed, 27 May 2026 13:11:50 +0200 Subject: [PATCH 3/3] Add a change entry. --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 71bae9a507..6dec72b664 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,11 @@ # Unreleased +### Added - Support for OxCaml unboxed named types (@art-w, #1407) +### Fixed +- Allow to break link into multiline (@Tim-ats-d, #1439) + # 3.2.1 ### Fixed