...

Source file src/cuelang.org/go/cue/ast/astutil/sanitize_test.go

Documentation: cuelang.org/go/cue/ast/astutil

     1  // Copyright 2020 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 astutil_test
    16  
    17  import (
    18  	"testing"
    19  
    20  	"cuelang.org/go/cue/ast"
    21  	"cuelang.org/go/cue/ast/astutil"
    22  	"cuelang.org/go/cue/format"
    23  	"cuelang.org/go/internal"
    24  
    25  	"github.com/go-quicktest/qt"
    26  )
    27  
    28  func TestSanitize(t *testing.T) {
    29  	testCases := []struct {
    30  		desc string
    31  		file *ast.File
    32  		want string
    33  	}{{
    34  		desc: "Take existing import and rename it",
    35  		file: func() *ast.File {
    36  			spec := ast.NewImport(nil, "list")
    37  			spec.AddComment(internal.NewComment(true, "will be renamed"))
    38  			return &ast.File{Decls: []ast.Decl{
    39  				&ast.ImportDecl{Specs: []*ast.ImportSpec{spec}},
    40  				&ast.EmbedDecl{
    41  					Expr: ast.NewStruct(
    42  						ast.NewIdent("list"), ast.NewCall(
    43  							ast.NewSel(&ast.Ident{Name: "list", Node: spec},
    44  								"Min")),
    45  					)},
    46  			}}
    47  		}(),
    48  		want: `import (
    49  	// will be renamed
    50  	list_1 "list"
    51  )
    52  
    53  {
    54  	list: list_1.Min()
    55  }
    56  `,
    57  	}, {
    58  		desc: "Take existing import and rename it",
    59  		file: func() *ast.File {
    60  			spec := ast.NewImport(nil, "list")
    61  			return &ast.File{Decls: []ast.Decl{
    62  				&ast.ImportDecl{Specs: []*ast.ImportSpec{spec}},
    63  				&ast.Field{
    64  					Label: ast.NewIdent("a"),
    65  					Value: ast.NewStruct(
    66  						ast.NewIdent("list"), ast.NewCall(
    67  							ast.NewSel(&ast.Ident{Name: "list", Node: spec}, "Min")),
    68  					),
    69  				},
    70  			}}
    71  		}(),
    72  		want: `import list_1 "list"
    73  
    74  a: {
    75  	list: list_1.Min()
    76  }
    77  `,
    78  	}, {
    79  		desc: "One import added, one removed",
    80  		file: &ast.File{Decls: []ast.Decl{
    81  			&ast.ImportDecl{Specs: []*ast.ImportSpec{
    82  				{Path: ast.NewString("foo")},
    83  			}},
    84  			&ast.Field{
    85  				Label: ast.NewIdent("a"),
    86  				Value: ast.NewCall(
    87  					ast.NewSel(&ast.Ident{
    88  						Name: "bar",
    89  						Node: &ast.ImportSpec{Path: ast.NewString("bar")},
    90  					}, "Min")),
    91  			},
    92  		}},
    93  		want: `import "bar"
    94  
    95  a: bar.Min()
    96  `,
    97  	}, {
    98  		desc: "Rename duplicate import",
    99  		file: func() *ast.File {
   100  			spec1 := ast.NewImport(nil, "bar")
   101  			spec2 := ast.NewImport(nil, "foo/bar")
   102  			spec3 := ast.NewImport(ast.NewIdent("bar"), "foo")
   103  			return &ast.File{Decls: []ast.Decl{
   104  				internal.NewComment(false, "File comment"),
   105  				&ast.Package{Name: ast.NewIdent("pkg")},
   106  				&ast.Field{
   107  					Label: ast.NewIdent("a"),
   108  					Value: ast.NewStruct(
   109  						ast.NewIdent("b"), ast.NewCall(
   110  							ast.NewSel(&ast.Ident{Name: "bar", Node: spec1}, "A")),
   111  						ast.NewIdent("c"), ast.NewCall(
   112  							ast.NewSel(&ast.Ident{Name: "bar", Node: spec2}, "A")),
   113  						ast.NewIdent("d"), ast.NewCall(
   114  							ast.NewSel(&ast.Ident{Name: "bar", Node: spec3}, "A")),
   115  					),
   116  				},
   117  			}}
   118  		}(),
   119  		want: `// File comment
   120  
   121  package pkg
   122  
   123  import (
   124  	"bar"
   125  	bar_1 "foo/bar"
   126  	bar_5 "foo"
   127  )
   128  
   129  a: {
   130  	b: bar.A()
   131  	c: bar_1.A()
   132  	d: bar_5.A()
   133  }
   134  `,
   135  	}, {
   136  		desc: "Rename duplicate import, reuse and drop",
   137  		file: func() *ast.File {
   138  			spec1 := ast.NewImport(nil, "bar")
   139  			spec2 := ast.NewImport(nil, "foo/bar")
   140  			spec3 := ast.NewImport(ast.NewIdent("bar"), "foo")
   141  			return &ast.File{Decls: []ast.Decl{
   142  				&ast.ImportDecl{Specs: []*ast.ImportSpec{
   143  					spec3,
   144  					ast.NewImport(nil, "foo"),
   145  				}},
   146  				&ast.Field{
   147  					Label: ast.NewIdent("a"),
   148  					Value: ast.NewStruct(
   149  						ast.NewIdent("b"), ast.NewCall(
   150  							ast.NewSel(&ast.Ident{Name: "bar", Node: spec1}, "A")),
   151  						ast.NewIdent("c"), ast.NewCall(
   152  							ast.NewSel(&ast.Ident{Name: "bar", Node: spec2}, "A")),
   153  						ast.NewIdent("d"), ast.NewCall(
   154  							ast.NewSel(&ast.Ident{Name: "bar", Node: spec3}, "A")),
   155  					),
   156  				},
   157  			}}
   158  		}(),
   159  		want: `import (
   160  	bar "foo"
   161  	bar_1 "bar"
   162  	bar_5 "foo/bar"
   163  )
   164  
   165  a: {
   166  	b: bar_1.A()
   167  	c: bar_5.A()
   168  	d: bar.A()
   169  }
   170  `,
   171  	}, {
   172  		desc: "Reuse different import",
   173  		file: &ast.File{Decls: []ast.Decl{
   174  			&ast.Package{Name: ast.NewIdent("pkg")},
   175  			&ast.ImportDecl{Specs: []*ast.ImportSpec{
   176  				{Path: ast.NewString("bar")},
   177  			}},
   178  			&ast.Field{
   179  				Label: ast.NewIdent("a"),
   180  				Value: ast.NewStruct(
   181  					ast.NewIdent("list"), ast.NewCall(
   182  						ast.NewSel(&ast.Ident{
   183  							Name: "bar",
   184  							Node: &ast.ImportSpec{Path: ast.NewString("bar")},
   185  						}, "Min")),
   186  				),
   187  			},
   188  		}},
   189  		want: `package pkg
   190  
   191  import "bar"
   192  
   193  a: {
   194  	list: bar.Min()
   195  }
   196  `,
   197  	}, {
   198  		desc: "Clear reference that does not exist in scope",
   199  		file: &ast.File{Decls: []ast.Decl{
   200  			&ast.Field{
   201  				Label: ast.NewIdent("a"),
   202  				Value: ast.NewStruct(
   203  					ast.NewIdent("b"), &ast.Ident{
   204  						Name: "c",
   205  						Node: ast.NewString("foo"),
   206  					},
   207  					ast.NewIdent("d"), ast.NewIdent("e"),
   208  				),
   209  			},
   210  		}},
   211  		want: `a: {
   212  	b: c
   213  	d: e
   214  }
   215  `,
   216  	}, {
   217  		desc: "Unshadow possible reference to other file",
   218  		file: &ast.File{Decls: []ast.Decl{
   219  			&ast.Field{
   220  				Label: ast.NewIdent("a"),
   221  				Value: ast.NewStruct(
   222  					ast.NewIdent("b"), &ast.Ident{
   223  						Name: "c",
   224  						Node: ast.NewString("foo"),
   225  					},
   226  					ast.NewIdent("c"), ast.NewIdent("d"),
   227  				),
   228  			},
   229  		}},
   230  		want: `a: {
   231  	b: c_1
   232  	c: d
   233  }
   234  
   235  let c_1 = c
   236  `,
   237  	}, {
   238  		desc: "Add alias to shadowed field",
   239  		file: func() *ast.File {
   240  			field := &ast.Field{
   241  				Label: ast.NewIdent("a"),
   242  				Value: ast.NewString("b"),
   243  			}
   244  			return &ast.File{Decls: []ast.Decl{
   245  				field,
   246  				&ast.Field{
   247  					Label: ast.NewIdent("c"),
   248  					Value: ast.NewStruct(
   249  						ast.NewIdent("a"), ast.NewStruct(),
   250  						ast.NewIdent("b"), &ast.Ident{
   251  							Name: "a",
   252  							Node: field.Value,
   253  						},
   254  						ast.NewIdent("c"), ast.NewIdent("d"),
   255  					),
   256  				},
   257  			}}
   258  		}(),
   259  		want: `a_1=a: "b"
   260  c: {
   261  	a: {}
   262  	b: a_1
   263  	c: d
   264  }
   265  `,
   266  	}, {
   267  		desc: "Add let clause to shadowed field",
   268  		// Resolve both identifiers to same clause.
   269  		file: func() *ast.File {
   270  			field := &ast.Field{
   271  				Label: ast.NewIdent("a"),
   272  				Value: ast.NewString("b"),
   273  			}
   274  			return &ast.File{Decls: []ast.Decl{
   275  				field,
   276  				&ast.Field{
   277  					Label: ast.NewIdent("c"),
   278  					Value: ast.NewStruct(
   279  						ast.NewIdent("a"), ast.NewStruct(),
   280  						// Remove this reference.
   281  						ast.NewIdent("b"), &ast.Ident{
   282  							Name: "a",
   283  							Node: field.Value,
   284  						},
   285  						ast.NewIdent("c"), ast.NewIdent("d"),
   286  						ast.NewIdent("e"), &ast.Ident{
   287  							Name: "a",
   288  							Node: field.Value,
   289  						},
   290  					),
   291  				},
   292  			}}
   293  		}(),
   294  		want: `a_1=a: "b"
   295  c: {
   296  	a: {}
   297  	b: a_1
   298  	c: d
   299  	e: a_1
   300  }
   301  `,
   302  	}, {
   303  		desc: "Add let clause to shadowed field",
   304  		// Resolve both identifiers to same clause.
   305  		file: func() *ast.File {
   306  			fieldX := &ast.Field{
   307  				Label: &ast.Alias{
   308  					Ident: ast.NewIdent("X"),
   309  					Expr:  ast.NewIdent("a"), // shadowed
   310  				},
   311  				Value: ast.NewString("b"),
   312  			}
   313  			fieldY := &ast.Field{
   314  				Label: &ast.Alias{
   315  					Ident: ast.NewIdent("Y"), // shadowed
   316  					Expr:  ast.NewIdent("q"), // not shadowed
   317  				},
   318  				Value: ast.NewString("b"),
   319  			}
   320  			return &ast.File{Decls: []ast.Decl{
   321  				fieldX,
   322  				fieldY,
   323  				&ast.Field{
   324  					Label: ast.NewIdent("c"),
   325  					Value: ast.NewStruct(
   326  						ast.NewIdent("a"), ast.NewStruct(),
   327  						ast.NewIdent("b"), &ast.Ident{
   328  							Name: "X",
   329  							Node: fieldX,
   330  						},
   331  						ast.NewIdent("c"), ast.NewIdent("d"),
   332  						ast.NewIdent("e"), &ast.Ident{
   333  							Name: "a",
   334  							Node: fieldX.Value,
   335  						},
   336  						ast.NewIdent("f"), &ast.Ident{
   337  							Name: "Y",
   338  							Node: fieldY,
   339  						},
   340  					),
   341  				},
   342  			}}
   343  		}(),
   344  		want: `
   345  let X_1 = X
   346  X=a: "b"
   347  Y=q: "b"
   348  c: {
   349  	a: {}
   350  	b: X
   351  	c: d
   352  	e: X_1
   353  	f: Y
   354  }
   355  `,
   356  	}, {
   357  		desc: "Add let clause to nested shadowed field",
   358  		// Resolve both identifiers to same clause.
   359  		file: func() *ast.File {
   360  			field := &ast.Field{
   361  				Label: ast.NewIdent("a"),
   362  				Value: ast.NewString("b"),
   363  			}
   364  			return &ast.File{Decls: []ast.Decl{
   365  				&ast.Field{
   366  					Label: ast.NewIdent("b"),
   367  					Value: ast.NewStruct(
   368  						field,
   369  						ast.NewIdent("b"), ast.NewStruct(
   370  							ast.NewIdent("a"), ast.NewString("bar"),
   371  							ast.NewIdent("b"), &ast.Ident{
   372  								Name: "a",
   373  								Node: field.Value,
   374  							},
   375  							ast.NewIdent("e"), &ast.Ident{
   376  								Name: "a",
   377  								Node: field.Value,
   378  							},
   379  						),
   380  					),
   381  				},
   382  			}}
   383  		}(),
   384  		want: `b: {
   385  	a_1=a: "b"
   386  	b: {
   387  		a: "bar"
   388  		b: a_1
   389  		e: a_1
   390  	}
   391  }
   392  `,
   393  	}, {
   394  		desc: "Add let clause to nested shadowed field with alias",
   395  		// Resolve both identifiers to same clause.
   396  		file: func() *ast.File {
   397  			field := &ast.Field{
   398  				Label: &ast.Alias{
   399  					Ident: ast.NewIdent("X"),
   400  					Expr:  ast.NewIdent("a"),
   401  				},
   402  				Value: ast.NewString("b"),
   403  			}
   404  			return &ast.File{Decls: []ast.Decl{
   405  				&ast.Field{
   406  					Label: ast.NewIdent("b"),
   407  					Value: ast.NewStruct(
   408  						field,
   409  						ast.NewIdent("b"), ast.NewStruct(
   410  							ast.NewIdent("a"), ast.NewString("bar"),
   411  							ast.NewIdent("b"), &ast.Ident{
   412  								Name: "a",
   413  								Node: field.Value,
   414  							},
   415  							ast.NewIdent("e"), &ast.Ident{
   416  								Name: "a",
   417  								Node: field.Value,
   418  							},
   419  						),
   420  					),
   421  				},
   422  			}}
   423  		}(),
   424  		want: `b: {
   425  	let X_1 = X
   426  	X=a: "b"
   427  	b: {
   428  		a: "bar"
   429  		b: X_1
   430  		e: X_1
   431  	}
   432  }
   433  `,
   434  	}}
   435  	for _, tc := range testCases {
   436  		t.Run(tc.desc, func(t *testing.T) {
   437  			err := astutil.Sanitize(tc.file)
   438  			if err != nil {
   439  				t.Fatal(err)
   440  			}
   441  
   442  			b, errs := format.Node(tc.file)
   443  			if errs != nil {
   444  				t.Fatal(errs)
   445  			}
   446  
   447  			got := string(b)
   448  			qt.Assert(t, qt.Equals(got, tc.want))
   449  		})
   450  	}
   451  }
   452  
   453  // For testing purposes: do not remove.
   454  func TestX(t *testing.T) {
   455  	t.Skip()
   456  
   457  	field := &ast.Field{
   458  		Label: &ast.Alias{
   459  			Ident: ast.NewIdent("X"),
   460  			Expr:  ast.NewIdent("a"),
   461  		},
   462  		Value: ast.NewString("b"),
   463  	}
   464  
   465  	file := &ast.File{Decls: []ast.Decl{
   466  		&ast.Field{
   467  			Label: ast.NewIdent("b"),
   468  			Value: ast.NewStruct(
   469  				field,
   470  				ast.NewIdent("b"), ast.NewStruct(
   471  					ast.NewIdent("a"), ast.NewString("bar"),
   472  					ast.NewIdent("b"), &ast.Ident{
   473  						Name: "a",
   474  						Node: field.Value,
   475  					},
   476  					ast.NewIdent("e"), &ast.Ident{
   477  						Name: "a",
   478  						Node: field.Value,
   479  					},
   480  				),
   481  			),
   482  		},
   483  	}}
   484  
   485  	err := astutil.Sanitize(file)
   486  	if err != nil {
   487  		t.Fatal(err)
   488  	}
   489  
   490  	b, errs := format.Node(file)
   491  	if errs != nil {
   492  		t.Fatal(errs)
   493  	}
   494  
   495  	t.Error(string(b))
   496  }
   497  

View as plain text