chore: enable forcetypeassert linter, fix issues (#38804)

Enable [`forcetypeassert`](https://github.com/gostaticanalysis/forcetypeassert)
linter to prevent unchecked type assertions. ~650 issues fixed, most
fixes were clean, some use `setting.PanicInDevOrTesting`.

The only behaviour changes are where code would previously send a 500 error
or panic, a 4xx error is now emitted.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
silverwind
2026-08-09 10:25:06 +00:00
committed by GitHub
co-authored by wxiaoguang
parent fac8bf2eca
commit 76a81b24f9
248 changed files with 1110 additions and 995 deletions
+2 -2
View File
@@ -64,7 +64,7 @@ func MatchScopedWorkflows(
parsed []*ParsedScopedWorkflow,
consumerGitRepo *git.Repository,
consumerCommit *git.Commit,
triggedEvent webhook_module.HookEventType,
inputEvent webhook_module.HookEventType,
payload api.Payloader,
) (matched, filtered []*DetectedWorkflow) {
for _, p := range parsed {
@@ -78,7 +78,7 @@ func MatchScopedWorkflows(
TriggerEvent: evt,
Content: p.Content,
}
switch detectWorkflowMatch(ctx, consumerGitRepo, consumerCommit, triggedEvent, payload, evt) {
switch detectWorkflowMatch(ctx, consumerGitRepo, consumerCommit, inputEvent, payload, evt) {
case detectMatched:
matched = append(matched, dwf)
case detectFilteredOut:
+33 -14
View File
@@ -266,12 +266,22 @@ func DetectScheduledWorkflows(ctx context.Context, gitRepo *git.Repository, comm
return wfs, nil
}
func detectWorkflowMatch(ctx context.Context, gitRepo *git.Repository, commit *git.Commit, triggedEvent webhook_module.HookEventType, payload api.Payloader, evt *jobparser.Event) detectResult {
if !canGithubEventMatch(evt.Name, triggedEvent) {
// payloadAs returns the payload as the type the event is expected to carry
func payloadAs[T api.Payloader](payload api.Payloader, inputEvent webhook_module.HookEventType) T {
typedPayload, ok := payload.(T)
if !ok {
// the event type determines the payload type, so a mismatch can only be a programming error
panic(fmt.Errorf("event %q was triggered with payload type %T instead of %T", inputEvent, payload, typedPayload))
}
return typedPayload
}
func detectWorkflowMatch(ctx context.Context, gitRepo *git.Repository, commit *git.Commit, inputEvent webhook_module.HookEventType, payload api.Payloader, evt *jobparser.Event) detectResult {
if !canGithubEventMatch(evt.Name, inputEvent) {
return detectNotApplicable
}
switch triggedEvent {
switch inputEvent {
case // events with no activity types
webhook_module.HookEventCreate,
webhook_module.HookEventDelete,
@@ -279,21 +289,23 @@ func detectWorkflowMatch(ctx context.Context, gitRepo *git.Repository, commit *g
webhook_module.HookEventWiki,
webhook_module.HookEventSchedule:
if len(evt.Acts()) != 0 {
log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts())
log.Warn("Ignore unsupported %s event arguments %v", inputEvent, evt.Acts())
}
// no special filter parameters for these events, just return true if name matched
return detectMatched
case // push
webhook_module.HookEventPush:
return matchPushEvent(ctx, gitRepo, commit, payload.(*api.PushPayload), evt)
pushPayload := payloadAs[*api.PushPayload](payload, inputEvent)
return matchPushEvent(ctx, gitRepo, commit, pushPayload, evt)
case // issues
webhook_module.HookEventIssues,
webhook_module.HookEventIssueAssign,
webhook_module.HookEventIssueLabel,
webhook_module.HookEventIssueMilestone:
if matchIssuesEvent(payload.(*api.IssuePayload), evt) {
issuePayload := payloadAs[*api.IssuePayload](payload, inputEvent)
if matchIssuesEvent(issuePayload, evt) {
return detectMatched
}
return detectNotApplicable
@@ -303,7 +315,8 @@ func detectWorkflowMatch(ctx context.Context, gitRepo *git.Repository, commit *g
// `pull_request_comment` is same as `issue_comment`
// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment
webhook_module.HookEventPullRequestComment:
if matchIssueCommentEvent(payload.(*api.IssueCommentPayload), evt) {
issueCommentPayload := payloadAs[*api.IssueCommentPayload](payload, inputEvent)
if matchIssueCommentEvent(issueCommentPayload, evt) {
return detectMatched
}
return detectNotApplicable
@@ -315,46 +328,52 @@ func detectWorkflowMatch(ctx context.Context, gitRepo *git.Repository, commit *g
webhook_module.HookEventPullRequestLabel,
webhook_module.HookEventPullRequestReviewRequest,
webhook_module.HookEventPullRequestMilestone:
return matchPullRequestEvent(ctx, gitRepo, commit, payload.(*api.PullRequestPayload), evt)
pullRequestPayload := payloadAs[*api.PullRequestPayload](payload, inputEvent)
return matchPullRequestEvent(ctx, gitRepo, commit, pullRequestPayload, evt)
case // pull_request_review
webhook_module.HookEventPullRequestReviewApproved,
webhook_module.HookEventPullRequestReviewRejected:
if matchPullRequestReviewEvent(payload.(*api.PullRequestPayload), evt) {
reviewPayload := payloadAs[*api.PullRequestPayload](payload, inputEvent)
if matchPullRequestReviewEvent(reviewPayload, evt) {
return detectMatched
}
return detectNotApplicable
case // pull_request_review_comment
webhook_module.HookEventPullRequestReviewComment:
if matchPullRequestReviewCommentEvent(payload.(*api.PullRequestPayload), evt) {
reviewCommentPayload := payloadAs[*api.PullRequestPayload](payload, inputEvent)
if matchPullRequestReviewCommentEvent(reviewCommentPayload, evt) {
return detectMatched
}
return detectNotApplicable
case // release
webhook_module.HookEventRelease:
if matchReleaseEvent(payload.(*api.ReleasePayload), evt) {
releasePayload := payloadAs[*api.ReleasePayload](payload, inputEvent)
if matchReleaseEvent(releasePayload, evt) {
return detectMatched
}
return detectNotApplicable
case // registry_package
webhook_module.HookEventPackage:
if matchPackageEvent(payload.(*api.PackagePayload), evt) {
packagePayload := payloadAs[*api.PackagePayload](payload, inputEvent)
if matchPackageEvent(packagePayload, evt) {
return detectMatched
}
return detectNotApplicable
case // workflow_run
webhook_module.HookEventWorkflowRun:
if matchWorkflowRunEvent(payload.(*api.WorkflowRunPayload), evt) {
workflowRunPayload := payloadAs[*api.WorkflowRunPayload](payload, inputEvent)
if matchWorkflowRunEvent(workflowRunPayload, evt) {
return detectMatched
}
return detectNotApplicable
default:
log.Warn("unsupported event %q", triggedEvent)
log.Warn("unsupported event %q", inputEvent)
return detectNotApplicable
}
}
+7 -3
View File
@@ -37,7 +37,9 @@ func TestEmbed(t *testing.T) {
assert.Equal(t, "a", string(content))
fi, err := fs.Stat(efs, "a.txt")
require.NoError(t, err)
_, ok := fi.(EmbeddedFileInfo).GetGzipContent()
fiEmbedded, ok := fi.(EmbeddedFileInfo)
require.True(t, ok)
_, ok = fiEmbedded.GetGzipContent()
assert.False(t, ok)
// test a compressed file
@@ -48,7 +50,9 @@ func TestEmbed(t *testing.T) {
require.NoError(t, err)
assert.False(t, fi.Mode().IsDir())
assert.True(t, fi.Mode().IsRegular())
gzipContent, ok := fi.(EmbeddedFileInfo).GetGzipContent()
fiEmbedded, ok = fi.(EmbeddedFileInfo)
require.True(t, ok)
gzipContent, ok := fiEmbedded.GetGzipContent()
assert.True(t, ok)
assert.Greater(t, len(gzipContent), 1)
assert.Less(t, len(gzipContent), 1000)
@@ -82,7 +86,7 @@ func TestEmbed(t *testing.T) {
require.NoError(t, err)
hi, err := hf.Stat()
require.NoError(t, err)
fiEmbedded, ok := hi.(EmbeddedFileInfo)
fiEmbedded, ok = hi.(EmbeddedFileInfo)
require.True(t, ok)
gzipContent, ok = fiEmbedded.GetGzipContent()
assert.True(t, ok)
+7 -5
View File
@@ -64,15 +64,17 @@ func CreateTimeLimitCode[T time.Time | string](data string, minutes int, startTi
const format = "200601021504"
var start time.Time
var startTimeAny any = startTimeGeneric
if t, ok := startTimeAny.(time.Time); ok {
start = t
} else {
switch startTime := any(startTimeGeneric).(type) {
case time.Time:
start = startTime
case string:
var err error
start, err = time.ParseInLocation(format, startTimeAny.(string), time.Local)
start, err = time.ParseInLocation(format, startTime, time.Local)
if err != nil {
return "" // return an invalid code because the "parse" failed
}
default:
panic(fmt.Sprintf("unsupported start time type %T", startTime)) // it shouldn't happen
}
startStr := start.Format(format)
end := start.Add(time.Minute * time.Duration(minutes))
+1 -1
View File
@@ -25,7 +25,7 @@ func TestWithCacheContext(t *testing.T) {
c.Put(field, "my_config1", 1)
v, _ = c.Get(field, "my_config1")
assert.NotNil(t, v)
assert.Equal(t, 1, v.(int))
assert.Equal(t, 1, v)
c.Delete(field, "my_config1")
c.Delete(field, "my_config2") // remove a non-exist key
+2 -2
View File
@@ -82,7 +82,7 @@ func getLastCommitForPathsByCommitNode(ctx context.Context, gitRepo *Repository,
// We do a tree traversal with nodes sorted by commit time
heap := binaryheap.NewWith(func(a, b any) int {
if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) {
if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) { //nolint:forcetypeassert // this heap only ever holds *commitAndPaths
return 1
}
return -1
@@ -110,7 +110,7 @@ heaploop:
if !ok {
break
}
current := cIn.(*commitAndPaths)
current := cIn.(*commitAndPaths) //nolint:forcetypeassert // this heap only ever holds *commitAndPaths
// Load the parent commits for the one we are currently examining
numParents := current.commit.NumParents()
+1 -1
View File
@@ -144,7 +144,7 @@ func (repo *Repository) GetTagInfos(ctx context.Context, page, pageSize int) ([]
sortTagsByTime(tags)
tagsTotal = len(tags)
if page != 0 {
tags = util.PaginateSlice(tags, page, pageSize).([]*Tag)
tags = util.PaginateSlice(tags, page, pageSize)
}
return nil
}).
+3 -1
View File
@@ -174,8 +174,10 @@ func TestGlob(t *testing.T) {
} {
g, err := Compile(test.pattern, test.delimiters...)
require.NoError(t, err)
compiler, ok := g.(*globCompiler)
require.True(t, ok)
result := g.Match(test.match)
assert.Equal(t, test.should, result, "pattern %q matching %q should be %v but got %v, compiled=%s", test.pattern, test.match, test.should, result, g.(*globCompiler).regexpPattern)
assert.Equal(t, test.should, result, "pattern %q matching %q should be %v but got %v, compiled=%s", test.pattern, test.match, test.should, result, compiler.regexpPattern)
}
}
+3 -1
View File
@@ -21,7 +21,9 @@ func TestLockAndDo(t *testing.T) {
locker := newTestRedisLocker(t)
defaultLocker.Store(new(locker))
testLockAndDo(t)
require.NoError(t, locker.(*redisLocker).Close())
rl, ok := locker.(*redisLocker)
require.True(t, ok)
require.NoError(t, rl.Close())
})
t.Run("memory", func(t *testing.T) {
defaultLocker.Store(new(NewMemoryLocker()))
+9 -4
View File
@@ -26,13 +26,17 @@ func TestLocker(t *testing.T) {
defer test.MockVariableValue(&redisLockExpiry, 5*time.Second)() // make it shorter for testing
locker := newTestRedisLocker(t)
testLocker(t, locker)
testRedisLocker(t, locker.(*redisLocker))
require.NoError(t, locker.(*redisLocker).Close())
rl, ok := locker.(*redisLocker)
require.True(t, ok)
testRedisLocker(t, rl)
require.NoError(t, rl.Close())
})
t.Run("memory", func(t *testing.T) {
locker := NewMemoryLocker()
testLocker(t, locker)
testMemoryLocker(t, locker.(*memoryLocker))
ml, ok := locker.(*memoryLocker)
require.True(t, ok)
testMemoryLocker(t, ml)
})
}
@@ -162,7 +166,8 @@ func testRedisLocker(t *testing.T, locker *redisLocker) {
// It simulates that there are some problems with extending like network issues or redis server down.
v, ok := locker.mutexM.Load("test")
require.True(t, ok)
m := v.(*redsync.Mutex)
m, ok := v.(*redsync.Mutex)
require.True(t, ok)
_, _ = m.Unlock() // release it to make it impossible to extend
// In current design, callers can't know the lock can't be extended.
+5 -6
View File
@@ -52,11 +52,10 @@ func (l *redisLocker) Lock(ctx context.Context, key string) (ReleaseFunc, error)
func (l *redisLocker) TryLock(ctx context.Context, key string) (bool, ReleaseFunc, error) {
f, err := l.lock(ctx, key, 1)
var (
errTaken *redsync.ErrTaken
errNodeTaken *redsync.ErrNodeTaken
)
if errors.As(err, &errTaken) || errors.As(err, &errNodeTaken) {
if _, taken := errors.AsType[*redsync.ErrTaken](err); taken {
return false, f, nil
}
if _, nodeTaken := errors.AsType[*redsync.ErrNodeTaken](err); nodeTaken {
return false, f, nil
}
return err == nil, f, err
@@ -112,7 +111,7 @@ func (l *redisLocker) startExtend() {
toExtend := make([]*redsync.Mutex, 0)
l.mutexM.Range(func(_, value any) bool {
m := value.(*redsync.Mutex)
m := value.(*redsync.Mutex) //nolint:forcetypeassert // mutexM only ever holds *redsync.Mutex
// Extend the lock if it is not expired.
// Although the mutex will be removed from the map before it is released,
+5 -2
View File
@@ -177,13 +177,15 @@ func GetListenerTCP(network string, address *net.TCPAddr) (*net.TCPListener, err
// look for a provided listener
for i, l := range providedListeners {
if isSameAddr(l.Addr(), address) {
tcpListener := l.(*net.TCPListener) //nolint:forcetypeassert // a listener matching a *net.TCPAddr is a *net.TCPListener
providedListeners = append(providedListeners[:i], providedListeners[i+1:]...)
needsUnlink := providedListenersToUnlink[i]
providedListenersToUnlink = append(providedListenersToUnlink[:i], providedListenersToUnlink[i+1:]...)
activeListeners = append(activeListeners, l)
activeListenersToUnlink = append(activeListenersToUnlink, needsUnlink)
return l.(*net.TCPListener), nil
return tcpListener, nil
}
}
@@ -211,13 +213,14 @@ func GetListenerUnix(network string, address *net.UnixAddr) (*net.UnixListener,
// look for a provided listener
for i, l := range providedListeners {
if isSameAddr(l.Addr(), address) {
unixListener := l.(*net.UnixListener) //nolint:forcetypeassert // a listener matching a *net.UnixAddr is a *net.UnixListener
providedListeners = append(providedListeners[:i], providedListeners[i+1:]...)
needsUnlink := providedListenersToUnlink[i]
providedListenersToUnlink = append(providedListenersToUnlink[:i], providedListenersToUnlink[i+1:]...)
activeListenersToUnlink = append(activeListenersToUnlink, needsUnlink)
activeListeners = append(activeListeners, l)
unixListener := l.(*net.UnixListener)
if needsUnlink {
unixListener.SetUnlinkOnClose(true)
}
+6 -2
View File
@@ -44,10 +44,14 @@ func RestartProcess() (int, error) {
// Extract the fds from the listeners.
files := make([]*os.File, len(listeners))
for i, l := range listeners {
var err error
// Now, all our listeners actually have File() functions so instead of
// individually casting we just use a hacky interface
files[i], err = l.(filer).File()
lf, ok := l.(filer)
if !ok {
return 0, fmt.Errorf("listener %T does not provide a File", l)
}
var err error
files[i], err = lf.File()
if err != nil {
return 0, err
}
+6 -1
View File
@@ -7,6 +7,7 @@ package graceful
import (
"crypto/tls"
"fmt"
"net"
"os"
"strings"
@@ -260,7 +261,11 @@ func (wl *wrappedListener) Accept() (c net.Conn, err error) {
func (wl *wrappedListener) File() (*os.File, error) {
// returns a dup(2) - FD_CLOEXEC flag *not* set so the listening socket can be passed to child processes
return wl.Listener.(filer).File()
lf, ok := wl.Listener.(filer)
if !ok {
return nil, fmt.Errorf("listener %T does not provide a File", wl.Listener)
}
return lf.File()
}
type wrappedConn struct {
+1 -1
View File
@@ -63,7 +63,7 @@ func (t *traceBuiltinSpan) toString(out *strings.Builder, indent int) {
}
out.WriteString("\n")
for _, c := range t.ts.children {
span := c.internalSpans[t.internalSpanIdx].(*traceBuiltinSpan)
span := c.internalSpans[t.internalSpanIdx].(*traceBuiltinSpan) //nolint:forcetypeassert // this tracer only stores its own spans
span.toString(out, indent+2)
}
}
+4 -1
View File
@@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// "vendor span" is a simple demo for a span from a vendor library
@@ -82,7 +83,9 @@ func TestTraceStarter(t *testing.T) {
collectSpanNames(fullName, c)
}
}
collectSpanNames("", span.internalSpans[0].(*testTraceSpan).vendorSpan)
rootSpan, ok := span.internalSpans[0].(*testTraceSpan)
require.True(t, ok)
collectSpanNames("", rootSpan.vendorSpan)
assert.Equal(t, []string{
"/root",
"/root/span1",
+17 -6
View File
@@ -21,6 +21,7 @@ import (
"gitea.dev/modules/indexer/code/internal"
indexer_internal "gitea.dev/modules/indexer/internal"
inner_bleve "gitea.dev/modules/indexer/internal/bleve"
"gitea.dev/modules/json"
"gitea.dev/modules/setting"
"gitea.dev/modules/timeutil"
"gitea.dev/modules/typesniffer"
@@ -330,6 +331,17 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
searchResults := make([]*internal.SearchResult, len(result.Hits))
for i, hit := range result.Hits {
content, okContent := hit.Fields["Content"].(string)
language, okLanguage := hit.Fields["Language"].(string)
commitID, okCommitID := hit.Fields["CommitID"].(string)
updatedAt, okUpdatedAt := hit.Fields["UpdatedAt"].(string)
repoID, okRepoID := hit.Fields["RepoID"].(float64)
if !okContent || !okLanguage || !okCommitID || !okUpdatedAt || !okRepoID {
hitFieldsJson, _ := json.Marshal(hit.Fields)
setting.PanicInDevOrTesting("unexpected field types in search hit %q: %s", hit.ID, string(hitFieldsJson))
return 0, nil, nil, fmt.Errorf("unexpected field types in search hit %q", hit.ID)
}
startIndex, endIndex := -1, -1
for _, locations := range hit.Locations["Content"] {
location := locations[0]
@@ -343,21 +355,20 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
}
}
if len(hit.Locations["Filename"]) > 0 {
startIndex, endIndex = internal.FilenameMatchIndexPos(hit.Fields["Content"].(string))
startIndex, endIndex = internal.FilenameMatchIndexPos(content)
}
language := hit.Fields["Language"].(string)
var updatedUnix timeutil.TimeStamp
if t, err := time.Parse(time.RFC3339, hit.Fields["UpdatedAt"].(string)); err == nil {
if t, err := time.Parse(time.RFC3339, updatedAt); err == nil {
updatedUnix = timeutil.TimeStamp(t.Unix())
}
searchResults[i] = &internal.SearchResult{
RepoID: int64(hit.Fields["RepoID"].(float64)),
RepoID: int64(repoID),
StartIndex: startIndex,
EndIndex: endIndex,
Filename: internal.FilenameOfIndexerID(hit.ID),
Content: hit.Fields["Content"].(string),
CommitID: hit.Fields["CommitID"].(string),
Content: content,
CommitID: commitID,
UpdatedUnix: updatedUnix,
Language: language,
Color: enry.GetColor(language),
@@ -261,12 +261,21 @@ func convertResult(searchResult *es.SearchResponse, kw string, pageSize int) (in
return 0, nil, nil, err
}
content, okContent := res["content"].(string)
language, okLanguage := res["language"].(string)
commitID, okCommitID := res["commit_id"].(string)
updatedAt, okUpdatedAt := res["updated_at"].(float64)
if !okContent || !okLanguage || !okCommitID || !okUpdatedAt {
setting.PanicInDevOrTesting("unexpected field types in search hit %q: %s", hit.ID, string(hit.Source))
return 0, nil, nil, fmt.Errorf("unexpected field types in search hit %q", hit.ID)
}
// FIXME: There is no way to get the position the keyword on the content currently on the same request.
// So we get it from content, this may made the query slower. See
// https://discuss.elastic.co/t/fetching-position-of-keyword-in-matched-document/94291
var startIndex, endIndex int
if c, ok := hit.Highlight["filename"]; ok && len(c) > 0 {
startIndex, endIndex = internal.FilenameMatchIndexPos(res["content"].(string))
startIndex, endIndex = internal.FilenameMatchIndexPos(content)
} else if c, ok := hit.Highlight["content"]; ok && len(c) > 0 {
// FIXME: Since the highlighting content will include <em> and </em> for the keywords,
// now we should find the positions. But how to avoid html content which contains the
@@ -279,14 +288,12 @@ func convertResult(searchResult *es.SearchResponse, kw string, pageSize int) (in
panic(fmt.Sprintf("2===%#v", hit.Highlight))
}
language := res["language"].(string)
hits = append(hits, &internal.SearchResult{
RepoID: repoID,
Filename: fileName,
CommitID: res["commit_id"].(string),
Content: res["content"].(string),
UpdatedUnix: timeutil.TimeStamp(res["updated_at"].(float64)),
CommitID: commitID,
Content: content,
UpdatedUnix: timeutil.TimeStamp(updatedAt),
Language: language,
StartIndex: startIndex,
EndIndex: endIndex,
+1 -1
View File
@@ -24,7 +24,7 @@ var _ EventWriter = (*eventWriterConn)(nil)
func NewEventWriterConn(writerName string, writerMode WriterMode) EventWriter {
w := &eventWriterConn{EventWriterBaseImpl: NewEventWriterBase(writerName, "conn", writerMode)}
opt := writerMode.WriterOption.(WriterConnOption)
opt := writerMode.WriterOption.(WriterConnOption) //nolint:forcetypeassert // a conn writer is only created with WriterConnOption
w.connWriter = connWriter{
ReconnectOnMsg: opt.ReconnectOnMsg,
Reconnect: opt.Reconnect,
+1 -1
View File
@@ -21,7 +21,7 @@ var _ EventWriter = (*eventWriterConsole)(nil)
func NewEventWriterConsole(name string, mode WriterMode) EventWriter {
w := &eventWriterConsole{EventWriterBaseImpl: NewEventWriterBase(name, "console", mode)}
opt := mode.WriterOption.(WriterConsoleOption)
opt := mode.WriterOption.(WriterConsoleOption) //nolint:forcetypeassert // a console writer is only created with WriterConsoleOption
if opt.Stderr {
w.OutputWriteCloser = util.NopCloser{Writer: os.Stderr}
} else {
+1 -1
View File
@@ -29,7 +29,7 @@ var _ EventWriter = (*eventWriterFile)(nil)
func NewEventWriterFile(name string, mode WriterMode) EventWriter {
w := &eventWriterFile{EventWriterBaseImpl: NewEventWriterBase(name, "file", mode)}
opt := mode.WriterOption.(WriterFileOption)
opt := mode.WriterOption.(WriterFileOption) //nolint:forcetypeassert // a file writer is only created with WriterFileOption
var err error
w.fileWriter, err = rotatingfilewriter.Open(opt.FileName, &rotatingfilewriter.Options{
Rotate: opt.LogRotate,
+4 -1
View File
@@ -7,6 +7,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSharedWorker(t *testing.T) {
@@ -37,6 +38,8 @@ func TestSharedWorker(t *testing.T) {
m.Close()
logs := w.(*dummyWriter).FetchLogs()
dw, ok := w.(*dummyWriter)
require.True(t, ok)
logs := dw.FetchLogs()
assert.Equal(t, []string{"msg-1\n", "msg-2\n", "msg-3\n"}, logs)
}
+13 -22
View File
@@ -237,10 +237,8 @@ func (b *footnoteBlockParser) Continue(node ast.Node, reader text.Reader, pc par
}
func (b *footnoteBlockParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
var list *FootnoteList
if tlist := pc.Get(footnoteListKey); tlist != nil {
list = tlist.(*FootnoteList)
} else {
list, _ := pc.Get(footnoteListKey).(*FootnoteList)
if list == nil {
list = NewFootnoteList()
pc.Set(footnoteListKey, list)
node.Parent().InsertBefore(node.Parent(), node, list)
@@ -295,17 +293,14 @@ func (s *footnoteParser) Parse(parent ast.Node, block text.Reader, pc parser.Con
value := block.Value(text.NewSegment(segment.Start+open, segment.Start+closes))
block.Advance(closes + 1)
var list *FootnoteList
if tlist := pc.Get(footnoteListKey); tlist != nil {
list = tlist.(*FootnoteList)
}
list, _ := pc.Get(footnoteListKey).(*FootnoteList)
if list == nil {
return nil
}
index := 0
name := []byte{}
for def := list.FirstChild(); def != nil; def = def.NextSibling() {
d := def.(*Footnote)
d := def.(*Footnote) //nolint:forcetypeassert // a FootnoteList only holds *Footnote children
if bytes.Equal(d.Ref, value) {
if d.Index < 0 {
list.Count++
@@ -339,10 +334,8 @@ func NewFootnoteASTTransformer() parser.ASTTransformer {
}
func (a *footnoteASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
var list *FootnoteList
if tlist := pc.Get(footnoteListKey); tlist != nil {
list = tlist.(*FootnoteList)
} else {
list, _ := pc.Get(footnoteListKey).(*FootnoteList)
if list == nil {
return
}
pc.Set(footnoteListKey, nil)
@@ -352,18 +345,16 @@ func (a *footnoteASTTransformer) Transform(node *ast.Document, reader text.Reade
if fc := container.LastChild(); fc != nil && ast.IsParagraph(fc) {
container = fc
}
footnoteNode := footnote.(*Footnote)
index := footnoteNode.Index
name := footnoteNode.Name
if index < 0 {
footnoteNode := footnote.(*Footnote) //nolint:forcetypeassert // a FootnoteList only holds *Footnote children
if footnoteNode.Index < 0 {
list.RemoveChild(list, footnote)
} else {
container.AppendChild(container, NewFootnoteBackLink(index, name))
container.AppendChild(container, NewFootnoteBackLink(footnoteNode.Index, footnoteNode.Name))
}
footnote = next
}
list.SortChildren(func(n1, n2 ast.Node) int {
if n1.(*Footnote).Index < n2.(*Footnote).Index {
if n1.(*Footnote).Index < n2.(*Footnote).Index { //nolint:forcetypeassert // a FootnoteList only holds *Footnote children
return -1
}
return 1
@@ -403,7 +394,7 @@ func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegist
func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if entering {
n := node.(*FootnoteLink)
n := node.(*FootnoteLink) //nolint:forcetypeassert // registered for KindFootnoteLink only
is := strconv.Itoa(n.Index)
_, _ = w.WriteString(`<sup id="fnref:user-content-`)
_, _ = w.Write(n.Name)
@@ -418,7 +409,7 @@ func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byt
func (r *FootnoteHTMLRenderer) renderFootnoteBackLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if entering {
n := node.(*FootnoteBackLink)
n := node.(*FootnoteBackLink) //nolint:forcetypeassert // registered for KindFootnoteBackLink only
_, _ = w.WriteString(` <a href="#fnref:user-content-`)
_, _ = w.Write(n.Name)
_, _ = w.WriteString(`" class="footnote-backref" role="doc-backlink">`)
@@ -429,7 +420,7 @@ func (r *FootnoteHTMLRenderer) renderFootnoteBackLink(w util.BufWriter, source [
}
func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*Footnote)
n := node.(*Footnote) //nolint:forcetypeassert // registered for KindFootnote only
if entering {
_, _ = w.WriteString(`<li id="fn:user-content-`)
_, _ = w.Write(n.Name)
+4 -6
View File
@@ -43,8 +43,8 @@ func (g *ASTTransformer) applyElementDir(n ast.Node) {
// Transform transforms the given AST tree.
func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
firstChild := node.FirstChild()
ctx := pc.Get(renderContextKey).(*markup.RenderContext)
rc := pc.Get(renderConfigKey).(*RenderConfig)
ctx := pc.Get(renderContextKey).(*markup.RenderContext) //nolint:forcetypeassert // the renderer always seeds this key before parsing
rc := pc.Get(renderConfigKey).(*RenderConfig) //nolint:forcetypeassert // the renderer always seeds this key before parsing
tocMode := ""
if rc.yamlNode != nil {
@@ -150,9 +150,7 @@ func (r *HTMLRenderer) renderCodeBlock(w util.BufWriter, source []byte, n ast.No
}
func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*ast.Document)
if val, has := n.AttributeString("lang"); has {
if val, has := node.AttributeString("lang"); has {
var err error
if entering {
_, err = w.WriteString("<div")
@@ -212,7 +210,7 @@ func (r *HTMLRenderer) renderRawHTML(w util.BufWriter, source []byte, node ast.N
if !entering {
return ast.WalkContinue, nil
}
n := node.(*RawHTML)
n := node.(*RawHTML) //nolint:forcetypeassert // registered for KindRawHTML only
_, err := w.WriteString(string(r.renderInternal.ProtectSafeAttrs(n.rawHTML)))
if err != nil {
return ast.WalkStop, err
+1 -1
View File
@@ -86,7 +86,7 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex
// Continue parses the current line and returns a result of parsing.
func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
block := node.(*Block)
block := node.(*Block) //nolint:forcetypeassert // this parser only ever opens *Block nodes
if block.Closed {
return parser.Close
}
@@ -49,7 +49,7 @@ func (r *BlockRenderer) writeLines(w util.BufWriter, source []byte, n gast.Node)
}
func (r *BlockRenderer) renderBlock(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
n := node.(*Block)
n := node.(*Block) //nolint:forcetypeassert // registered for KindBlock only
if entering {
codeHTML := giteaUtil.Iif[template.HTML](n.Inline, "", `<pre class="code-block is-loading">`) + `<code class="language-math">`
_, _ = w.WriteString(string(r.renderInternal.ProtectSafeAttrs(codeHTML)))
+2 -2
View File
@@ -19,8 +19,8 @@ func (n *Inline) Inline() {}
// IsBlank returns if this inline node is empty
func (n *Inline) IsBlank(source []byte) bool {
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
text := c.(*ast.Text).Segment
if !util.IsBlank(text.Value(source)) {
text := c.(*ast.Text) //nolint:forcetypeassert // an inline math node only holds text children
if !util.IsBlank(text.Segment.Value(source)) {
return false
}
}
@@ -160,12 +160,12 @@ func trimBlock(node *Inline, block text.Reader) {
}
// trim first space and last space
first := node.FirstChild().(*ast.Text)
first := node.FirstChild().(*ast.Text) //nolint:forcetypeassert // an inline math node only holds text children
if !(!first.Segment.IsEmpty() && block.Source()[first.Segment.Start] == ' ') {
return
}
last := node.LastChild().(*ast.Text)
last := node.LastChild().(*ast.Text) //nolint:forcetypeassert // an inline math node only holds text children
if !(!last.Segment.IsEmpty() && block.Source()[last.Segment.Stop-1] == ' ') {
return
}
@@ -30,7 +30,7 @@ func (r *InlineRenderer) renderInline(w util.BufWriter, source []byte, n ast.Nod
if entering {
_, _ = w.WriteString(string(r.renderInternal.ProtectSafeAttrs(`<code class="language-math">`)))
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
segment := c.(*ast.Text).Segment
segment := c.(*ast.Text).Segment //nolint:forcetypeassert // an inline math node only holds text children
value := util.EscapeHTML(segment.Value(source))
if bytes.HasSuffix(value, []byte("\n")) {
_, _ = w.Write(value[:len(value)-1])
@@ -18,7 +18,7 @@ import (
// renderAttention renders a quote marked with i.e. "> **Note**" or "> [!Warning]" with a corresponding svg
func (r *HTMLRenderer) renderAttention(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if entering {
n := node.(*Attention)
n := node.(*Attention) //nolint:forcetypeassert // registered for KindAttention only
var octiconName string
switch n.AttentionType {
case "tip":
+2 -2
View File
@@ -15,7 +15,7 @@ import (
)
func (r *HTMLRenderer) renderTaskCheckBoxListItem(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*TaskCheckBoxListItem)
n := node.(*TaskCheckBoxListItem) //nolint:forcetypeassert // registered for KindTaskCheckBoxListItem only
if entering {
if n.Attributes() != nil {
_, _ = w.WriteString("<li")
@@ -60,7 +60,7 @@ func (g *ASTTransformer) transformList(_ *markup.RenderContext, v *ast.List, rc
v.RemoveChildren(v)
for _, child := range children {
listItem := child.(*ast.ListItem)
listItem := child.(*ast.ListItem) //nolint:forcetypeassert // a list only holds list items
if !child.HasChildren() || !child.FirstChild().HasChildren() {
v.AppendChild(v, child)
continue
+2 -2
View File
@@ -22,8 +22,8 @@ func TestMigrationJSON_IssueOK(t *testing.T) {
func TestMigrationJSON_IssueFail(t *testing.T) {
issues := make([]*Issue, 0, 10)
err := Load("file_format_testdata/issue_b.json", &issues, true)
if _, ok := err.(*jsonschema.ValidationError); ok {
errors := strings.Split(err.(*jsonschema.ValidationError).GoString(), "\n")
if validationErr, ok := err.(*jsonschema.ValidationError); ok {
errors := strings.Split(validationErr.GoString(), "\n")
assert.Contains(t, errors[1], "missing properties")
assert.Contains(t, errors[1], "poster_id")
} else {
+17 -8
View File
@@ -56,21 +56,30 @@ func NewMultiHasher() *MultiHasher {
}
}
// marshalHash saves the state of a hash, every stdlib hash implements the marshaler interfaces
func marshalHash(h hash.Hash) ([]byte, error) {
return h.(encoding.BinaryMarshaler).MarshalBinary() //nolint:forcetypeassert // every hash used here is a stdlib hash
}
func unmarshalHash(h hash.Hash, state []byte) error {
return h.(encoding.BinaryUnmarshaler).UnmarshalBinary(state) //nolint:forcetypeassert // every hash used here is a stdlib hash
}
// MarshalBinary implements encoding.BinaryMarshaler
func (h *MultiHasher) MarshalBinary() ([]byte, error) {
md5Bytes, err := h.md5.(encoding.BinaryMarshaler).MarshalBinary()
md5Bytes, err := marshalHash(h.md5)
if err != nil {
return nil, err
}
sha1Bytes, err := h.sha1.(encoding.BinaryMarshaler).MarshalBinary()
sha1Bytes, err := marshalHash(h.sha1)
if err != nil {
return nil, err
}
sha256Bytes, err := h.sha256.(encoding.BinaryMarshaler).MarshalBinary()
sha256Bytes, err := marshalHash(h.sha256)
if err != nil {
return nil, err
}
sha512Bytes, err := h.sha512.(encoding.BinaryMarshaler).MarshalBinary()
sha512Bytes, err := marshalHash(h.sha512)
if err != nil {
return nil, err
}
@@ -89,22 +98,22 @@ func (h *MultiHasher) UnmarshalBinary(b []byte) error {
return errors.New("invalid hash state size")
}
if err := h.md5.(encoding.BinaryUnmarshaler).UnmarshalBinary(b[:marshaledSizeMD5]); err != nil {
if err := unmarshalHash(h.md5, b[:marshaledSizeMD5]); err != nil {
return err
}
b = b[marshaledSizeMD5:]
if err := h.sha1.(encoding.BinaryUnmarshaler).UnmarshalBinary(b[:marshaledSizeSHA1]); err != nil {
if err := unmarshalHash(h.sha1, b[:marshaledSizeSHA1]); err != nil {
return err
}
b = b[marshaledSizeSHA1:]
if err := h.sha256.(encoding.BinaryUnmarshaler).UnmarshalBinary(b[:marshaledSizeSHA256]); err != nil {
if err := unmarshalHash(h.sha256, b[:marshaledSizeSHA256]); err != nil {
return err
}
b = b[marshaledSizeSHA256:]
return h.sha512.(encoding.BinaryUnmarshaler).UnmarshalBinary(b[:marshaledSizeSHA512])
return unmarshalHash(h.sha512, b[:marshaledSizeSHA512])
}
// Write implements io.Writer
+7 -7
View File
@@ -107,13 +107,13 @@ func (e *MarshalEncoder) marshal(v any) error {
return e.marshalArray(val)
}
switch typ.Name() {
case "RubyUserMarshal":
return e.marshalUserMarshal(val.Interface().(RubyUserMarshal))
case "RubyUserDef":
return e.marshalUserDef(val.Interface().(RubyUserDef))
case "RubyObject":
return e.marshalObject(val.Interface().(RubyObject))
switch obj := val.Interface().(type) {
case RubyUserMarshal:
return e.marshalUserMarshal(obj)
case RubyUserDef:
return e.marshalUserDef(obj)
case RubyObject:
return e.marshalObject(obj)
}
return ErrUnsupportedType
+12
View File
@@ -5,13 +5,25 @@ package reqctx
import (
"context"
"fmt"
"io"
"maps"
"reflect"
"sync"
"gitea.dev/modules/process"
)
// MustContextValue returns the value stored under key. A missing or mistyped value can only
// be a programming error, and callers can't do anything useful with a zero value, so it panics.
func MustContextValue[T any](ctx context.Context, key any) T {
value, ok := ctx.Value(key).(T)
if !ok {
panic(fmt.Sprintf("context value %v is %T, expected %s", key, ctx.Value(key), reflect.TypeFor[T]()))
}
return value
}
type ContextDataProvider interface {
GetData() ContextData
}
+4 -4
View File
@@ -28,8 +28,8 @@ func RegenerateSession(resp http.ResponseWriter, req *http.Request) (Store, erro
f(resp, req)
}
if setting.IsInTesting {
if store := req.Context().Value(MockStoreContextKey); store != nil {
return store.(Store), nil
if store, ok := req.Context().Value(MockStoreContextKey).(Store); ok {
return store, nil
}
}
return session.RegenerateSession(resp, req)
@@ -37,8 +37,8 @@ func RegenerateSession(resp http.ResponseWriter, req *http.Request) (Store, erro
func GetContextSession(req *http.Request) Store {
if setting.IsInTesting {
if store := req.Context().Value(MockStoreContextKey); store != nil {
return store.(Store)
if store, ok := req.Context().Value(MockStoreContextKey).(Store); ok {
return store
}
}
return session.GetSession(req)
+1 -1
View File
@@ -99,7 +99,7 @@ func renderHTML(icon string, others ...any) (_ template.HTML, usingCache bool) {
cacheKey := svgCacheKey{icon, size, class}
cachedHTML, cached := svgCache.Load(cacheKey)
if cached && !svgItem.mocking {
return cachedHTML.(template.HTML), true
return cachedHTML.(template.HTML), true //nolint:forcetypeassert // svgCache only ever holds template.HTML
}
// the code is somewhat hacky, but it just works, because the SVG contents are all normalized
+3 -3
View File
@@ -145,7 +145,7 @@ func applyOp2(op operator, n1, n2 Num) Num {
f2, _ := util.ToFloat64(n2.Value)
return applyOp2Generic(op, f1, f2)
}
return applyOp2Generic(op, n1.Value.(int64), n2.Value.(int64))
return applyOp2Generic(op, n1.Value.(int64), n2.Value.(int64)) //nolint:forcetypeassert // castFloat64 above already ruled out float
}
func toOp(v any) (operator, error) {
@@ -321,13 +321,13 @@ func fnSum(nums []Num) Num {
if castFloat64(nums) {
var sum float64
for _, num := range nums {
sum += num.Value.(float64)
sum += num.Value.(float64) //nolint:forcetypeassert // castFloat64 reported every value is float64
}
return Num{sum}
}
var sum int64
for _, num := range nums {
sum += num.Value.(int64)
sum += num.Value.(int64) //nolint:forcetypeassert // castFloat64 ruled out float, so every value is int64
}
return Num{sum}
}
+4 -1
View File
@@ -9,6 +9,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func tokens(s string) (a []any) {
@@ -21,7 +22,9 @@ func tokens(s string) (a []any) {
func TestEval(t *testing.T) {
n, err := Expr(0, "/", 0.0)
assert.NoError(t, err)
assert.True(t, math.IsNaN(n.Value.(float64)))
nan, ok := n.Value.(float64)
require.True(t, ok)
assert.True(t, math.IsNaN(nan))
_, err = Expr(nil)
assert.ErrorContains(t, err, "unsupported token type")
+17 -21
View File
@@ -166,9 +166,9 @@ func newScopedTemplateSet(all *template.Template, name string) (*scopedTemplateS
var collectErr error // only need to collect the one error
collectTemplates = func(nodes []parse.Node) {
for _, node := range nodes {
if node.Type() == parse.NodeTemplate {
nodeTemplate := node.(*parse.TemplateNode)
subName := nodeTemplate.Name
switch node := node.(type) {
case *parse.TemplateNode:
subName := node.Name
if ts.htmlTemplates[subName] == nil {
subTmpl := all.Lookup(subName)
if subTmpl == nil {
@@ -185,26 +185,22 @@ func newScopedTemplateSet(all *template.Template, name string) (*scopedTemplateS
collectTemplates(subTmpl.Tree.Root.Nodes)
}
}
} else if node.Type() == parse.NodeList {
nodeList := node.(*parse.ListNode)
collectTemplates(nodeList.Nodes)
} else if node.Type() == parse.NodeIf {
nodeIf := node.(*parse.IfNode)
collectTemplates(nodeIf.BranchNode.List.Nodes)
if nodeIf.BranchNode.ElseList != nil {
collectTemplates(nodeIf.BranchNode.ElseList.Nodes)
case *parse.ListNode:
collectTemplates(node.Nodes)
case *parse.IfNode:
collectTemplates(node.BranchNode.List.Nodes)
if node.BranchNode.ElseList != nil {
collectTemplates(node.BranchNode.ElseList.Nodes)
}
} else if node.Type() == parse.NodeRange {
nodeRange := node.(*parse.RangeNode)
collectTemplates(nodeRange.BranchNode.List.Nodes)
if nodeRange.BranchNode.ElseList != nil {
collectTemplates(nodeRange.BranchNode.ElseList.Nodes)
case *parse.RangeNode:
collectTemplates(node.BranchNode.List.Nodes)
if node.BranchNode.ElseList != nil {
collectTemplates(node.BranchNode.ElseList.Nodes)
}
} else if node.Type() == parse.NodeWith {
nodeWith := node.(*parse.WithNode)
collectTemplates(nodeWith.BranchNode.List.Nodes)
if nodeWith.BranchNode.ElseList != nil {
collectTemplates(nodeWith.BranchNode.ElseList.Nodes)
case *parse.WithNode:
collectTemplates(node.BranchNode.List.Nodes)
if node.BranchNode.ElseList != nil {
collectTemplates(node.BranchNode.ElseList.Nodes)
}
}
}
+7 -3
View File
@@ -42,6 +42,10 @@ func NewRenderUtils(ctx reqctx.RequestContext) *RenderUtils {
return &RenderUtils{ctx: ctx, avatarUtils: NewAvatarUtils(ctx)}
}
func (ut *RenderUtils) locale() translation.Locale {
return ut.ctx.Value(translation.ContextKey).(translation.Locale) //nolint:forcetypeassert // the render context always carries a locale
}
// RenderCommitMessage renders commit message title (only title)
func (ut *RenderUtils) RenderCommitMessage(msg string, repo *repo.Repository) template.HTML {
msgLine := strings.TrimSpace(msg)
@@ -98,7 +102,7 @@ func (ut *RenderUtils) RenderIssueSimpleTitle(text string) template.HTML {
}
func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
locale := ut.ctx.Value(translation.ContextKey).(translation.Locale)
locale := ut.locale()
var extraCSSClasses string
textColor := util.ContrastColor(label.Color)
labelScope := label.ExclusiveScope()
@@ -279,7 +283,7 @@ func (ut *RenderUtils) RenderUnicodeEscapeToggleButton(escapeStatus *charset.Esc
if escapeStatus == nil || !escapeStatus.Escaped {
return ""
}
locale := ut.ctx.Value(translation.ContextKey).(translation.Locale)
locale := ut.locale()
var title template.HTML
if escapeStatus.HasAmbiguous {
title += locale.Tr("repo.ambiguous_runes_line")
@@ -376,7 +380,7 @@ func (ut *RenderUtils) AvatarStackPushCommit(pushCommit *repository.PushCommit)
// AvatarStackWithNames renders the avatar stack plus a label: `name` / `a and b` / `N people` (opens popup).
func (ut *RenderUtils) AvatarStackWithNames(data *user_model.AvatarStackData) template.HTML {
locale := ut.ctx.Value(translation.ContextKey).(translation.Locale)
locale := ut.locale()
participants := data.Participants
var b htmlutil.HTMLBuilder
+1 -2
View File
@@ -11,7 +11,6 @@ import (
"gitea.dev/modules/htmlutil"
"gitea.dev/modules/setting"
"gitea.dev/modules/svg"
"gitea.dev/modules/translation"
"gitea.dev/modules/util"
)
@@ -35,7 +34,7 @@ func (ut *RenderUtils) RenderTimelineEventBadge(c *issues_model.Comment) templat
func (ut *RenderUtils) RenderTimelineEventComment(c *issues_model.Comment, createdStr template.HTML) template.HTML {
if c.Type == issues_model.CommentTypeChangeTitle {
locale := ut.ctx.Value(translation.ContextKey).(translation.Locale)
locale := ut.locale()
isToggle, isWip := commentTimelineEventIsWipToggle(c)
if !isToggle {
return locale.Tr("repo.issues.change_title_at", ut.RenderEmoji(c.OldTitle), ut.RenderEmoji(c.NewTitle), createdStr)
+5 -1
View File
@@ -72,12 +72,16 @@ func (store *localeStore) AddLocaleByJSON(langName, langDesc string, source, mor
l.idxToMsgMap[idx] = v
case map[string]any:
for key, val := range v {
valStr, ok := val.(string)
if !ok {
return fmt.Errorf("unsupported value type %T for key %q", val, trKey+"."+key)
}
idx, ok := store.trKeyToIdxMap[trKey+"."+key]
if !ok {
idx = len(store.trKeyToIdxMap)
store.trKeyToIdxMap[trKey+"."+key] = idx
}
l.idxToMsgMap[idx] = val.(string)
l.idxToMsgMap[idx] = valStr
}
default:
return fmt.Errorf("unsupported value type %T for key %q", v, trKey)
+1 -1
View File
@@ -20,7 +20,7 @@ import (
// httpClient returns an HTTP client that honors Gitea's proxy configuration.
var httpClient = util.OnceValue[*http.Client]{
Func: func() *http.Client {
transport := http.DefaultTransport.(*http.Transport).Clone()
transport := http.DefaultTransport.(*http.Transport).Clone() //nolint:forcetypeassert // Golang stdlib
transport.Proxy = proxy.Proxy()
return &http.Client{Transport: transport}
},
+8 -3
View File
@@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestErrorTranslatable(t *testing.T) {
@@ -16,8 +17,10 @@ func TestErrorTranslatable(t *testing.T) {
err = ErrorWrapTranslatable(io.EOF, "key", 1)
assert.ErrorIs(t, err, io.EOF)
assert.Equal(t, "EOF", err.Error())
assert.Equal(t, "key", err.(*errorTranslatableWrapper).trKey)
assert.Equal(t, []any{1}, err.(*errorTranslatableWrapper).trArgs)
wrapped, ok := err.(*errorTranslatableWrapper)
require.True(t, ok)
assert.Equal(t, "key", wrapped.trKey)
assert.Equal(t, []any{1}, wrapped.trArgs)
err = ErrorWrap(err, "new msg %d", 100)
assert.ErrorIs(t, err, io.EOF)
@@ -25,5 +28,7 @@ func TestErrorTranslatable(t *testing.T) {
errTr := ErrorAsTranslatable(err)
assert.Equal(t, "EOF", errTr.Error())
assert.Equal(t, "key", errTr.(*errorTranslatableWrapper).trKey)
wrapped, ok = errTr.(*errorTranslatableWrapper)
require.True(t, ok)
assert.Equal(t, "key", wrapped.trKey)
}
+4 -1
View File
@@ -13,6 +13,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestKeygen(t *testing.T) {
@@ -55,6 +56,8 @@ func TestSignUsingKeys(t *testing.T) {
assert.NoError(t, err)
// Verify
err = rsa.VerifyPKCS1v15(pubParsed.(*rsa.PublicKey), crypto.SHA256, d, sig)
pubKey, ok := pubParsed.(*rsa.PublicKey)
require.True(t, ok)
err = rsa.VerifyPKCS1v15(pubKey, crypto.SHA256, d, sig)
assert.NoError(t, err)
}
-13
View File
@@ -1,13 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package util
func GetMapValueOrDefault[T any](m map[string]any, key string, defaultValue T) T {
if value, ok := m[key]; ok {
if v, ok := value.(T); ok {
return v
}
}
return defaultValue
}
-26
View File
@@ -1,26 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package util
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetMapValueOrDefault(t *testing.T) {
testMap := map[string]any{
"key1": "value1",
"key2": 42,
"key3": nil,
}
assert.Equal(t, "value1", GetMapValueOrDefault(testMap, "key1", "default"))
assert.Equal(t, 42, GetMapValueOrDefault(testMap, "key2", 0))
assert.Equal(t, "default", GetMapValueOrDefault(testMap, "key4", "default"))
assert.Equal(t, 100, GetMapValueOrDefault(testMap, "key5", 100))
assert.Equal(t, "default", GetMapValueOrDefault(testMap, "key3", "default"))
}
+7 -14
View File
@@ -3,31 +3,24 @@
package util
import "reflect"
// PaginateSlice cut a slice as per pagination options
// if page = 0 it do not paginate
func PaginateSlice(list any, page, pageSize int) any {
func PaginateSlice[S ~[]E, E any](list S, page, pageSize int) S {
if page <= 0 || pageSize <= 0 {
return list
}
if reflect.TypeOf(list).Kind() != reflect.Slice {
return list
}
listValue := reflect.ValueOf(list)
page--
if page*pageSize >= listValue.Len() {
return listValue.Slice(listValue.Len(), listValue.Len()).Interface()
if page*pageSize >= len(list) {
return list[len(list):]
}
listValue = listValue.Slice(page*pageSize, listValue.Len())
list = list[page*pageSize:]
if listValue.Len() > pageSize {
return listValue.Slice(0, pageSize).Interface()
if len(list) > pageSize {
return list[:pageSize]
}
return listValue.Interface()
return list
}
+7 -14
View File
@@ -11,24 +11,19 @@ import (
func TestPaginateSlice(t *testing.T) {
stringSlice := []string{"a", "b", "c", "d", "e"}
result, ok := PaginateSlice(stringSlice, 1, 2).([]string)
assert.True(t, ok)
result := PaginateSlice(stringSlice, 1, 2)
assert.Equal(t, []string{"a", "b"}, result)
result, ok = PaginateSlice(stringSlice, 100, 2).([]string)
assert.True(t, ok)
result = PaginateSlice(stringSlice, 100, 2)
assert.Equal(t, []string{}, result)
result, ok = PaginateSlice(stringSlice, 3, 2).([]string)
assert.True(t, ok)
result = PaginateSlice(stringSlice, 3, 2)
assert.Equal(t, []string{"e"}, result)
result, ok = PaginateSlice(stringSlice, 1, 0).([]string)
assert.True(t, ok)
result = PaginateSlice(stringSlice, 1, 0)
assert.Equal(t, []string{"a", "b", "c", "d", "e"}, result)
result, ok = PaginateSlice(stringSlice, 1, -1).([]string)
assert.True(t, ok)
result = PaginateSlice(stringSlice, 1, -1)
assert.Equal(t, []string{"a", "b", "c", "d", "e"}, result)
type Test struct {
@@ -36,11 +31,9 @@ func TestPaginateSlice(t *testing.T) {
}
testVar := []*Test{{Val: 2}, {Val: 3}, {Val: 4}}
testVar, ok = PaginateSlice(testVar, 1, 50).([]*Test)
assert.True(t, ok)
testVar = PaginateSlice(testVar, 1, 50)
assert.Equal(t, []*Test{{Val: 2}, {Val: 3}, {Val: 4}}, testVar)
testVar, ok = PaginateSlice(testVar, 2, 2).([]*Test)
assert.True(t, ok)
testVar = PaginateSlice(testVar, 2, 2)
assert.Equal(t, []*Test{{Val: 4}}, testVar)
}
+5 -4
View File
@@ -107,7 +107,7 @@ func FastCryptoRandomBytes(length int) []byte {
// ChaCha8 is about 20x times faster than system's crypto/rand.
// It is suitable for UUIDs, session IDs, etc
pool := chaCha8RandPool()
chaCha8Rand := pool.Get().(*rand2.ChaCha8)
chaCha8Rand := pool.Get().(*rand2.ChaCha8) //nolint:forcetypeassert // the pool's New only ever makes *rand2.ChaCha8
defer pool.Put(chaCha8Rand)
buf := make([]byte, length)
_, _ = chaCha8Rand.Read(buf)
@@ -270,15 +270,16 @@ func OptionalArg[T any](optArg []T, defaultValue ...T) (ret T) {
}
type EnumConst[T comparable] interface {
comparable
EnumValues() []T
}
// EnumValue returns the value if it's in the enum const's values,
// otherwise returns the first item of enums as default value.
func EnumValue[T comparable](val EnumConst[T]) (ret T, valid bool) {
func EnumValue[T EnumConst[T]](val T) (ret T, valid bool) {
enums := val.EnumValues()
if slices.Contains(enums, val.(T)) {
return val.(T), true
if slices.Contains(enums, val) {
return val, true
}
return enums[0], false
}
+7 -2
View File
@@ -4,6 +4,7 @@
package web
import (
"fmt"
"net/http"
"net/url"
"reflect"
@@ -37,8 +38,12 @@ func SetForm(dataStore reqctx.ContextDataProvider, obj any) {
}
// GetForm returns the validate form information
func GetForm(dataStore reqctx.RequestDataStore) any {
return dataStore.GetData()["__form"]
func GetForm[T any](dataStore reqctx.RequestDataStore) T {
form, ok := dataStore.GetData()["__form"].(T)
if !ok {
panic(fmt.Errorf("bound form %T does not match the requested type %s", dataStore.GetData()["__form"], reflect.TypeFor[T]()))
}
return form
}
// Router defines a route based on chi's router
+2 -2
View File
@@ -54,10 +54,10 @@ func (manager *loggerRequestManager) startSlowQueryDetector(threshold time.Durat
// print logs for slow requests
manager.reqRecords.Range(func(key, value any) bool {
index, record := key.(uint64), value.(*requestRecord)
record := value.(*requestRecord) //nolint:forcetypeassert // reqRecords only ever holds *requestRecord
if now.Sub(record.startTime) >= threshold {
manager.logPrint(StillExecutingEvent, record)
manager.reqRecords.Delete(index)
manager.reqRecords.Delete(key)
}
return true
})