comment.go (4107B)
1 // Copyright 2025 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package astutil 6 7 import ( 8 "go/ast" 9 "go/token" 10 "iter" 11 "sort" 12 "strings" 13 ) 14 15 // Deprecation returns the paragraph of the doc comment that starts with the 16 // conventional "Deprecation: " marker, as defined by 17 // https://go.dev/wiki/Deprecated, or "" if the documented symbol is not 18 // deprecated. 19 func Deprecation(doc *ast.CommentGroup) string { 20 for p := range strings.SplitSeq(doc.Text(), "\n\n") { 21 // There is still some ambiguity for deprecation message. This function 22 // only returns the paragraph introduced by "Deprecated: ". More 23 // information related to the deprecation may follow in additional 24 // paragraphs, but the deprecation message should be able to stand on 25 // its own. See golang/go#38743. 26 if strings.HasPrefix(p, "Deprecated: ") { 27 return p 28 } 29 } 30 return "" 31 } 32 33 // -- plundered from the future (CL 605517, issue #68021) -- 34 35 // TODO(adonovan): replace with ast.Directive in go1.26 (#68021). 36 // Beware of our local mods to handle analysistest 37 // "want" comments on the same line. 38 39 // A directive is a comment line with special meaning to the Go 40 // toolchain or another tool. It has the form: 41 // 42 // //tool:name args 43 // 44 // The "tool:" portion is missing for the three directives named 45 // line, extern, and export. 46 // 47 // See https://go.dev/doc/comment#Syntax for details of Go comment 48 // syntax and https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives 49 // for details of directives used by the Go compiler. 50 type Directive struct { 51 Pos token.Pos // of preceding "//" 52 Tool string 53 Name string 54 Args string // may contain internal spaces 55 } 56 57 // isDirective reports whether c is a comment directive. 58 // This code is also in go/printer. 59 func isDirective(c string) bool { 60 // "//line " is a line directive. 61 // "//extern " is for gccgo. 62 // "//export " is for cgo. 63 // (The // has been removed.) 64 if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") { 65 return true 66 } 67 68 // "//[a-z0-9]+:[a-z0-9]" 69 // (The // has been removed.) 70 colon := strings.Index(c, ":") 71 if colon <= 0 || colon+1 >= len(c) { 72 return false 73 } 74 for i := 0; i <= colon+1; i++ { 75 if i == colon { 76 continue 77 } 78 b := c[i] 79 if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') { 80 return false 81 } 82 } 83 return true 84 } 85 86 // Directives returns the directives within the comment. 87 func Directives(g *ast.CommentGroup) (res []*Directive) { 88 if g != nil { 89 // Avoid (*ast.CommentGroup).Text() as it swallows directives. 90 for _, c := range g.List { 91 if len(c.Text) > 2 && 92 c.Text[1] == '/' && 93 c.Text[2] != ' ' && 94 isDirective(c.Text[2:]) { 95 96 tool, nameargs, ok := strings.Cut(c.Text[2:], ":") 97 if !ok { 98 // Must be one of {line,extern,export}. 99 tool, nameargs = "", tool 100 } 101 name, args, _ := strings.Cut(nameargs, " ") // tab?? 102 // Permit an additional line comment after the args, chiefly to support 103 // [golang.org/x/tools/go/analysis/analysistest]. 104 args, _, _ = strings.Cut(args, "//") 105 res = append(res, &Directive{ 106 Pos: c.Slash, 107 Tool: tool, 108 Name: name, 109 Args: strings.TrimSpace(args), 110 }) 111 } 112 } 113 } 114 return 115 } 116 117 // Comments returns an iterator over the comments overlapping the specified interval. 118 // Comments are sorted by position in the file, so we can use binary search. 119 func Comments(file *ast.File, start, end token.Pos) iter.Seq[*ast.Comment] { 120 return func(yield func(*ast.Comment) bool) { 121 // Find the first comment group that overlaps the range. 122 i := sort.Search(len(file.Comments), func(i int) bool { 123 return file.Comments[i].End() >= start 124 }) 125 for _, cg := range file.Comments[i:] { 126 if cg.Pos() > end { 127 return 128 } 129 // Find the first comment in the group that overlaps the range. 130 j := sort.Search(len(cg.List), func(j int) bool { 131 return cg.List[j].End() >= start 132 }) 133 for _, co := range cg.List[j:] { 134 if co.Pos() > end { 135 return 136 } 137 if !yield(co) { 138 return 139 } 140 } 141 } 142 } 143 }