Skip to content

Commit db11a89

Browse files
committed
Implement verify flag to check that the output parses and formats the same as the input
1 parent bb39f76 commit db11a89

File tree

3 files changed

+63
-31
lines changed

3 files changed

+63
-31
lines changed

main/Main.hs

+21-15
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
module Main where
1010

1111
import Control.Concurrent (Chan, forkIO, newChan, readChan, writeChan)
12-
import Data.Bifunctor (first)
1312
import Data.Either (lefts)
1413
import Data.Text (Text)
1514
import Data.Version (showVersion)
@@ -24,7 +23,7 @@ import System.Posix.Signals (Handler(..), installHandler, keyboardSignal)
2423

2524
import qualified Data.Text.IO as TextIO (getContents, hPutStr, putStr)
2625

27-
import Nixfmt
26+
import qualified Nixfmt
2827
import System.IO.Atomic (withOutputFile)
2928
import System.IO.Utf8 (readFileUtf8, withUtf8StdHandles)
3029

@@ -36,6 +35,7 @@ data Nixfmt = Nixfmt
3635
, width :: Width
3736
, check :: Bool
3837
, quiet :: Bool
38+
, verify :: Bool
3939
} deriving (Show, Data, Typeable)
4040

4141
options :: Nixfmt
@@ -44,30 +44,28 @@ options = Nixfmt
4444
, width = 80 &= help "Maximum width in characters"
4545
, check = False &= help "Check whether files are formatted"
4646
, quiet = False &= help "Do not report errors"
47+
, verify = False &= help "Check that the output parses and formats the same as the input"
4748
} &= summary ("nixfmt v" ++ showVersion version)
4849
&= help "Format Nix source code"
4950

50-
format' :: Width -> FilePath -> Text -> Either String Text
51-
format' w path = first errorBundlePretty . format w path
52-
5351
data Target = Target
5452
{ tDoRead :: IO Text
5553
, tPath :: FilePath
5654
, tDoWrite :: Text -> IO ()
5755
}
5856

59-
formatTarget :: Width -> Target -> IO Result
60-
formatTarget w Target{tDoRead, tPath, tDoWrite} = do
57+
formatTarget :: Formatter -> Target -> IO Result
58+
formatTarget format Target{tDoRead, tPath, tDoWrite} = do
6159
contents <- tDoRead
62-
let formatted = format' w tPath contents
60+
let formatted = format tPath contents
6361
mapM tDoWrite formatted
6462

6563
-- | Return an error if target could not be parsed or was not formatted
6664
-- correctly.
67-
checkTarget :: Width -> Target -> IO Result
68-
checkTarget w Target{tDoRead, tPath} = do
65+
checkTarget :: Formatter -> Target -> IO Result
66+
checkTarget format Target{tDoRead, tPath} = do
6967
contents <- tDoRead
70-
return $ case format' w tPath contents of
68+
return $ case format tPath contents of
7169
Left err -> Left err
7270
Right formatted
7371
| formatted == contents -> Right ()
@@ -87,16 +85,24 @@ toTargets :: Nixfmt -> [Target]
8785
toTargets Nixfmt{ files = [] } = [stdioTarget]
8886
toTargets Nixfmt{ files = paths } = map fileTarget paths
8987

90-
toOperation :: Nixfmt -> Target -> IO Result
91-
toOperation Nixfmt{ width = w, check = True } = checkTarget w
92-
toOperation Nixfmt{ width = w } = formatTarget w
88+
type Formatter = FilePath -> Text -> Either String Text
89+
90+
toFormatter :: Nixfmt -> Formatter
91+
toFormatter Nixfmt{ width, verify = True } = Nixfmt.formatVerify width
92+
toFormatter Nixfmt{ width, verify = False } = Nixfmt.format width
93+
94+
type Operation = Formatter -> Target -> IO Result
95+
96+
toOperation :: Nixfmt -> Operation
97+
toOperation Nixfmt{ check = True } = checkTarget
98+
toOperation Nixfmt{ } = formatTarget
9399

94100
toWriteError :: Nixfmt -> String -> IO ()
95101
toWriteError Nixfmt{ quiet = False } = hPutStrLn stderr
96102
toWriteError Nixfmt{ quiet = True } = const $ return ()
97103

98104
toJobs :: Nixfmt -> [IO Result]
99-
toJobs opts = map (toOperation opts) $ toTargets opts
105+
toJobs opts = map (toOperation opts $ toFormatter opts) $ toTargets opts
100106

101107
-- TODO: Efficient parallel implementation. This is just a sequential stub.
102108
-- This was originally implemented using parallel-io, but it gave a factor two

src/Nixfmt.hs

+24-3
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,40 @@ module Nixfmt
88
( errorBundlePretty
99
, ParseErrorBundle
1010
, format
11+
, formatVerify
1112
) where
1213

14+
import Data.Bifunctor (bimap, first)
1315
import Data.Text (Text)
14-
import Text.Megaparsec (parse)
16+
import qualified Text.Megaparsec as Megaparsec (parse)
1517
import Text.Megaparsec.Error (errorBundlePretty)
1618

