diff --git a/.golangci.yml b/.golangci.yml index c30252c..82892e2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,17 +1,47 @@ --- # Ref: https://golangci-lint.run/usage/configuration/ -# Inspired by https://github.com/ccoVeille/golangci-lint-config-examples/ linters: - enable: + enable-all: true + + disable: + # TODO: remove the line and fix the issues + - bodyclose + - cyclop + - depguard + - err113 + - errorlint + - forbidigo + - gochecknoglobals + - gochecknoinits + - gomnd + - inamedparam + - ireturn + - lll + - musttag + - nestif + - paralleltest + - predeclared + - recvcheck - revive - - gci - - thelper - - mirror - - usestdlibvars - - misspell - - dupword - - loggercheck - - fatcontext + - whitespace + - wrapcheck + - wsl + + - nlreturn # keeps the code concise + - godox # I like leaving TODOs in the code + - varnamelen # short variable names are okay + - testpackage # keep the tests close to the code + - exhaustruct # it's ok not to specify all the fields in a struct definition + + # deprecated + - mnd + - exportloopref + +issues: + exclude-rules: + - path: _test.go + linters: + - funlen linters-settings: gci: @@ -21,30 +51,17 @@ linters-settings: - localmodule revive: + enable-all-rules: true rules: - - name: blank-imports - name: context-as-argument arguments: - allowTypesBefore: "*testing.T" - - name: context-keys-type - - name: dot-imports - - name: empty-block - - name: error-naming - - name: error-return - - name: error-strings - - name: errorf - - name: increment-decrement - - name: indent-error-flow - - name: range - - name: receiver-naming - - name: redefines-builtin-id - - name: superfluous-else - - name: time-naming - - name: unexported-return - - name: unreachable-code - - name: unused-parameter - - name: var-declaration - - name: var-naming + + tagliatelle: + case: + rules: + json: snake misspell: locale: US + mode: default diff --git a/internal/cache/cache.go b/internal/cache/cache.go index 6ae6e0c..7d2b1c7 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -42,7 +42,6 @@ func NewFileCache(path string) *FileCache { func (c *FileCache) Read(out any) error { content, err := os.ReadFile(c.path) - if err != nil { if errors.Is(err, os.ErrNotExist) { slog.Debug("cache doesn't exist", "path", c.path) @@ -94,9 +93,9 @@ func (c *FileCache) Write(in any) error { return err } - if err := os.MkdirAll(filepath.Dir(c.path), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(c.path), 0o755); err != nil { return err } - return os.WriteFile(c.path, marshaled, 0644) + return os.WriteFile(c.path, marshaled, 0o600) } diff --git a/internal/cmd/actions.go b/internal/cmd/actions.go index c54fd93..d0cecbf 100644 --- a/internal/cmd/actions.go +++ b/internal/cmd/actions.go @@ -9,13 +9,11 @@ import ( //go:embed actions-help.txt var longHelp string -var ( - actionsCmd = &cobra.Command{ - Use: "actions", - Short: "Show information about the actions", - Long: "'gh-not' has multiple actions that perform different actions:\n\n" + longHelp, - } -) +var actionsCmd = &cobra.Command{ + Use: "actions", + Short: "Show information about the actions", + Long: "'gh-not' has multiple actions that perform different actions:\n\n" + longHelp, +} func init() { rootCmd.AddCommand(actionsCmd) diff --git a/internal/cmd/config.go b/internal/cmd/config.go index a666ee6..273df8c 100644 --- a/internal/cmd/config.go +++ b/internal/cmd/config.go @@ -1,6 +1,7 @@ package cmd import ( + "errors" "fmt" "log/slog" "os" @@ -75,7 +76,7 @@ func editConfig() error { editor := os.Getenv("EDITOR") if editor == "" { - return fmt.Errorf("EDITOR environment variable not set") + return errors.New("EDITOR environment variable not set") } cmd := exec.Command(editor, config.Path) diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 0dae11a..f239172 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -149,7 +149,7 @@ func filter(notifications notifications.Notifications) (notifications.Notificati if !found { slog.Error("Rule not found", "rule", ruleFlag) - return nil, fmt.Errorf("Rule '%s' not found", ruleFlag) + return nil, fmt.Errorf("rule '%s' not found", ruleFlag) } } diff --git a/internal/cmd/sync.go b/internal/cmd/sync.go index 6d9cf94..0d8f6c2 100644 --- a/internal/cmd/sync.go +++ b/internal/cmd/sync.go @@ -44,8 +44,8 @@ See synchronization logic at https://pkg.go.dev/github.com/nobe4/gh-not/internal func init() { rootCmd.AddCommand(syncCmd) - syncCmd.Flags().VarP(&forceStrategy, "force-strategy", "f", fmt.Sprintf("Force strategy: %s", forceStrategy.Allowed())) - syncCmd.Flags().VarP(&refreshStrategy, "refresh-strategy", "r", fmt.Sprintf("Refresh strategy: %s", refreshStrategy.Allowed())) + syncCmd.Flags().VarP(&forceStrategy, "force-strategy", "f", "Force strategy: "+forceStrategy.Allowed()) + syncCmd.Flags().VarP(&refreshStrategy, "refresh-strategy", "r", "Refresh strategy: "+refreshStrategy.Allowed()) syncCmd.Flags().StringVarP(¬ificationDumpPath, "from-file", "", "", "Path to notification dump in JSON (generate with 'gh api /notifications')") } diff --git a/internal/config/keymap.go b/internal/config/keymap.go index d283527..3336a00 100644 --- a/internal/config/keymap.go +++ b/internal/config/keymap.go @@ -6,8 +6,10 @@ import ( "github.com/charmbracelet/bubbles/key" ) -type Keymap map[string]KeyBindings -type KeyBindings map[string]KeyBinding +type ( + Keymap map[string]KeyBindings + KeyBindings map[string]KeyBinding +) type KeyBinding []string diff --git a/internal/gh/gh.go b/internal/gh/gh.go index 0eda59c..b642e48 100644 --- a/internal/gh/gh.go +++ b/internal/gh/gh.go @@ -4,12 +4,12 @@ package gh import ( "encoding/json" "errors" - "fmt" "io" "log/slog" "net/http" "net/url" "regexp" + "strconv" ghapi "github.com/cli/go-gh/v2/pkg/api" @@ -45,7 +45,7 @@ func NewClient(api api.Requestor, cache cache.RefreshReadWriter, config config.E query.Set("all", "true") } if config.PerPage > 0 && config.PerPage != 100 { - query.Set("per_page", fmt.Sprintf("%d", config.PerPage)) + query.Set("per_page", strconv.Itoa(config.PerPage)) } url.RawQuery = query.Encode() diff --git a/internal/gh/gh_test.go b/internal/gh/gh_test.go index c3277e2..fef8937 100644 --- a/internal/gh/gh_test.go +++ b/internal/gh/gh_test.go @@ -549,7 +549,6 @@ func TestEnrich(t *testing.T) { notification *notifications.Notification assertError func(*testing.T, error) }{ - { name: "no notification", }, diff --git a/internal/manager/manager.go b/internal/manager/manager.go index eec5d4d..8737ff0 100644 --- a/internal/manager/manager.go +++ b/internal/manager/manager.go @@ -1,6 +1,7 @@ package manager import ( + "errors" "fmt" "log/slog" "os" @@ -63,7 +64,7 @@ func (m *Manager) Refresh() error { func (m *Manager) refreshNotifications() error { if m.client == nil { - return fmt.Errorf("manager has no client, cannot refresh notifications") + return errors.New("manager has no client, cannot refresh notifications") } fmt.Printf("Refreshing notifications...\n") diff --git a/internal/manager/strategies.go b/internal/manager/strategies.go index af93e1d..5fb1b90 100644 --- a/internal/manager/strategies.go +++ b/internal/manager/strategies.go @@ -48,6 +48,8 @@ func (r RefreshStrategy) ShouldRefresh(expired bool) bool { slog.Info("preventing a refresh") return false + case AutoRefresh: + fallthrough default: slog.Debug("refresh based on cache expiration", "expired", expired) return expired diff --git a/internal/notifications/notifications.go b/internal/notifications/notifications.go index 1c30dc1..21c888f 100644 --- a/internal/notifications/notifications.go +++ b/internal/notifications/notifications.go @@ -13,8 +13,10 @@ import ( "time" ) -type Notifications []*Notification -type NotificationMap map[string]*Notification +type ( + Notifications []*Notification + NotificationMap map[string]*Notification +) type Notification struct { // Standard API fields @@ -167,7 +169,7 @@ func (n Notifications) IDList() []string { return ids } -// TODO: in-place update +// TODO: in-place update. func (n Notifications) Compact() Notifications { return slices.DeleteFunc(n, func(n *Notification) bool { return n == nil @@ -185,7 +187,7 @@ func (n Notifications) Sort() { }) } -// TODO: in-place update +// TODO: in-place update. func (n Notifications) Uniq() Notifications { seenIDs := map[string]bool{} return slices.DeleteFunc(n, func(n *Notification) bool { diff --git a/internal/notifications/sync.go b/internal/notifications/sync.go index ac76742..60d4d35 100644 --- a/internal/notifications/sync.go +++ b/internal/notifications/sync.go @@ -21,7 +21,7 @@ It applies the following rules: Notes on (2) Update: Updating the notification will also reset the `Meta.Done` state if the remote notification is newer than the local one. -TODO: refactor this to `func (n Notifications) Sync(remote Notifications) {}` +TODO: refactor this to `func (n Notifications) Sync(remote Notifications) {}`. */ func Sync(local, remote Notifications) Notifications { // TODO: do we need to have the whole map? diff --git a/internal/repl/command.go b/internal/repl/command.go index f025df7..050da8c 100644 --- a/internal/repl/command.go +++ b/internal/repl/command.go @@ -84,7 +84,7 @@ func (msg ApplyCommandMsg) apply(m model) (tea.Model, tea.Cmd) { runner, ok := m.actions[msg.Command] if !ok { - return m, m.renderResult(fmt.Errorf("Invalid command %s", msg.Command)) + return m, m.renderResult(fmt.Errorf("invalid command %s", msg.Command)) } m.resultStrings = []string{} @@ -123,7 +123,7 @@ func (m model) applyNext() tea.Cmd { slog.Debug("apply next", "notification", current.notification.String()) - message := "" + var message string out := &strings.Builder{} if err := m.currentRun.Runner.Run(current.notification, m.currentRun.Args, out); err != nil { message = fmt.Sprintf("Error for '%s': %s", current.notification.Subject.Title, err.Error()) diff --git a/internal/repl/handlers.go b/internal/repl/handlers.go index 022f1a4..db1f7a2 100644 --- a/internal/repl/handlers.go +++ b/internal/repl/handlers.go @@ -66,7 +66,6 @@ func (m *model) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) { } func (m *model) handleResult(msg tea.KeyMsg) (tea.Model, tea.Cmd) { - switch { case key.Matches(msg, m.list.KeyMap.ShowFullHelp): diff --git a/internal/repl/view.go b/internal/repl/view.go index d9139e0..3bac0f8 100644 --- a/internal/repl/view.go +++ b/internal/repl/view.go @@ -10,9 +10,7 @@ import ( "github.com/charmbracelet/lipgloss" ) -var ( - noStyle = lipgloss.NewStyle() -) +var noStyle = lipgloss.NewStyle() func (m *model) initView() { m.list.SetShowStatusBar(false) @@ -112,8 +110,8 @@ func (m model) View() string { return m.viewFullHelp() } - content := "" - statusLine := "" + var content string + var statusLine string if m.showResult { content = m.result.View()