diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..75d886d --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,35 @@ +version: 2 + +run: + timeout: 5m + tests: true + +linters: + disable-all: true + enable: + - errcheck + - govet + - staticcheck + - errorlint + - errname + - bodyclose + - sqlclosecheck + - noctx + - ineffassign + - unconvert + - bidichk + +linters-settings: + errcheck: + check-type-assertions: true + check-blank: true + + govet: + enable-all: true + disable: + - fieldalignment + +issues: + exclude-use-default: false + max-issues-per-linter: 0 + max-same-issues: 0 diff --git a/commands/core/utils.go b/commands/core/utils.go index 5579ec1..c7856b4 100644 --- a/commands/core/utils.go +++ b/commands/core/utils.go @@ -10,7 +10,7 @@ var DebugWriter io.Writer func debugLog(format string, a ...interface{}) { if DebugWriter != nil { - fmt.Fprintf(DebugWriter, format+"\n", a...) + _, _ = fmt.Fprintf(DebugWriter, format+"\n", a...) } } diff --git a/commands/dev/git.go b/commands/dev/git.go index c8fb852..fcb6af6 100644 --- a/commands/dev/git.go +++ b/commands/dev/git.go @@ -1,6 +1,7 @@ package dev import ( + "context" "os/exec" "strings" @@ -50,7 +51,7 @@ func GitPushPullGenerator(tokens []string, prefix string, partial string) []core func getGitResults(prefix string, args ...string) []core.Suggestion { cwd := core.GetCWD() - cmd := exec.Command("git", args...) + cmd := exec.CommandContext(context.Background(), "git", args...) cmd.Dir = cwd out, err := cmd.Output() if err != nil { diff --git a/commands/fs/zoxide.go b/commands/fs/zoxide.go index f3c7243..08785c6 100644 --- a/commands/fs/zoxide.go +++ b/commands/fs/zoxide.go @@ -6,6 +6,7 @@ package fs import ( "bytes" + "context" "os" "os/exec" "strings" @@ -35,7 +36,7 @@ func ZoxideGenerator() core.GeneratorFunc { localSuggestions := core.FileGenerator("/")(tokens, prefix, fullQuery) var zoxideSuggestions []core.Suggestion - cmd := exec.Command("zoxide", "query", "-l") + cmd := exec.CommandContext(context.Background(), "zoxide", "query", "-l") out, err := cmd.Output() if err == nil { lines := strings.Split(string(bytes.TrimSpace(out)), "\n") @@ -79,10 +80,7 @@ func ZoxideGenerator() core.GeneratorFunc { var finalResults []core.Suggestion seen := make(map[string]bool) - for _, s := range localSuggestions { - finalResults = append(finalResults, s) - - } + finalResults = append(finalResults, localSuggestions...) for _, s := range zoxideSuggestions { if !seen[s.Cmd] { diff --git a/commands/runner/justfile.go b/commands/runner/justfile.go index 86c33fe..c248d11 100644 --- a/commands/runner/justfile.go +++ b/commands/runner/justfile.go @@ -23,7 +23,7 @@ func init() { return nil } } - defer file.Close() + defer func() { _ = file.Close() }() var suggestions []core.Suggestion seen := make(map[string]bool) diff --git a/commands/runner/makefile.go b/commands/runner/makefile.go index bb99d2a..ba9e8e4 100644 --- a/commands/runner/makefile.go +++ b/commands/runner/makefile.go @@ -19,7 +19,7 @@ func init() { // No Makefile, don't pretend we have one return nil } - defer file.Close() + defer func() { _ = file.Close() }() var suggestions []core.Suggestion seen := make(map[string]bool) diff --git a/integration/history.go b/integration/history.go index 9d217c6..7acf155 100644 --- a/integration/history.go +++ b/integration/history.go @@ -68,7 +68,7 @@ func SearchHistory(query string) ([]HistResult, error) { if err != nil { return nil, err } - defer file.Close() + defer func() { _ = file.Close() }() var allCmds []string scanner := bufio.NewScanner(file) diff --git a/integration/shell/adapter.go b/integration/shell/adapter.go index 536c728..8294993 100644 --- a/integration/shell/adapter.go +++ b/integration/shell/adapter.go @@ -2,6 +2,7 @@ package shell import ( "fmt" + "maps" "os" "path/filepath" "strings" @@ -94,9 +95,7 @@ func ScanPosixAliases(files []string) map[string]string { continue } - for k, v := range ParseAliases(string(data)) { - aliases[k] = v - } + maps.Copy(aliases, ParseAliases(string(data))) } return aliases } diff --git a/ipc/server.go b/ipc/server.go index 195bce8..10e50b1 100644 --- a/ipc/server.go +++ b/ipc/server.go @@ -53,6 +53,6 @@ func (s *Server) Start() { } func (s *Server) Close() { - s.conn.Close() + _ = s.conn.Close() close(s.StateChan) } diff --git a/justfile b/justfile index ffe65bc..dec4950 100644 --- a/justfile +++ b/justfile @@ -42,4 +42,9 @@ test: alias ana := analyze [group('dev')] analyze: - @go run scripts/test_analyzer.go \ No newline at end of file + @go run scripts/test_analyzer.go + +# run linter +[group('dev')] +lint: + @golangci-lint run ./... \ No newline at end of file diff --git a/root/init.go b/root/init.go index de531f1..62b68d3 100644 --- a/root/init.go +++ b/root/init.go @@ -100,9 +100,9 @@ var setupCmd = &cobra.Command{ fmt.Printf("Failed to update %s: %v\n", configFile, err) return } - defer f.Close() + defer func() { _ = f.Close() }() - f.WriteString("\n# Iris Autocomplete\n" + evalCmd + "\n") + _, _ = f.WriteString("\n# Iris Autocomplete\n" + evalCmd + "\n") fmt.Printf("✓ Added iris integration to %s\n", configFile) } diff --git a/root/root.go b/root/root.go index e7e9908..4b55de0 100644 --- a/root/root.go +++ b/root/root.go @@ -34,7 +34,7 @@ It works exactly like coding editor suggestion menu drop down.`, f, _ := os.OpenFile("iris.log", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) debugLogger = f core.DebugWriter = f - fmt.Fprintf(debugLogger, "--- IRIS DEBUG LOG ---\n") + _, _ = fmt.Fprintf(debugLogger, "--- IRIS DEBUG LOG ---\n") } runWrapper() }, @@ -42,7 +42,6 @@ It works exactly like coding editor suggestion menu drop down.`, shellFlag string debugMode bool debugLogger *os.File - isReload bool ) func init() { @@ -52,15 +51,14 @@ func init() { func debugLog(format string, a ...interface{}) { if debugLogger != nil { - fmt.Fprintf(debugLogger, format+"\n", a...) + _, _ = fmt.Fprintf(debugLogger, format+"\n", a...) } } func Execute() { if os.Getenv("IRIS_RELOADED") == "true" { - isReload = true fmt.Printf("\r\033[K\033[35m[IRIS] reloading...\033[0m\n") - os.Unsetenv("IRIS_RELOADED") + _ = os.Unsetenv("IRIS_RELOADED") } if err := rootCmd.Execute(); err != nil { diff --git a/root/shell.go b/root/shell.go index ec9bd61..f6a1b29 100644 --- a/root/shell.go +++ b/root/shell.go @@ -1,6 +1,7 @@ package root import ( + "context" "fmt" "os" "os/exec" @@ -55,7 +56,7 @@ func detectShell() string { } func getActiveInnerShell(rootPid int, defaultShell string) string { - cmd := exec.Command("ps", "-e", "-o", "pid,ppid,comm") + cmd := exec.CommandContext(context.Background(), "ps", "-e", "-o", "pid,ppid,comm") out, err := cmd.Output() if err != nil { return defaultShell diff --git a/root/term_sync.go b/root/term_sync.go index ba8e9f3..dc25072 100644 --- a/root/term_sync.go +++ b/root/term_sync.go @@ -10,5 +10,5 @@ var termMutex sync.Mutex func TermWrite(data []byte) { termMutex.Lock() defer termMutex.Unlock() - os.Stdout.Write(data) + _, _ = os.Stdout.Write(data) } diff --git a/root/wrapper.go b/root/wrapper.go index cd88398..2f5e267 100644 --- a/root/wrapper.go +++ b/root/wrapper.go @@ -3,6 +3,7 @@ package root import ( "bufio" "bytes" + "context" "encoding/json" "fmt" "io" @@ -34,7 +35,7 @@ func getStateFile() string { return "" } dir := filepath.Join(home, ".iris") - os.MkdirAll(dir, 0755) + _ = os.MkdirAll(dir, 0755) return filepath.Join(dir, "state.json") } @@ -60,7 +61,7 @@ func saveMode(mode string) { state := State{Mode: mode} data, err := json.MarshalIndent(state, "", " ") if err == nil { - os.WriteFile(file, data, 0644) + _ = os.WriteFile(file, data, 0644) } } } @@ -78,7 +79,7 @@ func runWrapper() { var shellName string if active := os.Getenv("IRIS_ACTIVE_SHELL"); active != "" { shellName = active - os.Unsetenv("IRIS_ACTIVE_SHELL") + _ = os.Unsetenv("IRIS_ACTIVE_SHELL") } else if shellFlag != "" { shellName = shellFlag } else { @@ -88,7 +89,8 @@ func runWrapper() { shell.Init(shellName) adapter := shell.Current - c := exec.Command(adapter.GetShellPath()) + ctx := context.Background() + c := exec.CommandContext(ctx, adapter.GetShellPath()) c.ExtraFiles = make([]*os.File, 11) // pass write end of pipe to shell as fd 10 (I chose 10 just because it won't conflict with other file descriptors) c.ExtraFiles[10] = w @@ -96,10 +98,10 @@ func runWrapper() { ptmx, err := pty.Start(c) if err != nil { - fmt.Fprintf(os.Stderr, "[IRIS] failed to start PTY: %v\n", err) + _, _ = fmt.Fprintf(os.Stderr, "[IRIS] failed to start PTY: %v\n", err) return } - defer ptmx.Close() + defer func() { _ = ptmx.Close() }() _ = pty.InheritSize(os.Stdin, ptmx) core.ShellPID = c.Process.Pid @@ -124,18 +126,17 @@ func runWrapper() { case syscall.SIGUSR1: // trigger iris reload by executing itself again exe, _ := os.Executable() - // this marks for the next iris process that it've just reloaded - os.Setenv("IRIS_RELOADED", "true") + _ = os.Setenv("IRIS_RELOADED", "true") innerShell := getActiveInnerShell(c.Process.Pid, shellName) if innerShell != "" { // to detect which is last shell (bash, zsh, fish) - os.Setenv("IRIS_ACTIVE_SHELL", innerShell) + _ = os.Setenv("IRIS_ACTIVE_SHELL", innerShell) } if c.Process != nil { _ = syscall.Kill(c.Process.Pid, syscall.SIGKILL) - ptmx.Close() + _ = ptmx.Close() } if oldState != nil { @@ -160,7 +161,7 @@ func runWrapper() { } continue } - os.Stdout.Write(buf[:n]) + _, _ = os.Stdout.Write(buf[:n]) } }() @@ -209,17 +210,17 @@ func runWrapper() { results := MergeResults(query, "spec") if len(results) == 0 { - os.Stdout.Write([]byte(overlay.ClearAndDisable())) + _, _ = os.Stdout.Write([]byte(overlay.ClearAndDisable())) continue } - os.Stdout.Write([]byte(overlay.Clear())) + _, _ = os.Stdout.Write([]byte(overlay.Clear())) overlay.UpdateItems(results) var rBuf strings.Builder if !disableGhostText.Load() { rBuf.WriteString(overlay.RenderGhostText(query, false)) } rBuf.WriteString(overlay.Render()) - os.Stdout.Write([]byte(rBuf.String())) + _, _ = os.Stdout.Write([]byte(rBuf.String())) } }() @@ -248,7 +249,7 @@ func runWrapper() { navCopy := userNavigated if bufCopy == "" && !navCopy { - os.Stdout.Write([]byte(overlay.ClearAndDisable())) + _, _ = os.Stdout.Write([]byte(overlay.ClearAndDisable())) return } @@ -268,7 +269,7 @@ func runWrapper() { if len(results) == 0 { b.WriteString(overlay.ClearAndDisable()) - os.Stdout.Write([]byte(b.String())) + _, _ = os.Stdout.Write([]byte(b.String())) return } @@ -286,7 +287,7 @@ func runWrapper() { b.WriteString(overlay.RenderGhostText(bufCopy, navCopy)) } b.WriteString(overlay.Render()) - os.Stdout.Write([]byte(b.String())) + _, _ = os.Stdout.Write([]byte(b.String())) }) } @@ -306,7 +307,7 @@ func runWrapper() { } b.WriteString(overlay.Render()) } - os.Stdout.Write([]byte(b.String())) + _, _ = os.Stdout.Write([]byte(b.String())) } // reads from stdin and decides what to forward or intercept @@ -322,7 +323,7 @@ func runWrapper() { if n > 0 { if isExecuting() { - ptmx.Write(inputSlice[:n]) + _, _ = ptmx.Write(inputSlice[:n]) continue } @@ -339,7 +340,7 @@ func runWrapper() { intercepted = true suggestionsEnabled = !suggestionsEnabled if !suggestionsEnabled { - os.Stdout.Write([]byte(overlay.ClearAndDisable())) + _, _ = os.Stdout.Write([]byte(overlay.ClearAndDisable())) } else { shouldOverlayDraw = true } @@ -351,7 +352,7 @@ func runWrapper() { intercepted = true userNavigated = true - os.Stdout.Write([]byte(overlay.Clear())) // clear old menu + _, _ = os.Stdout.Write([]byte(overlay.Clear())) // clear old menu if inputSlice[i+2] == 'A' { // up arrow overlay.Cursor-- @@ -366,8 +367,8 @@ func runWrapper() { } selected := overlay.Items[overlay.Cursor].Cmd - ptmx.Write([]byte{0x15}) // ctrl+u to clear line - ptmx.Write([]byte(selected)) + _, _ = ptmx.Write([]byte{0x15}) // ctrl+u to clear line + _, _ = ptmx.Write([]byte(selected)) naiveBuffer = selected renderNow() @@ -407,8 +408,8 @@ func runWrapper() { } selected := overlay.Items[overlay.Cursor].Cmd - ptmx.Write([]byte{0x15}) // ctrl+u to clear line - ptmx.Write([]byte(selected)) + _, _ = ptmx.Write([]byte{0x15}) // ctrl+u to clear line + _, _ = ptmx.Write([]byte(selected)) naiveBuffer = selected userNavigated = true @@ -423,7 +424,7 @@ func runWrapper() { if len(ghostText) > 0 { intercepted = true naiveBuffer += ghostText - ptmx.Write([]byte(ghostText)) + _, _ = ptmx.Write([]byte(ghostText)) shouldOverlayDraw = true i += 2 continue @@ -434,15 +435,15 @@ func runWrapper() { // forward escape sequence to pty if not intercepted if !intercepted { - os.Stdout.Write([]byte(overlay.ClearAndDisable())) + _, _ = os.Stdout.Write([]byte(overlay.ClearAndDisable())) disableGhostText.Store(true) naiveBuffer = "" - ptmx.Write([]byte{b}) + _, _ = ptmx.Write([]byte{b}) // skip remaining bytes of the escape sequence to avoid misinterpretation for j := i + 1; j < n; j++ { char := inputSlice[j] - ptmx.Write([]byte{char}) + _, _ = ptmx.Write([]byte{char}) i = j if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || char == '~' { break @@ -467,9 +468,9 @@ func runWrapper() { // enter is not used to select suggestions } else if overlay.Visible && (b == 0x0d || b == 0x0a) { intercepted = true - os.Stdout.Write([]byte(overlay.ClearAndDisable())) + _, _ = os.Stdout.Write([]byte(overlay.ClearAndDisable())) - ptmx.Write([]byte{0x0d}) + _, _ = ptmx.Write([]byte{0x0d}) naiveBuffer = "" disableGhostText.Store(false) shouldOverlayDraw = false @@ -481,7 +482,7 @@ func runWrapper() { shouldOverlayDraw = true } else { selected := overlay.Items[overlay.Cursor].Cmd - os.Stdout.Write([]byte(overlay.ClearAndDisable())) + _, _ = os.Stdout.Write([]byte(overlay.ClearAndDisable())) if mode == "spec" { selected = strings.TrimSpace(selected) + " " @@ -489,8 +490,8 @@ func runWrapper() { naiveBuffer = selected - ptmx.Write([]byte{0x15}) // ctrl+u to clear line - ptmx.Write([]byte(selected)) + _, _ = ptmx.Write([]byte{0x15}) // ctrl+u to clear line + _, _ = ptmx.Write([]byte(selected)) overlay.Cursor = 0 // this prevents when you tab, it switchs between suggestions non-stop @@ -503,7 +504,7 @@ func runWrapper() { } if !intercepted { - ptmx.Write([]byte{b}) + _, _ = ptmx.Write([]byte{b}) // we handle line editing keys manually to keep naiveBuffer in sync // since terminal is in raw mode, we must update our state for every change switch b { @@ -526,7 +527,7 @@ func runWrapper() { case '\r', '\n', 0x03, 0x15, 0x0C: // enter, ctrl+c, ctrl+u, ctrl+l: clear buffer on line reset naiveBuffer = "" disableGhostText.Store(false) - os.Stdout.Write([]byte(overlay.ClearAndDisable())) + _, _ = os.Stdout.Write([]byte(overlay.ClearAndDisable())) userNavigated = false default: // track normal printable characters in the buffer for matching @@ -535,8 +536,8 @@ func runWrapper() { if b == ' ' && naiveBuffer != "" && !strings.Contains(naiveBuffer, " ") { if target, ok := core.GetAlias(naiveBuffer); ok { // clear the current alias and replace it with the full command - ptmx.Write([]byte{0x15}) // ctrl+u to clear the current input line - ptmx.Write([]byte(target + " ")) + _, _ = ptmx.Write([]byte{0x15}) // ctrl+u to clear the current input line + _, _ = ptmx.Write([]byte(target + " ")) naiveBuffer = target + " " shouldOverlayDraw = true continue diff --git a/tests/core/filegen_test.go b/tests/core/filegen_test.go index 2f1cd52..b9b238d 100644 --- a/tests/core/filegen_test.go +++ b/tests/core/filegen_test.go @@ -12,15 +12,15 @@ import ( func TestFileGenerator(t *testing.T) { // Setup mock files tmp := t.TempDir() - os.MkdirAll(filepath.Join(tmp, "src"), 0755) - os.WriteFile(filepath.Join(tmp, "main.go"), []byte(""), 0644) - os.WriteFile(filepath.Join(tmp, "README.md"), []byte(""), 0644) - os.WriteFile(filepath.Join(tmp, ".hidden"), []byte(""), 0644) - os.WriteFile(filepath.Join(tmp, "src/utils.go"), []byte(""), 0644) + _ = os.MkdirAll(filepath.Join(tmp, "src"), 0755) + _ = os.WriteFile(filepath.Join(tmp, "main.go"), []byte(""), 0644) + _ = os.WriteFile(filepath.Join(tmp, "README.md"), []byte(""), 0644) + _ = os.WriteFile(filepath.Join(tmp, ".hidden"), []byte(""), 0644) + _ = os.WriteFile(filepath.Join(tmp, "src/utils.go"), []byte(""), 0644) oldWd, _ := os.Getwd() - os.Chdir(tmp) - defer os.Chdir(oldWd) + _ = os.Chdir(tmp) + defer func() { _ = os.Chdir(oldWd) }() // REQUIREMENT: dirOnly shows only dirs t.Run("dirOnly shows only dirs", func(t *testing.T) { diff --git a/tests/fs/zoxide_test.go b/tests/fs/zoxide_test.go index 216fe77..7ea781f 100644 --- a/tests/fs/zoxide_test.go +++ b/tests/fs/zoxide_test.go @@ -16,12 +16,12 @@ func TestZoxideGenerator(t *testing.T) { // Script that prints mock directories script := "#!/bin/sh\necho \"/home/verse/project1\n/home/verse/docs\n/home/verse/dev/iris\"" - os.WriteFile(mockZoxide, []byte(script), 0755) + _ = os.WriteFile(mockZoxide, []byte(script), 0755) // Add tmp to PATH oldPath := os.Getenv("PATH") - os.Setenv("PATH", tmp+string(os.PathListSeparator)+oldPath) - defer os.Setenv("PATH", oldPath) + _ = os.Setenv("PATH", tmp+string(os.PathListSeparator)+oldPath) + defer func() { _ = os.Setenv("PATH", oldPath) }() gen := fs.ZoxideGenerator() diff --git a/tests/integration_test.go b/tests/integration_test.go index 0095149..7eed423 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -14,22 +14,23 @@ func TestIntegration_ZoxideMultiWord(t *testing.T) { // Create mock directory with spaces tmp := t.TempDir() targetDir := filepath.Join(tmp, "My Awesome Project") - os.MkdirAll(targetDir, 0755) + _ = os.MkdirAll(targetDir, 0755) // Mock shell environment oldWd, _ := os.Getwd() - os.Chdir(tmp) - defer os.Chdir(oldWd) + _ = os.Chdir(tmp) + defer func() { _ = os.Chdir(oldWd) }() // Mock zoxide binary mockBinDir := t.TempDir() mockZoxide := filepath.Join(mockBinDir, "zoxide") + // Create mock zoxide binary script := "#!/bin/sh\necho \"" + targetDir + "\"" - os.WriteFile(mockZoxide, []byte(script), 0755) - + _ = os.WriteFile(mockZoxide, []byte(script), 0755) + // Add mock bin to PATH oldPath := os.Getenv("PATH") - os.Setenv("PATH", mockBinDir+string(os.PathListSeparator)+oldPath) - defer os.Setenv("PATH", oldPath) + _ = os.Setenv("PATH", mockBinDir+string(os.PathListSeparator)+oldPath) + defer func() { _ = os.Setenv("PATH", oldPath) }() t.Run("z matches multi-word folder without quotes", func(t *testing.T) { // Simulating user typing "z My Awe"