Skip to content
Merged

Fix CI #1706

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright Neil Mitchell 2006-2025.
Copyright Neil Mitchell 2006-2026.
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Perhaps:

Each hint says which file/line the hint relates to, how serious an issue it is, a description of the hint, what it found, and what you might want to replace it with. In the case of the first hint, it has suggested that instead of applying `concat` and `map` separately, it would be better to use the combination function `concatMap`.

The first hint is marked as an warning, because using `concatMap` in preference to the two separate functions is always desirable. In contrast, the removal of brackets is probably a good idea, but not always. Reasons that a hint might be a suggestion include requiring an additional import, something not everyone agrees on, and functions only available in more recent versions of the base library.
The first hint is marked as a warning, because using `concatMap` in preference to the two separate functions is always desirable. In contrast, the removal of brackets is probably a good idea, but not always. Reasons that a hint might be a suggestion include requiring an additional import, something not everyone agrees on, and functions only available in more recent versions of the base library.

Any configuration can be done via [.hlint.yaml](./README.md#customizing-the-hints) file. Any other file name, such as `.hlint.yml`, can only be used explicitly with the `--hint=FILE` option.

Expand Down
2 changes: 1 addition & 1 deletion hlint.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license-file: LICENSE
category: Development
author: Neil Mitchell <ndmitchell@gmail.com>
maintainer: Neil Mitchell <ndmitchell@gmail.com>
copyright: Neil Mitchell 2006-2025
copyright: Neil Mitchell 2006-2026
synopsis: Source code suggestions
description:
HLint gives suggestions on how to improve your source code.
Expand Down
2 changes: 1 addition & 1 deletion src/CmdLine.hs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ mode = cmdArgsMode $ modes
,"To check all Haskell files in 'src' and generate a report type:"
," hlint src --report"]
] &= program "hlint" &= verbosity
&= summary ("HLint v" ++ showVersion version ++ ", (C) Neil Mitchell 2006-2025")
&= summary ("HLint v" ++ showVersion version ++ ", (C) Neil Mitchell 2006-2026")
where
nam xs = nam_ xs &= name [NE.head $ NE.fromList xs]
nam_ xs = def &= explicit &= name xs
Expand Down
1 change: 0 additions & 1 deletion src/Config/Yaml.hs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import GHC.All
import Fixity
import Extension
import GHC.Unit.Module
import Data.Functor
import Data.Monoid
import Data.Semigroup
import Timing
Expand Down
3 changes: 2 additions & 1 deletion src/GHC/Util/ApiAnnotation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Control.Applicative
import Data.List.Extra
import Data.Maybe
import Data.Set qualified as Set
import Data.Set (Set)

trimCommentStart :: String -> String
trimCommentStart s
Expand Down Expand Up @@ -74,7 +75,7 @@ pragmas x =
]

-- All the extensions defined to be used.
extensions :: EpAnnComments -> Set.Set Extension
extensions :: EpAnnComments -> Set Extension
extensions = Set.fromList . concatMap (mapMaybe readExtension . snd) . languagePragmas . pragmas

-- Utility for a case insensitive prefix strip.
Expand Down
5 changes: 3 additions & 2 deletions src/Hint/Duplicate.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import Data.Tuple.Extra
import Data.List hiding (find)
import Data.List.NonEmpty qualified as NE
import Data.Map qualified as Map
import Data.Map (Map)

import GHC.Types.SrcLoc
import GHC.Hs
Expand Down Expand Up @@ -79,7 +80,7 @@ dupes ys =

-- | The position to return if we match at this point, and the map of where to go next
-- If two runs have the same vals, always use the first pos you find
data Dupe pos val = Dupe pos (Map.Map val (Dupe pos val))
data Dupe pos val = Dupe pos (Map val (Dupe pos val))


