Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 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 | |
Brad Fitzpatrick | e6ff53b | 2013-07-17 17:09:54 +1000 | [diff] [blame] | 11 | package godoc |
Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 12 | |
| 13 | import ( |
| 14 | "fmt" |
| 15 | "go/ast" |
Masahiro Furudate | 4de4a8d | 2017-08-25 15:00:21 +0900 | [diff] [blame] | 16 | "go/doc" |
Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 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 |
Masahiro Furudate | ce12915 | 2017-05-07 04:40:42 +0900 | [diff] [blame] | 57 | if info.isVal { |
Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 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 { |
Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 78 | path, name string // package path, identifier name |
Masahiro Furudate | ce12915 | 2017-05-07 04:40:42 +0900 | [diff] [blame] | 79 | isVal bool // identifier is defined in a const or var declaration |
Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 80 | } |
| 81 | |
Jay Conrod | e1bdc76 | 2017-02-10 16:20:20 -0500 | [diff] [blame] | 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) |
Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 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 { |
Masahiro Furudate | ce12915 | 2017-05-07 04:40:42 +0900 | [diff] [blame] | 96 | linkMap[n] = link{} |
Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 97 | } |
| 98 | case *ast.ImportSpec: |
| 99 | if name := n.Name; name != nil { |
Masahiro Furudate | ce12915 | 2017-05-07 04:40:42 +0900 | [diff] [blame] | 100 | linkMap[name] = link{} |
Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 101 | } |
| 102 | case *ast.ValueSpec: |
| 103 | for _, n := range n.Names { |
Masahiro Furudate | ce12915 | 2017-05-07 04:40:42 +0900 | [diff] [blame] | 104 | linkMap[n] = link{name: n.Name, isVal: true} |
Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 105 | } |
Masahiro Furudate | ce12915 | 2017-05-07 04:40:42 +0900 | [diff] [blame] | 106 | case *ast.FuncDecl: |
| 107 | linkMap[n.Name] = link{} |
Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 108 | case *ast.TypeSpec: |
Masahiro Furudate | ce12915 | 2017-05-07 04:40:42 +0900 | [diff] [blame] | 109 | linkMap[n.Name] = link{} |
Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 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 { |
Masahiro Furudate | ce12915 | 2017-05-07 04:40:42 +0900 | [diff] [blame] | 122 | linkMap[n] = link{isVal: true} |
Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 123 | } |
| 124 | } |
| 125 | } |
Jay Conrod | e1bdc76 | 2017-02-10 16:20:20 -0500 | [diff] [blame] | 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. |
Masahiro Furudate | ce12915 | 2017-05-07 04:40:42 +0900 | [diff] [blame] | 138 | linkMap[x] = link{path: path} |
| 139 | linkMap[n.Sel] = link{path: path, name: n.Sel.Name} |
Jay Conrod | e1bdc76 | 2017-02-10 16:20:20 -0500 | [diff] [blame] | 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. |
Masahiro Furudate | ce12915 | 2017-05-07 04:40:42 +0900 | [diff] [blame] | 161 | linkMap[x] = link{path: path} |
| 162 | linkMap[typ.Sel] = link{path: path, name: typ.Sel.Name} |
Jay Conrod | e1bdc76 | 2017-02-10 16:20:20 -0500 | [diff] [blame] | 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 |
Masahiro Furudate | ce12915 | 2017-05-07 04:40:42 +0900 | [diff] [blame] | 177 | linkMap[k] = link{path: fieldPath, name: name} |
Jay Conrod | e1bdc76 | 2017-02-10 16:20:20 -0500 | [diff] [blame] | 178 | } |
| 179 | } |
| 180 | } |
| 181 | case *ast.Ident: |
| 182 | if l, ok := linkMap[n]; ok { |
| 183 | links = append(links, l) |
| 184 | } else { |
Masahiro Furudate | ce12915 | 2017-05-07 04:40:42 +0900 | [diff] [blame] | 185 | l := link{name: n.Name} |
Masahiro Furudate | 4de4a8d | 2017-08-25 15:00:21 +0900 | [diff] [blame] | 186 | if n.Obj == nil && doc.IsPredeclared(n.Name) { |
Jay Conrod | e1bdc76 | 2017-02-10 16:20:20 -0500 | [diff] [blame] | 187 | l.path = builtinPkgPath |
| 188 | } |
| 189 | links = append(links, l) |
| 190 | } |
Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 191 | } |
| 192 | return true |
| 193 | }) |
Jay Conrod | e1bdc76 | 2017-02-10 16:20:20 -0500 | [diff] [blame] | 194 | return |
Andrew Gerrand | d79f4fe | 2013-07-17 14:02:35 +1000 | [diff] [blame] | 195 | } |