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