Skip to content

Commit 4f628cb

Browse files
committed
feat: check tautologically-false conditionals
- fix koalaman#3179 — negation of SC2055, `[ x = y -a x = z]` - fix koalaman#3181 — negation of SC2056, `(( x == y && x == z ))` - fix koalaman#3180 — negation of SC2252, `[ x = y ] && [ x = z ]`
1 parent d3001f3 commit 4f628cb

File tree

1 file changed

+50
-0
lines changed

1 file changed

+50
-0
lines changed

src/ShellCheck/Analytics.hs

+50
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ nodeChecks = [
123123
,checkCaseAgainstGlob
124124
,checkCommarrays
125125
,checkOrNeq
126+
,checkAndEq
126127
,checkEchoWc
127128
,checkConstantIfs
128129
,checkPipedAssignment
@@ -1631,6 +1632,55 @@ checkOrNeq _ (T_OrIf id lhs rhs) = sequence_ $ do
16311632
checkOrNeq _ _ = return ()
16321633

16331634

1635+
prop_checkAndEq1 = verify checkAndEq "if [[ $lol -eq cow && $lol -eq foo ]]; then echo foo; fi"
1636+
prop_checkAndEq2 = verify checkAndEq "(( a==lol && a==foo ))"
1637+
prop_checkAndEq3 = verify checkAndEq "[ \"$a\" = lol && \"$a\" = foo ]"
1638+
prop_checkAndEq4 = verifyNot checkAndEq "[ a = $cow && b = $foo ]"
1639+
prop_checkAndEq5 = verifyNot checkAndEq "[[ $a = /home && $a = */public_html/* ]]"
1640+
prop_checkAndEq6 = verify checkAndEq "[ $a = a ] && [ $a = b ]"
1641+
prop_checkAndEq7 = verify checkAndEq "[ $a = a ] && [ $a = b ] || true"
1642+
prop_checkAndEq8 = verifyNot checkAndEq "[[ $a == x && $a == x ]]"
1643+
prop_checkAndEq9 = verifyNot checkAndEq "[ 0 -eq $FOO ] && [ 0 -eq $BAR ]"
1644+
1645+
-- For test-level "and": [ x = y -a x = z ]
1646+
checkAndEq _ (TC_And id typ op (TC_Binary _ _ op1 lhs1 rhs1 ) (TC_Binary _ _ op2 lhs2 rhs2))
1647+
| (op1 == op2 && (op1 == "-eq" || op1 == "=" || op1 == "==")) && lhs1 == lhs2 && rhs1 /= rhs2 && not (any isGlob [rhs1,rhs2]) =
1648+
warn id 2055 $ "You probably wanted " ++ (if typ == SingleBracket then "-o" else "||") ++ " here, otherwise it's always false."
1649+
1650+
-- For arithmetic context "and"
1651+
checkAndEq _ (TA_Binary id "&&" (TA_Binary _ "==" word1 _) (TA_Binary _ "==" word2 _))
1652+
| word1 == word2 =
1653+
warn id 2056 "You probably wanted || here, otherwise it's always false."
1654+
1655+
-- For command level "and": [ x = y ] && [ x = z ]
1656+
checkAndEq _ (T_AndIf id lhs rhs) = sequence_ $ do
1657+
(lhs1, op1, rhs1) <- getExpr lhs
1658+
(lhs2, op2, rhs2) <- getExpr rhs
1659+
guard $ op1 == op2 && op1 `elem` ["-eq", "=", "=="]
1660+
guard $ lhs1 == lhs2 && rhs1 /= rhs2
1661+
guard . not $ any isGlob [rhs1, rhs2]
1662+
return $ warn id 2252 "You probably wanted || here, otherwise it's always false."
1663+
where
1664+
getExpr x =
1665+
case x of
1666+
T_AndIf _ lhs _ -> getExpr lhs -- Fetches x and y in `T_AndIf x (T_AndIf y z)`
1667+
T_Pipeline _ _ [x] -> getExpr x
1668+
T_Redirecting _ _ c -> getExpr c
1669+
T_Condition _ _ c -> getExpr c
1670+
TC_Binary _ _ op lhs rhs -> orient (lhs, op, rhs)
1671+
_ -> Nothing
1672+
1673+
-- Swap items so that the constant side is rhs (or Nothing if both/neither is constant)
1674+
orient (lhs, op, rhs) =
1675+
case (isConstant lhs, isConstant rhs) of
1676+
(True, False) -> return (rhs, op, lhs)
1677+
(False, True) -> return (lhs, op, rhs)
1678+
_ -> Nothing
1679+
1680+
1681+
checkAndEq _ _ = return ()
1682+
1683+
16341684
prop_checkValidCondOps1 = verify checkValidCondOps "[[ a -xz b ]]"
16351685
prop_checkValidCondOps2 = verify checkValidCondOps "[ -M a ]"
16361686
prop_checkValidCondOps2a = verifyNot checkValidCondOps "[ 3 \\> 2 ]"

0 commit comments

Comments
 (0)