1{-# LANGUAGE OverloadedStrings, FlexibleInstances #-}
2{-# OPTIONS_GHC -fno-warn-orphans #-}
3
4module Documentation.Haddock.ParserSpec (main, spec) where
5
6import           Data.String
7import qualified Documentation.Haddock.Parser as Parse
8import           Documentation.Haddock.Types
9import           Documentation.Haddock.Doc (docAppend)
10import           Test.Hspec
11import           Test.QuickCheck
12
13import           Prelude hiding ((<>))
14
15infixr 6 <>
16(<>) :: Doc id -> Doc id -> Doc id
17(<>) = docAppend
18
19type Doc id = DocH () id
20
21instance IsString (Doc String) where
22  fromString = DocString
23
24instance IsString a => IsString (Maybe a) where
25  fromString = Just . fromString
26
27emptyMeta :: Meta
28emptyMeta =
29  Meta {
30    _version = Nothing
31  , _package = Nothing
32  }
33
34parseParas :: String -> MetaDoc () String
35parseParas = overDoc Parse.toRegular . Parse.parseParas Nothing
36
37parseString :: String -> Doc String
38parseString = Parse.toRegular . Parse.parseString
39
40hyperlink :: String -> Maybe (Doc String) -> Doc String
41hyperlink url = DocHyperlink . Hyperlink url
42
43main :: IO ()
44main = hspec spec
45
46spec :: Spec
47spec = do
48  describe "parseString" $ do
49    let infix 1 `shouldParseTo`
50        shouldParseTo :: String -> Doc String -> Expectation
51        shouldParseTo input ast = parseString input `shouldBe` ast
52
53    it "is total" $ do
54      property $ \xs ->
55        (length . show . parseString) xs `shouldSatisfy` (> 0)
56
57    context "when parsing text" $ do
58      it "can handle unicode" $ do
59        "灼眼のシャナ" `shouldParseTo` "灼眼のシャナ"
60
61      it "accepts numeric character references" $ do
62        "foo b&#97;r b&#97;z &#955;" `shouldParseTo` "foo bar baz λ"
63
64      it "accepts hexadecimal character references" $ do
65        "&#x65;" `shouldParseTo` "e"
66
67      it "allows to backslash-escape characters except \\r" $ do
68        property $ \y -> case y of
69          '\r' -> "\\\r" `shouldParseTo` DocString "\\"
70          x -> ['\\', x] `shouldParseTo` DocString [x]
71
72      context "when parsing strings contaning numeric character references" $ do
73        it "will implicitly convert digits to characters" $ do
74          "&#65;&#65;&#65;&#65;" `shouldParseTo` "AAAA"
75
76          "&#28796;&#30524;&#12398;&#12471;&#12515;&#12490;"
77            `shouldParseTo` "灼眼のシャナ"
78
79        it "will implicitly convert hex encoded characters" $ do
80          "&#x65;&#x65;&#x65;&#x65;" `shouldParseTo` "eeee"
81
82    context "when parsing identifiers" $ do
83      it "parses identifiers enclosed within single ticks" $ do
84        "'foo'" `shouldParseTo` DocIdentifier "foo"
85
86      it "parses identifiers enclosed within backticks" $ do
87        "`foo`" `shouldParseTo` DocIdentifier "foo"
88
89      it "parses identifiers preceded by a backtick and followed by a single quote" $ do
90        "`foo'" `shouldParseTo` DocIdentifier "foo"
91
92      it "parses identifiers preceded by a single quote and followed by a backtick" $ do
93        "'foo`" `shouldParseTo` DocIdentifier "foo"
94
95      it "can parse a constructor identifier" $ do
96        "'Foo'" `shouldParseTo` DocIdentifier "Foo"
97
98      it "can parse a qualified identifier" $ do
99        "'Foo.bar'" `shouldParseTo` DocIdentifier "Foo.bar"
100
101      it "parses a word with an one of the delimiters in it as DocString" $ do
102          "don't" `shouldParseTo` "don't"
103
104      it "doesn't pass pairs of delimiters with spaces between them" $ do
105        "hel'lo w'orld" `shouldParseTo` "hel'lo w'orld"
106
107      it "don't use apostrophe's in the wrong place's" $ do
108        " don't use apostrophe's in the wrong place's" `shouldParseTo`
109          "don't use apostrophe's in the wrong place's"
110
111      it "doesn't parse empty identifiers" $ do
112        "``" `shouldParseTo` "``"
113
114      it "can parse an identifier in infix notation enclosed within backticks" $ do
115        "``infix``" `shouldParseTo` DocIdentifier "`infix`"
116
117      it "can parse identifiers containing a single quote" $ do
118        "'don't'" `shouldParseTo` DocIdentifier "don't"
119
120      it "can parse identifiers ending with a single quote" $ do
121        "'foo''" `shouldParseTo` DocIdentifier "foo'"
122
123      it "can parse an identifier containing a digit" $ do
124        "'f0'" `shouldParseTo` DocIdentifier "f0"
125
126      it "can parse an identifier containing unicode characters" $ do
127        "'λ'" `shouldParseTo` DocIdentifier "λ"
128
129      it "can parse a single quote followed by an identifier" $ do
130        "''foo'" `shouldParseTo` "'" <> DocIdentifier "foo"
131
132      it "can parse an identifier that starts with an underscore" $ do
133        "'_x'" `shouldParseTo` DocIdentifier "_x"
134
135      it "can parse value-namespaced identifiers" $ do
136        "v'foo'" `shouldParseTo` DocIdentifier "foo"
137
138      it "can parse type-namespaced identifiers" $ do
139        "t'foo'" `shouldParseTo` DocIdentifier "foo"
140
141      it "can parse parenthesized operators and backticked identifiers" $ do
142        "'(<|>)'" `shouldParseTo` DocIdentifier "(<|>)"
143        "'`elem`'" `shouldParseTo` DocIdentifier "`elem`"
144
145      it "can properly figure out the end of identifiers" $ do
146        "'DbModule'/'DbUnitId'" `shouldParseTo` DocIdentifier "DbModule" <> "/" <> DocIdentifier "DbUnitId"
147
148    context "when parsing operators" $ do
149      it "can parse an operator enclosed within single quotes" $ do
150        "'.='" `shouldParseTo` DocIdentifier ".="
151
152      it "can parse a qualified operator" $ do
153        "'F..'" `shouldParseTo` DocIdentifier "F.."
154
155      it "can parse a constructor operator" $ do
156        "':='" `shouldParseTo` DocIdentifier ":="
157
158      it "can parse a qualified constructor operator" $ do
159        "'F.:='" `shouldParseTo` DocIdentifier "F.:="
160
161      it "can parse a unicode operator" $ do
162        "'∧'" `shouldParseTo` DocIdentifier "∧"
163
164    context "when parsing URLs" $ do
165      it "parses a URL" $ do
166        "<http://example.com/>" `shouldParseTo` hyperlink "http://example.com/" Nothing
167
168      it "accepts an optional label" $ do
169        "<http://example.com/ some link>" `shouldParseTo` hyperlink "http://example.com/" "some link"
170
171      it "does not accept newlines in label" $ do
172        "<foo bar\nbaz>" `shouldParseTo` "<foo bar\nbaz>"
173
174      -- new behaviour test, this will be now consistent with other markup
175      it "allows us to escape > inside the URL" $ do
176        "<http://examp\\>le.com>" `shouldParseTo`
177          hyperlink "http://examp>le.com" Nothing
178
179        "<http://exa\\>mp\\>le.com>" `shouldParseTo`
180          hyperlink "http://exa>mp>le.com" Nothing
181
182        -- Likewise in label
183        "<http://example.com f\\>oo>" `shouldParseTo`
184          hyperlink "http://example.com" "f>oo"
185
186      it "parses inline URLs" $ do
187        "foo <http://example.com/> bar" `shouldParseTo`
188          "foo " <> hyperlink "http://example.com/" Nothing <> " bar"
189
190      it "doesn't allow for multi-line link tags" $ do
191        "<ba\nz aar>" `shouldParseTo` "<ba\nz aar>"
192
193      context "when parsing markdown links" $ do
194        it "parses a simple link" $ do
195          "[some label](url)" `shouldParseTo`
196            hyperlink "url" "some label"
197
198        it "allows whitespace between label and URL" $ do
199          "[some label] \t (url)" `shouldParseTo`
200            hyperlink "url" "some label"
201
202        it "allows newlines in label" $ do
203          "[some\n\nlabel](url)" `shouldParseTo`
204            hyperlink "url" "some\n\nlabel"
205
206        it "allows escaping in label" $ do
207          "[some\\] label](url)" `shouldParseTo`
208            hyperlink "url" "some] label"
209
210        it "strips leading and trailing whitespace from label" $ do
211          "[  some label  ](url)" `shouldParseTo`
212            hyperlink "url" "some label"
213
214        it "rejects whitespace in URL" $ do
215          "[some label]( url)" `shouldParseTo`
216            "[some label]( url)"
217
218        it "allows inline markup in the label" $ do
219          "[something /emphasized/](url)" `shouldParseTo`
220            hyperlink "url" (Just ("something " <> DocEmphasis "emphasized"))
221
222        context "when URL is on a separate line" $ do
223          it "allows URL to be on a separate line" $ do
224            "[some label]\n(url)" `shouldParseTo`
225              hyperlink "url" "some label"
226
227          it "allows leading whitespace" $ do
228            "[some label]\n  \t (url)" `shouldParseTo`
229              hyperlink "url" "some label"
230
231          it "rejects additional newlines" $ do
232            "[some label]\n\n(url)" `shouldParseTo`
233              "[some label]\n\n(url)"
234
235
236      context "when autolinking URLs" $ do
237        it "autolinks HTTP URLs" $ do
238          "http://example.com/" `shouldParseTo` hyperlink "http://example.com/" Nothing
239
240        it "autolinks HTTPS URLs" $ do
241          "https://www.example.com/" `shouldParseTo` hyperlink "https://www.example.com/" Nothing
242
243        it "autolinks FTP URLs" $ do
244          "ftp://example.com/" `shouldParseTo` hyperlink "ftp://example.com/" Nothing
245
246        it "does not include a trailing comma" $ do
247          "http://example.com/, Some other sentence." `shouldParseTo`
248            hyperlink "http://example.com/" Nothing <> ", Some other sentence."
249
250        it "does not include a trailing dot" $ do
251          "http://example.com/. Some other sentence." `shouldParseTo`
252            hyperlink "http://example.com/" Nothing <> ". Some other sentence."
253
254        it "does not include a trailing exclamation mark" $ do
255          "http://example.com/! Some other sentence." `shouldParseTo`
256            hyperlink "http://example.com/" Nothing <> "! Some other sentence."
257
258        it "does not include a trailing question mark" $ do
259          "http://example.com/? Some other sentence." `shouldParseTo`
260            hyperlink "http://example.com/" Nothing <> "? Some other sentence."
261
262        it "autolinks URLs occuring mid-sentence with multiple ‘/’s" $ do
263          "foo https://example.com/example bar" `shouldParseTo`
264            "foo " <> hyperlink "https://example.com/example" Nothing <> " bar"
265
266    context "when parsing images" $ do
267      let image :: String -> Maybe String -> Doc String
268          image uri = DocPic . Picture uri
269
270      it "accepts markdown syntax for images" $ do
271        "![label](url)" `shouldParseTo` image "url" "label"
272
273      it "accepts Unicode" $ do
274        "![灼眼のシャナ](url)" `shouldParseTo` image "url" "灼眼のシャナ"
275
276      it "supports deprecated picture syntax" $ do
277        "<<baz>>" `shouldParseTo` image "baz" Nothing
278
279      it "supports title for deprecated picture syntax" $ do
280        "<<b a z>>" `shouldParseTo` image "b" "a z"
281
282    context "when parsing display math" $ do
283
284      it "accepts markdown syntax for display math containing newlines" $ do
285         "\\[\\pi\n\\pi\\]" `shouldParseTo` DocMathDisplay "\\pi\n\\pi"
286
287    context "when parsing anchors" $ do
288      it "parses a single word anchor" $ do
289        "#foo#" `shouldParseTo` DocAName "foo"
290
291      it "parses a multi word anchor" $ do
292        "#foo bar#" `shouldParseTo` DocAName "foo bar"
293
294      it "parses a unicode anchor" $ do
295        "#灼眼のシャナ#" `shouldParseTo` DocAName "灼眼のシャナ"
296
297      it "does not accept newlines in anchors" $ do
298        "#foo\nbar#" `shouldParseTo` "#foo\nbar#"
299
300      it "accepts anchors mid-paragraph" $ do
301        "Hello #someAnchor# world!"
302          `shouldParseTo` "Hello " <> DocAName "someAnchor" <> " world!"
303
304      it "does not accept empty anchors" $ do
305        "##" `shouldParseTo` "##"
306
307    context "when parsing emphasised text" $ do
308      it "emphasises a word on its own" $ do
309        "/foo/" `shouldParseTo` DocEmphasis "foo"
310
311      it "emphasises inline correctly" $ do
312        "foo /bar/ baz" `shouldParseTo` "foo " <> DocEmphasis "bar" <> " baz"
313
314      it "emphasises unicode" $ do
315        "/灼眼のシャナ/" `shouldParseTo` DocEmphasis "灼眼のシャナ"
316
317      it "does not emphasise multi-line strings" $ do
318        " /foo\nbar/" `shouldParseTo` "/foo\nbar/"
319
320      it "does not emphasise the empty string" $ do
321        "//" `shouldParseTo` "//"
322
323      it "parses escaped slashes literally" $ do
324        "/foo\\/bar/" `shouldParseTo` DocEmphasis "foo/bar"
325
326      it "recognizes other markup constructs within emphasised text" $ do
327        "/foo @bar@ baz/" `shouldParseTo`
328          DocEmphasis ("foo " <> DocMonospaced "bar" <> " baz")
329
330      it "allows other markup inside of emphasis" $ do
331        "/__inner bold__/" `shouldParseTo` DocEmphasis (DocBold "inner bold")
332
333      it "doesn't mangle inner markup unicode" $ do
334        "/__灼眼のシャナ &#65;__/" `shouldParseTo` DocEmphasis (DocBold "灼眼のシャナ A")
335
336      it "properly converts HTML escape sequences" $ do
337        "/&#65;&#65;&#65;&#65;/" `shouldParseTo` DocEmphasis "AAAA"
338
339      it "allows to escape the emphasis delimiter inside of emphasis" $ do
340        "/empha\\/sis/" `shouldParseTo` DocEmphasis "empha/sis"
341
342    context "when parsing monospaced text" $ do
343      it "parses simple monospaced text" $ do
344        "@foo@" `shouldParseTo` DocMonospaced "foo"
345
346      it "parses inline monospaced text" $ do
347        "foo @bar@ baz" `shouldParseTo` "foo " <> DocMonospaced "bar" <> " baz"
348
349      it "allows to escape @" $ do
350        "@foo \\@ bar@" `shouldParseTo` DocMonospaced "foo @ bar"
351
352      it "accepts unicode" $ do
353        "@foo 灼眼のシャナ bar@" `shouldParseTo` DocMonospaced "foo 灼眼のシャナ bar"
354
355      it "accepts other markup in monospaced text" $ do
356        "@/foo/@" `shouldParseTo` DocMonospaced (DocEmphasis "foo")
357
358      it "requires the closing @" $ do
359        "@foo /bar/ baz" `shouldParseTo` "@foo " <> DocEmphasis "bar" <> " baz"
360
361    context "when parsing bold strings" $ do
362      it "allows for a bold string on its own" $ do
363        "__bold string__" `shouldParseTo`
364          DocBold "bold string"
365
366      it "bolds inline correctly" $ do
367        "hello __everyone__ there" `shouldParseTo`
368          "hello "
369           <> DocBold "everyone" <> " there"
370
371      it "bolds unicode" $ do
372        "__灼眼のシャナ__" `shouldParseTo`
373          DocBold "灼眼のシャナ"
374
375      it "does not do __multi-line\\n bold__" $ do
376        " __multi-line\n bold__" `shouldParseTo` "__multi-line\n bold__"
377
378      it "allows other markup inside of bold" $ do
379        "__/inner emphasis/__" `shouldParseTo`
380          (DocBold $ DocEmphasis "inner emphasis")
381
382      it "doesn't mangle inner markup unicode" $ do
383        "__/灼眼のシャナ &#65;/__" `shouldParseTo`
384          (DocBold $ DocEmphasis "灼眼のシャナ A")
385
386      it "properly converts HTML escape sequences" $ do
387        "__&#65;&#65;&#65;&#65;__" `shouldParseTo`
388          DocBold "AAAA"
389
390      it "allows to escape the bold delimiter inside of bold" $ do
391        "__bo\\__ld__" `shouldParseTo`
392          DocBold "bo__ld"
393
394      it "doesn't allow for empty bold" $ do
395        "____" `shouldParseTo` "____"
396
397    context "when parsing module strings" $ do
398      it "should parse a module on its own" $ do
399        "\"Module\"" `shouldParseTo`
400          DocModule "Module"
401
402      it "should parse a module inline" $ do
403        "This is a \"Module\"." `shouldParseTo`
404          "This is a " <> DocModule "Module" <> "."
405
406      it "can accept a simple module name" $ do
407        "\"Hello\"" `shouldParseTo` DocModule "Hello"
408
409      it "can accept a module name with dots" $ do
410        "\"Hello.World\"" `shouldParseTo` DocModule "Hello.World"
411
412      it "can accept a module name with unicode" $ do
413        "\"Hello.Worldλ\"" `shouldParseTo` DocModule "Hello.Worldλ"
414
415      it "parses a module name with a trailing dot as regular quoted string" $ do
416        "\"Hello.\"" `shouldParseTo` "\"Hello.\""
417
418      it "parses a module name with a space as regular quoted string" $ do
419        "\"Hello World\"" `shouldParseTo` "\"Hello World\""
420
421      it "parses a module name with invalid characters as regular quoted string" $ do
422        "\"Hello&[{}(=*)+]!\"" `shouldParseTo` "\"Hello&[{}(=*)+]!\""
423
424      it "accepts a module name with unicode" $ do
425        "\"Foo.Barλ\"" `shouldParseTo` DocModule "Foo.Barλ"
426
427      it "treats empty module name as regular double quotes" $ do
428        "\"\"" `shouldParseTo` "\"\""
429
430      it "accepts anchor reference syntax as DocModule" $ do
431        "\"Foo#bar\"" `shouldParseTo` DocModule "Foo#bar"
432
433      it "accepts old anchor reference syntax as DocModule" $ do
434        "\"Foo\\#bar\"" `shouldParseTo` DocModule "Foo\\#bar"
435
436  describe "parseParas" $ do
437    let infix 1 `shouldParseTo`
438        shouldParseTo :: String -> Doc String -> Expectation
439        shouldParseTo input ast = _doc (parseParas input) `shouldBe` ast
440
441    it "is total" $ do
442      property $ \xs ->
443        (length . show . parseParas) xs `shouldSatisfy` (> 0)
444
445    context "when parsing @since" $ do
446      it "adds specified version to the result" $ do
447        parseParas "@since 0.5.0" `shouldBe`
448          MetaDoc { _meta = emptyMeta { _version = Just [0,5,0] }
449                  , _doc = DocEmpty }
450
451      it "ignores trailing whitespace" $ do
452        parseParas "@since 0.5.0 \t " `shouldBe`
453          MetaDoc { _meta = emptyMeta { _version = Just [0,5,0] }
454                  , _doc = DocEmpty }
455
456      it "does not allow trailing input" $ do
457        parseParas "@since 0.5.0 foo" `shouldBe`
458          MetaDoc { _meta = emptyMeta { _version = Nothing }
459                  , _doc = DocParagraph "@since 0.5.0 foo" }
460
461
462      context "when given multiple times" $ do
463        it "gives last occurrence precedence" $ do
464          (parseParas . unlines) [
465              "@since 0.5.0"
466            , "@since 0.6.0"
467            , "@since 0.7.0"
468            ] `shouldBe` MetaDoc { _meta = emptyMeta { _version = Just [0,7,0] }
469                                 , _doc = DocEmpty }
470
471
472    context "when parsing text paragraphs" $ do
473      let filterSpecial = filter (`notElem` (".(=#-[*`\v\f\n\t\r\\\"'_/@<> " :: String))
474
475      it "parses an empty paragraph" $ do
476        "" `shouldParseTo` DocEmpty
477
478      it "parses a simple text paragraph" $ do
479        "foo bar baz" `shouldParseTo` DocParagraph "foo bar baz"
480
481      it "accepts markup in text paragraphs" $ do
482        "foo /bar/ baz" `shouldParseTo` DocParagraph ("foo " <> DocEmphasis "bar" <> " baz")
483
484      it "preserve all regular characters" $ do
485        property $ \xs -> let input = filterSpecial xs in (not . null) input ==>
486          input `shouldParseTo` DocParagraph (DocString input)
487
488      it "separates paragraphs by empty lines" $ do
489        unlines [
490            "foo"
491          , " \t "
492          , "bar"
493          ] `shouldParseTo` DocParagraph "foo" <> DocParagraph "bar"
494
495      context "when a pragraph only contains monospaced text" $ do
496        it "turns it into a code block" $ do
497          "@foo@" `shouldParseTo` DocCodeBlock "foo"
498
499      context "when a paragraph starts with a markdown link" $ do
500        it "correctly parses it as a text paragraph (not a definition list)" $ do
501          "[label](url)" `shouldParseTo`
502            DocParagraph (hyperlink "url" "label")
503
504        it "can be followed by an other paragraph" $ do
505          "[label](url)\n\nfoobar" `shouldParseTo`
506            DocParagraph (hyperlink "url" "label") <> DocParagraph "foobar"
507
508        context "when paragraph contains additional text" $ do
509          it "accepts more text after the link" $ do
510            "[label](url) foo bar baz" `shouldParseTo`
511              DocParagraph (hyperlink "url" "label" <> " foo bar baz")
512
513          it "accepts a newline right after the markdown link" $ do
514            "[label](url)\nfoo bar baz" `shouldParseTo`
515              DocParagraph (hyperlink "url" "label" <> " foo bar baz")
516
517          it "can be followed by an other paragraph" $ do
518            "[label](url)foo\n\nbar" `shouldParseTo`
519              DocParagraph (hyperlink "url" "label" <> "foo") <> DocParagraph "bar"
520
521    context "when parsing birdtracks" $ do
522      it "parses them as a code block" $ do
523        unlines [
524            ">foo"
525          , ">bar"
526          , ">baz"
527          ] `shouldParseTo` DocCodeBlock "foo\nbar\nbaz"
528
529      it "ignores leading whitespace" $ do
530        unlines [
531            " >foo"
532          , " \t >bar"
533          , " >baz"
534          ]
535        `shouldParseTo` DocCodeBlock "foo\nbar\nbaz"
536
537      it "strips one leading space from each line of the block" $ do
538        unlines [
539            "> foo"
540          , ">  bar"
541          , "> baz"
542          ] `shouldParseTo` DocCodeBlock "foo\n bar\nbaz"
543
544      it "ignores empty lines when stripping spaces" $ do
545        unlines [
546            "> foo"
547          , ">"
548          , "> bar"
549          ] `shouldParseTo` DocCodeBlock "foo\n\nbar"
550
551      context "when any non-empty line does not start with a space" $ do
552        it "does not strip any spaces" $ do
553          unlines [
554              ">foo"
555            , ">  bar"
556            ] `shouldParseTo` DocCodeBlock "foo\n  bar"
557
558      it "ignores nested markup" $ do
559        unlines [
560            ">/foo/"
561          ] `shouldParseTo` DocCodeBlock "/foo/"
562
563      it "treats them as regular text inside text paragraphs" $ do
564        unlines [
565            "foo"
566          , ">bar"
567          ] `shouldParseTo` DocParagraph "foo\n>bar"
568
569    context "when parsing code blocks" $ do
570      it "accepts a simple code block" $ do
571        unlines [
572            "@"
573          , "foo"
574          , "bar"
575          , "baz"
576          , "@"
577          ] `shouldParseTo` DocCodeBlock "foo\nbar\nbaz\n"
578
579      it "ignores trailing whitespace after the opening @" $ do
580        unlines [
581            "@   "
582          , "foo"
583          , "@"
584          ] `shouldParseTo` DocCodeBlock "foo\n"
585
586      it "rejects code blocks that are not closed" $ do
587        unlines [
588            "@"
589          , "foo"
590          ] `shouldParseTo` DocParagraph "@\nfoo"
591
592      it "accepts nested markup" $ do
593        unlines [
594            "@"
595          , "/foo/"
596          , "@"
597          ] `shouldParseTo` DocCodeBlock (DocEmphasis "foo" <> "\n")
598
599      it "allows to escape the @" $ do
600        unlines [
601            "@"
602          , "foo"
603          , "\\@"
604          , "bar"
605          , "@"
606          ] `shouldParseTo` DocCodeBlock "foo\n@\nbar\n"
607
608      it "accepts horizontal space before the @" $ do
609        unlines [ "     @"
610                , "foo"
611                , ""
612                , "bar"
613                , "@"
614                ] `shouldParseTo` DocCodeBlock "foo\n\nbar\n"
615
616      it "strips a leading space from a @ block if present" $ do
617        unlines [ " @"
618                , " hello"
619                , " world"
620                , " @"
621                ] `shouldParseTo` DocCodeBlock "hello\nworld\n"
622
623        unlines [ " @"
624                , " hello"
625                , ""
626                , " world"
627                , " @"
628                ] `shouldParseTo` DocCodeBlock "hello\n\nworld\n"
629
630      it "only drops whitespace if there's some before closing @" $ do
631        unlines [ "@"
632                , "    Formatting"
633                , "        matters."
634                , "@"
635                ]
636          `shouldParseTo` DocCodeBlock "    Formatting\n        matters.\n"
637
638      it "accepts unicode" $ do
639        "@foo 灼眼のシャナ bar@" `shouldParseTo` DocCodeBlock "foo 灼眼のシャナ bar"
640
641      it "requires the closing @" $ do
642        "@foo /bar/ baz"
643          `shouldParseTo` DocParagraph ("@foo " <> DocEmphasis "bar" <> " baz")
644
645
646    context "when parsing examples" $ do
647      it "parses a simple example" $ do
648        ">>> foo" `shouldParseTo` DocExamples [Example "foo" []]
649
650      it "parses an example with result" $ do
651        unlines [
652            ">>> foo"
653          , "bar"
654          , "baz"
655          ] `shouldParseTo` DocExamples [Example "foo" ["bar", "baz"]]
656
657      it "parses consecutive examples" $ do
658        unlines [
659            ">>> fib 5"
660          , "5"
661          , ">>> fib 10"
662          , "55"
663          ] `shouldParseTo` DocExamples [
664            Example "fib 5" ["5"]
665          , Example "fib 10" ["55"]
666          ]
667
668      it ("requires an example to be separated"
669          ++ " from a previous paragraph by an empty line") $ do
670        "foobar\n\n>>> fib 10\n55" `shouldParseTo`
671          DocParagraph "foobar"
672                <> DocExamples [Example "fib 10" ["55"]]
673
674      it "parses bird-tracks inside of paragraphs as plain strings" $ do
675        let xs = "foo\n>>> bar"
676        xs `shouldParseTo` DocParagraph (DocString xs)
677
678      it "skips empty lines in front of an example" $ do
679        "\n   \n\n>>> foo" `shouldParseTo` DocExamples [Example "foo" []]
680
681      it "terminates example on empty line" $ do
682        unlines [
683            ">>> foo"
684          , "bar"
685          , "    "
686          , "baz"
687          ]
688        `shouldParseTo`
689          DocExamples [Example "foo" ["bar"]] <> DocParagraph "baz"
690
691      it "parses a <BLANKLINE> result as an empty result" $ do
692        unlines [
693            ">>> foo"
694          , "bar"
695          , "<BLANKLINE>"
696          , "baz"
697          ]
698        `shouldParseTo` DocExamples [Example "foo" ["bar", "", "baz"]]
699
700      it "accepts unicode in examples" $ do
701        ">>> 灼眼\nシャナ" `shouldParseTo` DocExamples [Example "灼眼" ["シャナ"]]
702
703      context "when prompt is prefixed by whitespace" $ do
704        it "strips the exact same amount of whitespace from result lines" $ do
705          unlines [
706              "   >>> foo"
707            , "   bar"
708            , "   baz"
709            ] `shouldParseTo` DocExamples [Example "foo" ["bar", "baz"]]
710
711        it "preserves additional whitespace" $ do
712          unlines [
713              "   >>> foo"
714            , "    bar"
715            ] `shouldParseTo` DocExamples [Example "foo" [" bar"]]
716
717        it "keeps original if stripping is not possible" $ do
718          unlines [
719              "   >>> foo"
720            , " bar"
721            ] `shouldParseTo` DocExamples [Example "foo" [" bar"]]
722
723
724    context "when parsing paragraphs nested in lists" $ do
725      it "can nest the same type of list" $ do
726        "* foo\n\n    * bar" `shouldParseTo`
727          DocUnorderedList [ DocParagraph "foo"
728                             <> DocUnorderedList [DocParagraph "bar"]]
729
730      it "can nest another type of list inside" $ do
731        "* foo\n\n    1. bar" `shouldParseTo`
732          DocUnorderedList [ DocParagraph "foo"
733                             <> DocOrderedList [DocParagraph "bar"]]
734
735      it "can nest a code block inside" $ do
736        "* foo\n\n    @foo bar baz@" `shouldParseTo`
737          DocUnorderedList [ DocParagraph "foo"
738                             <> DocCodeBlock "foo bar baz"]
739
740        "* foo\n\n    @\n    foo bar baz\n    @" `shouldParseTo`
741          DocUnorderedList [ DocParagraph "foo"
742                             <> DocCodeBlock "foo bar baz\n"]
743
744      it "can nest more than one level" $ do
745        "* foo\n\n    * bar\n\n        * baz\n        qux" `shouldParseTo`
746          DocUnorderedList [ DocParagraph "foo"
747                             <> DocUnorderedList [ DocParagraph "bar"
748                                                   <> DocUnorderedList [DocParagraph "baz\nqux"]
749                                                 ]
750                           ]
751
752      it "won't fail on not fully indented paragraph" $ do
753        "* foo\n\n    * bar\n\n        * qux\nquux" `shouldParseTo`
754          DocUnorderedList [ DocParagraph "foo"
755                             <> DocUnorderedList [ DocParagraph "bar" ]
756                           , DocParagraph "qux\nquux"]
757
758
759      it "can nest definition lists" $ do
760        "[a]: foo\n\n    [b]: bar\n\n        [c]: baz\n        qux" `shouldParseTo`
761          DocDefList [ ("a", "foo"
762                             <> DocDefList [ ("b", "bar"
763                                                   <> DocDefList [("c", "baz\nqux")])
764                                           ])
765                     ]
766
767      it "can come back to top level with a different list" $ do
768        "* foo\n\n    * bar\n\n1. baz" `shouldParseTo`
769          DocUnorderedList [ DocParagraph "foo"
770                             <> DocUnorderedList [ DocParagraph "bar" ]
771                           ]
772          <> DocOrderedList [ DocParagraph "baz" ]
773
774      it "allows arbitrary initial indent of a list" $ do
775        unlines
776          [ "     * foo"
777          , "     * bar"
778          , ""
779          , "         * quux"
780          , ""
781          , "     * baz"
782          ]
783        `shouldParseTo`
784        DocUnorderedList
785          [ DocParagraph "foo"
786          , DocParagraph "bar"
787            <> DocUnorderedList [ DocParagraph "quux" ]
788          , DocParagraph "baz"
789          ]
790
791      it "definition lists can come back to top level with a different list" $ do
792        "[foo]: foov\n\n    [bar]: barv\n\n1. baz" `shouldParseTo`
793          DocDefList [ ("foo", "foov"
794                               <> DocDefList [ ("bar", "barv") ])
795                     ]
796          <> DocOrderedList [ DocParagraph "baz" ]
797
798      it "list order is preserved in presence of nesting + extra text" $ do
799        "1. Foo\n\n    > Some code\n\n2. Bar\n\nSome text"
800          `shouldParseTo`
801          DocOrderedList [ DocParagraph "Foo" <> DocCodeBlock "Some code"
802                         , DocParagraph "Bar"
803                         ]
804          <> DocParagraph (DocString "Some text")
805
806        "1. Foo\n\n2. Bar\n\nSome text"
807          `shouldParseTo`
808          DocOrderedList [ DocParagraph "Foo"
809                         , DocParagraph "Bar"
810                         ]
811          <> DocParagraph (DocString "Some text")
812
813    context "when parsing properties" $ do
814      it "can parse a single property" $ do
815        "prop> 23 == 23" `shouldParseTo` DocProperty "23 == 23"
816
817      it "can parse multiple subsequent properties" $ do
818        unlines [
819              "prop> 23 == 23"
820            , "prop> 42 == 42"
821            ]
822        `shouldParseTo`
823          DocProperty "23 == 23" <> DocProperty "42 == 42"
824
825      it "accepts unicode in properties" $ do
826        "prop> 灼眼のシャナ ≡ 愛" `shouldParseTo`
827          DocProperty "灼眼のシャナ ≡ 愛"
828
829      it "can deal with whitespace before and after the prop> prompt" $ do
830        "  prop>     xs == (reverse $ reverse xs)  " `shouldParseTo`
831          DocProperty "xs == (reverse $ reverse xs)"
832
833    context "when parsing unordered lists" $ do
834      it "parses a simple list" $ do
835        unlines [
836            " * one"
837          , " * two"
838          , " * three"
839          ]
840        `shouldParseTo` DocUnorderedList [
841            DocParagraph "one"
842          , DocParagraph "two"
843          , DocParagraph "three"
844          ]
845
846      it "ignores empty lines between list items" $ do
847        unlines [
848            "* one"
849          , ""
850          , "* two"
851          ]
852        `shouldParseTo` DocUnorderedList [
853            DocParagraph "one"
854          , DocParagraph "two"
855          ]
856
857      it "accepts an empty list item" $ do
858        "*" `shouldParseTo` DocUnorderedList [DocParagraph DocEmpty]
859
860      it "accepts multi-line list items" $ do
861        unlines [
862            "* point one"
863          , "  more one"
864          , "* point two"
865          , "more two"
866          ]
867        `shouldParseTo` DocUnorderedList [
868            DocParagraph "point one\n  more one"
869          , DocParagraph "point two\nmore two"
870          ]
871
872      it "accepts markup in list items" $ do
873        "* /foo/" `shouldParseTo` DocUnorderedList [DocParagraph (DocEmphasis "foo")]
874
875      it "requires empty lines between list and other paragraphs" $ do
876        unlines [
877            "foo"
878          , ""
879          , "* bar"
880          , ""
881          , "baz"
882          ]
883        `shouldParseTo` DocParagraph "foo" <> DocUnorderedList [DocParagraph "bar"] <> DocParagraph "baz"
884
885    context "when parsing ordered lists" $ do
886      it "parses a simple list" $ do
887        unlines [
888            " 1. one"
889          , " (1) two"
890          , " 3. three"
891          ]
892        `shouldParseTo` DocOrderedList [
893            DocParagraph "one"
894          , DocParagraph "two"
895          , DocParagraph "three"
896          ]
897
898      it "ignores empty lines between list items" $ do
899        unlines [
900            "1. one"
901          , ""
902          , "2. two"
903          ]
904        `shouldParseTo` DocOrderedList [
905            DocParagraph "one"
906          , DocParagraph "two"
907          ]
908
909      it "accepts an empty list item" $ do
910        "1." `shouldParseTo` DocOrderedList [DocParagraph DocEmpty]
911
912      it "accepts multi-line list items" $ do
913        unlines [
914            "1. point one"
915          , "  more one"
916          , "1. point two"
917          , "more two"
918          ]
919        `shouldParseTo` DocOrderedList [
920            DocParagraph "point one\n  more one"
921          , DocParagraph "point two\nmore two"
922          ]
923
924      it "accepts markup in list items" $ do
925        "1. /foo/" `shouldParseTo` DocOrderedList [DocParagraph (DocEmphasis "foo")]
926
927      it "requires empty lines between list and other paragraphs" $ do
928        unlines [
929            "foo"
930          , ""
931          , "1. bar"
932          , ""
933          , "baz"
934          ]
935        `shouldParseTo` DocParagraph "foo" <> DocOrderedList [DocParagraph "bar"] <> DocParagraph "baz"
936
937    context "when parsing definition lists" $ do
938      it "parses a simple list" $ do
939        unlines [
940            " [foo]: one"
941          , " [bar]: two"
942          , " [baz]: three"
943          ]
944        `shouldParseTo` DocDefList [
945            ("foo", "one")
946          , ("bar", "two")
947          , ("baz", "three")
948          ]
949
950      it "ignores empty lines between list items" $ do
951        unlines [
952            "[foo]: one"
953          , ""
954          , "[bar]: two"
955          ]
956        `shouldParseTo` DocDefList [
957            ("foo", "one")
958          , ("bar", "two")
959          ]
960
961      it "accepts an empty list item" $ do
962        "[foo]:" `shouldParseTo` DocDefList [("foo", DocEmpty)]
963
964      it "accepts multi-line list items" $ do
965        unlines [
966            "[foo]: point one"
967          , "  more one"
968          , "[bar]: point two"
969          , "more two"
970          ]
971        `shouldParseTo` DocDefList [
972            ("foo", "point one\n  more one")
973          , ("bar", "point two\nmore two")
974          ]
975
976      it "accepts markup in list items" $ do
977        "[foo]: /foo/" `shouldParseTo` DocDefList [("foo", DocEmphasis "foo")]
978
979      it "accepts markup for the label" $ do
980        "[/foo/]: bar" `shouldParseTo` DocDefList [(DocEmphasis "foo", "bar")]
981
982      it "requires empty lines between list and other paragraphs" $ do
983        unlines [
984            "foo"
985          , ""
986          , "[foo]: bar"
987          , ""
988          , "baz"
989          ]
990        `shouldParseTo` DocParagraph "foo" <> DocDefList [("foo", "bar")] <> DocParagraph "baz"
991
992      it "dose not require the colon (deprecated - this will be removed in a future release)" $ do
993        unlines [
994            " [foo] one"
995          , " [bar] two"
996          , " [baz] three"
997          ]
998        `shouldParseTo` DocDefList [
999            ("foo", "one")
1000          , ("bar", "two")
1001          , ("baz", "three")
1002          ]
1003
1004    context "when parsing consecutive paragraphs" $ do
1005      it "will not capture irrelevant consecutive lists" $ do
1006        unlines [ "   * bullet"
1007                , ""
1008                , ""
1009                , "   - different bullet"
1010                , ""
1011                , ""
1012                , "   (1) ordered"
1013                , " "
1014                , "   2. different bullet"
1015                , "   "
1016                , "   [cat]: kitten"
1017                , "   "
1018                , "   [pineapple]: fruit"
1019                ] `shouldParseTo`
1020          DocUnorderedList [ DocParagraph "bullet"
1021                           , DocParagraph "different bullet"]
1022          <> DocOrderedList [ DocParagraph "ordered"
1023                            , DocParagraph "different bullet"
1024                            ]
1025          <> DocDefList [ ("cat", "kitten")
1026                        , ("pineapple", "fruit")
1027                        ]
1028
1029    context "when parsing function documentation headers" $ do
1030      it "can parse a simple header" $ do
1031        "= Header 1\nHello." `shouldParseTo`
1032          (DocHeader (Header 1 "Header 1"))
1033          <> DocParagraph "Hello."
1034
1035      it "allow consecutive headers" $ do
1036        "= Header 1\n== Header 2" `shouldParseTo`
1037          DocHeader (Header 1 "Header 1")
1038          <> DocHeader (Header 2 "Header 2")
1039
1040      it "accepts markup in the header" $ do
1041        "= /Header/ __1__\nFoo" `shouldParseTo`
1042          DocHeader (Header 1 (DocEmphasis "Header" <> " " <> DocBold "1"))
1043          <> DocParagraph "Foo"
1044