Skip to content

Commit 324b2a2

Browse files
committed
Autodetect elm version per file
Resolves #561
1 parent 3b2a0fd commit 324b2a2

File tree

6 files changed

+96
-61
lines changed

6 files changed

+96
-61
lines changed

elm-format.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ test-Suite elm-format-tests
180180
Util.ListTest
181181

182182
build-depends:
183+
filepath >= 1.4.2.1 && < 2,
183184
tasty >= 1.2 && < 2,
184185
tasty-golden >= 2.3.2 && < 3,
185186
tasty-hunit >= 0.10.0.1 && < 0.11,

src/ElmFormat.hs

Lines changed: 49 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,21 @@ import qualified Reporting.Result as Result
3434
import qualified Text.JSON
3535

3636

37-
resolveFile :: FileStore f => FilePath -> Free f (Either InputFileMessage [FilePath])
37+
resolveFile :: FileStore f => FilePath -> Free f (Either InputFileMessage [ElmFile])
3838
resolveFile path =
3939
do
4040
fileType <- FileStore.stat path
4141

4242
case fileType of
4343
FileStore.IsFile ->
44-
return $ Right [path]
44+
do
45+
elmFile <- FS.addElmVersion path
46+
return $ Right [elmFile]
4547

4648
FileStore.IsDirectory ->
4749
do
48-
elmFiles <- FS.findAllElmFiles path
50+
files <- FS.findAllElmFiles path
51+
elmFiles <- sequence $ fmap FS.addElmVersion files
4952
case elmFiles of
5053
[] -> return $ Left $ NoElmFiles path
5154
_ -> return $ Right elmFiles
@@ -74,7 +77,7 @@ collectErrors list =
7477
foldl step (Right []) list
7578

7679

77-
resolveFiles :: FileStore f => [FilePath] -> Free f (Either [InputFileMessage] [FilePath])
80+
resolveFiles :: FileStore f => [FilePath] -> Free f (Either [InputFileMessage] [ElmFile])
7881
resolveFiles inputFiles =
7982
do
8083
result <- collectErrors <$> mapM resolveFile inputFiles
@@ -83,23 +86,26 @@ resolveFiles inputFiles =
8386
return $ Left ls
8487

8588
Right files ->
86-
return $ Right $ concat files
89+
return $ Right $ concat $ files
90+
8791

92+
type ElmFile
93+
= (FilePath, ElmVersion)
8894

8995
data WhatToDo
90-
= FormatToFile FilePath FilePath
96+
= FormatToFile ElmFile FilePath
9197
| StdinToFile FilePath
92-
| FormatInPlace FilePath [FilePath]
98+
| FormatInPlace ElmFile [ElmFile]
9399
| StdinToStdout
94100
| ValidateStdin
95-
| ValidateFiles FilePath [FilePath]
96-
| FileToJson FilePath
101+
| ValidateFiles ElmFile [ElmFile]
102+
| FileToJson ElmFile
97103
| StdinToJson
98104

99105

100106
data Source
101107
= Stdin
102-
| FromFiles FilePath [FilePath]
108+
| FromFiles ElmFile [ElmFile]
103109

104110

105111
data Destination
@@ -109,7 +115,7 @@ data Destination
109115
| ToJson
110116

111117

112-
determineSource :: Bool -> Either [InputFileMessage] [FilePath] -> Either ErrorMessage Source
118+
determineSource :: Bool -> Either [InputFileMessage] [ElmFile] -> Either ErrorMessage Source
113119
determineSource stdin inputFiles =
114120
case ( stdin, inputFiles ) of
115121
( _, Left fileErrors ) -> Left $ BadInputFiles fileErrors
@@ -145,7 +151,7 @@ determineWhatToDo source destination =
145151
( FromFiles _ _, ToJson ) -> Left SingleOutputWithMultipleInputs
146152

147153

