Add support for basic shflags semantics

The shflags command-line flags library creates variables at runtime
with a few well-defined functions.  This causes shellcheck to spit out
lots of warnings about unassigned variables, as well as miss warnings
about unused flag variables.

We can address this with two parts:

1. Pretend that the shflags global variables are predefined like other
   shell variables so that shellcheck doesn't expect users to set them.
2. Treat DEFINE_string, DEFINE_int, etc. as new commands that create
   variables, similar to the existing read, local, mapfile, etc.

Part 1 can theoretically be addresssed without this by following sourced
files, but that doesn't help if people are otherwise not following
external sources.

The new behavior is on by default, similar to automatic bats test
behavior.

Addresses #1597
This commit is contained in:
Benjamin Gordon 2019-05-31 10:42:53 -06:00
parent c5aa171a5f
commit f6ba500d6b
4 changed files with 22 additions and 0 deletions

View File

@ -9,6 +9,7 @@
- Source paths: Use `-P dir1:dir2` or a `source-path=dir1` directive - Source paths: Use `-P dir1:dir2` or a `source-path=dir1` directive
to specify search paths for sourced files. to specify search paths for sourced files.
- json1 format like --format=json but treats tabs as single characters - json1 format like --format=json but treats tabs as single characters
- Recognize FLAGS variables created by the shflags library.
- SC2154: Also warn about unassigned uppercase variables (optional) - SC2154: Also warn about unassigned uppercase variables (optional)
- SC2252: Warn about `[ $a != x ] || [ $a != y ]`, similar to SC2055 - SC2252: Warn about `[ $a != x ] || [ $a != y ]`, similar to SC2055
- SC2251: Inform about ineffectual ! in front of commands - SC2251: Inform about ineffectual ! in front of commands

View File

@ -2081,6 +2081,8 @@ prop_checkUnused38= verifyTree checkUnusedAssignments "(( a=42 ))"
prop_checkUnused39= verifyNotTree checkUnusedAssignments "declare -x -f foo" prop_checkUnused39= verifyNotTree checkUnusedAssignments "declare -x -f foo"
prop_checkUnused40= verifyNotTree checkUnusedAssignments "arr=(1 2); num=2; echo \"${arr[@]:num}\"" prop_checkUnused40= verifyNotTree checkUnusedAssignments "arr=(1 2); num=2; echo \"${arr[@]:num}\""
prop_checkUnused41= verifyNotTree checkUnusedAssignments "@test 'foo' {\ntrue\n}\n" prop_checkUnused41= verifyNotTree checkUnusedAssignments "@test 'foo' {\ntrue\n}\n"
prop_checkUnused42= verifyNotTree checkUnusedAssignments "DEFINE_string foo '' ''; echo \"${FLAGS_foo}\""
prop_checkUnused43= verifyTree checkUnusedAssignments "DEFINE_string foo '' ''"
checkUnusedAssignments params t = execWriter (mapM_ warnFor unused) checkUnusedAssignments params t = execWriter (mapM_ warnFor unused)
where where
flow = variableFlow params flow = variableFlow params

View File

@ -606,6 +606,11 @@ getModifiedVariableCommand base@(T_SimpleCommand _ _ (T_NormalWord _ (T_Literal
"mapfile" -> maybeToList $ getMapfileArray base rest "mapfile" -> maybeToList $ getMapfileArray base rest
"readarray" -> maybeToList $ getMapfileArray base rest "readarray" -> maybeToList $ getMapfileArray base rest
"DEFINE_boolean" -> maybeToList $ getFlagVariable rest
"DEFINE_float" -> maybeToList $ getFlagVariable rest
"DEFINE_integer" -> maybeToList $ getFlagVariable rest
"DEFINE_string" -> maybeToList $ getFlagVariable rest
_ -> [] _ -> []
where where
flags = map snd $ getAllFlags base flags = map snd $ getAllFlags base
@ -679,6 +684,12 @@ getModifiedVariableCommand base@(T_SimpleCommand _ _ (T_NormalWord _ (T_Literal
map (getLiteralArray . snd) map (getLiteralArray . snd)
(filter (\(x,_) -> getLiteralString x == Just "-a") (zip (args) (tail args))) (filter (\(x,_) -> getLiteralString x == Just "-a") (zip (args) (tail args)))
-- get the FLAGS_ variable created by a shflags DEFINE_ call
getFlagVariable (n:v:_) = return (base, n, flagName n, DataString $ SourceFrom [v])
where
flagName varName@(T_NormalWord _ _) = "FLAGS_" ++ (onlyLiteralString varName)
getFlagVariable _ = fail "Invalid flag definition"
getModifiedVariableCommand _ = [] getModifiedVariableCommand _ = []
getIndexReferences s = fromMaybe [] $ do getIndexReferences s = fromMaybe [] $ do

View File

@ -36,6 +36,11 @@ internalVariables = [
-- Ksh -- Ksh
, ".sh.version" , ".sh.version"
-- shflags
, "FLAGS_ARGC", "FLAGS_ARGV", "FLAGS_ERROR", "FLAGS_FALSE", "FLAGS_HELP",
"FLAGS_PARENT", "FLAGS_RESERVED", "FLAGS_TRUE", "FLAGS_VERSION",
"flags_error", "flags_return"
] ]
specialVariablesWithoutSpaces = [ specialVariablesWithoutSpaces = [
@ -45,6 +50,9 @@ variablesWithoutSpaces = specialVariablesWithoutSpaces ++ [
"BASHPID", "BASH_ARGC", "BASH_LINENO", "BASH_SUBSHELL", "EUID", "LINENO", "BASHPID", "BASH_ARGC", "BASH_LINENO", "BASH_SUBSHELL", "EUID", "LINENO",
"OPTIND", "PPID", "RANDOM", "SECONDS", "SHELLOPTS", "SHLVL", "UID", "OPTIND", "PPID", "RANDOM", "SECONDS", "SHELLOPTS", "SHLVL", "UID",
"COLUMNS", "HISTFILESIZE", "HISTSIZE", "LINES" "COLUMNS", "HISTFILESIZE", "HISTSIZE", "LINES"
-- shflags
, "FLAGS_ERROR", "FLAGS_FALSE", "FLAGS_TRUE"
] ]
specialVariables = specialVariablesWithoutSpaces ++ ["@", "*"] specialVariables = specialVariablesWithoutSpaces ++ ["@", "*"]