@@ -16,12 +16,15 @@ module Ide.Plugin.Hlint
16
16
-- , provider
17
17
) where
18
18
import Refact.Apply
19
+ import Control.Arrow ((&&&) )
19
20
import Control.DeepSeq
20
21
import Control.Exception
21
22
import Control.Lens ((^.) )
22
23
import Control.Monad
23
24
import Control.Monad.Extra
24
25
import Control.Monad.Trans.Maybe
26
+ import Control.Monad.IO.Class
27
+ import Control.Monad.Trans.Except
25
28
import qualified Data.Aeson as Aeson
26
29
import Data.Aeson.Types (ToJSON (.. ), FromJSON (.. ), Value (.. ), Result (.. ))
27
30
import Data.Binary
@@ -39,6 +42,7 @@ import Data.Maybe
39
42
import qualified Data.Set as Set
40
43
import Data.Set (Set )
41
44
import qualified Data.Text as T
45
+ import qualified Data.Text.IO as T
42
46
import Data.Typeable
43
47
import Data.Typeable (Typeable )
44
48
import Development.IDE.Core.OfInterest
@@ -58,8 +62,10 @@ import GHC.Generics
58
62
import GHC.Generics (Generic )
59
63
import SrcLoc
60
64
import HscTypes (ModIface , ModSummary )
65
+ import Ide.Logger
61
66
import Ide.Types
62
67
import Ide.Plugin
68
+ import Ide.PluginUtils
63
69
import Language.Haskell.HLint
64
70
import Language.Haskell.HLint as Hlint
65
71
import qualified Language.Haskell.LSP.Core as LSP
@@ -97,10 +103,8 @@ type instance RuleResult GetHlintDiagnostics = ()
97
103
rules :: Rules ()
98
104
rules = do
99
105
define $ \ GetHlintDiagnostics file -> do
100
- (classify, hint) <- useNoFile_ GetHlintSettings
101
- eModuleEx <- getModuleEx file
102
- let getIdeas moduleEx = applyHints classify hint [moduleEx]
103
- return $ (diagnostics file (fmap getIdeas eModuleEx), Just () )
106
+ ideas <- getIdeas file
107
+ return $ (diagnostics file ideas, Just () )
104
108
105
109
hlintDataDir <- liftIO getExecutablePath
106
110
@@ -112,17 +116,6 @@ rules = do
112
116
113
117
where
114
118
115
- getModuleEx :: NormalizedFilePath -> Action (Either ParseError ModuleEx )
116
- getModuleEx fp = do
117
- #ifndef GHC_LIB
118
- pm <- getParsedModule fp
119
- let anns = pm_annotations pm
120
- let modu = pm_parsed_source pm
121
- return $ Right (createModuleEx anns modu)
122
- #else
123
- liftIO $ parseModuleEx defaultParseFlags (fromNormalizedFilePath fp) Nothing
124
- #endif
125
-
126
119
diagnostics :: NormalizedFilePath -> Either ParseError [Idea ] -> [FileDiagnostic ]
127
120
diagnostics file (Right ideas) =
128
121
[(file, ShowDiag , ideaToDiagnostic i) | i <- ideas, ideaSeverity i /= Ignore ]
@@ -163,6 +156,22 @@ rules = do
163
156
}
164
157
srcSpanToRange (UnhelpfulSpan _) = noRange
165
158
159
+ getIdeas :: NormalizedFilePath -> Action (Either ParseError [Idea ])
160
+ getIdeas nfp = do
161
+ (classify, hint) <- useNoFile_ GetHlintSettings
162
+ let applyHints' modEx = applyHints classify hint [modEx]
163
+ fmap (fmap applyHints') moduleEx
164
+ where moduleEx :: Action (Either ParseError ModuleEx )
165
+ moduleEx = do
166
+ #ifndef GHC_LIB
167
+ pm <- getParsedModule fnp
168
+ let anns = pm_annotations pm
169
+ let modu = pm_parsed_source pm
170
+ return $ Right (createModuleEx anns modu)
171
+ #else
172
+ liftIO $ parseModuleEx defaultParseFlags (fromNormalizedFilePath nfp) Nothing
173
+ #endif
174
+
166
175
-- ---------------------------------------------------------------------
167
176
168
177
data HlintUsage
@@ -257,22 +266,24 @@ data OneHint = OneHint
257
266
} deriving (Eq , Show )
258
267
259
268
applyOneCmd :: CommandFunction ApplyOneParams
260
- applyOneCmd _lf ide (AOP uri pos title) = do
269
+ applyOneCmd _lf ide (AOP uri pos title) = do
261
270
let oneHint = OneHint pos title
262
- let file = uriToFilePath' uri
263
- applyHint file (Just oneHint)
271
+ let file = maybe (error $ show uri ++ " is not a file" ) toNormalizedFilePath'
272
+ (uriToFilePath' uri)
273
+ res <- applyHint ide file (Just oneHint)
264
274
logm $ " applyOneCmd:file=" ++ show file
265
275
logm $ " applyOneCmd:res=" ++ show res
266
- case res of
267
- Left err -> return $ IdeResultFail
268
- ( IdeError PluginError (T. pack $ " applyOne: " ++ show err) Null )
269
- Right fs -> return ( IdeResultOk fs )
276
+ return $
277
+ case res of
278
+ Left err -> ( Left (responseError (T. pack $ " applyOne: " ++ show err)), Nothing )
279
+ Right fs -> ( Right Null , Just ( WorkspaceApplyEdit , ApplyWorkspaceEditParams fs) )
270
280
271
- applyHint :: FilePath -> Maybe OneHint -> IO (Either String WorkspaceEdit )
272
- applyHint fp mhint fileMap = do
281
+ applyHint :: IdeState -> NormalizedFilePath -> Maybe OneHint -> IO (Either String WorkspaceEdit )
282
+ applyHint ide nfp mhint =
273
283
runExceptT $ do
274
- ideas <- getIdeas fp mhint
275
- let commands = map (show &&& ideaRefactoring) ideas
284
+ ideas <- bimapExceptT showParseError id $ ExceptT $ liftIO $ runAction " applyHint" ide $ getIdeas nfp
285
+ let ideas' = maybe ideas (`filterIdeas` ideas) mhint
286
+ let commands = map (show &&& ideaRefactoring) ideas'
276
287
liftIO $ logm $ " applyHint:apply=" ++ show commands
277
288
-- set Nothing as "position" for "applyRefactorings" because
278
289
-- applyRefactorings expects the provided position to be _within_ the scope
@@ -289,18 +300,42 @@ applyHint fp mhint fileMap = do
289
300
-- If we provide "applyRefactorings" with "Just (1,13)" then
290
301
-- the "Redundant bracket" hint will never be executed
291
302
-- because SrcSpan (1,20,??,??) doesn't contain position (1,13).
303
+ let fp = fromNormalizedFilePath nfp
292
304
res <- liftIO $ (Right <$> applyRefactorings Nothing commands fp) `catches`
293
305
[ Handler $ \ e -> return (Left (show (e :: IOException )))
294
306
, Handler $ \ e -> return (Left (show (e :: ErrorCall )))
295
307
]
296
308
case res of
297
309
Right appliedFile -> do
298
- diff <- ExceptT $ Right <$> makeDiffResult fp (T. pack appliedFile) fileMap
299
- liftIO $ logm $ " applyHint:diff=" ++ show diff
300
- return diff
310
+ let uri = fromNormalizedUri (filePathToUri' nfp)
311
+ oldContent <- liftIO $ T. readFile fp
312
+ let wsEdit = diffText' True (uri, oldContent) (T. pack appliedFile) IncludeDeletions
313
+ liftIO $ logm $ " applyHint:diff=" ++ show wsEdit
314
+ ExceptT $ Right <$> (return wsEdit)
301
315
Left err ->
302
- throwE (show err)
316
+ throwE (show err)
317
+ where
318
+ -- | If we are only interested in applying a particular hint then
319
+ -- let's filter out all the irrelevant ideas
320
+ filterIdeas :: OneHint -> [Idea ] -> [Idea ]
321
+ filterIdeas (OneHint (Position l c) title) ideas =
322
+ let title' = T. unpack title
323
+ ideaPos = (srcSpanStartLine &&& srcSpanStartCol) . toRealSrcSpan . ideaSpan
324
+ in filter (\ i -> ideaHint i == title' && ideaPos i == (l+ 1 , c+ 1 )) ideas
325
+
326
+ toRealSrcSpan (RealSrcSpan real) = real
327
+ toRealSrcSpan (UnhelpfulSpan _) = error " No real souce span"
328
+
329
+ showParseError :: Hlint. ParseError -> String
330
+ showParseError (Hlint. ParseError location message content) =
331
+ unlines [show location, message, content]
303
332
333
+ -- | Map over both failure and success.
334
+ bimapExceptT :: Functor m => (e -> f ) -> (a -> b ) -> ExceptT e m a -> ExceptT f m b
335
+ bimapExceptT f g (ExceptT m) = ExceptT (fmap h m) where
336
+ h (Left e) = Left (f e)
337
+ h (Right a) = Right (g a)
338
+ {-# INLINE bimapExceptT #-}
304
339
-- ---------------------------------------------------------------------
305
340
{-
306
341
{- # LANGUAGE CPP #-}
0 commit comments