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
16 changes: 16 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ linters:
- testifylint # Enforce testify best practices
- intrange # Suggest integer range in for loops
- modernize # Modernize Go code using modern language features
- errorlint # Find error wrapping issues (type assertions, comparisons)
- usestdlibvars # Use standard library variables/constants (e.g. http.MethodGet)
- mirror # Avoid unnecessary allocations in bytes/strings operations
- nolintlint # Report ill-formed or insufficient nolint directives
- wastedassign # Find wasted assignment statements
- makezero # Find slice declarations with non-zero initial length followed by append
- perfsprint # Replace fmt.Sprintf with faster alternatives where possible
- bodyclose # Check HTTP response bodies are closed
- gocheckcompilerdirectives # Validate go compiler directive comments
- recvcheck # Check receiver type consistency
- copyloopvar # Detect loop variable copies
- exptostd # Replace golang.org/x/exp functions with std equivalents
- durationcheck # Check for two durations multiplied together
- fatcontext # Detect nested contexts in loops and function literals
- nosprintfhostport # Check for misuse of Sprintf to construct host:port URLs
- reassign # Check that package variables are not reassigned
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great addition of 16 new linters! The reassign linter is especially valuable for catching accidental package-level variable reassignments. Consider adding a brief comment block grouping these into categories (e.g., "performance", "correctness", "style") to make future maintenance easier.

disable:
- errcheck # Disabled due to exclude-functions not working properly in golangci-lint v2
- gocritic # Disabled due to disabled-checks not working properly in golangci-lint v2
Expand Down
13 changes: 7 additions & 6 deletions cmd/gh-aw/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"errors"
"fmt"
"os"
"sort"
Expand Down Expand Up @@ -132,7 +133,7 @@ Examples:
if len(args) == 0 || interactiveFlag {
// Check if running in CI environment
if cli.IsRunningInCI() {
return fmt.Errorf("interactive mode cannot be used in CI environments. Please provide a workflow name")
return errors.New("interactive mode cannot be used in CI environments. Please provide a workflow name")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good use of errors.New instead of fmt.Errorf for static error strings — this is exactly what the perfsprint linter enforces and avoids unnecessary allocations. 👍

}

// Use default workflow name for interactive mode
Expand Down Expand Up @@ -380,18 +381,18 @@ Examples:
if len(args) == 0 {
// Check if running in CI environment
if cli.IsRunningInCI() {
return fmt.Errorf("interactive mode cannot be used in CI environments. Please provide a workflow name")
return errors.New("interactive mode cannot be used in CI environments. Please provide a workflow name")
}

// Interactive mode doesn't support repeat or enable flags
if repeatCount > 0 {
return fmt.Errorf("--repeat flag is not supported in interactive mode")
return errors.New("--repeat flag is not supported in interactive mode")
}
if enable {
return fmt.Errorf("--enable-if-needed flag is not supported in interactive mode")
return errors.New("--enable-if-needed flag is not supported in interactive mode")
}
if len(inputs) > 0 {
return fmt.Errorf("workflow inputs cannot be specified in interactive mode (they will be collected interactively)")
return errors.New("workflow inputs cannot be specified in interactive mode (they will be collected interactively)")
}

return cli.RunWorkflowInteractively(cmd.Context(), verboseFlag, repoOverride, refOverride, autoMergePRs, push, engineOverride, dryRun)
Expand Down Expand Up @@ -463,7 +464,7 @@ func init() {
rootCmd.SilenceErrors = true

// Set version template to match the version subcommand format
rootCmd.SetVersionTemplate(fmt.Sprintf("%s version {{.Version}}\n", string(constants.CLIExtensionPrefix)))
rootCmd.SetVersionTemplate(string(constants.CLIExtensionPrefix) + " version {{.Version}}\n")

// Create custom help command that supports "all" subcommand
customHelpCmd := &cobra.Command{
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/access_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func analyzeAccessLogs(runDir string, verbose bool) (*DomainAnalysis, error) {
// No access logs found
accessLogLog.Printf("No access logs directory found in: %s", runDir)
if verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("No access logs found in %s", runDir)))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("No access logs found in "+runDir))
}
return nil, nil
}
Expand Down
14 changes: 8 additions & 6 deletions pkg/cli/actionlint.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
Expand Down Expand Up @@ -50,7 +51,7 @@ func getActionlintDocsURL(kind string) string {
}
}

return fmt.Sprintf("https://github.com/rhysd/actionlint/blob/main/docs/checks.md#%s", anchor)
return "https://github.com/rhysd/actionlint/blob/main/docs/checks.md#" + anchor
}

