github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/godoc/linkify.go (about)

     1  // Copyright 2013 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  // This file implements LinkifyText which introduces
     6  // links for identifiers pointing to their declarations.
     7  // The approach does not cover all cases because godoc
     8  // doesn't have complete type information, but it's
     9  // reasonably good for browsing.
    10  
    11  package godoc
    12  
    13  import (
    14  	"fmt"
    15  	"go/ast"
    16  	"go/doc"
    17  	"go/token"
    18  	"io"
    19  	"strconv"
    20  
    21  	"github.com/powerman/golang-tools/internal/typeparams"
    22  )
    23  
    24  // LinkifyText HTML-escapes source text and writes it to w.
    25  // Identifiers that are in a "use" position (i.e., that are
    26  // not being declared), are wrapped with HTML links pointing
    27  // to the respective declaration, if possible. Comments are
    28  // formatted the same way as with FormatText.
    29  //
    30  func LinkifyText(w io.Writer, text []byte, n ast.Node) {
    31  	links := linksFor(n)
    32  
    33  	i := 0     // links index
    34  	prev := "" // prev HTML tag
    35  	linkWriter := func(w io.Writer, _ int, start bool) {
    36  		// end tag
    37  		if !start {
    38  			if prev != "" {
    39  				fmt.Fprintf(w, `</%s>`, prev)
    40  				prev = ""
    41  			}
    42  			return
    43  		}
    44  
    45  		// start tag
    46  		prev = ""
    47  		if i < len(links) {
    48  			switch info := links[i]; {
    49  			case info.path != "" && info.name == "":
    50  				// package path
    51  				fmt.Fprintf(w, `<a href="/pkg/%s/">`, info.path)
    52  				prev = "a"
    53  			case info.path != "" && info.name != "":
    54  				// qualified identifier
    55  				fmt.Fprintf(w, `<a href="/pkg/%s/#%s">`, info.path, info.name)
    56  				prev = "a"
    57  			case info.path == "" && info.name != "":
    58  				// local identifier
    59  				if info.isVal {
    60  					fmt.Fprintf(w, `<span id="%s">`, info.name)
    61  					prev = "span"
    62  				} else if ast.IsExported(info.name) {
    63  					fmt.Fprintf(w, `<a href="#%s">`, info.name)
    64  					prev = "a"
    65  				}
    66  			}
    67  			i++
    68  		}
    69  	}
    70  
    71  	idents := tokenSelection(text, token.IDENT)
    72  	comments := tokenSelection(text, token.COMMENT)
    73  	FormatSelections(w, text, linkWriter, idents, selectionTag, comments)
    74  }
    75  
    76  // A link describes the (HTML) link information for an identifier.
    77  // The zero value of a link represents "no link".
    78  //
    79  type link struct {
    80  	path, name string // package path, identifier name
    81  	isVal      bool   // identifier is defined in a const or var declaration
    82  }
    83  
    84  // linksFor returns the list of links for the identifiers used
    85  // by node in the same order as they appear in the source.
    86  //
    87  func linksFor(node ast.Node) (links []link) {
    88  	// linkMap tracks link information for each ast.Ident node. Entries may
    89  	// be created out of source order (for example, when we visit a parent
    90  	// definition node). These links are appended to the returned slice when
    91  	// their ast.Ident nodes are visited.
    92  	linkMap := make(map[*ast.Ident]link)
    93  
    94  	typeParams := make(map[string]bool)
    95  
    96  	ast.Inspect(node, func(node ast.Node) bool {
    97  		switch n := node.(type) {
    98  		case *ast.Field:
    99  			for _, n := range n.Names {
   100  				linkMap[n] = link{}
   101  			}
   102  		case *ast.ImportSpec:
   103  			if name := n.Name; name != nil {
   104  				linkMap[name] = link{}
   105  			}
   106  		case *ast.ValueSpec:
   107  			for _, n := range n.Names {
   108  				linkMap[n] = link{name: n.Name, isVal: true}
   109  			}
   110  		case *ast.FuncDecl:
   111  			linkMap[n.Name] = link{}
   112  			if n.Recv != nil {
   113  				recv := n.Recv.List[0].Type
   114  				if r, isstar := recv.(*ast.StarExpr); isstar {
   115  					recv = r.X
   116  				}
   117  				switch x := recv.(type) {
   118  				case *ast.IndexExpr:
   119  					if ident, _ := x.Index.(*ast.Ident); ident != nil {
   120  						typeParams[ident.Name] = true
   121  					}
   122  				case *typeparams.IndexListExpr:
   123  					for _, index := range x.Indices {
   124  						if ident, _ := index.(*ast.Ident); ident != nil {
   125  							typeParams[ident.Name] = true
   126  						}
   127  					}
   128  				}
   129  			}
   130  		case *ast.TypeSpec:
   131  			linkMap[n.Name] = link{}
   132  		case *ast.AssignStmt:
   133  			// Short variable declarations only show up if we apply
   134  			// this code to all source code (as opposed to exported
   135  			// declarations only).
   136  			if n.Tok == token.DEFINE {
   137  				// Some of the lhs variables may be re-declared,
   138  				// so technically they are not defs. We don't
   139  				// care for now.
   140  				for _, x := range n.Lhs {
   141  					// Each lhs expression should be an
   142  					// ident, but we are conservative and check.
   143  					if n, _ := x.(*ast.Ident); n != nil {
   144  						linkMap[n] = link{isVal: true}
   145  					}
   146  				}
   147  			}
   148  		case *ast.SelectorExpr:
   149  			// Detect qualified identifiers of the form pkg.ident.
   150  			// If anything fails we return true and collect individual
   151  			// identifiers instead.
   152  			if x, _ := n.X.(*ast.Ident); x != nil {
   153  				// Create links only if x is a qualified identifier.
   154  				if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
   155  					if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
   156  						// spec.Path.Value is the import path
   157  						if path, err := strconv.Unquote(spec.Path.Value); err == nil {
   158  							// Register two links, one for the package
   159  							// and one for the qualified identifier.
   160  							linkMap[x] = link{path: path}
   161  							linkMap[n.Sel] = link{path: path, name: n.Sel.Name}
   162  						}
   163  					}
   164  				}
   165  			}
   166  		case *ast.CompositeLit:
   167  			// Detect field names within composite literals. These links should
   168  			// be prefixed by the type name.
   169  			fieldPath := ""
   170  			prefix := ""
   171  			switch typ := n.Type.(type) {
   172  			case *ast.Ident:
   173  				prefix = typ.Name + "."
   174  			case *ast.SelectorExpr:
   175  				if x, _ := typ.X.(*ast.Ident); x != nil {
   176  					// Create links only if x is a qualified identifier.
   177  					if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
   178  						if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
   179  							// spec.Path.Value is the import path
   180  							if path, err := strconv.Unquote(spec.Path.Value); err == nil {
   181  								// Register two links, one for the package
   182  								// and one for the qualified identifier.
   183  								linkMap[x] = link{path: path}
   184  								linkMap[typ.Sel] = link{path: path, name: typ.Sel.Name}
   185  								fieldPath = path
   186  								prefix = typ.Sel.Name + "."
   187  							}
   188  						}
   189  					}
   190  				}
   191  			}
   192  			for _, e := range n.Elts {
   193  				if kv, ok := e.(*ast.KeyValueExpr); ok {
   194  					if k, ok := kv.Key.(*ast.Ident); ok {
   195  						// Note: there is some syntactic ambiguity here. We cannot determine
   196  						// if this is a struct literal or a map literal without type
   197  						// information. We assume struct literal.
   198  						name := prefix + k.Name
   199  						linkMap[k] = link{path: fieldPath, name: name}
   200  					}
   201  				}
   202  			}
   203  		case *ast.Ident:
   204  			if l, ok := linkMap[n]; ok {
   205  				links = append(links, l)
   206  			} else {
   207  				l := link{name: n.Name}
   208  				if n.Obj == nil {
   209  					if doc.IsPredeclared(n.Name) {
   210  						l.path = builtinPkgPath
   211  					} else {
   212  						if typeParams[n.Name] {
   213  							// If a type parameter was declared then do not generate a link.
   214  							// Doing this is necessary because type parameter identifiers do not
   215  							// have their Decl recorded sometimes, see
   216  							// https://golang.org/issue/50956.
   217  							l = link{}
   218  						}
   219  					}
   220  				} else {
   221  					if n.Obj.Kind == ast.Typ {
   222  						if _, isfield := n.Obj.Decl.(*ast.Field); isfield {
   223  							// If an identifier is a type declared in a field assume it is a type
   224  							// parameter and do not generate a link.
   225  							l = link{}
   226  						}
   227  					}
   228  				}
   229  				links = append(links, l)
   230  			}
   231  		}
   232  		return true
   233  	})
   234  	return
   235  }