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 }