diff --git a/ShellCheck/ASTLib.hs b/ShellCheck/ASTLib.hs index 635acf0..dc58a12 100644 --- a/ShellCheck/ASTLib.hs +++ b/ShellCheck/ASTLib.hs @@ -112,6 +112,7 @@ getFlagsUntil stopCondition (T_SimpleCommand _ _ (_:args)) = getFlagsUntil _ _ = error "Internal shellcheck error, please report! (getFlags on non-command)" -- Get all flags in a GNU way, up until -- +getAllFlags :: Token -> [(Token, String)] getAllFlags = getFlagsUntil (== "--") -- Get all flags in a BSD way, up until first non-flag argument or -- getLeadingFlags = getFlagsUntil (\x -> x == "--" || (not $ "-" `isPrefixOf` x)) diff --git a/ShellCheck/Analytics.hs b/ShellCheck/Analytics.hs index ff00922..4bc3d21 100644 --- a/ShellCheck/Analytics.hs +++ b/ShellCheck/Analytics.hs @@ -845,6 +845,7 @@ prop_checkUnquotedN = verify checkUnquotedN "if [ -n $foo ]; then echo cow; fi" prop_checkUnquotedN2 = verify checkUnquotedN "[ -n $cow ]" prop_checkUnquotedN3 = verifyNot checkUnquotedN "[[ -n $foo ]] && echo cow" prop_checkUnquotedN4 = verify checkUnquotedN "[ -n $cow -o -t 1 ]" +prop_checkUnquotedN5 = verifyNot checkUnquotedN "[ -n \"$@\" ]" checkUnquotedN _ (TC_Unary _ SingleBracket "-n" (T_NormalWord id [t])) | willSplit t = err id 2070 "-n doesn't work with unquoted arguments. Quote or use [[ ]]." checkUnquotedN _ _ = return () diff --git a/ShellCheck/Checks/Commands.hs b/ShellCheck/Checks/Commands.hs index c02e8be..63d3258 100644 --- a/ShellCheck/Checks/Commands.hs +++ b/ShellCheck/Checks/Commands.hs @@ -88,6 +88,7 @@ commandChecks = [ ,checkWhileGetoptsCase ,checkCatastrophicRm ,checkLetUsage + ,checkMvArguments, checkCpArguments, checkLnArguments ] buildCommandMap :: [CommandCheck] -> Map.Map CommandName (Token -> Analysis) @@ -850,5 +851,41 @@ checkLetUsage = CommandCheck (Exactly "let") f f t = whenShell [Bash,Ksh] $ do style (getId t) 2219 $ "Instead of 'let expr', prefer (( expr )) ." + +missingDestination handler token = do + case params of + [single] -> do + unless (hasTarget || mayBecomeMultipleArgs single) $ + handler token + _ -> return () + where + args = getAllFlags token + params = map fst $ filter (\(_,x) -> x == "") args + hasTarget = + any (\x -> x /= "" && x `isPrefixOf` "target-directory") $ + map snd args + +prop_checkMvArguments1 = verify checkMvArguments "mv 'foo bar'" +prop_checkMvArguments2 = verifyNot checkMvArguments "mv foo bar" +prop_checkMvArguments3 = verifyNot checkMvArguments "mv 'foo bar'{,bak}" +prop_checkMvArguments4 = verifyNot checkMvArguments "mv \"$@\"" +prop_checkMvArguments5 = verifyNot checkMvArguments "mv -t foo bar" +prop_checkMvArguments6 = verifyNot checkMvArguments "mv --target-directory=foo bar" +prop_checkMvArguments7 = verifyNot checkMvArguments "mv --target-direc=foo bar" +prop_checkMvArguments8 = verifyNot checkMvArguments "mv --version" +prop_checkMvArguments9 = verifyNot checkMvArguments "mv \"${!var}\"" +checkMvArguments = CommandCheck (Basename "mv") $ missingDestination f + where + f t = err (getId t) 2224 "This mv has no destination. Check the arguments." + +checkCpArguments = CommandCheck (Basename "cp") $ missingDestination f + where + f t = err (getId t) 2225 "This cp has no destination. Check the arguments." + +checkLnArguments = CommandCheck (Basename "ln") $ missingDestination f + where + f t = warn (getId t) 2226 "This ln has no destination. Check the arguments, or specify '.' explicitly." + + return [] runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])