github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/cue/ast/astutil/sanitize_test.go (about)

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