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