GoPLS Viewer

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