Skip to content

Commit d23b06b

Browse files
authored
Merge pull request #389 from NathanReb/report-all-syntax-errors
Report all syntax errors in Markdown files
2 parents 4d8e7e8 + 8eaedb5 commit d23b06b

File tree

27 files changed

+286
-155
lines changed

27 files changed

+286
-155
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#### Added
44

5+
- Report all parsing errors in Markdown files (#389, @NathanReb)
6+
57
#### Changed
68

79
#### Deprecated
@@ -11,6 +13,7 @@
1113
- Fixed compatibility with Cmdliner 1.1.0 (#371, @Leonidas-from-XIV)
1214
- Report errors and exit codes of toplevel directives (#382, @talex5,
1315
@Leonidas-from-XIV)
16+
- Fix block locations in error reporting (#389, @NathanReb)
1417

1518
#### Removed
1619

bin/test/main.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ let report_error_in_block block msg =
5252
| Cram _ -> "cram "
5353
| Toplevel _ -> "toplevel "
5454
in
55-
Fmt.epr "%a: Error in the %scode block@]\n%s\n"
56-
Stable_printer.Location.print_loc block.loc kind msg
55+
Fmt.epr "%a: Error in the %scode block@]\n%s\n" Stable_printer.Location.pp
56+
block.loc kind msg
5757

5858
let run setup non_deterministic silent_eval record_backtrace syntax silent
5959
verbose_findlib prelude prelude_str file section root force_output output :

lib/block.ml

Lines changed: 97 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@
1616

1717
open Util.Result.Infix
1818

19+
let loc_error ~loc fmt =
20+
Format.kasprintf
21+
(fun s -> Error (`Msg s))
22+
("%a: invalid code block: " ^^ fmt)
23+
Stable_printer.Location.pp loc
24+
25+
let locate_error_msg ~loc s =
26+
Format.asprintf "%a: invalid code block: %s" Stable_printer.Location.pp loc s
27+
28+
let locate_errors ~loc r =
29+
Result.map_error
30+
(fun l -> List.map (fun (`Msg m) -> `Msg (locate_error_msg ~loc m)) l)
31+
r
32+
1933
module Header = struct
2034
type t = Shell of [ `Sh | `Bash ] | OCaml | Other of string
2135

@@ -41,6 +55,26 @@ module Header = struct
4155
end
4256

4357
type section = int * string
58+
59+
module Raw = struct
60+
type t =
61+
| Include of { loc : Location.t; section : section option; labels : string }
62+
| Any of {
63+
loc : Location.t;
64+
section : section option;
65+
header : string;
66+
contents : string list;
67+
label_cmt : string option;
68+
legacy_labels : string;
69+
errors : Output.t list;
70+
}
71+
72+
let make ~loc ~section ~header ~contents ~label_cmt ~legacy_labels ~errors =
73+
Any { loc; section; header; contents; label_cmt; legacy_labels; errors }
74+
75+
let make_include ~loc ~section ~labels = Include { loc; section; labels }
76+
end
77+
4478
type cram_value = { language : [ `Sh | `Bash ]; non_det : Label.non_det option }
4579

4680
type ocaml_value = {
@@ -104,7 +138,7 @@ let dump ppf ({ loc; section; labels; contents; value; _ } as b) =
104138
Fmt.pf ppf
105139
"{@[loc: %a;@ section: %a;@ labels: %a;@ header: %a;@ contents: %a;@ \
106140
value: %a@]}"
107-
Stable_printer.Location.print_loc loc
141+
Stable_printer.Location.pp loc
108142
Fmt.(Dump.option dump_section)
109143
section
110144
Fmt.Dump.(list Label.pp)
@@ -253,20 +287,20 @@ let version_enabled version =
253287

254288
let get_label f (labels : Label.t list) = Util.List.find_map f labels
255289

256-
let label_not_allowed ~label ~kind =
257-
Util.Result.errorf "`%s` label is not allowed for %s blocks." label kind
290+
let label_not_allowed ~loc ~label ~kind =
291+
loc_error ~loc "`%s` label is not allowed for %s blocks." label kind
258292

259-
let label_required ~label ~kind =
260-
Util.Result.errorf "`%s` label is required for %s blocks." label kind
293+
let label_required ~loc ~label ~kind =
294+
loc_error ~loc "`%s` label is required for %s blocks." label kind
261295

262-
let check_not_set msg = function
263-
| Some _ -> Util.Result.errorf msg
296+
let check_not_set ~loc msg = function
297+
| Some _ -> loc_error ~loc "%s" msg
264298
| None -> Ok ()
265299

266-
let check_no_errors = function
300+
let check_no_errors ~loc = function
267301
| [] -> Ok ()
268302
| _ :: _ ->
269-
Util.Result.errorf "error block cannot be attached to a non-OCaml block"
303+
loc_error ~loc "error block cannot be attached to a non-OCaml block"
270304

271305
type block_config = {
272306
non_det : Label.non_det option;
@@ -301,22 +335,22 @@ let get_block_config l =
301335
file_inc = get_label (function File x -> Some x | _ -> None) l;
302336
}
303337

304-
let mk_ocaml ~config ~contents ~errors =
338+
let mk_ocaml ~loc ~config ~contents ~errors =
305339
let kind = "OCaml" in
306340
match config with
307341
| { file_inc = None; part = None; env; non_det; _ } -> (
308342
match guess_ocaml_kind contents with
309343
| `Code -> Ok (OCaml { env = Ocaml_env.mk env; non_det; errors })
310344
| `Toplevel ->
311-
Util.Result.errorf "toplevel syntax is not allowed in OCaml blocks.")
312-
| { file_inc = Some _; _ } -> label_not_allowed ~label:"file" ~kind
313-
| { part = Some _; _ } -> label_not_allowed ~label:"part" ~kind
345+
loc_error ~loc "toplevel syntax is not allowed in OCaml blocks.")
346+
| { file_inc = Some _; _ } -> label_not_allowed ~loc ~label:"file" ~kind
347+
| { part = Some _; _ } -> label_not_allowed ~loc ~label:"part" ~kind
314348

315-
let mk_cram ?language ~config ~header ~errors () =
349+
let mk_cram ~loc ?language ~config ~header ~errors () =
316350
let kind = "shell" in
317351
match config with
318352
| { file_inc = None; part = None; env = None; non_det; _ } ->
319-
check_no_errors errors >>| fun () ->
353+
check_no_errors ~loc errors >>| fun () ->
320354
let language =
321355
Util.Option.value language
322356
~default:
@@ -325,28 +359,27 @@ let mk_cram ?language ~config ~header ~errors () =
325359
| _ -> `Sh)
326360
in
327361
Cram { language; non_det }
328-
| { file_inc = Some _; _ } -> label_not_allowed ~label:"file" ~kind
329-
| { part = Some _; _ } -> label_not_allowed ~label:"part" ~kind
330-
| { env = Some _; _ } -> label_not_allowed ~label:"env" ~kind
362+
| { file_inc = Some _; _ } -> label_not_allowed ~loc ~label:"file" ~kind
363+
| { part = Some _; _ } -> label_not_allowed ~loc ~label:"part" ~kind
364+
| { env = Some _; _ } -> label_not_allowed ~loc ~label:"env" ~kind
331365

332-
let mk_toplevel ~config ~contents ~errors =
366+
let mk_toplevel ~loc ~config ~contents ~errors =
333367
let kind = "toplevel" in
334368
match config with
335369
| { file_inc = None; part = None; env; non_det; _ } -> (
336370
match guess_ocaml_kind contents with
337-
| `Code ->
338-
Util.Result.errorf "invalid toplevel syntax in toplevel blocks."
371+
| `Code -> loc_error ~loc "invalid toplevel syntax in toplevel blocks."
339372
| `Toplevel ->
340-
check_no_errors errors >>| fun () ->
373+
check_no_errors ~loc errors >>| fun () ->
341374
Toplevel { env = Ocaml_env.mk env; non_det })
342-
| { file_inc = Some _; _ } -> label_not_allowed ~label:"file" ~kind
343-
| { part = Some _; _ } -> label_not_allowed ~label:"part" ~kind
375+
| { file_inc = Some _; _ } -> label_not_allowed ~loc ~label:"file" ~kind
376+
| { part = Some _; _ } -> label_not_allowed ~loc ~label:"part" ~kind
344377

345-
let mk_include ~config ~header ~errors =
378+
let mk_include ~loc ~config ~header ~errors =
346379
let kind = "include" in
347380
match config with
348381
| { file_inc = Some file_included; part; non_det = None; env = None; _ } -> (
349-
check_no_errors errors >>= fun () ->
382+
check_no_errors ~loc errors >>= fun () ->
350383
match header with
351384
| Some Header.OCaml ->
352385
let file_kind = Fk_ocaml { part_included = part } in
@@ -356,40 +389,40 @@ let mk_include ~config ~header ~errors =
356389
| None ->
357390
let file_kind = Fk_other { header } in
358391
Ok (Include { file_included; file_kind })
359-
| Some _ -> label_not_allowed ~label:"part" ~kind:"non-OCaml include")
360-
)
361-
| { file_inc = None; _ } -> label_required ~label:"file" ~kind
392+
| Some _ ->
393+
label_not_allowed ~loc ~label:"part" ~kind:"non-OCaml include"))
394+
| { file_inc = None; _ } -> label_required ~loc ~label:"file" ~kind
362395
| { non_det = Some _; _ } ->
363-
label_not_allowed ~label:"non-deterministic" ~kind
364-
| { env = Some _; _ } -> label_not_allowed ~label:"env" ~kind
396+
label_not_allowed ~loc ~label:"non-deterministic" ~kind
397+
| { env = Some _; _ } -> label_not_allowed ~loc ~label:"env" ~kind
365398

366-
let infer_block ~config ~header ~contents ~errors =
399+
let infer_block ~loc ~config ~header ~contents ~errors =
367400
match config with
368-
| { file_inc = Some _; _ } -> mk_include ~config ~header ~errors
401+
| { file_inc = Some _; _ } -> mk_include ~loc ~config ~header ~errors
369402
| { file_inc = None; part; _ } -> (
370403
match header with
371404
| Some (Header.Shell language) ->
372-
mk_cram ~language ~config ~header ~errors ()
405+
mk_cram ~loc ~language ~config ~header ~errors ()
373406
| Some Header.OCaml -> (
374407
match guess_ocaml_kind contents with
375-
| `Code -> mk_ocaml ~config ~contents ~errors
376-
| `Toplevel -> mk_toplevel ~config ~contents ~errors)
408+
| `Code -> mk_ocaml ~loc ~config ~contents ~errors
409+
| `Toplevel -> mk_toplevel ~loc ~config ~contents ~errors)
377410
| _ ->
378-
check_not_set "`part` label requires a `file` label." part
411+
check_not_set ~loc "`part` label requires a `file` label." part
379412
>>= fun () ->
380-
check_no_errors errors >>| fun () -> Raw { header })
413+
check_no_errors ~loc errors >>| fun () -> Raw { header })
381414

382415
let mk ~loc ~section ~labels ~legacy_labels ~header ~contents ~errors =
383416
let block_kind =
384417
get_label (function Block_kind x -> Some x | _ -> None) labels
385418
in
386419
let config = get_block_config labels in
387420
(match block_kind with
388-
| Some OCaml -> mk_ocaml ~config ~contents ~errors
389-
| Some Cram -> mk_cram ~config ~header ~errors ()
390-
| Some Toplevel -> mk_toplevel ~config ~contents ~errors
391-
| Some Include -> mk_include ~config ~header ~errors
392-
| None -> infer_block ~config ~header ~contents ~errors)
421+
| Some OCaml -> mk_ocaml ~loc ~config ~contents ~errors
422+
| Some Cram -> mk_cram ~loc ~config ~header ~errors ()
423+
| Some Toplevel -> mk_toplevel ~loc ~config ~contents ~errors
424+
| Some Include -> mk_include ~loc ~config ~header ~errors
425+
| None -> infer_block ~loc ~config ~header ~contents ~errors)
393426
>>= fun value ->
394427
version_enabled config.version >>| fun version_enabled ->
395428
{
@@ -412,7 +445,27 @@ let mk_include ~loc ~section ~labels =
412445
let header = Header.infer_from_file file_inc in
413446
mk ~loc ~section ~labels ~legacy_labels:false ~header ~contents:[]
414447
~errors:[]
415-
| None -> label_required ~label:"file" ~kind:"include"
448+
| None -> label_required ~loc ~label:"file" ~kind:"include"
449+
450+
let parse_labels ~label_cmt ~legacy_labels =
451+
match (label_cmt, legacy_labels) with
452+
| Some label_cmt, "" ->
453+
Label.of_string label_cmt >>= fun labels -> Ok (labels, false)
454+
| Some _, _ -> Error [ `Msg "cannot mix both block labels syntax" ]
455+
| None, l -> Label.of_string l >>= fun labels -> Ok (labels, true)
456+
457+
let from_raw raw =
458+
match raw with
459+
| Raw.Include { loc; section; labels } ->
460+
locate_errors ~loc (Label.of_string labels) >>= fun labels ->
461+
Util.Result.to_error_list @@ mk_include ~loc ~section ~labels
462+
| Raw.Any { loc; section; header; contents; label_cmt; legacy_labels; errors }
463+
->
464+
let header = Header.of_string header in
465+
locate_errors ~loc (parse_labels ~label_cmt ~legacy_labels)
466+
>>= fun (labels, legacy_labels) ->
467+
Util.Result.to_error_list
468+
@@ mk ~loc ~section ~header ~contents ~labels ~legacy_labels ~errors
416469

417470
let is_active ?section:s t =
418471
let active =

lib/block.mli

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,23 @@ type value =
7272
type section = int * string
7373
(** The type for sections. *)
7474

75+
module Raw : sig
76+
type t
77+
78+
val make :
79+
loc:Location.t ->
80+
section:section option ->
81+
header:string ->
82+
contents:string list ->
83+
label_cmt:string option ->
84+
legacy_labels:string ->
85+
errors:Output.t list ->
86+
t
87+
88+
val make_include :
89+
loc:Location.t -> section:section option -> labels:string -> t
90+
end
91+
7592
type t = {
7693
loc : Location.t;
7794
section : section option;
@@ -106,6 +123,8 @@ val mk_include :
106123
(** [mk_include] builds an include block from a comment [<!-- $MDX ... -->]
107124
that is not followed by a code block [``` ... ```]. *)
108125

126+
val from_raw : Raw.t -> (t, [ `Msg of string ] list) Result.result
127+
109128
(** {2 Printers} *)
110129

111130
val dump : t Fmt.t

lib/lexer_mdx.mli

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
type token = [ `Block of Block.t | `Section of int * string | `Text of string ]
1+
type token =
2+
[ `Block of Block.Raw.t | `Section of int * string | `Text of string ]
23

34
val markdown_token : Lexing.lexbuf -> (token list, [ `Msg of string ]) result
45
val cram_token : Lexing.lexbuf -> (token list, [ `Msg of string ]) result

0 commit comments

Comments
 (0)