144 lines
4.0 KiB
Go
144 lines
4.0 KiB
Go
// Copyright 2025 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 astutil
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/token"
|
|
"iter"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Deprecation returns the paragraph of the doc comment that starts with the
|
|
// conventional "Deprecation: " marker, as defined by
|
|
// https://go.dev/wiki/Deprecated, or "" if the documented symbol is not
|
|
// deprecated.
|
|
func Deprecation(doc *ast.CommentGroup) string {
|
|
for p := range strings.SplitSeq(doc.Text(), "\n\n") {
|
|
// There is still some ambiguity for deprecation message. This function
|
|
// only returns the paragraph introduced by "Deprecated: ". More
|
|
// information related to the deprecation may follow in additional
|
|
// paragraphs, but the deprecation message should be able to stand on
|
|
// its own. See golang/go#38743.
|
|
if strings.HasPrefix(p, "Deprecated: ") {
|
|
return p
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// -- plundered from the future (CL 605517, issue #68021) --
|
|
|
|
// TODO(adonovan): replace with ast.Directive in go1.26 (#68021).
|
|
// Beware of our local mods to handle analysistest
|
|
// "want" comments on the same line.
|
|
|
|
// A directive is a comment line with special meaning to the Go
|
|
// toolchain or another tool. It has the form:
|
|
//
|
|
// //tool:name args
|
|
//
|
|
// The "tool:" portion is missing for the three directives named
|
|
// line, extern, and export.
|
|
//
|
|
// See https://go.dev/doc/comment#Syntax for details of Go comment
|
|
// syntax and https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives
|
|
// for details of directives used by the Go compiler.
|
|
type Directive struct {
|
|
Pos token.Pos // of preceding "//"
|
|
Tool string
|
|
Name string
|
|
Args string // may contain internal spaces
|
|
}
|
|
|
|
// isDirective reports whether c is a comment directive.
|
|
// This code is also in go/printer.
|
|
func isDirective(c string) bool {
|
|
// "//line " is a line directive.
|
|
// "//extern " is for gccgo.
|
|
// "//export " is for cgo.
|
|
// (The // has been removed.)
|
|
if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") {
|
|
return true
|
|
}
|
|
|
|
// "//[a-z0-9]+:[a-z0-9]"
|
|
// (The // has been removed.)
|
|
colon := strings.Index(c, ":")
|
|
if colon <= 0 || colon+1 >= len(c) {
|
|
return false
|
|
}
|
|
for i := 0; i <= colon+1; i++ {
|
|
if i == colon {
|
|
continue
|
|
}
|
|
b := c[i]
|
|
if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Directives returns the directives within the comment.
|
|
func Directives(g *ast.CommentGroup) (res []*Directive) {
|
|
if g != nil {
|
|
// Avoid (*ast.CommentGroup).Text() as it swallows directives.
|
|
for _, c := range g.List {
|
|
if len(c.Text) > 2 &&
|
|
c.Text[1] == '/' &&
|
|
c.Text[2] != ' ' &&
|
|
isDirective(c.Text[2:]) {
|
|
|
|
tool, nameargs, ok := strings.Cut(c.Text[2:], ":")
|
|
if !ok {
|
|
// Must be one of {line,extern,export}.
|
|
tool, nameargs = "", tool
|
|
}
|
|
name, args, _ := strings.Cut(nameargs, " ") // tab??
|
|
// Permit an additional line comment after the args, chiefly to support
|
|
// [golang.org/x/tools/go/analysis/analysistest].
|
|
args, _, _ = strings.Cut(args, "//")
|
|
res = append(res, &Directive{
|
|
Pos: c.Slash,
|
|
Tool: tool,
|
|
Name: name,
|
|
Args: strings.TrimSpace(args),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Comments returns an iterator over the comments overlapping the specified interval.
|
|
// Comments are sorted by position in the file, so we can use binary search.
|
|
func Comments(file *ast.File, start, end token.Pos) iter.Seq[*ast.Comment] {
|
|
return func(yield func(*ast.Comment) bool) {
|
|
// Find the first comment group that overlaps the range.
|
|
i := sort.Search(len(file.Comments), func(i int) bool {
|
|
return file.Comments[i].End() >= start
|
|
})
|
|
for _, cg := range file.Comments[i:] {
|
|
if cg.Pos() > end {
|
|
return
|
|
}
|
|
// Find the first comment in the group that overlaps the range.
|
|
j := sort.Search(len(cg.List), func(j int) bool {
|
|
return cg.List[j].End() >= start
|
|
})
|
|
for _, co := range cg.List[j:] {
|
|
if co.Pos() > end {
|
|
return
|
|
}
|
|
if !yield(co) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|