diff --git a/ShellCheck/Analytics.hs b/ShellCheck/Analytics.hs index 4766355..a5e6ad8 100644 --- a/ShellCheck/Analytics.hs +++ b/ShellCheck/Analytics.hs @@ -54,35 +54,22 @@ checksFor Bash = map runBasicAnalysis [ runAllAnalytics root m = addToMap notes m where shell = determineShell root - unsupported = (getId root, Note ErrorC "ShellCheck only handles Bourne based shells, sorry!") - notes = case shell of - Nothing -> [ unsupported ] - Just sh -> checkList ((checksFor sh) ++ genericChecks) root + notes = checkList ((checksFor shell) ++ genericChecks) root checkList l t = concatMap (\f -> f t) l addToMap list map = foldr (\(id,note) m -> Map.adjust (\(Metadata pos notes) -> Metadata pos (note:notes)) id m) map list -prop_determineShell0 = determineShell (T_Script (Id 0) "#!/bin/sh" []) == Just Sh -prop_determineShell1 = determineShell (T_Script (Id 0) "#!/usr/bin/env ksh" []) == Just Ksh -prop_determineShell2 = determineShell (T_Script (Id 0) "" []) == Just Bash +prop_determineShell0 = determineShell (T_Script (Id 0) "#!/bin/sh" []) == Sh +prop_determineShell1 = determineShell (T_Script (Id 0) "#!/usr/bin/env ksh" []) == Ksh +prop_determineShell2 = determineShell (T_Script (Id 0) "" []) == Bash determineShell (T_Script _ shebang _) = normalize $ shellFor shebang where shellFor s | "/env " `isInfixOf` s = head ((drop 1 $ words s)++[""]) shellFor s = reverse . takeWhile (/= '/') . reverse $ s - normalize "csh" = Nothing - normalize "tcsh" = Nothing - normalize "sh" = return Sh - normalize "ksh" = return Ksh - normalize "zsh" = return Zsh - normalize "bash" = return Bash - normalize x | any (`isPrefixOf` x) [ - "csh" - ,"tcsh" - ,"perl" - ,"awk" - ,"python" - ,"ruby" - ] = Nothing - normalize _ = return Bash + normalize "sh" = Sh + normalize "ksh" = Ksh + normalize "zsh" = Zsh + normalize "bash" = Bash + normalize _ = Bash runBasicAnalysis f t = snd $ runState (doAnalysis f t) [] basicChecks = [ diff --git a/ShellCheck/Parser.hs b/ShellCheck/Parser.hs index 74394a6..58b0c1b 100644 --- a/ShellCheck/Parser.hs +++ b/ShellCheck/Parser.hs @@ -24,7 +24,7 @@ import Text.Parsec import Debug.Trace import Control.Monad import Data.Char -import Data.List (isInfixOf, isSuffixOf, partition, sortBy, intercalate, nub) +import Data.List (isPrefixOf, isInfixOf, isSuffixOf, partition, sortBy, intercalate, nub) import qualified Data.Map as Map import qualified Control.Monad.State as Ms import Data.Maybe @@ -1558,18 +1558,68 @@ readShebang = do 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=(" readScript = do id <- getNextId + pos <- getPosition sb <- option "" readShebang - do { - allspacing; - commands <- readTerm; - eof <|> (parseProblem ErrorC "Parsing stopped here because of parsing errors."); - return $ T_Script id sb commands; - } <|> do { - parseProblem WarningC "Couldn't read any commands."; + verifyShell pos (getShell sb) + if (isValidShell $ getShell sb) /= Just False + then + do { + allspacing; + commands <- readTerm; + eof <|> (parseProblem ErrorC "Parsing stopped here because of parsing errors."); + return $ T_Script id sb commands; + } <|> do { + parseProblem WarningC "Couldn't read any commands."; + return $ T_Script id sb $ [T_EOF id]; + } + else do + many anyChar return $ T_Script id sb $ [T_EOF id]; - } + + where + basename s = reverse . takeWhile (/= '/') . reverse $ s + getShell sb = + case words sb of + [] -> "" + [x] -> basename x + (first:second:_) -> + if basename first == "env" + then second + else basename first + + verifyShell pos s = + case isValidShell s of + Just True -> return () + Just False -> parseProblemAt pos ErrorC "ShellCheck only supports Bourne based shell scripts, sorry!" + Nothing -> parseProblemAt pos InfoC "This shebang was unrecognized. Note that ShellCheck only handles Bourne based shells." + + isValidShell s = + let good = s == "" || any (`isPrefixOf` s) goodShells + bad = any (`isPrefixOf` s) badShells + in + if good + then Just True + else if bad + then Just False + else Nothing + + goodShells = [ + "sh", + "bash", + "ksh", + "zsh" + ] + badShells = [ + "awk", + "csh", + "perl", + "python", + "ruby", + "tcsh" + ] rp p filename contents = Ms.runState (runParserT p initialState filename contents) ([], [])