MM-68150: Upgrade golangci-lint to v2.12.2 (#36554)
Some checks are pending
API / build (push) Waiting to run
Server CI / Compute Go Version (push) Waiting to run
Server CI / Check mocks (push) Blocked by required conditions
Server CI / Check go mod tidy (push) Blocked by required conditions
Server CI / Check go fix (push) Blocked by required conditions
Server CI / check-style (push) Blocked by required conditions
Server CI / Check serialization methods for hot structs (push) Blocked by required conditions
Server CI / Vet API (push) Blocked by required conditions
Server CI / Check migration files (push) Blocked by required conditions
Server CI / Generate email templates (push) Blocked by required conditions
Server CI / Check store layers (push) Blocked by required conditions
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run

* 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 <build@mattermost.com>
This commit is contained in:
Jesse Hallam 2026-05-14 18:29:37 -03:00 committed by GitHub
parent d43dbe972e
commit d4fc0ecb1c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 215 additions and 77 deletions

View file

@ -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:

View file

@ -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

View file

@ -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 {

View file

@ -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))
}
}

View file

@ -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))
}

View file

@ -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",

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -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

View file

@ -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")}

View file

@ -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)

View file

@ -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))

View file

@ -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++

View file

@ -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 {

View file

@ -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)

View file

@ -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()
}

View file

@ -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)

View file

@ -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())
}

View file

@ -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

View file

@ -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))

View file

@ -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() {

View file

@ -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 {

View file

@ -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."

View file

@ -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
}

View file

@ -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)
}

View file

@ -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{

View file

@ -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.

View file

@ -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)

View file

@ -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 {

View file

@ -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" ||

View file

@ -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)
}
}
}

View file

@ -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
}

View file

@ -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