Hello community,

here is the log from the commit of package ShellCheck for openSUSE:Factory 
checked in at 2017-03-14 10:03:51
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/ShellCheck (Old)
 and      /work/SRC/openSUSE:Factory/.ShellCheck.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "ShellCheck"

Tue Mar 14 10:03:51 2017 rev:6 rq:461516 version:0.4.5

Changes:
--------
--- /work/SRC/openSUSE:Factory/ShellCheck/ShellCheck.changes    2016-07-21 
08:01:29.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.ShellCheck.new/ShellCheck.changes       
2017-03-14 10:03:55.062715770 +0100
@@ -1,0 +2,5 @@
+Sun Feb 12 14:19:35 UTC 2017 - [email protected]
+
+- Update to version 0.4.5 with cabal2obs.
+
+-------------------------------------------------------------------

Old:
----
  ShellCheck-0.4.4.tar.gz

New:
----
  ShellCheck-0.4.5.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ ShellCheck.spec ++++++
--- /var/tmp/diff_new_pack.GUoByK/_old  2017-03-14 10:03:55.598639884 +0100
+++ /var/tmp/diff_new_pack.GUoByK/_new  2017-03-14 10:03:55.602639317 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package ShellCheck
 #
-# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -19,14 +19,13 @@
 %global pkg_name ShellCheck
 %bcond_with tests
 Name:           %{pkg_name}
-Version:        0.4.4
+Version:        0.4.5
 Release:        0
 Summary:        Shell script analysis tool
 License:        GPL-3.0+
 Group:          Development/Languages/Other
 Url:            https://hackage.haskell.org/package/%{name}
 Source0:        
https://hackage.haskell.org/package/%{name}-%{version}/%{name}-%{version}.tar.gz
-# Begin cabal-rpm deps:
 BuildRequires:  chrpath
 BuildRequires:  ghc-Cabal-devel
 BuildRequires:  ghc-QuickCheck-devel
@@ -39,7 +38,6 @@
 BuildRequires:  ghc-regex-tdfa-devel
 BuildRequires:  ghc-rpm-macros
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
-# End cabal-rpm deps
 
 %description
 The goals of ShellCheck are:
@@ -74,23 +72,16 @@
 %prep
 %setup -q
 
-
 %build
 %ghc_lib_build
 
-
 %install
 %ghc_lib_install
-
-%ghc_fix_dynamic_rpath shellcheck
-
+%ghc_fix_rpath %{pkg_name}-%{version}
 install -Dpm 0644 shellcheck.1 %{buildroot}%{_mandir}/man1/shellcheck.1
 
 %check
-%if %{with tests}
-%{cabal} test
-%endif
-
+%cabal_test
 
 %post -n ghc-%{name}-devel
 %ghc_pkg_recache

++++++ ShellCheck-0.4.4.tar.gz -> ShellCheck-0.4.5.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ShellCheck-0.4.4/README.md 
new/ShellCheck-0.4.5/README.md
--- old/ShellCheck-0.4.4/README.md      2016-05-15 04:06:47.000000000 +0200
+++ new/ShellCheck-0.4.5/README.md      2016-10-21 23:19:54.000000000 +0200
@@ -70,6 +70,10 @@
 
     apt-get install shellcheck
 
+On Gentoo based distros:
+
+    emerge --ask shellcheck
+
 On Fedora based distros:
 
     dnf install ShellCheck
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ShellCheck-0.4.4/ShellCheck/AST.hs 
new/ShellCheck-0.4.5/ShellCheck/AST.hs
--- old/ShellCheck-0.4.4/ShellCheck/AST.hs      2016-05-15 04:06:47.000000000 
+0200
+++ new/ShellCheck-0.4.5/ShellCheck/AST.hs      2016-10-21 23:19:54.000000000 
+0200
@@ -21,6 +21,7 @@
 
 import Control.Monad
 import Control.Monad.Identity
+import Text.Parsec
 import qualified ShellCheck.Regex as Re
 
 data Id = Id Int deriving (Show, Eq, Ord)
@@ -50,8 +51,10 @@
     | T_AndIf Id (Token) (Token)
     | T_Arithmetic Id Token
     | T_Array Id [Token]
-    | T_IndexedElement Id Token Token
-    | T_Assignment Id AssignmentMode String (Maybe Token) Token
+    | T_IndexedElement Id [Token] Token
+    -- Store the index as string, and parse as arithmetic or string later
+    | T_UnparsedIndex Id SourcePos String
+    | T_Assignment Id AssignmentMode String [Token] Token
     | T_Backgrounded Id Token
     | T_Backticked Id [Token]
     | T_Bang Id
@@ -96,6 +99,7 @@
     | T_IfExpression Id [([Token],[Token])] [Token]
     | T_In  Id
     | T_IoFile Id Token Token
+    | T_IoDuplicate Id Token String
     | T_LESSAND Id
     | T_LESSGREAT Id
     | T_Lbrace Id
@@ -145,7 +149,7 @@
 instance Eq Token where
     (==) = tokenEquals
 
-analyze :: Monad m => (Token -> m ()) -> (Token -> m ()) -> (Token -> Token) 
-> Token -> m Token
+analyze :: Monad m => (Token -> m ()) -> (Token -> m ()) -> (Token -> m Token) 
-> Token -> m Token
 analyze f g i =
     round
   where
@@ -153,7 +157,7 @@
         f t
         newT <- delve t
         g t
-        return . i $ newT
+        i newT
     roundAll = mapM round
 
     roundMaybe Nothing = return Nothing
@@ -186,14 +190,18 @@
     delve (T_DollarArithmetic id c) = d1 c $ T_DollarArithmetic id
     delve (T_DollarBracket id c) = d1 c $ T_DollarBracket id
     delve (T_IoFile id op file) = d2 op file $ T_IoFile id
+    delve (T_IoDuplicate id op num) = d1 op $ \x -> T_IoDuplicate id x num
     delve (T_HereString id word) = d1 word $ T_HereString id
     delve (T_FdRedirect id v t) = d1 t $ T_FdRedirect id v
-    delve (T_Assignment id mode var index value) = do
-        a <- roundMaybe index
+    delve (T_Assignment id mode var indices value) = do
+        a <- roundAll indices
         b <- round value
         return $ T_Assignment id mode var a b
     delve (T_Array id t) = dl t $ T_Array id
-    delve (T_IndexedElement id t1 t2) = d2 t1 t2 $ T_IndexedElement id
+    delve (T_IndexedElement id indices t) = do
+        a <- roundAll indices
+        b <- round t
+        return $ T_IndexedElement id a b
     delve (T_Redirecting id redirs cmd) = do
         a <- roundAll redirs
         b <- round cmd
@@ -312,6 +320,7 @@
         T_BraceExpansion id _  -> id
         T_DollarBraceCommandExpansion id _  -> id
         T_IoFile id _ _  -> id
+        T_IoDuplicate id _ _  -> id
         T_HereDoc id _ _ _ _ -> id
         T_HereString id _  -> id
         T_FdRedirect id _ _  -> id
@@ -363,10 +372,11 @@
         T_CoProc id _ _ -> id
         T_CoProcBody id _ -> id
         T_Include id _ _ -> id
+        T_UnparsedIndex id _ _ -> id
 
 blank :: Monad m => Token -> m ()
 blank = const $ return ()
-doAnalysis f = analyze f blank id
-doStackAnalysis startToken endToken = analyze startToken endToken id
-doTransform i = runIdentity . analyze blank blank i
+doAnalysis f = analyze f blank (return . id)
+doStackAnalysis startToken endToken = analyze startToken endToken (return . id)
+doTransform i = runIdentity . analyze blank blank (return . i)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ShellCheck-0.4.4/ShellCheck/ASTLib.hs 
new/ShellCheck-0.4.5/ShellCheck/ASTLib.hs
--- old/ShellCheck-0.4.4/ShellCheck/ASTLib.hs   2016-05-15 04:06:47.000000000 
+0200
+++ new/ShellCheck-0.4.5/ShellCheck/ASTLib.hs   2016-10-21 23:19:54.000000000 
+0200
@@ -21,6 +21,7 @@
 
 import ShellCheck.AST
 
+import Control.Monad.Writer
 import Control.Monad
 import Data.List
 import Data.Maybe
@@ -54,6 +55,8 @@
 -- Is this shell word a constant?
 isConstant token =
     case token of
+        -- This ignores some cases like ~"foo":
+        T_NormalWord _ (T_Literal _ ('~':_) : _)  -> False
         T_NormalWord _ l   -> all isConstant l
         T_DoubleQuoted _ l -> all isConstant l
         T_SingleQuoted _ _ -> True
@@ -194,6 +197,8 @@
 -- Turn a NormalWord like foo="bar $baz" into a series of constituent elements 
like [foo=,bar ,$baz]
 getWordParts (T_NormalWord _ l)   = concatMap getWordParts l
 getWordParts (T_DoubleQuoted _ l) = l
+-- TA_Expansion is basically T_NormalWord for arithmetic expressions
+getWordParts (TA_Expansion _ l)   = concatMap getWordParts l
 getWordParts other                = [other]
 
 -- Return a list of NormalWords that would result from brace expansion
