Added -s flag to override dialect, e.g. -s ksh

This commit is contained in:
Vidar Holen 2014-02-02 19:28:09 -08:00
parent 075d58ee90
commit 4968e7d9ff
3 changed files with 57 additions and 33 deletions

View File

@ -15,7 +15,7 @@
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
-} -}
module ShellCheck.Analytics (AnalysisOption(..), filterByAnnotation, runAnalytics) where module ShellCheck.Analytics (AnalysisOption(..), filterByAnnotation, runAnalytics, shellForExecutable) where
import ShellCheck.AST import ShellCheck.AST
import ShellCheck.Data import ShellCheck.Data
@ -41,7 +41,7 @@ data Parameters = Parameters {
shellType :: Shell shellType :: Shell
} }
data AnalysisOption = NotImplemented data AnalysisOption = ForceShell Shell
-- Checks that are run on the AST root -- Checks that are run on the AST root
treeChecks :: [Parameters -> Token -> [Note]] treeChecks :: [Parameters -> Token -> [Note]]
@ -75,36 +75,41 @@ checksFor Bash = [
] ]
runAnalytics :: [AnalysisOption] -> Token -> [Note] runAnalytics :: [AnalysisOption] -> Token -> [Note]
runAnalytics options root = runList root treeChecks runAnalytics options root = runList options root treeChecks
runList root list = notes runList options root list = notes
where where
params = Parameters { params = Parameters {
shellType = determineShell root, shellType = getShellOption,
parentMap = getParentTree root, parentMap = getParentTree root,
variableFlow = getVariableFlow (shellType params) (parentMap params) root variableFlow = getVariableFlow (shellType params) (parentMap params) root
} }
notes = concatMap (\f -> f params root) list notes = concatMap (\f -> f params root) list
getShellOption =
fromMaybe (determineShell root) . msum $
map ((\option ->
case option of
ForceShell x -> return x
)) options
checkList l t = concatMap (\f -> f t) l checkList l t = concatMap (\f -> f t) l
prop_determineShell0 = determineShell (T_Script (Id 0) "#!/bin/sh" []) == Sh prop_determineShell0 = determineShell (T_Script (Id 0) "#!/bin/sh" []) == Sh
prop_determineShell1 = determineShell (T_Script (Id 0) "#!/usr/bin/env ksh" []) == Ksh prop_determineShell1 = determineShell (T_Script (Id 0) "#!/usr/bin/env ksh" []) == Ksh
prop_determineShell2 = determineShell (T_Script (Id 0) "" []) == Bash prop_determineShell2 = determineShell (T_Script (Id 0) "" []) == Bash
determineShell (T_Script _ shebang _) = normalize $ shellFor shebang determineShell (T_Script _ shebang _) = fromMaybe Bash . shellForExecutable $ shellFor shebang
where shellFor s | "/env " `isInfixOf` s = head ((drop 1 $ words s)++[""]) where shellFor s | "/env " `isInfixOf` s = head ((drop 1 $ words s)++[""])
shellFor s = reverse . takeWhile (/= '/') . reverse $ s shellFor s = reverse . takeWhile (/= '/') . reverse $ s
normalize "sh" = Sh
normalize "ash" = Sh
normalize "dash" = Sh
normalize "ksh" = Ksh shellForExecutable "sh" = return Sh
normalize "ksh93" = Ksh shellForExecutable "ash" = return Sh
shellForExecutable "dash" = return Sh
normalize "zsh" = Zsh shellForExecutable "ksh" = return Ksh
shellForExecutable "ksh93" = return Ksh
normalize "bash" = Bash shellForExecutable "zsh" = return Zsh
normalize _ = Bash shellForExecutable "bash" = return Bash
shellForExecutable _ = Nothing
-- Checks that are run on each node in the AST -- Checks that are run on each node in the AST
runNodeAnalysis f p t = execWriter (doAnalysis (f p) t) runNodeAnalysis f p t = execWriter (doAnalysis (f p) t)
@ -288,7 +293,7 @@ verifyNotTree f s = checkTree f s == Just False
checkNode f s = checkTree (runNodeAnalysis f) s checkNode f s = checkTree (runNodeAnalysis f) s
checkTree f s = case parseShell "-" s of checkTree f s = case parseShell "-" s of
(ParseResult (Just (t, m)) _) -> Just . not . null $ runList t [f] (ParseResult (Just (t, m)) _) -> Just . not . null $ runList [] t [f]
_ -> Nothing _ -> Nothing

View File

