From 6b89f33d0c579b71766010da6f75d674fc375c67 Mon Sep 17 00:00:00 2001
From: Vidar Holen <spam@vidarholen.net>
Date: Tue, 28 May 2013 20:06:20 -0700
Subject: [PATCH] Warn about arg='--foo="bar"'; cmd $arg

---
 ShellCheck/Analytics.hs | 40 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)

diff --git a/ShellCheck/Analytics.hs b/ShellCheck/Analytics.hs
index 7e29ecd..e97f26a 100644
--- a/ShellCheck/Analytics.hs
+++ b/ShellCheck/Analytics.hs
@@ -35,7 +35,7 @@ genericChecks = concat [
     map runBasicAnalysis basicChecks
     ,[runBasicTreeAnalysis treeChecks]
     ,[subshellAssignmentCheck]
-    ,[checkSpacefulness]
+    ,[checkSpacefulness, checkQuotesInLiterals]
     ,[checkShebang]
     ]
 
@@ -1095,7 +1095,9 @@ findSubshelled ((StackScope (SubshellScope reason)):rest) scopes deadVars =
     findSubshelled rest ((reason,[]):scopes) deadVars
 
 findSubshelled ((StackScopeEnd):rest) ((reason, scope):oldScopes) deadVars =
-    findSubshelled rest oldScopes $ foldl (\m (_, token, var, _) -> Map.insert var (Dead token reason) m) deadVars scope
+    findSubshelled rest oldScopes $
+        foldl (\m (_, token, var, _) ->
+            Map.insert var (Dead token reason) m) deadVars scope
 
 doVariableFlowAnalysis readFunc writeFunc empty t = fst $ runState (
     foldM (\list x -> do { l <- doFlow x;  return $ l ++ list; }) [] flow
@@ -1180,3 +1182,37 @@ checkSpacefulness t =
         containsAny s chars = any (\c -> c `elem` s) chars
 
 
+prop_checkQuotesInLiterals1 = verifyFull checkQuotesInLiterals "param='--foo=\"bar\"'; app $param"
+prop_checkQuotesInLiterals1a= verifyFull checkQuotesInLiterals "param=\"--foo='lolbar'\"; app $param"
+prop_checkQuotesInLiterals2 = verifyNotFull checkQuotesInLiterals "param='--foo=\"bar\"'; app \"$param\""
+prop_checkQuotesInLiterals3 =verifyNotFull checkQuotesInLiterals "param=('--foo='); app \"${param[@]}\""
+prop_checkQuotesInLiterals4 = verifyNotFull checkQuotesInLiterals "param=\"don't bother with this one\"; app $param"
+checkQuotesInLiterals t =
+    doVariableFlowAnalysis readF writeF Map.empty t
+  where
+    getQuotes name = get >>= (return . Map.lookup name)
+    setQuotes name ref = modify $ Map.insert name ref
+    deleteQuotes = modify . Map.delete
+    parents = getParentTree t
+    quoteRegex = mkRegex "\"|([= ]|^)'|'( |$)"
+    containsQuotes s = isJust $ matchRegex quoteRegex s
+
+    -- Just catch the most blatant cases of foo='--cow="lol bert"'; cmd $foo, since that's 99%
+    writeF _ _ name (DataFrom values) = do
+        let quotedVars = filter (\v -> containsQuotes (concat $ deadSimple v)) values
+        case quotedVars of
+            [] -> deleteQuotes name
+            x:_ -> setQuotes name (getId x)
+        return []
+    writeF _ _ _ _ = return []
+
+    readF _ expr name = do
+        assignment <- getQuotes name
+        if isJust assignment && not (inUnquotableContext parents expr)
+            then return [
+                    (fromJust assignment,
+                        Note WarningC "Word splitting will treat quotes as literals. Use an array."),
+                    (getId expr,
+                        Note WarningC "Embedded quotes in this variable will not be respected.")
+                    ]
+            else return []