Skip to content

Commit 7013099

Browse files
committed
Add a hint to avoid capitalisms in identifiers, with some exceptions.
1 parent 6b406c8 commit 7013099

File tree

5 files changed

+174
-22
lines changed

5 files changed

+174
-22
lines changed

hints.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,38 @@ Does not support refactoring.
11761176
</tr>
11771177
</table>
11781178

1179+
## Builtin NoCapitalisms
1180+
1181+
<table>
1182+
<tr>
1183+
<th>Hint Name</th>
1184+
<th>Hint</th>
1185+
<th>Severity</th>
1186+
</tr>
1187+
<tr>
1188+
<td>Avoid capitalisms</td>
1189+
<td>
1190+
Example:
1191+
<code>
1192+
type WarpTLSException = ()
1193+
</code>
1194+
<br>
1195+
Found:
1196+
<code>
1197+
type WarpTLSException = ()
1198+
</code>
1199+
<br>
1200+
Suggestion:
1201+
<code>
1202+
1203+
</code>
1204+
<br>
1205+
Does not support refactoring.
1206+
</td>
1207+
<td>Suggestion</td>
1208+
</tr>
1209+
</table>
1210+
11791211
## Builtin NumLiteral
11801212

11811213
<table>

hlint.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ library
168168
Hint.Type
169169
Hint.Unsafe
170170
Hint.NumLiteral
171+
Hint.NoCapitalisms
171172
Test.All
172173
Test.Annotations
173174
Test.InputOutput

src/Hint/All.hs

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@ import Hint.Unsafe
3434
import Hint.NewType
3535
import Hint.Smell
3636
import Hint.NumLiteral
37+
import Hint.NoCapitalisms
3738

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

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

5152
builtin :: HintBuiltin -> Hint
5253
builtin x = case x of
53-
HintLambda -> decl lambdaHint
54-
HintImport -> modu importHint
55-
HintExport -> modu exportHint
56-
HintComment -> modu commentHint
57-
HintPragma -> modu pragmaHint
58-
HintDuplicate -> if issue1150 then mempty else mods duplicateHint
59-
HintRestrict -> mempty{hintModule=restrictHint}
60-
HintList -> decl listHint
61-
HintNewType -> decl newtypeHint
62-
HintUnsafe -> decl unsafeHint
63-
HintListRec -> decl listRecHint
64-
HintNaming -> decl namingHint
65-
HintBracket -> decl bracketHint
66-
HintFixities -> mempty{hintDecl=fixitiesHint}
67-
HintNegation -> decl negationParensHint
68-
HintSmell -> mempty{hintDecl=smellHint,hintModule=smellModuleHint}
69-
HintPattern -> decl patternHint
70-
HintMonad -> decl monadHint
71-
HintExtensions -> modu extensionsHint
72-
HintNumLiteral -> decl numLiteralHint
54+
HintLambda -> decl lambdaHint
55+
HintImport -> modu importHint
56+
HintExport -> modu exportHint
57+
HintComment -> modu commentHint
58+
HintPragma -> modu pragmaHint
59+
HintDuplicate -> if issue1150 then mempty else mods duplicateHint
60+
HintRestrict -> mempty{hintModule=restrictHint}
61+
HintList -> decl listHint
62+
HintNewType -> decl newtypeHint
63+
HintUnsafe -> decl unsafeHint
64+
HintListRec -> decl listRecHint
65+
HintNaming -> decl namingHint
66+
HintBracket -> decl bracketHint
67+
HintFixities -> mempty{hintDecl=fixitiesHint}
68+
HintNegation -> decl negationParensHint
69+
HintSmell -> mempty{hintDecl=smellHint,hintModule=smellModuleHint}
70+
HintPattern -> decl patternHint
71+
HintMonad -> decl monadHint
72+
HintExtensions -> modu extensionsHint
73+
HintNumLiteral -> decl numLiteralHint
74+
HintNoCapitalisms -> decl noCapitalismsHint
7375
where
7476
wrap = timed "Hint" (drop 4 $ show x) . forceList
7577
decl f = mempty{hintDecl=const $ \a b c -> wrap $ f a b c}

