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 }