mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-11-04 08:21:11 +00: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
 | 
						|
}
 |