Style add golangci config (#670)

* style(lint): gofumpt

* style(lint): errcheck

* style(lint): revive

* style(lint): gocritic

* style(lint): forcetypeassert

* style(lint): add .golangci.yml

* oops
This commit is contained in:
Samuel Berthe
2025-09-20 01:37:51 +02:00
committed by GitHub
parent 76b76a7adb
commit 9c8308ffda
20 changed files with 182 additions and 82 deletions

96
.golangci.yml Normal file
View File

@@ -0,0 +1,96 @@
version: "2"
run:
concurrency: 4
# also lint _test.go files
tests: true
timeout: 5m
linters:
enable:
- govet
- staticcheck
- unused
- errcheck
- gocritic
- gocyclo
- revive
- ineffassign
- unconvert
- goconst
# - depguard
- prealloc
# - dupl
- misspell
- bodyclose
- sqlclosecheck
- nilerr
- nestif
- forcetypeassert
- exhaustive
- funlen
# - wsl_v5
- testifylint
# disable noisy/controversial ones which you might enable later
disable:
- lll # line length — handled by gfmt/gofumpt
settings:
dupl:
threshold: 20 # lower => stricter (tokens)
errcheck:
check-type-assertions: true
funlen:
lines: 120
statements: 80
goconst:
min-len: 2
min-occurrences: 3
gocyclo:
min-complexity: 15 # strict; lower => stricter
wsl_v5:
allow-first-in-block: true
allow-whole-block: false
branch-max-lines: 2
testifylint:
disable:
- require-error
- float-compare
exclusions:
generated: lax
paths:
- examples$
rules:
- linters:
- revive
text: "^unused-parameter:"
- linters:
- revive
text: "^package-comments:"
- linters:
- errcheck
text: "Error return value of `.*\\.Body\\.Close` is not checked"
# linters disabled in tests
- linters:
- dupl
- goconst
- funlen
path: "_test\\.go$"
issues:
max-issues-per-linter: 0 # 0 = unlimited (we want ALL issues)
max-same-issues: 100
formatters:
enable:
- gofmt
- gofumpt
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

View File

@@ -8,6 +8,7 @@ import (
"github.com/samber/lo/internal/rand"
)
// DispatchingStrategy is a function that distributes messages to channels.
type DispatchingStrategy[T any] func(msg T, index uint64, channels []<-chan T) int
// ChannelDispatcher distributes messages from input channels into N child channels.
@@ -22,7 +23,7 @@ func ChannelDispatcher[T any](stream <-chan T, count int, channelBufferCap int,
// propagate channel closing to children
defer closeChannels(children)
var i uint64 = 0
var i uint64
for {
msg, ok := <-stream

View File

@@ -17,7 +17,7 @@ func (s *synchronize) Do(cb func()) {
}
// Synchronize wraps the underlying callback in a mutex. It receives an optional mutex.
func Synchronize(opt ...sync.Locker) *synchronize {
func Synchronize(opt ...sync.Locker) *synchronize { //nolint:revive
if len(opt) > 1 {
panic("unexpected arguments")
} else if len(opt) == 0 {

View File

@@ -26,9 +26,9 @@ type ifElse[T any] struct {
done bool
}
// If.
// If is a 1 line if/else statement.
// Play: https://go.dev/play/p/WSw3ApMxhyW
func If[T any](condition bool, result T) *ifElse[T] {
func If[T any](condition bool, result T) *ifElse[T] { //nolint:revive
if condition {
return &ifElse[T]{result, true}
}
@@ -37,9 +37,9 @@ func If[T any](condition bool, result T) *ifElse[T] {
return &ifElse[T]{t, false}
}
// IfF.
// IfF is a 1 line if/else statement whose options are functions
// Play: https://go.dev/play/p/WSw3ApMxhyW
func IfF[T any](condition bool, resultF func() T) *ifElse[T] {
func IfF[T any](condition bool, resultF func() T) *ifElse[T] { //nolint:revive
if condition {
return &ifElse[T]{resultF(), true}
}
@@ -98,7 +98,7 @@ type switchCase[T comparable, R any] struct {
// Switch is a pure functional switch/case/default statement.
// Play: https://go.dev/play/p/TGbKUMAeRUd
func Switch[T comparable, R any](predicate T) *switchCase[T, R] {
func Switch[T comparable, R any](predicate T) *switchCase[T, R] { //nolint:revive
var result R
return &switchCase[T, R]{

View File

@@ -25,7 +25,7 @@ func messageFromMsgAndArgs(msgAndArgs ...any) string {
return fmt.Sprintf("%+v", msgAndArgs[0])
}
if len(msgAndArgs) > 1 {
return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...)
return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) //nolint:errcheck,forcetypeassert
}
return ""
}
@@ -51,9 +51,8 @@ func must(err any, messageArgs ...any) {
message := messageFromMsgAndArgs(messageArgs...)
if message != "" {
panic(message + ": " + e.Error())
} else {
panic(e.Error())
}
panic(e.Error())
default:
panic("must: invalid err type '" + reflect.TypeOf(err).Name() + "', should either be a bool or an error")

View File

@@ -518,7 +518,9 @@ func TestTryWithErrorValue(t *testing.T) {
return errors.New("foo")
})
is.False(ok)
is.EqualError(err.(error), "foo")
e, isError := err.(error)
is.True(isError)
is.EqualError(e, "foo")
err, ok = TryWithErrorValue(func() error {
return nil
@@ -618,7 +620,7 @@ func TestAssert(t *testing.T) {
Assert(false, "user defined message")
})
//checks that the examples in `README.md` compile
// checks that the examples in `README.md` compile
{
age := 20
is.NotPanics(func() {
@@ -650,7 +652,7 @@ func TestAssertf(t *testing.T) {
Assertf(false, "user defined message %d %d", 1, 2)
})
//checks that the example in `README.md` compiles
// checks that the example in `README.md` compiles
{
age := 7
is.PanicsWithValue("assertion failed: user age must be >= 15, got 7", func() {

View File

@@ -631,7 +631,7 @@ func Samples[T any, Slice ~[]T](collection Slice, count int) Slice {
func SamplesBy[T any, Slice ~[]T](collection Slice, count int, randomIntGenerator randomIntGenerator) Slice {
size := len(collection)
copy := append(Slice{}, collection...)
cOpy := append(Slice{}, collection...)
results := Slice{}
@@ -639,12 +639,12 @@ func SamplesBy[T any, Slice ~[]T](collection Slice, count int, randomIntGenerato
copyLength := size - i
index := randomIntGenerator(size - i)
results = append(results, copy[index])
results = append(results, cOpy[index])
// Removes element.
// It is faster to swap with last element and remove it.
copy[index] = copy[copyLength-1]
copy = copy[:copyLength-1]
cOpy[index] = cOpy[copyLength-1]
cOpy = cOpy[:copyLength-1]
}
return results

View File

@@ -6,4 +6,8 @@ import (
"cmp"
)
// Ordered is a constraint that permits any ordered type: any type
// that supports the operators < <= >= >.
// If future releases of Go add new ordered types,
// this constraint will be modified to include them.
type Ordered = cmp.Ordered

View File

@@ -1,22 +1,30 @@
//go:build !go1.22
//nolint:revive
package rand
import "math/rand"
// Shuffle returns an array of shuffled values. Uses the Fisher-Yates shuffle algorithm.
func Shuffle(n int, swap func(i, j int)) {
rand.Shuffle(n, swap)
}
// IntN returns, as an int, a pseudo-random number in the half-open interval [0,n)
// from the default Source.
// It panics if n <= 0.
func IntN(n int) int {
// bearer:disable go_gosec_crypto_weak_random
return rand.Intn(n)
}
// Int64 returns a non-negative pseudo-random 63-bit integer as an int64
// from the default Source.
func Int64() int64 {
// bearer:disable go_gosec_crypto_weak_random
n := rand.Int63()
// bearer:disable go_gosec_crypto_weak_random
if rand.Intn(2) == 0 {
return -n

View File

@@ -4,14 +4,20 @@ package rand
import "math/rand/v2"
// Shuffle returns an array of shuffled values. Uses the Fisher-Yates shuffle algorithm.
func Shuffle(n int, swap func(i, j int)) {
rand.Shuffle(n, swap)
}
// IntN returns, as an int, a pseudo-random number in the half-open interval [0,n)
// from the default Source.
// It panics if n <= 0.
func IntN(n int) int {
return rand.IntN(n)
}
// Int64 returns a non-negative pseudo-random 63-bit integer as an int64
// from the default Source.
func Int64() int64 {
return rand.Int64()
}

2
map.go
View File

@@ -262,7 +262,7 @@ func ChunkEntries[K comparable, V any](m map[K]V, size int) []map[K]V {
chunksNum := count / size
if count%size != 0 {
chunksNum += 1
chunksNum++
}
result := make([]map[K]V, 0, chunksNum)

View File

@@ -477,7 +477,7 @@ func TestMapEntries(t *testing.T) {
// return v, k
// }, map[string]string{"1": "foo", "2": "bar", "true": "ccc"})
//}
//NormalMappers
// NormalMappers
{
mapEntriesTest(t, map[string]string{"foo": "1", "foo2": "2", "Foo": "2", "Foo2": "2", "bar": "2", "ccc": "true"}, func(k string, v string) (string, string) {
return k, k + v

20
math.go
View File

@@ -68,7 +68,7 @@ func Clamp[T constraints.Ordered](value T, min T, max T) T {
// Sum sums the values in a collection. If collection is empty 0 is returned.
// Play: https://go.dev/play/p/upfeJVqs4Bt
func Sum[T constraints.Float | constraints.Integer | constraints.Complex](collection []T) T {
var sum T = 0
var sum T
for i := range collection {
sum += collection[i]
}
@@ -78,9 +78,9 @@ func Sum[T constraints.Float | constraints.Integer | constraints.Complex](collec
// SumBy summarizes the values in a collection using the given return value from the iteration function. If collection is empty 0 is returned.
// Play: https://go.dev/play/p/Dz_a_7jN_ca
func SumBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) R) R {
var sum R = 0
var sum R
for i := range collection {
sum = sum + iteratee(collection[i])
sum += iteratee(collection[i])
}
return sum
}
@@ -116,28 +116,28 @@ func ProductBy[T any, R constraints.Float | constraints.Integer | constraints.Co
var product R = 1
for i := range collection {
product = product * iteratee(collection[i])
product *= iteratee(collection[i])
}
return product
}
// Mean calculates the mean of a collection of numbers.
func Mean[T constraints.Float | constraints.Integer](collection []T) T {
var length = T(len(collection))
length := T(len(collection))
if length == 0 {
return 0
}
var sum = Sum(collection)
sum := Sum(collection)
return sum / length
}
// MeanBy calculates the mean of a collection of numbers using the given return value from the iteration function.
func MeanBy[T any, R constraints.Float | constraints.Integer](collection []T, iteratee func(item T) R) R {
var length = R(len(collection))
length := R(len(collection))
if length == 0 {
return 0
}
var sum = SumBy(collection, iteratee)
sum := SumBy(collection, iteratee)
return sum / length
}
@@ -145,12 +145,12 @@ func MeanBy[T any, R constraints.Float | constraints.Integer](collection []T, it
// If multiple values have the same highest frequency, then multiple values are returned.
// If the collection is empty, then the zero value of T is returned.
func Mode[T constraints.Integer | constraints.Float](collection []T) []T {
var length = T(len(collection))
length := T(len(collection))
if length == 0 {
return []T{}
}
var mode = make([]T, 0)
mode := make([]T, 0)
maxFreq := 0
frequency := make(map[T]int)

View File

@@ -64,7 +64,7 @@ func Reverse[T any, Slice ~[]T](collection Slice) {
length := len(collection)
half := length / 2
for i := 0; i < half; i = i + 1 {
for i := 0; i < half; i++ {
j := length - 1 - i
collection[i], collection[j] = collection[j], collection[i]
}

View File

@@ -526,7 +526,6 @@ func TestNewThrottle(t *testing.T) {
reset()
th()
is.Equal(3, callCount)
}
func TestNewThrottleWithCount(t *testing.T) {
@@ -598,7 +597,6 @@ func TestNewThrottleBy(t *testing.T) {
th("a")
is.Equal(3, callCountA)
is.Equal(2, callCountB)
}
func TestNewThrottleByWithCount(t *testing.T) {

View File

@@ -211,7 +211,7 @@ func Chunk[T any, Slice ~[]T](collection Slice, size int) []Slice {
chunksNum := len(collection) / size
if len(collection)%size != 0 {
chunksNum += 1
chunksNum++
}
result := make([]Slice, 0, chunksNum)
@@ -662,13 +662,13 @@ func Slice[T any, Slice ~[]T](collection Slice, start int, end int) Slice {
// Replace returns a copy of the slice with the first n non-overlapping instances of old replaced by new.
// Play: https://go.dev/play/p/XfPzmf9gql6
func Replace[T comparable, Slice ~[]T](collection Slice, old T, new T, n int) Slice {
func Replace[T comparable, Slice ~[]T](collection Slice, old T, nEw T, n int) Slice {
result := make(Slice, len(collection))
copy(result, collection)
for i := range result {
if result[i] == old && n != 0 {
result[i] = new
result[i] = nEw
n--
}
}
@@ -678,8 +678,8 @@ func Replace[T comparable, Slice ~[]T](collection Slice, old T, new T, n int) Sl
// ReplaceAll returns a copy of the slice with all non-overlapping instances of old replaced by new.
// Play: https://go.dev/play/p/a9xZFUHfYcV
func ReplaceAll[T comparable, Slice ~[]T](collection Slice, old T, new T) Slice {
return Replace(collection, old, new, -1)
func ReplaceAll[T comparable, Slice ~[]T](collection Slice, old T, nEw T) Slice {
return Replace(collection, old, nEw, -1)
}
// Compact returns a slice of all non-zero elements.
@@ -732,15 +732,16 @@ func Splice[T any, Slice ~[]T](collection Slice, i int, elements ...T) Slice {
sizeElements := len(elements)
output := make(Slice, 0, sizeCollection+sizeElements) // preallocate memory for the output slice
if sizeElements == 0 {
switch {
case sizeElements == 0:
return append(output, collection...) // simple copy
} else if i > sizeCollection {
case i > sizeCollection:
// positive overflow
return append(append(output, collection...), elements...)
} else if i < -sizeCollection {
case i < -sizeCollection:
// negative overflow
return append(append(output, elements...), collection...)
} else if i < 0 {
case i < 0:
// backward
i = sizeCollection + i
}

View File

@@ -14,6 +14,7 @@ import (
)
var (
//nolint:revive
LowerCaseLettersCharset = []rune("abcdefghijklmnopqrstuvwxyz")
UpperCaseLettersCharset = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
LettersCharset = append(LowerCaseLettersCharset, UpperCaseLettersCharset...)
@@ -35,7 +36,7 @@ func RandomString(size int, charset []rune) string {
if size <= 0 {
panic("lo.RandomString: Size parameter must be greater than 0")
}
if len(charset) <= 0 {
if len(charset) == 0 {
panic("lo.RandomString: Charset parameter must not be empty")
}
@@ -44,27 +45,27 @@ func RandomString(size int, charset []rune) string {
sb.Grow(size)
// Calculate the number of bits required to represent the charset,
// e.g., for 62 characters, it would need 6 bits (since 62 -> 64 = 2^6)
letterIdBits := int(math.Log2(float64(nearestPowerOfTwo(len(charset)))))
letterIDBits := int(math.Log2(float64(nearestPowerOfTwo(len(charset)))))
// Determine the corresponding bitmask,
// e.g., for 62 characters, the bitmask would be 111111.
var letterIdMask int64 = 1<<letterIdBits - 1
var letterIDMask int64 = 1<<letterIDBits - 1
// Available count, since rand.Int64() returns a non-negative number, the first bit is fixed, so there are 63 random bits
// e.g., for 62 characters, this value is 10 (63 / 6).
letterIdMax := 63 / letterIdBits
letterIDMax := 63 / letterIDBits
// Generate the random string in a loop.
for i, cache, remain := size-1, rand.Int64(), letterIdMax; i >= 0; {
for i, cache, remain := size-1, rand.Int64(), letterIDMax; i >= 0; {
// Regenerate the random number if all available bits have been used
if remain == 0 {
cache, remain = rand.Int64(), letterIdMax
cache, remain = rand.Int64(), letterIDMax
}
// Select a character from the charset
if idx := int(cache & letterIdMask); idx < len(charset) {
if idx := int(cache & letterIDMask); idx < len(charset) {
sb.WriteRune(charset[idx])
i--
}
// Shift the bits to the right to prepare for the next character selection,
// e.g., for 62 characters, shift by 6 bits.
cache >>= letterIdBits
cache >>= letterIDBits
// Decrease the remaining number of uses for the current random number.
remain--
}
@@ -72,8 +73,8 @@ func RandomString(size int, charset []rune) string {
}
// nearestPowerOfTwo returns the nearest power of two.
func nearestPowerOfTwo(cap int) int {
n := cap - 1
func nearestPowerOfTwo(capacity int) int {
n := capacity - 1
n |= n >> 1
n |= n >> 2
n |= n >> 4
@@ -128,7 +129,7 @@ func ChunkString[T ~string](str T, size int) []T {
return []T{str}
}
var chunks = make([]T, 0, ((len(str)-1)/size)+1)
chunks := make([]T, 0, ((len(str)-1)/size)+1)
currentLen := 0
currentStart := 0
for i := range str {

View File

@@ -350,18 +350,14 @@ func TestZipBy(t *testing.T) {
r1 := ZipBy2(
[]string{"a", "b"},
[]int{1, 2},
func(a string, b int) Tuple2[string, int] {
return T2(a, b)
},
T2[string, int],
)
r2 := ZipBy3(
[]string{"a", "b", "c"},
[]int{1, 2, 3},
[]int{4, 5, 6},
func(a string, b int, c int) Tuple3[string, int, int] {
return T3(a, b, c)
},
T3[string, int, int],
)
r3 := ZipBy4(
@@ -369,9 +365,7 @@ func TestZipBy(t *testing.T) {
[]int{1, 2, 3, 4},
[]int{5, 6, 7, 8},
[]bool{true, true, true, true},
func(a string, b int, c int, d bool) Tuple4[string, int, int, bool] {
return T4(a, b, c, d)
},
T4[string, int, int, bool],
)
r4 := ZipBy5(
@@ -380,9 +374,7 @@ func TestZipBy(t *testing.T) {
[]int{6, 7, 8, 9, 10},
[]bool{true, true, true, true, true},
[]float32{0.1, 0.2, 0.3, 0.4, 0.5},
func(a string, b int, c int, d bool, e float32) Tuple5[string, int, int, bool, float32] {
return T5(a, b, c, d, e)
},
T5[string, int, int, bool, float32],
)
r5 := ZipBy6(
@@ -392,9 +384,7 @@ func TestZipBy(t *testing.T) {
[]bool{true, true, true, true, true, true},
[]float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6},
[]float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06},
func(a string, b int, c int, d bool, e float32, f float64) Tuple6[string, int, int, bool, float32, float64] {
return T6(a, b, c, d, e, f)
},
T6[string, int, int, bool, float32, float64],
)
r6 := ZipBy7(
@@ -405,9 +395,7 @@ func TestZipBy(t *testing.T) {
[]float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7},
[]float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07},
[]int8{1, 2, 3, 4, 5, 6, 7},
func(a string, b int, c int, d bool, e float32, f float64, g int8) Tuple7[string, int, int, bool, float32, float64, int8] {
return T7(a, b, c, d, e, f, g)
},
T7[string, int, int, bool, float32, float64, int8],
)
r7 := ZipBy8(
@@ -419,9 +407,7 @@ func TestZipBy(t *testing.T) {
[]float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08},
[]int8{1, 2, 3, 4, 5, 6, 7, 8},
[]int16{1, 2, 3, 4, 5, 6, 7, 8},
func(a string, b int, c int, d bool, e float32, f float64, g int8, h int16) Tuple8[string, int, int, bool, float32, float64, int8, int16] {
return T8(a, b, c, d, e, f, g, h)
},
T8[string, int, int, bool, float32, float64, int8, int16],
)
r8 := ZipBy9(
@@ -434,9 +420,7 @@ func TestZipBy(t *testing.T) {
[]int8{1, 2, 3, 4, 5, 6, 7, 8, 9},
[]int16{1, 2, 3, 4, 5, 6, 7, 8, 9},
[]int32{1, 2, 3, 4, 5, 6, 7, 8, 9},
func(a string, b int, c int, d bool, e float32, f float64, g int8, h int16, i int32) Tuple9[string, int, int, bool, float32, float64, int8, int16, int32] {
return T9(a, b, c, d, e, f, g, h, i)
},
T9[string, int, int, bool, float32, float64, int8, int16, int32],
)
is.Equal([]Tuple2[string, int]{

View File

@@ -106,7 +106,7 @@ func FromAnySlice[T any](in []any) (out []T, ok bool) {
result := make([]T, len(in))
for i := range in {
result[i] = in[i].(T)
result[i] = in[i].(T) //nolint:errcheck,forcetypeassert
}
return result, true
}

View File

@@ -27,7 +27,7 @@ func TestIsNil(t *testing.T) {
var ifaceWithNilValue any = (*string)(nil) //nolint:staticcheck
is.True(IsNil(ifaceWithNilValue))
is.False(ifaceWithNilValue == nil) //nolint:staticcheck
is.False(ifaceWithNilValue == nil) //nolint:staticcheck,testifylint
}
func TestIsNotNil(t *testing.T) {
@@ -51,7 +51,7 @@ func TestIsNotNil(t *testing.T) {
var ifaceWithNilValue any = (*string)(nil) //nolint:staticcheck
is.False(IsNotNil(ifaceWithNilValue))
is.True(ifaceWithNilValue != nil) //nolint:staticcheck
is.True(ifaceWithNilValue != nil) //nolint:staticcheck,testifylint
}
func TestToPtr(t *testing.T) {
@@ -75,11 +75,11 @@ func TestNil(t *testing.T) {
is.Equal(expNilFloat64, nilFloat64)
is.Nil(nilFloat64)
is.NotEqual(nil, nilFloat64)
is.NotEqual(nil, nilFloat64) //nolint:testifylint
is.Equal(expNilString, nilString)
is.Nil(nilString)
is.NotEqual(nil, nilString)
is.NotEqual(nil, nilString) //nolint:testifylint
is.NotEqual(nilString, nilFloat64)
}