@@ -206,13 +211,31 @@
         braceExpand item
     part x = return x
 
+-- Maybe get a SimpleCommand from immediate wrappers like T_Redirections
+getCommand t =
+    case t of
+        T_Redirecting _ _ w -> getCommand w
+        T_SimpleCommand _ _ (w:_) -> return t
+        T_Annotation _ _ t -> getCommand t
+        otherwise -> Nothing
+
 -- Maybe get the command name of a token representing a command
-getCommandName t =
+getCommandName t = do
+    (T_SimpleCommand _ _ (w:_)) <- getCommand t
+    getLiteralString w
+
+-- If a command substitution is a single command, get its name.
+--  $(date +%s) = Just "date"
+getCommandNameFromExpansion :: Token -> Maybe String
+getCommandNameFromExpansion t =
     case t of
-        T_Redirecting _ _ w -> getCommandName w
-        T_SimpleCommand _ _ (w:_) -> getLiteralString w
-        T_Annotation _ _ t -> getCommandName t
+        T_DollarExpansion _ [c] -> extract c
+        T_Backticked _ [c] -> extract c
+        T_DollarBraceCommandExpansion _ [c] -> extract c
         otherwise -> Nothing
+  where
+    extract (T_Pipeline _ _ [cmd]) = getCommandName cmd
+    extract _ = Nothing
 
 -- Get the basename of a token representing a command
 getCommandBasename = liftM basename . getCommandName
@@ -237,8 +260,8 @@
 
 isFunction t = case t of T_Function {} -> True; _ -> False
 
--- Get the list of commands from tokens that contain them, such as
--- the body of while loops and if statements.
+-- Get the lists of commands from tokens that contain them, such as
+-- the body of while loops or branches of if statements.
 getCommandSequences t =
     case t of
         T_Script _ _ cmds -> [cmds]
@@ -251,3 +274,22 @@
         T_IfExpression _ thens elses -> map snd thens ++ [elses]
         otherwise -> []
 
+-- Get a list of names of associative arrays
+getAssociativeArrays t =
+    nub . execWriter $ doAnalysis f t
+  where
+    f :: Token -> Writer [String] ()
+    f t@(T_SimpleCommand {}) = fromMaybe (return ()) $ do
+        name <- getCommandName t
+        guard $ name == "declare"
+        let flags = getAllFlags t
+        guard $ elem "A" $ map snd flags
+        let args = map fst . filter ((==) "" . snd) $ flags
+        let names = mapMaybe (getLiteralStringExt nameAssignments) args
+        return $ tell names
+    f _ = return ()
+
+    nameAssignments t =
+        case t of
+            T_Assignment _ _ name _ _ -> return name
+            otherwise -> Nothing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ShellCheck-0.4.4/ShellCheck/Analytics.hs 
new/ShellCheck-0.4.5/ShellCheck/Analytics.hs
--- old/ShellCheck-0.4.4/ShellCheck/Analytics.hs        2016-05-15 
04:06:47.000000000 +0200
+++ new/ShellCheck-0.4.5/ShellCheck/Analytics.hs        2016-10-21 
23:19:54.000000000 +0200
@@ -85,6 +85,7 @@
     ,checkEchoSed
     ,checkForDecimals
     ,checkLocalScope
+    ,checkMultiDimensionalArrays
     ]
 
 runAnalytics :: AnalysisSpec -> [TokenComment]
@@ -178,6 +179,7 @@
     ,checkReadWithoutR
     ,checkLoopVariableReassignment
     ,checkTrailingBracket
+    ,checkReturnAgainstZero
     ]
 
 
@@ -369,6 +371,8 @@
 prop_checkPipePitfalls5 = verifyNot checkPipePitfalls "ls -N | foo"
 prop_checkPipePitfalls6 = verify checkPipePitfalls "find . | xargs foo"
 prop_checkPipePitfalls7 = verifyNot checkPipePitfalls "find . -printf '%s\\n' 
| xargs foo"
+prop_checkPipePitfalls8 = verify checkPipePitfalls "foo | grep bar | wc -l"
+prop_checkPipePitfalls9 = verifyNot checkPipePitfalls "foo | grep -o bar | wc 
-l"
 checkPipePitfalls _ (T_Pipeline id _ commands) = do
     for ["find", "xargs"] $
         \(find:xargs:_) ->
@@ -388,8 +392,12 @@
     for' ["ps", "grep"] $
         \x -> info x 2009 "Consider using pgrep instead of grepping ps output."
 
