diff --git a/go.mod b/go.mod index be2eb450..c216df55 100644 --- a/go.mod +++ b/go.mod @@ -8,14 +8,12 @@ require ( github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect github.com/efritz/pentimento v0.0.0-20190429011147-ade47d831101 github.com/google/go-cmp v0.5.2 - github.com/json-iterator/go v1.1.10 - github.com/kr/pretty v0.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pkg/errors v0.9.1 github.com/slimsag/godocmd v0.0.0-20161025000126-a1005ad29fe3 github.com/sourcegraph/lsif-protocol v1.0.0 golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56 - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect - gopkg.in/yaml.v2 v2.2.5 // indirect ) + +replace github.com/sourcegraph/lsif-protocol => github.com/alidn/lsif-protocol v1.0.1-0.20210113015314-a4c552ebb760 diff --git a/go.sum b/go.sum index 12137c76..31f718a4 100644 --- a/go.sum +++ b/go.sum @@ -4,14 +4,14 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alidn/lsif-protocol v1.0.1-0.20210113015314-a4c552ebb760 h1:u7CNjLwKGSWHwP9F9Khbj0Oflu1QBmOUcK5PHq/O3w8= +github.com/alidn/lsif-protocol v1.0.1-0.20210113015314-a4c552ebb760/go.mod h1:VEuG8FZ3ISQOAHbzdj+qwS9nUfFlMsP4rVRBnDLztkQ= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/efritz/pentimento v0.0.0-20190429011147-ade47d831101 h1:RylpU+KNJJNEJIk3o8gZ70uPTlutxaYnikKNPko39LA= github.com/efritz/pentimento v0.0.0-20190429011147-ade47d831101/go.mod h1:5ALWO82UZwfAtNRUtwzsWimcrcuYzyieTyyXOXrP6EQ= -github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -36,10 +36,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/slimsag/godocmd v0.0.0-20161025000126-a1005ad29fe3 h1:sAARUcYbwxnebBeWHzKX2MeyXtzy25TEglCTz9BhueY= github.com/slimsag/godocmd v0.0.0-20161025000126-a1005ad29fe3/go.mod h1:AIBPxLCkKUFc2ZkjCXzs/Kk9OUhQLw/Zicdd0Rhqz2U= -github.com/sourcegraph/lsif-protocol v0.0.0-20200827191700-d005ee28c8a1 h1:qzkEXQxHlmGpJrKo6mOe3Tq5zFKZd/+B3DnJlmmrsRA= -github.com/sourcegraph/lsif-protocol v0.0.0-20200827191700-d005ee28c8a1/go.mod h1:VEuG8FZ3ISQOAHbzdj+qwS9nUfFlMsP4rVRBnDLztkQ= -github.com/sourcegraph/lsif-protocol v1.0.0 h1:NLxbnHuN2o4fibjRUrXTwuojD4+kDFPXra9PA1V6tQM= -github.com/sourcegraph/lsif-protocol v1.0.0/go.mod h1:VEuG8FZ3ISQOAHbzdj+qwS9nUfFlMsP4rVRBnDLztkQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= diff --git a/internal/indexer/helpers_test.go b/internal/indexer/helpers_test.go index 87ec3601..302715b8 100644 --- a/internal/indexer/helpers_test.go +++ b/internal/indexer/helpers_test.go @@ -222,6 +222,23 @@ func findDefintionRangesByDefinitionResultID(elements []interface{}, id uint64) return ranges } +func findTypeDefinitionRangesByDefinitionResultID(elements []interface{}, id uint64) (_ protocol.Range, found bool) { + for _, elem := range elements { + switch e := elem.(type) { + case protocol.Item: + if e.OutV == id { + for _, inV := range e.InVs { + if r, ok := findRangeByID(elements, inV); ok { + return r, true + } + } + } + } + } + + return protocol.Range{}, false +} + // findReferenceRangesByReferenceResultID returns the ranges attached to the reference result with the given // identifier. func findReferenceRangesByReferenceResultID(elements []interface{}, id uint64) (ranges []protocol.Range) { @@ -327,6 +344,28 @@ func findDefinitionRangesByRangeOrResultSetID(elements []interface{}, id uint64) return ranges } +func findTypeDefinitionRangeByRangeOrResultSetID(elements []interface{}, id uint64) (_ protocol.Range, found bool) { + for _, elem := range elements { + switch e := elem.(type) { + case protocol.TextDocumentTypeDefinition: + if e.OutV == id { + return findTypeDefinitionRangesByDefinitionResultID(elements, e.InV) + } + } + } + + for _, elem := range elements { + switch e := elem.(type) { + case protocol.Next: + if e.OutV == id { + return findTypeDefinitionRangeByRangeOrResultSetID(elements, e.InV) + } + } + } + + return protocol.Range{}, false +} + // findReferenceRangesByRangeOrResultSetID returns the reference ranges attached to the range or result set with // the given identifier. func findReferenceRangesByRangeOrResultSetID(elements []interface{}, id uint64) (ranges []protocol.Range) { diff --git a/internal/indexer/indexer.go b/internal/indexer/indexer.go index f09704be..854fe922 100644 --- a/internal/indexer/indexer.go +++ b/internal/indexer/indexer.go @@ -102,6 +102,7 @@ func (i *Indexer) Index() error { i.emitDocuments() i.addImports() i.indexDefinitions() + i.indexTypeDefinitions() i.indexReferences() i.linkReferenceResultsToRanges() i.emitContains() @@ -267,7 +268,7 @@ func getAllReferencedPackages(pkgs []*packages.Package) (flattened []*packages.P } // indexDefinitions emits data for each definition in an index target package. This will emit -// a result set, a definition result, a hover result, and export monikers attached to each range. +// a result set, a definition result, a hover result, a type definition result, and export monikers attached to each range. // This method will also populate each document's definition range identifier slice. func (i *Indexer) indexDefinitions() { i.visitEachPackage("Indexing definitions", i.indexDefinitionsForPackage) @@ -319,6 +320,60 @@ func (i *Indexer) indexDefinitionsForPackage(p *packages.Package) { } } +// indexTypeDefinitions emits type definition data for each definition and reference in an index target package. +// This will emit an 'item' edge from type definition result to type definition range. +func (i *Indexer) indexTypeDefinitions() { + i.visitEachPackage("Indexing type definitions", i.indexTypeDefinitionForPackage) +} + +// indexDefinitionsForPackage emits data for each type definition definition within the given package. +func (i *Indexer) indexTypeDefinitionForPackage(p *packages.Package) { + for _, obj := range p.TypesInfo.Defs { + if obj == nil { + continue + } + + defInfo := i.getDefinitionInfo(obj) + if defInfo == nil { + continue + } + + if typeRangeID, ok := i.getTypeDefRangeID(obj); ok { + i.emitter.EmitItem(defInfo.TypeDefResultID, []uint64{typeRangeID}, defInfo.DocumentID) + } + } +} + +// getTypeDefResultID returns the range id of the type definition if it was emitted. The second result parameter +// indicates whether or not such id is found. +func (i *Indexer) getTypeDefRangeID(obj types.Object) (_ uint64, found bool) { + if namedType, ok := namedTypeFromObj(obj); ok { + if defInfo := i.getDefinitionInfo(namedType.Obj()); defInfo != nil { + return defInfo.RangeID, true + } + } + return 0, false +} + +// namedTypeFromObj returns a named type if the given object is of type types.Named, types.Pointer or types.Slice. Otherwise, +// it returns nil and false. If obj is a pointer, it returns the type that it is pointing to, and if it's a slice, it returns +// the type of the slice elements. +func namedTypeFromObj(obj types.Object) (_ *types.Named, ok bool) { + t := obj.Type() + switch t.(type) { + case *types.Named: + res, ok := t.(*types.Named) + return res, ok + case *types.Pointer: + res, ok := t.(*types.Pointer).Elem().(*types.Named) + return res, ok + case *types.Slice: + res, ok := t.(*types.Slice).Elem().(*types.Named) + return res, ok + } + return nil, false +} + // positionAndDocument returns the position of the given object and the document info object // that contains it. If the given package is not the canonical package for the containing file // in the packagesByFile map, this method returns false. @@ -363,9 +418,11 @@ func (i *Indexer) indexDefinition(p *packages.Package, filename string, document rangeID := i.emitter.EmitRange(rangeForObject(obj, pos)) resultSetID := i.emitter.EmitResultSet() defResultID := i.emitter.EmitDefinitionResult() + typeDefResultID := i.emitter.EmitTypeDefinitionResult() _ = i.emitter.EmitNext(rangeID, resultSetID) _ = i.emitter.EmitTextDocumentDefinition(resultSetID, defResultID) + _ = i.emitter.EmitTextDocumentTypeDefinition(resultSetID, typeDefResultID) _ = i.emitter.EmitItem(defResultID, []uint64{rangeID}, document.DocumentID) if typeSwitchHeader { @@ -393,6 +450,7 @@ func (i *Indexer) indexDefinition(p *packages.Package, filename string, document DocumentID: document.DocumentID, RangeID: rangeID, ResultSetID: resultSetID, + TypeDefResultID: typeDefResultID, ReferenceRangeIDs: map[uint64][]uint64{}, TypeSwitchHeader: typeSwitchHeader, }) diff --git a/internal/indexer/indexer_test.go b/internal/indexer/indexer_test.go index 845c5010..4536f573 100644 --- a/internal/indexer/indexer_test.go +++ b/internal/indexer/indexer_test.go @@ -114,6 +114,136 @@ func TestIndexer(t *testing.T) { compareRange(t, references[3], 26, 1, 26, 3) // wg.Wait() }) + t.Run("Check type definition struct field", func(t *testing.T) { + r, ok := findRange(w.elements, "file://"+filepath.Join(projectRoot, "types.go"), 5, 1) + if !ok { + t.Fatalf("could not find target range") + } + + typeDefRange, ok := findTypeDefinitionRangeByRangeOrResultSetID(w.elements, r.ID) + if !ok { + t.Fatalf("expected to find the type definition") + } + + compareRange(t, typeDefRange, 2, 5, 2, 15) + }) + + t.Run("Check type definition struct def field", func(t *testing.T) { + r, ok := findRange(w.elements, "file://"+filepath.Join(projectRoot, "types.go"), 8, 11) + if !ok { + t.Fatalf("could not find target range") + } + + typeDefRange, ok := findTypeDefinitionRangeByRangeOrResultSetID(w.elements, r.ID) + if !ok { + t.Fatalf("expected to find the type definition") + } + + compareRange(t, typeDefRange, 4, 5, 4, 8) + }) + + t.Run("Check type definition of reference field", func(t *testing.T) { + r, ok := findRange(w.elements, "file://"+filepath.Join(projectRoot, "types.go"), 9, 1) + if !ok { + t.Fatalf("could not find target range") + } + + typeDefRange, ok := findTypeDefinitionRangeByRangeOrResultSetID(w.elements, r.ID) + if !ok { + t.Fatalf("expected to find the type definition") + } + + compareRange(t, typeDefRange, 4, 5, 4, 8) + }) + + t.Run("Check type definition of reference struct", func(t *testing.T) { + r, ok := findRange(w.elements, "file://"+filepath.Join(projectRoot, "types.go"), 10, 9) + if !ok { + t.Fatalf("could not find target range") + } + + typeDefRange, ok := findTypeDefinitionRangeByRangeOrResultSetID(w.elements, r.ID) + if !ok { + t.Fatalf("expected to find the type definition") + } + + compareRange(t, typeDefRange, 4, 5, 4, 8) + }) + + t.Run("Check type definition of reference field", func(t *testing.T) { + r, ok := findRange(w.elements, "file://"+filepath.Join(projectRoot, "types.go"), 10, 11) + if !ok { + t.Fatalf("could not find target range") + } + + typeDefRange, ok := findTypeDefinitionRangeByRangeOrResultSetID(w.elements, r.ID) + if !ok { + t.Fatalf("expected to find the type definition") + } + + compareRange(t, typeDefRange, 2, 5, 2, 15) + }) + + t.Run("Check type definition of basic type", func(t *testing.T) { + r, ok := findRange(w.elements, "file://"+filepath.Join(projectRoot, "types.go"), 13, 9) + if !ok { + t.Fatalf("could not find target range") + } + + _, ok = findTypeDefinitionRangeByRangeOrResultSetID(w.elements, r.ID) + if ok { + t.Fatalf("Did not expect to find the type definition") + } + }) + + t.Run("Check type definition of pointer", func(t *testing.T) { + r, ok := findRange(w.elements, "file://"+filepath.Join(projectRoot, "types.go"), 16, 9) + if !ok { + t.Fatalf("could not find target range") + } + + typeDefRange, ok := findTypeDefinitionRangeByRangeOrResultSetID(w.elements, r.ID) + if !ok { + t.Fatalf("expected to find the type definition") + } + + compareRange(t, typeDefRange, 4, 5, 4, 8) + }) + + t.Run("Check type definition of slice", func(t *testing.T) { + r, ok := findRange(w.elements, "file://"+filepath.Join(projectRoot, "types.go"), 19, 9) + if !ok { + t.Fatalf("could not find target range") + } + + typeDefRange, ok := findTypeDefinitionRangeByRangeOrResultSetID(w.elements, r.ID) + if !ok { + t.Fatalf("expected to find the type definition") + } + + compareRange(t, typeDefRange, 4, 5, 4, 8) + }) + + t.Run("Check type definition of var defined in a different package", func(t *testing.T) { + r, ok := findRange(w.elements, "file://"+filepath.Join(projectRoot, "cross_package_types.go"), 6, 9) + if !ok { + t.Fatalf("could not find target range") + } + + typeDefRange, ok := findTypeDefinitionRangeByRangeOrResultSetID(w.elements, r.ID) + if !ok { + t.Fatalf("expected to find the type definition") + } + + compareRange(t, typeDefRange, 2, 5, 2, 15) + + docUri := findDocumentURIContaining(w.elements, typeDefRange.ID) + expectedDocUri := "file://" + filepath.Join(projectRoot, "internal/secret/secret_types.go") + if docUri != expectedDocUri { + t.Fatalf("Unexpected document uri, want=%v have=%v", expectedDocUri, docUri) + } + }) + t.Run("check NestedB monikers", func(t *testing.T) { r, ok := findRange(w.elements, "file://"+filepath.Join(projectRoot, "data.go"), 27, 3) if !ok { diff --git a/internal/indexer/info.go b/internal/indexer/info.go index 7207472e..4d78c4d6 100644 --- a/internal/indexer/info.go +++ b/internal/indexer/info.go @@ -31,6 +31,7 @@ type DefinitionInfo struct { DocumentID uint64 RangeID uint64 ResultSetID uint64 + TypeDefResultID uint64 ReferenceRangeIDs map[uint64][]uint64 TypeSwitchHeader bool m sync.Mutex diff --git a/internal/testdata/cross_package_types.go b/internal/testdata/cross_package_types.go new file mode 100644 index 00000000..20615d66 --- /dev/null +++ b/internal/testdata/cross_package_types.go @@ -0,0 +1,8 @@ +package testdata + +import "github.com/sourcegraph/lsif-go/internal/testdata/internal/secret" + +func test() { + secretType := secret.SecretType{} + println(secretType) +} diff --git a/internal/testdata/internal/secret/secret_types.go b/internal/testdata/internal/secret/secret_types.go new file mode 100644 index 00000000..f3b5d594 --- /dev/null +++ b/internal/testdata/internal/secret/secret_types.go @@ -0,0 +1,3 @@ +package secret + +type SecretType struct{} diff --git a/internal/testdata/types.go b/internal/testdata/types.go new file mode 100644 index 00000000..4f356aba --- /dev/null +++ b/internal/testdata/types.go @@ -0,0 +1,21 @@ +package testdata + +type InnerField struct{} + +type Ali struct { + f InnerField +} + +func TryMe(a Ali) { + b := Ali{f: InnerField{}} + println(b.f) + + c := "hello" + println(c) + + d := &Ali{} + println(d) + + e := []Ali{{f: InnerField{}}} + println(e) +}