diff --git a/common/script_parser.go b/common/script_parser.go index 36c16d6..8b1eb4a 100644 --- a/common/script_parser.go +++ b/common/script_parser.go @@ -7,9 +7,7 @@ import ( ) func ForEachVariableAssignment(key, input string, fn func(string)) { - // parse - r := strings.NewReader(input) - f, err := syntax.NewParser().Parse(r, "") + f, err := parseScript(input) if err != nil { return } @@ -25,3 +23,42 @@ func ForEachVariableAssignment(key, input string, fn func(string)) { return true }) } + +var isSourceCommand = map[string]bool{ + ".": true, + "source": true, + // eval? +} + +func ForEachSourcedScript(input string, fn func(string)) { + f, err := parseScript(input) + if err != nil { + return + } + + syntax.Walk(f, func(node syntax.Node) bool { + switch x := node.(type) { + case *syntax.CallExpr: + if len(x.Args) == 2 { + cmd := input[x.Args[0].Pos().Offset():x.Args[0].End().Offset()] + cmd = strings.TrimLeft(cmd, "\\") + + arg := input[x.Args[1].Pos().Offset():x.Args[1].End().Offset()] + arg = strings.Trim(arg, "\"'`") + + if !isSourceCommand[cmd] { + return true + } + + fn(arg) + } + } + return true + }) +} + +func parseScript(input string) (*syntax.File, error) { + r := strings.NewReader(input) + f, err := syntax.NewParser().Parse(r, "") + return f, err +} diff --git a/common/script_parser_test.go b/common/script_parser_test.go index 411df34..9ea9509 100644 --- a/common/script_parser_test.go +++ b/common/script_parser_test.go @@ -8,15 +8,16 @@ import ( func Test_parsing_scrip_assignments(t *testing.T) { input := ` -if [[ true ]]; then - export PATH="/path1:$PATH" -else - export PATH="${PATH}:/path2" -fi -PATH=$PATH:/path3 -path=$path:/path4 -TEST=$PATH:/path5 -` + if [[ true ]]; then + export PATH="/path1:$PATH" + else + export PATH="${PATH}:/path2" + fi + PATH=$PATH:/path3 + path=$path:/path4 + TEST=$PATH:/path5 + ` + var seenValues string ForEachVariableAssignment("PATH", input, func(s string) { seenValues += s @@ -27,3 +28,24 @@ TEST=$PATH:/path5 assert.NotContains(t, seenValues, "/path4") assert.NotContains(t, seenValues, "/path5") } + +func Test_recursively_walking_sourced_scripts(t *testing.T) { + input := ` + [ -s "/test1.sh" ] && \. "/test1.sh" + [ -f /test2 ] && source /test2 + if [ "${BASH-no}" != "no" ]; then + [ -r ~/.test3 ] && . '~/.test3' + fi + ./test4 + ` + + seenScripts := []string{} + ForEachSourcedScript(input, func(s string) { + seenScripts = append(seenScripts, s) + }) + + assert.Contains(t, seenScripts, "/test1.sh") + assert.Contains(t, seenScripts, "/test2") + assert.Contains(t, seenScripts, "~/.test3") + assert.NotContains(t, seenScripts, "./test4") +}