cuelang.org/go@v0.13.0/cue/format/format_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 format_test 16 17 // TODO: port more of the tests of go/printer 18 19 import ( 20 "strings" 21 "testing" 22 23 "github.com/go-quicktest/qt" 24 25 "cuelang.org/go/cue/ast" 26 "cuelang.org/go/cue/format" 27 "cuelang.org/go/cue/parser" 28 "cuelang.org/go/cue/token" 29 "cuelang.org/go/internal" 30 "cuelang.org/go/internal/cuetxtar" 31 ) 32 33 const debug = false 34 35 func TestFiles(t *testing.T) { 36 test := cuetxtar.TxTarTest{ 37 Root: "./testdata", 38 Name: "format", 39 } 40 test.Run(t, func(t *cuetxtar.Test) { 41 opts := []format.Option{format.TabIndent(true)} 42 if t.HasTag("simplify") { 43 opts = append(opts, format.Simplify()) 44 } 45 // TODO(mvdan): note that this option is not exposed in the API, 46 // nor does it seem to be actually tested in any of the txtar testdata files. 47 // if t.HasTag("sort-imports") { 48 // opts = append(opts, format.sortImportsOption()) 49 // } 50 51 for _, f := range t.Archive.Files { 52 if !strings.HasSuffix(f.Name, ".input") { 53 continue 54 } 55 res, err := format.Source(f.Data, opts...) 56 qt.Assert(t, qt.IsNil(err)) 57 58 // make sure formatted output is syntactically correct 59 _, err = parser.ParseFile("", res, parser.AllErrors) 60 qt.Assert(t, qt.IsNil(err)) 61 62 goldenFile := strings.TrimSuffix(f.Name, ".input") + ".golden" 63 t.Writer(goldenFile).Write(res) 64 65 // TODO(mvdan): check that all files format in an idempotent way, 66 // i.e. that formatting a golden file results in no changes. 67 } 68 }) 69 } 70 71 // Verify that the printer can be invoked during initialization. 72 func init() { 73 const name = "foobar" 74 b, err := format.Node(&ast.Ident{Name: name}) 75 if err != nil { 76 panic(err) // error in test 77 } 78 // in debug mode, the result contains additional information; 79 // ignore it 80 if s := string(b); !debug && s != name { 81 panic("got " + s + ", want " + name) 82 } 83 } 84 85 // TestNodes tests nodes that are invalid CUE, but are accepted by 86 // format. 87 func TestNodes(t *testing.T) { 88 testCases := []struct { 89 name string 90 in ast.Node 91 out string 92 }{{ 93 name: "old-style octal numbers", 94 in: ast.NewLit(token.INT, "0123"), 95 out: "0o123", 96 }, { 97 name: "labels with multi-line strings", 98 in: &ast.Field{ 99 Label: ast.NewLit(token.STRING, 100 `""" 101 foo 102 bar 103 """`, 104 ), 105 Value: ast.NewIdent("goo"), 106 }, 107 out: `"foo\nbar": goo`, 108 }, { 109 name: "foo", 110 in: func() ast.Node { 111 st := ast.NewStruct("version", ast.NewString("foo")) 112 st = ast.NewStruct("info", st) 113 ast.AddComment(st.Elts[0], internal.NewComment(true, "FOO")) 114 return st 115 }(), 116 out: `{ 117 // FOO 118 info: { 119 version: "foo" 120 } 121 }`, 122 }} 123 for _, tc := range testCases { 124 t.Run(tc.name, func(t *testing.T) { 125 b, err := format.Node(tc.in, format.Simplify()) 126 if err != nil { 127 t.Fatal(err) 128 } 129 if got := string(b); got != tc.out { 130 t.Errorf("\ngot: %v; want: %v", got, tc.out) 131 } 132 }) 133 } 134 135 } 136 137 // Verify that the printer doesn't crash if the AST contains Bad... nodes. 138 func TestBadNodes(t *testing.T) { 139 const src = "package p\n(" 140 const res = "package p\n\n(_|_)\n" 141 f, err := parser.ParseFile("", src, parser.ParseComments) 142 if err == nil { 143 t.Error("expected illegal program") // error in test 144 } 145 b, _ := format.Node(f) 146 if string(b) != res { 147 t.Errorf("got %q, expected %q", string(b), res) 148 } 149 } 150 func TestPackage(t *testing.T) { 151 f := &ast.File{ 152 Decls: []ast.Decl{ 153 &ast.Package{Name: ast.NewIdent("foo")}, 154 &ast.EmbedDecl{ 155 Expr: &ast.BasicLit{ 156 Kind: token.INT, 157 ValuePos: token.NoSpace.Pos(), 158 Value: "1", 159 }, 160 }, 161 }, 162 } 163 b, err := format.Node(f) 164 if err != nil { 165 t.Fatal(err) 166 } 167 const want = "package foo\n\n1\n" 168 if got := string(b); got != want { 169 t.Errorf("got %q, expected %q", got, want) 170 } 171 } 172 173 // idents is an iterator that returns all idents in f via the result channel. 174 func idents(f *ast.File) <-chan *ast.Ident { 175 v := make(chan *ast.Ident) 176 go func() { 177 ast.Walk(f, func(n ast.Node) bool { 178 if ident, ok := n.(*ast.Ident); ok { 179 v <- ident 180 } 181 return true 182 }, nil) 183 close(v) 184 }() 185 return v 186 } 187 188 // identCount returns the number of identifiers found in f. 189 func identCount(f *ast.File) int { 190 n := 0 191 for range idents(f) { 192 n++ 193 } 194 return n 195 } 196 197 // Verify that the SourcePos mode emits correct //line comments 198 // by testing that position information for matching identifiers 199 // is maintained. 200 func TestSourcePos(t *testing.T) { 201 const src = `package p 202 203 import ( 204 "go/printer" 205 "math" 206 "regexp" 207 ) 208 209 let pi = 3.14 210 let xx = 0 211 t: { 212 x: int 213 y: int 214 z: int 215 u: number 216 v: number 217 w: number 218 } 219 e: a*t.x + b*t.y 220 221 // two extra lines here // ... 222 e2: c*t.z 223 ` 224 225 // parse original 226 f1, err := parser.ParseFile("src", src, parser.ParseComments) 227 if err != nil { 228 t.Fatal(err) 229 } 230 231 // pretty-print original 232 b, err := format.Node(f1, format.UseSpaces(8)) 233 if err != nil { 234 t.Fatal(err) 235 } 236 237 // parse pretty printed original 238 // (//line comments must be interpreted even w/o parser.ParseComments set) 239 f2, err := parser.ParseFile("", b, parser.AllErrors, parser.ParseComments) 240 if err != nil { 241 t.Fatalf("%s\n%s", err, b) 242 } 243 244 // At this point the position information of identifiers in f2 should 245 // match the position information of corresponding identifiers in f1. 246 247 // number of identifiers must be > 0 (test should run) and must match 248 n1 := identCount(f1) 249 n2 := identCount(f2) 250 if n1 == 0 { 251 t.Fatal("got no idents") 252 } 253 if n2 != n1 { 254 t.Errorf("got %d idents; want %d", n2, n1) 255 } 256 257 // verify that all identifiers have correct line information 258 i2range := idents(f2) 259 for i1 := range idents(f1) { 260 i2 := <-i2range 261 262 if i2 == nil || i1 == nil { 263 t.Fatal("non nil identifiers") 264 } 265 if i2.Name != i1.Name { 266 t.Errorf("got ident %s; want %s", i2.Name, i1.Name) 267 } 268 269 l1 := i1.Pos().Line() 270 l2 := i2.Pos().Line() 271 if l2 != l1 { 272 t.Errorf("got line %d; want %d for %s", l2, l1, i1.Name) 273 } 274 } 275 276 if t.Failed() { 277 t.Logf("\n%s", b) 278 } 279 } 280 281 var decls = []string{ 282 "package p\n\n" + `import "fmt"`, 283 "package p\n\n" + "let pi = 3.1415\nlet e = 2.71828\n\nlet x = pi", 284 } 285 286 func TestDeclLists(t *testing.T) { 287 for _, src := range decls { 288 file, err := parser.ParseFile("", src, parser.ParseComments) 289 if err != nil { 290 panic(err) // error in test 291 } 292 293 b, err := format.Node(file) // only print declarations 294 if err != nil { 295 panic(err) // error in test 296 } 297 298 out := strings.TrimSpace(string(b)) 299 300 if out != src { 301 t.Errorf("\ngot : %q\nwant: %q\n", out, src) 302 } 303 } 304 } 305 306 func TestIncorrectIdent(t *testing.T) { 307 testCases := []struct { 308 ident string 309 out string 310 }{ 311 {"foo", "foo"}, 312 {"a.b.c", `"a.b.c"`}, 313 {"for", "for"}, 314 } 315 for _, tc := range testCases { 316 t.Run(tc.ident, func(t *testing.T) { 317 b, _ := format.Node(&ast.Field{Label: ast.NewIdent(tc.ident), Value: ast.NewIdent("A")}) 318 if got, want := string(b), tc.out+`: A`; got != want { 319 t.Errorf("got %q; want %q", got, want) 320 } 321 }) 322 } 323 } 324 325 // TextX is a skeleton test that can be filled in for debugging one-off cases. 326 // Do not remove. 327 func TestX(t *testing.T) { 328 t.Skip() 329 const src = ` 330 331 ` 332 b, err := format.Source([]byte(src), format.Simplify()) 333 if err != nil { 334 t.Error(err) 335 } 336 _ = b 337 t.Error("\n", string(b)) 338 }