github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/astutil/astutil_test.go (about)

     1  package astutil
     2  
     3  import (
     4  	"go/ast"
     5  	"strconv"
     6  	"testing"
     7  
     8  	"github.com/gopherjs/gopherjs/internal/srctesting"
     9  )
    10  
    11  func TestImportsUnsafe(t *testing.T) {
    12  	tests := []struct {
    13  		desc    string
    14  		imports string
    15  		want    bool
    16  	}{
    17  		{
    18  			desc:    "no imports",
    19  			imports: "",
    20  			want:    false,
    21  		}, {
    22  			desc:    "other imports",
    23  			imports: `import "some/other/package"`,
    24  			want:    false,
    25  		}, {
    26  			desc:    "only unsafe",
    27  			imports: `import "unsafe"`,
    28  			want:    true,
    29  		}, {
    30  			desc: "multi-import decl",
    31  			imports: `import (
    32  				"some/other/package"
    33  				"unsafe"
    34  			)`,
    35  			want: true,
    36  		}, {
    37  			desc: "two import decls",
    38  			imports: `import "some/other/package"
    39  			import "unsafe"`,
    40  			want: true,
    41  		},
    42  	}
    43  	for _, test := range tests {
    44  		t.Run(test.desc, func(t *testing.T) {
    45  			src := "package testpackage\n\n" + test.imports
    46  			file := srctesting.New(t).Parse("test.go", src)
    47  			got := ImportsUnsafe(file)
    48  			if got != test.want {
    49  				t.Fatalf("ImportsUnsafe() returned %t, want %t", got, test.want)
    50  			}
    51  		})
    52  	}
    53  }
    54  
    55  func TestImportName(t *testing.T) {
    56  	tests := []struct {
    57  		desc string
    58  		src  string
    59  		want string
    60  	}{
    61  		{
    62  			desc: `named import`,
    63  			src:  `import foo "some/other/bar"`,
    64  			want: `foo`,
    65  		}, {
    66  			desc: `unnamed import`,
    67  			src:  `import "some/other/bar"`,
    68  			want: `bar`,
    69  		}, {
    70  			desc: `dot import`,
    71  			src:  `import . "some/other/bar"`,
    72  			want: ``,
    73  		}, {
    74  			desc: `blank import`,
    75  			src:  `import _ "some/other/bar"`,
    76  			want: ``,
    77  		},
    78  	}
    79  	for _, test := range tests {
    80  		t.Run(test.desc, func(t *testing.T) {
    81  			src := "package testpackage\n\n" + test.src
    82  			file := srctesting.New(t).Parse("test.go", src)
    83  			if len(file.Imports) != 1 {
    84  				t.Fatal(`expected one and only one import`)
    85  			}
    86  			importSpec := file.Imports[0]
    87  			got := ImportName(importSpec)
    88  			if got != test.want {
    89  				t.Fatalf(`ImportName() returned %q, want %q`, got, test.want)
    90  			}
    91  		})
    92  	}
    93  }
    94  
    95  func TestFuncKey(t *testing.T) {
    96  	tests := []struct {
    97  		desc string
    98  		src  string
    99  		want string
   100  	}{
   101  		{
   102  			desc: `top-level function`,
   103  			src:  `func foo() {}`,
   104  			want: `foo`,
   105  		}, {
   106  			desc: `top-level exported function`,
   107  			src:  `func Foo() {}`,
   108  			want: `Foo`,
   109  		}, {
   110  			desc: `method on reference`,
   111  			src:  `func (_ myType) bar() {}`,
   112  			want: `myType.bar`,
   113  		}, {
   114  			desc: `method on pointer`,
   115  			src:  ` func (_ *myType) bar() {}`,
   116  			want: `myType.bar`,
   117  		}, {
   118  			desc: `method on generic reference`,
   119  			src:  ` func (_ myType[T]) bar() {}`,
   120  			want: `myType.bar`,
   121  		}, {
   122  			desc: `method on generic pointer`,
   123  			src:  ` func (_ *myType[T]) bar() {}`,
   124  			want: `myType.bar`,
   125  		}, {
   126  			desc: `method on struct with multiple generics`,
   127  			src:  ` func (_ *myType[T1, T2, T3, T4]) bar() {}`,
   128  			want: `myType.bar`,
   129  		},
   130  	}
   131  	for _, test := range tests {
   132  		t.Run(test.desc, func(t *testing.T) {
   133  			src := `package testpackage; ` + test.src
   134  			fdecl := srctesting.ParseFuncDecl(t, src)
   135  			if got := FuncKey(fdecl); got != test.want {
   136  				t.Errorf(`Got %q, want %q`, got, test.want)
   137  			}
   138  		})
   139  	}
   140  }
   141  
   142  func TestHasDirectiveOnDecl(t *testing.T) {
   143  	tests := []struct {
   144  		desc string
   145  		src  string
   146  		want bool
   147  	}{
   148  		{
   149  			desc: `no comment on function`,
   150  			src: `package testpackage;
   151  				func foo() {}`,
   152  			want: false,
   153  		}, {
   154  			desc: `no directive on function with comment`,
   155  			src: `package testpackage;
   156  				// foo has no directive
   157  				func foo() {}`,
   158  			want: false,
   159  		}, {
   160  			desc: `wrong directive on function`,
   161  			src: `package testpackage;
   162  				//gopherjs:wrong-directive
   163  				func foo() {}`,
   164  			want: false,
   165  		}, {
   166  			desc: `correct directive on function`,
   167  			src: `package testpackage;
   168  				//gopherjs:do-stuff
   169  				// foo has a directive to do stuff
   170  				func foo() {}`,
   171  			want: true,
   172  		}, {
   173  			desc: `correct directive in multiline comment on function`,
   174  			src: `package testpackage;
   175  				/*gopherjs:do-stuff
   176  				  foo has a directive to do stuff
   177  				*/
   178  				func foo() {}`,
   179  			want: true,
   180  		}, {
   181  			desc: `invalid directive in multiline comment on function`,
   182  			src: `package testpackage;
   183  				/*
   184  				gopherjs:do-stuff
   185  				*/
   186  				func foo() {}`,
   187  			want: false,
   188  		}, {
   189  			desc: `prefix directive on function`,
   190  			src: `package testpackage;
   191  				//gopherjs:do-stuffs
   192  				func foo() {}`,
   193  			want: false,
   194  		}, {
   195  			desc: `multiple directives on function`,
   196  			src: `package testpackage;
   197  				//gopherjs:wrong-directive
   198  				//gopherjs:do-stuff
   199  				//gopherjs:another-directive
   200  				func foo() {}`,
   201  			want: true,
   202  		}, {
   203  			desc: `directive with explanation on function`,
   204  			src: `package testpackage;
   205  				//gopherjs:do-stuff 'cause we can
   206  				func foo() {}`,
   207  			want: true,
   208  		}, {
   209  			desc: `no directive on type declaration`,
   210  			src: `package testpackage;
   211  				// Foo has a comment
   212  				type Foo int`,
   213  			want: false,
   214  		}, {
   215  			desc: `directive on type declaration`,
   216  			src: `package testpackage;
   217  				//gopherjs:do-stuff
   218  				type Foo int`,
   219  			want: true,
   220  		}, {
   221  			desc: `directive on specification, not on declaration`,
   222  			src: `package testpackage;
   223  				type (
   224  					Foo int
   225  
   226  					//gopherjs:do-stuff
   227  					Bar struct{}
   228  				)`,
   229  			want: false,
   230  		}, {
   231  			desc: `no directive on const declaration`,
   232  			src: `package testpackage;
   233  				const foo = 42`,
   234  			want: false,
   235  		}, {
   236  			desc: `directive on const documentation`,
   237  			src: `package testpackage;
   238  				//gopherjs:do-stuff
   239  				const foo = 42`,
   240  			want: true,
   241  		}, {
   242  			desc: `no directive on var declaration`,
   243  			src: `package testpackage;
   244  				var foo = 42`,
   245  			want: false,
   246  		}, {
   247  			desc: `directive on var documentation`,
   248  			src: `package testpackage;
   249  				//gopherjs:do-stuff
   250  				var foo = 42`,
   251  			want: true,
   252  		}, {
   253  			desc: `no directive on var declaration`,
   254  			src: `package testpackage;
   255  				import _ "embed"`,
   256  			want: false,
   257  		}, {
   258  			desc: `directive on var documentation`,
   259  			src: `package testpackage;
   260  				//gopherjs:do-stuff
   261  				import _ "embed"`,
   262  			want: true,
   263  		},
   264  	}
   265  
   266  	for _, test := range tests {
   267  		t.Run(test.desc, func(t *testing.T) {
   268  			const action = `do-stuff`
   269  			decl := srctesting.ParseDecl(t, test.src)
   270  			if got := hasDirective(decl, action); got != test.want {
   271  				t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, decl, action, got, test.want)
   272  			}
   273  		})
   274  	}
   275  }
   276  
   277  func TestHasDirectiveOnSpec(t *testing.T) {
   278  	tests := []struct {
   279  		desc string
   280  		src  string
   281  		want bool
   282  	}{
   283  		{
   284  			desc: `no directive on type specification`,
   285  			src: `package testpackage;
   286  				type Foo int`,
   287  			want: false,
   288  		}, {
   289  			desc: `directive on declaration, not on specification`,
   290  			src: `package testpackage;
   291  				//gopherjs:do-stuff
   292  				type Foo int`,
   293  			want: false,
   294  		}, {
   295  			desc: `directive in doc on type specification`,
   296  			src: `package testpackage;
   297  				type (
   298  					//gopherjs:do-stuff
   299  					Foo int
   300  				)`,
   301  			want: true,
   302  		}, {
   303  			desc: `directive in line on type specification`,
   304  			src: `package testpackage;
   305  				type Foo int //gopherjs:do-stuff`,
   306  			want: true,
   307  		}, {
   308  			desc: `no directive on const specification`,
   309  			src: `package testpackage;
   310  				const foo = 42`,
   311  			want: false,
   312  		}, {
   313  			desc: `directive in doc on const specification`,
   314  			src: `package testpackage;
   315  				const (
   316  					//gopherjs:do-stuff
   317  					foo = 42
   318  				)`,
   319  			want: true,
   320  		}, {
   321  			desc: `directive in line on const specification`,
   322  			src: `package testpackage;
   323  				const foo = 42 //gopherjs:do-stuff`,
   324  			want: true,
   325  		}, {
   326  			desc: `no directive on var specification`,
   327  			src: `package testpackage;
   328  				var foo = 42`,
   329  			want: false,
   330  		}, {
   331  			desc: `directive in doc on var specification`,
   332  			src: `package testpackage;
   333  				var (
   334  					//gopherjs:do-stuff
   335  					foo = 42
   336  				)`,
   337  			want: true,
   338  		}, {
   339  			desc: `directive in line on var specification`,
   340  			src: `package testpackage;
   341  				var foo = 42 //gopherjs:do-stuff`,
   342  			want: true,
   343  		}, {
   344  			desc: `no directive on import specification`,
   345  			src: `package testpackage;
   346  				import _ "embed"`,
   347  			want: false,
   348  		}, {
   349  			desc: `directive in doc on import specification`,
   350  			src: `package testpackage;
   351  				import (
   352  					//gopherjs:do-stuff
   353  					_ "embed"
   354  				)`,
   355  			want: true,
   356  		}, {
   357  			desc: `directive in line on import specification`,
   358  			src: `package testpackage;
   359  				import _ "embed" //gopherjs:do-stuff`,
   360  			want: true,
   361  		},
   362  	}
   363  
   364  	for _, test := range tests {
   365  		t.Run(test.desc, func(t *testing.T) {
   366  			const action = `do-stuff`
   367  			spec := srctesting.ParseSpec(t, test.src)
   368  			if got := hasDirective(spec, action); got != test.want {
   369  				t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, spec, action, got, test.want)
   370  			}
   371  		})
   372  	}
   373  }
   374  
   375  func TestHasDirectiveOnFile(t *testing.T) {
   376  	tests := []struct {
   377  		desc string
   378  		src  string
   379  		want bool
   380  	}{
   381  		{
   382  			desc: `no directive on file`,
   383  			src: `package testpackage;
   384  				//gopherjs:do-stuff
   385  				type Foo int`,
   386  			want: false,
   387  		}, {
   388  			desc: `directive on file`,
   389  			src: `//gopherjs:do-stuff
   390  				package testpackage;
   391  				type Foo int`,
   392  			want: true,
   393  		},
   394  	}
   395  
   396  	for _, test := range tests {
   397  		t.Run(test.desc, func(t *testing.T) {
   398  			const action = `do-stuff`
   399  			file := srctesting.New(t).Parse("test.go", test.src)
   400  			if got := hasDirective(file, action); got != test.want {
   401  				t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, file, action, got, test.want)
   402  			}
   403  		})
   404  	}
   405  }
   406  
   407  func TestHasDirectiveOnField(t *testing.T) {
   408  	tests := []struct {
   409  		desc string
   410  		src  string
   411  		want bool
   412  	}{
   413  		{
   414  			desc: `no directive on struct field`,
   415  			src: `package testpackage;
   416  				type Foo struct {
   417  					bar int
   418  				}`,
   419  			want: false,
   420  		}, {
   421  			desc: `directive in doc on struct field`,
   422  			src: `package testpackage;
   423  				type Foo struct {
   424  					//gopherjs:do-stuff
   425  					bar int
   426  				}`,
   427  			want: true,
   428  		}, {
   429  			desc: `directive in line on struct field`,
   430  			src: `package testpackage;
   431  				type Foo struct {
   432  					bar int //gopherjs:do-stuff
   433  				}`,
   434  			want: true,
   435  		}, {
   436  			desc: `no directive on interface method`,
   437  			src: `package testpackage;
   438  				type Foo interface {
   439  					Bar(a int) int
   440  				}`,
   441  			want: false,
   442  		}, {
   443  			desc: `directive in doc on interface method`,
   444  			src: `package testpackage;
   445  				type Foo interface {
   446  					//gopherjs:do-stuff
   447  					Bar(a int) int
   448  				}`,
   449  			want: true,
   450  		}, {
   451  			desc: `directive in line on interface method`,
   452  			src: `package testpackage;
   453  				type Foo interface {
   454  					Bar(a int) int //gopherjs:do-stuff
   455  				}`,
   456  			want: true,
   457  		},
   458  	}
   459  
   460  	for _, test := range tests {
   461  		t.Run(test.desc, func(t *testing.T) {
   462  			const action = `do-stuff`
   463  			spec := srctesting.ParseSpec(t, test.src)
   464  			tspec := spec.(*ast.TypeSpec)
   465  			var field *ast.Field
   466  			switch typeNode := tspec.Type.(type) {
   467  			case *ast.StructType:
   468  				field = typeNode.Fields.List[0]
   469  			case *ast.InterfaceType:
   470  				field = typeNode.Methods.List[0]
   471  			default:
   472  				t.Errorf(`unexpected node type, %T, when finding field`, typeNode)
   473  				return
   474  			}
   475  			if got := hasDirective(field, action); got != test.want {
   476  				t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, field, action, got, test.want)
   477  			}
   478  		})
   479  	}
   480  }
   481  
   482  func TestEndsWithReturn(t *testing.T) {
   483  	tests := []struct {
   484  		desc string
   485  		src  string
   486  		want bool
   487  	}{
   488  		{
   489  			desc: "empty function",
   490  			src:  `func foo() {}`,
   491  			want: false,
   492  		}, {
   493  			desc: "implicit return",
   494  			src:  `func foo() { a() }`,
   495  			want: false,
   496  		}, {
   497  			desc: "explicit return",
   498  			src:  `func foo() { a(); return }`,
   499  			want: true,
   500  		}, {
   501  			desc: "labelled return",
   502  			src:  `func foo() { Label: return }`,
   503  			want: true,
   504  		}, {
   505  			desc: "labelled call",
   506  			src:  `func foo() { Label: a() }`,
   507  			want: false,
   508  		}, {
   509  			desc: "return in a block",
   510  			src:  `func foo() { a(); { b(); return; } }`,
   511  			want: true,
   512  		}, {
   513  			desc: "a block without return",
   514  			src:  `func foo() { a(); { b(); c(); } }`,
   515  			want: false,
   516  		}, {
   517  			desc: "conditional block",
   518  			src:  `func foo() { a(); if x { b(); return; } }`,
   519  			want: false,
   520  		},
   521  	}
   522  
   523  	for _, test := range tests {
   524  		t.Run(test.desc, func(t *testing.T) {
   525  			fdecl := srctesting.ParseFuncDecl(t, "package testpackage\n"+test.src)
   526  			got := EndsWithReturn(fdecl.Body.List)
   527  			if got != test.want {
   528  				t.Errorf("EndsWithReturn() returned %t, want %t", got, test.want)
   529  			}
   530  		})
   531  	}
   532  }
   533  
   534  func TestSqueezeIdents(t *testing.T) {
   535  	tests := []struct {
   536  		desc   string
   537  		count  int
   538  		assign []int
   539  	}{
   540  		{
   541  			desc:   `no squeezing`,
   542  			count:  5,
   543  			assign: []int{0, 1, 2, 3, 4},
   544  		}, {
   545  			desc:   `missing front`,
   546  			count:  5,
   547  			assign: []int{3, 4},
   548  		}, {
   549  			desc:   `missing back`,
   550  			count:  5,
   551  			assign: []int{0, 1, 2},
   552  		}, {
   553  			desc:   `missing several`,
   554  			count:  10,
   555  			assign: []int{1, 2, 3, 6, 8},
   556  		}, {
   557  			desc:   `empty`,
   558  			count:  0,
   559  			assign: []int{},
   560  		},
   561  	}
   562  
   563  	for _, test := range tests {
   564  		t.Run(test.desc, func(t *testing.T) {
   565  			input := make([]*ast.Ident, test.count)
   566  			for _, i := range test.assign {
   567  				input[i] = ast.NewIdent(strconv.Itoa(i))
   568  			}
   569  
   570  			result := Squeeze(input)
   571  			if len(result) != len(test.assign) {
   572  				t.Errorf("Squeeze() returned a slice %d long, want %d", len(result), len(test.assign))
   573  			}
   574  			for i, id := range input {
   575  				if i < len(result) {
   576  					if id == nil {
   577  						t.Errorf(`Squeeze() returned a nil in result at %d`, i)
   578  					} else {
   579  						value, err := strconv.Atoi(id.Name)
   580  						if err != nil || value != test.assign[i] {
   581  							t.Errorf(`Squeeze() returned %s at %d instead of %d`, id.Name, i, test.assign[i])
   582  						}
   583  					}
   584  				} else if id != nil {
   585  					t.Errorf(`Squeeze() didn't clear out tail of slice, want %d nil`, i)
   586  				}
   587  			}
   588  		})
   589  	}
   590  }