blob: ff3773320148f85c65ac4d06e057802e048dedfa [file] [log] [blame]
package protoparse
import (
"bytes"
"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)
sci.newLocWithoutComments(fn, nil)
if fn.syntax != nil {
sci.newLoc(fn.syntax, append(path, internal.File_syntaxTag))
}
var depIndex, optIndex, msgIndex, enumIndex, extendIndex, svcIndex int32
for _, child := range fn.decls {
switch {
case child.imp != nil:
sci.newLoc(child.imp, append(path, internal.File_dependencyTag, int32(depIndex)))
depIndex++
case child.pkg != nil:
sci.newLoc(child.pkg, append(path, internal.File_packageTag))
case child.option != nil:
r.generateSourceCodeInfoForOption(&sci, child.option, false, &optIndex, append(path, internal.File_optionsTag))
case child.message != nil:
r.generateSourceCodeInfoForMessage(&sci, child.message, nil, append(path, internal.File_messagesTag, msgIndex))
msgIndex++
case child.enum != nil:
r.generateSourceCodeInfoForEnum(&sci, child.enum, append(path, internal.File_enumsTag, enumIndex))
enumIndex++
case child.extend != nil:
r.generateSourceCodeInfoForExtensions(&sci, child.extend, &extendIndex, &msgIndex, append(path, internal.File_extensionsTag), append(dup(path), internal.File_messagesTag))
case child.service != nil:
r.generateSourceCodeInfoForService(&sci, child.service, append(path, internal.File_servicesTag, svcIndex))
svcIndex++
}
}
return &dpb.SourceCodeInfo{Location: sci.locs}
}
func (r *parseResult) generateSourceCodeInfoForOption(sci *sourceCodeInfo, n *optionNode, compact bool, uninterpIndex *int32, path []int32) {
if !compact {
sci.newLocWithoutComments(n, path)
}
subPath := r.interpretedOptions[n]
if len(subPath) > 0 {
p := path
if subPath[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)
subPath = subPath[1:]
}
sci.newLoc(n, append(p, subPath...))
return
}
// it's an uninterpreted option
optPath := append(path, internal.UninterpretedOptionsTag, *uninterpIndex)
*uninterpIndex++
sci.newLoc(n, optPath)
var valTag int32
switch n.val.(type) {
case *compoundIdentNode:
valTag = internal.Uninterpreted_identTag
case *intLiteralNode:
valTag = internal.Uninterpreted_posIntTag
case *compoundIntNode:
valTag = internal.Uninterpreted_negIntTag
case *compoundFloatNode:
valTag = internal.Uninterpreted_doubleTag
case *compoundStringNode:
valTag = internal.Uninterpreted_stringTag
case *aggregateLiteralNode:
valTag = internal.Uninterpreted_aggregateTag
}
if valTag != 0 {
sci.newLoc(n.val, append(optPath, valTag))
}
for j, nn := range n.name.parts {
optNmPath := append(optPath, internal.Uninterpreted_nameTag, int32(j))
sci.newLoc(nn, optNmPath)
sci.newLoc(nn.text, append(optNmPath, internal.UninterpretedName_nameTag))
}
}
func (r *parseResult) generateSourceCodeInfoForMessage(sci *sourceCodeInfo, n msgDecl, fieldPath []int32, path []int32) {
sci.newLoc(n, path)
var decls []*messageElement
switch n := n.(type) {
case *messageNode:
decls = n.decls
case *groupNode:
decls = n.decls
case *mapFieldNode:
// map entry so nothing else to do
return
}
sci.newLoc(n.messageName(), append(path, internal.Message_nameTag))
// matching protoc, which emits the corresponding field type name (for group fields)
// right after the source location for the group message name
if fieldPath != nil {
sci.newLoc(n.messageName(), append(fieldPath, internal.Field_typeNameTag))
}
var optIndex, fieldIndex, oneOfIndex, extendIndex, nestedMsgIndex int32
var nestedEnumIndex, extRangeIndex, reservedRangeIndex, reservedNameIndex int32
for _, child := range decls {
switch {
case child.option != nil:
r.generateSourceCodeInfoForOption(sci, child.option, false, &optIndex, append(path, internal.Message_optionsTag))
case child.field != nil:
r.generateSourceCodeInfoForField(sci, child.field, append(path, internal.Message_fieldsTag, fieldIndex))
fieldIndex++
case child.group != nil:
fldPath := append(path, internal.Message_fieldsTag, fieldIndex)
r.generateSourceCodeInfoForField(sci, child.group, fldPath)
fieldIndex++
r.generateSourceCodeInfoForMessage(sci, child.group, fldPath, append(dup(path), internal.Message_nestedMessagesTag, nestedMsgIndex))
nestedMsgIndex++
case child.mapField != nil:
r.generateSourceCodeInfoForField(sci, child.mapField, append(path, internal.Message_fieldsTag, fieldIndex))
fieldIndex++
case child.oneOf != nil:
r.generateSourceCodeInfoForOneOf(sci, child.oneOf, &fieldIndex, &nestedMsgIndex, append(path, internal.Message_fieldsTag), append(dup(path), internal.Message_nestedMessagesTag), append(dup(path), internal.Message_oneOfsTag, oneOfIndex))
oneOfIndex++
case child.nested != nil:
r.generateSourceCodeInfoForMessage(sci, child.nested, nil, append(path, internal.Message_nestedMessagesTag, nestedMsgIndex))
nestedMsgIndex++
case child.enum != nil:
r.generateSourceCodeInfoForEnum(sci, child.enum, append(path, internal.Message_enumsTag, nestedEnumIndex))
nestedEnumIndex++
case child.extend != nil:
r.generateSourceCodeInfoForExtensions(sci, child.extend, &extendIndex, &nestedMsgIndex, append(path, internal.Message_extensionsTag), append(dup(path), internal.Message_nestedMessagesTag))
case child.extensionRange != nil:
r.generateSourceCodeInfoForExtensionRanges(sci, child.extensionRange, &extRangeIndex, append(path, internal.Message_extensionRangeTag))
case child.reserved != nil:
if len(child.reserved.names) > 0 {
resPath := append(path, internal.Message_reservedNameTag)
sci.newLoc(child.reserved, resPath)
for _, rn := range child.reserved.names {
sci.newLoc(rn, append(resPath, reservedNameIndex))
reservedNameIndex++
}
}
if len(child.reserved.ranges) > 0 {
resPath := append(path, internal.Message_reservedRangeTag)
sci.newLoc(child.reserved, resPath)
for _, rr := range child.reserved.ranges {
r.generateSourceCodeInfoForReservedRange(sci, rr, append(resPath, reservedRangeIndex))
reservedRangeIndex++
}
}
}
}
}
func (r *parseResult) generateSourceCodeInfoForEnum(sci *sourceCodeInfo, n *enumNode, path []int32) {
sci.newLoc(n, path)
sci.newLoc(n.name, append(path, internal.Enum_nameTag))
var optIndex, valIndex, reservedNameIndex, reservedRangeIndex int32
for _, child := range n.decls {
switch {
case child.option != nil:
r.generateSourceCodeInfoForOption(sci, child.option, false, &optIndex, append(path, internal.Enum_optionsTag))
case child.value != nil:
r.generateSourceCodeInfoForEnumValue(sci, child.value, append(path, internal.Enum_valuesTag, valIndex))
valIndex++
case child.reserved != nil:
if len(child.reserved.names) > 0 {
resPath := append(path, internal.Enum_reservedNameTag)
sci.newLoc(child.reserved, resPath)
for _, rn := range child.reserved.names {
sci.newLoc(rn, append(resPath, reservedNameIndex))
reservedNameIndex++
}
}
if len(child.reserved.ranges) > 0 {
resPath := append(path, internal.Enum_reservedRangeTag)
sci.newLoc(child.reserved, resPath)
for _, rr := range child.reserved.ranges {
r.generateSourceCodeInfoForReservedRange(sci, rr, append(resPath, reservedRangeIndex))
reservedRangeIndex++
}
}
}
}
}
func (r *parseResult) generateSourceCodeInfoForEnumValue(sci *sourceCodeInfo, n *enumValueNode, path []int32) {
sci.newLoc(n, path)
sci.newLoc(n.name, append(path, internal.EnumVal_nameTag))
sci.newLoc(n.getNumber(), append(path, internal.EnumVal_numberTag))
// enum value options
if n.options != nil {
optsPath := append(path, internal.EnumVal_optionsTag)
sci.newLoc(n.options, optsPath)
var optIndex int32
for _, opt := range n.options.decls {
r.generateSourceCodeInfoForOption(sci, opt, true, &optIndex, optsPath)
}
}
}
func (r *parseResult) generateSourceCodeInfoForReservedRange(sci *sourceCodeInfo, n *rangeNode, path []int32) {
sci.newLoc(n, path)
sci.newLoc(n.stNode, append(path, internal.ReservedRange_startTag))
if n.stNode != n.enNode {
sci.newLoc(n.enNode, append(path, internal.ReservedRange_endTag))
}
}
func (r *parseResult) generateSourceCodeInfoForExtensions(sci *sourceCodeInfo, n *extendNode, extendIndex, msgIndex *int32, extendPath, msgPath []int32) {
sci.newLoc(n, extendPath)
for _, decl := range n.decls {
switch {
case decl.field != nil:
r.generateSourceCodeInfoForField(sci, decl.field, append(extendPath, *extendIndex))
*extendIndex++
case decl.group != nil:
fldPath := append(extendPath, *extendIndex)
r.generateSourceCodeInfoForField(sci, decl.group, fldPath)
*extendIndex++
r.generateSourceCodeInfoForMessage(sci, decl.group, fldPath, append(msgPath, *msgIndex))
*msgIndex++
}
}
}
func (r *parseResult) generateSourceCodeInfoForOneOf(sci *sourceCodeInfo, n *oneOfNode, fieldIndex, nestedMsgIndex *int32, fieldPath, nestedMsgPath, oneOfPath []int32) {
sci.newLoc(n, oneOfPath)
sci.newLoc(n.name, append(oneOfPath, internal.OneOf_nameTag))
var optIndex int32
for _, child := range n.decls {
switch {
case child.option != nil:
r.generateSourceCodeInfoForOption(sci, child.option, false, &optIndex, append(oneOfPath, internal.OneOf_optionsTag))
case child.field != nil:
r.generateSourceCodeInfoForField(sci, child.field, append(fieldPath, *fieldIndex))
*fieldIndex++
case child.group != nil:
fldPath := append(fieldPath, *fieldIndex)
r.generateSourceCodeInfoForField(sci, child.group, fldPath)
*fieldIndex++
r.generateSourceCodeInfoForMessage(sci, child.group, fldPath, append(nestedMsgPath, *nestedMsgIndex))
*nestedMsgIndex++
}
}
}
func (r *parseResult) generateSourceCodeInfoForField(sci *sourceCodeInfo, n fieldDecl, path []int32) {
isGroup := false
var opts *compactOptionsNode
var extendee *extendNode
var fieldType string
switch n := n.(type) {
case *fieldNode:
opts = n.options
extendee = n.extendee
fieldType = n.fldType.val
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
}
if isGroup {
// comments will appear on group message
sci.newLocWithoutComments(n, path)
if extendee != nil {
sci.newLoc(extendee.extendee, append(path, internal.Field_extendeeTag))
}
if n.fieldLabel() != nil {
// no comments here either (label is first token for group, so we want
// to leave the comments to be associated with the group message instead)
sci.newLocWithoutComments(n.fieldLabel(), append(path, internal.Field_labelTag))
}
sci.newLoc(n.fieldType(), append(path, internal.Field_typeTag))
// let the name comments be attributed to the group name
sci.newLocWithoutComments(n.fieldName(), append(path, internal.Field_nameTag))
} else {
sci.newLoc(n, path)
if extendee != nil {
sci.newLoc(extendee.extendee, append(path, internal.Field_extendeeTag))
}
if n.fieldLabel() != nil {
sci.newLoc(n.fieldLabel(), append(path, internal.Field_labelTag))
}
n.fieldType()
var tag int32
if _, isScalar := fieldTypes[fieldType]; isScalar {
tag = internal.Field_typeTag
} else {
// this is a message or an enum, so attribute type location
// to the type name field
tag = internal.Field_typeNameTag
}
sci.newLoc(n.fieldType(), append(path, tag))
sci.newLoc(n.fieldName(), append(path, internal.Field_nameTag))
}
sci.newLoc(n.fieldTag(), append(path, internal.Field_numberTag))
if opts != nil {
optsPath := append(path, internal.Field_optionsTag)
sci.newLoc(opts, optsPath)
var optIndex int32
for _, opt := range opts.decls {
r.generateSourceCodeInfoForOption(sci, opt, true, &optIndex, optsPath)
}
}
}
func (r *parseResult) generateSourceCodeInfoForExtensionRanges(sci *sourceCodeInfo, n *extensionRangeNode, extRangeIndex *int32, path []int32) {
sci.newLoc(n, path)
for _, child := range n.ranges {
path := append(path, *extRangeIndex)
*extRangeIndex++
sci.newLoc(child, path)
sci.newLoc(child.stNode, append(path, internal.ExtensionRange_startTag))
if child.stNode != child.enNode {
sci.newLoc(child.enNode, append(path, internal.ExtensionRange_endTag))
}
if n.options != nil {
optsPath := append(path, internal.ExtensionRange_optionsTag)
sci.newLoc(n.options, optsPath)
var optIndex int32
for _, opt := range n.options.decls {
r.generateSourceCodeInfoForOption(sci, opt, true, &optIndex, optsPath)
}
}
}
}
func (r *parseResult) generateSourceCodeInfoForService(sci *sourceCodeInfo, n *serviceNode, path []int32) {
sci.newLoc(n, path)
sci.newLoc(n.name, append(path, internal.Service_nameTag))
var optIndex, rpcIndex int32
for _, child := range n.decls {
switch {
case child.option != nil:
r.generateSourceCodeInfoForOption(sci, child.option, false, &optIndex, append(path, internal.Service_optionsTag))
case child.rpc != nil:
r.generateSourceCodeInfoForMethod(sci, child.rpc, append(path, internal.Service_methodsTag, rpcIndex))
rpcIndex++
}
}
}
func (r *parseResult) generateSourceCodeInfoForMethod(sci *sourceCodeInfo, n *methodNode, path []int32) {
sci.newLoc(n, path)
sci.newLoc(n.name, append(path, internal.Method_nameTag))
if n.input.streamKeyword != nil {
sci.newLoc(n.input.streamKeyword, append(path, internal.Method_inputStreamTag))
}
sci.newLoc(n.input.msgType, append(path, internal.Method_inputTag))
if n.output.streamKeyword != nil {
sci.newLoc(n.output.streamKeyword, append(path, internal.Method_outputStreamTag))
}
sci.newLoc(n.output.msgType, append(path, internal.Method_outputTag))
optsPath := append(path, internal.Method_optionsTag)
var optIndex int32
for _, opt := range n.options {
r.generateSourceCodeInfoForOption(sci, opt, false, &optIndex, optsPath)
}
}
type sourceCodeInfo struct {
locs []*dpb.SourceCodeInfo_Location
commentsUsed map[*comment]struct{}
}
func (sci *sourceCodeInfo) newLocWithoutComments(n node, path []int32) {
dup := make([]int32, len(path))
copy(dup, path)
sci.locs = append(sci.locs, &dpb.SourceCodeInfo_Location{
Path: dup,
Span: makeSpan(n.start(), n.end()),
})
}
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)
var trail *string
if str, ok := combineComments(trailingComments); ok {
trail = proto.String(str)
}
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)
sci.locs = append(sci.locs, &dpb.SourceCodeInfo_Location{
LeadingDetachedComments: detached,
LeadingComments: lead,
TrailingComments: trail,
Path: dup,
Span: makeSpan(n.start(), n.end()),
})
}
func makeSpan(start, end *SourcePos) []int32 {
if start.Line == end.Line {
return []int32{int32(start.Line) - 1, int32(start.Col) - 1, int32(end.Col) - 1}
}
return []int32{int32(start.Line) - 1, int32(start.Col) - 1, int32(end.Line) - 1, int32(end.Col) - 1}
}
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!
if str, ok := combineComments(comments[start:i]); ok {
groups = append(groups, str)
}
start = i
}
line = c.end.Line
}
// don't forget last group
if str, ok := combineComments(comments[start:]); ok {
groups = append(groups, str)
}
return groups
}
func combineComments(comments []comment) (string, bool) {
if len(comments) == 0 {
return "", false
}
var buf bytes.Buffer
for _, c := range comments {
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 buf.String(), true
}
func dup(p []int32) []int32 {
return append(([]int32)(nil), p...)
}