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  }