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