Skip to content

Commit ce751fe

Browse files
Copilotdotysan
andauthored
Reduce SC2016 false positives: add command exceptions, PS0 handling, concatenated quotes detection, and fix BATS run with flags
- Add gojq, dash, script, printf to command exception list - Add PS0 to commonly-quoted variable names - Suppress SC2016 when single-quoted string is concatenated with double-quoted expansions - Fix run (BATS) to skip flags when resolving effective command - Add test properties for all new behaviors Agent-Logs-Url: https://github.com/dotysan/shellcheck/sessions/90f001fb-db77-4304-9af4-a819f4d48f85 Co-authored-by: dotysan <5060170+dotysan@users.noreply.github.com>
1 parent 766a836 commit ce751fe

2 files changed

Lines changed: 32 additions & 2 deletions

File tree

src/ShellCheck/ASTLib.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ getCommandNameAndToken direct t = fromMaybe (Nothing, t) $ do
547547
"busybox" -> firstArg
548548
"builtin" -> firstArg
549549
"command" -> firstArg
550-
"run" -> firstArg -- Used by bats
550+
"run" -> listToMaybe $ dropWhile isFlag args -- Used by bats
551551
"exec" -> do
552552
opts <- getBsdOpts "cla:" args
553553
(_, (t, _)) <- find (null . fst) opts

src/ShellCheck/Analytics.hs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,16 @@ prop_checkSingleQuotedVariables22 = verifyNot checkSingleQuotedVariables "jq '$_
10861086
prop_checkSingleQuotedVariables23 = verifyNot checkSingleQuotedVariables "command jq '$__loc__'"
10871087
prop_checkSingleQuotedVariables24 = verifyNot checkSingleQuotedVariables "exec jq '$__loc__'"
10881088
prop_checkSingleQuotedVariables25 = verifyNot checkSingleQuotedVariables "exec -c -a foo jq '$__loc__'"
1089+
prop_checkSingleQuotedVariables26 = verifyNot checkSingleQuotedVariables "gojq '$__loc__'"
1090+
prop_checkSingleQuotedVariables27 = verifyNot checkSingleQuotedVariables "dash -c 'echo $1'"
1091+
prop_checkSingleQuotedVariables28 = verifyNot checkSingleQuotedVariables "script -qec 'var=txt; echo \"$var\"' /dev/null"
1092+
prop_checkSingleQuotedVariables29 = verifyNot checkSingleQuotedVariables "printf 'eval \"$(pyenv init - %s)\"' \"$shell\""
1093+
prop_checkSingleQuotedVariables30 = verifyNot checkSingleQuotedVariables "PS0='`history -a`'$PS0"
1094+
prop_checkSingleQuotedVariables31 = verifyNot checkSingleQuotedVariables "echo '\\.$'\"$tld\"'\\.$'"
1095+
prop_checkSingleQuotedVariables32 = verifyNot checkSingleQuotedVariables "echo '$foo'\"$bar\""
1096+
prop_checkSingleQuotedVariables33 = verify checkSingleQuotedVariables "echo '$foo'"
1097+
prop_checkSingleQuotedVariables34 = verifyNot checkSingleQuotedVariables "find . -exec dash -c 'echo \"$1\"' shell {} \\;"
1098+
prop_checkSingleQuotedVariables35 = verifyNot checkSingleQuotedVariables "run --separate-stderr bash -c 'echo \"$1\"'"
10891099

10901100

10911101
checkSingleQuotedVariables params t@(T_SingleQuoted id s) =
@@ -1110,6 +1120,7 @@ checkSingleQuotedVariables params t@(T_SingleQuoted id s) =
11101120
,"bash"
11111121
,"ksh"
11121122
,"zsh"
1123+
,"dash"
11131124
,"ssh"
11141125
,"eval"
11151126
,"xprop"
@@ -1122,17 +1133,21 @@ checkSingleQuotedVariables params t@(T_SingleQuoted id s) =
11221133
,"oc"
11231134
,"dpkg-query"
11241135
,"jq" -- could also check that user provides --arg
1136+
,"gojq" -- golang implementation of jq
11251137
,"rename"
11261138
,"rg"
11271139
,"unset"
1140+
,"printf"
1141+
,"script"
11281142
,"git filter-branch"
11291143
,"mumps -run %XCMD"
11301144
,"mumps -run LOOP%XCMD"
11311145
]
11321146
|| "awk" `isSuffixOf` commandName
11331147
|| "perl" `isPrefixOf` commandName
1148+
|| isConcatenatedWithExpansion
11341149

1135-
commonlyQuoted = ["PS1", "PS2", "PS3", "PS4", "PROMPT_COMMAND"]
1150+
commonlyQuoted = ["PS0", "PS1", "PS2", "PS3", "PS4", "PROMPT_COMMAND"]
11361151
isOkAssignment t =
11371152
case t of
11381153
T_Assignment _ _ name _ _ -> name `elem` commonlyQuoted
@@ -1142,6 +1157,21 @@ checkSingleQuotedVariables params t@(T_SingleQuoted id s) =
11421157
re = mkRegex "\\$[{(0-9a-zA-Z_]|`[^`]+`"
11431158
sedContra = mkRegex "\\$[{dpsaic]($|[^a-zA-Z])"
11441159

1160+
-- When a single-quoted string is part of a word that also contains
1161+
-- double-quoted segments with expansions, the user clearly understands
1162+
-- quoting and is intentionally keeping parts in single quotes.
1163+
isConcatenatedWithExpansion =
1164+
case NE.tail $ getPath parents t of
1165+
(T_NormalWord _ parts):_ -> any hasExpansion parts
1166+
_ -> False
1167+
hasExpansion (T_DoubleQuoted _ parts) = any isExpansionToken parts
1168+
hasExpansion _ = False
1169+
isExpansionToken T_DollarBraced {} = True
1170+
isExpansionToken T_DollarExpansion {} = True
1171+
isExpansionToken T_DollarArithmetic {} = True
1172+
isExpansionToken T_Backticked {} = True
1173+
isExpansionToken _ = False
1174+
11451175
getFindCommand (T_SimpleCommand _ _ words) =
11461176
let list = map getLiteralString words
11471177
cmd = dropWhile (\x -> x `notElem` map Just ["-exec", "-execdir", "-ok", "-okdir"]) list

0 commit comments

Comments
 (0)