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