github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/cmd/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 main
    12  
    13  import (
    14  	"fmt"
    15  	"go/ast"
    16  	"go/token"
    17  	"io"
    18  	"strconv"
    19  )
    20  
    21  // LinkifyText HTML-escapes source text and writes it to w.
    22  // Identifiers that are in a "use" position (i.e., that are
    23  // not being declared), are wrapped with HTML links pointing
    24  // to the respective declaration, if possible. Comments are
    25  // formatted the same way as with FormatText.
    26  //
    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.mode == identVal {
    57  					fmt.Fprintf(w, `<span id="%s">`, info.name)
    58  					prev = "span"
    59  				} else {
    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  //
    76  type link struct {
    77  	mode       identMode
    78  	path, name string // package path, identifier name
    79  }
    80  
    81  // linksFor returns the list of links for the identifiers used
    82  // by node in the same order as they appear in the source.
    83  //
    84  func linksFor(node ast.Node) (list []link) {
    85  	modes := identModesFor(node)
    86  
    87  	// NOTE: We are expecting ast.Inspect to call the
    88  	//       callback function in source text order.
    89  	ast.Inspect(node, func(node ast.Node) bool {
    90  		switch n := node.(type) {
    91  		case *ast.Ident:
    92  			m := modes[n]
    93  			info := link{mode: m}
    94  			switch m {
    95  			case identUse:
    96  				if n.Obj == nil && predeclared[n.Name] {
    97  					info.path = builtinPkgPath
    98  				}
    99  				info.name = n.Name
   100  			case identDef:
   101  				// any declaration expect const or var - empty link
   102  			case identVal:
   103  				// const or var declaration
   104  				info.name = n.Name
   105  			}
   106  			list = append(list, info)
   107  			return false
   108  		case *ast.SelectorExpr:
   109  			// Detect qualified identifiers of the form pkg.ident.
   110  			// If anything fails we return true and collect individual
   111  			// identifiers instead.
   112  			if x, _ := n.X.(*ast.Ident); x != nil {
   113  				// x must be a package for a qualified identifier
   114  				if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
   115  					if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
   116  						// spec.Path.Value is the import path
   117  						if path, err := strconv.Unquote(spec.Path.Value); err == nil {
   118  							// Register two links, one for the package
   119  							// and one for the qualified identifier.
   120  							info := link{path: path}
   121  							list = append(list, info)
   122  							info.name = n.Sel.Name
   123  							list = append(list, info)
   124  							return false
   125  						}
   126  					}
   127  				}
   128  			}
   129  		}
   130  		return true
   131  	})
   132  
   133  	return
   134  }
   135  
   136  // The identMode describes how an identifier is "used" at its source location.
   137  type identMode int
   138  
   139  const (
   140  	identUse identMode = iota // identifier is used (must be zero value for identMode)
   141  	identDef                  // identifier is defined
   142  	identVal                  // identifier is defined in a const or var declaration
   143  )
   144  
   145  // identModesFor returns a map providing the identMode for each identifier used by node.
   146  func identModesFor(node ast.Node) map[*ast.Ident]identMode {
   147  	m := make(map[*ast.Ident]identMode)
   148  
   149  	ast.Inspect(node, func(node ast.Node) bool {
   150  		switch n := node.(type) {
   151  		case *ast.Field:
   152  			for _, n := range n.Names {
   153  				m[n] = identDef
   154  			}
   155  		case *ast.ImportSpec:
   156  			if name := n.Name; name != nil {
   157  				m[name] = identDef
   158  			}
   159  		case *ast.ValueSpec:
   160  			for _, n := range n.Names {
   161  				m[n] = identVal
   162  			}
   163  		case *ast.TypeSpec:
   164  			m[n.Name] = identDef
   165  		case *ast.FuncDecl:
   166  			m[n.Name] = identDef
   167  		case *ast.AssignStmt:
   168  			// Short variable declarations only show up if we apply
   169  			// this code to all source code (as opposed to exported
   170  			// declarations only).
   171  			if n.Tok == token.DEFINE {
   172  				// Some of the lhs variables may be re-declared,
   173  				// so technically they are not defs. We don't
   174  				// care for now.
   175  				for _, x := range n.Lhs {
   176  					// Each lhs expression should be an
   177  					// ident, but we are conservative and check.
   178  					if n, _ := x.(*ast.Ident); n != nil {
   179  						m[n] = identVal
   180  					}
   181  				}
   182  			}
   183  		}
   184  		return true
   185  	})
   186  
   187  	return m
   188  }
   189  
   190  // The predeclared map represents the set of all predeclared identifiers.
   191  // TODO(gri) This information is also encoded in similar maps in go/doc,
   192  //           but not exported. Consider exporting an accessor and using
   193  //           it instead.
   194  var predeclared = map[string]bool{
   195  	"bool":       true,
   196  	"byte":       true,
   197  	"complex64":  true,
   198  	"complex128": true,
   199  	"error":      true,
   200  	"float32":    true,
   201  	"float64":    true,
   202  	"int":        true,
   203  	"int8":       true,
   204  	"int16":      true,
   205  	"int32":      true,
   206  	"int64":      true,
   207  	"rune":       true,
   208  	"string":     true,
   209  	"uint":       true,
   210  	"uint8":      true,
   211  	"uint16":     true,
   212  	"uint32":     true,
   213  	"uint64":     true,
   214  	"uintptr":    true,
   215  	"true":       true,
   216  	"false":      true,
   217  	"iota":       true,
   218  	"nil":        true,
   219  	"append":     true,
   220  	"cap":        true,
   221  	"close":      true,
   222  	"complex":    true,
   223  	"copy":       true,
   224  	"delete":     true,
   225  	"imag":       true,
   226  	"len":        true,
   227  	"make":       true,
   228  	"new":        true,
   229  	"panic":      true,
   230  	"print":      true,
   231  	"println":    true,
   232  	"real":       true,
   233  	"recover":    true,
   234  }