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 }