From ce751fe197e3eb776266279d34b50367e2eb4172 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 18:52:40 +0000 Subject: [PATCH 1/2] 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> --- src/ShellCheck/ASTLib.hs | 2 +- src/ShellCheck/Analytics.hs | 32 +++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/ShellCheck/ASTLib.hs b/src/ShellCheck/ASTLib.hs index 7ddebe46d..adb0801e3 100644 --- a/src/ShellCheck/ASTLib.hs +++ b/src/ShellCheck/ASTLib.hs @@ -547,7 +547,7 @@ getCommandNameAndToken direct t = fromMaybe (Nothing, t) $ do "busybox" -> firstArg "builtin" -> firstArg "command" -> firstArg - "run" -> firstArg -- Used by bats + "run" -> listToMaybe $ dropWhile isFlag args -- Used by bats "exec" -> do opts <- getBsdOpts "cla:" args (_, (t, _)) <- find (null . fst) opts diff --git a/src/ShellCheck/Analytics.hs b/src/ShellCheck/Analytics.hs index a4e2186a4..b7257b792 100644 --- a/src/ShellCheck/Analytics.hs +++ b/src/ShellCheck/Analytics.hs @@ -1086,6 +1086,16 @@ prop_checkSingleQuotedVariables22 = verifyNot checkSingleQuotedVariables "jq '$_ prop_checkSingleQuotedVariables23 = verifyNot checkSingleQuotedVariables "command jq '$__loc__'" prop_checkSingleQuotedVariables24 = verifyNot checkSingleQuotedVariables "exec jq '$__loc__'" prop_checkSingleQuotedVariables25 = verifyNot checkSingleQuotedVariables "exec -c -a foo jq '$__loc__'" +prop_checkSingleQuotedVariables26 = verifyNot checkSingleQuotedVariables "gojq '$__loc__'" +prop_checkSingleQuotedVariables27 = verifyNot checkSingleQuotedVariables "dash -c 'echo $1'" +prop_checkSingleQuotedVariables28 = verifyNot checkSingleQuotedVariables "script -qec 'var=txt; echo \"$var\"' /dev/null" +prop_checkSingleQuotedVariables29 = verifyNot checkSingleQuotedVariables "printf 'eval \"$(pyenv init - %s)\"' \"$shell\"" +prop_checkSingleQuotedVariables30 = verifyNot checkSingleQuotedVariables "PS0='`history -a`'$PS0" +prop_checkSingleQuotedVariables31 = verifyNot checkSingleQuotedVariables "echo '\\.$'\"$tld\"'\\.$'" +prop_checkSingleQuotedVariables32 = verifyNot checkSingleQuotedVariables "echo '$foo'\"$bar\"" +prop_checkSingleQuotedVariables33 = verify checkSingleQuotedVariables "echo '$foo'" +prop_checkSingleQuotedVariables34 = verifyNot checkSingleQuotedVariables "find . -exec dash -c 'echo \"$1\"' shell {} \\;" +prop_checkSingleQuotedVariables35 = verifyNot checkSingleQuotedVariables "run --separate-stderr bash -c 'echo \"$1\"'" checkSingleQuotedVariables params t@(T_SingleQuoted id s) = @@ -1110,6 +1120,7 @@ checkSingleQuotedVariables params t@(T_SingleQuoted id s) = ,"bash" ,"ksh" ,"zsh" + ,"dash" ,"ssh" ,"eval" ,"xprop" @@ -1122,17 +1133,21 @@ checkSingleQuotedVariables params t@(T_SingleQuoted id s) = ,"oc" ,"dpkg-query" ,"jq" -- could also check that user provides --arg + ,"gojq" -- golang implementation of jq ,"rename" ,"rg" ,"unset" + ,"printf" + ,"script" ,"git filter-branch" ,"mumps -run %XCMD" ,"mumps -run LOOP%XCMD" ] || "awk" `isSuffixOf` commandName || "perl" `isPrefixOf` commandName + || isConcatenatedWithExpansion - commonlyQuoted = ["PS1", "PS2", "PS3", "PS4", "PROMPT_COMMAND"] + commonlyQuoted = ["PS0", "PS1", "PS2", "PS3", "PS4", "PROMPT_COMMAND"] isOkAssignment t = case t of T_Assignment _ _ name _ _ -> name `elem` commonlyQuoted @@ -1142,6 +1157,21 @@ checkSingleQuotedVariables params t@(T_SingleQuoted id s) = re = mkRegex "\\$[{(0-9a-zA-Z_]|`[^`]+`" sedContra = mkRegex "\\$[{dpsaic]($|[^a-zA-Z])" + -- When a single-quoted string is part of a word that also contains + -- double-quoted segments with expansions, the user clearly understands + -- quoting and is intentionally keeping parts in single quotes. + isConcatenatedWithExpansion = + case NE.tail $ getPath parents t of + (T_NormalWord _ parts):_ -> any hasExpansion parts + _ -> False + hasExpansion (T_DoubleQuoted _ parts) = any isExpansionToken parts + hasExpansion _ = False + isExpansionToken T_DollarBraced {} = True + isExpansionToken T_DollarExpansion {} = True + isExpansionToken T_DollarArithmetic {} = True + isExpansionToken T_Backticked {} = True + isExpansionToken _ = False + getFindCommand (T_SimpleCommand _ _ words) = let list = map getLiteralString words cmd = dropWhile (\x -> x `notElem` map Just ["-exec", "-execdir", "-ok", "-okdir"]) list From 563e984642eb71531af4fd183ca0e1f10949af36 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 18:53:33 +0000 Subject: [PATCH 2/2] Add clarifying comment for PS0 in commonlyQuoted list Agent-Logs-Url: https://github.com/dotysan/shellcheck/sessions/90f001fb-db77-4304-9af4-a819f4d48f85 Co-authored-by: dotysan <5060170+dotysan@users.noreply.github.com> --- src/ShellCheck/Analytics.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ShellCheck/Analytics.hs b/src/ShellCheck/Analytics.hs index b7257b792..d54b8997a 100644 --- a/src/ShellCheck/Analytics.hs +++ b/src/ShellCheck/Analytics.hs @@ -1147,7 +1147,7 @@ checkSingleQuotedVariables params t@(T_SingleQuoted id s) = || "perl" `isPrefixOf` commandName || isConcatenatedWithExpansion - commonlyQuoted = ["PS0", "PS1", "PS2", "PS3", "PS4", "PROMPT_COMMAND"] + commonlyQuoted = ["PS0", "PS1", "PS2", "PS3", "PS4", "PROMPT_COMMAND"] -- PS0 is bash 4.4+ isOkAssignment t = case t of T_Assignment _ _ name _ _ -> name `elem` commonlyQuoted