148-
determineWhatToDoFromConfig :: Flags.Config -> Either [InputFileMessage] [FilePath] -> Either ErrorMessage WhatToDo
154+
determineWhatToDoFromConfig :: Flags.Config -> Either [InputFileMessage] [(FilePath, ElmVersion)] -> Either ErrorMessage WhatToDo
149155
determineWhatToDoFromConfig config resolvedInputFiles =
150156
do
151157
source <- determineSource (Flags._stdin config) resolvedInputFiles
@@ -223,6 +229,7 @@ main'' elmFormatVersion_ experimental_ args =
223229
do
224230
let autoYes = Flags._yes config
225231
resolvedInputFiles <- Execute.run (Execute.forHuman autoYes) $ resolveFiles (Flags._input config)
232+
detectedElmVersion <- Execute.run (Execute.forHuman autoYes) $ ElmVersion.fromFile "."
226233

227234
case determineWhatToDoFromConfig config resolvedInputFiles of
228235
Left NoInputs ->
@@ -234,44 +241,24 @@ main'' elmFormatVersion_ experimental_ args =
234241
exitWithError message
235242

236243
Right whatToDo -> do
237-
elmVersionChoice <- case Flags._elmVersion config of
238-
Just v -> return $ Right v
239-
Nothing -> autoDetectElmVersion
244+
let elmVersionChoice = case Flags._elmVersion config of
245+
Just v -> v
246+
Nothing -> detectedElmVersion
240247

241-
case elmVersionChoice of
248+
let elmVersionResult = determineVersion elmVersionChoice (Flags._upgrade config)
249+
case elmVersionResult of
242250
Left message ->
243-
putStr message *> exitFailure
244-
245-
Right elmVersionChoice' -> do
246-
let elmVersionResult = determineVersion elmVersionChoice' (Flags._upgrade config)
251+
exitWithError message
247252

248-
case elmVersionResult of
249-
Left message ->
250-
exitWithError message
251-
252-
Right elmVersion ->
253-
do
254-
let run = case (Flags._validate config) of
255-
True -> Execute.run $ Execute.forMachine elmVersion True
256-
False -> Execute.run $ Execute.forHuman autoYes
257-
result <- run $ doIt elmVersion whatToDo
258-
if result
259-
then exitSuccess
260-
else exitFailure
261-
262-
263-
autoDetectElmVersion :: World m => m (Either String ElmVersion)
264-
autoDetectElmVersion =
265-
do
266-
hasElmPackageJson <- doesFileExist "elm-package.json"
267-
if hasElmPackageJson
268-
then
269-
do
270-
hasElmJson <- doesFileExist "elm.json"
271-
if hasElmJson
272-
then return $ Right Elm_0_19
273-
else return $ Right Elm_0_18
274-
else return $ Right Elm_0_19
253+
Right elmVersion ->
254+
do
255+
let run = case (Flags._validate config) of
256+
True -> Execute.run $ Execute.forMachine elmVersion True
257+
False -> Execute.run $ Execute.forHuman autoYes
258+
result <- run $ doIt elmVersion config whatToDo
259+
if result
260+
then exitSuccess
261+
else exitFailure
275262

276263

277264
validate :: ElmVersion -> (FilePath, Text.Text) -> Either InfoMessage ()
@@ -363,33 +350,41 @@ logErrorOr fn result =
363350
fn value *> return True
364351

365352

366-
doIt :: (InputConsole f, OutputConsole f, InfoFormatter f, FileStore f, FileWriter f) => ElmVersion -> WhatToDo -> Free f Bool
367-
doIt elmVersion whatToDo =
353+
354+
doIt :: (InputConsole f, OutputConsole f, InfoFormatter f, FileStore f, FileWriter f) => ElmVersion -> Flags.Config -> WhatToDo -> Free f Bool
355+
doIt elmVersion config whatToDo =
356+
let
357+
getVersion fileDetectedElmVersion =
358+
case (Flags._upgrade config, Flags._elmVersion config) of
359+
(True, _) -> elmVersion
360+
(False, Just v) -> v
361+
(False, Nothing) -> fileDetectedElmVersion
362+
in
368363
case whatToDo of
369364
ValidateStdin ->
370365
(validate elmVersion <$> readStdin) >>= logError
371366

