hlint 3.2.1 → 3.2.2
raw patch · 11 files changed
+61/−24 lines, 11 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Language.Haskell.HLint: showIdeaANSI :: Idea -> String
Files
- CHANGES.txt +5/−0
- README.md +15/−2
- hlint.cabal +1/−1
- src/GHC/Util/Brackets.hs +4/−2
- src/GHC/Util/HsExpr.hs +3/−4
- src/Hint/Bracket.hs +6/−3
- src/Hint/Lambda.hs +6/−4
- src/Hint/NewType.hs +6/−5
- src/HsColour.hs +7/−1
- src/Idea.hs +7/−1
- src/Language/Haskell/HLint.hs +1/−1
CHANGES.txt view
@@ -1,5 +1,10 @@ Changelog for HLint (* = breaking change) +3.2.2, released 2020-11-15+ #1166, detect more unboxed data to avoid suggesting newtype+ #1153, fix incorrect redundant bracket with @($foo)+ #1163, do not suggest "Use lambda" when there are guards+ #1160, add showIdeaANSI to show Idea values with escape codes 3.2.1, released 2020-10-15 #1150, remove the Duplicate hint (was slow) #1149, allow within to use module wildcards, e.g. **.Foo
README.md view
@@ -86,13 +86,12 @@ HLint is integrated into lots of places: * Lots of editors have HLint plugins (quite a few have more than one HLint plugin).-* HLint is part of the multiple editor plugins [ghc-mod](https://hackage.haskell.org/package/ghc-mod) and [Intero](https://github.com/commercialhaskell/intero).+* HLint is part of the multiple Haskell IDEs, [haskell-language-server](https://github.com/haskell/haskell-language-server), [ghc-mod](https://hackage.haskell.org/package/ghc-mod) and [Intero](https://github.com/commercialhaskell/intero). * [HLint Source Plugin](https://github.com/ocharles/hlint-source-plugin) makes HLint available as a GHC plugin. * [Splint](https://github.com/tfausak/splint) is another source plugin that doesn't require reparsing the GHC source if you are on the latest GHC version. * [Code Climate](https://docs.codeclimate.com/v1.0/docs/hlint) is a CI for analysis which integrates HLint. * [Danger](http://allocinit.io/haskell/danger-and-hlint/) can be used to automatically comment on pull requests with HLint suggestions. * [Restyled](https://restyled.io) includes an HLint Restyler to automatically run `hlint --refactor` on files changed in GitHub Pull Requests.-* [lpaste](http://lpaste.net/) integrates with HLint - suggestions are shown at the bottom. * [hlint-test](https://hackage.haskell.org/package/hlint-test) helps you write a small test runner with HLint. * [hint-man](https://github.com/apps/hint-man) automatically submits reviews to opened pull requests in your repositories with inline hints. * [CircleCI](https://circleci.com/orbs/registry/orb/haskell-works/hlint) has a plugin to run HLint more easily.@@ -172,6 +171,20 @@ HLint enables/disables a set of extensions designed to allow as many files to parse as possible, but sometimes you'll need to enable an additional extension (e.g. Arrows, QuasiQuotes, ...), or disable some (e.g. MagicHash) to enable your code to parse. You can enable extensions by specifying additional command line arguments in [.hlint.yaml](./README.md#customizing-the-hints), e.g.: `- arguments: [-XQuasiQuotes]`.++#### How do I only run hlint on changed files?++If you're using git, it may be helpful to only run hlint on changed files. This can be a considerable speedup on very large codebases.++```bash+{ git diff --diff-filter=d --name-only $(git merge-base HEAD origin/master) -- "***.hs" && git ls-files -o --exclude-standard -- "***.hs"; } | xargs hlint+```++Because hlint's `--refactor` option only works when you pass a single file, this approach is also helpful to enable refactoring many files in a single command:++```bash+{ git diff --diff-filter=d --name-only $(git merge-base HEAD origin/master) -- "***.hs" && git ls-files -o --exclude-standard -- "***.hs"; } | xargs -I file hlint file --refactor --refactor-options="--inplace --step"+``` ### Configuration
hlint.cabal view
@@ -1,7 +1,7 @@ cabal-version: >= 1.18 build-type: Simple name: hlint-version: 3.2.1+version: 3.2.2 license: BSD3 license-file: LICENSE category: Development
src/GHC/Util/Brackets.hs view
@@ -46,7 +46,9 @@ RecordUpd{} -> True ArithSeq{}-> True HsBracket{} -> True- HsSpliceE {} -> True+ -- HsSplice might be $foo, where @($foo) would require brackets,+ -- but in that case the $foo is a type, so we can still mark Splice as atomic+ HsSpliceE{} -> True HsOverLit _ x | not $ isNegativeOverLit x -> True HsLit _ x | not $ isNegativeLit x -> True _ -> False@@ -144,8 +146,8 @@ HsExplicitListTy{} -> True HsTyVar{} -> True HsSumTy{} -> True- HsSpliceTy{} -> True HsWildCardTy{} -> True+ -- HsSpliceTy{} is not atomic, because of @($foo) _ -> False isAtom _ = False -- '{-# COMPLETE L #-}'
src/GHC/Util/HsExpr.hs view
@@ -102,8 +102,7 @@ where g i y = if a then f i b else b where (a, b) = op y- f i y@(L _ e) | needBracket i x y = addParen y- f _ y = y+ f i y = if needBracket i x y then addParen y else y -- Add brackets as suggested 'needBracket at 1-level of depth. rebracket1 :: LHsExpr GhcPs -> LHsExpr GhcPs@@ -203,8 +202,8 @@ where -- Factor the expression with respect to x. factor :: LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [LHsExpr GhcPs])- factor y@(L _ (HsApp _ ini lst)) | view lst == Var_ x = Just (ini, [ini])- factor y@(L _ (HsApp _ ini lst)) | Just (z, ss) <- factor lst+ factor (L _ (HsApp _ ini lst)) | view lst == Var_ x = Just (ini, [ini])+ factor (L _ (HsApp _ ini lst)) | Just (z, ss) <- factor lst = let r = niceDotApp ini z in if astEq r z then Just (r, ss) else Just (r, ini : ss) factor (L _ (OpApp _ y op (factor -> Just (z, ss))))| isDol op
src/Hint/Bracket.hs view
@@ -92,6 +92,9 @@ special = foo $ Rec{x=1} special = foo (f{x=1}) loadCradleOnlyonce = skipManyTill anyMessage (message @PublishDiagnosticsNotification)++-- type splices are a bit special+no = f @($x) </TEST> -} @@ -121,7 +124,7 @@ -- Brackets the roots of annotations are fine, so we strip them. annotations :: AnnDecl GhcPs -> AnnDecl GhcPs annotations= descendBi $ \x -> case (x :: LHsExpr GhcPs) of- L l (HsPar _ x) -> x+ L _ (HsPar _ x) -> x x -> x -- If we find ourselves in the context of a section and we want to@@ -230,7 +233,7 @@ dollar :: LHsExpr GhcPs -> [Idea] dollar = concatMap f . universe where- f x = [ (suggest "Redundant $" x y [r]){ideaSpan = getLoc d} | o@(L _ (OpApp _ a d b)) <- [x], isDol d+ f x = [ (suggest "Redundant $" x y [r]){ideaSpan = getLoc d} | L _ (OpApp _ a d b) <- [x], isDol d , let y = noLoc (HsApp noExtField a b) :: LHsExpr GhcPs , not $ needBracket 0 y a , not $ needBracket 1 y b@@ -246,7 +249,7 @@ , let r = Replace Expr (toSS e) [("a", toSS a1), ("b", toSS a2)] "a (b)" ] ++ -- Special case of (v1 . v2) <$> v3 [ (suggest "Redundant bracket" x y [r]){ideaSpan = locPar}- | L _ (OpApp _ (L locPar (HsPar _ o1@(L locNoPar (OpApp _ v1 (isDot -> True) v2)))) o2 v3) <- [x], varToStr o2 == "<$>"+ | L _ (OpApp _ (L locPar (HsPar _ o1@(L locNoPar (OpApp _ _ (isDot -> True) _)))) o2 v3) <- [x], varToStr o2 == "<$>" , let y = noLoc (OpApp noExtField o1 o2 v3) :: LHsExpr GhcPs , let r = Replace Expr (toRefactSrcSpan locPar) [("a", toRefactSrcSpan locNoPar)] "a"] ++
src/Hint/Lambda.hs view
@@ -80,6 +80,7 @@ baz = bar (\x -> (x +)) -- (+) xs `withArgsFrom` args = f args foo = bar (\x -> case x of Y z -> z) -- \(Y z) -> z+foo = bar (\x -> case x of Y z | z > 0 -> z) -- \case Y z | z > 0 -> z yes = blah (\ x -> case x of A -> a; B -> b) -- \ case A -> a; B -> b yes = blah (\ x -> case x of A -> a; B -> b) -- @Note may require `{-# LANGUAGE LambdaCase #-}` adding to the top of the file no = blah (\ x -> case x of A -> a x; B -> b x)@@ -134,7 +135,7 @@ origBind@FunBind {fun_id = funName@(L loc1 _), fun_matches = MG {mg_alts = L _ [L _ (Match _ ctxt@(FunRhs _ Prefix _) pats (GRHSs _ [L _ (GRHS _ [] origBody@(L loc2 _))] bind))]}}))- | L _ (EmptyLocalBinds noExtField) <- bind+ | L _ (EmptyLocalBinds _) <- bind , isLambda $ fromParen origBody , null (universeBi pats :: [HsExpr GhcPs]) = let (newPats, newBody) = fromLambda . lambda pats $ origBody@@ -249,7 +250,8 @@ -- we need to -- * add brackets to the match, because matches in lambdas require them -- * mark match as being in a lambda context so that it's printed properly- oldMG@(MG _ (L _ [L _ oldmatch]) _) ->+ oldMG@(MG _ (L _ [L _ oldmatch]) _)+ | all (\(L _ (GRHS _ stmts _)) -> null stmts) (grhssGRHSs (m_grhss oldmatch)) -> [suggestN "Use lambda" o $ noLoc $ HsLam noExtField oldMG { mg_alts = noLoc [noLoc oldmatch@@ -260,7 +262,7 @@ ] -- otherwise we should use @LambdaCase@- MG _ (L _ xs) _ ->+ MG _ (L _ _) _ -> [(suggestN "Use lambda-case" o $ noLoc $ HsLamCase noExtField matchGroup) {ideaNote=[RequiresExtension "LambdaCase"]}] _ -> []@@ -269,7 +271,7 @@ -- | Filter out tuple arguments, converting the @x@ (matched in the lambda) variable argument -- to a missing argument, so that we get the proper section. removeX :: LHsTupArg GhcPs -> LHsTupArg GhcPs- removeX arg@(L _ (Present _ (view -> Var_ x')))+ removeX (L _ (Present _ (view -> Var_ x'))) | x == x' = noLoc $ Missing noExtField removeX y = y -- | Extract the name of an argument of a tuple if it's present and a variable.
src/Hint/NewType.hs view
@@ -21,6 +21,7 @@ data X = Y {-# UNPACK #-} !Int -- newtype X = Y Int data A = A {b :: !C} -- newtype A = A {b :: C} data A = A Int#+data A = A (MutableByteArray# s) {-# LANGUAGE UnboxedTuples #-}; data WithAnn x = WithAnn (# Ann, x #) {-# LANGUAGE UnboxedTuples #-}; data WithAnn x = WithAnn {getWithAnn :: (# Ann, x #)} data A = A () -- newtype A = A ()@@ -45,8 +46,9 @@ import Data.List (isSuffixOf) import GHC.Hs.Decls import GHC.Hs-import Outputable import SrcLoc+import Data.Generics.Uniplate.Data+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable newtypeHint :: DeclHint newtypeHint _ _ x = newtypeHintDecl x ++ newTypeDerivingStrategiesHintDecl x@@ -104,7 +106,7 @@ }} , insideType = inType }-singleSimpleField (L loc (InstD ext inst@(DataFamInstD instExt (DataFamInstDecl (HsIB hsibExt famEqn@(FamEqn _ _ _ _ _ dataDef))))))+singleSimpleField (L loc (InstD ext (DataFamInstD instExt (DataFamInstDecl (HsIB hsibExt famEqn@(FamEqn _ _ _ _ _ dataDef)))))) | Just inType <- simpleHsDataDefn dataDef = Just WarnNewtype { newDecl = L loc $ InstD ext $ DataFamInstD instExt $ DataFamInstDecl $ HsIB hsibExt famEqn {feqn_rhs = dataDef@@ -121,7 +123,7 @@ -- | Checks whether its argument is a \"simple\" data definition (see 'singleSimpleField') -- returning the type inside its constructor if it is. simpleHsDataDefn :: HsDataDefn GhcPs -> Maybe (HsType GhcPs)-simpleHsDataDefn dataDef@(HsDataDefn _ DataType _ _ _ [L _ constructor] _) = simpleCons constructor+simpleHsDataDefn (HsDataDefn _ DataType _ _ _ [L _ constructor] _) = simpleCons constructor simpleHsDataDefn _ = Nothing -- | Checks whether its argument is a \"simple\" constructor (see criteria in 'singleSimpleField')@@ -140,8 +142,7 @@ simpleCons _ = Nothing isHashy :: HsType GhcPs -> Bool-isHashy (HsTyVar _ _ identifier) = "#" `isSuffixOf` showSDocUnsafe (ppr identifier)-isHashy _ = False+isHashy x = or ["#" `isSuffixOf` unsafePrettyPrint v | v@HsTyVar{} <- universe x] warnBang :: HsType GhcPs -> Bool warnBang (HsBangTy _ (HsSrcBang _ _ SrcStrict) _) = False
src/HsColour.hs view
@@ -1,11 +1,14 @@ {-# LANGUAGE CPP #-}-module HsColour(hsColourHTML, hsColourConsole) where+module HsColour(hsColourHTML, hsColourConsole, hsColourConsolePure) where #ifdef GPL_SCARES_ME hsColourConsole :: IO (String -> String) hsColourConsole = pure id +hsColourConsolePure :: String -> String+hsColourConsolePure = id+ hsColourHTML :: String -> String hsColourHTML = id @@ -21,6 +24,9 @@ hsColourConsole :: IO (String -> String) hsColourConsole = TTY.hscolour <$> readColourPrefs++hsColourConsolePure :: String -> String+hsColourConsolePure = TTY.hscolour defaultColourPrefs hsColourHTML :: String -> String hsColourHTML = CSS.hscolour False 1
src/Idea.hs view
@@ -4,7 +4,7 @@ Idea(..), rawIdea, idea, suggest, suggestRemove, ideaRemove, warn, ignore, rawIdeaN, suggestN, ignoreNoSuggestion,- showIdeasJson, showANSI,+ showIdeasJson, showANSI, showIdeaANSI, Note(..), showNotes, Severity(..), ) where@@ -60,6 +60,7 @@ dict xs = "{" ++ intercalate "," [show k ++ ":" ++ v | (k,v) <- xs] ++ "}" list xs = "[" ++ intercalate "," xs ++ "]" +-- | Show a list of 'Idea' values as a JSON string. showIdeasJson :: [Idea] -> String showIdeasJson ideas = "[" ++ intercalate "\n," (map showIdeaJson ideas) ++ "]" @@ -67,8 +68,13 @@ show = showEx id +-- | Show an 'Idea' with ANSI color, using the hscolour preferences file. showANSI :: IO (Idea -> String) showANSI = showEx <$> hsColourConsole++-- | Show an 'Idea' with ANSI color codes to give syntax coloring to the Haskell code.+showIdeaANSI :: Idea -> String+showIdeaANSI = showEx hsColourConsolePure showEx :: (String -> String) -> Idea -> String showEx tt Idea{..} = unlines $
src/Language/Haskell/HLint.hs view
@@ -15,7 +15,7 @@ -- * Generate hints hlint, applyHints, -- * Idea data type- Idea(..), Severity(..), Note(..), unpackSrcSpan,+ Idea(..), Severity(..), Note(..), unpackSrcSpan, showIdeaANSI, -- * Settings Classify(..), getHLintDataDir, autoSettings, argsSettings,