func()

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...)
}