reflect/protoreflect: add FileDescriptor.SourceLocations

This adds minimal support for preserving the source context information.

Change-Id: I4b3cac9690b7469ecb4e5434251a809be4d7894c
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183157
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/reflect/protodesc/desc.go b/reflect/protodesc/desc.go
index fd310c0..e378d82 100644
--- a/reflect/protodesc/desc.go
+++ b/reflect/protodesc/desc.go
@@ -136,6 +136,32 @@
 		imps.importPublic(imp.Imports())
 	}
 
+	// Handle source locations.
+	for _, loc := range fd.GetSourceCodeInfo().GetLocation() {
+		var l protoreflect.SourceLocation
+		// TODO: Validate that the path points to an actual declaration?
+		l.Path = protoreflect.SourcePath(loc.GetPath())
+		s := loc.GetSpan()
+		switch len(s) {
+		case 3:
+			l.StartLine, l.StartColumn, l.EndLine, l.EndColumn = int(s[0]), int(s[1]), int(s[0]), int(s[2])
+		case 4:
+			l.StartLine, l.StartColumn, l.EndLine, l.EndColumn = int(s[0]), int(s[1]), int(s[2]), int(s[3])
+		default:
+			return nil, errors.New("invalid span: %v", s)
+		}
+		// TODO: Validate that the span information is sensible?
+		// See https://github.com/protocolbuffers/protobuf/issues/6378.
+		if false && (l.EndLine < l.StartLine || l.StartLine < 0 || l.StartColumn < 0 || l.EndColumn < 0 ||
+			(l.StartLine == l.EndLine && l.EndColumn <= l.StartColumn)) {
+			return nil, errors.New("invalid span: %v", s)
+		}
+		l.LeadingDetachedComments = loc.GetLeadingDetachedComments()
+		l.LeadingComments = loc.GetLeadingComments()
+		l.TrailingComments = loc.GetTrailingComments()
+		f.L2.Locations.List = append(f.L2.Locations.List, l)
+	}
+
 	// Step 1: Allocate and derive the names for all declarations.
 	// This copies all fields from the descriptor proto except:
 	//	google.protobuf.FieldDescriptorProto.type_name
diff --git a/reflect/protodesc/file_test.go b/reflect/protodesc/file_test.go
index dd6dde6..9de1d03 100644
--- a/reflect/protodesc/file_test.go
+++ b/reflect/protodesc/file_test.go
@@ -181,6 +181,33 @@
 		`),
 		// TODO: Test import public
 	}, {
+		label: "preserve source code locations",
+		inDesc: mustParseFile(`
+			name: "test.proto"
+			package: "fizz.buzz"
+			source_code_info: {location: [{
+				span: [39,0,882,1]
+			}, {
+				path: [12]
+				span: [39,0,18]
+				leading_detached_comments: [" foo\n"," bar\n"]
+			}, {
+				path: [8,9]
+				span: [51,0,28]
+				leading_comments: " Comment\n"
+			}]}
+		`),
+	}, {
+		label: "invalid source code span",
+		inDesc: mustParseFile(`
+			name: "test.proto"
+			package: "fizz.buzz"
+			source_code_info: {location: [{
+				span: [39]
+			}]}
+		`),
+		wantErr: `invalid span: [39]`,
+	}, {
 		label: "resolve relative reference",
 		inDesc: mustParseFile(`
 			name: "test.proto"
diff --git a/reflect/protodesc/proto.go b/reflect/protodesc/proto.go
index e2f8f4a..380fd70 100644
--- a/reflect/protodesc/proto.go
+++ b/reflect/protodesc/proto.go
@@ -34,6 +34,28 @@
 			p.WeakDependency = append(p.WeakDependency, int32(i))
 		}
 	}
+	for i, locs := 0, file.SourceLocations(); i < locs.Len(); i++ {
+		loc := locs.Get(i)
+		l := &descriptorpb.SourceCodeInfo_Location{}
+		l.Path = append(l.Path, loc.Path...)
+		if loc.StartLine == loc.EndLine {
+			l.Span = []int32{int32(loc.StartLine), int32(loc.StartColumn), int32(loc.EndColumn)}
+		} else {
+			l.Span = []int32{int32(loc.StartLine), int32(loc.StartColumn), int32(loc.EndLine), int32(loc.EndColumn)}
+		}
+		l.LeadingDetachedComments = append([]string(nil), loc.LeadingDetachedComments...)
+		if loc.LeadingComments != "" {
+			l.LeadingComments = proto.String(loc.LeadingComments)
+		}
+		if loc.TrailingComments != "" {
+			l.TrailingComments = proto.String(loc.TrailingComments)
+		}
+		if p.SourceCodeInfo == nil {
+			p.SourceCodeInfo = &descriptorpb.SourceCodeInfo{}
+		}
+		p.SourceCodeInfo.Location = append(p.SourceCodeInfo.Location, l)
+
+	}
 	for i, messages := 0, file.Messages(); i < messages.Len(); i++ {
 		p.MessageType = append(p.MessageType, ToDescriptorProto(messages.Get(i)))
 	}
diff --git a/reflect/protoreflect/source.go b/reflect/protoreflect/source.go
new file mode 100644
index 0000000..f08c983
--- /dev/null
+++ b/reflect/protoreflect/source.go
@@ -0,0 +1,52 @@
+// Copyright 2019 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 protoreflect
+
+// SourceLocations is a list of source locations.
+type SourceLocations interface {
+	// Len reports the number of source locations in the proto file.
+	Len() int
+	// Get returns the ith SourceLocation. It panics if out of bounds.
+	Get(int) SourceLocation
+
+	doNotImplement
+
+	// TODO: Add ByPath and ByDescriptor helper methods.
+}
+
+// SourceLocation describes a source location and
+// corresponds with the google.protobuf.SourceCodeInfo.Location message.
+type SourceLocation struct {
+	// Path is the path to the declaration from the root file descriptor.
+	// The contents of this slice must not be mutated.
+	Path SourcePath
+
+	// StartLine and StartColumn are the zero-indexed starting location
+	// in the source file for the declaration.
+	StartLine, StartColumn int
+	// EndLine and EndColumn are the zero-indexed ending location
+	// in the source file for the declaration.
+	// In the descriptor.proto, the end line may be omitted if it is identical
+	// to the start line. Here, it is always populated.
+	EndLine, EndColumn int
+
+	// LeadingDetachedComments are the leading detached comments
+	// for the declaration. The contents of this slice must not be mutated.
+	LeadingDetachedComments []string
+	// LeadingComments is the leading attached comment for the declaration.
+	LeadingComments string
+	// TrailingComments is the trailing attached comment for the declaration.
+	TrailingComments string
+}
+
+// SourcePath identifies a part of a file descriptor for a source location.
+// The SourcePath is a sequence of either field numbers or indexes into
+// a repeated field that form a path starting from the root file descriptor.
+//
+// See google.protobuf.SourceCodeInfo.Location.path.
+type SourcePath []int32
+
+// TODO: Add SourcePath.String method to pretty-print the path. For example:
+//	".message_type[6].nested_type[15].field[3]"
diff --git a/reflect/protoreflect/type.go b/reflect/protoreflect/type.go
index 53ffea4..cad02b1 100644
--- a/reflect/protoreflect/type.go
+++ b/reflect/protoreflect/type.go
@@ -133,6 +133,9 @@
 	// Services is a list of the top-level service declarations.
 	Services() ServiceDescriptors
 
+	// SourceLocations is a list of source locations.
+	SourceLocations() SourceLocations
+
 	isFileDescriptor
 }
 type isFileDescriptor interface{ ProtoType(FileDescriptor) }