1719
import Nixfmt.Parser (file)
1820
import Nixfmt.Predoc (layout)
1921
import Nixfmt.Pretty ()
2022
import Nixfmt.Types (ParseErrorBundle)
2123

24+
type Width = Int
25+
2226
-- | @format w filename source@ returns either a parsing error specifying a
2327
-- failure in @filename@ or a formatted version of @source@ with a maximum width
2428
-- of @w@ columns where possible.
25-
format :: Int -> FilePath -> Text -> Either ParseErrorBundle Text
26-
format width filename = fmap (layout width) . parse file filename
29+
format :: Width -> FilePath -> Text -> Either String Text
30+
format width filename
31+
= bimap errorBundlePretty (layout width)
32+
. Megaparsec.parse file filename
33+
34+
formatVerify :: Width -> FilePath -> Text -> Either String Text
35+
formatVerify width path unformatted = do
36+
unformattedParsed <- parse unformatted
37+
let formattedOnce = layout width unformattedParsed
38+
formattedOnceParsed <- parse formattedOnce
39+
let formattedTwice = layout width formattedOnceParsed
40+
if formattedOnceParsed /= unformattedParsed
41+
then pleaseReport "The formatted file parses differently."
42+
else if formattedOnce /= formattedTwice
43+
then pleaseReport "Nixfmt is not idempotent on this file."
44+
else Right formattedOnce
45+
where
46+
parse = first errorBundlePretty . Megaparsec.parse file path
47+
pleaseReport x = Left $ x <> " This is a bug in nixfmt. Please report it at https://github.com/serokell/nixfmt"

src/Nixfmt/Types.hs

+18-13
Original file line numberDiff line numberDiff line change
@@ -24,39 +24,44 @@ data Trivium
2424
= EmptyLine
2525
| LineComment Text
2626
| BlockComment [Text]
27-
deriving (Show)
27+
deriving (Eq, Show)
2828

2929
type Trivia = [Trivium]
3030

31-
newtype TrailingComment = TrailingComment Text deriving (Show)
31+
newtype TrailingComment = TrailingComment Text deriving (Eq, Show)
3232

3333
data Ann a
3434
= Ann a (Maybe TrailingComment) Trivia
3535
deriving (Show)
3636

37+
-- | Equality of annotated syntax is defines as equality of their corresponding
38+
-- semantics, thus ignoring the annotations.
39+
instance Eq a => Eq (Ann a) where
40+
Ann x _ _ == Ann y _ _ = x == y
41+
3742
type Leaf = Ann Token
3843

3944
data StringPart
4045
= TextPart Text
4146
| Interpolation Leaf Expression Token
42-
deriving (Show)
47+
deriving (Eq, Show)
4348

4449
type String = Ann [[StringPart]]
4550

4651
data SimpleSelector
4752
= IDSelector Leaf
4853
| InterpolSelector (Ann StringPart)
4954
| StringSelector String
50-
deriving (Show)
55+
deriving (Eq, Show)
5156

5257
data Selector
5358
= Selector (Maybe Leaf) SimpleSelector (Maybe (Leaf, Term))
54-
deriving (Show)
59+
deriving (Eq, Show)
5560

5661
data Binder
5762
= Inherit Leaf (Maybe Term) [Leaf] Leaf
5863
| Assignment [Selector] Leaf Expression Leaf
59-
deriving (Show)
64+
deriving (Eq, Show)
6065

6166
data Term
6267
= Token Leaf
@@ -65,18 +70,18 @@ data Term
6570
| Set (Maybe Leaf) Leaf [Binder] Leaf
6671
| Selection Term [Selector]
6772
| Parenthesized Leaf Expression Leaf
68-
deriving (Show)
73+
deriving (Eq, Show)
6974

7075
data ParamAttr
7176
= ParamAttr Leaf (Maybe (Leaf, Expression)) (Maybe Leaf)
7277
| ParamEllipsis Leaf
73-
deriving (Show)
78+
deriving (Eq, Show)
7479

7580
data Parameter
7681
= IDParameter Leaf
7782
| SetParameter Leaf [ParamAttr] Leaf
7883
| ContextParameter Parameter Leaf Parameter
79-
deriving (Show)
84+
deriving (Eq, Show)
8085

8186
data Expression
8287
= Term Term
@@ -91,11 +96,11 @@ data Expression
9196
| MemberCheck Expression Leaf [Selector]
9297
| Negation Leaf Expression
9398
| Inversion Leaf Expression
94-
deriving (Show)
99+
deriving (Eq, Show)
95100

96101
data File
97102
= File Leaf Expression
98-
deriving (Show)
103+
deriving (Eq, Show)
99104

100105
data Token
101106
= Integer Int
@@ -165,12 +170,12 @@ data Fixity
165170
| InfixN
166171
| InfixR
167172
| Postfix
168-
deriving (Show)
173+
deriving (Eq, Show)
169174

170175
data Operator
171176
= Op Fixity Token
172177
| Apply
173-
deriving (Show)
178+
deriving (Eq, Show)
174179

175180
-- | A list of lists of operators where lists that come first contain operators
176181
-- that bind more strongly.

0 commit comments

Comments
 (0)