src/Hint/NoCapitalisms.hs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
{-# LANGUAGE FlexibleContexts #-}
2+
{-# LANGUAGE LambdaCase #-}
3+
{-
4+
Detect uses of capitalisms
5+
6+
Only allow up to two consecutive capital letters in identifiers.
7+
8+
Identifiers containing underscores are exempted from thus rule.
9+
Identifiers of FFI bindings are exempted from thus rule.
10+
11+
<TEST>
12+
module SSL.Foo -- ???
13+
data LHsDecl
14+
class FOO a where -- ???
15+
class Foo a where getFOO -- ???
16+
data Foo = Bar | BAAZ -- ???
17+
data Foo = B_ar | BAAZ -- ???
18+
data Foo = Bar | B_AAZ
19+
data OTPToken = OTPToken -- ???
20+
data OTP_Token = Foo
21+
sendSMS = ... -- ???
22+
runTLS = ... -- ???
23+
runTLSSocket = ... -- ???
24+
runTLS_Socket
25+
newtype TLSSettings = ... -- ???
26+
tlsSettings
27+
data CertSettings = CertSettings
28+
tlsServerHooks
29+
tlsServerDHEParams = ... -- ???
30+
type WarpTLSException = () -- ???
31+
get_SMS
32+
runCI
33+
foreign import ccall _FIREMISSLES :: IO ()
34+
let getSMS = x in foo --- ???
35+
</TEST>
36+
-}
37+
38+
39+
module Hint.NoCapitalisms(noCapitalismsHint) where
40+
41+
import Hint.Type (DeclHint,remark, Severity (Ignore))
42+
import Data.List.Extra (nubOrd)
43+
import Data.List.NonEmpty (toList)
44+
import Data.Char
45+
import Data.Maybe
46+
47+
import GHC.Types.Basic
48+
import GHC.Types.SourceText
49+
import GHC.Data.FastString
50+
import GHC.Hs.Decls
51+
import GHC.Hs.Extension
52+
import GHC.Hs
53+
import GHC.Types.SrcLoc
54+
55+
import Language.Haskell.GhclibParserEx.GHC.Hs.Decls
56+
import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
57+
import GHC.Util
58+
59+
noCapitalismsHint :: DeclHint
60+
noCapitalismsHint _ _ decl = [ remark Ignore "Avoid capitalisms" (reLoc (shorten decl))
61+
| not $ isForD decl
62+
, name <- nubOrd $ getNames decl
63+
, not $ hasUnderscore name
64+
, hasCapitalism name
65+
]
66+
67+
hasUnderscore :: String -> Bool
68+
hasUnderscore = elem '_'
69+
70+
hasCapitalism :: String -> Bool
71+
hasCapitalism s = any isAllUpper (trigrams s)
72+
where
73+
isAllUpper = all isUpper
74+
trigrams = \case
75+
a:b:c:as -> [a,b,c] : trigrams (c:as)
76+
_otherwise -> []
77+
78+
--- these are copied from Hint.Naming ---
79+
80+
shorten :: LHsDecl GhcPs -> LHsDecl GhcPs
81+
shorten (L locDecl (ValD ttg0 bind@(FunBind _ _ matchGroup@(MG FromSource (L locMatches matches))))) =
82+
L locDecl (ValD ttg0 bind {fun_matches = matchGroup {mg_alts = L locMatches $ map shortenMatch matches}})
83+
shorten (L locDecl (ValD ttg0 bind@(PatBind _ _ grhss@(GRHSs _ rhss _)))) =
84+
L locDecl (ValD ttg0 bind {pat_rhs = grhss {grhssGRHSs = map shortenLGRHS rhss}})
85+
shorten x = x
86+
87+
shortenMatch :: LMatch GhcPs (LHsExpr GhcPs) -> LMatch GhcPs (LHsExpr GhcPs)
88+
shortenMatch (L locMatch match@(Match _ _ _ grhss@(GRHSs _ rhss _))) =
89+
L locMatch match {m_grhss = grhss {grhssGRHSs = map shortenLGRHS rhss}}
90+
91+
shortenLGRHS :: LGRHS GhcPs (LHsExpr GhcPs) -> LGRHS GhcPs (LHsExpr GhcPs)
92+
shortenLGRHS (L locGRHS (GRHS ttg0 guards (L locExpr _))) =
93+
L locGRHS (GRHS ttg0 guards (L locExpr dots))
94+
where
95+
dots :: HsExpr GhcPs
96+
dots = HsLit EpAnnNotUsed (HsString (SourceText (fsLit "...")) (fsLit "..."))
97+
98+
getNames :: LHsDecl GhcPs -> [String]
99+
getNames decl = maybeToList (declName decl) ++ getConstructorNames (unLoc decl)
100+
101+
getConstructorNames :: HsDecl GhcPs -> [String]
102+
getConstructorNames tycld = case tycld of
103+
(TyClD _ (DataDecl _ _ _ _ (HsDataDefn _ _ _ _ (NewTypeCon con) _))) -> conNames [con]
104+
(TyClD _ (DataDecl _ _ _ _ (HsDataDefn _ _ _ _ (DataTypeCons _ cons) _))) -> conNames cons
105+
_ -> []
106+
where
107+
conNames :: [LConDecl GhcPs] -> [String]
108+
conNames = concatMap (map unsafePrettyPrint . conNamesInDecl . unLoc)
109+
110+
conNamesInDecl :: ConDecl GhcPs -> [LIdP GhcPs]
111+
conNamesInDecl ConDeclH98 {con_name = name} = [name]
112+
conNamesInDecl ConDeclGADT {con_names = names} = Data.List.NonEmpty.toList names
113+

src/Idea.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
module Idea(
55
Idea(..),
6-
rawIdea, idea, suggest, suggestRemove, ideaRemove, warn, ignore,
6+
rawIdea, idea, suggest, suggestRemove, ideaRemove, warn, ignore, remark,
77
rawIdeaN, suggestN, ignoreNoSuggestion,
88
showIdeasJson, showIdeaANSI,
99
Note(..), showNotes,
@@ -99,6 +99,10 @@ idea severity hint from to =
9999
ideaRemove :: Severity -> String -> SrcSpan -> String -> [Refactoring R.SrcSpan] -> Idea
100100
ideaRemove severity hint span from = rawIdea severity hint span from (Just "") []
101101

102+
remark :: GHC.Utils.Outputable.Outputable a
103+
=> Severity -> String -> Located a -> Idea
104+
remark severity hint from = rawIdeaN severity hint (getLoc from) (unsafePrettyPrint from) Nothing []
105+
102106
suggest :: (GHC.Utils.Outputable.Outputable a, GHC.Utils.Outputable.Outputable b) =>
103107
String -> Located a -> Located b -> [Refactoring R.SrcSpan] -> Idea
104108
suggest = idea Suggestion

0 commit comments

Comments
 (0)