diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fd5ba6..1060c54 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,6 @@ jobs: - name: Test run: go test ./... - - uses: golangci/golangci-lint-action@v6 + - uses: golangci/golangci-lint-action@v7 with: - version: v1.64.5 - args: --timeout=3m + version: v2.12.0 diff --git a/.golangci.yml b/.golangci.yml index eb14e76..7b09b27 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,104 +1,42 @@ -linters-settings: - funlen: - lines: -1 - statements: 60 - gci: - sections: - - standard - - default - - prefix(quartr-cli) - skip-generated: true - custom-order: true - goconst: - min-len: 2 - min-occurrences: 3 - gocritic: - enabled-tags: - - diagnostic - - style - - performance - - experimental - - opinionated - disabled-checks: - - dupImport - - ifElseChain - - octalLiteral - - singleCaseSwitch - - unnamedResult - - whyNoLint - gocyclo: - min-complexity: 15 - gofmt: - rewrite-rules: - - pattern: 'interface{}' - replacement: 'any' - misspell: - locale: US - nolintlint: - allow-unused: false - allow-no-explanation: [] - require-explanation: false - require-specific: false - revive: - rules: - - name: unexported-return - disabled: true - - name: unused-parameter - disabled: true - +version: "2" +run: + go: "1.26" linters: - disable-all: true + default: none enable: - - errcheck - - gosimple - - govet - - ineffassign - - staticcheck - - unused - asasalint - asciicheck - bidichk - bodyclose - - canonicalheader - containedctx - contextcheck - copyloopvar - decorder - - dogsled - - dupl - dupword - durationcheck + - errcheck - errchkjson - errname - errorlint - exhaustive - fatcontext - forcetypeassert - - funlen - - gci - gocheckcompilerdirectives - gochecknoinits - - gochecksumtype - - goconst - gocritic - gocyclo - - gofmt - - goimports - gomoddirectives - - gomodguard + - gomodguard_v2 - goprintffuncname - gosec - - gosmopolitan + - govet - grouper - inamedparam - - interfacebloat + - ineffassign - intrange - - loggercheck - - maintidx - makezero - mirror - misspell - - musttag - nakedret - nestif - nilerr @@ -110,29 +48,79 @@ linters: - perfsprint - prealloc - predeclared - - promlinter - reassign - revive - rowserrcheck - sqlclosecheck - - stylecheck - - tagalign - - tagliatelle - - testableexamples - - testifylint + - staticcheck - thelper - tparallel - unconvert - unparam + - unused - usestdlibvars - - varnamelen - wastedassign - whitespace - - zerologlint - + settings: + gocritic: + disabled-checks: + - dupImport + - ifElseChain + - octalLiteral + - singleCaseSwitch + - unnamedResult + - whyNoLint + - hugeParam + enabled-tags: + - diagnostic + - style + - performance + - experimental + - opinionated + gocyclo: + min-complexity: 20 + nestif: + min-complexity: 8 + gosec: + excludes: + - G304 # file inclusion via variable — inherent to a CLI + - G602 # slice index out of range — bounds-checked at call site + misspell: + locale: US + nolintlint: + require-explanation: false + require-specific: false + allow-unused: false + revive: + rules: + - name: unexported-return + disabled: true + - name: unused-parameter + disabled: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling issues: max-same-issues: 10 - -run: - timeout: 3m - go: '1.26' +formatters: + enable: + - gci + - gofmt + - goimports + settings: + gci: + sections: + - standard + - default + - prefix(quartr-cli) + custom-order: true + gofmt: + rewrite-rules: + - pattern: interface{} + replacement: any + exclusions: + generated: lax diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f5b753b..fc8ca8f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,9 @@ repos: - repo: https://github.com/golangci/golangci-lint.git - rev: v1.64.5 + rev: v2.12.0 hooks: - id: golangci-lint - args: [--fix, --timeout=3m] + args: [--fix] - repo: local hooks: - id: go-test diff --git a/internal/cli/flags.go b/internal/cli/flags.go index 8ae06af..e322cea 100644 --- a/internal/cli/flags.go +++ b/internal/cli/flags.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net/url" + "strconv" "strings" ) @@ -41,13 +42,9 @@ func extractGlobalFlags(args []string) (globalOverrides, []string, error) { } nameVal := strings.TrimPrefix(a, "--") - name := nameVal - val := "" - hasEq := false - if eq := strings.Index(nameVal, "="); eq >= 0 { - name = nameVal[:eq] - val = nameVal[eq+1:] - hasEq = true + name, val, hasEq := nameVal, "", false + if k, v, found := strings.Cut(nameVal, "="); found { + name, val, hasEq = k, v, true } if f, ok := boolFlags[name]; ok { f() @@ -137,7 +134,7 @@ func (lf listFlags) toParams(allowed paramSet, companyEndpoint bool) url.Values } } if lf.limit > 0 && allowed.allows("limit") { - p.Set("limit", fmt.Sprintf("%d", lf.limit)) + p.Set("limit", strconv.Itoa(lf.limit)) } add("cursor", lf.cursor) add("direction", lf.direction) diff --git a/internal/output/output.go b/internal/output/output.go index aec5096..1be2331 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -250,7 +250,7 @@ func getPath(m map[string]any, path string) any { return cur } -func renderValue(v any, max int) string { +func renderValue(v any, maxLen int) string { if v == nil { return "" } @@ -279,8 +279,8 @@ func renderValue(v any, max int) string { s = strings.ReplaceAll(s, "\n", " ") s = strings.ReplaceAll(s, "\r", " ") s = strings.TrimSpace(s) - if max > 0 && len(s) > max { - return s[:max-1] + "…" + if maxLen > 0 && len(s) > maxLen { + return s[:maxLen-1] + "…" } return s } diff --git a/internal/quartr/client.go b/internal/quartr/client.go index c32f9ea..36a2b4a 100644 --- a/internal/quartr/client.go +++ b/internal/quartr/client.go @@ -39,7 +39,7 @@ func (e *APIError) Error() string { body = body[:800] + "…" } if body == "" { - return fmt.Sprintf("quartr api error: %s", e.Status) + return "quartr api error: " + e.Status } return fmt.Sprintf("quartr api error: %s: %s", e.Status, body) } @@ -74,7 +74,7 @@ func (c *Client) GetBytes(ctx context.Context, path string, params url.Values) ( var lastHeader http.Header var lastErr error for attempt := range 3 { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, http.NoBody) if err != nil { return nil, nil, err } @@ -205,7 +205,7 @@ func (c *Client) Download(ctx context.Context, rawURL, apiKey string, w io.Write if strings.TrimSpace(rawURL) == "" { return nil, errors.New("empty download url") } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, http.NoBody) if err != nil { return nil, err } diff --git a/internal/quartr/config.go b/internal/quartr/config.go index 578d61a..f92c7c6 100644 --- a/internal/quartr/config.go +++ b/internal/quartr/config.go @@ -49,7 +49,7 @@ func LoadConfig(path string) (Config, error) { } return cfg, err } - if len(strings.TrimSpace(string(b))) == 0 { + if strings.TrimSpace(string(b)) == "" { return cfg, nil } if err := json.Unmarshal(b, &cfg); err != nil { @@ -65,7 +65,7 @@ func SaveConfig(path string, cfg Config) error { if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { return err } - b, err := json.MarshalIndent(cfg, "", " ") + b, err := json.MarshalIndent(cfg, "", " ") //nolint:gosec // G117: APIKey field is credential storage by design if err != nil { return err }