blob: d0a61c2ecaa399ddcbb9e16508a3eb00019f6510 [file] [log] [blame]
package protoparse
import (
"bytes"
"reflect"
"sort"
"strings"
"github.com/golang/protobuf/proto"
dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
"github.com/jhump/protoreflect/desc/internal"
)
func (r *parseResult) generateSourceCodeInfo() *dpb.SourceCodeInfo {
if r.nodes == nil {
// skip files that do not have AST info (these will be files
// that came from well-known descriptors, instead of from source)
return nil
}
sci := sourceCodeInfo{commentsUsed: map[*comment]struct{}{}}
path := make([]int32, 0, 10)
fn := r.getFileNode(r.fd).(*fileNode)
if fn.syntax != nil {
sci.newLoc(fn.syntax, append(path, internal.File_syntaxTag))
}
if fn.pkg != nil {
sci.newLoc(fn.pkg, append(path, internal.File_packageTag))
}
for i, imp := range fn.imports {
sci.newLoc(imp, append(path, internal.File_dependencyTag, int32(i)))
}
// file options
r.generateSourceCodeInfoForOptions(&sci, fn.decls, func(n interface{}) *optionNode {
return n.(*fileElement).option
}, r.fd.Options.GetUninterpretedOption(), append(path, internal.File_optionsTag))
// message types
for i, msg := range r.fd.GetMessageType() {
r.generateSourceCodeInfoForMessage(&sci, msg, append(path, internal.File_messagesTag, int32(i)))
}
// enum types
for i, enum := range r.fd.GetEnumType() {
r.generateSourceCodeInfoForEnum(&sci, enum, append(path, internal.File_enumsTag, int32(i)))
}
// extension fields
for i, ext := range r.fd.GetExtension() {
r.generateSourceCodeInfoForField(&sci, ext, append(path, internal.File_extensionsTag, int32(i)))
}
// services and methods
for i, svc := range r.fd.GetService() {
n := r.getServiceNode(svc).(*serviceNode)
svcPath := append(path, internal.File_servicesTag, int32(i))
sci.newLoc(n, svcPath)
sci.newLoc(n.name, append(svcPath, internal.Service_nameTag))
// service options
r.generateSourceCodeInfoForOptions(&sci, n.decls, func(n interface{}) *optionNode {
return n.(*serviceElement).option
}, svc.Options.GetUninterpretedOption(), append(svcPath, internal.Service_optionsTag))
// methods
for j, mtd := range svc.GetMethod() {
mn := r.getMethodNode(mtd).(*methodNode)
mtdPath := append(svcPath, internal.Service_methodsTag, int32(j))
sci.newLoc(mn, mtdPath)
sci.newLoc(mn.name, append(mtdPath, internal.Method_nameTag))
sci.newLoc(mn.input.msgType, append(mtdPath, internal.Method_inputTag))
if mn.input.streamKeyword != nil {
sci.newLoc(mn.input.streamKeyword, append(mtdPath, internal.Method_inputStreamTag))
}
sci.newLoc(mn.output.msgType, append(mtdPath, internal.Method_outputTag))
if mn.output.streamKeyword != nil {
sci.newLoc(mn.output.streamKeyword, append(mtdPath, internal.Method_outputStreamTag))
}
// method options
r.generateSourceCodeInfoForOptions(&sci, mn.options, func(n interface{}) *optionNode {
return n.(*optionNode)
}, mtd.Options.GetUninterpretedOption(), append(mtdPath, internal.Method_optionsTag))
}
}
return &dpb.SourceCodeInfo{Location: sci.generateLocs()}
}
func (r *parseResult) generateSourceCodeInfoForOptions(sci *sourceCodeInfo, elements interface{}, extractor func(interface{}) *optionNode, uninterp []*dpb.UninterpretedOption, path []int32) {
// Known options are option node elements that have a corresponding
// path in r.interpretedOptions. We'll do those first.
rv := reflect.ValueOf(elements)
for i := 0; i < rv.Len(); i++ {
on := extractor(rv.Index(i).Interface())
if on == nil {
continue
}
optPath := r.interpretedOptions[on]
if len(optPath) > 0 {
p := path
if optPath[0] == -1 {
// used by "default" and "json_name" field pseudo-options
// to attribute path to parent element (since those are
// stored directly on the descriptor, not its options)
p = make([]int32, len(path)-1)
copy(p, path)
optPath = optPath[1:]
}
sci.newLoc(on, append(p, optPath...))
}
}
// Now uninterpreted options
for i, uo := range uninterp {
optPath := append(path, internal.UninterpretedOptionsTag, int32(i))
on := r.getOptionNode(uo).(*optionNode)
sci.newLoc(on, optPath)
var valTag int32
switch {
case uo.IdentifierValue != nil:
valTag = internal.Uninterpreted_identTag
case uo.PositiveIntValue != nil:
valTag = internal.Uninterpreted_posIntTag
case uo.NegativeIntValue != nil:
valTag = internal.Uninterpreted_negIntTag
case uo.DoubleValue != nil:
valTag = internal.Uninterpreted_doubleTag
case uo.StringValue != nil:
valTag = internal.Uninterpreted_stringTag
case uo.AggregateValue != nil:
valTag = internal.Uninterpreted_aggregateTag
}
if valTag != 0 {
sci.newLoc(on.val, append(optPath, valTag))
}
for j, n := range uo.Name {
optNmPath := append(optPath, internal.Uninterpreted_nameTag, int32(j))
nn := r.getOptionNamePartNode(n).(*optionNamePartNode)
sci.newLoc(nn, optNmPath)
sci.newLoc(nn.text, append(optNmPath, internal.UninterpretedName_nameTag))
}
}
}
func (r *parseResult) generateSourceCodeInfoForMessage(sci *sourceCodeInfo, msg *dpb.DescriptorProto, path []int32) {
n := r.getMessageNode(msg)
sci.newLoc(n, path)
var decls []*messageElement
var resvdNames []*stringLiteralNode
switch n := n.(type) {
case *messageNode:
decls = n.decls
resvdNames = n.reserved
case *groupNode:
decls = n.decls
resvdNames = n.reserved
}
if decls == nil {
// map entry so nothing else to do
return
}
sci.newLoc(n.messageName(), append(path, internal.Message_nameTag))
// message options
r.generateSourceCodeInfoForOptions(sci, decls, func(n interface{}) *optionNode {
return n.(*messageElement).option
}, msg.Options.GetUninterpretedOption(), append(path, internal.Message_optionsTag))
// fields
for i, fld := range msg.GetField() {
r.generateSourceCodeInfoForField(sci, fld, append(path, internal.Message_fieldsTag, int32(i)))
}
// one-ofs
for i, ood := range msg.GetOneofDecl() {
oon := r.getOneOfNode(ood).(*oneOfNode)
ooPath := append(path, internal.Message_oneOfsTag, int32(i))
sci.newLoc(oon, ooPath)
sci.newLoc(oon.name, append(ooPath, internal.OneOf_nameTag))
// one-of options
r.generateSourceCodeInfoForOptions(sci, oon.decls, func(n interface{}) *optionNode {
return n.(*oneOfElement).option
}, ood.Options.GetUninterpretedOption(), append(ooPath, internal.OneOf_optionsTag))
}
// nested messages
for i, nm := range msg.GetNestedType() {
r.generateSourceCodeInfoForMessage(sci, nm, append(path, internal.Message_nestedMessagesTag, int32(i)))
}
// nested enums
for i, enum := range msg.GetEnumType() {
r.generateSourceCodeInfoForEnum(sci, enum, append(path, internal.Message_enumsTag, int32(i)))
}
// nested extensions
for i, ext := range msg.GetExtension() {
r.generateSourceCodeInfoForField(sci, ext, append(path, internal.Message_extensionsTag, int32(i)))
}
// extension ranges
for i, er := range msg.ExtensionRange {
rangePath := append(path, internal.Message_extensionRangeTag, int32(i))
rn := r.getExtensionRangeNode(er).(*rangeNode)
sci.newLoc(rn, rangePath)
sci.newLoc(rn.stNode, append(rangePath, internal.ExtensionRange_startTag))
if rn.stNode != rn.enNode {
sci.newLoc(rn.enNode, append(rangePath, internal.ExtensionRange_endTag))
}
// now we have to find the extension decl and options that correspond to this range :(
for _, d := range decls {
found := false
if d.extensionRange != nil {
for _, r := range d.extensionRange.ranges {
if rn == r {
found = true
break
}
}
}
if found {
r.generateSourceCodeInfoForOptions(sci, d.extensionRange.options, func(n interface{}) *optionNode {
return n.(*optionNode)
}, er.Options.GetUninterpretedOption(), append(rangePath, internal.ExtensionRange_optionsTag))
break
}
}
}
// reserved ranges
for i, rr := range msg.ReservedRange {
rangePath := append(path, internal.Message_reservedRangeTag, int32(i))
rn := r.getMessageReservedRangeNode(rr).(*rangeNode)
sci.newLoc(rn, rangePath)
sci.newLoc(rn.stNode, append(rangePath, internal.ReservedRange_startTag))
if rn.stNode != rn.enNode {
sci.newLoc(rn.enNode, append(rangePath, internal.ReservedRange_endTag))
}
}
// reserved names
for i, n := range resvdNames {
sci.newLoc(n, append(path, internal.Message_reservedNameTag, int32(i)))
}
}
func (r *parseResult) generateSourceCodeInfoForEnum(sci *sourceCodeInfo, enum *dpb.EnumDescriptorProto, path []int32) {
n := r.getEnumNode(enum).(*enumNode)
sci.newLoc(n, path)
sci.newLoc(n.name, append(path, internal.Enum_nameTag))
// enum options
r.generateSourceCodeInfoForOptions(sci, n.decls, func(n interface{}) *optionNode {
return n.(*enumElement).option
}, enum.Options.GetUninterpretedOption(), append(path, internal.Enum_optionsTag))
// enum values
for j, ev := range enum.GetValue() {
evn := r.getEnumValueNode(ev).(*enumValueNode)
evPath := append(path, internal.Enum_valuesTag, int32(j))
sci.newLoc(evn, evPath)
sci.newLoc(evn.name, append(evPath, internal.EnumVal_nameTag))
sci.newLoc(evn.getNumber(), append(evPath, internal.EnumVal_numberTag))
// enum value options
r.generateSourceCodeInfoForOptions(sci, evn.options, func(n interface{}) *optionNode {
return n.(*optionNode)
}, ev.Options.GetUninterpretedOption(), append(evPath, internal.EnumVal_optionsTag))
}
// reserved ranges
for i, rr := range enum.GetReservedRange() {
rangePath := append(path, internal.Enum_reservedRangeTag, int32(i))
rn := r.getEnumReservedRangeNode(rr).(*rangeNode)
sci.newLoc(rn, rangePath)
sci.newLoc(rn.stNode, append(rangePath, internal.ReservedRange_startTag))
if rn.stNode != rn.enNode {
sci.newLoc(rn.enNode, append(rangePath, internal.ReservedRange_endTag))
}
}
// reserved names
for i, rn := range n.reserved {
sci.newLoc(rn, append(path, internal.Enum_reservedNameTag, int32(i)))
}
}
func (r *parseResult) generateSourceCodeInfoForField(sci *sourceCodeInfo, fld *dpb.FieldDescriptorProto, path []int32) {
n := r.getFieldNode(fld)
isGroup := false
var opts []*optionNode
var extendee *extendNode
switch n := n.(type) {
case *fieldNode:
opts = n.options
extendee = n.extendee
case *mapFieldNode:
opts = n.options
case *groupNode:
isGroup = true
extendee = n.extendee
case *syntheticMapField:
// shouldn't get here since we don't recurse into fields from a mapNode
// in generateSourceCodeInfoForMessage... but just in case
return
}
sci.newLoc(n, path)
if !isGroup {
sci.newLoc(n.fieldName(), append(path, internal.Field_nameTag))
sci.newLoc(n.fieldType(), append(path, internal.Field_typeTag))
}
if n.fieldLabel() != nil {
sci.newLoc(n.fieldLabel(), append(path, internal.Field_labelTag))
}
sci.newLoc(n.fieldTag(), append(path, internal.Field_numberTag))
if extendee != nil {
sci.newLoc(extendee.extendee, append(path, internal.Field_extendeeTag))
}
r.generateSourceCodeInfoForOptions(sci, opts, func(n interface{}) *optionNode {
return n.(*optionNode)
}, fld.Options.GetUninterpretedOption(), append(path, internal.Field_optionsTag))
}
type sourceCodeInfo struct {
locs []*dpb.SourceCodeInfo_Location
commentsUsed map[*comment]struct{}
}
func (sci *sourceCodeInfo) newLoc(n node, path []int32) {
leadingComments := n.leadingComments()
trailingComments := n.trailingComments()
if sci.commentUsed(leadingComments) {
leadingComments = nil
}
if sci.commentUsed(trailingComments) {
trailingComments = nil
}
detached := groupComments(leadingComments)
trail := combineComments(trailingComments)
var lead *string
if len(leadingComments) > 0 && leadingComments[len(leadingComments)-1].end.Line >= n.start().Line-1 {
lead = proto.String(detached[len(detached)-1])
detached = detached[:len(detached)-1]
}
dup := make([]int32, len(path))
copy(dup, path)
var span []int32
if n.start().Line == n.end().Line {
span = []int32{int32(n.start().Line) - 1, int32(n.start().Col) - 1, int32(n.end().Col) - 1}
} else {
span = []int32{int32(n.start().Line) - 1, int32(n.start().Col) - 1, int32(n.end().Line) - 1, int32(n.end().Col) - 1}
}
sci.locs = append(sci.locs, &dpb.SourceCodeInfo_Location{
LeadingDetachedComments: detached,
LeadingComments: lead,
TrailingComments: trail,
Path: dup,
Span: span,
})
}
func (sci *sourceCodeInfo) commentUsed(c []*comment) bool {
if len(c) == 0 {
return false
}
if _, ok := sci.commentsUsed[c[0]]; ok {
return true
}
sci.commentsUsed[c[0]] = struct{}{}
return false
}
func groupComments(comments []*comment) []string {
if len(comments) == 0 {
return nil
}
var groups []string
singleLineStyle := comments[0].text[:2] == "//"
line := comments[0].end.Line
start := 0
for i := 1; i < len(comments); i++ {
c := comments[i]
prevSingleLine := singleLineStyle
singleLineStyle = strings.HasPrefix(comments[i].text, "//")
if !singleLineStyle || prevSingleLine != singleLineStyle || c.start.Line > line+1 {
// new group!
groups = append(groups, *combineComments(comments[start:i]))
start = i
}
line = c.end.Line
}
// don't forget last group
groups = append(groups, *combineComments(comments[start:]))
return groups
}
func combineComments(comments []*comment) *string {
if len(comments) == 0 {
return nil
}
first := true
var buf bytes.Buffer
for _, c := range comments {
if first {
first = false
} else {
buf.WriteByte('\n')
}
if c.text[:2] == "//" {
buf.WriteString(c.text[2:])
} else {
lines := strings.Split(c.text[2:len(c.text)-2], "\n")
first := true
for _, l := range lines {
if first {
first = false
} else {
buf.WriteByte('\n')
}
// strip a prefix of whitespace followed by '*'
j := 0
for j < len(l) {
if l[j] != ' ' && l[j] != '\t' {
break
}
j++
}
if j == len(l) {
l = ""
} else if l[j] == '*' {
l = l[j+1:]
} else if j > 0 {
l = " " + l[j:]
}
buf.WriteString(l)
}
}
}
return proto.String(buf.String())
}
func (sci *sourceCodeInfo) generateLocs() []*dpb.SourceCodeInfo_Location {
// generate intermediate locations: paths between root (inclusive) and the
// leaf locations already created, these will not have comments but will
// have aggregate span, than runs from min(start pos) to max(end pos) for
// all descendent paths.
if len(sci.locs) == 0 {
// nothing to generate
return nil
}
var root locTrie
for _, loc := range sci.locs {
root.add(loc.Path, loc)
}
root.fillIn()
locs := make([]*dpb.SourceCodeInfo_Location, 0, root.countLocs())
root.aggregate(&locs)
// finally, sort the resulting slice by location
sort.Slice(locs, func(i, j int) bool {
startI, endI := getSpanPositions(locs[i].Span)
startJ, endJ := getSpanPositions(locs[j].Span)
cmp := compareSlice(startI, startJ)
if cmp == 0 {
// if start position is the same, sort by end position _decreasing_
// (so enclosing locations will appear before leaves)
cmp = -compareSlice(endI, endJ)
if cmp == 0 {
// start and end position are the same? so break ties using path
cmp = compareSlice(locs[i].Path, locs[j].Path)
}
}
return cmp < 0
})
return locs
}
type locTrie struct {
children map[int32]*locTrie
loc *dpb.SourceCodeInfo_Location
}
func (t *locTrie) add(path []int32, loc *dpb.SourceCodeInfo_Location) {
if len(path) == 0 {
t.loc = loc
return
}
child := t.children[path[0]]
if child == nil {
if t.children == nil {
t.children = map[int32]*locTrie{}
}
child = &locTrie{}
t.children[path[0]] = child
}
child.add(path[1:], loc)
}
func (t *locTrie) fillIn() {
var path []int32
var start, end []int32
for _, child := range t.children {
// recurse
child.fillIn()
if t.loc == nil {
// maintain min(start) and max(end) so we can
// populate t.loc below
childStart, childEnd := getSpanPositions(child.loc.Span)
if start == nil {
if path == nil {
path = child.loc.Path[:len(child.loc.Path)-1]
}
start = childStart
end = childEnd
} else {
if compareSlice(childStart, start) < 0 {
start = childStart
}
if compareSlice(childEnd, end) > 0 {
end = childEnd
}
}
}
}
if t.loc == nil {
var span []int32
// we don't use append below because we want a new slice
// that doesn't share underlying buffer with spans from
// any other location
if start[0] == end[0] {
span = []int32{start[0], start[1], end[1]}
} else {
span = []int32{start[0], start[1], end[0], end[1]}
}
t.loc = &dpb.SourceCodeInfo_Location{
Path: path,
Span: span,
}
}
}
func (t *locTrie) countLocs() int {
count := 0
if t.loc != nil {
count = 1
}
for _, ch := range t.children {
count += ch.countLocs()
}
return count
}
func (t *locTrie) aggregate(dest *[]*dpb.SourceCodeInfo_Location) {
if t.loc != nil {
*dest = append(*dest, t.loc)
}
for _, child := range t.children {
child.aggregate(dest)
}
}
func getSpanPositions(span []int32) (start, end []int32) {
start = span[:2]
if len(span) == 3 {
end = []int32{span[0], span[2]}
} else {
end = span[2:]
}
return
}
func compareSlice(a, b []int32) int {
end := len(a)
if len(b) < end {
end = len(b)
}
for i := 0; i < end; i++ {
if a[i] < b[i] {
return -1
}
if a[i] > b[i] {
return 1
}
}
if len(a) < len(b) {
return -1
}
if len(a) > len(b) {
return 1
}
return 0
}