diff --git a/CHANGES.md b/CHANGES.md index 9c041661b8..7200dc5c54 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -32,6 +32,10 @@ Tags: ### Changed - Style: Adjusted line height in the TOC to improve readability (@sorawee, #1045) +### Fixed +- Warn and exit when table(s) is not closed (@lubegasimon, #1050) +- Hint when list(s) is not closed (@lubegasimon, #1050) + # 2.3.0 ### Added diff --git a/src/parser/parse_error.ml b/src/parser/parse_error.ml index 4ee22c470a..a07a8a24bb 100644 --- a/src/parser/parse_error.ml +++ b/src/parser/parse_error.ml @@ -81,3 +81,8 @@ let truncated_code_block_meta : Loc.span -> Warning.t = let truncated_code_block : Loc.span -> Warning.t = Warning.make ~suggestion:"add ']}'." "Missing end of code block." + +let end_not_allowed : in_what:string -> Loc.span -> Warning.t = + fun ~in_what -> + Warning.make ~suggestion:"add '}'." "End of text is not allowed in %s." + in_what diff --git a/src/parser/syntax.ml b/src/parser/syntax.ml index d8ecb87b55..293682ded6 100644 --- a/src/parser/syntax.ml +++ b/src/parser/syntax.ml @@ -118,13 +118,18 @@ module Table = struct end module Reader = struct - let until_rbrace input acc = + let until_rbrace_or_eof input acc = let rec consume () = let next_token = peek input in match next_token.value with | `Right_brace -> junk input; `End (acc, next_token.location) + | `End -> + Parse_error.end_not_allowed next_token.location ~in_what:"table" + |> add_warning input; + junk input; + `End (acc, next_token.location) | `Space _ | `Single_newline _ | `Blank_line _ -> junk input; consume () @@ -1227,7 +1232,7 @@ and explicit_list_items : let next_token = peek input in match next_token.value with | `End -> - Parse_error.not_allowed next_token.location ~what:(Token.describe `End) + Parse_error.end_not_allowed next_token.location ~in_what:(Token.describe parent_markup) |> add_warning input; (List.rev acc, next_token.location) @@ -1275,8 +1280,8 @@ and explicit_list_items : (match token_after_list_item.value with | `Right_brace -> junk input | `End -> - Parse_error.not_allowed token_after_list_item.location - ~what:(Token.describe `End) ~in_what:(Token.describe token) + Parse_error.end_not_allowed token_after_list_item.location + ~in_what:(Token.describe token) |> add_warning input); let acc = content :: acc in @@ -1310,7 +1315,7 @@ and explicit_list_items : which is consumed. *) and light_table ~parent_markup ~parent_markup_location input = let rec consume_rows acc ~last_loc = - Reader.until_rbrace input acc >>> fun next_token -> + Reader.until_rbrace_or_eof input acc >>> fun next_token -> match next_token.Loc.value with | `Bar | #token_that_always_begins_an_inline_element -> ( let next, row, last_loc = @@ -1340,6 +1345,11 @@ and light_table_row ~parent_markup ~last_loc input = let return row cell = List.rev (push_cells row cell) in let next_token = peek input in match next_token.value with + | `End -> + Parse_error.end_not_allowed next_token.location ~in_what:"table" + |> add_warning input; + junk input; + (`Stop, return acc_row acc_cell, next_token.location) | `Right_brace -> junk input; (`Stop, return acc_row acc_cell, next_token.location) @@ -1385,7 +1395,7 @@ and light_table_row ~parent_markup ~last_loc input = which is consumed. *) and heavy_table ~parent_markup ~parent_markup_location input = let rec consume_rows acc ~last_loc = - Reader.until_rbrace input acc >>> fun next_token -> + Reader.until_rbrace_or_eof input acc >>> fun next_token -> match next_token.Loc.value with | `Begin_table_row as token -> junk input; @@ -1411,7 +1421,7 @@ and heavy_table ~parent_markup ~parent_markup_location input = which is consumed. *) and heavy_table_row ~parent_markup input = let rec consume_cell_items acc = - Reader.until_rbrace input acc >>> fun next_token -> + Reader.until_rbrace_or_eof input acc >>> fun next_token -> match next_token.Loc.value with | `Begin_table_cell kind as token -> junk input; diff --git a/src/parser/test/test.ml b/src/parser/test/test.ml index b760832d43..caed0e7f6e 100644 --- a/src/parser/test/test.ml +++ b/src/parser/test/test.ml @@ -3533,7 +3533,8 @@ let%expect_test _ = ((output (((f.ml (1 0) (1 3)) (unordered heavy ())))) (warnings ( "File \"f.ml\", line 1, characters 3-3:\ - \nEnd of text is not allowed in '{ul ...}' (bulleted list)." + \nEnd of text is not allowed in '{ul ...}' (bulleted list).\ + \nSuggestion: add '}'." "File \"f.ml\", line 1, characters 0-3:\ \n'{ul ...}' (bulleted list) should not be empty."))) |}] @@ -3567,9 +3568,11 @@ let%expect_test _ = ((((f.ml (1 8) (1 11)) (paragraph (((f.ml (1 8) (1 11)) (word foo))))))))))) (warnings ( "File \"f.ml\", line 1, characters 11-11:\ - \nEnd of text is not allowed in '{li ...}' (list item)." + \nEnd of text is not allowed in '{li ...}' (list item).\ + \nSuggestion: add '}'." "File \"f.ml\", line 1, characters 11-11:\ - \nEnd of text is not allowed in '{ul ...}' (bulleted list)."))) |}] + \nEnd of text is not allowed in '{ul ...}' (bulleted list).\ + \nSuggestion: add '}'."))) |}] let unterminated_left_curly_brace = test "{ul {- foo"; @@ -3581,9 +3584,11 @@ let%expect_test _ = ((((f.ml (1 7) (1 10)) (paragraph (((f.ml (1 7) (1 10)) (word foo))))))))))) (warnings ( "File \"f.ml\", line 1, characters 10-10:\ - \nEnd of text is not allowed in '{- ...}' (list item)." + \nEnd of text is not allowed in '{- ...}' (list item).\ + \nSuggestion: add '}'." "File \"f.ml\", line 1, characters 10-10:\ - \nEnd of text is not allowed in '{ul ...}' (bulleted list)."))) |}] + \nEnd of text is not allowed in '{ul ...}' (bulleted list).\ + \nSuggestion: add '}'."))) |}] let empty_li_styntax = test "{ul {li }}"; @@ -5083,7 +5088,8 @@ let%expect_test _ = \n']}' is not allowed in '{ul ...}' (bulleted list).\ \nSuggestion: move ']}' into a list item, '{li ...}' or '{- ...}'." "File \"f.ml\", line 1, characters 6-6:\ - \nEnd of text is not allowed in '{ul ...}' (bulleted list)." + \nEnd of text is not allowed in '{ul ...}' (bulleted list).\ + \nSuggestion: add '}'." "File \"f.ml\", line 1, characters 0-3:\ \n'{ul ...}' (bulleted list) should not be empty."))) |}] @@ -5096,7 +5102,8 @@ let%expect_test _ = ( "File \"f.ml\", line 1, characters 4-7:\ \n'{li ...}' (list item) should not be empty." "File \"f.ml\", line 1, characters 11-11:\ - \nEnd of text is not allowed in '{ul ...}' (bulleted list)."))) |}] + \nEnd of text is not allowed in '{ul ...}' (bulleted list).\ + \nSuggestion: add '}'."))) |}] let right_bracket_in_heading = test "{2 ]}"; diff --git a/src/parser/test/test_tables.ml b/src/parser/test/test_tables.ml index 32876485e4..d8840b068b 100644 --- a/src/parser/test/test_tables.ml +++ b/src/parser/test/test_tables.ml @@ -82,6 +82,18 @@ let%expect_test _ = (align "no alignment"))))) (warnings ())) |}] + let unclosed_table = + test "{table {tr {td}}"; + [%expect + {| + ((output + (((f.ml (1 0) (1 16)) + (table (syntax heavy) (grid ((row ((data ()))))) (align "no alignment"))))) + (warnings + ( "File \"f.ml\", line 1, characters 16-16:\ + \nEnd of text is not allowed in table.\ + \nSuggestion: add '}'."))) |}] + let complex_table = test {| @@ -190,6 +202,18 @@ let%expect_test _ = (table (syntax light) (grid ()) (align "no alignment"))))) (warnings ())) |}] + let unclosed_table = + test "{t "; + [%expect + {| + ((output + (((f.ml (1 0) (1 3)) + (table (syntax light) (grid ()) (align "no alignment"))))) + (warnings + ( "File \"f.ml\", line 1, characters 2-3:\ + \nEnd of text is not allowed in table.\ + \nSuggestion: add '}'."))) |}] + let simple = test {| {t diff --git a/test/model/semantics/test.ml b/test/model/semantics/test.ml index 29855b6dc8..f6ceffc4bd 100644 --- a/test/model/semantics/test.ml +++ b/test/model/semantics/test.ml @@ -885,25 +885,25 @@ let%expect_test _ = test "{ul {li foo @author Bar}}"; [%expect {| - {"value":[{"`List":["`Unordered",[[{"`Paragraph":[{"`Word":"foo"},"`Space"]},{"`Paragraph":[{"`Word":"@author"},"`Space",{"`Word":" Bar}}"}]}]]]}],"warnings":["File \"f.ml\", line 1, characters 12-25:\n'@author' is not allowed in '{li ...}' (list item).\nSuggestion: move '@author' outside of any other markup.","File \"f.ml\", line 1, characters 25-25:\nEnd of text is not allowed in '{li ...}' (list item).","File \"f.ml\", line 1, characters 25-25:\nEnd of text is not allowed in '{ul ...}' (bulleted list)."]} |}] + {"value":[{"`List":["`Unordered",[[{"`Paragraph":[{"`Word":"foo"},"`Space"]},{"`Paragraph":[{"`Word":"@author"},"`Space",{"`Word":" Bar}}"}]}]]]}],"warnings":["File \"f.ml\", line 1, characters 12-25:\n'@author' is not allowed in '{li ...}' (list item).\nSuggestion: move '@author' outside of any other markup.","File \"f.ml\", line 1, characters 25-25:\nEnd of text is not allowed in '{li ...}' (list item).\nSuggestion: add '}'.","File \"f.ml\", line 1, characters 25-25:\nEnd of text is not allowed in '{ul ...}' (bulleted list).\nSuggestion: add '}'."]} |}] let in_list_item_at_start = test "{ul {li @author Foo}}"; [%expect {| - {"value":[{"`List":["`Unordered",[[{"`Paragraph":[{"`Word":"@author"},"`Space",{"`Word":" Foo}}"}]}]]]}],"warnings":["File \"f.ml\", line 1, characters 8-21:\n'@author' is not allowed in '{li ...}' (list item).\nSuggestion: move '@author' outside of any other markup.","File \"f.ml\", line 1, characters 21-21:\nEnd of text is not allowed in '{li ...}' (list item).","File \"f.ml\", line 1, characters 21-21:\nEnd of text is not allowed in '{ul ...}' (bulleted list)."]} |}] + {"value":[{"`List":["`Unordered",[[{"`Paragraph":[{"`Word":"@author"},"`Space",{"`Word":" Foo}}"}]}]]]}],"warnings":["File \"f.ml\", line 1, characters 8-21:\n'@author' is not allowed in '{li ...}' (list item).\nSuggestion: move '@author' outside of any other markup.","File \"f.ml\", line 1, characters 21-21:\nEnd of text is not allowed in '{li ...}' (list item).\nSuggestion: add '}'.","File \"f.ml\", line 1, characters 21-21:\nEnd of text is not allowed in '{ul ...}' (bulleted list).\nSuggestion: add '}'."]} |}] let in_list_item_on_new_line = test "{ul {li foo\n@author Bar}}"; [%expect {| - {"value":[{"`List":["`Unordered",[[{"`Paragraph":[{"`Word":"foo"}]},{"`Paragraph":[{"`Word":"@author"},"`Space",{"`Word":" Bar}}"}]}]]]}],"warnings":["File \"f.ml\", line 2, characters 0-13:\n'@author' is not allowed in '{li ...}' (list item).\nSuggestion: move '@author' outside of any other markup.","File \"f.ml\", line 2, characters 13-13:\nEnd of text is not allowed in '{li ...}' (list item).","File \"f.ml\", line 2, characters 13-13:\nEnd of text is not allowed in '{ul ...}' (bulleted list)."]} |}] + {"value":[{"`List":["`Unordered",[[{"`Paragraph":[{"`Word":"foo"}]},{"`Paragraph":[{"`Word":"@author"},"`Space",{"`Word":" Bar}}"}]}]]]}],"warnings":["File \"f.ml\", line 2, characters 0-13:\n'@author' is not allowed in '{li ...}' (list item).\nSuggestion: move '@author' outside of any other markup.","File \"f.ml\", line 2, characters 13-13:\nEnd of text is not allowed in '{li ...}' (list item).\nSuggestion: add '}'.","File \"f.ml\", line 2, characters 13-13:\nEnd of text is not allowed in '{ul ...}' (bulleted list).\nSuggestion: add '}'."]} |}] let in_list = test "{ul @author Foo}"; [%expect {| - {"value":[{"`List":["`Unordered",[]]}],"warnings":["File \"f.ml\", line 1, characters 4-16:\n'@author' is not allowed in '{ul ...}' (bulleted list).\nSuggestion: move '@author' outside the list.","File \"f.ml\", line 1, characters 16-16:\nEnd of text is not allowed in '{ul ...}' (bulleted list).","File \"f.ml\", line 1, characters 0-3:\n'{ul ...}' (bulleted list) should not be empty."]} |}] + {"value":[{"`List":["`Unordered",[]]}],"warnings":["File \"f.ml\", line 1, characters 4-16:\n'@author' is not allowed in '{ul ...}' (bulleted list).\nSuggestion: move '@author' outside the list.","File \"f.ml\", line 1, characters 16-16:\nEnd of text is not allowed in '{ul ...}' (bulleted list).\nSuggestion: add '}'.","File \"f.ml\", line 1, characters 0-3:\n'{ul ...}' (bulleted list) should not be empty."]} |}] let in_code_block = test "{[@author Foo]}";