// actionlintStats tracks aggregate statistics across all actionlint validations
Expand Down Expand Up @@ -159,7 +160,7 @@ func getActionlintVersion() (string, error) {
// We only want the first line which contains the version number
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
if len(lines) == 0 {
return "", fmt.Errorf("no version output from actionlint")
return "", errors.New("no version output from actionlint")
}
version := strings.TrimSpace(lines[0])
actionlintVersion = version
Expand All @@ -183,7 +184,7 @@ func runActionlintOnFile(lockFiles []string, verbose bool, strict bool) error {
// Log error but continue - version display is not critical
actionlintLog.Printf("Could not fetch actionlint version: %v", err)
} else {
fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage(fmt.Sprintf("Using actionlint %s", version)))
fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage("Using actionlint "+version))
}
}

Expand Down Expand Up @@ -214,7 +215,7 @@ func runActionlintOnFile(lockFiles []string, verbose bool, strict bool) error {
dockerArgs := []string{
"run",
"--rm",
"-v", fmt.Sprintf("%s:/workdir", gitRoot),
"-v", gitRoot + ":/workdir",
"-w", "/workdir",
"rhysd/actionlint:latest",
"-format", "{{json .}}",
Expand All @@ -225,7 +226,7 @@ func runActionlintOnFile(lockFiles []string, verbose bool, strict bool) error {

// Always show that actionlint is running (regular verbosity)
if len(lockFiles) == 1 {
fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage(fmt.Sprintf("Running actionlint (includes shellcheck & pyflakes) on %s", relPaths[0])))
fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage("Running actionlint (includes shellcheck & pyflakes) on "+relPaths[0]))
} else {
fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage(fmt.Sprintf("Running actionlint (includes shellcheck & pyflakes) on %d files", len(lockFiles))))
}
Expand Down Expand Up @@ -286,7 +287,8 @@ func runActionlintOnFile(lockFiles []string, verbose bool, strict bool) error {
// Exit code 0 = no errors
// Exit code 1 = errors found
// Other codes = actual errors
if exitErr, ok := err.(*exec.ExitError); ok {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
exitCode := exitErr.ExitCode()
actionlintLog.Printf("Actionlint exited with code %d, found %d errors", exitCode, totalErrors)
// Exit code 1 indicates errors were found
Expand Down
17 changes: 9 additions & 8 deletions pkg/cli/actions_build_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -77,7 +78,7 @@ func ActionsValidateCommand() error {
}

if !allValid {
return fmt.Errorf("validation failed for one or more actions")
return errors.New("validation failed for one or more actions")
}

fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("✨ All actions valid"))
Expand Down Expand Up @@ -128,7 +129,7 @@ func ActionsCleanCommand() error {
// getActionDirectories returns a sorted list of action directory names
func getActionDirectories(actionsDir string) ([]string, error) {
if _, err := os.Stat(actionsDir); os.IsNotExist(err) {
return nil, fmt.Errorf("actions/ directory does not exist")
return nil, errors.New("actions/ directory does not exist")
}

entries, err := os.ReadDir(actionsDir)
Expand Down Expand Up @@ -164,7 +165,7 @@ func validateActionYml(actionPath string) error {
ymlPath := filepath.Join(actionPath, "action.yml")

if _, err := os.Stat(ymlPath); os.IsNotExist(err) {
return fmt.Errorf("action.yml not found")
return errors.New("action.yml not found")
}

content, err := os.ReadFile(ymlPath)
Expand All @@ -187,7 +188,7 @@ func validateActionYml(actionPath string) error {
isComposite := strings.Contains(contentStr, "using: 'composite'") || strings.Contains(contentStr, "using: \"composite\"")

if !isNode20 && !isComposite {
return fmt.Errorf("action must use either 'node20' or 'composite' runtime")
return errors.New("action must use either 'node20' or 'composite' runtime")
}

return nil
Expand All @@ -197,7 +198,7 @@ func validateActionYml(actionPath string) error {
func buildAction(actionsDir, actionName string) error {
actionsBuildLog.Printf("Building action: %s", actionName)

fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("\n📦 Building action: %s", actionName)))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\n📦 Building action: "+actionName))

actionPath := filepath.Join(actionsDir, actionName)

Expand Down Expand Up @@ -249,9 +250,9 @@ func buildAction(actionsDir, actionName string) error {
for _, dep := range dependencies {
if content, ok := sources[dep]; ok {
files[dep] = content
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - %s", dep)))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" - "+dep))
} else {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf(" ⚠ Warning: Could not find %s", dep)))
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(" ⚠ Warning: Could not find "+dep))
}
}

Expand All @@ -275,7 +276,7 @@ func buildAction(actionsDir, actionName string) error {
return fmt.Errorf("failed to write output file: %w", err)
}

fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✓ Built %s", outputPath)))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" ✓ Built "+outputPath))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✓ Embedded %d files", len(files))))

return nil
Expand Down
17 changes: 9 additions & 8 deletions pkg/cli/add_command.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cli

