blob: 32c0bf8306065eddea0a7204df2806be47386e41 [file] [log] [blame]
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +01001// 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
15package parser
16
17import (
18 "bytes"
19 "fmt"
20 "strings"
21 "testing"
22
23 "cuelang.org/go/cue/ast"
24 "cuelang.org/go/cue/token"
25)
26
27func TestParse(t *testing.T) {
28 testCases := []struct{ desc, in, out string }{{
29 "empty file", "", "",
30 }, {
31 "empty struct", "{}", "{}",
32 }, {
33 "empty structs", "{},{},", "{}, {}",
34 }, {
35 "empty structs; elided comma", "{}\n{}", "{}, {}",
36 }, {
37 "basic lits", `"a","b", 3,3.4,5,2_3`, `"a", "b", 3, 3.4, 5, 2_3`,
38 }, {
39 "keyword basic lits", `true,false,null`, `true, false, null`,
40 }, {
41 "keywords as labels",
42 `if: 0, for: 1, in: 2, where: 3, div: 4, quo: 5`,
43 `if: 0, for: 1, in: 2, where: 3, div: 4, quo: 5`,
44 }, {
45 "json",
46 `{
47 "a": 1,
48 "b": "2",
49 "c": 3
50 }`,
51 `{"a": 1, "b": "2", "c": 3}`,
52 }, {
53 "json:extra comma",
54 `{
55 "a": 1,
56 "b": "2",
57 "c": 3,
58 }`,
59 `{"a": 1, "b": "2", "c": 3}`,
60 }, {
61 "json:simplified",
62 `{
63 a: 1
64 b: "2"
65 c: 3
66 }`,
67 `{a: 1, b: "2", c: 3}`,
68 }, {
Marcel van Lohuizenb9b62d32019-03-14 23:50:15 +010069 "attributes",
70 `a: 1 @xml(,attr)
71 b: 2 @foo(a,b=4) @go(Foo)
72 c: {
73 d: "x" @go(D) @json(,omitempty)
74 e: "y" @ts(,type=string)
75 }`,
76 `a: 1 @xml(,attr), b: 2 @foo(a,b=4) @go(Foo), c: {d: "x" @go(D) @json(,omitempty), e: "y" @ts(,type=string)}`,
77 }, {
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +010078 "not emitted",
79 `a: true
Marcel van Lohuizen08a0ef22019-03-28 09:12:19 +010080 b?: "2"
81 c?: 3
82
83 "g\("en")"?: 4
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +010084 `,
Marcel van Lohuizen08a0ef22019-03-28 09:12:19 +010085 `a: true, b?: "2", c?: 3, "g\("en")"?: 4`,
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +010086 }, {
Marcel van Lohuizenb9b62d32019-03-14 23:50:15 +010087 "emitted referencing non-emitted",
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +010088 `a: 1
89 b: "2"
90 c: 3
91 { name: b, total: a + b }`,
92 `a: 1, b: "2", c: 3, {name: b, total: a+b}`,
93 }, {
94 "package file",
95 `package k8s
96 {}
97 `,
98 `package k8s, {}`,
99 }, {
100 "imports group",
101 `package k8s
102
103 import (
104 a "foo"
105 "bar/baz"
106 . "model"
107 )
108 `,
109 `package k8s, import ( a "foo", "bar/baz", . "model" )`,
110 }, {
111 "imports single",
112 `package k8s
113
114 import a "foo"
115 import "bar/baz"
116 import . "model"
117 `,
118 `package k8s, import a "foo", import "bar/baz", import . "model"`,
119 }, {
120 "collapsed fields",
121 `a b c: 1
122 // job foo { bar: 1 } // TODO error after foo
123 job "foo": { bar: 1 }
124 `,
125 `a: {b: {c: 1}}, job: {"foo": {bar: 1}}`,
126 }, {
127 "identifiers",
128 `// $_: 1,
129 a: {b: {c: d}}
130 c: a
131 d: a.b
132 // e: a."b" // TODO: is an error
133 e: a.b.c
134 "f": f,
135 <X>: X
136 `,
137 "a: {b: {c: d}}, c: a, d: a.b, e: a.b.c, \"f\": f, <X>: X",
138 }, {
139 "expressions",
140 ` a: (2 + 3) * 5
141 b: (2 + 3) + 4
142 c: 2 + 3 + 4
143 d: -1
144 e: !foo
145 f: _|_
146 `,
147 "a: (2+3)*5, b: (2+3)+4, c: 2+3+4, d: -1, e: !foo, f: _|_",
148 }, {
149 "pseudo keyword expressions",
150 ` a: (2 div 3) mod 5
151 b: (2 quo 3) rem 4
152 c: 2 div 3 div 4
153 `,
154 "a: (2 div 3) mod 5, b: (2 quo 3) rem 4, c: 2 div 3 div 4",
155 }, {
156 "ranges",
Marcel van Lohuizen7d0797b2019-02-07 18:35:28 +0100157 ` a: >=1 & <=2
158 b: >2.0 & <= 40.0
159 c: >"a" & <="b"
160 v: (>=1 & <=2) & <=(>=5 & <=10)
161 w: >1 & <=2 & <=3
162 d: >=3T & <=5M
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100163 `,
Marcel van Lohuizen7d0797b2019-02-07 18:35:28 +0100164 "a: >=1&<=2, b: >2.0&<=40.0, c: >\"a\"&<=\"b\", v: (>=1&<=2)&<=(>=5&<=10), w: >1&<=2&<=3, d: >=3T&<=5M",
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100165 }, {
166 "indices",
167 `{
168 a: b[2]
169 b: c[1:2]
170 c: "asdf"
171 d: c ["a"]
172 }`,
173 `{a: b[2], b: c[1:2], c: "asdf", d: c["a"]}`,
174 }, {
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100175 "calls",
176 `{
177 a: b(a.b, c.d)
178 b: a.b(c)
179 }`,
180 `{a: b(a.b, c.d), b: a.b(c)}`,
181 }, {
182 "lists",
183 `{
Marcel van Lohuizen935a4782019-02-22 19:54:29 +0100184 a: [ 1, 2, 3, b, c, ... ]
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100185 b: [ 1, 2, 3, ],
186 c: [ 1,
187 2,
188 3
189 ],
190 d: [ 1+2, 2, 4,]
191 }`,
Marcel van Lohuizen935a4782019-02-22 19:54:29 +0100192 `{a: [1, 2, 3, b, c, ...], b: [1, 2, 3], c: [1, 2, 3], d: [1+2, 2, 4]}`,
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100193 }, {
194 "list types",
195 `{
196 a: 4*[int]
Marcel van Lohuizen7d0797b2019-02-07 18:35:28 +0100197 b: <=5*[ {a: 5} ]
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100198 c1: [...int]
199 c2: [...]
200 c3: [1, 2, ...int,]
201 }`,
Marcel van Lohuizen7d0797b2019-02-07 18:35:28 +0100202 `{a: 4*[int], b: <=5*[{a: 5}], c1: [...int], c2: [...], c3: [1, 2, ...int]}`,
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100203 }, {
204 "list comprehensions",
205 `{
206 y: [1,2,3]
207 b: [ x for x in y if x == 1 ],
208 }`,
209 `{y: [1, 2, 3], b: [x for x in y if x==1 ]}`,
210 }, {
211 "field comprehensions",
212 `{
213 y: { a: 1, b: 2}
214 a: { "\(k)": v for k, v in y if v > 2 }
215 }`,
216 `{y: {a: 1, b: 2}, a: {"\(k)": v for k: v in y if v>2 }}`,
217 }, {
218 "duplicates allowed",
219 `{
220 a b: 3
221 a: { b: 3 }
222 }`,
223 "{a: {b: 3}, a: {b: 3}}",
224 }, {
225 "templates",
226 `{
227 <foo>: { a: int }
228 a: { a: 1 }
229 }`,
230 "{<foo>: {a: int}, a: {a: 1}}",
231 }, {
232 "foo",
233 `[
234 [1],
235 [1, 2],
236 [1, 2, 3],
237 ]`,
238 "[[1], [1, 2], [1, 2, 3]]",
239 }, {
240 "interpolation",
241 `a: "foo \(ident)"
242 b: "bar \(bar) $$$ "
243 c: "nest \( { a: "\( nest ) "}.a ) \(5)"
244 m1: """
245 multi \(bar)
246 """
247 m2: '''
248 \(bar) multi
249 '''`,
250 `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 '''",
251 }, {
252 "file comments",
253 `// foo
254
255 // uni
256 package foo // uniline
257
258 // file.1
259 // file.2
260
261 `,
262 "<[0// foo] [d0// uni] [l3// uniline] [3// file.1 // file.2] package foo, >",
263 }, {
264 "line comments",
265 `// doc
266 a: 5 // line
267 b: 6 // lineb
268 // next
269 `, // next is followed by EOF. Ensure it doesn't move to file.
Marcel van Lohuizenb9b62d32019-03-14 23:50:15 +0100270 "<[d0// doc] [l5// line] a: 5>, " +
271 "<[l5// lineb] [5// next] b: 6>",
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100272 }, {
273 "alt comments",
274 `// a ...
275 a: 5 // line a
276
277 // about a
278
279 // b ...
280 b: // lineb
281 6
282
283 // about b
284
285 c: 7
286
287 // about c
288
289 `,
Marcel van Lohuizenb9b62d32019-03-14 23:50:15 +0100290 "<[d0// a ...] [l5// line a] [5// about a] a: 5>, " +
291 "<[d0// b ...] [l2// lineb] [5// about b] b: 6>, " +
292 "<[5// about c] c: 7>",
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100293 }, {
294 "expr comments",
295 `
296 a: 2 + // 2 +
297 3 + // 3 +
298 4 // 4
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100299 `,
Marcel van Lohuizenb9b62d32019-03-14 23:50:15 +0100300 "<[l5// 4] a: <[l2// 3 +] <[l2// 2 +] 2+3>+4>>",
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100301 }, {
302 "composit comments",
303 `a : {
304 a: 1, b: 2, c: 3, d: 4
305 // end
306 }
307 b: [
308 1, 2, 3, 4, 5,
309 // end
310 ]
311 c: [ 1, 2, 3, 4, // here
312 5, 6, 7, 8 // and here
313 ]
314 d: {
315 a: /* 8 */ 1 // Hello
316 // Doc
317 b: 2
318 }
319 e1: [
320 // comment in list body
321 ]
322 e2: {
323 // comment in struct body
324 }
325 `,
326 "a: <[d2// end] {a: 1, b: 2, c: 3, d: 4}>, " +
327 "b: <[d2// end] [1, 2, 3, 4, 5]>, " +
328 "c: [1, 2, 3, <[l1// here] 4>, 5, 6, 7, <[l1// and here] 8>], " +
Marcel van Lohuizenb9b62d32019-03-14 23:50:15 +0100329 "d: {<[2/* 8 */] [l5// Hello] a: 1>, <[d0// Doc] b: 2>}, " +
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100330 "e1: <[d2// comment in list body] []>, " +
331 "e2: <[d1// comment in struct body] {}>",
332 }, {
Marcel van Lohuizenb9b62d32019-03-14 23:50:15 +0100333 "attribute comments",
334 `
335 a: 1 /* a */ @a() /* b */ @b() /* c */ // d
336 `,
337 `<[l5/* c */ // d] a: <[1/* a */] 1> <[1/* b */] @a()> @b()>`,
338 }, {
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100339 "emit comments",
340 `// a comment at the beginning of the file
341
342 // a second comment
343
344 // comment
345 a: 5
346
347 {}
348
349 // a comment at the end of the file
350 `,
351 "<[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] {}>>",
352 }}
353 for _, tc := range testCases {
354 t.Run(tc.desc, func(t *testing.T) {
355 fset := token.NewFileSet()
Marcel van Lohuizen2bf066f2018-12-16 11:43:49 +0100356 mode := []Option{AllErrors}
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100357 if strings.Contains(tc.desc, "comments") {
358 mode = append(mode, ParseComments)
359 }
360 f, err := ParseFile(fset, "input", tc.in, mode...)
361 if err != nil {
362 t.Errorf("unexpected error: %v", err)
363 }
364 if got := debugStr(f); got != tc.out {
365 t.Errorf("\ngot %q;\nwant %q", got, tc.out)
366 }
367 })
368 }
369}
370
371func TestParseExpr(t *testing.T) {
372 // just kicking the tires:
373 // a valid arithmetic expression
374 src := "a + b"
375 x, err := parseExprString(src)
376 if err != nil {
377 t.Errorf("ParseExpr(%q): %v", src, err)
378 }
379 // sanity check
380 if _, ok := x.(*ast.BinaryExpr); !ok {
381 t.Errorf("ParseExpr(%q): got %T, want *BinaryExpr", src, x)
382 }
383
384 // an invalid expression
385 src = "a + *"
386 if _, err := parseExprString(src); err == nil {
387 t.Errorf("ParseExpr(%q): got no error", src)
388 }
389
390 // a comma is not permitted unless automatically inserted
391 src = "a + b\n"
392 if _, err := parseExprString(src); err != nil {
393 t.Errorf("ParseExpr(%q): got error %s", src, err)
394 }
395 src = "a + b;"
396 if _, err := parseExprString(src); err == nil {
397 t.Errorf("ParseExpr(%q): got no error", src)
398 }
399
Marcel van Lohuizenea2de892019-04-03 22:34:44 +0200400 // check resolution
401 src = "{ foo: bar, bar: foo }"
402 x, err = parseExprString(src)
403 if err != nil {
404 t.Fatalf("ParseExpr(%q): %v", src, err)
405 }
406 for _, d := range x.(*ast.StructLit).Elts {
407 v := d.(*ast.Field).Value.(*ast.Ident)
408 if v.Scope == nil {
409 t.Errorf("ParseExpr(%q): scope of field %v not set", src, v.Name)
410 }
411 if v.Node == nil {
412 t.Errorf("ParseExpr(%q): scope of node %v not set", src, v.Name)
413 }
414 }
415
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100416 // various other stuff following a valid expression
417 const validExpr = "a + b"
418 const anything = "dh3*#D)#_"
419 for _, c := range "!)]};," {
420 src := validExpr + string(c) + anything
421 if _, err := parseExprString(src); err == nil {
422 t.Errorf("ParseExpr(%q): got no error", src)
423 }
424 }
425
426 // ParseExpr must not crash
427 for _, src := range valids {
428 parseExprString(src)
429 }
430}
431
432func TestImports(t *testing.T) {
433 var imports = map[string]bool{
434 `"a"`: true,
435 `"a/b"`: true,
436 `"a.b"`: true,
Marcel van Lohuizen369e4232019-02-15 10:59:29 +0400437 `'m\x61th'`: true,
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100438 `"greek/αβ"`: true,
439 `""`: false,
440
Marcel van Lohuizen369e4232019-02-15 10:59:29 +0400441 // Each of these pairs tests both #""# vs "" strings
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100442 // and also use of invalid characters spelled out as
443 // escape sequences and written directly.
444 // For example `"\x00"` tests import "\x00"
445 // while "`\x00`" tests import `<actual-NUL-byte>`.
Marcel van Lohuizen369e4232019-02-15 10:59:29 +0400446 `#"a"#`: true,
447 `"\x00"`: false,
448 "'\x00'": false,
449 `"\x7f"`: false,
450 "`\x7f`": false,
451 `"a!"`: false,
452 "#'a!'#": false,
453 `"a b"`: false,
454 `#"a b"#`: false,
455 `"a\\b"`: false,
456 "#\"a\\b\"#": false,
457 "\"`a`\"": false,
458 "#'\"a\"'#": false,
459 `"\x80\x80"`: false,
460 "#'\x80\x80'#": false,
461 `"\xFFFD"`: false,
462 "#'\xFFFD'#": false,
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100463 }
464 for path, isValid := range imports {
465 t.Run(path, func(t *testing.T) {
466 src := fmt.Sprintf("package p, import %s", path)
467 _, err := ParseFile(token.NewFileSet(), "", src)
468 switch {
469 case err != nil && isValid:
470 t.Errorf("ParseFile(%s): got %v; expected no error", src, err)
471 case err == nil && !isValid:
472 t.Errorf("ParseFile(%s): got no error; expected one", src)
473 }
474 })
475 }
476}
477
478func labelName(l ast.Label) string {
479 name, _ := ast.LabelName(l)
480 return name
481}
482
483func getField(file *ast.File, fieldname string) *ast.Field {
484 get := func(elts []ast.Decl, name string) *ast.Field {
485 for _, s := range elts {
486 if s, ok := s.(*ast.Field); ok && labelName(s.Label) == name {
487 return s
488 }
489 }
490 return nil
491 }
492 elts := file.Decls
493 var m *ast.Field
494 for _, p := range strings.Split(fieldname, ".") {
495 m = get(elts, p)
496 if v, ok := m.Value.(*ast.StructLit); ok {
497 elts = v.Elts
498 } else {
499 break
500 }
501 }
502 return m
503}
504
505// Don't use CommentGroup.Text() - we want to see exact comment text.
506func commentText(c *ast.CommentGroup) string {
507 var buf bytes.Buffer
508 if c != nil {
509 for _, c := range c.List {
510 buf.WriteString(c.Text)
511 }
512 }
513 return buf.String()
514}
515
516// TestIncompleteSelection ensures that an incomplete selector
517// expression is parsed as a (blank) *SelectorExpr, not a
518// *BadExpr.
519func TestIncompleteSelection(t *testing.T) {
520 for _, src := range []string{
521 "{ a: fmt. }", // at end of object
522 "{ a: fmt.\n\"a\": x }", // not at end of struct
523 } {
524 t.Run("", func(t *testing.T) {
525 fset := token.NewFileSet()
526 f, err := ParseFile(fset, "", src)
527 if err == nil {
528 t.Fatalf("ParseFile(%s) succeeded unexpectedly", src)
529 }
530
531 const wantErr = "expected selector"
532 if !strings.Contains(err.Error(), wantErr) {
533 t.Errorf("ParseFile returned wrong error %q, want %q", err, wantErr)
534 }
535
536 var sel *ast.SelectorExpr
537 ast.Walk(f, func(n ast.Node) bool {
538 if n, ok := n.(*ast.SelectorExpr); ok {
539 sel = n
540 }
541 return true
542 }, nil)
543 if sel == nil {
544 t.Fatalf("found no *SelectorExpr: %#v %s", f.Decls[0], debugStr(f))
545 }
546 const wantSel = "&{{<nil>} fmt _}"
547 if fmt.Sprint(sel) != wantSel {
548 t.Fatalf("found selector %v, want %s", sel, wantSel)
549 }
550 })
551 }
552}