Added -s flag to override dialect, e.g. -s ksh
This commit is contained in:
parent
075d58ee90
commit
4968e7d9ff
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue