Summary
generatedyamlheredoc (pkg/linters/generatedyamlheredoc/generatedyamlheredoc.go) flags shell heredocs embedded in generated workflow YAML, but its arithmetic-context guard only recognizes the $(( spelling of shell arithmetic expansion. Bare (( ... )) arithmetic commands/conditions are not recognized, so a << bit-shift inside one is misreported as a heredoc.
Evidence
lineContainsShellHeredoc (generatedyamlheredoc.go:89) special-cases arithmetic via:
if strings.LastIndex(line[:operatorIndex], "$((") > strings.LastIndex(line[:operatorIndex], "))") {
line = line[afterIndex:]
continue
}
This only looks for the literal substring "$((". Bash also supports the arithmetic compound command/conditional (( expr )) (no leading $), e.g. (( count << 1 )), if (( retries << 1 > max )); then. For that spelling, line[:operatorIndex] contains "((" but never "$((", so the guard is skipped entirely.
The code then falls through to the delimiter check:
afterOperator := strings.TrimLeft(line[afterIndex:], " \t")
delimiterStart := afterOperator[0]
return delimiterStart == '-' || delimiterStart == '\'' || delimiterStart == '"' || isShellWordByte(delimiterStart)
isShellWordByte accepts 0-9, so any numeric right-hand operand (<< 1, << 2, ...) satisfies delimiterStart, and the line is reported as a heredoc.
The existing test suite only covers the $(( spelling: "echo $((1 << 2))\n" in testdata/src/generatedyamlheredoc/generatedyamlheredoc.go:13 is correctly not flagged. There is no test case for bare (( ... << ... )), so this gap is untested and unguarded.
Reproduction
Add this string to the testdata fixture and run the analyzer:
Expected: not flagged (it's arithmetic, not a heredoc). Actual: flagged with "generated workflow shell uses a heredoc; pass plain content through an environment variable to a JavaScript renderer instead (do not base64 encode it)" -- a false positive, since there is no heredoc here at all.
Impact
Currently latent: a repo-wide search of pkg/**/*.go (non-test) found no production string literal combining bare (( arithmetic with <<, and generatedyamlheredoc is not wired into any CI LINTER_FLAGS enforcement list, so nothing breaks today. But the linter targets generated-workflow shell script strings, which are exactly where arithmetic idioms like (( i << 1 )) or retry-backoff/bitmask logic are likely to show up as the generator grows -- the first such addition would produce a confusing, incorrect lint failure with no legitimate fix available (there's no heredoc to remove).
Suggested fix
Broaden the arithmetic guard to also match bare ((, not just $(( -- e.g. search for "((" instead of "$((" in the "are we inside an open arithmetic expression" check (or check both spellings). Add a testdata case for "(( count << 1 ))\n" (and ideally "if (( a << 2 )); then\n") alongside the existing $((1 << 2)) case to lock in the fix.
Location
pkg/linters/generatedyamlheredoc/generatedyamlheredoc.go:89 (the $(( guard)
pkg/linters/generatedyamlheredoc/testdata/src/generatedyamlheredoc/generatedyamlheredoc.go:13 (existing $(( test case, needs a bare-(( sibling)
Generated by 🤖 Sergo - Serena Go Expert · agent · 131.1 AIC · ⌖ 4.82 AIC · ⊞ 6.3K · ◷
Summary
generatedyamlheredoc(pkg/linters/generatedyamlheredoc/generatedyamlheredoc.go) flags shell heredocs embedded in generated workflow YAML, but its arithmetic-context guard only recognizes the$((spelling of shell arithmetic expansion. Bare(( ... ))arithmetic commands/conditions are not recognized, so a<<bit-shift inside one is misreported as a heredoc.Evidence
lineContainsShellHeredoc(generatedyamlheredoc.go:89) special-cases arithmetic via:This only looks for the literal substring
"$((". Bash also supports the arithmetic compound command/conditional(( expr ))(no leading$), e.g.(( count << 1 )),if (( retries << 1 > max )); then. For that spelling,line[:operatorIndex]contains"(("but never"$((", so the guard is skipped entirely.The code then falls through to the delimiter check:
isShellWordByteaccepts0-9, so any numeric right-hand operand (<< 1,<< 2, ...) satisfiesdelimiterStart, and the line is reported as a heredoc.The existing test suite only covers the
$((spelling:"echo $((1 << 2))\n"intestdata/src/generatedyamlheredoc/generatedyamlheredoc.go:13is correctly not flagged. There is no test case for bare(( ... << ... )), so this gap is untested and unguarded.Reproduction
Add this string to the testdata fixture and run the analyzer:
"(( count << 1 ))\n",Expected: not flagged (it's arithmetic, not a heredoc). Actual: flagged with "generated workflow shell uses a heredoc; pass plain content through an environment variable to a JavaScript renderer instead (do not base64 encode it)" -- a false positive, since there is no heredoc here at all.
Impact
Currently latent: a repo-wide search of
pkg/**/*.go(non-test) found no production string literal combining bare((arithmetic with<<, andgeneratedyamlheredocis not wired into any CILINTER_FLAGSenforcement list, so nothing breaks today. But the linter targets generated-workflow shell script strings, which are exactly where arithmetic idioms like(( i << 1 ))or retry-backoff/bitmask logic are likely to show up as the generator grows -- the first such addition would produce a confusing, incorrect lint failure with no legitimate fix available (there's no heredoc to remove).Suggested fix
Broaden the arithmetic guard to also match bare
((, not just$((-- e.g. search for"(("instead of"$(("in the "are we inside an open arithmetic expression" check (or check both spellings). Add a testdata case for"(( count << 1 ))\n"(and ideally"if (( a << 2 )); then\n") alongside the existing$((1 << 2))case to lock in the fix.Location
pkg/linters/generatedyamlheredoc/generatedyamlheredoc.go:89(the$((guard)pkg/linters/generatedyamlheredoc/testdata/src/generatedyamlheredoc/generatedyamlheredoc.go:13(existing$((test case, needs a bare-((sibling)