* perf: apply perfpsrint linter (#33967) * perf: apply perfpsrint linter * further simplifications * improved TestParseHashtags coverage * more simplifications * simplify renderBlockHTML further --------- Co-authored-by: Jesse Hallam <jesse@mattermost.com> * Fixed a bad merge --------- Co-authored-by: Catena cyber <35799796+catenacyber@users.noreply.github.com> Co-authored-by: Jesse Hallam <jesse@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
183e6c4a07
Коммит
b3d6c0c564
@@ -717,8 +717,8 @@ var (
|
|||||||
func ParseHashtags(text string) (string, string) {
|
func ParseHashtags(text string) (string, string) {
|
||||||
words := strings.Fields(text)
|
words := strings.Fields(text)
|
||||||
|
|
||||||
hashtagString := ""
|
var hashtagStringSb strings.Builder
|
||||||
plainString := ""
|
var plainString strings.Builder
|
||||||
for _, word := range words {
|
for _, word := range words {
|
||||||
// trim off surrounding punctuation
|
// trim off surrounding punctuation
|
||||||
word = puncStart.ReplaceAllString(word, "")
|
word = puncStart.ReplaceAllString(word, "")
|
||||||
@@ -728,11 +728,12 @@ func ParseHashtags(text string) (string, string) {
|
|||||||
word = hashtagStart.ReplaceAllString(word, "#")
|
word = hashtagStart.ReplaceAllString(word, "#")
|
||||||
|
|
||||||
if validHashtag.MatchString(word) {
|
if validHashtag.MatchString(word) {
|
||||||
hashtagString += " " + word
|
hashtagStringSb.WriteString(" " + word)
|
||||||
} else {
|
} else {
|
||||||
plainString += " " + word
|
plainString.WriteString(" " + word)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
hashtagString := hashtagStringSb.String()
|
||||||
|
|
||||||
if len(hashtagString) > 1000 {
|
if len(hashtagString) > 1000 {
|
||||||
hashtagString = hashtagString[:999]
|
hashtagString = hashtagString[:999]
|
||||||
@@ -744,7 +745,7 @@ func ParseHashtags(text string) (string, string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString)
|
return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClearMentionTags(post string) string {
|
func ClearMentionTags(post string) string {
|
||||||
|
|||||||
@@ -541,10 +541,58 @@ func TestStringArray_Equal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseHashtags(t *testing.T) {
|
func TestParseHashtags(t *testing.T) {
|
||||||
for input, output := range hashtags {
|
t.Run("basic hashtag extraction", func(t *testing.T) {
|
||||||
o, _ := ParseHashtags(input)
|
for input, output := range hashtags {
|
||||||
require.Equal(t, o, output, "failed to parse hashtags from input="+input+" expected="+output+" actual="+o)
|
o, _ := ParseHashtags(input)
|
||||||
}
|
require.Equal(t, o, output, "failed to parse hashtags from input="+input+" expected="+output+" actual="+o)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("long hashtag string truncation", func(t *testing.T) {
|
||||||
|
// Test case where hashtag string exceeds 1000 characters with a space to truncate at
|
||||||
|
longHashtags := "#test " + strings.Repeat("#verylonghashtag ", 50)
|
||||||
|
hashtagString, plainString := ParseHashtags(longHashtags)
|
||||||
|
require.NotEmpty(t, hashtagString)
|
||||||
|
require.LessOrEqual(t, len(hashtagString), 1000)
|
||||||
|
require.Empty(t, plainString)
|
||||||
|
// Ensure it truncated at a space
|
||||||
|
require.NotEqual(t, "", hashtagString)
|
||||||
|
require.True(t, hashtagString[len(hashtagString)-1] != ' ')
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("long hashtag string truncation without spaces", func(t *testing.T) {
|
||||||
|
// Test case where hashtag string exceeds 1000 characters with no space after position 999
|
||||||
|
// Create a single very long hashtag that will be truncated
|
||||||
|
veryLongHashtag := "#" + strings.Repeat("a", 1010)
|
||||||
|
hashtagString, plainString := ParseHashtags(veryLongHashtag)
|
||||||
|
// Should be empty because no space was found to truncate at
|
||||||
|
require.Equal(t, "", hashtagString)
|
||||||
|
require.Empty(t, plainString)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("plain text extraction", func(t *testing.T) {
|
||||||
|
hashtagString, plainString := ParseHashtags("hello #world this is #test plain text")
|
||||||
|
require.Equal(t, "#world #test", hashtagString)
|
||||||
|
require.Equal(t, "hello this is plain text", plainString)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("only plain text", func(t *testing.T) {
|
||||||
|
hashtagString, plainString := ParseHashtags("no hashtags here")
|
||||||
|
require.Empty(t, hashtagString)
|
||||||
|
require.Equal(t, "no hashtags here", plainString)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("only hashtags", func(t *testing.T) {
|
||||||
|
hashtagString, plainString := ParseHashtags("#one #two #three")
|
||||||
|
require.Equal(t, "#one #two #three", hashtagString)
|
||||||
|
require.Empty(t, plainString)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty string", func(t *testing.T) {
|
||||||
|
hashtagString, plainString := ParseHashtags("")
|
||||||
|
require.Empty(t, hashtagString)
|
||||||
|
require.Empty(t, plainString)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsValidAlphaNum(t *testing.T) {
|
func TestIsValidAlphaNum(t *testing.T) {
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ type FencedCode struct {
|
|||||||
RawCode []FencedCodeLine
|
RawCode []FencedCodeLine
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *FencedCode) Code() (result string) {
|
func (b *FencedCode) Code() string {
|
||||||
|
var resultSb strings.Builder
|
||||||
for _, code := range b.RawCode {
|
for _, code := range b.RawCode {
|
||||||
result += strings.Repeat(" ", code.Indentation) + b.markdown[code.Range.Position:code.Range.End]
|
resultSb.WriteString(strings.Repeat(" ", code.Indentation) + b.markdown[code.Range.Position:code.Range.End])
|
||||||
}
|
}
|
||||||
return
|
return resultSb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *FencedCode) Info() string {
|
func (b *FencedCode) Info() string {
|
||||||
|
|||||||
@@ -27,69 +27,75 @@ func RenderBlockHTML(block Block, referenceDefinitions []*ReferenceDefinition) (
|
|||||||
return renderBlockHTML(block, referenceDefinitions, false)
|
return renderBlockHTML(block, referenceDefinitions, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderBlockHTML(block Block, referenceDefinitions []*ReferenceDefinition, isTightList bool) (result string) {
|
func renderBlockHTML(block Block, referenceDefinitions []*ReferenceDefinition, isTightList bool) string {
|
||||||
|
var resultSb strings.Builder
|
||||||
|
|
||||||
switch v := block.(type) {
|
switch v := block.(type) {
|
||||||
case *Document:
|
case *Document:
|
||||||
for _, block := range v.Children {
|
for _, block := range v.Children {
|
||||||
result += RenderBlockHTML(block, referenceDefinitions)
|
resultSb.WriteString(RenderBlockHTML(block, referenceDefinitions))
|
||||||
}
|
}
|
||||||
case *Paragraph:
|
case *Paragraph:
|
||||||
if len(v.Text) == 0 {
|
if len(v.Text) == 0 {
|
||||||
return
|
return ""
|
||||||
}
|
}
|
||||||
if !isTightList {
|
if !isTightList {
|
||||||
result += "<p>"
|
resultSb.WriteString("<p>")
|
||||||
}
|
}
|
||||||
for _, inline := range v.ParseInlines(referenceDefinitions) {
|
for _, inline := range v.ParseInlines(referenceDefinitions) {
|
||||||
result += RenderInlineHTML(inline)
|
resultSb.WriteString(RenderInlineHTML(inline))
|
||||||
}
|
}
|
||||||
if !isTightList {
|
if !isTightList {
|
||||||
result += "</p>"
|
resultSb.WriteString("</p>")
|
||||||
}
|
}
|
||||||
case *List:
|
case *List:
|
||||||
if v.IsOrdered {
|
if v.IsOrdered {
|
||||||
if v.OrderedStart != 1 {
|
if v.OrderedStart != 1 {
|
||||||
result += fmt.Sprintf(`<ol start="%v">`, v.OrderedStart)
|
resultSb.WriteString(fmt.Sprintf(`<ol start="%v">`, v.OrderedStart))
|
||||||
} else {
|
} else {
|
||||||
result += "<ol>"
|
resultSb.WriteString("<ol>")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result += "<ul>"
|
resultSb.WriteString("<ul>")
|
||||||
}
|
}
|
||||||
for _, block := range v.Children {
|
for _, block := range v.Children {
|
||||||
result += renderBlockHTML(block, referenceDefinitions, !v.IsLoose)
|
resultSb.WriteString(renderBlockHTML(block, referenceDefinitions, !v.IsLoose))
|
||||||
}
|
}
|
||||||
if v.IsOrdered {
|
if v.IsOrdered {
|
||||||
result += "</ol>"
|
resultSb.WriteString("</ol>")
|
||||||
} else {
|
} else {
|
||||||
result += "</ul>"
|
resultSb.WriteString("</ul>")
|
||||||
}
|
}
|
||||||
case *ListItem:
|
case *ListItem:
|
||||||
result += "<li>"
|
resultSb.WriteString("<li>")
|
||||||
for _, block := range v.Children {
|
for _, block := range v.Children {
|
||||||
result += renderBlockHTML(block, referenceDefinitions, isTightList)
|
resultSb.WriteString(renderBlockHTML(block, referenceDefinitions, isTightList))
|
||||||
}
|
}
|
||||||
result += "</li>"
|
resultSb.WriteString("</li>")
|
||||||
case *BlockQuote:
|
case *BlockQuote:
|
||||||
result += "<blockquote>"
|
resultSb.WriteString("<blockquote>")
|
||||||
for _, block := range v.Children {
|
for _, block := range v.Children {
|
||||||
result += RenderBlockHTML(block, referenceDefinitions)
|
resultSb.WriteString(RenderBlockHTML(block, referenceDefinitions))
|
||||||
}
|
}
|
||||||
result += "</blockquote>"
|
resultSb.WriteString("</blockquote>")
|
||||||
case *FencedCode:
|
case *FencedCode:
|
||||||
if info := v.Info(); info != "" {
|
if info := v.Info(); info != "" {
|
||||||
language := strings.Fields(info)[0]
|
language := strings.Fields(info)[0]
|
||||||
result += `<pre><code class="language-` + htmlEscaper.Replace(language) + `">`
|
resultSb.WriteString(`<pre><code class="language-` + htmlEscaper.Replace(language) + `">`)
|
||||||
} else {
|
} else {
|
||||||
result += "<pre><code>"
|
resultSb.WriteString("<pre><code>")
|
||||||
}
|
}
|
||||||
result += htmlEscaper.Replace(v.Code()) + "</code></pre>"
|
resultSb.WriteString(htmlEscaper.Replace(v.Code()))
|
||||||
|
resultSb.WriteString("</code></pre>")
|
||||||
case *IndentedCode:
|
case *IndentedCode:
|
||||||
result += "<pre><code>" + htmlEscaper.Replace(v.Code()) + "</code></pre>"
|
resultSb.WriteString("<pre><code>")
|
||||||
|
resultSb.WriteString(htmlEscaper.Replace(v.Code()))
|
||||||
|
resultSb.WriteString("</code></pre>")
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("missing case for type %T", v))
|
panic(fmt.Sprintf("missing case for type %T", v))
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
return resultSb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func escapeURL(url string) (result string) {
|
func escapeURL(url string) (result string) {
|
||||||
@@ -137,31 +143,37 @@ func RenderInlineHTML(inline Inline) (result string) {
|
|||||||
}
|
}
|
||||||
result += ` />`
|
result += ` />`
|
||||||
case *InlineLink:
|
case *InlineLink:
|
||||||
result += `<a href="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `"`
|
var resultSb strings.Builder
|
||||||
|
resultSb.WriteString(`<a href="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `"`)
|
||||||
if title := v.Title(); title != "" {
|
if title := v.Title(); title != "" {
|
||||||
result += ` title="` + htmlEscaper.Replace(title) + `"`
|
resultSb.WriteString(` title="` + htmlEscaper.Replace(title) + `"`)
|
||||||
}
|
}
|
||||||
result += `>`
|
resultSb.WriteString(`>`)
|
||||||
for _, inline := range v.Children {
|
for _, inline := range v.Children {
|
||||||
result += RenderInlineHTML(inline)
|
resultSb.WriteString(RenderInlineHTML(inline))
|
||||||
}
|
}
|
||||||
result += "</a>"
|
resultSb.WriteString("</a>")
|
||||||
|
return resultSb.String()
|
||||||
case *ReferenceLink:
|
case *ReferenceLink:
|
||||||
result += `<a href="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `"`
|
var resultSb strings.Builder
|
||||||
|
resultSb.WriteString(`<a href="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `"`)
|
||||||
if title := v.Title(); title != "" {
|
if title := v.Title(); title != "" {
|
||||||
result += ` title="` + htmlEscaper.Replace(title) + `"`
|
resultSb.WriteString(` title="` + htmlEscaper.Replace(title) + `"`)
|
||||||
}
|
}
|
||||||
result += `>`
|
resultSb.WriteString(`>`)
|
||||||
for _, inline := range v.Children {
|
for _, inline := range v.Children {
|
||||||
result += RenderInlineHTML(inline)
|
resultSb.WriteString(RenderInlineHTML(inline))
|
||||||
}
|
}
|
||||||
result += "</a>"
|
resultSb.WriteString("</a>")
|
||||||
|
return resultSb.String()
|
||||||
case *Autolink:
|
case *Autolink:
|
||||||
result += `<a href="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `">`
|
var resultSb strings.Builder
|
||||||
|
resultSb.WriteString(`<a href="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `">`)
|
||||||
for _, inline := range v.Children {
|
for _, inline := range v.Children {
|
||||||
result += RenderInlineHTML(inline)
|
resultSb.WriteString(RenderInlineHTML(inline))
|
||||||
}
|
}
|
||||||
result += "</a>"
|
resultSb.WriteString("</a>")
|
||||||
|
return resultSb.String()
|
||||||
case *Emoji:
|
case *Emoji:
|
||||||
escapedName := htmlEscaper.Replace(v.Name)
|
escapedName := htmlEscaper.Replace(v.Name)
|
||||||
result += fmt.Sprintf(`<span data-emoji-name="%s" data-literal=":%s:" />`, escapedName, escapedName)
|
result += fmt.Sprintf(`<span data-emoji-name="%s" data-literal=":%s:" />`, escapedName, escapedName)
|
||||||
@@ -172,25 +184,30 @@ func RenderInlineHTML(inline Inline) (result string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderImageAltText(children []Inline) (result string) {
|
func renderImageAltText(children []Inline) string {
|
||||||
|
var resultSb strings.Builder
|
||||||
for _, inline := range children {
|
for _, inline := range children {
|
||||||
result += renderImageChildAltText(inline)
|
resultSb.WriteString(renderImageChildAltText(inline))
|
||||||
}
|
}
|
||||||
return
|
return resultSb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderImageChildAltText(inline Inline) (result string) {
|
func renderImageChildAltText(inline Inline) string {
|
||||||
switch v := inline.(type) {
|
switch v := inline.(type) {
|
||||||
case *Text:
|
case *Text:
|
||||||
return v.Text
|
return v.Text
|
||||||
case *InlineImage:
|
case *InlineImage:
|
||||||
|
var resultSb strings.Builder
|
||||||
for _, inline := range v.Children {
|
for _, inline := range v.Children {
|
||||||
result += renderImageChildAltText(inline)
|
resultSb.WriteString(renderImageChildAltText(inline))
|
||||||
}
|
}
|
||||||
|
return resultSb.String()
|
||||||
case *InlineLink:
|
case *InlineLink:
|
||||||
|
var resultSb strings.Builder
|
||||||
for _, inline := range v.Children {
|
for _, inline := range v.Children {
|
||||||
result += renderImageChildAltText(inline)
|
resultSb.WriteString(renderImageChildAltText(inline))
|
||||||
}
|
}
|
||||||
|
return resultSb.String()
|
||||||
}
|
}
|
||||||
return
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,12 @@ type IndentedCode struct {
|
|||||||
RawCode []IndentedCodeLine
|
RawCode []IndentedCodeLine
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *IndentedCode) Code() (result string) {
|
func (b *IndentedCode) Code() string {
|
||||||
|
var resultSb strings.Builder
|
||||||
for _, code := range b.RawCode {
|
for _, code := range b.RawCode {
|
||||||
result += strings.Repeat(" ", code.Indentation) + b.markdown[code.Range.Position:code.Range.End]
|
resultSb.WriteString(strings.Repeat(" ", code.Indentation) + b.markdown[code.Range.Position:code.Range.End])
|
||||||
}
|
}
|
||||||
return
|
return resultSb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *IndentedCode) Continuation(indentation int, r Range) *continuation {
|
func (b *IndentedCode) Continuation(indentation int, r Range) *continuation {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
package markdown
|
package markdown
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
type ReferenceDefinition struct {
|
type ReferenceDefinition struct {
|
||||||
RawDestination Range
|
RawDestination Range
|
||||||
|
|
||||||
@@ -24,10 +26,11 @@ func (d *ReferenceDefinition) Title() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseReferenceDefinition(markdown string, ranges []Range) (*ReferenceDefinition, []Range) {
|
func parseReferenceDefinition(markdown string, ranges []Range) (*ReferenceDefinition, []Range) {
|
||||||
raw := ""
|
var rawSb strings.Builder
|
||||||
for _, r := range ranges {
|
for _, r := range ranges {
|
||||||
raw += markdown[r.Position:r.End]
|
rawSb.WriteString(markdown[r.Position:r.End])
|
||||||
}
|
}
|
||||||
|
raw := rawSb.String()
|
||||||
|
|
||||||
label, next, ok := parseLinkLabel(raw, 0)
|
label, next, ok := parseLinkLabel(raw, 0)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user