Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 72 additions & 6 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ version: "2"

linters:
enable:
# --- existing linters ---
- bodyclose
- dupl
- errcheck
Expand All @@ -18,13 +19,26 @@ linters:
- nilerr
- staticcheck
- unused
# --- new strict linters ---
- exhaustive # ensure switch statements cover all enum cases
- goconst # flag repeated string literals as constant candidates
- gocyclo # flag functions with high cyclomatic complexity
- misspell # catch common spelling mistakes in comments/strings
- paralleltest # encourage t.Parallel() in tests
- prealloc # suggest slice preallocation for known lengths
- tparallel # ensure t.Parallel() is called in subtests using t.Run
- unconvert # flag unnecessary type conversions
- unparam # flag unused function parameters and return values
- usestdlibvars # flag string literals that have stdlib constant equivalents
- wrapcheck # flag errors from external packages that aren't wrapped

settings:
gosec:
excludes:
- G301 # directory permissions (false positive for backup tool)
- G306 # WriteFile permissions (false positive for backup tool)
- G304 # file inclusion via variable (backup tool MUST read dynamic paths)
- G101 # hardcoded credentials (false positive: env var NAMES, not secrets)
maintidx:
under: 20 # flag functions with maintainability index below 20
dupl:
Expand All @@ -36,18 +50,70 @@ linters:
statements: 50 # ratcheted: functions must be <50 statements
nestif:
min-complexity: 6 # ratcheted: nesting depth must be <6
goconst:
min-occurrences: 3
ignore-tests: true # test data legitimately repeats string literals
gocyclo:
min-complexity: 15 # spec threshold; refactors + nolint keep functions at or below this
exhaustive:
default-signifies-exhaustive: true # switches with default: cover all cases
wrapcheck:
ignore-sigs:
- .Errorf(
- errors.New(
- errors.Unwrap(
- errors.Join(
- .Wrap(
- .Wrapf(
- .WithMessage(
- .WithMessagef(
- .WithStack(
ignore-package-globs:
# stdlib packages whose errors are self-descriptive — wrapping adds noise
- os
- io
- path/filepath
- path
- net
- net/http
- encoding/*
- strconv
- fmt
- strings
- sort
- errors
- time
- sync
- runtime
# internal packages — the action layer already provides error context
- github.com/danielxxomg/bak-cli/internal/*
# bubbletea — Program.Run() errors are framework-level (TTY I/O)
- charm.land/bubbletea/v2

exclusions:
rules:
# test file exemptions — tests have different constraints than production code
- path: '(.+_test\.go)'
linters:
- errcheck
- gosec # tests don't need security scanning
- maintidx # tests may have long table-driven setups
- dupl # tests legitimately repeat setup patterns
- gocognit # test setup is inherently branchy; complexity not actionable
- funlen # table-driven tests legitimately exceed line/statements limits
- nestif # test arrange/act/assert nesting is structural, not a smell
- gosec # tests don't need security scanning
- maintidx # tests may have long table-driven setups
- dupl # tests legitimately repeat setup patterns
- gocognit # test setup is inherently branchy; complexity not actionable
- funlen # table-driven tests legitimately exceed line/statements limits
- nestif # test arrange/act/assert nesting is structural, not a smell
- wrapcheck # test helpers don't need error context wrapping
- exhaustive # test switches intentionally omit cases
- unparam # test helper signatures follow table-driven patterns
- gocyclo # integration tests have inherent setup complexity
# cmd/ test exemptions — shared cobra global state prevents parallel execution
- path: 'cmd/.*_test\.go'
linters:
- paralleltest # cobra rootCmd is package-global; parallel cmd tests race on it
# cmd/ exemptions — cobra framework constraints
- path: 'cmd/.*\.go'
linters:
- unparam # cobra RunE requires (cmd, args) even when unused

run:
timeout: 5m
Expand Down
9 changes: 9 additions & 0 deletions cmd/goconst_constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package cmd

// String constants extracted to satisfy goconst (min-occurrences 3).
// These values appeared 3+ times across production code.

const (
keyEnter = "enter"
listAction = "list"
)
2 changes: 1 addition & 1 deletion cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
var listProvider string

var listCmd = &cobra.Command{
Use: "list",
Use: listAction,
Short: "List all local backups",
Long: `Scans ~/.bak/backups/ and displays a table of all local backups
with their ID, date, preset, file count, and size.
Expand Down
4 changes: 2 additions & 2 deletions cmd/pick.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (m pickModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.items[m.cursor].checked = !m.items[m.cursor].checked
}

case "enter":
case keyEnter:
m.confirmed = true
return m, tea.Quit
}
Expand Down Expand Up @@ -112,7 +112,7 @@ func (m pickModel) View() tea.View {
b.WriteString("\n")
b.WriteString(components.RenderHelp([]components.HelpKey{
{Key: "space", Desc: "toggle"},
{Key: "enter", Desc: "confirm"},
{Key: keyEnter, Desc: "confirm"},
{Key: "q/esc", Desc: "quit"},
}))

Expand Down
2 changes: 1 addition & 1 deletion cmd/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func runProfileCreateWithDeps(cmd *cobra.Command, args []string, deps cmdDeps) e
// --- list ---

var profileListCmd = &cobra.Command{
Use: "list",
Use: listAction,
Short: "List all configured profiles",
Long: "Display a table of all configured machine profiles with their provider, preset, and encryption status.",
Args: cobra.NoArgs,
Expand Down
4 changes: 2 additions & 2 deletions cmd/restore_picker.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (m restorePickerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.cursor++
}

case "enter":
case keyEnter:
if len(m.backups) > 0 {
m.confirmed = true
return m, tea.Quit
Expand Down Expand Up @@ -90,7 +90,7 @@ func (m restorePickerModel) View() tea.View {
b.WriteString("\n")
b.WriteString(components.RenderHelp([]components.HelpKey{
{Key: "\u2191/\u2193", Desc: "navigate"},
{Key: "enter", Desc: "select"},
{Key: keyEnter, Desc: "select"},
{Key: "q/esc", Desc: "cancel"},
}))

Expand Down
2 changes: 1 addition & 1 deletion cmd/schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func runScheduleCreateWithDeps(cmd *cobra.Command, args []string, deps cmdDeps)
// --- list ---

var scheduleListCmd = &cobra.Command{
Use: "list",
Use: listAction,
Short: "List all bak-cli scheduled backups",
Long: "Display a table of all active bak-cli backup schedules.",
Args: cobra.NoArgs,
Expand Down
Loading
Loading