blob: e0c5a6915f2deddebfaef764b1a176a93883f64c [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 }, {
69 "not emitted",
70 `a: true
71 b: "2"
72 c: 3
73 `,
74 `a: true, b: "2", c: 3`,
75 }, {
76 "emitted refrencing non-emitted",
77 `a: 1
78 b: "2"
79 c: 3
80 { name: b, total: a + b }`,
81 `a: 1, b: "2", c: 3, {name: b, total: a+b}`,
82 }, {
83 "package file",
84 `package k8s
85 {}
86 `,
87 `package k8s, {}`,
88 }, {
89 "imports group",
90 `package k8s
91
92 import (
93 a "foo"
94 "bar/baz"
95 . "model"
96 )
97 `,
98 `package k8s, import ( a "foo", "bar/baz", . "model" )`,
99 }, {
100 "imports single",
101 `package k8s
102
103 import a "foo"
104 import "bar/baz"
105 import . "model"
106 `,
107 `package k8s, import a "foo", import "bar/baz", import . "model"`,
108 }, {
109 "collapsed fields",
110 `a b c: 1
111 // job foo { bar: 1 } // TODO error after foo
112 job "foo": { bar: 1 }
113 `,
114 `a: {b: {c: 1}}, job: {"foo": {bar: 1}}`,
115 }, {
116 "identifiers",
117 `// $_: 1,
118 a: {b: {c: d}}
119 c: a
120 d: a.b
121 // e: a."b" // TODO: is an error
122 e: a.b.c
123 "f": f,
124 <X>: X
125 `,
126 "a: {b: {c: d}}, c: a, d: a.b, e: a.b.c, \"f\": f, <X>: X",
127 }, {
128 "expressions",
129 ` a: (2 + 3) * 5
130 b: (2 + 3) + 4
131 c: 2 + 3 + 4
132 d: -1
133 e: !foo
134 f: _|_
135 `,
136 "a: (2+3)*5, b: (2+3)+4, c: 2+3+4, d: -1, e: !foo, f: _|_",
137 }, {
138 "pseudo keyword expressions",
139 ` a: (2 div 3) mod 5
140 b: (2 quo 3) rem 4
141 c: 2 div 3 div 4
142 `,
143 "a: (2 div 3) mod 5, b: (2 quo 3) rem 4, c: 2 div 3 div 4",
144 }, {
145 "ranges",
146 ` a: 1..2
147 b: 2.0 .. 40.0
148 c: "a".."b"
149 v: (1..2)..(5..10)
150 w: 1..2..3
151 d: 3T..5M
152 `,
153 "a: 1..2, b: 2.0..40.0, c: \"a\"..\"b\", v: (1..2)..(5..10), w: 1..2..3, d: 3T..5M",
154 }, {
155 "indices",
156 `{
157 a: b[2]
158 b: c[1:2]
159 c: "asdf"
160 d: c ["a"]
161 }`,
162 `{a: b[2], b: c[1:2], c: "asdf", d: c["a"]}`,
163 }, {
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100164 "calls",
165 `{
166 a: b(a.b, c.d)
167 b: a.b(c)
168 }`,
169 `{a: b(a.b, c.d), b: a.b(c)}`,
170 }, {
171 "lists",
172 `{
173 a: [ 1, 2, 3, b..., c... ]
174 b: [ 1, 2, 3, ],
175 c: [ 1,
176 2,
177 3
178 ],
179 d: [ 1+2, 2, 4,]
180 }`,
181 `{a: [1, 2, 3, b..., c...], b: [1, 2, 3], c: [1, 2, 3], d: [1+2, 2, 4]}`,
182 }, {
183 "list types",
184 `{
185 a: 4*[int]
186 b: 0..5*[ {a: 5} ]
187 c1: [...int]
188 c2: [...]
189 c3: [1, 2, ...int,]
190 }`,
191 `{a: 4*[int], b: 0..5*[{a: 5}], c1: [...int], c2: [...], c3: [1, 2, ...int]}`,
192 }, {
193 "list comprehensions",
194 `{
195 y: [1,2,3]
196 b: [ x for x in y if x == 1 ],
197 }`,
198 `{y: [1, 2, 3], b: [x for x in y if x==1 ]}`,
199 }, {
200 "field comprehensions",
201 `{
202 y: { a: 1, b: 2}
203 a: { "\(k)": v for k, v in y if v > 2 }
204 }`,
205 `{y: {a: 1, b: 2}, a: {"\(k)": v for k: v in y if v>2 }}`,
206 }, {
207 "duplicates allowed",
208 `{
209 a b: 3
210 a: { b: 3 }
211 }`,
212 "{a: {b: 3}, a: {b: 3}}",
213 }, {
214 "templates",
215 `{
216 <foo>: { a: int }
217 a: { a: 1 }
218 }`,
219 "{<foo>: {a: int}, a: {a: 1}}",
220 }, {
221 "foo",
222 `[
223 [1],
224 [1, 2],
225 [1, 2, 3],
226 ]`,
227 "[[1], [1, 2], [1, 2, 3]]",
228 }, {
229 "interpolation",
230 `a: "foo \(ident)"
231 b: "bar \(bar) $$$ "
232 c: "nest \( { a: "\( nest ) "}.a ) \(5)"
233 m1: """
234 multi \(bar)
235 """
236 m2: '''
237 \(bar) multi
238 '''`,
239 `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 '''",
240 }, {
241 "file comments",
242 `// foo
243
244 // uni
245 package foo // uniline
246
247 // file.1
248 // file.2
249
250 `,
251 "<[0// foo] [d0// uni] [l3// uniline] [3// file.1 // file.2] package foo, >",
252 }, {
253 "line comments",
254 `// doc
255 a: 5 // line
256 b: 6 // lineb
257 // next
258 `, // next is followed by EOF. Ensure it doesn't move to file.
259 "<[d0// doc] [l4// line] a: 5>, " +
260 "<[l4// lineb] [4// next] b: 6>",
261 }, {
262 "alt comments",
263 `// a ...
264 a: 5 // line a
265
266 // about a
267
268 // b ...
269 b: // lineb
270 6
271
272 // about b
273
274 c: 7
275
276 // about c
277
278 `,
279 "<[d0// a ...] [l4// line a] [4// about a] a: 5>, " +
280 "<[d0// b ...] [l2// lineb] [4// about b] b: 6>, " +
281 "<[4// about c] c: 7>",
282 }, {
283 "expr comments",
284 `
285 a: 2 + // 2 +
286 3 + // 3 +
287 4 // 4
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100288 `,
Marcel van Lohuizen2bf066f2018-12-16 11:43:49 +0100289 "<[l4// 4] a: <[l2// 3 +] <[l2// 2 +] 2+3>+4>>",
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100290 }, {
291 "composit comments",
292 `a : {
293 a: 1, b: 2, c: 3, d: 4
294 // end
295 }
296 b: [
297 1, 2, 3, 4, 5,
298 // end
299 ]
300 c: [ 1, 2, 3, 4, // here
301 5, 6, 7, 8 // and here
302 ]
303 d: {
304 a: /* 8 */ 1 // Hello
305 // Doc
306 b: 2
307 }
308 e1: [
309 // comment in list body
310 ]
311 e2: {
312 // comment in struct body
313 }
314 `,
315 "a: <[d2// end] {a: 1, b: 2, c: 3, d: 4}>, " +
316 "b: <[d2// end] [1, 2, 3, 4, 5]>, " +
317 "c: [1, 2, 3, <[l1// here] 4>, 5, 6, 7, <[l1// and here] 8>], " +
318 "d: {<[2/* 8 */] [l4// Hello] a: 1>, <[d0// Doc] b: 2>}, " +
319 "e1: <[d2// comment in list body] []>, " +
320 "e2: <[d1// comment in struct body] {}>",
321 }, {
322 "emit comments",
323 `// a comment at the beginning of the file
324
325 // a second comment
326
327 // comment
328 a: 5
329
330 {}
331
332 // a comment at the end of the file
333 `,
334 "<[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] {}>>",
335 }}
336 for _, tc := range testCases {
337 t.Run(tc.desc, func(t *testing.T) {
338 fset := token.NewFileSet()
Marcel van Lohuizen2bf066f2018-12-16 11:43:49 +0100339 mode := []Option{AllErrors}
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100340 if strings.Contains(tc.desc, "comments") {
341 mode = append(mode, ParseComments)
342 }
343 f, err := ParseFile(fset, "input", tc.in, mode...)
344 if err != nil {
345 t.Errorf("unexpected error: %v", err)
346 }
347 if got := debugStr(f); got != tc.out {
348 t.Errorf("\ngot %q;\nwant %q", got, tc.out)
349 }
350 })
351 }
352}
353
354func TestParseExpr(t *testing.T) {
355 // just kicking the tires:
356 // a valid arithmetic expression
357 src := "a + b"
358 x, err := parseExprString(src)
359 if err != nil {
360 t.Errorf("ParseExpr(%q): %v", src, err)
361 }
362 // sanity check
363 if _, ok := x.(*ast.BinaryExpr); !ok {
364 t.Errorf("ParseExpr(%q): got %T, want *BinaryExpr", src, x)
365 }
366
367 // an invalid expression
368 src = "a + *"
369 if _, err := parseExprString(src); err == nil {
370 t.Errorf("ParseExpr(%q): got no error", src)
371 }
372
373 // a comma is not permitted unless automatically inserted
374 src = "a + b\n"
375 if _, err := parseExprString(src); err != nil {
376 t.Errorf("ParseExpr(%q): got error %s", src, err)
377 }
378 src = "a + b;"
379 if _, err := parseExprString(src); err == nil {
380 t.Errorf("ParseExpr(%q): got no error", src)
381 }
382
383 // various other stuff following a valid expression
384 const validExpr = "a + b"
385 const anything = "dh3*#D)#_"
386 for _, c := range "!)]};," {
387 src := validExpr + string(c) + anything
388 if _, err := parseExprString(src); err == nil {
389 t.Errorf("ParseExpr(%q): got no error", src)
390 }
391 }
392
393 // ParseExpr must not crash
394 for _, src := range valids {
395 parseExprString(src)
396 }
397}
398
399func TestImports(t *testing.T) {
400 var imports = map[string]bool{
401 `"a"`: true,
402 `"a/b"`: true,
403 `"a.b"`: true,
404 `"m\x61th"`: true,
405 `"greek/αβ"`: true,
406 `""`: false,
407
408 // Each of these pairs tests both `` vs "" strings
409 // and also use of invalid characters spelled out as
410 // escape sequences and written directly.
411 // For example `"\x00"` tests import "\x00"
412 // while "`\x00`" tests import `<actual-NUL-byte>`.
413 "`a`": true,
414 `"\x00"`: false,
415 "`\x00`": false,
416 `"\x7f"`: false,
417 "`\x7f`": false,
418 `"a!"`: false,
419 "`a!`": false,
420 `"a b"`: false,
421 "`a b`": false,
422 `"a\\b"`: false,
423 "`a\\b`": false,
424 "\"`a`\"": false,
425 "`\"a\"`": false,
426 `"\x80\x80"`: false,
427 "`\x80\x80`": false,
428 `"\xFFFD"`: false,
429 "`\xFFFD`": false,
430 }
431 for path, isValid := range imports {
432 t.Run(path, func(t *testing.T) {
433 src := fmt.Sprintf("package p, import %s", path)
434 _, err := ParseFile(token.NewFileSet(), "", src)
435 switch {
436 case err != nil && isValid:
437 t.Errorf("ParseFile(%s): got %v; expected no error", src, err)
438 case err == nil && !isValid:
439 t.Errorf("ParseFile(%s): got no error; expected one", src)
440 }
441 })
442 }
443}
444
445func labelName(l ast.Label) string {
446 name, _ := ast.LabelName(l)
447 return name
448}
449
450func getField(file *ast.File, fieldname string) *ast.Field {
451 get := func(elts []ast.Decl, name string) *ast.Field {
452 for _, s := range elts {
453 if s, ok := s.(*ast.Field); ok && labelName(s.Label) == name {
454 return s
455 }
456 }
457 return nil
458 }
459 elts := file.Decls
460 var m *ast.Field
461 for _, p := range strings.Split(fieldname, ".") {
462 m = get(elts, p)
463 if v, ok := m.Value.(*ast.StructLit); ok {
464 elts = v.Elts
465 } else {
466 break
467 }
468 }
469 return m
470}
471
472// Don't use CommentGroup.Text() - we want to see exact comment text.
473func commentText(c *ast.CommentGroup) string {
474 var buf bytes.Buffer
475 if c != nil {
476 for _, c := range c.List {
477 buf.WriteString(c.Text)
478 }
479 }
480 return buf.String()
481}
482
483// TestIncompleteSelection ensures that an incomplete selector
484// expression is parsed as a (blank) *SelectorExpr, not a
485// *BadExpr.
486func TestIncompleteSelection(t *testing.T) {
487 for _, src := range []string{
488 "{ a: fmt. }", // at end of object
489 "{ a: fmt.\n\"a\": x }", // not at end of struct
490 } {
491 t.Run("", func(t *testing.T) {
492 fset := token.NewFileSet()
493 f, err := ParseFile(fset, "", src)
494 if err == nil {
495 t.Fatalf("ParseFile(%s) succeeded unexpectedly", src)
496 }
497
498 const wantErr = "expected selector"
499 if !strings.Contains(err.Error(), wantErr) {
500 t.Errorf("ParseFile returned wrong error %q, want %q", err, wantErr)
501 }
502
503 var sel *ast.SelectorExpr
504 ast.Walk(f, func(n ast.Node) bool {
505 if n, ok := n.(*ast.SelectorExpr); ok {
506 sel = n
507 }
508 return true
509 }, nil)
510 if sel == nil {
511 t.Fatalf("found no *SelectorExpr: %#v %s", f.Decls[0], debugStr(f))
512 }
513 const wantSel = "&{{<nil>} fmt _}"
514 if fmt.Sprint(sel) != wantSel {
515 t.Fatalf("found selector %v, want %s", sel, wantSel)
516 }
517 })
518 }
519}