Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/ShellCheck/Analytics.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2079,6 +2079,9 @@ prop_subshellAssignmentCheck22 = verifyNotTree subshellAssignmentCheck "( [[ -n
prop_subshellAssignmentCheck23 = verifyNotTree subshellAssignmentCheck "( export foo ); echo $foo"
prop_subshellAssignmentCheck24 = verifyNotTree subshellAssignmentCheck "( read -r a _ c <<< 'x y z'; ); echo $_"
prop_subshellAssignmentCheck25 = verifyNotTree subshellAssignmentCheck "( _=discard; ); echo $_"
prop_subshellAssignmentCheck40 = verifyNotTree subshellAssignmentCheck "#!/usr/bin/env bats\n@test 'foo' { ( run true; ) & run true; echo $status; }"
prop_subshellAssignmentCheck41 = verifyTree subshellAssignmentCheck "( run true; ); echo $status"
prop_subshellAssignmentCheck42 = verifyNotTree subshellAssignmentCheck "#!/usr/bin/env bats\n@test 'foo' { run true | cat; }\n@test 'bar' { true; }"
subshellAssignmentCheck params t =
let flow = variableFlow params
check = findSubshelled flow [("oops",[])] Map.empty
Expand Down Expand Up @@ -2471,13 +2474,24 @@ prop_checkUnused48 = verifyNotTree checkUnusedAssignments "_a=1"
prop_checkUnused49 = verifyNotTree checkUnusedAssignments "declare -A array; key=a; [[ -v array[$key] ]]"
prop_checkUnused50 = verifyNotTree checkUnusedAssignments "foofunc() { :; }; typeset -fx foofunc"
prop_checkUnused51 = verifyTree checkUnusedAssignments "x[y[z=1]]=1; echo ${x[@]}"
prop_checkUnused52 = verifyNotTree checkUnusedAssignments "run --separate-stderr true"
prop_checkUnused53 = verifyNotTree checkUnusedAssignments "#!/usr/bin/env bats\n@test 'foo' { true; }"
prop_checkUnused54 = verifyTree checkUnusedAssignments "run=1"
prop_checkUnused55 = verifyNotTree checkUnusedAssignments "run foo bar"
prop_checkUnused56 = verifyTree checkUnusedAssignments "#!/usr/bin/env bats\n@test 'foo' { output=$(true); }"

checkUnusedAssignments params t = execWriter (mapM_ warnFor unused)
where
flow = variableFlow params
references = Map.union (Map.fromList [(stripSuffix name, ()) | Reference (base, token, name) <- flow]) defaultMap

assignments = Map.fromList [(name, token) | Assignment (_, token, name, _) <- flow, isVariableName name]
assignments = Map.fromList [(name, token) | Assignment (base, token, name, _) <- flow, isVariableName name, not $ isBats base]
-- Variables set by bats' run (or pretended to be set by @test)
-- are for the caller to check, if needed
isBats t = case t of
T_SimpleCommand _ _ (cmd:_) -> getLiteralString cmd == Just "run"
T_BatsTest {} -> True
_ -> False

unused = Map.assocs $ Map.difference assignments references

Expand Down Expand Up @@ -2542,6 +2556,11 @@ prop_checkUnassignedReferences50 = verifyNotTree checkUnassignedReferences "echo
prop_checkUnassignedReferences51 = verifyNotTree checkUnassignedReferences "echo ${foo:+$foo}"
prop_checkUnassignedReferences52 = verifyNotTree checkUnassignedReferences "wait -p pid; echo $pid"
prop_checkUnassignedReferences53 = verifyTree checkUnassignedReferences "x=($foo)"
prop_checkUnassignedReferences54 = verifyNotTree checkUnassignedReferences "f() { run true; echo \"$status $output ${lines[0]}\"; }"
prop_checkUnassignedReferences55 = verifyNotTree checkUnassignedReferences "#!/usr/bin/env bats\n@test 'foo' { run_wrapper; echo \"$status $stderr\"; }"
prop_checkUnassignedReferences56 = verifyTree checkUnassignedReferences "f() { run true; echo $stderr; }"
prop_checkUnassignedReferences57 = verifyNotTree checkUnassignedReferences "f() { run -1 --separate-stderr false; echo \"$stderr ${stderr_lines[0]}\"; }"
prop_checkUnassignedReferences58 = verifyTree checkUnassignedReferences "echo $stderr"

checkUnassignedReferences = checkUnassignedReferences' False
checkUnassignedReferences' includeGlobals params t = warnings
Expand Down
22 changes: 16 additions & 6 deletions src/ShellCheck/AnalyzerLib.hs
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,20 @@ getModifiedVariables t =
_ -> Nothing


-- Variables set by bats' run command
getBatsRunVariables args =
[
("status", DataString SourceInteger),
("output", DataString SourceExternal),
("lines", DataArray SourceExternal)
] ++ if "--separate-stderr" `elem` opts then [
("stderr", DataString SourceExternal),
("stderr_lines", DataArray SourceExternal)
] else []
where
opts = takeWhile (\s -> s /= "--" && (s == "!" || "-" `isPrefixOf` s)) $
map (fromMaybe "" . getLiteralString) args

-- Consider 'export/declare -x' a reference, since it makes the var available
getReferencedVariableCommand base@(T_SimpleCommand _ _ (T_NormalWord _ (T_Literal _ x:_):rest)) =
case x of
Expand Down Expand Up @@ -689,6 +703,8 @@ getModifiedVariableCommand base@(T_SimpleCommand id cmdPrefix (T_NormalWord _ (T
"DEFINE_integer" -> maybeToList $ getFlagVariable rest
"DEFINE_string" -> maybeToList $ getFlagVariable rest

"run" -> [(base, base, name, value) | (name, value) <- getBatsRunVariables rest]

_ -> []
where
flags = map snd $ getAllFlags base
Expand Down Expand Up @@ -816,12 +832,6 @@ getReferencedVariables parents t =
then concatMap (getIfReference t) [lhs, rhs]
else []

T_BatsTest {} -> [ -- pretend @test references vars to avoid warnings
(t, t, "lines"),
(t, t, "status"),
(t, t, "output")
]

T_FdRedirect _ ('{':var) op -> -- {foo}>&- references and closes foo
[(t, t, takeWhile (/= '}') var) | isClosingFileOp op]
x -> getReferencedVariableCommand x
Expand Down
3 changes: 0 additions & 3 deletions src/ShellCheck/Data.hs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,6 @@ internalVariables = [
, "FLAGS_ARGC", "FLAGS_ARGV", "FLAGS_ERROR", "FLAGS_FALSE", "FLAGS_HELP",
"FLAGS_PARENT", "FLAGS_RESERVED", "FLAGS_TRUE", "FLAGS_VERSION",
"flags_error", "flags_return"

-- Bats
,"stderr", "stderr_lines"
]

specialIntegerVariables = [
Expand Down
Loading