diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9029624..e29b526 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,9 +7,6 @@ on: merge_group: branches: ["**"] -permissions: - contents: read # for golangci-lint-action - jobs: tests: name: Run tests @@ -18,6 +15,8 @@ jobs: os: [Ubuntu] go-version: ["1.23.x"] runs-on: ${{ matrix.os }}-latest + permissions: + contents: read # for golangci-lint-action steps: - uses: actions/checkout@v4 with: @@ -40,6 +39,6 @@ jobs: paths: "test-report.xml" if: always() - name: Lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v7 with: - version: v1.62.2 + version: v2.0.1 diff --git a/.golangci.yaml b/.golangci.yaml index 7cdc8dc..31ca1c4 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,93 +1,167 @@ # yaml-language-server: $schema=https://golangci-lint.run/jsonschema/golangci.jsonschema.json -# Inspired from: (MIT license) https://gist.github.com/maratori/47a4d00457a92aa426dbd48a18776322 --- -run: - timeout: '5m' -linters-settings: - gosec: - excludes: - - G601 # Not relevant anymore with Go 1.22 and later - - G404 # Use of weak random number generator (math/rand instead of crypto/rand) - govet: - enable-all: true - disable: - - fieldalignment # too strict - - shadow # too strict - perfsprint: - strconcat: false - revive: - rules: - - name: var-naming - arguments: - - [] - - [] - - - skipPackageNameChecks: true - stylecheck: - checks: ["all", "-ST1003"] - tagalign: - order: ["flag", "env", "default", "secret", "toml", "json", "validate", "global", "help"] - strict: true +version: "2" linters: - enable: + enable: # list taken from https://golangci-lint.run/usage/linters/ - last updated 2025-02-14 for v1.64.5 + # enabled by default, but list them here to be explicit + - errcheck + - govet + - ineffassign + - staticcheck + - unused + # other linters (that would be disabled by default) - asasalint # checks for pass []any as any in variadic func(...any) - asciicheck # checks that your code does not contain non-ASCII identifiers - bidichk # checks for dangerous unicode character sequences - bodyclose # checks whether HTTP response body is closed successfully - canonicalheader # checks for canonical names in HTTP headers - - copyloopvar # detects places where loop variables are copied + - containedctx # detects struct contained context.Context field #- contextcheck # checks for inherited context.Context + - copyloopvar # detects places where loop variables are copied #- cyclop # checks function and package cyclomatic complexity + - decorder # check declaration order and count of types, constants, variables and functions + #- depguard # Go linter that checks if package imports are in a list of acceptable packages + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) #- dupl # tool for code clone detection + - dupword # checks for duplicate words in the source code - durationcheck # checks for two durations multiplied together + #- err113 # checks the errors handling expressions + - errchkjson # checks types passed to the json encoding functions. - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13 - exhaustive # checks exhaustiveness of enum switch statements + #- exhaustruct # checks if all structure fields are initialized + - exptostd # Detects functions from golang.org/x/exp/ that can be replaced by std functions. - fatcontext # finds nested context.WithValue calls in loops - - forbidigo # forbids identifiers + # - forbidigo # forbids identifiers + #- forcetypeassert # finds forced type assertions + #- funlen # Tool for detection of long functions + #- ginkgolinter # Enforces the Ginkgo testing package guidelines. - gocheckcompilerdirectives # validates go compiler directive comments (//go:) #- gochecknoglobals # checks that no global variables exist - gochecknoinits # checks that no init functions are present in Go code - gochecksumtype # checks exhaustiveness on Go "sum types" + #- gocognit # Computes and checks the cognitive complexity of functions - goconst # finds repeated strings that could be replaced by a constant - gocritic # provides diagnostics that check for bugs, performance and style issues - - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt - #- gomnd # detects magic numbers + #- gocyclo # Computes and checks the cyclomatic complexity of functions + #- godot # Check if comments end in a period + #- godox # Tool for detection of FIXME, TODO and other comment keywords + #- goheader # Checks is file header matches to pattern - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod - gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations - goprintffuncname # checks that printf-like functions are named with f at the end - gosec # inspects source code for security problems + - gosmopolitan # Report certain i18n/l10n anti-patterns in your Go codebase. + - grouper # Analyze expression groups + - iface # Detect the incorrect use of interfaces, helping developers avoid interface pollution. + - importas # Enforces consistent import aliases. + #- inamedparam # Reports interfaces with unnamed method parameters. + - interfacebloat # Checks the number of methods in an interface - intrange # finds places where for loops could make use of an integer range + #- ireturn # Accept Interfaces, Return Concrete Types + #- lll # Reports long lines - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) + #- maintidx # measures the maintainability index of each function - makezero # finds slice declarations with non-zero initial length - mirror # reports wrong mirror patterns of bytes/strings usage + - misspell # finds commonly misspelled English words in comments + #- mnd # Detects magic numbers - musttag # enforces field tags in (un)marshaled structs - nakedret # finds naked returns in functions greater than a specified function length + #- nestif # Reports deeply nested if statements - nilerr # finds the code that returns nil even if it checks that the error is not nil + - nilnesserr # Reports constructs that checks for err != nil, but returns a different nil value error. - nilnil # checks that there is no simultaneous return of nil error and an invalid value + #- nlreturn # Checks for a new line before return and branch statements to increase code clarity - noctx # finds sending http request without context.Context - nolintlint # reports ill-formed or insufficient nolint directives - nonamedreturns # reports all named returns - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL - perfsprint # checks that fmt.Sprintf can be replaced with a faster alternative + #- paralleltest # detects missing usage of t.Parallel() method in your Go test + - perfsprint # Checks that fmt.Sprintf can be replaced with a faster alternative. - prealloc # finds slice declarations that could potentially be preallocated - predeclared # finds code that shadows one of Go's predeclared identifiers - promlinter # checks Prometheus metrics naming via promlint - protogetter # reports direct reads from proto message fields when getters should be used - reassign # checks that package variables are not reassigned + - recvcheck # checks for receiver type consistency - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint - rowserrcheck # checks whether Err of rows is checked successfully - sloglint # ensure consistent code style when using log/slog - spancheck # checks for mistakes with OpenTelemetry/Census spans - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed - - stylecheck # is a replacement for golint - tagalign # checks that struct tags are well aligned - - tenv # detects using os.Setenv instead of t.Setenv since Go1.17 - - testableexamples # checks if examples are testable (have an expected output) + #- tagliatelle # Checks the struct tags. + #- testableexamples # checks if examples are testable (have an expected output) - testifylint # checks usage of github.com/stretchr/testify #- testpackage # makes you use a separate _test package + #- thelper # detects golang test helpers without t.Helper() call. - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes - unconvert # removes unnecessary type conversions - unparam # reports unused function parameters - usestdlibvars # detects the possibility to use variables/constants from the Go standard library + - usetesting # reports uses of functions with replacement inside the testing package + #- varnamelen # checks that the length of a variable's name matches its scope - wastedassign # finds wasted assignment statements - whitespace # detects leading and trailing whitespace + #- wrapcheck # Checks that errors returned from external packages are wrapped + #- wsl # whitespace linter - add or remove empty lines + #- zerologlint # checks wrong usage of zerolog + #- tenv # deprecated in favor of usetesting + settings: + gosec: + excludes: + - G601 # Not relevant anymore with Go 1.22 and later + - G404 # Use of weak random number generator (math/rand instead of crypto/rand) + govet: + enable-all: true + disable: + - fieldalignment # too strict + - shadow # too strict + perfsprint: + strconcat: false + revive: + rules: + - name: var-naming + arguments: + - [] + - [] + - - skipPackageNameChecks: true + staticcheck: + checks: + - all + - -ST1003 # should not use underscores in package names + tagalign: + order: + - flag + - env + - default + - secret + - toml + - json + - validate + - global + - help + strict: false + exclusions: + generated: strict + warn-unused: true + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + # Exclude some linters from running on tests files. + - linters: + - containedctx + - dogsled + path: _test.go +formatters: + enable: + - gci + - gofmt + - gofumpt + - goimports diff --git a/config.go b/config.go index d9299f3..d66ab08 100644 --- a/config.go +++ b/config.go @@ -9,7 +9,6 @@ import ( "time" "github.com/samber/lo" - "github.com/urfave/cli/v3" ) diff --git a/examples/01_simple_cli/main.go b/examples/01_simple_cli/main.go index 76bcc9e..6dc5ef0 100644 --- a/examples/01_simple_cli/main.go +++ b/examples/01_simple_cli/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/tilebox/structconf" ) diff --git a/examples/02_default_values/main.go b/examples/02_default_values/main.go index 47710a5..3144907 100644 --- a/examples/02_default_values/main.go +++ b/examples/02_default_values/main.go @@ -2,11 +2,12 @@ package main import ( "fmt" + "github.com/tilebox/structconf" ) type ProgramConfig struct { - Name string `default:"World" help:"Whom to greet"` + Name string `default:"World" help:"Whom to greet"` Greet bool `help:"Whether or not to greet"` } diff --git a/examples/03_nested/main.go b/examples/03_nested/main.go index 88765ab..ba6bd92 100644 --- a/examples/03_nested/main.go +++ b/examples/03_nested/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/tilebox/structconf" ) diff --git a/examples/05_override/main.go b/examples/05_override/main.go index d17fa0a..00712fb 100644 --- a/examples/05_override/main.go +++ b/examples/05_override/main.go @@ -9,7 +9,7 @@ type AppConfig struct { // --level flag // $LOGGING_LEVEL env var // log-level toml property - LogLevel string `flag:"level" env:"LOGGING_LEVEL" toml:"log-level" default:"INFO"` + LogLevel string `flag:"level" env:"LOGGING_LEVEL" default:"INFO" toml:"log-level"` // will not be configurable at all Ignored string `flag:"-" env:"-" toml:"-"` diff --git a/examples/08_validation/main.go b/examples/08_validation/main.go index 79405a0..2641e64 100644 --- a/examples/08_validation/main.go +++ b/examples/08_validation/main.go @@ -9,7 +9,7 @@ type AppConfig struct { Host string `validate:"required" help:"Hostname (required)"` // must be an integer between 1 and 65535 - Port int `validate:"gte=1,lte=65535" default:"8080" help:"Server port"` + Port int `default:"8080" validate:"gte=1,lte=65535" help:"Server port"` // If set, it must be a valid path to a directory Path string `validate:"omitempty,dir" help:"A valid path"` diff --git a/structconf_test.go b/structconf_test.go index 85ad163..6fb088c 100644 --- a/structconf_test.go +++ b/structconf_test.go @@ -203,7 +203,7 @@ duration = "1m5s" // write our test toml file to a temporary file configPath := path.Join(t.TempDir(), "test-config.toml") - require.NoError(t, os.WriteFile(configPath, []byte(tt.args.toml), 0600)) + require.NoError(t, os.WriteFile(configPath, []byte(tt.args.toml), 0o600)) cliArgs := slices.Clone(tt.args.cliArgs) cliArgs = append(cliArgs, "--load-config", configPath) @@ -247,10 +247,10 @@ second = "second_nested_config" `) firstConfigPath := path.Join(t.TempDir(), "first-config.toml") - require.NoError(t, os.WriteFile(firstConfigPath, []byte(firstConfig), 0600)) + require.NoError(t, os.WriteFile(firstConfigPath, []byte(firstConfig), 0o600)) secondConfigPath := path.Join(t.TempDir(), "second-config.toml") - require.NoError(t, os.WriteFile(secondConfigPath, []byte(secondConfig), 0600)) + require.NoError(t, os.WriteFile(secondConfigPath, []byte(secondConfig), 0o600)) SetArgsForTest(t, []string{"my-program", "--load-config", firstConfigPath + "," + secondConfigPath}) @@ -302,8 +302,8 @@ func Test_loadConfigExtraFlags(t *testing.T) { func Test_PrintCorrectUsage(t *testing.T) { type config struct { Value string - DocumentedValue string ` help:"Description of the documented value"` - ValueWithDefault string `default:"default" help:"A documented value that has a default"` + DocumentedValue string `help:"Description of the documented value"` + ValueWithDefault string `default:"default" help:"A documented value that has a default"` } SetArgsForTest(t, []string{"my-program", "--unknown-value", "to_trigger_usage"}) diff --git a/validate.go b/validate.go index 3dcba96..c2d5b22 100644 --- a/validate.go +++ b/validate.go @@ -18,9 +18,9 @@ func validate(configPointer any) error { for _, fieldError := range validationErrors { validationTag := fieldError.Tag() if validationTag == "required" { - errorMessage.WriteString(fmt.Sprintf("Missing required configuration: %s\n", fieldError.Namespace())) + fmt.Fprintf(errorMessage, "Missing required configuration: %s\n", fieldError.Namespace()) } else { - errorMessage.WriteString(fmt.Sprintf("Configuration error: %s - %s\n", fieldError.StructField(), fieldError.ActualTag())) + fmt.Fprintf(errorMessage, "Configuration error: %s - %s\n", fieldError.StructField(), fieldError.ActualTag()) } } return errors.New(errorMessage.String())