@@ -31,11 +31,14 @@ interface ShellScanFrame {
3131 kind : 'root' | 'command' | 'arithmetic' | 'backtick'
3232 quote : ShellQuote
3333 parenthesisDepth : number
34+ bracketDepth ?: number
3435 literalRoot : boolean
3536}
3637
3738interface ShellOccurrenceContext {
3839 quote : ShellQuote
40+ /** Includes nested command substitutions whose output can become an arithmetic operand. */
41+ arithmetic ?: boolean
3942 unsupported ?: 'escaped sequence'
4043}
4144
@@ -445,10 +448,11 @@ function isShellAssignmentName(code: string, occurrence: CodePlaceholderOccurren
445448function getUnsupportedShellPosition (
446449 code : string ,
447450 occurrence : CodePlaceholderOccurrence ,
448- quote : ShellQuote
451+ context : ShellOccurrenceContext
449452) : string | undefined {
453+ if ( context . arithmetic ) return 'in shell arithmetic'
450454 if ( code [ occurrence . start - 1 ] === '$' ) return 'immediately after "$"'
451- if ( quote !== 'none' ) return undefined
455+ if ( context . quote !== 'none' ) return undefined
452456
453457 const lineStart = Math . max (
454458 code . lastIndexOf ( '\n' , occurrence . start - 1 ) ,
@@ -488,6 +492,7 @@ function collectShellOccurrenceContexts(
488492 { kind : 'root' , quote : 'none' , parenthesisDepth : 0 , literalRoot } ,
489493 ]
490494 let skippedRangeIndex = 0
495+ let arithmeticDepth = 0
491496
492497 for ( let index = start ; index < end ; ) {
493498 const frame = frames . at ( - 1 )
@@ -507,7 +512,7 @@ function collectShellOccurrenceContexts(
507512
508513 const occurrence = occurrenceByStart . get ( index )
509514 if ( occurrence ) {
510- contexts . set ( occurrence , { quote : frame . quote } )
515+ contexts . set ( occurrence , { quote : frame . quote , arithmetic : arithmeticDepth > 0 } )
511516 index = occurrence . end
512517 continue
513518 }
@@ -533,6 +538,24 @@ function collectShellOccurrenceContexts(
533538 }
534539 continue
535540 }
541+ const arithmeticExpansion =
542+ character === '$' &&
543+ ( ( code [ index + 1 ] === '(' && code [ index + 2 ] === '(' ) || code [ index + 1 ] === '[' )
544+ const arithmeticCommand =
545+ frame . quote === 'none' && ! frame . literalRoot && shellArithmeticCommandStarts ( code , index )
546+ if ( arithmeticExpansion || arithmeticCommand ) {
547+ const brackets = arithmeticExpansion && code [ index + 1 ] === '['
548+ frames . push ( {
549+ kind : 'arithmetic' ,
550+ quote : 'none' ,
551+ parenthesisDepth : brackets ? 0 : 2 ,
552+ ...( brackets ? { bracketDepth : 1 } : { } ) ,
553+ literalRoot : false ,
554+ } )
555+ arithmeticDepth += 1
556+ index += arithmeticExpansion && ! brackets ? 3 : 2
557+ continue
558+ }
536559 if ( frame . quote === 'double' ) {
537560 if ( character === '\\' ) {
538561 const escaped = occurrenceByStart . get ( index + 1 )
@@ -572,7 +595,7 @@ function collectShellOccurrenceContexts(
572595 index += 1
573596 continue
574597 }
575- if ( ! frame . literalRoot && shellCommentStarts ( code , index ) ) {
598+ if ( frame . kind !== 'arithmetic' && ! frame . literalRoot && shellCommentStarts ( code , index ) ) {
576599 const newline = code . indexOf ( '\n' , index )
577600 index = newline === - 1 || newline >= end ? end : newline + 1
578601 continue
@@ -622,14 +645,29 @@ function collectShellOccurrenceContexts(
622645 index += 1
623646 continue
624647 }
625- if ( frame . kind === 'command' && character === '(' ) {
648+ if ( frame . kind === 'arithmetic' && frame . bracketDepth !== undefined ) {
649+ if ( character === '[' ) frame . bracketDepth += 1
650+ if ( character === ']' ) {
651+ frame . bracketDepth -= 1
652+ if ( frame . bracketDepth === 0 ) {
653+ frames . pop ( )
654+ arithmeticDepth -= 1
655+ }
656+ }
657+ index += 1
658+ continue
659+ }
660+ if ( ( frame . kind === 'command' || frame . kind === 'arithmetic' ) && character === '(' ) {
626661 frame . parenthesisDepth += 1
627662 index += 1
628663 continue
629664 }
630- if ( frame . kind === 'command' && character === ')' ) {
665+ if ( ( frame . kind === 'command' || frame . kind === 'arithmetic' ) && character === ')' ) {
631666 frame . parenthesisDepth -= 1
632- if ( frame . parenthesisDepth === 0 ) frames . pop ( )
667+ if ( frame . parenthesisDepth === 0 ) {
668+ frames . pop ( )
669+ if ( frame . kind === 'arithmetic' ) arithmeticDepth -= 1
670+ }
633671 index += 1
634672 continue
635673 }
@@ -849,7 +887,7 @@ export async function compileShellPlaceholders(
849887 const unsupportedPosition = getUnsupportedShellPosition (
850888 input . code ,
851889 occurrence ,
852- occurrenceContext . quote
890+ occurrenceContext
853891 )
854892 if ( unsupportedPosition ) {
855893 if ( context . hasValue ( occurrence . name ) ) {
@@ -904,7 +942,7 @@ export async function compileShellPlaceholders(
904942 const unsupportedPosition = getUnsupportedShellPosition (
905943 input . code ,
906944 occurrence ,
907- occurrenceContext . quote
945+ occurrenceContext
908946 )
909947 if ( unsupportedPosition ) {
910948 if ( context . hasValue ( occurrence . name ) ) {
0 commit comments