import (
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -228,12 +229,12 @@ func AddResolvedWorkflows(workflowStrings []string, resolved *ResolvedWorkflows,
if opts.CreatePR {
// Check if GitHub CLI is available
if !isGHCLIAvailable() {
return nil, fmt.Errorf("GitHub CLI (gh) is required for PR creation but not available")
return nil, errors.New("GitHub CLI (gh) is required for PR creation but not available")
}

// Check if we're in a git repository
if !isGitRepo() {
return nil, fmt.Errorf("not in a git repository - PR creation requires a git repository")
return nil, errors.New("not in a git repository - PR creation requires a git repository")
}

// Check no other changes are present
Expand Down Expand Up @@ -337,7 +338,7 @@ func addWorkflowsWithTracking(workflows []*ResolvedWorkflow, tracker *FileTracke
// Create commit message
var commitMessage string
if len(workflows) == 1 {
commitMessage = fmt.Sprintf("chore: add workflow %s", workflows[0].Spec.WorkflowName)
commitMessage = "chore: add workflow " + workflows[0].Spec.WorkflowName
} else {
commitMessage = fmt.Sprintf("chore: add %d workflows", len(workflows))
}
Expand Down Expand Up @@ -380,7 +381,7 @@ func addWorkflowWithTracking(resolved *ResolvedWorkflow, tracker *FileTracker, o
sourceInfo := resolved.SourceInfo

if opts.Verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Adding workflow: %s", workflowSpec.String())))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Adding workflow: "+workflowSpec.String()))
if opts.Force {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Force flag enabled: will overwrite existing files"))
}
Expand Down Expand Up @@ -479,7 +480,7 @@ func addWorkflowWithTracking(resolved *ResolvedWorkflow, tracker *FileTracker, o
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Destination file '%s' already exists, skipping.", destFile)))
return nil
}
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Overwriting existing file: %s", destFile)))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Overwriting existing file: "+destFile))
}

content := string(sourceContent)
Expand Down Expand Up @@ -548,7 +549,7 @@ func addWorkflowWithTracking(resolved *ResolvedWorkflow, tracker *FileTracker, o
} else {
content = updatedContent
if opts.Verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Set stop-after field to: %s", opts.StopAfter)))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Set stop-after field to: "+opts.StopAfter))
}
}
}
Expand All @@ -563,7 +564,7 @@ func addWorkflowWithTracking(resolved *ResolvedWorkflow, tracker *FileTracker, o
} else {
content = updatedContent
if opts.Verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Set engine field to: %s", opts.EngineOverride)))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Set engine field to: "+opts.EngineOverride))
}
}
}
Expand Down Expand Up @@ -592,7 +593,7 @@ func addWorkflowWithTracking(resolved *ResolvedWorkflow, tracker *FileTracker, o

// Show output
if !opts.Quiet {
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Added workflow: %s", destFile)))
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Added workflow: "+destFile))

if description := ExtractWorkflowDescription(content); description != "" {
fmt.Fprintln(os.Stderr, "")
Expand Down
7 changes: 4 additions & 3 deletions pkg/cli/add_interactive_auth.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cli

import (
"errors"
"fmt"
"os"
"strings"
Expand All @@ -27,7 +28,7 @@ func (c *AddInteractiveConfig) checkGitRepository() error {
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" git init"))
fmt.Fprintln(os.Stderr, "")
return fmt.Errorf("not in a git repository")
return errors.New("not in a git repository")
}

// Try to get the repository slug
Expand All @@ -49,7 +50,7 @@ func (c *AddInteractiveConfig) checkGitRepository() error {
Validate(func(s string) error {
parts := strings.Split(s, "/")
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return fmt.Errorf("please enter in format 'owner/repo'")
return errors.New("please enter in format 'owner/repo'")
}
return nil
}),
Expand All @@ -66,7 +67,7 @@ func (c *AddInteractiveConfig) checkGitRepository() error {
c.RepoOverride = repoSlug
}

fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Target repository: %s", repoSlug)))
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Target repository: "+repoSlug))
addInteractiveLog.Printf("Target repository: %s", repoSlug)

// Check if repository is public or private
Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/add_interactive_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func (c *AddInteractiveConfig) selectAIEngineAndKey() error {

// Inform user if workflow specifies an engine
if workflowSpecifiedEngine != "" {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Workflow specifies engine: %s", workflowSpecifiedEngine)))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Workflow specifies engine: "+workflowSpecifiedEngine))
}

// Build engine options with notes about existing secrets and workflow specification
Expand Down Expand Up @@ -124,7 +124,7 @@ func (c *AddInteractiveConfig) selectAIEngineAndKey() error {
}

c.EngineOverride = selectedEngine
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Selected engine: %s", selectedEngine)))
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Selected engine: "+selectedEngine))

return c.collectAPIKey(selectedEngine)
}
Expand Down
Loading