github.com/cilki/sh@v2.6.4+incompatible/syntax/parser_test.go (about) 1 // Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc> 2 // See LICENSE for licensing information 3 4 package syntax 5 6 import ( 7 "bytes" 8 "errors" 9 "fmt" 10 "io" 11 "os" 12 "os/exec" 13 "reflect" 14 "regexp" 15 "strings" 16 "testing" 17 18 "github.com/kr/pretty" 19 ) 20 21 func TestKeepComments(t *testing.T) { 22 t.Parallel() 23 in := "# foo\ncmd\n# bar" 24 want := &File{StmtList: StmtList{ 25 Stmts: []*Stmt{{ 26 Comments: []Comment{{Text: " foo"}}, 27 Cmd: litCall("cmd"), 28 }}, 29 Last: []Comment{{Text: " bar"}}, 30 }} 31 singleParse(NewParser(KeepComments), in, want)(t) 32 } 33 34 func TestParseBash(t *testing.T) { 35 t.Parallel() 36 p := NewParser() 37 for i, c := range append(fileTests, fileTestsNoPrint...) { 38 want := c.Bash 39 if want == nil { 40 continue 41 } 42 for j, in := range c.Strs { 43 t.Run(fmt.Sprintf("%03d-%d", i, j), singleParse(p, in, want)) 44 } 45 } 46 } 47 48 func TestParsePosix(t *testing.T) { 49 t.Parallel() 50 p := NewParser(Variant(LangPOSIX)) 51 for i, c := range append(fileTests, fileTestsNoPrint...) { 52 want := c.Posix 53 if want == nil { 54 continue 55 } 56 for j, in := range c.Strs { 57 t.Run(fmt.Sprintf("%03d-%d", i, j), 58 singleParse(p, in, want)) 59 } 60 } 61 } 62 63 func TestParseMirBSDKorn(t *testing.T) { 64 t.Parallel() 65 p := NewParser(Variant(LangMirBSDKorn)) 66 for i, c := range append(fileTests, fileTestsNoPrint...) { 67 want := c.MirBSDKorn 68 if want == nil { 69 continue 70 } 71 for j, in := range c.Strs { 72 t.Run(fmt.Sprintf("%03d-%d", i, j), 73 singleParse(p, in, want)) 74 } 75 } 76 } 77 78 var ( 79 hasBash44 bool 80 hasDash059 bool 81 hasMksh56 bool 82 ) 83 84 func TestMain(m *testing.M) { 85 os.Setenv("LANGUAGE", "en_US.UTF8") 86 os.Setenv("LC_ALL", "en_US.UTF8") 87 hasBash44 = cmdContains("version 4.4", "bash", "--version") 88 // dash provides no way to check its version, so we have to 89 // check if it's new enough as to not have the bug that breaks 90 // our integration tests. Blergh. 91 hasDash059 = cmdContains("Bad subst", "dash", "-c", "echo ${#<}") 92 hasMksh56 = cmdContains(" R56 ", "mksh", "-c", "echo $KSH_VERSION") 93 os.Exit(m.Run()) 94 } 95 96 func cmdContains(substr string, cmd string, args ...string) bool { 97 out, err := exec.Command(cmd, args...).CombinedOutput() 98 got := string(out) 99 if err != nil { 100 got += "\n" + err.Error() 101 } 102 return strings.Contains(got, substr) 103 } 104 105 var extGlobRe = regexp.MustCompile(`[@?*+!]\(`) 106 107 func confirmParse(in, cmd string, wantErr bool) func(*testing.T) { 108 return func(t *testing.T) { 109 t.Parallel() 110 var opts []string 111 if cmd == "bash" && extGlobRe.MatchString(in) { 112 // otherwise bash refuses to parse these 113 // properly. Also avoid -n since that too makes 114 // bash bail. 115 in = "shopt -s extglob\n" + in 116 } else if !wantErr { 117 // -n makes bash accept invalid inputs like 118 // "let" or "`{`", so only use it in 119 // non-erroring tests. Should be safe to not use 120 // -n anyway since these are supposed to just 121 // fail. 122 // also, -n will break if we are using extglob 123 // as extglob is not actually applied. 124 opts = append(opts, "-n") 125 } 126 cmd := exec.Command(cmd, opts...) 127 cmd.Stdin = strings.NewReader(in) 128 var stderr bytes.Buffer 129 cmd.Stderr = &stderr 130 err := cmd.Run() 131 if stderr.Len() > 0 { 132 // bash sometimes likes to error on an input via stderr 133 // while forgetting to set the exit code to non-zero. 134 // Fun. 135 if s := stderr.String(); !strings.Contains(s, ": warning: ") { 136 err = errors.New(s) 137 } 138 } 139 if err != nil && strings.Contains(err.Error(), "command not found") { 140 err = nil 141 } 142 if wantErr && err == nil { 143 t.Fatalf("Expected error in `%s` of %q, found none", strings.Join(cmd.Args, " "), in) 144 } else if !wantErr && err != nil { 145 t.Fatalf("Unexpected error in `%s` of %q: %v", strings.Join(cmd.Args, " "), in, err) 146 } 147 } 148 } 149 150 func TestParseBashConfirm(t *testing.T) { 151 if testing.Short() { 152 t.Skip("calling bash is slow.") 153 } 154 if !hasBash44 { 155 t.Skip("bash 4.4 required to run") 156 } 157 i := 0 158 for _, c := range append(fileTests, fileTestsNoPrint...) { 159 if c.Bash == nil { 160 continue 161 } 162 for j, in := range c.Strs { 163 t.Run(fmt.Sprintf("%03d-%d", i, j), 164 confirmParse(in, "bash", false)) 165 } 166 i++ 167 } 168 } 169 170 func TestParsePosixConfirm(t *testing.T) { 171 if testing.Short() { 172 t.Skip("calling dash is slow.") 173 } 174 if !hasDash059 { 175 t.Skip("dash 0.5.9 or newer required to run") 176 } 177 i := 0 178 for _, c := range append(fileTests, fileTestsNoPrint...) { 179 if c.Posix == nil { 180 continue 181 } 182 for j, in := range c.Strs { 183 t.Run(fmt.Sprintf("%03d-%d", i, j), 184 confirmParse(in, "dash", false)) 185 } 186 i++ 187 } 188 } 189 190 func TestParseMirBSDKornConfirm(t *testing.T) { 191 if testing.Short() { 192 t.Skip("calling mksh is slow.") 193 } 194 if !hasMksh56 { 195 t.Skip("mksh 56 required to run") 196 } 197 i := 0 198 for _, c := range append(fileTests, fileTestsNoPrint...) { 199 if c.MirBSDKorn == nil { 200 continue 201 } 202 for j, in := range c.Strs { 203 t.Run(fmt.Sprintf("%03d-%d", i, j), 204 confirmParse(in, "mksh", false)) 205 } 206 i++ 207 } 208 } 209 210 func TestParseErrBashConfirm(t *testing.T) { 211 if testing.Short() { 212 t.Skip("calling bash is slow.") 213 } 214 if !hasBash44 { 215 t.Skip("bash 4.4 required to run") 216 } 217 i := 0 218 for _, c := range shellTests { 219 want := c.common 220 if c.bsmk != nil { 221 want = c.bsmk 222 } 223 if c.bash != nil { 224 want = c.bash 225 } 226 if want == nil { 227 continue 228 } 229 wantErr := !strings.Contains(want.(string), " #NOERR") 230 t.Run(fmt.Sprintf("%03d", i), confirmParse(c.in, "bash", wantErr)) 231 i++ 232 } 233 } 234 235 func TestParseErrPosixConfirm(t *testing.T) { 236 if testing.Short() { 237 t.Skip("calling dash is slow.") 238 } 239 if !hasDash059 { 240 t.Skip("dash 0.5.9 or newer required to run") 241 } 242 i := 0 243 for _, c := range shellTests { 244 want := c.common 245 if c.posix != nil { 246 want = c.posix 247 } 248 if want == nil { 249 continue 250 } 251 wantErr := !strings.Contains(want.(string), " #NOERR") 252 t.Run(fmt.Sprintf("%03d", i), confirmParse(c.in, "dash", wantErr)) 253 i++ 254 } 255 } 256 257 func TestParseErrMirBSDKornConfirm(t *testing.T) { 258 if testing.Short() { 259 t.Skip("calling mksh is slow.") 260 } 261 if !hasMksh56 { 262 t.Skip("mksh 56 required to run") 263 } 264 i := 0 265 for _, c := range shellTests { 266 want := c.common 267 if c.bsmk != nil { 268 want = c.bsmk 269 } 270 if c.mksh != nil { 271 want = c.mksh 272 } 273 if want == nil { 274 continue 275 } 276 wantErr := !strings.Contains(want.(string), " #NOERR") 277 t.Run(fmt.Sprintf("%03d", i), confirmParse(c.in, "mksh", wantErr)) 278 i++ 279 } 280 } 281 282 func singleParse(p *Parser, in string, want *File) func(t *testing.T) { 283 return func(t *testing.T) { 284 got, err := p.Parse(newStrictReader(in), "") 285 if err != nil { 286 t.Fatalf("Unexpected error in %q: %v", in, err) 287 } 288 clearPosRecurse(t, in, got) 289 if !reflect.DeepEqual(got, want) { 290 t.Fatalf("syntax tree mismatch in %q\ndiff:\n%s", in, 291 strings.Join(pretty.Diff(want, got), "\n")) 292 } 293 } 294 } 295 296 func BenchmarkParse(b *testing.B) { 297 src := "" + 298 strings.Repeat("\n\n\t\t \n", 10) + 299 "# " + strings.Repeat("foo bar ", 10) + "\n" + 300 strings.Repeat("longlit_", 10) + "\n" + 301 "'" + strings.Repeat("foo bar ", 10) + "'\n" + 302 `"` + strings.Repeat("foo bar ", 10) + `"` + "\n" + 303 strings.Repeat("aa bb cc dd; ", 6) + 304 "a() { (b); { c; }; }; $(d; `e`)\n" + 305 "foo=bar; a=b; c=d$foo${bar}e $simple ${complex:-default}\n" + 306 "if a; then while b; do for c in d e; do f; done; done; fi\n" + 307 "a | b && c || d | e && g || f\n" + 308 "foo >a <b <<<c 2>&1 <<EOF\n" + 309 strings.Repeat("somewhat long heredoc line\n", 10) + 310 "EOF" + 311 "" 312 p := NewParser(KeepComments) 313 in := strings.NewReader(src) 314 for i := 0; i < b.N; i++ { 315 if _, err := p.Parse(in, ""); err != nil { 316 b.Fatal(err) 317 } 318 in.Reset(src) 319 } 320 } 321 322 type errorCase struct { 323 in string 324 common interface{} 325 bash, posix interface{} 326 bsmk, mksh interface{} 327 } 328 329 var shellTests = []errorCase{ 330 { 331 in: "echo \x80", 332 common: `1:6: invalid UTF-8 encoding #NOERR common shells use bytes`, 333 }, 334 { 335 in: "\necho \x80", 336 common: `2:6: invalid UTF-8 encoding #NOERR common shells use bytes`, 337 }, 338 { 339 in: "echo foo\x80bar", 340 common: `1:9: invalid UTF-8 encoding #NOERR common shells use bytes`, 341 }, 342 { 343 in: "echo foo\xc3", 344 common: `1:9: invalid UTF-8 encoding #NOERR common shells use bytes`, 345 }, 346 { 347 in: "#foo\xc3", 348 common: `1:5: invalid UTF-8 encoding #NOERR common shells use bytes`, 349 }, 350 { 351 in: "echo a\x80", 352 common: `1:7: invalid UTF-8 encoding #NOERR common shells use bytes`, 353 }, 354 { 355 in: "<<$\xc8\n$\xc8", 356 common: `1:4: invalid UTF-8 encoding #NOERR common shells use bytes`, 357 mksh: `1:4: invalid UTF-8 encoding`, // mksh says heredoc unclosed now? 358 }, 359 { 360 in: "echo $((foo\x80bar", 361 common: `1:12: invalid UTF-8 encoding`, 362 }, 363 { 364 in: `((# 1 + 2))`, 365 bash: `1:1: unsigned expressions are a mksh feature`, 366 }, 367 { 368 in: `$((# 1 + 2))`, 369 posix: `1:1: unsigned expressions are a mksh feature`, 370 bash: `1:1: unsigned expressions are a mksh feature`, 371 }, 372 { 373 in: `${ foo;}`, 374 posix: `1:1: "${ stmts;}" is a mksh feature`, 375 bash: `1:1: "${ stmts;}" is a mksh feature`, 376 }, 377 { 378 in: `${ `, 379 mksh: `1:1: reached EOF without matching ${ with }`, 380 }, 381 { 382 in: `${ foo;`, 383 mksh: `1:1: reached EOF without matching ${ with }`, 384 }, 385 { 386 in: `${ foo }`, 387 mksh: `1:1: reached EOF without matching ${ with }`, 388 }, 389 { 390 in: `${|foo;}`, 391 posix: `1:1: "${|stmts;}" is a mksh feature`, 392 bash: `1:1: "${|stmts;}" is a mksh feature`, 393 }, 394 { 395 in: `${|`, 396 mksh: `1:1: reached EOF without matching ${ with }`, 397 }, 398 { 399 in: `${|foo;`, 400 mksh: `1:1: reached EOF without matching ${ with }`, 401 }, 402 { 403 in: `${|foo }`, 404 mksh: `1:1: reached EOF without matching ${ with }`, 405 }, 406 { 407 in: "((foo\x80bar", 408 common: `1:6: invalid UTF-8 encoding`, 409 }, 410 { 411 in: ";\x80", 412 common: `1:2: invalid UTF-8 encoding`, 413 }, 414 { 415 in: "${a\x80", 416 common: `1:4: invalid UTF-8 encoding`, 417 }, 418 { 419 in: "${a#\x80", 420 common: `1:5: invalid UTF-8 encoding`, 421 }, 422 { 423 in: "${a-'\x80", 424 common: `1:6: invalid UTF-8 encoding`, 425 }, 426 { 427 in: "echo $((a |\x80", 428 common: `1:12: invalid UTF-8 encoding`, 429 }, 430 { 431 in: "!", 432 common: `1:1: "!" cannot form a statement alone`, 433 }, 434 { 435 // bash allows lone '!', unlike dash, mksh, and us. 436 in: "! !", 437 common: `1:1: cannot negate a command multiple times`, 438 bash: `1:1: cannot negate a command multiple times #NOERR`, 439 }, 440 { 441 in: "! ! foo", 442 common: `1:1: cannot negate a command multiple times #NOERR`, 443 posix: `1:1: cannot negate a command multiple times`, 444 }, 445 { 446 in: "}", 447 common: `1:1: "}" can only be used to close a block`, 448 }, 449 { 450 in: "then", 451 common: `1:1: "then" can only be used in an if`, 452 }, 453 { 454 in: "elif", 455 common: `1:1: "elif" can only be used in an if`, 456 }, 457 { 458 in: "fi", 459 common: `1:1: "fi" can only be used to end an if`, 460 }, 461 { 462 in: "do", 463 common: `1:1: "do" can only be used in a loop`, 464 }, 465 { 466 in: "done", 467 common: `1:1: "done" can only be used to end a loop`, 468 }, 469 { 470 in: "esac", 471 common: `1:1: "esac" can only be used to end a case`, 472 }, 473 { 474 in: "a=b { foo; }", 475 common: `1:12: "}" can only be used to close a block`, 476 }, 477 { 478 in: "a=b foo() { bar; }", 479 common: `1:8: a command can only contain words and redirects`, 480 }, 481 { 482 in: "a=b if foo; then bar; fi", 483 common: `1:13: "then" can only be used in an if`, 484 }, 485 { 486 in: ">f { foo; }", 487 common: `1:11: "}" can only be used to close a block`, 488 }, 489 { 490 in: ">f foo() { bar; }", 491 common: `1:7: a command can only contain words and redirects`, 492 }, 493 { 494 in: ">f if foo; then bar; fi", 495 common: `1:12: "then" can only be used in an if`, 496 }, 497 { 498 in: "if done; then b; fi", 499 common: `1:4: "done" can only be used to end a loop`, 500 }, 501 { 502 in: "'", 503 common: `1:1: reached EOF without closing quote '`, 504 }, 505 { 506 in: `"`, 507 common: `1:1: reached EOF without closing quote "`, 508 }, 509 { 510 in: `'\''`, 511 common: `1:4: reached EOF without closing quote '`, 512 }, 513 { 514 in: ";", 515 common: `1:1: ; can only immediately follow a statement`, 516 }, 517 { 518 in: "{ ; }", 519 common: `1:3: ; can only immediately follow a statement`, 520 }, 521 { 522 in: `"foo"(){ :; }`, 523 common: `1:1: invalid func name`, 524 mksh: `1:1: invalid func name #NOERR`, 525 }, 526 { 527 in: `foo$bar(){ :; }`, 528 common: `1:1: invalid func name`, 529 }, 530 { 531 in: "{", 532 common: `1:1: reached EOF without matching { with }`, 533 }, 534 { 535 in: "{ #}", 536 common: `1:1: reached EOF without matching { with }`, 537 }, 538 { 539 in: "(", 540 common: `1:1: reached EOF without matching ( with )`, 541 }, 542 { 543 in: ")", 544 common: `1:1: ) can only be used to close a subshell`, 545 }, 546 { 547 in: "`", 548 common: "1:1: reached EOF without closing quote `", 549 }, 550 { 551 in: ";;", 552 common: `1:1: ;; can only be used in a case clause`, 553 }, 554 { 555 in: "( foo;", 556 common: `1:1: reached EOF without matching ( with )`, 557 }, 558 { 559 in: "&", 560 common: `1:1: & can only immediately follow a statement`, 561 }, 562 { 563 in: "|", 564 common: `1:1: | can only immediately follow a statement`, 565 }, 566 { 567 in: "&&", 568 common: `1:1: && can only immediately follow a statement`, 569 }, 570 { 571 in: "||", 572 common: `1:1: || can only immediately follow a statement`, 573 }, 574 { 575 in: "foo; || bar", 576 common: `1:6: || can only immediately follow a statement`, 577 }, 578 { 579 in: "echo & || bar", 580 common: `1:8: || can only immediately follow a statement`, 581 }, 582 { 583 in: "echo & ; bar", 584 common: `1:8: ; can only immediately follow a statement`, 585 }, 586 { 587 in: "foo;;", 588 common: `1:4: ;; can only be used in a case clause`, 589 }, 590 { 591 in: "foo(", 592 common: `1:1: "foo(" must be followed by )`, 593 }, 594 { 595 in: "foo(bar", 596 common: `1:1: "foo(" must be followed by )`, 597 }, 598 { 599 in: "à(", 600 common: `1:1: "foo(" must be followed by )`, 601 }, 602 { 603 in: "foo'", 604 common: `1:4: reached EOF without closing quote '`, 605 }, 606 { 607 in: `foo"`, 608 common: `1:4: reached EOF without closing quote "`, 609 }, 610 { 611 in: `"foo`, 612 common: `1:1: reached EOF without closing quote "`, 613 }, 614 { 615 in: `"foobar\`, 616 common: `1:1: reached EOF without closing quote "`, 617 }, 618 { 619 in: `"foo\a`, 620 common: `1:1: reached EOF without closing quote "`, 621 }, 622 { 623 in: "foo()", 624 common: `1:1: "foo()" must be followed by a statement`, 625 mksh: `1:1: "foo()" must be followed by a statement #NOERR`, 626 }, 627 { 628 in: "foo() {", 629 common: `1:7: reached EOF without matching { with }`, 630 }, 631 { 632 in: "foo-bar() { x; }", 633 posix: `1:1: invalid func name`, 634 }, 635 { 636 in: "foò() { x; }", 637 posix: `1:1: invalid func name`, 638 }, 639 { 640 in: "echo foo(", 641 common: `1:9: a command can only contain words and redirects`, 642 }, 643 { 644 in: "echo &&", 645 common: `1:6: && must be followed by a statement`, 646 }, 647 { 648 in: "echo |", 649 common: `1:6: | must be followed by a statement`, 650 }, 651 { 652 in: "echo ||", 653 common: `1:6: || must be followed by a statement`, 654 }, 655 { 656 in: "echo | #bar", 657 common: `1:6: | must be followed by a statement`, 658 }, 659 { 660 in: "echo && #bar", 661 common: `1:6: && must be followed by a statement`, 662 }, 663 { 664 in: "`echo &&`", 665 common: `1:7: && must be followed by a statement`, 666 }, 667 { 668 in: "`echo |`", 669 common: `1:7: | must be followed by a statement`, 670 }, 671 { 672 in: "echo | ! bar", 673 common: `1:8: "!" can only be used in full statements`, 674 }, 675 { 676 in: "echo >", 677 common: `1:6: > must be followed by a word`, 678 }, 679 { 680 in: "echo >>", 681 common: `1:6: >> must be followed by a word`, 682 }, 683 { 684 in: "echo <", 685 common: `1:6: < must be followed by a word`, 686 }, 687 { 688 in: "echo 2>", 689 common: `1:7: > must be followed by a word`, 690 }, 691 { 692 in: "echo <\nbar", 693 common: `1:6: < must be followed by a word`, 694 }, 695 { 696 in: "echo | < #bar", 697 common: `1:8: < must be followed by a word`, 698 }, 699 { 700 in: "echo && > #", 701 common: `1:9: > must be followed by a word`, 702 }, 703 { 704 in: "<<", 705 common: `1:1: << must be followed by a word`, 706 }, 707 { 708 in: "<<EOF", 709 common: `1:1: unclosed here-document 'EOF' #NOERR`, 710 mksh: `1:1: unclosed here-document 'EOF'`, 711 }, 712 { 713 in: "<<EOF\n\\", 714 common: `1:1: unclosed here-document 'EOF' #NOERR`, 715 mksh: `1:1: unclosed here-document 'EOF'`, 716 }, 717 { 718 in: "<<EOF <`\n#\n`\n``", 719 common: `1:1: unclosed here-document 'EOF'`, 720 mksh: `1:1: unclosed here-document 'EOF'`, 721 }, 722 { 723 in: "<<'EOF'", 724 common: `1:1: unclosed here-document 'EOF' #NOERR`, 725 mksh: `1:1: unclosed here-document 'EOF'`, 726 }, 727 { 728 in: "<<\\EOF", 729 common: `1:1: unclosed here-document 'EOF' #NOERR`, 730 mksh: `1:1: unclosed here-document 'EOF'`, 731 }, 732 { 733 in: "<<\\\\EOF", 734 common: `1:1: unclosed here-document '\EOF' #NOERR`, 735 mksh: `1:1: unclosed here-document '\EOF'`, 736 }, 737 { 738 in: "<<-EOF", 739 common: `1:1: unclosed here-document 'EOF' #NOERR`, 740 mksh: `1:1: unclosed here-document 'EOF'`, 741 }, 742 { 743 in: "<<-EOF\n\t", 744 common: `1:1: unclosed here-document 'EOF' #NOERR`, 745 mksh: `1:1: unclosed here-document 'EOF'`, 746 }, 747 { 748 in: "<<-'EOF'\n\t", 749 common: `1:1: unclosed here-document 'EOF' #NOERR`, 750 mksh: `1:1: unclosed here-document 'EOF'`, 751 }, 752 { 753 in: "<<\nEOF\nbar\nEOF", 754 common: `1:1: << must be followed by a word`, 755 }, 756 { 757 in: "if", 758 common: `1:1: "if" must be followed by a statement list`, 759 }, 760 { 761 in: "if true;", 762 common: `1:1: "if <cond>" must be followed by "then"`, 763 }, 764 { 765 in: "if true then", 766 common: `1:1: "if <cond>" must be followed by "then"`, 767 }, 768 { 769 in: "if true; then bar;", 770 common: `1:1: if statement must end with "fi"`, 771 }, 772 { 773 in: "if true; then bar; fi#etc", 774 common: `1:1: if statement must end with "fi"`, 775 }, 776 { 777 in: "if a; then b; elif c;", 778 common: `1:15: "elif <cond>" must be followed by "then"`, 779 }, 780 { 781 in: "'foo' '", 782 common: `1:7: reached EOF without closing quote '`, 783 }, 784 { 785 in: "'foo\n' '", 786 common: `2:3: reached EOF without closing quote '`, 787 }, 788 { 789 in: "while", 790 common: `1:1: "while" must be followed by a statement list`, 791 }, 792 { 793 in: "while true;", 794 common: `1:1: "while <cond>" must be followed by "do"`, 795 }, 796 { 797 in: "while true; do bar", 798 common: `1:1: while statement must end with "done"`, 799 }, 800 { 801 in: "while true; do bar;", 802 common: `1:1: while statement must end with "done"`, 803 }, 804 { 805 in: "until", 806 common: `1:1: "until" must be followed by a statement list`, 807 }, 808 { 809 in: "until true;", 810 common: `1:1: "until <cond>" must be followed by "do"`, 811 }, 812 { 813 in: "until true; do bar", 814 common: `1:1: until statement must end with "done"`, 815 }, 816 { 817 in: "until true; do bar;", 818 common: `1:1: until statement must end with "done"`, 819 }, 820 { 821 in: "for", 822 common: `1:1: "for" must be followed by a literal`, 823 }, 824 { 825 in: "for i", 826 common: `1:1: "for foo" must be followed by "in", "do", ;, or a newline`, 827 }, 828 { 829 in: "for i in;", 830 common: `1:1: "for foo [in words]" must be followed by "do"`, 831 }, 832 { 833 in: "for i in 1 2 3;", 834 common: `1:1: "for foo [in words]" must be followed by "do"`, 835 }, 836 { 837 in: "for i in 1 2 &", 838 common: `1:1: "for foo [in words]" must be followed by "do"`, 839 }, 840 { 841 in: "for i in 1 2 (", 842 common: `1:14: word list can only contain words`, 843 }, 844 { 845 in: "for i in 1 2 3; do echo $i;", 846 common: `1:1: for statement must end with "done"`, 847 }, 848 { 849 in: "for i in 1 2 3; echo $i;", 850 common: `1:1: "for foo [in words]" must be followed by "do"`, 851 }, 852 { 853 in: "for 'i' in 1 2 3; do echo $i; done", 854 common: `1:1: "for" must be followed by a literal`, 855 }, 856 { 857 in: "for in 1 2 3; do echo $i; done", 858 common: `1:1: "for foo" must be followed by "in", "do", ;, or a newline`, 859 }, 860 { 861 in: "select", 862 bsmk: `1:1: "select" must be followed by a literal`, 863 }, 864 { 865 in: "select i", 866 bsmk: `1:1: "select foo" must be followed by "in", "do", ;, or a newline`, 867 }, 868 { 869 in: "select i in;", 870 bsmk: `1:1: "select foo [in words]" must be followed by "do"`, 871 }, 872 { 873 in: "select i in 1 2 3;", 874 bsmk: `1:1: "select foo [in words]" must be followed by "do"`, 875 }, 876 { 877 in: "select i in 1 2 3; do echo $i;", 878 bsmk: `1:1: select statement must end with "done"`, 879 }, 880 { 881 in: "select i in 1 2 3; echo $i;", 882 bsmk: `1:1: "select foo [in words]" must be followed by "do"`, 883 }, 884 { 885 in: "select 'i' in 1 2 3; do echo $i; done", 886 bsmk: `1:1: "select" must be followed by a literal`, 887 }, 888 { 889 in: "select in 1 2 3; do echo $i; done", 890 bsmk: `1:1: "select foo" must be followed by "in", "do", ;, or a newline`, 891 }, 892 { 893 in: "echo foo &\n;", 894 common: `2:1: ; can only immediately follow a statement`, 895 }, 896 { 897 in: "echo $(foo", 898 common: `1:6: reached EOF without matching ( with )`, 899 }, 900 { 901 in: "echo $((foo", 902 common: `1:6: reached EOF without matching $(( with ))`, 903 }, 904 { 905 in: `echo $((\`, 906 common: `1:6: reached EOF without matching $(( with ))`, 907 }, 908 { 909 in: `echo $((o\`, 910 common: `1:6: reached EOF without matching $(( with ))`, 911 }, 912 { 913 in: `echo $((foo\a`, 914 common: `1:6: reached EOF without matching $(( with ))`, 915 }, 916 { 917 in: `echo $(($(a"`, 918 common: `1:12: reached EOF without closing quote "`, 919 }, 920 { 921 in: "echo $((`echo 0`", 922 common: `1:6: reached EOF without matching $(( with ))`, 923 }, 924 { 925 in: `echo $((& $(`, 926 common: `1:9: & must follow an expression`, 927 }, 928 { 929 in: `echo $((a'`, 930 common: `1:10: not a valid arithmetic operator: '`, 931 }, 932 { 933 in: `echo $((a b"`, 934 common: `1:11: not a valid arithmetic operator: b`, 935 }, 936 { 937 in: "echo $(())", 938 common: `1:6: $(( must be followed by an expression #NOERR`, 939 }, 940 { 941 in: "echo $((()))", 942 common: `1:9: ( must be followed by an expression`, 943 }, 944 { 945 in: "echo $(((3))", 946 common: `1:6: reached ) without matching $(( with ))`, 947 }, 948 { 949 in: "echo $((+))", 950 common: `1:9: + must be followed by an expression`, 951 }, 952 { 953 in: "echo $((a b c))", 954 common: `1:11: not a valid arithmetic operator: b`, 955 }, 956 { 957 in: "echo $((a ; c))", 958 common: `1:11: not a valid arithmetic operator: ;`, 959 }, 960 { 961 in: "echo $((foo) )", 962 bsmk: `1:6: reached ) without matching $(( with )) #NOERR`, 963 }, 964 { 965 in: "echo $((a *))", 966 common: `1:11: * must be followed by an expression`, 967 }, 968 { 969 in: "echo $((++))", 970 common: `1:9: ++ must be followed by a literal`, 971 }, 972 { 973 in: "echo $((a ? b))", 974 common: `1:9: ternary operator missing : after ?`, 975 }, 976 { 977 in: "echo $((a : b))", 978 common: `1:9: ternary operator missing ? before :`, 979 }, 980 { 981 in: "echo $((/", 982 common: `1:9: / must follow an expression`, 983 }, 984 { 985 in: "echo $((:", 986 common: `1:9: : must follow an expression`, 987 }, 988 { 989 in: "echo $(((a)+=b))", 990 common: `1:12: += must follow a name`, 991 mksh: `1:12: += must follow a name #NOERR`, 992 }, 993 { 994 in: "echo $((1=2))", 995 common: `1:10: = must follow a name`, 996 }, 997 { 998 in: "echo $(($0=2))", 999 common: `1:11: = must follow a name #NOERR`, 1000 }, 1001 { 1002 in: "echo $(($(a)=2))", 1003 common: `1:13: = must follow a name #NOERR`, 1004 }, 1005 { 1006 in: "echo $((1'2'))", 1007 common: `1:10: not a valid arithmetic operator: '`, 1008 }, 1009 { 1010 in: "<<EOF\n$(()a", 1011 common: `2:1: $(( must be followed by an expression`, 1012 }, 1013 { 1014 in: "<<EOF\n`))", 1015 common: `2:2: ) can only be used to close a subshell`, 1016 }, 1017 { 1018 in: "echo ${foo", 1019 common: `1:6: reached EOF without matching ${ with }`, 1020 }, 1021 { 1022 in: "echo $foo ${}", 1023 common: `1:13: parameter expansion requires a literal`, 1024 }, 1025 { 1026 in: "echo ${à}", 1027 common: `1:8: invalid parameter name`, 1028 }, 1029 { 1030 in: "echo ${1a}", 1031 common: `1:8: invalid parameter name`, 1032 }, 1033 { 1034 in: "echo ${foo-bar", 1035 common: `1:6: reached EOF without matching ${ with }`, 1036 }, 1037 { 1038 in: "#foo\n{", 1039 common: `2:1: reached EOF without matching { with }`, 1040 }, 1041 { 1042 in: `echo "foo${bar"`, 1043 common: `1:15: not a valid parameter expansion operator: "`, 1044 }, 1045 { 1046 in: "echo ${%", 1047 common: `1:6: "${%foo}" is a mksh feature`, 1048 mksh: `1:8: parameter expansion requires a literal`, 1049 }, 1050 { 1051 in: "echo ${##", 1052 common: `1:6: reached EOF without matching ${ with }`, 1053 }, 1054 { 1055 in: "echo ${#<}", 1056 common: `1:9: parameter expansion requires a literal`, 1057 }, 1058 { 1059 in: "echo ${%<}", 1060 mksh: `1:9: parameter expansion requires a literal`, 1061 }, 1062 { 1063 in: "echo ${!<}", 1064 bsmk: `1:9: parameter expansion requires a literal`, 1065 }, 1066 { 1067 in: "echo ${@foo}", 1068 common: `1:9: @ cannot be followed by a word`, 1069 }, 1070 { 1071 in: "echo ${$foo}", 1072 common: `1:9: $ cannot be followed by a word`, 1073 }, 1074 { 1075 in: "echo ${?foo}", 1076 common: `1:9: ? cannot be followed by a word`, 1077 }, 1078 { 1079 in: "echo ${-foo}", 1080 common: `1:9: - cannot be followed by a word`, 1081 }, 1082 { 1083 in: "echo ${@[@]} ${@[*]}", 1084 bsmk: `1:9: cannot index a special parameter name`, 1085 }, 1086 { 1087 in: "echo ${*[@]} ${*[*]}", 1088 bsmk: `1:9: cannot index a special parameter name`, 1089 }, 1090 { 1091 in: "echo ${#[x]}", 1092 bsmk: `1:9: cannot index a special parameter name`, 1093 }, 1094 { 1095 in: "echo ${$[0]}", 1096 bsmk: `1:9: cannot index a special parameter name`, 1097 }, 1098 { 1099 in: "echo ${?[@]}", 1100 bsmk: `1:9: cannot index a special parameter name`, 1101 }, 1102 { 1103 in: "echo ${2[@]}", 1104 bsmk: `1:9: cannot index a special parameter name`, 1105 }, 1106 { 1107 in: "echo ${foo*}", 1108 bsmk: `1:11: not a valid parameter expansion operator: *`, 1109 }, 1110 { 1111 in: "echo ${foo;}", 1112 bsmk: `1:11: not a valid parameter expansion operator: ;`, 1113 }, 1114 { 1115 in: "echo ${foo!}", 1116 bsmk: `1:11: not a valid parameter expansion operator: !`, 1117 }, 1118 { 1119 in: "echo ${#foo:-bar}", 1120 bsmk: `1:12: cannot combine multiple parameter expansion operators`, 1121 }, 1122 { 1123 in: "echo ${%foo:1:3}", 1124 mksh: `1:12: cannot combine multiple parameter expansion operators`, 1125 }, 1126 { 1127 in: "echo ${#foo%x}", 1128 mksh: `1:12: cannot combine multiple parameter expansion operators`, 1129 }, 1130 { 1131 in: "echo foo\n;", 1132 common: `2:1: ; can only immediately follow a statement`, 1133 }, 1134 { 1135 in: "<<$ <<0\n$(<<$<<", 1136 bsmk: `2:6: << must be followed by a word`, 1137 }, 1138 { 1139 in: "(foo) bar", 1140 common: `1:7: statements must be separated by &, ; or a newline`, 1141 }, 1142 { 1143 in: "{ foo; } bar", 1144 common: `1:10: statements must be separated by &, ; or a newline`, 1145 }, 1146 { 1147 in: "if foo; then bar; fi bar", 1148 common: `1:22: statements must be separated by &, ; or a newline`, 1149 }, 1150 { 1151 in: "case", 1152 common: `1:1: "case" must be followed by a word`, 1153 }, 1154 { 1155 in: "case i", 1156 common: `1:1: "case x" must be followed by "in"`, 1157 }, 1158 { 1159 in: "case i in 3) foo;", 1160 common: `1:1: case statement must end with "esac"`, 1161 }, 1162 { 1163 in: "case i in 3) foo; 4) bar; esac", 1164 common: `1:20: a command can only contain words and redirects`, 1165 }, 1166 { 1167 in: "case i in 3&) foo;", 1168 common: `1:12: case patterns must be separated with |`, 1169 }, 1170 { 1171 in: "case $i in &) foo;", 1172 common: `1:12: case patterns must consist of words`, 1173 }, 1174 { 1175 in: "case i {", 1176 common: `1:1: "case i {" is a mksh feature`, 1177 mksh: `1:1: case statement must end with "}"`, 1178 }, 1179 { 1180 in: "case i { x) y ;;", 1181 mksh: `1:1: case statement must end with "}"`, 1182 }, 1183 { 1184 in: "\"`\"", 1185 common: `1:3: reached EOF without closing quote "`, 1186 }, 1187 { 1188 in: "`\"`", 1189 common: "1:3: reached EOF without closing quote `", 1190 }, 1191 { 1192 in: "`\\```", 1193 common: "1:2: reached EOF without closing quote `", 1194 }, 1195 { 1196 in: "`{\n`", 1197 common: "1:2: reached ` without matching { with }", 1198 }, 1199 { 1200 in: "echo \"`)`\"", 1201 bsmk: `1:8: ) can only be used to close a subshell`, 1202 posix: `1:8: ) can only be used to close a subshell #NOERR dash bug`, 1203 }, 1204 { 1205 in: "<<$bar\n$bar", 1206 common: `1:3: expansions not allowed in heredoc words #NOERR`, 1207 }, 1208 { 1209 in: "<<${bar}\n${bar}", 1210 common: `1:3: expansions not allowed in heredoc words #NOERR`, 1211 }, 1212 { 1213 in: "<<$(bar)\n$", 1214 bsmk: `1:3: expansions not allowed in heredoc words #NOERR`, 1215 posix: `1:3: expansions not allowed in heredoc words`, 1216 }, 1217 { 1218 in: "<<$-\n$-", 1219 common: `1:3: expansions not allowed in heredoc words #NOERR`, 1220 }, 1221 { 1222 in: "<<`bar`\n`bar`", 1223 common: `1:3: expansions not allowed in heredoc words #NOERR`, 1224 }, 1225 { 1226 in: "<<\"$bar\"\n$bar", 1227 common: `1:4: expansions not allowed in heredoc words #NOERR`, 1228 }, 1229 { 1230 in: "<<a <<0\n$(<<$<<", 1231 common: `2:6: << must be followed by a word`, 1232 }, 1233 { 1234 in: `""()`, 1235 common: `1:1: invalid func name`, 1236 mksh: `1:1: invalid func name #NOERR`, 1237 }, 1238 { 1239 // bash errors on the empty condition here, this is to 1240 // add coverage for empty statement lists 1241 in: `if; then bar; fi; ;`, 1242 common: `1:19: ; can only immediately follow a statement`, 1243 }, 1244 { 1245 in: "]] )", 1246 bsmk: `1:1: "]]" can only be used to close a test`, 1247 posix: `1:4: a command can only contain words and redirects`, 1248 }, 1249 { 1250 in: "((foo", 1251 bsmk: `1:1: reached EOF without matching (( with ))`, 1252 posix: `1:2: reached EOF without matching ( with )`, 1253 }, 1254 { 1255 in: "(())", 1256 bsmk: `1:1: (( must be followed by an expression`, 1257 }, 1258 { 1259 in: "echo ((foo", 1260 bsmk: `1:6: (( can only be used to open an arithmetic cmd`, 1261 posix: `1:1: "foo(" must be followed by )`, 1262 }, 1263 { 1264 in: "echo |&", 1265 bash: `1:6: |& must be followed by a statement`, 1266 posix: `1:6: | must be followed by a statement`, 1267 }, 1268 { 1269 in: "|& a", 1270 bsmk: `1:1: |& is not a valid start for a statement`, 1271 }, 1272 { 1273 in: "let", 1274 bsmk: `1:1: "let" must be followed by an expression`, 1275 }, 1276 { 1277 in: "let a+ b", 1278 bsmk: `1:6: + must be followed by an expression`, 1279 }, 1280 { 1281 in: "let + a", 1282 bsmk: `1:5: + must be followed by an expression`, 1283 }, 1284 { 1285 in: "let a ++", 1286 bsmk: `1:7: ++ must be followed by a literal`, 1287 }, 1288 { 1289 in: "let (a)++", 1290 bsmk: `1:8: ++ must follow a name`, 1291 }, 1292 { 1293 in: "let 1++", 1294 bsmk: `1:6: ++ must follow a name`, 1295 }, 1296 { 1297 in: "let $0++", 1298 bsmk: `1:7: ++ must follow a name`, 1299 }, 1300 { 1301 in: "let --(a)", 1302 bsmk: `1:5: -- must be followed by a literal`, 1303 }, 1304 { 1305 in: "let --$a", 1306 bsmk: `1:5: -- must be followed by a literal`, 1307 }, 1308 { 1309 in: "let a+\n", 1310 bsmk: `1:6: + must be followed by an expression`, 1311 }, 1312 { 1313 in: "let ))", 1314 bsmk: `1:1: "let" must be followed by an expression`, 1315 }, 1316 { 1317 in: "`let !`", 1318 bsmk: `1:6: ! must be followed by an expression`, 1319 }, 1320 { 1321 in: "let a:b", 1322 bsmk: `1:5: ternary operator missing ? before :`, 1323 }, 1324 { 1325 in: "let a+b=c", 1326 bsmk: `1:8: = must follow a name`, 1327 }, 1328 { 1329 in: "`let` { foo; }", 1330 bsmk: `1:2: "let" must be followed by an expression`, 1331 }, 1332 { 1333 in: "[[", 1334 bsmk: `1:1: test clause requires at least one expression`, 1335 }, 1336 { 1337 in: "[[ ]]", 1338 bsmk: `1:1: test clause requires at least one expression`, 1339 }, 1340 { 1341 in: "[[ a", 1342 bsmk: `1:1: reached EOF without matching [[ with ]]`, 1343 }, 1344 { 1345 in: "[[ a ||", 1346 bsmk: `1:6: || must be followed by an expression`, 1347 }, 1348 { 1349 in: "[[ a ==", 1350 bsmk: `1:6: == must be followed by a word`, 1351 }, 1352 { 1353 in: "[[ a =~", 1354 bash: `1:6: =~ must be followed by a word`, 1355 mksh: `1:6: regex tests are a bash feature`, 1356 }, 1357 { 1358 in: "[[ -f a", 1359 bsmk: `1:1: reached EOF without matching [[ with ]]`, 1360 }, 1361 { 1362 in: "[[ -n\na ]]", 1363 bsmk: `1:4: -n must be followed by a word`, 1364 }, 1365 { 1366 in: "[[ a -nt b", 1367 bsmk: `1:1: reached EOF without matching [[ with ]]`, 1368 }, 1369 { 1370 in: "[[ a =~ b", 1371 bash: `1:1: reached EOF without matching [[ with ]]`, 1372 }, 1373 { 1374 in: "[[ a b c ]]", 1375 bsmk: `1:6: not a valid test operator: b`, 1376 }, 1377 { 1378 in: "[[ a b$x c ]]", 1379 bsmk: `1:6: test operator words must consist of a single literal`, 1380 }, 1381 { 1382 in: "[[ a & b ]]", 1383 bsmk: `1:6: not a valid test operator: &`, 1384 }, 1385 { 1386 in: "[[ true && () ]]", 1387 bsmk: `1:12: ( must be followed by an expression`, 1388 }, 1389 { 1390 in: "[[ a == ! b ]]", 1391 bsmk: `1:11: not a valid test operator: b`, 1392 }, 1393 { 1394 in: "[[ (! ) ]]", 1395 bsmk: `1:5: ! must be followed by an expression`, 1396 }, 1397 { 1398 in: "[[ (-e ) ]]", 1399 bsmk: `1:5: -e must be followed by a word`, 1400 }, 1401 { 1402 in: "[[ (a) == b ]]", 1403 bsmk: `1:8: expected &&, || or ]] after complex expr`, 1404 }, 1405 { 1406 in: "[[ a =~ ; ]]", 1407 bash: `1:6: =~ must be followed by a word`, 1408 }, 1409 { 1410 in: "[[ a =~ )", 1411 bash: `1:6: =~ must be followed by a word`, 1412 }, 1413 { 1414 in: "[[ a =~ ())", 1415 bash: `1:1: reached ) without matching [[ with ]]`, 1416 }, 1417 { 1418 in: "[[ >", 1419 bsmk: `1:1: [[ must be followed by a word`, 1420 }, 1421 { 1422 in: "local (", 1423 bash: `1:7: "local" must be followed by names or assignments`, 1424 }, 1425 { 1426 in: "declare 0=${o})", 1427 bash: `1:9: invalid var name`, 1428 }, 1429 { 1430 in: "a=(<)", 1431 bsmk: `1:4: array element values must be words`, 1432 }, 1433 { 1434 in: "a=([)", 1435 bash: `1:4: [ must be followed by an expression`, 1436 }, 1437 { 1438 in: "a=([i)", 1439 bash: `1:4: reached ) without matching [ with ]`, 1440 }, 1441 { 1442 in: "a=([i])", 1443 bash: `1:4: "[x]" must be followed by = #NOERR`, 1444 }, 1445 { 1446 in: "a[i]=(y)", 1447 bash: `1:6: arrays cannot be nested`, 1448 }, 1449 { 1450 in: "a=([i]=(y))", 1451 bash: `1:8: arrays cannot be nested`, 1452 }, 1453 { 1454 in: "o=([0]=#", 1455 bash: `1:8: array element values must be words`, 1456 }, 1457 { 1458 in: "a=(x y) foo", 1459 bash: `1:1: inline variables cannot be arrays #NOERR stringifies `, 1460 }, 1461 { 1462 in: "a[2]=x foo", 1463 bash: `1:1: inline variables cannot be arrays #NOERR stringifies`, 1464 }, 1465 { 1466 in: "function", 1467 bsmk: `1:1: "function" must be followed by a word`, 1468 }, 1469 { 1470 in: "function foo(", 1471 bsmk: `1:10: "foo(" must be followed by )`, 1472 }, 1473 { 1474 in: "function `function", 1475 bsmk: `1:11: "function" must be followed by a word`, 1476 }, 1477 { 1478 in: `function "foo"(){}`, 1479 bsmk: `1:10: invalid func name`, 1480 }, 1481 { 1482 in: "function foo()", 1483 bsmk: `1:1: "foo()" must be followed by a statement`, 1484 }, 1485 { 1486 in: "echo <<<", 1487 bsmk: `1:6: <<< must be followed by a word`, 1488 }, 1489 { 1490 in: "a[", 1491 bsmk: `1:2: [ must be followed by an expression`, 1492 }, 1493 { 1494 in: "a[b", 1495 bsmk: `1:2: reached EOF without matching [ with ]`, 1496 }, 1497 { 1498 in: "a[]", 1499 bsmk: `1:2: [ must be followed by an expression #NOERR is cmd`, 1500 }, 1501 { 1502 in: "a[[", 1503 bsmk: `1:3: [ must follow a name`, 1504 }, 1505 { 1506 in: "echo $((a[))", 1507 bsmk: `1:10: [ must be followed by an expression`, 1508 }, 1509 { 1510 in: "echo $((a[b))", 1511 bsmk: `1:10: reached ) without matching [ with ]`, 1512 }, 1513 { 1514 in: "echo $((a[]))", 1515 bash: `1:10: [ must be followed by an expression`, 1516 mksh: `1:10: [ must be followed by an expression #NOERR wrong?`, 1517 }, 1518 { 1519 in: "echo $((x$t[", 1520 bsmk: `1:12: [ must follow a name`, 1521 }, 1522 { 1523 in: "a[1]", 1524 bsmk: `1:1: "a[b]" must be followed by = #NOERR is cmd`, 1525 }, 1526 { 1527 in: "a[i]+", 1528 bsmk: `1:1: "a[b]+" must be followed by = #NOERR is cmd`, 1529 }, 1530 { 1531 in: "a[1]#", 1532 bsmk: `1:1: "a[b]" must be followed by = #NOERR is cmd`, 1533 }, 1534 { 1535 in: "echo $[foo", 1536 bash: `1:6: reached EOF without matching $[ with ]`, 1537 }, 1538 { 1539 in: "echo $'", 1540 bsmk: `1:6: reached EOF without closing quote '`, 1541 }, 1542 { 1543 in: `echo $"`, 1544 bsmk: `1:6: reached EOF without closing quote "`, 1545 }, 1546 { 1547 in: "echo @(", 1548 bsmk: `1:6: reached EOF without matching @( with )`, 1549 }, 1550 { 1551 in: "echo @(a", 1552 bsmk: `1:6: reached EOF without matching @( with )`, 1553 }, 1554 { 1555 in: "((@(", 1556 bsmk: `1:1: reached ( without matching (( with ))`, 1557 }, 1558 { 1559 in: "time {", 1560 bsmk: `1:6: reached EOF without matching { with }`, 1561 }, 1562 { 1563 in: "time ! foo", 1564 bash: `1:6: "!" can only be used in full statements #NOERR wrong`, 1565 mksh: `1:6: "!" can only be used in full statements`, 1566 }, 1567 { 1568 in: "coproc", 1569 bash: `1:1: coproc clause requires a command`, 1570 }, 1571 { 1572 in: "coproc\n$", 1573 bash: `1:1: coproc clause requires a command`, 1574 }, 1575 { 1576 in: "coproc declare (", 1577 bash: `1:16: "declare" must be followed by names or assignments`, 1578 }, 1579 { 1580 in: "echo ${foo[1 2]}", 1581 bsmk: `1:14: not a valid arithmetic operator: 2`, 1582 }, 1583 { 1584 in: "echo ${foo[}", 1585 bsmk: `1:11: [ must be followed by an expression`, 1586 }, 1587 { 1588 in: "echo ${foo]}", 1589 bsmk: `1:11: not a valid parameter expansion operator: ]`, 1590 }, 1591 { 1592 in: "echo ${foo[]}", 1593 bash: `1:11: [ must be followed by an expression`, 1594 mksh: `1:11: [ must be followed by an expression #NOERR wrong?`, 1595 }, 1596 { 1597 in: "echo ${a/\n", 1598 bsmk: `1:6: reached EOF without matching ${ with }`, 1599 }, 1600 { 1601 in: "echo ${a-\n", 1602 bsmk: `1:6: reached EOF without matching ${ with }`, 1603 }, 1604 { 1605 in: "echo ${foo:", 1606 bsmk: `1:11: : must be followed by an expression`, 1607 }, 1608 { 1609 in: "echo ${foo:1 2}", 1610 bsmk: `1:14: not a valid arithmetic operator: 2 #NOERR lazy eval`, 1611 }, 1612 { 1613 in: "echo ${foo:1", 1614 bsmk: `1:6: reached EOF without matching ${ with }`, 1615 }, 1616 { 1617 in: "echo ${foo:1:", 1618 bsmk: `1:13: : must be followed by an expression`, 1619 }, 1620 { 1621 in: "echo ${foo:1:2", 1622 bsmk: `1:6: reached EOF without matching ${ with }`, 1623 }, 1624 { 1625 in: "echo ${foo,", 1626 bash: `1:6: reached EOF without matching ${ with }`, 1627 }, 1628 { 1629 in: "echo ${foo@", 1630 bash: `1:11: @ expansion operator requires a literal`, 1631 }, 1632 { 1633 in: "echo ${foo@}", 1634 bash: `1:12: @ expansion operator requires a literal #NOERR empty string fallback`, 1635 }, 1636 { 1637 in: "echo ${foo@Q", 1638 bash: `1:6: reached EOF without matching ${ with }`, 1639 }, 1640 { 1641 in: "echo ${foo@bar}", 1642 bash: `1:12: invalid @ expansion operator #NOERR at runtime`, 1643 }, 1644 { 1645 in: "echo ${foo@'Q'}", 1646 bash: `1:12: @ expansion operator requires a literal #NOERR at runtime`, 1647 }, 1648 { 1649 in: `echo $((echo a); (echo b))`, 1650 bsmk: `1:14: not a valid arithmetic operator: a #NOERR backtrack`, 1651 }, 1652 { 1653 in: `((echo a); (echo b))`, 1654 bsmk: `1:8: not a valid arithmetic operator: a #NOERR backtrack`, 1655 }, 1656 { 1657 in: "for ((;;", 1658 bash: `1:5: reached EOF without matching (( with ))`, 1659 }, 1660 { 1661 in: "for ((;;0000000", 1662 bash: `1:5: reached EOF without matching (( with ))`, 1663 }, 1664 { 1665 in: "function foo() { bar; }", 1666 posix: `1:13: a command can only contain words and redirects`, 1667 }, 1668 { 1669 in: "echo <(", 1670 posix: `1:6: < must be followed by a word`, 1671 mksh: `1:6: < must be followed by a word`, 1672 }, 1673 { 1674 in: "echo >(", 1675 posix: `1:6: > must be followed by a word`, 1676 mksh: `1:6: > must be followed by a word`, 1677 }, 1678 { 1679 // shells treat {var} as an argument, but we are a bit stricter 1680 // so that users won't think this will work like they expect in 1681 // POSIX shell. 1682 in: "echo {var}>foo", 1683 posix: `1:6: {varname} redirects are a bash feature #NOERR`, 1684 mksh: `1:6: {varname} redirects are a bash feature #NOERR`, 1685 }, 1686 { 1687 in: "echo ;&", 1688 posix: `1:7: & can only immediately follow a statement`, 1689 bsmk: `1:6: ;& can only be used in a case clause`, 1690 }, 1691 { 1692 in: "echo ;;&", 1693 posix: `1:6: ;; can only be used in a case clause`, 1694 mksh: `1:6: ;; can only be used in a case clause`, 1695 }, 1696 { 1697 in: "echo ;|", 1698 posix: `1:7: | can only immediately follow a statement`, 1699 bash: `1:7: | can only immediately follow a statement`, 1700 }, 1701 { 1702 in: "for ((i=0; i<5; i++)); do echo; done", 1703 posix: `1:5: c-style fors are a bash feature`, 1704 mksh: `1:5: c-style fors are a bash feature`, 1705 }, 1706 { 1707 in: "echo !(a)", 1708 posix: `1:6: extended globs are a bash/mksh feature`, 1709 }, 1710 { 1711 in: "echo $a@(b)", 1712 posix: `1:8: extended globs are a bash/mksh feature`, 1713 }, 1714 { 1715 in: "foo=(1 2)", 1716 posix: `1:5: arrays are a bash/mksh feature`, 1717 }, 1718 { 1719 in: "a=$c\n'", 1720 common: `2:1: reached EOF without closing quote '`, 1721 }, 1722 { 1723 in: "echo ${!foo}", 1724 posix: `1:8: ${!foo} is a bash/mksh feature`, 1725 }, 1726 { 1727 in: "echo ${foo[1]}", 1728 posix: `1:11: arrays are a bash/mksh feature`, 1729 }, 1730 { 1731 in: "echo ${foo/a/b}", 1732 posix: `1:11: search and replace is a bash/mksh feature`, 1733 }, 1734 { 1735 in: "echo ${foo:1}", 1736 posix: `1:11: slicing is a bash/mksh feature`, 1737 }, 1738 { 1739 in: "echo ${foo,bar}", 1740 posix: `1:11: this expansion operator is a bash feature`, 1741 mksh: `1:11: this expansion operator is a bash feature`, 1742 }, 1743 { 1744 in: "echo ${foo@Q}", 1745 posix: `1:11: this expansion operator is a bash/mksh feature`, 1746 }, 1747 { 1748 in: "`\"`\\", 1749 common: "1:3: reached EOF without closing quote `", 1750 }, 1751 } 1752 1753 func checkError(p *Parser, in, want string) func(*testing.T) { 1754 return func(t *testing.T) { 1755 if i := strings.Index(want, " #NOERR"); i >= 0 { 1756 want = want[:i] 1757 } 1758 _, err := p.Parse(newStrictReader(in), "") 1759 if err == nil { 1760 t.Fatalf("Expected error in %q: %v", in, want) 1761 } 1762 if got := err.Error(); got != want { 1763 t.Fatalf("Error mismatch in %q\nwant: %s\ngot: %s", 1764 in, want, got) 1765 } 1766 } 1767 } 1768 1769 func TestParseErrPosix(t *testing.T) { 1770 t.Parallel() 1771 p := NewParser(KeepComments, Variant(LangPOSIX)) 1772 i := 0 1773 for _, c := range shellTests { 1774 want := c.common 1775 if c.posix != nil { 1776 want = c.posix 1777 } 1778 if want == nil { 1779 continue 1780 } 1781 t.Run(fmt.Sprintf("%03d", i), checkError(p, c.in, want.(string))) 1782 i++ 1783 } 1784 } 1785 1786 func TestParseErrBash(t *testing.T) { 1787 t.Parallel() 1788 p := NewParser(KeepComments) 1789 i := 0 1790 for _, c := range shellTests { 1791 want := c.common 1792 if c.bsmk != nil { 1793 want = c.bsmk 1794 } 1795 if c.bash != nil { 1796 want = c.bash 1797 } 1798 if want == nil { 1799 continue 1800 } 1801 t.Run(fmt.Sprintf("%03d", i), checkError(p, c.in, want.(string))) 1802 i++ 1803 } 1804 } 1805 1806 func TestParseErrMirBSDKorn(t *testing.T) { 1807 t.Parallel() 1808 p := NewParser(KeepComments, Variant(LangMirBSDKorn)) 1809 i := 0 1810 for _, c := range shellTests { 1811 want := c.common 1812 if c.bsmk != nil { 1813 want = c.bsmk 1814 } 1815 if c.mksh != nil { 1816 want = c.mksh 1817 } 1818 if want == nil { 1819 continue 1820 } 1821 t.Run(fmt.Sprintf("%03d", i), checkError(p, c.in, want.(string))) 1822 i++ 1823 } 1824 } 1825 1826 func TestInputName(t *testing.T) { 1827 t.Parallel() 1828 in := "(" 1829 want := "some-file.sh:1:1: reached EOF without matching ( with )" 1830 p := NewParser() 1831 _, err := p.Parse(strings.NewReader(in), "some-file.sh") 1832 if err == nil { 1833 t.Fatalf("Expected error in %q: %v", in, want) 1834 } 1835 got := err.Error() 1836 if got != want { 1837 t.Fatalf("Error mismatch in %q\nwant: %s\ngot: %s", 1838 in, want, got) 1839 } 1840 } 1841 1842 var errBadReader = fmt.Errorf("write: expected error") 1843 1844 type badReader struct{} 1845 1846 func (b badReader) Read(p []byte) (int, error) { return 0, errBadReader } 1847 1848 func TestReadErr(t *testing.T) { 1849 t.Parallel() 1850 p := NewParser() 1851 _, err := p.Parse(badReader{}, "") 1852 if err == nil { 1853 t.Fatalf("Expected error with bad reader") 1854 } 1855 if err != errBadReader { 1856 t.Fatalf("Error mismatch with bad reader:\nwant: %v\ngot: %v", 1857 errBadReader, err) 1858 } 1859 } 1860 1861 type strictStringReader struct { 1862 *strings.Reader 1863 gaveEOF bool 1864 } 1865 1866 func newStrictReader(s string) *strictStringReader { 1867 return &strictStringReader{Reader: strings.NewReader(s)} 1868 } 1869 1870 func (r *strictStringReader) Read(p []byte) (int, error) { 1871 n, err := r.Reader.Read(p) 1872 if err == io.EOF { 1873 if r.gaveEOF { 1874 return n, fmt.Errorf("duplicate EOF read") 1875 } 1876 r.gaveEOF = true 1877 } 1878 return n, err 1879 } 1880 1881 func TestParseStmts(t *testing.T) { 1882 t.Parallel() 1883 p := NewParser() 1884 inReader, inWriter := io.Pipe() 1885 recv := make(chan bool, 10) 1886 errc := make(chan error) 1887 go func() { 1888 errc <- p.Stmts(inReader, func(s *Stmt) bool { 1889 recv <- true 1890 return true 1891 }) 1892 }() 1893 io.WriteString(inWriter, "foo\n") 1894 <-recv 1895 io.WriteString(inWriter, "bar; baz") 1896 inWriter.Close() 1897 <-recv 1898 <-recv 1899 if err := <-errc; err != nil { 1900 t.Fatalf("Expected no error: %v", err) 1901 } 1902 } 1903 1904 func TestParseStmtsStopEarly(t *testing.T) { 1905 t.Parallel() 1906 p := NewParser() 1907 inReader, inWriter := io.Pipe() 1908 recv := make(chan bool, 10) 1909 errc := make(chan error) 1910 go func() { 1911 errc <- p.Stmts(inReader, func(s *Stmt) bool { 1912 recv <- true 1913 return !s.Background 1914 }) 1915 }() 1916 io.WriteString(inWriter, "a\n") 1917 <-recv 1918 io.WriteString(inWriter, "b &\n") 1919 <-recv 1920 io.WriteString(inWriter, "c\n") 1921 inWriter.Close() 1922 if err := <-errc; err != nil { 1923 t.Fatalf("Expected no error: %v", err) 1924 } 1925 } 1926 1927 func TestParseStmtsError(t *testing.T) { 1928 t.Parallel() 1929 in := "foo; )" 1930 p := NewParser() 1931 recv := make(chan bool, 10) 1932 errc := make(chan error) 1933 go func() { 1934 errc <- p.Stmts(strings.NewReader(in), func(s *Stmt) bool { 1935 recv <- true 1936 return true 1937 }) 1938 }() 1939 <-recv 1940 if err := <-errc; err == nil { 1941 t.Fatalf("Expected an error in %q, but got nil", in) 1942 } 1943 } 1944 1945 func TestParseWords(t *testing.T) { 1946 t.Parallel() 1947 p := NewParser() 1948 inReader, inWriter := io.Pipe() 1949 recv := make(chan bool, 10) 1950 errc := make(chan error) 1951 go func() { 1952 errc <- p.Words(inReader, func(w *Word) bool { 1953 recv <- true 1954 return true 1955 }) 1956 }() 1957 // TODO: Allow a single space to end parsing a word. At the moment, the 1958 // parser must read the next non-space token (the next literal or 1959 // newline, in this case) to finish parsing a word. 1960 io.WriteString(inWriter, "foo ") 1961 io.WriteString(inWriter, "bar\n") 1962 <-recv 1963 io.WriteString(inWriter, "baz etc") 1964 inWriter.Close() 1965 <-recv 1966 <-recv 1967 <-recv 1968 if err := <-errc; err != nil { 1969 t.Fatalf("Expected no error: %v", err) 1970 } 1971 } 1972 1973 func TestParseWordsStopEarly(t *testing.T) { 1974 t.Parallel() 1975 p := NewParser() 1976 r := strings.NewReader("a\nb\nc\n") 1977 parsed := 0 1978 err := p.Words(r, func(w *Word) bool { 1979 parsed++ 1980 return w.Lit() != "b" 1981 }) 1982 if err != nil { 1983 t.Fatalf("Expected no error: %v", err) 1984 } 1985 if want := 2; parsed != want { 1986 t.Fatalf("wanted %d words parsed, got %d", want, parsed) 1987 } 1988 } 1989 1990 func TestParseWordsError(t *testing.T) { 1991 t.Parallel() 1992 in := "foo )" 1993 p := NewParser() 1994 recv := make(chan bool, 10) 1995 errc := make(chan error) 1996 go func() { 1997 errc <- p.Words(strings.NewReader(in), func(w *Word) bool { 1998 recv <- true 1999 return true 2000 }) 2001 }() 2002 <-recv 2003 want := "1:5: ) is not a valid word" 2004 got := fmt.Sprintf("%v", <-errc) 2005 if got != want { 2006 t.Fatalf("Expected %q as an error, but got %q", want, got) 2007 } 2008 } 2009 2010 var documentTests = []struct { 2011 in string 2012 want []WordPart 2013 }{ 2014 { 2015 "foo", 2016 []WordPart{lit("foo")}, 2017 }, 2018 { 2019 " foo $bar", 2020 []WordPart{ 2021 lit(" foo "), 2022 litParamExp("bar"), 2023 }, 2024 }, 2025 { 2026 "$bar\n\n", 2027 []WordPart{ 2028 litParamExp("bar"), 2029 lit("\n\n"), 2030 }, 2031 }, 2032 } 2033 2034 func TestParseDocument(t *testing.T) { 2035 t.Parallel() 2036 p := NewParser() 2037 2038 for i, tc := range documentTests { 2039 t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { 2040 got, err := p.Document(strings.NewReader(tc.in)) 2041 if err != nil { 2042 t.Fatal(err) 2043 } 2044 clearPosRecurse(t, "", got) 2045 want := &Word{Parts: tc.want} 2046 if !reflect.DeepEqual(got, want) { 2047 t.Fatalf("syntax tree mismatch in %q\ndiff:\n%s", tc.in, 2048 strings.Join(pretty.Diff(want, got), "\n")) 2049 } 2050 }) 2051 } 2052 } 2053 2054 func TestParseDocumentError(t *testing.T) { 2055 t.Parallel() 2056 in := "foo $(" 2057 p := NewParser() 2058 _, err := p.Document(strings.NewReader(in)) 2059 want := "1:5: reached EOF without matching ( with )" 2060 got := fmt.Sprintf("%v", err) 2061 if got != want { 2062 t.Fatalf("Expected %q as an error, but got %q", want, got) 2063 } 2064 } 2065 2066 var stopAtTests = []struct { 2067 in string 2068 stop string 2069 want interface{} 2070 }{ 2071 { 2072 "foo bar", "$$", 2073 litCall("foo", "bar"), 2074 }, 2075 { 2076 "$foo $", "$$", 2077 call(word(litParamExp("foo")), litWord("$")), 2078 }, 2079 { 2080 "echo foo $$", "$$", 2081 litCall("echo", "foo"), 2082 }, 2083 { 2084 "$$", "$$", 2085 &File{}, 2086 }, 2087 { 2088 "echo foo\n$$\n", "$$", 2089 litCall("echo", "foo"), 2090 }, 2091 { 2092 "echo foo; $$", "$$", 2093 litCall("echo", "foo"), 2094 }, 2095 { 2096 "echo foo; $$", "$$", 2097 litCall("echo", "foo"), 2098 }, 2099 { 2100 "echo foo;$$", "$$", 2101 litCall("echo", "foo"), 2102 }, 2103 { 2104 "echo '$$'", "$$", 2105 call(litWord("echo"), word(sglQuoted("$$"))), 2106 }, 2107 } 2108 2109 func TestParseStmtsStopAt(t *testing.T) { 2110 t.Parallel() 2111 for i, c := range stopAtTests { 2112 p := NewParser(StopAt(c.stop)) 2113 want := fullProg(c.want) 2114 t.Run(fmt.Sprintf("%02d", i), singleParse(p, c.in, want)) 2115 } 2116 } 2117 2118 func TestValidName(t *testing.T) { 2119 t.Parallel() 2120 tests := []struct { 2121 name string 2122 in string 2123 want bool 2124 }{ 2125 {"Empty", "", false}, 2126 {"Simple", "foo", true}, 2127 {"MixedCase", "Foo", true}, 2128 {"Underscore", "_foo", true}, 2129 {"NumberPrefix", "3foo", false}, 2130 {"NumberSuffix", "foo3", true}, 2131 } 2132 for _, tc := range tests { 2133 t.Run(tc.name, func(t *testing.T) { 2134 got := ValidName(tc.in) 2135 if got != tc.want { 2136 t.Fatalf("ValidName(%q) got %t, wanted %t", 2137 tc.in, got, tc.want) 2138 } 2139 }) 2140 } 2141 }