@@ -1053,6 +1053,43 @@ describe('code placeholder compiler', () => {
10531053 'total=$(( $(( 1 + 1 )) + {{KEY}} ))' ,
10541054 'cat <<PAYLOAD\n$(( {{KEY}} * 2 ))\nPAYLOAD' ,
10551055 'cat <<PAYLOAD\n$[ {{KEY}} * 2 ]\nPAYLOAD' ,
1056+ 'echo $(( $(cat <<EOF\n{{KEY}}\nEOF\n) + 1 ))' ,
1057+ "echo $(( $(cat <<'EOF'\n{{KEY}}\nEOF\n) + 1 ))" ,
1058+ "echo $(( $(cat <<-'EOF'\n\t{{KEY}}\n\tEOF\n) + 1 ))" ,
1059+ '[[ {{KEY}} -eq 0 ]] && echo zero' ,
1060+ '[[ "{{KEY}}" -eq 0 ]] && echo zero' ,
1061+ 'if [[ 0 -lt {{KEY}} ]]; then echo positive; fi' ,
1062+ '[[ -n x && ( "{{KEY}}" -ge 1 ) ]]' ,
1063+ '[[ $(printf "%s" "{{KEY}}") -ne 0 ]]' ,
1064+ '[[ $(printf "%s" "{{KEY}}"; echo 1) -le 0 ]]' ,
1065+ '[[ $(cat <<EOF\n{{KEY}}\nEOF\n) -gt 0 ]]' ,
1066+ 'let "x={{KEY}}"' ,
1067+ 'let x={{KEY}}+1' ,
1068+ 'declare -i x="{{KEY}}"' ,
1069+ 'typeset -i x={{KEY}}' ,
1070+ 'f() { local -i x="{{KEY}}"; }' ,
1071+ 'declare -ai values=("{{KEY}}")' ,
1072+ 'declare -x -i x="{{KEY}}"' ,
1073+ 'declare -i x; x="{{KEY}}"' ,
1074+ 'f() { x+={{KEY}}; }; typeset -i x' ,
1075+ // biome-ignore lint/suspicious/noTemplateCurlyInString: shell parameter expansion, not a JS template
1076+ 'a=(1 2); echo "${a[{{KEY}}]}"' ,
1077+ 'a[{{KEY}}]=1' ,
1078+ 'a=([{{KEY}}]=1)' ,
1079+ 'test -v "a[{{KEY}}]"' ,
1080+ 'read "a[{{KEY}}]" <<< x' ,
1081+ // biome-ignore lint/suspicious/noTemplateCurlyInString: shell parameter expansion, not a JS template
1082+ 's=abc; echo "${s:{{KEY}}}"' ,
1083+ // biome-ignore lint/suspicious/noTemplateCurlyInString: shell parameter expansion, not a JS template
1084+ 's=abc; echo "${s:0:{{KEY}}}"' ,
1085+ // biome-ignore lint/suspicious/noTemplateCurlyInString: shell parameter expansion, not a JS template
1086+ 's=abc; echo "${s: -1:{{KEY}}}"' ,
1087+ // biome-ignore lint/suspicious/noTemplateCurlyInString: shell parameter expansion, not a JS template
1088+ 'set -- a b; echo "${@:{{KEY}}}"' ,
1089+ // biome-ignore lint/suspicious/noTemplateCurlyInString: shell parameter expansion, not a JS template
1090+ 'cat <<PAYLOAD\n${a[{{KEY}}]}\nPAYLOAD' ,
1091+ // biome-ignore lint/suspicious/noTemplateCurlyInString: shell parameter expansion, not a JS template
1092+ 'cat <<PAYLOAD\n${s:{{KEY}}}\nPAYLOAD' ,
10561093 ] ) ( 'rejects shell placeholders whose values enter arithmetic: %s' , async ( code ) => {
10571094 await expect (
10581095 compileCodePlaceholders ( {
@@ -1087,6 +1124,32 @@ describe('code placeholder compiler', () => {
10871124 )
10881125 } )
10891126
1127+ it ( 'compiles shell placeholders beside arithmetic that never evaluates them' , async ( ) => {
1128+ const value = 'values[$(printf injected >&2)]'
1129+ const compiled = await compileCodePlaceholders ( {
1130+ code : [
1131+ '[ "{{KEY}}" -eq 0 ] 2>/dev/null || printf "%s\\n" not-zero' ,
1132+ '[[ "{{KEY}}" == values* && 1 -eq 1 ]] && printf "%s\\n" matched' ,
1133+ 'let total=1+1; printf "%s\\n" "{{KEY}}"' ,
1134+ 'f() { local copy="{{KEY}}"; printf "%s\\n" "$copy"; }; f' ,
1135+ 'declare copy="{{KEY}}"; printf "%s\\n" "$copy"' ,
1136+ // biome-ignore lint/suspicious/noTemplateCurlyInString: shell parameter expansion, not a JS template
1137+ 'printf "%s\\n" "${missing:-{{KEY}}}"' ,
1138+ // biome-ignore lint/suspicious/noTemplateCurlyInString: shell parameter expansion, not a JS template
1139+ 'a=(x y); printf "%s\\n" "${a[1]}{{KEY}}"' ,
1140+ 'cat <<PAYLOAD' ,
1141+ '{{KEY}} $(( 1 + 1 ))' ,
1142+ 'PAYLOAD' ,
1143+ ] . join ( '\n' ) ,
1144+ language : CodeLanguage . Shell ,
1145+ environmentVariables : { KEY : value } ,
1146+ } )
1147+
1148+ expect ( executeShell ( `{\n${ compiled . code } \n} 2>&1` , compiled . bindings ) ) . toBe (
1149+ `not-zero\nmatched\n${ value } \n${ value } \n${ value } \n${ value } \ny${ value } \n${ value } 2\n`
1150+ )
1151+ } )
1152+
10901153 it ( 'discovers shell arithmetic placeholders without compiling missing values' , async ( ) => {
10911154 const code = 'total=$(( {{MISSING}} + {{KEY}} ))'
10921155 await expect ( analyzeCodePlaceholders ( code , CodeLanguage . Shell ) ) . resolves . toEqual ( [
0 commit comments