@ -25,27 +25,27 @@ import Data.List
prop_findsParseIssue = prop_findsParseIssue =
let comments = shellCheck "echo \"$12\"" in let comments = shellCheck "echo \"$12\"" [] in
(length comments) == 1 && (scCode $ head comments) == 1037 (length comments) == 1 && (scCode $ head comments) == 1037
prop_commentDisablesParseIssue1 = prop_commentDisablesParseIssue1 =
null $ shellCheck "#shellcheck disable=SC1037\necho \"$12\"" null $ shellCheck "#shellcheck disable=SC1037\necho \"$12\"" []
prop_commentDisablesParseIssue2 = prop_commentDisablesParseIssue2 =
null $ shellCheck "#shellcheck disable=SC1037\n#lol\necho \"$12\"" null $ shellCheck "#shellcheck disable=SC1037\n#lol\necho \"$12\"" []
prop_findsAnalysisIssue = prop_findsAnalysisIssue =
let comments = shellCheck "echo $1" in let comments = shellCheck "echo $1" [] in
(length comments) == 1 && (scCode $ head comments) == 2086 (length comments) == 1 && (scCode $ head comments) == 2086
prop_commentDisablesAnalysisIssue1 = prop_commentDisablesAnalysisIssue1 =
null $ shellCheck "#shellcheck disable=SC2086\necho $1" null $ shellCheck "#shellcheck disable=SC2086\necho $1" []
prop_commentDisablesAnalysisIssue2 = prop_commentDisablesAnalysisIssue2 =
null $ shellCheck "#shellcheck disable=SC2086\n#lol\necho $1" null $ shellCheck "#shellcheck disable=SC2086\n#lol\necho $1" []
shellCheck :: String -> [ShellCheckComment] shellCheck :: String -> [AnalysisOption] -> [ShellCheckComment]
shellCheck script = shellCheck script options =
let (ParseResult result notes) = parseShell "-" script in let (ParseResult result notes) = parseShell "-" script in
let allNotes = notes ++ (concat $ maybeToList $ do let allNotes = notes ++ (concat $ maybeToList $ do
(tree, posMap) <- result (tree, posMap) <- result
let list = runAnalytics [] tree let list = runAnalytics options tree
return $ map (noteToParseNote posMap) $ filterByAnnotation tree list return $ map (noteToParseNote posMap) $ filterByAnnotation tree list
) )
in in

View File

@ -18,10 +18,12 @@
import Control.Exception import Control.Exception
import Control.Monad import Control.Monad
import Data.Char import Data.Char
import Data.Maybe
import GHC.Exts import GHC.Exts
import GHC.IO.Device import GHC.IO.Device
import Prelude hiding (catch) import Prelude hiding (catch)
import ShellCheck.Simple import ShellCheck.Simple
import ShellCheck.Analytics
import System.Console.GetOpt import System.Console.GetOpt
import System.Directory import System.Directory
import System.Environment import System.Environment
@ -37,7 +39,9 @@ options = [
Option ['f'] ["format"] Option ['f'] ["format"]
(ReqArg (Flag "format") "FORMAT") "output format", (ReqArg (Flag "format") "FORMAT") "output format",
Option ['e'] ["exclude"] Option ['e'] ["exclude"]
(ReqArg (Flag "exclude") "CODE1,CODE2..") "exclude types of warnings" (ReqArg (Flag "exclude") "CODE1,CODE2..") "exclude types of warnings",
Option ['s'] ["shell"]
(ReqArg (Flag "shell") "SHELLNAME") "Specify dialect (bash,sh,ksh,zsh)"
] ]
printErr = hPutStrLn stderr printErr = hPutStrLn stderr
@ -200,7 +204,14 @@ commentsFor options file =
liftM (getComments options) $ readContents file liftM (getComments options) $ readContents file
getComments options contents = getComments options contents =
excludeCodes (getExclusions options) $ shellCheck contents excludeCodes (getExclusions options) $ shellCheck contents analysisOptions
where
analysisOptions = catMaybes [ shellOption ]
shellOption = do
option <- getOption options "shell"
sh <- shellForExecutable option
return $ ForceShell sh
readContents file = if file == "-" then getContents else readFile file readContents file = if file == "-" then getContents else readFile file
@ -216,9 +227,9 @@ makeNonVirtual comments contents =
real rest (r+1) (v + 8 - (v `mod` 8)) target real rest (r+1) (v + 8 - (v `mod` 8)) target
real (_:rest) r v target = real rest (r+1) (v+1) target real (_:rest) r v target = real rest (r+1) (v+1) target
getOption [] _ def = def getOption [] _ = Nothing
getOption ((Flag var val):_) name _ | name == var = val getOption ((Flag var val):_) name | name == var = return val
getOption (_:rest) flag def = getOption rest flag def getOption (_:rest) flag = getOption rest flag
getOptions options name = getOptions options name =
map (\(Flag _ val) -> val) . filter (\(Flag var _) -> var == name) $ options map (\(Flag _ val) -> val) . filter (\(Flag var _) -> var == name) $ options
@ -256,8 +267,9 @@ main = do
exitWith code exitWith code
process Nothing = return False process Nothing = return False
process (Just (options, files)) = process (Just (options, files)) = do
let format = getOption options "format" "tty" in verifyShellOption options
let format = fromMaybe "tty" $ getOption options "format" in
case Map.lookup format formats of case Map.lookup format formats of
Nothing -> do Nothing -> do
printErr $ "Unknown format " ++ format printErr $ "Unknown format " ++ format
@ -268,3 +280,10 @@ process (Just (options, files)) =
Just f -> do Just f -> do
f options files f options files
verifyShellOption options =
let shell = getOption options "shell" in
if isNothing shell
then return ()
else when (isNothing $ shell >>= shellForExecutable) $ do
printErr $ "Unknown shell: " ++ (fromJust shell)
exitWith supportFailure