You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

195 lines
5.8 KiB

  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. // This file implements LinkifyText which introduces
  5. // links for identifiers pointing to their declarations.
  6. // The approach does not cover all cases because godoc
  7. // doesn't have complete type information, but it's
  8. // reasonably good for browsing.
  9. package godoc
  10. import (
  11. "fmt"
  12. "go/ast"
  13. "go/doc"
  14. "go/token"
  15. "io"
  16. "strconv"
  17. )
  18. // LinkifyText HTML-escapes source text and writes it to w.
  19. // Identifiers that are in a "use" position (i.e., that are
  20. // not being declared), are wrapped with HTML links pointing
  21. // to the respective declaration, if possible. Comments are
  22. // formatted the same way as with FormatText.
  23. //
  24. func LinkifyText(w io.Writer, text []byte, n ast.Node) {
  25. links := linksFor(n)
  26. i := 0 // links index
  27. prev := "" // prev HTML tag
  28. linkWriter := func(w io.Writer, _ int, start bool) {
  29. // end tag
  30. if !start {
  31. if prev != "" {
  32. fmt.Fprintf(w, `</%s>`, prev)
  33. prev = ""
  34. }
  35. return
  36. }
  37. // start tag
  38. prev = ""
  39. if i < len(links) {
  40. switch info := links[i]; {
  41. case info.path != "" && info.name == "":
  42. // package path
  43. fmt.Fprintf(w, `<a href="/pkg/%s/">`, info.path)
  44. prev = "a"
  45. case info.path != "" && info.name != "":
  46. // qualified identifier
  47. fmt.Fprintf(w, `<a href="/pkg/%s/#%s">`, info.path, info.name)
  48. prev = "a"
  49. case info.path == "" && info.name != "":
  50. // local identifier
  51. if info.isVal {
  52. fmt.Fprintf(w, `<span id="%s">`, info.name)
  53. prev = "span"
  54. } else if ast.IsExported(info.name) {
  55. fmt.Fprintf(w, `<a href="#%s">`, info.name)
  56. prev = "a"
  57. }
  58. }
  59. i++
  60. }
  61. }
  62. idents := tokenSelection(text, token.IDENT)
  63. comments := tokenSelection(text, token.COMMENT)
  64. FormatSelections(w, text, linkWriter, idents, selectionTag, comments)
  65. }
  66. // A link describes the (HTML) link information for an identifier.
  67. // The zero value of a link represents "no link".
  68. //
  69. type link struct {
  70. path, name string // package path, identifier name
  71. isVal bool // identifier is defined in a const or var declaration
  72. }
  73. // linksFor returns the list of links for the identifiers used
  74. // by node in the same order as they appear in the source.
  75. //
  76. func linksFor(node ast.Node) (links []link) {
  77. // linkMap tracks link information for each ast.Ident node. Entries may
  78. // be created out of source order (for example, when we visit a parent
  79. // definition node). These links are appended to the returned slice when
  80. // their ast.Ident nodes are visited.
  81. linkMap := make(map[*ast.Ident]link)
  82. ast.Inspect(node, func(node ast.Node) bool {
  83. switch n := node.(type) {
  84. case *ast.Field:
  85. for _, n := range n.Names {
  86. linkMap[n] = link{}
  87. }
  88. case *ast.ImportSpec:
  89. if name := n.Name; name != nil {
  90. linkMap[name] = link{}
  91. }
  92. case *ast.ValueSpec:
  93. for _, n := range n.Names {
  94. linkMap[n] = link{name: n.Name, isVal: true}
  95. }
  96. case *ast.FuncDecl:
  97. linkMap[n.Name] = link{}
  98. case *ast.TypeSpec:
  99. linkMap[n.Name] = link{}
  100. case *ast.AssignStmt:
  101. // Short variable declarations only show up if we apply
  102. // this code to all source code (as opposed to exported
  103. // declarations only).
  104. if n.Tok == token.DEFINE {
  105. // Some of the lhs variables may be re-declared,
  106. // so technically they are not defs. We don't
  107. // care for now.
  108. for _, x := range n.Lhs {
  109. // Each lhs expression should be an
  110. // ident, but we are conservative and check.
  111. if n, _ := x.(*ast.Ident); n != nil {
  112. linkMap[n] = link{isVal: true}
  113. }
  114. }
  115. }
  116. case *ast.SelectorExpr:
  117. // Detect qualified identifiers of the form pkg.ident.
  118. // If anything fails we return true and collect individual
  119. // identifiers instead.
  120. if x, _ := n.X.(*ast.Ident); x != nil {
  121. // Create links only if x is a qualified identifier.
  122. if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
  123. if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
  124. // spec.Path.Value is the import path
  125. if path, err := strconv.Unquote(spec.Path.Value); err == nil {
  126. // Register two links, one for the package
  127. // and one for the qualified identifier.
  128. linkMap[x] = link{path: path}
  129. linkMap[n.Sel] = link{path: path, name: n.Sel.Name}
  130. }
  131. }
  132. }
  133. }
  134. case *ast.CompositeLit:
  135. // Detect field names within composite literals. These links should
  136. // be prefixed by the type name.
  137. fieldPath := ""
  138. prefix := ""
  139. switch typ := n.Type.(type) {
  140. case *ast.Ident:
  141. prefix = typ.Name + "."
  142. case *ast.SelectorExpr:
  143. if x, _ := typ.X.(*ast.Ident); x != nil {
  144. // Create links only if x is a qualified identifier.
  145. if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
  146. if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
  147. // spec.Path.Value is the import path
  148. if path, err := strconv.Unquote(spec.Path.Value); err == nil {
  149. // Register two links, one for the package
  150. // and one for the qualified identifier.
  151. linkMap[x] = link{path: path}
  152. linkMap[typ.Sel] = link{path: path, name: typ.Sel.Name}
  153. fieldPath = path
  154. prefix = typ.Sel.Name + "."
  155. }
  156. }
  157. }
  158. }
  159. }
  160. for _, e := range n.Elts {
  161. if kv, ok := e.(*ast.KeyValueExpr); ok {
  162. if k, ok := kv.Key.(*ast.Ident); ok {
  163. // Note: there is some syntactic ambiguity here. We cannot determine
  164. // if this is a struct literal or a map literal without type
  165. // information. We assume struct literal.
  166. name := prefix + k.Name
  167. linkMap[k] = link{path: fieldPath, name: name}
  168. }
  169. }
  170. }
  171. case *ast.Ident:
  172. if l, ok := linkMap[n]; ok {
  173. links = append(links, l)
  174. } else {
  175. l := link{name: n.Name}
  176. if n.Obj == nil && doc.IsPredeclared(n.Name) {
  177. l.path = builtinPkgPath
  178. }
  179. links = append(links, l)
  180. }
  181. }
  182. return true
  183. })
  184. return
  185. }