Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
32 changes: 32 additions & 0 deletions hints.md
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,38 @@ Does not support refactoring.
</tr>
</table>

## Builtin NoCapitalisms

<table>
<tr>
<th>Hint Name</th>
<th>Hint</th>
<th>Severity</th>
</tr>
<tr>
<td>Avoid capitalisms</td>
<td>
Example:
<code>
getFOO = _
</code>
<br>
Found:
<code>
getFOO = ...
</code>
<br>
Suggestion:
<code>

</code>
<br>
Does not support refactoring.
</td>
<td>Ignore</td>
</tr>
</table>

## Builtin NumLiteral

<table>
Expand Down
1 change: 1 addition & 0 deletions hlint.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ library
Hint.Type
Hint.Unsafe
Hint.NumLiteral
Hint.NoCapitalisms
Test.All
Test.Annotations
Test.InputOutput
Expand Down
44 changes: 23 additions & 21 deletions src/Hint/All.hs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ import Hint.Unsafe
import Hint.NewType
import Hint.Smell
import Hint.NumLiteral
import Hint.NoCapitalisms

-- | A list of the builtin hints wired into HLint.
-- This list is likely to grow over time.
data HintBuiltin =
HintList | HintListRec | HintMonad | HintLambda | HintFixities | HintNegation |
HintBracket | HintNaming | HintPattern | HintImport | HintExport |
HintPragma | HintExtensions | HintUnsafe | HintDuplicate | HintRestrict |
HintComment | HintNewType | HintSmell | HintNumLiteral
HintComment | HintNewType | HintSmell | HintNumLiteral | HintNoCapitalisms
deriving (Show,Eq,Ord,Bounded,Enum)

-- See https://github.com/ndmitchell/hlint/issues/1150 - Duplicate is too slow
Expand All @@ -50,26 +51,27 @@ issue1150 = True

builtin :: HintBuiltin -> Hint
builtin x = case x of
HintLambda -> decl lambdaHint
HintImport -> modu importHint
HintExport -> modu exportHint
HintComment -> modu commentHint
HintPragma -> modu pragmaHint
HintDuplicate -> if issue1150 then mempty else mods duplicateHint
HintRestrict -> mempty{hintModule=restrictHint}
HintList -> decl listHint
HintNewType -> decl newtypeHint
HintUnsafe -> decl unsafeHint
HintListRec -> decl listRecHint
HintNaming -> decl namingHint
HintBracket -> decl bracketHint
HintFixities -> mempty{hintDecl=fixitiesHint}
HintNegation -> decl negationParensHint
HintSmell -> mempty{hintDecl=smellHint,hintModule=smellModuleHint}
HintPattern -> decl patternHint
HintMonad -> decl monadHint
HintExtensions -> modu extensionsHint
HintNumLiteral -> decl numLiteralHint
HintLambda -> decl lambdaHint
HintImport -> modu importHint
HintExport -> modu exportHint
HintComment -> modu commentHint
HintPragma -> modu pragmaHint
HintDuplicate -> if issue1150 then mempty else mods duplicateHint
HintRestrict -> mempty{hintModule=restrictHint}
HintList -> decl listHint
HintNewType -> decl newtypeHint
HintUnsafe -> decl unsafeHint
HintListRec -> decl listRecHint
HintNaming -> decl namingHint
HintBracket -> decl bracketHint
HintFixities -> mempty{hintDecl=fixitiesHint}
HintNegation -> decl negationParensHint
HintSmell -> mempty{hintDecl=smellHint,hintModule=smellModuleHint}
HintPattern -> decl patternHint
HintMonad -> decl monadHint
HintExtensions -> modu extensionsHint
HintNumLiteral -> decl numLiteralHint
HintNoCapitalisms -> decl noCapitalismsHint
where
wrap = timed "Hint" (drop 4 $ show x) . forceList
decl f = mempty{hintDecl=const $ \a b c -> wrap $ f a b c}
Expand Down
119 changes: 119 additions & 0 deletions src/Hint/NoCapitalisms.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-
Detect uses of capitalisms

Only allow up to two consecutive capital letters in top level
identifiers.

Identifiers containing underscores are exempted from thus rule.
Identifiers of FFI bindings are exempted from thus rule.

Locally bound identifiers and module names are not checked.

<TEST>
data LHsDecl
class FOO a where -- @Ignore
class Foo a where getFOO :: Bool
data Foo = Bar | BAAZ -- @Ignore
data Foo = B_ar | BAAZ -- @Ignore
data Foo = Bar | B_AAZ
data OTPToken = OTPToken -- @Ignore
data OTP_Token = Foo
sendSMS = _ -- @Ignore
runTLS = _ -- @Ignore
runTLSSocket = _ -- @Ignore
runTLS_Socket
newtype TLSSettings = TLSSettings -- @Ignore
tlsSettings
data CertSettings = CertSettings
tlsServerHooks
tlsServerDHEParams = _ -- @Ignore
type WarpTLSException = () -- @Ignore
get_SMS
runCI
foreign import ccall _FIREMISSLES :: IO ()
getSMS :: IO () -- @Ignore
gFOO = _ -- @Ignore
geFOO = _ -- @Ignore
getFOO = _ -- @Ignore
</TEST>
-}

