hlint 2.2 → 2.2.1
raw patch · 29 files changed
+656/−283 lines, 29 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Language.Haskell.HLint4: createModuleEx :: ApiAnns -> Located (HsModule GhcPs) -> ModuleEx
Files
- CHANGES.txt +5/−0
- data/hlint.yaml +11/−0
- hlint.cabal +1/−1
- src/Apply.hs +19/−14
- src/Config/Compute.hs +1/−1
- src/Config/Haskell.hs +15/−10
- src/Config/Yaml.hs +2/−2
- src/GHC/Util.hs +111/−37
- src/Grep.hs +2/−2
- src/HSE/All.hs +42/−8
- src/HSE/Util.hs +1/−10
- src/Hint/All.hs +5/−5
- src/Hint/Comment.hs +22/−11
- src/Hint/Duplicate.hs +1/−1
- src/Hint/Export.hs +28/−8
- src/Hint/Extensions.hs +5/−5
- src/Hint/Import.hs +102/−64
- src/Hint/Match.hs +1/−1
- src/Hint/Naming.hs +66/−45
- src/Hint/NewType.hs +103/−30
- src/Hint/Pattern.hs +1/−1
- src/Hint/Pragma.hs +2/−2
- src/Hint/Restrict.hs +4/−4
- src/Hint/Smell.hs +1/−1
- src/Hint/Type.hs +14/−11
- src/Hint/Unsafe.hs +1/−1
- src/Idea.hs +55/−4
- src/Language/Haskell/HLint4.hs +12/−3
- src/Refact.hs +23/−1
CHANGES.txt view
@@ -1,5 +1,10 @@ Changelog for HLint (* = breaking change) +2.2.1, released 2019-07-22+ #713, make sure -XNoPatternSynonyms works (fix regression)+ #700, add some Monoid and Alternative hints+ Add createModuleEx to the API+ #698, don't suggest a replacement for DerivingStrategies 2.2, released 2019-06-26 * Remove functions and make some things abstract in HLint3 API 2.1.26, released 2019-06-26
data/hlint.yaml view
@@ -210,6 +210,15 @@ - warn: {lhs: zipWith f (repeat x), rhs: map (f x)} - warn: {lhs: zipWith f y (repeat z), rhs: map (\x -> f x z) y} + # MONOIDS++ - warn: {lhs: mempty <> x, rhs: x, name: "Monoid law, left identity"}+ - warn: {lhs: mempty `mappend` x, rhs: x, name: "Monoid law, left identity"}+ - warn: {lhs: x <> mempty, rhs: x, name: "Monoid law, right identity"}+ - warn: {lhs: x `mappend` mempty, rhs: x, name: "Monoid law, right identity"}+ - warn: {lhs: foldr (<>) mempty, rhs: mconcat}+ - warn: {lhs: foldr mappend mempty, rhs: mconcat}+ # TRAVERSABLES - warn: {lhs: sequenceA (map f x), rhs: traverse f x}@@ -441,6 +450,8 @@ - warn: {lhs: Just <$> a <|> pure Nothing, rhs: optional a} - hint: {lhs: m >>= pure . f, rhs: f <$> m} - hint: {lhs: pure . f =<< m, rhs: f <$> m}+ - warn: {lhs: empty <|> x, rhs: x, name: "Alternative law, left identity"}+ - warn: {lhs: x <|> empty, rhs: x, name: "Alternative law, right identity"} # LIST COMP
hlint.cabal view
@@ -1,7 +1,7 @@ cabal-version: >= 1.18 build-type: Simple name: hlint-version: 2.2+version: 2.2.1 license: BSD3 license-file: LICENSE category: Development
src/Apply.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE PackageImports #-} module Apply(applyHints, applyHintFile, applyHintFiles) where @@ -5,6 +6,7 @@ import Data.Monoid import HSE.All import Hint.All+import GHC.Util import Idea import Data.Tuple.Extra import Data.Either@@ -13,6 +15,8 @@ import Data.Ord import Config.Type import Config.Haskell+import "ghc-lib-parser" HsSyn+import qualified "ghc-lib-parser" SrcLoc as GHC import qualified Data.HashSet as Set import Prelude @@ -40,26 +44,27 @@ -- When given multiple modules at once this function attempts to find hints between modules, -- which is slower and often pointless (by default HLint passes modules singularly, using -- @--cross@ to pass all modules together).-applyHints {- PUBLIC -} :: [Classify] -> Hint -> [(Module SrcSpanInfo, [Comment])] -> [Idea]+applyHints {- PUBLIC -} :: [Classify] -> Hint -> [ModuleEx] -> [Idea] applyHints cs = applyHintsReal $ map SettingClassify cs -applyHintsReal :: [Setting] -> Hint -> [(Module_, [Comment])] -> [Idea]+applyHintsReal :: [Setting] -> Hint -> [ModuleEx] -> [Idea] applyHintsReal settings hints_ ms = concat $- [ map (classify classifiers . removeRequiresExtensionNotes m) $+ [ map (classify classifiers . removeRequiresExtensionNotes (hseModule m)) $ order [] (hintModule hints settings nm m) `merge`- concat [order [fromNamed d] $ decHints d | d <- moduleDecls m] `merge`- concat [order [] $ hintComment hints settings c | c <- cs]- | (nm,(m,cs)) <- mns- , let classifiers = cls ++ mapMaybe readPragma (universeBi m) ++ concatMap readComment cs+ concat [order [fromNamed d] $ decHints d | d <- moduleDecls (hseModule m)] `merge`+ concat [order [declName $ GHC.unLoc d] $ decHints' d | d <- hsmodDecls $ GHC.unLoc $ ghcModule m]+ | (nm, m) <- mns+ , let classifiers = cls ++ mapMaybe readPragma (universeBi (hseModule m)) ++ concatMap readComment (ghcComments m) , seq (length classifiers) True -- to force any errors from readPragma or readComment , let decHints = hintDecl hints settings nm m -- partially apply- , let order n = map (\i -> i{ideaModule= f $ moduleName m : ideaModule i, ideaDecl= f $ n ++ ideaDecl i}) . sortOn ideaSpan+ , let decHints' = hintDecl' hints settings nm m -- partially apply+ , let order n = map (\i -> i{ideaModule= f $ moduleName (hseModule m) : ideaModule i, ideaDecl = f $ n ++ ideaDecl i}) . sortOn ideaSpan , let merge = mergeBy (comparing ideaSpan)] ++- [map (classify cls) (hintModules hints settings $ map (second fst) mns)]+ [map (classify cls) (hintModules hints settings mns)] where f = nubOrd . filter (/= "") cls = [x | SettingClassify x <- settings]- mns = map (scopeCreate . fst &&& id) ms+ mns = map (\x -> (scopeCreate (hseModule x), x)) ms hints = (if length ms <= 1 then noModules else id) hints_ noModules h = h{hintModules = \_ _ -> []} `mappend` mempty{hintModule = \s a b -> hintModules h s [(a,b)]} @@ -72,17 +77,17 @@ keep _ = True -- | Given a list of settings (a way to classify) and a list of hints, run them over a list of modules.-executeHints :: [Setting] -> [(Module_, [Comment])] -> [Idea]+executeHints :: [Setting] -> [ModuleEx] -> [Idea] executeHints s = applyHintsReal s (allHints s) -- | Return either an idea (a parse error) or the module. In IO because might call the C pre processor.-parseModuleApply :: ParseFlags -> [Setting] -> FilePath -> Maybe String -> IO (Either Idea (Module_, [Comment]))+parseModuleApply :: ParseFlags -> [Setting] -> FilePath -> Maybe String -> IO (Either Idea ModuleEx) parseModuleApply flags s file src = do res <- parseModuleEx (parseFlagsAddFixities [x | Infix x <- s] flags) file src case res of- Right (ModuleEx (m, c) _) -> return $ Right (m,c)- Left (ParseError sl msg ctxt) ->+ Right r -> return $ Right r+ Left (ParseError sl msg ctxt) -> return $ Left $ classify [x | SettingClassify x <- s] $ rawIdeaN Error "Parse error" (mkSrcSpan sl sl) ctxt Nothing []
src/Config/Compute.hs view
@@ -18,7 +18,7 @@ case x of Left (ParseError sl msg _) -> return ("# Parse error " ++ showSrcLoc sl ++ ": " ++ msg, [])- Right (ModuleEx (m, _) _) -> do+ Right (ModuleEx m _ _ _) -> do let xs = concatMap (findSetting $ UnQual an) (moduleDecls m) r = concatMap (readSetting mempty) xs s = unlines $ ["# hints found in " ++ file] ++ concatMap renderSetting r ++ ["# no hints found" | null xs]
src/Config/Haskell.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE PatternGuards, ViewPatterns, ScopedTypeVariables, TupleSections #-}-+{-# LANGUAGE PackageImports #-} module Config.Haskell( readPragma, readComment,@@ -16,6 +16,9 @@ import Config.Type import Util import Prelude+import GHC.Util+import "ghc-lib-parser" SrcLoc as GHC+import "ghc-lib-parser" ApiAnnotation addInfix = parseFlagsAddFixities $ infix_ (-1) ["==>"]@@ -31,7 +34,7 @@ case res of Left (ParseError sl msg err) -> error $ "Config parse failure at " ++ showSrcLoc sl ++ ": " ++ msg ++ "\n" ++ err- Right (ModuleEx (m, cs) _) -> return $ readSettings m ++ map SettingClassify (concatMap readComment cs)+ Right modEx@(ModuleEx m _ _ _) -> return $ readSettings m ++ map SettingClassify (concatMap readComment (ghcComments modEx)) -- | Given a module containing HLint settings information return the 'Classify' rules and the 'HintRule' expressions.@@ -78,22 +81,23 @@ f _ _ = Nothing -readComment :: Comment -> [Classify]-readComment o@(Comment True _ x)+readComment :: GHC.Located AnnotationComment -> [Classify]+readComment c@(L pos AnnBlockComment{}) | (hash, x) <- maybe (False, x) (True,) $ stripPrefix "#" x , x <- trim x , (hlint, x) <- word1 x , lower hlint == "hlint" = f hash x where+ x = commentText c f hash x | Just x <- if hash then stripSuffix "#" x else Just x , (sev, x) <- word1 x , Just sev <- getSeverity sev , (things, x) <- g x- , Just (hint :: String) <- if x == "" then Just "" else readMaybe x+ , Just hint <- if x == "" then Just "" else readMaybe x = map (Classify sev hint "") $ ["" | null things] ++ things- f hash _ = errorOnComment o $ "bad HLINT pragma, expected:\n {-" ++ h ++ " HLINT <severity> <identifier> \"Hint name\" " ++ h ++ "-}"+ f hash _ = errorOnComment c $ "bad HLINT pragma, expected:\n {-" ++ h ++ " HLINT <severity> <identifier> \"Hint name\" " ++ h ++ "-}" where h = ['#' | hash] g x | (s, x) <- word1 x@@ -155,8 +159,9 @@ ": Error while reading hint file, " ++ msg ++ "\n" ++ prettyPrint val -errorOnComment :: Comment -> String -> b-errorOnComment (Comment b ann x) msg = exitMessageImpure $- showSrcLoc (getPointLoc ann) +++errorOnComment :: GHC.Located AnnotationComment -> String -> b+errorOnComment c@(L s _) msg = exitMessageImpure $+ let isMultiline = isCommentMultiline c in+ showSrcLoc (ghcSrcLocToHSE $ GHC.srcSpanStart s) ++ ": Error while reading hint file, " ++ msg ++ "\n" ++- (if b then "{-" else "--") ++ x ++ (if b then "-}" else "")+ (if isMultiline then "{-" else "--") ++ commentText c ++ (if isMultiline then "-}" else "")
src/Config/Yaml.hs view
@@ -159,8 +159,8 @@ parseConfigYaml :: Val -> Parser ConfigYaml parseConfigYaml v = do vs <- parseArray v- fmap ConfigYaml $ forM vs $ \o@v -> do- (s, v) <- parseObject1 v+ fmap ConfigYaml $ forM vs $ \o -> do+ (s, v) <- parseObject1 o case s of "package" -> ConfigPackage <$> parsePackage v "group" -> ConfigGroup <$> parseGroup v
src/GHC/Util.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE PackageImports #-}-{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeFamilies, NamedFieldPuns #-} {-# OPTIONS_GHC -fno-warn-missing-fields #-} module GHC.Util (@@ -12,6 +12,12 @@ , SDoc , Located , readExtension+ , commentText, isCommentMultiline+ , declName+ , unsafePrettyPrint+ , eqMaybe+ , noloc, unloc, getloc, noext+ , isForD -- Temporary : Export these so GHC doesn't consider them unused and -- tell weeder to ignore them. , isAtom, addParen, paren, isApp, isOpApp, isAnyApp, isDot, isSection, isDotApp@@ -27,6 +33,7 @@ import "ghc-lib-parser" Lexer import "ghc-lib-parser" Parser import "ghc-lib-parser" SrcLoc+import "ghc-lib-parser" OccName import "ghc-lib-parser" FastString import "ghc-lib-parser" StringBuffer import "ghc-lib-parser" ErrUtils@@ -35,8 +42,9 @@ import "ghc-lib-parser" Panic import "ghc-lib-parser" HscTypes import "ghc-lib-parser" HeaderInfo+import "ghc-lib-parser" ApiAnnotation -import Data.List+import Data.List.Extra import System.FilePath import Language.Preprocessor.Unlit import qualified Data.Map.Strict as Map@@ -60,25 +68,18 @@ fakeLlvmConfig :: (LlvmTargets, LlvmPasses) fakeLlvmConfig = ([], []) -badExtensions :: [Extension]-badExtensions =- [- AlternativeLayoutRule- , AlternativeLayoutRuleTransitional- , Arrows- , TransformListComp- , UnboxedTuples- , UnboxedSums- , QuasiQuotes- , RecursiveDo- ]--enabledExtensions :: [Extension]-enabledExtensions = filter (`notElem` badExtensions) enumerateExtensions- baseDynFlags :: DynFlags-baseDynFlags = foldl' xopt_set- (defaultDynFlags fakeSettings fakeLlvmConfig) enabledExtensions+baseDynFlags =+ -- The list of default enabled extensions is empty except for+ -- 'TemplateHaskellQuotes'. This is because:+ -- * The extensions to enable/disable are set exclusively in+ -- 'parsePragmasIntoDynFlags' based solely on HSE parse flags+ -- (and source level annotations);+ -- * 'TemplateHaskellQuotes' is not a known HSE extension but IS+ -- needed if the GHC parse is to succeed for the unit-test at+ -- hlint.yaml:860+ let enable = [TemplateHaskellQuotes]+ in foldl' xopt_set (defaultDynFlags fakeSettings fakeLlvmConfig) enable -- | Adjust the input 'DynFlags' to take into account language -- extensions to explicitly enable/disable as well as language@@ -94,7 +95,8 @@ (flags, _, _) <- parseDynamicFilePragma flags opts let flags' = foldl' xopt_set flags enable let flags'' = foldl' xopt_unset flags' disable- return $ Right flags''+ let flags''' = flags'' `gopt_set` Opt_KeepRawTokenStream+ return $ Right flags''' where catchErrors :: IO (Either String DynFlags) -> IO (Either String DynFlags) catchErrors act = handleGhcException reportErr@@ -118,7 +120,7 @@ -- (of which at least one of them came from this project originally). -- | 'isAtom e' if 'e' requires no bracketing ever.-isAtom :: (p ~ GhcPass pass) => HsExpr p -> Bool+isAtom :: HsExpr GhcPs -> Bool isAtom x = case x of HsVar {} -> True HsUnboundVar {} -> True@@ -156,30 +158,30 @@ isNegativeOverLit _ = False -- | 'addParen e' wraps 'e' in parens.-addParen :: (p ~ GhcPass pass) => HsExpr p -> HsExpr p+addParen :: HsExpr GhcPs -> HsExpr GhcPs addParen e = HsPar noExt (noLoc e) -- | 'paren e' wraps 'e' in parens if 'e' is non-atomic.-paren :: (p ~ GhcPass pass) => HsExpr GhcPs -> HsExpr GhcPs+paren :: HsExpr GhcPs -> HsExpr GhcPs paren x | isAtom x = x | otherwise = addParen x -- | 'isApp e' if 'e' is a (regular) application.-isApp :: (p ~ GhcPass pass) => HsExpr p -> Bool+isApp :: HsExpr GhcPs -> Bool isApp x = case x of HsApp {} -> True _ -> False -- | 'isOpApp e' if 'e' is an operator application.-isOpApp :: (p ~ GhcPass pass) => HsExpr p -> Bool+isOpApp :: HsExpr GhcPs -> Bool isOpApp x = case x of OpApp {} -> True _ -> False -- | 'isAnyApp e' if 'e' is either an application or operator -- application.-isAnyApp :: (p ~ GhcPass pass) => HsExpr p -> Bool+isAnyApp :: HsExpr GhcPs -> Bool isAnyApp x = isApp x || isOpApp x -- | 'isDot e' if 'e' is the unqualifed variable '.'.@@ -190,27 +192,99 @@ | otherwise = False -- | 'isSection e' if 'e' is a section.-isSection :: (p ~ GhcPass pass) => HsExpr p -> Bool+isSection :: HsExpr GhcPs -> Bool isSection x = case x of SectionL {} -> True SectionR {} -> True _ -> False +-- | 'isForD d' if 'd' is a foreign declaration.+isForD :: HsDecl GhcPs -> Bool+isForD ForD{} = True+isForD _ = False+ -- | 'isDotApp e' if 'e' is dot application. isDotApp :: HsExpr GhcPs -> Bool isDotApp (OpApp _ _ (L _ op) _) = isDot op isDotApp _ = False --- | All available GHC extensions-enumerateExtensions :: [Extension]--- 'Cpp' are the first and last cases of type 'Extension' in--- 'libraries/ghc-boot-th/GHC/LanguageExtensions/Type.hs'. When we are--- on a version of GHC that has MR--- https://gitlab.haskell.org/ghc/ghc/merge_requests/826, we can--- replace them with 'minBound' and 'maxBound' respectively.-enumerateExtensions = [Cpp .. StarIsType]- -- | Parse a GHC extension readExtension :: String -> Maybe Extension readExtension x = Map.lookup x exts where exts = Map.fromList [(show x, x) | x <- [Cpp .. StarIsType]]++trimCommentStart :: String -> String+trimCommentStart s+ | Just s <- stripPrefix "{-" s = s+ | Just s <- stripPrefix "--" s = s+ | otherwise = s++trimCommentEnd :: String -> String+trimCommentEnd s+ | Just s <- stripSuffix "-}" s = s+ | otherwise = s++trimCommentDelims :: String -> String+trimCommentDelims = trimCommentEnd . trimCommentStart++-- | Access to a comment's text.+commentText :: Located AnnotationComment -> String+commentText (L _ (AnnDocCommentNext s)) = trimCommentDelims s+commentText (L _ (AnnDocCommentPrev s)) = trimCommentDelims s+commentText (L _ (AnnDocCommentNamed s)) = trimCommentDelims s+commentText (L _ (AnnDocSection _ s)) = trimCommentDelims s+commentText (L _ (AnnDocOptions s)) = trimCommentDelims s+commentText (L _ (AnnLineComment s)) = trimCommentDelims s+commentText (L _ (AnnBlockComment s)) = trimCommentDelims s++isCommentMultiline :: Located AnnotationComment -> Bool+isCommentMultiline (L _ (AnnBlockComment _)) = True+isCommentMultiline _ = False++declName :: HsDecl GhcPs -> String+declName (TyClD _ FamDecl{tcdFam=FamilyDecl{fdLName}}) = occNameString $ occName $ unLoc fdLName+declName (TyClD _ SynDecl{tcdLName}) = occNameString $ occName $ unLoc tcdLName+declName (TyClD _ DataDecl{tcdLName}) = occNameString $ occName $ unLoc tcdLName+declName (TyClD _ ClassDecl{tcdLName}) = occNameString $ occName $ unLoc tcdLName+declName (ValD _ FunBind{fun_id}) = occNameString $ occName $ unLoc fun_id+declName (ValD _ VarBind{var_id}) = occNameString $ occName var_id+declName (ValD _ (PatSynBind _ PSB{psb_id})) = occNameString $ occName $ unLoc psb_id+declName (SigD _ (TypeSig _ (x:_) _)) = occNameString $ occName $ unLoc x+declName (SigD _ (PatSynSig _ (x:_) _)) = occNameString $ occName $ unLoc x+declName (SigD _ (ClassOpSig _ _ (x:_) _)) = occNameString $ occName $ unLoc x+declName (ForD _ ForeignImport{fd_name}) = occNameString $ occName $ unLoc fd_name+declName (ForD _ ForeignExport{fd_name}) = occNameString $ occName $ unLoc fd_name+declName _ = ""++-- \"Unsafely\" in this case means that it uses the following+-- 'DynFlags' for printing -+-- <http://hackage.haskell.org/package/ghc-lib-parser-8.8.0.20190424/docs/src/DynFlags.html#v_unsafeGlobalDynFlags+-- unsafeGlobalDynFlags> This could lead to the issues documented+-- there, but it also might not be a problem for our use case. TODO:+-- Decide whether this really is unsafe, and if it is, what needs to+-- be done to make it safe.+unsafePrettyPrint :: (Outputable.Outputable a) => a -> String+unsafePrettyPrint = Outputable.showSDocUnsafe . Outputable.ppr++-- | Test if two AST elements are equal modulo annotations.+(=~=) :: Eq a => Located a -> Located a -> Bool+a =~= b = unLoc a == unLoc b++-- | Compare two 'Maybe (Located a)' values for equality modulo+-- locations.+eqMaybe:: Eq a => Maybe (Located a) -> Maybe (Located a) -> Bool+eqMaybe (Just x) (Just y) = x =~= y+eqMaybe Nothing Nothing = True+eqMaybe _ _ = False++noloc :: e -> Located e+noloc = noLoc++unloc :: Located e -> e+unloc = unLoc++getloc :: Located e -> SrcSpan+getloc = getLoc++noext :: NoExt+noext = noExt
src/Grep.hs view
@@ -26,6 +26,6 @@ case res of Left (ParseError sl msg ctxt) -> print $ rawIdeaN Error (if "Parse error" `isPrefixOf` msg then msg else "Parse error: " ++ msg) (mkSrcSpan sl sl) ctxt Nothing []- Right (ModuleEx (m, c) _) ->- forM_ (applyHints [] rule [(m, c)]) $ \i ->+ Right m ->+ forM_ (applyHints [] rule [m]) $ \i -> print i{ideaHint="", ideaTo=Nothing}
src/HSE/All.hs view
@@ -7,8 +7,9 @@ CppFlags(..), ParseFlags(..), defaultParseFlags, parseFlagsAddFixities, parseFlagsSetLanguage, ParseError(..), ModuleEx(..),- parseModuleEx,- freeVars, vars, varss, pvars+ parseModuleEx, ghcComments,+ freeVars, vars, varss, pvars,+ ghcSpanToHSE, ghcSrcLocToHSE ) where import Language.Haskell.Exts.Util hiding (freeVars, Vars(..))@@ -38,8 +39,32 @@ import qualified "ghc-lib-parser" SrcLoc as GHC import qualified "ghc-lib-parser" ErrUtils import qualified "ghc-lib-parser" Outputable+import qualified "ghc-lib-parser" Lexer as GHC import qualified "ghc-lib-parser" GHC.LanguageExtensions.Type as GHC+import qualified "ghc-lib-parser" ApiAnnotation as GHC +-- | Convert a GHC source loc into an HSE equivalent.+ghcSrcLocToHSE :: GHC.SrcLoc -> SrcLoc+ghcSrcLocToHSE (GHC.RealSrcLoc l) =+ SrcLoc {+ srcFilename = FastString.unpackFS (GHC.srcLocFile l)+ , srcLine = GHC.srcLocLine l+ , srcColumn = GHC.srcLocCol l+ }+ghcSrcLocToHSE (GHC.UnhelpfulLoc _) = noLoc++-- | Convert a GHC source span into an HSE equivalent.+ghcSpanToHSE :: GHC.SrcSpan -> SrcSpan+ghcSpanToHSE (GHC.RealSrcSpan s) =+ SrcSpan {+ srcSpanFilename = FastString.unpackFS (GHC.srcSpanFile s)+ , srcSpanStartLine = GHC.srcSpanStartLine s+ , srcSpanStartColumn = GHC.srcSpanStartCol s+ , srcSpanEndLine = GHC.srcSpanEndLine s+ , srcSpanEndColumn = GHC.srcSpanEndCol s+ }+ghcSpanToHSE (GHC.UnhelpfulSpan _) = mkSrcSpan noLoc noLoc+ vars :: FreeVars a => a -> [String] freeVars :: FreeVars a => a -> Set String varss, pvars :: AllVars a => a -> [String]@@ -162,14 +187,19 @@ -- | Result of 'parseModuleEx', representing a parsed module. data ModuleEx = ModuleEx {- -- Combined 'hs-src-ext' and 'ghc-lib-parser' parse trees.- pm_hsext :: (Module SrcSpanInfo, [Comment]) -- hs-src-ext result- , pm_ghclib :: Located (HsSyn.HsModule HsSyn.GhcPs) -- ghc-lib-parser result+ hseModule :: Module SrcSpanInfo+ , hseComments :: [Comment]+ , ghcModule :: Located (HsSyn.HsModule HsSyn.GhcPs)+ , ghcAnnotations :: GHC.ApiAnns } +-- | Extract a list of all of a parsed module's comments.+ghcComments :: ModuleEx -> [Located GHC.AnnotationComment]+ghcComments m = concat (Map.elems $ snd (ghcAnnotations m))+ -- | Utility called from 'parseModuleEx' and 'hseFailOpParseModuleEx'. mkMode :: ParseFlags -> String -> ParseMode-mkMode flags file = (hseFlags flags){parseFilename = file,fixities = Nothing }+mkMode flags file = (hseFlags flags){ parseFilename = file,fixities = Nothing } -- | Error handler dispatcher. Invoked when HSE parsing has failed. failOpParseModuleEx :: String@@ -262,8 +292,12 @@ case dynFlags of Right ghcFlags -> case (parseFileContentsWithComments (mkMode flags file) ppstr, parseFileGhcLib file ppstr ghcFlags) of- (ParseOk (x, cs), POk _ a) ->- return $ Right (ModuleEx (applyFixity fixity x, cs) a)+ (ParseOk (x, cs), POk pst a) ->+ let anns =+ ( Map.fromListWith (++) $ GHC.annotations pst+ , Map.fromList ((GHC.noSrcSpan, GHC.comment_q pst) : GHC.annotations_comments pst)+ ) in+ return $ Right (ModuleEx (applyFixity fixity x) cs a anns) -- Parse error if GHC parsing fails (see -- https://github.com/ndmitchell/hlint/issues/645). (ParseOk _, PFailed _ loc err) ->
src/HSE/Util.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleContexts, ViewPatterns, TupleSections #-}+{-# LANGUAGE FlexibleContexts, TupleSections #-} module HSE.Util(module HSE.Util, def) where @@ -235,15 +235,6 @@ --------------------------------------------------------------------- -- HSE FUNCTIONS--isKindHash :: Type_ -> Bool-isKindHash (TyParen _ x) = isKindHash x-isKindHash (TyApp _ x _) = isKindHash x-isKindHash (TyCon _ (fromQual -> Just (Ident _ s))) = "#" `isSuffixOf` s-isKindHash (TyTuple _ Unboxed _) = True-isKindHash TyUnboxedSum{} = True-isKindHash _ = False- getEquations :: Decl s -> [Decl s] getEquations (FunBind s xs) = map (FunBind s . (:[])) xs
src/Hint/All.hs view
@@ -1,6 +1,6 @@ module Hint.All(- Hint(..), HintBuiltin(..), DeclHint, ModuHint,+ Hint(..), DeclHint, ModuHint, resolveHints, hintRules, builtinHints ) where @@ -49,24 +49,24 @@ HintMonad -> decl monadHint HintLambda -> decl lambdaHint HintBracket -> decl bracketHint- HintNaming -> decl namingHint+ HintNaming -> decl' namingHint HintPattern -> decl patternHint HintImport -> modu importHint HintExport -> modu exportHint+ HintComment -> modu commentHint HintPragma -> modu pragmaHint HintExtensions -> modu extensionsHint HintUnsafe -> decl unsafeHint HintDuplicate -> mods duplicateHint- HintComment -> comm commentHint- HintNewType -> decl newtypeHint+ HintNewType -> decl' newtypeHint HintRestrict -> mempty{hintModule=restrictHint} HintSmell -> mempty{hintDecl=smellHint,hintModule=smellModuleHint} where wrap = timed "Hint" (drop 4 $ show x) . forceList decl f = mempty{hintDecl=const $ \a b c -> wrap $ f a b c}+ decl' f = mempty{hintDecl'=const $ \a b c -> wrap $ f a b c} modu f = mempty{hintModule=const $ \a b -> wrap $ f a b} mods f = mempty{hintModules=const $ \a -> wrap $ f a}- comm f = mempty{hintComment=const $ \a -> wrap $ f a} -- | A list of builtin hints, currently including entries such as @\"List\"@ and @\"Bracket\"@. builtinHints :: [(String, Hint)]
src/Hint/Comment.hs view
@@ -10,6 +10,7 @@ <COMMENT> INLINE X </TEST> -}+{-# LANGUAGE PackageImports #-} module Hint.Comment(commentHint) where@@ -18,21 +19,31 @@ import Data.Char import Data.List.Extra import Refact.Types(Refactoring(ModifyComment))-+import "ghc-lib-parser" SrcLoc+import "ghc-lib-parser" ApiAnnotation+import GHC.Util pragmas = words $ "LANGUAGE OPTIONS_GHC INCLUDE WARNING DEPRECATED MINIMAL INLINE NOINLINE INLINABLE " ++ "CONLIKE LINE SPECIALIZE SPECIALISE UNPACK NOUNPACK SOURCE" -commentHint :: Comment -> [Idea]-commentHint c@(Comment True span s)- | "#" `isSuffixOf` s && not ("#" `isPrefixOf` s) = [grab "Fix pragma markup" c $ '#':s]- | name `elem` pragmas = [grab "Use pragma syntax" c $ "# " ++ trim s ++ " #"]- where name = takeWhile (\x -> isAlphaNum x || x == '_') $ dropWhile isSpace s-commentHint _ = []+commentHint :: ModuHint+commentHint _ m = concatMap chk (ghcComments m)+ where+ chk :: Located AnnotationComment -> [Idea]+ chk comm+ | isMultiline, "#" `isSuffixOf` s && not ("#" `isPrefixOf` s) = [grab "Fix pragma markup" comm $ '#':s]+ | isMultiline, name `elem` pragmas = [grab "Use pragma syntax" comm $ "# " ++ trim s ++ " #"]+ where+ isMultiline = isCommentMultiline comm+ s = commentText comm+ name = takeWhile (\x -> isAlphaNum x || x == '_') $ dropWhile isSpace s+ chk _ = [] -grab :: String -> Comment -> String -> Idea-grab msg (Comment typ pos s1) s2 = rawIdea Suggestion msg pos (f s1) (Just $ f s2) [] refact- where f s = if typ then "{-" ++ s ++ "-}" else "--" ++ s- refact = [ModifyComment (toRefactSrcSpan pos) (f s2)]+ grab :: String -> Located AnnotationComment -> String -> Idea+ grab msg o@(L pos _) s2 =+ let s1 = commentText o in+ rawIdea Suggestion msg (ghcSpanToHSE pos) (f s1) (Just $ f s2) [] refact+ where f s = if isCommentMultiline o then "{-" ++ s ++ "-}" else "--" ++ s+ refact = [ModifyComment (toRefactSrcSpan (ghcSpanToHSE pos)) (f s2)]
src/Hint/Duplicate.hs view
@@ -32,7 +32,7 @@ duplicateHint ms = dupes [(m,d,y) | (m,d,x) <- ds, Do _ y :: Exp S <- universeBi x] ++ dupes [(m,d,y) | (m,d,x) <- ds, BDecls _ y :: Binds S <- universeBi x]- where ds = [(moduleName m, fromNamed d, d) | m <- map snd ms, d <- moduleDecls m]+ where ds = [(moduleName (hseModule m), fromNamed d, d) | m <- map snd ms, d <- moduleDecls (hseModule m)] dupes :: (Pretty (f SrcSpan), Annotated f, Ord (f ())) => [(String, String, [f S])] -> [Idea]
src/Hint/Export.hs view
@@ -9,18 +9,38 @@ module Foo(module Foo, foo) where foo = 1 -- module Foo(..., foo) where </TEST> -}+{-# LANGUAGE PackageImports #-} module Hint.Export(exportHint) where import Hint.Type-+import "ghc-lib-parser" HsSyn+import qualified "ghc-lib-parser" Module as GHC+import "ghc-lib-parser" SrcLoc as GHC+import "ghc-lib-parser" OccName+import "ghc-lib-parser" RdrName exportHint :: ModuHint-exportHint _ (Module _ (Just o@(ModuleHead a name warning exports)) _ _ _)- | Nothing <- exports =- let o2 = ModuleHead a name warning $ Just $ ExportSpecList a [EModuleContents a name]- in [(ignore "Use module export list" o o2 []){ideaNote = [Note "an explicit list is usually better"]}]- | Just (ExportSpecList _ xs) <- exports, EModuleContents a name `elem_` xs =- let o2 = ModuleHead a name warning $ Just $ ExportSpecList a $ EVar a ellipses : delete_ (EModuleContents a name) xs- in [ignore "Use explicit module export list" o o2 []]+exportHint _ (ModuleEx _ _ (L s m@HsModule {hsmodName = Just name, hsmodExports = exports}) _)+ | Nothing <- exports =+ let r = o{ hsmodExports = Just (GHC.noLoc [GHC.noLoc (IEModuleContents noExt name)] )} in+ [(ignore' "Use module export list" (L s o) (GHC.noLoc r) []){ideaNote = [Note "an explicit list is usally better"]}]+ | Just (L _ xs) <- exports+ , mods <- [x | x <- xs, isMod x]+ , modName <- GHC.moduleNameString (GHC.unLoc name)+ , names <- [GHC.moduleNameString (GHC.unLoc n) | (L _ (IEModuleContents _ n)) <- mods]+ , exports' <- [x | x <- xs, not (matchesModName modName x)]+ , modName `elem` names =+ let dots = mkRdrUnqual (mkVarOcc " ... ")+ r = o{ hsmodExports = Just (GHC.noLoc (GHC.noLoc (IEVar noExt (GHC.noLoc (IEName (GHC.noLoc dots)))) : exports') )}+ in+ [ignore' "Use explicit module export list" (L s o) (GHC.noLoc r) []]+ where+ o = m{hsmodImports=[], hsmodDecls=[], hsmodDeprecMessage=Nothing, hsmodHaddockModHeader=Nothing }+ isMod (L _ (IEModuleContents _ _)) = True+ isMod _ = False++ matchesModName m (L _ (IEModuleContents _ (L _ n))) = GHC.moduleNameString n == m+ matchesModName _ _ = False+ exportHint _ _ = []
src/Hint/Extensions.hs view
@@ -156,7 +156,7 @@ [ Note $ "Extension " ++ prettyExtension x ++ " is " ++ reason x | x <- explainedRemovals]) [ModifyComment (toSS o) newPragma]- | o@(LanguagePragma sl exts) <- modulePragmas x+ | o@(LanguagePragma sl exts) <- modulePragmas (hseModule x) , let before = map (parseExtension . prettyPrint) exts , let after = filter (`Set.member` keep) before , before /= after@@ -166,14 +166,14 @@ , let newPragma = if null after then "" else prettyPrint $ LanguagePragma sl $ map (toNamed . prettyExtension) after ] where- usedTH = used TemplateHaskell x || used QuasiQuotes x+ usedTH = used TemplateHaskell (hseModule x) || used QuasiQuotes (hseModule x) -- if TH or QuasiQuotes is on, can use all other extensions programmatically -- all the extensions defined to be used- extensions = Set.fromList [parseExtension $ fromNamed e | LanguagePragma _ exts <- modulePragmas x, e <- exts]+ extensions = Set.fromList [parseExtension $ fromNamed e | LanguagePragma _ exts <- modulePragmas (hseModule x), e <- exts] -- those extensions we detect to be useful- useful = if usedTH then extensions else Set.filter (`usedExt` x) extensions+ useful = if usedTH then extensions else Set.filter (`usedExt` hseModule x) extensions -- those extensions which are useful, but implied by other useful extensions implied = Map.fromList@@ -192,7 +192,7 @@ | e <- Set.toList $ extensions `Set.difference` keep , a <- extensionImplies e , a `Set.notMember` useful- , usedTH || usedExt a x+ , usedTH || usedExt a (hseModule x) ] reason x =
src/Hint/Import.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE PackageImports #-} {-# LANGUAGE PatternGuards, ScopedTypeVariables, RecordWildCards #-} {- Reduce the number of import declarations.@@ -48,61 +49,123 @@ import Data.Maybe import Prelude +import "ghc-lib-parser" FastString+import "ghc-lib-parser" BasicTypes+import "ghc-lib-parser" RdrName+import "ghc-lib-parser" Module+import "ghc-lib-parser" HsSyn as GHC+import qualified "ghc-lib-parser" SrcLoc as GHC+import GHC.Util importHint :: ModuHint-importHint _ x = concatMap (wrap . snd) (groupSort- [((fromNamed $ importModule i,importPkg i),i) | i <- universeBi x, not $ importSrc i]) ++- concatMap (\x -> hierarchy x ++ combine1 x) (universeBi x)---wrap :: [ImportDecl S] -> [Idea]-wrap o = [ rawIdea Warning "Use fewer imports" (srcInfoSpan $ ann $ head o) (f o) (Just $ f x) [] rs- | Just (x, rs) <- [simplify o]]- where f = unlines . map prettyPrint+importHint _ ModuleEx {ghcModule=GHC.L _ HsModule{hsmodImports=ms}} =+ -- Ideas for combining multiple imports.+ concatMap (reduceImports . snd) (+ groupSort [((n, pkg), i) | i <- ms+ , not $ ideclSource (unloc i)+ , let i' = unloc i+ , let n = unloc $ ideclName i'+ , let pkg = unpackFS . sl_fs <$> ideclPkgQual i']) +++ -- Ideas for removing redundant 'as' clauses.+ concatMap stripRedundantAlias ms +++ -- Ideas for replacing deprecated imports by their preferred+ -- equivalents.+ concatMap preferHierarchicalImports ms +reduceImports :: [LImportDecl GhcPs] -> [Idea]+reduceImports ms =+ [rawIdea Hint.Type.Warning "Use fewer imports"+ (ghcSpanToHSE (getloc $ head ms)) (f ms) (Just $ f x) [] rs+ | Just (x, rs) <- [simplify ms]]+ where f = unlines . map unsafePrettyPrint -simplify :: [ImportDecl S] -> Maybe ([ImportDecl S], [Refactoring R.SrcSpan])+simplify :: [LImportDecl GhcPs]+ -> Maybe ([LImportDecl GhcPs], [Refactoring R.SrcSpan]) simplify [] = Nothing-simplify (x:xs) = case simplifyHead x xs of+simplify (x : xs) = case simplifyHead x xs of Nothing -> first (x:) <$> simplify xs Just (xs, rs) -> Just $ maybe (xs, rs) (second (++ rs)) $ simplify xs --simplifyHead :: ImportDecl S -> [ImportDecl S] -> Maybe ([ImportDecl S], [Refactoring R.SrcSpan])-simplifyHead x [] = Nothing-simplifyHead x (y:ys) = case combine x y of+simplifyHead :: LImportDecl GhcPs+ -> [LImportDecl GhcPs]+ -> Maybe ([LImportDecl GhcPs], [Refactoring R.SrcSpan])+simplifyHead x (y : ys) = case combine x y of Nothing -> first (y:) <$> simplifyHead x ys Just (xy, rs) -> Just (xy : ys, rs)---combine :: ImportDecl S -> ImportDecl S -> Maybe (ImportDecl S, [Refactoring R.SrcSpan])-combine x y | qual, as, specs = Just (x, [Delete Import (toSS y)])- | qual, as, Just (ImportSpecList _ False xs) <- importSpecs x, Just (ImportSpecList _ False ys) <- importSpecs y = let newImp = x{importSpecs = Just $ ImportSpecList an False $ nub_ $ xs ++ ys}- in Just (newImp, [ Replace Import (toSS x) [] (prettyPrint newImp)- , Delete Import (toSS y) ] )-- | qual, as, isNothing (importSpecs x) || isNothing (importSpecs y) =- let (newImp, toDelete) = if isNothing (importSpecs x) then (x, y) else (y, x)- in Just (newImp, [Delete Import (toSS toDelete)])- | not (importQualified x), qual, specs, length ass == 1 =- let (newImp, toDelete) = if isJust (importAs x) then (x, y) else (y, x)- in Just (newImp, [Delete Import (toSS toDelete)])+simplifyHead x [] = Nothing +combine :: LImportDecl GhcPs+ -> LImportDecl GhcPs+ -> Maybe (LImportDecl GhcPs, [Refactoring R.SrcSpan])+combine x@(GHC.L _ x') y@(GHC.L _ y')+ -- Both (un/)qualified, common 'as', same names : Delete the second.+ | qual, as, specs = Just (x, [Delete Import (toSS' y)])+ -- Both (un/)qualified, common 'as', different names : Merge the+ -- second into the first and delete it.+ | qual, as+ , Just (False, xs) <- ideclHiding x'+ , Just (False, ys) <- ideclHiding y' =+ let newImp = noloc x'{ideclHiding = Just (False, noloc (unloc xs ++ unloc ys))}+ in Just (newImp, [Replace Import (toSS' x) [] (unsafePrettyPrint (unloc newImp))+ , Delete Import (toSS' y)])+ -- Both (un/qualified), common 'as', one has names the other doesn't+ -- : Delete the one with names.+ | qual, as, isNothing (ideclHiding x') || isNothing (ideclHiding y') =+ let (newImp, toDelete) = if isNothing (ideclHiding x') then (x, y) else (y, x)+ in Just (newImp, [Delete Import (toSS' toDelete)])+ -- Both unqualified, same names, one (and only one) has an 'as'+ -- clause : Delete the one without an 'as'.+ | not (ideclQualified x'), qual, specs, length ass == 1 =+ let (newImp, toDelete) = if isJust (ideclAs x') then (x, y) else (y, x)+ in Just (newImp, [Delete Import (toSS' toDelete)])+ -- No hints.+ | otherwise = Nothing where- qual = importQualified x == importQualified y- as = importAs x `eqMaybe` importAs y- ass = mapMaybe importAs [x,y]- specs = importSpecs x `eqMaybe` importSpecs y+ qual = ideclQualified x' == ideclQualified y'+ as = ideclAs x' `GHC.Util.eqMaybe` ideclAs y'+ ass = mapMaybe ideclAs [x', y']+ specs = transformBi (const noSrcSpan) (ideclHiding x') ==+ transformBi (const noSrcSpan) (ideclHiding y') -combine _ _ = Nothing+stripRedundantAlias :: LImportDecl GhcPs -> [Idea]+stripRedundantAlias x@(GHC.L loc i@GHC.ImportDecl {..})+ -- Suggest 'import M as M' be just 'import M'.+ | Just (unloc ideclName) == fmap unloc ideclAs =+ [suggest' "Redundant as" x (GHC.L loc i{ideclAs=Nothing}) [RemoveAsKeyword (toSS' x)]]+stripRedundantAlias _ = [] -combine1 :: ImportDecl S -> [Idea]-combine1 i@ImportDecl{..}- | Just (dropAnn importModule) == fmap dropAnn importAs- = [suggest "Redundant as" i i{importAs=Nothing} [RemoveAsKeyword (toSS i)]]-combine1 _ = []+preferHierarchicalImports :: LImportDecl GhcPs -> [Idea]+preferHierarchicalImports x@(GHC.L loc i@GHC.ImportDecl{ideclName=(GHC.L _ n),ideclPkgQual=Nothing})+ -- Suggest 'import IO' be rewritten 'import System.IO, import+ -- System.IO.Error, import Control.Exception(bracket, bracket_)'.+ | n == mkModuleName "IO" && isNothing (ideclHiding i) =+ [rawIdeaN Suggestion "Use hierarchical imports" (ghcSpanToHSE loc)+ (trimStart $ unsafePrettyPrint i) (+ Just $ unlines $ map (trimStart . unsafePrettyPrint)+ [ f "System.IO" Nothing, f "System.IO.Error" Nothing+ , f "Control.Exception" $ Just (False, noloc [mkLIE x | x <- ["bracket","bracket_"]])]) []]+ -- Suggest that a module import like 'Monad' should be rewritten with+ -- its hiearchical equivalent e.g. 'Control.Monad'.+ | Just y <- lookup (moduleNameString n) newNames =+ let newModuleName = y ++ "." ++ moduleNameString n+ r = [Replace R.ModuleName (toSS' x) [] newModuleName] in+ [suggest' "Use hierarchical imports"+ x (noloc (desugarQual i){ideclName=noloc (mkModuleName newModuleName)}) r]+ where+ -- Substitute a new module name.+ f a b = (desugarQual i){ideclName=noloc (mkModuleName a), ideclHiding=b}+ -- Wrap a literal name into an 'IE' (import/export) value.+ mkLIE :: String -> LIE GhcPs+ mkLIE n = noloc $ IEVar noext (noloc (IEName (noloc (mkVarUnqual (fsLit n)))))+ -- Rewrite 'import qualified X' as 'import qualified X as X'.+ desugarQual :: GHC.ImportDecl GhcPs -> GHC.ImportDecl GhcPs+ desugarQual i+ | ideclQualified i && isNothing (ideclAs i) = i{ideclAs = Just (ideclName i)}+ | otherwise = i +preferHierarchicalImports _ = [] +newNames :: [(String, String)] newNames = let (*) = flip (,) in ["Control" * "Monad" ,"Data" * "Char"@@ -118,28 +181,3 @@ -- ,"System" * "Locale" -- ,"System" * "Time" ]---hierarchy :: ImportDecl S -> [Idea]-hierarchy i@ImportDecl{importModule=m@(ModuleName _ x),importPkg=Nothing} | Just y <- lookup x newNames- =- let newModuleName = y ++ "." ++ x- r = [Replace R.ModuleName (toSS m) [] newModuleName] in- [suggest "Use hierarchical imports" i (desugarQual i){importModule=ModuleName an newModuleName} r]---- import IO is equivalent to--- import System.IO, import System.IO.Error, import Control.Exception(bracket, bracket_)-hierarchy i@ImportDecl{importModule=ModuleName _ "IO", importSpecs=Nothing,importPkg=Nothing}- = [rawIdeaN Suggestion "Use hierarchical imports" (srcInfoSpan $ ann i) (trimStart $ prettyPrint i) (- Just $ unlines $ map (trimStart . prettyPrint)- [f "System.IO" Nothing, f "System.IO.Error" Nothing- ,f "Control.Exception" $ Just $ ImportSpecList an False [IVar an $ toNamed x | x <- ["bracket","bracket_"]]]) []]- where f a b = (desugarQual i){importModule=ModuleName an a, importSpecs=b}--hierarchy _ = []----- import qualified X ==> import qualified X as X-desugarQual :: ImportDecl S -> ImportDecl S-desugarQual x | importQualified x && isNothing (importAs x) = x{importAs=Just (importModule x)}- | otherwise = x
src/Hint/Match.hs view
@@ -91,7 +91,7 @@ --------------------------------------------------------------------- -- PERFORM THE MATCHING -findIdeas :: [HintRule] -> Scope -> Module S -> Decl_ -> [Idea]+findIdeas :: [HintRule] -> Scope -> ModuleEx -> Decl_ -> [Idea] findIdeas matches s _ decl = timed "Hint" "Match apply" $ forceList [ (idea (hintRuleSeverity m) (hintRuleName m) x y [r]){ideaNote=notes} | decl <- findDecls decl
src/Hint/Naming.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE ViewPatterns #-} {- Suggest the use of camelCase @@ -11,6 +13,7 @@ Also allow test_ as it's a standard tasty-th idiom Also allow numbers separated by _ Also don't suggest anything mentioned elsewhere in the module+ Don't suggest for FFI, since they match their C names <TEST> data Yes = Foo | Bar'Test -- data Yes = Foo | BarTest@@ -19,6 +22,7 @@ data Yes = Foo {bar_cap :: Int} data No = FOO | BarBAR | BarBBar yes_foo = yes_foo + yes_foo -- yesFoo = ...+yes_fooPattern Nothing = 0 -- yesFooPattern Nothing = ... no = 1 where yes_foo = 2 a -== b = 1 myTest = 1; my_test = 1@@ -31,69 +35,86 @@ _foo__ = 1 section_1_1 = 1 runMutator# = 1+foreign import ccall hexml_node_child :: IO () </TEST> -} module Hint.Naming(namingHint) where -import Hint.Type-import Data.List.Extra+import Hint.Type (Idea, DeclHint', suggest', transformBi, isSym, toSrcSpan', ghcModule)+import Data.List.Extra (nubOrd, isPrefixOf) import Data.Data import Data.Char import Data.Maybe import Refact.Types hiding (RType(Match)) import qualified Data.Set as Set --namingHint :: DeclHint-namingHint _ modu = naming $ Set.fromList $ concatMap getNames $ moduleDecls modu--naming :: Set.Set String -> Decl_ -> [Idea]-naming seen x = [suggest "Use camelCase" x' x2' [Replace Bind (toSS x) [] (prettyPrint x2)] | not $ null res]- where res = [(n,y) | n <- nubOrd $ getNames x, Just y <- [suggestName n], not $ y `Set.member` seen]- x2 = replaceNames res x- x' = shorten x- x2' = shorten x2+import GHC.Util+import "ghc-lib-parser" BasicTypes+import "ghc-lib-parser" FastString+import "ghc-lib-parser" HsDecls+import "ghc-lib-parser" HsExtension+import "ghc-lib-parser" HsSyn+import "ghc-lib-parser" OccName+import "ghc-lib-parser" SrcLoc +namingHint :: DeclHint'+namingHint _ modu = naming $ Set.fromList $ concatMap getNames $ hsmodDecls $ unLoc (ghcModule modu) -shorten :: Decl_ -> Decl_-shorten x = case x of- FunBind sl (Match a b c d _:_) -> FunBind sl [f (Match a b c) d]- PatBind a b c _ -> f (PatBind a b) c- x -> x+naming :: Set.Set String -> LHsDecl GhcPs -> [Idea]+naming seen originalDecl =+ [ suggest' "Use camelCase"+ (shorten originalDecl)+ (shorten replacedDecl)+ [Replace Bind (toSrcSpan' originalDecl) [] (unsafePrettyPrint replacedDecl)]+ | not $ null suggestedNames+ ] where- dots = Var an ellipses- f cont (UnGuardedRhs _ _) = cont (UnGuardedRhs an dots) Nothing- f cont (GuardedRhss _ _) = cont (GuardedRhss an [GuardedRhs an [Qualifier an dots] dots]) Nothing+ suggestedNames =+ [ (originalName, suggestedName)+ | not $ isForD $ unloc originalDecl+ , originalName <- nubOrd $ getNames originalDecl+ , Just suggestedName <- [suggestName originalName]+ , not $ suggestedName `Set.member` seen+ ]+ replacedDecl = replaceNames suggestedNames originalDecl +shorten :: LHsDecl GhcPs -> LHsDecl GhcPs+shorten (L locDecl (ValD ttg0 bind@(FunBind _ _ matchGroup@(MG _ (L locMatches matches) _) _ _))) =+ L locDecl (ValD ttg0 bind {fun_matches = matchGroup {mg_alts = L locMatches $ map shortenMatch matches}})+shorten (L locDecl (ValD ttg0 bind@(PatBind _ _ grhss@(GRHSs _ rhss _) _))) =+ L locDecl (ValD ttg0 bind {pat_rhs = grhss {grhssGRHSs = map shortenLGRHS rhss}})+shorten x = x -getNames :: Decl_ -> [String]-getNames x = case x of- FunBind{} -> name- PatBind{} -> name- TypeDecl{} -> name- DataDecl _ _ _ _ cons _ -> name ++ [fromNamed x | QualConDecl _ _ _ x <- cons, x <- f x]- GDataDecl _ _ _ _ _ cons _ -> name ++ [fromNamed x | GadtDecl _ x _ _ _ _ <- cons]- TypeFamDecl{} -> name- DataFamDecl{} -> name- ClassDecl{} -> name- _ -> []+shortenMatch :: LMatch GhcPs (LHsExpr GhcPs) -> LMatch GhcPs (LHsExpr GhcPs)+shortenMatch (L locMatch match@(Match _ _ _ grhss@(GRHSs _ rhss _))) =+ L locMatch match {m_grhss = grhss {grhssGRHSs = map shortenLGRHS rhss}}+shortenMatch x = x++shortenLGRHS :: LGRHS GhcPs (LHsExpr GhcPs) -> LGRHS GhcPs (LHsExpr GhcPs)+shortenLGRHS (L locGRHS (GRHS ttg0 guards (L locExpr _))) =+ L locGRHS (GRHS ttg0 guards (L locExpr dots)) where- name = [fromNamed x]+ dots :: HsExpr GhcPs+ dots = HsLit NoExt (HsString (SourceText "...") (mkFastString "..."))+shortenLGRHS x = x - f (ConDecl _ x _) = [x]- f (InfixConDecl _ _ x _) = [x]- f (RecDecl _ x _) = [x]+getNames :: LHsDecl GhcPs -> [String]+getNames (L _ decl) = declName decl : getConstructorNames decl +getConstructorNames :: HsDecl GhcPs -> [String]+getConstructorNames (TyClD _ (DataDecl _ _ _ _ (HsDataDefn _ _ _ _ _ cons _))) =+ concatMap (map unsafePrettyPrint . getConNames . unLoc) cons+getConstructorNames _ = [] suggestName :: String -> Maybe String-suggestName x- | isSym x || good || not (any isLower x) || any isDigit x ||- any (`isPrefixOf` x) ["prop_","case_","unit_","test_","spec_","scprop_","hprop_"] = Nothing- | otherwise = Just $ f x+suggestName original+ | isSym original || good || not (any isLower original) || any isDigit original ||+ any (`isPrefixOf` original) ["prop_","case_","unit_","test_","spec_","scprop_","hprop_"] = Nothing+ | otherwise = Just $ f original where- good = all isAlphaNum $ drp '_' $ drp '#' $ drp '\'' $ reverse $ drp '_' x+ good = all isAlphaNum $ drp '_' $ drp '#' $ drp '\'' $ reverse $ drp '_' original drp x = dropWhile (== x) f xs = us ++ g ys@@ -105,8 +126,8 @@ | otherwise = g xs g [] = [] --replaceNames :: Data a => [(String,String)] -> a -> a-replaceNames rep = transformBi f- where f (Ident _ x) = Ident an $ fromMaybe x $ lookup x rep- f x = x+replaceNames :: Data a => [(String, String)] -> a -> a+replaceNames rep = transformBi replace+ where+ replace :: OccName -> OccName+ replace (unsafePrettyPrint -> name) = mkOccName srcDataName $ fromMaybe name $ lookup name rep
src/Hint/NewType.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE PackageImports #-} {- Suggest newtype instead of data for type declarations that have only one field. Don't suggest newtype for existentially@@ -10,58 +12,129 @@ data Foo a b = Foo a -- newtype Foo a b = Foo a data Foo = Foo { field1, field2 :: Int} data S a = forall b . Show b => S b+{-# LANGUAGE RankNTypes #-}; data Foo = Foo (forall a. a) -- newtype Foo = Foo (forall a. a) data Color a = Red a | Green a | Blue a data Pair a b = Pair a b data Foo = Bar data Foo a = Eq a => MkFoo a+data Foo a = () => Foo a -- newtype Foo a = Foo a data X = Y {-# UNPACK #-} !Int -- newtype X = Y Int data A = A {b :: !C} -- newtype A = A {b :: C} data A = A Int# {-# LANGUAGE UnboxedTuples #-}; data WithAnn x = WithAnn (# Ann, x #)+{-# LANGUAGE UnboxedTuples #-}; data WithAnn x = WithAnn {getWithAnn :: (# Ann, x #)} data A = A () -- newtype A = A ()-newtype Foo = Foo Int deriving (Show, Eq) -- newtype Foo = Foo Int deriving newtype (Show, Eq)-newtype Foo = Foo { getFoo :: Int } deriving (Show, Eq) -- newtype Foo = Foo { getFoo :: Int } deriving newtype (Show, Eq)+newtype Foo = Foo Int deriving (Show, Eq) --+newtype Foo = Foo { getFoo :: Int } deriving (Show, Eq) -- newtype Foo = Foo Int deriving stock Show </TEST> -} module Hint.NewType (newtypeHint) where -import Hint.Type+import Hint.Type (Idea, DeclHint', Note(DecreasesLaziness), ideaNote, ignoreNoSuggestion', suggestN') -newtypeHint :: DeclHint+import Data.List (isSuffixOf)+import "ghc-lib-parser" HsDecls+import "ghc-lib-parser" HsSyn+import "ghc-lib-parser" Outputable+import "ghc-lib-parser" SrcLoc++newtypeHint :: DeclHint' newtypeHint _ _ x = newtypeHintDecl x ++ newTypeDerivingStrategiesHintDecl x -newtypeHintDecl :: Decl_ -> [Idea]-newtypeHintDecl x- | Just (DataType s, t, f) <- singleSimpleField x- = [(suggestN "Use newtype instead of data" x $ f (NewType s) $ fromTyBang t)- {ideaNote = [DecreasesLaziness | not $ isTyBang t]}]+newtypeHintDecl :: LHsDecl GhcPs -> [Idea]+newtypeHintDecl old+ | Just WarnNewtype{newDecl, insideType} <- singleSimpleField old+ = [(suggestN' "Use newtype instead of data" old newDecl)+ {ideaNote = [DecreasesLaziness | warnBang insideType]}] newtypeHintDecl _ = [] +newTypeDerivingStrategiesHintDecl :: LHsDecl GhcPs -> [Idea]+newTypeDerivingStrategiesHintDecl decl@(L _ (TyClD _ (DataDecl _ _ _ _ dataDef))) =+ [ignoreNoSuggestion' "Use DerivingStrategies" decl | not $ isData dataDef, not $ hasAllStrategies dataDef]+newTypeDerivingStrategiesHintDecl _ = [] -singleSimpleField :: Decl_ -> Maybe (DataOrNew S, Type_, DataOrNew S -> Type_ -> Decl_)-singleSimpleField (DataDecl x1 dt x2 x3 [QualConDecl y1 Nothing Nothing ctor] x4)- | Just (t, ft) <- f ctor = Just (dt, t, \dt t -> DataDecl x1 dt x2 x3 [QualConDecl y1 Nothing Nothing $ ft t] x4)- where- f (ConDecl x1 x2 [t]) | not $ isKindHash t = Just (t, \t -> ConDecl x1 x2 [t])- f (RecDecl x1 x2 [FieldDecl y1 [y2] t]) = Just (t, \t -> RecDecl x1 x2 [FieldDecl y1 [y2] t])- f _ = Nothing+hasAllStrategies :: HsDataDefn GhcPs -> Bool+hasAllStrategies (HsDataDefn _ NewType _ _ _ _ (L _ xs)) = all hasStrategyClause xs+hasAllStrategies _ = False++isData :: HsDataDefn GhcPs -> Bool+isData (HsDataDefn _ NewType _ _ _ _ _) = False+isData (HsDataDefn _ DataType _ _ _ _ _) = True+isData _ = False++hasStrategyClause :: LHsDerivingClause GhcPs -> Bool+hasStrategyClause (L _ (HsDerivingClause _ (Just _) _)) = True+hasStrategyClause _ = False++data WarnNewtype = WarnNewtype+ { newDecl :: LHsDecl GhcPs+ , insideType :: HsType GhcPs+ }++-- | Given a declaration, returns the suggested \"newtype\"ized declaration following these guidelines:+-- * Types ending in a \"#\" are __ignored__, because they are usually unboxed primitives - @data X = X Int#@+-- * @ExistentialQuantification@ stuff is __ignored__ - @data X = forall t. X t@+-- * Constructors with (nonempty) constraints are __ignored__ - @data X a = (Eq a) => X a@+-- * Single field constructors get newtyped - @data X = X Int@ -> @newtype X = X Int@+-- * Single record field constructors get newtyped - @data X = X {getX :: Int}@ -> @newtype X = X {getX :: Int}@+-- * All other declarations are ignored.+singleSimpleField :: LHsDecl GhcPs -> Maybe WarnNewtype+singleSimpleField (L loc (TyClD ext decl@(DataDecl _ _ _ _ dataDef@(HsDataDefn _ DataType _ _ _ [L _ constructor] _))))+ | Just inType <- simpleCons constructor =+ Just WarnNewtype+ { newDecl = L loc $ TyClD ext decl {tcdDataDefn = dataDef+ { dd_ND = NewType+ , dd_cons = map (\(L consloc x) -> L consloc $ dropConsBang x) $ dd_cons dataDef+ }}+ , insideType = inType+ } singleSimpleField _ = Nothing -newTypeDerivingStrategiesHintDecl :: Decl_ -> [Idea]-newTypeDerivingStrategiesHintDecl x = [ignoreN "Use DerivingStrategies" x new | Just new <- [newtypeDecl x]]+-- | Checks whether its argument is a \"simple constructor\" (see criteria in 'singleSimpleFieldNew')+-- returning the type inside the constructor if it is. This is needed for strictness analysis.+simpleCons :: ConDecl GhcPs -> Maybe (HsType GhcPs)+simpleCons (ConDeclH98 _ _ _ [] context (PrefixCon [L _ inType]) _)+ | emptyOrNoContext context+ , not $ isUnboxedTuple inType+ , not $ isHashy inType+ = Just inType+simpleCons (ConDeclH98 _ _ _ [] context (RecCon (L _ [L _ (ConDeclField _ [_] (L _ inType) _)])) _)+ | emptyOrNoContext context+ , not $ isUnboxedTuple inType+ , not $ isHashy inType+ = Just inType+simpleCons _ = Nothing -newtypeDecl :: Decl_ -> Maybe Decl_-newtypeDecl (DataDecl x1 x2@(NewType _) x3 x4 x5 x6)- | any hasNoStrategy x6 = Just $ DataDecl x1 x2 x3 x4 x5 (withNewtype <$> x6)-newtypeDecl (GDataDecl x1 x2@(NewType _) x3 x4 x5 x6 x7)- | any hasNoStrategy x7 = Just $ GDataDecl x1 x2 x3 x4 x5 x6 (withNewtype <$> x7)-newtypeDecl _ = Nothing+isHashy :: HsType GhcPs -> Bool+isHashy (HsTyVar _ _ identifier) = "#" `isSuffixOf` showSDocUnsafe (ppr identifier)+isHashy _ = False -hasNoStrategy :: Deriving a -> Bool-hasNoStrategy (Deriving _ Nothing _) = True-hasNoStrategy _ = False+warnBang :: HsType GhcPs -> Bool+warnBang (HsBangTy _ (HsSrcBang _ _ SrcStrict) _) = False+warnBang _ = True -withNewtype :: Deriving a -> Deriving a-withNewtype (Deriving l Nothing rs) = Deriving l (Just $ DerivNewtype l) rs-withNewtype d = d+emptyOrNoContext :: Maybe (LHsContext GhcPs) -> Bool+emptyOrNoContext Nothing = True+emptyOrNoContext (Just (L _ [])) = True+emptyOrNoContext _ = False++-- | The \"Bang\" here refers to 'HsSrcBang', which notably also includes @UNPACK@ pragmas!+dropConsBang :: ConDecl GhcPs -> ConDecl GhcPs+dropConsBang decl@(ConDeclH98 _ _ _ _ _ (PrefixCon fields) _) =+ decl {con_args = PrefixCon $ map getBangType fields}+dropConsBang decl@(ConDeclH98 _ _ _ _ _ (RecCon (L recloc conDeclFields)) _) =+ decl {con_args = RecCon $ L recloc $ removeUnpacksRecords conDeclFields}+ where+ removeUnpacksRecords :: [LConDeclField GhcPs] -> [LConDeclField GhcPs]+ removeUnpacksRecords = map (\(L conDeclFieldLoc x) -> L conDeclFieldLoc $ removeConDeclFieldUnpacks x)++ removeConDeclFieldUnpacks :: ConDeclField GhcPs -> ConDeclField GhcPs+ removeConDeclFieldUnpacks conDeclField@(ConDeclField _ _ fieldType _) =+ conDeclField {cd_fld_type = getBangType fieldType}+ removeConDeclFieldUnpacks x = x+dropConsBang x = x++isUnboxedTuple :: HsType GhcPs -> Bool+isUnboxedTuple (HsTupleTy _ HsUnboxedTuple _) = True+isUnboxedTuple _ = False
src/Hint/Pattern.hs view
@@ -76,7 +76,7 @@ noPatBind (PatBind a _ b c) = PatBind a (PWildCard a) b c noPatBind x = x - strict = "Strict" `elem` [n | LanguagePragma _ ns <- modulePragmas modu, Ident _ n <- ns]+ strict = "Strict" `elem` [n | LanguagePragma _ ns <- modulePragmas (hseModule modu), Ident _ n <- ns] hints :: (String -> Pattern -> [Refactoring R.SrcSpan] -> Idea) -> Pattern -> [Idea]
src/Hint/Pragma.hs view
@@ -36,9 +36,9 @@ pragmaHint :: ModuHint-pragmaHint _ x = languageDupes lang ++ optToPragma x lang+pragmaHint _ x = languageDupes lang ++ optToPragma (hseModule x) lang where- lang = [x | x@LanguagePragma{} <- modulePragmas x]+ lang = [x | x@LanguagePragma{} <- modulePragmas (hseModule x)] optToPragma :: Module_ -> [ModulePragma S] -> [Idea] optToPragma x lang =
src/Hint/Restrict.hs view
@@ -26,11 +26,11 @@ -- FIXME: The settings should be partially applied, but that's hard to orchestrate right now restrictHint :: [Setting] -> ModuHint restrictHint settings scope m =- checkPragmas modu (modulePragmas m) restrict ++- maybe [] (checkImports modu $ moduleImports m) (Map.lookup RestrictModule restrict) ++- maybe [] (checkFunctions modu $ moduleDecls m) (Map.lookup RestrictFunction restrict)+ checkPragmas modu (modulePragmas (hseModule m)) restrict +++ maybe [] (checkImports modu $ moduleImports (hseModule m)) (Map.lookup RestrictModule restrict) +++ maybe [] (checkFunctions modu $ moduleDecls (hseModule m)) (Map.lookup RestrictFunction restrict) where- modu = moduleName m+ modu = moduleName (hseModule m) restrict = restrictions settings ---------------------------------------------------------------------
src/Hint/Smell.hs view
@@ -85,7 +85,7 @@ import qualified Data.Map as Map smellModuleHint :: [Setting] -> ModuHint-smellModuleHint settings scope m@(moduleImports -> imports) = case Map.lookup SmellManyImports (smells settings) of+smellModuleHint settings scope (moduleImports . hseModule -> imports) = case Map.lookup SmellManyImports (smells settings) of Just n | length imports >= n -> let span = foldl1 mergeSrcSpan $ srcInfoSpan . ann <$> imports displayImports = unlines $ f <$> imports
src/Hint/Type.hs view
@@ -1,6 +1,7 @@+{-# LANGUAGE PackageImports #-} module Hint.Type(- DeclHint, ModuHint, CrossHint, Hint(..),+ DeclHint, DeclHint', ModuHint, CrossHint, Hint(..), module Export ) where @@ -10,20 +11,22 @@ import Idea as Export import Prelude import Refact as Export-+import "ghc-lib-parser" HsExtension+import "ghc-lib-parser" HsDecls -type DeclHint = Scope -> Module_ -> Decl_ -> [Idea]-type ModuHint = Scope -> Module_ -> [Idea]-type CrossHint = [(Scope, Module_)] -> [Idea]+type DeclHint = Scope -> ModuleEx -> Decl_ -> [Idea]+type DeclHint' = Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea]+type ModuHint = Scope -> ModuleEx -> [Idea]+type CrossHint = [(Scope, ModuleEx)] -> [Idea] -- | Functions to generate hints, combined using the 'Monoid' instance. data Hint {- PUBLIC -} = Hint- {hintModules :: [Setting] -> [(Scope, Module SrcSpanInfo)] -> [Idea] -- ^ Given a list of modules (and their scope information) generate some 'Idea's.- ,hintModule :: [Setting] -> Scope -> Module SrcSpanInfo -> [Idea] -- ^ Given a single module and its scope information generate some 'Idea's.- ,hintDecl :: [Setting] -> Scope -> Module SrcSpanInfo -> Decl SrcSpanInfo -> [Idea]+ { hintModules :: [Setting] -> [(Scope, ModuleEx)] -> [Idea] -- ^ Given a list of modules (and their scope information) generate some 'Idea's.+ , hintModule :: [Setting] -> Scope -> ModuleEx -> [Idea] -- ^ Given a single module and its scope information generate some 'Idea's.+ , hintDecl :: [Setting] -> Scope -> ModuleEx -> Decl SrcSpanInfo -> [Idea]+ , hintDecl' :: [Setting] -> Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea] -- ^ Given a declaration (with a module and scope) generate some 'Idea's. -- This function will be partially applied with one module/scope, then used on multiple 'Decl' values.- ,hintComment :: [Setting] -> Comment -> [Idea] -- ^ Given a comment generate some 'Idea's. } instance Semigroup Hint where@@ -31,8 +34,8 @@ (\a b -> x1 a b ++ y1 a b) (\a b c -> x2 a b c ++ y2 a b c) (\a b c d -> x3 a b c d ++ y3 a b c d)- (\a b -> x4 a b ++ y4 a b)+ (\a b c d -> x4 a b c d ++ y4 a b c d) instance Monoid Hint where- mempty = Hint (\_ _ -> []) (\_ _ _ -> []) (\_ _ _ _ -> []) (\_ _ -> [])+ mempty = Hint (\_ _ -> []) (\_ _ _ -> []) (\_ _ _ _ -> []) (\_ _ _ _ -> []) mappend = (<>)
src/Hint/Unsafe.hs view
@@ -33,7 +33,7 @@ , isUnsafeDecl d, x `notElem_` noinline] where gen x = InlineSig an False Nothing $ UnQual an x- noinline = [q | InlineSig _ False Nothing (UnQual _ q) <- moduleDecls m]+ noinline = [q | InlineSig _ False Nothing (UnQual _ q) <- moduleDecls (hseModule m)] isUnsafeDecl :: Decl_ -> Bool isUnsafeDecl (PatBind _ _ rhs bind) =
src/Idea.hs view
@@ -1,12 +1,13 @@ {-# LANGUAGE RecordWildCards, NoMonomorphismRestriction #-}+{-# LANGUAGE PackageImports #-} module Idea( Idea(..),- rawIdea, idea, suggest, warn, ignore,- rawIdeaN, suggestN, ignoreN,+ rawIdea, idea, idea', suggest, suggest', warn, warn',ignore, ignore',+ rawIdeaN, suggestN, suggestN', ignoreN, ignoreNoSuggestion', showIdeasJson, showANSI, Note(..), showNotes,- Severity(..)+ Severity(..), ) where import Data.Functor@@ -17,7 +18,9 @@ import Refact.Types hiding (SrcSpan) import qualified Refact.Types as R import Prelude-+import qualified "ghc-lib-parser" SrcLoc as GHC+import qualified "ghc-lib-parser" Outputable+import qualified GHC.Util as GHC -- | An idea suggest by a 'Hint'. data Idea = Idea@@ -79,17 +82,65 @@ where xs = lines $ tt x +rawIdea :: Severity -> String -> SrcSpan -> String -> Maybe String -> [Note]-> [Refactoring R.SrcSpan] -> Idea rawIdea = Idea [] []+rawIdeaN :: Severity -> String -> SrcSpan -> String -> Maybe String -> [Note] -> Idea rawIdeaN a b c d e f = Idea [] [] a b c d e f [] +idea :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) =>+ Severity -> String -> ast SrcSpanInfo -> a -> [Refactoring R.SrcSpan] -> Idea idea severity hint from to = rawIdea severity hint (srcInfoSpan $ ann from) (f from) (Just $ f to) [] where f = trimStart . prettyPrint +idea' :: (GHC.HasSrcSpan a, Outputable.Outputable a) =>+ Severity -> String -> a -> a -> [Refactoring R.SrcSpan] -> Idea+idea' severity hint from to =+ rawIdea severity hint (ghcSpanToHSE (GHC.getLoc from)) (GHC.unsafePrettyPrint from) (Just $ GHC.unsafePrettyPrint to) []++suggest :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) =>+ String -> ast SrcSpanInfo -> a -> [Refactoring R.SrcSpan] -> Idea suggest = idea Suggestion++suggest' :: (GHC.HasSrcSpan a, Outputable.Outputable a) =>+ String -> a -> a -> [Refactoring R.SrcSpan] -> Idea+suggest' = idea' Suggestion++warn :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) =>+ String -> ast SrcSpanInfo -> a -> [Refactoring R.SrcSpan] -> Idea warn = idea Warning++warn' :: (GHC.HasSrcSpan a, Outputable.Outputable a) =>+ String -> a -> a -> [Refactoring R.SrcSpan] -> Idea+warn' = idea' Warning++ignoreNoSuggestion' :: (GHC.HasSrcSpan a, Outputable.Outputable a)+ => String -> a -> Idea+ignoreNoSuggestion' hint x = rawIdeaN Ignore hint (ghcSpanToHSE (GHC.getLoc x)) (GHC.unsafePrettyPrint x) Nothing []++ignore :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) =>+ String -> ast SrcSpanInfo -> a -> [Refactoring R.SrcSpan] -> Idea ignore = idea Ignore +ignore' :: (GHC.HasSrcSpan a, Outputable.Outputable a) =>+ String -> a -> a -> [Refactoring R.SrcSpan] -> Idea+ignore' = idea' Ignore++ideaN :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) =>+ Severity -> String -> ast SrcSpanInfo -> a -> Idea ideaN severity hint from to = idea severity hint from to [] +ideaN' :: (GHC.HasSrcSpan a, Outputable.Outputable a) =>+ Severity -> String -> a -> a -> Idea+ideaN' severity hint from to = idea' severity hint from to []++suggestN :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) =>+ String -> ast SrcSpanInfo -> a -> Idea suggestN = ideaN Suggestion++suggestN' :: (GHC.HasSrcSpan a, Outputable.Outputable a) =>+ String -> a -> a -> Idea+suggestN' = ideaN' Suggestion++ignoreN :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) =>+ String -> ast SrcSpanInfo -> a -> Idea ignoreN = ideaN Ignore
src/Language/Haskell/HLint4.hs view
@@ -23,7 +23,7 @@ -- * Hints Hint, resolveHints, -- * Parse files- ModuleEx, parseModuleEx, defaultParseFlags, parseFlagsAddFixities, ParseError(..), ParseFlags(..), CppFlags(..)+ ModuleEx, parseModuleEx, createModuleEx, defaultParseFlags, parseFlagsAddFixities, ParseError(..), ParseFlags(..), CppFlags(..) ) where import Config.Type@@ -34,6 +34,9 @@ import HSE.All import Hint.All hiding (resolveHints) import qualified Hint.All as H+import qualified ApiAnnotation as GHC+import qualified HsSyn as GHC+import SrcLoc import CmdLine import Paths_hlint @@ -127,8 +130,7 @@ -- which is slower and often pointless (by default HLint passes modules singularly, using -- @--cross@ to pass all modules together). applyHints :: [Classify] -> Hint -> [ModuleEx] -> [Idea]-applyHints a b = H.applyHints a b . map pm_hsext-+applyHints = H.applyHints -- | Snippet from the documentation, if this changes, update the documentation _docs :: IO ()@@ -136,3 +138,10 @@ (flags, classify, hint) <- autoSettings Right m <- parseModuleEx flags "MyFile.hs" Nothing print $ applyHints classify hint [m]+++-- | Create a 'ModuleEx' from GHC annotations and module tree.+-- Note that any hints that work on the @haskell-src-exts@ won't work.+createModuleEx:: GHC.ApiAnns -> Located (GHC.HsModule GHC.GhcPs) -> ModuleEx+createModuleEx anns ast = ModuleEx empty [] ast anns+ where empty = Module an Nothing [] [] []
src/Refact.hs view
@@ -1,8 +1,15 @@-module Refact(toRefactSrcSpan, toSS) where+{-# LANGUAGE PackageImports #-}+module Refact+ ( toRefactSrcSpan+ , toSS, toSS'+ , toSrcSpan'+ ) where import qualified Refact.Types as R import HSE.All +import qualified "ghc-lib-parser" SrcLoc as GHC+ toRefactSrcSpan :: SrcSpan -> R.SrcSpan toRefactSrcSpan ss = R.SrcSpan (srcSpanStartLine ss) (srcSpanStartColumn ss)@@ -11,3 +18,18 @@ toSS :: Annotated a => a S -> R.SrcSpan toSS = toRefactSrcSpan . srcInfoSpan . ann++-- | Don't crash in case ghc gives us a \"fake\" span,+-- opting instead to show @0 0 0 0@ coordinates.+toSrcSpan' :: GHC.HasSrcSpan a => a -> R.SrcSpan+toSrcSpan' x = case GHC.getLoc x of+ GHC.RealSrcSpan span ->+ R.SrcSpan (GHC.srcSpanStartLine span)+ (GHC.srcSpanStartCol span)+ (GHC.srcSpanEndLine span)+ (GHC.srcSpanEndCol span)+ GHC.UnhelpfulSpan _ ->+ R.SrcSpan 0 0 0 0++toSS' :: GHC.Located e -> R.SrcSpan+toSS' = toRefactSrcSpan . ghcSpanToHSE . GHC.getLoc