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  }