From d4fc0ecb1c352280ecc07aa48386756cf234d6ad Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Thu, 14 May 2026 18:29:37 -0300 Subject: [PATCH] MM-68150: Upgrade golangci-lint to v2.12.2 (#36554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Simplify invite_people email parsing Replace backwards in-place mutation loop with a straightforward forward filter into a new slice. Extract into parseEmailList so the logic can be unit tested directly. * MM-68150: Upgrade golangci-lint to v2.12.2 Remove //go:fix inline from NewPointer, which is a generic function not yet supported by the inline analyzer, and fix 11 slicesbackward modernize issues flagged by the new version. * MM-68150: Enable all linters by default; disable those with >20 existing issues Switch from opt-in (default: none) to opt-out (default: all) so new linters added to golangci-lint are evaluated automatically. Explicitly disable every linter that has more than 20 pre-existing violations, deferring those for later cleanup. Also disable a handful of linters whose violations are intentional patterns in this codebase (nilerr, dogsled, sqlclosecheck, iotamixing, predeclared, containedctx, iface, gocheckcompilerdirectives, promlinter, goprintffuncname, gomoddirectives). * MM-68150: Fix mirror linter issues Replace Write([]byte(s)) with WriteString(s), and FindIndex([]byte(s)) with FindStringIndex(s), to avoid unnecessary allocations. * MM-68150: Fix nosprintfhostport linter issue Use net.JoinHostPort to construct host:port strings instead of fmt.Sprintf with a manually formatted pattern. * MM-68150: Fix rowserrcheck and sqlclosecheck linter issues Check rows.Err() after iteration loops in schema_dump.go. In the sqlx_wrapper test, defer rows.Close() rather than closing inline. * MM-68150: Fix nilnesserr linter issues — wrong variable in error handlers In 11 places, a stale variable (often the outer err from a prior assignment) was used instead of the freshly-checked error variable (appErr, rowErr, jsonErr, writeErr, esErr). Each produces a typed-nil wrapped in a non-nil interface, silently discarding the real error. * MM-68150: Add i18n string for app.compile_csv_chunks.write_error --------- Co-authored-by: Mattermost Build --- server/.golangci.yml | 94 ++++++++++++++++--- server/Makefile | 2 +- .../api4/outgoing_oauth_connection_test.go | 6 +- server/channels/app/channel.go | 2 +- server/channels/app/platform/web_hub.go | 2 +- server/channels/app/plugin_requests.go | 4 +- server/channels/app/reaction.go | 2 +- server/channels/app/report.go | 2 +- .../slashcommands/command_custom_status.go | 2 +- .../slashcommands/command_invite_people.go | 20 ++-- .../command_invite_people_test.go | 52 ++++++++++ server/channels/app/user.go | 2 +- server/channels/jobs/batch_report_worker.go | 2 +- .../export_users_to_csv.go | 2 +- server/channels/store/sqlstore/job_store.go | 2 +- server/channels/store/sqlstore/schema_dump.go | 9 ++ .../channels/store/sqlstore/session_store.go | 2 +- .../store/sqlstore/sqlx_wrapper_test.go | 5 +- server/channels/utils/license.go | 2 +- server/cmd/mmctl/commands/config_e2e_test.go | 4 +- server/cmd/mmctl/commands/config_test.go | 4 +- .../elasticsearch/opensearch/opensearch.go | 2 +- server/i18n/en.json | 4 + .../services/sharedchannel/sync_send.go | 4 +- server/platform/shared/mail/inbucket.go | 3 +- server/platform/shared/mail/mail_test.go | 4 +- server/public/model/builtin.go | 2 - server/public/model/config.go | 2 +- server/public/model/config_test.go | 6 +- server/public/model/utils_test.go | 2 +- server/public/shared/markdown/inspect.go | 34 +++---- server/public/shared/markdown/paragraph.go | 5 +- tools/mattermost-govet/Makefile | 2 +- 33 files changed, 215 insertions(+), 77 deletions(-) diff --git a/server/.golangci.yml b/server/.golangci.yml index d3f09acf71a..0476dcc59ce 100644 --- a/server/.golangci.yml +++ b/server/.golangci.yml @@ -1,20 +1,84 @@ version: "2" linters: - default: none - enable: - - bidichk - - errcheck - - govet - - ineffassign - - makezero - - misspell - - modernize - - revive - - staticcheck - - unconvert - - unqueryvet - - unused - - whitespace + default: all + disable: + - bodyclose + - canonicalheader + - containedctx # storing context.Context in a struct is an established pattern here + - contextcheck + - cyclop + - depguard + - dogsled # test helpers return many values; blank-heavy destructuring is idiomatic + - dupl + - dupword + - embeddedstructfieldcheck + - err113 + - errchkjson + - errname + - errorlint + - exhaustive + - exhaustruct + - forbidigo + - forcetypeassert + - funcorder + - funlen + - gocheckcompilerdirectives # //go:fix is a valid directive in Go 1.24+; linter doesn't know it yet + - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - gocyclo + - godoclint + - godot + - godox + - gomoddirectives # replace directives in go.mod are intentional forks + - gomodguard # deprecated since v2.12.0; replaced by gomodguard_v2 (enabled via default: all) + - goprintffuncname # Ephemeral → Ephemeralf rename is a plugin API breaking change; deferred + - gosec + - gosmopolitan + - iface # identical job interfaces are intentional — type-safe scheduling without coupling + - inamedparam + - interfacebloat + - intrange + - iotamixing # const blocks intentionally mix iota with explicit values (ABI stability, ASCII) + - ireturn + - lll + - maintidx + - mnd + - musttag + - nakedret + - nestif + - nilerr # intentionally dropping errors is common here (graceful degradation, security non-disclosure, fallbacks) + - nilnil + - nlreturn + - noctx + - noinlineerr + - nolintlint + - nonamedreturns + - paralleltest + - perfsprint + - prealloc + - predeclared # variable named 'copy' is intentional; already suppressed for revive + - promlinter # metric renames are a breaking change; deferred + - protogetter + - recvcheck + - sqlclosecheck # wrapper functions return *sqlx.Rows to callers who close them; not a real leak + - tagalign + - tagliatelle + - testableexamples + - testifylint + - testpackage + - thelper + - tparallel + - unparam + - usestdlibvars + - usetesting + - varnamelen + - wastedassign + - wrapcheck + - wsl + - wsl_v5 settings: govet: disable: diff --git a/server/Makefile b/server/Makefile index b6bbdecfe46..81970e5e986 100644 --- a/server/Makefile +++ b/server/Makefile @@ -328,7 +328,7 @@ golang-versions: ## Install Golang versions used for compatibility testing (e.g. export GO_COMPATIBILITY_TEST_VERSIONS="${GO_COMPATIBILITY_TEST_VERSIONS}" golangci-lint: setup-go-work ## Run golangci-lint on codebase - $(GO) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4 + $(GO) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 ifeq ($(BUILD_ENTERPRISE_READY),true) $(GOBIN)/golangci-lint run ./... ./public/... $(BUILD_ENTERPRISE_DIR)/... else diff --git a/server/channels/api4/outgoing_oauth_connection_test.go b/server/channels/api4/outgoing_oauth_connection_test.go index f8df04c41f5..0dab7bf7fc1 100644 --- a/server/channels/api4/outgoing_oauth_connection_test.go +++ b/server/channels/api4/outgoing_oauth_connection_test.go @@ -942,7 +942,7 @@ func TestHandlerOutgoingOAuthConnectionUpdate(t *testing.T) { th.AddPermissionToRole(t, model.PermissionManageOutgoingOAuthConnections.Id, model.SystemUserRoleId) body := &bytes.Buffer{} - body.Write([]byte(`{/}`)) + body.WriteString(`{/}`) req, err := http.NewRequest("PUT", "/", body) if err != nil { @@ -990,7 +990,7 @@ func TestHandlerOutgoingOAuthConnectionUpdate(t *testing.T) { th.AddPermissionToRole(t, model.PermissionManageOutgoingOAuthConnections.Id, model.SystemUserRoleId) body := &bytes.Buffer{} - body.Write([]byte(`{"Id": "` + model.NewId() + `", "name": "changed name"}`)) + body.WriteString(`{"Id": "` + model.NewId() + `", "name": "changed name"}`) req, err := http.NewRequest("PUT", "/", body) if err != nil { @@ -1133,7 +1133,7 @@ func TestHandlerOutgoingOAuthConnectionHandlerCreate(t *testing.T) { th.AddPermissionToRole(t, model.PermissionManageOutgoingOAuthConnections.Id, model.SystemUserRoleId) body := &bytes.Buffer{} - body.Write([]byte(`{/}`)) + body.WriteString(`{/}`) req, err := http.NewRequest("POST", "/", body) if err != nil { diff --git a/server/channels/app/channel.go b/server/channels/app/channel.go index 3acd05496e7..535de6a5b78 100644 --- a/server/channels/app/channel.go +++ b/server/channels/app/channel.go @@ -4153,7 +4153,7 @@ func (a *App) setSidebarCategoriesForConvertedGroupMessage(rctx request.CTX, gmC channelsCategory := categories.Categories[0] _, appErr = a.UpdateSidebarCategories(rctx, user.Id, gmConversionRequest.TeamID, []*model.SidebarCategoryWithChannels{channelsCategory}) if appErr != nil { - rctx.Logger().Error("Failed to add converted GM to default sidebar category for user", mlog.String("user_id", user.Id), mlog.Err(err)) + rctx.Logger().Error("Failed to add converted GM to default sidebar category for user", mlog.String("user_id", user.Id), mlog.Err(appErr)) } } diff --git a/server/channels/app/platform/web_hub.go b/server/channels/app/platform/web_hub.go index 9ca6c998b8b..e5e1990c435 100644 --- a/server/channels/app/platform/web_hub.go +++ b/server/channels/app/platform/web_hub.go @@ -164,7 +164,7 @@ func (ps *PlatformService) GetHubForUserId(userID string) *Hub { // https://mattermost.atlassian.net/browse/MM-26629. var hash maphash.Hash hash.SetSeed(ps.hashSeed) - _, err := hash.Write([]byte(userID)) + _, err := hash.WriteString(userID) if err != nil { ps.logger.Error("Unable to write userID to hash", mlog.String("userID", userID), mlog.Err(err)) } diff --git a/server/channels/app/plugin_requests.go b/server/channels/app/plugin_requests.go index ef033f4888d..ff535237357 100644 --- a/server/channels/app/plugin_requests.go +++ b/server/channels/app/plugin_requests.go @@ -232,7 +232,7 @@ func (ch *Channels) servePluginRequest(w http.ResponseWriter, r *http.Request, h session, appErr := app.GetSession(token) if appErr != nil { if appErr.StatusCode == http.StatusInternalServerError { - handleInternalServerError(rctx, "Internal server error while loading session", err) + handleInternalServerError(rctx, "Internal server error while loading session", appErr) return } rctx.Logger().Debug("Token in plugin request is invalid. Treating request as unauthenticated", @@ -254,7 +254,7 @@ func (ch *Channels) servePluginRequest(w http.ResponseWriter, r *http.Request, h // If MFA is required and user has not activated it, treat it as unauthenticated if appErr := app.MFARequired(rctx); appErr != nil { if appErr.StatusCode == http.StatusInternalServerError { - handleInternalServerError(rctx, "Internal server error during MFA validation", err) + handleInternalServerError(rctx, "Internal server error during MFA validation", appErr) return } rctx.Logger().Warn("Treating session as unauthenticated since MFA required", diff --git a/server/channels/app/reaction.go b/server/channels/app/reaction.go index 7ece20157d8..126264e95c9 100644 --- a/server/channels/app/reaction.go +++ b/server/channels/app/reaction.go @@ -165,7 +165,7 @@ func (a *App) DeleteReactionForPost(rctx request.CTX, reaction *model.Reaction) restrictDM, appErr := a.CheckIfChannelIsRestrictedDM(rctx, channel) if appErr != nil { - return err + return appErr } if restrictDM { diff --git a/server/channels/app/report.go b/server/channels/app/report.go index 9866469b52f..89b5de0c306 100644 --- a/server/channels/app/report.go +++ b/server/channels/app/report.go @@ -75,7 +75,7 @@ func (a *App) compileCSVChunks(prefix string, numberOfChunks int, headers []stri } _, writeErr := compiledBuf.Write(chunk) if writeErr != nil { - return err + return model.NewAppError("compileCSVChunks", "app.compile_csv_chunks.write_error", nil, "", http.StatusInternalServerError).Wrap(writeErr) } } diff --git a/server/channels/app/slashcommands/command_custom_status.go b/server/channels/app/slashcommands/command_custom_status.go index 914484a5b00..2dd320fa16e 100644 --- a/server/channels/app/slashcommands/command_custom_status.go +++ b/server/channels/app/slashcommands/command_custom_status.go @@ -119,7 +119,7 @@ func GetCustomStatus(message string) *model.CustomStatus { func removeUnicodeSkinTone(unicodeString string) string { skinToneDetectorRegex := regexp.MustCompile("-(1f3fb|1f3fc|1f3fd|1f3fe|1f3ff)") - skinToneLocations := skinToneDetectorRegex.FindIndex([]byte(unicodeString)) + skinToneLocations := skinToneDetectorRegex.FindStringIndex(unicodeString) if len(skinToneLocations) == 0 { return unicodeString diff --git a/server/channels/app/slashcommands/command_invite_people.go b/server/channels/app/slashcommands/command_invite_people.go index 601f0cec10b..011947299b7 100644 --- a/server/channels/app/slashcommands/command_invite_people.go +++ b/server/channels/app/slashcommands/command_invite_people.go @@ -41,6 +41,17 @@ func (*InvitePeopleProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model } } +func parseEmailList(message string) []string { + var emails []string + for token := range strings.FieldsSeq(message) { + token = strings.Trim(token, ",") + if strings.Contains(token, "@") { + emails = append(emails, token) + } + } + return emails +} + func (*InvitePeopleProvider) DoCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { if !a.HasPermissionToTeam(rctx, args.UserId, args.TeamId, model.PermissionInviteUser) { return &model.CommandResponse{Text: args.T("api.command_invite_people.permission.app_error"), ResponseType: model.CommandResponseTypeEphemeral} @@ -62,14 +73,7 @@ func (*InvitePeopleProvider) DoCommand(a *app.App, rctx request.CTX, args *model return &model.CommandResponse{ResponseType: model.CommandResponseTypeEphemeral, Text: args.T("api.command.invite_people.email_invitations_off")} } - emailList := strings.Fields(message) - - for i := len(emailList) - 1; i >= 0; i-- { - emailList[i] = strings.Trim(emailList[i], ",") - if !strings.Contains(emailList[i], "@") { - emailList = append(emailList[:i], emailList[i+1:]...) - } - } + emailList := parseEmailList(message) if len(emailList) == 0 { return &model.CommandResponse{ResponseType: model.CommandResponseTypeEphemeral, Text: args.T("api.command.invite_people.no_email")} diff --git a/server/channels/app/slashcommands/command_invite_people_test.go b/server/channels/app/slashcommands/command_invite_people_test.go index 7bba88f465f..33edc3a7d69 100644 --- a/server/channels/app/slashcommands/command_invite_people_test.go +++ b/server/channels/app/slashcommands/command_invite_people_test.go @@ -7,10 +7,62 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/mattermost/mattermost/server/public/model" ) +func TestParseEmailList(t *testing.T) { + tests := []struct { + name string + input string + expected []string + }{ + { + name: "single valid email", + input: "user@example.com", + expected: []string{"user@example.com"}, + }, + { + name: "multiple valid emails", + input: "a@example.com b@example.com", + expected: []string{"a@example.com", "b@example.com"}, + }, + { + name: "trailing commas stripped", + input: "a@example.com, b@example.com,", + expected: []string{"a@example.com", "b@example.com"}, + }, + { + name: "non-email tokens filtered out", + input: "notanemail a@example.com alsoinvalid", + expected: []string{"a@example.com"}, + }, + { + name: "comma immediately after email treated as one token", + input: "a@example.com,b@example.com", + expected: []string{"a@example.com,b@example.com"}, + }, + { + name: "empty input", + input: "", + expected: nil, + }, + { + name: "all tokens invalid", + input: "notanemail alsoinvalid", + expected: nil, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := parseEmailList(tc.input) + require.Equal(t, tc.expected, result) + }) + } +} + func TestInvitePeopleProvider(t *testing.T) { th := setup(t).initBasic(t) diff --git a/server/channels/app/user.go b/server/channels/app/user.go index 360fff44ca6..932b039dd0c 100644 --- a/server/channels/app/user.go +++ b/server/channels/app/user.go @@ -1832,7 +1832,7 @@ func (a *App) CreatePasswordRecoveryToken(rctx request.CTX, userID, email string // remove any previously created tokens for user appErr := a.InvalidatePasswordRecoveryTokensForUser(userID) if appErr != nil { - rctx.Logger().Warn("Error while deleting additional user tokens.", mlog.Err(err)) + rctx.Logger().Warn("Error while deleting additional user tokens.", mlog.Err(appErr)) } token := model.NewToken(model.TokenTypePasswordRecovery, string(jsonData)) diff --git a/server/channels/jobs/batch_report_worker.go b/server/channels/jobs/batch_report_worker.go index b0553003467..c8686421313 100644 --- a/server/channels/jobs/batch_report_worker.go +++ b/server/channels/jobs/batch_report_worker.go @@ -106,7 +106,7 @@ func (worker *BatchReportWorker) processChunk(job *model.Job, reportData []model appErr := worker.app.SaveReportChunk(worker.reportFormat, job.Id, fileCount, reportData) if appErr != nil { - return err + return appErr } fileCount++ diff --git a/server/channels/jobs/export_users_to_csv/export_users_to_csv.go b/server/channels/jobs/export_users_to_csv/export_users_to_csv.go index 0c89a929a88..d9655a6cd2a 100644 --- a/server/channels/jobs/export_users_to_csv/export_users_to_csv.go +++ b/server/channels/jobs/export_users_to_csv/export_users_to_csv.go @@ -114,7 +114,7 @@ func getData(app ExportUsersToCSVAppIFace) func(jobData model.StringMap) ([]mode users, appErr := app.GetUsersForReporting(filter) if appErr != nil { - return nil, nil, false, errors.Wrapf(err, "failed to get the next batch (column_value=%v, user_id=%v)", filter.FromColumnValue, filter.FromId) + return nil, nil, false, errors.Wrapf(appErr, "failed to get the next batch (column_value=%v, user_id=%v)", filter.FromColumnValue, filter.FromId) } if len(users) == 0 { diff --git a/server/channels/store/sqlstore/job_store.go b/server/channels/store/sqlstore/job_store.go index cb39c58d13d..8e4471990e6 100644 --- a/server/channels/store/sqlstore/job_store.go +++ b/server/channels/store/sqlstore/job_store.go @@ -447,7 +447,7 @@ func (jss SqlJobStore) Cleanup(expiryTime int64, batchSize int) error { var rowErr error rowsAffected, rowErr = sqlResult.RowsAffected() if rowErr != nil { - return errors.Wrap(err, "unable to delete jobs") + return errors.Wrap(rowErr, "unable to delete jobs") } time.Sleep(jobsCleanupDelay) diff --git a/server/channels/store/sqlstore/schema_dump.go b/server/channels/store/sqlstore/schema_dump.go index 385abeecfa7..312584bc61f 100644 --- a/server/channels/store/sqlstore/schema_dump.go +++ b/server/channels/store/sqlstore/schema_dump.go @@ -177,6 +177,9 @@ func (ss *SqlStore) getTableOptions() (map[string]map[string]string, error) { // Add option to the table tableOptions[tableName][key] = value } + if err := optionsRows.Err(); err != nil { + rErr = multierror.Append(rErr, errors.Wrap(err, "error iterating table options rows")) + } return tableOptions, rErr.ErrorOrNil() } @@ -253,6 +256,9 @@ func (ss *SqlStore) getTableSchemaInformation() (map[string]*model.DatabaseTable }) } } + if err := rows.Err(); err != nil { + rErr = multierror.Append(rErr, errors.Wrap(err, "error iterating schema rows")) + } return tablesMap, tableCollations, rErr.ErrorOrNil() } @@ -298,6 +304,9 @@ func (ss *SqlStore) getTableIndexes() (map[string][]model.DatabaseIndex, error) tableIndexes[tableName] = append(tableIndexes[tableName], index) } + if err := rows.Err(); err != nil { + rErr = multierror.Append(rErr, errors.Wrap(err, "error iterating index rows")) + } return tableIndexes, rErr.ErrorOrNil() } diff --git a/server/channels/store/sqlstore/session_store.go b/server/channels/store/sqlstore/session_store.go index 98e0e280d39..141d5a2acfc 100644 --- a/server/channels/store/sqlstore/session_store.go +++ b/server/channels/store/sqlstore/session_store.go @@ -381,7 +381,7 @@ func (me SqlSessionStore) Cleanup(expiryTime int64, batchSize int64) error { var rowErr error rowsAffected, rowErr = sqlResult.RowsAffected() if rowErr != nil { - return errors.Wrap(err, "unable to delete sessions") + return errors.Wrap(rowErr, "unable to delete sessions") } time.Sleep(sessionsCleanupDelay) diff --git a/server/channels/store/sqlstore/sqlx_wrapper_test.go b/server/channels/store/sqlstore/sqlx_wrapper_test.go index 01449d833c1..cdfec44f029 100644 --- a/server/channels/store/sqlstore/sqlx_wrapper_test.go +++ b/server/channels/store/sqlstore/sqlx_wrapper_test.go @@ -46,7 +46,10 @@ func TestSqlX(t *testing.T) { query := `SELECT pg_sleep(:timeout);` arg := struct{ Timeout int }{Timeout: 2} - _, err = tx.NamedQuery(query, arg) + rows, err := tx.NamedQuery(query, arg) + if rows != nil { + defer rows.Close() + } require.Equal(t, context.DeadlineExceeded, err) require.NoError(t, tx.Commit()) } diff --git a/server/channels/utils/license.go b/server/channels/utils/license.go index d4f97410bad..74930e4895a 100644 --- a/server/channels/utils/license.go +++ b/server/channels/utils/license.go @@ -123,7 +123,7 @@ func GetAndValidateLicenseFileFromDisk(location string) (*model.License, []byte, var license model.License if jsonErr := json.Unmarshal([]byte(licenseStr), &license); jsonErr != nil { - return nil, nil, fmt.Errorf("Found license key at %s but it appears to be invalid: %w", fileName, err) + return nil, nil, fmt.Errorf("Found license key at %s but it appears to be invalid: %w", fileName, jsonErr) } return &license, licenseBytes, nil diff --git a/server/cmd/mmctl/commands/config_e2e_test.go b/server/cmd/mmctl/commands/config_e2e_test.go index 4fcc9a1aa8f..e1ab559f995 100644 --- a/server/cmd/mmctl/commands/config_e2e_test.go +++ b/server/cmd/mmctl/commands/config_e2e_test.go @@ -51,7 +51,7 @@ func (s *MmctlE2ETestSuite) TestConfigPatchCmd() { invalidFile, err := os.CreateTemp(os.TempDir(), "invalid_config_*.json") s.Require().Nil(err) - _, err = tmpFile.Write([]byte(configFilePayload)) + _, err = tmpFile.WriteString(configFilePayload) s.Require().Nil(err) defer func() { @@ -212,7 +212,7 @@ rm $1'old'` defer func() { os.Remove(file.Name()) }() - _, err = file.Write([]byte(content)) + _, err = file.WriteString(content) s.Require().Nil(err) s.Require().Nil(file.Close()) s.Require().Nil(os.Chmod(file.Name(), 0700)) diff --git a/server/cmd/mmctl/commands/config_test.go b/server/cmd/mmctl/commands/config_test.go index f20a7cb44a2..b42dba1340b 100644 --- a/server/cmd/mmctl/commands/config_test.go +++ b/server/cmd/mmctl/commands/config_test.go @@ -600,10 +600,10 @@ func (s *MmctlUnitTestSuite) TestConfigPatchCmd() { pluginFile, err := os.CreateTemp(os.TempDir(), "plugin_config_*.json") s.Require().NoError(err) - _, err = tmpFile.Write([]byte(configFilePayload)) + _, err = tmpFile.WriteString(configFilePayload) s.Require().NoError(err) - _, err = pluginFile.Write([]byte(configFilePluginPayload)) + _, err = pluginFile.WriteString(configFilePluginPayload) s.Require().NoError(err) defer func() { diff --git a/server/enterprise/elasticsearch/opensearch/opensearch.go b/server/enterprise/elasticsearch/opensearch/opensearch.go index 30812f572e9..8932244657e 100644 --- a/server/enterprise/elasticsearch/opensearch/opensearch.go +++ b/server/enterprise/elasticsearch/opensearch/opensearch.go @@ -2308,7 +2308,7 @@ func checkMaxVersion(ctx context.Context, client *opensearchapi.Client) (string, major, _, _, esErr := common.GetVersionComponents(resp.Version.Number) if esErr != nil { - return "", 0, model.NewAppError("Opensearch.checkMaxVersion", "ent.elasticsearch.start.parse_server_version.app_error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError).Wrap(err) + return "", 0, model.NewAppError("Opensearch.checkMaxVersion", "ent.elasticsearch.start.parse_server_version.app_error", map[string]any{"Backend": model.ElasticsearchSettingsOSBackend}, "", http.StatusInternalServerError).Wrap(esErr) } if major > opensearchMaxVersion { diff --git a/server/i18n/en.json b/server/i18n/en.json index 09f24cf195a..15a5aa99763 100644 --- a/server/i18n/en.json +++ b/server/i18n/en.json @@ -5846,6 +5846,10 @@ "id": "app.compile_csv_chunks.header_error", "translation": "Failed to write CSV headers." }, + { + "id": "app.compile_csv_chunks.write_error", + "translation": "Failed to write CSV data." + }, { "id": "app.compile_report_chunks.unsupported_format", "translation": "Unsupported report format." diff --git a/server/platform/services/sharedchannel/sync_send.go b/server/platform/services/sharedchannel/sync_send.go index fcb77c70ac6..c0f900146fb 100644 --- a/server/platform/services/sharedchannel/sync_send.go +++ b/server/platform/services/sharedchannel/sync_send.go @@ -6,6 +6,7 @@ package sharedchannel import ( "context" "fmt" + "slices" "time" "github.com/mattermost/mattermost/server/public/model" @@ -529,8 +530,7 @@ func (scs *Service) notifyRemoteOffline(posts []*model.Post, rc *model.RemoteClu // range the slice in reverse so the newest posts are visited first; this ensures an ephemeral // get added where it is mostly likely to be seen. - for i := len(posts) - 1; i >= 0; i-- { - post := posts[i] + for _, post := range slices.Backward(posts) { if didNotify := notified[post.UserId]; didNotify { continue } diff --git a/server/platform/shared/mail/inbucket.go b/server/platform/shared/mail/inbucket.go index 851d1dd9a62..ccbdc87e68d 100644 --- a/server/platform/shared/mail/inbucket.go +++ b/server/platform/shared/mail/inbucket.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "io" + "net" "net/http" "os" "strings" @@ -178,5 +179,5 @@ func getInbucketHost() (host string) { if inbucket_port == "" { inbucket_port = "9001" } - return fmt.Sprintf("http://%s:%s", inbucket_host, inbucket_port) + return "http://" + net.JoinHostPort(inbucket_host, inbucket_port) } diff --git a/server/platform/shared/mail/mail_test.go b/server/platform/shared/mail/mail_test.go index 514a56d8a54..33555d4ee5a 100644 --- a/server/platform/shared/mail/mail_test.go +++ b/server/platform/shared/mail/mail_test.go @@ -254,13 +254,13 @@ func TestSendMailUsingConfigAdvanced(t *testing.T) { file1, err := os.CreateTemp("", "*") require.NoError(t, err) defer os.Remove(file1.Name()) - file1.Write([]byte("hello world")) + file1.WriteString("hello world") file1.Close() file2, err := os.CreateTemp("", "*") require.NoError(t, err) defer os.Remove(file2.Name()) - file2.Write([]byte("foo bar")) + file2.WriteString("foo bar") file2.Close() embeddedFiles := map[string]io.Reader{ diff --git a/server/public/model/builtin.go b/server/public/model/builtin.go index 5d52c72e180..1a0d3d55209 100644 --- a/server/public/model/builtin.go +++ b/server/public/model/builtin.go @@ -4,8 +4,6 @@ package model // NewPointer returns a pointer to the object passed. -// -//go:fix inline func NewPointer[T any](t T) *T { return new(t) } // SafeDereference returns the zero value of T if t is nil. diff --git a/server/public/model/config.go b/server/public/model/config.go index 543d751875d..e04fe346529 100644 --- a/server/public/model/config.go +++ b/server/public/model/config.go @@ -5397,7 +5397,7 @@ func structToMapFilteredByTag(t any, typeOfTag, filterTag string) map[string]any switch field.Kind() { case reflect.Struct: value = structToMapFilteredByTag(field.Interface(), typeOfTag, filterTag) - case reflect.Ptr: + case reflect.Pointer: indirectType := field.Elem() if indirectType.Kind() == reflect.Struct { value = structToMapFilteredByTag(indirectType.Interface(), typeOfTag, filterTag) diff --git a/server/public/model/config_test.go b/server/public/model/config_test.go index 19676351aca..41cf045f181 100644 --- a/server/public/model/config_test.go +++ b/server/public/model/config_test.go @@ -32,7 +32,7 @@ func TestConfigDefaults(t *testing.T) { t.Run("nowhere nil when partially initialized", func(t *testing.T) { var recursivelyUninitialize func(*Config, string, reflect.Value) recursivelyUninitialize = func(config *Config, name string, v reflect.Value) { - if v.Type().Kind() == reflect.Ptr { + if v.Type().Kind() == reflect.Pointer { // Ignoring these 2 settings. // TODO: remove them completely in v8.0. if name == "config.ElasticsearchSettings.BulkIndexingTimeWindowSeconds" || @@ -2937,10 +2937,10 @@ func TestConfigAccessTagsMapToValidPermissions(t *testing.T) { fieldPath := path + "." + field.Name elemType := field.Type - if elemType.Kind() == reflect.Ptr || elemType.Kind() == reflect.Slice { + if elemType.Kind() == reflect.Pointer || elemType.Kind() == reflect.Slice { elemType = elemType.Elem() } - if elemType.Kind() == reflect.Ptr { + if elemType.Kind() == reflect.Pointer { elemType = elemType.Elem() } if elemType.Kind() == reflect.Struct { diff --git a/server/public/model/utils_test.go b/server/public/model/utils_test.go index 67a39f1ccb4..4b04b26f92c 100644 --- a/server/public/model/utils_test.go +++ b/server/public/model/utils_test.go @@ -1087,7 +1087,7 @@ func checkNowhereNil(t *testing.T, name string, value any) bool { v := reflect.ValueOf(value) switch v.Type().Kind() { - case reflect.Ptr: + case reflect.Pointer: // Ignoring these 2 settings. // TODO: remove them completely in v8.0. if name == "config.ElasticsearchSettings.BulkIndexingTimeWindowSeconds" || diff --git a/server/public/shared/markdown/inspect.go b/server/public/shared/markdown/inspect.go index 151b9590244..b3eb2b6d5ee 100644 --- a/server/public/shared/markdown/inspect.go +++ b/server/public/shared/markdown/inspect.go @@ -3,6 +3,8 @@ package markdown +import "slices" + const ( // Assuming 64k maxSize of a post which can be stored in DB. // Allow scanning upto twice(arbitrary value) the post size. @@ -58,20 +60,20 @@ func InspectBlock(block Block, f func(Block) bool) { switch v := block.(type) { case *Document: - for i := len(v.Children) - 1; i >= 0; i-- { - stack = append(stack, v.Children[i]) + for _, v0 := range slices.Backward(v.Children) { + stack = append(stack, v0) } case *List: - for i := len(v.Children) - 1; i >= 0; i-- { - stack = append(stack, v.Children[i]) + for _, v0 := range slices.Backward(v.Children) { + stack = append(stack, v0) } case *ListItem: - for i := len(v.Children) - 1; i >= 0; i-- { - stack = append(stack, v.Children[i]) + for _, v0 := range slices.Backward(v.Children) { + stack = append(stack, v0) } case *BlockQuote: - for i := len(v.Children) - 1; i >= 0; i-- { - stack = append(stack, v.Children[i]) + for _, v0 := range slices.Backward(v.Children) { + stack = append(stack, v0) } } } @@ -103,20 +105,20 @@ func InspectInline(inline Inline, f func(Inline) bool) { switch v := inline.(type) { case *InlineImage: - for i := len(v.Children) - 1; i >= 0; i-- { - stack = append(stack, v.Children[i]) + for _, v0 := range slices.Backward(v.Children) { + stack = append(stack, v0) } case *InlineLink: - for i := len(v.Children) - 1; i >= 0; i-- { - stack = append(stack, v.Children[i]) + for _, v0 := range slices.Backward(v.Children) { + stack = append(stack, v0) } case *ReferenceImage: - for i := len(v.Children) - 1; i >= 0; i-- { - stack = append(stack, v.Children[i]) + for _, v0 := range slices.Backward(v.Children) { + stack = append(stack, v0) } case *ReferenceLink: - for i := len(v.Children) - 1; i >= 0; i-- { - stack = append(stack, v.Children[i]) + for _, v0 := range slices.Backward(v.Children) { + stack = append(stack, v0) } } } diff --git a/server/public/shared/markdown/paragraph.go b/server/public/shared/markdown/paragraph.go index aef01b5e151..5a2012d56de 100644 --- a/server/public/shared/markdown/paragraph.go +++ b/server/public/shared/markdown/paragraph.go @@ -4,6 +4,7 @@ package markdown import ( + "slices" "strings" ) @@ -51,8 +52,8 @@ func (b *Paragraph) Close() { b.Text = remaining } - for i := len(b.Text) - 1; i >= 0; i-- { - b.Text[i] = trimRightSpace(b.markdown, b.Text[i]) + for i, v := range slices.Backward(b.Text) { + b.Text[i] = trimRightSpace(b.markdown, v) if b.Text[i].Position < b.Text[i].End { break } diff --git a/tools/mattermost-govet/Makefile b/tools/mattermost-govet/Makefile index 05254a180c6..c05d7fd23a9 100644 --- a/tools/mattermost-govet/Makefile +++ b/tools/mattermost-govet/Makefile @@ -12,7 +12,7 @@ clean: rm -rf dist golangci-lint: - $(GO) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4 + $(GO) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 $(GOBIN)/golangci-lint run ./... check-style: golangci-lint