find :: Ord val => [val] -> Dupe pos val -> (pos, Int)
Expand All @@ -100,7 +101,7 @@ duplicateOrdered threshold xs = concat $ concat $ snd $ mapAccumL f (Dupe def Ma
f d xs = second overlaps $ mapAccumL (g pos) d $ onlyAtLeast threshold $ tails xs
where pos = Map.fromList $ zip (map fst xs) [0..]

g :: Map.Map pos Int -> Dupe pos val -> NE.NonEmpty (pos, val) -> (Dupe pos val, [(pos, pos, [val])])
g :: Map pos Int -> Dupe pos val -> NE.NonEmpty (pos, val) -> (Dupe pos val, [(pos, pos, [val])])
g pos d xs = (d2, res)
where
res = [(p,pme,take mx vs) | i >= threshold
Expand Down
12 changes: 7 additions & 5 deletions src/Hint/Extensions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,9 @@ import Data.List.Extra
import Data.Data
import Refact.Types
import Data.Set qualified as Set
import Data.Set (Set)
import Data.Map qualified as Map
import Data.Map (Map)

import GHC.Data.FastString
import GHC.Types.SrcLoc
Expand Down Expand Up @@ -333,7 +335,7 @@ extensionsHint _ x =
-- programmatically.

-- All the extensions defined to be used.
extensions :: Set.Set Extension
extensions :: Set Extension
extensions = Set.fromList $
concatMap
(mapMaybe readExtension . snd)
Expand All @@ -346,25 +348,25 @@ extensionsHint _ x =
-- (https://gitlab.haskell.org/ghc/ghc/-/merge_requests/9517).

-- Those extensions we detect to be useful.
useful :: Set.Set Extension
useful :: Set Extension
useful =
if usedTH
then Set.filter (\case TemplateHaskell -> usedExt TemplateHaskell (ghcModule x); _ -> True) extensions
else Set.filter (`usedExt` ghcModule x) extensions
-- Those extensions which are useful, but implied by other useful
-- extensions.
implied :: Map.Map Extension Extension
implied :: Map Extension Extension
implied = Map.fromList
[ (e, a)
| e <- Set.toList useful
, a:_ <- [filter (`Set.member` useful) $ extensionImpliedEnabledBy e]
]
-- Those we should keep.
keep :: Set.Set Extension
keep :: Set Extension
keep = useful `Set.difference` Map.keysSet implied
-- The meaning of (a,b) is a used to imply b, but has gone, so
-- suggest enabling b.
disappear :: Map.Map Extension [Extension]
disappear :: Map Extension [Extension]
disappear =
Map.fromListWith (++) $
nubOrdOn snd -- Only keep one instance for each of a.
Expand Down
3 changes: 2 additions & 1 deletion src/Hint/Naming.hs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import Data.Data
import Data.Char
import Data.Maybe
import Data.Set qualified as Set
import Data.Set (Set)

import GHC.Types.Basic
import GHC.Types.SourceText
Expand All @@ -67,7 +68,7 @@ import GHC.Util
namingHint :: DeclHint
namingHint _ modu = naming $ Set.fromList $ concatMap getNames $ hsmodDecls $ unLoc (ghcModule modu)

naming :: Set.Set String -> LHsDecl GhcPs -> [Idea]
naming :: Set String -> LHsDecl GhcPs -> [Idea]
naming seen originalDecl =
[ suggest "Use camelCase"
(reLoc (shorten originalDecl))
Expand Down
28 changes: 15 additions & 13 deletions src/Hint/Restrict.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ import Util
import Data.Generics.Uniplate.DataOnly
import Data.List.NonEmpty qualified as NonEmpty
import Data.Set qualified as Set
import Data.Set (Set)
import Data.Map qualified as Map
import Data.Map (Map)
import Data.List.Extra
import Data.List.NonEmpty (nonEmpty)
import Data.Either
Expand Down Expand Up @@ -96,13 +98,13 @@ instance Semigroup RestrictItem where
-- rather than some arbitrary function of the two. Withins and messages still
-- merge, because a name can sensibly be allowed in the union of two places but
-- cannot sensibly both require and forbid a type application.
newtype RestrictFunction = RestrictFun (Map.Map (Maybe String) ([(String, String)], Maybe String, Maybe (Max (Arg Int RestrictTypeApp))))
newtype RestrictFunction = RestrictFun (Map (Maybe String) ([(String, String)], Maybe String, Maybe (Max (Arg Int RestrictTypeApp))))

instance Semigroup RestrictFunction where
RestrictFun m1 <> RestrictFun m2 = RestrictFun (Map.unionWith (<>) m1 m2)

type RestrictFunctions = (Bool, Map.Map String RestrictFunction)
type OtherRestrictItems = Map.Map RestrictType (Bool, Map.Map String RestrictItem)
type RestrictFunctions = (Bool, Map String RestrictFunction)
type OtherRestrictItems = Map RestrictType (Bool, Map String RestrictItem)

restrictions :: [Setting] -> (RestrictFunctions, OtherRestrictItems)
restrictions settings = (rFunction, rOthers)
Expand Down Expand Up @@ -148,7 +150,7 @@ within modu func = any (\(a,b) -> (a ~= modu || a == "") && (b ~= func || b == "
checkPragmas :: String
-> [(LEpaComment, [String])]
-> [(LEpaComment, [String])]
-> Map.Map RestrictType (Bool, Map.Map String RestrictItem)
-> Map RestrictType (Bool, Map String RestrictItem)
-> [Idea]
checkPragmas modu flags exts mps =
f RestrictFlag "flags" flags ++ f RestrictExtension "extensions" exts
Expand All @@ -168,7 +170,7 @@ checkPragmas modu flags exts mps =
-- i.e. the preference of "either pre- or post-, but qualified" in a rule.
data QualifiedPostOrPre = QualifiedPostOrPre deriving Eq

checkImports :: String -> [LImportDecl GhcPs] -> (Bool, Map.Map String RestrictItem) -> [Idea]
checkImports :: String -> [LImportDecl GhcPs] -> (Bool, Map String RestrictItem) -> [Idea]
checkImports modu lImportDecls (def, mp) = mapMaybe getImportHint lImportDecls
where
getImportHint :: LImportDecl GhcPs -> Maybe Idea
Expand Down Expand Up @@ -236,12 +238,12 @@ checkImports modu lImportDecls (def, mp) = mapMaybe getImportHint lImportDecls
msg = moduleNameString (unLoc ideclName) <> " should be imported " <> hint
Left $ warn msg (reLoc i) i' []

getRestrictItem :: Bool -> LocatedA ModuleName -> Map.Map String RestrictItem -> RestrictItem
getRestrictItem :: Bool -> LocatedA ModuleName -> Map String RestrictItem -> RestrictItem
getRestrictItem def ideclName =
fromMaybe (RestrictItem mempty mempty mempty mempty [("","") | def] NoRestrictIdents Nothing)
. lookupRestrictItem ideclName

lookupRestrictItem :: LocatedA ModuleName -> Map.Map String RestrictItem -> Maybe RestrictItem
lookupRestrictItem :: LocatedA ModuleName -> Map String RestrictItem -> Maybe RestrictItem
lookupRestrictItem ideclName mp =
let moduleName = moduleNameString $ unLoc ideclName
exact = Map.lookup moduleName mp
Expand Down Expand Up @@ -311,7 +313,7 @@ addTypeAppCounts c1 c2 = TypeAppCount
-- visible type application violation.
restrictFunctionHint
:: String -> String -> [(String, String)] -> Maybe RestrictTypeApp
-> Map.Map SrcSpanD TypeAppCount -> Set.Set SrcSpanD -> LocatedN RdrName -> Maybe String
-> Map SrcSpanD TypeAppCount -> Set SrcSpanD -> LocatedN RdrName -> Maybe String
restrictFunctionHint modu dname withins typeApp typeAppCounts typeAppSites x
| not $ within modu dname withins = Just "Avoid restricted function"
| not $ sp `Set.member` typeAppSites = Nothing
Expand All @@ -337,10 +339,10 @@ restrictFunctionHint modu dname withins typeApp typeAppCounts typeAppSites x
--
-- Note that this does not resolve local binders, so a locally bound name that
-- shadows a restricted one is still treated as a use of it.
typeApplicationSites :: [LHsDecl GhcPs] -> Set.Set SrcSpanD
typeApplicationSites :: [LHsDecl GhcPs] -> Set SrcSpanD
typeApplicationSites decls = Set.difference sites infixOperators
where
sites :: Set.Set SrcSpanD
sites :: Set SrcSpanD
sites = Set.fromList $
[ SrcSpanD (locA (getLoc name))
| L _ (HsVar _ name) <- universeBi decls :: [LHsExpr GhcPs]
Expand All @@ -349,7 +351,7 @@ typeApplicationSites decls = Set.difference sites infixOperators
| L _ (ConPat _ name PrefixCon{}) <- universeBi decls :: [LPat GhcPs]
]

infixOperators :: Set.Set SrcSpanD
infixOperators :: Set SrcSpanD
infixOperators = Set.fromList
[ SrcSpanD (locA (getLoc name))
| L _ (HsVar _ name) <- concatMap operator (universeBi decls :: [LHsExpr GhcPs])
Expand All @@ -366,7 +368,7 @@ typeApplicationSites decls = Set.difference sites infixOperators
-- attached to it. Each @\@T@ is a separate 'HsAppType' node (or an element of a
-- constructor pattern's type-argument list), and every node in an application
-- chain shares the head name's source span, so summing gives the count.
typeApplicationCounts :: [LHsDecl GhcPs] -> Map.Map SrcSpanD TypeAppCount
typeApplicationCounts :: [LHsDecl GhcPs] -> Map SrcSpanD TypeAppCount
typeApplicationCounts decls = Map.fromListWith addTypeAppCounts $
[ (SrcSpanD (locA (getLoc h)), countTypeApps [ty])
| L _ (HsAppType _ fun (HsWC _ ty)) <- universeBi decls :: [LHsExpr GhcPs]
Expand Down Expand Up @@ -407,7 +409,7 @@ typeAppHead = \case
-- If there are multiple matching rules (e.g., there's both an unqualified version and a qualified version), their
-- withins and messages are concatenated with (<>), and the last-declared type application restriction wins.
findFunction
:: Map.Map String RestrictFunction
:: Map String RestrictFunction
-> LocatedN RdrName
-> [ModuleName]
-> Maybe ([(String, String)], Maybe String, Maybe RestrictTypeApp)
Expand Down
3 changes: 2 additions & 1 deletion src/Hint/Smell.hs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import Config.Type
import Data.Generics.Uniplate.DataOnly
import Data.List.Extra
import Data.Map qualified as Map
import Data.Map (Map)

import GHC.Utils.Outputable
import GHC.Types.Basic
Expand Down Expand Up @@ -177,5 +178,5 @@ countFunctionArgs (HsFunTy _ _ _ t) = 1 + countFunctionArgs (unLoc t)
countFunctionArgs (HsParTy _ t) = countFunctionArgs (unLoc t)
countFunctionArgs _ = 0

smells :: [Setting] -> Map.Map SmellType Int
smells :: [Setting] -> Map SmellType Int
smells settings = Map.fromList [ (smellType, smellLimit) | SettingSmell smellType smellLimit <- settings]
Loading