splint 1.0.2.1 → 2.0.0.0
raw patch · 13 files changed
+212/−311 lines, 13 filesdep ~basedep ~containersdep ~ghc
Dependency ranges changed: base, containers, ghc, hlint, stm
Files
- LICENSE.markdown +0/−13
- LICENSE.txt +13/−0
- README.markdown +3/−6
- source/library/Splint.hs +73/−0
- source/library/Splint/RemoteData.hs +8/−0
- source/library/Splint/Replacement.hs +16/−0
- source/library/Splint/Settings.hs +71/−0
- splint.cabal +28/−25
- src/ghc-8.10/Splint.hs +0/−65
- src/ghc-9.0/Splint.hs +0/−65
- src/ghc-9.2/Splint.hs +0/−62
- src/lib/Splint/RemoteData.hs +0/−8
- src/lib/Splint/Settings.hs +0/−67
− LICENSE.markdown
@@ -1,13 +0,0 @@-Copyright 2022 Taylor Fausak--Permission to use, copy, modify, and/or distribute this software for any-purpose with or without fee is hereby granted, provided that the above-copyright notice and this permission notice appear in all copies.--THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH-REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND-FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,-INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM-LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR-OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR-PERFORMANCE OF THIS SOFTWARE.
+ LICENSE.txt view
@@ -0,0 +1,13 @@+Copyright 2024 Taylor Fausak++Permission to use, copy, modify, and/or distribute this software for any+purpose with or without fee is hereby granted, provided that the above+copyright notice and this permission notice appear in all copies.++THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR+PERFORMANCE OF THIS SOFTWARE.
README.markdown view
@@ -1,10 +1,7 @@ # Splint -[](https://github.com/tfausak/splint/actions/new)-[](https://hackage.haskell.org/package/splint)-[](https://www.stackage.org/package/splint)--:warning: This package is not maintained anymore.+[](https://github.com/tfausak/splint/actions/workflows/ci.yml)+[](https://hackage.haskell.org/package/splint) Splint makes [HLint 3][] available as a [GHC source plugin][]. It is similar to [hlint-source-plugin][] by Ollie Charles, except that it doesn't have to@@ -31,7 +28,7 @@ ``` Main.hs:1:8: warning: Use concatMap- Perhaps: print (concatMap pure ['a' .. 'z'])+ Suggested fix: print (concatMap pure ['a' .. 'z']) | 1 | main = print . concat $ map pure [ 'a' .. 'z' ] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ source/library/Splint.hs view
@@ -0,0 +1,73 @@+module Splint where++import qualified Data.Maybe as Maybe+import qualified GHC.Data.Bag+import qualified GHC.Driver.Config.Diagnostic+import qualified GHC.Driver.Errors+import qualified GHC.Driver.Errors.Types+import qualified GHC.Hs+import qualified GHC.Plugins+import qualified GHC.Types.Error+import qualified GHC.Utils.Error+import qualified GHC.Utils.Logger+import qualified Language.Haskell.HLint as HLint+import qualified Splint.Replacement as Replacement+import qualified Splint.Settings as Settings++plugin :: GHC.Plugins.Plugin+plugin =+ GHC.Plugins.defaultPlugin+ { GHC.Plugins.parsedResultAction = parsedResultAction,+ GHC.Plugins.pluginRecompile = GHC.Plugins.purePlugin+ }++parsedResultAction ::+ [GHC.Plugins.CommandLineOption] ->+ modSummary ->+ GHC.Plugins.ParsedResult ->+ GHC.Plugins.Hsc GHC.Plugins.ParsedResult+parsedResultAction commandLineOptions _modSummary parsedResult = do+ logger <- GHC.Utils.Logger.getLogger+ dynFlags <- GHC.Plugins.getDynFlags+ let ghcMessageOpts = GHC.Driver.Config.Diagnostic.initPrintConfig dynFlags+ diagOpts = GHC.Driver.Config.Diagnostic.initDiagOpts dynFlags+ GHC.Plugins.liftIO $ do+ settings <- Settings.load commandLineOptions+ GHC.Driver.Errors.printOrThrowDiagnostics logger ghcMessageOpts diagOpts+ . GHC.Types.Error.mkMessages+ . GHC.Data.Bag.listToBag+ . fmap (ideaToWarnMsg diagOpts)+ . uncurry HLint.applyHints settings+ . pure+ . HLint.createModuleEx+ . GHC.Hs.hpm_module+ $ GHC.Plugins.parsedResultModule parsedResult+ pure parsedResult++ideaToWarnMsg :: GHC.Utils.Error.DiagOpts -> HLint.Idea -> GHC.Driver.Errors.Types.WarnMsg+ideaToWarnMsg diagOpts idea =+ let srcSpan = HLint.ideaSpan idea+ ghcHints =+ fmap (GHC.Types.Error.UnknownHint . Replacement.fromString)+ . Maybe.maybeToList+ $ HLint.ideaTo idea+ decoratedSDoc =+ GHC.Types.Error.mkDecorated $+ GHC.Plugins.text+ (HLint.ideaHint idea)+ : fmap+ (GHC.Plugins.text . mappend "Note: " . show)+ (HLint.ideaNote idea)+ diagnosticReason = case HLint.ideaSeverity idea of+ HLint.Ignore -> GHC.Types.Error.WarningWithoutFlag+ HLint.Suggestion -> GHC.Types.Error.WarningWithoutFlag+ HLint.Warning -> GHC.Types.Error.WarningWithoutFlag+ HLint.Error -> GHC.Types.Error.ErrorWithoutFlag+ diagnosticMessage =+ GHC.Types.Error.DiagnosticMessage+ { GHC.Types.Error.diagHints = ghcHints,+ GHC.Types.Error.diagMessage = decoratedSDoc,+ GHC.Types.Error.diagReason = diagnosticReason+ }+ ghcMessage = GHC.Driver.Errors.Types.ghcUnknownMessage diagnosticMessage+ in GHC.Utils.Error.mkPlainMsgEnvelope diagOpts srcSpan ghcMessage
+ source/library/Splint/RemoteData.hs view
@@ -0,0 +1,8 @@+module Splint.RemoteData where++data RemoteData e a+ = NotAsked+ | Loading+ | Failure e+ | Success a+ deriving (Eq, Show)
+ source/library/Splint/Replacement.hs view
@@ -0,0 +1,16 @@+module Splint.Replacement where++import qualified GHC.Plugins as Plugin++newtype Replacement+ = Replacement String+ deriving (Eq, Show)++instance Plugin.Outputable Replacement where+ ppr = Plugin.text . toString++fromString :: String -> Replacement+fromString = Replacement++toString :: Replacement -> String+toString (Replacement x) = x
+ source/library/Splint/Settings.hs view
@@ -0,0 +1,71 @@+module Splint.Settings where++import qualified Control.Concurrent.STM as Stm+import qualified Control.Exception as Exception+import qualified Data.Map as Map+import qualified Language.Haskell.HLint as HLint+import qualified Splint.RemoteData as RemoteData+import qualified System.IO.Unsafe as Unsafe++type Settings = ([HLint.Classify], HLint.Hint)++-- | Getting settings is not instantaneous. Since settings are usually reused+-- between modules, it makes sense to cache them. However each module can+-- potentially customize its settings using the @OPTIONS_GHC@ pragma. This+-- variable is used as a cache of settings keyed on the command line options.+cache ::+ Stm.TVar+ (Map.Map [String] (RemoteData.RemoteData Exception.IOException Settings))+cache = Unsafe.unsafePerformIO $ Stm.newTVarIO Map.empty+{-# NOINLINE cache #-}++-- | Even though we cache settings based on command line options, we only want+-- to load settings one at a time. Practically this is to work around a bug in+-- GHC. But aside from that, loading settings calls @withArgs@ and doing that+-- simultaneously in separate threads is dubious.+-- <https://gitlab.haskell.org/ghc/ghc/issues/18261>+semaphore :: Stm.TMVar ()+semaphore = Unsafe.unsafePerformIO $ Stm.newTMVarIO ()+{-# NOINLINE semaphore #-}++withTMVar :: Stm.TMVar a -> (a -> IO b) -> IO b+withTMVar x =+ Exception.bracket+ (Stm.atomically $ Stm.takeTMVar x)+ (Stm.atomically . Stm.putTMVar x)++load :: [String] -> IO Settings+load commandLineOptions = do+ remoteData <- Stm.atomically $ do+ settings <- Stm.readTVar cache+ let remoteData =+ Map.findWithDefault RemoteData.NotAsked commandLineOptions settings+ case remoteData of+ RemoteData.NotAsked ->+ Stm.modifyTVar cache $ Map.insert commandLineOptions RemoteData.Loading+ RemoteData.Loading -> Stm.retry+ _ -> pure ()+ pure remoteData+ case remoteData of+ RemoteData.NotAsked -> do+ result <-+ withTMVar semaphore+ . const+ . Exception.try+ $ HLint.argsSettings commandLineOptions+ case result of+ Left ioException -> do+ Stm.atomically+ . Stm.modifyTVar cache+ . Map.insert commandLineOptions+ $ RemoteData.Failure ioException+ Exception.throwIO ioException+ Right (_, classifies, hint) -> do+ Stm.atomically+ . Stm.modifyTVar cache+ . Map.insert commandLineOptions+ $ RemoteData.Success (classifies, hint)+ pure (classifies, hint)+ RemoteData.Loading -> load commandLineOptions+ RemoteData.Failure ioException -> Exception.throwIO ioException+ RemoteData.Success settings -> pure settings
splint.cabal view
@@ -1,12 +1,8 @@-cabal-version: >= 1.10-+cabal-version: 2.2 name: splint-version: 1.0.2.1-+version: 2.0.0.0 synopsis: HLint as a GHC source plugin. description:- Warning: This package is not maintained anymore.- . Splint makes HLint available as a GHC source plugin. To use it, pass @-fplugin=Splint@ to GHC. Any options passed to Splint are passed through to HLint. For example you can use @-fplugin-opt=Splint:'--ignore=Use concatMap'@@@ -14,8 +10,8 @@ build-type: Simple category: Development-extra-source-files: README.markdown-license-file: LICENSE.markdown+extra-doc-files: README.markdown+license-file: LICENSE.txt license: ISC maintainer: Taylor Fausak @@ -23,39 +19,46 @@ location: https://github.com/tfausak/splint type: git +flag pedantic+ default: False+ manual: True+ library build-depends:- base >= 4.14 && < 4.17- , containers >= 0.6 && < 0.7- , ghc >= 8.10 && < 8.11 || >= 9.0 && < 9.3- , hlint- , stm >= 2.5 && < 2.6+ base ^>=4.18.0.0 || ^>=4.19.0.0,+ containers ^>=0.6.7,+ ghc ^>=9.6.1 || ^>=9.8.1,+ stm ^>=2.5.1.0,+ default-language: Haskell2010+ -- cabal-gild: discover source/library exposed-modules: Splint Splint.RemoteData+ Splint.Replacement Splint.Settings+ ghc-options: -Weverything+ -Wno-all-missed-specialisations -Wno-implicit-prelude -Wno-missing-deriving-strategies -Wno-missing-export-lists -Wno-missing-exported-signatures -Wno-missing-import-lists+ -Wno-missing-kind-signatures -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module -Wno-safe -Wno-unsafe- hs-source-dirs: src/lib - if impl(ghc >= 9.2)- build-depends: hlint >= 3.4 && < 3.5- ghc-options: -Wno-missing-kind-signatures- hs-source-dirs: src/ghc-9.2- else- if impl(ghc >= 9.0)- build-depends: hlint >= 3.3 && < 3.4- hs-source-dirs: src/ghc-9.0- else- build-depends: hlint >= 3.2 && < 3.3- hs-source-dirs: src/ghc-8.10+ hs-source-dirs: source/library++ if flag(pedantic)+ ghc-options: -Werror++ if impl(ghc ^>=9.6.1)+ build-depends: hlint ^>=3.6.1+ elif impl(ghc ^>=9.8.1)+ build-depends: hlint ^>=3.8+ ghc-options: -Wno-missing-role-annotations
− src/ghc-8.10/Splint.hs
@@ -1,65 +0,0 @@-module Splint where--import qualified Bag as GHC-import qualified ErrUtils as GHC-import qualified GhcPlugins as GHC-import qualified Language.Haskell.HLint as HLint-import qualified Splint.Settings as Settings--plugin :: GHC.Plugin-plugin = GHC.defaultPlugin- { GHC.parsedResultAction = action- , GHC.pluginRecompile = GHC.purePlugin- }--action- :: [GHC.CommandLineOption]- -> GHC.ModSummary- -> GHC.HsParsedModule- -> GHC.Hsc GHC.HsParsedModule-action commandLineOptions modSummary hsParsedModule = do- (parseFlags, classifies, hint) <- GHC.liftIO $ Settings.load commandLineOptions- moduleEx <- parse parseFlags modSummary hsParsedModule- dynFlags <- GHC.getDynFlags- GHC.liftIO- . GHC.printOrThrowWarnings dynFlags- . GHC.listToBag- . fmap (ideaToWarnMsg dynFlags)- . filter ((/= HLint.Ignore) . HLint.ideaSeverity)- $ HLint.applyHints classifies hint [moduleEx]- pure hsParsedModule--ideaToWarnMsg :: GHC.DynFlags -> HLint.Idea -> GHC.WarnMsg-ideaToWarnMsg dynFlags idea =- let- mkErrMsg = case HLint.ideaSeverity idea of- HLint.Error -> GHC.mkPlainErrMsg- _ -> GHC.mkPlainWarnMsg- srcSpan = case HLint.unpackSrcSpan $ HLint.ideaSpan idea of- Nothing -> GHC.noSrcSpan- Just (file, (startLine, startColumn), (endLine, endColumn)) ->- GHC.mkSrcSpan- (GHC.mkSrcLoc (GHC.mkFastString file) startLine startColumn)- (GHC.mkSrcLoc (GHC.mkFastString file) endLine endColumn)- msgDoc = ideaToMsgDoc idea- in mkErrMsg dynFlags srcSpan msgDoc--ideaToMsgDoc :: HLint.Idea -> GHC.MsgDoc-ideaToMsgDoc idea = GHC.vcat- [ GHC.text $ HLint.ideaHint idea- , case HLint.ideaTo idea of- Just to | not $ null to -> GHC.text $ "Perhaps: " <> to- _ -> GHC.empty- , GHC.vcat . fmap (GHC.text . mappend "Note: " . show) $ HLint.ideaNote idea- ]--parse- :: HLint.ParseFlags- -> GHC.ModSummary- -> GHC.HsParsedModule- -> GHC.Hsc HLint.ModuleEx-parse _ _ hsParsedModule = do- let- apiAnns = GHC.hpm_annotations hsParsedModule- hsModule = GHC.hpm_module hsParsedModule- pure $ HLint.createModuleEx apiAnns hsModule
− src/ghc-9.0/Splint.hs
@@ -1,65 +0,0 @@-module Splint where--import qualified GHC.Data.Bag as GHC-import qualified GHC.Plugins as GHC-import qualified GHC.Utils.Error as GHC-import qualified Language.Haskell.HLint as HLint-import qualified Splint.Settings as Settings--plugin :: GHC.Plugin-plugin = GHC.defaultPlugin- { GHC.parsedResultAction = action- , GHC.pluginRecompile = GHC.purePlugin- }--action- :: [GHC.CommandLineOption]- -> GHC.ModSummary- -> GHC.HsParsedModule- -> GHC.Hsc GHC.HsParsedModule-action commandLineOptions modSummary hsParsedModule = do- (parseFlags, classifies, hint) <- GHC.liftIO $ Settings.load commandLineOptions- moduleEx <- parse parseFlags modSummary hsParsedModule- dynFlags <- GHC.getDynFlags- GHC.liftIO- . GHC.printOrThrowWarnings dynFlags- . GHC.listToBag- . fmap (ideaToWarnMsg dynFlags)- . filter ((/= HLint.Ignore) . HLint.ideaSeverity)- $ HLint.applyHints classifies hint [moduleEx]- pure hsParsedModule--ideaToWarnMsg :: GHC.DynFlags -> HLint.Idea -> GHC.WarnMsg-ideaToWarnMsg dynFlags idea =- let- mkErrMsg = case HLint.ideaSeverity idea of- HLint.Error -> GHC.mkPlainErrMsg- _ -> GHC.mkPlainWarnMsg- srcSpan = case HLint.unpackSrcSpan $ HLint.ideaSpan idea of- Nothing -> GHC.noSrcSpan- Just (file, (startLine, startColumn), (endLine, endColumn)) ->- GHC.mkSrcSpan- (GHC.mkSrcLoc (GHC.mkFastString file) startLine startColumn)- (GHC.mkSrcLoc (GHC.mkFastString file) endLine endColumn)- msgDoc = ideaToMsgDoc idea- in mkErrMsg dynFlags srcSpan msgDoc--ideaToMsgDoc :: HLint.Idea -> GHC.MsgDoc-ideaToMsgDoc idea = GHC.vcat- [ GHC.text $ HLint.ideaHint idea- , case HLint.ideaTo idea of- Just to | not $ null to -> GHC.text $ "Perhaps: " <> to- _ -> GHC.empty- , GHC.vcat . fmap (GHC.text . mappend "Note: " . show) $ HLint.ideaNote idea- ]--parse- :: HLint.ParseFlags- -> GHC.ModSummary- -> GHC.HsParsedModule- -> GHC.Hsc HLint.ModuleEx-parse _ _ hsParsedModule = do- let- apiAnns = GHC.hpm_annotations hsParsedModule- hsModule = GHC.hpm_module hsParsedModule- pure $ HLint.createModuleEx apiAnns hsModule
− src/ghc-9.2/Splint.hs
@@ -1,62 +0,0 @@-module Splint where--import qualified GHC.Data.Bag as Bag-import qualified GHC.Driver.Errors as Errors-import qualified GHC.Hs as Hs-import qualified GHC.Plugins as Plugins-import qualified GHC.Types.Error as Error-import qualified GHC.Utils.Logger as Logger-import qualified Language.Haskell.HLint as HLint-import qualified Splint.Settings as Settings--plugin :: Plugins.Plugin-plugin = Plugins.defaultPlugin- { Plugins.parsedResultAction = parsedResultAction- , Plugins.pluginRecompile = Plugins.purePlugin- }--parsedResultAction- :: [Plugins.CommandLineOption]- -> Plugins.ModSummary- -> Hs.HsParsedModule- -> Plugins.Hsc Hs.HsParsedModule-parsedResultAction commandLineOptions _modSummary hsParsedModule = do- (_parseFlags, classifies, hint) <- Plugins.liftIO- $ Settings.load commandLineOptions- logger <- Logger.getLogger- dynFlags <- Plugins.getDynFlags- Plugins.liftIO- . Errors.printOrThrowWarnings logger dynFlags- . Bag.listToBag- . fmap ideaToWarnMsg- . filter ((/=) HLint.Ignore . HLint.ideaSeverity)- . HLint.applyHints classifies hint- . pure- . HLint.createModuleEx- $ Hs.hpm_module hsParsedModule- pure hsParsedModule--ideaToWarnMsg :: HLint.Idea -> Error.WarnMsg-ideaToWarnMsg idea =- Error.mkPlainWarnMsg (ideaToSrcSpan idea) (ideaToSDoc idea)--ideaToSrcSpan :: HLint.Idea -> Plugins.SrcSpan-ideaToSrcSpan idea = case HLint.unpackSrcSpan $ HLint.ideaSpan idea of- Nothing -> Plugins.noSrcSpan- Just (filePath, (startLine, startColumn), (endLine, endColumn)) ->- let fastString = Plugins.mkFastString filePath- in- Plugins.mkSrcSpan- (Plugins.mkSrcLoc fastString startLine startColumn)- (Plugins.mkSrcLoc fastString endLine endColumn)--ideaToSDoc :: HLint.Idea -> Error.SDoc-ideaToSDoc idea = Plugins.vcat- [ Plugins.text $ HLint.ideaHint idea- , case HLint.ideaTo idea of- Just to | not $ null to -> Plugins.text $ "Perhaps: " <> to- _ -> Plugins.empty- , Plugins.vcat- . fmap (Plugins.text . mappend "Note: " . show)- $ HLint.ideaNote idea- ]
− src/lib/Splint/RemoteData.hs
@@ -1,8 +0,0 @@-module Splint.RemoteData where--data RemoteData e a- = NotAsked- | Loading- | Failure e- | Success a- deriving (Eq, Show)
− src/lib/Splint/Settings.hs
@@ -1,67 +0,0 @@-module Splint.Settings where--import qualified Control.Concurrent.STM as Stm-import qualified Control.Exception as Exception-import qualified Data.Map as Map-import qualified Language.Haskell.HLint as HLint-import qualified Splint.RemoteData as RemoteData-import qualified System.IO.Unsafe as Unsafe--type Settings = (HLint.ParseFlags, [HLint.Classify], HLint.Hint)---- | Getting settings is not instantaneous. Since settings are usually reused--- between modules, it makes sense to cache them. However each module can--- potentially customize its settings using the @OPTIONS_GHC@ pragma. This--- variable is used as a cache of settings keyed on the command line options.-cache- :: Stm.TVar- (Map.Map [String] (RemoteData.RemoteData Exception.IOException Settings))-cache = Unsafe.unsafePerformIO $ Stm.newTVarIO Map.empty-{-# NOINLINE cache #-}---- | Even though we cache settings based on command line options, we only want--- to load settings one at a time. Practically this is to work around a bug in--- GHC. But aside from that, loading settings calls @withArgs@ and doing that--- simultaneously in separate threads is dubious.--- <https://gitlab.haskell.org/ghc/ghc/issues/18261>-semaphore :: Stm.TMVar ()-semaphore = Unsafe.unsafePerformIO $ Stm.newTMVarIO ()-{-# NOINLINE semaphore #-}--withTMVar :: Stm.TMVar a -> (a -> IO b) -> IO b-withTMVar x = Exception.bracket- (Stm.atomically $ Stm.takeTMVar x)- (Stm.atomically . Stm.putTMVar x)--load :: [String] -> IO Settings-load commandLineOptions = do- remoteData <- Stm.atomically $ do- settings <- Stm.readTVar cache- let- remoteData =- Map.findWithDefault RemoteData.NotAsked commandLineOptions settings- case remoteData of- RemoteData.NotAsked ->- Stm.modifyTVar cache $ Map.insert commandLineOptions RemoteData.Loading- RemoteData.Loading -> Stm.retry- _ -> pure ()- pure remoteData- case remoteData of- RemoteData.NotAsked -> withTMVar semaphore . const $ do- result <- Exception.try $ HLint.argsSettings commandLineOptions- case result of- Left ioException -> do- Stm.atomically- . Stm.modifyTVar cache- . Map.insert commandLineOptions- $ RemoteData.Failure ioException- Exception.throwIO ioException- Right settings -> do- Stm.atomically- . Stm.modifyTVar cache- . Map.insert commandLineOptions- $ RemoteData.Success settings- pure settings- RemoteData.Loading -> load commandLineOptions- RemoteData.Failure ioException -> Exception.throwIO ioException- RemoteData.Success settings -> pure settings