...

Source file src/cloud.google.com/go/third_party/pkgsite/print_type.go

Documentation: cloud.google.com/go/third_party/pkgsite

     1  // Copyright 2020 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 pkgsite is not for external use. May change at any time without
     6  // warning.
     7  //
     8  // Copied from
     9  // https://github.com/golang/pkgsite/blob/ff1e697b104e751da362159cf6c7743898eea3fe/internal/fetch/dochtml/internal/render/
    10  // and
    11  // https://github.com/golang/pkgsite/tree/88f8a28ab2102416529d05d11e8135a43e146d46/internal/fetch/dochtml.
    12  package pkgsite
    13  
    14  import (
    15  	"bytes"
    16  	"fmt"
    17  	"go/ast"
    18  	"go/doc"
    19  	"go/printer"
    20  	"go/scanner"
    21  	"go/token"
    22  	"strconv"
    23  	"strings"
    24  )
    25  
    26  // PrintType returns a string representation of the decl.
    27  //
    28  // PrintType works by:
    29  //  1. Generate a map from every identifier to a URL for the identifier (or no
    30  //     URL if the identifier shouldn't link).
    31  //  2. ast.Inspect the decl to get an ordered slice of every identifier to the
    32  //     link for it, using the map from step 1.
    33  //  3. Print out the plain doc for the decl.
    34  //  4. Use scanner.Scanner to find every identifier (in the same order as step
    35  //     2). If there is a link for the identifier, insert it. Otherwise, print
    36  //     the plain doc.
    37  func PrintType(fset *token.FileSet, decl ast.Decl, toURL func(string, string) string, topLevelDecls map[interface{}]bool) string {
    38  	anchorLinksMap := generateAnchorLinks(decl, toURL, topLevelDecls)
    39  	// Convert the map (keyed by *ast.Ident) to a slice of URLs (or no URL).
    40  	//
    41  	// This relies on the ast.Inspect and scanner.Scanner both
    42  	// visiting *ast.Ident and token.IDENT nodes in the same order.
    43  	var anchorLinks []string
    44  	ast.Inspect(decl, func(node ast.Node) bool {
    45  		if id, ok := node.(*ast.Ident); ok {
    46  			anchorLinks = append(anchorLinks, anchorLinksMap[id])
    47  		}
    48  		return true
    49  	})
    50  
    51  	v := &declVisitor{}
    52  	ast.Walk(v, decl)
    53  
    54  	var b bytes.Buffer
    55  	p := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 4}
    56  	p.Fprint(&b, fset, &printer.CommentedNode{Node: decl, Comments: v.Comments})
    57  	src := b.Bytes()
    58  	var out strings.Builder
    59  
    60  	fakeFset := token.NewFileSet()
    61  	file := fakeFset.AddFile("", fakeFset.Base(), b.Len())
    62  
    63  	var lastOffset int // last src offset copied to output buffer
    64  	var s scanner.Scanner
    65  	s.Init(file, src, nil, scanner.ScanComments)
    66  	identIdx := 0
    67  scan:
    68  	for {
    69  		p, tok, lit := s.Scan()
    70  		line := file.Line(p) - 1 // current 0-indexed line number
    71  		offset := file.Offset(p) // current offset into source file
    72  
    73  		// Add traversed bytes from src to the appropriate line.
    74  		prevLines := strings.SplitAfter(string(src[lastOffset:offset]), "\n")
    75  		for i, ln := range prevLines {
    76  			n := line - len(prevLines) + i + 1
    77  			if n < 0 { // possible at EOF
    78  				n = 0
    79  			}
    80  			out.WriteString(ln)
    81  		}
    82  
    83  		lastOffset = offset
    84  		switch tok {
    85  		case token.EOF:
    86  			break scan
    87  		case token.IDENT:
    88  			if identIdx < len(anchorLinks) && anchorLinks[identIdx] != "" {
    89  				fmt.Fprintf(&out, `<a href="%s">%s</a>`, anchorLinks[identIdx], lit)
    90  			} else {
    91  				out.WriteString(lit)
    92  			}
    93  			identIdx++
    94  			lastOffset += len(lit)
    95  		}
    96  	}
    97  	return out.String()
    98  }
    99  
   100  // declVisitor is used to walk over the AST and trim large string
   101  // literals and arrays before package documentation is rendered.
   102  // Comments are added to Comments to indicate that a part of the
   103  // original code is not displayed.
   104  type declVisitor struct {
   105  	Comments []*ast.CommentGroup
   106  }
   107  
   108  // Visit implements ast.Visitor.
   109  func (v *declVisitor) Visit(n ast.Node) ast.Visitor {
   110  	switch n := n.(type) {
   111  	case *ast.BasicLit:
   112  		if n.Kind == token.STRING && len(n.Value) > 128 {
   113  			v.Comments = append(v.Comments,
   114  				&ast.CommentGroup{List: []*ast.Comment{{
   115  					Slash: n.Pos(),
   116  					Text:  stringBasicLitSize(n.Value),
   117  				}}})
   118  			n.Value = `""`
   119  		}
   120  	case *ast.CompositeLit:
   121  		if len(n.Elts) > 100 {
   122  			v.Comments = append(v.Comments,
   123  				&ast.CommentGroup{List: []*ast.Comment{{
   124  					Slash: n.Lbrace,
   125  					Text:  fmt.Sprintf("/* %d elements not displayed */", len(n.Elts)),
   126  				}}})
   127  			n.Elts = n.Elts[:0]
   128  		}
   129  	}
   130  	return v
   131  }
   132  
   133  // stringBasicLitSize computes the number of bytes in the given string basic literal.
   134  //
   135  // See noder.basicLit and syntax.StringLit cases in cmd/compile/internal/gc/noder.go.
   136  func stringBasicLitSize(s string) string {
   137  	if len(s) > 0 && s[0] == '`' {
   138  		// strip carriage returns from raw string
   139  		s = strings.ReplaceAll(s, "\r", "")
   140  	}
   141  	u, err := strconv.Unquote(s)
   142  	if err != nil {
   143  		return fmt.Sprintf("/* invalid %d byte string literal not displayed */", len(s))
   144  	}
   145  	return fmt.Sprintf("/* %d byte string literal not displayed */", len(u))
   146  }
   147  
   148  // generateAnchorLinks returns a mapping of *ast.Ident objects to the URL
   149  // that the identifier should link to.
   150  func generateAnchorLinks(decl ast.Decl, toURL func(string, string) string, topLevelDecls map[interface{}]bool) map[*ast.Ident]string {
   151  	m := map[*ast.Ident]string{}
   152  	ignore := map[ast.Node]bool{}
   153  	ast.Inspect(decl, func(node ast.Node) bool {
   154  		if ignore[node] {
   155  			return false
   156  		}
   157  		switch node := node.(type) {
   158  		case *ast.SelectorExpr:
   159  			// Package qualified identifier (e.g., "io.EOF").
   160  			if prefix, _ := node.X.(*ast.Ident); prefix != nil {
   161  				if obj := prefix.Obj; obj != nil && obj.Kind == ast.Pkg {
   162  					if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
   163  						if path, err := strconv.Unquote(spec.Path.Value); err == nil {
   164  							// Register two links, one for the package
   165  							// and one for the qualified identifier.
   166  							m[prefix] = toURL(path, "")
   167  							m[node.Sel] = toURL(path, node.Sel.Name)
   168  							return false
   169  						}
   170  					}
   171  				}
   172  			}
   173  		case *ast.Ident:
   174  			if node.Obj == nil && doc.IsPredeclared(node.Name) {
   175  				m[node] = toURL("builtin", node.Name)
   176  			} else if node.Obj != nil && topLevelDecls[node.Obj.Decl] {
   177  				m[node] = toURL("", node.Name)
   178  			}
   179  		case *ast.FuncDecl:
   180  			ignore[node.Name] = true // E.g., "func NoLink() int"
   181  		case *ast.TypeSpec:
   182  			ignore[node.Name] = true // E.g., "type NoLink int"
   183  		case *ast.ValueSpec:
   184  			for _, n := range node.Names {
   185  				ignore[n] = true // E.g., "var NoLink1, NoLink2 int"
   186  			}
   187  		case *ast.AssignStmt:
   188  			for _, n := range node.Lhs {
   189  				ignore[n] = true // E.g., "NoLink1, NoLink2 := 0, 1"
   190  			}
   191  		}
   192  		return true
   193  	})
   194  	return m
   195  }
   196  
   197  // TopLevelDecls returns the top level declarations in the package.
   198  func TopLevelDecls(pkg *doc.Package) map[interface{}]bool {
   199  	topLevelDecls := map[interface{}]bool{}
   200  	forEachPackageDecl(pkg, func(decl ast.Decl) {
   201  		topLevelDecls[decl] = true
   202  		if gd, _ := decl.(*ast.GenDecl); gd != nil {
   203  			for _, sp := range gd.Specs {
   204  				topLevelDecls[sp] = true
   205  			}
   206  		}
   207  	})
   208  	return topLevelDecls
   209  }
   210  
   211  // forEachPackageDecl iterates though every top-level declaration in a package.
   212  func forEachPackageDecl(pkg *doc.Package, fnc func(decl ast.Decl)) {
   213  	for _, c := range pkg.Consts {
   214  		fnc(c.Decl)
   215  	}
   216  	for _, v := range pkg.Vars {
   217  		fnc(v.Decl)
   218  	}
   219  	for _, f := range pkg.Funcs {
   220  		fnc(f.Decl)
   221  	}
   222  	for _, t := range pkg.Types {
   223  		fnc(t.Decl)
   224  		for _, c := range t.Consts {
   225  			fnc(c.Decl)
   226  		}
   227  		for _, v := range t.Vars {
   228  			fnc(v.Decl)
   229  		}
   230  		for _, f := range t.Funcs {
   231  			fnc(f.Decl)
   232  		}
   233  		for _, m := range t.Methods {
   234  			fnc(m.Decl)
   235  		}
   236  	}
   237  }
   238  

View as plain text