114 lines
3.4 KiB
Go
114 lines
3.4 KiB
Go
|
// Copyright 2023 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 analysisinternal
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"go/parser"
|
||
|
"go/token"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// MustExtractDoc is like [ExtractDoc] but it panics on error.
|
||
|
//
|
||
|
// To use, define a doc.go file such as:
|
||
|
//
|
||
|
// // Package halting defines an analyzer of program termination.
|
||
|
// //
|
||
|
// // # Analyzer halting
|
||
|
// //
|
||
|
// // halting: reports whether execution will halt.
|
||
|
// //
|
||
|
// // The halting analyzer reports a diagnostic for functions
|
||
|
// // that run forever. To suppress the diagnostics, try inserting
|
||
|
// // a 'break' statement into each loop.
|
||
|
// package halting
|
||
|
//
|
||
|
// import _ "embed"
|
||
|
//
|
||
|
// //go:embed doc.go
|
||
|
// var doc string
|
||
|
//
|
||
|
// And declare your analyzer as:
|
||
|
//
|
||
|
// var Analyzer = &analysis.Analyzer{
|
||
|
// Name: "halting",
|
||
|
// Doc: analysisutil.MustExtractDoc(doc, "halting"),
|
||
|
// ...
|
||
|
// }
|
||
|
func MustExtractDoc(content, name string) string {
|
||
|
doc, err := ExtractDoc(content, name)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
return doc
|
||
|
}
|
||
|
|
||
|
// ExtractDoc extracts a section of a package doc comment from the
|
||
|
// provided contents of an analyzer package's doc.go file.
|
||
|
//
|
||
|
// A section is a portion of the comment between one heading and
|
||
|
// the next, using this form:
|
||
|
//
|
||
|
// # Analyzer NAME
|
||
|
//
|
||
|
// NAME: SUMMARY
|
||
|
//
|
||
|
// Full description...
|
||
|
//
|
||
|
// where NAME matches the name argument, and SUMMARY is a brief
|
||
|
// verb-phrase that describes the analyzer. The following lines, up
|
||
|
// until the next heading or the end of the comment, contain the full
|
||
|
// description. ExtractDoc returns the portion following the colon,
|
||
|
// which is the form expected by Analyzer.Doc.
|
||
|
//
|
||
|
// Example:
|
||
|
//
|
||
|
// # Analyzer printf
|
||
|
//
|
||
|
// printf: checks consistency of calls to printf
|
||
|
//
|
||
|
// The printf analyzer checks consistency of calls to printf.
|
||
|
// Here is the complete description...
|
||
|
//
|
||
|
// This notation allows a single doc comment to provide documentation
|
||
|
// for multiple analyzers, each in its own section.
|
||
|
// The HTML anchors generated for each heading are predictable.
|
||
|
//
|
||
|
// It returns an error if the content was not a valid Go source file
|
||
|
// containing a package doc comment with a heading of the required
|
||
|
// form.
|
||
|
//
|
||
|
// This machinery enables the package documentation (typically
|
||
|
// accessible via the web at https://pkg.go.dev/) and the command
|
||
|
// documentation (typically printed to a terminal) to be derived from
|
||
|
// the same source and formatted appropriately.
|
||
|
func ExtractDoc(content, name string) (string, error) {
|
||
|
if content == "" {
|
||
|
return "", fmt.Errorf("empty Go source file")
|
||
|
}
|
||
|
fset := token.NewFileSet()
|
||
|
f, err := parser.ParseFile(fset, "", content, parser.ParseComments|parser.PackageClauseOnly)
|
||
|
if err != nil {
|
||
|
return "", fmt.Errorf("not a Go source file")
|
||
|
}
|
||
|
if f.Doc == nil {
|
||
|
return "", fmt.Errorf("Go source file has no package doc comment")
|
||
|
}
|
||
|
for _, section := range strings.Split(f.Doc.Text(), "\n# ") {
|
||
|
if body := strings.TrimPrefix(section, "Analyzer "+name); body != section &&
|
||
|
body != "" &&
|
||
|
body[0] == '\r' || body[0] == '\n' {
|
||
|
body = strings.TrimSpace(body)
|
||
|
rest := strings.TrimPrefix(body, name+":")
|
||
|
if rest == body {
|
||
|
return "", fmt.Errorf("'Analyzer %s' heading not followed by '%s: summary...' line", name, name)
|
||
|
}
|
||
|
return strings.TrimSpace(rest), nil
|
||
|
}
|
||
|
}
|
||
|
return "", fmt.Errorf("package doc comment contains no 'Analyzer %s' heading", name)
|
||
|
}
|