Repository: googleapis/api-linter
Pull request #76 (2019-06-10 20:16:37)

Added support for resolving dependencies among FileDescriptors


Patch:
diff --git a/cmd/api-linter/protoc_parser.go b/cmd/api-linter/protoc_parser.go
index be5b582..127023e 100644
--- a/cmd/api-linter/protoc_parser.go
+++ b/cmd/api-linter/protoc_parser.go
@@ -43,6 +43,7 @@ func compileProto(protoc, importPath string, filenames ...string) ([]*descriptor
 
 	cmd := exec.Command(protoc, "--descriptor_set_out="+outfile.Name())
 	cmd.Args = append(cmd.Args, "--include_source_info")
+	cmd.Args = append(cmd.Args, "--include_imports")
 	cmd.Args = append(cmd.Args, "--proto_path="+importPath)
 	cmd.Args = append(cmd.Args, filenames...)
 	if out, err := cmd.CombinedOutput(); err != nil {
diff --git a/lint/linter.go b/lint/linter.go
index e4ce62d..2c79f78 100644
--- a/lint/linter.go
+++ b/lint/linter.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"strings"
 
+	"google.golang.org/protobuf/reflect/protoregistry"
 	"google.golang.org/protobuf/types/descriptorpb"
 )
 
@@ -39,15 +40,25 @@ func New(rules Rules, configs Configs) *Linter {
 	return l
 }
 
-// LintProtos checks protobuf files and returns a list of problems or an error.
+// LintProtos checks protobuf files and returns a list of problems or an error. Note that a *FileDescriptorProto
+// for any imported file must be present in files. If any file in files has an import that is not also in the
+// slice, an error will be returned.
 func (l *Linter) LintProtos(files []*descriptorpb.FileDescriptorProto) ([]Response, error) {
-	return l.lintProtos(files)
+	reg, err := makeRegistryFromAllFiles(files)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return l.LintProtosWithRegistry(files, reg)
 }
 
-func (l *Linter) lintProtos(files []*descriptorpb.FileDescriptorProto) ([]Response, error) {
+// LintProtosWithRegistry checks protobuf files and returns a list of Responses or returns an error.
+// The input *protoregistry.Files must include all imports from any file in files.
+func (l *Linter) LintProtosWithRegistry(files []*descriptorpb.FileDescriptorProto, reg *protoregistry.Files) ([]Response, error) {
 	var responses []Response
 	for _, proto := range files {
-		req, err := NewProtoRequest(proto)
+		req, err := NewProtoRequest(proto, reg)
 		if err != nil {
 			return nil, err
 		}
diff --git a/lint/registry.go b/lint/registry.go
new file mode 100644
index 0000000..0dec855
--- /dev/null
+++ b/lint/registry.go
@@ -0,0 +1,92 @@
+package lint
+
+import (
+	"fmt"
+	"google.golang.org/protobuf/reflect/protodesc"
+	"google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/reflect/protoregistry"
+	"google.golang.org/protobuf/types/descriptorpb"
+)
+
+// makeRegistryFromAllFiles creates a *protoregistry.Files with all dependencies present in descs resolved.
+// Missing dependencies will be filled with placeholders. If there are cyclic dependencies, an error will
+// be returned.
+func makeRegistryFromAllFiles(descs []*descriptorpb.FileDescriptorProto) (*protoregistry.Files, error) {
+	filesSet, err := makeFilesSet(descs)
+	if err != nil {
+		return nil, err
+	}
+
+	f := files{
+		reg:      new(protoregistry.Files),
+		filesSet: filesSet,
+	}
+
+	for _, desc := range descs {
+		if err := f.register(desc.GetName()); err != nil {
+			return nil, err
+		}
+	}
+
+	return f.reg, nil
+}
+
+type files struct {
+	reg *protoregistry.Files
+
+	filesSet map[string]*entry
+}
+
+type entry struct {
+	descProto   *descriptorpb.FileDescriptorProto
+	desc        protoreflect.FileDescriptor
+	registering bool
+}
+
+func (e entry) isRegistered() bool {
+	return e.desc != nil
+}
+
+func (f *files) register(path string) error {
+	e, ok := f.filesSet[path]
+
+	// if we don't have the file do nothing, and a placeholder will be filled in for those that import it
+	if !ok || e.isRegistered() {
+		return nil
+	}
+
+	if e.registering {
+		return fmt.Errorf("cyclic dependency found on import of %q", path)
+	}
+
+	e.registering = true
+	defer func() {
+		e.registering = false
+	}()
+
+	for _, dep := range e.descProto.GetDependency() {
+		if err := f.register(dep); err != nil {
+			return err
+		}
+	}
+
+	var err error
+	if e.desc, err = protodesc.NewFile(e.descProto, f.reg); err != nil {
+		return err
+	}
+	if err = f.reg.Register(e.desc); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func makeFilesSet(files []*descriptorpb.FileDescriptorProto) (map[string]*entry, error) {
+	filesSet := make(map[string]*entry, len(files))
+
+	for _, f := range files {
+		filesSet[f.GetName()] = &entry{descProto: f}
+	}
+
+	return filesSet, nil
+}
diff --git a/lint/request.go b/lint/request.go
index c01066a..d69beb7 100644
--- a/lint/request.go
+++ b/lint/request.go
@@ -17,6 +17,7 @@ package lint
 import (
 	"google.golang.org/protobuf/reflect/protodesc"
 	"google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/reflect/protoregistry"
 	"google.golang.org/protobuf/types/descriptorpb"
 )
 
@@ -38,8 +39,8 @@ func (r Request) DescriptorSource() DescriptorSource {
 }
 
 // NewProtoRequest creates a linting Request for a .proto file.
-func NewProtoRequest(fd *descriptorpb.FileDescriptorProto) (Request, error) {
-	f, err := protodesc.NewFile(fd, nil)
+func NewProtoRequest(fd *descriptorpb.FileDescriptorProto, reg *protoregistry.Files) (Request, error) {
+	f, err := protodesc.NewFile(fd, reg)
 	if err != nil {
 		return Request{}, err
 	}


Test patch:
diff --git a/lint/linter_test.go b/lint/linter_test.go
index a754787..b392346 100644
--- a/lint/linter_test.go
+++ b/lint/linter_test.go
@@ -28,7 +28,7 @@ func TestLinter_run(t *testing.T) {
 	req, _ := NewProtoRequest(
 		&descriptorpb.FileDescriptorProto{
 			Name: &fileName,
-		})
+		}, nil)
 
 	defaultConfigs := Configs{
 		{[]string{"**"}, []string{}, map[string]RuleConfig{}},
diff --git a/lint/registry_test.go b/lint/registry_test.go
new file mode 100644
index 0000000..82ff69c
--- /dev/null
+++ b/lint/registry_test.go
@@ -0,0 +1,195 @@
+package lint
+
+import (
+	"github.com/googleapis/api-linter/rules/testutil"
+	"google.golang.org/protobuf/types/descriptorpb"
+	"testing"
+)
+
+func TestMakeRegistryFromAllFiles(t *testing.T) {
+	barProto := testutil.MustCreateFileDescriptorProtoFromTemplate(testutil.FileDescriptorSpec{
+		Filename: "bar.proto",
+		Template: `syntax = "proto3";
+
+message Bar {
+  string baz = 1;
+}`,
+	})
+
+	fooProto := testutil.MustCreateFileDescriptorProtoFromTemplate(testutil.FileDescriptorSpec{
+		Filename: "foo.proto",
+		Template: `syntax = "proto3";
+
+import "bar.proto";
+
+message Foo {
+  Bar bar = 1;
+}`,
+		Deps: []*descriptorpb.FileDescriptorProto{barProto},
+	})
+
+	reg, err := makeRegistryFromAllFiles([]*descriptorpb.FileDescriptorProto{fooProto, barProto})
+
+	if err != nil {
+		t.Fatalf("makeRegistryFromAllFiles() returned error %q; want nil", err)
+	}
+
+	foo, err := reg.FindMessageByName("Foo")
+
+	if err != nil {
+		t.Fatalf("reg.FindMessageByName(%q) returned error %q; want nil", fooProto.GetName(), err)
+	}
+
+	if foo.Fields().Len() != 1 {
+		t.Fatalf("foo.Fields().Len()=%d; want 1", foo.Fields().Len())
+	}
+
+	if foo.Fields().Get(0).Message() == nil {
+		t.Fatalf("foo.Fields().Get(0).Message() was nil")
+	}
+	if foo.Fields().Get(0).Message().Name() != "Bar" {
+		t.Fatalf("foo.Fields().Get(0).Message().Name() = %q; want %q", foo.Fields().Get(0).Message().Name(), "Bar")
+	}
+	if foo.Fields().Get(0).Message().IsPlaceholder() {
+		t.Fatalf("foo.Fields().Get(0).IsPlaceholder()=true; want false")
+	}
+}
+
+func TestMakeRegistryFromAllFiles_DirectAndIndirectDependencies(t *testing.T) {
+	barProto := testutil.MustCreateFileDescriptorProtoFromTemplate(testutil.FileDescriptorSpec{
+		Filename: "bar.proto",
+		Template: `syntax = "proto3";
+
+message Bar {
+  string baz = 1;
+}`,
+	})
+
+	fooProto := testutil.MustCreateFileDescriptorProtoFromTemplate(testutil.FileDescriptorSpec{
+		Filename: "foo.proto",
+		Template: `syntax = "proto3";
+
+import "bar.proto";
+
+message Foo {
+  Bar bar = 1;
+}`,
+		Deps: []*descriptorpb.FileDescriptorProto{barProto},
+	})
+
+	bazProto := testutil.MustCreateFileDescriptorProtoFromTemplate(testutil.FileDescriptorSpec{
+		Filename: "baz.proto",
+		Template: `syntax = "proto3";
+
+import "bar.proto";
+import "foo.proto";
+
+message Baz {
+	Foo foo = 1;
+	Bar bar = 2;
+}
+`,
+		Deps: []*descriptorpb.FileDescriptorProto{barProto, fooProto},
+	})
+
+	reg, err := makeRegistryFromAllFiles([]*descriptorpb.FileDescriptorProto{fooProto, barProto, bazProto})
+	if err != nil {
+		t.Fatalf("makeRegistryFromAllFiles() returned error %q; want nil", err)
+	}
+
+	foo, err := reg.FindMessageByName("Foo")
+	if err != nil {
+		t.Fatalf("reg.FindMessageByName(%q) returned error %q; want nil", "Foo", err)
+	}
+
+	if foo.Fields().Len() != 1 {
+		t.Fatalf("foo.Fields().Len()=%d; want 1", foo.Fields().Len())
+	}
+
+	if foo.Fields().Get(0).Message() == nil {
+		t.Fatalf("foo.Fields().Get(0).Message() was nil")
+	}
+	if foo.Fields().Get(0).Message().Name() != "Bar" {
+		t.Fatalf("foo.Fields().Get(0).Message().Name() = %q; want %q", foo.Fields().Get(0).Message().Name(), "Bar")
+	}
+	if foo.Fields().Get(0).Message().IsPlaceholder() {
+		t.Fatalf("foo.Fields().Get(0).IsPlaceholder()=true; want false")
+	}
+
+	baz, err := reg.FindMessageByName("Baz")
+	if err != nil {
+		t.Fatalf("reg.FindMessageByName(%q) returned error %q; want nil", "Baz", err)
+	}
+
+	if baz.Fields().Len() != 2 {
+		t.Fatalf("baz.Fields.Len()=%d; want 2", baz.Fields().Len())
+	}
+
+	if baz.Fields().Get(0).Message() == nil {
+		t.Fatalf("baz.Fields().Get(0).Message() was nil")
+	}
+	if baz.Fields().Get(0).Message().Name() != "Foo" {
+		t.Fatalf("baz.Fields().Get(0).Message().Name() = %q; want %q", baz.Fields().Get(1).Message().Name(), "Foo")
+	}
+	if baz.Fields().Get(0).Message().IsPlaceholder() {
+		t.Fatalf("baz.Fields().Get(0).IsPlaceholder()=true; want false")
+	}
+
+	if baz.Fields().Get(1).Message() == nil {
+		t.Fatalf("baz.Fields().Get(1).Message() was nil")
+	}
+	if baz.Fields().Get(1).Message().Name() != "Bar" {
+		t.Fatalf("baz.Fields().Get(1).Message().Name() = %q; want %q", baz.Fields().Get(1).Message().Name(), "Bar")
+	}
+	if baz.Fields().Get(1).Message().IsPlaceholder() {
+		t.Fatalf("baz.Fields().Get(1).IsPlaceholder()=true; want false")
+	}
+}
+
+func TestMakeRegistryFromAllFiles_MissingImports(t *testing.T) {
+	barProto := testutil.MustCreateFileDescriptorProtoFromTemplate(testutil.FileDescriptorSpec{
+		Filename: "bar.proto",
+		Template: `syntax = "proto3";
+
+message Bar {
+  string baz = 1;
+}`,
+	})
+
+	fooProto := testutil.MustCreateFileDescriptorProtoFromTemplate(testutil.FileDescriptorSpec{
+		Filename: "foo.proto",
+		Template: `syntax = "proto3";
+
+import "bar.proto";
+
+message Foo {
+  Bar bar = 1;
+}`,
+		Deps: []*descriptorpb.FileDescriptorProto{barProto},
+	})
+
+	reg, err := makeRegistryFromAllFiles([]*descriptorpb.FileDescriptorProto{fooProto})
+
+	if err != nil {
+		t.Fatalf("makeRegistryFromAllFiles() returned error %q; want nil", err)
+	}
+
+	foo, err := reg.FindMessageByName("Foo")
+	if err != nil {
+		t.Fatalf("Failed to find Foo message: %s.", err)
+	}
+
+	if foo.Fields().Len() != 1 {
+		t.Fatalf("foo.Fields().Len()=%d; want 1", foo.Fields().Len())
+	}
+
+	if foo.Fields().Get(0).Message() == nil {
+		t.Fatalf("foo.Fields().Get(0).Message() was nil")
+	}
+	if foo.Fields().Get(0).Message().Name() != "Bar" {
+		t.Fatalf("foo.Fields().Get(0).Message().Name() = %q; want %q", foo.Fields().Get(0).Message().Name(), "Bar")
+	}
+	if !foo.Fields().Get(0).Message().IsPlaceholder() {
+		t.Fatalf("foo.Fields().Get(0).IsPlaceholder()=true; want false")
+	}
+}
diff --git a/lint/source_test.go b/lint/source_test.go
index 718b058..eb07128 100644
--- a/lint/source_test.go
+++ b/lint/source_test.go
@@ -272,7 +272,7 @@ func readProtoFile(t *testing.T, fileName string) Request {
 		t.Fatalf("Unable to parse %T from %s: %v", protoset, path, err)
 	}
 	proto := protoset.GetFile()[0]
-	req, err := NewProtoRequest(proto)
+	req, err := NewProtoRequest(proto, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/rules/check_field_names_use_lower_snake_case_test.go b/rules/check_field_names_use_lower_snake_case_test.go
index 42d907b..0f6173b 100644
--- a/rules/check_field_names_use_lower_snake_case_test.go
+++ b/rules/check_field_names_use_lower_snake_case_test.go
@@ -16,17 +16,18 @@ package rules
 
 import (
 	"fmt"
+	"github.com/googleapis/api-linter/lint"
 	"testing"
 
 	"github.com/googleapis/api-linter/rules/testutil"
 )
 
 func TestFieldNamesUseLowerSnakeCaseRule(t *testing.T) {
-	tmpl := testutil.MustCreateTemplate(`
+	tmpl := `
 	syntax = "proto2";
 	message Foo {
 	  optional string {{.FieldName}} = 1;
-	}`)
+	}`
 
 	tests := []struct {
 		FieldName  string
@@ -45,9 +46,15 @@ func TestFieldNamesUseLowerSnakeCaseRule(t *testing.T) {
 	rule := checkFieldNamesUseLowerSnakeCase()
 
 	for _, test := range tests {
-		req := testutil.MustCreateRequestFromTemplate(tmpl, test)
-
 		errPrefix := fmt.Sprintf("Check field name `%s`", test.FieldName)
+
+		req, err := lint.NewProtoRequest(testutil.MustCreateFileDescriptorProtoFromTemplate(
+			testutil.FileDescriptorSpec{Template: tmpl, Data: test},
+		), nil)
+		if err != nil {
+			t.Errorf("%s: lint.NewProtoRequest returned error %v", errPrefix, err)
+		}
+
 		resp, err := rule.Lint(req)
 		if err != nil {
 			t.Errorf("%s: lint.Run return error %v", errPrefix, err)
diff --git a/rules/check_proto_syntax_version_test.go b/rules/check_proto_syntax_version_test.go
index 9aab2ce..ae7b697 100644
--- a/rules/check_proto_syntax_version_test.go
+++ b/rules/check_proto_syntax_version_test.go
@@ -16,13 +16,14 @@ package rules
 
 import (
 	"fmt"
+	"github.com/googleapis/api-linter/lint"
 	"testing"
 
 	"github.com/googleapis/api-linter/rules/testutil"
 )
 
 func TestProtoVersionRule(t *testing.T) {
-	tmpl := testutil.MustCreateTemplate(`syntax = "{{.Syntax}}";`)
+	tmpl := `syntax = "{{.Syntax}}";`
 
 	tests := []struct {
 		Syntax     string
@@ -36,10 +37,15 @@ func TestProtoVersionRule(t *testing.T) {
 
 	rule := checkProtoVersion()
 	for _, test := range tests {
-		req := testutil.MustCreateRequestFromTemplate(tmpl, test)
-
 		errPrefix := fmt.Sprintf("Check syntax `%s`", test.Syntax)
 
+		req, err := lint.NewProtoRequest(testutil.MustCreateFileDescriptorProtoFromTemplate(
+			testutil.FileDescriptorSpec{Template: tmpl, Data: test},
+		), nil)
+		if err != nil {
+			t.Errorf("%s: NewProtoRequest returned error %v", errPrefix, err)
+		}
+
 		resp, err := rule.Lint(req)
 		if err != nil {
 			t.Errorf("%s: Lint return error %v", errPrefix, err)
diff --git a/rules/corp/proto_files_must_include_version_test.go b/rules/corp/proto_files_must_include_version_test.go
index 42ddcdd..ba95aae 100644
--- a/rules/corp/proto_files_must_include_version_test.go
+++ b/rules/corp/proto_files_must_include_version_test.go
@@ -17,8 +17,8 @@ package corp
 import (
 	"testing"
 
-	"google.golang.org/protobuf/types/descriptorpb"
 	"github.com/googleapis/api-linter/lint"
+	"google.golang.org/protobuf/types/descriptorpb"
 )
 
 func TestProtoFilesMustIncludeVersion(t *testing.T) {
@@ -39,7 +39,7 @@ func TestProtoFilesMustIncludeVersion(t *testing.T) {
 		req, err := lint.NewProtoRequest(&descriptorpb.FileDescriptorProto{
 			Name:           &test.path,
 			SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
-		})
+		}, nil)
 
 		if err != nil {
 			t.Errorf("Failed to create proto request because %v", err)
diff --git a/rules/testutil/testutil.go b/rules/testutil/testutil.go
index 370f156..1d04b90 100644
--- a/rules/testutil/testutil.go
+++ b/rules/testutil/testutil.go
@@ -13,95 +13,126 @@ import (
 
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/descriptorpb"
-	"github.com/googleapis/api-linter/lint"
 )
 
 var protocPath = func() string {
 	return "protoc"
 }
 
-func descriptorProtoFromSource(source io.Reader) (*descriptorpb.FileDescriptorProto, error) {
+type FileDescriptorSpec struct {
+	Filename string
+	Template string
+	Data     interface{}
+	Deps     []*descriptorpb.FileDescriptorProto
+}
+
+// MustCreateFileDescriptorProtoFromTemplate creates a *descriptorpb.FileDescriptorProto from a string template and data.
+func MustCreateFileDescriptorProtoFromTemplate(spec FileDescriptorSpec) *descriptorpb.FileDescriptorProto {
+	tmpl := template.Must(template.New("test").Parse(spec.Template))
+	b := new(bytes.Buffer)
+	if err := tmpl.Execute(b, spec.Data); err != nil {
+		log.Fatalf("Error executing template %v", err)
+	}
+
+	return mustCreateDescriptorProtoFromSource(spec.Filename, b, spec.Deps)
+}
+
+func mustCreateDescriptorProtoFromSource(filename string, source io.Reader, deps []*descriptorpb.FileDescriptorProto) *descriptorpb.FileDescriptorProto {
 	tmpDir := os.TempDir()
 
 	f, err := ioutil.TempFile(tmpDir, "proto*")
 	if err != nil {
-		return nil, err
+		log.Fatalf("Failed creating temp proto source file: %s", err)
 	}
 	defer mustCloseAndRemoveFile(f)
 
 	if _, err = io.Copy(f, source); err != nil {
-		return nil, err
+		log.Fatalf("Failed to copy source to templ file: %s", err)
 	}
 
 	descSetF, err := ioutil.TempFile(tmpDir, "descset*")
 	if err != nil {
-		return nil, err
+		log.Fatalf("Failed to create temp descriptor set file: %s", err)
 	}
 	defer mustCloseAndRemoveFile(descSetF)
 
-	cmd := exec.Command(
-		protocPath(),
+	args := []string{
 		"--include_source_info",
 		fmt.Sprintf("--proto_path=%s", tmpDir),
 		fmt.Sprintf("--descriptor_set_out=%s", descSetF.Name()),
-		f.Name(),
-	)
+	}
+
+	if len(deps) > 0 {
+		descSetIn := mustCreateDescSetFileFromFileDescriptorProtos(deps)
+		defer mustCloseAndRemoveFile(descSetIn)
+
+		args = append(args, fmt.Sprintf("--descriptor_set_in=%s", descSetIn.Name()))
+	}
+
+	args = append(args, f.Name())
+
+	cmd := exec.Command(protocPath(), args...)
 
 	stderr := new(bytes.Buffer)
 	cmd.Stderr = stderr
 
 	if err = cmd.Run(); err != nil {
-		return nil, fmt.Errorf("protoc failed with %v and Stderr %q", err, stderr.String())
+		log.Fatalf("protoc failed with %v and Stderr %q", err, stderr.String())
 	}
 
 	descSet, err := ioutil.ReadFile(descSetF.Name())
 	if err != nil {
-		return nil, err
+		log.Fatalf("Failed to read descriptor set file: %s", err)
 	}
 
 	protoset := &descriptorpb.FileDescriptorSet{}
 	if err := proto.Unmarshal(descSet, protoset); err != nil {
-		return nil, err
+		log.Fatalf("Failed to unmarshal descriptor set file: %s", err)
 	}
 
 	if len(protoset.GetFile()) == 0 {
-		return nil, fmt.Errorf("protoset.GetFile() returns empty list")
+		log.Fatalf("protoset.GetFile() returns empty list")
 	}
 
-	return protoset.GetFile()[0], nil
+	protoset.GetFile()[0].Name = &filename
+
+	return protoset.GetFile()[0]
 }
 
-func mustCloseAndRemoveFile(f *os.File) {
-	if err := f.Close(); err != nil {
-		log.Fatalf("Error closing proto file: %v", err)
+func mustCreateDescSetFileFromFileDescriptorProtos(descs []*descriptorpb.FileDescriptorProto) *os.File {
+	if len(descs) == 0 {
+		return nil
 	}
 
-	if err := os.Remove(f.Name()); err != nil {
-		log.Fatalf("Error removing proto file: %v", err)
-	}
-}
+	descSet := new(descriptorpb.FileDescriptorSet)
+	descSet.File = descs
 
-// MustCreateTemplate creates a template with name "test" from
-// the provided template string.
-func MustCreateTemplate(tmpl string) *template.Template {
-	return template.Must(template.New("test").Parse(tmpl))
-}
+	rawDescSet, err := proto.Marshal(descSet)
 
-// MustCreateRequestFromTemplate creates a lint.Request from the provided template and test data.
-func MustCreateRequestFromTemplate(tmpl *template.Template, testData interface{}) lint.Request {
-	b := new(bytes.Buffer)
-	if err := tmpl.Execute(b, testData); err != nil {
-		log.Fatalf("Error executing template %v", err)
+	if err != nil {
+		log.Fatalf("Failed to marshal descriptor set: %s", err)
 	}
 
-	pd, err := descriptorProtoFromSource(b)
+	descSetF, err := ioutil.TempFile(os.TempDir(), "descset*")
+
 	if err != nil {
-		log.Fatalf("Error generating proto descriptor: %v", err)
+		log.Fatalf("Failed to make descriptor set file: %s", err)
 	}
 
-	req, err := lint.NewProtoRequest(pd)
-	if err != nil {
-		log.Fatalf("Error creating proto request: %v", err)
+	if _, err := io.Copy(descSetF, bytes.NewReader(rawDescSet)); err != nil {
+		mustCloseAndRemoveFile(descSetF)
+		log.Fatalf("Failed to ")
+	}
+
+	return descSetF
+}
+
+func mustCloseAndRemoveFile(f *os.File) {
+	if err := f.Close(); err != nil {
+		log.Fatalf("Error closing proto file: %v", err)
+	}
+
+	if err := os.Remove(f.Name()); err != nil {
+		log.Fatalf("Error removing proto file: %v", err)
 	}
-	return req
 }