in internal/godoc/dochtml/internal/render/linkify.go [377:504]
func (r *Renderer) formatDeclHTML(decl ast.Decl, idr *identifierResolver) safehtml.HTML {
// Generate all anchor points and links for the given decl.
anchorPointsMap := generateAnchorPoints(decl)
anchorLinksMap := generateAnchorLinks(idr, decl)
// Convert the maps (keyed by *ast.Ident) to slices of idKinds or URLs.
//
// This relies on the ast.Inspect and scanner.Scanner both
// visiting *ast.Ident and token.IDENT nodes in the same order.
var anchorPoints []idKind
var anchorLinks []string
ast.Inspect(decl, func(node ast.Node) bool {
if id, ok := node.(*ast.Ident); ok {
anchorPoints = append(anchorPoints, anchorPointsMap[id])
anchorLinks = append(anchorLinks, anchorLinksMap[id])
}
return true
})
// Trim large string literals and composite literals.
const (
maxStringSize = 125
maxElements = 100
)
decl = rewriteDecl(decl, maxStringSize, maxElements)
// Format decl as Go source code file.
p := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 4}
var b bytes.Buffer
p.Fprint(&b, r.fset, decl)
src := b.Bytes()
fset := token.NewFileSet()
file := fset.AddFile("", fset.Base(), b.Len())
// anchorLines is a list of anchor IDs that should be placed for each line.
// lineTypes is a list of the type (e.g., comment or code) of each line.
type lineType byte
const codeType, commentType lineType = 1 << 0, 1 << 1 // may OR together
numLines := bytes.Count(src, []byte("\n")) + 1
anchorLines := make([][]idKind, numLines)
lineTypes := make([]lineType, numLines)
htmlLines := make([][]safehtml.HTML, numLines)
// Scan through the source code, appropriately annotating it with HTML spans
// for comments, and HTML links and anchors for relevant identifiers.
var idIdx int // current index in anchorPoints and anchorLinks
var lastOffset int // last src offset copied to output buffer
var s scanner.Scanner
s.Init(file, src, nil, scanner.ScanComments)
scan:
for {
p, tok, lit := s.Scan()
line := file.Line(p) - 1 // current 0-indexed line number
offset := file.Offset(p) // current offset into source file
tokType := codeType // current token type (assume source code)
// Add traversed bytes from src to the appropriate line.
prevLines := strings.SplitAfter(string(src[lastOffset:offset]), "\n")
for i, ln := range prevLines {
n := line - len(prevLines) + i + 1
if n < 0 { // possible at EOF
n = 0
}
htmlLines[n] = append(htmlLines[n], safehtml.HTMLEscaped(ln))
}
lastOffset = offset
switch tok {
case token.EOF:
break scan
case token.COMMENT:
tokType = commentType
htmlLines[line] = append(htmlLines[line],
template.MustParseAndExecuteToHTML(`<span class="comment">`),
r.formatLineHTML(lit, idr),
template.MustParseAndExecuteToHTML(`</span>`))
lastOffset += len(lit)
case token.IDENT:
if idIdx < len(anchorPoints) && anchorPoints[idIdx].ID.String() != "" {
anchorLines[line] = append(anchorLines[line], anchorPoints[idIdx])
}
if idIdx < len(anchorLinks) && anchorLinks[idIdx] != "" {
htmlLines[line] = append(htmlLines[line], ExecuteToHTML(LinkTemplate, Link{Href: anchorLinks[idIdx], Text: lit}))
lastOffset += len(lit)
}
idIdx++
}
for i := strings.Count(strings.TrimSuffix(lit, "\n"), "\n"); i >= 0; i-- {
lineTypes[line+i] |= tokType
}
}
// Move anchor points up to the start of a comment
// if the next line has no anchors.
for i := range anchorLines {
if i+1 == len(anchorLines) || len(anchorLines[i+1]) == 0 {
j := i
for j > 0 && lineTypes[j-1] == commentType {
j--
}
anchorLines[i], anchorLines[j] = anchorLines[j], anchorLines[i]
}
}
// Emit anchor IDs and data-kind attributes for each relevant line.
var htmls []safehtml.HTML
for line, iks := range anchorLines {
inAnchor := false
for _, ik := range iks {
// Attributes for types and functions are handled in the template
// that generates the full documentation HTML.
if ik.Kind == "function" || ik.Kind == "type" {
continue
}
// Top-level methods are handled in the template, but interface methods
// are handled here.
if fd, ok := decl.(*ast.FuncDecl); ok && fd.Recv != nil {
continue
}
htmls = append(htmls, ExecuteToHTML(anchorTemplate, ik))
inAnchor = true
}
htmls = append(htmls, htmlLines[line]...)
if inAnchor {
htmls = append(htmls, template.MustParseAndExecuteToHTML("</span>"))
}
}
return safehtml.HTMLConcat(htmls...)
}