mirror of
https://codeberg.org/davrot/forgejo.git
synced 2025-07-03 22:00:02 +02:00

A fix for a bug introduced by me earlier, where attempting to parse an issue reference in an empty token would crash. An empty token occurs if the search string is `\` or `"` (among other scenarios, probably). I'll make another PR that avoids having empty tokens (seems like a good idea). ### Tests - I added test coverage for Go changes... - [x] in their respective `*_test.go` for unit tests. ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [x] I do not want this change to show in the release notes. - [ ] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8260 Reviewed-by: Shiny Nematoda <snematoda@noreply.codeberg.org> Co-authored-by: Danko Aleksejevs <danko@very.lv> Co-committed-by: Danko Aleksejevs <danko@very.lv>
121 lines
2.1 KiB
Go
121 lines
2.1 KiB
Go
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package internal
|
|
|
|
import (
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type BoolOpt int
|
|
|
|
const (
|
|
BoolOptMust BoolOpt = iota
|
|
BoolOptShould
|
|
BoolOptNot
|
|
)
|
|
|
|
type Token struct {
|
|
Term string
|
|
Kind BoolOpt
|
|
Fuzzy bool
|
|
}
|
|
|
|
func (tk *Token) ParseIssueReference() (int64, error) {
|
|
term := tk.Term
|
|
if len(term) > 1 && (term[0] == '#' || term[0] == '!') {
|
|
term = term[1:]
|
|
}
|
|
return strconv.ParseInt(term, 10, 64)
|
|
}
|
|
|
|
type Tokenizer struct {
|
|
in *strings.Reader
|
|
}
|
|
|
|
func (t *Tokenizer) next() (tk Token, err error) {
|
|
var (
|
|
sb strings.Builder
|
|
r rune
|
|
)
|
|
tk.Kind = BoolOptShould
|
|
tk.Fuzzy = true
|
|
|
|
// skip all leading white space
|
|
for {
|
|
if r, _, err = t.in.ReadRune(); err == nil && r == ' ' {
|
|
//nolint:staticcheck,wastedassign // SA4006 the variable is used after the loop
|
|
r, _, err = t.in.ReadRune()
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
if err != nil {
|
|
return tk, err
|
|
}
|
|
|
|
// check for +/- op, increment to the next rune in both cases
|
|
switch r {
|
|
case '+':
|
|
tk.Kind = BoolOptMust
|
|
r, _, err = t.in.ReadRune()
|
|
case '-':
|
|
tk.Kind = BoolOptNot
|
|
r, _, err = t.in.ReadRune()
|
|
}
|
|
if err != nil {
|
|
return tk, err
|
|
}
|
|
|
|
// parse the string, escaping special characters
|
|
for esc := false; err == nil; r, _, err = t.in.ReadRune() {
|
|
if esc {
|
|
if !strings.ContainsRune("+-\\\"", r) {
|
|
sb.WriteRune('\\')
|
|
}
|
|
sb.WriteRune(r)
|
|
esc = false
|
|
continue
|
|
}
|
|
switch r {
|
|
case '\\':
|
|
esc = true
|
|
case '"':
|
|
if !tk.Fuzzy {
|
|
goto nextEnd
|
|
}
|
|
tk.Fuzzy = false
|
|
case ' ', '\t':
|
|
if tk.Fuzzy {
|
|
goto nextEnd
|
|
}
|
|
sb.WriteRune(r)
|
|
default:
|
|
sb.WriteRune(r)
|
|
}
|
|
}
|
|
nextEnd:
|
|
|
|
tk.Term = sb.String()
|
|
if err == io.EOF {
|
|
err = nil
|
|
} // do not consider EOF as an error at the end
|
|
return tk, err
|
|
}
|
|
|
|
// Tokenize the keyword
|
|
func (o *SearchOptions) Tokens() (tokens []Token, err error) {
|
|
in := strings.NewReader(o.Keyword)
|
|
it := Tokenizer{in: in}
|
|
|
|
for token, err := it.next(); err == nil; token, err = it.next() {
|
|
tokens = append(tokens, token)
|
|
}
|
|
if err != nil && err != io.EOF {
|
|
return nil, err
|
|
}
|
|
|
|
return tokens, nil
|
|
}
|