{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE Strict #-}
module Tokstyle.Linter.Booleans (descr) where
import Control.Monad.State.Strict (State)
import qualified Control.Monad.State.Strict as State
import Data.Fix (Fix (..))
import Data.Text (Text)
import qualified Data.Text as Text
import Language.Cimple (Lexeme, LiteralType (..), Node,
NodeF (..))
import Language.Cimple.Diagnostics (CimplePos, Diagnostic)
import Language.Cimple.TraverseAst (AstActions, astActions, doNode,
traverseAst)
import Tokstyle.Common (warn)
pattern ReturnBool, OnlyReturnBool :: Node a
pattern $mReturnBool :: forall r a. Node a -> (Void# -> r) -> (Void# -> r) -> r
ReturnBool <- Fix (Return (Just (Fix (LiteralExpr Bool _))))
pattern $mOnlyReturnBool :: forall r a. Node a -> (Void# -> r) -> (Void# -> r) -> r
OnlyReturnBool <- Fix (CompoundStmt [ReturnBool])
checkStmts :: FilePath -> [Node (Lexeme Text)] -> State [Diagnostic CimplePos] ()
checkStmts :: FilePath -> [Node (Lexeme Text)] -> State [Diagnostic CimplePos] ()
checkStmts FilePath
_ [] = () -> State [Diagnostic CimplePos] ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkStmts FilePath
file [s :: Node (Lexeme Text)
s@(Fix (IfStmt Node (Lexeme Text)
_ Node (Lexeme Text)
OnlyReturnBool Maybe (Node (Lexeme Text))
Nothing)), Node (Lexeme Text)
ReturnBool] =
FilePath
-> Node (Lexeme Text) -> Text -> State [Diagnostic CimplePos] ()
forall diags at.
(HasDiagnosticsRich diags CimplePos,
HasDiagnosticInfo at CimplePos) =>
FilePath -> at -> Text -> DiagnosticsT diags ()
warn FilePath
file Node (Lexeme Text)
s Text
"if-statement followed by boolean return can be simplified to return"
checkStmts FilePath
file [s :: Node (Lexeme Text)
s@(Fix (IfStmt Node (Lexeme Text)
_ Node (Lexeme Text)
OnlyReturnBool (Just Node (Lexeme Text)
OnlyReturnBool)))] =
FilePath
-> Node (Lexeme Text) -> Text -> State [Diagnostic CimplePos] ()
forall diags at.
(HasDiagnosticsRich diags CimplePos,
HasDiagnosticInfo at CimplePos) =>
FilePath -> at -> Text -> DiagnosticsT diags ()
warn FilePath
file Node (Lexeme Text)
s Text
"if/else with return true/false can be simplified to return"
checkStmts FilePath
file (Node (Lexeme Text)
_:[Node (Lexeme Text)]
ss) = FilePath -> [Node (Lexeme Text)] -> State [Diagnostic CimplePos] ()
checkStmts FilePath
file [Node (Lexeme Text)]
ss
linter :: AstActions (State [Diagnostic CimplePos]) Text
linter :: AstActions (State [Diagnostic CimplePos]) Text
linter = AstActions (State [Diagnostic CimplePos]) Text
forall (f :: * -> *) text. Applicative f => AstActions f text
astActions
{ doNode :: FilePath
-> Node (Lexeme Text)
-> State [Diagnostic CimplePos] ()
-> State [Diagnostic CimplePos] ()
doNode = \FilePath
file Node (Lexeme Text)
node State [Diagnostic CimplePos] ()
act ->
case Node (Lexeme Text) -> NodeF (Lexeme Text) (Node (Lexeme Text))
forall (f :: * -> *). Fix f -> f (Fix f)
unFix Node (Lexeme Text)
node of
BinaryExpr (Fix (LiteralExpr LiteralType
Bool Lexeme Text
_)) BinaryOp
_ Node (Lexeme Text)
_ -> FilePath
-> Node (Lexeme Text) -> Text -> State [Diagnostic CimplePos] ()
forall diags at.
(HasDiagnosticsRich diags CimplePos,
HasDiagnosticInfo at CimplePos) =>
FilePath -> at -> Text -> DiagnosticsT diags ()
warn FilePath
file Node (Lexeme Text)
node Text
message
BinaryExpr Node (Lexeme Text)
_ BinaryOp
_ (Fix (LiteralExpr LiteralType
Bool Lexeme Text
_)) -> FilePath
-> Node (Lexeme Text) -> Text -> State [Diagnostic CimplePos] ()
forall diags at.
(HasDiagnosticsRich diags CimplePos,
HasDiagnosticInfo at CimplePos) =>
FilePath -> at -> Text -> DiagnosticsT diags ()
warn FilePath
file Node (Lexeme Text)
node Text
message
CompoundStmt [Node (Lexeme Text)]
stmts -> do
FilePath -> [Node (Lexeme Text)] -> State [Diagnostic CimplePos] ()
checkStmts FilePath
file [Node (Lexeme Text)]
stmts
State [Diagnostic CimplePos] ()
act
NodeF (Lexeme Text) (Node (Lexeme Text))
_ -> State [Diagnostic CimplePos] ()
act
}
where
message :: Text
message = Text
"boolean constants should not appear in binary expressions (use ! for negation)"
analyse :: (FilePath, [Node (Lexeme Text)]) -> [Diagnostic CimplePos]
analyse :: (FilePath, [Node (Lexeme Text)]) -> [Diagnostic CimplePos]
analyse = [Diagnostic CimplePos] -> [Diagnostic CimplePos]
forall a. [a] -> [a]
reverse ([Diagnostic CimplePos] -> [Diagnostic CimplePos])
-> ((FilePath, [Node (Lexeme Text)]) -> [Diagnostic CimplePos])
-> (FilePath, [Node (Lexeme Text)])
-> [Diagnostic CimplePos]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (State [Diagnostic CimplePos] ()
-> [Diagnostic CimplePos] -> [Diagnostic CimplePos])
-> [Diagnostic CimplePos]
-> State [Diagnostic CimplePos] ()
-> [Diagnostic CimplePos]
forall a b c. (a -> b -> c) -> b -> a -> c
flip State [Diagnostic CimplePos] ()
-> [Diagnostic CimplePos] -> [Diagnostic CimplePos]
forall s a. State s a -> s -> s
State.execState [] (State [Diagnostic CimplePos] () -> [Diagnostic CimplePos])
-> ((FilePath, [Node (Lexeme Text)])
-> State [Diagnostic CimplePos] ())
-> (FilePath, [Node (Lexeme Text)])
-> [Diagnostic CimplePos]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AstActions (State [Diagnostic CimplePos]) Text
-> (FilePath, [Node (Lexeme Text)])
-> State [Diagnostic CimplePos] ()
forall text a (f :: * -> *).
(TraverseAst text a, Applicative f) =>
AstActions f text -> a -> f ()
traverseAst AstActions (State [Diagnostic CimplePos]) Text
linter
descr :: ((FilePath, [Node (Lexeme Text)]) -> [Diagnostic CimplePos], (Text, Text))
descr :: ((FilePath, [Node (Lexeme Text)]) -> [Diagnostic CimplePos],
(Text, Text))
descr = ((FilePath, [Node (Lexeme Text)]) -> [Diagnostic CimplePos]
analyse, (Text
"booleans", [Text] -> Text
Text.unlines
[ Text
"Checks for if/else statements that return true/false and could be simplified to"
, Text
"just return. E.g.:"
, Text
""
, Text
"```cpp"
, Text
"bool foo(void) {"
, Text
" if (check_something()) {"
, Text
" return false;"
, Text
" }"
, Text
" return true;"
, Text
"}"
, Text
"```"
, Text
""
, Text
"could be simplified to:"
, Text
""
, Text
"```cpp"
, Text
"bool foo(void) {"
, Text
" return !check_something();"
, Text
"}"
, Text
"```"
, Text
""
, Text
"Also checks for the use of `true` or `false` in binary expressions. E.g."
, Text
"`a == true` should be `a` and `a != true` should be `!a`."
, Text
""
, Text
"**Reason:** simpler code is easier to read."
]))