Skip to content
51 changes: 50 additions & 1 deletion pkg/linters/generatedyamlheredoc/generatedyamlheredoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func lineContainsShellHeredoc(line string) bool {
line = line[afterIndex+1:]
continue
}
if strings.LastIndex(line[:operatorIndex], "$((") > strings.LastIndex(line[:operatorIndex], "))") {
if hasOpenShellArithmeticExpression(line[:operatorIndex]) {
line = line[afterIndex:]
continue
}
Expand All @@ -102,6 +102,55 @@ func lineContainsShellHeredoc(line string) bool {
}
}

func hasOpenShellArithmeticExpression(line string) bool {
arithmeticDepth := 0
inSingleQuote := false
inDoubleQuote := false

for index := 0; index < len(line); index++ {
switch line[index] {
case '\\':
if !inSingleQuote {
index++
}
case '\'':
if !inDoubleQuote {
inSingleQuote = !inSingleQuote
}
case '"':
if !inSingleQuote {
inDoubleQuote = !inDoubleQuote
}
case '(':
if !inSingleQuote && index+1 < len(line) && line[index+1] == '(' &&
((index > 0 && line[index-1] == '$') || (!inDoubleQuote && isShellTokenBoundary(line, index))) {
arithmeticDepth++
index++
}
case ')':
if !inSingleQuote && arithmeticDepth > 0 && index+1 < len(line) && line[index+1] == ')' {
arithmeticDepth--
index++
}
}
}

return arithmeticDepth > 0
}

func isShellTokenBoundary(line string, index int) bool {
if index == 0 {
return true
}

switch line[index-1] {
case ' ', '\t', ';', '&', '|', '(':
return true
default:
return false
}
}

func isShellWordByte(value byte) bool {
return value == '_' ||
value >= 'a' && value <= 'z' ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ func generatedWorkflowFragments() []string {
"<<EOF command\ncontent\nEOF\n", // want `generated workflow shell uses a heredoc; pass plain content through an environment variable to a JavaScript renderer instead \(do not base64 encode it\)`
"cat <<< \"$VALUE\"\n",
"echo $((1 << 2))\n",
"(( count << 1 ))\n",
"if (( a << 2 )); then\n",
"printf '%s\n' '((' && cat <<EOF\ncontent\nEOF\n", // want `generated workflow shell uses a heredoc; pass plain content through an environment variable to a JavaScript renderer instead \(do not base64 encode it\)`
`(?ms)<<\s*EOF`,
"echo no-heredoc\n",
}
Expand Down
Loading