...

Source file src/golang.org/x/tools/godoc/linkify.go

Documentation: golang.org/x/tools/godoc

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

View as plain text