diff --git a/file.go b/file.go index 9b7a3de..3e8c075 100644 --- a/file.go +++ b/file.go @@ -20,6 +20,10 @@ type File interface { // File. Use Imports to obtain only direct dependencies. TransitiveImports() []File + // UnusedImports returns all imported files that aren't used by the current + // File. Public imports are not included in this list. + UnusedImports() []File + // Dependents returns all files where the given file was directly or // transitively imported. Dependents() []File @@ -125,6 +129,40 @@ func (f *file) TransitiveImports() []File { return out } +func (f *file) UnusedImports() []File { + public := make(map[int]struct{}, len(f.desc.PublicDependency)) + for _, i := range f.desc.PublicDependency { + public[int(i)] = struct{}{} + } + + mp := make(map[string]File, len(f.fileDependencies)) + for i, fl := range f.fileDependencies { + if _, ok := public[i]; ok { + continue + } + mp[fl.Name().String()] = fl + } + + for _, msg := range f.AllMessages() { + for _, imp := range msg.Imports() { + delete(mp, imp.Name().String()) + } + } + + for _, svc := range f.Services() { + for _, imp := range svc.Imports() { + delete(mp, imp.Name().String()) + } + } + + out := make([]File, 0, len(mp)) + for _, fl := range mp { + out = append(out, fl) + } + + return out +} + func (f *file) Dependents() []File { if f.dependentsCache == nil { set := make(map[string]File) diff --git a/file_test.go b/file_test.go index f3f2706..8384925 100644 --- a/file_test.go +++ b/file_test.go @@ -182,6 +182,48 @@ func TestFile_TransitiveImports(t *testing.T) { assert.Len(t, f.TransitiveImports(), 2) } +func TestFile_UnusedImports(t *testing.T) { + t.Parallel() + + target := &file{desc: &descriptor.FileDescriptorProto{ + Name: proto.String("foobar"), + }} + + unusedFile := &file{desc: &descriptor.FileDescriptorProto{ + Name: proto.String("i/am/unused.proto"), + }} + + target.addFileDependency(unusedFile) + + publicFile := &file{desc: &descriptor.FileDescriptorProto{ + Name: proto.String("i/am/public.proto"), + }} + + target.addFileDependency(publicFile) + target.desc.PublicDependency = append(target.desc.PublicDependency, 1) + + msgDep := dummyMsg() + usedFile := msgDep.File().(*file) + + ft := &embedT{scalarT: &scalarT{}, msg: msgDep} + fld := &field{} + fld.addType(ft) + m := &msg{} + m.addField(fld) + target.addMessage(m) + + mtd := &method{in: msgDep, out: m} + svc := &service{} + svc.addMethod(mtd) + target.addService(svc) + + target.addFileDependency(usedFile) + + unused := target.UnusedImports() + assert.Len(t, unused, 1) + assert.Equal(t, unusedFile, unused[0]) +} + func TestFile_Dependents(t *testing.T) { t.Parallel()