github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/cue/parser/parser_test.go (about)

     1  // Copyright 2018 The CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package parser
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  	"testing"
    21  
    22  	"github.com/joomcode/cue/cue/ast"
    23  )
    24  
    25  func TestParse(t *testing.T) {
    26  	testCases := []struct{ desc, in, out string }{{
    27  
    28  		"ellipsis in structs",
    29  		`#Def: {
    30  			b: "2"
    31  			...
    32  		}
    33  		...
    34  
    35  		#Def2: {
    36  			...
    37  			b: "2"
    38  		}
    39  		#Def3: {...
    40  		_}
    41  		...
    42  		`,
    43  		`#Def: {b: "2", ...}, ..., #Def2: {..., b: "2"}, #Def3: {..., _}, ...`,
    44  	}, {
    45  
    46  		"empty file", "", "",
    47  	}, {
    48  		"empty struct", "{}", "{}",
    49  	}, {
    50  		"empty structs", "{},{},", "{}, {}",
    51  	}, {
    52  		"empty structs; elided comma", "{}\n{}", "{}, {}",
    53  	}, {
    54  		"basic lits", `"a","b", 3,3.4,5,2_3`, `"a", "b", 3, 3.4, 5, 2_3`,
    55  	}, {
    56  		"keyword basic lits", `true,false,null,for,in,if,let,if`, `true, false, null, for, in, if, let, if`,
    57  	}, {
    58  		"keyword basic newline", `
    59  		true
    60  		false
    61  		null
    62  		for
    63  		in
    64  		if
    65  		let
    66  		if
    67  		`, `true, false, null, for, in, if, let, if`,
    68  	}, {
    69  		"keywords as labels",
    70  		`if: 0, for: 1, in: 2, where: 3, div: 4, quo: 5
    71  		for: if: let: 3
    72  		`,
    73  		`if: 0, for: 1, in: 2, where: 3, div: 4, quo: 5, for: {if: {let: 3}}`,
    74  	}, {
    75  		"keywords as alias",
    76  		`if=foo: 0
    77  		for=bar: 2
    78  		let=bar: 3
    79  		`,
    80  		`if=foo: 0, for=bar: 2, let=bar: 3`,
    81  	}, {
    82  		"json",
    83  		`{
    84  			"a": 1,
    85  			"b": "2",
    86  			"c": 3
    87  		}`,
    88  		`{"a": 1, "b": "2", "c": 3}`,
    89  	}, {
    90  		"json:extra comma",
    91  		`{
    92  			"a": 1,
    93  			"b": "2",
    94  			"c": 3,
    95  		}`,
    96  		`{"a": 1, "b": "2", "c": 3}`,
    97  	}, {
    98  		"json:simplified",
    99  		`{
   100  			a: 1
   101  			b: "2"
   102  			c: 3
   103  		}`,
   104  		`{a: 1, b: "2", c: 3}`,
   105  	}, {
   106  		"attributes",
   107  		`a: 1 @xml(,attr)
   108  		 b: 2 @foo(a,b=4) @go(Foo)
   109  		 c: {
   110  			 d: "x" @go(D) @json(,omitempty)
   111  			 e: "y" @ts(,type=string,"str")
   112  		 }`,
   113  		`a: 1 @xml(,attr), b: 2 @foo(a,b=4) @go(Foo), c: {d: "x" @go(D) @json(,omitempty), e: "y" @ts(,type=string,"str")}`,
   114  	}, {
   115  		"not emitted",
   116  		`a: true
   117  		 b?: "2"
   118  		 c?: 3
   119  
   120  		 "g\("en")"?: 4
   121  		`,
   122  		`a: true, b?: "2", c?: 3, "g\("en")"?: 4`,
   123  	}, {
   124  		"definition",
   125  		`#Def: {
   126  			 b: "2"
   127  			 c: 3
   128  
   129  			 embedding
   130  		}
   131  		#Def: {}
   132  		`,
   133  		`#Def: {b: "2", c: 3, embedding}, #Def: {}`,
   134  	}, {
   135  		"one-line embedding",
   136  		`{ V1, V2 }`,
   137  		`{V1, V2}`,
   138  	}, {
   139  		"selectors",
   140  		`a.b. "str"`,
   141  		`a.b."str"`,
   142  	}, {
   143  		"selectors",
   144  		`a.b. "str"`,
   145  		`a.b."str"`,
   146  	}, {
   147  		"faulty bytes selector",
   148  		`a.b.'str'`,
   149  		"a.b._\nexpected selector, found 'STRING' 'str'",
   150  	}, {
   151  		"faulty multiline string selector",
   152  		`a.b."""
   153  			"""`,
   154  		"a.b._\nexpected selector, found 'STRING' \"\"\"\n\t\t\t\"\"\"",
   155  	}, {
   156  		"expression embedding",
   157  		`#Def: {
   158  			a.b.c
   159  			a > b < c
   160  			-1<2
   161  
   162  			foo: 2
   163  		}`,
   164  		`#Def: {a.b.c, a>b<c, -1<2, foo: 2}`,
   165  	}, {
   166  		"ellipsis in structs",
   167  		`#Def: {
   168  			b: "2"
   169  			...
   170  		}
   171  		...
   172  
   173  		#Def2: {
   174  			...
   175  			b: "2"
   176  		}
   177  		#Def3: {...
   178  		_}
   179  		...
   180  		`,
   181  		`#Def: {b: "2", ...}, ..., #Def2: {..., b: "2"}, #Def3: {..., _}, ...`,
   182  	}, {
   183  		"emitted referencing non-emitted",
   184  		`a: 1
   185  		 b: "2"
   186  		 c: 3
   187  		{ name: b, total: a + b }`,
   188  		`a: 1, b: "2", c: 3, {name: b, total: a+b}`,
   189  	}, {
   190  		"package file",
   191  		`package k8s
   192  		 {}
   193  		`,
   194  		`package k8s, {}`,
   195  	}, {
   196  		"imports group",
   197  		`package k8s
   198  
   199  		import (
   200  			a "foo"
   201  			"bar/baz"
   202  		)
   203  		`,
   204  		`package k8s, import ( a "foo", "bar/baz" )`,
   205  	}, {
   206  		"imports single",
   207  		`package k8s
   208  
   209  		import a "foo"
   210  		import "bar/baz"
   211  			`,
   212  		`package k8s, import a "foo", import "bar/baz"`,
   213  	}, {
   214  		"collapsed fields",
   215  		`a: b:: c?: [Name=_]: d: 1
   216  		"g\("en")"?: 4
   217  		 // job foo { bar: 1 } // TODO error after foo
   218  		 job: "foo": [_]: { bar: 1 }
   219  		`,
   220  		`a: {b :: {c?: {[Name=_]: {d: 1}}}}, "g\("en")"?: 4, job: {"foo": {[_]: {bar: 1}}}`,
   221  	}, {
   222  		"identifiers",
   223  		`// 	$_: 1,
   224  			a: {b: {c: d}}
   225  			c: a
   226  			d: a.b
   227  			// e: a."b" // TODO: is an error
   228  			e: a.b.c
   229  			"f": f,
   230  			[X=_]: X
   231  		`,
   232  		"a: {b: {c: d}}, c: a, d: a.b, e: a.b.c, \"f\": f, [X=_]: X",
   233  	}, {
   234  		"empty fields",
   235  		`
   236  		"": 3
   237  		`,
   238  		`"": 3`,
   239  	}, {
   240  		"expressions",
   241  		`	a: (2 + 3) * 5
   242  			b: (2 + 3) + 4
   243  			c: 2 + 3 + 4
   244  			d: -1
   245  			e: !foo
   246  			f: _|_
   247  		`,
   248  		"a: (2+3)*5, b: (2+3)+4, c: 2+3+4, d: -1, e: !foo, f: _|_",
   249  	}, {
   250  		"pseudo keyword expressions",
   251  		`	a: (2 div 3) mod 5
   252  			b: (2 quo 3) rem 4
   253  			c: 2 div 3 div 4
   254  		`,
   255  		"a: (2 div 3) mod 5, b: (2 quo 3) rem 4, c: 2 div 3 div 4",
   256  	}, {
   257  		"ranges",
   258  		`	a: >=1 & <=2
   259  			b: >2.0  & <= 40.0
   260  			c: >"a" & <="b"
   261  			v: (>=1 & <=2) & <=(>=5 & <=10)
   262  			w: >1 & <=2 & <=3
   263  			d: >=3T & <=5M
   264  		`,
   265  		"a: >=1&<=2, b: >2.0&<=40.0, c: >\"a\"&<=\"b\", v: (>=1&<=2)&<=(>=5&<=10), w: >1&<=2&<=3, d: >=3T&<=5M",
   266  	}, {
   267  		"indices",
   268  		`{
   269  			a: b[2]
   270  			b: c[1:2]
   271  			c: "asdf"
   272  			d: c ["a"]
   273  		}`,
   274  		`{a: b[2], b: c[1:2], c: "asdf", d: c["a"]}`,
   275  	}, {
   276  		"calls",
   277  		`{
   278  			a: b(a.b, c.d)
   279  			b: a.b(c)
   280  		}`,
   281  		`{a: b(a.b, c.d), b: a.b(c)}`,
   282  	}, {
   283  		"lists",
   284  		`{
   285  			a: [ 1, 2, 3, b, c, ... ]
   286  			b: [ 1, 2, 3, ],
   287  			c: [ 1,
   288  			 2,
   289  			 3
   290  			 ],
   291  			d: [ 1+2, 2, 4,]
   292  		}`,
   293  		`{a: [1, 2, 3, b, c, ...], b: [1, 2, 3], c: [1, 2, 3], d: [1+2, 2, 4]}`,
   294  	}, {
   295  		"list types",
   296  		`{
   297  			a: 4*[int]
   298  			b: <=5*[ {a: 5} ]
   299  			c1: [...int]
   300  			c2: [...]
   301  			c3: [1, 2, ...int,]
   302  		}`,
   303  		`{a: 4*[int], b: <=5*[{a: 5}], c1: [...int], c2: [...], c3: [1, 2, ...int]}`,
   304  	}, {
   305  		"list comprehensions",
   306  		`{
   307  				y: [1,2,3]
   308  				b: [ for x in y if x == 1 { x } ],
   309  			}`,
   310  		`{y: [1, 2, 3], b: [for x in y if x==1 {x}]}`,
   311  	}, {
   312  		"field comprehensions",
   313  		`{
   314  				y: { a: 1, b: 2}
   315  				a: {
   316  					for k, v in y if v > 2 {
   317  						"\(k)": v
   318  					}
   319  				}
   320  			 }`,
   321  		`{y: {a: 1, b: 2}, a: {for k: v in y if v>2 {"\(k)": v}}}`,
   322  	}, {
   323  		"nested comprehensions",
   324  		`{
   325  			y: { a: 1, b: 2}
   326  			a: {
   327  				for k, v in y let x = v+2 if x > 2 {
   328  					"\(k)": v
   329  				}
   330  			}
   331  		}`,
   332  		`{y: {a: 1, b: 2}, a: {for k: v in y let x=v+2 if x>2 {"\(k)": v}}}`,
   333  	}, {
   334  		"let declaration",
   335  		`{
   336  			let X = 42
   337  			let Y = "42",
   338  			let Z = 10 + 12
   339  		}`,
   340  		`{let X=42, let Y="42", let Z=10+12}`,
   341  	}, {
   342  		"duplicates allowed",
   343  		`{
   344  			a: b: 3
   345  			a: { b: 3 }
   346  		}`,
   347  		"{a: {b: 3}, a: {b: 3}}",
   348  	}, {
   349  		"templates", // TODO: remove
   350  		`{
   351  			[foo=_]: { a: int }
   352  			a:     { a: 1 }
   353  		}`,
   354  		"{[foo=_]: {a: int}, a: {a: 1}}",
   355  	}, {
   356  		"value alias",
   357  		`
   358  		{
   359  			a: X=foo
   360  			b: Y={foo}
   361  			c: d: e: X=5
   362  		}
   363  		`,
   364  		`{a: X=foo, b: Y={foo}, c: {d: {e: X=5}}}`,
   365  	}, {
   366  		"dynamic labels",
   367  		`{
   368  			(x): a: int
   369  			x:   "foo"
   370  			a: {
   371  				(a.b)
   372  			}
   373  		}`,
   374  		`{(x): {a: int}, x: "foo", a: {(a.b)}}`,
   375  	}, {
   376  		"foo",
   377  		`[
   378  			[1],
   379  			[1, 2],
   380  			[1, 2, 3],
   381  		]`,
   382  		"[[1], [1, 2], [1, 2, 3]]",
   383  	}, {
   384  		"interpolation",
   385  		`a: "foo \(ident)"
   386  		 b: "bar \(bar)  $$$ "
   387  		 c: "nest \(   { a: "\( nest ) "}.a ) \(5)"
   388  		 m1: """
   389  			 multi \(bar)
   390  			 """
   391  		 m2: '''
   392  			 \(bar) multi
   393  			 '''`,
   394  		`a: "foo \(ident)", b: "bar \(bar)  $$$ ", c: "nest \({a: "\(nest) "}.a) \(5)", ` + "m1: \"\"\"\n\t\t\t multi \\(bar)\n\t\t\t \"\"\", m2: '''\n\t\t\t \\(bar) multi\n\t\t\t '''",
   395  	}, {
   396  		"file comments",
   397  		`// foo
   398  
   399  		// uni
   400  		package foo // uniline
   401  
   402  		// file.1
   403  		// file.2
   404  
   405  		`,
   406  		"<[0// foo] <[d0// uni] [l3// uniline] [3// file.1 // file.2] package foo>>",
   407  	}, {
   408  		"line comments",
   409  		`// doc
   410  		 a: 5 // line
   411  		 b: 6 // lineb
   412  			  // next
   413  			`, // next is followed by EOF. Ensure it doesn't move to file.
   414  		"<[d0// doc] [l5// line] a: 5>, " +
   415  			"<[l5// lineb] [5// next] b: 6>",
   416  	}, {
   417  		"alt comments",
   418  		`// a ...
   419  		a: 5 // line a
   420  
   421  		// about a
   422  
   423  		// b ...
   424  		b: // lineb
   425  		  6
   426  
   427  		// about b
   428  
   429  		c: 7
   430  
   431  		// about c
   432  
   433  		// about d
   434  		d:
   435  			// about e
   436  			e: 3
   437  		`,
   438  		"<[d0// a ...] [l5// line a] [5// about a] a: 5>, " +
   439  			"<[d0// b ...] [l2// lineb] [5// about b] b: 6>, " +
   440  			"<[5// about c] c: 7>, " +
   441  			"<[d0// about d] d: {<[d0// about e] e>: 3}>",
   442  	}, {
   443  		"expr comments",
   444  		`
   445  		a: 2 +  // 2 +
   446  		   3 +  // 3 +
   447  		   4    // 4
   448  		   `,
   449  		"<[l5// 4] a: <[l2// 3 +] <[l2// 2 +] 2+3>+4>>",
   450  	}, {
   451  		"composit comments",
   452  		`a : {
   453  			a: 1, b: 2, c: 3, d: 4
   454  			// end
   455  		}
   456  		b: [
   457  			1, 2, 3, 4, 5,
   458  			// end
   459  		]
   460  		c: [ 1, 2, 3, 4, // here
   461  			{ a: 3 }, // here
   462  			5, 6, 7, 8 // and here
   463  		]
   464  		d: {
   465  			a: 1 // Hello
   466  			// Doc
   467  			b: 2
   468  		}
   469  		e1: [
   470  			// comment in list body
   471  		]
   472  		e2: {
   473  			// comment in struct body
   474  		}
   475  		`,
   476  		"a: {a: 1, b: 2, c: 3, <[d5// end] d: 4>}, " +
   477  			"b: [1, 2, 3, 4, <[d2// end] 5>], " +
   478  			"c: [1, 2, 3, <[l2// here] 4>, <[l4// here] {a: 3}>, 5, 6, 7, <[l2// and here] 8>], " +
   479  			"d: {<[l5// Hello] a: 1>, <[d0// Doc] b: 2>}, " +
   480  			"e1: <[d1// comment in list body] []>, " +
   481  			"e2: <[d1// comment in struct body] {}>",
   482  	}, {
   483  		"attribute comments",
   484  		`
   485  		a: 1 @a() @b() // d
   486  		`,
   487  		`<[l5// d] a: 1 @a() @b()>`,
   488  	}, {
   489  		"attribute declarations",
   490  		`
   491  		@foo()
   492  
   493  		package bar
   494  
   495  		@bar()
   496  
   497  		import "strings"
   498  
   499  		@baz()
   500  			`,
   501  		`@foo(), package bar, @bar(), import "strings", @baz()`,
   502  	}, {
   503  		"comprehension comments",
   504  		`
   505  		if X {
   506  			// Comment 1
   507  			Field: 2
   508  			// Comment 2
   509  		}
   510  		`,
   511  		`if X <[d2// Comment 2] {<[d0// Comment 1] Field: 2>}>`,
   512  	}, {
   513  		"let comments",
   514  		`let X = foo // Comment 1`,
   515  		`<[5// Comment 1] let X=foo>`,
   516  	}, {
   517  		"emit comments",
   518  		`// a comment at the beginning of the file
   519  
   520  		// a second comment
   521  
   522  		// comment
   523  		a: 5
   524  
   525  		{}
   526  
   527  		// a comment at the end of the file
   528  		`,
   529  		"<[0// a comment at the beginning of the file] [0// a second comment] <[d0// comment] a: 5>, <[2// a comment at the end of the file] {}>>",
   530  	}, {
   531  		"composite comments 2",
   532  		`
   533  	{
   534  // foo
   535  
   536  // fooo
   537  foo: 1
   538  
   539  bar: 2
   540  	}
   541  
   542  [
   543  	{"name": "value"}, // each element has a long
   544  	{"name": "next"}   // optional next element
   545  ]
   546  `,
   547  		`{<[0// foo] [d0// fooo] foo: 1>, bar: 2}, [<[l4// each element has a long] {"name": "value"}>, <[l4// optional next element] {"name": "next"}>]`,
   548  	}, {
   549  		desc: "field aliasing",
   550  		in: `
   551  		I="\(k)": v
   552  		S="foo-bar": w
   553  		L=foo: x
   554  		X=[0]: {
   555  			foo: X | null
   556  		}
   557  		[Y=string]: { name: Y }
   558  		X1=[X2=<"d"]: { name: X2 }
   559  		Y1=foo: Y2=bar: [Y1, Y2]
   560  		`,
   561  		out: `I="\(k)": v, ` +
   562  			`S="foo-bar": w, ` +
   563  			`L=foo: x, ` +
   564  			`X=[0]: {foo: X|null}, ` +
   565  			`[Y=string]: {name: Y}, ` +
   566  			`X1=[X2=<"d"]: {name: X2}, ` +
   567  			`Y1=foo: {Y2=bar: [Y1, Y2]}`,
   568  	}, {
   569  		desc: "allow keyword in expression",
   570  		in: `
   571  		foo: in & 2
   572  		`,
   573  		out: "foo: in&2",
   574  	}, {
   575  		desc: "dot import",
   576  		in: `
   577  		import . "foo"
   578  		`,
   579  		out: "import , \"foo\"\nexpected 'STRING', found '.'",
   580  	}, {
   581  		desc: "attributes",
   582  		in: `
   583  		package name
   584  
   585  		@t1(v1)
   586  
   587  		{
   588  			@t2(v2)
   589  		}
   590  		a: {
   591  			a: 1
   592  			@t3(v3)
   593  			@t4(v4)
   594  			c: 2
   595  		}
   596  		`,
   597  		out: "package name, @t1(v1), {@t2(v2)}, a: {a: 1, @t3(v3), @t4(v4), c: 2}",
   598  	}, {
   599  		desc: "Issue #276",
   600  		in: `
   601  		a: int=>2
   602  		`,
   603  		out: "a: int=>2",
   604  	}, {
   605  		desc: "struct comments",
   606  		in: `
   607  		struct: {
   608  			// This is a comment
   609  		
   610  			// This is a comment
   611  		
   612  			// Another comment
   613  			something: {
   614  			}
   615  		
   616  			// extra comment
   617  		}`,
   618  		out: `struct: {<[0// This is a comment] [0// This is a comment] [d0// Another comment] [d5// extra comment] something: {}>}`,
   619  	}, {
   620  		desc: "list comments",
   621  		in: `
   622  		list: [
   623  			// Comment1
   624  
   625  			// Comment2
   626  
   627  			// Another comment
   628  			{
   629  			},
   630  
   631  			// Comment 3
   632  		]`,
   633  		out: "list: [<[0// Comment1] [0// Comment2] [d0// Another comment] [d3// Comment 3] {}>]",
   634  	}, {
   635  		desc: "call comments",
   636  		in: `
   637  		funcArg1: foo(
   638  			{},
   639  	
   640  			// Comment1
   641  
   642  			// Comment2
   643  			{}
   644  	
   645  			// Comment3
   646  		)`,
   647  		out: "funcArg1: foo(<[1// Comment1] {}>, <[d0// Comment2] [d1// Comment3] {}>)",
   648  	}, {
   649  		desc: "front-style commas",
   650  		in: `
   651  			frontStyle: { "key": "value"
   652  				, "key2": "value2"
   653  				, "foo" : bar
   654  			}
   655  			`,
   656  		out: "frontStyle: {\"key\": \"value\", \"key2\": \"value2\", \"foo\": bar}",
   657  	}}
   658  	for _, tc := range testCases {
   659  		t.Run(tc.desc, func(t *testing.T) {
   660  			mode := []Option{AllErrors}
   661  			if strings.Contains(tc.desc, "comments") {
   662  				mode = append(mode, ParseComments)
   663  			}
   664  			f, err := ParseFile("input", tc.in, mode...)
   665  			got := debugStr(f)
   666  			if err != nil {
   667  				got += "\n" + err.Error()
   668  			}
   669  			if got != tc.out {
   670  				t.Errorf("\ngot  %q;\nwant %q", got, tc.out)
   671  			}
   672  		})
   673  	}
   674  }
   675  
   676  func TestStrict(t *testing.T) {
   677  	testCases := []struct{ desc, in string }{
   678  		{"block comments",
   679  			`a: 1 /* a */`},
   680  		{"space separator",
   681  			`a b c: 2`},
   682  		{"reserved identifiers",
   683  			`__foo: 3`},
   684  		{"old-style definition",
   685  			`foo :: 3`},
   686  		{"old-style alias 1",
   687  			`X=3`},
   688  		{"old-style alias 2",
   689  			`X={}`},
   690  
   691  		// Not yet supported
   692  		{"additional typed not yet supported",
   693  			`{...int}`},
   694  	}
   695  	for _, tc := range testCases {
   696  		t.Run(tc.desc, func(t *testing.T) {
   697  			mode := []Option{AllErrors, ParseComments, FromVersion(Latest)}
   698  			_, err := ParseFile("input", tc.in, mode...)
   699  			if err == nil {
   700  				t.Errorf("unexpected success: %v", tc.in)
   701  			}
   702  		})
   703  	}
   704  }
   705  
   706  func TestParseExpr(t *testing.T) {
   707  	// just kicking the tires:
   708  	// a valid arithmetic expression
   709  	src := "a + b"
   710  	x, err := parseExprString(src)
   711  	if err != nil {
   712  		t.Errorf("ParseExpr(%q): %v", src, err)
   713  	}
   714  	// sanity check
   715  	if _, ok := x.(*ast.BinaryExpr); !ok {
   716  		t.Errorf("ParseExpr(%q): got %T, want *BinaryExpr", src, x)
   717  	}
   718  
   719  	// an invalid expression
   720  	src = "a + *"
   721  	if _, err := parseExprString(src); err == nil {
   722  		t.Errorf("ParseExpr(%q): got no error", src)
   723  	}
   724  
   725  	// a comma is not permitted unless automatically inserted
   726  	src = "a + b\n"
   727  	if _, err := parseExprString(src); err != nil {
   728  		t.Errorf("ParseExpr(%q): got error %s", src, err)
   729  	}
   730  	src = "a + b;"
   731  	if _, err := parseExprString(src); err == nil {
   732  		t.Errorf("ParseExpr(%q): got no error", src)
   733  	}
   734  
   735  	// check resolution
   736  	src = "{ foo: bar, bar: foo }"
   737  	x, err = parseExprString(src)
   738  	if err != nil {
   739  		t.Fatalf("ParseExpr(%q): %v", src, err)
   740  	}
   741  	for _, d := range x.(*ast.StructLit).Elts {
   742  		v := d.(*ast.Field).Value.(*ast.Ident)
   743  		if v.Scope == nil {
   744  			t.Errorf("ParseExpr(%q): scope of field %v not set", src, v.Name)
   745  		}
   746  		if v.Node == nil {
   747  			t.Errorf("ParseExpr(%q): scope of node %v not set", src, v.Name)
   748  		}
   749  	}
   750  
   751  	// various other stuff following a valid expression
   752  	const validExpr = "a + b"
   753  	const anything = "dh3*#D)#_"
   754  	for _, c := range "!)]};," {
   755  		src := validExpr + string(c) + anything
   756  		if _, err := parseExprString(src); err == nil {
   757  			t.Errorf("ParseExpr(%q): got no error", src)
   758  		}
   759  	}
   760  
   761  	// ParseExpr must not crash
   762  	for _, src := range valids {
   763  		_, _ = parseExprString(src)
   764  	}
   765  }
   766  
   767  func TestImports(t *testing.T) {
   768  	var imports = map[string]bool{
   769  		`"a"`:        true,
   770  		`"a/b"`:      true,
   771  		`"a.b"`:      true,
   772  		`'m\x61th'`:  true,
   773  		`"greek/αβ"`: true,
   774  		`""`:         false,
   775  
   776  		// Each of these pairs tests both #""# vs "" strings
   777  		// and also use of invalid characters spelled out as
   778  		// escape sequences and written directly.
   779  		// For example `"\x00"` tests import "\x00"
   780  		// while "`\x00`" tests import `<actual-NUL-byte>`.
   781  		`#"a"#`:        true,
   782  		`"\x00"`:       false,
   783  		"'\x00'":       false,
   784  		`"\x7f"`:       false,
   785  		"`\x7f`":       false,
   786  		`"a!"`:         false,
   787  		"#'a!'#":       false,
   788  		`"a b"`:        false,
   789  		`#"a b"#`:      false,
   790  		`"a\\b"`:       false,
   791  		"#\"a\\b\"#":   false,
   792  		"\"`a`\"":      false,
   793  		"#'\"a\"'#":    false,
   794  		`"\x80\x80"`:   false,
   795  		"#'\x80\x80'#": false,
   796  		`"\xFFFD"`:     false,
   797  		"#'\xFFFD'#":   false,
   798  	}
   799  	for path, isValid := range imports {
   800  		t.Run(path, func(t *testing.T) {
   801  			src := fmt.Sprintf("package p, import %s", path)
   802  			_, err := ParseFile("", src)
   803  			switch {
   804  			case err != nil && isValid:
   805  				t.Errorf("ParseFile(%s): got %v; expected no error", src, err)
   806  			case err == nil && !isValid:
   807  				t.Errorf("ParseFile(%s): got no error; expected one", src)
   808  			}
   809  		})
   810  	}
   811  }
   812  
   813  // TestIncompleteSelection ensures that an incomplete selector
   814  // expression is parsed as a (blank) *SelectorExpr, not a
   815  // *BadExpr.
   816  func TestIncompleteSelection(t *testing.T) {
   817  	for _, src := range []string{
   818  		"{ a: fmt. }",         // at end of object
   819  		"{ a: fmt.\n0.0: x }", // not at end of struct
   820  	} {
   821  		t.Run("", func(t *testing.T) {
   822  			f, err := ParseFile("", src)
   823  			if err == nil {
   824  				t.Fatalf("ParseFile(%s) succeeded unexpectedly", src)
   825  			}
   826  
   827  			const wantErr = "expected selector"
   828  			if !strings.Contains(err.Error(), wantErr) {
   829  				t.Errorf("ParseFile returned wrong error %q, want %q", err, wantErr)
   830  			}
   831  
   832  			var sel *ast.SelectorExpr
   833  			ast.Walk(f, func(n ast.Node) bool {
   834  				if n, ok := n.(*ast.SelectorExpr); ok {
   835  					sel = n
   836  				}
   837  				return true
   838  			}, nil)
   839  			if sel == nil {
   840  				t.Fatalf("found no *SelectorExpr: %#v %s", f.Decls[0], debugStr(f))
   841  			}
   842  			const wantSel = "&{fmt _ {<nil>} {{}}}"
   843  			if fmt.Sprint(sel) != wantSel {
   844  				t.Fatalf("found selector %v, want %s", sel, wantSel)
   845  			}
   846  		})
   847  	}
   848  }
   849  
   850  // For debugging, do not delete.
   851  func TestX(t *testing.T) {
   852  	t.Skip()
   853  
   854  	f, err := ParseFile("input", `
   855  	`, ParseComments)
   856  	if err != nil {
   857  		t.Errorf("unexpected error: %v", err)
   858  	}
   859  	t.Error(debugStr(f))
   860  }