Source file src/go/doc/headscan.go

     1  // Copyright 2011 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  //go:build ignore
     6  
     7  /*
     8  The headscan command extracts comment headings from package files;
     9  it is used to detect false positives which may require an adjustment
    10  to the comment formatting heuristics in comment.go.
    11  
    12  Usage: headscan [-root root_directory]
    13  
    14  By default, the $GOROOT/src directory is scanned.
    15  */
    16  package main
    17  
    18  import (
    19  	"flag"
    20  	"fmt"
    21  	"go/doc"
    22  	"go/parser"
    23  	"go/token"
    24  	"io/fs"
    25  	"os"
    26  	"path/filepath"
    27  	"regexp"
    28  	"runtime"
    29  	"strings"
    30  )
    31  
    32  var (
    33  	root    = flag.String("root", filepath.Join(runtime.GOROOT(), "src"), "root of filesystem tree to scan")
    34  	verbose = flag.Bool("v", false, "verbose mode")
    35  )
    36  
    37  // ToHTML in comment.go assigns a (possibly blank) ID to each heading
    38  var html_h = regexp.MustCompile(`<h3 id="[^"]*">`)
    39  
    40  const html_endh = "</h3>\n"
    41  
    42  func isGoFile(fi fs.FileInfo) bool {
    43  	return strings.HasSuffix(fi.Name(), ".go") &&
    44  		!strings.HasSuffix(fi.Name(), "_test.go")
    45  }
    46  
    47  func appendHeadings(list []string, comment string) []string {
    48  	var buf strings.Builder
    49  	doc.ToHTML(&buf, comment, nil)
    50  	for s := buf.String(); s != ""; {
    51  		loc := html_h.FindStringIndex(s)
    52  		if len(loc) == 0 {
    53  			break
    54  		}
    55  		var inner string
    56  		inner, s, _ = strings.Cut(s[loc[1]:], html_endh)
    57  		list = append(list, inner)
    58  	}
    59  	return list
    60  }
    61  
    62  func main() {
    63  	flag.Parse()
    64  	fset := token.NewFileSet()
    65  	nheadings := 0
    66  	err := filepath.WalkDir(*root, func(path string, info fs.DirEntry, err error) error {
    67  		if !info.IsDir() {
    68  			return nil
    69  		}
    70  		pkgs, err := parser.ParseDir(fset, path, isGoFile, parser.ParseComments)
    71  		if err != nil {
    72  			if *verbose {
    73  				fmt.Fprintln(os.Stderr, err)
    74  			}
    75  			return nil
    76  		}
    77  		for _, pkg := range pkgs {
    78  			d := doc.New(pkg, path, doc.Mode(0))
    79  			list := appendHeadings(nil, d.Doc)
    80  			for _, d := range d.Consts {
    81  				list = appendHeadings(list, d.Doc)
    82  			}
    83  			for _, d := range d.Types {
    84  				list = appendHeadings(list, d.Doc)
    85  			}
    86  			for _, d := range d.Vars {
    87  				list = appendHeadings(list, d.Doc)
    88  			}
    89  			for _, d := range d.Funcs {
    90  				list = appendHeadings(list, d.Doc)
    91  			}
    92  			if len(list) > 0 {
    93  				// directories may contain multiple packages;
    94  				// print path and package name
    95  				fmt.Printf("%s (package %s)\n", path, pkg.Name)
    96  				for _, h := range list {
    97  					fmt.Printf("\t%s\n", h)
    98  				}
    99  				nheadings += len(list)
   100  			}
   101  		}
   102  		return nil
   103  	})
   104  	if err != nil {
   105  		fmt.Fprintln(os.Stderr, err)
   106  		os.Exit(1)
   107  	}
   108  	fmt.Println(nheadings, "headings found")
   109  }
   110  

View as plain text