in internal/godoc/dochtml/internal/render/linkify.go [271:357]
func (r *Renderer) formatLineHTML(line string, idr *identifierResolver) safehtml.HTML {
var htmls []safehtml.HTML
var lastChar, nextChar byte
var numQuotes int
addLink := func(href, text string) {
htmls = append(htmls, ExecuteToHTML(LinkTemplate, Link{Href: href, Text: text}))
}
line = convertQuotes(line)
for len(line) > 0 {
m0, m1 := len(line), len(line)
if m := matchRx.FindStringIndex(line); m != nil {
m0, m1 = m[0], m[1]
}
if m0 > 0 {
nonWord := line[:m0]
htmls = append(htmls, safehtml.HTMLEscaped(nonWord))
lastChar = nonWord[len(nonWord)-1]
numQuotes += countQuotes(nonWord)
}
if m1 > m0 {
word := line[m0:m1]
nextChar = 0
if m1 < len(line) {
nextChar = line[m1]
}
// Reduce false-positives by having a list of allowed
// characters preceding and succeeding an identifier.
// Also, forbid ID linking within unbalanced quotes on same line.
validPrefix := strings.IndexByte("\x00 \t()[]*\n", lastChar) >= 0
validSuffix := strings.IndexByte("\x00 \t()[]:;,.'\n", nextChar) >= 0
forbidLinking := !validPrefix || !validSuffix || numQuotes%2 != 0
// TODO: Should we provide hotlinks for related packages?
switch {
case strings.Contains(word, "://"):
// Forbid closing brackets without prior opening brackets.
// See https://golang.org/issue/22285.
if i := strings.IndexByte(word, ')'); i >= 0 && i < strings.IndexByte(word, '(') {
m1 = m0 + i
word = line[m0:m1]
}
if i := strings.IndexByte(word, ']'); i >= 0 && i < strings.IndexByte(word, '[') {
m1 = m0 + i
word = line[m0:m1]
}
// Require balanced pairs of parentheses.
// See https://golang.org/issue/5043.
for i := 0; strings.Count(word, "(") != strings.Count(word, ")") && i < 10; i++ {
m1 = strings.LastIndexAny(line[:m1], "()")
word = line[m0:m1]
}
for i := 0; strings.Count(word, "[") != strings.Count(word, "]") && i < 10; i++ {
m1 = strings.LastIndexAny(line[:m1], "[]")
word = line[m0:m1]
}
addLink(word, word)
// Match "RFC ..." to link RFCs.
case strings.HasPrefix(word, "RFC") && len(word) > 3 && unicode.IsSpace(rune(word[3])):
// Strip all characters except for letters, numbers, and '.' to
// obtain RFC fields.
rfcFields := strings.FieldsFunc(word, func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '.'
})
if len(rfcFields) >= 4 {
// RFC x Section y
addLink(fmt.Sprintf("https://rfc-editor.org/rfc/rfc%s.html#section-%s", rfcFields[1], rfcFields[3]), word)
} else if len(rfcFields) >= 2 {
// RFC x
addLink(fmt.Sprintf("https://rfc-editor.org/rfc/rfc%s.html", rfcFields[1]), word)
}
case !forbidLinking && !r.disableHotlinking && idr != nil: // && numQuotes%2 == 0:
htmls = append(htmls, idr.toHTML(word))
default:
htmls = append(htmls, safehtml.HTMLEscaped(word))
}
numQuotes += countQuotes(word)
}
line = line[m1:]
}
return safehtml.HTMLConcat(htmls...)
}