github.com/jhump/protocompile@v0.0.0-20221021153901-4f6f732835e8/sourceinfo/source_code_info.go (about) 1 // Package sourceinfo contains the logic for computing source code info for a 2 // file descriptor. 3 // 4 // The inputs to the computation are an AST for a file as well as the index of 5 // interpreted options for that file. 6 package sourceinfo 7 8 import ( 9 "bytes" 10 "strings" 11 12 "google.golang.org/protobuf/proto" 13 "google.golang.org/protobuf/types/descriptorpb" 14 15 "github.com/jhump/protocompile/ast" 16 "github.com/jhump/protocompile/internal" 17 "github.com/jhump/protocompile/options" 18 ) 19 20 // GenerateSourceInfo generates source code info for the given AST. If the given 21 // opts is present, it can generate source code info for interpreted options. 22 // Otherwise, any options in the AST will get source code info as uninterpreted 23 // options. 24 func GenerateSourceInfo(file *ast.FileNode, opts options.Index) *descriptorpb.SourceCodeInfo { 25 if file == nil { 26 return nil 27 } 28 29 sci := sourceCodeInfo{file: file, commentsUsed: map[ast.SourcePos]struct{}{}} 30 path := make([]int32, 0, 10) 31 32 sci.newLocWithoutComments(file, nil) 33 34 if file.Syntax != nil { 35 sci.newLoc(file.Syntax, append(path, internal.File_syntaxTag)) 36 } 37 38 var depIndex, optIndex, msgIndex, enumIndex, extendIndex, svcIndex int32 39 40 for _, child := range file.Decls { 41 switch child := child.(type) { 42 case *ast.ImportNode: 43 sci.newLoc(child, append(path, internal.File_dependencyTag, depIndex)) 44 depIndex++ 45 case *ast.PackageNode: 46 sci.newLoc(child, append(path, internal.File_packageTag)) 47 case *ast.OptionNode: 48 generateSourceCodeInfoForOption(opts, &sci, child, false, &optIndex, append(path, internal.File_optionsTag)) 49 case *ast.MessageNode: 50 generateSourceCodeInfoForMessage(opts, &sci, child, nil, append(path, internal.File_messagesTag, msgIndex)) 51 msgIndex++ 52 case *ast.EnumNode: 53 generateSourceCodeInfoForEnum(opts, &sci, child, append(path, internal.File_enumsTag, enumIndex)) 54 enumIndex++ 55 case *ast.ExtendNode: 56 generateSourceCodeInfoForExtensions(opts, &sci, child, &extendIndex, &msgIndex, append(path, internal.File_extensionsTag), append(dup(path), internal.File_messagesTag)) 57 case *ast.ServiceNode: 58 generateSourceCodeInfoForService(opts, &sci, child, append(path, internal.File_servicesTag, svcIndex)) 59 svcIndex++ 60 } 61 } 62 63 return &descriptorpb.SourceCodeInfo{Location: sci.locs} 64 } 65 66 func generateSourceCodeInfoForOption(opts options.Index, sci *sourceCodeInfo, n *ast.OptionNode, compact bool, uninterpIndex *int32, path []int32) { 67 if !compact { 68 sci.newLocWithoutComments(n, path) 69 } 70 subPath := opts[n] 71 if len(subPath) > 0 { 72 p := path 73 if subPath[0] == -1 { 74 // used by "default" and "json_name" field pseudo-options 75 // to attribute path to parent element (since those are 76 // stored directly on the descriptor, not its options) 77 p = make([]int32, len(path)-1) 78 copy(p, path) 79 subPath = subPath[1:] 80 } 81 sci.newLoc(n, append(p, subPath...)) 82 return 83 } 84 85 // it's an uninterpreted option 86 optPath := append(path, internal.UninterpretedOptionsTag, *uninterpIndex) 87 *uninterpIndex++ 88 sci.newLoc(n, optPath) 89 var valTag int32 90 switch n.Val.(type) { 91 case ast.IdentValueNode: 92 valTag = internal.Uninterpreted_identTag 93 case *ast.NegativeIntLiteralNode: 94 valTag = internal.Uninterpreted_negIntTag 95 case ast.IntValueNode: 96 valTag = internal.Uninterpreted_posIntTag 97 case ast.FloatValueNode: 98 valTag = internal.Uninterpreted_doubleTag 99 case ast.StringValueNode: 100 valTag = internal.Uninterpreted_stringTag 101 case *ast.MessageLiteralNode: 102 valTag = internal.Uninterpreted_aggregateTag 103 } 104 if valTag != 0 { 105 sci.newLoc(n.Val, append(optPath, valTag)) 106 } 107 for j, nn := range n.Name.Parts { 108 optNmPath := append(optPath, internal.Uninterpreted_nameTag, int32(j)) 109 sci.newLoc(nn, optNmPath) 110 sci.newLoc(nn.Name, append(optNmPath, internal.UninterpretedName_nameTag)) 111 } 112 } 113 114 func generateSourceCodeInfoForMessage(opts options.Index, sci *sourceCodeInfo, n ast.MessageDeclNode, fieldPath []int32, path []int32) { 115 sci.newLoc(n, path) 116 117 var decls []ast.MessageElement 118 switch n := n.(type) { 119 case *ast.MessageNode: 120 decls = n.Decls 121 case *ast.GroupNode: 122 decls = n.Decls 123 case *ast.MapFieldNode: 124 // map entry so nothing else to do 125 return 126 } 127 128 sci.newLoc(n.MessageName(), append(path, internal.Message_nameTag)) 129 // matching protoc, which emits the corresponding field type name (for group fields) 130 // right after the source location for the group message name 131 if fieldPath != nil { 132 sci.newLoc(n.MessageName(), append(fieldPath, internal.Field_typeNameTag)) 133 } 134 135 var optIndex, fieldIndex, oneOfIndex, extendIndex, nestedMsgIndex int32 136 var nestedEnumIndex, extRangeIndex, reservedRangeIndex, reservedNameIndex int32 137 for _, child := range decls { 138 switch child := child.(type) { 139 case *ast.OptionNode: 140 generateSourceCodeInfoForOption(opts, sci, child, false, &optIndex, append(path, internal.Message_optionsTag)) 141 case *ast.FieldNode: 142 generateSourceCodeInfoForField(opts, sci, child, append(path, internal.Message_fieldsTag, fieldIndex)) 143 fieldIndex++ 144 case *ast.GroupNode: 145 fldPath := append(path, internal.Message_fieldsTag, fieldIndex) 146 generateSourceCodeInfoForField(opts, sci, child, fldPath) 147 fieldIndex++ 148 generateSourceCodeInfoForMessage(opts, sci, child, fldPath, append(dup(path), internal.Message_nestedMessagesTag, nestedMsgIndex)) 149 nestedMsgIndex++ 150 case *ast.MapFieldNode: 151 generateSourceCodeInfoForField(opts, sci, child, append(path, internal.Message_fieldsTag, fieldIndex)) 152 fieldIndex++ 153 nestedMsgIndex++ 154 case *ast.OneOfNode: 155 generateSourceCodeInfoForOneOf(opts, sci, child, &fieldIndex, &nestedMsgIndex, append(path, internal.Message_fieldsTag), append(dup(path), internal.Message_nestedMessagesTag), append(dup(path), internal.Message_oneOfsTag, oneOfIndex)) 156 oneOfIndex++ 157 case *ast.MessageNode: 158 generateSourceCodeInfoForMessage(opts, sci, child, nil, append(path, internal.Message_nestedMessagesTag, nestedMsgIndex)) 159 nestedMsgIndex++ 160 case *ast.EnumNode: 161 generateSourceCodeInfoForEnum(opts, sci, child, append(path, internal.Message_enumsTag, nestedEnumIndex)) 162 nestedEnumIndex++ 163 case *ast.ExtendNode: 164 generateSourceCodeInfoForExtensions(opts, sci, child, &extendIndex, &nestedMsgIndex, append(path, internal.Message_extensionsTag), append(dup(path), internal.Message_nestedMessagesTag)) 165 case *ast.ExtensionRangeNode: 166 generateSourceCodeInfoForExtensionRanges(opts, sci, child, &extRangeIndex, append(path, internal.Message_extensionRangeTag)) 167 case *ast.ReservedNode: 168 if len(child.Names) > 0 { 169 resPath := append(path, internal.Message_reservedNameTag) 170 sci.newLoc(child, resPath) 171 for _, rn := range child.Names { 172 sci.newLoc(rn, append(resPath, reservedNameIndex)) 173 reservedNameIndex++ 174 } 175 } 176 if len(child.Ranges) > 0 { 177 resPath := append(path, internal.Message_reservedRangeTag) 178 sci.newLoc(child, resPath) 179 for _, rr := range child.Ranges { 180 generateSourceCodeInfoForReservedRange(sci, rr, append(resPath, reservedRangeIndex)) 181 reservedRangeIndex++ 182 } 183 } 184 } 185 } 186 } 187 188 func generateSourceCodeInfoForEnum(opts options.Index, sci *sourceCodeInfo, n *ast.EnumNode, path []int32) { 189 sci.newLoc(n, path) 190 sci.newLoc(n.Name, append(path, internal.Enum_nameTag)) 191 192 var optIndex, valIndex, reservedNameIndex, reservedRangeIndex int32 193 for _, child := range n.Decls { 194 switch child := child.(type) { 195 case *ast.OptionNode: 196 generateSourceCodeInfoForOption(opts, sci, child, false, &optIndex, append(path, internal.Enum_optionsTag)) 197 case *ast.EnumValueNode: 198 generateSourceCodeInfoForEnumValue(opts, sci, child, append(path, internal.Enum_valuesTag, valIndex)) 199 valIndex++ 200 case *ast.ReservedNode: 201 if len(child.Names) > 0 { 202 resPath := append(path, internal.Enum_reservedNameTag) 203 sci.newLoc(child, resPath) 204 for _, rn := range child.Names { 205 sci.newLoc(rn, append(resPath, reservedNameIndex)) 206 reservedNameIndex++ 207 } 208 } 209 if len(child.Ranges) > 0 { 210 resPath := append(path, internal.Enum_reservedRangeTag) 211 sci.newLoc(child, resPath) 212 for _, rr := range child.Ranges { 213 generateSourceCodeInfoForReservedRange(sci, rr, append(resPath, reservedRangeIndex)) 214 reservedRangeIndex++ 215 } 216 } 217 } 218 } 219 } 220 221 func generateSourceCodeInfoForEnumValue(opts options.Index, sci *sourceCodeInfo, n *ast.EnumValueNode, path []int32) { 222 sci.newLoc(n, path) 223 sci.newLoc(n.Name, append(path, internal.EnumVal_nameTag)) 224 sci.newLoc(n.Number, append(path, internal.EnumVal_numberTag)) 225 226 // enum value options 227 if n.Options != nil { 228 optsPath := append(path, internal.EnumVal_optionsTag) 229 sci.newLoc(n.Options, optsPath) 230 var optIndex int32 231 for _, opt := range n.Options.GetElements() { 232 generateSourceCodeInfoForOption(opts, sci, opt, true, &optIndex, optsPath) 233 } 234 } 235 } 236 237 func generateSourceCodeInfoForReservedRange(sci *sourceCodeInfo, n *ast.RangeNode, path []int32) { 238 sci.newLoc(n, path) 239 sci.newLoc(n.StartVal, append(path, internal.ReservedRange_startTag)) 240 if n.EndVal != nil { 241 sci.newLoc(n.EndVal, append(path, internal.ReservedRange_endTag)) 242 } else if n.Max != nil { 243 sci.newLoc(n.Max, append(path, internal.ReservedRange_endTag)) 244 } 245 } 246 247 func generateSourceCodeInfoForExtensions(opts options.Index, sci *sourceCodeInfo, n *ast.ExtendNode, extendIndex, msgIndex *int32, extendPath, msgPath []int32) { 248 sci.newLoc(n, extendPath) 249 for _, decl := range n.Decls { 250 switch decl := decl.(type) { 251 case *ast.FieldNode: 252 generateSourceCodeInfoForField(opts, sci, decl, append(extendPath, *extendIndex)) 253 *extendIndex++ 254 case *ast.GroupNode: 255 fldPath := append(extendPath, *extendIndex) 256 generateSourceCodeInfoForField(opts, sci, decl, fldPath) 257 *extendIndex++ 258 generateSourceCodeInfoForMessage(opts, sci, decl, fldPath, append(msgPath, *msgIndex)) 259 *msgIndex++ 260 } 261 } 262 } 263 264 func generateSourceCodeInfoForOneOf(opts options.Index, sci *sourceCodeInfo, n *ast.OneOfNode, fieldIndex, nestedMsgIndex *int32, fieldPath, nestedMsgPath, oneOfPath []int32) { 265 sci.newLoc(n, oneOfPath) 266 sci.newLoc(n.Name, append(oneOfPath, internal.OneOf_nameTag)) 267 268 var optIndex int32 269 for _, child := range n.Decls { 270 switch child := child.(type) { 271 case *ast.OptionNode: 272 generateSourceCodeInfoForOption(opts, sci, child, false, &optIndex, append(oneOfPath, internal.OneOf_optionsTag)) 273 case *ast.FieldNode: 274 generateSourceCodeInfoForField(opts, sci, child, append(fieldPath, *fieldIndex)) 275 *fieldIndex++ 276 case *ast.GroupNode: 277 fldPath := append(fieldPath, *fieldIndex) 278 generateSourceCodeInfoForField(opts, sci, child, fldPath) 279 *fieldIndex++ 280 generateSourceCodeInfoForMessage(opts, sci, child, fldPath, append(nestedMsgPath, *nestedMsgIndex)) 281 *nestedMsgIndex++ 282 } 283 } 284 } 285 286 func generateSourceCodeInfoForField(opts options.Index, sci *sourceCodeInfo, n ast.FieldDeclNode, path []int32) { 287 var fieldType string 288 if f, ok := n.(*ast.FieldNode); ok { 289 fieldType = string(f.FldType.AsIdentifier()) 290 } 291 292 if n.GetGroupKeyword() != nil { 293 // comments will appear on group message 294 sci.newLocWithoutComments(n, path) 295 if n.FieldExtendee() != nil { 296 sci.newLoc(n.FieldExtendee(), append(path, internal.Field_extendeeTag)) 297 } 298 if n.FieldLabel() != nil { 299 // no comments here either (label is first token for group, so we want 300 // to leave the comments to be associated with the group message instead) 301 sci.newLocWithoutComments(n.FieldLabel(), append(path, internal.Field_labelTag)) 302 } 303 sci.newLoc(n.FieldType(), append(path, internal.Field_typeTag)) 304 // let the name comments be attributed to the group name 305 sci.newLocWithoutComments(n.FieldName(), append(path, internal.Field_nameTag)) 306 } else { 307 sci.newLoc(n, path) 308 if n.FieldExtendee() != nil { 309 sci.newLoc(n.FieldExtendee(), append(path, internal.Field_extendeeTag)) 310 } 311 if n.FieldLabel() != nil { 312 sci.newLoc(n.FieldLabel(), append(path, internal.Field_labelTag)) 313 } 314 var tag int32 315 if _, isScalar := internal.FieldTypes[fieldType]; isScalar { 316 tag = internal.Field_typeTag 317 } else { 318 // this is a message or an enum, so attribute type location 319 // to the type name field 320 tag = internal.Field_typeNameTag 321 } 322 sci.newLoc(n.FieldType(), append(path, tag)) 323 sci.newLoc(n.FieldName(), append(path, internal.Field_nameTag)) 324 } 325 sci.newLoc(n.FieldTag(), append(path, internal.Field_numberTag)) 326 327 if n.GetOptions() != nil { 328 optsPath := append(path, internal.Field_optionsTag) 329 sci.newLoc(n.GetOptions(), optsPath) 330 var optIndex int32 331 for _, opt := range n.GetOptions().GetElements() { 332 generateSourceCodeInfoForOption(opts, sci, opt, true, &optIndex, optsPath) 333 } 334 } 335 } 336 337 func generateSourceCodeInfoForExtensionRanges(opts options.Index, sci *sourceCodeInfo, n *ast.ExtensionRangeNode, extRangeIndex *int32, path []int32) { 338 sci.newLoc(n, path) 339 for _, child := range n.Ranges { 340 path := append(path, *extRangeIndex) 341 *extRangeIndex++ 342 sci.newLoc(child, path) 343 sci.newLoc(child.StartVal, append(path, internal.ExtensionRange_startTag)) 344 if child.EndVal != nil { 345 sci.newLoc(child.EndVal, append(path, internal.ExtensionRange_endTag)) 346 } else if child.Max != nil { 347 sci.newLoc(child.Max, append(path, internal.ExtensionRange_endTag)) 348 } 349 if n.Options != nil { 350 optsPath := append(path, internal.ExtensionRange_optionsTag) 351 sci.newLoc(n.Options, optsPath) 352 var optIndex int32 353 for _, opt := range n.Options.GetElements() { 354 generateSourceCodeInfoForOption(opts, sci, opt, true, &optIndex, optsPath) 355 } 356 } 357 } 358 } 359 360 func generateSourceCodeInfoForService(opts options.Index, sci *sourceCodeInfo, n *ast.ServiceNode, path []int32) { 361 sci.newLoc(n, path) 362 sci.newLoc(n.Name, append(path, internal.Service_nameTag)) 363 var optIndex, rpcIndex int32 364 for _, child := range n.Decls { 365 switch child := child.(type) { 366 case *ast.OptionNode: 367 generateSourceCodeInfoForOption(opts, sci, child, false, &optIndex, append(path, internal.Service_optionsTag)) 368 case *ast.RPCNode: 369 generateSourceCodeInfoForMethod(opts, sci, child, append(path, internal.Service_methodsTag, rpcIndex)) 370 rpcIndex++ 371 } 372 } 373 } 374 375 func generateSourceCodeInfoForMethod(opts options.Index, sci *sourceCodeInfo, n *ast.RPCNode, path []int32) { 376 sci.newLoc(n, path) 377 sci.newLoc(n.Name, append(path, internal.Method_nameTag)) 378 if n.Input.Stream != nil { 379 sci.newLoc(n.Input.Stream, append(path, internal.Method_inputStreamTag)) 380 } 381 sci.newLoc(n.Input.MessageType, append(path, internal.Method_inputTag)) 382 if n.Output.Stream != nil { 383 sci.newLoc(n.Output.Stream, append(path, internal.Method_outputStreamTag)) 384 } 385 sci.newLoc(n.Output.MessageType, append(path, internal.Method_outputTag)) 386 387 optsPath := append(path, internal.Method_optionsTag) 388 var optIndex int32 389 for _, decl := range n.Decls { 390 if opt, ok := decl.(*ast.OptionNode); ok { 391 generateSourceCodeInfoForOption(opts, sci, opt, false, &optIndex, optsPath) 392 } 393 } 394 } 395 396 type sourceCodeInfo struct { 397 file *ast.FileNode 398 locs []*descriptorpb.SourceCodeInfo_Location 399 commentsUsed map[ast.SourcePos]struct{} 400 } 401 402 func (sci *sourceCodeInfo) newLocWithoutComments(n ast.Node, path []int32) { 403 dup := make([]int32, len(path)) 404 copy(dup, path) 405 info := sci.file.NodeInfo(n) 406 sci.locs = append(sci.locs, &descriptorpb.SourceCodeInfo_Location{ 407 Path: dup, 408 Span: makeSpan(info.Start(), info.End()), 409 }) 410 } 411 412 func (sci *sourceCodeInfo) newLoc(n ast.Node, path []int32) { 413 nodeInfo := sci.file.NodeInfo(n) 414 leadingComments := nodeInfo.LeadingComments() 415 trailingComments := nodeInfo.TrailingComments() 416 if sci.commentUsed(leadingComments) { 417 leadingComments = ast.Comments{} 418 } 419 if sci.commentUsed(trailingComments) { 420 trailingComments = ast.Comments{} 421 } 422 detached := groupComments(leadingComments) 423 var trail *string 424 if str, ok := combineComments(trailingComments, 0, trailingComments.Len()); ok { 425 trail = proto.String(str) 426 } 427 var lead *string 428 if leadingComments.Len() > 0 && leadingComments.Index(leadingComments.Len()-1).End().Line >= nodeInfo.Start().Line-1 { 429 lead = proto.String(detached[len(detached)-1]) 430 detached = detached[:len(detached)-1] 431 } 432 dup := make([]int32, len(path)) 433 copy(dup, path) 434 sci.locs = append(sci.locs, &descriptorpb.SourceCodeInfo_Location{ 435 LeadingDetachedComments: detached, 436 LeadingComments: lead, 437 TrailingComments: trail, 438 Path: dup, 439 Span: makeSpan(nodeInfo.Start(), nodeInfo.End()), 440 }) 441 } 442 443 func makeSpan(start, end ast.SourcePos) []int32 { 444 if start.Line == end.Line { 445 return []int32{int32(start.Line) - 1, int32(start.Col) - 1, int32(end.Col) - 1} 446 } 447 return []int32{int32(start.Line) - 1, int32(start.Col) - 1, int32(end.Line) - 1, int32(end.Col) - 1} 448 } 449 450 func (sci *sourceCodeInfo) commentUsed(c ast.Comments) bool { 451 if c.Len() == 0 { 452 return false 453 } 454 pos := c.Index(0).Start() 455 if _, ok := sci.commentsUsed[pos]; ok { 456 return true 457 } 458 459 sci.commentsUsed[pos] = struct{}{} 460 return false 461 } 462 463 func groupComments(comments ast.Comments) []string { 464 if comments.Len() == 0 { 465 return nil 466 } 467 468 var groups []string 469 singleLineStyle := comments.Index(0).RawText()[:2] == "//" 470 line := comments.Index(0).End().Line 471 start := 0 472 for i := 1; i < comments.Len(); i++ { 473 c := comments.Index(i) 474 prevSingleLine := singleLineStyle 475 singleLineStyle = strings.HasPrefix(c.RawText(), "//") 476 if !singleLineStyle || prevSingleLine != singleLineStyle || c.Start().Line > line+1 { 477 // new group! 478 if str, ok := combineComments(comments, start, i); ok { 479 groups = append(groups, str) 480 } 481 start = i 482 } 483 line = c.End().Line 484 } 485 // don't forget last group 486 if str, ok := combineComments(comments, start, comments.Len()); ok { 487 groups = append(groups, str) 488 } 489 return groups 490 } 491 492 func combineComments(comments ast.Comments, start, end int) (string, bool) { 493 if start >= end { 494 return "", false 495 } 496 var buf bytes.Buffer 497 for i := start; i < end; i++ { 498 c := comments.Index(i) 499 txt := c.RawText() 500 if txt[:2] == "//" { 501 buf.WriteString(txt[2:]) 502 } else { 503 lines := strings.Split(txt[2:len(txt)-2], "\n") 504 first := true 505 for _, l := range lines { 506 if first { 507 first = false 508 } else { 509 buf.WriteByte('\n') 510 } 511 512 // strip a prefix of whitespace followed by '*' 513 j := 0 514 for j < len(l) { 515 if l[j] != ' ' && l[j] != '\t' { 516 break 517 } 518 j++ 519 } 520 if j == len(l) { 521 l = "" 522 } else if l[j] == '*' { 523 l = l[j+1:] 524 } else if j > 0 { 525 l = " " + l[j:] 526 } 527 528 buf.WriteString(l) 529 } 530 } 531 } 532 return buf.String(), true 533 } 534 535 func dup(p []int32) []int32 { 536 return append(([]int32)(nil), p...) 537 }