-    for' ["grep", "wc"] $
-        \x -> style x 2126 "Consider using grep -c instead of grep|wc."
+    for ["grep", "wc"] $
+        \(grep:wc:_) ->
+            let flags = fromMaybe [] $ map snd <$> getAllFlags <$> getCommand 
grep
+            in
+                unless (any (`elem` ["o", "only-matching"]) flags) $
+                    style (getId grep) 2126 "Consider using grep -c instead of 
grep|wc."
 
     didLs <- liftM or . sequence $ [
         for' ["ls", "grep"] $
@@ -439,10 +447,16 @@
 prop_checkShebang1 = verifyNotTree checkShebang "#!/usr/bin/env bash -x\necho 
cow"
 prop_checkShebang2 = verifyNotTree checkShebang "#! /bin/sh  -l "
 prop_checkShebang3 = verifyTree checkShebang "ls -l"
-checkShebang params (T_Annotation _ _ t) = checkShebang params t
+prop_checkShebang4 = verifyNotTree checkShebang "#shellcheck shell=sh\nfoo"
+checkShebang params (T_Annotation _ list t) =
+    if any isOverride list then [] else checkShebang params t
+  where
+    isOverride (ShellOverride _) = True
+    isOverride _ = False
 checkShebang params (T_Script id sb _) =
-    [makeComment ErrorC id 2148 "Tips depend on target shell and yours is 
unknown. Add a shebang."
-        | not (shellTypeSpecified params) && sb == "" ]
+    [makeComment ErrorC id 2148
+        "Tips depend on target shell and yours is unknown. Add a shebang."
+      | not (shellTypeSpecified params) && sb == "" ]
 
 prop_checkBashisms = verify checkBashisms "while read a; do :; done < <(a)"
 prop_checkBashisms2 = verify checkBashisms "[ foo -nt bar ]"
@@ -493,6 +507,9 @@
 prop_checkBashisms47= verify checkBashisms "#!/bin/dash\necho foo 42>/dev/null"
 prop_checkBashisms48= verifyNot checkBashisms "#!/bin/dash\necho $LINENO"
 prop_checkBashisms49= verify checkBashisms "#!/bin/dash\necho $MACHTYPE"
+prop_checkBashisms50= verify checkBashisms "#!/bin/sh\ncmd >& file"
+prop_checkBashisms51= verifyNot checkBashisms "#!/bin/sh\ncmd 2>&1"
+prop_checkBashisms52= verifyNot checkBashisms "#!/bin/sh\ncmd >&2"
 checkBashisms params = bashism
   where
     isDash = shellType params == Dash
@@ -524,6 +541,7 @@
             warnMsg id $ filter (/= '|') op ++ " is"
     bashism (TA_Binary id "**" _ _) = warnMsg id "exponentials are"
     bashism (T_FdRedirect id "&" (T_IoFile _ (T_Greater _) _)) = warnMsg id 
"&> is"
+    bashism (T_FdRedirect id "" (T_IoFile _ (T_GREATAND _) _)) = warnMsg id 
">& is"
     bashism (T_FdRedirect id ('{':_) _) = warnMsg id "named file descriptors 
are"
     bashism (T_FdRedirect id num _)
         | all isDigit num && length num > 1 = warnMsg id "FDs outside 0-9 are"
@@ -765,18 +783,23 @@
 prop_checkUnquotedExpansions5 = verifyNot checkUnquotedExpansions "for f in 
$(cmd); do echo $f; done"
 prop_checkUnquotedExpansions6 = verifyNot checkUnquotedExpansions "$(cmd)"
 prop_checkUnquotedExpansions7 = verifyNot checkUnquotedExpansions "cat << 
foo\n$(ls)\nfoo"
+prop_checkUnquotedExpansions8 = verifyNot checkUnquotedExpansions "set -- 
$(seq 1 4)"
+prop_checkUnquotedExpansions9 = verifyNot checkUnquotedExpansions "echo foo `# 
inline comment`"
 checkUnquotedExpansions params =
     check
   where
-    check t@(T_DollarExpansion _ _) = examine t
-    check t@(T_Backticked _ _) = examine t
-    check t@(T_DollarBraceCommandExpansion _ _) = examine t
+    check t@(T_DollarExpansion _ c) = examine t c
+    check t@(T_Backticked _ c) = examine t c
+    check t@(T_DollarBraceCommandExpansion _ c) = examine t c
     check _ = return ()
     tree = parentMap params
-    examine t =
-        unless (isQuoteFree tree t || usedAsCommandName tree t) $
+    examine t contents =
+        unless (null contents || shouldBeSplit t || isQuoteFree tree t || 
usedAsCommandName tree t) $
             warn (getId t) 2046 "Quote this to prevent word splitting."
 
+    shouldBeSplit t =
+        getCommandNameFromExpansion t == Just "seq"
+
 
 prop_checkRedirectToSame = verify checkRedirectToSame "cat foo > foo"
 prop_checkRedirectToSame2 = verify checkRedirectToSame "cat lol | sed -e 
's/a/b/g' > lol"
@@ -933,7 +956,7 @@
                     "Expanding an array without an index only gives the first 
element."
     readF _ _ _ = return []
 
-    writeF _ (T_Assignment id mode name Nothing _) _ (DataString _) = do
+    writeF _ (T_Assignment id mode name [] _) _ (DataString _) = do
         isArray <- gets (isJust . Map.lookup name)
         return $ if not isArray then [] else
             case mode of
@@ -951,7 +974,7 @@
 
     isIndexed expr =
         case expr of
-            T_Assignment _ _ _ (Just _) _ -> True
+            T_Assignment _ _ _ (_:_) _ -> True
             _ -> False
 
 prop_checkStderrRedirect = verify checkStderrRedirect "test 2>&1 > cow"
@@ -961,7 +984,7 @@
 prop_checkStderrRedirect5 = verifyNot checkStderrRedirect "read < <(test 2>&1 
> file)"
 prop_checkStderrRedirect6 = verify checkStderrRedirect "foo | bar 2>&1 > 
/dev/null"
 checkStderrRedirect params redir@(T_Redirecting _ [
-    T_FdRedirect id "2" (T_IoFile _ (T_GREATAND _) (T_NormalWord _ [T_Literal 
_ "1"])),
+    T_FdRedirect id "2" (T_IoDuplicate _ (T_GREATAND _) "1"),
     T_FdRedirect _ _ (T_IoFile _ op _)
     ] _) = case op of
             T_Greater _ -> error
@@ -1030,6 +1053,7 @@
                 ,"alias"
                 ,"sudo" -- covering "sudo sh" and such
                 ,"dpkg-query"
+                ,"jq"  -- could also check that user provides --arg
                 ]
             || "awk" `isSuffixOf` commandName
             || "perl" `isPrefixOf` commandName
@@ -1154,6 +1178,7 @@
 prop_checkSingleBracketOperators2 = verify checkSingleBracketOperators "[ $foo 
> $bar ]"
 prop_checkSingleBracketOperators3 = verifyNot checkSingleBracketOperators "[[ 
foo < bar ]]"
 prop_checkSingleBracketOperators5 = verify checkSingleBracketOperators "until 
[ $n <= $z ]; do echo foo; done"
+prop_checkSingleBracketOperators6 = verifyNot checkSingleBracketOperators "[ 
$foo '>' $bar ]"
 checkSingleBracketOperators _ (TC_Binary id typ op lhs rhs)
     | typ == SingleBracket && op `elem` ["<", ">", "<=", ">="] =
         err id 2073 $ "Can't use " ++ op ++" in [ ]. Escape it or use [[..]]."
@@ -1234,12 +1259,11 @@
 prop_checkConstantIfs5 = verifyNot checkConstantIfs "[[ $n -le $n ]]"
 prop_checkConstantIfs6 = verifyNot checkConstantIfs "[[ a -ot b ]]"
 prop_checkConstantIfs7 = verifyNot checkConstantIfs "[ a -nt b ]"
+prop_checkConstantIfs8 = verifyNot checkConstantIfs "[[ ~foo == '~foo' ]]"
 checkConstantIfs _ (TC_Binary id typ op lhs rhs) | not isDynamic =
-    when (isJust lLit && isJust rLit) $
+    when (isConstant lhs && isConstant rhs) $
         warn id 2050 "This expression is constant. Did you forget the $ on a 
variable?"
   where
-    lLit = getLiteralString lhs
-    rLit = getLiteralString rhs
     isDynamic =
         op `elem` [ "-lt", "-gt", "-le", "-ge", "-eq", "-ne" ]
             && typ == DoubleBracket
@@ -1322,7 +1346,9 @@
         (`isUnqualifiedCommand` "eval") <$> getClosestCommand (parentMap 
params) t
 checkBraceExpansionVars _ _ = return ()
 
-prop_checkForDecimals = verify checkForDecimals "((3.14*c))"
+prop_checkForDecimals1 = verify checkForDecimals "((3.14*c))"
+prop_checkForDecimals2 = verify checkForDecimals "foo[1.2]=bar"
+prop_checkForDecimals3 = verifyNot checkForDecimals "declare -A foo; 
foo[1.2]=bar"
 checkForDecimals params t@(TA_Expansion id _) = potentially $ do
     guard $ not (hasFloatingPoint params)
     str <- getLiteralString t
@@ -1356,7 +1382,7 @@
     unless (isException $ bracedString b) getWarning
   where
     isException [] = True
-    isException s = any (`elem` "/.:#%?*@$") s || isDigit (head s)
+    isException s = any (`elem` "/.:#%?*@$-") s || isDigit (head s)
     getWarning = fromMaybe noWarning . msum . map warningFor $ parents params t
     warningFor t =
         case t of
@@ -2071,6 +2097,8 @@
 prop_checkUnused31= verifyTree checkUnusedAssignments "let 'a=1'"
 prop_checkUnused32= verifyTree checkUnusedAssignments "let a=b=c; echo $a"
 prop_checkUnused33= verifyNotTree checkUnusedAssignments "a=foo; [[ foo =~ 
^{$a}$ ]]"
+prop_checkUnused34= verifyNotTree checkUnusedAssignments "foo=1; (( t = foo 
)); echo $t"
+prop_checkUnused35= verifyNotTree checkUnusedAssignments "a=foo; b=2; echo 
${a:b}"
 checkUnusedAssignments params t = execWriter (mapM_ warnFor unused)
   where
     flow = variableFlow params
@@ -2115,6 +2143,11 @@
 prop_checkUnassignedReferences20= verifyNotTree checkUnassignedReferences 
"printf -v foo bar; echo $foo"
 prop_checkUnassignedReferences21= verifyTree checkUnassignedReferences "echo 
${#foo}"
 prop_checkUnassignedReferences22= verifyNotTree checkUnassignedReferences 
"echo ${!os*}"
+prop_checkUnassignedReferences23= verifyTree checkUnassignedReferences 
"declare -a foo; foo[bar]=42;"
+prop_checkUnassignedReferences24= verifyNotTree checkUnassignedReferences 
"declare -A foo; foo[bar]=42;"
+prop_checkUnassignedReferences25= verifyNotTree checkUnassignedReferences 
"declare -A foo=(); foo[bar]=42;"
+prop_checkUnassignedReferences26= verifyNotTree checkUnassignedReferences 
"a::b() { foo; }; readonly -f a::b"
+prop_checkUnassignedReferences27= verifyNotTree checkUnassignedReferences ": 
${foo:=bar}"
 checkUnassignedReferences params t = warnings
   where
     (readMap, writeMap) = execState (mapM tally $ variableFlow params) 
(Map.empty, Map.empty)
@@ -2262,7 +2295,7 @@
         case t of
             T_SimpleCommand _ vars (_:_) -> mapM_ checkVar vars
             otherwise -> check rest
-    checkVar (T_Assignment aId mode aName Nothing value) |
+    checkVar (T_Assignment aId mode aName [] value) |
             aName == name && (aId `notElem` idPath) = do
         warn aId 2097 "This assignment is only seen by the forked process."
         warn id 2098 "This expansion will not see the mentioned assignment."
@@ -2542,7 +2575,7 @@
 checkOverridingPath _ (T_SimpleCommand _ vars []) =
     mapM_ checkVar vars
   where
-    checkVar (T_Assignment id Assign "PATH" Nothing word) =
+    checkVar (T_Assignment id Assign "PATH" [] word) =
         let string = concat $ oversimplify word
         in unless (any (`isInfixOf` string) ["/bin", "/sbin" ]) $ do
             when ('/' `elem` string && ':' `notElem` string) $ notify id
@@ -2557,7 +2590,7 @@
 checkTildeInPath _ (T_SimpleCommand _ vars _) =
     mapM_ checkVar vars
   where
-    checkVar (T_Assignment id Assign "PATH" Nothing (T_NormalWord _ parts)) =
+    checkVar (T_Assignment id Assign "PATH" [] (T_NormalWord _ parts)) =
         when (any (\x -> isQuoted x && hasTilde x) parts) $
             warn id 2147 "Literal tilde in PATH works poorly across programs."
     checkVar _ = return ()
@@ -2618,7 +2651,7 @@
 
 prop_checkSuspiciousIFS1 = verify checkSuspiciousIFS "IFS=\"\\n\""
 prop_checkSuspiciousIFS2 = verifyNot checkSuspiciousIFS "IFS=$'\\t'"
-checkSuspiciousIFS params (T_Assignment id Assign "IFS" Nothing value) =
+checkSuspiciousIFS params (T_Assignment id Assign "IFS" [] value) =
     potentially $ do
         str <- getLiteralString value
         return $ check str
@@ -2790,5 +2823,54 @@
             "]" -> "["
             x -> x
 
+prop_checkMultiDimensionalArrays1 = verify checkMultiDimensionalArrays 
"foo[a][b]=3"
+prop_checkMultiDimensionalArrays2 = verifyNot checkMultiDimensionalArrays 
"foo[a]=3"
+prop_checkMultiDimensionalArrays3 = verify checkMultiDimensionalArrays "foo=( 
[a][b]=c )"
+prop_checkMultiDimensionalArrays4 = verifyNot checkMultiDimensionalArrays 
"foo=( [a]=c )"
+prop_checkMultiDimensionalArrays5 = verify checkMultiDimensionalArrays "echo 
${foo[bar][baz]}"
+prop_checkMultiDimensionalArrays6 = verifyNot checkMultiDimensionalArrays 
"echo ${foo[bar]}"
+checkMultiDimensionalArrays _ token =
+    case token of
+        T_Assignment _ _ name (first:second:_) _ -> about second
+        T_IndexedElement _ (first:second:_) _ -> about second
+        T_DollarBraced {} ->
+            when (isMultiDim token) $ about token
+        _ -> return ()
+  where
+    about t = warn (getId t) 2180 "Bash does not support multidimensional 
arrays. Use 1D or associative arrays."
+
+    re = mkRegex "^\\[.*\\]\\[.*\\]"  -- Fixme, this matches ${foo:- [][]} and 
such as well
+    isMultiDim t = getBracedModifier (bracedString t) `matches` re
+
+prop_checkReturnAgainstZero1 = verify checkReturnAgainstZero "[ $? -eq 0 ]"
+prop_checkReturnAgainstZero2 = verify checkReturnAgainstZero "[[ \"$?\" -gt 0 
]]"
+prop_checkReturnAgainstZero3 = verify checkReturnAgainstZero "[[ 0 -ne $? ]]"
+prop_checkReturnAgainstZero4 = verifyNot checkReturnAgainstZero "[[ $? -eq 4 
]]"
+prop_checkReturnAgainstZero5 = verify checkReturnAgainstZero "[[ 0 -eq $? ]]"
+prop_checkReturnAgainstZero6 = verifyNot checkReturnAgainstZero "[[ $R -eq 0 
]]"
+prop_checkReturnAgainstZero7 = verify checkReturnAgainstZero "(( $? == 0 ))"
+prop_checkReturnAgainstZero8 = verify checkReturnAgainstZero "(( $? ))"
+prop_checkReturnAgainstZero9 = verify checkReturnAgainstZero "(( ! $? ))"
+checkReturnAgainstZero _ token =
+    case token of
+        TC_Binary id _ _ lhs rhs -> check lhs rhs
+        TA_Binary id _ lhs rhs -> check lhs rhs
+        TA_Unary id _ exp ->
+            when (isExitCode exp) $ message (getId exp)
+        TA_Sequence _ [exp] ->
+            when (isExitCode exp) $ message (getId exp)
+        otherwise -> return ()
+  where
+    check lhs rhs =
+        if isZero rhs && isExitCode lhs
+        then message (getId lhs)
+        else when (isZero lhs && isExitCode rhs) $ message (getId rhs)
+    isZero t = getLiteralString t == Just "0"
+    isExitCode t =
+        case getWordParts t of
+            [exp@(T_DollarBraced {})] -> bracedString exp == "?"
+            otherwise -> False
+    message id = style id 2181 "Check exit code directly with e.g. 'if 
mycmd;', not indirectly with $?."
+
 return []
 runTests =  $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { 
maxSuccess = 1 }) ) |])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ShellCheck-0.4.4/ShellCheck/AnalyzerLib.hs 
new/ShellCheck-0.4.5/ShellCheck/AnalyzerLib.hs
--- old/ShellCheck-0.4.4/ShellCheck/AnalyzerLib.hs      2016-05-15 
04:06:47.000000000 +0200
+++ new/ShellCheck-0.4.5/ShellCheck/AnalyzerLib.hs      2016-10-21 
23:19:54.000000000 +0200
@@ -334,6 +334,12 @@
             name <- getLiteralString lhs
             return (t, t, name, DataString $ SourceFrom [rhs])
 
+        T_DollarBraced _ l -> maybeToList $ do
+            let string = bracedString t
+            let modifier = getBracedModifier string
+            guard $ ":=" `isPrefixOf` modifier
+            return (t, t, getBracedReference string, DataString $ SourceFrom 
[l])
+
         t@(T_FdRedirect _ ('{':var) op) -> -- {foo}>&2 modifies foo
             [(t, t, takeWhile (/= '}') var, DataString SourceInteger) | not $ 
isClosingFileOp op]
 
@@ -361,7 +367,10 @@
         "declare" -> if any (`elem` flags) ["x", "p"]
             then concatMap getReference rest
             else []
-        "readonly" -> concatMap getReference rest
+        "readonly" ->
+            if any (`elem` flags) ["f", "p"]
+            then []
+            else concatMap getReference rest
         "trap" ->
             case rest of
                 head:_ -> map (\x -> (head, head, x)) $ 
getVariablesFromLiteralToken head
@@ -395,7 +404,10 @@
         "typeset" -> declaredVars
 
         "local" -> concatMap getModifierParamString rest
-        "readonly" -> concatMap getModifierParamString rest
+        "readonly" ->
+            if any (`elem` flags) ["f", "p"]
+            then []
+            else concatMap getModifierParamString rest
         "set" -> maybeToList $ do
             params <- getSetParams rest
             return (base, base, "@", DataString $ SourceFrom params)
@@ -474,11 +486,20 @@
   where
     re = mkRegex "(\\[.*\\])"
 
+getOffsetReferences mods = fromMaybe [] $ do
+    match <- matchRegex re mods
+    offsets <- match !!! 0
+    return $ matchAllStrings variableNameRegex offsets
+  where
+    re = mkRegex "^ *:(.*)"
+
 getReferencedVariables parents t =
     case t of
         T_DollarBraced id l -> let str = bracedString t in
             (t, t, getBracedReference str) :
-                map (\x -> (l, l, x)) (getIndexReferences str)
+                map (\x -> (l, l, x)) (
+                    getIndexReferences str
+                    ++ getOffsetReferences (getBracedModifier str))
         TA_Expansion id _ ->
             if isArithmeticAssignment t
             then []
@@ -520,7 +541,7 @@
     isDereferencing = (`elem` ["-eq", "-ne", "-lt", "-le", "-gt", "-ge"])
 
     isArithmeticAssignment t = case getPath parents t of
-        this: TA_Assignment _ "=" _ _ :_ -> True
+        this: TA_Assignment _ "=" lhs _ :_ -> lhs == t
         _ -> False
 
 dataTypeFrom defaultType v = (case v of T_Array {} -> DataArray; _ -> 
defaultType) $ SourceFrom [v]
@@ -573,6 +594,7 @@
 prop_getBracedReference10= getBracedReference "foo: -1" == "foo"
 prop_getBracedReference11= getBracedReference "!os*" == ""
 prop_getBracedReference12= getBracedReference "!os?bar**" == ""
+prop_getBracedReference13= getBracedReference "foo[bar]" == "foo"
 getBracedReference s = fromMaybe s $
     nameExpansion s `mplus` takeName noPrefix `mplus` getSpecial noPrefix 
`mplus` getSpecial s
   where
@@ -595,6 +617,20 @@
         return ""
     nameExpansion _ = Nothing
 
+prop_getBracedModifier1 = getBracedModifier "foo:bar:baz" == ":bar:baz"
+prop_getBracedModifier2 = getBracedModifier "!var:-foo" == ":-foo"
+prop_getBracedModifier3 = getBracedModifier "foo[bar]" == "[bar]"
+getBracedModifier s = fromMaybe "" . listToMaybe $ do
+    let var = getBracedReference s
+    a <- dropModifier s
+    dropPrefix var a
+  where
+    dropPrefix [] t = return t
+    dropPrefix (a:b) (c:d) | a == c = dropPrefix b d
+    dropPrefix _ _ = []
+
+    dropModifier (c:rest) | c `elem` "#!" = [rest, c:rest]
+    dropModifier x = [x]
 
 -- Useful generic functions
 potentially :: Monad m => Maybe (m ()) -> m ()
@@ -628,5 +664,5 @@
     getCode (TokenComment _ (Comment _ c _)) = c
 
 
-return [] 
+return []
 runTests =  $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { 
maxSuccess = 1 }) ) |])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ShellCheck-0.4.4/ShellCheck/Checker.hs 
new/ShellCheck-0.4.5/ShellCheck/Checker.hs
--- old/ShellCheck-0.4.4/ShellCheck/Checker.hs  2016-05-15 04:06:47.000000000 
+0200
+++ new/ShellCheck-0.4.5/ShellCheck/Checker.hs  2016-10-21 23:19:54.000000000 
+0200
@@ -39,7 +39,7 @@
 
 tokenToPosition map (TokenComment id c) = fromMaybe fail $ do
     position <- Map.lookup id map
-    return $ PositionedComment position c
+    return $ PositionedComment position position c
   where
     fail = error "Internal shellcheck error: id doesn't exist. Please report!"
 
@@ -65,13 +65,13 @@
         return . nub . sortMessages . filter shouldInclude $
             (parseMessages ++ map translator analysisMessages)
 
-    shouldInclude (PositionedComment _ (Comment _ code _)) =
+    shouldInclude (PositionedComment _ _ (Comment _ code _)) =
         code `notElem` csExcludedWarnings spec
 
     sortMessages = sortBy (comparing order)
-    order (PositionedComment pos (Comment severity code message)) =
+    order (PositionedComment pos _ (Comment severity code message)) =
         (posFile pos, posLine pos, posColumn pos, severity, code, message)
-    getPosition (PositionedComment pos _) = pos
+    getPosition (PositionedComment pos _ _) = pos
 
     analysisSpec root =
         AnalysisSpec {
@@ -84,7 +84,7 @@
     sort . map getCode . crComments $
         runIdentity (checkScript sys spec)
   where
-    getCode (PositionedComment _ (Comment _ code _)) = code
+    getCode (PositionedComment _ _ (Comment _ code _)) = code
 
 check = checkWithIncludes []
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ShellCheck-0.4.4/ShellCheck/Checks/Commands.hs 
new/ShellCheck-0.4.5/ShellCheck/Checks/Commands.hs
--- old/ShellCheck-0.4.4/ShellCheck/Checks/Commands.hs  2016-05-15 
04:06:47.000000000 +0200
+++ new/ShellCheck-0.4.5/ShellCheck/Checks/Commands.hs  2016-10-21 
23:19:54.000000000 +0200
@@ -90,6 +90,8 @@
     ,checkExportedExpansions
     ,checkAliasesUsesArgs
     ,checkAliasesExpandEarly
+    ,checkUnsetGlobs
+    ,checkFindWithoutPath
     ]
 
 buildCommandMap :: [CommandCheck] -> Map.Map CommandName (Token -> Analysis)
@@ -150,7 +152,7 @@
             info (getId word) 2020 "tr replaces sets of chars, not words 
(mentioned due to duplicates)."
           unless ("[:" `isPrefixOf` s) $
             when ("[" `isPrefixOf` s && "]" `isSuffixOf` s && (length s > 2) 
&& ('*' `notElem` s)) $
-              info (getId word) 2021 "Don't use [] around ranges in tr, it 
replaces literal square brackets."
+              info (getId word) 2021 "Don't use [] around classes in tr, it 
replaces literal square brackets."
         Nothing -> return ()
 
     duplicated s =
@@ -470,16 +472,49 @@
 prop_checkPrintfVar2 = verifyNot checkPrintfVar "printf 'Lol: $s'"
 prop_checkPrintfVar3 = verify checkPrintfVar "printf -v cow $(cmd)"
 prop_checkPrintfVar4 = verifyNot checkPrintfVar "printf \"%${count}s\" var"
+prop_checkPrintfVar5 = verify checkPrintfVar "printf '%s %s %s' foo bar"
+prop_checkPrintfVar6 = verify checkPrintfVar "printf foo bar baz"
+prop_checkPrintfVar7 = verify checkPrintfVar "printf -- foo bar baz"
+prop_checkPrintfVar8 = verifyNot checkPrintfVar "printf '%s %s %s' 
\"${var[@]}\""
+prop_checkPrintfVar9 = verifyNot checkPrintfVar "printf '%s %s %s\\n' *.png"
+prop_checkPrintfVar10= verifyNot checkPrintfVar "printf '%s %s %s' foo bar baz"
 checkPrintfVar = CommandCheck (Exactly "printf") (f . arguments) where
+    f (doubledash:rest) | getLiteralString doubledash == Just "--" = f rest
     f (dashv:var:rest) | getLiteralString dashv == Just "-v" = f rest
-    f (format:params) = check format
+    f (format:params) = check format params
     f _ = return ()
-    check format =
+
+    countFormats string =
+        case string of
+            '%':'%':rest -> countFormats rest
+            '%':rest -> 1 + countFormats rest
+            _:rest -> countFormats rest
+            [] -> 0
+
+    check format more = do
+        fromMaybe (return ()) $ do
+            string <- getLiteralString format
+            let vars = countFormats string
+
+            return $ do
+                when (vars == 0 && more /= []) $
+                    err (getId format) 2182
+                        "This printf format string has no variables. Other 
arguments are ignored."
+
+                when (vars > 0
+                        && length more < vars
+                        && all (not . mayBecomeMultipleArgs) more) $
+                    warn (getId format) 2183 $
+                        "This format string has " ++ show vars ++ " variables, 
but is passed " ++ show (length more) ++ " arguments."
+
+
         unless ('%' `elem` concat (oversimplify format) || isLiteral format) $
-          warn (getId format) 2059
+          info (getId format) 2059
               "Don't use variables in the printf format string. Use printf 
\"..%s..\" \"$foo\"."
 
 
+
+
 prop_checkUuoeCmd1 = verify checkUuoeCmd "echo $(date)"
 prop_checkUuoeCmd2 = verify checkUuoeCmd "echo `date`"
 prop_checkUuoeCmd3 = verify checkUuoeCmd "echo \"$(date)\""
@@ -556,5 +591,33 @@
     checkArg _ = return ()
 
 
+prop_checkUnsetGlobs1 = verify checkUnsetGlobs "unset foo[1]"
+prop_checkUnsetGlobs2 = verifyNot checkUnsetGlobs "unset foo"
+checkUnsetGlobs = CommandCheck (Exactly "unset") (mapM_ check . arguments)
+  where
+    check arg =
+        when (isGlob arg) $
+            warn (getId arg) 2184 "Quote arguments to unset so they're not 
glob expanded."
+
+
+prop_checkFindWithoutPath1 = verify checkFindWithoutPath "find -type f"
+prop_checkFindWithoutPath2 = verify checkFindWithoutPath "find"
+prop_checkFindWithoutPath3 = verifyNot checkFindWithoutPath "find . -type f"
+prop_checkFindWithoutPath4 = verifyNot checkFindWithoutPath "find -H -L 
\"$path\" -print"
+checkFindWithoutPath = CommandCheck (Basename "find") f
+  where
+    f (T_SimpleCommand _ _ (cmd:args)) =
+        unless (hasPath args) $
+            info (getId cmd) 2185 "Some finds don't have a default path. 
Specify '.' explicitly."
+
+    -- This is a bit of a kludge. find supports flag arguments both before and 
after the path,
+    -- as well as multiple non-flag arguments that are not the path. We assume 
that all the
+    -- pre-path flags are single characters, which is generally the case.
+    hasPath (first:rest) =
+        let flag = fromJust $ getLiteralStringExt (const $ return "___") first 
in
+            not ("-" `isPrefixOf` flag) || length flag <= 2 && hasPath rest
+    hasPath [] = False
+
+
 return []
 runTests =  $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { 
maxSuccess = 1 }) ) |])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ShellCheck-0.4.4/ShellCheck/Formatter/Format.hs 
new/ShellCheck-0.4.5/ShellCheck/Formatter/Format.hs
--- old/ShellCheck-0.4.4/ShellCheck/Formatter/Format.hs 2016-05-15 
04:06:47.000000000 +0200
+++ new/ShellCheck-0.4.5/ShellCheck/Formatter/Format.hs 2016-10-21 
23:19:54.000000000 +0200
@@ -30,13 +30,15 @@
     footer :: IO ()
 }
 
-lineNo (PositionedComment pos _) = posLine pos
-colNo  (PositionedComment pos _) = posColumn pos
-codeNo (PositionedComment _ (Comment _ code _)) = code
-messageText (PositionedComment _ (Comment _ _ t)) = t
+lineNo (PositionedComment pos _ _) = posLine pos
+endLineNo (PositionedComment _ end _) = posLine end
+colNo  (PositionedComment pos _ _) = posColumn pos
+endColNo  (PositionedComment _ end _) = posColumn end
+codeNo (PositionedComment _ _ (Comment _ code _)) = code
+messageText (PositionedComment _ _ (Comment _ _ t)) = t
 
 severityText :: PositionedComment -> String
-severityText (PositionedComment _ (Comment c _ _)) =
+severityText (PositionedComment _ _ (Comment c _ _)) =
     case c of
         ErrorC   -> "error"
         WarningC -> "warning"
@@ -48,12 +50,15 @@
     map fix comments
   where
     ls = lines contents
-    fix c@(PositionedComment pos comment) = PositionedComment pos {
-        posColumn =
-            if lineNo c > 0 && lineNo c <= fromIntegral (length ls)
-            then real (ls !! fromIntegral (lineNo c - 1)) 0 0 (colNo c)
-            else colNo c
+    fix c@(PositionedComment start end comment) = PositionedComment start {
+        posColumn = realignColumn lineNo colNo c
+    } end {
+        posColumn = realignColumn endLineNo endColNo c
     } comment
+    realignColumn lineNo colNo c =
+      if lineNo c > 0 && lineNo c <= fromIntegral (length ls)
+      then real (ls !! fromIntegral (lineNo c - 1)) 0 0 (colNo c)
+      else colNo c
     real _ r v target | target <= v = r
     real [] r v _ = r -- should never happen
     real ('\t':rest) r v target =
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ShellCheck-0.4.4/ShellCheck/Formatter/JSON.hs 
new/ShellCheck-0.4.5/ShellCheck/Formatter/JSON.hs
--- old/ShellCheck-0.4.4/ShellCheck/Formatter/JSON.hs   2016-05-15 
04:06:47.000000000 +0200
+++ new/ShellCheck-0.4.5/ShellCheck/Formatter/JSON.hs   2016-10-21 
23:19:54.000000000 +0200
@@ -37,10 +37,12 @@
     }
 
 instance JSON (PositionedComment) where
-  showJSON comment@(PositionedComment pos (Comment level code string)) = 
makeObj [
-      ("file", showJSON $ posFile pos),
-      ("line", showJSON $ posLine pos),
-      ("column", showJSON $ posColumn pos),
+  showJSON comment@(PositionedComment start end (Comment level code string)) = 
makeObj [
+      ("file", showJSON $ posFile start),
+      ("line", showJSON $ posLine start),
+      ("endLine", showJSON $ posLine end),
+      ("column", showJSON $ posColumn start),
+      ("endColumn", showJSON $ posColumn end),
       ("level", showJSON $ severityText comment),
       ("code", showJSON code),
       ("message", showJSON string)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ShellCheck-0.4.4/ShellCheck/Interface.hs 
new/ShellCheck-0.4.5/ShellCheck/Interface.hs
--- old/ShellCheck-0.4.4/ShellCheck/Interface.hs        2016-05-15 
04:06:47.000000000 +0200
+++ new/ShellCheck-0.4.5/ShellCheck/Interface.hs        2016-10-21 
23:19:54.000000000 +0200
@@ -94,7 +94,7 @@
 } deriving (Show, Eq)
 
 data Comment = Comment Severity Code String deriving (Show, Eq)
-data PositionedComment = PositionedComment Position Comment deriving (Show, Eq)
+data PositionedComment = PositionedComment Position Position Comment deriving 
(Show, Eq)
 data TokenComment = TokenComment Id Comment deriving (Show, Eq)
 
 data ColorOption =
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ShellCheck-0.4.4/ShellCheck/Parser.hs 
new/ShellCheck-0.4.5/ShellCheck/Parser.hs
--- old/ShellCheck-0.4.4/ShellCheck/Parser.hs   2016-05-15 04:06:47.000000000 
+0200
+++ new/ShellCheck-0.4.5/ShellCheck/Parser.hs   2016-10-21 23:19:54.000000000 
+0200
@@ -135,7 +135,7 @@
 
 --------- Message/position annotation on top of user state
 data Note = Note Id Severity Code String deriving (Show, Eq)
-data ParseNote = ParseNote SourcePos Severity Code String deriving (Show, Eq)
+data ParseNote = ParseNote SourcePos SourcePos Severity Code String deriving 
(Show, Eq)
 data Context =
         ContextName SourcePos String
         | ContextAnnotation [Annotation]
@@ -162,9 +162,9 @@
     pendingHereDocs = []
 }
 
-codeForParseNote (ParseNote _ _ code _) = code
+codeForParseNote (ParseNote _ _ _ code _) = code
 noteToParseNote map (Note id severity code message) =
-        ParseNote pos severity code message
+        ParseNote pos pos severity code message
     where
         pos = fromJust $ Map.lookup id map
 
@@ -181,6 +181,7 @@
     return newId
   where incId (Id n) = Id $ n+1
 
+getNextId :: Monad m => SCParser m Id
 getNextId = do
     pos <- getPosition
     getNextIdAt pos
@@ -320,14 +321,16 @@
     v <- getCurrentContexts
     setCurrentContexts (c:v)
 
-parseProblemAt pos level code msg = do
+parseProblemAtWithEnd start end level code msg = do
     irrelevant <- shouldIgnoreCode code
     unless irrelevant $
         Ms.modify (\state -> state {
             parseProblems = note:parseProblems state
         })
   where
-    note = ParseNote pos level code msg
+    note = ParseNote start end level code msg
+
+parseProblemAt pos = parseProblemAtWithEnd pos pos
 
 -- Store non-parse problems inside
 
@@ -335,7 +338,9 @@
     pos <- getPosition
     parseNoteAt pos c l a
 
-parseNoteAt pos c l a = addParseNote $ ParseNote pos c l a
+parseNoteAt pos c l a = addParseNote $ ParseNote pos pos c l a
+
+parseNoteAtWithEnd start end c l a = addParseNote $ ParseNote start end c l a
 
 --------- Convenient combinators
 thenSkip main follow = do
@@ -406,7 +411,7 @@
                                 pos <- getPosition
                                 s <- many1 letter
                                 when (s `elem` commonCommands) $
-                                    parseProblemAt pos WarningC 1009 "Use 'if 
cmd; then ..' to check exit code, or 'if [[ $(cmd) == .. ]]' to check output.")
+                                    parseProblemAt pos WarningC 1014 "Use 'if 
cmd; then ..' to check exit code, or 'if [[ $(cmd) == .. ]]' to check output.")
 
   where
     spacingOrLf = condSpacing True
@@ -442,7 +447,7 @@
             c <- oneOf "'\""
             s <- anyOp
             char c
-            return s
+            return $ escaped s
 
         anyOp = flagOp <|> flaglessOp <|> fail
                     "Expected comparison operator (don't wrap commands in 
[]/[[]])"
@@ -645,6 +650,7 @@
 prop_a20= isOk readArithmeticContents "a ? b ? c : d : e"
 prop_a21= isOk readArithmeticContents "a ? b : c ? d : e"
 prop_a22= isOk readArithmeticContents "!!a"
+readArithmeticContents :: Monad m => SCParser m Token
 readArithmeticContents =
     readSequence
   where
@@ -658,12 +664,40 @@
         id <- getNextId
         op <- choice (map (\x -> try $ do
                                         s <- string x
-                                        notFollowedBy2 $ oneOf "&|<>="
+                                        failIfIncompleteOp
                                         return s
                             ) op)
         spacing
         return $ token id op
 
+    failIfIncompleteOp = notFollowedBy2 $ oneOf "&|<>="
+
+    -- Read binary minus, but also check for -lt, -gt and friends:
+    readMinusOp = do
+        id <- getNextId
+        pos <- getPosition
+        try $ do
+            char '-'
+            failIfIncompleteOp
+        optional $ do
+            (str, alt) <- lookAhead . choice $ map tryOp [
+                ("lt", "<"),
+                ("gt", ">"),
+                ("le", "<="),
+                ("ge", ">="),
+                ("eq", "=="),
+                ("ne", "!=")
+              ]
+            parseProblemAt pos ErrorC 1106 $ "In arithmetic contexts, use " ++ 
alt ++ " instead of -" ++ str
+        spacing
+        return $ TA_Binary id "-"
+      where
+        tryOp (str, alt) = try $ do
+            string str
+            spacing1
+            return (str, alt)
+
+
     readArrayIndex = do
         id <- getNextId
         char '['
@@ -733,7 +767,7 @@
     readEquated = readCompared `splitBy` ["==", "!="]
     readCompared = readShift `splitBy` ["<=", ">=", "<", ">"]
     readShift = readAddition `splitBy` ["<<", ">>"]
-    readAddition = readMultiplication `splitBy` ["+", "-"]
+    readAddition = chainl1 readMultiplication (readBinary ["+"] <|> 
readMinusOp)
     readMultiplication = readExponential `splitBy` ["*", "/", "%"]
     readExponential = readAnyNegated `splitBy` ["**"]
 
@@ -810,7 +844,7 @@
     pos <- getPosition
     space <- allspacing
     when (null space) $
-        parseProblemAt pos ErrorC 1035 $ "You need a space after the " ++
+        parseProblemAtWithEnd opos pos ErrorC 1035 $ "You need a space after 
the " ++
             if single
                 then "[ and before the ]."
                 else "[[ and before the ]]."
@@ -835,10 +869,11 @@
 prop_readAnnotation1 = isOk readAnnotation "# shellcheck disable=1234,5678\n"
 prop_readAnnotation2 = isOk readAnnotation "# shellcheck disable=SC1234 
disable=SC5678\n"
 prop_readAnnotation3 = isOk readAnnotation "# shellcheck disable=SC1234 
source=/dev/null disable=SC5678\n"
+prop_readAnnotation4 = isWarning readAnnotation "# shellcheck cats=dogs 
disable=SC1234\n"
 readAnnotation = called "shellcheck annotation" $ do
     try readAnnotationPrefix
     many1 linewhitespace
-    values <- many1 (readDisable <|> readSourceOverride <|> readShellOverride)
+    values <- many1 (readDisable <|> readSourceOverride <|> readShellOverride 
<|> anyKey)
     linefeed
     many linewhitespace
     return $ concat values
@@ -870,6 +905,13 @@
         many linewhitespace
         return value
 
+    anyKey = do
+        pos <- getPosition
+        anyChar `reluctantlyTill1` whitespace
+        many linewhitespace
+        parseNoteAt pos WarningC 1107 "This directive is unknown. It will be 
ignored."
+        return []
+
 readAnnotations = do
     annotations <- many (readAnnotation `thenSkip` allspacing)
     return $ concat annotations
@@ -897,6 +939,20 @@
     checkPossibleTermination pos x
     return $ T_NormalWord id x
 
+readIndexSpan = do
+    id <- getNextId
+    x <- many (readNormalWordPart "]" <|> someSpace <|> otherLiteral)
+    return $ T_NormalWord id x
+  where
+    someSpace = do
+        id <- getNextId
+        str <- spacing1
+        return $ T_Literal id str
+    otherLiteral = do
+        id <- getNextId
+        str <- many1 $ oneOf quotableChars
+        return $ T_Literal id str
+
 checkPossibleTermination pos [T_Literal _ x] =
     when (x `elem` ["do", "done", "then", "fi", "esac"]) $
         parseProblemAt pos WarningC 1010 $ "Use semicolon or linefeed before 
'" ++ x ++ "' (or quote to make it literal)."
@@ -1060,13 +1116,18 @@
     setPosition lastPosition
     return result
 
-inSeparateContext parser = do
+-- Parse something, but forget all parseProblems
+inSeparateContext = parseForgettingContext True
+-- Parse something, but forget all parseProblems on failure
+forgetOnFailure = parseForgettingContext False
+
+parseForgettingContext alsoOnSuccess parser = do
     context <- Ms.get
     success context <|> failure context
   where
     success c = do
         res <- try parser
-        Ms.put c
+        when alsoOnSuccess $ Ms.put c
         return res
     failure c = do
         Ms.put c
@@ -1303,7 +1364,17 @@
 
 prop_readDollarExpression1 = isOk readDollarExpression "$(((1) && 3))"
 prop_readDollarExpression2 = isWarning readDollarExpression "$(((1)) && 3)"
-readDollarExpression = readTripleParenthesis "$" readDollarArithmetic 
readDollarExpansion <|> readDollarArithmetic <|> readDollarBracket <|> 
readDollarBraceCommandExpansion <|> readDollarBraced <|> readDollarExpansion 
<|> readDollarVariable
+prop_readDollarExpression3 = isWarning readDollarExpression "$((\"$@\" &); 
foo;)"
+readDollarExpression :: Monad m => SCParser m Token
+readDollarExpression = do
+    -- The grammar should have been designed along the lines of readDollarExpr 
= char '$' >> stuff, but
+    -- instead, each subunit parses its own $. This results in ~7 1-3 char 
lookaheads instead of one 1-char.
+    -- Instead of optimizing the grammar, here's a green cut that decreases 
shellcheck runtime by 10%:
+    lookAhead $ char '$'
+    arithmetic <|> readDollarExpansion <|> readDollarBracket <|> 
readDollarBraceCommandExpansion <|> readDollarBraced <|> readDollarVariable
+  where
+    arithmetic = readAmbiguous "$((" readDollarArithmetic readDollarExpansion 
(\pos ->
+        parseNoteAt pos WarningC 1102 "Shells disambiguate $(( differently or 
not at all. For $(command substition), add space after $( . For 
$((arithmetics)), fix parsing errors.")
 
 prop_readDollarSingleQuote = isOk readDollarSingleQuote "$'foo\\\'lol'"
 readDollarSingleQuote = called "$'..' expression" $ do
@@ -1349,25 +1420,20 @@
     string "))"
     return (T_Arithmetic id c)
 
--- Check if maybe ((( was intended as ( (( rather than (( (
-readTripleParenthesis prefix expected alternative = do
-    pos <- try . lookAhead $ do
-        string prefix
-        p <- getPosition
-        string "(((" -- should optimally be "((" but it's noisy and rarely 
helpful
-        return p
-
+-- If the next characters match prefix, try two different parsers and warn if 
the alternate parser had to be used
+readAmbiguous :: Monad m => String -> SCParser m p -> SCParser m p -> 
(SourcePos -> SCParser m ()) -> SCParser m p
+readAmbiguous prefix expected alternative warner = do
+    pos <- getPosition
+    try . lookAhead $ string prefix
     -- If the expected parser fails, try the alt.
     -- If the alt fails, run the expected one again for the errors.
-    try expected <|> tryAlt pos <|> expected
+    try expected <|> try (withAlt pos) <|> expected
   where
-    tryAlt pos = do
-        t <- try alternative
-        parseNoteAt pos WarningC 1102 $
-            "Shells differ in parsing ambiguous " ++ prefix ++ "(((. Use 
spaces: " ++ prefix ++ "( (( ."
+    withAlt pos = do
+        t <- forgetOnFailure alternative
+        warner pos
         return t
 
-
 prop_readDollarBraceCommandExpansion1 = isOk readDollarBraceCommandExpansion 
"${ ls; }"
 prop_readDollarBraceCommandExpansion2 = isOk readDollarBraceCommandExpansion 
"${\nls\n}"
 readDollarBraceCommandExpansion = called "ksh ${ ..; } command expansion" $ do
@@ -1481,7 +1547,7 @@
     -- add empty tokens for now, read the rest in readPendingHereDocs
     let doc = T_HereDoc hid dashed quoted endToken []
     addPendingHereDoc doc
-    return $ T_FdRedirect fid "" doc
+    return doc
   where
     stripLiteral (T_Literal _ x) = x
     stripLiteral (T_SingleQuoted _ x) = x
@@ -1552,7 +1618,13 @@
 
 
 readFilename = readNormalWord
-readIoFileOp = choice [g_LESSAND, g_GREATAND, g_DGREAT, g_LESSGREAT, 
g_CLOBBER, redirToken '<' T_Less, redirToken '>' T_Greater ]
+readIoFileOp = choice [g_DGREAT, g_LESSGREAT, g_GREATAND, g_LESSAND, 
g_CLOBBER, redirToken '<' T_Less, redirToken '>' T_Greater ]
+
+readIoDuplicate = try $ do
+    id <- getNextId
+    op <- g_GREATAND <|> g_LESSAND
+    target <- readIoVariable <|> many1 digit <|> string "-"
+    return $ T_IoDuplicate id op target
 
 prop_readIoFile = isOk readIoFile ">> \"$(date +%YYmmDD)\""
 readIoFile = called "redirection" $ do
@@ -1560,35 +1632,31 @@
     op <- readIoFileOp
     spacing
     file <- readFilename
-    return $ T_FdRedirect id "" $ T_IoFile id op file
+    return $ T_IoFile id op file
 
 readIoVariable = try $ do
     char '{'
     x <- readVariableName
     char '}'
-    lookAhead readIoFileOp
     return $ "{" ++ x ++ "}"
 
-readIoNumber = try $ do
-    x <- many1 digit <|> string "&"
-    lookAhead readIoFileOp
+readIoSource = try $ do
+    x <- string "&" <|> readIoVariable <|> many digit
+    lookAhead $ void readIoFileOp <|> void (string "<<")
     return x
 
-prop_readIoNumberRedirect = isOk readIoNumberRedirect "3>&2"
-prop_readIoNumberRedirect2 = isOk readIoNumberRedirect "2> lol"
-prop_readIoNumberRedirect3 = isOk readIoNumberRedirect "4>&-"
-prop_readIoNumberRedirect4 = isOk readIoNumberRedirect "&> lol"
-prop_readIoNumberRedirect5 = isOk readIoNumberRedirect "{foo}>&2"
-prop_readIoNumberRedirect6 = isOk readIoNumberRedirect "{foo}<&-"
-readIoNumberRedirect = do
-    id <- getNextId
-    n <- readIoVariable <|> readIoNumber
-    op <- readHereString <|> readHereDoc <|> readIoFile
-    let actualOp = case op of T_FdRedirect _ "" x -> x
+prop_readIoRedirect = isOk readIoRedirect "3>&2"
+prop_readIoRedirect2 = isOk readIoRedirect "2> lol"
+prop_readIoRedirect3 = isOk readIoRedirect "4>&-"
+prop_readIoRedirect4 = isOk readIoRedirect "&> lol"
+prop_readIoRedirect5 = isOk readIoRedirect "{foo}>&2"
+prop_readIoRedirect6 = isOk readIoRedirect "{foo}<&-"
+readIoRedirect = do
+    id <- getNextId
+    n <- readIoSource
+    redir <- readHereString <|> readHereDoc <|> readIoDuplicate <|> readIoFile
     spacing
-    return $ T_FdRedirect id n actualOp
-
-readIoRedirect = choice [ readIoNumberRedirect, readHereString, readHereDoc, 
readIoFile ] `thenSkip` spacing
+    return $ T_FdRedirect id n redir
 
 readRedirectList = many1 readIoRedirect
 
@@ -1599,7 +1667,7 @@
     spacing
     id2 <- getNextId
     word <- readNormalWord
-    return $ T_FdRedirect id "" $ T_HereString id2 word
+    return $ T_HereString id2 word
 
 readNewlineList = many1 ((linefeed <|> carriageReturn) `thenSkip` spacing)
 readLineBreak = optional readNewlineList
@@ -1878,7 +1946,7 @@
     pos <- getPosition
     correctElif <- elif
     unless correctElif $
-        parseProblemAt pos ErrorC 1075 "Use 'elif' instead of 'else if'."
+        parseProblemAt pos ErrorC 1075 "Use 'elif' instead of 'else if' (or 
put 'if' on new line if nesting)."
     allspacing
     condition <- readTerm
 
@@ -2176,8 +2244,8 @@
     id <- getNextId
     cmd <- choice [
         readBraceGroup,
-        readTripleParenthesis "" readArithmeticExpression readSubshell,
-        readArithmeticExpression,
+        readAmbiguous "((" readArithmeticExpression readSubshell (\pos ->
+            parseNoteAt pos WarningC 1105 "Shells disambiguate (( differently 
or not at all. For subshell, add spaces around ( . For ((, fix parsing 
errors."),
         readSubshell,
         readCondition,
         readWhileClause,
@@ -2251,7 +2319,7 @@
 
 -- Get whatever a parser would parse as a string
 readStringForParser parser = do
-    pos <- lookAhead (parser >> getPosition)
+    pos <- inSeparateContext $ lookAhead (parser >> getPosition)
     readUntil pos
   where
     readUntil endPos = anyChar `reluctantlyTill` (getPosition >>= guard . (== 
endPos))
@@ -2278,7 +2346,7 @@
     variable <- readVariableName
     optional (readNormalDollar >> parseNoteAt pos ErrorC
                                 1067 "For indirection, use (associative) 
arrays or 'read \"var$n\" <<< \"value\"'")
-    index <- optionMaybe readArrayIndex
+    indices <- many readArrayIndex
     hasLeftSpace <- liftM (not . null) spacing
     pos <- getPosition
     op <- readAssignmentOp
@@ -2290,13 +2358,13 @@
             parseNoteAt pos WarningC 1007
                 "Remove space after = if trying to assign a value (for empty 
string, use var='' ... )."
         value <- readEmptyLiteral
-        return $ T_Assignment id op variable index value
+        return $ T_Assignment id op variable indices value
       else do
         when (hasLeftSpace || hasRightSpace) $
             parseNoteAt pos ErrorC 1068 "Don't put spaces around the = in 
assignments."
         value <- readArray <|> readNormalWord
         spacing
-        return $ T_Assignment id op variable index value
+        return $ T_Assignment id op variable indices value
   where
     readAssignmentOp = do
         pos <- getPosition
@@ -2316,12 +2384,14 @@
         return $ T_Literal id ""
 
 readArrayIndex = do
+    id <- getNextId
     char '['
-    optional space
-    x <- readArithmeticContents
+    pos <- getPosition
+    str <- readStringForParser readIndexSpan
     char ']'
-    return x
+    return $ T_UnparsedIndex id pos str
 
+readArray :: Monad m => SCParser m Token
 readArray = called "array assignment" $ do
     id <- getNextId
     char '('
@@ -2334,7 +2404,7 @@
     readIndexed = do
         id <- getNextId
         index <- try $ do
-            x <- readArrayIndex
+            x <- many1 readArrayIndex
             char '='
             return x
         value <- readNormalWord <|> nothing
@@ -2477,12 +2547,12 @@
         try (lookAhead p)
         action
 
-prop_readScript1 = isOk readScript "#!/bin/bash\necho hello world\n"
-prop_readScript2 = isWarning readScript "#!/bin/bash\r\necho hello world\n"
-prop_readScript3 = isWarning readScript "#!/bin/bash\necho hello\xA0world"
-prop_readScript4 = isWarning readScript "#!/usr/bin/perl\nfoo=("
-prop_readScript5 = isOk readScript "#!/bin/bash\n#This is an empty script\n\n"
-readScript = do
+prop_readScript1 = isOk readScriptFile "#!/bin/bash\necho hello world\n"
+prop_readScript2 = isWarning readScriptFile "#!/bin/bash\r\necho hello world\n"
+prop_readScript3 = isWarning readScriptFile "#!/bin/bash\necho hello\xA0world"
+prop_readScript4 = isWarning readScriptFile "#!/usr/bin/perl\nfoo=("
+prop_readScript5 = isOk readScriptFile "#!/bin/bash\n#This is an empty 
script\n\n"
+readScriptFile = do
     id <- getNextId
     pos <- getPosition
     optional $ do
@@ -2497,7 +2567,8 @@
             annotations <- readAnnotations
             commands <- withAnnotations annotations readCompoundListOrEmpty
             verifyEof
-            return $ T_Annotation annotationId annotations $  T_Script id sb 
commands
+            let script = T_Annotation annotationId annotations $  T_Script id 
sb commands
+            reparseIndices script
         else do
             many anyChar
             return $ T_Script id sb []
@@ -2549,6 +2620,9 @@
 
     readUtf8Bom = called "Byte Order Mark" $ string "\xFEFF"
 
+readScript = do
+    script <- readScriptFile
+    reparseIndices script
 
 isWarning p s = parsesCleanly p s == Just False
 isOk p s =      parsesCleanly p s == Just True
@@ -2571,13 +2645,15 @@
     state <- getState
     return (item, state)
 
-compareNotes (ParseNote pos1 level1 _ s1) (ParseNote pos2 level2 _ s2) = 
compare (pos1, level1) (pos2, level2)
+compareNotes (ParseNote pos1 pos1' level1 _ s1) (ParseNote pos2 pos2' level2 _ 
s2) = compare (pos1, pos1', level1) (pos2, pos2', level2)
 sortNotes = sortBy compareNotes
 
 
 makeErrorFor parsecError =
-    ParseNote (errorPos parsecError) ErrorC 1072 $
+    ParseNote pos pos ErrorC 1072 $
         getStringFromParsec $ errorMessages parsecError
+    where
+      pos = errorPos parsecError
 
 getStringFromParsec errors =
         case map f errors of
@@ -2630,11 +2706,46 @@
     isName (ContextName _ _) = True
     isName _ = False
     notesForContext list = zipWith ($) [first, second] $ filter isName list
-    first (ContextName pos str) = ParseNote pos ErrorC 1073 $
+    first (ContextName pos str) = ParseNote pos pos ErrorC 1073 $
         "Couldn't parse this " ++ str ++ "."
-    second (ContextName pos str) = ParseNote pos InfoC 1009 $
+    second (ContextName pos str) = ParseNote pos pos InfoC 1009 $
         "The mentioned parser error was in this " ++ str ++ "."
 
+-- Go over all T_UnparsedIndex and reparse them as either arithmetic or text
+-- depending on declare -A statements.
+reparseIndices root =
+   analyze blank blank f root
+  where
+    associative = getAssociativeArrays root
+    isAssociative s = s `elem` associative
+    f (T_Assignment id mode name indices value) = do
+        newIndices <- mapM (fixAssignmentIndex name) indices
+        newValue <- case value of
+            (T_Array id2 words) -> do
+                newWords <- mapM (fixIndexElement name) words
+                return $ T_Array id2 newWords
+            x -> return x
+        return $ T_Assignment id mode name newIndices newValue
+    f t = return t
+
+    fixIndexElement name word =
+        case word of
+            T_IndexedElement id indices value -> do
+                new <- mapM (fixAssignmentIndex name) indices
+                return $ T_IndexedElement id new value
+            otherwise -> return word
+
+    fixAssignmentIndex name word =
+        case word of
+            T_UnparsedIndex id pos src -> do
+                parsed name pos src
+            otherwise -> return word
+
+    parsed name pos src =
+        if isAssociative name
+        then subParse pos (called "associative array index" $ readIndexSpan) 
src
+        else subParse pos (called "arithmetic array index expression" $ 
optional space >> readArithmeticContents) src
+
 reattachHereDocs root map =
     doTransform f root
   where
@@ -2644,8 +2755,8 @@
     f t = t
 
 toPositionedComment :: ParseNote -> PositionedComment
-toPositionedComment (ParseNote pos severity code message) =
-    PositionedComment (posToPos pos) $ Comment severity code message
+toPositionedComment (ParseNote start end severity code message) =
+    PositionedComment (posToPos start) (posToPos end) $ Comment severity code 
message
 
 posToPos :: SourcePos -> Position
 posToPos sp = Position {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ShellCheck-0.4.4/ShellCheck.cabal 
new/ShellCheck-0.4.5/ShellCheck.cabal
--- old/ShellCheck-0.4.4/ShellCheck.cabal       2016-05-15 04:06:47.000000000 
+0200
+++ new/ShellCheck-0.4.5/ShellCheck.cabal       2016-10-21 23:19:54.000000000 
+0200
@@ -1,5 +1,5 @@
 Name:             ShellCheck
-Version:          0.4.4
+Version:          0.4.5
 Synopsis:         Shell script analysis tool
 License:          GPL-3
 License-file:     LICENSE


Reply via email to