@@ -56,12 +56,23 @@ suite('TreeSitterCommandParser', () => {
5656 test ( 'variable assignments' , ( ) => t ( 'VAR=value command1 && echo $VAR' , [ 'VAR=value command1' , 'echo $VAR' ] ) ) ;
5757 test ( 'redirections' , ( ) => t ( 'echo hello > file.txt && cat < file.txt' , [ 'echo hello' , 'cat' ] ) ) ;
5858 test ( 'arithmetic expansion' , ( ) => t ( 'echo $((1 + 2)) && ls' , [ 'echo $((1 + 2))' , 'ls' ] ) ) ;
59+ test ( 'nested command substitution' , ( ) => t ( 'echo $(cat $(echo file.txt)) && ls' , [ 'echo $(cat $(echo file.txt))' , 'cat $(echo file.txt)' , 'echo file.txt' , 'ls' ] ) ) ;
60+ test ( 'mixed operators' , ( ) => t ( 'cmd1 && cmd2 || cmd3; cmd4 | cmd5 & cmd6' , [ 'cmd1' , 'cmd2' , 'cmd3' , 'cmd4' , 'cmd5' , 'cmd6' ] ) ) ;
61+ test ( 'parameter expansion' , ( ) => t ( 'echo ${VAR:-default} && echo ${#VAR}' , [ 'echo ${VAR:-default}' , 'echo ${#VAR}' ] ) ) ;
62+ test ( 'process substitution' , ( ) => t ( 'diff <(sort file1) <(sort file2) && echo done' , [ 'diff <(sort file1) <(sort file2)' , 'sort file1' , 'sort file2' , 'echo done' ] ) ) ;
63+ test ( 'brace expansion' , ( ) => t ( 'echo {a,b,c}.txt && ls' , [ 'echo {a,b,c}.txt' , 'ls' ] ) ) ;
64+ test ( 'tilde expansion' , ( ) => t ( 'cd ~/Documents && ls ~/.bashrc' , [ 'cd ~/Documents' , 'ls ~/.bashrc' ] ) ) ;
5965
6066 suite ( 'control flow and structures' , ( ) => {
6167 test ( 'if-then-else' , ( ) => t ( 'if [ -f file.txt ]; then cat file.txt; else echo "not found"; fi' , [ 'cat file.txt' , 'echo "not found"' ] ) ) ;
6268 test ( 'simple iteration' , ( ) => t ( 'for file in *.txt; do cat "$file"; done' , [ 'cat "$file"' ] ) ) ;
6369 test ( 'function declaration and call' , ( ) => t ( 'function test_func() { echo "inside function"; } && test_func' , [ 'echo "inside function"' , 'test_func' ] ) ) ;
6470 test ( 'heredoc with commands' , ( ) => t ( 'cat << EOF\nhello\nworld\nEOF\necho done' , [ 'cat' , 'echo done' ] ) ) ;
71+ test ( 'while loop' , ( ) => t ( 'while read line; do echo "$line"; done < file.txt' , [ 'read line' , 'echo "$line"' ] ) ) ;
72+ test ( 'case statement' , ( ) => t ( 'case $var in pattern1) echo "match1" ;; pattern2) echo "match2" ;; esac' , [ 'echo "match1"' , 'echo "match2"' ] ) ) ;
73+ test ( 'until loop' , ( ) => t ( 'until [ -f ready.txt ]; do sleep 1; done && echo ready' , [ 'sleep 1' , 'echo ready' ] ) ) ;
74+ test ( 'nested conditionals' , ( ) => t ( 'if [ -f file ]; then if [ -r file ]; then cat file; fi; fi' , [ 'cat file' ] ) ) ;
75+ test ( 'test command alternatives' , ( ) => t ( '[[ -f file ]] && cat file || echo missing' , [ 'cat file' , 'echo missing' ] ) ) ;
6576 } ) ;
6677
6778 suite ( 'edge cases' , ( ) => {
@@ -71,6 +82,14 @@ suite('TreeSitterCommandParser', () => {
7182 test ( 'special characters' , ( ) => t ( 'echo "πλαςε 测试 🚀" && ls' , [ 'echo "πλαςε 测试 🚀"' , 'ls' ] ) ) ;
7283 test ( 'multiline with continuations' , ( ) => t ( 'echo hello \\\n&& echo world \\\n&& ls' , [ 'echo hello' , 'echo world' , 'ls' ] ) ) ;
7384 test ( 'commands with comments' , ( ) => t ( 'echo hello # this is a comment\nls # another comment' , [ 'echo hello' , 'ls' ] ) ) ;
85+ test ( 'empty command in chain' , ( ) => t ( 'echo hello && && echo world' , [ 'echo hello' , 'echo world' ] ) ) ;
86+ test ( 'trailing operators' , ( ) => t ( 'echo hello &&' , [ 'echo hello' , '' ] ) ) ;
87+ test ( 'only operators' , ( ) => t ( '&& || ;' , [ ] ) ) ;
88+ test ( 'nested quotes' , ( ) => t ( 'echo "outer \"inner\" outer" && ls' , [ 'echo "outer \"inner\" outer"' , 'ls' ] ) ) ;
89+ test ( 'incomplete escape sequences' , ( ) => t ( 'echo hello\\ && ls' , [ 'echo hello\\ ' , 'ls' ] ) ) ;
90+ test ( 'mixed quote types' , ( ) => t ( 'echo "hello \`world\`" && echo \'test\'' , [ 'echo "hello \`world\`"' , 'world' , 'echo \'test\'' ] ) ) ;
91+ test ( 'deeply nested structures' , ( ) => t ( 'echo $(echo $(echo $(echo nested))) && ls' , [ 'echo $(echo $(echo $(echo nested)))' , 'echo $(echo $(echo nested))' , 'echo $(echo nested)' , 'echo nested' , 'ls' ] ) ) ;
92+ test ( 'unicode command names' , ( ) => t ( '测试命令 && echo done' , [ '测试命令' , 'echo done' ] ) ) ;
7493 } ) ;
7594
7695 // TODO: These should be common but the pwsh grammar doesn't handle && yet https://github.com/microsoft/vscode/issues/272704
@@ -93,6 +112,32 @@ suite('TreeSitterCommandParser', () => {
93112 'nproc' ,
94113 'make install PREFIX=/usr/local'
95114 ] ) ) ;
115+ test ( 'deployment script' , ( ) => t ( 'rsync -avz --delete src/ user@server:/path/ && ssh user@server "systemctl restart service" && echo "Deployed successfully"' , [
116+ 'rsync -avz --delete src/ user@server:/path/' ,
117+ 'ssh user@server "systemctl restart service"' ,
118+ 'echo "Deployed successfully"'
119+ ] ) ) ;
120+ test ( 'database backup script' , ( ) => t ( 'mysqldump -u user -p database > backup_$(date +%Y%m%d).sql && gzip backup_$(date +%Y%m%d).sql && echo "Backup complete"' , [
121+ 'mysqldump -u user -p database' ,
122+ 'date +%Y%m%d' ,
123+ 'gzip backup_$(date +%Y%m%d).sql' ,
124+ 'date +%Y%m%d' ,
125+ 'echo "Backup complete"'
126+ ] ) ) ;
127+ test ( 'log analysis pipeline' , ( ) => t ( 'tail -f /var/log/app.log | grep ERROR | while read line; do echo "$(date): $line" >> error.log; done' , [
128+ 'tail -f /var/log/app.log' ,
129+ 'grep ERROR' ,
130+ 'read line' ,
131+ 'echo "$(date): $line"' ,
132+ 'date'
133+ ] ) ) ;
134+ test ( 'conditional installation' , ( ) => t ( 'which docker || (curl -fsSL https://get.docker.com | sh && systemctl enable docker) && docker --version' , [
135+ 'which docker' ,
136+ 'curl -fsSL https://get.docker.com' ,
137+ 'sh' ,
138+ 'systemctl enable docker' ,
139+ 'docker --version'
140+ ] ) ) ;
96141 } ) ;
97142 } ) ;
98143
@@ -111,12 +156,27 @@ suite('TreeSitterCommandParser', () => {
111156 test ( 'here-strings' , ( ) => t ( 'Write-Host @"\nhello\nworld\n"@ ; Get-Date' , [ 'Write-Host @"\nhello\nworld\n"@' , 'Get-Date' ] ) ) ;
112157 test ( 'method calls' , ( ) => t ( '"hello".ToUpper() ; Get-Date' , [ 'Get-Date' ] ) ) ;
113158 test ( 'complex quoting' , ( ) => t ( 'Write-Host "She said `"Hello`"" ; Write-Host \'Single quotes\'' , [ 'Write-Host "She said `"Hello`""' , 'Write-Host \'Single quotes\'' ] ) ) ;
159+ test ( 'array operations' , ( ) => t ( '$arr = @(1,2,3); $arr | ForEach-Object { $_ * 2 }' , [ 'ForEach-Object { $_ * 2 }' ] ) ) ;
160+ test ( 'hashtable operations' , ( ) => t ( '$hash = @{key="value"}; Write-Host $hash.key' , [ 'Write-Host $hash.key' ] ) ) ;
161+ test ( 'type casting' , ( ) => t ( '[int]"123" + [int]"456" ; Write-Host "done"' , [ 'Write-Host "done"' ] ) ) ;
162+ test ( 'regex operations' , ( ) => t ( '"hello world" -match "w.*d" ; Get-Date' , [ 'Get-Date' ] ) ) ;
163+ test ( 'comparison operators' , ( ) => t ( '5 -gt 3 -and "hello" -like "h*" ; Write-Host "true"' , [ 'Write-Host "true"' ] ) ) ;
164+ test ( 'null-conditional operators' , ( ) => t ( '$obj?.Property?.SubProperty ; Get-Date' , [ 'Get-Date' ] ) ) ;
165+ test ( 'string interpolation' , ( ) => t ( '$name="World"; "Hello $name" ; Get-Date' , [ 'Get-Date' ] ) ) ;
166+ test ( 'expandable strings' , ( ) => t ( '$var="test"; "Value: $($var.ToUpper())" ; Get-Date' , [ 'Get-Date' ] ) ) ;
114167
115168 suite ( 'Control flow and structures' , ( ) => {
116169 test ( 'logical and' , ( ) => t ( 'Test-Path "file.txt" -and Get-Content "file.txt"' , [ 'Test-Path "file.txt" -and Get-Content "file.txt"' ] ) ) ;
117170 test ( 'foreach with script block' , ( ) => t ( 'ForEach-Object { Write-Host $_.Name } ; Get-Date' , [ 'ForEach-Object { Write-Host $_.Name }' , 'Write-Host $_.Name' , 'Get-Date' ] ) ) ;
118171 test ( 'if-else' , ( ) => t ( 'if (Test-Path "file.txt") { Get-Content "file.txt" } else { Write-Host "not found" }' , [ 'Test-Path "file.txt"' , 'Get-Content "file.txt"' , 'Write-Host "not found"' ] ) ) ;
119172 test ( 'error handling' , ( ) => t ( 'try { Get-Content "file.txt" } catch { Write-Error "failed" }' , [ 'Get-Content "file.txt"' , 'Write-Error "failed"' ] ) ) ;
173+ test ( 'switch statement' , ( ) => t ( 'switch ($var) { 1 { "one" } 2 { "two" } default { "other" } } ; Get-Date' , [ 'Get-Date' ] ) ) ;
174+ test ( 'do-while loop' , ( ) => t ( 'do { Write-Host $i; $i++ } while ($i -lt 5) ; Get-Date' , [ 'Write-Host $i' , 'Get-Date' ] ) ) ;
175+ test ( 'for loop' , ( ) => t ( 'for ($i=0; $i -lt 5; $i++) { Write-Host $i } ; Get-Date' , [ 'Write-Host $i' , 'Get-Date' ] ) ) ;
176+ test ( 'foreach loop with range' , ( ) => t ( 'foreach ($i in 1..5) { Write-Host $i } ; Get-Date' , [ '1..5' , 'Write-Host $i' , 'Get-Date' ] ) ) ;
177+ test ( 'break and continue' , ( ) => t ( 'while ($true) { if ($condition) { break } ; Write-Host "running" } ; Get-Date' , [ 'Write-Host "running"' , 'Get-Date' ] ) ) ;
178+ test ( 'nested try-catch-finally' , ( ) => t ( 'try { try { Get-Content "file" } catch { throw } } catch { Write-Error "outer" } finally { Write-Host "cleanup" }' , [ 'Get-Content "file"' , 'Write-Error "outer"' , 'Write-Host "cleanup"' ] ) ) ;
179+ test ( 'parallel processing' , ( ) => t ( '1..10 | ForEach-Object -Parallel { Start-Sleep 1; Write-Host $_ } ; Get-Date' , [ '1..10 ' , 'ForEach-Object -Parallel { Start-Sleep 1; Write-Host $_ }' , 'Start-Sleep 1' , 'Write-Host $_' , 'Get-Date' ] ) ) ;
120180 } ) ;
121181 } ) ;
122182
0 commit comments