| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package golang |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/importer" |
| "go/parser" |
| "go/token" |
| "go/types" |
| "reflect" |
| "runtime" |
| "strings" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| ) |
| |
| // TestFreeRefs is a unit test of the free-references algorithm. |
| func TestFreeRefs(t *testing.T) { |
| if runtime.GOOS == "js" || runtime.GOARCH == "wasm" { |
| t.Skip("some test imports are unsupported on js or wasm") |
| } |
| |
| for i, test := range []struct { |
| src string |
| want []string // expected list of "scope kind dotted-path" triples |
| }{ |
| { |
| // basic example (has a "cannot infer" type error) |
| `package p; func f[T ~int](x any) { var y T; « f(x.(T) + y) » }`, |
| []string{"pkg func f", "local var x", "local typename T", "local var y"}, |
| }, |
| { |
| // selection need not be tree-aligned |
| `package p; type T int; type U « T; func _(x U) »`, |
| []string{"pkg typename T", "pkg typename U"}, |
| }, |
| { |
| // imported symbols |
| `package p; import "fmt"; func f() { « var x fmt.Stringer » }`, |
| []string{"file pkgname fmt.Stringer"}, |
| }, |
| { |
| // unsafe and error, our old nemeses |
| `package p; import "unsafe"; var ( « _ unsafe.Pointer; _ = error(nil).Error »; )`, |
| []string{"file pkgname unsafe.Pointer"}, |
| }, |
| { |
| // two attributes of a var, but not the var itself |
| `package p; import "bytes"; func _(buf bytes.Buffer) { « buf.WriteByte(0); buf.WriteString(""); » }`, |
| []string{"local var buf.WriteByte", "local var buf.WriteString"}, |
| }, |
| { |
| // dot imports (an edge case) |
| `package p; import . "errors"; var _ = « New»`, |
| []string{"file pkgname errors.New"}, |
| }, |
| { |
| // struct field (regression test for overzealous dot import logic) |
| `package p; import "net/url"; var _ = «url.URL{Host: ""}»`, |
| []string{"file pkgname url.URL"}, |
| }, |
| { |
| // dot imports (another regression test of same) |
| `package p; import . "net/url"; var _ = «URL{Host: ""}»`, |
| []string{"file pkgname url.URL"}, |
| }, |
| { |
| // dot import of unsafe (a corner case) |
| `package p; import . "unsafe"; var _ « Pointer»`, |
| []string{"file pkgname unsafe.Pointer"}, |
| }, |
| { |
| // dotted path |
| `package p; import "go/build"; var _ = « build.Default.GOOS »`, |
| []string{"file pkgname build.Default.GOOS"}, |
| }, |
| { |
| // type error |
| `package p; import "nope"; var _ = « nope.nope.nope »`, |
| []string{"file pkgname nope"}, |
| }, |
| } { |
| name := fmt.Sprintf("file%d.go", i) |
| t.Run(name, func(t *testing.T) { |
| fset := token.NewFileSet() |
| startOffset := strings.Index(test.src, "«") |
| endOffset := strings.Index(test.src, "»") |
| if startOffset < 0 || endOffset < startOffset { |
| t.Fatalf("invalid «...» selection (%d:%d)", startOffset, endOffset) |
| } |
| src := test.src[:startOffset] + |
| " " + |
| test.src[startOffset+len("«"):endOffset] + |
| " " + |
| test.src[endOffset+len("»"):] |
| f, err := parser.ParseFile(fset, name, src, parser.SkipObjectResolution) |
| if err != nil { |
| t.Fatal(err) |
| } |
| conf := &types.Config{ |
| Importer: importer.Default(), |
| Error: func(err error) { t.Log(err) }, // not fatal |
| } |
| info := &types.Info{ |
| Uses: make(map[*ast.Ident]types.Object), |
| Scopes: make(map[ast.Node]*types.Scope), |
| Types: make(map[ast.Expr]types.TypeAndValue), |
| } |
| pkg, _ := conf.Check(f.Name.Name, fset, []*ast.File{f}, info) // ignore errors |
| tf := fset.File(f.Package) |
| refs := freeRefs(pkg, info, f, tf.Pos(startOffset), tf.Pos(endOffset)) |
| |
| kind := func(obj types.Object) string { // e.g. "var", "const" |
| return strings.ToLower(reflect.TypeOf(obj).Elem().Name()) |
| } |
| |
| var got []string |
| for _, ref := range refs { |
| msg := ref.scope + " " + kind(ref.objects[0]) + " " + ref.dotted |
| got = append(got, msg) |
| } |
| if diff := cmp.Diff(test.want, got); diff != "" { |
| t.Errorf("(-want +got)\n%s", diff) |
| } |
| }) |
| } |
| } |