Directives after the shebang now apply to the entire script.

Also adds support for the shell= directive.
This commit is contained in:
Vidar Holen 2016-03-08 20:16:16 -08:00
parent 6af1aeb259
commit 944313c6ba
3 changed files with 52 additions and 15 deletions

View File

@ -129,7 +129,11 @@ data Token =
| T_Include Id Token Token -- . & source: SimpleCommand T_Script | T_Include Id Token Token -- . & source: SimpleCommand T_Script
deriving (Show) deriving (Show)
data Annotation = DisableComment Integer | SourceOverride String deriving (Show, Eq) data Annotation =
DisableComment Integer
| SourceOverride String
| ShellOverride String
deriving (Show, Eq)
data ConditionType = DoubleBracket | SingleBracket deriving (Show, Eq) data ConditionType = DoubleBracket | SingleBracket deriving (Show, Eq)
-- This is an abomination. -- This is an abomination.

View File

@ -115,14 +115,32 @@ checkList l t = concatMap (\f -> f t) l
getCode (TokenComment _ (Comment _ c _)) = c getCode (TokenComment _ (Comment _ c _)) = c
prop_determineShell0 = determineShell (T_Script (Id 0) "#!/bin/sh" []) == Sh prop_determineShell0 = determineShell (fromJust $ pScript "#!/bin/sh") == Sh
prop_determineShell1 = determineShell (T_Script (Id 0) "#!/usr/bin/env ksh" []) == Ksh prop_determineShell1 = determineShell (fromJust $ pScript "#!/usr/bin/env ksh") == Ksh
prop_determineShell2 = determineShell (T_Script (Id 0) "" []) == Bash prop_determineShell2 = determineShell (fromJust $ pScript "") == Bash
prop_determineShell3 = determineShell (T_Script (Id 0) "#!/bin/sh -e" []) == Sh prop_determineShell3 = determineShell (fromJust $ pScript "#!/bin/sh -e") == Sh
determineShell (T_Script _ shebang _) = fromMaybe Bash . shellForExecutable $ shellFor shebang prop_determineShell4 = determineShell (fromJust $ pScript
where shellFor s | "/env " `isInfixOf` s = head (drop 1 (words s)++[""]) "#!/bin/ksh\n#shellcheck shell=sh\nfoo") == Sh
shellFor s | ' ' `elem` s = shellFor $ takeWhile (/= ' ') s prop_determineShell5 = determineShell (fromJust $ pScript
shellFor s = reverse . takeWhile (/= '/') . reverse $ s "#shellcheck shell=sh\nfoo") == Sh
determineShell t = fromMaybe Bash $ do
shellString <- foldl mplus Nothing $ getCandidates t
shellForExecutable shellString
where
forAnnotation t =
case t of
(ShellOverride s) -> return s
_ -> fail ""
getCandidates :: Token -> [Maybe String]
getCandidates t@(T_Script {}) = [Just $ fromShebang t]
getCandidates (T_Annotation _ annotations s) =
map forAnnotation annotations ++
[Just $ fromShebang s]
fromShebang (T_Script _ s t) = shellFor s
shellFor s | "/env " `isInfixOf` s = head (drop 1 (words s)++[""])
shellFor s | ' ' `elem` s = shellFor $ takeWhile (/= ' ') s
shellFor s = reverse . takeWhile (/= '/') . reverse $ s
-- 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)
@ -310,14 +328,16 @@ defaultSpec root = AnalysisSpec {
checkNode f = producesComments (runNodeAnalysis f) checkNode f = producesComments (runNodeAnalysis f)
producesComments :: (Parameters -> Token -> [TokenComment]) -> String -> Maybe Bool producesComments :: (Parameters -> Token -> [TokenComment]) -> String -> Maybe Bool
producesComments f s = do producesComments f s = do
root <- prRoot pResult root <- pScript s
return . not . null $ runList (defaultSpec root) [f] return . not . null $ runList (defaultSpec root) [f]
where
pScript s =
let
pSpec = ParseSpec { pSpec = ParseSpec {
psFilename = "script", psFilename = "script",
psScript = s psScript = s
} }
pResult = runIdentity $ parseScript (mockedSystemInterface []) pSpec in prRoot . runIdentity $ parseScript (mockedSystemInterface []) pSpec
-- Copied from https://wiki.haskell.org/Edit_distance -- Copied from https://wiki.haskell.org/Edit_distance
dist :: Eq a => [a] -> [a] -> Int dist :: Eq a => [a] -> [a] -> Int
@ -554,12 +574,14 @@ indexOfSublists sub = f 0
prop_checkShebangParameters1 = verifyTree checkShebangParameters "#!/usr/bin/env bash -x\necho cow" prop_checkShebangParameters1 = verifyTree checkShebangParameters "#!/usr/bin/env bash -x\necho cow"
prop_checkShebangParameters2 = verifyNotTree checkShebangParameters "#! /bin/sh -l " prop_checkShebangParameters2 = verifyNotTree checkShebangParameters "#! /bin/sh -l "
checkShebangParameters p (T_Annotation _ _ t) = checkShebangParameters p t
checkShebangParameters _ (T_Script id sb _) = checkShebangParameters _ (T_Script id sb _) =
[makeComment ErrorC id 2096 "On most OS, shebangs can only specify a single parameter." | length (words sb) > 2] [makeComment ErrorC id 2096 "On most OS, shebangs can only specify a single parameter." | length (words sb) > 2]
prop_checkShebang1 = verifyNotTree checkShebang "#!/usr/bin/env bash -x\necho cow" prop_checkShebang1 = verifyNotTree checkShebang "#!/usr/bin/env bash -x\necho cow"
prop_checkShebang2 = verifyNotTree checkShebang "#! /bin/sh -l " prop_checkShebang2 = verifyNotTree checkShebang "#! /bin/sh -l "
prop_checkShebang3 = verifyTree checkShebang "ls -l" prop_checkShebang3 = verifyTree checkShebang "ls -l"
checkShebang params (T_Annotation _ _ t) = checkShebang params t
checkShebang params (T_Script id sb _) = checkShebang params (T_Script id sb _) =
[makeComment ErrorC id 2148 "Tips depend on target shell and yours is unknown. Add a shebang." [makeComment ErrorC id 2148 "Tips depend on target shell and yours is unknown. Add a shebang."
| not (shellTypeSpecified params) && sb == "" ] | not (shellTypeSpecified params) && sb == "" ]

View File

@ -772,7 +772,7 @@ prop_readAnnotation3 = isOk readAnnotation "# shellcheck disable=SC1234 source=/
readAnnotation = called "shellcheck annotation" $ do readAnnotation = called "shellcheck annotation" $ do
try readAnnotationPrefix try readAnnotationPrefix
many1 linewhitespace many1 linewhitespace
values <- many1 (readDisable <|> readSourceOverride) values <- many1 (readDisable <|> readSourceOverride <|> readShellOverride)
linefeed linefeed
many linewhitespace many linewhitespace
return $ concat values return $ concat values
@ -789,6 +789,14 @@ readAnnotation = called "shellcheck annotation" $ do
filename <- many1 $ noneOf " \n" filename <- many1 $ noneOf " \n"
return [SourceOverride filename] return [SourceOverride filename]
readShellOverride = forKey "shell" $ do
pos <- getPosition
shell <- many1 $ noneOf " \n"
when (isNothing $ shellForExecutable shell) $
parseNoteAt pos ErrorC 1103
"This shell type is unknown. Use e.g. sh or bash."
return [ShellOverride shell]
forKey s p = do forKey s p = do
try $ string s try $ string s
char '=' char '='
@ -2334,6 +2342,7 @@ ifParse p t f =
prop_readShebang1 = isOk readShebang "#!/bin/sh\n" prop_readShebang1 = isOk readShebang "#!/bin/sh\n"
prop_readShebang2 = isWarning readShebang "!# /bin/sh\n" prop_readShebang2 = isWarning readShebang "!# /bin/sh\n"
prop_readShebang3 = isNotOk readShebang "#shellcheck shell=/bin/sh\n"
readShebang = do readShebang = do
try readCorrect <|> try readSwapped try readCorrect <|> try readSwapped
str <- many $ noneOf "\r\n" str <- many $ noneOf "\r\n"
@ -2378,9 +2387,11 @@ readScript = do
verifyShell pos (getShell sb) verifyShell pos (getShell sb)
if isValidShell (getShell sb) /= Just False if isValidShell (getShell sb) /= Just False
then do then do
commands <- readCompoundListOrEmpty annotationId <- getNextId
annotations <- readAnnotations
commands <- withAnnotations annotations readCompoundListOrEmpty
verifyEof verifyEof
return $ T_Script id sb commands return $ T_Annotation annotationId annotations $ T_Script id sb commands
else do else do
many anyChar many anyChar
return $ T_Script id sb [] return $ T_Script id sb []