372367
ValidateFiles first rest ->
373368
all id <$> mapM validateFile (first:rest)
374-
where validateFile file = (validate elmVersion <$> ElmFormat.readFile file) >>= logError
369+
where validateFile (file, fileElmVersion) = (validate (getVersion fileElmVersion) <$> ElmFormat.readFile file) >>= logError
375370

376371
StdinToStdout ->
377372
(fmap getOutputText <$> format elmVersion <$> readStdin) >>= logErrorOr OutputConsole.writeStdout
378373

379374
StdinToFile outputFile ->
380375
(fmap getOutputText <$> format elmVersion <$> readStdin) >>= logErrorOr (FileWriter.overwriteFile outputFile)
381376

382-
FormatToFile inputFile outputFile ->
383-
(fmap getOutputText <$> format elmVersion <$> ElmFormat.readFile inputFile) >>= logErrorOr (FileWriter.overwriteFile outputFile)
377+
FormatToFile (inputFile, fileElmVersion) outputFile ->
378+
(fmap getOutputText <$> format (getVersion fileElmVersion) <$> ElmFormat.readFile inputFile) >>= logErrorOr (FileWriter.overwriteFile outputFile)
384379

385380
FormatInPlace first rest ->
386381
do
387-
canOverwrite <- approve $ FilesWillBeOverwritten (first:rest)
382+
canOverwrite <- approve $ FilesWillBeOverwritten $ fmap fst $ (first:rest)
388383
if canOverwrite
389384
then all id <$> mapM formatFile (first:rest)
390385
else return True
391386
where
392-
formatFile file = (format elmVersion <$> ElmFormat.readFile file) >>= logErrorOr ElmFormat.updateFile
387+
formatFile (file, fileElmVersion) = (format (getVersion fileElmVersion) <$> ElmFormat.readFile file) >>= logErrorOr ElmFormat.updateFile
393388

394389
StdinToJson ->
395390
(fmap (Text.pack . Text.JSON.encode . AST.Json.showModule) <$> parseModule elmVersion <$> readStdin) >>= logErrorOr OutputConsole.writeStdout

src/ElmFormat/Filesystem.hs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module ElmFormat.Filesystem where
33
import Control.Monad.Free
44
import ElmFormat.FileStore
55
import System.FilePath ((</>))
6+
import ElmVersion
67
import qualified System.FilePath as FilePath
78

89

@@ -63,3 +64,7 @@ findAllElmFiles inputFile =
6364
hasFilename :: String -> FilePath -> Bool
6465
hasFilename name path =
6566
name == FilePath.takeFileName path
67+
68+
addElmVersion :: FileStore f => FilePath -> Free f (FilePath, ElmVersion)
69+
addElmVersion path =
70+
fmap ((,) path) $ ElmVersion.fromFile path

