File: Main.hs

package info (click to toggle)
haskell-prettyprinter 1.7.1-3
  • links: PTS
  • area: main
  • in suites: forky, sid, trixie
  • size: 372 kB
  • sloc: haskell: 2,453; ansic: 16; makefile: 6
file content (402 lines) | stat: -rw-r--r-- 15,343 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
{-# LANGUAGE CPP               #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-orphans   #-}

#include "version-compatibility-macros.h"

module Main (main) where



import           Control.Exception     (evaluate)
import qualified Data.ByteString.Lazy  as BSL
import qualified Data.Text             as T
import           Data.Text.PgpWordlist
import           Data.Word
import           System.Timeout        (timeout)

import           Prettyprinter
import           Prettyprinter.Internal.Debug
import           Prettyprinter.Render.Text
import           Prettyprinter.Render.Util.StackMachine (renderSimplyDecorated)

import Test.QuickCheck.Instances.Text ()
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck

import StripTrailingSpace

#if !(APPLICATIVE_MONAD)
import Control.Applicative
#endif
#if !(MONOID_IN_PRELUDE)
import Data.Monoid (mconcat)
#endif



main :: IO ()
main = defaultMain tests

tests :: TestTree
tests = testGroup "Tests"
    [ testGroup "Fusion"
        [ testProperty "Shallow fusion does not change rendering"
                       (fusionDoesNotChangeRendering Shallow)
        , testProperty "Deep fusion does not change rendering"
                       (fusionDoesNotChangeRendering Deep)
        ]
    , testStripTrailingSpace
    , testGroup "Performance tests"
        [ testCase "Grouping performance"
                   groupingPerformance
        , testCase "fillSep performance"
                   fillSepPerformance
        ]
    , testGroup "Regression tests"
        [ testCase "layoutSmart: softline behaves like a newline (#49)"
                   regressionLayoutSmartSoftline
        , testCase "alterAnnotationsS causes panic when removing annotations (#50)"
                   regressionAlterAnnotationsS
        , testCase "Bad fallback handling with align (#83)" badFallbackAlign
        , testGroup "removeTrailingWhitespace removes leading whitespace (#84)"
            [ testCase "Text node"
                       doNotRemoveLeadingWhitespaceText
            , testCase "Char node"
                       doNotRemoveLeadingWhitespaceChar
            , testCase "Text+Char nodes"
                       doNotRemoveLeadingWhitespaceTextChar
            ]
        , testGroup "removeTrailingWhitespace removes trailing line breaks (#86)"
            [ testCase "Keep lonely single trailing newline"
                       removeTrailingWhitespaceKeepLonelyTrailingNewline
            , testCase "Trailing newline with spaces"
                       removeTrailingNewlineWithSpaces
            , testCase "Keep single trailing newline"
                       removeTrailingWhitespaceKeepTrailingNewline
            , testCase "Reduce to single trailing newline"
                       removeTrailingWhitespaceInTrailingNewlines
            ]
        , testGroup "removeTrailingWhitespace restores indentation in the wrong spot (#93)"
            [ testCase "Don't restore indentation in the wrong spot"
                       removeTrailingWhitespaceDontRestoreIndentationInTheWrongSpot
            , testCase "Preserve leading indentation"
                       removeTrailingWhitespacePreserveIndentation
            ]
        , testGroup "Unbounded layout of hard linebreak within `group` fails (#91)"
            [ testCase "Line" regressionUnboundedGroupedLine
            , testCase "Line within align" regressionUnboundedGroupedLineWithinAlign
            ]
        , testCase "Indentation on otherwise empty lines results in trailing whitespace (#139)"
                   indentationShouldntCauseTrailingWhitespaceOnOtherwiseEmptyLines
        , testCase "Ribbon width should be computed with `floor` instead of `round` (#157)"
                   computeRibbonWidthWithFloor
        ]
    ]

fusionDoesNotChangeRendering :: FusionDepth -> Property
fusionDoesNotChangeRendering depth
  = forAllShow (arbitrary :: Gen (Doc Int)) (show . diag) (\doc ->
    forAll arbitrary (\layouter ->
        let tShow = T.pack . show
            render = renderSimplyDecorated id tShow tShow . layout layouter
            rendered = render doc
            renderedFused = render (fuse depth doc)
        in counterexample (mkCounterexample rendered renderedFused)
                          (render doc == render (fuse depth doc)) ))
  where
    mkCounterexample rendered renderedFused
      = (T.unpack . renderStrict . layoutPretty defaultLayoutOptions . vsep)
            [ "Unfused and fused documents render differently!"
            , "Unfused:"
            , indent 4 (pretty rendered)
            , "Fused:"
            , indent 4 (pretty renderedFused) ]

instance Arbitrary ann => Arbitrary (Doc ann) where
    arbitrary = document
    shrink = genericShrink -- Possibly not a good idea, may break invariants

document :: Arbitrary ann => Gen (Doc ann)
document = (dampen . frequency)
    [ (20, content)
    , (1, newlines)
    , (1, nestingAndAlignment)
    , (1, grouping)
    , (20, concatenationOfTwo)
    , (5, concatenationOfMany)
    , (1, enclosingOfOne)
    , (1, enclosingOfMany)
    , (1, annotated) ]

annotated :: Arbitrary ann => Gen (Doc ann)
annotated = annotate <$> arbitrary <*> document

content :: Gen (Doc ann)
content = frequency
    [ (1, pure emptyDoc)
    , (10, do word <- choose (minBound, maxBound :: Word8)
              let pgp8Word = toText (BSL.singleton word)
              pure (pretty pgp8Word) )
    , (1, (fmap pretty . elements . mconcat)
              [ ['a'..'z']
              , ['A'..'Z']
              , ['0'..'9']
              , "…_[]^!<>=&@:-()?*}{/\\#$|~`+%\"';" ] )
    ]

newlines :: Gen (Doc ann)
newlines = frequency
    [ (1, pure line)
    , (1, pure line')
    , (1, pure softline)
    , (1, pure softline')
    , (1, pure hardline) ]

nestingAndAlignment :: Arbitrary ann => Gen (Doc ann)
nestingAndAlignment = frequency
    [ (1, nest   <$> arbitrary <*> concatenationOfMany)
    , (1, group  <$> document)
    , (1, hang   <$> arbitrary <*> concatenationOfMany)
    , (1, indent <$> arbitrary <*> concatenationOfMany) ]

grouping :: Arbitrary ann => Gen (Doc ann)
grouping = frequency
    [ (1, align  <$> document)
    , (1, flatAlt <$> document <*> document) ]

concatenationOfTwo :: Arbitrary ann => Gen (Doc ann)
concatenationOfTwo = frequency
    [ (1, (<>) <$> document <*> document)
    , (1, (<+>) <$> document <*> document) ]

concatenationOfMany :: Arbitrary ann => Gen (Doc ann)
concatenationOfMany = frequency
    [ (1, hsep    <$> listOf document)
    , (1, vsep    <$> listOf document)
    , (1, fillSep <$> listOf document)
    , (1, sep     <$> listOf document)
    , (1, hcat    <$> listOf document)
    , (1, vcat    <$> listOf document)
    , (1, fillCat <$> listOf document)
    , (1, cat     <$> listOf document) ]

enclosingOfOne :: Arbitrary ann => Gen (Doc ann)
enclosingOfOne = frequency
    [ (1, squotes  <$> document)
    , (1, dquotes  <$> document)
    , (1, parens   <$> document)
    , (1, angles   <$> document)
    , (1, brackets <$> document)
    , (1, braces   <$> document) ]

enclosingOfMany :: Arbitrary ann => Gen (Doc ann)
enclosingOfMany = frequency
    [ (1, encloseSep <$> document <*> document <*> pure ", " <*> listOf document)
    , (1, list       <$> listOf document)
    , (1, tupled     <$> listOf document) ]

-- A 'show'able type representing a layout algorithm.
data Layouter ann
    = LayoutPretty LayoutOptions
    | LayoutSmart LayoutOptions
    | LayoutCompact
    -- LayoutWadlerLeijen (FittingPredicate ann) LayoutOptions
    deriving Show

instance Arbitrary (Layouter ann) where
    arbitrary = oneof
        [ LayoutPretty <$> arbitrary
        , LayoutSmart <$> arbitrary
        , pure LayoutCompact
        -- This produces inconsistent layouts that break the fusionDoesNotChangeRendering test
        -- , LayoutWadlerLeijen <$> arbitrary <*> arbitrary
        ]

{-
instance Show (FittingPredicate ann) where
    show _ = "<fitting predicate>"

instance Arbitrary (FittingPredicate ann) where
    arbitrary = FittingPredicate <$> arbitrary
-}

layout :: Layouter ann -> Doc ann -> SimpleDocStream ann
layout (LayoutPretty opts) = layoutPretty opts
layout (LayoutSmart opts) = layoutSmart opts
layout LayoutCompact = layoutCompact
-- layout (LayoutWadlerLeijen fp opts) = layoutWadlerLeijen fp opts

instance Arbitrary LayoutOptions where
    arbitrary = LayoutOptions <$> oneof
        [ AvailablePerLine <$> arbitrary <*> arbitrary
        , pure Unbounded
        ]

instance CoArbitrary (SimpleDocStream ann) where
    coarbitrary s0 = case s0 of
        SFail         -> variant' 0
        SEmpty        -> variant' 1
        SChar _c s    -> variant' 2 . coarbitrary s
        SText l _t s  -> variant' 3 . coarbitrary (l, s)
        SLine i s     -> variant' 4 . coarbitrary (i, s)
        SAnnPush _a s -> variant' 5 . coarbitrary s
        SAnnPop s     -> variant' 6 . coarbitrary s

instance CoArbitrary PageWidth where
    coarbitrary (AvailablePerLine a b) = variant' 0 . coarbitrary (a, b)
    coarbitrary Unbounded              = variant' 1

-- | Silences type defaulting warnings for 'variant'
variant' :: Int -> Gen a -> Gen a
variant' = variant

-- QuickCheck 2.8 does not have 'scale' yet, so for compatibility with older
-- releases we hand-code it here
dampen :: Gen a -> Gen a
dampen gen = sized (\n -> resize ((n*2) `quot` 3) gen)

docPerformanceTest :: Doc ann -> Assertion
docPerformanceTest doc
  = timeout 10000000 (forceDoc doc) >>= \doc' -> case doc' of
    Nothing -> assertFailure "Timeout!"
    Just _success -> pure ()
  where
    forceDoc :: Doc ann -> IO ()
    forceDoc = evaluate . foldr seq () . show

-- Deeply nested group/flatten calls can result in exponential performance.
--
-- See https://github.com/quchen/prettyprinter/issues/22
groupingPerformance :: Assertion
groupingPerformance = docPerformanceTest (pathological 1000)
  where
    pathological :: Int -> Doc ann
    pathological n = iterate (\x ->  hsep [x, sep []] ) "foobar" !! n

-- This test case was written because the `pretty` package had an issue with
-- this specific example.
--
-- See https://github.com/haskell/pretty/issues/32
fillSepPerformance :: Assertion
fillSepPerformance = docPerformanceTest (pathological 1000)
  where
    pathological :: Int -> Doc ann
    pathological n = iterate (\x -> fillSep ["a", x <+> "b"] ) "foobar" !! n

regressionLayoutSmartSoftline :: Assertion
regressionLayoutSmartSoftline
  = let doc = "a" <> softline <> "b"
        layouted :: SimpleDocStream ()
        layouted = layoutSmart (defaultLayoutOptions { layoutPageWidth = Unbounded }) doc
    in assertEqual "softline should be rendered as space page width is unbounded"
                   (SChar 'a' (SChar ' ' (SChar 'b' SEmpty)))
                   layouted

-- Removing annotations with alterAnnotationsS used to remove pushes, but not
-- pops, leading to imbalanced SimpleDocStreams.
regressionAlterAnnotationsS :: Assertion
regressionAlterAnnotationsS
  = let sdoc, sdoc' :: SimpleDocStream Int
        sdoc = layoutSmart defaultLayoutOptions (annotate 1 (annotate 2 (annotate 3 "a")))
        sdoc' = alterAnnotationsS (\ann -> case ann of 2 -> Just 2; _ -> Nothing) sdoc
    in assertEqual "" (SAnnPush 2 (SChar 'a' (SAnnPop SEmpty))) sdoc'

doNotRemoveLeadingWhitespaceText :: Assertion
doNotRemoveLeadingWhitespaceText
  = let sdoc :: SimpleDocStream ()
        sdoc = SLine 0 (SText 2 "  " (SChar 'x' SEmpty))
        sdoc' = SLine 2 (SChar 'x' SEmpty)
    in assertEqual "" sdoc' (removeTrailingWhitespace sdoc)

doNotRemoveLeadingWhitespaceChar :: Assertion
doNotRemoveLeadingWhitespaceChar
  = let sdoc :: SimpleDocStream ()
        sdoc = SLine 0 (SChar ' ' (SChar 'x' SEmpty))
        sdoc' = SLine 1 (SChar 'x' SEmpty)
    in assertEqual "" sdoc' (removeTrailingWhitespace sdoc)

doNotRemoveLeadingWhitespaceTextChar :: Assertion
doNotRemoveLeadingWhitespaceTextChar
  = let sdoc :: SimpleDocStream ()
        sdoc = SLine 0 (SChar ' ' (SText 2 "  " (SChar 'x' SEmpty)))
        sdoc' = SLine 3 (SChar 'x' SEmpty)
    in assertEqual "" sdoc' (removeTrailingWhitespace sdoc)

removeTrailingWhitespaceKeepTrailingNewline :: Assertion
removeTrailingWhitespaceKeepTrailingNewline
  = let sdoc :: SimpleDocStream ()
        sdoc = SLine 0 SEmpty
    in assertEqual "" sdoc (removeTrailingWhitespace sdoc)

removeTrailingNewlineWithSpaces :: Assertion
removeTrailingNewlineWithSpaces
  = let sdoc :: SimpleDocStream ()
        sdoc = SChar 'x' (SLine 2 (SText 2 "  " SEmpty))
        sdoc' = SChar 'x' (SLine 0 SEmpty)
    in assertEqual "" sdoc' (removeTrailingWhitespace sdoc)

removeTrailingWhitespaceKeepLonelyTrailingNewline :: Assertion
removeTrailingWhitespaceKeepLonelyTrailingNewline
  = let sdoc :: SimpleDocStream ()
        sdoc = SChar 'x' (SLine 0 SEmpty)
    in assertEqual "" sdoc (removeTrailingWhitespace sdoc)

removeTrailingWhitespaceInTrailingNewlines :: Assertion
removeTrailingWhitespaceInTrailingNewlines
  = let sdoc :: SimpleDocStream ()
        sdoc = SChar 'x' (SLine 2 (SLine 2 SEmpty))
        sdoc' = SChar 'x' (SLine 0 (SLine 0 SEmpty))
    in assertEqual "" sdoc' (removeTrailingWhitespace sdoc)

badFallbackAlign :: Assertion
badFallbackAlign
  = let x = group (flatAlt "Default" "Fallback")
        doc = "/" <> align (cat [x, x, "Too wide!!!!!"])
        actual = renderStrict (layoutSmart (LayoutOptions (AvailablePerLine 12 1)) doc)
        expected = "/Fallback\n Fallback\n Too wide!!!!!"
    in assertEqual "" expected actual

removeTrailingWhitespaceDontRestoreIndentationInTheWrongSpot :: Assertion
removeTrailingWhitespaceDontRestoreIndentationInTheWrongSpot
  = let sdoc :: SimpleDocStream ()
        sdoc = SLine 2 (SLine 0 (SChar 'x' SEmpty))
        sdoc' = SLine 0 (SLine 0 (SChar 'x' SEmpty))
    in assertEqual "" sdoc' (removeTrailingWhitespace sdoc)

removeTrailingWhitespacePreserveIndentation :: Assertion
removeTrailingWhitespacePreserveIndentation
  = let sdoc :: SimpleDocStream ()
        sdoc = SLine 2 (SChar 'x' SEmpty)
    in assertEqual "" sdoc (removeTrailingWhitespace sdoc)

regressionUnboundedGroupedLine :: Assertion
regressionUnboundedGroupedLine
  = let sdoc :: SimpleDocStream ()
        sdoc = layoutPretty (LayoutOptions Unbounded) (group hardline)
    in assertEqual "" (SLine 0 SEmpty) sdoc

regressionUnboundedGroupedLineWithinAlign :: Assertion
regressionUnboundedGroupedLineWithinAlign
  = let doc :: Doc ()
        doc = group (align ("x" <> hardline <> "y"))
        sdoc = layoutPretty (LayoutOptions Unbounded) doc
        expected = SChar 'x' (SLine 0 (SChar 'y' SEmpty))
    in assertEqual "" expected sdoc

indentationShouldntCauseTrailingWhitespaceOnOtherwiseEmptyLines :: Assertion
indentationShouldntCauseTrailingWhitespaceOnOtherwiseEmptyLines
  = let doc :: Doc ()
        doc = indent 1 ("x" <> hardline <> hardline <> "y" <> hardline)
        sdoc = layoutPretty (LayoutOptions Unbounded) doc
        expected = SChar ' ' (SChar 'x' (SLine 0 (SLine 1 (SChar 'y' (SLine 0 SEmpty)))))
    in assertEqual "" expected sdoc

computeRibbonWidthWithFloor :: Assertion
computeRibbonWidthWithFloor
  = let doc :: Doc ()
        doc = "a" <> softline' <> "b"
        sdoc = layoutPretty (LayoutOptions (AvailablePerLine 3 0.5)) doc
        expected = SChar 'a' (SLine 0 (SChar 'b' SEmpty))
    in assertEqual "" expected sdoc