module Hint.NoCapitalisms(noCapitalismsHint) where
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"capitalism"? How about "NoCAPs"?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capitalism, as in, an expression written in all caps.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not see such a meaning for capitalism in the dictionary.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, "NoCAPs" seems better since it doesn't have political connotations and shouldn't provoke unnecessary discussions, unlike the current name.


import Hint.Type
import Data.List.Extra as E
import Data.List.NonEmpty as NE
import Data.Char
import Data.Maybe

import GHC.Types.Basic
import GHC.Types.SourceText
import GHC.Data.FastString
import GHC.Hs.Decls
import GHC.Hs.Extension
import GHC.Hs
import GHC.Types.SrcLoc

import Language.Haskell.GhclibParserEx.GHC.Hs.Decls
import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
import GHC.Util

noCapitalismsHint :: DeclHint
noCapitalismsHint _ _ decl = [ remark Ignore "Avoid capitalisms" (reLoc (shorten decl))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a Note to the hint, explaining what it does?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the description at beginning of the file sufficient? I followed the style of Hint.Naming.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's just a code comment. A Note is attached to a hint and is displayed with the hint. Alternatively, you can make the hint name more descriptive: "Avoid three consecutive capital letters" instead of "Avoid capitalisms".

| not $ isForD decl
, name <- nubOrd $ getNames decl
, not $ hasUnderscore name
, hasCapitalism name
]

hasUnderscore :: String -> Bool
hasUnderscore = elem '_'

hasCapitalism :: String -> Bool
hasCapitalism s = any isAllUpper (trigrams s)
where
isAllUpper = all isUpper

trigrams :: String -> [String]
trigrams = \case
a:b:c:as -> [a,b,c] : trigrams (b:c:as)
_otherwise -> []

--- these are copied from Hint.Naming ---
Copy link
Copy Markdown
Author

@bgohla bgohla Jul 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this PR goes ahead, I would propose factoring out the code below.


shorten :: LHsDecl GhcPs -> LHsDecl GhcPs
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add some comments explaining what this does.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

shorten (L locDecl (ValD ttg0 bind@(FunBind _ _ matchGroup@(MG FromSource (L locMatches matches))))) =
L locDecl (ValD ttg0 bind {fun_matches = matchGroup {mg_alts = L locMatches $ E.map shortenMatch matches}})
shorten (L locDecl (ValD ttg0 bind@(PatBind _ _ _ grhss@(GRHSs _ rhss _)))) =
L locDecl (ValD ttg0 bind {pat_rhs = grhss {grhssGRHSs = E.map shortenLGRHS rhss}})
shorten x = x

shortenMatch :: LMatch GhcPs (LHsExpr GhcPs) -> LMatch GhcPs (LHsExpr GhcPs)
shortenMatch (L locMatch match@(Match _ _ _ grhss@(GRHSs _ rhss _))) =
L locMatch match {m_grhss = grhss {grhssGRHSs = E.map shortenLGRHS rhss}}

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
dots :: HsExpr GhcPs
dots = HsLit noExtField (HsString (SourceText (fsLit "...")) (fsLit "..."))

getNames :: LHsDecl GhcPs -> [String]
getNames decl = maybeToList (declName decl) ++ getConstructorNames (unLoc decl)

getConstructorNames :: HsDecl GhcPs -> [String]
getConstructorNames tycld = case tycld of
(TyClD _ (DataDecl _ _ _ _ (HsDataDefn _ _ _ _ (NewTypeCon con) _))) -> conNames [con]
(TyClD _ (DataDecl _ _ _ _ (HsDataDefn _ _ _ _ (DataTypeCons _ cons) _))) -> conNames cons
_ -> []
where
conNames :: [LConDecl GhcPs] -> [String]
conNames = concatMap (E.map unsafePrettyPrint . conNamesInDecl . unLoc)

conNamesInDecl :: ConDecl GhcPs -> [LIdP GhcPs]
conNamesInDecl ConDeclH98 {con_name = name} = [name]
conNamesInDecl ConDeclGADT {con_names = names} = NE.toList names

6 changes: 5 additions & 1 deletion src/Idea.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

module Idea(
Idea(..),
rawIdea, idea, suggest, suggestRemove, ideaRemove, warn, ignore,
rawIdea, idea, suggest, suggestRemove, ideaRemove, warn, ignore, remark,
rawIdeaN, suggestN, ignoreNoSuggestion,
showIdeasJson, showIdeaANSI,
ideaFile,
Expand Down Expand Up @@ -107,6 +107,10 @@ idea severity hint from to =
ideaRemove :: Severity -> String -> SrcSpan -> String -> [Refactoring R.SrcSpan] -> Idea
ideaRemove severity hint span from = rawIdea severity hint span from (Just "") []

remark :: GHC.Utils.Outputable.Outputable a
=> Severity -> String -> Located a -> Idea
remark severity hint from = rawIdeaN severity hint (getLoc from) (unsafePrettyPrint from) Nothing []

suggest :: (GHC.Utils.Outputable.Outputable a, GHC.Utils.Outputable.Outputable b) =>
String -> Located a -> Located b -> [Refactoring R.SrcSpan] -> Idea
suggest = idea Suggestion
Expand Down
Loading