src/ElmVersion.hs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{-# OPTIONS_GHC -Wall #-}
22
module ElmVersion where
33

4+
import Control.Monad.Free
5+
import System.FilePath ((</>), takeDirectory)
6+
import ElmFormat.FileStore
47

58
data ElmVersion
69
= Elm_0_16 -- TODO: remove 0_16
@@ -58,3 +61,22 @@ style_0_19_cannotExposeOpenListing elmVersion =
5861
Elm_0_18 -> False
5962
Elm_0_18_Upgrade -> False
6063
_ -> True
64+
65+
fromFile :: FileStore f => FilePath -> Free f ElmVersion
66+
fromFile path =
67+
do
68+
let dir = takeDirectory (path)
69+
elmPackageJson <- stat (path </> "elm-package.json")
70+
case elmPackageJson of
71+
IsFile ->
72+
do
73+
elmJson <- stat (path </> "elm.json")
74+
return $ case elmJson of
75+
IsFile -> Elm_0_19
76+
_ -> Elm_0_18
77+
78+
_ | path == dir ->
79+
return Elm_0_19
80+
81+
_ ->
82+
fromFile $ dir

tests/ElmFormat/TestWorld.hs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import qualified Data.Map.Strict as Dict
1414
import qualified Data.Text.Lazy as Text
1515
import qualified Data.Text.Lazy.Encoding as Text
1616
import qualified Data.Text as StrictText
17+
import qualified System.FilePath as FilePath
1718

1819

1920
data TestWorldState =
@@ -43,20 +44,24 @@ fullStderr state =
4344
|> reverse
4445
|> concat
4546

46-
47+
{-| Files paths are normalized to allow:
48+
- "./elm-package.json" and "package.json" for example to point to the same file
49+
(this is required for Elm version autodetection to work correctly in tests)
50+
- POSIX paths like "src/test.elm" to also work on Windows in tests
51+
-}
4752
instance World (State.State TestWorldState) where
4853
doesFileExist path =
4954
do
5055
state <- State.get
51-
return $ Dict.member path (filesystem state)
56+
return $ Dict.member (FilePath.normalise path) (filesystem state)
5257

5358
doesDirectoryExist _path =
5459
return False
5560

5661
readFile path =
5762
do
5863
state <- State.get
59-
case Dict.lookup path (filesystem state) of
64+
case Dict.lookup (FilePath.normalise path) (filesystem state) of
6065
Nothing ->
6166
error $ path ++ ": does not exist"
6267

@@ -71,7 +76,7 @@ instance World (State.State TestWorldState) where
7176
writeFile path content =
7277
do
7378
state <- State.get
74-
State.put $ state { filesystem = Dict.insert path content (filesystem state) }
79+
State.put $ state { filesystem = Dict.insert (FilePath.normalise path) content (filesystem state) }
7580

7681
writeUtf8File path content =
7782
writeFile path (StrictText.unpack content)
@@ -152,7 +157,7 @@ assertOutput :: [(String, String)] -> TestWorldState -> Assertion
152157
assertOutput expectedFiles context =
153158
assertBool
154159
("Expected filesystem to contain: " ++ show expectedFiles ++ "\nActual: " ++ show (filesystem context))
155-
(all (\(k,v) -> Dict.lookup k (filesystem context) == Just v) expectedFiles)
160+
(all (\(k,v) -> Dict.lookup (FilePath.normalise k) (filesystem context) == Just v) expectedFiles)
156161

157162

158163
goldenStdout :: String -> FilePath -> TestWorldState -> TestTree
@@ -187,12 +192,12 @@ init = testWorld []
187192

188193
uploadFile :: String -> String -> TestWorld -> TestWorld
189194
uploadFile name content world =
190-
world { filesystem = Dict.insert name content (filesystem world) }
195+
world { filesystem = Dict.insert (FilePath.normalise name) content (filesystem world) }
191196

192197

193198
downloadFile :: String -> TestWorld -> Maybe String
194199
downloadFile name world =
195-
Dict.lookup name (filesystem world)
200+
Dict.lookup (FilePath.normalise name) (filesystem world)
196201

197202

198203
installProgram :: String -> ([String] -> State.State TestWorld ()) -> TestWorld -> TestWorld

tests/Integration/CliTest.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ tests =
4646
|> TestWorld.uploadFile "elm-package.json" "{\"elm-version\": \"0.18.0 <= v < 0.19.0\"}"
4747
|> run "elm-format" ["test.elm", "--validate"]
4848
|> expectExit 0
49+
, testCase "for mixed Elm 0.18 and 0.19" $ world
50+
|> TestWorld.uploadFile "0.18/src/test.elm" "module Main exposing (f)\n\n\nf =\n '\\x2000'\n"
51+
|> TestWorld.uploadFile "0.18/elm-package.json" "{\"elm-version\": \"0.18.0 <= v < 0.19.0\"}"
52+
|> TestWorld.uploadFile "0.19/src/test.elm" "module Main exposing (f)\n\n\nf =\n '\\u{2000}'\n"
53+
|> TestWorld.uploadFile "0.19/elm.json" "{\"elm-version\": \"0.19.0 <= v < 0.20.0\"}"
54+
|> run "elm-format" ["0.18/src/test.elm", "0.19/src/test.elm", "--validate"]
55+
|> expectExit 0
4956
, testCase "default to Elm 0.19" $ world
5057
|> TestWorld.uploadFile "test.elm" "module Main exposing (f)\n\n\nf =\n '\\u{2000}'\n"
5158
|> run "elm-format" ["test.elm", "--validate"]

0 commit comments

Comments
 (0)