Source file src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/extractdoc.go

     1  // Copyright 2023 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 analysisinternal
     6  
     7  import (
     8  	"fmt"
     9  	"go/parser"
    10  	"go/token"
    11  	"strings"
    12  )
    13  
    14  // MustExtractDoc is like [ExtractDoc] but it panics on error.
    15  //
    16  // To use, define a doc.go file such as:
    17  //
    18  //	// Package halting defines an analyzer of program termination.
    19  //	//
    20  //	// # Analyzer halting
    21  //	//
    22  //	// halting: reports whether execution will halt.
    23  //	//
    24  //	// The halting analyzer reports a diagnostic for functions
    25  //	// that run forever. To suppress the diagnostics, try inserting
    26  //	// a 'break' statement into each loop.
    27  //	package halting
    28  //
    29  //	import _ "embed"
    30  //
    31  //	//go:embed doc.go
    32  //	var doc string
    33  //
    34  // And declare your analyzer as:
    35  //
    36  //	var Analyzer = &analysis.Analyzer{
    37  //		Name:             "halting",
    38  //		Doc:              analysisutil.MustExtractDoc(doc, "halting"),
    39  //		...
    40  //	}
    41  func MustExtractDoc(content, name string) string {
    42  	doc, err := ExtractDoc(content, name)
    43  	if err != nil {
    44  		panic(err)
    45  	}
    46  	return doc
    47  }
    48  
    49  // ExtractDoc extracts a section of a package doc comment from the
    50  // provided contents of an analyzer package's doc.go file.
    51  //
    52  // A section is a portion of the comment between one heading and
    53  // the next, using this form:
    54  //
    55  //	# Analyzer NAME
    56  //
    57  //	NAME: SUMMARY
    58  //
    59  //	Full description...
    60  //
    61  // where NAME matches the name argument, and SUMMARY is a brief
    62  // verb-phrase that describes the analyzer. The following lines, up
    63  // until the next heading or the end of the comment, contain the full
    64  // description. ExtractDoc returns the portion following the colon,
    65  // which is the form expected by Analyzer.Doc.
    66  //
    67  // Example:
    68  //
    69  //	# Analyzer printf
    70  //
    71  //	printf: checks consistency of calls to printf
    72  //
    73  //	The printf analyzer checks consistency of calls to printf.
    74  //	Here is the complete description...
    75  //
    76  // This notation allows a single doc comment to provide documentation
    77  // for multiple analyzers, each in its own section.
    78  // The HTML anchors generated for each heading are predictable.
    79  //
    80  // It returns an error if the content was not a valid Go source file
    81  // containing a package doc comment with a heading of the required
    82  // form.
    83  //
    84  // This machinery enables the package documentation (typically
    85  // accessible via the web at https://pkg.go.dev/) and the command
    86  // documentation (typically printed to a terminal) to be derived from
    87  // the same source and formatted appropriately.
    88  func ExtractDoc(content, name string) (string, error) {
    89  	if content == "" {
    90  		return "", fmt.Errorf("empty Go source file")
    91  	}
    92  	fset := token.NewFileSet()
    93  	f, err := parser.ParseFile(fset, "", content, parser.ParseComments|parser.PackageClauseOnly)
    94  	if err != nil {
    95  		return "", fmt.Errorf("not a Go source file")
    96  	}
    97  	if f.Doc == nil {
    98  		return "", fmt.Errorf("Go source file has no package doc comment")
    99  	}
   100  	for _, section := range strings.Split(f.Doc.Text(), "\n# ") {
   101  		if body := strings.TrimPrefix(section, "Analyzer "+name); body != section &&
   102  			body != "" &&
   103  			body[0] == '\r' || body[0] == '\n' {
   104  			body = strings.TrimSpace(body)
   105  			rest := strings.TrimPrefix(body, name+":")
   106  			if rest == body {
   107  				return "", fmt.Errorf("'Analyzer %s' heading not followed by '%s: summary...' line", name, name)
   108  			}
   109  			return strings.TrimSpace(rest), nil
   110  		}
   111  	}
   112  	return "", fmt.Errorf("package doc comment contains no 'Analyzer %s' heading", name)
   113  }
   114  

View as plain text